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