From e7098522b2ad84fd3bba0fcd1deb4bc1a8bef04d Mon Sep 17 00:00:00 2001 From: John Berlin Date: Thu, 4 Oct 2018 13:41:48 -0400 Subject: [PATCH] Added window.Text override to wombat.js to account for css in JS (#382) frameworks that like to append a single text node as a child to a style node modifying and then only modify that text node to add/remove css dynamically via: - initTextNodeOverrides (entry point) - overrideTextProtoFunction (overrides the appendData, insertData, and replaceData functions of inherited by Text) - overrideTextProtoGetSet (overrides property getters and setters of data and wholeText) Added window.CSSStyleSheet.insertRule override - dynamically adds a raw css rule (text) to an existing stylesheet --- pywb/static/wombat.js | 95 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 87 insertions(+), 8 deletions(-) diff --git a/pywb/static/wombat.js b/pywb/static/wombat.js index 0d11050c..4b0ba4b4 100644 --- a/pywb/static/wombat.js +++ b/pywb/static/wombat.js @@ -1617,12 +1617,12 @@ var _WBWombat = function($wbwindow, wbinfo) { } //============================================ + function style_replacer(match, n1, n2, n3, offset, string) { + return n1 + rewrite_url(n2) + n3; + } + function rewrite_style(value) { - function style_replacer(match, n1, n2, n3, offset, string) { - return n1 + rewrite_url(n2) + n3; - } - if (!value) { return value; } @@ -2118,8 +2118,17 @@ var _WBWombat = function($wbwindow, wbinfo) { override_style_attr(style_proto, "borderImageSource", "border-image-source"); override_style_setProp(style_proto); + + if ($wbwindow.CSSStyleSheet && $wbwindow.CSSStyleSheet.prototype) { + // https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet/insertRule + // ruleText is a string of raw css.... + var oInsertRule = $wbwindow.CSSStyleSheet.prototype.insertRule; + $wbwindow.CSSStyleSheet.prototype.insertRule = function (ruleText, index) { + return oInsertRule.call(this, rewrite_style(ruleText), index); + }; + } } - + //============================================ function override_style_setProp(style_proto) { var orig_setProp = style_proto.setProperty; @@ -2329,7 +2338,75 @@ var _WBWombat = function($wbwindow, wbinfo) { Object.defineProperty($wbwindow.FontFace.prototype, "constructor", {value: $wbwindow.FontFace}); $wbwindow.FontFace.__wboverriden__ = true; } - + + //============================================ + function overrideTextProtoGetSet(textProto, whichProp) { + var orig_getter = get_orig_getter(textProto, whichProp); + var setter; + // data, from CharacterData, is both readable and writable whereas wholeText, from Text, is not + if (whichProp === 'data') { + var orig_setter = get_orig_setter(textProto, whichProp); + setter = function (orig) { + var res = orig; + if (!this._no_rewrite && this.parentElement && this.parentElement.tagName === 'STYLE') { + res = rewrite_style(orig); + } + return orig_setter.call(this, res); + }; + } + var getter = function () { + var res = orig_getter.call(this); + if (!this._no_rewrite && this.parentElement && this.parentElement.tagName === 'STYLE') { + res = res.replace(wb_unrewrite_rx, ""); + } + return res; + }; + def_prop(textProto, whichProp, setter, getter); + } + + function overrideTextProtoFunction(textProto, whichFN) { + var original = textProto[whichFN]; + textProto[whichFN] = function () { + var args; + if (arguments.length > 0 && this.parentElement && this.parentElement.tagName === 'STYLE') { + // appendData(DOMString data); dataIndex = 0 + // insertData(unsigned long offset, DOMString data); dataIndex = 1 + // replaceData(unsigned long offset, unsigned long count, DOMString data); dataIndex = 2 + args = new Array(arguments.length); + var dataIndex = arguments.length - 1; + if (dataIndex === 2) { + args[0] = arguments[0]; + args[1] = arguments[1]; + } else if (dataIndex === 1) { + args[0] = arguments[0]; + } + args[dataIndex] = rewrite_style(arguments[dataIndex]); + } else { + args = arguments; + } + if (original.__WB_orig_apply) { + return original.__WB_orig_apply(this, args); + } + return original.apply(this, args); + }; + } + + function initTextNodeOverrides($wbwindow) { + if (!$wbwindow.Text || !$wbwindow.Text.prototype) return; + // https://dom.spec.whatwg.org/#characterdata and https://dom.spec.whatwg.org/#interface-text + // depending on the JS frameworks used some pages include JS that will append a single text node child + // to a style tag and then progressively modify that text nodes data for changing the css values that + // style tag contains + var textProto = $wbwindow.Text.prototype; + // override inherited CharacterData functions + overrideTextProtoFunction(textProto, 'appendData'); + overrideTextProtoFunction(textProto, 'insertData'); + overrideTextProtoFunction(textProto, 'replaceData'); + // override property getters and setters + overrideTextProtoGetSet(textProto, 'data'); + overrideTextProtoGetSet(textProto, 'wholeText'); + } + //============================================ function init_wombat_loc(win) { @@ -3662,8 +3739,10 @@ var _WBWombat = function($wbwindow, wbinfo) { init_web_worker_override(); init_service_worker_override(); initSharedWorkerOverride(); - - + + // text node overrides for js frameworks doing funky things with CSS + initTextNodeOverrides($wbwindow); + // innerHTML can be overriden on prototype! override_html_assign($wbwindow.HTMLElement, "innerHTML", true); override_html_assign($wbwindow.HTMLElement, "outerHTML", true);