1
0
mirror of https://github.com/webrecorder/pywb.git synced 2025-03-15 08:04:49 +01:00
pywb/wombat/test/setup-after-initialization.js
John Berlin 94784d6e5d wombat overhaul! fixes #449 (#451)
wombat:
 - I: function overrides applied by wombat now better appear to be the original new function name same as originals when possible
 - I: WombatLocation now looks and behaves more like the original Location interface
 - I: The custom storage class now looks and behaves more like the original Storage
 - I: SVG image rewriting has been improved: both the href and xlink:href deprecated since SVG2 now rewritten always
 - I: document.open now handles the case of creation of a new window
 - I: Request object rewriting of the readonly href property is now correctly handled
 - I: EventTarget.addEventListener, removeEventListener overrides now preserve the original this argument of the wrapped listener
 - A: document.close override to ensure wombat is initialized after write or writeln usage
 - A: reconstruction of <doctype...> in rewriteHTMLComplete IFF it was included in the original string of HTML
 - A: document.body setter override to ensure rewriting of the new body or frameset
 - A: Attr.[value, nodeValue, textContent] added setter override to perform URL rewrites
 - A: SVGElements rewriting of the filter, style, xlink:href, href, and src attributes
 - A: HTMLTrackElement rewriting of the src attribute of the
 - A: HTMLQuoteElement and HTMLModElement rewriting of the cite attribute
 - A: Worklet.addModule: Loads JS module specified by a URL.
 - A: HTMLHyperlinkElementUtils overrides to the areaelement
 - A: ShadowRootoverrides to: innerHTML even though inherites from DocumentFragement and Node it still has innerHTML getter setter.
 - A: ShadowRoot, Element, DocumentFragment append, prepend: adds strings of HTML or a new Node inherited from ParentNode
 - A: StylePropertyMap override: New way to access and set CSS properties.
 - A: Response.redirecthttps rewriting of the URL argument.
 - A:  UIEvent, MouseEvent, TouchEvent, KeyboardEvent, WheelEvent, InputEvent, and CompositionEven constructor and init{even-name} overrides in order to ensure that wombats JS Proxy usage does not affect their defined behaviors
 - A: XSLTProcessor override to ensure its usage is not affected by wombats JS Proxy usage.
 - A: navigator.unregisterProtocolHandler: Same override as existing navigator.registerProtocolHandler but from the inverse operation
 - A: PresentationRequest: Constructor takes a URL or an array of URLs.
 - A: EventSource and WebSocket override in order to ensure that they do not cause live leaks
 - A: overrides for the child node interface
 - Fix: autofetch worker creatation of the backing worker when it is operating within an execution context with a null origin
tests:
  - A: 559 tests specific to wombat and client side rewritting
pywb:
  - Fix: a few broken tests due to iana.org requiring a user agent in its requests
rewrite:
  - introduced a new JSWorkerRewriter class in order to support rewriting via wombat workers in the context of all supported worker variants via
  - ensured rewriter app correctly sets the static prefix
ci:
 - Modified travis.yml to specifically enumerate jobs
documentation:
  - Documented new wombat, wombat proxy moded, wombat workers
auto-fetch:
 - switched to mutation observer when in proxy mode so that the behaviors can operate in tandem with the autofetcher
2019-05-15 11:42:51 -07:00

558 lines
19 KiB
JavaScript

