Bug 1706347 - Check mOOPChildrenLoading when deciding to put page in BFCache with Fission enabled. r=smaug

Differential Revision: https://phabricator.services.mozilla.com/D117199
This commit is contained in:
Peter Van der Beken 2021-08-12 14:11:29 +00:00
Родитель e72d41676c
Коммит f78596c95a
10 изменённых файлов: 144 добавлений и 41 удалений

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

@ -2549,11 +2549,25 @@ void CanonicalBrowsingContext::CloneDocumentTreeInto(
[source = MaybeDiscardedBrowsingContext{aSource},
data = std::move(aPrintData)](
BrowserParent* aBp) -> RefPtr<GenericNonExclusivePromise> {
RefPtr<BrowserBridgeParent> bridge =
aBp->GetBrowserBridgeParent();
return aBp->SendCloneDocumentTreeIntoSelf(source, data)
->Then(
GetMainThreadSerialEventTarget(), __func__,
[](BrowserParent::CloneDocumentTreeIntoSelfPromise::
ResolveOrRejectValue&& aValue) {
[bridge](
BrowserParent::CloneDocumentTreeIntoSelfPromise::
ResolveOrRejectValue&& aValue) {
// We're cloning a remote iframe, so we created a
// BrowserBridge which makes us register an OOP load
// (see Document::OOPChildLoadStarted), even though
// this isn't a real load. We call
// SendMaybeFireEmbedderLoadEvents here so that we do
// register the end of the load (see
// Document::OOPChildLoadDone).
if (bridge) {
Unused << bridge->SendMaybeFireEmbedderLoadEvents(
EmbedderElementEventType::NoEvent);
}
if (aValue.IsResolve() && aValue.ResolveValue()) {
return GenericNonExclusivePromise::CreateAndResolve(
true, __func__);

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

@ -0,0 +1,5 @@
<html>
<body>
<iframe id="inner" src="iframe_slow_onload_inner.html">
</body>
</html>

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

@ -0,0 +1,19 @@
<html>
<head>
<script>
function load() {
// Let the test page know that it can try to navigate.
top.postMessage("onload", "*");
// We're starting an infinite loop, but we need to time out after a
// while, or the loop will keep running until shutdown.
let t0 = performance.now();
while (performance.now() - t0 < 5000) {
document.getElementById("output").innerText = Math.random();
}
}
</script>
</head>
<body onload="load()">
<p id="output"></p>
</body>
</html>

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

@ -106,6 +106,8 @@ support-files = file_bug1536471.html
support-files =
file_blockBFCache.html
slow.sjs
iframe_slow_onload.html
iframe_slow_onload_inner.html
[test_child.html]
[test_docshell_gotoindex.html]
support-files = file_docshell_gotoindex.html

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

@ -98,6 +98,30 @@ const blockBFCacheTests = [
},
];
if (SpecialPowers.Services.appinfo.sessionHistoryInParent) {
blockBFCacheTests.push({
name: "Loading OOP iframe",
test: () => {
return new Promise((resolve) => {
const el = document.body.appendChild(document.createElement("iframe"));
el.id = "frame";
addEventListener("message", ({ data }) => {
if (data == "onload") {
resolve();
}
});
el.src = "https://example.com/tests/docshell/test/navigation/iframe_slow_onload.html";
});
},
waitForDone: () => {
SimpleTest.requestFlakyTimeout("Test has a loop in an onload handler that runs for 5000ms, we need to make sure the loop is done before moving to the next test.");
return new Promise(resolve => {
setTimeout(resolve, 5000);
});
},
});
}
const dontBlockBFCacheTests = [
{
name: "getUserMedia",
@ -158,7 +182,7 @@ function promisePageShowNotFromBFCache(e) {
}
function runTests(testArray, shouldBlockBFCache) {
for (const { name, prefs = {}, test } of testArray) {
for (const { name, prefs = {}, test, waitForDone } of testArray) {
add_task(async function() {
await SpecialPowers.pushPrefEnv(prefs, async function() {
// Load a mostly blank page that we can communicate with over
@ -189,6 +213,15 @@ function runTests(testArray, shouldBlockBFCache) {
let result = await goneForward;
ok(result, `Page ${shouldBlockBFCache ? "should" : "should not"} have been blocked from going into the BFCache (${name})`);
// If the test will keep running after navigation, then we need to make
// sure it's completely done before moving to the next test, to avoid
// interfering with any following tests. If waitForDone is defined then
// it'll return a Promise that we can use to wait for the end of the
// test.
if (waitForDone) {
await waitForDone();
}
bc.postMessage({ message: "close" });
SpecialPowers.popPrefEnv();

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

@ -17351,4 +17351,37 @@ void Document::UnregisterFromMemoryReportingForDataDocument() {
}
}
}
void Document::OOPChildLoadStarted(BrowserBridgeChild* aChild) {
MOZ_DIAGNOSTIC_ASSERT(!mOOPChildrenLoading.Contains(aChild));
mOOPChildrenLoading.AppendElement(aChild);
if (mOOPChildrenLoading.Length() == 1) {
// Let's block unload so that we're blocked from going into the BFCache
// until the child has actually notified us that it has done loading.
BlockOnload();
}
}
void Document::OOPChildLoadDone(BrowserBridgeChild* aChild) {
// aChild will not be in the list if nsDocLoader::Stop() was called, since
// that clears mOOPChildrenLoading. It also dispatches the 'load' event,
// so we don't need to call DocLoaderIsEmpty in that case.
if (mOOPChildrenLoading.RemoveElement(aChild)) {
if (mOOPChildrenLoading.IsEmpty()) {
UnblockOnload(false);
}
RefPtr<nsDocLoader> docLoader(mDocumentContainer);
if (docLoader) {
docLoader->OOPChildrenLoadingIsEmpty();
}
}
}
void Document::ClearOOPChildrenLoading() {
nsTArray<const BrowserBridgeChild*> oopChildrenLoading;
mOOPChildrenLoading.SwapElements(oopChildrenLoading);
if (!oopChildrenLoading.IsEmpty()) {
UnblockOnload(false);
}
}
} // namespace mozilla::dom

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

@ -220,6 +220,7 @@ class AnonymousContent;
class Attr;
class XULBroadcastManager;
class XULPersist;
class BrowserBridgeChild;
class ChromeObserver;
class ClientInfo;
class ClientState;
@ -4023,6 +4024,20 @@ class Document : public nsINode,
static bool IsValidDomain(nsIURI* aOrigHost, nsIURI* aNewURI);
// Inform a parent document that a BrowserBridgeChild has been created for
// an OOP sub-document.
// (This is the OOP counterpart to nsDocLoader::ChildEnteringOnload)
void OOPChildLoadStarted(BrowserBridgeChild* aChild);
// Inform a parent document that the BrowserBridgeChild for one of its
// OOP sub-documents is done calling its onload handler.
// (This is the OOP counterpart to nsDocLoader::ChildDoneWithOnload)
void OOPChildLoadDone(BrowserBridgeChild* aChild);
void ClearOOPChildrenLoading();
bool HasOOPChildrenLoading() { return !mOOPChildrenLoading.IsEmpty(); }
protected:
// Returns the WindowContext for the document that we will contribute
// page use counters to.
@ -5257,6 +5272,10 @@ class Document : public nsINode,
// Accumulate page load metrics
void AccumulatePageLoadTelemetry();
// The OOP counterpart to nsDocLoader::mChildrenInOnload.
// Not holding strong refs here since we don't actually use the BBCs.
nsTArray<const BrowserBridgeChild*> mOOPChildrenLoading;
public:
// Needs to be public because the bindings code pokes at it.
JS::ExpandoAndGeneration mExpandoAndGeneration;

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

@ -43,14 +43,11 @@ already_AddRefed<BrowserBridgeHost> BrowserBridgeChild::FinishInit(
mFrameLoader = aFrameLoader;
RefPtr<Element> owner = mFrameLoader->GetOwnerContent();
nsCOMPtr<nsIDocShell> docShell = do_GetInterface(owner->GetOwnerGlobal());
MOZ_DIAGNOSTIC_ASSERT(docShell);
nsDocShell::Cast(docShell)->OOPChildLoadStarted(this);
Document* doc = owner->OwnerDoc();
doc->OOPChildLoadStarted(this);
#if defined(ACCESSIBILITY)
if (a11y::DocAccessible* docAcc =
a11y::GetExistingDocAccessible(owner->OwnerDoc())) {
if (a11y::DocAccessible* docAcc = a11y::GetExistingDocAccessible(doc)) {
if (a11y::LocalAccessible* ownerAcc = docAcc->GetAccessible(owner)) {
if (a11y::OuterDocAccessible* outerAcc = ownerAcc->AsOuterDoc()) {
outerAcc->SendEmbedderAccessible(this);
@ -230,9 +227,9 @@ void BrowserBridgeChild::ActorDestroy(ActorDestroyReason aWhy) {
void BrowserBridgeChild::UnblockOwnerDocsLoadEvent() {
if (!mHadInitialLoad) {
mHadInitialLoad = true;
if (auto* docShell =
nsDocShell::Cast(mBrowsingContext->GetParent()->GetDocShell())) {
docShell->OOPChildLoadDone(this);
if (Document* doc = mBrowsingContext->GetParent()->GetExtantDocument()) {
doc->OOPChildLoadDone(this);
}
}
}

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

@ -266,7 +266,11 @@ nsDocLoader::Stop(void) {
// after this, since mDocumentRequest will be null after the
// DocLoaderIsEmpty() call.
mChildrenInOnload.Clear();
mOOPChildrenLoading.Clear();
nsCOMPtr<nsIDocShell> ds = do_QueryInterface(GetAsSupports(this));
Document* doc = ds ? ds->GetExtantDocument() : nullptr;
if (doc) {
doc->ClearOOPChildrenLoading();
}
// Make sure to call DocLoaderIsEmpty now so that we reset mDocumentRequest,
// etc, as needed. We could be getting into here from a subframe onload, in
@ -306,7 +310,9 @@ bool nsDocLoader::IsBusy() {
// 3. It's currently flushing layout in DocLoaderIsEmpty().
//
if (!mChildrenInOnload.IsEmpty() || !mOOPChildrenLoading.IsEmpty() ||
nsCOMPtr<nsIDocShell> ds = do_QueryInterface(GetAsSupports(this));
Document* doc = ds ? ds->GetExtantDocument() : nullptr;
if (!mChildrenInOnload.IsEmpty() || (doc && doc->HasOOPChildrenLoading()) ||
mIsFlushingLayout) {
return true;
}

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

@ -30,7 +30,6 @@
namespace mozilla {
namespace dom {
class BrowserBridgeChild;
class BrowsingContext;
} // namespace dom
} // namespace mozilla
@ -55,8 +54,6 @@ class nsDocLoader : public nsIDocumentLoader,
public nsIChannelEventSink,
public nsISupportsPriority {
public:
using BrowserBridgeChild = mozilla::dom::BrowserBridgeChild;
NS_DECLARE_STATIC_IID_ACCESSOR(NS_THIS_DOCLOADER_IMPL_CID)
nsDocLoader() : nsDocLoader(false) {}
@ -136,28 +133,10 @@ class nsDocLoader : public nsIDocumentLoader,
mTreatAsBackgroundLoad = false;
};
// Inform a parent docloader that a BrowserBridgeChild has been created for
// an OOP sub-document.
// (This is the OOP counterpart to ChildEnteringOnload below.)
void OOPChildLoadStarted(BrowserBridgeChild* aChild) {
MOZ_DIAGNOSTIC_ASSERT(!mOOPChildrenLoading.Contains(aChild));
mOOPChildrenLoading.AppendElement(aChild);
}
// Inform a parent docloader that the BrowserBridgeChild for one of its
// OOP sub-documents is done calling its onload handler.
// (This is the OOP counterpart to ChildDoneWithOnload below.)
void OOPChildLoadDone(BrowserBridgeChild* aChild) {
// aChild will not be in the list if nsDocLoader::Stop() was called, since
// that clears mOOPChildrenLoading. It also dispatches the 'load' event,
// so we don't need to call DocLoaderIsEmpty in that case.
if (mOOPChildrenLoading.RemoveElement(aChild)) {
DocLoaderIsEmpty(true);
}
}
uint32_t ChildCount() const { return mChildList.Length(); }
void OOPChildrenLoadingIsEmpty() { DocLoaderIsEmpty(true); }
protected:
explicit nsDocLoader(bool aNotifyAboutBackgroundRequests);
virtual ~nsDocLoader();
@ -374,10 +353,6 @@ class nsDocLoader : public nsIDocumentLoader,
// loadgroup) unless this is empty.
nsCOMArray<nsIDocumentLoader> mChildrenInOnload;
// The OOP counterpart to mChildrenInOnload.
// Not holding strong refs here since we don't actually use the BBCs.
nsTArray<const BrowserBridgeChild*> mOOPChildrenLoading;
int64_t GetMaxTotalProgress();
nsresult AddRequestInfo(nsIRequest* aRequest);