Bug 1773793 [wpt PR 34381] - [FedCM] Move most tests to fedcm-network-requests, a...
[gecko.git] / docshell / base / CanonicalBrowsingContext.cpp
blob7da3557f53cc229822b95eee30a58bf34b74cbf6
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/CanonicalBrowsingContext.h"
9 #include "mozilla/CheckedInt.h"
10 #include "mozilla/ErrorResult.h"
11 #include "mozilla/EventForwards.h"
12 #include "mozilla/AsyncEventDispatcher.h"
13 #include "mozilla/dom/BrowserParent.h"
14 #include "mozilla/dom/BrowsingContextBinding.h"
15 #include "mozilla/dom/BrowsingContextGroup.h"
16 #include "mozilla/dom/ContentParent.h"
17 #include "mozilla/dom/EventTarget.h"
18 #include "mozilla/dom/PBrowserParent.h"
19 #include "mozilla/dom/PBackgroundSessionStorageCache.h"
20 #include "mozilla/dom/PWindowGlobalParent.h"
21 #include "mozilla/dom/WindowGlobalParent.h"
22 #include "mozilla/dom/ContentProcessManager.h"
23 #include "mozilla/dom/MediaController.h"
24 #include "mozilla/dom/MediaControlService.h"
25 #include "mozilla/dom/ContentPlaybackController.h"
26 #include "mozilla/dom/SessionStorageManager.h"
27 #include "mozilla/ipc/ProtocolUtils.h"
28 #ifdef NS_PRINTING
29 # include "mozilla/layout/RemotePrintJobParent.h"
30 #endif
31 #include "mozilla/net/DocumentLoadListener.h"
32 #include "mozilla/NullPrincipal.h"
33 #include "mozilla/StaticPrefs_docshell.h"
34 #include "mozilla/StaticPrefs_fission.h"
35 #include "mozilla/Telemetry.h"
36 #include "nsILayoutHistoryState.h"
37 #include "nsIPrintSettings.h"
38 #include "nsIPrintSettingsService.h"
39 #include "nsISupports.h"
40 #include "nsIWebNavigation.h"
41 #include "mozilla/MozPromiseInlines.h"
42 #include "nsDocShell.h"
43 #include "nsFrameLoader.h"
44 #include "nsFrameLoaderOwner.h"
45 #include "nsGlobalWindowOuter.h"
46 #include "nsIWebBrowserChrome.h"
47 #include "nsIXULRuntime.h"
48 #include "nsNetUtil.h"
49 #include "nsSHistory.h"
50 #include "nsSecureBrowserUI.h"
51 #include "nsQueryObject.h"
52 #include "nsBrowserStatusFilter.h"
53 #include "nsIBrowser.h"
54 #include "nsTHashSet.h"
55 #include "SessionStoreFunctions.h"
56 #include "nsIXPConnect.h"
57 #include "nsImportModule.h"
58 #include "UnitTransforms.h"
60 using namespace mozilla::ipc;
62 extern mozilla::LazyLogModule gAutoplayPermissionLog;
63 extern mozilla::LazyLogModule gSHLog;
64 extern mozilla::LazyLogModule gSHIPBFCacheLog;
66 #define AUTOPLAY_LOG(msg, ...) \
67 MOZ_LOG(gAutoplayPermissionLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
69 static mozilla::LazyLogModule sPBContext("PBContext");
71 // Global count of canonical browsing contexts with the private attribute set
72 static uint32_t gNumberOfPrivateContexts = 0;
74 // Current parent process epoch for parent initiated navigations
75 static uint64_t gParentInitiatedNavigationEpoch = 0;
77 static void IncreasePrivateCount() {
78 gNumberOfPrivateContexts++;
79 MOZ_LOG(sPBContext, mozilla::LogLevel::Debug,
80 ("%s: Private browsing context count %d -> %d", __func__,
81 gNumberOfPrivateContexts - 1, gNumberOfPrivateContexts));
82 if (gNumberOfPrivateContexts > 1) {
83 return;
86 static bool sHasSeenPrivateContext = false;
87 if (!sHasSeenPrivateContext) {
88 sHasSeenPrivateContext = true;
89 mozilla::Telemetry::ScalarSet(
90 mozilla::Telemetry::ScalarID::DOM_PARENTPROCESS_PRIVATE_WINDOW_USED,
91 true);
95 static void DecreasePrivateCount() {
96 MOZ_ASSERT(gNumberOfPrivateContexts > 0);
97 gNumberOfPrivateContexts--;
99 MOZ_LOG(sPBContext, mozilla::LogLevel::Debug,
100 ("%s: Private browsing context count %d -> %d", __func__,
101 gNumberOfPrivateContexts + 1, gNumberOfPrivateContexts));
102 if (!gNumberOfPrivateContexts &&
103 !mozilla::Preferences::GetBool("browser.privatebrowsing.autostart")) {
104 nsCOMPtr<nsIObserverService> observerService =
105 mozilla::services::GetObserverService();
106 if (observerService) {
107 MOZ_LOG(sPBContext, mozilla::LogLevel::Debug,
108 ("%s: last-pb-context-exited fired", __func__));
109 observerService->NotifyObservers(nullptr, "last-pb-context-exited",
110 nullptr);
115 namespace mozilla {
116 namespace dom {
118 extern mozilla::LazyLogModule gUserInteractionPRLog;
120 #define USER_ACTIVATION_LOG(msg, ...) \
121 MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
123 CanonicalBrowsingContext::CanonicalBrowsingContext(WindowContext* aParentWindow,
124 BrowsingContextGroup* aGroup,
125 uint64_t aBrowsingContextId,
126 uint64_t aOwnerProcessId,
127 uint64_t aEmbedderProcessId,
128 BrowsingContext::Type aType,
129 FieldValues&& aInit)
130 : BrowsingContext(aParentWindow, aGroup, aBrowsingContextId, aType,
131 std::move(aInit)),
132 mProcessId(aOwnerProcessId),
133 mEmbedderProcessId(aEmbedderProcessId),
134 mPermanentKey(JS::NullValue()) {
135 // You are only ever allowed to create CanonicalBrowsingContexts in the
136 // parent process.
137 MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
139 // The initial URI in a BrowsingContext is always "about:blank".
140 MOZ_ALWAYS_SUCCEEDS(
141 NS_NewURI(getter_AddRefs(mCurrentRemoteURI), "about:blank"));
143 mozilla::HoldJSObjects(this);
146 CanonicalBrowsingContext::~CanonicalBrowsingContext() {
147 mPermanentKey.setNull();
149 mozilla::DropJSObjects(this);
151 if (mSessionHistory) {
152 mSessionHistory->SetBrowsingContext(nullptr);
156 /* static */
157 already_AddRefed<CanonicalBrowsingContext> CanonicalBrowsingContext::Get(
158 uint64_t aId) {
159 MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
160 return BrowsingContext::Get(aId).downcast<CanonicalBrowsingContext>();
163 /* static */
164 CanonicalBrowsingContext* CanonicalBrowsingContext::Cast(
165 BrowsingContext* aContext) {
166 MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
167 return static_cast<CanonicalBrowsingContext*>(aContext);
170 /* static */
171 const CanonicalBrowsingContext* CanonicalBrowsingContext::Cast(
172 const BrowsingContext* aContext) {
173 MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
174 return static_cast<const CanonicalBrowsingContext*>(aContext);
177 already_AddRefed<CanonicalBrowsingContext> CanonicalBrowsingContext::Cast(
178 already_AddRefed<BrowsingContext>&& aContext) {
179 MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
180 return aContext.downcast<CanonicalBrowsingContext>();
183 ContentParent* CanonicalBrowsingContext::GetContentParent() const {
184 if (mProcessId == 0) {
185 return nullptr;
188 ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
189 if (!cpm) {
190 return nullptr;
192 return cpm->GetContentProcessById(ContentParentId(mProcessId));
195 void CanonicalBrowsingContext::GetCurrentRemoteType(nsACString& aRemoteType,
196 ErrorResult& aRv) const {
197 // If we're in the parent process, dump out the void string.
198 if (mProcessId == 0) {
199 aRemoteType = NOT_REMOTE_TYPE;
200 return;
203 ContentParent* cp = GetContentParent();
204 if (!cp) {
205 aRv.Throw(NS_ERROR_UNEXPECTED);
206 return;
209 aRemoteType = cp->GetRemoteType();
212 void CanonicalBrowsingContext::SetOwnerProcessId(uint64_t aProcessId) {
213 MOZ_LOG(GetLog(), LogLevel::Debug,
214 ("SetOwnerProcessId for 0x%08" PRIx64 " (0x%08" PRIx64
215 " -> 0x%08" PRIx64 ")",
216 Id(), mProcessId, aProcessId));
218 mProcessId = aProcessId;
221 nsISecureBrowserUI* CanonicalBrowsingContext::GetSecureBrowserUI() {
222 if (!IsTop()) {
223 return nullptr;
225 if (!mSecureBrowserUI) {
226 mSecureBrowserUI = new nsSecureBrowserUI(this);
228 return mSecureBrowserUI;
231 namespace {
232 // The DocShellProgressBridge is attached to a root content docshell loaded in
233 // the parent process. Notifications are paired up with the docshell which they
234 // came from, so that they can be fired to the correct
235 // BrowsingContextWebProgress and bubble through this tree separately.
237 // Notifications are filtered by a nsBrowserStatusFilter before being received
238 // by the DocShellProgressBridge.
239 class DocShellProgressBridge : public nsIWebProgressListener {
240 public:
241 NS_DECL_ISUPPORTS
242 // NOTE: This relies in the expansion of `NS_FORWARD_SAFE` and all listener
243 // methods accepting an `aWebProgress` argument. If this changes in the
244 // future, this may need to be written manually.
245 NS_FORWARD_SAFE_NSIWEBPROGRESSLISTENER(GetTargetContext(aWebProgress))
247 explicit DocShellProgressBridge(uint64_t aTopContextId)
248 : mTopContextId(aTopContextId) {}
250 private:
251 virtual ~DocShellProgressBridge() = default;
253 nsIWebProgressListener* GetTargetContext(nsIWebProgress* aWebProgress) {
254 RefPtr<CanonicalBrowsingContext> context;
255 if (nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(aWebProgress)) {
256 context = docShell->GetBrowsingContext()->Canonical();
257 } else {
258 context = CanonicalBrowsingContext::Get(mTopContextId);
260 return context && !context->IsDiscarded() ? context->GetWebProgress()
261 : nullptr;
264 uint64_t mTopContextId = 0;
267 NS_IMPL_ISUPPORTS(DocShellProgressBridge, nsIWebProgressListener)
268 } // namespace
270 void CanonicalBrowsingContext::MaybeAddAsProgressListener(
271 nsIWebProgress* aWebProgress) {
272 // Only add as a listener if the created docshell is a toplevel content
273 // docshell. We'll get notifications for all of our subframes through a single
274 // listener.
275 if (!IsTopContent()) {
276 return;
279 if (!mDocShellProgressBridge) {
280 mDocShellProgressBridge = new DocShellProgressBridge(Id());
281 mStatusFilter = new nsBrowserStatusFilter();
282 mStatusFilter->AddProgressListener(mDocShellProgressBridge,
283 nsIWebProgress::NOTIFY_ALL);
286 aWebProgress->AddProgressListener(mStatusFilter, nsIWebProgress::NOTIFY_ALL);
289 void CanonicalBrowsingContext::ReplacedBy(
290 CanonicalBrowsingContext* aNewContext,
291 const NavigationIsolationOptions& aRemotenessOptions) {
292 MOZ_ASSERT(!aNewContext->mWebProgress);
293 MOZ_ASSERT(!aNewContext->mSessionHistory);
294 MOZ_ASSERT(IsTop() && aNewContext->IsTop());
296 mIsReplaced = true;
297 aNewContext->mIsReplaced = false;
299 if (mStatusFilter) {
300 mStatusFilter->RemoveProgressListener(mDocShellProgressBridge);
301 mStatusFilter = nullptr;
304 mWebProgress->ContextReplaced(aNewContext);
305 aNewContext->mWebProgress = std::move(mWebProgress);
307 // Use the Transaction for the fields which need to be updated whether or not
308 // the new context has been attached before.
309 // SetWithoutSyncing can be used if context hasn't been attached.
310 Transaction txn;
311 txn.SetBrowserId(GetBrowserId());
312 txn.SetHistoryID(GetHistoryID());
313 txn.SetExplicitActive(GetExplicitActive());
314 txn.SetEmbedderColorScheme(GetEmbedderColorScheme());
315 txn.SetHasRestoreData(GetHasRestoreData());
316 txn.SetShouldDelayMediaFromStart(GetShouldDelayMediaFromStart());
317 // As this is a different BrowsingContext, set InitialSandboxFlags to the
318 // current flags in the new context so that they also apply to any initial
319 // about:blank documents created in it.
320 txn.SetSandboxFlags(GetSandboxFlags());
321 txn.SetInitialSandboxFlags(GetSandboxFlags());
322 txn.SetTargetTopLevelLinkClicksToBlankInternal(
323 TargetTopLevelLinkClicksToBlank());
324 if (aNewContext->EverAttached()) {
325 MOZ_ALWAYS_SUCCEEDS(txn.Commit(aNewContext));
326 } else {
327 txn.CommitWithoutSyncing(aNewContext);
330 aNewContext->mRestoreState = mRestoreState.forget();
331 MOZ_ALWAYS_SUCCEEDS(SetHasRestoreData(false));
333 // XXXBFCache name handling is still a bit broken in Fission in general,
334 // at least in case name should be cleared.
335 if (aRemotenessOptions.mTryUseBFCache) {
336 MOZ_ASSERT(!aNewContext->EverAttached());
337 aNewContext->mFields.SetWithoutSyncing<IDX_Name>(GetName());
338 // We don't copy over HasLoadedNonInitialDocument here, we'll actually end
339 // up loading a new initial document at this point, before the real load.
340 // The real load will then end up setting HasLoadedNonInitialDocument to
341 // true.
344 if (mSessionHistory) {
345 mSessionHistory->SetBrowsingContext(aNewContext);
346 // At this point we will be creating a new ChildSHistory in the child.
347 // That means that the child's epoch will be reset, so it makes sense to
348 // reset the epoch in the parent too.
349 mSessionHistory->SetEpoch(0, Nothing());
350 mSessionHistory.swap(aNewContext->mSessionHistory);
351 RefPtr<ChildSHistory> childSHistory = ForgetChildSHistory();
352 aNewContext->SetChildSHistory(childSHistory);
355 if (mozilla::SessionHistoryInParent()) {
356 BackgroundSessionStorageManager::PropagateManager(Id(), aNewContext->Id());
359 // Transfer the ownership of the priority active status from the old context
360 // to the new context.
361 aNewContext->mPriorityActive = mPriorityActive;
362 mPriorityActive = false;
364 MOZ_ASSERT(aNewContext->mLoadingEntries.IsEmpty());
365 mLoadingEntries.SwapElements(aNewContext->mLoadingEntries);
366 MOZ_ASSERT(!aNewContext->mActiveEntry);
367 mActiveEntry.swap(aNewContext->mActiveEntry);
369 aNewContext->mPermanentKey = mPermanentKey;
370 mPermanentKey.setNull();
373 void CanonicalBrowsingContext::UpdateSecurityState() {
374 if (mSecureBrowserUI) {
375 mSecureBrowserUI->RecomputeSecurityFlags();
379 void CanonicalBrowsingContext::GetWindowGlobals(
380 nsTArray<RefPtr<WindowGlobalParent>>& aWindows) {
381 aWindows.SetCapacity(GetWindowContexts().Length());
382 for (auto& window : GetWindowContexts()) {
383 aWindows.AppendElement(static_cast<WindowGlobalParent*>(window.get()));
387 WindowGlobalParent* CanonicalBrowsingContext::GetCurrentWindowGlobal() const {
388 return static_cast<WindowGlobalParent*>(GetCurrentWindowContext());
391 WindowGlobalParent* CanonicalBrowsingContext::GetParentWindowContext() {
392 return static_cast<WindowGlobalParent*>(
393 BrowsingContext::GetParentWindowContext());
396 WindowGlobalParent* CanonicalBrowsingContext::GetTopWindowContext() {
397 return static_cast<WindowGlobalParent*>(
398 BrowsingContext::GetTopWindowContext());
401 already_AddRefed<nsIWidget>
402 CanonicalBrowsingContext::GetParentProcessWidgetContaining() {
403 // If our document is loaded in-process, such as chrome documents, get the
404 // widget directly from our outer window. Otherwise, try to get the widget
405 // from the toplevel content's browser's element.
406 nsCOMPtr<nsIWidget> widget;
407 if (nsGlobalWindowOuter* window = nsGlobalWindowOuter::Cast(GetDOMWindow())) {
408 widget = window->GetNearestWidget();
409 } else if (Element* topEmbedder = Top()->GetEmbedderElement()) {
410 widget = nsContentUtils::WidgetForContent(topEmbedder);
411 if (!widget) {
412 widget = nsContentUtils::WidgetForDocument(topEmbedder->OwnerDoc());
416 if (widget) {
417 widget = widget->GetTopLevelWidget();
420 return widget.forget();
423 already_AddRefed<nsIBrowserDOMWindow>
424 CanonicalBrowsingContext::GetBrowserDOMWindow() {
425 RefPtr<CanonicalBrowsingContext> chromeTop = TopCrossChromeBoundary();
426 if (nsCOMPtr<nsIDOMChromeWindow> chromeWin =
427 do_QueryInterface(chromeTop->GetDOMWindow())) {
428 nsCOMPtr<nsIBrowserDOMWindow> bdw;
429 if (NS_SUCCEEDED(chromeWin->GetBrowserDOMWindow(getter_AddRefs(bdw)))) {
430 return bdw.forget();
433 return nullptr;
436 already_AddRefed<WindowGlobalParent>
437 CanonicalBrowsingContext::GetEmbedderWindowGlobal() const {
438 uint64_t windowId = GetEmbedderInnerWindowId();
439 if (windowId == 0) {
440 return nullptr;
443 return WindowGlobalParent::GetByInnerWindowId(windowId);
446 already_AddRefed<CanonicalBrowsingContext>
447 CanonicalBrowsingContext::GetParentCrossChromeBoundary() {
448 if (GetParent()) {
449 return do_AddRef(Cast(GetParent()));
451 if (GetEmbedderElement()) {
452 return do_AddRef(
453 Cast(GetEmbedderElement()->OwnerDoc()->GetBrowsingContext()));
455 return nullptr;
458 already_AddRefed<CanonicalBrowsingContext>
459 CanonicalBrowsingContext::TopCrossChromeBoundary() {
460 RefPtr<CanonicalBrowsingContext> bc(this);
461 while (RefPtr<CanonicalBrowsingContext> parent =
462 bc->GetParentCrossChromeBoundary()) {
463 bc = parent.forget();
465 return bc.forget();
468 Nullable<WindowProxyHolder> CanonicalBrowsingContext::GetTopChromeWindow() {
469 RefPtr<CanonicalBrowsingContext> bc = TopCrossChromeBoundary();
470 if (bc->IsChrome()) {
471 return WindowProxyHolder(bc.forget());
473 return nullptr;
476 nsISHistory* CanonicalBrowsingContext::GetSessionHistory() {
477 if (!IsTop()) {
478 return Cast(Top())->GetSessionHistory();
481 // Check GetChildSessionHistory() to make sure that this BrowsingContext has
482 // session history enabled.
483 if (!mSessionHistory && GetChildSessionHistory()) {
484 mSessionHistory = new nsSHistory(this);
487 return mSessionHistory;
490 SessionHistoryEntry* CanonicalBrowsingContext::GetActiveSessionHistoryEntry() {
491 return mActiveEntry;
494 void CanonicalBrowsingContext::SetActiveSessionHistoryEntry(
495 SessionHistoryEntry* aEntry) {
496 mActiveEntry = aEntry;
499 bool CanonicalBrowsingContext::HasHistoryEntry(nsISHEntry* aEntry) {
500 // XXX Should we check also loading entries?
501 return aEntry && mActiveEntry == aEntry;
504 void CanonicalBrowsingContext::SwapHistoryEntries(nsISHEntry* aOldEntry,
505 nsISHEntry* aNewEntry) {
506 // XXX Should we check also loading entries?
507 if (mActiveEntry == aOldEntry) {
508 nsCOMPtr<SessionHistoryEntry> newEntry = do_QueryInterface(aNewEntry);
509 mActiveEntry = newEntry.forget();
513 void CanonicalBrowsingContext::AddLoadingSessionHistoryEntry(
514 uint64_t aLoadId, SessionHistoryEntry* aEntry) {
515 Unused << SetHistoryID(aEntry->DocshellID());
516 mLoadingEntries.AppendElement(LoadingSessionHistoryEntry{aLoadId, aEntry});
519 void CanonicalBrowsingContext::GetLoadingSessionHistoryInfoFromParent(
520 Maybe<LoadingSessionHistoryInfo>& aLoadingInfo) {
521 nsISHistory* shistory = GetSessionHistory();
522 if (!shistory || !GetParent()) {
523 return;
526 SessionHistoryEntry* parentSHE =
527 GetParent()->Canonical()->GetActiveSessionHistoryEntry();
528 if (parentSHE) {
529 int32_t index = -1;
530 for (BrowsingContext* sibling : GetParent()->Children()) {
531 ++index;
532 if (sibling == this) {
533 nsCOMPtr<nsISHEntry> shEntry;
534 parentSHE->GetChildSHEntryIfHasNoDynamicallyAddedChild(
535 index, getter_AddRefs(shEntry));
536 nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(shEntry);
537 if (she) {
538 aLoadingInfo.emplace(she);
539 mLoadingEntries.AppendElement(LoadingSessionHistoryEntry{
540 aLoadingInfo.value().mLoadId, she.get()});
541 Unused << SetHistoryID(she->DocshellID());
543 break;
549 UniquePtr<LoadingSessionHistoryInfo>
550 CanonicalBrowsingContext::CreateLoadingSessionHistoryEntryForLoad(
551 nsDocShellLoadState* aLoadState, nsIChannel* aChannel) {
552 RefPtr<SessionHistoryEntry> entry;
553 const LoadingSessionHistoryInfo* existingLoadingInfo =
554 aLoadState->GetLoadingSessionHistoryInfo();
555 if (existingLoadingInfo) {
556 entry = SessionHistoryEntry::GetByLoadId(existingLoadingInfo->mLoadId);
557 MOZ_LOG(gSHLog, LogLevel::Verbose,
558 ("SHEntry::GetByLoadId(%" PRIu64 ") -> %p",
559 existingLoadingInfo->mLoadId, entry.get()));
560 if (!entry) {
561 return nullptr;
563 Unused << SetHistoryEntryCount(entry->BCHistoryLength());
564 } else if (aLoadState->LoadType() == LOAD_REFRESH &&
565 !ShouldAddEntryForRefresh(aLoadState->URI(),
566 aLoadState->PostDataStream()) &&
567 mActiveEntry) {
568 entry = mActiveEntry;
569 } else {
570 entry = new SessionHistoryEntry(aLoadState, aChannel);
571 if (IsTop()) {
572 // Only top level pages care about Get/SetPersist.
573 entry->SetPersist(
574 nsDocShell::ShouldAddToSessionHistory(aLoadState->URI(), aChannel));
575 } else if (mActiveEntry || !mLoadingEntries.IsEmpty()) {
576 entry->SetIsSubFrame(true);
578 entry->SetDocshellID(GetHistoryID());
579 entry->SetIsDynamicallyAdded(CreatedDynamically());
580 entry->SetForInitialLoad(true);
582 MOZ_DIAGNOSTIC_ASSERT(entry);
584 UniquePtr<LoadingSessionHistoryInfo> loadingInfo;
585 if (existingLoadingInfo) {
586 loadingInfo = MakeUnique<LoadingSessionHistoryInfo>(*existingLoadingInfo);
587 } else {
588 loadingInfo = MakeUnique<LoadingSessionHistoryInfo>(entry);
589 mLoadingEntries.AppendElement(
590 LoadingSessionHistoryEntry{loadingInfo->mLoadId, entry});
593 MOZ_ASSERT(SessionHistoryEntry::GetByLoadId(loadingInfo->mLoadId) == entry);
595 return loadingInfo;
598 UniquePtr<LoadingSessionHistoryInfo>
599 CanonicalBrowsingContext::ReplaceLoadingSessionHistoryEntryForLoad(
600 LoadingSessionHistoryInfo* aInfo, nsIChannel* aNewChannel) {
601 MOZ_ASSERT(aInfo);
602 MOZ_ASSERT(aNewChannel);
604 SessionHistoryInfo newInfo = SessionHistoryInfo(
605 aNewChannel, aInfo->mInfo.LoadType(),
606 aInfo->mInfo.GetPartitionedPrincipalToInherit(), aInfo->mInfo.GetCsp());
608 for (size_t i = 0; i < mLoadingEntries.Length(); ++i) {
609 if (mLoadingEntries[i].mLoadId == aInfo->mLoadId) {
610 RefPtr<SessionHistoryEntry> loadingEntry = mLoadingEntries[i].mEntry;
611 loadingEntry->SetInfo(&newInfo);
613 if (IsTop()) {
614 // Only top level pages care about Get/SetPersist.
615 nsCOMPtr<nsIURI> uri;
616 aNewChannel->GetURI(getter_AddRefs(uri));
617 loadingEntry->SetPersist(
618 nsDocShell::ShouldAddToSessionHistory(uri, aNewChannel));
619 } else {
620 loadingEntry->SetIsSubFrame(aInfo->mInfo.IsSubFrame());
622 loadingEntry->SetDocshellID(GetHistoryID());
623 loadingEntry->SetIsDynamicallyAdded(CreatedDynamically());
624 return MakeUnique<LoadingSessionHistoryInfo>(loadingEntry, aInfo);
627 return nullptr;
630 using PrintPromise = CanonicalBrowsingContext::PrintPromise;
631 #ifdef NS_PRINTING
632 class PrintListenerAdapter final : public nsIWebProgressListener {
633 public:
634 explicit PrintListenerAdapter(PrintPromise::Private* aPromise)
635 : mPromise(aPromise) {}
637 NS_DECL_ISUPPORTS
639 // NS_DECL_NSIWEBPROGRESSLISTENER
640 NS_IMETHOD OnStateChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
641 uint32_t aStateFlags, nsresult aStatus) override {
642 if (aStateFlags & nsIWebProgressListener::STATE_STOP &&
643 aStateFlags & nsIWebProgressListener::STATE_IS_DOCUMENT && mPromise) {
644 mPromise->Resolve(true, __func__);
645 mPromise = nullptr;
647 return NS_OK;
649 NS_IMETHOD OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
650 nsresult aStatus,
651 const char16_t* aMessage) override {
652 if (aStatus != NS_OK && mPromise) {
653 mPromise->Reject(aStatus, __func__);
654 mPromise = nullptr;
656 return NS_OK;
658 NS_IMETHOD OnProgressChange(nsIWebProgress* aWebProgress,
659 nsIRequest* aRequest, int32_t aCurSelfProgress,
660 int32_t aMaxSelfProgress,
661 int32_t aCurTotalProgress,
662 int32_t aMaxTotalProgress) override {
663 return NS_OK;
665 NS_IMETHOD OnLocationChange(nsIWebProgress* aWebProgress,
666 nsIRequest* aRequest, nsIURI* aLocation,
667 uint32_t aFlags) override {
668 return NS_OK;
670 NS_IMETHOD OnSecurityChange(nsIWebProgress* aWebProgress,
671 nsIRequest* aRequest, uint32_t aState) override {
672 return NS_OK;
674 NS_IMETHOD OnContentBlockingEvent(nsIWebProgress* aWebProgress,
675 nsIRequest* aRequest,
676 uint32_t aEvent) override {
677 return NS_OK;
680 private:
681 ~PrintListenerAdapter() = default;
683 RefPtr<PrintPromise::Private> mPromise;
686 NS_IMPL_ISUPPORTS(PrintListenerAdapter, nsIWebProgressListener)
687 #endif
689 already_AddRefed<Promise> CanonicalBrowsingContext::PrintJS(
690 nsIPrintSettings* aPrintSettings, ErrorResult& aRv) {
691 RefPtr<Promise> promise = Promise::Create(GetIncumbentGlobal(), aRv);
692 if (NS_WARN_IF(aRv.Failed())) {
693 return promise.forget();
696 Print(aPrintSettings)
697 ->Then(
698 GetCurrentSerialEventTarget(), __func__,
699 [promise](bool) { promise->MaybeResolveWithUndefined(); },
700 [promise](nsresult aResult) { promise->MaybeReject(aResult); });
701 return promise.forget();
704 RefPtr<PrintPromise> CanonicalBrowsingContext::Print(
705 nsIPrintSettings* aPrintSettings) {
706 #ifndef NS_PRINTING
707 return PrintPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__);
708 #else
710 auto promise = MakeRefPtr<PrintPromise::Private>(__func__);
711 auto listener = MakeRefPtr<PrintListenerAdapter>(promise);
712 if (IsInProcess()) {
713 RefPtr<nsGlobalWindowOuter> outerWindow =
714 nsGlobalWindowOuter::Cast(GetDOMWindow());
715 if (NS_WARN_IF(!outerWindow)) {
716 promise->Reject(NS_ERROR_FAILURE, __func__);
717 return promise;
720 ErrorResult rv;
721 outerWindow->Print(aPrintSettings,
722 /* aRemotePrintJob = */ nullptr, listener,
723 /* aDocShellToCloneInto = */ nullptr,
724 nsGlobalWindowOuter::IsPreview::No,
725 nsGlobalWindowOuter::IsForWindowDotPrint::No,
726 /* aPrintPreviewCallback = */ nullptr, rv);
727 if (rv.Failed()) {
728 promise->Reject(rv.StealNSResult(), __func__);
730 return promise;
733 auto* browserParent = GetBrowserParent();
734 if (NS_WARN_IF(!browserParent)) {
735 promise->Reject(NS_ERROR_FAILURE, __func__);
736 return promise;
739 nsCOMPtr<nsIPrintSettingsService> printSettingsSvc =
740 do_GetService("@mozilla.org/gfx/printsettings-service;1");
741 if (NS_WARN_IF(!printSettingsSvc)) {
742 promise->Reject(NS_ERROR_FAILURE, __func__);
743 return promise;
746 nsresult rv;
747 nsCOMPtr<nsIPrintSettings> printSettings = aPrintSettings;
748 if (!printSettings) {
749 rv =
750 printSettingsSvc->CreateNewPrintSettings(getter_AddRefs(printSettings));
751 if (NS_WARN_IF(NS_FAILED(rv))) {
752 promise->Reject(rv, __func__);
753 return promise;
757 embedding::PrintData printData;
758 rv = printSettingsSvc->SerializeToPrintData(printSettings, &printData);
759 if (NS_WARN_IF(NS_FAILED(rv))) {
760 promise->Reject(rv, __func__);
761 return promise;
764 layout::RemotePrintJobParent* remotePrintJob =
765 new layout::RemotePrintJobParent(printSettings);
766 printData.remotePrintJobParent() =
767 browserParent->Manager()->SendPRemotePrintJobConstructor(remotePrintJob);
769 if (listener) {
770 remotePrintJob->RegisterListener(listener);
773 if (NS_WARN_IF(!browserParent->SendPrint(this, printData))) {
774 promise->Reject(NS_ERROR_FAILURE, __func__);
776 return promise.forget();
777 #endif
780 void CanonicalBrowsingContext::CallOnAllTopDescendants(
781 const std::function<mozilla::CallState(CanonicalBrowsingContext*)>&
782 aCallback) {
783 #ifdef DEBUG
784 RefPtr<CanonicalBrowsingContext> parent = GetParentCrossChromeBoundary();
785 MOZ_ASSERT(!parent, "Should only call on top chrome BC");
786 #endif
788 nsTArray<RefPtr<BrowsingContextGroup>> groups;
789 BrowsingContextGroup::GetAllGroups(groups);
790 for (auto& browsingContextGroup : groups) {
791 for (auto& bc : browsingContextGroup->Toplevels()) {
792 if (bc == this) {
793 // Cannot be a descendent of myself so skip.
794 continue;
797 RefPtr<CanonicalBrowsingContext> top =
798 bc->Canonical()->TopCrossChromeBoundary();
799 if (top == this) {
800 if (aCallback(bc->Canonical()) == CallState::Stop) {
801 return;
808 void CanonicalBrowsingContext::SessionHistoryCommit(
809 uint64_t aLoadId, const nsID& aChangeID, uint32_t aLoadType, bool aPersist,
810 bool aCloneEntryChildren, bool aChannelExpired, uint32_t aCacheKey) {
811 MOZ_LOG(gSHLog, LogLevel::Verbose,
812 ("CanonicalBrowsingContext::SessionHistoryCommit %p %" PRIu64, this,
813 aLoadId));
814 MOZ_ASSERT(aLoadId != UINT64_MAX,
815 "Must not send special about:blank loadinfo to parent.");
816 for (size_t i = 0; i < mLoadingEntries.Length(); ++i) {
817 if (mLoadingEntries[i].mLoadId == aLoadId) {
818 nsSHistory* shistory = static_cast<nsSHistory*>(GetSessionHistory());
819 if (!shistory) {
820 SessionHistoryEntry::RemoveLoadId(aLoadId);
821 mLoadingEntries.RemoveElementAt(i);
822 return;
825 RefPtr<SessionHistoryEntry> newActiveEntry = mLoadingEntries[i].mEntry;
826 if (aCacheKey != 0) {
827 newActiveEntry->SetCacheKey(aCacheKey);
830 if (aChannelExpired) {
831 newActiveEntry->SharedInfo()->mExpired = true;
834 bool loadFromSessionHistory = !newActiveEntry->ForInitialLoad();
835 newActiveEntry->SetForInitialLoad(false);
836 SessionHistoryEntry::RemoveLoadId(aLoadId);
837 mLoadingEntries.RemoveElementAt(i);
839 int32_t indexOfHistoryLoad = -1;
840 if (loadFromSessionHistory) {
841 nsCOMPtr<nsISHEntry> root = nsSHistory::GetRootSHEntry(newActiveEntry);
842 indexOfHistoryLoad = shistory->GetIndexOfEntry(root);
843 if (indexOfHistoryLoad < 0) {
844 // Entry has been removed from the session history.
845 return;
849 CallerWillNotifyHistoryIndexAndLengthChanges caller(shistory);
851 // If there is a name in the new entry, clear the name of all contiguous
852 // entries. This is for https://html.spec.whatwg.org/#history-traversal
853 // Step 4.4.2.
854 nsAutoString nameOfNewEntry;
855 newActiveEntry->GetName(nameOfNewEntry);
856 if (!nameOfNewEntry.IsEmpty()) {
857 nsSHistory::WalkContiguousEntries(
858 newActiveEntry,
859 [](nsISHEntry* aEntry) { aEntry->SetName(EmptyString()); });
862 bool addEntry = ShouldUpdateSessionHistory(aLoadType);
863 if (IsTop()) {
864 if (mActiveEntry && !mActiveEntry->GetFrameLoader()) {
865 bool sharesDocument = true;
866 mActiveEntry->SharesDocumentWith(newActiveEntry, &sharesDocument);
867 if (!sharesDocument) {
868 // If the old page won't be in the bfcache,
869 // clear the dynamic entries.
870 RemoveDynEntriesFromActiveSessionHistoryEntry();
874 if (LOAD_TYPE_HAS_FLAGS(aLoadType,
875 nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY)) {
876 // Replace the current entry with the new entry.
877 int32_t index = shistory->GetIndexForReplace();
879 // If we're trying to replace an inexistant shistory entry then we
880 // should append instead.
881 addEntry = index < 0;
882 if (!addEntry) {
883 shistory->ReplaceEntry(index, newActiveEntry);
885 mActiveEntry = newActiveEntry;
886 } else if (LOAD_TYPE_HAS_FLAGS(
887 aLoadType, nsIWebNavigation::LOAD_FLAGS_IS_REFRESH) &&
888 !ShouldAddEntryForRefresh(newActiveEntry) && mActiveEntry) {
889 addEntry = false;
890 mActiveEntry->ReplaceWith(*newActiveEntry);
891 } else {
892 mActiveEntry = newActiveEntry;
895 if (loadFromSessionHistory) {
896 // XXX Synchronize browsing context tree and session history tree?
897 shistory->InternalSetRequestedIndex(indexOfHistoryLoad);
898 shistory->UpdateIndex();
900 if (IsTop()) {
901 mActiveEntry->SetWireframe(Nothing());
903 } else if (addEntry) {
904 shistory->AddEntry(mActiveEntry, aPersist);
905 shistory->InternalSetRequestedIndex(-1);
907 } else {
908 // FIXME The old implementations adds it to the parent's mLSHE if there
909 // is one, need to figure out if that makes sense here (peterv
910 // doesn't think it would).
911 if (loadFromSessionHistory) {
912 if (mActiveEntry) {
913 // mActiveEntry is null if we're loading iframes from session
914 // history while also parent page is loading from session history.
915 // In that case there isn't anything to sync.
916 mActiveEntry->SyncTreesForSubframeNavigation(newActiveEntry, Top(),
917 this);
919 mActiveEntry = newActiveEntry;
921 shistory->InternalSetRequestedIndex(indexOfHistoryLoad);
922 // FIXME UpdateIndex() here may update index too early (but even the
923 // old implementation seems to have similar issues).
924 shistory->UpdateIndex();
925 } else if (addEntry) {
926 if (mActiveEntry) {
927 if (LOAD_TYPE_HAS_FLAGS(
928 aLoadType, nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY) ||
929 (LOAD_TYPE_HAS_FLAGS(aLoadType,
930 nsIWebNavigation::LOAD_FLAGS_IS_REFRESH) &&
931 !ShouldAddEntryForRefresh(newActiveEntry))) {
932 // FIXME We need to make sure that when we create the info we
933 // make a copy of the shared state.
934 mActiveEntry->ReplaceWith(*newActiveEntry);
935 } else {
936 // AddChildSHEntryHelper does update the index of the session
937 // history!
938 shistory->AddChildSHEntryHelper(mActiveEntry, newActiveEntry,
939 Top(), aCloneEntryChildren);
940 mActiveEntry = newActiveEntry;
942 } else {
943 SessionHistoryEntry* parentEntry = GetParent()->mActiveEntry;
944 // XXX What should happen if parent doesn't have mActiveEntry?
945 // Or can that even happen ever?
946 if (parentEntry) {
947 mActiveEntry = newActiveEntry;
948 // FIXME Using IsInProcess for aUseRemoteSubframes isn't quite
949 // right, but aUseRemoteSubframes should be going away.
950 parentEntry->AddChild(
951 mActiveEntry,
952 CreatedDynamically() ? -1 : GetParent()->IndexOf(this),
953 IsInProcess());
956 shistory->InternalSetRequestedIndex(-1);
960 ResetSHEntryHasUserInteractionCache();
962 HistoryCommitIndexAndLength(aChangeID, caller);
964 shistory->LogHistory();
966 return;
968 // XXX Should the loading entries before [i] be removed?
970 // FIXME Should we throw an error if we don't find an entry for
971 // aSessionHistoryEntryId?
974 static already_AddRefed<nsDocShellLoadState> CreateLoadInfo(
975 SessionHistoryEntry* aEntry) {
976 const SessionHistoryInfo& info = aEntry->Info();
977 RefPtr<nsDocShellLoadState> loadState(new nsDocShellLoadState(info.GetURI()));
978 info.FillLoadInfo(*loadState);
979 UniquePtr<LoadingSessionHistoryInfo> loadingInfo;
980 loadingInfo = MakeUnique<LoadingSessionHistoryInfo>(aEntry);
981 loadState->SetLoadingSessionHistoryInfo(std::move(loadingInfo));
983 return loadState.forget();
986 void CanonicalBrowsingContext::NotifyOnHistoryReload(
987 bool aForceReload, bool& aCanReload,
988 Maybe<RefPtr<nsDocShellLoadState>>& aLoadState,
989 Maybe<bool>& aReloadActiveEntry) {
990 MOZ_DIAGNOSTIC_ASSERT(!aLoadState);
992 aCanReload = true;
993 nsISHistory* shistory = GetSessionHistory();
994 NS_ENSURE_TRUE_VOID(shistory);
996 shistory->NotifyOnHistoryReload(&aCanReload);
997 if (!aCanReload) {
998 return;
1001 if (mActiveEntry) {
1002 aLoadState.emplace(CreateLoadInfo(mActiveEntry));
1003 aReloadActiveEntry.emplace(true);
1004 if (aForceReload) {
1005 shistory->RemoveFrameEntries(mActiveEntry);
1007 } else if (!mLoadingEntries.IsEmpty()) {
1008 const LoadingSessionHistoryEntry& loadingEntry =
1009 mLoadingEntries.LastElement();
1010 aLoadState.emplace(CreateLoadInfo(loadingEntry.mEntry));
1011 aReloadActiveEntry.emplace(false);
1012 if (aForceReload) {
1013 SessionHistoryEntry* entry =
1014 SessionHistoryEntry::GetByLoadId(loadingEntry.mLoadId);
1015 if (entry) {
1016 shistory->RemoveFrameEntries(entry);
1021 if (aLoadState) {
1022 // Use 0 as the offset, since aLoadState will be be used for reload.
1023 aLoadState.ref()->SetLoadIsFromSessionHistory(0,
1024 aReloadActiveEntry.value());
1026 // If we don't have an active entry and we don't have a loading entry then
1027 // the nsDocShell will create a load state based on its document.
1030 void CanonicalBrowsingContext::SetActiveSessionHistoryEntry(
1031 const Maybe<nsPoint>& aPreviousScrollPos, SessionHistoryInfo* aInfo,
1032 uint32_t aLoadType, uint32_t aUpdatedCacheKey, const nsID& aChangeID) {
1033 nsISHistory* shistory = GetSessionHistory();
1034 if (!shistory) {
1035 return;
1037 CallerWillNotifyHistoryIndexAndLengthChanges caller(shistory);
1039 RefPtr<SessionHistoryEntry> oldActiveEntry = mActiveEntry;
1040 if (aPreviousScrollPos.isSome() && oldActiveEntry) {
1041 oldActiveEntry->SetScrollPosition(aPreviousScrollPos.ref().x,
1042 aPreviousScrollPos.ref().y);
1044 mActiveEntry = new SessionHistoryEntry(aInfo);
1045 mActiveEntry->SetDocshellID(GetHistoryID());
1046 mActiveEntry->AdoptBFCacheEntry(oldActiveEntry);
1047 if (aUpdatedCacheKey != 0) {
1048 mActiveEntry->SharedInfo()->mCacheKey = aUpdatedCacheKey;
1051 if (IsTop()) {
1052 Maybe<int32_t> previousEntryIndex, loadedEntryIndex;
1053 shistory->AddToRootSessionHistory(
1054 true, oldActiveEntry, this, mActiveEntry, aLoadType,
1055 nsDocShell::ShouldAddToSessionHistory(aInfo->GetURI(), nullptr),
1056 &previousEntryIndex, &loadedEntryIndex);
1057 } else {
1058 if (oldActiveEntry) {
1059 shistory->AddChildSHEntryHelper(oldActiveEntry, mActiveEntry, Top(),
1060 true);
1061 } else if (GetParent() && GetParent()->mActiveEntry) {
1062 GetParent()->mActiveEntry->AddChild(
1063 mActiveEntry, CreatedDynamically() ? -1 : GetParent()->IndexOf(this),
1064 UseRemoteSubframes());
1068 ResetSHEntryHasUserInteractionCache();
1070 shistory->InternalSetRequestedIndex(-1);
1072 // FIXME Need to do the equivalent of EvictContentViewersOrReplaceEntry.
1073 HistoryCommitIndexAndLength(aChangeID, caller);
1075 static_cast<nsSHistory*>(shistory)->LogHistory();
1078 void CanonicalBrowsingContext::ReplaceActiveSessionHistoryEntry(
1079 SessionHistoryInfo* aInfo) {
1080 if (!mActiveEntry) {
1081 return;
1084 mActiveEntry->SetInfo(aInfo);
1085 // Notify children of the update
1086 nsSHistory* shistory = static_cast<nsSHistory*>(GetSessionHistory());
1087 if (shistory) {
1088 shistory->NotifyOnHistoryReplaceEntry();
1089 shistory->UpdateRootBrowsingContextState();
1092 ResetSHEntryHasUserInteractionCache();
1094 if (IsTop()) {
1095 mActiveEntry->SetWireframe(Nothing());
1098 // FIXME Need to do the equivalent of EvictContentViewersOrReplaceEntry.
1101 void CanonicalBrowsingContext::RemoveDynEntriesFromActiveSessionHistoryEntry() {
1102 nsISHistory* shistory = GetSessionHistory();
1103 // In theory shistory can be null here if the method is called right after
1104 // CanonicalBrowsingContext::ReplacedBy call.
1105 NS_ENSURE_TRUE_VOID(shistory);
1106 nsCOMPtr<nsISHEntry> root = nsSHistory::GetRootSHEntry(mActiveEntry);
1107 shistory->RemoveDynEntries(shistory->GetIndexOfEntry(root), mActiveEntry);
1110 void CanonicalBrowsingContext::RemoveFromSessionHistory(const nsID& aChangeID) {
1111 nsSHistory* shistory = static_cast<nsSHistory*>(GetSessionHistory());
1112 if (shistory) {
1113 CallerWillNotifyHistoryIndexAndLengthChanges caller(shistory);
1114 nsCOMPtr<nsISHEntry> root = nsSHistory::GetRootSHEntry(mActiveEntry);
1115 bool didRemove;
1116 AutoTArray<nsID, 16> ids({GetHistoryID()});
1117 shistory->RemoveEntries(ids, shistory->GetIndexOfEntry(root), &didRemove);
1118 if (didRemove) {
1119 RefPtr<BrowsingContext> rootBC = shistory->GetBrowsingContext();
1120 if (rootBC) {
1121 if (!rootBC->IsInProcess()) {
1122 if (ContentParent* cp = rootBC->Canonical()->GetContentParent()) {
1123 Unused << cp->SendDispatchLocationChangeEvent(rootBC);
1125 } else if (rootBC->GetDocShell()) {
1126 rootBC->GetDocShell()->DispatchLocationChangeEvent();
1130 HistoryCommitIndexAndLength(aChangeID, caller);
1134 void CanonicalBrowsingContext::HistoryGo(
1135 int32_t aOffset, uint64_t aHistoryEpoch, bool aRequireUserInteraction,
1136 bool aUserActivation, Maybe<ContentParentId> aContentId,
1137 std::function<void(int32_t&&)>&& aResolver) {
1138 if (aRequireUserInteraction && aOffset != -1 && aOffset != 1) {
1139 NS_ERROR(
1140 "aRequireUserInteraction may only be used with an offset of -1 or 1");
1141 return;
1144 nsSHistory* shistory = static_cast<nsSHistory*>(GetSessionHistory());
1145 if (!shistory) {
1146 return;
1149 CheckedInt<int32_t> index = shistory->GetRequestedIndex() >= 0
1150 ? shistory->GetRequestedIndex()
1151 : shistory->Index();
1152 MOZ_LOG(gSHLog, LogLevel::Debug,
1153 ("HistoryGo(%d->%d) epoch %" PRIu64 "/id %" PRIu64, aOffset,
1154 (index + aOffset).value(), aHistoryEpoch,
1155 (uint64_t)(aContentId.isSome() ? aContentId.value() : 0)));
1157 while (true) {
1158 index += aOffset;
1159 if (!index.isValid()) {
1160 MOZ_LOG(gSHLog, LogLevel::Debug, ("Invalid index"));
1161 return;
1164 // Check for user interaction if desired, except for the first and last
1165 // history entries. We compare with >= to account for the case where
1166 // aOffset >= length.
1167 if (!aRequireUserInteraction || index.value() >= shistory->Length() - 1 ||
1168 index.value() <= 0) {
1169 break;
1171 if (shistory->HasUserInteractionAtIndex(index.value())) {
1172 break;
1176 // Implement aborting additional history navigations from within the same
1177 // event spin of the content process.
1179 uint64_t epoch;
1180 bool sameEpoch = false;
1181 Maybe<ContentParentId> id;
1182 shistory->GetEpoch(epoch, id);
1184 if (aContentId == id && epoch >= aHistoryEpoch) {
1185 sameEpoch = true;
1186 MOZ_LOG(gSHLog, LogLevel::Debug, ("Same epoch/id"));
1188 // Don't update the epoch until we know if the target index is valid
1190 // GoToIndex checks that index is >= 0 and < length.
1191 nsTArray<nsSHistory::LoadEntryResult> loadResults;
1192 nsresult rv = shistory->GotoIndex(index.value(), loadResults, sameEpoch,
1193 aOffset == 0, aUserActivation);
1194 if (NS_FAILED(rv)) {
1195 MOZ_LOG(gSHLog, LogLevel::Debug,
1196 ("Dropping HistoryGo - bad index or same epoch (not in same doc)"));
1197 return;
1199 if (epoch < aHistoryEpoch || aContentId != id) {
1200 MOZ_LOG(gSHLog, LogLevel::Debug, ("Set epoch"));
1201 shistory->SetEpoch(aHistoryEpoch, aContentId);
1203 aResolver(shistory->GetRequestedIndex());
1204 nsSHistory::LoadURIs(loadResults);
1207 JSObject* CanonicalBrowsingContext::WrapObject(
1208 JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
1209 return CanonicalBrowsingContext_Binding::Wrap(aCx, this, aGivenProto);
1212 void CanonicalBrowsingContext::DispatchWheelZoomChange(bool aIncrease) {
1213 Element* element = Top()->GetEmbedderElement();
1214 if (!element) {
1215 return;
1218 auto event = aIncrease ? u"DoZoomEnlargeBy10"_ns : u"DoZoomReduceBy10"_ns;
1219 auto dispatcher = MakeRefPtr<AsyncEventDispatcher>(
1220 element, event, CanBubble::eYes, ChromeOnlyDispatch::eYes);
1221 dispatcher->PostDOMEvent();
1224 void CanonicalBrowsingContext::CanonicalDiscard() {
1225 if (mTabMediaController) {
1226 mTabMediaController->Shutdown();
1227 mTabMediaController = nullptr;
1230 if (mCurrentLoad) {
1231 mCurrentLoad->Cancel(NS_BINDING_ABORTED);
1234 if (mWebProgress) {
1235 RefPtr<BrowsingContextWebProgress> progress = mWebProgress;
1236 progress->ContextDiscarded();
1239 if (IsTop()) {
1240 BackgroundSessionStorageManager::RemoveManager(Id());
1243 CancelSessionStoreUpdate();
1245 if (UsePrivateBrowsing() && EverAttached() && IsContent()) {
1246 DecreasePrivateCount();
1250 void CanonicalBrowsingContext::CanonicalAttach() {
1251 if (UsePrivateBrowsing() && IsContent()) {
1252 IncreasePrivateCount();
1256 void CanonicalBrowsingContext::AddPendingDiscard() {
1257 MOZ_ASSERT(!mFullyDiscarded);
1258 mPendingDiscards++;
1261 void CanonicalBrowsingContext::RemovePendingDiscard() {
1262 mPendingDiscards--;
1263 if (!mPendingDiscards) {
1264 mFullyDiscarded = true;
1265 auto listeners = std::move(mFullyDiscardedListeners);
1266 for (const auto& listener : listeners) {
1267 listener(Id());
1272 void CanonicalBrowsingContext::AddFinalDiscardListener(
1273 std::function<void(uint64_t)>&& aListener) {
1274 if (mFullyDiscarded) {
1275 aListener(Id());
1276 return;
1278 mFullyDiscardedListeners.AppendElement(std::move(aListener));
1281 net::EarlyHintsService* CanonicalBrowsingContext::GetEarlyHintsService() {
1282 return &mEarlyHintsService;
1285 void CanonicalBrowsingContext::AdjustPrivateBrowsingCount(
1286 bool aPrivateBrowsing) {
1287 if (IsDiscarded() || !EverAttached() || IsChrome()) {
1288 return;
1291 MOZ_DIAGNOSTIC_ASSERT(aPrivateBrowsing == UsePrivateBrowsing());
1292 if (aPrivateBrowsing) {
1293 IncreasePrivateCount();
1294 } else {
1295 DecreasePrivateCount();
1299 void CanonicalBrowsingContext::NotifyStartDelayedAutoplayMedia() {
1300 WindowContext* windowContext = GetCurrentWindowContext();
1301 if (!windowContext) {
1302 return;
1305 // As this function would only be called when user click the play icon on the
1306 // tab bar. That's clear user intent to play, so gesture activate the window
1307 // context so that the block-autoplay logic allows the media to autoplay.
1308 windowContext->NotifyUserGestureActivation();
1309 AUTOPLAY_LOG("NotifyStartDelayedAutoplayMedia for chrome bc 0x%08" PRIx64,
1310 Id());
1311 StartDelayedAutoplayMediaComponents();
1312 // Notfiy all content browsing contexts which are related with the canonical
1313 // browsing content tree to start delayed autoplay media.
1315 Group()->EachParent([&](ContentParent* aParent) {
1316 Unused << aParent->SendStartDelayedAutoplayMediaComponents(this);
1320 void CanonicalBrowsingContext::NotifyMediaMutedChanged(bool aMuted,
1321 ErrorResult& aRv) {
1322 MOZ_ASSERT(!GetParent(),
1323 "Notify media mute change on non top-level context!");
1324 SetMuted(aMuted, aRv);
1327 uint32_t CanonicalBrowsingContext::CountSiteOrigins(
1328 GlobalObject& aGlobal,
1329 const Sequence<OwningNonNull<BrowsingContext>>& aRoots) {
1330 nsTHashSet<nsCString> uniqueSiteOrigins;
1332 for (const auto& root : aRoots) {
1333 root->PreOrderWalk([&](BrowsingContext* aContext) {
1334 WindowGlobalParent* windowGlobalParent =
1335 aContext->Canonical()->GetCurrentWindowGlobal();
1336 if (windowGlobalParent) {
1337 nsIPrincipal* documentPrincipal =
1338 windowGlobalParent->DocumentPrincipal();
1340 bool isContentPrincipal = documentPrincipal->GetIsContentPrincipal();
1341 if (isContentPrincipal) {
1342 nsCString siteOrigin;
1343 documentPrincipal->GetSiteOrigin(siteOrigin);
1344 uniqueSiteOrigins.Insert(siteOrigin);
1350 return uniqueSiteOrigins.Count();
1353 void CanonicalBrowsingContext::UpdateMediaControlAction(
1354 const MediaControlAction& aAction) {
1355 if (IsDiscarded()) {
1356 return;
1358 ContentMediaControlKeyHandler::HandleMediaControlAction(this, aAction);
1359 Group()->EachParent([&](ContentParent* aParent) {
1360 Unused << aParent->SendUpdateMediaControlAction(this, aAction);
1364 void CanonicalBrowsingContext::LoadURI(const nsAString& aURI,
1365 const LoadURIOptions& aOptions,
1366 ErrorResult& aError) {
1367 RefPtr<nsDocShellLoadState> loadState;
1368 nsresult rv = nsDocShellLoadState::CreateFromLoadURIOptions(
1369 this, aURI, aOptions, getter_AddRefs(loadState));
1371 if (rv == NS_ERROR_MALFORMED_URI) {
1372 DisplayLoadError(aURI);
1373 return;
1376 if (NS_FAILED(rv)) {
1377 aError.Throw(rv);
1378 return;
1381 LoadURI(loadState, true);
1384 void CanonicalBrowsingContext::GoBack(
1385 const Optional<int32_t>& aCancelContentJSEpoch,
1386 bool aRequireUserInteraction, bool aUserActivation) {
1387 if (IsDiscarded()) {
1388 return;
1391 // Stop any known network loads if necessary.
1392 if (mCurrentLoad) {
1393 mCurrentLoad->Cancel(NS_BINDING_CANCELLED_OLD_LOAD);
1396 if (nsDocShell* docShell = nsDocShell::Cast(GetDocShell())) {
1397 if (aCancelContentJSEpoch.WasPassed()) {
1398 docShell->SetCancelContentJSEpoch(aCancelContentJSEpoch.Value());
1400 docShell->GoBack(aRequireUserInteraction, aUserActivation);
1401 } else if (ContentParent* cp = GetContentParent()) {
1402 Maybe<int32_t> cancelContentJSEpoch;
1403 if (aCancelContentJSEpoch.WasPassed()) {
1404 cancelContentJSEpoch = Some(aCancelContentJSEpoch.Value());
1406 Unused << cp->SendGoBack(this, cancelContentJSEpoch,
1407 aRequireUserInteraction, aUserActivation);
1410 void CanonicalBrowsingContext::GoForward(
1411 const Optional<int32_t>& aCancelContentJSEpoch,
1412 bool aRequireUserInteraction, bool aUserActivation) {
1413 if (IsDiscarded()) {
1414 return;
1417 // Stop any known network loads if necessary.
1418 if (mCurrentLoad) {
1419 mCurrentLoad->Cancel(NS_BINDING_CANCELLED_OLD_LOAD);
1422 if (auto* docShell = nsDocShell::Cast(GetDocShell())) {
1423 if (aCancelContentJSEpoch.WasPassed()) {
1424 docShell->SetCancelContentJSEpoch(aCancelContentJSEpoch.Value());
1426 docShell->GoForward(aRequireUserInteraction, aUserActivation);
1427 } else if (ContentParent* cp = GetContentParent()) {
1428 Maybe<int32_t> cancelContentJSEpoch;
1429 if (aCancelContentJSEpoch.WasPassed()) {
1430 cancelContentJSEpoch.emplace(aCancelContentJSEpoch.Value());
1432 Unused << cp->SendGoForward(this, cancelContentJSEpoch,
1433 aRequireUserInteraction, aUserActivation);
1436 void CanonicalBrowsingContext::GoToIndex(
1437 int32_t aIndex, const Optional<int32_t>& aCancelContentJSEpoch,
1438 bool aUserActivation) {
1439 if (IsDiscarded()) {
1440 return;
1443 // Stop any known network loads if necessary.
1444 if (mCurrentLoad) {
1445 mCurrentLoad->Cancel(NS_BINDING_CANCELLED_OLD_LOAD);
1448 if (auto* docShell = nsDocShell::Cast(GetDocShell())) {
1449 if (aCancelContentJSEpoch.WasPassed()) {
1450 docShell->SetCancelContentJSEpoch(aCancelContentJSEpoch.Value());
1452 docShell->GotoIndex(aIndex, aUserActivation);
1453 } else if (ContentParent* cp = GetContentParent()) {
1454 Maybe<int32_t> cancelContentJSEpoch;
1455 if (aCancelContentJSEpoch.WasPassed()) {
1456 cancelContentJSEpoch.emplace(aCancelContentJSEpoch.Value());
1458 Unused << cp->SendGoToIndex(this, aIndex, cancelContentJSEpoch,
1459 aUserActivation);
1462 void CanonicalBrowsingContext::Reload(uint32_t aReloadFlags) {
1463 if (IsDiscarded()) {
1464 return;
1467 // Stop any known network loads if necessary.
1468 if (mCurrentLoad) {
1469 mCurrentLoad->Cancel(NS_BINDING_CANCELLED_OLD_LOAD);
1472 if (auto* docShell = nsDocShell::Cast(GetDocShell())) {
1473 docShell->Reload(aReloadFlags);
1474 } else if (ContentParent* cp = GetContentParent()) {
1475 Unused << cp->SendReload(this, aReloadFlags);
1479 void CanonicalBrowsingContext::Stop(uint32_t aStopFlags) {
1480 if (IsDiscarded()) {
1481 return;
1484 // Stop any known network loads if necessary.
1485 if (mCurrentLoad && (aStopFlags & nsIWebNavigation::STOP_NETWORK)) {
1486 mCurrentLoad->Cancel(NS_BINDING_ABORTED);
1489 // Ask the docshell to stop to handle loads that haven't
1490 // yet reached here, as well as non-network activity.
1491 if (auto* docShell = nsDocShell::Cast(GetDocShell())) {
1492 docShell->Stop(aStopFlags);
1493 } else if (ContentParent* cp = GetContentParent()) {
1494 Unused << cp->SendStopLoad(this, aStopFlags);
1498 void CanonicalBrowsingContext::PendingRemotenessChange::ProcessLaunched() {
1499 if (!mPromise) {
1500 return;
1503 if (mContentParent) {
1504 // If our new content process is still unloading from a previous process
1505 // switch, wait for that unload to complete before continuing.
1506 auto found = mTarget->FindUnloadingHost(mContentParent->ChildID());
1507 if (found != mTarget->mUnloadingHosts.end()) {
1508 found->mCallbacks.AppendElement(
1509 [self = RefPtr{this}]() { self->ProcessReady(); });
1510 return;
1514 ProcessReady();
1517 void CanonicalBrowsingContext::PendingRemotenessChange::ProcessReady() {
1518 if (!mPromise) {
1519 return;
1522 MOZ_ASSERT(!mProcessReady);
1523 mProcessReady = true;
1524 MaybeFinish();
1527 void CanonicalBrowsingContext::PendingRemotenessChange::MaybeFinish() {
1528 if (!mPromise) {
1529 return;
1532 if (!mProcessReady || mWaitingForPrepareToChange) {
1533 return;
1536 // If this BrowsingContext is embedded within the parent process, perform the
1537 // process switch directly.
1538 nsresult rv = mTarget->IsTopContent() ? FinishTopContent() : FinishSubframe();
1539 if (NS_FAILED(rv)) {
1540 NS_WARNING("Error finishing PendingRemotenessChange!");
1541 Cancel(rv);
1542 } else {
1543 Clear();
1547 // Logic for finishing a toplevel process change embedded within the parent
1548 // process. Due to frontend integration the logic differs substantially from
1549 // subframe process switches, and is handled separately.
1550 nsresult CanonicalBrowsingContext::PendingRemotenessChange::FinishTopContent() {
1551 MOZ_DIAGNOSTIC_ASSERT(mTarget->IsTop(),
1552 "We shouldn't be trying to change the remoteness of "
1553 "non-remote iframes");
1555 // Abort if our ContentParent died while process switching.
1556 if (mContentParent && NS_WARN_IF(mContentParent->IsDead())) {
1557 return NS_ERROR_FAILURE;
1560 // While process switching, we need to check if any of our ancestors are
1561 // discarded or no longer current, in which case the process switch needs to
1562 // be aborted.
1563 RefPtr<CanonicalBrowsingContext> target(mTarget);
1564 if (target->IsDiscarded() || !target->AncestorsAreCurrent()) {
1565 return NS_ERROR_FAILURE;
1568 Element* browserElement = target->GetEmbedderElement();
1569 if (!browserElement) {
1570 return NS_ERROR_FAILURE;
1573 nsCOMPtr<nsIBrowser> browser = browserElement->AsBrowser();
1574 if (!browser) {
1575 return NS_ERROR_FAILURE;
1578 RefPtr<nsFrameLoaderOwner> frameLoaderOwner = do_QueryObject(browserElement);
1579 MOZ_RELEASE_ASSERT(frameLoaderOwner,
1580 "embedder browser must be nsFrameLoaderOwner");
1582 // If we're process switching a browsing context in private browsing
1583 // mode we might decrease the private browsing count to '0', which
1584 // would make us fire "last-pb-context-exited" and drop the private
1585 // session. To prevent that we artificially increment the number of
1586 // private browsing contexts with '1' until the process switch is done.
1587 bool usePrivateBrowsing = mTarget->UsePrivateBrowsing();
1588 if (usePrivateBrowsing) {
1589 IncreasePrivateCount();
1592 auto restorePrivateCount = MakeScopeExit([usePrivateBrowsing]() {
1593 if (usePrivateBrowsing) {
1594 DecreasePrivateCount();
1598 // Tell frontend code that this browser element is about to change process.
1599 nsresult rv = browser->BeforeChangeRemoteness();
1600 if (NS_FAILED(rv)) {
1601 return rv;
1604 // Some frontend code checks the value of the `remote` attribute on the
1605 // browser to determine if it is remote, so update the value.
1606 browserElement->SetAttr(kNameSpaceID_None, nsGkAtoms::remote,
1607 mContentParent ? u"true"_ns : u"false"_ns,
1608 /* notify */ true);
1610 // The process has been created, hand off to nsFrameLoaderOwner to finish
1611 // the process switch.
1612 ErrorResult error;
1613 frameLoaderOwner->ChangeRemotenessToProcess(mContentParent, mOptions,
1614 mSpecificGroup, error);
1615 if (error.Failed()) {
1616 return error.StealNSResult();
1619 // Tell frontend the load is done.
1620 bool loadResumed = false;
1621 rv = browser->FinishChangeRemoteness(mPendingSwitchId, &loadResumed);
1622 if (NS_WARN_IF(NS_FAILED(rv))) {
1623 return rv;
1626 // We did it! The process switch is complete.
1627 RefPtr<nsFrameLoader> frameLoader = frameLoaderOwner->GetFrameLoader();
1628 RefPtr<BrowserParent> newBrowser = frameLoader->GetBrowserParent();
1629 if (!newBrowser) {
1630 if (mContentParent) {
1631 // Failed to create the BrowserParent somehow! Abort the process switch
1632 // attempt.
1633 return NS_ERROR_UNEXPECTED;
1636 if (!loadResumed) {
1637 RefPtr<nsDocShell> newDocShell = frameLoader->GetDocShell(error);
1638 if (error.Failed()) {
1639 return error.StealNSResult();
1642 rv = newDocShell->ResumeRedirectedLoad(mPendingSwitchId,
1643 /* aHistoryIndex */ -1);
1644 if (NS_FAILED(rv)) {
1645 return rv;
1648 } else if (!loadResumed) {
1649 newBrowser->ResumeLoad(mPendingSwitchId);
1652 mPromise->Resolve(newBrowser, __func__);
1653 return NS_OK;
1656 nsresult CanonicalBrowsingContext::PendingRemotenessChange::FinishSubframe() {
1657 MOZ_DIAGNOSTIC_ASSERT(!mOptions.mReplaceBrowsingContext,
1658 "Cannot replace BC for subframe");
1659 MOZ_DIAGNOSTIC_ASSERT(!mTarget->IsTop());
1661 // While process switching, we need to check if any of our ancestors are
1662 // discarded or no longer current, in which case the process switch needs to
1663 // be aborted.
1664 RefPtr<CanonicalBrowsingContext> target(mTarget);
1665 if (target->IsDiscarded() || !target->AncestorsAreCurrent()) {
1666 return NS_ERROR_FAILURE;
1669 if (NS_WARN_IF(!mContentParent)) {
1670 return NS_ERROR_FAILURE;
1673 RefPtr<WindowGlobalParent> embedderWindow = target->GetParentWindowContext();
1674 if (NS_WARN_IF(!embedderWindow) || NS_WARN_IF(!embedderWindow->CanSend())) {
1675 return NS_ERROR_FAILURE;
1678 RefPtr<BrowserParent> embedderBrowser = embedderWindow->GetBrowserParent();
1679 if (NS_WARN_IF(!embedderBrowser)) {
1680 return NS_ERROR_FAILURE;
1683 // If we're creating a new remote browser, and the host process is already
1684 // dead, abort the process switch.
1685 if (mContentParent != embedderBrowser->Manager() &&
1686 NS_WARN_IF(mContentParent->IsDead())) {
1687 return NS_ERROR_FAILURE;
1690 RefPtr<BrowserParent> oldBrowser = target->GetBrowserParent();
1691 target->SetCurrentBrowserParent(nullptr);
1693 // If we were in a remote frame, trigger unloading of the remote window. The
1694 // previous BrowserParent is registered in `mUnloadingHosts` and will only be
1695 // cleared when the BrowserParent is fully destroyed.
1696 bool wasRemote = oldBrowser && oldBrowser->GetBrowsingContext() == target;
1697 if (wasRemote) {
1698 MOZ_DIAGNOSTIC_ASSERT(oldBrowser != embedderBrowser);
1699 MOZ_DIAGNOSTIC_ASSERT(oldBrowser->IsDestroyed() ||
1700 oldBrowser->GetBrowserBridgeParent());
1702 // `oldBrowser` will clear the `UnloadingHost` status once the actor has
1703 // been destroyed.
1704 if (oldBrowser->CanSend()) {
1705 target->StartUnloadingHost(oldBrowser->Manager()->ChildID());
1706 Unused << oldBrowser->SendWillChangeProcess();
1707 oldBrowser->Destroy();
1711 // Update which process is considered the current owner
1712 target->SetOwnerProcessId(mContentParent->ChildID());
1714 // If we're switching from remote to local, we don't need to create a
1715 // BrowserBridge, and can instead perform the switch directly.
1716 if (mContentParent == embedderBrowser->Manager()) {
1717 MOZ_DIAGNOSTIC_ASSERT(
1718 mPendingSwitchId,
1719 "We always have a PendingSwitchId, except for print-preview loads, "
1720 "which will never perform a process-switch to being in-process with "
1721 "their embedder");
1722 MOZ_DIAGNOSTIC_ASSERT(wasRemote,
1723 "Attempt to process-switch from local to local?");
1725 target->SetCurrentBrowserParent(embedderBrowser);
1726 Unused << embedderWindow->SendMakeFrameLocal(target, mPendingSwitchId);
1727 mPromise->Resolve(embedderBrowser, __func__);
1728 return NS_OK;
1731 // The BrowsingContext will be remote, either as an already-remote frame
1732 // changing processes, or as a local frame becoming remote. Construct a new
1733 // BrowserBridgeParent to host the remote content.
1734 target->SetCurrentBrowserParent(nullptr);
1736 MOZ_DIAGNOSTIC_ASSERT(target->UseRemoteTabs() && target->UseRemoteSubframes(),
1737 "Not supported without fission");
1738 uint32_t chromeFlags = nsIWebBrowserChrome::CHROME_REMOTE_WINDOW |
1739 nsIWebBrowserChrome::CHROME_FISSION_WINDOW;
1740 if (target->UsePrivateBrowsing()) {
1741 chromeFlags |= nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW;
1744 nsCOMPtr<nsIPrincipal> initialPrincipal =
1745 NullPrincipal::CreateWithInheritedAttributes(
1746 target->OriginAttributesRef(),
1747 /* isFirstParty */ false);
1748 WindowGlobalInit windowInit =
1749 WindowGlobalActor::AboutBlankInitializer(target, initialPrincipal);
1751 // Create and initialize our new BrowserBridgeParent.
1752 TabId tabId(nsContentUtils::GenerateTabId());
1753 RefPtr<BrowserBridgeParent> bridge = new BrowserBridgeParent();
1754 nsresult rv = bridge->InitWithProcess(embedderBrowser, mContentParent,
1755 windowInit, chromeFlags, tabId);
1756 if (NS_WARN_IF(NS_FAILED(rv))) {
1757 // If we've already destroyed our previous document, make a best-effort
1758 // attempt to recover from this failure and show the crashed tab UI. We only
1759 // do this in the previously-remote case, as previously in-process frames
1760 // will have their navigation cancelled, and will remain visible.
1761 if (wasRemote) {
1762 target->ShowSubframeCrashedUI(oldBrowser->GetBrowserBridgeParent());
1764 return rv;
1767 // Tell the embedder process a remoteness change is in-process. When this is
1768 // acknowledged, reset the in-flight ID if it used to be an in-process load.
1769 RefPtr<BrowserParent> newBrowser = bridge->GetBrowserParent();
1771 // If we weren't remote, mark our embedder window browser as unloading until
1772 // our embedder process has acked our MakeFrameRemote message.
1773 Maybe<uint64_t> clearChildID;
1774 if (!wasRemote) {
1775 clearChildID = Some(embedderBrowser->Manager()->ChildID());
1776 target->StartUnloadingHost(*clearChildID);
1778 auto callback = [target, clearChildID](auto&&) {
1779 if (clearChildID) {
1780 target->ClearUnloadingHost(*clearChildID);
1784 ManagedEndpoint<PBrowserBridgeChild> endpoint =
1785 embedderBrowser->OpenPBrowserBridgeEndpoint(bridge);
1786 MOZ_DIAGNOSTIC_ASSERT(endpoint.IsValid());
1787 embedderWindow->SendMakeFrameRemote(target, std::move(endpoint), tabId,
1788 newBrowser->GetLayersId(), callback,
1789 callback);
1792 // Resume the pending load in our new process.
1793 if (mPendingSwitchId) {
1794 newBrowser->ResumeLoad(mPendingSwitchId);
1797 // We did it! The process switch is complete.
1798 mPromise->Resolve(newBrowser, __func__);
1799 return NS_OK;
1802 void CanonicalBrowsingContext::PendingRemotenessChange::Cancel(nsresult aRv) {
1803 if (!mPromise) {
1804 return;
1807 mPromise->Reject(aRv, __func__);
1808 Clear();
1811 void CanonicalBrowsingContext::PendingRemotenessChange::Clear() {
1812 // Make sure we don't die while we're doing cleanup.
1813 RefPtr<PendingRemotenessChange> kungFuDeathGrip(this);
1814 if (mTarget) {
1815 MOZ_DIAGNOSTIC_ASSERT(mTarget->mPendingRemotenessChange == this);
1816 mTarget->mPendingRemotenessChange = nullptr;
1819 // When this PendingRemotenessChange was created, it was given a
1820 // `mContentParent`.
1821 if (mContentParent) {
1822 mContentParent->RemoveKeepAlive();
1823 mContentParent = nullptr;
1826 // If we were given a specific group, stop keeping that group alive manually.
1827 if (mSpecificGroup) {
1828 mSpecificGroup->RemoveKeepAlive();
1829 mSpecificGroup = nullptr;
1832 mPromise = nullptr;
1833 mTarget = nullptr;
1836 CanonicalBrowsingContext::PendingRemotenessChange::PendingRemotenessChange(
1837 CanonicalBrowsingContext* aTarget, RemotenessPromise::Private* aPromise,
1838 uint64_t aPendingSwitchId, const NavigationIsolationOptions& aOptions)
1839 : mTarget(aTarget),
1840 mPromise(aPromise),
1841 mPendingSwitchId(aPendingSwitchId),
1842 mOptions(aOptions) {}
1844 CanonicalBrowsingContext::PendingRemotenessChange::~PendingRemotenessChange() {
1845 MOZ_ASSERT(!mPromise && !mTarget && !mContentParent && !mSpecificGroup,
1846 "should've already been Cancel() or Complete()-ed");
1849 BrowserParent* CanonicalBrowsingContext::GetBrowserParent() const {
1850 return mCurrentBrowserParent;
1853 void CanonicalBrowsingContext::SetCurrentBrowserParent(
1854 BrowserParent* aBrowserParent) {
1855 MOZ_DIAGNOSTIC_ASSERT(!mCurrentBrowserParent || !aBrowserParent,
1856 "BrowsingContext already has a current BrowserParent!");
1857 MOZ_DIAGNOSTIC_ASSERT_IF(aBrowserParent, aBrowserParent->CanSend());
1858 MOZ_DIAGNOSTIC_ASSERT_IF(aBrowserParent,
1859 aBrowserParent->Manager()->ChildID() == mProcessId);
1861 // BrowserParent must either be directly for this BrowsingContext, or the
1862 // manager out our embedder WindowGlobal.
1863 MOZ_DIAGNOSTIC_ASSERT_IF(
1864 aBrowserParent && aBrowserParent->GetBrowsingContext() != this,
1865 GetParentWindowContext() &&
1866 GetParentWindowContext()->Manager() == aBrowserParent);
1868 mCurrentBrowserParent = aBrowserParent;
1871 RefPtr<CanonicalBrowsingContext::RemotenessPromise>
1872 CanonicalBrowsingContext::ChangeRemoteness(
1873 const NavigationIsolationOptions& aOptions, uint64_t aPendingSwitchId) {
1874 MOZ_DIAGNOSTIC_ASSERT(IsContent(),
1875 "cannot change the process of chrome contexts");
1876 MOZ_DIAGNOSTIC_ASSERT(
1877 IsTop() == IsEmbeddedInProcess(0),
1878 "toplevel content must be embedded in the parent process");
1879 MOZ_DIAGNOSTIC_ASSERT(!aOptions.mReplaceBrowsingContext || IsTop(),
1880 "Cannot replace BrowsingContext for subframes");
1881 MOZ_DIAGNOSTIC_ASSERT(
1882 aOptions.mSpecificGroupId == 0 || aOptions.mReplaceBrowsingContext,
1883 "Cannot specify group ID unless replacing BC");
1884 MOZ_DIAGNOSTIC_ASSERT(aPendingSwitchId || !IsTop(),
1885 "Should always have aPendingSwitchId for top-level "
1886 "frames");
1888 if (!AncestorsAreCurrent()) {
1889 NS_WARNING("An ancestor context is no longer current");
1890 return RemotenessPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
1893 // Ensure our embedder hasn't been destroyed already.
1894 RefPtr<WindowGlobalParent> embedderWindowGlobal = GetEmbedderWindowGlobal();
1895 if (!embedderWindowGlobal) {
1896 NS_WARNING("Non-embedded BrowsingContext");
1897 return RemotenessPromise::CreateAndReject(NS_ERROR_UNEXPECTED, __func__);
1900 if (!embedderWindowGlobal->CanSend()) {
1901 NS_WARNING("Embedder already been destroyed.");
1902 return RemotenessPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__);
1905 if (aOptions.mRemoteType.IsEmpty() && (!IsTop() || !GetEmbedderElement())) {
1906 NS_WARNING("Cannot load non-remote subframes");
1907 return RemotenessPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
1910 // Cancel ongoing remoteness changes.
1911 if (mPendingRemotenessChange) {
1912 mPendingRemotenessChange->Cancel(NS_ERROR_ABORT);
1913 MOZ_DIAGNOSTIC_ASSERT(!mPendingRemotenessChange, "Should have cleared");
1916 auto promise = MakeRefPtr<RemotenessPromise::Private>(__func__);
1917 promise->UseDirectTaskDispatch(__func__);
1919 RefPtr<PendingRemotenessChange> change =
1920 new PendingRemotenessChange(this, promise, aPendingSwitchId, aOptions);
1921 mPendingRemotenessChange = change;
1923 // If a specific BrowsingContextGroup ID was specified for this load, make
1924 // sure to keep it alive until the process switch is completed.
1925 if (aOptions.mSpecificGroupId) {
1926 change->mSpecificGroup =
1927 BrowsingContextGroup::GetOrCreate(aOptions.mSpecificGroupId);
1928 change->mSpecificGroup->AddKeepAlive();
1931 // Call `prepareToChangeRemoteness` in parallel with starting a new process
1932 // for <browser> loads.
1933 if (IsTop() && GetEmbedderElement()) {
1934 nsCOMPtr<nsIBrowser> browser = GetEmbedderElement()->AsBrowser();
1935 if (!browser) {
1936 change->Cancel(NS_ERROR_FAILURE);
1937 return promise.forget();
1940 RefPtr<Promise> blocker;
1941 nsresult rv = browser->PrepareToChangeRemoteness(getter_AddRefs(blocker));
1942 if (NS_FAILED(rv)) {
1943 change->Cancel(rv);
1944 return promise.forget();
1947 // Mark prepareToChange as unresolved, and wait for it to become resolved.
1948 if (blocker && blocker->State() != Promise::PromiseState::Resolved) {
1949 change->mWaitingForPrepareToChange = true;
1950 RefPtr<DomPromiseListener> listener = new DomPromiseListener(
1951 [change](JSContext* aCx, JS::HandleValue aValue) {
1952 change->mWaitingForPrepareToChange = false;
1953 change->MaybeFinish();
1955 [change](nsresult aRv) { change->Cancel(aRv); });
1956 blocker->AppendNativeHandler(listener);
1960 // Switching a subframe to be local within it's embedding process.
1961 RefPtr<BrowserParent> embedderBrowser =
1962 embedderWindowGlobal->GetBrowserParent();
1963 if (embedderBrowser &&
1964 aOptions.mRemoteType == embedderBrowser->Manager()->GetRemoteType()) {
1965 MOZ_DIAGNOSTIC_ASSERT(
1966 aPendingSwitchId,
1967 "We always have a PendingSwitchId, except for print-preview loads, "
1968 "which will never perform a process-switch to being in-process with "
1969 "their embedder");
1970 MOZ_DIAGNOSTIC_ASSERT(!aOptions.mReplaceBrowsingContext);
1971 MOZ_DIAGNOSTIC_ASSERT(!aOptions.mRemoteType.IsEmpty());
1972 MOZ_DIAGNOSTIC_ASSERT(!change->mWaitingForPrepareToChange);
1973 MOZ_DIAGNOSTIC_ASSERT(!change->mSpecificGroup);
1975 // Switching to local, so we don't need to create a new process, and will
1976 // instead use our embedder process.
1977 change->mContentParent = embedderBrowser->Manager();
1978 change->mContentParent->AddKeepAlive();
1979 change->ProcessLaunched();
1980 return promise.forget();
1983 // Switching to the parent process.
1984 if (aOptions.mRemoteType.IsEmpty()) {
1985 change->ProcessLaunched();
1986 return promise.forget();
1989 // If we're aiming to end up in a new process of the same type as our old
1990 // process, and then putting our previous document in the BFCache, try to stay
1991 // in the same process to avoid creating new processes unnecessarily.
1992 RefPtr<ContentParent> existingProcess = GetContentParent();
1993 if (existingProcess && existingProcess->IsAlive() &&
1994 aOptions.mReplaceBrowsingContext &&
1995 aOptions.mRemoteType == existingProcess->GetRemoteType()) {
1996 change->mContentParent = existingProcess;
1997 change->mContentParent->AddKeepAlive();
1998 change->ProcessLaunched();
1999 return promise.forget();
2002 // Try to predict which BrowsingContextGroup will be used for the final load
2003 // in this BrowsingContext. This has to be accurate if switching into an
2004 // existing group, as it will control what pool of processes will be used
2005 // for process selection.
2007 // It's _technically_ OK to provide a group here if we're actually going to
2008 // switch into a brand new group, though it's sub-optimal, as it can
2009 // restrict the set of processes we're using.
2010 BrowsingContextGroup* finalGroup =
2011 aOptions.mReplaceBrowsingContext ? change->mSpecificGroup.get() : Group();
2013 bool preferUsed =
2014 StaticPrefs::browser_tabs_remote_subframesPreferUsed() && !IsTop();
2016 change->mContentParent = ContentParent::GetNewOrUsedLaunchingBrowserProcess(
2017 /* aRemoteType = */ aOptions.mRemoteType,
2018 /* aGroup = */ finalGroup,
2019 /* aPriority = */ hal::PROCESS_PRIORITY_FOREGROUND,
2020 /* aPreferUsed = */ preferUsed);
2021 if (!change->mContentParent) {
2022 change->Cancel(NS_ERROR_FAILURE);
2023 return promise.forget();
2026 // Add a KeepAlive used by this ContentParent, which will be cleared when
2027 // the change is complete. This should prevent the process dying before
2028 // we're ready to use it.
2029 change->mContentParent->AddKeepAlive();
2030 if (change->mContentParent->IsLaunching()) {
2031 change->mContentParent->WaitForLaunchAsync()->Then(
2032 GetMainThreadSerialEventTarget(), __func__,
2033 [change](ContentParent*) { change->ProcessLaunched(); },
2034 [change](LaunchError) { change->Cancel(NS_ERROR_FAILURE); });
2035 } else {
2036 change->ProcessLaunched();
2038 return promise.forget();
2041 void CanonicalBrowsingContext::MaybeSetPermanentKey(Element* aEmbedder) {
2042 MOZ_DIAGNOSTIC_ASSERT(IsTop());
2044 if (aEmbedder) {
2045 if (nsCOMPtr<nsIBrowser> browser = aEmbedder->AsBrowser()) {
2046 JS::RootedValue key(RootingCx());
2047 if (NS_SUCCEEDED(browser->GetPermanentKey(&key)) && key.isObject()) {
2048 mPermanentKey = key;
2054 MediaController* CanonicalBrowsingContext::GetMediaController() {
2055 // We would only create one media controller per tab, so accessing the
2056 // controller via the top-level browsing context.
2057 if (GetParent()) {
2058 return Cast(Top())->GetMediaController();
2061 MOZ_ASSERT(!GetParent(),
2062 "Must access the controller from the top-level browsing context!");
2063 // Only content browsing context can create media controller, we won't create
2064 // controller for chrome document, such as the browser UI.
2065 if (!mTabMediaController && !IsDiscarded() && IsContent()) {
2066 mTabMediaController = new MediaController(Id());
2068 return mTabMediaController;
2071 bool CanonicalBrowsingContext::HasCreatedMediaController() const {
2072 return !!mTabMediaController;
2075 bool CanonicalBrowsingContext::SupportsLoadingInParent(
2076 nsDocShellLoadState* aLoadState, uint64_t* aOuterWindowId) {
2077 // We currently don't support initiating loads in the parent when they are
2078 // watched by devtools. This is because devtools tracks loads using content
2079 // process notifications, which happens after the load is initiated in this
2080 // case. Devtools clears all prior requests when it detects a new navigation,
2081 // so it drops the main document load that happened here.
2082 if (WatchedByDevTools()) {
2083 return false;
2086 // Session-history-in-parent implementation relies currently on getting a
2087 // round trip through a child process.
2088 if (aLoadState->LoadIsFromSessionHistory()) {
2089 return false;
2092 // DocumentChannel currently only supports connecting channels into the
2093 // content process, so we can only support schemes that will always be loaded
2094 // there for now. Restrict to just http(s) for simplicity.
2095 if (!net::SchemeIsHTTP(aLoadState->URI()) &&
2096 !net::SchemeIsHTTPS(aLoadState->URI())) {
2097 return false;
2100 if (WindowGlobalParent* global = GetCurrentWindowGlobal()) {
2101 nsCOMPtr<nsIURI> currentURI = global->GetDocumentURI();
2102 if (currentURI) {
2103 bool newURIHasRef = false;
2104 aLoadState->URI()->GetHasRef(&newURIHasRef);
2105 bool equalsExceptRef = false;
2106 aLoadState->URI()->EqualsExceptRef(currentURI, &equalsExceptRef);
2108 if (equalsExceptRef && newURIHasRef) {
2109 // This navigation is same-doc WRT the current one, we should pass it
2110 // down to the docshell to be handled.
2111 return false;
2114 // If the current document has a beforeunload listener, then we need to
2115 // start the load in that process after we fire the event.
2116 if (global->HasBeforeUnload()) {
2117 return false;
2120 *aOuterWindowId = global->OuterWindowId();
2122 return true;
2125 bool CanonicalBrowsingContext::LoadInParent(nsDocShellLoadState* aLoadState,
2126 bool aSetNavigating) {
2127 // We currently only support starting loads directly from the
2128 // CanonicalBrowsingContext for top-level BCs.
2129 // We currently only support starting loads directly from the
2130 // CanonicalBrowsingContext for top-level BCs.
2131 if (!IsTopContent() || !GetContentParent() ||
2132 !StaticPrefs::browser_tabs_documentchannel_parent_controlled()) {
2133 return false;
2136 uint64_t outerWindowId = 0;
2137 if (!SupportsLoadingInParent(aLoadState, &outerWindowId)) {
2138 return false;
2141 MOZ_ASSERT(!net::SchemeIsJavascript(aLoadState->URI()));
2143 MOZ_ALWAYS_SUCCEEDS(
2144 SetParentInitiatedNavigationEpoch(++gParentInitiatedNavigationEpoch));
2145 // Note: If successful, this will recurse into StartDocumentLoad and
2146 // set mCurrentLoad to the DocumentLoadListener instance created.
2147 // Ideally in the future we will only start loads from here, and we can
2148 // just set this directly instead.
2149 return net::DocumentLoadListener::LoadInParent(this, aLoadState,
2150 aSetNavigating);
2153 bool CanonicalBrowsingContext::AttemptSpeculativeLoadInParent(
2154 nsDocShellLoadState* aLoadState) {
2155 // We currently only support starting loads directly from the
2156 // CanonicalBrowsingContext for top-level BCs.
2157 // We currently only support starting loads directly from the
2158 // CanonicalBrowsingContext for top-level BCs.
2159 if (!IsTopContent() || !GetContentParent() ||
2160 (StaticPrefs::browser_tabs_documentchannel_parent_controlled())) {
2161 return false;
2164 uint64_t outerWindowId = 0;
2165 if (!SupportsLoadingInParent(aLoadState, &outerWindowId)) {
2166 return false;
2169 // If we successfully open the DocumentChannel, then it'll register
2170 // itself using aLoadIdentifier and be kept alive until it completes
2171 // loading.
2172 return net::DocumentLoadListener::SpeculativeLoadInParent(this, aLoadState);
2175 bool CanonicalBrowsingContext::StartDocumentLoad(
2176 net::DocumentLoadListener* aLoad) {
2177 // If we're controlling loads from the parent, then starting a new load means
2178 // that we need to cancel any existing ones.
2179 if (StaticPrefs::browser_tabs_documentchannel_parent_controlled() &&
2180 mCurrentLoad) {
2181 // Make sure we are not loading a javascript URI.
2182 MOZ_ASSERT(!aLoad->IsLoadingJSURI());
2184 // If we want to do a download, don't cancel the current navigation.
2185 if (!aLoad->IsDownload()) {
2186 mCurrentLoad->Cancel(NS_BINDING_CANCELLED_OLD_LOAD);
2189 mCurrentLoad = aLoad;
2191 if (NS_FAILED(SetCurrentLoadIdentifier(Some(aLoad->GetLoadIdentifier())))) {
2192 mCurrentLoad = nullptr;
2193 return false;
2196 return true;
2199 void CanonicalBrowsingContext::EndDocumentLoad(bool aContinueNavigating) {
2200 mCurrentLoad = nullptr;
2202 if (!aContinueNavigating) {
2203 // Resetting the current load identifier on a discarded context
2204 // has no effect when a document load has finished.
2205 Unused << SetCurrentLoadIdentifier(Nothing());
2209 already_AddRefed<nsIURI> CanonicalBrowsingContext::GetCurrentURI() const {
2210 nsCOMPtr<nsIURI> currentURI;
2211 if (nsIDocShell* docShell = GetDocShell()) {
2212 MOZ_ALWAYS_SUCCEEDS(
2213 nsDocShell::Cast(docShell)->GetCurrentURI(getter_AddRefs(currentURI)));
2214 } else {
2215 currentURI = mCurrentRemoteURI;
2217 return currentURI.forget();
2220 void CanonicalBrowsingContext::SetCurrentRemoteURI(nsIURI* aCurrentRemoteURI) {
2221 MOZ_ASSERT(!GetDocShell());
2222 mCurrentRemoteURI = aCurrentRemoteURI;
2225 void CanonicalBrowsingContext::ResetSHEntryHasUserInteractionCache() {
2226 WindowContext* topWc = GetTopWindowContext();
2227 if (topWc && !topWc->IsDiscarded()) {
2228 MOZ_ALWAYS_SUCCEEDS(topWc->SetSHEntryHasUserInteraction(false));
2232 void CanonicalBrowsingContext::HistoryCommitIndexAndLength() {
2233 nsID changeID = {};
2234 CallerWillNotifyHistoryIndexAndLengthChanges caller(nullptr);
2235 HistoryCommitIndexAndLength(changeID, caller);
2237 void CanonicalBrowsingContext::HistoryCommitIndexAndLength(
2238 const nsID& aChangeID,
2239 const CallerWillNotifyHistoryIndexAndLengthChanges& aProofOfCaller) {
2240 if (!IsTop()) {
2241 Cast(Top())->HistoryCommitIndexAndLength(aChangeID, aProofOfCaller);
2242 return;
2245 nsISHistory* shistory = GetSessionHistory();
2246 if (!shistory) {
2247 return;
2249 int32_t index = 0;
2250 shistory->GetIndex(&index);
2251 int32_t length = shistory->GetCount();
2253 GetChildSessionHistory()->SetIndexAndLength(index, length, aChangeID);
2255 shistory->EvictOutOfRangeContentViewers(index);
2257 Group()->EachParent([&](ContentParent* aParent) {
2258 Unused << aParent->SendHistoryCommitIndexAndLength(this, index, length,
2259 aChangeID);
2263 void CanonicalBrowsingContext::SynchronizeLayoutHistoryState() {
2264 if (mActiveEntry) {
2265 if (IsInProcess()) {
2266 nsIDocShell* docShell = GetDocShell();
2267 if (docShell) {
2268 docShell->PersistLayoutHistoryState();
2270 nsCOMPtr<nsILayoutHistoryState> state;
2271 docShell->GetLayoutHistoryState(getter_AddRefs(state));
2272 if (state) {
2273 mActiveEntry->SetLayoutHistoryState(state);
2276 } else if (ContentParent* cp = GetContentParent()) {
2277 cp->SendGetLayoutHistoryState(this)->Then(
2278 GetCurrentSerialEventTarget(), __func__,
2279 [activeEntry =
2280 mActiveEntry](const RefPtr<nsILayoutHistoryState>& aState) {
2281 if (aState) {
2282 activeEntry->SetLayoutHistoryState(aState);
2285 []() {});
2290 void CanonicalBrowsingContext::ResetScalingZoom() {
2291 // This currently only ever gets called in the parent process, and we
2292 // pass the message on to the WindowGlobalChild for the rootmost browsing
2293 // context.
2294 if (WindowGlobalParent* topWindow = GetTopWindowContext()) {
2295 Unused << topWindow->SendResetScalingZoom();
2299 void CanonicalBrowsingContext::SetRestoreData(SessionStoreRestoreData* aData,
2300 ErrorResult& aError) {
2301 MOZ_DIAGNOSTIC_ASSERT(aData);
2303 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
2304 RefPtr<Promise> promise = Promise::Create(global, aError);
2305 if (aError.Failed()) {
2306 return;
2309 if (NS_WARN_IF(NS_FAILED(SetHasRestoreData(true)))) {
2310 aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
2311 return;
2314 mRestoreState = new RestoreState();
2315 mRestoreState->mData = aData;
2316 mRestoreState->mPromise = promise;
2319 already_AddRefed<Promise> CanonicalBrowsingContext::GetRestorePromise() {
2320 if (mRestoreState) {
2321 return do_AddRef(mRestoreState->mPromise);
2323 return nullptr;
2326 void CanonicalBrowsingContext::ClearRestoreState() {
2327 if (!mRestoreState) {
2328 MOZ_DIAGNOSTIC_ASSERT(!GetHasRestoreData());
2329 return;
2331 if (mRestoreState->mPromise) {
2332 mRestoreState->mPromise->MaybeRejectWithUndefined();
2334 mRestoreState = nullptr;
2335 MOZ_ALWAYS_SUCCEEDS(SetHasRestoreData(false));
2338 void CanonicalBrowsingContext::RequestRestoreTabContent(
2339 WindowGlobalParent* aWindow) {
2340 MOZ_DIAGNOSTIC_ASSERT(IsTop());
2342 if (IsDiscarded() || !mRestoreState || !mRestoreState->mData) {
2343 return;
2346 CanonicalBrowsingContext* context = aWindow->GetBrowsingContext();
2347 MOZ_DIAGNOSTIC_ASSERT(!context->IsDiscarded());
2349 RefPtr<SessionStoreRestoreData> data =
2350 mRestoreState->mData->FindDataForChild(context);
2352 if (context->IsTop()) {
2353 MOZ_DIAGNOSTIC_ASSERT(context == this);
2355 // We need to wait until the appropriate load event has fired before we
2356 // can "complete" the restore process, so if we're holding an empty data
2357 // object, just resolve the promise immediately.
2358 if (mRestoreState->mData->IsEmpty()) {
2359 MOZ_DIAGNOSTIC_ASSERT(!data || data->IsEmpty());
2360 mRestoreState->Resolve();
2361 ClearRestoreState();
2362 return;
2365 // Since we're following load event order, we'll only arrive here for a
2366 // toplevel context after we've already sent down data for all child frames,
2367 // so it's safe to clear this reference now. The completion callback below
2368 // relies on the mData field being null to determine if all requests have
2369 // been sent out.
2370 mRestoreState->ClearData();
2371 MOZ_ALWAYS_SUCCEEDS(SetHasRestoreData(false));
2374 if (data && !data->IsEmpty()) {
2375 auto onTabRestoreComplete = [self = RefPtr{this},
2376 state = RefPtr{mRestoreState}](auto) {
2377 state->mResolves++;
2378 if (!state->mData && state->mRequests == state->mResolves) {
2379 state->Resolve();
2380 if (state == self->mRestoreState) {
2381 self->ClearRestoreState();
2386 mRestoreState->mRequests++;
2388 if (data->CanRestoreInto(aWindow->GetDocumentURI())) {
2389 if (!aWindow->IsInProcess()) {
2390 aWindow->SendRestoreTabContent(data, onTabRestoreComplete,
2391 onTabRestoreComplete);
2392 return;
2394 data->RestoreInto(context);
2397 // This must be called both when we're doing an in-process restore, and when
2398 // we didn't do a restore at all due to a URL mismatch.
2399 onTabRestoreComplete(true);
2403 void CanonicalBrowsingContext::RestoreState::Resolve() {
2404 MOZ_DIAGNOSTIC_ASSERT(mPromise);
2405 mPromise->MaybeResolveWithUndefined();
2406 mPromise = nullptr;
2409 nsresult CanonicalBrowsingContext::WriteSessionStorageToSessionStore(
2410 const nsTArray<SSCacheCopy>& aSesssionStorage, uint32_t aEpoch) {
2411 nsCOMPtr<nsISessionStoreFunctions> funcs = do_ImportModule(
2412 "resource://gre/modules/SessionStoreFunctions.jsm", fallible);
2413 if (!funcs) {
2414 return NS_ERROR_FAILURE;
2417 nsCOMPtr<nsIXPConnectWrappedJS> wrapped = do_QueryInterface(funcs);
2418 AutoJSAPI jsapi;
2419 if (!jsapi.Init(wrapped->GetJSObjectGlobal())) {
2420 return NS_ERROR_FAILURE;
2423 JS::RootedValue key(jsapi.cx(), Top()->PermanentKey());
2425 Record<nsCString, Record<nsString, nsString>> storage;
2426 JS::RootedValue update(jsapi.cx());
2428 if (!aSesssionStorage.IsEmpty()) {
2429 SessionStoreUtils::ConstructSessionStorageValues(this, aSesssionStorage,
2430 storage);
2431 if (!ToJSValue(jsapi.cx(), storage, &update)) {
2432 return NS_ERROR_FAILURE;
2434 } else {
2435 update.setNull();
2438 return funcs->UpdateSessionStoreForStorage(Top()->GetEmbedderElement(), this,
2439 key, aEpoch, update);
2442 void CanonicalBrowsingContext::UpdateSessionStoreSessionStorage(
2443 const std::function<void()>& aDone) {
2444 if constexpr (!SessionStoreUtils::NATIVE_LISTENER) {
2445 aDone();
2446 return;
2449 using DataPromise = BackgroundSessionStorageManager::DataPromise;
2450 BackgroundSessionStorageManager::GetData(
2451 this, StaticPrefs::browser_sessionstore_dom_storage_limit(),
2452 /* aClearSessionStoreTimer = */ true)
2453 ->Then(GetCurrentSerialEventTarget(), __func__,
2454 [self = RefPtr{this}, aDone, epoch = GetSessionStoreEpoch()](
2455 const DataPromise::ResolveOrRejectValue& valueList) {
2456 if (valueList.IsResolve()) {
2457 self->WriteSessionStorageToSessionStore(
2458 valueList.ResolveValue(), epoch);
2460 aDone();
2464 /* static */
2465 void CanonicalBrowsingContext::UpdateSessionStoreForStorage(
2466 uint64_t aBrowsingContextId) {
2467 RefPtr<CanonicalBrowsingContext> browsingContext = Get(aBrowsingContextId);
2469 if (!browsingContext) {
2470 return;
2473 browsingContext->UpdateSessionStoreSessionStorage([]() {});
2476 void CanonicalBrowsingContext::MaybeScheduleSessionStoreUpdate() {
2477 if constexpr (!SessionStoreUtils::NATIVE_LISTENER) {
2478 return;
2481 if (!IsTop()) {
2482 Top()->MaybeScheduleSessionStoreUpdate();
2483 return;
2486 if (IsInBFCache()) {
2487 return;
2490 if (mSessionStoreSessionStorageUpdateTimer) {
2491 return;
2494 if (!StaticPrefs::browser_sessionstore_debug_no_auto_updates()) {
2495 auto result = NS_NewTimerWithFuncCallback(
2496 [](nsITimer*, void* aClosure) {
2497 auto* context = static_cast<CanonicalBrowsingContext*>(aClosure);
2498 context->UpdateSessionStoreSessionStorage([]() {});
2500 this, StaticPrefs::browser_sessionstore_interval(),
2501 nsITimer::TYPE_ONE_SHOT,
2502 "CanonicalBrowsingContext::MaybeScheduleSessionStoreUpdate");
2504 if (result.isErr()) {
2505 return;
2508 mSessionStoreSessionStorageUpdateTimer = result.unwrap();
2512 void CanonicalBrowsingContext::CancelSessionStoreUpdate() {
2513 if (mSessionStoreSessionStorageUpdateTimer) {
2514 mSessionStoreSessionStorageUpdateTimer->Cancel();
2515 mSessionStoreSessionStorageUpdateTimer = nullptr;
2519 void CanonicalBrowsingContext::SetContainerFeaturePolicy(
2520 FeaturePolicy* aContainerFeaturePolicy) {
2521 mContainerFeaturePolicy = aContainerFeaturePolicy;
2523 if (WindowGlobalParent* current = GetCurrentWindowGlobal()) {
2524 Unused << current->SendSetContainerFeaturePolicy(mContainerFeaturePolicy);
2528 void CanonicalBrowsingContext::SetCrossGroupOpenerId(uint64_t aOpenerId) {
2529 MOZ_DIAGNOSTIC_ASSERT(IsTopContent());
2530 MOZ_DIAGNOSTIC_ASSERT(mCrossGroupOpenerId == 0,
2531 "Can only set CrossGroupOpenerId once");
2532 mCrossGroupOpenerId = aOpenerId;
2535 void CanonicalBrowsingContext::SetCrossGroupOpener(
2536 CanonicalBrowsingContext& aCrossGroupOpener, ErrorResult& aRv) {
2537 if (!IsTopContent()) {
2538 aRv.ThrowNotAllowedError(
2539 "Can only set crossGroupOpener on toplevel content");
2540 return;
2542 if (mCrossGroupOpenerId != 0) {
2543 aRv.ThrowNotAllowedError("Can only set crossGroupOpener once");
2544 return;
2547 SetCrossGroupOpenerId(aCrossGroupOpener.Id());
2550 auto CanonicalBrowsingContext::FindUnloadingHost(uint64_t aChildID)
2551 -> nsTArray<UnloadingHost>::iterator {
2552 return std::find_if(
2553 mUnloadingHosts.begin(), mUnloadingHosts.end(),
2554 [&](const auto& host) { return host.mChildID == aChildID; });
2557 void CanonicalBrowsingContext::ClearUnloadingHost(uint64_t aChildID) {
2558 // Notify any callbacks which were waiting for the host to finish unloading
2559 // that it has.
2560 auto found = FindUnloadingHost(aChildID);
2561 if (found != mUnloadingHosts.end()) {
2562 auto callbacks = std::move(found->mCallbacks);
2563 mUnloadingHosts.RemoveElementAt(found);
2564 for (const auto& callback : callbacks) {
2565 callback();
2570 void CanonicalBrowsingContext::StartUnloadingHost(uint64_t aChildID) {
2571 MOZ_DIAGNOSTIC_ASSERT(FindUnloadingHost(aChildID) == mUnloadingHosts.end());
2572 mUnloadingHosts.AppendElement(UnloadingHost{aChildID, {}});
2575 void CanonicalBrowsingContext::BrowserParentDestroyed(
2576 BrowserParent* aBrowserParent, bool aAbnormalShutdown) {
2577 ClearUnloadingHost(aBrowserParent->Manager()->ChildID());
2579 // Handling specific to when the current BrowserParent has been destroyed.
2580 if (mCurrentBrowserParent == aBrowserParent) {
2581 mCurrentBrowserParent = nullptr;
2583 // If this BrowserParent is for a subframe, attempt to recover from a
2584 // subframe crash by rendering the subframe crashed page in the embedding
2585 // content.
2586 if (aAbnormalShutdown) {
2587 ShowSubframeCrashedUI(aBrowserParent->GetBrowserBridgeParent());
2592 void CanonicalBrowsingContext::ShowSubframeCrashedUI(
2593 BrowserBridgeParent* aBridge) {
2594 if (!aBridge || IsDiscarded() || !aBridge->CanSend()) {
2595 return;
2598 MOZ_DIAGNOSTIC_ASSERT(!aBridge->GetBrowsingContext() ||
2599 aBridge->GetBrowsingContext() == this);
2601 // There is no longer a current inner window within this
2602 // BrowsingContext, update the `CurrentInnerWindowId` field to reflect
2603 // this.
2604 MOZ_ALWAYS_SUCCEEDS(SetCurrentInnerWindowId(0));
2606 // The owning process will now be the embedder to render the subframe
2607 // crashed page, switch ownership back over.
2608 SetOwnerProcessId(aBridge->Manager()->Manager()->ChildID());
2609 SetCurrentBrowserParent(aBridge->Manager());
2611 Unused << aBridge->SendSubFrameCrashed();
2614 static void LogBFCacheBlockingForDoc(BrowsingContext* aBrowsingContext,
2615 uint32_t aBFCacheCombo, bool aIsSubDoc) {
2616 if (aIsSubDoc) {
2617 nsAutoCString uri("[no uri]");
2618 nsCOMPtr<nsIURI> currentURI =
2619 aBrowsingContext->Canonical()->GetCurrentURI();
2620 if (currentURI) {
2621 uri = currentURI->GetSpecOrDefault();
2623 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
2624 (" ** Blocked for document %s", uri.get()));
2626 if (aBFCacheCombo & BFCacheStatus::EVENT_HANDLING_SUPPRESSED) {
2627 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
2628 (" * event handling suppression"));
2630 if (aBFCacheCombo & BFCacheStatus::SUSPENDED) {
2631 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * suspended Window"));
2633 if (aBFCacheCombo & BFCacheStatus::UNLOAD_LISTENER) {
2634 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * unload listener"));
2636 if (aBFCacheCombo & BFCacheStatus::REQUEST) {
2637 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * requests in the loadgroup"));
2639 if (aBFCacheCombo & BFCacheStatus::ACTIVE_GET_USER_MEDIA) {
2640 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * GetUserMedia"));
2642 if (aBFCacheCombo & BFCacheStatus::ACTIVE_PEER_CONNECTION) {
2643 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * PeerConnection"));
2645 if (aBFCacheCombo & BFCacheStatus::CONTAINS_EME_CONTENT) {
2646 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * EME content"));
2648 if (aBFCacheCombo & BFCacheStatus::CONTAINS_MSE_CONTENT) {
2649 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * MSE use"));
2651 if (aBFCacheCombo & BFCacheStatus::HAS_ACTIVE_SPEECH_SYNTHESIS) {
2652 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * Speech use"));
2654 if (aBFCacheCombo & BFCacheStatus::HAS_USED_VR) {
2655 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * used VR"));
2657 if (aBFCacheCombo & BFCacheStatus::BEFOREUNLOAD_LISTENER) {
2658 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * beforeunload listener"));
2660 if (aBFCacheCombo & BFCacheStatus::ACTIVE_LOCK) {
2661 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * has active Web Locks"));
2665 bool CanonicalBrowsingContext::AllowedInBFCache(
2666 const Maybe<uint64_t>& aChannelId, nsIURI* aNewURI) {
2667 if (MOZ_UNLIKELY(MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug))) {
2668 nsAutoCString uri("[no uri]");
2669 nsCOMPtr<nsIURI> currentURI = GetCurrentURI();
2670 if (currentURI) {
2671 uri = currentURI->GetSpecOrDefault();
2673 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, ("Checking %s", uri.get()));
2676 if (IsInProcess()) {
2677 return false;
2680 uint32_t bfcacheCombo = 0;
2681 if (mRestoreState) {
2682 bfcacheCombo |= BFCacheStatus::RESTORING;
2683 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * during session restore"));
2686 if (Group()->Toplevels().Length() > 1) {
2687 bfcacheCombo |= BFCacheStatus::NOT_ONLY_TOPLEVEL_IN_BCG;
2688 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
2689 (" * auxiliary BrowsingContexts"));
2692 // There are not a lot of about:* pages that are allowed to load in
2693 // subframes, so it's OK to allow those few about:* pages enter BFCache.
2694 MOZ_ASSERT(IsTop(), "Trying to put a non top level BC into BFCache");
2696 WindowGlobalParent* wgp = GetCurrentWindowGlobal();
2697 if (wgp && wgp->GetDocumentURI()) {
2698 nsCOMPtr<nsIURI> currentURI = wgp->GetDocumentURI();
2699 // Exempt about:* pages from bfcache, with the exception of about:blank
2700 if (currentURI->SchemeIs("about") &&
2701 !currentURI->GetSpecOrDefault().EqualsLiteral("about:blank")) {
2702 bfcacheCombo |= BFCacheStatus::ABOUT_PAGE;
2703 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * about:* page"));
2706 if (aNewURI) {
2707 bool equalUri = false;
2708 aNewURI->Equals(currentURI, &equalUri);
2709 if (equalUri) {
2710 // When loading the same uri, disable bfcache so that
2711 // nsDocShell::OnNewURI transforms the load to LOAD_NORMAL_REPLACE.
2712 return false;
2717 // For telemetry we're collecting all the flags for all the BCs hanging
2718 // from this top-level BC.
2719 PreOrderWalk([&](BrowsingContext* aBrowsingContext) {
2720 WindowGlobalParent* wgp =
2721 aBrowsingContext->Canonical()->GetCurrentWindowGlobal();
2722 uint32_t subDocBFCacheCombo = wgp ? wgp->GetBFCacheStatus() : 0;
2723 if (wgp) {
2724 const Maybe<uint64_t>& singleChannelId = wgp->GetSingleChannelId();
2725 if (singleChannelId.isSome()) {
2726 if (singleChannelId.value() == 0 || aChannelId.isNothing() ||
2727 singleChannelId.value() != aChannelId.value()) {
2728 subDocBFCacheCombo |= BFCacheStatus::REQUEST;
2733 if (MOZ_UNLIKELY(MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug))) {
2734 LogBFCacheBlockingForDoc(aBrowsingContext, subDocBFCacheCombo,
2735 aBrowsingContext != this);
2738 bfcacheCombo |= subDocBFCacheCombo;
2741 nsDocShell::ReportBFCacheComboTelemetry(bfcacheCombo);
2742 if (MOZ_UNLIKELY(MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug))) {
2743 nsAutoCString uri("[no uri]");
2744 nsCOMPtr<nsIURI> currentURI = GetCurrentURI();
2745 if (currentURI) {
2746 uri = currentURI->GetSpecOrDefault();
2748 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
2749 (" +> %s %s be blocked from going into the BFCache", uri.get(),
2750 bfcacheCombo == 0 ? "shouldn't" : "should"));
2753 if (StaticPrefs::docshell_shistory_bfcache_allow_unload_listeners()) {
2754 bfcacheCombo &= ~BFCacheStatus::UNLOAD_LISTENER;
2757 return bfcacheCombo == 0;
2760 void CanonicalBrowsingContext::SetTouchEventsOverride(
2761 dom::TouchEventsOverride aOverride, ErrorResult& aRv) {
2762 SetTouchEventsOverrideInternal(aOverride, aRv);
2765 void CanonicalBrowsingContext::SetTargetTopLevelLinkClicksToBlank(
2766 bool aTargetTopLevelLinkClicksToBlank, ErrorResult& aRv) {
2767 SetTargetTopLevelLinkClicksToBlankInternal(aTargetTopLevelLinkClicksToBlank,
2768 aRv);
2771 void CanonicalBrowsingContext::AddPageAwakeRequest() {
2772 MOZ_ASSERT(IsTop());
2773 auto count = GetPageAwakeRequestCount();
2774 MOZ_ASSERT(count < UINT32_MAX);
2775 Unused << SetPageAwakeRequestCount(++count);
2778 void CanonicalBrowsingContext::RemovePageAwakeRequest() {
2779 MOZ_ASSERT(IsTop());
2780 auto count = GetPageAwakeRequestCount();
2781 MOZ_ASSERT(count > 0);
2782 Unused << SetPageAwakeRequestCount(--count);
2785 void CanonicalBrowsingContext::CloneDocumentTreeInto(
2786 CanonicalBrowsingContext* aSource, const nsACString& aRemoteType,
2787 embedding::PrintData&& aPrintData) {
2788 NavigationIsolationOptions options;
2789 options.mRemoteType = aRemoteType;
2791 mClonePromise =
2792 ChangeRemoteness(options, /* aPendingSwitchId = */ 0)
2793 ->Then(
2794 GetMainThreadSerialEventTarget(), __func__,
2795 [source = MaybeDiscardedBrowsingContext{aSource},
2796 data = std::move(aPrintData)](
2797 BrowserParent* aBp) -> RefPtr<GenericNonExclusivePromise> {
2798 RefPtr<BrowserBridgeParent> bridge =
2799 aBp->GetBrowserBridgeParent();
2800 return aBp->SendCloneDocumentTreeIntoSelf(source, data)
2801 ->Then(
2802 GetMainThreadSerialEventTarget(), __func__,
2803 [bridge](
2804 BrowserParent::CloneDocumentTreeIntoSelfPromise::
2805 ResolveOrRejectValue&& aValue) {
2806 // We're cloning a remote iframe, so we created a
2807 // BrowserBridge which makes us register an OOP load
2808 // (see Document::OOPChildLoadStarted), even though
2809 // this isn't a real load. We call
2810 // SendMaybeFireEmbedderLoadEvents here so that we do
2811 // register the end of the load (see
2812 // Document::OOPChildLoadDone).
2813 if (bridge) {
2814 Unused << bridge->SendMaybeFireEmbedderLoadEvents(
2815 EmbedderElementEventType::NoEvent);
2817 if (aValue.IsResolve() && aValue.ResolveValue()) {
2818 return GenericNonExclusivePromise::CreateAndResolve(
2819 true, __func__);
2821 return GenericNonExclusivePromise::CreateAndReject(
2822 NS_ERROR_FAILURE, __func__);
2825 [](nsresult aRv) -> RefPtr<GenericNonExclusivePromise> {
2826 NS_WARNING(
2827 nsPrintfCString("Remote clone failed: %x\n", unsigned(aRv))
2828 .get());
2829 return GenericNonExclusivePromise::CreateAndReject(
2830 NS_ERROR_FAILURE, __func__);
2833 mClonePromise->Then(
2834 GetMainThreadSerialEventTarget(), __func__,
2835 [self = RefPtr{this}]() { self->mClonePromise = nullptr; });
2838 bool CanonicalBrowsingContext::StartApzAutoscroll(float aAnchorX,
2839 float aAnchorY,
2840 nsViewID aScrollId,
2841 uint32_t aPresShellId) {
2842 nsCOMPtr<nsIWidget> widget;
2843 mozilla::layers::LayersId layersId{0};
2845 if (IsInProcess()) {
2846 nsCOMPtr<nsPIDOMWindowOuter> outer = GetDOMWindow();
2847 if (!outer) {
2848 return false;
2851 widget = widget::WidgetUtils::DOMWindowToWidget(outer);
2852 if (widget) {
2853 layersId = widget->GetRootLayerTreeId();
2855 } else {
2856 RefPtr<BrowserParent> parent = GetBrowserParent();
2857 if (!parent) {
2858 return false;
2861 widget = parent->GetWidget();
2862 layersId = parent->GetLayersId();
2865 if (!widget || !widget->AsyncPanZoomEnabled()) {
2866 return false;
2869 // The anchor coordinates that are passed in are relative to the origin of the
2870 // screen, but we are sending them to APZ which only knows about coordinates
2871 // relative to the widget, so convert them accordingly.
2872 const LayoutDeviceIntPoint anchor =
2873 RoundedToInt(LayoutDevicePoint(aAnchorX, aAnchorY)) -
2874 widget->WidgetToScreenOffset();
2876 mozilla::layers::ScrollableLayerGuid guid(layersId, aPresShellId, aScrollId);
2878 return widget->StartAsyncAutoscroll(
2879 ViewAs<ScreenPixel>(
2880 anchor, PixelCastJustification::LayoutDeviceIsScreenForBounds),
2881 guid);
2884 void CanonicalBrowsingContext::StopApzAutoscroll(nsViewID aScrollId,
2885 uint32_t aPresShellId) {
2886 nsCOMPtr<nsIWidget> widget;
2887 mozilla::layers::LayersId layersId{0};
2889 if (IsInProcess()) {
2890 nsCOMPtr<nsPIDOMWindowOuter> outer = GetDOMWindow();
2891 if (!outer) {
2892 return;
2895 widget = widget::WidgetUtils::DOMWindowToWidget(outer);
2896 if (widget) {
2897 layersId = widget->GetRootLayerTreeId();
2899 } else {
2900 RefPtr<BrowserParent> parent = GetBrowserParent();
2901 if (!parent) {
2902 return;
2905 widget = parent->GetWidget();
2906 layersId = parent->GetLayersId();
2909 if (!widget || !widget->AsyncPanZoomEnabled()) {
2910 return;
2913 mozilla::layers::ScrollableLayerGuid guid(layersId, aPresShellId, aScrollId);
2914 widget->StopAsyncAutoscroll(guid);
2917 NS_IMPL_CYCLE_COLLECTION_CLASS(CanonicalBrowsingContext)
2919 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(CanonicalBrowsingContext,
2920 BrowsingContext)
2921 tmp->mPermanentKey.setNull();
2922 if (tmp->mSessionHistory) {
2923 tmp->mSessionHistory->SetBrowsingContext(nullptr);
2925 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSessionHistory, mContainerFeaturePolicy,
2926 mCurrentBrowserParent, mWebProgress,
2927 mSessionStoreSessionStorageUpdateTimer)
2928 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
2930 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(CanonicalBrowsingContext,
2931 BrowsingContext)
2932 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSessionHistory, mContainerFeaturePolicy,
2933 mCurrentBrowserParent, mWebProgress,
2934 mSessionStoreSessionStorageUpdateTimer)
2935 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
2937 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(CanonicalBrowsingContext,
2938 BrowsingContext)
2939 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mPermanentKey)
2940 NS_IMPL_CYCLE_COLLECTION_TRACE_END
2942 NS_IMPL_ADDREF_INHERITED(CanonicalBrowsingContext, BrowsingContext)
2943 NS_IMPL_RELEASE_INHERITED(CanonicalBrowsingContext, BrowsingContext)
2945 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CanonicalBrowsingContext)
2946 NS_INTERFACE_MAP_END_INHERITING(BrowsingContext)
2948 } // namespace dom
2949 } // namespace mozilla