summaryrefslogtreecommitdiffstats
path: root/dom/base/nsFrameLoaderOwner.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/base/nsFrameLoaderOwner.cpp400
1 files changed, 400 insertions, 0 deletions
diff --git a/dom/base/nsFrameLoaderOwner.cpp b/dom/base/nsFrameLoaderOwner.cpp
new file mode 100644
index 0000000000..e9b8a876c5
--- /dev/null
+++ b/dom/base/nsFrameLoaderOwner.cpp
@@ -0,0 +1,400 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsFrameLoaderOwner.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "nsFrameLoader.h"
+#include "nsFocusManager.h"
+#include "nsNetUtil.h"
+#include "nsSubDocumentFrame.h"
+#include "nsQueryObject.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/Logging.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/FrameLoaderBinding.h"
+#include "mozilla/dom/HTMLIFrameElement.h"
+#include "mozilla/dom/MozFrameLoaderOwnerBinding.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/dom/BrowserBridgeChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/BrowserBridgeHost.h"
+#include "mozilla/dom/BrowserHost.h"
+#include "mozilla/StaticPrefs_fission.h"
+#include "mozilla/EventStateManager.h"
+
+extern mozilla::LazyLogModule gSHIPBFCacheLog;
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+already_AddRefed<nsFrameLoader> nsFrameLoaderOwner::GetFrameLoader() {
+ return do_AddRef(mFrameLoader);
+}
+
+void nsFrameLoaderOwner::SetFrameLoader(nsFrameLoader* aNewFrameLoader) {
+ mFrameLoader = aNewFrameLoader;
+}
+
+mozilla::dom::BrowsingContext* nsFrameLoaderOwner::GetBrowsingContext() {
+ if (mFrameLoader) {
+ return mFrameLoader->GetBrowsingContext();
+ }
+ return nullptr;
+}
+
+mozilla::dom::BrowsingContext* nsFrameLoaderOwner::GetExtantBrowsingContext() {
+ if (mFrameLoader) {
+ return mFrameLoader->GetExtantBrowsingContext();
+ }
+ return nullptr;
+}
+
+bool nsFrameLoaderOwner::UseRemoteSubframes() {
+ RefPtr<Element> owner = do_QueryObject(this);
+
+ nsILoadContext* loadContext = owner->OwnerDoc()->GetLoadContext();
+ MOZ_DIAGNOSTIC_ASSERT(loadContext);
+
+ return loadContext->UseRemoteSubframes();
+}
+
+nsFrameLoaderOwner::ChangeRemotenessContextType
+nsFrameLoaderOwner::ShouldPreserveBrowsingContext(
+ bool aIsRemote, bool aReplaceBrowsingContext) {
+ if (aReplaceBrowsingContext) {
+ return ChangeRemotenessContextType::DONT_PRESERVE;
+ }
+
+ if (XRE_IsParentProcess()) {
+ // Don't preserve for remote => parent loads.
+ if (!aIsRemote) {
+ return ChangeRemotenessContextType::DONT_PRESERVE;
+ }
+
+ // Don't preserve for parent => remote loads.
+ if (mFrameLoader && !mFrameLoader->IsRemoteFrame()) {
+ return ChangeRemotenessContextType::DONT_PRESERVE;
+ }
+ }
+
+ // We will preserve our browsing context if either fission is enabled, or the
+ // `preserve_browsing_contexts` pref is active.
+ if (UseRemoteSubframes() ||
+ StaticPrefs::fission_preserve_browsing_contexts()) {
+ return ChangeRemotenessContextType::PRESERVE;
+ }
+ return ChangeRemotenessContextType::DONT_PRESERVE;
+}
+
+void nsFrameLoaderOwner::ChangeRemotenessCommon(
+ const ChangeRemotenessContextType& aContextType,
+ const NavigationIsolationOptions& aOptions, bool aSwitchingInProgressLoad,
+ bool aIsRemote, BrowsingContextGroup* aGroup,
+ std::function<void()>& aFrameLoaderInit, mozilla::ErrorResult& aRv) {
+ MOZ_ASSERT_IF(aGroup, aContextType != ChangeRemotenessContextType::PRESERVE);
+
+ RefPtr<mozilla::dom::BrowsingContext> bc;
+ bool networkCreated = false;
+
+ // In this case, we're not reparenting a frameloader, we're just destroying
+ // our current one and creating a new one, so we can use ourselves as the
+ // owner.
+ RefPtr<Element> owner = do_QueryObject(this);
+ MOZ_ASSERT(owner);
+
+ // When we destroy the original frameloader, it will stop blocking the parent
+ // document's load event, and immediately trigger the load event if there are
+ // no other blockers. Since we're going to be adding a new blocker as soon as
+ // we recreate the frame loader, this is not what we want, so add our own
+ // blocker until the process is complete.
+ Document* doc = owner->OwnerDoc();
+ doc->BlockOnload();
+ auto cleanup = MakeScopeExit([&]() { doc->UnblockOnload(false); });
+
+ // If we store the previous nsFrameLoader in the bfcache, this will be filled
+ // with the SessionHistoryEntry which now owns the frame.
+ RefPtr<SessionHistoryEntry> bfcacheEntry;
+
+ {
+ // Introduce a script blocker to ensure no JS is executed during the
+ // nsFrameLoader teardown & recreation process. Unload listeners will be run
+ // for the previous document, and the load will be started for the new one,
+ // at the end of this block.
+ nsAutoScriptBlocker sb;
+
+ // If we already have a Frameloader, destroy it, possibly preserving its
+ // browsing context.
+ if (mFrameLoader) {
+ // Calling `GetBrowsingContext` here will force frameloader
+ // initialization if it hasn't already happened, which we neither need
+ // or want, so we use the initial (possibly pending) browsing context
+ // directly, instead.
+ bc = mFrameLoader->GetMaybePendingBrowsingContext();
+ networkCreated = mFrameLoader->IsNetworkCreated();
+
+ MOZ_ASSERT_IF(aOptions.mTryUseBFCache, aOptions.mReplaceBrowsingContext);
+ if (aOptions.mTryUseBFCache && bc) {
+ bfcacheEntry = bc->Canonical()->GetActiveSessionHistoryEntry();
+ bool useBFCache = bfcacheEntry &&
+ bfcacheEntry == aOptions.mActiveSessionHistoryEntry &&
+ !bfcacheEntry->GetFrameLoader();
+ if (useBFCache) {
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
+ ("nsFrameLoaderOwner::ChangeRemotenessCommon: store the old "
+ "page in bfcache"));
+ Unused << bc->SetIsInBFCache(true);
+ bfcacheEntry->SetFrameLoader(mFrameLoader);
+ // Session history owns now the frameloader.
+ mFrameLoader = nullptr;
+ }
+ }
+
+ if (mFrameLoader) {
+ if (aContextType == ChangeRemotenessContextType::PRESERVE) {
+ mFrameLoader->SetWillChangeProcess();
+ }
+
+ // Preserve the networkCreated status, as nsDocShells created after a
+ // process swap may shouldn't change their dynamically-created status.
+ mFrameLoader->Destroy(aSwitchingInProgressLoad);
+ mFrameLoader = nullptr;
+ }
+ }
+
+ mFrameLoader = nsFrameLoader::Recreate(
+ owner, bc, aGroup, aOptions, aIsRemote, networkCreated,
+ aContextType == ChangeRemotenessContextType::PRESERVE);
+ if (NS_WARN_IF(!mFrameLoader)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ // Invoke the frame loader initialization callback to perform setup on our
+ // new nsFrameLoader. This may cause our ErrorResult to become errored, so
+ // double-check after calling.
+ aFrameLoaderInit();
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ }
+
+ // Now that we have a new FrameLoader, we'll eventually need to reset
+ // nsSubDocumentFrame to use the new one. We can delay doing this if we're
+ // keeping our old frameloader around in the BFCache and the new frame hasn't
+ // presented yet to continue painting the previous document.
+ const bool retainPaint = bfcacheEntry && mFrameLoader->IsRemoteFrame();
+ if (!retainPaint) {
+ MOZ_LOG(
+ gSHIPBFCacheLog, LogLevel::Debug,
+ ("Previous frameLoader not entering BFCache - not retaining paint data"
+ "(bfcacheEntry=%p, isRemoteFrame=%d)",
+ bfcacheEntry.get(), mFrameLoader->IsRemoteFrame()));
+ }
+
+ ChangeFrameLoaderCommon(owner, retainPaint);
+
+ UpdateFocusAndMouseEnterStateAfterFrameLoaderChange(owner);
+}
+
+void nsFrameLoaderOwner::ChangeFrameLoaderCommon(Element* aOwner,
+ bool aRetainPaint) {
+ // Now that we've got a new FrameLoader, we need to reset our
+ // nsSubDocumentFrame to use the new FrameLoader.
+ if (nsSubDocumentFrame* ourFrame = do_QueryFrame(aOwner->GetPrimaryFrame())) {
+ auto retain = aRetainPaint ? nsSubDocumentFrame::RetainPaintData::Yes
+ : nsSubDocumentFrame::RetainPaintData::No;
+ ourFrame->ResetFrameLoader(retain);
+ }
+
+ if (aOwner->IsXULElement()) {
+ // Assuming this element is a XULFrameElement, once we've reset our
+ // FrameLoader, fire an event to act like we've recreated ourselves, similar
+ // to what XULFrameElement does after rebinding to the tree.
+ // ChromeOnlyDispatch is turns on to make sure this isn't fired into
+ // content.
+ mozilla::AsyncEventDispatcher::RunDOMEventWhenSafe(
+ *aOwner, u"XULFrameLoaderCreated"_ns, mozilla::CanBubble::eYes,
+ mozilla::ChromeOnlyDispatch::eYes);
+ }
+
+ if (mFrameLoader) {
+ mFrameLoader->PropagateIsUnderHiddenEmbedderElement(
+ !aOwner->GetPrimaryFrame() ||
+ !aOwner->GetPrimaryFrame()->StyleVisibility()->IsVisible());
+ }
+}
+
+void nsFrameLoaderOwner::UpdateFocusAndMouseEnterStateAfterFrameLoaderChange() {
+ RefPtr<Element> owner = do_QueryObject(this);
+ UpdateFocusAndMouseEnterStateAfterFrameLoaderChange(owner);
+}
+
+void nsFrameLoaderOwner::UpdateFocusAndMouseEnterStateAfterFrameLoaderChange(
+ Element* aOwner) {
+ // If the element is focused, or the current mouse over target then
+ // we need to update that state for the new BrowserParent too.
+ if (nsFocusManager* fm = nsFocusManager::GetFocusManager()) {
+ if (fm->GetFocusedElement() == aOwner) {
+ fm->ActivateRemoteFrameIfNeeded(*aOwner,
+ nsFocusManager::GenerateFocusActionId());
+ }
+ }
+
+ if (aOwner->GetPrimaryFrame()) {
+ EventStateManager* eventManager =
+ aOwner->GetPrimaryFrame()->PresContext()->EventStateManager();
+ eventManager->RecomputeMouseEnterStateForRemoteFrame(*aOwner);
+ }
+}
+
+void nsFrameLoaderOwner::ChangeRemoteness(
+ const mozilla::dom::RemotenessOptions& aOptions, mozilla::ErrorResult& rv) {
+ bool isRemote = !aOptions.mRemoteType.IsEmpty();
+
+ std::function<void()> frameLoaderInit = [&] {
+ if (isRemote) {
+ mFrameLoader->ConfigRemoteProcess(aOptions.mRemoteType, nullptr);
+ }
+
+ if (aOptions.mPendingSwitchID.WasPassed()) {
+ mFrameLoader->ResumeLoad(aOptions.mPendingSwitchID.Value());
+ } else {
+ mFrameLoader->LoadFrame(false);
+ }
+ };
+
+ auto shouldPreserve = ShouldPreserveBrowsingContext(
+ isRemote, /* replaceBrowsingContext */ false);
+ NavigationIsolationOptions options;
+ ChangeRemotenessCommon(shouldPreserve, options,
+ aOptions.mSwitchingInProgressLoad, isRemote,
+ /* group */ nullptr, frameLoaderInit, rv);
+}
+
+void nsFrameLoaderOwner::ChangeRemotenessWithBridge(BrowserBridgeChild* aBridge,
+ mozilla::ErrorResult& rv) {
+ MOZ_ASSERT(XRE_IsContentProcess());
+ if (NS_WARN_IF(!mFrameLoader)) {
+ rv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ std::function<void()> frameLoaderInit = [&] {
+ MOZ_DIAGNOSTIC_ASSERT(!mFrameLoader->mInitialized);
+ RefPtr<BrowserBridgeHost> host = aBridge->FinishInit(mFrameLoader);
+ mFrameLoader->mPendingBrowsingContext->SetEmbedderElement(
+ mFrameLoader->GetOwnerContent());
+ mFrameLoader->mRemoteBrowser = host;
+ mFrameLoader->mInitialized = true;
+ };
+
+ NavigationIsolationOptions options;
+ ChangeRemotenessCommon(ChangeRemotenessContextType::PRESERVE, options,
+ /* inProgress */ true,
+ /* isRemote */ true, /* group */ nullptr,
+ frameLoaderInit, rv);
+}
+
+void nsFrameLoaderOwner::ChangeRemotenessToProcess(
+ ContentParent* aContentParent, const NavigationIsolationOptions& aOptions,
+ BrowsingContextGroup* aGroup, mozilla::ErrorResult& rv) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT_IF(aGroup, aOptions.mReplaceBrowsingContext);
+ bool isRemote = aContentParent != nullptr;
+
+ std::function<void()> frameLoaderInit = [&] {
+ if (isRemote) {
+ mFrameLoader->ConfigRemoteProcess(aContentParent->GetRemoteType(),
+ aContentParent);
+ }
+ };
+
+ auto shouldPreserve =
+ ShouldPreserveBrowsingContext(isRemote, aOptions.mReplaceBrowsingContext);
+ ChangeRemotenessCommon(shouldPreserve, aOptions, /* inProgress */ true,
+ isRemote, aGroup, frameLoaderInit, rv);
+}
+
+void nsFrameLoaderOwner::SubframeCrashed() {
+ MOZ_ASSERT(XRE_IsContentProcess());
+
+ std::function<void()> frameLoaderInit = [&] {
+ RefPtr<nsFrameLoader> frameLoader = mFrameLoader;
+ nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
+ "nsFrameLoaderOwner::SubframeCrashed", [frameLoader]() {
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), "about:blank");
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ RefPtr<nsDocShell> docShell =
+ frameLoader->GetDocShell(IgnoreErrors());
+ if (NS_WARN_IF(!docShell)) {
+ return;
+ }
+ bool displayed = false;
+ docShell->DisplayLoadError(NS_ERROR_FRAME_CRASHED, uri,
+ u"about:blank", nullptr, &displayed);
+ }));
+ };
+
+ NavigationIsolationOptions options;
+ ChangeRemotenessCommon(ChangeRemotenessContextType::PRESERVE, options,
+ /* inProgress */ false, /* isRemote */ false,
+ /* group */ nullptr, frameLoaderInit, IgnoreErrors());
+}
+
+void nsFrameLoaderOwner::RestoreFrameLoaderFromBFCache(
+ nsFrameLoader* aNewFrameLoader) {
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
+ ("nsFrameLoaderOwner::RestoreFrameLoaderFromBFCache: Replace "
+ "frameloader"));
+
+ Maybe<bool> renderLayers;
+ if (mFrameLoader) {
+ if (auto* oldParent = mFrameLoader->GetBrowserParent()) {
+ renderLayers.emplace(oldParent->GetRenderLayers());
+ }
+ }
+
+ mFrameLoader = aNewFrameLoader;
+
+ if (auto* browserParent = mFrameLoader->GetBrowserParent()) {
+ browserParent->AddWindowListeners();
+ if (renderLayers.isSome()) {
+ browserParent->SetRenderLayers(renderLayers.value());
+ }
+ }
+
+ RefPtr<Element> owner = do_QueryObject(this);
+ ChangeFrameLoaderCommon(owner, /* aRetainPaint = */ false);
+}
+
+void nsFrameLoaderOwner::AttachFrameLoader(nsFrameLoader* aFrameLoader) {
+ mFrameLoaderList.insertBack(aFrameLoader);
+}
+
+void nsFrameLoaderOwner::DetachFrameLoader(nsFrameLoader* aFrameLoader) {
+ if (aFrameLoader->isInList()) {
+ MOZ_ASSERT(mFrameLoaderList.contains(aFrameLoader));
+ aFrameLoader->remove();
+ }
+}
+
+void nsFrameLoaderOwner::FrameLoaderDestroying(nsFrameLoader* aFrameLoader) {
+ if (aFrameLoader == mFrameLoader) {
+ while (!mFrameLoaderList.isEmpty()) {
+ RefPtr<nsFrameLoader> loader = mFrameLoaderList.popFirst();
+ if (loader != mFrameLoader) {
+ loader->Destroy();
+ }
+ }
+ } else {
+ DetachFrameLoader(aFrameLoader);
+ }
+}