mirror of
https://github.com/webrecorder/pywb.git
synced 2025-03-24 06:59:52 +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
|
- stage: test
|
||||||
name: "Wombat Tests"
|
name: "Wombat Tests"
|
||||||
language: node_js
|
language: node_js
|
||||||
node_js: 12.0.0
|
node_js: 12.4.0
|
||||||
env:
|
env:
|
||||||
- WR_TEST=no WOMBAT_TEST=yes
|
- WR_TEST=no WOMBAT_TEST=yes
|
||||||
addons:
|
addons:
|
||||||
|
@ -19,6 +19,12 @@ class JSONPRewriter(StreamingRewriter):
|
|||||||
m_callback = self.CALLBACK.search(self.url_rewriter.wburl.url)
|
m_callback = self.CALLBACK.search(self.url_rewriter.wburl.url)
|
||||||
if not m_callback:
|
if not m_callback:
|
||||||
return string
|
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):]
|
string = m_callback.group(1) + string[m_json.end(1):]
|
||||||
return string
|
return string
|
||||||
|
@ -65,7 +65,7 @@ class JSWombatProxyRules(RxRules):
|
|||||||
local_init_func = '\nvar {0} = function(name) {{\
|
local_init_func = '\nvar {0} = function(name) {{\
|
||||||
return (self._wb_wombat && self._wb_wombat.local_init && \
|
return (self._wb_wombat && self._wb_wombat.local_init && \
|
||||||
self._wb_wombat.local_init(name)) || self[name]; }};\n\
|
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'
|
{{\n'
|
||||||
local_check_this_fn = 'var {0} = function (thisObj) {{ \
|
local_check_this_fn = 'var {0} = function (thisObj) {{ \
|
||||||
if (thisObj && thisObj._WB_wombat_obj_proxy) return thisObj._WB_wombat_obj_proxy; return 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)
|
prop_str = '|'.join(self.local_objs)
|
||||||
|
|
||||||
rules = [
|
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'(?<=\.)postMessage\b\(', self.add_prefix('__WB_pmw(self).'), 0),
|
||||||
(r'(?<![$.])\s*location\b\s*[=]\s*(?![=])', self.add_suffix(check_loc), 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),
|
(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';
|
import get from 'lodash-es/get';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {TestOverwatch}
|
* @typedef {Object} TestOverwatchInit
|
||||||
|
* @property {HTMLIFrameElement} sandbox
|
||||||
|
* @property {Object} [domStructure]
|
||||||
|
* @property {boolean} [direct]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
window.TestOverwatch = class TestOverwatch {
|
window.TestOverwatch = class TestOverwatch {
|
||||||
/**
|
/**
|
||||||
* @param {Object} domStructure
|
* @param {TestOverwatchInit} init
|
||||||
*/
|
*/
|
||||||
constructor(domStructure) {
|
constructor({ sandbox, domStructure, direct }) {
|
||||||
/**
|
/**
|
||||||
* @type {{document: Document, window: Window}}
|
* @type {{document: Document, window: Window}}
|
||||||
*/
|
*/
|
||||||
this.ownContextWinDoc = { window, document };
|
this.ownContextWinDoc = { window, document };
|
||||||
this.wbMessages = { load: false };
|
this.wbMessages = { load: false };
|
||||||
this.domStructure = domStructure;
|
this.domStructure = domStructure;
|
||||||
|
this.direct = direct;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {HTMLIFrameElement}
|
* @type {HTMLIFrameElement}
|
||||||
*/
|
*/
|
||||||
this.sandbox = domStructure.sandbox;
|
this.sandbox = sandbox;
|
||||||
window.addEventListener(
|
if (!this.direct) {
|
||||||
'message',
|
window.addEventListener(
|
||||||
event => {
|
'message',
|
||||||
if (event.data) {
|
event => {
|
||||||
const { data } = event;
|
if (event.data) {
|
||||||
switch (data.wb_type) {
|
const { data } = event;
|
||||||
case 'load':
|
switch (data.wb_type) {
|
||||||
this.wbMessages.load =
|
case 'load':
|
||||||
this.wbMessages.load || data.readyState === 'complete';
|
this.wbMessages.load =
|
||||||
this.domStructure.load.url.data = data.url;
|
this.wbMessages.load || data.readyState === 'complete';
|
||||||
this.domStructure.load.title.data = data.title;
|
this.domStructure.load.url.data = data.url;
|
||||||
this.domStructure.load.readyState.data = data.readyState;
|
this.domStructure.load.title.data = data.title;
|
||||||
break;
|
this.domStructure.load.readyState.data = data.readyState;
|
||||||
case 'replace-url':
|
break;
|
||||||
this.wbMessages['replace-url'] = data;
|
case 'replace-url':
|
||||||
this.domStructure.replaceURL.url.data = data.url;
|
this.wbMessages['replace-url'] = data;
|
||||||
this.domStructure.replaceURL.title.data = data.title;
|
this.domStructure.replaceURL.url.data = data.url;
|
||||||
break;
|
this.domStructure.replaceURL.title.data = data.title;
|
||||||
case 'title':
|
break;
|
||||||
this.wbMessages.title = data.title;
|
case 'title':
|
||||||
this.domStructure.titleMsg.data = data.title;
|
this.wbMessages.title = data.title;
|
||||||
break;
|
this.domStructure.titleMsg.data = data.title;
|
||||||
case 'hashchange':
|
break;
|
||||||
this.domStructure.hashchange.data = data.title;
|
case 'hashchange':
|
||||||
this.wbMessages.hashchange = data.hash;
|
this.domStructure.hashchange.data = data.title;
|
||||||
break;
|
this.wbMessages.hashchange = data.hash;
|
||||||
case 'cookie':
|
break;
|
||||||
this.domStructure.cookie.domain = data.domain;
|
case 'cookie':
|
||||||
this.domStructure.cookie.cookie = data.cookie;
|
this.domStructure.cookie.domain = data.domain;
|
||||||
this.wbMessages.cookie = data;
|
this.domStructure.cookie.cookie = data.cookie;
|
||||||
break;
|
this.wbMessages.cookie = data;
|
||||||
default:
|
break;
|
||||||
this.domStructure.unknown.data = JSON.stringify(data);
|
default:
|
||||||
break;
|
this.domStructure.unknown.data = JSON.stringify(data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
false
|
||||||
false
|
);
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -67,7 +74,9 @@ window.TestOverwatch = class TestOverwatch {
|
|||||||
* environment purity.
|
* environment purity.
|
||||||
*/
|
*/
|
||||||
initSandbox() {
|
initSandbox() {
|
||||||
this.domStructure.reset();
|
if (this.domStructure) {
|
||||||
|
this.domStructure.reset();
|
||||||
|
}
|
||||||
this.wbMessages = { load: false };
|
this.wbMessages = { load: false };
|
||||||
this.sandbox.contentWindow._WBWombatInit(this.sandbox.contentWindow.wbinfo);
|
this.sandbox.contentWindow._WBWombatInit(this.sandbox.contentWindow.wbinfo);
|
||||||
this.sandbox.contentWindow.WombatTestUtil = {
|
this.sandbox.contentWindow.WombatTestUtil = {
|
||||||
|
@ -4,25 +4,25 @@
|
|||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/fs-extra": "^5.0.5",
|
"@types/fs-extra": "^7.0.0",
|
||||||
"ava": "^1.4.1",
|
"ava": "^2.1.0",
|
||||||
"chokidar": "^2.1.5",
|
"chokidar": "^3.0.1",
|
||||||
"chrome-remote-interface-extra": "^1.0.0",
|
"chrome-remote-interface-extra": "^1.1.1",
|
||||||
"eslint": "^5.16.0",
|
"eslint": "^5.16.0",
|
||||||
"eslint-config-prettier": "^4.2.0",
|
"eslint-config-prettier": "^5.0.0",
|
||||||
"eslint-plugin-prettier": "^3.0.1",
|
"eslint-plugin-prettier": "^3.1.0",
|
||||||
"fastify": "^2.3.0",
|
"fastify": "^2.5.0",
|
||||||
"fastify-favicon": "^2.0.0",
|
"fastify-favicon": "^2.0.0",
|
||||||
"fastify-graceful-shutdown": "^2.0.1",
|
"fastify-graceful-shutdown": "^2.0.1",
|
||||||
"fastify-static": "^2.4.0",
|
"fastify-static": "^2.5.0",
|
||||||
"fs-extra": "^7.0.1",
|
"fs-extra": "^8.0.1",
|
||||||
"lodash-es": "^4.17.11",
|
"lodash-es": "^4.17.11",
|
||||||
"prettier": "^1.17.0",
|
"prettier": "^1.18.2",
|
||||||
"rollup": "^1.10.1",
|
"rollup": "^1.15.6",
|
||||||
"rollup-plugin-babel-minify": "^8.0.0",
|
"rollup-plugin-babel-minify": "^8.0.0",
|
||||||
"rollup-plugin-cleanup": "^3.1.1",
|
"rollup-plugin-cleanup": "^3.1.1",
|
||||||
"rollup-plugin-commonjs": "^9.3.4",
|
"rollup-plugin-commonjs": "^10.0.0",
|
||||||
"rollup-plugin-node-resolve": "^4.2.3",
|
"rollup-plugin-node-resolve": "^5.0.3",
|
||||||
"rollup-plugin-uglify": "^6.0.2",
|
"rollup-plugin-uglify": "^6.0.2",
|
||||||
"rollup-plugin-uglify-es": "^0.0.1",
|
"rollup-plugin-uglify-es": "^0.0.1",
|
||||||
"tls-keygen": "^3.7.0"
|
"tls-keygen": "^3.7.0"
|
||||||
@ -32,8 +32,8 @@
|
|||||||
"build-dev": "ALL=1 rollup -c rollup.config.dev.js",
|
"build-dev": "ALL=1 rollup -c rollup.config.dev.js",
|
||||||
"build-dev-watch": "rollup --watch -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-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-test": "rollup -c rollup.config.test.js",
|
||||||
"build-full-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",
|
"build-test-bundle": "rollup -c ./internal/rollup.testPageBundle.config.js",
|
||||||
"test": "ava --verbose"
|
"test": "ava --verbose"
|
||||||
},
|
},
|
||||||
@ -50,16 +50,17 @@
|
|||||||
"!test/assests/*",
|
"!test/assests/*",
|
||||||
"!test/helpers/extractOrigFunky.js"
|
"!test/helpers/extractOrigFunky.js"
|
||||||
],
|
],
|
||||||
|
"helpers": [
|
||||||
|
"test/helpers/**.js"
|
||||||
|
],
|
||||||
"sources": [
|
"sources": [
|
||||||
"!src/**/*"
|
"src/**/*"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"*/**/graceful-fs": "~4.1.15",
|
"*/**/graceful-fs": "~4.1.15"
|
||||||
"*/**/jsonfile": "~5.0.0",
|
|
||||||
"*/**/universalify": "~0.1.2"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chrome-launcher": "^0.10.5"
|
"just-launch-chrome": "^1.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,5 +38,26 @@ export default [
|
|||||||
exports: 'none'
|
exports: 'none'
|
||||||
},
|
},
|
||||||
plugins: [noStrict]
|
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.
|
* 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
|
* @see https://html.spec.whatwg.org/multipage/webstorage.html#the-storage-interface
|
||||||
*/
|
*/
|
||||||
export default function Storage(wombat, proxying) {
|
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
|
// hide our values from enumeration, spreed, et al
|
||||||
Object.defineProperties(this, {
|
Object.defineProperties(this, {
|
||||||
data: {
|
data: {
|
||||||
@ -111,9 +121,14 @@ Storage.prototype.fireEvent = function fireEvent(key, oldValue, newValue) {
|
|||||||
oldValue: oldValue,
|
oldValue: oldValue,
|
||||||
url: this.wombat.$wbwindow.WB_wombat_location.href
|
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;
|
sevent._storageArea = this;
|
||||||
|
|
||||||
this.wombat.storage_listeners.map(sevent);
|
this.wombat.storage_listeners.map(sevent);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3,8 +3,12 @@ import FuncMap from './funcMap';
|
|||||||
import Storage from './customStorage';
|
import Storage from './customStorage';
|
||||||
import WombatLocation from './wombatLocation';
|
import WombatLocation from './wombatLocation';
|
||||||
import AutoFetchWorker from './autoFetchWorker';
|
import AutoFetchWorker from './autoFetchWorker';
|
||||||
import { wrapSameOriginEventListener, wrapEventListener } from './listeners';
|
import { wrapEventListener, wrapSameOriginEventListener } from './listeners';
|
||||||
import { addToStringTagToClass, autobind } from './wombatUtils';
|
import {
|
||||||
|
addToStringTagToClass,
|
||||||
|
autobind,
|
||||||
|
ThrowExceptions
|
||||||
|
} from './wombatUtils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Window} $wbwindow
|
* @param {Window} $wbwindow
|
||||||
@ -276,6 +280,11 @@ function Wombat($wbwindow, wbinfo) {
|
|||||||
},
|
},
|
||||||
addEventListener: eTargetProto.addEventListener,
|
addEventListener: eTargetProto.addEventListener,
|
||||||
removeEventListener: eTargetProto.removeEventListener,
|
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,
|
wbSheetMediaQChecker: null,
|
||||||
XHRopen: null
|
XHRopen: null
|
||||||
};
|
};
|
||||||
@ -410,10 +419,19 @@ Wombat.prototype.getPageUnderModifier = function() {
|
|||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
*/
|
*/
|
||||||
Wombat.prototype.isNativeFunction = function(funToTest) {
|
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;
|
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
|
* Returns T/F indicating if the supplied element may have attributes that
|
||||||
* are auto-fetched
|
* are auto-fetched
|
||||||
@ -491,7 +509,13 @@ Wombat.prototype.isArgumentsObj = function(maybeArgumentsObj) {
|
|||||||
) {
|
) {
|
||||||
return false;
|
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 (
|
if (
|
||||||
!maybeArgumentsObj ||
|
!maybeArgumentsObj ||
|
||||||
maybeArgumentsObj instanceof NodeList ||
|
maybeArgumentsObj instanceof NodeList ||
|
||||||
maybeArgumentsObj.length == 0
|
!maybeArgumentsObj.length
|
||||||
) {
|
) {
|
||||||
return maybeArgumentsObj;
|
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.
|
* 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
|
* Note this function should be used when the Node(s) being considered can
|
||||||
@ -699,7 +770,10 @@ Wombat.prototype.wrapScriptTextJsProxy = function(scriptText) {
|
|||||||
return (
|
return (
|
||||||
'var _____WB$wombat$assign$function_____ = function(name) {return (self._wb_wombat && ' +
|
'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' +
|
'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 window = _____WB$wombat$assign$function_____("window");\n' +
|
||||||
'let self = _____WB$wombat$assign$function_____("self");\n' +
|
'let self = _____WB$wombat$assign$function_____("self");\n' +
|
||||||
'let document = _____WB$wombat$assign$function_____("document");\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 parent = _____WB$wombat$assign$function_____("parent");\n' +
|
||||||
'let frames = _____WB$wombat$assign$function_____("frames");\n' +
|
'let frames = _____WB$wombat$assign$function_____("frames");\n' +
|
||||||
'let opener = _____WB$wombat$assign$function_____("opener");\n' +
|
'let opener = _____WB$wombat$assign$function_____("opener");\n' +
|
||||||
scriptText +
|
scriptText.replace(this.DotPostMessageRe, '.__WB_pmw(self.window)$1') +
|
||||||
'\n\n}'
|
'\n\n}'
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -1194,16 +1268,20 @@ Wombat.prototype.objToProxy = function(obj) {
|
|||||||
* @param {*} obj
|
* @param {*} obj
|
||||||
* @param {*} prop
|
* @param {*} prop
|
||||||
* @param {Array<string>} ownProps
|
* @param {Array<string>} ownProps
|
||||||
|
* @param {Object} fnCache
|
||||||
* @return {*}
|
* @return {*}
|
||||||
*/
|
*/
|
||||||
Wombat.prototype.defaultProxyGet = function(obj, prop, ownProps) {
|
Wombat.prototype.defaultProxyGet = function(obj, prop, ownProps, fnCache) {
|
||||||
switch (prop) {
|
switch (prop) {
|
||||||
case '__WBProxyRealObj__':
|
case '__WBProxyRealObj__':
|
||||||
return obj;
|
return obj;
|
||||||
case 'location':
|
case 'location':
|
||||||
|
case 'WB_wombat_location':
|
||||||
return obj.WB_wombat_location;
|
return obj.WB_wombat_location;
|
||||||
case '_WB_wombat_obj_proxy':
|
case '_WB_wombat_obj_proxy':
|
||||||
return obj._WB_wombat_obj_proxy;
|
return obj._WB_wombat_obj_proxy;
|
||||||
|
case '__WB_pmw':
|
||||||
|
return obj[prop];
|
||||||
case 'constructor':
|
case 'constructor':
|
||||||
// allow tests such as self.constructor === Window to work
|
// allow tests such as self.constructor === Window to work
|
||||||
// you can't create a new instance of window using its constructor
|
// you can't create a new instance of window using its constructor
|
||||||
@ -1228,7 +1306,17 @@ Wombat.prototype.defaultProxyGet = function(obj, prop, ownProps) {
|
|||||||
break;
|
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) {
|
} else if (type === 'object' && retVal && retVal._WB_wombat_obj_proxy) {
|
||||||
if (retVal instanceof Window) {
|
if (retVal instanceof Window) {
|
||||||
this.initNewWindowWombat(retVal);
|
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.
|
* injected functions are present in the supplied window.
|
||||||
*
|
*
|
||||||
* They could be absent due to how certain pages use iframes
|
* They could be absent due to how certain pages use iframes
|
||||||
* @param {Window} win
|
* @param {?Window} win
|
||||||
*/
|
*/
|
||||||
Wombat.prototype.ensureServerSideInjectsExistOnWindow = function(win) {
|
Wombat.prototype.ensureServerSideInjectsExistOnWindow = function(win) {
|
||||||
|
if (!win) return;
|
||||||
if (typeof win._____WB$wombat$check$this$function_____ !== 'function') {
|
if (typeof win._____WB$wombat$check$this$function_____ !== 'function') {
|
||||||
win._____WB$wombat$check$this$function_____ = function(thisObj) {
|
win._____WB$wombat$check$this$function_____ = function(thisObj) {
|
||||||
if (thisObj && thisObj._WB_wombat_obj_proxy)
|
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
|
* Rewrites the arguments supplied to an function of the Node interface
|
||||||
* @param {Object} fnThis
|
* @param {Object} fnThis
|
||||||
@ -1431,8 +1565,9 @@ Wombat.prototype.rewriteNodeFuncArgs = function(
|
|||||||
this.rewriteElemComplete(newNode);
|
this.rewriteElemComplete(newNode);
|
||||||
break;
|
break;
|
||||||
case Node.TEXT_NODE:
|
case Node.TEXT_NODE:
|
||||||
|
// newNode is the new child of fnThis (the parent node)
|
||||||
if (
|
if (
|
||||||
newNode.tagName === 'STYLE' ||
|
fnThis.tagName === 'STYLE' ||
|
||||||
(newNode.parentNode && newNode.parentNode.tagName === 'STYLE')
|
(newNode.parentNode && newNode.parentNode.tagName === 'STYLE')
|
||||||
) {
|
) {
|
||||||
newNode.textContent = this.rewriteStyle(newNode.textContent);
|
newNode.textContent = this.rewriteStyle(newNode.textContent);
|
||||||
@ -1912,51 +2047,10 @@ Wombat.prototype.rewriteScript = function(elem) {
|
|||||||
if (elem.hasAttribute('src') || !elem.textContent || !this.$wbwindow.Proxy) {
|
if (elem.hasAttribute('src') || !elem.textContent || !this.$wbwindow.Proxy) {
|
||||||
return this.rewriteAttr(elem, 'src');
|
return this.rewriteAttr(elem, 'src');
|
||||||
}
|
}
|
||||||
var type = elem.type;
|
if (this.skipWrapScriptBasedOnType(elem.type)) return false;
|
||||||
|
|
||||||
if (
|
|
||||||
type &&
|
|
||||||
(type === 'application/json' || type.indexOf('text/template') !== -1)
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var text = elem.textContent.trim();
|
var text = elem.textContent.trim();
|
||||||
|
if (this.skipWrapScriptTextBasedOnText(text)) return false;
|
||||||
if (
|
elem.textContent = this.wrapScriptTextJsProxy(text);
|
||||||
!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;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2230,7 +2324,6 @@ Wombat.prototype.rewriteHtmlFull = function(string, checkEndTag) {
|
|||||||
|
|
||||||
if (changed) {
|
if (changed) {
|
||||||
var new_html;
|
var new_html;
|
||||||
|
|
||||||
// if original had <html> tag, add full document HTML
|
// if original had <html> tag, add full document HTML
|
||||||
if (string && string.indexOf('<html') >= 0) {
|
if (string && string.indexOf('<html') >= 0) {
|
||||||
inner_doc.documentElement._no_rewrite = true;
|
inner_doc.documentElement._no_rewrite = true;
|
||||||
@ -2529,9 +2622,7 @@ Wombat.prototype.rewriteSetTimeoutInterval = function(
|
|||||||
argsObj
|
argsObj
|
||||||
) {
|
) {
|
||||||
// strings are primitives with a prototype or __proto__ of String depending on the browser
|
// strings are primitives with a prototype or __proto__ of String depending on the browser
|
||||||
var rw =
|
var rw = this.isString(argsObj[0]);
|
||||||
argsObj[0] != null &&
|
|
||||||
Object.getPrototypeOf(argsObj[0]) === String.prototype;
|
|
||||||
// do not mess with the arguments object unless you want instant de-optimization
|
// do not mess with the arguments object unless you want instant de-optimization
|
||||||
var args = rw ? new Array(argsObj.length) : argsObj;
|
var args = rw ? new Array(argsObj.length) : argsObj;
|
||||||
if (rw) {
|
if (rw) {
|
||||||
@ -2553,6 +2644,66 @@ Wombat.prototype.rewriteSetTimeoutInterval = function(
|
|||||||
return originalFn.apply(thisObj, args);
|
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
|
* Applies an Event property getter override for the supplied property
|
||||||
* @param {string} attr
|
* @param {string} attr
|
||||||
@ -2866,34 +3017,18 @@ Wombat.prototype.overrideHtmlAssign = function(elem, prop, rewriteGetter) {
|
|||||||
|
|
||||||
if (!orig_setter) return;
|
if (!orig_setter) return;
|
||||||
|
|
||||||
var wombat = this;
|
var rewriteFn = this.rewriteHTMLAssign;
|
||||||
|
|
||||||
var setter = function overrideHTMLAssignSetter(orig) {
|
var setter = function overrideHTMLAssignSetter(orig) {
|
||||||
var res = orig;
|
return rewriteFn(this, orig_setter, 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 wb_unrewrite_rx = this.wb_unrewrite_rx;
|
||||||
|
|
||||||
var getter = function overrideHTMLAssignGetter() {
|
var getter = function overrideHTMLAssignGetter() {
|
||||||
var res = orig_getter.call(this);
|
var res = orig_getter.call(this);
|
||||||
if (!this._no_rewrite) {
|
if (!this._no_rewrite) {
|
||||||
return res.replace(wombat.wb_unrewrite_rx, '');
|
return res.replace(wb_unrewrite_rx, '');
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
@ -3164,7 +3299,7 @@ Wombat.prototype.overrideTextProtoGetSet = function(textProto, whichProp) {
|
|||||||
* Overrides the constructor of an UIEvent object in order to ensure
|
* Overrides the constructor of an UIEvent object in order to ensure
|
||||||
* that the `view`, `relatedTarget`, and `target` arguments of the
|
* that the `view`, `relatedTarget`, and `target` arguments of the
|
||||||
* constructor are not a JS Proxy used by wombat.
|
* constructor are not a JS Proxy used by wombat.
|
||||||
* @param {Object} which
|
* @param {string} which
|
||||||
*/
|
*/
|
||||||
Wombat.prototype.overrideAnUIEvent = function(which) {
|
Wombat.prototype.overrideAnUIEvent = function(which) {
|
||||||
var didOverrideKey = '__wb_' + which + '_overridden';
|
var didOverrideKey = '__wb_' + which + '_overridden';
|
||||||
@ -3205,6 +3340,7 @@ Wombat.prototype.overrideAnUIEvent = function(which) {
|
|||||||
}
|
}
|
||||||
this.$wbwindow[which] = (function(EventConstructor) {
|
this.$wbwindow[which] = (function(EventConstructor) {
|
||||||
return function NewEventConstructor(type, init) {
|
return function NewEventConstructor(type, init) {
|
||||||
|
wombat.domConstructorErrorChecker(this, which, arguments);
|
||||||
if (init) {
|
if (init) {
|
||||||
if (init.view != null) {
|
if (init.view != null) {
|
||||||
init.view = wombat.proxyToObj(init.view);
|
init.view = wombat.proxyToObj(init.view);
|
||||||
@ -3234,7 +3370,9 @@ Wombat.prototype.overrideAnUIEvent = function(which) {
|
|||||||
* @return {*}
|
* @return {*}
|
||||||
*/
|
*/
|
||||||
Wombat.prototype.rewriteParentNodeFn = function(fnThis, originalFn, argsObj) {
|
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);
|
var thisObj = this.proxyToObj(fnThis);
|
||||||
if (originalFn.__WB_orig_apply) {
|
if (originalFn.__WB_orig_apply) {
|
||||||
return originalFn.__WB_orig_apply(thisObj, argArr);
|
return originalFn.__WB_orig_apply(thisObj, argArr);
|
||||||
@ -3485,6 +3623,7 @@ Wombat.prototype.initCSSOMOverrides = function() {
|
|||||||
var oCSSKV = this.$wbwindow.CSSKeywordValue;
|
var oCSSKV = this.$wbwindow.CSSKeywordValue;
|
||||||
this.$wbwindow.CSSKeywordValue = (function(CSSKeywordValue_) {
|
this.$wbwindow.CSSKeywordValue = (function(CSSKeywordValue_) {
|
||||||
return function CSSKeywordValue(cssValue) {
|
return function CSSKeywordValue(cssValue) {
|
||||||
|
wombat.domConstructorErrorChecker(this, 'CSSKeywordValue', arguments);
|
||||||
return new CSSKeywordValue_(wombat.rewriteStyle(cssValue));
|
return new CSSKeywordValue_(wombat.rewriteStyle(cssValue));
|
||||||
};
|
};
|
||||||
})(this.$wbwindow.CSSKeywordValue);
|
})(this.$wbwindow.CSSKeywordValue);
|
||||||
@ -3521,6 +3660,7 @@ Wombat.prototype.initCSSOMOverrides = function() {
|
|||||||
}
|
}
|
||||||
return originalSet.apply(this, newArgs);
|
return originalSet.apply(this, newArgs);
|
||||||
};
|
};
|
||||||
|
|
||||||
var originalAppend = this.$wbwindow.StylePropertyMap.prototype.append;
|
var originalAppend = this.$wbwindow.StylePropertyMap.prototype.append;
|
||||||
this.$wbwindow.StylePropertyMap.prototype.append = function append() {
|
this.$wbwindow.StylePropertyMap.prototype.append = function append() {
|
||||||
if (arguments.length <= 1) {
|
if (arguments.length <= 1) {
|
||||||
@ -3552,6 +3692,7 @@ Wombat.prototype.initAudioOverride = function() {
|
|||||||
var wombat = this;
|
var wombat = this;
|
||||||
this.$wbwindow.Audio = (function(Audio_) {
|
this.$wbwindow.Audio = (function(Audio_) {
|
||||||
return function Audio(url) {
|
return function Audio(url) {
|
||||||
|
wombat.domConstructorErrorChecker(this, 'Audio');
|
||||||
return new Audio_(wombat.rewriteUrl(url, true, 'oe_'));
|
return new Audio_(wombat.rewriteUrl(url, true, 'oe_'));
|
||||||
};
|
};
|
||||||
})(this.$wbwindow.Audio);
|
})(this.$wbwindow.Audio);
|
||||||
@ -3688,6 +3829,7 @@ Wombat.prototype.initFontFaceOverride = function() {
|
|||||||
var origFontFace = this.$wbwindow.FontFace;
|
var origFontFace = this.$wbwindow.FontFace;
|
||||||
this.$wbwindow.FontFace = (function(FontFace_) {
|
this.$wbwindow.FontFace = (function(FontFace_) {
|
||||||
return function FontFace(family, source, descriptors) {
|
return function FontFace(family, source, descriptors) {
|
||||||
|
wombat.domConstructorErrorChecker(this, 'FontFace', arguments, 2);
|
||||||
var rwSource = source;
|
var rwSource = source;
|
||||||
if (source != null) {
|
if (source != null) {
|
||||||
if (typeof source !== 'string') {
|
if (typeof source !== 'string') {
|
||||||
@ -3866,6 +4008,7 @@ Wombat.prototype.initHTTPOverrides = function() {
|
|||||||
var orig_request = this.$wbwindow.Request;
|
var orig_request = this.$wbwindow.Request;
|
||||||
this.$wbwindow.Request = (function(Request_) {
|
this.$wbwindow.Request = (function(Request_) {
|
||||||
return function Request(input, init_opts) {
|
return function Request(input, init_opts) {
|
||||||
|
wombat.domConstructorErrorChecker(this, 'Request', arguments);
|
||||||
var newInitOpts = init_opts || {};
|
var newInitOpts = init_opts || {};
|
||||||
var newInput = input;
|
var newInput = input;
|
||||||
var inputType = typeof input;
|
var inputType = typeof input;
|
||||||
@ -3894,6 +4037,9 @@ Wombat.prototype.initHTTPOverrides = function() {
|
|||||||
})(this.$wbwindow.Request);
|
})(this.$wbwindow.Request);
|
||||||
|
|
||||||
this.$wbwindow.Request.prototype = orig_request.prototype;
|
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) {
|
if (this.$wbwindow.Response && this.$wbwindow.Response.prototype) {
|
||||||
@ -3912,6 +4058,7 @@ Wombat.prototype.initHTTPOverrides = function() {
|
|||||||
var origEventSource = this.$wbwindow.EventSource;
|
var origEventSource = this.$wbwindow.EventSource;
|
||||||
this.$wbwindow.EventSource = (function(EventSource_) {
|
this.$wbwindow.EventSource = (function(EventSource_) {
|
||||||
return function EventSource(url, configuration) {
|
return function EventSource(url, configuration) {
|
||||||
|
wombat.domConstructorErrorChecker(this, 'EventSource', arguments);
|
||||||
var rwURL = url;
|
var rwURL = url;
|
||||||
if (url != null) {
|
if (url != null) {
|
||||||
rwURL = wombat.rewriteUrl(url);
|
rwURL = wombat.rewriteUrl(url);
|
||||||
@ -3930,6 +4077,7 @@ Wombat.prototype.initHTTPOverrides = function() {
|
|||||||
var origWebSocket = this.$wbwindow.WebSocket;
|
var origWebSocket = this.$wbwindow.WebSocket;
|
||||||
this.$wbwindow.WebSocket = (function(WebSocket_) {
|
this.$wbwindow.WebSocket = (function(WebSocket_) {
|
||||||
return function WebSocket(url, configuration) {
|
return function WebSocket(url, configuration) {
|
||||||
|
wombat.domConstructorErrorChecker(this, 'WebSocket', arguments);
|
||||||
var rwURL = url;
|
var rwURL = url;
|
||||||
if (url != null) {
|
if (url != null) {
|
||||||
rwURL = wombat.rewriteWSURL(url);
|
rwURL = wombat.rewriteWSURL(url);
|
||||||
@ -4424,12 +4572,16 @@ Wombat.prototype.initWorkerOverrides = function() {
|
|||||||
// Worker unrewrite postMessage
|
// Worker unrewrite postMessage
|
||||||
var orig_worker = this.$wbwindow.Worker;
|
var orig_worker = this.$wbwindow.Worker;
|
||||||
this.$wbwindow.Worker = (function(Worker_) {
|
this.$wbwindow.Worker = (function(Worker_) {
|
||||||
return function Worker(url) {
|
return function Worker(url, options) {
|
||||||
return new Worker_(wombat.rewriteWorker(url));
|
wombat.domConstructorErrorChecker(this, 'Worker', arguments);
|
||||||
|
return new Worker_(wombat.rewriteWorker(url), options);
|
||||||
};
|
};
|
||||||
})(orig_worker);
|
})(orig_worker);
|
||||||
|
|
||||||
this.$wbwindow.Worker.prototype = orig_worker.prototype;
|
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;
|
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
|
// per https://html.spec.whatwg.org/multipage/workers.html#sharedworker
|
||||||
var oSharedWorker = this.$wbwindow.SharedWorker;
|
var oSharedWorker = this.$wbwindow.SharedWorker;
|
||||||
this.$wbwindow.SharedWorker = (function(SharedWorker_) {
|
this.$wbwindow.SharedWorker = (function(SharedWorker_) {
|
||||||
return function SharedWorker(url) {
|
return function SharedWorker(url, options) {
|
||||||
return new SharedWorker_(wombat.rewriteWorker(url));
|
wombat.domConstructorErrorChecker(this, 'SharedWorker', arguments);
|
||||||
|
return new SharedWorker_(wombat.rewriteWorker(url), options);
|
||||||
};
|
};
|
||||||
})(oSharedWorker);
|
})(oSharedWorker);
|
||||||
|
|
||||||
this.$wbwindow.SharedWorker.prototype = oSharedWorker.prototype;
|
this.$wbwindow.SharedWorker.prototype = oSharedWorker.prototype;
|
||||||
|
Object.defineProperty(
|
||||||
|
this.$wbwindow.SharedWorker.prototype,
|
||||||
|
'constructor',
|
||||||
|
{
|
||||||
|
value: this.$wbwindow.SharedWorker
|
||||||
|
}
|
||||||
|
);
|
||||||
this.$wbwindow.SharedWorker.__wb_sharedWorker_overriden = true;
|
this.$wbwindow.SharedWorker.__wb_sharedWorker_overriden = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4625,7 +4785,7 @@ Wombat.prototype.initHashChange = function() {
|
|||||||
if (!message.wb_type) return;
|
if (!message.wb_type) return;
|
||||||
|
|
||||||
if (message.wb_type === 'outer_hashchange') {
|
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;
|
wombat.$wbwindow.location.hash = message.hash;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -5008,6 +5168,11 @@ Wombat.prototype.initPresentationRequestOverride = function() {
|
|||||||
var origPresentationRequest = this.$wbwindow.PresentationRequest;
|
var origPresentationRequest = this.$wbwindow.PresentationRequest;
|
||||||
this.$wbwindow.PresentationRequest = (function(PresentationRequest_) {
|
this.$wbwindow.PresentationRequest = (function(PresentationRequest_) {
|
||||||
return function PresentationRequest(url) {
|
return function PresentationRequest(url) {
|
||||||
|
wombat.domConstructorErrorChecker(
|
||||||
|
this,
|
||||||
|
'PresentationRequest',
|
||||||
|
arguments
|
||||||
|
);
|
||||||
var rwURL = url;
|
var rwURL = url;
|
||||||
if (url != null) {
|
if (url != null) {
|
||||||
if (Array.isArray(rwURL)) {
|
if (Array.isArray(rwURL)) {
|
||||||
@ -5095,7 +5260,7 @@ Wombat.prototype.initStorageOverride = function() {
|
|||||||
var session;
|
var session;
|
||||||
var pLocal = 'localStorage';
|
var pLocal = 'localStorage';
|
||||||
var pSession = 'sessionStorage';
|
var pSession = 'sessionStorage';
|
||||||
|
ThrowExceptions.yes = false;
|
||||||
if (this.$wbwindow.Proxy) {
|
if (this.$wbwindow.Proxy) {
|
||||||
var storageProxyHandler = function() {
|
var storageProxyHandler = function() {
|
||||||
return {
|
return {
|
||||||
@ -5133,6 +5298,9 @@ Wombat.prototype.initStorageOverride = function() {
|
|||||||
this.defGetterProp(this.$wbwindow, pSession, function sessionStorage() {
|
this.defGetterProp(this.$wbwindow, pSession, function sessionStorage() {
|
||||||
return session;
|
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;
|
if (!$wbwindow.Proxy) return undefined;
|
||||||
|
|
||||||
var ownProps = this.getAllOwnProps($wbwindow);
|
var ownProps = this.getAllOwnProps($wbwindow);
|
||||||
|
var funCache = {};
|
||||||
var wombat = this;
|
var wombat = this;
|
||||||
var windowProxy = new $wbwindow.Proxy(
|
var windowProxy = new $wbwindow.Proxy(
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
get: function(target, prop) {
|
get: function(target, prop) {
|
||||||
if (prop === 'top') {
|
switch (prop) {
|
||||||
return wombat.$wbwindow.WB_wombat_top._WB_wombat_obj_proxy;
|
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) {
|
set: function(target, prop, value) {
|
||||||
switch (prop) {
|
switch (prop) {
|
||||||
@ -5212,6 +5388,7 @@ Wombat.prototype.initWindowObjProxy = function($wbwindow) {
|
|||||||
if (propDescriptor.configurable === false) {
|
if (propDescriptor.configurable === false) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
delete target[prop];
|
||||||
delete $wbwindow[prop];
|
delete $wbwindow[prop];
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
@ -5239,11 +5416,12 @@ Wombat.prototype.initWindowObjProxy = function($wbwindow) {
|
|||||||
Wombat.prototype.initDocumentObjProxy = function($document) {
|
Wombat.prototype.initDocumentObjProxy = function($document) {
|
||||||
this.initDocOverrides($document);
|
this.initDocOverrides($document);
|
||||||
if (!this.$wbwindow.Proxy) return undefined;
|
if (!this.$wbwindow.Proxy) return undefined;
|
||||||
|
var funCache = {};
|
||||||
var ownProps = this.getAllOwnProps($document);
|
var ownProps = this.getAllOwnProps($document);
|
||||||
var wombat = this;
|
var wombat = this;
|
||||||
var documentProxy = new this.$wbwindow.Proxy($document, {
|
var documentProxy = new this.$wbwindow.Proxy($document, {
|
||||||
get: function(target, prop) {
|
get: function(target, prop) {
|
||||||
return wombat.defaultProxyGet($document, prop, ownProps);
|
return wombat.defaultProxyGet($document, prop, ownProps, funCache);
|
||||||
},
|
},
|
||||||
set: function(target, prop, value) {
|
set: function(target, prop, value) {
|
||||||
if (prop === 'location') {
|
if (prop === 'location') {
|
||||||
@ -5482,6 +5660,42 @@ Wombat.prototype.initWombatTop = function($wbwindow) {
|
|||||||
this.defProp($wbwindow.Object.prototype, 'WB_wombat_top', setter, getter);
|
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
|
* Initialize wombat's internal state and apply all overrides
|
||||||
* @return {Object}
|
* @return {Object}
|
||||||
@ -5512,7 +5726,7 @@ Wombat.prototype.wombatInit = function() {
|
|||||||
this.initDocWriteOpenCloseOverride();
|
this.initDocWriteOpenCloseOverride();
|
||||||
|
|
||||||
// eval
|
// eval
|
||||||
// initEvalOverride();
|
this.initEvalOverride();
|
||||||
|
|
||||||
// Ajax, Fetch, Request, Response, EventSource, WebSocket
|
// Ajax, Fetch, Request, Response, EventSource, WebSocket
|
||||||
this.initHTTPOverrides();
|
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 ||
|
||||||
url.indexOf('blob:') === 0 ||
|
url.indexOf('blob:') === 0 ||
|
||||||
url.indexOf('javascript:') === 0 ||
|
url.indexOf('javascript:') === 0 ||
|
||||||
|
url.indexOf('data:') === 0 ||
|
||||||
url.indexOf(this.info.prefix) === 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>
|
<script>
|
||||||
const defaultText = ' not sent';
|
const defaultText = ' not sent';
|
||||||
const domStructure = {
|
const domStructure = {
|
||||||
sandbox: document.getElementById('wombatSandbox'),
|
|
||||||
load: {
|
load: {
|
||||||
url: document.createTextNode(defaultText),
|
url: document.createTextNode(defaultText),
|
||||||
title: document.createTextNode(defaultText),
|
title: document.createTextNode(defaultText),
|
||||||
@ -187,7 +186,11 @@
|
|||||||
document.getElementById('title-title').appendChild(domStructure.titleMsg);
|
document.getElementById('title-title').appendChild(domStructure.titleMsg);
|
||||||
document.getElementById('hash-hash').appendChild(domStructure.hashchange);
|
document.getElementById('hash-hash').appendChild(domStructure.hashchange);
|
||||||
document.getElementById('unknown-msg').appendChild(domStructure.unknown);
|
document.getElementById('unknown-msg').appendChild(domStructure.unknown);
|
||||||
window.overwatch = new TestOverwatch(domStructure);
|
window.overwatch = new TestOverwatch({
|
||||||
|
domStructure,
|
||||||
|
sandbox: document.getElementById('wombatSandbox'),
|
||||||
|
direct: false
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</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 { launch } = require('just-launch-chrome');
|
||||||
const path = require('path');
|
const Browser = require('chrome-remote-interface-extra/lib/browser/Browser');
|
||||||
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 CHROME_PROFILE_PATH = path.join(os.tmpdir(), 'temp_chrome_profile-');
|
|
||||||
const winPos = !process.env.NO_MOVE_WINDOW ? '--window-position=2000,0' : '';
|
const winPos = !process.env.NO_MOVE_WINDOW ? '--window-position=2000,0' : '';
|
||||||
|
|
||||||
const chromeArgs = userDataDir => [
|
const chromeArgs = [
|
||||||
'--enable-automation',
|
|
||||||
'--force-color-profile=srgb',
|
'--force-color-profile=srgb',
|
||||||
'--remote-debugging-port=9222',
|
|
||||||
'--disable-background-networking',
|
'--disable-background-networking',
|
||||||
'--disable-background-timer-throttling',
|
'--disable-background-timer-throttling',
|
||||||
'--disable-renderer-backgrounding',
|
'--disable-renderer-backgrounding',
|
||||||
'--disable-backgrounding-occluded-windows',
|
'--disable-backgrounding-occluded-windows',
|
||||||
'--disable-ipc-flooding-protection',
|
'--disable-ipc-flooding-protection',
|
||||||
'--enable-features=NetworkService,NetworkServiceInProcess',
|
'--enable-features=NetworkService,NetworkServiceInProcess,AwaitOptimization',
|
||||||
'--disable-client-side-phishing-detection',
|
'--disable-client-side-phishing-detection',
|
||||||
'--disable-default-apps',
|
'--disable-default-apps',
|
||||||
'--disable-extensions',
|
'--disable-extensions',
|
||||||
@ -28,7 +20,7 @@ const chromeArgs = userDataDir => [
|
|||||||
'--disable-sync',
|
'--disable-sync',
|
||||||
'--disable-domain-reliability',
|
'--disable-domain-reliability',
|
||||||
'--disable-infobars',
|
'--disable-infobars',
|
||||||
'--disable-features=site-per-process,TranslateUI',
|
'--disable-features=site-per-process,TranslateUI,BlinkGenPropertyTrees,LazyFrameLoading',
|
||||||
'--disable-breakpad',
|
'--disable-breakpad',
|
||||||
'--disable-backing-store-limit',
|
'--disable-backing-store-limit',
|
||||||
'--metrics-recording-only',
|
'--metrics-recording-only',
|
||||||
@ -38,131 +30,26 @@ const chromeArgs = userDataDir => [
|
|||||||
'--use-mock-keychain',
|
'--use-mock-keychain',
|
||||||
'--mute-audio',
|
'--mute-audio',
|
||||||
'--autoplay-policy=no-user-gesture-required',
|
'--autoplay-policy=no-user-gesture-required',
|
||||||
`--user-data-dir=${userDataDir}`,
|
|
||||||
winPos,
|
winPos,
|
||||||
'about:blank'
|
'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() {
|
async function initChrome() {
|
||||||
const executable = findChrome();
|
const { browserWSEndpoint, closeBrowser, chromeProcess } = await launch({
|
||||||
const userDataDir = await fs.mkdtemp(CHROME_PROFILE_PATH);
|
args: chromeArgs
|
||||||
const chromeArguments = chromeArgs(userDataDir);
|
|
||||||
const chromeProcess = cp.spawn(executable, chromeArguments, {
|
|
||||||
stdio: ['ignore', 'ignore', 'pipe'],
|
|
||||||
env: process.env,
|
|
||||||
detached: process.platform !== 'win32'
|
|
||||||
});
|
});
|
||||||
|
const browser = await Browser.connect(browserWSEndpoint, {
|
||||||
const maybeRemoveUDataDir = () => {
|
ignoreHTTPSErrors: true,
|
||||||
try {
|
additionalDomains: { workers: true },
|
||||||
fs.removeSync(userDataDir);
|
process: chromeProcess,
|
||||||
} catch (e) {}
|
closeCallback: closeBrowser
|
||||||
};
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
process.once('SIGTERM', killChrome);
|
await browser.waitForTarget(t => t.type() === 'page');
|
||||||
process.once('SIGHUP', killChrome);
|
return browser;
|
||||||
await waitForWSEndpoint(chromeProcess, 15 * 1000);
|
|
||||||
return { chromeProcess, killChrome };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = initChrome;
|
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 shutdownOnSignals = ['SIGINT', 'SIGTERM', 'SIGHUP'];
|
||||||
const assetsPath = path.join(__dirname, '..', 'assets');
|
const assetsPath = path.join(__dirname, '..', 'assets');
|
||||||
const httpsSandboxPath = path.join(assetsPath, 'sandbox.html');
|
const httpsSandboxPath = path.join(assetsPath, 'sandbox.html');
|
||||||
|
const sandboxDirectPath = path.join(assetsPath, 'sandboxDirect.html');
|
||||||
const theyFoundItPath = path.join(assetsPath, 'it.html');
|
const theyFoundItPath = path.join(assetsPath, 'it.html');
|
||||||
|
|
||||||
|
const testPageURL = `http://localhost:${port}/testPage.html`;
|
||||||
|
const testPageDirectURL = `http://localhost:${port}/testPageDirect.html`;
|
||||||
|
|
||||||
function promiseResolveReject() {
|
function promiseResolveReject() {
|
||||||
const prr = { promise: null, resolve: null, reject: null };
|
const prr = { promise: null, resolve: null, reject: null };
|
||||||
prr.promise = new Promise((resolve, reject) => {
|
prr.promise = new Promise((resolve, reject) => {
|
||||||
@ -85,6 +89,15 @@ async function initServer() {
|
|||||||
return fs.createReadStream(httpsSandboxPath);
|
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(
|
.get(
|
||||||
'/live/20180803160549mp_/https://tests.wombat.io/test',
|
'/live/20180803160549mp_/https://tests.wombat.io/test',
|
||||||
async (request, reply) => {
|
async (request, reply) => {
|
||||||
@ -103,7 +116,8 @@ async function initServer() {
|
|||||||
fastify.reset();
|
fastify.reset();
|
||||||
return fastify.close();
|
return fastify.close();
|
||||||
})
|
})
|
||||||
.decorate('testPage', `http://localhost:${port}/testPage.html`)
|
.decorate('testPage', testPageURL)
|
||||||
|
.decorate('testPageDirect', testPageDirectURL)
|
||||||
.decorate('waitForRequest', route => {
|
.decorate('waitForRequest', route => {
|
||||||
let prr = requestSubscribers.get(route);
|
let prr = requestSubscribers.get(route);
|
||||||
if (prr) return prr.promise;
|
if (prr) return prr.promise;
|
||||||
|
@ -1,29 +1,22 @@
|
|||||||
const initChrome = require('./initChrome');
|
const initChrome = require('./initChrome');
|
||||||
const initServer = require('./initServer');
|
const initServer = require('./initServer');
|
||||||
const { CRIExtra, Browser, Events } = require('chrome-remote-interface-extra');
|
const { Browser } = require('chrome-remote-interface-extra');
|
||||||
|
|
||||||
const testDomains = { workers: true };
|
|
||||||
|
|
||||||
class TestHelper {
|
class TestHelper {
|
||||||
/**
|
/**
|
||||||
* @param {*} t
|
* @param {*} t
|
||||||
|
* @param {boolean} [direct = false]
|
||||||
* @return {Promise<TestHelper>}
|
* @return {Promise<TestHelper>}
|
||||||
*/
|
*/
|
||||||
static async init(t) {
|
static async init(t, direct = false) {
|
||||||
const { chromeProcess, killChrome } = await initChrome();
|
const browser = await initChrome();
|
||||||
const server = await initServer();
|
const server = await initServer();
|
||||||
const { webSocketDebuggerUrl } = await CRIExtra.Version();
|
const th = new TestHelper({
|
||||||
const client = await CRIExtra({ target: webSocketDebuggerUrl });
|
server,
|
||||||
const browser = await Browser.create(client, {
|
browser,
|
||||||
ignoreHTTPSErrors: true,
|
t,
|
||||||
process: chromeProcess,
|
direct
|
||||||
additionalDomains: testDomains,
|
|
||||||
async closeCallback() {
|
|
||||||
killChrome();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
await browser.waitForTarget(t => t.type() === 'page');
|
|
||||||
const th = new TestHelper({ server, client, browser, t, killChrome });
|
|
||||||
await th.setup();
|
await th.setup();
|
||||||
return th;
|
return th;
|
||||||
}
|
}
|
||||||
@ -31,17 +24,12 @@ class TestHelper {
|
|||||||
/**
|
/**
|
||||||
* @param {TestHelperInit} init
|
* @param {TestHelperInit} init
|
||||||
*/
|
*/
|
||||||
constructor({ server, client, browser, t, killChrome }) {
|
constructor({ server, browser, t, direct }) {
|
||||||
/**
|
/**
|
||||||
* @type {fastify.FastifyInstance}
|
* @type {fastify.FastifyInstance}
|
||||||
*/
|
*/
|
||||||
this._server = server;
|
this._server = server;
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {CRIConnection}
|
|
||||||
*/
|
|
||||||
this._client = client;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {Browser}
|
* @type {Browser}
|
||||||
*/
|
*/
|
||||||
@ -50,13 +38,14 @@ class TestHelper {
|
|||||||
/** @type {*} */
|
/** @type {*} */
|
||||||
this._t = t;
|
this._t = t;
|
||||||
|
|
||||||
this._killChrome = killChrome;
|
|
||||||
|
|
||||||
/** @type {Page} */
|
/** @type {Page} */
|
||||||
this._testPage = null;
|
this._testPage = null;
|
||||||
|
|
||||||
/** @type {Frame} */
|
/** @type {Frame} */
|
||||||
this._sandbox = null;
|
this._sandbox = null;
|
||||||
|
|
||||||
|
/** @type {boolean} */
|
||||||
|
this._direct = direct;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -98,7 +87,10 @@ class TestHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async cleanup() {
|
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'
|
waitUntil: 'networkidle2'
|
||||||
});
|
});
|
||||||
this._sandbox = this._testPage.frames()[1];
|
this._sandbox = this._testPage.frames()[1];
|
||||||
@ -110,7 +102,8 @@ class TestHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async ensureSandbox() {
|
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();
|
await this.fullRefresh();
|
||||||
} else {
|
} else {
|
||||||
await this.maybeInitWombat();
|
await this.maybeInitWombat();
|
||||||
@ -146,9 +139,8 @@ module.exports = TestHelper;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} TestHelperInit
|
* @typedef {Object} TestHelperInit
|
||||||
* @property {Browser} browser
|
|
||||||
* @property {CRIConnection} client
|
|
||||||
* @property {fastify.FastifyInstance} server
|
* @property {fastify.FastifyInstance} server
|
||||||
* @property {*} t
|
* @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