/* -*- 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/CheckedInt.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/dom/SessionStorageManager.h" #include "mozilla/ipc/ProtocolUtils.h" #include "mozilla/net/DocumentLoadListener.h" #include "mozilla/NullPrincipal.h" #include "nsIWebNavigation.h" #include "mozilla/MozPromiseInlines.h" #include "nsDocShell.h" #include "nsFrameLoader.h" #include "nsFrameLoaderOwner.h" #include "nsGlobalWindowOuter.h" #include "nsIWebBrowserChrome.h" #include "nsIXULRuntime.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; extern mozilla::LazyLogModule gSHLog; #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, FieldValues&& aInit) : BrowsingContext(aParentWindow, aGroup, aBrowsingContextId, aType, std::move(aInit)), 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()); MOZ_ASSERT(IsTop() && aNewContext->IsTop()); if (mStatusFilter) { mStatusFilter->RemoveProgressListener(mWebProgress); mStatusFilter = nullptr; } aNewContext->mWebProgress = std::move(mWebProgress); aNewContext->mFields.SetWithoutSyncing(GetBrowserId()); aNewContext->mFields.SetWithoutSyncing(GetHistoryID()); aNewContext->mFields.SetWithoutSyncing( GetExplicitActive()); if (mSessionHistory) { mSessionHistory->SetBrowsingContext(aNewContext); mSessionHistory.swap(aNewContext->mSessionHistory); RefPtr childSHistory = ForgetChildSHistory(); aNewContext->SetChildSHistory(childSHistory); } if (mozilla::SessionHistoryInParent()) { BackgroundSessionStorageManager::PropagateManager(Id(), aNewContext->Id()); } MOZ_ASSERT(aNewContext->mLoadingEntries.IsEmpty()); mLoadingEntries.SwapElements(aNewContext->mLoadingEntries); MOZ_ASSERT(!aNewContext->mActiveEntry); mActiveEntry.swap(aNewContext->mActiveEntry); } void CanonicalBrowsingContext::UpdateSecurityState() { if (mSecureBrowserUI) { mSecureBrowserUI->RecomputeSecurityFlags(); } } 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; } already_AddRefed CanonicalBrowsingContext::TopCrossChromeBoundary() { RefPtr bc(this); while (RefPtr parent = bc->GetParentCrossChromeBoundary()) { bc = parent.forget(); } return bc.forget(); } Nullable CanonicalBrowsingContext::GetTopChromeWindow() { RefPtr bc = TopCrossChromeBoundary(); 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; } SessionHistoryEntry* CanonicalBrowsingContext::GetActiveSessionHistoryEntry() { return mActiveEntry; } bool CanonicalBrowsingContext::HasHistoryEntry(nsISHEntry* aEntry) { // XXX Should we check also loading entries? return aEntry && mActiveEntry == aEntry; } void CanonicalBrowsingContext::SwapHistoryEntries(nsISHEntry* aOldEntry, nsISHEntry* aNewEntry) { // XXX Should we check also loading entries? if (mActiveEntry == aOldEntry) { nsCOMPtr newEntry = do_QueryInterface(aNewEntry); mActiveEntry = newEntry.forget(); } } void CanonicalBrowsingContext::AddLoadingSessionHistoryEntry( uint64_t aLoadId, SessionHistoryEntry* aEntry) { Unused << SetHistoryID(aEntry->DocshellID()); mLoadingEntries.AppendElement(LoadingSessionHistoryEntry{aLoadId, aEntry}); } void CanonicalBrowsingContext::GetLoadingSessionHistoryInfoFromParent( Maybe& aLoadingInfo, int32_t* aRequestedIndex, int32_t* aLength) { *aRequestedIndex = -1; *aLength = 0; nsISHistory* shistory = GetSessionHistory(); if (!shistory || !GetParent()) { return; } SessionHistoryEntry* parentSHE = GetParent()->Canonical()->GetActiveSessionHistoryEntry(); if (parentSHE) { int32_t index = -1; for (BrowsingContext* sibling : GetParent()->Children()) { ++index; if (sibling == this) { nsCOMPtr shEntry; parentSHE->GetChildSHEntryIfHasNoDynamicallyAddedChild( index, getter_AddRefs(shEntry)); nsCOMPtr she = do_QueryInterface(shEntry); if (she) { aLoadingInfo.emplace(she); mLoadingEntries.AppendElement(LoadingSessionHistoryEntry{ aLoadingInfo.value().mLoadId, she.get()}); *aRequestedIndex = shistory->GetRequestedIndex(); *aLength = shistory->GetCount(); Unused << SetHistoryID(she->DocshellID()); } break; } } } } UniquePtr CanonicalBrowsingContext::CreateLoadingSessionHistoryEntryForLoad( nsDocShellLoadState* aLoadState, nsIChannel* aChannel) { RefPtr entry; const LoadingSessionHistoryInfo* existingLoadingInfo = aLoadState->GetLoadingSessionHistoryInfo(); if (existingLoadingInfo) { entry = SessionHistoryEntry::GetByLoadId(existingLoadingInfo->mLoadId); MOZ_LOG(gSHLog, LogLevel::Verbose, ("SHEntry::GetByLoadId(%" PRIu64 ") -> %p", existingLoadingInfo->mLoadId, entry.get())); if (!entry) { return nullptr; } Unused << SetHistoryEntryCount(entry->BCHistoryLength()); } else { entry = new SessionHistoryEntry(aLoadState, aChannel); if (IsTop()) { // Only top level pages care about Get/SetPersist. entry->SetPersist( nsDocShell::ShouldAddToSessionHistory(aLoadState->URI(), aChannel)); } else if (mActiveEntry || !mLoadingEntries.IsEmpty()) { entry->SetIsSubFrame(true); } entry->SetDocshellID(GetHistoryID()); entry->SetIsDynamicallyAdded(CreatedDynamically()); entry->SetForInitialLoad(true); } MOZ_DIAGNOSTIC_ASSERT(entry); UniquePtr loadingInfo; if (existingLoadingInfo) { loadingInfo = MakeUnique(*existingLoadingInfo); } else { loadingInfo = MakeUnique(entry); mLoadingEntries.AppendElement( LoadingSessionHistoryEntry{loadingInfo->mLoadId, entry}); } MOZ_ASSERT(SessionHistoryEntry::GetByLoadId(loadingInfo->mLoadId) == entry); return loadingInfo; } UniquePtr CanonicalBrowsingContext::ReplaceLoadingSessionHistoryEntryForLoad( LoadingSessionHistoryInfo* aInfo, nsIChannel* aChannel) { MOZ_ASSERT(aInfo); MOZ_ASSERT(aChannel); UniquePtr newInfo = MakeUnique( aChannel, aInfo->mInfo.LoadType(), aInfo->mInfo.GetPartitionedPrincipalToInherit(), aInfo->mInfo.GetCsp()); RefPtr newEntry = new SessionHistoryEntry(newInfo.get()); if (IsTop()) { // Only top level pages care about Get/SetPersist. nsCOMPtr uri; aChannel->GetURI(getter_AddRefs(uri)); newEntry->SetPersist(nsDocShell::ShouldAddToSessionHistory(uri, aChannel)); } else { newEntry->SetIsSubFrame(aInfo->mInfo.IsSubFrame()); } newEntry->SetDocshellID(GetHistoryID()); newEntry->SetIsDynamicallyAdded(CreatedDynamically()); // Replacing the old entry. SessionHistoryEntry::SetByLoadId(aInfo->mLoadId, newEntry); bool forInitialLoad = true; for (size_t i = 0; i < mLoadingEntries.Length(); ++i) { if (mLoadingEntries[i].mLoadId == aInfo->mLoadId) { forInitialLoad = mLoadingEntries[i].mEntry->ForInitialLoad(); mLoadingEntries[i].mEntry = newEntry; break; } } newEntry->SetForInitialLoad(forInitialLoad); return MakeUnique(newEntry, aInfo->mLoadId); } void CanonicalBrowsingContext::CallOnAllTopDescendants( const std::function& aCallback) { #ifdef DEBUG RefPtr parent = GetParentCrossChromeBoundary(); MOZ_ASSERT(!parent, "Should only call on top chrome BC"); #endif nsTArray> groups; BrowsingContextGroup::GetAllGroups(groups); for (auto& browsingContextGroup : groups) { for (auto& bc : browsingContextGroup->Toplevels()) { if (bc == this) { // Cannot be a descendent of myself so skip. continue; } RefPtr top = bc->Canonical()->TopCrossChromeBoundary(); if (top == this) { if (aCallback(bc->Canonical()) == CallState::Stop) { return; } } } } } void CanonicalBrowsingContext::SessionHistoryCommit(uint64_t aLoadId, const nsID& aChangeID, uint32_t aLoadType, bool aPersist, bool aCloneEntryChildren) { MOZ_LOG(gSHLog, LogLevel::Verbose, ("CanonicalBrowsingContext::SessionHistoryCommit %p %" PRIu64, this, aLoadId)); for (size_t i = 0; i < mLoadingEntries.Length(); ++i) { if (mLoadingEntries[i].mLoadId == aLoadId) { nsSHistory* shistory = static_cast(GetSessionHistory()); if (!shistory) { SessionHistoryEntry::RemoveLoadId(aLoadId); mLoadingEntries.RemoveElementAt(i); return; } CallerWillNotifyHistoryIndexAndLengthChanges caller(shistory); RefPtr newActiveEntry = mLoadingEntries[i].mEntry; bool loadFromSessionHistory = !newActiveEntry->ForInitialLoad(); newActiveEntry->SetForInitialLoad(false); SessionHistoryEntry::RemoveLoadId(aLoadId); mLoadingEntries.RemoveElementAt(i); // If there is a name in the new entry, clear the name of all contiguous // entries. This is for https://html.spec.whatwg.org/#history-traversal // Step 4.4.2. nsAutoString nameOfNewEntry; newActiveEntry->GetName(nameOfNewEntry); if (!nameOfNewEntry.IsEmpty()) { nsSHistory::WalkContiguousEntries( newActiveEntry, [](nsISHEntry* aEntry) { aEntry->SetName(EmptyString()); }); } bool addEntry = ShouldUpdateSessionHistory(aLoadType); if (IsTop()) { mActiveEntry = newActiveEntry; if (LOAD_TYPE_HAS_FLAGS(aLoadType, nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY)) { // Replace the current entry with the new entry. int32_t index = shistory->GetIndexForReplace(); // If we're trying to replace an inexistant shistory entry then we // should append instead. addEntry = index < 0; if (!addEntry) { shistory->ReplaceEntry(index, mActiveEntry); } } if (loadFromSessionHistory) { // XXX Synchronize browsing context tree and session history tree? shistory->UpdateIndex(); } else if (addEntry) { shistory->AddEntry(mActiveEntry, aPersist); } } else { // 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 (loadFromSessionHistory) { if (mActiveEntry) { // mActiveEntry is null if we're loading iframes from session // history while also parent page is loading from session history. // In that case there isn't anything to sync. mActiveEntry->SyncTreesForSubframeNavigation(newActiveEntry, Top(), this); } mActiveEntry = newActiveEntry; // FIXME UpdateIndex() here may update index too early (but even the // old implementation seems to have similar issues). shistory->UpdateIndex(); } else if (addEntry) { if (mActiveEntry) { if (LOAD_TYPE_HAS_FLAGS( aLoadType, nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY)) { // FIXME We need to make sure that when we create the info we // make a copy of the shared state. mActiveEntry->ReplaceWith(*newActiveEntry); } else { // AddChildSHEntryHelper does update the index of the session // history! shistory->AddChildSHEntryHelper(mActiveEntry, newActiveEntry, Top(), aCloneEntryChildren); mActiveEntry = newActiveEntry; } } else { SessionHistoryEntry* parentEntry = GetParent()->mActiveEntry; // XXX What should happen if parent doesn't have mActiveEntry? // Or can that even happen ever? if (parentEntry) { mActiveEntry = newActiveEntry; // FIXME Using IsInProcess for aUseRemoteSubframes isn't quite // right, but aUseRemoteSubframes should be going away. parentEntry->AddChild( mActiveEntry, CreatedDynamically() ? -1 : GetParent()->IndexOf(this), IsInProcess()); } } } } ResetSHEntryHasUserInteractionCache(); HistoryCommitIndexAndLength(aChangeID, caller); shistory->LogHistory(); return; } // XXX Should the loading entries before [i] be removed? } // FIXME Should we throw an error if we don't find an entry for // aSessionHistoryEntryId? } static already_AddRefed CreateLoadInfo( SessionHistoryEntry* aEntry) { const SessionHistoryInfo& info = aEntry->Info(); RefPtr loadState(new nsDocShellLoadState(info.GetURI())); info.FillLoadInfo(*loadState); UniquePtr loadingInfo; loadingInfo = MakeUnique(aEntry); loadState->SetLoadingSessionHistoryInfo(std::move(loadingInfo)); return loadState.forget(); } void CanonicalBrowsingContext::NotifyOnHistoryReload( bool aForceReload, bool& aCanReload, Maybe>& aLoadState, Maybe& aReloadActiveEntry) { MOZ_DIAGNOSTIC_ASSERT(!aLoadState); aCanReload = true; nsISHistory* shistory = GetSessionHistory(); NS_ENSURE_TRUE_VOID(shistory); shistory->NotifyOnHistoryReload(&aCanReload); if (!aCanReload) { return; } if (mActiveEntry) { aLoadState.emplace(CreateLoadInfo(mActiveEntry)); aReloadActiveEntry.emplace(true); if (aForceReload) { shistory->RemoveFrameEntries(mActiveEntry); } } else if (!mLoadingEntries.IsEmpty()) { const LoadingSessionHistoryEntry& loadingEntry = mLoadingEntries.LastElement(); aLoadState.emplace(CreateLoadInfo(loadingEntry.mEntry)); aReloadActiveEntry.emplace(false); if (aForceReload) { SessionHistoryEntry* entry = SessionHistoryEntry::GetByLoadId(loadingEntry.mLoadId); if (entry) { shistory->RemoveFrameEntries(entry); } } } if (aLoadState) { int32_t index = 0; int32_t requestedIndex = -1; int32_t length = 0; shistory->GetIndex(&index); shistory->GetRequestedIndex(&requestedIndex); shistory->GetCount(&length); aLoadState.ref()->SetLoadIsFromSessionHistory( requestedIndex >= 0 ? requestedIndex : index, length, aReloadActiveEntry.value()); } // 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. } void CanonicalBrowsingContext::SetActiveSessionHistoryEntry( const Maybe& aPreviousScrollPos, SessionHistoryInfo* aInfo, uint32_t aLoadType, uint32_t aUpdatedCacheKey, const nsID& aChangeID) { nsISHistory* shistory = GetSessionHistory(); if (!shistory) { return; } CallerWillNotifyHistoryIndexAndLengthChanges caller(shistory); RefPtr oldActiveEntry = mActiveEntry; if (aPreviousScrollPos.isSome() && oldActiveEntry) { oldActiveEntry->SetScrollPosition(aPreviousScrollPos.ref().x, aPreviousScrollPos.ref().y); } mActiveEntry = new SessionHistoryEntry(aInfo); mActiveEntry->SetDocshellID(GetHistoryID()); mActiveEntry->AdoptBFCacheEntry(oldActiveEntry); if (aUpdatedCacheKey != 0) { mActiveEntry->SharedInfo()->mCacheKey = aUpdatedCacheKey; } if (IsTop()) { Maybe previousEntryIndex, loadedEntryIndex; shistory->AddToRootSessionHistory( true, oldActiveEntry, this, mActiveEntry, aLoadType, nsDocShell::ShouldAddToSessionHistory(aInfo->GetURI(), nullptr), &previousEntryIndex, &loadedEntryIndex); } else { if (oldActiveEntry) { shistory->AddChildSHEntryHelper(oldActiveEntry, mActiveEntry, Top(), true); } else if (GetParent() && GetParent()->mActiveEntry) { GetParent()->mActiveEntry->AddChild( mActiveEntry, CreatedDynamically() ? -1 : GetParent()->IndexOf(this), UseRemoteSubframes()); } } ResetSHEntryHasUserInteractionCache(); // FIXME Need to do the equivalent of EvictContentViewersOrReplaceEntry. HistoryCommitIndexAndLength(aChangeID, caller); static_cast(shistory)->LogHistory(); } void CanonicalBrowsingContext::ReplaceActiveSessionHistoryEntry( SessionHistoryInfo* aInfo) { if (!mActiveEntry) { return; } mActiveEntry->SetInfo(aInfo); // Notify children of the update nsSHistory* shistory = static_cast(GetSessionHistory()); if (shistory) { shistory->NotifyOnHistoryReplaceEntry(); shistory->UpdateRootBrowsingContextState(); } ResetSHEntryHasUserInteractionCache(); // FIXME Need to do the equivalent of EvictContentViewersOrReplaceEntry. } void CanonicalBrowsingContext::RemoveDynEntriesFromActiveSessionHistoryEntry() { nsISHistory* shistory = GetSessionHistory(); // In theory shistory can be null here if the method is called right after // CanonicalBrowsingContext::ReplacedBy call. NS_ENSURE_TRUE_VOID(shistory); nsCOMPtr root = nsSHistory::GetRootSHEntry(mActiveEntry); shistory->RemoveDynEntries(shistory->GetIndexOfEntry(root), mActiveEntry); } void CanonicalBrowsingContext::RemoveFromSessionHistory(const nsID& aChangeID) { nsSHistory* shistory = static_cast(GetSessionHistory()); if (shistory) { CallerWillNotifyHistoryIndexAndLengthChanges caller(shistory); nsCOMPtr root = nsSHistory::GetRootSHEntry(mActiveEntry); bool didRemove; AutoTArray ids({GetHistoryID()}); shistory->RemoveEntries(ids, shistory->GetIndexOfEntry(root), &didRemove); if (didRemove) { BrowsingContext* rootBC = shistory->GetBrowsingContext(); if (rootBC) { if (!rootBC->IsInProcess()) { Unused << rootBC->Canonical() ->GetContentParent() ->SendDispatchLocationChangeEvent(rootBC); } else if (rootBC->GetDocShell()) { rootBC->GetDocShell()->DispatchLocationChangeEvent(); } } } HistoryCommitIndexAndLength(aChangeID, caller); } } void CanonicalBrowsingContext::HistoryGo( int32_t aOffset, uint64_t aHistoryEpoch, bool aRequireUserInteraction, Maybe aContentId, std::function&& aResolver) { if (aRequireUserInteraction && aOffset != -1 && aOffset != 1) { NS_ERROR( "aRequireUserInteraction may only be used with an offset of -1 or 1"); return; } nsSHistory* shistory = static_cast(GetSessionHistory()); if (!shistory) { return; } CheckedInt index = shistory->GetRequestedIndex() >= 0 ? shistory->GetRequestedIndex() : shistory->Index(); MOZ_LOG(gSHLog, LogLevel::Debug, ("HistoryGo(%d->%d) epoch %" PRIu64 "/id %" PRIu64, aOffset, (index + aOffset).value(), aHistoryEpoch, (uint64_t)(aContentId.isSome() ? aContentId.value() : 0))); while (true) { index += aOffset; if (!index.isValid()) { MOZ_LOG(gSHLog, LogLevel::Debug, ("Invalid index")); return; } // Check for user interaction if desired, except for the first and last // history entries. We compare with >= to account for the case where // aOffset >= length. if (!aRequireUserInteraction || index.value() >= shistory->Length() - 1 || index.value() <= 0) { break; } if (shistory->HasUserInteractionAtIndex(index.value())) { break; } } // Implement aborting additional history navigations from within the same // event spin of the content process. uint64_t epoch; bool sameEpoch = false; Maybe id; shistory->GetEpoch(epoch, id); if (aContentId == id && epoch >= aHistoryEpoch) { sameEpoch = true; MOZ_LOG(gSHLog, LogLevel::Debug, ("Same epoch/id")); } // Don't update the epoch until we know if the target index is valid // GoToIndex checks that index is >= 0 and < length. nsTArray loadResults; nsresult rv = shistory->GotoIndex(index.value(), loadResults, sameEpoch); if (NS_FAILED(rv)) { MOZ_LOG(gSHLog, LogLevel::Debug, ("Dropping HistoryGo - bad index or same epoch (not in same doc)")); return; } if (epoch < aHistoryEpoch || aContentId != id) { MOZ_LOG(gSHLog, LogLevel::Debug, ("Set epoch")); shistory->SetEpoch(aHistoryEpoch, aContentId); } aResolver(shistory->GetRequestedIndex()); nsSHistory::LoadURIs(loadResults); } 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; } if (IsTop()) { BackgroundSessionStorageManager::RemoveManager(Id()); } } void CanonicalBrowsingContext::NotifyStartDelayedAutoplayMedia() { WindowContext* windowContext = GetCurrentWindowContext(); if (!windowContext) { 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 window // context so that the block-autoplay logic allows the media to autoplay. windowContext->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, ErrorResult& aRv) { MOZ_ASSERT(!GetParent(), "Notify media mute change on non top-level context!"); SetMuted(aMuted, aRv); } 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) { if (IsDiscarded()) { return; } 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; } // While process switching, we need to check if any of our ancestors are // discarded or no longer current, in which case the process switch needs to // be aborted. if (!target->AncestorsAreCurrent()) { NS_WARNING("Ancestor context is no longer current"); 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); // The process has been created, hand off to nsFrameLoaderOwner to finish // the process switch. ErrorResult error; frameLoaderOwner->ChangeRemotenessToProcess( mContentParent, mReplaceBrowsingContext, mSpecificGroup, 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. if (mPendingSwitchId) { 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; } // If we were given a specific group, stop keeping that group alive manually. if (mSpecificGroup) { mSpecificGroup->RemoveKeepAlive(); mSpecificGroup = nullptr; } mPromise = nullptr; mTarget = nullptr; mPrepareToChangePromise = nullptr; } CanonicalBrowsingContext::PendingRemotenessChange::PendingRemotenessChange( CanonicalBrowsingContext* aTarget, RemotenessPromise::Private* aPromise, uint64_t aPendingSwitchId, bool aReplaceBrowsingContext) : mTarget(aTarget), mPromise(aPromise), mPendingSwitchId(aPendingSwitchId), mReplaceBrowsingContext(aReplaceBrowsingContext) {} CanonicalBrowsingContext::PendingRemotenessChange::~PendingRemotenessChange() { MOZ_ASSERT(!mPromise && !mTarget && !mContentParent && !mSpecificGroup && !mPrepareToChangePromise, "should've already been Cancel() or Complete()-ed"); } BrowserParent* CanonicalBrowsingContext::GetBrowserParent() const { if (auto* wg = GetCurrentWindowGlobal()) { return wg->GetBrowserParent(); } return nullptr; } 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"); MOZ_DIAGNOSTIC_ASSERT(aPendingSwitchId || !IsTop(), "Should always have aPendingSwitchId for top-level " "frames"); if (!AncestorsAreCurrent()) { NS_WARNING("An ancestor context is no longer current"); return RemotenessPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); } // 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 == embedderBrowser->Manager()->GetRemoteType()) { MOZ_DIAGNOSTIC_ASSERT( aPendingSwitchId, "We always have a PendingSwitchId, except for print-preview loads, " "which will never perform a process-switch to being in-process with " "their embedder"); 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); mPendingRemotenessChange = change; // If a specific BrowsingContextGroup ID was specified for this load, make // sure to keep it alive until the process switch is completed. if (aSpecificGroupId) { change->mSpecificGroup = BrowsingContextGroup::GetOrCreate(aSpecificGroupId); change->mSpecificGroup->AddKeepAlive(); } // 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 { // Try to predict which BrowsingContextGroup will be used for the final load // in this BrowsingContext. This has to be accurate if switching into an // existing group, as it will control what pool of processes will be used // for process selection. // // It's _technically_ OK to provide a group here if we're actually going to // switch into a brand new group, though it's sub-optimal, as it can // restrict the set of processes we're using. BrowsingContextGroup* finalGroup = aReplaceBrowsingContext ? change->mSpecificGroup.get() : Group(); change->mContentParent = ContentParent::GetNewOrUsedLaunchingBrowserProcess( /* aRemoteType = */ aRemoteType, /* aGroup = */ finalGroup, /* 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::HasCreatedMediaController() const { 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_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, 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_parent_controlled()) { return false; } uint64_t outerWindowId = 0; if (!SupportsLoadingInParent(aLoadState, &outerWindowId)) { return false; } // Session-history-in-parent implementation relies currently on getting a // round trip through a child process. if (aLoadState->LoadIsFromSessionHistory()) { 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); } 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; if (NS_FAILED(SetCurrentLoadIdentifier(Some(aLoad->GetLoadIdentifier())))) { mCurrentLoad = nullptr; return false; } return true; } void CanonicalBrowsingContext::EndDocumentLoad(bool aForProcessSwitch) { mCurrentLoad = nullptr; if (!aForProcessSwitch) { // Resetting the current load identifier on a discarded context // has no effect when a document load has finished. Unused << SetCurrentLoadIdentifier(Nothing()); } } void CanonicalBrowsingContext::ResetSHEntryHasUserInteractionCache() { WindowContext* topWc = GetTopWindowContext(); if (topWc && !topWc->IsDiscarded()) { MOZ_ALWAYS_SUCCEEDS(topWc->SetSHEntryHasUserInteraction(false)); } } void CanonicalBrowsingContext::HistoryCommitIndexAndLength() { nsID changeID = {}; CallerWillNotifyHistoryIndexAndLengthChanges caller(nullptr); HistoryCommitIndexAndLength(changeID, caller); } void CanonicalBrowsingContext::HistoryCommitIndexAndLength( const nsID& aChangeID, const CallerWillNotifyHistoryIndexAndLengthChanges& aProofOfCaller) { if (!IsTop()) { Cast(Top())->HistoryCommitIndexAndLength(aChangeID, aProofOfCaller); return; } nsISHistory* shistory = GetSessionHistory(); if (!shistory) { return; } int32_t index = 0; shistory->GetIndex(&index); int32_t length = shistory->GetCount(); GetChildSessionHistory()->SetIndexAndLength(index, length, aChangeID); Group()->EachParent([&](ContentParent* aParent) { Unused << aParent->SendHistoryCommitIndexAndLength(this, index, length, aChangeID); }); } void CanonicalBrowsingContext::ResetScalingZoom() { // This currently only ever gets called in the parent process, and we // pass the message on to the WindowGlobalChild for the rootmost browsing // context. if (WindowGlobalParent* topWindow = GetTopWindowContext()) { Unused << topWindow->SendResetScalingZoom(); } } void CanonicalBrowsingContext::SetContainerFeaturePolicy( FeaturePolicy* aContainerFeaturePolicy) { mContainerFeaturePolicy = aContainerFeaturePolicy; if (WindowGlobalParent* current = GetCurrentWindowGlobal()) { Unused << current->SendSetContainerFeaturePolicy(mContainerFeaturePolicy); } } void CanonicalBrowsingContext::SetCrossGroupOpenerId(uint64_t aOpenerId) { MOZ_DIAGNOSTIC_ASSERT(IsTopContent()); MOZ_DIAGNOSTIC_ASSERT(mCrossGroupOpenerId == 0, "Can only set CrossGroupOpenerId once"); mCrossGroupOpenerId = aOpenerId; } NS_IMPL_CYCLE_COLLECTION_INHERITED(CanonicalBrowsingContext, BrowsingContext, mSessionHistory, mContainerFeaturePolicy) 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