/* -*- 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 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, const NavigationIsolationOptions& aOptions, 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); }); // If we store the previous nsFrameLoader in the bfcache, this will be filled // with the SessionHistoryEntry which now owns the frame. RefPtr 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 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 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 frameLoaderInit = [&] { MOZ_DIAGNOSTIC_ASSERT(!mFrameLoader->mInitialized); RefPtr 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 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 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); })); }; 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 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 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, bool aDestroyBFCached) { if (aFrameLoader == mFrameLoader) { if (aDestroyBFCached) { while (!mFrameLoaderList.isEmpty()) { RefPtr loader = mFrameLoaderList.popFirst(); if (loader != mFrameLoader) { loader->Destroy(); } } } } else { DetachFrameLoader(aFrameLoader); } }