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

// This test verifies that the internals for associating requests with tabId
// are only active when a session rule with a tabId rule exists.
//
// There are tests for the logic of tabId matching in the match_tabIds task in
// toolkit/components/extensions/test/xpcshell/test_ext_dnr_testMatchOutcome.js
//
// And there are tests that verify matching with real network requests in
// toolkit/components/extensions/test/mochitest/test_ext_dnr_tabIds.html

const server = createHttpServer({ hosts: ["from", "any", "in", "ex"] });
server.registerPathHandler("/", (req, res) => {
  res.setHeader("Access-Control-Allow-Origin", "*");
});

let gTabLookupSpy;

add_setup(async () => {
  Services.prefs.setBoolPref("extensions.manifestV3.enabled", true);
  Services.prefs.setBoolPref("extensions.dnr.enabled", true);

  // Install a spy on WebRequest.getTabIdForChannelWrapper.
  const { WebRequest } = ChromeUtils.importESModule(
    "resource://gre/modules/WebRequest.sys.mjs"
  );
  const { sinon } = ChromeUtils.importESModule(
    "resource://testing-common/Sinon.sys.mjs"
  );
  gTabLookupSpy = sinon.spy(WebRequest, "getTabIdForChannelWrapper");

  await ExtensionTestUtils.startAddonManager();
});

function numberOfTabLookupsSinceLastCheck() {
  let result = gTabLookupSpy.callCount;
  gTabLookupSpy.resetHistory();
  return result;
}

