Bug 1741671 - Enable BFCache for pages with beforeunload event listeners on Desktop r=smaug

The changes only made it works in SHIP(session-history-in-parent) only.

Differential Revision: https://phabricator.services.mozilla.com/D131715
This commit is contained in:
Sean Feng 2022-03-03 19:13:47 +00:00
Родитель 3ba72459c3
Коммит f61ec1c3d7
14 изменённых файлов: 335 добавлений и 49 удалений

Просмотреть файл

@ -1324,37 +1324,34 @@ void nsSHistory::LoadURIOrBFCache(LoadEntryResult& aLoadEntry) {
"saving presentation=%i",
canSave));
if (!canSave) {
nsCOMPtr<nsFrameLoaderOwner> frameLoaderOwner =
do_QueryInterface(canonicalBC->GetEmbedderElement());
if (frameLoaderOwner) {
RefPtr<nsFrameLoader> currentFrameLoader =
frameLoaderOwner->GetFrameLoader();
if (currentFrameLoader &&
currentFrameLoader->GetMaybePendingBrowsingContext()) {
WindowGlobalParent* wgp =
currentFrameLoader->GetMaybePendingBrowsingContext()
->Canonical()
->GetCurrentWindowGlobal();
if (wgp) {
wgp->PermitUnload([canonicalBC, loadState, she, frameLoader,
currentFrameLoader](bool aAllow) {
if (aAllow) {
FinishRestore(canonicalBC, loadState, she, frameLoader,
false);
} else if (currentFrameLoader
->GetMaybePendingBrowsingContext()) {
nsISHistory* shistory =
currentFrameLoader->GetMaybePendingBrowsingContext()
->Canonical()
->GetSessionHistory();
if (shistory) {
shistory->InternalSetRequestedIndex(-1);
}
nsCOMPtr<nsFrameLoaderOwner> frameLoaderOwner =
do_QueryInterface(canonicalBC->GetEmbedderElement());
if (frameLoaderOwner) {
RefPtr<nsFrameLoader> currentFrameLoader =
frameLoaderOwner->GetFrameLoader();
if (currentFrameLoader &&
currentFrameLoader->GetMaybePendingBrowsingContext()) {
if (WindowGlobalParent* wgp =
currentFrameLoader->GetMaybePendingBrowsingContext()
->Canonical()
->GetCurrentWindowGlobal()) {
wgp->PermitUnload([canonicalBC, loadState, she, frameLoader,
currentFrameLoader, canSave](bool aAllow) {
if (aAllow) {
FinishRestore(canonicalBC, loadState, she, frameLoader,
canSave && canonicalBC->AllowedInBFCache(
Nothing(), nullptr));
} else if (currentFrameLoader->GetMaybePendingBrowsingContext()) {
nsISHistory* shistory =
currentFrameLoader->GetMaybePendingBrowsingContext()
->Canonical()
->GetSessionHistory();
if (shistory) {
shistory->InternalSetRequestedIndex(-1);
}
});
return;
}
}
});
return;
}
}
}

Просмотреть файл

@ -0,0 +1,37 @@
<html>
<script>
onpageshow = function(pageShowEvent) {
var bc = new BroadcastChannel("ship_beforeunload");
bc.onmessage = function(event) {
if (event.data.action == "register_beforeunload") {
onbeforeunload = function() {
bc.postMessage("beforeunload_fired");
bc.close();
}
if (event.data.loadNextPageFromSessionHistory) {
history.back();
} else {
location.href += "?differentpage";
}
} else if (event.data.action == "navigate_to_page_b") {
bc.close();
if (event.data.blockBFCache) {
window.blockBFCache = new RTCPeerConnection();
}
location.href += "?pageb";
} else if (event.data.action == "back_to_page_b") {
if (event.data.forwardNavigateToPageB) {
history.forward();
} else {
history.back();
}
bc.close();
} else if (event.data.action == "close") {
bc.close();
window.close();
}
}
bc.postMessage({type: pageShowEvent.type, persisted: pageShowEvent.persisted});
}
</script>
</html>

Просмотреть файл

@ -200,3 +200,15 @@ support-files =
[test_recursive_frames.html]
[test_bug1699721.html]
skip-if = !fission # tests fission-only process switching behaviour
[test_ship_beforeunload_fired.html]
support-files =
file_ship_beforeunload_fired.html
skip-if = !sessionHistoryInParent
[test_ship_beforeunload_fired_2.html]
support-files =
file_ship_beforeunload_fired.html
skip-if = !sessionHistoryInParent
[test_ship_beforeunload_fired_3.html]
support-files =
file_ship_beforeunload_fired.html
skip-if = !sessionHistoryInParent

Просмотреть файл

@ -0,0 +1,63 @@
<html>
<head>
<title>
Test that ensures beforeunload is fired when session-history-in-parent is enabled
</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
</head>
<script>
SimpleTest.waitForExplicitFinish();
/*
* This test ensures beforeunload is fired on the current page
* when it is entering BFCache and the next page is coming out
* from BFCache
*
* (1) The controller page opens a new window, and page A is loaded there.
* (2) Page A then navigates to page B, and a beforeunload event
* listener is registered on page B.
* (3) Page B then navigates back to page A, and the beforeunload handler
* should send a message to the controller page.
* (4) Page A then navigates back to page B to check if page B has
* been successfully added to BFCache.
*/
var bc = new BroadcastChannel("ship_beforeunload");
var pageshowCount = 0;
var beforeUnloadFired = false;
bc.onmessage = function(event) {
if (event.data.type == "pageshow") {
++pageshowCount;
if (pageshowCount == 1) {
bc.postMessage({action: "navigate_to_page_b"});
} else if (pageshowCount == 2) {
ok(!event.data.persisted, "?pageb shouldn't in BFCache because it's the first navigation");
bc.postMessage({action: "register_beforeunload", loadNextPageFromSessionHistory: true});
} else if (pageshowCount == 3) {
ok(event.data.persisted, "navigated back to page A that was in BFCacache from page B");
ok(beforeUnloadFired, "beforeunload has fired on page B");
bc.postMessage({action: "back_to_page_b", forwardNavigateToPageB: true});
} else if (pageshowCount == 4) {
ok(event.data.persisted, "page B has beforeunload fired and also entered BFCache");
bc.postMessage({action: "close"});
SimpleTest.finish();
}
} else if (event.data == "beforeunload_fired") {
beforeUnloadFired = true;
}
}
function runTest() {
SpecialPowers.pushPrefEnv({"set": [
["fission.bfcacheInParent", true],
["docshell.shistory.bfcache.ship_allow_beforeunload_listeners", true]
]},
function() {
window.open("file_ship_beforeunload_fired.html", "", "noopener");
}
);
}
</script>
<body onload="runTest()"></body>
</html>

Просмотреть файл

@ -0,0 +1,65 @@
<html>
<head>
<title>
Test that ensures beforeunload is fired when session-history-in-parent is enabled
</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
</head>
<script>
SimpleTest.waitForExplicitFinish();
/*
* This test ensures beforeunload is fired on the current page
* when it is entering BFCache, and the next page is not coming from
* session history and also not coming out from BFCache.
*
* (1) The controller page opens a new window, and page A is loaded there.
* (2) Page A then navigates to page B, and a beforeunload event
* listener is registered on page B.
* (3) Page B then navigates to page C, and the beforeunload handler
* should send a message to the controller page.
* (4) Page C then navigates back to page B to check if page B has
* been successfully added to BFCache.
*/
var bc = new BroadcastChannel("ship_beforeunload");
var pageshowCount = 0;
var beforeUnloadFired = false;
bc.onmessage = function(event) {
if (event.data.type == "pageshow") {
++pageshowCount;
if (pageshowCount == 1) {
bc.postMessage({action: "navigate_to_page_b"});
} else if (pageshowCount == 2) {
ok(!event.data.persisted, "?page B shouldn't in BFCache because it's the first navigation");
bc.postMessage({action: "register_beforeunload",
loadNextPageFromSessionHistory: false});
} else if (pageshowCount == 3) {
ok(!event.data.persisted, "navigated to page C that was a new page");
ok(beforeUnloadFired, "beforeUnload should be fired on page B");
bc.postMessage({action: "back_to_page_b", forwardNavigateToPageB: false});
} else if (pageshowCount == 4) {
ok(event.data.persisted, "page B has been successfully added to BFCache");
bc.postMessage({action: "close"});
SimpleTest.finish();
}
} else if (event.data == "beforeunload_fired") {
beforeUnloadFired = true;
}
}
function runTest() {
SpecialPowers.pushPrefEnv({"set": [
["fission.bfcacheInParent", true],
["docshell.shistory.bfcache.ship_allow_beforeunload_listeners", true]
]},
function() {
window.open("file_ship_beforeunload_fired.html", "", "noopener");
}
);
}
</script>
<body onload="runTest()"></body>
</html>

Просмотреть файл

@ -0,0 +1,65 @@
<html>
<head>
<title>
Test that ensures beforeunload is fired when session-history-in-parent is enabled
</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
</head>
<script>
SimpleTest.waitForExplicitFinish();
/*
* This test ensures beforeunload is fired on the current page
* when it is entering BFCache, and the next page is not coming out
* from BFCache, but coming from session history.
*
* (1) The controller page opens a new window, and page A is loaded there.
* (2) Page A then navigates to page B, and a beforeunload event
* listener is registered on page B.
* (3) Page B then navigates back to page A, and the beforeunload handler
* should send a message to the controller page.
* (4) Page A then navigates back to page B to check if page B has
* been successfully added to BFCache.
*/
var bc = new BroadcastChannel("ship_beforeunload");
var pageshowCount = 0;
var beforeUnloadFired = false;
bc.onmessage = function(event) {
if (event.data.type == "pageshow") {
++pageshowCount;
if (pageshowCount == 1) {
bc.postMessage({action: "navigate_to_page_b", blockBFCache: true});
} else if (pageshowCount == 2) {
ok(!event.data.persisted, "?page B shouldn't in BFCache because it's the first navigation");
bc.postMessage({action: "register_beforeunload",
loadNextPageFromSessionHistory: true});
} else if (pageshowCount == 3) {
ok(!event.data.persisted, "navigated back to page A that was session history but not in BFCache");
ok(beforeUnloadFired, "beforeUnload should be fired on page B");
bc.postMessage({action: "back_to_page_b", forwardNavigateToPageB: true});
} else if (pageshowCount == 4) {
ok(event.data.persisted, "page B has been successfully added to BFCache");
bc.postMessage({action: "close"});
SimpleTest.finish();
}
} else if (event.data == "beforeunload_fired") {
beforeUnloadFired = true;
}
}
function runTest() {
SpecialPowers.pushPrefEnv({"set": [
["fission.bfcacheInParent", true],
["docshell.shistory.bfcache.ship_allow_beforeunload_listeners", true]
]},
function() {
window.open("file_ship_beforeunload_fired.html", "", "noopener");
}
);
}
</script>
<body onload="runTest()"></body>
</html>

Просмотреть файл

@ -11173,10 +11173,15 @@ bool Document::CanSavePresentation(nsIRequest* aNewRequest,
ret = false;
}
if (manager->HasBeforeUnloadListeners()) {
MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
("Save of %s blocked due to beforeUnload handlers", uri.get()));
aBFCacheCombo |= BFCacheStatus::BEFOREUNLOAD_LISTENER;
ret = false;
if (!mozilla::SessionHistoryInParent() ||
!StaticPrefs::
docshell_shistory_bfcache_ship_allow_beforeunload_listeners()) {
MOZ_LOG(
gPageCacheLog, mozilla::LogLevel::Verbose,
("Save of %s blocked due to beforeUnload handlers", uri.get()));
aBFCacheCombo |= BFCacheStatus::BEFOREUNLOAD_LISTENER;
ret = false;
}
}
}
}

Просмотреть файл

@ -81,6 +81,7 @@
#include "mozilla/SpinEventLoopUntil.h"
#include "mozilla/Sprintf.h"
#include "mozilla/StaticPrefs_browser.h"
#include "mozilla/StaticPrefs_docshell.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StaticPrefs_privacy.h"
#include "mozilla/StorageAccess.h"
@ -6590,13 +6591,22 @@ void nsGlobalWindowInner::EventListenerAdded(nsAtom* aType) {
mHasVRDisplayActivateEvents = true;
}
if ((aType == nsGkAtoms::onunload || aType == nsGkAtoms::onbeforeunload) &&
mWindowGlobalChild) {
if (aType == nsGkAtoms::onunload && mWindowGlobalChild) {
if (++mUnloadOrBeforeUnloadListenerCount == 1) {
mWindowGlobalChild->BlockBFCacheFor(BFCacheStatus::UNLOAD_LISTENER);
}
if (aType == nsGkAtoms::onbeforeunload &&
(!mDoc || !(mDoc->GetSandboxFlags() & SANDBOXED_MODALS))) {
}
if (aType == nsGkAtoms::onbeforeunload && mWindowGlobalChild) {
if (!mozilla::SessionHistoryInParent() ||
!StaticPrefs::
docshell_shistory_bfcache_ship_allow_beforeunload_listeners()) {
if (++mUnloadOrBeforeUnloadListenerCount == 1) {
mWindowGlobalChild->BlockBFCacheFor(
BFCacheStatus::BEFOREUNLOAD_LISTENER);
}
}
if (!mDoc || !(mDoc->GetSandboxFlags() & SANDBOXED_MODALS)) {
mWindowGlobalChild->BeforeUnloadAdded();
MOZ_ASSERT(mWindowGlobalChild->BeforeUnloadListeners() > 0);
}
@ -6618,14 +6628,23 @@ void nsGlobalWindowInner::EventListenerAdded(nsAtom* aType) {
}
void nsGlobalWindowInner::EventListenerRemoved(nsAtom* aType) {
if ((aType == nsGkAtoms::onunload || aType == nsGkAtoms::onbeforeunload) &&
mWindowGlobalChild) {
if (aType == nsGkAtoms::onunload && mWindowGlobalChild) {
MOZ_ASSERT(mUnloadOrBeforeUnloadListenerCount > 0);
if (--mUnloadOrBeforeUnloadListenerCount == 0) {
mWindowGlobalChild->UnblockBFCacheFor(BFCacheStatus::UNLOAD_LISTENER);
}
if (aType == nsGkAtoms::onbeforeunload &&
(!mDoc || !(mDoc->GetSandboxFlags() & SANDBOXED_MODALS))) {
}
if (aType == nsGkAtoms::onbeforeunload && mWindowGlobalChild) {
if (!mozilla::SessionHistoryInParent() ||
!StaticPrefs::
docshell_shistory_bfcache_ship_allow_beforeunload_listeners()) {
if (--mUnloadOrBeforeUnloadListenerCount == 0) {
mWindowGlobalChild->UnblockBFCacheFor(
BFCacheStatus::BEFOREUNLOAD_LISTENER);
}
}
if (!mDoc || !(mDoc->GetSandboxFlags() & SANDBOXED_MODALS)) {
mWindowGlobalChild->BeforeUnloadRemoved();
MOZ_ASSERT(mWindowGlobalChild->BeforeUnloadListeners() >= 0);
}

Просмотреть файл

@ -1520,7 +1520,7 @@ class nsGlobalWindowInner final : public mozilla::dom::EventTarget,
RefPtr<mozilla::dom::VREventObserver> mVREventObserver;
// The number of unload and beforeunload even listeners registered on this
// The number of unload and beforeunload event listeners registered on this
// window.
uint64_t mUnloadOrBeforeUnloadListenerCount = 0;

Просмотреть файл

@ -1388,6 +1388,7 @@ nsCString BFCacheStatusToString(uint32_t aFlags) {
ADD_BFCACHESTATUS_TO_STRING(HAS_USED_VR);
ADD_BFCACHESTATUS_TO_STRING(CONTAINS_REMOTE_SUBFRAMES);
ADD_BFCACHESTATUS_TO_STRING(NOT_ONLY_TOPLEVEL_IN_BCG);
ADD_BFCACHESTATUS_TO_STRING(BEFOREUNLOAD_LISTENER);
ADD_BFCACHESTATUS_TO_STRING(ACTIVE_LOCK);
#undef ADD_BFCACHESTATUS_TO_STRING

Просмотреть файл

@ -408,6 +408,12 @@ function assertHasBeforeUnload(browser, expected) {
* inner window is removed from the DOM.
*/
add_task(async function test_inner_window_scenarios() {
// Turn this off because the test expects the page to be not bfcached.
await SpecialPowers.pushPrefEnv({
set: [
["docshell.shistory.bfcache.ship_allow_beforeunload_listeners", false],
],
});
await BrowserTestUtils.withNewTab(
{
gBrowser,
@ -592,6 +598,12 @@ add_task(async function test_inner_window_scenarios() {
* to the iframe DOM nodes instead of the inner windows.
*/
add_task(async function test_outer_window_scenarios() {
// Turn this off because the test expects the page to be not bfcached.
await SpecialPowers.pushPrefEnv({
set: [
["docshell.shistory.bfcache.ship_allow_beforeunload_listeners", false],
],
});
await BrowserTestUtils.withNewTab(
{
gBrowser,
@ -787,6 +799,12 @@ add_task(async function test_outer_window_scenarios() {
* are added on both inner and outer windows.
*/
add_task(async function test_mixed_inner_and_outer_window_scenarios() {
// Turn this off because the test expects the page to be not bfcached.
await SpecialPowers.pushPrefEnv({
set: [
["docshell.shistory.bfcache.ship_allow_beforeunload_listeners", false],
],
});
await BrowserTestUtils.withNewTab(
{
gBrowser,

Просмотреть файл

@ -1816,6 +1816,13 @@
value: @IS_ANDROID@
mirror: always
# If true, page with beforeunload event listeners can be bfcached.
# This only works when sessionHistoryInParent is enabled.
- name: docshell.shistory.bfcache.ship_allow_beforeunload_listeners
type: bool
value: @IS_NIGHTLY_BUILD@
mirror: always
#---------------------------------------------------------------------------
# Prefs starting with "dom."
#---------------------------------------------------------------------------

Просмотреть файл

@ -1,9 +1,8 @@
[events.html]
prefs: [docshell.shistory.bfcache.ship_allow_beforeunload_listeners:true]
[beforeunload]
expected:
if os == "android": PASS
PRECONDITION_FAILED
if not sessionHistoryInParent and (os != "android") : PRECONDITION_FAILED
[unload]
expected:
if os == "android": PASS

Просмотреть файл

@ -7,9 +7,7 @@
<script>
// Prevent this page from being stored in the bfcache for the
// browser_httpToFileHistory.js test
window.addEventListener('beforeunload', event => {
// do nothing
});
window.blockBFCache = new RTCPeerConnection();
</script>
</body>
</html>