/* -*- 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/PBrowserParent.h" #include "mozilla/dom/PBackgroundSessionStorageCache.h" #include "mozilla/dom/PWindowGlobalParent.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/SessionStorageManager.h" #include "mozilla/ipc/ProtocolUtils.h" #include "mozilla/net/DocumentLoadListener.h" #include "mozilla/NullPrincipal.h" #include "mozilla/StaticPrefs_docshell.h" #include "mozilla/StaticPrefs_fission.h" #include "mozilla/Telemetry.h" #include "nsISupports.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" #include "nsTHashSet.h" #include "SessionStoreFunctions.h" #include "nsIXPConnect.h" #include "nsImportModule.h" #include "UnitTransforms.h" #ifdef NS_PRINTING # include "mozilla/embedding/printingui/PrintingParent.h" # include "nsIWebBrowserPrint.h" #endif using namespace mozilla::ipc; extern mozilla::LazyLogModule gAutoplayPermissionLog; extern mozilla::LazyLogModule gSHLog; extern mozilla::LazyLogModule gSHIPBFCacheLog; #define AUTOPLAY_LOG(msg, ...) \ MOZ_LOG(gAutoplayPermissionLog, LogLevel::Debug, (msg, ##__VA_ARGS__)) static mozilla::LazyLogModule sPBContext("PBContext"); // Global count of canonical browsing contexts with the private attribute set static uint32_t gNumberOfPrivateContexts = 0; static void IncreasePrivateCount() { gNumberOfPrivateContexts++; MOZ_LOG(sPBContext, mozilla::LogLevel::Debug, ("%s: Private browsing context count %d -> %d", __func__, gNumberOfPrivateContexts - 1, gNumberOfPrivateContexts)); if (gNumberOfPrivateContexts > 1) { return; } static bool sHasSeenPrivateContext = false; if (!sHasSeenPrivateContext) { sHasSeenPrivateContext = true; mozilla::Telemetry::ScalarSet( mozilla::Telemetry::ScalarID::DOM_PARENTPROCESS_PRIVATE_WINDOW_USED, true); } } static void DecreasePrivateCount() { MOZ_ASSERT(gNumberOfPrivateContexts > 0); gNumberOfPrivateContexts--; MOZ_LOG(sPBContext, mozilla::LogLevel::Debug, ("%s: Private browsing context count %d -> %d", __func__, gNumberOfPrivateContexts + 1, gNumberOfPrivateContexts)); if (!gNumberOfPrivateContexts && !mozilla::Preferences::GetBool("browser.privatebrowsing.autostart")) { nsCOMPtr observerService = mozilla::services::GetObserverService(); if (observerService) { MOZ_LOG(sPBContext, mozilla::LogLevel::Debug, ("%s: last-pb-context-exited fired", __func__)); observerService->NotifyObservers(nullptr, "last-pb-context-exited", nullptr); } } } 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), mPermanentKey(JS::NullValue()) { // You are only ever allowed to create CanonicalBrowsingContexts in the // parent process. MOZ_RELEASE_ASSERT(XRE_IsParentProcess()); // The initial URI in a BrowsingContext is always "about:blank". MOZ_ALWAYS_SUCCEEDS( NS_NewURI(getter_AddRefs(mCurrentRemoteURI), "about:blank")); mozilla::HoldJSObjects(this); } CanonicalBrowsingContext::~CanonicalBrowsingContext() { mPermanentKey.setNull(); mozilla::DropJSObjects(this); if (mSessionHistory) { mSessionHistory->SetBrowsingContext(nullptr); } } /* 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; } namespace { // The DocShellProgressBridge is attached to a root content docshell loaded in // the parent process. Notifications are paired up with the docshell which they // came from, so that they can be fired to the correct // BrowsingContextWebProgress and bubble through this tree separately. // // Notifications are filtered by a nsBrowserStatusFilter before being received // by the DocShellProgressBridge. class DocShellProgressBridge : public nsIWebProgressListener { public: NS_DECL_ISUPPORTS // NOTE: This relies in the expansion of `NS_FORWARD_SAFE` and all listener // methods accepting an `aWebProgress` argument. If this changes in the // future, this may need to be written manually. NS_FORWARD_SAFE_NSIWEBPROGRESSLISTENER(GetTargetContext(aWebProgress)) explicit DocShellProgressBridge(uint64_t aTopContextId) : mTopContextId(aTopContextId) {} private: virtual ~DocShellProgressBridge() = default; nsIWebProgressListener* GetTargetContext(nsIWebProgress* aWebProgress) { RefPtr context; if (nsCOMPtr docShell = do_QueryInterface(aWebProgress)) { context = docShell->GetBrowsingContext()->Canonical(); } else { context = CanonicalBrowsingContext::Get(mTopContextId); } return context && !context->IsDiscarded() ? context->GetWebProgress() : nullptr; } uint64_t mTopContextId = 0; }; NS_IMPL_ISUPPORTS(DocShellProgressBridge, nsIWebProgressListener) } // namespace void CanonicalBrowsingContext::MaybeAddAsProgressListener( nsIWebProgress* aWebProgress) { // Only add as a listener if the created docshell is a toplevel content // docshell. We'll get notifications for all of our subframes through a single // listener. if (!IsTopContent()) { return; } if (!mDocShellProgressBridge) { mDocShellProgressBridge = new DocShellProgressBridge(Id()); mStatusFilter = new nsBrowserStatusFilter(); mStatusFilter->AddProgressListener(mDocShellProgressBridge, nsIWebProgress::NOTIFY_ALL); } aWebProgress->AddProgressListener(mStatusFilter, nsIWebProgress::NOTIFY_ALL); } void CanonicalBrowsingContext::ReplacedBy( CanonicalBrowsingContext* aNewContext, const NavigationIsolationOptions& aRemotenessOptions) { MOZ_ASSERT(!aNewContext->mWebProgress); MOZ_ASSERT(!aNewContext->mSessionHistory); MOZ_ASSERT(IsTop() && aNewContext->IsTop()); mIsReplaced = true; aNewContext->mIsReplaced = false; if (mStatusFilter) { mStatusFilter->RemoveProgressListener(mDocShellProgressBridge); mStatusFilter = nullptr; } mWebProgress->ContextReplaced(aNewContext); aNewContext->mWebProgress = std::move(mWebProgress); // Use the Transaction for the fields which need to be updated whether or not // the new context has been attached before. // SetWithoutSyncing can be used if context hasn't been attached. Transaction txn; txn.SetBrowserId(GetBrowserId()); txn.SetHistoryID(GetHistoryID()); txn.SetExplicitActive(GetExplicitActive()); txn.SetHasRestoreData(GetHasRestoreData()); txn.SetShouldDelayMediaFromStart(GetShouldDelayMediaFromStart()); if (aNewContext->EverAttached()) { MOZ_ALWAYS_SUCCEEDS(txn.Commit(aNewContext)); } else { txn.CommitWithoutSyncing(aNewContext); } aNewContext->mRestoreState = mRestoreState.forget(); MOZ_ALWAYS_SUCCEEDS(SetHasRestoreData(false)); // XXXBFCache name handling is still a bit broken in Fission in general, // at least in case name should be cleared. if (aRemotenessOptions.mTryUseBFCache) { MOZ_ASSERT(!aNewContext->EverAttached()); aNewContext->mFields.SetWithoutSyncing(GetName()); // We don't copy over HasLoadedNonInitialDocument here, we'll actually end // up loading a new initial document at this point, before the real load. // The real load will then end up setting HasLoadedNonInitialDocument to // true. } if (mSessionHistory) { mSessionHistory->SetBrowsingContext(aNewContext); // At this point we will be creating a new ChildSHistory in the child. // That means that the child's epoch will be reset, so it makes sense to // reset the epoch in the parent too. mSessionHistory->SetEpoch(0, Nothing()); mSessionHistory.swap(aNewContext->mSessionHistory); RefPtr childSHistory = ForgetChildSHistory(); aNewContext->SetChildSHistory(childSHistory); } if (mozilla::SessionHistoryInParent()) { BackgroundSessionStorageManager::PropagateManager(Id(), aNewContext->Id()); } // Transfer the ownership of the priority active status from the old context // to the new context. aNewContext->mPriorityActive = mPriorityActive; mPriorityActive = false; MOZ_ASSERT(aNewContext->mLoadingEntries.IsEmpty()); mLoadingEntries.SwapElements(aNewContext->mLoadingEntries); MOZ_ASSERT(!aNewContext->mActiveEntry); mActiveEntry.swap(aNewContext->mActiveEntry); aNewContext->mPermanentKey = mPermanentKey; mPermanentKey.setNull(); } void CanonicalBrowsingContext::UpdateSecurityState() { if (mSecureBrowserUI) { mSecureBrowserUI->RecomputeSecurityFlags(); } } 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; } void CanonicalBrowsingContext::SetActiveSessionHistoryEntry( SessionHistoryEntry* aEntry) { mActiveEntry = aEntry; } 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) { 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()}); 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* aOldChannel, nsIChannel* aNewChannel) { MOZ_ASSERT(aInfo); MOZ_ASSERT(aNewChannel); SessionHistoryInfo newInfo = SessionHistoryInfo( aOldChannel, aNewChannel, aInfo->mInfo.LoadType(), aInfo->mInfo.GetPartitionedPrincipalToInherit(), aInfo->mInfo.GetCsp()); for (size_t i = 0; i < mLoadingEntries.Length(); ++i) { if (mLoadingEntries[i].mLoadId == aInfo->mLoadId) { RefPtr loadingEntry = mLoadingEntries[i].mEntry; loadingEntry->SetInfo(&newInfo); if (IsTop()) { // Only top level pages care about Get/SetPersist. nsCOMPtr uri; aNewChannel->GetURI(getter_AddRefs(uri)); loadingEntry->SetPersist( nsDocShell::ShouldAddToSessionHistory(uri, aNewChannel)); } else { loadingEntry->SetIsSubFrame(aInfo->mInfo.IsSubFrame()); } loadingEntry->SetDocshellID(GetHistoryID()); loadingEntry->SetIsDynamicallyAdded(CreatedDynamically()); return MakeUnique(loadingEntry, aInfo); } } return nullptr; } #ifdef NS_PRINTING class PrintListenerAdapter final : public nsIWebProgressListener { public: explicit PrintListenerAdapter(Promise* aPromise) : mPromise(aPromise) {} NS_DECL_ISUPPORTS // NS_DECL_NSIWEBPROGRESSLISTENER NS_IMETHOD OnStateChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, uint32_t aStateFlags, nsresult aStatus) override { if (aStateFlags & nsIWebProgressListener::STATE_STOP && aStateFlags & nsIWebProgressListener::STATE_IS_DOCUMENT && mPromise) { mPromise->MaybeResolveWithUndefined(); mPromise = nullptr; } return NS_OK; } NS_IMETHOD OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, nsresult aStatus, const char16_t* aMessage) override { if (aStatus != NS_OK && mPromise) { mPromise->MaybeReject(ErrorResult(aStatus)); mPromise = nullptr; } return NS_OK; } NS_IMETHOD OnProgressChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, int32_t aCurSelfProgress, int32_t aMaxSelfProgress, int32_t aCurTotalProgress, int32_t aMaxTotalProgress) override { return NS_OK; } NS_IMETHOD OnLocationChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, nsIURI* aLocation, uint32_t aFlags) override { return NS_OK; } NS_IMETHOD OnSecurityChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, uint32_t aState) override { return NS_OK; } NS_IMETHOD OnContentBlockingEvent(nsIWebProgress* aWebProgress, nsIRequest* aRequest, uint32_t aEvent) override { return NS_OK; } private: ~PrintListenerAdapter() = default; RefPtr mPromise; }; NS_IMPL_ISUPPORTS(PrintListenerAdapter, nsIWebProgressListener) #endif already_AddRefed CanonicalBrowsingContext::Print( nsIPrintSettings* aPrintSettings, ErrorResult& aRv) { RefPtr promise = Promise::Create(GetIncumbentGlobal(), aRv); if (NS_WARN_IF(aRv.Failed())) { return promise.forget(); } #ifndef NS_PRINTING promise->MaybeReject(ErrorResult(NS_ERROR_NOT_AVAILABLE)); return promise.forget(); #else auto listener = MakeRefPtr(promise); if (IsInProcess()) { RefPtr outerWindow = nsGlobalWindowOuter::Cast(GetDOMWindow()); if (NS_WARN_IF(!outerWindow)) { promise->MaybeReject(ErrorResult(NS_ERROR_FAILURE)); return promise.forget(); } ErrorResult rv; outerWindow->Print(aPrintSettings, listener, /* aDocShellToCloneInto = */ nullptr, nsGlobalWindowOuter::IsPreview::No, nsGlobalWindowOuter::IsForWindowDotPrint::No, /* aPrintPreviewCallback = */ nullptr, rv); if (rv.Failed()) { promise->MaybeReject(std::move(rv)); } return promise.forget(); } auto* browserParent = GetBrowserParent(); if (NS_WARN_IF(!browserParent)) { promise->MaybeReject(ErrorResult(NS_ERROR_FAILURE)); return promise.forget(); } RefPtr printingParent = browserParent->Manager()->GetPrintingParent(); embedding::PrintData printData; nsresult rv = printingParent->SerializeAndEnsureRemotePrintJob( aPrintSettings, listener, nullptr, &printData); if (NS_WARN_IF(NS_FAILED(rv))) { promise->MaybeReject(ErrorResult(rv)); return promise.forget(); } if (NS_WARN_IF(!browserParent->SendPrint(this, printData))) { promise->MaybeReject(ErrorResult(NS_ERROR_FAILURE)); } return promise.forget(); #endif } 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, bool aChannelExpired) { 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; } RefPtr newActiveEntry = mLoadingEntries[i].mEntry; if (aChannelExpired) { newActiveEntry->SharedInfo()->mExpired = true; } bool loadFromSessionHistory = !newActiveEntry->ForInitialLoad(); newActiveEntry->SetForInitialLoad(false); SessionHistoryEntry::RemoveLoadId(aLoadId); mLoadingEntries.RemoveElementAt(i); int32_t indexOfHistoryLoad = -1; if (loadFromSessionHistory) { nsCOMPtr root = nsSHistory::GetRootSHEntry(newActiveEntry); indexOfHistoryLoad = shistory->GetIndexOfEntry(root); if (indexOfHistoryLoad < 0) { // Entry has been removed from the session history. return; } } CallerWillNotifyHistoryIndexAndLengthChanges caller(shistory); // 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()) { if (mActiveEntry && !mActiveEntry->GetFrameLoader()) { bool sharesDocument = true; mActiveEntry->SharesDocumentWith(newActiveEntry, &sharesDocument); if (!sharesDocument) { // If the old page won't be in the bfcache, // clear the dynamic entries. RemoveDynEntriesFromActiveSessionHistoryEntry(); } } 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->InternalSetRequestedIndex(indexOfHistoryLoad); shistory->UpdateIndex(); } else if (addEntry) { shistory->AddEntry(mActiveEntry, aPersist); shistory->InternalSetRequestedIndex(-1); } } 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; shistory->InternalSetRequestedIndex(indexOfHistoryLoad); // 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()); } } shistory->InternalSetRequestedIndex(-1); } } 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) { // Use 0 as the offset, since aLoadState will be be used for reload. aLoadState.ref()->SetLoadIsFromSessionHistory(0, 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(); shistory->InternalSetRequestedIndex(-1); // 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, bool aUserActivation, 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, aOffset == 0, aUserActivation); 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 (mWebProgress) { RefPtr progress = mWebProgress; progress->ContextDiscarded(); } if (IsTop()) { BackgroundSessionStorageManager::RemoveManager(Id()); } CancelSessionStoreUpdate(); if (UsePrivateBrowsing() && EverAttached() && IsContent()) { DecreasePrivateCount(); } } void CanonicalBrowsingContext::CanonicalAttach() { if (UsePrivateBrowsing() && IsContent()) { IncreasePrivateCount(); } } void CanonicalBrowsingContext::AddPendingDiscard() { MOZ_ASSERT(!mFullyDiscarded); mPendingDiscards++; } void CanonicalBrowsingContext::RemovePendingDiscard() { mPendingDiscards--; if (!mPendingDiscards) { mFullyDiscarded = true; auto listeners = std::move(mFullyDiscardedListeners); for (const auto& listener : listeners) { listener(Id()); } } } void CanonicalBrowsingContext::AddFinalDiscardListener( std::function&& aListener) { if (mFullyDiscarded) { aListener(Id()); return; } mFullyDiscardedListeners.AppendElement(std::move(aListener)); } void CanonicalBrowsingContext::AdjustPrivateBrowsingCount( bool aPrivateBrowsing) { if (IsDiscarded() || !EverAttached() || IsChrome()) { return; } MOZ_DIAGNOSTIC_ASSERT(aPrivateBrowsing == UsePrivateBrowsing()); if (aPrivateBrowsing) { IncreasePrivateCount(); } else { DecreasePrivateCount(); } } 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) { nsTHashSet 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.Insert(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, bool aUserActivation) { 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, aUserActivation); } else if (ContentParent* cp = GetContentParent()) { Maybe cancelContentJSEpoch; if (aCancelContentJSEpoch.WasPassed()) { cancelContentJSEpoch = Some(aCancelContentJSEpoch.Value()); } Unused << cp->SendGoBack(this, cancelContentJSEpoch, aRequireUserInteraction, aUserActivation); } } void CanonicalBrowsingContext::GoForward( const Optional& aCancelContentJSEpoch, bool aRequireUserInteraction, bool aUserActivation) { 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, aUserActivation); } else if (ContentParent* cp = GetContentParent()) { Maybe cancelContentJSEpoch; if (aCancelContentJSEpoch.WasPassed()) { cancelContentJSEpoch.emplace(aCancelContentJSEpoch.Value()); } Unused << cp->SendGoForward(this, cancelContentJSEpoch, aRequireUserInteraction, aUserActivation); } } void CanonicalBrowsingContext::GoToIndex( int32_t aIndex, const Optional& aCancelContentJSEpoch, bool aUserActivation) { 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, aUserActivation); } else if (ContentParent* cp = GetContentParent()) { Maybe cancelContentJSEpoch; if (aCancelContentJSEpoch.WasPassed()) { cancelContentJSEpoch.emplace(aCancelContentJSEpoch.Value()); } Unused << cp->SendGoToIndex(this, aIndex, cancelContentJSEpoch, aUserActivation); } } 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::ProcessLaunched() { if (!mPromise) { return; } if (mContentParent) { // If our new content process is still unloading from a previous process // switch, wait for that unload to complete before continuing. auto found = mTarget->FindUnloadingHost(mContentParent->ChildID()); if (found != mTarget->mUnloadingHosts.end()) { found->mCallbacks.AppendElement( [self = RefPtr{this}]() { self->ProcessReady(); }); return; } } ProcessReady(); } void CanonicalBrowsingContext::PendingRemotenessChange::ProcessReady() { if (!mPromise) { return; } MOZ_ASSERT(!mProcessReady); mProcessReady = true; MaybeFinish(); } void CanonicalBrowsingContext::PendingRemotenessChange::MaybeFinish() { if (!mPromise) { return; } if (!mProcessReady || mWaitingForPrepareToChange) { return; } // If this BrowsingContext is embedded within the parent process, perform the // process switch directly. nsresult rv = mTarget->IsTopContent() ? FinishTopContent() : FinishSubframe(); if (NS_FAILED(rv)) { NS_WARNING("Error finishing PendingRemotenessChange!"); Cancel(rv); } else { Clear(); } } // Logic for finishing a toplevel process change embedded within the parent // process. Due to frontend integration the logic differs substantially from // subframe process switches, and is handled separately. nsresult CanonicalBrowsingContext::PendingRemotenessChange::FinishTopContent() { MOZ_DIAGNOSTIC_ASSERT(mTarget->IsTop(), "We shouldn't be trying to change the remoteness of " "non-remote iframes"); // Abort if our ContentParent died while process switching. if (mContentParent && NS_WARN_IF(mContentParent->IsDead())) { return NS_ERROR_FAILURE; } // 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. RefPtr target(mTarget); if (target->IsDiscarded() || !target->AncestorsAreCurrent()) { return NS_ERROR_FAILURE; } Element* browserElement = target->GetEmbedderElement(); if (!browserElement) { return NS_ERROR_FAILURE; } nsCOMPtr browser = browserElement->AsBrowser(); if (!browser) { return NS_ERROR_FAILURE; } RefPtr frameLoaderOwner = do_QueryObject(browserElement); MOZ_RELEASE_ASSERT(frameLoaderOwner, "embedder browser must be nsFrameLoaderOwner"); // If we're process switching a browsing context in private browsing // mode we might decrease the private browsing count to '0', which // would make us fire "last-pb-context-exited" and drop the private // session. To prevent that we artificially increment the number of // private browsing contexts with '1' until the process switch is done. bool usePrivateBrowsing = mTarget->UsePrivateBrowsing(); if (usePrivateBrowsing) { IncreasePrivateCount(); } auto restorePrivateCount = MakeScopeExit([usePrivateBrowsing]() { if (usePrivateBrowsing) { DecreasePrivateCount(); } }); // Tell frontend code that this browser element is about to change process. nsresult rv = browser->BeforeChangeRemoteness(); if (NS_FAILED(rv)) { return rv; } // 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, mOptions, mSpecificGroup, error); if (error.Failed()) { return error.StealNSResult(); } // Tell frontend the load is done. bool loadResumed = false; rv = browser->FinishChangeRemoteness(mPendingSwitchId, &loadResumed); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // 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. return NS_ERROR_UNEXPECTED; } if (!loadResumed) { RefPtr newDocShell = frameLoader->GetDocShell(error); if (error.Failed()) { return error.StealNSResult(); } rv = newDocShell->ResumeRedirectedLoad(mPendingSwitchId, /* aHistoryIndex */ -1); if (NS_FAILED(rv)) { return rv; } } } else if (!loadResumed) { newBrowser->ResumeLoad(mPendingSwitchId); } mPromise->Resolve(newBrowser, __func__); return NS_OK; } nsresult CanonicalBrowsingContext::PendingRemotenessChange::FinishSubframe() { MOZ_DIAGNOSTIC_ASSERT(!mOptions.mReplaceBrowsingContext, "Cannot replace BC for subframe"); MOZ_DIAGNOSTIC_ASSERT(!mTarget->IsTop()); // 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. RefPtr target(mTarget); if (target->IsDiscarded() || !target->AncestorsAreCurrent()) { return NS_ERROR_FAILURE; } if (NS_WARN_IF(!mContentParent)) { return NS_ERROR_FAILURE; } RefPtr embedderWindow = target->GetParentWindowContext(); if (NS_WARN_IF(!embedderWindow) || NS_WARN_IF(!embedderWindow->CanSend())) { return NS_ERROR_FAILURE; } RefPtr embedderBrowser = embedderWindow->GetBrowserParent(); if (NS_WARN_IF(!embedderBrowser)) { return NS_ERROR_FAILURE; } // If we're creating a new remote browser, and the host process is already // dead, abort the process switch. if (mContentParent != embedderBrowser->Manager() && NS_WARN_IF(mContentParent->IsDead())) { return NS_ERROR_FAILURE; } RefPtr oldBrowser = target->GetBrowserParent(); target->SetCurrentBrowserParent(nullptr); // If we were in a remote frame, trigger unloading of the remote window. The // previous BrowserParent is registered in `mUnloadingHosts` and will only be // cleared when the BrowserParent is fully destroyed. bool wasRemote = oldBrowser && oldBrowser->GetBrowsingContext() == target; if (wasRemote) { MOZ_DIAGNOSTIC_ASSERT(oldBrowser != embedderBrowser); MOZ_DIAGNOSTIC_ASSERT(oldBrowser->IsDestroyed() || oldBrowser->GetBrowserBridgeParent()); // `oldBrowser` will clear the `UnloadingHost` status once the actor has // been destroyed. if (oldBrowser->CanSend()) { target->StartUnloadingHost(oldBrowser->Manager()->ChildID()); Unused << oldBrowser->SendWillChangeProcess(); oldBrowser->Destroy(); } } // Update which process is considered the current owner target->SetOwnerProcessId(mContentParent->ChildID()); // If we're switching from remote to local, we don't need to create a // BrowserBridge, and can instead perform the switch directly. if (mContentParent == embedderBrowser->Manager()) { MOZ_DIAGNOSTIC_ASSERT( mPendingSwitchId, "We always have a PendingSwitchId, except for print-preview loads, " "which will never perform a process-switch to being in-process with " "their embedder"); MOZ_DIAGNOSTIC_ASSERT(wasRemote, "Attempt to process-switch from local to local?"); target->SetCurrentBrowserParent(embedderBrowser); Unused << embedderWindow->SendMakeFrameLocal(target, mPendingSwitchId); mPromise->Resolve(embedderBrowser, __func__); return NS_OK; } // The BrowsingContext will be remote, either as an already-remote frame // changing processes, or as a local frame becoming remote. Construct a new // BrowserBridgeParent to host the remote content. target->SetCurrentBrowserParent(nullptr); MOZ_DIAGNOSTIC_ASSERT(target->UseRemoteTabs() && target->UseRemoteSubframes(), "Not supported without fission"); uint32_t chromeFlags = nsIWebBrowserChrome::CHROME_REMOTE_WINDOW | nsIWebBrowserChrome::CHROME_FISSION_WINDOW; if (target->UsePrivateBrowsing()) { chromeFlags |= nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW; } 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))) { // If we've already destroyed our previous document, make a best-effort // attempt to recover from this failure and show the crashed tab UI. We only // do this in the previously-remote case, as previously in-process frames // will have their navigation cancelled, and will remain visible. if (wasRemote) { target->ShowSubframeCrashedUI(oldBrowser->GetBrowserBridgeParent()); } return rv; } // 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(); { // If we weren't remote, mark our embedder window browser as unloading until // our embedder process has acked our MakeFrameRemote message. Maybe clearChildID; if (!wasRemote) { clearChildID = Some(embedderBrowser->Manager()->ChildID()); target->StartUnloadingHost(*clearChildID); } auto callback = [target, clearChildID](auto&&) { if (clearChildID) { target->ClearUnloadingHost(*clearChildID); } }; ManagedEndpoint endpoint = embedderBrowser->OpenPBrowserBridgeEndpoint(bridge); MOZ_DIAGNOSTIC_ASSERT(endpoint.IsValid()); 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__); return NS_OK; } 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; } CanonicalBrowsingContext::PendingRemotenessChange::PendingRemotenessChange( CanonicalBrowsingContext* aTarget, RemotenessPromise::Private* aPromise, uint64_t aPendingSwitchId, const NavigationIsolationOptions& aOptions) : mTarget(aTarget), mPromise(aPromise), mPendingSwitchId(aPendingSwitchId), mOptions(aOptions) {} CanonicalBrowsingContext::PendingRemotenessChange::~PendingRemotenessChange() { MOZ_ASSERT(!mPromise && !mTarget && !mContentParent && !mSpecificGroup, "should've already been Cancel() or Complete()-ed"); } BrowserParent* CanonicalBrowsingContext::GetBrowserParent() const { return mCurrentBrowserParent; } void CanonicalBrowsingContext::SetCurrentBrowserParent( BrowserParent* aBrowserParent) { MOZ_DIAGNOSTIC_ASSERT(!mCurrentBrowserParent || !aBrowserParent, "BrowsingContext already has a current BrowserParent!"); MOZ_DIAGNOSTIC_ASSERT_IF(aBrowserParent, aBrowserParent->CanSend()); MOZ_DIAGNOSTIC_ASSERT_IF(aBrowserParent, aBrowserParent->Manager()->ChildID() == mProcessId); // BrowserParent must either be directly for this BrowsingContext, or the // manager out our embedder WindowGlobal. MOZ_DIAGNOSTIC_ASSERT_IF( aBrowserParent && aBrowserParent->GetBrowsingContext() != this, GetParentWindowContext() && GetParentWindowContext()->Manager() == aBrowserParent); mCurrentBrowserParent = aBrowserParent; } RefPtr CanonicalBrowsingContext::ChangeRemoteness( const NavigationIsolationOptions& aOptions, uint64_t aPendingSwitchId) { 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(!aOptions.mReplaceBrowsingContext || IsTop(), "Cannot replace BrowsingContext for subframes"); MOZ_DIAGNOSTIC_ASSERT( aOptions.mSpecificGroupId == 0 || aOptions.mReplaceBrowsingContext, "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 (aOptions.mRemoteType.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"); } auto promise = MakeRefPtr(__func__); promise->UseDirectTaskDispatch(__func__); RefPtr change = new PendingRemotenessChange(this, promise, aPendingSwitchId, aOptions); 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 (aOptions.mSpecificGroupId) { change->mSpecificGroup = BrowsingContextGroup::GetOrCreate(aOptions.mSpecificGroupId); 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(); } // Mark prepareToChange as unresolved, and wait for it to become resolved. if (blocker && blocker->State() != Promise::PromiseState::Resolved) { change->mWaitingForPrepareToChange = true; RefPtr listener = new DomPromiseListener( [change](JSContext* aCx, JS::HandleValue aValue) { change->mWaitingForPrepareToChange = false; change->MaybeFinish(); }, [change](nsresult aRv) { change->Cancel(aRv); }); blocker->AppendNativeHandler(listener); } } // Switching a subframe to be local within it's embedding process. RefPtr embedderBrowser = embedderWindowGlobal->GetBrowserParent(); if (embedderBrowser && aOptions.mRemoteType == 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"); MOZ_DIAGNOSTIC_ASSERT(!aOptions.mReplaceBrowsingContext); MOZ_DIAGNOSTIC_ASSERT(!aOptions.mRemoteType.IsEmpty()); MOZ_DIAGNOSTIC_ASSERT(!change->mWaitingForPrepareToChange); MOZ_DIAGNOSTIC_ASSERT(!change->mSpecificGroup); // Switching to local, so we don't need to create a new process, and will // instead use our embedder process. change->mContentParent = embedderBrowser->Manager(); change->mContentParent->AddKeepAlive(); change->ProcessLaunched(); return promise.forget(); } // Switching to the parent process. if (aOptions.mRemoteType.IsEmpty()) { change->ProcessLaunched(); return promise.forget(); } // If we're aiming to end up in a new process of the same type as our old // process, and then putting our previous document in the BFCache, try to stay // in the same process to avoid creating new processes unnecessarially. RefPtr existingProcess = GetContentParent(); if (existingProcess && existingProcess->IsAlive() && aOptions.mReplaceBrowsingContext && aOptions.mRemoteType == existingProcess->GetRemoteType() && aOptions.mRemoteType != LARGE_ALLOCATION_REMOTE_TYPE) { change->mContentParent = existingProcess; change->mContentParent->AddKeepAlive(); change->ProcessLaunched(); return promise.forget(); } // 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 = aOptions.mReplaceBrowsingContext ? change->mSpecificGroup.get() : Group(); bool preferUsed = StaticPrefs::browser_tabs_remote_subframesPreferUsed() && !IsTop(); change->mContentParent = ContentParent::GetNewOrUsedLaunchingBrowserProcess( /* aRemoteType = */ aOptions.mRemoteType, /* aGroup = */ finalGroup, /* aPriority = */ hal::PROCESS_PRIORITY_FOREGROUND, /* aPreferUsed = */ preferUsed); 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(); if (change->mContentParent->IsLaunching()) { change->mContentParent->WaitForLaunchAsync()->Then( GetMainThreadSerialEventTarget(), __func__, [change](ContentParent*) { change->ProcessLaunched(); }, [change](LaunchError) { change->Cancel(NS_ERROR_FAILURE); }); } else { change->ProcessLaunched(); } return promise.forget(); } void CanonicalBrowsingContext::MaybeSetPermanentKey(Element* aEmbedder) { MOZ_DIAGNOSTIC_ASSERT(IsTop()); if (aEmbedder) { if (nsCOMPtr browser = aEmbedder->AsBrowser()) { JS::RootedValue key(RootingCx()); if (NS_SUCCEEDED(browser->GetPermanentKey(&key)) && key.isObject()) { mPermanentKey = key; } } } } 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; } // Session-history-in-parent implementation relies currently on getting a // round trip through a child process. if (aLoadState->LoadIsFromSessionHistory()) { 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; } // 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()); } } already_AddRefed CanonicalBrowsingContext::GetCurrentURI() const { nsCOMPtr currentURI; if (nsIDocShell* docShell = GetDocShell()) { MOZ_ALWAYS_SUCCEEDS( nsDocShell::Cast(docShell)->GetCurrentURI(getter_AddRefs(currentURI))); } else { currentURI = mCurrentRemoteURI; } return currentURI.forget(); } void CanonicalBrowsingContext::SetCurrentRemoteURI(nsIURI* aCurrentRemoteURI) { MOZ_ASSERT(!GetDocShell()); mCurrentRemoteURI = aCurrentRemoteURI; } 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::SetRestoreData(SessionStoreRestoreData* aData, ErrorResult& aError) { MOZ_DIAGNOSTIC_ASSERT(aData); nsCOMPtr global = do_QueryInterface(GetParentObject()); RefPtr promise = Promise::Create(global, aError); if (aError.Failed()) { return; } if (NS_WARN_IF(NS_FAILED(SetHasRestoreData(true)))) { aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } mRestoreState = new RestoreState(); mRestoreState->mData = aData; mRestoreState->mPromise = promise; } already_AddRefed CanonicalBrowsingContext::GetRestorePromise() { if (mRestoreState) { return do_AddRef(mRestoreState->mPromise); } return nullptr; } void CanonicalBrowsingContext::ClearRestoreState() { if (!mRestoreState) { MOZ_DIAGNOSTIC_ASSERT(!GetHasRestoreData()); return; } if (mRestoreState->mPromise) { mRestoreState->mPromise->MaybeRejectWithUndefined(); } mRestoreState = nullptr; MOZ_ALWAYS_SUCCEEDS(SetHasRestoreData(false)); } void CanonicalBrowsingContext::RequestRestoreTabContent( WindowGlobalParent* aWindow) { MOZ_DIAGNOSTIC_ASSERT(IsTop()); if (IsDiscarded() || !mRestoreState || !mRestoreState->mData) { return; } CanonicalBrowsingContext* context = aWindow->GetBrowsingContext(); MOZ_DIAGNOSTIC_ASSERT(!context->IsDiscarded()); RefPtr data = mRestoreState->mData->FindDataForChild(context); if (context->IsTop()) { MOZ_DIAGNOSTIC_ASSERT(context == this); // We need to wait until the appropriate load event has fired before we // can "complete" the restore process, so if we're holding an empty data // object, just resolve the promise immediately. if (mRestoreState->mData->IsEmpty()) { MOZ_DIAGNOSTIC_ASSERT(!data || data->IsEmpty()); mRestoreState->Resolve(); ClearRestoreState(); return; } // Since we're following load event order, we'll only arrive here for a // toplevel context after we've already sent down data for all child frames, // so it's safe to clear this reference now. The completion callback below // relies on the mData field being null to determine if all requests have // been sent out. mRestoreState->ClearData(); MOZ_ALWAYS_SUCCEEDS(SetHasRestoreData(false)); } if (data && !data->IsEmpty()) { auto onTabRestoreComplete = [self = RefPtr{this}, state = RefPtr{mRestoreState}](auto) { state->mResolves++; if (!state->mData && state->mRequests == state->mResolves) { state->Resolve(); if (state == self->mRestoreState) { self->ClearRestoreState(); } } }; mRestoreState->mRequests++; if (data->CanRestoreInto(aWindow->GetDocumentURI())) { if (!aWindow->IsInProcess()) { aWindow->SendRestoreTabContent(data, onTabRestoreComplete, onTabRestoreComplete); return; } data->RestoreInto(context); } // This must be called both when we're doing an in-process restore, and when // we didn't do a restore at all due to a URL mismatch. onTabRestoreComplete(true); } } void CanonicalBrowsingContext::RestoreState::Resolve() { MOZ_DIAGNOSTIC_ASSERT(mPromise); mPromise->MaybeResolveWithUndefined(); mPromise = nullptr; } nsresult CanonicalBrowsingContext::WriteSessionStorageToSessionStore( const nsTArray& aSesssionStorage, uint32_t aEpoch) { nsCOMPtr funcs = do_ImportModule( "resource://gre/modules/SessionStoreFunctions.jsm", fallible); if (!funcs) { return NS_ERROR_FAILURE; } nsCOMPtr wrapped = do_QueryInterface(funcs); AutoJSAPI jsapi; if (!jsapi.Init(wrapped->GetJSObjectGlobal())) { return NS_ERROR_FAILURE; } JS::RootedValue key(jsapi.cx(), Top()->PermanentKey()); Record> storage; JS::RootedValue update(jsapi.cx()); if (!aSesssionStorage.IsEmpty()) { SessionStoreUtils::ConstructSessionStorageValues(this, aSesssionStorage, storage); if (!ToJSValue(jsapi.cx(), storage, &update)) { return NS_ERROR_FAILURE; } } else { update.setNull(); } return funcs->UpdateSessionStoreForStorage(Top()->GetEmbedderElement(), this, key, aEpoch, update); } void CanonicalBrowsingContext::UpdateSessionStoreSessionStorage( const std::function& aDone) { if constexpr (!SessionStoreUtils::NATIVE_LISTENER) { aDone(); return; } using DataPromise = BackgroundSessionStorageManager::DataPromise; BackgroundSessionStorageManager::GetData( this, StaticPrefs::browser_sessionstore_dom_storage_limit(), /* aCancelSessionStoreTiemr = */ true) ->Then(GetCurrentSerialEventTarget(), __func__, [self = RefPtr{this}, aDone, epoch = GetSessionStoreEpoch()]( const DataPromise::ResolveOrRejectValue& valueList) { if (valueList.IsResolve()) { self->WriteSessionStorageToSessionStore( valueList.ResolveValue(), epoch); } aDone(); }); } /* static */ void CanonicalBrowsingContext::UpdateSessionStoreForStorage( uint64_t aBrowsingContextId) { RefPtr browsingContext = Get(aBrowsingContextId); if (!browsingContext) { return; } browsingContext->UpdateSessionStoreSessionStorage([]() {}); } void CanonicalBrowsingContext::MaybeScheduleSessionStoreUpdate() { if constexpr (!SessionStoreUtils::NATIVE_LISTENER) { return; } if (!IsTop()) { Top()->MaybeScheduleSessionStoreUpdate(); return; } if (IsInBFCache()) { return; } if (mSessionStoreSessionStorageUpdateTimer) { return; } if (!StaticPrefs::browser_sessionstore_debug_no_auto_updates()) { auto result = NS_NewTimerWithFuncCallback( [](nsITimer*, void* aClosure) { auto* context = static_cast(aClosure); context->UpdateSessionStoreSessionStorage([]() {}); }, this, StaticPrefs::browser_sessionstore_interval(), nsITimer::TYPE_ONE_SHOT, "CanonicalBrowsingContext::MaybeScheduleSessionStoreUpdate"); if (result.isErr()) { return; } mSessionStoreSessionStorageUpdateTimer = result.unwrap(); } } void CanonicalBrowsingContext::CancelSessionStoreUpdate() { if (mSessionStoreSessionStorageUpdateTimer) { mSessionStoreSessionStorageUpdateTimer->Cancel(); mSessionStoreSessionStorageUpdateTimer = nullptr; } } 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; } auto CanonicalBrowsingContext::FindUnloadingHost(uint64_t aChildID) -> nsTArray::iterator { return std::find_if( mUnloadingHosts.begin(), mUnloadingHosts.end(), [&](const auto& host) { return host.mChildID == aChildID; }); } void CanonicalBrowsingContext::ClearUnloadingHost(uint64_t aChildID) { // Notify any callbacks which were waiting for the host to finish unloading // that it has. auto found = FindUnloadingHost(aChildID); if (found != mUnloadingHosts.end()) { auto callbacks = std::move(found->mCallbacks); mUnloadingHosts.RemoveElementAt(found); for (const auto& callback : callbacks) { callback(); } } } void CanonicalBrowsingContext::StartUnloadingHost(uint64_t aChildID) { MOZ_DIAGNOSTIC_ASSERT(FindUnloadingHost(aChildID) == mUnloadingHosts.end()); mUnloadingHosts.AppendElement(UnloadingHost{aChildID, {}}); } void CanonicalBrowsingContext::BrowserParentDestroyed( BrowserParent* aBrowserParent, bool aAbnormalShutdown) { ClearUnloadingHost(aBrowserParent->Manager()->ChildID()); // Handling specific to when the current BrowserParent has been destroyed. if (mCurrentBrowserParent == aBrowserParent) { mCurrentBrowserParent = nullptr; // If this BrowserParent is for a subframe, attempt to recover from a // subframe crash by rendering the subframe crashed page in the embedding // content. if (aAbnormalShutdown) { ShowSubframeCrashedUI(aBrowserParent->GetBrowserBridgeParent()); } } } void CanonicalBrowsingContext::ShowSubframeCrashedUI( BrowserBridgeParent* aBridge) { if (!aBridge || IsDiscarded() || !aBridge->CanSend()) { return; } MOZ_DIAGNOSTIC_ASSERT(!aBridge->GetBrowsingContext() || aBridge->GetBrowsingContext() == this); // There is no longer a current inner window within this // BrowsingContext, update the `CurrentInnerWindowId` field to reflect // this. MOZ_ALWAYS_SUCCEEDS(SetCurrentInnerWindowId(0)); // The owning process will now be the embedder to render the subframe // crashed page, switch ownership back over. SetOwnerProcessId(aBridge->Manager()->Manager()->ChildID()); SetCurrentBrowserParent(aBridge->Manager()); Unused << aBridge->SendSubFrameCrashed(); } static void LogBFCacheBlockingForDoc(BrowsingContext* aBrowsingContext, uint16_t aBFCacheCombo, bool aIsSubDoc) { if (aIsSubDoc) { nsAutoCString uri("[no uri]"); nsCOMPtr currentURI = aBrowsingContext->Canonical()->GetCurrentURI(); if (currentURI) { uri = currentURI->GetSpecOrDefault(); } MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" ** Blocked for document %s", uri.get())); } if (aBFCacheCombo & BFCacheStatus::EVENT_HANDLING_SUPPRESSED) { MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * event handling suppression")); } if (aBFCacheCombo & BFCacheStatus::SUSPENDED) { MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * suspended Window")); } if (aBFCacheCombo & BFCacheStatus::UNLOAD_LISTENER) { MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * unload listener")); } if (aBFCacheCombo & BFCacheStatus::REQUEST) { MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * requests in the loadgroup")); } if (aBFCacheCombo & BFCacheStatus::ACTIVE_GET_USER_MEDIA) { MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * GetUserMedia")); } if (aBFCacheCombo & BFCacheStatus::ACTIVE_PEER_CONNECTION) { MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * PeerConnection")); } if (aBFCacheCombo & BFCacheStatus::CONTAINS_EME_CONTENT) { MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * EME content")); } if (aBFCacheCombo & BFCacheStatus::CONTAINS_MSE_CONTENT) { MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * MSE use")); } if (aBFCacheCombo & BFCacheStatus::HAS_ACTIVE_SPEECH_SYNTHESIS) { MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * Speech use")); } if (aBFCacheCombo & BFCacheStatus::HAS_USED_VR) { MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * used VR")); } if (aBFCacheCombo & BFCacheStatus::BEFOREUNLOAD_LISTENER) { MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * beforeunload listener")); } } bool CanonicalBrowsingContext::AllowedInBFCache( const Maybe& aChannelId) { if (MOZ_UNLIKELY(MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug))) { nsAutoCString uri("[no uri]"); nsCOMPtr currentURI = GetCurrentURI(); if (currentURI) { uri = currentURI->GetSpecOrDefault(); } MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, ("Checking %s", uri.get())); } if (IsInProcess()) { return false; } uint32_t bfcacheCombo = 0; if (mRestoreState) { bfcacheCombo |= BFCacheStatus::RESTORING; MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * during session restore")); } if (Group()->Toplevels().Length() > 1) { bfcacheCombo |= BFCacheStatus::NOT_ONLY_TOPLEVEL_IN_BCG; MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * auxiliary BrowsingContexts")); } // There are not a lot of about:* pages that are allowed to load in // subframes, so it's OK to allow those few about:* pages enter BFCache. MOZ_ASSERT(IsTop(), "Trying to put a non top level BC into BFCache"); WindowGlobalParent* wgp = GetCurrentWindowGlobal(); if (wgp && wgp->GetDocumentURI()) { nsCOMPtr currentURI = wgp->GetDocumentURI(); // Exempt about:* pages from bfcache, with the exception of about:blank if (currentURI->SchemeIs("about") && !currentURI->GetSpecOrDefault().EqualsLiteral("about:blank")) { bfcacheCombo |= BFCacheStatus::ABOUT_PAGE; MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * about:* page")); } } // For telemetry we're collecting all the flags for all the BCs hanging // from this top-level BC. PreOrderWalk([&](BrowsingContext* aBrowsingContext) { WindowGlobalParent* wgp = aBrowsingContext->Canonical()->GetCurrentWindowGlobal(); uint32_t subDocBFCacheCombo = wgp ? wgp->GetBFCacheStatus() : 0; if (wgp) { const Maybe& singleChannelId = wgp->GetSingleChannelId(); if (singleChannelId.isSome()) { if (singleChannelId.value() == 0 || aChannelId.isNothing() || singleChannelId.value() != aChannelId.value()) { subDocBFCacheCombo |= BFCacheStatus::REQUEST; } } } if (MOZ_UNLIKELY(MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug))) { LogBFCacheBlockingForDoc(aBrowsingContext, subDocBFCacheCombo, aBrowsingContext != this); } bfcacheCombo |= subDocBFCacheCombo; }); nsDocShell::ReportBFCacheComboTelemetry(bfcacheCombo); if (MOZ_UNLIKELY(MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug))) { nsAutoCString uri("[no uri]"); nsCOMPtr currentURI = GetCurrentURI(); if (currentURI) { uri = currentURI->GetSpecOrDefault(); } MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" +> %s %s be blocked from going into the BFCache", uri.get(), bfcacheCombo == 0 ? "shouldn't" : "should")); } if (StaticPrefs::docshell_shistory_bfcache_allow_unload_listeners()) { bfcacheCombo &= ~BFCacheStatus::UNLOAD_LISTENER; } return bfcacheCombo == 0; } void CanonicalBrowsingContext::SetTouchEventsOverride( dom::TouchEventsOverride aOverride, ErrorResult& aRv) { SetTouchEventsOverrideInternal(aOverride, aRv); } void CanonicalBrowsingContext::AddPageAwakeRequest() { MOZ_ASSERT(IsTop()); auto count = GetPageAwakeRequestCount(); MOZ_ASSERT(count < UINT32_MAX); Unused << SetPageAwakeRequestCount(++count); } void CanonicalBrowsingContext::RemovePageAwakeRequest() { MOZ_ASSERT(IsTop()); auto count = GetPageAwakeRequestCount(); MOZ_ASSERT(count > 0); Unused << SetPageAwakeRequestCount(--count); } void CanonicalBrowsingContext::CloneDocumentTreeInto( CanonicalBrowsingContext* aSource, const nsACString& aRemoteType, embedding::PrintData&& aPrintData) { NavigationIsolationOptions options; options.mRemoteType = aRemoteType; mClonePromise = ChangeRemoteness(options, /* aPendingSwitchId = */ 0) ->Then( GetMainThreadSerialEventTarget(), __func__, [source = MaybeDiscardedBrowsingContext{aSource}, data = std::move(aPrintData)]( BrowserParent* aBp) -> RefPtr { RefPtr bridge = aBp->GetBrowserBridgeParent(); return aBp->SendCloneDocumentTreeIntoSelf(source, data) ->Then( GetMainThreadSerialEventTarget(), __func__, [bridge]( BrowserParent::CloneDocumentTreeIntoSelfPromise:: ResolveOrRejectValue&& aValue) { // We're cloning a remote iframe, so we created a // BrowserBridge which makes us register an OOP load // (see Document::OOPChildLoadStarted), even though // this isn't a real load. We call // SendMaybeFireEmbedderLoadEvents here so that we do // register the end of the load (see // Document::OOPChildLoadDone). if (bridge) { Unused << bridge->SendMaybeFireEmbedderLoadEvents( EmbedderElementEventType::NoEvent); } if (aValue.IsResolve() && aValue.ResolveValue()) { return GenericNonExclusivePromise::CreateAndResolve( true, __func__); } return GenericNonExclusivePromise::CreateAndReject( NS_ERROR_FAILURE, __func__); }); }, [](nsresult aRv) -> RefPtr { NS_WARNING( nsPrintfCString("Remote clone failed: %x\n", unsigned(aRv)) .get()); return GenericNonExclusivePromise::CreateAndReject( NS_ERROR_FAILURE, __func__); }); mClonePromise->Then( GetMainThreadSerialEventTarget(), __func__, [self = RefPtr{this}]() { self->mClonePromise = nullptr; }); } bool CanonicalBrowsingContext::StartApzAutoscroll(float aAnchorX, float aAnchorY, nsViewID aScrollId, uint32_t aPresShellId) { nsCOMPtr widget; mozilla::layers::LayersId layersId{0}; if (IsInProcess()) { nsCOMPtr outer = GetDOMWindow(); if (!outer) { return false; } widget = widget::WidgetUtils::DOMWindowToWidget(outer); if (widget) { layersId = widget->GetRootLayerTreeId(); } } else { RefPtr parent = GetBrowserParent(); if (!parent) { return false; } widget = parent->GetWidget(); layersId = parent->GetLayersId(); } if (!widget || !widget->AsyncPanZoomEnabled()) { return false; } // The anchor coordinates that are passed in are relative to the origin // of the screen, but we are sending them to APZ which only knows about // coordinates relative to the widget, so convert them accordingly. CSSPoint anchorCss{aAnchorX, aAnchorY}; LayoutDeviceIntPoint anchor = RoundedToInt(anchorCss * widget->GetDefaultScale()); anchor -= widget->WidgetToScreenOffset(); mozilla::layers::ScrollableLayerGuid guid(layersId, aPresShellId, aScrollId); return widget->StartAsyncAutoscroll( ViewAs( anchor, PixelCastJustification::LayoutDeviceIsScreenForBounds), guid); } void CanonicalBrowsingContext::StopApzAutoscroll(nsViewID aScrollId, uint32_t aPresShellId) { nsCOMPtr widget; mozilla::layers::LayersId layersId{0}; if (IsInProcess()) { nsCOMPtr outer = GetDOMWindow(); if (!outer) { return; } widget = widget::WidgetUtils::DOMWindowToWidget(outer); if (widget) { layersId = widget->GetRootLayerTreeId(); } } else { RefPtr parent = GetBrowserParent(); if (!parent) { return; } widget = parent->GetWidget(); layersId = parent->GetLayersId(); } if (!widget || !widget->AsyncPanZoomEnabled()) { return; } mozilla::layers::ScrollableLayerGuid guid(layersId, aPresShellId, aScrollId); widget->StopAsyncAutoscroll(guid); } NS_IMPL_CYCLE_COLLECTION_CLASS(CanonicalBrowsingContext) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(CanonicalBrowsingContext, BrowsingContext) tmp->mPermanentKey.setNull(); if (tmp->mSessionHistory) { tmp->mSessionHistory->SetBrowsingContext(nullptr); } NS_IMPL_CYCLE_COLLECTION_UNLINK(mSessionHistory, mContainerFeaturePolicy, mCurrentBrowserParent, mWebProgress, mSessionStoreSessionStorageUpdateTimer) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(CanonicalBrowsingContext, BrowsingContext) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSessionHistory, mContainerFeaturePolicy, mCurrentBrowserParent, mWebProgress, mSessionStoreSessionStorageUpdateTimer) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(CanonicalBrowsingContext, BrowsingContext) NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mPermanentKey) NS_IMPL_CYCLE_COLLECTION_TRACE_END 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