<!DOCTYPE html>
<html>
<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=1436400 -->
<!-- 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.
-->
<head>
  <meta charset="utf-8">
  <title>Test for request fallback for SRI mismatch on module bytecode</title>
  <script src="/resources/testharness.js"></script>
  <script src="/resources/testharnessreport.js"></script>
</head>
<body>
  <script type="application/javascript">

// List of testcases
//
// prep
//   HTML file loaded in iframe that prepares the bytecode cache
// prepEvents
//   Non-ordered multi-set of expected events dispatched during loading the
//   "prep" HTML file
// src
//   HTML file loaded in iframe after preparation finishes
// events
//   Non-ordered multi-set of expected events dispatched during loading the
//   "src" HTML file
const tests = [
  // 1. Module bytecode is saved with integrity
  // 2. Module bytecode is loaded by script element with integrity
  {
    prep: "file_script_module_sri_basic_prep.html",
    prepEvents: [
      "scriptloader_load_source",
      "scriptloader_evaluate_module",
      "scriptloader_encode",
      "scriptloader_bytecode_saved",
      "test_evaluated",
    ],
    src: "file_script_module_sri_basic.html",
    events: [
      "scriptloader_load_bytecode",
      "scriptloader_evaluate_module",
      "test_evaluated",
    ],
  },

  // 1. Module bytecode is saved without integrity
  // 2. Module bytecode is loaded by script element with integrity
  {
    prep: "file_script_module_sri_fallback_prep.html",
    prepEvents: [
      "scriptloader_load_source",
      "scriptloader_evaluate_module",
      "scriptloader_encode",
      "scriptloader_bytecode_saved",
      "test_evaluated",
    ],
    src: "file_script_module_sri_fallback.html",
    events: [
      "scriptloader_load_bytecode",
      "scriptloader_fallback",
      "scriptloader_load_source",
      "scriptloader_evaluate_module",
      "scriptloader_encode",
      "scriptloader_bytecode_saved",
      "test_evaluated",
    ],
  },

  // 1. Module bytecode is saved without integrity
  // 2. Module bytecode is loaded by script element with wrong integrity
  //
  //    The module script is not evaluated
  {
    prep: "file_script_module_sri_fallback_failure_prep.html",
    prepEvents: [
      "scriptloader_load_source",
      "scriptloader_evaluate_module",
      "scriptloader_encode",
      "scriptloader_bytecode_saved",
      "test_evaluated",
    ],
    src: "file_script_module_sri_fallback_failure.html",
    events: [
      "scriptloader_load_bytecode",
      "scriptloader_fallback",
      "scriptloader_load_source",

      // This event is dispatched by another script, after the first module
      // script load is terminated.
      "test_evaluated",
    ],
  },

  // 1. Module bytecode is saved without integrity
  // 2. Module bytecode is loaded by script element without integrity, and then
  //    loaded by script element with integrity
  //
  //    The integrity attribute is ignored because the first request without
  //    integrity is shared between them.
  {
    prep: "file_script_module_sri_elem_elem_1_prep.html",
    prepEvents: [
      "scriptloader_load_source",
      "scriptloader_evaluate_module",
      "scriptloader_encode",
      "scriptloader_bytecode_saved",
      "test_evaluated",
    ],
    src: "file_script_module_sri_elem_elem_1.html",
    events: [
      "scriptloader_load_bytecode",
      "scriptloader_evaluate_module",
      "test_evaluated",

      // NOTE: scriptloader_evaluate_module is dispatched even if
      //       the module is already evaluated before and it's evaluation is
      //       skipped this time.
      "scriptloader_evaluate_module",
    ],
  },

  // 1. Module bytecode is saved without integrity
  // 2. Module bytecode is loaded by script element with integrity, and then
  //    loaded by script element without integrity
  //
  //    The request with integrity is shared between them.
  {
    prep: "file_script_module_sri_elem_elem_2_prep.html",
    prepEvents: [
      "scriptloader_load_source",
      "scriptloader_evaluate_module",
      "scriptloader_encode",
      "scriptloader_bytecode_saved",
      "test_evaluated",
    ],
    src: "file_script_module_sri_elem_elem_2.html",
    events: [
      "scriptloader_load_bytecode",
      "scriptloader_fallback",
      "scriptloader_load_source",
      "scriptloader_evaluate_module",
      "scriptloader_encode",
      "scriptloader_bytecode_saved",
      "test_evaluated",

      "scriptloader_evaluate_module",
    ],
  },

  // 1. Module bytecode is saved without integrity
  // 2. Module bytecode is loaded by script element with integrity, and then
  //    imported statically.
  //
  //    The request with integrity is shared between them.
  {
    prep: "file_script_module_sri_elem_import_prep.html",
    prepEvents: [
      // file_script_module_sri_elem_import_imported.mjs
      "scriptloader_load_source",
      "scriptloader_evaluate_module",
      "scriptloader_encode",
      "scriptloader_bytecode_saved",
      "test_evaluated",
    ],
    src: "file_script_module_sri_elem_import.html",
    events: [
      // file_script_module_sri_elem_import_imported.mjs
      "scriptloader_load_bytecode",
      "scriptloader_fallback",
      "scriptloader_load_source",
      "scriptloader_evaluate_module",
      "scriptloader_encode",
      "scriptloader_bytecode_saved",
      "test_evaluated",

      // file_script_module_sri_elem_import.mjs
      "scriptloader_load_source",
      "scriptloader_evaluate_module",
      "scriptloader_encode",
      "scriptloader_bytecode_saved",
      "test_evaluated",
    ],
  },

  // 1. Module bytecode is saved without integrity
  // 2. Module bytecode is imported statically, and
  //    loaded by script element with integrity
  //
  //    Even if the import is performed first, the script element's request
  //    with integrity is used because of preload.
  {
    prep: "file_script_module_sri_import_elem_prep.html",
    prepEvents: [
      // file_script_module_sri_import_elem_imported.mjs
      "scriptloader_load_source",
      "scriptloader_evaluate_module",
      "scriptloader_encode",
      "scriptloader_bytecode_saved",
      "test_evaluated",
    ],
    src: "file_script_module_sri_import_elem.html",
    events: [
      // file_script_module_sri_import_elem.mjs
      "scriptloader_load_source",
      "scriptloader_evaluate_module",
      "scriptloader_encode",
      "scriptloader_bytecode_saved",
      "test_evaluated",

      // file_script_module_sri_import_elem_imported.mjs
      "scriptloader_load_bytecode",
      "scriptloader_fallback",
      "scriptloader_load_source",
      "scriptloader_evaluate_module",
      "scriptloader_encode",
      "scriptloader_bytecode_saved",
      "test_evaluated",
    ],
  },

  // 1. Module bytecode is saved without integrity
  // 2. Module bytecode is imported statically, and
  //    loaded by script element with integrity, without preload
  //
  //    The request without integrity triggered by import is shared between
  //    them.
  {
    prep: "file_script_module_sri_import_elem_nopreload_prep.html",
    prepEvents: [
      // file_script_module_sri_import_elem_nopreload_imported.mjs
      "scriptloader_load_source",
      "scriptloader_evaluate_module",
      "scriptloader_encode",
      "scriptloader_bytecode_saved",
      "test_evaluated",
    ],
    src: "file_script_module_sri_import_elem_nopreload.html",
    events: [
      // file_script_module_sri_import_elem_nopreload.mjs
      "scriptloader_load_source",
      "scriptloader_evaluate_module",
      "scriptloader_encode",
      "scriptloader_bytecode_saved",
      "test_evaluated",

      // file_script_module_sri_import_elem_nopreload_imported.mjs
      "scriptloader_load_bytecode",
      "scriptloader_evaluate_module",
      "test_evaluated",
    ],
  },

  // 1. Module bytecode is saved without integrity
  // 2. Module bytecode is loaded by script element with integrity, and then
  //    imported dynamically.
  //
  //    The request with integrity is shared between them.
  {
    prep: "file_script_module_sri_elem_dynamic_prep.html",
    prepEvents: [
      // file_script_module_sri_elem_dynamic_imported.mjs
      "scriptloader_load_source",
      "scriptloader_evaluate_module",
      "scriptloader_encode",
      "scriptloader_bytecode_saved",
      "test_evaluated",
    ],
    src: "file_script_module_sri_elem_dynamic.html",
    events: [
      // file_script_module_sri_elem_dynamic_imported.mjs
      "scriptloader_load_bytecode",
      "scriptloader_fallback",
      "scriptloader_load_source",
      "scriptloader_evaluate_module", // (element)
      "scriptloader_evaluate_module", // (dynamic)
      "scriptloader_encode",
      "scriptloader_bytecode_saved",
      "test_evaluated",

      // file_script_module_sri_elem_dynamic.mjs
      "scriptloader_load_source",
      "scriptloader_evaluate_module",
      "scriptloader_encode",
      "scriptloader_bytecode_saved",
      "test_evaluated",
    ],
  },

  // 1. Module bytecode is saved without integrity
  // 2. Module bytecode is imported dynamically, and then
  //    loaded by script element with integrity
  //
  //    Even if the dynamic import is performed first, the script element's
  //    request with integrity is used because of preload.
  {
    prep: "file_script_module_sri_dynamic_elem_prep.html",
    prepEvents: [
      // file_script_module_sri_dynamic_elem_imported.mjs
      "scriptloader_load_source",
      "scriptloader_evaluate_module",
      "scriptloader_encode",
      "scriptloader_bytecode_saved",
      "test_evaluated",
    ],
    src: "file_script_module_sri_dynamic_elem.html",
    events: [
      // file_script_module_sri_dynamic_elem_imported.mjs
      "scriptloader_load_bytecode",
      "scriptloader_fallback",
      "scriptloader_load_source",
      "scriptloader_evaluate_module", // (element)
      "scriptloader_evaluate_module", // (dynamic)
      "scriptloader_encode",
      "test_evaluated",

      // file_script_module_sri_dynamic_elem.mjs
      "scriptloader_load_source",
      "scriptloader_evaluate_module",
      "scriptloader_encode",
      "scriptloader_bytecode_saved",
      "test_evaluated",
    ],
  },

  // 1. Module bytecode is saved without integrity
  // 2. Module bytecode is imported dynamically, and then
  //    loaded by script element with integrity, without preload
  //
  //    The request without integrity triggered by dynamic import is shared
  //    between them.
  {
    prep: "file_script_module_sri_dynamic_elem_nopreload_prep.html",
    prepEvents: [
      // file_script_module_sri_dynamic_elem_nopreload_imported.mjs
      "scriptloader_load_source",
      "scriptloader_evaluate_module",
      "scriptloader_encode",
      "scriptloader_bytecode_saved",
      "test_evaluated",
    ],
    src: "file_script_module_sri_dynamic_elem_nopreload.html",
    events: [
      // file_script_module_sri_dynamic_elem_nopreload_imported.mjs
      "scriptloader_load_bytecode",
      "scriptloader_evaluate_module", // (element)
      "scriptloader_evaluate_module", // (dynamic)
      "test_evaluated",

      // file_script_module_sri_dynamic_elem_nopreload.mjs
      "scriptloader_load_source",
      "scriptloader_evaluate_module",
      "scriptloader_encode",
      "scriptloader_bytecode_saved",
      "test_evaluated",
    ],
  },
];

