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
This commit is contained in:
Ryan Hunt 2021-09-09 16:20:07 +00:00
Родитель 23fa492341
Коммит 81a19d221d
3 изменённых файлов: 217 добавлений и 129 удалений

Просмотреть файл

@ -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();
});

Просмотреть файл

@ -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);
}

Просмотреть файл

@ -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();
});