import test from 'ava';
import TestHelper from './helpers/testHelper';
import * as testedChanges from './helpers/testedValues';
/**
* @type {TestHelper}
*/
let helper = null;
test.before(async t => {
helper = await TestHelper.init(t);
await helper.initWombat();
});
test.beforeEach(async t => {
t.context.sandbox = helper.sandbox();
t.context.testPage = helper.testPage();
t.context.server = helper.server();
});
test.after.always(async t => {
await helper.stop();
});
test('internal globals: should not have removed _WBWombat from window', async t => {
const { sandbox, server } = t.context;
t.deepEqual(
await sandbox.evaluate(() => ({
_WBWombat: {
exists: window._WBWombat != null,
type: typeof window._WBWombat
},
_WBWombatInit: {
exists: window._WBWombatInit != null,
type: typeof window._WBWombatInit
}
})),
{
_WBWombat: { exists: true, type: 'function' },
_WBWombatInit: { exists: true, type: 'function' }
},
'The internal globals _WBWombat and _WBWombatInit are not as expected after initialization'
);
});
test('internal globals: should add the property __WB_replay_top to window that is equal to the same window', async t => {
const { sandbox, server } = t.context;
t.deepEqual(
await sandbox.evaluate(() => ({
exists: window.__WB_replay_top != null,
eq: window.__WB_replay_top === window
})),
{
exists: true,
eq: true
},
'The internal global __WB_replay_top is not as expected after initialization'
);
});
test('internal globals: should define the property __WB_top_frame when it is the top replayed page', async t => {
const { sandbox, server } = t.context;
t.deepEqual(
await sandbox.evaluate(() => ({
exists: window.__WB_top_frame != null,
neq: window.__WB_top_frame !== window
})),
{
exists: true,
neq: true
},
'The internal global __WB_top_frame is not as expected after initialization'
);
});
test('internal globals: should define the WB_wombat_top property on Object.prototype', async t => {
const { sandbox, server } = t.context;
t.deepEqual(
await sandbox.evaluate(() => {
const descriptor = Reflect.getOwnPropertyDescriptor(
Object.prototype,
'WB_wombat_top'
);
if (!descriptor) return { exists: false };
return {
exists: true,
configurable: descriptor.configurable,
enumerable: descriptor.enumerable,
get: typeof descriptor.get,
set: typeof descriptor.set
};
}),
{
exists: true,
configurable: true,
enumerable: false,
get: 'function',
set: 'function'
},
'The property descriptor added to the prototype of Object for WB_wombat_top is not correct'
);
});
test('internal globals: should add the _WB_wombat_location property to window', async t => {
const { sandbox, server } = t.context;
t.deepEqual(
await sandbox.evaluate(() => ({
_WB_wombat_location: {
exists: window._WB_wombat_location != null,
type: typeof window._WB_wombat_location
},
WB_wombat_location: {
exists: window.WB_wombat_location != null,
type: typeof window.WB_wombat_location
}
})),
{
_WB_wombat_location: { exists: true, type: 'object' },
WB_wombat_location: { exists: true, type: 'object' }
},
'Wombat location properties on window are not correct'
);
});
test('internal globals: should add the __wb_Date_now property to window', async t => {
const { sandbox, server } = t.context;
t.deepEqual(
await sandbox.evaluate(() => ({
exists: window.__wb_Date_now != null,
type: typeof window.__wb_Date_now
})),
{ exists: true, type: 'function' },
'The __wb_Date_now property of window is incorrect'
);
});
test('internal globals: should add the __WB_timediff property to window.Date', async t => {
const { sandbox, server } = t.context;
t.deepEqual(
await sandbox.evaluate(() => ({
exists: window.Date.__WB_timediff != null,
type: typeof window.Date.__WB_timediff
})),
{ exists: true, type: 'number' },
'The __WB_timediff property of window.Date is incorrect'
);
});
test('internal globals: should persist the original window.postMessage as __orig_postMessage', async t => {
const { sandbox, server } = t.context;
t.deepEqual(
await sandbox.evaluate(() => ({
exists: window.__orig_postMessage != null,
type: typeof window.__orig_postMessage,
isO: window.__orig_postMessage.toString().includes('[native code]')
})),
{ exists: true, type: 'function', isO: true },
'The __WB_timediff property of window.Date is incorrect'
);
});
test('internal globals: should not expose WombatLocation on window', async t => {
const { sandbox, server } = t.context;
t.true(
await sandbox.evaluate(() => !('WombatLocation' in window)),
'WombatLocation should not be exposed directly'
);
});
test('exposed functions - extract_orig: should should extract the original url', async t => {
const { sandbox, server } = t.context;
t.true(
await sandbox.evaluate(
() =>
window._wb_wombat.extract_orig(
'http://localhost:3030/jberlin/sw/20180510171123/https://n0tan3rd.github.io/replay_test/'
) === 'https://n0tan3rd.github.io/replay_test/'
),
'extract_orig could not extract the original URL'
);
});
test('exposed functions - extract_orig: should not modify an un-rewritten url', async t => {
const { sandbox, server } = t.context;
t.true(
await sandbox.evaluate(
() =>
window._wb_wombat.extract_orig(
'https://n0tan3rd.github.io/replay_test/'
) === 'https://n0tan3rd.github.io/replay_test/'
),
'extract_orig modified an original URL'
);
});
test('exposed functions - extract_orig: should be able to extract the original url from an encoded string', async t => {
const { sandbox, server } = t.context;
t.deepEqual(
await sandbox.evaluate(() => {
const expected = 'https://n0tan3rd.github.io/replay_test/';
const extractO = window._wb_wombat.extract_orig;
const unicode = extractO(
'\u0068\u0074\u0074\u0070\u003a\u002f\u002f\u006c\u006f\u0063\u0061\u006c\u0068\u006f\u0073\u0074\u003a\u0033\u0030\u0033\u0030\u002f\u006a\u0062\u0065\u0072\u006c\u0069\u006e\u002f\u0073\u0077\u002f\u0032\u0030\u0031\u0038\u0030\u0035\u0031\u0030\u0031\u0037\u0031\u0031\u0032\u0033\u002f\u0068\u0074\u0074\u0070\u0073\u003a\u002f\u002f\u006e\u0030\u0074\u0061\u006e\u0033\u0072\u0064\u002e\u0067\u0069\u0074\u0068\u0075\u0062\u002e\u0069\u006f\u002f\u0072\u0065\u0070\u006c\u0061\u0079\u005f\u0074\u0065\u0073\u0074\u002f'
);
const hex = extractO(
'\x68\x74\x74\x70\x3a\x2f\x2f\x6c\x6f\x63\x61\x6c\x68\x6f\x73\x74\x3a\x33\x30\x33\x30\x2f\x6a\x62\x65\x72\x6c\x69\x6e\x2f\x73\x77\x2f\x32\x30\x31\x38\x30\x35\x31\x30\x31\x37\x31\x31\x32\x33\x2f\x68\x74\x74\x70\x73\x3a\x2f\x2f\x6e\x30\x74\x61\x6e\x33\x72\x64\x2e\x67\x69\x74\x68\x75\x62\x2e\x69\x6f\x2f\x72\x65\x70\x6c\x61\x79\x5f\x74\x65\x73\x74\x2f'
);
return {
unicode:
unicode === expected &&
unicode ===
'\u0068\u0074\u0074\u0070\u0073\u003a\u002f\u002f\u006e\u0030\u0074\u0061\u006e\u0033\u0072\u0064\u002e\u0067\u0069\u0074\u0068\u0075\u0062\u002e\u0069\u006f\u002f\u0072\u0065\u0070\u006c\u0061\u0079\u005f\u0074\u0065\u0073\u0074\u002f',
hex:
hex === expected &&
hex ===
'\x68\x74\x74\x70\x73\x3a\x2f\x2f\x6e\x30\x74\x61\x6e\x33\x72\x64\x2e\x67\x69\x74\x68\x75\x62\x2e\x69\x6f\x2f\x72\x65\x70\x6c\x61\x79\x5f\x74\x65\x73\x74\x2f'
};
}),
{ unicode: true, hex: true },
'extract_orig could not extract the original URL from an encoded string'
);
});
test('exposed functions - rewrite_url: should be able to rewrite an encoded string', async t => {
const { sandbox, server } = t.context;
t.deepEqual(
await sandbox.evaluate(() => {
const expected = 'https://n0tan3rd.github.io/replay_test/';
const rewrite_url = window._wb_wombat.rewrite_url;
const unicode = rewrite_url(
'\u0068\u0074\u0074\u0070\u0073\u003a\u002f\u002f\u006e\u0030\u0074\u0061\u006e\u0033\u0072\u0064\u002e\u0067\u0069\u0074\u0068\u0075\u0062\u002e\u0069\u006f\u002f\u0072\u0065\u0070\u006c\u0061\u0079\u005f\u0074\u0065\u0073\u0074\u002f'
);
const hex = rewrite_url(
'\x68\x74\x74\x70\x73\x3a\x2f\x2f\x6e\x30\x74\x61\x6e\x33\x72\x64\x2e\x67\x69\x74\x68\x75\x62\x2e\x69\x6f\x2f\x72\x65\x70\x6c\x61\x79\x5f\x74\x65\x73\x74\x2f'
);
return {
unicode:
unicode ===
`${window.wbinfo.prefix}${window.wbinfo.wombat_ts}${
window.wbinfo.mod
}/${expected}`,
hex:
hex ===
`${window.wbinfo.prefix}${window.wbinfo.wombat_ts}${
window.wbinfo.mod
}/${expected}`
};
}),
{ unicode: true, hex: true },
'rewrite_url could not rewrite an encoded string'
);
});
testedChanges.TestedPropertyDescriptorUpdates.forEach(aTest => {
const msg = 'an property descriptor override should have been applied';
aTest.props.forEach(prop => {
if (aTest.docOrWin) {
test(`${aTest.docOrWin}.${prop}: ${msg}`, async t => {
const { sandbox, server } = t.context;
const result = await sandbox.evaluate(
testFn,
aTest.docOrWin,
prop,
aTest.expectedInterface,
aTest.skipGet,
aTest.skipSet
);
t.deepEqual(
result.main,
{ exists: true, good: true },
`The property descriptor for ${aTest.docOrWin}.${prop} is incorrect`
);
for (let i = 0; i < result.sub.length; i++) {
const subTest = result.sub[i];
t.true(subTest.result, subTest.what);
}
});
} else if (aTest.objPaths) {
aTest.objPaths.forEach(objPath => {
test(`${objPath.replace('window.', '')}.${prop}: ${msg}`, async t => {
const { sandbox, server } = t.context;
const result = await sandbox.evaluate(
testFn,
objPath,
prop,
aTest.expectedInterface,
aTest.skipGet,
aTest.skipSet
);
t.deepEqual(
result.main,
{ exists: true, good: true },
`The property descriptor for ${objPath.replace(
'window.',
''
)}.${prop} is incorrect`
);
for (let i = 0; i < result.sub.length; i++) {
const subTest = result.sub[i];
t.true(subTest.result, subTest.what);
}
});
});
} else {
test(`${aTest.objPath.replace(
'window.',
''
)}.${prop}: ${msg}`, async t => {
const { sandbox, server } = t.context;
const result = await sandbox.evaluate(
testFn,
aTest.objPath,
prop,
aTest.expectedInterface,
aTest.skipGet,
aTest.skipSet
);
t.deepEqual(
result.main,
{ exists: true, good: true },
`The property descriptor for ${aTest.objPath.replace(
'window.',
''
)}.prop is incorrect`
);
for (let i = 0; i < result.sub.length; i++) {
const subTest = result.sub[i];
t.true(subTest.result, subTest.what);
}
});
}
});
function testFn(objectPath, prop, expectedInterface, skipGet, skipSet) {
// get the original and wombat object represented by the object path expression
// eg if objectPath is window.Node.prototype then original === window.Node.prototype and obj === wombatSandbox.window.Node.prototype
const original = window.WombatTestUtil.getOriginalWinDomViaPath(objectPath);
const existing = window.WombatTestUtil.getViaPath(
window.wombatSandbox,
objectPath
);
// sometimes we need to skip a property get/set toString check
let skipGetCheck = !!(skipGet && skipGet.indexOf(prop) > -1);
let skipSetCheck = !!(skipSet && skipSet.indexOf(prop) > -1);
// use the reflect object in the wombat sandbox context
const newPD = Reflect.getOwnPropertyDescriptor(existing, prop);
const expectedEntries = Object.entries(expectedInterface);
const tResults = {
main: {
exists: newPD != null,
good: expectedEntries.every(([pk, ptype]) => typeof newPD[pk] === ptype)
},
sub: []
};
const originalPD = window.WombatTestUtil.getOriginalPropertyDescriptorFor(
original,
prop
);
if (originalPD) {
// do a quick deep check first to see if we modified something
tResults.sub.push({
what: 'newPD !== originalPD',
result: expectedEntries.some(
([pk, ptype]) => newPD[pk] !== originalPD[pk]
)
});
// now check each part of the expected property descriptor to make sure nothing went wrong
if (
!skipGetCheck &&
expectedInterface.get &&
newPD.get &&
originalPD.get
) {
tResults.sub.push({
what: `${objectPath}.${prop} getter.toString() !== original getter.toString()`,
result: newPD.get.toString() !== originalPD.get.toString()
});
}
if (
!skipSetCheck &&
expectedInterface.set &&
newPD.set &&
originalPD.set
) {
tResults.sub.push({
what: `${objectPath}.${prop} setter.toString() !== original setter.toString()`,
result: newPD.set.toString() !== originalPD.set.toString()
});
}
if (originalPD.configurable != null && newPD.configurable != null) {
tResults.sub.push({
what: `${objectPath}.${prop} the "new" configurable pd does not equals the original`,
result: newPD.configurable === originalPD.configurable
});
}
if (originalPD.writable != null && newPD.writable != null) {
tResults.sub.push({
what: `${objectPath}.${prop} the "new" writable pd does not equals the original`,
result: newPD.writable === originalPD.writable
});
}
if (originalPD.value != null && newPD.value != null) {
tResults.sub.push({
what: `${objectPath}.${prop} the "new" value pd does equals the original`,
result: newPD.value !== originalPD.value
});
}
}
return tResults;
}
});
testedChanges.TestFunctionChanges.forEach(aTest => {
if (aTest.constructors) {
aTest.constructors.forEach(ctor => {
const niceC = ctor.replace('window.', '');
test(`${niceC}: an constructor override should have been applied`, async t => {
const { sandbox, server } = t.context;
t.true(
await sandbox.evaluate(testFN, ctor),
`The ${ctor} was not updated`
);
function testFN(ctor) {
const existing = window.WombatTestUtil.getViaPath(
window.wombatSandbox,
ctor
);
const original = window.WombatTestUtil.getOriginalWinDomViaPath(ctor);
return existing.toString() !== original.toString();
}
});
});
} else if (aTest.objPath && aTest.origs) {
for (let i = 0; i < aTest.fns.length; i++) {
const fn = aTest.fns[i];
const ofn = aTest.origs[i];
const niceWhat = `${aTest.objPath.replace('window.', '')}.${fn}`;
test(`${niceWhat}: an function override should have been applied`, async t => {
const { sandbox, server } = t.context;
t.deepEqual(
await sandbox.evaluate(testFN, aTest.objPath, fn, ofn),
{ ne: true, persisted: true },
`The ${niceWhat} was not updated correctly`
);
function testFN(objPath, fn, ofn) {
const existing = window.WombatTestUtil.getViaPath(
window.wombatSandbox,
objPath
);
const original = window.WombatTestUtil.getOriginalWinDomViaPath(
objPath
);
return {
ne: existing[fn].toString() !== original[fn].toString(),
persisted: existing[ofn].toString() === original[fn].toString()
};
}
});
}
} else if (aTest.fnPath) {
test(`${
aTest.fnPath
}: an function override should have been applied`, async t => {
const { sandbox, server } = t.context;
const result = await sandbox.evaluate(testFN, aTest.fnPath, aTest.oPath);
t.true(result.ne, `${aTest.fnPath} was not updated`);
if (result.originalPersisted) {
t.true(
result.originalPersisted,
`The persisted original function for ${
aTest.fnPath
} does not match the original`
);
}
function testFN(fnPath, oPath) {
const existing = window.WombatTestUtil.getViaPath(
window.wombatSandbox,
fnPath
);
const original = window.WombatTestUtil.getOriginalWinDomViaPath(fnPath);
const result = {
ne: existing.toString() !== original.toString()
};
if (oPath) {
const ofnOnfn = window.WombatTestUtil.getViaPath(
window.wombatSandbox,
oPath
);
result.originalPersisted = ofnOnfn.toString() === original.toString();
}
return result;
}
});
} else if (aTest.fns) {
aTest.fns.forEach(fn => {
const niceWhat = `${aTest.objPath.replace('window.', '')}.${fn}`;
test(`${niceWhat}: an function override should have been applied`, async t => {
const { sandbox, server } = t.context;
const results = await sandbox.evaluate(testFn, aTest.objPath, fn);
if (results.tests) {
results.tests.forEach(({ test, msg }) => {
t.true(test, msg);
});
} else {
t.true(results.test, `${aTest.objPath}.${fn} was not updated`);
}
function testFn(objPath, fn) {
const existing = window.WombatTestUtil.getViaPath(
window.wombatSandbox,
objPath
);
const original = window.WombatTestUtil.getOriginalWinDomViaPath(
objPath
);
const result = {};
if (existing[fn] && original[fn]) {
result.test = existing[fn].toString() !== original[fn].toString();
} else if (existing[fn]) {
result.tests = [
{
test: existing[fn] !== null,
msg: `${objPath}.${fn} was not overridden (is undefined/null)`
},
{
test: !existing[fn].toString().includes('[native code]'),
msg: `${objPath}.${fn} was not overridden at all`
}
];
} else {
result.test = false;
}
return result;
}
});
});
} else if (aTest.persisted) {
aTest.persisted.forEach(fn => {
const niceWhat = `${aTest.objPath.replace('window.', '')}.${fn}`;
test(`${niceWhat}: the original function should exist on the overridden object`, async t => {
const { sandbox, server } = t.context;
t.true(
await sandbox.evaluate(testFn, aTest.objPath, fn),
`the original function '${niceWhat}' was not persisted`
);
function testFn(objPath, fn) {
const existing = window.WombatTestUtil.getViaPath(
window.wombatSandbox,
objPath
);
const original = window.WombatTestUtil.getOriginalWinDomViaPath(
objPath
);
return existing[fn].toString() === original[fn].toString();
}
});
});
}
});