/* -*- 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 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 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& aFrameLoaderInit, mozilla::ErrorResult& aRv) { MOZ_ASSERT_IF(aGroup, aContextType != ChangeRemotenessContextType::PRESERVE); RefPtr 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 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 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 frameLoaderInit = [&] { RefPtr 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 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 frameLoaderInit = [&] { RefPtr frameLoader = mFrameLoader; nsContentUtils::AddScriptRunner(NS_NewRunnableFunction( "nsFrameLoaderOwner::SubframeCrashed", [frameLoader]() { nsCOMPtr uri; nsresult rv = NS_NewURI(getter_AddRefs(uri), "about:blank"); if (NS_WARN_IF(NS_FAILED(rv))) { return; } RefPtr 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()); }