From 2aa4a82499d4becd2284cdb482213d541b8804dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 16:29:10 +0200 Subject: Adding upstream version 86.0.1. Signed-off-by: Daniel Baumann --- docshell/base/BaseHistory.cpp | 249 + docshell/base/BaseHistory.h | 80 + docshell/base/BrowsingContext.cpp | 3301 +++++ docshell/base/BrowsingContext.h | 1166 ++ docshell/base/BrowsingContextGroup.cpp | 460 + docshell/base/BrowsingContextGroup.h | 232 + docshell/base/BrowsingContextWebProgress.cpp | 217 + docshell/base/BrowsingContextWebProgress.h | 68 + docshell/base/CanonicalBrowsingContext.cpp | 1694 +++ docshell/base/CanonicalBrowsingContext.h | 383 + docshell/base/ChildProcessChannelListener.cpp | 61 + docshell/base/ChildProcessChannelListener.h | 56 + docshell/base/IHistory.h | 150 + docshell/base/LoadContext.cpp | 236 + docshell/base/LoadContext.h | 68 + docshell/base/SerializedLoadContext.cpp | 87 + docshell/base/SerializedLoadContext.h | 97 + docshell/base/SyncedContext.h | 333 + docshell/base/SyncedContextInlines.h | 213 + docshell/base/URIFixup.jsm | 1054 ++ docshell/base/WindowContext.cpp | 569 + docshell/base/WindowContext.h | 328 + docshell/base/crashtests/1257730-1.html | 25 + docshell/base/crashtests/1331295.html | 25 + docshell/base/crashtests/1341657.html | 18 + docshell/base/crashtests/1584467.html | 12 + docshell/base/crashtests/1614211-1.html | 15 + docshell/base/crashtests/1617315-1.html | 8 + docshell/base/crashtests/1667491.html | 16 + docshell/base/crashtests/1667491_1.html | 21 + docshell/base/crashtests/369126-1.html | 16 + docshell/base/crashtests/40929-1-inner.html | 14 + docshell/base/crashtests/40929-1.html | 6 + docshell/base/crashtests/430124-1.html | 5 + docshell/base/crashtests/430628-1.html | 8 + docshell/base/crashtests/432114-1.html | 8 + docshell/base/crashtests/432114-2.html | 21 + docshell/base/crashtests/436900-1-inner.html | 21 + docshell/base/crashtests/436900-1.html | 8 + docshell/base/crashtests/436900-2-inner.html | 21 + docshell/base/crashtests/436900-2.html | 8 + docshell/base/crashtests/443655.html | 15 + docshell/base/crashtests/500328-1.html | 17 + docshell/base/crashtests/514779-1.xhtml | 9 + docshell/base/crashtests/614499-1.html | 20 + docshell/base/crashtests/678872-1.html | 36 + docshell/base/crashtests/914521.html | 32 + docshell/base/crashtests/crashtests.list | 21 + docshell/base/crashtests/file_432114-2.xhtml | 1 + docshell/base/moz.build | 130 + docshell/base/nsAboutRedirector.cpp | 266 + docshell/base/nsAboutRedirector.h | 26 + docshell/base/nsCTooltipTextProvider.h | 15 + docshell/base/nsDSURIContentListener.cpp | 307 + docshell/base/nsDSURIContentListener.h | 99 + docshell/base/nsDocShell.cpp | 13439 +++++++++++++++++++ docshell/base/nsDocShell.h | 1281 ++ docshell/base/nsDocShellEditorData.cpp | 139 + docshell/base/nsDocShellEditorData.h | 66 + docshell/base/nsDocShellEnumerator.cpp | 85 + docshell/base/nsDocShellEnumerator.h | 39 + docshell/base/nsDocShellLoadState.cpp | 957 ++ docshell/base/nsDocShellLoadState.h | 492 + docshell/base/nsDocShellLoadTypes.h | 211 + docshell/base/nsDocShellTelemetryUtils.cpp | 210 + docshell/base/nsDocShellTelemetryUtils.h | 22 + docshell/base/nsDocShellTreeOwner.cpp | 1304 ++ docshell/base/nsDocShellTreeOwner.h | 194 + docshell/base/nsIContentViewer.idl | 348 + docshell/base/nsIContentViewerEdit.idl | 36 + docshell/base/nsIDocShell.idl | 900 ++ docshell/base/nsIDocShellTreeItem.idl | 171 + docshell/base/nsIDocShellTreeOwner.idl | 106 + docshell/base/nsIDocumentLoaderFactory.idl | 39 + docshell/base/nsILoadContext.idl | 148 + docshell/base/nsILoadURIDelegate.idl | 49 + docshell/base/nsIPrivacyTransitionObserver.idl | 11 + docshell/base/nsIReflowObserver.idl | 31 + docshell/base/nsIRefreshURI.idl | 88 + docshell/base/nsIScrollObserver.h | 45 + docshell/base/nsITooltipListener.idl | 44 + docshell/base/nsITooltipTextProvider.idl | 44 + docshell/base/nsIURIFixup.idl | 156 + docshell/base/nsIWebNavigation.idl | 375 + docshell/base/nsIWebNavigationInfo.idl | 63 + docshell/base/nsIWebPageDescriptor.idl | 30 + docshell/base/nsPingListener.cpp | 345 + docshell/base/nsPingListener.h | 48 + docshell/base/nsRefreshTimer.cpp | 53 + docshell/base/nsRefreshTimer.h | 41 + docshell/base/nsWebNavigationInfo.cpp | 119 + docshell/base/nsWebNavigationInfo.h | 37 + docshell/base/timeline/AbstractTimelineMarker.cpp | 72 + docshell/base/timeline/AbstractTimelineMarker.h | 71 + .../base/timeline/AutoGlobalTimelineMarker.cpp | 41 + docshell/base/timeline/AutoGlobalTimelineMarker.h | 48 + .../base/timeline/AutoRestyleTimelineMarker.cpp | 53 + docshell/base/timeline/AutoRestyleTimelineMarker.h | 30 + docshell/base/timeline/AutoTimelineMarker.cpp | 48 + docshell/base/timeline/AutoTimelineMarker.h | 46 + docshell/base/timeline/CompositeTimelineMarker.h | 31 + docshell/base/timeline/ConsoleTimelineMarker.h | 53 + docshell/base/timeline/DocLoadingTimelineMarker.h | 38 + docshell/base/timeline/EventTimelineMarker.h | 40 + docshell/base/timeline/JavascriptTimelineMarker.h | 96 + docshell/base/timeline/LayerTimelineMarker.h | 47 + docshell/base/timeline/MarkersStorage.cpp | 20 + docshell/base/timeline/MarkersStorage.h | 48 + docshell/base/timeline/MessagePortTimelineMarker.h | 46 + docshell/base/timeline/ObservedDocShell.cpp | 169 + docshell/base/timeline/ObservedDocShell.h | 52 + docshell/base/timeline/RestyleTimelineMarker.h | 37 + docshell/base/timeline/TimelineConsumers.cpp | 287 + docshell/base/timeline/TimelineConsumers.h | 131 + docshell/base/timeline/TimelineMarker.cpp | 65 + docshell/base/timeline/TimelineMarker.h | 47 + docshell/base/timeline/TimelineMarkerEnums.h | 18 + docshell/base/timeline/TimestampTimelineMarker.h | 36 + docshell/base/timeline/WorkerTimelineMarker.h | 44 + docshell/base/timeline/moz.build | 45 + docshell/base/timeline/readme.md | 97 + 121 files changed, 35994 insertions(+) create mode 100644 docshell/base/BaseHistory.cpp create mode 100644 docshell/base/BaseHistory.h create mode 100644 docshell/base/BrowsingContext.cpp create mode 100644 docshell/base/BrowsingContext.h create mode 100644 docshell/base/BrowsingContextGroup.cpp create mode 100644 docshell/base/BrowsingContextGroup.h create mode 100644 docshell/base/BrowsingContextWebProgress.cpp create mode 100644 docshell/base/BrowsingContextWebProgress.h create mode 100644 docshell/base/CanonicalBrowsingContext.cpp create mode 100644 docshell/base/CanonicalBrowsingContext.h create mode 100644 docshell/base/ChildProcessChannelListener.cpp create mode 100644 docshell/base/ChildProcessChannelListener.h create mode 100644 docshell/base/IHistory.h create mode 100644 docshell/base/LoadContext.cpp create mode 100644 docshell/base/LoadContext.h create mode 100644 docshell/base/SerializedLoadContext.cpp create mode 100644 docshell/base/SerializedLoadContext.h create mode 100644 docshell/base/SyncedContext.h create mode 100644 docshell/base/SyncedContextInlines.h create mode 100644 docshell/base/URIFixup.jsm create mode 100644 docshell/base/WindowContext.cpp create mode 100644 docshell/base/WindowContext.h create mode 100644 docshell/base/crashtests/1257730-1.html create mode 100644 docshell/base/crashtests/1331295.html create mode 100644 docshell/base/crashtests/1341657.html create mode 100644 docshell/base/crashtests/1584467.html create mode 100644 docshell/base/crashtests/1614211-1.html create mode 100644 docshell/base/crashtests/1617315-1.html create mode 100644 docshell/base/crashtests/1667491.html create mode 100644 docshell/base/crashtests/1667491_1.html create mode 100644 docshell/base/crashtests/369126-1.html create mode 100644 docshell/base/crashtests/40929-1-inner.html create mode 100644 docshell/base/crashtests/40929-1.html create mode 100644 docshell/base/crashtests/430124-1.html create mode 100644 docshell/base/crashtests/430628-1.html create mode 100644 docshell/base/crashtests/432114-1.html create mode 100644 docshell/base/crashtests/432114-2.html create mode 100644 docshell/base/crashtests/436900-1-inner.html create mode 100644 docshell/base/crashtests/436900-1.html create mode 100644 docshell/base/crashtests/436900-2-inner.html create mode 100644 docshell/base/crashtests/436900-2.html create mode 100644 docshell/base/crashtests/443655.html create mode 100644 docshell/base/crashtests/500328-1.html create mode 100644 docshell/base/crashtests/514779-1.xhtml create mode 100644 docshell/base/crashtests/614499-1.html create mode 100644 docshell/base/crashtests/678872-1.html create mode 100644 docshell/base/crashtests/914521.html create mode 100644 docshell/base/crashtests/crashtests.list create mode 100644 docshell/base/crashtests/file_432114-2.xhtml create mode 100644 docshell/base/moz.build create mode 100644 docshell/base/nsAboutRedirector.cpp create mode 100644 docshell/base/nsAboutRedirector.h create mode 100644 docshell/base/nsCTooltipTextProvider.h create mode 100644 docshell/base/nsDSURIContentListener.cpp create mode 100644 docshell/base/nsDSURIContentListener.h create mode 100644 docshell/base/nsDocShell.cpp create mode 100644 docshell/base/nsDocShell.h create mode 100644 docshell/base/nsDocShellEditorData.cpp create mode 100644 docshell/base/nsDocShellEditorData.h create mode 100644 docshell/base/nsDocShellEnumerator.cpp create mode 100644 docshell/base/nsDocShellEnumerator.h create mode 100644 docshell/base/nsDocShellLoadState.cpp create mode 100644 docshell/base/nsDocShellLoadState.h create mode 100644 docshell/base/nsDocShellLoadTypes.h create mode 100644 docshell/base/nsDocShellTelemetryUtils.cpp create mode 100644 docshell/base/nsDocShellTelemetryUtils.h create mode 100644 docshell/base/nsDocShellTreeOwner.cpp create mode 100644 docshell/base/nsDocShellTreeOwner.h create mode 100644 docshell/base/nsIContentViewer.idl create mode 100644 docshell/base/nsIContentViewerEdit.idl create mode 100644 docshell/base/nsIDocShell.idl create mode 100644 docshell/base/nsIDocShellTreeItem.idl create mode 100644 docshell/base/nsIDocShellTreeOwner.idl create mode 100644 docshell/base/nsIDocumentLoaderFactory.idl create mode 100644 docshell/base/nsILoadContext.idl create mode 100644 docshell/base/nsILoadURIDelegate.idl create mode 100644 docshell/base/nsIPrivacyTransitionObserver.idl create mode 100644 docshell/base/nsIReflowObserver.idl create mode 100644 docshell/base/nsIRefreshURI.idl create mode 100644 docshell/base/nsIScrollObserver.h create mode 100644 docshell/base/nsITooltipListener.idl create mode 100644 docshell/base/nsITooltipTextProvider.idl create mode 100644 docshell/base/nsIURIFixup.idl create mode 100644 docshell/base/nsIWebNavigation.idl create mode 100644 docshell/base/nsIWebNavigationInfo.idl create mode 100644 docshell/base/nsIWebPageDescriptor.idl create mode 100644 docshell/base/nsPingListener.cpp create mode 100644 docshell/base/nsPingListener.h create mode 100644 docshell/base/nsRefreshTimer.cpp create mode 100644 docshell/base/nsRefreshTimer.h create mode 100644 docshell/base/nsWebNavigationInfo.cpp create mode 100644 docshell/base/nsWebNavigationInfo.h create mode 100644 docshell/base/timeline/AbstractTimelineMarker.cpp create mode 100644 docshell/base/timeline/AbstractTimelineMarker.h create mode 100644 docshell/base/timeline/AutoGlobalTimelineMarker.cpp create mode 100644 docshell/base/timeline/AutoGlobalTimelineMarker.h create mode 100644 docshell/base/timeline/AutoRestyleTimelineMarker.cpp create mode 100644 docshell/base/timeline/AutoRestyleTimelineMarker.h create mode 100644 docshell/base/timeline/AutoTimelineMarker.cpp create mode 100644 docshell/base/timeline/AutoTimelineMarker.h create mode 100644 docshell/base/timeline/CompositeTimelineMarker.h create mode 100644 docshell/base/timeline/ConsoleTimelineMarker.h create mode 100644 docshell/base/timeline/DocLoadingTimelineMarker.h create mode 100644 docshell/base/timeline/EventTimelineMarker.h create mode 100644 docshell/base/timeline/JavascriptTimelineMarker.h create mode 100644 docshell/base/timeline/LayerTimelineMarker.h create mode 100644 docshell/base/timeline/MarkersStorage.cpp create mode 100644 docshell/base/timeline/MarkersStorage.h create mode 100644 docshell/base/timeline/MessagePortTimelineMarker.h create mode 100644 docshell/base/timeline/ObservedDocShell.cpp create mode 100644 docshell/base/timeline/ObservedDocShell.h create mode 100644 docshell/base/timeline/RestyleTimelineMarker.h create mode 100644 docshell/base/timeline/TimelineConsumers.cpp create mode 100644 docshell/base/timeline/TimelineConsumers.h create mode 100644 docshell/base/timeline/TimelineMarker.cpp create mode 100644 docshell/base/timeline/TimelineMarker.h create mode 100644 docshell/base/timeline/TimelineMarkerEnums.h create mode 100644 docshell/base/timeline/TimestampTimelineMarker.h create mode 100644 docshell/base/timeline/WorkerTimelineMarker.h create mode 100644 docshell/base/timeline/moz.build create mode 100644 docshell/base/timeline/readme.md (limited to 'docshell/base') diff --git a/docshell/base/BaseHistory.cpp b/docshell/base/BaseHistory.cpp new file mode 100644 index 0000000000..b39c33bbae --- /dev/null +++ b/docshell/base/BaseHistory.cpp @@ -0,0 +1,249 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "BaseHistory.h" +#include "nsThreadUtils.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/Link.h" +#include "mozilla/dom/Element.h" +#include "mozilla/StaticPrefs_browser.h" +#include "mozilla/StaticPrefs_layout.h" + +namespace mozilla { + +using mozilla::dom::ContentParent; +using mozilla::dom::Link; + +BaseHistory::BaseHistory() : mTrackedURIs(kTrackedUrisInitialSize) {} + +BaseHistory::~BaseHistory() = default; + +static constexpr nsLiteralCString kDisallowedSchemes[] = { + "about"_ns, "blob"_ns, "data"_ns, "chrome"_ns, + "imap"_ns, "javascript"_ns, "mailbox"_ns, "moz-anno"_ns, + "news"_ns, "page-icon"_ns, "resource"_ns, "view-source"_ns, + "moz-extension"_ns, +}; + +bool BaseHistory::CanStore(nsIURI* aURI) { + nsAutoCString scheme; + if (NS_WARN_IF(NS_FAILED(aURI->GetScheme(scheme)))) { + return false; + } + + if (!scheme.EqualsLiteral("http") && !scheme.EqualsLiteral("https")) { + for (const nsLiteralCString& disallowed : kDisallowedSchemes) { + if (scheme.Equals(disallowed)) { + return false; + } + } + } + + nsAutoCString spec; + aURI->GetSpec(spec); + return spec.Length() <= StaticPrefs::browser_history_maxUrlLength(); +} + +void BaseHistory::ScheduleVisitedQuery(nsIURI* aURI) { + mPendingQueries.PutEntry(aURI); + if (mStartPendingVisitedQueriesScheduled) { + return; + } + mStartPendingVisitedQueriesScheduled = + NS_SUCCEEDED(NS_DispatchToMainThreadQueue( + NS_NewRunnableFunction( + "BaseHistory::StartPendingVisitedQueries", + [self = RefPtr(this)] { + self->mStartPendingVisitedQueriesScheduled = false; + auto queries = std::move(self->mPendingQueries); + self->StartPendingVisitedQueries(queries); + MOZ_DIAGNOSTIC_ASSERT(self->mPendingQueries.IsEmpty()); + }), + EventQueuePriority::Idle)); +} + +void BaseHistory::CancelVisitedQueryIfPossible(nsIURI* aURI) { + mPendingQueries.RemoveEntry(aURI); + // TODO(bug 1591393): It could be worth to make this virtual and allow places + // to stop the existing database query? Needs some measurement. +} + +void BaseHistory::RegisterVisitedCallback(nsIURI* aURI, Link* aLink) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aURI, "Must pass a non-null URI!"); + if (XRE_IsContentProcess()) { + MOZ_ASSERT(aLink, "Must pass a non-null Link!"); + } + + if (!CanStore(aURI)) { + if (aLink) { + aLink->VisitedQueryFinished(/* visited = */ false); + } + return; + } + + // Obtain our array of observers for this URI. + auto entry = mTrackedURIs.LookupForAdd(aURI); + MOZ_DIAGNOSTIC_ASSERT(!entry || !entry.Data().mLinks.IsEmpty(), + "An empty key was kept around in our hashtable!"); + if (!entry) { + ScheduleVisitedQuery(aURI); + } + + if (!aLink) { + // In IPC builds, we are passed a nullptr Link from + // ContentParent::RecvStartVisitedQuery. All of our code after this point + // assumes aLink is non-nullptr, so we have to return now. + MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess(), + "We should only ever get a null Link " + "in the parent process!"); + // We don't want to remove if we're tracking other links. + if (!entry) { + entry.OrRemove(); + } + return; + } + + ObservingLinks& links = entry.OrInsert([] { return ObservingLinks{}; }); + + // Sanity check that Links are not registered more than once for a given URI. + // This will not catch a case where it is registered for two different URIs. + MOZ_DIAGNOSTIC_ASSERT(!links.mLinks.Contains(aLink), + "Already tracking this Link object!"); + // FIXME(emilio): We should consider changing this (see the entry.Remove() + // call in NotifyVisitedInThisProcess). + MOZ_DIAGNOSTIC_ASSERT(links.mStatus != VisitedStatus::Visited, + "We don't keep tracking known-visited links"); + + links.mLinks.AppendElement(aLink); + + // If this link has already been queried and we should notify, do so now. + switch (links.mStatus) { + case VisitedStatus::Unknown: + break; + case VisitedStatus::Unvisited: + if (!StaticPrefs::layout_css_notify_of_unvisited()) { + break; + } + [[fallthrough]]; + case VisitedStatus::Visited: + aLink->VisitedQueryFinished(links.mStatus == VisitedStatus::Visited); + break; + } +} + +void BaseHistory::UnregisterVisitedCallback(nsIURI* aURI, Link* aLink) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aURI, "Must pass a non-null URI!"); + MOZ_ASSERT(aLink, "Must pass a non-null Link object!"); + + // Get the array, and remove the item from it. + auto entry = mTrackedURIs.Lookup(aURI); + if (!entry) { + MOZ_ASSERT(!CanStore(aURI), + "Trying to unregister URI that wasn't registered, " + "and that could be visited!"); + return; + } + + ObserverArray& observers = entry.Data().mLinks; + if (!observers.RemoveElement(aLink)) { + MOZ_ASSERT_UNREACHABLE("Trying to unregister node that wasn't registered!"); + return; + } + + // If the array is now empty, we should remove it from the hashtable. + if (observers.IsEmpty()) { + entry.Remove(); + CancelVisitedQueryIfPossible(aURI); + } +} + +void BaseHistory::NotifyVisited(nsIURI* aURI, VisitedStatus aStatus) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aStatus != VisitedStatus::Unknown); + + if (aStatus == VisitedStatus::Unvisited && + !StaticPrefs::layout_css_notify_of_unvisited()) { + return; + } + + NotifyVisitedInThisProcess(aURI, aStatus); + if (XRE_IsParentProcess()) { + NotifyVisitedFromParent(aURI, aStatus); + } +} + +void BaseHistory::NotifyVisitedInThisProcess(nsIURI* aURI, + VisitedStatus aStatus) { + if (NS_WARN_IF(!aURI)) { + return; + } + + auto entry = mTrackedURIs.Lookup(aURI); + if (!entry) { + // If we have no observers for this URI, we have nothing to notify about. + return; + } + + ObservingLinks& links = entry.Data(); + links.mStatus = aStatus; + + // If we have a key, it should have at least one observer. + MOZ_ASSERT(!links.mLinks.IsEmpty()); + + // Dispatch an event to each document which has a Link observing this URL. + // These will fire asynchronously in the correct DocGroup. + + const bool visited = aStatus == VisitedStatus::Visited; + for (Link* link : links.mLinks.BackwardRange()) { + link->VisitedQueryFinished(visited); + } + + // We never go from visited -> unvisited. + // + // FIXME(emilio): It seems unfortunate to remove a link to a visited uri and + // then re-add it to the document to trigger a new visited query. It shouldn't + // if we keep track of mStatus. + if (visited) { + entry.Remove(); + } +} + +void BaseHistory::SendPendingVisitedResultsToChildProcesses() { + MOZ_ASSERT(!mPendingResults.IsEmpty()); + + mStartPendingResultsScheduled = false; + + auto results = std::move(mPendingResults); + MOZ_ASSERT(mPendingResults.IsEmpty()); + + nsTArray cplist; + ContentParent::GetAll(cplist); + for (ContentParent* cp : cplist) { + Unused << NS_WARN_IF(!cp->SendNotifyVisited(results)); + } +} + +void BaseHistory::NotifyVisitedFromParent(nsIURI* aURI, VisitedStatus aStatus) { + MOZ_ASSERT(XRE_IsParentProcess()); + auto& result = *mPendingResults.AppendElement(); + result.visited() = aStatus == VisitedStatus::Visited; + result.uri() = aURI; + + if (mStartPendingResultsScheduled) { + return; + } + + mStartPendingResultsScheduled = NS_SUCCEEDED(NS_DispatchToMainThreadQueue( + NewRunnableMethod( + "BaseHistory::SendPendingVisitedResultsToChildProcesses", this, + &BaseHistory::SendPendingVisitedResultsToChildProcesses), + EventQueuePriority::Idle)); +} + +} // namespace mozilla diff --git a/docshell/base/BaseHistory.h b/docshell/base/BaseHistory.h new file mode 100644 index 0000000000..941ec29972 --- /dev/null +++ b/docshell/base/BaseHistory.h @@ -0,0 +1,80 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_BaseHistory_h +#define mozilla_BaseHistory_h + +#include "IHistory.h" +#include "mozilla/dom/ContentParent.h" + +/* A base class for history implementations that implement link coloring. */ + +namespace mozilla { + +class BaseHistory : public IHistory { + public: + void RegisterVisitedCallback(nsIURI*, dom::Link*) final; + void UnregisterVisitedCallback(nsIURI*, dom::Link*) final; + void NotifyVisited(nsIURI*, VisitedStatus) final; + + // Some URIs like data-uris are never going to be stored in history, so we can + // avoid doing IPC roundtrips for them or what not. + static bool CanStore(nsIURI*); + + protected: + void NotifyVisitedInThisProcess(nsIURI*, VisitedStatus); + void NotifyVisitedFromParent(nsIURI*, VisitedStatus); + static constexpr const size_t kTrackedUrisInitialSize = 64; + + BaseHistory(); + ~BaseHistory(); + + using ObserverArray = nsTObserverArray; + struct ObservingLinks { + ObserverArray mLinks; + VisitedStatus mStatus = VisitedStatus::Unknown; + + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { + return mLinks.ShallowSizeOfExcludingThis(aMallocSizeOf); + } + }; + + using PendingVisitedQueries = nsTHashtable; + using PendingVisitedResults = nsTArray; + + // Starts all the queries in the pending queries list, potentially at the same + // time. + virtual void StartPendingVisitedQueries(const PendingVisitedQueries&) = 0; + + private: + void ScheduleVisitedQuery(nsIURI*); + + // Cancels a visited query, if it is at all possible, because we know we won't + // use the results anymore. + void CancelVisitedQueryIfPossible(nsIURI*); + + void SendPendingVisitedResultsToChildProcesses(); + + protected: + // A map from URI to links that depend on that URI, and whether that URI is + // known-to-be-visited-or-unvisited already. + nsDataHashtable mTrackedURIs; + + private: + // The set of pending URIs that we haven't queried yet but need to. + PendingVisitedQueries mPendingQueries; + // The set of pending query results that we still haven't dispatched to child + // processes. + PendingVisitedResults mPendingResults; + // Whether we've successfully scheduled a runnable to call + // StartPendingVisitedQueries already. + bool mStartPendingVisitedQueriesScheduled = false; + // Whether we've successfully scheduled a runnable to call + // SendPendingVisitedResultsToChildProcesses already. + bool mStartPendingResultsScheduled = false; +}; + +} // namespace mozilla + +#endif diff --git a/docshell/base/BrowsingContext.cpp b/docshell/base/BrowsingContext.cpp new file mode 100644 index 0000000000..0d9cce338f --- /dev/null +++ b/docshell/base/BrowsingContext.cpp @@ -0,0 +1,3301 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/BrowsingContext.h" + +#include "ipc/IPCMessageUtils.h" + +#ifdef ACCESSIBILITY +# include "mozilla/a11y/DocAccessibleParent.h" +# include "mozilla/a11y/Platform.h" +# include "mozilla/a11y/ProxyAccessibleBase.h" +# include "nsAccessibilityService.h" +# if defined(XP_WIN) +# include "mozilla/a11y/AccessibleWrap.h" +# include "mozilla/a11y/Compatibility.h" +# include "mozilla/a11y/nsWinUtils.h" +# endif +#endif +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/dom/BrowsingContextGroup.h" +#include "mozilla/dom/BrowsingContextBinding.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/HTMLEmbedElement.h" +#include "mozilla/dom/HTMLIFrameElement.h" +#include "mozilla/dom/Location.h" +#include "mozilla/dom/LocationBinding.h" +#include "mozilla/dom/PopupBlocker.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/SessionStorageManager.h" +#include "mozilla/dom/StructuredCloneTags.h" +#include "mozilla/dom/UserActivationIPCUtils.h" +#include "mozilla/dom/WindowBinding.h" +#include "mozilla/dom/WindowGlobalChild.h" +#include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/dom/WindowProxyHolder.h" +#include "mozilla/dom/SyncedContextInlines.h" +#include "mozilla/dom/XULFrameElement.h" +#include "mozilla/net/DocumentLoadListener.h" +#include "mozilla/net/RequestContextService.h" +#include "mozilla/Assertions.h" +#include "mozilla/AsyncEventDispatcher.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/Components.h" +#include "mozilla/HashTable.h" +#include "mozilla/Logging.h" +#include "mozilla/MediaFeatureChange.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPrefs_fission.h" +#include "mozilla/StaticPrefs_page_load.h" +#include "mozilla/StaticPtr.h" +#include "nsIURIFixup.h" +#include "nsIXULRuntime.h" + +#include "nsDocShell.h" +#include "nsDocShellLoadState.h" +#include "nsFocusManager.h" +#include "nsGlobalWindowOuter.h" +#include "nsIObserverService.h" +#include "nsISHistory.h" +#include "nsContentUtils.h" +#include "nsQueryObject.h" +#include "nsSandboxFlags.h" +#include "nsScriptError.h" +#include "nsThreadUtils.h" +#include "xpcprivate.h" + +#include "AutoplayPolicy.h" +#include "GVAutoplayRequestStatusIPC.h" + +extern mozilla::LazyLogModule gAutoplayPermissionLog; +extern mozilla::LazyLogModule gTimeoutDeferralLog; + +#define AUTOPLAY_LOG(msg, ...) \ + MOZ_LOG(gAutoplayPermissionLog, LogLevel::Debug, (msg, ##__VA_ARGS__)) + +namespace IPC { +// Allow serialization and deserialization of OrientationType over IPC +template <> +struct ParamTraits + : public ContiguousEnumSerializer< + mozilla::dom::OrientationType, + mozilla::dom::OrientationType::Portrait_primary, + mozilla::dom::OrientationType::EndGuard_> {}; + +template <> +struct ParamTraits + : public ContiguousEnumSerializer {}; + +template <> +struct ParamTraits + : public ContiguousEnumSerializer< + mozilla::dom::ExplicitActiveStatus, + mozilla::dom::ExplicitActiveStatus::None, + mozilla::dom::ExplicitActiveStatus::EndGuard_> {}; + +// Allow serialization and deserialization of TouchEventsOverride over IPC +template <> +struct ParamTraits + : public ContiguousEnumSerializer< + mozilla::dom::TouchEventsOverride, + mozilla::dom::TouchEventsOverride::Disabled, + mozilla::dom::TouchEventsOverride::EndGuard_> {}; +} // namespace IPC + +namespace mozilla { +namespace dom { + +// Explicit specialization of the `Transaction` type. Required by the `extern +// template class` declaration in the header. +template class syncedcontext::Transaction; + +extern mozilla::LazyLogModule gUserInteractionPRLog; + +#define USER_ACTIVATION_LOG(msg, ...) \ + MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug, (msg, ##__VA_ARGS__)) + +static LazyLogModule gBrowsingContextLog("BrowsingContext"); + +typedef nsDataHashtable BrowsingContextMap; + +// All BrowsingContexts indexed by Id +static StaticAutoPtr sBrowsingContexts; +// Top-level Content BrowsingContexts only, indexed by BrowserId instead of Id +static StaticAutoPtr sCurrentTopByBrowserId; + +static void UnregisterBrowserId(BrowsingContext* aBrowsingContext) { + if (!aBrowsingContext->IsTopContent() || !sCurrentTopByBrowserId) { + return; + } + + // Avoids an extra lookup + auto browserIdEntry = + sCurrentTopByBrowserId->Lookup(aBrowsingContext->BrowserId()); + if (browserIdEntry && browserIdEntry.Data() == aBrowsingContext) { + browserIdEntry.Remove(); + } +} + +static void Register(BrowsingContext* aBrowsingContext) { + sBrowsingContexts->Put(aBrowsingContext->Id(), aBrowsingContext); + if (aBrowsingContext->IsTopContent()) { + sCurrentTopByBrowserId->Put(aBrowsingContext->BrowserId(), + aBrowsingContext); + } + + aBrowsingContext->Group()->Register(aBrowsingContext); +} + +BrowsingContext* BrowsingContext::GetParent() const { + return mParentWindow ? mParentWindow->GetBrowsingContext() : nullptr; +} + +bool BrowsingContext::IsInSubtreeOf(BrowsingContext* aContext) { + BrowsingContext* bc = this; + do { + if (bc == aContext) { + return true; + } + } while ((bc = bc->GetParent())); + return false; +} + +BrowsingContext* BrowsingContext::Top() { + BrowsingContext* bc = this; + while (bc->mParentWindow) { + bc = bc->GetParent(); + } + return bc; +} + +int32_t BrowsingContext::IndexOf(BrowsingContext* aChild) { + int32_t index = -1; + for (BrowsingContext* child : Children()) { + ++index; + if (child == aChild) { + break; + } + } + return index; +} + +WindowContext* BrowsingContext::GetTopWindowContext() { + if (mParentWindow) { + return mParentWindow->TopWindowContext(); + } + return mCurrentWindowContext; +} + +/* static */ +void BrowsingContext::Init() { + if (!sBrowsingContexts) { + sBrowsingContexts = new BrowsingContextMap(); + sCurrentTopByBrowserId = new BrowsingContextMap(); + ClearOnShutdown(&sBrowsingContexts); + ClearOnShutdown(&sCurrentTopByBrowserId); + } +} + +/* static */ +LogModule* BrowsingContext::GetLog() { return gBrowsingContextLog; } + +/* static */ +already_AddRefed BrowsingContext::Get(uint64_t aId) { + return do_AddRef(sBrowsingContexts->Get(aId)); +} + +/* static */ +already_AddRefed BrowsingContext::GetCurrentTopByBrowserId( + uint64_t aBrowserId) { + return do_AddRef(sCurrentTopByBrowserId->Get(aBrowserId)); +} + +/* static */ +already_AddRefed BrowsingContext::GetFromWindow( + WindowProxyHolder& aProxy) { + return do_AddRef(aProxy.get()); +} + +CanonicalBrowsingContext* BrowsingContext::Canonical() { + return CanonicalBrowsingContext::Cast(this); +} + +bool BrowsingContext::IsOwnedByProcess() const { + return mIsInProcess && mDocShell && + !nsDocShell::Cast(mDocShell)->WillChangeProcess(); +} + +bool BrowsingContext::SameOriginWithTop() { + MOZ_ASSERT(IsInProcess()); + // If the top BrowsingContext is not same-process to us, it is cross-origin + if (!Top()->IsInProcess()) { + return false; + } + + nsIDocShell* docShell = GetDocShell(); + if (!docShell) { + return false; + } + Document* doc = docShell->GetDocument(); + if (!doc) { + return false; + } + nsIPrincipal* principal = doc->NodePrincipal(); + + nsIDocShell* topDocShell = Top()->GetDocShell(); + if (!topDocShell) { + return false; + } + Document* topDoc = topDocShell->GetDocument(); + if (!topDoc) { + return false; + } + nsIPrincipal* topPrincipal = topDoc->NodePrincipal(); + + return principal->Equals(topPrincipal); +} + +/* static */ +already_AddRefed BrowsingContext::CreateDetached( + nsGlobalWindowInner* aParent, BrowsingContext* aOpener, + BrowsingContextGroup* aSpecificGroup, const nsAString& aName, Type aType, + bool aCreatedDynamically) { + if (aParent) { + MOZ_DIAGNOSTIC_ASSERT(aParent->GetWindowContext()); + MOZ_DIAGNOSTIC_ASSERT(aParent->GetBrowsingContext()->mType == aType); + MOZ_DIAGNOSTIC_ASSERT(aParent->GetBrowsingContext()->GetBrowserId() != 0); + } + + MOZ_DIAGNOSTIC_ASSERT(aType != Type::Chrome || XRE_IsParentProcess()); + + uint64_t id = nsContentUtils::GenerateBrowsingContextId(); + + MOZ_LOG(GetLog(), LogLevel::Debug, + ("Creating 0x%08" PRIx64 " in %s", id, + XRE_IsParentProcess() ? "Parent" : "Child")); + + RefPtr parentBC = + aParent ? aParent->GetBrowsingContext() : nullptr; + RefPtr parentWC = + aParent ? aParent->GetWindowContext() : nullptr; + BrowsingContext* inherit = parentBC ? parentBC.get() : aOpener; + + // Determine which BrowsingContextGroup this context should be created in. + RefPtr group = aSpecificGroup; + if (aType == Type::Chrome) { + MOZ_DIAGNOSTIC_ASSERT(!group); + group = BrowsingContextGroup::GetChromeGroup(); + } else if (!group) { + group = BrowsingContextGroup::Select(parentWC, aOpener); + } + + // Configure initial values for synced fields. + FieldValues fields; + fields.mName = aName; + + if (aOpener) { + MOZ_DIAGNOSTIC_ASSERT(!aParent, + "new BC with both initial opener and parent"); + MOZ_DIAGNOSTIC_ASSERT(aOpener->Group() == group); + MOZ_DIAGNOSTIC_ASSERT(aOpener->mType == aType); + fields.mOpenerId = aOpener->Id(); + fields.mHadOriginalOpener = true; + } + + if (aParent) { + MOZ_DIAGNOSTIC_ASSERT(parentBC->Group() == group); + MOZ_DIAGNOSTIC_ASSERT(parentBC->mType == aType); + fields.mEmbedderInnerWindowId = aParent->WindowID(); + + // XXX(farre): Can/Should we check aParent->IsLoading() here? (Bug + // 1608448) Check if the parent was itself loading already + auto readystate = aParent->GetDocument()->GetReadyStateEnum(); + fields.mAncestorLoading = + parentBC->GetAncestorLoading() || + readystate == Document::ReadyState::READYSTATE_LOADING || + readystate == Document::ReadyState::READYSTATE_INTERACTIVE; + } + + fields.mBrowserId = + parentBC ? parentBC->GetBrowserId() : nsContentUtils::GenerateBrowserId(); + + fields.mOpenerPolicy = nsILoadInfo::OPENER_POLICY_UNSAFE_NONE; + if (aOpener && aOpener->SameOriginWithTop()) { + // We inherit the opener policy if there is a creator and if the creator's + // origin is same origin with the creator's top-level origin. + // If it is cross origin we should not inherit the CrossOriginOpenerPolicy + fields.mOpenerPolicy = aOpener->Top()->GetOpenerPolicy(); + } else if (aOpener) { + // They are not same origin + auto topPolicy = aOpener->Top()->GetOpenerPolicy(); + MOZ_RELEASE_ASSERT(topPolicy == nsILoadInfo::OPENER_POLICY_UNSAFE_NONE || + topPolicy == + nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_ALLOW_POPUPS); + } + + nsContentUtils::GenerateUUIDInPlace(fields.mHistoryID); + fields.mExplicitActive = [&] { + if (parentBC) { + // Non-root browsing-contexts inherit their status from its parent. + return ExplicitActiveStatus::None; + } + if (aType == Type::Content) { + // Content gets managed by the chrome front-end / embedder element and + // starts as inactive. + return ExplicitActiveStatus::Inactive; + } + // Chrome starts as active. + return ExplicitActiveStatus::Active; + }(); + + fields.mFullZoom = parentBC ? parentBC->FullZoom() : 1.0f; + fields.mTextZoom = parentBC ? parentBC->TextZoom() : 1.0f; + + bool allowContentRetargeting = + inherit ? inherit->GetAllowContentRetargetingOnChildren() : true; + fields.mAllowContentRetargeting = allowContentRetargeting; + fields.mAllowContentRetargetingOnChildren = allowContentRetargeting; + + // Assume top allows fullscreen for its children unless otherwise stated. + // Subframes start with it false unless otherwise noted in SetEmbedderElement. + fields.mFullscreenAllowedByOwner = !aParent; + + fields.mAllowPlugins = inherit ? inherit->GetAllowPlugins() : true; + + fields.mDefaultLoadFlags = + inherit ? inherit->GetDefaultLoadFlags() : nsIRequest::LOAD_NORMAL; + + fields.mOrientationLock = mozilla::hal::eScreenOrientation_None; + + fields.mUseGlobalHistory = inherit ? inherit->GetUseGlobalHistory() : false; + + fields.mUseErrorPages = true; + + fields.mTouchEventsOverrideInternal = TouchEventsOverride::None; + + RefPtr context; + if (XRE_IsParentProcess()) { + context = new CanonicalBrowsingContext(parentWC, group, id, + /* aOwnerProcessId */ 0, + /* aEmbedderProcessId */ 0, aType, + std::move(fields)); + } else { + context = + new BrowsingContext(parentWC, group, id, aType, std::move(fields)); + } + + context->mEmbeddedByThisProcess = XRE_IsParentProcess() || aParent; + context->mCreatedDynamically = aCreatedDynamically; + if (inherit) { + context->mPrivateBrowsingId = inherit->mPrivateBrowsingId; + context->mUseRemoteTabs = inherit->mUseRemoteTabs; + context->mUseRemoteSubframes = inherit->mUseRemoteSubframes; + context->mOriginAttributes = inherit->mOriginAttributes; + } + + nsCOMPtr rcsvc = + net::RequestContextService::GetOrCreate(); + if (rcsvc) { + nsCOMPtr requestContext; + nsresult rv = rcsvc->NewRequestContext(getter_AddRefs(requestContext)); + if (NS_SUCCEEDED(rv) && requestContext) { + context->mRequestContextId = requestContext->GetID(); + } + } + + return context.forget(); +} + +already_AddRefed BrowsingContext::CreateIndependent( + Type aType) { + RefPtr bc( + CreateDetached(nullptr, nullptr, nullptr, u""_ns, aType)); + bc->mWindowless = bc->IsContent(); + bc->mEmbeddedByThisProcess = true; + bc->EnsureAttached(); + return bc.forget(); +} + +void BrowsingContext::EnsureAttached() { + if (!mEverAttached) { + Register(this); + + // Attach the browsing context to the tree. + Attach(/* aFromIPC */ false, /* aOriginProcess */ nullptr); + } +} + +/* static */ +void BrowsingContext::CreateFromIPC(BrowsingContext::IPCInitializer&& aInit, + BrowsingContextGroup* aGroup, + ContentParent* aOriginProcess) { + MOZ_DIAGNOSTIC_ASSERT(aOriginProcess || XRE_IsContentProcess()); + MOZ_DIAGNOSTIC_ASSERT(aGroup); + + uint64_t originId = 0; + if (aOriginProcess) { + originId = aOriginProcess->ChildID(); + aGroup->EnsureHostProcess(aOriginProcess); + } + + MOZ_LOG(GetLog(), LogLevel::Debug, + ("Creating 0x%08" PRIx64 " from IPC (origin=0x%08" PRIx64 ")", + aInit.mId, originId)); + + RefPtr parent = aInit.GetParent(); + + RefPtr context; + if (XRE_IsParentProcess()) { + // If the new BrowsingContext has a parent, it is a sub-frame embedded in + // whatever process sent the message. If it doesn't, and is not windowless, + // it is a new window or tab, and will be embedded in the parent process. + uint64_t embedderProcessId = (aInit.mWindowless || parent) ? originId : 0; + context = new CanonicalBrowsingContext(parent, aGroup, aInit.mId, originId, + embedderProcessId, Type::Content, + std::move(aInit.mFields)); + } else { + context = new BrowsingContext(parent, aGroup, aInit.mId, Type::Content, + std::move(aInit.mFields)); + } + + context->mWindowless = aInit.mWindowless; + context->mCreatedDynamically = aInit.mCreatedDynamically; + if (context->GetHasSessionHistory()) { + context->CreateChildSHistory(); + if (mozilla::SessionHistoryInParent()) { + context->GetChildSessionHistory()->SetIndexAndLength( + aInit.mSessionHistoryIndex, aInit.mSessionHistoryCount, nsID()); + } + } + + // NOTE: Call through the `Set` methods for these values to ensure that any + // relevant process-local state is also updated. + context->SetOriginAttributes(aInit.mOriginAttributes); + context->SetRemoteTabs(aInit.mUseRemoteTabs); + context->SetRemoteSubframes(aInit.mUseRemoteSubframes); + context->mRequestContextId = aInit.mRequestContextId; + // NOTE: Private browsing ID is set by `SetOriginAttributes`. + + Register(context); + + context->Attach(/* aFromIPC */ true, aOriginProcess); +} + +BrowsingContext::BrowsingContext(WindowContext* aParentWindow, + BrowsingContextGroup* aGroup, + uint64_t aBrowsingContextId, Type aType, + FieldValues&& aInit) + : mFields(std::move(aInit)), + mType(aType), + mBrowsingContextId(aBrowsingContextId), + mGroup(aGroup), + mParentWindow(aParentWindow), + mPrivateBrowsingId(0), + mEverAttached(false), + mIsInProcess(false), + mIsDiscarded(false), + mWindowless(false), + mDanglingRemoteOuterProxies(false), + mEmbeddedByThisProcess(false), + mUseRemoteTabs(false), + mUseRemoteSubframes(false), + mCreatedDynamically(false) { + MOZ_RELEASE_ASSERT(!mParentWindow || mParentWindow->Group() == mGroup); + MOZ_RELEASE_ASSERT(mBrowsingContextId != 0); + MOZ_RELEASE_ASSERT(mGroup); +} + +void BrowsingContext::SetDocShell(nsIDocShell* aDocShell) { + // XXX(nika): We should communicate that we are now an active BrowsingContext + // process to the parent & do other validation here. + MOZ_DIAGNOSTIC_ASSERT(mEverAttached); + MOZ_RELEASE_ASSERT(aDocShell->GetBrowsingContext() == this); + mDocShell = aDocShell; + mDanglingRemoteOuterProxies = !mIsInProcess; + mIsInProcess = true; + if (mChildSessionHistory) { + mChildSessionHistory->SetIsInProcess(true); + } +} + +// This class implements a callback that will return the remote window proxy for +// mBrowsingContext in that compartment, if it has one. It also removes the +// proxy from the map, because the object will be transplanted into another kind +// of object. +class MOZ_STACK_CLASS CompartmentRemoteProxyTransplantCallback + : public js::CompartmentTransplantCallback { + public: + explicit CompartmentRemoteProxyTransplantCallback( + BrowsingContext* aBrowsingContext) + : mBrowsingContext(aBrowsingContext) {} + + virtual JSObject* getObjectToTransplant( + JS::Compartment* compartment) override { + auto* priv = xpc::CompartmentPrivate::Get(compartment); + if (!priv) { + return nullptr; + } + + auto& map = priv->GetRemoteProxyMap(); + auto result = map.lookup(mBrowsingContext); + if (!result) { + return nullptr; + } + JSObject* resultObject = result->value(); + map.remove(result); + + return resultObject; + } + + private: + BrowsingContext* mBrowsingContext; +}; + +void BrowsingContext::CleanUpDanglingRemoteOuterWindowProxies( + JSContext* aCx, JS::MutableHandle aOuter) { + if (!mDanglingRemoteOuterProxies) { + return; + } + mDanglingRemoteOuterProxies = false; + + CompartmentRemoteProxyTransplantCallback cb(this); + js::RemapRemoteWindowProxies(aCx, &cb, aOuter); +} + +bool BrowsingContext::IsActive() const { + const BrowsingContext* current = this; + do { + auto explicit_ = current->GetExplicitActive(); + if (explicit_ != ExplicitActiveStatus::None) { + return explicit_ == ExplicitActiveStatus::Active; + } + if (current->IsCached()) { + return false; + } + } while ((current = current->GetParent())); + + return false; +} + +bool BrowsingContext::GetIsActiveBrowserWindow() { + if (!XRE_IsParentProcess()) { + return Top()->GetIsActiveBrowserWindowInternal(); + } + + // chrome:// urls loaded in the parent won't receive + // their own activation so we defer to the top chrome + // Browsing Context when in the parent process. + RefPtr chromeTop = + Canonical()->TopCrossChromeBoundary(); + return chromeTop->GetIsActiveBrowserWindowInternal(); +} + +void BrowsingContext::SetIsActiveBrowserWindow(bool aActive) { + Unused << SetIsActiveBrowserWindowInternal(aActive); +} + +bool BrowsingContext::FullscreenAllowed() const { + for (auto* current = this; current; current = current->GetParent()) { + if (!current->GetFullscreenAllowedByOwner()) { + return false; + } + } + return true; +} + +static bool OwnerAllowsFullscreen(const Element& aEmbedder) { + if (aEmbedder.IsXULElement()) { + return !aEmbedder.HasAttr(nsGkAtoms::disablefullscreen); + } + if (aEmbedder.IsHTMLElement(nsGkAtoms::iframe)) { + // This is controlled by feature policy. + return true; + } + if (const auto* embed = HTMLEmbedElement::FromNode(aEmbedder)) { + return embed->AllowFullscreen(); + } + return false; +} + +void BrowsingContext::SetEmbedderElement(Element* aEmbedder) { + mEmbeddedByThisProcess = true; + + // Update embedder-element-specific fields in a shared transaction. + // Don't do this when clearing our embedder, as we're being destroyed either + // way. + if (aEmbedder) { + Transaction txn; + txn.SetEmbedderElementType(Some(aEmbedder->LocalName())); + if (nsCOMPtr inner = + do_QueryInterface(aEmbedder->GetOwnerGlobal())) { + txn.SetEmbedderInnerWindowId(inner->WindowID()); + } + txn.SetFullscreenAllowedByOwner(OwnerAllowsFullscreen(*aEmbedder)); + if (XRE_IsParentProcess() && IsTopContent()) { + nsAutoString messageManagerGroup; + if (aEmbedder->IsXULElement()) { + aEmbedder->GetAttr(nsGkAtoms::messagemanagergroup, messageManagerGroup); + if (!aEmbedder->AttrValueIs(kNameSpaceID_None, + nsGkAtoms::initiallyactive, + nsGkAtoms::_false, eIgnoreCase)) { + txn.SetExplicitActive(ExplicitActiveStatus::Active); + } + } + txn.SetMessageManagerGroup(messageManagerGroup); + + bool useGlobalHistory = !aEmbedder->HasAttr( + kNameSpaceID_None, nsGkAtoms::disableglobalhistory); + txn.SetUseGlobalHistory(useGlobalHistory); + } + + MOZ_ALWAYS_SUCCEEDS(txn.Commit(this)); + } + + mEmbedderElement = aEmbedder; +} + +void BrowsingContext::Embed() { + if (auto* frame = HTMLIFrameElement::FromNode(mEmbedderElement)) { + frame->BindToBrowsingContext(this); + } +} + +void BrowsingContext::Attach(bool aFromIPC, ContentParent* aOriginProcess) { + MOZ_DIAGNOSTIC_ASSERT(!mEverAttached); + mEverAttached = true; + + if (MOZ_LOG_TEST(GetLog(), LogLevel::Debug)) { + nsAutoCString suffix; + mOriginAttributes.CreateSuffix(suffix); + MOZ_LOG(GetLog(), LogLevel::Debug, + ("%s: Connecting 0x%08" PRIx64 " to 0x%08" PRIx64 + " (private=%d, remote=%d, fission=%d, oa=%s)", + XRE_IsParentProcess() ? "Parent" : "Child", Id(), + GetParent() ? GetParent()->Id() : 0, (int)mPrivateBrowsingId, + (int)mUseRemoteTabs, (int)mUseRemoteSubframes, suffix.get())); + } + + MOZ_DIAGNOSTIC_ASSERT(mGroup); + MOZ_DIAGNOSTIC_ASSERT(!mIsDiscarded); + + AssertCoherentLoadContext(); + + // Add ourselves either to our parent or BrowsingContextGroup's child list. + if (mParentWindow) { + if (!aFromIPC) { + MOZ_DIAGNOSTIC_ASSERT(!mParentWindow->IsDiscarded(), + "local attach in discarded window"); + MOZ_DIAGNOSTIC_ASSERT(!GetParent()->IsDiscarded(), + "local attach call in discarded bc"); + } + + mParentWindow->AppendChildBrowsingContext(this); + } else { + mGroup->Toplevels().AppendElement(this); + } + + if (GetIsPopupSpam()) { + PopupBlocker::RegisterOpenPopupSpam(); + } + + if (IsTop() && GetHasSessionHistory() && !mChildSessionHistory) { + CreateChildSHistory(); + } + + if (XRE_IsContentProcess() && !aFromIPC) { + // Send attach to our parent if we need to. + ContentChild::GetSingleton()->SendCreateBrowsingContext( + mGroup->Id(), GetIPCInitializer()); + } else if (XRE_IsParentProcess()) { + mGroup->EachOtherParent(aOriginProcess, [&](ContentParent* aParent) { + MOZ_DIAGNOSTIC_ASSERT(IsContent(), + "chrome BCG cannot be synced to content process"); + if (!Canonical()->IsEmbeddedInProcess(aParent->ChildID())) { + Unused << aParent->SendCreateBrowsingContext(mGroup->Id(), + GetIPCInitializer()); + } + }); + + if (IsTopContent() && !Canonical()->GetWebProgress()) { + Canonical()->mWebProgress = new BrowsingContextWebProgress(); + } + } + + if (nsCOMPtr obs = services::GetObserverService()) { + obs->NotifyWhenScriptSafe(ToSupports(this), "browsing-context-attached", + nullptr); + } +} + +void BrowsingContext::Detach(bool aFromIPC) { + MOZ_LOG(GetLog(), LogLevel::Debug, + ("%s: Detaching 0x%08" PRIx64 " from 0x%08" PRIx64, + XRE_IsParentProcess() ? "Parent" : "Child", Id(), + GetParent() ? GetParent()->Id() : 0)); + + MOZ_DIAGNOSTIC_ASSERT(mEverAttached); + MOZ_DIAGNOSTIC_ASSERT(!mIsDiscarded); + + nsCOMPtr rcsvc = + net::RequestContextService::GetOrCreate(); + if (rcsvc) { + rcsvc->RemoveRequestContext(GetRequestContextId()); + } + + // This will only ever be null if the cycle-collector has unlinked us. Don't + // try to detach ourselves in that case. + if (NS_WARN_IF(!mGroup)) { + return; + } + + if (mParentWindow) { + mParentWindow->RemoveChildBrowsingContext(this); + } else { + mGroup->Toplevels().RemoveElement(this); + } + + auto callSendDiscard = [&](auto* aActor) { + // Hold a strong reference to ourself, and keep our BrowsingContextGroup + // alive, until the responses comes back to ensure we don't die while + // messages relating to this context are in-flight. + // + // When the callback is called, the keepalive on our group will be + // destroyed, and the reference to the BrowsingContext will be dropped, + // which may cause it to be fully destroyed. + mGroup->AddKeepAlive(); + auto callback = [self = RefPtr{this}](auto) { + self->mGroup->RemoveKeepAlive(); + }; + + aActor->SendDiscardBrowsingContext(this, callback, callback); + }; + + if (XRE_IsParentProcess()) { + Group()->EachParent([&](ContentParent* aParent) { + // Only the embedder process is allowed to initiate a BrowsingContext + // detach, so if we've gotten here, the host process already knows we've + // been detached, and there's no need to tell it again. + // + // If the owner process is not the same as the embedder process, its + // BrowsingContext will be detached when its nsWebBrowser instance is + // destroyed. + if (!Canonical()->IsEmbeddedInProcess(aParent->ChildID()) && + !Canonical()->IsOwnedByProcess(aParent->ChildID())) { + callSendDiscard(aParent); + } + }); + } else if (!aFromIPC) { + callSendDiscard(ContentChild::GetSingleton()); + } + + mGroup->Unregister(this); + UnregisterBrowserId(this); + mIsDiscarded = true; + + if (XRE_IsParentProcess()) { + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm) { + fm->BrowsingContextDetached(this); + } + } + + if (nsCOMPtr obs = services::GetObserverService()) { + obs->NotifyObservers(ToSupports(this), "browsing-context-discarded", + nullptr); + } + + // NOTE: Doesn't use SetClosed, as it will be set in all processes + // automatically by calls to Detach() + mFields.SetWithoutSyncing(true); + + if (GetIsPopupSpam()) { + PopupBlocker::UnregisterOpenPopupSpam(); + // NOTE: Doesn't use SetIsPopupSpam, as it will be set all processes + // automatically. + mFields.SetWithoutSyncing(false); + } + + AssertOriginAttributesMatchPrivateBrowsing(); + + if (XRE_IsParentProcess()) { + Canonical()->CanonicalDiscard(); + } +} + +void BrowsingContext::PrepareForProcessChange() { + MOZ_LOG(GetLog(), LogLevel::Debug, + ("%s: Preparing 0x%08" PRIx64 " for a process change", + XRE_IsParentProcess() ? "Parent" : "Child", Id())); + + MOZ_ASSERT(mIsInProcess, "Must currently be an in-process frame"); + MOZ_ASSERT(!mIsDiscarded, "We're already closed?"); + + mIsInProcess = false; + mUserGestureStart = TimeStamp(); + + // NOTE: For now, clear our nsDocShell reference, as we're primarily in a + // different process now. This may need to change in the future with + // Cross-Process BFCache. + mDocShell = nullptr; + if (mChildSessionHistory) { + // This can be removed once session history is stored exclusively in the + // parent process. + mChildSessionHistory->SetIsInProcess(false); + } + + if (!mWindowProxy) { + return; + } + + // We have to go through mWindowProxy rather than calling GetDOMWindow() on + // mDocShell because the mDocshell reference gets cleared immediately after + // the window is closed. + nsGlobalWindowOuter::PrepareForProcessChange(mWindowProxy); + MOZ_ASSERT(!mWindowProxy); +} + +bool BrowsingContext::IsCached() const { + return mParentWindow && mParentWindow->IsCached(); +} + +bool BrowsingContext::IsTargetable() const { + return !GetClosed() && !mIsDiscarded && !IsCached(); +} + +bool BrowsingContext::HasOpener() const { + return sBrowsingContexts->Contains(GetOpenerId()); +} + +bool BrowsingContext::AncestorsAreCurrent() const { + const BrowsingContext* bc = this; + while (true) { + if (bc->IsDiscarded()) { + return false; + } + + if (WindowContext* wc = bc->GetParentWindowContext()) { + if (wc->IsCached() || wc->IsDiscarded()) { + return false; + } + + bc = wc->GetBrowsingContext(); + } else { + return true; + } + } +} + +Span> BrowsingContext::Children() const { + if (WindowContext* current = mCurrentWindowContext) { + return current->Children(); + } + return Span>(); +} + +void BrowsingContext::GetChildren( + nsTArray>& aChildren) { + aChildren.AppendElements(Children()); +} + +void BrowsingContext::GetWindowContexts( + nsTArray>& aWindows) { + aWindows.AppendElements(mWindowContexts); +} + +void BrowsingContext::RegisterWindowContext(WindowContext* aWindow) { + MOZ_ASSERT(!mWindowContexts.Contains(aWindow), + "WindowContext already registered!"); + MOZ_ASSERT(aWindow->GetBrowsingContext() == this); + + mWindowContexts.AppendElement(aWindow); + + // If the newly registered WindowContext is for our current inner window ID, + // re-run the `DidSet` handler to re-establish the relationship. + if (aWindow->InnerWindowId() == GetCurrentInnerWindowId()) { + DidSet(FieldIndex()); + MOZ_DIAGNOSTIC_ASSERT(mCurrentWindowContext == aWindow); + } +} + +void BrowsingContext::UnregisterWindowContext(WindowContext* aWindow) { + MOZ_ASSERT(mWindowContexts.Contains(aWindow), + "WindowContext not registered!"); + mWindowContexts.RemoveElement(aWindow); + + // If our currently active window was unregistered, clear our reference to it. + if (aWindow == mCurrentWindowContext) { + // Re-read our `CurrentInnerWindowId` value and use it to set + // `mCurrentWindowContext`. As `aWindow` is now unregistered and discarded, + // we won't find it, and the value will be cleared back to `nullptr`. + DidSet(FieldIndex()); + MOZ_DIAGNOSTIC_ASSERT(mCurrentWindowContext == nullptr); + } +} + +void BrowsingContext::PreOrderWalk( + const std::function& aCallback) { + aCallback(this); + + AutoTArray, 8> children; + children.AppendElements(Children()); + + for (auto& child : children) { + child->PreOrderWalk(aCallback); + } +} + +void BrowsingContext::PostOrderWalk( + const std::function& aCallback) { + AutoTArray, 8> children; + children.AppendElements(Children()); + + for (auto& child : children) { + child->PostOrderWalk(aCallback); + } + + aCallback(this); +} + +void BrowsingContext::GetAllBrowsingContextsInSubtree( + nsTArray>& aBrowsingContexts) { + PreOrderWalk([&](BrowsingContext* aContext) { + aBrowsingContexts.AppendElement(aContext); + }); +} + +// FindWithName follows the rules for choosing a browsing context, +// with the exception of sandboxing for iframes. The implementation +// for arbitrarily choosing between two browsing contexts with the +// same name is as follows: +// +// 1) The start browsing context, i.e. 'this' +// 2) Descendants in insertion order +// 3) The parent +// 4) Siblings and their children, both in insertion order +// 5) After this we iteratively follow the parent chain, repeating 3 +// and 4 until +// 6) If there is no parent, consider all other top level browsing +// contexts and their children, both in insertion order +// +// See +// https://html.spec.whatwg.org/multipage/browsers.html#the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name +BrowsingContext* BrowsingContext::FindWithName( + const nsAString& aName, bool aUseEntryGlobalForAccessCheck) { + RefPtr requestingContext = this; + if (aUseEntryGlobalForAccessCheck) { + if (nsCOMPtr caller = do_GetInterface(GetEntryGlobal())) { + if (caller->GetBrowsingContext()) { + requestingContext = caller->GetBrowsingContext(); + } + } + } + + BrowsingContext* found = nullptr; + if (aName.IsEmpty()) { + // You can't find a browsing context with an empty name. + found = nullptr; + } else if (aName.LowerCaseEqualsLiteral("_blank")) { + // Just return null. Caller must handle creating a new window with + // a blank name. + found = nullptr; + } else if (nsContentUtils::IsSpecialName(aName)) { + found = FindWithSpecialName(aName, *requestingContext); + } else if (BrowsingContext* child = + FindWithNameInSubtree(aName, *requestingContext)) { + found = child; + } else { + BrowsingContext* current = this; + + do { + Span> siblings; + BrowsingContext* parent = current->GetParent(); + + if (!parent) { + // We've reached the root of the tree, consider browsing + // contexts in the same browsing context group. + siblings = mGroup->Toplevels(); + } else if (parent->NameEquals(aName) && + requestingContext->CanAccess(parent) && + parent->IsTargetable()) { + found = parent; + break; + } else { + siblings = parent->Children(); + } + + for (BrowsingContext* sibling : siblings) { + if (sibling == current) { + continue; + } + + if (BrowsingContext* relative = + sibling->FindWithNameInSubtree(aName, *requestingContext)) { + found = relative; + // Breaks the outer loop + parent = nullptr; + break; + } + } + + current = parent; + } while (current); + } + + // Helpers should perform access control checks, which means that we + // only need to assert that we can access found. + MOZ_DIAGNOSTIC_ASSERT(!found || requestingContext->CanAccess(found)); + + return found; +} + +BrowsingContext* BrowsingContext::FindChildWithName( + const nsAString& aName, BrowsingContext& aRequestingContext) { + if (aName.IsEmpty()) { + // You can't find a browsing context with the empty name. + return nullptr; + } + + for (BrowsingContext* child : Children()) { + if (child->NameEquals(aName) && aRequestingContext.CanAccess(child) && + child->IsTargetable()) { + return child; + } + } + + return nullptr; +} + +BrowsingContext* BrowsingContext::FindWithSpecialName( + const nsAString& aName, BrowsingContext& aRequestingContext) { + // TODO(farre): Neither BrowsingContext nor nsDocShell checks if the + // browsing context pointed to by a special name is active. Should + // it be? See Bug 1527913. + if (aName.LowerCaseEqualsLiteral("_self")) { + return this; + } + + if (aName.LowerCaseEqualsLiteral("_parent")) { + if (BrowsingContext* parent = GetParent()) { + return aRequestingContext.CanAccess(parent) ? parent : nullptr; + } + return this; + } + + if (aName.LowerCaseEqualsLiteral("_top")) { + BrowsingContext* top = Top(); + + return aRequestingContext.CanAccess(top) ? top : nullptr; + } + + return nullptr; +} + +BrowsingContext* BrowsingContext::FindWithNameInSubtree( + const nsAString& aName, BrowsingContext& aRequestingContext) { + MOZ_DIAGNOSTIC_ASSERT(!aName.IsEmpty()); + + if (NameEquals(aName) && aRequestingContext.CanAccess(this) && + IsTargetable()) { + return this; + } + + for (BrowsingContext* child : Children()) { + if (BrowsingContext* found = + child->FindWithNameInSubtree(aName, aRequestingContext)) { + return found; + } + } + + return nullptr; +} + +// For historical context, see: +// +// Bug 13871: Prevent frameset spoofing +// Bug 103638: Targets with same name in different windows open in wrong +// window with javascript +// Bug 408052: Adopt "ancestor" frame navigation policy +// Bug 1570207: Refactor logic to rely on BrowsingContextGroups to enforce +// origin attribute isolation. +bool BrowsingContext::CanAccess(BrowsingContext* aTarget, + bool aConsiderOpener) { + MOZ_ASSERT( + mDocShell, + "CanAccess() may only be called in the process of the accessing window"); + MOZ_ASSERT(aTarget, "Must have a target"); + + MOZ_DIAGNOSTIC_ASSERT( + Group() == aTarget->Group(), + "A BrowsingContext should never see a context from a different group"); + + // A frame can navigate itself and its own root. + if (aTarget == this || aTarget == Top()) { + return true; + } + + // A frame can navigate any frame with a same-origin ancestor. + for (BrowsingContext* bc = aTarget; bc; bc = bc->GetParent()) { + if (bc->mDocShell && nsDocShell::ValidateOrigin(this, bc)) { + return true; + } + } + + // If the target is a top-level document, a frame can navigate it if it can + // navigate its opener. + if (aConsiderOpener && !aTarget->GetParent()) { + if (RefPtr opener = aTarget->GetOpener()) { + return CanAccess(opener, false); + } + } + + return false; +} + +bool BrowsingContext::IsSandboxedFrom(BrowsingContext* aTarget) { + // If no target then not sandboxed. + if (!aTarget) { + return false; + } + + // We cannot be sandboxed from ourselves. + if (aTarget == this) { + return false; + } + + // Default the sandbox flags to our flags, so that if we can't retrieve the + // active document, we will still enforce our own. + uint32_t sandboxFlags = GetSandboxFlags(); + if (mDocShell) { + if (RefPtr doc = mDocShell->GetExtantDocument()) { + sandboxFlags = doc->GetSandboxFlags(); + } + } + + // If no flags, we are not sandboxed at all. + if (!sandboxFlags) { + return false; + } + + // If aTarget has an ancestor, it is not top level. + if (RefPtr ancestorOfTarget = aTarget->GetParent()) { + do { + // We are not sandboxed if we are an ancestor of target. + if (ancestorOfTarget == this) { + return false; + } + ancestorOfTarget = ancestorOfTarget->GetParent(); + } while (ancestorOfTarget); + + // Otherwise, we are sandboxed from aTarget. + return true; + } + + // aTarget is top level, are we the "one permitted sandboxed + // navigator", i.e. did we open aTarget? + if (aTarget->GetOnePermittedSandboxedNavigatorId() == Id()) { + return false; + } + + // If SANDBOXED_TOPLEVEL_NAVIGATION flag is not on, we are not sandboxed + // from our top. + if (!(sandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION) && aTarget == Top()) { + return false; + } + + // If SANDBOXED_TOPLEVEL_NAVIGATION_USER_ACTIVATION flag is not on, we are not + // sandboxed from our top if we have user interaction. + if (!(sandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION_USER_ACTIVATION) && + mCurrentWindowContext && + mCurrentWindowContext->HasValidTransientUserGestureActivation() && + aTarget == Top()) { + return false; + } + + // Otherwise, we are sandboxed from aTarget. + return true; +} + +RefPtr BrowsingContext::GetSessionStorageManager() { + RefPtr& manager = Top()->mSessionStorageManager; + if (!manager) { + manager = new SessionStorageManager(this); + } + return manager; +} + +bool BrowsingContext::CrossOriginIsolated() { + MOZ_ASSERT(NS_IsMainThread()); + + return StaticPrefs:: + dom_postMessage_sharedArrayBuffer_withCOOP_COEP_AtStartup() && + Top()->GetOpenerPolicy() == + nsILoadInfo:: + OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP && + XRE_IsContentProcess() && + StringBeginsWith(ContentChild::GetSingleton()->GetRemoteType(), + WITH_COOP_COEP_REMOTE_TYPE_PREFIX); +} + +void BrowsingContext::SetTriggeringAndInheritPrincipals( + nsIPrincipal* aTriggeringPrincipal, nsIPrincipal* aPrincipalToInherit, + uint64_t aLoadIdentifier) { + mTriggeringPrincipal = Some( + PrincipalWithLoadIdentifierTuple(aTriggeringPrincipal, aLoadIdentifier)); + if (aPrincipalToInherit) { + mPrincipalToInherit = Some( + PrincipalWithLoadIdentifierTuple(aPrincipalToInherit, aLoadIdentifier)); + } +} + +Tuple, nsCOMPtr> +BrowsingContext::GetTriggeringAndInheritPrincipalsForCurrentLoad() { + nsCOMPtr triggeringPrincipal = + GetSavedPrincipal(mTriggeringPrincipal); + nsCOMPtr principalToInherit = + GetSavedPrincipal(mPrincipalToInherit); + return MakeTuple(triggeringPrincipal, principalToInherit); +} + +nsIPrincipal* BrowsingContext::GetSavedPrincipal( + Maybe aPrincipalTuple) { + if (aPrincipalTuple) { + nsCOMPtr principal; + uint64_t loadIdentifier; + Tie(principal, loadIdentifier) = *aPrincipalTuple; + // We want to return a principal only if the load identifier for it + // matches the current one for this BC. + if (auto current = GetCurrentLoadIdentifier(); + current && *current == loadIdentifier) { + return principal; + } + } + return nullptr; +} + +BrowsingContext::~BrowsingContext() { + MOZ_DIAGNOSTIC_ASSERT(!mParentWindow || + !mParentWindow->mChildren.Contains(this)); + MOZ_DIAGNOSTIC_ASSERT(!mGroup || !mGroup->Toplevels().Contains(this)); + + mDeprioritizedLoadRunner.clear(); + + if (sBrowsingContexts) { + sBrowsingContexts->Remove(Id()); + } + UnregisterBrowserId(this); +} + +/* static */ +void BrowsingContext::DiscardFromContentParent(ContentParent* aCP) { + MOZ_ASSERT(XRE_IsParentProcess()); + + if (sBrowsingContexts) { + AutoTArray, 8> toDiscard; + for (const auto& entry : *sBrowsingContexts) { + auto* bc = entry.GetData()->Canonical(); + if (!bc->IsDiscarded() && bc->IsEmbeddedInProcess(aCP->ChildID())) { + toDiscard.AppendElement(bc); + } + } + + for (BrowsingContext* bc : toDiscard) { + bc->Detach(/* aFromIPC */ true); + } + } +} + +nsISupports* BrowsingContext::GetParentObject() const { + return xpc::NativeGlobal(xpc::PrivilegedJunkScope()); +} + +JSObject* BrowsingContext::WrapObject(JSContext* aCx, + JS::Handle aGivenProto) { + return BrowsingContext_Binding::Wrap(aCx, this, aGivenProto); +} + +bool BrowsingContext::WriteStructuredClone(JSContext* aCx, + JSStructuredCloneWriter* aWriter, + StructuredCloneHolder* aHolder) { + MOZ_DIAGNOSTIC_ASSERT(mEverAttached); + return (JS_WriteUint32Pair(aWriter, SCTAG_DOM_BROWSING_CONTEXT, 0) && + JS_WriteUint32Pair(aWriter, uint32_t(Id()), uint32_t(Id() >> 32))); +} + +/* static */ +JSObject* BrowsingContext::ReadStructuredClone(JSContext* aCx, + JSStructuredCloneReader* aReader, + StructuredCloneHolder* aHolder) { + uint32_t idLow = 0; + uint32_t idHigh = 0; + if (!JS_ReadUint32Pair(aReader, &idLow, &idHigh)) { + return nullptr; + } + uint64_t id = uint64_t(idHigh) << 32 | idLow; + + // Note: Do this check after reading our ID data. Returning null will abort + // the decode operation anyway, but we should at least be as safe as possible. + if (NS_WARN_IF(!NS_IsMainThread())) { + MOZ_DIAGNOSTIC_ASSERT(false, + "We shouldn't be trying to decode a BrowsingContext " + "on a background thread."); + return nullptr; + } + + JS::RootedValue val(aCx, JS::NullValue()); + // We'll get rooting hazard errors from the RefPtr destructor if it isn't + // destroyed before we try to return a raw JSObject*, so create it in its own + // scope. + if (RefPtr context = Get(id)) { + if (!GetOrCreateDOMReflector(aCx, context, &val) || !val.isObject()) { + return nullptr; + } + } + return val.toObjectOrNull(); +} + +bool BrowsingContext::CanSetOriginAttributes() { + // A discarded BrowsingContext has already been destroyed, and cannot modify + // its OriginAttributes. + if (NS_WARN_IF(IsDiscarded())) { + return false; + } + + // Before attaching is the safest time to set OriginAttributes, and the only + // allowed time for content BrowsingContexts. + if (!EverAttached()) { + return true; + } + + // Attached content BrowsingContexts may have been synced to other processes. + if (NS_WARN_IF(IsContent())) { + MOZ_CRASH(); + return false; + } + MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess()); + + // Cannot set OriginAttributes after we've created our child BrowsingContext. + if (NS_WARN_IF(!Children().IsEmpty())) { + return false; + } + + // Only allow setting OriginAttributes if we have no associated document, or + // the document is still `about:blank`. + // TODO: Bug 1273058 - should have no document when setting origin attributes. + if (WindowGlobalParent* window = Canonical()->GetCurrentWindowGlobal()) { + if (nsIURI* uri = window->GetDocumentURI()) { + MOZ_ASSERT(NS_IsAboutBlank(uri)); + return NS_IsAboutBlank(uri); + } + } + return true; +} + +Nullable BrowsingContext::GetAssociatedWindow() { + // nsILoadContext usually only returns same-process windows, + // so we intentionally return nullptr if this BC is out of + // process. + if (IsInProcess()) { + return WindowProxyHolder(this); + } + return nullptr; +} + +Nullable BrowsingContext::GetTopWindow() { + return Top()->GetAssociatedWindow(); +} + +Element* BrowsingContext::GetTopFrameElement() { + return Top()->GetEmbedderElement(); +} + +void BrowsingContext::SetUsePrivateBrowsing(bool aUsePrivateBrowsing, + ErrorResult& aError) { + nsresult rv = SetUsePrivateBrowsing(aUsePrivateBrowsing); + if (NS_FAILED(rv)) { + aError.Throw(rv); + } +} + +void BrowsingContext::SetUseTrackingProtectionWebIDL( + bool aUseTrackingProtection, ErrorResult& aRv) { + SetForceEnableTrackingProtection(aUseTrackingProtection, aRv); +} + +void BrowsingContext::GetOriginAttributes(JSContext* aCx, + JS::MutableHandle aVal, + ErrorResult& aError) { + AssertOriginAttributesMatchPrivateBrowsing(); + + if (!ToJSValue(aCx, mOriginAttributes, aVal)) { + aError.NoteJSContextException(aCx); + } +} + +NS_IMETHODIMP BrowsingContext::GetAssociatedWindow( + mozIDOMWindowProxy** aAssociatedWindow) { + nsCOMPtr win = GetDOMWindow(); + win.forget(aAssociatedWindow); + return NS_OK; +} + +NS_IMETHODIMP BrowsingContext::GetTopWindow(mozIDOMWindowProxy** aTopWindow) { + return Top()->GetAssociatedWindow(aTopWindow); +} + +NS_IMETHODIMP BrowsingContext::GetTopFrameElement(Element** aTopFrameElement) { + RefPtr topFrameElement = GetTopFrameElement(); + topFrameElement.forget(aTopFrameElement); + return NS_OK; +} + +NS_IMETHODIMP BrowsingContext::GetIsContent(bool* aIsContent) { + *aIsContent = IsContent(); + return NS_OK; +} + +NS_IMETHODIMP BrowsingContext::GetUsePrivateBrowsing( + bool* aUsePrivateBrowsing) { + *aUsePrivateBrowsing = mPrivateBrowsingId > 0; + return NS_OK; +} + +NS_IMETHODIMP BrowsingContext::SetUsePrivateBrowsing(bool aUsePrivateBrowsing) { + if (!CanSetOriginAttributes()) { + bool changed = aUsePrivateBrowsing != (mPrivateBrowsingId > 0); + if (changed) { + NS_WARNING("SetUsePrivateBrowsing when !CanSetOriginAttributes()"); + } + return changed ? NS_ERROR_FAILURE : NS_OK; + } + + return SetPrivateBrowsing(aUsePrivateBrowsing); +} + +NS_IMETHODIMP BrowsingContext::SetPrivateBrowsing(bool aPrivateBrowsing) { + if (!CanSetOriginAttributes()) { + NS_WARNING("Attempt to set PrivateBrowsing when !CanSetOriginAttributes"); + return NS_ERROR_FAILURE; + } + + bool changed = aPrivateBrowsing != (mPrivateBrowsingId > 0); + if (changed) { + mPrivateBrowsingId = aPrivateBrowsing ? 1 : 0; + if (IsContent()) { + mOriginAttributes.SyncAttributesWithPrivateBrowsing(aPrivateBrowsing); + } + } + AssertOriginAttributesMatchPrivateBrowsing(); + + if (changed && mDocShell) { + nsDocShell::Cast(mDocShell)->NotifyPrivateBrowsingChanged(); + } + return NS_OK; +} + +NS_IMETHODIMP BrowsingContext::GetUseRemoteTabs(bool* aUseRemoteTabs) { + *aUseRemoteTabs = mUseRemoteTabs; + return NS_OK; +} + +NS_IMETHODIMP BrowsingContext::SetRemoteTabs(bool aUseRemoteTabs) { + if (!CanSetOriginAttributes()) { + NS_WARNING("Attempt to set RemoteTabs when !CanSetOriginAttributes"); + return NS_ERROR_FAILURE; + } + + static bool annotated = false; + if (aUseRemoteTabs && !annotated) { + annotated = true; + CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::DOMIPCEnabled, + true); + } + + // Don't allow non-remote tabs with remote subframes. + if (NS_WARN_IF(!aUseRemoteTabs && mUseRemoteSubframes)) { + return NS_ERROR_UNEXPECTED; + } + + mUseRemoteTabs = aUseRemoteTabs; + return NS_OK; +} + +NS_IMETHODIMP BrowsingContext::GetUseRemoteSubframes( + bool* aUseRemoteSubframes) { + *aUseRemoteSubframes = mUseRemoteSubframes; + return NS_OK; +} + +NS_IMETHODIMP BrowsingContext::SetRemoteSubframes(bool aUseRemoteSubframes) { + if (!CanSetOriginAttributes()) { + NS_WARNING("Attempt to set RemoteSubframes when !CanSetOriginAttributes"); + return NS_ERROR_FAILURE; + } + + static bool annotated = false; + if (aUseRemoteSubframes && !annotated) { + annotated = true; + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::DOMFissionEnabled, true); + } + + // Don't allow non-remote tabs with remote subframes. + if (NS_WARN_IF(aUseRemoteSubframes && !mUseRemoteTabs)) { + return NS_ERROR_UNEXPECTED; + } + + mUseRemoteSubframes = aUseRemoteSubframes; + return NS_OK; +} + +NS_IMETHODIMP BrowsingContext::GetUseTrackingProtection( + bool* aUseTrackingProtection) { + *aUseTrackingProtection = false; + + if (GetForceEnableTrackingProtection() || + StaticPrefs::privacy_trackingprotection_enabled() || + (UsePrivateBrowsing() && + StaticPrefs::privacy_trackingprotection_pbmode_enabled())) { + *aUseTrackingProtection = true; + return NS_OK; + } + + if (GetParent()) { + return GetParent()->GetUseTrackingProtection(aUseTrackingProtection); + } + + return NS_OK; +} + +NS_IMETHODIMP BrowsingContext::SetUseTrackingProtection( + bool aUseTrackingProtection) { + return SetForceEnableTrackingProtection(aUseTrackingProtection); +} + +NS_IMETHODIMP BrowsingContext::GetScriptableOriginAttributes( + JSContext* aCx, JS::MutableHandle aVal) { + AssertOriginAttributesMatchPrivateBrowsing(); + + bool ok = ToJSValue(aCx, mOriginAttributes, aVal); + NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE); + return NS_OK; +} + +NS_IMETHODIMP_(void) +BrowsingContext::GetOriginAttributes(OriginAttributes& aAttrs) { + aAttrs = mOriginAttributes; + AssertOriginAttributesMatchPrivateBrowsing(); +} + +nsresult BrowsingContext::SetOriginAttributes(const OriginAttributes& aAttrs) { + if (!CanSetOriginAttributes()) { + NS_WARNING("Attempt to set OriginAttributes when !CanSetOriginAttributes"); + return NS_ERROR_FAILURE; + } + + AssertOriginAttributesMatchPrivateBrowsing(); + mOriginAttributes = aAttrs; + + bool isPrivate = mOriginAttributes.mPrivateBrowsingId != + nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID; + // Chrome Browsing Context can not contain OriginAttributes.mPrivateBrowsingId + if (IsChrome() && isPrivate) { + mOriginAttributes.mPrivateBrowsingId = + nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID; + } + SetPrivateBrowsing(isPrivate); + AssertOriginAttributesMatchPrivateBrowsing(); + + return NS_OK; +} + +void BrowsingContext::AssertCoherentLoadContext() { +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + // LoadContext should generally match our opener or parent. + if (IsContent()) { + if (RefPtr opener = GetOpener()) { + MOZ_DIAGNOSTIC_ASSERT(opener->mType == mType); + MOZ_DIAGNOSTIC_ASSERT(opener->mGroup == mGroup); + MOZ_DIAGNOSTIC_ASSERT(opener->mUseRemoteTabs == mUseRemoteTabs); + MOZ_DIAGNOSTIC_ASSERT(opener->mUseRemoteSubframes == mUseRemoteSubframes); + MOZ_DIAGNOSTIC_ASSERT(opener->mPrivateBrowsingId == mPrivateBrowsingId); + MOZ_DIAGNOSTIC_ASSERT( + opener->mOriginAttributes.EqualsIgnoringFPD(mOriginAttributes)); + } + } + if (RefPtr parent = GetParent()) { + MOZ_DIAGNOSTIC_ASSERT(parent->mType == mType); + MOZ_DIAGNOSTIC_ASSERT(parent->mGroup == mGroup); + MOZ_DIAGNOSTIC_ASSERT(parent->mUseRemoteTabs == mUseRemoteTabs); + MOZ_DIAGNOSTIC_ASSERT(parent->mUseRemoteSubframes == mUseRemoteSubframes); + MOZ_DIAGNOSTIC_ASSERT(parent->mPrivateBrowsingId == mPrivateBrowsingId); + MOZ_DIAGNOSTIC_ASSERT( + parent->mOriginAttributes.EqualsIgnoringFPD(mOriginAttributes)); + } + + // UseRemoteSubframes and UseRemoteTabs must match. + MOZ_DIAGNOSTIC_ASSERT( + !mUseRemoteSubframes || mUseRemoteTabs, + "Cannot set useRemoteSubframes without also setting useRemoteTabs"); + + // Double-check OriginAttributes/Private Browsing + AssertOriginAttributesMatchPrivateBrowsing(); +#endif +} + +void BrowsingContext::AssertOriginAttributesMatchPrivateBrowsing() { + // Chrome browsing contexts must not have a private browsing OriginAttribute + // Content browsing contexts must maintain the equality: + // mOriginAttributes.mPrivateBrowsingId == mPrivateBrowsingId + if (IsChrome()) { + MOZ_DIAGNOSTIC_ASSERT(mOriginAttributes.mPrivateBrowsingId == 0); + } else { + MOZ_DIAGNOSTIC_ASSERT(mOriginAttributes.mPrivateBrowsingId == + mPrivateBrowsingId); + } +} + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BrowsingContext) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsILoadContext) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION_CLASS(BrowsingContext) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(BrowsingContext) +NS_IMPL_CYCLE_COLLECTING_RELEASE(BrowsingContext) + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(BrowsingContext) + NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(BrowsingContext) + if (sBrowsingContexts) { + sBrowsingContexts->Remove(tmp->Id()); + } + UnregisterBrowserId(tmp); + + if (tmp->GetIsPopupSpam()) { + PopupBlocker::UnregisterOpenPopupSpam(); + // NOTE: Doesn't use SetIsPopupSpam, as it will be set all processes + // automatically. + tmp->mFields.SetWithoutSyncing(false); + } + + NS_IMPL_CYCLE_COLLECTION_UNLINK( + mDocShell, mParentWindow, mGroup, mEmbedderElement, mWindowContexts, + mCurrentWindowContext, mSessionStorageManager, mChildSessionHistory) + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(BrowsingContext) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE( + mDocShell, mParentWindow, mGroup, mEmbedderElement, mWindowContexts, + mCurrentWindowContext, mSessionStorageManager, mChildSessionHistory) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +class RemoteLocationProxy + : public RemoteObjectProxy { + public: + typedef RemoteObjectProxy Base; + + constexpr RemoteLocationProxy() + : RemoteObjectProxy(prototypes::id::Location) {} + + void NoteChildren(JSObject* aProxy, + nsCycleCollectionTraversalCallback& aCb) const override { + auto location = + static_cast(GetNative(aProxy)); + CycleCollectionNoteChild(aCb, location->GetBrowsingContext(), + "JS::GetPrivate(obj)->GetBrowsingContext()"); + } +}; + +static const RemoteLocationProxy sSingleton; + +// Give RemoteLocationProxy 2 reserved slots, like the other wrappers, +// so JSObject::swap can swap it with CrossCompartmentWrappers without requiring +// malloc. +template <> +const JSClass RemoteLocationProxy::Base::sClass = + PROXY_CLASS_DEF("Proxy", JSCLASS_HAS_RESERVED_SLOTS(2)); + +void BrowsingContext::Location(JSContext* aCx, + JS::MutableHandle aLocation, + ErrorResult& aError) { + aError.MightThrowJSException(); + sSingleton.GetProxyObject(aCx, &mLocation, /* aTransplantTo = */ nullptr, + aLocation); + if (!aLocation) { + aError.StealExceptionFromJSContext(aCx); + } +} + +bool BrowsingContext::RemoveRootFromBFCacheSync() { + if (WindowContext* wc = GetParentWindowContext()) { + if (RefPtr doc = wc->TopWindowContext()->GetDocument()) { + return doc->RemoveFromBFCacheSync(); + } + } + return false; +} + +nsresult BrowsingContext::CheckSandboxFlags(nsDocShellLoadState* aLoadState) { + const auto& sourceBC = aLoadState->SourceBrowsingContext(); + if (sourceBC.IsDiscarded() || (sourceBC && sourceBC->IsSandboxedFrom(this))) { + return NS_ERROR_DOM_INVALID_ACCESS_ERR; + } + return NS_OK; +} + +nsresult BrowsingContext::LoadURI(nsDocShellLoadState* aLoadState, + bool aSetNavigating) { + // Per spec, most load attempts are silently ignored when a BrowsingContext is + // null (which in our code corresponds to discarded), so we simply fail + // silently in those cases. Regardless, we cannot trigger loads in/from + // discarded BrowsingContexts via IPC, so we need to abort in any case. + if (IsDiscarded()) { + return NS_OK; + } + + MOZ_DIAGNOSTIC_ASSERT(aLoadState->TargetBrowsingContext().IsNull(), + "Targeting occurs in InternalLoad"); + + if (mDocShell) { + return mDocShell->LoadURI(aLoadState, aSetNavigating); + } + + // Note: We do this check both here and in `nsDocShell::InternalLoad`, since + // document-specific sandbox flags are only available in the process + // triggering the load, and we don't want the target process to have to trust + // the triggering process to do the appropriate checks for the + // BrowsingContext's sandbox flags. + MOZ_TRY(CheckSandboxFlags(aLoadState)); + SetTriggeringAndInheritPrincipals(aLoadState->TriggeringPrincipal(), + aLoadState->PrincipalToInherit(), + aLoadState->GetLoadIdentifier()); + + const auto& sourceBC = aLoadState->SourceBrowsingContext(); + MOZ_DIAGNOSTIC_ASSERT(!sourceBC || sourceBC->Group() == Group()); + if (sourceBC && sourceBC->IsInProcess()) { + if (!sourceBC->CanAccess(this)) { + return NS_ERROR_DOM_PROP_ACCESS_DENIED; + } + + nsCOMPtr win(sourceBC->GetDOMWindow()); + if (WindowGlobalChild* wgc = + win->GetCurrentInnerWindow()->GetWindowGlobalChild()) { + wgc->SendLoadURI(this, aLoadState, aSetNavigating); + } + } else if (XRE_IsParentProcess()) { + if (Canonical()->LoadInParent(aLoadState, aSetNavigating)) { + return NS_OK; + } + + if (ContentParent* cp = Canonical()->GetContentParent()) { + // Attempt to initiate this load immediately in the parent, if it succeeds + // it'll return a unique identifier so that we can find it later. + uint64_t loadIdentifier = 0; + if (Canonical()->AttemptSpeculativeLoadInParent(aLoadState)) { + MOZ_DIAGNOSTIC_ASSERT(GetCurrentLoadIdentifier().isSome()); + loadIdentifier = GetCurrentLoadIdentifier().value(); + aLoadState->SetChannelInitialized(true); + } + + cp->TransmitBlobDataIfBlobURL(aLoadState->URI(), + aLoadState->TriggeringPrincipal()); + + // Setup a confirmation callback once the content process receives this + // load. Normally we'd expect a PDocumentChannel actor to have been + // created to claim the load identifier by that time. If not, then it + // won't be coming, so make sure we clean up and deregister. + cp->SendLoadURI(this, aLoadState, aSetNavigating) + ->Then(GetMainThreadSerialEventTarget(), __func__, + [loadIdentifier]( + const PContentParent::LoadURIPromise::ResolveOrRejectValue& + aValue) { + if (loadIdentifier) { + net::DocumentLoadListener::CleanupParentLoadAttempt( + loadIdentifier); + } + }); + } + } else { + MOZ_DIAGNOSTIC_ASSERT(sourceBC); + if (!sourceBC) { + return NS_ERROR_UNEXPECTED; + } + // If we're in a content process and the source BC is no longer in-process, + // just fail silently. + } + return NS_OK; +} + +nsresult BrowsingContext::InternalLoad(nsDocShellLoadState* aLoadState) { + if (IsDiscarded()) { + return NS_OK; + } + SetTriggeringAndInheritPrincipals(aLoadState->TriggeringPrincipal(), + aLoadState->PrincipalToInherit(), + aLoadState->GetLoadIdentifier()); + + MOZ_DIAGNOSTIC_ASSERT(aLoadState->Target().IsEmpty(), + "should already have retargeted"); + MOZ_DIAGNOSTIC_ASSERT(!aLoadState->TargetBrowsingContext().IsNull(), + "should have target bc set"); + MOZ_DIAGNOSTIC_ASSERT(aLoadState->TargetBrowsingContext() == this, + "must be targeting this BrowsingContext"); + + if (mDocShell) { + return nsDocShell::Cast(mDocShell)->InternalLoad(aLoadState); + } + + // Note: We do this check both here and in `nsDocShell::InternalLoad`, since + // document-specific sandbox flags are only available in the process + // triggering the load, and we don't want the target process to have to trust + // the triggering process to do the appropriate checks for the + // BrowsingContext's sandbox flags. + MOZ_TRY(CheckSandboxFlags(aLoadState)); + + const auto& sourceBC = aLoadState->SourceBrowsingContext(); + if (XRE_IsParentProcess()) { + ContentParent* cp = Canonical()->GetContentParent(); + if (!cp || !cp->CanSend()) { + return NS_ERROR_FAILURE; + } + + MOZ_ALWAYS_SUCCEEDS( + SetCurrentLoadIdentifier(Some(aLoadState->GetLoadIdentifier()))); + Unused << cp->SendInternalLoad(aLoadState); + } else { + MOZ_DIAGNOSTIC_ASSERT(sourceBC); + MOZ_DIAGNOSTIC_ASSERT(sourceBC->Group() == Group()); + + if (!sourceBC->CanAccess(this)) { + return NS_ERROR_DOM_PROP_ACCESS_DENIED; + } + + nsCOMPtr win(sourceBC->GetDOMWindow()); + WindowGlobalChild* wgc = + win->GetCurrentInnerWindow()->GetWindowGlobalChild(); + if (!wgc || !wgc->CanSend()) { + return NS_ERROR_FAILURE; + } + + MOZ_ALWAYS_SUCCEEDS( + SetCurrentLoadIdentifier(Some(aLoadState->GetLoadIdentifier()))); + wgc->SendInternalLoad(aLoadState); + } + + return NS_OK; +} + +void BrowsingContext::DisplayLoadError(const nsAString& aURI) { + MOZ_LOG(GetLog(), LogLevel::Debug, ("DisplayLoadError")); + MOZ_DIAGNOSTIC_ASSERT(!IsDiscarded()); + MOZ_DIAGNOSTIC_ASSERT(mDocShell || XRE_IsParentProcess()); + + if (mDocShell) { + bool didDisplayLoadError = false; + mDocShell->DisplayLoadError(NS_ERROR_MALFORMED_URI, nullptr, + PromiseFlatString(aURI).get(), nullptr, + &didDisplayLoadError); + } else { + if (ContentParent* cp = Canonical()->GetContentParent()) { + Unused << cp->SendDisplayLoadError(this, PromiseFlatString(aURI)); + } + } +} + +WindowProxyHolder BrowsingContext::Window() { + return WindowProxyHolder(Self()); +} + +WindowProxyHolder BrowsingContext::GetFrames(ErrorResult& aError) { + return Window(); +} + +void BrowsingContext::Close(CallerType aCallerType, ErrorResult& aError) { + if (mIsDiscarded) { + return; + } + + if (IsFrame()) { + // .close() on frames is a no-op. + return; + } + + if (GetDOMWindow()) { + nsGlobalWindowOuter::Cast(GetDOMWindow()) + ->CloseOuter(aCallerType == CallerType::System); + return; + } + + // This is a bit of a hack for webcompat. Content needs to see an updated + // |window.closed| value as early as possible, so we set this before we + // actually send the DOMWindowClose event, which happens in the process where + // the document for this browsing context is loaded. + MOZ_ALWAYS_SUCCEEDS(SetClosed(true)); + + if (ContentChild* cc = ContentChild::GetSingleton()) { + cc->SendWindowClose(this, aCallerType == CallerType::System); + } else if (ContentParent* cp = Canonical()->GetContentParent()) { + Unused << cp->SendWindowClose(this, aCallerType == CallerType::System); + } +} + +/* + * Examine the current document state to see if we're in a way that is + * typically abused by web designers. The window.open code uses this + * routine to determine whether to allow the new window. + * Returns a value from the PopupControlState enum. + */ +PopupBlocker::PopupControlState BrowsingContext::RevisePopupAbuseLevel( + PopupBlocker::PopupControlState aControl) { + if (!IsContent()) { + return PopupBlocker::openAllowed; + } + + PopupBlocker::PopupControlState abuse = aControl; + switch (abuse) { + case PopupBlocker::openControlled: + case PopupBlocker::openBlocked: + case PopupBlocker::openOverridden: + if (IsPopupAllowed()) { + abuse = PopupBlocker::PopupControlState(abuse - 1); + } + break; + case PopupBlocker::openAbused: + if (IsPopupAllowed()) { + // Skip PopupBlocker::openBlocked + abuse = PopupBlocker::openControlled; + } + break; + case PopupBlocker::openAllowed: + break; + default: + NS_WARNING("Strange PopupControlState!"); + } + + // limit the number of simultaneously open popups + if (abuse == PopupBlocker::openAbused || abuse == PopupBlocker::openBlocked || + abuse == PopupBlocker::openControlled) { + int32_t popupMax = StaticPrefs::dom_popup_maximum(); + if (popupMax >= 0 && + PopupBlocker::GetOpenPopupSpamCount() >= (uint32_t)popupMax) { + abuse = PopupBlocker::openOverridden; + } + } + + // If we're currently in-process, attempt to consume transient user gesture + // activations. + if (RefPtr doc = GetExtantDocument()) { + // HACK: Some pages using bogus library + UA sniffing call window.open() + // from a blank iframe, only on Firefox, see bug 1685056. + // + // This is a hack-around to preserve behavior in that particular and + // specific case, by consuming activation on the parent document, so we + // don't care about the InProcessParent bits not being fission-safe or what + // not. + auto ConsumeTransientUserActivationForMultiplePopupBlocking = + [&]() -> bool { + if (doc->ConsumeTransientUserGestureActivation()) { + return true; + } + if (!doc->IsInitialDocument()) { + return false; + } + Document* parentDoc = doc->GetInProcessParentDocument(); + if (!parentDoc || + !parentDoc->NodePrincipal()->Equals(doc->NodePrincipal())) { + return false; + } + return parentDoc->ConsumeTransientUserGestureActivation(); + }; + + // If this popup is allowed, let's block any other for this event, forcing + // PopupBlocker::openBlocked state. + if ((abuse == PopupBlocker::openAllowed || + abuse == PopupBlocker::openControlled) && + StaticPrefs::dom_block_multiple_popups() && !IsPopupAllowed() && + !ConsumeTransientUserActivationForMultiplePopupBlocking()) { + nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns, + doc, nsContentUtils::eDOM_PROPERTIES, + "MultiplePopupsBlockedNoUserActivation"); + abuse = PopupBlocker::openBlocked; + } + } + + return abuse; +} + +std::tuple BrowsingContext::CanFocusCheck(CallerType aCallerType) { + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + if (!fm) { + return {false, false}; + } + + nsCOMPtr caller = do_QueryInterface(GetEntryGlobal()); + BrowsingContext* callerBC = caller ? caller->GetBrowsingContext() : nullptr; + RefPtr openerBC = GetOpener(); + MOZ_DIAGNOSTIC_ASSERT(!openerBC || openerBC->Group() == Group()); + + // Enforce dom.disable_window_flip (for non-chrome), but still allow the + // window which opened us to raise us at times when popups are allowed + // (bugs 355482 and 369306). + bool canFocus = aCallerType == CallerType::System || + !Preferences::GetBool("dom.disable_window_flip", true); + if (!canFocus && openerBC == callerBC) { + canFocus = (RevisePopupAbuseLevel(PopupBlocker::GetPopupControlState()) < + PopupBlocker::openBlocked); + } + + bool isActive = false; + if (XRE_IsParentProcess()) { + RefPtr chromeTop = + Canonical()->TopCrossChromeBoundary(); + nsCOMPtr activeWindow = fm->GetActiveWindow(); + isActive = (activeWindow == chromeTop->GetDOMWindow()); + } else { + isActive = (fm->GetActiveBrowsingContext() == Top()); + } + + return {canFocus, isActive}; +} + +void BrowsingContext::Focus(CallerType aCallerType, ErrorResult& aError) { + // These checks need to happen before the RequestFrameFocus call, which + // is why they are done in an untrusted process. If we wanted to enforce + // these in the parent, we'd need to do the checks there _also_. + // These should be kept in sync with nsGlobalWindowOuter::FocusOuter. + + auto [canFocus, isActive] = CanFocusCheck(aCallerType); + + if (!(canFocus || isActive)) { + return; + } + + // Permission check passed + + if (mEmbedderElement) { + // Make the activeElement in this process update synchronously. + nsContentUtils::RequestFrameFocus(*mEmbedderElement, true, aCallerType); + } + uint64_t actionId = nsFocusManager::GenerateFocusActionId(); + if (ContentChild* cc = ContentChild::GetSingleton()) { + cc->SendWindowFocus(this, aCallerType, actionId); + } else if (ContentParent* cp = Canonical()->GetContentParent()) { + Unused << cp->SendWindowFocus(this, aCallerType, actionId); + } +} + +void BrowsingContext::Blur(ErrorResult& aError) { + if (ContentChild* cc = ContentChild::GetSingleton()) { + cc->SendWindowBlur(this); + } else if (ContentParent* cp = Canonical()->GetContentParent()) { + Unused << cp->SendWindowBlur(this); + } +} + +Nullable BrowsingContext::GetWindow() { + if (XRE_IsParentProcess() && !IsInProcess()) { + return nullptr; + } + return WindowProxyHolder(this); +} + +Nullable BrowsingContext::GetTop(ErrorResult& aError) { + if (mIsDiscarded) { + return nullptr; + } + + // We never return null or throw an error, but the implementation in + // nsGlobalWindow does and we need to use the same signature. + return WindowProxyHolder(Top()); +} + +void BrowsingContext::GetOpener(JSContext* aCx, + JS::MutableHandle aOpener, + ErrorResult& aError) const { + RefPtr opener = GetOpener(); + if (!opener) { + aOpener.setNull(); + return; + } + + if (!ToJSValue(aCx, WindowProxyHolder(opener), aOpener)) { + aError.NoteJSContextException(aCx); + } +} + +// We never throw an error, but the implementation in nsGlobalWindow does and +// we need to use the same signature. +Nullable BrowsingContext::GetParent(ErrorResult& aError) { + if (mIsDiscarded) { + return nullptr; + } + + if (GetParent()) { + return WindowProxyHolder(GetParent()); + } + return WindowProxyHolder(this); +} + +void BrowsingContext::PostMessageMoz(JSContext* aCx, + JS::Handle aMessage, + const nsAString& aTargetOrigin, + const Sequence& aTransfer, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aError) { + if (mIsDiscarded) { + return; + } + + RefPtr sourceBc; + PostMessageData data; + data.targetOrigin() = aTargetOrigin; + data.subjectPrincipal() = &aSubjectPrincipal; + RefPtr callerInnerWindow; + nsAutoCString scriptLocation; + // We don't need to get the caller's agentClusterId since that is used for + // checking whether it's okay to sharing memory (and it's not allowed to share + // memory cross processes) + if (!nsGlobalWindowOuter::GatherPostMessageData( + aCx, aTargetOrigin, getter_AddRefs(sourceBc), data.origin(), + getter_AddRefs(data.targetOriginURI()), + getter_AddRefs(data.callerPrincipal()), + getter_AddRefs(callerInnerWindow), getter_AddRefs(data.callerURI()), + /* aCallerAgentClusterId */ nullptr, &scriptLocation, aError)) { + return; + } + if (sourceBc && sourceBc->IsDiscarded()) { + return; + } + data.source() = sourceBc; + data.isFromPrivateWindow() = + callerInnerWindow && + nsScriptErrorBase::ComputeIsFromPrivateWindow(callerInnerWindow); + data.innerWindowId() = callerInnerWindow ? callerInnerWindow->WindowID() : 0; + data.scriptLocation() = scriptLocation; + JS::Rooted transferArray(aCx); + aError = nsContentUtils::CreateJSValueFromSequenceOfObject(aCx, aTransfer, + &transferArray); + if (NS_WARN_IF(aError.Failed())) { + return; + } + + JS::CloneDataPolicy clonePolicy; + if (callerInnerWindow && callerInnerWindow->IsSharedMemoryAllowed()) { + clonePolicy.allowSharedMemoryObjects(); + } + + // We will see if the message is required to be in the same process or it can + // be in the different process after Write(). + ipc::StructuredCloneData message = ipc::StructuredCloneData( + StructuredCloneHolder::StructuredCloneScope::UnknownDestination, + StructuredCloneHolder::TransferringSupported); + message.Write(aCx, aMessage, transferArray, clonePolicy, aError); + if (NS_WARN_IF(aError.Failed())) { + return; + } + + ClonedOrErrorMessageData messageData; + if (ContentChild* cc = ContentChild::GetSingleton()) { + // The clone scope gets set when we write the message data based on the + // requirements of that data that we're writing. + // If the message data contins a shared memory object, then CloneScope would + // return SameProcess. Otherwise, it returns DifferentProcess. + if (message.CloneScope() == + StructuredCloneHolder::StructuredCloneScope::DifferentProcess) { + ClonedMessageData clonedMessageData; + if (!message.BuildClonedMessageDataForChild(cc, clonedMessageData)) { + aError.Throw(NS_ERROR_FAILURE); + return; + } + + messageData = std::move(clonedMessageData); + } else { + MOZ_ASSERT(message.CloneScope() == + StructuredCloneHolder::StructuredCloneScope::SameProcess); + + messageData = ErrorMessageData(); + + nsContentUtils::ReportToConsole( + nsIScriptError::warningFlag, "DOM Window"_ns, + callerInnerWindow ? callerInnerWindow->GetDocument() : nullptr, + nsContentUtils::eDOM_PROPERTIES, + "PostMessageSharedMemoryObjectToCrossOriginWarning"); + } + + cc->SendWindowPostMessage(this, messageData, data); + } else if (ContentParent* cp = Canonical()->GetContentParent()) { + if (message.CloneScope() == + StructuredCloneHolder::StructuredCloneScope::DifferentProcess) { + ClonedMessageData clonedMessageData; + if (!message.BuildClonedMessageDataForParent(cp, clonedMessageData)) { + aError.Throw(NS_ERROR_FAILURE); + return; + } + + messageData = std::move(clonedMessageData); + } else { + MOZ_ASSERT(message.CloneScope() == + StructuredCloneHolder::StructuredCloneScope::SameProcess); + + messageData = ErrorMessageData(); + + nsContentUtils::ReportToConsole( + nsIScriptError::warningFlag, "DOM Window"_ns, + callerInnerWindow ? callerInnerWindow->GetDocument() : nullptr, + nsContentUtils::eDOM_PROPERTIES, + "PostMessageSharedMemoryObjectToCrossOriginWarning"); + } + + Unused << cp->SendWindowPostMessage(this, messageData, data); + } +} + +void BrowsingContext::PostMessageMoz(JSContext* aCx, + JS::Handle aMessage, + const WindowPostMessageOptions& aOptions, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aError) { + PostMessageMoz(aCx, aMessage, aOptions.mTargetOrigin, aOptions.mTransfer, + aSubjectPrincipal, aError); +} + +void BrowsingContext::SendCommitTransaction(ContentParent* aParent, + const BaseTransaction& aTxn, + uint64_t aEpoch) { + Unused << aParent->SendCommitBrowsingContextTransaction(this, aTxn, aEpoch); +} + +void BrowsingContext::SendCommitTransaction(ContentChild* aChild, + const BaseTransaction& aTxn, + uint64_t aEpoch) { + aChild->SendCommitBrowsingContextTransaction(this, aTxn, aEpoch); +} + +BrowsingContext::IPCInitializer BrowsingContext::GetIPCInitializer() { + MOZ_DIAGNOSTIC_ASSERT(mEverAttached); + MOZ_DIAGNOSTIC_ASSERT(mType == Type::Content); + + IPCInitializer init; + init.mId = Id(); + init.mParentId = mParentWindow ? mParentWindow->Id() : 0; + init.mWindowless = mWindowless; + init.mUseRemoteTabs = mUseRemoteTabs; + init.mUseRemoteSubframes = mUseRemoteSubframes; + init.mCreatedDynamically = mCreatedDynamically; + init.mOriginAttributes = mOriginAttributes; + if (mChildSessionHistory && mozilla::SessionHistoryInParent()) { + init.mSessionHistoryIndex = mChildSessionHistory->Index(); + init.mSessionHistoryCount = mChildSessionHistory->Count(); + } + init.mRequestContextId = mRequestContextId; + init.mFields = mFields.RawValues(); + return init; +} + +already_AddRefed BrowsingContext::IPCInitializer::GetParent() { + RefPtr parent; + if (mParentId != 0) { + parent = WindowContext::GetById(mParentId); + MOZ_RELEASE_ASSERT(parent); + } + return parent.forget(); +} + +already_AddRefed BrowsingContext::IPCInitializer::GetOpener() { + RefPtr opener; + if (GetOpenerId() != 0) { + opener = BrowsingContext::Get(GetOpenerId()); + MOZ_RELEASE_ASSERT(opener); + } + return opener.forget(); +} + +void BrowsingContext::StartDelayedAutoplayMediaComponents() { + if (!mDocShell) { + return; + } + AUTOPLAY_LOG("%s : StartDelayedAutoplayMediaComponents for bc 0x%08" PRIx64, + XRE_IsParentProcess() ? "Parent" : "Child", Id()); + mDocShell->StartDelayedAutoplayMediaComponents(); +} + +nsresult BrowsingContext::ResetGVAutoplayRequestStatus() { + MOZ_ASSERT(IsTop(), + "Should only set GVAudibleAutoplayRequestStatus in the top-level " + "browsing context"); + + Transaction txn; + txn.SetGVAudibleAutoplayRequestStatus(GVAutoplayRequestStatus::eUNKNOWN); + txn.SetGVInaudibleAutoplayRequestStatus(GVAutoplayRequestStatus::eUNKNOWN); + return txn.Commit(this); +} + +void BrowsingContext::DidSet(FieldIndex) { + MOZ_ASSERT(IsTop(), + "Should only set GVAudibleAutoplayRequestStatus in the top-level " + "browsing context"); +} + +void BrowsingContext::DidSet(FieldIndex) { + MOZ_ASSERT(IsTop(), + "Should only set GVAudibleAutoplayRequestStatus in the top-level " + "browsing context"); +} + +void BrowsingContext::DidSet(FieldIndex, + ExplicitActiveStatus aOldValue) { + const bool isActive = IsActive(); + const bool wasActive = [&] { + if (aOldValue != ExplicitActiveStatus::None) { + return aOldValue == ExplicitActiveStatus::Active; + } + return GetParent() && GetParent()->IsActive(); + }(); + + if (isActive == wasActive) { + return; + } + + if (IsTop()) { + Group()->UpdateToplevelsSuspendedIfNeeded(); + +#if defined(XP_WIN) && defined(ACCESSIBILITY) + if (XRE_IsParentProcess() && a11y::Compatibility::IsDolphin()) { + // update active accessible documents on windows + if (BrowserParent* bp = Canonical()->GetBrowserParent()) { + if (a11y::DocAccessibleParent* tabDoc = + bp->GetTopLevelDocAccessible()) { + HWND window = tabDoc->GetEmulatedWindowHandle(); + MOZ_ASSERT(window); + if (window) { + if (isActive) { + a11y::nsWinUtils::ShowNativeWindow(window); + } else { + a11y::nsWinUtils::HideNativeWindow(window); + } + } + } + } + } +#endif + } + + PreOrderWalk([&](BrowsingContext* aContext) { + if (nsCOMPtr ds = aContext->GetDocShell()) { + nsDocShell::Cast(ds)->ActivenessMaybeChanged(); + } + }); +} + +bool BrowsingContext::CanSet(FieldIndex, + bool aNewValue, ContentParent* aSource) { + return IsTop() && CheckOnlyOwningProcessCanSet(aSource); +} + +void BrowsingContext::DidSet(FieldIndex, + bool aOldValue) { + if (!IsTop() || aOldValue == GetHasMainMediaController()) { + return; + } + Group()->UpdateToplevelsSuspendedIfNeeded(); +} + +bool BrowsingContext::InactiveForSuspend() const { + if (!StaticPrefs::dom_suspend_inactive_enabled()) { + return false; + } + // We should suspend a page only when it's inactive and doesn't have a main + // media controller. Having a main controller in context means it might be + // playing media, or waiting media keys to control media (could be not playing + // anything currently) + return !IsActive() && !GetHasMainMediaController(); +} + +bool BrowsingContext::CanSet( + FieldIndex, + const enum TouchEventsOverride& aTouchEventsOverride, + ContentParent* aSource) { + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool BrowsingContext::CanSet(FieldIndex, + const enum DisplayMode& aDisplayMOde, + ContentParent* aSource) { + return IsTop(); +} + +void BrowsingContext::DidSet(FieldIndex, + enum DisplayMode aOldValue) { + MOZ_ASSERT(IsTop()); + + if (GetDisplayMode() == aOldValue) { + return; + } + + PreOrderWalk([&](BrowsingContext* aContext) { + if (nsIDocShell* shell = aContext->GetDocShell()) { + if (nsPresContext* pc = shell->GetPresContext()) { + pc->MediaFeatureValuesChanged( + {MediaFeatureChangeReason::DisplayModeChange}, + // We're already iterating through sub documents, so we don't need + // to propagate the change again. + // + // Images and other resources don't change their display-mode + // evaluation, display-mode is a property of the browsing context. + MediaFeatureChangePropagation::JustThisDocument); + } + } + }); +} + +void BrowsingContext::DidSet(FieldIndex) { + MOZ_ASSERT(IsTop(), "Set muted flag on non top-level context!"); + USER_ACTIVATION_LOG("Set audio muted %d for %s browsing context 0x%08" PRIx64, + GetMuted(), XRE_IsParentProcess() ? "Parent" : "Child", + Id()); + PreOrderWalk([&](BrowsingContext* aContext) { + nsPIDOMWindowOuter* win = aContext->GetDOMWindow(); + if (win) { + win->RefreshMediaElementsVolume(); + } + }); +} + +void BrowsingContext::SetCustomUserAgent(const nsAString& aUserAgent, + ErrorResult& aRv) { + Top()->SetUserAgentOverride(aUserAgent, aRv); +} + +nsresult BrowsingContext::SetCustomUserAgent(const nsAString& aUserAgent) { + return Top()->SetUserAgentOverride(aUserAgent); +} + +void BrowsingContext::DidSet(FieldIndex) { + MOZ_ASSERT(IsTop()); + + PreOrderWalk([&](BrowsingContext* aContext) { + nsIDocShell* shell = aContext->GetDocShell(); + if (shell) { + shell->ClearCachedUserAgent(); + } + }); +} + +void BrowsingContext::SetCustomPlatform(const nsAString& aPlatform, + ErrorResult& aRv) { + Top()->SetPlatformOverride(aPlatform, aRv); +} + +void BrowsingContext::DidSet(FieldIndex) { + MOZ_ASSERT(IsTop()); + + PreOrderWalk([&](BrowsingContext* aContext) { + nsIDocShell* shell = aContext->GetDocShell(); + if (shell) { + shell->ClearCachedPlatform(); + } + }); +} + +bool BrowsingContext::CheckOnlyOwningProcessCanSet( + ContentParent* aSource) { + if (aSource) { + MOZ_ASSERT(XRE_IsParentProcess()); + + // Double-check ownership if we aren't the setter. + if (!Canonical()->IsOwnedByProcess(aSource->ChildID()) && + aSource->ChildID() != Canonical()->GetInFlightProcessId()) { + return false; + } + } else if (!IsInProcess() && !XRE_IsParentProcess()) { + // Don't allow this to be set from content processes that + // don't own the BrowsingContext. + return false; + } + + return true; +} + +bool BrowsingContext::CanSet(FieldIndex, + const bool& aValue, ContentParent* aSource) { + // Should only be set in the parent process. + return XRE_IsParentProcess() && !aSource && IsTop(); +} + +void BrowsingContext::DidSet(FieldIndex, + bool aOldValue) { + bool isActivateEvent = GetIsActiveBrowserWindowInternal(); + // The browser window containing this context has changed + // activation state so update window inactive document states + // for all in-process documents. + PreOrderWalk([isActivateEvent](BrowsingContext* aContext) { + if (RefPtr doc = aContext->GetExtantDocument()) { + doc->UpdateDocumentStates(NS_DOCUMENT_STATE_WINDOW_INACTIVE, true); + + if (XRE_IsContentProcess() && + (!aContext->GetParent() || !aContext->GetParent()->IsInProcess())) { + // Send the inner window an activate/deactivate event if + // the context is the top of a sub-tree of in-process + // contexts. + nsContentUtils::DispatchEventOnlyToChrome( + doc, doc->GetWindow()->GetCurrentInnerWindow(), + isActivateEvent ? u"activate"_ns : u"deactivate"_ns, + CanBubble::eYes, Cancelable::eYes, nullptr); + } + } + }); +} + +bool BrowsingContext::CanSet(FieldIndex, + const bool& aAllowContentRetargeting, + ContentParent* aSource) { + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool BrowsingContext::CanSet(FieldIndex, + const bool& aAllowContentRetargetingOnChildren, + ContentParent* aSource) { + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool BrowsingContext::CanSet(FieldIndex, + const bool& aAllowPlugins, + ContentParent* aSource) { + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool BrowsingContext::CanSet(FieldIndex, + const bool& aAllowed, ContentParent* aSource) { + return CheckOnlyEmbedderCanSet(aSource); +} + +bool BrowsingContext::CanSet(FieldIndex, + const bool& aUseErrorPages, + ContentParent* aSource) { + return CheckOnlyEmbedderCanSet(aSource); +} + +mozilla::dom::TouchEventsOverride BrowsingContext::TouchEventsOverride() const { + const BrowsingContext* bc = this; + while (bc) { + mozilla::dom::TouchEventsOverride tev = + bc->GetTouchEventsOverrideInternal(); + if (tev != mozilla::dom::TouchEventsOverride::None) { + return tev; + } + + bc = bc->GetParent(); + } + + return mozilla::dom::TouchEventsOverride::None; +} + +void BrowsingContext::SetTouchEventsOverride( + const enum TouchEventsOverride aTouchEventsOverride, ErrorResult& aRv) { + SetTouchEventsOverrideInternal(aTouchEventsOverride, aRv); +} + +nsresult BrowsingContext::SetTouchEventsOverride( + const enum TouchEventsOverride aTouchEventsOverride) { + ErrorResult rv; + SetTouchEventsOverride(aTouchEventsOverride, rv); + return rv.StealNSResult(); +} + +// We map `watchedByDevTools` WebIDL attribute to `watchedByDevToolsInternal` +// BC field. And we map it to the top level BrowsingContext. +bool BrowsingContext::WatchedByDevTools() { + return Top()->GetWatchedByDevToolsInternal(); +} + +// Enforce that the watchedByDevTools BC field can only be set on the top level +// Browsing Context. +bool BrowsingContext::CanSet(FieldIndex, + const bool& aWatchedByDevTools, + ContentParent* aSource) { + return IsTop(); +} +void BrowsingContext::SetWatchedByDevTools(bool aWatchedByDevTools, + ErrorResult& aRv) { + if (!IsTop()) { + aRv.ThrowInvalidModificationError( + "watchedByDevTools can only be set on top BrowsingContext"); + return; + } + SetWatchedByDevToolsInternal(aWatchedByDevTools, aRv); +} + +bool BrowsingContext::CanSet(FieldIndex, + const uint32_t& aDefaultLoadFlags, + ContentParent* aSource) { + // Bug 1623565 - Are these flags only used by the debugger, which makes it + // possible that this field can only be settable by the parent process? + return CheckOnlyOwningProcessCanSet(aSource); +} + +void BrowsingContext::DidSet(FieldIndex) { + auto loadFlags = GetDefaultLoadFlags(); + if (GetDocShell()) { + nsDocShell::Cast(GetDocShell())->SetLoadGroupDefaultLoadFlags(loadFlags); + } + + if (XRE_IsParentProcess()) { + PreOrderWalk([&](BrowsingContext* aContext) { + if (aContext != this) { + // Setting load flags on a discarded context has no effect. + Unused << aContext->SetDefaultLoadFlags(loadFlags); + } + }); + } +} + +bool BrowsingContext::CanSet(FieldIndex, + const bool& aUseGlobalHistory, + ContentParent* aSource) { + // Should only be set in the parent process. + // return XRE_IsParentProcess() && !aSource; + return true; +} + +bool BrowsingContext::CanSet(FieldIndex, + const nsString& aUserAgent, + ContentParent* aSource) { + if (!IsTop()) { + return false; + } + + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool BrowsingContext::CanSet(FieldIndex, + const nsString& aPlatform, + ContentParent* aSource) { + if (!IsTop()) { + return false; + } + + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool BrowsingContext::CheckOnlyEmbedderCanSet(ContentParent* aSource) { + if (XRE_IsParentProcess()) { + uint64_t childId = aSource ? aSource->ChildID() : 0; + return Canonical()->IsEmbeddedInProcess(childId); + } + return mEmbeddedByThisProcess; +} + +bool BrowsingContext::CanSet(FieldIndex, + const uint64_t& aValue, ContentParent* aSource) { + // If we have a parent window, our embedder inner window ID must match it. + if (mParentWindow) { + return mParentWindow->Id() == aValue; + } + + // For toplevel BrowsingContext instances, this value may only be set by the + // parent process, or initialized to `0`. + return CheckOnlyEmbedderCanSet(aSource); +} + +bool BrowsingContext::CanSet(FieldIndex, + const Maybe&, ContentParent* aSource) { + return CheckOnlyEmbedderCanSet(aSource); +} + +bool BrowsingContext::CanSet(FieldIndex, + const uint64_t& aValue, ContentParent* aSource) { + // Generally allow clearing this. We may want to be more precise about this + // check in the future. + if (aValue == 0) { + return true; + } + + if (aSource) { + MOZ_ASSERT(XRE_IsParentProcess()); + + // If in the parent process, double-check ownership and WindowGlobalParent + // as well. + RefPtr wgp = + WindowGlobalParent::GetByInnerWindowId(aValue); + if (NS_WARN_IF(!wgp) || NS_WARN_IF(wgp->BrowsingContext() != this)) { + return false; + } + + // Double-check ownership if we aren't the setter. + if (!Canonical()->IsOwnedByProcess(aSource->ChildID()) && + aSource->ChildID() != Canonical()->GetInFlightProcessId()) { + return false; + } + } else if (XRE_IsContentProcess() && !IsOwnedByProcess()) { + return false; + } + + // We must have access to the specified context. + RefPtr window = WindowContext::GetById(aValue); + return window && window->GetBrowsingContext() == this; +} + +void BrowsingContext::DidSet(FieldIndex) { + RefPtr prevWindowContext = mCurrentWindowContext.forget(); + mCurrentWindowContext = WindowContext::GetById(GetCurrentInnerWindowId()); + MOZ_ASSERT( + !mCurrentWindowContext || mWindowContexts.Contains(mCurrentWindowContext), + "WindowContext not registered?"); + + // Clear our cached `children` value, to ensure that JS sees the up-to-date + // value. + BrowsingContext_Binding::ClearCachedChildrenValue(this); + + if (XRE_IsParentProcess()) { + if (prevWindowContext != mCurrentWindowContext) { + if (prevWindowContext) { + prevWindowContext->Canonical()->DidBecomeCurrentWindowGlobal(false); + } + if (mCurrentWindowContext) { + mCurrentWindowContext->Canonical()->DidBecomeCurrentWindowGlobal(true); + } + } + BrowserParent::UpdateFocusFromBrowsingContext(); + } +} + +bool BrowsingContext::CanSet(FieldIndex, const bool& aValue, + ContentParent* aSource) { + // Ensure that we only mark a browsing context as popup spam once and never + // unmark it. + return aValue && !GetIsPopupSpam(); +} + +void BrowsingContext::DidSet(FieldIndex) { + if (GetIsPopupSpam()) { + PopupBlocker::RegisterOpenPopupSpam(); + } +} + +bool BrowsingContext::CanSet(FieldIndex, + const nsString& aMessageManagerGroup, + ContentParent* aSource) { + // Should only be set in the parent process on toplevel. + return XRE_IsParentProcess() && !aSource && IsTopContent(); +} + +bool BrowsingContext::CanSet( + FieldIndex, + const mozilla::hal::ScreenOrientation& aOrientationLock, + ContentParent* aSource) { + return IsTop(); +} + +bool BrowsingContext::IsLoading() { + if (GetLoading()) { + return true; + } + + // If we're in the same process as the page, we're possibly just + // updating the flag. + nsIDocShell* shell = GetDocShell(); + if (shell) { + Document* doc = shell->GetDocument(); + return doc && doc->GetReadyStateEnum() < Document::READYSTATE_COMPLETE; + } + + return false; +} + +void BrowsingContext::DidSet(FieldIndex) { + if (mFields.Get()) { + return; + } + + while (!mDeprioritizedLoadRunner.isEmpty()) { + nsCOMPtr runner = mDeprioritizedLoadRunner.popFirst(); + NS_DispatchToCurrentThread(runner.forget()); + } + + if (StaticPrefs::dom_separate_event_queue_for_post_message_enabled() && + Top() == this) { + Group()->FlushPostMessageEvents(); + } +} + +// Inform the Document for this context of the (potential) change in +// loading state +void BrowsingContext::DidSet(FieldIndex) { + nsPIDOMWindowOuter* outer = GetDOMWindow(); + if (!outer) { + MOZ_LOG(gTimeoutDeferralLog, mozilla::LogLevel::Debug, + ("DidSetAncestorLoading BC: %p -- No outer window", (void*)this)); + return; + } + Document* document = nsGlobalWindowOuter::Cast(outer)->GetExtantDoc(); + if (document) { + MOZ_LOG(gTimeoutDeferralLog, mozilla::LogLevel::Debug, + ("DidSetAncestorLoading BC: %p -- NotifyLoading(%d, %d, %d)", + (void*)this, GetAncestorLoading(), document->GetReadyStateEnum(), + document->GetReadyStateEnum())); + document->NotifyLoading(GetAncestorLoading(), document->GetReadyStateEnum(), + document->GetReadyStateEnum()); + } +} + +void BrowsingContext::DidSet(FieldIndex) { + MOZ_ASSERT(IsTop(), + "Should only set AuthorStyleDisabledDefault in the top " + "browsing context"); + + // We don't need to handle changes to this field, since PageStyleChild.jsm + // will respond to the PageStyle:Disable message in all content processes. + // + // But we store the state here on the top BrowsingContext so that the + // docshell has somewhere to look for the current author style disabling + // state when new iframes are inserted. +} + +void BrowsingContext::DidSet(FieldIndex, float aOldValue) { + if (GetTextZoom() == aOldValue) { + return; + } + + if (IsInProcess()) { + if (nsIDocShell* shell = GetDocShell()) { + if (nsPresContext* pc = shell->GetPresContext()) { + pc->RecomputeBrowsingContextDependentData(); + } + } + + for (BrowsingContext* child : Children()) { + // Setting text zoom on a discarded context has no effect. + Unused << child->SetTextZoom(GetTextZoom()); + } + } + + if (IsTop() && XRE_IsParentProcess()) { + if (Element* element = GetEmbedderElement()) { + auto dispatcher = MakeRefPtr( + element, u"TextZoomChange"_ns, CanBubble::eYes, + ChromeOnlyDispatch::eYes); + dispatcher->RunDOMEventWhenSafe(); + } + } +} + +// TODO(emilio): It'd be potentially nicer and cheaper to allow to set this only +// on the Top() browsing context, but there are a lot of tests that rely on +// zooming a subframe so... +void BrowsingContext::DidSet(FieldIndex, float aOldValue) { + if (GetFullZoom() == aOldValue) { + return; + } + + if (IsInProcess()) { + if (nsIDocShell* shell = GetDocShell()) { + if (nsPresContext* pc = shell->GetPresContext()) { + pc->RecomputeBrowsingContextDependentData(); + } + } + + for (BrowsingContext* child : Children()) { + // Setting full zoom on a discarded context has no effect. + Unused << child->SetFullZoom(GetFullZoom()); + } + } + + if (IsTop() && XRE_IsParentProcess()) { + if (Element* element = GetEmbedderElement()) { + auto dispatcher = MakeRefPtr( + element, u"FullZoomChange"_ns, CanBubble::eYes, + ChromeOnlyDispatch::eYes); + dispatcher->RunDOMEventWhenSafe(); + } + } +} + +void BrowsingContext::AddDeprioritizedLoadRunner(nsIRunnable* aRunner) { + MOZ_ASSERT(IsLoading()); + MOZ_ASSERT(Top() == this); + + RefPtr runner = new DeprioritizedLoadRunner(aRunner); + mDeprioritizedLoadRunner.insertBack(runner); + NS_DispatchToCurrentThreadQueue( + runner.forget(), StaticPrefs::page_load_deprioritization_period(), + EventQueuePriority::Idle); +} + +void BrowsingContext::GetHistoryID(JSContext* aCx, + JS::MutableHandle aVal, + ErrorResult& aError) { + if (!xpc::ID2JSValue(aCx, GetHistoryID(), aVal)) { + aError.Throw(NS_ERROR_OUT_OF_MEMORY); + } +} + +void BrowsingContext::InitSessionHistory() { + MOZ_ASSERT(!IsDiscarded()); + MOZ_ASSERT(IsTop()); + MOZ_ASSERT(EverAttached()); + + if (!GetHasSessionHistory()) { + MOZ_ALWAYS_SUCCEEDS(SetHasSessionHistory(true)); + } +} + +ChildSHistory* BrowsingContext::GetChildSessionHistory() { + if (!mozilla::SessionHistoryInParent()) { + // For now we're checking that the session history object for the child + // process is available before returning the ChildSHistory object, because + // it is the actual implementation that ChildSHistory forwards to. This can + // be removed once session history is stored exclusively in the parent + // process. + return mChildSessionHistory && mChildSessionHistory->IsInProcess() + ? mChildSessionHistory.get() + : nullptr; + } + + return mChildSessionHistory; +} + +void BrowsingContext::CreateChildSHistory() { + MOZ_ASSERT(IsTop()); + MOZ_ASSERT(GetHasSessionHistory()); + MOZ_DIAGNOSTIC_ASSERT(!mChildSessionHistory); + + // Because session history is global in a browsing context tree, every process + // that has access to a browsing context tree needs access to its session + // history. That is why we create the ChildSHistory object in every process + // where we have access to this browsing context (which is the top one). + mChildSessionHistory = new ChildSHistory(this); + + // If the top browsing context (this one) is loaded in this process then we + // also create the session history implementation for the child process. + // This can be removed once session history is stored exclusively in the + // parent process. + mChildSessionHistory->SetIsInProcess(IsInProcess()); +} + +void BrowsingContext::DidSet(FieldIndex, + bool aOldValue) { + MOZ_ASSERT(GetHasSessionHistory() || !aOldValue, + "We don't support turning off session history."); + + if (GetHasSessionHistory() && !aOldValue) { + CreateChildSHistory(); + } +} + +bool BrowsingContext::CanSet(FieldIndex, const uint32_t& aValue, + ContentParent* aSource) { + // We should only be able to set this for toplevel contexts which don't have + // an ID yet. + return GetBrowserId() == 0 && IsTop() && Children().IsEmpty(); +} + +bool BrowsingContext::CanSet(FieldIndex, + bool aNewValue, ContentParent* aSource) { + // Can only be cleared from `true` to `false`, and should only ever be set on + // the toplevel BrowsingContext. + return IsTop() && GetPendingInitialization() && !aNewValue; +} + +void BrowsingContext::SessionHistoryChanged(int32_t aIndexDelta, + int32_t aLengthDelta) { + if (XRE_IsParentProcess() || mozilla::SessionHistoryInParent()) { + // This method is used to test index and length for the session history + // in child process only. + return; + } + + if (!IsTop()) { + // Some tests have unexpected setup while Fission shistory is being + // implemented. + return; + } + + RefPtr shistory = GetChildSessionHistory(); + if (!shistory || !shistory->AsyncHistoryLength()) { + return; + } + + nsID changeID = shistory->AddPendingHistoryChange(aIndexDelta, aLengthDelta); + uint32_t index = shistory->Index(); + uint32_t length = shistory->Count(); + + // Do artificial history update through parent process to test asynchronous + // history.length handling. + ContentChild::GetSingleton()->SendSessionHistoryUpdate(this, index, length, + changeID); +} + +bool BrowsingContext::IsPopupAllowed() { + for (auto* context = GetCurrentWindowContext(); context; + context = context->GetParentWindowContext()) { + if (context->CanShowPopup()) { + return true; + } + } + + return false; +} + +void BrowsingContext::SessionHistoryCommit( + const LoadingSessionHistoryInfo& aInfo, uint32_t aLoadType, + bool aHadActiveEntry, bool aPersist, bool aCloneEntryChildren) { + nsID changeID = {}; + if (XRE_IsContentProcess()) { + RefPtr rootSH = Top()->GetChildSessionHistory(); + if (rootSH) { + if (!aInfo.mLoadIsFromSessionHistory) { + // We try to mimic as closely as possible what will happen in + // CanonicalBrowsingContext::SessionHistoryCommit. We'll be + // incrementing the session history length if we're not replacing, + // this is a top-level load or it's not the initial load in an iframe, + // and ShouldUpdateSessionHistory(loadType) returns true. + // It is possible that this leads to wrong length temporarily, but + // so would not having the check for replace. + if (!LOAD_TYPE_HAS_FLAGS( + aLoadType, nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY) && + (IsTop() || aHadActiveEntry) && + ShouldUpdateSessionHistory(aLoadType)) { + changeID = rootSH->AddPendingHistoryChange(); + } + } else { + // This is a load from session history, so we can update + // index and length immediately. + rootSH->SetIndexAndLength(aInfo.mRequestedIndex, + aInfo.mSessionHistoryLength, changeID); + } + } + ContentChild* cc = ContentChild::GetSingleton(); + mozilla::Unused << cc->SendHistoryCommit(this, aInfo.mLoadId, changeID, + aLoadType, aPersist, + aCloneEntryChildren); + } else { + Canonical()->SessionHistoryCommit(aInfo.mLoadId, changeID, aLoadType, + aPersist, aCloneEntryChildren); + } +} + +void BrowsingContext::SetActiveSessionHistoryEntry( + const Maybe& aPreviousScrollPos, SessionHistoryInfo* aInfo, + uint32_t aLoadType, uint32_t aUpdatedCacheKey) { + if (XRE_IsContentProcess()) { + // XXX Why we update cache key only in content process case? + if (aUpdatedCacheKey != 0) { + aInfo->SetCacheKey(aUpdatedCacheKey); + } + + nsID changeID = {}; + RefPtr shistory = Top()->GetChildSessionHistory(); + if (shistory) { + changeID = shistory->AddPendingHistoryChange(); + } + ContentChild::GetSingleton()->SendSetActiveSessionHistoryEntry( + this, aPreviousScrollPos, *aInfo, aLoadType, aUpdatedCacheKey, + changeID); + } else { + Canonical()->SetActiveSessionHistoryEntry( + aPreviousScrollPos, aInfo, aLoadType, aUpdatedCacheKey, nsID()); + } +} + +void BrowsingContext::ReplaceActiveSessionHistoryEntry( + SessionHistoryInfo* aInfo) { + if (XRE_IsContentProcess()) { + ContentChild::GetSingleton()->SendReplaceActiveSessionHistoryEntry(this, + *aInfo); + } else { + Canonical()->ReplaceActiveSessionHistoryEntry(aInfo); + } +} + +void BrowsingContext::RemoveDynEntriesFromActiveSessionHistoryEntry() { + if (XRE_IsContentProcess()) { + ContentChild::GetSingleton() + ->SendRemoveDynEntriesFromActiveSessionHistoryEntry(this); + } else { + Canonical()->RemoveDynEntriesFromActiveSessionHistoryEntry(); + } +} + +void BrowsingContext::RemoveFromSessionHistory() { + if (XRE_IsContentProcess()) { + ContentChild::GetSingleton()->SendRemoveFromSessionHistory(this); + } else { + Canonical()->RemoveFromSessionHistory(); + } +} + +void BrowsingContext::HistoryGo(int32_t aOffset, uint64_t aHistoryEpoch, + bool aRequireUserInteraction, + std::function&& aResolver) { + if (XRE_IsContentProcess()) { + ContentChild::GetSingleton()->SendHistoryGo( + this, aOffset, aHistoryEpoch, aRequireUserInteraction, + std::move(aResolver), + [](mozilla::ipc:: + ResponseRejectReason) { /* FIXME Is ignoring this fine? */ }); + } else { + Canonical()->HistoryGo( + aOffset, aHistoryEpoch, aRequireUserInteraction, + Canonical()->GetContentParent() + ? Some(Canonical()->GetContentParent()->ChildID()) + : Nothing(), + std::move(aResolver)); + } +} + +void BrowsingContext::SetChildSHistory(ChildSHistory* aChildSHistory) { + mChildSessionHistory = aChildSHistory; + mChildSessionHistory->SetBrowsingContext(this); + mFields.SetWithoutSyncing(true); +} + +bool BrowsingContext::ShouldUpdateSessionHistory(uint32_t aLoadType) { + // We don't update session history on reload unless we're loading + // an iframe in shift-reload case. + return nsDocShell::ShouldUpdateGlobalHistory(aLoadType) && + (!(aLoadType & nsIDocShell::LOAD_CMD_RELOAD) || + (IsForceReloadType(aLoadType) && IsFrame())); +} + +nsresult BrowsingContext::CheckLocationChangeRateLimit(CallerType aCallerType) { + // We only rate limit non system callers + if (aCallerType == CallerType::System) { + return NS_OK; + } + + // Fetch rate limiting preferences + uint32_t limitCount = + StaticPrefs::dom_navigation_locationChangeRateLimit_count(); + uint32_t timeSpanSeconds = + StaticPrefs::dom_navigation_locationChangeRateLimit_timespan(); + + // Disable throttling if either of the preferences is set to 0. + if (limitCount == 0 || timeSpanSeconds == 0) { + return NS_OK; + } + + TimeDuration throttleSpan = TimeDuration::FromSeconds(timeSpanSeconds); + + if (mLocationChangeRateLimitSpanStart.IsNull() || + ((TimeStamp::Now() - mLocationChangeRateLimitSpanStart) > throttleSpan)) { + // Initial call or timespan exceeded, reset counter and timespan. + mLocationChangeRateLimitSpanStart = TimeStamp::Now(); + mLocationChangeRateLimitCount = 1; + return NS_OK; + } + + if (mLocationChangeRateLimitCount >= limitCount) { + // Rate limit reached + + Document* doc = GetDocument(); + if (doc) { + nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, "DOM"_ns, doc, + nsContentUtils::eDOM_PROPERTIES, + "LocChangeFloodingPrevented"); + } + + return NS_ERROR_DOM_SECURITY_ERR; + } + + mLocationChangeRateLimitCount++; + return NS_OK; +} + +void BrowsingContext::ResetLocationChangeRateLimit() { + // Resetting the timestamp object will cause the check function to + // init again and reset the rate limit. + mLocationChangeRateLimitSpanStart = TimeStamp(); +} + +} // namespace dom + +namespace ipc { + +void IPDLParamTraits>::Write( + IPC::Message* aMsg, IProtocol* aActor, + const dom::MaybeDiscarded& aParam) { + MOZ_DIAGNOSTIC_ASSERT(!aParam.GetMaybeDiscarded() || + aParam.GetMaybeDiscarded()->EverAttached()); + uint64_t id = aParam.ContextId(); + WriteIPDLParam(aMsg, aActor, id); +} + +bool IPDLParamTraits>::Read( + const IPC::Message* aMsg, PickleIterator* aIter, IProtocol* aActor, + dom::MaybeDiscarded* aResult) { + uint64_t id = 0; + if (!ReadIPDLParam(aMsg, aIter, aActor, &id)) { + return false; + } + + if (id == 0) { + *aResult = nullptr; + } else if (RefPtr bc = dom::BrowsingContext::Get(id)) { + *aResult = std::move(bc); + } else { + aResult->SetDiscarded(id); + } + return true; +} + +void IPDLParamTraits::Write( + IPC::Message* aMessage, IProtocol* aActor, + const dom::BrowsingContext::IPCInitializer& aInit) { + // Write actor ID parameters. + WriteIPDLParam(aMessage, aActor, aInit.mId); + WriteIPDLParam(aMessage, aActor, aInit.mParentId); + WriteIPDLParam(aMessage, aActor, aInit.mWindowless); + WriteIPDLParam(aMessage, aActor, aInit.mUseRemoteTabs); + WriteIPDLParam(aMessage, aActor, aInit.mUseRemoteSubframes); + WriteIPDLParam(aMessage, aActor, aInit.mCreatedDynamically); + WriteIPDLParam(aMessage, aActor, aInit.mOriginAttributes); + WriteIPDLParam(aMessage, aActor, aInit.mRequestContextId); + WriteIPDLParam(aMessage, aActor, aInit.mSessionHistoryIndex); + WriteIPDLParam(aMessage, aActor, aInit.mSessionHistoryCount); + WriteIPDLParam(aMessage, aActor, aInit.mFields); +} + +bool IPDLParamTraits::Read( + const IPC::Message* aMessage, PickleIterator* aIterator, IProtocol* aActor, + dom::BrowsingContext::IPCInitializer* aInit) { + // Read actor ID parameters. + if (!ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mId) || + !ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mParentId) || + !ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mWindowless) || + !ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mUseRemoteTabs) || + !ReadIPDLParam(aMessage, aIterator, aActor, + &aInit->mUseRemoteSubframes) || + !ReadIPDLParam(aMessage, aIterator, aActor, + &aInit->mCreatedDynamically) || + !ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mOriginAttributes) || + !ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mRequestContextId) || + !ReadIPDLParam(aMessage, aIterator, aActor, + &aInit->mSessionHistoryIndex) || + !ReadIPDLParam(aMessage, aIterator, aActor, + &aInit->mSessionHistoryCount) || + !ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mFields)) { + return false; + } + return true; +} + +template struct IPDLParamTraits; + +} // namespace ipc +} // namespace mozilla diff --git a/docshell/base/BrowsingContext.h b/docshell/base/BrowsingContext.h new file mode 100644 index 0000000000..4b09798e01 --- /dev/null +++ b/docshell/base/BrowsingContext.h @@ -0,0 +1,1166 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_BrowsingContext_h +#define mozilla_dom_BrowsingContext_h + +#include +#include "GVAutoplayRequestUtils.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/HalScreenConfiguration.h" +#include "mozilla/LinkedList.h" +#include "mozilla/Maybe.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Span.h" +#include "mozilla/Tuple.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/LocationBase.h" +#include "mozilla/dom/MaybeDiscarded.h" +#include "mozilla/dom/PopupBlocker.h" +#include "mozilla/dom/UserActivation.h" +#include "mozilla/dom/BrowsingContextBinding.h" +#include "mozilla/dom/ScreenOrientationBinding.h" +#include "mozilla/dom/SyncedContext.h" +#include "nsCOMPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "nsIDocShell.h" +#include "nsTArray.h" +#include "nsWrapperCache.h" +#include "nsILoadInfo.h" +#include "nsILoadContext.h" +#include "nsThreadUtils.h" + +class nsDocShellLoadState; +class nsGlobalWindowInner; +class nsGlobalWindowOuter; +class nsIPrincipal; +class nsOuterWindowProxy; +struct nsPoint; +class PickleIterator; + +namespace IPC { +class Message; +} // namespace IPC + +namespace mozilla { + +class ErrorResult; +class LogModule; + +namespace ipc { +class IProtocol; + +template +struct IPDLParamTraits; +} // namespace ipc + +namespace dom { +class BrowsingContent; +class BrowsingContextGroup; +class CanonicalBrowsingContext; +class ChildSHistory; +class ContentParent; +class Element; +struct LoadingSessionHistoryInfo; +template +struct Nullable; +template +class Sequence; +class SessionHistoryInfo; +class SessionStorageManager; +class StructuredCloneHolder; +class WindowContext; +struct WindowPostMessageOptions; +class WindowProxyHolder; + +enum class ExplicitActiveStatus : uint8_t { + None, + Active, + Inactive, + EndGuard_, +}; + +// Fields are, by default, settable by any process and readable by any process. +// Racy sets will be resolved as-if they occurred in the order the parent +// process finds out about them. +// +// The `DidSet` and `CanSet` methods may be overloaded to provide different +// behavior for a specific field. +// * `DidSet` is called to run code in every process whenever the value is +// updated (This currently occurs even if the value didn't change, though +// this may change in the future). +// * `CanSet` is called before attempting to set the value, in both the process +// which calls `Set`, and the parent process, and will kill the misbehaving +// process if it fails. +#define MOZ_EACH_BC_FIELD(FIELD) \ + FIELD(Name, nsString) \ + FIELD(Closed, bool) \ + FIELD(ExplicitActive, ExplicitActiveStatus) \ + /* Top()-only. If true, new-playing media will be suspended when in an \ + * inactive browsing context. */ \ + FIELD(SuspendMediaWhenInactive, bool) \ + /* If true, we're within the nested event loop in window.open, and this \ + * context may not be used as the target of a load */ \ + FIELD(PendingInitialization, bool) \ + /* Indicates if the browser window is active for the purpose of the \ + * :-moz-window-inactive pseudoclass. Only read from or set on the \ + * top BrowsingContext. */ \ + FIELD(IsActiveBrowserWindowInternal, bool) \ + FIELD(OpenerPolicy, nsILoadInfo::CrossOriginOpenerPolicy) \ + /* Current opener for the BrowsingContext. Weak reference */ \ + FIELD(OpenerId, uint64_t) \ + FIELD(OnePermittedSandboxedNavigatorId, uint64_t) \ + /* WindowID of the inner window which embeds this BC */ \ + FIELD(EmbedderInnerWindowId, uint64_t) \ + FIELD(CurrentInnerWindowId, uint64_t) \ + FIELD(HadOriginalOpener, bool) \ + FIELD(IsPopupSpam, bool) \ + /* Hold the audio muted state and should be used on top level browsing \ + * contexts only */ \ + FIELD(Muted, bool) \ + /* See nsSandboxFlags.h for the possible flags. */ \ + FIELD(SandboxFlags, uint32_t) \ + FIELD(InitialSandboxFlags, uint32_t) \ + /* A non-zero unique identifier for the browser element that is hosting \ + * this \ + * BrowsingContext tree. Every BrowsingContext in the element's tree will \ + * return the same ID in all processes and it will remain stable \ + * regardless of process changes. When a browser element's frameloader is \ + * switched to another browser element this ID will remain the same but \ + * hosted under the under the new browser element. */ \ + FIELD(BrowserId, uint64_t) \ + FIELD(HistoryID, nsID) \ + FIELD(InRDMPane, bool) \ + FIELD(Loading, bool) \ + /* A field only set on top browsing contexts, which indicates that either: \ + * \ + * * This is a browsing context created explicitly for printing or print \ + * preview (thus hosting static documents). \ + * \ + * * This is a browsing context where something in this tree is calling \ + * window.print() (and thus showing a modal dialog). \ + * \ + * We use it exclusively to block navigation for both of these cases. */ \ + FIELD(IsPrinting, bool) \ + FIELD(AncestorLoading, bool) \ + FIELD(AllowPlugins, bool) \ + FIELD(AllowContentRetargeting, bool) \ + FIELD(AllowContentRetargetingOnChildren, bool) \ + FIELD(ForceEnableTrackingProtection, bool) \ + FIELD(UseGlobalHistory, bool) \ + FIELD(FullscreenAllowedByOwner, bool) \ + /* These field are used to store the states of autoplay media request on \ + * GeckoView only, and it would only be modified on the top level browsing \ + * context. */ \ + FIELD(GVAudibleAutoplayRequestStatus, GVAutoplayRequestStatus) \ + FIELD(GVInaudibleAutoplayRequestStatus, GVAutoplayRequestStatus) \ + /* ScreenOrientation-related APIs */ \ + FIELD(CurrentOrientationAngle, float) \ + FIELD(CurrentOrientationType, mozilla::dom::OrientationType) \ + FIELD(OrientationLock, mozilla::hal::ScreenOrientation) \ + FIELD(UserAgentOverride, nsString) \ + FIELD(TouchEventsOverrideInternal, mozilla::dom::TouchEventsOverride) \ + FIELD(EmbedderElementType, Maybe) \ + FIELD(MessageManagerGroup, nsString) \ + FIELD(MaxTouchPointsOverride, uint8_t) \ + FIELD(FullZoom, float) \ + FIELD(WatchedByDevToolsInternal, bool) \ + FIELD(TextZoom, float) \ + /* The current in-progress load. */ \ + FIELD(CurrentLoadIdentifier, Maybe) \ + /* See nsIRequest for possible flags. */ \ + FIELD(DefaultLoadFlags, uint32_t) \ + /* Signals that session history is enabled for this browsing context tree. \ + * This is only ever set to true on the top BC, so consumers need to get \ + * the value from the top BC! */ \ + FIELD(HasSessionHistory, bool) \ + /* Tracks if this context is the only top-level document in the session \ + * history of the context. */ \ + FIELD(IsSingleToplevelInHistory, bool) \ + FIELD(UseErrorPages, bool) \ + FIELD(PlatformOverride, nsString) \ + FIELD(HasLoadedNonInitialDocument, bool) \ + FIELD(CreatedDynamically, bool) \ + /* Default value for nsIContentViewer::authorStyleDisabled in any new \ + * browsing contexts created as a descendant of this one. Valid only for \ + * top BCs. */ \ + FIELD(AuthorStyleDisabledDefault, bool) \ + FIELD(DisplayMode, mozilla::dom::DisplayMode) \ + /* True if the top level browsing context owns a main media controller */ \ + FIELD(HasMainMediaController, bool) + +// BrowsingContext, in this context, is the cross process replicated +// environment in which information about documents is stored. In +// particular the tree structure of nested browsing contexts is +// represented by the tree of BrowsingContexts. +// +// The tree of BrowsingContexts is created in step with its +// corresponding nsDocShell, and when nsDocShells are connected +// through a parent/child relationship, so are BrowsingContexts. The +// major difference is that BrowsingContexts are replicated (synced) +// to the parent process, making it possible to traverse the +// BrowsingContext tree for a tab, in both the parent and the child +// process. +// +// Trees of BrowsingContexts should only ever contain nodes of the +// same BrowsingContext::Type. This is enforced by asserts in the +// BrowsingContext::Create* methods. +class BrowsingContext : public nsILoadContext, public nsWrapperCache { + MOZ_DECL_SYNCED_CONTEXT(BrowsingContext, MOZ_EACH_BC_FIELD) + + public: + enum class Type { Chrome, Content }; + + static void Init(); + static LogModule* GetLog(); + + // Look up a BrowsingContext in the current process by ID. + static already_AddRefed Get(uint64_t aId); + static already_AddRefed Get(GlobalObject&, uint64_t aId) { + return Get(aId); + } + // Look up the top-level BrowsingContext by BrowserID. + static already_AddRefed GetCurrentTopByBrowserId( + uint64_t aBrowserId); + + static already_AddRefed GetFromWindow( + WindowProxyHolder& aProxy); + static already_AddRefed GetFromWindow( + GlobalObject&, WindowProxyHolder& aProxy) { + return GetFromWindow(aProxy); + } + + static void DiscardFromContentParent(ContentParent* aCP); + + // Create a brand-new toplevel BrowsingContext with no relationships to other + // BrowsingContexts, and which is not embedded within any or frame + // element. + // + // This BrowsingContext is immediately attached, and cannot have LoadContext + // flags customized unless it is of `Type::Chrome`. + // + // The process which created this BrowsingContext is responsible for detaching + // it. + static already_AddRefed CreateIndependent(Type aType); + + // Create a brand-new BrowsingContext object, but does not immediately attach + // it. State such as OriginAttributes and PrivateBrowsingId may be customized + // to configure the BrowsingContext before it is attached. + // + // `EnsureAttached()` must be called before the BrowsingContext is used for a + // DocShell, BrowserParent, or BrowserBridgeChild. + static already_AddRefed CreateDetached( + nsGlobalWindowInner* aParent, BrowsingContext* aOpener, + BrowsingContextGroup* aSpecificGroup, const nsAString& aName, Type aType, + bool aCreatedDynamically = false); + + void EnsureAttached(); + + bool EverAttached() const { return mEverAttached; } + + // Cast this object to a canonical browsing context, and return it. + CanonicalBrowsingContext* Canonical(); + + // Is the most recent Document in this BrowsingContext loaded within this + // process? This may be true with a null mDocShell after the Window has been + // closed. + bool IsInProcess() const { return mIsInProcess; } + + bool IsOwnedByProcess() const; + + bool CanHaveRemoteOuterProxies() const { + return !mIsInProcess || mDanglingRemoteOuterProxies; + } + + // Has this BrowsingContext been discarded. A discarded browsing context has + // been destroyed, and may not be available on the other side of an IPC + // message. + bool IsDiscarded() const { return mIsDiscarded; } + + // Returns true if none of the BrowsingContext's ancestor BrowsingContexts or + // WindowContexts are discarded or cached. + bool AncestorsAreCurrent() const; + + bool Windowless() const { return mWindowless; } + + // Get the DocShell for this BrowsingContext if it is in-process, or + // null if it's not. + nsIDocShell* GetDocShell() const { return mDocShell; } + void SetDocShell(nsIDocShell* aDocShell); + void ClearDocShell() { mDocShell = nullptr; } + + // Get the Document for this BrowsingContext if it is in-process, or + // null if it's not. + Document* GetDocument() const { + return mDocShell ? mDocShell->GetDocument() : nullptr; + } + Document* GetExtantDocument() const { + return mDocShell ? mDocShell->GetExtantDocument() : nullptr; + } + + // This cleans up remote outer window proxies that might have been left behind + // when the browsing context went from being remote to local. It does this by + // turning them into cross-compartment wrappers to aOuter. If there is already + // a remote proxy in the compartment of aOuter, then aOuter will get swapped + // to it and the value of aOuter will be set to the object that used to be the + // remote proxy and is now an OuterWindowProxy. + void CleanUpDanglingRemoteOuterWindowProxies( + JSContext* aCx, JS::MutableHandle aOuter); + + // Get the embedder element for this BrowsingContext if the embedder is + // in-process, or null if it's not. + Element* GetEmbedderElement() const { return mEmbedderElement; } + void SetEmbedderElement(Element* aEmbedder); + + // Called after the BrowingContext has been embedded in a FrameLoader. This + // happens after `SetEmbedderElement` is called on the BrowsingContext and + // after the BrowsingContext has been set on the FrameLoader. + void Embed(); + + // Get the outer window object for this BrowsingContext if it is in-process + // and still has a docshell, or null otherwise. + nsPIDOMWindowOuter* GetDOMWindow() const { + return mDocShell ? mDocShell->GetWindow() : nullptr; + } + + uint64_t GetRequestContextId() const { return mRequestContextId; } + + // Detach the current BrowsingContext from its parent, in both the + // child and the parent process. + void Detach(bool aFromIPC = false); + + // Prepare this BrowsingContext to leave the current process. + void PrepareForProcessChange(); + + // Triggers a load in the process which currently owns this BrowsingContext. + nsresult LoadURI(nsDocShellLoadState* aLoadState, + bool aSetNavigating = false); + + nsresult InternalLoad(nsDocShellLoadState* aLoadState); + + // Removes the root document for this BrowsingContext tree from the BFCache, + // if it is cached, and returns true if it was. + bool RemoveRootFromBFCacheSync(); + + // If the load state includes a source BrowsingContext has been passed, check + // to see if we are sandboxed from it as the result of an iframe or CSP + // sandbox. + nsresult CheckSandboxFlags(nsDocShellLoadState* aLoadState); + + void DisplayLoadError(const nsAString& aURI); + + // Determine if the current BrowsingContext is in the BFCache. + bool IsCached() const; + + // Check that this browsing context is targetable for navigations (i.e. that + // it is neither closed, cached, nor discarded). + bool IsTargetable() const; + + // True if this browsing context is inactive and is able to be suspended. + bool InactiveForSuspend() const; + + const nsString& Name() const { return GetName(); } + void GetName(nsAString& aName) { aName = GetName(); } + bool NameEquals(const nsAString& aName) { return GetName().Equals(aName); } + + Type GetType() const { return mType; } + bool IsContent() const { return mType == Type::Content; } + bool IsChrome() const { return !IsContent(); } + + bool IsTop() const { return !GetParent(); } + bool IsFrame() const { return !IsTop(); } + + bool IsTopContent() const { return IsContent() && IsTop(); } + + bool IsInSubtreeOf(BrowsingContext* aContext); + + bool IsContentSubframe() const { return IsContent() && IsFrame(); } + // non-zero + uint64_t Id() const { return mBrowsingContextId; } + + BrowsingContext* GetParent() const; + BrowsingContext* Top(); + int32_t IndexOf(BrowsingContext* aChild); + + // NOTE: Unlike `GetEmbedderWindowGlobal`, `GetParentWindowContext` does not + // cross toplevel content browser boundaries. + WindowContext* GetParentWindowContext() const { return mParentWindow; } + WindowContext* GetTopWindowContext(); + + already_AddRefed GetOpener() const { + RefPtr opener(Get(GetOpenerId())); + if (!mIsDiscarded && opener && !opener->mIsDiscarded) { + MOZ_DIAGNOSTIC_ASSERT(opener->mType == mType); + return opener.forget(); + } + return nullptr; + } + void SetOpener(BrowsingContext* aOpener) { + MOZ_DIAGNOSTIC_ASSERT(!aOpener || aOpener->Group() == Group()); + MOZ_DIAGNOSTIC_ASSERT(!aOpener || aOpener->mType == mType); + + MOZ_ALWAYS_SUCCEEDS(SetOpenerId(aOpener ? aOpener->Id() : 0)); + } + + bool HasOpener() const; + + bool HadOriginalOpener() const { return GetHadOriginalOpener(); } + + // Returns true if the browsing context and top context are same origin + bool SameOriginWithTop(); + + /** + * When a new browsing context is opened by a sandboxed document, it needs to + * keep track of the browsing context that opened it, so that it can be + * navigated by it. This is the "one permitted sandboxed navigator". + */ + already_AddRefed GetOnePermittedSandboxedNavigator() const { + return Get(GetOnePermittedSandboxedNavigatorId()); + } + [[nodiscard]] nsresult SetOnePermittedSandboxedNavigator( + BrowsingContext* aNavigator) { + if (GetOnePermittedSandboxedNavigatorId()) { + MOZ_ASSERT(false, + "One Permitted Sandboxed Navigator should only be set once."); + return NS_ERROR_FAILURE; + } else { + return SetOnePermittedSandboxedNavigatorId(aNavigator ? aNavigator->Id() + : 0); + } + } + + uint32_t SandboxFlags() { return GetSandboxFlags(); } + + Span> Children() const; + void GetChildren(nsTArray>& aChildren); + + const nsTArray>& GetWindowContexts() { + return mWindowContexts; + } + void GetWindowContexts(nsTArray>& aWindows); + + void RegisterWindowContext(WindowContext* aWindow); + void UnregisterWindowContext(WindowContext* aWindow); + WindowContext* GetCurrentWindowContext() const { + return mCurrentWindowContext; + } + + // Helpers to traverse this BrowsingContext subtree. Note that these will only + // traverse active contexts, and will ignore ones in the BFCache. + void PreOrderWalk(const std::function& aCallback); + void PostOrderWalk(const std::function& aCallback); + void GetAllBrowsingContextsInSubtree( + nsTArray>& aBrowsingContexts); + + BrowsingContextGroup* Group() { return mGroup; } + + // WebIDL bindings for nsILoadContext + Nullable GetAssociatedWindow(); + Nullable GetTopWindow(); + Element* GetTopFrameElement(); + bool GetIsContent() { return IsContent(); } + void SetUsePrivateBrowsing(bool aUsePrivateBrowsing, ErrorResult& aError); + // Needs a different name to disambiguate from the xpidl method with + // the same signature but different return value. + void SetUseTrackingProtectionWebIDL(bool aUseTrackingProtection, + ErrorResult& aRv); + bool UseTrackingProtectionWebIDL() { return UseTrackingProtection(); } + void GetOriginAttributes(JSContext* aCx, JS::MutableHandle aVal, + ErrorResult& aError); + + bool InRDMPane() const { return GetInRDMPane(); } + + bool WatchedByDevTools(); + void SetWatchedByDevTools(bool aWatchedByDevTools, ErrorResult& aRv); + + mozilla::dom::TouchEventsOverride TouchEventsOverride() const; + void SetTouchEventsOverride( + const enum TouchEventsOverride aTouchEventsOverride, ErrorResult& aRv); + MOZ_MUST_USE nsresult + SetTouchEventsOverride(const enum TouchEventsOverride aTouchEventsOverride); + + bool FullscreenAllowed() const; + + float FullZoom() const { return GetFullZoom(); } + float TextZoom() const { return GetTextZoom(); } + + bool SuspendMediaWhenInactive() const { + return GetSuspendMediaWhenInactive(); + } + + bool IsActive() const; + void SetIsActive(bool aIsActive, mozilla::ErrorResult& aRv) { + SetExplicitActive(aIsActive ? ExplicitActiveStatus::Active + : ExplicitActiveStatus::Inactive, + aRv); + } + + bool AuthorStyleDisabledDefault() const { + return GetAuthorStyleDisabledDefault(); + } + + bool UseGlobalHistory() const { return GetUseGlobalHistory(); } + + bool GetIsActiveBrowserWindow(); + + void SetIsActiveBrowserWindow(bool aActive); + + uint64_t BrowserId() const { return GetBrowserId(); } + + bool IsLoading(); + + void GetEmbedderElementType(nsString& aElementType) { + if (GetEmbedderElementType().isSome()) { + aElementType = GetEmbedderElementType().value(); + } + } + + bool IsLoadingIdentifier(uint64_t aLoadIdentifer) { + if (GetCurrentLoadIdentifier() && + *GetCurrentLoadIdentifier() == aLoadIdentifer) { + return true; + } + return false; + } + + // ScreenOrientation related APIs + [[nodiscard]] nsresult SetCurrentOrientation(OrientationType aType, + float aAngle) { + Transaction txn; + txn.SetCurrentOrientationType(aType); + txn.SetCurrentOrientationAngle(aAngle); + return txn.Commit(this); + } + + void SetRDMPaneOrientation(OrientationType aType, float aAngle, + ErrorResult& aRv) { + if (InRDMPane()) { + if (NS_FAILED(SetCurrentOrientation(aType, aAngle))) { + aRv.ThrowInvalidStateError("Browsing context is discarded"); + } + } + } + + void SetRDMPaneMaxTouchPoints(uint8_t aMaxTouchPoints, ErrorResult& aRv) { + if (InRDMPane()) { + SetMaxTouchPointsOverride(aMaxTouchPoints, aRv); + } + } + + // Using the rules for choosing a browsing context we try to find + // the browsing context with the given name in the set of + // transitively reachable browsing contexts. Performs access control + // checks with regard to this. + // See + // https://html.spec.whatwg.org/multipage/browsers.html#the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name. + // + // BrowsingContext::FindWithName(const nsAString&) is equivalent to + // calling nsIDocShellTreeItem::FindItemWithName(aName, nullptr, + // nullptr, false, ). + BrowsingContext* FindWithName(const nsAString& aName, + bool aUseEntryGlobalForAccessCheck = true); + + // Find a browsing context in this context's list of + // children. Doesn't consider the special names, '_self', '_parent', + // '_top', or '_blank'. Performs access control checks with regard to + // 'this'. + BrowsingContext* FindChildWithName(const nsAString& aName, + BrowsingContext& aRequestingContext); + + // Find a browsing context in the subtree rooted at 'this' Doesn't + // consider the special names, '_self', '_parent', '_top', or + // '_blank'. Performs access control checks with regard to + // 'aRequestingContext'. + BrowsingContext* FindWithNameInSubtree(const nsAString& aName, + BrowsingContext& aRequestingContext); + + nsISupports* GetParentObject() const; + JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + + // Return the window proxy object that corresponds to this browsing context. + inline JSObject* GetWindowProxy() const { return mWindowProxy; } + inline JSObject* GetUnbarrieredWindowProxy() const { + return mWindowProxy.unbarrieredGet(); + } + + // Set the window proxy object that corresponds to this browsing context. + void SetWindowProxy(JS::Handle aWindowProxy) { + mWindowProxy = aWindowProxy; + } + + Nullable GetWindow(); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(BrowsingContext) + NS_DECL_NSILOADCONTEXT + + // Window APIs that are cross-origin-accessible (from the HTML spec). + WindowProxyHolder Window(); + BrowsingContext* GetBrowsingContext() { return this; }; + BrowsingContext* Self() { return this; } + void Location(JSContext* aCx, JS::MutableHandle aLocation, + ErrorResult& aError); + void Close(CallerType aCallerType, ErrorResult& aError); + bool GetClosed(ErrorResult&) { return GetClosed(); } + void Focus(CallerType aCallerType, ErrorResult& aError); + void Blur(ErrorResult& aError); + WindowProxyHolder GetFrames(ErrorResult& aError); + int32_t Length() const { return Children().Length(); } + Nullable GetTop(ErrorResult& aError); + void GetOpener(JSContext* aCx, JS::MutableHandle aOpener, + ErrorResult& aError) const; + Nullable GetParent(ErrorResult& aError); + void PostMessageMoz(JSContext* aCx, JS::Handle aMessage, + const nsAString& aTargetOrigin, + const Sequence& aTransfer, + nsIPrincipal& aSubjectPrincipal, ErrorResult& aError); + void PostMessageMoz(JSContext* aCx, JS::Handle aMessage, + const WindowPostMessageOptions& aOptions, + nsIPrincipal& aSubjectPrincipal, ErrorResult& aError); + + void GetCustomUserAgent(nsAString& aUserAgent) { + aUserAgent = Top()->GetUserAgentOverride(); + } + nsresult SetCustomUserAgent(const nsAString& aUserAgent); + void SetCustomUserAgent(const nsAString& aUserAgent, ErrorResult& aRv); + + void GetCustomPlatform(nsAString& aPlatform) { + aPlatform = Top()->GetPlatformOverride(); + } + void SetCustomPlatform(const nsAString& aPlatform, ErrorResult& aRv); + + JSObject* WrapObject(JSContext* aCx); + + static JSObject* ReadStructuredClone(JSContext* aCx, + JSStructuredCloneReader* aReader, + StructuredCloneHolder* aHolder); + bool WriteStructuredClone(JSContext* aCx, JSStructuredCloneWriter* aWriter, + StructuredCloneHolder* aHolder); + + void StartDelayedAutoplayMediaComponents(); + + [[nodiscard]] nsresult ResetGVAutoplayRequestStatus(); + + /** + * Information required to initialize a BrowsingContext in another process. + * This object may be serialized over IPC. + */ + struct IPCInitializer { + uint64_t mId = 0; + + // IDs are used for Parent and Opener to allow for this object to be + // deserialized before other BrowsingContext in the BrowsingContextGroup + // have been initialized. + uint64_t mParentId = 0; + already_AddRefed GetParent(); + already_AddRefed GetOpener(); + + uint64_t GetOpenerId() const { return mFields.mOpenerId; } + + bool mWindowless = false; + bool mUseRemoteTabs = false; + bool mUseRemoteSubframes = false; + bool mCreatedDynamically = false; + int32_t mSessionHistoryIndex = -1; + int32_t mSessionHistoryCount = 0; + OriginAttributes mOriginAttributes; + uint64_t mRequestContextId = 0; + + FieldValues mFields; + }; + + // Create an IPCInitializer object for this BrowsingContext. + IPCInitializer GetIPCInitializer(); + + // Create a BrowsingContext object from over IPC. + static void CreateFromIPC(IPCInitializer&& aInitializer, + BrowsingContextGroup* aGroup, + ContentParent* aOriginProcess); + + // Performs access control to check that 'this' can access 'aTarget'. + bool CanAccess(BrowsingContext* aTarget, bool aConsiderOpener = true); + + bool IsSandboxedFrom(BrowsingContext* aTarget); + + // The runnable will be called once there is idle time, or the top level + // page has been loaded or if a timeout has fired. + // Must be called only on the top level BrowsingContext. + void AddDeprioritizedLoadRunner(nsIRunnable* aRunner); + + RefPtr GetSessionStorageManager(); + + // Set PendingInitialization on this BrowsingContext before the context has + // been attached. + void InitPendingInitialization(bool aPendingInitialization) { + MOZ_ASSERT(!EverAttached()); + mFields.SetWithoutSyncing( + aPendingInitialization); + } + + bool CreatedDynamically() const { return mCreatedDynamically; } + + const OriginAttributes& OriginAttributesRef() { return mOriginAttributes; } + nsresult SetOriginAttributes(const OriginAttributes& aAttrs); + + void GetHistoryID(JSContext* aCx, JS::MutableHandle aVal, + ErrorResult& aError); + + // This should only be called on the top browsing context. + void InitSessionHistory(); + + // This will only ever return a non-null value if called on the top browsing + // context. + ChildSHistory* GetChildSessionHistory(); + + bool CrossOriginIsolated(); + + void SessionHistoryChanged(int32_t aIndexDelta, int32_t aLengthDelta); + + // Check if it is allowed to open a popup from the current browsing + // context or any of its ancestors. + bool IsPopupAllowed(); + + void SessionHistoryCommit(const LoadingSessionHistoryInfo& aInfo, + uint32_t aLoadType, bool aHadActiveEntry, + bool aPersist, bool aCloneEntryChildren); + + // Set a new active entry on this browsing context. This is used for + // implementing history.pushState/replaceState and same document navigations. + // The new active entry will be linked to the current active entry through + // its shared state. + // aPreviousScrollPos is the scroll position that needs to be saved on the + // previous active entry. + // aUpdatedCacheKey is the cache key to set on the new active entry. If + // aUpdatedCacheKey is 0 then it will be ignored. + void SetActiveSessionHistoryEntry(const Maybe& aPreviousScrollPos, + SessionHistoryInfo* aInfo, + uint32_t aLoadType, + uint32_t aUpdatedCacheKey); + + // Replace the active entry for this browsing context. This is used for + // implementing history.replaceState and same document navigations. + void ReplaceActiveSessionHistoryEntry(SessionHistoryInfo* aInfo); + + // Removes dynamic child entries of the active entry. + void RemoveDynEntriesFromActiveSessionHistoryEntry(); + + // Removes entries corresponding to this BrowsingContext from session history. + void RemoveFromSessionHistory(); + + void SetTriggeringAndInheritPrincipals(nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aPrincipalToInherit, + uint64_t aLoadIdentifier); + + // Return mTriggeringPrincipal and mPrincipalToInherit if the load id + // saved with the principal matches the current load identifier of this BC. + Tuple, nsCOMPtr> + GetTriggeringAndInheritPrincipalsForCurrentLoad(); + + void HistoryGo(int32_t aOffset, uint64_t aHistoryEpoch, + bool aRequireUserInteraction, + std::function&& aResolver); + + bool ShouldUpdateSessionHistory(uint32_t aLoadType); + + // Checks if we reached the rate limit for calls to Location and History API. + // The rate limit is controlled by the + // "dom.navigation.locationChangeRateLimit" prefs. + // Rate limit applies per BrowsingContext. + // Returns NS_OK if we are below the rate limit and increments the counter. + // Returns NS_ERROR_DOM_SECURITY_ERR if limit is reached. + nsresult CheckLocationChangeRateLimit(CallerType aCallerType); + + void ResetLocationChangeRateLimit(); + + mozilla::dom::DisplayMode DisplayMode() { return Top()->GetDisplayMode(); } + + // Returns canFocus, isActive + std::tuple CanFocusCheck(CallerType aCallerType); + + PopupBlocker::PopupControlState RevisePopupAbuseLevel( + PopupBlocker::PopupControlState aControl); + + protected: + virtual ~BrowsingContext(); + BrowsingContext(WindowContext* aParentWindow, BrowsingContextGroup* aGroup, + uint64_t aBrowsingContextId, Type aType, FieldValues&& aInit); + + void SetChildSHistory(ChildSHistory* aChildSHistory); + already_AddRefed ForgetChildSHistory() { + // FIXME Do we need to unset mHasSessionHistory? + return mChildSessionHistory.forget(); + } + + private: + void Attach(bool aFromIPC, ContentParent* aOriginProcess); + + // Find the special browsing context if aName is '_self', '_parent', + // '_top', but not '_blank'. The latter is handled in FindWithName + BrowsingContext* FindWithSpecialName(const nsAString& aName, + BrowsingContext& aRequestingContext); + + // Is it early enough in the BrowsingContext's lifecycle that it is still + // OK to set OriginAttributes? + bool CanSetOriginAttributes(); + + void AssertOriginAttributesMatchPrivateBrowsing(); + + // Assert that the BrowsingContext's LoadContext flags appear coherent + // relative to related BrowsingContexts. + void AssertCoherentLoadContext(); + + friend class ::nsOuterWindowProxy; + friend class ::nsGlobalWindowOuter; + friend class WindowContext; + + // Update the window proxy object that corresponds to this browsing context. + // This should be called from the window proxy object's objectMoved hook, if + // the object mWindowProxy points to was moved by the JS GC. + void UpdateWindowProxy(JSObject* obj, JSObject* old) { + if (mWindowProxy) { + MOZ_ASSERT(mWindowProxy == old); + mWindowProxy = obj; + } + } + // Clear the window proxy object that corresponds to this browsing context. + // This should be called if the window proxy object is finalized, or it can't + // reach its browsing context anymore. + void ClearWindowProxy() { mWindowProxy = nullptr; } + + friend class Location; + friend class RemoteLocationProxy; + /** + * LocationProxy is the class for the native object stored as a private in a + * RemoteLocationProxy proxy representing a Location object in a different + * process. It forwards all operations to its BrowsingContext and aggregates + * its refcount to that BrowsingContext. + */ + class LocationProxy final : public LocationBase { + public: + MozExternalRefCountType AddRef() { return GetBrowsingContext()->AddRef(); } + MozExternalRefCountType Release() { + return GetBrowsingContext()->Release(); + } + + protected: + friend class RemoteLocationProxy; + BrowsingContext* GetBrowsingContext() override { + return reinterpret_cast( + uintptr_t(this) - offsetof(BrowsingContext, mLocation)); + } + + already_AddRefed GetDocShell() override { return nullptr; } + }; + + // Send a given `BaseTransaction` object to the correct remote. + void SendCommitTransaction(ContentParent* aParent, + const BaseTransaction& aTxn, uint64_t aEpoch); + void SendCommitTransaction(ContentChild* aChild, const BaseTransaction& aTxn, + uint64_t aEpoch); + + // Ensure that opener is in the same BrowsingContextGroup. + bool CanSet(FieldIndex, const uint64_t& aValue, + ContentParent* aSource) { + if (aValue != 0) { + RefPtr opener = Get(aValue); + return opener && opener->Group() == Group(); + } + return true; + } + + bool CanSet(FieldIndex, bool, ContentParent*) { + return IsTop(); + } + + bool CanSet(FieldIndex, + const enum TouchEventsOverride& aTouchEventsOverride, + ContentParent* aSource); + + bool CanSet(FieldIndex, const enum DisplayMode& aDisplayMode, + ContentParent* aSource); + void DidSet(FieldIndex, enum DisplayMode aOldValue); + + void DidSet(FieldIndex, ExplicitActiveStatus aOldValue); + + bool CanSet(FieldIndex, const bool& aValue, + ContentParent* aSource); + void DidSet(FieldIndex, bool aOldValue); + + // Ensure that we only set the flag on the top level browsingContext. + // And then, we do a pre-order walk in the tree to refresh the + // volume of all media elements. + void DidSet(FieldIndex); + + bool CanSet(FieldIndex, const uint64_t& aValue, + ContentParent* aSource); + + bool CanSet(FieldIndex, const uint64_t& aValue, + ContentParent* aSource); + + void DidSet(FieldIndex); + + bool CanSet(FieldIndex, const bool& aValue, + ContentParent* aSource); + + void DidSet(FieldIndex); + + void DidSet(FieldIndex); + void DidSet(FieldIndex); + + void DidSet(FieldIndex); + + void DidSet(FieldIndex); + + void DidSet(FieldIndex); + bool CanSet(FieldIndex, + const nsString& aPlatformOverride, ContentParent* aSource); + + void DidSet(FieldIndex); + bool CanSet(FieldIndex, const nsString& aUserAgent, + ContentParent* aSource); + bool CanSet(FieldIndex, + const mozilla::hal::ScreenOrientation& aOrientationLock, + ContentParent* aSource); + + bool CanSet(FieldIndex, + const Maybe& aInitiatorType, ContentParent* aSource); + + bool CanSet(FieldIndex, + const nsString& aMessageManagerGroup, ContentParent* aSource); + + bool CanSet(FieldIndex, + const bool& aAllowContentRetargeting, ContentParent* aSource); + bool CanSet(FieldIndex, + const bool& aAllowContentRetargetingOnChildren, + ContentParent* aSource); + bool CanSet(FieldIndex, const bool& aAllowPlugins, + ContentParent* aSource); + bool CanSet(FieldIndex, const bool&, + ContentParent*); + bool CanSet(FieldIndex, + const bool& aWatchedByDevToolsInternal, ContentParent* aSource); + + bool CanSet(FieldIndex, + const uint32_t& aDefaultLoadFlags, ContentParent* aSource); + void DidSet(FieldIndex); + + bool CanSet(FieldIndex, const bool& aUseGlobalHistory, + ContentParent* aSource); + + void DidSet(FieldIndex, bool aOldValue); + + bool CanSet(FieldIndex, const uint32_t& aValue, + ContentParent* aSource); + + bool CanSet(FieldIndex, const bool& aUseErrorPages, + ContentParent* aSource); + + bool CanSet(FieldIndex, bool aNewValue, + ContentParent* aSource); + + bool CanSet(FieldIndex, bool aNewValue, + ContentParent* aSource); + void DidSet(FieldIndex, bool aOldValue); + + template + bool CanSet(FieldIndex, const T&, ContentParent*) { + return true; + } + + // Overload `DidSet` to get notifications for a particular field being set. + // + // You can also overload the variant that gets the old value if you need it. + template + void DidSet(FieldIndex) {} + template + void DidSet(FieldIndex, T&& aOldValue) {} + + void DidSet(FieldIndex, float aOldValue); + void DidSet(FieldIndex, float aOldValue); + void DidSet(FieldIndex); + + // True if the process attemping to set field is the same as the owning + // process. + bool CheckOnlyOwningProcessCanSet(ContentParent* aSource); + + // True if the process attempting to set field is the same as the embedder's + // process. + bool CheckOnlyEmbedderCanSet(ContentParent* aSource); + + void CreateChildSHistory(); + + using PrincipalWithLoadIdentifierTuple = + Tuple, uint64_t>; + + nsIPrincipal* GetSavedPrincipal( + Maybe aPrincipalTuple); + + // Type of BrowsingContent + const Type mType; + + // Unique id identifying BrowsingContext + const uint64_t mBrowsingContextId; + + RefPtr mGroup; + RefPtr mParentWindow; + nsCOMPtr mDocShell; + + RefPtr mEmbedderElement; + + nsTArray> mWindowContexts; + RefPtr mCurrentWindowContext; + + // This is not a strong reference, but using a JS::Heap for that should be + // fine. The JSObject stored in here should be a proxy with a + // nsOuterWindowProxy handler, which will update the pointer from its + // objectMoved hook and clear it from its finalize hook. + JS::Heap mWindowProxy; + LocationProxy mLocation; + + // OriginAttributes for this BrowsingContext. May not be changed after this + // BrowsingContext is attached. + OriginAttributes mOriginAttributes; + + // The network request context id, representing the nsIRequestContext + // associated with this BrowsingContext, and LoadGroups created for it. + uint64_t mRequestContextId = 0; + + // Determines if private browsing should be used. May not be changed after + // this BrowsingContext is attached. This field matches mOriginAttributes in + // content Browsing Contexts. Currently treated as a binary value: 1 - in + // private mode, 0 - not private mode. + uint32_t mPrivateBrowsingId; + + // True if Attach() has been called on this BrowsingContext already. + bool mEverAttached : 1; + + // Is the most recent Document in this BrowsingContext loaded within this + // process? This may be true with a null mDocShell after the Window has been + // closed. + bool mIsInProcess : 1; + + // Has this browsing context been discarded? BrowsingContexts should + // only be discarded once. + bool mIsDiscarded : 1; + + // True if this BrowsingContext has no associated visible window, and is owned + // by whichever process created it, even if top-level. + bool mWindowless : 1; + + // This is true if the BrowsingContext was out of process, but is now in + // process, and might have remote window proxies that need to be cleaned up. + bool mDanglingRemoteOuterProxies : 1; + + // True if this BrowsingContext has been embedded in a element in this + // process. + bool mEmbeddedByThisProcess : 1; + + // Determines if remote (out-of-process) tabs should be used. May not be + // changed after this BrowsingContext is attached. + bool mUseRemoteTabs : 1; + + // Determines if out-of-process iframes should be used. May not be changed + // after this BrowsingContext is attached. + bool mUseRemoteSubframes : 1; + + // True if this BrowsingContext is for a frame that was added dynamically. + bool mCreatedDynamically : 1; + + // The start time of user gesture, this is only available if the browsing + // context is in process. + TimeStamp mUserGestureStart; + + // Triggering principal and principal to inherit need to point to original + // principal instances if the document is loaded in the same process as the + // process that initiated the load. When the load starts we save the + // principals along with the current load id. + // These principals correspond to the most recent load that took place within + // the process of this browsing context. + Maybe mTriggeringPrincipal; + Maybe mPrincipalToInherit; + + class DeprioritizedLoadRunner + : public mozilla::Runnable, + public mozilla::LinkedListElement { + public: + explicit DeprioritizedLoadRunner(nsIRunnable* aInner) + : Runnable("DeprioritizedLoadRunner"), mInner(aInner) {} + + NS_IMETHOD Run() override { + if (mInner) { + RefPtr inner = std::move(mInner); + inner->Run(); + } + + return NS_OK; + } + + private: + RefPtr mInner; + }; + + mozilla::LinkedList mDeprioritizedLoadRunner; + + RefPtr mSessionStorageManager; + RefPtr mChildSessionHistory; + + // Counter and time span for rate limiting Location and History API calls. + // Used by CheckLocationChangeRateLimit. Do not apply cross-process. + uint32_t mLocationChangeRateLimitCount; + mozilla::TimeStamp mLocationChangeRateLimitSpanStart; +}; + +/** + * Gets a WindowProxy object for a BrowsingContext that lives in a different + * process (creating the object if it doesn't already exist). The WindowProxy + * object will be in the compartment that aCx is currently in. This should only + * be called if aContext doesn't hold a docshell, otherwise the BrowsingContext + * lives in this process, and a same-process WindowProxy should be used (see + * nsGlobalWindowOuter). This should only be called by bindings code, ToJSValue + * is the right API to get a WindowProxy for a BrowsingContext. + * + * If aTransplantTo is non-null, then the WindowProxy object will eventually be + * transplanted onto it. Therefore it should be used as the value in the remote + * proxy map. + */ +extern bool GetRemoteOuterWindowProxy(JSContext* aCx, BrowsingContext* aContext, + JS::Handle aTransplantTo, + JS::MutableHandle aRetVal); + +using BrowsingContextTransaction = BrowsingContext::BaseTransaction; +using BrowsingContextInitializer = BrowsingContext::IPCInitializer; +using MaybeDiscardedBrowsingContext = MaybeDiscarded; + +// Specialize the transaction object for every translation unit it's used in. +extern template class syncedcontext::Transaction; + +} // namespace dom + +// Allow sending BrowsingContext objects over IPC. +namespace ipc { +template <> +struct IPDLParamTraits> { + static void Write(IPC::Message* aMsg, IProtocol* aActor, + const dom::MaybeDiscarded& aParam); + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, + dom::MaybeDiscarded* aResult); +}; + +template <> +struct IPDLParamTraits { + static void Write(IPC::Message* aMessage, IProtocol* aActor, + const dom::BrowsingContext::IPCInitializer& aInitializer); + + static bool Read(const IPC::Message* aMessage, PickleIterator* aIterator, + IProtocol* aActor, + dom::BrowsingContext::IPCInitializer* aInitializer); +}; +} // namespace ipc +} // namespace mozilla + +#endif // !defined(mozilla_dom_BrowsingContext_h) diff --git a/docshell/base/BrowsingContextGroup.cpp b/docshell/base/BrowsingContextGroup.cpp new file mode 100644 index 0000000000..cf3bd9504d --- /dev/null +++ b/docshell/base/BrowsingContextGroup.cpp @@ -0,0 +1,460 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/BrowsingContextGroup.h" + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/InputTaskManager.h" +#include "mozilla/dom/BrowsingContextBinding.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/DocGroup.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/ThrottledEventQueue.h" +#include "nsFocusManager.h" + +namespace mozilla { +namespace dom { + +static StaticRefPtr sChromeGroup; + +static StaticAutoPtr< + nsDataHashtable>> + sBrowsingContextGroups; + +already_AddRefed BrowsingContextGroup::GetOrCreate( + uint64_t aId) { + if (!sBrowsingContextGroups) { + sBrowsingContextGroups = + new nsDataHashtable>(); + ClearOnShutdown(&sBrowsingContextGroups); + } + + auto entry = sBrowsingContextGroups->LookupForAdd(aId); + RefPtr group = + entry.OrInsert([&] { return do_AddRef(new BrowsingContextGroup(aId)); }); + return group.forget(); +} + +already_AddRefed BrowsingContextGroup::Create() { + return GetOrCreate(nsContentUtils::GenerateBrowsingContextId()); +} + +BrowsingContextGroup::BrowsingContextGroup(uint64_t aId) : mId(aId) { + mTimerEventQueue = ThrottledEventQueue::Create( + GetMainThreadSerialEventTarget(), "BrowsingContextGroup timer queue"); + + mWorkerEventQueue = ThrottledEventQueue::Create( + GetMainThreadSerialEventTarget(), "BrowsingContextGroup worker queue"); +} + +void BrowsingContextGroup::Register(nsISupports* aContext) { + MOZ_ASSERT(!mDestroyed); + MOZ_DIAGNOSTIC_ASSERT(aContext); + mContexts.PutEntry(aContext); +} + +void BrowsingContextGroup::Unregister(nsISupports* aContext) { + MOZ_ASSERT(!mDestroyed); + MOZ_DIAGNOSTIC_ASSERT(aContext); + mContexts.RemoveEntry(aContext); + + MaybeDestroy(); +} + +void BrowsingContextGroup::EnsureHostProcess(ContentParent* aProcess) { + MOZ_ASSERT(!mDestroyed); + MOZ_DIAGNOSTIC_ASSERT(this != sChromeGroup, + "cannot have content host for chrome group"); + MOZ_DIAGNOSTIC_ASSERT(aProcess->GetRemoteType() != PREALLOC_REMOTE_TYPE, + "cannot use preallocated process as host"); + MOZ_DIAGNOSTIC_ASSERT(!aProcess->GetRemoteType().IsEmpty(), + "host process must have remote type"); + + if (!aProcess->IsDead()) { + auto entry = mHosts.LookupForAdd(aProcess->GetRemoteType()); + if (entry) { + MOZ_DIAGNOSTIC_ASSERT( + entry.Data() == aProcess, + "There's already another host process for this remote type"); + return; + } + + // This process wasn't already marked as our host, so insert it, and begin + // subscribing, unless the process is still launching. + entry.OrInsert([&] { return do_AddRef(aProcess); }); + } + + aProcess->AddBrowsingContextGroup(this); +} + +void BrowsingContextGroup::RemoveHostProcess(ContentParent* aProcess) { + MOZ_DIAGNOSTIC_ASSERT(aProcess); + MOZ_DIAGNOSTIC_ASSERT(aProcess->GetRemoteType() != PREALLOC_REMOTE_TYPE); + auto entry = mHosts.Lookup(aProcess->GetRemoteType()); + if (entry && entry.Data() == aProcess) { + entry.Remove(); + } +} + +static void CollectContextInitializers( + Span> aContexts, + nsTArray& aInits) { + // The order that we record these initializers is important, as it will keep + // the order that children are attached to their parent in the newly connected + // content process consistent. + for (auto& context : aContexts) { + aInits.AppendElement(context->GetIPCInitializer()); + for (auto& window : context->GetWindowContexts()) { + aInits.AppendElement(window->GetIPCInitializer()); + CollectContextInitializers(window->Children(), aInits); + } + } +} + +void BrowsingContextGroup::Subscribe(ContentParent* aProcess) { + MOZ_ASSERT(!mDestroyed); + MOZ_DIAGNOSTIC_ASSERT(aProcess && !aProcess->IsLaunching()); + MOZ_DIAGNOSTIC_ASSERT(aProcess->GetRemoteType() != PREALLOC_REMOTE_TYPE); + + // Check if we're already subscribed to this process. + if (!mSubscribers.EnsureInserted(aProcess)) { + return; + } + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + // If the process is already marked as dead, we won't be the host, but may + // still need to subscribe to the process due to creating a popup while + // shutting down. + if (!aProcess->IsDead()) { + auto hostEntry = mHosts.Lookup(aProcess->GetRemoteType()); + MOZ_DIAGNOSTIC_ASSERT(hostEntry && hostEntry.Data() == aProcess, + "Cannot subscribe a non-host process"); + } +#endif + + // FIXME: This won't send non-discarded children of discarded BCs, but those + // BCs will be in the process of being destroyed anyway. + // FIXME: Prevent that situation from occuring. + nsTArray inits(mContexts.Count()); + CollectContextInitializers(mToplevels, inits); + + // Send all of our contexts to the target content process. + Unused << aProcess->SendRegisterBrowsingContextGroup(Id(), inits); + + // If the focused or active BrowsingContexts belong in this group, tell the + // newly subscribed process. + if (nsFocusManager* fm = nsFocusManager::GetFocusManager()) { + BrowsingContext* focused = fm->GetFocusedBrowsingContextInChrome(); + if (focused && focused->Group() != this) { + focused = nullptr; + } + BrowsingContext* active = fm->GetActiveBrowsingContextInChrome(); + if (active && active->Group() != this) { + active = nullptr; + } + + if (focused || active) { + Unused << aProcess->SendSetupFocusedAndActive(focused, active); + } + } +} + +void BrowsingContextGroup::Unsubscribe(ContentParent* aProcess) { + MOZ_DIAGNOSTIC_ASSERT(aProcess); + MOZ_DIAGNOSTIC_ASSERT(aProcess->GetRemoteType() != PREALLOC_REMOTE_TYPE); + mSubscribers.RemoveEntry(aProcess); + aProcess->RemoveBrowsingContextGroup(this); + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + auto hostEntry = mHosts.Lookup(aProcess->GetRemoteType()); + MOZ_DIAGNOSTIC_ASSERT(!hostEntry || hostEntry.Data() != aProcess, + "Unsubscribing existing host entry"); +#endif +} + +ContentParent* BrowsingContextGroup::GetHostProcess( + const nsACString& aRemoteType) { + return mHosts.GetWeak(aRemoteType); +} + +void BrowsingContextGroup::UpdateToplevelsSuspendedIfNeeded() { + if (!StaticPrefs::dom_suspend_inactive_enabled()) { + return; + } + + mToplevelsSuspended = ShouldSuspendAllTopLevelContexts(); + for (const auto& context : mToplevels) { + nsPIDOMWindowOuter* outer = context->GetDOMWindow(); + if (!outer) { + continue; + } + nsCOMPtr inner = outer->GetCurrentInnerWindow(); + if (!inner) { + continue; + } + if (mToplevelsSuspended && !inner->GetWasSuspendedByGroup()) { + inner->Suspend(); + inner->SetWasSuspendedByGroup(true); + } else if (!mToplevelsSuspended && inner->GetWasSuspendedByGroup()) { + inner->Resume(); + inner->SetWasSuspendedByGroup(false); + } + } +} + +bool BrowsingContextGroup::ShouldSuspendAllTopLevelContexts() const { + for (const auto& context : mToplevels) { + if (!context->InactiveForSuspend()) { + return false; + } + } + return true; +} + +BrowsingContextGroup::~BrowsingContextGroup() { Destroy(); } + +void BrowsingContextGroup::Destroy() { +#ifdef DEBUG + if (mDestroyed) { + MOZ_ASSERT(mHosts.Count() == 0); + MOZ_ASSERT(mSubscribers.Count() == 0); + MOZ_ASSERT_IF(sBrowsingContextGroups, + sBrowsingContextGroups->Get(Id()) != this); + } + mDestroyed = true; +#endif + + // Make sure to call `RemoveBrowsingContextGroup` for every entry in both + // `mHosts` and `mSubscribers`. This will visit most entries twice, but + // `RemoveBrowsingContextGroup` is safe to call multiple times. + for (auto& entry : mHosts) { + entry.GetData()->RemoveBrowsingContextGroup(this); + } + for (auto& entry : mSubscribers) { + entry.GetKey()->RemoveBrowsingContextGroup(this); + } + mHosts.Clear(); + mSubscribers.Clear(); + + if (sBrowsingContextGroups) { + sBrowsingContextGroups->Remove(Id()); + } +} + +void BrowsingContextGroup::AddKeepAlive() { + MOZ_ASSERT(!mDestroyed); + mKeepAliveCount++; +} + +void BrowsingContextGroup::RemoveKeepAlive() { + MOZ_ASSERT(!mDestroyed); + MOZ_DIAGNOSTIC_ASSERT(mKeepAliveCount > 0); + mKeepAliveCount--; + + MaybeDestroy(); +} + +void BrowsingContextGroup::MaybeDestroy() { + if (mContexts.IsEmpty() && mKeepAliveCount == 0 && this != sChromeGroup) { + // There are no synced contexts still referencing this group. We can clear + // all subscribers, and destroy ourselves. + Destroy(); + + // We may have been deleted here as the ContentChild/Parent may + // have held the last references to `this`. + // Do not access any members at this point. + } +} + +nsISupports* BrowsingContextGroup::GetParentObject() const { + return xpc::NativeGlobal(xpc::PrivilegedJunkScope()); +} + +JSObject* BrowsingContextGroup::WrapObject(JSContext* aCx, + JS::Handle aGivenProto) { + return BrowsingContextGroup_Binding::Wrap(aCx, this, aGivenProto); +} + +nsresult BrowsingContextGroup::QueuePostMessageEvent( + already_AddRefed&& aRunnable) { + if (StaticPrefs::dom_separate_event_queue_for_post_message_enabled()) { + if (!mPostMessageEventQueue) { + nsCOMPtr target = GetMainThreadSerialEventTarget(); + mPostMessageEventQueue = ThrottledEventQueue::Create( + target, "PostMessage Queue", + nsIRunnablePriority::PRIORITY_DEFERRED_TIMERS); + nsresult rv = mPostMessageEventQueue->SetIsPaused(false); + MOZ_ALWAYS_SUCCEEDS(rv); + } + + // Ensure the queue is enabled + if (mPostMessageEventQueue->IsPaused()) { + nsresult rv = mPostMessageEventQueue->SetIsPaused(false); + MOZ_ALWAYS_SUCCEEDS(rv); + } + + if (mPostMessageEventQueue) { + mPostMessageEventQueue->Dispatch(std::move(aRunnable), + NS_DISPATCH_NORMAL); + return NS_OK; + } + } + return NS_ERROR_FAILURE; +} + +void BrowsingContextGroup::FlushPostMessageEvents() { + if (StaticPrefs::dom_separate_event_queue_for_post_message_enabled()) { + if (mPostMessageEventQueue) { + nsresult rv = mPostMessageEventQueue->SetIsPaused(true); + MOZ_ALWAYS_SUCCEEDS(rv); + nsCOMPtr event; + while ((event = mPostMessageEventQueue->GetEvent())) { + NS_DispatchToMainThread(event.forget()); + } + } + } +} + +bool BrowsingContextGroup::HasActiveBC() { + for (auto& topLevelBC : Toplevels()) { + if (topLevelBC->IsActive()) { + return true; + } + } + return false; +} + +void BrowsingContextGroup::IncInputEventSuspensionLevel() { + MOZ_ASSERT(StaticPrefs::dom_input_events_canSuspendInBCG_enabled()); + if (!mHasIncreasedInputTaskManagerSuspensionLevel && HasActiveBC()) { + IncInputTaskManagerSuspensionLevel(); + } + ++mInputEventSuspensionLevel; +} + +void BrowsingContextGroup::DecInputEventSuspensionLevel() { + MOZ_ASSERT(StaticPrefs::dom_input_events_canSuspendInBCG_enabled()); + --mInputEventSuspensionLevel; + if (!mInputEventSuspensionLevel && + mHasIncreasedInputTaskManagerSuspensionLevel) { + DecInputTaskManagerSuspensionLevel(); + } +} + +void BrowsingContextGroup::DecInputTaskManagerSuspensionLevel() { + MOZ_ASSERT(StaticPrefs::dom_input_events_canSuspendInBCG_enabled()); + MOZ_ASSERT(mHasIncreasedInputTaskManagerSuspensionLevel); + + InputTaskManager::Get()->DecSuspensionLevel(); + mHasIncreasedInputTaskManagerSuspensionLevel = false; +} + +void BrowsingContextGroup::IncInputTaskManagerSuspensionLevel() { + MOZ_ASSERT(StaticPrefs::dom_input_events_canSuspendInBCG_enabled()); + MOZ_ASSERT(!mHasIncreasedInputTaskManagerSuspensionLevel); + MOZ_ASSERT(HasActiveBC()); + + InputTaskManager::Get()->IncSuspensionLevel(); + mHasIncreasedInputTaskManagerSuspensionLevel = true; +} + +void BrowsingContextGroup::UpdateInputTaskManagerIfNeeded(bool aIsActive) { + MOZ_ASSERT(StaticPrefs::dom_input_events_canSuspendInBCG_enabled()); + if (!aIsActive) { + if (mHasIncreasedInputTaskManagerSuspensionLevel) { + MOZ_ASSERT(mInputEventSuspensionLevel > 0); + if (!HasActiveBC()) { + DecInputTaskManagerSuspensionLevel(); + } + } + } else { + if (mInputEventSuspensionLevel && + !mHasIncreasedInputTaskManagerSuspensionLevel) { + IncInputTaskManagerSuspensionLevel(); + } + } +} + +/* static */ +BrowsingContextGroup* BrowsingContextGroup::GetChromeGroup() { + MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess()); + if (!sChromeGroup && XRE_IsParentProcess()) { + sChromeGroup = BrowsingContextGroup::Create(); + ClearOnShutdown(&sChromeGroup); + } + + return sChromeGroup; +} + +void BrowsingContextGroup::GetDocGroups(nsTArray& aDocGroups) { + MOZ_ASSERT(NS_IsMainThread()); + for (auto iter = mDocGroups.ConstIter(); !iter.Done(); iter.Next()) { + aDocGroups.AppendElement(iter.Data()); + } +} + +already_AddRefed BrowsingContextGroup::AddDocument( + const nsACString& aKey, Document* aDocument) { + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr& docGroup = mDocGroups.GetOrInsert(aKey); + if (!docGroup) { + docGroup = DocGroup::Create(this, aKey); + } + + docGroup->AddDocument(aDocument); + return do_AddRef(docGroup); +} + +void BrowsingContextGroup::RemoveDocument(const nsACString& aKey, + Document* aDocument) { + MOZ_ASSERT(NS_IsMainThread()); + RefPtr docGroup = aDocument->GetDocGroup(); + // Removing the last document in DocGroup might decrement the + // DocGroup BrowsingContextGroup's refcount to 0. + RefPtr kungFuDeathGrip(this); + docGroup->RemoveDocument(aDocument); + + if (docGroup->IsEmpty()) { + mDocGroups.Remove(aKey); + } +} + +already_AddRefed BrowsingContextGroup::Select( + WindowContext* aParent, BrowsingContext* aOpener) { + if (aParent) { + return do_AddRef(aParent->Group()); + } + if (aOpener) { + return do_AddRef(aOpener->Group()); + } + return Create(); +} + +void BrowsingContextGroup::GetAllGroups( + nsTArray>& aGroups) { + aGroups.Clear(); + if (!sBrowsingContextGroups) { + return; + } + + aGroups.SetCapacity(sBrowsingContextGroups->Count()); + for (auto& group : *sBrowsingContextGroups) { + aGroups.AppendElement(group.GetData()); + } +} + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(BrowsingContextGroup, mContexts, + mToplevels, mHosts, mSubscribers, + mTimerEventQueue, mWorkerEventQueue) + +NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(BrowsingContextGroup, AddRef) +NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(BrowsingContextGroup, Release) + +} // namespace dom +} // namespace mozilla diff --git a/docshell/base/BrowsingContextGroup.h b/docshell/base/BrowsingContextGroup.h new file mode 100644 index 0000000000..39fc6d24f8 --- /dev/null +++ b/docshell/base/BrowsingContextGroup.h @@ -0,0 +1,232 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_BrowsingContextGroup_h +#define mozilla_dom_BrowsingContextGroup_h + +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/FunctionRef.h" +#include "nsRefPtrHashtable.h" +#include "nsHashKeys.h" +#include "nsTArray.h" +#include "nsTHashtable.h" +#include "nsWrapperCache.h" +#include "nsXULAppAPI.h" + +namespace mozilla { +class ThrottledEventQueue; + +namespace dom { + +class BrowsingContext; +class WindowContext; +class ContentParent; +class DocGroup; + +// A BrowsingContextGroup represents the Unit of Related Browsing Contexts in +// the standard. +// +// A BrowsingContext may not hold references to other BrowsingContext objects +// which are not in the same BrowsingContextGroup. +class BrowsingContextGroup final : public nsWrapperCache { + public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(BrowsingContextGroup) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(BrowsingContextGroup) + + // Interact with the list of synced contexts. This controls the lifecycle of + // the BrowsingContextGroup and contexts loaded within them. + void Register(nsISupports* aContext); + void Unregister(nsISupports* aContext); + + // Control which processes will be used to host documents loaded in this + // BrowsingContextGroup. There should only ever be one host process per remote + // type. + // + // A new host process will be subscribed to the BrowsingContextGroup unless it + // is still launching, in which case it will subscribe itself when it is done + // launching. + void EnsureHostProcess(ContentParent* aProcess); + + // A removed host process will no longer be used to host documents loaded in + // this BrowsingContextGroup. + void RemoveHostProcess(ContentParent* aProcess); + + // Synchronize the current BrowsingContextGroup state down to the given + // content process, and continue updating it. + // + // You rarely need to call this directy, as it's automatically called by + // |EnsureHostProcess| as needed. + void Subscribe(ContentParent* aProcess); + + // Stop synchromizing the current BrowsingContextGroup state down to a given + // content process. The content process must no longer be a host process. + void Unsubscribe(ContentParent* aProcess); + + // Look up the process which should be used to host documents with this + // RemoteType. This will be a non-dead process associated with this + // BrowsingContextGroup, if possible. + ContentParent* GetHostProcess(const nsACString& aRemoteType); + + // When a BrowsingContext is being discarded, we may want to keep the + // corresponding BrowsingContextGroup alive until the other process + // acknowledges the BrowsingContext has been discarded. A `KeepAlive` will be + // added to the `BrowsingContextGroup`, delaying destruction. + void AddKeepAlive(); + void RemoveKeepAlive(); + + // Call when we want to check if we should suspend or resume all top level + // contexts. + void UpdateToplevelsSuspendedIfNeeded(); + + // Get a reference to the list of toplevel contexts in this + // BrowsingContextGroup. + nsTArray>& Toplevels() { return mToplevels; } + void GetToplevels(nsTArray>& aToplevels) { + aToplevels.AppendElements(mToplevels); + } + + uint64_t Id() { return mId; } + + nsISupports* GetParentObject() const; + JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + + // Get or create a BrowsingContextGroup with the given ID. + static already_AddRefed GetOrCreate(uint64_t aId); + static already_AddRefed Create(); + static already_AddRefed Select( + WindowContext* aParent, BrowsingContext* aOpener); + + // For each 'ContentParent', except for 'aExcludedParent', + // associated with this group call 'aCallback'. + template + void EachOtherParent(ContentParent* aExcludedParent, Func&& aCallback) { + MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess()); + for (auto iter = mSubscribers.Iter(); !iter.Done(); iter.Next()) { + if (iter.Get()->GetKey() != aExcludedParent) { + aCallback(iter.Get()->GetKey()); + } + } + } + + // For each 'ContentParent' associated with + // this group call 'aCallback'. + template + void EachParent(Func&& aCallback) { + MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess()); + for (auto iter = mSubscribers.Iter(); !iter.Done(); iter.Next()) { + aCallback(iter.Get()->GetKey()); + } + } + + nsresult QueuePostMessageEvent(already_AddRefed&& aRunnable); + + void FlushPostMessageEvents(); + + // Increase or decrease the suspension level in InputTaskManager + void UpdateInputTaskManagerIfNeeded(bool aIsActive); + + static BrowsingContextGroup* GetChromeGroup(); + + void GetDocGroups(nsTArray& aDocGroups); + + // Called by Document when a Document needs to be added to a DocGroup. + already_AddRefed AddDocument(const nsACString& aKey, + Document* aDocument); + + // Called by Document when a Document needs to be removed to a DocGroup. + void RemoveDocument(const nsACString& aKey, Document* aDocument); + + auto DocGroups() const { return mDocGroups.ConstIter(); } + + mozilla::ThrottledEventQueue* GetTimerEventQueue() const { + return mTimerEventQueue; + } + + mozilla::ThrottledEventQueue* GetWorkerEventQueue() const { + return mWorkerEventQueue; + } + + static void GetAllGroups(nsTArray>& aGroups); + + void IncInputEventSuspensionLevel(); + void DecInputEventSuspensionLevel(); + + private: + friend class CanonicalBrowsingContext; + + explicit BrowsingContextGroup(uint64_t aId); + ~BrowsingContextGroup(); + + void MaybeDestroy(); + void Destroy(); + + bool ShouldSuspendAllTopLevelContexts() const; + + bool HasActiveBC(); + void DecInputTaskManagerSuspensionLevel(); + void IncInputTaskManagerSuspensionLevel(); + + uint64_t mId; + + uint32_t mKeepAliveCount = 0; + +#ifdef DEBUG + bool mDestroyed = false; +#endif + + // A BrowsingContextGroup contains a series of {Browsing,Window}Context + // objects. They are addressed using a hashtable to avoid linear lookup when + // adding or removing elements from the set. + // + // FIXME: This list is only required over a counter to keep nested + // non-discarded contexts within discarded contexts alive. It should be + // removed in the future. + // FIXME: Consider introducing a better common base than `nsISupports`? + nsTHashtable> mContexts; + + // The set of toplevel browsing contexts in the current BrowsingContextGroup. + nsTArray> mToplevels; + + // Whether or not all toplevels in this group should be suspended + bool mToplevelsSuspended = false; + + // DocGroups are thread-safe, and not able to be cycle collected, + // but we still keep strong pointers. When all Documents are removed + // from DocGroup (by the BrowsingContextGroup), the DocGroup is + // removed from here. + nsRefPtrHashtable mDocGroups; + + // The content process which will host documents in this BrowsingContextGroup + // which need to be loaded with a given remote type. + // + // A non-launching host process must also be a subscriber, though a launching + // host process may not yet be subscribed, and a subscriber need not be a host + // process. + nsRefPtrHashtable mHosts; + + nsTHashtable> mSubscribers; + + // A queue to store postMessage events during page load, the queue will be + // flushed once the page is loaded + RefPtr mPostMessageEventQueue; + + RefPtr mTimerEventQueue; + RefPtr mWorkerEventQueue; + + // A counter to keep track of the input event suspension level of this BCG + // + // We use BrowsingContextGroup to emulate process isolation in Fission, so + // documents within the same the same BCG will behave like they share + // the same input task queue. + uint32_t mInputEventSuspensionLevel = 0; + // Whether this BCG has increased the suspension level in InputTaskManager + bool mHasIncreasedInputTaskManagerSuspensionLevel = false; +}; +} // namespace dom +} // namespace mozilla + +#endif // !defined(mozilla_dom_BrowsingContextGroup_h) diff --git a/docshell/base/BrowsingContextWebProgress.cpp b/docshell/base/BrowsingContextWebProgress.cpp new file mode 100644 index 0000000000..0dfe9d0eb8 --- /dev/null +++ b/docshell/base/BrowsingContextWebProgress.cpp @@ -0,0 +1,217 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "BrowsingContextWebProgress.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_ADDREF(BrowsingContextWebProgress) +NS_IMPL_RELEASE(BrowsingContextWebProgress) + +NS_INTERFACE_MAP_BEGIN(BrowsingContextWebProgress) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebProgress) + NS_INTERFACE_MAP_ENTRY(nsIWebProgress) + NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener) +NS_INTERFACE_MAP_END + +NS_IMETHODIMP BrowsingContextWebProgress::AddProgressListener( + nsIWebProgressListener* aListener, uint32_t aNotifyMask) { + nsWeakPtr listener = do_GetWeakReference(aListener); + if (!listener) { + return NS_ERROR_INVALID_ARG; + } + + if (mListenerInfoList.Contains(listener)) { + // The listener is already registered! + return NS_ERROR_FAILURE; + } + + mListenerInfoList.AppendElement(ListenerInfo(listener, aNotifyMask)); + return NS_OK; +} + +NS_IMETHODIMP BrowsingContextWebProgress::RemoveProgressListener( + nsIWebProgressListener* aListener) { + nsWeakPtr listener = do_GetWeakReference(aListener); + if (!listener) { + return NS_ERROR_INVALID_ARG; + } + + return mListenerInfoList.RemoveElement(listener) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP BrowsingContextWebProgress::GetDOMWindow( + mozIDOMWindowProxy** aDOMWindow) { + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP BrowsingContextWebProgress::GetIsTopLevel(bool* aIsTopLevel) { + *aIsTopLevel = true; + return NS_OK; +} + +NS_IMETHODIMP BrowsingContextWebProgress::GetIsLoadingDocument( + bool* aIsLoadingDocument) { + *aIsLoadingDocument = false; + return NS_OK; +} + +NS_IMETHODIMP BrowsingContextWebProgress::GetLoadType(uint32_t* aLoadType) { + *aLoadType = 0; + return NS_OK; +} + +NS_IMETHODIMP BrowsingContextWebProgress::GetTarget(nsIEventTarget** aTarget) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP BrowsingContextWebProgress::SetTarget(nsIEventTarget* aTarget) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +void BrowsingContextWebProgress::UpdateAndNotifyListeners( + uint32_t aFlag, + const std::function& aCallback) { + RefPtr kungFuDeathGrip = this; + + ListenerArray::ForwardIterator iter(mListenerInfoList); + while (iter.HasMore()) { + ListenerInfo& info = iter.GetNext(); + if (!(info.mNotifyMask & aFlag)) { + continue; + } + + nsCOMPtr listener = + do_QueryReferent(info.mWeakListener); + if (!listener) { + mListenerInfoList.RemoveElement(info); + continue; + } + + aCallback(listener); + } + + mListenerInfoList.Compact(); +} + +//////////////////////////////////////////////////////////////////////////////// +// nsIWebProgressListener + +NS_IMETHODIMP +BrowsingContextWebProgress::OnStateChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + uint32_t aStateFlags, + nsresult aStatus) { + const uint32_t startDocumentFlags = + nsIWebProgressListener::STATE_START | + nsIWebProgressListener::STATE_IS_DOCUMENT | + nsIWebProgressListener::STATE_IS_REQUEST | + nsIWebProgressListener::STATE_IS_WINDOW | + nsIWebProgressListener::STATE_IS_NETWORK; + bool isTopLevel = false; + nsresult rv = aWebProgress->GetIsTopLevel(&isTopLevel); + NS_ENSURE_SUCCESS(rv, rv); + bool isTopLevelStartDocumentEvent = + (aStateFlags & startDocumentFlags) == startDocumentFlags && isTopLevel; + // If we receive a matching STATE_START for a top-level document event, + // and we are currently not suspending this event, start suspending all + // further matching STATE_START events after this one. + if (isTopLevelStartDocumentEvent && !mSuspendOnStateStartChangeEvents) { + mSuspendOnStateStartChangeEvents = true; + } else if (mSuspendOnStateStartChangeEvents) { + // If we are currently suspending matching STATE_START events, check if this + // is a corresponding STATE_STOP event. + const uint32_t stopWindowFlags = nsIWebProgressListener::STATE_STOP | + nsIWebProgressListener::STATE_IS_WINDOW; + bool isTopLevelStopWindowEvent = + (aStateFlags & stopWindowFlags) == stopWindowFlags && isTopLevel; + if (isTopLevelStopWindowEvent) { + // If this is a STATE_STOP event corresponding to the initial STATE_START + // event, stop suspending matching STATE_START events. + mSuspendOnStateStartChangeEvents = false; + } else if (isTopLevelStartDocumentEvent) { + // We have received a matching STATE_START event at least twice, but + // haven't received the corresponding STATE_STOP event for the first one. + // Don't let this event through. This is probably a duplicate event from + // the new BrowserParent due to a process switch. + return NS_OK; + } + // Allow all other progress events that don't match top-level start document + // flags. + } + + UpdateAndNotifyListeners( + ((aStateFlags >> 16) & nsIWebProgress::NOTIFY_STATE_ALL), + [&](nsIWebProgressListener* listener) { + listener->OnStateChange(aWebProgress, aRequest, aStateFlags, aStatus); + }); + return NS_OK; +} + +NS_IMETHODIMP +BrowsingContextWebProgress::OnProgressChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + int32_t aCurSelfProgress, + int32_t aMaxSelfProgress, + int32_t aCurTotalProgress, + int32_t aMaxTotalProgress) { + UpdateAndNotifyListeners( + nsIWebProgress::NOTIFY_PROGRESS, [&](nsIWebProgressListener* listener) { + listener->OnProgressChange(aWebProgress, aRequest, aCurSelfProgress, + aMaxSelfProgress, aCurTotalProgress, + aMaxTotalProgress); + }); + return NS_OK; +} + +NS_IMETHODIMP +BrowsingContextWebProgress::OnLocationChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + nsIURI* aLocation, + uint32_t aFlags) { + UpdateAndNotifyListeners( + nsIWebProgress::NOTIFY_LOCATION, [&](nsIWebProgressListener* listener) { + listener->OnLocationChange(aWebProgress, aRequest, aLocation, aFlags); + }); + return NS_OK; +} + +NS_IMETHODIMP +BrowsingContextWebProgress::OnStatusChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + nsresult aStatus, + const char16_t* aMessage) { + UpdateAndNotifyListeners( + nsIWebProgress::NOTIFY_STATUS, [&](nsIWebProgressListener* listener) { + listener->OnStatusChange(aWebProgress, aRequest, aStatus, aMessage); + }); + return NS_OK; +} + +NS_IMETHODIMP +BrowsingContextWebProgress::OnSecurityChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + uint32_t aState) { + UpdateAndNotifyListeners( + nsIWebProgress::NOTIFY_SECURITY, [&](nsIWebProgressListener* listener) { + listener->OnSecurityChange(aWebProgress, aRequest, aState); + }); + return NS_OK; +} + +NS_IMETHODIMP +BrowsingContextWebProgress::OnContentBlockingEvent(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + uint32_t aEvent) { + UpdateAndNotifyListeners(nsIWebProgress::NOTIFY_CONTENT_BLOCKING, + [&](nsIWebProgressListener* listener) { + listener->OnContentBlockingEvent(aWebProgress, + aRequest, aEvent); + }); + return NS_OK; +} + +} // namespace dom +} // namespace mozilla diff --git a/docshell/base/BrowsingContextWebProgress.h b/docshell/base/BrowsingContextWebProgress.h new file mode 100644 index 0000000000..8085da715b --- /dev/null +++ b/docshell/base/BrowsingContextWebProgress.h @@ -0,0 +1,68 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_BrowsingContextWebProgress_h +#define mozilla_dom_BrowsingContextWebProgress_h + +#include "nsIWebProgress.h" +#include "nsIWebProgressListener.h" +#include "nsTObserverArray.h" +#include "nsWeakReference.h" + +namespace mozilla { +namespace dom { + +class BrowsingContextWebProgress final : public nsIWebProgress, + public nsIWebProgressListener { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIWEBPROGRESS + NS_DECL_NSIWEBPROGRESSLISTENER + + BrowsingContextWebProgress() = default; + + struct ListenerInfo { + ListenerInfo(nsIWeakReference* aListener, unsigned long aNotifyMask) + : mWeakListener(aListener), mNotifyMask(aNotifyMask) {} + + bool operator==(const ListenerInfo& aOther) const { + return mWeakListener == aOther.mWeakListener; + } + bool operator==(const nsWeakPtr& aOther) const { + return mWeakListener == aOther; + } + + // Weak pointer for the nsIWebProgressListener... + nsWeakPtr mWeakListener; + + // Mask indicating which notifications the listener wants to receive. + unsigned long mNotifyMask; + }; + + private: + virtual ~BrowsingContextWebProgress() = default; + + void UpdateAndNotifyListeners( + uint32_t aFlag, + const std::function& aCallback); + + using ListenerArray = nsAutoTObserverArray; + ListenerArray mListenerInfoList; + + // This indicates whether we are currently suspending onStateChange top level + // STATE_START events for a document. We start suspending whenever we receive + // the first STATE_START event with the matching flags (see + // ::RecvOnStateChange for details), until we get a corresponding STATE_STOP + // event. In the meantime, if there other onStateChange events, this flag does + // not affect them. We do this to avoid duplicate onStateChange STATE_START + // events that happen during process switch. With this flag, we allow + // onStateChange STATE_START event from the old BrowserParent, but not the + // same event from the new BrowserParent during a process switch. + bool mSuspendOnStateStartChangeEvents = false; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_BrowsingContextWebProgress_h diff --git a/docshell/base/CanonicalBrowsingContext.cpp b/docshell/base/CanonicalBrowsingContext.cpp new file mode 100644 index 0000000000..7f01ff6cf8 --- /dev/null +++ b/docshell/base/CanonicalBrowsingContext.cpp @@ -0,0 +1,1694 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/CanonicalBrowsingContext.h" + +#include "mozilla/CheckedInt.h" +#include "mozilla/EventForwards.h" +#include "mozilla/AsyncEventDispatcher.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/dom/BrowsingContextBinding.h" +#include "mozilla/dom/BrowsingContextGroup.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/EventTarget.h" +#include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/dom/ContentProcessManager.h" +#include "mozilla/dom/MediaController.h" +#include "mozilla/dom/MediaControlService.h" +#include "mozilla/dom/ContentPlaybackController.h" +#include "mozilla/dom/SessionHistoryEntry.h" +#include "mozilla/dom/SessionStorageManager.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/net/DocumentLoadListener.h" +#include "mozilla/NullPrincipal.h" +#include "nsIWebNavigation.h" +#include "mozilla/MozPromiseInlines.h" +#include "nsDocShell.h" +#include "nsFrameLoader.h" +#include "nsFrameLoaderOwner.h" +#include "nsGlobalWindowOuter.h" +#include "nsIWebBrowserChrome.h" +#include "nsIXULRuntime.h" +#include "nsNetUtil.h" +#include "nsSHistory.h" +#include "nsSecureBrowserUI.h" +#include "nsQueryObject.h" +#include "nsBrowserStatusFilter.h" +#include "nsIBrowser.h" + +using namespace mozilla::ipc; + +extern mozilla::LazyLogModule gAutoplayPermissionLog; +extern mozilla::LazyLogModule gSHLog; + +#define AUTOPLAY_LOG(msg, ...) \ + MOZ_LOG(gAutoplayPermissionLog, LogLevel::Debug, (msg, ##__VA_ARGS__)) + +namespace mozilla { +namespace dom { + +extern mozilla::LazyLogModule gUserInteractionPRLog; + +#define USER_ACTIVATION_LOG(msg, ...) \ + MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug, (msg, ##__VA_ARGS__)) + +CanonicalBrowsingContext::CanonicalBrowsingContext(WindowContext* aParentWindow, + BrowsingContextGroup* aGroup, + uint64_t aBrowsingContextId, + uint64_t aOwnerProcessId, + uint64_t aEmbedderProcessId, + BrowsingContext::Type aType, + FieldValues&& aInit) + : BrowsingContext(aParentWindow, aGroup, aBrowsingContextId, aType, + std::move(aInit)), + mProcessId(aOwnerProcessId), + mEmbedderProcessId(aEmbedderProcessId) { + // You are only ever allowed to create CanonicalBrowsingContexts in the + // parent process. + MOZ_RELEASE_ASSERT(XRE_IsParentProcess()); +} + +/* static */ +already_AddRefed CanonicalBrowsingContext::Get( + uint64_t aId) { + MOZ_RELEASE_ASSERT(XRE_IsParentProcess()); + return BrowsingContext::Get(aId).downcast(); +} + +/* static */ +CanonicalBrowsingContext* CanonicalBrowsingContext::Cast( + BrowsingContext* aContext) { + MOZ_RELEASE_ASSERT(XRE_IsParentProcess()); + return static_cast(aContext); +} + +/* static */ +const CanonicalBrowsingContext* CanonicalBrowsingContext::Cast( + const BrowsingContext* aContext) { + MOZ_RELEASE_ASSERT(XRE_IsParentProcess()); + return static_cast(aContext); +} + +already_AddRefed CanonicalBrowsingContext::Cast( + already_AddRefed&& aContext) { + MOZ_RELEASE_ASSERT(XRE_IsParentProcess()); + return aContext.downcast(); +} + +ContentParent* CanonicalBrowsingContext::GetContentParent() const { + if (mProcessId == 0) { + return nullptr; + } + + ContentProcessManager* cpm = ContentProcessManager::GetSingleton(); + return cpm->GetContentProcessById(ContentParentId(mProcessId)); +} + +void CanonicalBrowsingContext::GetCurrentRemoteType(nsACString& aRemoteType, + ErrorResult& aRv) const { + // If we're in the parent process, dump out the void string. + if (mProcessId == 0) { + aRemoteType = NOT_REMOTE_TYPE; + return; + } + + ContentParent* cp = GetContentParent(); + if (!cp) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return; + } + + aRemoteType = cp->GetRemoteType(); +} + +void CanonicalBrowsingContext::SetOwnerProcessId(uint64_t aProcessId) { + MOZ_LOG(GetLog(), LogLevel::Debug, + ("SetOwnerProcessId for 0x%08" PRIx64 " (0x%08" PRIx64 + " -> 0x%08" PRIx64 ")", + Id(), mProcessId, aProcessId)); + + mProcessId = aProcessId; +} + +nsISecureBrowserUI* CanonicalBrowsingContext::GetSecureBrowserUI() { + if (!IsTop()) { + return nullptr; + } + if (!mSecureBrowserUI) { + mSecureBrowserUI = new nsSecureBrowserUI(this); + } + return mSecureBrowserUI; +} + +void CanonicalBrowsingContext::MaybeAddAsProgressListener( + nsIWebProgress* aWebProgress) { + if (!GetWebProgress()) { + return; + } + if (!mStatusFilter) { + mStatusFilter = new nsBrowserStatusFilter(); + mStatusFilter->AddProgressListener(GetWebProgress(), + nsIWebProgress::NOTIFY_ALL); + } + aWebProgress->AddProgressListener(mStatusFilter, nsIWebProgress::NOTIFY_ALL); +} + +void CanonicalBrowsingContext::ReplacedBy( + CanonicalBrowsingContext* aNewContext) { + MOZ_ASSERT(!aNewContext->EverAttached()); + MOZ_ASSERT(IsTop() && aNewContext->IsTop()); + if (mStatusFilter) { + mStatusFilter->RemoveProgressListener(mWebProgress); + mStatusFilter = nullptr; + } + aNewContext->mWebProgress = std::move(mWebProgress); + aNewContext->mFields.SetWithoutSyncing(GetBrowserId()); + aNewContext->mFields.SetWithoutSyncing(GetHistoryID()); + aNewContext->mFields.SetWithoutSyncing( + GetExplicitActive()); + + if (mSessionHistory) { + mSessionHistory->SetBrowsingContext(aNewContext); + mSessionHistory.swap(aNewContext->mSessionHistory); + RefPtr childSHistory = ForgetChildSHistory(); + aNewContext->SetChildSHistory(childSHistory); + } + + if (mozilla::SessionHistoryInParent()) { + BackgroundSessionStorageManager::PropagateManager(Id(), aNewContext->Id()); + } + + MOZ_ASSERT(aNewContext->mLoadingEntries.IsEmpty()); + mLoadingEntries.SwapElements(aNewContext->mLoadingEntries); + MOZ_ASSERT(!aNewContext->mActiveEntry); + mActiveEntry.swap(aNewContext->mActiveEntry); +} + +void CanonicalBrowsingContext::UpdateSecurityState() { + if (mSecureBrowserUI) { + mSecureBrowserUI->RecomputeSecurityFlags(); + } +} + +void CanonicalBrowsingContext::SetInFlightProcessId(uint64_t aProcessId) { + MOZ_ASSERT(aProcessId); + mInFlightProcessId = aProcessId; +} + +void CanonicalBrowsingContext::ClearInFlightProcessId(uint64_t aProcessId) { + MOZ_ASSERT(aProcessId); + if (mInFlightProcessId == aProcessId) { + mInFlightProcessId = 0; + } +} + +void CanonicalBrowsingContext::GetWindowGlobals( + nsTArray>& aWindows) { + aWindows.SetCapacity(GetWindowContexts().Length()); + for (auto& window : GetWindowContexts()) { + aWindows.AppendElement(static_cast(window.get())); + } +} + +WindowGlobalParent* CanonicalBrowsingContext::GetCurrentWindowGlobal() const { + return static_cast(GetCurrentWindowContext()); +} + +WindowGlobalParent* CanonicalBrowsingContext::GetParentWindowContext() { + return static_cast( + BrowsingContext::GetParentWindowContext()); +} + +WindowGlobalParent* CanonicalBrowsingContext::GetTopWindowContext() { + return static_cast( + BrowsingContext::GetTopWindowContext()); +} + +already_AddRefed +CanonicalBrowsingContext::GetParentProcessWidgetContaining() { + // If our document is loaded in-process, such as chrome documents, get the + // widget directly from our outer window. Otherwise, try to get the widget + // from the toplevel content's browser's element. + nsCOMPtr widget; + if (nsGlobalWindowOuter* window = nsGlobalWindowOuter::Cast(GetDOMWindow())) { + widget = window->GetNearestWidget(); + } else if (Element* topEmbedder = Top()->GetEmbedderElement()) { + widget = nsContentUtils::WidgetForContent(topEmbedder); + if (!widget) { + widget = nsContentUtils::WidgetForDocument(topEmbedder->OwnerDoc()); + } + } + + if (widget) { + widget = widget->GetTopLevelWidget(); + } + + return widget.forget(); +} + +already_AddRefed +CanonicalBrowsingContext::GetEmbedderWindowGlobal() const { + uint64_t windowId = GetEmbedderInnerWindowId(); + if (windowId == 0) { + return nullptr; + } + + return WindowGlobalParent::GetByInnerWindowId(windowId); +} + +already_AddRefed +CanonicalBrowsingContext::GetParentCrossChromeBoundary() { + if (GetParent()) { + return do_AddRef(Cast(GetParent())); + } + if (GetEmbedderElement()) { + return do_AddRef( + Cast(GetEmbedderElement()->OwnerDoc()->GetBrowsingContext())); + } + return nullptr; +} + +already_AddRefed +CanonicalBrowsingContext::TopCrossChromeBoundary() { + RefPtr bc(this); + while (RefPtr parent = + bc->GetParentCrossChromeBoundary()) { + bc = parent.forget(); + } + return bc.forget(); +} + +Nullable CanonicalBrowsingContext::GetTopChromeWindow() { + RefPtr bc = TopCrossChromeBoundary(); + if (bc->IsChrome()) { + return WindowProxyHolder(bc.forget()); + } + return nullptr; +} + +nsISHistory* CanonicalBrowsingContext::GetSessionHistory() { + if (!IsTop()) { + return Cast(Top())->GetSessionHistory(); + } + + // Check GetChildSessionHistory() to make sure that this BrowsingContext has + // session history enabled. + if (!mSessionHistory && GetChildSessionHistory()) { + mSessionHistory = new nsSHistory(this); + } + + return mSessionHistory; +} + +SessionHistoryEntry* CanonicalBrowsingContext::GetActiveSessionHistoryEntry() { + return mActiveEntry; +} + +bool CanonicalBrowsingContext::HasHistoryEntry(nsISHEntry* aEntry) { + // XXX Should we check also loading entries? + return aEntry && mActiveEntry == aEntry; +} + +void CanonicalBrowsingContext::SwapHistoryEntries(nsISHEntry* aOldEntry, + nsISHEntry* aNewEntry) { + // XXX Should we check also loading entries? + if (mActiveEntry == aOldEntry) { + nsCOMPtr newEntry = do_QueryInterface(aNewEntry); + mActiveEntry = newEntry.forget(); + } +} + +void CanonicalBrowsingContext::AddLoadingSessionHistoryEntry( + uint64_t aLoadId, SessionHistoryEntry* aEntry) { + Unused << SetHistoryID(aEntry->DocshellID()); + mLoadingEntries.AppendElement(LoadingSessionHistoryEntry{aLoadId, aEntry}); +} + +void CanonicalBrowsingContext::GetLoadingSessionHistoryInfoFromParent( + Maybe& aLoadingInfo, int32_t* aRequestedIndex, + int32_t* aLength) { + *aRequestedIndex = -1; + *aLength = 0; + + nsISHistory* shistory = GetSessionHistory(); + if (!shistory || !GetParent()) { + return; + } + + SessionHistoryEntry* parentSHE = + GetParent()->Canonical()->GetActiveSessionHistoryEntry(); + if (parentSHE) { + int32_t index = -1; + for (BrowsingContext* sibling : GetParent()->Children()) { + ++index; + if (sibling == this) { + nsCOMPtr shEntry; + parentSHE->GetChildSHEntryIfHasNoDynamicallyAddedChild( + index, getter_AddRefs(shEntry)); + nsCOMPtr she = do_QueryInterface(shEntry); + if (she) { + aLoadingInfo.emplace(she); + mLoadingEntries.AppendElement(LoadingSessionHistoryEntry{ + aLoadingInfo.value().mLoadId, she.get()}); + *aRequestedIndex = shistory->GetRequestedIndex(); + *aLength = shistory->GetCount(); + Unused << SetHistoryID(she->DocshellID()); + } + break; + } + } + } +} + +UniquePtr +CanonicalBrowsingContext::CreateLoadingSessionHistoryEntryForLoad( + nsDocShellLoadState* aLoadState, nsIChannel* aChannel) { + RefPtr entry; + const LoadingSessionHistoryInfo* existingLoadingInfo = + aLoadState->GetLoadingSessionHistoryInfo(); + if (existingLoadingInfo) { + entry = SessionHistoryEntry::GetByLoadId(existingLoadingInfo->mLoadId); + MOZ_LOG(gSHLog, LogLevel::Verbose, + ("SHEntry::GetByLoadId(%" PRIu64 ") -> %p", + existingLoadingInfo->mLoadId, entry.get())); + if (!entry) { + return nullptr; + } + } else { + entry = new SessionHistoryEntry(aLoadState, aChannel); + if (IsTop()) { + // Only top level pages care about Get/SetPersist. + entry->SetPersist( + nsDocShell::ShouldAddToSessionHistory(aLoadState->URI(), aChannel)); + } else if (mActiveEntry || !mLoadingEntries.IsEmpty()) { + entry->SetIsSubFrame(true); + } + entry->SetDocshellID(GetHistoryID()); + entry->SetIsDynamicallyAdded(CreatedDynamically()); + entry->SetForInitialLoad(true); + } + MOZ_DIAGNOSTIC_ASSERT(entry); + + UniquePtr loadingInfo; + if (existingLoadingInfo) { + loadingInfo = MakeUnique(*existingLoadingInfo); + } else { + loadingInfo = MakeUnique(entry); + mLoadingEntries.AppendElement( + LoadingSessionHistoryEntry{loadingInfo->mLoadId, entry}); + } + + MOZ_ASSERT(SessionHistoryEntry::GetByLoadId(loadingInfo->mLoadId) == entry); + + return loadingInfo; +} + +UniquePtr +CanonicalBrowsingContext::ReplaceLoadingSessionHistoryEntryForLoad( + LoadingSessionHistoryInfo* aInfo, nsIChannel* aChannel) { + MOZ_ASSERT(aInfo); + MOZ_ASSERT(aChannel); + + UniquePtr newInfo = MakeUnique( + aChannel, aInfo->mInfo.LoadType(), + aInfo->mInfo.GetPartitionedPrincipalToInherit(), aInfo->mInfo.GetCsp()); + + RefPtr newEntry = new SessionHistoryEntry(newInfo.get()); + if (IsTop()) { + // Only top level pages care about Get/SetPersist. + nsCOMPtr uri; + aChannel->GetURI(getter_AddRefs(uri)); + newEntry->SetPersist(nsDocShell::ShouldAddToSessionHistory(uri, aChannel)); + } else { + newEntry->SetIsSubFrame(aInfo->mInfo.IsSubFrame()); + } + newEntry->SetDocshellID(GetHistoryID()); + newEntry->SetIsDynamicallyAdded(CreatedDynamically()); + newEntry->SetForInitialLoad(true); + + // Replacing the old entry. + SessionHistoryEntry::SetByLoadId(aInfo->mLoadId, newEntry); + + for (size_t i = 0; i < mLoadingEntries.Length(); ++i) { + if (mLoadingEntries[i].mLoadId == aInfo->mLoadId) { + mLoadingEntries[i].mEntry = newEntry; + break; + } + } + + return MakeUnique(newEntry, aInfo->mLoadId); +} + +void CanonicalBrowsingContext::CallOnAllTopDescendants( + const std::function& + aCallback) { +#ifdef DEBUG + RefPtr parent = GetParentCrossChromeBoundary(); + MOZ_ASSERT(!parent, "Should only call on top chrome BC"); +#endif + + nsTArray> groups; + BrowsingContextGroup::GetAllGroups(groups); + for (auto& browsingContextGroup : groups) { + for (auto& bc : browsingContextGroup->Toplevels()) { + if (bc == this) { + // Cannot be a descendent of myself so skip. + continue; + } + + RefPtr top = + bc->Canonical()->TopCrossChromeBoundary(); + if (top == this) { + if (aCallback(bc->Canonical()) == CallState::Stop) { + return; + } + } + } + } +} + +void CanonicalBrowsingContext::SessionHistoryCommit(uint64_t aLoadId, + const nsID& aChangeID, + uint32_t aLoadType, + bool aPersist, + bool aCloneEntryChildren) { + MOZ_LOG(gSHLog, LogLevel::Verbose, + ("CanonicalBrowsingContext::SessionHistoryCommit %p %" PRIu64, this, + aLoadId)); + for (size_t i = 0; i < mLoadingEntries.Length(); ++i) { + if (mLoadingEntries[i].mLoadId == aLoadId) { + nsSHistory* shistory = static_cast(GetSessionHistory()); + if (!shistory) { + SessionHistoryEntry::RemoveLoadId(aLoadId); + mLoadingEntries.RemoveElementAt(i); + return; + } + + CallerWillNotifyHistoryIndexAndLengthChanges caller(shistory); + + RefPtr newActiveEntry = mLoadingEntries[i].mEntry; + + bool loadFromSessionHistory = !newActiveEntry->ForInitialLoad(); + newActiveEntry->SetForInitialLoad(false); + SessionHistoryEntry::RemoveLoadId(aLoadId); + mLoadingEntries.RemoveElementAt(i); + + // If there is a name in the new entry, clear the name of all contiguous + // entries. This is for https://html.spec.whatwg.org/#history-traversal + // Step 4.4.2. + nsAutoString nameOfNewEntry; + newActiveEntry->GetName(nameOfNewEntry); + if (!nameOfNewEntry.IsEmpty()) { + nsSHistory::WalkContiguousEntries( + newActiveEntry, + [](nsISHEntry* aEntry) { aEntry->SetName(EmptyString()); }); + } + + bool addEntry = ShouldUpdateSessionHistory(aLoadType); + if (IsTop()) { + mActiveEntry = newActiveEntry; + if (loadFromSessionHistory) { + // XXX Synchronize browsing context tree and session history tree? + shistory->UpdateIndex(); + } else { + if (LOAD_TYPE_HAS_FLAGS( + aLoadType, nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY)) { + // Replace the current entry with the new entry. + int32_t index = shistory->GetIndexForReplace(); + + // If we're trying to replace an inexistant shistory entry then we + // should append instead. + addEntry = index < 0; + if (!addEntry) { + shistory->ReplaceEntry(index, mActiveEntry); + } + } + + if (addEntry) { + shistory->AddEntry(mActiveEntry, aPersist); + } + } + } else { + // FIXME The old implementations adds it to the parent's mLSHE if there + // is one, need to figure out if that makes sense here (peterv + // doesn't think it would). + if (loadFromSessionHistory) { + if (mActiveEntry) { + // mActiveEntry is null if we're loading iframes from session + // history while also parent page is loading from session history. + // In that case there isn't anything to sync. + mActiveEntry->SyncTreesForSubframeNavigation(newActiveEntry, Top(), + this); + } + mActiveEntry = newActiveEntry; + // FIXME UpdateIndex() here may update index too early (but even the + // old implementation seems to have similar issues). + shistory->UpdateIndex(); + } else if (addEntry) { + if (mActiveEntry) { + if (LOAD_TYPE_HAS_FLAGS( + aLoadType, nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY)) { + // FIXME We need to make sure that when we create the info we + // make a copy of the shared state. + mActiveEntry->ReplaceWith(*newActiveEntry); + } else { + // AddChildSHEntryHelper does update the index of the session + // history! + shistory->AddChildSHEntryHelper(mActiveEntry, newActiveEntry, + Top(), aCloneEntryChildren); + mActiveEntry = newActiveEntry; + } + } else { + SessionHistoryEntry* parentEntry = GetParent()->mActiveEntry; + // XXX What should happen if parent doesn't have mActiveEntry? + // Or can that even happen ever? + if (parentEntry) { + mActiveEntry = newActiveEntry; + // FIXME Using IsInProcess for aUseRemoteSubframes isn't quite + // right, but aUseRemoteSubframes should be going away. + parentEntry->AddChild( + mActiveEntry, + CreatedDynamically() ? -1 : GetParent()->IndexOf(this), + IsInProcess()); + } + } + } + } + + ResetSHEntryHasUserInteractionCache(); + + HistoryCommitIndexAndLength(aChangeID, caller); + + shistory->LogHistory(); + + return; + } + // XXX Should the loading entries before [i] be removed? + } + // FIXME Should we throw an error if we don't find an entry for + // aSessionHistoryEntryId? +} + +static already_AddRefed CreateLoadInfo( + SessionHistoryEntry* aEntry) { + const SessionHistoryInfo& info = aEntry->Info(); + RefPtr loadState(new nsDocShellLoadState(info.GetURI())); + info.FillLoadInfo(*loadState); + UniquePtr loadingInfo; + loadingInfo = MakeUnique(aEntry); + loadState->SetLoadingSessionHistoryInfo(std::move(loadingInfo)); + + return loadState.forget(); +} + +void CanonicalBrowsingContext::NotifyOnHistoryReload( + bool aForceReload, bool& aCanReload, + Maybe>& aLoadState, + Maybe& aReloadActiveEntry) { + MOZ_DIAGNOSTIC_ASSERT(!aLoadState); + + aCanReload = true; + nsISHistory* shistory = GetSessionHistory(); + NS_ENSURE_TRUE_VOID(shistory); + + shistory->NotifyOnHistoryReload(&aCanReload); + if (!aCanReload) { + return; + } + + if (mActiveEntry) { + aLoadState.emplace(CreateLoadInfo(mActiveEntry)); + aReloadActiveEntry.emplace(true); + if (aForceReload) { + shistory->RemoveFrameEntries(mActiveEntry); + } + } else if (!mLoadingEntries.IsEmpty()) { + const LoadingSessionHistoryEntry& loadingEntry = + mLoadingEntries.LastElement(); + aLoadState.emplace(CreateLoadInfo(loadingEntry.mEntry)); + aReloadActiveEntry.emplace(false); + if (aForceReload) { + SessionHistoryEntry* entry = + SessionHistoryEntry::GetByLoadId(loadingEntry.mLoadId); + if (entry) { + shistory->RemoveFrameEntries(entry); + } + } + } + + if (aLoadState) { + int32_t index = 0; + int32_t requestedIndex = -1; + int32_t length = 0; + shistory->GetIndex(&index); + shistory->GetRequestedIndex(&requestedIndex); + shistory->GetCount(&length); + aLoadState.ref()->SetLoadIsFromSessionHistory( + requestedIndex >= 0 ? requestedIndex : index, length, + aReloadActiveEntry.value()); + } + // If we don't have an active entry and we don't have a loading entry then + // the nsDocShell will create a load state based on its document. +} + +void CanonicalBrowsingContext::SetActiveSessionHistoryEntry( + const Maybe& aPreviousScrollPos, SessionHistoryInfo* aInfo, + uint32_t aLoadType, uint32_t aUpdatedCacheKey, const nsID& aChangeID) { + nsISHistory* shistory = GetSessionHistory(); + if (!shistory) { + return; + } + CallerWillNotifyHistoryIndexAndLengthChanges caller(shistory); + + RefPtr oldActiveEntry = mActiveEntry; + if (aPreviousScrollPos.isSome() && oldActiveEntry) { + oldActiveEntry->SetScrollPosition(aPreviousScrollPos.ref().x, + aPreviousScrollPos.ref().y); + } + mActiveEntry = new SessionHistoryEntry(aInfo); + mActiveEntry->SetDocshellID(GetHistoryID()); + mActiveEntry->AdoptBFCacheEntry(oldActiveEntry); + if (aUpdatedCacheKey != 0) { + mActiveEntry->SharedInfo()->mCacheKey = aUpdatedCacheKey; + } + + if (IsTop()) { + Maybe previousEntryIndex, loadedEntryIndex; + shistory->AddToRootSessionHistory( + true, oldActiveEntry, this, mActiveEntry, aLoadType, + nsDocShell::ShouldAddToSessionHistory(aInfo->GetURI(), nullptr), + &previousEntryIndex, &loadedEntryIndex); + } else { + if (oldActiveEntry) { + shistory->AddChildSHEntryHelper(oldActiveEntry, mActiveEntry, Top(), + true); + } else if (GetParent() && GetParent()->mActiveEntry) { + GetParent()->mActiveEntry->AddChild( + mActiveEntry, CreatedDynamically() ? -1 : GetParent()->IndexOf(this), + UseRemoteSubframes()); + } + } + + ResetSHEntryHasUserInteractionCache(); + + // FIXME Need to do the equivalent of EvictContentViewersOrReplaceEntry. + HistoryCommitIndexAndLength(aChangeID, caller); + + static_cast(shistory)->LogHistory(); +} + +void CanonicalBrowsingContext::ReplaceActiveSessionHistoryEntry( + SessionHistoryInfo* aInfo) { + if (!mActiveEntry) { + return; + } + + mActiveEntry->SetInfo(aInfo); + // Notify children of the update + nsSHistory* shistory = static_cast(GetSessionHistory()); + if (shistory) { + shistory->NotifyOnHistoryReplaceEntry(); + shistory->UpdateRootBrowsingContextState(); + } + + ResetSHEntryHasUserInteractionCache(); + + // FIXME Need to do the equivalent of EvictContentViewersOrReplaceEntry. +} + +void CanonicalBrowsingContext::RemoveDynEntriesFromActiveSessionHistoryEntry() { + nsISHistory* shistory = GetSessionHistory(); + // In theory shistory can be null here if the method is called right after + // CanonicalBrowsingContext::ReplacedBy call. + NS_ENSURE_TRUE_VOID(shistory); + nsCOMPtr root = nsSHistory::GetRootSHEntry(mActiveEntry); + shistory->RemoveDynEntries(shistory->GetIndexOfEntry(root), mActiveEntry); +} + +void CanonicalBrowsingContext::RemoveFromSessionHistory() { + nsSHistory* shistory = static_cast(GetSessionHistory()); + if (shistory) { + nsCOMPtr root = nsSHistory::GetRootSHEntry(mActiveEntry); + bool didRemove; + AutoTArray ids({GetHistoryID()}); + shistory->RemoveEntries(ids, shistory->GetIndexOfEntry(root), &didRemove); + if (didRemove) { + BrowsingContext* rootBC = shistory->GetBrowsingContext(); + if (rootBC) { + if (!rootBC->IsInProcess()) { + Unused << rootBC->Canonical() + ->GetContentParent() + ->SendDispatchLocationChangeEvent(rootBC); + } else if (rootBC->GetDocShell()) { + rootBC->GetDocShell()->DispatchLocationChangeEvent(); + } + } + } + } +} + +void CanonicalBrowsingContext::HistoryGo( + int32_t aOffset, uint64_t aHistoryEpoch, bool aRequireUserInteraction, + Maybe aContentId, + std::function&& aResolver) { + if (aRequireUserInteraction && aOffset != -1 && aOffset != 1) { + NS_ERROR( + "aRequireUserInteraction may only be used with an offset of -1 or 1"); + return; + } + + nsSHistory* shistory = static_cast(GetSessionHistory()); + if (!shistory) { + return; + } + + CheckedInt index = shistory->GetRequestedIndex() >= 0 + ? shistory->GetRequestedIndex() + : shistory->Index(); + MOZ_LOG(gSHLog, LogLevel::Debug, + ("HistoryGo(%d->%d) epoch %" PRIu64 "/id %" PRIu64, aOffset, + (index + aOffset).value(), aHistoryEpoch, + (uint64_t)(aContentId.isSome() ? aContentId.value() : 0))); + + while (true) { + index += aOffset; + if (!index.isValid()) { + MOZ_LOG(gSHLog, LogLevel::Debug, ("Invalid index")); + return; + } + + // Check for user interaction if desired, except for the first and last + // history entries. We compare with >= to account for the case where + // aOffset >= length. + if (!aRequireUserInteraction || index.value() >= shistory->Length() - 1 || + index.value() <= 0) { + break; + } + if (shistory->HasUserInteractionAtIndex(index.value())) { + break; + } + } + + // Implement aborting additional history navigations from within the same + // event spin of the content process. + + uint64_t epoch; + bool sameEpoch = false; + Maybe id; + shistory->GetEpoch(epoch, id); + + if (aContentId == id && epoch >= aHistoryEpoch) { + sameEpoch = true; + MOZ_LOG(gSHLog, LogLevel::Debug, ("Same epoch/id")); + } + // Don't update the epoch until we know if the target index is valid + + // GoToIndex checks that index is >= 0 and < length. + nsTArray loadResults; + nsresult rv = shistory->GotoIndex(index.value(), loadResults, sameEpoch); + if (NS_FAILED(rv)) { + MOZ_LOG(gSHLog, LogLevel::Debug, + ("Dropping HistoryGo - bad index or same epoch (not in same doc)")); + return; + } + if (epoch < aHistoryEpoch || aContentId != id) { + MOZ_LOG(gSHLog, LogLevel::Debug, ("Set epoch")); + shistory->SetEpoch(aHistoryEpoch, aContentId); + } + aResolver(shistory->GetRequestedIndex()); + nsSHistory::LoadURIs(loadResults); +} + +JSObject* CanonicalBrowsingContext::WrapObject( + JSContext* aCx, JS::Handle aGivenProto) { + return CanonicalBrowsingContext_Binding::Wrap(aCx, this, aGivenProto); +} + +void CanonicalBrowsingContext::DispatchWheelZoomChange(bool aIncrease) { + Element* element = Top()->GetEmbedderElement(); + if (!element) { + return; + } + + auto event = aIncrease ? u"DoZoomEnlargeBy10"_ns : u"DoZoomReduceBy10"_ns; + auto dispatcher = MakeRefPtr( + element, event, CanBubble::eYes, ChromeOnlyDispatch::eYes); + dispatcher->PostDOMEvent(); +} + +void CanonicalBrowsingContext::CanonicalDiscard() { + if (mTabMediaController) { + mTabMediaController->Shutdown(); + mTabMediaController = nullptr; + } + + if (IsTop()) { + BackgroundSessionStorageManager::RemoveManager(Id()); + } +} + +void CanonicalBrowsingContext::NotifyStartDelayedAutoplayMedia() { + WindowContext* windowContext = GetCurrentWindowContext(); + if (!windowContext) { + return; + } + + // As this function would only be called when user click the play icon on the + // tab bar. That's clear user intent to play, so gesture activate the window + // context so that the block-autoplay logic allows the media to autoplay. + windowContext->NotifyUserGestureActivation(); + AUTOPLAY_LOG("NotifyStartDelayedAutoplayMedia for chrome bc 0x%08" PRIx64, + Id()); + StartDelayedAutoplayMediaComponents(); + // Notfiy all content browsing contexts which are related with the canonical + // browsing content tree to start delayed autoplay media. + + Group()->EachParent([&](ContentParent* aParent) { + Unused << aParent->SendStartDelayedAutoplayMediaComponents(this); + }); +} + +void CanonicalBrowsingContext::NotifyMediaMutedChanged(bool aMuted, + ErrorResult& aRv) { + MOZ_ASSERT(!GetParent(), + "Notify media mute change on non top-level context!"); + SetMuted(aMuted, aRv); +} + +uint32_t CanonicalBrowsingContext::CountSiteOrigins( + GlobalObject& aGlobal, + const Sequence>& aRoots) { + nsTHashtable uniqueSiteOrigins; + + for (const auto& root : aRoots) { + root->PreOrderWalk([&](BrowsingContext* aContext) { + WindowGlobalParent* windowGlobalParent = + aContext->Canonical()->GetCurrentWindowGlobal(); + if (windowGlobalParent) { + nsIPrincipal* documentPrincipal = + windowGlobalParent->DocumentPrincipal(); + + bool isContentPrincipal = documentPrincipal->GetIsContentPrincipal(); + if (isContentPrincipal) { + nsCString siteOrigin; + documentPrincipal->GetSiteOrigin(siteOrigin); + uniqueSiteOrigins.PutEntry(siteOrigin); + } + } + }); + } + + return uniqueSiteOrigins.Count(); +} + +void CanonicalBrowsingContext::UpdateMediaControlAction( + const MediaControlAction& aAction) { + if (IsDiscarded()) { + return; + } + ContentMediaControlKeyHandler::HandleMediaControlAction(this, aAction); + Group()->EachParent([&](ContentParent* aParent) { + Unused << aParent->SendUpdateMediaControlAction(this, aAction); + }); +} + +void CanonicalBrowsingContext::LoadURI(const nsAString& aURI, + const LoadURIOptions& aOptions, + ErrorResult& aError) { + RefPtr loadState; + nsresult rv = nsDocShellLoadState::CreateFromLoadURIOptions( + this, aURI, aOptions, getter_AddRefs(loadState)); + + if (rv == NS_ERROR_MALFORMED_URI) { + DisplayLoadError(aURI); + return; + } + + if (NS_FAILED(rv)) { + aError.Throw(rv); + return; + } + + LoadURI(loadState, true); +} + +void CanonicalBrowsingContext::GoBack( + const Optional& aCancelContentJSEpoch, + bool aRequireUserInteraction) { + if (IsDiscarded()) { + return; + } + + // Stop any known network loads if necessary. + if (mCurrentLoad) { + mCurrentLoad->Cancel(NS_BINDING_ABORTED); + } + + if (nsDocShell* docShell = nsDocShell::Cast(GetDocShell())) { + if (aCancelContentJSEpoch.WasPassed()) { + docShell->SetCancelContentJSEpoch(aCancelContentJSEpoch.Value()); + } + docShell->GoBack(aRequireUserInteraction); + } else if (ContentParent* cp = GetContentParent()) { + Maybe cancelContentJSEpoch; + if (aCancelContentJSEpoch.WasPassed()) { + cancelContentJSEpoch = Some(aCancelContentJSEpoch.Value()); + } + Unused << cp->SendGoBack(this, cancelContentJSEpoch, + aRequireUserInteraction); + } +} +void CanonicalBrowsingContext::GoForward( + const Optional& aCancelContentJSEpoch, + bool aRequireUserInteraction) { + if (IsDiscarded()) { + return; + } + + // Stop any known network loads if necessary. + if (mCurrentLoad) { + mCurrentLoad->Cancel(NS_BINDING_ABORTED); + } + + if (auto* docShell = nsDocShell::Cast(GetDocShell())) { + if (aCancelContentJSEpoch.WasPassed()) { + docShell->SetCancelContentJSEpoch(aCancelContentJSEpoch.Value()); + } + docShell->GoForward(aRequireUserInteraction); + } else if (ContentParent* cp = GetContentParent()) { + Maybe cancelContentJSEpoch; + if (aCancelContentJSEpoch.WasPassed()) { + cancelContentJSEpoch.emplace(aCancelContentJSEpoch.Value()); + } + Unused << cp->SendGoForward(this, cancelContentJSEpoch, + aRequireUserInteraction); + } +} +void CanonicalBrowsingContext::GoToIndex( + int32_t aIndex, const Optional& aCancelContentJSEpoch) { + if (IsDiscarded()) { + return; + } + + // Stop any known network loads if necessary. + if (mCurrentLoad) { + mCurrentLoad->Cancel(NS_BINDING_ABORTED); + } + + if (auto* docShell = nsDocShell::Cast(GetDocShell())) { + if (aCancelContentJSEpoch.WasPassed()) { + docShell->SetCancelContentJSEpoch(aCancelContentJSEpoch.Value()); + } + docShell->GotoIndex(aIndex); + } else if (ContentParent* cp = GetContentParent()) { + Maybe cancelContentJSEpoch; + if (aCancelContentJSEpoch.WasPassed()) { + cancelContentJSEpoch.emplace(aCancelContentJSEpoch.Value()); + } + Unused << cp->SendGoToIndex(this, aIndex, cancelContentJSEpoch); + } +} +void CanonicalBrowsingContext::Reload(uint32_t aReloadFlags) { + if (IsDiscarded()) { + return; + } + + // Stop any known network loads if necessary. + if (mCurrentLoad) { + mCurrentLoad->Cancel(NS_BINDING_ABORTED); + } + + if (auto* docShell = nsDocShell::Cast(GetDocShell())) { + docShell->Reload(aReloadFlags); + } else if (ContentParent* cp = GetContentParent()) { + Unused << cp->SendReload(this, aReloadFlags); + } +} + +void CanonicalBrowsingContext::Stop(uint32_t aStopFlags) { + if (IsDiscarded()) { + return; + } + + // Stop any known network loads if necessary. + if (mCurrentLoad && (aStopFlags & nsIWebNavigation::STOP_NETWORK)) { + mCurrentLoad->Cancel(NS_BINDING_ABORTED); + } + + // Ask the docshell to stop to handle loads that haven't + // yet reached here, as well as non-network activity. + if (auto* docShell = nsDocShell::Cast(GetDocShell())) { + docShell->Stop(aStopFlags); + } else if (ContentParent* cp = GetContentParent()) { + Unused << cp->SendStopLoad(this, aStopFlags); + } +} + +void CanonicalBrowsingContext::PendingRemotenessChange::ProcessReady() { + if (!mPromise) { + return; + } + + // Wait for our blocker promise to resolve, if present. + if (mPrepareToChangePromise) { + mPrepareToChangePromise->Then( + GetMainThreadSerialEventTarget(), __func__, + [self = RefPtr{this}](bool) { self->Finish(); }, + [self = RefPtr{this}](nsresult aRv) { self->Cancel(aRv); }); + return; + } + + Finish(); +} + +void CanonicalBrowsingContext::PendingRemotenessChange::Finish() { + if (!mPromise) { + return; + } + + RefPtr target(mTarget); + if (target->IsDiscarded()) { + Cancel(NS_ERROR_FAILURE); + return; + } + + // While process switching, we need to check if any of our ancestors are + // discarded or no longer current, in which case the process switch needs to + // be aborted. + if (!target->AncestorsAreCurrent()) { + NS_WARNING("Ancestor context is no longer current"); + Cancel(NS_ERROR_FAILURE); + return; + } + + // If this BrowsingContext is embedded within the parent process, perform the + // process switch directly. + if (Element* browserElement = target->GetEmbedderElement()) { + MOZ_DIAGNOSTIC_ASSERT(target->IsTop(), + "We shouldn't be trying to change the remoteness of " + "non-remote iframes"); + + nsCOMPtr browser = browserElement->AsBrowser(); + if (!browser) { + Cancel(NS_ERROR_FAILURE); + return; + } + + RefPtr frameLoaderOwner = + do_QueryObject(browserElement); + MOZ_RELEASE_ASSERT(frameLoaderOwner, + "embedder browser must be nsFrameLoaderOwner"); + + // Tell frontend code that this browser element is about to change process. + nsresult rv = browser->BeforeChangeRemoteness(); + if (NS_FAILED(rv)) { + Cancel(rv); + return; + } + + // Some frontend code checks the value of the `remote` attribute on the + // browser to determine if it is remote, so update the value. + browserElement->SetAttr(kNameSpaceID_None, nsGkAtoms::remote, + mContentParent ? u"true"_ns : u"false"_ns, + /* notify */ true); + + // The process has been created, hand off to nsFrameLoaderOwner to finish + // the process switch. + ErrorResult error; + frameLoaderOwner->ChangeRemotenessToProcess( + mContentParent, mReplaceBrowsingContext, mSpecificGroup, error); + if (error.Failed()) { + Cancel(error.StealNSResult()); + return; + } + + // Tell frontend the load is done. + bool loadResumed = false; + rv = browser->FinishChangeRemoteness(mPendingSwitchId, &loadResumed); + if (NS_WARN_IF(NS_FAILED(rv))) { + Cancel(rv); + return; + } + + // We did it! The process switch is complete. + RefPtr frameLoader = frameLoaderOwner->GetFrameLoader(); + RefPtr newBrowser = frameLoader->GetBrowserParent(); + if (!newBrowser) { + if (mContentParent) { + // Failed to create the BrowserParent somehow! Abort the process switch + // attempt. + Cancel(NS_ERROR_UNEXPECTED); + return; + } + + if (!loadResumed) { + RefPtr newDocShell = frameLoader->GetDocShell(error); + if (error.Failed()) { + Cancel(error.StealNSResult()); + return; + } + + rv = newDocShell->ResumeRedirectedLoad(mPendingSwitchId, + /* aHistoryIndex */ -1); + if (NS_FAILED(rv)) { + Cancel(error.StealNSResult()); + return; + } + } + } else if (!loadResumed) { + newBrowser->ResumeLoad(mPendingSwitchId); + } + + mPromise->Resolve(newBrowser, __func__); + Clear(); + return; + } + + if (NS_WARN_IF(!mContentParent)) { + Cancel(NS_ERROR_FAILURE); + return; + } + + RefPtr embedderWindow = target->GetEmbedderWindowGlobal(); + if (NS_WARN_IF(!embedderWindow) || NS_WARN_IF(!embedderWindow->CanSend())) { + Cancel(NS_ERROR_FAILURE); + return; + } + + RefPtr embedderBrowser = embedderWindow->GetBrowserParent(); + if (NS_WARN_IF(!embedderBrowser)) { + Cancel(NS_ERROR_FAILURE); + return; + } + + // Pull load flags from our embedder browser. + nsCOMPtr loadContext = embedderBrowser->GetLoadContext(); + MOZ_DIAGNOSTIC_ASSERT( + loadContext->UseRemoteTabs() && loadContext->UseRemoteSubframes(), + "Not supported without fission"); + + // NOTE: These are the only flags we actually care about + uint32_t chromeFlags = nsIWebBrowserChrome::CHROME_REMOTE_WINDOW | + nsIWebBrowserChrome::CHROME_FISSION_WINDOW; + if (loadContext->UsePrivateBrowsing()) { + chromeFlags |= nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW; + } + + RefPtr oldWindow = target->GetCurrentWindowGlobal(); + RefPtr oldBrowser = + oldWindow ? oldWindow->GetBrowserParent() : nullptr; + bool wasRemote = oldWindow && oldWindow->IsProcessRoot(); + + // Update which process is considered the current owner + uint64_t inFlightProcessId = target->OwnerProcessId(); + target->SetInFlightProcessId(inFlightProcessId); + target->SetOwnerProcessId(mContentParent->ChildID()); + + auto resetInFlightId = [target, inFlightProcessId] { + target->ClearInFlightProcessId(inFlightProcessId); + }; + + // If we were in a remote frame, trigger unloading of the remote window. When + // the original remote window acknowledges, we can clear the in-flight ID. + if (wasRemote) { + MOZ_DIAGNOSTIC_ASSERT(oldBrowser); + MOZ_DIAGNOSTIC_ASSERT(oldBrowser != embedderBrowser); + MOZ_DIAGNOSTIC_ASSERT(oldBrowser->GetBrowserBridgeParent()); + + auto callback = [resetInFlightId](auto) { resetInFlightId(); }; + oldBrowser->SendWillChangeProcess(callback, callback); + oldBrowser->Destroy(); + } + + MOZ_ASSERT(!mReplaceBrowsingContext, "Cannot replace BC for subframe"); + nsCOMPtr initialPrincipal = + NullPrincipal::CreateWithInheritedAttributes( + target->OriginAttributesRef(), + /* isFirstParty */ false); + WindowGlobalInit windowInit = + WindowGlobalActor::AboutBlankInitializer(target, initialPrincipal); + + // Create and initialize our new BrowserBridgeParent. + TabId tabId(nsContentUtils::GenerateTabId()); + RefPtr bridge = new BrowserBridgeParent(); + nsresult rv = bridge->InitWithProcess(embedderBrowser, mContentParent, + windowInit, chromeFlags, tabId); + if (NS_WARN_IF(NS_FAILED(rv))) { + Cancel(rv); + return; + } + + // Tell the embedder process a remoteness change is in-process. When this is + // acknowledged, reset the in-flight ID if it used to be an in-process load. + RefPtr newBrowser = bridge->GetBrowserParent(); + { + auto callback = [wasRemote, resetInFlightId](auto) { + if (!wasRemote) { + resetInFlightId(); + } + }; + + ManagedEndpoint endpoint = + embedderBrowser->OpenPBrowserBridgeEndpoint(bridge); + if (NS_WARN_IF(!endpoint.IsValid())) { + Cancel(NS_ERROR_UNEXPECTED); + return; + } + embedderWindow->SendMakeFrameRemote(target, std::move(endpoint), tabId, + newBrowser->GetLayersId(), callback, + callback); + } + + // Resume the pending load in our new process. + if (mPendingSwitchId) { + newBrowser->ResumeLoad(mPendingSwitchId); + } + + // We did it! The process switch is complete. + mPromise->Resolve(newBrowser, __func__); + Clear(); +} + +void CanonicalBrowsingContext::PendingRemotenessChange::Cancel(nsresult aRv) { + if (!mPromise) { + return; + } + + mPromise->Reject(aRv, __func__); + Clear(); +} + +void CanonicalBrowsingContext::PendingRemotenessChange::Clear() { + // Make sure we don't die while we're doing cleanup. + RefPtr kungFuDeathGrip(this); + if (mTarget) { + MOZ_DIAGNOSTIC_ASSERT(mTarget->mPendingRemotenessChange == this); + mTarget->mPendingRemotenessChange = nullptr; + } + + // When this PendingRemotenessChange was created, it was given a + // `mContentParent`. + if (mContentParent) { + mContentParent->RemoveKeepAlive(); + mContentParent = nullptr; + } + + // If we were given a specific group, stop keeping that group alive manually. + if (mSpecificGroup) { + mSpecificGroup->RemoveKeepAlive(); + mSpecificGroup = nullptr; + } + + mPromise = nullptr; + mTarget = nullptr; + mPrepareToChangePromise = nullptr; +} + +CanonicalBrowsingContext::PendingRemotenessChange::PendingRemotenessChange( + CanonicalBrowsingContext* aTarget, RemotenessPromise::Private* aPromise, + uint64_t aPendingSwitchId, bool aReplaceBrowsingContext) + : mTarget(aTarget), + mPromise(aPromise), + mPendingSwitchId(aPendingSwitchId), + mReplaceBrowsingContext(aReplaceBrowsingContext) {} + +CanonicalBrowsingContext::PendingRemotenessChange::~PendingRemotenessChange() { + MOZ_ASSERT(!mPromise && !mTarget && !mContentParent && !mSpecificGroup && + !mPrepareToChangePromise, + "should've already been Cancel() or Complete()-ed"); +} + +BrowserParent* CanonicalBrowsingContext::GetBrowserParent() const { + if (auto* wg = GetCurrentWindowGlobal()) { + return wg->GetBrowserParent(); + } + return nullptr; +} + +RefPtr +CanonicalBrowsingContext::ChangeRemoteness(const nsACString& aRemoteType, + uint64_t aPendingSwitchId, + bool aReplaceBrowsingContext, + uint64_t aSpecificGroupId) { + MOZ_DIAGNOSTIC_ASSERT(IsContent(), + "cannot change the process of chrome contexts"); + MOZ_DIAGNOSTIC_ASSERT( + IsTop() == IsEmbeddedInProcess(0), + "toplevel content must be embedded in the parent process"); + MOZ_DIAGNOSTIC_ASSERT(!aReplaceBrowsingContext || IsTop(), + "Cannot replace BrowsingContext for subframes"); + MOZ_DIAGNOSTIC_ASSERT(aSpecificGroupId == 0 || aReplaceBrowsingContext, + "Cannot specify group ID unless replacing BC"); + MOZ_DIAGNOSTIC_ASSERT(aPendingSwitchId || !IsTop(), + "Should always have aPendingSwitchId for top-level " + "frames"); + + if (!AncestorsAreCurrent()) { + NS_WARNING("An ancestor context is no longer current"); + return RemotenessPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + + // Ensure our embedder hasn't been destroyed already. + RefPtr embedderWindowGlobal = GetEmbedderWindowGlobal(); + if (!embedderWindowGlobal) { + NS_WARNING("Non-embedded BrowsingContext"); + return RemotenessPromise::CreateAndReject(NS_ERROR_UNEXPECTED, __func__); + } + + if (!embedderWindowGlobal->CanSend()) { + NS_WARNING("Embedder already been destroyed."); + return RemotenessPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__); + } + + if (aRemoteType.IsEmpty() && (!IsTop() || !GetEmbedderElement())) { + NS_WARNING("Cannot load non-remote subframes"); + return RemotenessPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + + // Cancel ongoing remoteness changes. + if (mPendingRemotenessChange) { + mPendingRemotenessChange->Cancel(NS_ERROR_ABORT); + MOZ_DIAGNOSTIC_ASSERT(!mPendingRemotenessChange, "Should have cleared"); + } + + RefPtr embedderBrowser = + embedderWindowGlobal->GetBrowserParent(); + // Switching to local. No new process, so perform switch sync. + if (embedderBrowser && + aRemoteType == embedderBrowser->Manager()->GetRemoteType()) { + MOZ_DIAGNOSTIC_ASSERT( + aPendingSwitchId, + "We always have a PendingSwitchId, except for print-preview loads, " + "which will never perform a process-switch to being in-process with " + "their embedder"); + if (GetCurrentWindowGlobal()) { + MOZ_DIAGNOSTIC_ASSERT(GetCurrentWindowGlobal()->IsProcessRoot()); + RefPtr oldBrowser = + GetCurrentWindowGlobal()->GetBrowserParent(); + + uint64_t targetProcessId = OwnerProcessId(); + SetInFlightProcessId(targetProcessId); + auto callback = [target = RefPtr{this}, targetProcessId](auto) { + target->ClearInFlightProcessId(targetProcessId); + }; + oldBrowser->SendWillChangeProcess(callback, callback); + oldBrowser->Destroy(); + } + + // If the embedder process is remote, tell that remote process to become + // the owner. + MOZ_DIAGNOSTIC_ASSERT(!aReplaceBrowsingContext); + MOZ_DIAGNOSTIC_ASSERT(!aRemoteType.IsEmpty()); + SetOwnerProcessId(embedderBrowser->Manager()->ChildID()); + Unused << embedderWindowGlobal->SendMakeFrameLocal(this, aPendingSwitchId); + return RemotenessPromise::CreateAndResolve(embedderBrowser, __func__); + } + + // Switching to remote. Wait for new process to launch before switch. + auto promise = MakeRefPtr(__func__); + RefPtr change = new PendingRemotenessChange( + this, promise, aPendingSwitchId, aReplaceBrowsingContext); + mPendingRemotenessChange = change; + + // If a specific BrowsingContextGroup ID was specified for this load, make + // sure to keep it alive until the process switch is completed. + if (aSpecificGroupId) { + change->mSpecificGroup = + BrowsingContextGroup::GetOrCreate(aSpecificGroupId); + change->mSpecificGroup->AddKeepAlive(); + } + + // Call `prepareToChangeRemoteness` in parallel with starting a new process + // for loads. + if (IsTop() && GetEmbedderElement()) { + nsCOMPtr browser = GetEmbedderElement()->AsBrowser(); + if (!browser) { + change->Cancel(NS_ERROR_FAILURE); + return promise.forget(); + } + + RefPtr blocker; + nsresult rv = browser->PrepareToChangeRemoteness(getter_AddRefs(blocker)); + if (NS_FAILED(rv)) { + change->Cancel(rv); + return promise.forget(); + } + change->mPrepareToChangePromise = GenericPromise::FromDomPromise(blocker); + } + + if (aRemoteType.IsEmpty()) { + change->ProcessReady(); + } else { + // Try to predict which BrowsingContextGroup will be used for the final load + // in this BrowsingContext. This has to be accurate if switching into an + // existing group, as it will control what pool of processes will be used + // for process selection. + // + // It's _technically_ OK to provide a group here if we're actually going to + // switch into a brand new group, though it's sub-optimal, as it can + // restrict the set of processes we're using. + BrowsingContextGroup* finalGroup = + aReplaceBrowsingContext ? change->mSpecificGroup.get() : Group(); + + change->mContentParent = ContentParent::GetNewOrUsedLaunchingBrowserProcess( + /* aRemoteType = */ aRemoteType, + /* aGroup = */ finalGroup, + /* aPriority = */ hal::PROCESS_PRIORITY_FOREGROUND, + /* aPreferUsed = */ false); + if (!change->mContentParent) { + change->Cancel(NS_ERROR_FAILURE); + return promise.forget(); + } + + // Add a KeepAlive used by this ContentParent, which will be cleared when + // the change is complete. This should prevent the process dying before + // we're ready to use it. + change->mContentParent->AddKeepAlive(); + change->mContentParent->WaitForLaunchAsync()->Then( + GetMainThreadSerialEventTarget(), __func__, + [change](ContentParent*) { change->ProcessReady(); }, + [change](LaunchError) { change->Cancel(NS_ERROR_FAILURE); }); + } + return promise.forget(); +} + +MediaController* CanonicalBrowsingContext::GetMediaController() { + // We would only create one media controller per tab, so accessing the + // controller via the top-level browsing context. + if (GetParent()) { + return Cast(Top())->GetMediaController(); + } + + MOZ_ASSERT(!GetParent(), + "Must access the controller from the top-level browsing context!"); + // Only content browsing context can create media controller, we won't create + // controller for chrome document, such as the browser UI. + if (!mTabMediaController && !IsDiscarded() && IsContent()) { + mTabMediaController = new MediaController(Id()); + } + return mTabMediaController; +} + +bool CanonicalBrowsingContext::HasCreatedMediaController() const { + return !!mTabMediaController; +} + +bool CanonicalBrowsingContext::SupportsLoadingInParent( + nsDocShellLoadState* aLoadState, uint64_t* aOuterWindowId) { + // We currently don't support initiating loads in the parent when they are + // watched by devtools. This is because devtools tracks loads using content + // process notifications, which happens after the load is initiated in this + // case. Devtools clears all prior requests when it detects a new navigation, + // so it drops the main document load that happened here. + if (WatchedByDevTools()) { + return false; + } + + // DocumentChannel currently only supports connecting channels into the + // content process, so we can only support schemes that will always be loaded + // there for now. Restrict to just http(s) for simplicity. + if (!net::SchemeIsHTTP(aLoadState->URI()) && + !net::SchemeIsHTTPS(aLoadState->URI())) { + return false; + } + + if (WindowGlobalParent* global = GetCurrentWindowGlobal()) { + nsCOMPtr currentURI = global->GetDocumentURI(); + if (currentURI) { + bool newURIHasRef = false; + aLoadState->URI()->GetHasRef(&newURIHasRef); + bool equalsExceptRef = false; + aLoadState->URI()->EqualsExceptRef(currentURI, &equalsExceptRef); + + if (equalsExceptRef && newURIHasRef) { + // This navigation is same-doc WRT the current one, we should pass it + // down to the docshell to be handled. + return false; + } + } + // If the current document has a beforeunload listener, then we need to + // start the load in that process after we fire the event. + if (global->HasBeforeUnload()) { + return false; + } + + *aOuterWindowId = global->OuterWindowId(); + } + return true; +} + +bool CanonicalBrowsingContext::LoadInParent(nsDocShellLoadState* aLoadState, + bool aSetNavigating) { + // We currently only support starting loads directly from the + // CanonicalBrowsingContext for top-level BCs. + // We currently only support starting loads directly from the + // CanonicalBrowsingContext for top-level BCs. + if (!IsTopContent() || !GetContentParent() || + !StaticPrefs::browser_tabs_documentchannel_parent_controlled()) { + return false; + } + + uint64_t outerWindowId = 0; + if (!SupportsLoadingInParent(aLoadState, &outerWindowId)) { + return false; + } + + // Note: If successful, this will recurse into StartDocumentLoad and + // set mCurrentLoad to the DocumentLoadListener instance created. + // Ideally in the future we will only start loads from here, and we can + // just set this directly instead. + return net::DocumentLoadListener::LoadInParent(this, aLoadState, + aSetNavigating); +} + +bool CanonicalBrowsingContext::AttemptSpeculativeLoadInParent( + nsDocShellLoadState* aLoadState) { + // We currently only support starting loads directly from the + // CanonicalBrowsingContext for top-level BCs. + // We currently only support starting loads directly from the + // CanonicalBrowsingContext for top-level BCs. + if (!IsTopContent() || !GetContentParent() || + StaticPrefs::browser_tabs_documentchannel_parent_controlled()) { + return false; + } + + uint64_t outerWindowId = 0; + if (!SupportsLoadingInParent(aLoadState, &outerWindowId)) { + return false; + } + + // Session-history-in-parent implementation relies currently on getting a + // round trip through a child process. + if (aLoadState->LoadIsFromSessionHistory()) { + return false; + } + + // If we successfully open the DocumentChannel, then it'll register + // itself using aLoadIdentifier and be kept alive until it completes + // loading. + return net::DocumentLoadListener::SpeculativeLoadInParent(this, aLoadState); +} + +bool CanonicalBrowsingContext::StartDocumentLoad( + net::DocumentLoadListener* aLoad) { + // If we're controlling loads from the parent, then starting a new load means + // that we need to cancel any existing ones. + if (StaticPrefs::browser_tabs_documentchannel_parent_controlled() && + mCurrentLoad) { + mCurrentLoad->Cancel(NS_BINDING_ABORTED); + } + mCurrentLoad = aLoad; + + if (NS_FAILED(SetCurrentLoadIdentifier(Some(aLoad->GetLoadIdentifier())))) { + mCurrentLoad = nullptr; + return false; + } + + return true; +} + +void CanonicalBrowsingContext::EndDocumentLoad(bool aForProcessSwitch) { + mCurrentLoad = nullptr; + + if (!aForProcessSwitch) { + // Resetting the current load identifier on a discarded context + // has no effect when a document load has finished. + Unused << SetCurrentLoadIdentifier(Nothing()); + } +} + +void CanonicalBrowsingContext::ResetSHEntryHasUserInteractionCache() { + WindowContext* topWc = GetTopWindowContext(); + if (topWc && !topWc->IsDiscarded()) { + MOZ_ALWAYS_SUCCEEDS(topWc->SetSHEntryHasUserInteraction(false)); + } +} + +void CanonicalBrowsingContext::HistoryCommitIndexAndLength() { + nsID changeID = {}; + CallerWillNotifyHistoryIndexAndLengthChanges caller(nullptr); + HistoryCommitIndexAndLength(changeID, caller); +} +void CanonicalBrowsingContext::HistoryCommitIndexAndLength( + const nsID& aChangeID, + const CallerWillNotifyHistoryIndexAndLengthChanges& aProofOfCaller) { + if (!IsTop()) { + Cast(Top())->HistoryCommitIndexAndLength(aChangeID, aProofOfCaller); + return; + } + + nsISHistory* shistory = GetSessionHistory(); + if (!shistory) { + return; + } + int32_t index = 0; + shistory->GetIndex(&index); + int32_t length = shistory->GetCount(); + + GetChildSessionHistory()->SetIndexAndLength(index, length, aChangeID); + + Group()->EachParent([&](ContentParent* aParent) { + Unused << aParent->SendHistoryCommitIndexAndLength(this, index, length, + aChangeID); + }); +} + +void CanonicalBrowsingContext::ResetScalingZoom() { + // This currently only ever gets called in the parent process, and we + // pass the message on to the WindowGlobalChild for the rootmost browsing + // context. + if (WindowGlobalParent* topWindow = GetTopWindowContext()) { + Unused << topWindow->SendResetScalingZoom(); + } +} + +void CanonicalBrowsingContext::SetContainerFeaturePolicy( + FeaturePolicy* aContainerFeaturePolicy) { + mContainerFeaturePolicy = aContainerFeaturePolicy; + + if (WindowGlobalParent* current = GetCurrentWindowGlobal()) { + Unused << current->SendSetContainerFeaturePolicy(mContainerFeaturePolicy); + } +} + +void CanonicalBrowsingContext::SetCrossGroupOpenerId(uint64_t aOpenerId) { + MOZ_DIAGNOSTIC_ASSERT(IsTopContent()); + MOZ_DIAGNOSTIC_ASSERT(mCrossGroupOpenerId == 0, + "Can only set CrossGroupOpenerId once"); + mCrossGroupOpenerId = aOpenerId; +} + +NS_IMPL_CYCLE_COLLECTION_INHERITED(CanonicalBrowsingContext, BrowsingContext, + mSessionHistory, mContainerFeaturePolicy) + +NS_IMPL_ADDREF_INHERITED(CanonicalBrowsingContext, BrowsingContext) +NS_IMPL_RELEASE_INHERITED(CanonicalBrowsingContext, BrowsingContext) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CanonicalBrowsingContext) +NS_INTERFACE_MAP_END_INHERITING(BrowsingContext) + +} // namespace dom +} // namespace mozilla diff --git a/docshell/base/CanonicalBrowsingContext.h b/docshell/base/CanonicalBrowsingContext.h new file mode 100644 index 0000000000..dd4226d7e1 --- /dev/null +++ b/docshell/base/CanonicalBrowsingContext.h @@ -0,0 +1,383 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_CanonicalBrowsingContext_h +#define mozilla_dom_CanonicalBrowsingContext_h + +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/MediaControlKeySource.h" +#include "mozilla/dom/BrowsingContextWebProgress.h" +#include "mozilla/dom/ipc/IdType.h" +#include "mozilla/RefPtr.h" +#include "mozilla/MozPromise.h" +#include "nsCycleCollectionParticipant.h" +#include "nsWrapperCache.h" +#include "nsTArray.h" +#include "nsTHashtable.h" +#include "nsHashKeys.h" +#include "nsISecureBrowserUI.h" + +class nsISHistory; +class nsIWidget; +class nsSHistory; +class nsBrowserStatusFilter; +class nsSecureBrowserUI; +class CallerWillNotifyHistoryIndexAndLengthChanges; + +namespace mozilla { +enum class CallState; + +namespace net { +class DocumentLoadListener; +} + +namespace dom { + +class BrowserParent; +class FeaturePolicy; +struct LoadURIOptions; +class MediaController; +struct LoadingSessionHistoryInfo; +class SessionHistoryEntry; +class WindowGlobalParent; + +// CanonicalBrowsingContext is a BrowsingContext living in the parent +// process, with whatever extra data that a BrowsingContext in the +// parent needs. +class CanonicalBrowsingContext final : public BrowsingContext { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(CanonicalBrowsingContext, + BrowsingContext) + + static already_AddRefed Get(uint64_t aId); + static CanonicalBrowsingContext* Cast(BrowsingContext* aContext); + static const CanonicalBrowsingContext* Cast(const BrowsingContext* aContext); + static already_AddRefed Cast( + already_AddRefed&& aContext); + + bool IsOwnedByProcess(uint64_t aProcessId) const { + return mProcessId == aProcessId; + } + bool IsEmbeddedInProcess(uint64_t aProcessId) const { + return mEmbedderProcessId == aProcessId; + } + uint64_t OwnerProcessId() const { return mProcessId; } + uint64_t EmbedderProcessId() const { return mEmbedderProcessId; } + ContentParent* GetContentParent() const; + + void GetCurrentRemoteType(nsACString& aRemoteType, ErrorResult& aRv) const; + + void SetOwnerProcessId(uint64_t aProcessId); + + void SetInFlightProcessId(uint64_t aProcessId); + void ClearInFlightProcessId(uint64_t aProcessId); + uint64_t GetInFlightProcessId() const { return mInFlightProcessId; } + + // The ID of the BrowsingContext which caused this BrowsingContext to be + // opened, or `0` if this is unknown. + // Only set for toplevel content BrowsingContexts, and may be from a different + // BrowsingContextGroup. + uint64_t GetCrossGroupOpenerId() const { return mCrossGroupOpenerId; } + void SetCrossGroupOpenerId(uint64_t aOpenerId); + + void GetWindowGlobals(nsTArray>& aWindows); + + // The current active WindowGlobal. + WindowGlobalParent* GetCurrentWindowGlobal() const; + + // Same as the methods on `BrowsingContext`, but with the types already cast + // to the parent process type. + CanonicalBrowsingContext* GetParent() { + return Cast(BrowsingContext::GetParent()); + } + CanonicalBrowsingContext* Top() { return Cast(BrowsingContext::Top()); } + WindowGlobalParent* GetParentWindowContext(); + WindowGlobalParent* GetTopWindowContext(); + + already_AddRefed GetParentProcessWidgetContaining(); + + // Same as `GetParentWindowContext`, but will also cross and + // content/chrome boundaries. + already_AddRefed GetEmbedderWindowGlobal() const; + + already_AddRefed GetParentCrossChromeBoundary(); + + already_AddRefed TopCrossChromeBoundary(); + Nullable GetTopChromeWindow(); + + nsISHistory* GetSessionHistory(); + SessionHistoryEntry* GetActiveSessionHistoryEntry(); + + UniquePtr CreateLoadingSessionHistoryEntryForLoad( + nsDocShellLoadState* aLoadState, nsIChannel* aChannel); + + UniquePtr ReplaceLoadingSessionHistoryEntryForLoad( + LoadingSessionHistoryInfo* aInfo, nsIChannel* aChannel); + + // Call the given callback on all top-level descendant BrowsingContexts. + // Return Callstate::Stop from the callback to stop calling + // further children. + void CallOnAllTopDescendants( + const std::function& + aCallback); + + void SessionHistoryCommit(uint64_t aLoadId, const nsID& aChangeID, + uint32_t aLoadType, bool aPersist, + bool aCloneEntryChildren); + + // Calls the session history listeners' OnHistoryReload, storing the result in + // aCanReload. If aCanReload is set to true and we have an active or a loading + // entry then aLoadState will be initialized from that entry, and + // aReloadActiveEntry will be true if we have an active entry. If aCanReload + // is true and aLoadState and aReloadActiveEntry are not set then we should + // attempt to reload based on the current document in the docshell. + void NotifyOnHistoryReload(bool aForceReload, bool& aCanReload, + Maybe>& aLoadState, + Maybe& aReloadActiveEntry); + + // See BrowsingContext::SetActiveSessionHistoryEntry. + void SetActiveSessionHistoryEntry(const Maybe& aPreviousScrollPos, + SessionHistoryInfo* aInfo, + uint32_t aLoadType, + uint32_t aUpdatedCacheKey, + const nsID& aChangeID); + + void ReplaceActiveSessionHistoryEntry(SessionHistoryInfo* aInfo); + + void RemoveDynEntriesFromActiveSessionHistoryEntry(); + + void RemoveFromSessionHistory(); + + void HistoryGo(int32_t aIndex, uint64_t aHistoryEpoch, + bool aRequireUserInteraction, + Maybe aContentId, + std::function&& aResolver); + + JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + + // Dispatches a wheel zoom change to the embedder element. + void DispatchWheelZoomChange(bool aIncrease); + + // This function is used to start the autoplay media which are delayed to + // start. If needed, it would also notify the content browsing context which + // are related with the canonical browsing content tree to start delayed + // autoplay media. + void NotifyStartDelayedAutoplayMedia(); + + // This function is used to mute or unmute all media within a tab. It would + // set the media mute property for the top level window and propagate it to + // other top level windows in other processes. + void NotifyMediaMutedChanged(bool aMuted, ErrorResult& aRv); + + // Return the number of unique site origins by iterating all given BCs, + // including their subtrees. + static uint32_t CountSiteOrigins( + GlobalObject& aGlobal, + const Sequence>& aRoots); + + // This function would propogate the action to its all child browsing contexts + // in content processes. + void UpdateMediaControlAction(const MediaControlAction& aAction); + + // Triggers a load in the process + using BrowsingContext::LoadURI; + void LoadURI(const nsAString& aURI, const LoadURIOptions& aOptions, + ErrorResult& aError); + + void GoBack(const Optional& aCancelContentJSEpoch, + bool aRequireUserInteraction); + void GoForward(const Optional& aCancelContentJSEpoch, + bool aRequireUserInteraction); + void GoToIndex(int32_t aIndex, + const Optional& aCancelContentJSEpoch); + void Reload(uint32_t aReloadFlags); + void Stop(uint32_t aStopFlags); + + BrowserParent* GetBrowserParent() const; + + // Internal method to change which process a BrowsingContext is being loaded + // in. The returned promise will resolve when the process switch is completed. + // + // A NOT_REMOTE_TYPE aRemoteType argument will perform a process switch into + // the parent process, and the method will resolve with a null BrowserParent. + using RemotenessPromise = MozPromise, nsresult, false>; + RefPtr ChangeRemoteness(const nsACString& aRemoteType, + uint64_t aPendingSwitchId, + bool aReplaceBrowsingContext, + uint64_t aSpecificGroupId); + + // Return a media controller from the top-level browsing context that can + // control all media belonging to this browsing context tree. Return nullptr + // if the top-level browsing context has been discarded. + MediaController* GetMediaController(); + bool HasCreatedMediaController() const; + + // Attempts to start loading the given load state in this BrowsingContext, + // without requiring any communication from a docshell. This will handle + // computing the right process to load in, and organising handoff to + // the right docshell when we get a response. + bool LoadInParent(nsDocShellLoadState* aLoadState, bool aSetNavigating); + + // Attempts to start loading the given load state in this BrowsingContext, + // in parallel with a DocumentChannelChild being created in the docshell. + // Requires the DocumentChannel to connect with this load for it to + // complete successfully. + bool AttemptSpeculativeLoadInParent(nsDocShellLoadState* aLoadState); + + // Get or create a secure browser UI for this BrowsingContext + nsISecureBrowserUI* GetSecureBrowserUI(); + + BrowsingContextWebProgress* GetWebProgress() { return mWebProgress; } + + // Called when the current URI changes (from an + // nsIWebProgressListener::OnLocationChange event, so that we + // can update our security UI for the new location, or when the + // mixed content/https-only state for our current window is changed. + void UpdateSecurityState(); + + void MaybeAddAsProgressListener(nsIWebProgress* aWebProgress); + + // Called when a navigation forces us to recreate our browsing + // context (for example, when switching in or out of the parent + // process). + // aNewContext is the newly created BrowsingContext that is replacing + // us. + void ReplacedBy(CanonicalBrowsingContext* aNewContext); + + bool HasHistoryEntry(nsISHEntry* aEntry); + + void SwapHistoryEntries(nsISHEntry* aOldEntry, nsISHEntry* aNewEntry); + + void AddLoadingSessionHistoryEntry(uint64_t aLoadId, + SessionHistoryEntry* aEntry); + + void GetLoadingSessionHistoryInfoFromParent( + Maybe& aLoadingInfo, int32_t* aRequestedIndex, + int32_t* aLength); + + void HistoryCommitIndexAndLength(); + + void ResetScalingZoom(); + + void SetContainerFeaturePolicy(FeaturePolicy* aContainerFeaturePolicy); + FeaturePolicy* GetContainerFeaturePolicy() const { + return mContainerFeaturePolicy; + } + + protected: + // Called when the browsing context is being discarded. + void CanonicalDiscard(); + + using Type = BrowsingContext::Type; + CanonicalBrowsingContext(WindowContext* aParentWindow, + BrowsingContextGroup* aGroup, + uint64_t aBrowsingContextId, + uint64_t aOwnerProcessId, + uint64_t aEmbedderProcessId, Type aType, + FieldValues&& aInit); + + private: + friend class BrowsingContext; + + ~CanonicalBrowsingContext() = default; + + class PendingRemotenessChange { + public: + NS_INLINE_DECL_REFCOUNTING(PendingRemotenessChange) + + PendingRemotenessChange(CanonicalBrowsingContext* aTarget, + RemotenessPromise::Private* aPromise, + uint64_t aPendingSwitchId, + bool aReplaceBrowsingContext); + + void Cancel(nsresult aRv); + + private: + friend class CanonicalBrowsingContext; + + ~PendingRemotenessChange(); + void ProcessReady(); + void Finish(); + void Clear(); + + RefPtr mTarget; + RefPtr mPromise; + RefPtr mPrepareToChangePromise; + RefPtr mContentParent; + RefPtr mSpecificGroup; + + uint64_t mPendingSwitchId; + bool mReplaceBrowsingContext; + }; + + friend class net::DocumentLoadListener; + // Called when a DocumentLoadListener is created to start a load for + // this browsing context. Returns false if a higher priority load is + // already in-progress and the new one has been rejected. + bool StartDocumentLoad(net::DocumentLoadListener* aLoad); + // Called once DocumentLoadListener completes handling a load, and it + // is either complete, or handed off to the final channel to deliver + // data to the destination docshell. + void EndDocumentLoad(bool aForProcessSwitch); + + bool SupportsLoadingInParent(nsDocShellLoadState* aLoadState, + uint64_t* aOuterWindowId); + + void HistoryCommitIndexAndLength( + const nsID& aChangeID, + const CallerWillNotifyHistoryIndexAndLengthChanges& aProofOfCaller); + + // XXX(farre): Store a ContentParent pointer here rather than mProcessId? + // Indicates which process owns the docshell. + uint64_t mProcessId; + + // Indicates which process owns the embedder element. + uint64_t mEmbedderProcessId; + + // The ID of the former owner process during an ownership change, which may + // have in-flight messages that assume it is still the owner. + uint64_t mInFlightProcessId = 0; + + uint64_t mCrossGroupOpenerId = 0; + + // This function will make the top window context reset its + // "SHEntryHasUserInteraction" cache that prevents documents from repeatedly + // setting user interaction on SH entries. Should be called anytime SH + // entries are added or replaced. + void ResetSHEntryHasUserInteractionCache(); + + // The current remoteness change which is in a pending state. + RefPtr mPendingRemotenessChange; + + RefPtr mSessionHistory; + + // Tab media controller is used to control all media existing in the same + // browsing context tree, so it would only exist in the top level browsing + // context. + RefPtr mTabMediaController; + + RefPtr mCurrentLoad; + + struct LoadingSessionHistoryEntry { + uint64_t mLoadId = 0; + RefPtr mEntry; + }; + nsTArray mLoadingEntries; + RefPtr mActiveEntry; + + RefPtr mSecureBrowserUI; + RefPtr mWebProgress; + RefPtr mStatusFilter; + + RefPtr mContainerFeaturePolicy; +}; + +} // namespace dom +} // namespace mozilla + +#endif // !defined(mozilla_dom_CanonicalBrowsingContext_h) diff --git a/docshell/base/ChildProcessChannelListener.cpp b/docshell/base/ChildProcessChannelListener.cpp new file mode 100644 index 0000000000..3580127f6a --- /dev/null +++ b/docshell/base/ChildProcessChannelListener.cpp @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/ChildProcessChannelListener.h" + +#include "mozilla/ipc/Endpoint.h" +#include "nsDocShellLoadState.h" + +namespace mozilla { +namespace dom { + +static StaticRefPtr sCPCLSingleton; + +void ChildProcessChannelListener::RegisterCallback(uint64_t aIdentifier, + Callback&& aCallback) { + if (auto args = mChannelArgs.GetAndRemove(aIdentifier)) { + nsresult rv = + aCallback(args->mLoadState, std::move(args->mStreamFilterEndpoints), + args->mTiming); + args->mResolver(rv); + } else { + mCallbacks.Put(aIdentifier, std::move(aCallback)); + } +} + +void ChildProcessChannelListener::OnChannelReady( + nsDocShellLoadState* aLoadState, uint64_t aIdentifier, + nsTArray&& aStreamFilterEndpoints, nsDOMNavigationTiming* aTiming, + Resolver&& aResolver) { + if (auto callback = mCallbacks.GetAndRemove(aIdentifier)) { + nsresult rv = + (*callback)(aLoadState, std::move(aStreamFilterEndpoints), aTiming); + aResolver(rv); + } else { + mChannelArgs.Put(aIdentifier, + {aLoadState, std::move(aStreamFilterEndpoints), aTiming, + std::move(aResolver)}); + } +} + +ChildProcessChannelListener::~ChildProcessChannelListener() { + for (auto& args : mChannelArgs) { + args.GetData().mResolver(NS_ERROR_FAILURE); + } +} + +already_AddRefed +ChildProcessChannelListener::GetSingleton() { + if (!sCPCLSingleton) { + sCPCLSingleton = new ChildProcessChannelListener(); + ClearOnShutdown(&sCPCLSingleton); + } + RefPtr cpcl = sCPCLSingleton; + return cpcl.forget(); +} + +} // namespace dom +} // namespace mozilla diff --git a/docshell/base/ChildProcessChannelListener.h b/docshell/base/ChildProcessChannelListener.h new file mode 100644 index 0000000000..232ca12dd5 --- /dev/null +++ b/docshell/base/ChildProcessChannelListener.h @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_ChildProcessChannelListener_h +#define mozilla_dom_ChildProcessChannelListener_h + +#include + +#include "mozilla/extensions/StreamFilterParent.h" +#include "mozilla/net/NeckoChannelParams.h" +#include "nsDOMNavigationTiming.h" +#include "nsDataHashtable.h" +#include "nsIChannel.h" +#include "BackgroundUtils.h" + +namespace mozilla { +namespace dom { + +class ChildProcessChannelListener final { + NS_INLINE_DECL_REFCOUNTING(ChildProcessChannelListener) + + using Endpoint = mozilla::ipc::Endpoint; + using Resolver = std::function; + using Callback = std::function&&, nsDOMNavigationTiming*)>; + + void RegisterCallback(uint64_t aIdentifier, Callback&& aCallback); + + void OnChannelReady(nsDocShellLoadState* aLoadState, uint64_t aIdentifier, + nsTArray&& aStreamFilterEndpoints, + nsDOMNavigationTiming* aTiming, Resolver&& aResolver); + + static already_AddRefed GetSingleton(); + + private: + ChildProcessChannelListener() = default; + ~ChildProcessChannelListener(); + struct CallbackArgs { + RefPtr mLoadState; + nsTArray mStreamFilterEndpoints; + RefPtr mTiming; + Resolver mResolver; + }; + + // TODO Backtrack. + nsDataHashtable mCallbacks; + nsDataHashtable mChannelArgs; +}; + +} // namespace dom +} // namespace mozilla + +#endif // !defined(mozilla_dom_ChildProcessChannelListener_h) diff --git a/docshell/base/IHistory.h b/docshell/base/IHistory.h new file mode 100644 index 0000000000..12609cc110 --- /dev/null +++ b/docshell/base/IHistory.h @@ -0,0 +1,150 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_IHistory_h_ +#define mozilla_IHistory_h_ + +#include "nsISupports.h" +#include "nsDataHashtable.h" +#include "nsURIHashKey.h" +#include "nsTObserverArray.h" + +class nsIURI; +class nsIWidget; + +namespace mozilla { + +namespace dom { +class Document; +class Link; +} // namespace dom + +// 0057c9d3-b98e-4933-bdc5-0275d06705e1 +#define IHISTORY_IID \ + { \ + 0x0057c9d3, 0xb98e, 0x4933, { \ + 0xbd, 0xc5, 0x02, 0x75, 0xd0, 0x67, 0x05, 0xe1 \ + } \ + } + +class IHistory : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(IHISTORY_IID) + + /** + * Registers the Link for notifications about the visited-ness of aURI. + * Consumers should assume that the URI is unvisited after calling this, and + * they will be notified if that state (unvisited) changes by having + * VisitedQueryFinished called on themselves. Note that it may call + * synchronously if the answer is already known. + * + * @note VisitedQueryFinished must not call RegisterVisitedCallback or + * UnregisterVisitedCallback. + * + * @pre aURI must not be null. + * @pre aLink may be null only in the parent (chrome) process. + * + * @param aURI + * The URI to check. + * @param aLink + * The link to update whenever the history status changes. The + * implementation will only hold onto a raw pointer, so if this + * object should be destroyed, be sure to call + * UnregisterVistedCallback first. + */ + virtual void RegisterVisitedCallback(nsIURI* aURI, dom::Link* aLink) = 0; + + /** + * Unregisters a previously registered Link object. This must be called + * before destroying the registered object, and asserts when misused. + * + * @pre aURI must not be null. + * @pre aLink must not be null. + * + * @param aURI + * The URI that aLink was registered for. + * @param aLink + * The link object to unregister for aURI. + */ + virtual void UnregisterVisitedCallback(nsIURI* aURI, dom::Link* aLink) = 0; + + enum class VisitedStatus : uint8_t { + Unknown, + Visited, + Unvisited, + }; + + /** + * Notifies about the visited status of a given URI. The visited status cannot + * be unknown, otherwise there's no point in notifying of anything. + */ + virtual void NotifyVisited(nsIURI*, VisitedStatus) = 0; + + enum VisitFlags { + /** + * Indicates whether the URI was loaded in a top-level window. + */ + TOP_LEVEL = 1 << 0, + /** + * Indicates whether the URI is the target of a permanent redirect. + */ + REDIRECT_PERMANENT = 1 << 1, + /** + * Indicates whether the URI is the target of a temporary redirect. + */ + REDIRECT_TEMPORARY = 1 << 2, + /** + * Indicates the URI will redirect (Response code 3xx). + */ + REDIRECT_SOURCE = 1 << 3, + /** + * Indicates the URI caused an error that is unlikely fixable by a + * retry, like a not found or unfetchable page. + */ + UNRECOVERABLE_ERROR = 1 << 4, + /** + * If REDIRECT_SOURCE is set, this indicates that the redirect is permanent. + * Note this differs from REDIRECT_PERMANENT because that one refers to how + * we reached the URI, while this is used when the URI itself redirects. + */ + REDIRECT_SOURCE_PERMANENT = 1 << 5 + }; + + /** + * Adds a history visit for the URI. + * + * @pre aURI must not be null. + * + * @param aWidget + * The widget for the DocShell. + * @param aURI + * The URI of the page being visited. + * @param aLastVisitedURI + * The URI of the last visit in the chain. + * @param aFlags + * The VisitFlags describing this visit. + */ + NS_IMETHOD VisitURI(nsIWidget* aWidget, nsIURI* aURI, nsIURI* aLastVisitedURI, + uint32_t aFlags) = 0; + + /** + * Set the title of the URI. + * + * @pre aURI must not be null. + * + * @param aURI + * The URI to set the title for. + * @param aTitle + * The title string. + */ + NS_IMETHOD SetURITitle(nsIURI* aURI, const nsAString& aTitle) = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(IHistory, IHISTORY_IID) + +} // namespace mozilla + +#endif // mozilla_IHistory_h_ diff --git a/docshell/base/LoadContext.cpp b/docshell/base/LoadContext.cpp new file mode 100644 index 0000000000..e90ad77f80 --- /dev/null +++ b/docshell/base/LoadContext.cpp @@ -0,0 +1,236 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Assertions.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/dom/ToJSValue.h" +#include "mozilla/LoadContext.h" +#include "mozilla/Preferences.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/ScriptSettings.h" // for AutoJSAPI +#include "mozilla/dom/BrowsingContext.h" +#include "nsContentUtils.h" +#include "xpcpublic.h" + +namespace mozilla { + +NS_IMPL_ISUPPORTS(LoadContext, nsILoadContext, nsIInterfaceRequestor) + +LoadContext::LoadContext(const IPC::SerializedLoadContext& aToCopy, + dom::Element* aTopFrameElement, + OriginAttributes& aAttrs) + : mTopFrameElement(do_GetWeakReference(aTopFrameElement)), + mIsContent(aToCopy.mIsContent), + mUseRemoteTabs(aToCopy.mUseRemoteTabs), + mUseRemoteSubframes(aToCopy.mUseRemoteSubframes), + mUseTrackingProtection(aToCopy.mUseTrackingProtection), +#ifdef DEBUG + mIsNotNull(aToCopy.mIsNotNull), +#endif + mOriginAttributes(aAttrs) { +} + +LoadContext::LoadContext(OriginAttributes& aAttrs) + : mTopFrameElement(nullptr), + mIsContent(false), + mUseRemoteTabs(false), + mUseRemoteSubframes(false), + mUseTrackingProtection(false), +#ifdef DEBUG + mIsNotNull(true), +#endif + mOriginAttributes(aAttrs) { +} + +LoadContext::LoadContext(nsIPrincipal* aPrincipal, + nsILoadContext* aOptionalBase) + : mTopFrameElement(nullptr), + mIsContent(true), + mUseRemoteTabs(false), + mUseRemoteSubframes(false), + mUseTrackingProtection(false), +#ifdef DEBUG + mIsNotNull(true), +#endif + mOriginAttributes(aPrincipal->OriginAttributesRef()) { + if (!aOptionalBase) { + return; + } + + MOZ_ALWAYS_SUCCEEDS(aOptionalBase->GetIsContent(&mIsContent)); + MOZ_ALWAYS_SUCCEEDS(aOptionalBase->GetUseRemoteTabs(&mUseRemoteTabs)); + MOZ_ALWAYS_SUCCEEDS( + aOptionalBase->GetUseRemoteSubframes(&mUseRemoteSubframes)); + MOZ_ALWAYS_SUCCEEDS( + aOptionalBase->GetUseTrackingProtection(&mUseTrackingProtection)); +} + +LoadContext::~LoadContext() = default; + +//----------------------------------------------------------------------------- +// LoadContext::nsILoadContext +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +LoadContext::GetAssociatedWindow(mozIDOMWindowProxy**) { + MOZ_ASSERT(mIsNotNull); + + // can't support this in the parent process + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +LoadContext::GetTopWindow(mozIDOMWindowProxy**) { + MOZ_ASSERT(mIsNotNull); + + // can't support this in the parent process + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +LoadContext::GetTopFrameElement(dom::Element** aElement) { + nsCOMPtr element = do_QueryReferent(mTopFrameElement); + element.forget(aElement); + return NS_OK; +} + +NS_IMETHODIMP +LoadContext::GetIsContent(bool* aIsContent) { + MOZ_ASSERT(mIsNotNull); + + NS_ENSURE_ARG_POINTER(aIsContent); + + *aIsContent = mIsContent; + return NS_OK; +} + +NS_IMETHODIMP +LoadContext::GetUsePrivateBrowsing(bool* aUsePrivateBrowsing) { + MOZ_ASSERT(mIsNotNull); + + NS_ENSURE_ARG_POINTER(aUsePrivateBrowsing); + + *aUsePrivateBrowsing = mOriginAttributes.mPrivateBrowsingId > 0; + return NS_OK; +} + +NS_IMETHODIMP +LoadContext::SetUsePrivateBrowsing(bool aUsePrivateBrowsing) { + MOZ_ASSERT(mIsNotNull); + + // We shouldn't need this on parent... + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +LoadContext::SetPrivateBrowsing(bool aUsePrivateBrowsing) { + MOZ_ASSERT(mIsNotNull); + + // We shouldn't need this on parent... + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +LoadContext::GetUseRemoteTabs(bool* aUseRemoteTabs) { + MOZ_ASSERT(mIsNotNull); + + NS_ENSURE_ARG_POINTER(aUseRemoteTabs); + + *aUseRemoteTabs = mUseRemoteTabs; + return NS_OK; +} + +NS_IMETHODIMP +LoadContext::SetRemoteTabs(bool aUseRemoteTabs) { + MOZ_ASSERT(mIsNotNull); + + // We shouldn't need this on parent... + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +LoadContext::GetUseRemoteSubframes(bool* aUseRemoteSubframes) { + MOZ_ASSERT(mIsNotNull); + + NS_ENSURE_ARG_POINTER(aUseRemoteSubframes); + + *aUseRemoteSubframes = mUseRemoteSubframes; + return NS_OK; +} + +NS_IMETHODIMP +LoadContext::SetRemoteSubframes(bool aUseRemoteSubframes) { + MOZ_ASSERT(mIsNotNull); + + // We shouldn't need this on parent... + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +LoadContext::GetScriptableOriginAttributes(JSContext* aCx, + JS::MutableHandleValue aAttrs) { + bool ok = ToJSValue(aCx, mOriginAttributes, aAttrs); + NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE); + return NS_OK; +} + +NS_IMETHODIMP_(void) +LoadContext::GetOriginAttributes(mozilla::OriginAttributes& aAttrs) { + aAttrs = mOriginAttributes; +} + +NS_IMETHODIMP +LoadContext::GetUseTrackingProtection(bool* aUseTrackingProtection) { + MOZ_ASSERT(mIsNotNull); + + NS_ENSURE_ARG_POINTER(aUseTrackingProtection); + + *aUseTrackingProtection = mUseTrackingProtection; + return NS_OK; +} + +NS_IMETHODIMP +LoadContext::SetUseTrackingProtection(bool aUseTrackingProtection) { + MOZ_ASSERT_UNREACHABLE("Should only be set through nsDocShell"); + + return NS_ERROR_UNEXPECTED; +} + +//----------------------------------------------------------------------------- +// LoadContext::nsIInterfaceRequestor +//----------------------------------------------------------------------------- +NS_IMETHODIMP +LoadContext::GetInterface(const nsIID& aIID, void** aResult) { + NS_ENSURE_ARG_POINTER(aResult); + *aResult = nullptr; + + if (aIID.Equals(NS_GET_IID(nsILoadContext))) { + *aResult = static_cast(this); + NS_ADDREF_THIS(); + return NS_OK; + } + + return NS_NOINTERFACE; +} + +static already_AddRefed CreateInstance(bool aPrivate) { + OriginAttributes oa; + oa.mPrivateBrowsingId = aPrivate ? 1 : 0; + + nsCOMPtr lc = new LoadContext(oa); + + return lc.forget(); +} + +already_AddRefed CreateLoadContext() { + return CreateInstance(false); +} + +already_AddRefed CreatePrivateLoadContext() { + return CreateInstance(true); +} + +} // namespace mozilla diff --git a/docshell/base/LoadContext.h b/docshell/base/LoadContext.h new file mode 100644 index 0000000000..5cb71ff347 --- /dev/null +++ b/docshell/base/LoadContext.h @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef LoadContext_h +#define LoadContext_h + +#include "SerializedLoadContext.h" +#include "mozilla/Attributes.h" +#include "mozilla/BasePrincipal.h" +#include "nsIWeakReferenceUtils.h" +#include "nsIInterfaceRequestor.h" +#include "nsILoadContext.h" + +namespace mozilla::dom { +class Element; +} + +namespace mozilla { + +/** + * Class that provides nsILoadContext info in Parent process. Typically copied + * from Child via SerializedLoadContext. + * + * Note: this is not the "normal" or "original" nsILoadContext. That is + * typically provided by BrowsingContext. This is only used when the original + * docshell is in a different process and we need to copy certain values from + * it. + */ + +class LoadContext final : public nsILoadContext, public nsIInterfaceRequestor { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSILOADCONTEXT + NS_DECL_NSIINTERFACEREQUESTOR + + LoadContext(const IPC::SerializedLoadContext& aToCopy, + dom::Element* aTopFrameElement, OriginAttributes& aAttrs); + + // Constructor taking reserved origin attributes. + explicit LoadContext(OriginAttributes& aAttrs); + + // Constructor for creating a LoadContext with a given browser flag. + explicit LoadContext(nsIPrincipal* aPrincipal, + nsILoadContext* aOptionalBase = nullptr); + + private: + ~LoadContext(); + + nsWeakPtr mTopFrameElement; + bool mIsContent; + bool mUseRemoteTabs; + bool mUseRemoteSubframes; + bool mUseTrackingProtection; +#ifdef DEBUG + bool mIsNotNull; +#endif + OriginAttributes mOriginAttributes; +}; + +already_AddRefed CreateLoadContext(); +already_AddRefed CreatePrivateLoadContext(); + +} // namespace mozilla + +#endif // LoadContext_h diff --git a/docshell/base/SerializedLoadContext.cpp b/docshell/base/SerializedLoadContext.cpp new file mode 100644 index 0000000000..cb598b43fe --- /dev/null +++ b/docshell/base/SerializedLoadContext.cpp @@ -0,0 +1,87 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SerializedLoadContext.h" +#include "nsNetUtil.h" +#include "nsIChannel.h" +#include "nsILoadContext.h" +#include "nsIPrivateBrowsingChannel.h" +#include "nsIWebSocketChannel.h" + +namespace IPC { + +SerializedLoadContext::SerializedLoadContext(nsILoadContext* aLoadContext) + : mIsContent(false), + mUseRemoteTabs(false), + mUseRemoteSubframes(false), + mUseTrackingProtection(false) { + Init(aLoadContext); +} + +SerializedLoadContext::SerializedLoadContext(nsIChannel* aChannel) + : mIsContent(false), + mUseRemoteTabs(false), + mUseRemoteSubframes(false), + mUseTrackingProtection(false) { + if (!aChannel) { + Init(nullptr); + return; + } + + nsCOMPtr loadContext; + NS_QueryNotificationCallbacks(aChannel, loadContext); + Init(loadContext); + + if (!loadContext) { + // Attempt to retrieve the private bit from the channel if it has been + // overriden. + bool isPrivate = false; + bool isOverriden = false; + nsCOMPtr pbChannel = do_QueryInterface(aChannel); + if (pbChannel && + NS_SUCCEEDED( + pbChannel->IsPrivateModeOverriden(&isPrivate, &isOverriden)) && + isOverriden) { + mIsPrivateBitValid = true; + } + mOriginAttributes.SyncAttributesWithPrivateBrowsing(isPrivate); + } +} + +SerializedLoadContext::SerializedLoadContext(nsIWebSocketChannel* aChannel) + : mIsContent(false), + mUseRemoteTabs(false), + mUseRemoteSubframes(false), + mUseTrackingProtection(false) { + nsCOMPtr loadContext; + if (aChannel) { + NS_QueryNotificationCallbacks(aChannel, loadContext); + } + Init(loadContext); +} + +void SerializedLoadContext::Init(nsILoadContext* aLoadContext) { + if (aLoadContext) { + mIsNotNull = true; + mIsPrivateBitValid = true; + aLoadContext->GetIsContent(&mIsContent); + aLoadContext->GetUseRemoteTabs(&mUseRemoteTabs); + aLoadContext->GetUseRemoteSubframes(&mUseRemoteSubframes); + aLoadContext->GetUseTrackingProtection(&mUseTrackingProtection); + aLoadContext->GetOriginAttributes(mOriginAttributes); + } else { + mIsNotNull = false; + mIsPrivateBitValid = false; + // none of below values really matter when mIsNotNull == false: + // we won't be GetInterfaced to nsILoadContext + mIsContent = true; + mUseRemoteTabs = false; + mUseRemoteSubframes = false; + mUseTrackingProtection = false; + } +} + +} // namespace IPC diff --git a/docshell/base/SerializedLoadContext.h b/docshell/base/SerializedLoadContext.h new file mode 100644 index 0000000000..689934f714 --- /dev/null +++ b/docshell/base/SerializedLoadContext.h @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef SerializedLoadContext_h +#define SerializedLoadContext_h + +#include "base/basictypes.h" +#include "ipc/IPCMessageUtils.h" +#include "ipc/IPCMessageUtilsSpecializations.h" +#include "mozilla/BasePrincipal.h" + +class nsILoadContext; + +/* + * This file contains the IPC::SerializedLoadContext class, which is used to + * copy data across IPDL from Child process contexts so it is available in the + * Parent. + */ + +class nsIChannel; +class nsIWebSocketChannel; + +namespace IPC { + +class SerializedLoadContext { + public: + SerializedLoadContext() + : mIsNotNull(false), + mIsPrivateBitValid(false), + mIsContent(false), + mUseRemoteTabs(false), + mUseRemoteSubframes(false), + mUseTrackingProtection(false) { + Init(nullptr); + } + + explicit SerializedLoadContext(nsILoadContext* aLoadContext); + explicit SerializedLoadContext(nsIChannel* aChannel); + explicit SerializedLoadContext(nsIWebSocketChannel* aChannel); + + void Init(nsILoadContext* aLoadContext); + + bool IsNotNull() const { return mIsNotNull; } + bool IsPrivateBitValid() const { return mIsPrivateBitValid; } + + // used to indicate if child-side LoadContext * was null. + bool mIsNotNull; + // used to indicate if child-side mUsePrivateBrowsing flag is valid, even if + // mIsNotNull is false, i.e., child LoadContext was null. + bool mIsPrivateBitValid; + bool mIsContent; + bool mUseRemoteTabs; + bool mUseRemoteSubframes; + bool mUseTrackingProtection; + mozilla::OriginAttributes mOriginAttributes; +}; + +// Function to serialize over IPDL +template <> +struct ParamTraits { + typedef SerializedLoadContext paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + nsAutoCString suffix; + aParam.mOriginAttributes.CreateSuffix(suffix); + + WriteParam(aMsg, aParam.mIsNotNull); + WriteParam(aMsg, aParam.mIsContent); + WriteParam(aMsg, aParam.mIsPrivateBitValid); + WriteParam(aMsg, aParam.mUseRemoteTabs); + WriteParam(aMsg, aParam.mUseRemoteSubframes); + WriteParam(aMsg, aParam.mUseTrackingProtection); + WriteParam(aMsg, suffix); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + nsAutoCString suffix; + if (!ReadParam(aMsg, aIter, &aResult->mIsNotNull) || + !ReadParam(aMsg, aIter, &aResult->mIsContent) || + !ReadParam(aMsg, aIter, &aResult->mIsPrivateBitValid) || + !ReadParam(aMsg, aIter, &aResult->mUseRemoteTabs) || + !ReadParam(aMsg, aIter, &aResult->mUseRemoteSubframes) || + !ReadParam(aMsg, aIter, &aResult->mUseTrackingProtection) || + !ReadParam(aMsg, aIter, &suffix)) { + return false; + } + return aResult->mOriginAttributes.PopulateFromSuffix(suffix); + } +}; + +} // namespace IPC + +#endif // SerializedLoadContext_h diff --git a/docshell/base/SyncedContext.h b/docshell/base/SyncedContext.h new file mode 100644 index 0000000000..e9ad5017cf --- /dev/null +++ b/docshell/base/SyncedContext.h @@ -0,0 +1,333 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_SyncedContext_h +#define mozilla_dom_SyncedContext_h + +#include +#include +#include +#include "mozilla/Attributes.h" +#include "mozilla/EnumSet.h" +#include "nsStringFwd.h" +#include "nscore.h" + +// Referenced via macro definitions +#include "mozilla/ErrorResult.h" + +class PickleIterator; + +namespace IPC { +class Message; +} // namespace IPC + +namespace mozilla { +namespace ipc { +class IProtocol; +class IPCResult; +template +struct IPDLParamTraits; +} // namespace ipc + +namespace dom { +class ContentParent; +class ContentChild; +template +class MaybeDiscarded; + +namespace syncedcontext { + +template +using Index = typename std::integral_constant; + +using IndexSet = EnumSet; + +template +class Transaction { + public: + // Set a field at the given index in this `Transaction`. Creating a + // `Transaction` object and setting multiple fields on it allows for + // multiple mutations to be performed atomically. + template + void Set(U&& aValue) { + mValues.Get(Index{}) = std::forward(aValue); + mModified += I; + } + + // Apply the changes from this transaction to the specified Context in all + // processes. This method will call the correct `CanSet` and `DidSet` methods, + // as well as move the value. + // + // If the target has been discarded, changes will be ignored. + // + // NOTE: This method mutates `this`, clearing the modified field set. + [[nodiscard]] nsresult Commit(Context* aOwner); + + // Called from `ContentParent` in response to a transaction from content. + mozilla::ipc::IPCResult CommitFromIPC(const MaybeDiscarded& aOwner, + ContentParent* aSource); + + // Called from `ContentChild` in response to a transaction from the parent. + mozilla::ipc::IPCResult CommitFromIPC(const MaybeDiscarded& aOwner, + uint64_t aEpoch, ContentChild* aSource); + + private: + friend struct mozilla::ipc::IPDLParamTraits>; + + void Write(IPC::Message* aMsg, mozilla::ipc::IProtocol* aActor) const; + bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + mozilla::ipc::IProtocol* aActor); + + // You probably don't want to directly call this method - instead call + // `Commit`, which will perform the necessary synchronization. + // + // `Validate` must be called before calling this method. + void Apply(Context* aOwner); + + // Returns the set of fields which failed to validate, or an empty set if + // there were no validation errors. + IndexSet Validate(Context* aOwner, ContentParent* aSource); + + template + static void EachIndex(F&& aCallback) { + Context::FieldValues::EachIndex(aCallback); + } + + template + static uint64_t& FieldEpoch(Index, Context* aContext) { + return std::get(aContext->mFields.mEpochs); + } + + typename Context::FieldValues mValues; + IndexSet mModified; +}; + +template +class FieldValues : public Base { + public: + // The number of fields stored by this type. + static constexpr size_t count = Count; + static_assert(count < 64, + "At most 64 synced fields are supported. Please file a bug if " + "you need additional fields."); + + // The base type will define a series of `Get` methods for looking up a field + // by its field index. + using Base::Get; + + // Calls a generic lambda with an `Index` for each index less than the + // field count. + template + static void EachIndex(F&& aCallback) { + EachIndexInner(std::make_index_sequence(), + std::forward(aCallback)); + } + + private: + friend struct mozilla::ipc::IPDLParamTraits>; + + void Write(IPC::Message* aMsg, mozilla::ipc::IProtocol* aActor) const; + bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + mozilla::ipc::IProtocol* aActor); + + template + static void EachIndexInner(std::index_sequence aIndexes, + F&& aCallback) { + (aCallback(Index()), ...); + } +}; + +// Storage related to synchronized context fields. Contains both a tuple of +// individual field values, and epoch information for field synchronization. +template +class FieldStorage { + public: + // Unsafely grab a reference directly to the internal values structure which + // can be modified without telling other processes about the change. + // + // This is only sound in specific code which is already messaging other + // processes, and doesn't need to worry about epochs or other properties of + // field synchronization. + Values& RawValues() { return mValues; } + const Values& RawValues() const { return mValues; } + + // Get an individual field by index. + template + const auto& Get() const { + return RawValues().Get(Index{}); + } + + // Set the value of a field without telling other processes about the change. + // + // This is only sound in specific code which is already messaging other + // processes, and doesn't need to worry about epochs or other properties of + // field synchronization. + template + void SetWithoutSyncing(U&& aValue) { + GetNonSyncingReference() = std::move(aValue); + } + + // Get a reference to a field that can modify without telling other + // processes about the change. + // + // This is only sound in specific code which is already messaging other + // processes, and doesn't need to worry about epochs or other properties of + // field synchronization. + template + auto& GetNonSyncingReference() { + return RawValues().Get(Index{}); + } + + FieldStorage() = default; + explicit FieldStorage(Values&& aInit) : mValues(std::move(aInit)) {} + + private: + template + friend class Transaction; + + // Data Members + std::array mEpochs{}; + Values mValues; +}; + +// Helper type traits to use concrete types rather than generic forwarding +// references for the `SetXXX` methods defined on the synced context type. +// +// This helps avoid potential issues where someone accidentally declares an +// overload of these methods with slightly different types and different +// behaviours. See bug 1659520. +template +struct GetFieldSetterType { + using SetterArg = T; +}; +template <> +struct GetFieldSetterType { + using SetterArg = const nsAString&; +}; +template <> +struct GetFieldSetterType { + using SetterArg = const nsACString&; +}; +template +using FieldSetterType = typename GetFieldSetterType::SetterArg; + +#define MOZ_DECL_SYNCED_CONTEXT_FIELD_INDEX(name, type) IDX_##name, +#define MOZ_DECL_SYNCED_CONTEXT_FIELDS_DECL(name, type) \ + /* index based field lookup */ \ + type& Get(FieldIndex) { return m##name; } \ + const type& Get(FieldIndex) const { return m##name; } \ + \ + /* storage for the field */ \ + type m##name{}; +#define MOZ_DECL_SYNCED_CONTEXT_FIELD_GETSET(name, type) \ + const type& Get##name() const { return mFields.template Get(); } \ + \ + [[nodiscard]] nsresult Set##name( \ + ::mozilla::dom::syncedcontext::FieldSetterType aValue) { \ + Transaction txn; \ + txn.template Set(std::move(aValue)); \ + return txn.Commit(this); \ + } \ + void Set##name(::mozilla::dom::syncedcontext::FieldSetterType aValue, \ + ErrorResult& aRv) { \ + nsresult rv = this->Set##name(std::move(aValue)); \ + if (NS_FAILED(rv)) { \ + aRv.ThrowInvalidStateError("cannot set synced field '" #name \ + "': context is discarded"); \ + } \ + } + +#define MOZ_DECL_SYNCED_CONTEXT_TRANSACTION_SET(name, type) \ + template \ + void Set##name(U&& aValue) { \ + this->template Set(std::forward(aValue)); \ + } +#define MOZ_DECL_SYNCED_CONTEXT_INDEX_TO_NAME(name, type) \ + case IDX_##name: \ + return #name; + +// Declare a type as a synced context type. +// +// clazz is the name of the type being declared, and `eachfield` is a macro +// which, when called with the name of the macro, will call that macro once for +// each field in the synced context. +#define MOZ_DECL_SYNCED_CONTEXT(clazz, eachfield) \ + public: \ + /* Index constants for referring to each field in generic code */ \ + enum FieldIndexes { \ + eachfield(MOZ_DECL_SYNCED_CONTEXT_FIELD_INDEX) SYNCED_FIELD_COUNT \ + }; \ + \ + /* Helper for overloading methods like `CanSet` and `DidSet` */ \ + template \ + using FieldIndex = typename ::mozilla::dom::syncedcontext::Index; \ + \ + /* Struct containing the data for all synced fields as members */ \ + struct BaseFieldValues { \ + eachfield(MOZ_DECL_SYNCED_CONTEXT_FIELDS_DECL) \ + }; \ + using FieldValues = \ + typename ::mozilla::dom::syncedcontext::FieldValues; \ + \ + protected: \ + friend class ::mozilla::dom::syncedcontext::Transaction; \ + ::mozilla::dom::syncedcontext::FieldStorage mFields; \ + \ + public: \ + /* Transaction types for bulk mutations */ \ + using BaseTransaction = ::mozilla::dom::syncedcontext::Transaction; \ + class Transaction final : public BaseTransaction { \ + public: \ + eachfield(MOZ_DECL_SYNCED_CONTEXT_TRANSACTION_SET) \ + }; \ + \ + /* Field name getter by field index */ \ + static const char* FieldIndexToName(size_t aIndex) { \ + switch (aIndex) { eachfield(MOZ_DECL_SYNCED_CONTEXT_INDEX_TO_NAME) } \ + return ""; \ + } \ + eachfield(MOZ_DECL_SYNCED_CONTEXT_FIELD_GETSET) + +} // namespace syncedcontext +} // namespace dom + +namespace ipc { + +template +struct IPDLParamTraits> { + typedef dom::syncedcontext::Transaction paramType; + + static void Write(IPC::Message* aMsg, IProtocol* aActor, + const paramType& aParam) { + aParam.Write(aMsg, aActor); + } + + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, paramType* aResult) { + return aResult->Read(aMsg, aIter, aActor); + } +}; + +template +struct IPDLParamTraits> { + typedef dom::syncedcontext::FieldValues paramType; + + static void Write(IPC::Message* aMsg, IProtocol* aActor, + const paramType& aParam) { + aParam.Write(aMsg, aActor); + } + + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, paramType* aResult) { + return aResult->Read(aMsg, aIter, aActor); + } +}; + +} // namespace ipc +} // namespace mozilla + +#endif // !defined(mozilla_dom_SyncedContext_h) diff --git a/docshell/base/SyncedContextInlines.h b/docshell/base/SyncedContextInlines.h new file mode 100644 index 0000000000..d4fda40162 --- /dev/null +++ b/docshell/base/SyncedContextInlines.h @@ -0,0 +1,213 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_SyncedContextInlines_h +#define mozilla_dom_SyncedContextInlines_h + +#include "mozilla/dom/SyncedContext.h" +#include "mozilla/dom/BrowsingContextGroup.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/ContentChild.h" +#include "nsReadableUtils.h" + +namespace mozilla { +namespace dom { +namespace syncedcontext { + +template +nsCString FormatValidationError(IndexSet aFailedFields, const char* prefix) { + MOZ_ASSERT(!aFailedFields.isEmpty()); + return nsDependentCString{prefix} + + StringJoin(", "_ns, aFailedFields, + [](nsACString& dest, const auto& idx) { + dest.Append(Context::FieldIndexToName(idx)); + }); +} + +template +nsresult Transaction::Commit(Context* aOwner) { + if (NS_WARN_IF(aOwner->IsDiscarded())) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + IndexSet failedFields = Validate(aOwner, nullptr); + if (!failedFields.isEmpty()) { + nsCString error = FormatValidationError( + failedFields, "CanSet failed for field(s): "); + MOZ_CRASH_UNSAFE_PRINTF("%s", error.get()); + } + + if (XRE_IsContentProcess()) { + ContentChild* cc = ContentChild::GetSingleton(); + + // Increment the field epoch for fields affected by this transaction. + uint64_t epoch = cc->NextBrowsingContextFieldEpoch(); + EachIndex([&](auto idx) { + if (mModified.contains(idx)) { + FieldEpoch(idx, aOwner) = epoch; + } + }); + + // Tell our derived class to send the correct "Commit" IPC message. + aOwner->SendCommitTransaction(cc, *this, epoch); + } else { + MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess()); + + // Tell our derived class to send the correct "Commit" IPC messages. + BrowsingContextGroup* group = aOwner->Group(); + group->EachParent([&](ContentParent* aParent) { + aOwner->SendCommitTransaction(aParent, *this, + aParent->GetBrowsingContextFieldEpoch()); + }); + } + + Apply(aOwner); + return NS_OK; +} + +template +mozilla::ipc::IPCResult Transaction::CommitFromIPC( + const MaybeDiscarded& aOwner, ContentParent* aSource) { + MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess()); + if (aOwner.IsNullOrDiscarded()) { + MOZ_LOG(Context::GetLog(), LogLevel::Debug, + ("IPC: Trying to send a message to dead or detached context")); + return IPC_OK(); + } + Context* owner = aOwner.get(); + + // Validate that the set from content is allowed before continuing. + IndexSet failedFields = Validate(owner, aSource); + if (!failedFields.isEmpty()) { + nsCString error = FormatValidationError( + failedFields, + "Invalid Transaction from Child - CanSet failed for field(s): "); + return IPC_FAIL(aSource, error.get()); + } + + BrowsingContextGroup* group = owner->Group(); + group->EachOtherParent(aSource, [&](ContentParent* aParent) { + owner->SendCommitTransaction(aParent, *this, + aParent->GetBrowsingContextFieldEpoch()); + }); + + Apply(owner); + return IPC_OK(); +} + +template +mozilla::ipc::IPCResult Transaction::CommitFromIPC( + const MaybeDiscarded& aOwner, uint64_t aEpoch, + ContentChild* aSource) { + MOZ_DIAGNOSTIC_ASSERT(XRE_IsContentProcess()); + if (aOwner.IsNullOrDiscarded()) { + MOZ_LOG(Context::GetLog(), LogLevel::Debug, + ("ChildIPC: Trying to send a message to dead or detached context")); + return IPC_OK(); + } + Context* owner = aOwner.get(); + + // Clear any fields which have been obsoleted by the epoch. + EachIndex([&](auto idx) { + if (mModified.contains(idx) && FieldEpoch(idx, owner) > aEpoch) { + mModified -= idx; + } + }); + + Apply(owner); + return IPC_OK(); +} + +template +void Transaction::Apply(Context* aOwner) { + EachIndex([&](auto idx) { + if (mModified.contains(idx)) { + auto& txnField = mValues.Get(idx); + auto& ownerField = aOwner->mFields.mValues.Get(idx); + std::swap(ownerField, txnField); + aOwner->DidSet(idx); + aOwner->DidSet(idx, std::move(txnField)); + } + }); + mModified.clear(); +} + +template +IndexSet Transaction::Validate(Context* aOwner, + ContentParent* aSource) { + IndexSet failedFields; + // Validate that the set from content is allowed before continuing. + EachIndex([&](auto idx) { + if (mModified.contains(idx) && + NS_WARN_IF(!aOwner->CanSet(idx, mValues.Get(idx), aSource))) { + failedFields += idx; + } + }); + return failedFields; +} + +template +void Transaction::Write(IPC::Message* aMsg, + mozilla::ipc::IProtocol* aActor) const { + // Record which field indices will be included, and then write those fields + // out. + uint64_t modified = mModified.serialize(); + WriteIPDLParam(aMsg, aActor, modified); + EachIndex([&](auto idx) { + if (mModified.contains(idx)) { + WriteIPDLParam(aMsg, aActor, mValues.Get(idx)); + } + }); +} + +template +bool Transaction::Read(const IPC::Message* aMsg, PickleIterator* aIter, + mozilla::ipc::IProtocol* aActor) { + // Read in which field indices were sent by the remote, followed by the fields + // identified by those indices. + uint64_t modified = 0; + if (!ReadIPDLParam(aMsg, aIter, aActor, &modified)) { + return false; + } + mModified.deserialize(modified); + + bool ok = true; + EachIndex([&](auto idx) { + if (ok && mModified.contains(idx)) { + ok = ReadIPDLParam(aMsg, aIter, aActor, &mValues.Get(idx)); + } + }); + return ok; +} + +template +void FieldValues::Write(IPC::Message* aMsg, + mozilla::ipc::IProtocol* aActor) const { + // XXX The this-> qualification is necessary to work around a bug in older gcc + // versions causing an ICE. + EachIndex([&](auto idx) { WriteIPDLParam(aMsg, aActor, this->Get(idx)); }); +} + +template +bool FieldValues::Read(const IPC::Message* aMsg, + PickleIterator* aIter, + mozilla::ipc::IProtocol* aActor) { + bool ok = true; + EachIndex([&](auto idx) { + if (ok) { + // XXX The this-> qualification is necessary to work around a bug in older + // gcc versions causing an ICE. + ok = ReadIPDLParam(aMsg, aIter, aActor, &this->Get(idx)); + } + }); + return ok; +} + +} // namespace syncedcontext +} // namespace dom +} // namespace mozilla + +#endif // !defined(mozilla_dom_SyncedContextInlines_h) diff --git a/docshell/base/URIFixup.jsm b/docshell/base/URIFixup.jsm new file mode 100644 index 0000000000..797b1ed981 --- /dev/null +++ b/docshell/base/URIFixup.jsm @@ -0,0 +1,1054 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- + * vim: sw=2 ts=2 sts=2 expandtab + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This component handles fixing up URIs, by correcting obvious typos and adding + * missing schemes. + * URI references: + * http://www.faqs.org/rfcs/rfc1738.html + * http://www.faqs.org/rfcs/rfc2396.html + */ + +// TODO (Bug 1641220) getFixupURIInfo has a complex logic, that likely could be +// simplified, but the risk of regressing its behavior is high. +/* eslint complexity: ["error", 43] */ + +var EXPORTED_SYMBOLS = ["URIFixup", "URIFixupInfo"]; + +const { ComponentUtils } = ChromeUtils.import( + "resource://gre/modules/ComponentUtils.jsm" +); +const { XPCOMUtils } = ChromeUtils.import( + "resource://gre/modules/XPCOMUtils.jsm" +); +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +const { AppConstants } = ChromeUtils.import( + "resource://gre/modules/AppConstants.jsm" +); + +XPCOMUtils.defineLazyServiceGetter( + this, + "externalProtocolService", + "@mozilla.org/uriloader/external-protocol-service;1", + "nsIExternalProtocolService" +); + +XPCOMUtils.defineLazyServiceGetter( + this, + "defaultProtocolHandler", + "@mozilla.org/network/protocol;1?name=default", + "nsIProtocolHandler" +); + +XPCOMUtils.defineLazyServiceGetter( + this, + "fileProtocolHandler", + "@mozilla.org/network/protocol;1?name=file", + "nsIFileProtocolHandler" +); + +XPCOMUtils.defineLazyPreferenceGetter( + this, + "fixupSchemeTypos", + "browser.fixup.typo.scheme", + true +); +XPCOMUtils.defineLazyPreferenceGetter( + this, + "dnsFirstForSingleWords", + "browser.fixup.dns_first_for_single_words", + false +); +XPCOMUtils.defineLazyPreferenceGetter( + this, + "keywordEnabled", + "keyword.enabled", + true +); +XPCOMUtils.defineLazyPreferenceGetter( + this, + "alternateEnabled", + "browser.fixup.alternate.enabled", + true +); +XPCOMUtils.defineLazyPreferenceGetter( + this, + "alternateProtocol", + "browser.fixup.alternate.protocol", + "https" +); + +const { + FIXUP_FLAG_NONE, + FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP, + FIXUP_FLAGS_MAKE_ALTERNATE_URI, + FIXUP_FLAG_PRIVATE_CONTEXT, + FIXUP_FLAG_FIX_SCHEME_TYPOS, +} = Ci.nsIURIFixup; + +const COMMON_PROTOCOLS = ["http", "https", "ftp", "file"]; + +// Regex used to identify user:password tokens in url strings. +// This is not a strict valid characters check, because we try to fixup this +// part of the url too. +XPCOMUtils.defineLazyGetter( + this, + "userPasswordRegex", + () => /^([a-z+.-]+:\/{0,3})*[^\/@]+@.+/i +); + +// Regex used to identify specific URI characteristics to disallow searching. +XPCOMUtils.defineLazyGetter( + this, + "uriLikeRegex", + () => /(:\d{1,5}([?#/]|$)|\/.*[?#])/ +); + +// Regex used to identify numbers. +XPCOMUtils.defineLazyGetter(this, "numberRegex", () => /^[0-9]+(\.[0-9]+)?$/); + +// Regex used to identify tab separated content (having at least 2 tabs). +XPCOMUtils.defineLazyGetter(this, "maxOneTabRegex", () => /^[^\t]*\t?[^\t]*$/); + +// Regex used to test if a string with a protocol might instead be a url +// without a protocol but with a port: +// +// : or +// :/ +// +// Where is a string of alphanumeric characters and dashes +// separated by dots. +// and is a 5 or less digits. This actually breaks the rfc2396 +// definition of a scheme which allows dots in schemes. +// +// Note: +// People expecting this to work with +// :@:/ will be disappointed! +// +// Note: Parser could be a lot tighter, tossing out silly hostnames +// such as those containing consecutive dots and so on. +XPCOMUtils.defineLazyGetter( + this, + "possiblyHostPortRegex", + () => /^[a-z0-9-]+(\.[a-z0-9-]+)*:[0-9]{1,5}([/?#]|$)/i +); + +// Regex used to strip newlines. +XPCOMUtils.defineLazyGetter(this, "newLinesRegex", () => /[\r\n]/g); + +// Regex used to match a possible protocol. +// This resembles the logic in Services.io.extractScheme, thus \t is admitted +// and stripped later. We don't use Services.io.extractScheme because of +// performance bottleneck caused by crossing XPConnect. +XPCOMUtils.defineLazyGetter( + this, + "possibleProtocolRegex", + () => /^([a-z][a-z0-9.+\t-]*)(:|;)?(\/\/)?/i +); + +// Regex used to match IPs. Note that these are not made to validate IPs, but +// just to detect strings that look like an IP. They also skip protocol. +// For IPv4 this also accepts a shorthand format with just 2 dots. +XPCOMUtils.defineLazyGetter( + this, + "IPv4LikeRegex", + () => /^(?:[a-z+.-]+:\/*(?!\/))?(?:\d{1,3}\.){2,3}\d{1,3}(?::\d+|\/)?/i +); +XPCOMUtils.defineLazyGetter( + this, + "IPv6LikeRegex", + () => + /^(?:[a-z+.-]+:\/*(?!\/))?\[(?:[0-9a-f]{0,4}:){0,7}[0-9a-f]{0,4}\]?(?::\d+|\/)?/i +); + +// Cache of known domains. +XPCOMUtils.defineLazyGetter(this, "knownDomains", () => { + const branch = "browser.fixup.domainwhitelist."; + let domains = new Set( + Services.prefs + .getChildList(branch) + .filter(p => Services.prefs.getBoolPref(p, false)) + .map(p => p.substring(branch.length)) + ); + // Hold onto the observer to avoid it being GC-ed. + domains._observer = { + observe(subject, topic, data) { + let domain = data.substring(branch.length); + if (Services.prefs.getBoolPref(data, false)) { + domains.add(domain); + } else { + domains.delete(domain); + } + }, + QueryInterface: ChromeUtils.generateQI([ + "nsIObserver", + "nsISupportsWeakReference", + ]), + }; + Services.prefs.addObserver(branch, domains._observer, true); + return domains; +}); + +// Cache of known suffixes. +// This works differently from the known domains, because when we examine a +// domain we can't tell how many dot-separated parts constitute the suffix. +// We create a Map keyed by the last dotted part, containing a Set of +// all the suffixes ending with that part: +// "two" => ["two"] +// "three" => ["some.three", "three"] +// When searching we can restrict the linear scan based on the last part. +// The ideal structure for this would be a Directed Acyclic Word Graph, but +// since we expect this list to be small it's not worth the complication. +XPCOMUtils.defineLazyGetter(this, "knownSuffixes", () => { + const branch = "browser.fixup.domainsuffixwhitelist."; + let suffixes = new Map(); + let prefs = Services.prefs + .getChildList(branch) + .filter(p => Services.prefs.getBoolPref(p, false)); + for (let pref of prefs) { + let suffix = pref.substring(branch.length); + let lastPart = suffix.substr(suffix.lastIndexOf(".") + 1); + if (lastPart) { + let entries = suffixes.get(lastPart); + if (!entries) { + entries = new Set(); + suffixes.set(lastPart, entries); + } + entries.add(suffix); + } + } + // Hold onto the observer to avoid it being GC-ed. + suffixes._observer = { + observe(subject, topic, data) { + let suffix = data.substring(branch.length); + let lastPart = suffix.substr(suffix.lastIndexOf(".") + 1); + let entries = suffixes.get(lastPart); + if (Services.prefs.getBoolPref(data, false)) { + // Add the suffix. + if (!entries) { + entries = new Set(); + suffixes.set(lastPart, entries); + } + entries.add(suffix); + } else if (entries) { + // Remove the suffix. + entries.delete(suffix); + if (!entries.size) { + suffixes.delete(lastPart); + } + } + }, + QueryInterface: ChromeUtils.generateQI([ + "nsIObserver", + "nsISupportsWeakReference", + ]), + }; + Services.prefs.addObserver(branch, suffixes._observer, true); + return suffixes; +}); + +function URIFixup() {} + +URIFixup.prototype = { + get FIXUP_FLAG_NONE() { + return FIXUP_FLAG_NONE; + }, + get FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP() { + return FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP; + }, + get FIXUP_FLAGS_MAKE_ALTERNATE_URI() { + return FIXUP_FLAGS_MAKE_ALTERNATE_URI; + }, + get FIXUP_FLAG_PRIVATE_CONTEXT() { + return FIXUP_FLAG_PRIVATE_CONTEXT; + }, + get FIXUP_FLAG_FIX_SCHEME_TYPOS() { + return FIXUP_FLAG_FIX_SCHEME_TYPOS; + }, + + getFixupURIInfo(uriString, fixupFlags = FIXUP_FLAG_NONE) { + let isPrivateContext = fixupFlags & FIXUP_FLAG_PRIVATE_CONTEXT; + + // Eliminate embedded newlines, which single-line text fields now allow, + // and cleanup the empty spaces and tabs that might be on each end. + uriString = uriString.trim().replace(newLinesRegex, ""); + + if (!uriString) { + throw new Components.Exception( + "Should pass a non-null uri", + Cr.NS_ERROR_FAILURE + ); + } + + let info = new URIFixupInfo(uriString); + + const { + scheme, + fixedSchemeUriString, + fixupChangedProtocol, + } = extractScheme(uriString, fixupFlags); + uriString = fixedSchemeUriString; + info.fixupChangedProtocol = fixupChangedProtocol; + + if (scheme == "view-source") { + let { preferredURI, postData } = fixupViewSource(uriString, fixupFlags); + info.preferredURI = info.fixedURI = preferredURI; + info.postData = postData; + return info; + } + + if (scheme.length < 2) { + // Check if it is a file path. We skip most schemes because the only case + // where a file path may look like having a scheme is "X:" on Windows. + let fileURI = fileURIFixup(uriString); + if (fileURI) { + info.preferredURI = info.fixedURI = fileURI; + info.fixupChangedProtocol = true; + return info; + } + } + + const isCommonProtocol = COMMON_PROTOCOLS.includes(scheme); + + let canHandleProtocol = + scheme && + (isCommonProtocol || + Services.io.getProtocolHandler(scheme) != defaultProtocolHandler || + externalProtocolService.externalProtocolHandlerExists(scheme)); + + if ( + canHandleProtocol || + // If it's an unknown handler and the given URL looks like host:port or + // has a user:password we can't pass it to the external protocol handler. + // We'll instead try fixing it with http later. + (!possiblyHostPortRegex.test(uriString) && + !userPasswordRegex.test(uriString)) + ) { + // Just try to create an URL out of it. + try { + info.fixedURI = Services.io.newURI(uriString); + } catch (ex) { + if (ex.result != Cr.NS_ERROR_MALFORMED_URI) { + throw ex; + } + } + } + + // We're dealing with a theoretically valid URI but we have no idea how to + // load it. (e.g. "christmas:humbug") + // It's more likely the user wants to search, and so we chuck this over to + // their preferred search provider. + // TODO (Bug 1588118): Should check FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP + // instead of FIXUP_FLAG_FIX_SCHEME_TYPOS. + if ( + info.fixedURI && + keywordEnabled && + fixupFlags & FIXUP_FLAG_FIX_SCHEME_TYPOS && + scheme && + !canHandleProtocol + ) { + tryKeywordFixupForURIInfo(uriString, info, isPrivateContext); + } + + if (info.fixedURI) { + if (!info.preferredURI) { + maybeSetAlternateFixedURI(info, fixupFlags); + info.preferredURI = info.fixedURI; + } + return info; + } + + // Fix up protocol string before calling KeywordURIFixup, because + // it cares about the hostname of such URIs. + // Prune duff protocol schemes: + // ://totallybroken.url.com + // //shorthand.url.com + let inputHadDuffProtocol = + uriString.startsWith("://") || uriString.startsWith("//"); + if (inputHadDuffProtocol) { + uriString = uriString.replace(/^:?\/\//, ""); + } + + // Avoid fixing up content that looks like tab-separated values. + // Assume that 1 tab is accidental, but more than 1 implies this is + // supposed to be tab-separated content. + if (!isCommonProtocol && maxOneTabRegex.test(uriString)) { + let uriWithProtocol = fixupURIProtocol(uriString); + if (uriWithProtocol) { + info.fixedURI = uriWithProtocol; + info.fixupChangedProtocol = true; + maybeSetAlternateFixedURI(info, fixupFlags); + info.preferredURI = info.fixedURI; + // Check if it's a forced visit. The user can enforce a visit by + // appending a slash, but the string must be in a valid uri format. + if (uriString.endsWith("/")) { + return info; + } + } + } + + // Handle "www." as a URI. + const asciiHost = info.fixedURI?.asciiHost; + if ( + asciiHost?.length > 4 && + asciiHost?.startsWith("www.") && + asciiHost?.lastIndexOf(".") == 3 + ) { + return info; + } + + // Memoize the public suffix check, since it may be expensive and should + // only run once when necessary. + let suffixInfo; + function checkSuffix(info) { + if (!suffixInfo) { + suffixInfo = checkAndFixPublicSuffix(info); + } + return suffixInfo; + } + + // See if it is a keyword and whether a keyword must be fixed up. + if ( + keywordEnabled && + fixupFlags & FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP && + !inputHadDuffProtocol && + !checkSuffix(info).suffix && + keywordURIFixup(uriString, info, isPrivateContext) + ) { + return info; + } + + if ( + info.fixedURI && + (!info.fixupChangedProtocol || !checkSuffix(info).hasUnknownSuffix) + ) { + return info; + } + + // If we still haven't been able to construct a valid URI, try to force a + // keyword match. + if (keywordEnabled && fixupFlags & FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP) { + tryKeywordFixupForURIInfo(info.originalInput, info, isPrivateContext); + } + + if (!info.preferredURI) { + // We couldn't salvage anything. + throw new Components.Exception( + "Couldn't build a valid uri", + Cr.NS_ERROR_MALFORMED_URI + ); + } + + return info; + }, + + webNavigationFlagsToFixupFlags(href, navigationFlags) { + try { + Services.io.newURI(href); + // Remove LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP for valid uris. + navigationFlags &= ~Ci.nsIWebNavigation + .LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP; + } catch (ex) {} + + let fixupFlags = FIXUP_FLAG_NONE; + if ( + navigationFlags & Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP + ) { + fixupFlags |= FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP; + } + if (navigationFlags & Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS) { + fixupFlags |= FIXUP_FLAG_FIX_SCHEME_TYPOS; + } + return fixupFlags; + }, + + keywordToURI(keyword, isPrivateContext) { + if (Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT) { + // There's no search service in the content process, thus all the calls + // from it that care about keywords conversion should go through the + // parent process. + throw new Components.Exception( + "Can't invoke URIFixup in the content process", + Cr.NS_ERROR_NOT_AVAILABLE + ); + } + let info = new URIFixupInfo(keyword); + + // Strip leading "?" and leading/trailing spaces from aKeyword + if (keyword.startsWith("?")) { + keyword = keyword.substring(1); + } + keyword = keyword.trim(); + + // Try falling back to the search service's default search engine + // We must use an appropriate search engine depending on the private + // context. + let engine = isPrivateContext + ? Services.search.defaultPrivateEngine + : Services.search.defaultEngine; + + // We allow default search plugins to specify alternate parameters that are + // specific to keyword searches. + let responseType = null; + if (engine.supportsResponseType("application/x-moz-keywordsearch")) { + responseType = "application/x-moz-keywordsearch"; + } + let submission = engine.getSubmission(keyword, responseType, "keyword"); + if ( + !submission || + // For security reasons (avoid redirecting to file, data, or other unsafe + // protocols) we only allow fixup to http/https search engines. + !submission.uri.scheme.startsWith("http") + ) { + throw new Components.Exception( + "Invalid search submission uri", + Cr.NS_ERROR_NOT_AVAILABLE + ); + } + let submissionPostDataStream = submission.postData; + if (submissionPostDataStream) { + info.postData = submissionPostDataStream; + } + + info.keywordProviderName = engine.name; + info.keywordAsSent = keyword; + info.preferredURI = submission.uri; + return info; + }, + + isDomainKnown, + + classID: Components.ID("{c6cf88b7-452e-47eb-bdc9-86e3561648ef}"), + _xpcom_factory: ComponentUtils.generateSingletonFactory(URIFixup), + QueryInterface: ChromeUtils.generateQI(["nsIURIFixup"]), +}; + +function URIFixupInfo(originalInput = "") { + this._originalInput = originalInput; +} + +URIFixupInfo.prototype = { + set consumer(consumer) { + this._consumer = consumer || null; + }, + get consumer() { + return this._consumer || null; + }, + + set preferredURI(uri) { + this._preferredURI = uri; + }, + get preferredURI() { + return this._preferredURI || null; + }, + + set fixedURI(uri) { + this._fixedURI = uri; + }, + get fixedURI() { + return this._fixedURI || null; + }, + + set keywordProviderName(name) { + this._keywordProviderName = name; + }, + get keywordProviderName() { + return this._keywordProviderName || ""; + }, + + set keywordAsSent(keyword) { + this._keywordAsSent = keyword; + }, + get keywordAsSent() { + return this._keywordAsSent || ""; + }, + + set fixupChangedProtocol(changed) { + this._fixupChangedProtocol = changed; + }, + get fixupChangedProtocol() { + return !!this._fixupChangedProtocol; + }, + + set fixupCreatedAlternateURI(changed) { + this._fixupCreatedAlternateURI = changed; + }, + get fixupCreatedAlternateURI() { + return !!this._fixupCreatedAlternateURI; + }, + + set originalInput(input) { + this._originalInput = input; + }, + get originalInput() { + return this._originalInput || ""; + }, + + set postData(postData) { + this._postData = postData; + }, + get postData() { + return this._postData || null; + }, + + classID: Components.ID("{33d75835-722f-42c0-89cc-44f328e56a86}"), + QueryInterface: ChromeUtils.generateQI(["nsIURIFixupInfo"]), +}; + +// Helpers + +/** + * Implementation of isDomainKnown, so we don't have to go through the + * service. + * @param {string} asciiHost + * @returns {boolean} whether the domain is known + */ +function isDomainKnown(asciiHost) { + if (dnsFirstForSingleWords) { + return true; + } + // Check if this domain is known as an actual + // domain (which will prevent a keyword query) + // Note that any processing of the host here should stay in sync with + // code in the front-end(s) that set the pref. + let lastDotIndex = asciiHost.lastIndexOf("."); + if (lastDotIndex == asciiHost.length - 1) { + asciiHost = asciiHost.substring(0, asciiHost.length - 1); + lastDotIndex = asciiHost.lastIndexOf("."); + } + if (knownDomains.has(asciiHost.toLowerCase())) { + return true; + } + // If there's no dot or only a leading dot we are done, otherwise we'll check + // against the known suffixes. + if (lastDotIndex <= 0) { + return false; + } + // Don't use getPublicSuffix here, since the suffix is not in the PSL, + // thus it couldn't tell if the suffix is made up of one or multiple + // dot-separated parts. + let lastPart = asciiHost.substr(lastDotIndex + 1); + let suffixes = knownSuffixes.get(lastPart); + if (suffixes) { + return Array.from(suffixes).some(s => asciiHost.endsWith(s)); + } + return false; +} + +/** + * Checks the suffix of info.fixedURI against the Public Suffix List. + * If the suffix is unknown due to a typo this will try to fix it up. + * @param {URIFixupInfo} info about the uri to check. + * @note this may modify the public suffix of info.fixedURI. + * @returns {object} result The lookup result. + * @returns {string} result.suffix The public suffix if one can be identified. + * @returns {boolean} result.hasUnknownSuffix True when the suffix is not in the + * Public Suffix List and it's not in knownSuffixes. False in the other cases. + */ +function checkAndFixPublicSuffix(info) { + let uri = info.fixedURI; + let asciiHost = uri?.asciiHost; + if ( + !asciiHost || + !asciiHost.includes(".") || + asciiHost.endsWith(".") || + isDomainKnown(asciiHost) + ) { + return { suffix: "", hasUnknownSuffix: false }; + } + + // Quick bailouts for most common cases, according to Alexa Top 1 million. + if ( + asciiHost.endsWith(".com") || + asciiHost.endsWith(".net") || + asciiHost.endsWith(".org") || + asciiHost.endsWith(".ru") || + asciiHost.endsWith(".de") + ) { + return { + suffix: asciiHost.substring(asciiHost.lastIndexOf(".") + 1), + hasUnknownSuffix: false, + }; + } + try { + let suffix = Services.eTLD.getKnownPublicSuffix(uri); + if (suffix) { + return { suffix, hasUnknownSuffix: false }; + } + } catch (ex) { + return { suffix: "", hasUnknownSuffix: false }; + } + // Suffix is unknown, try to fix most common 3 chars TLDs typos. + // .com is the most commonly mistyped tld, so it has more cases. + let suffix = Services.eTLD.getPublicSuffix(uri); + if (!suffix || numberRegex.test(suffix)) { + return { suffix: "", hasUnknownSuffix: false }; + } + for (let [typo, fixed] of [ + ["ocm", "com"], + ["con", "com"], + ["cmo", "com"], + ["xom", "com"], + ["vom", "com"], + ["cpm", "com"], + ["com'", "com"], + ["ent", "net"], + ["ner", "net"], + ["nte", "net"], + ["met", "net"], + ["rog", "org"], + ["ogr", "org"], + ["prg", "org"], + ["orh", "org"], + ]) { + if (suffix == typo) { + let host = uri.host.substring(0, uri.host.length - typo.length) + fixed; + let updatePreferredURI = info.preferredURI == info.fixedURI; + info.fixedURI = uri + .mutate() + .setHost(host) + .finalize(); + if (updatePreferredURI) { + info.preferredURI = info.fixedURI; + } + return { suffix: fixed, hasUnknownSuffix: false }; + } + } + return { suffix: "", hasUnknownSuffix: true }; +} + +function tryKeywordFixupForURIInfo(uriString, fixupInfo, isPrivateContext) { + try { + let keywordInfo = Services.uriFixup.keywordToURI( + uriString, + isPrivateContext + ); + fixupInfo.keywordProviderName = keywordInfo.keywordProviderName; + fixupInfo.keywordAsSent = keywordInfo.keywordAsSent; + fixupInfo.preferredURI = keywordInfo.preferredURI; + return true; + } catch (ex) {} + return false; +} + +/** + * This generates an alternate fixedURI, by adding a prefix and a suffix to + * the fixedURI host, if and only if the protocol is http. It should _never_ + * modify URIs with other protocols. + * @param {URIFixupInfo} info an URIInfo object + * @param {integer} fixupFlags the fixup flags + * @returns {boolean} Whether an alternate uri was generated + */ +function maybeSetAlternateFixedURI(info, fixupFlags) { + let uri = info.fixedURI; + if ( + !(fixupFlags & FIXUP_FLAGS_MAKE_ALTERNATE_URI) || + !alternateEnabled || + // Code only works for http. Not for any other protocol including https! + !uri.schemeIs("http") || + // Security - URLs with user / password info should NOT be fixed up + uri.userPass || + // Don't fix up hosts with ports + uri.port != -1 + ) { + return false; + } + + let oldHost = uri.host; + // Don't create an alternate uri for localhost, because it would be confusing. + if (oldHost == "localhost") { + return false; + } + + // Get the prefix and suffix to stick onto the new hostname. By default these + // are www. & .com but they could be any other value, e.g. www. & .org + let prefix = Services.prefs.getCharPref( + "browser.fixup.alternate.prefix", + "www." + ); + let suffix = Services.prefs.getCharPref( + "browser.fixup.alternate.suffix", + ".com" + ); + + let newHost = ""; + let numDots = (oldHost.match(/\./g) || []).length; + if (numDots == 0) { + newHost = prefix + oldHost + suffix; + } else if (numDots == 1) { + if (prefix && oldHost == prefix) { + newHost = oldHost + suffix; + } else if (suffix && !oldHost.startsWith(prefix)) { + newHost = prefix + oldHost; + } + } + if (!newHost) { + return false; + } + + // Assign the new host string over the old one + try { + info.fixedURI = uri + .mutate() + .setScheme(alternateProtocol) + .setHost(newHost) + .finalize(); + } catch (ex) { + if (ex.result != Cr.NS_ERROR_MALFORMED_URI) { + throw ex; + } + return false; + } + info.fixupCreatedAlternateURI = true; + return true; +} + +/** + * Try to fixup a file URI. + * @param {string} uriString The file URI to fix. + * @returns {nsIURI} a fixed uri or null. + * @note FileURIFixup only returns a URI if it has to add the file: protocol. + */ +function fileURIFixup(uriString) { + let attemptFixup = false; + if (AppConstants.platform == "win") { + // Check for "\"" in the url-string or just a drive (e.g. C:). + attemptFixup = + uriString.includes("\\") || + (uriString.length == 2 && uriString.endsWith(":")); + } else { + // UNIX: Check if it starts with "/". + attemptFixup = uriString.startsWith("/"); + } + if (attemptFixup) { + try { + // Test if this is a valid path by trying to create a local file + // object. The URL of that is returned if successful. + let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + file.initWithPath(uriString); + return Services.io.newURI( + fileProtocolHandler.getURLSpecFromActualFile(file) + ); + } catch (ex) { + // Not a file uri. + } + } + return null; +} + +/** + * Tries to fixup a string to an nsIURI by adding the default protocol. + * + * Should fix things like: + * no-scheme.com + * ftp.no-scheme.com + * ftp4.no-scheme.com + * no-scheme.com/query?foo=http://www.foo.com + * user:pass@no-scheme.com + * + * @param {string} uriString The string to fixup. + * @returns {nsIURI} an nsIURI built adding the default protocol to the string, + * or null if fixing was not possible. + */ +function fixupURIProtocol(uriString) { + let schemePos = uriString.indexOf("://"); + if (schemePos == -1 || schemePos > uriString.search(/[:\/]/)) { + uriString = "http://" + uriString; + } + try { + return Services.io.newURI(uriString); + } catch (ex) { + // We generated an invalid uri. + } + return null; +} + +/** + * Tries to fixup a string to a search url. + * @param {string} uriString the string to fixup. + * @param {URIFixupInfo} fixupInfo The fixup info object, modified in-place. + * @param {boolean} isPrivateContext Whether this happens in a private context. + * @param {nsIInputStream} postData optional POST data for the search + * @returns {boolean} Whether the keyword fixup was succesful. + */ +function keywordURIFixup(uriString, fixupInfo, isPrivateContext) { + // Here is a few examples of strings that should be searched: + // "what is mozilla" + // "what is mozilla?" + // "docshell site:mozilla.org" - has a space in the origin part + // "?site:mozilla.org - anything that begins with a question mark + // "mozilla'.org" - Things that have a quote before the first dot/colon + // "mozilla/test" - unknown host + // ".mozilla", "mozilla." - starts or ends with a dot () + + // These other strings should not be searched, because they could be URIs: + // "www.blah.com" - Domain with a standard or known suffix + // "knowndomain" - known domain + // "nonQualifiedHost:8888?something" - has a port + // "user@nonQualifiedHost" + // "blah.com." + + // We do keyword lookups if the input starts with a question mark. + if (uriString.startsWith("?")) { + return tryKeywordFixupForURIInfo( + fixupInfo.originalInput, + fixupInfo, + isPrivateContext + ); + } + + // Check for IPs. + if (IPv4LikeRegex.test(uriString) || IPv6LikeRegex.test(uriString)) { + return false; + } + + // Avoid lookup if we can identify a host and it's known, or ends with + // a dot and has some path. + // Note that if dnsFirstForSingleWords is true isDomainKnown will always + // return true, so we can avoid checking dnsFirstForSingleWords after this. + let asciiHost = fixupInfo.fixedURI?.asciiHost; + if ( + asciiHost && + (isDomainKnown(asciiHost) || + (asciiHost.endsWith(".") && + asciiHost.indexOf(".") != asciiHost.length - 1)) + ) { + return false; + } + + // Even if the host is invalid, avoid lookup if the string has uri-like + // characteristics. + // Also avoid lookup if there's a valid userPass. We only check for spaces, + // the URI parser has encoded any disallowed chars at this point, but if the + // user typed spaces before the first @, it's unlikely a valid userPass, plus + // some urlbar features use the @ char and we don't want to break them. + let userPass = fixupInfo.fixedURI?.userPass; + if ( + !uriLikeRegex.test(uriString) && + !(userPass && /^[^\s@]+@/.test(uriString)) + ) { + return tryKeywordFixupForURIInfo( + fixupInfo.originalInput, + fixupInfo, + isPrivateContext + ); + } + + return false; +} + +/** + * Mimics the logic in Services.io.extractScheme, but avoids crossing XPConnect. + * This also tries to fixup the scheme if it was clearly mistyped. + * @param {string} uriString the string to examine + * @param {integer} fixupFlags The original fixup flags + * @returns {object} + * scheme: a typo fixed scheme or empty string if one could not be identified + * fixedSchemeUriString: uri string with a typo fixed scheme + * fixupChangedProtocol: true if the scheme is fixed up + */ +function extractScheme(uriString, fixupFlags = FIXUP_FLAG_NONE) { + const matches = uriString.match(possibleProtocolRegex); + const hasColon = matches?.[2] === ":"; + const hasSlash2 = matches?.[3] === "//"; + + const isFixupSchemeTypos = + fixupSchemeTypos && fixupFlags & FIXUP_FLAG_FIX_SCHEME_TYPOS; + + if ( + !matches || + (!hasColon && !hasSlash2) || + (!hasColon && !isFixupSchemeTypos) + ) { + return { + scheme: "", + fixedSchemeUriString: uriString, + fixupChangedProtocol: false, + }; + } + + let scheme = matches[1].replace("\t", "").toLowerCase(); + let fixedSchemeUriString = uriString; + + if (isFixupSchemeTypos && hasSlash2) { + // Fix up typos for string that user would have intented as protocol. + const afterProtocol = uriString.substring(matches[0].length); + fixedSchemeUriString = `${scheme}://${afterProtocol}`; + } + + let fixupChangedProtocol = false; + + if (isFixupSchemeTypos) { + // Fix up common scheme typos. + // TODO: Use levenshtein distance here? + fixupChangedProtocol = [ + ["ttp", "http"], + ["htp", "http"], + ["ttps", "https"], + ["tps", "https"], + ["ps", "https"], + ["htps", "https"], + ["ile", "file"], + ["le", "file"], + ].some(([typo, fixed]) => { + if (scheme === typo) { + scheme = fixed; + fixedSchemeUriString = + scheme + fixedSchemeUriString.substring(typo.length); + return true; + } + return false; + }); + } + + return { + scheme, + fixedSchemeUriString, + fixupChangedProtocol, + }; +} + +/** + * View-source is a pseudo scheme. We're interested in fixing up the stuff + * after it. The easiest way to do that is to call this method again with + * the "view-source:" lopped off and then prepend it again afterwards. + * @param {string} uriString The original string to fixup + * @param {integer} fixupFlags The original fixup flags + * @param {nsIInputStream} postData Optional POST data for the search + * @returns {object} {preferredURI, postData} The fixed URI and relative postData + * @throws if it's not possible to fixup the url + */ +function fixupViewSource(uriString, fixupFlags) { + // We disable keyword lookup and alternate URIs so that small typos don't + // cause us to look at very different domains. + let newFixupFlags = + fixupFlags & + ~FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP & + ~FIXUP_FLAGS_MAKE_ALTERNATE_URI; + + let innerURIString = uriString.substring(12).trim(); + + // Prevent recursion. + const { scheme: innerScheme } = extractScheme(innerURIString); + if (innerScheme == "view-source") { + throw new Components.Exception( + "Prevent view-source recursion", + Cr.NS_ERROR_FAILURE + ); + } + + let info = Services.uriFixup.getFixupURIInfo(innerURIString, newFixupFlags); + if (!info.preferredURI) { + throw new Components.Exception( + "Couldn't build a valid uri", + Cr.NS_ERROR_MALFORMED_URI + ); + } + return { + preferredURI: Services.io.newURI("view-source:" + info.preferredURI.spec), + postData: info.postData, + }; +} diff --git a/docshell/base/WindowContext.cpp b/docshell/base/WindowContext.cpp new file mode 100644 index 0000000000..4d5b1e1781 --- /dev/null +++ b/docshell/base/WindowContext.cpp @@ -0,0 +1,569 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/WindowContext.h" +#include "mozilla/dom/WindowGlobalActorsBinding.h" +#include "mozilla/dom/WindowGlobalChild.h" +#include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/dom/SyncedContextInlines.h" +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/UserActivationIPCUtils.h" +#include "mozilla/PermissionDelegateIPCUtils.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/ClearOnShutdown.h" +#include "nsGlobalWindowInner.h" +#include "nsIScriptError.h" +#include "nsRefPtrHashtable.h" +#include "nsContentUtils.h" + +namespace mozilla { +namespace dom { + +// Explicit specialization of the `Transaction` type. Required by the `extern +// template class` declaration in the header. +template class syncedcontext::Transaction; + +static LazyLogModule gWindowContextLog("WindowContext"); + +extern mozilla::LazyLogModule gUserInteractionPRLog; + +#define USER_ACTIVATION_LOG(msg, ...) \ + MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug, (msg, ##__VA_ARGS__)) + +using WindowContextByIdMap = nsDataHashtable; +static StaticAutoPtr gWindowContexts; + +/* static */ +LogModule* WindowContext::GetLog() { return gWindowContextLog; } + +/* static */ +already_AddRefed WindowContext::GetById( + uint64_t aInnerWindowId) { + if (!gWindowContexts) { + return nullptr; + } + return do_AddRef(gWindowContexts->Get(aInnerWindowId)); +} + +BrowsingContextGroup* WindowContext::Group() const { + return mBrowsingContext->Group(); +} + +WindowGlobalParent* WindowContext::Canonical() { + MOZ_RELEASE_ASSERT(XRE_IsParentProcess()); + return static_cast(this); +} + +bool WindowContext::IsCached() const { + return mBrowsingContext->mCurrentWindowContext != this; +} + +nsGlobalWindowInner* WindowContext::GetInnerWindow() const { + if (mInProcess) { + // FIXME: Replace this with something more efficient. + return nsGlobalWindowInner::GetInnerWindowWithId(mInnerWindowId); + } + return nullptr; +} + +Document* WindowContext::GetDocument() const { + nsGlobalWindowInner* innerWindow = GetInnerWindow(); + return innerWindow ? innerWindow->GetDocument() : nullptr; +} + +Document* WindowContext::GetExtantDoc() const { + nsGlobalWindowInner* innerWindow = GetInnerWindow(); + return innerWindow ? innerWindow->GetExtantDoc() : nullptr; +} + +WindowGlobalChild* WindowContext::GetWindowGlobalChild() const { + MOZ_ASSERT(XRE_IsContentProcess()); + NS_ENSURE_TRUE(XRE_IsContentProcess(), nullptr); + nsGlobalWindowInner* innerWindow = GetInnerWindow(); + return innerWindow ? innerWindow->GetWindowGlobalChild() : nullptr; +} + +WindowContext* WindowContext::GetParentWindowContext() { + return mBrowsingContext->GetParentWindowContext(); +} + +WindowContext* WindowContext::TopWindowContext() { + WindowContext* current = this; + while (current->GetParentWindowContext()) { + current = current->GetParentWindowContext(); + } + return current; +} + +bool WindowContext::IsTop() const { return mBrowsingContext->IsTop(); } + +bool WindowContext::SameOriginWithTop() const { + return mBrowsingContext->SameOriginWithTop(); +} + +nsIGlobalObject* WindowContext::GetParentObject() const { + return xpc::NativeGlobal(xpc::PrivilegedJunkScope()); +} + +void WindowContext::AppendChildBrowsingContext( + BrowsingContext* aBrowsingContext) { + MOZ_DIAGNOSTIC_ASSERT(Group() == aBrowsingContext->Group(), + "Mismatched groups?"); + MOZ_DIAGNOSTIC_ASSERT(!mChildren.Contains(aBrowsingContext)); + + mChildren.AppendElement(aBrowsingContext); + + // If we're the current WindowContext in our BrowsingContext, make sure to + // clear any cached `children` value. + if (!IsCached()) { + BrowsingContext_Binding::ClearCachedChildrenValue(mBrowsingContext); + } +} + +void WindowContext::RemoveChildBrowsingContext( + BrowsingContext* aBrowsingContext) { + MOZ_DIAGNOSTIC_ASSERT(Group() == aBrowsingContext->Group(), + "Mismatched groups?"); + + mChildren.RemoveElement(aBrowsingContext); + + // If we're the current WindowContext in our BrowsingContext, make sure to + // clear any cached `children` value. + if (!IsCached()) { + BrowsingContext_Binding::ClearCachedChildrenValue(mBrowsingContext); + } +} + +void WindowContext::SendCommitTransaction(ContentParent* aParent, + const BaseTransaction& aTxn, + uint64_t aEpoch) { + Unused << aParent->SendCommitWindowContextTransaction(this, aTxn, aEpoch); +} + +void WindowContext::SendCommitTransaction(ContentChild* aChild, + const BaseTransaction& aTxn, + uint64_t aEpoch) { + aChild->SendCommitWindowContextTransaction(this, aTxn, aEpoch); +} + +bool WindowContext::CheckOnlyOwningProcessCanSet(ContentParent* aSource) { + if (mInProcess) { + return true; + } + + if (XRE_IsParentProcess() && aSource) { + return Canonical()->GetContentParent() == aSource; + } + + return false; +} + +bool WindowContext::CanSet(FieldIndex, const bool& aIsSecure, + ContentParent* aSource) { + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool WindowContext::CanSet(FieldIndex, + const bool& aAllowMixedContent, + ContentParent* aSource) { + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool WindowContext::CanSet(FieldIndex, + const bool& aHasBeforeUnload, + ContentParent* aSource) { + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool WindowContext::CanSet(FieldIndex, + const Maybe& aValue, + ContentParent* aSource) { + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool WindowContext::CanSet(FieldIndex, + const bool& aValue, ContentParent* aSource) { + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool WindowContext::CanSet(FieldIndex, + const bool& IsThirdPartyWindow, + ContentParent* aSource) { + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool WindowContext::CanSet(FieldIndex, + const bool& aIsThirdPartyTrackingResourceWindow, + ContentParent* aSource) { + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool WindowContext::CanSet(FieldIndex, + const bool& aIsSecureContext, + ContentParent* aSource) { + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool WindowContext::CanSet(FieldIndex, + const bool& aIsOriginalFrameSource, + ContentParent* aSource) { + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool WindowContext::CanSet(FieldIndex, const bool& aValue, + ContentParent* aSource) { + return IsTop(); +} + +bool WindowContext::CanSet(FieldIndex, + const uint32_t& aValue, ContentParent* aSource) { + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool WindowContext::CanSet(FieldIndex, + const uint32_t& aValue, ContentParent* aSource) { + return IsTop() && CheckOnlyOwningProcessCanSet(aSource); +} + +bool WindowContext::CanSet(FieldIndex, + const Maybe& aValue, + ContentParent* aSource) { + return IsTop(); +} + +bool WindowContext::CanSet(FieldIndex, const uint32_t&, + ContentParent* aSource) { + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool WindowContext::CanSet( + FieldIndex, + const PermissionDelegateHandler::DelegatedPermissionList& aValue, + ContentParent* aSource) { + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool WindowContext::CanSet( + FieldIndex, + const PermissionDelegateHandler::DelegatedPermissionList& aValue, + ContentParent* aSource) { + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool WindowContext::CanSet(FieldIndex, const bool& aValue, + ContentParent* aSource) { + return CheckOnlyOwningProcessCanSet(aSource); +} + +void WindowContext::DidSet(FieldIndex, + bool aOldValue) { + MOZ_ASSERT( + TopWindowContext() == this, + "SHEntryHasUserInteraction can only be set on the top window context"); + // This field is set when the child notifies us of new user interaction, so we + // also set the currently active shentry in the parent as having interaction. + if (XRE_IsParentProcess() && mBrowsingContext) { + SessionHistoryEntry* activeEntry = + mBrowsingContext->Canonical()->GetActiveSessionHistoryEntry(); + if (activeEntry && GetSHEntryHasUserInteraction()) { + activeEntry->SetHasUserInteraction(true); + } + } +} + +void WindowContext::DidSet(FieldIndex) { + MOZ_ASSERT_IF(!mInProcess, mUserGestureStart.IsNull()); + USER_ACTIVATION_LOG("Set user gesture activation %" PRIu8 + " for %s browsing context 0x%08" PRIx64, + static_cast(GetUserActivationState()), + XRE_IsParentProcess() ? "Parent" : "Child", Id()); + if (mInProcess) { + USER_ACTIVATION_LOG( + "Set user gesture start time for %s browsing context 0x%08" PRIx64, + XRE_IsParentProcess() ? "Parent" : "Child", Id()); + mUserGestureStart = + (GetUserActivationState() == UserActivation::State::FullActivated) + ? TimeStamp::Now() + : TimeStamp(); + } +} + +void WindowContext::DidSet(FieldIndex, + bool aOldValue) { + if (!aOldValue && GetHasReportedShadowDOMUsage() && IsInProcess()) { + MOZ_ASSERT(TopWindowContext() == this); + if (mBrowsingContext) { + Document* topLevelDoc = mBrowsingContext->GetDocument(); + if (topLevelDoc) { + nsAutoString uri; + Unused << topLevelDoc->GetDocumentURI(uri); + if (!uri.IsEmpty()) { + nsAutoString msg = u"Shadow DOM used in ["_ns + uri + + u"] or in some of its subdocuments."_ns; + nsContentUtils::ReportToConsoleNonLocalized( + msg, nsIScriptError::infoFlag, "DOM"_ns, topLevelDoc); + } + } + } + } +} + +void WindowContext::CreateFromIPC(IPCInitializer&& aInit) { + MOZ_RELEASE_ASSERT(XRE_IsContentProcess(), + "Should be a WindowGlobalParent in the parent"); + + RefPtr bc = BrowsingContext::Get(aInit.mBrowsingContextId); + MOZ_RELEASE_ASSERT(bc); + + if (bc->IsDiscarded()) { + // If we have already closed our browsing context, the + // WindowGlobalChild actor is bound to be destroyed soon and it's + // safe to ignore creating the WindowContext. + return; + } + + RefPtr context = + new WindowContext(bc, aInit.mInnerWindowId, aInit.mOuterWindowId, + /* aInProcess */ false, std::move(aInit.mFields)); + context->Init(); +} + +void WindowContext::Init() { + MOZ_LOG(GetLog(), LogLevel::Debug, + ("Registering 0x%" PRIx64 " (bc=0x%" PRIx64 ")", mInnerWindowId, + mBrowsingContext->Id())); + + // Register the WindowContext in the `WindowContextByIdMap`. + if (!gWindowContexts) { + gWindowContexts = new WindowContextByIdMap(); + ClearOnShutdown(&gWindowContexts); + } + auto& entry = gWindowContexts->GetOrInsert(mInnerWindowId); + MOZ_RELEASE_ASSERT(!entry, "Duplicate WindowContext for ID!"); + entry = this; + + // Register this to the browsing context. + mBrowsingContext->RegisterWindowContext(this); + Group()->Register(this); +} + +void WindowContext::Discard() { + MOZ_LOG(GetLog(), LogLevel::Debug, + ("Discarding 0x%" PRIx64 " (bc=0x%" PRIx64 ")", mInnerWindowId, + mBrowsingContext->Id())); + if (mIsDiscarded) { + return; + } + + mIsDiscarded = true; + if (gWindowContexts) { + gWindowContexts->Remove(InnerWindowId()); + } + mBrowsingContext->UnregisterWindowContext(this); + Group()->Unregister(this); +} + +void WindowContext::AddSecurityState(uint32_t aStateFlags) { + MOZ_ASSERT(TopWindowContext() == this); + MOZ_ASSERT((aStateFlags & + (nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT | + nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT | + nsIWebProgressListener::STATE_BLOCKED_MIXED_DISPLAY_CONTENT | + nsIWebProgressListener::STATE_BLOCKED_MIXED_ACTIVE_CONTENT | + nsIWebProgressListener::STATE_HTTPS_ONLY_MODE_UPGRADED | + nsIWebProgressListener::STATE_HTTPS_ONLY_MODE_UPGRADE_FAILED)) == + aStateFlags, + "Invalid flags specified!"); + + if (XRE_IsParentProcess()) { + Canonical()->AddSecurityState(aStateFlags); + } else { + ContentChild* child = ContentChild::GetSingleton(); + child->SendAddSecurityState(this, aStateFlags); + } +} + +void WindowContext::NotifyUserGestureActivation() { + Unused << SetUserActivationState(UserActivation::State::FullActivated); +} + +void WindowContext::NotifyResetUserGestureActivation() { + Unused << SetUserActivationState(UserActivation::State::None); +} + +bool WindowContext::HasBeenUserGestureActivated() { + return GetUserActivationState() != UserActivation::State::None; +} + +bool WindowContext::HasValidTransientUserGestureActivation() { + MOZ_ASSERT(mInProcess); + + if (GetUserActivationState() != UserActivation::State::FullActivated) { + MOZ_ASSERT(mUserGestureStart.IsNull(), + "mUserGestureStart should be null if the document hasn't ever " + "been activated by user gesture"); + return false; + } + + MOZ_ASSERT(!mUserGestureStart.IsNull(), + "mUserGestureStart shouldn't be null if the document has ever " + "been activated by user gesture"); + TimeDuration timeout = TimeDuration::FromMilliseconds( + StaticPrefs::dom_user_activation_transient_timeout()); + + return timeout <= TimeDuration() || + (TimeStamp::Now() - mUserGestureStart) <= timeout; +} + +bool WindowContext::ConsumeTransientUserGestureActivation() { + MOZ_ASSERT(mInProcess); + MOZ_ASSERT(!IsCached()); + + if (!HasValidTransientUserGestureActivation()) { + return false; + } + + BrowsingContext* top = mBrowsingContext->Top(); + top->PreOrderWalk([&](BrowsingContext* aBrowsingContext) { + WindowContext* windowContext = aBrowsingContext->GetCurrentWindowContext(); + if (windowContext && windowContext->GetUserActivationState() == + UserActivation::State::FullActivated) { + Unused << windowContext->SetUserActivationState( + UserActivation::State::HasBeenActivated); + } + }); + + return true; +} + +bool WindowContext::CanShowPopup() { + uint32_t permit = GetPopupPermission(); + if (permit == nsIPermissionManager::ALLOW_ACTION) { + return true; + } + if (permit == nsIPermissionManager::DENY_ACTION) { + return false; + } + + return !StaticPrefs::dom_disable_open_during_load(); +} + +WindowContext::IPCInitializer WindowContext::GetIPCInitializer() { + IPCInitializer init; + init.mInnerWindowId = mInnerWindowId; + init.mOuterWindowId = mOuterWindowId; + init.mBrowsingContextId = mBrowsingContext->Id(); + init.mFields = mFields.RawValues(); + return init; +} + +WindowContext::WindowContext(BrowsingContext* aBrowsingContext, + uint64_t aInnerWindowId, uint64_t aOuterWindowId, + bool aInProcess, FieldValues&& aInit) + : mFields(std::move(aInit)), + mInnerWindowId(aInnerWindowId), + mOuterWindowId(aOuterWindowId), + mBrowsingContext(aBrowsingContext), + mInProcess(aInProcess) { + MOZ_ASSERT(mBrowsingContext); + MOZ_ASSERT(mInnerWindowId); + MOZ_ASSERT(mOuterWindowId); +} + +WindowContext::~WindowContext() { + if (gWindowContexts) { + gWindowContexts->Remove(InnerWindowId()); + } +} + +JSObject* WindowContext::WrapObject(JSContext* cx, + JS::Handle aGivenProto) { + return WindowContext_Binding::Wrap(cx, this, aGivenProto); +} + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WindowContext) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(WindowContext) +NS_IMPL_CYCLE_COLLECTING_RELEASE(WindowContext) + +NS_IMPL_CYCLE_COLLECTION_CLASS(WindowContext) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WindowContext) + if (gWindowContexts) { + gWindowContexts->Remove(tmp->InnerWindowId()); + } + + NS_IMPL_CYCLE_COLLECTION_UNLINK(mBrowsingContext) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildren) + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WindowContext) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowsingContext) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildren) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(WindowContext) + +} // namespace dom + +namespace ipc { + +void IPDLParamTraits>::Write( + IPC::Message* aMsg, IProtocol* aActor, + const dom::MaybeDiscarded& aParam) { + uint64_t id = aParam.ContextId(); + WriteIPDLParam(aMsg, aActor, id); +} + +bool IPDLParamTraits>::Read( + const IPC::Message* aMsg, PickleIterator* aIter, IProtocol* aActor, + dom::MaybeDiscarded* aResult) { + uint64_t id = 0; + if (!ReadIPDLParam(aMsg, aIter, aActor, &id)) { + return false; + } + + if (id == 0) { + *aResult = nullptr; + } else if (RefPtr wc = dom::WindowContext::GetById(id)) { + *aResult = std::move(wc); + } else { + aResult->SetDiscarded(id); + } + return true; +} + +void IPDLParamTraits::Write( + IPC::Message* aMessage, IProtocol* aActor, + const dom::WindowContext::IPCInitializer& aInit) { + // Write actor ID parameters. + WriteIPDLParam(aMessage, aActor, aInit.mInnerWindowId); + WriteIPDLParam(aMessage, aActor, aInit.mOuterWindowId); + WriteIPDLParam(aMessage, aActor, aInit.mBrowsingContextId); + WriteIPDLParam(aMessage, aActor, aInit.mFields); +} + +bool IPDLParamTraits::Read( + const IPC::Message* aMessage, PickleIterator* aIterator, IProtocol* aActor, + dom::WindowContext::IPCInitializer* aInit) { + // Read actor ID parameters. + return ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mInnerWindowId) && + ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mOuterWindowId) && + ReadIPDLParam(aMessage, aIterator, aActor, + &aInit->mBrowsingContextId) && + ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mFields); +} + +template struct IPDLParamTraits; + +} // namespace ipc +} // namespace mozilla diff --git a/docshell/base/WindowContext.h b/docshell/base/WindowContext.h new file mode 100644 index 0000000000..f0ee15ee43 --- /dev/null +++ b/docshell/base/WindowContext.h @@ -0,0 +1,328 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_WindowContext_h +#define mozilla_dom_WindowContext_h + +#include "mozilla/PermissionDelegateHandler.h" +#include "mozilla/Span.h" +#include "mozilla/dom/MaybeDiscarded.h" +#include "mozilla/dom/SyncedContext.h" +#include "mozilla/dom/UserActivation.h" +#include "nsILoadInfo.h" +#include "nsWrapperCache.h" + +class nsIGlobalObject; + +class nsGlobalWindowInner; + +namespace mozilla { +class LogModule; + +namespace dom { + +class WindowGlobalChild; +class WindowGlobalParent; +class WindowGlobalInit; +class BrowsingContext; +class BrowsingContextGroup; + +#define MOZ_EACH_WC_FIELD(FIELD) \ + /* Whether the SHEntry associated with the current top-level \ + * window has already seen user interaction. \ + * As such, this will be reset to false when a new SHEntry is \ + * created without changing the WC (e.g. when using pushState or \ + * sub-frame navigation) \ + * This flag is set for optimization purposes, to avoid \ + * having to get the top SHEntry and update it on every \ + * user interaction. \ + * This is only meaningful on the top-level WC. */ \ + FIELD(SHEntryHasUserInteraction, bool) \ + FIELD(CookieBehavior, Maybe) \ + FIELD(IsOnContentBlockingAllowList, bool) \ + /* Whether the given window hierarchy is third party. See \ + * ThirdPartyUtil::IsThirdPartyWindow for details */ \ + FIELD(IsThirdPartyWindow, bool) \ + /* Whether this window's channel has been marked as a third-party \ + * tracking resource */ \ + FIELD(IsThirdPartyTrackingResourceWindow, bool) \ + FIELD(IsSecureContext, bool) \ + FIELD(IsOriginalFrameSource, bool) \ + /* Mixed-Content: If the corresponding documentURI is https, \ + * then this flag is true. */ \ + FIELD(IsSecure, bool) \ + /* Whether the user has overriden the mixed content blocker to allow \ + * mixed content loads to happen */ \ + FIELD(AllowMixedContent, bool) \ + /* Whether this window has registered a "beforeunload" event \ + * handler */ \ + FIELD(HasBeforeUnload, bool) \ + /* Controls whether the WindowContext is currently considered to be \ + * activated by a gesture */ \ + FIELD(UserActivationState, UserActivation::State) \ + FIELD(EmbedderPolicy, nsILoadInfo::CrossOriginEmbedderPolicy) \ + /* True if this document tree contained at least a HTMLMediaElement. \ + * This should only be set on top level context. */ \ + FIELD(DocTreeHadMedia, bool) \ + FIELD(AutoplayPermission, uint32_t) \ + FIELD(ShortcutsPermission, uint32_t) \ + /* Store the Id of the browsing context where active media session \ + * exists on the top level window context */ \ + FIELD(ActiveMediaSessionContextId, Maybe) \ + /* ALLOW_ACTION if it is allowed to open popups for the sub-tree \ + * starting and including the current WindowContext */ \ + FIELD(PopupPermission, uint32_t) \ + FIELD(DelegatedPermissions, \ + PermissionDelegateHandler::DelegatedPermissionList) \ + FIELD(DelegatedExactHostMatchPermissions, \ + PermissionDelegateHandler::DelegatedPermissionList) \ + FIELD(HasReportedShadowDOMUsage, bool) \ + /* Whether the principal of this window is for a local \ + * IP address */ \ + FIELD(IsLocalIP, bool) + +class WindowContext : public nsISupports, public nsWrapperCache { + MOZ_DECL_SYNCED_CONTEXT(WindowContext, MOZ_EACH_WC_FIELD) + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(WindowContext) + + public: + static already_AddRefed GetById(uint64_t aInnerWindowId); + static LogModule* GetLog(); + + BrowsingContext* GetBrowsingContext() const { return mBrowsingContext; } + BrowsingContextGroup* Group() const; + uint64_t Id() const { return InnerWindowId(); } + uint64_t InnerWindowId() const { return mInnerWindowId; } + uint64_t OuterWindowId() const { return mOuterWindowId; } + bool IsDiscarded() const { return mIsDiscarded; } + + bool IsCached() const; + + bool IsInProcess() const { return mInProcess; } + + bool HasBeforeUnload() const { return GetHasBeforeUnload(); } + + bool IsLocalIP() const { return GetIsLocalIP(); } + + nsGlobalWindowInner* GetInnerWindow() const; + Document* GetDocument() const; + Document* GetExtantDoc() const; + + WindowGlobalChild* GetWindowGlobalChild() const; + + // Get the parent WindowContext of this WindowContext, taking the BFCache into + // account. This will not cross chrome/content boundaries. + WindowContext* GetParentWindowContext(); + WindowContext* TopWindowContext(); + + bool SameOriginWithTop() const; + + bool IsTop() const; + + Span> Children() { return mChildren; } + + // Cast this object to it's parent-process canonical form. + WindowGlobalParent* Canonical(); + + nsIGlobalObject* GetParentObject() const; + JSObject* WrapObject(JSContext* cx, + JS::Handle aGivenProto) override; + + void Discard(); + + struct IPCInitializer { + uint64_t mInnerWindowId; + uint64_t mOuterWindowId; + uint64_t mBrowsingContextId; + + FieldValues mFields; + }; + IPCInitializer GetIPCInitializer(); + + static void CreateFromIPC(IPCInitializer&& aInit); + + // Add new security state flags. + // These should be some of the nsIWebProgressListener 'HTTPS_ONLY_MODE' or + // 'MIXED' state flags, and should only be called on the top window context. + void AddSecurityState(uint32_t aStateFlags); + + // This function would be called when its corresponding window is activated + // by user gesture. + void NotifyUserGestureActivation(); + + // This function would be called when we want to reset the user gesture + // activation flag. + void NotifyResetUserGestureActivation(); + + // Return true if its corresponding window has been activated by user + // gesture. + bool HasBeenUserGestureActivated(); + + // Return true if its corresponding window has transient user gesture + // activation and the transient user gesture activation haven't yet timed + // out. + bool HasValidTransientUserGestureActivation(); + + // Return true if the corresponding window has valid transient user gesture + // activation and the transient user gesture activation had been consumed + // successfully. + bool ConsumeTransientUserGestureActivation(); + + bool CanShowPopup(); + + protected: + WindowContext(BrowsingContext* aBrowsingContext, uint64_t aInnerWindowId, + uint64_t aOuterWindowId, bool aInProcess, + FieldValues&& aFields); + virtual ~WindowContext(); + + virtual void Init(); + + private: + friend class BrowsingContext; + friend class WindowGlobalChild; + friend class WindowGlobalActor; + + void AppendChildBrowsingContext(BrowsingContext* aBrowsingContext); + void RemoveChildBrowsingContext(BrowsingContext* aBrowsingContext); + + // Send a given `BaseTransaction` object to the correct remote. + void SendCommitTransaction(ContentParent* aParent, + const BaseTransaction& aTxn, uint64_t aEpoch); + void SendCommitTransaction(ContentChild* aChild, const BaseTransaction& aTxn, + uint64_t aEpoch); + + bool CheckOnlyOwningProcessCanSet(ContentParent* aSource); + + // Overload `CanSet` to get notifications for a particular field being set. + bool CanSet(FieldIndex, const bool& aIsSecure, + ContentParent* aSource); + bool CanSet(FieldIndex, const bool& aAllowMixedContent, + ContentParent* aSource); + + bool CanSet(FieldIndex, const bool& aHasBeforeUnload, + ContentParent* aSource); + + bool CanSet(FieldIndex, const Maybe& aValue, + ContentParent* aSource); + + bool CanSet(FieldIndex, const bool& aValue, + ContentParent* aSource); + + bool CanSet(FieldIndex, const bool& aValue, + ContentParent* aSource) { + return true; + } + + bool CanSet(FieldIndex, + const bool& IsThirdPartyWindow, ContentParent* aSource); + bool CanSet(FieldIndex, + const bool& aIsThirdPartyTrackingResourceWindow, + ContentParent* aSource); + bool CanSet(FieldIndex, const bool& aIsSecureContext, + ContentParent* aSource); + bool CanSet(FieldIndex, + const bool& aIsOriginalFrameSource, ContentParent* aSource); + bool CanSet(FieldIndex, const bool& aValue, + ContentParent* aSource); + bool CanSet(FieldIndex, const uint32_t& aValue, + ContentParent* aSource); + bool CanSet(FieldIndex, const uint32_t& aValue, + ContentParent* aSource); + bool CanSet(FieldIndex, + const Maybe& aValue, ContentParent* aSource); + bool CanSet(FieldIndex, const uint32_t&, + ContentParent* aSource); + bool CanSet(FieldIndex, + const bool& aSHEntryHasUserInteraction, ContentParent* aSource) { + return true; + } + bool CanSet(FieldIndex, + const PermissionDelegateHandler::DelegatedPermissionList& aValue, + ContentParent* aSource); + bool CanSet(FieldIndex, + const PermissionDelegateHandler::DelegatedPermissionList& aValue, + ContentParent* aSource); + bool CanSet(FieldIndex, + const UserActivation::State& aUserActivationState, + ContentParent* aSource) { + return true; + } + + bool CanSet(FieldIndex, const bool& aValue, + ContentParent* aSource) { + return true; + } + + bool CanSet(FieldIndex, const bool& aValue, + ContentParent* aSource); + + void DidSet(FieldIndex, bool aOldValue); + + void DidSet(FieldIndex, bool aOldValue); + + // Overload `DidSet` to get notifications for a particular field being set. + // + // You can also overload the variant that gets the old value if you need it. + template + void DidSet(FieldIndex) {} + template + void DidSet(FieldIndex, T&& aOldValue) {} + void DidSet(FieldIndex); + + const uint64_t mInnerWindowId; + const uint64_t mOuterWindowId; + RefPtr mBrowsingContext; + + // --- NEVER CHANGE `mChildren` DIRECTLY! --- + // Changes to this list need to be synchronized to the list within our + // `mBrowsingContext`, and should only be performed through the + // `AppendChildBrowsingContext` and `RemoveChildBrowsingContext` methods. + nsTArray> mChildren; + + bool mIsDiscarded = false; + bool mInProcess = false; + + // The start time of user gesture, this is only available if the window + // context is in process. + TimeStamp mUserGestureStart; +}; + +using WindowContextTransaction = WindowContext::BaseTransaction; +using WindowContextInitializer = WindowContext::IPCInitializer; +using MaybeDiscardedWindowContext = MaybeDiscarded; + +// Don't specialize the `Transaction` object for every translation unit it's +// used in. This should help keep code size down. +extern template class syncedcontext::Transaction; + +} // namespace dom + +namespace ipc { +template <> +struct IPDLParamTraits> { + static void Write(IPC::Message* aMsg, IProtocol* aActor, + const dom::MaybeDiscarded& aParam); + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, + dom::MaybeDiscarded* aResult); +}; + +template <> +struct IPDLParamTraits { + static void Write(IPC::Message* aMessage, IProtocol* aActor, + const dom::WindowContext::IPCInitializer& aInitializer); + + static bool Read(const IPC::Message* aMessage, PickleIterator* aIterator, + IProtocol* aActor, + dom::WindowContext::IPCInitializer* aInitializer); +}; +} // namespace ipc +} // namespace mozilla + +#endif // !defined(mozilla_dom_WindowContext_h) diff --git a/docshell/base/crashtests/1257730-1.html b/docshell/base/crashtests/1257730-1.html new file mode 100644 index 0000000000..028a1adb88 --- /dev/null +++ b/docshell/base/crashtests/1257730-1.html @@ -0,0 +1,25 @@ + + + + + + + + + diff --git a/docshell/base/crashtests/1331295.html b/docshell/base/crashtests/1331295.html new file mode 100644 index 0000000000..cdcb29e7fe --- /dev/null +++ b/docshell/base/crashtests/1331295.html @@ -0,0 +1,25 @@ + + + + + + + +
+ + + + diff --git a/docshell/base/crashtests/1341657.html b/docshell/base/crashtests/1341657.html new file mode 100644 index 0000000000..d68fa1eb03 --- /dev/null +++ b/docshell/base/crashtests/1341657.html @@ -0,0 +1,18 @@ + + + + + + diff --git a/docshell/base/crashtests/1584467.html b/docshell/base/crashtests/1584467.html new file mode 100644 index 0000000000..5509808bcc --- /dev/null +++ b/docshell/base/crashtests/1584467.html @@ -0,0 +1,12 @@ + + + diff --git a/docshell/base/crashtests/1614211-1.html b/docshell/base/crashtests/1614211-1.html new file mode 100644 index 0000000000..1d683e0714 --- /dev/null +++ b/docshell/base/crashtests/1614211-1.html @@ -0,0 +1,15 @@ + + + diff --git a/docshell/base/crashtests/1617315-1.html b/docshell/base/crashtests/1617315-1.html new file mode 100644 index 0000000000..05d9a704dc --- /dev/null +++ b/docshell/base/crashtests/1617315-1.html @@ -0,0 +1,8 @@ + + diff --git a/docshell/base/crashtests/1667491.html b/docshell/base/crashtests/1667491.html new file mode 100644 index 0000000000..ecc77a5e9b --- /dev/null +++ b/docshell/base/crashtests/1667491.html @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/docshell/base/crashtests/1667491_1.html b/docshell/base/crashtests/1667491_1.html new file mode 100644 index 0000000000..3df3353f72 --- /dev/null +++ b/docshell/base/crashtests/1667491_1.html @@ -0,0 +1,21 @@ + + + + + + + + + + + + diff --git a/docshell/base/crashtests/369126-1.html b/docshell/base/crashtests/369126-1.html new file mode 100644 index 0000000000..e9dacec301 --- /dev/null +++ b/docshell/base/crashtests/369126-1.html @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/docshell/base/crashtests/40929-1-inner.html b/docshell/base/crashtests/40929-1-inner.html new file mode 100644 index 0000000000..313046a348 --- /dev/null +++ b/docshell/base/crashtests/40929-1-inner.html @@ -0,0 +1,14 @@ +Infinite Loop + + + + + diff --git a/docshell/base/crashtests/40929-1.html b/docshell/base/crashtests/40929-1.html new file mode 100644 index 0000000000..90685d9f1f --- /dev/null +++ b/docshell/base/crashtests/40929-1.html @@ -0,0 +1,6 @@ + +Infinite Loop + + + + diff --git a/docshell/base/crashtests/430124-1.html b/docshell/base/crashtests/430124-1.html new file mode 100644 index 0000000000..8cdbc1d077 --- /dev/null +++ b/docshell/base/crashtests/430124-1.html @@ -0,0 +1,5 @@ + + + +
+ diff --git a/docshell/base/crashtests/430628-1.html b/docshell/base/crashtests/430628-1.html new file mode 100644 index 0000000000..4a68a5a015 --- /dev/null +++ b/docshell/base/crashtests/430628-1.html @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/docshell/base/crashtests/432114-1.html b/docshell/base/crashtests/432114-1.html new file mode 100644 index 0000000000..8878d6605a --- /dev/null +++ b/docshell/base/crashtests/432114-1.html @@ -0,0 +1,8 @@ + + +Bug - Crash [@ PL_DHashTableOperate] with DOMNodeInserted event listener removing window and frameset contenteditable + + + + + diff --git a/docshell/base/crashtests/432114-2.html b/docshell/base/crashtests/432114-2.html new file mode 100644 index 0000000000..da77287b61 --- /dev/null +++ b/docshell/base/crashtests/432114-2.html @@ -0,0 +1,21 @@ + + +testcase2 Bug 432114 � Crash [@ PL_DHashTableOperate] with DOMNodeInserted event listener removing window and frameset contenteditable + + + + + + + diff --git a/docshell/base/crashtests/436900-1-inner.html b/docshell/base/crashtests/436900-1-inner.html new file mode 100644 index 0000000000..6fe35ccb1a --- /dev/null +++ b/docshell/base/crashtests/436900-1-inner.html @@ -0,0 +1,21 @@ + + + + + + + + + + + + diff --git a/docshell/base/crashtests/436900-1.html b/docshell/base/crashtests/436900-1.html new file mode 100644 index 0000000000..582d1919d1 --- /dev/null +++ b/docshell/base/crashtests/436900-1.html @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/docshell/base/crashtests/436900-2-inner.html b/docshell/base/crashtests/436900-2-inner.html new file mode 100644 index 0000000000..ea79f75e88 --- /dev/null +++ b/docshell/base/crashtests/436900-2-inner.html @@ -0,0 +1,21 @@ + + + + + + + + + + + + diff --git a/docshell/base/crashtests/436900-2.html b/docshell/base/crashtests/436900-2.html new file mode 100644 index 0000000000..2e1f0c1def --- /dev/null +++ b/docshell/base/crashtests/436900-2.html @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/docshell/base/crashtests/443655.html b/docshell/base/crashtests/443655.html new file mode 100644 index 0000000000..ce0a8c18b8 --- /dev/null +++ b/docshell/base/crashtests/443655.html @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/docshell/base/crashtests/500328-1.html b/docshell/base/crashtests/500328-1.html new file mode 100644 index 0000000000..fd97f84ae1 --- /dev/null +++ b/docshell/base/crashtests/500328-1.html @@ -0,0 +1,17 @@ + + + + + + diff --git a/docshell/base/crashtests/514779-1.xhtml b/docshell/base/crashtests/514779-1.xhtml new file mode 100644 index 0000000000..16ac3d9d66 --- /dev/null +++ b/docshell/base/crashtests/514779-1.xhtml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/docshell/base/crashtests/678872-1.html b/docshell/base/crashtests/678872-1.html new file mode 100644 index 0000000000..294b3e689b --- /dev/null +++ b/docshell/base/crashtests/678872-1.html @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + diff --git a/docshell/base/crashtests/914521.html b/docshell/base/crashtests/914521.html new file mode 100644 index 0000000000..f30d78c10f --- /dev/null +++ b/docshell/base/crashtests/914521.html @@ -0,0 +1,32 @@ + + + + + + + + diff --git a/docshell/base/crashtests/crashtests.list b/docshell/base/crashtests/crashtests.list new file mode 100644 index 0000000000..3912fe5081 --- /dev/null +++ b/docshell/base/crashtests/crashtests.list @@ -0,0 +1,21 @@ +load 40929-1.html +load 369126-1.html +load 430124-1.html +load 430628-1.html +load 432114-1.html +load 432114-2.html +load 436900-1.html +asserts(0-1) load 436900-2.html # bug 566159 +load 443655.html +load 500328-1.html +load 514779-1.xhtml +load 614499-1.html +load 678872-1.html +skip-if(Android) pref(dom.disable_open_during_load,false) load 914521.html # Android bug 1584562 +pref(browser.send_pings,true) asserts(0-2) load 1257730-1.html # bug 566159 +load 1331295.html +load 1341657.html +load 1584467.html +load 1614211-1.html +load 1617315-1.html +skip-if(Android) pref(dom.disable_open_during_load,false) load 1667491.html diff --git a/docshell/base/crashtests/file_432114-2.xhtml b/docshell/base/crashtests/file_432114-2.xhtml new file mode 100644 index 0000000000..40bf886b8e --- /dev/null +++ b/docshell/base/crashtests/file_432114-2.xhtml @@ -0,0 +1 @@ + diff --git a/docshell/base/moz.build b/docshell/base/moz.build new file mode 100644 index 0000000000..5b84c5ec9e --- /dev/null +++ b/docshell/base/moz.build @@ -0,0 +1,130 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +with Files("**"): + BUG_COMPONENT = ("Core", "DOM: Navigation") + +with Files("crashtests/430628*"): + BUG_COMPONENT = ("Core", "DOM: Editor") + +with Files("crashtests/432114*"): + BUG_COMPONENT = ("Core", "DOM: Editor") + +with Files("crashtests/500328*"): + BUG_COMPONENT = ("Firefox", "Bookmarks & History") + +with Files("IHistory.h"): + BUG_COMPONENT = ("Toolkit", "Places") + +with Files("*LoadContext.*"): + BUG_COMPONENT = ("Core", "Networking") + +with Files("nsAboutRedirector.*"): + BUG_COMPONENT = ("Core", "General") + +with Files("nsIScrollObserver.*"): + BUG_COMPONENT = ("Core", "Panning and Zooming") + +DIRS += [ + "timeline", +] + +XPIDL_SOURCES += [ + "nsIContentViewer.idl", + "nsIContentViewerEdit.idl", + "nsIDocShell.idl", + "nsIDocShellTreeItem.idl", + "nsIDocShellTreeOwner.idl", + "nsIDocumentLoaderFactory.idl", + "nsILoadContext.idl", + "nsILoadURIDelegate.idl", + "nsIPrivacyTransitionObserver.idl", + "nsIReflowObserver.idl", + "nsIRefreshURI.idl", + "nsITooltipListener.idl", + "nsITooltipTextProvider.idl", + "nsIURIFixup.idl", + "nsIWebNavigation.idl", + "nsIWebNavigationInfo.idl", + "nsIWebPageDescriptor.idl", +] + +XPIDL_MODULE = "docshell" + +EXPORTS += [ + "nsCTooltipTextProvider.h", + "nsDocShell.h", + "nsDocShellLoadState.h", + "nsDocShellLoadTypes.h", + "nsDocShellTreeOwner.h", + "nsIScrollObserver.h", + "nsWebNavigationInfo.h", + "SerializedLoadContext.h", +] + +EXPORTS.mozilla += [ + "BaseHistory.h", + "IHistory.h", + "LoadContext.h", +] + +EXPORTS.mozilla.dom += [ + "BrowsingContext.h", + "BrowsingContextGroup.h", + "BrowsingContextWebProgress.h", + "CanonicalBrowsingContext.h", + "ChildProcessChannelListener.h", + "SyncedContext.h", + "SyncedContextInlines.h", + "WindowContext.h", +] + +UNIFIED_SOURCES += [ + "BaseHistory.cpp", + "BrowsingContext.cpp", + "BrowsingContextGroup.cpp", + "BrowsingContextWebProgress.cpp", + "CanonicalBrowsingContext.cpp", + "ChildProcessChannelListener.cpp", + "LoadContext.cpp", + "nsAboutRedirector.cpp", + "nsDocShell.cpp", + "nsDocShellEditorData.cpp", + "nsDocShellEnumerator.cpp", + "nsDocShellLoadState.cpp", + "nsDocShellTelemetryUtils.cpp", + "nsDocShellTreeOwner.cpp", + "nsDSURIContentListener.cpp", + "nsPingListener.cpp", + "nsRefreshTimer.cpp", + "nsWebNavigationInfo.cpp", + "SerializedLoadContext.cpp", + "WindowContext.cpp", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" +LOCAL_INCLUDES += [ + "/docshell/shistory", + "/dom/base", + "/dom/bindings", + "/js/xpconnect/src", + "/layout/base", + "/layout/generic", + "/layout/style", + "/layout/xul", + "/netwerk/base", + "/netwerk/protocol/viewsource", + "/toolkit/components/browser", + "/toolkit/components/find", + "/tools/profiler", +] + +if CONFIG["CC_TYPE"] in ("clang", "gcc"): + CXXFLAGS += ["-Wno-error=shadow"] + +EXTRA_JS_MODULES += ["URIFixup.jsm"] diff --git a/docshell/base/nsAboutRedirector.cpp b/docshell/base/nsAboutRedirector.cpp new file mode 100644 index 0000000000..10ac77b519 --- /dev/null +++ b/docshell/base/nsAboutRedirector.cpp @@ -0,0 +1,266 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsAboutRedirector.h" +#include "nsNetUtil.h" +#include "nsAboutProtocolUtils.h" +#include "nsBaseChannel.h" +#include "mozilla/ArrayUtils.h" +#include "nsIProtocolHandler.h" +#include "nsXULAppAPI.h" +#include "mozilla/Preferences.h" + +#define ABOUT_CONFIG_ENABLED_PREF "general.aboutConfig.enable" + +NS_IMPL_ISUPPORTS(nsAboutRedirector, nsIAboutModule) + +struct RedirEntry { + const char* id; + const char* url; + uint32_t flags; +}; + +class CrashChannel final : public nsBaseChannel { + public: + explicit CrashChannel(nsIURI* aURI) { SetURI(aURI); } + + nsresult OpenContentStream(bool async, nsIInputStream** stream, + nsIChannel** channel) override { + nsAutoCString spec; + mURI->GetSpec(spec); + + if (spec.EqualsASCII("about:crashparent") && XRE_IsParentProcess()) { + MOZ_CRASH("Crash via about:crashparent"); + } + + if (spec.EqualsASCII("about:crashcontent") && XRE_IsContentProcess()) { + MOZ_CRASH("Crash via about:crashcontent"); + } + + NS_WARNING("Unhandled about:crash* URI or wrong process"); + return NS_ERROR_NOT_IMPLEMENTED; + } + + protected: + virtual ~CrashChannel() = default; +}; + +/* + Entries which do not have URI_SAFE_FOR_UNTRUSTED_CONTENT will run with chrome + privileges. This is potentially dangerous. Please use + URI_SAFE_FOR_UNTRUSTED_CONTENT in the third argument to each map item below + unless your about: page really needs chrome privileges. Security review is + required before adding new map entries without + URI_SAFE_FOR_UNTRUSTED_CONTENT. + + URI_SAFE_FOR_UNTRUSTED_CONTENT is not enough to let web pages load that page, + for that you need MAKE_LINKABLE. + */ +static const RedirEntry kRedirMap[] = { + {"about", "chrome://global/content/aboutAbout.html", 0}, + {"addons", "chrome://mozapps/content/extensions/extensions.xhtml", + nsIAboutModule::ALLOW_SCRIPT}, + {"buildconfig", "chrome://global/content/buildconfig.html", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT}, + {"checkerboard", "chrome://global/content/aboutCheckerboard.html", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::ALLOW_SCRIPT}, +#ifndef MOZ_BUILD_APP_IS_BROWSER + {"config", "chrome://global/content/config.xhtml", 0}, +#endif +#ifdef MOZ_CRASHREPORTER + {"crashes", "chrome://global/content/crashes.html", 0}, +#endif + {"credits", "https://www.mozilla.org/credits/", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::URI_MUST_LOAD_IN_CHILD}, + {"httpsonlyerror", "chrome://global/content/httpsonlyerror/errorpage.html", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::URI_CAN_LOAD_IN_CHILD | nsIAboutModule::ALLOW_SCRIPT | + nsIAboutModule::HIDE_FROM_ABOUTABOUT}, + {"license", "chrome://global/content/license.html", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT}, + {"logo", "chrome://branding/content/about.png", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + // Linkable for testing reasons. + nsIAboutModule::MAKE_LINKABLE}, + {"memory", "chrome://global/content/aboutMemory.xhtml", + nsIAboutModule::ALLOW_SCRIPT}, + {"certificate", "chrome://global/content/certviewer/certviewer.html", + nsIAboutModule::ALLOW_SCRIPT | + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::URI_MUST_LOAD_IN_CHILD | + nsIAboutModule::URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS}, + {"mozilla", "chrome://global/content/mozilla.html", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT}, + {"neterror", "chrome://global/content/netError.xhtml", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::URI_CAN_LOAD_IN_CHILD | nsIAboutModule::ALLOW_SCRIPT | + nsIAboutModule::HIDE_FROM_ABOUTABOUT}, + {"networking", "chrome://global/content/aboutNetworking.html", + nsIAboutModule::ALLOW_SCRIPT}, + {"performance", "chrome://global/content/aboutPerformance.html", + nsIAboutModule::ALLOW_SCRIPT}, + {"plugins", "chrome://global/content/plugins.html", + nsIAboutModule::URI_MUST_LOAD_IN_CHILD}, + {"processes", "chrome://global/content/aboutProcesses.html", + nsIAboutModule::ALLOW_SCRIPT}, + // about:serviceworkers always wants to load in the parent process because + // when dom.serviceWorkers.parent_intercept is set to true (the new default) + // then the only place nsIServiceWorkerManager has any data is in the + // parent process. + // + // There is overlap without about:debugging, but about:debugging is not + // available on mobile at this time, and it's useful to be able to know if + // a ServiceWorker is registered directly from the mobile browser without + // having to connect the device to a desktop machine and all that entails. + {"serviceworkers", "chrome://global/content/aboutServiceWorkers.xhtml", + nsIAboutModule::ALLOW_SCRIPT}, +#ifndef ANDROID + {"profiles", "chrome://global/content/aboutProfiles.xhtml", + nsIAboutModule::ALLOW_SCRIPT}, +#endif + // about:srcdoc is unresolvable by specification. It is included here + // because the security manager would disallow srcdoc iframes otherwise. + {"srcdoc", "about:blank", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::HIDE_FROM_ABOUTABOUT | + // Needs to be linkable so content can touch its own srcdoc frames + nsIAboutModule::MAKE_LINKABLE | nsIAboutModule::URI_CAN_LOAD_IN_CHILD}, + {"support", "chrome://global/content/aboutSupport.xhtml", + nsIAboutModule::ALLOW_SCRIPT}, +#ifdef MOZ_GLEAN + {"glean", "chrome://global/content/aboutGlean.html", + nsIAboutModule::HIDE_FROM_ABOUTABOUT | nsIAboutModule::ALLOW_SCRIPT}, +#endif + {"telemetry", "chrome://global/content/aboutTelemetry.xhtml", + nsIAboutModule::ALLOW_SCRIPT}, + {"url-classifier", "chrome://global/content/aboutUrlClassifier.xhtml", + nsIAboutModule::ALLOW_SCRIPT}, + {"webrtc", "chrome://global/content/aboutwebrtc/aboutWebrtc.html", + nsIAboutModule::ALLOW_SCRIPT}, + {"printpreview", "about:blank", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::HIDE_FROM_ABOUTABOUT | + nsIAboutModule::URI_CAN_LOAD_IN_CHILD}, + {"crashparent", "about:blank", nsIAboutModule::HIDE_FROM_ABOUTABOUT}, + {"crashcontent", "about:blank", + nsIAboutModule::HIDE_FROM_ABOUTABOUT | + nsIAboutModule::URI_CAN_LOAD_IN_CHILD | + nsIAboutModule::URI_MUST_LOAD_IN_CHILD}}; +static const int kRedirTotal = mozilla::ArrayLength(kRedirMap); + +NS_IMETHODIMP +nsAboutRedirector::NewChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo, + nsIChannel** aResult) { + NS_ENSURE_ARG_POINTER(aURI); + NS_ENSURE_ARG_POINTER(aLoadInfo); + NS_ASSERTION(aResult, "must not be null"); + + nsAutoCString path; + nsresult rv = NS_GetAboutModuleName(aURI, path); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr ioService = do_GetIOService(&rv); + NS_ENSURE_SUCCESS(rv, rv); + + if (path.EqualsASCII("crashparent") || path.EqualsASCII("crashcontent")) { + bool isExternal; + aLoadInfo->GetLoadTriggeredFromExternal(&isExternal); + if (isExternal) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsCOMPtr channel = new CrashChannel(aURI); + channel->SetLoadInfo(aLoadInfo); + channel.forget(aResult); + return NS_OK; + } + + if (path.EqualsASCII("config") && + !mozilla::Preferences::GetBool(ABOUT_CONFIG_ENABLED_PREF, true)) { + return NS_ERROR_NOT_AVAILABLE; + } + + for (int i = 0; i < kRedirTotal; i++) { + if (!strcmp(path.get(), kRedirMap[i].id)) { + nsCOMPtr tempChannel; + nsCOMPtr tempURI; + rv = NS_NewURI(getter_AddRefs(tempURI), kRedirMap[i].url); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_NewChannelInternal(getter_AddRefs(tempChannel), tempURI, + aLoadInfo); + NS_ENSURE_SUCCESS(rv, rv); + + // If tempURI links to an external URI (i.e. something other than + // chrome:// or resource://) then set result principal URI on the + // load info which forces the channel principal to reflect the displayed + // URL rather then being the systemPrincipal. + bool isUIResource = false; + rv = NS_URIChainHasFlags(tempURI, nsIProtocolHandler::URI_IS_UI_RESOURCE, + &isUIResource); + NS_ENSURE_SUCCESS(rv, rv); + + bool isAboutBlank = NS_IsAboutBlank(tempURI); + + if (!isUIResource && !isAboutBlank) { + aLoadInfo->SetResultPrincipalURI(tempURI); + } + + tempChannel->SetOriginalURI(aURI); + + tempChannel.forget(aResult); + return rv; + } + } + + NS_ERROR("nsAboutRedirector called for unknown case"); + return NS_ERROR_ILLEGAL_VALUE; +} + +NS_IMETHODIMP +nsAboutRedirector::GetURIFlags(nsIURI* aURI, uint32_t* aResult) { + NS_ENSURE_ARG_POINTER(aURI); + + nsAutoCString name; + nsresult rv = NS_GetAboutModuleName(aURI, name); + NS_ENSURE_SUCCESS(rv, rv); + + for (int i = 0; i < kRedirTotal; i++) { + if (name.EqualsASCII(kRedirMap[i].id)) { + *aResult = kRedirMap[i].flags; + return NS_OK; + } + } + + NS_ERROR("nsAboutRedirector called for unknown case"); + return NS_ERROR_ILLEGAL_VALUE; +} + +NS_IMETHODIMP +nsAboutRedirector::GetChromeURI(nsIURI* aURI, nsIURI** chromeURI) { + NS_ENSURE_ARG_POINTER(aURI); + + nsAutoCString name; + nsresult rv = NS_GetAboutModuleName(aURI, name); + NS_ENSURE_SUCCESS(rv, rv); + + for (const auto& redir : kRedirMap) { + if (name.EqualsASCII(redir.id)) { + return NS_NewURI(chromeURI, redir.url); + } + } + + NS_ERROR("nsAboutRedirector called for unknown case"); + return NS_ERROR_ILLEGAL_VALUE; +} + +nsresult nsAboutRedirector::Create(nsISupports* aOuter, REFNSIID aIID, + void** aResult) { + RefPtr about = new nsAboutRedirector(); + return about->QueryInterface(aIID, aResult); +} diff --git a/docshell/base/nsAboutRedirector.h b/docshell/base/nsAboutRedirector.h new file mode 100644 index 0000000000..08448787e9 --- /dev/null +++ b/docshell/base/nsAboutRedirector.h @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsAboutRedirector_h__ +#define nsAboutRedirector_h__ + +#include "nsIAboutModule.h" + +class nsAboutRedirector : public nsIAboutModule { + public: + NS_DECL_ISUPPORTS + + NS_DECL_NSIABOUTMODULE + + nsAboutRedirector() {} + + static nsresult Create(nsISupports* aOuter, REFNSIID aIID, void** aResult); + + protected: + virtual ~nsAboutRedirector() {} +}; + +#endif // nsAboutRedirector_h__ diff --git a/docshell/base/nsCTooltipTextProvider.h b/docshell/base/nsCTooltipTextProvider.h new file mode 100644 index 0000000000..731edf1170 --- /dev/null +++ b/docshell/base/nsCTooltipTextProvider.h @@ -0,0 +1,15 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NSCTOOLTIPTEXTPROVIDER_H +#define NSCTOOLTIPTEXTPROVIDER_H + +#define NS_TOOLTIPTEXTPROVIDER_CONTRACTID \ + "@mozilla.org/embedcomp/tooltiptextprovider;1" +#define NS_DEFAULTTOOLTIPTEXTPROVIDER_CONTRACTID \ + "@mozilla.org/embedcomp/default-tooltiptextprovider;1" + +#endif diff --git a/docshell/base/nsDSURIContentListener.cpp b/docshell/base/nsDSURIContentListener.cpp new file mode 100644 index 0000000000..d2f339c9ec --- /dev/null +++ b/docshell/base/nsDSURIContentListener.cpp @@ -0,0 +1,307 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsDocShell.h" +#include "nsDSURIContentListener.h" +#include "nsIChannel.h" +#include "nsServiceManagerUtils.h" +#include "nsDocShellCID.h" +#include "nsIWebNavigationInfo.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/Unused.h" +#include "nsError.h" +#include "nsContentSecurityManager.h" +#include "nsDocShellLoadTypes.h" +#include "nsGlobalWindowOuter.h" +#include "nsIInterfaceRequestor.h" +#include "nsIMultiPartChannel.h" +#include "nsWebNavigationInfo.h" + +using namespace mozilla; +using namespace mozilla::dom; + +NS_IMPL_ADDREF(MaybeCloseWindowHelper) +NS_IMPL_RELEASE(MaybeCloseWindowHelper) + +NS_INTERFACE_MAP_BEGIN(MaybeCloseWindowHelper) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +MaybeCloseWindowHelper::MaybeCloseWindowHelper(BrowsingContext* aContentContext) + : mBrowsingContext(aContentContext), + mTimer(nullptr), + mShouldCloseWindow(false) {} + +MaybeCloseWindowHelper::~MaybeCloseWindowHelper() {} + +void MaybeCloseWindowHelper::SetShouldCloseWindow(bool aShouldCloseWindow) { + mShouldCloseWindow = aShouldCloseWindow; +} + +BrowsingContext* MaybeCloseWindowHelper::MaybeCloseWindow() { + if (!mShouldCloseWindow) { + return mBrowsingContext; + } + + // This method should not be called more than once, but it's better to avoid + // closing the current window again. + mShouldCloseWindow = false; + + // Reset the window context to the opener window so that the dependent + // dialogs have a parent + RefPtr newBC = ChooseNewBrowsingContext(mBrowsingContext); + + if (newBC != mBrowsingContext && newBC && !newBC->IsDiscarded()) { + mBCToClose = mBrowsingContext; + mBrowsingContext = newBC; + + // Now close the old window. Do it on a timer so that we don't run + // into issues trying to close the window before it has fully opened. + NS_ASSERTION(!mTimer, "mTimer was already initialized once!"); + NS_NewTimerWithCallback(getter_AddRefs(mTimer), this, 0, + nsITimer::TYPE_ONE_SHOT); + } + + return mBrowsingContext; +} + +already_AddRefed +MaybeCloseWindowHelper::ChooseNewBrowsingContext(BrowsingContext* aBC) { + RefPtr opener = aBC->GetOpener(); + if (opener && !opener->IsDiscarded()) { + return opener.forget(); + } + + if (!XRE_IsParentProcess()) { + return nullptr; + } + + opener = BrowsingContext::Get(aBC->Canonical()->GetCrossGroupOpenerId()); + if (!opener || opener->IsDiscarded()) { + return nullptr; + } + return opener.forget(); +} + +NS_IMETHODIMP +MaybeCloseWindowHelper::Notify(nsITimer* timer) { + NS_ASSERTION(mBCToClose, "No window to close after timer fired"); + + mBCToClose->Close(CallerType::System, IgnoreErrors()); + mBCToClose = nullptr; + mTimer = nullptr; + + return NS_OK; +} + +nsDSURIContentListener::nsDSURIContentListener(nsDocShell* aDocShell) + : mDocShell(aDocShell), + mExistingJPEGRequest(nullptr), + mParentContentListener(nullptr) {} + +nsDSURIContentListener::~nsDSURIContentListener() {} + +NS_IMPL_ADDREF(nsDSURIContentListener) +NS_IMPL_RELEASE(nsDSURIContentListener) + +NS_INTERFACE_MAP_BEGIN(nsDSURIContentListener) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIURIContentListener) + NS_INTERFACE_MAP_ENTRY(nsIURIContentListener) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) +NS_INTERFACE_MAP_END + +NS_IMETHODIMP +nsDSURIContentListener::DoContent(const nsACString& aContentType, + bool aIsContentPreferred, + nsIRequest* aRequest, + nsIStreamListener** aContentHandler, + bool* aAbortProcess) { + nsresult rv; + NS_ENSURE_ARG_POINTER(aContentHandler); + NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE); + + *aAbortProcess = false; + + // determine if the channel has just been retargeted to us... + nsLoadFlags loadFlags = 0; + nsCOMPtr aOpenedChannel = do_QueryInterface(aRequest); + + if (aOpenedChannel) { + aOpenedChannel->GetLoadFlags(&loadFlags); + + // block top-level data URI navigations if triggered by the web + if (!nsContentSecurityManager::AllowTopLevelNavigationToDataURI( + aOpenedChannel)) { + // logging to console happens within AllowTopLevelNavigationToDataURI + aRequest->Cancel(NS_ERROR_DOM_BAD_URI); + *aAbortProcess = true; + // close the window since the navigation to a data URI was blocked + if (mDocShell && mDocShell->GetBrowsingContext()) { + RefPtr maybeCloseWindowHelper = + new MaybeCloseWindowHelper(mDocShell->GetBrowsingContext()); + maybeCloseWindowHelper->SetShouldCloseWindow(true); + Unused << maybeCloseWindowHelper->MaybeCloseWindow(); + } + return NS_OK; + } + } + + if (loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI) { + // XXX: Why does this not stop the content too? + mDocShell->Stop(nsIWebNavigation::STOP_NETWORK); + + mDocShell->SetLoadType(aIsContentPreferred ? LOAD_LINK : LOAD_NORMAL); + } + + // In case of multipart jpeg request (mjpeg) we don't really want to + // create new viewer since the one we already have is capable of + // rendering multipart jpeg correctly (see bug 625012) + nsCOMPtr baseChannel; + if (nsCOMPtr mpchan = do_QueryInterface(aRequest)) { + mpchan->GetBaseChannel(getter_AddRefs(baseChannel)); + } + + bool reuseCV = baseChannel && baseChannel == mExistingJPEGRequest && + aContentType.EqualsLiteral("image/jpeg"); + + if (mExistingJPEGStreamListener && reuseCV) { + RefPtr copy(mExistingJPEGStreamListener); + copy.forget(aContentHandler); + rv = NS_OK; + } else { + rv = + mDocShell->CreateContentViewer(aContentType, aRequest, aContentHandler); + if (NS_SUCCEEDED(rv) && reuseCV) { + mExistingJPEGStreamListener = *aContentHandler; + } else { + mExistingJPEGStreamListener = nullptr; + } + mExistingJPEGRequest = baseChannel; + } + + if (rv == NS_ERROR_REMOTE_XUL || rv == NS_ERROR_DOCSHELL_DYING) { + aRequest->Cancel(rv); + *aAbortProcess = true; + return NS_OK; + } + + if (NS_FAILED(rv)) { + // we don't know how to handle the content + nsCOMPtr forget = dont_AddRef(*aContentHandler); + *aContentHandler = nullptr; + return rv; + } + + if (loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI) { + nsCOMPtr domWindow = + mDocShell ? mDocShell->GetWindow() : nullptr; + NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE); + domWindow->Focus(mozilla::dom::CallerType::System); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDSURIContentListener::IsPreferred(const char* aContentType, + char** aDesiredContentType, + bool* aCanHandle) { + NS_ENSURE_ARG_POINTER(aCanHandle); + NS_ENSURE_ARG_POINTER(aDesiredContentType); + + // the docshell has no idea if it is the preferred content provider or not. + // It needs to ask its parent if it is the preferred content handler or not... + + nsCOMPtr parentListener; + GetParentContentListener(getter_AddRefs(parentListener)); + if (parentListener) { + return parentListener->IsPreferred(aContentType, aDesiredContentType, + aCanHandle); + } + // we used to return false here if we didn't have a parent properly registered + // at the top of the docshell hierarchy to dictate what content types this + // docshell should be a preferred handler for. But this really makes it hard + // for developers using iframe or browser tags because then they need to make + // sure they implement nsIURIContentListener otherwise all link clicks would + // get sent to another window because we said we weren't the preferred handler + // type. I'm going to change the default now... if we can handle the content, + // and someone didn't EXPLICITLY set a nsIURIContentListener at the top of our + // docshell chain, then we'll now always attempt to process the content + // ourselves... + return CanHandleContent(aContentType, true, aDesiredContentType, aCanHandle); +} + +NS_IMETHODIMP +nsDSURIContentListener::CanHandleContent(const char* aContentType, + bool aIsContentPreferred, + char** aDesiredContentType, + bool* aCanHandleContent) { + MOZ_ASSERT(aCanHandleContent, "Null out param?"); + NS_ENSURE_ARG_POINTER(aDesiredContentType); + + *aCanHandleContent = false; + *aDesiredContentType = nullptr; + + if (aContentType) { + uint32_t canHandle = nsWebNavigationInfo::IsTypeSupported( + nsDependentCString(aContentType), mDocShell); + *aCanHandleContent = (canHandle != nsIWebNavigationInfo::UNSUPPORTED); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDSURIContentListener::GetLoadCookie(nsISupports** aLoadCookie) { + NS_IF_ADDREF(*aLoadCookie = nsDocShell::GetAsSupports(mDocShell)); + return NS_OK; +} + +NS_IMETHODIMP +nsDSURIContentListener::SetLoadCookie(nsISupports* aLoadCookie) { +#ifdef DEBUG + RefPtr cookieAsDocLoader = + nsDocLoader::GetAsDocLoader(aLoadCookie); + NS_ASSERTION(cookieAsDocLoader && cookieAsDocLoader == mDocShell, + "Invalid load cookie being set!"); +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsDSURIContentListener::GetParentContentListener( + nsIURIContentListener** aParentListener) { + if (mWeakParentContentListener) { + nsCOMPtr tempListener = + do_QueryReferent(mWeakParentContentListener); + *aParentListener = tempListener; + NS_IF_ADDREF(*aParentListener); + } else { + *aParentListener = mParentContentListener; + NS_IF_ADDREF(*aParentListener); + } + return NS_OK; +} + +NS_IMETHODIMP +nsDSURIContentListener::SetParentContentListener( + nsIURIContentListener* aParentListener) { + if (aParentListener) { + // Store the parent listener as a weak ref. Parents not supporting + // nsISupportsWeakReference assert but may still be used. + mParentContentListener = nullptr; + mWeakParentContentListener = do_GetWeakReference(aParentListener); + if (!mWeakParentContentListener) { + mParentContentListener = aParentListener; + } + } else { + mWeakParentContentListener = nullptr; + mParentContentListener = nullptr; + } + return NS_OK; +} diff --git a/docshell/base/nsDSURIContentListener.h b/docshell/base/nsDSURIContentListener.h new file mode 100644 index 0000000000..a9e7da728f --- /dev/null +++ b/docshell/base/nsDSURIContentListener.h @@ -0,0 +1,99 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsDSURIContentListener_h__ +#define nsDSURIContentListener_h__ + +#include "nsCOMPtr.h" +#include "nsIURIContentListener.h" +#include "nsWeakReference.h" +#include "nsITimer.h" + +class nsDocShell; +class nsIInterfaceRequestor; +class nsIWebNavigationInfo; +class nsPIDOMWindowOuter; + +// Helper Class to eventually close an already opened window +class MaybeCloseWindowHelper final : public nsITimerCallback { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSITIMERCALLBACK + + explicit MaybeCloseWindowHelper( + mozilla::dom::BrowsingContext* aContentContext); + + /** + * Closes the provided window async (if mShouldCloseWindow is true) and + * returns a valid browsingContext to be used instead as parent for dialogs or + * similar things. + * In case mShouldCloseWindow is true, the returned BrowsingContext will be + * the window's opener (or original cross-group opener in the case of a + * `noopener` popup). + */ + mozilla::dom::BrowsingContext* MaybeCloseWindow(); + + void SetShouldCloseWindow(bool aShouldCloseWindow); + + protected: + ~MaybeCloseWindowHelper(); + + private: + already_AddRefed ChooseNewBrowsingContext( + mozilla::dom::BrowsingContext* aBC); + + /** + * The dom window associated to handle content. + */ + RefPtr mBrowsingContext; + + /** + * Used to close the window on a timer, to avoid any exceptions that are + * thrown if we try to close the window before it's fully loaded. + */ + RefPtr mBCToClose; + nsCOMPtr mTimer; + + /** + * This is set based on whether the channel indicates that a new window + * was opened, e.g. for a download, or was blocked. If so, then we + * close it. + */ + bool mShouldCloseWindow; +}; + +class nsDSURIContentListener final : public nsIURIContentListener, + public nsSupportsWeakReference { + friend class nsDocShell; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIURICONTENTLISTENER + + protected: + explicit nsDSURIContentListener(nsDocShell* aDocShell); + virtual ~nsDSURIContentListener(); + + void DropDocShellReference() { + mDocShell = nullptr; + mExistingJPEGRequest = nullptr; + mExistingJPEGStreamListener = nullptr; + } + + protected: + nsDocShell* mDocShell; + // Hack to handle multipart images without creating a new viewer + nsCOMPtr mExistingJPEGStreamListener; + nsCOMPtr mExistingJPEGRequest; + + // Store the parent listener in either of these depending on + // if supports weak references or not. Proper weak refs are + // preferred and encouraged! + nsWeakPtr mWeakParentContentListener; + nsIURIContentListener* mParentContentListener; +}; + +#endif /* nsDSURIContentListener_h__ */ diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp new file mode 100644 index 0000000000..207a757eef --- /dev/null +++ b/docshell/base/nsDocShell.cpp @@ -0,0 +1,13439 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsDocShell.h" + +#include + +#ifdef XP_WIN +# include +# define getpid _getpid +#else +# include // for getpid() +#endif + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Attributes.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/Casting.h" +#include "mozilla/Components.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Encoding.h" +#include "mozilla/EventStateManager.h" +#include "mozilla/HTMLEditor.h" +#include "mozilla/InputTaskManager.h" +#include "mozilla/LoadInfo.h" +#include "mozilla/Logging.h" +#include "mozilla/MediaFeatureChange.h" +#include "mozilla/ObservedDocShell.h" +#include "mozilla/Preferences.h" +#include "mozilla/PresShell.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/ScrollTypes.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPrefs_browser.h" +#include "mozilla/StaticPrefs_docshell.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/StaticPrefs_extensions.h" +#include "mozilla/StaticPrefs_privacy.h" +#include "mozilla/StaticPrefs_security.h" +#include "mozilla/StaticPrefs_ui.h" +#include "mozilla/StaticPrefs_fission.h" +#include "mozilla/StartupTimeline.h" +#include "mozilla/StorageAccess.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Tuple.h" +#include "mozilla/Unused.h" +#include "mozilla/WidgetUtils.h" + +#include "mozilla/dom/ChildProcessChannelListener.h" +#include "mozilla/dom/ClientChannelHelper.h" +#include "mozilla/dom/ClientHandle.h" +#include "mozilla/dom/ClientInfo.h" +#include "mozilla/dom/ClientManager.h" +#include "mozilla/dom/ClientSource.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentFrameMessageManager.h" +#include "mozilla/dom/DocGroup.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/HTMLAnchorElement.h" +#include "mozilla/dom/HTMLIFrameElement.h" +#include "mozilla/dom/PerformanceNavigation.h" +#include "mozilla/dom/PermissionMessageUtils.h" +#include "mozilla/dom/PopupBlocker.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" +#include "mozilla/dom/ScreenOrientation.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/ServiceWorkerInterceptController.h" +#include "mozilla/dom/ServiceWorkerUtils.h" +#include "mozilla/dom/SessionHistoryEntry.h" +#include "mozilla/dom/SessionStorageManager.h" +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/dom/ToJSValue.h" +#include "mozilla/dom/UserActivation.h" +#include "mozilla/dom/ChildSHistory.h" +#include "mozilla/dom/nsCSPContext.h" +#include "mozilla/dom/nsHTTPSOnlyUtils.h" +#include "mozilla/dom/LoadURIOptionsBinding.h" +#include "mozilla/dom/JSWindowActorChild.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/net/DocumentChannel.h" +#include "mozilla/net/ParentChannelWrapper.h" +#include "mozilla/net/UrlClassifierFeatureFactory.h" +#include "ReferrerInfo.h" + +#include "nsIApplicationCacheChannel.h" +#include "nsIApplicationCacheContainer.h" +#include "nsIAppShell.h" +#include "nsIAuthPrompt.h" +#include "nsIAuthPrompt2.h" +#include "nsICachingChannel.h" +#include "nsICaptivePortalService.h" +#include "nsIChannel.h" +#include "nsIChannelEventSink.h" +#include "nsIClassOfService.h" +#include "nsIConsoleReportCollector.h" +#include "nsIContent.h" +#include "nsIContentInlines.h" +#include "nsIContentSecurityPolicy.h" +#include "nsIContentViewer.h" +#include "nsIController.h" +#include "nsIDocShellTreeItem.h" +#include "nsIDocShellTreeOwner.h" +#include "mozilla/dom/Document.h" +#include "nsIDocumentLoaderFactory.h" +#include "nsIDOMWindow.h" +#include "nsIEditingSession.h" +#include "nsIEffectiveTLDService.h" +#include "nsIExternalProtocolService.h" +#include "nsIFormPOSTActionChannel.h" +#include "nsIFrame.h" +#include "nsIGlobalObject.h" +#include "nsIHttpChannel.h" +#include "nsIHttpChannelInternal.h" +#include "nsIIDNService.h" +#include "nsIInputStreamChannel.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsILayoutHistoryState.h" +#include "nsILoadInfo.h" +#include "nsILoadURIDelegate.h" +#include "nsIMultiPartChannel.h" +#include "nsINestedURI.h" +#include "nsINetworkPredictor.h" +#include "nsINode.h" +#include "nsINSSErrorsService.h" +#include "nsIObserverService.h" +#include "nsIOService.h" +#include "nsIPrincipal.h" +#include "nsIPrivacyTransitionObserver.h" +#include "nsIPrompt.h" +#include "nsIPromptCollection.h" +#include "nsIPromptFactory.h" +#include "nsIReflowObserver.h" +#include "nsIScriptChannel.h" +#include "nsIScriptObjectPrincipal.h" +#include "nsIScriptSecurityManager.h" +#include "nsIScrollableFrame.h" +#include "nsIScrollObserver.h" +#include "nsISecureBrowserUI.h" +#include "nsISeekableStream.h" +#include "nsISelectionDisplay.h" +#include "nsISHEntry.h" +#include "nsISiteSecurityService.h" +#include "nsISocketProvider.h" +#include "nsIStringBundle.h" +#include "nsIStructuredCloneContainer.h" +#include "nsIBrowserChild.h" +#include "nsITextToSubURI.h" +#include "nsITimedChannel.h" +#include "nsITimer.h" +#include "nsITransportSecurityInfo.h" +#include "nsIUploadChannel.h" +#include "nsIURIFixup.h" +#include "nsIURIMutator.h" +#include "nsIURILoader.h" +#include "nsIViewSourceChannel.h" +#include "nsIWebBrowserChrome.h" +#include "nsIWebBrowserChrome3.h" +#include "nsIWebBrowserChromeFocus.h" +#include "nsIWebBrowserFind.h" +#include "nsIWebProgress.h" +#include "nsIWidget.h" +#include "nsIWindowWatcher.h" +#include "nsIWritablePropertyBag2.h" +#include "nsIX509Cert.h" +#include "nsIXULRuntime.h" + +#include "nsCommandManager.h" +#include "nsPIDOMWindow.h" +#include "nsPIWindowRoot.h" + +#include "IHistory.h" +#include "IUrlClassifierUITelemetry.h" + +#include "nsArray.h" +#include "nsArrayUtils.h" +#include "nsCExternalHandlerService.h" +#include "nsContentDLF.h" +#include "nsContentPolicyUtils.h" // NS_CheckContentLoadPolicy(...) +#include "nsContentSecurityManager.h" +#include "nsContentSecurityUtils.h" +#include "nsContentUtils.h" +#include "nsCURILoader.h" +#include "nsDocShellCID.h" +#include "nsDocShellEditorData.h" +#include "nsDocShellEnumerator.h" +#include "nsDocShellLoadState.h" +#include "nsDocShellLoadTypes.h" +#include "nsDOMCID.h" +#include "nsDOMNavigationTiming.h" +#include "nsDSURIContentListener.h" +#include "nsEditingSession.h" +#include "nsError.h" +#include "nsEscape.h" +#include "nsFocusManager.h" +#include "nsGlobalWindow.h" +#include "nsISearchService.h" +#include "nsJSEnvironment.h" +#include "nsNetCID.h" +#include "nsNetUtil.h" +#include "nsObjectLoadingContent.h" +#include "nsPingListener.h" +#include "nsPoint.h" +#include "nsQueryObject.h" +#include "nsQueryActor.h" +#include "nsRect.h" +#include "nsRefreshTimer.h" +#include "nsSandboxFlags.h" +#include "nsSHEntry.h" +#include "nsSHistory.h" +#include "nsSHEntry.h" +#include "nsStructuredCloneContainer.h" +#include "nsSubDocumentFrame.h" +#include "nsURILoader.h" +#include "nsURLHelper.h" +#include "nsView.h" +#include "nsViewManager.h" +#include "nsViewSourceHandler.h" +#include "nsWebBrowserFind.h" +#include "nsWhitespaceTokenizer.h" +#include "nsWidgetsCID.h" +#include "nsXULAppAPI.h" + +#include "BRNameMatchingPolicy.h" +#include "GeckoProfiler.h" +#include "mozilla/NullPrincipal.h" +#include "Navigator.h" +#include "prenv.h" +#include "URIUtils.h" +#include "sslerr.h" +#include "mozpkix/pkix.h" +#include "NSSErrorsService.h" + +#include "timeline/JavascriptTimelineMarker.h" +#include "nsDocShellTelemetryUtils.h" + +#ifdef MOZ_PLACES +# include "nsIFaviconService.h" +# include "mozIPlacesPendingOperation.h" +#endif + +#if NS_PRINT_PREVIEW +# include "nsIDocumentViewerPrint.h" +# include "nsIWebBrowserPrint.h" +#endif + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::net; + +using mozilla::ipc::Endpoint; + +// Threshold value in ms for META refresh based redirects +#define REFRESH_REDIRECT_TIMER 15000 + +// Hint for native dispatch of events on how long to delay after +// all documents have loaded in milliseconds before favoring normal +// native event dispatch priorites over performance +// Can be overridden with docshell.event_starvation_delay_hint pref. +#define NS_EVENT_STARVATION_DELAY_HINT 2000 + +static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID); + +// Number of documents currently loading +static int32_t gNumberOfDocumentsLoading = 0; + +// Global count of docshells with the private attribute set +static uint32_t gNumberOfPrivateDocShells = 0; + +static mozilla::LazyLogModule gCharsetMenuLog("CharsetMenu"); + +#define LOGCHARSETMENU(args) \ + MOZ_LOG(gCharsetMenuLog, mozilla::LogLevel::Debug, args) + +#ifdef DEBUG +unsigned long nsDocShell::gNumberOfDocShells = 0; +static uint64_t gDocshellIDCounter = 0; + +static mozilla::LazyLogModule gDocShellLog("nsDocShell"); +static mozilla::LazyLogModule gDocShellAndDOMWindowLeakLogging( + "DocShellAndDOMWindowLeak"); +#endif +static mozilla::LazyLogModule gDocShellLeakLog("nsDocShellLeak"); +extern mozilla::LazyLogModule gPageCacheLog; +mozilla::LazyLogModule gSHLog("SessionHistory"); + +const char kBrandBundleURL[] = "chrome://branding/locale/brand.properties"; +const char kAppstringsBundleURL[] = + "chrome://global/locale/appstrings.properties"; + +static void FavorPerformanceHint(bool aPerfOverStarvation) { + nsCOMPtr appShell = do_GetService(kAppShellCID); + if (appShell) { + appShell->FavorPerformanceHint( + aPerfOverStarvation, + Preferences::GetUint("docshell.event_starvation_delay_hint", + NS_EVENT_STARVATION_DELAY_HINT)); + } +} + +static void IncreasePrivateDocShellCount() { + gNumberOfPrivateDocShells++; + if (gNumberOfPrivateDocShells > 1 || !XRE_IsContentProcess()) { + return; + } + + mozilla::dom::ContentChild* cc = mozilla::dom::ContentChild::GetSingleton(); + cc->SendPrivateDocShellsExist(true); +} + +static void DecreasePrivateDocShellCount() { + MOZ_ASSERT(gNumberOfPrivateDocShells > 0); + gNumberOfPrivateDocShells--; + if (!gNumberOfPrivateDocShells) { + if (XRE_IsContentProcess()) { + dom::ContentChild* cc = dom::ContentChild::GetSingleton(); + cc->SendPrivateDocShellsExist(false); + return; + } + + nsCOMPtr obsvc = services::GetObserverService(); + if (obsvc) { + obsvc->NotifyObservers(nullptr, "last-pb-context-exited", nullptr); + } + } +} + +static bool IsTopLevelDoc(BrowsingContext* aBrowsingContext, + nsILoadInfo* aLoadInfo) { + MOZ_ASSERT(aBrowsingContext); + MOZ_ASSERT(aLoadInfo); + + if (aLoadInfo->GetExternalContentPolicyType() != + ExtContentPolicy::TYPE_DOCUMENT) { + return false; + } + + return aBrowsingContext->IsTopContent(); +} + +// True if loading for top level document loading in active tab. +static bool IsUrgentStart(BrowsingContext* aBrowsingContext, + nsILoadInfo* aLoadInfo, uint32_t aLoadType) { + MOZ_ASSERT(aBrowsingContext); + MOZ_ASSERT(aLoadInfo); + + if (!IsTopLevelDoc(aBrowsingContext, aLoadInfo)) { + return false; + } + + if (aLoadType & + (nsIDocShell::LOAD_CMD_NORMAL | nsIDocShell::LOAD_CMD_HISTORY)) { + return true; + } + + return aBrowsingContext->IsActive(); +} + +nsDocShell::nsDocShell(BrowsingContext* aBrowsingContext, + uint64_t aContentWindowID) + : nsDocLoader(), + mContentWindowID(aContentWindowID), + mBrowsingContext(aBrowsingContext), + mForcedCharset(nullptr), + mParentCharset(nullptr), + mTreeOwner(nullptr), + mScrollbarPref(ScrollbarPreference::Auto), + mCharsetReloadState(eCharsetReloadInit), + mParentCharsetSource(0), + mFrameMargins(-1, -1), + mItemType(aBrowsingContext->IsContent() ? typeContent : typeChrome), + mPreviousEntryIndex(-1), + mLoadedEntryIndex(-1), + mChildOffset(0), + mBusyFlags(BUSY_FLAGS_NONE), + mAppType(nsIDocShell::APP_TYPE_UNKNOWN), + mLoadType(0), + mFailedLoadType(0), + mJSRunToCompletionDepth(0), + mMetaViewportOverride(nsIDocShell::META_VIEWPORT_OVERRIDE_NONE), + mCreatingDocument(false), +#ifdef DEBUG + mInEnsureScriptEnv(false), +#endif + mInitialized(false), + mAllowSubframes(true), + mAllowJavascript(true), + mAllowMetaRedirects(true), + mAllowImages(true), + mAllowMedia(true), + mAllowDNSPrefetch(true), + mAllowWindowControl(true), + mCSSErrorReportingEnabled(false), + mAllowAuth(mItemType == typeContent), + mAllowKeywordFixup(false), + mIsOffScreenBrowser(false), + mDisableMetaRefreshWhenInactive(false), + mIsAppTab(false), + mDeviceSizeIsPageSize(false), + mWindowDraggingAllowed(false), + mInFrameSwap(false), + mCanExecuteScripts(false), + mFiredUnloadEvent(false), + mEODForCurrentDocument(false), + mURIResultedInDocument(false), + mIsBeingDestroyed(false), + mIsExecutingOnLoadHandler(false), + mSavingOldViewer(false), + mAffectPrivateSessionLifetime(true), + mInvisible(false), + mHasLoadedNonBlankURI(false), + mBlankTiming(false), + mTitleValidForCurrentURI(false), + mWillChangeProcess(false), + mIsNavigating(false), + mSuspendMediaWhenInactive(false), + mForcedAutodetection(false) { + // If no outer window ID was provided, generate a new one. + if (aContentWindowID == 0) { + mContentWindowID = nsContentUtils::GenerateWindowId(); + } + + MOZ_LOG(gDocShellLeakLog, LogLevel::Debug, ("DOCSHELL %p created\n", this)); + +#ifdef DEBUG + mDocShellID = gDocshellIDCounter++; + // We're counting the number of |nsDocShells| to help find leaks + ++gNumberOfDocShells; + MOZ_LOG(gDocShellAndDOMWindowLeakLogging, LogLevel::Info, + ("++DOCSHELL %p == %ld [pid = %d] [id = %" PRIu64 "]\n", (void*)this, + gNumberOfDocShells, getpid(), mDocShellID)); +#endif +} + +nsDocShell::~nsDocShell() { + MOZ_ASSERT(!mObserved); + + // Avoid notifying observers while we're in the dtor. + mIsBeingDestroyed = true; + + Destroy(); + + if (mContentViewer) { + mContentViewer->Close(nullptr); + mContentViewer->Destroy(); + mContentViewer = nullptr; + } + + MOZ_LOG(gDocShellLeakLog, LogLevel::Debug, ("DOCSHELL %p destroyed\n", this)); + +#ifdef DEBUG + if (MOZ_LOG_TEST(gDocShellAndDOMWindowLeakLogging, LogLevel::Info)) { + nsAutoCString url; + if (mLastOpenedURI) { + url = mLastOpenedURI->GetSpecOrDefault(); + + // Data URLs can be very long, so truncate to avoid flooding the log. + const uint32_t maxURLLength = 1000; + if (url.Length() > maxURLLength) { + url.Truncate(maxURLLength); + } + } + + // We're counting the number of |nsDocShells| to help find leaks + --gNumberOfDocShells; + MOZ_LOG( + gDocShellAndDOMWindowLeakLogging, LogLevel::Info, + ("--DOCSHELL %p == %ld [pid = %d] [id = %" PRIu64 "] [url = %s]\n", + (void*)this, gNumberOfDocShells, getpid(), mDocShellID, url.get())); + } +#endif +} + +bool nsDocShell::Initialize() { + if (mInitialized) { + // We've already been initialized. + return true; + } + + NS_ASSERTION(mItemType == typeContent || mItemType == typeChrome, + "Unexpected item type in docshell"); + + NS_ENSURE_TRUE(Preferences::GetRootBranch(), false); + mInitialized = true; + + mDisableMetaRefreshWhenInactive = + Preferences::GetBool("browser.meta_refresh_when_inactive.disabled", + mDisableMetaRefreshWhenInactive); + + mDeviceSizeIsPageSize = Preferences::GetBool( + "docshell.device_size_is_page_size", mDeviceSizeIsPageSize); + + if (nsCOMPtr serv = services::GetObserverService()) { + const char* msg = mItemType == typeContent ? NS_WEBNAVIGATION_CREATE + : NS_CHROME_WEBNAVIGATION_CREATE; + serv->NotifyWhenScriptSafe(GetAsSupports(this), msg, nullptr); + } + + return true; +} + +/* static */ +already_AddRefed nsDocShell::Create( + BrowsingContext* aBrowsingContext, uint64_t aContentWindowID) { + MOZ_ASSERT(aBrowsingContext, "DocShell without a BrowsingContext!"); + + nsresult rv; + RefPtr ds = new nsDocShell(aBrowsingContext, aContentWindowID); + + // Initialize the underlying nsDocLoader. + rv = ds->nsDocLoader::InitWithBrowsingContext(aBrowsingContext); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + // Create our ContentListener + ds->mContentListener = new nsDSURIContentListener(ds); + + // If parent intercept is not enabled then we must forward to + // the network controller from docshell. We also enable if we're + // in the parent process in order to support non-e10s configurations. + // Note: This check is duplicated in SharedWorkerInterfaceRequestor's + // constructor. + if (!ServiceWorkerParentInterceptEnabled() || XRE_IsParentProcess()) { + ds->mInterceptController = new ServiceWorkerInterceptController(); + } + + // We want to hold a strong ref to the loadgroup, so it better hold a weak + // ref to us... use an InterfaceRequestorProxy to do this. + nsCOMPtr proxy = new InterfaceRequestorProxy(ds); + ds->mLoadGroup->SetNotificationCallbacks(proxy); + + // XXX(nika): We have our BrowsingContext, so we might be able to skip this. + // It could be nice to directly set up our DocLoader tree? + rv = nsDocLoader::AddDocLoaderAsChildOfRoot(ds); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + // Add |ds| as a progress listener to itself. A little weird, but simpler + // than reproducing all the listener-notification logic in overrides of the + // various methods via which nsDocLoader can be notified. Note that this + // holds an nsWeakPtr to |ds|, so it's ok. + rv = ds->AddProgressListener(ds, nsIWebProgress::NOTIFY_STATE_DOCUMENT | + nsIWebProgress::NOTIFY_STATE_NETWORK | + nsIWebProgress::NOTIFY_LOCATION); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + // If our BrowsingContext has private browsing enabled, update the number of + // private browsing docshells. + if (aBrowsingContext->UsePrivateBrowsing()) { + ds->NotifyPrivateBrowsingChanged(); + } + + // If our parent window is present in this process, set up our parent now. + RefPtr parentWC = aBrowsingContext->GetParentWindowContext(); + if (parentWC && parentWC->IsInProcess()) { + // If we don't have a parent element anymore, we can't finish this load! + // How'd we get here? + RefPtr parentElement = aBrowsingContext->GetEmbedderElement(); + if (!parentElement) { + MOZ_ASSERT_UNREACHABLE("nsDocShell::Create() - !parentElement"); + return nullptr; + } + + // We have an in-process parent window, but don't have a parent nsDocShell? + // How'd we get here! + nsCOMPtr parentShell = + parentElement->OwnerDoc()->GetDocShell(); + if (!parentShell) { + MOZ_ASSERT_UNREACHABLE("nsDocShell::Create() - !parentShell"); + return nullptr; + } + parentShell->AddChild(ds); + } + + // Make |ds| the primary DocShell for the given context. + aBrowsingContext->SetDocShell(ds); + + // Set |ds| default load flags on load group. + ds->SetLoadGroupDefaultLoadFlags(aBrowsingContext->GetDefaultLoadFlags()); + + if (XRE_IsParentProcess()) { + aBrowsingContext->Canonical()->MaybeAddAsProgressListener(ds); + } + + return ds.forget(); +} + +void nsDocShell::DestroyChildren() { + for (auto* child : mChildList.ForwardRange()) { + nsCOMPtr shell = do_QueryObject(child); + NS_ASSERTION(shell, "docshell has null child"); + + if (shell) { + shell->SetTreeOwner(nullptr); + } + } + + nsDocLoader::DestroyChildren(); +} + +NS_IMPL_CYCLE_COLLECTION_WEAK_PTR_INHERITED(nsDocShell, nsDocLoader, + mScriptGlobal, mInitialClientSource, + mBrowsingContext, + mChromeEventHandler) + +NS_IMPL_ADDREF_INHERITED(nsDocShell, nsDocLoader) +NS_IMPL_RELEASE_INHERITED(nsDocShell, nsDocLoader) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDocShell) + NS_INTERFACE_MAP_ENTRY(nsIDocShell) + NS_INTERFACE_MAP_ENTRY(nsIDocShellTreeItem) + NS_INTERFACE_MAP_ENTRY(nsIWebNavigation) + NS_INTERFACE_MAP_ENTRY(nsIBaseWindow) + NS_INTERFACE_MAP_ENTRY(nsIRefreshURI) + NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + NS_INTERFACE_MAP_ENTRY(nsIWebPageDescriptor) + NS_INTERFACE_MAP_ENTRY(nsIAuthPromptProvider) + NS_INTERFACE_MAP_ENTRY(nsILoadContext) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsINetworkInterceptController, + mInterceptController) + NS_INTERFACE_MAP_ENTRY(nsIDeprecationWarner) +NS_INTERFACE_MAP_END_INHERITING(nsDocLoader) + +NS_IMETHODIMP +nsDocShell::GetInterface(const nsIID& aIID, void** aSink) { + MOZ_ASSERT(aSink, "null out param"); + + *aSink = nullptr; + + if (aIID.Equals(NS_GET_IID(nsICommandManager))) { + NS_ENSURE_SUCCESS(EnsureCommandHandler(), NS_ERROR_FAILURE); + *aSink = static_cast(mCommandManager.get()); + } else if (aIID.Equals(NS_GET_IID(nsIURIContentListener))) { + *aSink = mContentListener; + } else if ((aIID.Equals(NS_GET_IID(nsIScriptGlobalObject)) || + aIID.Equals(NS_GET_IID(nsIGlobalObject)) || + aIID.Equals(NS_GET_IID(nsPIDOMWindowOuter)) || + aIID.Equals(NS_GET_IID(mozIDOMWindowProxy)) || + aIID.Equals(NS_GET_IID(nsIDOMWindow))) && + NS_SUCCEEDED(EnsureScriptEnvironment())) { + return mScriptGlobal->QueryInterface(aIID, aSink); + } else if (aIID.Equals(NS_GET_IID(Document)) && + NS_SUCCEEDED(EnsureContentViewer())) { + RefPtr doc = mContentViewer->GetDocument(); + doc.forget(aSink); + return *aSink ? NS_OK : NS_NOINTERFACE; + } else if (aIID.Equals(NS_GET_IID(nsIApplicationCacheContainer))) { + *aSink = nullptr; + + // Return application cache associated with this docshell, if any + + nsCOMPtr contentViewer; + GetContentViewer(getter_AddRefs(contentViewer)); + if (!contentViewer) { + return NS_ERROR_NO_INTERFACE; + } + + RefPtr doc = contentViewer->GetDocument(); + NS_ASSERTION(doc, "Should have a document."); + if (!doc) { + return NS_ERROR_NO_INTERFACE; + } + +#if defined(DEBUG) + MOZ_LOG( + gDocShellLog, LogLevel::Debug, + ("nsDocShell[%p]: returning app cache container %p", this, doc.get())); +#endif + return doc->QueryInterface(aIID, aSink); + } else if (aIID.Equals(NS_GET_IID(nsIPrompt)) && + NS_SUCCEEDED(EnsureScriptEnvironment())) { + nsresult rv; + nsCOMPtr wwatch = + do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Get the an auth prompter for our window so that the parenting + // of the dialogs works as it should when using tabs. + nsIPrompt* prompt; + rv = wwatch->GetNewPrompter(mScriptGlobal, &prompt); + NS_ENSURE_SUCCESS(rv, rv); + + *aSink = prompt; + return NS_OK; + } else if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) || + aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) { + return NS_SUCCEEDED(GetAuthPrompt(PROMPT_NORMAL, aIID, aSink)) + ? NS_OK + : NS_NOINTERFACE; + } else if (aIID.Equals(NS_GET_IID(nsISHistory))) { + // This is deprecated, you should instead directly get + // ChildSHistory from the browsing context. + MOZ_DIAGNOSTIC_ASSERT( + false, "Do not try to get a nsISHistory interface from nsIDocShell"); + return NS_NOINTERFACE; + } else if (aIID.Equals(NS_GET_IID(nsIWebBrowserFind))) { + nsresult rv = EnsureFind(); + if (NS_FAILED(rv)) { + return rv; + } + + *aSink = mFind; + NS_ADDREF((nsISupports*)*aSink); + return NS_OK; + } else if (aIID.Equals(NS_GET_IID(nsISelectionDisplay))) { + if (PresShell* presShell = GetPresShell()) { + return presShell->QueryInterface(aIID, aSink); + } + } else if (aIID.Equals(NS_GET_IID(nsIDocShellTreeOwner))) { + nsCOMPtr treeOwner; + nsresult rv = GetTreeOwner(getter_AddRefs(treeOwner)); + if (NS_SUCCEEDED(rv) && treeOwner) { + return treeOwner->QueryInterface(aIID, aSink); + } + } else if (aIID.Equals(NS_GET_IID(nsIBrowserChild))) { + *aSink = GetBrowserChild().take(); + return *aSink ? NS_OK : NS_ERROR_FAILURE; + } else { + return nsDocLoader::GetInterface(aIID, aSink); + } + + NS_IF_ADDREF(((nsISupports*)*aSink)); + return *aSink ? NS_OK : NS_NOINTERFACE; +} + +NS_IMETHODIMP +nsDocShell::SetCancelContentJSEpoch(int32_t aEpoch) { + // Note: this gets called fairly early (before a pageload actually starts). + // We could probably defer this even longer. + nsCOMPtr browserChild = GetBrowserChild(); + static_cast(browserChild.get()) + ->SetCancelContentJSEpoch(aEpoch); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::LoadURI(nsDocShellLoadState* aLoadState, bool aSetNavigating) { + return LoadURI(aLoadState, aSetNavigating, false); +} + +nsresult nsDocShell::LoadURI(nsDocShellLoadState* aLoadState, + bool aSetNavigating, + bool aContinueHandlingSubframeHistory) { + MOZ_ASSERT(aLoadState, "Must have a valid load state!"); + MOZ_ASSERT( + (aLoadState->LoadFlags() & INTERNAL_LOAD_FLAGS_LOADURI_SETUP_FLAGS) == 0, + "Should not have these flags set"); + MOZ_ASSERT(aLoadState->TargetBrowsingContext().IsNull(), + "Targeting doesn't occur until InternalLoad"); + + if (!aLoadState->TriggeringPrincipal()) { + MOZ_ASSERT(false, "LoadURI must have a triggering principal"); + return NS_ERROR_FAILURE; + } + + bool oldIsNavigating = mIsNavigating; + auto cleanupIsNavigating = + MakeScopeExit([&]() { mIsNavigating = oldIsNavigating; }); + if (aSetNavigating) { + mIsNavigating = true; + } + + PopupBlocker::PopupControlState popupState = PopupBlocker::openOverridden; + if (aLoadState->LoadFlags() & LOAD_FLAGS_ALLOW_POPUPS) { + popupState = PopupBlocker::openAllowed; + // If we allow popups as part of the navigation, ensure we fake a user + // interaction, so that popups can, in fact, be allowed to open. + if (WindowContext* wc = mBrowsingContext->GetCurrentWindowContext()) { + wc->NotifyUserGestureActivation(); + } + } + + AutoPopupStatePusher statePusher(popupState); + + if (aLoadState->GetCancelContentJSEpoch().isSome()) { + SetCancelContentJSEpoch(*aLoadState->GetCancelContentJSEpoch()); + } + + // Note: we allow loads to get through here even if mFiredUnloadEvent is + // true; that case will get handled in LoadInternal or LoadHistoryEntry, + // so we pass false as the second parameter to IsNavigationAllowed. + // However, we don't allow the page to change location *in the middle of* + // firing beforeunload, so we do need to check if *beforeunload* is currently + // firing, so we call IsNavigationAllowed rather than just IsPrintingOrPP. + if (!IsNavigationAllowed(true, false)) { + return NS_OK; // JS may not handle returning of an error code + } + + nsLoadFlags defaultLoadFlags = mBrowsingContext->GetDefaultLoadFlags(); + if (aLoadState->LoadFlags() & LOAD_FLAGS_FORCE_TRR) { + defaultLoadFlags |= nsIRequest::LOAD_TRR_ONLY_MODE; + } else if (aLoadState->LoadFlags() & LOAD_FLAGS_DISABLE_TRR) { + defaultLoadFlags |= nsIRequest::LOAD_TRR_DISABLED_MODE; + } + + MOZ_ALWAYS_SUCCEEDS(mBrowsingContext->SetDefaultLoadFlags(defaultLoadFlags)); + + if (!StartupTimeline::HasRecord(StartupTimeline::FIRST_LOAD_URI) && + mItemType == typeContent && !NS_IsAboutBlank(aLoadState->URI())) { + StartupTimeline::RecordOnce(StartupTimeline::FIRST_LOAD_URI); + } + + // LoadType used to be set to a default value here, if no LoadInfo/LoadState + // object was passed in. That functionality has been removed as of bug + // 1492648. LoadType should now be set up by the caller at the time they + // create their nsDocShellLoadState object to pass into LoadURI. + + MOZ_LOG( + gDocShellLeakLog, LogLevel::Debug, + ("nsDocShell[%p]: loading %s with flags 0x%08x", this, + aLoadState->URI()->GetSpecOrDefault().get(), aLoadState->LoadFlags())); + + if ((!aLoadState->LoadIsFromSessionHistory() && + !LOAD_TYPE_HAS_FLAGS(aLoadState->LoadType(), + LOAD_FLAGS_REPLACE_HISTORY)) || + aContinueHandlingSubframeHistory) { + // This is possibly a subframe, so handle it accordingly. + // + // If history exists, it will be loaded into the aLoadState object, and the + // LoadType will be changed. + if (MaybeHandleSubframeHistory(aLoadState, + aContinueHandlingSubframeHistory)) { + // MaybeHandleSubframeHistory returns true if we need to continue loading + // asynchronously. + return NS_OK; + } + } + + if (aLoadState->LoadIsFromSessionHistory()) { + MOZ_LOG(gSHLog, LogLevel::Debug, + ("nsDocShell[%p]: loading from session history", this)); + + if (!mozilla::SessionHistoryInParent()) { + return LoadHistoryEntry(aLoadState->SHEntry(), aLoadState->LoadType()); + } + + // FIXME Null check aLoadState->GetLoadingSessionHistoryInfo()? + return LoadHistoryEntry(*aLoadState->GetLoadingSessionHistoryInfo(), + aLoadState->LoadType()); + } + + // On history navigation via Back/Forward buttons, don't execute + // automatic JavaScript redirection such as |location.href = ...| or + // |window.open()| + // + // LOAD_NORMAL: window.open(...) etc. + // LOAD_STOP_CONTENT: location.href = ..., location.assign(...) + if ((aLoadState->LoadType() == LOAD_NORMAL || + aLoadState->LoadType() == LOAD_STOP_CONTENT) && + ShouldBlockLoadingForBackButton()) { + return NS_OK; + } + + BrowsingContext::Type bcType = mBrowsingContext->GetType(); + + // Set up the inheriting principal in LoadState. + nsresult rv = aLoadState->SetupInheritingPrincipal( + bcType, mBrowsingContext->OriginAttributesRef()); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aLoadState->SetupTriggeringPrincipal( + mBrowsingContext->OriginAttributesRef()); + NS_ENSURE_SUCCESS(rv, rv); + + aLoadState->CalculateLoadURIFlags(); + + MOZ_ASSERT(aLoadState->TypeHint().IsVoid(), + "Typehint should be null when calling InternalLoad from LoadURI"); + MOZ_ASSERT(aLoadState->FileName().IsVoid(), + "FileName should be null when calling InternalLoad from LoadURI"); + MOZ_ASSERT(!aLoadState->LoadIsFromSessionHistory(), + "Shouldn't be loading from an entry when calling InternalLoad " + "from LoadURI"); + + rv = InternalLoad(aLoadState); + NS_ENSURE_SUCCESS(rv, rv); + + if (aLoadState->GetOriginalURIString().isSome()) { + // Save URI string in case it's needed later when + // sending to search engine service in EndPageLoad() + mOriginalUriString = *aLoadState->GetOriginalURIString(); + } + + return NS_OK; +} + +bool nsDocShell::IsLoadingFromSessionHistory() { + return mActiveEntryIsLoadingFromSessionHistory; +} + +bool nsDocShell::MaybeHandleSubframeHistory( + nsDocShellLoadState* aLoadState, bool aContinueHandlingSubframeHistory) { + // First, verify if this is a subframe. + // Note, it is ok to rely on docshell here and not browsing context since when + // an iframe is created, it has first in-process docshell. + nsCOMPtr parentAsItem; + GetInProcessSameTypeParent(getter_AddRefs(parentAsItem)); + nsCOMPtr parentDS(do_QueryInterface(parentAsItem)); + + if (!parentDS || parentDS == static_cast(this)) { + if (mBrowsingContext && mBrowsingContext->IsTop()) { + // This is the root docshell. If we got here while + // executing an onLoad Handler,this load will not go + // into session history. + // XXX Why is this code in a method which deals with iframes! + bool inOnLoadHandler = false; + GetIsExecutingOnLoadHandler(&inOnLoadHandler); + if (inOnLoadHandler) { + aLoadState->SetLoadType(LOAD_NORMAL_REPLACE); + } + } + return false; + } + + /* OK. It is a subframe. Checkout the parent's loadtype. If the parent was + * loaded through a history mechanism, then get the SH entry for the child + * from the parent. This is done to restore frameset navigation while going + * back/forward. If the parent was loaded through any other loadType, set the + * child's loadType too accordingly, so that session history does not get + * confused. + */ + + // Get the parent's load type + uint32_t parentLoadType; + parentDS->GetLoadType(&parentLoadType); + + if (!aContinueHandlingSubframeHistory) { + if (mozilla::SessionHistoryInParent()) { + if (nsDocShell::Cast(parentDS.get())->IsLoadingFromSessionHistory() && + !GetCreatedDynamically()) { + if (XRE_IsContentProcess()) { + dom::ContentChild* contentChild = dom::ContentChild::GetSingleton(); + if (contentChild) { + RefPtr parentDoc = parentDS->GetDocument(); + parentDoc->BlockOnload(); + RefPtr browsingContext = mBrowsingContext; + Maybe currentLoadIdentifier = + mBrowsingContext->GetCurrentLoadIdentifier(); + RefPtr loadState = aLoadState; + bool isNavigating = mIsNavigating; + + auto resolve = + [currentLoadIdentifier, browsingContext, parentDoc, loadState, + isNavigating](Tuple, + int32_t, int32_t>&& aResult) { + if (currentLoadIdentifier == + browsingContext->GetCurrentLoadIdentifier() && + Get<0>(aResult).isSome()) { + loadState->SetLoadingSessionHistoryInfo( + Get<0>(aResult).value()); + loadState->SetLoadIsFromSessionHistory( + Get<1>(aResult), Get<2>(aResult), false); + } + RefPtr docShell = + static_cast(browsingContext->GetDocShell()); + if (docShell) { + // We got the results back from the parent process, call + // LoadURI again with the possibly updated data. + docShell->LoadURI(loadState, isNavigating, true); + } + parentDoc->UnblockOnload(false); + }; + auto reject = [parentDoc](mozilla::ipc::ResponseRejectReason) { + parentDoc->UnblockOnload(false); + }; + contentChild->SendGetLoadingSessionHistoryInfoFromParent( + mBrowsingContext, std::move(resolve), std::move(reject)); + return true; + } + } else { + Maybe info; + int32_t requestedIndex = -1; + int32_t sessionHistoryLength = 0; + mBrowsingContext->Canonical()->GetLoadingSessionHistoryInfoFromParent( + info, &requestedIndex, &sessionHistoryLength); + if (info.isSome()) { + aLoadState->SetLoadingSessionHistoryInfo(info.value()); + aLoadState->SetLoadIsFromSessionHistory( + requestedIndex, sessionHistoryLength, false); + } + } + } + } else { + // Get the ShEntry for the child from the parent + nsCOMPtr currentSH; + bool oshe = false; + parentDS->GetCurrentSHEntry(getter_AddRefs(currentSH), &oshe); + bool dynamicallyAddedChild = GetCreatedDynamically(); + + if (!dynamicallyAddedChild && !oshe && currentSH) { + // Only use the old SHEntry, if we're sure enough that + // it wasn't originally for some other frame. + nsCOMPtr shEntry; + currentSH->GetChildSHEntryIfHasNoDynamicallyAddedChild( + mChildOffset, getter_AddRefs(shEntry)); + if (shEntry) { + aLoadState->SetSHEntry(shEntry); + } + } + } + } + + // Make some decisions on the child frame's loadType based on the + // parent's loadType, if the subframe hasn't loaded anything into it. + // + // In some cases privileged scripts may try to get the DOMWindow + // reference of this docshell before the loading starts, causing the + // initial about:blank content viewer being created and mCurrentURI being + // set. To handle this case we check if mCurrentURI is about:blank and + // currentSHEntry is null. + bool oshe = false; + nsCOMPtr currentChildEntry; + GetCurrentSHEntry(getter_AddRefs(currentChildEntry), &oshe); + + if (mCurrentURI && (!NS_IsAboutBlank(mCurrentURI) || currentChildEntry || + mLoadingEntry || mActiveEntry)) { + // This is a pre-existing subframe. If + // 1. The load of this frame was not originally initiated by session + // history directly (i.e. (!shEntry) condition succeeded, but it can + // still be a history load on parent which causes this frame being + // loaded), which we checked with the above assert, and + // 2. mCurrentURI is not null, nor the initial about:blank, + // it is possible that a parent's onLoadHandler or even self's + // onLoadHandler is loading a new page in this child. Check parent's and + // self's busy flag and if it is set, we don't want this onLoadHandler + // load to get in to session history. + BusyFlags parentBusy = parentDS->GetBusyFlags(); + BusyFlags selfBusy = GetBusyFlags(); + + if (parentBusy & BUSY_FLAGS_BUSY || selfBusy & BUSY_FLAGS_BUSY) { + aLoadState->SetLoadType(LOAD_NORMAL_REPLACE); + aLoadState->ClearLoadIsFromSessionHistory(); + } + return false; + } + + // This is a newly created frame. Check for exception cases first. + // By default the subframe will inherit the parent's loadType. + if (aLoadState->LoadIsFromSessionHistory() && + (parentLoadType == LOAD_NORMAL || parentLoadType == LOAD_LINK || + parentLoadType == LOAD_NORMAL_EXTERNAL)) { + // The parent was loaded normally. In this case, this *brand new* + // child really shouldn't have a SHEntry. If it does, it could be + // because the parent is replacing an existing frame with a new frame, + // in the onLoadHandler. We don't want this url to get into session + // history. Clear off shEntry, and set load type to + // LOAD_BYPASS_HISTORY. + bool inOnLoadHandler = false; + parentDS->GetIsExecutingOnLoadHandler(&inOnLoadHandler); + if (inOnLoadHandler) { + aLoadState->SetLoadType(LOAD_NORMAL_REPLACE); + aLoadState->ClearLoadIsFromSessionHistory(); + } + } else if (parentLoadType == LOAD_REFRESH) { + // Clear shEntry. For refresh loads, we have to load + // what comes through the pipe, not what's in history. + aLoadState->ClearLoadIsFromSessionHistory(); + } else if ((parentLoadType == LOAD_BYPASS_HISTORY) || + (aLoadState->LoadIsFromSessionHistory() && + ((parentLoadType & LOAD_CMD_HISTORY) || + (parentLoadType == LOAD_RELOAD_NORMAL) || + (parentLoadType == LOAD_RELOAD_CHARSET_CHANGE) || + (parentLoadType == LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE) || + (parentLoadType == + LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE)))) { + // If the parent url, bypassed history or was loaded from + // history, pass on the parent's loadType to the new child + // frame too, so that the child frame will also + // avoid getting into history. + aLoadState->SetLoadType(parentLoadType); + } else if (parentLoadType == LOAD_ERROR_PAGE) { + // If the parent document is an error page, we don't + // want to update global/session history. However, + // this child frame is not an error page. + aLoadState->SetLoadType(LOAD_BYPASS_HISTORY); + } else if ((parentLoadType == LOAD_RELOAD_BYPASS_CACHE) || + (parentLoadType == LOAD_RELOAD_BYPASS_PROXY) || + (parentLoadType == LOAD_RELOAD_BYPASS_PROXY_AND_CACHE)) { + // the new frame should inherit the parent's load type so that it also + // bypasses the cache and/or proxy + aLoadState->SetLoadType(parentLoadType); + } + + return false; +} + +/* + * Reset state to a new content model within the current document and the + * document viewer. Called by the document before initiating an out of band + * document.write(). + */ +NS_IMETHODIMP +nsDocShell::PrepareForNewContentModel() { + // Clear out our form control state, because the state of controls + // in the pre-open() document should not affect the state of + // controls that are now going to be written. + SetLayoutHistoryState(nullptr); + mEODForCurrentDocument = false; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::FirePageHideNotification(bool aIsUnload) { + FirePageHideNotificationInternal(aIsUnload, false); + return NS_OK; +} + +void nsDocShell::FirePageHideNotificationInternal( + bool aIsUnload, bool aSkipCheckingDynEntries) { + if (mContentViewer && !mFiredUnloadEvent) { + // Keep an explicit reference since calling PageHide could release + // mContentViewer + nsCOMPtr contentViewer(mContentViewer); + mFiredUnloadEvent = true; + + if (mTiming) { + mTiming->NotifyUnloadEventStart(); + } + + contentViewer->PageHide(aIsUnload); + + if (mTiming) { + mTiming->NotifyUnloadEventEnd(); + } + + AutoTArray, 8> kids; + uint32_t n = mChildList.Length(); + kids.SetCapacity(n); + for (uint32_t i = 0; i < n; i++) { + kids.AppendElement(do_QueryInterface(ChildAt(i))); + } + + n = kids.Length(); + for (uint32_t i = 0; i < n; ++i) { + RefPtr child = static_cast(kids[i].get()); + if (child) { + // Skip checking dynamic subframe entries in our children. + child->FirePageHideNotificationInternal(aIsUnload, true); + } + } + + // If the document is unloading, remove all dynamic subframe entries. + if (aIsUnload && !aSkipCheckingDynEntries) { + RefPtr rootSH = GetRootSessionHistory(); + if (rootSH) { + MOZ_LOG( + gSHLog, LogLevel::Debug, + ("nsDocShell %p unloading, remove dynamic subframe entries", this)); + if (mozilla::SessionHistoryInParent()) { + if (mActiveEntry) { + mBrowsingContext->RemoveDynEntriesFromActiveSessionHistoryEntry(); + } + MOZ_LOG(gSHLog, LogLevel::Debug, + ("nsDocShell %p unloading, no active entries", this)); + } else if (mOSHE) { + int32_t index = rootSH->Index(); + rootSH->LegacySHistory()->RemoveDynEntries(index, mOSHE); + } + } + } + + // Now make sure our editor, if any, is detached before we go + // any farther. + DetachEditorFromWindow(); + } +} + +nsresult nsDocShell::Dispatch(TaskCategory aCategory, + already_AddRefed&& aRunnable) { + nsCOMPtr runnable(aRunnable); + nsCOMPtr win = GetWindow(); + if (NS_WARN_IF(!win)) { + // Window should only be unavailable after destroyed. + MOZ_ASSERT(mIsBeingDestroyed); + return NS_ERROR_FAILURE; + } + + if (win->GetDocGroup()) { + return win->GetDocGroup()->Dispatch(aCategory, runnable.forget()); + } + + return SchedulerGroup::Dispatch(aCategory, runnable.forget()); +} + +NS_IMETHODIMP +nsDocShell::DispatchLocationChangeEvent() { + return Dispatch( + TaskCategory::Other, + NewRunnableMethod("nsDocShell::FireDummyOnLocationChange", this, + &nsDocShell::FireDummyOnLocationChange)); +} + +NS_IMETHODIMP +nsDocShell::StartDelayedAutoplayMediaComponents() { + RefPtr outerWindow = GetWindow(); + if (outerWindow) { + outerWindow->SetMediaSuspend(nsISuspendedTypes::NONE_SUSPENDED); + } + return NS_OK; +} + +bool nsDocShell::MaybeInitTiming() { + if (mTiming && !mBlankTiming) { + return false; + } + + bool canBeReset = false; + + if (mScriptGlobal && mBlankTiming) { + nsPIDOMWindowInner* innerWin = mScriptGlobal->GetCurrentInnerWindow(); + if (innerWin && innerWin->GetPerformance()) { + mTiming = innerWin->GetPerformance()->GetDOMTiming(); + mBlankTiming = false; + } + } + + if (!mTiming) { + mTiming = new nsDOMNavigationTiming(this); + canBeReset = true; + } + + mTiming->NotifyNavigationStart( + mBrowsingContext->IsActive() + ? nsDOMNavigationTiming::DocShellState::eActive + : nsDOMNavigationTiming::DocShellState::eInactive); + + return canBeReset; +} + +void nsDocShell::MaybeResetInitTiming(bool aReset) { + if (aReset) { + mTiming = nullptr; + } +} + +nsDOMNavigationTiming* nsDocShell::GetNavigationTiming() const { + return mTiming; +} + +// +// Bug 13871: Prevent frameset spoofing +// +// This routine answers: 'Is origin's document from same domain as +// target's document?' +// +// file: uris are considered the same domain for the purpose of +// frame navigation regardless of script accessibility (bug 420425) +// +/* static */ +bool nsDocShell::ValidateOrigin(BrowsingContext* aOrigin, + BrowsingContext* aTarget) { + nsIDocShell* originDocShell = aOrigin->GetDocShell(); + MOZ_ASSERT(originDocShell, "originDocShell must not be null"); + Document* originDocument = originDocShell->GetDocument(); + NS_ENSURE_TRUE(originDocument, false); + + nsIDocShell* targetDocShell = aTarget->GetDocShell(); + MOZ_ASSERT(targetDocShell, "targetDocShell must not be null"); + Document* targetDocument = targetDocShell->GetDocument(); + NS_ENSURE_TRUE(targetDocument, false); + + bool equal; + nsresult rv = originDocument->NodePrincipal()->Equals( + targetDocument->NodePrincipal(), &equal); + if (NS_SUCCEEDED(rv) && equal) { + return true; + } + // Not strictly equal, special case if both are file: uris + nsCOMPtr originURI; + nsCOMPtr targetURI; + nsCOMPtr innerOriginURI; + nsCOMPtr innerTargetURI; + + // Casting to BasePrincipal, as we can't get InnerMost URI otherwise + auto* originDocumentBasePrincipal = + BasePrincipal::Cast(originDocument->NodePrincipal()); + + rv = originDocumentBasePrincipal->GetURI(getter_AddRefs(originURI)); + if (NS_SUCCEEDED(rv) && originURI) { + innerOriginURI = NS_GetInnermostURI(originURI); + } + + auto* targetDocumentBasePrincipal = + BasePrincipal::Cast(targetDocument->NodePrincipal()); + + rv = targetDocumentBasePrincipal->GetURI(getter_AddRefs(targetURI)); + if (NS_SUCCEEDED(rv) && targetURI) { + innerTargetURI = NS_GetInnermostURI(targetURI); + } + + return innerOriginURI && innerTargetURI && SchemeIsFile(innerOriginURI) && + SchemeIsFile(innerTargetURI); +} + +nsPresContext* nsDocShell::GetEldestPresContext() { + nsIContentViewer* viewer = mContentViewer; + while (viewer) { + nsIContentViewer* prevViewer = viewer->GetPreviousViewer(); + if (!prevViewer) { + return viewer->GetPresContext(); + } + viewer = prevViewer; + } + + return nullptr; +} + +nsPresContext* nsDocShell::GetPresContext() { + if (!mContentViewer) { + return nullptr; + } + + return mContentViewer->GetPresContext(); +} + +PresShell* nsDocShell::GetPresShell() { + nsPresContext* presContext = GetPresContext(); + return presContext ? presContext->GetPresShell() : nullptr; +} + +PresShell* nsDocShell::GetEldestPresShell() { + nsPresContext* presContext = GetEldestPresContext(); + + if (presContext) { + return presContext->GetPresShell(); + } + + return nullptr; +} + +NS_IMETHODIMP +nsDocShell::GetContentViewer(nsIContentViewer** aContentViewer) { + NS_ENSURE_ARG_POINTER(aContentViewer); + + *aContentViewer = mContentViewer; + NS_IF_ADDREF(*aContentViewer); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetOuterWindowID(uint64_t* aWindowID) { + *aWindowID = mContentWindowID; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetChromeEventHandler(EventTarget* aChromeEventHandler) { + mChromeEventHandler = aChromeEventHandler; + + if (mScriptGlobal) { + mScriptGlobal->SetChromeEventHandler(mChromeEventHandler); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetChromeEventHandler(EventTarget** aChromeEventHandler) { + NS_ENSURE_ARG_POINTER(aChromeEventHandler); + RefPtr handler = mChromeEventHandler; + handler.forget(aChromeEventHandler); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetCurrentURI(nsIURI* aURI) { + // Note that securityUI will set STATE_IS_INSECURE, even if + // the scheme of |aURI| is "https". + SetCurrentURI(aURI, nullptr, true, 0); + return NS_OK; +} + +bool nsDocShell::SetCurrentURI(nsIURI* aURI, nsIRequest* aRequest, + bool aFireOnLocationChange, + uint32_t aLocationFlags) { + MOZ_ASSERT(!mIsBeingDestroyed); + + MOZ_LOG(gDocShellLeakLog, LogLevel::Debug, + ("DOCSHELL %p SetCurrentURI %s\n", this, + aURI ? aURI->GetSpecOrDefault().get() : "")); + + // We don't want to send a location change when we're displaying an error + // page, and we don't want to change our idea of "current URI" either + if (mLoadType == LOAD_ERROR_PAGE) { + return false; + } + + bool uriIsEqual = false; + if (!mCurrentURI || !aURI || + NS_FAILED(mCurrentURI->Equals(aURI, &uriIsEqual)) || !uriIsEqual) { + mTitleValidForCurrentURI = false; + } + + mCurrentURI = aURI; + +#ifdef DEBUG + mLastOpenedURI = aURI; +#endif + + if (!NS_IsAboutBlank(mCurrentURI)) { + mHasLoadedNonBlankURI = true; + } + + bool isRoot = mBrowsingContext->IsTop(); + bool isSubFrame = false; // Is this a subframe navigation? + + if (mozilla::SessionHistoryInParent()) { + if (mLoadingEntry) { + isSubFrame = mLoadingEntry->mInfo.IsSubFrame(); + } else { + isSubFrame = !mBrowsingContext->IsTop() && mActiveEntry; + } + MOZ_LOG(gSHLog, LogLevel::Debug, + ("nsDocShell %p SetCurrentURI, isSubFrame=%d", this, isSubFrame)); + } else { + if (mLSHE) { + isSubFrame = mLSHE->GetIsSubFrame(); + } + } + + if (!isSubFrame && !isRoot) { + /* + * We don't want to send OnLocationChange notifications when + * a subframe is being loaded for the first time, while + * visiting a frameset page + */ + return false; + } + + if (aFireOnLocationChange) { + FireOnLocationChange(this, aRequest, aURI, aLocationFlags); + } + return !aFireOnLocationChange; +} + +NS_IMETHODIMP +nsDocShell::GetCharset(nsACString& aCharset) { + aCharset.Truncate(); + + PresShell* presShell = GetPresShell(); + NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); + Document* doc = presShell->GetDocument(); + NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); + doc->GetDocumentCharacterSet()->Name(aCharset); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GatherCharsetMenuTelemetry() { + nsCOMPtr viewer; + GetContentViewer(getter_AddRefs(viewer)); + if (!viewer) { + return NS_OK; + } + + Document* doc = viewer->GetDocument(); + if (!doc || doc->WillIgnoreCharsetOverride()) { + return NS_OK; + } + + if (mForcedAutodetection) { + LOGCHARSETMENU(("ENCODING_OVERRIDE_USED_AUTOMATIC")); + Telemetry::ScalarSet(Telemetry::ScalarID::ENCODING_OVERRIDE_USED_AUTOMATIC, + true); + } else { + LOGCHARSETMENU(("ENCODING_OVERRIDE_USED_MANUAL")); + Telemetry::ScalarSet(Telemetry::ScalarID::ENCODING_OVERRIDE_USED_MANUAL, + true); + } + + nsIURI* url = doc->GetOriginalURI(); + bool isFileURL = url && SchemeIsFile(url); + + int32_t charsetSource = doc->GetDocumentCharacterSetSource(); + auto encoding = doc->GetDocumentCharacterSet(); + switch (charsetSource) { + case kCharsetFromInitialUserForcedAutoDetection: + case kCharsetFromFinalUserForcedAutoDetection: + LOGCHARSETMENU(("AutoOverridden")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::AutoOverridden); + break; + case kCharsetFromUserForced: + case kCharsetFromUserForcedJapaneseAutoDetection: + LOGCHARSETMENU(("ManuallyOverridden")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::ManuallyOverridden); + break; + case kCharsetFromTopLevelDomain: + if (encoding == WINDOWS_1252_ENCODING) { + LOGCHARSETMENU(("UnlabeledInLk")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::UnlabeledInLk); + } else { + LOGCHARSETMENU(("UnlabeledJp")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::UnlabeledJp); + } + break; + case kCharsetFromFinalJapaneseAutoDetection: + LOGCHARSETMENU(("UnlabeledJp")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::UnlabeledJp); + break; + case kCharsetFromInitialAutoDetectionASCII: + // Deliberately no final version + LOGCHARSETMENU(("UnlabeledAscii")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::UnlabeledAscii); + break; + case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8Generic: + case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8Generic: + case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8Content: + case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8Content: + LOGCHARSETMENU(("UnlabeledNonUtf8")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::UnlabeledNonUtf8); + break; + case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD: + case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD: + LOGCHARSETMENU(("UnlabeledNonUtf8TLD")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::UnlabeledNonUtf8TLD); + break; + case kCharsetFromInitialAutoDetectionWouldHaveBeenUTF8: + case kCharsetFromFinalAutoDetectionWouldHaveBeenUTF8: + LOGCHARSETMENU(("UnlabeledUtf8")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::UnlabeledUtf8); + break; + case kCharsetFromChannel: + if (encoding == UTF_8_ENCODING) { + LOGCHARSETMENU(("ChannelUtf8")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::ChannelUtf8); + } else { + LOGCHARSETMENU(("ChannelNonUtf8")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::ChannelNonUtf8); + } + break; + case kCharsetFromMetaPrescan: + case kCharsetFromMetaTag: + if (isFileURL) { + LOGCHARSETMENU(("LocalLabeled")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::LocalLabeled); + } else if (encoding == UTF_8_ENCODING) { + LOGCHARSETMENU(("MetaUtf8")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::MetaUtf8); + } else { + LOGCHARSETMENU(("MetaNonUtf8")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::MetaNonUtf8); + } + break; + case kCharsetFromFinalAutoDetectionFile: + if (isFileURL) { + LOGCHARSETMENU(("LocalUnlabeled")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::LocalUnlabeled); + } else { + LOGCHARSETMENU(("Bug")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::Bug); + } + break; + default: + LOGCHARSETMENU(("Bug")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::Bug); + break; + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetCharset(const nsACString& aCharset) { + mForcedAutodetection = false; + if (aCharset.IsEmpty()) { + mForcedCharset = nullptr; + return NS_OK; + } + if (aCharset.EqualsLiteral("_autodetect_all")) { + mForcedCharset = WINDOWS_1252_ENCODING; + mForcedAutodetection = true; + return NS_OK; + } + const Encoding* encoding = Encoding::ForLabel(aCharset); + if (!encoding) { + // Reject unknown labels + return NS_ERROR_INVALID_ARG; + } + if (!encoding->IsAsciiCompatible() && encoding != ISO_2022_JP_ENCODING) { + // Reject XSS hazards + return NS_ERROR_INVALID_ARG; + } + mForcedCharset = encoding; + return NS_OK; +} + +void nsDocShell::SetParentCharset(const Encoding*& aCharset, + int32_t aCharsetSource, + nsIPrincipal* aPrincipal) { + mParentCharset = aCharset; + mParentCharsetSource = aCharsetSource; + mParentCharsetPrincipal = aPrincipal; +} + +void nsDocShell::GetParentCharset(const Encoding*& aCharset, + int32_t* aCharsetSource, + nsIPrincipal** aPrincipal) { + aCharset = mParentCharset; + *aCharsetSource = mParentCharsetSource; + NS_IF_ADDREF(*aPrincipal = mParentCharsetPrincipal); +} + +NS_IMETHODIMP +nsDocShell::GetHasTrackingContentBlocked(Promise** aPromise) { + MOZ_ASSERT(aPromise); + + ErrorResult rv; + RefPtr doc(GetDocument()); + RefPtr retPromise = Promise::Create(doc->GetOwnerGlobal(), rv); + if (NS_WARN_IF(rv.Failed())) { + return rv.StealNSResult(); + } + + // Retrieve the document's content blocking events from the parent process. + RefPtr promise = + doc->GetContentBlockingEvents(); + if (promise) { + promise->Then( + GetCurrentSerialEventTarget(), __func__, + [retPromise](const Document::GetContentBlockingEventsPromise:: + ResolveOrRejectValue& aValue) { + if (aValue.IsResolve()) { + bool has = aValue.ResolveValue() & + nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT; + retPromise->MaybeResolve(has); + } else { + retPromise->MaybeResolve(false); + } + }); + } else { + retPromise->MaybeResolve(false); + } + + retPromise.forget(aPromise); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAllowPlugins(bool* aAllowPlugins) { + NS_ENSURE_ARG_POINTER(aAllowPlugins); + + *aAllowPlugins = mBrowsingContext->GetAllowPlugins(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowPlugins(bool aAllowPlugins) { + // XXX should enable or disable a plugin host + return mBrowsingContext->SetAllowPlugins(aAllowPlugins); +} + +NS_IMETHODIMP +nsDocShell::GetAllowJavascript(bool* aAllowJavascript) { + NS_ENSURE_ARG_POINTER(aAllowJavascript); + + *aAllowJavascript = mAllowJavascript; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetCssErrorReportingEnabled(bool* aEnabled) { + MOZ_ASSERT(aEnabled); + *aEnabled = mCSSErrorReportingEnabled; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetCssErrorReportingEnabled(bool aEnabled) { + mCSSErrorReportingEnabled = aEnabled; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowJavascript(bool aAllowJavascript) { + mAllowJavascript = aAllowJavascript; + RecomputeCanExecuteScripts(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetUsePrivateBrowsing(bool* aUsePrivateBrowsing) { + NS_ENSURE_ARG_POINTER(aUsePrivateBrowsing); + return mBrowsingContext->GetUsePrivateBrowsing(aUsePrivateBrowsing); +} + +void nsDocShell::NotifyPrivateBrowsingChanged() { + MOZ_ASSERT(!mIsBeingDestroyed); + + if (mAffectPrivateSessionLifetime) { + if (UsePrivateBrowsing()) { + IncreasePrivateDocShellCount(); + } else { + DecreasePrivateDocShellCount(); + } + } + + nsTObserverArray::ForwardIterator iter(mPrivacyObservers); + while (iter.HasMore()) { + nsWeakPtr ref = iter.GetNext(); + nsCOMPtr obs = do_QueryReferent(ref); + if (!obs) { + iter.Remove(); + } else { + obs->PrivateModeChanged(UsePrivateBrowsing()); + } + } +} + +NS_IMETHODIMP +nsDocShell::SetUsePrivateBrowsing(bool aUsePrivateBrowsing) { + return mBrowsingContext->SetUsePrivateBrowsing(aUsePrivateBrowsing); +} + +NS_IMETHODIMP +nsDocShell::SetPrivateBrowsing(bool aUsePrivateBrowsing) { + return mBrowsingContext->SetPrivateBrowsing(aUsePrivateBrowsing); +} + +NS_IMETHODIMP +nsDocShell::GetHasLoadedNonBlankURI(bool* aResult) { + NS_ENSURE_ARG_POINTER(aResult); + + *aResult = mHasLoadedNonBlankURI; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetUseRemoteTabs(bool* aUseRemoteTabs) { + NS_ENSURE_ARG_POINTER(aUseRemoteTabs); + return mBrowsingContext->GetUseRemoteTabs(aUseRemoteTabs); +} + +NS_IMETHODIMP +nsDocShell::SetRemoteTabs(bool aUseRemoteTabs) { + return mBrowsingContext->SetRemoteTabs(aUseRemoteTabs); +} + +NS_IMETHODIMP +nsDocShell::GetUseRemoteSubframes(bool* aUseRemoteSubframes) { + NS_ENSURE_ARG_POINTER(aUseRemoteSubframes); + return mBrowsingContext->GetUseRemoteSubframes(aUseRemoteSubframes); +} + +NS_IMETHODIMP +nsDocShell::SetRemoteSubframes(bool aUseRemoteSubframes) { + return mBrowsingContext->SetRemoteSubframes(aUseRemoteSubframes); +} + +NS_IMETHODIMP +nsDocShell::SetAffectPrivateSessionLifetime(bool aAffectLifetime) { + MOZ_ASSERT(!mIsBeingDestroyed); + + bool change = aAffectLifetime != mAffectPrivateSessionLifetime; + if (change && UsePrivateBrowsing()) { + if (aAffectLifetime) { + IncreasePrivateDocShellCount(); + } else { + DecreasePrivateDocShellCount(); + } + } + mAffectPrivateSessionLifetime = aAffectLifetime; + + for (auto* child : mChildList.ForwardRange()) { + nsCOMPtr shell = do_QueryObject(child); + if (shell) { + shell->SetAffectPrivateSessionLifetime(aAffectLifetime); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAffectPrivateSessionLifetime(bool* aAffectLifetime) { + *aAffectLifetime = mAffectPrivateSessionLifetime; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::AddWeakPrivacyTransitionObserver( + nsIPrivacyTransitionObserver* aObserver) { + nsWeakPtr weakObs = do_GetWeakReference(aObserver); + if (!weakObs) { + return NS_ERROR_NOT_AVAILABLE; + } + mPrivacyObservers.AppendElement(weakObs); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::AddWeakReflowObserver(nsIReflowObserver* aObserver) { + nsWeakPtr weakObs = do_GetWeakReference(aObserver); + if (!weakObs) { + return NS_ERROR_FAILURE; + } + mReflowObservers.AppendElement(weakObs); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::RemoveWeakReflowObserver(nsIReflowObserver* aObserver) { + nsWeakPtr obs = do_GetWeakReference(aObserver); + return mReflowObservers.RemoveElement(obs) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsDocShell::NotifyReflowObservers(bool aInterruptible, + DOMHighResTimeStamp aStart, + DOMHighResTimeStamp aEnd) { + nsTObserverArray::ForwardIterator iter(mReflowObservers); + while (iter.HasMore()) { + nsWeakPtr ref = iter.GetNext(); + nsCOMPtr obs = do_QueryReferent(ref); + if (!obs) { + iter.Remove(); + } else if (aInterruptible) { + obs->ReflowInterruptible(aStart, aEnd); + } else { + obs->Reflow(aStart, aEnd); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAllowMetaRedirects(bool* aReturn) { + NS_ENSURE_ARG_POINTER(aReturn); + + *aReturn = mAllowMetaRedirects; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowMetaRedirects(bool aValue) { + mAllowMetaRedirects = aValue; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAllowSubframes(bool* aAllowSubframes) { + NS_ENSURE_ARG_POINTER(aAllowSubframes); + + *aAllowSubframes = mAllowSubframes; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowSubframes(bool aAllowSubframes) { + mAllowSubframes = aAllowSubframes; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAllowImages(bool* aAllowImages) { + NS_ENSURE_ARG_POINTER(aAllowImages); + + *aAllowImages = mAllowImages; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowImages(bool aAllowImages) { + mAllowImages = aAllowImages; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAllowMedia(bool* aAllowMedia) { + *aAllowMedia = mAllowMedia; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowMedia(bool aAllowMedia) { + mAllowMedia = aAllowMedia; + + // Mute or unmute audio contexts attached to the inner window. + if (mScriptGlobal) { + if (nsPIDOMWindowInner* innerWin = mScriptGlobal->GetCurrentInnerWindow()) { + if (aAllowMedia) { + innerWin->UnmuteAudioContexts(); + } else { + innerWin->MuteAudioContexts(); + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAllowDNSPrefetch(bool* aAllowDNSPrefetch) { + *aAllowDNSPrefetch = mAllowDNSPrefetch; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowDNSPrefetch(bool aAllowDNSPrefetch) { + mAllowDNSPrefetch = aAllowDNSPrefetch; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAllowWindowControl(bool* aAllowWindowControl) { + *aAllowWindowControl = mAllowWindowControl; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowWindowControl(bool aAllowWindowControl) { + mAllowWindowControl = aAllowWindowControl; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAllowContentRetargeting(bool* aAllowContentRetargeting) { + *aAllowContentRetargeting = mBrowsingContext->GetAllowContentRetargeting(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowContentRetargeting(bool aAllowContentRetargeting) { + BrowsingContext::Transaction txn; + txn.SetAllowContentRetargeting(aAllowContentRetargeting); + txn.SetAllowContentRetargetingOnChildren(aAllowContentRetargeting); + return txn.Commit(mBrowsingContext); +} + +NS_IMETHODIMP +nsDocShell::GetAllowContentRetargetingOnChildren( + bool* aAllowContentRetargetingOnChildren) { + *aAllowContentRetargetingOnChildren = + mBrowsingContext->GetAllowContentRetargetingOnChildren(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowContentRetargetingOnChildren( + bool aAllowContentRetargetingOnChildren) { + return mBrowsingContext->SetAllowContentRetargetingOnChildren( + aAllowContentRetargetingOnChildren); +} + +NS_IMETHODIMP +nsDocShell::GetMayEnableCharacterEncodingMenu( + bool* aMayEnableCharacterEncodingMenu) { + *aMayEnableCharacterEncodingMenu = false; + if (!mContentViewer) { + return NS_OK; + } + Document* doc = mContentViewer->GetDocument(); + if (!doc) { + return NS_OK; + } + if (doc->WillIgnoreCharsetOverride()) { + return NS_OK; + } + + *aMayEnableCharacterEncodingMenu = true; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetCharsetAutodetected(bool* aCharsetAutodetected) { + *aCharsetAutodetected = false; + if (!mContentViewer) { + return NS_OK; + } + Document* doc = mContentViewer->GetDocument(); + if (!doc) { + return NS_OK; + } + int32_t source = doc->GetDocumentCharacterSetSource(); + + if ((source >= kCharsetFromInitialAutoDetectionASCII && + source <= kCharsetFromFinalAutoDetectionFile) || + source == kCharsetFromUserForcedJapaneseAutoDetection || + source == kCharsetFromPendingUserForcedAutoDetection || + source == kCharsetFromInitialUserForcedAutoDetection || + source == kCharsetFromFinalUserForcedAutoDetection) { + *aCharsetAutodetected = true; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAllDocShellsInSubtree(int32_t aItemType, + DocShellEnumeratorDirection aDirection, + nsTArray>& aResult) { + aResult.Clear(); + + nsDocShellEnumerator docShellEnum( + (aDirection == ENUMERATE_FORWARDS) + ? nsDocShellEnumerator::EnumerationDirection::Forwards + : nsDocShellEnumerator::EnumerationDirection::Backwards, + aItemType, *this); + + nsresult rv = docShellEnum.BuildDocShellArray(aResult); + if (NS_FAILED(rv)) { + return rv; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAppType(AppType* aAppType) { + *aAppType = mAppType; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAppType(AppType aAppType) { + mAppType = aAppType; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAllowAuth(bool* aAllowAuth) { + *aAllowAuth = mAllowAuth; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowAuth(bool aAllowAuth) { + mAllowAuth = aAllowAuth; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetZoom(float* aZoom) { + NS_ENSURE_ARG_POINTER(aZoom); + *aZoom = 1.0f; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetZoom(float aZoom) { return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP +nsDocShell::GetBusyFlags(BusyFlags* aBusyFlags) { + NS_ENSURE_ARG_POINTER(aBusyFlags); + + *aBusyFlags = mBusyFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::TabToTreeOwner(bool aForward, bool aForDocumentNavigation, + bool* aTookFocus) { + NS_ENSURE_ARG_POINTER(aTookFocus); + + nsCOMPtr chromeFocus = do_GetInterface(mTreeOwner); + if (chromeFocus) { + if (aForward) { + *aTookFocus = + NS_SUCCEEDED(chromeFocus->FocusNextElement(aForDocumentNavigation)); + } else { + *aTookFocus = + NS_SUCCEEDED(chromeFocus->FocusPrevElement(aForDocumentNavigation)); + } + } else { + *aTookFocus = false; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetLoadURIDelegate(nsILoadURIDelegate** aLoadURIDelegate) { + nsCOMPtr delegate = GetLoadURIDelegate(); + delegate.forget(aLoadURIDelegate); + return NS_OK; +} + +already_AddRefed nsDocShell::GetLoadURIDelegate() { + if (nsCOMPtr result = + do_QueryActor("LoadURIDelegate", GetDocument())) { + return result.forget(); + } + + return nullptr; +} + +NS_IMETHODIMP +nsDocShell::GetUseErrorPages(bool* aUseErrorPages) { + *aUseErrorPages = mBrowsingContext->GetUseErrorPages(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetUseErrorPages(bool aUseErrorPages) { + return mBrowsingContext->SetUseErrorPages(aUseErrorPages); +} + +NS_IMETHODIMP +nsDocShell::GetPreviousEntryIndex(int32_t* aPreviousEntryIndex) { + *aPreviousEntryIndex = mPreviousEntryIndex; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetLoadedEntryIndex(int32_t* aLoadedEntryIndex) { + *aLoadedEntryIndex = mLoadedEntryIndex; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::HistoryPurged(int32_t aNumEntries) { + // These indices are used for fastback cache eviction, to determine + // which session history entries are candidates for content viewer + // eviction. We need to adjust by the number of entries that we + // just purged from history, so that we look at the right session history + // entries during eviction. + mPreviousEntryIndex = std::max(-1, mPreviousEntryIndex - aNumEntries); + mLoadedEntryIndex = std::max(0, mLoadedEntryIndex - aNumEntries); + + for (auto* child : mChildList.ForwardRange()) { + nsCOMPtr shell = do_QueryObject(child); + if (shell) { + shell->HistoryPurged(aNumEntries); + } + } + + return NS_OK; +} + +void nsDocShell::TriggerParentCheckDocShellIsEmpty() { + if (RefPtr parent = GetInProcessParentDocshell()) { + parent->DocLoaderIsEmpty(true); + } + if (GetBrowsingContext()->IsContentSubframe() && + !GetBrowsingContext()->GetParent()->IsInProcess()) { + if (BrowserChild* browserChild = BrowserChild::GetFrom(this)) { + mozilla::Unused << browserChild->SendMaybeFireEmbedderLoadEvents( + EmbedderElementEventType::NoEvent); + } + } +} + +nsresult nsDocShell::HistoryEntryRemoved(int32_t aIndex) { + // These indices are used for fastback cache eviction, to determine + // which session history entries are candidates for content viewer + // eviction. We need to adjust by the number of entries that we + // just purged from history, so that we look at the right session history + // entries during eviction. + if (aIndex == mPreviousEntryIndex) { + mPreviousEntryIndex = -1; + } else if (aIndex < mPreviousEntryIndex) { + --mPreviousEntryIndex; + } + if (mLoadedEntryIndex == aIndex) { + mLoadedEntryIndex = 0; + } else if (aIndex < mLoadedEntryIndex) { + --mLoadedEntryIndex; + } + + for (auto* child : mChildList.ForwardRange()) { + nsCOMPtr shell = do_QueryObject(child); + if (shell) { + static_cast(shell.get())->HistoryEntryRemoved(aIndex); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetRecordProfileTimelineMarkers(bool aValue) { + bool currentValue = nsIDocShell::GetRecordProfileTimelineMarkers(); + if (currentValue == aValue) { + return NS_OK; + } + + RefPtr timelines = TimelineConsumers::Get(); + if (!timelines) { + return NS_OK; + } + + if (aValue) { + MOZ_ASSERT(!timelines->HasConsumer(this)); + timelines->AddConsumer(this); + MOZ_ASSERT(timelines->HasConsumer(this)); + UseEntryScriptProfiling(); + } else { + MOZ_ASSERT(timelines->HasConsumer(this)); + timelines->RemoveConsumer(this); + MOZ_ASSERT(!timelines->HasConsumer(this)); + UnuseEntryScriptProfiling(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetRecordProfileTimelineMarkers(bool* aValue) { + *aValue = !!mObserved; + return NS_OK; +} + +nsresult nsDocShell::PopProfileTimelineMarkers( + JSContext* aCx, JS::MutableHandle aOut) { + RefPtr timelines = TimelineConsumers::Get(); + if (!timelines) { + return NS_OK; + } + + nsTArray store; + SequenceRooter rooter(aCx, &store); + + timelines->PopMarkers(this, aCx, store); + + if (!ToJSValue(aCx, store, aOut)) { + JS_ClearPendingException(aCx); + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +nsresult nsDocShell::Now(DOMHighResTimeStamp* aWhen) { + *aWhen = (TimeStamp::Now() - TimeStamp::ProcessCreation()).ToMilliseconds(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetWindowDraggingAllowed(bool aValue) { + RefPtr parent = GetInProcessParentDocshell(); + if (!aValue && mItemType == typeChrome && !parent) { + // Window dragging is always allowed for top level + // chrome docshells. + return NS_ERROR_FAILURE; + } + mWindowDraggingAllowed = aValue; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetWindowDraggingAllowed(bool* aValue) { + // window dragging regions in CSS (-moz-window-drag:drag) + // can be slow. Default behavior is to only allow it for + // chrome top level windows. + RefPtr parent = GetInProcessParentDocshell(); + if (mItemType == typeChrome && !parent) { + // Top level chrome window + *aValue = true; + } else { + *aValue = mWindowDraggingAllowed; + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetCurrentDocumentChannel(nsIChannel** aResult) { + NS_IF_ADDREF(*aResult = GetCurrentDocChannel()); + return NS_OK; +} + +nsIChannel* nsDocShell::GetCurrentDocChannel() { + if (mContentViewer) { + Document* doc = mContentViewer->GetDocument(); + if (doc) { + return doc->GetChannel(); + } + } + return nullptr; +} + +NS_IMETHODIMP +nsDocShell::AddWeakScrollObserver(nsIScrollObserver* aObserver) { + nsWeakPtr weakObs = do_GetWeakReference(aObserver); + if (!weakObs) { + return NS_ERROR_FAILURE; + } + mScrollObservers.AppendElement(weakObs); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::RemoveWeakScrollObserver(nsIScrollObserver* aObserver) { + nsWeakPtr obs = do_GetWeakReference(aObserver); + return mScrollObservers.RemoveElement(obs) ? NS_OK : NS_ERROR_FAILURE; +} + +void nsDocShell::NotifyAsyncPanZoomStarted() { + nsTObserverArray::ForwardIterator iter(mScrollObservers); + while (iter.HasMore()) { + nsWeakPtr ref = iter.GetNext(); + nsCOMPtr obs = do_QueryReferent(ref); + if (obs) { + obs->AsyncPanZoomStarted(); + } else { + iter.Remove(); + } + } +} + +void nsDocShell::NotifyAsyncPanZoomStopped() { + nsTObserverArray::ForwardIterator iter(mScrollObservers); + while (iter.HasMore()) { + nsWeakPtr ref = iter.GetNext(); + nsCOMPtr obs = do_QueryReferent(ref); + if (obs) { + obs->AsyncPanZoomStopped(); + } else { + iter.Remove(); + } + } +} + +NS_IMETHODIMP +nsDocShell::NotifyScrollObservers() { + nsTObserverArray::ForwardIterator iter(mScrollObservers); + while (iter.HasMore()) { + nsWeakPtr ref = iter.GetNext(); + nsCOMPtr obs = do_QueryReferent(ref); + if (obs) { + obs->ScrollPositionChanged(); + } else { + iter.Remove(); + } + } + return NS_OK; +} + +//***************************************************************************** +// nsDocShell::nsIDocShellTreeItem +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::GetName(nsAString& aName) { + aName = mBrowsingContext->Name(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetName(const nsAString& aName) { + return mBrowsingContext->SetName(aName); +} + +NS_IMETHODIMP +nsDocShell::NameEquals(const nsAString& aName, bool* aResult) { + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mBrowsingContext->NameEquals(aName); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetCustomUserAgent(nsAString& aCustomUserAgent) { + mBrowsingContext->GetCustomUserAgent(aCustomUserAgent); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetCustomUserAgent(const nsAString& aCustomUserAgent) { + if (mWillChangeProcess) { + NS_WARNING("SetCustomUserAgent: Process is changing. Ignoring set"); + return NS_ERROR_FAILURE; + } + + return mBrowsingContext->SetCustomUserAgent(aCustomUserAgent); +} + +NS_IMETHODIMP +nsDocShell::ClearCachedPlatform() { + RefPtr win = + mScriptGlobal ? mScriptGlobal->GetCurrentInnerWindowInternal() : nullptr; + if (win) { + Navigator* navigator = win->Navigator(); + if (navigator) { + navigator->ClearPlatformCache(); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::ClearCachedUserAgent() { + RefPtr win = + mScriptGlobal ? mScriptGlobal->GetCurrentInnerWindowInternal() : nullptr; + if (win) { + Navigator* navigator = win->Navigator(); + if (navigator) { + navigator->ClearUserAgentCache(); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetMetaViewportOverride( + MetaViewportOverride* aMetaViewportOverride) { + NS_ENSURE_ARG_POINTER(aMetaViewportOverride); + + *aMetaViewportOverride = mMetaViewportOverride; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetMetaViewportOverride( + MetaViewportOverride aMetaViewportOverride) { + // We don't have a way to verify this coming from Javascript, so this check is + // still needed. + if (!(aMetaViewportOverride == META_VIEWPORT_OVERRIDE_NONE || + aMetaViewportOverride == META_VIEWPORT_OVERRIDE_ENABLED || + aMetaViewportOverride == META_VIEWPORT_OVERRIDE_DISABLED)) { + return NS_ERROR_INVALID_ARG; + } + + mMetaViewportOverride = aMetaViewportOverride; + + // Inform our presShell that it needs to re-check its need for a viewport + // override. + if (RefPtr presShell = GetPresShell()) { + presShell->MaybeRecreateMobileViewportManager(true); + } + + return NS_OK; +} + +/* virtual */ +int32_t nsDocShell::ItemType() { return mItemType; } + +NS_IMETHODIMP +nsDocShell::GetItemType(int32_t* aItemType) { + NS_ENSURE_ARG_POINTER(aItemType); + + MOZ_DIAGNOSTIC_ASSERT( + (mBrowsingContext->IsContent() ? typeContent : typeChrome) == mItemType); + *aItemType = mItemType; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetInProcessParent(nsIDocShellTreeItem** aParent) { + if (!mParent) { + *aParent = nullptr; + } else { + CallQueryInterface(mParent, aParent); + } + // Note that in the case when the parent is not an nsIDocShellTreeItem we + // don't want to throw; we just want to return null. + return NS_OK; +} + +// With Fission, related nsDocShell objects may exist in a different process. In +// that case, this method will return `nullptr`, despite a parent nsDocShell +// object existing. +// +// Prefer using `BrowsingContext::Parent()`, which will succeed even if the +// parent entry is not in the current process, and handle the case where the +// parent nsDocShell is inaccessible. +already_AddRefed nsDocShell::GetInProcessParentDocshell() { + nsCOMPtr docshell = do_QueryInterface(GetAsSupports(mParent)); + return docshell.forget().downcast(); +} + +void nsDocShell::MaybeCreateInitialClientSource(nsIPrincipal* aPrincipal) { + MOZ_ASSERT(!mIsBeingDestroyed); + + // If there is an existing document then there is no need to create + // a client for a future initial about:blank document. + if (mScriptGlobal && mScriptGlobal->GetCurrentInnerWindowInternal() && + mScriptGlobal->GetCurrentInnerWindowInternal()->GetExtantDoc()) { + MOZ_DIAGNOSTIC_ASSERT(mScriptGlobal->GetCurrentInnerWindowInternal() + ->GetClientInfo() + .isSome()); + MOZ_DIAGNOSTIC_ASSERT(!mInitialClientSource); + return; + } + + // Don't recreate the initial client source. We call this multiple times + // when DoChannelLoad() is called before CreateAboutBlankContentViewer. + if (mInitialClientSource) { + return; + } + + // Don't pre-allocate the client when we are sandboxed. The inherited + // principal does not take sandboxing into account. + // TODO: Refactor sandboxing principal code out so we can use it here. + if (!aPrincipal && mBrowsingContext->GetSandboxFlags()) { + return; + } + + nsIPrincipal* principal = + aPrincipal ? aPrincipal : GetInheritedPrincipal(false); + + // Sometimes there is no principal available when we are called from + // CreateAboutBlankContentViewer. For example, sometimes the principal + // is only extracted from the load context after the document is created + // in Document::ResetToURI(). Ideally we would do something similar + // here, but for now lets just avoid the issue by not preallocating the + // client. + if (!principal) { + return; + } + + nsCOMPtr win = GetWindow(); + if (!win) { + return; + } + + mInitialClientSource = ClientManager::CreateSource( + ClientType::Window, win->EventTargetFor(TaskCategory::Other), principal); + MOZ_DIAGNOSTIC_ASSERT(mInitialClientSource); + + // Mark the initial client as execution ready, but owned by the docshell. + // If the client is actually used this will cause ClientSource to force + // the creation of the initial about:blank by calling + // nsDocShell::GetDocument(). + mInitialClientSource->DocShellExecutionReady(this); + + // Next, check to see if the parent is controlled. + nsCOMPtr parent = GetInProcessParentDocshell(); + nsPIDOMWindowOuter* parentOuter = parent ? parent->GetWindow() : nullptr; + nsPIDOMWindowInner* parentInner = + parentOuter ? parentOuter->GetCurrentInnerWindow() : nullptr; + if (!parentInner) { + return; + } + + nsCOMPtr uri; + MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), "about:blank"_ns)); + + // We're done if there is no parent controller or if this docshell + // is not permitted to control for some reason. + Maybe controller(parentInner->GetController()); + if (controller.isNothing() || + !ServiceWorkerAllowedToControlWindow(principal, uri)) { + return; + } + + mInitialClientSource->InheritController(controller.ref()); +} + +Maybe nsDocShell::GetInitialClientInfo() const { + if (mInitialClientSource) { + Maybe result; + result.emplace(mInitialClientSource->Info()); + return result; + } + + nsGlobalWindowInner* innerWindow = + mScriptGlobal ? mScriptGlobal->GetCurrentInnerWindowInternal() : nullptr; + Document* doc = innerWindow ? innerWindow->GetExtantDoc() : nullptr; + + if (!doc || !doc->IsInitialDocument()) { + return Maybe(); + } + + return innerWindow->GetClientInfo(); +} + +void nsDocShell::RecomputeCanExecuteScripts() { + bool old = mCanExecuteScripts; + RefPtr parent = GetInProcessParentDocshell(); + + // If we have no tree owner, that means that we've been detached from the + // docshell tree (this is distinct from having no parent docshell, which + // is the case for root docshells). It would be nice to simply disallow + // script in detached docshells, but bug 986542 demonstrates that this + // behavior breaks at least one website. + // + // So instead, we use our previous value, unless mAllowJavascript has been + // explicitly set to false. + if (!mTreeOwner) { + mCanExecuteScripts = mCanExecuteScripts && mAllowJavascript; + // If scripting has been explicitly disabled on our docshell, we're done. + } else if (!mAllowJavascript) { + mCanExecuteScripts = false; + // If we have a parent, inherit. + } else if (parent) { + mCanExecuteScripts = parent->mCanExecuteScripts; + // Otherwise, we're the root of the tree, and we haven't explicitly disabled + // script. Allow. + } else { + mCanExecuteScripts = true; + } + + // Inform our active DOM window. + // + // This will pass the outer, which will be in the scope of the active inner. + if (mScriptGlobal && mScriptGlobal->GetGlobalJSObject()) { + xpc::Scriptability& scriptability = + xpc::Scriptability::Get(mScriptGlobal->GetGlobalJSObject()); + scriptability.SetDocShellAllowsScript(mCanExecuteScripts); + } + + // If our value has changed, our children might be affected. Recompute their + // value as well. + if (old != mCanExecuteScripts) { + for (auto* child : mChildList.ForwardRange()) { + static_cast(child)->RecomputeCanExecuteScripts(); + } + } +} + +nsresult nsDocShell::SetDocLoaderParent(nsDocLoader* aParent) { + bool wasFrame = IsFrame(); + + nsresult rv = nsDocLoader::SetDocLoaderParent(aParent); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr priorityGroup = do_QueryInterface(mLoadGroup); + if (wasFrame != IsFrame() && priorityGroup) { + priorityGroup->AdjustPriority(wasFrame ? -1 : 1); + } + + // Curse ambiguous nsISupports inheritance! + nsISupports* parent = GetAsSupports(aParent); + + // If parent is another docshell, we inherit all their flags for + // allowing plugins, scripting etc. + bool value; + nsCOMPtr parentAsDocShell(do_QueryInterface(parent)); + + if (parentAsDocShell) { + if (mAllowJavascript && + NS_SUCCEEDED(parentAsDocShell->GetAllowJavascript(&value))) { + SetAllowJavascript(value); + } + if (mAllowMetaRedirects && + NS_SUCCEEDED(parentAsDocShell->GetAllowMetaRedirects(&value))) { + SetAllowMetaRedirects(value); + } + if (mAllowSubframes && + NS_SUCCEEDED(parentAsDocShell->GetAllowSubframes(&value))) { + SetAllowSubframes(value); + } + if (mAllowImages && + NS_SUCCEEDED(parentAsDocShell->GetAllowImages(&value))) { + SetAllowImages(value); + } + SetAllowMedia(parentAsDocShell->GetAllowMedia() && mAllowMedia); + if (mAllowWindowControl && + NS_SUCCEEDED(parentAsDocShell->GetAllowWindowControl(&value))) { + SetAllowWindowControl(value); + } + if (NS_FAILED(parentAsDocShell->GetAllowDNSPrefetch(&value))) { + value = false; + } + SetAllowDNSPrefetch(mAllowDNSPrefetch && value); + SetAffectPrivateSessionLifetime( + parentAsDocShell->GetAffectPrivateSessionLifetime()); + + // We don't need to inherit metaViewportOverride, because the viewport + // is only relevant for the outermost nsDocShell, not for any iframes + // like this that might be embedded within it. + } + + nsCOMPtr parentURIListener(do_GetInterface(parent)); + if (parentURIListener) { + mContentListener->SetParentContentListener(parentURIListener); + } + + // Our parent has changed. Recompute scriptability. + RecomputeCanExecuteScripts(); + + // Inform windows when they're being removed from their parent. + if (!aParent) { + MaybeClearStorageAccessFlag(); + } + + return NS_OK; +} + +void nsDocShell::MaybeClearStorageAccessFlag() { + if (mScriptGlobal) { + // Tell our window that the parent has now changed. + mScriptGlobal->ParentWindowChanged(); + + // Tell all of our children about the change recursively as well. + for (auto* childDocLoader : mChildList.ForwardRange()) { + nsCOMPtr child = do_QueryObject(childDocLoader); + if (child) { + static_cast(child.get())->MaybeClearStorageAccessFlag(); + } + } + } +} + +void nsDocShell::MaybeRestoreWindowName() { + if (!StaticPrefs::privacy_window_name_update_enabled()) { + return; + } + + // We only restore window.name for the top-level content. + if (!mBrowsingContext->IsTopContent()) { + return; + } + + nsAutoString name; + + // Following implements https://html.spec.whatwg.org/#history-traversal: + // Step 4.4. Check if the loading entry has a name. + + if (mLSHE) { + mLSHE->GetName(name); + } + + if (mLoadingEntry) { + name = mLoadingEntry->mInfo.GetName(); + } + + if (name.IsEmpty()) { + return; + } + + // Step 4.4.1. Set the name to the browsing context. + Unused << mBrowsingContext->SetName(name); + + // Step 4.4.2. Clear the name of all entries that are contiguous and + // same-origin with the loading entry. + if (mLSHE) { + nsSHistory::WalkContiguousEntries( + mLSHE, [](nsISHEntry* aEntry) { aEntry->SetName(EmptyString()); }); + } + + if (mLoadingEntry) { + // Clear the name of the session entry in the child side. For parent side, + // the clearing will be done when we commit the history to the parent. + mLoadingEntry->mInfo.SetName(EmptyString()); + } +} + +void nsDocShell::StoreWindowNameToSHEntries() { + MOZ_ASSERT(mBrowsingContext->IsTopContent()); + + nsAutoString name; + mBrowsingContext->GetName(name); + + if (mOSHE) { + nsSHistory::WalkContiguousEntries( + mOSHE, [&](nsISHEntry* aEntry) { aEntry->SetName(name); }); + } + + if (mozilla::SessionHistoryInParent()) { + if (XRE_IsParentProcess()) { + SessionHistoryEntry* entry = + mBrowsingContext->Canonical()->GetActiveSessionHistoryEntry(); + if (entry) { + nsSHistory::WalkContiguousEntries( + entry, [&](nsISHEntry* aEntry) { aEntry->SetName(name); }); + } + } else { + // Ask parent process to store the name in entries. + mozilla::Unused + << ContentChild::GetSingleton() + ->SendSessionHistoryEntryStoreWindowNameInContiguousEntries( + mBrowsingContext, name); + } + } +} + +NS_IMETHODIMP +nsDocShell::GetInProcessSameTypeParent(nsIDocShellTreeItem** aParent) { + if (BrowsingContext* parentBC = mBrowsingContext->GetParent()) { + *aParent = do_AddRef(parentBC->GetDocShell()).take(); + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetSameTypeInProcessParentIgnoreBrowserBoundaries( + nsIDocShell** aParent) { + NS_ENSURE_ARG_POINTER(aParent); + *aParent = nullptr; + + nsCOMPtr parent = + do_QueryInterface(GetAsSupports(mParent)); + if (!parent) { + return NS_OK; + } + + if (parent->ItemType() == mItemType) { + nsCOMPtr parentDS = do_QueryInterface(parent); + parentDS.forget(aParent); + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetInProcessRootTreeItem(nsIDocShellTreeItem** aRootTreeItem) { + NS_ENSURE_ARG_POINTER(aRootTreeItem); + + RefPtr root = this; + RefPtr parent = root->GetInProcessParentDocshell(); + while (parent) { + root = parent; + parent = root->GetInProcessParentDocshell(); + } + + root.forget(aRootTreeItem); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetInProcessSameTypeRootTreeItem( + nsIDocShellTreeItem** aRootTreeItem) { + NS_ENSURE_ARG_POINTER(aRootTreeItem); + *aRootTreeItem = static_cast(this); + + nsCOMPtr parent; + NS_ENSURE_SUCCESS(GetInProcessSameTypeParent(getter_AddRefs(parent)), + NS_ERROR_FAILURE); + while (parent) { + *aRootTreeItem = parent; + NS_ENSURE_SUCCESS( + (*aRootTreeItem)->GetInProcessSameTypeParent(getter_AddRefs(parent)), + NS_ERROR_FAILURE); + } + NS_ADDREF(*aRootTreeItem); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetTreeOwner(nsIDocShellTreeOwner** aTreeOwner) { + NS_ENSURE_ARG_POINTER(aTreeOwner); + + *aTreeOwner = mTreeOwner; + NS_IF_ADDREF(*aTreeOwner); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetTreeOwner(nsIDocShellTreeOwner* aTreeOwner) { + if (mIsBeingDestroyed && aTreeOwner) { + return NS_ERROR_FAILURE; + } + + // Don't automatically set the progress based on the tree owner for frames + if (!IsFrame()) { + nsCOMPtr webProgress = + do_QueryInterface(GetAsSupports(this)); + + if (webProgress) { + nsCOMPtr oldListener = + do_QueryInterface(mTreeOwner); + nsCOMPtr newListener = + do_QueryInterface(aTreeOwner); + + if (oldListener) { + webProgress->RemoveProgressListener(oldListener); + } + + if (newListener) { + webProgress->AddProgressListener(newListener, + nsIWebProgress::NOTIFY_ALL); + } + } + } + + mTreeOwner = aTreeOwner; // Weak reference per API + + for (auto* childDocLoader : mChildList.ForwardRange()) { + nsCOMPtr child = do_QueryObject(childDocLoader); + NS_ENSURE_TRUE(child, NS_ERROR_FAILURE); + + if (child->ItemType() == mItemType) { + child->SetTreeOwner(aTreeOwner); + } + } + + // If we're in the content process and have had a TreeOwner set on us, extract + // our BrowserChild actor. If we've already had our BrowserChild set, assert + // that it hasn't changed. + if (mTreeOwner && XRE_IsContentProcess()) { + nsCOMPtr newBrowserChild = do_GetInterface(mTreeOwner); + MOZ_ASSERT(newBrowserChild, + "No BrowserChild actor for tree owner in Content!"); + + if (mBrowserChild) { + nsCOMPtr oldBrowserChild = + do_QueryReferent(mBrowserChild); + MOZ_RELEASE_ASSERT( + oldBrowserChild == newBrowserChild, + "Cannot change BrowserChild during nsDocShell lifetime!"); + } else { + mBrowserChild = do_GetWeakReference(newBrowserChild); + } + } + + // Our tree owner has changed. Recompute scriptability. + // + // Note that this is near-redundant with the recomputation in + // SetDocLoaderParent(), but not so for the root DocShell, where the call to + // SetTreeOwner() happens after the initial AddDocLoaderAsChildOfRoot(), + // and we never set another parent. Given that this is neither expensive nor + // performance-critical, let's be safe and unconditionally recompute this + // state whenever dependent state changes. + RecomputeCanExecuteScripts(); + + return NS_OK; +} + +void nsDocShell::SetChildOffset(int32_t aChildOffset) { + mChildOffset = aChildOffset; +} + +int32_t nsDocShell::GetChildOffset() { return mChildOffset; } + +NS_IMETHODIMP +nsDocShell::GetHistoryID(nsID& aID) { + aID = mBrowsingContext->GetHistoryID(); + return NS_OK; +} + +const nsID& nsDocShell::HistoryID() { return mBrowsingContext->GetHistoryID(); } + +NS_IMETHODIMP +nsDocShell::GetIsInUnload(bool* aIsInUnload) { + *aIsInUnload = mFiredUnloadEvent; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetInProcessChildCount(int32_t* aChildCount) { + NS_ENSURE_ARG_POINTER(aChildCount); + *aChildCount = mChildList.Length(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::AddChild(nsIDocShellTreeItem* aChild) { + NS_ENSURE_ARG_POINTER(aChild); + + RefPtr childAsDocLoader = GetAsDocLoader(aChild); + NS_ENSURE_TRUE(childAsDocLoader, NS_ERROR_UNEXPECTED); + + // Make sure we're not creating a loop in the docshell tree + nsDocLoader* ancestor = this; + do { + if (childAsDocLoader == ancestor) { + return NS_ERROR_ILLEGAL_VALUE; + } + ancestor = ancestor->GetParent(); + } while (ancestor); + + // Make sure to remove the child from its current parent. + nsDocLoader* childsParent = childAsDocLoader->GetParent(); + if (childsParent) { + nsresult rv = childsParent->RemoveChildLoader(childAsDocLoader); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Make sure to clear the treeowner in case this child is a different type + // from us. + aChild->SetTreeOwner(nullptr); + + nsresult res = AddChildLoader(childAsDocLoader); + NS_ENSURE_SUCCESS(res, res); + NS_ASSERTION(!mChildList.IsEmpty(), + "child list must not be empty after a successful add"); + + nsCOMPtr childDocShell = do_QueryInterface(aChild); + bool dynamic = nsDocShell::Cast(childDocShell)->GetCreatedDynamically(); + if (!dynamic) { + nsCOMPtr currentSH; + bool oshe = false; + GetCurrentSHEntry(getter_AddRefs(currentSH), &oshe); + if (currentSH) { + currentSH->HasDynamicallyAddedChild(&dynamic); + } + } + childDocShell->SetChildOffset(dynamic ? -1 : mChildList.Length() - 1); + + /* Set the child's global history if the parent has one */ + if (mBrowsingContext->GetUseGlobalHistory()) { + // childDocShell->SetUseGlobalHistory(true); + // this should be set through BC inherit + MOZ_ASSERT(nsDocShell::Cast(childDocShell) + ->mBrowsingContext->GetUseGlobalHistory()); + } + + if (aChild->ItemType() != mItemType) { + return NS_OK; + } + + aChild->SetTreeOwner(mTreeOwner); + + nsCOMPtr childAsDocShell(do_QueryInterface(aChild)); + if (!childAsDocShell) { + return NS_OK; + } + + // charset, style-disabling, and zoom will be inherited in SetupNewViewer() + + // Now take this document's charset and set the child's parentCharset field + // to it. We'll later use that field, in the loading process, for the + // charset choosing algorithm. + // If we fail, at any point, we just return NS_OK. + // This code has some performance impact. But this will be reduced when + // the current charset will finally be stored as an Atom, avoiding the + // alias resolution extra look-up. + + // we are NOT going to propagate the charset is this Chrome's docshell + if (mItemType == nsIDocShellTreeItem::typeChrome) { + return NS_OK; + } + + // get the parent's current charset + if (!mContentViewer) { + return NS_OK; + } + Document* doc = mContentViewer->GetDocument(); + if (!doc) { + return NS_OK; + } + + const Encoding* parentCS = doc->GetDocumentCharacterSet(); + int32_t charsetSource = doc->GetDocumentCharacterSetSource(); + // set the child's parentCharset + childAsDocShell->SetParentCharset(parentCS, charsetSource, + doc->NodePrincipal()); + + // printf("### 1 >>> Adding child. Parent CS = %s. ItemType = %d.\n", + // NS_LossyConvertUTF16toASCII(parentCS).get(), mItemType); + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::RemoveChild(nsIDocShellTreeItem* aChild) { + NS_ENSURE_ARG_POINTER(aChild); + + RefPtr childAsDocLoader = GetAsDocLoader(aChild); + NS_ENSURE_TRUE(childAsDocLoader, NS_ERROR_UNEXPECTED); + + nsresult rv = RemoveChildLoader(childAsDocLoader); + NS_ENSURE_SUCCESS(rv, rv); + + aChild->SetTreeOwner(nullptr); + + return nsDocLoader::AddDocLoaderAsChildOfRoot(childAsDocLoader); +} + +NS_IMETHODIMP +nsDocShell::GetInProcessChildAt(int32_t aIndex, nsIDocShellTreeItem** aChild) { + NS_ENSURE_ARG_POINTER(aChild); + + RefPtr child = GetInProcessChildAt(aIndex); + NS_ENSURE_TRUE(child, NS_ERROR_UNEXPECTED); + + child.forget(aChild); + + return NS_OK; +} + +nsDocShell* nsDocShell::GetInProcessChildAt(int32_t aIndex) { +#ifdef DEBUG + if (aIndex < 0) { + NS_WARNING("Negative index passed to GetChildAt"); + } else if (static_cast(aIndex) >= mChildList.Length()) { + NS_WARNING("Too large an index passed to GetChildAt"); + } +#endif + + nsIDocumentLoader* child = ChildAt(aIndex); + + // child may be nullptr here. + return static_cast(child); +} + +nsresult nsDocShell::AddChildSHEntry(nsISHEntry* aCloneRef, + nsISHEntry* aNewEntry, + int32_t aChildOffset, uint32_t aLoadType, + bool aCloneChildren) { + MOZ_ASSERT(!mozilla::SessionHistoryInParent()); + nsresult rv = NS_OK; + + if (mLSHE && aLoadType != LOAD_PUSHSTATE) { + /* You get here if you are currently building a + * hierarchy ie.,you just visited a frameset page + */ + if (NS_FAILED(mLSHE->ReplaceChild(aNewEntry))) { + rv = mLSHE->AddChild(aNewEntry, aChildOffset); + } + } else if (!aCloneRef) { + /* This is an initial load in some subframe. Just append it if we can */ + if (mOSHE) { + rv = mOSHE->AddChild(aNewEntry, aChildOffset, UseRemoteSubframes()); + } + } else { + RefPtr shistory = GetRootSessionHistory(); + if (shistory) { + rv = shistory->LegacySHistory()->AddChildSHEntryHelper( + aCloneRef, aNewEntry, mBrowsingContext->Top(), aCloneChildren); + } + } + return rv; +} + +nsresult nsDocShell::AddChildSHEntryToParent(nsISHEntry* aNewEntry, + int32_t aChildOffset, + bool aCloneChildren) { + MOZ_ASSERT(!mozilla::SessionHistoryInParent()); + /* You will get here when you are in a subframe and + * a new url has been loaded on you. + * The mOSHE in this subframe will be the previous url's + * mOSHE. This mOSHE will be used as the identification + * for this subframe in the CloneAndReplace function. + */ + + // In this case, we will end up calling AddEntry, which increases the + // current index by 1 + RefPtr rootSH = GetRootSessionHistory(); + if (rootSH) { + mPreviousEntryIndex = rootSH->Index(); + } + + nsresult rv; + // XXX(farre): this is not Fission safe, expect errors. This never + // get's executed once session history in the parent is enabled. + nsCOMPtr parent = do_QueryInterface(GetAsSupports(mParent), &rv); + NS_WARNING_ASSERTION( + parent || !UseRemoteSubframes(), + "Failed to add child session history entry! This will be resolved once " + "session history in the parent is enabled."); + if (parent) { + rv = nsDocShell::Cast(parent)->AddChildSHEntry( + mOSHE, aNewEntry, aChildOffset, mLoadType, aCloneChildren); + } + + if (rootSH) { + mLoadedEntryIndex = rootSH->Index(); + + if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Verbose))) { + MOZ_LOG(gPageCacheLog, LogLevel::Verbose, + ("Previous index: %d, Loaded index: %d", mPreviousEntryIndex, + mLoadedEntryIndex)); + } + } + + return rv; +} + +NS_IMETHODIMP +nsDocShell::GetCurrentSHEntry(nsISHEntry** aEntry, bool* aOSHE) { + *aOSHE = false; + *aEntry = nullptr; + if (mLSHE) { + NS_ADDREF(*aEntry = mLSHE); + } else if (mOSHE) { + NS_ADDREF(*aEntry = mOSHE); + *aOSHE = true; + } + return NS_OK; +} + +NS_IMETHODIMP nsDocShell::SynchronizeLayoutHistoryState() { + if (mActiveEntry && mActiveEntry->GetLayoutHistoryState() && + mBrowsingContext) { + if (XRE_IsContentProcess()) { + dom::ContentChild* contentChild = dom::ContentChild::GetSingleton(); + if (contentChild) { + contentChild->SendSynchronizeLayoutHistoryState( + mBrowsingContext, mActiveEntry->GetLayoutHistoryState()); + } + } else { + SessionHistoryEntry* entry = + mBrowsingContext->Canonical()->GetActiveSessionHistoryEntry(); + if (entry) { + entry->SetLayoutHistoryState(mActiveEntry->GetLayoutHistoryState()); + } + } + if (mLoadingEntry && + mLoadingEntry->mInfo.SharedId() == mActiveEntry->SharedId()) { + mLoadingEntry->mInfo.SetLayoutHistoryState( + mActiveEntry->GetLayoutHistoryState()); + } + } + + return NS_OK; +} + +void nsDocShell::SetLoadGroupDefaultLoadFlags(nsLoadFlags aLoadFlags) { + if (mLoadGroup) { + mLoadGroup->SetDefaultLoadFlags(aLoadFlags); + } else { + NS_WARNING( + "nsDocShell::SetLoadGroupDefaultLoadFlags has no loadGroup to " + "propagate the mode to"); + } +} + +nsIScriptGlobalObject* nsDocShell::GetScriptGlobalObject() { + NS_ENSURE_SUCCESS(EnsureScriptEnvironment(), nullptr); + return mScriptGlobal; +} + +Document* nsDocShell::GetDocument() { + NS_ENSURE_SUCCESS(EnsureContentViewer(), nullptr); + return mContentViewer->GetDocument(); +} + +Document* nsDocShell::GetExtantDocument() { + return mContentViewer ? mContentViewer->GetDocument() : nullptr; +} + +nsPIDOMWindowOuter* nsDocShell::GetWindow() { + if (NS_FAILED(EnsureScriptEnvironment())) { + return nullptr; + } + return mScriptGlobal; +} + +NS_IMETHODIMP +nsDocShell::GetDomWindow(mozIDOMWindowProxy** aWindow) { + NS_ENSURE_ARG_POINTER(aWindow); + + nsresult rv = EnsureScriptEnvironment(); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr window = mScriptGlobal; + window.forget(aWindow); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetMessageManager(ContentFrameMessageManager** aMessageManager) { + RefPtr mm; + if (RefPtr browserChild = BrowserChild::GetFrom(this)) { + mm = browserChild->GetMessageManager(); + } else if (nsPIDOMWindowOuter* win = GetWindow()) { + mm = win->GetMessageManager(); + } + mm.forget(aMessageManager); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetIsNavigating(bool* aOut) { + *aOut = mIsNavigating; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetDeviceSizeIsPageSize(bool aValue) { + if (mDeviceSizeIsPageSize != aValue) { + mDeviceSizeIsPageSize = aValue; + RefPtr presContext = GetPresContext(); + if (presContext) { + presContext->MediaFeatureValuesChanged( + {MediaFeatureChangeReason::DeviceSizeIsPageSizeChange}, + MediaFeatureChangePropagation::JustThisDocument); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetDeviceSizeIsPageSize(bool* aValue) { + *aValue = mDeviceSizeIsPageSize; + return NS_OK; +} + +void nsDocShell::ClearFrameHistory(nsISHEntry* aEntry) { + MOZ_ASSERT(!mozilla::SessionHistoryInParent()); + RefPtr rootSH = GetRootSessionHistory(); + if (!rootSH || !aEntry) { + return; + } + + rootSH->LegacySHistory()->RemoveFrameEntries(aEntry); +} + +//------------------------------------- +//-- Helper Method for Print discovery +//------------------------------------- +bool nsDocShell::NavigationBlockedByPrinting(bool aDisplayErrorDialog) { + if (!mBrowsingContext->Top()->GetIsPrinting()) { + return false; + } + if (aDisplayErrorDialog) { + DisplayLoadError(NS_ERROR_DOCUMENT_IS_PRINTMODE, nullptr, nullptr, nullptr); + } + return true; +} + +bool nsDocShell::IsNavigationAllowed(bool aDisplayPrintErrorDialog, + bool aCheckIfUnloadFired) { + bool isAllowed = !NavigationBlockedByPrinting(aDisplayPrintErrorDialog) && + (!aCheckIfUnloadFired || !mFiredUnloadEvent); + if (!isAllowed) { + return false; + } + if (!mContentViewer) { + return true; + } + bool firingBeforeUnload; + mContentViewer->GetBeforeUnloadFiring(&firingBeforeUnload); + return !firingBeforeUnload; +} + +//***************************************************************************** +// nsDocShell::nsIWebNavigation +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::GetCanGoBack(bool* aCanGoBack) { + *aCanGoBack = false; + if (!IsNavigationAllowed(false)) { + return NS_OK; // JS may not handle returning of an error code + } + RefPtr rootSH = GetRootSessionHistory(); + if (rootSH) { + *aCanGoBack = rootSH->CanGo(-1); + MOZ_LOG(gSHLog, LogLevel::Verbose, + ("nsDocShell %p CanGoBack()->%d", this, *aCanGoBack)); + + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsDocShell::GetCanGoForward(bool* aCanGoForward) { + *aCanGoForward = false; + if (!IsNavigationAllowed(false)) { + return NS_OK; // JS may not handle returning of an error code + } + RefPtr rootSH = GetRootSessionHistory(); + if (rootSH) { + *aCanGoForward = rootSH->CanGo(1); + MOZ_LOG(gSHLog, LogLevel::Verbose, + ("nsDocShell %p CanGoForward()->%d", this, *aCanGoForward)); + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsDocShell::GoBack(bool aRequireUserInteraction) { + if (!IsNavigationAllowed()) { + return NS_OK; // JS may not handle returning of an error code + } + + auto cleanupIsNavigating = MakeScopeExit([&]() { mIsNavigating = false; }); + mIsNavigating = true; + + RefPtr rootSH = GetRootSessionHistory(); + NS_ENSURE_TRUE(rootSH, NS_ERROR_FAILURE); + ErrorResult rv; + rootSH->Go(-1, aRequireUserInteraction, rv); + return rv.StealNSResult(); +} + +NS_IMETHODIMP +nsDocShell::GoForward(bool aRequireUserInteraction) { + if (!IsNavigationAllowed()) { + return NS_OK; // JS may not handle returning of an error code + } + + auto cleanupIsNavigating = MakeScopeExit([&]() { mIsNavigating = false; }); + mIsNavigating = true; + + RefPtr rootSH = GetRootSessionHistory(); + NS_ENSURE_TRUE(rootSH, NS_ERROR_FAILURE); + ErrorResult rv; + rootSH->Go(1, aRequireUserInteraction, rv); + return rv.StealNSResult(); +} + +// XXX(nika): We may want to stop exposing this API in the child process? Going +// to a specific index from multiple different processes could definitely race. +NS_IMETHODIMP +nsDocShell::GotoIndex(int32_t aIndex) { + if (!IsNavigationAllowed()) { + return NS_OK; // JS may not handle returning of an error code + } + + auto cleanupIsNavigating = MakeScopeExit([&]() { mIsNavigating = false; }); + mIsNavigating = true; + + RefPtr rootSH = GetRootSessionHistory(); + NS_ENSURE_TRUE(rootSH, NS_ERROR_FAILURE); + + ErrorResult rv; + rootSH->GotoIndex(aIndex, aIndex - rootSH->Index(), false, rv); + return rv.StealNSResult(); +} + +nsresult nsDocShell::LoadURI(const nsAString& aURI, + const LoadURIOptions& aLoadURIOptions) { + if (!IsNavigationAllowed()) { + return NS_OK; // JS may not handle returning of an error code + } + + RefPtr loadState; + nsresult rv = nsDocShellLoadState::CreateFromLoadURIOptions( + mBrowsingContext, aURI, aLoadURIOptions, getter_AddRefs(loadState)); + + uint32_t loadFlags = aLoadURIOptions.mLoadFlags; + if (NS_ERROR_MALFORMED_URI == rv) { + if (DisplayLoadError(rv, nullptr, PromiseFlatString(aURI).get(), nullptr) && + (loadFlags & LOAD_FLAGS_ERROR_LOAD_CHANGES_RV) != 0) { + return NS_ERROR_LOAD_SHOWED_ERRORPAGE; + } + } + + if (NS_FAILED(rv) || !loadState) { + return NS_ERROR_FAILURE; + } + + return LoadURI(loadState, true); +} + +NS_IMETHODIMP +nsDocShell::LoadURIFromScript(const nsAString& aURI, + JS::Handle aLoadURIOptions, + JSContext* aCx) { + // generate dictionary for aLoadURIOptions and forward call + LoadURIOptions loadURIOptions; + if (!loadURIOptions.Init(aCx, aLoadURIOptions)) { + return NS_ERROR_INVALID_ARG; + } + return LoadURI(aURI, loadURIOptions); +} + +void nsDocShell::UnblockEmbedderLoadEventForFailure(bool aFireFrameErrorEvent) { + // If we're not in a content frame, or are at a BrowsingContext tree boundary, + // such as the content-chrome boundary, don't fire the error event. + if (mBrowsingContext->IsTopContent() || mBrowsingContext->IsChrome()) { + return; + } + + // If embedder is same-process, then unblocking the load event is already + // handled by nsDocLoader. Fire the error event on our embedder element if + // requested. + // + // XXX: Bug 1440212 is looking into potentially changing this behaviour to act + // more like the remote case when in-process. + RefPtr element = mBrowsingContext->GetEmbedderElement(); + if (element) { + if (aFireFrameErrorEvent) { + if (RefPtr flo = do_QueryObject(element)) { + if (RefPtr fl = flo->GetFrameLoader()) { + fl->FireErrorEvent(); + } + } + } + return; + } + + // If we have a cross-process parent document, we must notify it that we no + // longer block its load event. This is necessary for OOP sub-documents + // because error documents do not result in a call to + // SendMaybeFireEmbedderLoadEvents via any of the normal call paths. + // (Obviously, we must do this before any of the returns below.) + RefPtr browserChild = BrowserChild::GetFrom(this); + if (browserChild) { + mozilla::Unused << browserChild->SendMaybeFireEmbedderLoadEvents( + aFireFrameErrorEvent ? EmbedderElementEventType::ErrorEvent + : EmbedderElementEventType::NoEvent); + } +} + +NS_IMETHODIMP +nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI, + const char16_t* aURL, nsIChannel* aFailedChannel, + bool* aDisplayedErrorPage) { + MOZ_LOG(gDocShellLeakLog, LogLevel::Debug, + ("DOCSHELL %p DisplayLoadError %s\n", this, + aURI ? aURI->GetSpecOrDefault().get() : "")); + + *aDisplayedErrorPage = false; + // Get prompt and string bundle services + nsCOMPtr prompter; + nsCOMPtr stringBundle; + GetPromptAndStringBundle(getter_AddRefs(prompter), + getter_AddRefs(stringBundle)); + + NS_ENSURE_TRUE(stringBundle, NS_ERROR_FAILURE); + NS_ENSURE_TRUE(prompter, NS_ERROR_FAILURE); + + const char* error = nullptr; + // The key used to select the appropriate error message from the properties + // file. + const char* errorDescriptionID = nullptr; + AutoTArray formatStrs; + bool addHostPort = false; + nsresult rv = NS_OK; + nsAutoString messageStr; + nsAutoCString cssClass; + nsAutoCString errorPage; + + errorPage.AssignLiteral("neterror"); + + // Turn the error code into a human readable error message. + if (NS_ERROR_UNKNOWN_PROTOCOL == aError) { + NS_ENSURE_ARG_POINTER(aURI); + + // Extract the schemes into a comma delimited list. + nsAutoCString scheme; + aURI->GetScheme(scheme); + CopyASCIItoUTF16(scheme, *formatStrs.AppendElement()); + nsCOMPtr nestedURI = do_QueryInterface(aURI); + while (nestedURI) { + nsCOMPtr tempURI; + nsresult rv2; + rv2 = nestedURI->GetInnerURI(getter_AddRefs(tempURI)); + if (NS_SUCCEEDED(rv2) && tempURI) { + tempURI->GetScheme(scheme); + formatStrs[0].AppendLiteral(", "); + AppendASCIItoUTF16(scheme, formatStrs[0]); + } + nestedURI = do_QueryInterface(tempURI); + } + error = "unknownProtocolFound"; + } else if (NS_ERROR_FILE_NOT_FOUND == aError) { + NS_ENSURE_ARG_POINTER(aURI); + error = "fileNotFound"; + } else if (NS_ERROR_FILE_ACCESS_DENIED == aError) { + NS_ENSURE_ARG_POINTER(aURI); + error = "fileAccessDenied"; + } else if (NS_ERROR_UNKNOWN_HOST == aError) { + NS_ENSURE_ARG_POINTER(aURI); + // Get the host + nsAutoCString host; + nsCOMPtr innermostURI = NS_GetInnermostURI(aURI); + innermostURI->GetHost(host); + CopyUTF8toUTF16(host, *formatStrs.AppendElement()); + errorDescriptionID = "dnsNotFound2"; + error = "dnsNotFound"; + } else if (NS_ERROR_CONNECTION_REFUSED == aError || + NS_ERROR_PROXY_BAD_GATEWAY == aError) { + NS_ENSURE_ARG_POINTER(aURI); + addHostPort = true; + error = "connectionFailure"; + } else if (NS_ERROR_NET_INTERRUPT == aError) { + NS_ENSURE_ARG_POINTER(aURI); + addHostPort = true; + error = "netInterrupt"; + } else if (NS_ERROR_NET_TIMEOUT == aError || + NS_ERROR_PROXY_GATEWAY_TIMEOUT == aError) { + NS_ENSURE_ARG_POINTER(aURI); + // Get the host + nsAutoCString host; + aURI->GetHost(host); + CopyUTF8toUTF16(host, *formatStrs.AppendElement()); + error = "netTimeout"; + } else if (NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION == aError || + NS_ERROR_CSP_FORM_ACTION_VIOLATION == aError || + NS_ERROR_CSP_NAVIGATE_TO_VIOLATION == aError) { + // CSP error + cssClass.AssignLiteral("neterror"); + error = "cspBlocked"; + } else if (NS_ERROR_XFO_VIOLATION == aError) { + // XFO error + cssClass.AssignLiteral("neterror"); + error = "xfoBlocked"; + } else if (NS_ERROR_GET_MODULE(aError) == NS_ERROR_MODULE_SECURITY) { + nsCOMPtr nsserr = + do_GetService(NS_NSS_ERRORS_SERVICE_CONTRACTID); + + uint32_t errorClass; + if (!nsserr || NS_FAILED(nsserr->GetErrorClass(aError, &errorClass))) { + errorClass = nsINSSErrorsService::ERROR_CLASS_SSL_PROTOCOL; + } + + nsCOMPtr securityInfo; + nsCOMPtr tsi; + if (aFailedChannel) { + aFailedChannel->GetSecurityInfo(getter_AddRefs(securityInfo)); + } + tsi = do_QueryInterface(securityInfo); + if (tsi) { + uint32_t securityState; + tsi->GetSecurityState(&securityState); + if (securityState & nsIWebProgressListener::STATE_USES_SSL_3) { + error = "sslv3Used"; + addHostPort = true; + } else if (securityState & + nsIWebProgressListener::STATE_USES_WEAK_CRYPTO) { + error = "weakCryptoUsed"; + addHostPort = true; + } + } else { + // No channel, let's obtain the generic error message + if (nsserr) { + nsserr->GetErrorMessage(aError, messageStr); + } + } + // We don't have a message string here anymore but DisplayLoadError + // requires a non-empty messageStr. + messageStr.Truncate(); + messageStr.AssignLiteral(u" "); + if (errorClass == nsINSSErrorsService::ERROR_CLASS_BAD_CERT) { + error = "nssBadCert"; + + // If this is an HTTP Strict Transport Security host or a pinned host + // and the certificate is bad, don't allow overrides (RFC 6797 section + // 12.1). + uint32_t flags = + UsePrivateBrowsing() ? nsISocketProvider::NO_PERMANENT_STORAGE : 0; + bool isStsHost = false; + bool isPinnedHost = false; + if (XRE_IsParentProcess()) { + nsCOMPtr sss = + do_GetService(NS_SSSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, aURI, flags, + GetOriginAttributes(), nullptr, nullptr, + &isStsHost); + NS_ENSURE_SUCCESS(rv, rv); + rv = sss->IsSecureURI(nsISiteSecurityService::STATIC_PINNING, aURI, + flags, GetOriginAttributes(), nullptr, nullptr, + &isPinnedHost); + NS_ENSURE_SUCCESS(rv, rv); + } else { + mozilla::dom::ContentChild* cc = + mozilla::dom::ContentChild::GetSingleton(); + cc->SendIsSecureURI(nsISiteSecurityService::HEADER_HSTS, aURI, flags, + GetOriginAttributes(), &isStsHost); + cc->SendIsSecureURI(nsISiteSecurityService::STATIC_PINNING, aURI, flags, + GetOriginAttributes(), &isPinnedHost); + } + + if (Preferences::GetBool("browser.xul.error_pages.expert_bad_cert", + false)) { + cssClass.AssignLiteral("expertBadCert"); + } + + // HSTS/pinning takes precedence over the expert bad cert pref. We + // never want to show the "Add Exception" button for these sites. + // In the future we should differentiate between an HSTS host and a + // pinned host and display a more informative message to the user. + if (isStsHost || isPinnedHost) { + cssClass.AssignLiteral("badStsCert"); + } + + // See if an alternate cert error page is registered + nsAutoCString alternateErrorPage; + nsresult rv = Preferences::GetCString( + "security.alternate_certificate_error_page", alternateErrorPage); + if (NS_SUCCEEDED(rv)) { + errorPage.Assign(alternateErrorPage); + } + } else { + error = "nssFailure2"; + } + } else if (NS_ERROR_PHISHING_URI == aError || + NS_ERROR_MALWARE_URI == aError || + NS_ERROR_UNWANTED_URI == aError || + NS_ERROR_HARMFUL_URI == aError) { + nsAutoCString host; + aURI->GetHost(host); + CopyUTF8toUTF16(host, *formatStrs.AppendElement()); + + // Malware and phishing detectors may want to use an alternate error + // page, but if the pref's not set, we'll fall back on the standard page + nsAutoCString alternateErrorPage; + nsresult rv = Preferences::GetCString("urlclassifier.alternate_error_page", + alternateErrorPage); + if (NS_SUCCEEDED(rv)) { + errorPage.Assign(alternateErrorPage); + } + + if (NS_ERROR_PHISHING_URI == aError) { + error = "deceptiveBlocked"; + } else if (NS_ERROR_MALWARE_URI == aError) { + error = "malwareBlocked"; + } else if (NS_ERROR_UNWANTED_URI == aError) { + error = "unwantedBlocked"; + } else if (NS_ERROR_HARMFUL_URI == aError) { + error = "harmfulBlocked"; + } + + cssClass.AssignLiteral("blacklist"); + } else if (NS_ERROR_CONTENT_CRASHED == aError) { + errorPage.AssignLiteral("tabcrashed"); + error = "tabcrashed"; + + RefPtr handler = mChromeEventHandler; + if (handler) { + nsCOMPtr element = do_QueryInterface(handler); + element->GetAttribute(u"crashedPageTitle"_ns, messageStr); + } + + // DisplayLoadError requires a non-empty messageStr to proceed and call + // LoadErrorPage. If the page doesn't have a title, we will use a blank + // space which will be trimmed and thus treated as empty by the front-end. + if (messageStr.IsEmpty()) { + messageStr.AssignLiteral(u" "); + } + } else if (NS_ERROR_FRAME_CRASHED == aError) { + errorPage.AssignLiteral("framecrashed"); + error = "framecrashed"; + messageStr.AssignLiteral(u" "); + } else if (NS_ERROR_BUILDID_MISMATCH == aError) { + errorPage.AssignLiteral("restartrequired"); + error = "restartrequired"; + + // DisplayLoadError requires a non-empty messageStr to proceed and call + // LoadErrorPage. If the page doesn't have a title, we will use a blank + // space which will be trimmed and thus treated as empty by the front-end. + if (messageStr.IsEmpty()) { + messageStr.AssignLiteral(u" "); + } + } else { + // Errors requiring simple formatting + switch (aError) { + case NS_ERROR_MALFORMED_URI: + // URI is malformed + error = "malformedURI"; + errorDescriptionID = "malformedURI2"; + break; + case NS_ERROR_REDIRECT_LOOP: + // Doc failed to load because the server generated too many redirects + error = "redirectLoop"; + break; + case NS_ERROR_UNKNOWN_SOCKET_TYPE: + // Doc failed to load because PSM is not installed + error = "unknownSocketType"; + break; + case NS_ERROR_NET_RESET: + // Doc failed to load because the server kept reseting the connection + // before we could read any data from it + error = "netReset"; + break; + case NS_ERROR_DOCUMENT_NOT_CACHED: + // Doc failed to load because the cache does not contain a copy of + // the document. + error = "notCached"; + break; + case NS_ERROR_OFFLINE: + // Doc failed to load because we are offline. + error = "netOffline"; + break; + case NS_ERROR_DOCUMENT_IS_PRINTMODE: + // Doc navigation attempted while Printing or Print Preview + error = "isprinting"; + break; + case NS_ERROR_PORT_ACCESS_NOT_ALLOWED: + // Port blocked for security reasons + addHostPort = true; + error = "deniedPortAccess"; + break; + case NS_ERROR_UNKNOWN_PROXY_HOST: + // Proxy hostname could not be resolved. + error = "proxyResolveFailure"; + break; + case NS_ERROR_PROXY_CONNECTION_REFUSED: + case NS_ERROR_PROXY_FORBIDDEN: + case NS_ERROR_PROXY_NOT_IMPLEMENTED: + case NS_ERROR_PROXY_AUTHENTICATION_FAILED: + case NS_ERROR_PROXY_TOO_MANY_REQUESTS: + // Proxy connection was refused. + error = "proxyConnectFailure"; + break; + case NS_ERROR_INVALID_CONTENT_ENCODING: + // Bad Content Encoding. + error = "contentEncodingError"; + break; + case NS_ERROR_REMOTE_XUL: + error = "remoteXUL"; + break; + case NS_ERROR_UNSAFE_CONTENT_TYPE: + // Channel refused to load from an unrecognized content type. + error = "unsafeContentType"; + break; + case NS_ERROR_CORRUPTED_CONTENT: + // Broken Content Detected. e.g. Content-MD5 check failure. + error = "corruptedContentErrorv2"; + break; + case NS_ERROR_INTERCEPTION_FAILED: + // ServiceWorker intercepted request, but something went wrong. + error = "corruptedContentErrorv2"; + break; + case NS_ERROR_NET_INADEQUATE_SECURITY: + // Server negotiated bad TLS for HTTP/2. + error = "inadequateSecurityError"; + addHostPort = true; + break; + case NS_ERROR_BLOCKED_BY_POLICY: + // Page blocked by policy + error = "blockedByPolicy"; + break; + case NS_ERROR_NET_HTTP2_SENT_GOAWAY: + case NS_ERROR_NET_HTTP3_PROTOCOL_ERROR: + // HTTP/2 or HTTP/3 stack detected a protocol error + error = "networkProtocolError"; + break; + + default: + break; + } + } + + // If the HTTPS-Only Mode upgraded this request and the upgrade might have + // caused this error, we replace the error-page with about:httpsonlyerror + if (nsHTTPSOnlyUtils::CouldBeHttpsOnlyError(aFailedChannel, aError)) { + errorPage.AssignLiteral("httpsonlyerror"); + } + + if (nsCOMPtr loadURIDelegate = GetLoadURIDelegate()) { + nsCOMPtr errorPageURI; + rv = loadURIDelegate->HandleLoadError(aURI, aError, + NS_ERROR_GET_MODULE(aError), + getter_AddRefs(errorPageURI)); + // If the docshell is going away there's no point in showing an error page. + if (NS_FAILED(rv) || mIsBeingDestroyed) { + *aDisplayedErrorPage = false; + return NS_OK; + } + + if (errorPageURI) { + *aDisplayedErrorPage = + NS_SUCCEEDED(LoadErrorPage(errorPageURI, aURI, aFailedChannel)); + return NS_OK; + } + } + + // Test if the error should be displayed + if (!error) { + return NS_OK; + } + + if (!errorDescriptionID) { + errorDescriptionID = error; + } + + Telemetry::AccumulateCategoricalKeyed( + IsFrame() ? "frame"_ns : "top"_ns, + mozilla::dom::LoadErrorToTelemetryLabel(aError)); + + // Test if the error needs to be formatted + if (!messageStr.IsEmpty()) { + // already obtained message + } else { + if (addHostPort) { + // Build up the host:port string. + nsAutoCString hostport; + if (aURI) { + aURI->GetHostPort(hostport); + } else { + hostport.Assign('?'); + } + CopyUTF8toUTF16(hostport, *formatStrs.AppendElement()); + } + + nsAutoCString spec; + rv = NS_ERROR_NOT_AVAILABLE; + auto& nextFormatStr = *formatStrs.AppendElement(); + if (aURI) { + // displaying "file://" is aesthetically unpleasing and could even be + // confusing to the user + if (SchemeIsFile(aURI)) { + aURI->GetPathQueryRef(spec); + } else { + aURI->GetSpec(spec); + } + + nsCOMPtr textToSubURI( + do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + rv = textToSubURI->UnEscapeURIForUI(spec, nextFormatStr); + } + } else { + spec.Assign('?'); + } + if (NS_FAILED(rv)) { + CopyUTF8toUTF16(spec, nextFormatStr); + } + rv = NS_OK; + + nsAutoString str; + rv = + stringBundle->FormatStringFromName(errorDescriptionID, formatStrs, str); + NS_ENSURE_SUCCESS(rv, rv); + messageStr.Assign(str); + } + + // Display the error as a page or an alert prompt + NS_ENSURE_FALSE(messageStr.IsEmpty(), NS_ERROR_FAILURE); + + if ((NS_ERROR_NET_INTERRUPT == aError || NS_ERROR_NET_RESET == aError) && + SchemeIsHTTPS(aURI)) { + // Maybe TLS intolerant. Treat this as an SSL error. + error = "nssFailure2"; + } + + if (mBrowsingContext->GetUseErrorPages()) { + // Display an error page + nsresult loadedPage = + LoadErrorPage(aURI, aURL, errorPage.get(), error, messageStr.get(), + cssClass.get(), aFailedChannel); + *aDisplayedErrorPage = NS_SUCCEEDED(loadedPage); + } else { + // The prompter reqires that our private window has a document (or it + // asserts). Satisfy that assertion now since GetDoc will force + // creation of one if it hasn't already been created. + if (mScriptGlobal) { + Unused << mScriptGlobal->GetDoc(); + } + + // Display a message box + prompter->Alert(nullptr, messageStr.get()); + } + + return NS_OK; +} + +#define PREF_SAFEBROWSING_ALLOWOVERRIDE "browser.safebrowsing.allowOverride" + +nsresult nsDocShell::LoadErrorPage(nsIURI* aURI, const char16_t* aURL, + const char* aErrorPage, + const char* aErrorType, + const char16_t* aDescription, + const char* aCSSClass, + nsIChannel* aFailedChannel) { + MOZ_ASSERT(!mIsBeingDestroyed); + +#if defined(DEBUG) + if (MOZ_LOG_TEST(gDocShellLog, LogLevel::Debug)) { + nsAutoCString chanName; + if (aFailedChannel) { + aFailedChannel->GetName(chanName); + } else { + chanName.AssignLiteral(""); + } + + MOZ_LOG(gDocShellLog, LogLevel::Debug, + ("nsDocShell[%p]::LoadErrorPage(\"%s\", \"%s\", {...}, [%s])\n", + this, aURI ? aURI->GetSpecOrDefault().get() : "", + NS_ConvertUTF16toUTF8(aURL).get(), chanName.get())); + } +#endif + + nsAutoCString url; + if (aURI) { + nsresult rv = aURI->GetSpec(url); + NS_ENSURE_SUCCESS(rv, rv); + } else if (aURL) { + CopyUTF16toUTF8(MakeStringSpan(aURL), url); + } else { + return NS_ERROR_INVALID_POINTER; + } + + // Create a URL to pass all the error information through to the page. + +#undef SAFE_ESCAPE +#define SAFE_ESCAPE(output, input, params) \ + if (NS_WARN_IF(!NS_Escape(input, output, params))) { \ + return NS_ERROR_OUT_OF_MEMORY; \ + } + + nsCString escapedUrl, escapedError, escapedDescription, escapedCSSClass; + SAFE_ESCAPE(escapedUrl, url, url_Path); + SAFE_ESCAPE(escapedError, nsDependentCString(aErrorType), url_Path); + SAFE_ESCAPE(escapedDescription, NS_ConvertUTF16toUTF8(aDescription), + url_Path); + if (aCSSClass) { + nsCString cssClass(aCSSClass); + SAFE_ESCAPE(escapedCSSClass, cssClass, url_Path); + } + nsCString errorPageUrl("about:"); + errorPageUrl.AppendASCII(aErrorPage); + errorPageUrl.AppendLiteral("?e="); + + errorPageUrl.AppendASCII(escapedError.get()); + errorPageUrl.AppendLiteral("&u="); + errorPageUrl.AppendASCII(escapedUrl.get()); + if ((strcmp(aErrorPage, "blocked") == 0) && + Preferences::GetBool(PREF_SAFEBROWSING_ALLOWOVERRIDE, true)) { + errorPageUrl.AppendLiteral("&o=1"); + } + if (!escapedCSSClass.IsEmpty()) { + errorPageUrl.AppendLiteral("&s="); + errorPageUrl.AppendASCII(escapedCSSClass.get()); + } + errorPageUrl.AppendLiteral("&c=UTF-8"); + + nsCOMPtr cps = do_GetService(NS_CAPTIVEPORTAL_CID); + int32_t cpsState; + if (cps && NS_SUCCEEDED(cps->GetState(&cpsState)) && + cpsState == nsICaptivePortalService::LOCKED_PORTAL) { + errorPageUrl.AppendLiteral("&captive=true"); + } + + // netError.xhtml's getDescription only handles the "d" parameter at the + // end of the URL, so append it last. + errorPageUrl.AppendLiteral("&d="); + errorPageUrl.AppendASCII(escapedDescription.get()); + + nsCOMPtr errorPageURI; + nsresult rv = NS_NewURI(getter_AddRefs(errorPageURI), errorPageUrl); + NS_ENSURE_SUCCESS(rv, rv); + + return LoadErrorPage(errorPageURI, aURI, aFailedChannel); +} + +nsresult nsDocShell::LoadErrorPage(nsIURI* aErrorURI, nsIURI* aFailedURI, + nsIChannel* aFailedChannel) { + mFailedChannel = aFailedChannel; + mFailedURI = aFailedURI; + mFailedLoadType = mLoadType; + + if (mLSHE) { + // Abandon mLSHE's BFCache entry and create a new one. This way, if + // we go back or forward to another SHEntry with the same doc + // identifier, the error page won't persist. + mLSHE->AbandonBFCacheEntry(); + } + + RefPtr loadState = new nsDocShellLoadState(aErrorURI); + loadState->SetTriggeringPrincipal(nsContentUtils::GetSystemPrincipal()); + if (mBrowsingContext) { + loadState->SetTriggeringSandboxFlags(mBrowsingContext->GetSandboxFlags()); + } + loadState->SetLoadType(LOAD_ERROR_PAGE); + loadState->SetFirstParty(true); + loadState->SetSourceBrowsingContext(mBrowsingContext); + if (mozilla::SessionHistoryInParent() && mLoadingEntry) { + // We keep the loading entry for the load that failed here. If the user + // reloads we want to try to reload the original load, not the error page. + loadState->SetLoadingSessionHistoryInfo( + MakeUnique(*mLoadingEntry)); + } + return InternalLoad(loadState); +} + +NS_IMETHODIMP +nsDocShell::Reload(uint32_t aReloadFlags) { + if (!IsNavigationAllowed()) { + return NS_OK; // JS may not handle returning of an error code + } + + NS_ASSERTION(((aReloadFlags & INTERNAL_LOAD_FLAGS_LOADURI_SETUP_FLAGS) == 0), + "Reload command not updated to use load flags!"); + NS_ASSERTION((aReloadFlags & EXTRA_LOAD_FLAGS) == 0, + "Don't pass these flags to Reload"); + + uint32_t loadType = MAKE_LOAD_TYPE(LOAD_RELOAD_NORMAL, aReloadFlags); + NS_ENSURE_TRUE(IsValidLoadType(loadType), NS_ERROR_INVALID_ARG); + + // Send notifications to the HistoryListener if any, about the impending + // reload + RefPtr rootSH = GetRootSessionHistory(); + if (mozilla::SessionHistoryInParent()) { + MOZ_LOG(gSHLog, LogLevel::Debug, ("nsDocShell %p Reload", this)); + bool forceReload = IsForceReloadType(loadType); + if (!XRE_IsParentProcess()) { + RefPtr docShell(this); + nsCOMPtr cv(mContentViewer); + + bool okToUnload = true; + MOZ_TRY(cv->PermitUnload(&okToUnload)); + if (!okToUnload) { + return NS_OK; + } + + RefPtr doc(GetDocument()); + RefPtr browsingContext(mBrowsingContext); + nsCOMPtr currentURI(mCurrentURI); + nsCOMPtr referrerInfo(mReferrerInfo); + + ContentChild::GetSingleton()->SendNotifyOnHistoryReload( + mBrowsingContext, forceReload, + [docShell, doc, loadType, browsingContext, currentURI, referrerInfo]( + Tuple>, Maybe>&& + aResult) { + bool canReload; + Maybe> loadState; + Maybe reloadingActiveEntry; + + Tie(canReload, loadState, reloadingActiveEntry) = aResult; + + if (!canReload) { + return; + } + + if (loadState.isSome()) { + MOZ_LOG( + gSHLog, LogLevel::Debug, + ("nsDocShell %p Reload - LoadHistoryEntry", docShell.get())); + loadState.ref()->SetNotifiedBeforeUnloadListeners(true); + docShell->LoadHistoryEntry(loadState.ref(), loadType, + reloadingActiveEntry.ref()); + } else { + MOZ_LOG(gSHLog, LogLevel::Debug, + ("nsDocShell %p ReloadDocument", docShell.get())); + ReloadDocument(docShell, doc, loadType, browsingContext, + currentURI, referrerInfo, + /* aNotifiedBeforeUnloadListeners */ true); + } + }, + [](mozilla::ipc::ResponseRejectReason) {}); + } else { + // Parent process + bool canReload = false; + Maybe> loadState; + Maybe reloadingActiveEntry; + if (!mBrowsingContext->IsDiscarded()) { + mBrowsingContext->Canonical()->NotifyOnHistoryReload( + forceReload, canReload, loadState, reloadingActiveEntry); + } + if (canReload) { + if (loadState.isSome()) { + MOZ_LOG(gSHLog, LogLevel::Debug, + ("nsDocShell %p Reload - LoadHistoryEntry", this)); + LoadHistoryEntry(loadState.ref(), loadType, + reloadingActiveEntry.ref()); + } else { + MOZ_LOG(gSHLog, LogLevel::Debug, + ("nsDocShell %p ReloadDocument", this)); + ReloadDocument(this, GetDocument(), loadType, mBrowsingContext, + mCurrentURI, mReferrerInfo); + } + } + } + return NS_OK; + } + + bool canReload = true; + if (rootSH) { + rootSH->LegacySHistory()->NotifyOnHistoryReload(&canReload); + } + + if (!canReload) { + return NS_OK; + } + + /* If you change this part of code, make sure bug 45297 does not re-occur */ + if (mOSHE) { + return LoadHistoryEntry(mOSHE, loadType); + } + + if (mLSHE) { // In case a reload happened before the current load is done + return LoadHistoryEntry(mLSHE, loadType); + } + + return ReloadDocument(this, GetDocument(), loadType, mBrowsingContext, + mCurrentURI, mReferrerInfo); +} + +/* static */ +nsresult nsDocShell::ReloadDocument(nsDocShell* aDocShell, Document* aDocument, + uint32_t aLoadType, + BrowsingContext* aBrowsingContext, + nsIURI* aCurrentURI, + nsIReferrerInfo* aReferrerInfo, + bool aNotifiedBeforeUnloadListeners) { + if (!aDocument) { + return NS_OK; + } + + // Do not inherit owner from document + uint32_t flags = INTERNAL_LOAD_FLAGS_NONE; + nsAutoString srcdoc; + nsIURI* baseURI = nullptr; + nsCOMPtr originalURI; + nsCOMPtr resultPrincipalURI; + bool loadReplace = false; + + nsIPrincipal* triggeringPrincipal = aDocument->NodePrincipal(); + nsCOMPtr csp = aDocument->GetCsp(); + uint32_t triggeringSandboxFlags = aDocument->GetSandboxFlags(); + + nsAutoString contentTypeHint; + aDocument->GetContentType(contentTypeHint); + + if (aDocument->IsSrcdocDocument()) { + aDocument->GetSrcdocData(srcdoc); + flags |= INTERNAL_LOAD_FLAGS_IS_SRCDOC; + baseURI = aDocument->GetBaseURI(); + } else { + srcdoc = VoidString(); + } + nsCOMPtr chan = aDocument->GetChannel(); + if (chan) { + uint32_t loadFlags; + chan->GetLoadFlags(&loadFlags); + loadReplace = loadFlags & nsIChannel::LOAD_REPLACE; + nsCOMPtr httpChan(do_QueryInterface(chan)); + if (httpChan) { + httpChan->GetOriginalURI(getter_AddRefs(originalURI)); + } + + nsCOMPtr loadInfo = chan->LoadInfo(); + loadInfo->GetResultPrincipalURI(getter_AddRefs(resultPrincipalURI)); + } + + if (!triggeringPrincipal) { + MOZ_ASSERT(false, "Reload needs a valid triggeringPrincipal"); + return NS_ERROR_FAILURE; + } + + // Stack variables to ensure changes to the member variables don't affect to + // the call. + nsCOMPtr currentURI = aCurrentURI; + + // Reload always rewrites result principal URI. + Maybe> emplacedResultPrincipalURI; + emplacedResultPrincipalURI.emplace(std::move(resultPrincipalURI)); + + RefPtr context = aBrowsingContext->GetCurrentWindowContext(); + RefPtr loadState = new nsDocShellLoadState(currentURI); + loadState->SetReferrerInfo(aReferrerInfo); + loadState->SetOriginalURI(originalURI); + loadState->SetMaybeResultPrincipalURI(emplacedResultPrincipalURI); + loadState->SetLoadReplace(loadReplace); + loadState->SetTriggeringPrincipal(triggeringPrincipal); + loadState->SetTriggeringSandboxFlags(triggeringSandboxFlags); + loadState->SetPrincipalToInherit(triggeringPrincipal); + loadState->SetCsp(csp); + loadState->SetLoadFlags(flags); + loadState->SetTypeHint(NS_ConvertUTF16toUTF8(contentTypeHint)); + loadState->SetLoadType(aLoadType); + loadState->SetFirstParty(true); + loadState->SetSrcdocData(srcdoc); + loadState->SetSourceBrowsingContext(aBrowsingContext); + loadState->SetBaseURI(baseURI); + loadState->SetHasValidUserGestureActivation( + context && context->HasValidTransientUserGestureActivation()); + loadState->SetNotifiedBeforeUnloadListeners(aNotifiedBeforeUnloadListeners); + return aDocShell->InternalLoad(loadState); +} + +NS_IMETHODIMP +nsDocShell::Stop(uint32_t aStopFlags) { + // Revoke any pending event related to content viewer restoration + mRestorePresentationEvent.Revoke(); + + if (mLoadType == LOAD_ERROR_PAGE) { + if (mLSHE) { + // Since error page loads never unset mLSHE, do so now + SetHistoryEntryAndUpdateBC(Some(nullptr), Some(mLSHE)); + } + mActiveEntryIsLoadingFromSessionHistory = false; + + mFailedChannel = nullptr; + mFailedURI = nullptr; + } + + if (nsIWebNavigation::STOP_CONTENT & aStopFlags) { + // Stop the document loading + if (mContentViewer) { + nsCOMPtr cv = mContentViewer; + cv->Stop(); + } + } + + if (nsIWebNavigation::STOP_NETWORK & aStopFlags) { + // Suspend any timers that were set for this loader. We'll clear + // them out for good in CreateContentViewer. + if (mRefreshURIList) { + SuspendRefreshURIs(); + mSavedRefreshURIList.swap(mRefreshURIList); + mRefreshURIList = nullptr; + } + + // XXXbz We could also pass |this| to nsIURILoader::Stop. That will + // just call Stop() on us as an nsIDocumentLoader... We need fewer + // redundant apis! + Stop(); + } + + for (auto* child : mChildList.ForwardRange()) { + nsCOMPtr shellAsNav(do_QueryObject(child)); + if (shellAsNav) { + shellAsNav->Stop(aStopFlags); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetDocument(Document** aDocument) { + NS_ENSURE_ARG_POINTER(aDocument); + NS_ENSURE_SUCCESS(EnsureContentViewer(), NS_ERROR_FAILURE); + + RefPtr doc = mContentViewer->GetDocument(); + if (!doc) { + return NS_ERROR_NOT_AVAILABLE; + } + + doc.forget(aDocument); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetCurrentURI(nsIURI** aURI) { + NS_ENSURE_ARG_POINTER(aURI); + + nsCOMPtr uri = mCurrentURI; + uri.forget(aURI); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetSessionHistoryXPCOM(nsISupports** aSessionHistory) { + NS_ENSURE_ARG_POINTER(aSessionHistory); + RefPtr shistory = GetSessionHistory(); + shistory.forget(aSessionHistory); + return NS_OK; +} + +//***************************************************************************** +// nsDocShell::nsIWebPageDescriptor +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::LoadPageAsViewSource(nsIDocShell* aOtherDocShell, + const nsAString& aURI) { + if (!aOtherDocShell) { + return NS_ERROR_INVALID_POINTER; + } + nsCOMPtr newURI; + nsresult rv = NS_NewURI(getter_AddRefs(newURI), aURI); + if (NS_FAILED(rv)) { + return rv; + } + + RefPtr loadState; + uint32_t cacheKey; + auto* otherDocShell = nsDocShell::Cast(aOtherDocShell); + if (mozilla::SessionHistoryInParent()) { + loadState = new nsDocShellLoadState(newURI); + if (!otherDocShell->FillLoadStateFromCurrentEntry(*loadState)) { + return NS_ERROR_INVALID_POINTER; + } + cacheKey = otherDocShell->GetCacheKeyFromCurrentEntry().valueOr(0); + } else { + nsCOMPtr entry; + bool isOriginalSHE; + otherDocShell->GetCurrentSHEntry(getter_AddRefs(entry), &isOriginalSHE); + if (!entry) { + return NS_ERROR_INVALID_POINTER; + } + rv = entry->CreateLoadInfo(getter_AddRefs(loadState)); + NS_ENSURE_SUCCESS(rv, rv); + entry->GetCacheKey(&cacheKey); + loadState->SetURI(newURI); + loadState->SetSHEntry(nullptr); + } + + // We're doing a load of the page, via an API that + // is only exposed to system code. The triggering principal for this load + // should be the system principal. + loadState->SetTriggeringPrincipal(nsContentUtils::GetSystemPrincipal()); + loadState->SetOriginalURI(nullptr); + loadState->SetResultPrincipalURI(nullptr); + + return InternalLoad(loadState, Some(cacheKey)); +} + +NS_IMETHODIMP +nsDocShell::GetCurrentDescriptor(nsISupports** aPageDescriptor) { + MOZ_ASSERT(aPageDescriptor, "Null out param?"); + + *aPageDescriptor = nullptr; + + nsISHEntry* src = mOSHE ? mOSHE : mLSHE; + if (src) { + nsCOMPtr dest; + + nsresult rv = src->Clone(getter_AddRefs(dest)); + if (NS_FAILED(rv)) { + return rv; + } + + // null out inappropriate cloned attributes... + dest->SetParent(nullptr); + dest->SetIsSubFrame(false); + + return CallQueryInterface(dest, aPageDescriptor); + } + + return NS_ERROR_NOT_AVAILABLE; +} + +already_AddRefed nsDocShell::GetPostDataFromCurrentEntry() + const { + nsCOMPtr postData; + if (mozilla::SessionHistoryInParent()) { + if (mActiveEntry) { + postData = mActiveEntry->GetPostData(); + } else if (mLoadingEntry) { + postData = mLoadingEntry->mInfo.GetPostData(); + } + } else { + if (mOSHE) { + postData = mOSHE->GetPostData(); + } else if (mLSHE) { + postData = mLSHE->GetPostData(); + } + } + + return postData.forget(); +} + +Maybe nsDocShell::GetCacheKeyFromCurrentEntry() const { + if (mozilla::SessionHistoryInParent()) { + if (mActiveEntry) { + return Some(mActiveEntry->GetCacheKey()); + } + + if (mLoadingEntry) { + return Some(mLoadingEntry->mInfo.GetCacheKey()); + } + } else { + if (mOSHE) { + return Some(mOSHE->GetCacheKey()); + } + + if (mLSHE) { + return Some(mLSHE->GetCacheKey()); + } + } + + return Nothing(); +} + +bool nsDocShell::FillLoadStateFromCurrentEntry( + nsDocShellLoadState& aLoadState) { + if (mLoadingEntry) { + mLoadingEntry->mInfo.FillLoadInfo(aLoadState); + return true; + } + if (mActiveEntry) { + mActiveEntry->FillLoadInfo(aLoadState); + return true; + } + return false; +} + +//***************************************************************************** +// nsDocShell::nsIBaseWindow +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::InitWindow(nativeWindow aParentNativeWindow, + nsIWidget* aParentWidget, int32_t aX, int32_t aY, + int32_t aWidth, int32_t aHeight) { + SetParentWidget(aParentWidget); + SetPositionAndSize(aX, aY, aWidth, aHeight, 0); + NS_ENSURE_TRUE(Initialize(), NS_ERROR_FAILURE); + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::Destroy() { + // XXX: We allow this function to be called just once. If you are going to + // reset new variables in this function, please make sure the variables will + // never be re-initialized. Adding assertions to check |mIsBeingDestroyed| + // in the setter functions for the variables would be enough. + if (mIsBeingDestroyed) { + return NS_ERROR_DOCSHELL_DYING; + } + + NS_ASSERTION(mItemType == typeContent || mItemType == typeChrome, + "Unexpected item type in docshell"); + + nsCOMPtr serv = services::GetObserverService(); + if (serv) { + const char* msg = mItemType == typeContent + ? NS_WEBNAVIGATION_DESTROY + : NS_CHROME_WEBNAVIGATION_DESTROY; + serv->NotifyObservers(GetAsSupports(this), msg, nullptr); + } + + mIsBeingDestroyed = true; + + // Brak the cycle with the initial client, if present. + mInitialClientSource.reset(); + + // Make sure we don't record profile timeline markers anymore + SetRecordProfileTimelineMarkers(false); + + // Make sure to blow away our mLoadingURI just in case. No loads + // from inside this pagehide. + mLoadingURI = nullptr; + + // Fire unload event before we blow anything away. + (void)FirePageHideNotification(true); + + // Clear pointers to any detached nsEditorData that's lying + // around in shistory entries. Breaks cycle. See bug 430921. + if (mOSHE) { + mOSHE->SetEditorData(nullptr); + } + if (mLSHE) { + mLSHE->SetEditorData(nullptr); + } + + // Note: mContentListener can be null if Init() failed and we're being + // called from the destructor. + if (mContentListener) { + mContentListener->DropDocShellReference(); + mContentListener->SetParentContentListener(nullptr); + // Note that we do NOT set mContentListener to null here; that + // way if someone tries to do a load in us after this point + // the nsDSURIContentListener will block it. All of which + // means that we should do this before calling Stop(), of + // course. + } + + // Stop any URLs that are currently being loaded... + Stop(nsIWebNavigation::STOP_ALL); + + mEditorData = nullptr; + + // Save the state of the current document, before destroying the window. + // This is needed to capture the state of a frameset when the new document + // causes the frameset to be destroyed... + PersistLayoutHistoryState(); + + // Remove this docshell from its parent's child list + nsCOMPtr docShellParentAsItem = + do_QueryInterface(GetAsSupports(mParent)); + if (docShellParentAsItem) { + docShellParentAsItem->RemoveChild(this); + } + + if (mContentViewer) { + mContentViewer->Close(nullptr); + mContentViewer->Destroy(); + mContentViewer = nullptr; + } + + nsDocLoader::Destroy(); + + mParentWidget = nullptr; + mCurrentURI = nullptr; + + if (mScriptGlobal) { + mScriptGlobal->DetachFromDocShell(!mWillChangeProcess); + mScriptGlobal = nullptr; + } + + if (GetSessionHistory()) { + // We want to destroy these content viewers now rather than + // letting their destruction wait for the session history + // entries to get garbage collected. (Bug 488394) + GetSessionHistory()->EvictLocalContentViewers(); + } + + if (mWillChangeProcess) { + mBrowsingContext->PrepareForProcessChange(); + } + + SetTreeOwner(nullptr); + + mBrowserChild = nullptr; + + mChromeEventHandler = nullptr; + + // Cancel any timers that were set for this docshell; this is needed + // to break the cycle between us and the timers. + CancelRefreshURITimers(); + + if (UsePrivateBrowsing() && mAffectPrivateSessionLifetime) { + DecreasePrivateDocShellCount(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetUnscaledDevicePixelsPerCSSPixel(double* aScale) { + if (mParentWidget) { + *aScale = mParentWidget->GetDefaultScale().scale; + return NS_OK; + } + + nsCOMPtr ownerWindow(do_QueryInterface(mTreeOwner)); + if (ownerWindow) { + return ownerWindow->GetUnscaledDevicePixelsPerCSSPixel(aScale); + } + + *aScale = 1.0; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetDevicePixelsPerDesktopPixel(double* aScale) { + if (mParentWidget) { + *aScale = mParentWidget->GetDesktopToDeviceScale().scale; + return NS_OK; + } + + nsCOMPtr ownerWindow(do_QueryInterface(mTreeOwner)); + if (ownerWindow) { + return ownerWindow->GetDevicePixelsPerDesktopPixel(aScale); + } + + *aScale = 1.0; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetPosition(int32_t aX, int32_t aY) { + mBounds.MoveTo(aX, aY); + + if (mContentViewer) { + NS_ENSURE_SUCCESS(mContentViewer->Move(aX, aY), NS_ERROR_FAILURE); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetPositionDesktopPix(int32_t aX, int32_t aY) { + nsCOMPtr ownerWindow(do_QueryInterface(mTreeOwner)); + if (ownerWindow) { + return ownerWindow->SetPositionDesktopPix(aX, aY); + } + + double scale = 1.0; + GetDevicePixelsPerDesktopPixel(&scale); + return SetPosition(NSToIntRound(aX * scale), NSToIntRound(aY * scale)); +} + +NS_IMETHODIMP +nsDocShell::GetPosition(int32_t* aX, int32_t* aY) { + return GetPositionAndSize(aX, aY, nullptr, nullptr); +} + +NS_IMETHODIMP +nsDocShell::SetSize(int32_t aWidth, int32_t aHeight, bool aRepaint) { + int32_t x = 0, y = 0; + GetPosition(&x, &y); + return SetPositionAndSize(x, y, aWidth, aHeight, + aRepaint ? nsIBaseWindow::eRepaint : 0); +} + +NS_IMETHODIMP +nsDocShell::GetSize(int32_t* aWidth, int32_t* aHeight) { + return GetPositionAndSize(nullptr, nullptr, aWidth, aHeight); +} + +NS_IMETHODIMP +nsDocShell::SetPositionAndSize(int32_t aX, int32_t aY, int32_t aWidth, + int32_t aHeight, uint32_t aFlags) { + mBounds.SetRect(aX, aY, aWidth, aHeight); + + // Hold strong ref, since SetBounds can make us null out mContentViewer + nsCOMPtr viewer = mContentViewer; + if (viewer) { + uint32_t cvflags = (aFlags & nsIBaseWindow::eDelayResize) + ? nsIContentViewer::eDelayResize + : 0; + // XXX Border figured in here or is that handled elsewhere? + nsresult rv = viewer->SetBoundsWithFlags(mBounds, cvflags); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetPositionAndSize(int32_t* aX, int32_t* aY, int32_t* aWidth, + int32_t* aHeight) { + if (mParentWidget) { + // ensure size is up-to-date if window has changed resolution + LayoutDeviceIntRect r = mParentWidget->GetClientBounds(); + SetPositionAndSize(mBounds.X(), mBounds.Y(), r.Width(), r.Height(), 0); + } + + // We should really consider just getting this information from + // our window instead of duplicating the storage and code... + if (aWidth || aHeight) { + // Caller wants to know our size; make sure to give them up to + // date information. + RefPtr doc(do_GetInterface(GetAsSupports(mParent))); + if (doc) { + doc->FlushPendingNotifications(FlushType::Layout); + } + } + + DoGetPositionAndSize(aX, aY, aWidth, aHeight); + return NS_OK; +} + +void nsDocShell::DoGetPositionAndSize(int32_t* aX, int32_t* aY, int32_t* aWidth, + int32_t* aHeight) { + if (aX) { + *aX = mBounds.X(); + } + if (aY) { + *aY = mBounds.Y(); + } + if (aWidth) { + *aWidth = mBounds.Width(); + } + if (aHeight) { + *aHeight = mBounds.Height(); + } +} + +NS_IMETHODIMP +nsDocShell::Repaint(bool aForce) { + PresShell* presShell = GetPresShell(); + NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); + + RefPtr viewManager = presShell->GetViewManager(); + NS_ENSURE_TRUE(viewManager, NS_ERROR_FAILURE); + + viewManager->InvalidateAllViews(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetParentWidget(nsIWidget** aParentWidget) { + NS_ENSURE_ARG_POINTER(aParentWidget); + + *aParentWidget = mParentWidget; + NS_IF_ADDREF(*aParentWidget); + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetParentWidget(nsIWidget* aParentWidget) { + MOZ_ASSERT(!mIsBeingDestroyed); + mParentWidget = aParentWidget; + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetParentNativeWindow(nativeWindow* aParentNativeWindow) { + NS_ENSURE_ARG_POINTER(aParentNativeWindow); + + if (mParentWidget) { + *aParentNativeWindow = mParentWidget->GetNativeData(NS_NATIVE_WIDGET); + } else { + *aParentNativeWindow = nullptr; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetParentNativeWindow(nativeWindow aParentNativeWindow) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsDocShell::GetNativeHandle(nsAString& aNativeHandle) { + // the nativeHandle should be accessed from nsIAppWindow + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsDocShell::GetVisibility(bool* aVisibility) { + NS_ENSURE_ARG_POINTER(aVisibility); + + *aVisibility = false; + + if (!mContentViewer) { + return NS_OK; + } + + PresShell* presShell = GetPresShell(); + if (!presShell) { + return NS_OK; + } + + // get the view manager + nsViewManager* vm = presShell->GetViewManager(); + NS_ENSURE_TRUE(vm, NS_ERROR_FAILURE); + + // get the root view + nsView* view = vm->GetRootView(); // views are not ref counted + NS_ENSURE_TRUE(view, NS_ERROR_FAILURE); + + // if our root view is hidden, we are not visible + if (view->GetVisibility() == nsViewVisibility_kHide) { + return NS_OK; + } + + // otherwise, we must walk up the document and view trees checking + // for a hidden view, unless we're an off screen browser, which + // would make this test meaningless. + + RefPtr docShell = this; + RefPtr parentItem = docShell->GetInProcessParentDocshell(); + while (parentItem) { + // Null-check for crash in bug 267804 + if (!parentItem->GetPresShell()) { + MOZ_ASSERT_UNREACHABLE("parent docshell has null pres shell"); + return NS_OK; + } + + vm = docShell->GetPresShell()->GetViewManager(); + if (vm) { + view = vm->GetRootView(); + } + + if (view) { + view = view->GetParent(); // anonymous inner view + if (view) { + view = view->GetParent(); // subdocumentframe's view + } + } + + nsIFrame* frame = view ? view->GetFrame() : nullptr; + bool isDocShellOffScreen = false; + docShell->GetIsOffScreenBrowser(&isDocShellOffScreen); + if (frame && + !frame->IsVisibleConsideringAncestors( + nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY) && + !isDocShellOffScreen) { + return NS_OK; + } + + docShell = parentItem; + parentItem = docShell->GetInProcessParentDocshell(); + } + + nsCOMPtr treeOwnerAsWin(do_QueryInterface(mTreeOwner)); + if (!treeOwnerAsWin) { + *aVisibility = true; + return NS_OK; + } + + // Check with the tree owner as well to give embedders a chance to + // expose visibility as well. + return treeOwnerAsWin->GetVisibility(aVisibility); +} + +NS_IMETHODIMP +nsDocShell::SetIsOffScreenBrowser(bool aIsOffScreen) { + mIsOffScreenBrowser = aIsOffScreen; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetIsOffScreenBrowser(bool* aIsOffScreen) { + *aIsOffScreen = mIsOffScreenBrowser; + return NS_OK; +} + +void nsDocShell::ActivenessMaybeChanged() { + bool isActive = mBrowsingContext->IsActive(); + if (RefPtr presShell = GetPresShell()) { + presShell->SetIsActive(isActive); + } + + // Tell the window about it + if (mScriptGlobal) { + mScriptGlobal->SetIsBackground(!isActive); + if (RefPtr doc = mScriptGlobal->GetExtantDoc()) { + // Update orientation when the top-level browsing context becomes active. + if (isActive && mBrowsingContext->IsTop()) { + // We only care about the top-level browsing context. + uint16_t orientation = mBrowsingContext->GetOrientationLock(); + ScreenOrientation::UpdateActiveOrientationLock(orientation); + } + + doc->PostVisibilityUpdateEvent(); + } + } + + // Tell the nsDOMNavigationTiming about it + RefPtr timing = mTiming; + if (!timing && mContentViewer) { + if (Document* doc = mContentViewer->GetDocument()) { + timing = doc->GetNavigationTiming(); + } + } + if (timing) { + timing->NotifyDocShellStateChanged( + isActive ? nsDOMNavigationTiming::DocShellState::eActive + : nsDOMNavigationTiming::DocShellState::eInactive); + } + + // Restart or stop meta refresh timers if necessary + if (mDisableMetaRefreshWhenInactive) { + if (isActive) { + ResumeRefreshURIs(); + } else { + SuspendRefreshURIs(); + } + } + + if (InputTaskManager::CanSuspendInputEvent()) { + mBrowsingContext->Group()->UpdateInputTaskManagerIfNeeded(isActive); + } +} + +NS_IMETHODIMP +nsDocShell::SetIsAppTab(bool aIsAppTab) { + mIsAppTab = aIsAppTab; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetIsAppTab(bool* aIsAppTab) { + *aIsAppTab = mIsAppTab; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetDefaultLoadFlags(uint32_t aDefaultLoadFlags) { + if (!mWillChangeProcess) { + // Intentionally ignoring handling discarded browsing contexts. + Unused << mBrowsingContext->SetDefaultLoadFlags(aDefaultLoadFlags); + } else { + // Bug 1623565: DevTools tries to clean up defaultLoadFlags on + // shutdown. Sorry DevTools, your DocShell is in another process. + NS_WARNING("nsDocShell::SetDefaultLoadFlags called on Zombie DocShell"); + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetDefaultLoadFlags(uint32_t* aDefaultLoadFlags) { + *aDefaultLoadFlags = mBrowsingContext->GetDefaultLoadFlags(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetMixedContentChannel(nsIChannel* aMixedContentChannel) { +#ifdef DEBUG + // if the channel is non-null + if (aMixedContentChannel) { + NS_WARNING_ASSERTION(mBrowsingContext->IsTop(), + "Setting mMixedContentChannel on a docshell that is " + "not the root docshell"); + } +#endif + mMixedContentChannel = aMixedContentChannel; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetFailedChannel(nsIChannel** aFailedChannel) { + NS_ENSURE_ARG_POINTER(aFailedChannel); + Document* doc = GetDocument(); + if (!doc) { + *aFailedChannel = nullptr; + return NS_OK; + } + NS_IF_ADDREF(*aFailedChannel = doc->GetFailedChannel()); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetMixedContentChannel(nsIChannel** aMixedContentChannel) { + NS_ENSURE_ARG_POINTER(aMixedContentChannel); + NS_IF_ADDREF(*aMixedContentChannel = mMixedContentChannel); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetVisibility(bool aVisibility) { + // Show()/Hide() may change mContentViewer. + nsCOMPtr cv = mContentViewer; + if (!cv) { + return NS_OK; + } + if (aVisibility) { + cv->Show(); + } else { + cv->Hide(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetEnabled(bool* aEnabled) { + NS_ENSURE_ARG_POINTER(aEnabled); + *aEnabled = true; + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsDocShell::SetEnabled(bool aEnabled) { return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP +nsDocShell::SetFocus() { return NS_OK; } + +NS_IMETHODIMP +nsDocShell::GetMainWidget(nsIWidget** aMainWidget) { + // We don't create our own widget, so simply return the parent one. + return GetParentWidget(aMainWidget); +} + +NS_IMETHODIMP +nsDocShell::GetTitle(nsAString& aTitle) { + aTitle = mTitle; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetTitle(const nsAString& aTitle) { + // Avoid unnecessary updates of the title if the URI and the title haven't + // changed. + if (mTitleValidForCurrentURI && mTitle == aTitle) { + return NS_OK; + } + + // Store local title + mTitle = aTitle; + mTitleValidForCurrentURI = true; + + // When title is set on the top object it should then be passed to the + // tree owner. + if (mBrowsingContext->IsTop()) { + nsCOMPtr treeOwnerAsWin(do_QueryInterface(mTreeOwner)); + if (treeOwnerAsWin) { + treeOwnerAsWin->SetTitle(aTitle); + } + } + + if (mCurrentURI && mLoadType != LOAD_ERROR_PAGE) { + UpdateGlobalHistoryTitle(mCurrentURI); + } + + // Update SessionHistory with the document's title. + if (mLoadType != LOAD_BYPASS_HISTORY && mLoadType != LOAD_ERROR_PAGE) { + SetTitleOnHistoryEntry(); + } + + return NS_OK; +} + +void nsDocShell::SetTitleOnHistoryEntry() { + if (mOSHE) { + mOSHE->SetTitle(mTitle); + } + + if (mActiveEntry && mBrowsingContext) { + mActiveEntry->SetTitle(mTitle); + if (XRE_IsParentProcess()) { + SessionHistoryEntry* entry = + mBrowsingContext->Canonical()->GetActiveSessionHistoryEntry(); + if (entry) { + entry->SetTitle(mTitle); + } + } else { + mozilla::Unused + << ContentChild::GetSingleton()->SendSessionHistoryEntryTitle( + mBrowsingContext, mTitle); + } + } +} + +nsPoint nsDocShell::GetCurScrollPos() { + nsPoint scrollPos; + if (nsIScrollableFrame* sf = GetRootScrollFrame()) { + scrollPos = sf->GetVisualViewportOffset(); + } + return scrollPos; +} + +nsresult nsDocShell::SetCurScrollPosEx(int32_t aCurHorizontalPos, + int32_t aCurVerticalPos) { + nsIScrollableFrame* sf = GetRootScrollFrame(); + NS_ENSURE_TRUE(sf, NS_ERROR_FAILURE); + + ScrollMode scrollMode = + sf->IsSmoothScroll() ? ScrollMode::SmoothMsd : ScrollMode::Instant; + + nsPoint targetPos(aCurHorizontalPos, aCurVerticalPos); + sf->ScrollTo(targetPos, scrollMode); + + // Set the visual viewport offset as well. + + RefPtr presShell = GetPresShell(); + NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); + + nsPresContext* presContext = presShell->GetPresContext(); + NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE); + + // Only the root content document can have a distinct visual viewport offset. + if (!presContext->IsRootContentDocument()) { + return NS_OK; + } + + // Not on a platform with a distinct visual viewport - don't bother setting + // the visual viewport offset. + if (!presShell->IsVisualViewportSizeSet()) { + return NS_OK; + } + + presShell->ScrollToVisual(targetPos, layers::FrameMetrics::eMainThread, + scrollMode); + + return NS_OK; +} + +void nsDocShell::SetScrollbarPreference(mozilla::ScrollbarPreference aPref) { + if (mScrollbarPref == aPref) { + return; + } + mScrollbarPref = aPref; + auto* ps = GetPresShell(); + if (!ps) { + return; + } + nsIFrame* scrollFrame = ps->GetRootScrollFrame(); + if (!scrollFrame) { + return; + } + ps->FrameNeedsReflow(scrollFrame, IntrinsicDirty::StyleChange, + NS_FRAME_IS_DIRTY); +} + +//***************************************************************************** +// nsDocShell::nsIRefreshURI +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::RefreshURI(nsIURI* aURI, nsIPrincipal* aPrincipal, int32_t aDelay, + bool aRepeat, bool aMetaRefresh) { + MOZ_ASSERT(!mIsBeingDestroyed); + + NS_ENSURE_ARG(aURI); + + /* Check if Meta refresh/redirects are permitted. Some + * embedded applications may not want to do this. + * Must do this before sending out NOTIFY_REFRESH events + * because listeners may have side effects (e.g. displaying a + * button to manually trigger the refresh later). + */ + bool allowRedirects = true; + GetAllowMetaRedirects(&allowRedirects); + if (!allowRedirects) { + return NS_OK; + } + + // If any web progress listeners are listening for NOTIFY_REFRESH events, + // give them a chance to block this refresh. + bool sameURI; + nsresult rv = aURI->Equals(mCurrentURI, &sameURI); + if (NS_FAILED(rv)) { + sameURI = false; + } + if (!RefreshAttempted(this, aURI, aDelay, sameURI)) { + return NS_OK; + } + + nsCOMPtr refreshTimer = + new nsRefreshTimer(this, aURI, aPrincipal, aDelay, aRepeat, aMetaRefresh); + + BusyFlags busyFlags = GetBusyFlags(); + + if (!mRefreshURIList) { + mRefreshURIList = nsArray::Create(); + } + + if (busyFlags & BUSY_FLAGS_BUSY || + (!mBrowsingContext->IsActive() && mDisableMetaRefreshWhenInactive)) { + // We don't want to create the timer right now. Instead queue up the + // request and trigger the timer in EndPageLoad() or whenever we become + // active. + mRefreshURIList->AppendElement(refreshTimer); + } else { + // There is no page loading going on right now. Create the + // timer and fire it right away. + nsCOMPtr win = GetWindow(); + NS_ENSURE_TRUE(win, NS_ERROR_FAILURE); + + nsCOMPtr timer; + MOZ_TRY_VAR(timer, NS_NewTimerWithCallback(refreshTimer, aDelay, + nsITimer::TYPE_ONE_SHOT)); + + mRefreshURIList->AppendElement(timer); // owning timer ref + } + return NS_OK; +} + +nsresult nsDocShell::ForceRefreshURIFromTimer(nsIURI* aURI, + nsIPrincipal* aPrincipal, + int32_t aDelay, bool aMetaRefresh, + nsITimer* aTimer) { + MOZ_ASSERT(aTimer, "Must have a timer here"); + + // Remove aTimer from mRefreshURIList if needed + if (mRefreshURIList) { + uint32_t n = 0; + mRefreshURIList->GetLength(&n); + + for (uint32_t i = 0; i < n; ++i) { + nsCOMPtr timer = do_QueryElementAt(mRefreshURIList, i); + if (timer == aTimer) { + mRefreshURIList->RemoveElementAt(i); + break; + } + } + } + + return ForceRefreshURI(aURI, aPrincipal, aDelay, aMetaRefresh); +} + +NS_IMETHODIMP +nsDocShell::ForceRefreshURI(nsIURI* aURI, nsIPrincipal* aPrincipal, + int32_t aDelay, bool aMetaRefresh) { + NS_ENSURE_ARG(aURI); + + RefPtr loadState = new nsDocShellLoadState(aURI); + loadState->SetOriginalURI(mCurrentURI); + loadState->SetResultPrincipalURI(aURI); + loadState->SetResultPrincipalURIIsSome(true); + loadState->SetKeepResultPrincipalURIIfSet(true); + + // Set the triggering pricipal to aPrincipal if available, or current + // document's principal otherwise. + nsCOMPtr principal = aPrincipal; + RefPtr doc = GetDocument(); + if (!principal) { + if (!doc) { + return NS_ERROR_FAILURE; + } + principal = doc->NodePrincipal(); + } + loadState->SetTriggeringPrincipal(principal); + if (doc) { + loadState->SetCsp(doc->GetCsp()); + loadState->SetHasValidUserGestureActivation( + doc->HasValidTransientUserGestureActivation()); + loadState->SetTriggeringSandboxFlags(doc->GetSandboxFlags()); + } + + loadState->SetPrincipalIsExplicit(true); + + /* Check if this META refresh causes a redirection + * to another site. + */ + bool equalUri = false; + nsresult rv = aURI->Equals(mCurrentURI, &equalUri); + + nsCOMPtr referrerInfo; + if (NS_SUCCEEDED(rv) && (!equalUri) && aMetaRefresh && + aDelay <= REFRESH_REDIRECT_TIMER) { + /* It is a META refresh based redirection within the threshold time + * we have in mind (15000 ms as defined by REFRESH_REDIRECT_TIMER). + * Pass a REPLACE flag to LoadURI(). + */ + loadState->SetLoadType(LOAD_NORMAL_REPLACE); + + /* For redirects we mimic HTTP, which passes the + * original referrer. + * We will pass in referrer but will not send to server + */ + if (mReferrerInfo) { + referrerInfo = static_cast(mReferrerInfo.get()) + ->CloneWithNewSendReferrer(false); + } + } else { + loadState->SetLoadType(LOAD_REFRESH); + /* We do need to pass in a referrer, but we don't want it to + * be sent to the server. + * For most refreshes the current URI is an appropriate + * internal referrer. + */ + referrerInfo = new ReferrerInfo(mCurrentURI, ReferrerPolicy::_empty, false); + } + + loadState->SetReferrerInfo(referrerInfo); + loadState->SetLoadFlags( + nsIWebNavigation::LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL); + loadState->SetFirstParty(true); + + /* + * LoadURI(...) will cancel all refresh timers... This causes the + * Timer and its refreshData instance to be released... + */ + LoadURI(loadState, false); + + return NS_OK; +} + +nsresult nsDocShell::SetupRefreshURIFromHeader(nsIURI* aBaseURI, + nsIPrincipal* aPrincipal, + uint64_t aInnerWindowID, + const nsACString& aHeader) { + if (mIsBeingDestroyed) { + return NS_ERROR_FAILURE; + } + + // Refresh headers are parsed with the following format in mind + // + // By the time we are here, the following is true: + // header = "REFRESH" + // content = "5; URL=http://uri" // note the URL attribute is + // optional, if it is absent, the currently loaded url is used. + // Also note that the seconds and URL separator can be either + // a ';' or a ','. The ',' separator should be illegal but CNN + // is using it. + // + // We need to handle the following strings, where + // - X is a set of digits + // - URI is either a relative or absolute URI + // + // Note that URI should start with "url=" but we allow omission + // + // "" || ";" || "," + // empty string. use the currently loaded URI + // and refresh immediately. + // "X" || "X;" || "X," + // Refresh the currently loaded URI in X seconds. + // "X; URI" || "X, URI" + // Refresh using URI as the destination in X seconds. + // "URI" || "; URI" || ", URI" + // Refresh immediately using URI as the destination. + // + // Currently, anything immediately following the URI, if + // separated by any char in the set "'\"\t\r\n " will be + // ignored. So "10; url=go.html ; foo=bar" will work, + // and so will "10; url='go.html'; foo=bar". However, + // "10; url=go.html; foo=bar" will result in the uri + // "go.html;" since ';' and ',' are valid uri characters. + // + // Note that we need to remove any tokens wrapping the URI. + // These tokens currently include spaces, double and single + // quotes. + + // when done, seconds is 0 or the given number of seconds + // uriAttrib is empty or the URI specified + MOZ_ASSERT(aPrincipal); + + nsAutoCString uriAttrib; + int32_t seconds = 0; + bool specifiesSeconds = false; + + nsACString::const_iterator iter, tokenStart, doneIterating; + + aHeader.BeginReading(iter); + aHeader.EndReading(doneIterating); + + // skip leading whitespace + while (iter != doneIterating && nsCRT::IsAsciiSpace(*iter)) { + ++iter; + } + + tokenStart = iter; + + // skip leading + and - + if (iter != doneIterating && (*iter == '-' || *iter == '+')) { + ++iter; + } + + // parse number + while (iter != doneIterating && (*iter >= '0' && *iter <= '9')) { + seconds = seconds * 10 + (*iter - '0'); + specifiesSeconds = true; + ++iter; + } + + if (iter != doneIterating) { + // if we started with a '-', number is negative + if (*tokenStart == '-') { + seconds = -seconds; + } + + // skip to next ';' or ',' + nsACString::const_iterator iterAfterDigit = iter; + while (iter != doneIterating && !(*iter == ';' || *iter == ',')) { + if (specifiesSeconds) { + // Non-whitespace characters here mean that the string is + // malformed but tolerate sites that specify a decimal point, + // even though meta refresh only works on whole seconds. + if (iter == iterAfterDigit && !nsCRT::IsAsciiSpace(*iter) && + *iter != '.') { + // The characters between the seconds and the next + // section are just garbage! + // e.g. content="2a0z+,URL=http://www.mozilla.org/" + // Just ignore this redirect. + return NS_ERROR_FAILURE; + } else if (nsCRT::IsAsciiSpace(*iter)) { + // We've had at least one whitespace so tolerate the mistake + // and drop through. + // e.g. content="10 foo" + ++iter; + break; + } + } + ++iter; + } + + // skip any remaining whitespace + while (iter != doneIterating && nsCRT::IsAsciiSpace(*iter)) { + ++iter; + } + + // skip ';' or ',' + if (iter != doneIterating && (*iter == ';' || *iter == ',')) { + ++iter; + } + + // skip whitespace + while (iter != doneIterating && nsCRT::IsAsciiSpace(*iter)) { + ++iter; + } + } + + // possible start of URI + tokenStart = iter; + + // skip "url = " to real start of URI + if (iter != doneIterating && (*iter == 'u' || *iter == 'U')) { + ++iter; + if (iter != doneIterating && (*iter == 'r' || *iter == 'R')) { + ++iter; + if (iter != doneIterating && (*iter == 'l' || *iter == 'L')) { + ++iter; + + // skip whitespace + while (iter != doneIterating && nsCRT::IsAsciiSpace(*iter)) { + ++iter; + } + + if (iter != doneIterating && *iter == '=') { + ++iter; + + // skip whitespace + while (iter != doneIterating && nsCRT::IsAsciiSpace(*iter)) { + ++iter; + } + + // found real start of URI + tokenStart = iter; + } + } + } + } + + // skip a leading '"' or '\''. + + bool isQuotedURI = false; + if (tokenStart != doneIterating && + (*tokenStart == '"' || *tokenStart == '\'')) { + isQuotedURI = true; + ++tokenStart; + } + + // set iter to start of URI + iter = tokenStart; + + // tokenStart here points to the beginning of URI + + // grab the rest of the URI + while (iter != doneIterating) { + if (isQuotedURI && (*iter == '"' || *iter == '\'')) { + break; + } + ++iter; + } + + // move iter one back if the last character is a '"' or '\'' + if (iter != tokenStart && isQuotedURI) { + --iter; + if (!(*iter == '"' || *iter == '\'')) { + ++iter; + } + } + + // URI is whatever's contained from tokenStart to iter. + // note: if tokenStart == doneIterating, so is iter. + + nsresult rv = NS_OK; + + nsCOMPtr uri; + bool specifiesURI = false; + if (tokenStart == iter) { + uri = aBaseURI; + } else { + uriAttrib = Substring(tokenStart, iter); + // NS_NewURI takes care of any whitespace surrounding the URL + rv = NS_NewURI(getter_AddRefs(uri), uriAttrib, nullptr, aBaseURI); + specifiesURI = true; + } + + // No URI or seconds were specified + if (!specifiesSeconds && !specifiesURI) { + // Do nothing because the alternative is to spin around in a refresh + // loop forever! + return NS_ERROR_FAILURE; + } + + if (NS_SUCCEEDED(rv)) { + nsCOMPtr securityManager( + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + rv = securityManager->CheckLoadURIWithPrincipal( + aPrincipal, uri, + nsIScriptSecurityManager::LOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENT, + aInnerWindowID); + + if (NS_SUCCEEDED(rv)) { + bool isjs = true; + rv = NS_URIChainHasFlags( + uri, nsIProtocolHandler::URI_OPENING_EXECUTES_SCRIPT, &isjs); + NS_ENSURE_SUCCESS(rv, rv); + + if (isjs) { + return NS_ERROR_FAILURE; + } + } + + if (NS_SUCCEEDED(rv)) { + // Since we can't travel back in time yet, just pretend + // negative numbers do nothing at all. + if (seconds < 0) { + return NS_ERROR_FAILURE; + } + + rv = RefreshURI(uri, aPrincipal, seconds * 1000, false, true); + } + } + } + return rv; +} + +NS_IMETHODIMP +nsDocShell::SetupRefreshURI(nsIChannel* aChannel) { + nsresult rv; + nsCOMPtr httpChannel(do_QueryInterface(aChannel, &rv)); + if (NS_SUCCEEDED(rv)) { + nsAutoCString refreshHeader; + rv = httpChannel->GetResponseHeader("refresh"_ns, refreshHeader); + + if (!refreshHeader.IsEmpty()) { + nsCOMPtr secMan = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr principal; + rv = secMan->GetChannelResultPrincipal(aChannel, + getter_AddRefs(principal)); + NS_ENSURE_SUCCESS(rv, rv); + + SetupReferrerInfoFromChannel(aChannel); + // We have no idea what window id to use for error reporting + // here, so just pass 0. + rv = SetupRefreshURIFromHeader(mCurrentURI, principal, 0, refreshHeader); + if (NS_SUCCEEDED(rv)) { + return NS_REFRESHURI_HEADER_FOUND; + } + } + } + return rv; +} + +static void DoCancelRefreshURITimers(nsIMutableArray* aTimerList) { + if (!aTimerList) { + return; + } + + uint32_t n = 0; + aTimerList->GetLength(&n); + + while (n) { + nsCOMPtr timer(do_QueryElementAt(aTimerList, --n)); + + aTimerList->RemoveElementAt(n); // bye bye owning timer ref + + if (timer) { + timer->Cancel(); + } + } +} + +NS_IMETHODIMP +nsDocShell::CancelRefreshURITimers() { + DoCancelRefreshURITimers(mRefreshURIList); + DoCancelRefreshURITimers(mSavedRefreshURIList); + mRefreshURIList = nullptr; + mSavedRefreshURIList = nullptr; + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetRefreshPending(bool* aResult) { + if (!mRefreshURIList) { + *aResult = false; + return NS_OK; + } + + uint32_t count; + nsresult rv = mRefreshURIList->GetLength(&count); + if (NS_SUCCEEDED(rv)) { + *aResult = (count != 0); + } + return rv; +} + +NS_IMETHODIMP +nsDocShell::SuspendRefreshURIs() { + if (mRefreshURIList) { + uint32_t n = 0; + mRefreshURIList->GetLength(&n); + + for (uint32_t i = 0; i < n; ++i) { + nsCOMPtr timer = do_QueryElementAt(mRefreshURIList, i); + if (!timer) { + continue; // this must be a nsRefreshURI already + } + + // Replace this timer object with a nsRefreshTimer object. + nsCOMPtr callback; + timer->GetCallback(getter_AddRefs(callback)); + + timer->Cancel(); + + mRefreshURIList->ReplaceElementAt(callback, i); + } + } + + // Suspend refresh URIs for our child shells as well. + for (auto* child : mChildList.ForwardRange()) { + nsCOMPtr shell = do_QueryObject(child); + if (shell) { + shell->SuspendRefreshURIs(); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::ResumeRefreshURIs() { + RefreshURIFromQueue(); + + // Resume refresh URIs for our child shells as well. + for (auto* child : mChildList.ForwardRange()) { + nsCOMPtr shell = do_QueryObject(child); + if (shell) { + shell->ResumeRefreshURIs(); + } + } + + return NS_OK; +} + +nsresult nsDocShell::RefreshURIFromQueue() { + if (!mRefreshURIList) { + return NS_OK; + } + uint32_t n = 0; + mRefreshURIList->GetLength(&n); + + while (n) { + nsCOMPtr refreshInfo = + do_QueryElementAt(mRefreshURIList, --n); + + if (refreshInfo) { + // This is the nsRefreshTimer object, waiting to be + // setup in a timer object and fired. + // Create the timer and trigger it. + uint32_t delay = static_cast( + static_cast(refreshInfo)) + ->GetDelay(); + nsCOMPtr win = GetWindow(); + if (win) { + nsCOMPtr timer; + NS_NewTimerWithCallback(getter_AddRefs(timer), refreshInfo, delay, + nsITimer::TYPE_ONE_SHOT); + + if (timer) { + // Replace the nsRefreshTimer element in the queue with + // its corresponding timer object, so that in case another + // load comes through before the timer can go off, the timer will + // get cancelled in CancelRefreshURITimer() + mRefreshURIList->ReplaceElementAt(timer, n); + } + } + } + } + + return NS_OK; +} + +nsresult nsDocShell::Embed(nsIContentViewer* aContentViewer, + WindowGlobalChild* aWindowActor, + bool aIsTransientAboutBlank, bool aPersist) { + // Save the LayoutHistoryState of the previous document, before + // setting up new document + PersistLayoutHistoryState(); + + nsresult rv = SetupNewViewer(aContentViewer, aWindowActor); + NS_ENSURE_SUCCESS(rv, rv); + + // XXX What if SetupNewViewer fails? + if (mozilla::SessionHistoryInParent() ? !!mLoadingEntry : !!mLSHE) { + // Set history.state + SetDocCurrentStateObj(mLSHE, + mLoadingEntry ? &mLoadingEntry->mInfo : nullptr); + } + + if (mLSHE) { + // Restore the editing state, if it's stored in session history. + if (mLSHE->HasDetachedEditor()) { + ReattachEditorToWindow(mLSHE); + } + + SetHistoryEntryAndUpdateBC(Nothing(), Some(mLSHE)); + } + + if (!aIsTransientAboutBlank && mozilla::SessionHistoryInParent()) { + MOZ_LOG(gSHLog, LogLevel::Debug, ("document %p Embed", this)); + MoveLoadingToActiveEntry(aPersist); + } + + bool updateHistory = true; + + // Determine if this type of load should update history + switch (mLoadType) { + case LOAD_NORMAL_REPLACE: + case LOAD_STOP_CONTENT_AND_REPLACE: + case LOAD_RELOAD_BYPASS_CACHE: + case LOAD_RELOAD_BYPASS_PROXY: + case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE: + case LOAD_REPLACE_BYPASS_CACHE: + updateHistory = false; + break; + default: + break; + } + + if (!updateHistory) { + SetLayoutHistoryState(nullptr); + } + + return NS_OK; +} + +//***************************************************************************** +// nsDocShell::nsIWebProgressListener +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::OnProgressChange(nsIWebProgress* aProgress, nsIRequest* aRequest, + int32_t aCurSelfProgress, int32_t aMaxSelfProgress, + int32_t aCurTotalProgress, + int32_t aMaxTotalProgress) { + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::OnStateChange(nsIWebProgress* aProgress, nsIRequest* aRequest, + uint32_t aStateFlags, nsresult aStatus) { + if ((~aStateFlags & (STATE_START | STATE_IS_NETWORK)) == 0) { + // Save timing statistics. + nsCOMPtr channel(do_QueryInterface(aRequest)); + nsCOMPtr uri; + channel->GetURI(getter_AddRefs(uri)); + nsAutoCString aURI; + uri->GetAsciiSpec(aURI); + + if (this == aProgress) { + mozilla::Unused << MaybeInitTiming(); + mTiming->NotifyFetchStart(uri, + ConvertLoadTypeToNavigationType(mLoadType)); + // If we are starting a DocumentChannel, we need to pass the timing + // statistics so that should a process switch occur, the starting type can + // be passed to the new DocShell running in the other content process. + if (RefPtr docChannel = do_QueryObject(aRequest)) { + docChannel->SetNavigationTiming(mTiming); + } + } + + // Page has begun to load + mBusyFlags = (BusyFlags)(BUSY_FLAGS_BUSY | BUSY_FLAGS_BEFORE_PAGE_LOAD); + + if ((aStateFlags & STATE_RESTORING) == 0) { + // Show the progress cursor if the pref is set + if (StaticPrefs::ui_use_activity_cursor()) { + nsCOMPtr mainWidget; + GetMainWidget(getter_AddRefs(mainWidget)); + if (mainWidget) { + mainWidget->SetCursor(eCursor_spinning, nullptr, 0, 0); + } + } + } + } else if ((~aStateFlags & (STATE_TRANSFERRING | STATE_IS_DOCUMENT)) == 0) { + // Page is loading + mBusyFlags = (BusyFlags)(BUSY_FLAGS_BUSY | BUSY_FLAGS_PAGE_LOADING); + } else if ((aStateFlags & STATE_STOP) && (aStateFlags & STATE_IS_NETWORK)) { + // Page has finished loading + mBusyFlags = BUSY_FLAGS_NONE; + + // Hide the progress cursor if the pref is set + if (StaticPrefs::ui_use_activity_cursor()) { + nsCOMPtr mainWidget; + GetMainWidget(getter_AddRefs(mainWidget)); + if (mainWidget) { + mainWidget->SetCursor(eCursor_standard, nullptr, 0, 0); + } + } + } + if ((~aStateFlags & (STATE_IS_DOCUMENT | STATE_STOP)) == 0) { + nsCOMPtr webProgress = + do_QueryInterface(GetAsSupports(this)); + // Is the document stop notification for this document? + if (aProgress == webProgress.get()) { + nsCOMPtr channel(do_QueryInterface(aRequest)); + EndPageLoad(aProgress, channel, aStatus); + } + } + // note that redirect state changes will go through here as well, but it + // is better to handle those in OnRedirectStateChange where more + // information is available. + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::OnLocationChange(nsIWebProgress* aProgress, nsIRequest* aRequest, + nsIURI* aURI, uint32_t aFlags) { + if (XRE_IsParentProcess()) { + // Since we've now changed Documents, notify the BrowsingContext that we've + // changed. Ideally we'd just let the BrowsingContext do this when it + // changes the current window global, but that happens before this and we + // have a lot of tests that depend on the specific ordering of messages. + if (!(aFlags & nsIWebProgressListener::LOCATION_CHANGE_SAME_DOCUMENT)) { + GetBrowsingContext()->Canonical()->UpdateSecurityState(); + } + } + return NS_OK; +} + +void nsDocShell::OnRedirectStateChange(nsIChannel* aOldChannel, + nsIChannel* aNewChannel, + uint32_t aRedirectFlags, + uint32_t aStateFlags) { + NS_ASSERTION(aStateFlags & STATE_REDIRECTING, + "Calling OnRedirectStateChange when there is no redirect"); + + // If mixed content is allowed for the old channel, we forward + // the permission to the new channel if it has the same origin + // as the old one. + if (mMixedContentChannel && mMixedContentChannel == aOldChannel) { + nsresult rv = + nsContentUtils::CheckSameOrigin(mMixedContentChannel, aNewChannel); + if (NS_SUCCEEDED(rv)) { + SetMixedContentChannel(aNewChannel); // Same origin: forward permission. + } else { + SetMixedContentChannel( + nullptr); // Different origin: clear mMixedContentChannel. + } + } + + if (!(aStateFlags & STATE_IS_DOCUMENT)) { + return; // not a toplevel document + } + + nsCOMPtr oldURI, newURI; + aOldChannel->GetURI(getter_AddRefs(oldURI)); + aNewChannel->GetURI(getter_AddRefs(newURI)); + if (!oldURI || !newURI) { + return; + } + + // DocumentChannel adds redirect chain to global history in the parent + // process. The redirect chain can't be queried from the content process, so + // there's no need to update global history here. + RefPtr docChannel = do_QueryObject(aOldChannel); + if (!docChannel) { + // Below a URI visit is saved (see AddURIVisit method doc). + // The visit chain looks something like: + // ... + // Site N - 1 + // => Site N + // (redirect to =>) Site N + 1 (we are here!) + + // Get N - 1 and transition type + nsCOMPtr previousURI; + uint32_t previousFlags = 0; + ExtractLastVisit(aOldChannel, getter_AddRefs(previousURI), &previousFlags); + + if (aRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL || + net::ChannelIsPost(aOldChannel)) { + // 1. Internal redirects are ignored because they are specific to the + // channel implementation. + // 2. POSTs are not saved by global history. + // + // Regardless, we need to propagate the previous visit to the new + // channel. + SaveLastVisit(aNewChannel, previousURI, previousFlags); + } else { + // Get the HTTP response code, if available. + uint32_t responseStatus = 0; + nsCOMPtr httpChannel = do_QueryInterface(aOldChannel); + if (httpChannel) { + Unused << httpChannel->GetResponseStatus(&responseStatus); + } + + // Add visit N -1 => N + AddURIVisit(oldURI, previousURI, previousFlags, responseStatus); + + // Since N + 1 could be the final destination, we will not save N => N + 1 + // here. OnNewURI will do that, so we will cache it. + SaveLastVisit(aNewChannel, oldURI, aRedirectFlags); + } + } + + // check if the new load should go through the application cache. + nsCOMPtr appCacheChannel = + do_QueryInterface(aNewChannel); + if (appCacheChannel && !docChannel) { + if (GeckoProcessType_Default != XRE_GetProcessType()) { + // Permission will be checked in the parent process. + appCacheChannel->SetChooseApplicationCache(true); + } else { + nsCOMPtr secMan = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID); + + if (secMan) { + nsCOMPtr principal; + secMan->GetDocShellContentPrincipal(newURI, this, + getter_AddRefs(principal)); + appCacheChannel->SetChooseApplicationCache( + NS_ShouldCheckAppCache(principal)); + } + } + } + + if (!(aRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) && + mLoadType & (LOAD_CMD_RELOAD | LOAD_CMD_HISTORY)) { + mLoadType = LOAD_NORMAL_REPLACE; + SetHistoryEntryAndUpdateBC(Some(nullptr), Nothing()); + } +} + +NS_IMETHODIMP +nsDocShell::OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, + nsresult aStatus, const char16_t* aMessage) { + MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::OnSecurityChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, + uint32_t aState) { + MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::OnContentBlockingEvent(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, uint32_t aEvent) { + MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + +already_AddRefed nsDocShell::KeywordToURI( + const nsACString& aKeyword, bool aIsPrivateContext) { + nsCOMPtr info; + if (!XRE_IsContentProcess()) { + nsCOMPtr uriFixup = components::URIFixup::Service(); + if (uriFixup) { + uriFixup->KeywordToURI(aKeyword, aIsPrivateContext, getter_AddRefs(info)); + } + } + return info.forget(); +} + +/* static */ +already_AddRefed nsDocShell::MaybeFixBadCertDomainErrorURI( + nsIChannel* aChannel, nsIURI* aUrl) { + if (!aChannel) { + return nullptr; + } + + nsresult rv = NS_OK; + nsAutoCString host; + rv = aUrl->GetAsciiHost(host); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + // No point in going further if "www." is included in the hostname + // already. That is the only hueristic we're applying in this function. + if (StringBeginsWith(host, "www."_ns)) { + return nullptr; + } + + // Return if fixup enable pref is turned off. + if (!mozilla::StaticPrefs::security_bad_cert_domain_error_url_fix_enabled()) { + return nullptr; + } + + // Return if scheme is not HTTPS. + if (!SchemeIsHTTPS(aUrl)) { + return nullptr; + } + + nsCOMPtr info = aChannel->LoadInfo(); + if (!info) { + return nullptr; + } + + // Skip doing the fixup if our channel was redirected, because we + // shouldn't be guessing things about the post-redirect URI. + if (!info->RedirectChain().IsEmpty()) { + return nullptr; + } + + int32_t port = 0; + rv = aUrl->GetPort(&port); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + // Don't fix up hosts with ports. + if (port != -1) { + return nullptr; + } + + // Don't fix up localhost url. + if (host == "localhost") { + return nullptr; + } + + // Don't fix up hostnames with IP address. + if (net_IsValidIPv4Addr(host) || net_IsValidIPv6Addr(host)) { + return nullptr; + } + + nsAutoCString userPass; + rv = aUrl->GetUserPass(userPass); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + // Security - URLs with user / password info should NOT be modified. + if (!userPass.IsEmpty()) { + return nullptr; + } + + nsCOMPtr securityInfo; + rv = aChannel->GetSecurityInfo(getter_AddRefs(securityInfo)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + nsCOMPtr tsi = do_QueryInterface(securityInfo); + if (NS_WARN_IF(!tsi)) { + return nullptr; + } + + nsCOMPtr cert; + rv = tsi->GetServerCert(getter_AddRefs(cert)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + nsTArray certBytes; + rv = cert->GetRawDER(certBytes); + if (NS_FAILED(rv)) { + return nullptr; + } + + mozilla::pkix::Input serverCertInput; + mozilla::pkix::Result rv1 = + serverCertInput.Init(certBytes.Elements(), certBytes.Length()); + if (rv1 != mozilla::pkix::Success) { + return nullptr; + } + + nsAutoCString newHost("www."_ns); + newHost.Append(host); + + mozilla::pkix::Input newHostInput; + rv1 = newHostInput.Init( + BitwiseCast(newHost.BeginReading()), + newHost.Length()); + if (rv1 != mozilla::pkix::Success) { + return nullptr; + } + + // Check if adding a "www." prefix to the request's hostname will + // cause the response's certificate to match. + mozilla::psm::BRNameMatchingPolicy nameMatchingPolicy( + mozilla::psm::BRNameMatchingPolicy::Mode::Enforce); + rv1 = mozilla::pkix::CheckCertHostname(serverCertInput, newHostInput, + nameMatchingPolicy); + if (rv1 != mozilla::pkix::Success) { + return nullptr; + } + + nsCOMPtr newURI; + Unused << NS_MutateURI(aUrl).SetHost(newHost).Finalize( + getter_AddRefs(newURI)); + + return newURI.forget(); +} + +/* static */ +already_AddRefed nsDocShell::AttemptURIFixup( + nsIChannel* aChannel, nsresult aStatus, + const mozilla::Maybe& aOriginalURIString, uint32_t aLoadType, + bool aIsTopFrame, bool aAllowKeywordFixup, bool aUsePrivateBrowsing, + bool aNotifyKeywordSearchLoading, nsIInputStream** aNewPostData) { + if (aStatus != NS_ERROR_UNKNOWN_HOST && aStatus != NS_ERROR_NET_RESET && + aStatus != NS_ERROR_CONNECTION_REFUSED && + aStatus != + mozilla::psm::GetXPCOMFromNSSError(SSL_ERROR_BAD_CERT_DOMAIN)) { + return nullptr; + } + + if (!(aLoadType == LOAD_NORMAL && aIsTopFrame) && !aAllowKeywordFixup) { + return nullptr; + } + + nsCOMPtr url; + nsresult rv = aChannel->GetURI(getter_AddRefs(url)); + if (NS_FAILED(rv)) { + return nullptr; + } + + // + // Try and make an alternative URI from the old one + // + nsCOMPtr newURI; + nsCOMPtr newPostData; + + nsAutoCString oldSpec; + url->GetSpec(oldSpec); + + // + // First try keyword fixup + // + nsAutoString keywordProviderName, keywordAsSent; + if (aStatus == NS_ERROR_UNKNOWN_HOST && aAllowKeywordFixup) { + // we should only perform a keyword search under the following + // conditions: + // (0) Pref keyword.enabled is true + // (1) the url scheme is http (or https) + // (2) the url does not have a protocol scheme + // If we don't enforce such a policy, then we end up doing + // keyword searchs on urls we don't intend like imap, file, + // mailbox, etc. This could lead to a security problem where we + // send data to the keyword server that we shouldn't be. + // Someone needs to clean up keywords in general so we can + // determine on a per url basis if we want keywords + // enabled...this is just a bandaid... + nsAutoCString scheme; + Unused << url->GetScheme(scheme); + if (Preferences::GetBool("keyword.enabled", false) && + StringBeginsWith(scheme, "http"_ns)) { + bool attemptFixup = false; + nsAutoCString host; + Unused << url->GetHost(host); + if (host.FindChar('.') == kNotFound) { + attemptFixup = true; + } else { + // For domains with dots, we check the public suffix validity. + nsCOMPtr tldService = + do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID); + if (tldService) { + nsAutoCString suffix; + attemptFixup = + NS_SUCCEEDED(tldService->GetKnownPublicSuffix(url, suffix)) && + suffix.IsEmpty(); + } + } + if (attemptFixup) { + nsCOMPtr info; + // only send non-qualified hosts to the keyword server + if (aOriginalURIString && !aOriginalURIString->IsEmpty()) { + info = KeywordToURI(*aOriginalURIString, aUsePrivateBrowsing); + } else { + // + // If this string was passed through nsStandardURL by + // chance, then it may have been converted from UTF-8 to + // ACE, which would result in a completely bogus keyword + // query. Here we try to recover the original Unicode + // value, but this is not 100% correct since the value may + // have been normalized per the IDN normalization rules. + // + // Since we don't have access to the exact original string + // that was entered by the user, this will just have to do. + bool isACE; + nsAutoCString utf8Host; + nsCOMPtr idnSrv = + do_GetService(NS_IDNSERVICE_CONTRACTID); + if (idnSrv && NS_SUCCEEDED(idnSrv->IsACE(host, &isACE)) && isACE && + NS_SUCCEEDED(idnSrv->ConvertACEtoUTF8(host, utf8Host))) { + info = KeywordToURI(utf8Host, aUsePrivateBrowsing); + + } else { + info = KeywordToURI(host, aUsePrivateBrowsing); + } + } + if (info) { + info->GetPreferredURI(getter_AddRefs(newURI)); + if (newURI) { + info->GetKeywordAsSent(keywordAsSent); + info->GetKeywordProviderName(keywordProviderName); + info->GetPostData(getter_AddRefs(newPostData)); + } + } + } + } + } + + // + // Now try change the address, e.g. turn http://foo into + // http://www.foo.com, and if that doesn't work try https with + // https://foo and https://www.foo.com. + // + if (aStatus == NS_ERROR_UNKNOWN_HOST || aStatus == NS_ERROR_NET_RESET) { + bool doCreateAlternate = true; + + // Skip fixup for anything except a normal document load + // operation on the topframe. + + if (aLoadType != LOAD_NORMAL || !aIsTopFrame) { + doCreateAlternate = false; + } else { + // Test if keyword lookup produced a new URI or not + if (newURI) { + bool sameURI = false; + url->Equals(newURI, &sameURI); + if (!sameURI) { + // Keyword lookup made a new URI so no need to try + // an alternate one. + doCreateAlternate = false; + } + } + + if (doCreateAlternate) { + nsCOMPtr info = aChannel->LoadInfo(); + // Skip doing this if our channel was redirected, because we + // shouldn't be guessing things about the post-redirect URI. + if (!info->RedirectChain().IsEmpty()) { + doCreateAlternate = false; + } + } + } + if (doCreateAlternate) { + newURI = nullptr; + newPostData = nullptr; + keywordProviderName.Truncate(); + keywordAsSent.Truncate(); + nsCOMPtr uriFixup = components::URIFixup::Service(); + if (uriFixup) { + nsCOMPtr fixupInfo; + uriFixup->GetFixupURIInfo(oldSpec, + nsIURIFixup::FIXUP_FLAGS_MAKE_ALTERNATE_URI, + getter_AddRefs(fixupInfo)); + if (fixupInfo) { + fixupInfo->GetPreferredURI(getter_AddRefs(newURI)); + } + } + } + } else if (aStatus == NS_ERROR_CONNECTION_REFUSED && + Preferences::GetBool("browser.fixup.fallback-to-https", false)) { + // Try HTTPS, since http didn't work + if (SchemeIsHTTP(url)) { + int32_t port = 0; + url->GetPort(&port); + + // Fall back to HTTPS only if port is default + if (port == -1) { + newURI = nullptr; + newPostData = nullptr; + Unused << NS_MutateURI(url) + .SetScheme("https"_ns) + .Finalize(getter_AddRefs(newURI)); + } + } + } + + // If we have a SSL_ERROR_BAD_CERT_DOMAIN error, try prefixing the domain name + // with www. to see if we can avoid showing the cert error page. For example, + // https://example.com -> https://www.example.com. + if (aStatus == + mozilla::psm::GetXPCOMFromNSSError(SSL_ERROR_BAD_CERT_DOMAIN)) { + newPostData = nullptr; + newURI = MaybeFixBadCertDomainErrorURI(aChannel, url); + } + + // Did we make a new URI that is different to the old one? If so + // load it. + // + if (newURI) { + // Make sure the new URI is different from the old one, + // otherwise there's little point trying to load it again. + bool sameURI = false; + url->Equals(newURI, &sameURI); + if (!sameURI) { + if (aNewPostData) { + newPostData.forget(aNewPostData); + } + if (aNotifyKeywordSearchLoading) { + // This notification is meant for Firefox Health Report so it + // can increment counts from the search engine + MaybeNotifyKeywordSearchLoading(keywordProviderName, keywordAsSent); + } + return newURI.forget(); + } + } + + return nullptr; +} + +nsresult nsDocShell::FilterStatusForErrorPage( + nsresult aStatus, nsIChannel* aChannel, uint32_t aLoadType, + bool aIsTopFrame, bool aUseErrorPages, bool aIsInitialDocument, + bool* aSkippedUnknownProtocolNavigation) { + // Errors to be shown only on top-level frames + if ((aStatus == NS_ERROR_UNKNOWN_HOST || + aStatus == NS_ERROR_CONNECTION_REFUSED || + aStatus == NS_ERROR_UNKNOWN_PROXY_HOST || + aStatus == NS_ERROR_PROXY_CONNECTION_REFUSED || + aStatus == NS_ERROR_PROXY_FORBIDDEN || + aStatus == NS_ERROR_PROXY_NOT_IMPLEMENTED || + aStatus == NS_ERROR_PROXY_AUTHENTICATION_FAILED || + aStatus == NS_ERROR_PROXY_TOO_MANY_REQUESTS || + aStatus == NS_ERROR_MALFORMED_URI || + aStatus == NS_ERROR_BLOCKED_BY_POLICY) && + (aIsTopFrame || aUseErrorPages)) { + return aStatus; + } + + if (aStatus == NS_ERROR_NET_TIMEOUT || + aStatus == NS_ERROR_PROXY_GATEWAY_TIMEOUT || + aStatus == NS_ERROR_REDIRECT_LOOP || + aStatus == NS_ERROR_UNKNOWN_SOCKET_TYPE || + aStatus == NS_ERROR_NET_INTERRUPT || aStatus == NS_ERROR_NET_RESET || + aStatus == NS_ERROR_PROXY_BAD_GATEWAY || aStatus == NS_ERROR_OFFLINE || + aStatus == NS_ERROR_MALWARE_URI || aStatus == NS_ERROR_PHISHING_URI || + aStatus == NS_ERROR_UNWANTED_URI || aStatus == NS_ERROR_HARMFUL_URI || + aStatus == NS_ERROR_UNSAFE_CONTENT_TYPE || + aStatus == NS_ERROR_REMOTE_XUL || + aStatus == NS_ERROR_INTERCEPTION_FAILED || + aStatus == NS_ERROR_NET_INADEQUATE_SECURITY || + aStatus == NS_ERROR_NET_HTTP2_SENT_GOAWAY || + aStatus == NS_ERROR_NET_HTTP3_PROTOCOL_ERROR || + aStatus == NS_ERROR_DOM_BAD_URI || aStatus == NS_ERROR_FILE_NOT_FOUND || + aStatus == NS_ERROR_FILE_ACCESS_DENIED || + aStatus == NS_ERROR_CORRUPTED_CONTENT || + aStatus == NS_ERROR_INVALID_CONTENT_ENCODING || + NS_ERROR_GET_MODULE(aStatus) == NS_ERROR_MODULE_SECURITY) { + // Errors to be shown for any frame + return aStatus; + } + + if (aStatus == NS_ERROR_UNKNOWN_PROTOCOL) { + // For unknown protocols we only display an error if the load is triggered + // by the browser itself, or we're replacing the initial document (and + // nothing else). Showing the error for page-triggered navigations causes + // annoying behavior for users, see bug 1528305. + // + // We could, maybe, try to detect if this is in response to some user + // interaction (like clicking a link, or something else) and maybe show + // the error page in that case. But this allows for ctrl+clicking and such + // to see the error page. + nsCOMPtr info = aChannel->LoadInfo(); + if (!info->TriggeringPrincipal()->IsSystemPrincipal() && + StaticPrefs::dom_no_unknown_protocol_error_enabled() && + !aIsInitialDocument) { + if (aSkippedUnknownProtocolNavigation) { + *aSkippedUnknownProtocolNavigation = true; + } + return NS_OK; + } + return aStatus; + } + + if (aStatus == NS_ERROR_DOCUMENT_NOT_CACHED) { + // Non-caching channels will simply return NS_ERROR_OFFLINE. + // Caching channels would have to look at their flags to work + // out which error to return. Or we can fix up the error here. + if (!(aLoadType & LOAD_CMD_HISTORY)) { + return NS_ERROR_OFFLINE; + } + return aStatus; + } + + return NS_OK; +} + +nsresult nsDocShell::EndPageLoad(nsIWebProgress* aProgress, + nsIChannel* aChannel, nsresult aStatus) { + MOZ_LOG(gDocShellLeakLog, LogLevel::Debug, + ("DOCSHELL %p EndPageLoad status: %" PRIx32 "\n", this, + static_cast(aStatus))); + if (!aChannel) { + return NS_ERROR_NULL_POINTER; + } + + // Make sure to discard the initial client if we never created the initial + // about:blank document. Do this before possibly returning from the method + // due to an error. + mInitialClientSource.reset(); + + nsCOMPtr reporter = do_QueryInterface(aChannel); + if (reporter) { + nsCOMPtr loadGroup; + aChannel->GetLoadGroup(getter_AddRefs(loadGroup)); + if (loadGroup) { + reporter->FlushConsoleReports(loadGroup); + } else { + reporter->FlushConsoleReports(GetDocument()); + } + } + + nsCOMPtr url; + nsresult rv = aChannel->GetURI(getter_AddRefs(url)); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr timingChannel = do_QueryInterface(aChannel); + if (timingChannel) { + TimeStamp channelCreationTime; + rv = timingChannel->GetChannelCreation(&channelCreationTime); + if (NS_SUCCEEDED(rv) && !channelCreationTime.IsNull()) { + Telemetry::AccumulateTimeDelta(Telemetry::TOTAL_CONTENT_PAGE_LOAD_TIME, + channelCreationTime); + } + } + + // Timing is picked up by the window, we don't need it anymore + mTiming = nullptr; + + // clean up reload state for meta charset + if (eCharsetReloadRequested == mCharsetReloadState) { + mCharsetReloadState = eCharsetReloadStopOrigional; + } else { + mCharsetReloadState = eCharsetReloadInit; + } + + // Save a pointer to the currently-loading history entry. + // nsDocShell::EndPageLoad will clear mLSHE, but we may need this history + // entry further down in this method. + nsCOMPtr loadingSHE = mLSHE; + mozilla::Unused << loadingSHE; // XXX: Not sure if we need this anymore + + // + // one of many safeguards that prevent death and destruction if + // someone is so very very rude as to bring this window down + // during this load handler. + // + nsCOMPtr kungFuDeathGrip(this); + + // Notify the ContentViewer that the Document has finished loading. This + // will cause any OnLoad(...) and PopState(...) handlers to fire. + if (!mEODForCurrentDocument && mContentViewer) { + mIsExecutingOnLoadHandler = true; + nsCOMPtr contentViewer = mContentViewer; + contentViewer->LoadComplete(aStatus); + mIsExecutingOnLoadHandler = false; + + mEODForCurrentDocument = true; + + // If all documents have completed their loading + // favor native event dispatch priorities + // over performance + if (--gNumberOfDocumentsLoading == 0) { + // Hint to use normal native event dispatch priorities + FavorPerformanceHint(false); + } + } + /* Check if the httpChannel has any cache-control related response headers, + * like no-store, no-cache. If so, update SHEntry so that + * when a user goes back/forward to this page, we appropriately do + * form value restoration or load from server. + */ + nsCOMPtr httpChannel(do_QueryInterface(aChannel)); + if (!httpChannel) { + // HttpChannel could be hiding underneath a Multipart channel. + GetHttpChannel(aChannel, getter_AddRefs(httpChannel)); + } + + if (httpChannel) { + // figure out if SH should be saving layout state. + bool discardLayoutState = ShouldDiscardLayoutState(httpChannel); + if (mLSHE && discardLayoutState && (mLoadType & LOAD_CMD_NORMAL) && + (mLoadType != LOAD_BYPASS_HISTORY) && (mLoadType != LOAD_ERROR_PAGE)) { + mLSHE->SetSaveLayoutStateFlag(false); + } + } + + // Clear mLSHE after calling the onLoadHandlers. This way, if the + // onLoadHandler tries to load something different in + // itself or one of its children, we can deal with it appropriately. + if (mLSHE) { + mLSHE->SetLoadType(LOAD_HISTORY); + + // Clear the mLSHE reference to indicate document loading is done one + // way or another. + SetHistoryEntryAndUpdateBC(Some(nullptr), Nothing()); + } + mActiveEntryIsLoadingFromSessionHistory = false; + + // if there's a refresh header in the channel, this method + // will set it up for us. + if (mBrowsingContext->IsActive() || !mDisableMetaRefreshWhenInactive) + RefreshURIFromQueue(); + + // Test whether this is the top frame or a subframe + bool isTopFrame = mBrowsingContext->IsTop(); + + // If status code indicates an error it means that DocumentChannel already + // tried to fixup the uri and failed. Throw an error dialog box here. + if (NS_FAILED(aStatus)) { + // If we got CONTENT_BLOCKED from EndPageLoad, then we need to fire + // the error event to our embedder, since tests are relying on this. + // The error event is usually fired by the caller of InternalLoad, but + // this particular error can happen asynchronously. + // Bug 1629201 is filed for having much clearer decision making around + // which cases need error events. + bool fireFrameErrorEvent = (aStatus == NS_ERROR_CONTENT_BLOCKED_SHOW_ALT || + aStatus == NS_ERROR_CONTENT_BLOCKED); + UnblockEmbedderLoadEventForFailure(fireFrameErrorEvent); + + bool isInitialDocument = + !GetExtantDocument() || GetExtantDocument()->IsInitialDocument(); + bool skippedUnknownProtocolNavigation = false; + aStatus = FilterStatusForErrorPage(aStatus, aChannel, mLoadType, isTopFrame, + mBrowsingContext->GetUseErrorPages(), + isInitialDocument, + &skippedUnknownProtocolNavigation); + if (NS_FAILED(aStatus)) { + DisplayLoadError(aStatus, url, nullptr, aChannel); + } else if (skippedUnknownProtocolNavigation) { + nsTArray params; + if (NS_FAILED( + NS_GetSanitizedURIStringFromURI(url, *params.AppendElement()))) { + params.LastElement().AssignLiteral(u"(unknown uri)"); + } + nsContentUtils::ReportToConsole( + nsIScriptError::warningFlag, "DOM"_ns, GetExtantDocument(), + nsContentUtils::eDOM_PROPERTIES, "UnknownProtocolNavigationPrevented", + params); + } + } else { + // If we have a host + nsCOMPtr loadInfo = aChannel->LoadInfo(); + PredictorLearnRedirect(url, aChannel, loadInfo->GetOriginAttributes()); + } + + return NS_OK; +} + +//***************************************************************************** +// nsDocShell: Content Viewer Management +//***************************************************************************** + +nsresult nsDocShell::EnsureContentViewer() { + if (mContentViewer) { + return NS_OK; + } + if (mIsBeingDestroyed) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr cspToInheritForAboutBlank; + nsCOMPtr baseURI; + nsIPrincipal* principal = GetInheritedPrincipal(false); + nsIPrincipal* partitionedPrincipal = GetInheritedPrincipal(false, true); + + nsCOMPtr parentItem; + GetInProcessSameTypeParent(getter_AddRefs(parentItem)); + if (parentItem) { + if (nsCOMPtr domWin = GetWindow()) { + nsCOMPtr parentElement = domWin->GetFrameElementInternal(); + if (parentElement) { + baseURI = parentElement->GetBaseURI(); + cspToInheritForAboutBlank = parentElement->GetCsp(); + } + } + } + + nsresult rv = CreateAboutBlankContentViewer( + principal, partitionedPrincipal, cspToInheritForAboutBlank, baseURI); + + NS_ENSURE_STATE(mContentViewer); + + if (NS_SUCCEEDED(rv)) { + RefPtr doc(GetDocument()); + NS_ASSERTION(doc, + "Should have doc if CreateAboutBlankContentViewer " + "succeeded!"); + + doc->SetIsInitialDocument(true); + + // Documents created using EnsureContentViewer may be transient + // placeholders created by framescripts before content has a + // chance to load. In some cases, window.open(..., "noopener") + // will create such a document and then synchronously tear it + // down, firing a "pagehide" event. Doing so violates our + // assertions about DocGroups. It's easier to silence the + // assertion here than to avoid creating the extra document. + doc->IgnoreDocGroupMismatches(); + } + + return rv; +} + +nsresult nsDocShell::CreateAboutBlankContentViewer( + nsIPrincipal* aPrincipal, nsIPrincipal* aPartitionedPrincipal, + nsIContentSecurityPolicy* aCSP, nsIURI* aBaseURI, + const Maybe& aCOEP, + bool aTryToSaveOldPresentation, bool aCheckPermitUnload, + WindowGlobalChild* aActor) { + RefPtr blankDoc; + nsCOMPtr viewer; + nsresult rv = NS_ERROR_FAILURE; + + MOZ_ASSERT_IF(aActor, aActor->DocumentPrincipal() == aPrincipal); + + /* mCreatingDocument should never be true at this point. However, it's + a theoretical possibility. We want to know about it and make it stop, + and this sounds like a job for an assertion. */ + NS_ASSERTION(!mCreatingDocument, + "infinite(?) loop creating document averted"); + if (mCreatingDocument) { + return NS_ERROR_FAILURE; + } + + if (!mBrowsingContext->AncestorsAreCurrent()) { + mBrowsingContext->RemoveRootFromBFCacheSync(); + return NS_ERROR_NOT_AVAILABLE; + } + + // mContentViewer->PermitUnload may release |this| docshell. + nsCOMPtr kungFuDeathGrip(this); + + AutoRestore creatingDocument(mCreatingDocument); + mCreatingDocument = true; + + if (aPrincipal && !aPrincipal->IsSystemPrincipal() && + mItemType != typeChrome) { + MOZ_ASSERT(aPrincipal->OriginAttributesRef() == + mBrowsingContext->OriginAttributesRef()); + } + + // Make sure timing is created. But first record whether we had it + // already, so we don't clobber the timing for an in-progress load. + bool hadTiming = mTiming; + bool toBeReset = MaybeInitTiming(); + if (mContentViewer) { + if (aCheckPermitUnload) { + // We've got a content viewer already. Make sure the user + // permits us to discard the current document and replace it + // with about:blank. And also ensure we fire the unload events + // in the current document. + + // Unload gets fired first for + // document loaded from the session history. + mTiming->NotifyBeforeUnload(); + + bool okToUnload; + rv = mContentViewer->PermitUnload(&okToUnload); + + if (NS_SUCCEEDED(rv) && !okToUnload) { + // The user chose not to unload the page, interrupt the load. + MaybeResetInitTiming(toBeReset); + return NS_ERROR_FAILURE; + } + if (mTiming) { + mTiming->NotifyUnloadAccepted(mCurrentURI); + } + } + + mSavingOldViewer = aTryToSaveOldPresentation && + CanSavePresentation(LOAD_NORMAL, nullptr, nullptr); + + // Make sure to blow away our mLoadingURI just in case. No loads + // from inside this pagehide. + mLoadingURI = nullptr; + + // Stop any in-progress loading, so that we don't accidentally trigger any + // PageShow notifications from Embed() interrupting our loading below. + Stop(); + + // Notify the current document that it is about to be unloaded!! + // + // It is important to fire the unload() notification *before* any state + // is changed within the DocShell - otherwise, javascript will get the + // wrong information :-( + // + (void)FirePageHideNotification(!mSavingOldViewer); + // pagehide notification might destroy this docshell. + if (mIsBeingDestroyed) { + return NS_ERROR_DOCSHELL_DYING; + } + } + + // Now make sure we don't think we're in the middle of firing unload after + // this point. This will make us fire unload when the about:blank document + // unloads... but that's ok, more or less. Would be nice if it fired load + // too, of course. + mFiredUnloadEvent = false; + + nsCOMPtr docFactory = + nsContentUtils::FindInternalContentViewer("text/html"_ns); + + if (docFactory) { + nsCOMPtr principal, partitionedPrincipal; + const uint32_t sandboxFlags = + mBrowsingContext->GetHasLoadedNonInitialDocument() + ? mBrowsingContext->GetSandboxFlags() + : mBrowsingContext->GetInitialSandboxFlags(); + // If we're sandboxed, then create a new null principal. We skip + // this if we're being created from WindowGlobalChild, since in + // that case we already have a null principal if required. + // We can't compare againt the BrowsingContext sandbox flag, since + // the value was taken when the load initiated and may have since + // changed. + if ((sandboxFlags & SANDBOXED_ORIGIN) && !aActor) { + if (aPrincipal) { + principal = NullPrincipal::CreateWithInheritedAttributes(aPrincipal); + } else { + principal = NullPrincipal::CreateWithInheritedAttributes(this); + } + partitionedPrincipal = principal; + } else { + principal = aPrincipal; + partitionedPrincipal = aPartitionedPrincipal; + } + + MaybeCreateInitialClientSource(principal); + + // generate (about:blank) document to load + blankDoc = nsContentDLF::CreateBlankDocument(mLoadGroup, principal, + partitionedPrincipal, this); + if (blankDoc) { + // Hack: manually set the CSP for the new document + // Please create an actual copy of the CSP (do not share the same + // reference) otherwise appending a new policy within the new + // document will be incorrectly propagated to the opening doc. + if (aCSP) { + RefPtr cspToInherit = new nsCSPContext(); + cspToInherit->InitFromOther(static_cast(aCSP)); + blankDoc->SetCsp(cspToInherit); + } + + blankDoc->SetEmbedderPolicy(aCOEP); + + // Hack: set the base URI manually, since this document never + // got Reset() with a channel. + blankDoc->SetBaseURI(aBaseURI); + + // Copy our sandbox flags to the document. These are immutable + // after being set here. + blankDoc->SetSandboxFlags(sandboxFlags); + + // create a content viewer for us and the new document + docFactory->CreateInstanceForDocument( + NS_ISUPPORTS_CAST(nsIDocShell*, this), blankDoc, "view", + getter_AddRefs(viewer)); + + // hook 'em up + if (viewer) { + viewer->SetContainer(this); + rv = Embed(viewer, aActor, true, false); + NS_ENSURE_SUCCESS(rv, rv); + + SetCurrentURI(blankDoc->GetDocumentURI(), nullptr, true, 0); + rv = mIsBeingDestroyed ? NS_ERROR_NOT_AVAILABLE : NS_OK; + } + } + } + + // The transient about:blank viewer doesn't have a session history entry. + SetHistoryEntryAndUpdateBC(Nothing(), Some(nullptr)); + + // Clear out our mTiming like we would in EndPageLoad, if we didn't + // have one before entering this function. + if (!hadTiming) { + mTiming = nullptr; + mBlankTiming = true; + } + + return rv; +} + +NS_IMETHODIMP +nsDocShell::CreateAboutBlankContentViewer(nsIPrincipal* aPrincipal, + nsIPrincipal* aPartitionedPrincipal, + nsIContentSecurityPolicy* aCSP) { + return CreateAboutBlankContentViewer(aPrincipal, aPartitionedPrincipal, aCSP, + nullptr); +} + +nsresult nsDocShell::CreateContentViewerForActor( + WindowGlobalChild* aWindowActor) { + MOZ_ASSERT(aWindowActor); + + // FIXME: WindowGlobalChild should provide the PartitionedPrincipal. + nsresult rv = CreateAboutBlankContentViewer( + aWindowActor->DocumentPrincipal(), aWindowActor->DocumentPrincipal(), + /* aCsp */ nullptr, + /* aBaseURI */ nullptr, + /* aCOEP */ Nothing(), + /* aTryToSaveOldPresentation */ true, + /* aCheckPermitUnload */ true, aWindowActor); + if (NS_SUCCEEDED(rv)) { + RefPtr doc(GetDocument()); + MOZ_ASSERT( + doc, + "Should have a document if CreateAboutBlankContentViewer succeeded"); + MOZ_ASSERT(doc->GetOwnerGlobal() == aWindowActor->GetWindowGlobal(), + "New document should be in the same global as our actor"); + + // FIXME: We may want to support non-initial documents here. + doc->SetIsInitialDocument(true); + } + + return rv; +} + +bool nsDocShell::CanSavePresentation(uint32_t aLoadType, + nsIRequest* aNewRequest, + Document* aNewDocument) { + if (!mOSHE) { + return false; // no entry to save into + } + + nsCOMPtr viewer = mOSHE->GetContentViewer(); + if (viewer) { + NS_WARNING("mOSHE already has a content viewer!"); + return false; + } + + // Only save presentation for "normal" loads and link loads. Anything else + // probably wants to refetch the page, so caching the old presentation + // would be incorrect. + if (aLoadType != LOAD_NORMAL && aLoadType != LOAD_HISTORY && + aLoadType != LOAD_LINK && aLoadType != LOAD_STOP_CONTENT && + aLoadType != LOAD_STOP_CONTENT_AND_REPLACE && + aLoadType != LOAD_ERROR_PAGE) { + return false; + } + + // If the session history entry has the saveLayoutState flag set to false, + // then we should not cache the presentation. + if (!mOSHE->GetSaveLayoutStateFlag()) { + return false; + } + + // If the document is not done loading, don't cache it. + if (!mScriptGlobal || mScriptGlobal->IsLoading()) { + MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose, + ("Blocked due to document still loading")); + return false; + } + + if (mScriptGlobal->WouldReuseInnerWindow(aNewDocument)) { + return false; + } + + // Avoid doing the work of saving the presentation state in the case where + // the content viewer cache is disabled. + if (nsSHistory::GetMaxTotalViewers() == 0) { + return false; + } + + // Don't cache the content viewer if we're in a subframe. + if (mBrowsingContext->GetParent()) { + return false; // this is a subframe load + } + + // If the document does not want its presentation cached, then don't. + RefPtr doc = mScriptGlobal->GetExtantDoc(); + + uint16_t bfCacheCombo = 0; + bool canSavePresentation = + doc->CanSavePresentation(aNewRequest, bfCacheCombo); + MOZ_ASSERT_IF(canSavePresentation, bfCacheCombo == 0); + if (canSavePresentation && doc->IsTopLevelContentDocument()) { + auto* browsingContextGroup = mBrowsingContext->Group(); + nsTArray>& topLevelContext = + browsingContextGroup->Toplevels(); + + for (const auto& browsingContext : topLevelContext) { + if (browsingContext != mBrowsingContext) { + if (StaticPrefs::docshell_shistory_bfcache_require_no_opener()) { + canSavePresentation = false; + } + bfCacheCombo |= BFCacheStatus::NOT_ONLY_TOPLEVEL_IN_BCG; + break; + } + } + } + ReportBFCacheComboTelemetry(bfCacheCombo); + + return doc && canSavePresentation; +} + +void nsDocShell::ReportBFCacheComboTelemetry(uint16_t aCombo) { + // There are 11 possible reasons to make a request fails to use BFCache + // (see BFCacheStatus in dom/base/Document.h), and we'd like to record + // the common combinations for reasons which make requests fail to use + // BFCache. These combinations are generated based on some local browsings, + // we need to adjust them when necessary. + enum BFCacheStatusCombo : uint16_t { + BFCACHE_SUCCESS, + NOT_ONLY_TOPLEVEL = mozilla::dom::BFCacheStatus::NOT_ONLY_TOPLEVEL_IN_BCG, + UNLOAD = mozilla::dom::BFCacheStatus::UNLOAD_LISTENER, + UNLOAD_REQUEST = mozilla::dom::BFCacheStatus::UNLOAD_LISTENER | + mozilla::dom::BFCacheStatus::REQUEST, + REQUEST = mozilla::dom::BFCacheStatus::REQUEST, + UNLOAD_REQUEST_PEER = mozilla::dom::BFCacheStatus::UNLOAD_LISTENER | + mozilla::dom::BFCacheStatus::REQUEST | + mozilla::dom::BFCacheStatus::ACTIVE_PEER_CONNECTION, + UNLOAD_REQUEST_PEER_MSE = + mozilla::dom::BFCacheStatus::UNLOAD_LISTENER | + mozilla::dom::BFCacheStatus::REQUEST | + mozilla::dom::BFCacheStatus::ACTIVE_PEER_CONNECTION | + mozilla::dom::BFCacheStatus::CONTAINS_MSE_CONTENT, + UNLOAD_REQUEST_MSE = mozilla::dom::BFCacheStatus::UNLOAD_LISTENER | + mozilla::dom::BFCacheStatus::REQUEST | + mozilla::dom::BFCacheStatus::CONTAINS_MSE_CONTENT, + SUSPENDED_UNLOAD_REQUEST_PEER = + mozilla::dom::BFCacheStatus::SUSPENDED | + mozilla::dom::BFCacheStatus::UNLOAD_LISTENER | + mozilla::dom::BFCacheStatus::REQUEST | + mozilla::dom::BFCacheStatus::ACTIVE_PEER_CONNECTION, + REMOTE_SUBFRAMES = mozilla::dom::BFCacheStatus::CONTAINS_REMOTE_SUBFRAMES + }; + + switch (aCombo) { + case BFCACHE_SUCCESS: + Telemetry::AccumulateCategorical( + Telemetry::LABELS_BFCACHE_COMBO::BFCache_Success); + break; + case NOT_ONLY_TOPLEVEL: + if (StaticPrefs::docshell_shistory_bfcache_require_no_opener()) { + Telemetry::AccumulateCategorical( + Telemetry::LABELS_BFCACHE_COMBO::Other); + break; + } + Telemetry::AccumulateCategorical( + Telemetry::LABELS_BFCACHE_COMBO::BFCache_Success); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_BFCACHE_COMBO::Success_Not_Toplevel); + break; + case UNLOAD: + Telemetry::AccumulateCategorical(Telemetry::LABELS_BFCACHE_COMBO::Unload); + break; + case UNLOAD_REQUEST: + Telemetry::AccumulateCategorical( + Telemetry::LABELS_BFCACHE_COMBO::Unload_Req); + break; + case REQUEST: + Telemetry::AccumulateCategorical(Telemetry::LABELS_BFCACHE_COMBO::Req); + break; + case UNLOAD_REQUEST_PEER: + Telemetry::AccumulateCategorical( + Telemetry::LABELS_BFCACHE_COMBO::Unload_Req_Peer); + break; + case UNLOAD_REQUEST_PEER_MSE: + Telemetry::AccumulateCategorical( + Telemetry::LABELS_BFCACHE_COMBO::Unload_Req_Peer_MSE); + break; + case UNLOAD_REQUEST_MSE: + Telemetry::AccumulateCategorical( + Telemetry::LABELS_BFCACHE_COMBO::Unload_Req_MSE); + break; + case SUSPENDED_UNLOAD_REQUEST_PEER: + Telemetry::AccumulateCategorical( + Telemetry::LABELS_BFCACHE_COMBO::SPD_Unload_Req_Peer); + break; + case REMOTE_SUBFRAMES: + Telemetry::AccumulateCategorical( + Telemetry::LABELS_BFCACHE_COMBO::Remote_Subframes); + break; + default: + Telemetry::AccumulateCategorical(Telemetry::LABELS_BFCACHE_COMBO::Other); + break; + } +}; + +void nsDocShell::ReattachEditorToWindow(nsISHEntry* aSHEntry) { + MOZ_ASSERT(!mIsBeingDestroyed); + + NS_ASSERTION(!mEditorData, + "Why reattach an editor when we already have one?"); + NS_ASSERTION(aSHEntry && aSHEntry->HasDetachedEditor(), + "Reattaching when there's not a detached editor."); + + if (mEditorData || !aSHEntry) { + return; + } + + mEditorData = WrapUnique(aSHEntry->ForgetEditorData()); + if (mEditorData) { +#ifdef DEBUG + nsresult rv = +#endif + mEditorData->ReattachToWindow(this); + NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to reattach editing session"); + } +} + +void nsDocShell::DetachEditorFromWindow() { + if (!mEditorData || mEditorData->WaitingForLoad()) { + // If there's nothing to detach, or if the editor data is actually set + // up for the _new_ page that's coming in, don't detach. + return; + } + + NS_ASSERTION(!mOSHE || !mOSHE->HasDetachedEditor(), + "Detaching editor when it's already detached."); + + nsresult res = mEditorData->DetachFromWindow(); + NS_ASSERTION(NS_SUCCEEDED(res), "Failed to detach editor"); + + if (NS_SUCCEEDED(res)) { + // Make mOSHE hold the owning ref to the editor data. + if (mOSHE) { + MOZ_ASSERT(!mIsBeingDestroyed || !mOSHE->HasDetachedEditor(), + "We should not set the editor data again once after we " + "detached the editor data during destroying this docshell"); + mOSHE->SetEditorData(mEditorData.release()); + } else { + mEditorData = nullptr; + } + } + +#ifdef DEBUG + { + bool isEditable; + GetEditable(&isEditable); + NS_ASSERTION(!isEditable, + "Window is still editable after detaching editor."); + } +#endif // DEBUG +} + +nsresult nsDocShell::CaptureState() { + if (!mOSHE || mOSHE == mLSHE) { + // No entry to save into, or we're replacing the existing entry. + return NS_ERROR_FAILURE; + } + + if (!mScriptGlobal) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr windowState = mScriptGlobal->SaveWindowState(); + NS_ENSURE_TRUE(windowState, NS_ERROR_FAILURE); + + if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Debug))) { + nsAutoCString spec; + nsCOMPtr uri; + if (mozilla::SessionHistoryInParent()) { + uri = mActiveEntry->GetURI(); + } else { + uri = mOSHE->GetURI(); + } + if (uri) { + uri->GetSpec(spec); + } + MOZ_LOG(gPageCacheLog, LogLevel::Debug, + ("Saving presentation into session history, URI: %s", spec.get())); + } + + mOSHE->SetWindowState(windowState); + + // Suspend refresh URIs and save off the timer queue + mOSHE->SetRefreshURIList(mSavedRefreshURIList); + + // Capture the current content viewer bounds. + if (mContentViewer) { + nsIntRect bounds; + mContentViewer->GetBounds(bounds); + mOSHE->SetViewerBounds(bounds); + } + + // Capture the docshell hierarchy. + mOSHE->ClearChildShells(); + + uint32_t childCount = mChildList.Length(); + for (uint32_t i = 0; i < childCount; ++i) { + nsCOMPtr childShell = do_QueryInterface(ChildAt(i)); + NS_ASSERTION(childShell, "null child shell"); + + mOSHE->AddChildShell(childShell); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::RestorePresentationEvent::Run() { + if (mDocShell && NS_FAILED(mDocShell->RestoreFromHistory())) { + NS_WARNING("RestoreFromHistory failed"); + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::BeginRestore(nsIContentViewer* aContentViewer, bool aTop) { + nsresult rv; + if (!aContentViewer) { + rv = EnsureContentViewer(); + NS_ENSURE_SUCCESS(rv, rv); + + aContentViewer = mContentViewer; + } + + // Dispatch events for restoring the presentation. We try to simulate + // the progress notifications loading the document would cause, so we add + // the document's channel to the loadgroup to initiate stateChange + // notifications. + + RefPtr doc = aContentViewer->GetDocument(); + if (doc) { + nsIChannel* channel = doc->GetChannel(); + if (channel) { + mEODForCurrentDocument = false; + mIsRestoringDocument = true; + mLoadGroup->AddRequest(channel, nullptr); + mIsRestoringDocument = false; + } + } + + if (!aTop) { + // This point corresponds to us having gotten OnStartRequest or + // STATE_START, so do the same thing that CreateContentViewer does at + // this point to ensure that unload/pagehide events for this document + // will fire when it's unloaded again. + mFiredUnloadEvent = false; + + // For non-top frames, there is no notion of making sure that the + // previous document is in the domwindow when STATE_START notifications + // happen. We can just call BeginRestore for all of the child shells + // now. + rv = BeginRestoreChildren(); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +nsresult nsDocShell::BeginRestoreChildren() { + for (auto* childDocLoader : mChildList.ForwardRange()) { + nsCOMPtr child = do_QueryObject(childDocLoader); + if (child) { + nsresult rv = child->BeginRestore(nullptr, false); + NS_ENSURE_SUCCESS(rv, rv); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::FinishRestore() { + // First we call finishRestore() on our children. In the simulated load, + // all of the child frames finish loading before the main document. + + for (auto* childDocLoader : mChildList.ForwardRange()) { + nsCOMPtr child = do_QueryObject(childDocLoader); + if (child) { + child->FinishRestore(); + } + } + + if (mOSHE && mOSHE->HasDetachedEditor()) { + ReattachEditorToWindow(mOSHE); + } + + RefPtr doc = GetDocument(); + if (doc) { + // Finally, we remove the request from the loadgroup. This will + // cause onStateChange(STATE_STOP) to fire, which will fire the + // pageshow event to the chrome. + + nsIChannel* channel = doc->GetChannel(); + if (channel) { + mIsRestoringDocument = true; + mLoadGroup->RemoveRequest(channel, nullptr, NS_OK); + mIsRestoringDocument = false; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetRestoringDocument(bool* aRestoring) { + *aRestoring = mIsRestoringDocument; + return NS_OK; +} + +nsresult nsDocShell::RestorePresentation(nsISHEntry* aSHEntry, + bool* aRestoring) { + MOZ_ASSERT(!mIsBeingDestroyed); + + NS_ASSERTION(mLoadType & LOAD_CMD_HISTORY, + "RestorePresentation should only be called for history loads"); + + nsCOMPtr viewer = aSHEntry->GetContentViewer(); + + nsAutoCString spec; + if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Debug))) { + nsCOMPtr uri = aSHEntry->GetURI(); + if (uri) { + uri->GetSpec(spec); + } + } + + *aRestoring = false; + + if (!viewer) { + MOZ_LOG(gPageCacheLog, LogLevel::Debug, + ("no saved presentation for uri: %s", spec.get())); + return NS_OK; + } + + // We need to make sure the content viewer's container is this docshell. + // In subframe navigation, it's possible for the docshell that the + // content viewer was originally loaded into to be replaced with a + // different one. We don't currently support restoring the presentation + // in that case. + + nsCOMPtr container; + viewer->GetContainer(getter_AddRefs(container)); + if (!::SameCOMIdentity(container, GetAsSupports(this))) { + MOZ_LOG(gPageCacheLog, LogLevel::Debug, + ("No valid container, clearing presentation")); + aSHEntry->SetContentViewer(nullptr); + return NS_ERROR_FAILURE; + } + + NS_ASSERTION(mContentViewer != viewer, "Restoring existing presentation"); + + MOZ_LOG(gPageCacheLog, LogLevel::Debug, + ("restoring presentation from session history: %s", spec.get())); + + SetHistoryEntryAndUpdateBC(Some(aSHEntry), Nothing()); + + // Post an event that will remove the request after we've returned + // to the event loop. This mimics the way it is called by nsIChannel + // implementations. + + // Revoke any pending restore (just in case). + NS_ASSERTION(!mRestorePresentationEvent.IsPending(), + "should only have one RestorePresentationEvent"); + mRestorePresentationEvent.Revoke(); + + RefPtr evt = new RestorePresentationEvent(this); + nsresult rv = Dispatch(TaskCategory::Other, do_AddRef(evt)); + if (NS_SUCCEEDED(rv)) { + mRestorePresentationEvent = evt.get(); + // The rest of the restore processing will happen on our event + // callback. + *aRestoring = true; + } + + return rv; +} + +namespace { +class MOZ_STACK_CLASS PresentationEventForgetter { + public: + explicit PresentationEventForgetter( + nsRevocableEventPtr& + aRestorePresentationEvent) + : mRestorePresentationEvent(aRestorePresentationEvent), + mEvent(aRestorePresentationEvent.get()) {} + + ~PresentationEventForgetter() { Forget(); } + + void Forget() { + if (mRestorePresentationEvent.get() == mEvent) { + mRestorePresentationEvent.Forget(); + mEvent = nullptr; + } + } + + private: + nsRevocableEventPtr& + mRestorePresentationEvent; + RefPtr mEvent; +}; + +} // namespace + +bool nsDocShell::SandboxFlagsImplyCookies(const uint32_t& aSandboxFlags) { + return (aSandboxFlags & (SANDBOXED_ORIGIN | SANDBOXED_SCRIPTS)) == 0; +} + +nsresult nsDocShell::RestoreFromHistory() { + MOZ_ASSERT(mRestorePresentationEvent.IsPending()); + PresentationEventForgetter forgetter(mRestorePresentationEvent); + + // This section of code follows the same ordering as CreateContentViewer. + if (!mLSHE) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr viewer = mLSHE->GetContentViewer(); + if (!viewer) { + return NS_ERROR_FAILURE; + } + + if (mSavingOldViewer) { + // We determined that it was safe to cache the document presentation + // at the time we initiated the new load. We need to check whether + // it's still safe to do so, since there may have been DOM mutations + // or new requests initiated. + RefPtr doc = viewer->GetDocument(); + nsIRequest* request = nullptr; + if (doc) { + request = doc->GetChannel(); + } + mSavingOldViewer = CanSavePresentation(mLoadType, request, doc); + } + + nsCOMPtr oldCv(mContentViewer); + nsCOMPtr newCv(viewer); + float overrideDPPX = 0.0f; + + if (oldCv) { + oldCv->GetOverrideDPPX(&overrideDPPX); + } + + // Protect against mLSHE going away via a load triggered from + // pagehide or unload. + nsCOMPtr origLSHE = mLSHE; + + // Make sure to blow away our mLoadingURI just in case. No loads + // from inside this pagehide. + mLoadingURI = nullptr; + + // Notify the old content viewer that it's being hidden. + FirePageHideNotification(!mSavingOldViewer); + // pagehide notification might destroy this docshell. + if (mIsBeingDestroyed) { + return NS_ERROR_DOCSHELL_DYING; + } + + // If mLSHE was changed as a result of the pagehide event, then + // something else was loaded. Don't finish restoring. + if (mLSHE != origLSHE) { + return NS_OK; + } + + // Add the request to our load group. We do this before swapping out + // the content viewers so that consumers of STATE_START can access + // the old document. We only deal with the toplevel load at this time -- + // to be consistent with normal document loading, subframes cannot start + // loading until after data arrives, which is after STATE_START completes. + + RefPtr currentPresentationRestoration = + mRestorePresentationEvent.get(); + Stop(); + // Make sure we're still restoring the same presentation. + // If we aren't, docshell is in process doing another load already. + NS_ENSURE_STATE(currentPresentationRestoration == + mRestorePresentationEvent.get()); + BeginRestore(viewer, true); + NS_ENSURE_STATE(currentPresentationRestoration == + mRestorePresentationEvent.get()); + forgetter.Forget(); + + // Set mFiredUnloadEvent = false so that the unload handler for the + // *new* document will fire. + mFiredUnloadEvent = false; + + mURIResultedInDocument = true; + RefPtr rootSH = GetRootSessionHistory(); + if (rootSH) { + mPreviousEntryIndex = rootSH->Index(); + if (!mozilla::SessionHistoryInParent()) { + rootSH->LegacySHistory()->UpdateIndex(); + } + mLoadedEntryIndex = rootSH->Index(); + MOZ_LOG(gPageCacheLog, LogLevel::Verbose, + ("Previous index: %d, Loaded index: %d", mPreviousEntryIndex, + mLoadedEntryIndex)); + } + + // Rather than call Embed(), we will retrieve the viewer from the session + // history entry and swap it in. + // XXX can we refactor this so that we can just call Embed()? + PersistLayoutHistoryState(); + nsresult rv; + if (mContentViewer) { + if (mSavingOldViewer && NS_FAILED(CaptureState())) { + if (mOSHE) { + mOSHE->SyncPresentationState(); + } + mSavingOldViewer = false; + } + } + + mSavedRefreshURIList = nullptr; + + // In cases where we use a transient about:blank viewer between loads, + // we never show the transient viewer, so _its_ previous viewer is never + // unhooked from the view hierarchy. Destroy any such previous viewer now, + // before we grab the root view sibling, so that we don't grab a view + // that's about to go away. + + if (mContentViewer) { + // Make sure to hold a strong ref to previousViewer here while we + // drop the reference to it from mContentViewer. + nsCOMPtr previousViewer = + mContentViewer->GetPreviousViewer(); + if (previousViewer) { + mContentViewer->SetPreviousViewer(nullptr); + previousViewer->Destroy(); + } + } + + // Save off the root view's parent and sibling so that we can insert the + // new content viewer's root view at the same position. Also save the + // bounds of the root view's widget. + + nsView* rootViewSibling = nullptr; + nsView* rootViewParent = nullptr; + nsIntRect newBounds(0, 0, 0, 0); + + PresShell* oldPresShell = GetPresShell(); + if (oldPresShell) { + nsViewManager* vm = oldPresShell->GetViewManager(); + if (vm) { + nsView* oldRootView = vm->GetRootView(); + + if (oldRootView) { + rootViewSibling = oldRootView->GetNextSibling(); + rootViewParent = oldRootView->GetParent(); + + mContentViewer->GetBounds(newBounds); + } + } + } + + nsCOMPtr container; + RefPtr sibling; + if (rootViewParent && rootViewParent->GetParent()) { + nsIFrame* frame = rootViewParent->GetParent()->GetFrame(); + container = frame ? frame->GetContent() : nullptr; + } + if (rootViewSibling) { + nsIFrame* frame = rootViewSibling->GetFrame(); + sibling = frame ? frame->PresShell()->GetDocument() : nullptr; + } + + // Transfer ownership to mContentViewer. By ensuring that either the + // docshell or the session history, but not both, have references to the + // content viewer, we prevent the viewer from being torn down after + // Destroy() is called. + + if (mContentViewer) { + mContentViewer->Close(mSavingOldViewer ? mOSHE.get() : nullptr); + viewer->SetPreviousViewer(mContentViewer); + } + if (mOSHE && (!mContentViewer || !mSavingOldViewer)) { + // We don't plan to save a viewer in mOSHE; tell it to drop + // any other state it's holding. + mOSHE->SyncPresentationState(); + } + + // Order the mContentViewer setup just like Embed does. + mContentViewer = nullptr; + + // Now that we're about to switch documents, forget all of our children. + // Note that we cached them as needed up in CaptureState above. + DestroyChildren(); + + mContentViewer.swap(viewer); + + // Grab all of the related presentation from the SHEntry now. + // Clearing the viewer from the SHEntry will clear all of this state. + nsCOMPtr windowState = mLSHE->GetWindowState(); + mLSHE->SetWindowState(nullptr); + + bool sticky = mLSHE->GetSticky(); + + RefPtr document = mContentViewer->GetDocument(); + + nsCOMArray childShells; + int32_t i = 0; + nsCOMPtr child; + while (NS_SUCCEEDED(mLSHE->ChildShellAt(i++, getter_AddRefs(child))) && + child) { + childShells.AppendObject(child); + } + + // get the previous content viewer size + nsIntRect oldBounds(0, 0, 0, 0); + mLSHE->GetViewerBounds(oldBounds); + + // Restore the refresh URI list. The refresh timers will be restarted + // when EndPageLoad() is called. + nsCOMPtr refreshURIList = mLSHE->GetRefreshURIList(); + + // Reattach to the window object. + mIsRestoringDocument = true; // for MediaDocument::BecomeInteractive + rv = mContentViewer->Open(windowState, mLSHE); + mIsRestoringDocument = false; + + // Hack to keep nsDocShellEditorData alive across the + // SetContentViewer(nullptr) call below. + UniquePtr data(mLSHE->ForgetEditorData()); + + // Now remove it from the cached presentation. + mLSHE->SetContentViewer(nullptr); + mEODForCurrentDocument = false; + + mLSHE->SetEditorData(data.release()); + +#ifdef DEBUG + { + nsCOMPtr refreshURIs = mLSHE->GetRefreshURIList(); + nsCOMPtr childShell; + mLSHE->ChildShellAt(0, getter_AddRefs(childShell)); + NS_ASSERTION(!refreshURIs && !childShell, + "SHEntry should have cleared presentation state"); + } +#endif + + // Restore the sticky state of the viewer. The viewer has set this state + // on the history entry in Destroy() just before marking itself non-sticky, + // to avoid teardown of the presentation. + mContentViewer->SetSticky(sticky); + + NS_ENSURE_SUCCESS(rv, rv); + + // mLSHE is now our currently-loaded document. + SetHistoryEntryAndUpdateBC(Nothing(), Some(mLSHE)); + + // We aren't going to restore any items from the LayoutHistoryState, + // but we don't want them to stay around in case the page is reloaded. + SetLayoutHistoryState(nullptr); + + // This is the end of our Embed() replacement + + mSavingOldViewer = false; + mEODForCurrentDocument = false; + + // Tell the event loop to favor plevents over user events, see comments + // in CreateContentViewer. + if (++gNumberOfDocumentsLoading == 1) { + FavorPerformanceHint(true); + } + + if (oldCv) { + newCv->SetOverrideDPPX(overrideDPPX); + } + + if (document) { + RefPtr parent = GetInProcessParentDocshell(); + if (parent) { + RefPtr d = parent->GetDocument(); + if (d) { + if (d->EventHandlingSuppressed()) { + document->SuppressEventHandling(d->EventHandlingSuppressed()); + } + } + } + + // Use the uri from the mLSHE we had when we entered this function + // (which need not match the document's URI if anchors are involved), + // since that's the history entry we're loading. Note that if we use + // origLSHE we don't have to worry about whether the entry in question + // is still mLSHE or whether it's now mOSHE. + nsCOMPtr uri = origLSHE->GetURI(); + SetCurrentURI(uri, document->GetChannel(), true, 0); + } + + // This is the end of our CreateContentViewer() replacement. + // Now we simulate a load. First, we restore the state of the javascript + // window object. + nsCOMPtr privWin = GetWindow(); + NS_ASSERTION(privWin, "could not get nsPIDOMWindow interface"); + + // Now, dispatch a title change event which would happen as the + // is parsed. + document->NotifyPossibleTitleChange(false); + + // Now we simulate appending child docshells for subframes. + for (i = 0; i < childShells.Count(); ++i) { + nsIDocShellTreeItem* childItem = childShells.ObjectAt(i); + nsCOMPtr childShell = do_QueryInterface(childItem); + + // Make sure to not clobber the state of the child. Since AddChild + // always clobbers it, save it off first. + bool allowJavascript; + childShell->GetAllowJavascript(&allowJavascript); + + bool allowRedirects; + childShell->GetAllowMetaRedirects(&allowRedirects); + + bool allowSubframes; + childShell->GetAllowSubframes(&allowSubframes); + + bool allowImages; + childShell->GetAllowImages(&allowImages); + + bool allowMedia = childShell->GetAllowMedia(); + + bool allowDNSPrefetch; + childShell->GetAllowDNSPrefetch(&allowDNSPrefetch); + + bool allowContentRetargeting = childShell->GetAllowContentRetargeting(); + bool allowContentRetargetingOnChildren = + childShell->GetAllowContentRetargetingOnChildren(); + + // this.AddChild(child) calls child.SetDocLoaderParent(this), meaning that + // the child inherits our state. Among other things, this means that the + // child inherits our mPrivateBrowsingId, which is what we want. + AddChild(childItem); + + childShell->SetAllowJavascript(allowJavascript); + childShell->SetAllowMetaRedirects(allowRedirects); + childShell->SetAllowSubframes(allowSubframes); + childShell->SetAllowImages(allowImages); + childShell->SetAllowMedia(allowMedia); + childShell->SetAllowDNSPrefetch(allowDNSPrefetch); + childShell->SetAllowContentRetargeting(allowContentRetargeting); + childShell->SetAllowContentRetargetingOnChildren( + allowContentRetargetingOnChildren); + + rv = childShell->BeginRestore(nullptr, false); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Make sure to restore the window state after adding the child shells back + // to the tree. This is necessary for Thaw() and Resume() to propagate + // properly. + rv = privWin->RestoreWindowState(windowState); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr presShell = GetPresShell(); + + // We may be displayed on a different monitor (or in a different + // HiDPI mode) than when we got into the history list. So we need + // to check if this has happened. See bug 838239. + + // Because the prescontext normally handles resolution changes via + // a runnable (see nsPresContext::UIResolutionChanged), its device + // context won't be -immediately- updated as a result of calling + // presShell->BackingScaleFactorChanged(). + + // But we depend on that device context when adjusting the view size + // via mContentViewer->SetBounds(newBounds) below. So we need to + // explicitly tell it to check for changed resolution here. + if (presShell) { + RefPtr pc = presShell->GetPresContext(); + if (pc->DeviceContext()->CheckDPIChange()) { + presShell->BackingScaleFactorChanged(); + } + // Recompute zoom and text-zoom and such. + pc->RecomputeBrowsingContextDependentData(); + } + + nsViewManager* newVM = presShell ? presShell->GetViewManager() : nullptr; + nsView* newRootView = newVM ? newVM->GetRootView() : nullptr; + + // Insert the new root view at the correct location in the view tree. + if (container) { + nsSubDocumentFrame* subDocFrame = + do_QueryFrame(container->GetPrimaryFrame()); + rootViewParent = subDocFrame ? subDocFrame->EnsureInnerView() : nullptr; + } else { + rootViewParent = nullptr; + } + if (sibling && sibling->GetPresShell() && + sibling->GetPresShell()->GetViewManager()) { + rootViewSibling = sibling->GetPresShell()->GetViewManager()->GetRootView(); + } else { + rootViewSibling = nullptr; + } + if (rootViewParent && newRootView && + newRootView->GetParent() != rootViewParent) { + nsViewManager* parentVM = rootViewParent->GetViewManager(); + if (parentVM) { + // InsertChild(parent, child, sib, true) inserts the child after + // sib in content order, which is before sib in view order. BUT + // when sib is null it inserts at the end of the the document + // order, i.e., first in view order. But when oldRootSibling is + // null, the old root as at the end of the view list --- last in + // content order --- and we want to call InsertChild(parent, child, + // nullptr, false) in that case. + parentVM->InsertChild(rootViewParent, newRootView, rootViewSibling, + rootViewSibling ? true : false); + + NS_ASSERTION(newRootView->GetNextSibling() == rootViewSibling, + "error in InsertChild"); + } + } + + nsCOMPtr privWinInner = privWin->GetCurrentInnerWindow(); + + // If parent is suspended, increase suspension count. + // This can't be done as early as event suppression since this + // depends on docshell tree. + privWinInner->SyncStateFromParentWindow(); + + // Now that all of the child docshells have been put into place, we can + // restart the timers for the window and all of the child frames. + privWinInner->Resume(); + + // Now that we have found the inner window of the page restored + // from the history, we have to make sure that + // performance.navigation.type is 2. + privWinInner->GetPerformance()->GetDOMTiming()->NotifyRestoreStart(); + + // Restore the refresh URI list. The refresh timers will be restarted + // when EndPageLoad() is called. + mRefreshURIList = refreshURIList; + + // Meta-refresh timers have been restarted for this shell, but not + // for our children. Walk the child shells and restart their timers. + for (auto* childDocLoader : mChildList.ForwardRange()) { + nsCOMPtr child = do_QueryObject(childDocLoader); + if (child) { + child->ResumeRefreshURIs(); + } + } + + // Make sure this presentation is the same size as the previous + // presentation. If this is not the same size we showed it at last time, + // then we need to resize the widget. + + // XXXbryner This interacts poorly with Firefox's infobar. If the old + // presentation had the infobar visible, then we will resize the new + // presentation to that smaller size. However, firing the locationchanged + // event will hide the infobar, which will immediately resize the window + // back to the larger size. A future optimization might be to restore + // the presentation at the "wrong" size, then fire the locationchanged + // event and check whether the docshell's new size is the same as the + // cached viewer size (skipping the resize if they are equal). + + if (newRootView) { + if (!newBounds.IsEmpty() && !newBounds.IsEqualEdges(oldBounds)) { + MOZ_LOG(gPageCacheLog, LogLevel::Debug, + ("resize widget(%d, %d, %d, %d)", newBounds.x, newBounds.y, + newBounds.width, newBounds.height)); + mContentViewer->SetBounds(newBounds); + } else { + nsIScrollableFrame* rootScrollFrame = + presShell->GetRootScrollFrameAsScrollable(); + if (rootScrollFrame) { + rootScrollFrame->PostScrolledAreaEventForCurrentArea(); + } + } + } + + // The FinishRestore call below can kill these, null them out so we don't + // have invalid pointer lying around. + newRootView = rootViewSibling = rootViewParent = nullptr; + newVM = nullptr; + + // If the IsUnderHiddenEmbedderElement() state has been changed, we need to + // update it. + if (oldPresShell && presShell && + presShell->IsUnderHiddenEmbedderElement() != + oldPresShell->IsUnderHiddenEmbedderElement()) { + presShell->SetIsUnderHiddenEmbedderElement( + oldPresShell->IsUnderHiddenEmbedderElement()); + } + + // Simulate the completion of the load. + nsDocShell::FinishRestore(); + + // Restart plugins, and paint the content. + if (presShell) { + presShell->Thaw(); + } + + return privWin->FireDelayedDOMEvents(); +} + +nsresult nsDocShell::CreateContentViewer(const nsACString& aContentType, + nsIRequest* aRequest, + nsIStreamListener** aContentHandler) { + if (DocGroup::TryToLoadIframesInBackground()) { + ResetToFirstLoad(); + } + + *aContentHandler = nullptr; + + if (!mTreeOwner || mIsBeingDestroyed) { + // If we don't have a tree owner, then we're in the process of being + // destroyed. Rather than continue trying to load something, just give up. + return NS_ERROR_DOCSHELL_DYING; + } + + if (!mBrowsingContext->AncestorsAreCurrent()) { + mBrowsingContext->RemoveRootFromBFCacheSync(); + return NS_ERROR_NOT_AVAILABLE; + } + + // Can we check the content type of the current content viewer + // and reuse it without destroying it and re-creating it? + + NS_ASSERTION(mLoadGroup, "Someone ignored return from Init()?"); + + // Instantiate the content viewer object + nsCOMPtr viewer; + nsresult rv = NewContentViewerObj(aContentType, aRequest, mLoadGroup, + aContentHandler, getter_AddRefs(viewer)); + + if (NS_FAILED(rv)) { + return rv; + } + + // Notify the current document that it is about to be unloaded!! + // + // It is important to fire the unload() notification *before* any state + // is changed within the DocShell - otherwise, javascript will get the + // wrong information :-( + // + + if (mSavingOldViewer) { + // We determined that it was safe to cache the document presentation + // at the time we initiated the new load. We need to check whether + // it's still safe to do so, since there may have been DOM mutations + // or new requests initiated. + RefPtr doc = viewer->GetDocument(); + mSavingOldViewer = CanSavePresentation(mLoadType, aRequest, doc); + } + + NS_ASSERTION(!mLoadingURI, "Re-entering unload?"); + + nsCOMPtr aOpenedChannel = do_QueryInterface(aRequest); + if (aOpenedChannel) { + aOpenedChannel->GetURI(getter_AddRefs(mLoadingURI)); + } + FirePageHideNotification(!mSavingOldViewer); + if (mIsBeingDestroyed) { + // Force to stop the newly created orphaned viewer. + viewer->Stop(); + return NS_ERROR_DOCSHELL_DYING; + } + mLoadingURI = nullptr; + + // Set mFiredUnloadEvent = false so that the unload handler for the + // *new* document will fire. + mFiredUnloadEvent = false; + + // we've created a new document so go ahead and call + // OnNewURI(), but don't fire OnLocationChange() + // notifications before we've called Embed(). See bug 284993. + mURIResultedInDocument = true; + bool errorOnLocationChangeNeeded = false; + nsCOMPtr failedChannel = mFailedChannel; + nsCOMPtr failedURI; + + if (mLoadType == LOAD_ERROR_PAGE) { + // We need to set the SH entry and our current URI here and not + // at the moment we load the page. We want the same behavior + // of Stop() as for a normal page load. See bug 514232 for details. + + // Revert mLoadType to load type to state the page load failed, + // following function calls need it. + mLoadType = mFailedLoadType; + + Document* doc = viewer->GetDocument(); + if (doc) { + doc->SetFailedChannel(failedChannel); + } + + nsCOMPtr triggeringPrincipal; + if (failedChannel) { + // Make sure we have a URI to set currentURI. + NS_GetFinalChannelURI(failedChannel, getter_AddRefs(failedURI)); + } else { + // if there is no failed channel we have to explicitly provide + // a triggeringPrincipal for the history entry. + triggeringPrincipal = nsContentUtils::GetSystemPrincipal(); + } + + if (!failedURI) { + failedURI = mFailedURI; + } + if (!failedURI) { + // We need a URI object to store a session history entry, so make up a URI + NS_NewURI(getter_AddRefs(failedURI), "about:blank"); + } + + // When we don't have failedURI, something wrong will happen. See + // bug 291876. + MOZ_ASSERT(failedURI, "We don't have a URI for history APIs."); + + mFailedChannel = nullptr; + mFailedURI = nullptr; + + // Create an shistory entry for the old load. + if (failedURI) { + errorOnLocationChangeNeeded = + OnNewURI(failedURI, failedChannel, triggeringPrincipal, nullptr, + nullptr, nullptr, false, false, false); + } + + // Be sure to have a correct mLSHE, it may have been cleared by + // EndPageLoad. See bug 302115. + ChildSHistory* shistory = GetSessionHistory(); + if (!mozilla::SessionHistoryInParent() && shistory && !mLSHE) { + int32_t idx = shistory->LegacySHistory()->GetRequestedIndex(); + if (idx == -1) { + idx = shistory->Index(); + } + shistory->LegacySHistory()->GetEntryAtIndex(idx, getter_AddRefs(mLSHE)); + } + + mLoadType = LOAD_ERROR_PAGE; + } + + nsCOMPtr finalURI; + // If this a redirect, use the final url (uri) + // else use the original url + // + // Note that this should match what documents do (see Document::Reset). + NS_GetFinalChannelURI(aOpenedChannel, getter_AddRefs(finalURI)); + + bool onLocationChangeNeeded = false; + if (finalURI) { + // Pass false for aCloneSHChildren, since we're loading a new page here. + onLocationChangeNeeded = + OnNewURI(finalURI, aOpenedChannel, nullptr, nullptr, nullptr, nullptr, + false, true, false); + } + + // let's try resetting the load group if we need to... + nsCOMPtr currentLoadGroup; + NS_ENSURE_SUCCESS( + aOpenedChannel->GetLoadGroup(getter_AddRefs(currentLoadGroup)), + NS_ERROR_FAILURE); + + if (currentLoadGroup != mLoadGroup) { + nsLoadFlags loadFlags = 0; + + // Cancel any URIs that are currently loading... + // XXX: Need to do this eventually Stop(); + // + // Retarget the document to this loadgroup... + // + /* First attach the channel to the right loadgroup + * and then remove from the old loadgroup. This + * puts the notifications in the right order and + * we don't null-out mLSHE in OnStateChange() for + * all redirected urls + */ + aOpenedChannel->SetLoadGroup(mLoadGroup); + + // Mark the channel as being a document URI... + aOpenedChannel->GetLoadFlags(&loadFlags); + loadFlags |= nsIChannel::LOAD_DOCUMENT_URI; + nsCOMPtr loadInfo = aOpenedChannel->LoadInfo(); + if (SandboxFlagsImplyCookies(loadInfo->GetSandboxFlags())) { + loadFlags |= nsIRequest::LOAD_DOCUMENT_NEEDS_COOKIE; + } + + aOpenedChannel->SetLoadFlags(loadFlags); + + mLoadGroup->AddRequest(aRequest, nullptr); + if (currentLoadGroup) { + currentLoadGroup->RemoveRequest(aRequest, nullptr, NS_BINDING_RETARGETED); + } + + // Update the notification callbacks, so that progress and + // status information are sent to the right docshell... + aOpenedChannel->SetNotificationCallbacks(this); + } + + if (DocGroup::TryToLoadIframesInBackground()) { + if ((!mContentViewer || GetDocument()->IsInitialDocument()) && IsFrame()) { + // At this point, we know we just created a new iframe document based on + // the response from the server, and we check if it's a cross-domain + // iframe + + RefPtr newDoc = viewer->GetDocument(); + + RefPtr parent = GetInProcessParentDocshell(); + nsCOMPtr parentPrincipal = + parent->GetDocument()->NodePrincipal(); + nsCOMPtr thisPrincipal = newDoc->NodePrincipal(); + + SiteIdentifier parentSite; + SiteIdentifier thisSite; + + nsresult rv = + BasePrincipal::Cast(parentPrincipal)->GetSiteIdentifier(parentSite); + NS_ENSURE_SUCCESS(rv, rv); + + rv = BasePrincipal::Cast(thisPrincipal)->GetSiteIdentifier(thisSite); + NS_ENSURE_SUCCESS(rv, rv); + + if (!parentSite.Equals(thisSite)) { +#ifdef MOZ_GECKO_PROFILER + nsCOMPtr prinURI; + BasePrincipal::Cast(thisPrincipal)->GetURI(getter_AddRefs(prinURI)); + nsPrintfCString marker("Iframe loaded in background: %s", + prinURI->GetSpecOrDefault().get()); + PROFILER_MARKER_TEXT("Background Iframe", DOM, {}, marker); +#endif + SetBackgroundLoadIframe(); + } + } + } + + NS_ENSURE_SUCCESS(Embed(viewer, nullptr, false, + ShouldAddToSessionHistory(finalURI, aOpenedChannel)), + NS_ERROR_FAILURE); + + if (!mBrowsingContext->GetHasLoadedNonInitialDocument()) { + MOZ_ALWAYS_SUCCEEDS(mBrowsingContext->SetHasLoadedNonInitialDocument(true)); + } + + if (TreatAsBackgroundLoad()) { + nsCOMPtr triggerParentCheckDocShell = + NewRunnableMethod("nsDocShell::TriggerParentCheckDocShellIsEmpty", this, + &nsDocShell::TriggerParentCheckDocShellIsEmpty); + nsresult rv = NS_DispatchToCurrentThread(triggerParentCheckDocShell); + NS_ENSURE_SUCCESS(rv, rv); + } + + mSavedRefreshURIList = nullptr; + mSavingOldViewer = false; + mEODForCurrentDocument = false; + + // if this document is part of a multipart document, + // the ID can be used to distinguish it from the other parts. + nsCOMPtr multiPartChannel(do_QueryInterface(aRequest)); + if (multiPartChannel) { + if (PresShell* presShell = GetPresShell()) { + if (Document* doc = presShell->GetDocument()) { + uint32_t partID; + multiPartChannel->GetPartID(&partID); + doc->SetPartID(partID); + } + } + } + + // Give hint to native plevent dispatch mechanism. If a document + // is loading the native plevent dispatch mechanism should favor + // performance over normal native event dispatch priorities. + if (++gNumberOfDocumentsLoading == 1) { + // Hint to favor performance for the plevent notification mechanism. + // We want the pages to load as fast as possible even if its means + // native messages might be starved. + FavorPerformanceHint(true); + } + + if (errorOnLocationChangeNeeded) { + FireOnLocationChange(this, failedChannel, failedURI, + LOCATION_CHANGE_ERROR_PAGE); + } else if (onLocationChangeNeeded) { + uint32_t locationFlags = + (mLoadType & LOAD_CMD_RELOAD) ? uint32_t(LOCATION_CHANGE_RELOAD) : 0; + FireOnLocationChange(this, aRequest, mCurrentURI, locationFlags); + } + + return NS_OK; +} + +nsresult nsDocShell::NewContentViewerObj(const nsACString& aContentType, + nsIRequest* aRequest, + nsILoadGroup* aLoadGroup, + nsIStreamListener** aContentHandler, + nsIContentViewer** aViewer) { + nsCOMPtr aOpenedChannel = do_QueryInterface(aRequest); + + nsCOMPtr docLoaderFactory = + nsContentUtils::FindInternalContentViewer(aContentType); + if (!docLoaderFactory) { + return NS_ERROR_FAILURE; + } + + // Now create an instance of the content viewer nsLayoutDLF makes the + // determination if it should be a "view-source" instead of "view" + nsresult rv = docLoaderFactory->CreateInstance( + "view", aOpenedChannel, aLoadGroup, aContentType, this, nullptr, + aContentHandler, aViewer); + NS_ENSURE_SUCCESS(rv, rv); + + (*aViewer)->SetContainer(this); + return NS_OK; +} + +nsresult nsDocShell::SetupNewViewer(nsIContentViewer* aNewViewer, + WindowGlobalChild* aWindowActor) { + MOZ_ASSERT(!mIsBeingDestroyed); + + // + // Copy content viewer state from previous or parent content viewer. + // + // The following logic is mirrored in nsHTMLDocument::StartDocumentLoad! + // + // Do NOT to maintain a reference to the old content viewer outside + // of this "copying" block, or it will not be destroyed until the end of + // this routine and all