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/. */
6 
7 #include "mozilla/dom/CanonicalBrowsingContext.h"
8 
9 #include "mozilla/CheckedInt.h"
10 #include "mozilla/EventForwards.h"
11 #include "mozilla/AsyncEventDispatcher.h"
12 #include "mozilla/dom/BrowserParent.h"
13 #include "mozilla/dom/BrowsingContextBinding.h"
14 #include "mozilla/dom/BrowsingContextGroup.h"
15 #include "mozilla/dom/ContentParent.h"
16 #include "mozilla/dom/EventTarget.h"
17 #include "mozilla/dom/PBackgroundSessionStorageCache.h"
18 #include "mozilla/dom/PWindowGlobalParent.h"
19 #include "mozilla/dom/WindowGlobalParent.h"
20 #include "mozilla/dom/ContentProcessManager.h"
21 #include "mozilla/dom/MediaController.h"
22 #include "mozilla/dom/MediaControlService.h"
23 #include "mozilla/dom/ContentPlaybackController.h"
24 #include "mozilla/dom/SessionStorageManager.h"
25 #include "mozilla/ipc/ProtocolUtils.h"
26 #include "mozilla/net/DocumentLoadListener.h"
27 #include "mozilla/NullPrincipal.h"
28 #include "mozilla/StaticPrefs_docshell.h"
29 #include "mozilla/StaticPrefs_fission.h"
30 #include "mozilla/Telemetry.h"
31 #include "nsIWebNavigation.h"
32 #include "mozilla/MozPromiseInlines.h"
33 #include "nsDocShell.h"
34 #include "nsFrameLoader.h"
35 #include "nsFrameLoaderOwner.h"
36 #include "nsGlobalWindowOuter.h"
37 #include "nsIWebBrowserChrome.h"
38 #include "nsIXULRuntime.h"
39 #include "nsNetUtil.h"
40 #include "nsSHistory.h"
41 #include "nsSecureBrowserUI.h"
42 #include "nsQueryObject.h"
43 #include "nsBrowserStatusFilter.h"
44 #include "nsIBrowser.h"
45 #include "nsTHashSet.h"
46 #include "SessionStoreFunctions.h"
47 #include "nsIXPConnect.h"
48 #include "nsImportModule.h"
49 
50 #ifdef NS_PRINTING
51 #  include "mozilla/embedding/printingui/PrintingParent.h"
52 #  include "nsIWebBrowserPrint.h"
53 #endif
54 
55 using namespace mozilla::ipc;
56 
57 extern mozilla::LazyLogModule gAutoplayPermissionLog;
58 extern mozilla::LazyLogModule gSHLog;
59 extern mozilla::LazyLogModule gSHIPBFCacheLog;
60 
61 #define AUTOPLAY_LOG(msg, ...) \
62   MOZ_LOG(gAutoplayPermissionLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
63 
64 static mozilla::LazyLogModule sPBContext("PBContext");
65 
66 // Global count of canonical browsing contexts with the private attribute set
67 static uint32_t gNumberOfPrivateContexts = 0;
68 
IncreasePrivateCount()69 static void IncreasePrivateCount() {
70   gNumberOfPrivateContexts++;
71   MOZ_LOG(sPBContext, mozilla::LogLevel::Debug,
72           ("%s: Private browsing context count %d -> %d", __func__,
73            gNumberOfPrivateContexts - 1, gNumberOfPrivateContexts));
74   if (gNumberOfPrivateContexts > 1) {
75     return;
76   }
77 
78   static bool sHasSeenPrivateContext = false;
79   if (!sHasSeenPrivateContext) {
80     sHasSeenPrivateContext = true;
81     mozilla::Telemetry::ScalarSet(
82         mozilla::Telemetry::ScalarID::DOM_PARENTPROCESS_PRIVATE_WINDOW_USED,
83         true);
84   }
85 }
86 
DecreasePrivateCount()87 static void DecreasePrivateCount() {
88   MOZ_ASSERT(gNumberOfPrivateContexts > 0);
89   gNumberOfPrivateContexts--;
90 
91   MOZ_LOG(sPBContext, mozilla::LogLevel::Debug,
92           ("%s: Private browsing context count %d -> %d", __func__,
93            gNumberOfPrivateContexts + 1, gNumberOfPrivateContexts));
94   if (!gNumberOfPrivateContexts &&
95       !mozilla::Preferences::GetBool("browser.privatebrowsing.autostart")) {
96     nsCOMPtr<nsIObserverService> observerService =
97         mozilla::services::GetObserverService();
98     if (observerService) {
99       MOZ_LOG(sPBContext, mozilla::LogLevel::Debug,
100               ("%s: last-pb-context-exited fired", __func__));
101       observerService->NotifyObservers(nullptr, "last-pb-context-exited",
102                                        nullptr);
103     }
104   }
105 }
106 
107 namespace mozilla {
108 namespace dom {
109 
110 extern mozilla::LazyLogModule gUserInteractionPRLog;
111 
112 #define USER_ACTIVATION_LOG(msg, ...) \
113   MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
114 
CanonicalBrowsingContext(WindowContext * aParentWindow,BrowsingContextGroup * aGroup,uint64_t aBrowsingContextId,uint64_t aOwnerProcessId,uint64_t aEmbedderProcessId,BrowsingContext::Type aType,FieldValues && aInit)115 CanonicalBrowsingContext::CanonicalBrowsingContext(WindowContext* aParentWindow,
116                                                    BrowsingContextGroup* aGroup,
117                                                    uint64_t aBrowsingContextId,
118                                                    uint64_t aOwnerProcessId,
119                                                    uint64_t aEmbedderProcessId,
120                                                    BrowsingContext::Type aType,
121                                                    FieldValues&& aInit)
122     : BrowsingContext(aParentWindow, aGroup, aBrowsingContextId, aType,
123                       std::move(aInit)),
124       mProcessId(aOwnerProcessId),
125       mEmbedderProcessId(aEmbedderProcessId),
126       mPermanentKey(JS::NullValue()) {
127   // You are only ever allowed to create CanonicalBrowsingContexts in the
128   // parent process.
129   MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
130 
131   // The initial URI in a BrowsingContext is always "about:blank".
132   MOZ_ALWAYS_SUCCEEDS(
133       NS_NewURI(getter_AddRefs(mCurrentRemoteURI), "about:blank"));
134 
135   mozilla::HoldJSObjects(this);
136 }
137 
~CanonicalBrowsingContext()138 CanonicalBrowsingContext::~CanonicalBrowsingContext() {
139   mPermanentKey.setNull();
140 
141   mozilla::DropJSObjects(this);
142 
143   if (mSessionHistory) {
144     mSessionHistory->SetBrowsingContext(nullptr);
145   }
146 }
147 
148 /* static */
Get(uint64_t aId)149 already_AddRefed<CanonicalBrowsingContext> CanonicalBrowsingContext::Get(
150     uint64_t aId) {
151   MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
152   return BrowsingContext::Get(aId).downcast<CanonicalBrowsingContext>();
153 }
154 
155 /* static */
Cast(BrowsingContext * aContext)156 CanonicalBrowsingContext* CanonicalBrowsingContext::Cast(
157     BrowsingContext* aContext) {
158   MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
159   return static_cast<CanonicalBrowsingContext*>(aContext);
160 }
161 
162 /* static */
Cast(const BrowsingContext * aContext)163 const CanonicalBrowsingContext* CanonicalBrowsingContext::Cast(
164     const BrowsingContext* aContext) {
165   MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
166   return static_cast<const CanonicalBrowsingContext*>(aContext);
167 }
168 
Cast(already_AddRefed<BrowsingContext> && aContext)169 already_AddRefed<CanonicalBrowsingContext> CanonicalBrowsingContext::Cast(
170     already_AddRefed<BrowsingContext>&& aContext) {
171   MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
172   return aContext.downcast<CanonicalBrowsingContext>();
173 }
174 
GetContentParent() const175 ContentParent* CanonicalBrowsingContext::GetContentParent() const {
176   if (mProcessId == 0) {
177     return nullptr;
178   }
179 
180   ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
181   return cpm->GetContentProcessById(ContentParentId(mProcessId));
182 }
183 
GetCurrentRemoteType(nsACString & aRemoteType,ErrorResult & aRv) const184 void CanonicalBrowsingContext::GetCurrentRemoteType(nsACString& aRemoteType,
185                                                     ErrorResult& aRv) const {
186   // If we're in the parent process, dump out the void string.
187   if (mProcessId == 0) {
188     aRemoteType = NOT_REMOTE_TYPE;
189     return;
190   }
191 
192   ContentParent* cp = GetContentParent();
193   if (!cp) {
194     aRv.Throw(NS_ERROR_UNEXPECTED);
195     return;
196   }
197 
198   aRemoteType = cp->GetRemoteType();
199 }
200 
SetOwnerProcessId(uint64_t aProcessId)201 void CanonicalBrowsingContext::SetOwnerProcessId(uint64_t aProcessId) {
202   MOZ_LOG(GetLog(), LogLevel::Debug,
203           ("SetOwnerProcessId for 0x%08" PRIx64 " (0x%08" PRIx64
204            " -> 0x%08" PRIx64 ")",
205            Id(), mProcessId, aProcessId));
206 
207   mProcessId = aProcessId;
208 }
209 
GetSecureBrowserUI()210 nsISecureBrowserUI* CanonicalBrowsingContext::GetSecureBrowserUI() {
211   if (!IsTop()) {
212     return nullptr;
213   }
214   if (!mSecureBrowserUI) {
215     mSecureBrowserUI = new nsSecureBrowserUI(this);
216   }
217   return mSecureBrowserUI;
218 }
219 
220 namespace {
221 // The DocShellProgressBridge is attached to a root content docshell loaded in
222 // the parent process. Notifications are paired up with the docshell which they
223 // came from, so that they can be fired to the correct
224 // BrowsingContextWebProgress and bubble through this tree separately.
225 //
226 // Notifications are filtered by a nsBrowserStatusFilter before being received
227 // by the DocShellProgressBridge.
228 class DocShellProgressBridge : public nsIWebProgressListener {
229  public:
230   NS_DECL_ISUPPORTS
231   // NOTE: This relies in the expansion of `NS_FORWARD_SAFE` and all listener
232   // methods accepting an `aWebProgress` argument. If this changes in the
233   // future, this may need to be written manually.
NS_FORWARD_SAFE_NSIWEBPROGRESSLISTENER(GetTargetContext (aWebProgress))234   NS_FORWARD_SAFE_NSIWEBPROGRESSLISTENER(GetTargetContext(aWebProgress))
235 
236   explicit DocShellProgressBridge(uint64_t aTopContextId)
237       : mTopContextId(aTopContextId) {}
238 
239  private:
240   virtual ~DocShellProgressBridge() = default;
241 
GetTargetContext(nsIWebProgress * aWebProgress)242   nsIWebProgressListener* GetTargetContext(nsIWebProgress* aWebProgress) {
243     RefPtr<CanonicalBrowsingContext> context;
244     if (nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(aWebProgress)) {
245       context = docShell->GetBrowsingContext()->Canonical();
246     } else {
247       context = CanonicalBrowsingContext::Get(mTopContextId);
248     }
249     return context && !context->IsDiscarded() ? context->GetWebProgress()
250                                               : nullptr;
251   }
252 
253   uint64_t mTopContextId = 0;
254 };
255 
256 NS_IMPL_ISUPPORTS(DocShellProgressBridge, nsIWebProgressListener)
257 }  // namespace
258 
MaybeAddAsProgressListener(nsIWebProgress * aWebProgress)259 void CanonicalBrowsingContext::MaybeAddAsProgressListener(
260     nsIWebProgress* aWebProgress) {
261   // Only add as a listener if the created docshell is a toplevel content
262   // docshell. We'll get notifications for all of our subframes through a single
263   // listener.
264   if (!IsTopContent()) {
265     return;
266   }
267 
268   if (!mDocShellProgressBridge) {
269     mDocShellProgressBridge = new DocShellProgressBridge(Id());
270     mStatusFilter = new nsBrowserStatusFilter();
271     mStatusFilter->AddProgressListener(mDocShellProgressBridge,
272                                        nsIWebProgress::NOTIFY_ALL);
273   }
274 
275   aWebProgress->AddProgressListener(mStatusFilter, nsIWebProgress::NOTIFY_ALL);
276 }
277 
ReplacedBy(CanonicalBrowsingContext * aNewContext,const RemotenessChangeOptions & aRemotenessOptions)278 void CanonicalBrowsingContext::ReplacedBy(
279     CanonicalBrowsingContext* aNewContext,
280     const RemotenessChangeOptions& aRemotenessOptions) {
281   MOZ_ASSERT(!aNewContext->mWebProgress);
282   MOZ_ASSERT(!aNewContext->mSessionHistory);
283   MOZ_ASSERT(IsTop() && aNewContext->IsTop());
284 
285   mIsReplaced = true;
286   aNewContext->mIsReplaced = false;
287 
288   if (mStatusFilter) {
289     mStatusFilter->RemoveProgressListener(mDocShellProgressBridge);
290     mStatusFilter = nullptr;
291   }
292 
293   mWebProgress->ContextReplaced(aNewContext);
294   aNewContext->mWebProgress = std::move(mWebProgress);
295 
296   // Use the Transaction for the fields which need to be updated whether or not
297   // the new context has been attached before.
298   // SetWithoutSyncing can be used if context hasn't been attached.
299   Transaction txn;
300   txn.SetBrowserId(GetBrowserId());
301   txn.SetHistoryID(GetHistoryID());
302   txn.SetExplicitActive(GetExplicitActive());
303   txn.SetHasRestoreData(GetHasRestoreData());
304   // As this is a different BrowsingContext, set InitialSandboxFlags to the
305   // current flags in the new context so that they also apply to any initial
306   // about:blank documents created in it.
307   txn.SetSandboxFlags(GetSandboxFlags());
308   txn.SetInitialSandboxFlags(GetSandboxFlags());
309   if (aNewContext->EverAttached()) {
310     MOZ_ALWAYS_SUCCEEDS(txn.Commit(aNewContext));
311   } else {
312     txn.CommitWithoutSyncing(aNewContext);
313   }
314 
315   aNewContext->mRestoreState = mRestoreState.forget();
316   MOZ_ALWAYS_SUCCEEDS(SetHasRestoreData(false));
317 
318   // XXXBFCache name handling is still a bit broken in Fission in general,
319   // at least in case name should be cleared.
320   if (aRemotenessOptions.mTryUseBFCache) {
321     MOZ_ASSERT(!aNewContext->EverAttached());
322     aNewContext->mFields.SetWithoutSyncing<IDX_Name>(GetName());
323     // We don't copy over HasLoadedNonInitialDocument here, we'll actually end
324     // up loading a new initial document at this point, before the real load.
325     // The real load will then end up setting HasLoadedNonInitialDocument to
326     // true.
327   }
328 
329   if (mSessionHistory) {
330     mSessionHistory->SetBrowsingContext(aNewContext);
331     // At this point we will be creating a new ChildSHistory in the child.
332     // That means that the child's epoch will be reset, so it makes sense to
333     // reset the epoch in the parent too.
334     mSessionHistory->SetEpoch(0, Nothing());
335     mSessionHistory.swap(aNewContext->mSessionHistory);
336     RefPtr<ChildSHistory> childSHistory = ForgetChildSHistory();
337     aNewContext->SetChildSHistory(childSHistory);
338   }
339 
340   if (mozilla::SessionHistoryInParent()) {
341     BackgroundSessionStorageManager::PropagateManager(Id(), aNewContext->Id());
342   }
343 
344   // Transfer the ownership of the priority active status from the old context
345   // to the new context.
346   aNewContext->mPriorityActive = mPriorityActive;
347   mPriorityActive = false;
348 
349   MOZ_ASSERT(aNewContext->mLoadingEntries.IsEmpty());
350   mLoadingEntries.SwapElements(aNewContext->mLoadingEntries);
351   MOZ_ASSERT(!aNewContext->mActiveEntry);
352   mActiveEntry.swap(aNewContext->mActiveEntry);
353 
354   aNewContext->mPermanentKey = mPermanentKey;
355   mPermanentKey.setNull();
356 }
357 
UpdateSecurityState()358 void CanonicalBrowsingContext::UpdateSecurityState() {
359   if (mSecureBrowserUI) {
360     mSecureBrowserUI->RecomputeSecurityFlags();
361   }
362 }
363 
GetWindowGlobals(nsTArray<RefPtr<WindowGlobalParent>> & aWindows)364 void CanonicalBrowsingContext::GetWindowGlobals(
365     nsTArray<RefPtr<WindowGlobalParent>>& aWindows) {
366   aWindows.SetCapacity(GetWindowContexts().Length());
367   for (auto& window : GetWindowContexts()) {
368     aWindows.AppendElement(static_cast<WindowGlobalParent*>(window.get()));
369   }
370 }
371 
GetCurrentWindowGlobal() const372 WindowGlobalParent* CanonicalBrowsingContext::GetCurrentWindowGlobal() const {
373   return static_cast<WindowGlobalParent*>(GetCurrentWindowContext());
374 }
375 
GetParentWindowContext()376 WindowGlobalParent* CanonicalBrowsingContext::GetParentWindowContext() {
377   return static_cast<WindowGlobalParent*>(
378       BrowsingContext::GetParentWindowContext());
379 }
380 
GetTopWindowContext()381 WindowGlobalParent* CanonicalBrowsingContext::GetTopWindowContext() {
382   return static_cast<WindowGlobalParent*>(
383       BrowsingContext::GetTopWindowContext());
384 }
385 
386 already_AddRefed<nsIWidget>
GetParentProcessWidgetContaining()387 CanonicalBrowsingContext::GetParentProcessWidgetContaining() {
388   // If our document is loaded in-process, such as chrome documents, get the
389   // widget directly from our outer window. Otherwise, try to get the widget
390   // from the toplevel content's browser's element.
391   nsCOMPtr<nsIWidget> widget;
392   if (nsGlobalWindowOuter* window = nsGlobalWindowOuter::Cast(GetDOMWindow())) {
393     widget = window->GetNearestWidget();
394   } else if (Element* topEmbedder = Top()->GetEmbedderElement()) {
395     widget = nsContentUtils::WidgetForContent(topEmbedder);
396     if (!widget) {
397       widget = nsContentUtils::WidgetForDocument(topEmbedder->OwnerDoc());
398     }
399   }
400 
401   if (widget) {
402     widget = widget->GetTopLevelWidget();
403   }
404 
405   return widget.forget();
406 }
407 
408 already_AddRefed<WindowGlobalParent>
GetEmbedderWindowGlobal() const409 CanonicalBrowsingContext::GetEmbedderWindowGlobal() const {
410   uint64_t windowId = GetEmbedderInnerWindowId();
411   if (windowId == 0) {
412     return nullptr;
413   }
414 
415   return WindowGlobalParent::GetByInnerWindowId(windowId);
416 }
417 
418 already_AddRefed<CanonicalBrowsingContext>
GetParentCrossChromeBoundary()419 CanonicalBrowsingContext::GetParentCrossChromeBoundary() {
420   if (GetParent()) {
421     return do_AddRef(Cast(GetParent()));
422   }
423   if (GetEmbedderElement()) {
424     return do_AddRef(
425         Cast(GetEmbedderElement()->OwnerDoc()->GetBrowsingContext()));
426   }
427   return nullptr;
428 }
429 
430 already_AddRefed<CanonicalBrowsingContext>
TopCrossChromeBoundary()431 CanonicalBrowsingContext::TopCrossChromeBoundary() {
432   RefPtr<CanonicalBrowsingContext> bc(this);
433   while (RefPtr<CanonicalBrowsingContext> parent =
434              bc->GetParentCrossChromeBoundary()) {
435     bc = parent.forget();
436   }
437   return bc.forget();
438 }
439 
GetTopChromeWindow()440 Nullable<WindowProxyHolder> CanonicalBrowsingContext::GetTopChromeWindow() {
441   RefPtr<CanonicalBrowsingContext> bc = TopCrossChromeBoundary();
442   if (bc->IsChrome()) {
443     return WindowProxyHolder(bc.forget());
444   }
445   return nullptr;
446 }
447 
GetSessionHistory()448 nsISHistory* CanonicalBrowsingContext::GetSessionHistory() {
449   if (!IsTop()) {
450     return Cast(Top())->GetSessionHistory();
451   }
452 
453   // Check GetChildSessionHistory() to make sure that this BrowsingContext has
454   // session history enabled.
455   if (!mSessionHistory && GetChildSessionHistory()) {
456     mSessionHistory = new nsSHistory(this);
457   }
458 
459   return mSessionHistory;
460 }
461 
GetActiveSessionHistoryEntry()462 SessionHistoryEntry* CanonicalBrowsingContext::GetActiveSessionHistoryEntry() {
463   return mActiveEntry;
464 }
465 
SetActiveSessionHistoryEntry(SessionHistoryEntry * aEntry)466 void CanonicalBrowsingContext::SetActiveSessionHistoryEntry(
467     SessionHistoryEntry* aEntry) {
468   mActiveEntry = aEntry;
469 }
470 
HasHistoryEntry(nsISHEntry * aEntry)471 bool CanonicalBrowsingContext::HasHistoryEntry(nsISHEntry* aEntry) {
472   // XXX Should we check also loading entries?
473   return aEntry && mActiveEntry == aEntry;
474 }
475 
SwapHistoryEntries(nsISHEntry * aOldEntry,nsISHEntry * aNewEntry)476 void CanonicalBrowsingContext::SwapHistoryEntries(nsISHEntry* aOldEntry,
477                                                   nsISHEntry* aNewEntry) {
478   // XXX Should we check also loading entries?
479   if (mActiveEntry == aOldEntry) {
480     nsCOMPtr<SessionHistoryEntry> newEntry = do_QueryInterface(aNewEntry);
481     mActiveEntry = newEntry.forget();
482   }
483 }
484 
AddLoadingSessionHistoryEntry(uint64_t aLoadId,SessionHistoryEntry * aEntry)485 void CanonicalBrowsingContext::AddLoadingSessionHistoryEntry(
486     uint64_t aLoadId, SessionHistoryEntry* aEntry) {
487   Unused << SetHistoryID(aEntry->DocshellID());
488   mLoadingEntries.AppendElement(LoadingSessionHistoryEntry{aLoadId, aEntry});
489 }
490 
GetLoadingSessionHistoryInfoFromParent(Maybe<LoadingSessionHistoryInfo> & aLoadingInfo,int32_t * aRequestedIndex,int32_t * aLength)491 void CanonicalBrowsingContext::GetLoadingSessionHistoryInfoFromParent(
492     Maybe<LoadingSessionHistoryInfo>& aLoadingInfo, int32_t* aRequestedIndex,
493     int32_t* aLength) {
494   *aRequestedIndex = -1;
495   *aLength = 0;
496 
497   nsISHistory* shistory = GetSessionHistory();
498   if (!shistory || !GetParent()) {
499     return;
500   }
501 
502   SessionHistoryEntry* parentSHE =
503       GetParent()->Canonical()->GetActiveSessionHistoryEntry();
504   if (parentSHE) {
505     int32_t index = -1;
506     for (BrowsingContext* sibling : GetParent()->Children()) {
507       ++index;
508       if (sibling == this) {
509         nsCOMPtr<nsISHEntry> shEntry;
510         parentSHE->GetChildSHEntryIfHasNoDynamicallyAddedChild(
511             index, getter_AddRefs(shEntry));
512         nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(shEntry);
513         if (she) {
514           aLoadingInfo.emplace(she);
515           mLoadingEntries.AppendElement(LoadingSessionHistoryEntry{
516               aLoadingInfo.value().mLoadId, she.get()});
517           *aRequestedIndex = shistory->GetRequestedIndex();
518           *aLength = shistory->GetCount();
519           Unused << SetHistoryID(she->DocshellID());
520         }
521         break;
522       }
523     }
524   }
525 }
526 
527 UniquePtr<LoadingSessionHistoryInfo>
CreateLoadingSessionHistoryEntryForLoad(nsDocShellLoadState * aLoadState,nsIChannel * aChannel)528 CanonicalBrowsingContext::CreateLoadingSessionHistoryEntryForLoad(
529     nsDocShellLoadState* aLoadState, nsIChannel* aChannel) {
530   RefPtr<SessionHistoryEntry> entry;
531   const LoadingSessionHistoryInfo* existingLoadingInfo =
532       aLoadState->GetLoadingSessionHistoryInfo();
533   if (existingLoadingInfo) {
534     entry = SessionHistoryEntry::GetByLoadId(existingLoadingInfo->mLoadId);
535     MOZ_LOG(gSHLog, LogLevel::Verbose,
536             ("SHEntry::GetByLoadId(%" PRIu64 ") -> %p",
537              existingLoadingInfo->mLoadId, entry.get()));
538     if (!entry) {
539       return nullptr;
540     }
541     Unused << SetHistoryEntryCount(entry->BCHistoryLength());
542   } else {
543     entry = new SessionHistoryEntry(aLoadState, aChannel);
544     if (IsTop()) {
545       // Only top level pages care about Get/SetPersist.
546       entry->SetPersist(
547           nsDocShell::ShouldAddToSessionHistory(aLoadState->URI(), aChannel));
548     } else if (mActiveEntry || !mLoadingEntries.IsEmpty()) {
549       entry->SetIsSubFrame(true);
550     }
551     entry->SetDocshellID(GetHistoryID());
552     entry->SetIsDynamicallyAdded(CreatedDynamically());
553     entry->SetForInitialLoad(true);
554   }
555   MOZ_DIAGNOSTIC_ASSERT(entry);
556 
557   UniquePtr<LoadingSessionHistoryInfo> loadingInfo;
558   if (existingLoadingInfo) {
559     loadingInfo = MakeUnique<LoadingSessionHistoryInfo>(*existingLoadingInfo);
560   } else {
561     loadingInfo = MakeUnique<LoadingSessionHistoryInfo>(entry);
562     mLoadingEntries.AppendElement(
563         LoadingSessionHistoryEntry{loadingInfo->mLoadId, entry});
564   }
565 
566   MOZ_ASSERT(SessionHistoryEntry::GetByLoadId(loadingInfo->mLoadId) == entry);
567 
568   return loadingInfo;
569 }
570 
571 UniquePtr<LoadingSessionHistoryInfo>
ReplaceLoadingSessionHistoryEntryForLoad(LoadingSessionHistoryInfo * aInfo,nsIChannel * aChannel)572 CanonicalBrowsingContext::ReplaceLoadingSessionHistoryEntryForLoad(
573     LoadingSessionHistoryInfo* aInfo, nsIChannel* aChannel) {
574   MOZ_ASSERT(aInfo);
575   MOZ_ASSERT(aChannel);
576 
577   SessionHistoryInfo newInfo = SessionHistoryInfo(
578       aChannel, aInfo->mInfo.LoadType(),
579       aInfo->mInfo.GetPartitionedPrincipalToInherit(), aInfo->mInfo.GetCsp());
580 
581   for (size_t i = 0; i < mLoadingEntries.Length(); ++i) {
582     if (mLoadingEntries[i].mLoadId == aInfo->mLoadId) {
583       RefPtr<SessionHistoryEntry> loadingEntry = mLoadingEntries[i].mEntry;
584       loadingEntry->SetInfo(&newInfo);
585 
586       if (IsTop()) {
587         // Only top level pages care about Get/SetPersist.
588         nsCOMPtr<nsIURI> uri;
589         aChannel->GetURI(getter_AddRefs(uri));
590         loadingEntry->SetPersist(
591             nsDocShell::ShouldAddToSessionHistory(uri, aChannel));
592       } else {
593         loadingEntry->SetIsSubFrame(aInfo->mInfo.IsSubFrame());
594       }
595       loadingEntry->SetDocshellID(GetHistoryID());
596       loadingEntry->SetIsDynamicallyAdded(CreatedDynamically());
597       return MakeUnique<LoadingSessionHistoryInfo>(loadingEntry, aInfo);
598     }
599   }
600   return nullptr;
601 }
602 
603 #ifdef NS_PRINTING
604 class PrintListenerAdapter final : public nsIWebProgressListener {
605  public:
PrintListenerAdapter(Promise * aPromise)606   explicit PrintListenerAdapter(Promise* aPromise) : mPromise(aPromise) {}
607 
608   NS_DECL_ISUPPORTS
609 
610   // NS_DECL_NSIWEBPROGRESSLISTENER
OnStateChange(nsIWebProgress * aWebProgress,nsIRequest * aRequest,uint32_t aStateFlags,nsresult aStatus)611   NS_IMETHOD OnStateChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
612                            uint32_t aStateFlags, nsresult aStatus) override {
613     if (aStateFlags & nsIWebProgressListener::STATE_STOP &&
614         aStateFlags & nsIWebProgressListener::STATE_IS_DOCUMENT && mPromise) {
615       mPromise->MaybeResolveWithUndefined();
616       mPromise = nullptr;
617     }
618     return NS_OK;
619   }
OnStatusChange(nsIWebProgress * aWebProgress,nsIRequest * aRequest,nsresult aStatus,const char16_t * aMessage)620   NS_IMETHOD OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
621                             nsresult aStatus,
622                             const char16_t* aMessage) override {
623     if (aStatus != NS_OK && mPromise) {
624       mPromise->MaybeReject(ErrorResult(aStatus));
625       mPromise = nullptr;
626     }
627     return NS_OK;
628   }
OnProgressChange(nsIWebProgress * aWebProgress,nsIRequest * aRequest,int32_t aCurSelfProgress,int32_t aMaxSelfProgress,int32_t aCurTotalProgress,int32_t aMaxTotalProgress)629   NS_IMETHOD OnProgressChange(nsIWebProgress* aWebProgress,
630                               nsIRequest* aRequest, int32_t aCurSelfProgress,
631                               int32_t aMaxSelfProgress,
632                               int32_t aCurTotalProgress,
633                               int32_t aMaxTotalProgress) override {
634     return NS_OK;
635   }
OnLocationChange(nsIWebProgress * aWebProgress,nsIRequest * aRequest,nsIURI * aLocation,uint32_t aFlags)636   NS_IMETHOD OnLocationChange(nsIWebProgress* aWebProgress,
637                               nsIRequest* aRequest, nsIURI* aLocation,
638                               uint32_t aFlags) override {
639     return NS_OK;
640   }
OnSecurityChange(nsIWebProgress * aWebProgress,nsIRequest * aRequest,uint32_t aState)641   NS_IMETHOD OnSecurityChange(nsIWebProgress* aWebProgress,
642                               nsIRequest* aRequest, uint32_t aState) override {
643     return NS_OK;
644   }
OnContentBlockingEvent(nsIWebProgress * aWebProgress,nsIRequest * aRequest,uint32_t aEvent)645   NS_IMETHOD OnContentBlockingEvent(nsIWebProgress* aWebProgress,
646                                     nsIRequest* aRequest,
647                                     uint32_t aEvent) override {
648     return NS_OK;
649   }
650 
651  private:
652   ~PrintListenerAdapter() = default;
653 
654   RefPtr<Promise> mPromise;
655 };
656 
NS_IMPL_ISUPPORTS(PrintListenerAdapter,nsIWebProgressListener)657 NS_IMPL_ISUPPORTS(PrintListenerAdapter, nsIWebProgressListener)
658 #endif
659 
660 already_AddRefed<Promise> CanonicalBrowsingContext::Print(
661     nsIPrintSettings* aPrintSettings, ErrorResult& aRv) {
662   RefPtr<Promise> promise = Promise::Create(GetIncumbentGlobal(), aRv);
663   if (NS_WARN_IF(aRv.Failed())) {
664     return promise.forget();
665   }
666 
667 #ifndef NS_PRINTING
668   promise->MaybeReject(ErrorResult(NS_ERROR_NOT_AVAILABLE));
669   return promise.forget();
670 #else
671 
672   auto listener = MakeRefPtr<PrintListenerAdapter>(promise);
673   if (IsInProcess()) {
674     RefPtr<nsGlobalWindowOuter> outerWindow =
675         nsGlobalWindowOuter::Cast(GetDOMWindow());
676     if (NS_WARN_IF(!outerWindow)) {
677       promise->MaybeReject(ErrorResult(NS_ERROR_FAILURE));
678       return promise.forget();
679     }
680 
681     ErrorResult rv;
682     outerWindow->Print(aPrintSettings, listener,
683                        /* aDocShellToCloneInto = */ nullptr,
684                        nsGlobalWindowOuter::IsPreview::No,
685                        nsGlobalWindowOuter::IsForWindowDotPrint::No,
686                        /* aPrintPreviewCallback = */ nullptr, rv);
687     if (rv.Failed()) {
688       promise->MaybeReject(std::move(rv));
689     }
690     return promise.forget();
691   }
692 
693   auto* browserParent = GetBrowserParent();
694   if (NS_WARN_IF(!browserParent)) {
695     promise->MaybeReject(ErrorResult(NS_ERROR_FAILURE));
696     return promise.forget();
697   }
698 
699   RefPtr<embedding::PrintingParent> printingParent =
700       browserParent->Manager()->GetPrintingParent();
701 
702   embedding::PrintData printData;
703   nsresult rv = printingParent->SerializeAndEnsureRemotePrintJob(
704       aPrintSettings, listener, nullptr, &printData);
705   if (NS_WARN_IF(NS_FAILED(rv))) {
706     promise->MaybeReject(ErrorResult(rv));
707     return promise.forget();
708   }
709 
710   if (NS_WARN_IF(!browserParent->SendPrint(this, printData))) {
711     promise->MaybeReject(ErrorResult(NS_ERROR_FAILURE));
712   }
713   return promise.forget();
714 #endif
715 }
716 
CallOnAllTopDescendants(const std::function<mozilla::CallState (CanonicalBrowsingContext *)> & aCallback)717 void CanonicalBrowsingContext::CallOnAllTopDescendants(
718     const std::function<mozilla::CallState(CanonicalBrowsingContext*)>&
719         aCallback) {
720 #ifdef DEBUG
721   RefPtr<CanonicalBrowsingContext> parent = GetParentCrossChromeBoundary();
722   MOZ_ASSERT(!parent, "Should only call on top chrome BC");
723 #endif
724 
725   nsTArray<RefPtr<BrowsingContextGroup>> groups;
726   BrowsingContextGroup::GetAllGroups(groups);
727   for (auto& browsingContextGroup : groups) {
728     for (auto& bc : browsingContextGroup->Toplevels()) {
729       if (bc == this) {
730         // Cannot be a descendent of myself so skip.
731         continue;
732       }
733 
734       RefPtr<CanonicalBrowsingContext> top =
735           bc->Canonical()->TopCrossChromeBoundary();
736       if (top == this) {
737         if (aCallback(bc->Canonical()) == CallState::Stop) {
738           return;
739         }
740       }
741     }
742   }
743 }
744 
SessionHistoryCommit(uint64_t aLoadId,const nsID & aChangeID,uint32_t aLoadType,bool aPersist,bool aCloneEntryChildren)745 void CanonicalBrowsingContext::SessionHistoryCommit(uint64_t aLoadId,
746                                                     const nsID& aChangeID,
747                                                     uint32_t aLoadType,
748                                                     bool aPersist,
749                                                     bool aCloneEntryChildren) {
750   MOZ_LOG(gSHLog, LogLevel::Verbose,
751           ("CanonicalBrowsingContext::SessionHistoryCommit %p %" PRIu64, this,
752            aLoadId));
753   MOZ_ASSERT(aLoadId != UINT64_MAX,
754              "Must not send special about:blank loadinfo to parent.");
755   for (size_t i = 0; i < mLoadingEntries.Length(); ++i) {
756     if (mLoadingEntries[i].mLoadId == aLoadId) {
757       nsSHistory* shistory = static_cast<nsSHistory*>(GetSessionHistory());
758       if (!shistory) {
759         SessionHistoryEntry::RemoveLoadId(aLoadId);
760         mLoadingEntries.RemoveElementAt(i);
761         return;
762       }
763 
764       CallerWillNotifyHistoryIndexAndLengthChanges caller(shistory);
765 
766       RefPtr<SessionHistoryEntry> newActiveEntry = mLoadingEntries[i].mEntry;
767 
768       bool loadFromSessionHistory = !newActiveEntry->ForInitialLoad();
769       newActiveEntry->SetForInitialLoad(false);
770       SessionHistoryEntry::RemoveLoadId(aLoadId);
771       mLoadingEntries.RemoveElementAt(i);
772 
773       // If there is a name in the new entry, clear the name of all contiguous
774       // entries. This is for https://html.spec.whatwg.org/#history-traversal
775       // Step 4.4.2.
776       nsAutoString nameOfNewEntry;
777       newActiveEntry->GetName(nameOfNewEntry);
778       if (!nameOfNewEntry.IsEmpty()) {
779         nsSHistory::WalkContiguousEntries(
780             newActiveEntry,
781             [](nsISHEntry* aEntry) { aEntry->SetName(EmptyString()); });
782       }
783 
784       bool addEntry = ShouldUpdateSessionHistory(aLoadType);
785       if (IsTop()) {
786         if (mActiveEntry && !mActiveEntry->GetFrameLoader()) {
787           bool sharesDocument = true;
788           mActiveEntry->SharesDocumentWith(newActiveEntry, &sharesDocument);
789           if (!sharesDocument) {
790             // If the old page won't be in the bfcache,
791             // clear the dynamic entries.
792             RemoveDynEntriesFromActiveSessionHistoryEntry();
793           }
794         }
795         mActiveEntry = newActiveEntry;
796 
797         if (LOAD_TYPE_HAS_FLAGS(aLoadType,
798                                 nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY)) {
799           // Replace the current entry with the new entry.
800           int32_t index = shistory->GetIndexForReplace();
801 
802           // If we're trying to replace an inexistant shistory entry then we
803           // should append instead.
804           addEntry = index < 0;
805           if (!addEntry) {
806             shistory->ReplaceEntry(index, mActiveEntry);
807           }
808         }
809 
810         if (loadFromSessionHistory) {
811           // XXX Synchronize browsing context tree and session history tree?
812           shistory->UpdateIndex();
813         } else if (addEntry) {
814           shistory->AddEntry(mActiveEntry, aPersist);
815           shistory->InternalSetRequestedIndex(-1);
816         }
817       } else {
818         // FIXME The old implementations adds it to the parent's mLSHE if there
819         //       is one, need to figure out if that makes sense here (peterv
820         //       doesn't think it would).
821         if (loadFromSessionHistory) {
822           if (mActiveEntry) {
823             // mActiveEntry is null if we're loading iframes from session
824             // history while also parent page is loading from session history.
825             // In that case there isn't anything to sync.
826             mActiveEntry->SyncTreesForSubframeNavigation(newActiveEntry, Top(),
827                                                          this);
828           }
829           mActiveEntry = newActiveEntry;
830           // FIXME UpdateIndex() here may update index too early (but even the
831           //       old implementation seems to have similar issues).
832           shistory->UpdateIndex();
833         } else if (addEntry) {
834           if (mActiveEntry) {
835             if (LOAD_TYPE_HAS_FLAGS(
836                     aLoadType, nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY)) {
837               // FIXME We need to make sure that when we create the info we
838               //       make a copy of the shared state.
839               mActiveEntry->ReplaceWith(*newActiveEntry);
840             } else {
841               // AddChildSHEntryHelper does update the index of the session
842               // history!
843               shistory->AddChildSHEntryHelper(mActiveEntry, newActiveEntry,
844                                               Top(), aCloneEntryChildren);
845               mActiveEntry = newActiveEntry;
846             }
847           } else {
848             SessionHistoryEntry* parentEntry = GetParent()->mActiveEntry;
849             // XXX What should happen if parent doesn't have mActiveEntry?
850             //     Or can that even happen ever?
851             if (parentEntry) {
852               mActiveEntry = newActiveEntry;
853               // FIXME Using IsInProcess for aUseRemoteSubframes isn't quite
854               //       right, but aUseRemoteSubframes should be going away.
855               parentEntry->AddChild(
856                   mActiveEntry,
857                   CreatedDynamically() ? -1 : GetParent()->IndexOf(this),
858                   IsInProcess());
859             }
860           }
861           shistory->InternalSetRequestedIndex(-1);
862         }
863       }
864 
865       ResetSHEntryHasUserInteractionCache();
866 
867       HistoryCommitIndexAndLength(aChangeID, caller);
868 
869       shistory->LogHistory();
870 
871       return;
872     }
873     // XXX Should the loading entries before [i] be removed?
874   }
875   // FIXME Should we throw an error if we don't find an entry for
876   // aSessionHistoryEntryId?
877 }
878 
CreateLoadInfo(SessionHistoryEntry * aEntry)879 static already_AddRefed<nsDocShellLoadState> CreateLoadInfo(
880     SessionHistoryEntry* aEntry) {
881   const SessionHistoryInfo& info = aEntry->Info();
882   RefPtr<nsDocShellLoadState> loadState(new nsDocShellLoadState(info.GetURI()));
883   info.FillLoadInfo(*loadState);
884   UniquePtr<LoadingSessionHistoryInfo> loadingInfo;
885   loadingInfo = MakeUnique<LoadingSessionHistoryInfo>(aEntry);
886   loadState->SetLoadingSessionHistoryInfo(std::move(loadingInfo));
887 
888   return loadState.forget();
889 }
890 
NotifyOnHistoryReload(bool aForceReload,bool & aCanReload,Maybe<RefPtr<nsDocShellLoadState>> & aLoadState,Maybe<bool> & aReloadActiveEntry)891 void CanonicalBrowsingContext::NotifyOnHistoryReload(
892     bool aForceReload, bool& aCanReload,
893     Maybe<RefPtr<nsDocShellLoadState>>& aLoadState,
894     Maybe<bool>& aReloadActiveEntry) {
895   MOZ_DIAGNOSTIC_ASSERT(!aLoadState);
896 
897   aCanReload = true;
898   nsISHistory* shistory = GetSessionHistory();
899   NS_ENSURE_TRUE_VOID(shistory);
900 
901   shistory->NotifyOnHistoryReload(&aCanReload);
902   if (!aCanReload) {
903     return;
904   }
905 
906   if (mActiveEntry) {
907     aLoadState.emplace(CreateLoadInfo(mActiveEntry));
908     aReloadActiveEntry.emplace(true);
909     if (aForceReload) {
910       shistory->RemoveFrameEntries(mActiveEntry);
911     }
912   } else if (!mLoadingEntries.IsEmpty()) {
913     const LoadingSessionHistoryEntry& loadingEntry =
914         mLoadingEntries.LastElement();
915     aLoadState.emplace(CreateLoadInfo(loadingEntry.mEntry));
916     aReloadActiveEntry.emplace(false);
917     if (aForceReload) {
918       SessionHistoryEntry* entry =
919           SessionHistoryEntry::GetByLoadId(loadingEntry.mLoadId);
920       if (entry) {
921         shistory->RemoveFrameEntries(entry);
922       }
923     }
924   }
925 
926   if (aLoadState) {
927     int32_t index = 0;
928     int32_t requestedIndex = -1;
929     int32_t length = 0;
930     shistory->GetIndex(&index);
931     shistory->GetRequestedIndex(&requestedIndex);
932     shistory->GetCount(&length);
933     aLoadState.ref()->SetLoadIsFromSessionHistory(
934         requestedIndex >= 0 ? requestedIndex : index, length,
935         aReloadActiveEntry.value());
936   }
937   // If we don't have an active entry and we don't have a loading entry then
938   // the nsDocShell will create a load state based on its document.
939 }
940 
SetActiveSessionHistoryEntry(const Maybe<nsPoint> & aPreviousScrollPos,SessionHistoryInfo * aInfo,uint32_t aLoadType,uint32_t aUpdatedCacheKey,const nsID & aChangeID)941 void CanonicalBrowsingContext::SetActiveSessionHistoryEntry(
942     const Maybe<nsPoint>& aPreviousScrollPos, SessionHistoryInfo* aInfo,
943     uint32_t aLoadType, uint32_t aUpdatedCacheKey, const nsID& aChangeID) {
944   nsISHistory* shistory = GetSessionHistory();
945   if (!shistory) {
946     return;
947   }
948   CallerWillNotifyHistoryIndexAndLengthChanges caller(shistory);
949 
950   RefPtr<SessionHistoryEntry> oldActiveEntry = mActiveEntry;
951   if (aPreviousScrollPos.isSome() && oldActiveEntry) {
952     oldActiveEntry->SetScrollPosition(aPreviousScrollPos.ref().x,
953                                       aPreviousScrollPos.ref().y);
954   }
955   mActiveEntry = new SessionHistoryEntry(aInfo);
956   mActiveEntry->SetDocshellID(GetHistoryID());
957   mActiveEntry->AdoptBFCacheEntry(oldActiveEntry);
958   if (aUpdatedCacheKey != 0) {
959     mActiveEntry->SharedInfo()->mCacheKey = aUpdatedCacheKey;
960   }
961 
962   if (IsTop()) {
963     Maybe<int32_t> previousEntryIndex, loadedEntryIndex;
964     shistory->AddToRootSessionHistory(
965         true, oldActiveEntry, this, mActiveEntry, aLoadType,
966         nsDocShell::ShouldAddToSessionHistory(aInfo->GetURI(), nullptr),
967         &previousEntryIndex, &loadedEntryIndex);
968   } else {
969     if (oldActiveEntry) {
970       shistory->AddChildSHEntryHelper(oldActiveEntry, mActiveEntry, Top(),
971                                       true);
972     } else if (GetParent() && GetParent()->mActiveEntry) {
973       GetParent()->mActiveEntry->AddChild(
974           mActiveEntry, CreatedDynamically() ? -1 : GetParent()->IndexOf(this),
975           UseRemoteSubframes());
976     }
977   }
978 
979   ResetSHEntryHasUserInteractionCache();
980 
981   shistory->InternalSetRequestedIndex(-1);
982 
983   // FIXME Need to do the equivalent of EvictContentViewersOrReplaceEntry.
984   HistoryCommitIndexAndLength(aChangeID, caller);
985 
986   static_cast<nsSHistory*>(shistory)->LogHistory();
987 }
988 
ReplaceActiveSessionHistoryEntry(SessionHistoryInfo * aInfo)989 void CanonicalBrowsingContext::ReplaceActiveSessionHistoryEntry(
990     SessionHistoryInfo* aInfo) {
991   if (!mActiveEntry) {
992     return;
993   }
994 
995   mActiveEntry->SetInfo(aInfo);
996   // Notify children of the update
997   nsSHistory* shistory = static_cast<nsSHistory*>(GetSessionHistory());
998   if (shistory) {
999     shistory->NotifyOnHistoryReplaceEntry();
1000     shistory->UpdateRootBrowsingContextState();
1001   }
1002 
1003   ResetSHEntryHasUserInteractionCache();
1004 
1005   // FIXME Need to do the equivalent of EvictContentViewersOrReplaceEntry.
1006 }
1007 
RemoveDynEntriesFromActiveSessionHistoryEntry()1008 void CanonicalBrowsingContext::RemoveDynEntriesFromActiveSessionHistoryEntry() {
1009   nsISHistory* shistory = GetSessionHistory();
1010   // In theory shistory can be null here if the method is called right after
1011   // CanonicalBrowsingContext::ReplacedBy call.
1012   NS_ENSURE_TRUE_VOID(shistory);
1013   nsCOMPtr<nsISHEntry> root = nsSHistory::GetRootSHEntry(mActiveEntry);
1014   shistory->RemoveDynEntries(shistory->GetIndexOfEntry(root), mActiveEntry);
1015 }
1016 
RemoveFromSessionHistory(const nsID & aChangeID)1017 void CanonicalBrowsingContext::RemoveFromSessionHistory(const nsID& aChangeID) {
1018   nsSHistory* shistory = static_cast<nsSHistory*>(GetSessionHistory());
1019   if (shistory) {
1020     CallerWillNotifyHistoryIndexAndLengthChanges caller(shistory);
1021     nsCOMPtr<nsISHEntry> root = nsSHistory::GetRootSHEntry(mActiveEntry);
1022     bool didRemove;
1023     AutoTArray<nsID, 16> ids({GetHistoryID()});
1024     shistory->RemoveEntries(ids, shistory->GetIndexOfEntry(root), &didRemove);
1025     if (didRemove) {
1026       BrowsingContext* rootBC = shistory->GetBrowsingContext();
1027       if (rootBC) {
1028         if (!rootBC->IsInProcess()) {
1029           Unused << rootBC->Canonical()
1030                         ->GetContentParent()
1031                         ->SendDispatchLocationChangeEvent(rootBC);
1032         } else if (rootBC->GetDocShell()) {
1033           rootBC->GetDocShell()->DispatchLocationChangeEvent();
1034         }
1035       }
1036     }
1037     HistoryCommitIndexAndLength(aChangeID, caller);
1038   }
1039 }
1040 
HistoryGo(int32_t aOffset,uint64_t aHistoryEpoch,bool aRequireUserInteraction,bool aUserActivation,Maybe<ContentParentId> aContentId,std::function<void (int32_t &&)> && aResolver)1041 void CanonicalBrowsingContext::HistoryGo(
1042     int32_t aOffset, uint64_t aHistoryEpoch, bool aRequireUserInteraction,
1043     bool aUserActivation, Maybe<ContentParentId> aContentId,
1044     std::function<void(int32_t&&)>&& aResolver) {
1045   if (aRequireUserInteraction && aOffset != -1 && aOffset != 1) {
1046     NS_ERROR(
1047         "aRequireUserInteraction may only be used with an offset of -1 or 1");
1048     return;
1049   }
1050 
1051   nsSHistory* shistory = static_cast<nsSHistory*>(GetSessionHistory());
1052   if (!shistory) {
1053     return;
1054   }
1055 
1056   CheckedInt<int32_t> index = shistory->GetRequestedIndex() >= 0
1057                                   ? shistory->GetRequestedIndex()
1058                                   : shistory->Index();
1059   MOZ_LOG(gSHLog, LogLevel::Debug,
1060           ("HistoryGo(%d->%d) epoch %" PRIu64 "/id %" PRIu64, aOffset,
1061            (index + aOffset).value(), aHistoryEpoch,
1062            (uint64_t)(aContentId.isSome() ? aContentId.value() : 0)));
1063 
1064   while (true) {
1065     index += aOffset;
1066     if (!index.isValid()) {
1067       MOZ_LOG(gSHLog, LogLevel::Debug, ("Invalid index"));
1068       return;
1069     }
1070 
1071     // Check for user interaction if desired, except for the first and last
1072     // history entries. We compare with >= to account for the case where
1073     // aOffset >= length.
1074     if (!aRequireUserInteraction || index.value() >= shistory->Length() - 1 ||
1075         index.value() <= 0) {
1076       break;
1077     }
1078     if (shistory->HasUserInteractionAtIndex(index.value())) {
1079       break;
1080     }
1081   }
1082 
1083   // Implement aborting additional history navigations from within the same
1084   // event spin of the content process.
1085 
1086   uint64_t epoch;
1087   bool sameEpoch = false;
1088   Maybe<ContentParentId> id;
1089   shistory->GetEpoch(epoch, id);
1090 
1091   if (aContentId == id && epoch >= aHistoryEpoch) {
1092     sameEpoch = true;
1093     MOZ_LOG(gSHLog, LogLevel::Debug, ("Same epoch/id"));
1094   }
1095   // Don't update the epoch until we know if the target index is valid
1096 
1097   // GoToIndex checks that index is >= 0 and < length.
1098   nsTArray<nsSHistory::LoadEntryResult> loadResults;
1099   nsresult rv = shistory->GotoIndex(index.value(), loadResults, sameEpoch,
1100                                     aUserActivation);
1101   if (NS_FAILED(rv)) {
1102     MOZ_LOG(gSHLog, LogLevel::Debug,
1103             ("Dropping HistoryGo - bad index or same epoch (not in same doc)"));
1104     return;
1105   }
1106   if (epoch < aHistoryEpoch || aContentId != id) {
1107     MOZ_LOG(gSHLog, LogLevel::Debug, ("Set epoch"));
1108     shistory->SetEpoch(aHistoryEpoch, aContentId);
1109   }
1110   aResolver(shistory->GetRequestedIndex());
1111   nsSHistory::LoadURIs(loadResults);
1112 }
1113 
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)1114 JSObject* CanonicalBrowsingContext::WrapObject(
1115     JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
1116   return CanonicalBrowsingContext_Binding::Wrap(aCx, this, aGivenProto);
1117 }
1118 
DispatchWheelZoomChange(bool aIncrease)1119 void CanonicalBrowsingContext::DispatchWheelZoomChange(bool aIncrease) {
1120   Element* element = Top()->GetEmbedderElement();
1121   if (!element) {
1122     return;
1123   }
1124 
1125   auto event = aIncrease ? u"DoZoomEnlargeBy10"_ns : u"DoZoomReduceBy10"_ns;
1126   auto dispatcher = MakeRefPtr<AsyncEventDispatcher>(
1127       element, event, CanBubble::eYes, ChromeOnlyDispatch::eYes);
1128   dispatcher->PostDOMEvent();
1129 }
1130 
CanonicalDiscard()1131 void CanonicalBrowsingContext::CanonicalDiscard() {
1132   if (mTabMediaController) {
1133     mTabMediaController->Shutdown();
1134     mTabMediaController = nullptr;
1135   }
1136 
1137   if (mWebProgress) {
1138     RefPtr<BrowsingContextWebProgress> progress = mWebProgress;
1139     progress->ContextDiscarded();
1140   }
1141 
1142   if (IsTop()) {
1143     BackgroundSessionStorageManager::RemoveManager(Id());
1144   }
1145 
1146   CancelSessionStoreUpdate();
1147 
1148   if (UsePrivateBrowsing() && EverAttached() && IsContent()) {
1149     DecreasePrivateCount();
1150   }
1151 }
1152 
CanonicalAttach()1153 void CanonicalBrowsingContext::CanonicalAttach() {
1154   if (UsePrivateBrowsing() && IsContent()) {
1155     IncreasePrivateCount();
1156   }
1157 }
1158 
AdjustPrivateBrowsingCount(bool aPrivateBrowsing)1159 void CanonicalBrowsingContext::AdjustPrivateBrowsingCount(
1160     bool aPrivateBrowsing) {
1161   if (IsDiscarded() || !EverAttached() || IsChrome()) {
1162     return;
1163   }
1164 
1165   MOZ_DIAGNOSTIC_ASSERT(aPrivateBrowsing == UsePrivateBrowsing());
1166   if (aPrivateBrowsing) {
1167     IncreasePrivateCount();
1168   } else {
1169     DecreasePrivateCount();
1170   }
1171 }
1172 
NotifyStartDelayedAutoplayMedia()1173 void CanonicalBrowsingContext::NotifyStartDelayedAutoplayMedia() {
1174   WindowContext* windowContext = GetCurrentWindowContext();
1175   if (!windowContext) {
1176     return;
1177   }
1178 
1179   // As this function would only be called when user click the play icon on the
1180   // tab bar. That's clear user intent to play, so gesture activate the window
1181   // context so that the block-autoplay logic allows the media to autoplay.
1182   windowContext->NotifyUserGestureActivation();
1183   AUTOPLAY_LOG("NotifyStartDelayedAutoplayMedia for chrome bc 0x%08" PRIx64,
1184                Id());
1185   StartDelayedAutoplayMediaComponents();
1186   // Notfiy all content browsing contexts which are related with the canonical
1187   // browsing content tree to start delayed autoplay media.
1188 
1189   Group()->EachParent([&](ContentParent* aParent) {
1190     Unused << aParent->SendStartDelayedAutoplayMediaComponents(this);
1191   });
1192 }
1193 
NotifyMediaMutedChanged(bool aMuted,ErrorResult & aRv)1194 void CanonicalBrowsingContext::NotifyMediaMutedChanged(bool aMuted,
1195                                                        ErrorResult& aRv) {
1196   MOZ_ASSERT(!GetParent(),
1197              "Notify media mute change on non top-level context!");
1198   SetMuted(aMuted, aRv);
1199 }
1200 
CountSiteOrigins(GlobalObject & aGlobal,const Sequence<OwningNonNull<BrowsingContext>> & aRoots)1201 uint32_t CanonicalBrowsingContext::CountSiteOrigins(
1202     GlobalObject& aGlobal,
1203     const Sequence<OwningNonNull<BrowsingContext>>& aRoots) {
1204   nsTHashSet<nsCString> uniqueSiteOrigins;
1205 
1206   for (const auto& root : aRoots) {
1207     root->PreOrderWalk([&](BrowsingContext* aContext) {
1208       WindowGlobalParent* windowGlobalParent =
1209           aContext->Canonical()->GetCurrentWindowGlobal();
1210       if (windowGlobalParent) {
1211         nsIPrincipal* documentPrincipal =
1212             windowGlobalParent->DocumentPrincipal();
1213 
1214         bool isContentPrincipal = documentPrincipal->GetIsContentPrincipal();
1215         if (isContentPrincipal) {
1216           nsCString siteOrigin;
1217           documentPrincipal->GetSiteOrigin(siteOrigin);
1218           uniqueSiteOrigins.Insert(siteOrigin);
1219         }
1220       }
1221     });
1222   }
1223 
1224   return uniqueSiteOrigins.Count();
1225 }
1226 
UpdateMediaControlAction(const MediaControlAction & aAction)1227 void CanonicalBrowsingContext::UpdateMediaControlAction(
1228     const MediaControlAction& aAction) {
1229   if (IsDiscarded()) {
1230     return;
1231   }
1232   ContentMediaControlKeyHandler::HandleMediaControlAction(this, aAction);
1233   Group()->EachParent([&](ContentParent* aParent) {
1234     Unused << aParent->SendUpdateMediaControlAction(this, aAction);
1235   });
1236 }
1237 
LoadURI(const nsAString & aURI,const LoadURIOptions & aOptions,ErrorResult & aError)1238 void CanonicalBrowsingContext::LoadURI(const nsAString& aURI,
1239                                        const LoadURIOptions& aOptions,
1240                                        ErrorResult& aError) {
1241   RefPtr<nsDocShellLoadState> loadState;
1242   nsresult rv = nsDocShellLoadState::CreateFromLoadURIOptions(
1243       this, aURI, aOptions, getter_AddRefs(loadState));
1244 
1245   if (rv == NS_ERROR_MALFORMED_URI) {
1246     DisplayLoadError(aURI);
1247     return;
1248   }
1249 
1250   if (NS_FAILED(rv)) {
1251     aError.Throw(rv);
1252     return;
1253   }
1254 
1255   LoadURI(loadState, true);
1256 }
1257 
GoBack(const Optional<int32_t> & aCancelContentJSEpoch,bool aRequireUserInteraction,bool aUserActivation)1258 void CanonicalBrowsingContext::GoBack(
1259     const Optional<int32_t>& aCancelContentJSEpoch,
1260     bool aRequireUserInteraction, bool aUserActivation) {
1261   if (IsDiscarded()) {
1262     return;
1263   }
1264 
1265   // Stop any known network loads if necessary.
1266   if (mCurrentLoad) {
1267     mCurrentLoad->Cancel(NS_BINDING_ABORTED);
1268   }
1269 
1270   if (nsDocShell* docShell = nsDocShell::Cast(GetDocShell())) {
1271     if (aCancelContentJSEpoch.WasPassed()) {
1272       docShell->SetCancelContentJSEpoch(aCancelContentJSEpoch.Value());
1273     }
1274     docShell->GoBack(aRequireUserInteraction, aUserActivation);
1275   } else if (ContentParent* cp = GetContentParent()) {
1276     Maybe<int32_t> cancelContentJSEpoch;
1277     if (aCancelContentJSEpoch.WasPassed()) {
1278       cancelContentJSEpoch = Some(aCancelContentJSEpoch.Value());
1279     }
1280     Unused << cp->SendGoBack(this, cancelContentJSEpoch,
1281                              aRequireUserInteraction, aUserActivation);
1282   }
1283 }
GoForward(const Optional<int32_t> & aCancelContentJSEpoch,bool aRequireUserInteraction,bool aUserActivation)1284 void CanonicalBrowsingContext::GoForward(
1285     const Optional<int32_t>& aCancelContentJSEpoch,
1286     bool aRequireUserInteraction, bool aUserActivation) {
1287   if (IsDiscarded()) {
1288     return;
1289   }
1290 
1291   // Stop any known network loads if necessary.
1292   if (mCurrentLoad) {
1293     mCurrentLoad->Cancel(NS_BINDING_ABORTED);
1294   }
1295 
1296   if (auto* docShell = nsDocShell::Cast(GetDocShell())) {
1297     if (aCancelContentJSEpoch.WasPassed()) {
1298       docShell->SetCancelContentJSEpoch(aCancelContentJSEpoch.Value());
1299     }
1300     docShell->GoForward(aRequireUserInteraction, aUserActivation);
1301   } else if (ContentParent* cp = GetContentParent()) {
1302     Maybe<int32_t> cancelContentJSEpoch;
1303     if (aCancelContentJSEpoch.WasPassed()) {
1304       cancelContentJSEpoch.emplace(aCancelContentJSEpoch.Value());
1305     }
1306     Unused << cp->SendGoForward(this, cancelContentJSEpoch,
1307                                 aRequireUserInteraction, aUserActivation);
1308   }
1309 }
GoToIndex(int32_t aIndex,const Optional<int32_t> & aCancelContentJSEpoch,bool aUserActivation)1310 void CanonicalBrowsingContext::GoToIndex(
1311     int32_t aIndex, const Optional<int32_t>& aCancelContentJSEpoch,
1312     bool aUserActivation) {
1313   if (IsDiscarded()) {
1314     return;
1315   }
1316 
1317   // Stop any known network loads if necessary.
1318   if (mCurrentLoad) {
1319     mCurrentLoad->Cancel(NS_BINDING_ABORTED);
1320   }
1321 
1322   if (auto* docShell = nsDocShell::Cast(GetDocShell())) {
1323     if (aCancelContentJSEpoch.WasPassed()) {
1324       docShell->SetCancelContentJSEpoch(aCancelContentJSEpoch.Value());
1325     }
1326     docShell->GotoIndex(aIndex, aUserActivation);
1327   } else if (ContentParent* cp = GetContentParent()) {
1328     Maybe<int32_t> cancelContentJSEpoch;
1329     if (aCancelContentJSEpoch.WasPassed()) {
1330       cancelContentJSEpoch.emplace(aCancelContentJSEpoch.Value());
1331     }
1332     Unused << cp->SendGoToIndex(this, aIndex, cancelContentJSEpoch,
1333                                 aUserActivation);
1334   }
1335 }
Reload(uint32_t aReloadFlags)1336 void CanonicalBrowsingContext::Reload(uint32_t aReloadFlags) {
1337   if (IsDiscarded()) {
1338     return;
1339   }
1340 
1341   // Stop any known network loads if necessary.
1342   if (mCurrentLoad) {
1343     mCurrentLoad->Cancel(NS_BINDING_ABORTED);
1344   }
1345 
1346   if (auto* docShell = nsDocShell::Cast(GetDocShell())) {
1347     docShell->Reload(aReloadFlags);
1348   } else if (ContentParent* cp = GetContentParent()) {
1349     Unused << cp->SendReload(this, aReloadFlags);
1350   }
1351 }
1352 
Stop(uint32_t aStopFlags)1353 void CanonicalBrowsingContext::Stop(uint32_t aStopFlags) {
1354   if (IsDiscarded()) {
1355     return;
1356   }
1357 
1358   // Stop any known network loads if necessary.
1359   if (mCurrentLoad && (aStopFlags & nsIWebNavigation::STOP_NETWORK)) {
1360     mCurrentLoad->Cancel(NS_BINDING_ABORTED);
1361   }
1362 
1363   // Ask the docshell to stop to handle loads that haven't
1364   // yet reached here, as well as non-network activity.
1365   if (auto* docShell = nsDocShell::Cast(GetDocShell())) {
1366     docShell->Stop(aStopFlags);
1367   } else if (ContentParent* cp = GetContentParent()) {
1368     Unused << cp->SendStopLoad(this, aStopFlags);
1369   }
1370 }
1371 
ProcessLaunched()1372 void CanonicalBrowsingContext::PendingRemotenessChange::ProcessLaunched() {
1373   if (!mPromise) {
1374     return;
1375   }
1376 
1377   if (mContentParent) {
1378     // If our new content process is still unloading from a previous process
1379     // switch, wait for that unload to complete before continuing.
1380     auto found = mTarget->FindUnloadingHost(mContentParent->ChildID());
1381     if (found != mTarget->mUnloadingHosts.end()) {
1382       found->mCallbacks.AppendElement(
1383           [self = RefPtr{this}]() { self->ProcessReady(); });
1384       return;
1385     }
1386   }
1387 
1388   ProcessReady();
1389 }
1390 
ProcessReady()1391 void CanonicalBrowsingContext::PendingRemotenessChange::ProcessReady() {
1392   if (!mPromise) {
1393     return;
1394   }
1395 
1396   // Wait for our blocker promise to resolve, if present.
1397   if (mPrepareToChangePromise) {
1398     mPrepareToChangePromise->Then(
1399         GetMainThreadSerialEventTarget(), __func__,
1400         [self = RefPtr{this}](bool) { self->Finish(); },
1401         [self = RefPtr{this}](nsresult aRv) { self->Cancel(aRv); });
1402     return;
1403   }
1404 
1405   Finish();
1406 }
1407 
Finish()1408 void CanonicalBrowsingContext::PendingRemotenessChange::Finish() {
1409   if (!mPromise) {
1410     return;
1411   }
1412 
1413   // If this BrowsingContext is embedded within the parent process, perform the
1414   // process switch directly.
1415   nsresult rv = mTarget->IsTopContent() ? FinishTopContent() : FinishSubframe();
1416   if (NS_FAILED(rv)) {
1417     NS_WARNING("Error finishing PendingRemotenessChange!");
1418     Cancel(rv);
1419   } else {
1420     Clear();
1421   }
1422 }
1423 
1424 // Logic for finishing a toplevel process change embedded within the parent
1425 // process. Due to frontend integration the logic differs substantially from
1426 // subframe process switches, and is handled separately.
FinishTopContent()1427 nsresult CanonicalBrowsingContext::PendingRemotenessChange::FinishTopContent() {
1428   MOZ_DIAGNOSTIC_ASSERT(mTarget->IsTop(),
1429                         "We shouldn't be trying to change the remoteness of "
1430                         "non-remote iframes");
1431 
1432   // While process switching, we need to check if any of our ancestors are
1433   // discarded or no longer current, in which case the process switch needs to
1434   // be aborted.
1435   RefPtr<CanonicalBrowsingContext> target(mTarget);
1436   if (target->IsDiscarded() || !target->AncestorsAreCurrent()) {
1437     return NS_ERROR_FAILURE;
1438   }
1439 
1440   Element* browserElement = target->GetEmbedderElement();
1441   if (!browserElement) {
1442     return NS_ERROR_FAILURE;
1443   }
1444 
1445   nsCOMPtr<nsIBrowser> browser = browserElement->AsBrowser();
1446   if (!browser) {
1447     return NS_ERROR_FAILURE;
1448   }
1449 
1450   RefPtr<nsFrameLoaderOwner> frameLoaderOwner = do_QueryObject(browserElement);
1451   MOZ_RELEASE_ASSERT(frameLoaderOwner,
1452                      "embedder browser must be nsFrameLoaderOwner");
1453 
1454   // If we're process switching a browsing context in private browsing
1455   // mode we might decrease the private browsing count to '0', which
1456   // would make us fire "last-pb-context-exited" and drop the private
1457   // session. To prevent that we artificially increment the number of
1458   // private browsing contexts with '1' until the process switch is done.
1459   bool usePrivateBrowsing = mTarget->UsePrivateBrowsing();
1460   if (usePrivateBrowsing) {
1461     IncreasePrivateCount();
1462   }
1463 
1464   auto restorePrivateCount = MakeScopeExit([usePrivateBrowsing]() {
1465     if (usePrivateBrowsing) {
1466       DecreasePrivateCount();
1467     }
1468   });
1469 
1470   // Tell frontend code that this browser element is about to change process.
1471   nsresult rv = browser->BeforeChangeRemoteness();
1472   if (NS_FAILED(rv)) {
1473     return rv;
1474   }
1475 
1476   // Some frontend code checks the value of the `remote` attribute on the
1477   // browser to determine if it is remote, so update the value.
1478   browserElement->SetAttr(kNameSpaceID_None, nsGkAtoms::remote,
1479                           mContentParent ? u"true"_ns : u"false"_ns,
1480                           /* notify */ true);
1481 
1482   // The process has been created, hand off to nsFrameLoaderOwner to finish
1483   // the process switch.
1484   ErrorResult error;
1485   frameLoaderOwner->ChangeRemotenessToProcess(mContentParent, mOptions,
1486                                               mSpecificGroup, error);
1487   if (error.Failed()) {
1488     return error.StealNSResult();
1489   }
1490 
1491   // Tell frontend the load is done.
1492   bool loadResumed = false;
1493   rv = browser->FinishChangeRemoteness(mPendingSwitchId, &loadResumed);
1494   if (NS_WARN_IF(NS_FAILED(rv))) {
1495     return rv;
1496   }
1497 
1498   // We did it! The process switch is complete.
1499   RefPtr<nsFrameLoader> frameLoader = frameLoaderOwner->GetFrameLoader();
1500   RefPtr<BrowserParent> newBrowser = frameLoader->GetBrowserParent();
1501   if (!newBrowser) {
1502     if (mContentParent) {
1503       // Failed to create the BrowserParent somehow! Abort the process switch
1504       // attempt.
1505       return NS_ERROR_UNEXPECTED;
1506     }
1507 
1508     if (!loadResumed) {
1509       RefPtr<nsDocShell> newDocShell = frameLoader->GetDocShell(error);
1510       if (error.Failed()) {
1511         return error.StealNSResult();
1512       }
1513 
1514       rv = newDocShell->ResumeRedirectedLoad(mPendingSwitchId,
1515                                              /* aHistoryIndex */ -1);
1516       if (NS_FAILED(rv)) {
1517         return rv;
1518       }
1519     }
1520   } else if (!loadResumed) {
1521     newBrowser->ResumeLoad(mPendingSwitchId);
1522   }
1523 
1524   mPromise->Resolve(newBrowser, __func__);
1525   return NS_OK;
1526 }
1527 
FinishSubframe()1528 nsresult CanonicalBrowsingContext::PendingRemotenessChange::FinishSubframe() {
1529   MOZ_DIAGNOSTIC_ASSERT(!mOptions.mReplaceBrowsingContext,
1530                         "Cannot replace BC for subframe");
1531   MOZ_DIAGNOSTIC_ASSERT(!mTarget->IsTop());
1532 
1533   // While process switching, we need to check if any of our ancestors are
1534   // discarded or no longer current, in which case the process switch needs to
1535   // be aborted.
1536   RefPtr<CanonicalBrowsingContext> target(mTarget);
1537   if (target->IsDiscarded() || !target->AncestorsAreCurrent()) {
1538     return NS_ERROR_FAILURE;
1539   }
1540 
1541   if (NS_WARN_IF(!mContentParent)) {
1542     return NS_ERROR_FAILURE;
1543   }
1544 
1545   RefPtr<WindowGlobalParent> embedderWindow = target->GetParentWindowContext();
1546   if (NS_WARN_IF(!embedderWindow) || NS_WARN_IF(!embedderWindow->CanSend())) {
1547     return NS_ERROR_FAILURE;
1548   }
1549 
1550   RefPtr<BrowserParent> embedderBrowser = embedderWindow->GetBrowserParent();
1551   if (NS_WARN_IF(!embedderBrowser)) {
1552     return NS_ERROR_FAILURE;
1553   }
1554 
1555   RefPtr<BrowserParent> oldBrowser = target->GetBrowserParent();
1556   target->SetCurrentBrowserParent(nullptr);
1557 
1558   // If we were in a remote frame, trigger unloading of the remote window. The
1559   // previous BrowserParent is registered in `mUnloadingHosts` and will only be
1560   // cleared when the BrowserParent is fully destroyed.
1561   bool wasRemote = oldBrowser && oldBrowser->GetBrowsingContext() == target;
1562   if (wasRemote) {
1563     MOZ_DIAGNOSTIC_ASSERT(oldBrowser != embedderBrowser);
1564     MOZ_DIAGNOSTIC_ASSERT(oldBrowser->IsDestroyed() ||
1565                           oldBrowser->GetBrowserBridgeParent());
1566 
1567     // `oldBrowser` will clear the `UnloadingHost` status once the actor has
1568     // been destroyed.
1569     if (oldBrowser->CanSend()) {
1570       target->StartUnloadingHost(oldBrowser->Manager()->ChildID());
1571       Unused << oldBrowser->SendWillChangeProcess();
1572       oldBrowser->Destroy();
1573     }
1574   }
1575 
1576   // Update which process is considered the current owner
1577   target->SetOwnerProcessId(mContentParent->ChildID());
1578 
1579   // If we're switching from remote to local, we don't need to create a
1580   // BrowserBridge, and can instead perform the switch directly.
1581   if (mContentParent == embedderBrowser->Manager()) {
1582     MOZ_DIAGNOSTIC_ASSERT(
1583         mPendingSwitchId,
1584         "We always have a PendingSwitchId, except for print-preview loads, "
1585         "which will never perform a process-switch to being in-process with "
1586         "their embedder");
1587     MOZ_DIAGNOSTIC_ASSERT(wasRemote,
1588                           "Attempt to process-switch from local to local?");
1589 
1590     target->SetCurrentBrowserParent(embedderBrowser);
1591     Unused << embedderWindow->SendMakeFrameLocal(target, mPendingSwitchId);
1592     mPromise->Resolve(embedderBrowser, __func__);
1593     return NS_OK;
1594   }
1595 
1596   // The BrowsingContext will be remote, either as an already-remote frame
1597   // changing processes, or as a local frame becoming remote. Construct a new
1598   // BrowserBridgeParent to host the remote content.
1599   target->SetCurrentBrowserParent(nullptr);
1600 
1601   MOZ_DIAGNOSTIC_ASSERT(target->UseRemoteTabs() && target->UseRemoteSubframes(),
1602                         "Not supported without fission");
1603   uint32_t chromeFlags = nsIWebBrowserChrome::CHROME_REMOTE_WINDOW |
1604                          nsIWebBrowserChrome::CHROME_FISSION_WINDOW;
1605   if (target->UsePrivateBrowsing()) {
1606     chromeFlags |= nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW;
1607   }
1608 
1609   nsCOMPtr<nsIPrincipal> initialPrincipal =
1610       NullPrincipal::CreateWithInheritedAttributes(
1611           target->OriginAttributesRef(),
1612           /* isFirstParty */ false);
1613   WindowGlobalInit windowInit =
1614       WindowGlobalActor::AboutBlankInitializer(target, initialPrincipal);
1615 
1616   // Create and initialize our new BrowserBridgeParent.
1617   TabId tabId(nsContentUtils::GenerateTabId());
1618   RefPtr<BrowserBridgeParent> bridge = new BrowserBridgeParent();
1619   nsresult rv = bridge->InitWithProcess(embedderBrowser, mContentParent,
1620                                         windowInit, chromeFlags, tabId);
1621   if (NS_WARN_IF(NS_FAILED(rv))) {
1622     // If we've already destroyed our previous document, make a best-effort
1623     // attempt to recover from this failure and show the crashed tab UI. We only
1624     // do this in the previously-remote case, as previously in-process frames
1625     // will have their navigation cancelled, and will remain visible.
1626     if (wasRemote) {
1627       target->ShowSubframeCrashedUI(oldBrowser->GetBrowserBridgeParent());
1628     }
1629     return rv;
1630   }
1631 
1632   // Tell the embedder process a remoteness change is in-process. When this is
1633   // acknowledged, reset the in-flight ID if it used to be an in-process load.
1634   RefPtr<BrowserParent> newBrowser = bridge->GetBrowserParent();
1635   {
1636     // If we weren't remote, mark our embedder window browser as unloading until
1637     // our embedder process has acked our MakeFrameRemote message.
1638     Maybe<uint64_t> clearChildID;
1639     if (!wasRemote) {
1640       clearChildID = Some(embedderBrowser->Manager()->ChildID());
1641       target->StartUnloadingHost(*clearChildID);
1642     }
1643     auto callback = [target, clearChildID](auto&&) {
1644       if (clearChildID) {
1645         target->ClearUnloadingHost(*clearChildID);
1646       }
1647     };
1648 
1649     ManagedEndpoint<PBrowserBridgeChild> endpoint =
1650         embedderBrowser->OpenPBrowserBridgeEndpoint(bridge);
1651     MOZ_DIAGNOSTIC_ASSERT(endpoint.IsValid());
1652     embedderWindow->SendMakeFrameRemote(target, std::move(endpoint), tabId,
1653                                         newBrowser->GetLayersId(), callback,
1654                                         callback);
1655   }
1656 
1657   // Resume the pending load in our new process.
1658   if (mPendingSwitchId) {
1659     newBrowser->ResumeLoad(mPendingSwitchId);
1660   }
1661 
1662   // We did it! The process switch is complete.
1663   mPromise->Resolve(newBrowser, __func__);
1664   return NS_OK;
1665 }
1666 
Cancel(nsresult aRv)1667 void CanonicalBrowsingContext::PendingRemotenessChange::Cancel(nsresult aRv) {
1668   if (!mPromise) {
1669     return;
1670   }
1671 
1672   mPromise->Reject(aRv, __func__);
1673   Clear();
1674 }
1675 
Clear()1676 void CanonicalBrowsingContext::PendingRemotenessChange::Clear() {
1677   // Make sure we don't die while we're doing cleanup.
1678   RefPtr<PendingRemotenessChange> kungFuDeathGrip(this);
1679   if (mTarget) {
1680     MOZ_DIAGNOSTIC_ASSERT(mTarget->mPendingRemotenessChange == this);
1681     mTarget->mPendingRemotenessChange = nullptr;
1682   }
1683 
1684   // When this PendingRemotenessChange was created, it was given a
1685   // `mContentParent`.
1686   if (mContentParent) {
1687     mContentParent->RemoveKeepAlive();
1688     mContentParent = nullptr;
1689   }
1690 
1691   // If we were given a specific group, stop keeping that group alive manually.
1692   if (mSpecificGroup) {
1693     mSpecificGroup->RemoveKeepAlive();
1694     mSpecificGroup = nullptr;
1695   }
1696 
1697   mPromise = nullptr;
1698   mTarget = nullptr;
1699   mPrepareToChangePromise = nullptr;
1700 }
1701 
PendingRemotenessChange(CanonicalBrowsingContext * aTarget,RemotenessPromise::Private * aPromise,uint64_t aPendingSwitchId,const RemotenessChangeOptions & aOptions)1702 CanonicalBrowsingContext::PendingRemotenessChange::PendingRemotenessChange(
1703     CanonicalBrowsingContext* aTarget, RemotenessPromise::Private* aPromise,
1704     uint64_t aPendingSwitchId, const RemotenessChangeOptions& aOptions)
1705     : mTarget(aTarget),
1706       mPromise(aPromise),
1707       mPendingSwitchId(aPendingSwitchId),
1708       mOptions(aOptions) {}
1709 
~PendingRemotenessChange()1710 CanonicalBrowsingContext::PendingRemotenessChange::~PendingRemotenessChange() {
1711   MOZ_ASSERT(!mPromise && !mTarget && !mContentParent && !mSpecificGroup &&
1712                  !mPrepareToChangePromise,
1713              "should've already been Cancel() or Complete()-ed");
1714 }
1715 
GetBrowserParent() const1716 BrowserParent* CanonicalBrowsingContext::GetBrowserParent() const {
1717   return mCurrentBrowserParent;
1718 }
1719 
SetCurrentBrowserParent(BrowserParent * aBrowserParent)1720 void CanonicalBrowsingContext::SetCurrentBrowserParent(
1721     BrowserParent* aBrowserParent) {
1722   MOZ_DIAGNOSTIC_ASSERT(!mCurrentBrowserParent || !aBrowserParent,
1723                         "BrowsingContext already has a current BrowserParent!");
1724   MOZ_DIAGNOSTIC_ASSERT_IF(aBrowserParent, aBrowserParent->CanSend());
1725   MOZ_DIAGNOSTIC_ASSERT_IF(aBrowserParent,
1726                            aBrowserParent->Manager()->ChildID() == mProcessId);
1727 
1728   // BrowserParent must either be directly for this BrowsingContext, or the
1729   // manager out our embedder WindowGlobal.
1730   MOZ_DIAGNOSTIC_ASSERT_IF(
1731       aBrowserParent && aBrowserParent->GetBrowsingContext() != this,
1732       GetParentWindowContext() &&
1733           GetParentWindowContext()->Manager() == aBrowserParent);
1734 
1735   mCurrentBrowserParent = aBrowserParent;
1736 }
1737 
1738 RefPtr<CanonicalBrowsingContext::RemotenessPromise>
ChangeRemoteness(const RemotenessChangeOptions & aOptions,uint64_t aPendingSwitchId)1739 CanonicalBrowsingContext::ChangeRemoteness(
1740     const RemotenessChangeOptions& aOptions, uint64_t aPendingSwitchId) {
1741   MOZ_DIAGNOSTIC_ASSERT(IsContent(),
1742                         "cannot change the process of chrome contexts");
1743   MOZ_DIAGNOSTIC_ASSERT(
1744       IsTop() == IsEmbeddedInProcess(0),
1745       "toplevel content must be embedded in the parent process");
1746   MOZ_DIAGNOSTIC_ASSERT(!aOptions.mReplaceBrowsingContext || IsTop(),
1747                         "Cannot replace BrowsingContext for subframes");
1748   MOZ_DIAGNOSTIC_ASSERT(
1749       aOptions.mSpecificGroupId == 0 || aOptions.mReplaceBrowsingContext,
1750       "Cannot specify group ID unless replacing BC");
1751   MOZ_DIAGNOSTIC_ASSERT(aPendingSwitchId || !IsTop(),
1752                         "Should always have aPendingSwitchId for top-level "
1753                         "frames");
1754 
1755   if (!AncestorsAreCurrent()) {
1756     NS_WARNING("An ancestor context is no longer current");
1757     return RemotenessPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
1758   }
1759 
1760   // Ensure our embedder hasn't been destroyed already.
1761   RefPtr<WindowGlobalParent> embedderWindowGlobal = GetEmbedderWindowGlobal();
1762   if (!embedderWindowGlobal) {
1763     NS_WARNING("Non-embedded BrowsingContext");
1764     return RemotenessPromise::CreateAndReject(NS_ERROR_UNEXPECTED, __func__);
1765   }
1766 
1767   if (!embedderWindowGlobal->CanSend()) {
1768     NS_WARNING("Embedder already been destroyed.");
1769     return RemotenessPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__);
1770   }
1771 
1772   if (aOptions.mRemoteType.IsEmpty() && (!IsTop() || !GetEmbedderElement())) {
1773     NS_WARNING("Cannot load non-remote subframes");
1774     return RemotenessPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
1775   }
1776 
1777   // Cancel ongoing remoteness changes.
1778   if (mPendingRemotenessChange) {
1779     mPendingRemotenessChange->Cancel(NS_ERROR_ABORT);
1780     MOZ_DIAGNOSTIC_ASSERT(!mPendingRemotenessChange, "Should have cleared");
1781   }
1782 
1783   auto promise = MakeRefPtr<RemotenessPromise::Private>(__func__);
1784   RefPtr<PendingRemotenessChange> change =
1785       new PendingRemotenessChange(this, promise, aPendingSwitchId, aOptions);
1786   mPendingRemotenessChange = change;
1787 
1788   // If a specific BrowsingContextGroup ID was specified for this load, make
1789   // sure to keep it alive until the process switch is completed.
1790   if (aOptions.mSpecificGroupId) {
1791     change->mSpecificGroup =
1792         BrowsingContextGroup::GetOrCreate(aOptions.mSpecificGroupId);
1793     change->mSpecificGroup->AddKeepAlive();
1794   }
1795 
1796   // Call `prepareToChangeRemoteness` in parallel with starting a new process
1797   // for <browser> loads.
1798   if (IsTop() && GetEmbedderElement()) {
1799     nsCOMPtr<nsIBrowser> browser = GetEmbedderElement()->AsBrowser();
1800     if (!browser) {
1801       change->Cancel(NS_ERROR_FAILURE);
1802       return promise.forget();
1803     }
1804 
1805     RefPtr<Promise> blocker;
1806     nsresult rv = browser->PrepareToChangeRemoteness(getter_AddRefs(blocker));
1807     if (NS_FAILED(rv)) {
1808       change->Cancel(rv);
1809       return promise.forget();
1810     }
1811     change->mPrepareToChangePromise = GenericPromise::FromDomPromise(blocker);
1812   }
1813 
1814   // Switching a subframe to be local within it's embedding process.
1815   RefPtr<BrowserParent> embedderBrowser =
1816       embedderWindowGlobal->GetBrowserParent();
1817   if (embedderBrowser &&
1818       aOptions.mRemoteType == embedderBrowser->Manager()->GetRemoteType()) {
1819     MOZ_DIAGNOSTIC_ASSERT(
1820         aPendingSwitchId,
1821         "We always have a PendingSwitchId, except for print-preview loads, "
1822         "which will never perform a process-switch to being in-process with "
1823         "their embedder");
1824     MOZ_DIAGNOSTIC_ASSERT(!aOptions.mReplaceBrowsingContext);
1825     MOZ_DIAGNOSTIC_ASSERT(!aOptions.mRemoteType.IsEmpty());
1826     MOZ_DIAGNOSTIC_ASSERT(!change->mPrepareToChangePromise);
1827     MOZ_DIAGNOSTIC_ASSERT(!change->mSpecificGroup);
1828 
1829     // Switching to local, so we don't need to create a new process, and will
1830     // instead use our embedder process.
1831     change->mContentParent = embedderBrowser->Manager();
1832     change->mContentParent->AddKeepAlive();
1833     change->ProcessLaunched();
1834     return promise.forget();
1835   }
1836 
1837   // Switching to the parent process.
1838   if (aOptions.mRemoteType.IsEmpty()) {
1839     change->ProcessLaunched();
1840     return promise.forget();
1841   }
1842 
1843   // Try to predict which BrowsingContextGroup will be used for the final load
1844   // in this BrowsingContext. This has to be accurate if switching into an
1845   // existing group, as it will control what pool of processes will be used
1846   // for process selection.
1847   //
1848   // It's _technically_ OK to provide a group here if we're actually going to
1849   // switch into a brand new group, though it's sub-optimal, as it can
1850   // restrict the set of processes we're using.
1851   BrowsingContextGroup* finalGroup =
1852       aOptions.mReplaceBrowsingContext ? change->mSpecificGroup.get() : Group();
1853 
1854   change->mContentParent = ContentParent::GetNewOrUsedLaunchingBrowserProcess(
1855       /* aRemoteType = */ aOptions.mRemoteType,
1856       /* aGroup = */ finalGroup,
1857       /* aPriority = */ hal::PROCESS_PRIORITY_FOREGROUND,
1858       /* aPreferUsed = */ false);
1859   if (!change->mContentParent) {
1860     change->Cancel(NS_ERROR_FAILURE);
1861     return promise.forget();
1862   }
1863 
1864   // Add a KeepAlive used by this ContentParent, which will be cleared when
1865   // the change is complete. This should prevent the process dying before
1866   // we're ready to use it.
1867   change->mContentParent->AddKeepAlive();
1868   change->mContentParent->WaitForLaunchAsync()->Then(
1869       GetMainThreadSerialEventTarget(), __func__,
1870       [change](ContentParent*) { change->ProcessLaunched(); },
1871       [change](LaunchError) { change->Cancel(NS_ERROR_FAILURE); });
1872   return promise.forget();
1873 }
1874 
MaybeSetPermanentKey(Element * aEmbedder)1875 void CanonicalBrowsingContext::MaybeSetPermanentKey(Element* aEmbedder) {
1876   MOZ_DIAGNOSTIC_ASSERT(IsTop());
1877 
1878   if (aEmbedder) {
1879     if (nsCOMPtr<nsIBrowser> browser = aEmbedder->AsBrowser()) {
1880       JS::RootedValue key(RootingCx());
1881       if (NS_SUCCEEDED(browser->GetPermanentKey(&key)) && key.isObject()) {
1882         mPermanentKey = key;
1883       }
1884     }
1885   }
1886 }
1887 
GetMediaController()1888 MediaController* CanonicalBrowsingContext::GetMediaController() {
1889   // We would only create one media controller per tab, so accessing the
1890   // controller via the top-level browsing context.
1891   if (GetParent()) {
1892     return Cast(Top())->GetMediaController();
1893   }
1894 
1895   MOZ_ASSERT(!GetParent(),
1896              "Must access the controller from the top-level browsing context!");
1897   // Only content browsing context can create media controller, we won't create
1898   // controller for chrome document, such as the browser UI.
1899   if (!mTabMediaController && !IsDiscarded() && IsContent()) {
1900     mTabMediaController = new MediaController(Id());
1901   }
1902   return mTabMediaController;
1903 }
1904 
HasCreatedMediaController() const1905 bool CanonicalBrowsingContext::HasCreatedMediaController() const {
1906   return !!mTabMediaController;
1907 }
1908 
SupportsLoadingInParent(nsDocShellLoadState * aLoadState,uint64_t * aOuterWindowId)1909 bool CanonicalBrowsingContext::SupportsLoadingInParent(
1910     nsDocShellLoadState* aLoadState, uint64_t* aOuterWindowId) {
1911   // We currently don't support initiating loads in the parent when they are
1912   // watched by devtools. This is because devtools tracks loads using content
1913   // process notifications, which happens after the load is initiated in this
1914   // case. Devtools clears all prior requests when it detects a new navigation,
1915   // so it drops the main document load that happened here.
1916   if (WatchedByDevTools()) {
1917     return false;
1918   }
1919 
1920   // Session-history-in-parent implementation relies currently on getting a
1921   // round trip through a child process.
1922   if (aLoadState->LoadIsFromSessionHistory()) {
1923     return false;
1924   }
1925 
1926   // DocumentChannel currently only supports connecting channels into the
1927   // content process, so we can only support schemes that will always be loaded
1928   // there for now. Restrict to just http(s) for simplicity.
1929   if (!net::SchemeIsHTTP(aLoadState->URI()) &&
1930       !net::SchemeIsHTTPS(aLoadState->URI())) {
1931     return false;
1932   }
1933 
1934   if (WindowGlobalParent* global = GetCurrentWindowGlobal()) {
1935     nsCOMPtr<nsIURI> currentURI = global->GetDocumentURI();
1936     if (currentURI) {
1937       bool newURIHasRef = false;
1938       aLoadState->URI()->GetHasRef(&newURIHasRef);
1939       bool equalsExceptRef = false;
1940       aLoadState->URI()->EqualsExceptRef(currentURI, &equalsExceptRef);
1941 
1942       if (equalsExceptRef && newURIHasRef) {
1943         // This navigation is same-doc WRT the current one, we should pass it
1944         // down to the docshell to be handled.
1945         return false;
1946       }
1947     }
1948     // If the current document has a beforeunload listener, then we need to
1949     // start the load in that process after we fire the event.
1950     if (global->HasBeforeUnload()) {
1951       return false;
1952     }
1953 
1954     *aOuterWindowId = global->OuterWindowId();
1955   }
1956   return true;
1957 }
1958 
LoadInParent(nsDocShellLoadState * aLoadState,bool aSetNavigating)1959 bool CanonicalBrowsingContext::LoadInParent(nsDocShellLoadState* aLoadState,
1960                                             bool aSetNavigating) {
1961   // We currently only support starting loads directly from the
1962   // CanonicalBrowsingContext for top-level BCs.
1963   // We currently only support starting loads directly from the
1964   // CanonicalBrowsingContext for top-level BCs.
1965   if (!IsTopContent() || !GetContentParent() ||
1966       !StaticPrefs::browser_tabs_documentchannel_parent_controlled()) {
1967     return false;
1968   }
1969 
1970   uint64_t outerWindowId = 0;
1971   if (!SupportsLoadingInParent(aLoadState, &outerWindowId)) {
1972     return false;
1973   }
1974 
1975   // Note: If successful, this will recurse into StartDocumentLoad and
1976   // set mCurrentLoad to the DocumentLoadListener instance created.
1977   // Ideally in the future we will only start loads from here, and we can
1978   // just set this directly instead.
1979   return net::DocumentLoadListener::LoadInParent(this, aLoadState,
1980                                                  aSetNavigating);
1981 }
1982 
AttemptSpeculativeLoadInParent(nsDocShellLoadState * aLoadState)1983 bool CanonicalBrowsingContext::AttemptSpeculativeLoadInParent(
1984     nsDocShellLoadState* aLoadState) {
1985   // We currently only support starting loads directly from the
1986   // CanonicalBrowsingContext for top-level BCs.
1987   // We currently only support starting loads directly from the
1988   // CanonicalBrowsingContext for top-level BCs.
1989   if (!IsTopContent() || !GetContentParent() ||
1990       StaticPrefs::browser_tabs_documentchannel_parent_controlled()) {
1991     return false;
1992   }
1993 
1994   uint64_t outerWindowId = 0;
1995   if (!SupportsLoadingInParent(aLoadState, &outerWindowId)) {
1996     return false;
1997   }
1998 
1999   // If we successfully open the DocumentChannel, then it'll register
2000   // itself using aLoadIdentifier and be kept alive until it completes
2001   // loading.
2002   return net::DocumentLoadListener::SpeculativeLoadInParent(this, aLoadState);
2003 }
2004 
StartDocumentLoad(net::DocumentLoadListener * aLoad)2005 bool CanonicalBrowsingContext::StartDocumentLoad(
2006     net::DocumentLoadListener* aLoad) {
2007   // If we're controlling loads from the parent, then starting a new load means
2008   // that we need to cancel any existing ones.
2009   if (StaticPrefs::browser_tabs_documentchannel_parent_controlled() &&
2010       mCurrentLoad) {
2011     mCurrentLoad->Cancel(NS_BINDING_ABORTED);
2012   }
2013   mCurrentLoad = aLoad;
2014 
2015   if (NS_FAILED(SetCurrentLoadIdentifier(Some(aLoad->GetLoadIdentifier())))) {
2016     mCurrentLoad = nullptr;
2017     return false;
2018   }
2019 
2020   return true;
2021 }
2022 
EndDocumentLoad(bool aForProcessSwitch)2023 void CanonicalBrowsingContext::EndDocumentLoad(bool aForProcessSwitch) {
2024   mCurrentLoad = nullptr;
2025 
2026   if (!aForProcessSwitch) {
2027     // Resetting the current load identifier on a discarded context
2028     // has no effect when a document load has finished.
2029     Unused << SetCurrentLoadIdentifier(Nothing());
2030   }
2031 }
2032 
GetCurrentURI() const2033 already_AddRefed<nsIURI> CanonicalBrowsingContext::GetCurrentURI() const {
2034   nsCOMPtr<nsIURI> currentURI;
2035   if (nsIDocShell* docShell = GetDocShell()) {
2036     MOZ_ALWAYS_SUCCEEDS(
2037         nsDocShell::Cast(docShell)->GetCurrentURI(getter_AddRefs(currentURI)));
2038   } else {
2039     currentURI = mCurrentRemoteURI;
2040   }
2041   return currentURI.forget();
2042 }
2043 
SetCurrentRemoteURI(nsIURI * aCurrentRemoteURI)2044 void CanonicalBrowsingContext::SetCurrentRemoteURI(nsIURI* aCurrentRemoteURI) {
2045   MOZ_ASSERT(!GetDocShell());
2046   mCurrentRemoteURI = aCurrentRemoteURI;
2047 }
2048 
ResetSHEntryHasUserInteractionCache()2049 void CanonicalBrowsingContext::ResetSHEntryHasUserInteractionCache() {
2050   WindowContext* topWc = GetTopWindowContext();
2051   if (topWc && !topWc->IsDiscarded()) {
2052     MOZ_ALWAYS_SUCCEEDS(topWc->SetSHEntryHasUserInteraction(false));
2053   }
2054 }
2055 
HistoryCommitIndexAndLength()2056 void CanonicalBrowsingContext::HistoryCommitIndexAndLength() {
2057   nsID changeID = {};
2058   CallerWillNotifyHistoryIndexAndLengthChanges caller(nullptr);
2059   HistoryCommitIndexAndLength(changeID, caller);
2060 }
HistoryCommitIndexAndLength(const nsID & aChangeID,const CallerWillNotifyHistoryIndexAndLengthChanges & aProofOfCaller)2061 void CanonicalBrowsingContext::HistoryCommitIndexAndLength(
2062     const nsID& aChangeID,
2063     const CallerWillNotifyHistoryIndexAndLengthChanges& aProofOfCaller) {
2064   if (!IsTop()) {
2065     Cast(Top())->HistoryCommitIndexAndLength(aChangeID, aProofOfCaller);
2066     return;
2067   }
2068 
2069   nsISHistory* shistory = GetSessionHistory();
2070   if (!shistory) {
2071     return;
2072   }
2073   int32_t index = 0;
2074   shistory->GetIndex(&index);
2075   int32_t length = shistory->GetCount();
2076 
2077   GetChildSessionHistory()->SetIndexAndLength(index, length, aChangeID);
2078 
2079   Group()->EachParent([&](ContentParent* aParent) {
2080     Unused << aParent->SendHistoryCommitIndexAndLength(this, index, length,
2081                                                        aChangeID);
2082   });
2083 }
2084 
ResetScalingZoom()2085 void CanonicalBrowsingContext::ResetScalingZoom() {
2086   // This currently only ever gets called in the parent process, and we
2087   // pass the message on to the WindowGlobalChild for the rootmost browsing
2088   // context.
2089   if (WindowGlobalParent* topWindow = GetTopWindowContext()) {
2090     Unused << topWindow->SendResetScalingZoom();
2091   }
2092 }
2093 
SetRestoreData(SessionStoreRestoreData * aData,ErrorResult & aError)2094 void CanonicalBrowsingContext::SetRestoreData(SessionStoreRestoreData* aData,
2095                                               ErrorResult& aError) {
2096   MOZ_DIAGNOSTIC_ASSERT(aData);
2097 
2098   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
2099   RefPtr<Promise> promise = Promise::Create(global, aError);
2100   if (aError.Failed()) {
2101     return;
2102   }
2103 
2104   if (NS_WARN_IF(NS_FAILED(SetHasRestoreData(true)))) {
2105     aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
2106     return;
2107   }
2108 
2109   mRestoreState = new RestoreState();
2110   mRestoreState->mData = aData;
2111   mRestoreState->mPromise = promise;
2112 }
2113 
GetRestorePromise()2114 already_AddRefed<Promise> CanonicalBrowsingContext::GetRestorePromise() {
2115   if (mRestoreState) {
2116     return do_AddRef(mRestoreState->mPromise);
2117   }
2118   return nullptr;
2119 }
2120 
ClearRestoreState()2121 void CanonicalBrowsingContext::ClearRestoreState() {
2122   if (!mRestoreState) {
2123     MOZ_DIAGNOSTIC_ASSERT(!GetHasRestoreData());
2124     return;
2125   }
2126   if (mRestoreState->mPromise) {
2127     mRestoreState->mPromise->MaybeRejectWithUndefined();
2128   }
2129   mRestoreState = nullptr;
2130   MOZ_ALWAYS_SUCCEEDS(SetHasRestoreData(false));
2131 }
2132 
RequestRestoreTabContent(WindowGlobalParent * aWindow)2133 void CanonicalBrowsingContext::RequestRestoreTabContent(
2134     WindowGlobalParent* aWindow) {
2135   MOZ_DIAGNOSTIC_ASSERT(IsTop());
2136 
2137   if (IsDiscarded() || !mRestoreState || !mRestoreState->mData) {
2138     return;
2139   }
2140 
2141   CanonicalBrowsingContext* context = aWindow->GetBrowsingContext();
2142   MOZ_DIAGNOSTIC_ASSERT(!context->IsDiscarded());
2143 
2144   RefPtr<SessionStoreRestoreData> data =
2145       mRestoreState->mData->FindDataForChild(context);
2146 
2147   if (context->IsTop()) {
2148     MOZ_DIAGNOSTIC_ASSERT(context == this);
2149 
2150     // We need to wait until the appropriate load event has fired before we
2151     // can "complete" the restore process, so if we're holding an empty data
2152     // object, just resolve the promise immediately.
2153     if (mRestoreState->mData->IsEmpty()) {
2154       MOZ_DIAGNOSTIC_ASSERT(!data || data->IsEmpty());
2155       mRestoreState->Resolve();
2156       ClearRestoreState();
2157       return;
2158     }
2159 
2160     // Since we're following load event order, we'll only arrive here for a
2161     // toplevel context after we've already sent down data for all child frames,
2162     // so it's safe to clear this reference now. The completion callback below
2163     // relies on the mData field being null to determine if all requests have
2164     // been sent out.
2165     mRestoreState->ClearData();
2166     MOZ_ALWAYS_SUCCEEDS(SetHasRestoreData(false));
2167   }
2168 
2169   if (data && !data->IsEmpty()) {
2170     auto onTabRestoreComplete = [self = RefPtr{this},
2171                                  state = RefPtr{mRestoreState}](auto) {
2172       state->mResolves++;
2173       if (!state->mData && state->mRequests == state->mResolves) {
2174         state->Resolve();
2175         if (state == self->mRestoreState) {
2176           self->ClearRestoreState();
2177         }
2178       }
2179     };
2180 
2181     mRestoreState->mRequests++;
2182 
2183     if (data->CanRestoreInto(aWindow->GetDocumentURI())) {
2184       if (!aWindow->IsInProcess()) {
2185         aWindow->SendRestoreTabContent(data, onTabRestoreComplete,
2186                                        onTabRestoreComplete);
2187         return;
2188       }
2189       data->RestoreInto(context);
2190     }
2191 
2192     // This must be called both when we're doing an in-process restore, and when
2193     // we didn't do a restore at all due to a URL mismatch.
2194     onTabRestoreComplete(true);
2195   }
2196 }
2197 
Resolve()2198 void CanonicalBrowsingContext::RestoreState::Resolve() {
2199   MOZ_DIAGNOSTIC_ASSERT(mPromise);
2200   mPromise->MaybeResolveWithUndefined();
2201   mPromise = nullptr;
2202 }
2203 
WriteSessionStorageToSessionStore(const nsTArray<SSCacheCopy> & aSesssionStorage,uint32_t aEpoch)2204 nsresult CanonicalBrowsingContext::WriteSessionStorageToSessionStore(
2205     const nsTArray<SSCacheCopy>& aSesssionStorage, uint32_t aEpoch) {
2206   nsCOMPtr<nsISessionStoreFunctions> funcs =
2207       do_ImportModule("resource://gre/modules/SessionStoreFunctions.jsm");
2208   if (!funcs) {
2209     return NS_ERROR_FAILURE;
2210   }
2211 
2212   nsCOMPtr<nsIXPConnectWrappedJS> wrapped = do_QueryInterface(funcs);
2213   AutoJSAPI jsapi;
2214   if (!jsapi.Init(wrapped->GetJSObjectGlobal())) {
2215     return NS_ERROR_FAILURE;
2216   }
2217 
2218   JS::RootedValue key(jsapi.cx(), Top()->PermanentKey());
2219 
2220   Record<nsCString, Record<nsString, nsString>> storage;
2221   JS::RootedValue update(jsapi.cx());
2222 
2223   if (!aSesssionStorage.IsEmpty()) {
2224     SessionStoreUtils::ConstructSessionStorageValues(this, aSesssionStorage,
2225                                                      storage);
2226     if (!ToJSValue(jsapi.cx(), storage, &update)) {
2227       return NS_ERROR_FAILURE;
2228     }
2229   } else {
2230     update.setNull();
2231   }
2232 
2233   return funcs->UpdateSessionStoreForStorage(Top()->GetEmbedderElement(), this,
2234                                              key, aEpoch, update);
2235 }
2236 
UpdateSessionStoreSessionStorage(const std::function<void ()> & aDone)2237 void CanonicalBrowsingContext::UpdateSessionStoreSessionStorage(
2238     const std::function<void()>& aDone) {
2239   using DataPromise = BackgroundSessionStorageManager::DataPromise;
2240   BackgroundSessionStorageManager::GetData(
2241       this, StaticPrefs::browser_sessionstore_dom_storage_limit(),
2242       /* aCancelSessionStoreTiemr = */ true)
2243       ->Then(GetCurrentSerialEventTarget(), __func__,
2244              [self = RefPtr{this}, aDone, epoch = GetSessionStoreEpoch()](
2245                  const DataPromise::ResolveOrRejectValue& valueList) {
2246                if (valueList.IsResolve()) {
2247                  self->WriteSessionStorageToSessionStore(
2248                      valueList.ResolveValue(), epoch);
2249                }
2250                aDone();
2251              });
2252 }
2253 
2254 /* static */
UpdateSessionStoreForStorage(uint64_t aBrowsingContextId)2255 void CanonicalBrowsingContext::UpdateSessionStoreForStorage(
2256     uint64_t aBrowsingContextId) {
2257   RefPtr<CanonicalBrowsingContext> browsingContext = Get(aBrowsingContextId);
2258 
2259   if (!browsingContext) {
2260     return;
2261   }
2262 
2263   browsingContext->UpdateSessionStoreSessionStorage([]() {});
2264 }
2265 
MaybeScheduleSessionStoreUpdate()2266 void CanonicalBrowsingContext::MaybeScheduleSessionStoreUpdate() {
2267   if (!IsTop()) {
2268     Top()->MaybeScheduleSessionStoreUpdate();
2269     return;
2270   }
2271 
2272   if (IsInBFCache()) {
2273     return;
2274   }
2275 
2276   if (mSessionStoreSessionStorageUpdateTimer) {
2277     return;
2278   }
2279 
2280   if (!StaticPrefs::browser_sessionstore_debug_no_auto_updates()) {
2281     auto result = NS_NewTimerWithFuncCallback(
2282         [](nsITimer*, void* aClosure) {
2283           auto* context = static_cast<CanonicalBrowsingContext*>(aClosure);
2284           context->UpdateSessionStoreSessionStorage([]() {});
2285         },
2286         this, StaticPrefs::browser_sessionstore_interval(),
2287         nsITimer::TYPE_ONE_SHOT,
2288         "CanonicalBrowsingContext::MaybeScheduleSessionStoreUpdate");
2289 
2290     if (result.isErr()) {
2291       return;
2292     }
2293 
2294     mSessionStoreSessionStorageUpdateTimer = result.unwrap();
2295   }
2296 }
2297 
CancelSessionStoreUpdate()2298 void CanonicalBrowsingContext::CancelSessionStoreUpdate() {
2299   if (mSessionStoreSessionStorageUpdateTimer) {
2300     mSessionStoreSessionStorageUpdateTimer->Cancel();
2301     mSessionStoreSessionStorageUpdateTimer = nullptr;
2302   }
2303 }
2304 
SetContainerFeaturePolicy(FeaturePolicy * aContainerFeaturePolicy)2305 void CanonicalBrowsingContext::SetContainerFeaturePolicy(
2306     FeaturePolicy* aContainerFeaturePolicy) {
2307   mContainerFeaturePolicy = aContainerFeaturePolicy;
2308 
2309   if (WindowGlobalParent* current = GetCurrentWindowGlobal()) {
2310     Unused << current->SendSetContainerFeaturePolicy(mContainerFeaturePolicy);
2311   }
2312 }
2313 
SetCrossGroupOpenerId(uint64_t aOpenerId)2314 void CanonicalBrowsingContext::SetCrossGroupOpenerId(uint64_t aOpenerId) {
2315   MOZ_DIAGNOSTIC_ASSERT(IsTopContent());
2316   MOZ_DIAGNOSTIC_ASSERT(mCrossGroupOpenerId == 0,
2317                         "Can only set CrossGroupOpenerId once");
2318   mCrossGroupOpenerId = aOpenerId;
2319 }
2320 
FindUnloadingHost(uint64_t aChildID)2321 auto CanonicalBrowsingContext::FindUnloadingHost(uint64_t aChildID)
2322     -> nsTArray<UnloadingHost>::iterator {
2323   return std::find_if(
2324       mUnloadingHosts.begin(), mUnloadingHosts.end(),
2325       [&](const auto& host) { return host.mChildID == aChildID; });
2326 }
2327 
ClearUnloadingHost(uint64_t aChildID)2328 void CanonicalBrowsingContext::ClearUnloadingHost(uint64_t aChildID) {
2329   // Notify any callbacks which were waiting for the host to finish unloading
2330   // that it has.
2331   auto found = FindUnloadingHost(aChildID);
2332   if (found != mUnloadingHosts.end()) {
2333     auto callbacks = std::move(found->mCallbacks);
2334     mUnloadingHosts.RemoveElementAt(found);
2335     for (const auto& callback : callbacks) {
2336       callback();
2337     }
2338   }
2339 }
2340 
StartUnloadingHost(uint64_t aChildID)2341 void CanonicalBrowsingContext::StartUnloadingHost(uint64_t aChildID) {
2342   MOZ_DIAGNOSTIC_ASSERT(FindUnloadingHost(aChildID) == mUnloadingHosts.end());
2343   mUnloadingHosts.AppendElement(UnloadingHost{aChildID, {}});
2344 }
2345 
BrowserParentDestroyed(BrowserParent * aBrowserParent,bool aAbnormalShutdown)2346 void CanonicalBrowsingContext::BrowserParentDestroyed(
2347     BrowserParent* aBrowserParent, bool aAbnormalShutdown) {
2348   ClearUnloadingHost(aBrowserParent->Manager()->ChildID());
2349 
2350   // Handling specific to when the current BrowserParent has been destroyed.
2351   if (mCurrentBrowserParent == aBrowserParent) {
2352     mCurrentBrowserParent = nullptr;
2353 
2354     // If this BrowserParent is for a subframe, attempt to recover from a
2355     // subframe crash by rendering the subframe crashed page in the embedding
2356     // content.
2357     if (aAbnormalShutdown) {
2358       ShowSubframeCrashedUI(aBrowserParent->GetBrowserBridgeParent());
2359     }
2360   }
2361 }
2362 
ShowSubframeCrashedUI(BrowserBridgeParent * aBridge)2363 void CanonicalBrowsingContext::ShowSubframeCrashedUI(
2364     BrowserBridgeParent* aBridge) {
2365   if (!aBridge || IsDiscarded() || !aBridge->CanSend()) {
2366     return;
2367   }
2368 
2369   MOZ_DIAGNOSTIC_ASSERT(!aBridge->GetBrowsingContext() ||
2370                         aBridge->GetBrowsingContext() == this);
2371 
2372   // There is no longer a current inner window within this
2373   // BrowsingContext, update the `CurrentInnerWindowId` field to reflect
2374   // this.
2375   MOZ_ALWAYS_SUCCEEDS(SetCurrentInnerWindowId(0));
2376 
2377   // The owning process will now be the embedder to render the subframe
2378   // crashed page, switch ownership back over.
2379   SetOwnerProcessId(aBridge->Manager()->Manager()->ChildID());
2380   SetCurrentBrowserParent(aBridge->Manager());
2381 
2382   Unused << aBridge->SendSubFrameCrashed();
2383 }
2384 
LogBFCacheBlockingForDoc(BrowsingContext * aBrowsingContext,uint16_t aBFCacheCombo,bool aIsSubDoc)2385 static void LogBFCacheBlockingForDoc(BrowsingContext* aBrowsingContext,
2386                                      uint16_t aBFCacheCombo, bool aIsSubDoc) {
2387   if (aIsSubDoc) {
2388     nsAutoCString uri("[no uri]");
2389     nsCOMPtr<nsIURI> currentURI =
2390         aBrowsingContext->Canonical()->GetCurrentURI();
2391     if (currentURI) {
2392       uri = currentURI->GetSpecOrDefault();
2393     }
2394     MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
2395             (" ** Blocked for document %s", uri.get()));
2396   }
2397   if (aBFCacheCombo & BFCacheStatus::EVENT_HANDLING_SUPPRESSED) {
2398     MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
2399             (" * event handling suppression"));
2400   }
2401   if (aBFCacheCombo & BFCacheStatus::SUSPENDED) {
2402     MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * suspended Window"));
2403   }
2404   if (aBFCacheCombo & BFCacheStatus::UNLOAD_LISTENER) {
2405     MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
2406             (" * beforeunload or unload listener"));
2407   }
2408   if (aBFCacheCombo & BFCacheStatus::REQUEST) {
2409     MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * requests in the loadgroup"));
2410   }
2411   if (aBFCacheCombo & BFCacheStatus::ACTIVE_GET_USER_MEDIA) {
2412     MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * GetUserMedia"));
2413   }
2414   if (aBFCacheCombo & BFCacheStatus::ACTIVE_PEER_CONNECTION) {
2415     MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * PeerConnection"));
2416   }
2417   if (aBFCacheCombo & BFCacheStatus::CONTAINS_EME_CONTENT) {
2418     MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * EME content"));
2419   }
2420   if (aBFCacheCombo & BFCacheStatus::CONTAINS_MSE_CONTENT) {
2421     MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * MSE use"));
2422   }
2423   if (aBFCacheCombo & BFCacheStatus::HAS_ACTIVE_SPEECH_SYNTHESIS) {
2424     MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * Speech use"));
2425   }
2426   if (aBFCacheCombo & BFCacheStatus::HAS_USED_VR) {
2427     MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * used VR"));
2428   }
2429 }
2430 
AllowedInBFCache(const Maybe<uint64_t> & aChannelId)2431 bool CanonicalBrowsingContext::AllowedInBFCache(
2432     const Maybe<uint64_t>& aChannelId) {
2433   if (MOZ_UNLIKELY(MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug))) {
2434     nsAutoCString uri("[no uri]");
2435     nsCOMPtr<nsIURI> currentURI = GetCurrentURI();
2436     if (currentURI) {
2437       uri = currentURI->GetSpecOrDefault();
2438     }
2439     MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, ("Checking %s", uri.get()));
2440   }
2441 
2442   if (IsInProcess()) {
2443     return false;
2444   }
2445 
2446   uint16_t bfcacheCombo = 0;
2447   if (mRestoreState) {
2448     bfcacheCombo |= BFCacheStatus::RESTORING;
2449     MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * during session restore"));
2450   }
2451 
2452   if (Group()->Toplevels().Length() > 1) {
2453     bfcacheCombo |= BFCacheStatus::NOT_ONLY_TOPLEVEL_IN_BCG;
2454     MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
2455             (" * auxiliary BrowsingContexts"));
2456   }
2457 
2458   // There are not a lot of about:* pages that are allowed to load in
2459   // subframes, so it's OK to allow those few about:* pages enter BFCache.
2460   MOZ_ASSERT(IsTop(), "Trying to put a non top level BC into BFCache");
2461 
2462   WindowGlobalParent* wgp = GetCurrentWindowGlobal();
2463   if (wgp && wgp->GetDocumentURI()) {
2464     nsCOMPtr<nsIURI> currentURI = wgp->GetDocumentURI();
2465     // Exempt about:* pages from bfcache, with the exception of about:blank
2466     if (currentURI->SchemeIs("about") &&
2467         !currentURI->GetSpecOrDefault().EqualsLiteral("about:blank")) {
2468       bfcacheCombo |= BFCacheStatus::ABOUT_PAGE;
2469       MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * about:* page"));
2470     }
2471   }
2472 
2473   // For telemetry we're collecting all the flags for all the BCs hanging
2474   // from this top-level BC.
2475   PreOrderWalk([&](BrowsingContext* aBrowsingContext) {
2476     WindowGlobalParent* wgp =
2477         aBrowsingContext->Canonical()->GetCurrentWindowGlobal();
2478     uint16_t subDocBFCacheCombo = wgp ? wgp->GetBFCacheStatus() : 0;
2479     if (wgp) {
2480       const Maybe<uint64_t>& singleChannelId = wgp->GetSingleChannelId();
2481       if (singleChannelId.isSome()) {
2482         if (singleChannelId.value() == 0 || aChannelId.isNothing() ||
2483             singleChannelId.value() != aChannelId.value()) {
2484           subDocBFCacheCombo |= BFCacheStatus::REQUEST;
2485         }
2486       }
2487     }
2488 
2489     if (MOZ_UNLIKELY(MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug))) {
2490       LogBFCacheBlockingForDoc(aBrowsingContext, subDocBFCacheCombo,
2491                                aBrowsingContext != this);
2492     }
2493 
2494     bfcacheCombo |= subDocBFCacheCombo;
2495   });
2496 
2497   nsDocShell::ReportBFCacheComboTelemetry(bfcacheCombo);
2498   if (MOZ_UNLIKELY(MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug))) {
2499     nsAutoCString uri("[no uri]");
2500     nsCOMPtr<nsIURI> currentURI = GetCurrentURI();
2501     if (currentURI) {
2502       uri = currentURI->GetSpecOrDefault();
2503     }
2504     MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
2505             (" +> %s %s be blocked from going into the BFCache", uri.get(),
2506              bfcacheCombo == 0 ? "shouldn't" : "should"));
2507   }
2508 
2509   if (StaticPrefs::docshell_shistory_bfcache_allow_unload_listeners()) {
2510     bfcacheCombo &= ~BFCacheStatus::UNLOAD_LISTENER;
2511   }
2512 
2513   return bfcacheCombo == 0;
2514 }
2515 
SetTouchEventsOverride(dom::TouchEventsOverride aOverride,ErrorResult & aRv)2516 void CanonicalBrowsingContext::SetTouchEventsOverride(
2517     dom::TouchEventsOverride aOverride, ErrorResult& aRv) {
2518   SetTouchEventsOverrideInternal(aOverride, aRv);
2519 }
2520 
2521 NS_IMPL_CYCLE_COLLECTION_CLASS(CanonicalBrowsingContext)
2522 
2523 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(CanonicalBrowsingContext,
2524                                                 BrowsingContext)
2525   tmp->mPermanentKey.setNull();
2526   if (tmp->mSessionHistory) {
2527     tmp->mSessionHistory->SetBrowsingContext(nullptr);
2528   }
2529   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSessionHistory, mContainerFeaturePolicy,
2530                                   mCurrentBrowserParent, mWebProgress,
2531                                   mSessionStoreSessionStorageUpdateTimer)
2532 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
2533 
2534 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(CanonicalBrowsingContext,
2535                                                   BrowsingContext)
2536   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSessionHistory, mContainerFeaturePolicy,
2537                                     mCurrentBrowserParent, mWebProgress,
2538                                     mSessionStoreSessionStorageUpdateTimer)
2539 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
2540 
2541 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(CanonicalBrowsingContext,
2542                                                BrowsingContext)
2543   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mPermanentKey)
2544 NS_IMPL_CYCLE_COLLECTION_TRACE_END
2545 
2546 NS_IMPL_ADDREF_INHERITED(CanonicalBrowsingContext, BrowsingContext)
2547 NS_IMPL_RELEASE_INHERITED(CanonicalBrowsingContext, BrowsingContext)
2548 
2549 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CanonicalBrowsingContext)
2550 NS_INTERFACE_MAP_END_INHERITING(BrowsingContext)
2551 
2552 }  // namespace dom
2553 }  // namespace mozilla
2554