diff options
Diffstat (limited to '')
-rw-r--r-- | dom/base/test/test_script_loader_js_cache.html | 264 |
1 files changed, 264 insertions, 0 deletions
diff --git a/dom/base/test/test_script_loader_js_cache.html b/dom/base/test/test_script_loader_js_cache.html new file mode 100644 index 0000000000..de168ad109 --- /dev/null +++ b/dom/base/test/test_script_loader_js_cache.html @@ -0,0 +1,264 @@ +<!DOCTYPE html> +<html> +<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=900784 --> +<!-- The JS bytecode cache is not supposed to be observable. To make it + observable, the ScriptLoader is instrumented to trigger events on the + script tag. These events are followed to reconstruct the code path taken by + the script loader and associate a simple name which is checked in these + test cases. +--> +<head> + <meta charset="utf-8"> + <title>Test for saving and loading bytecode in/from the necko cache</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script type="application/javascript"> + // This is the state machine of the trace events produced by the + // ScriptLoader. This state machine is used to give a name to each + // code path, such that we can assert each code path with a single word. + var scriptLoaderStateMachine = { + "scriptloader_load_source": { + "scriptloader_execute": { + "scriptloader_encode": { + "scriptloader_bytecode_saved": "bytecode_saved", + "scriptloader_bytecode_failed": "bytecode_failed" + }, + "scriptloader_no_encode": "source_exec" + }, + "scriptloader_evaluate_module": { + "scriptloader_encode": { + "scriptloader_bytecode_saved": "module_bytecode_saved", + "scriptloader_bytecode_failed": "module_bytecode_failed" + }, + "scriptloader_no_encode": "module_source_exec" + }, + }, + "scriptloader_load_bytecode": { + "scriptloader_fallback": { + // Replicate the top-level state machine without + // "scriptloader_load_bytecode" transition. + "scriptloader_load_source": { + "scriptloader_execute": { + "scriptloader_encode": { + "scriptloader_bytecode_saved": "fallback_bytecode_saved", + "scriptloader_bytecode_failed": "fallback_bytecode_failed" + }, + "scriptloader_no_encode": "fallback_source_exec" + }, + "scriptloader_evaluate_module": { + "scriptloader_encode": { + "scriptloader_bytecode_saved": "module_fallback_bytecode_saved", + "scriptloader_bytecode_failed": "module_fallback_bytecode_failed", + }, + "scriptloader_no_encode": "module_fallback_source_exec", + }, + } + }, + "scriptloader_execute": "bytecode_exec", + "scriptloader_evaluate_module": "module_bytecode_exec", + } + }; + + function WaitForScriptTagEvent(url) { + var iframe = document.createElement("iframe"); + document.body.appendChild(iframe); + + var stateMachine = scriptLoaderStateMachine; + var stateHistory = []; + var stateMachineResolve, stateMachineReject; + var statePromise = new Promise((resolve, reject) => { + stateMachineResolve = resolve; + stateMachineReject = reject; + }); + var ping = 0; + + // Walk the script loader state machine with the emitted events. + function log_event(evt) { + // If we have multiple script tags in the loaded source, make sure + // we only watch a single one. + if (evt.target.id != "watchme") + return; + + dump("## ScriptLoader event: " + evt.type + "\n"); + stateHistory.push(evt.type) + if (typeof stateMachine == "object") + stateMachine = stateMachine[evt.type]; + if (typeof stateMachine == "string") { + // We arrived to a final state, report the name of it. + var result = stateMachine; + if (ping) { + result = `${result} & ping(=${ping})`; + } + stateMachineResolve(result); + } else if (stateMachine === undefined) { + // We followed an unknown transition, report the known history. + stateMachineReject(stateHistory); + } + } + + var iwin = iframe.contentWindow; + iwin.addEventListener("scriptloader_load_source", log_event); + iwin.addEventListener("scriptloader_load_bytecode", log_event); + iwin.addEventListener("scriptloader_generate_bytecode", log_event); + iwin.addEventListener("scriptloader_execute", log_event); + iwin.addEventListener("scriptloader_evaluate_module", log_event); + iwin.addEventListener("scriptloader_encode", log_event); + iwin.addEventListener("scriptloader_no_encode", log_event); + iwin.addEventListener("scriptloader_bytecode_saved", log_event); + iwin.addEventListener("scriptloader_bytecode_failed", log_event); + iwin.addEventListener("scriptloader_fallback", log_event); + iwin.addEventListener("ping", (evt) => { + ping += 1; + dump(`## Content event: ${evt.type} (=${ping})\n`); + }); + iframe.src = url; + + statePromise.then(() => { + document.body.removeChild(iframe); + }); + return statePromise; + } + + async function basicTest(isModule) { + const prefix = isModule ? "module_" : ""; + const name = isModule ? "module" : "script"; + + // Setting dom.expose_test_interfaces pref causes the + // nsScriptLoadRequest to fire event on script tags, with information + // about its internal state. The ScriptLoader source send events to + // trace these and resolve a promise with the path taken by the + // script loader. + // + // Setting dom.script_loader.bytecode_cache.strategy to -1 causes the + // nsScriptLoadRequest to force all the conditions necessary to make a + // script be saved as bytecode in the alternate data storage provided + // by the channel (necko cache). + await SpecialPowers.pushPrefEnv({set: [ + ['dom.script_loader.bytecode_cache.enabled', true], + ['dom.expose_test_interfaces', true], + ['dom.script_loader.bytecode_cache.strategy', -1] + ]}); + + // Load the test page, and verify that the code path taken by the + // nsScriptLoadRequest corresponds to the code path which is loading a + // source and saving it as bytecode. + var stateMachineResult = WaitForScriptTagEvent(`file_${prefix}js_cache.html`); + assert_equals(await stateMachineResult, `${prefix}bytecode_saved`, + `[1-${name}] ScriptLoadRequest status after the first visit`); + + // Reload the same test page, and verify that the code path taken by + // the nsScriptLoadRequest corresponds to the code path which is + // loading bytecode and executing it. + stateMachineResult = WaitForScriptTagEvent(`file_${prefix}js_cache.html`); + assert_equals(await stateMachineResult, `${prefix}bytecode_exec`, + `[2-${name}] ScriptLoadRequest status after the second visit`); + + // Load another page which loads the same script with an SRI, while + // the cached bytecode does not have any. This should fallback to + // loading the source before saving the bytecode once more. + stateMachineResult = WaitForScriptTagEvent(`file_${prefix}js_cache_with_sri.html`); + assert_equals(await stateMachineResult, `${prefix}fallback_bytecode_saved`, + `[3-${name}] ScriptLoadRequest status after the SRI hash`); + + // Loading a page, which has the same SRI should verify the SRI and + // continue by executing the bytecode. + var stateMachineResult1 = WaitForScriptTagEvent(`file_${prefix}js_cache_with_sri.html`); + + // Loading a page which does not have a SRI while we have one in the + // cache should not change anything. We should also be able to load + // the cache simultanesouly. + var stateMachineResult2 = WaitForScriptTagEvent(`file_${prefix}js_cache.html`); + + assert_equals(await stateMachineResult1, `${prefix}bytecode_exec`, + `[4-${name}] ScriptLoadRequest status after same SRI hash`); + assert_equals(await stateMachineResult2, `${prefix}bytecode_exec`, + `[5-${name}] ScriptLoadRequest status after visit with no SRI`); + + if (!isModule) { + // Load a page that uses the same script as a module and verify that we + // re-parse it from source. + stateMachineResult = WaitForScriptTagEvent("file_js_cache_module.html"); + assert_equals(await stateMachineResult, "module_bytecode_saved", + `[6-${name}] ScriptLoadRequest status for a module`); + } else { + // Load a page that uses the same module script as a regular script and + // verify that we re-parse it from source. + stateMachineResult = WaitForScriptTagEvent("file_module_js_cache_no_module.html"); + assert_equals(await stateMachineResult, "bytecode_saved", + `[6-${name}] ScriptLoadRequest status for a script`); + } + } + + promise_test(async function() { + await basicTest(false); + }, "Check the JS bytecode cache for script"); + + promise_test(async function() { + await basicTest(true); + }, "Check the JS bytecode cache for module"); + + promise_test(async function() { + // (see above) + await SpecialPowers.pushPrefEnv({set: [ + ['dom.script_loader.bytecode_cache.enabled', true], + ['dom.expose_test_interfaces', true], + ['dom.script_loader.bytecode_cache.strategy', -1] + ]}); + + // The test page add a new script which generate a "ping" event, which + // should be recorded before the bytecode is stored in the cache. + var stateMachineResult = + WaitForScriptTagEvent("file_js_cache_save_after_load.html"); + assert_equals(await stateMachineResult, "bytecode_saved & ping(=3)", + "Wait on all scripts to be executed"); + + }, "Save bytecode after the initialization of the page"); + + promise_test(async function() { + // (see above) + await SpecialPowers.pushPrefEnv({set: [ + ['dom.script_loader.bytecode_cache.enabled', true], + ['dom.expose_test_interfaces', true], + ['dom.script_loader.bytecode_cache.strategy', -1] + ]}); + + // The test page loads a script which contains a syntax error, we should + // not attempt to encode any bytecode for it. + var stateMachineResult = + WaitForScriptTagEvent("file_js_cache_syntax_error.html"); + assert_equals(await stateMachineResult, "source_exec", + "Check the lack of bytecode encoding"); + + }, "Do not save bytecode on compilation errors"); + + promise_test(async function() { + // (see above) + await SpecialPowers.pushPrefEnv({set: [ + ['dom.script_loader.bytecode_cache.enabled', true], + ['dom.expose_test_interfaces', true], + ['dom.script_loader.bytecode_cache.strategy', -1], + ['browser.cache.jsbc_compression_level', 2] + ]}); + + // Load the test page, and verify that the code path taken by the + // nsScriptLoadRequest corresponds to the code path which is loading a + // source and saving it as compressed bytecode. + var stateMachineResult = WaitForScriptTagEvent(`file_js_cache.html`); + assert_equals(await stateMachineResult, `bytecode_saved`, + `[1-script] ScriptLoadRequest status after the first visit`); + + // Reload the same test page, and verify that the code path taken by + // the nsScriptLoadRequest corresponds to the code path which is + // loading compressed bytecode, decompressing it, and executing it. + stateMachineResult = WaitForScriptTagEvent(`file_js_cache.html`); + assert_equals(await stateMachineResult, `bytecode_exec`, + `[2-script] ScriptLoadRequest status after the second visit`); + }, "Check the JS bytecode cache can save and load compressed bytecode"); + + done(); + </script> +</head> +<body> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=900784">Mozilla Bug 900784</a> +</body> +</html> |