summaryrefslogtreecommitdiffstats
path: root/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_errors.js
blob: c4f504c21966448e0e53e691cced0776556000f8 (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
"use strict";

const server = createHttpServer();
server.registerDirectory("/data/", do_get_file("data"));

const BASE_URL = `http://localhost:${server.identity.primaryPort}/data`;
const TEST_URL_1 = `${BASE_URL}/file_sample.html`;
const TEST_URL_2 = `${BASE_URL}/file_content_script_errors.html`;

// ExtensionContent.jsm needs to know when it's running from xpcshell,
// to use the right timeout for content scripts executed at document_idle.
ExtensionTestUtils.mockAppInfo();

add_task(async function test_cached_contentscript_on_document_start() {
  let extension = ExtensionTestUtils.loadExtension({
    manifest: {
      content_scripts: [
        // Use distinct content scripts as some will throw and would prevent executing the next script
        {
          matches: ["http://*/*/file_content_script_errors.html"],
          js: ["script_does_not_exist.js"],
          run_at: "document_start",
        },
        {
          matches: ["http://*/*/file_content_script_errors.html"],
          js: ["script1.js"],
          run_at: "document_start",
        },
        {
          matches: ["http://*/*/file_content_script_errors.html"],
          js: ["script2.js"],
          run_at: "document_start",
        },
        {
          matches: ["http://*/*/file_content_script_errors.html"],
          js: ["script3.js"],
          run_at: "document_start",
        },
        {
          matches: ["http://*/*/file_content_script_errors.html"],
          js: ["script4.js"],
          run_at: "document_start",
        },
        {
          matches: ["http://*/*/file_content_script_errors.html"],
          js: ["script5.js"],
          run_at: "document_start",
        },
        {
          matches: ["http://*/*/file_content_script_errors.html"],
          js: ["script6.js"],
          run_at: "document_start",
        },
        {
          matches: ["http://*/*/file_content_script_errors.html"],
          js: ["script7.js"],
          run_at: "document_start",
        },
      ],
    },

    files: {
      "script1.js": `
        throw new Error("Object exception");
      `,
      "script2.js": `
        throw "String exception";
      `,
      "script3.js": `
        undefinedSymbol();
      `,
      "script4.js": `
        )
      `,
      "script5.js": `
        throw null;
      `,
      "script6.js": `
        throw Symbol("MySymbol");
      `,
      "script7.js": `
        Promise.reject("rejected promise");

        (async () => {
          /* make the async, really async */
          await new Promise(r => setTimeout(r, 0));
          throw new Error("async function exception");
        })();

        setTimeout(() => {
          asyncUndefinedSymbol();
        });

        /* Use a delay in order to resume test execution after these async errors */
        setTimeout(() => {
          browser.test.sendMessage("content-script-loaded");
        }, 500);
      `,
    },
  });

  await extension.startup();

  // Error messages, in roughly the order they appear above.
  let expectedMessages = [
    `Unable to load script: moz-extension://${extension.uuid}/script_does_not_exist.js`,
    "Error: Object exception",
    "uncaught exception: String exception",
    "ReferenceError: undefinedSymbol is not defined",
    "SyntaxError: expected expression, got ')'",
    "uncaught exception: null",
    "uncaught exception: Symbol(MySymbol)",
    "uncaught exception: rejected promise",
    "Error: async function exception",
    "ReferenceError: asyncUndefinedSymbol is not defined",
  ];

  // Load a first page in order to be able to register a console listener in the content process.
  // This has to be done in the same domain of the second page to stay in the same process.
  let contentPage = await ExtensionTestUtils.loadContentPage(TEST_URL_1);

  // Listen to the errors logged in the content process.
  let errorsPromise = ContentTask.spawn(contentPage.browser, {}, async () => {
    return new Promise(resolve => {
      function listener(error0) {
        let error = error0.QueryInterface(Ci.nsIScriptError);

        // Ignore errors from ExtensionContent.jsm
        if (!error.innerWindowID) {
          return;
        }

        this.collectedErrors.push({
          innerWindowID: error.innerWindowID,
          message: error.errorMessage,
        });
        if (this.collectedErrors.length == 10) {
          Services.console.unregisterListener(this);
          resolve(this.collectedErrors);
        }
      }
      listener.collectedErrors = [];
      Services.console.registerListener(listener);
    });
  });

  // Reload the page and check that the cached content script is still able to
  // run on document_start.
  await contentPage.loadURL(TEST_URL_2);

  let errors = await errorsPromise;

  await extension.awaitMessage("content-script-loaded");

  equal(errors.length, 10);
  let messages = [];
  for (const { innerWindowID, message } of errors) {
    equal(
      innerWindowID,
      contentPage.browser.innerWindowID,
      `Message ${message} has the innerWindowID set`
    );

    messages.push(message);
  }

  messages.sort();
  expectedMessages.sort();
  Assert.deepEqual(messages, expectedMessages, "Got the expected errors");

  await extension.unload();

  await contentPage.close();
});