@zorilla/puppeteer-extra-plugin-stealth 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +21 -0
- package/LICENSE +21 -0
- package/README.md +324 -0
- package/dist/evasions/README.md +13 -0
- package/dist/evasions/_template/README.md +18 -0
- package/dist/evasions/_template/index.d.ts +13 -0
- package/dist/evasions/_template/index.d.ts.map +1 -0
- package/dist/evasions/_template/index.js +26 -0
- package/dist/evasions/_template/index.js.map +1 -0
- package/dist/evasions/_template/package.json +5 -0
- package/dist/evasions/_utils/README.md +287 -0
- package/dist/evasions/_utils/index.d.ts +238 -0
- package/dist/evasions/_utils/index.d.ts.map +1 -0
- package/dist/evasions/_utils/index.js +588 -0
- package/dist/evasions/_utils/index.js.map +1 -0
- package/dist/evasions/_utils/withUtils.d.ts +12 -0
- package/dist/evasions/_utils/withUtils.d.ts.map +1 -0
- package/dist/evasions/_utils/withUtils.js +47 -0
- package/dist/evasions/_utils/withUtils.js.map +1 -0
- package/dist/evasions/chrome.app/README.md +16 -0
- package/dist/evasions/chrome.app/index.d.ts +11 -0
- package/dist/evasions/chrome.app/index.d.ts.map +1 -0
- package/dist/evasions/chrome.app/index.js +97 -0
- package/dist/evasions/chrome.app/index.js.map +1 -0
- package/dist/evasions/chrome.app/package.json +5 -0
- package/dist/evasions/chrome.csi/README.md +29 -0
- package/dist/evasions/chrome.csi/index.d.ts +25 -0
- package/dist/evasions/chrome.csi/index.d.ts.map +1 -0
- package/dist/evasions/chrome.csi/index.js +69 -0
- package/dist/evasions/chrome.csi/index.js.map +1 -0
- package/dist/evasions/chrome.csi/package.json +5 -0
- package/dist/evasions/chrome.loadTimes/README.md +27 -0
- package/dist/evasions/chrome.loadTimes/index.d.ts +23 -0
- package/dist/evasions/chrome.loadTimes/index.d.ts.map +1 -0
- package/dist/evasions/chrome.loadTimes/index.js +163 -0
- package/dist/evasions/chrome.loadTimes/index.js.map +1 -0
- package/dist/evasions/chrome.loadTimes/package.json +5 -0
- package/dist/evasions/chrome.runtime/README.md +32 -0
- package/dist/evasions/chrome.runtime/index.d.ts +14 -0
- package/dist/evasions/chrome.runtime/index.d.ts.map +1 -0
- package/dist/evasions/chrome.runtime/index.js +252 -0
- package/dist/evasions/chrome.runtime/index.js.map +1 -0
- package/dist/evasions/chrome.runtime/package.json +5 -0
- package/dist/evasions/chrome.runtime/staticData.json +41 -0
- package/dist/evasions/defaultArgs/README.md +17 -0
- package/dist/evasions/defaultArgs/index.d.ts +2 -0
- package/dist/evasions/defaultArgs/index.d.ts.map +1 -0
- package/dist/evasions/defaultArgs/index.js +43 -0
- package/dist/evasions/defaultArgs/index.js.map +1 -0
- package/dist/evasions/defaultArgs/package.json +5 -0
- package/dist/evasions/iframe.contentWindow/README.md +19 -0
- package/dist/evasions/iframe.contentWindow/index.d.ts +15 -0
- package/dist/evasions/iframe.contentWindow/index.d.ts.map +1 -0
- package/dist/evasions/iframe.contentWindow/index.js +132 -0
- package/dist/evasions/iframe.contentWindow/index.js.map +1 -0
- package/dist/evasions/iframe.contentWindow/package.json +5 -0
- package/dist/evasions/media.codecs/README.md +38 -0
- package/dist/evasions/media.codecs/index.d.ts +12 -0
- package/dist/evasions/media.codecs/index.d.ts.map +1 -0
- package/dist/evasions/media.codecs/index.js +89 -0
- package/dist/evasions/media.codecs/index.js.map +1 -0
- package/dist/evasions/media.codecs/package.json +5 -0
- package/dist/evasions/navigator.hardwareConcurrency/README.md +19 -0
- package/dist/evasions/navigator.hardwareConcurrency/index.d.ts +2 -0
- package/dist/evasions/navigator.hardwareConcurrency/index.d.ts.map +1 -0
- package/dist/evasions/navigator.hardwareConcurrency/index.js +45 -0
- package/dist/evasions/navigator.hardwareConcurrency/index.js.map +1 -0
- package/dist/evasions/navigator.hardwareConcurrency/package.json +5 -0
- package/dist/evasions/navigator.languages/README.md +17 -0
- package/dist/evasions/navigator.languages/index.d.ts +2 -0
- package/dist/evasions/navigator.languages/index.d.ts.map +1 -0
- package/dist/evasions/navigator.languages/index.js +44 -0
- package/dist/evasions/navigator.languages/index.js.map +1 -0
- package/dist/evasions/navigator.languages/package.json +5 -0
- package/dist/evasions/navigator.permissions/README.md +16 -0
- package/dist/evasions/navigator.permissions/index.d.ts +2 -0
- package/dist/evasions/navigator.permissions/index.d.ts.map +1 -0
- package/dist/evasions/navigator.permissions/index.js +66 -0
- package/dist/evasions/navigator.permissions/index.js.map +1 -0
- package/dist/evasions/navigator.permissions/package.json +5 -0
- package/dist/evasions/navigator.plugins/README.md +24 -0
- package/dist/evasions/navigator.plugins/data.json +48 -0
- package/dist/evasions/navigator.plugins/functionMocks.d.ts +9 -0
- package/dist/evasions/navigator.plugins/functionMocks.d.ts.map +1 -0
- package/dist/evasions/navigator.plugins/functionMocks.js +47 -0
- package/dist/evasions/navigator.plugins/functionMocks.js.map +1 -0
- package/dist/evasions/navigator.plugins/index.d.ts +19 -0
- package/dist/evasions/navigator.plugins/index.d.ts.map +1 -0
- package/dist/evasions/navigator.plugins/index.js +98 -0
- package/dist/evasions/navigator.plugins/index.js.map +1 -0
- package/dist/evasions/navigator.plugins/magicArray.d.ts +2 -0
- package/dist/evasions/navigator.plugins/magicArray.d.ts.map +1 -0
- package/dist/evasions/navigator.plugins/magicArray.js +145 -0
- package/dist/evasions/navigator.plugins/magicArray.js.map +1 -0
- package/dist/evasions/navigator.plugins/mimeTypes.d.ts +2 -0
- package/dist/evasions/navigator.plugins/mimeTypes.d.ts.map +1 -0
- package/dist/evasions/navigator.plugins/mimeTypes.js +18 -0
- package/dist/evasions/navigator.plugins/mimeTypes.js.map +1 -0
- package/dist/evasions/navigator.plugins/package.json +5 -0
- package/dist/evasions/navigator.plugins/plugins.d.ts +2 -0
- package/dist/evasions/navigator.plugins/plugins.d.ts.map +1 -0
- package/dist/evasions/navigator.plugins/plugins.js +18 -0
- package/dist/evasions/navigator.plugins/plugins.js.map +1 -0
- package/dist/evasions/navigator.vendor/README.md +36 -0
- package/dist/evasions/navigator.vendor/index.d.ts +2 -0
- package/dist/evasions/navigator.vendor/index.d.ts.map +1 -0
- package/dist/evasions/navigator.vendor/index.js +64 -0
- package/dist/evasions/navigator.vendor/index.js.map +1 -0
- package/dist/evasions/navigator.vendor/package.json +5 -0
- package/dist/evasions/navigator.webdriver/README.md +17 -0
- package/dist/evasions/navigator.webdriver/index.d.ts +13 -0
- package/dist/evasions/navigator.webdriver/index.d.ts.map +1 -0
- package/dist/evasions/navigator.webdriver/index.js +48 -0
- package/dist/evasions/navigator.webdriver/index.js.map +1 -0
- package/dist/evasions/navigator.webdriver/package.json +5 -0
- package/dist/evasions/sourceurl/README.md +17 -0
- package/dist/evasions/sourceurl/_fixtures/test.html +35 -0
- package/dist/evasions/sourceurl/index.d.ts +12 -0
- package/dist/evasions/sourceurl/index.d.ts.map +1 -0
- package/dist/evasions/sourceurl/index.js +82 -0
- package/dist/evasions/sourceurl/index.js.map +1 -0
- package/dist/evasions/sourceurl/package.json +5 -0
- package/dist/evasions/user-agent-override/README.md +53 -0
- package/dist/evasions/user-agent-override/index.d.ts +2 -0
- package/dist/evasions/user-agent-override/index.d.ts.map +1 -0
- package/dist/evasions/user-agent-override/index.js +206 -0
- package/dist/evasions/user-agent-override/index.js.map +1 -0
- package/dist/evasions/user-agent-override/package.json +5 -0
- package/dist/evasions/webgl.vendor/README.md +20 -0
- package/dist/evasions/webgl.vendor/index.d.ts +17 -0
- package/dist/evasions/webgl.vendor/index.d.ts.map +1 -0
- package/dist/evasions/webgl.vendor/index.js +57 -0
- package/dist/evasions/webgl.vendor/index.js.map +1 -0
- package/dist/evasions/webgl.vendor/package.json +5 -0
- package/dist/evasions/window.outerdimensions/README.md +17 -0
- package/dist/evasions/window.outerdimensions/index.d.ts +13 -0
- package/dist/evasions/window.outerdimensions/index.d.ts.map +1 -0
- package/dist/evasions/window.outerdimensions/index.js +42 -0
- package/dist/evasions/window.outerdimensions/index.js.map +1 -0
- package/dist/evasions/window.outerdimensions/package.json +5 -0
- package/dist/index.d.ts +130 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +164 -0
- package/dist/index.js.map +1 -0
- package/examples/detect-headless.js +95 -0
- package/examples/test1.js +20 -0
- package/examples/test2.js +25 -0
- package/package.json +70 -0
- package/src/evasions/README.md +13 -0
- package/src/evasions/_template/README.md +18 -0
- package/src/evasions/_template/index.js +26 -0
- package/src/evasions/_template/package.json +5 -0
- package/src/evasions/_utils/README.md +287 -0
- package/src/evasions/_utils/index.js +588 -0
- package/src/evasions/_utils/withUtils.js +47 -0
- package/src/evasions/chrome.app/README.md +16 -0
- package/src/evasions/chrome.app/index.js +97 -0
- package/src/evasions/chrome.app/package.json +5 -0
- package/src/evasions/chrome.csi/README.md +29 -0
- package/src/evasions/chrome.csi/index.js +69 -0
- package/src/evasions/chrome.csi/package.json +5 -0
- package/src/evasions/chrome.loadTimes/README.md +27 -0
- package/src/evasions/chrome.loadTimes/index.js +163 -0
- package/src/evasions/chrome.loadTimes/package.json +5 -0
- package/src/evasions/chrome.runtime/README.md +32 -0
- package/src/evasions/chrome.runtime/index.js +252 -0
- package/src/evasions/chrome.runtime/package.json +5 -0
- package/src/evasions/chrome.runtime/staticData.json +41 -0
- package/src/evasions/defaultArgs/README.md +17 -0
- package/src/evasions/defaultArgs/index.js +43 -0
- package/src/evasions/defaultArgs/package.json +5 -0
- package/src/evasions/iframe.contentWindow/README.md +19 -0
- package/src/evasions/iframe.contentWindow/index.js +132 -0
- package/src/evasions/iframe.contentWindow/package.json +5 -0
- package/src/evasions/media.codecs/README.md +38 -0
- package/src/evasions/media.codecs/index.js +89 -0
- package/src/evasions/media.codecs/package.json +5 -0
- package/src/evasions/navigator.hardwareConcurrency/README.md +19 -0
- package/src/evasions/navigator.hardwareConcurrency/index.js +45 -0
- package/src/evasions/navigator.hardwareConcurrency/package.json +5 -0
- package/src/evasions/navigator.languages/README.md +17 -0
- package/src/evasions/navigator.languages/index.js +44 -0
- package/src/evasions/navigator.languages/package.json +5 -0
- package/src/evasions/navigator.permissions/README.md +16 -0
- package/src/evasions/navigator.permissions/index.js +66 -0
- package/src/evasions/navigator.permissions/package.json +5 -0
- package/src/evasions/navigator.plugins/README.md +24 -0
- package/src/evasions/navigator.plugins/data.json +48 -0
- package/src/evasions/navigator.plugins/functionMocks.js +47 -0
- package/src/evasions/navigator.plugins/index.js +98 -0
- package/src/evasions/navigator.plugins/magicArray.js +145 -0
- package/src/evasions/navigator.plugins/mimeTypes.js +18 -0
- package/src/evasions/navigator.plugins/package.json +5 -0
- package/src/evasions/navigator.plugins/plugins.js +18 -0
- package/src/evasions/navigator.vendor/README.md +36 -0
- package/src/evasions/navigator.vendor/index.js +64 -0
- package/src/evasions/navigator.vendor/package.json +5 -0
- package/src/evasions/navigator.webdriver/README.md +17 -0
- package/src/evasions/navigator.webdriver/index.js +48 -0
- package/src/evasions/navigator.webdriver/package.json +5 -0
- package/src/evasions/sourceurl/README.md +17 -0
- package/src/evasions/sourceurl/_fixtures/test.html +35 -0
- package/src/evasions/sourceurl/index.js +82 -0
- package/src/evasions/sourceurl/package.json +5 -0
- package/src/evasions/user-agent-override/README.md +53 -0
- package/src/evasions/user-agent-override/index.js +206 -0
- package/src/evasions/user-agent-override/package.json +5 -0
- package/src/evasions/webgl.vendor/README.md +20 -0
- package/src/evasions/webgl.vendor/index.js +57 -0
- package/src/evasions/webgl.vendor/package.json +5 -0
- package/src/evasions/window.outerdimensions/README.md +17 -0
- package/src/evasions/window.outerdimensions/index.js +42 -0
- package/src/evasions/window.outerdimensions/package.json +5 -0
- package/src/index.d.ts +111 -0
- package/src/index.ts +188 -0
- package/test/cat-and-mouse.test.ts +170 -0
- package/test/evasions/_utils/index.test.ts +710 -0
- package/test/evasions/chrome.app/index.test.ts +69 -0
- package/test/evasions/chrome.csi/index.test.ts +46 -0
- package/test/evasions/chrome.loadTimes/index.test.ts +61 -0
- package/test/evasions/chrome.runtime/index.test.ts +282 -0
- package/test/evasions/defaultArgs/index.test.ts +36 -0
- package/test/evasions/iframe.contentWindow/index.test.js +450 -0
- package/test/evasions/media.codecs/index.test.js +103 -0
- package/test/evasions/navigator.hardwareConcurrency/index.test.js +58 -0
- package/test/evasions/navigator.languages/index.test.js +101 -0
- package/test/evasions/navigator.permissions/index.test.js +104 -0
- package/test/evasions/navigator.plugins/index.test.js +55 -0
- package/test/evasions/navigator.plugins/mimeTypes.test.js +220 -0
- package/test/evasions/navigator.plugins/plugins.test.js +181 -0
- package/test/evasions/navigator.vendor/index.test.js +68 -0
- package/test/evasions/navigator.webdriver/index.test.js +47 -0
- package/test/evasions/sourceurl/_fixtures/test.html +35 -0
- package/test/evasions/sourceurl/index.test.js +62 -0
- package/test/evasions/user-agent-override/index.test.js +338 -0
- package/test/evasions/webgl.vendor/index.test.js +220 -0
- package/test/fixtures/dummy-with-service-worker.html +22 -0
- package/test/fixtures/dummy.html +11 -0
- package/test/fixtures/sw.js +1 -0
- package/test/fpscanner.test.ts +54 -0
- package/test/index.test.ts +51 -0
- package/test/service-worker.test.ts +112 -0
- package/test/stealth/_results/_thumbs/headful-chrome-stealth.js.png +0 -0
- package/test/stealth/_results/_thumbs/headful-chrome-vanilla.js.png +0 -0
- package/test/stealth/_results/_thumbs/headful-chromium-stealth.js.png +0 -0
- package/test/stealth/_results/_thumbs/headful-chromium-vanilla.js.png +0 -0
- package/test/stealth/_results/_thumbs/headless-chrome-stealth.js.png +0 -0
- package/test/stealth/_results/_thumbs/headless-chrome-vanilla.js.png +0 -0
- package/test/stealth/_results/_thumbs/headless-chromium-stealth.js.png +0 -0
- package/test/stealth/_results/_thumbs/headless-chromium-vanilla.js.png +0 -0
- package/test/stealth/_results/headful-chrome-stealth.js.png +0 -0
- package/test/stealth/_results/headful-chrome-vanilla.js.png +0 -0
- package/test/stealth/_results/headful-chromium-stealth.js.png +0 -0
- package/test/stealth/_results/headful-chromium-vanilla.js.png +0 -0
- package/test/stealth/_results/headless-chrome-stealth.js.png +0 -0
- package/test/stealth/_results/headless-chrome-vanilla.js.png +0 -0
- package/test/stealth/_results/headless-chromium-stealth.js.png +0 -0
- package/test/stealth/_results/headless-chromium-vanilla.js.png +0 -0
- package/test/stealth/headful-chrome-stealth.js +25 -0
- package/test/stealth/headful-chrome-vanilla.js +23 -0
- package/test/stealth/headful-chromium-stealth.js +22 -0
- package/test/stealth/headful-chromium-vanilla.js +20 -0
- package/test/stealth/headless-chrome-stealth.js +25 -0
- package/test/stealth/headless-chrome-vanilla.js +23 -0
- package/test/stealth/headless-chromium-stealth.js +22 -0
- package/test/stealth/headless-chromium-vanilla.js +20 -0
- package/test/util.js +82 -0
- package/tsconfig.json +10 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/vitest.config.ts +28 -0
|
@@ -0,0 +1,710 @@
|
|
|
1
|
+
import { expect, test } from 'vitest';
|
|
2
|
+
import utils from '../../../src/evasions/_utils/index.js';
|
|
3
|
+
import withUtils from '../../../src/evasions/_utils/withUtils.js';
|
|
4
|
+
import { vanillaPuppeteer } from '../../util.js';
|
|
5
|
+
|
|
6
|
+
/* global HTMLMediaElement WebGLRenderingContext */
|
|
7
|
+
|
|
8
|
+
test('splitObjPath: will do what it says', async () => {
|
|
9
|
+
const { objName, propName } = utils.splitObjPath(
|
|
10
|
+
'HTMLMediaElement.prototype.canPlayType'
|
|
11
|
+
);
|
|
12
|
+
expect(objName).toBe('HTMLMediaElement.prototype');
|
|
13
|
+
expect(propName).toBe('canPlayType');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('makeNativeString: will do what it says', async () => {
|
|
17
|
+
utils.init();
|
|
18
|
+
expect(utils.makeNativeString('bob')).toBe(
|
|
19
|
+
'function bob() { [native code] }'
|
|
20
|
+
);
|
|
21
|
+
expect(utils.makeNativeString('toString')).toBe(
|
|
22
|
+
'function toString() { [native code] }'
|
|
23
|
+
);
|
|
24
|
+
expect(utils.makeNativeString()).toBe('function () { [native code] }');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('replaceWithProxy: will work correctly', async () => {
|
|
28
|
+
const browser = await vanillaPuppeteer.launch({ headless: true });
|
|
29
|
+
const page = await browser.newPage();
|
|
30
|
+
|
|
31
|
+
const test1 = await withUtils(page).evaluate(utils => {
|
|
32
|
+
const dummyProxyHandler = {
|
|
33
|
+
get(_target, param) {
|
|
34
|
+
if (param && param === 'ping') {
|
|
35
|
+
return 'pong';
|
|
36
|
+
}
|
|
37
|
+
return utils.cache.Reflect.get(...(arguments || []));
|
|
38
|
+
},
|
|
39
|
+
apply() {
|
|
40
|
+
return utils.cache.Reflect.apply(...arguments);
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
utils.replaceWithProxy(
|
|
44
|
+
HTMLMediaElement.prototype,
|
|
45
|
+
'canPlayType',
|
|
46
|
+
dummyProxyHandler
|
|
47
|
+
);
|
|
48
|
+
return {
|
|
49
|
+
toString: HTMLMediaElement.prototype.canPlayType.toString(),
|
|
50
|
+
ping: HTMLMediaElement.prototype.canPlayType.ping,
|
|
51
|
+
};
|
|
52
|
+
});
|
|
53
|
+
expect(test1).toEqual({
|
|
54
|
+
toString: 'function canPlayType() { [native code] }',
|
|
55
|
+
ping: 'pong',
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('replaceObjPathWithProxy: will work correctly', async () => {
|
|
60
|
+
const browser = await vanillaPuppeteer.launch({ headless: true });
|
|
61
|
+
const page = await browser.newPage();
|
|
62
|
+
|
|
63
|
+
const test1 = await withUtils(page).evaluate(utils => {
|
|
64
|
+
const dummyProxyHandler = {
|
|
65
|
+
get(_target, param) {
|
|
66
|
+
if (param && param === 'ping') {
|
|
67
|
+
return 'pong';
|
|
68
|
+
}
|
|
69
|
+
return utils.cache.Reflect.get(...(arguments || []));
|
|
70
|
+
},
|
|
71
|
+
apply() {
|
|
72
|
+
return utils.cache.Reflect.apply(...arguments);
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
utils.replaceObjPathWithProxy(
|
|
76
|
+
'HTMLMediaElement.prototype.canPlayType',
|
|
77
|
+
dummyProxyHandler
|
|
78
|
+
);
|
|
79
|
+
return {
|
|
80
|
+
toString: HTMLMediaElement.prototype.canPlayType.toString(),
|
|
81
|
+
ping: HTMLMediaElement.prototype.canPlayType.ping,
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
expect(test1).toEqual({
|
|
85
|
+
toString: 'function canPlayType() { [native code] }',
|
|
86
|
+
ping: 'pong',
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('redirectToString: is battle hardened', async () => {
|
|
91
|
+
const browser = await vanillaPuppeteer.launch({ headless: true });
|
|
92
|
+
const page = await browser.newPage();
|
|
93
|
+
|
|
94
|
+
// Patch all documents including iframes
|
|
95
|
+
await withUtils(page).evaluateOnNewDocument(utils => {
|
|
96
|
+
// We redirect toString calls targeted at `canPlayType` to `getParameter`,
|
|
97
|
+
// so if everything works correctly we expect `getParameter` as response.
|
|
98
|
+
const proxyObj = HTMLMediaElement.prototype.canPlayType;
|
|
99
|
+
const originalObj = WebGLRenderingContext.prototype.getParameter;
|
|
100
|
+
|
|
101
|
+
utils.redirectToString(proxyObj, originalObj);
|
|
102
|
+
});
|
|
103
|
+
await page.goto('about:blank');
|
|
104
|
+
|
|
105
|
+
const result = await withUtils(page).evaluate(_utils => {
|
|
106
|
+
const iframe = document.createElement('iframe');
|
|
107
|
+
document.body.appendChild(iframe);
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
target: {
|
|
111
|
+
raw: HTMLMediaElement.prototype.canPlayType + '',
|
|
112
|
+
rawiframe:
|
|
113
|
+
iframe.contentWindow.HTMLMediaElement.prototype.canPlayType + '',
|
|
114
|
+
raw2: HTMLMediaElement.prototype.canPlayType.toString(),
|
|
115
|
+
rawiframe2:
|
|
116
|
+
iframe.contentWindow.HTMLMediaElement.prototype.canPlayType.toString(),
|
|
117
|
+
direct: Function.prototype.toString.call(
|
|
118
|
+
HTMLMediaElement.prototype.canPlayType
|
|
119
|
+
),
|
|
120
|
+
directWithiframe: iframe.contentWindow.Function.prototype.toString.call(
|
|
121
|
+
HTMLMediaElement.prototype.canPlayType
|
|
122
|
+
),
|
|
123
|
+
iframeWithdirect: Function.prototype.toString.call(
|
|
124
|
+
iframe.contentWindow.HTMLMediaElement.prototype.canPlayType
|
|
125
|
+
),
|
|
126
|
+
iframeWithiframe: iframe.contentWindow.Function.prototype.toString.call(
|
|
127
|
+
iframe.contentWindow.HTMLMediaElement.prototype.canPlayType
|
|
128
|
+
),
|
|
129
|
+
},
|
|
130
|
+
toString: {
|
|
131
|
+
obj: HTMLMediaElement.prototype.canPlayType.toString + '',
|
|
132
|
+
objiframe:
|
|
133
|
+
iframe.contentWindow.HTMLMediaElement.prototype.canPlayType.toString +
|
|
134
|
+
'',
|
|
135
|
+
raw: Function.prototype.toString + '',
|
|
136
|
+
rawiframe: iframe.contentWindow.Function.prototype.toString + '',
|
|
137
|
+
direct: Function.prototype.toString.call(Function.prototype.toString),
|
|
138
|
+
directWithiframe: iframe.contentWindow.Function.prototype.toString.call(
|
|
139
|
+
Function.prototype.toString
|
|
140
|
+
),
|
|
141
|
+
iframeWithdirect: Function.prototype.toString.call(
|
|
142
|
+
iframe.contentWindow.Function.prototype.toString
|
|
143
|
+
),
|
|
144
|
+
iframeWithiframe: iframe.contentWindow.Function.prototype.toString.call(
|
|
145
|
+
iframe.contentWindow.Function.prototype.toString
|
|
146
|
+
),
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
});
|
|
150
|
+
expect(result).toEqual({
|
|
151
|
+
target: {
|
|
152
|
+
raw: 'function getParameter() { [native code] }',
|
|
153
|
+
raw2: 'function getParameter() { [native code] }',
|
|
154
|
+
rawiframe: 'function getParameter() { [native code] }',
|
|
155
|
+
rawiframe2: 'function getParameter() { [native code] }',
|
|
156
|
+
direct: 'function getParameter() { [native code] }',
|
|
157
|
+
directWithiframe: 'function getParameter() { [native code] }',
|
|
158
|
+
iframeWithdirect: 'function getParameter() { [native code] }',
|
|
159
|
+
iframeWithiframe: 'function getParameter() { [native code] }',
|
|
160
|
+
},
|
|
161
|
+
toString: {
|
|
162
|
+
obj: 'function toString() { [native code] }',
|
|
163
|
+
objiframe: 'function toString() { [native code] }',
|
|
164
|
+
raw: 'function toString() { [native code] }',
|
|
165
|
+
rawiframe: 'function toString() { [native code] }',
|
|
166
|
+
direct: 'function toString() { [native code] }',
|
|
167
|
+
directWithiframe: 'function toString() { [native code] }',
|
|
168
|
+
iframeWithdirect: 'function toString() { [native code] }',
|
|
169
|
+
iframeWithiframe: 'function toString() { [native code] }',
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('redirectToString: has proper errors', async () => {
|
|
175
|
+
const browser = await vanillaPuppeteer.launch({ headless: true });
|
|
176
|
+
const page = await browser.newPage();
|
|
177
|
+
|
|
178
|
+
// Patch all documents including iframes
|
|
179
|
+
await withUtils(page).evaluateOnNewDocument(utils => {
|
|
180
|
+
// We redirect toString calls targeted at `canPlayType` to `getParameter`,
|
|
181
|
+
// so if everything works correctly we expect `getParameter` as response.
|
|
182
|
+
const proxyObj = HTMLMediaElement.prototype.canPlayType;
|
|
183
|
+
const originalObj = WebGLRenderingContext.prototype.getParameter;
|
|
184
|
+
|
|
185
|
+
utils.redirectToString(proxyObj, originalObj);
|
|
186
|
+
});
|
|
187
|
+
await page.goto('about:blank');
|
|
188
|
+
|
|
189
|
+
const result = await withUtils(page).evaluate(_utils => {
|
|
190
|
+
const evalErr = (str = '') => {
|
|
191
|
+
try {
|
|
192
|
+
// eslint-disable-next-line no-eval
|
|
193
|
+
return eval(str);
|
|
194
|
+
} catch (err) {
|
|
195
|
+
return err.toString();
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
blank: evalErr(`Function.prototype.toString.apply()`),
|
|
201
|
+
null: evalErr(`Function.prototype.toString.apply(null)`),
|
|
202
|
+
undef: evalErr(`Function.prototype.toString.apply(undefined)`),
|
|
203
|
+
emptyObject: evalErr(`Function.prototype.toString.apply({})`),
|
|
204
|
+
};
|
|
205
|
+
});
|
|
206
|
+
expect(result).toEqual({
|
|
207
|
+
blank:
|
|
208
|
+
"TypeError: Function.prototype.toString requires that 'this' be a Function",
|
|
209
|
+
null: "TypeError: Function.prototype.toString requires that 'this' be a Function",
|
|
210
|
+
undef:
|
|
211
|
+
"TypeError: Function.prototype.toString requires that 'this' be a Function",
|
|
212
|
+
emptyObject:
|
|
213
|
+
"TypeError: Function.prototype.toString requires that 'this' be a Function",
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test('patchToString: will work correctly', async () => {
|
|
218
|
+
const browser = await vanillaPuppeteer.launch({ headless: true });
|
|
219
|
+
const page = await browser.newPage();
|
|
220
|
+
|
|
221
|
+
// Test verbatim string replacement
|
|
222
|
+
const test1 = await withUtils(page).evaluate(utils => {
|
|
223
|
+
utils.patchToString(HTMLMediaElement.prototype.canPlayType, 'bob');
|
|
224
|
+
return HTMLMediaElement.prototype.canPlayType.toString();
|
|
225
|
+
});
|
|
226
|
+
expect(test1).toBe('bob');
|
|
227
|
+
|
|
228
|
+
// Test automatic mode derived from `.name`
|
|
229
|
+
const test2 = await withUtils(page).evaluate(utils => {
|
|
230
|
+
utils.patchToString(HTMLMediaElement.prototype.canPlayType);
|
|
231
|
+
return HTMLMediaElement.prototype.canPlayType.toString();
|
|
232
|
+
});
|
|
233
|
+
expect(test2).toBe('function canPlayType() { [native code] }');
|
|
234
|
+
|
|
235
|
+
// Make sure automatic mode derived from `.name` works with proxies
|
|
236
|
+
const test3 = await withUtils(page).evaluate(utils => {
|
|
237
|
+
HTMLMediaElement.prototype.canPlayType = new Proxy(
|
|
238
|
+
HTMLMediaElement.prototype.canPlayType,
|
|
239
|
+
{}
|
|
240
|
+
);
|
|
241
|
+
utils.patchToString(HTMLMediaElement.prototype.canPlayType);
|
|
242
|
+
return HTMLMediaElement.prototype.canPlayType.toString();
|
|
243
|
+
});
|
|
244
|
+
expect(test3).toBe('function canPlayType() { [native code] }');
|
|
245
|
+
|
|
246
|
+
// Actually verify there's an issue when using vanilla Proxies
|
|
247
|
+
const test4 = await withUtils(page).evaluate(_utils => {
|
|
248
|
+
HTMLMediaElement.prototype.canPlayType = new Proxy(
|
|
249
|
+
HTMLMediaElement.prototype.canPlayType,
|
|
250
|
+
{}
|
|
251
|
+
);
|
|
252
|
+
return HTMLMediaElement.prototype.canPlayType.toString();
|
|
253
|
+
});
|
|
254
|
+
expect(test4).toBe('function () { [native code] }');
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
function toStringTest(obj) {
|
|
258
|
+
obj = eval(obj); // eslint-disable-line no-eval
|
|
259
|
+
return `
|
|
260
|
+
- obj.toString(): ${obj.toString()}
|
|
261
|
+
- obj.name: ${obj.name}
|
|
262
|
+
- obj.toString + "": ${obj.toString + ''}
|
|
263
|
+
- obj.toString.name: ${obj.toString.name}
|
|
264
|
+
- obj.valueOf + "": ${obj.valueOf + ''}
|
|
265
|
+
- obj.valueOf().name: ${obj.valueOf().name}
|
|
266
|
+
- Object.prototype.toString.apply(obj): ${Object.prototype.toString.apply(obj)}
|
|
267
|
+
- Function.prototype.toString.call(obj): ${Function.prototype.toString.call(
|
|
268
|
+
obj
|
|
269
|
+
)}
|
|
270
|
+
- Function.prototype.valueOf.call(obj) + "": ${
|
|
271
|
+
Function.prototype.valueOf.call(obj) + ''
|
|
272
|
+
}
|
|
273
|
+
- obj.toString === Function.prototype.toString: ${
|
|
274
|
+
obj.toString === Function.prototype.toString
|
|
275
|
+
}
|
|
276
|
+
`.trim();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
test('patchToString: passes all toString tests', async () => {
|
|
280
|
+
const toStringVanilla = await (async () => {
|
|
281
|
+
const browser = await vanillaPuppeteer.launch({ headless: true });
|
|
282
|
+
const page = await browser.newPage();
|
|
283
|
+
return page.evaluate(
|
|
284
|
+
toStringTest,
|
|
285
|
+
'HTMLMediaElement.prototype.canPlayType'
|
|
286
|
+
);
|
|
287
|
+
})();
|
|
288
|
+
const toStringStealth = await (async () => {
|
|
289
|
+
const browser = await vanillaPuppeteer.launch({ headless: true });
|
|
290
|
+
const page = await browser.newPage();
|
|
291
|
+
await withUtils(page).evaluate(utils => {
|
|
292
|
+
HTMLMediaElement.prototype.canPlayType = function canPlayType() {};
|
|
293
|
+
utils.patchToString(HTMLMediaElement.prototype.canPlayType);
|
|
294
|
+
});
|
|
295
|
+
return page.evaluate(
|
|
296
|
+
toStringTest,
|
|
297
|
+
'HTMLMediaElement.prototype.canPlayType'
|
|
298
|
+
);
|
|
299
|
+
})();
|
|
300
|
+
|
|
301
|
+
// Check that the unmodified results are as expected
|
|
302
|
+
expect(toStringVanilla).toBe(
|
|
303
|
+
`
|
|
304
|
+
- obj.toString(): function canPlayType() { [native code] }
|
|
305
|
+
- obj.name: canPlayType
|
|
306
|
+
- obj.toString + "": function toString() { [native code] }
|
|
307
|
+
- obj.toString.name: toString
|
|
308
|
+
- obj.valueOf + "": function valueOf() { [native code] }
|
|
309
|
+
- obj.valueOf().name: canPlayType
|
|
310
|
+
- Object.prototype.toString.apply(obj): [object Function]
|
|
311
|
+
- Function.prototype.toString.call(obj): function canPlayType() { [native code] }
|
|
312
|
+
- Function.prototype.valueOf.call(obj) + "": function canPlayType() { [native code] }
|
|
313
|
+
- obj.toString === Function.prototype.toString: true
|
|
314
|
+
`.trim()
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
// Make sure our customizations leave no trace
|
|
318
|
+
expect(toStringVanilla).toBe(toStringStealth);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
test('patchToString: passes stack trace tests', async () => {
|
|
322
|
+
const toStringStackTrace = () => {
|
|
323
|
+
try {
|
|
324
|
+
Object.create(
|
|
325
|
+
Object.getOwnPropertyDescriptor(Function.prototype, 'toString').get
|
|
326
|
+
).toString();
|
|
327
|
+
} catch (err) {
|
|
328
|
+
return err.stack.split('\n').slice(0, 2).join('|');
|
|
329
|
+
}
|
|
330
|
+
return 'error not thrown';
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
const toStringVanilla = await (async () => {
|
|
334
|
+
const browser = await vanillaPuppeteer.launch({ headless: true });
|
|
335
|
+
const page = await browser.newPage();
|
|
336
|
+
return page.evaluate(toStringStackTrace);
|
|
337
|
+
})();
|
|
338
|
+
const toStringStealth = await (async () => {
|
|
339
|
+
const browser = await vanillaPuppeteer.launch({ headless: true });
|
|
340
|
+
const page = await browser.newPage();
|
|
341
|
+
await withUtils(page).evaluate(utils => {
|
|
342
|
+
HTMLMediaElement.prototype.canPlayType = function canPlayType() {};
|
|
343
|
+
utils.patchToString(HTMLMediaElement.prototype.canPlayType);
|
|
344
|
+
});
|
|
345
|
+
return page.evaluate(toStringStackTrace);
|
|
346
|
+
})();
|
|
347
|
+
|
|
348
|
+
// Check that the unmodified results are as expected
|
|
349
|
+
expect(toStringVanilla).toBe(
|
|
350
|
+
`TypeError: Object prototype may only be an Object or null: undefined| at Object.create (<anonymous>)`.trim()
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
// Make sure our customizations leave no trace
|
|
354
|
+
expect(toStringVanilla).toBe(toStringStealth);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test('patchToString: vanilla has iframe issues', async () => {
|
|
358
|
+
const browser = await vanillaPuppeteer.launch({ headless: true });
|
|
359
|
+
const page = await browser.newPage();
|
|
360
|
+
|
|
361
|
+
// Only patch the main window
|
|
362
|
+
const result = await withUtils(page).evaluate(utils => {
|
|
363
|
+
utils.patchToString(HTMLMediaElement.prototype.canPlayType, 'bob');
|
|
364
|
+
|
|
365
|
+
const iframe = document.createElement('iframe');
|
|
366
|
+
document.body.appendChild(iframe);
|
|
367
|
+
return {
|
|
368
|
+
direct: Function.prototype.toString.call(
|
|
369
|
+
HTMLMediaElement.prototype.canPlayType
|
|
370
|
+
),
|
|
371
|
+
directWithiframe: iframe.contentWindow.Function.prototype.toString.call(
|
|
372
|
+
HTMLMediaElement.prototype.canPlayType
|
|
373
|
+
),
|
|
374
|
+
iframeWithdirect: Function.prototype.toString.call(
|
|
375
|
+
iframe.contentWindow.HTMLMediaElement.prototype.canPlayType
|
|
376
|
+
),
|
|
377
|
+
iframeWithiframe: iframe.contentWindow.Function.prototype.toString.call(
|
|
378
|
+
iframe.contentWindow.HTMLMediaElement.prototype.canPlayType
|
|
379
|
+
),
|
|
380
|
+
};
|
|
381
|
+
});
|
|
382
|
+
expect(result).toEqual({
|
|
383
|
+
direct: 'bob',
|
|
384
|
+
directWithiframe: 'function canPlayType() { [native code] }',
|
|
385
|
+
iframeWithdirect: 'function canPlayType() { [native code] }',
|
|
386
|
+
iframeWithiframe: 'function canPlayType() { [native code] }',
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
test('patchToString: stealth has no iframe issues', async () => {
|
|
391
|
+
const browser = await vanillaPuppeteer.launch({ headless: true });
|
|
392
|
+
const page = await browser.newPage();
|
|
393
|
+
|
|
394
|
+
// Patch all documents including iframes
|
|
395
|
+
await withUtils(page).evaluateOnNewDocument(utils => {
|
|
396
|
+
utils.patchToString(HTMLMediaElement.prototype.canPlayType, 'alice');
|
|
397
|
+
});
|
|
398
|
+
await page.goto('about:blank');
|
|
399
|
+
|
|
400
|
+
const result = await withUtils(page).evaluate(_utils => {
|
|
401
|
+
const iframe = document.createElement('iframe');
|
|
402
|
+
document.body.appendChild(iframe);
|
|
403
|
+
return {
|
|
404
|
+
direct: Function.prototype.toString.call(
|
|
405
|
+
HTMLMediaElement.prototype.canPlayType
|
|
406
|
+
),
|
|
407
|
+
directWithiframe: iframe.contentWindow.Function.prototype.toString.call(
|
|
408
|
+
HTMLMediaElement.prototype.canPlayType
|
|
409
|
+
),
|
|
410
|
+
iframeWithdirect: Function.prototype.toString.call(
|
|
411
|
+
iframe.contentWindow.HTMLMediaElement.prototype.canPlayType
|
|
412
|
+
),
|
|
413
|
+
iframeWithiframe: iframe.contentWindow.Function.prototype.toString.call(
|
|
414
|
+
iframe.contentWindow.HTMLMediaElement.prototype.canPlayType
|
|
415
|
+
),
|
|
416
|
+
};
|
|
417
|
+
});
|
|
418
|
+
expect(result).toEqual({
|
|
419
|
+
direct: 'alice',
|
|
420
|
+
directWithiframe: 'alice',
|
|
421
|
+
iframeWithdirect: 'alice',
|
|
422
|
+
iframeWithiframe: 'alice',
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
test('stripProxyFromErrors: will work correctly', async () => {
|
|
427
|
+
const browser = await vanillaPuppeteer.launch({ headless: true });
|
|
428
|
+
const page = await browser.newPage();
|
|
429
|
+
|
|
430
|
+
const results = await withUtils(page).evaluate(utils => {
|
|
431
|
+
const getStack = prop => {
|
|
432
|
+
try {
|
|
433
|
+
prop.caller(); // Will throw (HTMLMediaElement.prototype.canPlayType.caller)
|
|
434
|
+
return false;
|
|
435
|
+
} catch (err) {
|
|
436
|
+
return err.stack;
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
/** We need traps to show up in the error stack */
|
|
440
|
+
const dummyProxyHandler = {
|
|
441
|
+
get() {
|
|
442
|
+
return utils.cache.Reflect.get(...(arguments || []));
|
|
443
|
+
},
|
|
444
|
+
apply() {
|
|
445
|
+
return utils.cache.Reflect.apply(...arguments);
|
|
446
|
+
},
|
|
447
|
+
};
|
|
448
|
+
const vanillaProxy = new Proxy(
|
|
449
|
+
HTMLMediaElement.prototype.canPlayType,
|
|
450
|
+
dummyProxyHandler
|
|
451
|
+
);
|
|
452
|
+
const stealthProxy = new Proxy(
|
|
453
|
+
HTMLMediaElement.prototype.canPlayType,
|
|
454
|
+
utils.stripProxyFromErrors(dummyProxyHandler)
|
|
455
|
+
);
|
|
456
|
+
|
|
457
|
+
const stacks = {
|
|
458
|
+
vanilla: getStack(HTMLMediaElement.prototype.canPlayType),
|
|
459
|
+
vanillaProxy: getStack(vanillaProxy),
|
|
460
|
+
stealthProxy: getStack(stealthProxy),
|
|
461
|
+
};
|
|
462
|
+
return stacks;
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
// Check that the untouched stuff behaves as expected
|
|
466
|
+
expect(results.vanilla.includes(`TypeError: 'caller'`)).toBe(true);
|
|
467
|
+
expect(results.vanilla.includes(`at Object.get`)).toBe(false);
|
|
468
|
+
|
|
469
|
+
// Regression test: Make sure vanilla JS Proxies leak the stack trace
|
|
470
|
+
expect(results.vanillaProxy.includes(`TypeError: 'caller'`)).toBe(true);
|
|
471
|
+
expect(results.vanillaProxy.includes(`at Object.get`)).toBe(true);
|
|
472
|
+
|
|
473
|
+
// Stealth tests
|
|
474
|
+
expect(results.stealthProxy.includes(`TypeError: 'caller'`)).toBe(true);
|
|
475
|
+
expect(results.stealthProxy.includes(`at Object.get`)).toBe(false);
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
test('replaceProperty: will work without traces', async () => {
|
|
479
|
+
const browser = await vanillaPuppeteer.launch({ headless: true });
|
|
480
|
+
const page = await browser.newPage();
|
|
481
|
+
|
|
482
|
+
const results = await withUtils(page).evaluate(utils => {
|
|
483
|
+
utils.replaceProperty(Object.getPrototypeOf(navigator), 'languages', {
|
|
484
|
+
get: () => ['de-DE'],
|
|
485
|
+
});
|
|
486
|
+
return {
|
|
487
|
+
propNames: Object.getOwnPropertyNames(navigator),
|
|
488
|
+
};
|
|
489
|
+
});
|
|
490
|
+
expect(results.propNames.includes('languages')).toBe(false);
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
test('cache: will prevent leaks through overriding methods', async () => {
|
|
494
|
+
const browser = await vanillaPuppeteer.launch({ headless: true });
|
|
495
|
+
const page = await browser.newPage();
|
|
496
|
+
|
|
497
|
+
const results = await withUtils(page).evaluate(utils => {
|
|
498
|
+
const sniffResults = {
|
|
499
|
+
vanilla: false,
|
|
500
|
+
stealth: false,
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
const vanillaProxy = new Proxy(
|
|
504
|
+
{},
|
|
505
|
+
{
|
|
506
|
+
get() {
|
|
507
|
+
return Reflect.get(...arguments);
|
|
508
|
+
},
|
|
509
|
+
}
|
|
510
|
+
);
|
|
511
|
+
Reflect.get = () => (sniffResults.vanilla = true);
|
|
512
|
+
// trigger get trap
|
|
513
|
+
vanillaProxy.foo; // eslint-disable-line
|
|
514
|
+
|
|
515
|
+
const stealthProxy = new Proxy(
|
|
516
|
+
{},
|
|
517
|
+
{
|
|
518
|
+
get() {
|
|
519
|
+
return utils.cache.Reflect.get(...arguments); // using cached copy
|
|
520
|
+
},
|
|
521
|
+
}
|
|
522
|
+
);
|
|
523
|
+
Reflect.get = () => (sniffResults.stealth = true);
|
|
524
|
+
// trigger get trap
|
|
525
|
+
stealthProxy.foo; // eslint-disable-line
|
|
526
|
+
|
|
527
|
+
return sniffResults;
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
expect(results).toEqual({
|
|
531
|
+
vanilla: true,
|
|
532
|
+
stealth: false,
|
|
533
|
+
});
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
test('replaceWithProxy: will throw prototype errors', async () => {
|
|
537
|
+
const browser = await vanillaPuppeteer.launch({ headless: true });
|
|
538
|
+
const page = await browser.newPage();
|
|
539
|
+
await page.goto('about:blank');
|
|
540
|
+
|
|
541
|
+
const result = await withUtils(page).evaluate(utils => {
|
|
542
|
+
utils.replaceWithProxy(HTMLMediaElement.prototype, 'canPlayType', {});
|
|
543
|
+
|
|
544
|
+
const evalErr = (str = '') => {
|
|
545
|
+
try {
|
|
546
|
+
// eslint-disable-next-line no-eval
|
|
547
|
+
return eval(str);
|
|
548
|
+
} catch (err) {
|
|
549
|
+
return err.toString();
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
return {
|
|
554
|
+
same: evalErr(
|
|
555
|
+
`Object.setPrototypeOf(HTMLMediaElement.prototype.canPlayType, HTMLMediaElement.prototype.canPlayType) + ""`
|
|
556
|
+
),
|
|
557
|
+
sameString: evalErr(
|
|
558
|
+
`Object.setPrototypeOf(Function.prototype.toString, Function.prototype.toString) + ""`
|
|
559
|
+
),
|
|
560
|
+
null: evalErr(
|
|
561
|
+
`Object.setPrototypeOf(Function.prototype.toString, null) + ""`
|
|
562
|
+
),
|
|
563
|
+
undef: evalErr(
|
|
564
|
+
`Object.setPrototypeOf(Function.prototype.toString, undefined) + ""`
|
|
565
|
+
),
|
|
566
|
+
none: evalErr(`Object.setPrototypeOf(Function.prototype.toString) + ""`),
|
|
567
|
+
};
|
|
568
|
+
});
|
|
569
|
+
expect(result).toEqual({
|
|
570
|
+
same: 'TypeError: Cyclic __proto__ value',
|
|
571
|
+
sameString: 'TypeError: Cyclic __proto__ value',
|
|
572
|
+
null: 'TypeError: Cannot convert object to primitive value',
|
|
573
|
+
undef:
|
|
574
|
+
'TypeError: Object prototype may only be an Object or null: undefined',
|
|
575
|
+
none: 'TypeError: Object prototype may only be an Object or null: undefined',
|
|
576
|
+
});
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
test('replaceGetterSetter', async () => {
|
|
580
|
+
const browser = await vanillaPuppeteer.launch({ headless: true });
|
|
581
|
+
const page = await browser.newPage();
|
|
582
|
+
await page.goto('about:blank');
|
|
583
|
+
|
|
584
|
+
const results = await withUtils(page).evaluate(utils => {
|
|
585
|
+
const getDetails = a => ({
|
|
586
|
+
href: a.href,
|
|
587
|
+
typeof: typeof a.href,
|
|
588
|
+
in: 'href' in a,
|
|
589
|
+
keys: Object.keys(a),
|
|
590
|
+
// eslint-disable-next-line no-undef
|
|
591
|
+
prototypeKeys: Object.keys(HTMLAnchorElement.prototype),
|
|
592
|
+
getOwnPropertyNames: Object.getOwnPropertyNames(a),
|
|
593
|
+
prototypeGetOwnPropertyNames: Object.getOwnPropertyNames(
|
|
594
|
+
// eslint-disable-next-line no-undef
|
|
595
|
+
HTMLAnchorElement.prototype
|
|
596
|
+
),
|
|
597
|
+
ownPropertyDescriptor:
|
|
598
|
+
undefined === Object.getOwnPropertyDescriptor(a, 'href'),
|
|
599
|
+
prototypeOwnPropertyDescriptor: Object.getOwnPropertyDescriptor(
|
|
600
|
+
// eslint-disable-next-line no-undef
|
|
601
|
+
HTMLAnchorElement.prototype,
|
|
602
|
+
'href'
|
|
603
|
+
),
|
|
604
|
+
ownPropertyDescriptors: Object.getOwnPropertyDescriptors(a, 'href'),
|
|
605
|
+
prototypeOwnPropertyDescriptors: Object.getOwnPropertyDescriptors(
|
|
606
|
+
// eslint-disable-next-line no-undef
|
|
607
|
+
HTMLAnchorElement.prototype,
|
|
608
|
+
'href'
|
|
609
|
+
),
|
|
610
|
+
getToString: Object.getOwnPropertyDescriptor(
|
|
611
|
+
// eslint-disable-next-line no-undef
|
|
612
|
+
HTMLAnchorElement.prototype,
|
|
613
|
+
'href'
|
|
614
|
+
).get.toString(),
|
|
615
|
+
setToString: Object.getOwnPropertyDescriptor(
|
|
616
|
+
// eslint-disable-next-line no-undef
|
|
617
|
+
HTMLAnchorElement.prototype,
|
|
618
|
+
'href'
|
|
619
|
+
).set.toString(),
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
// Use native a.href.
|
|
623
|
+
const a1 = document.createElement('a');
|
|
624
|
+
a1.href = 'http://foo.com/';
|
|
625
|
+
const details1 = getDetails(a1);
|
|
626
|
+
|
|
627
|
+
// Override a.href.
|
|
628
|
+
let href = '';
|
|
629
|
+
// eslint-disable-next-line no-undef
|
|
630
|
+
utils.replaceGetterSetter(HTMLAnchorElement.prototype, 'href', {
|
|
631
|
+
get: () => href,
|
|
632
|
+
set: newValue => {
|
|
633
|
+
href = newValue;
|
|
634
|
+
},
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
// Use overrided a.href.
|
|
638
|
+
const a2 = document.createElement('a');
|
|
639
|
+
a2.href = 'http://foo.com/';
|
|
640
|
+
const details2 = getDetails(a2);
|
|
641
|
+
|
|
642
|
+
return [details1, details2];
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
expect(results[1]).toEqual(results[0]);
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
test('arrayEquals', async () => {
|
|
649
|
+
const browser = await vanillaPuppeteer.launch({ headless: true });
|
|
650
|
+
const page = await browser.newPage();
|
|
651
|
+
await page.goto('about:blank');
|
|
652
|
+
|
|
653
|
+
const results = await withUtils(page).evaluate(utils => {
|
|
654
|
+
const obj = { foo: 'bar' };
|
|
655
|
+
return {
|
|
656
|
+
a: utils.arrayEquals(['a', 'Alpha'], ['a', 'Alpha']),
|
|
657
|
+
b: !utils.arrayEquals(['b', 'Beta'], ['b', 'Blue']),
|
|
658
|
+
c: !utils.arrayEquals(['c', { foo: 'bar' }], ['c', { foo: 'bar' }]),
|
|
659
|
+
d: utils.arrayEquals(['d', obj], ['d', obj]),
|
|
660
|
+
e: utils.arrayEquals([null], [null]),
|
|
661
|
+
f: utils.arrayEquals([undefined], [undefined]),
|
|
662
|
+
g: utils.arrayEquals([false], [false]),
|
|
663
|
+
};
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
expect(results).toEqual({
|
|
667
|
+
a: true,
|
|
668
|
+
b: true,
|
|
669
|
+
c: true,
|
|
670
|
+
d: true,
|
|
671
|
+
e: true,
|
|
672
|
+
f: true,
|
|
673
|
+
g: true,
|
|
674
|
+
});
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
test('memoize', async () => {
|
|
678
|
+
const browser = await vanillaPuppeteer.launch({ headless: true });
|
|
679
|
+
const page = await browser.newPage();
|
|
680
|
+
await page.goto('about:blank');
|
|
681
|
+
|
|
682
|
+
const results = await withUtils(page).evaluate(utils => {
|
|
683
|
+
const objectify = utils.memoize((valueAdded, _valueIgnored) => {
|
|
684
|
+
return { valueAdded };
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
const obj = { foo: 'bar' };
|
|
688
|
+
/* eslint-disable no-self-compare */
|
|
689
|
+
return {
|
|
690
|
+
a: objectify('a', 'Alpha') === objectify('a', 'Alpha'),
|
|
691
|
+
b: objectify('b', 'Beta') !== objectify('b', 'Blue'),
|
|
692
|
+
c: objectify('c', { foo: 'bar' }) !== objectify('c', { foo: 'bar' }),
|
|
693
|
+
d: objectify('d', obj) === objectify('d', obj),
|
|
694
|
+
e: objectify(null) === objectify(null),
|
|
695
|
+
f: objectify(undefined) === objectify(undefined),
|
|
696
|
+
g: objectify(false) === objectify(false),
|
|
697
|
+
};
|
|
698
|
+
/* eslint-enable no-self-compare */
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
expect(results).toEqual({
|
|
702
|
+
a: true,
|
|
703
|
+
b: true,
|
|
704
|
+
c: true,
|
|
705
|
+
d: true,
|
|
706
|
+
e: true,
|
|
707
|
+
f: true,
|
|
708
|
+
g: true,
|
|
709
|
+
});
|
|
710
|
+
});
|