diff options
Diffstat (limited to 'dom/base/nsFrameLoaderOwner.cpp')
-rw-r--r-- | dom/base/nsFrameLoaderOwner.cpp | 279 |
1 files changed, 279 insertions, 0 deletions
diff --git a/dom/base/nsFrameLoaderOwner.cpp b/dom/base/nsFrameLoaderOwner.cpp new file mode 100644 index 0000000000..dc2c8c70d3 --- /dev/null +++ b/dom/base/nsFrameLoaderOwner.cpp @@ -0,0 +1,279 @@ +/* -*- 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 "nsFrameLoader.h" +#include "nsFocusManager.h" +#include "nsNetUtil.h" +#include "nsSubDocumentFrame.h" +#include "nsQueryObject.h" +#include "mozilla/AsyncEventDispatcher.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" + +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, + 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); }); + + { + // 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(); + 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. + networkCreated = mFrameLoader->IsNetworkCreated(); + mFrameLoader->Destroy(aSwitchingInProgressLoad); + mFrameLoader = nullptr; + } + + mFrameLoader = nsFrameLoader::Recreate( + owner, bc, aGroup, 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've got a new FrameLoader, we need to reset our + // nsSubDocumentFrame to use the new FrameLoader. + if (nsSubDocumentFrame* ourFrame = do_QueryFrame(owner->GetPrimaryFrame())) { + ourFrame->ResetFrameLoader(); + } + + // 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() == owner) { + fm->ActivateRemoteFrameIfNeeded(*owner, + nsFocusManager::GenerateFocusActionId()); + } + } + + if (owner->GetPrimaryFrame()) { + EventStateManager* eventManager = + owner->GetPrimaryFrame()->PresContext()->EventStateManager(); + eventManager->RecomputeMouseEnterStateForRemoteFrame(*owner); + } + + if (owner->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. + (new mozilla::AsyncEventDispatcher(owner, u"XULFrameLoaderCreated"_ns, + mozilla::CanBubble::eYes, + mozilla::ChromeOnlyDispatch::eYes)) + ->RunDOMEventWhenSafe(); + } +} + +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); + ChangeRemotenessCommon(shouldPreserve, 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 = [&] { + RefPtr<BrowserBridgeHost> host = aBridge->FinishInit(mFrameLoader); + mFrameLoader->mPendingBrowsingContext->SetEmbedderElement( + mFrameLoader->GetOwnerContent()); + mFrameLoader->mRemoteBrowser = host; + }; + + ChangeRemotenessCommon(ChangeRemotenessContextType::PRESERVE, + /* inProgress */ true, + /* isRemote */ true, /* group */ nullptr, + frameLoaderInit, rv); +} + +void nsFrameLoaderOwner::ChangeRemotenessToProcess( + ContentParent* aContentParent, bool aReplaceBrowsingContext, + BrowsingContextGroup* aGroup, mozilla::ErrorResult& rv) { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT_IF(aGroup, aReplaceBrowsingContext); + bool isRemote = aContentParent != nullptr; + + std::function<void()> frameLoaderInit = [&] { + if (isRemote) { + mFrameLoader->ConfigRemoteProcess(aContentParent->GetRemoteType(), + aContentParent); + } + }; + + auto shouldPreserve = + ShouldPreserveBrowsingContext(isRemote, aReplaceBrowsingContext); + ChangeRemotenessCommon(shouldPreserve, /* 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); + })); + }; + + ChangeRemotenessCommon(ChangeRemotenessContextType::PRESERVE, + /* inProgress */ false, /* isRemote */ false, + /* group */ nullptr, frameLoaderInit, IgnoreErrors()); +} |