1
0
mirror of https://github.com/webrecorder/pywb.git synced 2025-03-26 15:59:23 +01:00
pywb/wombat/src/wombat.js

5665 lines
166 KiB
JavaScript
Executable File

/* eslint-disable camelcase */
import FuncMap from './funcMap';
import Storage from './customStorage';
import WombatLocation from './wombatLocation';
import AutoFetchWorker from './autoFetchWorker';
import { wrapSameOriginEventListener, wrapEventListener } from './listeners';
import { addToStringTagToClass, autobind } from './wombatUtils';
/**
* @param {Window} $wbwindow
* @param {Object} wbinfo
*/
function Wombat($wbwindow, wbinfo) {
if (!(this instanceof Wombat)) return new Wombat($wbwindow, wbinfo);
/** @type {boolean} */
this.actual = false;
/** @type {boolean} */
this.debug_rw = false;
/** @type {Window} */
this.$wbwindow = $wbwindow;
/** @type {string} */
this.HTTP_PREFIX = 'http://';
/** @type {string} */
this.HTTPS_PREFIX = 'https://';
/** @type {string} */
this.REL_PREFIX = '//';
/** @type {Array<string>} */
this.VALID_PREFIXES = [this.HTTP_PREFIX, this.HTTPS_PREFIX, this.REL_PREFIX];
/** @type {Array<string>} */
this.IGNORE_PREFIXES = [
'#',
'about:',
'data:',
'mailto:',
'javascript:',
'{',
'*'
];
/** @type {function(qualifiedName: string, value: string): void} */
this.wb_setAttribute = $wbwindow.Element.prototype.setAttribute;
/** @type {function(qualifiedName: string): ?string} */
this.wb_getAttribute = $wbwindow.Element.prototype.getAttribute;
/** @type {function(): string} */
this.wb_funToString = Function.prototype.toString;
/** @type {AutoFetchWorker} */
this.WBAutoFetchWorker = null;
/** @type {boolean} */
this.wbUseAFWorker =
wbinfo.enable_auto_fetch && $wbwindow.Worker != null && wbinfo.is_live;
/** @type {string} */
this.wb_rel_prefix = '';
/** @type {boolean} */
this.wb_wombat_updating = false;
/** @type {FuncMap} */
this.message_listeners = new FuncMap();
/** @type {FuncMap} */
this.storage_listeners = new FuncMap();
/**
* rewrite modifiers for <link href="URL" rel="import|preload" as="x">
* expressed as as-value -> modifier
* @type {Object}
*/
this.linkAsTypes = {
script: 'js_',
worker: 'js_',
style: 'cs_',
image: 'im_',
document: 'if_',
fetch: 'mp_',
font: 'oe_',
audio: 'oe_',
video: 'oe_',
embed: 'oe_',
object: 'oe_',
track: 'oe_',
// the following cover the default case
'': 'mp_',
null: 'mp_',
undefined: 'mp_'
};
/**
* rewrite modifiers for <link href="URL" rel="x"> and or
* <link href="URL" rel="x" as="y"> expressed as a mapping of
* rel -> modifier or rel -> as -> modifier
* @type {Object}
*/
this.linkTagMods = {
linkRelToAs: {
import: this.linkAsTypes,
preload: this.linkAsTypes
},
stylesheet: 'cs_',
// the following cover the default case
null: 'mp_',
undefined: 'mp_',
'': 'mp_'
};
/**
* pre-computed modifiers for each tag
* @type {Object}
*/
this.tagToMod = {
A: { href: 'mp_' },
AREA: { href: 'mp_' },
AUDIO: { src: 'oe_', poster: 'im_' },
BASE: { href: 'mp_' },
EMBED: { src: 'oe_' },
FORM: { action: 'mp_' },
FRAME: { src: 'fr_' },
IFRAME: { src: 'if_' },
IMAGE: { href: 'im_', 'xlink:href': 'im_' },
IMG: { src: 'im_', srcset: 'im_' },
INPUT: { src: 'oe_' },
INS: { cite: 'mp_' },
META: { content: 'mp_' },
OBJECT: { data: 'oe_', codebase: 'oe_' },
Q: { cite: 'mp_' },
// covers both HTML and SVG script element,
SCRIPT: { src: 'js_', 'xlink:href': 'js_' },
SOURCE: { src: 'oe_', srcset: 'oe_' },
TRACK: { src: 'oe_' },
VIDEO: { src: 'oe_', poster: 'im_' },
image: { href: 'im_', 'xlink:href': 'im_' }
};
/** @type {Array<string>} */
this.URL_PROPS = [
'href',
'hash',
'pathname',
'host',
'hostname',
'protocol',
'origin',
'search',
'port'
];
/** @type {Object} */
this.wb_info = wbinfo;
/**
* custom options
* @type {Object}
*/
this.wb_opts = wbinfo.wombat_opts;
/** @type {string} */
this.wb_replay_prefix = wbinfo.prefix;
/** @type {boolean} */
this.wb_is_proxy = this.wb_info.proxy_magic || !this.wb_replay_prefix;
/** @type {string} */
this.wb_info.top_host = this.wb_info.top_host || '*';
/** @type {string} */
this.wb_curr_host =
$wbwindow.location.protocol + '//' + $wbwindow.location.host;
/** @type {Object} */
this.wb_info.wombat_opts = this.wb_info.wombat_opts || {};
/** @type {string} */
this.wb_orig_scheme = this.wb_info.wombat_scheme + '://';
/** @type {string} */
this.wb_orig_origin = this.wb_orig_scheme + this.wb_info.wombat_host;
/** @type {string} */
this.wb_abs_prefix = this.wb_replay_prefix;
/** @type {string} */
this.wb_capture_date_part = '';
if (!this.wb_info.is_live && this.wb_info.wombat_ts) {
this.wb_capture_date_part = '/' + this.wb_info.wombat_ts + '/';
}
/** @type {Array<string>} */
this.BAD_PREFIXES = [
'http:' + this.wb_replay_prefix,
'https:' + this.wb_replay_prefix,
'http:/' + this.wb_replay_prefix,
'https:/' + this.wb_replay_prefix
];
/** @type {RegExp} */
this.hostnamePortRe = /^[\w-]+(\.[\w-_]+)+(:\d+)(\/|$)/;
/** @type {RegExp} */
this.ipPortRe = /^\d+\.\d+\.\d+\.\d+(:\d+)?(\/|$)/;
/** @type {RegExp} */
this.workerBlobRe = /__WB_pmw\(.*?\)\.(?=postMessage\()/g;
/** @type {RegExp} */
this.rmCheckThisInjectRe = /_____WB\$wombat\$check\$this\$function_____\(.*?\)/g;
/** @type {RegExp} */
this.STYLE_REGEX = /(url\s*\(\s*[\\"']*)([^)'"]+)([\\"']*\s*\))/gi;
/** @type {RegExp} */
this.IMPORT_REGEX = /(@import\s*[\\"']*)([^)'";]+)([\\"']*\s*;?)/gi;
/** @type {RegExp} */
this.no_wombatRe = /WB_wombat_/g;
/** @type {RegExp} */
this.srcsetRe = /\s*(\S*\s+[\d.]+[wx]),|(?:\s*,(?:\s+|(?=https?:)))/;
/** @type {RegExp} */
this.cookie_path_regex = /\bPath='?"?([^;'"\s]+)/i;
/** @type {RegExp} */
this.cookie_domain_regex = /\bDomain=([^;'"\s]+)/i;
/** @type {RegExp} */
this.cookie_expires_regex = /\bExpires=([^;'"]+)/gi;
/** @type {RegExp} */
this.SetCookieRe = /,(?![|])/;
/** @type {RegExp} */
this.IP_RX = /^(\d)+\.(\d)+\.(\d)+\.(\d)+$/;
/** @type {RegExp} */
this.FullHTMLRegex = /^\s*<(?:html|head|body|!doctype html)/i;
/** @type {RegExp} */
this.DotPostMessageRe = /(.postMessage\s*\()/;
/** @type {RegExp} */
this.extractPageUnderModiferRE = /\/(?:[0-9]{14})?([a-z]{2, 3}_)\//;
/** @type {string} */
this.write_buff = '';
var eTargetProto = ($wbwindow.EventTarget || {}).prototype;
/** @type {Object} */
this.utilFns = {
cspViolationListener: function(e) {
console.group('CSP Violation');
console.log('Replayed Page URL', window.WB_wombat_location.href);
console.log('The documentURI', e.documentURI);
console.log('The blocked URL', e.blockedURI);
console.log('The directive violated', e.violatedDirective);
console.log('Our policy', e.originalPolicy);
if (e.sourceFile) {
var fileInfo = 'File: ' + e.sourceFile;
if (e.lineNumber && e.columnNumber) {
fileInfo += ' @ ' + e.lineNumber + ':' + e.columnNumber;
} else if (e.lineNumber) {
fileInfo += ' @ ' + e.lineNumber;
}
console.log(fileInfo);
}
console.groupEnd();
},
addEventListener: eTargetProto.addEventListener,
removeEventListener: eTargetProto.removeEventListener,
wbSheetMediaQChecker: null,
XHRopen: null
};
/**
* @type {{yesNo: boolean, added: boolean}}
*/
this.showCSPViolations = { yesNo: false, added: false };
autobind(this);
// this._addRemoveCSPViolationListener(true);
}
/**
* Performs the initialization of wombat's internals:
* - {@link initTopFrame}
* - {@link initWombatLoc}
* - {@link initWombatTop}
* - {@link initAutoFetchWorker}
* - initializes the wb_rel_prefix property
* - initializes the wb_unrewrite_rx property
* - if we are in framed replay mode and the wb_info mod is not bn_
* {@link initTopFrameNotify} is called
* @private
*/
Wombat.prototype._internalInit = function() {
this.initTopFrame(this.$wbwindow);
this.initWombatLoc(this.$wbwindow);
this.initWombatTop(this.$wbwindow);
// updated wb_unrewrite_rx for imgur.com
var wb_origin = this.$wbwindow.__WB_replay_top.location.origin;
var wb_host = this.$wbwindow.__WB_replay_top.location.host;
var wb_proto = this.$wbwindow.__WB_replay_top.location.protocol;
if (this.wb_replay_prefix && this.wb_replay_prefix.indexOf(wb_origin) === 0) {
this.wb_rel_prefix = this.wb_replay_prefix.substring(wb_origin.length);
} else {
this.wb_rel_prefix = this.wb_replay_prefix;
}
// make the protocol and host optional now
var rx =
'((' + wb_proto + ')?//' + wb_host + ')?' + this.wb_rel_prefix + '[^/]+/';
this.wb_unrewrite_rx = new RegExp(rx, 'g');
if (this.wb_info.is_framed && this.wb_info.mod !== 'bn_') {
this.initTopFrameNotify(this.wb_info);
}
this.initAutoFetchWorker();
};
/**
* Internal function that adds a "securitypolicyviolation" event listener
* to the document that will log any CSP violations in a nicer way than
* is the default
*
* If the yesNo argument is true, the event listener is added, otherwise
* it is removed
* @param {boolean} yesNo
* @private
*/
Wombat.prototype._addRemoveCSPViolationListener = function(yesNo) {
this.showCSPViolations.yesNo = yesNo;
if (this.showCSPViolations.yesNo && !this.showCSPViolations.added) {
this.showCSPViolations.added = true;
this._addEventListener(
document,
'securitypolicyviolation',
this.utilFns.cspViolationListener
);
} else {
this.showCSPViolations.added = false;
this._removeEventListener(
document,
'securitypolicyviolation',
this.utilFns.cspViolationListener
);
}
};
/**
* Adds the supplied event listener on the supplied event target
* @param {Object} obj
* @param {string} event
* @param {Function} fun
* @return {*}
* @private
*/
Wombat.prototype._addEventListener = function(obj, event, fun) {
if (this.utilFns.addEventListener) {
return this.utilFns.addEventListener.call(obj, event, fun);
}
obj.addEventListener(event, fun);
};
/**
* Removes the supplied event listener on the supplied event target
* @param {Object} obj
* @param {string} event
* @param {Function} fun
* @return {*}
* @private
*/
Wombat.prototype._removeEventListener = function(obj, event, fun) {
if (this.utilFns.removeEventListener) {
return this.utilFns.removeEventListener.call(obj, event, fun);
}
obj.removeEventListener(event, fun);
};
/**
* Extracts the modifier (i.e. mp\_, if\_, ...) the page is under that wombat is
* operating in. If extracting the modifier fails for some reason mp\_ is returned.
* Used to ensure the correct modifier is used for rewriting the service workers scope.
* @return {string}
*/
Wombat.prototype.getPageUnderModifier = function() {
try {
var pageUnderModifier = this.extractPageUnderModiferRE.exec(
location.pathname
);
if (pageUnderModifier && pageUnderModifier[1]) {
var mod = pageUnderModifier[1].trim();
return mod || 'mp_';
}
} catch (e) {}
return 'mp_';
};
/**
* Returns T/F indicating if the supplied function is a native function
* or not. The test checks for the presence of the substring `'[native code]'`
* in the result of calling `toString` on the function
* @param {Function} funToTest - The function to be tested
* @return {boolean}
*/
Wombat.prototype.isNativeFunction = function(funToTest) {
if (!funToTest) return false;
return this.wb_funToString.call(funToTest).indexOf('[native code]') >= 0;
};
/**
* Returns T/F indicating if the supplied element may have attributes that
* are auto-fetched
* @param {Element} elem
* @return {boolean}
*/
Wombat.prototype.isSavedSrcSrcset = function(elem) {
switch (elem.tagName) {
case 'IMG':
case 'VIDEO':
case 'AUDIO':
return true;
case 'SOURCE':
if (!elem.parentElement) return false;
switch (elem.parentElement.tagName) {
case 'PICTURE':
case 'VIDEO':
case 'AUDIO':
return true;
default:
return false;
}
default:
return false;
}
};
/**
* Returns T/F indicating if the supplied element is an Image element that
* may have srcset values to be sent to the backing auto-fetch worker
* @param {Element} elem
* @return {boolean}
*/
Wombat.prototype.isSavedDataSrcSrcset = function(elem) {
if (elem.dataset && elem.dataset.srcset != null) {
return this.isSavedSrcSrcset(elem);
}
return false;
};
/**
* Determines if the supplied string is an host URL
* @param {string} str
* @return {boolean}
*/
Wombat.prototype.isHostUrl = function(str) {
// Good guess that's its a hostname
if (str.indexOf('www.') === 0) {
return true;
}
// hostname:port (port required)
var matches = str.match(this.hostnamePortRe);
if (matches && matches[0].length < 64) {
return true;
}
// ip:port
matches = str.match(this.ipPortRe);
if (matches) {
return matches[0].length < 64;
}
return false;
};
/**
* Returns T/F indicating if the supplied object is the arguments object
* @param {*} maybeArgumentsObj
* @return {boolean}
*/
Wombat.prototype.isArgumentsObj = function(maybeArgumentsObj) {
if (
!maybeArgumentsObj ||
!(typeof maybeArgumentsObj.toString === 'function')
) {
return false;
}
return maybeArgumentsObj.toString() === '[object Arguments]';
};
/**
* Ensures that each element in the supplied arguments object or
* array is deproxied handling cases where we can not modify the
* supplied object returning a new or modified object with the
* exect elements/properties
* @param {*} maybeArgumentsObj
* @return {*}
*/
Wombat.prototype.deproxyArrayHandlingArgumentsObj = function(
maybeArgumentsObj
) {
if (
!maybeArgumentsObj ||
maybeArgumentsObj instanceof NodeList ||
maybeArgumentsObj.length == 0
) {
return maybeArgumentsObj;
}
var args = this.isArgumentsObj(maybeArgumentsObj)
? new Array(maybeArgumentsObj.length)
: maybeArgumentsObj;
for (var i = 0; i < maybeArgumentsObj.length; ++i) {
args[i] = this.proxyToObj(maybeArgumentsObj[i]);
}
return args;
};
/**
* Determines if a string starts with the supplied prefix.
* If it does the matching prefix is returned otherwise undefined.
* @param {?string} string
* @param {string} prefix
* @return {?string}
*/
Wombat.prototype.startsWith = function(string, prefix) {
if (!string) return undefined;
return string.indexOf(prefix) === 0 ? prefix : undefined;
};
/**
* Determines if a string starts with the supplied array of prefixes.
* If it does the matching prefix is returned otherwise undefined.
* @param {?string} string
* @param {Array<string>} prefixes
* @return {?string}
*/
Wombat.prototype.startsWithOneOf = function(string, prefixes) {
if (!string) return undefined;
for (var i = 0; i < prefixes.length; i++) {
if (string.indexOf(prefixes[i]) === 0) {
return prefixes[i];
}
}
return undefined;
};
/**
* Determines if a string ends with the supplied suffix.
* If it does the suffix is returned otherwise undefined.
* @param {?string} str
* @param {string} suffix
* @return {?string}
*/
Wombat.prototype.endsWith = function(str, suffix) {
if (!str) return undefined;
if (str.indexOf(suffix, str.length - suffix.length) !== -1) {
return suffix;
}
return undefined;
};
/**
* Returns T/F indicating if the supplied tag name and attribute name
* combination are to be rewritten
* @param {string} tagName
* @param {string} attr
* @return {boolean}
*/
Wombat.prototype.shouldRewriteAttr = function(tagName, attr) {
switch (attr) {
case 'href':
case 'src':
case 'xlink:href':
return true;
}
if (
tagName &&
this.tagToMod[tagName] &&
this.tagToMod[tagName][attr] !== undefined
) {
return true;
}
return (
(tagName === 'VIDEO' && attr === 'poster') ||
(tagName === 'META' && attr === 'content')
);
};
/**
* Returns T/F indicating if the supplied DOM Node has child Elements/Nodes.
* Note this function should be used when the Node(s) being considered can
* be null/undefined.
* @param {Node} node
* @return {boolean}
*/
Wombat.prototype.nodeHasChildren = function(node) {
if (!node) return false;
if (typeof node.hasChildNodes === 'function') return node.hasChildNodes();
var kids = node.children || node.childNodes;
if (kids) return kids.length > 0;
return false;
};
/**
* Returns the correct rewrite modifier for the supplied element and
* attribute combination if one exists otherwise mp_.
* Used by
* - {@link performAttributeRewrite}
* - {@link rewriteFrameSrc}
* - {@link initElementGetSetAttributeOverride}
* - {@link overrideHrefAttr}
*
* @param {*} elem
* @param {string} attrName
* @return {?string}
*/
Wombat.prototype.rwModForElement = function(elem, attrName) {
if (!elem) return undefined;
// the default modifier, if none is supplied to rewrite_url, is mp_
var mod = 'mp_';
if (elem.tagName === 'LINK' && attrName === 'href') {
// link types are always ASCII case-insensitive, and must be compared as such.
// https://html.spec.whatwg.org/multipage/links.html#linkTypes
if (elem.rel) {
var relV = elem.rel.trim().toLowerCase();
var asV = this.wb_getAttribute.call(elem, 'as');
if (asV && this.linkTagMods.linkRelToAs[relV] != null) {
var asMods = this.linkTagMods.linkRelToAs[relV];
mod = asMods[asV.toLowerCase()];
} else if (this.linkTagMods[relV] != null) {
mod = this.linkTagMods[relV];
}
}
} else {
// check if this element has an rewrite modifiers and set mod to it if it does
var maybeMod = this.tagToMod[elem.tagName];
if (maybeMod != null) {
mod = maybeMod[attrName];
}
}
return mod;
};
/**
* If the supplied element is a script tag and has the server-side rewrite added
* property "__wb_orig_src" it is removed and the "__$removedWBOSRC$__" property
* is added to element as an internal flag indicating no further checks are to be
* made.
*
* See also {@link retrieveWBOSRC}
* @param {Element} elem
*/
Wombat.prototype.removeWBOSRC = function(elem) {
if (elem.tagName === 'SCRIPT' && !elem.__$removedWBOSRC$__) {
if (elem.hasAttribute('__wb_orig_src')) {
elem.removeAttribute('__wb_orig_src');
}
elem.__$removedWBOSRC$__ = true;
}
};
/**
* If the supplied element is a script tag and has the server-side rewrite added
* property "__wb_orig_src" its value is returned otherwise undefined is returned.
* If the element did not have the "__wb_orig_src" property the
* "__$removedWBOSRC$__" property is added to element as an internal flag
* indicating no further checks are to be made.
*
* See also {@link removeWBOSRC}
* @param {Element} elem
* @return {?string}
*/
Wombat.prototype.retrieveWBOSRC = function(elem) {
if (elem.tagName === 'SCRIPT' && !elem.__$removedWBOSRC$__) {
var maybeWBOSRC;
if (this.wb_getAttribute) {
maybeWBOSRC = this.wb_getAttribute.call(elem, '__wb_orig_src');
} else {
maybeWBOSRC = elem.getAttribute('__wb_orig_src');
}
if (maybeWBOSRC == null) elem.__$removedWBOSRC$__ = true;
return maybeWBOSRC;
}
return undefined;
};
/**
* Wraps the supplied text contents of a script tag with the required Wombat setup
* @param {?string} scriptText
* @return {string}
*/
Wombat.prototype.wrapScriptTextJsProxy = function(scriptText) {
return (
'var _____WB$wombat$assign$function_____ = function(name) {return (self._wb_wombat && ' +
'self._wb_wombat.local_init &&self._wb_wombat.local_init(name)) || self[name]; };\n' +
'if (!self.__WB_pmw) { self.__WB_pmw = function(obj) { return obj; } }\n{\n' +
'let window = _____WB$wombat$assign$function_____("window");\n' +
'let self = _____WB$wombat$assign$function_____("self");\n' +
'let document = _____WB$wombat$assign$function_____("document");\n' +
'let location = _____WB$wombat$assign$function_____("location");\n' +
'let top = _____WB$wombat$assign$function_____("top");\n' +
'let parent = _____WB$wombat$assign$function_____("parent");\n' +
'let frames = _____WB$wombat$assign$function_____("frames");\n' +
'let opener = _____WB$wombat$assign$function_____("opener");\n' +
scriptText +
'\n\n}'
);
};
/**
* Calls the supplied function when the supplied element undergoes mutations
* @param elem
* @param func
* @return {boolean}
*/
Wombat.prototype.watchElem = function(elem, func) {
if (!this.$wbwindow.MutationObserver) {
return false;
}
var m = new this.$wbwindow.MutationObserver(function(records, observer) {
for (var i = 0; i < records.length; i++) {
var r = records[i];
if (r.type === 'childList') {
for (var j = 0; j < r.addedNodes.length; j++) {
func(r.addedNodes[j]);
}
}
}
});
m.observe(elem, {
childList: true,
subtree: true
});
};
/**
* Reconstructs the doctype string if the supplied doctype object
* is non null/undefined. This function is used by {@link rewriteHtmlFull}
* in order to ensure correctness of rewriting full string of HTML that
* started with <!doctype ...> since the innerHTML and outerHTML properties
* do not include that.
* @param {DocumentType} doctype
* @return {string}
*/
Wombat.prototype.reconstructDocType = function(doctype) {
if (doctype == null) return '';
return (
'<!doctype ' +
doctype.name +
(doctype.publicId ? ' PUBLIC "' + doctype.publicId + '"' : '') +
(!doctype.publicId && doctype.systemId ? ' SYSTEM' : '') +
(doctype.systemId ? ' "' + doctype.systemId + '"' : '') +
'>'
);
};
/**
* Constructs the final URL for the URL rewriting process
* @param {boolean} useRel
* @param {string} mod
* @param {string} url
* @return {string}
*/
Wombat.prototype.getFinalUrl = function(useRel, mod, url) {
var prefix = useRel ? this.wb_rel_prefix : this.wb_abs_prefix;
if (mod == null) {
mod = this.wb_info.mod;
}
// if live, don't add the timestamp
if (!this.wb_info.is_live) {
prefix += this.wb_info.wombat_ts;
}
prefix += mod;
if (prefix[prefix.length - 1] !== '/') {
prefix += '/';
}
return prefix + url;
};
/**
* Converts the supplied relative URL to an absolute URL using an A tag
* @param {string} url
* @param {?Document} doc
* @return {string}
*/
Wombat.prototype.resolveRelUrl = function(url, doc) {
var docObj = doc || this.$wbwindow.document;
var parser = this.makeParser(docObj.baseURI, docObj);
var hash = parser.href.lastIndexOf('#');
var href = hash >= 0 ? parser.href.substring(0, hash) : parser.href;
var lastslash = href.lastIndexOf('/');
if (lastslash >= 0 && lastslash !== href.length - 1) {
parser.href = href.substring(0, lastslash + 1) + url;
} else {
parser.href = href + url;
}
return parser.href;
};
/**
* Extracts the original URL from the supplied rewritten URL
* @param {?string} rewrittenUrl
* @return {string}
*/
Wombat.prototype.extractOriginalURL = function(rewrittenUrl) {
if (!rewrittenUrl) {
return '';
} else if (this.wb_is_proxy) {
// proxy mode: no extraction needed
return rewrittenUrl;
}
var rwURLString = rewrittenUrl.toString();
var url = rwURLString;
// ignore certain urls
if (this.startsWithOneOf(url, this.IGNORE_PREFIXES)) {
return url;
}
// if no coll, start from beginning, otherwise could be part of coll..
var start = this.wb_rel_prefix ? 1 : 0;
var index = url.indexOf('/http', start);
if (index < 0) {
index = url.indexOf('///', start);
}
// extract original url from wburl
if (index >= 0) {
url = url.substr(index + 1);
} else {
index = url.indexOf(this.wb_replay_prefix);
if (index >= 0) {
url = url.substr(index + this.wb_replay_prefix.length);
}
if (url.length > 4 && url.charAt(2) === '_' && url.charAt(3) === '/') {
url = url.substr(4);
}
if (
url !== rwURLString &&
!this.startsWithOneOf(url, this.VALID_PREFIXES)
) {
url = this.wb_orig_scheme + url;
}
}
if (
rwURLString.charAt(0) === '/' &&
rwURLString.charAt(1) !== '/' &&
this.startsWith(url, this.wb_orig_origin)
) {
url = url.substr(this.wb_orig_origin.length);
}
if (this.startsWith(url, this.REL_PREFIX)) {
return this.wb_info.wombat_scheme + ':' + url;
}
return url;
};
/**
* Creates and returns an A tag ready for parsing the original URL
* part of the supplied URL.
* @param {string} maybeRewrittenURL
* @param {?Document} doc
* @return {HTMLAnchorElement}
*/
Wombat.prototype.makeParser = function(maybeRewrittenURL, doc) {
var originalURL = this.extractOriginalURL(maybeRewrittenURL);
var docElem = doc;
if (!doc) {
// special case: for newly opened blank windows, use the opener
// to create parser to have the proper baseURI
if (
this.$wbwindow.location.href === 'about:blank' &&
this.$wbwindow.opener
) {
docElem = this.$wbwindow.opener.document;
} else {
docElem = this.$wbwindow.document;
}
}
var p = docElem.createElement('a');
p._no_rewrite = true;
p.href = originalURL;
return p;
};
/**
* Defines a new getter and optional setter for the property on the supplied
* object returning T/F to indicate if the new property was successfully defined
* @param {Object} obj
* @param {string} prop
* @param {?function(value: *): *} setFunc
* @param {function(): *} getFunc
* @param {?boolean} [enumerable]
* @return {boolean}
*/
Wombat.prototype.defProp = function(obj, prop, setFunc, getFunc, enumerable) {
// if the property is marked as non-configurable in the current
// browser, skip the override
var existingDescriptor = Object.getOwnPropertyDescriptor(obj, prop);
if (existingDescriptor && !existingDescriptor.configurable) {
return false;
}
// if no getter function was supplied, skip the override.
// See https://github.com/webrecorder/pywb/issues/147 for context
if (!getFunc) {
return false;
}
var descriptor = {
configurable: true,
enumerable: enumerable || false,
get: getFunc
};
if (setFunc) {
descriptor.set = setFunc;
}
try {
Object.defineProperty(obj, prop, descriptor);
return true;
} catch (e) {
console.warn('Failed to redefine property %s', prop, e.message);
return false;
}
};
/**
* Defines a new getter for the property on the supplied object returning
* T/F to indicate if the new property was successfully defined
* @param {Object} obj
* @param {string} prop
* @param {function(): *} getFunc
* @param {?boolean} [enumerable]
* @return {boolean}
*/
Wombat.prototype.defGetterProp = function(obj, prop, getFunc, enumerable) {
var existingDescriptor = Object.getOwnPropertyDescriptor(obj, prop);
if (existingDescriptor && !existingDescriptor.configurable) {
return false;
}
// if no getter function was supplied, skip the override.
// See https://github.com/webrecorder/pywb/issues/147 for context
if (!getFunc) return false;
try {
Object.defineProperty(obj, prop, {
configurable: true,
enumerable: enumerable || false,
get: getFunc
});
return true;
} catch (e) {
console.warn('Failed to redefine property %s', prop, e.message);
return false;
}
};
/**
* Returns the original getter function for the supplied object's property
* @param {Object} obj
* @param {string} prop
* @return {function(): *}
*/
Wombat.prototype.getOrigGetter = function(obj, prop) {
var orig_getter;
if (obj.__lookupGetter__) {
orig_getter = obj.__lookupGetter__(prop);
}
if (!orig_getter && Object.getOwnPropertyDescriptor) {
var props = Object.getOwnPropertyDescriptor(obj, prop);
if (props) {
orig_getter = props.get;
}
}
return orig_getter;
};
/**
* Returns the original setter function for the supplied object's property
* @param {Object} obj
* @param {string} prop
* @return {function(): *}
*/
Wombat.prototype.getOrigSetter = function(obj, prop) {
var orig_setter;
if (obj.__lookupSetter__) {
orig_setter = obj.__lookupSetter__(prop);
}
if (!orig_setter && Object.getOwnPropertyDescriptor) {
var props = Object.getOwnPropertyDescriptor(obj, prop);
if (props) {
orig_setter = props.set;
}
}
return orig_setter;
};
/**
* Returns an array containing the names of all the properties
* that exist on the supplied object
* @param {Object} obj
* @return {Array<string>}
*/
Wombat.prototype.getAllOwnProps = function(obj) {
/** @type {Array<string>} */
var ownProps = [];
var props = Object.getOwnPropertyNames(obj);
var i = 0;
for (; i < props.length; i++) {
var prop = props[i];
try {
if (obj[prop] && !obj[prop].prototype) {
ownProps.push(prop);
}
} catch (e) {}
}
var traverseObj = Object.getPrototypeOf(obj);
while (traverseObj) {
props = Object.getOwnPropertyNames(traverseObj);
for (i = 0; i < props.length; i++) {
ownProps.push(props[i]);
}
traverseObj = Object.getPrototypeOf(traverseObj);
}
return ownProps;
};
/**
* Sends the supplied message to __WB_top_frame
* @param {*} message
* @param {boolean} [skipTopCheck]
*/
Wombat.prototype.sendTopMessage = function(message, skipTopCheck) {
if (!this.$wbwindow.__WB_top_frame) return;
if (!skipTopCheck && this.$wbwindow != this.$wbwindow.__WB_replay_top) {
return;
}
this.$wbwindow.__WB_top_frame.postMessage(message, this.wb_info.top_host);
};
/**
* Notifies __WB_top_frame of an history update
* @param {?string} url
* @param {?string} title
*/
Wombat.prototype.sendHistoryUpdate = function(url, title) {
this.sendTopMessage({
url: url,
ts: this.wb_info.timestamp,
request_ts: this.wb_info.request_ts,
is_live: this.wb_info.is_live,
title: title,
wb_type: 'replace-url'
});
};
/**
* Updates the real location object with the results of rewriting the supplied URL
* @param {?string} reqHref
* @param {string} origHref
* @param {Location} actualLocation
*/
Wombat.prototype.updateLocation = function(reqHref, origHref, actualLocation) {
if (!reqHref || reqHref === origHref) return;
var ext_orig = this.extractOriginalURL(origHref);
var ext_req = this.extractOriginalURL(reqHref);
if (!ext_orig || ext_orig === ext_req) return;
var final_href = this.rewriteUrl(reqHref);
console.log(actualLocation.href + ' -> ' + final_href);
actualLocation.href = final_href;
};
/**
* Updates the real location with a change
* @param {*} wombatLoc
* @param {boolean} isTop
*/
Wombat.prototype.checkLocationChange = function(wombatLoc, isTop) {
var locType = typeof wombatLoc;
var actual_location = isTop
? this.$wbwindow.__WB_replay_top.location
: this.$wbwindow.location;
// String has been assigned to location, so assign it
if (locType === 'string') {
this.updateLocation(wombatLoc, actual_location.href, actual_location);
} else if (locType === 'object') {
this.updateLocation(wombatLoc.href, wombatLoc._orig_href, actual_location);
}
};
/**
* Checks for a location change, either this browser context or top and updates
* accordingly
* @return {boolean}
*/
Wombat.prototype.checkAllLocations = function() {
if (this.wb_wombat_updating) {
return false;
}
this.wb_wombat_updating = true;
this.checkLocationChange(this.$wbwindow.WB_wombat_location, false);
// Only check top if its a different $wbwindow
if (
this.$wbwindow.WB_wombat_location !=
this.$wbwindow.__WB_replay_top.WB_wombat_location
) {
this.checkLocationChange(
this.$wbwindow.__WB_replay_top.WB_wombat_location,
true
);
}
this.wb_wombat_updating = false;
};
/**
* Returns the Object the Proxy was proxying if it exists otherwise
* the original object
* @param {*} source
* @return {?Object}
*/
Wombat.prototype.proxyToObj = function(source) {
if (source) {
try {
var proxyRealObj = source.__WBProxyRealObj__;
if (proxyRealObj) return proxyRealObj;
} catch (e) {}
}
return source;
};
/**
* Returns the Proxy object for the supplied Object if it exists otherwise
* the original object
* @param {?Object} obj
* @return {Proxy|?Object}
*/
Wombat.prototype.objToProxy = function(obj) {
if (obj) {
try {
var maybeWbProxy = obj._WB_wombat_obj_proxy;
if (maybeWbProxy) return maybeWbProxy;
} catch (e) {}
}
return obj;
};
/**
* Returns the value of supplied object that is being Proxied
* @param {*} obj
* @param {*} prop
* @param {Array<string>} ownProps
* @return {*}
*/
Wombat.prototype.defaultProxyGet = function(obj, prop, ownProps) {
switch (prop) {
case '__WBProxyRealObj__':
return obj;
case 'location':
return obj.WB_wombat_location;
case '_WB_wombat_obj_proxy':
return obj._WB_wombat_obj_proxy;
case 'constructor':
// allow tests such as self.constructor === Window to work
// you can't create a new instance of window using its constructor
if (obj.constructor === Window) return obj.constructor;
break;
}
var retVal = obj[prop];
var type = typeof retVal;
if (type === 'function' && ownProps.indexOf(prop) !== -1) {
// certain sites (e.g. facebook) are applying polyfills to native functions
// treating the polyfill as a native function [fn.bind(obj)] causes incorrect execution of the polyfill
// also depending on the site, the site can detect we "tampered" with the polyfill by binding it to obj
// to avoid these situations, we do not bind the returned fn if we detect they were polyfilled
switch (prop) {
case 'requestAnimationFrame':
case 'cancelAnimationFrame': {
if (!this.isNativeFunction(retVal)) {
return retVal;
}
break;
}
}
return retVal.bind(obj);
} else if (type === 'object' && retVal && retVal._WB_wombat_obj_proxy) {
if (retVal instanceof Window) {
this.initNewWindowWombat(retVal);
}
return retVal._WB_wombat_obj_proxy;
}
return retVal;
};
/**
* Set the location properties for either an instance of WombatLocation
* or an anchor tag
* @param {HTMLAnchorElement|WombatLocation} loc
* @param {string} originalURL
*/
Wombat.prototype.setLoc = function(loc, originalURL) {
var parser = this.makeParser(originalURL, loc.ownerDocument);
loc._orig_href = originalURL;
loc._parser = parser;
var href = parser.href;
loc._hash = parser.hash;
loc._href = href;
loc._host = parser.host;
loc._hostname = parser.hostname;
if (parser.origin) {
loc._origin = parser.origin;
} else {
loc._origin =
parser.protocol +
'//' +
parser.hostname +
(parser.port ? ':' + parser.port : '');
}
loc._pathname = parser.pathname;
loc._port = parser.port;
// this.protocol = parser.protocol;
loc._protocol = parser.protocol;
loc._search = parser.search;
if (!Object.defineProperty) {
loc.href = href;
loc.hash = parser.hash;
loc.host = loc._host;
loc.hostname = loc._hostname;
loc.origin = loc._origin;
loc.pathname = loc._pathname;
loc.port = loc._port;
loc.protocol = loc._protocol;
loc.search = loc._search;
}
};
/**
* Returns a function for retrieving some property on an instance of either
* WombatLocation or an anchor tag
* @param {string} prop
* @param {function(): string} origGetter
* @return {function(): string}
*/
Wombat.prototype.makeGetLocProp = function(prop, origGetter) {
var wombat = this;
return function newGetLocProp() {
if (this._no_rewrite) return origGetter.call(this, prop);
var curr_orig_href = origGetter.call(this, 'href');
if (prop === 'href') {
return wombat.extractOriginalURL(curr_orig_href);
}
if (this._orig_href !== curr_orig_href) {
wombat.setLoc(this, curr_orig_href);
}
return this['_' + prop];
};
};
/**
* Returns a function for setting some property on an instance of either
* WombatLocation or an anchor tag
* @param {string} prop
* @param {function (value: *): *} origSetter
* @param {function(): *} origGetter
* @return {function (value: *): *}
*/
Wombat.prototype.makeSetLocProp = function(prop, origSetter, origGetter) {
var wombat = this;
return function newSetLocProp(value) {
if (this._no_rewrite) {
return origSetter.call(this, prop, value);
}
if (this['_' + prop] === value) return;
this['_' + prop] = value;
if (!this._parser) {
var href = origGetter.call(this);
this._parser = wombat.makeParser(href, this.ownerDocument);
}
var rel = false;
// Special case for href="." assignment
if (prop === 'href' && typeof value === 'string') {
if (value) {
if (value[0] === '.') {
value = wombat.resolveRelUrl(value, this.ownerDocument);
} else if (
value[0] === '/' &&
(value.length <= 1 || value[1] !== '/')
) {
rel = true;
value = WB_wombat_location.origin + value;
}
}
}
try {
this._parser[prop] = value;
} catch (e) {
console.log('Error setting ' + prop + ' = ' + value);
}
if (prop === 'hash') {
value = this._parser[prop];
origSetter.call(this, 'hash', value);
} else {
rel = rel || value === this._parser.pathname;
value = wombat.rewriteUrl(this._parser.href, rel);
origSetter.call(this, 'href', value);
}
};
};
/**
* Function used for rewriting URL's contained in CSS style definitions
* @param {Object} match
* @param {string} n1
* @param {string} n2
* @param {string} n3
* @param {number} offset
* @param {string} string
* @return {string}
*/
Wombat.prototype.styleReplacer = function(match, n1, n2, n3, offset, string) {
return n1 + this.rewriteUrl(n2) + n3;
};
/**
* Simple helper function for ensuring that the server side rewriting
* injected functions are present in the supplied window.
*
* They could be absent due to how certain pages use iframes
* @param {Window} win
*/
Wombat.prototype.ensureServerSideInjectsExistOnWindow = function(win) {
if (typeof win._____WB$wombat$check$this$function_____ !== 'function') {
win._____WB$wombat$check$this$function_____ = function(thisObj) {
if (thisObj && thisObj._WB_wombat_obj_proxy)
return thisObj._WB_wombat_obj_proxy;
return thisObj;
};
}
if (typeof win._____WB$wombat$assign$function_____ !== 'function') {
win._____WB$wombat$assign$function_____ = function(name) {
return (
(self._wb_wombat &&
self._wb_wombat.local_init &&
self._wb_wombat.local_init(name)) ||
self[name]
);
};
}
if (typeof !win.__WB_pmw !== 'function') {
win.__WB_pmw = function(obj) {
return obj;
};
}
};
/**
* Rewrites the arguments supplied to an function of the Node interface
* @param {Object} fnThis
* @param {function} originalFn
* @param {Node} newNode
* @param {Node} [oldNode]
*/
Wombat.prototype.rewriteNodeFuncArgs = function(
fnThis,
originalFn,
newNode,
oldNode
) {
if (newNode) {
switch (newNode.nodeType) {
case Node.ELEMENT_NODE:
this.rewriteElemComplete(newNode);
break;
case Node.TEXT_NODE:
if (
newNode.tagName === 'STYLE' ||
(newNode.parentNode && newNode.parentNode.tagName === 'STYLE')
) {
newNode.textContent = this.rewriteStyle(newNode.textContent);
}
break;
case Node.DOCUMENT_FRAGMENT_NODE:
this.recurseRewriteElem(newNode);
break;
}
}
var created = originalFn.call(fnThis, newNode, oldNode);
if (created && created.tagName === 'IFRAME') {
this.initIframeWombat(created);
}
return created;
};
/**
* Mini url rewriter specifically for rewriting web sockets
* @param {?string} originalURL
* @return {string}
*/
Wombat.prototype.rewriteWSURL = function(originalURL) {
// If undefined, just return it
if (!originalURL) return originalURL;
var urltype_ = typeof originalURL;
var url = originalURL;
// If object, use toString
if (urltype_ === 'object') {
url = originalURL.toString();
} else if (urltype_ !== 'string') {
return originalURL;
}
// empty string check
if (!url) return url;
var wsScheme = 'ws://';
var wssScheme = 'wss://';
// proxy mode: If no wb_replay_prefix, only rewrite scheme
// proxy mode: If no wb_replay_prefix, only rewrite scheme
if (this.wb_is_proxy) {
if (
this.wb_orig_scheme === this.HTTP_PREFIX &&
this.startsWith(url, wssScheme)
) {
return wsScheme + url.substr(wssScheme.length);
} else if (
this.wb_orig_scheme === this.HTTPS_PREFIX &&
this.startsWith(url, wsScheme)
) {
return wssScheme + url.substr(wsScheme.length);
} else {
return url;
}
}
var wbSecure = this.wb_abs_prefix.indexOf(this.HTTPS_PREFIX) === 0;
var wbPrefix = this.wb_abs_prefix.replace(
wbSecure ? this.HTTPS_PREFIX : this.HTTP_PREFIX,
wbSecure ? wssScheme : wsScheme
);
wbPrefix += this.wb_info.wombat_ts + 'ws_';
if (url[url.length - 1] !== '/') {
wbPrefix += '/';
}
return wbPrefix + url.replace('WB_wombat_', '');
};
/**
* Rewrites the supplied URL returning the rewritten URL
* @param {?string} originalURL
* @param {?boolean} [useRel]
* @param {?string} [mod]
* @param {?Document} [doc]
* @return {?string}
* @private
*/
Wombat.prototype.rewriteUrl_ = function(originalURL, useRel, mod, doc) {
// If undefined, just return it
if (!originalURL) return originalURL;
var urltype_ = typeof originalURL;
var url;
// If object, use toString
if (urltype_ === 'object') {
url = originalURL.toString();
} else if (urltype_ !== 'string') {
return originalURL;
} else {
url = originalURL;
}
// empty string check
if (!url) return url;
// proxy mode: If no wb_replay_prefix, only rewrite scheme
if (this.wb_is_proxy) {
if (
this.wb_orig_scheme === this.HTTP_PREFIX &&
this.startsWith(url, this.HTTPS_PREFIX)
) {
return this.HTTP_PREFIX + url.substr(this.HTTPS_PREFIX.length);
} else if (
this.wb_orig_scheme === this.HTTPS_PREFIX &&
this.startsWith(url, this.HTTP_PREFIX)
) {
return this.HTTPS_PREFIX + url.substr(this.HTTP_PREFIX.length);
} else {
return url;
}
}
// just in case _wombat reference made it into url!
url = url.replace('WB_wombat_', '');
// ignore anchors, about, data
if (this.startsWithOneOf(url.toLowerCase(), this.IGNORE_PREFIXES)) {
return url;
}
// OPTS: additional ignore prefixes
if (
this.wb_opts.no_rewrite_prefixes &&
this.startsWithOneOf(url, this.wb_opts.no_rewrite_prefixes)
) {
return url;
}
// If starts with prefix, no rewriting needed
// Only check replay prefix (no date) as date may be different for each
// capture
// if scheme relative, prepend current scheme
var check_url;
if (url.indexOf('//') === 0) {
check_url = window.location.protocol + url;
} else {
check_url = url;
}
var originalLoc = this.$wbwindow.location;
if (
this.startsWith(check_url, this.wb_replay_prefix) ||
this.startsWith(check_url, originalLoc.origin + this.wb_replay_prefix)
) {
return url;
}
// A special case where the port somehow gets dropped
// Check for this and add it back in, eg http://localhost/path/ -> http://localhost:8080/path/
if (
originalLoc.host !== originalLoc.hostname &&
this.startsWith(
url,
originalLoc.protocol + '//' + originalLoc.hostname + '/'
)
) {
return url.replace(
'/' + originalLoc.hostname + '/',
'/' + originalLoc.host + '/'
);
}
// If server relative url, add prefix and original host
if (url.charAt(0) === '/' && !this.startsWith(url, this.REL_PREFIX)) {
// Already a relative url, don't make any changes!
if (
this.wb_capture_date_part &&
url.indexOf(this.wb_capture_date_part) >= 0
) {
return url;
}
// relative collection
if (url.indexOf(this.wb_rel_prefix) === 0 && url.indexOf('http') > 1) {
var scheme_sep = url.indexOf(':/');
if (scheme_sep > 0 && url[scheme_sep + 2] !== '/') {
return (
url.substring(0, scheme_sep + 2) + '/' + url.substring(scheme_sep + 2)
);
}
return url;
}
return this.getFinalUrl(true, mod, this.wb_orig_origin + url);
}
// Use a parser
if (url.charAt(0) === '.') {
url = this.resolveRelUrl(url, doc);
}
// If full url starting with http://, https:// or //
// add rewrite prefix, we convert to lower case for this check
// due to the fact that the URL's scheme could be HTTP(S) LUL
var prefix = this.startsWithOneOf(url.toLowerCase(), this.VALID_PREFIXES);
if (prefix) {
var orig_host = this.$wbwindow.__WB_replay_top.location.host;
var orig_protocol = this.$wbwindow.__WB_replay_top.location.protocol;
var prefix_host = prefix + orig_host + '/';
// if already rewritten url, must still check scheme
if (this.startsWith(url, prefix_host)) {
if (this.startsWith(url, this.wb_replay_prefix)) {
return url;
}
var curr_scheme = orig_protocol + '//';
var path = url.substring(prefix_host.length);
var rebuild = false;
if (path.indexOf(this.wb_rel_prefix) < 0 && url.indexOf('/static/') < 0) {
path = this.getFinalUrl(
true,
mod,
WB_wombat_location.origin + '/' + path
);
rebuild = true;
}
// replace scheme to ensure using the correct server scheme
// if (starts_with(url, wb_orig_scheme) && (wb_orig_scheme != curr_scheme)) {
if (prefix !== curr_scheme && prefix !== this.REL_PREFIX) {
rebuild = true;
}
if (rebuild) {
if (!useRel) {
url = curr_scheme + orig_host;
} else {
url = '';
}
if (path && path[0] !== '/') {
url += '/';
}
url += path;
}
return url;
}
return this.getFinalUrl(useRel, mod, url);
}
// Check for common bad prefixes and remove them
prefix = this.startsWithOneOf(url, this.BAD_PREFIXES);
if (prefix) {
return this.getFinalUrl(useRel, mod, this.extractOriginalURL(url));
}
// May or may not be a hostname, call function to determine
// If it is, add the prefix and make sure port is removed
if (this.isHostUrl(url) && !this.startsWith(url, originalLoc.host + '/')) {
return this.getFinalUrl(useRel, mod, this.wb_orig_scheme + url);
}
return url;
};
/**
* Rewrites the supplied URL returning the rewritten URL.
* If wombat is in debug mode the rewrite is logged to the console
* @param {*} url
* @param {?boolean} [useRel]
* @param {?string} [mod]
* @param {?Document} [doc]
* @return {?string}
*/
Wombat.prototype.rewriteUrl = function(url, useRel, mod, doc) {
var rewritten = this.rewriteUrl_(url, useRel, mod, doc);
if (this.debug_rw) {
if (url !== rewritten) {
console.log('REWRITE: ' + url + ' -> ' + rewritten);
} else {
console.log('NOT REWRITTEN ' + url);
}
}
return rewritten;
};
/**
* Rewrites the value of the supplied elements attribute returning its rewritten value.
* Used by {@link newAttrObjGetSet} and {@link rewriteAttr}
*
* @param {Element} elem
* @param {string} name
* @param {*} value
* @param {boolean} [absUrlOnly]
* @return {*}
*/
Wombat.prototype.performAttributeRewrite = function(
elem,
name,
value,
absUrlOnly
) {
switch (name) {
// inner and outer HTML are for the overrides applied by newAttrObjGetSet
case 'innerHTML':
case 'outerHTML':
return this.rewriteHtml(value);
case 'filter': // for svg filter attribute which is url(...)
return this.rewriteInlineStyle(value);
case 'style':
return this.rewriteStyle(value);
case 'srcset':
return this.rewriteSrcset(value, elem);
}
// Only rewrite if absolute url
if (absUrlOnly && !this.startsWithOneOf(value, this.VALID_PREFIXES)) {
return value;
}
var mod = this.rwModForElement(elem, name);
if (
this.wbUseAFWorker &&
this.WBAutoFetchWorker &&
this.isSavedDataSrcSrcset(elem)
) {
this.WBAutoFetchWorker.preserveDataSrcset(elem);
}
return this.rewriteUrl(value, false, mod, elem.ownerDocument);
};
/**
* Rewrites an element attribute's value
* @param {Element} elem
* @param {string} name
* @param {boolean} [absUrlOnly]
* @return {boolean}
*/
Wombat.prototype.rewriteAttr = function(elem, name, absUrlOnly) {
var changed = false;
if (!elem || !elem.getAttribute || elem._no_rewrite || elem['_' + name]) {
return changed;
}
var value = this.wb_getAttribute.call(elem, name);
if (!value || this.startsWith(value, 'javascript:')) return changed;
var new_value = this.performAttributeRewrite(elem, name, value, absUrlOnly);
if (new_value !== value) {
this.removeWBOSRC(elem);
this.wb_setAttribute.call(elem, name, new_value);
changed = true;
}
return changed;
};
/**
* {@link rewriteStyle} wrapped in a try catch
* @param {string|Object} style
* @return {string|Object|null}
*/
Wombat.prototype.noExceptRewriteStyle = function(style) {
try {
return this.rewriteStyle(style);
} catch (e) {
return style;
}
};
/**
* Rewrites the supplied CSS style definitions
* @param {string|Object} style
* @return {string|Object|null}
*/
Wombat.prototype.rewriteStyle = function(style) {
if (!style) return style;
var value = style;
if (typeof style === 'object') {
value = style.toString();
}
if (typeof value === 'string') {
return value
.replace(this.STYLE_REGEX, this.styleReplacer)
.replace(this.IMPORT_REGEX, this.styleReplacer)
.replace(this.no_wombatRe, '');
}
return value;
};
/**
* Rewrites the supplied srcset string returning the rewritten results.
* If the element is one the srcset values are auto-fetched they are sent
* to the backing auto-fetch worker
* @param {string} value
* @param {Element} elem
* @return {string}
*/
Wombat.prototype.rewriteSrcset = function(value, elem) {
if (!value) return '';
var split = value.split(this.srcsetRe);
var values = [];
for (var i = 0; i < split.length; i++) {
// Filter removes non-truthy values like null, undefined, and ""
if (split[i]) {
var trimmed = split[i].trim();
if (trimmed) values.push(this.rewriteUrl(trimmed));
}
}
if (
this.wbUseAFWorker &&
this.WBAutoFetchWorker &&
this.isSavedSrcSrcset(elem)
) {
// send post split values to preservation worker
this.WBAutoFetchWorker.preserveSrcset(
values,
this.WBAutoFetchWorker.rwMod(elem)
);
}
return values.join(', ');
};
/**
* Rewrites the URL supplied to the setter of an (i)frame's src attribute
* @param {Element} elem
* @param {string} attrName
* @return {boolean}
*/
Wombat.prototype.rewriteFrameSrc = function(elem, attrName) {
var value = this.wb_getAttribute.call(elem, attrName);
var new_value;
// special case for rewriting javascript: urls that contain WB_wombat_
// must insert _wombat init first!
if (this.startsWith(value, 'javascript:')) {
if (value.indexOf('WB_wombat_') >= 0) {
var JS = 'javascript:';
new_value =
JS +
'window.parent._wb_wombat.initNewWindowWombat(window);' +
value.substr(JS.length);
}
}
if (!new_value) {
new_value = this.rewriteUrl(
value,
false,
this.rwModForElement(elem, attrName)
);
}
if (new_value !== value) {
this.wb_setAttribute.call(elem, attrName, new_value);
return true;
}
return false;
};
/**
* Rewrites either the URL contained in the src attribute or the text contents
* of the supplied script element. Returns T/F indicating if a rewrite occurred
* @param elem
* @return {boolean}
*/
Wombat.prototype.rewriteScript = function(elem) {
if (elem.hasAttribute('src') || !elem.textContent || !this.$wbwindow.Proxy) {
return this.rewriteAttr(elem, 'src');
}
var type = elem.type;
if (
type &&
(type === 'application/json' || type.indexOf('text/template') !== -1)
) {
return false;
}
var text = elem.textContent.trim();
if (
!text ||
text.indexOf('_____WB$wombat$assign$function_____') >= 0 ||
text.indexOf('<') === 0
) {
return false;
}
var override_props = [
'window',
'self',
'document',
'location',
'top',
'parent',
'frames',
'opener'
];
var contains_props = false;
for (var i = 0; i < override_props.length; i++) {
if (text.indexOf(override_props[i]) >= 0) {
contains_props = true;
break;
}
}
if (!contains_props) return false;
elem.textContent = this.wrapScriptTextJsProxy(
text.replace(this.DotPostMessageRe, '.__WB_pmw(self.window)$1')
);
return true;
};
/**
* Rewrites the supplied SVG element returning T/F indicating if a rewrite occurred
* @param {SVGElement} elem
* @return {boolean}
*/
Wombat.prototype.rewriteSVGElem = function(elem) {
var changed = this.rewriteAttr(elem, 'filter');
changed = this.rewriteAttr(elem, 'style') || changed;
// xlink:href is deprecated since SVG 2 in favor of href
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/xlink:href
changed = this.rewriteAttr(elem, 'xlink:href') || changed;
changed = this.rewriteAttr(elem, 'href') || changed;
changed = this.rewriteAttr(elem, 'src') || changed;
return changed;
};
/**
* Rewrites the supplied element returning T/F indicating if a rewrite occured
* @param {Element|Node} elem - The element to be rewritten
* @return {boolean}
*/
Wombat.prototype.rewriteElem = function(elem) {
var changed = false;
if (!elem) return changed;
if (elem instanceof SVGElement) {
changed = this.rewriteSVGElem(elem);
} else {
switch (elem.tagName) {
case 'META':
var maybeCSP = this.wb_getAttribute.call(elem, 'http-equiv');
if (maybeCSP && maybeCSP.toLowerCase() === 'content-security-policy') {
this.wb_setAttribute.call(elem, 'http-equiv', '_' + maybeCSP);
changed = true;
}
break;
case 'STYLE':
var new_content = this.rewriteStyle(elem.textContent);
if (elem.textContent !== new_content) {
elem.textContent = new_content;
changed = true;
if (
this.wbUseAFWorker &&
this.WBAutoFetchWorker &&
elem.sheet != null
) {
// we have a stylesheet so lets be nice to UI thread
// and defer extraction
this.WBAutoFetchWorker.deferredSheetExtraction(elem.sheet);
}
}
break;
case 'LINK':
changed = this.rewriteAttr(elem, 'href');
if (this.wbUseAFWorker && elem.rel === 'stylesheet') {
// we can only check link[rel='stylesheet'] when it loads
this._addEventListener(
elem,
'load',
this.utilFns.wbSheetMediaQChecker
);
}
break;
case 'IMG':
changed = this.rewriteAttr(elem, 'src');
changed = this.rewriteAttr(elem, 'srcset') || changed;
changed = this.rewriteAttr(elem, 'style') || changed;
if (
this.wbUseAFWorker &&
this.WBAutoFetchWorker &&
elem.dataset.srcset
) {
this.WBAutoFetchWorker.preserveDataSrcset(elem);
}
break;
case 'OBJECT':
changed = this.rewriteAttr(elem, 'data', true);
changed = this.rewriteAttr(elem, 'style') || changed;
break;
case 'FORM':
changed = this.rewriteAttr(elem, 'poster');
changed = this.rewriteAttr(elem, 'action') || changed;
changed = this.rewriteAttr(elem, 'style') || changed;
break;
case 'IFRAME':
case 'FRAME':
changed = this.rewriteFrameSrc(elem, 'src');
changed = this.rewriteAttr(elem, 'style') || changed;
break;
case 'SCRIPT':
changed = this.rewriteScript(elem);
break;
default: {
changed = this.rewriteAttr(elem, 'src');
changed = this.rewriteAttr(elem, 'srcset') || changed;
changed = this.rewriteAttr(elem, 'href') || changed;
changed = this.rewriteAttr(elem, 'style') || changed;
changed = this.rewriteAttr(elem, 'poster') || changed;
break;
}
}
}
if (elem.hasAttribute && elem.removeAttribute) {
if (elem.hasAttribute('crossorigin')) {
elem.removeAttribute('crossorigin');
changed = true;
}
if (elem.hasAttribute('integrity')) {
elem.removeAttribute('integrity');
changed = true;
}
}
return changed;
};
/**
* Rewrites all the children and there descendants of the supplied Node
* returning T/F if a rewrite occurred
* @param {Node} curr
* @return {boolean}
*/
Wombat.prototype.recurseRewriteElem = function(curr) {
if (!this.nodeHasChildren(curr)) return false;
var changed = false;
var rewriteQ = [curr.children || curr.childNodes];
while (rewriteQ.length > 0) {
var children = rewriteQ.shift();
for (var i = 0; i < children.length; i++) {
var child = children[i];
if (child.nodeType === Node.ELEMENT_NODE) {
changed = this.rewriteElem(child) || changed;
if (this.nodeHasChildren(child)) {
rewriteQ.push(child.children || child.childNodes);
}
}
}
}
return changed;
};
/**
* Rewrites the supplied element and all its children if any.
* See {@link rewriteElem} and {@link recurseRewriteElem} for more details
* @param {Node} elem
* @return {boolean}
*/
Wombat.prototype.rewriteElemComplete = function(elem) {
if (!elem) return false;
var changed = this.rewriteElem(elem);
var changedRecursively = this.recurseRewriteElem(elem);
return changed || changedRecursively;
};
/**
* Rewrites any elements found in the supplied arguments object returning
* a new array containing the original contents of the supplied arguments
* object after rewriting.
* @param {Object} originalArguments
* @return {Array<*>}
*/
Wombat.prototype.rewriteElementsInArguments = function(originalArguments) {
var argArr = new Array(originalArguments.length);
for (var i = 0; i < originalArguments.length; i++) {
var argElem = originalArguments[i];
if (argElem instanceof Node) {
this.rewriteElemComplete(argElem);
argArr[i] = argElem;
} else if (typeof argElem === 'string') {
argArr[i] = this.rewriteHtml(argElem);
} else {
argArr[i] = argElem;
}
}
return argArr;
};
/**
* Rewrites the supplied string containing HTML, if the supplied string
* is full HTML (starts with <HTML, <DOCUMENT...) the string is rewritten
* using {@link Wombat#rewriteHtmlFull}
* @param {string} string
* @param {boolean} [checkEndTag]
* @return {?string}
*/
Wombat.prototype.rewriteHtml = function(string, checkEndTag) {
if (!string) {
return string;
}
var rwString = string;
if (typeof string !== 'string') {
rwString = string.toString();
}
if (this.write_buff) {
rwString = this.write_buff + rwString;
this.write_buff = '';
}
if (rwString.indexOf('<script') <= 0) {
// string = string.replace(/WB_wombat_/g, "");
rwString = rwString.replace(/((id|class)=".*)WB_wombat_([^"]+)/, '$1$3');
}
if (
!this.$wbwindow.HTMLTemplateElement ||
this.FullHTMLRegex.test(rwString)
) {
return this.rewriteHtmlFull(rwString, checkEndTag);
}
var inner_doc = new DOMParser().parseFromString(
'<template>' + rwString + '</template>',
'text/html'
);
if (
!inner_doc ||
!this.nodeHasChildren(inner_doc.head) ||
!inner_doc.head.children[0].content
) {
return rwString;
}
var template = inner_doc.head.children[0];
template._no_rewrite = true;
if (this.recurseRewriteElem(template.content)) {
var new_html = template.innerHTML;
if (checkEndTag) {
var first_elem =
template.content.children && template.content.children[0];
if (first_elem) {
var end_tag = '</' + first_elem.tagName.toLowerCase() + '>';
if (
this.endsWith(new_html, end_tag) &&
!this.endsWith(rwString, end_tag)
) {
new_html = new_html.substring(0, new_html.length - end_tag.length);
}
} else if (rwString[0] !== '<' || rwString[rwString.length - 1] !== '>') {
this.write_buff += rwString;
return undefined;
}
}
return new_html;
}
return rwString;
};
/**
* Rewrites the supplied string containing full HTML
* @param {string} string
* @param {boolean} [checkEndTag]
* @return {?string}
*/
Wombat.prototype.rewriteHtmlFull = function(string, checkEndTag) {
var inner_doc = new DOMParser().parseFromString(string, 'text/html');
if (!inner_doc) return string;
var changed = false;
for (var i = 0; i < inner_doc.all.length; i++) {
changed = this.rewriteElem(inner_doc.all[i]) || changed;
}
if (changed) {
var new_html;
// if original had <html> tag, add full document HTML
if (string && string.indexOf('<html') >= 0) {
inner_doc.documentElement._no_rewrite = true;
new_html =
this.reconstructDocType(inner_doc.doctype) +
inner_doc.documentElement.outerHTML;
} else {
//
inner_doc.head._no_rewrite = true;
inner_doc.body._no_rewrite = true;
// hasChildNodes includes text nodes
var headHasKids = this.nodeHasChildren(inner_doc.head);
var bodyHasKids = this.nodeHasChildren(inner_doc.body);
new_html =
(headHasKids ? inner_doc.head.outerHTML : '') +
(bodyHasKids ? inner_doc.body.outerHTML : '');
if (checkEndTag) {
if (inner_doc.all.length > 3) {
var end_tag = '</' + inner_doc.all[3].tagName.toLowerCase() + '>';
if (
this.endsWith(new_html, end_tag) &&
!this.endsWith(string, end_tag)
) {
new_html = new_html.substring(0, new_html.length - end_tag.length);
}
} else if (string[0] !== '<' || string[string.length - 1] !== '>') {
this.write_buff += string;
return;
}
}
new_html = this.reconstructDocType(inner_doc.doctype) + new_html;
}
return new_html;
}
return string;
};
/**
* Rewrites a CSS style string found in the style property of an element or
* FontFace
* @param {string} orig
* @return {string}
*/
Wombat.prototype.rewriteInlineStyle = function(orig) {
var decoded;
try {
decoded = decodeURIComponent(orig);
} catch (e) {
decoded = orig;
}
if (decoded !== orig) {
var parts = this.rewriteStyle(decoded).split(',', 2);
return parts[0] + ',' + encodeURIComponent(parts[1]);
}
return this.rewriteStyle(orig);
};
/**
* Rewrites the supplied cookie
* @param {string} cookie
* @return {string}
*/
Wombat.prototype.rewriteCookie = function(cookie) {
var wombat = this;
var rwCookie = cookie
.replace(this.wb_abs_prefix, '')
.replace(this.wb_rel_prefix, '');
rwCookie = rwCookie
.replace(this.cookie_domain_regex, function(m, m1) {
// rewrite domain
var message = {
domain: m1,
cookie: rwCookie,
wb_type: 'cookie'
};
// norify of cookie setting to allow server-side tracking
wombat.sendTopMessage(message, true);
// if no subdomain, eg. "localhost", just remove domain altogether
if (
wombat.$wbwindow.location.hostname.indexOf('.') >= 0 &&
!wombat.IP_RX.test(wombat.$wbwindow.location.hostname)
) {
return 'Domain=.' + wombat.$wbwindow.location.hostname;
}
return '';
})
.replace(this.cookie_path_regex, function(m, m1) {
// rewrite path
var rewritten = wombat.rewriteUrl(m1);
if (rewritten.indexOf(wombat.wb_curr_host) === 0) {
rewritten = rewritten.substring(wombat.wb_curr_host.length);
}
return 'Path=' + rewritten;
});
// rewrite secure, if needed
if (wombat.$wbwindow.location.protocol !== 'https:') {
rwCookie = rwCookie.replace('secure', '');
}
return rwCookie.replace(',|', ',');
};
/**
* Rewrites the supplied web worker URL
* @param {string} workerUrl
* @return {string}
*/
Wombat.prototype.rewriteWorker = function(workerUrl) {
if (!workerUrl) return workerUrl;
var isBlob = workerUrl.indexOf('blob:') === 0;
var isJS = workerUrl.indexOf('javascript:') === 0;
if (!isBlob && !isJS) {
if (
!this.startsWithOneOf(workerUrl, this.VALID_PREFIXES) &&
!this.startsWith(workerUrl, '/') &&
!this.startsWithOneOf(workerUrl, this.BAD_PREFIXES)
) {
// super relative url assets/js/xyz.js
var rurl = this.resolveRelUrl(workerUrl, this.$wbwindow.document);
return this.rewriteUrl(rurl, false, 'wkr_', this.$wbwindow.document);
}
return this.rewriteUrl(workerUrl, false, 'wkr_', this.$wbwindow.document);
}
var workerCode = isJS ? workerUrl.replace('javascript:', '') : null;
if (isBlob) {
// fetching only skipped if it was JS url
var x = new XMLHttpRequest();
// use sync ajax request to get the contents, remove postMessage() rewriting
this.utilFns.XHRopen.call(x, 'GET', workerUrl, false);
x.send();
workerCode = x.responseText
.replace(this.workerBlobRe, '')
// resolving blobs hit our sever side rewriting so we gotta
// ensure we good
.replace(this.rmCheckThisInjectRe, 'this');
}
if (this.wb_info.static_prefix || this.wb_info.ww_rw_script) {
var originalURL = this.$wbwindow.document.baseURI;
// if we are here we can must return blob so set makeBlob to true
var ww_rw =
this.wb_info.ww_rw_script ||
this.wb_info.static_prefix + 'wombatWorkers.js';
var rw =
"(function() { self.importScripts('" +
ww_rw +
"'); new WBWombat({'prefix': '" +
this.wb_abs_prefix +
"', 'prefixMod': '" +
this.wb_abs_prefix +
"wkrf_/', 'originalURL': '" +
originalURL +
"'}); })();";
workerCode = rw + workerCode;
}
var blob = new Blob([workerCode], { type: 'application/javascript' });
return URL.createObjectURL(blob);
};
/**
* Rewrite the arguments supplied to a function of the Text interface in order
* to ensure CSS is rewritten when a text node is the child of the style tag
* @param {Object} fnThis
* @param {function} originalFn
* @param {Object} argsObj
*/
Wombat.prototype.rewriteTextNodeFn = function(fnThis, originalFn, argsObj) {
var deproxiedThis = this.proxyToObj(fnThis);
var args;
if (
argsObj.length > 0 &&
deproxiedThis.parentElement &&
deproxiedThis.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(argsObj.length);
var dataIndex = argsObj.length - 1;
if (dataIndex === 2) {
args[0] = argsObj[0];
args[1] = argsObj[1];
} else if (dataIndex === 1) {
args[0] = argsObj[0];
}
args[dataIndex] = this.rewriteStyle(argsObj[dataIndex]);
} else {
args = argsObj;
}
if (originalFn.__WB_orig_apply) {
return originalFn.__WB_orig_apply(deproxiedThis, args);
}
return originalFn.apply(deproxiedThis, args);
};
/**
* Rewrite the arguments supplied to document.[write, writeln] in order
* to ensure that the string of HTML is rewritten
* @param {Object} fnThis
* @param {function} originalFn
* @param {Object} argsObj
*/
Wombat.prototype.rewriteDocWriteWriteln = function(
fnThis,
originalFn,
argsObj
) {
var thisObj = this.proxyToObj(fnThis);
var argLen = argsObj.length;
var string;
if (argLen === 0) {
return originalFn.call(thisObj);
}
if (argLen === 1) {
string = argsObj[0];
} else {
// use Array.join rather than Array.apply because join works with array like objects
string = Array.prototype.join.call(argsObj, '');
}
var new_buff = this.rewriteHtml(string, true);
var res = originalFn.call(thisObj, new_buff);
this.initNewWindowWombat(thisObj.defaultView);
return res;
};
/**
* Rewrite the arguments supplied to a function of the ChildNode interface
* in order to ensure that elements are rewritten
* @param {Object} fnThis
* @param {function} originalFn
* @param {Object} argsObj
*/
Wombat.prototype.rewriteChildNodeFn = function(fnThis, originalFn, argsObj) {
var thisObj = this.proxyToObj(fnThis);
if (argsObj.length === 0) return originalFn.call(thisObj);
var newArgs = this.rewriteElementsInArguments(argsObj);
if (originalFn.__WB_orig_apply) {
return originalFn.__WB_orig_apply(thisObj, newArgs);
}
return originalFn.apply(thisObj, newArgs);
};
/**
* Rewrites the arguments supplied to Element.[insertAdjacentElement, insertAdjacentHTML].
* If rwHTML is true the rewrite performed is done by {@link rewriteHtml} other wise
* {@link rewriteElemComplete}
* @param {Object} fnThis
* @param {function} originalFn
* @param {number} position
* @param {string|Node} textOrElem
* @param {boolean} rwHTML
* @return {*}
*/
Wombat.prototype.rewriteInsertAdjHTMLOrElemArgs = function(
fnThis,
originalFn,
position,
textOrElem,
rwHTML
) {
var fnThisObj = this.proxyToObj(fnThis);
if (fnThisObj._no_rewrite) {
return originalFn.call(fnThisObj, position, textOrElem);
}
if (rwHTML) {
return originalFn.call(fnThisObj, position, this.rewriteHtml(textOrElem));
}
this.rewriteElemComplete(textOrElem);
return originalFn.call(fnThisObj, position, textOrElem);
};
/**
* Rewrites the arguments of either setTimeout or setInterval because
* [setTimeout|setInterval]('document.location.href = "xyz.com"', time)
* is legal and used
* @param {Object} fnThis
* @param {function} originalFn
* @param {Object} argsObj
* @return {*}
*/
Wombat.prototype.rewriteSetTimeoutInterval = function(
fnThis,
originalFn,
argsObj
) {
// strings are primitives with a prototype or __proto__ of String depending on the browser
var rw =
argsObj[0] != null &&
Object.getPrototypeOf(argsObj[0]) === String.prototype;
// do not mess with the arguments object unless you want instant de-optimization
var args = rw ? new Array(argsObj.length) : argsObj;
if (rw) {
if (this.$wbwindow.Proxy) {
args[0] = this.wrapScriptTextJsProxy(argsObj[0]);
} else {
args[0] = argsObj[0].replace(/\blocation\b/g, 'WB_wombat_$&');
}
for (var i = 1; i < argsObj.length; ++i) {
args[i] = this.proxyToObj(argsObj[i]);
}
}
// setTimeout|setInterval does not require its this arg to be window so just in case
// someone got funky with it
var thisObj = this.proxyToObj(fnThis);
if (originalFn.__WB_orig_apply) {
return originalFn.__WB_orig_apply(thisObj, args);
}
return originalFn.apply(thisObj, args);
};
/**
* Applies an Event property getter override for the supplied property
* @param {string} attr
* @param {Object} [eventProto]
*/
Wombat.prototype.addEventOverride = function(attr, eventProto) {
var theProto = eventProto;
if (!eventProto) {
theProto = this.$wbwindow.MessageEvent.prototype;
}
var origGetter = this.getOrigGetter(theProto, attr);
if (!origGetter) return;
this.defGetterProp(theProto, attr, function() {
if (this['_' + attr] != null) {
return this['_' + attr];
}
return origGetter.call(this);
});
};
/**
* Returns T/F indicating if the supplied attribute node is to be rewritten
* @param {Object} attr
* @return {boolean}
*/
Wombat.prototype.isAttrObjRewrite = function(attr) {
if (!attr) return false;
var tagName = attr.ownerElement && attr.ownerElement.tagName;
return this.shouldRewriteAttr(tagName, attr.nodeName);
};
/**
* Defines a new getter and setter function for the supplied
* property of the Attr interface
* @param {Object} attrProto
* @param {string} prop
*/
Wombat.prototype.newAttrObjGetSet = function(attrProto, prop) {
var wombat = this;
var oGetter = this.getOrigGetter(attrProto, prop);
var oSetter = this.getOrigSetter(attrProto, prop);
this.defProp(
attrProto,
prop,
function newAttrObjSetter(newValue) {
var obj = wombat.proxyToObj(this);
var res = newValue;
if (wombat.isAttrObjRewrite(obj)) {
res = wombat.performAttributeRewrite(
obj.ownerElement,
obj.name,
newValue,
false
);
}
return oSetter.call(obj, res);
},
function newAttrObjGetter() {
var obj = wombat.proxyToObj(this);
var res = oGetter.call(obj);
if (wombat.isAttrObjRewrite(obj)) {
return wombat.extractOriginalURL(res);
}
return res;
}
);
};
/**
* Overrides the nodeValue property of the Attr interface
*/
Wombat.prototype.overrideAttrProps = function() {
var attrProto = this.$wbwindow.Attr.prototype;
this.newAttrObjGetSet(attrProto, 'value');
this.newAttrObjGetSet(attrProto, 'nodeValue');
this.newAttrObjGetSet(attrProto, 'textContent');
};
/**
* Applies an override the attribute get/set override
* @param {Object} obj
* @param {string} attr
* @param {string} mod
*/
Wombat.prototype.overrideAttr = function(obj, attr, mod) {
var orig_getter = this.getOrigGetter(obj, attr);
var orig_setter = this.getOrigSetter(obj, attr);
var wombat = this;
var setter = function newAttrPropSetter(orig) {
if (mod === 'js_' && !this.__$removedWBOSRC$__) {
wombat.removeWBOSRC(this);
}
var val = wombat.rewriteUrl(orig, false, mod);
if (orig_setter) {
return orig_setter.call(this, val);
} else if (wombat.wb_setAttribute) {
return wombat.wb_setAttribute.call(this, attr, val);
}
};
var getter = function newAttrPropGetter() {
var res;
if (orig_getter) {
res = orig_getter.call(this);
} else if (wombat.wb_getAttribute) {
res = wombat.wb_getAttribute.call(this, attr);
}
return wombat.extractOriginalURL(res);
};
this.defProp(obj, attr, setter, getter);
};
/**
* Applies an attribute getter override IFF an original getter exists
* @param {Object} proto
* @param {string} prop
* @param {*} [cond]
*/
Wombat.prototype.overridePropExtract = function(proto, prop, cond) {
var orig_getter = this.getOrigGetter(proto, prop);
var wombat = this;
if (orig_getter) {
var new_getter = function overridePropExtractNewGetter() {
var obj = wombat.proxyToObj(this);
var res = orig_getter.call(obj);
if (!cond || cond(obj)) {
return wombat.extractOriginalURL(res);
}
return res;
};
this.defGetterProp(proto, prop, new_getter);
}
};
/**
* Applies an attribute getter override IFF an original getter exists that
* ensures that the results of retrieving the attributes value is not a
* wombat Proxy
* @param {Object} proto
* @param {string} prop
*/
Wombat.prototype.overridePropToProxy = function(proto, prop) {
var orig_getter = this.getOrigGetter(proto, prop);
if (orig_getter) {
var wombat = this;
var new_getter = function overridePropToProxyNewGetter() {
return wombat.objToProxy(orig_getter.call(this));
};
this.defGetterProp(proto, prop, new_getter);
}
};
/**
* Applies an override to supplied history function name IFF it exists
* @param {string} funcName
* @return {?function}
*/
Wombat.prototype.overrideHistoryFunc = function(funcName) {
if (!this.$wbwindow.history) return undefined;
var orig_func = this.$wbwindow.history[funcName];
if (!orig_func) return undefined;
this.$wbwindow.history['_orig_' + funcName] = orig_func;
var wombat = this;
var rewrittenFunc = function histNewFunc(stateObj, title, url) {
var wombatLocation = wombat.$wbwindow.WB_wombat_location;
var rewritten_url;
var resolvedURL;
if (url) {
var parser = wombat.$wbwindow.document.createElement('a');
parser.href = url;
resolvedURL = parser.href;
rewritten_url = wombat.rewriteUrl(resolvedURL);
if (
resolvedURL !== wombatLocation.origin &&
wombatLocation.href !== 'about:blank' &&
!wombat.startsWith(resolvedURL, wombatLocation.origin + '/')
) {
throw new DOMException('Invalid history change: ' + resolvedURL);
}
} else {
resolvedURL = wombatLocation.href;
}
orig_func.call(this, stateObj, title, rewritten_url);
wombat.sendHistoryUpdate(resolvedURL, title);
};
this.$wbwindow.history[funcName] = rewrittenFunc;
if (this.$wbwindow.History && this.$wbwindow.History.prototype) {
this.$wbwindow.History.prototype[funcName] = rewrittenFunc;
}
return rewrittenFunc;
};
/**
* Applies an getter/setter override to the supplied style interface's attribute
* and prop name combination
* @param {Object} obj
* @param {string} attr
* @param {string} [propName]
*/
Wombat.prototype.overrideStyleAttr = function(obj, attr, propName) {
var orig_getter = this.getOrigGetter(obj, attr);
var orig_setter = this.getOrigSetter(obj, attr);
var wombat = this;
var setter = function overrideStyleAttrSetter(orig) {
var val = wombat.rewriteStyle(orig);
if (orig_setter) {
orig_setter.call(this, val);
} else {
this.setProperty(propName, val);
}
return val;
};
var getter = orig_getter;
if (!orig_getter) {
getter = function overrideStyleAttrGetter() {
return this.getPropertyValue(propName);
};
}
if ((orig_setter && orig_getter) || propName) {
this.defProp(obj, attr, setter, getter);
}
};
/**
* Applies an override to the setProperty function
* @param style_proto
*/
Wombat.prototype.overrideStyleSetProp = function(style_proto) {
var orig_setProp = style_proto.setProperty;
var wombat = this;
style_proto.setProperty = function rwSetProperty(name, value, priority) {
var rwvalue = wombat.rewriteStyle(value);
return orig_setProp.call(this, name, rwvalue, priority);
};
};
/**
* Overrides the getter and setter functions for the properties listed in
* {@link Wombat#URL_PROPS} for the `a` and `area` tags
* @param {Object} whichObj
*/
Wombat.prototype.overrideAnchorAreaElem = function(whichObj) {
if (!whichObj || !whichObj.prototype) return;
var originalGetSets = {};
var originalProto = whichObj.prototype;
var anchorAreaSetter = function anchorAreaSetter(prop, value) {
var func = originalGetSets['set_' + prop];
if (func) return func.call(this, value);
return '';
};
var anchorAreaGetter = function anchorAreaGetter(prop) {
var func = originalGetSets['get_' + prop];
if (func) return func.call(this);
return '';
};
for (var i = 0; i < this.URL_PROPS.length; i++) {
var prop = this.URL_PROPS[i];
originalGetSets['get_' + prop] = this.getOrigGetter(originalProto, prop);
originalGetSets['set_' + prop] = this.getOrigSetter(originalProto, prop);
if (Object.defineProperty) {
this.defProp(
originalProto,
prop,
this.makeSetLocProp(prop, anchorAreaSetter, anchorAreaGetter),
this.makeGetLocProp(prop, anchorAreaGetter),
true
);
}
}
originalProto.toString = function toString() {
return this.href;
};
};
/**
* Overrides the getter and setter functions for the `innerHTML` and `outerHTML`
* properties of the supplied element
* @param {Object} elem
* @param {string} prop
* @param {boolean} [rewriteGetter]
*/
Wombat.prototype.overrideHtmlAssign = function(elem, prop, rewriteGetter) {
if (!this.$wbwindow.DOMParser || !elem || !elem.prototype) {
return;
}
var obj = elem.prototype;
var orig_getter = this.getOrigGetter(obj, prop);
var orig_setter = this.getOrigSetter(obj, prop);
if (!orig_setter) return;
var wombat = this;
var setter = function overrideHTMLAssignSetter(orig) {
var res = orig;
if (!this._no_rewrite) {
// init_iframe_insert_obs(this);
if (this.tagName === 'STYLE') {
res = wombat.rewriteStyle(orig);
} else {
res = wombat.rewriteHtml(orig);
}
}
orig_setter.call(this, res);
if (
this.wbUseAFWorker &&
this.WBAutoFetchWorker &&
this.tagName === 'STYLE' &&
this.sheet != null
) {
// got preserve all the things
this.WBAutoFetchWorker.deferredSheetExtraction(this.sheet);
}
};
var getter = function overrideHTMLAssignGetter() {
var res = orig_getter.call(this);
if (!this._no_rewrite) {
return res.replace(wombat.wb_unrewrite_rx, '');
}
return res;
};
this.defProp(obj, prop, setter, rewriteGetter ? getter : orig_getter);
};
/**
* Overrides the getter and setter functions for the supplied property
* on the HTMLIFrameElement
* @param {string} prop
*/
Wombat.prototype.overrideIframeContentAccess = function(prop) {
if (
!this.$wbwindow.HTMLIFrameElement ||
!this.$wbwindow.HTMLIFrameElement.prototype
) {
return;
}
var obj = this.$wbwindow.HTMLIFrameElement.prototype;
var orig_getter = this.getOrigGetter(obj, prop);
if (!orig_getter) return;
var orig_setter = this.getOrigSetter(obj, prop);
var wombat = this;
var getter = function overrideIframeContentAccessGetter() {
wombat.initIframeWombat(this);
return wombat.objToProxy(orig_getter.call(this));
};
this.defProp(obj, prop, orig_setter, getter);
obj['_get_' + prop] = orig_getter;
};
/**
* Applies an override to the gettter function for the frames property of
* the supplied window in order to ensure that wombat is initialized in
* all frames.
* * @param {Window} $wbwindow
*/
Wombat.prototype.overrideFramesAccess = function($wbwindow) {
// If $wbwindow.frames is the window itself, nothing to override
// This can be handled in the Obj Proxy
if ($wbwindow.Proxy && $wbwindow === $wbwindow.frames) {
return;
}
$wbwindow.__wb_frames = $wbwindow.frames;
var wombat = this;
var getter = function overrideFramesAccessGetter() {
for (var i = 0; i < this.__wb_frames.length; i++) {
try {
wombat.initNewWindowWombat(this.__wb_frames[i]);
} catch (e) {}
}
return this.__wb_frames;
};
this.defGetterProp($wbwindow, 'frames', getter);
this.defGetterProp($wbwindow.Window.prototype, 'frames', getter);
};
/**
* Overrides the supplied method in order to ensure that the `this` argument
* of the function is not one of the JS Proxy objects used by wombat.
* @param {object} cls
* @param {string} method
* @param {Object} [obj]
*/
Wombat.prototype.overrideFuncThisProxyToObj = function(cls, method, obj) {
if (!cls) return;
var ovrObj = obj;
if (!obj && cls.prototype && cls.prototype[method]) {
ovrObj = cls.prototype;
} else if (!obj && cls[method]) {
ovrObj = cls;
}
if (!ovrObj) return;
var wombat = this;
var orig = ovrObj[method];
ovrObj[method] = function deproxyThis() {
return orig.apply(wombat.proxyToObj(this), arguments);
};
};
/**
* Applies an function override that ensures that the argument the supplied index
* is not one of the JS Proxy objects used by wombat.
* @param {Object} cls
* @param {string} method
* @param {number} [argumentIdx]
*/
Wombat.prototype.overrideFuncArgProxyToObj = function(
cls,
method,
argumentIdx
) {
if (!cls || !cls.prototype) return;
var argIndex = argumentIdx || 0;
var orig = cls.prototype[method];
var wombat = this;
cls.prototype[method] = function deproxyFnArg() {
var args = new Array(arguments.length);
for (var i = 0; i < args.length; i++) {
if (i === argIndex) {
args[i] = wombat.proxyToObj(arguments[i]);
} else {
args[i] = arguments[i];
}
}
var thisObj = wombat.proxyToObj(this);
if (orig.__WB_orig_apply) {
return orig.__WB_orig_apply(thisObj, args);
}
return orig.apply(thisObj, args);
};
};
/**
* Overrides Function.prototype.apply in order to ensure that none of the
* arguments of `native` functions are one of the JS Proxy objects used by wombat.
* @param {Window} $wbwindow
*/
Wombat.prototype.overrideFunctionApply = function($wbwindow) {
if ($wbwindow.Function.prototype.__WB_orig_apply) {
return;
}
var orig_apply = $wbwindow.Function.prototype.apply;
$wbwindow.Function.prototype.__WB_orig_apply = orig_apply;
var wombat = this;
$wbwindow.Function.prototype.apply = function apply(obj, args) {
var deproxiedObj = wombat.proxyToObj(obj);
var newArgs = args;
if (wombat.isNativeFunction(this)) {
newArgs = wombat.deproxyArrayHandlingArgumentsObj(args);
}
return this.__WB_orig_apply(deproxiedObj, newArgs);
};
this.wb_funToString.apply = orig_apply;
};
/**
* Overrides the getter and setter functions for the `srcset` property
* of the supplied Object in order to rewrite accesses and retrievals
* @param {Object} obj
* @param {string} [mod]
*/
Wombat.prototype.overrideSrcsetAttr = function(obj, mod) {
var prop = 'srcset';
var orig_getter = this.getOrigGetter(obj, prop);
var orig_setter = this.getOrigSetter(obj, prop);
var wombat = this;
var setter = function srcset(orig) {
var val = wombat.rewriteSrcset(orig, this);
if (orig_setter) {
return orig_setter.call(this, val);
} else if (wombat.wb_setAttribute) {
return wombat.wb_setAttribute.call(this, prop, val);
}
};
var getter = function srcset() {
var res;
if (orig_getter) {
res = orig_getter.call(this);
} else if (wombat.wb_getAttribute) {
res = wombat.wb_getAttribute.call(this, prop);
}
res = wombat.extractOriginalURL(res);
return res;
};
this.defProp(obj, prop, setter, getter);
};
/**
* Overrides the getter and setter functions for the `href` property
* of the supplied Object in order to rewrite accesses and retrievals
* @param {Object} obj
* @param {string} mod
*/
Wombat.prototype.overrideHrefAttr = function(obj, mod) {
var orig_getter = this.getOrigGetter(obj, 'href');
var orig_setter = this.getOrigSetter(obj, 'href');
var wombat = this;
var setter = function href(orig) {
var val;
if (mod === 'cs_' && orig.indexOf('data:text/css') === 0) {
val = wombat.rewriteInlineStyle(orig);
} else if (this.tagName === 'LINK') {
val = wombat.rewriteUrl(
orig,
false,
wombat.rwModForElement(this, 'href')
);
} else {
val = wombat.rewriteUrl(orig, false, mod, this.ownerDocument);
}
if (orig_setter) {
return orig_setter.call(this, val);
} else if (wombat.wb_setAttribute) {
return wombat.wb_setAttribute.call(this, 'href', val);
}
};
var getter = function href() {
var res;
if (orig_getter) {
res = orig_getter.call(this);
} else if (wombat.wb_getAttribute) {
res = wombat.wb_getAttribute.call(this, 'href');
}
if (!this._no_rewrite) return wombat.extractOriginalURL(res);
return res;
};
this.defProp(obj, 'href', setter, getter);
};
/**
* Overrides the getter and setter functions for a property of the Text
* interface in order to rewrite accesses and retrievals when a text node
* is the child of the style tag
* @param {Object} textProto
* @param {string} whichProp
*/
Wombat.prototype.overrideTextProtoGetSet = function(textProto, whichProp) {
var orig_getter = this.getOrigGetter(textProto, whichProp);
var wombat = this;
var setter;
// data, from CharacterData, is both readable and writable whereas wholeText, from Text, is not
if (whichProp === 'data') {
var orig_setter = this.getOrigSetter(textProto, whichProp);
setter = function rwTextProtoSetter(orig) {
var res = orig;
if (
!this._no_rewrite &&
this.parentElement &&
this.parentElement.tagName === 'STYLE'
) {
res = wombat.rewriteStyle(orig);
}
return orig_setter.call(this, res);
};
}
var getter = function rwTextProtoGetter() {
var res = orig_getter.call(this);
if (
!this._no_rewrite &&
this.parentElement &&
this.parentElement.tagName === 'STYLE'
) {
return res.replace(wombat.wb_unrewrite_rx, '');
}
return res;
};
this.defProp(textProto, whichProp, setter, getter);
};
/**
* Overrides the constructor of an UIEvent object in order to ensure
* that the `view`, `relatedTarget`, and `target` arguments of the
* constructor are not a JS Proxy used by wombat.
* @param {Object} which
*/
Wombat.prototype.overrideAnUIEvent = function(which) {
var didOverrideKey = '__wb_' + which + '_overridden';
var ConstructorFN = this.$wbwindow[which];
if (
!ConstructorFN ||
!ConstructorFN.prototype ||
ConstructorFN.prototype[didOverrideKey]
)
return;
// ensure if and when view is accessed it is proxied
var wombat = this;
this.overridePropToProxy(ConstructorFN.prototype, 'view');
var initFNKey = 'init' + which;
if (ConstructorFN.prototype[initFNKey]) {
var originalInitFn = ConstructorFN.prototype[initFNKey];
ConstructorFN.prototype[initFNKey] = function() {
var thisObj = wombat.proxyToObj(this);
if (arguments.length === 0 || arguments.length < 3) {
if (originalInitFn.__WB_orig_apply) {
return originalInitFn.__WB_orig_apply(thisObj, arguments);
}
return originalInitFn.apply(thisObj, arguments);
}
var newArgs = new Array(arguments.length);
for (var i = 0; i < arguments.length; i++) {
if (i === 3) {
newArgs[i] = wombat.proxyToObj(arguments[i]);
} else {
newArgs[i] = arguments[i];
}
}
if (originalInitFn.__WB_orig_apply) {
return originalInitFn.__WB_orig_apply(thisObj, newArgs);
}
return originalInitFn.apply(thisObj, newArgs);
};
}
this.$wbwindow[which] = (function(EventConstructor) {
return function NewEventConstructor(type, init) {
if (init) {
if (init.view != null) {
init.view = wombat.proxyToObj(init.view);
}
if (init.relatedTarget != null) {
init.relatedTarget = wombat.proxyToObj(init.relatedTarget);
}
if (init.target != null) {
init.target = wombat.proxyToObj(init.target);
}
}
return new EventConstructor(type, init);
};
})(ConstructorFN);
this.$wbwindow[which].prototype = ConstructorFN.prototype;
Object.defineProperty(this.$wbwindow[which].prototype, 'constructor', {
value: this.$wbwindow[which]
});
this.$wbwindow[which].prototype[didOverrideKey] = true;
};
/**
* Rewrites the arguments supplied to the functions of the ParentNode interface
* @param {Object} fnThis
* @param {function} originalFn
* @param {Object} argsObj
* @return {*}
*/
Wombat.prototype.rewriteParentNodeFn = function(fnThis, originalFn, argsObj) {
var argArr = this.rewriteElementsInArguments(argsObj);
var thisObj = this.proxyToObj(fnThis);
if (originalFn.__WB_orig_apply) {
return originalFn.__WB_orig_apply(thisObj, argArr);
}
return originalFn.apply(thisObj, argArr);
};
/**
* Overrides the append and prepend functions on the supplied object in order
* to ensure that the elements or string of HTML supplied as arguments to these
* functions are rewritten
* @param {Object} obj
* @see https://developer.mozilla.org/en-US/docs/Web/API/ParentNode/append
* @see https://developer.mozilla.org/en-US/docs/Web/API/ParentNode/prepend
*/
Wombat.prototype.overrideParentNodeAppendPrepend = function(obj) {
var rewriteParentNodeFn = this.rewriteParentNodeFn;
if (obj.prototype.append) {
var originalAppend = obj.prototype.append;
obj.prototype.append = function append() {
return rewriteParentNodeFn(this, originalAppend, arguments);
};
}
if (obj.prototype.prepend) {
var originalPrepend = obj.prototype.prepend;
obj.prototype.prepend = function prepend() {
return rewriteParentNodeFn(this, originalPrepend, arguments);
};
}
};
/**
* Overrides the `innerHTML` property and `append`, `prepend` functions
* on the ShadowRoot interface in order to ensure any HTML elements
* added via these methods are rewritten
* @see https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot
*/
Wombat.prototype.overrideShadowDom = function() {
if (!this.$wbwindow.ShadowRoot || !this.$wbwindow.ShadowRoot.prototype) {
return;
}
// shadow root inherits from DocumentFragment, Node, and ParentNode not Element
this.overrideHtmlAssign(this.$wbwindow.ShadowRoot, 'innerHTML', true);
this.overrideParentNodeAppendPrepend(this.$wbwindow.ShadowRoot);
};
/**
* Applies an override to the ChildNode interface that is inherited by
* the supplied Object. If the textIface argument is truthy the rewrite function
* used is {@link rewriteChildNodeFn} otherwise {@link rewriteTextNodeFn}
* @param {*} ifaceWithChildNode
* @param {boolean} [textIface]
*/
Wombat.prototype.overrideChildNodeInterface = function(
ifaceWithChildNode,
textIface
) {
if (!ifaceWithChildNode || !ifaceWithChildNode.prototype) return;
var rewriteFn = textIface ? this.rewriteTextNodeFn : this.rewriteChildNodeFn;
if (ifaceWithChildNode.prototype.before) {
var originalBefore = ifaceWithChildNode.prototype.before;
ifaceWithChildNode.prototype.before = function before() {
return rewriteFn(this, originalBefore, arguments);
};
}
if (ifaceWithChildNode.prototype.after) {
var originalAfter = ifaceWithChildNode.prototype.after;
ifaceWithChildNode.prototype.after = function after() {
return rewriteFn(this, originalAfter, arguments);
};
}
if (ifaceWithChildNode.prototype.replaceWith) {
var originalReplaceWith = ifaceWithChildNode.prototype.replaceWith;
ifaceWithChildNode.prototype.replaceWith = function replaceWith() {
return rewriteFn(this, originalReplaceWith, arguments);
};
}
};
/**
* Applies overrides to the `appendData`, `insertData`, and `replaceData` functions
* and `data` and `wholeText` properties on the Text interface in order to ensure
* CSS strings are rewritten when Text nodes are children of the style tag
*/
Wombat.prototype.initTextNodeOverrides = function() {
var Text = this.$wbwindow.Text;
if (!Text || !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 = Text.prototype;
// override inherited CharacterData functions
var rewriteTextProtoFunction = this.rewriteTextNodeFn;
if (textProto.appendData) {
var originalAppendData = textProto.appendData;
textProto.appendData = function appendData() {
return rewriteTextProtoFunction(this, originalAppendData, arguments);
};
}
if (textProto.insertData) {
var originalInsertData = textProto.insertData;
textProto.insertData = function insertData() {
return rewriteTextProtoFunction(this, originalInsertData, arguments);
};
}
if (textProto.replaceData) {
var originalReplaceData = textProto.replaceData;
textProto.replaceData = function replaceData() {
return rewriteTextProtoFunction(this, originalReplaceData, arguments);
};
}
this.overrideChildNodeInterface(Text, true);
// override property getters and setters
this.overrideTextProtoGetSet(textProto, 'data');
this.overrideTextProtoGetSet(textProto, 'wholeText');
};
/**
* Applies attribute getter and setter function overrides to the HTML elements
* and CSS properties that are URLs are rewritten
*/
Wombat.prototype.initAttrOverrides = function() {
// href attr overrides
this.overrideHrefAttr(this.$wbwindow.HTMLLinkElement.prototype, 'cs_');
this.overrideHrefAttr(this.$wbwindow.CSSStyleSheet.prototype, 'cs_');
this.overrideHrefAttr(this.$wbwindow.HTMLBaseElement.prototype, 'mp_');
// srcset attr overrides
this.overrideSrcsetAttr(this.$wbwindow.HTMLImageElement.prototype, 'im_');
this.overrideSrcsetAttr(this.$wbwindow.HTMLSourceElement.prototype, 'oe_');
// poster attr overrides
this.overrideAttr(this.$wbwindow.HTMLVideoElement.prototype, 'poster', 'im_');
this.overrideAttr(this.$wbwindow.HTMLAudioElement.prototype, 'poster', 'im_');
// src attr overrides
this.overrideAttr(this.$wbwindow.HTMLImageElement.prototype, 'src', 'im_');
this.overrideAttr(this.$wbwindow.HTMLInputElement.prototype, 'src', 'oe_');
this.overrideAttr(this.$wbwindow.HTMLEmbedElement.prototype, 'src', 'oe_');
this.overrideAttr(this.$wbwindow.HTMLVideoElement.prototype, 'src', 'oe_');
this.overrideAttr(this.$wbwindow.HTMLAudioElement.prototype, 'src', 'oe_');
this.overrideAttr(this.$wbwindow.HTMLSourceElement.prototype, 'src', 'oe_');
if (window.HTMLTrackElement && window.HTMLTrackElement.prototype) {
this.overrideAttr(this.$wbwindow.HTMLTrackElement.prototype, 'src', 'oe_');
}
this.overrideAttr(this.$wbwindow.HTMLIFrameElement.prototype, 'src', 'if_');
if (
this.$wbwindow.HTMLFrameElement &&
this.$wbwindow.HTMLFrameElement.prototype
) {
this.overrideAttr(this.$wbwindow.HTMLFrameElement.prototype, 'src', 'fr_');
}
this.overrideAttr(this.$wbwindow.HTMLScriptElement.prototype, 'src', 'js_');
// other attr overrides
this.overrideAttr(this.$wbwindow.HTMLObjectElement.prototype, 'data', 'oe_');
this.overrideAttr(
this.$wbwindow.HTMLObjectElement.prototype,
'codebase',
'oe_'
);
this.overrideAttr(this.$wbwindow.HTMLMetaElement.prototype, 'content', 'mp_');
this.overrideAttr(this.$wbwindow.HTMLFormElement.prototype, 'action', 'mp_');
this.overrideAttr(this.$wbwindow.HTMLQuoteElement.prototype, 'cite', 'mp_');
this.overrideAttr(this.$wbwindow.HTMLModElement.prototype, 'cite', 'mp_');
// a, area tag overrides
this.overrideAnchorAreaElem(this.$wbwindow.HTMLAnchorElement);
this.overrideAnchorAreaElem(this.$wbwindow.HTMLAreaElement);
var style_proto = this.$wbwindow.CSSStyleDeclaration.prototype;
// For FF
if (this.$wbwindow.CSS2Properties) {
style_proto = this.$wbwindow.CSS2Properties.prototype;
}
this.overrideStyleAttr(style_proto, 'cssText');
this.overrideStyleAttr(style_proto, 'background', 'background');
this.overrideStyleAttr(style_proto, 'backgroundImage', 'background-image');
this.overrideStyleAttr(style_proto, 'cursor', 'cursor');
this.overrideStyleAttr(style_proto, 'listStyle', 'list-style');
this.overrideStyleAttr(style_proto, 'listStyleImage', 'list-style-image');
this.overrideStyleAttr(style_proto, 'border', 'border');
this.overrideStyleAttr(style_proto, 'borderImage', 'border-image');
this.overrideStyleAttr(
style_proto,
'borderImageSource',
'border-image-source'
);
this.overrideStyleAttr(style_proto, 'maskImage', 'mask-image');
this.overrideStyleSetProp(style_proto);
if (this.$wbwindow.CSSStyleSheet && this.$wbwindow.CSSStyleSheet.prototype) {
// https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet/insertRule
// ruleText is a string of raw css....
var wombat = this;
var oInsertRule = this.$wbwindow.CSSStyleSheet.prototype.insertRule;
this.$wbwindow.CSSStyleSheet.prototype.insertRule = function insertRule(
ruleText,
index
) {
return oInsertRule.call(this, wombat.rewriteStyle(ruleText), index);
};
}
if (this.$wbwindow.CSSRule && this.$wbwindow.CSSRule.prototype) {
this.overrideStyleAttr(this.$wbwindow.CSSRule.prototype, 'cssText');
}
};
/**
* Applies overrides to CSSStyleValue.[parse,parseAll], CSSKeywordValue, and
* StylePropertyMap in order to ensure the URLs these interfaces operate on
* are rewritten. Gotta love Chrome.
* @see https://drafts.css-houdini.org/css-typed-om-1/
*/
Wombat.prototype.initCSSOMOverrides = function() {
var wombat = this;
if (this.$wbwindow.CSSStyleValue) {
var cssStyleValueOverride = function(CSSSV, which) {
var oFN = CSSSV[which];
CSSSV[which] = function parseOrParseAllOverride(property, cssText) {
if (cssText == null) return oFN.call(this, property, cssText);
var rwCSSText = wombat.noExceptRewriteStyle(cssText);
return oFN.call(this, property, rwCSSText);
};
};
if (
this.$wbwindow.CSSStyleValue.parse &&
this.$wbwindow.CSSStyleValue.parse.toString().indexOf('[native code]') > 0
) {
cssStyleValueOverride(this.$wbwindow.CSSStyleValue, 'parse');
}
if (
this.$wbwindow.CSSStyleValue.parseAll &&
this.$wbwindow.CSSStyleValue.parseAll
.toString()
.indexOf('[native code]') > 0
) {
cssStyleValueOverride(this.$wbwindow.CSSStyleValue, 'parseAll');
}
}
if (
this.$wbwindow.CSSKeywordValue &&
this.$wbwindow.CSSKeywordValue.prototype
) {
var oCSSKV = this.$wbwindow.CSSKeywordValue;
this.$wbwindow.CSSKeywordValue = (function(CSSKeywordValue_) {
return function CSSKeywordValue(cssValue) {
return new CSSKeywordValue_(wombat.rewriteStyle(cssValue));
};
})(this.$wbwindow.CSSKeywordValue);
this.$wbwindow.CSSKeywordValue.prototype = oCSSKV.prototype;
Object.defineProperty(
this.$wbwindow.CSSKeywordValue.prototype,
'constructor',
{
value: this.$wbwindow.CSSKeywordValue
}
);
addToStringTagToClass(this.$wbwindow.CSSKeywordValue, 'CSSKeywordValue');
}
if (
this.$wbwindow.StylePropertyMap &&
this.$wbwindow.StylePropertyMap.prototype
) {
var originalSet = this.$wbwindow.StylePropertyMap.prototype.set;
this.$wbwindow.StylePropertyMap.prototype.set = function set() {
if (arguments.length <= 1) {
if (originalSet.__WB_orig_apply) {
return originalSet.__WB_orig_apply(this, arguments);
}
return originalSet.apply(this, arguments);
}
var newArgs = new Array(arguments.length);
newArgs[0] = arguments[0];
for (var i = 1; i < arguments.length; i++) {
newArgs[i] = wombat.noExceptRewriteStyle(arguments[i]);
}
if (originalSet.__WB_orig_apply) {
return originalSet.__WB_orig_apply(this, newArgs);
}
return originalSet.apply(this, newArgs);
};
var originalAppend = this.$wbwindow.StylePropertyMap.prototype.append;
this.$wbwindow.StylePropertyMap.prototype.append = function append() {
if (arguments.length <= 1) {
if (originalSet.__WB_orig_apply) {
return originalAppend.__WB_orig_apply(this, arguments);
}
return originalAppend.apply(this, arguments);
}
var newArgs = new Array(arguments.length);
newArgs[0] = arguments[0];
for (var i = 1; i < arguments.length; i++) {
newArgs[i] = wombat.noExceptRewriteStyle(arguments[i]);
}
if (originalAppend.__WB_orig_apply) {
return originalAppend.__WB_orig_apply(this, newArgs);
}
return originalAppend.apply(this, newArgs);
};
}
};
/**
* Applies an overrides to the Audio constructor in order to ensure its URL
* argument is rewritten
*/
Wombat.prototype.initAudioOverride = function() {
if (!this.$wbwindow.Audio) return;
var orig_audio = this.$wbwindow.Audio;
var wombat = this;
this.$wbwindow.Audio = (function(Audio_) {
return function Audio(url) {
return new Audio_(wombat.rewriteUrl(url, true, 'oe_'));
};
})(this.$wbwindow.Audio);
this.$wbwindow.Audio.prototype = orig_audio.prototype;
Object.defineProperty(this.$wbwindow.Audio.prototype, 'constructor', {
value: this.$wbwindow.Audio
});
addToStringTagToClass(this.$wbwindow.Audio, 'HTMLAudioElement');
};
/**
* Initializes the BAD_PREFIXES array using the supplied prefix
* @param {string} prefix
*/
Wombat.prototype.initBadPrefixes = function(prefix) {
this.BAD_PREFIXES = [
'http:' + prefix,
'https:' + prefix,
'http:/' + prefix,
'https:/' + prefix
];
};
/**
* Applies an override to crypto.getRandomValues in order to make
* the values it returns are deterministic during replay
*/
Wombat.prototype.initCryptoRandom = function() {
if (!this.$wbwindow.crypto || !this.$wbwindow.Crypto) return;
var wombat = this;
var new_getrandom = function getRandomValues(array) {
for (var i = 0; i < array.length; i++) {
array[i] = parseInt(wombat.$wbwindow.Math.random() * 4294967296);
}
return array;
};
this.$wbwindow.Crypto.prototype.getRandomValues = new_getrandom;
this.$wbwindow.crypto.getRandomValues = new_getrandom;
};
/**
* Applies an override to the Date object in order to ensure that
* all Dates used during replay are in the datetime of replay
* @param {string} timestamp
*/
Wombat.prototype.initDateOverride = function(timestamp) {
if (this.$wbwindow.__wb_Date_now) return;
var newTimestamp = parseInt(timestamp) * 1000;
// var timezone = new Date().getTimezoneOffset() * 60 * 1000;
// Already UTC!
var timezone = 0;
var start_now = this.$wbwindow.Date.now();
var timediff = start_now - (newTimestamp - timezone);
var orig_date = this.$wbwindow.Date;
var orig_utc = this.$wbwindow.Date.UTC;
var orig_parse = this.$wbwindow.Date.parse;
var orig_now = this.$wbwindow.Date.now;
this.$wbwindow.__wb_Date_now = orig_now;
this.$wbwindow.Date = (function(Date_) {
return function Date(A, B, C, D, E, F, G) {
// [native code]
// Apply doesn't work for constructors and Date doesn't
// seem to like undefined args, so must explicitly
// call constructor for each possible args 0..7
if (A === undefined) {
return new Date_(orig_now() - timediff);
} else if (B === undefined) {
return new Date_(A);
} else if (C === undefined) {
return new Date_(A, B);
} else if (D === undefined) {
return new Date_(A, B, C);
} else if (E === undefined) {
return new Date_(A, B, C, D);
} else if (F === undefined) {
return new Date_(A, B, C, D, E);
} else if (G === undefined) {
return new Date_(A, B, C, D, E, F);
} else {
return new Date_(A, B, C, D, E, F, G);
}
};
})(this.$wbwindow.Date);
this.$wbwindow.Date.prototype = orig_date.prototype;
this.$wbwindow.Date.now = function now() {
return orig_now() - timediff;
};
this.$wbwindow.Date.UTC = orig_utc;
this.$wbwindow.Date.parse = orig_parse;
this.$wbwindow.Date.__WB_timediff = timediff;
Object.defineProperty(this.$wbwindow.Date.prototype, 'constructor', {
value: this.$wbwindow.Date
});
};
/**
* Applies an override to the document.title property in order to ensure
* that actual top (archive top frame containing the replay iframe) receives
* document.title updates
*/
Wombat.prototype.initDocTitleOverride = function() {
var orig_get_title = this.getOrigGetter(this.$wbwindow.document, 'title');
var orig_set_title = this.getOrigSetter(this.$wbwindow.document, 'title');
var wombat = this;
var set_title = function title(value) {
var res = orig_set_title.call(this, value);
var message = { wb_type: 'title', title: value };
wombat.sendTopMessage(message);
return res;
};
this.defProp(this.$wbwindow.document, 'title', set_title, orig_get_title);
};
/**
* Applies an override to the FontFace constructor in order to ensure font URLs
* are rewritten
* @see https://drafts.csswg.org/css-font-loading/#FontFace-interface
*/
Wombat.prototype.initFontFaceOverride = function() {
if (!this.$wbwindow.FontFace || this.$wbwindow.FontFace.__wboverriden__) {
return;
}
var wombat = this;
var origFontFace = this.$wbwindow.FontFace;
this.$wbwindow.FontFace = (function(FontFace_) {
return function FontFace(family, source, descriptors) {
var rwSource = source;
if (source != null) {
if (typeof source !== 'string') {
// is CSSOMString or ArrayBuffer or ArrayBufferView
rwSource = wombat.rewriteInlineStyle(source.toString());
} else {
rwSource = wombat.rewriteInlineStyle(source);
}
}
return new FontFace_(family, rwSource, descriptors);
};
})(this.$wbwindow.FontFace);
this.$wbwindow.FontFace.prototype = origFontFace.prototype;
Object.defineProperty(this.$wbwindow.FontFace.prototype, 'constructor', {
value: this.$wbwindow.FontFace
});
this.$wbwindow.FontFace.__wboverriden__ = true;
addToStringTagToClass(this.$wbwindow.FontFace, 'FontFace');
};
/**
* Forces, when possible, the devicePixelRatio property of window to 1
* in order to ensure deterministic replay
*/
Wombat.prototype.initFixedRatio = function() {
try {
// otherwise, just set it
this.$wbwindow.devicePixelRatio = 1;
} catch (e) {}
// prevent changing, if possible
if (Object.defineProperty) {
try {
// fixed pix ratio
Object.defineProperty(this.$wbwindow, 'devicePixelRatio', {
value: 1,
writable: false
});
} catch (e) {}
}
};
/**
* Initializes wombats path information from the supplied wbinfo object
* @param {Object} wbinfo
*/
Wombat.prototype.initPaths = function(wbinfo) {
wbinfo.wombat_opts = wbinfo.wombat_opts || {};
this.wb_info = wbinfo;
this.wb_opts = wbinfo.wombat_opts;
this.wb_replay_prefix = wbinfo.prefix;
this.wb_is_proxy = wbinfo.proxy_magic || !this.wb_replay_prefix;
this.wb_info.top_host = this.wb_info.top_host || '*';
this.wb_curr_host =
this.$wbwindow.location.protocol + '//' + this.$wbwindow.location.host;
this.wb_orig_scheme = wbinfo.wombat_scheme + '://';
this.wb_orig_origin = this.wb_orig_scheme + wbinfo.wombat_host;
this.wb_abs_prefix = this.wb_replay_prefix;
if (!wbinfo.is_live && wbinfo.wombat_ts) {
this.wb_capture_date_part = '/' + wbinfo.wombat_ts + '/';
} else {
this.wb_capture_date_part = '';
}
this.initBadPrefixes(this.wb_replay_prefix);
};
/**
* Applies an override to Math.seed and Math.random using the supplied
* seed in order to ensure that random numbers are deterministic during
* replay
* @param {string} seed
*/
Wombat.prototype.initSeededRandom = function(seed) {
// Adapted from:
// http://indiegamr.com/generate-repeatable-random-numbers-in-js/
this.$wbwindow.Math.seed = parseInt(seed);
var wombat = this;
this.$wbwindow.Math.random = function random() {
wombat.$wbwindow.Math.seed =
(wombat.$wbwindow.Math.seed * 9301 + 49297) % 233280;
return wombat.$wbwindow.Math.seed / 233280;
};
};
/**
* Applies overrides to history.pushState and history.replaceState in order
* to ensure that URLs used for browser history manipulation are rewritten.
* Also adds a `popstate` listener to window of the browser context wombat is in
* in order to ensure that actual top (archive top frame containing the replay iframe)
* browser history is updated IFF the history manipulation happens in the replay top
*/
Wombat.prototype.initHistoryOverrides = function() {
this.overrideHistoryFunc('pushState');
this.overrideHistoryFunc('replaceState');
var wombat = this;
this.$wbwindow.addEventListener('popstate', function(event) {
wombat.sendHistoryUpdate(
wombat.$wbwindow.WB_wombat_location.href,
wombat.$wbwindow.document.title
);
});
};
/**
* Applies overrides to the XMLHttpRequest.open and XMLHttpRequest.responseURL
* in order to ensure URLs are rewritten.
*
* Applies an override to window.fetch in order to rewrite URLs and URLs of
* the supplied Request objects used as arguments to fetch.
*
* Applies overrides to window.Request, window.Response, window.EventSource,
* and window.WebSocket in order to ensure URLs they operate on are rewritten.
*
* @see https://xhr.spec.whatwg.org/
* @see https://fetch.spec.whatwg.org/
* @see https://html.spec.whatwg.org/multipage/web-sockets.html#websocket
* @see https://html.spec.whatwg.org/multipage/server-sent-events.html#the-eventsource-interface
*/
Wombat.prototype.initHTTPOverrides = function() {
var wombat = this;
if (
this.$wbwindow.XMLHttpRequest &&
this.$wbwindow.XMLHttpRequest.prototype
) {
if (this.$wbwindow.XMLHttpRequest.prototype.open) {
var origXMLHttpOpen = this.$wbwindow.XMLHttpRequest.prototype.open;
this.utilFns.XHRopen = origXMLHttpOpen;
this.$wbwindow.XMLHttpRequest.prototype.open = function open(
method,
url,
async,
user,
password
) {
var rwURL = !this._no_rewrite ? wombat.rewriteUrl(url) : url;
var openAsync = true;
if (async != null && !async) openAsync = false;
origXMLHttpOpen.call(this, method, rwURL, openAsync, user, password);
if (!wombat.startsWith(rwURL, 'data:')) {
this.setRequestHeader('X-Pywb-Requested-With', 'XMLHttpRequest');
}
};
}
// responseURL override
this.overridePropExtract(
this.$wbwindow.XMLHttpRequest.prototype,
'responseURL'
);
}
if (this.$wbwindow.fetch) {
var orig_fetch = this.$wbwindow.fetch;
this.$wbwindow.fetch = function fetch(input, init_opts) {
var rwInput = input;
var inputType = typeof input;
if (inputType === 'string') {
rwInput = wombat.rewriteUrl(input);
} else if (inputType === 'object' && input.url) {
var new_url = wombat.rewriteUrl(input.url);
if (new_url !== input.url) {
rwInput = new Request(new_url, input);
}
} else if (inputType === 'object' && input.href) {
// it is likely that input is either window.location or window.URL
rwInput = wombat.rewriteUrl(input.href);
}
init_opts = init_opts || {};
init_opts['credentials'] = 'include';
return orig_fetch.call(wombat.proxyToObj(this), rwInput, init_opts);
};
}
if (this.$wbwindow.Request && this.$wbwindow.Request.prototype) {
var orig_request = this.$wbwindow.Request;
this.$wbwindow.Request = (function(Request_) {
return function Request(input, init_opts) {
var newInitOpts = init_opts || {};
var newInput = input;
var inputType = typeof input;
switch (inputType) {
case 'string':
newInput = wombat.rewriteUrl(input);
break;
case 'object':
newInput = input;
if (input.url) {
var new_url = wombat.rewriteUrl(input.url);
if (new_url !== input.url) {
// not much we can do here Request.url is read only
// https://developer.mozilla.org/en-US/docs/Web/API/Request/url
newInput = new Request_(new_url, input);
}
} else if (input.href) {
// it is likely that input is either window.location or window.URL
newInput = wombat.rewriteUrl(input.toString(), true);
}
break;
}
newInitOpts['credentials'] = 'include';
return new Request_(newInput, newInitOpts);
};
})(this.$wbwindow.Request);
this.$wbwindow.Request.prototype = orig_request.prototype;
}
if (this.$wbwindow.Response && this.$wbwindow.Response.prototype) {
// https://developer.mozilla.org/en-US/docs/Web/API/Response/redirect
var originalRedirect = this.$wbwindow.Response.prototype.redirect;
this.$wbwindow.Response.prototype.redirect = function redirect(
url,
status
) {
var rwURL = wombat.rewriteUrl(url, true, null, wombat.$wbwindow.document);
return originalRedirect.call(this, rwURL, status);
};
}
if (this.$wbwindow.EventSource && this.$wbwindow.EventSource.prototype) {
var origEventSource = this.$wbwindow.EventSource;
this.$wbwindow.EventSource = (function(EventSource_) {
return function EventSource(url, configuration) {
var rwURL = url;
if (url != null) {
rwURL = wombat.rewriteUrl(url);
}
return new EventSource_(rwURL, configuration);
};
})(this.$wbwindow.EventSource);
this.$wbwindow.EventSource.prototype = origEventSource.prototype;
Object.defineProperty(this.$wbwindow.EventSource.prototype, 'constructor', {
value: this.$wbwindow.EventSource
});
addToStringTagToClass(this.$wbwindow.EventSource, 'EventSource');
}
if (this.$wbwindow.WebSocket && this.$wbwindow.WebSocket.prototype) {
var origWebSocket = this.$wbwindow.WebSocket;
this.$wbwindow.WebSocket = (function(WebSocket_) {
return function WebSocket(url, configuration) {
var rwURL = url;
if (url != null) {
rwURL = wombat.rewriteWSURL(url);
}
return new WebSocket_(rwURL, configuration);
};
})(this.$wbwindow.WebSocket);
this.$wbwindow.WebSocket.prototype = origWebSocket.prototype;
Object.defineProperty(this.$wbwindow.WebSocket.prototype, 'constructor', {
value: this.$wbwindow.WebSocket
});
addToStringTagToClass(this.$wbwindow.WebSocket, 'WebSocket');
}
};
/**
* Applies an override to Element.[getAttribute, setAttribute] in order to
* ensure that operations on properties that contain URLs are rewritten
* @see https://www.w3.org/TR/dom/#interface-element
*/
Wombat.prototype.initElementGetSetAttributeOverride = function() {
if (
this.wb_opts.skip_setAttribute ||
(!this.$wbwindow.Element || !this.$wbwindow.Element.prototype)
) {
return;
}
var wombat = this;
var ElementProto = this.$wbwindow.Element.prototype;
if (ElementProto.setAttribute) {
var orig_setAttribute = ElementProto.setAttribute;
ElementProto._orig_setAttribute = orig_setAttribute;
ElementProto.setAttribute = function setAttribute(name, value) {
var rwValue = value;
if (name && typeof rwValue === 'string') {
var lowername = name.toLowerCase();
if (
this.tagName === 'LINK' &&
lowername === 'href' &&
rwValue.indexOf('data:text/css') === 0
) {
rwValue = wombat.rewriteInlineStyle(value);
} else if (lowername === 'style') {
rwValue = wombat.rewriteStyle(value);
} else if (lowername === 'srcset') {
rwValue = wombat.rewriteSrcset(value, this);
} else {
var shouldRW = wombat.shouldRewriteAttr(this.tagName, lowername);
if (shouldRW) {
wombat.removeWBOSRC(this);
if (!this._no_rewrite) {
rwValue = wombat.rewriteUrl(
value,
false,
wombat.rwModForElement(this, lowername)
);
}
}
}
}
return orig_setAttribute.call(this, name, rwValue);
};
}
if (ElementProto.getAttribute) {
var orig_getAttribute = ElementProto.getAttribute;
this.wb_getAttribute = orig_getAttribute;
ElementProto.getAttribute = function getAttribute(name) {
var result = orig_getAttribute.call(this, name);
var lowerName = name;
if (name) {
lowerName = name.toLowerCase();
}
if (wombat.shouldRewriteAttr(this.tagName, lowerName)) {
var maybeWBOSRC = wombat.retrieveWBOSRC(this);
if (maybeWBOSRC) return maybeWBOSRC;
return wombat.extractOriginalURL(result);
} else if (
wombat.startsWith(lowerName, 'data-') &&
wombat.startsWithOneOf(result, wombat.VALID_PREFIXES)
) {
return wombat.extractOriginalURL(result);
}
return result;
};
}
};
/**
* Applies an override to the getAttribute[NS] and setAttribute[NS] functions
* of the SVGImageElement interface in order to ensure that the URLs of the
* href and xlink:href properties are rewritten
*/
Wombat.prototype.initSvgImageOverrides = function() {
if (!this.$wbwindow.SVGImageElement) {
return;
}
var svgImgProto = this.$wbwindow.SVGImageElement.prototype;
var orig_getAttr = svgImgProto.getAttribute;
var orig_getAttrNS = svgImgProto.getAttributeNS;
var orig_setAttr = svgImgProto.setAttribute;
var orig_setAttrNS = svgImgProto.setAttributeNS;
var wombat = this;
svgImgProto.getAttribute = function getAttribute(name) {
var value = orig_getAttr.call(this, name);
if (name.indexOf('xlink:href') >= 0 || name === 'href') {
return wombat.extractOriginalURL(value);
}
return value;
};
svgImgProto.getAttributeNS = function getAttributeNS(ns, name) {
var value = orig_getAttrNS.call(this, ns, name);
if (name.indexOf('xlink:href') >= 0 || name === 'href') {
return wombat.extractOriginalURL(value);
}
return value;
};
svgImgProto.setAttribute = function setAttribute(name, value) {
var rwValue = value;
if (name.indexOf('xlink:href') >= 0 || name === 'href') {
rwValue = wombat.rewriteUrl(value);
}
return orig_setAttr.call(this, name, rwValue);
};
svgImgProto.setAttributeNS = function setAttributeNS(ns, name, value) {
var rwValue = value;
if (name.indexOf('xlink:href') >= 0 || name === 'href') {
rwValue = wombat.rewriteUrl(value);
}
return orig_setAttrNS.call(this, ns, name, rwValue);
};
};
/**
* Applies an override to document.createElementNS in order to ensure that the
* nameSpaceURI argument is un-rewritten
*/
Wombat.prototype.initCreateElementNSFix = function() {
if (
!this.$wbwindow.document.createElementNS ||
!this.$wbwindow.Document.prototype.createElementNS
) {
return;
}
var orig_createElementNS = this.$wbwindow.document.createElementNS;
var wombat = this;
var createElementNS = function createElementNS(namespaceURI, qualifiedName) {
return orig_createElementNS.call(
wombat.proxyToObj(this),
wombat.extractOriginalURL(namespaceURI),
qualifiedName
);
};
this.$wbwindow.Document.prototype.createElementNS = createElementNS;
this.$wbwindow.document.createElementNS = createElementNS;
};
/**
* Applies an override to Element.insertAdjacentHTML in order to ensure
* that the strings of HTML to be inserted are rewritten and to
* Element.insertAdjacentElement in order to ensure that the Elements to
* be inserted are rewritten
*/
Wombat.prototype.initInsertAdjacentElementHTMLOverrides = function() {
var Element = this.$wbwindow.Element;
if (!Element || !Element.prototype) return;
var elementProto = Element.prototype;
var rewriteFn = this.rewriteInsertAdjHTMLOrElemArgs;
if (elementProto.insertAdjacentHTML) {
var origInsertAdjacentHTML = elementProto.insertAdjacentHTML;
elementProto.insertAdjacentHTML = function insertAdjacentHTML(
position,
text
) {
return rewriteFn(this, origInsertAdjacentHTML, position, text, true);
};
}
if (elementProto.insertAdjacentElement) {
var origIAdjElem = elementProto.insertAdjacentElement;
elementProto.insertAdjacentElement = function insertAdjacentElement(
position,
element
) {
return rewriteFn(this, origIAdjElem, position, element, false);
};
}
};
/**
* Applies overrides to Node.[appendChild, insertBefore, replaceChild] and
* [Element, DocumentFragment].[append, prepend) in order to ensure the that
* the elements added by these functions are rewritten.
* Also applies an override to the Node.ownerDocument, HTMLHtmlElement.parentNode,
* and Event.target getter functions do not return a JS Proxy object used by wombat
* @see https://www.w3.org/TR/dom/#node
*/
Wombat.prototype.initDomOverride = function() {
var Node = this.$wbwindow.Node;
if (Node && Node.prototype) {
var rewriteFn = this.rewriteNodeFuncArgs;
if (Node.prototype.appendChild) {
var originalAppendChild = Node.prototype.appendChild;
Node.prototype.appendChild = function appendChild(newNode, oldNode) {
return rewriteFn(this, originalAppendChild, newNode, oldNode);
};
}
if (Node.prototype.insertBefore) {
var originalInsertBefore = Node.prototype.insertBefore;
Node.prototype.insertBefore = function insertBefore(newNode, oldNode) {
return rewriteFn(this, originalInsertBefore, newNode, oldNode);
};
}
if (Node.prototype.replaceChild) {
var originalReplaceChild = Node.prototype.replaceChild;
Node.prototype.replaceChild = function replaceChild(newNode, oldNode) {
return rewriteFn(this, originalReplaceChild, newNode, oldNode);
};
}
this.overridePropToProxy(Node.prototype, 'ownerDocument');
this.overridePropToProxy(
this.$wbwindow.HTMLHtmlElement.prototype,
'parentNode'
);
this.overridePropToProxy(this.$wbwindow.Event.prototype, 'target');
}
if (this.$wbwindow.Element && this.$wbwindow.Element.prototype) {
this.overrideParentNodeAppendPrepend(this.$wbwindow.Element);
this.overrideChildNodeInterface(this.$wbwindow.Element, false);
}
if (
this.$wbwindow.DocumentFragment &&
this.$wbwindow.DocumentFragment.prototype
) {
this.overrideParentNodeAppendPrepend(this.$wbwindow.DocumentFragment);
}
};
/**
* Applies overrides to document.referrer, document.origin, document.domain, and
* window.origin in order to ensure their getters and setters behave as expected
* on the live web
* @param {Document} $document
*/
Wombat.prototype.initDocOverrides = function($document) {
if (!Object.defineProperty) return;
// referrer
this.overridePropExtract($document, 'referrer');
// origin
this.defGetterProp($document, 'origin', function origin() {
return this.WB_wombat_location.origin;
});
// https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/origin, chrome 59+ and ff 54+
this.defGetterProp(this.$wbwindow, 'origin', function origin() {
return this.WB_wombat_location.origin;
});
var wombat = this;
// domain
var domain_setter = function domain(val) {
var loc = this.WB_wombat_location;
if (loc && wombat.endsWith(loc.hostname, val)) {
this.__wb_domain = val;
}
};
var domain_getter = function domain() {
return this.__wb_domain || this.WB_wombat_location.hostname;
};
this.defProp($document, 'domain', domain_setter, domain_getter);
};
/**
* Apples overrides to document.[write, writeln, open, close] in order
* to ensure that the values they operate on or create are rewritten and
* wombat is initialized in the new documents/windows.
* @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html
* @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-document-open-window
* @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-document-close
* @see https://html.spec.whatwg.org/multipage/dom.html#dom-document-body
*/
Wombat.prototype.initDocWriteOpenCloseOverride = function() {
if (!this.$wbwindow.DOMParser) {
return;
}
// for both document.write and document.writeln, all arguments are treated as a string and concatenated together
// https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html
var DocumentProto = this.$wbwindow.Document.prototype;
var $wbDocument = this.$wbwindow.document;
var docWriteWritelnRWFn = this.rewriteDocWriteWriteln;
// Write
var orig_doc_write = $wbDocument.write;
var new_write = function write() {
return docWriteWritelnRWFn(this, orig_doc_write, arguments);
};
$wbDocument.write = new_write;
DocumentProto.write = new_write;
// Writeln
var orig_doc_writeln = $wbDocument.writeln;
var new_writeln = function writeln() {
return docWriteWritelnRWFn(this, orig_doc_writeln, arguments);
};
$wbDocument.writeln = new_writeln;
DocumentProto.writeln = new_writeln;
var wombat = this;
// Open
var orig_doc_open = $wbDocument.open;
var new_open = function open() {
var thisObj = wombat.proxyToObj(this);
var res;
if (arguments.length === 3) {
// we have the case where a new window will be opened
// https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-document-open-window
var rwUrl = wombat.rewriteUrl(arguments[0], false, 'mp_');
res = orig_doc_open.call(thisObj, rwUrl, arguments[1], arguments[2]);
wombat.initNewWindowWombat(res, rwUrl);
} else {
res = orig_doc_open.call(thisObj);
wombat.initNewWindowWombat(thisObj.defaultView);
}
return res;
};
$wbDocument.open = new_open;
DocumentProto.open = new_open;
// we override close in order to ensure wombat is init'd
// https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-document-close
var originalClose = $wbDocument.close;
var newClose = function close() {
var thisObj = wombat.proxyToObj(this);
wombat.initNewWindowWombat(thisObj.defaultView);
if (originalClose.__WB_orig_apply) {
return originalClose.__WB_orig_apply(thisObj, arguments);
}
return originalClose.apply(thisObj, arguments);
};
$wbDocument.close = newClose;
DocumentProto.close = newClose;
// we override the setter for document.body because it is settable
// to either an instance of HTMLBodyElement or HTMLFrameSetElement and
// there are ways to get un-rewritten elements into replay we must allow
// https://html.spec.whatwg.org/multipage/dom.html#dom-document-body
var oBodyGetter = this.getOrigGetter(DocumentProto, 'body');
var oBodySetter = this.getOrigSetter(DocumentProto, 'body');
if (oBodyGetter && oBodySetter) {
this.defProp(
DocumentProto,
'body',
function body(newBody) {
if (
newBody &&
(newBody instanceof HTMLBodyElement ||
newBody instanceof HTMLFrameSetElement)
) {
wombat.rewriteElemComplete(newBody);
}
return oBodySetter.call(wombat.proxyToObj(this), newBody);
},
oBodyGetter
);
}
};
/**
* Inits wombat in the supplied iframe
* @param {HTMLIFrameElement} iframe
*/
Wombat.prototype.initIframeWombat = function(iframe) {
var win;
if (iframe._get_contentWindow) {
win = iframe._get_contentWindow.call(iframe); // eslint-disable-line no-useless-call
} else {
win = iframe.contentWindow;
}
try {
if (!win || win === this.$wbwindow || win._skip_wombat || win._wb_wombat) {
return;
}
} catch (e) {
return;
}
// var src = iframe.src;
var src = this.wb_getAttribute.call(iframe, 'src');
this.initNewWindowWombat(win, src);
};
/**
* Initializes wombat in the supplied window IFF the src URL of the window is
* not the empty string, about:blank, or a "javascript:" URL
* @param {Window} win
* @param {string} [src]
*/
Wombat.prototype.initNewWindowWombat = function(win, src) {
this.ensureServerSideInjectsExistOnWindow(win);
if (!win || win._wb_wombat) return;
if (
!src ||
src === '' ||
src === 'about:blank' ||
src.indexOf('javascript:') >= 0
) {
// win._WBWombat = wombat_internal(win);
// win._wb_wombat = new win._WBWombat(wb_info);
var wombat = new Wombat(win, this.wb_info);
win._wb_wombat = wombat.wombatInit();
} else {
// These should get overriden when content is loaded, but just in case...
// win._WB_wombat_location = win.location;
// win.document.WB_wombat_location = win.document.location;
// win._WB_wombat_top = $wbwindow.WB_wombat_top;
this.initProtoPmOrigin(win);
this.initPostMessageOverride(win);
this.initMessageEventOverride(win);
}
};
/**
* Applies an override to either window.[setTimeout, setInterval] functions
* in order to ensure that usage such as [setTimeout|setInterval]('document.location.href = "xyz.com"', time)
* behaves as expected during replay.
*
* In this case the supplied string is eval'd in the current context skipping
* the surrounding scope
*/
Wombat.prototype.initTimeoutIntervalOverrides = function() {
var rewriteFn = this.rewriteSetTimeoutInterval;
if (this.$wbwindow.setTimeout && !this.$wbwindow.setTimeout.__$wbpatched$__) {
var originalSetTimeout = this.$wbwindow.setTimeout;
this.$wbwindow.setTimeout = function setTimeout() {
return rewriteFn(this, originalSetTimeout, arguments);
};
this.$wbwindow.setTimeout.__$wbpatched$__ = true;
}
if (
this.$wbwindow.setInterval &&
!this.$wbwindow.setInterval.__$wbpatched$__
) {
var originalSetInterval = this.$wbwindow.setInterval;
this.$wbwindow.setInterval = function setInterval() {
return rewriteFn(this, originalSetInterval, arguments);
};
this.$wbwindow.setInterval.__$wbpatched$__ = true;
}
};
/**
* Applies an overrides to the constructor of window.[Worker, SharedWorker] in
* order to ensure that the URL argument is rewritten.
*
* Applies an override to ServiceWorkerContainer.register in order to ensure
* that the URLs used in ServiceWorker registration are rewritten.
*
* Applies an override to Worklet.addModule in order to ensure that URL
* to the worklet module is rewritten
* @see https://html.spec.whatwg.org/multipage/workers.html
* @see https://w3c.github.io/ServiceWorker/
* @see https://drafts.css-houdini.org/worklets/#worklet
*/
Wombat.prototype.initWorkerOverrides = function() {
var wombat = this;
if (this.$wbwindow.Worker && !this.$wbwindow.Worker._wb_worker_overriden) {
// Worker unrewrite postMessage
var orig_worker = this.$wbwindow.Worker;
this.$wbwindow.Worker = (function(Worker_) {
return function Worker(url) {
return new Worker_(wombat.rewriteWorker(url));
};
})(orig_worker);
this.$wbwindow.Worker.prototype = orig_worker.prototype;
this.$wbwindow.Worker._wb_worker_overriden = true;
}
if (
this.$wbwindow.SharedWorker &&
!this.$wbwindow.SharedWorker.__wb_sharedWorker_overriden
) {
// per https://html.spec.whatwg.org/multipage/workers.html#sharedworker
var oSharedWorker = this.$wbwindow.SharedWorker;
this.$wbwindow.SharedWorker = (function(SharedWorker_) {
return function SharedWorker(url) {
return new SharedWorker_(wombat.rewriteWorker(url));
};
})(oSharedWorker);
this.$wbwindow.SharedWorker.prototype = oSharedWorker.prototype;
this.$wbwindow.SharedWorker.__wb_sharedWorker_overriden = true;
}
if (
this.$wbwindow.ServiceWorkerContainer &&
this.$wbwindow.ServiceWorkerContainer.prototype &&
this.$wbwindow.ServiceWorkerContainer.prototype.register
) {
// https://w3c.github.io/ServiceWorker/
var orig_register = this.$wbwindow.ServiceWorkerContainer.prototype
.register;
this.$wbwindow.ServiceWorkerContainer.prototype.register = function register(
scriptURL,
options
) {
var newScriptURL = new URL(scriptURL, wombat.$wbwindow.document.baseURI)
.href;
var mod = wombat.getPageUnderModifier();
if (options && options.scope) {
options.scope = wombat.rewriteUrl(options.scope, false, mod);
} else {
options = { scope: wombat.rewriteUrl('/', false, mod) };
}
return orig_register.call(
this,
wombat.rewriteUrl(newScriptURL, false, 'sw_'),
options
);
};
}
if (
this.$wbwindow.Worklet &&
this.$wbwindow.Worklet.prototype &&
this.$wbwindow.Worklet.prototype.addModule &&
!this.$wbwindow.Worklet.__wb_workerlet_overriden
) {
// https://developer.mozilla.org/en-US/docs/Web/API/Worklet/addModule
var oAddModule = this.$wbwindow.Worklet.prototype.addModule;
this.$wbwindow.Worklet.prototype.addModule = function addModule(
moduleURL,
options
) {
var rwModuleURL = wombat.rewriteUrl(moduleURL, false, 'js_');
return oAddModule.call(this, rwModuleURL, options);
};
this.$wbwindow.Worklet.__wb_workerlet_overriden = true;
}
};
/**
* Applies overrides to the getter setter functions of the supplied object
* for the properties defined in {@link Wombat#URL_PROPS} IFF
* Object.defineProperty is defined
* @param {Object} loc
* @param {function} oSetter
* @param {function} oGetter
*/
Wombat.prototype.initLocOverride = function(loc, oSetter, oGetter) {
if (Object.defineProperty) {
for (var i = 0; i < this.URL_PROPS.length; i++) {
var prop = this.URL_PROPS[i];
this.defProp(
loc,
prop,
this.makeSetLocProp(prop, oSetter, oGetter),
this.makeGetLocProp(prop, oGetter),
true
);
}
}
};
/**
* Initialized WombatLocation on the supplied window object and adds the
* __WB_pmw function on the window, as well as, defines WB_wombat_location
* as property on the prototype of Object and adds the _WB_wombat_location
* and __WB_check_loc properties to the supplied window
* @param {Window} win
*/
Wombat.prototype.initWombatLoc = function(win) {
if (!win || (win.WB_wombat_location && win.document.WB_wombat_location)) {
return;
}
// Location
var wombat_location = new WombatLocation(win.location, this);
if (Object.defineProperty) {
var setter = function location(value) {
var loc =
this._WB_wombat_location ||
(this.defaultView && this.defaultView._WB_wombat_location) ||
this.location;
if (loc) {
loc.href = value;
}
};
var getter = function location() {
return (
this._WB_wombat_location ||
(this.defaultView && this.defaultView._WB_wombat_location) ||
this.location
);
};
this.defProp(win.Object.prototype, 'WB_wombat_location', setter, getter);
this.initProtoPmOrigin(win);
win._WB_wombat_location = wombat_location;
} else {
win.WB_wombat_location = wombat_location;
// Check quickly after page load
setTimeout(this.checkAllLocations, 500);
// Check periodically every few seconds
setInterval(this.checkAllLocations, 500);
}
};
/**
* Adds the __WB_pmw property to prototype of Object and adds the
* __WB_check_loc property to window
* @param {Window} win
*/
Wombat.prototype.initProtoPmOrigin = function(win) {
if (win.Object.prototype.__WB_pmw) return;
var pm_origin = function pm_origin(origin_window) {
this.__WB_source = origin_window;
return this;
};
try {
win.Object.defineProperty(win.Object.prototype, '__WB_pmw', {
get: function() {
return pm_origin;
},
set: function() {},
configurable: true,
enumerable: false
});
} catch (e) {}
win.__WB_check_loc = function(loc) {
if (loc instanceof Location || loc instanceof WombatLocation) {
return this.WB_wombat_location;
} else {
return {};
}
};
};
/**
* Adds listeners for `message` and `hashchange` to window of the browser context wombat is in
* in order to ensure that actual top (archive top frame containing the replay iframe)
* browser history is updated IFF the history manipulation happens in the replay top
*/
Wombat.prototype.initHashChange = function() {
if (!this.$wbwindow.__WB_top_frame) return;
var wombat = this;
var receive_hash_change = function receive_hash_change(event) {
var source = wombat.proxyToObj(event.source);
if (!event.data || source !== wombat.$wbwindow.__WB_top_frame) {
return;
}
var message = event.data;
if (!message.wb_type) return;
if (message.wb_type === 'outer_hashchange') {
if (wombat.$wbwindow.location.hash !== message.hash) {
wombat.$wbwindow.location.hash = message.hash;
}
}
};
var send_hash_change = function send_hash_change() {
var message = {
wb_type: 'hashchange',
hash: wombat.$wbwindow.location.hash
};
wombat.sendTopMessage(message);
};
this.$wbwindow.addEventListener('message', receive_hash_change);
this.$wbwindow.addEventListener('hashchange', send_hash_change);
};
/**
* Overrides window.postMessage in order to ensure that messages sent
* via this function are routed to the correct window, especially that
* messages sent to the "top frame" do not go to archive top but replay top.
*
* This function also applies an override to EventTarget.[addEventListener, removeEventListener]
* to ensure that listening to events behaves correctly during replay.
*
* This function is the place where the `onmessage` and `onstorage` setter functions
* are overriden.
* @param {Window} $wbwindow
*/
Wombat.prototype.initPostMessageOverride = function($wbwindow) {
if (!$wbwindow.postMessage || $wbwindow.__orig_postMessage) {
return;
}
var orig = $wbwindow.postMessage;
var wombat = this;
$wbwindow.__orig_postMessage = orig;
// use this_obj.__WB_source not window to fix google calendar embeds, pm_origin sets this.__WB_source
var postmessage_rewritten = function postMessage(
message,
targetOrigin,
transfer,
from_top
) {
var from;
var src_id;
var this_obj = wombat.proxyToObj(this);
if (this_obj.__WB_source && this_obj.__WB_source.WB_wombat_location) {
var source = this_obj.__WB_source;
from = source.WB_wombat_location.origin;
if (!this_obj.__WB_win_id) {
this_obj.__WB_win_id = {};
this_obj.__WB_counter = 0;
}
if (!source.__WB_id) {
this_obj.__WB_counter += 1;
source.__WB_id = this_obj.__WB_counter + source.WB_wombat_location.href;
}
this_obj.__WB_win_id[source.__WB_id] = source;
src_id = source.__WB_id;
this_obj.__WB_source = undefined;
} else {
from = window.WB_wombat_location.origin;
}
var to_origin = targetOrigin;
// if passed in origin is the replay (rewriting missed somewhere?)
// set origin to current 'from' origin
if (to_origin === this_obj.location.origin) {
to_origin = from;
}
var new_message = {
from: from,
to_origin: to_origin,
src_id: src_id,
message: message,
from_top: from_top
};
// set to 'real' origin if not '*'
if (targetOrigin !== '*') {
// if target origin is null (about:blank) or empty, don't pass event at all
// as it would never succeed
if (
this_obj.location.origin === 'null' ||
this_obj.location.origin === ''
) {
return;
}
// set to actual (rewritten) origin
targetOrigin = this_obj.location.origin;
}
// console.log("Sending " + from + " -> " + to + " (" + targetOrigin + ") " + message);
return orig.call(this_obj, new_message, targetOrigin, transfer);
};
$wbwindow.postMessage = postmessage_rewritten;
$wbwindow.Window.prototype.postMessage = postmessage_rewritten;
var eventTarget = null;
if ($wbwindow.EventTarget && $wbwindow.EventTarget.prototype) {
eventTarget = $wbwindow.EventTarget.prototype;
} else {
eventTarget = $wbwindow;
}
// ADD
var _oAddEventListener = eventTarget.addEventListener;
eventTarget.addEventListener = function addEventListener(
type,
listener,
useCapture
) {
var obj = wombat.proxyToObj(this);
var rwListener = listener;
if (type === 'message') {
rwListener = wombat.message_listeners.add_or_get(listener, function() {
return wrapEventListener(listener, obj, wombat);
});
} else if (type === 'storage') {
wombat.storage_listeners.add_or_get(listener, function() {
return wrapSameOriginEventListener(listener, obj);
});
}
if (rwListener) {
return _oAddEventListener.call(obj, type, rwListener, useCapture);
}
};
// REMOVE
var _oRemoveEventListener = eventTarget.removeEventListener;
eventTarget.removeEventListener = function removeEventListener(
type,
listener,
useCapture
) {
var obj = wombat.proxyToObj(this);
var rwListener = listener;
if (type === 'message') {
rwListener = wombat.message_listeners.remove(listener);
} else if (type === 'storage') {
wombat.storage_listeners.remove(listener);
}
if (rwListener) {
return _oRemoveEventListener.call(obj, type, rwListener, useCapture);
}
};
// ONMESSAGE & ONSTORAGE
var override_on_prop = function(onevent, wrapperFN) {
// var orig_getter = _wombat.getOrigGetter($wbwindow, onevent)
var orig_setter = wombat.getOrigSetter($wbwindow, onevent);
var setter = function(value) {
this['__orig_' + onevent] = value;
var obj = wombat.proxyToObj(this);
var listener = value ? wrapperFN(value, obj, wombat) : value;
return orig_setter.call(obj, listener);
};
var getter = function() {
return this['__orig_' + onevent];
};
wombat.defProp($wbwindow, onevent, setter, getter);
};
override_on_prop('onmessage', wrapEventListener);
override_on_prop('onstorage', wrapSameOriginEventListener);
};
/**
* Applies overrides to the MessageEvent.[target, srcElement, currentTarget, eventPhase, path, source]
* in order to ensure they are not a JS Proxy used by wombat
* @param {Window} $wbwindow
*/
Wombat.prototype.initMessageEventOverride = function($wbwindow) {
if (!$wbwindow.MessageEvent || $wbwindow.MessageEvent.prototype.__extended) {
return;
}
this.addEventOverride('target');
this.addEventOverride('srcElement');
this.addEventOverride('currentTarget');
this.addEventOverride('eventPhase');
this.addEventOverride('path');
this.overridePropToProxy($wbwindow.MessageEvent.prototype, 'source');
$wbwindow.MessageEvent.prototype.__extended = true;
};
/**
* Applies overrides to the constructors
* - UIEvent
* - MouseEvent
* - TouchEvent
* - KeyboardEvent
* - WheelEvent
* - InputEvent
* - CompositionEvent
*
* in order to ensure the proper behavior of the events when wombat is using
* an JS Proxy
*/
Wombat.prototype.initUIEventsOverrides = function() {
this.overrideAnUIEvent('UIEvent');
this.overrideAnUIEvent('MouseEvent');
this.overrideAnUIEvent('TouchEvent');
this.overrideAnUIEvent('FocusEvent');
this.overrideAnUIEvent('KeyboardEvent');
this.overrideAnUIEvent('WheelEvent');
this.overrideAnUIEvent('InputEvent');
this.overrideAnUIEvent('CompositionEvent');
};
/**
* Applies an override to window.open in order to ensure the URL argument is rewritten.
* Also applies the same override to the open function of all frames returned by
* window.frames
*/
Wombat.prototype.initOpenOverride = function() {
var orig = this.$wbwindow.open;
if (this.$wbwindow.Window.prototype.open) {
orig = this.$wbwindow.Window.prototype.open;
}
var wombat = this;
var open_rewritten = function open(strUrl, strWindowName, strWindowFeatures) {
var rwStrUrl = wombat.rewriteUrl(strUrl, false, '');
var res = orig.call(
wombat.proxyToObj(this),
rwStrUrl,
strWindowName,
strWindowFeatures
);
wombat.initNewWindowWombat(res, rwStrUrl);
return res;
};
this.$wbwindow.open = open_rewritten;
if (this.$wbwindow.Window.prototype.open) {
this.$wbwindow.Window.prototype.open = open_rewritten;
}
for (var i = 0; i < this.$wbwindow.frames.length; i++) {
try {
this.$wbwindow.frames[i].open = open_rewritten;
} catch (e) {
console.log(e);
}
}
};
/**
* Applies an override to the getter and setter functions of document.cookie
* in order to ensure that cookies are rewritten
*/
Wombat.prototype.initCookiesOverride = function() {
var orig_get_cookie = this.getOrigGetter(this.$wbwindow.document, 'cookie');
var orig_set_cookie = this.getOrigSetter(this.$wbwindow.document, 'cookie');
if (!orig_get_cookie) {
orig_get_cookie = this.getOrigGetter(
this.$wbwindow.Document.prototype,
'cookie'
);
}
if (!orig_set_cookie) {
orig_set_cookie = this.getOrigSetter(
this.$wbwindow.Document.prototype,
'cookie'
);
}
var rwCookieReplacer = function(m, d1) {
var date = new Date(d1);
if (isNaN(date.getTime())) {
return 'Expires=Thu,| 01 Jan 1970 00:00:00 GMT';
}
var finalDate = new Date(date.getTime() + Date.__WB_timediff);
return 'Expires=' + finalDate.toUTCString().replace(',', ',|');
};
var wombat = this;
var set_cookie = function cookie(value) {
if (!value) return;
var newValue = value.replace(wombat.cookie_expires_regex, rwCookieReplacer);
var cookies = newValue.split(wombat.SetCookieRe);
for (var i = 0; i < cookies.length; i++) {
cookies[i] = wombat.rewriteCookie(cookies[i]);
}
return orig_set_cookie.call(wombat.proxyToObj(this), cookies.join(','));
};
var get_cookie = function cookie() {
return orig_get_cookie.call(wombat.proxyToObj(this));
};
this.defProp(this.$wbwindow.document, 'cookie', set_cookie, get_cookie);
};
/**
* Applies an override to navigator.[registerProtocolHandler, unregisterProtocolHandler] in order to
* ensure that the URI argument is rewritten
*/
Wombat.prototype.initRegisterUnRegPHOverride = function() {
var wombat = this;
var winNavigator = this.$wbwindow.navigator;
if (winNavigator.registerProtocolHandler) {
var orig_registerPH = winNavigator.registerProtocolHandler;
winNavigator.registerProtocolHandler = function registerProtocolHandler(
protocol,
uri,
title
) {
return orig_registerPH.call(
this,
protocol,
wombat.rewriteUrl(uri),
title
);
};
}
if (winNavigator.unregisterProtocolHandler) {
var origUnregPH = winNavigator.unregisterProtocolHandler;
winNavigator.unregisterProtocolHandler = function unregisterProtocolHandler(
scheme,
url
) {
return origUnregPH.call(this, scheme, wombat.rewriteUrl(url));
};
}
};
/**
* Applies an override to navigator.sendBeacon in order to ensure that
* the URL argument is rewritten. This ensures that when a page is rewritten
* no information about who is viewing is leaked to the outside world
*/
Wombat.prototype.initBeaconOverride = function() {
if (!this.$wbwindow.navigator.sendBeacon) return;
var orig_sendBeacon = this.$wbwindow.navigator.sendBeacon;
var wombat = this;
this.$wbwindow.navigator.sendBeacon = function sendBeacon(url, data) {
return orig_sendBeacon.call(this, wombat.rewriteUrl(url), data);
};
};
/**
* Applies an override to the constructor of the PresentationRequest interface object
* in order to rewrite its URL(s) arguments
* @see https://w3c.github.io/presentation-api/#constructing-a-presentationrequest
*/
Wombat.prototype.initPresentationRequestOverride = function() {
if (
this.$wbwindow.PresentationRequest &&
this.$wbwindow.PresentationRequest.prototype
) {
var wombat = this;
var origPresentationRequest = this.$wbwindow.PresentationRequest;
this.$wbwindow.PresentationRequest = (function(PresentationRequest_) {
return function PresentationRequest(url) {
var rwURL = url;
if (url != null) {
if (Array.isArray(rwURL)) {
for (var i = 0; i < rwURL.length; i++) {
rwURL[i] = wombat.rewriteUrl(rwURL[i], true, 'mp_');
}
} else {
rwURL = wombat.rewriteUrl(url, true, 'mp_');
}
}
return new PresentationRequest_(rwURL);
};
})(this.$wbwindow.PresentationRequest);
this.$wbwindow.PresentationRequest.prototype =
origPresentationRequest.prototype;
Object.defineProperty(
this.$wbwindow.PresentationRequest.prototype,
'constructor',
{
value: this.$wbwindow.PresentationRequest
}
);
}
};
/**
* Applies an override that disables the pages ability to send OS native
* notifications. Also disables the ability of the replayed page to retrieve the geolocation
* of the view.
*
* This is done in order to ensure that no malicious abuse of these functions
* can happen during replay.
*/
Wombat.prototype.initDisableNotificationsGeoLocation = function() {
if (window.Notification) {
window.Notification.requestPermission = function requestPermission(
callback
) {
if (callback) {
// eslint-disable-next-line standard/no-callback-literal
callback('denied');
}
return Promise.resolve('denied');
};
}
var applyOverride = function(on) {
if (!on) return;
if (on.getCurrentPosition) {
on.getCurrentPosition = function getCurrentPosition(
success,
error,
options
) {
if (error) {
error({ code: 2, message: 'not available' });
}
};
}
if (on.watchPosition) {
on.watchPosition = function watchPosition(success, error, options) {
if (error) {
error({ code: 2, message: 'not available' });
}
};
}
};
if (window.geolocation) {
applyOverride(window.geolocation);
}
if (window.navigator.geolocation) {
applyOverride(window.navigator.geolocation);
}
};
/**
* Applies an override to window.[localStorage, sessionStorage] storage in order to ensure
* that the replayed page can use both interfaces as expected during replay.
*/
Wombat.prototype.initStorageOverride = function() {
this.addEventOverride('storageArea', this.$wbwindow.StorageEvent.prototype);
var local;
var session;
var pLocal = 'localStorage';
var pSession = 'sessionStorage';
if (this.$wbwindow.Proxy) {
var storageProxyHandler = function() {
return {
get: function(target, prop) {
if (prop in target) return target[prop];
return target.getItem(prop);
},
set: function(target, prop, value) {
if (target.hasOwnProperty(prop)) return false;
target.setItem(prop, value);
return true;
},
getOwnPropertyDescriptor: function(target, prop) {
return Object.getOwnPropertyDescriptor(target, prop);
}
};
};
local = new this.$wbwindow.Proxy(
new Storage(this, pLocal),
storageProxyHandler()
);
session = new this.$wbwindow.Proxy(
new Storage(this, pSession),
storageProxyHandler()
);
} else {
local = new Storage(this, pLocal);
session = new Storage(this, pSession);
}
this.defGetterProp(this.$wbwindow, pLocal, function localStorage() {
return local;
});
this.defGetterProp(this.$wbwindow, pSession, function sessionStorage() {
return session;
});
};
/**
* Initializes the wombat window JS Proxy object IFF JS Proxies are available.
* @param {Window} $wbwindow
* @return {Proxy<Window>}
*/
Wombat.prototype.initWindowObjProxy = function($wbwindow) {
if (!$wbwindow.Proxy) return undefined;
var ownProps = this.getAllOwnProps($wbwindow);
var wombat = this;
var windowProxy = new $wbwindow.Proxy(
{},
{
get: function(target, prop) {
if (prop === 'top') {
return wombat.$wbwindow.WB_wombat_top._WB_wombat_obj_proxy;
}
return wombat.defaultProxyGet($wbwindow, prop, ownProps);
},
set: function(target, prop, value) {
switch (prop) {
case 'location':
$wbwindow.WB_wombat_location = value;
return true;
case 'postMessage':
case 'document':
return true;
}
try {
if (!Reflect.set(target, prop, value)) {
return false;
}
} catch (e) {}
return Reflect.set($wbwindow, prop, value);
},
has: function(target, prop) {
return prop in $wbwindow;
},
ownKeys: function(target) {
return Object.getOwnPropertyNames($wbwindow).concat(
Object.getOwnPropertySymbols($wbwindow)
);
},
getOwnPropertyDescriptor: function(target, key) {
// first try the underlying object's descriptor
// (to match defineProperty() behavior)
var descriptor = Object.getOwnPropertyDescriptor(target, key);
if (!descriptor) {
descriptor = Object.getOwnPropertyDescriptor($wbwindow, key);
// if using window's descriptor, must ensure it's configurable
if (descriptor) {
descriptor.configurable = true;
}
}
return descriptor;
},
getPrototypeOf: function(target) {
return Object.getPrototypeOf($wbwindow);
},
setPrototypeOf: function(target, newProto) {
return false;
},
isExtensible: function(target) {
return Object.isExtensible($wbwindow);
},
preventExtensions: function(target) {
Object.preventExtensions($wbwindow);
return true;
},
deleteProperty: function(target, prop) {
var propDescriptor = Object.getOwnPropertyDescriptor($wbwindow, prop);
if (propDescriptor === undefined) {
return true;
}
if (propDescriptor.configurable === false) {
return false;
}
delete $wbwindow[prop];
return true;
},
defineProperty: function(target, prop, desc) {
var ndesc = desc || {};
if (!ndesc.value && !ndesc.get) {
ndesc.value = $wbwindow[prop];
}
Reflect.defineProperty($wbwindow, prop, ndesc);
return Reflect.defineProperty(target, prop, ndesc);
}
}
);
$wbwindow._WB_wombat_obj_proxy = windowProxy;
return windowProxy;
};
/**
* Initializes the wombat document JS Proxy object IFF JS Proxies are available.
* This function also applies the {@link initDocOverrides} overrides regardless
* if JS Proxies are available.
* @param {Document} $document
* @return {Proxy<Document>}
*/
Wombat.prototype.initDocumentObjProxy = function($document) {
this.initDocOverrides($document);
if (!this.$wbwindow.Proxy) return undefined;
var ownProps = this.getAllOwnProps($document);
var wombat = this;
var documentProxy = new this.$wbwindow.Proxy($document, {
get: function(target, prop) {
return wombat.defaultProxyGet($document, prop, ownProps);
},
set: function(target, prop, value) {
if (prop === 'location') {
$document.WB_wombat_location = value;
} else {
target[prop] = value;
}
return true;
}
});
$document._WB_wombat_obj_proxy = documentProxy;
return documentProxy;
};
/**
* Initializes and starts the auto-fetch worker IFF wbUseAFWorker is true
*/
Wombat.prototype.initAutoFetchWorker = function() {
if (!this.wbUseAFWorker) return;
this.WBAutoFetchWorker = new AutoFetchWorker(this);
var wombat = this;
this.utilFns.wbSheetMediaQChecker = function checkStyle() {
// used only for link[rel='stylesheet'] so we remove our listener
wombat._removeEventListener(
this,
'load',
wombat.utilFns.wbSheetMediaQChecker
);
// check no op condition
if (this.sheet == null) return;
// defer extraction to be nice :)
if (wombat.WBAutoFetchWorker) {
wombat.WBAutoFetchWorker.deferredSheetExtraction(this.sheet);
}
};
};
/**
* Initializes the listener for when the document.readyState is "complete" in
* order to send archive top the information about the title of the page and
* have it add any favicons of the replayed page to its own markup.
*
* The wb_type="load" message is sent to archive top IFF the page it originates
* is replay top
* @param {Object} wbinfo
*/
Wombat.prototype.initTopFrameNotify = function(wbinfo) {
var wombat = this;
var notify_top = function notify_top(event) {
if (!wombat.$wbwindow.__WB_top_frame) {
var hash = wombat.$wbwindow.location.hash;
wombat.$wbwindow.location.replace(wbinfo.top_url + hash);
return;
}
if (!wombat.$wbwindow.WB_wombat_location) return;
var url = wombat.$wbwindow.WB_wombat_location.href;
if (
typeof url !== 'string' ||
url === 'about:blank' ||
url.indexOf('javascript:') === 0
) {
return;
}
if (
wombat.$wbwindow.document.readyState === 'complete' &&
wombat.wbUseAFWorker &&
wombat.WBAutoFetchWorker
) {
wombat.WBAutoFetchWorker.extractFromLocalDoc();
}
if (wombat.$wbwindow !== wombat.$wbwindow.__WB_replay_top) {
return;
}
var icons = [];
var hicons = wombat.$wbwindow.document.querySelectorAll(
"link[rel*='icon']"
);
for (var i = 0; i < hicons.length; i++) {
var hicon = hicons[i];
icons.push({
rel: hicon.rel,
href: wombat.wb_getAttribute.call(hicon, 'href')
});
}
var message = {
icons: icons,
url: wombat.$wbwindow.WB_wombat_location.href,
ts: wombat.wb_info.timestamp,
request_ts: wombat.wb_info.request_ts,
is_live: wombat.wb_info.is_live,
title: wombat.$wbwindow.document ? wombat.$wbwindow.document.title : '',
readyState: wombat.$wbwindow.document.readyState,
wb_type: 'load'
};
wombat.sendTopMessage(message);
};
if (this.$wbwindow.document.readyState === 'complete') {
notify_top();
} else if (this.$wbwindow.addEventListener) {
this.$wbwindow.document.addEventListener('readystatechange', notify_top);
} else if (this.$wbwindow.attachEvent) {
this.$wbwindow.document.attachEvent('onreadystatechange', notify_top);
}
};
/**
* Initialises the _WB_replay_top and _WB_top_frame properties on window
* @param {Window} $wbwindow
*/
Wombat.prototype.initTopFrame = function($wbwindow) {
// proxy mode
if (this.wb_is_proxy) {
$wbwindow.__WB_replay_top = $wbwindow.top;
$wbwindow.__WB_top_frame = undefined;
return;
}
var next_parent = function(win) {
try {
if (!win) return false;
// if no wbinfo, see if _wb_wombat was set (eg. if about:blank page)
if (!win.wbinfo) {
return win._wb_wombat != null;
} else {
// otherwise, ensure that it is not a top container frame
return win.wbinfo.is_framed;
}
} catch (e) {
return false;
}
};
var replay_top = $wbwindow;
while (replay_top.parent != replay_top && next_parent(replay_top.parent)) {
replay_top = replay_top.parent;
}
$wbwindow.__WB_replay_top = replay_top;
var real_parent = replay_top.__WB_orig_parent || replay_top.parent;
// Check to ensure top frame is different window and directly accessible (later refactor to support postMessage)
if (real_parent == $wbwindow || !this.wb_info.is_framed) {
real_parent = undefined;
}
if (real_parent) {
$wbwindow.__WB_top_frame = real_parent;
this.initFrameElementOverride($wbwindow);
} else {
$wbwindow.__WB_top_frame = undefined;
}
// Fix .parent only if not embeddable, otherwise leave for accessing embedding window
if (!this.wb_opts.embedded && replay_top == $wbwindow) {
if (this.wbUseAFWorker) {
var wombat = this;
this.$wbwindow.addEventListener(
'message',
function(event) {
if (
event.data &&
event.data.wb_type === 'aaworker' &&
wombat.WBAutoFetchWorker
) {
wombat.WBAutoFetchWorker.postMessage(event.data.msg);
}
},
false
);
}
$wbwindow.__WB_orig_parent = $wbwindow.parent;
$wbwindow.parent = replay_top;
}
};
/**
* Applies an override to window.frameElement IFF the supplied windows
* __WB_replay_top property is equal to the window object of the browser context
* wombat is currently operating in
* @param {Window} $wbwindow
*/
Wombat.prototype.initFrameElementOverride = function($wbwindow) {
if (!Object.defineProperty) return;
// Also try disabling frameElement directly, though may no longer be supported in all browsers
if (
this.proxyToObj($wbwindow.__WB_replay_top) == this.proxyToObj($wbwindow)
) {
try {
Object.defineProperty($wbwindow, 'frameElement', {
value: null,
configurable: false
});
} catch (e) {}
}
};
/**
* Adds the WB_wombat_top property to the prototype of Object
* @param {Window} $wbwindow
*/
Wombat.prototype.initWombatTop = function($wbwindow) {
if (!Object.defineProperty) return;
// from http://stackoverflow.com/a/6229603
var isWindow = function isWindow(obj) {
if (typeof window.constructor === 'undefined') {
return obj instanceof window.constructor;
}
return obj.window == obj;
};
var getter = function top() {
if (this.__WB_replay_top) {
return this.__WB_replay_top;
} else if (isWindow(this)) {
return this;
}
return this.top;
};
var setter = function top(val) {
this.top = val;
};
this.defProp($wbwindow.Object.prototype, 'WB_wombat_top', setter, getter);
};
/**
* Initialize wombat's internal state and apply all overrides
* @return {Object}
*/
Wombat.prototype.wombatInit = function() {
// wombat init
this._internalInit();
// History
this.initHistoryOverrides();
this.overrideFunctionApply(this.$wbwindow);
// Doc Title
this.initDocTitleOverride();
this.initHashChange();
// postMessage
// OPT skip
if (!this.wb_opts.skip_postmessage) {
this.initPostMessageOverride(this.$wbwindow);
this.initMessageEventOverride(this.$wbwindow);
}
this.initUIEventsOverrides();
// write
this.initDocWriteOpenCloseOverride();
// eval
// initEvalOverride();
// Ajax, Fetch, Request, Response, EventSource, WebSocket
this.initHTTPOverrides();
// Audio
this.initAudioOverride();
// FontFace
this.initFontFaceOverride(this.$wbwindow);
// Worker override (experimental)
this.initWorkerOverrides();
// text node overrides for js frameworks doing funky things with CSS
this.initTextNodeOverrides();
this.initCSSOMOverrides();
// innerHTML & outerHTML can be overriden on prototype!
// we must override innerHTML & outerHTML on Element otherwise un-rewritten HTML
// can get into replay
this.overrideHtmlAssign(this.$wbwindow.Element, 'innerHTML', true);
this.overrideHtmlAssign(this.$wbwindow.Element, 'outerHTML', true);
this.overrideHtmlAssign(this.$wbwindow.HTMLIFrameElement, 'srcdoc', true);
this.overrideHtmlAssign(this.$wbwindow.HTMLStyleElement, 'textContent');
this.overrideShadowDom();
// Document.URL override
this.overridePropExtract(this.$wbwindow.Document.prototype, 'URL');
this.overridePropExtract(this.$wbwindow.Document.prototype, 'documentURI');
// Node.baseURI override
this.overridePropExtract(this.$wbwindow.Node.prototype, 'baseURI');
// Attr nodeValue and value
this.overrideAttrProps();
// init insertAdjacent[Element, HTML] override
this.initInsertAdjacentElementHTMLOverrides();
// iframe.contentWindow and iframe.contentDocument overrides to
// ensure _wombat is inited on the iframe $wbwindow!
this.overrideIframeContentAccess('contentWindow');
this.overrideIframeContentAccess('contentDocument');
// override funcs to convert first arg proxy->obj
this.overrideFuncArgProxyToObj(this.$wbwindow.MutationObserver, 'observe');
this.overrideFuncArgProxyToObj(
this.$wbwindow.Node,
'compareDocumentPosition'
);
this.overrideFuncArgProxyToObj(this.$wbwindow.Node, 'contains');
this.overrideFuncArgProxyToObj(this.$wbwindow.Document, 'createTreeWalker');
this.overrideFuncArgProxyToObj(this.$wbwindow.Document, 'evaluate', 1);
this.overrideFuncArgProxyToObj(this.$wbwindow.Document, 'createTouch', 1);
this.overrideFuncArgProxyToObj(
this.$wbwindow.XSLTProcessor,
'transformToFragment',
1
);
this.overrideFuncThisProxyToObj(
this.$wbwindow,
'getComputedStyle',
this.$wbwindow
);
this.initTimeoutIntervalOverrides();
this.overrideFramesAccess(this.$wbwindow);
// setAttribute
this.initElementGetSetAttributeOverride();
this.initSvgImageOverrides();
// override href and src attrs
this.initAttrOverrides();
// Cookies
this.initCookiesOverride();
// ensure namespace urls are NOT rewritten
this.initCreateElementNSFix();
// DOM
// OPT skip
if (!this.wb_opts.skip_dom) {
this.initDomOverride();
}
// registerProtocolHandler override
this.initRegisterUnRegPHOverride();
this.initPresentationRequestOverride();
// sendBeacon override
this.initBeaconOverride();
// other overrides
// proxy mode: only using these overrides
// Random
this.initSeededRandom(this.wb_info.wombat_sec);
// Crypto Random
this.initCryptoRandom();
// set fixed pixel ratio
this.initFixedRatio();
// Date
this.initDateOverride(this.wb_info.wombat_sec);
// open
this.initOpenOverride();
// disable notifications
this.initDisableNotificationsGeoLocation();
// custom storage
this.initStorageOverride();
// add window and document obj proxies, if available
this.initWindowObjProxy(this.$wbwindow);
this.initDocumentObjProxy(this.$wbwindow.document);
var wombat = this;
return {
actual: false,
extract_orig: this.extractOriginalURL,
rewrite_url: this.rewriteUrl,
watch_elem: this.watchElem,
init_new_window_wombat: this.initNewWindowWombat,
init_paths: this.initPaths,
local_init: function(name) {
var res = wombat.$wbwindow._WB_wombat_obj_proxy[name];
if (name === 'document' && res && !res._WB_wombat_obj_proxy) {
return wombat.initDocumentObjProxy(res) || res;
}
return res;
},
showCSPViolations: function(yesNo) {
wombat._addRemoveCSPViolationListener(yesNo);
}
};
};
export default Wombat;