summaryrefslogtreecommitdiffstats
path: root/dom/base/test/test_script_loader_js_cache.html
blob: de168ad109cae690841e75d0e738cc834fe2e055 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
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>