summaryrefslogtreecommitdiffstats
path: root/docshell/test/browser/browser_fission_maxOrigins.js
blob: b8efbe0ca1789c803595c84d4cb6aefae211353c (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
/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";

SimpleTest.requestFlakyTimeout("Need to test expiration timeout");

function delay(msec) {
  // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
  return new Promise(resolve => setTimeout(resolve, msec));
}

function promiseIdle() {
  return new Promise(resolve => {
    Services.tm.idleDispatchToMainThread(resolve);
  });
}

const ORIGIN_CAP = 5;
const SLIDING_WINDOW_MS = 5000;

const PREF_ORIGIN_CAP = "fission.experiment.max-origins.origin-cap";
const PREF_SLIDING_WINDOW_MS =
  "fission.experiment.max-origins.sliding-window-ms";
const PREF_QUALIFIED = "fission.experiment.max-origins.qualified";
const PREF_LAST_QUALIFIED = "fission.experiment.max-origins.last-qualified";
const PREF_LAST_DISQUALIFIED =
  "fission.experiment.max-origins.last-disqualified";

const SITE_ORIGINS = [
  // eslint-disable-next-line @microsoft/sdl/no-insecure-url
  "http://example.com/",
  // eslint-disable-next-line @microsoft/sdl/no-insecure-url
  "http://example.org/",
  // eslint-disable-next-line @microsoft/sdl/no-insecure-url
  "http://example.net/",
  // eslint-disable-next-line @microsoft/sdl/no-insecure-url
  "http://example.tw/",
  // eslint-disable-next-line @microsoft/sdl/no-insecure-url
  "http://example.cn/",
  // eslint-disable-next-line @microsoft/sdl/no-insecure-url
  "http://example.fi/",
  // eslint-disable-next-line @microsoft/sdl/no-insecure-url
  "http://example.in/",
  // eslint-disable-next-line @microsoft/sdl/no-insecure-url
  "http://example.lk/",
  // eslint-disable-next-line @microsoft/sdl/no-insecure-url
  "http://w3c-test.org/",
  "https://www.mozilla.org/",
];

function openTab(url) {
  return BrowserTestUtils.openNewForegroundTab({
    gBrowser,
    url,
    waitForStateStop: true,
  });
}

async function assertQualified() {
  // The unique origin calculation runs from an idle task, so make sure
  // the queued idle task has had a chance to run.
  await promiseIdle();

  // Make sure the clock has advanced since the qualification timestamp
  // was recorded.
  await delay(1);

  let qualified = Services.prefs.getBoolPref(PREF_QUALIFIED);
  let lastQualified = Services.prefs.getIntPref(PREF_LAST_QUALIFIED);
  let lastDisqualified = Services.prefs.getIntPref(PREF_LAST_DISQUALIFIED);
  let currentTime = Date.now() / 1000;

  ok(qualified, "Should be qualified");
  ok(
    lastQualified > 0,
    `Last qualified timestamp (${lastQualified}) should be greater than 0`
  );
  ok(
    lastQualified < currentTime,
    `Last qualified timestamp (${lastQualified}) should be less than the current time (${currentTime})`
  );
  ok(
    lastQualified > lastDisqualified,
    `Last qualified timestamp (${lastQualified}) should be after the last disqualified time (${lastDisqualified})`
  );

  ok(
    lastDisqualified < currentTime,
    `Last disqualified timestamp (${lastDisqualified}) should be less than the current time (${currentTime})`
  );
}

async function assertDisqualified(opts) {
  // The unique origin calculation runs from an idle task, so make sure
  // the queued idle task has had a chance to run.
  await promiseIdle();

  let qualified = Services.prefs.getBoolPref(PREF_QUALIFIED);
  let lastQualified = Services.prefs.getIntPref(PREF_LAST_QUALIFIED, 0);
  let lastDisqualified = Services.prefs.getIntPref(PREF_LAST_DISQUALIFIED);
  let currentTime = Date.now() / 1000;

  ok(!qualified, "Should not be qualified");
  if (!opts.initialValues) {
    ok(
      lastQualified > 0,
      `Last qualified timestamp (${lastQualified}) should be greater than 0`
    );
  }
  ok(
    lastQualified < currentTime,
    `Last qualified timestamp (${lastQualified}) should be less than the current time (${currentTime})`
  );

  ok(
    lastDisqualified < currentTime,
    `Last disqualified timestamp (${lastDisqualified}) should be less than the current time (${currentTime})`
  );

  ok(
    lastDisqualified > 0,
    `Last disqualified timestamp (${lastDisqualified}) should be greater than 0`
  );

  if (opts.qualificationPending) {
    ok(
      lastQualified > lastDisqualified,
      `Last qualified timestamp (${lastQualified}) should be after the last disqualified time (${lastDisqualified})`
    );
  } else {
    ok(
      lastDisqualified > lastQualified,
      `Last disqualified timestamp (${lastDisqualified}) should be after the last qualified time (${lastQualified})`
    );
  }
}

add_task(async function() {
  await SpecialPowers.pushPrefEnv({
    set: [
      [PREF_ORIGIN_CAP, ORIGIN_CAP],
      [PREF_SLIDING_WINDOW_MS, SLIDING_WINDOW_MS],
    ],
  });

  const { BrowserTelemetryUtils } = ChromeUtils.importESModule(
    "resource://gre/modules/BrowserTelemetryUtils.sys.mjs"
  );

  // Make sure we actually record telemetry for our disqualifying origin
  // count.
  BrowserTelemetryUtils.min_interval = 1;

  let tabs = [];

  // Open one initial tab to make sure the origin counting code has had
  // a chance to run before checking the initial state.
  tabs.push(await openTab("http://mochi.test:8888/"));

  await assertQualified();

  let lastDisqualified = Services.prefs.getIntPref(PREF_LAST_DISQUALIFIED);
  is(lastDisqualified, 0, "Last disqualification timestamp should be 0");

  info(
    `Opening ${SITE_ORIGINS.length} tabs with distinct origins to exceed the cap (${ORIGIN_CAP})`
  );
  ok(
    SITE_ORIGINS.length > ORIGIN_CAP,
    "Should have enough site origins to exceed the origin cap"
  );
  tabs.push(...(await Promise.all(SITE_ORIGINS.map(openTab))));

  await assertDisqualified({ qualificationPending: false });

  info("Close unique-origin tabs");
  await Promise.all(tabs.map(tab => BrowserTestUtils.removeTab(tab)));

  info("Open a new tab to trigger the origin count code once more");
  tabs = [await openTab(SITE_ORIGINS[0])];

  await assertDisqualified({ qualificationPending: true });

  info(
    "Wait long enough to clear the sliding window since last disqualified state"
  );
  await delay(SLIDING_WINDOW_MS + 1000);

  info("Open a new tab to trigger the origin count code again");
  tabs.push(await openTab(SITE_ORIGINS[0]));

  await assertQualified();

  info(
    "Clear preference values and re-populate the initial value from telemetry"
  );
  Services.prefs.clearUserPref(PREF_QUALIFIED);
  Services.prefs.clearUserPref(PREF_LAST_QUALIFIED);
  Services.prefs.clearUserPref(PREF_LAST_DISQUALIFIED);
  BrowserTelemetryUtils._checkedInitialExperimentQualification = false;

  info("Open a new tab to trigger the origin count code again");
  tabs.push(await openTab(SITE_ORIGINS[0]));

  await assertDisqualified({ initialValues: true });

  info(
    "Wait long enough to clear the sliding window since last disqualified state"
  );
  await delay(SLIDING_WINDOW_MS + 1000);

  info("Open a new tab to trigger the origin count code again");
  tabs.push(await openTab(SITE_ORIGINS[0]));

  await assertQualified();

  await Promise.all(tabs.map(tab => BrowserTestUtils.removeTab(tab)));

  // Clear the cached recording interval so it resets to the default
  // value on the next call.
  BrowserTelemetryUtils.min_interval = null;
});