summaryrefslogtreecommitdiffstats
path: root/browser/base/content/test/webrtc/browser_tab_switch_warning.js
blob: f0625ab4ca6671daf55d536b382236507a0b7974 (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
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

/**
 * Tests the warning that is displayed when switching to background
 * tabs when sharing the browser window or screen
 */

// The number of tabs to have in the background for testing.
const NEW_BACKGROUND_TABS_TO_OPEN = 5;
const WARNING_PANEL_ID = "sharing-tabs-warning-panel";
const ALLOW_BUTTON_ID = "sharing-warning-proceed-to-tab";
const DISABLE_WARNING_FOR_SESSION_CHECKBOX_ID =
  "sharing-warning-disable-for-session";
const WINDOW_SHARING_HEADER_ID = "sharing-warning-window-panel-header";
const SCREEN_SHARING_HEADER_ID = "sharing-warning-screen-panel-header";
// The number of milliseconds we're willing to wait for the
// warning panel before we decide that it's not coming.
const WARNING_PANEL_TIMEOUT_MS = 1000;
const CTRL_TAB_RUO_PREF = "browser.ctrlTab.sortByRecentlyUsed";

/**
 * Common helper function that pretendToShareWindow and pretendToShareScreen
 * call into. Ensures that the first tab is selected, and then (optionally)
 * does the first "freebie" tab switch to the second tab.
 *
 * @param {boolean} doFirstTabSwitch - True if this function should take
 *   care of doing the "freebie" tab switch for you.
 * @return {Promise}
 * @resolves {undefined} - Once the simulation is set up.
 */
async function pretendToShareDisplay(doFirstTabSwitch) {
  Assert.equal(
    gBrowser.selectedTab,
    gBrowser.tabs[0],
    "Should start on the first tab."
  );

  webrtcUI.sharingDisplay = true;
  if (doFirstTabSwitch) {
    await BrowserTestUtils.switchTab(gBrowser, gBrowser.tabs[1]);
  }
}

/**
 * Simulates the sharing of a particular browser window. The
 * simulation doesn't actually share the window over WebRTC, but
 * does enough to convince webrtcUI that the window is in the shared
 * window list.
 *
 * It is assumed that the first tab is the selected tab when calling
 * this function.
 *
 * This helper function can also automatically perform the first
 * "freebie" tab switch that never warns. This is its default behaviour.
 *
 * @param {DOM Window} aWindow - The window that we're simulating sharing.
 * @param {boolean} doFirstTabSwitch - True if this function should take
 *   care of doing the "freebie" tab switch for you. Defaults to true.
 * @return {Promise}
 * @resolves {undefined} - Once the simulation is set up.
 */
async function pretendToShareWindow(aWindow, doFirstTabSwitch = true) {
  // Poke into webrtcUI so that it thinks that the current browser
  // window is being shared.
  webrtcUI.sharedBrowserWindows.add(aWindow);
  await pretendToShareDisplay(doFirstTabSwitch);
}

/**
 * Simulates the sharing of the screen. The simulation doesn't actually share
 * the screen over WebRTC, but does enough to convince webrtcUI that the screen
 * is being shared.
 *
 * It is assumed that the first tab is the selected tab when calling
 * this function.
 *
 * This helper function can also automatically perform the first
 * "freebie" tab switch that never warns. This is its default behaviour.
 *
 * @param {boolean} doFirstTabSwitch - True if this function should take
 *   care of doing the "freebie" tab switch for you. Defaults to true.
 * @return {Promise}
 * @resolves {undefined} - Once the simulation is set up.
 */
async function pretendToShareScreen(doFirstTabSwitch = true) {
  // Poke into webrtcUI so that it thinks that the current screen is being
  // shared.
  webrtcUI.sharingScreen = true;
  await pretendToShareDisplay(doFirstTabSwitch);
}

/**
 * Resets webrtcUI's notion of what is being shared. This also clears
 * out any simulated shared windows, and resets any state that only
 * persists for a sharing session.
 *
 * This helper function will also:
 * 1. Switch back to the first tab if it's not already selected.
 * 2. Check if the tab switch warning panel is open, and if so, close it.
 *
 * @return {Promise}
 * @resolves {undefined} - Once the state is reset.
 */
async function resetDisplaySharingState() {
  let firstTabBC = gBrowser.browsers[0].browsingContext;
  webrtcUI.streamAddedOrRemoved(firstTabBC, { remove: true });

  if (gBrowser.selectedTab !== gBrowser.tabs[0]) {
    await BrowserTestUtils.switchTab(gBrowser, gBrowser.tabs[0]);
  }

  let panel = document.getElementById(WARNING_PANEL_ID);
  if (panel && (panel.state == "open" || panel.state == "showing")) {
    info("Closing the warning panel.");
    let panelHidden = BrowserTestUtils.waitForEvent(panel, "popuphidden");
    panel.hidePopup();
    await panelHidden;
  }
}

/**
 * Checks to make sure that a tab switch warning doesn't show
 * within WARNING_PANEL_TIMEOUT_MS milliseconds.
 *
 * @return {Promise}
 * @resolves {undefined} - Once the check is complete.
 */
async function ensureNoWarning() {
  let timerExpired = false;
  let sawWarning = false;

  let resolver;
  let timeoutOrPopupShowingPromise = new Promise(resolve => {
    resolver = resolve;
  });

  let onPopupShowing = event => {
    if (event.target.id == WARNING_PANEL_ID) {
      sawWarning = true;
      resolver();
    }
  };
  // The panel might not have been lazily-inserted yet, so we
  // attach the popupshowing handler to the window instead.
  window.addEventListener("popupshowing", onPopupShowing);

  // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
  let timer = setTimeout(() => {
    timerExpired = true;
    resolver();
  }, WARNING_PANEL_TIMEOUT_MS);

  await timeoutOrPopupShowingPromise;

  clearTimeout(timer);
  window.removeEventListener("popupshowing", onPopupShowing);

  Assert.ok(timerExpired, "Timer should have expired.");
  Assert.ok(!sawWarning, "Should not have shown the tab switch warning.");
}

/**
 * Checks to make sure that a tab switch warning appears for
 * a particular tab.
 *
 * @param {<xul:tab>} tab - The tab that the warning should be anchored to.
 * @return {Promise}
 * @resolves {undefined} - Once the check is complete.
 */
async function ensureWarning(tab) {
  let popupShowingEvent = await BrowserTestUtils.waitForEvent(
    window,
    "popupshowing",
    false,
    event => {
      return event.target.id == WARNING_PANEL_ID;
    }
  );
  let panel = popupShowingEvent.target;

  Assert.equal(
    panel.anchorNode,
    tab,
    "Expected the warning to be anchored to the right tab."
  );
}

add_setup(async function () {
  await SpecialPowers.pushPrefEnv({
    set: [["privacy.webrtc.sharedTabWarning", true]],
  });

  // Loads up NEW_BACKGROUND_TABS_TO_OPEN background tabs at about:blank,
  // and waits until they're fully open.
  let uris = new Array(NEW_BACKGROUND_TABS_TO_OPEN).fill("about:blank");

  let loadPromises = Promise.all(
    uris.map(uri => BrowserTestUtils.waitForNewTab(gBrowser, uri, false, true))
  );

  gBrowser.loadTabs(uris, {
    inBackground: true,
    triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
  });

  await loadPromises;

  // Switches to the first tab and closes all of the rest.
  registerCleanupFunction(async () => {
    await resetDisplaySharingState();
    gBrowser.removeAllTabsBut(gBrowser.tabs[0]);
  });
});

/**
 * Tests that when sharing the window that the first tab switch does _not_ show
 * the warning. This is because we presume that the first tab switch since
 * starting display sharing is for a tab that is intentionally being shared.
 */
add_task(async function testFirstTabSwitchAllowed() {
  pretendToShareWindow(window, false);

  let targetTab = gBrowser.tabs[1];

  let noWarningPromise = ensureNoWarning();
  await BrowserTestUtils.switchTab(gBrowser, targetTab);
  await noWarningPromise;

  await resetDisplaySharingState();
});

/**
 * Tests that the second tab switch after sharing is not allowed
 * without a warning. Also tests that the warning can "allow"
 * the tab switch to proceed, and that no warning is subsequently
 * shown for the "allowed" tab. Finally, ensures that if the sharing
 * session ends and a new session begins, that warnings are shown
 * again for the allowed tabs.
 */
add_task(async function testWarningOnSecondTabSwitch() {
  pretendToShareWindow(window);
  let originalTab = gBrowser.selectedTab;

  // pretendToShareWindow will have switched us to the second
  // tab automatically as the first "freebie" tab switch

  let targetTab = gBrowser.tabs[2];

  // Ensure that we show the warning on the second tab switch
  let warningPromise = ensureWarning(targetTab);
  await BrowserTestUtils.switchTab(gBrowser, targetTab);
  await warningPromise;

  // Not only should we have warned, but we should have prevented
  // the tab switch from occurring.
  Assert.equal(
    gBrowser.selectedTab,
    originalTab,
    "Should still be on the original tab."
  );

  // Now test the "Allow" button in the warning to make sure the tab
  // switch goes through.
  let tabSwitchPromise = BrowserTestUtils.waitForEvent(
    gBrowser,
    "TabSwitchDone"
  );
  let allowButton = document.getElementById(ALLOW_BUTTON_ID);
  allowButton.click();
  await tabSwitchPromise;

  Assert.equal(
    gBrowser.selectedTab,
    targetTab,
    "Should have switched tabs to the target."
  );

  // We shouldn't see a warning when switching back to that first
  // "freebie" tab.
  let noWarningPromise = ensureNoWarning();
  await BrowserTestUtils.switchTab(gBrowser, originalTab);
  await noWarningPromise;

  Assert.equal(
    gBrowser.selectedTab,
    originalTab,
    "Should have switched tabs back to the original tab."
  );

  // We shouldn't see a warning when switching back to the tab that
  // we had just allowed.
  noWarningPromise = ensureNoWarning();
  await BrowserTestUtils.switchTab(gBrowser, targetTab);
  await noWarningPromise;

  Assert.equal(
    gBrowser.selectedTab,
    targetTab,
    "Should have switched tabs back to the target tab."
  );

  // Reset the sharing state, and make sure that warnings can
  // be displayed again.
  await resetDisplaySharingState();
  pretendToShareWindow(window);

  // pretendToShareWindow will have switched us to the second
  // tab automatically as the first "freebie" tab switch
  //
  // Make sure we get the warning again when switching to the
  // target tab.
  warningPromise = ensureWarning(targetTab);
  await BrowserTestUtils.switchTab(gBrowser, targetTab);
  await warningPromise;

  await resetDisplaySharingState();
});

/**
 * Tests that warnings can be skipped for a session via the
 * checkbox in the warning panel. Also checks that once the
 * session ends and a new one begins that warnings are displayed
 * again.
 */
add_task(async function testDisableWarningForSession() {
  pretendToShareWindow(window);

  // pretendToShareWindow will have switched us to the second
  // tab automatically as the first "freebie" tab switch
  let targetTab = gBrowser.tabs[2];

  // Ensure that we show the warning on the second tab switch
  let warningPromise = ensureWarning(targetTab);
  await BrowserTestUtils.switchTab(gBrowser, targetTab);
  await warningPromise;

  // Check the checkbox to suppress warnings for the rest of this session.
  let checkbox = document.getElementById(
    DISABLE_WARNING_FOR_SESSION_CHECKBOX_ID
  );
  checkbox.checked = true;

  // Now test the "Allow" button in the warning to make sure the tab
  // switch goes through.
  let tabSwitchPromise = BrowserTestUtils.waitForEvent(
    gBrowser,
    "TabSwitchDone"
  );
  let allowButton = document.getElementById(ALLOW_BUTTON_ID);
  allowButton.click();
  await tabSwitchPromise;

  Assert.equal(
    gBrowser.selectedTab,
    targetTab,
    "Should have switched tabs to the target tab."
  );

  // Switching to the 4th and 5th tabs should now not show warnings.
  let noWarningPromise = ensureNoWarning();
  await BrowserTestUtils.switchTab(gBrowser, gBrowser.tabs[3]);
  await noWarningPromise;

  noWarningPromise = ensureNoWarning();
  await BrowserTestUtils.switchTab(gBrowser, gBrowser.tabs[4]);
  await noWarningPromise;

  // Reset the sharing state, and make sure that warnings can
  // be displayed again.
  await resetDisplaySharingState();
  pretendToShareWindow(window);

  // pretendToShareWindow will have switched us to the second
  // tab automatically as the first "freebie" tab switch
  //
  // Make sure we get the warning again when switching to the
  // target tab.
  warningPromise = ensureWarning(targetTab);
  await BrowserTestUtils.switchTab(gBrowser, targetTab);
  await warningPromise;

  await resetDisplaySharingState();
});

/**
 * Tests that we don't show a warning when sharing a different
 * window than the one we're switching tabs in.
 */
add_task(async function testOtherWindow() {
  let otherWin = await BrowserTestUtils.openNewBrowserWindow();
  await SimpleTest.promiseFocus(window);
  pretendToShareWindow(otherWin);

  // Switching to the 4th and 5th tabs should now not show warnings.
  let noWarningPromise = ensureNoWarning();
  await BrowserTestUtils.switchTab(gBrowser, gBrowser.tabs[3]);
  await noWarningPromise;

  noWarningPromise = ensureNoWarning();
  await BrowserTestUtils.switchTab(gBrowser, gBrowser.tabs[4]);
  await noWarningPromise;

  await BrowserTestUtils.closeWindow(otherWin);

  await resetDisplaySharingState();
});

/**
 * Tests that we show a different label when sharing the screen
 * vs when sharing a window.
 */
add_task(async function testWindowVsScreenLabel() {
  pretendToShareWindow(window);

  // pretendToShareWindow will have switched us to the second
  // tab automatically as the first "freebie" tab switch.
  // Let's now switch to the third tab.
  let targetTab = gBrowser.tabs[2];

  // Ensure that we show the warning on this second tab switch
  let warningPromise = ensureWarning(targetTab);
  await BrowserTestUtils.switchTab(gBrowser, targetTab);
  await warningPromise;

  let windowHeader = document.getElementById(WINDOW_SHARING_HEADER_ID);
  let screenHeader = document.getElementById(SCREEN_SHARING_HEADER_ID);
  Assert.ok(
    !BrowserTestUtils.is_hidden(windowHeader),
    "Should be showing window sharing header"
  );
  Assert.ok(
    BrowserTestUtils.is_hidden(screenHeader),
    "Should not be showing screen sharing header"
  );

  // Reset the sharing state, and then pretend to share the screen.
  await resetDisplaySharingState();
  pretendToShareScreen();

  // Ensure that we show the warning on this second tab switch
  warningPromise = ensureWarning(targetTab);
  await BrowserTestUtils.switchTab(gBrowser, targetTab);
  await warningPromise;

  Assert.ok(
    BrowserTestUtils.is_hidden(windowHeader),
    "Should not be showing window sharing header"
  );
  Assert.ok(
    !BrowserTestUtils.is_hidden(screenHeader),
    "Should be showing screen sharing header"
  );
  await resetDisplaySharingState();
});

/**
 * Tests that tab switching via the keyboard can also trigger the
 * tab switch warnings.
 */
add_task(async function testKeyboardTabSwitching() {
  let pressCtrlTab = async (expectPanel = false) => {
    let promise;
    if (expectPanel) {
      promise = BrowserTestUtils.waitForEvent(ctrlTab.panel, "popupshown");
    } else {
      promise = BrowserTestUtils.waitForEvent(document, "keyup");
    }
    EventUtils.synthesizeKey("VK_TAB", {
      ctrlKey: true,
      shiftKey: false,
    });
    await promise;
  };

  let releaseCtrl = async () => {
    let promise;
    if (ctrlTab.isOpen) {
      promise = BrowserTestUtils.waitForEvent(ctrlTab.panel, "popuphidden");
    } else {
      promise = BrowserTestUtils.waitForEvent(document, "keyup");
    }
    EventUtils.synthesizeKey("VK_CONTROL", { type: "keyup" });
    return promise;
  };

  // Ensure that the (on by default) ctrl-tab switch panel is enabled.
  await SpecialPowers.pushPrefEnv({
    set: [[CTRL_TAB_RUO_PREF, true]],
  });

  pretendToShareWindow(window);
  let originalTab = gBrowser.selectedTab;
  await pressCtrlTab(true);

  // The Ctrl-Tab MRU list should be:
  // 0: Second tab (currently selected)
  // 1: First tab
  // 2: Last tab
  //
  // Having pressed Ctrl-Tab once, 1 (First tab) is selected in the
  // panel. We want a tab that will warn, so let's hit Ctrl-Tab again
  // to choose 2 (Last tab).
  let targetTab = ctrlTab.tabList[2];
  await pressCtrlTab();

  let warningPromise = ensureWarning(targetTab);
  await releaseCtrl();
  await warningPromise;

  // Hide the warning without allowing the tab switch.
  let panel = document.getElementById(WARNING_PANEL_ID);
  panel.hidePopup();

  Assert.equal(
    gBrowser.selectedTab,
    originalTab,
    "Should not have changed from the original tab."
  );

  // Now switch to the in-order tab switching keyboard shortcut mode.
  await SpecialPowers.popPrefEnv();
  await SpecialPowers.pushPrefEnv({
    set: [[CTRL_TAB_RUO_PREF, false]],
  });

  // Hitting Ctrl-Tab should choose the _next_ tab over from
  // the originalTab, which should be the third tab.
  targetTab = gBrowser.tabs[2];

  warningPromise = ensureWarning(targetTab);
  await pressCtrlTab();
  await warningPromise;

  await resetDisplaySharingState();
});