summaryrefslogtreecommitdiffstats
path: root/browser/components/originattributes/test/browser/head.js
blob: bd4307fd1cea9915fc0afd7c3137896d89192ce8 (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
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const TEST_URL_PATH =
  "/browser/browser/components/originattributes/test/browser/";

// The flags of test modes.
const TEST_MODE_FIRSTPARTY = 0;
const TEST_MODE_NO_ISOLATION = 1;
const TEST_MODE_CONTAINERS = 2;

// The name of each mode.
const TEST_MODE_NAMES = ["first party isolation", "no isolation", "containers"];

// The frame types.
const TEST_TYPE_FRAME = 1;
const TEST_TYPE_IFRAME = 2;

// The default frame setting.
const DEFAULT_FRAME_SETTING = [TEST_TYPE_IFRAME];

let gFirstPartyBasicPage = TEST_URL_PATH + "file_firstPartyBasic.html";

/**
 * Add a tab for the given url with the specific user context id.
 *
 * @param aURL
 *    The url of the page.
 * @param aUserContextId
 *    The user context id for this tab.
 *
 * @return tab     - The tab object of this tab.
 *         browser - The browser object of this tab.
 */
async function openTabInUserContext(aURL, aUserContextId) {
  info(`Start to open tab in specific userContextID: ${aUserContextId}.`);
  let originAttributes = {
    userContextId: aUserContextId,
  };
  info("Create triggeringPrincipal.");
  let triggeringPrincipal =
    Services.scriptSecurityManager.createContentPrincipal(
      makeURI(aURL),
      originAttributes
    );
  // Open the tab in the correct userContextId.
  info("Open the tab and wait for it to be loaded.");
  let tab = BrowserTestUtils.addTab(gBrowser, aURL, {
    userContextId: aUserContextId,
    triggeringPrincipal,
  });

  // Select tab and make sure its browser is focused.
  gBrowser.selectedTab = tab;
  tab.ownerGlobal.focus();

  let browser = gBrowser.getBrowserForTab(tab);
  await BrowserTestUtils.browserLoaded(browser);
  info("Finished tab opening.");

  return { tab, browser };
}

/**
 * Add a tab for a page with the given first party domain. This page will have
 * an iframe which is loaded with the given url by default or you could specify
 * a frame setting to create nested frames. And this function will also modify
 * the 'content' in the ContentTask to the target frame's window object.
 *
 * @param aURL
 *    The url of the iframe.
 * @param aFirstPartyDomain
 *    The first party domain.
 * @param aFrameSetting
 *    This setting controls how frames are organized within the page. The
 *    setting is an array of frame types, the first item indicates the
 *    frame type (iframe or frame) of the first layer of the frame structure,
 *    and the second item indicates the second layer, and so on. The aURL will
 *    be loaded at the deepest layer. This is optional.
 *
 * @return tab     - The tab object of this tab.
 *         browser - The browser object of this tab.
 */
async function openTabInFirstParty(
  aURL,
  aFirstPartyDomain,
  aFrameSetting = DEFAULT_FRAME_SETTING
) {
  info(`Start to open tab under first party domain "${aFirstPartyDomain}".`);
  // If the first party domain ends with '/', we remove it.
  if (aFirstPartyDomain.endsWith("/")) {
    aFirstPartyDomain = aFirstPartyDomain.slice(0, -1);
  }

  let basicPageURL = aFirstPartyDomain + gFirstPartyBasicPage;

  // Open the tab for the basic first party page.
  info("Open the tab and then wait for it to be loaded.");
  let tab = BrowserTestUtils.addTab(gBrowser, basicPageURL);

  // Select tab and make sure its browser is focused.
  gBrowser.selectedTab = tab;
  tab.ownerGlobal.focus();

  let browser = gBrowser.getBrowserForTab(tab);
  await BrowserTestUtils.browserLoaded(browser);

  // Clone the frame setting here since we will modify it later.
  let frameSetting = aFrameSetting.slice(0);
  let frameType;
  let targetBrowsingContext;

  // Create the frame structure.
  info("Create the frame structure.");
  while ((frameType = frameSetting.shift())) {
    if (!targetBrowsingContext) {
      targetBrowsingContext = browser;
    }

    let frameURL = !frameSetting.length ? aURL : basicPageURL;

    if (frameType == TEST_TYPE_FRAME) {
      info("Add the frameset.");
      targetBrowsingContext = await SpecialPowers.spawn(
        targetBrowsingContext,
        [frameURL],
        async function (aFrameURL) {
          // Add a frameset which carries the frame element.
          let frameSet = content.document.createElement("frameset");
          frameSet.cols = "50%,50%";

          let frame = content.document.createElement("frame");
          let dummyFrame = content.document.createElement("frame");

          frameSet.appendChild(frame);
          frameSet.appendChild(dummyFrame);

          content.document.body.appendChild(frameSet);

          // Wait for the frame to be loaded.
          await new Promise(done => {
            frame.addEventListener(
              "load",
              function () {
                done();
              },
              { capture: true, once: true }
            );

            frame.setAttribute("src", aFrameURL);
          });

          return frame.browsingContext;
        }
      );
    } else if (frameType == TEST_TYPE_IFRAME) {
      info("Add the iframe.");
      targetBrowsingContext = await SpecialPowers.spawn(
        targetBrowsingContext,
        [frameURL],
        async function (aFrameURL) {
          // Add an iframe.
          let frame = content.document.createElement("iframe");
          content.document.body.appendChild(frame);

          // Wait for the frame to be loaded.
          await new Promise(done => {
            frame.addEventListener(
              "load",
              function () {
                done();
              },
              { capture: true, once: true }
            );

            frame.setAttribute("src", aFrameURL);
          });

          return frame.browsingContext;
        }
      );
    } else {
      ok(false, "Invalid frame type.");
      break;
    }
    info("Successfully added a frame");
  }
  info("Finished the frame structure");

  return { tab, browser: targetBrowsingContext };
}

this.IsolationTestTools = {
  /**
   * Adds isolation tests for first party isolation, no isolation
   * and containers respectively.
   *
   * @param aTask
   *    The testing task which will be run in different settings.
   */
  _add_task(aTask) {
    let testSettings = [
      {
        mode: TEST_MODE_FIRSTPARTY,
        skip: false,
        prefs: [["privacy.firstparty.isolate", true]],
      },
      {
        mode: TEST_MODE_NO_ISOLATION,
        skip: false,
        prefs: [["privacy.firstparty.isolate", false]],
      },
      {
        mode: TEST_MODE_CONTAINERS,
        skip: false,
        prefs: [["privacy.userContext.enabled", true]],
      },
    ];

    // Add test tasks.
    for (let testSetting of testSettings) {
      IsolationTestTools._addTaskForMode(
        testSetting.mode,
        testSetting.prefs,
        testSetting.skip,
        aTask
      );
    }
  },

  _addTaskForMode(aMode, aPref, aSkip, aTask) {
    if (aSkip) {
      return;
    }

    add_task(async function () {
      info(`Starting the test for ${TEST_MODE_NAMES[aMode]}.`);

      // Before run this task, reset the preferences first.
      await SpecialPowers.flushPrefEnv();

      // Make sure preferences are set properly.
      await SpecialPowers.pushPrefEnv({ set: aPref });

      await SpecialPowers.pushPrefEnv({ set: [["dom.ipc.processCount", 1]] });
      await SpecialPowers.pushPrefEnv({
        set: [
          [
            "network.auth.non-web-content-triggered-resources-http-auth-allow",
            true,
          ],
        ],
      });

      await aTask(aMode);
    });
  },

  /**
   * Add a tab with the given tab setting, this will open different types of
   * tabs according to the given test mode. A tab setting means a isolation
   * target in different test mode; a tab setting indicates a first party
   * domain when testing the first party isolation, it is a user context
   * id when testing containers.
   *
   * @param aMode
   *    The test mode which decides what type of tabs will be opened.
   * @param aURL
   *    The url which is going to open.
   * @param aTabSettingObj
   *    The tab setting object includes 'firstPartyDomain' for the first party
   *    domain and 'userContextId' for Containers.
   * @param aFrameSetting
   *    This setting controls how frames are organized within the page. The
   *    setting is an array of frame types, the first item indicates the
   *    frame type (iframe or frame) of the first layer of the frame structure,
   *    and the second item indicates the second layer, and so on. The aURL
   *    will be loaded at the deepest layer. This is optional.
   *
   * @return tab     - The tab object of this tab.
   *         browser - The browser object of this tab.
   */
  _addTab(aMode, aURL, aTabSettingObj, aFrameSetting) {
    if (aMode === TEST_MODE_CONTAINERS) {
      return openTabInUserContext(aURL, aTabSettingObj.userContextId);
    }

    return openTabInFirstParty(
      aURL,
      aTabSettingObj.firstPartyDomain,
      aFrameSetting
    );
  },

  /**
   * Run isolation tests. The framework will run tests with standard combinations
   * of prefs and tab settings, and checks whether the isolation is working.
   *
   * @param aURL
   *    The URL of the page that will be tested or an object contains 'url',
   *    the tested page, 'firstFrameSetting' for the frame setting of the first
   *    tab, and 'secondFrameSetting' for the second tab.
   * @param aGetResultFuncs
   *    An array of functions or a single function which are responsible for
   *    returning the isolation result back to the framework for further checking.
   *    Each of these functions will be provided the browser object of the tab,
   *    that allows modifying or fetchings results from the page content.
   * @param aCompareResultFunc
   *    An optional function which allows modifying the way how does framework
   *    check results. This function will be provided a boolean to indicate
   *    the isolation is no or off and two results. This function should return
   *    a boolean to tell that whether isolation is working. If this function
   *    is not given, the framework will take case checking by itself.
   * @param aBeforeFunc
   *    An optional function which is called before any tabs are created so
   *    that the test case can set up/reset local state.
   * @param aGetResultImmediately
   *    An optional boolean to ensure we get results before the next tab is opened.
   */
  runTests(
    aURL,
    aGetResultFuncs,
    aCompareResultFunc,
    aBeforeFunc,
    aGetResultImmediately,
    aUseHttps
  ) {
    let pageURL;
    let firstFrameSetting;
    let secondFrameSetting;

    // Request a longer timeout since the test will run a test for three times
    // with different settings. Thus, one test here represents three tests.
    // For this reason, we triple the timeout.
    requestLongerTimeout(3);

    if (typeof aURL === "string") {
      pageURL = aURL;
    } else if (typeof aURL === "object") {
      pageURL = aURL.url;
      firstFrameSetting = aURL.firstFrameSetting;
      secondFrameSetting = aURL.secondFrameSetting;
    }

    if (!Array.isArray(aGetResultFuncs)) {
      aGetResultFuncs = [aGetResultFuncs];
    }

    let tabSettings = aUseHttps
      ? [
          { firstPartyDomain: "https://example.com", userContextId: 1 },
          { firstPartyDomain: "https://example.org", userContextId: 2 },
        ]
      : [
          { firstPartyDomain: "http://example.com", userContextId: 1 },
          { firstPartyDomain: "http://example.org", userContextId: 2 },
        ];

    this._add_task(async function (aMode) {
      let tabSettingA = 0;

      for (let tabSettingB of [0, 1]) {
        // Give the test a chance to set up before each case is run.
        if (aBeforeFunc) {
          try {
            await aBeforeFunc(aMode);
          } catch (e) {
            ok(false, `Caught error while doing testing setup: ${e}.`);
          }
        }
        // Create Tabs.
        info(`Create tab A for ${TEST_MODE_NAMES[aMode]} test.`);
        let tabInfoA = await IsolationTestTools._addTab(
          aMode,
          pageURL,
          tabSettings[tabSettingA],
          firstFrameSetting
        );
        info(`Finished Create tab A for ${TEST_MODE_NAMES[aMode]} test.`);
        let resultsA = [];
        if (aGetResultImmediately) {
          try {
            info(
              `Immediately get result from tab A for ${TEST_MODE_NAMES[aMode]} test`
            );
            for (let getResultFunc of aGetResultFuncs) {
              resultsA.push(await getResultFunc(tabInfoA.browser));
            }
          } catch (e) {
            ok(false, `Caught error while getting result from Tab A: ${e}.`);
          }
        }
        info(`Create tab B for ${TEST_MODE_NAMES[aMode]}.`);
        let tabInfoB = await IsolationTestTools._addTab(
          aMode,
          pageURL,
          tabSettings[tabSettingB],
          secondFrameSetting
        );
        info(`Finished Create tab B for ${TEST_MODE_NAMES[aMode]} test.`);
        let i = 0;
        for (let getResultFunc of aGetResultFuncs) {
          // Fetch results from tabs.
          info(`Fetching result from tab A for ${TEST_MODE_NAMES[aMode]}.`);
          let resultA;
          try {
            resultA = aGetResultImmediately
              ? resultsA[i++]
              : await getResultFunc(tabInfoA.browser);
          } catch (e) {
            ok(false, `Caught error while getting result from Tab A: ${e}.`);
          }
          info(`Fetching result from tab B for ${TEST_MODE_NAMES[aMode]}.`);
          let resultB;
          try {
            resultB = await getResultFunc(tabInfoB.browser);
          } catch (e) {
            ok(false, `Caught error while getting result from Tab B: ${e}.`);
          }
          // Compare results.
          let result = false;
          let shouldIsolate =
            aMode !== TEST_MODE_NO_ISOLATION && tabSettingA !== tabSettingB;
          if (aCompareResultFunc) {
            result = await aCompareResultFunc(shouldIsolate, resultA, resultB);
          } else {
            result = shouldIsolate ? resultA !== resultB : resultA === resultB;
          }

          let msg =
            `Result of Testing ${TEST_MODE_NAMES[aMode]} for ` +
            `isolation ${shouldIsolate ? "on" : "off"} with TabSettingA ` +
            `${tabSettingA} and tabSettingB ${tabSettingB}` +
            `, resultA = ${resultA}, resultB = ${resultB}`;

          ok(result, msg);
        }

        // Close Tabs.
        BrowserTestUtils.removeTab(tabInfoA.tab);
        BrowserTestUtils.removeTab(tabInfoB.tab);

        // A workaround for avoiding a timing issue in Fission. This workaround
        // makes sure that the shutdown process between parent and content
        // is finished before the next round of testing.
        if (SpecialPowers.useRemoteSubframes) {
          await new Promise(resolve => {
            let observer = (subject, topic, data) => {
              if (topic === "ipc:content-shutdown") {
                Services.obs.removeObserver(observer, "ipc:content-shutdown");
                resolve();
              }
            };
            Services.obs.addObserver(observer, "ipc:content-shutdown");
          });
        }
      }
    });
  },
};