promise_test(async function() {
  await SpecialPowers.pushPrefEnv({set: [
    ['dom.script_loader.bytecode_cache.enabled', true],
    ['dom.expose_test_interfaces', true],
    ['dom.script_loader.bytecode_cache.strategy', -1]
  ]});

  for (const { prep, prepEvents, src, events } of tests) {
    for (let i = 0; i < 2; i++) {
      const expectedEvents = i == 0 ? prepEvents : events;
      const currentSrc = i == 0 ? prep : src;

      const iframe = document.createElement("iframe");
      document.body.appendChild(iframe);

      const iwin = iframe.contentWindow;

      dump("## Start: " + currentSrc + "\n");
      let observedEvents = [];
      await new Promise(resolve => {
        function logEvent(evt) {
          if (evt.type != "test_evaluated") {
            if (!/^watchme/.test(evt.target.id)) {
              return;
            }
          }
          dump("## ScriptLoader event: " + evt.type + "\n");
          observedEvents.push(evt.type);
          if (observedEvents.length == expectedEvents.length) {
            resolve();
          }
        }
        iwin.addEventListener("scriptloader_load_source", logEvent);
        iwin.addEventListener("scriptloader_load_bytecode", logEvent);
        iwin.addEventListener("scriptloader_execute", logEvent);
        iwin.addEventListener("scriptloader_evaluate_module", logEvent);
        iwin.addEventListener("scriptloader_encode", logEvent);
        iwin.addEventListener("scriptloader_no_encode", logEvent);
        iwin.addEventListener("scriptloader_bytecode_saved", logEvent);
        iwin.addEventListener("scriptloader_bytecode_failed", logEvent);
        iwin.addEventListener("scriptloader_fallback", logEvent);
        iwin.addEventListener("test_evaluated", logEvent);
        iframe.src = currentSrc;
      });

      // The event order is non-deterministic.
      // Just compare them as multi-set.
      expectedEvents.sort();
      observedEvents.sort();
      assert_equals(
        JSON.stringify(observedEvents),
        JSON.stringify(expectedEvents),
        `Expected events should be observed for ${currentSrc}`);

      document.body.removeChild(iframe);
    }
  }
}, "Test module bytecode save and load");

done();
  </script>
  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1436400">Mozilla Bug 1436400</a>
</body>
</html>
