summaryrefslogtreecommitdiffstats
path: root/toolkit/components/extensions/test/browser/browser_ext_extension_page_tab_navigated.js
blob: b2fb9484c2b0c7c61af97c4483d39ff7df6c9cf5 (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
/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

const { AddonTestUtils } = ChromeUtils.importESModule(
  "resource://testing-common/AddonTestUtils.sys.mjs"
);

// The test tasks in this test file tends to trigger an intermittent
// exception raised from JSActor::AfterDestroy, because of a race between
// when the WebExtensions API event is being emitted from the parent process
// and the navigation triggered on the test extension pages.
const { PromiseTestUtils } = ChromeUtils.importESModule(
  "resource://testing-common/PromiseTestUtils.sys.mjs"
);
PromiseTestUtils.allowMatchingRejectionsGlobally(
  /Actor 'Conduits' destroyed before query 'RunListener' was resolved/
);

AddonTestUtils.initMochitest(this);

const server = AddonTestUtils.createHttpServer({
  hosts: ["example.com", "anotherwebpage.org"],
});

server.registerPathHandler("/", (request, response) => {
  response.write(`<!DOCTYPE html>
    <html>
      <head>
       <meta charset="utf-8">
       <title>test webpage</title>
      </head>
    </html>
  `);
});

function createTestExtPage({ script }) {
  return `<!DOCTYPE html>
    <html>
      <head>
       <meta charset="utf-8">
       <script src="${script}"></script>
      </head>
    </html>
  `;
}

function createTestExtPageScript(name) {
  return `(${function (pageName) {
    browser.webRequest.onBeforeRequest.addListener(
      details => {
        browser.test.log(
          `Extension page "${pageName}" got a webRequest event: ${details.url}`
        );
        browser.test.sendMessage(`event-received:${pageName}`);
      },
      { types: ["main_frame"], urls: ["http://example.com/*"] }
    );
    /* eslint-disable mozilla/balanced-listeners */
    window.addEventListener("pageshow", () => {
      browser.test.log(`Extension page "${pageName}" got a pageshow event`);
      browser.test.sendMessage(`pageshow:${pageName}`);
    });
    window.addEventListener("pagehide", () => {
      browser.test.log(`Extension page "${pageName}" got a pagehide event`);
      browser.test.sendMessage(`pagehide:${pageName}`);
    });
    /* eslint-enable mozilla/balanced-listeners */
  }})("${name}");`;
}

// Triggers a WebRequest listener registered by the test extensions by
// opening a tab on the given web page URL and then closing it after
// it did load.
async function triggerWebRequestListener(webPageURL, pause) {
  let webPageTab = await BrowserTestUtils.openNewForegroundTab(
    {
      gBrowser,
      url: webPageURL,
    },
    true /* waitForLoad */,
    true /* waitForStop */
  );
  BrowserTestUtils.removeTab(webPageTab);
}

// The following tests tasks are testing the expected behaviors related to same-process and cross-process
// navigations for an extension page, similarly to test_ext_extension_page_navigated.js, but unlike its
// xpcshell counterpart this tests are only testing that after navigating back to an extension page
// previously stored in the BFCache the WebExtensions events subscribed are being received as expected.

add_task(async function test_extension_page_sameprocess_navigation() {
  const extension = ExtensionTestUtils.loadExtension({
    manifest: {
      permissions: ["webRequest", "http://example.com/*"],
    },
    files: {
      "extpage1.html": createTestExtPage({ script: "extpage1.js" }),
      "extpage1.js": createTestExtPageScript("extpage1"),
      "extpage2.html": createTestExtPage({ script: "extpage2.js" }),
      "extpage2.js": createTestExtPageScript("extpage2"),
    },
  });

  await extension.startup();

  const policy = WebExtensionPolicy.getByID(extension.id);

  const extPageURL1 = policy.extension.baseURI.resolve("extpage1.html");
  const extPageURL2 = policy.extension.baseURI.resolve("extpage2.html");

  info("Opening extension page in a new tab");
  const extPageTab = await BrowserTestUtils.addTab(gBrowser, extPageURL1);
  let browser = gBrowser.getBrowserForTab(extPageTab);
  info("Wait for the extension page to be loaded");
  await extension.awaitMessage("pageshow:extpage1");

  await triggerWebRequestListener("http://example.com");
  await extension.awaitMessage("event-received:extpage1");
  ok(true, "extpage1 got a webRequest event as expected");

  info("Load a second extension page in the same tab");
  BrowserTestUtils.loadURIString(browser, extPageURL2);

  info("Wait extpage1 to receive a pagehide event");
  await extension.awaitMessage("pagehide:extpage1");
  info("Wait extpage2 to receive a pageshow event");
  await extension.awaitMessage("pageshow:extpage2");

  info(
    "Trigger a web request event and expect extpage2 to be the only one receiving it"
  );
  await triggerWebRequestListener("http://example.com");
  await extension.awaitMessage("event-received:extpage2");
  ok(true, "extpage2 got a webRequest event as expected");

  info(
    "Navigating back to extpage1 and expect extpage2 to be the only one receiving the webRequest event"
  );

  browser.goBack();
  info("Wait for extpage1 to receive a pageshow event");
  await extension.awaitMessage("pageshow:extpage1");
  info("Wait for extpage2 to receive a pagehide event");
  await extension.awaitMessage("pagehide:extpage2");

  // We only expect extpage1 to be able to receive API events.
  await triggerWebRequestListener("http://example.com");
  await extension.awaitMessage("event-received:extpage1");
  ok(true, "extpage1 got a webRequest event as expected");

  BrowserTestUtils.removeTab(extPageTab);
  await extension.awaitMessage("pagehide:extpage1");

  await extension.unload();
});

add_task(async function test_extension_page_context_navigated_to_web_page() {
  const extension = ExtensionTestUtils.loadExtension({
    manifest: {
      permissions: ["webRequest", "http://example.com/*"],
    },
    files: {
      "extpage.html": createTestExtPage({ script: "extpage.js" }),
      "extpage.js": createTestExtPageScript("extpage"),
    },
  });

  await extension.startup();

  const policy = WebExtensionPolicy.getByID(extension.id);

  const extPageURL = policy.extension.baseURI.resolve("extpage.html");
  // NOTE: this test will navigate the extension page to a webpage url that
  // isn't matching the match pattern the test extension is going to use
  // in its webRequest event listener, otherwise the extension page being
  // navigated will be intermittently able to receive an event before it
  // is navigated to the webpage url (and moved into the BFCache or destroyed)
  // and trigger an intermittent failure of this test.
  const webPageURL = "http://anotherwebpage.org/";
  const triggerWebRequestURL = "http://example.com/";

  info("Opening extension page in a new tab");
  const extPageTab1 = await BrowserTestUtils.addTab(gBrowser, extPageURL);
  let browserForTab1 = gBrowser.getBrowserForTab(extPageTab1);
  info("Wait for the extension page to be loaded");
  await extension.awaitMessage("pageshow:extpage");

  info("Navigate the tab from the extension page to a web page");
  let promiseLoaded = BrowserTestUtils.browserLoaded(
    browserForTab1,
    false,
    webPageURL
  );
  BrowserTestUtils.loadURIString(browserForTab1, webPageURL);
  info("Wait the tab to have loaded the new webpage url");
  await promiseLoaded;
  info("Wait the extension page to receive a pagehide event");
  await extension.awaitMessage("pagehide:extpage");

  // Trigger a webRequest listener, the extension page is expected to
  // not be active, if that isn't the case a test message will be queued
  // and will trigger an explicit test failure.
  await triggerWebRequestListener(triggerWebRequestURL);

  info("Navigate back to the extension page");
  browserForTab1.goBack();
  info("Wait for extension page to receive a pageshow event");
  await extension.awaitMessage("pageshow:extpage");

  await triggerWebRequestListener(triggerWebRequestURL);
  await extension.awaitMessage("event-received:extpage");
  ok(
    true,
    "extpage got a webRequest event as expected after being restored from BFCache"
  );

  info("Cleanup and exit test");
  BrowserTestUtils.removeTab(extPageTab1);

  info("Wait the extension page to receive a pagehide event");
  await extension.awaitMessage("pagehide:extpage");

  await extension.unload();
});