// This test checks that WebRequest.getTabIdForChannelWrapper is only called
// when there are any registered tabId/excludedTabIds rules. Moreover, it
// verifies that after unloading (reloading) the extension, that the method is
// still not called unnecessarily.
add_task(async function getTabIdForChannelWrapper_only_called_when_needed() {
  async function background() {
    const RULE_ANY_TAB_ID = {
      id: 1,
      condition: { requestDomains: ["from"] },
      action: { type: "redirect", redirect: { url: "http://any/" } },
    };
    const RULE_INCLUDE_TAB_ID = {
      id: 2,
      condition: { requestDomains: ["from"], tabIds: [-1] },
      action: { type: "redirect", redirect: { url: "http://in/" } },
      priority: 2,
    };
    const RULE_EXCLUDE_TAB_ID = {
      id: 3,
      condition: { requestDomains: ["from"], excludedTabIds: [-1] },
      action: { type: "redirect", redirect: { url: "http://ex/" } },
      priority: 2,
    };
    async function promiseOneMessage(messageName) {
      return new Promise(resolve => {
        browser.test.onMessage.addListener(function listener(msg, result) {
          if (messageName === msg) {
            browser.test.onMessage.removeListener(listener);
            resolve(result);
          }
        });
      });
    }
    async function numberOfTabLookupsSinceLastCheck() {
      let promise = promiseOneMessage("tabLookups");
      browser.test.sendMessage("getTabLookups");
      return promise;
    }
    async function testFetchUrl(url, expectedUrl, expectedCount, description) {
      let res = await fetch(url);
      browser.test.assertEq(expectedUrl, res.url, `Final URL for ${url}`);
      browser.test.assertEq(
        expectedCount,
        await numberOfTabLookupsSinceLastCheck(),
        `Expected number of tab lookups - ${url} - ${description}`
      );
    }

    const startupCountPromise = promiseOneMessage("extensionStartupCount");
    browser.test.sendMessage("extensionStarted");
    const startupCount = await startupCountPromise;
    if (startupCount !== 0) {
      browser.test.assertEq(1, startupCount, "Extension restarted once");

      // Note: declarativeNetRequest.updateSessionRules is intentionally not
      // called here, because we want to verify that upon unloading the
      // extension, that the tabId lookup logic was properly cleaned up,
      // i.e. that NetworkIntegration.maybeUpdateTabIdChecker() was called.

      await testFetchUrl(
        "http://from/?after-restart-supposedly-no-include-tab",
        "http://from/?after-restart-supposedly-no-include-tab",
        0,
        "No lookup because session rules should have disappeared at reload"
      );

      browser.test.assertDeepEq(
        [],
        await browser.declarativeNetRequest.getSessionRules(),
        "The session rules have indeed been cleared upon reload."
      );

      browser.test.sendMessage("test_completed_after_reload");
      return;
    }

    browser.test.assertEq(
      0,
      await numberOfTabLookupsSinceLastCheck(),
      "Initially, no tab lookups"
    );

    await testFetchUrl(
      "http://from/?no_dnr_rules",
      "http://from/?no_dnr_rules",
      0,
      "No tab lookups without any registered DNR rules"
    );

    await browser.declarativeNetRequest.updateSessionRules({
      addRules: [RULE_ANY_TAB_ID],
    });
    // Active rules now: RULE_ANY_TAB_ID

    await testFetchUrl(
      "http://from/?only_dnr_rule_matches_any_tab",
      "http://any/",
      0,
      "No tab lookups when only rule has no tabIds/excludedTabIds conditions"
    );

    await browser.declarativeNetRequest.updateSessionRules({
      addRules: [RULE_EXCLUDE_TAB_ID],
    });
    // Active rules now: RULE_ANY_TAB_ID, RULE_EXCLUDE_TAB_ID

    await testFetchUrl(
      "http://from/?dnr_rule_matches_any,dnr_rule_excludes_-1",
      // should be "any" instead of "ex" because excludedTabIds: [-1] should
      // exclude the background.
      "http://any/",
      2, // initial request + redirect request.
      "Expected tabId lookup when a tabId rule is registered"
    );

    await browser.declarativeNetRequest.updateSessionRules({
      removeRuleIds: [RULE_ANY_TAB_ID.id],
    });
    // Active rules now: RULE_EXCLUDE_TAB_ID

    await testFetchUrl(
      "http://from/?only_dnr_rule_excludes_-1",
      // Not redirected to "ex" because excludedTabIds: [-1] does not match the
      // background that has tabId -1.
      "http://from/?only_dnr_rule_excludes_-1",
      1,
      "Expected lookup after unregistering unrelated rule, keeping tabId rule"
    );

    await browser.declarativeNetRequest.updateSessionRules({
      addRules: [RULE_INCLUDE_TAB_ID],
    });
    // Active rules now: RULE_EXCLUDE_TAB_ID, RULE_INCLUDE_TAB_ID
    await testFetchUrl(
      "http://from/?two_dnr_rule_include_and_exclude_-1",
      "http://in/",
      2, // initial request + redirect request.
      "Expecting lookup because of 2 DNR rules with tabId and excludedTabIds"
    );

    await browser.declarativeNetRequest.updateSessionRules({
      removeRuleIds: [RULE_EXCLUDE_TAB_ID.id],
    });
    // Active rules now: RULE_INCLUDE_TAB_ID

    await testFetchUrl(
      "http://from/?only_dnr_rule_includes_-1",
      "http://in/",
      2, // initial request + redirect request.
      "Expecting lookup because of remaining tabId DNR rule"
    );

    await browser.declarativeNetRequest.updateSessionRules({
      removeRuleIds: [RULE_INCLUDE_TAB_ID.id],
    });
    // Active rules now: none

    await testFetchUrl(
      "http://from/?no_rules_again",
      "http://from/?no_rules_again",
      0,
      "Expected no lookups after unregistering the last remaining rule"
    );

    await browser.declarativeNetRequest.updateSessionRules({
      addRules: [RULE_INCLUDE_TAB_ID],
    });
    // Active rules now: RULE_INCLUDE_TAB_ID

    await testFetchUrl(
      "http://from/?again_with-include-1",
      "http://in/",
      2, // initial request + redirect request.
      "Expecting lookup again because of include rule"
    );

    // Ending test with remaining rule: RULE_INCLUDE_TAB_ID
    // Reload extension.
    browser.test.sendMessage("reload_extension");
  }
  let extension = ExtensionTestUtils.loadExtension({
    background,
    useAddonManager: "temporary", // for reload and granted_host_permissions.
    allowInsecureRequests: true,
    manifest: {
      manifest_version: 3,
      host_permissions: ["*://from/*"],
      granted_host_permissions: true,
      permissions: ["declarativeNetRequest"],
    },
  });
  extension.onMessage("getTabLookups", () => {
    extension.sendMessage("tabLookups", numberOfTabLookupsSinceLastCheck());
  });
  let startupCount = 0;
  extension.onMessage("extensionStarted", () => {
    extension.sendMessage("extensionStartupCount", startupCount++);
  });
  await extension.startup();
  await extension.awaitMessage("reload_extension");
  await extension.addon.reload();
  await extension.awaitMessage("test_completed_after_reload");
  Assert.equal(
    0,
    numberOfTabLookupsSinceLastCheck(),
    "No new tab lookups since completion of extension tests"
  );
  await extension.unload();
});