From 81a19d221d5df78706072a7f3d37d2eb081a8f21 Mon Sep 17 00:00:00 2001 From: Ryan Hunt Date: Thu, 9 Sep 2021 16:20:07 +0000 Subject: [PATCH] Bug 1721686 - Add test for exposed interfaces on WebAssembly namespace. r=smaug This commit modifies test_interfaces.js to also test the exposed interfaces in the WebAssembly namespace. We have conditional features that we'd like to have confidence that we're not accidentally exposing to web content. Currently there are: * WebAssembly exceptions, enabled only in nightly with a default-off pref * mozIntGemm accelerator function, available only in system or addon principals Depends on D120731 Differential Revision: https://phabricator.services.mozilla.com/D120732 --- .../test/test_serviceworker_interfaces.js | 107 ++++++++++------ .../mochitest/general/test_interfaces.js | 119 ++++++++++------- dom/workers/test/test_worker_interfaces.js | 120 +++++++++++------- 3 files changed, 217 insertions(+), 129 deletions(-) diff --git a/dom/serviceworkers/test/test_serviceworker_interfaces.js b/dom/serviceworkers/test/test_serviceworker_interfaces.js index 4ae800b126f8..7f64e273b3ea 100644 --- a/dom/serviceworkers/test/test_serviceworker_interfaces.js +++ b/dom/serviceworkers/test/test_serviceworker_interfaces.js @@ -19,6 +19,28 @@ // // See createInterfaceMap() below for a complete list of properties. +// IMPORTANT: Do not change this list without review from +// a JavaScript Engine peer! +var wasmGlobalEntry = { + name: "WebAssembly", + insecureContext: true, + disabled: !getJSTestingFunctions().wasmIsSupportedByHardware(), +}; +var wasmGlobalInterfaces = [ + { name: "Module", insecureContext: true }, + { name: "Instance", insecureContext: true }, + { name: "Memory", insecureContext: true }, + { name: "Table", insecureContext: true }, + { name: "Global", insecureContext: true }, + { name: "CompileError", insecureContext: true }, + { name: "LinkError", insecureContext: true }, + { name: "RuntimeError", insecureContext: true }, + { + name: "Function", + insecureContext: true, + nightly: true, + }, +]; // IMPORTANT: Do not change this list without review from // a JavaScript Engine peer! var ecmaGlobals = [ @@ -76,7 +98,7 @@ var ecmaGlobals = [ "WeakMap", "WeakRef", "WeakSet", - { name: "WebAssembly", optional: true }, + wasmGlobalEntry, ]; // IMPORTANT: Do not change the list above without review from // a JavaScript Engine peer! @@ -265,16 +287,37 @@ var interfaceNamesInGlobalScope = [ ]; // IMPORTANT: Do not change the list above without review from a DOM peer! -function createInterfaceMap({ - isNightly, - isEarlyBetaOrEarlier, - isRelease, - isDesktop, - isAndroid, - isInsecureContext, - isFennec, - isCrossOriginIsolated, -}) { +function entryDisabled( + entry, + { + isNightly, + isEarlyBetaOrEarlier, + isRelease, + isDesktop, + isAndroid, + isInsecureContext, + isFennec, + isCrossOriginIsolated, + } +) { + return ( + entry.nightly === !isNightly || + (entry.nightlyAndroid === !(isAndroid && isNightly) && isAndroid) || + (entry.nonReleaseAndroid === !(isAndroid && !isRelease) && isAndroid) || + entry.desktop === !isDesktop || + (entry.android === !isAndroid && + !entry.nonReleaseAndroid && + !entry.nightlyAndroid) || + entry.fennecOrDesktop === (isAndroid && !isFennec) || + entry.fennec === !isFennec || + entry.release === !isRelease || + entry.earlyBetaOrEarlier === !isEarlyBetaOrEarlier || + entry.crossOriginIsolated === !isCrossOriginIsolated || + entry.disabled + ); +} + +function createInterfaceMap(data, ...interfaceGroups) { var interfaceMap = {}; function addInterfaces(interfaces) { @@ -283,22 +326,7 @@ function createInterfaceMap({ interfaceMap[entry] = true; } else { ok(!("pref" in entry), "Bogus pref annotation for " + entry.name); - if ( - entry.nightly === !isNightly || - (entry.nightlyAndroid === !(isAndroid && isNightly) && isAndroid) || - (entry.nonReleaseAndroid === !(isAndroid && !isRelease) && - isAndroid) || - entry.desktop === !isDesktop || - (entry.android === !isAndroid && - !entry.nonReleaseAndroid && - !entry.nightlyAndroid) || - entry.fennecOrDesktop === (isAndroid && !isFennec) || - entry.fennec === !isFennec || - entry.release === !isRelease || - entry.earlyBetaOrEarlier === !isEarlyBetaOrEarlier || - entry.crossOriginIsolated === !isCrossOriginIsolated || - entry.disabled - ) { + if (entryDisabled(entry, data)) { interfaceMap[entry.name] = false; } else if (entry.optional) { interfaceMap[entry.name] = "optional"; @@ -309,15 +337,16 @@ function createInterfaceMap({ } } - addInterfaces(ecmaGlobals); - addInterfaces(interfaceNamesInGlobalScope); + for (let interfaceGroup of interfaceGroups) { + addInterfaces(interfaceGroup); + } return interfaceMap; } -function runTest(data) { - var interfaceMap = createInterfaceMap(data); - for (var name of Object.getOwnPropertyNames(self)) { +function runTest(parentName, parent, data, ...interfaceGroups) { + var interfaceMap = createInterfaceMap(data, ...interfaceGroups); + for (var name of Object.getOwnPropertyNames(parent)) { // An interface name should start with an upper case character. if (!/^[A-Z]/.test(name)) { continue; @@ -326,7 +355,9 @@ function runTest(data) { interfaceMap[name] === "optional" || interfaceMap[name], "If this is failing: DANGER, are you sure you want to expose the new interface " + name + - " to all webpages as a property on the service worker? Do not make a change to this file without a " + + " to all webpages as a property on " + + parentName + + "? Do not make a change to this file without a " + " review from a DOM peer for that specific change!!! (or a JS peer for changes to ecmaGlobals)" ); delete interfaceMap[name]; @@ -336,11 +367,12 @@ function runTest(data) { delete interfaceMap[name]; } else { ok( - name in self === interfaceMap[name], + name in parent === interfaceMap[name], name + " should " + (interfaceMap[name] ? "" : " NOT") + - " be defined on the global scope" + " be defined on " + + parentName ); if (!interfaceMap[name]) { delete interfaceMap[name]; @@ -356,6 +388,9 @@ function runTest(data) { } workerTestGetHelperData(function(data) { - runTest(data); + runTest("self", self, data, ecmaGlobals, interfaceNamesInGlobalScope); + if (WebAssembly && !entryDisabled(wasmGlobalEntry, data)) { + runTest("WebAssembly", WebAssembly, data, wasmGlobalInterfaces); + } workerTestDone(); }); diff --git a/dom/tests/mochitest/general/test_interfaces.js b/dom/tests/mochitest/general/test_interfaces.js index 092b3a839efd..631240cea2e9 100644 --- a/dom/tests/mochitest/general/test_interfaces.js +++ b/dom/tests/mochitest/general/test_interfaces.js @@ -47,6 +47,28 @@ const isFennec = ).isFennec; const isCrossOriginIsolated = window.crossOriginIsolated; +// IMPORTANT: Do not change this list without review from +// a JavaScript Engine peer! +var wasmGlobalEntry = { + name: "WebAssembly", + insecureContext: true, + disabled: !SpecialPowers.Cu.getJSTestingFunctions().wasmIsSupportedByHardware(), +}; +var wasmGlobalInterfaces = [ + { name: "Module", insecureContext: true }, + { name: "Instance", insecureContext: true }, + { name: "Memory", insecureContext: true }, + { name: "Table", insecureContext: true }, + { name: "Global", insecureContext: true }, + { name: "CompileError", insecureContext: true }, + { name: "LinkError", insecureContext: true }, + { name: "RuntimeError", insecureContext: true }, + { + name: "Function", + insecureContext: true, + nightly: true, + }, +]; // IMPORTANT: Do not change this list without review from // a JavaScript Engine peer! var ecmaGlobals = [ @@ -105,11 +127,7 @@ var ecmaGlobals = [ { name: "WeakMap", insecureContext: true }, { name: "WeakRef", insecureContext: true }, { name: "WeakSet", insecureContext: true }, - { - name: "WebAssembly", - insecureContext: true, - disabled: !SpecialPowers.Cu.getJSTestingFunctions().wasmIsSupportedByHardware(), - }, + wasmGlobalEntry, ]; // IMPORTANT: Do not change the list above without review from // a JavaScript Engine peer! @@ -1381,7 +1399,33 @@ var interfaceNamesInGlobalScope = [ ]; // IMPORTANT: Do not change the list above without review from a DOM peer! -function createInterfaceMap() { +function entryDisabled(entry) { + return ( + entry.nightly === !isNightly || + (entry.nightlyAndroid === !(isAndroid && isNightly) && isAndroid) || + entry.desktop === !isDesktop || + entry.windows === !isWindows || + entry.mac === !isMac || + entry.linux === !isLinux || + (entry.android === !isAndroid && !entry.nightlyAndroid) || + entry.fennecOrDesktop === (isAndroid && !isFennec) || + entry.fennec === !isFennec || + entry.release === !isRelease || + entry.releaseNonWindowsAndMac === !(isRelease && !isWindows && !isMac) || + entry.releaseNonWindows === !(isRelease && !isWindows) || + // The insecureContext test is very purposefully converting + // entry.insecureContext to boolean, so undefined will convert to + // false. That way entries without an insecureContext annotation + // will get treated as "insecureContext: false", which means exposed + // only in secure contexts. + (isInsecureContext && !entry.insecureContext) || + entry.earlyBetaOrEarlier === !isEarlyBetaOrEarlier || + entry.crossOriginIsolated === !isCrossOriginIsolated || + entry.disabled + ); +} + +function createInterfaceMap(...interfaceGroups) { var interfaceMap = {}; function addInterfaces(interfaces) { @@ -1390,47 +1434,21 @@ function createInterfaceMap() { interfaceMap[entry] = !isInsecureContext; } else { ok(!("pref" in entry), "Bogus pref annotation for " + entry.name); - if ( - entry.nightly === !isNightly || - (entry.nightlyAndroid === !(isAndroid && isNightly) && isAndroid) || - entry.desktop === !isDesktop || - entry.windows === !isWindows || - entry.mac === !isMac || - entry.linux === !isLinux || - (entry.android === !isAndroid && !entry.nightlyAndroid) || - entry.fennecOrDesktop === (isAndroid && !isFennec) || - entry.fennec === !isFennec || - entry.release === !isRelease || - entry.releaseNonWindowsAndMac === - !(isRelease && !isWindows && !isMac) || - entry.releaseNonWindows === !(isRelease && !isWindows) || - // The insecureContext test is very purposefully converting - // entry.insecureContext to boolean, so undefined will convert to - // false. That way entries without an insecureContext annotation - // will get treated as "insecureContext: false", which means exposed - // only in secure contexts. - (isInsecureContext && !entry.insecureContext) || - entry.earlyBetaOrEarlier === !isEarlyBetaOrEarlier || - entry.crossOriginIsolated === !isCrossOriginIsolated || - entry.disabled - ) { - interfaceMap[entry.name] = false; - } else { - interfaceMap[entry.name] = true; - } + interfaceMap[entry.name] = !entryDisabled(entry); } } } - addInterfaces(ecmaGlobals); - addInterfaces(interfaceNamesInGlobalScope); + for (let interfaceGroup of interfaceGroups) { + addInterfaces(interfaceGroup); + } return interfaceMap; } -function runTest() { - var interfaceMap = createInterfaceMap(); - for (var name of Object.getOwnPropertyNames(window)) { +function runTest(parentName, parent, ...interfaceGroups) { + var interfaceMap = createInterfaceMap(...interfaceGroups); + for (var name of Object.getOwnPropertyNames(parent)) { // An interface name should start with an upper case character. // However, we have a couple of legacy interfaces that start with 'moz', so // we want to allow those until we can remove them. @@ -1441,28 +1459,32 @@ function runTest() { interfaceMap[name], "If this is failing: DANGER, are you sure you want to expose the new interface " + name + - " to all webpages as a property on the window? Do not make a change to this file without a " + + " to all webpages as a property on '" + + parentName + + "'? Do not make a change to this file without a " + " review from a DOM peer for that specific change!!! (or a JS peer for changes to ecmaGlobals)" ); ok( - name in window, - `${name} is exposed as an own property on the window but tests false for "in" in the global scope` + name in parent, + `${name} is exposed as an own property on '" + parentName + "' but tests false for "in" in the global scope` ); ok( - Object.getOwnPropertyDescriptor(window, name), - `${name} is exposed as an own property on the window but has no property descriptor in the global scope` + Object.getOwnPropertyDescriptor(parent, name), + `${name} is exposed as an own property on '" + parentName + "' but has no property descriptor in the global scope` ); delete interfaceMap[name]; } for (var name of Object.keys(interfaceMap)) { ok( - name in window === interfaceMap[name], + name in parent === interfaceMap[name], name + " should " + (interfaceMap[name] ? "" : " NOT") + - " be defined on the global scope" + " be defined on '" + + parentName + + "' scope" ); if (!interfaceMap[name]) { delete interfaceMap[name]; @@ -1476,4 +1498,7 @@ function runTest() { ); } -runTest(); +runTest("window", window, ecmaGlobals, interfaceNamesInGlobalScope); +if (window.WebAssembly && !entryDisabled(wasmGlobalEntry)) { + runTest("WebAssembly", window.WebAssembly, wasmGlobalInterfaces); +} diff --git a/dom/workers/test/test_worker_interfaces.js b/dom/workers/test/test_worker_interfaces.js index 164315d1764b..9cba35192984 100644 --- a/dom/workers/test/test_worker_interfaces.js +++ b/dom/workers/test/test_worker_interfaces.js @@ -25,6 +25,28 @@ // value needs to depend on channel or OS, we will need to make sure // we have that information before setting up the property lists. +// IMPORTANT: Do not change this list without review from +// a JavaScript Engine peer! +var wasmGlobalEntry = { + name: "WebAssembly", + insecureContext: true, + disabled: !getJSTestingFunctions().wasmIsSupportedByHardware(), +}; +var wasmGlobalInterfaces = [ + { name: "Module", insecureContext: true }, + { name: "Instance", insecureContext: true }, + { name: "Memory", insecureContext: true }, + { name: "Table", insecureContext: true }, + { name: "Global", insecureContext: true }, + { name: "CompileError", insecureContext: true }, + { name: "LinkError", insecureContext: true }, + { name: "RuntimeError", insecureContext: true }, + { + name: "Function", + insecureContext: true, + nightly: true, + }, +]; // IMPORTANT: Do not change this list without review from // a JavaScript Engine peer! var ecmaGlobals = [ @@ -85,11 +107,7 @@ var ecmaGlobals = [ { name: "WeakMap", insecureContext: true }, { name: "WeakRef", insecureContext: true }, { name: "WeakSet", insecureContext: true }, - { - name: "WebAssembly", - insecureContext: true, - disabled: !getJSTestingFunctions().wasmIsSupportedByHardware(), - }, + wasmGlobalEntry, ]; // IMPORTANT: Do not change the list above without review from // a JavaScript Engine peer! @@ -293,16 +311,40 @@ var interfaceNamesInGlobalScope = [ ]; // IMPORTANT: Do not change the list above without review from a DOM peer! -function createInterfaceMap({ - isNightly, - isEarlyBetaOrEarlier, - isRelease, - isDesktop, - isAndroid, - isInsecureContext, - isFennec, - isCrossOringinIsolated, -}) { +function entryDisabled( + entry, + { + isNightly, + isEarlyBetaOrEarlier, + isRelease, + isDesktop, + isAndroid, + isInsecureContext, + isFennec, + isCrossOringinIsolated, + } +) { + return ( + entry.nightly === !isNightly || + (entry.nightlyAndroid === !(isAndroid && isNightly) && isAndroid) || + entry.desktop === !isDesktop || + (entry.android === !isAndroid && !entry.nightlyAndroid) || + entry.fennecOrDesktop === (isAndroid && !isFennec) || + entry.fennec === !isFennec || + entry.release === !isRelease || + // The insecureContext test is very purposefully converting + // entry.insecureContext to boolean, so undefined will convert to + // false. That way entries without an insecureContext annotation + // will get treated as "insecureContext: false", which means exposed + // only in secure contexts. + (isInsecureContext && !entry.insecureContext) || + entry.earlyBetaOrEarlier === !isEarlyBetaOrEarlier || + entry.crossOringinIsolated === !isCrossOringinIsolated || + entry.disabled + ); +} + +function createInterfaceMap(data, ...interfaceGroups) { var interfaceMap = {}; function addInterfaces(interfaces) { @@ -311,41 +353,21 @@ function createInterfaceMap({ interfaceMap[entry] = !isInsecureContext; } else { ok(!("pref" in entry), "Bogus pref annotation for " + entry.name); - if ( - entry.nightly === !isNightly || - (entry.nightlyAndroid === !(isAndroid && isNightly) && isAndroid) || - entry.desktop === !isDesktop || - (entry.android === !isAndroid && !entry.nightlyAndroid) || - entry.fennecOrDesktop === (isAndroid && !isFennec) || - entry.fennec === !isFennec || - entry.release === !isRelease || - // The insecureContext test is very purposefully converting - // entry.insecureContext to boolean, so undefined will convert to - // false. That way entries without an insecureContext annotation - // will get treated as "insecureContext: false", which means exposed - // only in secure contexts. - (isInsecureContext && !entry.insecureContext) || - entry.earlyBetaOrEarlier === !isEarlyBetaOrEarlier || - entry.crossOringinIsolated === !isCrossOringinIsolated || - entry.disabled - ) { - interfaceMap[entry.name] = false; - } else { - interfaceMap[entry.name] = true; - } + interfaceMap[entry.name] = !entryDisabled(entry, data); } } } - addInterfaces(ecmaGlobals); - addInterfaces(interfaceNamesInGlobalScope); + for (let interfaceGroup of interfaceGroups) { + addInterfaces(interfaceGroup); + } return interfaceMap; } -function runTest(data) { - var interfaceMap = createInterfaceMap(data); - for (var name of Object.getOwnPropertyNames(self)) { +function runTest(parentName, parent, data, ...interfaceGroups) { + var interfaceMap = createInterfaceMap(data, ...interfaceGroups); + for (var name of Object.getOwnPropertyNames(parent)) { // An interface name should start with an upper case character. if (!/^[A-Z]/.test(name)) { continue; @@ -354,18 +376,21 @@ function runTest(data) { interfaceMap[name], "If this is failing: DANGER, are you sure you want to expose the new interface " + name + - " to all webpages as a property on the worker? Do not make a change to this file without a " + + " to all webpages as a property of " + + parentName + + "? Do not make a change to this file without a " + " review from a DOM peer for that specific change!!! (or a JS peer for changes to ecmaGlobals)" ); delete interfaceMap[name]; } for (var name of Object.keys(interfaceMap)) { ok( - name in self === interfaceMap[name], + name in parent === interfaceMap[name], name + " should " + (interfaceMap[name] ? "" : " NOT") + - " be defined on the global scope" + " be defined on " + + parentName ); if (!interfaceMap[name]) { delete interfaceMap[name]; @@ -380,6 +405,9 @@ function runTest(data) { } workerTestGetHelperData(function(data) { - runTest(data); + runTest("self", self, data, ecmaGlobals, interfaceNamesInGlobalScope); + if (WebAssembly && !entryDisabled(wasmGlobalEntry, data)) { + runTest("WebAssembly", WebAssembly, data, wasmGlobalInterfaces); + } workerTestDone(); });