Bug 2067200 - Return an empty WebAssembly binary module if scripting is disabled. r=spidermonkey-reviewers,frontend-codestyle-reviewers,dminor
Implement https://html.spec.whatwg.org/#creating-a-webassembly-module-script Differential Revision: https://phabricator.services.mozilla.com/D323657
This commit is contained in:
committed by
allstars.chh@gmail.com
parent
de14c16b69
commit
28e5b605fa
Binary file not shown.
@@ -15,6 +15,8 @@ support-files = [
|
||||
|
||||
["test_scriptDisabled_modules.html"]
|
||||
support-files = [
|
||||
"!/dom/base/test/file_wasm_offthread_module.sjs",
|
||||
"exports-fun.wasm",
|
||||
"module_large1.mjs",
|
||||
"scriptDisabledDiskCache_module.sjs",
|
||||
"scriptDisabled_frame.html",
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
// returned promise: it can never settle, and nothing settles it afterwards.
|
||||
window.importModule = url => import(url);
|
||||
|
||||
window.importSource = url => import.source(url);
|
||||
|
||||
window.startModuleScript = url => {
|
||||
const script = document.createElement("script");
|
||||
script.type = "module";
|
||||
|
||||
@@ -9,6 +9,11 @@
|
||||
const SIMPLE_MODULE = "./scriptDisabled_module.mjs";
|
||||
const CACHEABLE_MODULE = "./scriptDisabledDiskCache_module.sjs";
|
||||
const LARGE_MODULE = "./module_large1.mjs";
|
||||
const WASM_MODULE = "./exports-fun.wasm";
|
||||
|
||||
// Serve a large off-the-main-thread-compiled wasm module.
|
||||
const LARGE_WASM_MODULE = "../file_wasm_offthread_module.sjs";
|
||||
const WASM_ESM_PREF = "javascript.options.experimental.wasm_esm_integration";
|
||||
|
||||
function addFrame() {
|
||||
return new Promise(resolve => {
|
||||
@@ -223,5 +228,161 @@ add_task(async function moduleWithMemoryCacheEntry() {
|
||||
frame.remove();
|
||||
await SpecialPowers.popPrefEnv();
|
||||
});
|
||||
|
||||
// https://html.spec.whatwg.org/#creating-a-webassembly-module-script
|
||||
// Step 1. If scripting is disabled for settings, then set bodyBytes to the byte
|
||||
// sequence 0x00 0x61 0x73 0x6D 0x01 0x00 0x00 0x00.
|
||||
add_task(async function wasmModuleCreatedEmpty() {
|
||||
// The WebAssembly ESM integration, and so its pref, only exists on Nightly.
|
||||
if (
|
||||
SpecialPowers.Services.prefs.getPrefType(WASM_ESM_PREF) ==
|
||||
SpecialPowers.Ci.nsIPrefBranch.PREF_INVALID
|
||||
) {
|
||||
info("skipped: the WebAssembly ESM integration is not available");
|
||||
return;
|
||||
}
|
||||
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
[WASM_ESM_PREF, true],
|
||||
["javascript.options.experimental.source_phase_imports", true],
|
||||
],
|
||||
});
|
||||
|
||||
const countExports = frame =>
|
||||
SpecialPowers.spawn(frame, [WASM_MODULE], async url => {
|
||||
const source = await content.wrappedJSObject.importSource(url);
|
||||
return content.wrappedJSObject.WebAssembly.Module.exports(source).length;
|
||||
});
|
||||
|
||||
// Control, in its own frame so that it gets its own module map: the module
|
||||
// served is not empty to begin with.
|
||||
const control = await addFrame();
|
||||
is(
|
||||
await countExports(control),
|
||||
1,
|
||||
"the module has an export when script is enabled"
|
||||
);
|
||||
control.remove();
|
||||
|
||||
const frame = await addFrame();
|
||||
|
||||
// Start an import and block script in the same task.
|
||||
await SpecialPowers.spawn(frame, [WASM_MODULE], async url => {
|
||||
const fileName = url.split("/").pop();
|
||||
const loadGroup =
|
||||
content.docShell.QueryInterface(Ci.nsIDocumentLoader).loadGroup;
|
||||
const moduleIsFetching = () => {
|
||||
for (const request of loadGroup.requests) {
|
||||
if (request.name.endsWith(fileName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
content.wrappedJSObject.importSource(url);
|
||||
Cu.blockScriptForGlobal(content);
|
||||
try {
|
||||
ok(moduleIsFetching(), "the module's fetch is in flight");
|
||||
await ContentTaskUtils.waitForCondition(
|
||||
() => !moduleIsFetching(),
|
||||
"the module's fetch completed while script was disabled"
|
||||
);
|
||||
} finally {
|
||||
Cu.unblockScriptForGlobal(content);
|
||||
}
|
||||
});
|
||||
|
||||
// The empty wasm module is in this realm's module map, so importing again
|
||||
// returns it rather than fetching.
|
||||
is(
|
||||
await countExports(frame),
|
||||
0,
|
||||
"the module was created as an empty WebAssembly module"
|
||||
);
|
||||
|
||||
frame.remove();
|
||||
await SpecialPowers.popPrefEnv();
|
||||
});
|
||||
|
||||
// Only source phase wasm modules at or above OffThreadMinimumWasmLength are
|
||||
// compiled off the main thread. Creating the empty module has to discard that
|
||||
// pending compilation rather than adopt its result.
|
||||
add_task(async function wasmModuleCompiledOffMainThreadCreatedEmpty() {
|
||||
if (
|
||||
SpecialPowers.Services.prefs.getPrefType(WASM_ESM_PREF) ==
|
||||
SpecialPowers.Ci.nsIPrefBranch.PREF_INVALID
|
||||
) {
|
||||
info("skipped: the WebAssembly ESM integration is not available");
|
||||
return;
|
||||
}
|
||||
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
[WASM_ESM_PREF, true],
|
||||
["javascript.options.experimental.source_phase_imports", true],
|
||||
["javascript.options.parallel_parsing", true],
|
||||
// Enables the ScriptLoaderTest notifications checked below.
|
||||
["dom.expose_test_interfaces", true],
|
||||
],
|
||||
});
|
||||
|
||||
const frame = await addFrame();
|
||||
|
||||
await SpecialPowers.spawn(frame, [LARGE_WASM_MODULE], async url => {
|
||||
const fileName = url.split("/").pop();
|
||||
const traces = [];
|
||||
const observer = (subject, topic, data) => traces.push(data);
|
||||
Services.obs.addObserver(observer, "ScriptLoaderTest");
|
||||
|
||||
const loadGroup =
|
||||
content.docShell.QueryInterface(Ci.nsIDocumentLoader).loadGroup;
|
||||
const moduleIsFetching = () => {
|
||||
for (const request of loadGroup.requests) {
|
||||
if (request.name.endsWith(fileName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
const traced = event =>
|
||||
traces.some(
|
||||
data => data.includes(`event:${event}`) && data.includes(fileName)
|
||||
);
|
||||
|
||||
content.wrappedJSObject.importSource(url);
|
||||
Cu.blockScriptForGlobal(content);
|
||||
try {
|
||||
ok(moduleIsFetching(), "the module's fetch is in flight");
|
||||
|
||||
// Waiting for the empty wasm module is created.
|
||||
await ContentTaskUtils.waitForCondition(
|
||||
() => traced("compile:wasm empty"),
|
||||
"the empty module was created while script was disabled"
|
||||
);
|
||||
|
||||
ok(
|
||||
traced("compile:off thread"),
|
||||
"the module was compiled off the main thread"
|
||||
);
|
||||
} finally {
|
||||
Cu.unblockScriptForGlobal(content);
|
||||
Services.obs.removeObserver(observer, "ScriptLoaderTest");
|
||||
}
|
||||
|
||||
// The empty module is in this realm's module map, so importing again
|
||||
// returns it rather than fetching.
|
||||
const source = await content.wrappedJSObject.importSource(url);
|
||||
is(
|
||||
content.wrappedJSObject.WebAssembly.Module.exports(source).length,
|
||||
0,
|
||||
"the off-thread compiled module was created as an empty WebAssembly module"
|
||||
);
|
||||
});
|
||||
|
||||
frame.remove();
|
||||
await SpecialPowers.popPrefEnv();
|
||||
});
|
||||
</script>
|
||||
<body></body>
|
||||
|
||||
+53
-20
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "GeckoProfiler.h"
|
||||
#include "ScriptLoader.h"
|
||||
#include "ScriptTrace.h" // TRACE_FOR_TEST
|
||||
#include "js/CompileOptions.h" // JS::CompileOptions, JS::InstantiateOptions
|
||||
#include "js/ContextOptions.h" // JS::ContextOptionsRef
|
||||
#include "js/MemoryFunctions.h"
|
||||
@@ -275,47 +276,79 @@ nsresult ModuleLoader::CompileEmptyJavaScriptModule(
|
||||
return aModuleOut ? NS_OK : NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
#ifdef NIGHTLY_BUILD
|
||||
nsresult ModuleLoader::CompileWasmModuleBytes(
|
||||
JSContext* aCx, JS::CompileOptions& aOptions, ModuleLoadRequest* aRequest,
|
||||
WasmBytesBuffer& aBytes, JS::MutableHandle<JSObject*> aModuleOut) {
|
||||
JSObject* wasmModule;
|
||||
if (aRequest->IsSourcePhaseRequest(aCx)) {
|
||||
wasmModule = JS::CompileWasmModuleAsSource(aCx, aOptions, aBytes);
|
||||
} else {
|
||||
wasmModule = JS::CompileWasmModule(aCx, aOptions, aBytes);
|
||||
}
|
||||
if (!wasmModule) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
aModuleOut.set(wasmModule);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/#creating-a-webassembly-module-script
|
||||
nsresult ModuleLoader::CompileEmptyWasmModule(
|
||||
JSContext* aCx, JS::CompileOptions& aOptions, ModuleLoadRequest* aRequest,
|
||||
JS::MutableHandle<JSObject*> aModuleOut) {
|
||||
TRACE_FOR_TEST(aRequest, "compile:wasm empty");
|
||||
|
||||
// Step 1: If scripting is disabled, set bodyBytes to the byte sequence
|
||||
// 0x00 0x61 0x73 0x6D 0x01 0x00 0x00 0x00
|
||||
static constexpr uint8_t kEmptyWasmModule[] = {0x00, 0x61, 0x73, 0x6D,
|
||||
0x01, 0x00, 0x00, 0x00};
|
||||
|
||||
WasmBytesBuffer bytes;
|
||||
if (!bytes.append(kEmptyWasmModule, sizeof(kEmptyWasmModule))) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
return CompileWasmModuleBytes(aCx, aOptions, aRequest, bytes, aModuleOut);
|
||||
}
|
||||
#endif
|
||||
|
||||
nsresult ModuleLoader::CompileJavaScriptOrWasmModule(
|
||||
JSContext* aCx, JS::Handle<JSObject*> aGlobal, JS::CompileOptions& aOptions,
|
||||
ModuleLoadRequest* aRequest, JS::MutableHandle<JSObject*> aModuleOut) {
|
||||
GetScriptLoader()->CalculateCacheFlag(aRequest);
|
||||
|
||||
if (!nsJSUtils::IsScriptable(aGlobal)) {
|
||||
aRequest->GetScriptLoadContext()->MaybeCancelOffThreadScript();
|
||||
|
||||
#ifdef NIGHTLY_BUILD
|
||||
// TODO: Bug 2067200, Creating an empty WebAssembly module if scripting is
|
||||
// disabled.
|
||||
if (aRequest->HasWasmMimeTypeEssence()) {
|
||||
MOZ_ASSERT(aRequest->IsWasmBytes());
|
||||
return NS_ERROR_FAILURE;
|
||||
return CompileEmptyWasmModule(aCx, aOptions, aRequest, aModuleOut);
|
||||
}
|
||||
#endif
|
||||
|
||||
aRequest->GetScriptLoadContext()->MaybeCancelOffThreadScript();
|
||||
return CompileEmptyJavaScriptModule(aCx, aOptions, aRequest, aModuleOut);
|
||||
}
|
||||
|
||||
#ifdef NIGHTLY_BUILD
|
||||
if (aRequest->HasWasmMimeTypeEssence()) {
|
||||
MOZ_ASSERT(aRequest->IsWasmBytes());
|
||||
if (aRequest->IsSourcePhaseRequest(aCx)) {
|
||||
if (aRequest->GetScriptLoadContext()->mWasCompiledOMT) {
|
||||
if (!aRequest->GetScriptLoadContext()->StealOffThreadWasmResult(
|
||||
aCx, aModuleOut)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
} else {
|
||||
aModuleOut.set(JS::CompileWasmModuleAsSource(aCx, aOptions,
|
||||
aRequest->WasmBytes()));
|
||||
// Only source phase requests are compiled off-thread, and the request's
|
||||
// bytes were moved into the WasmCompileTask, so the result has to be taken
|
||||
// from the task rather than recompiled.
|
||||
if (aRequest->GetScriptLoadContext()->mWasCompiledOMT) {
|
||||
MOZ_ASSERT(aRequest->IsSourcePhaseRequest(aCx));
|
||||
if (!aRequest->GetScriptLoadContext()->StealOffThreadWasmResult(
|
||||
aCx, aModuleOut)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
} else {
|
||||
aModuleOut.set(
|
||||
JS::CompileWasmModule(aCx, aOptions, aRequest->WasmBytes()));
|
||||
}
|
||||
if (!aModuleOut) {
|
||||
return NS_ERROR_FAILURE;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
return CompileWasmModuleBytes(aCx, aOptions, aRequest,
|
||||
aRequest->WasmBytes(), aModuleOut);
|
||||
}
|
||||
#endif
|
||||
MOZ_ASSERT(!aRequest->IsWasmBytes());
|
||||
|
||||
@@ -92,6 +92,17 @@ class ModuleLoader final : public JS::loader::ModuleLoaderBase {
|
||||
nsresult CompileEmptyJavaScriptModule(
|
||||
JSContext* aCx, JS::CompileOptions& aOptions, ModuleLoadRequest* aRequest,
|
||||
JS::MutableHandle<JSObject*> aModuleOut);
|
||||
#ifdef NIGHTLY_BUILD
|
||||
using WasmBytesBuffer =
|
||||
JS::loader::ScriptLoadRequest::ScriptTextBuffer<uint8_t>;
|
||||
nsresult CompileWasmModuleBytes(JSContext* aCx, JS::CompileOptions& aOptions,
|
||||
ModuleLoadRequest* aRequest,
|
||||
WasmBytesBuffer& aBytes,
|
||||
JS::MutableHandle<JSObject*> aModuleOut);
|
||||
nsresult CompileEmptyWasmModule(JSContext* aCx, JS::CompileOptions& aOptions,
|
||||
ModuleLoadRequest* aRequest,
|
||||
JS::MutableHandle<JSObject*> aModuleOut);
|
||||
#endif
|
||||
nsresult CompileJsonModule(JSContext* aCx, JS::CompileOptions& aOptions,
|
||||
ModuleLoadRequest* aRequest,
|
||||
JS::MutableHandle<JSObject*> aModuleOut);
|
||||
|
||||
@@ -146,6 +146,7 @@ export default [
|
||||
|
||||
// ESLint parse does not support import source yet (Bug 2063547)
|
||||
"dom/base/test/test_wasm_offthread_compile.html",
|
||||
"dom/base/test/jsmodules/scriptDisabled_frame.html",
|
||||
|
||||
// Intentional broken files
|
||||
"dom/base/test/file_js_cache_syntax_error.js",
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<meta charset="utf-8">
|
||||
<!--
|
||||
Loaded in a sandboxed iframe by ../scripting-disabled.tentative.html, where
|
||||
scripting is disabled, so nothing in this document runs.
|
||||
-->
|
||||
<script type="module" src="./invalid-bytecode.wasm"></script>
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
<!DOCTYPE html>
|
||||
<title>WebAssembly module script in a document where scripting is disabled</title>
|
||||
<meta name="author" title="Mozilla" href="mailto:yhuang@mozilla.com">
|
||||
<meta name="help" href="https://html.spec.whatwg.org/#creating-a-webassembly-module-script">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<body>
|
||||
<script>
|
||||
// https://html.spec.whatwg.org/#creating-a-webassembly-module-script
|
||||
// Step 1. If scripting is disabled for settings, then set bodyBytes to the byte
|
||||
// sequence 0x00 0x61 0x73 0x6D 0x01 0x00 0x00 0x00.
|
||||
promise_test(async t => {
|
||||
const frame = document.createElement("iframe");
|
||||
// A sandbox without "allow-scripts" disables scripting for the frame;
|
||||
// "allow-same-origin" keeps the module script's fetch same-origin.
|
||||
frame.sandbox = "allow-same-origin";
|
||||
frame.src = "resources/scripting-disabled.html";
|
||||
t.add_cleanup(() => frame.remove());
|
||||
|
||||
const loaded = new Promise((resolve, reject) => {
|
||||
frame.addEventListener("load", resolve, { once: true });
|
||||
frame.addEventListener(
|
||||
"error",
|
||||
() => reject(new Error("the frame failed to load")),
|
||||
{ once: true }
|
||||
);
|
||||
});
|
||||
document.body.appendChild(frame);
|
||||
await loaded;
|
||||
|
||||
assert_equals(
|
||||
frame.contentDocument.readyState,
|
||||
"complete",
|
||||
"the document holding the module script loaded"
|
||||
);
|
||||
}, "An invalid WebAssembly module script does not fail when scripting is disabled");
|
||||
</script>
|
||||
Reference in New Issue
Block a user