Skip to content

Commit

Permalink
Move back privacy test pages to reduce duplication (#329)
Browse files Browse the repository at this point in the history
* Move back privacy test pages to reduce duplication

* Fix up ts and add CODEOWNERS
  • Loading branch information
jonathanKingston authored Mar 21, 2023
1 parent 6d145db commit 9eb4010
Show file tree
Hide file tree
Showing 13 changed files with 508 additions and 224 deletions.
5 changes: 4 additions & 1 deletion CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,7 @@ src/locales/click-to-load/ @kzar @ladamski
inject/android.js @jonathanKingston @joshliebe
inject/chrome-mv3.js @kzar @sammacbeth
inject/chrome.js @jonathanKingston @sammacbeth
inject/windows.js @jonathanKingston @q71114 @szanto90balazs
inject/windows.js @jonathanKingston @q71114 @szanto90balazs

# Test owners
integration-tests/test-pages/ @kdzwinel @jonathanKingston
1 change: 1 addition & 0 deletions integration-test/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export default {
jsLoader: 'import',
spec_files: [
'**/*.js',
'!test-pages/**/*.js',
'!pages/**/*.js',
'!extension/**/*.js'
],
Expand Down
22 changes: 20 additions & 2 deletions integration-test/helpers/harness.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,29 @@ export async function setup (ops = {}) {
* @returns {http.Server}
*/
function setupServer (port) {
return _startupServerInternal('../pages', port)
}

/**
* @param {number|string} [port]
* @returns {http.Server}
*/
function setupIntegrationPagesServer (port) {
return _startupServerInternal('../test-pages', port)
}

/**
* @param {string} pathName
* @param {number|string} [port]
* @returns {http.Server}
*/
function _startupServerInternal (pathName, port) {
const server = http.createServer(function (req, res) {
const url = new URL(req.url, `http://${req.headers.host}`)
// @ts-expect-error https://app.asana.com/0/1201614831475344/1203979574128023/f
const importUrl = new URL(import.meta.url)
const dirname = importUrl.pathname.replace(/\/[^/]*$/, '')
const pathname = path.join(dirname, '../pages', url.pathname)
const pathname = path.join(dirname, pathName, url.pathname)

fs.readFile(pathname, (err, data) => {
if (err) {
Expand Down Expand Up @@ -118,10 +135,11 @@ export async function setup (ops = {}) {

// wait until contentScopeFeatures.init(args) has completed
await page.waitForFunction(() => {
window.dispatchEvent(new Event('content-scope-init-complete'))
// @ts-expect-error https://app.asana.com/0/1201614831475344/1203979574128023/f
return window.__content_scope_status === 'initialized'
})
}

return { browser, teardown, setupServer, gotoAndWait }
return { browser, teardown, setupServer, setupIntegrationPagesServer, gotoAndWait }
}
85 changes: 85 additions & 0 deletions integration-test/test-pages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/**
* Tests for runtime checks
*/
import { processConfig } from '../src/utils.js'
import { setup } from './helpers/harness.js'
import * as fs from 'fs'

describe('Test integration pages', () => {
let browser
let server
let teardown
let setupIntegrationPagesServer
let gotoAndWait
beforeAll(async () => {
({ browser, setupIntegrationPagesServer, teardown, gotoAndWait } = await setup({ withExtension: true }))
server = setupIntegrationPagesServer()
})
afterAll(async () => {
await server?.close()
await teardown()
})

it('Script that should not execute', async () => {
const pages = {
'runtime-checks/pages/basic-run.html': 'runtime-checks/config/basic-run.json',
'runtime-checks/pages/filter-props.html': 'runtime-checks/config/filter-props.json'
}
for (const pageName in pages) {
const configName = pages[pageName]

const port = server.address().port
const page = await browser.newPage()
const res = fs.readFileSync(process.cwd() + '/integration-test/test-pages/' + configName)
// @ts-expect-error - JSON.parse returns any
const config = JSON.parse(res)
// Pollyfill for globalThis methods needed in processConfig
globalThis.document = {
referrer: 'http://localhost:8080',
location: {
href: 'http://localhost:8080',
// @ts-expect-error - ancestorOrigins is not defined in the type definition
ancestorOrigins: {
length: 0
}
}
}
globalThis.location = {
href: 'http://localhost:8080',
// @ts-expect-error - ancestorOrigins is not defined in the type definition
ancestorOrigins: {
length: 0
}
}

const processedConfig = processConfig(config, /* userList */ [], /* preferences */ {}/*, platformSpecificFeatures = [] */)

await gotoAndWait(page, `http://localhost:${port}/${pageName}?automation=true`, processedConfig)
// Check page results
const pageResults = await page.evaluate(
async () => {
let res
const promise = new Promise(resolve => {
res = resolve
})
// @ts-expect-error - results is not defined in the type definition
if (window.results) {
// @ts-expect-error - results is not defined in the type definition
res(window.results)
} else {
window.addEventListener('results-ready', (e) => {
// @ts-expect-error - e.detail is not defined in the type definition
res(e.detail)
})
}
return promise
}
)
for (const key in pageResults) {
for (const result of pageResults[key]) {
expect(result.result).withContext(key + ':\n ' + result.name).toEqual(result.expected)
}
}
}
})
})
19 changes: 19 additions & 0 deletions integration-test/test-pages/runtime-checks/config/basic-run.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"features": {
"runtimeChecks": {
"state": "enabled",
"exceptions": [],
"settings": {
"taintCheck": "enabled",
"matchAllDomains": "enabled",
"matchAllStackDomains": "enabled",
"overloadInstanceOf": "enabled",
"elementRemovalTimeout": 1000,
"domains": [
],
"stackDomains": [
]
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"features": {
"runtimeChecks": {
"state": "enabled",
"exceptions": [],
"settings": {
"taintCheck": "enabled",
"matchAllDomains": "enabled",
"matchAllStackDomains": "enabled",
"overloadInstanceOf": "enabled",
"domains": [
],
"stackDomains": [
],
"tagModifiers": {
"script": {
"filters": {
"property": ["madeUpProp1", "madeUpProp3"],
"attribute": ["madeupattr1", "madeupattr3"]
}
}
}
}
}
}
}
18 changes: 18 additions & 0 deletions integration-test/test-pages/runtime-checks/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Runtime checks</title>
</head>
<body>
<p><a href="../../index.html">[Home]</a></p>

<p>Runtime element interrogation (runtimeChecks) allows our clients the ability to validate, inspect and modify elements as they get injected into a web page by website scripts.</p>
<ul>
<li><a href="./pages/basic-run.html">Basic Run</a> - <a href="./config/basic-run.json">Config</a></li>
<li><a href="./pages/filter-props.html">Filter props</a> - <a href="./config/filter-props.json">Config</a></li>
</ul>

</body>
</html>
119 changes: 119 additions & 0 deletions integration-test/test-pages/runtime-checks/pages/basic-run.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Runtime checks</title>
<link rel="stylesheet" href="../../shared/style.css">
</head>
<body>
<script src="../../shared/utils.js"></script>
<p><a href="../index.html">[Runtime checks]</a></p>

<p>This page verifies that runtime checking is enabled given the corresponding <a href="../config/basic-run.json">config</a></p>

<script>
// eslint-disable-next-line no-undef
test('Script that should not execute', async () => {
window.scripty1Ran = false;
const scriptElement = document.createElement('script');
scriptElement.innerText = 'window.scripty1Ran = true';
scriptElement.id = 'scripty';
scriptElement.setAttribute('type', 'application/evilscript');
document.body.appendChild(scriptElement);
const hadInspectorNode = scriptElement === document.querySelector('ddg-runtime-checks:last-of-type');
// Continue to modify the script element after it has been added to the DOM
scriptElement.integrity = 'sha256-123';
scriptElement.madeUpProp = 'val';
const instanceofResult = scriptElement instanceof HTMLScriptElement;
const scripty = document.querySelector('#scripty');

return [
{ name: 'hadInspectorNode', result: hadInspectorNode, expected: true },
{ name: 'expect script to match', result: scripty, expected: scriptElement },
{ name: 'instanceof matches HTMLScriptElement', result: instanceofResult, expected: true },
{ name: 'scripty.integrity', result: scripty.integrity, expected: 'sha256-123' },
{ name: 'scripty.madeUpProp', result: scripty.madeUpProp, expected: 'val' },
{ name: 'scripty.type', result: scripty.type, expected: 'application/evilscript' },
{ name: 'scripty.id', result: scripty.id, expected: 'scripty' },
{ name: 'script ran', result: window.scripty1Ran, expected: false }
];
});

// eslint-disable-next-line no-undef
test('Script that should execute', async () => {
window.scripty2Ran = false;
const scriptElement = document.createElement('script');
scriptElement.innerText = 'window.scripty2Ran = true';
scriptElement.id = 'scripty2';
scriptElement.setAttribute('type', 'application/javascript');
document.body.appendChild(scriptElement);
const hadInspectorNode = scriptElement === document.querySelector('ddg-runtime-checks:last-of-type');
// Continue to modify the script element after it has been added to the DOM
scriptElement.madeUpProp = 'val';
const instanceofResult = scriptElement instanceof HTMLScriptElement;
const scripty = document.querySelector('#scripty2');

return [
{ name: 'hadInspectorNode', result: hadInspectorNode, expected: true },
{ name: 'expect script to match', result: scripty, expected: scriptElement },
{ name: 'instanceof matches HTMLScriptElement', result: instanceofResult, expected: true },
{ name: 'scripty.madeUpProp', result: scripty.madeUpProp, expected: 'val' },
{ name: 'scripty.type', result: scripty.type, expected: 'application/javascript' },
{ name: 'scripty.id', result: scripty.id, expected: 'scripty2' },
{ name: 'script ran', result: window.scripty2Ran, expected: true }
];
});

// eslint-disable-next-line no-undef
test('Invalid external script should trigger error listeners', async () => {
const scriptElement = document.createElement('script');
scriptElement.id = 'scripty3';
scriptElement.src = 'invalid://url';
scriptElement.setAttribute('type', 'application/javascript');

let listenerCount = 0;
let resolver = null;
const promise = new Promise(resolve => {
resolver = resolve;
});
scriptElement.onerror = () => {
listenerCount++;
resolver();
};

let resolver2 = null;
const promise2 = new Promise(resolve => {
resolver2 = resolve;
});
scriptElement.addEventListener('error', () => {
listenerCount++;
resolver2();
});

document.body.appendChild(scriptElement);
await Promise.all([promise, promise2]);

const hadInspectorNode = scriptElement === document.querySelector('ddg-runtime-checks:last-of-type');
// Continue to modify the script element after it has been added to the DOM
// @ts-expect-error https://app.asana.com/0/1201614831475344/1203979574128023/f
scriptElement.madeUpProp = 'val';
const instanceofResult = scriptElement instanceof HTMLScriptElement;
const scripty = document.querySelector('#scripty3');

return [
{ name: 'listenerCount', result: listenerCount, expected: 2 },
{ name: 'hadInspectorNode', result: hadInspectorNode, expected: true },
{ name: 'instanceof matches HTMLScriptElement', result: instanceofResult, expected: true },
{ name: 'scripty.madeUpProp', result: scripty.madeUpProp, expected: 'val' },
{ name: 'scripty.type', result: scripty.type, expected: 'application/javascript' },
{ name: 'scripty.id', result: scripty.id, expected: 'scripty3' },
{ name: 'scripty.src', result: scripty.src, expected: 'invalid://url' }
];
});

// eslint-disable-next-line no-undef
renderResults();
</script>
</body>
</html>
Loading

0 comments on commit 9eb4010

Please sign in to comment.