Backed out changeset 9d8b4c0b99ed (bug 1945683) for causing btime failures. CLOSED...
[gecko.git] / dom / tests / browser / browser_hasbeforeunload.js
blob0344ae9761cd93fcb9fc028f700379cde5bfc56b
1 "use strict";
3 const PAGE_URL =
4 "http://example.com/browser/dom/tests/browser/beforeunload_test_page.html";
6 /**
7 * Adds 1 or more inert beforeunload event listeners in this browser.
8 * By default, will target the top-level content window, but callers
9 * can specify the index of a subframe to target. See prepareSubframes
10 * for an idea of how the subframes are structured.
12 * @param {<xul:browser>} browser
13 * The browser to add the beforeunload event listener in.
14 * @param {int} howMany
15 * How many beforeunload event listeners to add. Note that these
16 * beforeunload event listeners are inert and will not actually
17 * prevent the host window from navigating.
18 * @param {optional int} frameDepth
19 * The depth of the frame to add the event listener to. Defaults
20 * to 0, which is the top-level content window.
21 * @return {Promise}
23 function addBeforeUnloadListeners(browser, howMany = 1, frameDepth = 0) {
24 return controlFrameAt(browser, frameDepth, {
25 name: "AddBeforeUnload",
26 howMany,
27 });
30 /**
31 * Adds 1 or more inert beforeunload event listeners in this browser on
32 * a particular subframe. By default, this will target the first subframe
33 * under the top-level content window, but callers can specify the index
34 * of a subframe to target. See prepareSubframes for an idea of how the
35 * subframes are structured.
37 * Note that this adds the beforeunload event listener on the "outer" window,
38 * by doing:
40 * iframe.addEventListener("beforeunload", ...);
42 * @param {<xul:browser>} browser
43 * The browser to add the beforeunload event listener in.
44 * @param {int} howMany
45 * How many beforeunload event listeners to add. Note that these
46 * beforeunload event listeners are inert and will not actually
47 * prevent the host window from navigating.
48 * @param {optional int} frameDepth
49 * The depth of the frame to add the event listener to. Defaults
50 * to 1, which is the first subframe inside the top-level content
51 * window. Setting this to 0 will throw.
52 * @return {Promise}
54 function addOuterBeforeUnloadListeners(browser, howMany = 1, frameDepth = 1) {
55 if (frameDepth == 0) {
56 throw new Error(
57 "When adding a beforeunload listener on an outer " +
58 "window, the frame you're targeting needs to be at " +
59 "depth > 0."
63 return controlFrameAt(browser, frameDepth, {
64 name: "AddOuterBeforeUnload",
65 howMany,
66 });
69 /**
70 * Removes 1 or more inert beforeunload event listeners in this browser.
71 * This assumes that addBeforeUnloadListeners has been called previously
72 * for the target frame.
74 * By default, will target the top-level content window, but callers
75 * can specify the index of a subframe to target. See prepareSubframes
76 * for an idea of how the subframes are structured.
78 * @param {<xul:browser>} browser
79 * The browser to remove the beforeunload event listener from.
80 * @param {int} howMany
81 * How many beforeunload event listeners to remove.
82 * @param {optional int} frameDepth
83 * The depth of the frame to remove the event listener from. Defaults
84 * to 0, which is the top-level content window.
85 * @return {Promise}
87 function removeBeforeUnloadListeners(browser, howMany = 1, frameDepth = 0) {
88 return controlFrameAt(browser, frameDepth, {
89 name: "RemoveBeforeUnload",
90 howMany,
91 });
94 /**
95 * Removes 1 or more inert beforeunload event listeners in this browser on
96 * a particular subframe. By default, this will target the first subframe
97 * under the top-level content window, but callers can specify the index
98 * of a subframe to target. See prepareSubframes for an idea of how the
99 * subframes are structured.
101 * Note that this removes the beforeunload event listener on the "outer" window,
102 * by doing:
104 * iframe.removeEventListener("beforeunload", ...);
106 * @param {<xul:browser>} browser
107 * The browser to remove the beforeunload event listener from.
108 * @param {int} howMany
109 * How many beforeunload event listeners to remove.
110 * @param {optional int} frameDepth
111 * The depth of the frame to remove the event listener from. Defaults
112 * to 1, which is the first subframe inside the top-level content
113 * window. Setting this to 0 will throw.
114 * @return {Promise}
116 function removeOuterBeforeUnloadListeners(
117 browser,
118 howMany = 1,
119 frameDepth = 1
121 if (frameDepth == 0) {
122 throw new Error(
123 "When removing a beforeunload listener from an outer " +
124 "window, the frame you're targeting needs to be at " +
125 "depth > 0."
129 return controlFrameAt(browser, frameDepth, {
130 name: "RemoveOuterBeforeUnload",
131 howMany,
136 * Navigates a content window to a particular URL and waits for it to
137 * finish loading that URL.
139 * By default, will target the top-level content window, but callers
140 * can specify the index of a subframe to target. See prepareSubframes
141 * for an idea of how the subframes are structured.
143 * @param {<xul:browser>} browser
144 * The browser that will have the navigation occur within it.
145 * @param {string} url
146 * The URL to send the content window to.
147 * @param {optional int} frameDepth
148 * The depth of the frame to navigate. Defaults to 0, which is
149 * the top-level content window.
150 * @return {Promise}
152 function navigateSubframe(browser, url, frameDepth = 0) {
153 let navigatePromise = controlFrameAt(browser, frameDepth, {
154 name: "Navigate",
155 url,
157 let subframeLoad = BrowserTestUtils.browserLoaded(
158 browser,
159 true,
160 new URL(url).href
162 return Promise.all([navigatePromise, subframeLoad]);
166 * Removes the <iframe> from a content window pointed at PAGE_URL.
168 * By default, will target the top-level content window, but callers
169 * can specify the index of a subframe to target. See prepareSubframes
170 * for an idea of how the subframes are structured.
172 * @param {<xul:browser>} browser
173 * The browser that will have removal occur within it.
174 * @param {optional int} frameDepth
175 * The depth of the frame that will have the removal occur within
176 * it. Defaults to 0, which is the top-level content window, meaning
177 * that the first subframe will be removed.
178 * @return {Promise}
180 function removeSubframeFrom(browser, frameDepth = 0) {
181 return controlFrameAt(browser, frameDepth, {
182 name: "RemoveSubframe",
187 * Sends a command to a frame pointed at PAGE_URL. There are utility
188 * functions defined in this file that call this function. You should
189 * use those instead.
191 * @param {<xul:browser>} browser
192 * The browser to send the command to.
193 * @param {int} frameDepth
194 * The depth of the frame that we'll send the command to. 0 means
195 * sending it to the top-level content window.
196 * @param {object} command
197 * An object with the following structure:
200 * name: (string),
201 * <arbitrary arguments to send with the command>
204 * Here are the commands that can be sent:
206 * AddBeforeUnload
207 * {int} howMany
208 * How many beforeunload event listeners to add.
210 * AddOuterBeforeUnload
211 * {int} howMany
212 * How many beforeunload event listeners to add to
213 * the iframe in the document at this depth.
215 * RemoveBeforeUnload
216 * {int} howMany
217 * How many beforeunload event listeners to remove.
219 * RemoveOuterBeforeUnload
220 * {int} howMany
221 * How many beforeunload event listeners to remove from
222 * the iframe in the document at this depth.
224 * Navigate
225 * {string} url
226 * The URL to send the frame to.
228 * RemoveSubframe
230 * @return {Promise}
232 function controlFrameAt(browser, frameDepth, command) {
233 return SpecialPowers.spawn(
234 browser,
235 [{ frameDepth, command }],
236 async function (args) {
237 const { TestUtils } = ChromeUtils.importESModule(
238 "resource://testing-common/TestUtils.sys.mjs"
241 let { command: contentCommand, frameDepth: contentFrameDepth } = args;
243 let targetContent = content;
244 let targetSubframe = content.document.getElementById("subframe");
246 // We want to not only find the frame that maps to the
247 // target frame depth that we've been given, but we also want
248 // to count the total depth so that if a middle frame is removed
249 // or navigated, then we know how many outer-window-destroyed
250 // observer notifications to expect.
251 let currentContent = targetContent;
252 let currentSubframe = targetSubframe;
254 let depth = 0;
256 do {
257 currentContent = currentSubframe.contentWindow;
258 currentSubframe = currentContent.document.getElementById("subframe");
259 depth++;
260 if (depth == contentFrameDepth) {
261 targetContent = currentContent;
262 targetSubframe = currentSubframe;
264 } while (currentSubframe);
266 switch (contentCommand.name) {
267 case "AddBeforeUnload": {
268 let BeforeUnloader = targetContent.wrappedJSObject.BeforeUnloader;
269 Assert.ok(BeforeUnloader, "Found BeforeUnloader in the test page.");
270 BeforeUnloader.pushInner(contentCommand.howMany);
271 break;
273 case "AddOuterBeforeUnload": {
274 let BeforeUnloader = targetContent.wrappedJSObject.BeforeUnloader;
275 Assert.ok(BeforeUnloader, "Found BeforeUnloader in the test page.");
276 BeforeUnloader.pushOuter(contentCommand.howMany);
277 break;
279 case "RemoveBeforeUnload": {
280 let BeforeUnloader = targetContent.wrappedJSObject.BeforeUnloader;
281 Assert.ok(BeforeUnloader, "Found BeforeUnloader in the test page.");
282 BeforeUnloader.popInner(contentCommand.howMany);
283 break;
285 case "RemoveOuterBeforeUnload": {
286 let BeforeUnloader = targetContent.wrappedJSObject.BeforeUnloader;
287 Assert.ok(BeforeUnloader, "Found BeforeUnloader in the test page.");
288 BeforeUnloader.popOuter(contentCommand.howMany);
289 break;
291 case "Navigate": {
292 // How many frames are going to be destroyed when we do this? We
293 // need to wait for that many window destroyed notifications.
294 targetContent.location = contentCommand.url;
296 let destroyedOuterWindows = depth - contentFrameDepth;
297 if (destroyedOuterWindows) {
298 await TestUtils.topicObserved("outer-window-destroyed", () => {
299 destroyedOuterWindows--;
300 return !destroyedOuterWindows;
303 break;
305 case "RemoveSubframe": {
306 let subframe = targetContent.document.getElementById("subframe");
307 Assert.ok(
308 subframe,
309 "Found subframe at frame depth of " + contentFrameDepth
311 subframe.remove();
313 let destroyedOuterWindows = depth - contentFrameDepth;
314 if (destroyedOuterWindows) {
315 await TestUtils.topicObserved("outer-window-destroyed", () => {
316 destroyedOuterWindows--;
317 return !destroyedOuterWindows;
320 break;
324 ).catch(console.error);
328 * Sets up a structure where a page at PAGE_URL will host an
329 * <iframe> also pointed at PAGE_URL, and does this repeatedly
330 * until we've achieved the desired frame depth. Note that this
331 * will cause the top-level browser to reload, and wipe out any
332 * previous changes to the DOM under it.
334 * @param {<xul:browser>} browser
335 * The browser in which we'll load our structure at the
336 * top level.
337 * @param {Array<object>} options
338 * Set-up options for each subframe. The following properties
339 * are accepted:
341 * {string} sandboxAttributes
342 * The value to set the sandbox attribute to. If null, no sandbox
343 * attribute will be set (and any pre-existing sandbox attributes)
344 * on the <iframe> will be removed.
346 * The number of entries on the options Array corresponds to how many
347 * subframes are under the top-level content window.
349 * Example:
351 * yield prepareSubframes(browser, [
352 * { sandboxAttributes: null },
353 * { sandboxAttributes: "allow-modals" },
354 * ]);
356 * This would create the following structure:
358 * <top-level content window at PAGE_URL>
360 * |--> <iframe at PAGE_URL, no sandbox attributes>
362 * |--> <iframe at PAGE_URL, sandbox="allow-modals">
364 * @return {Promise}
366 async function prepareSubframes(browser, options) {
367 browser.reload();
368 await BrowserTestUtils.browserLoaded(browser);
370 await SpecialPowers.spawn(
371 browser,
372 [{ options, PAGE_URL }],
373 async function (args) {
374 let { options: allSubframeOptions, PAGE_URL: contentPageURL } = args;
375 function loadBeforeUnloadHelper(doc, url, subframeOptions) {
376 let subframe = doc.getElementById("subframe");
377 subframe.remove();
378 if (subframeOptions.sandboxAttributes === null) {
379 subframe.removeAttribute("sandbox");
380 } else {
381 subframe.setAttribute("sandbox", subframeOptions.sandboxAttributes);
383 doc.body.appendChild(subframe);
384 subframe.contentWindow.location = url;
385 return ContentTaskUtils.waitForEvent(subframe, "load").then(() => {
386 return subframe.contentDocument;
390 let currentDoc = content.document;
391 let depth = 1;
392 for (let subframeOptions of allSubframeOptions) {
393 // Circumvent recursive load checks.
394 let url = new URL(contentPageURL);
395 url.search = `depth=${depth++}`;
396 currentDoc = await loadBeforeUnloadHelper(
397 currentDoc,
398 url.href,
399 subframeOptions
407 * Ensures that a browser's nsIRemoteTab hasBeforeUnload attribute
408 * is set to the expected value.
410 * @param {<xul:browser>} browser
411 * The browser whose nsIRemoteTab we will check.
412 * @param {bool} expected
413 * True if hasBeforeUnload is expected to be true.
415 function assertHasBeforeUnload(browser, expected) {
416 Assert.equal(browser.hasBeforeUnload, expected);
420 * Tests that the MozBrowser hasBeforeUnload property works under
421 * a number of different scenarios on inner windows. At a high-level,
422 * we test that hasBeforeUnload works properly during page / iframe
423 * navigation, or when an <iframe> with a beforeunload listener on its
424 * inner window is removed from the DOM.
426 add_task(async function test_inner_window_scenarios() {
427 // Turn this off because the test expects the page to be not bfcached.
428 await SpecialPowers.pushPrefEnv({
429 set: [
430 ["docshell.shistory.bfcache.ship_allow_beforeunload_listeners", false],
433 await BrowserTestUtils.withNewTab(
435 gBrowser,
436 url: PAGE_URL,
438 async function (browser) {
439 Assert.ok(
440 browser.isRemoteBrowser,
441 "This test only makes sense with out of process browsers."
443 assertHasBeforeUnload(browser, false);
445 // Test the simple case on the top-level window by adding a single
446 // beforeunload event listener on the inner window and then removing
447 // it.
448 await addBeforeUnloadListeners(browser);
449 assertHasBeforeUnload(browser, true);
450 await removeBeforeUnloadListeners(browser);
451 assertHasBeforeUnload(browser, false);
453 // Now let's add several beforeunload listeners, and
454 // ensure that we only set hasBeforeUnload to false once
455 // the last listener is removed.
456 await addBeforeUnloadListeners(browser, 3);
457 assertHasBeforeUnload(browser, true);
458 await removeBeforeUnloadListeners(browser); // 2 left...
459 assertHasBeforeUnload(browser, true);
460 await removeBeforeUnloadListeners(browser); // 1 left...
461 assertHasBeforeUnload(browser, true);
462 await removeBeforeUnloadListeners(browser); // None left!
464 assertHasBeforeUnload(browser, false);
466 // Now let's have the top-level content window navigate
467 // away with a beforeunload listener set, and ensure
468 // that we clear the hasBeforeUnload value.
469 await addBeforeUnloadListeners(browser, 5);
470 await navigateSubframe(browser, "http://example.com");
471 assertHasBeforeUnload(browser, false);
473 // Now send the page back to the test page for
474 // the next few tests.
475 BrowserTestUtils.startLoadingURIString(browser, PAGE_URL);
476 await BrowserTestUtils.browserLoaded(browser);
478 // We want to test hasBeforeUnload works properly with
479 // beforeunload event listeners in <iframe> elements too.
480 // We prepare a structure like this with 3 content windows
481 // to exercise:
483 // <top-level content window at PAGE_URL> (TOP)
484 // |
485 // |--> <iframe at PAGE_URL> (MIDDLE)
486 // |
487 // |--> <iframe at PAGE_URL> (BOTTOM)
489 await prepareSubframes(browser, [
490 { sandboxAttributes: null },
491 { sandboxAttributes: null },
493 // These constants are just to make it easier to know which
494 // frame we're referring to without having to remember the
495 // exact indices.
496 const TOP = 0;
497 const MIDDLE = 1;
498 const BOTTOM = 2;
500 // We should initially start with hasBeforeUnload set to false.
501 assertHasBeforeUnload(browser, false);
503 // Tests that if there are beforeunload event listeners on
504 // all levels of our window structure, that we only set
505 // hasBeforeUnload to false once the last beforeunload
506 // listener has been unset.
507 await addBeforeUnloadListeners(browser, 2, MIDDLE);
508 assertHasBeforeUnload(browser, true);
509 await addBeforeUnloadListeners(browser, 1, TOP);
510 assertHasBeforeUnload(browser, true);
511 await addBeforeUnloadListeners(browser, 5, BOTTOM);
512 assertHasBeforeUnload(browser, true);
514 await removeBeforeUnloadListeners(browser, 1, TOP);
515 assertHasBeforeUnload(browser, true);
516 await removeBeforeUnloadListeners(browser, 5, BOTTOM);
517 assertHasBeforeUnload(browser, true);
518 await removeBeforeUnloadListeners(browser, 2, MIDDLE);
519 assertHasBeforeUnload(browser, false);
521 // Tests that if a beforeunload event listener is set on
522 // an iframe that navigates away to a page without a
523 // beforeunload listener, that hasBeforeUnload is set
524 // to false.
525 await addBeforeUnloadListeners(browser, 5, BOTTOM);
526 assertHasBeforeUnload(browser, true);
528 await navigateSubframe(browser, "http://example.com", BOTTOM);
529 assertHasBeforeUnload(browser, false);
531 // Reset our window structure now.
532 await prepareSubframes(browser, [
533 { sandboxAttributes: null },
534 { sandboxAttributes: null },
537 // This time, add beforeunload event listeners to both the
538 // MIDDLE and BOTTOM frame, and then navigate the MIDDLE
539 // away. This should set hasBeforeUnload to false.
540 await addBeforeUnloadListeners(browser, 3, MIDDLE);
541 await addBeforeUnloadListeners(browser, 1, BOTTOM);
542 assertHasBeforeUnload(browser, true);
543 await navigateSubframe(browser, "http://example.com", MIDDLE);
544 assertHasBeforeUnload(browser, false);
546 // Tests that if the MIDDLE and BOTTOM frames have beforeunload
547 // event listeners, and if we remove the BOTTOM <iframe> and the
548 // MIDDLE <iframe>, that hasBeforeUnload is set to false.
549 await prepareSubframes(browser, [
550 { sandboxAttributes: null },
551 { sandboxAttributes: null },
553 await addBeforeUnloadListeners(browser, 3, MIDDLE);
554 await addBeforeUnloadListeners(browser, 1, BOTTOM);
555 assertHasBeforeUnload(browser, true);
556 await removeSubframeFrom(browser, MIDDLE);
557 assertHasBeforeUnload(browser, true);
558 await removeSubframeFrom(browser, TOP);
559 assertHasBeforeUnload(browser, false);
561 // Tests that if the MIDDLE and BOTTOM frames have beforeunload
562 // event listeners, and if we remove just the MIDDLE <iframe>, that
563 // hasBeforeUnload is set to false.
564 await prepareSubframes(browser, [
565 { sandboxAttributes: null },
566 { sandboxAttributes: null },
568 await addBeforeUnloadListeners(browser, 3, MIDDLE);
569 await addBeforeUnloadListeners(browser, 1, BOTTOM);
570 assertHasBeforeUnload(browser, true);
571 await removeSubframeFrom(browser, TOP);
572 assertHasBeforeUnload(browser, false);
574 // Test that two sandboxed iframes, _without_ the allow-modals
575 // permission, do not result in the hasBeforeUnload attribute
576 // being set to true when beforeunload event listeners are added.
577 await prepareSubframes(browser, [
578 { sandboxAttributes: "allow-scripts" },
579 { sandboxAttributes: "allow-scripts" },
582 await addBeforeUnloadListeners(browser, 3, MIDDLE);
583 await addBeforeUnloadListeners(browser, 1, BOTTOM);
584 assertHasBeforeUnload(browser, false);
586 await removeBeforeUnloadListeners(browser, 3, MIDDLE);
587 await removeBeforeUnloadListeners(browser, 1, BOTTOM);
588 assertHasBeforeUnload(browser, false);
590 // Test that two sandboxed iframes, both with the allow-modals
591 // permission, cause the hasBeforeUnload attribute to be set
592 // to true when beforeunload event listeners are added.
593 await prepareSubframes(browser, [
594 { sandboxAttributes: "allow-scripts allow-modals" },
595 { sandboxAttributes: "allow-scripts allow-modals" },
598 await addBeforeUnloadListeners(browser, 3, MIDDLE);
599 await addBeforeUnloadListeners(browser, 1, BOTTOM);
600 assertHasBeforeUnload(browser, true);
602 await removeBeforeUnloadListeners(browser, 1, BOTTOM);
603 assertHasBeforeUnload(browser, true);
604 await removeBeforeUnloadListeners(browser, 3, MIDDLE);
605 assertHasBeforeUnload(browser, false);
611 * Tests that the nsIRemoteTab hasBeforeUnload attribute works under
612 * a number of different scenarios on outer windows. Very similar to
613 * the above set of tests, except that we add the beforeunload listeners
614 * to the iframe DOM nodes instead of the inner windows.
616 add_task(async function test_outer_window_scenarios() {
617 // Turn this off because the test expects the page to be not bfcached.
618 await SpecialPowers.pushPrefEnv({
619 set: [
620 ["docshell.shistory.bfcache.ship_allow_beforeunload_listeners", false],
623 await BrowserTestUtils.withNewTab(
625 gBrowser,
626 url: PAGE_URL,
628 async function (browser) {
629 Assert.ok(
630 browser.isRemoteBrowser,
631 "This test only makes sense with out of process browsers."
633 assertHasBeforeUnload(browser, false);
635 // We want to test hasBeforeUnload works properly with
636 // beforeunload event listeners in <iframe> elements.
637 // We prepare a structure like this with 3 content windows
638 // to exercise:
640 // <top-level content window at PAGE_URL> (TOP)
641 // |
642 // |--> <iframe at PAGE_URL> (MIDDLE)
643 // |
644 // |--> <iframe at PAGE_URL> (BOTTOM)
646 await prepareSubframes(browser, [
647 { sandboxAttributes: null },
648 { sandboxAttributes: null },
651 // These constants are just to make it easier to know which
652 // frame we're referring to without having to remember the
653 // exact indices.
654 const TOP = 0;
655 const MIDDLE = 1;
656 const BOTTOM = 2;
658 // Test the simple case on the top-level window by adding a single
659 // beforeunload event listener on the outer window of the iframe
660 // in the TOP document.
661 await addOuterBeforeUnloadListeners(browser);
662 assertHasBeforeUnload(browser, true);
664 await removeOuterBeforeUnloadListeners(browser);
665 assertHasBeforeUnload(browser, false);
667 // Now let's add several beforeunload listeners, and
668 // ensure that we only set hasBeforeUnload to false once
669 // the last listener is removed.
670 await addOuterBeforeUnloadListeners(browser, 3);
671 assertHasBeforeUnload(browser, true);
672 await removeOuterBeforeUnloadListeners(browser); // 2 left...
673 assertHasBeforeUnload(browser, true);
674 await removeOuterBeforeUnloadListeners(browser); // 1 left...
675 assertHasBeforeUnload(browser, true);
676 await removeOuterBeforeUnloadListeners(browser); // None left!
678 assertHasBeforeUnload(browser, false);
680 // Now let's have the top-level content window navigate away
681 // with a beforeunload listener set on the outer window of the
682 // iframe inside it, and ensure that we clear the hasBeforeUnload
683 // value.
684 await addOuterBeforeUnloadListeners(browser, 5);
685 await navigateSubframe(browser, "http://example.com", TOP);
686 assertHasBeforeUnload(browser, false);
688 // Now send the page back to the test page for
689 // the next few tests.
690 BrowserTestUtils.startLoadingURIString(browser, PAGE_URL);
691 await BrowserTestUtils.browserLoaded(browser);
693 // We should initially start with hasBeforeUnload set to false.
694 assertHasBeforeUnload(browser, false);
696 await prepareSubframes(browser, [
697 { sandboxAttributes: null },
698 { sandboxAttributes: null },
701 // Tests that if there are beforeunload event listeners on
702 // all levels of our window structure, that we only set
703 // hasBeforeUnload to false once the last beforeunload
704 // listener has been unset.
705 await addOuterBeforeUnloadListeners(browser, 3, MIDDLE);
706 assertHasBeforeUnload(browser, true);
707 await addOuterBeforeUnloadListeners(browser, 7, BOTTOM);
708 assertHasBeforeUnload(browser, true);
710 await removeOuterBeforeUnloadListeners(browser, 7, BOTTOM);
711 assertHasBeforeUnload(browser, true);
712 await removeOuterBeforeUnloadListeners(browser, 3, MIDDLE);
713 assertHasBeforeUnload(browser, false);
715 // Tests that if a beforeunload event listener is set on
716 // an iframe that navigates away to a page without a
717 // beforeunload listener, that hasBeforeUnload is set
718 // to false. We're setting the event listener on the
719 // outer window on the <iframe> in the MIDDLE, which
720 // itself contains the BOTTOM frame it our structure.
721 await addOuterBeforeUnloadListeners(browser, 5, BOTTOM);
722 assertHasBeforeUnload(browser, true);
724 // Now navigate that BOTTOM frame.
725 await navigateSubframe(browser, "http://example.com", BOTTOM);
726 assertHasBeforeUnload(browser, false);
728 // Reset our window structure now.
729 await prepareSubframes(browser, [
730 { sandboxAttributes: null },
731 { sandboxAttributes: null },
734 // This time, add beforeunload event listeners to the outer
735 // windows for MIDDLE and BOTTOM. Then navigate the MIDDLE
736 // frame. This should set hasBeforeUnload to false.
737 await addOuterBeforeUnloadListeners(browser, 3, MIDDLE);
738 await addOuterBeforeUnloadListeners(browser, 1, BOTTOM);
739 assertHasBeforeUnload(browser, true);
740 await navigateSubframe(browser, "http://example.com", MIDDLE);
741 assertHasBeforeUnload(browser, false);
743 // Adds beforeunload event listeners to the outer windows of
744 // MIDDLE and BOTOTM, and then removes those iframes. Removing
745 // both iframes should set hasBeforeUnload to false.
746 await prepareSubframes(browser, [
747 { sandboxAttributes: null },
748 { sandboxAttributes: null },
750 await addOuterBeforeUnloadListeners(browser, 3, MIDDLE);
751 await addOuterBeforeUnloadListeners(browser, 1, BOTTOM);
752 assertHasBeforeUnload(browser, true);
753 await removeSubframeFrom(browser, BOTTOM);
754 assertHasBeforeUnload(browser, true);
755 await removeSubframeFrom(browser, MIDDLE);
756 assertHasBeforeUnload(browser, false);
758 // Adds beforeunload event listeners to the outer windows of MIDDLE
759 // and BOTTOM, and then removes just the MIDDLE iframe (which will
760 // take the bottom one with it). This should set hasBeforeUnload to
761 // false.
762 await prepareSubframes(browser, [
763 { sandboxAttributes: null },
764 { sandboxAttributes: null },
766 await addOuterBeforeUnloadListeners(browser, 3, MIDDLE);
767 await addOuterBeforeUnloadListeners(browser, 1, BOTTOM);
768 assertHasBeforeUnload(browser, true);
769 await removeSubframeFrom(browser, TOP);
770 assertHasBeforeUnload(browser, false);
772 // Test that two sandboxed iframes, _without_ the allow-modals
773 // permission, do not result in the hasBeforeUnload attribute
774 // being set to true when beforeunload event listeners are added
775 // to the outer windows. Note that this requires the
776 // allow-same-origin permission, otherwise a cross-origin
777 // security exception is thrown.
778 await prepareSubframes(browser, [
779 { sandboxAttributes: "allow-same-origin allow-scripts" },
780 { sandboxAttributes: "allow-same-origin allow-scripts" },
783 await addOuterBeforeUnloadListeners(browser, 3, MIDDLE);
784 await addOuterBeforeUnloadListeners(browser, 1, BOTTOM);
785 assertHasBeforeUnload(browser, false);
787 await removeOuterBeforeUnloadListeners(browser, 3, MIDDLE);
788 await removeOuterBeforeUnloadListeners(browser, 1, BOTTOM);
789 assertHasBeforeUnload(browser, false);
791 // Test that two sandboxed iframes, both with the allow-modals
792 // permission, cause the hasBeforeUnload attribute to be set
793 // to true when beforeunload event listeners are added. Note
794 // that this requires the allow-same-origin permission,
795 // otherwise a cross-origin security exception is thrown.
796 await prepareSubframes(browser, [
797 { sandboxAttributes: "allow-same-origin allow-scripts allow-modals" },
798 { sandboxAttributes: "allow-same-origin allow-scripts allow-modals" },
801 await addOuterBeforeUnloadListeners(browser, 3, MIDDLE);
802 await addOuterBeforeUnloadListeners(browser, 1, BOTTOM);
803 assertHasBeforeUnload(browser, true);
805 await removeOuterBeforeUnloadListeners(browser, 1, BOTTOM);
806 assertHasBeforeUnload(browser, true);
807 await removeOuterBeforeUnloadListeners(browser, 3, MIDDLE);
808 assertHasBeforeUnload(browser, false);
814 * Tests hasBeforeUnload behaviour when beforeunload event listeners
815 * are added on both inner and outer windows.
817 add_task(async function test_mixed_inner_and_outer_window_scenarios() {
818 // Turn this off because the test expects the page to be not bfcached.
819 await SpecialPowers.pushPrefEnv({
820 set: [
821 ["docshell.shistory.bfcache.ship_allow_beforeunload_listeners", false],
824 await BrowserTestUtils.withNewTab(
826 gBrowser,
827 url: PAGE_URL,
829 async function (browser) {
830 Assert.ok(
831 browser.isRemoteBrowser,
832 "This test only makes sense with out of process browsers."
834 assertHasBeforeUnload(browser, false);
836 // We want to test hasBeforeUnload works properly with
837 // beforeunload event listeners in <iframe> elements.
838 // We prepare a structure like this with 3 content windows
839 // to exercise:
841 // <top-level content window at PAGE_URL> (TOP)
842 // |
843 // |--> <iframe at PAGE_URL> (MIDDLE)
844 // |
845 // |--> <iframe at PAGE_URL> (BOTTOM)
847 await prepareSubframes(browser, [
848 { sandboxAttributes: null },
849 { sandboxAttributes: null },
852 // These constants are just to make it easier to know which
853 // frame we're referring to without having to remember the
854 // exact indices.
855 const TOP = 0;
856 const MIDDLE = 1;
857 const BOTTOM = 2;
859 await addBeforeUnloadListeners(browser, 1, TOP);
860 assertHasBeforeUnload(browser, true);
861 await addBeforeUnloadListeners(browser, 2, MIDDLE);
862 assertHasBeforeUnload(browser, true);
863 await addBeforeUnloadListeners(browser, 5, BOTTOM);
864 assertHasBeforeUnload(browser, true);
866 await addOuterBeforeUnloadListeners(browser, 3, MIDDLE);
867 assertHasBeforeUnload(browser, true);
868 await addOuterBeforeUnloadListeners(browser, 7, BOTTOM);
869 assertHasBeforeUnload(browser, true);
871 await removeBeforeUnloadListeners(browser, 5, BOTTOM);
872 assertHasBeforeUnload(browser, true);
874 await removeBeforeUnloadListeners(browser, 2, MIDDLE);
875 assertHasBeforeUnload(browser, true);
877 await removeOuterBeforeUnloadListeners(browser, 3, MIDDLE);
878 assertHasBeforeUnload(browser, true);
880 await removeBeforeUnloadListeners(browser, 1, TOP);
881 assertHasBeforeUnload(browser, true);
883 await removeOuterBeforeUnloadListeners(browser, 7, BOTTOM);
884 assertHasBeforeUnload(browser, false);