summaryrefslogtreecommitdiffstats
path: root/toolkit/components/antitracking/test/browser/storage_access_head.js
blob: ea4f67b4fe28bdd13f12482489b17cf4bb1f13f0 (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
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
/* import-globals-from ../../../../../browser/modules/test/browser/head.js */
/* import-globals-from antitracking_head.js */

async function openPageAndRunCode(
  topPage,
  topPageCallback,
  embeddedPage,
  embeddedPageCallback
) {
  let tab = await BrowserTestUtils.openNewForegroundTab({
    gBrowser,
    url: topPage,
    waitForLoad: true,
  });
  let browser = gBrowser.getBrowserForTab(tab);

  await topPageCallback();
  await SpecialPowers.spawn(
    browser,
    [{ page: embeddedPage, callback: embeddedPageCallback.toString() }],
    async function (obj) {
      await new content.Promise(resolve => {
        let ifr = content.document.createElement("iframe");
        ifr.onload = function () {
          ifr.contentWindow.postMessage(obj.callback, "*");
        };

        content.addEventListener("message", function msg(event) {
          if (event.data.type == "finish") {
            content.removeEventListener("message", msg);
            resolve();
            return;
          }

          if (event.data.type == "ok") {
            ok(event.data.what, event.data.msg);
            return;
          }

          if (event.data.type == "info") {
            info(event.data.msg);
            return;
          }

          ok(false, "Unknown message");
        });

        content.document.body.appendChild(ifr);
        ifr.src = obj.page;
      });
    }
  );

  await BrowserTestUtils.removeTab(tab);
}

// This function returns a function that spawns an asynchronous task to handle
// the popup and click on the appropriate values. If that task is never executed
// the catch case is reached and we fail the test. If for some reason that catch
// case isn't reached, having an extra event listener at the end of the test
// will cause the test to fail anyway.
// Note: this means that tests that use this callback should probably be in
// their own test file.
function getExpectPopupAndClick(accept) {
  return function () {
    let shownPromise = BrowserTestUtils.waitForEvent(
      PopupNotifications.panel,
      "popupshown"
    );
    shownPromise
      .then(async _ => {
        // This occurs when the promise resolves on the test finishing
        let popupNotifications = PopupNotifications.panel.childNodes;
        if (!popupNotifications.length) {
          ok(false, "Prompt did not show up");
        } else if (accept == "accept") {
          ok(true, "Prompt shows up, clicking accept.");
          await clickMainAction();
        } else if (accept == "reject") {
          ok(true, "Prompt shows up, clicking reject.");
          await clickSecondaryAction();
        } else {
          ok(false, "Unknown accept value for test: " + accept);
          info("Clicking accept so that the test can finish.");
          await clickMainAction();
        }
      })
      .catch(() => {
        ok(false, "Prompt did not show up");
      });
  };
}

// Click popup after a delay of {timeout} ms
function getExpectPopupAndClickAfterDelay(accept, timeout) {
  return function () {
    let shownPromise = BrowserTestUtils.waitForEvent(
      PopupNotifications.panel,
      "popupshown"
    );
    shownPromise
      .then(
        setTimeout(async _ => {
          // This occurs when the promise resolves on the test finishing
          let popupNotifications = PopupNotifications.panel.childNodes;
          if (!popupNotifications.length) {
            ok(false, "Prompt did not show up");
          } else if (accept == "accept") {
            ok(true, "Prompt shows up, clicking accept.");
            await clickMainAction();
          } else if (accept == "reject") {
            ok(true, "Prompt shows up, clicking reject.");
            await clickSecondaryAction();
          } else {
            ok(false, "Unknown accept value for test: " + accept);
            info("Clicking accept so that the test can finish.");
            await clickMainAction();
          }
        }, timeout)
      )
      .catch(() => {
        ok(false, "Prompt did not show up");
      });
  };
}

// This function spawns an asynchronous task that fails the test if a popup
// appears. If that never happens, the catch case is executed on the test
// cleanup.
// Note: this means that tests that use this callback should probably be in
// their own test file.
function expectNoPopup() {
  let shownPromise = BrowserTestUtils.waitForEvent(
    PopupNotifications.panel,
    "popupshown"
  );
  shownPromise
    .then(async _ => {
      // This occurs when the promise resolves on the test finishing
      let popupNotifications = PopupNotifications.panel.childNodes;
      if (!popupNotifications.length) {
        ok(true, "Prompt did not show up");
      } else {
        ok(false, "Prompt shows up");
        info(PopupNotifications.panel);
        await clickSecondaryAction();
      }
    })
    .catch(() => {
      ok(true, "Prompt did not show up");
    });
}

async function requestStorageAccessAndExpectSuccess() {
  const aps = SpecialPowers.Services.prefs.getBoolPref(
    "privacy.partition.always_partition_third_party_non_cookie_storage"
  );

  // When always partitioning storage, we do not clear non-cookie storage
  // after a requestStorageAccess is accepted by the user. So here we test
  // that indexedDB is cleared when the pref is off, but not when it is on.
  await new Promise((resolve, reject) => {
    const db = window.indexedDB.open("rSATest", 1);
    db.onupgradeneeded = resolve;
    db.success = resolve;
    db.onerror = reject;
  });

  const hadAccessAlready = await document.hasStorageAccess();
  const shouldClearIDB = !aps && !hadAccessAlready;

  SpecialPowers.wrap(document).notifyUserGestureActivation();
  let p = document.requestStorageAccess();
  try {
    await p;
    ok(true, "gain storage access.");
  } catch {
    ok(false, "denied storage access.");
  }

  await new Promise((resolve, reject) => {
    const req = window.indexedDB.open("rSATest", 1);
    req.onerror = reject;
    req.onupgradeneeded = () => {
      ok(shouldClearIDB, "iDB was cleared");
      req.onsuccess = undefined;
      resolve();
    };
    req.onsuccess = () => {
      ok(!shouldClearIDB, "iDB was not cleared");
      resolve();
    };
  });

  await new Promise(resolve => {
    const req = window.indexedDB.deleteDatabase("rSATest");
    req.onsuccess = resolve;
    req.onerror = resolve;
  });

  SpecialPowers.wrap(document).clearUserGestureActivation();
}

async function requestStorageAccessAndExpectFailure() {
  // When always partitioning storage, we do not clear non-cookie storage
  // after a requestStorageAccess is accepted by the user. So here we test
  // that indexedDB is cleared when the pref is off, but not when it is on.
  await new Promise((resolve, reject) => {
    const db = window.indexedDB.open("rSATest", 1);
    db.onupgradeneeded = resolve;
    db.success = resolve;
    db.onerror = reject;
  });

  SpecialPowers.wrap(document).notifyUserGestureActivation();
  let p = document.requestStorageAccess();
  try {
    await p;
    ok(false, "gain storage access.");
  } catch {
    ok(true, "denied storage access.");
  }

  await new Promise((resolve, reject) => {
    const req = window.indexedDB.open("rSATest", 1);
    req.onerror = reject;
    req.onupgradeneeded = () => {
      ok(false, "iDB was cleared");
      req.onsuccess = undefined;
      resolve();
    };
    req.onsuccess = () => {
      ok(true, "iDB was not cleared");
      resolve();
    };
  });

  await new Promise(resolve => {
    const req = window.indexedDB.deleteDatabase("rSATest");
    req.onsuccess = resolve;
    req.onerror = resolve;
  });

  SpecialPowers.wrap(document).clearUserGestureActivation();
}

async function cleanUpData() {
  await new Promise(resolve => {
    Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
      resolve()
    );
  });
  ok(true, "Deleted all data.");
}

async function setPreferences(alwaysPartitionStorage = true) {
  await SpecialPowers.pushPrefEnv({
    set: [
      ["dom.storage_access.auto_grants", true],
      ["dom.storage_access.auto_grants.delayed", false],
      ["dom.storage_access.enabled", true],
      ["dom.storage_access.max_concurrent_auto_grants", 0],
      ["dom.storage_access.prompt.testing", false],
      [
        "network.cookie.cookieBehavior",
        Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
      ],
      [
        "network.cookie.cookieBehavior.pbmode",
        Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
      ],
      [
        "privacy.partition.always_partition_third_party_non_cookie_storage",
        alwaysPartitionStorage,
      ],
      ["privacy.trackingprotection.enabled", false],
      ["privacy.trackingprotection.pbmode.enabled", false],
      ["privacy.trackingprotection.annotate_channels", true],
    ],
  });
}