/* -*- 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 "mozilla/dom/CanonicalBrowsingContext.h" #include "mozilla/EventForwards.h" #include "mozilla/AsyncEventDispatcher.h" #include "mozilla/dom/BrowserParent.h" #include "mozilla/dom/BrowsingContextBinding.h" #include "mozilla/dom/BrowsingContextGroup.h" #include "mozilla/dom/ContentParent.h" #include "mozilla/dom/EventTarget.h" #include "mozilla/dom/WindowGlobalParent.h" #include "mozilla/dom/ContentProcessManager.h" #include "mozilla/dom/MediaController.h" #include "mozilla/dom/MediaControlService.h" #include "mozilla/dom/ContentPlaybackController.h" #include "mozilla/dom/SessionHistoryEntry.h" #include "mozilla/ipc/ProtocolUtils.h" #include "mozilla/net/DocumentLoadListener.h" #include "mozilla/NullPrincipal.h" #include "nsIWebNavigation.h" #include "mozilla/MozPromiseInlines.h" #include "nsGlobalWindowOuter.h" #include "nsIWebBrowserChrome.h" #include "nsNetUtil.h" #include "nsSHistory.h" #include "nsSecureBrowserUI.h" #include "nsQueryObject.h" #include "nsBrowserStatusFilter.h" #include "nsIBrowser.h" using namespace mozilla::ipc; extern mozilla::LazyLogModule gAutoplayPermissionLog; #define AUTOPLAY_LOG(msg, ...) \ MOZ_LOG(gAutoplayPermissionLog, LogLevel::Debug, (msg, ##__VA_ARGS__)) namespace mozilla { namespace dom { extern mozilla::LazyLogModule gUserInteractionPRLog; #define USER_ACTIVATION_LOG(msg, ...) \ MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug, (msg, ##__VA_ARGS__)) CanonicalBrowsingContext::CanonicalBrowsingContext(WindowContext* aParentWindow, BrowsingContextGroup* aGroup, uint64_t aBrowsingContextId, uint64_t aOwnerProcessId, uint64_t aEmbedderProcessId, BrowsingContext::Type aType, FieldTuple&& aFields) : BrowsingContext(aParentWindow, aGroup, aBrowsingContextId, aType, std::move(aFields)), mProcessId(aOwnerProcessId), mEmbedderProcessId(aEmbedderProcessId) { // You are only ever allowed to create CanonicalBrowsingContexts in the // parent process. MOZ_RELEASE_ASSERT(XRE_IsParentProcess()); } /* static */ already_AddRefed CanonicalBrowsingContext::Get( uint64_t aId) { MOZ_RELEASE_ASSERT(XRE_IsParentProcess()); return BrowsingContext::Get(aId).downcast(); } /* static */ CanonicalBrowsingContext* CanonicalBrowsingContext::Cast( BrowsingContext* aContext) { MOZ_RELEASE_ASSERT(XRE_IsParentProcess()); return static_cast(aContext); } /* static */ const CanonicalBrowsingContext* CanonicalBrowsingContext::Cast( const BrowsingContext* aContext) { MOZ_RELEASE_ASSERT(XRE_IsParentProcess()); return static_cast(aContext); } already_AddRefed CanonicalBrowsingContext::Cast( already_AddRefed&& aContext) { MOZ_RELEASE_ASSERT(XRE_IsParentProcess()); return aContext.downcast(); } ContentParent* CanonicalBrowsingContext::GetContentParent() const { if (mProcessId == 0) { return nullptr; } ContentProcessManager* cpm = ContentProcessManager::GetSingleton(); return cpm->GetContentProcessById(ContentParentId(mProcessId)); } void CanonicalBrowsingContext::GetCurrentRemoteType(nsACString& aRemoteType, ErrorResult& aRv) const { // If we're in the parent process, dump out the void string. if (mProcessId == 0) { aRemoteType = NOT_REMOTE_TYPE; return; } ContentParent* cp = GetContentParent(); if (!cp) { aRv.Throw(NS_ERROR_UNEXPECTED); return; } aRemoteType = cp->GetRemoteType(); } void CanonicalBrowsingContext::SetOwnerProcessId(uint64_t aProcessId) { MOZ_LOG(GetLog(), LogLevel::Debug, ("SetOwnerProcessId for 0x%08" PRIx64 " (0x%08" PRIx64 " -> 0x%08" PRIx64 ")", Id(), mProcessId, aProcessId)); mProcessId = aProcessId; } nsISecureBrowserUI* CanonicalBrowsingContext::GetSecureBrowserUI() { if (!IsTop()) { return nullptr; } if (!mSecureBrowserUI) { mSecureBrowserUI = new nsSecureBrowserUI(this); } return mSecureBrowserUI; } void CanonicalBrowsingContext::MaybeAddAsProgressListener( nsIWebProgress* aWebProgress) { if (!GetWebProgress()) { return; } if (!mStatusFilter) { mStatusFilter = new nsBrowserStatusFilter(); mStatusFilter->AddProgressListener(GetWebProgress(), nsIWebProgress::NOTIFY_ALL); } aWebProgress->AddProgressListener(mStatusFilter, nsIWebProgress::NOTIFY_ALL); } void CanonicalBrowsingContext::ReplacedBy( CanonicalBrowsingContext* aNewContext) { MOZ_ASSERT(!aNewContext->EverAttached()); if (mStatusFilter) { mStatusFilter->RemoveProgressListener(mWebProgress); mStatusFilter = nullptr; } aNewContext->mWebProgress = std::move(mWebProgress); aNewContext->mFields.SetWithoutSyncing(GetBrowserId()); } void CanonicalBrowsingContext:: UpdateSecurityStateForLocationOrMixedContentChange() { if (mSecureBrowserUI) { mSecureBrowserUI->UpdateForLocationOrMixedContentChange(); } } void CanonicalBrowsingContext::SetInFlightProcessId(uint64_t aProcessId) { MOZ_ASSERT(aProcessId); mInFlightProcessId = aProcessId; } void CanonicalBrowsingContext::ClearInFlightProcessId(uint64_t aProcessId) { MOZ_ASSERT(aProcessId); if (mInFlightProcessId == aProcessId) { mInFlightProcessId = 0; } } void CanonicalBrowsingContext::GetWindowGlobals( nsTArray>& aWindows) { aWindows.SetCapacity(GetWindowContexts().Length()); for (auto& window : GetWindowContexts()) { aWindows.AppendElement(static_cast(window.get())); } } WindowGlobalParent* CanonicalBrowsingContext::GetCurrentWindowGlobal() const { return static_cast(GetCurrentWindowContext()); } WindowGlobalParent* CanonicalBrowsingContext::GetParentWindowContext() { return static_cast( BrowsingContext::GetParentWindowContext()); } WindowGlobalParent* CanonicalBrowsingContext::GetTopWindowContext() { return static_cast( BrowsingContext::GetTopWindowContext()); } already_AddRefed CanonicalBrowsingContext::GetParentProcessWidgetContaining() { // If our document is loaded in-process, such as chrome documents, get the // widget directly from our outer window. Otherwise, try to get the widget // from the toplevel content's browser's element. nsCOMPtr widget; if (nsGlobalWindowOuter* window = nsGlobalWindowOuter::Cast(GetDOMWindow())) { widget = window->GetNearestWidget(); } else if (Element* topEmbedder = Top()->GetEmbedderElement()) { widget = nsContentUtils::WidgetForContent(topEmbedder); if (!widget) { widget = nsContentUtils::WidgetForDocument(topEmbedder->OwnerDoc()); } } if (widget) { widget = widget->GetTopLevelWidget(); } return widget.forget(); } already_AddRefed CanonicalBrowsingContext::GetEmbedderWindowGlobal() const { uint64_t windowId = GetEmbedderInnerWindowId(); if (windowId == 0) { return nullptr; } return WindowGlobalParent::GetByInnerWindowId(windowId); } already_AddRefed CanonicalBrowsingContext::GetParentCrossChromeBoundary() { if (GetParent()) { return do_AddRef(Cast(GetParent())); } if (GetEmbedderElement()) { return do_AddRef( Cast(GetEmbedderElement()->OwnerDoc()->GetBrowsingContext())); } return nullptr; } Nullable CanonicalBrowsingContext::GetTopChromeWindow() { RefPtr bc(this); while (RefPtr parent = bc->GetParentCrossChromeBoundary()) { bc = parent.forget(); } if (bc->IsChrome()) { return WindowProxyHolder(bc.forget()); } return nullptr; } nsISHistory* CanonicalBrowsingContext::GetSessionHistory() { if (!IsTop()) { return Cast(Top())->GetSessionHistory(); } // Check GetChildSessionHistory() to make sure that this BrowsingContext has // session history enabled. if (!mSessionHistory && GetChildSessionHistory()) { mSessionHistory = new nsSHistory(this); } return mSessionHistory; } UniquePtr CanonicalBrowsingContext::CreateSessionHistoryEntryForLoad( nsDocShellLoadState* aLoadState, nsIChannel* aChannel) { RefPtr entry = new SessionHistoryEntry(aLoadState, aChannel); mLoadingEntries.AppendElement(entry); MOZ_ASSERT(SessionHistoryEntry::GetByInfoId(entry->Info().Id()) == entry); return MakeUnique(entry->Info()); } void CanonicalBrowsingContext::SessionHistoryCommit( uint64_t aSessionHistoryEntryId) { for (size_t i = 0; i < mLoadingEntries.Length(); ++i) { if (mLoadingEntries[i]->Info().Id() == aSessionHistoryEntryId) { nsISHistory* shistory = GetSessionHistory(); if (!shistory) { mLoadingEntries.RemoveElementAt(i); return; } RefPtr oldActiveEntry = mActiveEntry.forget(); mActiveEntry = mLoadingEntries[i]; mLoadingEntries.RemoveElementAt(i); if (IsTop()) { shistory->AddEntry(mActiveEntry, /* FIXME aPersist = */ true); } else { // FIXME Check if we're replacing before adding a child. // FIXME The old implementations adds it to the parent's mLSHE if there // is one, need to figure out if that makes sense here (peterv // doesn't think it would). if (oldActiveEntry) { // FIXME Need to figure out the right value for aCloneChildren. shistory->AddChildSHEntryHelper(oldActiveEntry, mActiveEntry, Top(), true); } else { SessionHistoryEntry* parentEntry = static_cast(GetParent())->mActiveEntry; if (parentEntry) { // FIXME The docshell code sometime uses -1 for aChildOffset! // FIXME Using IsInProcess for aUseRemoteSubframes isn't quite // right, but aUseRemoteSubframes should be going away. parentEntry->AddChild(mActiveEntry, Children().Length() - 1, IsInProcess()); } } } Group()->EachParent([&](ContentParent* aParent) { // FIXME Should we return the length to the one process that committed // as an async return value? Or should this use synced fields? Unused << aParent->SendHistoryCommitLength(Top(), shistory->GetCount()); }); return; } } // FIXME Should we throw an error if we don't find an entry for // aSessionHistoryEntryId? } void CanonicalBrowsingContext::NotifyOnHistoryReload( bool& aCanReload, Maybe>& aLoadState, Maybe& aReloadActiveEntry) { GetSessionHistory()->NotifyOnHistoryReload(&aCanReload); if (!aCanReload) { return; } if (mActiveEntry) { aLoadState.emplace(); mActiveEntry->CreateLoadInfo(getter_AddRefs(aLoadState.ref())); aReloadActiveEntry.emplace(true); } else if (!mLoadingEntries.IsEmpty()) { aLoadState.emplace(); mLoadingEntries.LastElement()->CreateLoadInfo( getter_AddRefs(aLoadState.ref())); aReloadActiveEntry.emplace(false); } // If we don't have an active entry and we don't have a loading entry then // the nsDocShell will create a load state based on its document. } JSObject* CanonicalBrowsingContext::WrapObject( JSContext* aCx, JS::Handle aGivenProto) { return CanonicalBrowsingContext_Binding::Wrap(aCx, this, aGivenProto); } void CanonicalBrowsingContext::DispatchWheelZoomChange(bool aIncrease) { Element* element = Top()->GetEmbedderElement(); if (!element) { return; } auto event = aIncrease ? u"DoZoomEnlargeBy10"_ns : u"DoZoomReduceBy10"_ns; auto dispatcher = MakeRefPtr( element, event, CanBubble::eYes, ChromeOnlyDispatch::eYes); dispatcher->PostDOMEvent(); } void CanonicalBrowsingContext::CanonicalDiscard() { if (mTabMediaController) { mTabMediaController->Shutdown(); mTabMediaController = nullptr; } } void CanonicalBrowsingContext::NotifyStartDelayedAutoplayMedia() { if (!GetCurrentWindowGlobal()) { return; } // As this function would only be called when user click the play icon on the // tab bar. That's clear user intent to play, so gesture activate the browsing // context so that the block-autoplay logic allows the media to autoplay. NotifyUserGestureActivation(); AUTOPLAY_LOG("NotifyStartDelayedAutoplayMedia for chrome bc 0x%08" PRIx64, Id()); StartDelayedAutoplayMediaComponents(); // Notfiy all content browsing contexts which are related with the canonical // browsing content tree to start delayed autoplay media. Group()->EachParent([&](ContentParent* aParent) { Unused << aParent->SendStartDelayedAutoplayMediaComponents(this); }); } void CanonicalBrowsingContext::NotifyMediaMutedChanged(bool aMuted) { MOZ_ASSERT(!GetParent(), "Notify media mute change on non top-level context!"); SetMuted(aMuted); } uint32_t CanonicalBrowsingContext::CountSiteOrigins( GlobalObject& aGlobal, const Sequence>& aRoots) { nsTHashtable uniqueSiteOrigins; for (const auto& root : aRoots) { root->PreOrderWalk([&](BrowsingContext* aContext) { WindowGlobalParent* windowGlobalParent = aContext->Canonical()->GetCurrentWindowGlobal(); if (windowGlobalParent) { nsIPrincipal* documentPrincipal = windowGlobalParent->DocumentPrincipal(); bool isContentPrincipal = documentPrincipal->GetIsContentPrincipal(); if (isContentPrincipal) { nsCString siteOrigin; documentPrincipal->GetSiteOrigin(siteOrigin); uniqueSiteOrigins.PutEntry(siteOrigin); } } }); } return uniqueSiteOrigins.Count(); } void CanonicalBrowsingContext::UpdateMediaControlAction( const MediaControlAction& aAction) { ContentMediaControlKeyHandler::HandleMediaControlAction(this, aAction); Group()->EachParent([&](ContentParent* aParent) { Unused << aParent->SendUpdateMediaControlAction(this, aAction); }); } void CanonicalBrowsingContext::LoadURI(const nsAString& aURI, const LoadURIOptions& aOptions, ErrorResult& aError) { RefPtr loadState; nsresult rv = nsDocShellLoadState::CreateFromLoadURIOptions( this, aURI, aOptions, getter_AddRefs(loadState)); if (rv == NS_ERROR_MALFORMED_URI) { DisplayLoadError(aURI); return; } if (NS_FAILED(rv)) { aError.Throw(rv); return; } LoadURI(loadState, true); } void CanonicalBrowsingContext::GoBack( const Optional& aCancelContentJSEpoch, bool aRequireUserInteraction) { if (IsDiscarded()) { return; } // Stop any known network loads if necessary. if (mCurrentLoad) { mCurrentLoad->Cancel(NS_BINDING_ABORTED); } if (nsDocShell* docShell = nsDocShell::Cast(GetDocShell())) { if (aCancelContentJSEpoch.WasPassed()) { docShell->SetCancelContentJSEpoch(aCancelContentJSEpoch.Value()); } docShell->GoBack(aRequireUserInteraction); } else if (ContentParent* cp = GetContentParent()) { Maybe cancelContentJSEpoch; if (aCancelContentJSEpoch.WasPassed()) { cancelContentJSEpoch = Some(aCancelContentJSEpoch.Value()); } Unused << cp->SendGoBack(this, cancelContentJSEpoch, aRequireUserInteraction); } } void CanonicalBrowsingContext::GoForward( const Optional& aCancelContentJSEpoch, bool aRequireUserInteraction) { if (IsDiscarded()) { return; } // Stop any known network loads if necessary. if (mCurrentLoad) { mCurrentLoad->Cancel(NS_BINDING_ABORTED); } if (auto* docShell = nsDocShell::Cast(GetDocShell())) { if (aCancelContentJSEpoch.WasPassed()) { docShell->SetCancelContentJSEpoch(aCancelContentJSEpoch.Value()); } docShell->GoForward(aRequireUserInteraction); } else if (ContentParent* cp = GetContentParent()) { Maybe cancelContentJSEpoch; if (aCancelContentJSEpoch.WasPassed()) { cancelContentJSEpoch.emplace(aCancelContentJSEpoch.Value()); } Unused << cp->SendGoForward(this, cancelContentJSEpoch, aRequireUserInteraction); } } void CanonicalBrowsingContext::GoToIndex( int32_t aIndex, const Optional& aCancelContentJSEpoch) { if (IsDiscarded()) { return; } // Stop any known network loads if necessary. if (mCurrentLoad) { mCurrentLoad->Cancel(NS_BINDING_ABORTED); } if (auto* docShell = nsDocShell::Cast(GetDocShell())) { if (aCancelContentJSEpoch.WasPassed()) { docShell->SetCancelContentJSEpoch(aCancelContentJSEpoch.Value()); } docShell->GotoIndex(aIndex); } else if (ContentParent* cp = GetContentParent()) { Maybe cancelContentJSEpoch; if (aCancelContentJSEpoch.WasPassed()) { cancelContentJSEpoch.emplace(aCancelContentJSEpoch.Value()); } Unused << cp->SendGoToIndex(this, aIndex, cancelContentJSEpoch); } } void CanonicalBrowsingContext::Reload(uint32_t aReloadFlags) { if (IsDiscarded()) { return; } // Stop any known network loads if necessary. if (mCurrentLoad) { mCurrentLoad->Cancel(NS_BINDING_ABORTED); } if (auto* docShell = nsDocShell::Cast(GetDocShell())) { docShell->Reload(aReloadFlags); } else if (ContentParent* cp = GetContentParent()) { Unused << cp->SendReload(this, aReloadFlags); } } void CanonicalBrowsingContext::Stop(uint32_t aStopFlags) { if (IsDiscarded()) { return; } // Stop any known network loads if necessary. if (mCurrentLoad && (aStopFlags & nsIWebNavigation::STOP_NETWORK)) { mCurrentLoad->Cancel(NS_BINDING_ABORTED); } // Ask the docshell to stop to handle loads that haven't // yet reached here, as well as non-network activity. if (auto* docShell = nsDocShell::Cast(GetDocShell())) { docShell->Stop(aStopFlags); } else if (ContentParent* cp = GetContentParent()) { Unused << cp->SendStopLoad(this, aStopFlags); } } void CanonicalBrowsingContext::PendingRemotenessChange::ProcessReady() { if (!mPromise) { return; } // Wait for our blocker promise to resolve, if present. if (mPrepareToChangePromise) { mPrepareToChangePromise->Then( GetMainThreadSerialEventTarget(), __func__, [self = RefPtr{this}](bool) { self->Finish(); }, [self = RefPtr{this}](nsresult aRv) { self->Cancel(aRv); }); return; } Finish(); } void CanonicalBrowsingContext::PendingRemotenessChange::Finish() { if (!mPromise) { return; } RefPtr target(mTarget); if (target->IsDiscarded()) { Cancel(NS_ERROR_FAILURE); return; } // If this BrowsingContext is embedded within the parent process, perform the // process switch directly. if (Element* browserElement = target->GetEmbedderElement()) { MOZ_DIAGNOSTIC_ASSERT(target->IsTop(), "We shouldn't be trying to change the remoteness of " "non-remote iframes"); nsCOMPtr browser = browserElement->AsBrowser(); if (!browser) { Cancel(NS_ERROR_FAILURE); return; } RefPtr frameLoaderOwner = do_QueryObject(browserElement); MOZ_RELEASE_ASSERT(frameLoaderOwner, "embedder browser must be nsFrameLoaderOwner"); // Tell frontend code that this browser element is about to change process. nsresult rv = browser->BeforeChangeRemoteness(); if (NS_FAILED(rv)) { Cancel(rv); return; } // Some frontend code checks the value of the `remote` attribute on the // browser to determine if it is remote, so update the value. browserElement->SetAttr(kNameSpaceID_None, nsGkAtoms::remote, mContentParent ? u"true"_ns : u"false"_ns, /* notify */ true); RefPtr specificGroup; if (mSpecificGroupId != 0) { specificGroup = BrowsingContextGroup::GetOrCreate(mSpecificGroupId); } // The process has been created, hand off to nsFrameLoaderOwner to finish // the process switch. ErrorResult error; frameLoaderOwner->ChangeRemotenessToProcess( mContentParent, mReplaceBrowsingContext, specificGroup, error); if (error.Failed()) { Cancel(error.StealNSResult()); return; } // Tell frontend the load is done. bool loadResumed = false; rv = browser->FinishChangeRemoteness(mPendingSwitchId, &loadResumed); if (NS_WARN_IF(NS_FAILED(rv))) { Cancel(rv); return; } // We did it! The process switch is complete. RefPtr frameLoader = frameLoaderOwner->GetFrameLoader(); RefPtr newBrowser = frameLoader->GetBrowserParent(); if (!newBrowser) { if (mContentParent) { // Failed to create the BrowserParent somehow! Abort the process switch // attempt. Cancel(NS_ERROR_UNEXPECTED); return; } if (!loadResumed) { RefPtr newDocShell = frameLoader->GetDocShell(error); if (error.Failed()) { Cancel(error.StealNSResult()); return; } rv = newDocShell->ResumeRedirectedLoad(mPendingSwitchId, /* aHistoryIndex */ -1); if (NS_FAILED(rv)) { Cancel(error.StealNSResult()); return; } } } else if (!loadResumed) { newBrowser->ResumeLoad(mPendingSwitchId); } mPromise->Resolve(newBrowser, __func__); Clear(); return; } if (NS_WARN_IF(!mContentParent)) { Cancel(NS_ERROR_FAILURE); return; } RefPtr embedderWindow = target->GetEmbedderWindowGlobal(); if (NS_WARN_IF(!embedderWindow) || NS_WARN_IF(!embedderWindow->CanSend())) { Cancel(NS_ERROR_FAILURE); return; } RefPtr embedderBrowser = embedderWindow->GetBrowserParent(); if (NS_WARN_IF(!embedderBrowser)) { Cancel(NS_ERROR_FAILURE); return; } // Pull load flags from our embedder browser. nsCOMPtr loadContext = embedderBrowser->GetLoadContext(); MOZ_DIAGNOSTIC_ASSERT( loadContext->UseRemoteTabs() && loadContext->UseRemoteSubframes(), "Not supported without fission"); // NOTE: These are the only flags we actually care about uint32_t chromeFlags = nsIWebBrowserChrome::CHROME_REMOTE_WINDOW | nsIWebBrowserChrome::CHROME_FISSION_WINDOW; if (loadContext->UsePrivateBrowsing()) { chromeFlags |= nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW; } RefPtr oldWindow = target->GetCurrentWindowGlobal(); RefPtr oldBrowser = oldWindow ? oldWindow->GetBrowserParent() : nullptr; bool wasRemote = oldWindow && oldWindow->IsProcessRoot(); // Update which process is considered the current owner uint64_t inFlightProcessId = target->OwnerProcessId(); target->SetInFlightProcessId(inFlightProcessId); target->SetOwnerProcessId(mContentParent->ChildID()); auto resetInFlightId = [target, inFlightProcessId] { target->ClearInFlightProcessId(inFlightProcessId); }; // If we were in a remote frame, trigger unloading of the remote window. When // the original remote window acknowledges, we can clear the in-flight ID. if (wasRemote) { MOZ_DIAGNOSTIC_ASSERT(oldBrowser); MOZ_DIAGNOSTIC_ASSERT(oldBrowser != embedderBrowser); MOZ_DIAGNOSTIC_ASSERT(oldBrowser->GetBrowserBridgeParent()); auto callback = [resetInFlightId](auto) { resetInFlightId(); }; oldBrowser->SendWillChangeProcess(callback, callback); oldBrowser->Destroy(); } MOZ_ASSERT(!mReplaceBrowsingContext, "Cannot replace BC for subframe"); nsCOMPtr initialPrincipal = NullPrincipal::CreateWithInheritedAttributes( target->OriginAttributesRef(), /* isFirstParty */ false); WindowGlobalInit windowInit = WindowGlobalActor::AboutBlankInitializer(target, initialPrincipal); // Create and initialize our new BrowserBridgeParent. TabId tabId(nsContentUtils::GenerateTabId()); RefPtr bridge = new BrowserBridgeParent(); nsresult rv = bridge->InitWithProcess(embedderBrowser, mContentParent, windowInit, chromeFlags, tabId); if (NS_WARN_IF(NS_FAILED(rv))) { Cancel(rv); return; } // Tell the embedder process a remoteness change is in-process. When this is // acknowledged, reset the in-flight ID if it used to be an in-process load. RefPtr newBrowser = bridge->GetBrowserParent(); { auto callback = [wasRemote, resetInFlightId](auto) { if (!wasRemote) { resetInFlightId(); } }; ManagedEndpoint endpoint = embedderBrowser->OpenPBrowserBridgeEndpoint(bridge); if (NS_WARN_IF(!endpoint.IsValid())) { Cancel(NS_ERROR_UNEXPECTED); return; } embedderWindow->SendMakeFrameRemote(target, std::move(endpoint), tabId, newBrowser->GetLayersId(), callback, callback); } // Resume the pending load in our new process. newBrowser->ResumeLoad(mPendingSwitchId); // We did it! The process switch is complete. mPromise->Resolve(newBrowser, __func__); Clear(); } void CanonicalBrowsingContext::PendingRemotenessChange::Cancel(nsresult aRv) { if (!mPromise) { return; } mPromise->Reject(aRv, __func__); Clear(); } void CanonicalBrowsingContext::PendingRemotenessChange::Clear() { // Make sure we don't die while we're doing cleanup. RefPtr kungFuDeathGrip(this); if (mTarget) { MOZ_DIAGNOSTIC_ASSERT(mTarget->mPendingRemotenessChange == this); mTarget->mPendingRemotenessChange = nullptr; } // When this PendingRemotenessChange was created, it was given a // `mContentParent`. if (mContentParent) { mContentParent->RemoveKeepAlive(); mContentParent = nullptr; } mPromise = nullptr; mTarget = nullptr; mPrepareToChangePromise = nullptr; } CanonicalBrowsingContext::PendingRemotenessChange::PendingRemotenessChange( CanonicalBrowsingContext* aTarget, RemotenessPromise::Private* aPromise, uint64_t aPendingSwitchId, bool aReplaceBrowsingContext, uint64_t aSpecificGroupId) : mTarget(aTarget), mPromise(aPromise), mPendingSwitchId(aPendingSwitchId), mSpecificGroupId(aSpecificGroupId), mReplaceBrowsingContext(aReplaceBrowsingContext) {} CanonicalBrowsingContext::PendingRemotenessChange::~PendingRemotenessChange() { MOZ_ASSERT(!mPromise && !mTarget, "should've already been Cancel() or Complete()-ed"); } RefPtr CanonicalBrowsingContext::ChangeRemoteness(const nsACString& aRemoteType, uint64_t aPendingSwitchId, bool aReplaceBrowsingContext, uint64_t aSpecificGroupId) { MOZ_DIAGNOSTIC_ASSERT(IsContent(), "cannot change the process of chrome contexts"); MOZ_DIAGNOSTIC_ASSERT( IsTop() == IsEmbeddedInProcess(0), "toplevel content must be embedded in the parent process"); MOZ_DIAGNOSTIC_ASSERT(!aReplaceBrowsingContext || IsTop(), "Cannot replace BrowsingContext for subframes"); MOZ_DIAGNOSTIC_ASSERT(aSpecificGroupId == 0 || aReplaceBrowsingContext, "Cannot specify group ID unless replacing BC"); // Ensure our embedder hasn't been destroyed already. RefPtr embedderWindowGlobal = GetEmbedderWindowGlobal(); if (!embedderWindowGlobal) { NS_WARNING("Non-embedded BrowsingContext"); return RemotenessPromise::CreateAndReject(NS_ERROR_UNEXPECTED, __func__); } if (!embedderWindowGlobal->CanSend()) { NS_WARNING("Embedder already been destroyed."); return RemotenessPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__); } if (aRemoteType.IsEmpty() && (!IsTop() || !GetEmbedderElement())) { NS_WARNING("Cannot load non-remote subframes"); return RemotenessPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); } // Cancel ongoing remoteness changes. if (mPendingRemotenessChange) { mPendingRemotenessChange->Cancel(NS_ERROR_ABORT); MOZ_DIAGNOSTIC_ASSERT(!mPendingRemotenessChange, "Should have cleared"); } RefPtr embedderBrowser = embedderWindowGlobal->GetBrowserParent(); // Switching to local. No new process, so perform switch sync. if (embedderBrowser && aRemoteType.Equals(embedderBrowser->Manager()->GetRemoteType())) { if (GetCurrentWindowGlobal()) { MOZ_DIAGNOSTIC_ASSERT(GetCurrentWindowGlobal()->IsProcessRoot()); RefPtr oldBrowser = GetCurrentWindowGlobal()->GetBrowserParent(); uint64_t targetProcessId = OwnerProcessId(); SetInFlightProcessId(targetProcessId); auto callback = [target = RefPtr{this}, targetProcessId](auto) { target->ClearInFlightProcessId(targetProcessId); }; oldBrowser->SendWillChangeProcess(callback, callback); oldBrowser->Destroy(); } // If the embedder process is remote, tell that remote process to become // the owner. MOZ_DIAGNOSTIC_ASSERT(!aReplaceBrowsingContext); MOZ_DIAGNOSTIC_ASSERT(!aRemoteType.IsEmpty()); SetOwnerProcessId(embedderBrowser->Manager()->ChildID()); Unused << embedderWindowGlobal->SendMakeFrameLocal(this, aPendingSwitchId); return RemotenessPromise::CreateAndResolve(embedderBrowser, __func__); } // Switching to remote. Wait for new process to launch before switch. auto promise = MakeRefPtr(__func__); RefPtr change = new PendingRemotenessChange(this, promise, aPendingSwitchId, aReplaceBrowsingContext, aSpecificGroupId); mPendingRemotenessChange = change; // Call `prepareToChangeRemoteness` in parallel with starting a new process // for loads. if (IsTop() && GetEmbedderElement()) { nsCOMPtr browser = GetEmbedderElement()->AsBrowser(); if (!browser) { change->Cancel(NS_ERROR_FAILURE); return promise.forget(); } RefPtr blocker; nsresult rv = browser->PrepareToChangeRemoteness(getter_AddRefs(blocker)); if (NS_FAILED(rv)) { change->Cancel(rv); return promise.forget(); } change->mPrepareToChangePromise = GenericPromise::FromDomPromise(blocker); } if (aRemoteType.IsEmpty()) { change->ProcessReady(); } else { change->mContentParent = ContentParent::GetNewOrUsedLaunchingBrowserProcess( /* aFrameElement = */ nullptr, /* aRemoteType = */ aRemoteType, /* aPriority = */ hal::PROCESS_PRIORITY_FOREGROUND, /* aPreferUsed = */ false); if (!change->mContentParent) { change->Cancel(NS_ERROR_FAILURE); return promise.forget(); } // Add a KeepAlive used by this ContentParent, which will be cleared when // the change is complete. This should prevent the process dying before // we're ready to use it. change->mContentParent->AddKeepAlive(); change->mContentParent->WaitForLaunchAsync()->Then( GetMainThreadSerialEventTarget(), __func__, [change](ContentParent*) { change->ProcessReady(); }, [change](LaunchError) { change->Cancel(NS_ERROR_FAILURE); }); } return promise.forget(); } MediaController* CanonicalBrowsingContext::GetMediaController() { // We would only create one media controller per tab, so accessing the // controller via the top-level browsing context. if (GetParent()) { return Cast(Top())->GetMediaController(); } MOZ_ASSERT(!GetParent(), "Must access the controller from the top-level browsing context!"); // Only content browsing context can create media controller, we won't create // controller for chrome document, such as the browser UI. if (!mTabMediaController && !IsDiscarded() && IsContent()) { mTabMediaController = new MediaController(Id()); } return mTabMediaController; } bool CanonicalBrowsingContext::SupportsLoadingInParent( nsDocShellLoadState* aLoadState, uint64_t* aOuterWindowId) { // We currently don't support initiating loads in the parent when they are // watched by devtools. This is because devtools tracks loads using content // process notifications, which happens after the load is initiated in this // case. Devtools clears all prior requests when it detects a new navigation, // so it drops the main document load that happened here. if (WatchedByDevTools()) { return false; } // DocumentChannel currently only supports connecting channels into the // content process, so we can only support schemes that will always be loaded // there for now. Restrict to just http(s) for simplicity. if (!net::SchemeIsHTTP(aLoadState->URI()) && !net::SchemeIsHTTPS(aLoadState->URI())) { return false; } if (WindowGlobalParent* global = GetCurrentWindowGlobal()) { nsCOMPtr currentURI = global->GetDocumentURI(); if (currentURI) { bool newURIHasRef = false; aLoadState->URI()->GetHasRef(&newURIHasRef); bool equalsExceptRef = false; aLoadState->URI()->EqualsExceptRef(currentURI, &equalsExceptRef); if (equalsExceptRef && newURIHasRef) { // This navigation is same-doc WRT the current one, we should pass it // down to the docshell to be handled. return false; } } // If the current document has a beforeunload listener, then we need to // start the load in that process after we fire the event. if (global->HasBeforeUnload()) { return false; } *aOuterWindowId = global->OuterWindowId(); } return true; } bool CanonicalBrowsingContext::LoadInParent(nsDocShellLoadState* aLoadState, bool aSetNavigating) { // We currently only support starting loads directly from the // CanonicalBrowsingContext for top-level BCs. // We currently only support starting loads directly from the // CanonicalBrowsingContext for top-level BCs. if (!IsTopContent() || !GetContentParent() || !StaticPrefs::browser_tabs_documentchannel() || !StaticPrefs::browser_tabs_documentchannel_parent_controlled()) { return false; } uint64_t outerWindowId = 0; if (!SupportsLoadingInParent(aLoadState, &outerWindowId)) { return false; } // Note: If successful, this will recurse into StartDocumentLoad and // set mCurrentLoad to the DocumentLoadListener instance created. // Ideally in the future we will only start loads from here, and we can // just set this directly instead. return net::DocumentLoadListener::LoadInParent(this, aLoadState, outerWindowId, aSetNavigating); } bool CanonicalBrowsingContext::AttemptSpeculativeLoadInParent( nsDocShellLoadState* aLoadState) { // We currently only support starting loads directly from the // CanonicalBrowsingContext for top-level BCs. // We currently only support starting loads directly from the // CanonicalBrowsingContext for top-level BCs. if (!IsTopContent() || !GetContentParent() || !StaticPrefs::browser_tabs_documentchannel() || !StaticPrefs::browser_tabs_documentchannel_parent_initiated() || StaticPrefs::browser_tabs_documentchannel_parent_controlled()) { return false; } uint64_t outerWindowId = 0; if (!SupportsLoadingInParent(aLoadState, &outerWindowId)) { return false; } // If we successfully open the DocumentChannel, then it'll register // itself using aLoadIdentifier and be kept alive until it completes // loading. return net::DocumentLoadListener::SpeculativeLoadInParent(this, aLoadState, outerWindowId); } bool CanonicalBrowsingContext::StartDocumentLoad( net::DocumentLoadListener* aLoad) { // If we're controlling loads from the parent, then starting a new load means // that we need to cancel any existing ones. if (StaticPrefs::browser_tabs_documentchannel_parent_controlled() && mCurrentLoad) { mCurrentLoad->Cancel(NS_BINDING_ABORTED); } mCurrentLoad = aLoad; SetCurrentLoadIdentifier(Some(aLoad->GetLoadIdentifier())); return true; } void CanonicalBrowsingContext::EndDocumentLoad(bool aForProcessSwitch) { mCurrentLoad = nullptr; if (!aForProcessSwitch) { SetCurrentLoadIdentifier(Nothing()); } } NS_IMPL_CYCLE_COLLECTION_INHERITED(CanonicalBrowsingContext, BrowsingContext, mSessionHistory) NS_IMPL_ADDREF_INHERITED(CanonicalBrowsingContext, BrowsingContext) NS_IMPL_RELEASE_INHERITED(CanonicalBrowsingContext, BrowsingContext) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CanonicalBrowsingContext) NS_INTERFACE_MAP_END_INHERITING(BrowsingContext) } // namespace dom } // namespace mozilla