summaryrefslogtreecommitdiffstats
path: root/browser/components/extensions/test/browser/browser_ext_windows_create_url.js
blob: 0ee3a50dffcdfc10d38b3eef95b794224ddb2333 (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
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";

add_task(async function testWindowCreate() {
  let pageExt = ExtensionTestUtils.loadExtension({
    manifest: {
      browser_specific_settings: { gecko: { id: "page@mochitest" } },
      protocol_handlers: [
        {
          protocol: "ext+foo",
          name: "a foo protocol handler",
          uriTemplate: "page.html?val=%s",
        },
      ],
    },
    files: {
      "page.html": `<html><head>
          <meta charset="utf-8">
        </head></html>`,
    },
  });
  await pageExt.startup();

  async function background(OTHER_PAGE) {
    browser.test.log(`== using ${OTHER_PAGE}`);
    const EXTENSION_URL = browser.runtime.getURL("test.html");
    const EXT_PROTO = "ext+bar:foo";
    const OTHER_PROTO = "ext+foo:bar";

    let windows = new (class extends Map {
      // eslint-disable-line new-parens
      get(id) {
        if (!this.has(id)) {
          let window = {
            tabs: new Map(),
          };
          window.promise = new Promise(resolve => {
            window.resolvePromise = resolve;
          });

          this.set(id, window);
        }

        return super.get(id);
      }
    })();

    browser.tabs.onUpdated.addListener((tabId, changed, tab) => {
      if (changed.status == "complete" && tab.url !== "about:blank") {
        let window = windows.get(tab.windowId);
        window.tabs.set(tab.index, tab);

        if (window.tabs.size === window.expectedTabs) {
          browser.test.log("resolving a window load");
          window.resolvePromise(window);
        }
      }
    });

    async function create(options) {
      browser.test.log(`creating window for ${options.url}`);
      // Note: may reject
      let window = await browser.windows.create(options);
      let win = windows.get(window.id);
      win.id = window.id;

      win.expectedTabs = Array.isArray(options.url) ? options.url.length : 1;

      return win.promise;
    }

    let TEST_SETS = [
      {
        name: "Single protocol URL in this extension",
        url: EXT_PROTO,
        expect: [`${EXTENSION_URL}?val=ext%2Bbar%3Afoo`],
      },
      {
        name: "Single, relative URL",
        url: "test.html",
        expect: [EXTENSION_URL],
      },
      {
        name: "Single, absolute, extension URL",
        url: EXTENSION_URL,
        expect: [EXTENSION_URL],
      },
      {
        // This is primarily for backwards-compatibility, to allow extensions
        // to open other home pages. This test case opens the home page
        // explicitly; the implicit case (windows.create({}) without URL) is at
        // browser_ext_chrome_settings_overrides_home.js.
        name: "Single, absolute, other extension URL",
        url: OTHER_PAGE,
        expect: [OTHER_PAGE],
      },
      {
        // This is oddly inconsistent with the non-array case, but here we are
        // intentionally stricter because of lesser backwards-compatibility
        // concerns.
        name: "Array, absolute, other extension URL",
        url: [OTHER_PAGE],
        expectError: `Illegal URL: ${OTHER_PAGE}`,
      },
      {
        name: "Single protocol URL in other extension",
        url: OTHER_PROTO,
        expect: [`${OTHER_PAGE}?val=ext%2Bfoo%3Abar`],
      },
      {
        name: "Single, about:blank",
        // Added "?" after "about:blank" because the test's tab load detection
        // ignores about:blank.
        url: "about:blank?",
        expect: ["about:blank?"],
      },
      {
        name: "multiple urls",
        url: [EXT_PROTO, "test.html", EXTENSION_URL, OTHER_PROTO],
        expect: [
          `${EXTENSION_URL}?val=ext%2Bbar%3Afoo`,
          EXTENSION_URL,
          EXTENSION_URL,
          `${OTHER_PAGE}?val=ext%2Bfoo%3Abar`,
        ],
      },
      {
        name: "Reject array of own allowed URLs and other moz-extension:-URL",
        url: [EXTENSION_URL, EXT_PROTO, "about:blank?#", OTHER_PAGE],
        expectError: `Illegal URL: ${OTHER_PAGE}`,
      },
      {
        name: "Single, about:robots",
        url: "about:robots",
        expectError: "Illegal URL: about:robots",
      },
      {
        name: "Array containing about:robots",
        url: ["about:robots"],
        expectError: "Illegal URL: about:robots",
      },
    ];
    async function checkCreateResult({ status, value, reason }, testCase) {
      const window = status === "fulfilled" ? value : null;
      try {
        if (testCase.expectError) {
          let error = reason?.message;
          browser.test.assertEq(testCase.expectError, error, testCase.name);
        } else {
          let tabUrls = [];
          for (let [tabIndex, tab] of window.tabs) {
            tabUrls[tabIndex] = tab.url;
          }
          browser.test.assertDeepEq(testCase.expect, tabUrls, testCase.name);
        }
      } catch (e) {
        browser.test.fail(`Unexpected failure in ${testCase.name} :: ${e}`);
      } finally {
        // Close opened windows, whether they were expected or not.
        if (window) {
          await browser.windows.remove(window.id);
        }
      }
    }
    try {
      // First collect all results, in parallel.
      const results = await Promise.allSettled(
        TEST_SETS.map(t => create({ url: t.url }))
      );
      // Then check the results sequentially
      await Promise.all(
        TEST_SETS.map((t, i) => checkCreateResult(results[i], t))
      );
      browser.test.notifyPass("window-create-url");
    } catch (e) {
      browser.test.fail(`${e} :: ${e.stack}`);
      browser.test.notifyFail("window-create-url");
    }
  }

  // Watch for any permission prompts to show up and accept them.
  let dialogCount = 0;
  let windowObserver = window => {
    // This listener will go away when the window is closed so there is no need
    // to explicitely remove it.
    // eslint-disable-next-line mozilla/balanced-listeners
    window.addEventListener("dialogopen", event => {
      dialogCount++;
      let { dialog } = event.detail;
      Assert.equal(
        dialog?._openedURL,
        "chrome://mozapps/content/handling/permissionDialog.xhtml",
        "Should only ever see the permission dialog"
      );
      let dialogEl = dialog._frame.contentDocument.querySelector("dialog");
      Assert.ok(dialogEl, "Dialog element should exist");
      dialogEl.setAttribute("buttondisabledaccept", false);
      dialogEl.acceptDialog();
    });
  };
  Services.obs.addObserver(windowObserver, "browser-delayed-startup-finished");
  registerCleanupFunction(() => {
    Services.obs.removeObserver(
      windowObserver,
      "browser-delayed-startup-finished"
    );
  });

  let extension = ExtensionTestUtils.loadExtension({
    manifest: {
      permissions: ["tabs"],
      protocol_handlers: [
        {
          protocol: "ext+bar",
          name: "a bar protocol handler",
          uriTemplate: "test.html?val=%s",
        },
      ],
    },

    background: `(${background})("moz-extension://${pageExt.uuid}/page.html")`,

    files: {
      "test.html": `<!DOCTYPE html><html><head><meta charset="utf-8"></head><body></body></html>`,
    },
  });

  await extension.startup();
  await extension.awaitFinish("window-create-url");
  await extension.unload();
  await pageExt.unload();

  Assert.equal(
    dialogCount,
    2,
    "Expected to see the right number of permission prompts."
  );

  // Make sure windows have been released before finishing.
  Cu.forceGC();
});