mirror of
https://github.com/webrecorder/pywb.git
synced 2025-03-15 00:03:28 +01:00
server side rewriting: (#475)
- fixed edge case in jsonP rewriting where no callback name is supplied only ? but body has normal jsonP callback (url = https://geolocation.onetrust.com/cookieconsentpub/v1/geo/countries/EU?callback=?) - made the `!self.__WB_pmw` server side inject match the client side one done via wombat - added regex's for eval override to JSWombatProxyRules wombat: - added eval override in order to ensure AD network JS does not lockup the browser (cnn.com) - added returning a proxied value for window.parent from the window proxy object in order to ensure AD network JS does not lock up browser (cnn.com, boston.com, et. al.) - ensured that the read only storageArea property of 'StorageEvents' fired by the custom storage class is present (spec) - ensured that userland cannot create new instances of Storage and that localStorage instanceof Storage works with our override (spec) - ensured that assignments to SomeElement.[innerHTML|outerHTML], iframe.srcdoc, or style.textContent are more correctly handled in particular doing script.[innerHTML|outerHTML] = <JS> - ensured that the test used in the isArgumentsObj function does not throw errors IFF the pages JS has made it so when toString is called (linkedin.com) - ensured that Web Worker construction when using the optional options object, the options get supplied to the create worker (spec) - ensured that the expected TypeError exception thrown from DomConstructors of overridden interfaces are thrown when their pre-conditions are not met (spec) - ensured that wombat worker rewriting skipes data urls which should not be rewritten - ensured the bound function returned by the window function is the same on subsequent retrievals fixes #474 tests: - added direct testing of wombat's parts
This commit is contained in:
parent
fb0a927238
commit
6437dab1f4
@ -47,7 +47,7 @@ jobs:
|
||||
- stage: test
|
||||
name: "Wombat Tests"
|
||||
language: node_js
|
||||
node_js: 12.0.0
|
||||
node_js: 12.4.0
|
||||
env:
|
||||
- WR_TEST=no WOMBAT_TEST=yes
|
||||
addons:
|
||||
|
@ -19,6 +19,12 @@ class JSONPRewriter(StreamingRewriter):
|
||||
m_callback = self.CALLBACK.search(self.url_rewriter.wburl.url)
|
||||
if not m_callback:
|
||||
return string
|
||||
if m_callback.group(1) == '?':
|
||||
# this is a very sharp edge case e.g. callback=?
|
||||
# since we only have this string[m_json.end(1):]
|
||||
# would cut off the name of the CB if any is included
|
||||
# so we just pass the string through
|
||||
return string
|
||||
|
||||
string = m_callback.group(1) + string[m_json.end(1):]
|
||||
return string
|
||||
|
@ -65,7 +65,7 @@ class JSWombatProxyRules(RxRules):
|
||||
local_init_func = '\nvar {0} = 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\
|
||||
if (!self.__WB_pmw) {{ self.__WB_pmw = function(obj) {{ this.__WB_source = obj; return this; }} }}\n\
|
||||
{{\n'
|
||||
local_check_this_fn = 'var {0} = function (thisObj) {{ \
|
||||
if (thisObj && thisObj._WB_wombat_obj_proxy) return thisObj._WB_wombat_obj_proxy; return thisObj; }};'
|
||||
@ -102,6 +102,8 @@ if (thisObj && thisObj._WB_wombat_obj_proxy) return thisObj._WB_wombat_obj_proxy
|
||||
prop_str = '|'.join(self.local_objs)
|
||||
|
||||
rules = [
|
||||
(r'\beval\s*\(', self.add_prefix('WB_wombat_runEval(function _____evalIsEvil(_______eval_arg$$) { return eval(_______eval_arg$$); }.bind(this)).'), 0),
|
||||
(r'\beval\b', self.add_prefix('WB_wombat_'), 0),
|
||||
(r'(?<=\.)postMessage\b\(', self.add_prefix('__WB_pmw(self).'), 0),
|
||||
(r'(?<![$.])\s*location\b\s*[=]\s*(?![=])', self.add_suffix(check_loc), 0),
|
||||
(r'\breturn\s+this\b\s*(?![.$])', self.replace_str(this_rw), 0),
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,63 +1,70 @@
|
||||
import get from 'lodash-es/get';
|
||||
|
||||
/**
|
||||
* @type {TestOverwatch}
|
||||
* @typedef {Object} TestOverwatchInit
|
||||
* @property {HTMLIFrameElement} sandbox
|
||||
* @property {Object} [domStructure]
|
||||
* @property {boolean} [direct]
|
||||
*/
|
||||
|
||||
window.TestOverwatch = class TestOverwatch {
|
||||
/**
|
||||
* @param {Object} domStructure
|
||||
* @param {TestOverwatchInit} init
|
||||
*/
|
||||
constructor(domStructure) {
|
||||
constructor({ sandbox, domStructure, direct }) {
|
||||
/**
|
||||
* @type {{document: Document, window: Window}}
|
||||
*/
|
||||
this.ownContextWinDoc = { window, document };
|
||||
this.wbMessages = { load: false };
|
||||
this.domStructure = domStructure;
|
||||
this.direct = direct;
|
||||
|
||||
/**
|
||||
* @type {HTMLIFrameElement}
|
||||
*/
|
||||
this.sandbox = domStructure.sandbox;
|
||||
window.addEventListener(
|
||||
'message',
|
||||
event => {
|
||||
if (event.data) {
|
||||
const { data } = event;
|
||||
switch (data.wb_type) {
|
||||
case 'load':
|
||||
this.wbMessages.load =
|
||||
this.wbMessages.load || data.readyState === 'complete';
|
||||
this.domStructure.load.url.data = data.url;
|
||||
this.domStructure.load.title.data = data.title;
|
||||
this.domStructure.load.readyState.data = data.readyState;
|
||||
break;
|
||||
case 'replace-url':
|
||||
this.wbMessages['replace-url'] = data;
|
||||
this.domStructure.replaceURL.url.data = data.url;
|
||||
this.domStructure.replaceURL.title.data = data.title;
|
||||
break;
|
||||
case 'title':
|
||||
this.wbMessages.title = data.title;
|
||||
this.domStructure.titleMsg.data = data.title;
|
||||
break;
|
||||
case 'hashchange':
|
||||
this.domStructure.hashchange.data = data.title;
|
||||
this.wbMessages.hashchange = data.hash;
|
||||
break;
|
||||
case 'cookie':
|
||||
this.domStructure.cookie.domain = data.domain;
|
||||
this.domStructure.cookie.cookie = data.cookie;
|
||||
this.wbMessages.cookie = data;
|
||||
break;
|
||||
default:
|
||||
this.domStructure.unknown.data = JSON.stringify(data);
|
||||
break;
|
||||
this.sandbox = sandbox;
|
||||
if (!this.direct) {
|
||||
window.addEventListener(
|
||||
'message',
|
||||
event => {
|
||||
if (event.data) {
|
||||
const { data } = event;
|
||||
switch (data.wb_type) {
|
||||
case 'load':
|
||||
this.wbMessages.load =
|
||||
this.wbMessages.load || data.readyState === 'complete';
|
||||
this.domStructure.load.url.data = data.url;
|
||||
this.domStructure.load.title.data = data.title;
|
||||
this.domStructure.load.readyState.data = data.readyState;
|
||||
break;
|
||||
case 'replace-url':
|
||||
this.wbMessages['replace-url'] = data;
|
||||
this.domStructure.replaceURL.url.data = data.url;
|
||||
this.domStructure.replaceURL.title.data = data.title;
|
||||
break;
|
||||
case 'title':
|
||||
this.wbMessages.title = data.title;
|
||||
this.domStructure.titleMsg.data = data.title;
|
||||
break;
|
||||
case 'hashchange':
|
||||
this.domStructure.hashchange.data = data.title;
|
||||
this.wbMessages.hashchange = data.hash;
|
||||
break;
|
||||
case 'cookie':
|
||||
this.domStructure.cookie.domain = data.domain;
|
||||
this.domStructure.cookie.cookie = data.cookie;
|
||||
this.wbMessages.cookie = data;
|
||||
break;
|
||||
default:
|
||||
this.domStructure.unknown.data = JSON.stringify(data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
false
|
||||
);
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -67,7 +74,9 @@ window.TestOverwatch = class TestOverwatch {
|
||||
* environment purity.
|
||||
*/
|
||||
initSandbox() {
|
||||
this.domStructure.reset();
|
||||
if (this.domStructure) {
|
||||
this.domStructure.reset();
|
||||
}
|
||||
this.wbMessages = { load: false };
|
||||
this.sandbox.contentWindow._WBWombatInit(this.sandbox.contentWindow.wbinfo);
|
||||
this.sandbox.contentWindow.WombatTestUtil = {
|
||||
|
@ -4,25 +4,25 @@
|
||||
"main": "index.js",
|
||||
"license": "GPL-3.0",
|
||||
"devDependencies": {
|
||||
"@types/fs-extra": "^5.0.5",
|
||||
"ava": "^1.4.1",
|
||||
"chokidar": "^2.1.5",
|
||||
"chrome-remote-interface-extra": "^1.0.0",
|
||||
"@types/fs-extra": "^7.0.0",
|
||||
"ava": "^2.1.0",
|
||||
"chokidar": "^3.0.1",
|
||||
"chrome-remote-interface-extra": "^1.1.1",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-config-prettier": "^4.2.0",
|
||||
"eslint-plugin-prettier": "^3.0.1",
|
||||
"fastify": "^2.3.0",
|
||||
"eslint-config-prettier": "^5.0.0",
|
||||
"eslint-plugin-prettier": "^3.1.0",
|
||||
"fastify": "^2.5.0",
|
||||
"fastify-favicon": "^2.0.0",
|
||||
"fastify-graceful-shutdown": "^2.0.1",
|
||||
"fastify-static": "^2.4.0",
|
||||
"fs-extra": "^7.0.1",
|
||||
"fastify-static": "^2.5.0",
|
||||
"fs-extra": "^8.0.1",
|
||||
"lodash-es": "^4.17.11",
|
||||
"prettier": "^1.17.0",
|
||||
"rollup": "^1.10.1",
|
||||
"prettier": "^1.18.2",
|
||||
"rollup": "^1.15.6",
|
||||
"rollup-plugin-babel-minify": "^8.0.0",
|
||||
"rollup-plugin-cleanup": "^3.1.1",
|
||||
"rollup-plugin-commonjs": "^9.3.4",
|
||||
"rollup-plugin-node-resolve": "^4.2.3",
|
||||
"rollup-plugin-commonjs": "^10.0.0",
|
||||
"rollup-plugin-node-resolve": "^5.0.3",
|
||||
"rollup-plugin-uglify": "^6.0.2",
|
||||
"rollup-plugin-uglify-es": "^0.0.1",
|
||||
"tls-keygen": "^3.7.0"
|
||||
@ -32,8 +32,8 @@
|
||||
"build-dev": "ALL=1 rollup -c rollup.config.dev.js",
|
||||
"build-dev-watch": "rollup --watch -c rollup.config.dev.js",
|
||||
"build-dev-watch-proxy": "PROXY=1 rollup --watch -c rollup.config.dev.js",
|
||||
"build-test": "rollup -c rollup.config.test.js && rollup -c ./internal/rollup.testPageBundle.config.js",
|
||||
"build-full-test": "rollup -c rollup.config.test.js",
|
||||
"build-test": "rollup -c rollup.config.test.js",
|
||||
"build-full-test": "rollup -c rollup.config.test.js && rollup -c ./internal/rollup.testPageBundle.config.js",
|
||||
"build-test-bundle": "rollup -c ./internal/rollup.testPageBundle.config.js",
|
||||
"test": "ava --verbose"
|
||||
},
|
||||
@ -50,16 +50,17 @@
|
||||
"!test/assests/*",
|
||||
"!test/helpers/extractOrigFunky.js"
|
||||
],
|
||||
"helpers": [
|
||||
"test/helpers/**.js"
|
||||
],
|
||||
"sources": [
|
||||
"!src/**/*"
|
||||
"src/**/*"
|
||||
]
|
||||
},
|
||||
"resolutions": {
|
||||
"*/**/graceful-fs": "~4.1.15",
|
||||
"*/**/jsonfile": "~5.0.0",
|
||||
"*/**/universalify": "~0.1.2"
|
||||
"*/**/graceful-fs": "~4.1.15"
|
||||
},
|
||||
"dependencies": {
|
||||
"chrome-launcher": "^0.10.5"
|
||||
"just-launch-chrome": "^1.0.0"
|
||||
}
|
||||
}
|
||||
|
@ -38,5 +38,26 @@ export default [
|
||||
exports: 'none'
|
||||
},
|
||||
plugins: [noStrict]
|
||||
},
|
||||
{
|
||||
input: 'src/wbWombat.js',
|
||||
output: {
|
||||
name: 'wombat',
|
||||
file: path.join(baseTestOutput, 'wombatDirect.js'),
|
||||
sourcemap: false,
|
||||
format: 'es'
|
||||
},
|
||||
plugins: [
|
||||
noStrict,
|
||||
{
|
||||
renderChunk(code) {
|
||||
return code.replace(
|
||||
/(this\._wb_wombat\.actual\s=\strue;)/gi,
|
||||
`this._wb_wombat.actual = true;
|
||||
this.wombat = wombat;`
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
@ -1,4 +1,8 @@
|
||||
import { addToStringTagToClass, ensureNumber } from './wombatUtils';
|
||||
import {
|
||||
addToStringTagToClass,
|
||||
ensureNumber,
|
||||
ThrowExceptions
|
||||
} from './wombatUtils';
|
||||
|
||||
/**
|
||||
* A re-implementation of the Storage interface.
|
||||
@ -13,6 +17,12 @@ import { addToStringTagToClass, ensureNumber } from './wombatUtils';
|
||||
* @see https://html.spec.whatwg.org/multipage/webstorage.html#the-storage-interface
|
||||
*/
|
||||
export default function Storage(wombat, proxying) {
|
||||
if (ThrowExceptions.yes) {
|
||||
// there is no constructor exposed for this interface however there is an
|
||||
// interface object exposed, thus we must throw an TypeError if userland
|
||||
// attempts to create us
|
||||
throw new TypeError('Illegal constructor');
|
||||
}
|
||||
// hide our values from enumeration, spreed, et al
|
||||
Object.defineProperties(this, {
|
||||
data: {
|
||||
@ -111,9 +121,14 @@ Storage.prototype.fireEvent = function fireEvent(key, oldValue, newValue) {
|
||||
oldValue: oldValue,
|
||||
url: this.wombat.$wbwindow.WB_wombat_location.href
|
||||
});
|
||||
|
||||
// storage is a read only property of StorageEvent
|
||||
// that must be on the fired instance of the event
|
||||
Object.defineProperty(sevent, 'storageArea', {
|
||||
value: this,
|
||||
writable: false,
|
||||
configurable: false
|
||||
});
|
||||
sevent._storageArea = this;
|
||||
|
||||
this.wombat.storage_listeners.map(sevent);
|
||||
};
|
||||
|
||||
|
@ -3,8 +3,12 @@ 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';
|
||||
import { wrapEventListener, wrapSameOriginEventListener } from './listeners';
|
||||
import {
|
||||
addToStringTagToClass,
|
||||
autobind,
|
||||
ThrowExceptions
|
||||
} from './wombatUtils';
|
||||
|
||||
/**
|
||||
* @param {Window} $wbwindow
|
||||
@ -276,6 +280,11 @@ function Wombat($wbwindow, wbinfo) {
|
||||
},
|
||||
addEventListener: eTargetProto.addEventListener,
|
||||
removeEventListener: eTargetProto.removeEventListener,
|
||||
// some sites do funky things with the toString function
|
||||
// (e.g. if used throw error or deny operation) hence we
|
||||
// need a surefire and safe way to tell us what an object
|
||||
// or function is hence Objects native toString
|
||||
objToString: Object.prototype.toString,
|
||||
wbSheetMediaQChecker: null,
|
||||
XHRopen: null
|
||||
};
|
||||
@ -410,10 +419,19 @@ Wombat.prototype.getPageUnderModifier = function() {
|
||||
* @return {boolean}
|
||||
*/
|
||||
Wombat.prototype.isNativeFunction = function(funToTest) {
|
||||
if (!funToTest) return false;
|
||||
if (!funToTest || typeof funToTest !== 'function') return false;
|
||||
return this.wb_funToString.call(funToTest).indexOf('[native code]') >= 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns T/F indicating if the supplied argument is a string or not
|
||||
* @param {*} arg
|
||||
* @return {boolean}
|
||||
*/
|
||||
Wombat.prototype.isString = function(arg) {
|
||||
return arg != null && Object.getPrototypeOf(arg) === String.prototype;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns T/F indicating if the supplied element may have attributes that
|
||||
* are auto-fetched
|
||||
@ -491,7 +509,13 @@ Wombat.prototype.isArgumentsObj = function(maybeArgumentsObj) {
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return maybeArgumentsObj.toString() === '[object Arguments]';
|
||||
try {
|
||||
return (
|
||||
this.utilFns.objToString.call(maybeArgumentsObj) === '[object Arguments]'
|
||||
);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -508,7 +532,7 @@ Wombat.prototype.deproxyArrayHandlingArgumentsObj = function(
|
||||
if (
|
||||
!maybeArgumentsObj ||
|
||||
maybeArgumentsObj instanceof NodeList ||
|
||||
maybeArgumentsObj.length == 0
|
||||
!maybeArgumentsObj.length
|
||||
) {
|
||||
return maybeArgumentsObj;
|
||||
}
|
||||
@ -592,6 +616,53 @@ Wombat.prototype.shouldRewriteAttr = function(tagName, attr) {
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns T/F indicating if the script tag being rewritten should not
|
||||
* have its text contents wrapped based on the supplied script type.
|
||||
* @param {?string} scriptType
|
||||
* @return {boolean}
|
||||
*/
|
||||
Wombat.prototype.skipWrapScriptBasedOnType = function(scriptType) {
|
||||
if (!scriptType) return false;
|
||||
if (scriptType.indexOf('json') >= 0) return true;
|
||||
return scriptType.indexOf('text/template') >= 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns T/F indicating if the script tag being rewritten should not
|
||||
* have its text contents wrapped based on heuristic analysis of its
|
||||
* text contents.
|
||||
* @param {?string} text
|
||||
* @return {boolean}
|
||||
*/
|
||||
Wombat.prototype.skipWrapScriptTextBasedOnText = function(text) {
|
||||
if (
|
||||
!text ||
|
||||
text.indexOf('_____WB$wombat$assign$function_____') >= 0 ||
|
||||
text.indexOf('<') === 0
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
var override_props = [
|
||||
'window',
|
||||
'self',
|
||||
'document',
|
||||
'location',
|
||||
'top',
|
||||
'parent',
|
||||
'frames',
|
||||
'opener'
|
||||
];
|
||||
|
||||
for (var i = 0; i < override_props.length; i++) {
|
||||
if (text.indexOf(override_props[i]) >= 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
@ -699,7 +770,10 @@ 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' +
|
||||
'var _____WB$wombat$check$this$function_____ = function(thisObj) {' +
|
||||
'if (thisObj && thisObj._WB_wombat_obj_proxy) return thisObj._WB_wombat_obj_proxy;' +
|
||||
'return thisObj; }\nif (!self.__WB_pmw) { self.__WB_pmw = function(obj) { ' +
|
||||
'this.__WB_source = obj; return this; } }\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' +
|
||||
@ -708,7 +782,7 @@ Wombat.prototype.wrapScriptTextJsProxy = function(scriptText) {
|
||||
'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 +
|
||||
scriptText.replace(this.DotPostMessageRe, '.__WB_pmw(self.window)$1') +
|
||||
'\n\n}'
|
||||
);
|
||||
};
|
||||
@ -1194,16 +1268,20 @@ Wombat.prototype.objToProxy = function(obj) {
|
||||
* @param {*} obj
|
||||
* @param {*} prop
|
||||
* @param {Array<string>} ownProps
|
||||
* @param {Object} fnCache
|
||||
* @return {*}
|
||||
*/
|
||||
Wombat.prototype.defaultProxyGet = function(obj, prop, ownProps) {
|
||||
Wombat.prototype.defaultProxyGet = function(obj, prop, ownProps, fnCache) {
|
||||
switch (prop) {
|
||||
case '__WBProxyRealObj__':
|
||||
return obj;
|
||||
case 'location':
|
||||
case 'WB_wombat_location':
|
||||
return obj.WB_wombat_location;
|
||||
case '_WB_wombat_obj_proxy':
|
||||
return obj._WB_wombat_obj_proxy;
|
||||
case '__WB_pmw':
|
||||
return obj[prop];
|
||||
case 'constructor':
|
||||
// allow tests such as self.constructor === Window to work
|
||||
// you can't create a new instance of window using its constructor
|
||||
@ -1228,7 +1306,17 @@ Wombat.prototype.defaultProxyGet = function(obj, prop, ownProps) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return retVal.bind(obj);
|
||||
// due to specific use cases involving native functions
|
||||
// we must return the
|
||||
var cachedFN = fnCache[prop];
|
||||
if (!cachedFN || cachedFN.original !== retVal) {
|
||||
cachedFN = {
|
||||
original: retVal,
|
||||
boundFn: retVal.bind(obj)
|
||||
};
|
||||
fnCache[prop] = cachedFN;
|
||||
}
|
||||
return cachedFN.boundFn;
|
||||
} else if (type === 'object' && retVal && retVal._WB_wombat_obj_proxy) {
|
||||
if (retVal instanceof Window) {
|
||||
this.initNewWindowWombat(retVal);
|
||||
@ -1390,9 +1478,10 @@ Wombat.prototype.styleReplacer = function(match, n1, n2, n3, offset, string) {
|
||||
* injected functions are present in the supplied window.
|
||||
*
|
||||
* They could be absent due to how certain pages use iframes
|
||||
* @param {Window} win
|
||||
* @param {?Window} win
|
||||
*/
|
||||
Wombat.prototype.ensureServerSideInjectsExistOnWindow = function(win) {
|
||||
if (!win) return;
|
||||
if (typeof win._____WB$wombat$check$this$function_____ !== 'function') {
|
||||
win._____WB$wombat$check$this$function_____ = function(thisObj) {
|
||||
if (thisObj && thisObj._WB_wombat_obj_proxy)
|
||||
@ -1412,6 +1501,51 @@ Wombat.prototype.ensureServerSideInjectsExistOnWindow = function(win) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Due to the fact that we override specific DOM constructors, e.g. Worker,
|
||||
* the normal TypeErrors are not thrown if the pre-conditions for those
|
||||
* constructors are not met.
|
||||
*
|
||||
* Code that performs polyfills or browser feature detection based
|
||||
* on those TypeErrors will not work as expected if we do not perform
|
||||
* those checks ourselves (Note we use Chrome's error messages)
|
||||
*
|
||||
* This function checks for those pre-conditions and throws an TypeError
|
||||
* with the appropriate message if a pre-condition is not met
|
||||
* - `this` instanceof Window is false (requires new)
|
||||
* - supplied required arguments
|
||||
*
|
||||
* @param {Object} thisObj
|
||||
* @param {string} what
|
||||
* @param {Object} [args]
|
||||
* @param {number} [numRequiredArgs]
|
||||
*/
|
||||
Wombat.prototype.domConstructorErrorChecker = function(
|
||||
thisObj,
|
||||
what,
|
||||
args,
|
||||
numRequiredArgs
|
||||
) {
|
||||
var needArgs = typeof numRequiredArgs === 'number' ? numRequiredArgs : 1;
|
||||
var erorMsg;
|
||||
if (thisObj instanceof Window) {
|
||||
erorMsg =
|
||||
"Failed to construct '" +
|
||||
what +
|
||||
"': Please use the 'new' operator, this DOM object constructor cannot be called as a function.";
|
||||
} else if (args && args.length < needArgs) {
|
||||
erorMsg =
|
||||
"Failed to construct '" +
|
||||
what +
|
||||
"': " +
|
||||
needArgs +
|
||||
' argument required, but only 0 present.';
|
||||
}
|
||||
if (erorMsg) {
|
||||
throw new TypeError(erorMsg);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Rewrites the arguments supplied to an function of the Node interface
|
||||
* @param {Object} fnThis
|
||||
@ -1431,8 +1565,9 @@ Wombat.prototype.rewriteNodeFuncArgs = function(
|
||||
this.rewriteElemComplete(newNode);
|
||||
break;
|
||||
case Node.TEXT_NODE:
|
||||
// newNode is the new child of fnThis (the parent node)
|
||||
if (
|
||||
newNode.tagName === 'STYLE' ||
|
||||
fnThis.tagName === 'STYLE' ||
|
||||
(newNode.parentNode && newNode.parentNode.tagName === 'STYLE')
|
||||
) {
|
||||
newNode.textContent = this.rewriteStyle(newNode.textContent);
|
||||
@ -1912,51 +2047,10 @@ 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;
|
||||
}
|
||||
|
||||
if (this.skipWrapScriptBasedOnType(elem.type)) 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')
|
||||
);
|
||||
|
||||
if (this.skipWrapScriptTextBasedOnText(text)) return false;
|
||||
elem.textContent = this.wrapScriptTextJsProxy(text);
|
||||
return true;
|
||||
};
|
||||
|
||||
@ -2230,7 +2324,6 @@ Wombat.prototype.rewriteHtmlFull = function(string, checkEndTag) {
|
||||
|
||||
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;
|
||||
@ -2529,9 +2622,7 @@ Wombat.prototype.rewriteSetTimeoutInterval = function(
|
||||
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;
|
||||
var rw = this.isString(argsObj[0]);
|
||||
// do not mess with the arguments object unless you want instant de-optimization
|
||||
var args = rw ? new Array(argsObj.length) : argsObj;
|
||||
if (rw) {
|
||||
@ -2553,6 +2644,66 @@ Wombat.prototype.rewriteSetTimeoutInterval = function(
|
||||
return originalFn.apply(thisObj, args);
|
||||
};
|
||||
|
||||
/**
|
||||
* Rewrites the value of used in to set SomeElement.[innerHTML|outerHTML]
|
||||
* iframe.srcdoc, or style.textContent handling edge cases e.g. script tags.
|
||||
*
|
||||
* If the element is a style tag and it has a sheet after the new value is set
|
||||
* it, the sheet, is checked for media rules.
|
||||
*
|
||||
* @param {Object} thisObj
|
||||
* @param {Function} oSetter
|
||||
* @param {?string} newValue
|
||||
*/
|
||||
Wombat.prototype.rewriteHTMLAssign = function(thisObj, oSetter, newValue) {
|
||||
var res = newValue;
|
||||
var tagName = thisObj.tagName;
|
||||
if (!thisObj._no_rewrite) {
|
||||
if (tagName === 'STYLE') {
|
||||
res = this.rewriteStyle(newValue);
|
||||
} else {
|
||||
res = this.rewriteHtml(newValue);
|
||||
if (tagName === 'SCRIPT' && res === newValue) {
|
||||
// script tags are used to hold HTML for later use
|
||||
// so we let the rewriteHtml function go first
|
||||
// however in this case inner or outer html was
|
||||
// used to populate a script tag with some JS
|
||||
if (
|
||||
!this.skipWrapScriptBasedOnType(thisObj.type) &&
|
||||
!this.skipWrapScriptTextBasedOnText(newValue)
|
||||
) {
|
||||
res = this.wrapScriptTextJsProxy(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
oSetter.call(thisObj, res);
|
||||
if (
|
||||
this.wbUseAFWorker &&
|
||||
this.WBAutoFetchWorker &&
|
||||
tagName === 'STYLE' &&
|
||||
thisObj.sheet != null
|
||||
) {
|
||||
// got to preserve all the things
|
||||
this.WBAutoFetchWorker.deferredSheetExtraction(thisObj.sheet);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Rewrites the value to be supplied to eval or our injected wrapper
|
||||
* @param {Function} rawEvalOrWrapper
|
||||
* @param {*} evalArg
|
||||
* @return {*}
|
||||
*/
|
||||
Wombat.prototype.rewriteEvalArg = function(rawEvalOrWrapper, evalArg) {
|
||||
var toBeEvald =
|
||||
this.isString(evalArg) &&
|
||||
evalArg.indexOf('_____WB$wombat$assign$function_____') === -1
|
||||
? this.wrapScriptTextJsProxy(evalArg)
|
||||
: evalArg;
|
||||
return rawEvalOrWrapper(toBeEvald);
|
||||
};
|
||||
|
||||
/**
|
||||
* Applies an Event property getter override for the supplied property
|
||||
* @param {string} attr
|
||||
@ -2866,34 +3017,18 @@ Wombat.prototype.overrideHtmlAssign = function(elem, prop, rewriteGetter) {
|
||||
|
||||
if (!orig_setter) return;
|
||||
|
||||
var wombat = this;
|
||||
var rewriteFn = this.rewriteHTMLAssign;
|
||||
|
||||
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);
|
||||
}
|
||||
return rewriteFn(this, orig_setter, orig);
|
||||
};
|
||||
|
||||
var wb_unrewrite_rx = this.wb_unrewrite_rx;
|
||||
|
||||
var getter = function overrideHTMLAssignGetter() {
|
||||
var res = orig_getter.call(this);
|
||||
if (!this._no_rewrite) {
|
||||
return res.replace(wombat.wb_unrewrite_rx, '');
|
||||
return res.replace(wb_unrewrite_rx, '');
|
||||
}
|
||||
return res;
|
||||
};
|
||||
@ -3164,7 +3299,7 @@ Wombat.prototype.overrideTextProtoGetSet = function(textProto, whichProp) {
|
||||
* 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
|
||||
* @param {string} which
|
||||
*/
|
||||
Wombat.prototype.overrideAnUIEvent = function(which) {
|
||||
var didOverrideKey = '__wb_' + which + '_overridden';
|
||||
@ -3205,6 +3340,7 @@ Wombat.prototype.overrideAnUIEvent = function(which) {
|
||||
}
|
||||
this.$wbwindow[which] = (function(EventConstructor) {
|
||||
return function NewEventConstructor(type, init) {
|
||||
wombat.domConstructorErrorChecker(this, which, arguments);
|
||||
if (init) {
|
||||
if (init.view != null) {
|
||||
init.view = wombat.proxyToObj(init.view);
|
||||
@ -3234,7 +3370,9 @@ Wombat.prototype.overrideAnUIEvent = function(which) {
|
||||
* @return {*}
|
||||
*/
|
||||
Wombat.prototype.rewriteParentNodeFn = function(fnThis, originalFn, argsObj) {
|
||||
var argArr = this.rewriteElementsInArguments(argsObj);
|
||||
var argArr = this._no_rewrite
|
||||
? argsObj
|
||||
: this.rewriteElementsInArguments(argsObj);
|
||||
var thisObj = this.proxyToObj(fnThis);
|
||||
if (originalFn.__WB_orig_apply) {
|
||||
return originalFn.__WB_orig_apply(thisObj, argArr);
|
||||
@ -3485,6 +3623,7 @@ Wombat.prototype.initCSSOMOverrides = function() {
|
||||
var oCSSKV = this.$wbwindow.CSSKeywordValue;
|
||||
this.$wbwindow.CSSKeywordValue = (function(CSSKeywordValue_) {
|
||||
return function CSSKeywordValue(cssValue) {
|
||||
wombat.domConstructorErrorChecker(this, 'CSSKeywordValue', arguments);
|
||||
return new CSSKeywordValue_(wombat.rewriteStyle(cssValue));
|
||||
};
|
||||
})(this.$wbwindow.CSSKeywordValue);
|
||||
@ -3521,6 +3660,7 @@ Wombat.prototype.initCSSOMOverrides = function() {
|
||||
}
|
||||
return originalSet.apply(this, newArgs);
|
||||
};
|
||||
|
||||
var originalAppend = this.$wbwindow.StylePropertyMap.prototype.append;
|
||||
this.$wbwindow.StylePropertyMap.prototype.append = function append() {
|
||||
if (arguments.length <= 1) {
|
||||
@ -3552,6 +3692,7 @@ Wombat.prototype.initAudioOverride = function() {
|
||||
var wombat = this;
|
||||
this.$wbwindow.Audio = (function(Audio_) {
|
||||
return function Audio(url) {
|
||||
wombat.domConstructorErrorChecker(this, 'Audio');
|
||||
return new Audio_(wombat.rewriteUrl(url, true, 'oe_'));
|
||||
};
|
||||
})(this.$wbwindow.Audio);
|
||||
@ -3688,6 +3829,7 @@ Wombat.prototype.initFontFaceOverride = function() {
|
||||
var origFontFace = this.$wbwindow.FontFace;
|
||||
this.$wbwindow.FontFace = (function(FontFace_) {
|
||||
return function FontFace(family, source, descriptors) {
|
||||
wombat.domConstructorErrorChecker(this, 'FontFace', arguments, 2);
|
||||
var rwSource = source;
|
||||
if (source != null) {
|
||||
if (typeof source !== 'string') {
|
||||
@ -3866,6 +4008,7 @@ Wombat.prototype.initHTTPOverrides = function() {
|
||||
var orig_request = this.$wbwindow.Request;
|
||||
this.$wbwindow.Request = (function(Request_) {
|
||||
return function Request(input, init_opts) {
|
||||
wombat.domConstructorErrorChecker(this, 'Request', arguments);
|
||||
var newInitOpts = init_opts || {};
|
||||
var newInput = input;
|
||||
var inputType = typeof input;
|
||||
@ -3894,6 +4037,9 @@ Wombat.prototype.initHTTPOverrides = function() {
|
||||
})(this.$wbwindow.Request);
|
||||
|
||||
this.$wbwindow.Request.prototype = orig_request.prototype;
|
||||
Object.defineProperty(this.$wbwindow.Request.prototype, 'constructor', {
|
||||
value: this.$wbwindow.Request
|
||||
});
|
||||
}
|
||||
|
||||
if (this.$wbwindow.Response && this.$wbwindow.Response.prototype) {
|
||||
@ -3912,6 +4058,7 @@ Wombat.prototype.initHTTPOverrides = function() {
|
||||
var origEventSource = this.$wbwindow.EventSource;
|
||||
this.$wbwindow.EventSource = (function(EventSource_) {
|
||||
return function EventSource(url, configuration) {
|
||||
wombat.domConstructorErrorChecker(this, 'EventSource', arguments);
|
||||
var rwURL = url;
|
||||
if (url != null) {
|
||||
rwURL = wombat.rewriteUrl(url);
|
||||
@ -3930,6 +4077,7 @@ Wombat.prototype.initHTTPOverrides = function() {
|
||||
var origWebSocket = this.$wbwindow.WebSocket;
|
||||
this.$wbwindow.WebSocket = (function(WebSocket_) {
|
||||
return function WebSocket(url, configuration) {
|
||||
wombat.domConstructorErrorChecker(this, 'WebSocket', arguments);
|
||||
var rwURL = url;
|
||||
if (url != null) {
|
||||
rwURL = wombat.rewriteWSURL(url);
|
||||
@ -4424,12 +4572,16 @@ Wombat.prototype.initWorkerOverrides = function() {
|
||||
// Worker unrewrite postMessage
|
||||
var orig_worker = this.$wbwindow.Worker;
|
||||
this.$wbwindow.Worker = (function(Worker_) {
|
||||
return function Worker(url) {
|
||||
return new Worker_(wombat.rewriteWorker(url));
|
||||
return function Worker(url, options) {
|
||||
wombat.domConstructorErrorChecker(this, 'Worker', arguments);
|
||||
return new Worker_(wombat.rewriteWorker(url), options);
|
||||
};
|
||||
})(orig_worker);
|
||||
|
||||
this.$wbwindow.Worker.prototype = orig_worker.prototype;
|
||||
Object.defineProperty(this.$wbwindow.Worker.prototype, 'constructor', {
|
||||
value: this.$wbwindow.Worker
|
||||
});
|
||||
this.$wbwindow.Worker._wb_worker_overriden = true;
|
||||
}
|
||||
|
||||
@ -4440,12 +4592,20 @@ Wombat.prototype.initWorkerOverrides = function() {
|
||||
// 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));
|
||||
return function SharedWorker(url, options) {
|
||||
wombat.domConstructorErrorChecker(this, 'SharedWorker', arguments);
|
||||
return new SharedWorker_(wombat.rewriteWorker(url), options);
|
||||
};
|
||||
})(oSharedWorker);
|
||||
|
||||
this.$wbwindow.SharedWorker.prototype = oSharedWorker.prototype;
|
||||
Object.defineProperty(
|
||||
this.$wbwindow.SharedWorker.prototype,
|
||||
'constructor',
|
||||
{
|
||||
value: this.$wbwindow.SharedWorker
|
||||
}
|
||||
);
|
||||
this.$wbwindow.SharedWorker.__wb_sharedWorker_overriden = true;
|
||||
}
|
||||
|
||||
@ -4625,7 +4785,7 @@ Wombat.prototype.initHashChange = function() {
|
||||
if (!message.wb_type) return;
|
||||
|
||||
if (message.wb_type === 'outer_hashchange') {
|
||||
if (wombat.$wbwindow.location.hash !== message.hash) {
|
||||
if (wombat.$wbwindow.location.hash != message.hash) {
|
||||
wombat.$wbwindow.location.hash = message.hash;
|
||||
}
|
||||
}
|
||||
@ -5008,6 +5168,11 @@ Wombat.prototype.initPresentationRequestOverride = function() {
|
||||
var origPresentationRequest = this.$wbwindow.PresentationRequest;
|
||||
this.$wbwindow.PresentationRequest = (function(PresentationRequest_) {
|
||||
return function PresentationRequest(url) {
|
||||
wombat.domConstructorErrorChecker(
|
||||
this,
|
||||
'PresentationRequest',
|
||||
arguments
|
||||
);
|
||||
var rwURL = url;
|
||||
if (url != null) {
|
||||
if (Array.isArray(rwURL)) {
|
||||
@ -5095,7 +5260,7 @@ Wombat.prototype.initStorageOverride = function() {
|
||||
var session;
|
||||
var pLocal = 'localStorage';
|
||||
var pSession = 'sessionStorage';
|
||||
|
||||
ThrowExceptions.yes = false;
|
||||
if (this.$wbwindow.Proxy) {
|
||||
var storageProxyHandler = function() {
|
||||
return {
|
||||
@ -5133,6 +5298,9 @@ Wombat.prototype.initStorageOverride = function() {
|
||||
this.defGetterProp(this.$wbwindow, pSession, function sessionStorage() {
|
||||
return session;
|
||||
});
|
||||
// ensure localStorage instanceof Storage works
|
||||
this.$wbwindow.Storage = Storage;
|
||||
ThrowExceptions.yes = true;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -5144,15 +5312,23 @@ Wombat.prototype.initWindowObjProxy = function($wbwindow) {
|
||||
if (!$wbwindow.Proxy) return undefined;
|
||||
|
||||
var ownProps = this.getAllOwnProps($wbwindow);
|
||||
var funCache = {};
|
||||
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;
|
||||
switch (prop) {
|
||||
case 'top':
|
||||
return wombat.$wbwindow.WB_wombat_top._WB_wombat_obj_proxy;
|
||||
case 'parent':
|
||||
var realParent = $wbwindow.parent;
|
||||
if (realParent === $wbwindow.WB_wombat_top) {
|
||||
return $wbwindow.WB_wombat_top._WB_wombat_obj_proxy;
|
||||
}
|
||||
return realParent._WB_wombat_obj_proxy;
|
||||
}
|
||||
return wombat.defaultProxyGet($wbwindow, prop, ownProps);
|
||||
return wombat.defaultProxyGet($wbwindow, prop, ownProps, funCache);
|
||||
},
|
||||
set: function(target, prop, value) {
|
||||
switch (prop) {
|
||||
@ -5212,6 +5388,7 @@ Wombat.prototype.initWindowObjProxy = function($wbwindow) {
|
||||
if (propDescriptor.configurable === false) {
|
||||
return false;
|
||||
}
|
||||
delete target[prop];
|
||||
delete $wbwindow[prop];
|
||||
return true;
|
||||
},
|
||||
@ -5239,11 +5416,12 @@ Wombat.prototype.initWindowObjProxy = function($wbwindow) {
|
||||
Wombat.prototype.initDocumentObjProxy = function($document) {
|
||||
this.initDocOverrides($document);
|
||||
if (!this.$wbwindow.Proxy) return undefined;
|
||||
var funCache = {};
|
||||
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);
|
||||
return wombat.defaultProxyGet($document, prop, ownProps, funCache);
|
||||
},
|
||||
set: function(target, prop, value) {
|
||||
if (prop === 'location') {
|
||||
@ -5482,6 +5660,42 @@ Wombat.prototype.initWombatTop = function($wbwindow) {
|
||||
this.defProp($wbwindow.Object.prototype, 'WB_wombat_top', setter, getter);
|
||||
};
|
||||
|
||||
/**
|
||||
* There is evil in this world because this is it.
|
||||
* To quote the MDN / every sane person 'Do not ever use eval'.
|
||||
* Why cause we gotta otherwise infinite loops.
|
||||
*/
|
||||
Wombat.prototype.initEvalOverride = function() {
|
||||
var rewriteEvalArg = this.rewriteEvalArg;
|
||||
var setNoop = function() {};
|
||||
var wrappedEval = function wrappedEval(arg) {
|
||||
return rewriteEvalArg(eval, arg);
|
||||
};
|
||||
this.defProp(
|
||||
this.$wbwindow.Object.prototype,
|
||||
'WB_wombat_eval',
|
||||
setNoop,
|
||||
function() {
|
||||
return wrappedEval;
|
||||
}
|
||||
);
|
||||
var runEval = function runEval(func) {
|
||||
return {
|
||||
eval: function(arg) {
|
||||
return rewriteEvalArg(func, arg);
|
||||
}
|
||||
};
|
||||
};
|
||||
this.defProp(
|
||||
this.$wbwindow.Object.prototype,
|
||||
'WB_wombat_runEval',
|
||||
setNoop,
|
||||
function() {
|
||||
return runEval;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize wombat's internal state and apply all overrides
|
||||
* @return {Object}
|
||||
@ -5512,7 +5726,7 @@ Wombat.prototype.wombatInit = function() {
|
||||
this.initDocWriteOpenCloseOverride();
|
||||
|
||||
// eval
|
||||
// initEvalOverride();
|
||||
this.initEvalOverride();
|
||||
|
||||
// Ajax, Fetch, Request, Response, EventSource, WebSocket
|
||||
this.initHTTPOverrides();
|
||||
|
@ -54,3 +54,11 @@ export function autobind(clazz) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Because we overriding specific interfaces (e.g. Storage) that do not expose
|
||||
* an constructor only an interface object with our own we must have a way
|
||||
* to indicate to our overrides when it is proper to throw exceptions
|
||||
* @type {{yes: boolean}}
|
||||
*/
|
||||
export var ThrowExceptions = { yes: false };
|
||||
|
@ -24,6 +24,7 @@ WBWombat.prototype.noRewrite = function(url) {
|
||||
!url ||
|
||||
url.indexOf('blob:') === 0 ||
|
||||
url.indexOf('javascript:') === 0 ||
|
||||
url.indexOf('data:') === 0 ||
|
||||
url.indexOf(this.info.prefix) === 0
|
||||
);
|
||||
};
|
||||
|
40
wombat/test/assets/sandboxDirect.html
Executable file
40
wombat/test/assets/sandboxDirect.html
Executable file
@ -0,0 +1,40 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Wombat sandbox direct</title>
|
||||
<script>
|
||||
window.wombatSandbox = {
|
||||
window,
|
||||
document,
|
||||
originalLocation: location.href
|
||||
};
|
||||
window.wbinfo = {};
|
||||
window.wbinfo.enable_auto_fetch = false;
|
||||
window.wbinfo.top_url = location.href.replace('mp_', '');
|
||||
window.wbinfo.url = 'https://tests.wombat.io/';
|
||||
window.wbinfo.timestamp = '20180803160549';
|
||||
window.wbinfo.request_ts = '20180803160549';
|
||||
window.wbinfo.prefix = decodeURI(
|
||||
`${window.location.protocol}//localhost:${window.location.port}/live/`
|
||||
);
|
||||
window.wbinfo.mod = 'mp_';
|
||||
window.wbinfo.is_framed = true;
|
||||
window.wbinfo.is_live = false;
|
||||
window.wbinfo.coll = '';
|
||||
window.wbinfo.proxy_magic = '';
|
||||
window.wbinfo.static_prefix =
|
||||
'https://content.webrecorder.io/static/bundle/';
|
||||
window.wbinfo.wombat_ts = '20180803160549';
|
||||
window.wbinfo.wombat_sec = '1533312349';
|
||||
window.wbinfo.wombat_scheme = 'https';
|
||||
window.wbinfo.wombat_host = 'tests.wombat.io';
|
||||
window.wbinfo.wombat_opts = {};
|
||||
window.wbinfo.state = '';
|
||||
window.wbinfo.metadata = {};
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<script src="/wombatDirect.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -126,7 +126,6 @@
|
||||
<script>
|
||||
const defaultText = ' not sent';
|
||||
const domStructure = {
|
||||
sandbox: document.getElementById('wombatSandbox'),
|
||||
load: {
|
||||
url: document.createTextNode(defaultText),
|
||||
title: document.createTextNode(defaultText),
|
||||
@ -187,7 +186,11 @@
|
||||
document.getElementById('title-title').appendChild(domStructure.titleMsg);
|
||||
document.getElementById('hash-hash').appendChild(domStructure.hashchange);
|
||||
document.getElementById('unknown-msg').appendChild(domStructure.unknown);
|
||||
window.overwatch = new TestOverwatch(domStructure);
|
||||
window.overwatch = new TestOverwatch({
|
||||
domStructure,
|
||||
sandbox: document.getElementById('wombatSandbox'),
|
||||
direct: false
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
20
wombat/test/assets/testPageDirect.html
Normal file
20
wombat/test/assets/testPageDirect.html
Normal file
@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Wombat Direct Tests</title>
|
||||
<script src="./testPageBundle.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<iframe
|
||||
id="wombatSandbox"
|
||||
src="/live/20180803160549mp_/https://tests.direct.wombat.io/"
|
||||
></iframe>
|
||||
<script>
|
||||
window.overwatch = new TestOverwatch({
|
||||
sandbox: document.getElementById('wombatSandbox'),
|
||||
direct: true
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
225
wombat/test/direct-custom-storage.js
Normal file
225
wombat/test/direct-custom-storage.js
Normal file
@ -0,0 +1,225 @@
|
||||
import test from 'ava';
|
||||
import TestHelper from './helpers/testHelper';
|
||||
|
||||
/**
|
||||
* @type {TestHelper}
|
||||
*/
|
||||
let helper = null;
|
||||
|
||||
test.before(async t => {
|
||||
helper = await TestHelper.init(t, true);
|
||||
});
|
||||
|
||||
test.beforeEach(async t => {
|
||||
t.context.sandbox = helper.sandbox();
|
||||
t.context.testPage = helper.testPage();
|
||||
t.context.server = helper.server();
|
||||
await t.context.sandbox.evaluate(() => {
|
||||
window.fakeWombat = {
|
||||
$wbwindow: {
|
||||
WB_wombat_location: {
|
||||
href: 'bogus url'
|
||||
}
|
||||
},
|
||||
storage_listeners: {
|
||||
sEvents: [],
|
||||
map(sEvent) {
|
||||
this.sEvents.push(sEvent);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
test.after.always(async t => {
|
||||
await helper.stop();
|
||||
});
|
||||
|
||||
test('Storage - creation: should not throw errors', async t => {
|
||||
const { sandbox, server } = t.context;
|
||||
const creationPromise = sandbox.evaluate(() => {
|
||||
new Storage();
|
||||
});
|
||||
await t.notThrowsAsync(creationPromise);
|
||||
});
|
||||
|
||||
test('Storage - post creation: internal values should not be exposed', async t => {
|
||||
const { sandbox, server } = t.context;
|
||||
const testResult = await sandbox.evaluate(() => {
|
||||
const storage = new Storage(window.fakeWombat, 'bogus value');
|
||||
return { ...storage };
|
||||
});
|
||||
t.deepEqual(testResult, {});
|
||||
});
|
||||
|
||||
test('Storage - getItem: the item set should be retrievable', async t => {
|
||||
const { sandbox, server } = t.context;
|
||||
const testResult = await sandbox.evaluate(() => {
|
||||
const storage = new Storage(window.fakeWombat, 'bogus value');
|
||||
const key = 'a';
|
||||
const value = 'b';
|
||||
storage.setItem(key, value);
|
||||
return storage.getItem(key) === value;
|
||||
});
|
||||
t.true(testResult);
|
||||
});
|
||||
|
||||
test('Storage - setItem: the item set should be mapped and an storage event fired', async t => {
|
||||
const { sandbox, server } = t.context;
|
||||
const testResult = await sandbox.evaluate(() => {
|
||||
const storage = new Storage(window.fakeWombat, 'bogus value');
|
||||
const key = 'a';
|
||||
const value = 'b';
|
||||
storage.setItem(key, value);
|
||||
const events = window.fakeWombat.storage_listeners.sEvents;
|
||||
const event = events[0];
|
||||
return {
|
||||
stored: storage.data[key] === value,
|
||||
numEvents: events.length,
|
||||
key: event.key === key,
|
||||
newValue: event.newValue === value,
|
||||
oldValue: event.oldValue === null,
|
||||
storageArea: event.storageArea === storage,
|
||||
url: event.url === 'bogus url'
|
||||
};
|
||||
});
|
||||
t.deepEqual(testResult, {
|
||||
stored: true,
|
||||
numEvents: 1,
|
||||
key: true,
|
||||
newValue: true,
|
||||
oldValue: true,
|
||||
storageArea: true,
|
||||
url: true
|
||||
});
|
||||
});
|
||||
|
||||
test('Storage - removeItem: the item set should be removable and an event should be fired indicating removal', async t => {
|
||||
const { sandbox, server } = t.context;
|
||||
const testResult = await sandbox.evaluate(() => {
|
||||
const storage = new Storage(window.fakeWombat, 'bogus value');
|
||||
const key = 'a';
|
||||
const value = 'b';
|
||||
storage.setItem(key, value);
|
||||
storage.removeItem(key);
|
||||
const events = window.fakeWombat.storage_listeners.sEvents;
|
||||
const event = events[1];
|
||||
return {
|
||||
stored: storage.data[key] === undefined,
|
||||
numEvents: events.length,
|
||||
key: event.key === key,
|
||||
newValue: event.newValue === null,
|
||||
oldValue: event.oldValue === value,
|
||||
storageArea: event.storageArea === storage,
|
||||
url: event.url === 'bogus url'
|
||||
};
|
||||
});
|
||||
t.deepEqual(testResult, {
|
||||
stored: true,
|
||||
numEvents: 2,
|
||||
key: true,
|
||||
newValue: true,
|
||||
oldValue: true,
|
||||
storageArea: true,
|
||||
url: true
|
||||
});
|
||||
});
|
||||
|
||||
test('Storage - clear: should clear all stored items and an event should be fired indicating clearing', async t => {
|
||||
const { sandbox, server } = t.context;
|
||||
const testResult = await sandbox.evaluate(() => {
|
||||
const storage = new Storage(window.fakeWombat, 'bogus value');
|
||||
const key = 'a';
|
||||
const value = 'b';
|
||||
storage.setItem(key, value);
|
||||
storage.clear();
|
||||
const events = window.fakeWombat.storage_listeners.sEvents;
|
||||
const event = events[1];
|
||||
return {
|
||||
numEvents: events.length,
|
||||
key: event.key === null,
|
||||
newValue: event.newValue === null,
|
||||
oldValue: event.oldValue === null,
|
||||
storageArea: event.storageArea === storage,
|
||||
url: event.url === 'bogus url'
|
||||
};
|
||||
});
|
||||
t.deepEqual(testResult, {
|
||||
numEvents: 2,
|
||||
key: true,
|
||||
newValue: true,
|
||||
oldValue: true,
|
||||
storageArea: true,
|
||||
url: true
|
||||
});
|
||||
});
|
||||
|
||||
test('Storage - key: should return the correct key given the keys index', async t => {
|
||||
const { sandbox, server } = t.context;
|
||||
const testResult = await sandbox.evaluate(() => {
|
||||
const storage = new Storage(window.fakeWombat, 'bogus value');
|
||||
const key1 = 'a1';
|
||||
const key2 = 'a2';
|
||||
const value1 = 'b1';
|
||||
const value2 = 'b2';
|
||||
storage.setItem(key1, value1);
|
||||
storage.setItem(key2, value2);
|
||||
return (
|
||||
storage.key(0) === key1 &&
|
||||
storage.key(1) === key2 &&
|
||||
storage.key(2) === null
|
||||
);
|
||||
});
|
||||
t.true(testResult);
|
||||
});
|
||||
|
||||
test('Storage - fireEvent: should fire a StorageEvent with the supplied arguments', async t => {
|
||||
const { sandbox, server } = t.context;
|
||||
const testResult = await sandbox.evaluate(() => {
|
||||
const storage = new Storage(window.fakeWombat, 'bogus value');
|
||||
storage.fireEvent('a', 'b', 'c');
|
||||
const events = window.fakeWombat.storage_listeners.sEvents;
|
||||
const event = events[0];
|
||||
return {
|
||||
numEvents: events.length,
|
||||
key: event.key === 'a',
|
||||
newValue: event.newValue === 'c',
|
||||
oldValue: event.oldValue === 'b',
|
||||
storageArea: event.storageArea === storage,
|
||||
url: event.url === 'bogus url'
|
||||
};
|
||||
});
|
||||
t.deepEqual(testResult, {
|
||||
numEvents: 1,
|
||||
key: true,
|
||||
newValue: true,
|
||||
oldValue: true,
|
||||
storageArea: true,
|
||||
url: true
|
||||
});
|
||||
});
|
||||
|
||||
test('Storage - valueOf: should return the correct value', async t => {
|
||||
const { sandbox, server } = t.context;
|
||||
const testResult = await sandbox.evaluate(() => {
|
||||
const storage = new Storage(window.fakeWombat, 'bogus value');
|
||||
fakeWombat.$wbwindow['bogus value'] = storage;
|
||||
return storage.valueOf() === storage;
|
||||
});
|
||||
t.true(testResult);
|
||||
});
|
||||
|
||||
test('Storage - length: should return the correct value', async t => {
|
||||
const { sandbox, server } = t.context;
|
||||
const testResult = await sandbox.evaluate(() => {
|
||||
const storage = new Storage(window.fakeWombat, 'bogus value');
|
||||
const key1 = 'a1';
|
||||
const key2 = 'a2';
|
||||
const value1 = 'b1';
|
||||
const value2 = 'b2';
|
||||
storage.setItem(key1, value1);
|
||||
storage.setItem(key2, value2);
|
||||
return storage.length;
|
||||
});
|
||||
t.is(testResult, 2);
|
||||
});
|
235
wombat/test/direct-funcmap.js
Normal file
235
wombat/test/direct-funcmap.js
Normal file
@ -0,0 +1,235 @@
|
||||
import test from 'ava';
|
||||
import TestHelper from './helpers/testHelper';
|
||||
|
||||
/**
|
||||
* @type {TestHelper}
|
||||
*/
|
||||
let helper = null;
|
||||
|
||||
test.before(async t => {
|
||||
helper = await TestHelper.init(t, true);
|
||||
});
|
||||
|
||||
test.beforeEach(async t => {
|
||||
t.context.sandbox = helper.sandbox();
|
||||
t.context.testPage = helper.testPage();
|
||||
t.context.server = helper.server();
|
||||
await t.context.sandbox.evaluate(() => {
|
||||
window.fnsCalled = {
|
||||
nTn: {
|
||||
callCount: 0,
|
||||
params: []
|
||||
},
|
||||
nTa: {
|
||||
callCount: 0,
|
||||
params: []
|
||||
},
|
||||
aTn: {
|
||||
callCount: 0,
|
||||
params: []
|
||||
},
|
||||
aTa: {
|
||||
callCount: 0,
|
||||
params: []
|
||||
}
|
||||
};
|
||||
window.testedFNs = [
|
||||
{
|
||||
testName: 'nTn',
|
||||
key() {
|
||||
return 'key nTn';
|
||||
},
|
||||
value(param) {
|
||||
window.fnsCalled.nTn.callCount += 1;
|
||||
window.fnsCalled.nTn.params.push(param);
|
||||
return 'value nTn';
|
||||
}
|
||||
},
|
||||
{
|
||||
testName: 'nTa',
|
||||
key() {
|
||||
return 'key nTa';
|
||||
},
|
||||
value: param => {
|
||||
window.fnsCalled.nTa.callCount += 1;
|
||||
window.fnsCalled.nTa.params.push(param);
|
||||
return 'value nTa';
|
||||
}
|
||||
},
|
||||
{
|
||||
testName: 'aTn',
|
||||
key: () => 'key aTn',
|
||||
value(param) {
|
||||
window.fnsCalled.aTn.callCount += 1;
|
||||
window.fnsCalled.aTn.params.push(param);
|
||||
return 'value aTn';
|
||||
}
|
||||
},
|
||||
{
|
||||
testName: 'aTa',
|
||||
key: () => 'key aTa',
|
||||
value: param => {
|
||||
window.fnsCalled.aTa.callCount += 1;
|
||||
window.fnsCalled.aTa.params.push(param);
|
||||
return 'value aTa';
|
||||
}
|
||||
}
|
||||
];
|
||||
});
|
||||
});
|
||||
|
||||
test.after.always(async t => {
|
||||
await helper.stop();
|
||||
});
|
||||
|
||||
test('Storage - creation: should not throw errors', async t => {
|
||||
const { sandbox, server } = t.context;
|
||||
const creationPromise = sandbox.evaluate(() => {
|
||||
new Storage();
|
||||
});
|
||||
await t.notThrowsAsync(creationPromise);
|
||||
});
|
||||
|
||||
test('FuncMap - set: normal and arrow functions should be added', async t => {
|
||||
const { sandbox, server } = t.context;
|
||||
const numKeys = await sandbox.evaluate(() => {
|
||||
const fnm = new FuncMap();
|
||||
for (let i = 0; i < testedFNs.length; i++) {
|
||||
const { key, value } = testedFNs[i];
|
||||
fnm.set(key, value);
|
||||
}
|
||||
return fnm._map.length;
|
||||
});
|
||||
t.is(numKeys, 4);
|
||||
});
|
||||
|
||||
test('FuncMap - get: normal and arrow functions that are added should be retrieved per the key', async t => {
|
||||
const { sandbox, server } = t.context;
|
||||
const testResult = await sandbox.evaluate(() => {
|
||||
const result = {
|
||||
nTn: false,
|
||||
nTa: false,
|
||||
aTn: false,
|
||||
aTa: false
|
||||
};
|
||||
const fnm = new FuncMap();
|
||||
for (let i = 0; i < testedFNs.length; i++) {
|
||||
const { key, value } = testedFNs[i];
|
||||
fnm.set(key, value);
|
||||
}
|
||||
for (let i = 0; i < testedFNs.length; i++) {
|
||||
const { testName, key, value } = testedFNs[i];
|
||||
const mappedValue = fnm.get(key);
|
||||
result[testName] = mappedValue === value && mappedValue() === value();
|
||||
}
|
||||
return result;
|
||||
});
|
||||
t.deepEqual(testResult, {
|
||||
nTn: true,
|
||||
nTa: true,
|
||||
aTn: true,
|
||||
aTa: true
|
||||
});
|
||||
});
|
||||
|
||||
test('FuncMap - find: should return the correct index of the internal mapping', async t => {
|
||||
const { sandbox, server } = t.context;
|
||||
const testResult = await sandbox.evaluate(() => {
|
||||
const fnm = new FuncMap();
|
||||
for (let i = 0; i < testedFNs.length; i++) {
|
||||
const { key, value } = testedFNs[i];
|
||||
fnm.set(key, value);
|
||||
}
|
||||
return testedFNs.every(({ key, value }) => {
|
||||
const idx = fnm.find(key);
|
||||
return fnm._map[idx][1] === value;
|
||||
});
|
||||
});
|
||||
t.true(testResult);
|
||||
});
|
||||
|
||||
test('FuncMap - add_or_get: should correctly add a function when no mapping exists and should return the existing mapping, not add a function, when a previous mapping was added', async t => {
|
||||
const { sandbox, server } = t.context;
|
||||
const testResult = await sandbox.evaluate(() => {
|
||||
const fnm = new FuncMap();
|
||||
let initerCalled = 0;
|
||||
const initer = () => {
|
||||
initerCalled += 1;
|
||||
return () => {};
|
||||
};
|
||||
const key = () => {};
|
||||
const fistCall = fnm.add_or_get(key, initer);
|
||||
const secondCall = fnm.add_or_get(key, initer);
|
||||
return {
|
||||
initerCalled,
|
||||
mappingCheck: fistCall === secondCall
|
||||
};
|
||||
});
|
||||
await t.deepEqual(testResult, {
|
||||
initerCalled: 1,
|
||||
mappingCheck: true
|
||||
});
|
||||
});
|
||||
|
||||
test('FuncMap - remove: should remove the mapped function and return the mapped value if a mapping exists', async t => {
|
||||
const { sandbox, server } = t.context;
|
||||
const testResult = await sandbox.evaluate(() => {
|
||||
const result = {
|
||||
nTn: false,
|
||||
nTa: false,
|
||||
aTn: false,
|
||||
aTa: false,
|
||||
noMapping: false
|
||||
};
|
||||
const fnm = new FuncMap();
|
||||
for (let i = 0; i < testedFNs.length; i++) {
|
||||
const { key, value } = testedFNs[i];
|
||||
fnm.set(key, value);
|
||||
}
|
||||
for (let i = 0; i < testedFNs.length; i++) {
|
||||
const { testName, key, value } = testedFNs[i];
|
||||
result[testName] = fnm.remove(key) === value;
|
||||
}
|
||||
result.noMapping = fnm.remove(() => {}) == null;
|
||||
return result;
|
||||
});
|
||||
t.deepEqual(testResult, {
|
||||
nTn: true,
|
||||
nTa: true,
|
||||
aTn: true,
|
||||
aTa: true,
|
||||
noMapping: true
|
||||
});
|
||||
});
|
||||
|
||||
test('FuncMap - map: should call every mapped with the supplied arguments', async t => {
|
||||
const { sandbox, server } = t.context;
|
||||
const testResult = await sandbox.evaluate(() => {
|
||||
const fnm = new FuncMap();
|
||||
for (let i = 0; i < testedFNs.length; i++) {
|
||||
const { key, value } = testedFNs[i];
|
||||
fnm.set(key, value);
|
||||
}
|
||||
fnm.map('the param 1');
|
||||
fnm.map('the param 2');
|
||||
return window.fnsCalled;
|
||||
});
|
||||
t.deepEqual(testResult, {
|
||||
nTn: {
|
||||
callCount: 2,
|
||||
params: ['the param 1', 'the param 2']
|
||||
},
|
||||
nTa: {
|
||||
callCount: 2,
|
||||
params: ['the param 1', 'the param 2']
|
||||
},
|
||||
aTn: {
|
||||
callCount: 2,
|
||||
params: ['the param 1', 'the param 2']
|
||||
},
|
||||
aTa: {
|
||||
callCount: 2,
|
||||
params: ['the param 1', 'the param 2']
|
||||
}
|
||||
});
|
||||
});
|
161
wombat/test/direct-wombat-util-fns.js
Normal file
161
wombat/test/direct-wombat-util-fns.js
Normal file
@ -0,0 +1,161 @@
|
||||
import test from 'ava';
|
||||
import TestHelper from './helpers/testHelper';
|
||||
import { NativeFnTest, SaveSrcSetDataSrcSet } from './helpers/testedValues';
|
||||
|
||||
/**
|
||||
* @type {TestHelper}
|
||||
*/
|
||||
let helper = null;
|
||||
|
||||
test.before(async t => {
|
||||
helper = await TestHelper.init(t, true);
|
||||
});
|
||||
|
||||
test.beforeEach(async t => {
|
||||
t.context.sandbox = helper.sandbox();
|
||||
t.context.testPage = helper.testPage();
|
||||
t.context.server = helper.server();
|
||||
await helper.initWombat();
|
||||
});
|
||||
|
||||
test.after.always(async t => {
|
||||
await helper.stop();
|
||||
});
|
||||
|
||||
test('getPageUnderModifier: should return the modifier the page is under', async t => {
|
||||
const { sandbox } = t.context;
|
||||
const testResult = await sandbox.evaluate(() =>
|
||||
wombat.getPageUnderModifier()
|
||||
);
|
||||
t.is(testResult, 'mp_');
|
||||
});
|
||||
|
||||
test('isNativeFunction: should return T/F indicating if a function is a native function or not', async t => {
|
||||
const { sandbox } = t.context;
|
||||
const testResult = await sandbox.evaluate(NativeFnTest.testFN);
|
||||
t.deepEqual(testResult, NativeFnTest.expectedValue);
|
||||
});
|
||||
|
||||
for (let i = 0; i < SaveSrcSetDataSrcSet.values.length; i++) {
|
||||
const value = SaveSrcSetDataSrcSet.values[i];
|
||||
test(`isSavedSrcSrcset: should return '${value.expected}' for '${
|
||||
value.name
|
||||
}'`, async t => {
|
||||
const { sandbox } = t.context;
|
||||
const testResult = await sandbox.evaluate(
|
||||
SaveSrcSetDataSrcSet.testFnSS,
|
||||
value.tagName,
|
||||
value.parentElement
|
||||
);
|
||||
t.is(testResult, value.expected);
|
||||
});
|
||||
test(`isSavedDataSrcSrcset: should return '${value.expected}' for '${
|
||||
value.name
|
||||
}' if it has data.srcset and 'false' when it does not`, async t => {
|
||||
const { sandbox } = t.context;
|
||||
const testResult = await sandbox.evaluate(
|
||||
SaveSrcSetDataSrcSet.testFnDSS,
|
||||
value.tagName,
|
||||
value.parentElement
|
||||
);
|
||||
t.deepEqual(testResult, {
|
||||
with: value.expected,
|
||||
without: false
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
test('isArgumentsObj: should return T/F indicating if the supplied object is the arguments object', async t => {
|
||||
const { sandbox } = t.context;
|
||||
const testResult = await sandbox.evaluate(() => ({
|
||||
null: wombat.isArgumentsObj(null),
|
||||
undefined: wombat.isArgumentsObj(undefined),
|
||||
objToStringNotFn: wombat.isArgumentsObj({ toString: 1 }),
|
||||
objToStringNotArgumentsObject: wombat.isArgumentsObj({}),
|
||||
actualArgumentsObject: wombat.isArgumentsObj(
|
||||
(function() {
|
||||
return arguments;
|
||||
})()
|
||||
)
|
||||
}));
|
||||
t.deepEqual(testResult, {
|
||||
null: false,
|
||||
undefined: false,
|
||||
objToStringNotFn: false,
|
||||
objToStringNotArgumentsObject: false,
|
||||
actualArgumentsObject: true
|
||||
});
|
||||
});
|
||||
|
||||
test('deproxyArrayHandlingArgumentsObj: should deproxy elements in both an array and the arguments object', async t => {
|
||||
const { sandbox } = t.context;
|
||||
const testResult = await sandbox.evaluate(() => {
|
||||
const makeProxy = returnWhat =>
|
||||
new Proxy(
|
||||
{},
|
||||
{
|
||||
get(target, p, receiver) {
|
||||
if (p === '__WBProxyRealObj__') return returnWhat;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
);
|
||||
const argumentsObjWithProxies = (function() {
|
||||
return arguments;
|
||||
})(makeProxy(1), makeProxy(2));
|
||||
const argumentsDeproxied = wombat.deproxyArrayHandlingArgumentsObj(
|
||||
argumentsObjWithProxies
|
||||
);
|
||||
const justAnArrayWithProxies = [makeProxy(3), makeProxy(4)];
|
||||
const justArrayDeproxied = wombat.deproxyArrayHandlingArgumentsObj(
|
||||
justAnArrayWithProxies
|
||||
);
|
||||
return {
|
||||
argsDeproxied:
|
||||
Array.isArray(argumentsDeproxied) &&
|
||||
argumentsDeproxied !== argumentsObjWithProxies &&
|
||||
argumentsDeproxied[0] === 1 &&
|
||||
argumentsDeproxied[1] === 2,
|
||||
arrayDeproxied:
|
||||
Array.isArray(justArrayDeproxied) &&
|
||||
justArrayDeproxied === justAnArrayWithProxies &&
|
||||
justArrayDeproxied[0] === 3 &&
|
||||
justArrayDeproxied[1] === 4
|
||||
};
|
||||
});
|
||||
t.deepEqual(testResult, {
|
||||
argsDeproxied: true,
|
||||
arrayDeproxied: true
|
||||
});
|
||||
});
|
||||
|
||||
test('deproxyArrayHandlingArgumentsObj: should return the original argument if it is falsy, a NodeList, has not elements, or the length property is falsy', async t => {
|
||||
const { sandbox } = t.context;
|
||||
const testResult = await sandbox.evaluate(() => {
|
||||
const nl = document.querySelectorAll('*');
|
||||
const dpNL = wombat.deproxyArrayHandlingArgumentsObj(nl);
|
||||
const zeroLenArray = [];
|
||||
const zeroLenArguments = (function() {
|
||||
return arguments;
|
||||
})();
|
||||
const falsyLength = {};
|
||||
return {
|
||||
falsey: wombat.deproxyArrayHandlingArgumentsObj(null) === null,
|
||||
nodeList: dpNL instanceof NodeList && dpNL === nl,
|
||||
zeroLenArray:
|
||||
wombat.deproxyArrayHandlingArgumentsObj(zeroLenArray) === zeroLenArray,
|
||||
zeroLenArguments:
|
||||
wombat.deproxyArrayHandlingArgumentsObj(zeroLenArguments) ===
|
||||
zeroLenArguments,
|
||||
falsyLength:
|
||||
wombat.deproxyArrayHandlingArgumentsObj(falsyLength) === falsyLength
|
||||
};
|
||||
});
|
||||
t.deepEqual(testResult, {
|
||||
falsey: true,
|
||||
nodeList: true,
|
||||
zeroLenArray: true,
|
||||
zeroLenArguments: true,
|
||||
falsyLength: true
|
||||
});
|
||||
});
|
@ -1,24 +1,16 @@
|
||||
const cp = require('child_process');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
const fs = require('fs-extra');
|
||||
const readline = require('readline');
|
||||
const chromeFinder = require('chrome-launcher/dist/chrome-finder');
|
||||
const criHelper = require('chrome-remote-interface-extra/lib/helper');
|
||||
const { launch } = require('just-launch-chrome');
|
||||
const Browser = require('chrome-remote-interface-extra/lib/browser/Browser');
|
||||
|
||||
const CHROME_PROFILE_PATH = path.join(os.tmpdir(), 'temp_chrome_profile-');
|
||||
const winPos = !process.env.NO_MOVE_WINDOW ? '--window-position=2000,0' : '';
|
||||
|
||||
const chromeArgs = userDataDir => [
|
||||
'--enable-automation',
|
||||
const chromeArgs = [
|
||||
'--force-color-profile=srgb',
|
||||
'--remote-debugging-port=9222',
|
||||
'--disable-background-networking',
|
||||
'--disable-background-timer-throttling',
|
||||
'--disable-renderer-backgrounding',
|
||||
'--disable-backgrounding-occluded-windows',
|
||||
'--disable-ipc-flooding-protection',
|
||||
'--enable-features=NetworkService,NetworkServiceInProcess',
|
||||
'--enable-features=NetworkService,NetworkServiceInProcess,AwaitOptimization',
|
||||
'--disable-client-side-phishing-detection',
|
||||
'--disable-default-apps',
|
||||
'--disable-extensions',
|
||||
@ -28,7 +20,7 @@ const chromeArgs = userDataDir => [
|
||||
'--disable-sync',
|
||||
'--disable-domain-reliability',
|
||||
'--disable-infobars',
|
||||
'--disable-features=site-per-process,TranslateUI',
|
||||
'--disable-features=site-per-process,TranslateUI,BlinkGenPropertyTrees,LazyFrameLoading',
|
||||
'--disable-breakpad',
|
||||
'--disable-backing-store-limit',
|
||||
'--metrics-recording-only',
|
||||
@ -38,131 +30,26 @@ const chromeArgs = userDataDir => [
|
||||
'--use-mock-keychain',
|
||||
'--mute-audio',
|
||||
'--autoplay-policy=no-user-gesture-required',
|
||||
`--user-data-dir=${userDataDir}`,
|
||||
winPos,
|
||||
'about:blank'
|
||||
];
|
||||
|
||||
const preferredExes = {
|
||||
linux: [
|
||||
'google-chrome-unstable',
|
||||
'google-chrome-beta',
|
||||
'google-chrome-stable'
|
||||
],
|
||||
darwin: ['Chrome Canary', 'Chrome'],
|
||||
win32: []
|
||||
};
|
||||
|
||||
function findChrome() {
|
||||
const findingFn = chromeFinder[process.platform];
|
||||
if (findingFn == null) {
|
||||
throw new Error(
|
||||
`Can not find chrome exe, unsupported platform - ${process.platform}`
|
||||
);
|
||||
}
|
||||
const exes = findingFn();
|
||||
const preferred = preferredExes[process.platform] || [];
|
||||
let exe;
|
||||
for (let i = 0; i < preferred.length; i++) {
|
||||
exe = exes.find(anExe => anExe.includes(preferred[i]));
|
||||
if (exe) return exe;
|
||||
}
|
||||
return exes[0];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return {Promise<{chromeProcess: ChildProcess, killChrome: function(): void}>}
|
||||
* @return {Promise<Browser>}
|
||||
*/
|
||||
async function initChrome() {
|
||||
const executable = findChrome();
|
||||
const userDataDir = await fs.mkdtemp(CHROME_PROFILE_PATH);
|
||||
const chromeArguments = chromeArgs(userDataDir);
|
||||
const chromeProcess = cp.spawn(executable, chromeArguments, {
|
||||
stdio: ['ignore', 'ignore', 'pipe'],
|
||||
env: process.env,
|
||||
detached: process.platform !== 'win32'
|
||||
const { browserWSEndpoint, closeBrowser, chromeProcess } = await launch({
|
||||
args: chromeArgs
|
||||
});
|
||||
|
||||
const maybeRemoveUDataDir = () => {
|
||||
try {
|
||||
fs.removeSync(userDataDir);
|
||||
} catch (e) {}
|
||||
};
|
||||
|
||||
let killed = false;
|
||||
|
||||
const killChrome = () => {
|
||||
if (killed) {
|
||||
return;
|
||||
}
|
||||
killed = true;
|
||||
chromeProcess.kill('SIGKILL');
|
||||
// process.kill(-chromeProcess.pid, 'SIGKILL')
|
||||
maybeRemoveUDataDir();
|
||||
};
|
||||
|
||||
process.on('exit', killChrome);
|
||||
chromeProcess.once('exit', maybeRemoveUDataDir);
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
killChrome();
|
||||
process.exit(130);
|
||||
const browser = await Browser.connect(browserWSEndpoint, {
|
||||
ignoreHTTPSErrors: true,
|
||||
additionalDomains: { workers: true },
|
||||
process: chromeProcess,
|
||||
closeCallback: closeBrowser
|
||||
});
|
||||
process.once('SIGTERM', killChrome);
|
||||
process.once('SIGHUP', killChrome);
|
||||
await waitForWSEndpoint(chromeProcess, 15 * 1000);
|
||||
return { chromeProcess, killChrome };
|
||||
await browser.waitForTarget(t => t.type() === 'page');
|
||||
return browser;
|
||||
}
|
||||
|
||||
module.exports = initChrome;
|
||||
|
||||
// module.exports = initChrome
|
||||
function waitForWSEndpoint(chromeProcess, timeout) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const rl = readline.createInterface({ input: chromeProcess.stderr });
|
||||
let stderr = '';
|
||||
const listeners = [
|
||||
criHelper.helper.addEventListener(rl, 'line', onLine),
|
||||
criHelper.helper.addEventListener(rl, 'close', onClose),
|
||||
criHelper.helper.addEventListener(chromeProcess, 'exit', onClose),
|
||||
criHelper.helper.addEventListener(chromeProcess, 'error', onClose)
|
||||
];
|
||||
const timeoutId = timeout ? setTimeout(onTimeout, timeout) : 0;
|
||||
|
||||
function onClose() {
|
||||
cleanup();
|
||||
reject(new Error(['Failed to launch chrome!', stderr].join('\n')));
|
||||
}
|
||||
|
||||
function onTimeout() {
|
||||
cleanup();
|
||||
reject(
|
||||
new Error(
|
||||
`Timed out after ${timeout} ms while trying to connect to Chrome!`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} line
|
||||
*/
|
||||
function onLine(line) {
|
||||
stderr += line + '\n';
|
||||
const match = line.match(/^DevTools listening on (ws:\/\/.*)$/);
|
||||
if (!match) {
|
||||
return;
|
||||
}
|
||||
cleanup();
|
||||
resolve(match[1]);
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
criHelper.helper.removeEventListeners(listeners);
|
||||
rl.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -8,8 +8,12 @@ const gracefullShutdownTimeout = 50000;
|
||||
const shutdownOnSignals = ['SIGINT', 'SIGTERM', 'SIGHUP'];
|
||||
const assetsPath = path.join(__dirname, '..', 'assets');
|
||||
const httpsSandboxPath = path.join(assetsPath, 'sandbox.html');
|
||||
const sandboxDirectPath = path.join(assetsPath, 'sandboxDirect.html');
|
||||
const theyFoundItPath = path.join(assetsPath, 'it.html');
|
||||
|
||||
const testPageURL = `http://localhost:${port}/testPage.html`;
|
||||
const testPageDirectURL = `http://localhost:${port}/testPageDirect.html`;
|
||||
|
||||
function promiseResolveReject() {
|
||||
const prr = { promise: null, resolve: null, reject: null };
|
||||
prr.promise = new Promise((resolve, reject) => {
|
||||
@ -85,6 +89,15 @@ async function initServer() {
|
||||
return fs.createReadStream(httpsSandboxPath);
|
||||
}
|
||||
)
|
||||
.get(
|
||||
'/live/20180803160549mp_/https://tests.direct.wombat.io/',
|
||||
(request, reply) => {
|
||||
reply
|
||||
.type('text/html')
|
||||
.status(200)
|
||||
.send(fs.createReadStream(sandboxDirectPath));
|
||||
}
|
||||
)
|
||||
.get(
|
||||
'/live/20180803160549mp_/https://tests.wombat.io/test',
|
||||
async (request, reply) => {
|
||||
@ -103,7 +116,8 @@ async function initServer() {
|
||||
fastify.reset();
|
||||
return fastify.close();
|
||||
})
|
||||
.decorate('testPage', `http://localhost:${port}/testPage.html`)
|
||||
.decorate('testPage', testPageURL)
|
||||
.decorate('testPageDirect', testPageDirectURL)
|
||||
.decorate('waitForRequest', route => {
|
||||
let prr = requestSubscribers.get(route);
|
||||
if (prr) return prr.promise;
|
||||
|
@ -1,29 +1,22 @@
|
||||
const initChrome = require('./initChrome');
|
||||
const initServer = require('./initServer');
|
||||
const { CRIExtra, Browser, Events } = require('chrome-remote-interface-extra');
|
||||
|
||||
const testDomains = { workers: true };
|
||||
const { Browser } = require('chrome-remote-interface-extra');
|
||||
|
||||
class TestHelper {
|
||||
/**
|
||||
* @param {*} t
|
||||
* @param {boolean} [direct = false]
|
||||
* @return {Promise<TestHelper>}
|
||||
*/
|
||||
static async init(t) {
|
||||
const { chromeProcess, killChrome } = await initChrome();
|
||||
static async init(t, direct = false) {
|
||||
const browser = await initChrome();
|
||||
const server = await initServer();
|
||||
const { webSocketDebuggerUrl } = await CRIExtra.Version();
|
||||
const client = await CRIExtra({ target: webSocketDebuggerUrl });
|
||||
const browser = await Browser.create(client, {
|
||||
ignoreHTTPSErrors: true,
|
||||
process: chromeProcess,
|
||||
additionalDomains: testDomains,
|
||||
async closeCallback() {
|
||||
killChrome();
|
||||
}
|
||||
const th = new TestHelper({
|
||||
server,
|
||||
browser,
|
||||
t,
|
||||
direct
|
||||
});
|
||||
await browser.waitForTarget(t => t.type() === 'page');
|
||||
const th = new TestHelper({ server, client, browser, t, killChrome });
|
||||
await th.setup();
|
||||
return th;
|
||||
}
|
||||
@ -31,17 +24,12 @@ class TestHelper {
|
||||
/**
|
||||
* @param {TestHelperInit} init
|
||||
*/
|
||||
constructor({ server, client, browser, t, killChrome }) {
|
||||
constructor({ server, browser, t, direct }) {
|
||||
/**
|
||||
* @type {fastify.FastifyInstance}
|
||||
*/
|
||||
this._server = server;
|
||||
|
||||
/**
|
||||
* @type {CRIConnection}
|
||||
*/
|
||||
this._client = client;
|
||||
|
||||
/**
|
||||
* @type {Browser}
|
||||
*/
|
||||
@ -50,13 +38,14 @@ class TestHelper {
|
||||
/** @type {*} */
|
||||
this._t = t;
|
||||
|
||||
this._killChrome = killChrome;
|
||||
|
||||
/** @type {Page} */
|
||||
this._testPage = null;
|
||||
|
||||
/** @type {Frame} */
|
||||
this._sandbox = null;
|
||||
|
||||
/** @type {boolean} */
|
||||
this._direct = direct;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -98,7 +87,10 @@ class TestHelper {
|
||||
}
|
||||
|
||||
async cleanup() {
|
||||
await this._testPage.goto(this._server.testPage, {
|
||||
const testPageURL = this._direct
|
||||
? this._server.testPageDirect
|
||||
: this._server.testPage;
|
||||
await this._testPage.goto(testPageURL, {
|
||||
waitUntil: 'networkidle2'
|
||||
});
|
||||
this._sandbox = this._testPage.frames()[1];
|
||||
@ -110,7 +102,8 @@ class TestHelper {
|
||||
}
|
||||
|
||||
async ensureSandbox() {
|
||||
if (!this._sandbox.url().endsWith('https://tests.wombat.io/')) {
|
||||
const url = `https://tests.${this._direct ? 'direct.' : ''}wombat.io/`;
|
||||
if (!this._sandbox.url().endsWith(url)) {
|
||||
await this.fullRefresh();
|
||||
} else {
|
||||
await this.maybeInitWombat();
|
||||
@ -146,9 +139,8 @@ module.exports = TestHelper;
|
||||
|
||||
/**
|
||||
* @typedef {Object} TestHelperInit
|
||||
* @property {Browser} browser
|
||||
* @property {CRIConnection} client
|
||||
* @property {fastify.FastifyInstance} server
|
||||
* @property {*} t
|
||||
* @property {function(): void} killChrome
|
||||
* @property {Browser} browser
|
||||
* @property {boolean} direct
|
||||
*/
|
||||
|
@ -1064,3 +1064,98 @@ exports.CSS = {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.NativeFnTest = {
|
||||
testFN() {
|
||||
const funkyFunction = function() {};
|
||||
funkyFunction.toString = function() {
|
||||
throw new Error('blah');
|
||||
};
|
||||
return {
|
||||
native: wombat.isNativeFunction(blur),
|
||||
notNative: wombat.isNativeFunction(() => {}),
|
||||
funkyFn: wombat.isNativeFunction(funkyFunction),
|
||||
null: wombat.isNativeFunction(null),
|
||||
undefined: wombat.isNativeFunction(undefined),
|
||||
obj: wombat.isNativeFunction({})
|
||||
};
|
||||
},
|
||||
expectedValue: {
|
||||
native: true,
|
||||
notNative: false,
|
||||
funkyFn: false,
|
||||
null: false,
|
||||
undefined: false,
|
||||
obj: false
|
||||
}
|
||||
};
|
||||
|
||||
exports.SaveSrcSetDataSrcSet = {
|
||||
values: [
|
||||
{ name: 'IMG', tagName: 'IMG', expected: true },
|
||||
{ name: 'VIDEO', tagName: 'VIDEO', expected: true },
|
||||
{ name: 'AUDIO', tagName: 'AUDIO', expected: true },
|
||||
{
|
||||
name: 'SOURCE with no parent',
|
||||
tagName: 'SOURCE',
|
||||
expected: false
|
||||
},
|
||||
{
|
||||
name: 'SOURCE with PICTURE parent',
|
||||
tagName: 'SOURCE',
|
||||
parentElement: 'PICTURE',
|
||||
expected: true
|
||||
},
|
||||
{
|
||||
name: 'SOURCE with VIDEO parent',
|
||||
tagName: 'SOURCE',
|
||||
parentElement: 'VIDEO',
|
||||
expected: true
|
||||
},
|
||||
{
|
||||
name: 'SOURCE with AUDIO parent',
|
||||
tagName: 'SOURCE',
|
||||
parentElement: 'AUDIO',
|
||||
expected: true
|
||||
},
|
||||
{
|
||||
name: 'IFRAME',
|
||||
tagName: 'IFRAME',
|
||||
expected: false
|
||||
},
|
||||
{
|
||||
name: 'SOURCE with DIV parent',
|
||||
tagName: 'SOURCE',
|
||||
parentElement: 'DIV',
|
||||
expected: false
|
||||
}
|
||||
],
|
||||
testFnSS(tagName, parentElementTagName) {
|
||||
const testValue = { tagName };
|
||||
if (parentElementTagName) {
|
||||
testValue.parentElement = {
|
||||
tagName: parentElementTagName
|
||||
};
|
||||
}
|
||||
return wombat.isSavedSrcSrcset(testValue);
|
||||
},
|
||||
testFnDSS(tagName, parentElementTagName) {
|
||||
const testValue = { tagName };
|
||||
if (parentElementTagName) {
|
||||
testValue.parentElement = {
|
||||
tagName: parentElementTagName
|
||||
};
|
||||
}
|
||||
return {
|
||||
without: wombat.isSavedDataSrcSrcset(testValue),
|
||||
with: wombat.isSavedSrcSrcset(
|
||||
Object.assign(
|
||||
{
|
||||
dataset: { srcset: true }
|
||||
},
|
||||
testValue
|
||||
)
|
||||
)
|
||||
};
|
||||
}
|
||||
};
|
||||
|
1928
wombat/yarn.lock
1928
wombat/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user