summaryrefslogtreecommitdiffstats
path: root/docshell/base
diff options
context:
space:
mode:
Diffstat (limited to 'docshell/base')
-rw-r--r--docshell/base/BaseHistory.cpp247
-rw-r--r--docshell/base/BaseHistory.h85
-rw-r--r--docshell/base/BrowsingContext.cpp3905
-rw-r--r--docshell/base/BrowsingContext.h1469
-rw-r--r--docshell/base/BrowsingContextGroup.cpp570
-rw-r--r--docshell/base/BrowsingContextGroup.h318
-rw-r--r--docshell/base/BrowsingContextWebProgress.cpp443
-rw-r--r--docshell/base/BrowsingContextWebProgress.h103
-rw-r--r--docshell/base/CanonicalBrowsingContext.cpp3104
-rw-r--r--docshell/base/CanonicalBrowsingContext.h620
-rw-r--r--docshell/base/ChildProcessChannelListener.cpp61
-rw-r--r--docshell/base/ChildProcessChannelListener.h54
-rw-r--r--docshell/base/IHistory.h175
-rw-r--r--docshell/base/LoadContext.cpp236
-rw-r--r--docshell/base/LoadContext.h68
-rw-r--r--docshell/base/SerializedLoadContext.cpp87
-rw-r--r--docshell/base/SerializedLoadContext.h96
-rw-r--r--docshell/base/SyncedContext.h402
-rw-r--r--docshell/base/SyncedContextInlines.h359
-rw-r--r--docshell/base/URIFixup.sys.mjs1306
-rw-r--r--docshell/base/WindowContext.cpp697
-rw-r--r--docshell/base/WindowContext.h429
-rw-r--r--docshell/base/crashtests/1257730-1.html25
-rw-r--r--docshell/base/crashtests/1331295.html25
-rw-r--r--docshell/base/crashtests/1341657.html18
-rw-r--r--docshell/base/crashtests/1584467.html12
-rw-r--r--docshell/base/crashtests/1614211-1.html15
-rw-r--r--docshell/base/crashtests/1617315-1.html8
-rw-r--r--docshell/base/crashtests/1667491.html16
-rw-r--r--docshell/base/crashtests/1667491_1.html21
-rw-r--r--docshell/base/crashtests/1672873.html6
-rw-r--r--docshell/base/crashtests/1690169-1.html11
-rw-r--r--docshell/base/crashtests/1753136.html2
-rw-r--r--docshell/base/crashtests/1804803.html13
-rw-r--r--docshell/base/crashtests/1804803.sjs18
-rw-r--r--docshell/base/crashtests/369126-1.html16
-rw-r--r--docshell/base/crashtests/40929-1-inner.html14
-rw-r--r--docshell/base/crashtests/40929-1.html6
-rw-r--r--docshell/base/crashtests/430124-1.html5
-rw-r--r--docshell/base/crashtests/430628-1.html8
-rw-r--r--docshell/base/crashtests/432114-1.html8
-rw-r--r--docshell/base/crashtests/432114-2.html21
-rw-r--r--docshell/base/crashtests/436900-1-inner.html21
-rw-r--r--docshell/base/crashtests/436900-1.html8
-rw-r--r--docshell/base/crashtests/436900-2-inner.html21
-rw-r--r--docshell/base/crashtests/436900-2.html8
-rw-r--r--docshell/base/crashtests/443655.html15
-rw-r--r--docshell/base/crashtests/500328-1.html17
-rw-r--r--docshell/base/crashtests/514779-1.xhtml9
-rw-r--r--docshell/base/crashtests/614499-1.html20
-rw-r--r--docshell/base/crashtests/678872-1.html36
-rw-r--r--docshell/base/crashtests/914521.html38
-rw-r--r--docshell/base/crashtests/crashtests.list25
-rw-r--r--docshell/base/crashtests/file_432114-2.xhtml1
-rw-r--r--docshell/base/metrics.yaml29
-rw-r--r--docshell/base/moz.build126
-rw-r--r--docshell/base/nsAboutRedirector.cpp318
-rw-r--r--docshell/base/nsAboutRedirector.h26
-rw-r--r--docshell/base/nsCTooltipTextProvider.h15
-rw-r--r--docshell/base/nsDSURIContentListener.cpp297
-rw-r--r--docshell/base/nsDSURIContentListener.h100
-rw-r--r--docshell/base/nsDocShell.cpp13763
-rw-r--r--docshell/base/nsDocShell.h1366
-rw-r--r--docshell/base/nsDocShellEditorData.cpp139
-rw-r--r--docshell/base/nsDocShellEditorData.h66
-rw-r--r--docshell/base/nsDocShellEnumerator.cpp85
-rw-r--r--docshell/base/nsDocShellEnumerator.h39
-rw-r--r--docshell/base/nsDocShellLoadState.cpp1325
-rw-r--r--docshell/base/nsDocShellLoadState.h609
-rw-r--r--docshell/base/nsDocShellLoadTypes.h205
-rw-r--r--docshell/base/nsDocShellTelemetryUtils.cpp202
-rw-r--r--docshell/base/nsDocShellTelemetryUtils.h22
-rw-r--r--docshell/base/nsDocShellTreeOwner.cpp1337
-rw-r--r--docshell/base/nsDocShellTreeOwner.h111
-rw-r--r--docshell/base/nsIDocShell.idl766
-rw-r--r--docshell/base/nsIDocShellTreeItem.idl171
-rw-r--r--docshell/base/nsIDocShellTreeOwner.idl113
-rw-r--r--docshell/base/nsIDocumentLoaderFactory.idl39
-rw-r--r--docshell/base/nsIDocumentViewer.idl318
-rw-r--r--docshell/base/nsIDocumentViewerEdit.idl36
-rw-r--r--docshell/base/nsILoadContext.idl148
-rw-r--r--docshell/base/nsILoadURIDelegate.idl35
-rw-r--r--docshell/base/nsIPrivacyTransitionObserver.idl11
-rw-r--r--docshell/base/nsIReflowObserver.idl31
-rw-r--r--docshell/base/nsIRefreshURI.idl52
-rw-r--r--docshell/base/nsIScrollObserver.h45
-rw-r--r--docshell/base/nsITooltipListener.idl44
-rw-r--r--docshell/base/nsITooltipTextProvider.idl44
-rw-r--r--docshell/base/nsIURIFixup.idl204
-rw-r--r--docshell/base/nsIWebNavigation.idl415
-rw-r--r--docshell/base/nsIWebNavigationInfo.idl55
-rw-r--r--docshell/base/nsIWebPageDescriptor.idl30
-rw-r--r--docshell/base/nsPingListener.cpp345
-rw-r--r--docshell/base/nsPingListener.h48
-rw-r--r--docshell/base/nsRefreshTimer.cpp49
-rw-r--r--docshell/base/nsRefreshTimer.h39
-rw-r--r--docshell/base/nsWebNavigationInfo.cpp64
-rw-r--r--docshell/base/nsWebNavigationInfo.h34
98 files changed, 38637 insertions, 0 deletions
diff --git a/docshell/base/BaseHistory.cpp b/docshell/base/BaseHistory.cpp
new file mode 100644
index 0000000000..3932711b5b
--- /dev/null
+++ b/docshell/base/BaseHistory.cpp
@@ -0,0 +1,247 @@
+/* -*- 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, "cached-favicon"_ns,
+ "chrome"_ns, "data"_ns, "imap"_ns,
+ "javascript"_ns, "mailbox"_ns, "news"_ns,
+ "page-icon"_ns, "resource"_ns, "view-source"_ns,
+ "moz-extension"_ns, "moz-page-thumb"_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,
+ dom::ContentParent* aForProcess) {
+ mPendingQueries.WithEntryHandle(aURI, [&](auto&& entry) {
+ auto& set = entry.OrInsertWith([] { return ContentParentSet(); });
+ if (aForProcess) {
+ set.Insert(aForProcess);
+ }
+ });
+ if (mStartPendingVisitedQueriesScheduled) {
+ return;
+ }
+ mStartPendingVisitedQueriesScheduled =
+ NS_SUCCEEDED(NS_DispatchToMainThreadQueue(
+ NS_NewRunnableFunction(
+ "BaseHistory::StartPendingVisitedQueries",
+ [self = RefPtr<BaseHistory>(this)] {
+ self->mStartPendingVisitedQueriesScheduled = false;
+ auto queries = std::move(self->mPendingQueries);
+ self->StartPendingVisitedQueries(std::move(queries));
+ MOZ_DIAGNOSTIC_ASSERT(self->mPendingQueries.IsEmpty());
+ }),
+ EventQueuePriority::Idle));
+}
+
+void BaseHistory::CancelVisitedQueryIfPossible(nsIURI* aURI) {
+ mPendingQueries.Remove(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!");
+ MOZ_ASSERT(aLink, "Must pass a non-null Link!");
+
+ if (!CanStore(aURI)) {
+ aLink->VisitedQueryFinished(/* visited = */ false);
+ return;
+ }
+
+ // Obtain our array of observers for this URI.
+ auto* const links =
+ mTrackedURIs.WithEntryHandle(aURI, [&](auto&& entry) -> ObservingLinks* {
+ MOZ_DIAGNOSTIC_ASSERT(!entry || !entry->mLinks.IsEmpty(),
+ "An empty key was kept around in our hashtable!");
+ if (!entry) {
+ ScheduleVisitedQuery(aURI, nullptr);
+ }
+
+ return &entry.OrInsertWith([] { return ObservingLinks{}; });
+ });
+
+ if (!links) {
+ return;
+ }
+
+ // 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!");
+
+ 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:
+ [[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->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,
+ const ContentParentSet* aListOfProcessesToNotify) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aStatus != VisitedStatus::Unknown);
+
+ NotifyVisitedInThisProcess(aURI, aStatus);
+ if (XRE_IsParentProcess()) {
+ NotifyVisitedFromParent(aURI, aStatus, aListOfProcessesToNotify);
+ }
+}
+
+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);
+ }
+}
+
+void BaseHistory::SendPendingVisitedResultsToChildProcesses() {
+ MOZ_ASSERT(!mPendingResults.IsEmpty());
+
+ mStartPendingResultsScheduled = false;
+
+ auto results = std::move(mPendingResults);
+ MOZ_ASSERT(mPendingResults.IsEmpty());
+
+ nsTArray<ContentParent*> cplist;
+ nsTArray<dom::VisitedQueryResult> resultsForProcess;
+ ContentParent::GetAll(cplist);
+ for (ContentParent* cp : cplist) {
+ resultsForProcess.ClearAndRetainStorage();
+ for (auto& result : results) {
+ if (result.mProcessesToNotify.IsEmpty() ||
+ result.mProcessesToNotify.Contains(cp)) {
+ resultsForProcess.AppendElement(result.mResult);
+ }
+ }
+ if (!resultsForProcess.IsEmpty()) {
+ Unused << NS_WARN_IF(!cp->SendNotifyVisited(resultsForProcess));
+ }
+ }
+}
+
+void BaseHistory::NotifyVisitedFromParent(
+ nsIURI* aURI, VisitedStatus aStatus,
+ const ContentParentSet* aListOfProcessesToNotify) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (aListOfProcessesToNotify && aListOfProcessesToNotify->IsEmpty()) {
+ return;
+ }
+
+ auto& result = *mPendingResults.AppendElement();
+ result.mResult.visited() = aStatus == VisitedStatus::Visited;
+ result.mResult.uri() = aURI;
+ if (aListOfProcessesToNotify) {
+ for (auto* entry : *aListOfProcessesToNotify) {
+ result.mProcessesToNotify.Insert(entry);
+ }
+ }
+
+ 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..f0fa36db99
--- /dev/null
+++ b/docshell/base/BaseHistory.h
@@ -0,0 +1,85 @@
+/* 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"
+#include "nsTHashSet.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 ScheduleVisitedQuery(nsIURI*, dom::ContentParent*) final;
+ void UnregisterVisitedCallback(nsIURI*, dom::Link*) final;
+ void NotifyVisited(nsIURI*, VisitedStatus,
+ const ContentParentSet* = nullptr) 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, const ContentParentSet*);
+ static constexpr const size_t kTrackedUrisInitialSize = 64;
+
+ BaseHistory();
+ ~BaseHistory();
+
+ using ObserverArray = nsTObserverArray<dom::Link*>;
+ struct ObservingLinks {
+ ObserverArray mLinks;
+ VisitedStatus mStatus = VisitedStatus::Unknown;
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ return mLinks.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ }
+ };
+
+ using PendingVisitedQueries = nsTHashMap<nsURIHashKey, ContentParentSet>;
+ struct PendingVisitedResult {
+ dom::VisitedQueryResult mResult;
+ ContentParentSet mProcessesToNotify;
+ };
+ using PendingVisitedResults = nsTArray<PendingVisitedResult>;
+
+ // Starts all the queries in the pending queries list, potentially at the same
+ // time.
+ virtual void StartPendingVisitedQueries(PendingVisitedQueries&&) = 0;
+
+ private:
+ // 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.
+ nsTHashMap<nsURIHashKey, ObservingLinks> 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..141036a86c
--- /dev/null
+++ b/docshell/base/BrowsingContext.cpp
@@ -0,0 +1,3905 @@
+/* -*- 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 "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/AppShutdown.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/BrowserHost.h"
+#include "mozilla/dom/BrowserChild.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/MediaDevices.h"
+#include "mozilla/dom/PopupBlocker.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/SessionStoreChild.h"
+#include "mozilla/dom/SessionStorageManager.h"
+#include "mozilla/dom/StructuredCloneTags.h"
+#include "mozilla/dom/UserActivationIPCUtils.h"
+#include "mozilla/dom/WindowBinding.h"
+#include "mozilla/dom/WindowContext.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/ipc/ProtocolUtils.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_media.h"
+#include "mozilla/StaticPrefs_page_load.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/URLQueryStringStripper.h"
+#include "mozilla/EventStateManager.h"
+#include "nsIURIFixup.h"
+#include "nsIXULRuntime.h"
+
+#include "nsDocShell.h"
+#include "nsDocShellLoadState.h"
+#include "nsFocusManager.h"
+#include "nsGlobalWindowInner.h"
+#include "nsGlobalWindowOuter.h"
+#include "PresShell.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<mozilla::dom::OrientationType>
+ : public ContiguousEnumSerializer<
+ mozilla::dom::OrientationType,
+ mozilla::dom::OrientationType::Portrait_primary,
+ mozilla::dom::OrientationType::EndGuard_> {};
+
+template <>
+struct ParamTraits<mozilla::dom::DisplayMode>
+ : public ContiguousEnumSerializer<mozilla::dom::DisplayMode,
+ mozilla::dom::DisplayMode::Browser,
+ mozilla::dom::DisplayMode::EndGuard_> {};
+
+template <>
+struct ParamTraits<mozilla::dom::PrefersColorSchemeOverride>
+ : public ContiguousEnumSerializer<
+ mozilla::dom::PrefersColorSchemeOverride,
+ mozilla::dom::PrefersColorSchemeOverride::None,
+ mozilla::dom::PrefersColorSchemeOverride::EndGuard_> {};
+
+template <>
+struct ParamTraits<mozilla::dom::ExplicitActiveStatus>
+ : public ContiguousEnumSerializer<
+ mozilla::dom::ExplicitActiveStatus,
+ mozilla::dom::ExplicitActiveStatus::None,
+ mozilla::dom::ExplicitActiveStatus::EndGuard_> {};
+
+// Allow serialization and deserialization of TouchEventsOverride over IPC
+template <>
+struct ParamTraits<mozilla::dom::TouchEventsOverride>
+ : public ContiguousEnumSerializer<
+ mozilla::dom::TouchEventsOverride,
+ mozilla::dom::TouchEventsOverride::Disabled,
+ mozilla::dom::TouchEventsOverride::EndGuard_> {};
+
+template <>
+struct ParamTraits<mozilla::dom::EmbedderColorSchemes> {
+ using paramType = mozilla::dom::EmbedderColorSchemes;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.mUsed);
+ WriteParam(aWriter, aParam.mPreferred);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return ReadParam(aReader, &aResult->mUsed) &&
+ ReadParam(aReader, &aResult->mPreferred);
+ }
+};
+
+} // 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<BrowsingContext>;
+
+extern mozilla::LazyLogModule gUserInteractionPRLog;
+
+#define USER_ACTIVATION_LOG(msg, ...) \
+ MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
+
+static LazyLogModule gBrowsingContextLog("BrowsingContext");
+static LazyLogModule gBrowsingContextSyncLog("BrowsingContextSync");
+
+typedef nsTHashMap<nsUint64HashKey, BrowsingContext*> BrowsingContextMap;
+
+// All BrowsingContexts indexed by Id
+static StaticAutoPtr<BrowsingContextMap> sBrowsingContexts;
+// Top-level Content BrowsingContexts only, indexed by BrowserId instead of Id
+static StaticAutoPtr<BrowsingContextMap> 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->InsertOrUpdate(aBrowsingContext->Id(), aBrowsingContext);
+ if (aBrowsingContext->IsTopContent()) {
+ sCurrentTopByBrowserId->InsertOrUpdate(aBrowsingContext->BrowserId(),
+ aBrowsingContext);
+ }
+
+ aBrowsingContext->Group()->Register(aBrowsingContext);
+}
+
+// static
+void BrowsingContext::UpdateCurrentTopByBrowserId(
+ BrowsingContext* aNewBrowsingContext) {
+ if (aNewBrowsingContext->IsTopContent()) {
+ sCurrentTopByBrowserId->InsertOrUpdate(aNewBrowsingContext->BrowserId(),
+ aNewBrowsingContext);
+ }
+}
+
+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;
+}
+
+const BrowsingContext* BrowsingContext::Top() const {
+ const 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() const {
+ 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 */
+LogModule* BrowsingContext::GetSyncLog() { return gBrowsingContextSyncLog; }
+
+/* static */
+already_AddRefed<BrowsingContext> BrowsingContext::Get(uint64_t aId) {
+ return do_AddRef(sBrowsingContexts->Get(aId));
+}
+
+/* static */
+already_AddRefed<BrowsingContext> BrowsingContext::GetCurrentTopByBrowserId(
+ uint64_t aBrowserId) {
+ return do_AddRef(sCurrentTopByBrowserId->Get(aBrowserId));
+}
+
+/* static */
+already_AddRefed<BrowsingContext> 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> BrowsingContext::CreateDetached(
+ nsGlobalWindowInner* aParent, BrowsingContext* aOpener,
+ BrowsingContextGroup* aSpecificGroup, const nsAString& aName, Type aType,
+ bool aIsPopupRequested, 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<BrowsingContext> parentBC =
+ aParent ? aParent->GetBrowsingContext() : nullptr;
+ RefPtr<WindowContext> parentWC =
+ aParent ? aParent->GetWindowContext() : nullptr;
+ BrowsingContext* inherit = parentBC ? parentBC.get() : aOpener;
+
+ // Determine which BrowsingContextGroup this context should be created in.
+ RefPtr<BrowsingContextGroup> 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.Get<IDX_Name>() = 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.Get<IDX_OpenerId>() = aOpener->Id();
+ fields.Get<IDX_HadOriginalOpener>() = true;
+
+ if (aType == Type::Chrome && !aParent) {
+ // See SetOpener for why we do this inheritance.
+ fields.Get<IDX_PrefersColorSchemeOverride>() =
+ aOpener->Top()->GetPrefersColorSchemeOverride();
+ }
+ }
+
+ if (aParent) {
+ MOZ_DIAGNOSTIC_ASSERT(parentBC->Group() == group);
+ MOZ_DIAGNOSTIC_ASSERT(parentBC->mType == aType);
+ fields.Get<IDX_EmbedderInnerWindowId>() = aParent->WindowID();
+ // Non-toplevel content documents are always embededed within content.
+ fields.Get<IDX_EmbeddedInContentDocument>() =
+ parentBC->mType == Type::Content;
+
+ // 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.Get<IDX_AncestorLoading>() =
+ parentBC->GetAncestorLoading() ||
+ readystate == Document::ReadyState::READYSTATE_LOADING ||
+ readystate == Document::ReadyState::READYSTATE_INTERACTIVE;
+ }
+
+ fields.Get<IDX_BrowserId>() =
+ parentBC ? parentBC->GetBrowserId() : nsContentUtils::GenerateBrowserId();
+
+ fields.Get<IDX_OpenerPolicy>() = 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.Get<IDX_OpenerPolicy>() = aOpener->Top()->GetOpenerPolicy();
+
+ // If we inherit a policy which is potentially cross-origin isolated, we
+ // must be in a potentially cross-origin isolated BCG.
+ bool isPotentiallyCrossOriginIsolated =
+ fields.Get<IDX_OpenerPolicy>() ==
+ nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP;
+ MOZ_RELEASE_ASSERT(isPotentiallyCrossOriginIsolated ==
+ group->IsPotentiallyCrossOriginIsolated());
+ } 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);
+ } else if (!aParent && group->IsPotentiallyCrossOriginIsolated()) {
+ // If we're creating a brand-new toplevel BC in a potentially cross-origin
+ // isolated group, it should start out with a strict opener policy.
+ fields.Get<IDX_OpenerPolicy>() =
+ nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP;
+ }
+
+ fields.Get<IDX_HistoryID>() = nsID::GenerateUUID();
+ fields.Get<IDX_ExplicitActive>() = [&] {
+ if (parentBC || aType == Type::Content) {
+ // Non-root browsing-contexts inherit their status from its parent.
+ // Top-content either gets managed by the top chrome, or gets manually
+ // managed by the front-end (see ManuallyManagesActiveness). In any case
+ // we want to start off as inactive.
+ return ExplicitActiveStatus::None;
+ }
+ // Chrome starts as active.
+ return ExplicitActiveStatus::Active;
+ }();
+
+ fields.Get<IDX_FullZoom>() = parentBC ? parentBC->FullZoom() : 1.0f;
+ fields.Get<IDX_TextZoom>() = parentBC ? parentBC->TextZoom() : 1.0f;
+
+ bool allowContentRetargeting =
+ inherit ? inherit->GetAllowContentRetargetingOnChildren() : true;
+ fields.Get<IDX_AllowContentRetargeting>() = allowContentRetargeting;
+ fields.Get<IDX_AllowContentRetargetingOnChildren>() = allowContentRetargeting;
+
+ // Assume top allows fullscreen for its children unless otherwise stated.
+ // Subframes start with it false unless otherwise noted in SetEmbedderElement.
+ fields.Get<IDX_FullscreenAllowedByOwner>() = !aParent;
+
+ fields.Get<IDX_DefaultLoadFlags>() =
+ inherit ? inherit->GetDefaultLoadFlags() : nsIRequest::LOAD_NORMAL;
+
+ fields.Get<IDX_OrientationLock>() = mozilla::hal::ScreenOrientation::None;
+
+ fields.Get<IDX_UseGlobalHistory>() =
+ inherit ? inherit->GetUseGlobalHistory() : false;
+
+ fields.Get<IDX_UseErrorPages>() = true;
+
+ fields.Get<IDX_TouchEventsOverrideInternal>() = TouchEventsOverride::None;
+
+ fields.Get<IDX_AllowJavascript>() =
+ inherit ? inherit->GetAllowJavascript() : true;
+
+ fields.Get<IDX_IsPopupRequested>() = aIsPopupRequested;
+
+ if (!parentBC) {
+ fields.Get<IDX_ShouldDelayMediaFromStart>() =
+ StaticPrefs::media_block_autoplay_until_in_foreground();
+ }
+
+ RefPtr<BrowsingContext> 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<nsIRequestContextService> rcsvc =
+ net::RequestContextService::GetOrCreate();
+ if (rcsvc) {
+ nsCOMPtr<nsIRequestContext> requestContext;
+ nsresult rv = rcsvc->NewRequestContext(getter_AddRefs(requestContext));
+ if (NS_SUCCEEDED(rv) && requestContext) {
+ context->mRequestContextId = requestContext->GetID();
+ }
+ }
+
+ return context.forget();
+}
+
+already_AddRefed<BrowsingContext> BrowsingContext::CreateIndependent(
+ Type aType) {
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess(),
+ "BCs created in the content process must be related to "
+ "some BrowserChild");
+ RefPtr<BrowsingContext> bc(
+ CreateDetached(nullptr, nullptr, nullptr, u""_ns, aType, false));
+ 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 */
+mozilla::ipc::IPCResult 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<WindowContext> parent = aInit.GetParent();
+
+ RefPtr<BrowsingContext> 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;
+ context->mChildOffset = aInit.mChildOffset;
+ 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);
+
+ return 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),
+ mIsInBFCache(false),
+ mCanExecuteScripts(true),
+ mChildOffset(0) {
+ 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);
+ }
+
+ RecomputeCanExecuteScripts();
+ ClearCachedValuesOfLocations();
+}
+
+// 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<JSObject*> 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 (mParentWindow && !mParentWindow->IsCurrent()) {
+ 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.
+ return Canonical()
+ ->TopCrossChromeBoundary()
+ ->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()));
+ txn.SetEmbeddedInContentDocument(
+ aEmbedder->OwnerDoc()->IsContentDocument());
+ if (nsCOMPtr<nsPIDOMWindowInner> inner =
+ do_QueryInterface(aEmbedder->GetOwnerGlobal())) {
+ txn.SetEmbedderInnerWindowId(inner->WindowID());
+ }
+ txn.SetFullscreenAllowedByOwner(OwnerAllowsFullscreen(*aEmbedder));
+ if (XRE_IsParentProcess() && aEmbedder->IsXULElement() && IsTopContent()) {
+ nsAutoString messageManagerGroup;
+ aEmbedder->GetAttr(nsGkAtoms::messagemanagergroup, messageManagerGroup);
+ txn.SetMessageManagerGroup(messageManagerGroup);
+ txn.SetUseGlobalHistory(
+ !aEmbedder->HasAttr(nsGkAtoms::disableglobalhistory));
+ if (!aEmbedder->HasAttr(nsGkAtoms::manualactiveness)) {
+ // We're active iff the parent cross-chrome-boundary is active. Note we
+ // can't just use this->Canonical()->GetParentCrossChromeBoundary here,
+ // since mEmbedderElement is still null at this point.
+ RefPtr bc = aEmbedder->OwnerDoc()->GetBrowsingContext();
+ const bool isActive = bc && bc->IsActive();
+ txn.SetExplicitActive(isActive ? ExplicitActiveStatus::Active
+ : ExplicitActiveStatus::Inactive);
+ if (auto* bp = Canonical()->GetBrowserParent()) {
+ bp->SetRenderLayers(isActive);
+ }
+ }
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(txn.Commit(this));
+ }
+
+ if (XRE_IsParentProcess() && IsTopContent()) {
+ Canonical()->MaybeSetPermanentKey(aEmbedder);
+ }
+
+ mEmbedderElement = aEmbedder;
+
+ if (mEmbedderElement) {
+ if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) {
+ obs->NotifyWhenScriptSafe(ToSupports(this),
+ "browsing-context-did-set-embedder", nullptr);
+ }
+
+ if (IsEmbedderTypeObjectOrEmbed()) {
+ Unused << SetSyntheticDocumentContainer(true);
+ }
+ }
+}
+
+bool BrowsingContext::IsEmbedderTypeObjectOrEmbed() {
+ if (const Maybe<nsString>& type = GetEmbedderElementType()) {
+ return nsGkAtoms::object->Equals(*type) || nsGkAtoms::embed->Equals(*type);
+ }
+ return false;
+}
+
+void BrowsingContext::Embed() {
+ if (auto* frame = HTMLIFrameElement::FromNode(mEmbedderElement)) {
+ frame->BindToBrowsingContext(this);
+ }
+}
+
+mozilla::ipc::IPCResult BrowsingContext::Attach(bool aFromIPC,
+ ContentParent* aOriginProcess) {
+ MOZ_DIAGNOSTIC_ASSERT(!mEverAttached);
+ MOZ_DIAGNOSTIC_ASSERT_IF(aFromIPC, aOriginProcess || XRE_IsContentProcess());
+ 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);
+
+ if (mGroup->IsPotentiallyCrossOriginIsolated() !=
+ (Top()->GetOpenerPolicy() ==
+ nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP)) {
+ MOZ_DIAGNOSTIC_ASSERT(aFromIPC);
+ if (aFromIPC) {
+ auto* actor = aOriginProcess
+ ? static_cast<mozilla::ipc::IProtocol*>(aOriginProcess)
+ : static_cast<mozilla::ipc::IProtocol*>(
+ ContentChild::GetSingleton());
+ return IPC_FAIL(
+ actor,
+ "Invalid CrossOriginIsolated state in BrowsingContext::Attach call");
+ } else {
+ MOZ_CRASH(
+ "Invalid CrossOriginIsolated state in BrowsingContext::Attach call");
+ }
+ }
+
+ AssertCoherentLoadContext();
+
+ // Add ourselves either to our parent or BrowsingContextGroup's child list.
+ // Important: We shouldn't return IPC_FAIL after this point, since the
+ // BrowsingContext will have already been added to the tree.
+ 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");
+ MOZ_DIAGNOSTIC_ASSERT(mParentWindow->GetWindowGlobalChild(),
+ "local attach call with oop parent window");
+ MOZ_DIAGNOSTIC_ASSERT(mParentWindow->GetWindowGlobalChild()->CanSend(),
+ "local attach call with dead parent window");
+ }
+ mChildOffset =
+ mCreatedDynamically ? -1 : mParentWindow->Children().Length();
+ mParentWindow->AppendChildBrowsingContext(this);
+ RecomputeCanExecuteScripts();
+ } else {
+ mGroup->Toplevels().AppendElement(this);
+ }
+
+ if (GetIsPopupSpam()) {
+ PopupBlocker::RegisterOpenPopupSpam();
+ }
+
+ if (IsTop() && GetHasSessionHistory() && !mChildSessionHistory) {
+ CreateChildSHistory();
+ }
+
+ // Why the context is being attached. This will always be "attach" in the
+ // content process, but may be "replace" if it's known the context being
+ // replaced in the parent process.
+ const char16_t* why = u"attach";
+
+ if (XRE_IsContentProcess() && !aFromIPC) {
+ // Send attach to our parent if we need to.
+ ContentChild::GetSingleton()->SendCreateBrowsingContext(
+ mGroup->Id(), GetIPCInitializer());
+ } else if (XRE_IsParentProcess()) {
+ // If this window was created as a subframe by a content process, it must be
+ // being hosted within the same BrowserParent as its mParentWindow.
+ // Toplevel BrowsingContexts created by content have their BrowserParent
+ // configured during `RecvConstructPopupBrowser`.
+ if (mParentWindow && aOriginProcess) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ mParentWindow->Canonical()->GetContentParent() == aOriginProcess,
+ "Creator process isn't the same as our embedder?");
+ Canonical()->SetCurrentBrowserParent(
+ mParentWindow->Canonical()->GetBrowserParent());
+ }
+
+ 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 (IsTop() && IsContent() && Canonical()->GetWebProgress()) {
+ why = u"replace";
+ }
+
+ // We want to create a BrowsingContextWebProgress for all content
+ // BrowsingContexts.
+ if (IsContent() && !Canonical()->mWebProgress) {
+ Canonical()->mWebProgress = new BrowsingContextWebProgress(Canonical());
+ }
+ }
+
+ if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) {
+ obs->NotifyWhenScriptSafe(ToSupports(this), "browsing-context-attached",
+ why);
+ }
+
+ if (XRE_IsParentProcess()) {
+ Canonical()->CanonicalAttach();
+ }
+ return IPC_OK();
+}
+
+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);
+
+ if (XRE_IsParentProcess()) {
+ Canonical()->AddPendingDiscard();
+ }
+ auto callListeners =
+ MakeScopeExit([&, listeners = std::move(mDiscardListeners), id = Id()] {
+ for (const auto& listener : listeners) {
+ listener(id);
+ }
+ if (XRE_IsParentProcess()) {
+ Canonical()->RemovePendingDiscard();
+ }
+ });
+
+ nsCOMPtr<nsIRequestContextService> 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)) {
+ MOZ_ASSERT_UNREACHABLE();
+ return;
+ }
+
+ if (mParentWindow) {
+ mParentWindow->RemoveChildBrowsingContext(this);
+ } else {
+ mGroup->Toplevels().RemoveElement(this);
+ }
+
+ if (XRE_IsParentProcess()) {
+ RefPtr<CanonicalBrowsingContext> self{Canonical()};
+ 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.
+ bool doDiscard = !Canonical()->IsEmbeddedInProcess(aParent->ChildID()) &&
+ !Canonical()->IsOwnedByProcess(aParent->ChildID());
+
+ // 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();
+ self->AddPendingDiscard();
+ auto callback = [self](auto) {
+ self->mGroup->RemoveKeepAlive();
+ self->RemovePendingDiscard();
+ };
+
+ aParent->SendDiscardBrowsingContext(this, doDiscard, callback, callback);
+ });
+ } else {
+ // Hold a strong reference to ourself until the responses come back to
+ // ensure the BrowsingContext isn't cleaned up before the parent process
+ // acknowledges the discard request.
+ auto callback = [self = RefPtr{this}](auto) {};
+ ContentChild::GetSingleton()->SendDiscardBrowsingContext(
+ this, !aFromIPC, callback, callback);
+ }
+
+ mGroup->Unregister(this);
+ UnregisterBrowserId(this);
+ mIsDiscarded = true;
+
+ if (XRE_IsParentProcess()) {
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm) {
+ fm->BrowsingContextDetached(this);
+ }
+ }
+
+ if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) {
+ // Why the context is being discarded. This will always be "discard" in the
+ // content process, but may be "replace" if it's known the context being
+ // replaced in the parent process.
+ const char16_t* why = u"discard";
+ if (XRE_IsParentProcess() && IsTop() && !Canonical()->GetWebProgress()) {
+ why = u"replace";
+ }
+ obs->NotifyObservers(ToSupports(this), "browsing-context-discarded", why);
+ }
+
+ // NOTE: Doesn't use SetClosed, as it will be set in all processes
+ // automatically by calls to Detach()
+ mFields.SetWithoutSyncing<IDX_Closed>(true);
+
+ if (GetIsPopupSpam()) {
+ PopupBlocker::UnregisterOpenPopupSpam();
+ // NOTE: Doesn't use SetIsPopupSpam, as it will be set all processes
+ // automatically.
+ mFields.SetWithoutSyncing<IDX_IsPopupSpam>(false);
+ }
+
+ AssertOriginAttributesMatchPrivateBrowsing();
+
+ if (XRE_IsParentProcess()) {
+ Canonical()->CanonicalDiscard();
+ }
+}
+
+void BrowsingContext::AddDiscardListener(
+ std::function<void(uint64_t)>&& aListener) {
+ if (mIsDiscarded) {
+ aListener(Id());
+ return;
+ }
+ mDiscardListeners.AppendElement(std::move(aListener));
+}
+
+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();
+
+ ClearCachedValuesOfLocations();
+
+ // 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::IsTargetable() const {
+ return !GetClosed() && AncestorsAreCurrent();
+}
+
+void BrowsingContext::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));
+
+ if (IsChrome() && IsTop() && aOpener) {
+ // Inherit color scheme overrides from parent window. This is to inherit the
+ // color scheme of dark themed PBM windows in dialogs opened by such
+ // windows.
+ auto openerOverride = aOpener->Top()->PrefersColorSchemeOverride();
+ if (openerOverride != PrefersColorSchemeOverride()) {
+ MOZ_ALWAYS_SUCCEEDS(SetPrefersColorSchemeOverride(openerOverride));
+ }
+ }
+}
+
+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->IsCurrent() || wc->IsDiscarded()) {
+ return false;
+ }
+
+ bc = wc->GetBrowsingContext();
+ } else {
+ return true;
+ }
+ }
+}
+
+bool BrowsingContext::IsInBFCache() const {
+ if (mozilla::SessionHistoryInParent()) {
+ return mIsInBFCache;
+ }
+ return mParentWindow &&
+ mParentWindow->TopWindowContext()->GetWindowStateSaved();
+}
+
+Span<RefPtr<BrowsingContext>> BrowsingContext::Children() const {
+ if (WindowContext* current = mCurrentWindowContext) {
+ return current->Children();
+ }
+ return Span<RefPtr<BrowsingContext>>();
+}
+
+void BrowsingContext::GetChildren(
+ nsTArray<RefPtr<BrowsingContext>>& aChildren) {
+ aChildren.AppendElements(Children());
+}
+
+Span<RefPtr<BrowsingContext>> BrowsingContext::NonSyntheticChildren() const {
+ if (WindowContext* current = mCurrentWindowContext) {
+ return current->NonSyntheticChildren();
+ }
+ return Span<RefPtr<BrowsingContext>>();
+}
+
+void BrowsingContext::GetWindowContexts(
+ nsTArray<RefPtr<WindowContext>>& 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<IDX_CurrentInnerWindowId>());
+ 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<IDX_CurrentInnerWindowId>());
+ MOZ_DIAGNOSTIC_ASSERT(mCurrentWindowContext == nullptr);
+ }
+}
+
+void BrowsingContext::PreOrderWalkVoid(
+ const std::function<void(BrowsingContext*)>& aCallback) {
+ aCallback(this);
+
+ AutoTArray<RefPtr<BrowsingContext>, 8> children;
+ children.AppendElements(Children());
+
+ for (auto& child : children) {
+ child->PreOrderWalkVoid(aCallback);
+ }
+}
+
+BrowsingContext::WalkFlag BrowsingContext::PreOrderWalkFlag(
+ const std::function<WalkFlag(BrowsingContext*)>& aCallback) {
+ switch (aCallback(this)) {
+ case WalkFlag::Skip:
+ return WalkFlag::Next;
+ case WalkFlag::Stop:
+ return WalkFlag::Stop;
+ case WalkFlag::Next:
+ default:
+ break;
+ }
+
+ AutoTArray<RefPtr<BrowsingContext>, 8> children;
+ children.AppendElements(Children());
+
+ for (auto& child : children) {
+ switch (child->PreOrderWalkFlag(aCallback)) {
+ case WalkFlag::Stop:
+ return WalkFlag::Stop;
+ default:
+ break;
+ }
+ }
+
+ return WalkFlag::Next;
+}
+
+void BrowsingContext::PostOrderWalk(
+ const std::function<void(BrowsingContext*)>& aCallback) {
+ AutoTArray<RefPtr<BrowsingContext>, 8> children;
+ children.AppendElements(Children());
+
+ for (auto& child : children) {
+ child->PostOrderWalk(aCallback);
+ }
+
+ aCallback(this);
+}
+
+void BrowsingContext::GetAllBrowsingContextsInSubtree(
+ nsTArray<RefPtr<BrowsingContext>>& aBrowsingContexts) {
+ PreOrderWalk([&](BrowsingContext* aContext) {
+ aBrowsingContexts.AppendElement(aContext);
+ });
+}
+
+BrowsingContext* BrowsingContext::FindChildWithName(
+ const nsAString& aName, WindowGlobalChild& aRequestingWindow) {
+ if (aName.IsEmpty()) {
+ // You can't find a browsing context with the empty name.
+ return nullptr;
+ }
+
+ for (BrowsingContext* child : NonSyntheticChildren()) {
+ if (child->NameEquals(aName) && aRequestingWindow.CanNavigate(child) &&
+ child->IsTargetable()) {
+ return child;
+ }
+ }
+
+ return nullptr;
+}
+
+BrowsingContext* BrowsingContext::FindWithSpecialName(
+ const nsAString& aName, WindowGlobalChild& aRequestingWindow) {
+ // 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 aRequestingWindow.CanNavigate(parent) ? parent : nullptr;
+ }
+ return this;
+ }
+
+ if (aName.LowerCaseEqualsLiteral("_top")) {
+ BrowsingContext* top = Top();
+
+ return aRequestingWindow.CanNavigate(top) ? top : nullptr;
+ }
+
+ return nullptr;
+}
+
+BrowsingContext* BrowsingContext::FindWithNameInSubtree(
+ const nsAString& aName, WindowGlobalChild* aRequestingWindow) {
+ MOZ_DIAGNOSTIC_ASSERT(!aName.IsEmpty());
+
+ if (NameEquals(aName) &&
+ (!aRequestingWindow || aRequestingWindow->CanNavigate(this)) &&
+ IsTargetable()) {
+ return this;
+ }
+
+ for (BrowsingContext* child : NonSyntheticChildren()) {
+ if (BrowsingContext* found =
+ child->FindWithNameInSubtree(aName, aRequestingWindow)) {
+ return found;
+ }
+ }
+
+ return nullptr;
+}
+
+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<Document> 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<BrowsingContext> 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. We assume there is a
+ // valid transient user gesture interaction if this check happens in the
+ // target process given that we have checked in the triggering process
+ // already.
+ if (!(sandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION_USER_ACTIVATION) &&
+ mCurrentWindowContext &&
+ (!mCurrentWindowContext->IsInProcess() ||
+ mCurrentWindowContext->HasValidTransientUserGestureActivation()) &&
+ aTarget == Top()) {
+ return false;
+ }
+
+ // Otherwise, we are sandboxed from aTarget.
+ return true;
+}
+
+RefPtr<SessionStorageManager> BrowsingContext::GetSessionStorageManager() {
+ RefPtr<SessionStorageManager>& 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));
+ }
+}
+
+std::tuple<nsCOMPtr<nsIPrincipal>, nsCOMPtr<nsIPrincipal>>
+BrowsingContext::GetTriggeringAndInheritPrincipalsForCurrentLoad() {
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal =
+ GetSavedPrincipal(mTriggeringPrincipal);
+ nsCOMPtr<nsIPrincipal> principalToInherit =
+ GetSavedPrincipal(mPrincipalToInherit);
+ return std::make_tuple(triggeringPrincipal, principalToInherit);
+}
+
+nsIPrincipal* BrowsingContext::GetSavedPrincipal(
+ Maybe<PrincipalWithLoadIdentifierTuple> aPrincipalTuple) {
+ if (aPrincipalTuple) {
+ nsCOMPtr<nsIPrincipal> principal;
+ uint64_t loadIdentifier;
+ std::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);
+
+ ClearCachedValuesOfLocations();
+ mLocations.clear();
+}
+
+/* static */
+void BrowsingContext::DiscardFromContentParent(ContentParent* aCP) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (sBrowsingContexts) {
+ AutoTArray<RefPtr<BrowsingContext>, 8> toDiscard;
+ for (const auto& data : sBrowsingContexts->Values()) {
+ auto* bc = data->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<JSObject*> 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::Rooted<JS::Value> 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<BrowsingContext> 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<WindowProxyHolder> 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<WindowProxyHolder> 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<JS::Value> aVal,
+ ErrorResult& aError) {
+ AssertOriginAttributesMatchPrivateBrowsing();
+
+ if (!ToJSValue(aCx, mOriginAttributes, aVal)) {
+ aError.NoteJSContextException(aCx);
+ }
+}
+
+NS_IMETHODIMP BrowsingContext::GetAssociatedWindow(
+ mozIDOMWindowProxy** aAssociatedWindow) {
+ nsCOMPtr<mozIDOMWindowProxy> 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<Element> 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);
+ }
+
+ if (XRE_IsParentProcess()) {
+ Canonical()->AdjustPrivateBrowsingCount(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<JS::Value> 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<BrowsingContext> 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<BrowsingContext> 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<IDX_IsPopupSpam>(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
+
+static bool IsCertainlyAliveForCC(BrowsingContext* aContext) {
+ return aContext->HasKnownLiveWrapper() ||
+ (AppShutdown::GetCurrentShutdownPhase() ==
+ ShutdownPhase::NotInShutdown &&
+ aContext->EverAttached() && !aContext->IsDiscarded());
+}
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(BrowsingContext)
+ if (IsCertainlyAliveForCC(tmp)) {
+ if (tmp->PreservingWrapper()) {
+ tmp->MarkWrapperLive();
+ }
+ return true;
+ }
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(BrowsingContext)
+ return IsCertainlyAliveForCC(tmp) && tmp->HasNothingToTrace(tmp);
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(BrowsingContext)
+ return IsCertainlyAliveForCC(tmp);
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
+
+class RemoteLocationProxy
+ : public RemoteObjectProxy<BrowsingContext::LocationProxy,
+ Location_Binding::sCrossOriginProperties> {
+ public:
+ typedef RemoteObjectProxy Base;
+
+ constexpr RemoteLocationProxy()
+ : RemoteObjectProxy(prototypes::id::Location) {}
+
+ void NoteChildren(JSObject* aProxy,
+ nsCycleCollectionTraversalCallback& aCb) const override {
+ auto location =
+ static_cast<BrowsingContext::LocationProxy*>(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<JSObject*> 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<Document> doc = wc->TopWindowContext()->GetDocument()) {
+ return doc->RemoveFromBFCacheSync();
+ }
+ }
+ return false;
+}
+
+nsresult BrowsingContext::CheckSandboxFlags(nsDocShellLoadState* aLoadState) {
+ const auto& sourceBC = aLoadState->SourceBrowsingContext();
+ if (sourceBC.IsNull()) {
+ return NS_OK;
+ }
+
+ // We might be called after the source BC has been discarded, but before we've
+ // destroyed our in-process instance of the BrowsingContext object in some
+ // situations (e.g. after creating a new pop-up with window.open while the
+ // window is being closed). In these situations we want to still perform the
+ // sandboxing check against our in-process copy. If we've forgotten about the
+ // context already, assume it is sanboxed. (bug 1643450)
+ BrowsingContext* bc = sourceBC.GetMaybeDiscarded();
+ if (!bc || bc->IsSandboxedFrom(this)) {
+ return NS_ERROR_DOM_SECURITY_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");
+ aLoadState->AssertProcessCouldTriggerLoadIfSystem();
+
+ if (mDocShell) {
+ nsCOMPtr<nsIDocShell> docShell = mDocShell;
+ return docShell->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();
+
+ if (net::SchemeIsJavascript(aLoadState->URI())) {
+ if (!XRE_IsParentProcess()) {
+ // Web content should only be able to load javascript: URIs into documents
+ // whose principals the caller principal subsumes, which by definition
+ // excludes any document in a cross-process BrowsingContext.
+ return NS_ERROR_DOM_BAD_CROSS_ORIGIN_URI;
+ }
+ MOZ_DIAGNOSTIC_ASSERT(!sourceBC,
+ "Should never see a cross-process javascript: load "
+ "triggered from content");
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(!sourceBC || sourceBC->Group() == Group());
+ if (sourceBC && sourceBC->IsInProcess()) {
+ nsCOMPtr<nsPIDOMWindowOuter> win(sourceBC->GetDOMWindow());
+ if (WindowGlobalChild* wgc =
+ win->GetCurrentInnerWindow()->GetWindowGlobalChild()) {
+ if (!wgc->CanNavigate(this)) {
+ return NS_ERROR_DOM_PROP_ACCESS_DENIED;
+ }
+ wgc->SendLoadURI(this, mozilla::WrapNotNull(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());
+
+ // 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, mozilla::WrapNotNull(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");
+ aLoadState->AssertProcessCouldTriggerLoadIfSystem();
+
+ if (mDocShell) {
+ RefPtr<nsDocShell> docShell = nsDocShell::Cast(mDocShell);
+ return docShell->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 (net::SchemeIsJavascript(aLoadState->URI())) {
+ if (!XRE_IsParentProcess()) {
+ // Web content should only be able to load javascript: URIs into documents
+ // whose principals the caller principal subsumes, which by definition
+ // excludes any document in a cross-process BrowsingContext.
+ return NS_ERROR_DOM_BAD_CROSS_ORIGIN_URI;
+ }
+ MOZ_DIAGNOSTIC_ASSERT(!sourceBC,
+ "Should never see a cross-process javascript: load "
+ "triggered from content");
+ }
+
+ 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(mozilla::WrapNotNull(aLoadState));
+ } else {
+ MOZ_DIAGNOSTIC_ASSERT(sourceBC);
+ MOZ_DIAGNOSTIC_ASSERT(sourceBC->Group() == Group());
+
+ nsCOMPtr<nsPIDOMWindowOuter> win(sourceBC->GetDOMWindow());
+ WindowGlobalChild* wgc =
+ win->GetCurrentInnerWindow()->GetWindowGlobalChild();
+ if (!wgc || !wgc->CanSend()) {
+ return NS_ERROR_FAILURE;
+ }
+ if (!wgc->CanNavigate(this)) {
+ return NS_ERROR_DOM_PROP_ACCESS_DENIED;
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(
+ SetCurrentLoadIdentifier(Some(aLoadState->GetLoadIdentifier())));
+ wgc->SendInternalLoad(mozilla::WrapNotNull(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;
+ nsCOMPtr<nsIDocShell> docShell = mDocShell;
+ docShell->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 (IsSubframe()) {
+ // .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);
+ }
+}
+
+template <typename FuncT>
+inline bool ApplyToDocumentsForPopup(Document* doc, FuncT func) {
+ // 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.
+ if (func(doc)) {
+ return true;
+ }
+ if (!doc->IsInitialDocument()) {
+ return false;
+ }
+ Document* parentDoc = doc->GetInProcessParentDocument();
+ if (!parentDoc || !parentDoc->NodePrincipal()->Equals(doc->NodePrincipal())) {
+ return false;
+ }
+ return func(parentDoc);
+}
+
+PopupBlocker::PopupControlState BrowsingContext::RevisePopupAbuseLevel(
+ PopupBlocker::PopupControlState aControl) {
+ if (!IsContent()) {
+ return PopupBlocker::openAllowed;
+ }
+
+ RefPtr<Document> doc = GetExtantDocument();
+ 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() ||
+ (doc && doc->HasValidTransientUserGestureActivation())) {
+ // 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 (doc) {
+ auto ConsumeTransientUserActivationForMultiplePopupBlocking =
+ [&]() -> bool {
+ return ApplyToDocumentsForPopup(doc, [](Document* doc) {
+ return doc->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;
+}
+
+void BrowsingContext::GetUserActivationModifiersForPopup(
+ UserActivation::Modifiers* aModifiers) {
+ RefPtr<Document> doc = GetExtantDocument();
+ if (doc) {
+ // Unlike RevisePopupAbuseLevel, modifiers can always be used regardless
+ // of PopupControlState.
+ (void)ApplyToDocumentsForPopup(doc, [&](Document* doc) {
+ return doc->GetTransientUserGestureActivationModifiers(aModifiers);
+ });
+ }
+}
+
+void BrowsingContext::IncrementHistoryEntryCountForBrowsingContext() {
+ Unused << SetHistoryEntryCount(GetHistoryEntryCount() + 1);
+}
+
+std::tuple<bool, bool> BrowsingContext::CanFocusCheck(CallerType aCallerType) {
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (!fm) {
+ return {false, false};
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> caller = do_QueryInterface(GetEntryGlobal());
+ BrowsingContext* callerBC = caller ? caller->GetBrowsingContext() : nullptr;
+ RefPtr<BrowsingContext> 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 =
+ (callerBC ? callerBC : this)
+ ->RevisePopupAbuseLevel(PopupBlocker::GetPopupControlState()) <
+ PopupBlocker::openBlocked;
+ }
+
+ bool isActive = false;
+ if (XRE_IsParentProcess()) {
+ CanonicalBrowsingContext* chromeTop = Canonical()->TopCrossChromeBoundary();
+ nsCOMPtr<nsPIDOMWindowOuter> 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);
+ }
+}
+
+bool BrowsingContext::CanBlurCheck(CallerType aCallerType) {
+ // If dom.disable_window_flip == true, then content should not be allowed
+ // to do blur (this would allow popunders, bug 369306)
+ return aCallerType == CallerType::System ||
+ !Preferences::GetBool("dom.disable_window_flip", true);
+}
+
+void BrowsingContext::Blur(CallerType aCallerType, ErrorResult& aError) {
+ if (!CanBlurCheck(aCallerType)) {
+ return;
+ }
+
+ if (ContentChild* cc = ContentChild::GetSingleton()) {
+ cc->SendWindowBlur(this, aCallerType);
+ } else if (ContentParent* cp = Canonical()->GetContentParent()) {
+ Unused << cp->SendWindowBlur(this, aCallerType);
+ }
+}
+
+Nullable<WindowProxyHolder> BrowsingContext::GetWindow() {
+ if (XRE_IsParentProcess() && !IsInProcess()) {
+ return nullptr;
+ }
+ return WindowProxyHolder(this);
+}
+
+Nullable<WindowProxyHolder> 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<JS::Value> aOpener,
+ ErrorResult& aError) const {
+ RefPtr<BrowsingContext> 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<WindowProxyHolder> BrowsingContext::GetParent(ErrorResult& aError) {
+ if (mIsDiscarded) {
+ return nullptr;
+ }
+
+ if (GetParent()) {
+ return WindowProxyHolder(GetParent());
+ }
+ return WindowProxyHolder(this);
+}
+
+void BrowsingContext::PostMessageMoz(JSContext* aCx,
+ JS::Handle<JS::Value> aMessage,
+ const nsAString& aTargetOrigin,
+ const Sequence<JSObject*>& aTransfer,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError) {
+ if (mIsDiscarded) {
+ return;
+ }
+
+ RefPtr<BrowsingContext> sourceBc;
+ PostMessageData data;
+ data.targetOrigin() = aTargetOrigin;
+ data.subjectPrincipal() = &aSubjectPrincipal;
+ RefPtr<nsGlobalWindowInner> 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<JS::Value> 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 contains a shared memory object, then CloneScope
+ // would return SameProcess. Otherwise, it returns DifferentProcess.
+ if (message.CloneScope() ==
+ StructuredCloneHolder::StructuredCloneScope::DifferentProcess) {
+ ClonedMessageData clonedMessageData;
+ if (!message.BuildClonedMessageData(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.BuildClonedMessageData(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<JS::Value> 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.mChildOffset = mChildOffset;
+ 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<WindowContext> BrowsingContext::IPCInitializer::GetParent() {
+ RefPtr<WindowContext> parent;
+ if (mParentId != 0) {
+ parent = WindowContext::GetById(mParentId);
+ MOZ_RELEASE_ASSERT(parent);
+ }
+ return parent.forget();
+}
+
+already_AddRefed<BrowsingContext> BrowsingContext::IPCInitializer::GetOpener() {
+ RefPtr<BrowsingContext> 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);
+}
+
+template <typename Callback>
+void BrowsingContext::WalkPresContexts(Callback&& aCallback) {
+ PreOrderWalk([&](BrowsingContext* aContext) {
+ if (nsIDocShell* shell = aContext->GetDocShell()) {
+ if (RefPtr pc = shell->GetPresContext()) {
+ aCallback(pc.get());
+ }
+ }
+ });
+}
+
+void BrowsingContext::PresContextAffectingFieldChanged() {
+ WalkPresContexts([&](nsPresContext* aPc) {
+ aPc->RecomputeBrowsingContextDependentData();
+ });
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_SessionStoreEpoch>,
+ uint32_t aOldValue) {
+ if (!mCurrentWindowContext) {
+ return;
+ }
+ SessionStoreChild* sessionStoreChild =
+ SessionStoreChild::From(mCurrentWindowContext->GetWindowGlobalChild());
+ if (!sessionStoreChild) {
+ return;
+ }
+
+ sessionStoreChild->SetEpoch(GetSessionStoreEpoch());
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_GVAudibleAutoplayRequestStatus>) {
+ MOZ_ASSERT(IsTop(),
+ "Should only set GVAudibleAutoplayRequestStatus in the top-level "
+ "browsing context");
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_GVInaudibleAutoplayRequestStatus>) {
+ MOZ_ASSERT(IsTop(),
+ "Should only set GVAudibleAutoplayRequestStatus in the top-level "
+ "browsing context");
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_ExplicitActive>,
+ const ExplicitActiveStatus&,
+ ContentParent* aSource) {
+ return XRE_IsParentProcess() && IsTop() && !aSource;
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_ExplicitActive>,
+ ExplicitActiveStatus aOldValue) {
+ MOZ_ASSERT(IsTop());
+
+ const bool isActive = IsActive();
+ const bool wasActive = [&] {
+ if (aOldValue != ExplicitActiveStatus::None) {
+ return aOldValue == ExplicitActiveStatus::Active;
+ }
+ return GetParent() && GetParent()->IsActive();
+ }();
+
+ if (isActive == wasActive) {
+ return;
+ }
+
+ Group()->UpdateToplevelsSuspendedIfNeeded();
+ if (XRE_IsParentProcess()) {
+ if (BrowserParent* bp = Canonical()->GetBrowserParent()) {
+ bp->RecomputeProcessPriority();
+#if defined(XP_WIN) && defined(ACCESSIBILITY)
+ if (a11y::Compatibility::IsDolphin()) {
+ // update active accessible documents on windows
+ 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
+ }
+
+ // NOTE(emilio): Ideally we'd want to reuse the ExplicitActiveStatus::None
+ // set-up, but that's non-trivial to do because in content processes we
+ // can't access the top-cross-chrome-boundary bc.
+ auto manageTopDescendant = [&](auto* aChild) {
+ if (!aChild->ManuallyManagesActiveness()) {
+ aChild->SetIsActiveInternal(isActive, IgnoreErrors());
+ if (BrowserParent* bp = aChild->GetBrowserParent()) {
+ bp->SetRenderLayers(isActive);
+ }
+ }
+ return CallState::Continue;
+ };
+ Canonical()->CallOnAllTopDescendants(manageTopDescendant,
+ /* aIncludeNestedBrowsers = */ false);
+ }
+
+ PreOrderWalk([&](BrowsingContext* aContext) {
+ if (nsCOMPtr<nsIDocShell> ds = aContext->GetDocShell()) {
+ nsDocShell::Cast(ds)->ActivenessMaybeChanged();
+ }
+ });
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_InRDMPane>, bool aOldValue) {
+ MOZ_ASSERT(IsTop(),
+ "Should only set InRDMPane in the top-level browsing context");
+ if (GetInRDMPane() == aOldValue) {
+ return;
+ }
+ PresContextAffectingFieldChanged();
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_PageAwakeRequestCount>,
+ uint32_t aNewValue, ContentParent* aSource) {
+ return IsTop() && XRE_IsParentProcess() && !aSource;
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_PageAwakeRequestCount>,
+ uint32_t aOldValue) {
+ if (!IsTop() || aOldValue == GetPageAwakeRequestCount()) {
+ return;
+ }
+ Group()->UpdateToplevelsSuspendedIfNeeded();
+}
+
+auto BrowsingContext::CanSet(FieldIndex<IDX_AllowJavascript>, bool aValue,
+ ContentParent* aSource) -> CanSetResult {
+ if (mozilla::SessionHistoryInParent()) {
+ return XRE_IsParentProcess() && !aSource ? CanSetResult::Allow
+ : CanSetResult::Deny;
+ }
+
+ // Without Session History in Parent, session restore code still needs to set
+ // this from content processes.
+ return LegacyRevertIfNotOwningOrParentProcess(aSource);
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_AllowJavascript>, bool aOldValue) {
+ RecomputeCanExecuteScripts();
+}
+
+void BrowsingContext::RecomputeCanExecuteScripts() {
+ const bool old = mCanExecuteScripts;
+ if (!AllowJavascript()) {
+ // Scripting has been explicitly disabled on our BrowsingContext.
+ mCanExecuteScripts = false;
+ } else if (GetParentWindowContext()) {
+ // Otherwise, inherit parent.
+ mCanExecuteScripts = GetParentWindowContext()->CanExecuteScripts();
+ } else {
+ // Otherwise, we're the root of the tree, and we haven't explicitly disabled
+ // script. Allow.
+ mCanExecuteScripts = true;
+ }
+
+ if (old != mCanExecuteScripts) {
+ for (WindowContext* wc : GetWindowContexts()) {
+ wc->RecomputeCanExecuteScripts();
+ }
+ }
+}
+
+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 any awake
+ // request that is used to prevent page from being suspended because web page
+ // might still need to run their script. Eg. waiting for media keys to resume
+ // media, playing web audio, waiting in a video call conference room.
+ return !IsActive() && GetPageAwakeRequestCount() == 0;
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_TouchEventsOverrideInternal>,
+ dom::TouchEventsOverride, ContentParent* aSource) {
+ return XRE_IsParentProcess() && !aSource;
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_TouchEventsOverrideInternal>,
+ dom::TouchEventsOverride&& aOldValue) {
+ if (GetTouchEventsOverrideInternal() == aOldValue) {
+ return;
+ }
+ WalkPresContexts([&](nsPresContext* aPc) {
+ aPc->MediaFeatureValuesChanged(
+ {MediaFeatureChangeReason::SystemMetricsChange},
+ // We're already iterating through sub documents, so we don't need to
+ // propagate the change again.
+ MediaFeatureChangePropagation::JustThisDocument);
+ });
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_EmbedderColorSchemes>,
+ EmbedderColorSchemes&& aOldValue) {
+ if (GetEmbedderColorSchemes() == aOldValue) {
+ return;
+ }
+ PresContextAffectingFieldChanged();
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_PrefersColorSchemeOverride>,
+ dom::PrefersColorSchemeOverride aOldValue) {
+ MOZ_ASSERT(IsTop());
+ if (PrefersColorSchemeOverride() == aOldValue) {
+ return;
+ }
+ PresContextAffectingFieldChanged();
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_MediumOverride>,
+ nsString&& aOldValue) {
+ MOZ_ASSERT(IsTop());
+ if (GetMediumOverride() == aOldValue) {
+ return;
+ }
+ PresContextAffectingFieldChanged();
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_DisplayMode>,
+ enum DisplayMode aOldValue) {
+ MOZ_ASSERT(IsTop());
+
+ if (GetDisplayMode() == aOldValue) {
+ return;
+ }
+
+ WalkPresContexts([&](nsPresContext* aPc) {
+ aPc->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<IDX_Muted>) {
+ 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();
+ }
+ });
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_IsAppTab>, const bool& aValue,
+ ContentParent* aSource) {
+ return XRE_IsParentProcess() && !aSource && IsTop();
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_HasSiblings>, const bool& aValue,
+ ContentParent* aSource) {
+ return XRE_IsParentProcess() && !aSource && IsTop();
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_ShouldDelayMediaFromStart>,
+ const bool& aValue, ContentParent* aSource) {
+ return IsTop();
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_ShouldDelayMediaFromStart>,
+ bool aOldValue) {
+ MOZ_ASSERT(IsTop(), "Set attribute on non top-level context!");
+ if (aOldValue == GetShouldDelayMediaFromStart()) {
+ return;
+ }
+ if (!GetShouldDelayMediaFromStart()) {
+ PreOrderWalk([&](BrowsingContext* aContext) {
+ if (nsPIDOMWindowOuter* win = aContext->GetDOMWindow()) {
+ win->ActivateMediaComponents();
+ }
+ });
+ }
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_OverrideDPPX>, const float& aValue,
+ ContentParent* aSource) {
+ return XRE_IsParentProcess() && !aSource && IsTop();
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_OverrideDPPX>, float aOldValue) {
+ MOZ_ASSERT(IsTop());
+ if (GetOverrideDPPX() == aOldValue) {
+ return;
+ }
+ PresContextAffectingFieldChanged();
+}
+
+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<IDX_UserAgentOverride>) {
+ MOZ_ASSERT(IsTop());
+
+ PreOrderWalk([&](BrowsingContext* aContext) {
+ nsIDocShell* shell = aContext->GetDocShell();
+ if (shell) {
+ shell->ClearCachedUserAgent();
+ }
+ });
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_IsInBFCache>, bool,
+ ContentParent* aSource) {
+ return IsTop() && !aSource && mozilla::BFCacheInParent();
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_IsInBFCache>) {
+ MOZ_RELEASE_ASSERT(mozilla::BFCacheInParent());
+ MOZ_DIAGNOSTIC_ASSERT(IsTop());
+
+ const bool isInBFCache = GetIsInBFCache();
+ if (!isInBFCache) {
+ UpdateCurrentTopByBrowserId(this);
+ PreOrderWalk([&](BrowsingContext* aContext) {
+ aContext->mIsInBFCache = false;
+ nsCOMPtr<nsIDocShell> shell = aContext->GetDocShell();
+ if (shell) {
+ nsDocShell::Cast(shell)->ThawFreezeNonRecursive(true);
+ }
+ });
+ }
+
+ if (isInBFCache && XRE_IsContentProcess() && mDocShell) {
+ nsDocShell::Cast(mDocShell)->MaybeDisconnectChildListenersOnPageHide();
+ }
+
+ if (isInBFCache) {
+ PreOrderWalk([&](BrowsingContext* aContext) {
+ nsCOMPtr<nsIDocShell> shell = aContext->GetDocShell();
+ if (shell) {
+ nsDocShell::Cast(shell)->FirePageHideShowNonRecursive(false);
+ }
+ });
+ } else {
+ PostOrderWalk([&](BrowsingContext* aContext) {
+ nsCOMPtr<nsIDocShell> shell = aContext->GetDocShell();
+ if (shell) {
+ nsDocShell::Cast(shell)->FirePageHideShowNonRecursive(true);
+ }
+ });
+ }
+
+ if (isInBFCache) {
+ PreOrderWalk([&](BrowsingContext* aContext) {
+ nsCOMPtr<nsIDocShell> shell = aContext->GetDocShell();
+ if (shell) {
+ nsDocShell::Cast(shell)->ThawFreezeNonRecursive(false);
+ if (nsPresContext* pc = shell->GetPresContext()) {
+ pc->EventStateManager()->ResetHoverState();
+ }
+ }
+ aContext->mIsInBFCache = true;
+ Document* doc = aContext->GetDocument();
+ if (doc) {
+ // Notifying needs to happen after mIsInBFCache is set to true.
+ doc->NotifyActivityChanged();
+ }
+ });
+
+ if (XRE_IsParentProcess()) {
+ if (mCurrentWindowContext &&
+ mCurrentWindowContext->Canonical()->Fullscreen()) {
+ mCurrentWindowContext->Canonical()->ExitTopChromeDocumentFullscreen();
+ }
+ }
+ }
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_SyntheticDocumentContainer>) {
+ if (WindowContext* parentWindowContext = GetParentWindowContext()) {
+ parentWindowContext->UpdateChildSynthetic(this,
+ GetSyntheticDocumentContainer());
+ }
+}
+
+void BrowsingContext::SetCustomPlatform(const nsAString& aPlatform,
+ ErrorResult& aRv) {
+ Top()->SetPlatformOverride(aPlatform, aRv);
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_PlatformOverride>) {
+ MOZ_ASSERT(IsTop());
+
+ PreOrderWalk([&](BrowsingContext* aContext) {
+ nsIDocShell* shell = aContext->GetDocShell();
+ if (shell) {
+ shell->ClearCachedPlatform();
+ }
+ });
+}
+
+auto BrowsingContext::LegacyRevertIfNotOwningOrParentProcess(
+ ContentParent* aSource) -> CanSetResult {
+ if (aSource) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (!Canonical()->IsOwnedByProcess(aSource->ChildID())) {
+ return CanSetResult::Revert;
+ }
+ } else if (!IsInProcess() && !XRE_IsParentProcess()) {
+ // Don't allow this to be set from content processes that
+ // don't own the BrowsingContext.
+ return CanSetResult::Deny;
+ }
+
+ return CanSetResult::Allow;
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_IsActiveBrowserWindowInternal>,
+ const bool& aValue, ContentParent* aSource) {
+ // Should only be set in the parent process.
+ return XRE_IsParentProcess() && !aSource && IsTop();
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_IsActiveBrowserWindowInternal>,
+ 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<Document> doc = aContext->GetExtantDocument()) {
+ doc->UpdateDocumentStates(DocumentState::WINDOW_INACTIVE, true);
+
+ RefPtr<nsPIDOMWindowInner> win = doc->GetInnerWindow();
+ if (win) {
+ RefPtr<MediaDevices> devices;
+ if (isActivateEvent && (devices = win->GetExtantMediaDevices())) {
+ devices->BrowserWindowBecameActive();
+ }
+
+ 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, nsGlobalWindowInner::Cast(win),
+ isActivateEvent ? u"activate"_ns : u"deactivate"_ns,
+ CanBubble::eYes, Cancelable::eYes, nullptr);
+ }
+ }
+ }
+ });
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_OpenerPolicy>,
+ nsILoadInfo::CrossOriginOpenerPolicy aPolicy,
+ ContentParent* aSource) {
+ // A potentially cross-origin isolated BC can't change opener policy, nor can
+ // a BC become potentially cross-origin isolated. An unchanged policy is
+ // always OK.
+ return GetOpenerPolicy() == aPolicy ||
+ (GetOpenerPolicy() !=
+ nsILoadInfo::
+ OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP &&
+ aPolicy !=
+ nsILoadInfo::
+ OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP);
+}
+
+auto BrowsingContext::CanSet(FieldIndex<IDX_AllowContentRetargeting>,
+ const bool& aAllowContentRetargeting,
+ ContentParent* aSource) -> CanSetResult {
+ return LegacyRevertIfNotOwningOrParentProcess(aSource);
+}
+
+auto BrowsingContext::CanSet(FieldIndex<IDX_AllowContentRetargetingOnChildren>,
+ const bool& aAllowContentRetargetingOnChildren,
+ ContentParent* aSource) -> CanSetResult {
+ return LegacyRevertIfNotOwningOrParentProcess(aSource);
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_FullscreenAllowedByOwner>,
+ const bool& aAllowed, ContentParent* aSource) {
+ return CheckOnlyEmbedderCanSet(aSource);
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_UseErrorPages>,
+ const bool& aUseErrorPages,
+ ContentParent* aSource) {
+ return CheckOnlyEmbedderCanSet(aSource);
+}
+
+TouchEventsOverride BrowsingContext::TouchEventsOverride() const {
+ for (const auto* bc = this; bc; bc = bc->GetParent()) {
+ auto tev = bc->GetTouchEventsOverrideInternal();
+ if (tev != dom::TouchEventsOverride::None) {
+ return tev;
+ }
+ }
+ return dom::TouchEventsOverride::None;
+}
+
+bool BrowsingContext::TargetTopLevelLinkClicksToBlank() const {
+ return Top()->GetTargetTopLevelLinkClicksToBlankInternal();
+}
+
+// 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<IDX_WatchedByDevToolsInternal>,
+ 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);
+}
+
+auto BrowsingContext::CanSet(FieldIndex<IDX_DefaultLoadFlags>,
+ const uint32_t& aDefaultLoadFlags,
+ ContentParent* aSource) -> CanSetResult {
+ // 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 LegacyRevertIfNotOwningOrParentProcess(aSource);
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_DefaultLoadFlags>) {
+ 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<IDX_UseGlobalHistory>,
+ const bool& aUseGlobalHistory,
+ ContentParent* aSource) {
+ // Should only be set in the parent process.
+ // return XRE_IsParentProcess() && !aSource;
+ return true;
+}
+
+auto BrowsingContext::CanSet(FieldIndex<IDX_UserAgentOverride>,
+ const nsString& aUserAgent, ContentParent* aSource)
+ -> CanSetResult {
+ if (!IsTop()) {
+ return CanSetResult::Deny;
+ }
+
+ return LegacyRevertIfNotOwningOrParentProcess(aSource);
+}
+
+auto BrowsingContext::CanSet(FieldIndex<IDX_PlatformOverride>,
+ const nsString& aPlatform, ContentParent* aSource)
+ -> CanSetResult {
+ if (!IsTop()) {
+ return CanSetResult::Deny;
+ }
+
+ return LegacyRevertIfNotOwningOrParentProcess(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<IDX_EmbedderInnerWindowId>,
+ 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<IDX_EmbedderElementType>,
+ const Maybe<nsString>&, ContentParent* aSource) {
+ return CheckOnlyEmbedderCanSet(aSource);
+}
+
+auto BrowsingContext::CanSet(FieldIndex<IDX_CurrentInnerWindowId>,
+ const uint64_t& aValue, ContentParent* aSource)
+ -> CanSetResult {
+ // Generally allow clearing this. We may want to be more precise about this
+ // check in the future.
+ if (aValue == 0) {
+ return CanSetResult::Allow;
+ }
+
+ // We must have access to the specified context.
+ RefPtr<WindowContext> window = WindowContext::GetById(aValue);
+ if (!window || window->GetBrowsingContext() != this) {
+ return CanSetResult::Deny;
+ }
+
+ if (aSource) {
+ // If the sending process is no longer the current owner, revert
+ MOZ_ASSERT(XRE_IsParentProcess());
+ if (!Canonical()->IsOwnedByProcess(aSource->ChildID())) {
+ return CanSetResult::Revert;
+ }
+ } else if (XRE_IsContentProcess() && !IsOwnedByProcess()) {
+ return CanSetResult::Deny;
+ }
+
+ return CanSetResult::Allow;
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_ParentInitiatedNavigationEpoch>,
+ const uint64_t& aValue, ContentParent* aSource) {
+ return XRE_IsParentProcess() && !aSource;
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_CurrentInnerWindowId>) {
+ RefPtr<WindowContext> 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) {
+ // We set a timer when we set the current inner window. This
+ // will then flush the session storage to session store to
+ // make sure that we don't miss to store session storage to
+ // session store that is a result of navigation. This is due
+ // to Bug 1700623. We wish to fix this in Bug 1711886, where
+ // making sure to store everything would make this timer
+ // unnecessary.
+ Canonical()->MaybeScheduleSessionStoreUpdate();
+ mCurrentWindowContext->Canonical()->DidBecomeCurrentWindowGlobal(true);
+ }
+ }
+ BrowserParent::UpdateFocusFromBrowsingContext();
+ }
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_IsPopupSpam>, 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<IDX_IsPopupSpam>) {
+ if (GetIsPopupSpam()) {
+ PopupBlocker::RegisterOpenPopupSpam();
+ }
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_MessageManagerGroup>,
+ const nsString& aMessageManagerGroup,
+ ContentParent* aSource) {
+ // Should only be set in the parent process on toplevel.
+ return XRE_IsParentProcess() && !aSource && IsTopContent();
+}
+
+bool BrowsingContext::CanSet(
+ FieldIndex<IDX_OrientationLock>,
+ 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<IDX_Loading>) {
+ if (mFields.Get<IDX_Loading>()) {
+ return;
+ }
+
+ while (!mDeprioritizedLoadRunner.isEmpty()) {
+ nsCOMPtr<nsIRunnable> runner = mDeprioritizedLoadRunner.popFirst();
+ NS_DispatchToCurrentThread(runner.forget());
+ }
+
+ if (IsTop()) {
+ Group()->FlushPostMessageEvents();
+ }
+}
+
+// Inform the Document for this context of the (potential) change in
+// loading state
+void BrowsingContext::DidSet(FieldIndex<IDX_AncestorLoading>) {
+ 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<IDX_AuthorStyleDisabledDefault>) {
+ MOZ_ASSERT(IsTop(),
+ "Should only set AuthorStyleDisabledDefault in the top "
+ "browsing context");
+
+ // We don't need to handle changes to this field, since PageStyleChild.sys.mjs
+ // 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<IDX_TextZoom>, 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()) {
+ AsyncEventDispatcher::RunDOMEventWhenSafe(*element, u"TextZoomChange"_ns,
+ CanBubble::eYes,
+ ChromeOnlyDispatch::eYes);
+ }
+ }
+}
+
+// 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<IDX_FullZoom>, 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()) {
+ AsyncEventDispatcher::RunDOMEventWhenSafe(*element, u"FullZoomChange"_ns,
+ CanBubble::eYes,
+ ChromeOnlyDispatch::eYes);
+ }
+ }
+}
+
+void BrowsingContext::AddDeprioritizedLoadRunner(nsIRunnable* aRunner) {
+ MOZ_ASSERT(IsLoading());
+ MOZ_ASSERT(Top() == this);
+
+ RefPtr<DeprioritizedLoadRunner> runner = new DeprioritizedLoadRunner(aRunner);
+ mDeprioritizedLoadRunner.insertBack(runner);
+ NS_DispatchToCurrentThreadQueue(
+ runner.forget(), StaticPrefs::page_load_deprioritization_period(),
+ EventQueuePriority::Idle);
+}
+
+bool BrowsingContext::IsDynamic() const {
+ const BrowsingContext* current = this;
+ do {
+ if (current->CreatedDynamically()) {
+ return true;
+ }
+ } while ((current = current->GetParent()));
+
+ return false;
+}
+
+bool BrowsingContext::GetOffsetPath(nsTArray<uint32_t>& aPath) const {
+ for (const BrowsingContext* current = this; current && current->GetParent();
+ current = current->GetParent()) {
+ if (current->CreatedDynamically()) {
+ return false;
+ }
+ aPath.AppendElement(current->ChildOffset());
+ }
+ return true;
+}
+
+void BrowsingContext::GetHistoryID(JSContext* aCx,
+ JS::MutableHandle<JS::Value> 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_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<IDX_HasSessionHistory>,
+ bool aOldValue) {
+ MOZ_ASSERT(GetHasSessionHistory() || !aOldValue,
+ "We don't support turning off session history.");
+
+ if (GetHasSessionHistory() && !aOldValue) {
+ CreateChildSHistory();
+ }
+}
+
+bool BrowsingContext::CanSet(
+ FieldIndex<IDX_TargetTopLevelLinkClicksToBlankInternal>,
+ const bool& aTargetTopLevelLinkClicksToBlankInternal,
+ ContentParent* aSource) {
+ return XRE_IsParentProcess() && !aSource && IsTop();
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_BrowserId>, 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<IDX_PendingInitialization>,
+ 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;
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_HasRestoreData>, bool aNewValue,
+ ContentParent* aSource) {
+ return IsTop();
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_IsUnderHiddenEmbedderElement>,
+ const bool& aIsUnderHiddenEmbedderElement,
+ ContentParent* aSource) {
+ return true;
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_ForceOffline>, bool aNewValue,
+ ContentParent* aSource) {
+ return XRE_IsParentProcess() && !aSource;
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_IsUnderHiddenEmbedderElement>,
+ bool aOldValue) {
+ nsIDocShell* shell = GetDocShell();
+ if (!shell) {
+ return;
+ }
+ const bool newValue = IsUnderHiddenEmbedderElement();
+ if (NS_WARN_IF(aOldValue == newValue)) {
+ return;
+ }
+
+ if (auto* bc = BrowserChild::GetFrom(shell)) {
+ bc->UpdateVisibility();
+ }
+
+ if (PresShell* presShell = shell->GetPresShell()) {
+ presShell->SetIsUnderHiddenEmbedderElement(newValue);
+ }
+
+ // Propagate to children.
+ for (BrowsingContext* child : Children()) {
+ Element* embedderElement = child->GetEmbedderElement();
+ if (!embedderElement) {
+ // TODO: We shouldn't need to null check here since `child` and the
+ // element returned by `child->GetEmbedderElement()` are in our
+ // process (the actual browsing context represented by `child` may not
+ // be, but that doesn't matter). However, there are currently a very
+ // small number of crashes due to `embedderElement` being null, somehow
+ // - see bug 1551241. For now we wallpaper the crash.
+ continue;
+ }
+
+ bool embedderFrameIsHidden = true;
+ if (auto* embedderFrame = embedderElement->GetPrimaryFrame()) {
+ embedderFrameIsHidden = !embedderFrame->StyleVisibility()->IsVisible();
+ }
+
+ bool hidden = IsUnderHiddenEmbedderElement() || embedderFrameIsHidden;
+ if (child->IsUnderHiddenEmbedderElement() != hidden) {
+ Unused << child->SetIsUnderHiddenEmbedderElement(hidden);
+ }
+ }
+}
+
+bool BrowsingContext::IsPopupAllowed() {
+ for (auto* context = GetCurrentWindowContext(); context;
+ context = context->GetParentWindowContext()) {
+ if (context->CanShowPopup()) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/* static */
+bool BrowsingContext::ShouldAddEntryForRefresh(
+ nsIURI* aPreviousURI, const SessionHistoryInfo& aInfo) {
+ return ShouldAddEntryForRefresh(aPreviousURI, aInfo.GetURI(),
+ aInfo.HasPostData());
+}
+
+/* static */
+bool BrowsingContext::ShouldAddEntryForRefresh(nsIURI* aPreviousURI,
+ nsIURI* aNewURI,
+ bool aHasPostData) {
+ if (aHasPostData) {
+ return true;
+ }
+
+ bool equalsURI = false;
+ if (aPreviousURI) {
+ aPreviousURI->Equals(aNewURI, &equalsURI);
+ }
+ return !equalsURI;
+}
+
+void BrowsingContext::SessionHistoryCommit(
+ const LoadingSessionHistoryInfo& aInfo, uint32_t aLoadType,
+ nsIURI* aPreviousURI, SessionHistoryInfo* aPreviousActiveEntry,
+ bool aPersist, bool aCloneEntryChildren, bool aChannelExpired,
+ uint32_t aCacheKey) {
+ nsID changeID = {};
+ if (XRE_IsContentProcess()) {
+ RefPtr<ChildSHistory> 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,
+ // ShouldUpdateSessionHistory(loadType) returns true and it's not a
+ // refresh for which ShouldAddEntryForRefresh returns false.
+ // It is possible that this leads to wrong length temporarily, but
+ // so would not having the check for replace.
+ // Note that nsSHistory::AddEntry does a replace load if the current
+ // entry is not marked as a persisted entry. The child process does
+ // not have access to the current entry, so we use the previous active
+ // entry as the best approximation. When that's not the current entry
+ // then the length might be wrong briefly, until the parent process
+ // commits the actual length.
+ if (!LOAD_TYPE_HAS_FLAGS(
+ aLoadType, nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY) &&
+ (IsTop()
+ ? (!aPreviousActiveEntry || aPreviousActiveEntry->GetPersist())
+ : !!aPreviousActiveEntry) &&
+ ShouldUpdateSessionHistory(aLoadType) &&
+ (!LOAD_TYPE_HAS_FLAGS(aLoadType,
+ nsIWebNavigation::LOAD_FLAGS_IS_REFRESH) ||
+ ShouldAddEntryForRefresh(aPreviousURI, aInfo.mInfo))) {
+ changeID = rootSH->AddPendingHistoryChange();
+ }
+ } else {
+ // History load doesn't change the length, only index.
+ changeID = rootSH->AddPendingHistoryChange(aInfo.mOffset, 0);
+ }
+ }
+ ContentChild* cc = ContentChild::GetSingleton();
+ mozilla::Unused << cc->SendHistoryCommit(
+ this, aInfo.mLoadId, changeID, aLoadType, aPersist, aCloneEntryChildren,
+ aChannelExpired, aCacheKey);
+ } else {
+ Canonical()->SessionHistoryCommit(aInfo.mLoadId, changeID, aLoadType,
+ aPersist, aCloneEntryChildren,
+ aChannelExpired, aCacheKey);
+ }
+}
+
+void BrowsingContext::SetActiveSessionHistoryEntry(
+ const Maybe<nsPoint>& aPreviousScrollPos, SessionHistoryInfo* aInfo,
+ uint32_t aLoadType, uint32_t aUpdatedCacheKey, bool aUpdateLength) {
+ if (XRE_IsContentProcess()) {
+ // XXX Why we update cache key only in content process case?
+ if (aUpdatedCacheKey != 0) {
+ aInfo->SetCacheKey(aUpdatedCacheKey);
+ }
+
+ nsID changeID = {};
+ if (aUpdateLength) {
+ RefPtr<ChildSHistory> 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(const nsID& aChangeID) {
+ if (XRE_IsContentProcess()) {
+ ContentChild::GetSingleton()->SendRemoveFromSessionHistory(this, aChangeID);
+ } else {
+ Canonical()->RemoveFromSessionHistory(aChangeID);
+ }
+}
+
+void BrowsingContext::HistoryGo(
+ int32_t aOffset, uint64_t aHistoryEpoch, bool aRequireUserInteraction,
+ bool aUserActivation, std::function<void(Maybe<int32_t>&&)>&& aResolver) {
+ if (XRE_IsContentProcess()) {
+ ContentChild::GetSingleton()->SendHistoryGo(
+ this, aOffset, aHistoryEpoch, aRequireUserInteraction, aUserActivation,
+ std::move(aResolver),
+ [](mozilla::ipc::
+ ResponseRejectReason) { /* FIXME Is ignoring this fine? */ });
+ } else {
+ RefPtr<CanonicalBrowsingContext> self = Canonical();
+ aResolver(self->HistoryGo(
+ aOffset, aHistoryEpoch, aRequireUserInteraction, aUserActivation,
+ Canonical()->GetContentParent()
+ ? Some(Canonical()->GetContentParent()->ChildID())
+ : Nothing()));
+ }
+}
+
+void BrowsingContext::SetChildSHistory(ChildSHistory* aChildSHistory) {
+ mChildSessionHistory = aChildSHistory;
+ mChildSessionHistory->SetBrowsingContext(this);
+ mFields.SetWithoutSyncing<IDX_HasSessionHistory>(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) && IsSubframe()));
+}
+
+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();
+}
+
+void BrowsingContext::LocationCreated(dom::Location* aLocation) {
+ MOZ_ASSERT(!aLocation->isInList());
+ mLocations.insertBack(aLocation);
+}
+
+void BrowsingContext::ClearCachedValuesOfLocations() {
+ for (dom::Location* loc = mLocations.getFirst(); loc; loc = loc->getNext()) {
+ loc->ClearCachedValues();
+ }
+}
+
+} // namespace dom
+
+namespace ipc {
+
+void IPDLParamTraits<dom::MaybeDiscarded<dom::BrowsingContext>>::Write(
+ IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const dom::MaybeDiscarded<dom::BrowsingContext>& aParam) {
+ MOZ_DIAGNOSTIC_ASSERT(!aParam.GetMaybeDiscarded() ||
+ aParam.GetMaybeDiscarded()->EverAttached());
+ uint64_t id = aParam.ContextId();
+ WriteIPDLParam(aWriter, aActor, id);
+}
+
+bool IPDLParamTraits<dom::MaybeDiscarded<dom::BrowsingContext>>::Read(
+ IPC::MessageReader* aReader, IProtocol* aActor,
+ dom::MaybeDiscarded<dom::BrowsingContext>* aResult) {
+ uint64_t id = 0;
+ if (!ReadIPDLParam(aReader, aActor, &id)) {
+ return false;
+ }
+
+ if (id == 0) {
+ *aResult = nullptr;
+ } else if (RefPtr<dom::BrowsingContext> bc = dom::BrowsingContext::Get(id)) {
+ *aResult = std::move(bc);
+ } else {
+ aResult->SetDiscarded(id);
+ }
+ return true;
+}
+
+void IPDLParamTraits<dom::BrowsingContext::IPCInitializer>::Write(
+ IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const dom::BrowsingContext::IPCInitializer& aInit) {
+ // Write actor ID parameters.
+ WriteIPDLParam(aWriter, aActor, aInit.mId);
+ WriteIPDLParam(aWriter, aActor, aInit.mParentId);
+ WriteIPDLParam(aWriter, aActor, aInit.mWindowless);
+ WriteIPDLParam(aWriter, aActor, aInit.mUseRemoteTabs);
+ WriteIPDLParam(aWriter, aActor, aInit.mUseRemoteSubframes);
+ WriteIPDLParam(aWriter, aActor, aInit.mCreatedDynamically);
+ WriteIPDLParam(aWriter, aActor, aInit.mChildOffset);
+ WriteIPDLParam(aWriter, aActor, aInit.mOriginAttributes);
+ WriteIPDLParam(aWriter, aActor, aInit.mRequestContextId);
+ WriteIPDLParam(aWriter, aActor, aInit.mSessionHistoryIndex);
+ WriteIPDLParam(aWriter, aActor, aInit.mSessionHistoryCount);
+ WriteIPDLParam(aWriter, aActor, aInit.mFields);
+}
+
+bool IPDLParamTraits<dom::BrowsingContext::IPCInitializer>::Read(
+ IPC::MessageReader* aReader, IProtocol* aActor,
+ dom::BrowsingContext::IPCInitializer* aInit) {
+ // Read actor ID parameters.
+ if (!ReadIPDLParam(aReader, aActor, &aInit->mId) ||
+ !ReadIPDLParam(aReader, aActor, &aInit->mParentId) ||
+ !ReadIPDLParam(aReader, aActor, &aInit->mWindowless) ||
+ !ReadIPDLParam(aReader, aActor, &aInit->mUseRemoteTabs) ||
+ !ReadIPDLParam(aReader, aActor, &aInit->mUseRemoteSubframes) ||
+ !ReadIPDLParam(aReader, aActor, &aInit->mCreatedDynamically) ||
+ !ReadIPDLParam(aReader, aActor, &aInit->mChildOffset) ||
+ !ReadIPDLParam(aReader, aActor, &aInit->mOriginAttributes) ||
+ !ReadIPDLParam(aReader, aActor, &aInit->mRequestContextId) ||
+ !ReadIPDLParam(aReader, aActor, &aInit->mSessionHistoryIndex) ||
+ !ReadIPDLParam(aReader, aActor, &aInit->mSessionHistoryCount) ||
+ !ReadIPDLParam(aReader, aActor, &aInit->mFields)) {
+ return false;
+ }
+ return true;
+}
+
+template struct IPDLParamTraits<dom::BrowsingContext::BaseTransaction>;
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/docshell/base/BrowsingContext.h b/docshell/base/BrowsingContext.h
new file mode 100644
index 0000000000..5ec95a61e4
--- /dev/null
+++ b/docshell/base/BrowsingContext.h
@@ -0,0 +1,1469 @@
+/* -*- 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 <tuple>
+#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/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;
+class MessageReader;
+class MessageWriter;
+} // namespace IPC
+
+namespace mozilla {
+
+class ErrorResult;
+class LogModule;
+
+namespace ipc {
+class IProtocol;
+class IPCResult;
+
+template <typename T>
+struct IPDLParamTraits;
+} // namespace ipc
+
+namespace dom {
+class BrowsingContent;
+class BrowsingContextGroup;
+class CanonicalBrowsingContext;
+class ChildSHistory;
+class ContentParent;
+class Element;
+struct LoadingSessionHistoryInfo;
+class Location;
+template <typename>
+struct Nullable;
+template <typename T>
+class Sequence;
+class SessionHistoryInfo;
+class SessionStorageManager;
+class StructuredCloneHolder;
+class WindowContext;
+class WindowGlobalChild;
+struct WindowPostMessageOptions;
+class WindowProxyHolder;
+
+enum class ExplicitActiveStatus : uint8_t {
+ None,
+ Active,
+ Inactive,
+ EndGuard_,
+};
+
+struct EmbedderColorSchemes {
+ PrefersColorSchemeOverride mUsed{};
+ PrefersColorSchemeOverride mPreferred{};
+
+ bool operator==(const EmbedderColorSchemes& aOther) const {
+ return mUsed == aOther.mUsed && mPreferred == aOther.mPreferred;
+ }
+
+ bool operator!=(const EmbedderColorSchemes& aOther) const {
+ return !(*this == aOther);
+ }
+};
+
+// 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) \
+ /* Hold the pinned/app-tab state and should be used on top level browsing \
+ * contexts only */ \
+ FIELD(IsAppTab, bool) \
+ /* Whether there's more than 1 tab / toplevel browsing context in this \
+ * parent window. Used to determine if a given BC is allowed to resize \
+ * and/or move the window or not. */ \
+ FIELD(HasSiblings, bool) \
+ /* Indicate that whether we should delay media playback, which would only \
+ be done on an unvisited tab. And this should only be used on the top \
+ level browsing contexts */ \
+ FIELD(ShouldDelayMediaFromStart, bool) \
+ /* See nsSandboxFlags.h for the possible flags. */ \
+ FIELD(SandboxFlags, uint32_t) \
+ /* The value of SandboxFlags when the BrowsingContext is first created. \
+ * Used for sandboxing the initial about:blank document. */ \
+ 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(AllowContentRetargeting, bool) \
+ FIELD(AllowContentRetargetingOnChildren, bool) \
+ FIELD(ForceEnableTrackingProtection, bool) \
+ FIELD(UseGlobalHistory, bool) \
+ FIELD(TargetTopLevelLinkClicksToBlankInternal, bool) \
+ FIELD(FullscreenAllowedByOwner, bool) \
+ FIELD(ForceDesktopViewport, bool) \
+ /* \
+ * "is popup" in the spec. \
+ * Set only on top browsing contexts. \
+ * This doesn't indicate whether this is actually a popup or not, \
+ * but whether this browsing context is created by requesting popup or not. \
+ * See also: nsWindowWatcher::ShouldOpenPopup. \
+ */ \
+ FIELD(IsPopupRequested, 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<nsString>) \
+ FIELD(MessageManagerGroup, nsString) \
+ FIELD(MaxTouchPointsOverride, uint8_t) \
+ FIELD(FullZoom, float) \
+ FIELD(WatchedByDevToolsInternal, bool) \
+ FIELD(TextZoom, float) \
+ FIELD(OverrideDPPX, float) \
+ /* The current in-progress load. */ \
+ FIELD(CurrentLoadIdentifier, Maybe<uint64_t>) \
+ /* 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) \
+ /* Specifies if this BC has loaded documents besides the initial \
+ * about:blank document. about:privatebrowsing, about:home, about:newtab \
+ * and non-initial about:blank are not considered to be initial \
+ * documents. */ \
+ FIELD(HasLoadedNonInitialDocument, bool) \
+ /* Default value for nsIDocumentViewer::authorStyleDisabled in any new \
+ * browsing contexts created as a descendant of this one. Valid only for \
+ * top BCs. */ \
+ FIELD(AuthorStyleDisabledDefault, bool) \
+ FIELD(ServiceWorkersTestingEnabled, bool) \
+ FIELD(MediumOverride, nsString) \
+ /* DevTools override for prefers-color-scheme */ \
+ FIELD(PrefersColorSchemeOverride, dom::PrefersColorSchemeOverride) \
+ /* prefers-color-scheme override based on the color-scheme style of our \
+ * <browser> embedder element. */ \
+ FIELD(EmbedderColorSchemes, EmbedderColorSchemes) \
+ FIELD(DisplayMode, dom::DisplayMode) \
+ /* The number of entries added to the session history because of this \
+ * browsing context. */ \
+ FIELD(HistoryEntryCount, uint32_t) \
+ /* Don't use the getter of the field, but IsInBFCache() method */ \
+ FIELD(IsInBFCache, bool) \
+ FIELD(HasRestoreData, bool) \
+ FIELD(SessionStoreEpoch, uint32_t) \
+ /* Whether we can execute scripts in this BrowsingContext. Has no effect \
+ * unless scripts are also allowed in the parent WindowContext. */ \
+ FIELD(AllowJavascript, bool) \
+ /* The count of request that are used to prevent the browsing context tree \
+ * from being suspended, which would ONLY be modified on the top level \
+ * context in the chrome process because that's a non-atomic counter */ \
+ FIELD(PageAwakeRequestCount, uint32_t) \
+ /* This field only gets incrememented when we start navigations in the \
+ * parent process. This is used for keeping track of the racing navigations \
+ * between the parent and content processes. */ \
+ FIELD(ParentInitiatedNavigationEpoch, uint64_t) \
+ /* This browsing context is for a synthetic image document wrapping an \
+ * image embedded in <object> or <embed>. */ \
+ FIELD(SyntheticDocumentContainer, bool) \
+ /* If true, this document is embedded within a content document, either \
+ * loaded in the parent (e.g. about:addons or the devtools toolbox), or in \
+ * a content process. */ \
+ FIELD(EmbeddedInContentDocument, bool) \
+ /* If true, this browsing context is within a hidden embedded document. */ \
+ FIELD(IsUnderHiddenEmbedderElement, bool) \
+ /* If true, this browsing context is offline */ \
+ FIELD(ForceOffline, 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();
+ static LogModule* GetSyncLog();
+
+ // Look up a BrowsingContext in the current process by ID.
+ static already_AddRefed<BrowsingContext> Get(uint64_t aId);
+ static already_AddRefed<BrowsingContext> Get(GlobalObject&, uint64_t aId) {
+ return Get(aId);
+ }
+ // Look up the top-level BrowsingContext by BrowserID.
+ static already_AddRefed<BrowsingContext> GetCurrentTopByBrowserId(
+ uint64_t aBrowserId);
+ static already_AddRefed<BrowsingContext> GetCurrentTopByBrowserId(
+ GlobalObject&, uint64_t aId) {
+ return GetCurrentTopByBrowserId(aId);
+ }
+
+ static void UpdateCurrentTopByBrowserId(BrowsingContext* aNewBrowsingContext);
+
+ static already_AddRefed<BrowsingContext> GetFromWindow(
+ WindowProxyHolder& aProxy);
+ static already_AddRefed<BrowsingContext> 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 <browser> 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<BrowsingContext> 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<BrowsingContext> CreateDetached(
+ nsGlobalWindowInner* aParent, BrowsingContext* aOpener,
+ BrowsingContextGroup* aSpecificGroup, const nsAString& aName, Type aType,
+ bool aIsPopupRequested, 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<JSObject*> 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);
+
+ // Return true if the type of the embedder element is either object
+ // or embed, false otherwise.
+ bool IsEmbedderTypeObjectOrEmbed();
+
+ // 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);
+
+ // 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 IsSubframe() const { return !IsTop(); }
+
+ bool IsTopContent() const { return IsContent() && IsTop(); }
+
+ bool IsInSubtreeOf(BrowsingContext* aContext);
+
+ bool IsContentSubframe() const { return IsContent() && IsSubframe(); }
+
+ // non-zero
+ uint64_t Id() const { return mBrowsingContextId; }
+
+ BrowsingContext* GetParent() const;
+ BrowsingContext* Top();
+ const BrowsingContext* Top() const;
+
+ int32_t IndexOf(BrowsingContext* aChild);
+
+ // NOTE: Unlike `GetEmbedderWindowGlobal`, `GetParentWindowContext` does not
+ // cross toplevel content browser boundaries.
+ WindowContext* GetParentWindowContext() const { return mParentWindow; }
+ WindowContext* GetTopWindowContext() const;
+
+ already_AddRefed<BrowsingContext> GetOpener() const {
+ RefPtr<BrowsingContext> opener(Get(GetOpenerId()));
+ if (!mIsDiscarded && opener && !opener->mIsDiscarded) {
+ MOZ_DIAGNOSTIC_ASSERT(opener->mType == mType);
+ return opener.forget();
+ }
+ return nullptr;
+ }
+ void SetOpener(BrowsingContext* aOpener);
+ 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<BrowsingContext> 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() const { return GetSandboxFlags(); }
+
+ Span<RefPtr<BrowsingContext>> Children() const;
+ void GetChildren(nsTArray<RefPtr<BrowsingContext>>& aChildren);
+
+ Span<RefPtr<BrowsingContext>> NonSyntheticChildren() const;
+
+ const nsTArray<RefPtr<WindowContext>>& GetWindowContexts() {
+ return mWindowContexts;
+ }
+ void GetWindowContexts(nsTArray<RefPtr<WindowContext>>& 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.
+ enum class WalkFlag {
+ Next,
+ Skip,
+ Stop,
+ };
+
+ /**
+ * Walk the browsing context tree in pre-order and call `aCallback`
+ * for every node in the tree. PreOrderWalk accepts two types of
+ * callbacks, either of the type `void(BrowsingContext*)` or
+ * `WalkFlag(BrowsingContext*)`. The former traverses the entire
+ * tree, but the latter let's you control if a sub-tree should be
+ * skipped by returning `WalkFlag::Skip`, completely abort traversal
+ * by returning `WalkFlag::Stop` or continue as normal with
+ * `WalkFlag::Next`.
+ */
+ template <typename F>
+ void PreOrderWalk(F&& aCallback) {
+ if constexpr (std::is_void_v<
+ typename std::invoke_result_t<F, BrowsingContext*>>) {
+ PreOrderWalkVoid(std::forward<F>(aCallback));
+ } else {
+ PreOrderWalkFlag(std::forward<F>(aCallback));
+ }
+ }
+
+ void PreOrderWalkVoid(const std::function<void(BrowsingContext*)>& aCallback);
+ WalkFlag PreOrderWalkFlag(
+ const std::function<WalkFlag(BrowsingContext*)>& aCallback);
+
+ void PostOrderWalk(const std::function<void(BrowsingContext*)>& aCallback);
+
+ void GetAllBrowsingContextsInSubtree(
+ nsTArray<RefPtr<BrowsingContext>>& aBrowsingContexts);
+
+ BrowsingContextGroup* Group() { return mGroup; }
+
+ // WebIDL bindings for nsILoadContext
+ Nullable<WindowProxyHolder> GetAssociatedWindow();
+ Nullable<WindowProxyHolder> 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<JS::Value> aVal,
+ ErrorResult& aError);
+
+ bool InRDMPane() const { return GetInRDMPane(); }
+
+ bool WatchedByDevTools();
+ void SetWatchedByDevTools(bool aWatchedByDevTools, ErrorResult& aRv);
+
+ dom::TouchEventsOverride TouchEventsOverride() const;
+ bool TargetTopLevelLinkClicksToBlank() const;
+
+ bool FullscreenAllowed() const;
+
+ float FullZoom() const { return GetFullZoom(); }
+ float TextZoom() const { return GetTextZoom(); }
+
+ float OverrideDPPX() const { return Top()->GetOverrideDPPX(); }
+
+ bool SuspendMediaWhenInactive() const {
+ return GetSuspendMediaWhenInactive();
+ }
+
+ bool IsActive() const;
+ bool ForceOffline() const { return GetForceOffline(); }
+
+ bool ForceDesktopViewport() const { return GetForceDesktopViewport(); }
+
+ 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);
+ }
+ }
+
+ // 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,
+ WindowGlobalChild& aRequestingWindow);
+
+ // Find a browsing context in the subtree rooted at 'this' Doesn't
+ // consider the special names, '_self', '_parent', '_top', or
+ // '_blank'.
+ //
+ // If passed, performs access control checks with regard to
+ // 'aRequestingContext', otherwise performs no access checks.
+ BrowsingContext* FindWithNameInSubtree(const nsAString& aName,
+ WindowGlobalChild* aRequestingWindow);
+
+ // 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,
+ WindowGlobalChild& aRequestingWindow);
+
+ nsISupports* GetParentObject() const;
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> 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<JSObject*> aWindowProxy) {
+ mWindowProxy = aWindowProxy;
+ }
+
+ Nullable<WindowProxyHolder> GetWindow();
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SKIPPABLE_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<JSObject*> aLocation,
+ ErrorResult& aError);
+ void Close(CallerType aCallerType, ErrorResult& aError);
+ bool GetClosed(ErrorResult&) { return GetClosed(); }
+ void Focus(CallerType aCallerType, ErrorResult& aError);
+ void Blur(CallerType aCallerType, ErrorResult& aError);
+ WindowProxyHolder GetFrames(ErrorResult& aError);
+ int32_t Length() const { return Children().Length(); }
+ Nullable<WindowProxyHolder> GetTop(ErrorResult& aError);
+ void GetOpener(JSContext* aCx, JS::MutableHandle<JS::Value> aOpener,
+ ErrorResult& aError) const;
+ Nullable<WindowProxyHolder> GetParent(ErrorResult& aError);
+ void PostMessageMoz(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const nsAString& aTargetOrigin,
+ const Sequence<JSObject*>& aTransfer,
+ nsIPrincipal& aSubjectPrincipal, ErrorResult& aError);
+ void PostMessageMoz(JSContext* aCx, JS::Handle<JS::Value> 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<WindowContext> GetParent();
+ already_AddRefed<BrowsingContext> GetOpener();
+
+ uint64_t GetOpenerId() const { return mFields.Get<IDX_OpenerId>(); }
+
+ bool mWindowless = false;
+ bool mUseRemoteTabs = false;
+ bool mUseRemoteSubframes = false;
+ bool mCreatedDynamically = false;
+ int32_t mChildOffset = 0;
+ 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 mozilla::ipc::IPCResult CreateFromIPC(IPCInitializer&& aInitializer,
+ BrowsingContextGroup* aGroup,
+ ContentParent* aOriginProcess);
+
+ 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<SessionStorageManager> GetSessionStorageManager();
+
+ // Set PendingInitialization on this BrowsingContext before the context has
+ // been attached.
+ void InitPendingInitialization(bool aPendingInitialization) {
+ MOZ_ASSERT(!EverAttached());
+ mFields.SetWithoutSyncing<IDX_PendingInitialization>(
+ aPendingInitialization);
+ }
+
+ bool CreatedDynamically() const { return mCreatedDynamically; }
+
+ // Returns true if this browsing context, or any ancestor to this browsing
+ // context was created dynamically. See also `CreatedDynamically`.
+ bool IsDynamic() const;
+
+ int32_t ChildOffset() const { return mChildOffset; }
+
+ bool GetOffsetPath(nsTArray<uint32_t>& aPath) const;
+
+ const OriginAttributes& OriginAttributesRef() { return mOriginAttributes; }
+ nsresult SetOriginAttributes(const OriginAttributes& aAttrs);
+
+ void GetHistoryID(JSContext* aCx, JS::MutableHandle<JS::Value> 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();
+
+ // Check if it is allowed to open a popup from the current browsing
+ // context or any of its ancestors.
+ bool IsPopupAllowed();
+
+ // aCurrentURI is only required to be non-null if the load type contains the
+ // nsIWebNavigation::LOAD_FLAGS_IS_REFRESH flag and aInfo is for a refresh to
+ // the current URI.
+ void SessionHistoryCommit(const LoadingSessionHistoryInfo& aInfo,
+ uint32_t aLoadType, nsIURI* aCurrentURI,
+ SessionHistoryInfo* aPreviousActiveEntry,
+ bool aPersist, bool aCloneEntryChildren,
+ bool aChannelExpired, uint32_t aCacheKey);
+
+ // 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<nsPoint>& aPreviousScrollPos,
+ SessionHistoryInfo* aInfo,
+ uint32_t aLoadType,
+ uint32_t aUpdatedCacheKey,
+ bool aUpdateLength = true);
+
+ // 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(const nsID& aChangeID);
+
+ 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.
+ std::tuple<nsCOMPtr<nsIPrincipal>, nsCOMPtr<nsIPrincipal>>
+ GetTriggeringAndInheritPrincipalsForCurrentLoad();
+
+ void HistoryGo(int32_t aOffset, uint64_t aHistoryEpoch,
+ bool aRequireUserInteraction, bool aUserActivation,
+ std::function<void(Maybe<int32_t>&&)>&& 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<bool, bool> CanFocusCheck(CallerType aCallerType);
+
+ bool CanBlurCheck(CallerType aCallerType);
+
+ // 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 RevisePopupAbuseLevel(
+ PopupBlocker::PopupControlState aControl);
+
+ // Get the modifiers associated with the user activation for relevant
+ // documents. The window.open code uses this routine to determine where the
+ // new window should be located.
+ void GetUserActivationModifiersForPopup(
+ UserActivation::Modifiers* aModifiers);
+
+ void IncrementHistoryEntryCountForBrowsingContext();
+
+ bool ServiceWorkersTestingEnabled() const {
+ return GetServiceWorkersTestingEnabled();
+ }
+
+ void GetMediumOverride(nsAString& aOverride) const {
+ aOverride = GetMediumOverride();
+ }
+
+ dom::PrefersColorSchemeOverride PrefersColorSchemeOverride() const {
+ return GetPrefersColorSchemeOverride();
+ }
+
+ bool IsInBFCache() const;
+
+ bool AllowJavascript() const { return GetAllowJavascript(); }
+ bool CanExecuteScripts() const { return mCanExecuteScripts; }
+
+ uint32_t DefaultLoadFlags() const { return GetDefaultLoadFlags(); }
+
+ // When request for page awake, it would increase a count that is used to
+ // prevent whole browsing context tree from being suspended. The request can
+ // be called multiple times. When calling the revoke, it would decrease the
+ // count and once the count reaches to zero, the browsing context tree could
+ // be suspended when the tree is inactive.
+ void RequestForPageAwake();
+ void RevokeForPageAwake();
+
+ void AddDiscardListener(std::function<void(uint64_t)>&& aListener);
+
+ bool IsAppTab() { return GetIsAppTab(); }
+ bool HasSiblings() { return GetHasSiblings(); }
+
+ bool IsUnderHiddenEmbedderElement() const {
+ return GetIsUnderHiddenEmbedderElement();
+ }
+
+ void LocationCreated(dom::Location* aLocation);
+ void ClearCachedValuesOfLocations();
+
+ protected:
+ virtual ~BrowsingContext();
+ BrowsingContext(WindowContext* aParentWindow, BrowsingContextGroup* aGroup,
+ uint64_t aBrowsingContextId, Type aType, FieldValues&& aInit);
+
+ void SetChildSHistory(ChildSHistory* aChildSHistory);
+ already_AddRefed<ChildSHistory> ForgetChildSHistory() {
+ // FIXME Do we need to unset mHasSessionHistory?
+ return mChildSessionHistory.forget();
+ }
+
+ static bool ShouldAddEntryForRefresh(nsIURI* aCurrentURI,
+ const SessionHistoryInfo& aInfo);
+ static bool ShouldAddEntryForRefresh(nsIURI* aCurrentURI, nsIURI* aNewURI,
+ bool aHasPostData);
+
+ private:
+ mozilla::ipc::IPCResult Attach(bool aFromIPC, ContentParent* aOriginProcess);
+
+ // Recomputes whether we can execute scripts in this BrowsingContext based on
+ // the value of AllowJavascript() and whether scripts are allowed in the
+ // parent WindowContext. Called whenever the AllowJavascript() flag or the
+ // parent WC changes.
+ void RecomputeCanExecuteScripts();
+
+ // 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<BrowsingContext*>(
+ uintptr_t(this) - offsetof(BrowsingContext, mLocation));
+ }
+
+ nsIDocShell* 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);
+
+ bool CanSet(FieldIndex<IDX_SessionStoreEpoch>, uint32_t aEpoch,
+ ContentParent* aSource) {
+ return IsTop() && !aSource;
+ }
+
+ void DidSet(FieldIndex<IDX_SessionStoreEpoch>, uint32_t aOldValue);
+
+ using CanSetResult = syncedcontext::CanSetResult;
+
+ // Ensure that opener is in the same BrowsingContextGroup.
+ bool CanSet(FieldIndex<IDX_OpenerId>, const uint64_t& aValue,
+ ContentParent* aSource) {
+ if (aValue != 0) {
+ RefPtr<BrowsingContext> opener = Get(aValue);
+ return opener && opener->Group() == Group();
+ }
+ return true;
+ }
+
+ bool CanSet(FieldIndex<IDX_OpenerPolicy>,
+ nsILoadInfo::CrossOriginOpenerPolicy, ContentParent*);
+
+ bool CanSet(FieldIndex<IDX_ServiceWorkersTestingEnabled>, bool,
+ ContentParent*) {
+ return IsTop();
+ }
+
+ bool CanSet(FieldIndex<IDX_MediumOverride>, const nsString&, ContentParent*) {
+ return IsTop();
+ }
+
+ bool CanSet(FieldIndex<IDX_EmbedderColorSchemes>, const EmbedderColorSchemes&,
+ ContentParent* aSource) {
+ return CheckOnlyEmbedderCanSet(aSource);
+ }
+
+ bool CanSet(FieldIndex<IDX_PrefersColorSchemeOverride>,
+ dom::PrefersColorSchemeOverride, ContentParent*) {
+ return IsTop();
+ }
+
+ void DidSet(FieldIndex<IDX_InRDMPane>, bool aOldValue);
+
+ void DidSet(FieldIndex<IDX_EmbedderColorSchemes>,
+ EmbedderColorSchemes&& aOldValue);
+
+ void DidSet(FieldIndex<IDX_PrefersColorSchemeOverride>,
+ dom::PrefersColorSchemeOverride aOldValue);
+
+ template <typename Callback>
+ void WalkPresContexts(Callback&&);
+ void PresContextAffectingFieldChanged();
+
+ void DidSet(FieldIndex<IDX_MediumOverride>, nsString&& aOldValue);
+
+ bool CanSet(FieldIndex<IDX_SuspendMediaWhenInactive>, bool, ContentParent*) {
+ return IsTop();
+ }
+
+ bool CanSet(FieldIndex<IDX_TouchEventsOverrideInternal>,
+ dom::TouchEventsOverride aTouchEventsOverride,
+ ContentParent* aSource);
+ void DidSet(FieldIndex<IDX_TouchEventsOverrideInternal>,
+ dom::TouchEventsOverride&& aOldValue);
+
+ bool CanSet(FieldIndex<IDX_DisplayMode>, const enum DisplayMode& aDisplayMode,
+ ContentParent* aSource) {
+ return IsTop();
+ }
+
+ void DidSet(FieldIndex<IDX_DisplayMode>, enum DisplayMode aOldValue);
+
+ bool CanSet(FieldIndex<IDX_ExplicitActive>, const ExplicitActiveStatus&,
+ ContentParent* aSource);
+ void DidSet(FieldIndex<IDX_ExplicitActive>, ExplicitActiveStatus aOldValue);
+
+ bool CanSet(FieldIndex<IDX_IsActiveBrowserWindowInternal>, const bool& aValue,
+ ContentParent* aSource);
+ void DidSet(FieldIndex<IDX_IsActiveBrowserWindowInternal>, 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<IDX_Muted>);
+
+ bool CanSet(FieldIndex<IDX_IsAppTab>, const bool& aValue,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_HasSiblings>, const bool& aValue,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_ShouldDelayMediaFromStart>, const bool& aValue,
+ ContentParent* aSource);
+ void DidSet(FieldIndex<IDX_ShouldDelayMediaFromStart>, bool aOldValue);
+
+ bool CanSet(FieldIndex<IDX_OverrideDPPX>, const float& aValue,
+ ContentParent* aSource);
+ void DidSet(FieldIndex<IDX_OverrideDPPX>, float aOldValue);
+
+ bool CanSet(FieldIndex<IDX_EmbedderInnerWindowId>, const uint64_t& aValue,
+ ContentParent* aSource);
+
+ CanSetResult CanSet(FieldIndex<IDX_CurrentInnerWindowId>,
+ const uint64_t& aValue, ContentParent* aSource);
+
+ void DidSet(FieldIndex<IDX_CurrentInnerWindowId>);
+
+ bool CanSet(FieldIndex<IDX_ParentInitiatedNavigationEpoch>,
+ const uint64_t& aValue, ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_IsPopupSpam>, const bool& aValue,
+ ContentParent* aSource);
+
+ void DidSet(FieldIndex<IDX_IsPopupSpam>);
+
+ void DidSet(FieldIndex<IDX_GVAudibleAutoplayRequestStatus>);
+ void DidSet(FieldIndex<IDX_GVInaudibleAutoplayRequestStatus>);
+
+ void DidSet(FieldIndex<IDX_Loading>);
+
+ void DidSet(FieldIndex<IDX_AncestorLoading>);
+
+ void DidSet(FieldIndex<IDX_PlatformOverride>);
+ CanSetResult CanSet(FieldIndex<IDX_PlatformOverride>,
+ const nsString& aPlatformOverride,
+ ContentParent* aSource);
+
+ void DidSet(FieldIndex<IDX_UserAgentOverride>);
+ CanSetResult CanSet(FieldIndex<IDX_UserAgentOverride>,
+ const nsString& aUserAgent, ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_OrientationLock>,
+ const mozilla::hal::ScreenOrientation& aOrientationLock,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_EmbedderElementType>,
+ const Maybe<nsString>& aInitiatorType, ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_MessageManagerGroup>,
+ const nsString& aMessageManagerGroup, ContentParent* aSource);
+
+ CanSetResult CanSet(FieldIndex<IDX_AllowContentRetargeting>,
+ const bool& aAllowContentRetargeting,
+ ContentParent* aSource);
+ CanSetResult CanSet(FieldIndex<IDX_AllowContentRetargetingOnChildren>,
+ const bool& aAllowContentRetargetingOnChildren,
+ ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_FullscreenAllowedByOwner>, const bool&,
+ ContentParent*);
+ bool CanSet(FieldIndex<IDX_WatchedByDevToolsInternal>,
+ const bool& aWatchedByDevToolsInternal, ContentParent* aSource);
+
+ CanSetResult CanSet(FieldIndex<IDX_DefaultLoadFlags>,
+ const uint32_t& aDefaultLoadFlags,
+ ContentParent* aSource);
+ void DidSet(FieldIndex<IDX_DefaultLoadFlags>);
+
+ bool CanSet(FieldIndex<IDX_UseGlobalHistory>, const bool& aUseGlobalHistory,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_TargetTopLevelLinkClicksToBlankInternal>,
+ const bool& aTargetTopLevelLinkClicksToBlankInternal,
+ ContentParent* aSource);
+
+ void DidSet(FieldIndex<IDX_HasSessionHistory>, bool aOldValue);
+
+ bool CanSet(FieldIndex<IDX_BrowserId>, const uint32_t& aValue,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_UseErrorPages>, const bool& aUseErrorPages,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_PendingInitialization>, bool aNewValue,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_PageAwakeRequestCount>, uint32_t aNewValue,
+ ContentParent* aSource);
+ void DidSet(FieldIndex<IDX_PageAwakeRequestCount>, uint32_t aOldValue);
+
+ CanSetResult CanSet(FieldIndex<IDX_AllowJavascript>, bool aValue,
+ ContentParent* aSource);
+ void DidSet(FieldIndex<IDX_AllowJavascript>, bool aOldValue);
+
+ bool CanSet(FieldIndex<IDX_ForceDesktopViewport>, bool aValue,
+ ContentParent* aSource) {
+ return IsTop() && XRE_IsParentProcess();
+ }
+
+ // TODO(emilio): Maybe handle the flag being set dynamically without
+ // navigating? The previous code didn't do it tho, and a reload is probably
+ // worth it regardless.
+ // void DidSet(FieldIndex<IDX_ForceDesktopViewport>, bool aOldValue);
+
+ bool CanSet(FieldIndex<IDX_HasRestoreData>, bool aNewValue,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_IsUnderHiddenEmbedderElement>,
+ const bool& aIsUnderHiddenEmbedderElement,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_ForceOffline>, bool aNewValue,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_EmbeddedInContentDocument>, bool,
+ ContentParent* aSource) {
+ return CheckOnlyEmbedderCanSet(aSource);
+ }
+
+ template <size_t I, typename T>
+ bool CanSet(FieldIndex<I>, 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 <size_t I>
+ void DidSet(FieldIndex<I>) {}
+ template <size_t I, typename T>
+ void DidSet(FieldIndex<I>, T&& aOldValue) {}
+
+ void DidSet(FieldIndex<IDX_FullZoom>, float aOldValue);
+ void DidSet(FieldIndex<IDX_TextZoom>, float aOldValue);
+ void DidSet(FieldIndex<IDX_AuthorStyleDisabledDefault>);
+
+ bool CanSet(FieldIndex<IDX_IsInBFCache>, bool, ContentParent* aSource);
+ void DidSet(FieldIndex<IDX_IsInBFCache>);
+
+ void DidSet(FieldIndex<IDX_SyntheticDocumentContainer>);
+
+ void DidSet(FieldIndex<IDX_IsUnderHiddenEmbedderElement>, bool aOldValue);
+
+ // Allow if the process attemping to set field is the same as the owning
+ // process. Deprecated. New code that might use this should generally be moved
+ // to WindowContext or be settable only by the parent process.
+ CanSetResult LegacyRevertIfNotOwningOrParentProcess(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 =
+ std::tuple<nsCOMPtr<nsIPrincipal>, uint64_t>;
+
+ nsIPrincipal* GetSavedPrincipal(
+ Maybe<PrincipalWithLoadIdentifierTuple> aPrincipalTuple);
+
+ // Type of BrowsingContent
+ const Type mType;
+
+ // Unique id identifying BrowsingContext
+ const uint64_t mBrowsingContextId;
+
+ RefPtr<BrowsingContextGroup> mGroup;
+ RefPtr<WindowContext> mParentWindow;
+ nsCOMPtr<nsIDocShell> mDocShell;
+
+ RefPtr<Element> mEmbedderElement;
+
+ nsTArray<RefPtr<WindowContext>> mWindowContexts;
+ RefPtr<WindowContext> 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<JSObject*> 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;
+
+ // Set to true if the browsing context is in the bfcache and pagehide has been
+ // dispatched. When coming out from the bfcache, the value is set to false
+ // before dispatching pageshow.
+ bool mIsInBFCache : 1;
+
+ // Determines if we can execute scripts in this BrowsingContext. True if
+ // AllowJavascript() is true and script execution is allowed in the parent
+ // WindowContext.
+ bool mCanExecuteScripts : 1;
+
+ // The original offset of this context in its container. This property is -1
+ // if this BrowsingContext is for a frame that was added dynamically.
+ int32_t mChildOffset;
+
+ // 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<PrincipalWithLoadIdentifierTuple> mTriggeringPrincipal;
+ Maybe<PrincipalWithLoadIdentifierTuple> mPrincipalToInherit;
+
+ class DeprioritizedLoadRunner
+ : public mozilla::Runnable,
+ public mozilla::LinkedListElement<DeprioritizedLoadRunner> {
+ public:
+ explicit DeprioritizedLoadRunner(nsIRunnable* aInner)
+ : Runnable("DeprioritizedLoadRunner"), mInner(aInner) {}
+
+ NS_IMETHOD Run() override {
+ if (mInner) {
+ RefPtr<nsIRunnable> inner = std::move(mInner);
+ inner->Run();
+ }
+
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<nsIRunnable> mInner;
+ };
+
+ mozilla::LinkedList<DeprioritizedLoadRunner> mDeprioritizedLoadRunner;
+
+ RefPtr<SessionStorageManager> mSessionStorageManager;
+ RefPtr<ChildSHistory> mChildSessionHistory;
+
+ nsTArray<std::function<void(uint64_t)>> mDiscardListeners;
+
+ // 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;
+
+ mozilla::LinkedList<dom::Location> mLocations;
+};
+
+/**
+ * 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. We assume that in this case the failure is unrecoverable, so we
+ * crash immediately rather than return false.
+ */
+extern bool GetRemoteOuterWindowProxy(JSContext* aCx, BrowsingContext* aContext,
+ JS::Handle<JSObject*> aTransplantTo,
+ JS::MutableHandle<JSObject*> aRetVal);
+
+using BrowsingContextTransaction = BrowsingContext::BaseTransaction;
+using BrowsingContextInitializer = BrowsingContext::IPCInitializer;
+using MaybeDiscardedBrowsingContext = MaybeDiscarded<BrowsingContext>;
+
+// Specialize the transaction object for every translation unit it's used in.
+extern template class syncedcontext::Transaction<BrowsingContext>;
+
+} // namespace dom
+
+// Allow sending BrowsingContext objects over IPC.
+namespace ipc {
+template <>
+struct IPDLParamTraits<dom::MaybeDiscarded<dom::BrowsingContext>> {
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const dom::MaybeDiscarded<dom::BrowsingContext>& aParam);
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ dom::MaybeDiscarded<dom::BrowsingContext>* aResult);
+};
+
+template <>
+struct IPDLParamTraits<dom::BrowsingContext::IPCInitializer> {
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const dom::BrowsingContext::IPCInitializer& aInitializer);
+
+ static bool Read(IPC::MessageReader* aReader, 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..622ed2a6c8
--- /dev/null
+++ b/docshell/base/BrowsingContextGroup.cpp
@@ -0,0 +1,570 @@
+/* -*- 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/Preferences.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"
+#include "nsTHashMap.h"
+
+namespace mozilla::dom {
+
+// Maximum number of successive dialogs before we prompt users to disable
+// dialogs for this window.
+#define MAX_SUCCESSIVE_DIALOG_COUNT 5
+
+static StaticRefPtr<BrowsingContextGroup> sChromeGroup;
+
+static StaticAutoPtr<nsTHashMap<uint64_t, RefPtr<BrowsingContextGroup>>>
+ sBrowsingContextGroups;
+
+already_AddRefed<BrowsingContextGroup> BrowsingContextGroup::GetOrCreate(
+ uint64_t aId) {
+ if (!sBrowsingContextGroups) {
+ sBrowsingContextGroups =
+ new nsTHashMap<nsUint64HashKey, RefPtr<BrowsingContextGroup>>();
+ ClearOnShutdown(&sBrowsingContextGroups);
+ }
+
+ return do_AddRef(sBrowsingContextGroups->LookupOrInsertWith(
+ aId, [&aId] { return do_AddRef(new BrowsingContextGroup(aId)); }));
+}
+
+already_AddRefed<BrowsingContextGroup> BrowsingContextGroup::GetExisting(
+ uint64_t aId) {
+ if (sBrowsingContextGroups) {
+ return do_AddRef(sBrowsingContextGroups->Get(aId));
+ }
+ return nullptr;
+}
+
+// Only use 53 bits for the BrowsingContextGroup ID.
+static constexpr uint64_t kBrowsingContextGroupIdTotalBits = 53;
+static constexpr uint64_t kBrowsingContextGroupIdProcessBits = 22;
+static constexpr uint64_t kBrowsingContextGroupIdFlagBits = 1;
+static constexpr uint64_t kBrowsingContextGroupIdBits =
+ kBrowsingContextGroupIdTotalBits - kBrowsingContextGroupIdProcessBits -
+ kBrowsingContextGroupIdFlagBits;
+
+// IDs for the relevant flags
+static constexpr uint64_t kPotentiallyCrossOriginIsolatedFlag = 0x1;
+
+// The next ID value which will be used.
+static uint64_t sNextBrowsingContextGroupId = 1;
+
+// Generate the next ID with the given flags.
+static uint64_t GenerateBrowsingContextGroupId(uint64_t aFlags) {
+ MOZ_RELEASE_ASSERT(aFlags < (uint64_t(1) << kBrowsingContextGroupIdFlagBits));
+ uint64_t childId = XRE_IsContentProcess()
+ ? ContentChild::GetSingleton()->GetID()
+ : uint64_t(0);
+ MOZ_RELEASE_ASSERT(childId <
+ (uint64_t(1) << kBrowsingContextGroupIdProcessBits));
+ uint64_t id = sNextBrowsingContextGroupId++;
+ MOZ_RELEASE_ASSERT(id < (uint64_t(1) << kBrowsingContextGroupIdBits));
+
+ return (childId << (kBrowsingContextGroupIdBits +
+ kBrowsingContextGroupIdFlagBits)) |
+ (id << kBrowsingContextGroupIdFlagBits) | aFlags;
+}
+
+// Extract flags from the given ID.
+static uint64_t GetBrowsingContextGroupIdFlags(uint64_t aId) {
+ return aId & ((uint64_t(1) << kBrowsingContextGroupIdFlagBits) - 1);
+}
+
+uint64_t BrowsingContextGroup::CreateId(bool aPotentiallyCrossOriginIsolated) {
+ // We encode the potentially cross-origin isolated bit within the ID so that
+ // the information can be recovered whenever the group needs to be re-created
+ // due to e.g. being garbage-collected.
+ //
+ // In the future if we end up needing more complex information stored within
+ // the ID, we can consider converting it to a more complex type, like a
+ // string.
+ uint64_t flags =
+ aPotentiallyCrossOriginIsolated ? kPotentiallyCrossOriginIsolatedFlag : 0;
+ uint64_t id = GenerateBrowsingContextGroupId(flags);
+ MOZ_ASSERT(GetBrowsingContextGroupIdFlags(id) == flags);
+ return id;
+}
+
+already_AddRefed<BrowsingContextGroup> BrowsingContextGroup::Create(
+ bool aPotentiallyCrossOriginIsolated) {
+ return GetOrCreate(CreateId(aPotentiallyCrossOriginIsolated));
+}
+
+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_DIAGNOSTIC_ASSERT(!mDestroyed);
+ MOZ_DIAGNOSTIC_ASSERT(aContext);
+ mContexts.Insert(aContext);
+}
+
+void BrowsingContextGroup::Unregister(nsISupports* aContext) {
+ MOZ_DIAGNOSTIC_ASSERT(!mDestroyed);
+ MOZ_DIAGNOSTIC_ASSERT(aContext);
+ mContexts.Remove(aContext);
+
+ MaybeDestroy();
+}
+
+void BrowsingContextGroup::EnsureHostProcess(ContentParent* aProcess) {
+ MOZ_DIAGNOSTIC_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");
+
+ // XXX: The diagnostic crashes in bug 1816025 seemed to come through caller
+ // ContentParent::GetNewOrUsedLaunchingBrowserProcess where we already
+ // did AssertAlive, so IsDead should be irrelevant here. Still it reads
+ // wrong that we ever might do AddBrowsingContextGroup if aProcess->IsDead().
+ if (aProcess->IsDead() ||
+ mHosts.WithEntryHandle(aProcess->GetRemoteType(), [&](auto&& entry) {
+ if (entry) {
+ // We know from bug 1816025 that this happens quite often and we have
+ // bug 1815480 on file that should harden the entire flow. But in the
+ // meantime we can just live with NOT replacing the found host
+ // process with a new one here if it is still alive.
+ MOZ_ASSERT(
+ entry.Data() == aProcess,
+ "There's already another host process for this remote type");
+ if (!entry.Data()->IsShuttingDown()) {
+ return false;
+ }
+ }
+
+ // This process wasn't already marked as our host, so insert it (or
+ // update if the old process is shutting down), and begin subscribing,
+ // unless the process is still launching.
+ entry.InsertOrUpdate(do_AddRef(aProcess));
+
+ return true;
+ })) {
+ 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<RefPtr<BrowsingContext>> aContexts,
+ nsTArray<SyncedContextInitializer>& 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 (const auto& window : context->GetWindowContexts()) {
+ aInits.AppendElement(window->GetIPCInitializer());
+ CollectContextInitializers(window->Children(), aInits);
+ }
+ }
+}
+
+void BrowsingContextGroup::Subscribe(ContentParent* aProcess) {
+ MOZ_DIAGNOSTIC_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<SyncedContextInitializer> 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, fm->GetActionIdForFocusedBrowsingContextInChrome(), active,
+ fm->GetActionIdForActiveBrowsingContextInChrome());
+ }
+ }
+}
+
+void BrowsingContextGroup::Unsubscribe(ContentParent* aProcess) {
+ MOZ_DIAGNOSTIC_ASSERT(aProcess);
+ MOZ_DIAGNOSTIC_ASSERT(aProcess->GetRemoteType() != PREALLOC_REMOTE_TYPE);
+ mSubscribers.Remove(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<nsPIDOMWindowInner> 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 MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ if (mDestroyed) {
+ MOZ_DIAGNOSTIC_ASSERT(mHosts.Count() == 0);
+ MOZ_DIAGNOSTIC_ASSERT(mSubscribers.Count() == 0);
+ MOZ_DIAGNOSTIC_ASSERT_IF(sBrowsingContextGroups,
+ !sBrowsingContextGroups->Contains(Id()) ||
+ *sBrowsingContextGroups->Lookup(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 (const auto& entry : mHosts.Values()) {
+ entry->RemoveBrowsingContextGroup(this);
+ }
+ for (const auto& key : mSubscribers) {
+ key->RemoveBrowsingContextGroup(this);
+ }
+ mHosts.Clear();
+ mSubscribers.Clear();
+
+ if (sBrowsingContextGroups) {
+ sBrowsingContextGroups->Remove(Id());
+ }
+}
+
+void BrowsingContextGroup::AddKeepAlive() {
+ MOZ_DIAGNOSTIC_ASSERT(!mDestroyed);
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
+ mKeepAliveCount++;
+}
+
+void BrowsingContextGroup::RemoveKeepAlive() {
+ MOZ_DIAGNOSTIC_ASSERT(!mDestroyed);
+ MOZ_DIAGNOSTIC_ASSERT(mKeepAliveCount > 0);
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
+ mKeepAliveCount--;
+
+ MaybeDestroy();
+}
+
+auto BrowsingContextGroup::MakeKeepAlivePtr() -> KeepAlivePtr {
+ AddKeepAlive();
+ return KeepAlivePtr{do_AddRef(this).take()};
+}
+
+void BrowsingContextGroup::MaybeDestroy() {
+ // Once there are no synced contexts referencing a `BrowsingContextGroup`, we
+ // can clear subscribers and destroy this group. We only do this in the parent
+ // process, as it will orchestrate destruction of BCGs in content processes.
+ if (XRE_IsParentProcess() && mContexts.IsEmpty() && mKeepAliveCount == 0 &&
+ this != sChromeGroup) {
+ Destroy();
+
+ // We may have been deleted here, as `Destroy()` will clear references. Do
+ // not access any members at this point.
+ }
+}
+
+void BrowsingContextGroup::ChildDestroy() {
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsContentProcess());
+ MOZ_DIAGNOSTIC_ASSERT(!mDestroyed);
+ MOZ_DIAGNOSTIC_ASSERT(mContexts.IsEmpty());
+ Destroy();
+}
+
+nsISupports* BrowsingContextGroup::GetParentObject() const {
+ return xpc::NativeGlobal(xpc::PrivilegedJunkScope());
+}
+
+JSObject* BrowsingContextGroup::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return BrowsingContextGroup_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsresult BrowsingContextGroup::QueuePostMessageEvent(nsIRunnable* aRunnable) {
+ MOZ_ASSERT(StaticPrefs::dom_separate_event_queue_for_post_message_enabled());
+
+ if (!mPostMessageEventQueue) {
+ nsCOMPtr<nsISerialEventTarget> 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);
+ }
+
+ return mPostMessageEventQueue->Dispatch(aRunnable, NS_DISPATCH_NORMAL);
+}
+
+void BrowsingContextGroup::FlushPostMessageEvents() {
+ if (!mPostMessageEventQueue) {
+ return;
+ }
+ nsresult rv = mPostMessageEventQueue->SetIsPaused(true);
+ MOZ_ALWAYS_SUCCEEDS(rv);
+ nsCOMPtr<nsIRunnable> 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<DocGroup*>& aDocGroups) {
+ MOZ_ASSERT(NS_IsMainThread());
+ AppendToArray(aDocGroups, mDocGroups.Values());
+}
+
+already_AddRefed<DocGroup> BrowsingContextGroup::AddDocument(
+ const nsACString& aKey, Document* aDocument) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<DocGroup>& docGroup = mDocGroups.LookupOrInsertWith(
+ aKey, [&] { return DocGroup::Create(this, aKey); });
+
+ docGroup->AddDocument(aDocument);
+ return do_AddRef(docGroup);
+}
+
+void BrowsingContextGroup::RemoveDocument(Document* aDocument,
+ DocGroup* aDocGroup) {
+ MOZ_ASSERT(NS_IsMainThread());
+ RefPtr<DocGroup> docGroup = aDocGroup;
+ // Removing the last document in DocGroup might decrement the
+ // DocGroup BrowsingContextGroup's refcount to 0.
+ RefPtr<BrowsingContextGroup> kungFuDeathGrip(this);
+ docGroup->RemoveDocument(aDocument);
+
+ if (docGroup->IsEmpty()) {
+ mDocGroups.Remove(docGroup->GetKey());
+ }
+}
+
+already_AddRefed<BrowsingContextGroup> 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<RefPtr<BrowsingContextGroup>>& aGroups) {
+ aGroups.Clear();
+ if (!sBrowsingContextGroups) {
+ return;
+ }
+
+ aGroups = ToArray(sBrowsingContextGroups->Values());
+}
+
+// For tests only.
+void BrowsingContextGroup::ResetDialogAbuseState() {
+ mDialogAbuseCount = 0;
+ // Reset the timer.
+ mLastDialogQuitTime =
+ TimeStamp::Now() -
+ TimeDuration::FromSeconds(DEFAULT_SUCCESSIVE_DIALOG_TIME_LIMIT);
+}
+
+bool BrowsingContextGroup::DialogsAreBeingAbused() {
+ if (mLastDialogQuitTime.IsNull() || nsContentUtils::IsCallerChrome()) {
+ return false;
+ }
+
+ TimeDuration dialogInterval(TimeStamp::Now() - mLastDialogQuitTime);
+ if (dialogInterval.ToSeconds() <
+ Preferences::GetInt("dom.successive_dialog_time_limit",
+ DEFAULT_SUCCESSIVE_DIALOG_TIME_LIMIT)) {
+ mDialogAbuseCount++;
+
+ return PopupBlocker::GetPopupControlState() > PopupBlocker::openAllowed ||
+ mDialogAbuseCount > MAX_SUCCESSIVE_DIALOG_COUNT;
+ }
+
+ // Reset the abuse counter
+ mDialogAbuseCount = 0;
+
+ return false;
+}
+
+bool BrowsingContextGroup::IsPotentiallyCrossOriginIsolated() {
+ return GetBrowsingContextGroupIdFlags(mId) &
+ kPotentiallyCrossOriginIsolatedFlag;
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(BrowsingContextGroup, mContexts,
+ mToplevels, mHosts, mSubscribers,
+ mTimerEventQueue, mWorkerEventQueue,
+ mDocGroups)
+
+} // namespace mozilla::dom
diff --git a/docshell/base/BrowsingContextGroup.h b/docshell/base/BrowsingContextGroup.h
new file mode 100644
index 0000000000..2f8649da2d
--- /dev/null
+++ b/docshell/base/BrowsingContextGroup.h
@@ -0,0 +1,318 @@
+/* -*- 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 "nsTHashSet.h"
+#include "nsWrapperCache.h"
+#include "nsXULAppAPI.h"
+
+namespace mozilla {
+class ThrottledEventQueue;
+
+namespace dom {
+
+// Amount of time allowed between alert/prompt/confirm before enabling
+// the stop dialog checkbox.
+#define DEFAULT_SUCCESSIVE_DIALOG_TIME_LIMIT 3 // 3 sec
+
+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_NATIVE_WRAPPERCACHE_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 directly, as it's automatically called by
+ // |EnsureHostProcess| as needed.
+ void Subscribe(ContentParent* aProcess);
+
+ // Stop synchronizing 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 that the BrowsingContext has been discarded. A `KeepAlive`
+ // will be added to the `BrowsingContextGroup`, delaying destruction.
+ void AddKeepAlive();
+ void RemoveKeepAlive();
+
+ // A `KeepAlivePtr` will hold both a strong reference to the
+ // `BrowsingContextGroup` and holds a `KeepAlive`. When the pointer is
+ // dropped, it will release both the strong reference and the keepalive.
+ struct KeepAliveDeleter {
+ void operator()(BrowsingContextGroup* aPtr) {
+ if (RefPtr<BrowsingContextGroup> ptr = already_AddRefed(aPtr)) {
+ ptr->RemoveKeepAlive();
+ }
+ }
+ };
+ using KeepAlivePtr = UniquePtr<BrowsingContextGroup, KeepAliveDeleter>;
+ KeepAlivePtr MakeKeepAlivePtr();
+
+ // 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<RefPtr<BrowsingContext>>& Toplevels() { return mToplevels; }
+ void GetToplevels(nsTArray<RefPtr<BrowsingContext>>& aToplevels) {
+ aToplevels.AppendElements(mToplevels);
+ }
+
+ uint64_t Id() { return mId; }
+
+ nsISupports* GetParentObject() const;
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // Get or create a BrowsingContextGroup with the given ID.
+ static already_AddRefed<BrowsingContextGroup> GetOrCreate(uint64_t aId);
+ static already_AddRefed<BrowsingContextGroup> GetExisting(uint64_t aId);
+ static already_AddRefed<BrowsingContextGroup> Create(
+ bool aPotentiallyCrossOriginIsolated = false);
+ static already_AddRefed<BrowsingContextGroup> Select(
+ WindowContext* aParent, BrowsingContext* aOpener);
+
+ // Like `Create` but only generates and reserves a new ID without actually
+ // creating the BrowsingContextGroup object.
+ static uint64_t CreateId(bool aPotentiallyCrossOriginIsolated = false);
+
+ // For each 'ContentParent', except for 'aExcludedParent',
+ // associated with this group call 'aCallback'.
+ template <typename Func>
+ void EachOtherParent(ContentParent* aExcludedParent, Func&& aCallback) {
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
+ for (const auto& key : mSubscribers) {
+ if (key != aExcludedParent) {
+ aCallback(key);
+ }
+ }
+ }
+
+ // For each 'ContentParent' associated with
+ // this group call 'aCallback'.
+ template <typename Func>
+ void EachParent(Func&& aCallback) {
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
+ for (const auto& key : mSubscribers) {
+ aCallback(key);
+ }
+ }
+
+ nsresult QueuePostMessageEvent(nsIRunnable* aRunnable);
+
+ void FlushPostMessageEvents();
+
+ // Increase or decrease the suspension level in InputTaskManager
+ void UpdateInputTaskManagerIfNeeded(bool aIsActive);
+
+ static BrowsingContextGroup* GetChromeGroup();
+
+ void GetDocGroups(nsTArray<DocGroup*>& aDocGroups);
+
+ // Called by Document when a Document needs to be added to a DocGroup.
+ already_AddRefed<DocGroup> AddDocument(const nsACString& aKey,
+ Document* aDocument);
+
+ // Called by Document when a Document needs to be removed from a DocGroup.
+ // aDocGroup should be from aDocument. This is done to avoid the assert
+ // in GetDocGroup() which can crash when called during unlinking.
+ void RemoveDocument(Document* aDocument, DocGroup* aDocGroup);
+
+ mozilla::ThrottledEventQueue* GetTimerEventQueue() const {
+ return mTimerEventQueue;
+ }
+
+ mozilla::ThrottledEventQueue* GetWorkerEventQueue() const {
+ return mWorkerEventQueue;
+ }
+
+ void SetAreDialogsEnabled(bool aAreDialogsEnabled) {
+ mAreDialogsEnabled = aAreDialogsEnabled;
+ }
+
+ bool GetAreDialogsEnabled() { return mAreDialogsEnabled; }
+
+ bool GetDialogAbuseCount() { return mDialogAbuseCount; }
+
+ // For tests only.
+ void ResetDialogAbuseState();
+
+ bool DialogsAreBeingAbused();
+
+ TimeStamp GetLastDialogQuitTime() { return mLastDialogQuitTime; }
+
+ void SetLastDialogQuitTime(TimeStamp aLastDialogQuitTime) {
+ mLastDialogQuitTime = aLastDialogQuitTime;
+ }
+
+ // Whether all toplevel documents loaded in this group are allowed to be
+ // Cross-Origin Isolated.
+ //
+ // This does not reflect the actual value of `crossOriginIsolated`, as that
+ // also requires that the document is loaded within a `webCOOP+COEP` content
+ // process.
+ bool IsPotentiallyCrossOriginIsolated();
+
+ static void GetAllGroups(nsTArray<RefPtr<BrowsingContextGroup>>& aGroups);
+
+ void IncInputEventSuspensionLevel();
+ void DecInputEventSuspensionLevel();
+
+ void ChildDestroy();
+
+ 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 MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ 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`?
+ nsTHashSet<nsRefPtrHashKey<nsISupports>> mContexts;
+
+ // The set of toplevel browsing contexts in the current BrowsingContextGroup.
+ nsTArray<RefPtr<BrowsingContext>> 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<nsCStringHashKey, DocGroup> 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<nsCStringHashKey, ContentParent> mHosts;
+
+ nsTHashSet<nsRefPtrHashKey<ContentParent>> mSubscribers;
+
+ // A queue to store postMessage events during page load, the queue will be
+ // flushed once the page is loaded
+ RefPtr<mozilla::ThrottledEventQueue> mPostMessageEventQueue;
+
+ RefPtr<mozilla::ThrottledEventQueue> mTimerEventQueue;
+ RefPtr<mozilla::ThrottledEventQueue> 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;
+
+ // This flag keeps track of whether dialogs are
+ // currently enabled for windows of this group.
+ // It's OK to have these local to each process only because even if
+ // frames from two/three different sites (and thus, processes) coordinate a
+ // dialog abuse attack, they would only the double/triple number of dialogs,
+ // as it is still limited per-site.
+ bool mAreDialogsEnabled = true;
+
+ // This counts the number of windows that have been opened in rapid succession
+ // (i.e. within dom.successive_dialog_time_limit of each other). It is reset
+ // to 0 once a dialog is opened after dom.successive_dialog_time_limit seconds
+ // have elapsed without any other dialogs.
+ // See comment for mAreDialogsEnabled as to why it's ok to have this local to
+ // each process.
+ uint32_t mDialogAbuseCount = 0;
+
+ // This holds the time when the last modal dialog was shown. If more than
+ // MAX_DIALOG_LIMIT dialogs are shown within the time span defined by
+ // dom.successive_dialog_time_limit, we show a checkbox or confirmation prompt
+ // to allow disabling of further dialogs from windows in this BC group.
+ TimeStamp mLastDialogQuitTime;
+};
+} // namespace dom
+} // namespace mozilla
+
+inline void ImplCycleCollectionUnlink(
+ mozilla::dom::BrowsingContextGroup::KeepAlivePtr& aField) {
+ aField = nullptr;
+}
+
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback,
+ mozilla::dom::BrowsingContextGroup::KeepAlivePtr& aField, const char* aName,
+ uint32_t aFlags = 0) {
+ CycleCollectionNoteChild(aCallback, aField.get(), aName, aFlags);
+}
+
+#endif // !defined(mozilla_dom_BrowsingContextGroup_h)
diff --git a/docshell/base/BrowsingContextWebProgress.cpp b/docshell/base/BrowsingContextWebProgress.cpp
new file mode 100644
index 0000000000..7c915bd666
--- /dev/null
+++ b/docshell/base/BrowsingContextWebProgress.cpp
@@ -0,0 +1,443 @@
+/* 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"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/BounceTrackingState.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/ErrorNames.h"
+#include "mozilla/Logging.h"
+#include "nsCOMPtr.h"
+#include "nsIWebProgressListener.h"
+#include "nsString.h"
+#include "nsPrintfCString.h"
+#include "nsIChannel.h"
+#include "xptinfo.h"
+#include "mozilla/RefPtr.h"
+
+namespace mozilla {
+namespace dom {
+
+static mozilla::LazyLogModule gBCWebProgressLog("BCWebProgress");
+
+static nsCString DescribeBrowsingContext(CanonicalBrowsingContext* aContext);
+static nsCString DescribeWebProgress(nsIWebProgress* aWebProgress);
+static nsCString DescribeRequest(nsIRequest* aRequest);
+static nsCString DescribeWebProgressFlags(uint32_t aFlags,
+ const nsACString& aPrefix);
+static nsCString DescribeError(nsresult aError);
+
+NS_IMPL_CYCLE_COLLECTION(BrowsingContextWebProgress, mCurrentBrowsingContext)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(BrowsingContextWebProgress)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(BrowsingContextWebProgress)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BrowsingContextWebProgress)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebProgress)
+ NS_INTERFACE_MAP_ENTRY(nsIWebProgress)
+ NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener)
+NS_INTERFACE_MAP_END
+
+BrowsingContextWebProgress::BrowsingContextWebProgress(
+ CanonicalBrowsingContext* aBrowsingContext)
+ : mCurrentBrowsingContext(aBrowsingContext) {}
+
+BrowsingContextWebProgress::~BrowsingContextWebProgress() = default;
+
+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::GetBrowsingContextXPCOM(
+ BrowsingContext** aBrowsingContext) {
+ NS_IF_ADDREF(*aBrowsingContext = mCurrentBrowsingContext);
+ return NS_OK;
+}
+
+BrowsingContext* BrowsingContextWebProgress::GetBrowsingContext() {
+ return mCurrentBrowsingContext;
+}
+
+NS_IMETHODIMP BrowsingContextWebProgress::GetDOMWindow(
+ mozIDOMWindowProxy** aDOMWindow) {
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP BrowsingContextWebProgress::GetIsTopLevel(bool* aIsTopLevel) {
+ *aIsTopLevel = mCurrentBrowsingContext->IsTop();
+ return NS_OK;
+}
+
+NS_IMETHODIMP BrowsingContextWebProgress::GetIsLoadingDocument(
+ bool* aIsLoadingDocument) {
+ *aIsLoadingDocument = mIsLoadingDocument;
+ return NS_OK;
+}
+
+NS_IMETHODIMP BrowsingContextWebProgress::GetLoadType(uint32_t* aLoadType) {
+ *aLoadType = mLoadType;
+ 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<void(nsIWebProgressListener*)>& aCallback) {
+ RefPtr<BrowsingContextWebProgress> kungFuDeathGrip = this;
+
+ ListenerArray::ForwardIterator iter(mListenerInfoList);
+ while (iter.HasMore()) {
+ ListenerInfo& info = iter.GetNext();
+ if (!(info.mNotifyMask & aFlag)) {
+ continue;
+ }
+
+ nsCOMPtr<nsIWebProgressListener> listener =
+ do_QueryReferent(info.mWeakListener);
+ if (!listener) {
+ mListenerInfoList.RemoveElement(info);
+ continue;
+ }
+
+ aCallback(listener);
+ }
+
+ mListenerInfoList.Compact();
+
+ // Notify the parent BrowsingContextWebProgress of the event to continue
+ // propagating.
+ auto* parent = mCurrentBrowsingContext->GetParent();
+ if (parent && parent->GetWebProgress()) {
+ aCallback(parent->GetWebProgress());
+ }
+}
+
+void BrowsingContextWebProgress::ContextDiscarded() {
+ if (!mIsLoadingDocument) {
+ return;
+ }
+
+ // If our BrowsingContext is being discarded while still loading a document,
+ // fire a synthetic `STATE_STOP` to end the ongoing load.
+ MOZ_LOG(gBCWebProgressLog, LogLevel::Info,
+ ("Discarded while loading %s",
+ DescribeBrowsingContext(mCurrentBrowsingContext).get()));
+
+ // This matches what nsDocLoader::doStopDocumentLoad does, except we don't
+ // bother notifying for `STATE_STOP | STATE_IS_DOCUMENT`,
+ // nsBrowserStatusFilter would filter it out before it gets to the parent
+ // process.
+ nsCOMPtr<nsIRequest> request = mLoadingDocumentRequest;
+ OnStateChange(this, request, STATE_STOP | STATE_IS_WINDOW | STATE_IS_NETWORK,
+ NS_ERROR_ABORT);
+}
+
+void BrowsingContextWebProgress::ContextReplaced(
+ CanonicalBrowsingContext* aNewContext) {
+ mCurrentBrowsingContext = aNewContext;
+}
+
+already_AddRefed<BounceTrackingState>
+BrowsingContextWebProgress::GetBounceTrackingState() {
+ if (!mBounceTrackingState) {
+ mBounceTrackingState = BounceTrackingState::GetOrCreate(this);
+ }
+ return do_AddRef(mBounceTrackingState);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIWebProgressListener
+
+NS_IMETHODIMP
+BrowsingContextWebProgress::OnStateChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t aStateFlags,
+ nsresult aStatus) {
+ MOZ_LOG(
+ gBCWebProgressLog, LogLevel::Info,
+ ("OnStateChange(%s, %s, %s, %s) on %s",
+ DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(),
+ DescribeWebProgressFlags(aStateFlags, "STATE_"_ns).get(),
+ DescribeError(aStatus).get(),
+ DescribeBrowsingContext(mCurrentBrowsingContext).get()));
+
+ bool targetIsThis = aWebProgress == this;
+
+ // We may receive a request from an in-process nsDocShell which doesn't have
+ // `aWebProgress == this` which we should still consider as targeting
+ // ourselves.
+ if (nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(aWebProgress);
+ docShell && docShell->GetBrowsingContext() == mCurrentBrowsingContext) {
+ targetIsThis = true;
+ aWebProgress->GetLoadType(&mLoadType);
+ }
+
+ // Track `mIsLoadingDocument` based on the notifications we've received so far
+ // if the nsIWebProgress being targeted is this one.
+ if (targetIsThis) {
+ constexpr uint32_t startFlags = nsIWebProgressListener::STATE_START |
+ nsIWebProgressListener::STATE_IS_DOCUMENT |
+ nsIWebProgressListener::STATE_IS_REQUEST |
+ nsIWebProgressListener::STATE_IS_WINDOW |
+ nsIWebProgressListener::STATE_IS_NETWORK;
+ constexpr uint32_t stopFlags = nsIWebProgressListener::STATE_STOP |
+ nsIWebProgressListener::STATE_IS_WINDOW;
+ constexpr uint32_t redirectFlags =
+ nsIWebProgressListener::STATE_IS_REDIRECTED_DOCUMENT;
+ if ((aStateFlags & startFlags) == startFlags) {
+ if (mIsLoadingDocument) {
+ // We received a duplicate `STATE_START` notification, silence this
+ // notification until we receive the matching `STATE_STOP` to not fire
+ // duplicate `STATE_START` notifications into frontend on process
+ // switches.
+ return NS_OK;
+ }
+ mIsLoadingDocument = true;
+
+ // Record the request we started the load with, so we can emit a synthetic
+ // `STATE_STOP` notification if the BrowsingContext is discarded before
+ // the notification arrives.
+ mLoadingDocumentRequest = aRequest;
+ } else if ((aStateFlags & stopFlags) == stopFlags) {
+ // We've received a `STATE_STOP` notification targeting this web progress,
+ // clear our loading document flag.
+ mIsLoadingDocument = false;
+ mLoadingDocumentRequest = nullptr;
+ } else if (mIsLoadingDocument &&
+ (aStateFlags & redirectFlags) == redirectFlags) {
+ // If we see a redirected document load, update the loading request which
+ // we'll emit the synthetic STATE_STOP notification with.
+ mLoadingDocumentRequest = aRequest;
+ }
+ }
+
+ 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) {
+ MOZ_LOG(
+ gBCWebProgressLog, LogLevel::Info,
+ ("OnProgressChange(%s, %s, %d, %d, %d, %d) on %s",
+ DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(),
+ aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress,
+ DescribeBrowsingContext(mCurrentBrowsingContext).get()));
+ 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) {
+ MOZ_LOG(
+ gBCWebProgressLog, LogLevel::Info,
+ ("OnLocationChange(%s, %s, %s, %s) on %s",
+ DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(),
+ aLocation ? aLocation->GetSpecOrDefault().get() : "<null>",
+ DescribeWebProgressFlags(aFlags, "LOCATION_CHANGE_"_ns).get(),
+ DescribeBrowsingContext(mCurrentBrowsingContext).get()));
+ 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) {
+ MOZ_LOG(
+ gBCWebProgressLog, LogLevel::Info,
+ ("OnStatusChange(%s, %s, %s, \"%s\") on %s",
+ DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(),
+ DescribeError(aStatus).get(), NS_ConvertUTF16toUTF8(aMessage).get(),
+ DescribeBrowsingContext(mCurrentBrowsingContext).get()));
+ 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) {
+ MOZ_LOG(
+ gBCWebProgressLog, LogLevel::Info,
+ ("OnSecurityChange(%s, %s, %x) on %s",
+ DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(),
+ aState, DescribeBrowsingContext(mCurrentBrowsingContext).get()));
+ 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) {
+ MOZ_LOG(
+ gBCWebProgressLog, LogLevel::Info,
+ ("OnContentBlockingEvent(%s, %s, %x) on %s",
+ DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(),
+ aEvent, DescribeBrowsingContext(mCurrentBrowsingContext).get()));
+ UpdateAndNotifyListeners(nsIWebProgress::NOTIFY_CONTENT_BLOCKING,
+ [&](nsIWebProgressListener* listener) {
+ listener->OnContentBlockingEvent(aWebProgress,
+ aRequest, aEvent);
+ });
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BrowsingContextWebProgress::GetDocumentRequest(nsIRequest** aRequest) {
+ NS_IF_ADDREF(*aRequest = mLoadingDocumentRequest);
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Helper methods for notification logging
+
+static nsCString DescribeBrowsingContext(CanonicalBrowsingContext* aContext) {
+ if (!aContext) {
+ return "<null>"_ns;
+ }
+
+ nsCOMPtr<nsIURI> currentURI = aContext->GetCurrentURI();
+ return nsPrintfCString(
+ "{top:%d, id:%" PRIx64 ", url:%s}", aContext->IsTop(), aContext->Id(),
+ currentURI ? currentURI->GetSpecOrDefault().get() : "<null>");
+}
+
+static nsCString DescribeWebProgress(nsIWebProgress* aWebProgress) {
+ if (!aWebProgress) {
+ return "<null>"_ns;
+ }
+
+ bool isTopLevel = false;
+ aWebProgress->GetIsTopLevel(&isTopLevel);
+ bool isLoadingDocument = false;
+ aWebProgress->GetIsLoadingDocument(&isLoadingDocument);
+ return nsPrintfCString("{isTopLevel:%d, isLoadingDocument:%d}", isTopLevel,
+ isLoadingDocument);
+}
+
+static nsCString DescribeRequest(nsIRequest* aRequest) {
+ if (!aRequest) {
+ return "<null>"_ns;
+ }
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ if (!channel) {
+ return "<non-channel>"_ns;
+ }
+
+ nsCOMPtr<nsIURI> originalURI;
+ channel->GetOriginalURI(getter_AddRefs(originalURI));
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+
+ return nsPrintfCString(
+ "{URI:%s, originalURI:%s}",
+ uri ? uri->GetSpecOrDefault().get() : "<null>",
+ originalURI ? originalURI->GetSpecOrDefault().get() : "<null>");
+}
+
+static nsCString DescribeWebProgressFlags(uint32_t aFlags,
+ const nsACString& aPrefix) {
+ nsCString flags;
+ uint32_t remaining = aFlags;
+
+ // Hackily fetch the names of each constant from the XPT information used for
+ // reflecting it into JS. This doesn't need to be reliable and just exists as
+ // a logging aid.
+ //
+ // If a change to xpt in the future breaks this code, just delete it and
+ // replace it with a normal hex log.
+ if (const auto* ifaceInfo =
+ nsXPTInterfaceInfo::ByIID(NS_GET_IID(nsIWebProgressListener))) {
+ for (uint16_t i = 0; i < ifaceInfo->ConstantCount(); ++i) {
+ const auto& constInfo = ifaceInfo->Constant(i);
+ nsDependentCString name(constInfo.Name());
+ if (!StringBeginsWith(name, aPrefix)) {
+ continue;
+ }
+
+ if (remaining & constInfo.mValue) {
+ remaining &= ~constInfo.mValue;
+ if (!flags.IsEmpty()) {
+ flags.AppendLiteral("|");
+ }
+ flags.Append(name);
+ }
+ }
+ }
+ if (remaining != 0 || flags.IsEmpty()) {
+ if (!flags.IsEmpty()) {
+ flags.AppendLiteral("|");
+ }
+ flags.AppendInt(remaining, 16);
+ }
+ return flags;
+}
+
+static nsCString DescribeError(nsresult aError) {
+ nsCString name;
+ GetErrorName(aError, name);
+ return name;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/docshell/base/BrowsingContextWebProgress.h b/docshell/base/BrowsingContextWebProgress.h
new file mode 100644
index 0000000000..81610886b9
--- /dev/null
+++ b/docshell/base/BrowsingContextWebProgress.h
@@ -0,0 +1,103 @@
+/* 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"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/BounceTrackingState.h"
+
+namespace mozilla::dom {
+
+class CanonicalBrowsingContext;
+
+/// Object acting as the nsIWebProgress instance for a BrowsingContext over its
+/// lifetime.
+///
+/// An active toplevel CanonicalBrowsingContext will always have a
+/// BrowsingContextWebProgress, which will be moved between contexts as
+/// BrowsingContextGroup-changing loads are performed.
+///
+/// Subframes will only have a `BrowsingContextWebProgress` if they are loaded
+/// in a content process, and will use the nsDocShell instead if they are loaded
+/// in the parent process, as parent process documents cannot have or be
+/// out-of-process iframes.
+class BrowsingContextWebProgress final : public nsIWebProgress,
+ public nsIWebProgressListener {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(BrowsingContextWebProgress,
+ nsIWebProgress)
+ NS_DECL_NSIWEBPROGRESS
+ NS_DECL_NSIWEBPROGRESSLISTENER
+
+ explicit BrowsingContextWebProgress(
+ CanonicalBrowsingContext* aBrowsingContext);
+
+ 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;
+ };
+
+ void ContextDiscarded();
+ void ContextReplaced(CanonicalBrowsingContext* aNewContext);
+
+ void SetLoadType(uint32_t aLoadType) { mLoadType = aLoadType; }
+
+ already_AddRefed<BounceTrackingState> GetBounceTrackingState();
+
+ private:
+ virtual ~BrowsingContextWebProgress();
+
+ void UpdateAndNotifyListeners(
+ uint32_t aFlag,
+ const std::function<void(nsIWebProgressListener*)>& aCallback);
+
+ using ListenerArray = nsAutoTObserverArray<ListenerInfo, 4>;
+ ListenerArray mListenerInfoList;
+
+ // The current BrowsingContext which owns this BrowsingContextWebProgress.
+ // This context may change during navigations and may not be fully attached at
+ // all times.
+ RefPtr<CanonicalBrowsingContext> mCurrentBrowsingContext;
+
+ // The current request being actively loaded by the BrowsingContext. Only set
+ // while mIsLoadingDocument is true, and is used to fire STATE_STOP
+ // notifications if the BrowsingContext is discarded while the load is
+ // ongoing.
+ nsCOMPtr<nsIRequest> mLoadingDocumentRequest;
+
+ // The most recent load type observed for this BrowsingContextWebProgress.
+ uint32_t mLoadType = 0;
+
+ // Are we currently in the process of loading a document? This is true between
+ // the `STATE_START` notification from content and the `STATE_STOP`
+ // notification being received. Duplicate `STATE_START` events may be
+ // discarded while loading a document to avoid noise caused by process
+ // switches.
+ bool mIsLoadingDocument = false;
+
+ RefPtr<mozilla::BounceTrackingState> mBounceTrackingState;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_BrowsingContextWebProgress_h
diff --git a/docshell/base/CanonicalBrowsingContext.cpp b/docshell/base/CanonicalBrowsingContext.cpp
new file mode 100644
index 0000000000..33ed5aab28
--- /dev/null
+++ b/docshell/base/CanonicalBrowsingContext.cpp
@@ -0,0 +1,3104 @@
+/* -*- 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 "ErrorList.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/dom/BrowsingContextBinding.h"
+#include "mozilla/dom/BrowsingContextGroup.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/EventTarget.h"
+#include "mozilla/dom/PBrowserParent.h"
+#include "mozilla/dom/PBackgroundSessionStorageCache.h"
+#include "mozilla/dom/PWindowGlobalParent.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/Promise-inl.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/dom/ContentProcessManager.h"
+#include "mozilla/dom/MediaController.h"
+#include "mozilla/dom/MediaControlService.h"
+#include "mozilla/dom/ContentPlaybackController.h"
+#include "mozilla/dom/SessionStorageManager.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "mozilla/layers/CompositorBridgeChild.h"
+#ifdef NS_PRINTING
+# include "mozilla/layout/RemotePrintJobParent.h"
+#endif
+#include "mozilla/net/DocumentLoadListener.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_docshell.h"
+#include "mozilla/StaticPrefs_fission.h"
+#include "mozilla/Telemetry.h"
+#include "nsILayoutHistoryState.h"
+#include "nsIPrintSettings.h"
+#include "nsIPrintSettingsService.h"
+#include "nsISupports.h"
+#include "nsIWebNavigation.h"
+#include "nsDocShell.h"
+#include "nsFrameLoader.h"
+#include "nsFrameLoaderOwner.h"
+#include "nsGlobalWindowOuter.h"
+#include "nsIWebBrowserChrome.h"
+#include "nsIXULRuntime.h"
+#include "nsNetUtil.h"
+#include "nsSHistory.h"
+#include "nsSecureBrowserUI.h"
+#include "nsQueryObject.h"
+#include "nsBrowserStatusFilter.h"
+#include "nsIBrowser.h"
+#include "nsTHashSet.h"
+#include "SessionStoreFunctions.h"
+#include "nsIXPConnect.h"
+#include "nsImportModule.h"
+#include "UnitTransforms.h"
+
+using namespace mozilla::ipc;
+
+extern mozilla::LazyLogModule gAutoplayPermissionLog;
+extern mozilla::LazyLogModule gSHLog;
+extern mozilla::LazyLogModule gSHIPBFCacheLog;
+
+#define AUTOPLAY_LOG(msg, ...) \
+ MOZ_LOG(gAutoplayPermissionLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
+
+static mozilla::LazyLogModule sPBContext("PBContext");
+
+// Global count of canonical browsing contexts with the private attribute set
+static uint32_t gNumberOfPrivateContexts = 0;
+
+// Current parent process epoch for parent initiated navigations
+static uint64_t gParentInitiatedNavigationEpoch = 0;
+
+static void IncreasePrivateCount() {
+ gNumberOfPrivateContexts++;
+ MOZ_LOG(sPBContext, mozilla::LogLevel::Debug,
+ ("%s: Private browsing context count %d -> %d", __func__,
+ gNumberOfPrivateContexts - 1, gNumberOfPrivateContexts));
+ if (gNumberOfPrivateContexts > 1) {
+ return;
+ }
+
+ static bool sHasSeenPrivateContext = false;
+ if (!sHasSeenPrivateContext) {
+ sHasSeenPrivateContext = true;
+ mozilla::Telemetry::ScalarSet(
+ mozilla::Telemetry::ScalarID::DOM_PARENTPROCESS_PRIVATE_WINDOW_USED,
+ true);
+ }
+}
+
+static void DecreasePrivateCount() {
+ MOZ_ASSERT(gNumberOfPrivateContexts > 0);
+ gNumberOfPrivateContexts--;
+
+ MOZ_LOG(sPBContext, mozilla::LogLevel::Debug,
+ ("%s: Private browsing context count %d -> %d", __func__,
+ gNumberOfPrivateContexts + 1, gNumberOfPrivateContexts));
+ if (!gNumberOfPrivateContexts &&
+ !mozilla::StaticPrefs::browser_privatebrowsing_autostart()) {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ MOZ_LOG(sPBContext, mozilla::LogLevel::Debug,
+ ("%s: last-pb-context-exited fired", __func__));
+ observerService->NotifyObservers(nullptr, "last-pb-context-exited",
+ nullptr);
+ }
+ }
+}
+
+namespace mozilla::dom {
+
+extern mozilla::LazyLogModule gUserInteractionPRLog;
+
+#define USER_ACTIVATION_LOG(msg, ...) \
+ MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
+
+CanonicalBrowsingContext::CanonicalBrowsingContext(WindowContext* aParentWindow,
+ BrowsingContextGroup* aGroup,
+ uint64_t aBrowsingContextId,
+ uint64_t aOwnerProcessId,
+ uint64_t aEmbedderProcessId,
+ BrowsingContext::Type aType,
+ FieldValues&& aInit)
+ : BrowsingContext(aParentWindow, aGroup, aBrowsingContextId, aType,
+ std::move(aInit)),
+ mProcessId(aOwnerProcessId),
+ mEmbedderProcessId(aEmbedderProcessId),
+ mPermanentKey(JS::NullValue()) {
+ // You are only ever allowed to create CanonicalBrowsingContexts in the
+ // parent process.
+ MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
+
+ // The initial URI in a BrowsingContext is always "about:blank".
+ MOZ_ALWAYS_SUCCEEDS(
+ NS_NewURI(getter_AddRefs(mCurrentRemoteURI), "about:blank"));
+
+ mozilla::HoldJSObjects(this);
+}
+
+CanonicalBrowsingContext::~CanonicalBrowsingContext() {
+ mPermanentKey.setNull();
+
+ mozilla::DropJSObjects(this);
+
+ if (mSessionHistory) {
+ mSessionHistory->SetBrowsingContext(nullptr);
+ }
+}
+
+/* static */
+already_AddRefed<CanonicalBrowsingContext> CanonicalBrowsingContext::Get(
+ uint64_t aId) {
+ MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
+ return BrowsingContext::Get(aId).downcast<CanonicalBrowsingContext>();
+}
+
+/* static */
+CanonicalBrowsingContext* CanonicalBrowsingContext::Cast(
+ BrowsingContext* aContext) {
+ MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
+ return static_cast<CanonicalBrowsingContext*>(aContext);
+}
+
+/* static */
+const CanonicalBrowsingContext* CanonicalBrowsingContext::Cast(
+ const BrowsingContext* aContext) {
+ MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
+ return static_cast<const CanonicalBrowsingContext*>(aContext);
+}
+
+already_AddRefed<CanonicalBrowsingContext> CanonicalBrowsingContext::Cast(
+ already_AddRefed<BrowsingContext>&& aContext) {
+ MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
+ return aContext.downcast<CanonicalBrowsingContext>();
+}
+
+ContentParent* CanonicalBrowsingContext::GetContentParent() const {
+ if (mProcessId == 0) {
+ return nullptr;
+ }
+
+ ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
+ if (!cpm) {
+ return nullptr;
+ }
+ return cpm->GetContentProcessById(ContentParentId(mProcessId));
+}
+
+void CanonicalBrowsingContext::GetCurrentRemoteType(nsACString& aRemoteType,
+ ErrorResult& aRv) const {
+ // If we're in the parent process, dump out the void string.
+ if (mProcessId == 0) {
+ aRemoteType = NOT_REMOTE_TYPE;
+ return;
+ }
+
+ ContentParent* cp = GetContentParent();
+ if (!cp) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ aRemoteType = cp->GetRemoteType();
+}
+
+void CanonicalBrowsingContext::SetOwnerProcessId(uint64_t aProcessId) {
+ MOZ_LOG(GetLog(), LogLevel::Debug,
+ ("SetOwnerProcessId for 0x%08" PRIx64 " (0x%08" PRIx64
+ " -> 0x%08" PRIx64 ")",
+ Id(), mProcessId, aProcessId));
+
+ mProcessId = aProcessId;
+}
+
+nsISecureBrowserUI* CanonicalBrowsingContext::GetSecureBrowserUI() {
+ if (!IsTop()) {
+ return nullptr;
+ }
+ if (!mSecureBrowserUI) {
+ mSecureBrowserUI = new nsSecureBrowserUI(this);
+ }
+ return mSecureBrowserUI;
+}
+
+namespace {
+// The DocShellProgressBridge is attached to a root content docshell loaded in
+// the parent process. Notifications are paired up with the docshell which they
+// came from, so that they can be fired to the correct
+// BrowsingContextWebProgress and bubble through this tree separately.
+//
+// Notifications are filtered by a nsBrowserStatusFilter before being received
+// by the DocShellProgressBridge.
+class DocShellProgressBridge : public nsIWebProgressListener {
+ public:
+ NS_DECL_ISUPPORTS
+ // NOTE: This relies in the expansion of `NS_FORWARD_SAFE` and all listener
+ // methods accepting an `aWebProgress` argument. If this changes in the
+ // future, this may need to be written manually.
+ NS_FORWARD_SAFE_NSIWEBPROGRESSLISTENER(GetTargetContext(aWebProgress))
+
+ explicit DocShellProgressBridge(uint64_t aTopContextId)
+ : mTopContextId(aTopContextId) {}
+
+ private:
+ virtual ~DocShellProgressBridge() = default;
+
+ nsIWebProgressListener* GetTargetContext(nsIWebProgress* aWebProgress) {
+ RefPtr<CanonicalBrowsingContext> context;
+ if (nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(aWebProgress)) {
+ context = docShell->GetBrowsingContext()->Canonical();
+ } else {
+ context = CanonicalBrowsingContext::Get(mTopContextId);
+ }
+ return context && !context->IsDiscarded() ? context->GetWebProgress()
+ : nullptr;
+ }
+
+ uint64_t mTopContextId = 0;
+};
+
+NS_IMPL_ISUPPORTS(DocShellProgressBridge, nsIWebProgressListener)
+} // namespace
+
+void CanonicalBrowsingContext::MaybeAddAsProgressListener(
+ nsIWebProgress* aWebProgress) {
+ // Only add as a listener if the created docshell is a toplevel content
+ // docshell. We'll get notifications for all of our subframes through a single
+ // listener.
+ if (!IsTopContent()) {
+ return;
+ }
+
+ if (!mDocShellProgressBridge) {
+ mDocShellProgressBridge = new DocShellProgressBridge(Id());
+ mStatusFilter = new nsBrowserStatusFilter();
+ mStatusFilter->AddProgressListener(mDocShellProgressBridge,
+ nsIWebProgress::NOTIFY_ALL);
+ }
+
+ aWebProgress->AddProgressListener(mStatusFilter, nsIWebProgress::NOTIFY_ALL);
+}
+
+void CanonicalBrowsingContext::ReplacedBy(
+ CanonicalBrowsingContext* aNewContext,
+ const NavigationIsolationOptions& aRemotenessOptions) {
+ MOZ_ASSERT(!aNewContext->mWebProgress);
+ MOZ_ASSERT(!aNewContext->mSessionHistory);
+ MOZ_ASSERT(IsTop() && aNewContext->IsTop());
+
+ mIsReplaced = true;
+ aNewContext->mIsReplaced = false;
+
+ if (mStatusFilter) {
+ mStatusFilter->RemoveProgressListener(mDocShellProgressBridge);
+ mStatusFilter = nullptr;
+ }
+
+ mWebProgress->ContextReplaced(aNewContext);
+ aNewContext->mWebProgress = std::move(mWebProgress);
+
+ // Use the Transaction for the fields which need to be updated whether or not
+ // the new context has been attached before.
+ // SetWithoutSyncing can be used if context hasn't been attached.
+ Transaction txn;
+ txn.SetBrowserId(GetBrowserId());
+ txn.SetIsAppTab(GetIsAppTab());
+ txn.SetHasSiblings(GetHasSiblings());
+ txn.SetHistoryID(GetHistoryID());
+ txn.SetExplicitActive(GetExplicitActive());
+ txn.SetEmbedderColorSchemes(GetEmbedderColorSchemes());
+ txn.SetHasRestoreData(GetHasRestoreData());
+ txn.SetShouldDelayMediaFromStart(GetShouldDelayMediaFromStart());
+ txn.SetForceOffline(GetForceOffline());
+
+ // Propagate some settings on BrowsingContext replacement so they're not lost
+ // on bfcached navigations. These are important for GeckoView (see bug
+ // 1781936).
+ txn.SetAllowJavascript(GetAllowJavascript());
+ txn.SetForceEnableTrackingProtection(GetForceEnableTrackingProtection());
+ txn.SetUserAgentOverride(GetUserAgentOverride());
+ txn.SetSuspendMediaWhenInactive(GetSuspendMediaWhenInactive());
+ txn.SetDisplayMode(GetDisplayMode());
+ txn.SetForceDesktopViewport(GetForceDesktopViewport());
+ txn.SetIsUnderHiddenEmbedderElement(GetIsUnderHiddenEmbedderElement());
+
+ // When using site-specific zoom, we let the front-end manage it, otherwise it
+ // can cause weirdness like bug 1846141.
+ if (!StaticPrefs::browser_zoom_siteSpecific()) {
+ txn.SetFullZoom(GetFullZoom());
+ txn.SetTextZoom(GetTextZoom());
+ }
+
+ // Propagate the default load flags so that the TRR mode flags are forwarded
+ // to the new browsing context. See bug 1828643.
+ txn.SetDefaultLoadFlags(GetDefaultLoadFlags());
+
+ // As this is a different BrowsingContext, set InitialSandboxFlags to the
+ // current flags in the new context so that they also apply to any initial
+ // about:blank documents created in it.
+ txn.SetSandboxFlags(GetSandboxFlags());
+ txn.SetInitialSandboxFlags(GetSandboxFlags());
+ txn.SetTargetTopLevelLinkClicksToBlankInternal(
+ TargetTopLevelLinkClicksToBlank());
+ if (aNewContext->EverAttached()) {
+ MOZ_ALWAYS_SUCCEEDS(txn.Commit(aNewContext));
+ } else {
+ txn.CommitWithoutSyncing(aNewContext);
+ }
+
+ aNewContext->mRestoreState = mRestoreState.forget();
+ MOZ_ALWAYS_SUCCEEDS(SetHasRestoreData(false));
+
+ // XXXBFCache name handling is still a bit broken in Fission in general,
+ // at least in case name should be cleared.
+ if (aRemotenessOptions.mTryUseBFCache) {
+ MOZ_ASSERT(!aNewContext->EverAttached());
+ aNewContext->mFields.SetWithoutSyncing<IDX_Name>(GetName());
+ // We don't copy over HasLoadedNonInitialDocument here, we'll actually end
+ // up loading a new initial document at this point, before the real load.
+ // The real load will then end up setting HasLoadedNonInitialDocument to
+ // true.
+ }
+
+ if (mSessionHistory) {
+ mSessionHistory->SetBrowsingContext(aNewContext);
+ // At this point we will be creating a new ChildSHistory in the child.
+ // That means that the child's epoch will be reset, so it makes sense to
+ // reset the epoch in the parent too.
+ mSessionHistory->SetEpoch(0, Nothing());
+ mSessionHistory.swap(aNewContext->mSessionHistory);
+ RefPtr<ChildSHistory> childSHistory = ForgetChildSHistory();
+ aNewContext->SetChildSHistory(childSHistory);
+ }
+
+ BackgroundSessionStorageManager::PropagateManager(Id(), aNewContext->Id());
+
+ // Transfer the ownership of the priority active status from the old context
+ // to the new context.
+ aNewContext->mPriorityActive = mPriorityActive;
+ mPriorityActive = false;
+
+ MOZ_ASSERT(aNewContext->mLoadingEntries.IsEmpty());
+ mLoadingEntries.SwapElements(aNewContext->mLoadingEntries);
+ MOZ_ASSERT(!aNewContext->mActiveEntry);
+ mActiveEntry.swap(aNewContext->mActiveEntry);
+
+ aNewContext->mPermanentKey = mPermanentKey;
+ mPermanentKey.setNull();
+}
+
+void CanonicalBrowsingContext::UpdateSecurityState() {
+ if (mSecureBrowserUI) {
+ mSecureBrowserUI->RecomputeSecurityFlags();
+ }
+}
+
+void CanonicalBrowsingContext::GetWindowGlobals(
+ nsTArray<RefPtr<WindowGlobalParent>>& aWindows) {
+ aWindows.SetCapacity(GetWindowContexts().Length());
+ for (auto& window : GetWindowContexts()) {
+ aWindows.AppendElement(static_cast<WindowGlobalParent*>(window.get()));
+ }
+}
+
+WindowGlobalParent* CanonicalBrowsingContext::GetCurrentWindowGlobal() const {
+ return static_cast<WindowGlobalParent*>(GetCurrentWindowContext());
+}
+
+WindowGlobalParent* CanonicalBrowsingContext::GetParentWindowContext() {
+ return static_cast<WindowGlobalParent*>(
+ BrowsingContext::GetParentWindowContext());
+}
+
+WindowGlobalParent* CanonicalBrowsingContext::GetTopWindowContext() {
+ return static_cast<WindowGlobalParent*>(
+ BrowsingContext::GetTopWindowContext());
+}
+
+already_AddRefed<nsIWidget>
+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<nsIWidget> 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<nsIBrowserDOMWindow>
+CanonicalBrowsingContext::GetBrowserDOMWindow() {
+ RefPtr<CanonicalBrowsingContext> chromeTop = TopCrossChromeBoundary();
+ nsGlobalWindowOuter* topWin;
+ if ((topWin = nsGlobalWindowOuter::Cast(chromeTop->GetDOMWindow())) &&
+ topWin->IsChromeWindow()) {
+ return do_AddRef(topWin->GetBrowserDOMWindow());
+ }
+ return nullptr;
+}
+
+already_AddRefed<WindowGlobalParent>
+CanonicalBrowsingContext::GetEmbedderWindowGlobal() const {
+ uint64_t windowId = GetEmbedderInnerWindowId();
+ if (windowId == 0) {
+ return nullptr;
+ }
+
+ return WindowGlobalParent::GetByInnerWindowId(windowId);
+}
+
+CanonicalBrowsingContext*
+CanonicalBrowsingContext::GetParentCrossChromeBoundary() {
+ if (GetParent()) {
+ return Cast(GetParent());
+ }
+ if (auto* embedder = GetEmbedderElement()) {
+ return Cast(embedder->OwnerDoc()->GetBrowsingContext());
+ }
+ return nullptr;
+}
+
+CanonicalBrowsingContext* CanonicalBrowsingContext::TopCrossChromeBoundary() {
+ CanonicalBrowsingContext* bc = this;
+ while (auto* parent = bc->GetParentCrossChromeBoundary()) {
+ bc = parent;
+ }
+ return bc;
+}
+
+Nullable<WindowProxyHolder> CanonicalBrowsingContext::GetTopChromeWindow() {
+ RefPtr<CanonicalBrowsingContext> bc = TopCrossChromeBoundary();
+ if (bc->IsChrome()) {
+ return WindowProxyHolder(bc.forget());
+ }
+ return nullptr;
+}
+
+nsISHistory* CanonicalBrowsingContext::GetSessionHistory() {
+ if (!IsTop()) {
+ return Cast(Top())->GetSessionHistory();
+ }
+
+ // Check GetChildSessionHistory() to make sure that this BrowsingContext has
+ // session history enabled.
+ if (!mSessionHistory && GetChildSessionHistory()) {
+ mSessionHistory = new nsSHistory(this);
+ }
+
+ return mSessionHistory;
+}
+
+SessionHistoryEntry* CanonicalBrowsingContext::GetActiveSessionHistoryEntry() {
+ return mActiveEntry;
+}
+
+void CanonicalBrowsingContext::SetActiveSessionHistoryEntry(
+ SessionHistoryEntry* aEntry) {
+ mActiveEntry = aEntry;
+}
+
+bool CanonicalBrowsingContext::HasHistoryEntry(nsISHEntry* aEntry) {
+ // XXX Should we check also loading entries?
+ return aEntry && mActiveEntry == aEntry;
+}
+
+void CanonicalBrowsingContext::SwapHistoryEntries(nsISHEntry* aOldEntry,
+ nsISHEntry* aNewEntry) {
+ // XXX Should we check also loading entries?
+ if (mActiveEntry == aOldEntry) {
+ nsCOMPtr<SessionHistoryEntry> 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<LoadingSessionHistoryInfo>& aLoadingInfo) {
+ nsISHistory* shistory = GetSessionHistory();
+ if (!shistory || !GetParent()) {
+ return;
+ }
+
+ SessionHistoryEntry* parentSHE =
+ GetParent()->Canonical()->GetActiveSessionHistoryEntry();
+ if (parentSHE) {
+ int32_t index = -1;
+ for (BrowsingContext* sibling : GetParent()->Children()) {
+ ++index;
+ if (sibling == this) {
+ nsCOMPtr<nsISHEntry> shEntry;
+ parentSHE->GetChildSHEntryIfHasNoDynamicallyAddedChild(
+ index, getter_AddRefs(shEntry));
+ nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(shEntry);
+ if (she) {
+ aLoadingInfo.emplace(she);
+ mLoadingEntries.AppendElement(LoadingSessionHistoryEntry{
+ aLoadingInfo.value().mLoadId, she.get()});
+ Unused << SetHistoryID(she->DocshellID());
+ }
+ break;
+ }
+ }
+ }
+}
+
+UniquePtr<LoadingSessionHistoryInfo>
+CanonicalBrowsingContext::CreateLoadingSessionHistoryEntryForLoad(
+ nsDocShellLoadState* aLoadState, SessionHistoryEntry* existingEntry,
+ nsIChannel* aChannel) {
+ RefPtr<SessionHistoryEntry> entry;
+ const LoadingSessionHistoryInfo* existingLoadingInfo =
+ aLoadState->GetLoadingSessionHistoryInfo();
+ MOZ_ASSERT_IF(!existingLoadingInfo, !existingEntry);
+ if (existingLoadingInfo) {
+ if (existingEntry) {
+ entry = existingEntry;
+ } else {
+ MOZ_ASSERT(!existingLoadingInfo->mLoadIsFromSessionHistory);
+
+ SessionHistoryEntry::LoadingEntry* loadingEntry =
+ SessionHistoryEntry::GetByLoadId(existingLoadingInfo->mLoadId);
+ MOZ_LOG(gSHLog, LogLevel::Verbose,
+ ("SHEntry::GetByLoadId(%" PRIu64 ") -> %p",
+ existingLoadingInfo->mLoadId, entry.get()));
+ if (!loadingEntry) {
+ return nullptr;
+ }
+ entry = loadingEntry->mEntry;
+ }
+
+ // If the entry was updated, update also the LoadingSessionHistoryInfo.
+ UniquePtr<LoadingSessionHistoryInfo> lshi =
+ MakeUnique<LoadingSessionHistoryInfo>(entry, existingLoadingInfo);
+ aLoadState->SetLoadingSessionHistoryInfo(std::move(lshi));
+ existingLoadingInfo = aLoadState->GetLoadingSessionHistoryInfo();
+ Unused << SetHistoryEntryCount(entry->BCHistoryLength());
+ } else if (aLoadState->LoadType() == LOAD_REFRESH &&
+ !ShouldAddEntryForRefresh(aLoadState->URI(),
+ aLoadState->PostDataStream()) &&
+ mActiveEntry) {
+ entry = mActiveEntry;
+ } 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<LoadingSessionHistoryInfo> loadingInfo;
+ if (existingLoadingInfo) {
+ loadingInfo = MakeUnique<LoadingSessionHistoryInfo>(*existingLoadingInfo);
+ } else {
+ loadingInfo = MakeUnique<LoadingSessionHistoryInfo>(entry);
+ mLoadingEntries.AppendElement(
+ LoadingSessionHistoryEntry{loadingInfo->mLoadId, entry});
+ }
+
+ MOZ_ASSERT(SessionHistoryEntry::GetByLoadId(loadingInfo->mLoadId)->mEntry ==
+ entry);
+
+ return loadingInfo;
+}
+
+UniquePtr<LoadingSessionHistoryInfo>
+CanonicalBrowsingContext::ReplaceLoadingSessionHistoryEntryForLoad(
+ LoadingSessionHistoryInfo* aInfo, nsIChannel* aNewChannel) {
+ MOZ_ASSERT(aInfo);
+ MOZ_ASSERT(aNewChannel);
+
+ SessionHistoryInfo newInfo = SessionHistoryInfo(
+ aNewChannel, aInfo->mInfo.LoadType(),
+ aInfo->mInfo.GetPartitionedPrincipalToInherit(), aInfo->mInfo.GetCsp());
+
+ for (size_t i = 0; i < mLoadingEntries.Length(); ++i) {
+ if (mLoadingEntries[i].mLoadId == aInfo->mLoadId) {
+ RefPtr<SessionHistoryEntry> loadingEntry = mLoadingEntries[i].mEntry;
+ loadingEntry->SetInfo(&newInfo);
+
+ if (IsTop()) {
+ // Only top level pages care about Get/SetPersist.
+ nsCOMPtr<nsIURI> uri;
+ aNewChannel->GetURI(getter_AddRefs(uri));
+ loadingEntry->SetPersist(
+ nsDocShell::ShouldAddToSessionHistory(uri, aNewChannel));
+ } else {
+ loadingEntry->SetIsSubFrame(aInfo->mInfo.IsSubFrame());
+ }
+ loadingEntry->SetDocshellID(GetHistoryID());
+ loadingEntry->SetIsDynamicallyAdded(CreatedDynamically());
+ return MakeUnique<LoadingSessionHistoryInfo>(loadingEntry, aInfo);
+ }
+ }
+ return nullptr;
+}
+
+using PrintPromise = CanonicalBrowsingContext::PrintPromise;
+#ifdef NS_PRINTING
+class PrintListenerAdapter final : public nsIWebProgressListener {
+ public:
+ explicit PrintListenerAdapter(PrintPromise::Private* aPromise)
+ : mPromise(aPromise) {}
+
+ NS_DECL_ISUPPORTS
+
+ // NS_DECL_NSIWEBPROGRESSLISTENER
+ NS_IMETHOD OnStateChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ uint32_t aStateFlags, nsresult aStatus) override {
+ if (aStateFlags & nsIWebProgressListener::STATE_STOP &&
+ aStateFlags & nsIWebProgressListener::STATE_IS_DOCUMENT && mPromise) {
+ mPromise->Resolve(true, __func__);
+ mPromise = nullptr;
+ }
+ return NS_OK;
+ }
+ NS_IMETHOD OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ nsresult aStatus,
+ const char16_t* aMessage) override {
+ if (aStatus != NS_OK && mPromise) {
+ mPromise->Reject(aStatus, __func__);
+ mPromise = nullptr;
+ }
+ return NS_OK;
+ }
+ NS_IMETHOD OnProgressChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, int32_t aCurSelfProgress,
+ int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress,
+ int32_t aMaxTotalProgress) override {
+ return NS_OK;
+ }
+ NS_IMETHOD OnLocationChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, nsIURI* aLocation,
+ uint32_t aFlags) override {
+ return NS_OK;
+ }
+ NS_IMETHOD OnSecurityChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, uint32_t aState) override {
+ return NS_OK;
+ }
+ NS_IMETHOD OnContentBlockingEvent(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t aEvent) override {
+ return NS_OK;
+ }
+
+ private:
+ ~PrintListenerAdapter() = default;
+
+ RefPtr<PrintPromise::Private> mPromise;
+};
+
+NS_IMPL_ISUPPORTS(PrintListenerAdapter, nsIWebProgressListener)
+#endif
+
+already_AddRefed<Promise> CanonicalBrowsingContext::PrintJS(
+ nsIPrintSettings* aPrintSettings, ErrorResult& aRv) {
+ RefPtr<Promise> promise = Promise::Create(GetIncumbentGlobal(), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return promise.forget();
+ }
+
+ Print(aPrintSettings)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [promise](bool) { promise->MaybeResolveWithUndefined(); },
+ [promise](nsresult aResult) { promise->MaybeReject(aResult); });
+ return promise.forget();
+}
+
+RefPtr<PrintPromise> CanonicalBrowsingContext::Print(
+ nsIPrintSettings* aPrintSettings) {
+#ifndef NS_PRINTING
+ return PrintPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__);
+#else
+
+ auto promise = MakeRefPtr<PrintPromise::Private>(__func__);
+ auto listener = MakeRefPtr<PrintListenerAdapter>(promise);
+ if (IsInProcess()) {
+ RefPtr<nsGlobalWindowOuter> outerWindow =
+ nsGlobalWindowOuter::Cast(GetDOMWindow());
+ if (NS_WARN_IF(!outerWindow)) {
+ promise->Reject(NS_ERROR_FAILURE, __func__);
+ return promise;
+ }
+
+ ErrorResult rv;
+ outerWindow->Print(aPrintSettings,
+ /* aRemotePrintJob = */ nullptr, listener,
+ /* aDocShellToCloneInto = */ nullptr,
+ nsGlobalWindowOuter::IsPreview::No,
+ nsGlobalWindowOuter::IsForWindowDotPrint::No,
+ /* aPrintPreviewCallback = */ nullptr, rv);
+ if (rv.Failed()) {
+ promise->Reject(rv.StealNSResult(), __func__);
+ }
+ return promise;
+ }
+
+ auto* browserParent = GetBrowserParent();
+ if (NS_WARN_IF(!browserParent)) {
+ promise->Reject(NS_ERROR_FAILURE, __func__);
+ return promise;
+ }
+
+ nsCOMPtr<nsIPrintSettingsService> printSettingsSvc =
+ do_GetService("@mozilla.org/gfx/printsettings-service;1");
+ if (NS_WARN_IF(!printSettingsSvc)) {
+ promise->Reject(NS_ERROR_FAILURE, __func__);
+ return promise;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIPrintSettings> printSettings = aPrintSettings;
+ if (!printSettings) {
+ rv =
+ printSettingsSvc->CreateNewPrintSettings(getter_AddRefs(printSettings));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ promise->Reject(rv, __func__);
+ return promise;
+ }
+ }
+
+ embedding::PrintData printData;
+ rv = printSettingsSvc->SerializeToPrintData(printSettings, &printData);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ promise->Reject(rv, __func__);
+ return promise;
+ }
+
+ layout::RemotePrintJobParent* remotePrintJob =
+ new layout::RemotePrintJobParent(printSettings);
+ printData.remotePrintJob() =
+ browserParent->Manager()->SendPRemotePrintJobConstructor(remotePrintJob);
+
+ if (listener) {
+ remotePrintJob->RegisterListener(listener);
+ }
+
+ if (NS_WARN_IF(!browserParent->SendPrint(this, printData))) {
+ promise->Reject(NS_ERROR_FAILURE, __func__);
+ }
+ return promise.forget();
+#endif
+}
+
+void CanonicalBrowsingContext::CallOnAllTopDescendants(
+ const FunctionRef<CallState(CanonicalBrowsingContext*)>& aCallback,
+ bool aIncludeNestedBrowsers) {
+ MOZ_ASSERT(IsTop(), "Should only call on top BC");
+ MOZ_ASSERT(
+ !aIncludeNestedBrowsers ||
+ (IsChrome() && !GetParentCrossChromeBoundary()),
+ "If aIncludeNestedBrowsers is set, should only call on top chrome BC");
+
+ if (!IsInProcess()) {
+ // We rely on top levels having to be embedded in the parent process, so
+ // we can only have top level descendants if embedded here..
+ return;
+ }
+
+ AutoTArray<RefPtr<BrowsingContextGroup>, 32> 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;
+ }
+
+ if (aIncludeNestedBrowsers) {
+ if (this != bc->Canonical()->TopCrossChromeBoundary()) {
+ continue;
+ }
+ } else {
+ auto* parent = bc->Canonical()->GetParentCrossChromeBoundary();
+ if (!parent || this != parent->Top()) {
+ continue;
+ }
+ }
+
+ if (aCallback(bc->Canonical()) == CallState::Stop) {
+ return;
+ }
+ }
+ }
+}
+
+void CanonicalBrowsingContext::SessionHistoryCommit(
+ uint64_t aLoadId, const nsID& aChangeID, uint32_t aLoadType, bool aPersist,
+ bool aCloneEntryChildren, bool aChannelExpired, uint32_t aCacheKey) {
+ MOZ_LOG(gSHLog, LogLevel::Verbose,
+ ("CanonicalBrowsingContext::SessionHistoryCommit %p %" PRIu64, this,
+ aLoadId));
+ MOZ_ASSERT(aLoadId != UINT64_MAX,
+ "Must not send special about:blank loadinfo to parent.");
+ for (size_t i = 0; i < mLoadingEntries.Length(); ++i) {
+ if (mLoadingEntries[i].mLoadId == aLoadId) {
+ nsSHistory* shistory = static_cast<nsSHistory*>(GetSessionHistory());
+ if (!shistory) {
+ SessionHistoryEntry::RemoveLoadId(aLoadId);
+ mLoadingEntries.RemoveElementAt(i);
+ return;
+ }
+
+ RefPtr<SessionHistoryEntry> newActiveEntry = mLoadingEntries[i].mEntry;
+ if (aCacheKey != 0) {
+ newActiveEntry->SetCacheKey(aCacheKey);
+ }
+
+ if (aChannelExpired) {
+ newActiveEntry->SharedInfo()->mExpired = true;
+ }
+
+ bool loadFromSessionHistory = !newActiveEntry->ForInitialLoad();
+ newActiveEntry->SetForInitialLoad(false);
+ SessionHistoryEntry::RemoveLoadId(aLoadId);
+ mLoadingEntries.RemoveElementAt(i);
+
+ int32_t indexOfHistoryLoad = -1;
+ if (loadFromSessionHistory) {
+ nsCOMPtr<nsISHEntry> root = nsSHistory::GetRootSHEntry(newActiveEntry);
+ indexOfHistoryLoad = shistory->GetIndexOfEntry(root);
+ if (indexOfHistoryLoad < 0) {
+ // Entry has been removed from the session history.
+ return;
+ }
+ }
+
+ CallerWillNotifyHistoryIndexAndLengthChanges caller(shistory);
+
+ // If there is a name in the new entry, clear the name of all contiguous
+ // entries. This is for https://html.spec.whatwg.org/#history-traversal
+ // Step 4.4.2.
+ nsAutoString nameOfNewEntry;
+ newActiveEntry->GetName(nameOfNewEntry);
+ if (!nameOfNewEntry.IsEmpty()) {
+ nsSHistory::WalkContiguousEntries(
+ newActiveEntry,
+ [](nsISHEntry* aEntry) { aEntry->SetName(EmptyString()); });
+ }
+
+ bool addEntry = ShouldUpdateSessionHistory(aLoadType);
+ if (IsTop()) {
+ if (mActiveEntry && !mActiveEntry->GetFrameLoader()) {
+ bool sharesDocument = true;
+ mActiveEntry->SharesDocumentWith(newActiveEntry, &sharesDocument);
+ if (!sharesDocument) {
+ // If the old page won't be in the bfcache,
+ // clear the dynamic entries.
+ RemoveDynEntriesFromActiveSessionHistoryEntry();
+ }
+ }
+
+ 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, newActiveEntry);
+ }
+ mActiveEntry = newActiveEntry;
+ } else if (LOAD_TYPE_HAS_FLAGS(
+ aLoadType, nsIWebNavigation::LOAD_FLAGS_IS_REFRESH) &&
+ !ShouldAddEntryForRefresh(newActiveEntry) && mActiveEntry) {
+ addEntry = false;
+ mActiveEntry->ReplaceWith(*newActiveEntry);
+ } else {
+ mActiveEntry = newActiveEntry;
+ }
+
+ if (loadFromSessionHistory) {
+ // XXX Synchronize browsing context tree and session history tree?
+ shistory->InternalSetRequestedIndex(indexOfHistoryLoad);
+ shistory->UpdateIndex();
+
+ if (IsTop()) {
+ mActiveEntry->SetWireframe(Nothing());
+ }
+ } else if (addEntry) {
+ shistory->AddEntry(mActiveEntry, aPersist);
+ shistory->InternalSetRequestedIndex(-1);
+ }
+ } else {
+ // FIXME The old implementations adds it to the parent's mLSHE if there
+ // is one, need to figure out if that makes sense here (peterv
+ // doesn't think it would).
+ if (loadFromSessionHistory) {
+ if (mActiveEntry) {
+ // mActiveEntry is null if we're loading iframes from session
+ // history while also parent page is loading from session history.
+ // In that case there isn't anything to sync.
+ mActiveEntry->SyncTreesForSubframeNavigation(newActiveEntry, Top(),
+ this);
+ }
+ mActiveEntry = newActiveEntry;
+
+ shistory->InternalSetRequestedIndex(indexOfHistoryLoad);
+ // FIXME UpdateIndex() here may update index too early (but even the
+ // old implementation seems to have similar issues).
+ shistory->UpdateIndex();
+ } else if (addEntry) {
+ if (mActiveEntry) {
+ if (LOAD_TYPE_HAS_FLAGS(
+ aLoadType, nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY) ||
+ (LOAD_TYPE_HAS_FLAGS(aLoadType,
+ nsIWebNavigation::LOAD_FLAGS_IS_REFRESH) &&
+ !ShouldAddEntryForRefresh(newActiveEntry))) {
+ // FIXME We need to make sure that when we create the info we
+ // make a copy of the shared state.
+ mActiveEntry->ReplaceWith(*newActiveEntry);
+ } else {
+ // AddChildSHEntryHelper does update the index of the session
+ // history!
+ shistory->AddChildSHEntryHelper(mActiveEntry, newActiveEntry,
+ Top(), aCloneEntryChildren);
+ mActiveEntry = newActiveEntry;
+ }
+ } else {
+ SessionHistoryEntry* parentEntry = GetParent()->mActiveEntry;
+ // XXX What should happen if parent doesn't have mActiveEntry?
+ // Or can that even happen ever?
+ if (parentEntry) {
+ mActiveEntry = newActiveEntry;
+ // FIXME Using IsInProcess for aUseRemoteSubframes isn't quite
+ // right, but aUseRemoteSubframes should be going away.
+ parentEntry->AddChild(
+ mActiveEntry,
+ CreatedDynamically() ? -1 : GetParent()->IndexOf(this),
+ IsInProcess());
+ }
+ }
+ shistory->InternalSetRequestedIndex(-1);
+ }
+ }
+
+ ResetSHEntryHasUserInteractionCache();
+
+ HistoryCommitIndexAndLength(aChangeID, caller);
+
+ shistory->LogHistory();
+
+ return;
+ }
+ // XXX Should the loading entries before [i] be removed?
+ }
+ // FIXME Should we throw an error if we don't find an entry for
+ // aSessionHistoryEntryId?
+}
+
+already_AddRefed<nsDocShellLoadState> CanonicalBrowsingContext::CreateLoadInfo(
+ SessionHistoryEntry* aEntry) {
+ const SessionHistoryInfo& info = aEntry->Info();
+ RefPtr<nsDocShellLoadState> loadState(new nsDocShellLoadState(info.GetURI()));
+ info.FillLoadInfo(*loadState);
+ UniquePtr<LoadingSessionHistoryInfo> loadingInfo;
+ loadingInfo = MakeUnique<LoadingSessionHistoryInfo>(aEntry);
+ mLoadingEntries.AppendElement(
+ LoadingSessionHistoryEntry{loadingInfo->mLoadId, aEntry});
+ loadState->SetLoadingSessionHistoryInfo(std::move(loadingInfo));
+
+ return loadState.forget();
+}
+
+void CanonicalBrowsingContext::NotifyOnHistoryReload(
+ bool aForceReload, bool& aCanReload,
+ Maybe<NotNull<RefPtr<nsDocShellLoadState>>>& aLoadState,
+ Maybe<bool>& 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(WrapMovingNotNull(RefPtr{CreateLoadInfo(mActiveEntry)}));
+ aReloadActiveEntry.emplace(true);
+ if (aForceReload) {
+ shistory->RemoveFrameEntries(mActiveEntry);
+ }
+ } else if (!mLoadingEntries.IsEmpty()) {
+ const LoadingSessionHistoryEntry& loadingEntry =
+ mLoadingEntries.LastElement();
+ uint64_t loadId = loadingEntry.mLoadId;
+ aLoadState.emplace(
+ WrapMovingNotNull(RefPtr{CreateLoadInfo(loadingEntry.mEntry)}));
+ aReloadActiveEntry.emplace(false);
+ if (aForceReload) {
+ SessionHistoryEntry::LoadingEntry* entry =
+ SessionHistoryEntry::GetByLoadId(loadId);
+ if (entry) {
+ shistory->RemoveFrameEntries(entry->mEntry);
+ }
+ }
+ }
+
+ if (aLoadState) {
+ // Use 0 as the offset, since aLoadState will be be used for reload.
+ aLoadState.ref()->SetLoadIsFromSessionHistory(0,
+ aReloadActiveEntry.value());
+ }
+ // If we don't have an active entry and we don't have a loading entry then
+ // the nsDocShell will create a load state based on its document.
+}
+
+void CanonicalBrowsingContext::SetActiveSessionHistoryEntry(
+ const Maybe<nsPoint>& aPreviousScrollPos, SessionHistoryInfo* aInfo,
+ uint32_t aLoadType, uint32_t aUpdatedCacheKey, const nsID& aChangeID) {
+ nsISHistory* shistory = GetSessionHistory();
+ if (!shistory) {
+ return;
+ }
+ CallerWillNotifyHistoryIndexAndLengthChanges caller(shistory);
+
+ RefPtr<SessionHistoryEntry> 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<int32_t> previousEntryIndex, loadedEntryIndex;
+ shistory->AddToRootSessionHistory(
+ true, oldActiveEntry, this, mActiveEntry, aLoadType,
+ nsDocShell::ShouldAddToSessionHistory(aInfo->GetURI(), nullptr),
+ &previousEntryIndex, &loadedEntryIndex);
+ } else {
+ if (oldActiveEntry) {
+ shistory->AddChildSHEntryHelper(oldActiveEntry, mActiveEntry, Top(),
+ true);
+ } else if (GetParent() && GetParent()->mActiveEntry) {
+ GetParent()->mActiveEntry->AddChild(
+ mActiveEntry, CreatedDynamically() ? -1 : GetParent()->IndexOf(this),
+ UseRemoteSubframes());
+ }
+ }
+
+ ResetSHEntryHasUserInteractionCache();
+
+ shistory->InternalSetRequestedIndex(-1);
+
+ // FIXME Need to do the equivalent of EvictDocumentViewersOrReplaceEntry.
+ HistoryCommitIndexAndLength(aChangeID, caller);
+
+ static_cast<nsSHistory*>(shistory)->LogHistory();
+}
+
+void CanonicalBrowsingContext::ReplaceActiveSessionHistoryEntry(
+ SessionHistoryInfo* aInfo) {
+ if (!mActiveEntry) {
+ return;
+ }
+
+ mActiveEntry->SetInfo(aInfo);
+ // Notify children of the update
+ nsSHistory* shistory = static_cast<nsSHistory*>(GetSessionHistory());
+ if (shistory) {
+ shistory->NotifyOnHistoryReplaceEntry();
+ shistory->UpdateRootBrowsingContextState();
+ }
+
+ ResetSHEntryHasUserInteractionCache();
+
+ if (IsTop()) {
+ mActiveEntry->SetWireframe(Nothing());
+ }
+
+ // FIXME Need to do the equivalent of EvictDocumentViewersOrReplaceEntry.
+}
+
+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<nsISHEntry> root = nsSHistory::GetRootSHEntry(mActiveEntry);
+ shistory->RemoveDynEntries(shistory->GetIndexOfEntry(root), mActiveEntry);
+}
+
+void CanonicalBrowsingContext::RemoveFromSessionHistory(const nsID& aChangeID) {
+ nsSHistory* shistory = static_cast<nsSHistory*>(GetSessionHistory());
+ if (shistory) {
+ CallerWillNotifyHistoryIndexAndLengthChanges caller(shistory);
+ nsCOMPtr<nsISHEntry> root = nsSHistory::GetRootSHEntry(mActiveEntry);
+ bool didRemove;
+ AutoTArray<nsID, 16> ids({GetHistoryID()});
+ shistory->RemoveEntries(ids, shistory->GetIndexOfEntry(root), &didRemove);
+ if (didRemove) {
+ RefPtr<BrowsingContext> rootBC = shistory->GetBrowsingContext();
+ if (rootBC) {
+ if (!rootBC->IsInProcess()) {
+ if (ContentParent* cp = rootBC->Canonical()->GetContentParent()) {
+ Unused << cp->SendDispatchLocationChangeEvent(rootBC);
+ }
+ } else if (rootBC->GetDocShell()) {
+ rootBC->GetDocShell()->DispatchLocationChangeEvent();
+ }
+ }
+ }
+ HistoryCommitIndexAndLength(aChangeID, caller);
+ }
+}
+
+Maybe<int32_t> CanonicalBrowsingContext::HistoryGo(
+ int32_t aOffset, uint64_t aHistoryEpoch, bool aRequireUserInteraction,
+ bool aUserActivation, Maybe<ContentParentId> aContentId) {
+ if (aRequireUserInteraction && aOffset != -1 && aOffset != 1) {
+ NS_ERROR(
+ "aRequireUserInteraction may only be used with an offset of -1 or 1");
+ return Nothing();
+ }
+
+ nsSHistory* shistory = static_cast<nsSHistory*>(GetSessionHistory());
+ if (!shistory) {
+ return Nothing();
+ }
+
+ CheckedInt<int32_t> 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 Nothing();
+ }
+
+ // 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<ContentParentId> 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<nsSHistory::LoadEntryResult> loadResults;
+ nsresult rv = shistory->GotoIndex(index.value(), loadResults, sameEpoch,
+ aOffset == 0, aUserActivation);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("Dropping HistoryGo - bad index or same epoch (not in same doc)"));
+ return Nothing();
+ }
+ if (epoch < aHistoryEpoch || aContentId != id) {
+ MOZ_LOG(gSHLog, LogLevel::Debug, ("Set epoch"));
+ shistory->SetEpoch(aHistoryEpoch, aContentId);
+ }
+ int32_t requestedIndex = shistory->GetRequestedIndex();
+ nsSHistory::LoadURIs(loadResults);
+ return Some(requestedIndex);
+}
+
+JSObject* CanonicalBrowsingContext::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> 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<AsyncEventDispatcher>(
+ element, event, CanBubble::eYes, ChromeOnlyDispatch::eYes);
+ dispatcher->PostDOMEvent();
+}
+
+void CanonicalBrowsingContext::CanonicalDiscard() {
+ if (mTabMediaController) {
+ mTabMediaController->Shutdown();
+ mTabMediaController = nullptr;
+ }
+
+ if (mCurrentLoad) {
+ mCurrentLoad->Cancel(NS_BINDING_ABORTED,
+ "CanonicalBrowsingContext::CanonicalDiscard"_ns);
+ }
+
+ if (mWebProgress) {
+ RefPtr<BrowsingContextWebProgress> progress = mWebProgress;
+ progress->ContextDiscarded();
+ }
+
+ if (IsTop()) {
+ BackgroundSessionStorageManager::RemoveManager(Id());
+ }
+
+ CancelSessionStoreUpdate();
+
+ if (UsePrivateBrowsing() && EverAttached() && IsContent()) {
+ DecreasePrivateCount();
+ }
+}
+
+void CanonicalBrowsingContext::CanonicalAttach() {
+ if (UsePrivateBrowsing() && IsContent()) {
+ IncreasePrivateCount();
+ }
+}
+
+void CanonicalBrowsingContext::AddPendingDiscard() {
+ MOZ_ASSERT(!mFullyDiscarded);
+ mPendingDiscards++;
+}
+
+void CanonicalBrowsingContext::RemovePendingDiscard() {
+ mPendingDiscards--;
+ if (!mPendingDiscards) {
+ mFullyDiscarded = true;
+ auto listeners = std::move(mFullyDiscardedListeners);
+ for (const auto& listener : listeners) {
+ listener(Id());
+ }
+ }
+}
+
+void CanonicalBrowsingContext::AddFinalDiscardListener(
+ std::function<void(uint64_t)>&& aListener) {
+ if (mFullyDiscarded) {
+ aListener(Id());
+ return;
+ }
+ mFullyDiscardedListeners.AppendElement(std::move(aListener));
+}
+
+void CanonicalBrowsingContext::SetForceAppWindowActive(bool aForceActive,
+ ErrorResult& aRv) {
+ MOZ_DIAGNOSTIC_ASSERT(IsChrome());
+ MOZ_DIAGNOSTIC_ASSERT(IsTop());
+ if (!IsChrome() || !IsTop()) {
+ return aRv.ThrowNotAllowedError(
+ "You shouldn't need to force this BrowsingContext to be active, use "
+ ".isActive instead");
+ }
+ if (mForceAppWindowActive == aForceActive) {
+ return;
+ }
+ mForceAppWindowActive = aForceActive;
+ RecomputeAppWindowVisibility();
+}
+
+void CanonicalBrowsingContext::RecomputeAppWindowVisibility() {
+ MOZ_RELEASE_ASSERT(IsChrome());
+ MOZ_RELEASE_ASSERT(IsTop());
+
+ const bool wasAlreadyActive = IsActive();
+
+ nsCOMPtr<nsIWidget> widget;
+ if (auto* docShell = GetDocShell()) {
+ nsDocShell::Cast(docShell)->GetMainWidget(getter_AddRefs(widget));
+ }
+
+ Unused << NS_WARN_IF(!widget);
+ const bool isNowActive =
+ ForceAppWindowActive() || (widget && !widget->IsFullyOccluded() &&
+ widget->SizeMode() != nsSizeMode_Minimized);
+
+ if (isNowActive == wasAlreadyActive) {
+ return;
+ }
+
+ SetIsActiveInternal(isNowActive, IgnoreErrors());
+ if (widget) {
+ // Pause if we are not active, resume if we are active.
+ widget->PauseOrResumeCompositor(!isNowActive);
+ }
+}
+
+void CanonicalBrowsingContext::AdjustPrivateBrowsingCount(
+ bool aPrivateBrowsing) {
+ if (IsDiscarded() || !EverAttached() || IsChrome()) {
+ return;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(aPrivateBrowsing == UsePrivateBrowsing());
+ if (aPrivateBrowsing) {
+ IncreasePrivateCount();
+ } else {
+ DecreasePrivateCount();
+ }
+}
+
+void CanonicalBrowsingContext::NotifyStartDelayedAutoplayMedia() {
+ WindowContext* windowContext = GetCurrentWindowContext();
+ if (!windowContext) {
+ return;
+ }
+
+ // As this function would only be called when user click the play icon on the
+ // tab bar. That's clear user intent to play, so gesture activate the window
+ // context so that the block-autoplay logic allows the media to autoplay.
+ windowContext->NotifyUserGestureActivation();
+ AUTOPLAY_LOG("NotifyStartDelayedAutoplayMedia for chrome bc 0x%08" PRIx64,
+ Id());
+ StartDelayedAutoplayMediaComponents();
+ // Notfiy all content browsing contexts which are related with the canonical
+ // browsing content tree to start delayed autoplay media.
+
+ Group()->EachParent([&](ContentParent* aParent) {
+ Unused << aParent->SendStartDelayedAutoplayMediaComponents(this);
+ });
+}
+
+void CanonicalBrowsingContext::NotifyMediaMutedChanged(bool aMuted,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(!GetParent(),
+ "Notify media mute change on non top-level context!");
+ SetMuted(aMuted, aRv);
+}
+
+uint32_t CanonicalBrowsingContext::CountSiteOrigins(
+ GlobalObject& aGlobal,
+ const Sequence<OwningNonNull<BrowsingContext>>& aRoots) {
+ nsTHashSet<nsCString> uniqueSiteOrigins;
+
+ for (const auto& root : aRoots) {
+ root->PreOrderWalk([&](BrowsingContext* aContext) {
+ WindowGlobalParent* windowGlobalParent =
+ aContext->Canonical()->GetCurrentWindowGlobal();
+ if (windowGlobalParent) {
+ nsIPrincipal* documentPrincipal =
+ windowGlobalParent->DocumentPrincipal();
+
+ bool isContentPrincipal = documentPrincipal->GetIsContentPrincipal();
+ if (isContentPrincipal) {
+ nsCString siteOrigin;
+ documentPrincipal->GetSiteOrigin(siteOrigin);
+ uniqueSiteOrigins.Insert(siteOrigin);
+ }
+ }
+ });
+ }
+
+ return uniqueSiteOrigins.Count();
+}
+
+/* static */
+bool CanonicalBrowsingContext::IsPrivateBrowsingActive() {
+ return gNumberOfPrivateContexts > 0;
+}
+
+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(nsIURI* aURI,
+ const LoadURIOptions& aOptions,
+ ErrorResult& aError) {
+ RefPtr<nsDocShellLoadState> loadState;
+ nsresult rv = nsDocShellLoadState::CreateFromLoadURIOptions(
+ this, aURI, aOptions, getter_AddRefs(loadState));
+ MOZ_ASSERT(rv != NS_ERROR_MALFORMED_URI);
+
+ if (NS_FAILED(rv)) {
+ aError.Throw(rv);
+ return;
+ }
+
+ LoadURI(loadState, true);
+}
+
+void CanonicalBrowsingContext::FixupAndLoadURIString(
+ const nsAString& aURI, const LoadURIOptions& aOptions,
+ ErrorResult& aError) {
+ RefPtr<nsDocShellLoadState> 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<int32_t>& aCancelContentJSEpoch,
+ bool aRequireUserInteraction, bool aUserActivation) {
+ if (IsDiscarded()) {
+ return;
+ }
+
+ // Stop any known network loads if necessary.
+ if (mCurrentLoad) {
+ mCurrentLoad->Cancel(NS_BINDING_CANCELLED_OLD_LOAD, ""_ns);
+ }
+
+ if (RefPtr<nsDocShell> docShell = nsDocShell::Cast(GetDocShell())) {
+ if (aCancelContentJSEpoch.WasPassed()) {
+ docShell->SetCancelContentJSEpoch(aCancelContentJSEpoch.Value());
+ }
+ docShell->GoBack(aRequireUserInteraction, aUserActivation);
+ } else if (ContentParent* cp = GetContentParent()) {
+ Maybe<int32_t> cancelContentJSEpoch;
+ if (aCancelContentJSEpoch.WasPassed()) {
+ cancelContentJSEpoch = Some(aCancelContentJSEpoch.Value());
+ }
+ Unused << cp->SendGoBack(this, cancelContentJSEpoch,
+ aRequireUserInteraction, aUserActivation);
+ }
+}
+void CanonicalBrowsingContext::GoForward(
+ const Optional<int32_t>& aCancelContentJSEpoch,
+ bool aRequireUserInteraction, bool aUserActivation) {
+ if (IsDiscarded()) {
+ return;
+ }
+
+ // Stop any known network loads if necessary.
+ if (mCurrentLoad) {
+ mCurrentLoad->Cancel(NS_BINDING_CANCELLED_OLD_LOAD, ""_ns);
+ }
+
+ if (RefPtr<nsDocShell> docShell = nsDocShell::Cast(GetDocShell())) {
+ if (aCancelContentJSEpoch.WasPassed()) {
+ docShell->SetCancelContentJSEpoch(aCancelContentJSEpoch.Value());
+ }
+ docShell->GoForward(aRequireUserInteraction, aUserActivation);
+ } else if (ContentParent* cp = GetContentParent()) {
+ Maybe<int32_t> cancelContentJSEpoch;
+ if (aCancelContentJSEpoch.WasPassed()) {
+ cancelContentJSEpoch.emplace(aCancelContentJSEpoch.Value());
+ }
+ Unused << cp->SendGoForward(this, cancelContentJSEpoch,
+ aRequireUserInteraction, aUserActivation);
+ }
+}
+void CanonicalBrowsingContext::GoToIndex(
+ int32_t aIndex, const Optional<int32_t>& aCancelContentJSEpoch,
+ bool aUserActivation) {
+ if (IsDiscarded()) {
+ return;
+ }
+
+ // Stop any known network loads if necessary.
+ if (mCurrentLoad) {
+ mCurrentLoad->Cancel(NS_BINDING_CANCELLED_OLD_LOAD, ""_ns);
+ }
+
+ if (RefPtr<nsDocShell> docShell = nsDocShell::Cast(GetDocShell())) {
+ if (aCancelContentJSEpoch.WasPassed()) {
+ docShell->SetCancelContentJSEpoch(aCancelContentJSEpoch.Value());
+ }
+ docShell->GotoIndex(aIndex, aUserActivation);
+ } else if (ContentParent* cp = GetContentParent()) {
+ Maybe<int32_t> cancelContentJSEpoch;
+ if (aCancelContentJSEpoch.WasPassed()) {
+ cancelContentJSEpoch.emplace(aCancelContentJSEpoch.Value());
+ }
+ Unused << cp->SendGoToIndex(this, aIndex, cancelContentJSEpoch,
+ aUserActivation);
+ }
+}
+void CanonicalBrowsingContext::Reload(uint32_t aReloadFlags) {
+ if (IsDiscarded()) {
+ return;
+ }
+
+ // Stop any known network loads if necessary.
+ if (mCurrentLoad) {
+ mCurrentLoad->Cancel(NS_BINDING_CANCELLED_OLD_LOAD, ""_ns);
+ }
+
+ if (RefPtr<nsDocShell> 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,
+ "CanonicalBrowsingContext::Stop"_ns);
+ }
+
+ // Ask the docshell to stop to handle loads that haven't
+ // yet reached here, as well as non-network activity.
+ if (auto* docShell = nsDocShell::Cast(GetDocShell())) {
+ docShell->Stop(aStopFlags);
+ } else if (ContentParent* cp = GetContentParent()) {
+ Unused << cp->SendStopLoad(this, aStopFlags);
+ }
+}
+
+void CanonicalBrowsingContext::PendingRemotenessChange::ProcessLaunched() {
+ if (!mPromise) {
+ return;
+ }
+
+ if (mContentParent) {
+ // If our new content process is still unloading from a previous process
+ // switch, wait for that unload to complete before continuing.
+ auto found = mTarget->FindUnloadingHost(mContentParent->ChildID());
+ if (found != mTarget->mUnloadingHosts.end()) {
+ found->mCallbacks.AppendElement(
+ [self = RefPtr{this}]() { self->ProcessReady(); });
+ return;
+ }
+ }
+
+ ProcessReady();
+}
+
+void CanonicalBrowsingContext::PendingRemotenessChange::ProcessReady() {
+ if (!mPromise) {
+ return;
+ }
+
+ MOZ_ASSERT(!mProcessReady);
+ mProcessReady = true;
+ MaybeFinish();
+}
+
+void CanonicalBrowsingContext::PendingRemotenessChange::MaybeFinish() {
+ if (!mPromise) {
+ return;
+ }
+
+ if (!mProcessReady || mWaitingForPrepareToChange) {
+ return;
+ }
+
+ // If this BrowsingContext is embedded within the parent process, perform the
+ // process switch directly.
+ nsresult rv = mTarget->IsTopContent() ? FinishTopContent() : FinishSubframe();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Error finishing PendingRemotenessChange!");
+ Cancel(rv);
+ } else {
+ Clear();
+ }
+}
+
+// Logic for finishing a toplevel process change embedded within the parent
+// process. Due to frontend integration the logic differs substantially from
+// subframe process switches, and is handled separately.
+nsresult CanonicalBrowsingContext::PendingRemotenessChange::FinishTopContent() {
+ MOZ_DIAGNOSTIC_ASSERT(mTarget->IsTop(),
+ "We shouldn't be trying to change the remoteness of "
+ "non-remote iframes");
+
+ // Abort if our ContentParent died while process switching.
+ if (mContentParent && NS_WARN_IF(mContentParent->IsShuttingDown())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // While process switching, we need to check if any of our ancestors are
+ // discarded or no longer current, in which case the process switch needs to
+ // be aborted.
+ RefPtr<CanonicalBrowsingContext> target(mTarget);
+ if (target->IsDiscarded() || !target->AncestorsAreCurrent()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ Element* browserElement = target->GetEmbedderElement();
+ if (!browserElement) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIBrowser> browser = browserElement->AsBrowser();
+ if (!browser) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<nsFrameLoaderOwner> frameLoaderOwner = do_QueryObject(browserElement);
+ MOZ_RELEASE_ASSERT(frameLoaderOwner,
+ "embedder browser must be nsFrameLoaderOwner");
+
+ // If we're process switching a browsing context in private browsing
+ // mode we might decrease the private browsing count to '0', which
+ // would make us fire "last-pb-context-exited" and drop the private
+ // session. To prevent that we artificially increment the number of
+ // private browsing contexts with '1' until the process switch is done.
+ bool usePrivateBrowsing = mTarget->UsePrivateBrowsing();
+ if (usePrivateBrowsing) {
+ IncreasePrivateCount();
+ }
+
+ auto restorePrivateCount = MakeScopeExit([usePrivateBrowsing]() {
+ if (usePrivateBrowsing) {
+ DecreasePrivateCount();
+ }
+ });
+
+ // Tell frontend code that this browser element is about to change process.
+ nsresult rv = browser->BeforeChangeRemoteness();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Some frontend code checks the value of the `remote` attribute on the
+ // browser to determine if it is remote, so update the value.
+ browserElement->SetAttr(kNameSpaceID_None, nsGkAtoms::remote,
+ mContentParent ? u"true"_ns : u"false"_ns,
+ /* notify */ true);
+
+ // The process has been created, hand off to nsFrameLoaderOwner to finish
+ // the process switch.
+ ErrorResult error;
+ frameLoaderOwner->ChangeRemotenessToProcess(mContentParent, mOptions,
+ mSpecificGroup, error);
+ if (error.Failed()) {
+ return error.StealNSResult();
+ }
+
+ // Tell frontend the load is done.
+ bool loadResumed = false;
+ rv = browser->FinishChangeRemoteness(mPendingSwitchId, &loadResumed);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // We did it! The process switch is complete.
+ RefPtr<nsFrameLoader> frameLoader = frameLoaderOwner->GetFrameLoader();
+ RefPtr<BrowserParent> newBrowser = frameLoader->GetBrowserParent();
+ if (!newBrowser) {
+ if (mContentParent) {
+ // Failed to create the BrowserParent somehow! Abort the process switch
+ // attempt.
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (!loadResumed) {
+ RefPtr<nsDocShell> newDocShell = frameLoader->GetDocShell(error);
+ if (error.Failed()) {
+ return error.StealNSResult();
+ }
+
+ rv = newDocShell->ResumeRedirectedLoad(mPendingSwitchId,
+ /* aHistoryIndex */ -1);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ } else if (!loadResumed) {
+ newBrowser->ResumeLoad(mPendingSwitchId);
+ }
+
+ mPromise->Resolve(newBrowser, __func__);
+ return NS_OK;
+}
+
+nsresult CanonicalBrowsingContext::PendingRemotenessChange::FinishSubframe() {
+ MOZ_DIAGNOSTIC_ASSERT(!mOptions.mReplaceBrowsingContext,
+ "Cannot replace BC for subframe");
+ MOZ_DIAGNOSTIC_ASSERT(!mTarget->IsTop());
+
+ // While process switching, we need to check if any of our ancestors are
+ // discarded or no longer current, in which case the process switch needs to
+ // be aborted.
+ RefPtr<CanonicalBrowsingContext> target(mTarget);
+ if (target->IsDiscarded() || !target->AncestorsAreCurrent()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (NS_WARN_IF(!mContentParent)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<WindowGlobalParent> embedderWindow = target->GetParentWindowContext();
+ if (NS_WARN_IF(!embedderWindow) || NS_WARN_IF(!embedderWindow->CanSend())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<BrowserParent> embedderBrowser = embedderWindow->GetBrowserParent();
+ if (NS_WARN_IF(!embedderBrowser)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // If we're creating a new remote browser, and the host process is already
+ // dead, abort the process switch.
+ if (mContentParent != embedderBrowser->Manager() &&
+ NS_WARN_IF(mContentParent->IsShuttingDown())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<BrowserParent> oldBrowser = target->GetBrowserParent();
+ target->SetCurrentBrowserParent(nullptr);
+
+ // If we were in a remote frame, trigger unloading of the remote window. The
+ // previous BrowserParent is registered in `mUnloadingHosts` and will only be
+ // cleared when the BrowserParent is fully destroyed.
+ bool wasRemote = oldBrowser && oldBrowser->GetBrowsingContext() == target;
+ if (wasRemote) {
+ MOZ_DIAGNOSTIC_ASSERT(oldBrowser != embedderBrowser);
+ MOZ_DIAGNOSTIC_ASSERT(oldBrowser->IsDestroyed() ||
+ oldBrowser->GetBrowserBridgeParent());
+
+ // `oldBrowser` will clear the `UnloadingHost` status once the actor has
+ // been destroyed.
+ if (oldBrowser->CanSend()) {
+ target->StartUnloadingHost(oldBrowser->Manager()->ChildID());
+ Unused << oldBrowser->SendWillChangeProcess();
+ oldBrowser->Destroy();
+ }
+ }
+
+ // Update which process is considered the current owner
+ target->SetOwnerProcessId(mContentParent->ChildID());
+
+ // If we're switching from remote to local, we don't need to create a
+ // BrowserBridge, and can instead perform the switch directly.
+ if (mContentParent == embedderBrowser->Manager()) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ mPendingSwitchId,
+ "We always have a PendingSwitchId, except for print-preview loads, "
+ "which will never perform a process-switch to being in-process with "
+ "their embedder");
+ MOZ_DIAGNOSTIC_ASSERT(wasRemote,
+ "Attempt to process-switch from local to local?");
+
+ target->SetCurrentBrowserParent(embedderBrowser);
+ Unused << embedderWindow->SendMakeFrameLocal(target, mPendingSwitchId);
+ mPromise->Resolve(embedderBrowser, __func__);
+ return NS_OK;
+ }
+
+ // The BrowsingContext will be remote, either as an already-remote frame
+ // changing processes, or as a local frame becoming remote. Construct a new
+ // BrowserBridgeParent to host the remote content.
+ target->SetCurrentBrowserParent(nullptr);
+
+ MOZ_DIAGNOSTIC_ASSERT(target->UseRemoteTabs() && target->UseRemoteSubframes(),
+ "Not supported without fission");
+ uint32_t chromeFlags = nsIWebBrowserChrome::CHROME_REMOTE_WINDOW |
+ nsIWebBrowserChrome::CHROME_FISSION_WINDOW;
+ if (target->UsePrivateBrowsing()) {
+ chromeFlags |= nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW;
+ }
+
+ nsCOMPtr<nsIPrincipal> initialPrincipal =
+ NullPrincipal::Create(target->OriginAttributesRef());
+ WindowGlobalInit windowInit =
+ WindowGlobalActor::AboutBlankInitializer(target, initialPrincipal);
+
+ // Create and initialize our new BrowserBridgeParent.
+ TabId tabId(nsContentUtils::GenerateTabId());
+ RefPtr<BrowserBridgeParent> bridge = new BrowserBridgeParent();
+ nsresult rv = bridge->InitWithProcess(embedderBrowser, mContentParent,
+ windowInit, chromeFlags, tabId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // If we've already destroyed our previous document, make a best-effort
+ // attempt to recover from this failure and show the crashed tab UI. We only
+ // do this in the previously-remote case, as previously in-process frames
+ // will have their navigation cancelled, and will remain visible.
+ if (wasRemote) {
+ target->ShowSubframeCrashedUI(oldBrowser->GetBrowserBridgeParent());
+ }
+ return rv;
+ }
+
+ // Tell the embedder process a remoteness change is in-process. When this is
+ // acknowledged, reset the in-flight ID if it used to be an in-process load.
+ RefPtr<BrowserParent> newBrowser = bridge->GetBrowserParent();
+ {
+ // If we weren't remote, mark our embedder window browser as unloading until
+ // our embedder process has acked our MakeFrameRemote message.
+ Maybe<uint64_t> clearChildID;
+ if (!wasRemote) {
+ clearChildID = Some(embedderBrowser->Manager()->ChildID());
+ target->StartUnloadingHost(*clearChildID);
+ }
+ auto callback = [target, clearChildID](auto&&) {
+ if (clearChildID) {
+ target->ClearUnloadingHost(*clearChildID);
+ }
+ };
+
+ ManagedEndpoint<PBrowserBridgeChild> endpoint =
+ embedderBrowser->OpenPBrowserBridgeEndpoint(bridge);
+ MOZ_DIAGNOSTIC_ASSERT(endpoint.IsValid());
+ embedderWindow->SendMakeFrameRemote(target, std::move(endpoint), tabId,
+ newBrowser->GetLayersId(), callback,
+ callback);
+ }
+
+ // Resume the pending load in our new process.
+ if (mPendingSwitchId) {
+ newBrowser->ResumeLoad(mPendingSwitchId);
+ }
+
+ // We did it! The process switch is complete.
+ mPromise->Resolve(newBrowser, __func__);
+ return NS_OK;
+}
+
+void CanonicalBrowsingContext::PendingRemotenessChange::Cancel(nsresult aRv) {
+ if (!mPromise) {
+ return;
+ }
+
+ mPromise->Reject(aRv, __func__);
+ Clear();
+}
+
+void CanonicalBrowsingContext::PendingRemotenessChange::Clear() {
+ // Make sure we don't die while we're doing cleanup.
+ RefPtr<PendingRemotenessChange> kungFuDeathGrip(this);
+ if (mTarget) {
+ MOZ_DIAGNOSTIC_ASSERT(mTarget->mPendingRemotenessChange == this);
+ mTarget->mPendingRemotenessChange = nullptr;
+ }
+
+ // When this PendingRemotenessChange was created, it was given a
+ // `mContentParent`.
+ if (mContentParent) {
+ mContentParent->RemoveKeepAlive();
+ mContentParent = nullptr;
+ }
+
+ // If we were given a specific group, stop keeping that group alive manually.
+ if (mSpecificGroup) {
+ mSpecificGroup->RemoveKeepAlive();
+ mSpecificGroup = nullptr;
+ }
+
+ mPromise = nullptr;
+ mTarget = nullptr;
+}
+
+CanonicalBrowsingContext::PendingRemotenessChange::PendingRemotenessChange(
+ CanonicalBrowsingContext* aTarget, RemotenessPromise::Private* aPromise,
+ uint64_t aPendingSwitchId, const NavigationIsolationOptions& aOptions)
+ : mTarget(aTarget),
+ mPromise(aPromise),
+ mPendingSwitchId(aPendingSwitchId),
+ mOptions(aOptions) {}
+
+CanonicalBrowsingContext::PendingRemotenessChange::~PendingRemotenessChange() {
+ MOZ_ASSERT(!mPromise && !mTarget && !mContentParent && !mSpecificGroup,
+ "should've already been Cancel() or Complete()-ed");
+}
+
+BrowserParent* CanonicalBrowsingContext::GetBrowserParent() const {
+ return mCurrentBrowserParent;
+}
+
+void CanonicalBrowsingContext::SetCurrentBrowserParent(
+ BrowserParent* aBrowserParent) {
+ MOZ_DIAGNOSTIC_ASSERT(!mCurrentBrowserParent || !aBrowserParent,
+ "BrowsingContext already has a current BrowserParent!");
+ MOZ_DIAGNOSTIC_ASSERT_IF(aBrowserParent, aBrowserParent->CanSend());
+ MOZ_DIAGNOSTIC_ASSERT_IF(aBrowserParent,
+ aBrowserParent->Manager()->ChildID() == mProcessId);
+
+ // BrowserParent must either be directly for this BrowsingContext, or the
+ // manager out our embedder WindowGlobal.
+ MOZ_DIAGNOSTIC_ASSERT_IF(
+ aBrowserParent && aBrowserParent->GetBrowsingContext() != this,
+ GetParentWindowContext() &&
+ GetParentWindowContext()->Manager() == aBrowserParent);
+
+ if (aBrowserParent && IsTopContent() && !ManuallyManagesActiveness()) {
+ aBrowserParent->SetRenderLayers(IsActive());
+ }
+
+ mCurrentBrowserParent = aBrowserParent;
+}
+
+bool CanonicalBrowsingContext::ManuallyManagesActiveness() const {
+ auto* el = GetEmbedderElement();
+ return el && el->IsXULElement() && el->HasAttr(nsGkAtoms::manualactiveness);
+}
+
+RefPtr<CanonicalBrowsingContext::RemotenessPromise>
+CanonicalBrowsingContext::ChangeRemoteness(
+ const NavigationIsolationOptions& aOptions, uint64_t aPendingSwitchId) {
+ MOZ_DIAGNOSTIC_ASSERT(IsContent(),
+ "cannot change the process of chrome contexts");
+ MOZ_DIAGNOSTIC_ASSERT(
+ IsTop() == IsEmbeddedInProcess(0),
+ "toplevel content must be embedded in the parent process");
+ MOZ_DIAGNOSTIC_ASSERT(!aOptions.mReplaceBrowsingContext || IsTop(),
+ "Cannot replace BrowsingContext for subframes");
+ MOZ_DIAGNOSTIC_ASSERT(
+ aOptions.mSpecificGroupId == 0 || aOptions.mReplaceBrowsingContext,
+ "Cannot specify group ID unless replacing BC");
+ MOZ_DIAGNOSTIC_ASSERT(aPendingSwitchId || !IsTop(),
+ "Should always have aPendingSwitchId for top-level "
+ "frames");
+
+ if (!AncestorsAreCurrent()) {
+ NS_WARNING("An ancestor context is no longer current");
+ return RemotenessPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ // Ensure our embedder hasn't been destroyed or asked to shutdown already.
+ RefPtr<WindowGlobalParent> 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__);
+ }
+
+ RefPtr<BrowserParent> embedderBrowser =
+ embedderWindowGlobal->GetBrowserParent();
+ if (embedderBrowser && embedderBrowser->Manager()->IsShuttingDown()) {
+ NS_WARNING("Embedder already asked to shutdown.");
+ return RemotenessPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__);
+ }
+
+ if (aOptions.mRemoteType.IsEmpty() && (!IsTop() || !GetEmbedderElement())) {
+ NS_WARNING("Cannot load non-remote subframes");
+ return RemotenessPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ // Cancel ongoing remoteness changes.
+ if (mPendingRemotenessChange) {
+ mPendingRemotenessChange->Cancel(NS_ERROR_ABORT);
+ MOZ_DIAGNOSTIC_ASSERT(!mPendingRemotenessChange, "Should have cleared");
+ }
+
+ auto promise = MakeRefPtr<RemotenessPromise::Private>(__func__);
+ promise->UseDirectTaskDispatch(__func__);
+
+ RefPtr<PendingRemotenessChange> change =
+ new PendingRemotenessChange(this, promise, aPendingSwitchId, aOptions);
+ mPendingRemotenessChange = change;
+
+ // If a specific BrowsingContextGroup ID was specified for this load, make
+ // sure to keep it alive until the process switch is completed.
+ if (aOptions.mSpecificGroupId) {
+ change->mSpecificGroup =
+ BrowsingContextGroup::GetOrCreate(aOptions.mSpecificGroupId);
+ change->mSpecificGroup->AddKeepAlive();
+ }
+
+ // Call `prepareToChangeRemoteness` in parallel with starting a new process
+ // for <browser> loads.
+ if (IsTop() && GetEmbedderElement()) {
+ nsCOMPtr<nsIBrowser> browser = GetEmbedderElement()->AsBrowser();
+ if (!browser) {
+ change->Cancel(NS_ERROR_FAILURE);
+ return promise.forget();
+ }
+
+ RefPtr<Promise> blocker;
+ nsresult rv = browser->PrepareToChangeRemoteness(getter_AddRefs(blocker));
+ if (NS_FAILED(rv)) {
+ change->Cancel(rv);
+ return promise.forget();
+ }
+
+ // Mark prepareToChange as unresolved, and wait for it to become resolved.
+ if (blocker && blocker->State() != Promise::PromiseState::Resolved) {
+ change->mWaitingForPrepareToChange = true;
+ blocker->AddCallbacksWithCycleCollectedArgs(
+ [change](JSContext*, JS::Handle<JS::Value>, ErrorResult&) {
+ change->mWaitingForPrepareToChange = false;
+ change->MaybeFinish();
+ },
+ [change](JSContext*, JS::Handle<JS::Value> aValue, ErrorResult&) {
+ change->Cancel(
+ Promise::TryExtractNSResultFromRejectionValue(aValue));
+ });
+ }
+ }
+
+ // Switching a subframe to be local within it's embedding process.
+ if (embedderBrowser &&
+ aOptions.mRemoteType == embedderBrowser->Manager()->GetRemoteType()) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ aPendingSwitchId,
+ "We always have a PendingSwitchId, except for print-preview loads, "
+ "which will never perform a process-switch to being in-process with "
+ "their embedder");
+ MOZ_DIAGNOSTIC_ASSERT(!aOptions.mReplaceBrowsingContext);
+ MOZ_DIAGNOSTIC_ASSERT(!aOptions.mRemoteType.IsEmpty());
+ MOZ_DIAGNOSTIC_ASSERT(!change->mWaitingForPrepareToChange);
+ MOZ_DIAGNOSTIC_ASSERT(!change->mSpecificGroup);
+
+ // Switching to local, so we don't need to create a new process, and will
+ // instead use our embedder process.
+ change->mContentParent = embedderBrowser->Manager();
+ change->mContentParent->AddKeepAlive();
+ change->ProcessLaunched();
+ return promise.forget();
+ }
+
+ // Switching to the parent process.
+ if (aOptions.mRemoteType.IsEmpty()) {
+ change->ProcessLaunched();
+ return promise.forget();
+ }
+
+ // If we're aiming to end up in a new process of the same type as our old
+ // process, and then putting our previous document in the BFCache, try to stay
+ // in the same process to avoid creating new processes unnecessarily.
+ RefPtr<ContentParent> existingProcess = GetContentParent();
+ if (existingProcess && !existingProcess->IsShuttingDown() &&
+ aOptions.mReplaceBrowsingContext &&
+ aOptions.mRemoteType == existingProcess->GetRemoteType()) {
+ change->mContentParent = existingProcess;
+ change->mContentParent->AddKeepAlive();
+ change->ProcessLaunched();
+ return promise.forget();
+ }
+
+ // Try to predict which BrowsingContextGroup will be used for the final load
+ // in this BrowsingContext. This has to be accurate if switching into an
+ // existing group, as it will control what pool of processes will be used
+ // for process selection.
+ //
+ // It's _technically_ OK to provide a group here if we're actually going to
+ // switch into a brand new group, though it's sub-optimal, as it can
+ // restrict the set of processes we're using.
+ BrowsingContextGroup* finalGroup =
+ aOptions.mReplaceBrowsingContext ? change->mSpecificGroup.get() : Group();
+
+ bool preferUsed =
+ StaticPrefs::browser_tabs_remote_subframesPreferUsed() && !IsTop();
+
+ change->mContentParent = ContentParent::GetNewOrUsedLaunchingBrowserProcess(
+ /* aRemoteType = */ aOptions.mRemoteType,
+ /* aGroup = */ finalGroup,
+ /* aPriority = */ hal::PROCESS_PRIORITY_FOREGROUND,
+ /* aPreferUsed = */ preferUsed);
+ if (!change->mContentParent) {
+ change->Cancel(NS_ERROR_FAILURE);
+ return promise.forget();
+ }
+
+ // Add a KeepAlive used by this ContentParent, which will be cleared when
+ // the change is complete. This should prevent the process dying before
+ // we're ready to use it.
+ change->mContentParent->AddKeepAlive();
+ if (change->mContentParent->IsLaunching()) {
+ change->mContentParent->WaitForLaunchAsync()->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [change](ContentParent*) { change->ProcessLaunched(); },
+ [change]() { change->Cancel(NS_ERROR_FAILURE); });
+ } else {
+ change->ProcessLaunched();
+ }
+ return promise.forget();
+}
+
+void CanonicalBrowsingContext::MaybeSetPermanentKey(Element* aEmbedder) {
+ MOZ_DIAGNOSTIC_ASSERT(IsTop());
+
+ if (aEmbedder) {
+ if (nsCOMPtr<nsIBrowser> browser = aEmbedder->AsBrowser()) {
+ JS::Rooted<JS::Value> key(RootingCx());
+ if (NS_SUCCEEDED(browser->GetPermanentKey(&key)) && key.isObject()) {
+ mPermanentKey = key;
+ }
+ }
+ }
+}
+
+MediaController* CanonicalBrowsingContext::GetMediaController() {
+ // We would only create one media controller per tab, so accessing the
+ // controller via the top-level browsing context.
+ if (GetParent()) {
+ return Cast(Top())->GetMediaController();
+ }
+
+ MOZ_ASSERT(!GetParent(),
+ "Must access the controller from the top-level browsing context!");
+ // Only content browsing context can create media controller, we won't create
+ // controller for chrome document, such as the browser UI.
+ if (!mTabMediaController && !IsDiscarded() && IsContent()) {
+ mTabMediaController = new MediaController(Id());
+ }
+ return mTabMediaController;
+}
+
+bool CanonicalBrowsingContext::HasCreatedMediaController() const {
+ return !!mTabMediaController;
+}
+
+bool CanonicalBrowsingContext::SupportsLoadingInParent(
+ nsDocShellLoadState* aLoadState, uint64_t* aOuterWindowId) {
+ // We currently don't support initiating loads in the parent when they are
+ // watched by devtools. This is because devtools tracks loads using content
+ // process notifications, which happens after the load is initiated in this
+ // case. Devtools clears all prior requests when it detects a new navigation,
+ // so it drops the main document load that happened here.
+ if (WatchedByDevTools()) {
+ return false;
+ }
+
+ // Session-history-in-parent implementation relies currently on getting a
+ // round trip through a child process.
+ if (aLoadState->LoadIsFromSessionHistory()) {
+ return false;
+ }
+
+ // DocumentChannel currently only supports connecting channels into the
+ // content process, so we can only support schemes that will always be loaded
+ // there for now. Restrict to just http(s) for simplicity.
+ if (!net::SchemeIsHTTP(aLoadState->URI()) &&
+ !net::SchemeIsHTTPS(aLoadState->URI())) {
+ return false;
+ }
+
+ if (WindowGlobalParent* global = GetCurrentWindowGlobal()) {
+ nsCOMPtr<nsIURI> 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;
+ }
+
+ MOZ_ASSERT(!net::SchemeIsJavascript(aLoadState->URI()));
+
+ MOZ_ALWAYS_SUCCEEDS(
+ SetParentInitiatedNavigationEpoch(++gParentInitiatedNavigationEpoch));
+ // Note: If successful, this will recurse into StartDocumentLoad and
+ // set mCurrentLoad to the DocumentLoadListener instance created.
+ // Ideally in the future we will only start loads from here, and we can
+ // just set this directly instead.
+ return net::DocumentLoadListener::LoadInParent(this, aLoadState,
+ aSetNavigating);
+}
+
+bool CanonicalBrowsingContext::AttemptSpeculativeLoadInParent(
+ nsDocShellLoadState* aLoadState) {
+ // We currently only support starting loads directly from the
+ // CanonicalBrowsingContext for top-level BCs.
+ // We currently only support starting loads directly from the
+ // CanonicalBrowsingContext for top-level BCs.
+ if (!IsTopContent() || !GetContentParent() ||
+ (StaticPrefs::browser_tabs_documentchannel_parent_controlled())) {
+ return false;
+ }
+
+ uint64_t outerWindowId = 0;
+ if (!SupportsLoadingInParent(aLoadState, &outerWindowId)) {
+ return false;
+ }
+
+ // If we successfully open the DocumentChannel, then it'll register
+ // itself using aLoadIdentifier and be kept alive until it completes
+ // loading.
+ return net::DocumentLoadListener::SpeculativeLoadInParent(this, aLoadState);
+}
+
+bool CanonicalBrowsingContext::StartDocumentLoad(
+ net::DocumentLoadListener* aLoad) {
+ // If we're controlling loads from the parent, then starting a new load means
+ // that we need to cancel any existing ones.
+ if (StaticPrefs::browser_tabs_documentchannel_parent_controlled() &&
+ mCurrentLoad) {
+ // Make sure we are not loading a javascript URI.
+ MOZ_ASSERT(!aLoad->IsLoadingJSURI());
+
+ // If we want to do a download, don't cancel the current navigation.
+ if (!aLoad->IsDownload()) {
+ mCurrentLoad->Cancel(NS_BINDING_CANCELLED_OLD_LOAD, ""_ns);
+ }
+ }
+ mCurrentLoad = aLoad;
+
+ if (NS_FAILED(SetCurrentLoadIdentifier(Some(aLoad->GetLoadIdentifier())))) {
+ mCurrentLoad = nullptr;
+ return false;
+ }
+
+ return true;
+}
+
+void CanonicalBrowsingContext::EndDocumentLoad(bool aContinueNavigating) {
+ mCurrentLoad = nullptr;
+
+ if (!aContinueNavigating) {
+ // Resetting the current load identifier on a discarded context
+ // has no effect when a document load has finished.
+ Unused << SetCurrentLoadIdentifier(Nothing());
+ }
+}
+
+already_AddRefed<nsIURI> CanonicalBrowsingContext::GetCurrentURI() const {
+ nsCOMPtr<nsIURI> currentURI;
+ if (nsIDocShell* docShell = GetDocShell()) {
+ MOZ_ALWAYS_SUCCEEDS(
+ nsDocShell::Cast(docShell)->GetCurrentURI(getter_AddRefs(currentURI)));
+ } else {
+ currentURI = mCurrentRemoteURI;
+ }
+ return currentURI.forget();
+}
+
+void CanonicalBrowsingContext::SetCurrentRemoteURI(nsIURI* aCurrentRemoteURI) {
+ MOZ_ASSERT(!GetDocShell());
+ mCurrentRemoteURI = aCurrentRemoteURI;
+}
+
+void CanonicalBrowsingContext::ResetSHEntryHasUserInteractionCache() {
+ WindowContext* topWc = GetTopWindowContext();
+ if (topWc && !topWc->IsDiscarded()) {
+ MOZ_ALWAYS_SUCCEEDS(topWc->SetSHEntryHasUserInteraction(false));
+ }
+}
+
+void CanonicalBrowsingContext::HistoryCommitIndexAndLength() {
+ nsID changeID = {};
+ CallerWillNotifyHistoryIndexAndLengthChanges caller(nullptr);
+ HistoryCommitIndexAndLength(changeID, caller);
+}
+void CanonicalBrowsingContext::HistoryCommitIndexAndLength(
+ const nsID& aChangeID,
+ const CallerWillNotifyHistoryIndexAndLengthChanges& aProofOfCaller) {
+ if (!IsTop()) {
+ Cast(Top())->HistoryCommitIndexAndLength(aChangeID, aProofOfCaller);
+ return;
+ }
+
+ nsISHistory* shistory = GetSessionHistory();
+ if (!shistory) {
+ return;
+ }
+ int32_t index = 0;
+ shistory->GetIndex(&index);
+ int32_t length = shistory->GetCount();
+
+ GetChildSessionHistory()->SetIndexAndLength(index, length, aChangeID);
+
+ shistory->EvictOutOfRangeDocumentViewers(index);
+
+ Group()->EachParent([&](ContentParent* aParent) {
+ Unused << aParent->SendHistoryCommitIndexAndLength(this, index, length,
+ aChangeID);
+ });
+}
+
+void CanonicalBrowsingContext::SynchronizeLayoutHistoryState() {
+ if (mActiveEntry) {
+ if (IsInProcess()) {
+ nsIDocShell* docShell = GetDocShell();
+ if (docShell) {
+ docShell->PersistLayoutHistoryState();
+
+ nsCOMPtr<nsILayoutHistoryState> state;
+ docShell->GetLayoutHistoryState(getter_AddRefs(state));
+ if (state) {
+ mActiveEntry->SetLayoutHistoryState(state);
+ }
+ }
+ } else if (ContentParent* cp = GetContentParent()) {
+ cp->SendGetLayoutHistoryState(this)->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [activeEntry = mActiveEntry](
+ const std::tuple<RefPtr<nsILayoutHistoryState>, Maybe<Wireframe>>&
+ aResult) {
+ if (std::get<0>(aResult)) {
+ activeEntry->SetLayoutHistoryState(std::get<0>(aResult));
+ }
+ if (std::get<1>(aResult)) {
+ activeEntry->SetWireframe(std::get<1>(aResult));
+ }
+ },
+ []() {});
+ }
+ }
+}
+
+void CanonicalBrowsingContext::ResetScalingZoom() {
+ // This currently only ever gets called in the parent process, and we
+ // pass the message on to the WindowGlobalChild for the rootmost browsing
+ // context.
+ if (WindowGlobalParent* topWindow = GetTopWindowContext()) {
+ Unused << topWindow->SendResetScalingZoom();
+ }
+}
+
+void CanonicalBrowsingContext::SetRestoreData(SessionStoreRestoreData* aData,
+ ErrorResult& aError) {
+ MOZ_DIAGNOSTIC_ASSERT(aData);
+
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+ RefPtr<Promise> promise = Promise::Create(global, aError);
+ if (aError.Failed()) {
+ return;
+ }
+
+ if (NS_WARN_IF(NS_FAILED(SetHasRestoreData(true)))) {
+ aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ mRestoreState = new RestoreState();
+ mRestoreState->mData = aData;
+ mRestoreState->mPromise = promise;
+}
+
+already_AddRefed<Promise> CanonicalBrowsingContext::GetRestorePromise() {
+ if (mRestoreState) {
+ return do_AddRef(mRestoreState->mPromise);
+ }
+ return nullptr;
+}
+
+void CanonicalBrowsingContext::ClearRestoreState() {
+ if (!mRestoreState) {
+ MOZ_DIAGNOSTIC_ASSERT(!GetHasRestoreData());
+ return;
+ }
+ if (mRestoreState->mPromise) {
+ mRestoreState->mPromise->MaybeRejectWithUndefined();
+ }
+ mRestoreState = nullptr;
+ MOZ_ALWAYS_SUCCEEDS(SetHasRestoreData(false));
+}
+
+void CanonicalBrowsingContext::RequestRestoreTabContent(
+ WindowGlobalParent* aWindow) {
+ MOZ_DIAGNOSTIC_ASSERT(IsTop());
+
+ if (IsDiscarded() || !mRestoreState || !mRestoreState->mData) {
+ return;
+ }
+
+ CanonicalBrowsingContext* context = aWindow->GetBrowsingContext();
+ MOZ_DIAGNOSTIC_ASSERT(!context->IsDiscarded());
+
+ RefPtr<SessionStoreRestoreData> data =
+ mRestoreState->mData->FindDataForChild(context);
+
+ if (context->IsTop()) {
+ MOZ_DIAGNOSTIC_ASSERT(context == this);
+
+ // We need to wait until the appropriate load event has fired before we
+ // can "complete" the restore process, so if we're holding an empty data
+ // object, just resolve the promise immediately.
+ if (mRestoreState->mData->IsEmpty()) {
+ MOZ_DIAGNOSTIC_ASSERT(!data || data->IsEmpty());
+ mRestoreState->Resolve();
+ ClearRestoreState();
+ return;
+ }
+
+ // Since we're following load event order, we'll only arrive here for a
+ // toplevel context after we've already sent down data for all child frames,
+ // so it's safe to clear this reference now. The completion callback below
+ // relies on the mData field being null to determine if all requests have
+ // been sent out.
+ mRestoreState->ClearData();
+ MOZ_ALWAYS_SUCCEEDS(SetHasRestoreData(false));
+ }
+
+ if (data && !data->IsEmpty()) {
+ auto onTabRestoreComplete = [self = RefPtr{this},
+ state = RefPtr{mRestoreState}](auto) {
+ state->mResolves++;
+ if (!state->mData && state->mRequests == state->mResolves) {
+ state->Resolve();
+ if (state == self->mRestoreState) {
+ self->ClearRestoreState();
+ }
+ }
+ };
+
+ mRestoreState->mRequests++;
+
+ if (data->CanRestoreInto(aWindow->GetDocumentURI())) {
+ if (!aWindow->IsInProcess()) {
+ aWindow->SendRestoreTabContent(WrapNotNull(data.get()),
+ onTabRestoreComplete,
+ onTabRestoreComplete);
+ return;
+ }
+ data->RestoreInto(context);
+ }
+
+ // This must be called both when we're doing an in-process restore, and when
+ // we didn't do a restore at all due to a URL mismatch.
+ onTabRestoreComplete(true);
+ }
+}
+
+void CanonicalBrowsingContext::RestoreState::Resolve() {
+ MOZ_DIAGNOSTIC_ASSERT(mPromise);
+ mPromise->MaybeResolveWithUndefined();
+ mPromise = nullptr;
+}
+
+nsresult CanonicalBrowsingContext::WriteSessionStorageToSessionStore(
+ const nsTArray<SSCacheCopy>& aSesssionStorage, uint32_t aEpoch) {
+ nsCOMPtr<nsISessionStoreFunctions> funcs = do_ImportESModule(
+ "resource://gre/modules/SessionStoreFunctions.sys.mjs", fallible);
+ if (!funcs) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIXPConnectWrappedJS> wrapped = do_QueryInterface(funcs);
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(wrapped->GetJSObjectGlobal())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JS::Rooted<JS::Value> key(jsapi.cx(), Top()->PermanentKey());
+
+ Record<nsCString, Record<nsString, nsString>> storage;
+ JS::Rooted<JS::Value> update(jsapi.cx());
+
+ if (!aSesssionStorage.IsEmpty()) {
+ SessionStoreUtils::ConstructSessionStorageValues(this, aSesssionStorage,
+ storage);
+ if (!ToJSValue(jsapi.cx(), storage, &update)) {
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ update.setNull();
+ }
+
+ return funcs->UpdateSessionStoreForStorage(Top()->GetEmbedderElement(), this,
+ key, aEpoch, update);
+}
+
+void CanonicalBrowsingContext::UpdateSessionStoreSessionStorage(
+ const std::function<void()>& aDone) {
+ if (!StaticPrefs::browser_sessionstore_collect_session_storage_AtStartup()) {
+ aDone();
+ return;
+ }
+
+ using DataPromise = BackgroundSessionStorageManager::DataPromise;
+ BackgroundSessionStorageManager::GetData(
+ this, StaticPrefs::browser_sessionstore_dom_storage_limit(),
+ /* aClearSessionStoreTimer = */ true)
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [self = RefPtr{this}, aDone, epoch = GetSessionStoreEpoch()](
+ const DataPromise::ResolveOrRejectValue& valueList) {
+ if (valueList.IsResolve()) {
+ self->WriteSessionStorageToSessionStore(
+ valueList.ResolveValue(), epoch);
+ }
+ aDone();
+ });
+}
+
+/* static */
+void CanonicalBrowsingContext::UpdateSessionStoreForStorage(
+ uint64_t aBrowsingContextId) {
+ RefPtr<CanonicalBrowsingContext> browsingContext = Get(aBrowsingContextId);
+
+ if (!browsingContext) {
+ return;
+ }
+
+ browsingContext->UpdateSessionStoreSessionStorage([]() {});
+}
+
+void CanonicalBrowsingContext::MaybeScheduleSessionStoreUpdate() {
+ if (!StaticPrefs::browser_sessionstore_platform_collection_AtStartup()) {
+ return;
+ }
+
+ if (!IsTop()) {
+ Top()->MaybeScheduleSessionStoreUpdate();
+ return;
+ }
+
+ if (IsInBFCache()) {
+ return;
+ }
+
+ if (mSessionStoreSessionStorageUpdateTimer) {
+ return;
+ }
+
+ if (!StaticPrefs::browser_sessionstore_debug_no_auto_updates()) {
+ auto result = NS_NewTimerWithFuncCallback(
+ [](nsITimer*, void* aClosure) {
+ auto* context = static_cast<CanonicalBrowsingContext*>(aClosure);
+ context->UpdateSessionStoreSessionStorage([]() {});
+ },
+ this, StaticPrefs::browser_sessionstore_interval(),
+ nsITimer::TYPE_ONE_SHOT,
+ "CanonicalBrowsingContext::MaybeScheduleSessionStoreUpdate");
+
+ if (result.isErr()) {
+ return;
+ }
+
+ mSessionStoreSessionStorageUpdateTimer = result.unwrap();
+ }
+}
+
+void CanonicalBrowsingContext::CancelSessionStoreUpdate() {
+ if (mSessionStoreSessionStorageUpdateTimer) {
+ mSessionStoreSessionStorageUpdateTimer->Cancel();
+ mSessionStoreSessionStorageUpdateTimer = nullptr;
+ }
+}
+
+void CanonicalBrowsingContext::SetContainerFeaturePolicy(
+ FeaturePolicy* aContainerFeaturePolicy) {
+ mContainerFeaturePolicy = aContainerFeaturePolicy;
+
+ if (WindowGlobalParent* current = GetCurrentWindowGlobal()) {
+ Unused << current->SendSetContainerFeaturePolicy(mContainerFeaturePolicy);
+ }
+}
+
+void CanonicalBrowsingContext::SetCrossGroupOpenerId(uint64_t aOpenerId) {
+ MOZ_DIAGNOSTIC_ASSERT(IsTopContent());
+ MOZ_DIAGNOSTIC_ASSERT(mCrossGroupOpenerId == 0,
+ "Can only set CrossGroupOpenerId once");
+ mCrossGroupOpenerId = aOpenerId;
+}
+
+void CanonicalBrowsingContext::SetCrossGroupOpener(
+ CanonicalBrowsingContext& aCrossGroupOpener, ErrorResult& aRv) {
+ if (!IsTopContent()) {
+ aRv.ThrowNotAllowedError(
+ "Can only set crossGroupOpener on toplevel content");
+ return;
+ }
+ if (mCrossGroupOpenerId != 0) {
+ aRv.ThrowNotAllowedError("Can only set crossGroupOpener once");
+ return;
+ }
+
+ SetCrossGroupOpenerId(aCrossGroupOpener.Id());
+}
+
+auto CanonicalBrowsingContext::FindUnloadingHost(uint64_t aChildID)
+ -> nsTArray<UnloadingHost>::iterator {
+ return std::find_if(
+ mUnloadingHosts.begin(), mUnloadingHosts.end(),
+ [&](const auto& host) { return host.mChildID == aChildID; });
+}
+
+void CanonicalBrowsingContext::ClearUnloadingHost(uint64_t aChildID) {
+ // Notify any callbacks which were waiting for the host to finish unloading
+ // that it has.
+ auto found = FindUnloadingHost(aChildID);
+ if (found != mUnloadingHosts.end()) {
+ auto callbacks = std::move(found->mCallbacks);
+ mUnloadingHosts.RemoveElementAt(found);
+ for (const auto& callback : callbacks) {
+ callback();
+ }
+ }
+}
+
+void CanonicalBrowsingContext::StartUnloadingHost(uint64_t aChildID) {
+ MOZ_DIAGNOSTIC_ASSERT(FindUnloadingHost(aChildID) == mUnloadingHosts.end());
+ mUnloadingHosts.AppendElement(UnloadingHost{aChildID, {}});
+}
+
+void CanonicalBrowsingContext::BrowserParentDestroyed(
+ BrowserParent* aBrowserParent, bool aAbnormalShutdown) {
+ ClearUnloadingHost(aBrowserParent->Manager()->ChildID());
+
+ // Handling specific to when the current BrowserParent has been destroyed.
+ if (mCurrentBrowserParent == aBrowserParent) {
+ mCurrentBrowserParent = nullptr;
+
+ // If this BrowserParent is for a subframe, attempt to recover from a
+ // subframe crash by rendering the subframe crashed page in the embedding
+ // content.
+ if (aAbnormalShutdown) {
+ ShowSubframeCrashedUI(aBrowserParent->GetBrowserBridgeParent());
+ }
+ }
+}
+
+void CanonicalBrowsingContext::ShowSubframeCrashedUI(
+ BrowserBridgeParent* aBridge) {
+ if (!aBridge || IsDiscarded() || !aBridge->CanSend()) {
+ return;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(!aBridge->GetBrowsingContext() ||
+ aBridge->GetBrowsingContext() == this);
+
+ // There is no longer a current inner window within this
+ // BrowsingContext, update the `CurrentInnerWindowId` field to reflect
+ // this.
+ MOZ_ALWAYS_SUCCEEDS(SetCurrentInnerWindowId(0));
+
+ // The owning process will now be the embedder to render the subframe
+ // crashed page, switch ownership back over.
+ SetOwnerProcessId(aBridge->Manager()->Manager()->ChildID());
+ SetCurrentBrowserParent(aBridge->Manager());
+
+ Unused << aBridge->SendSubFrameCrashed();
+}
+
+static void LogBFCacheBlockingForDoc(BrowsingContext* aBrowsingContext,
+ uint32_t aBFCacheCombo, bool aIsSubDoc) {
+ if (aIsSubDoc) {
+ nsAutoCString uri("[no uri]");
+ nsCOMPtr<nsIURI> currentURI =
+ aBrowsingContext->Canonical()->GetCurrentURI();
+ if (currentURI) {
+ uri = currentURI->GetSpecOrDefault();
+ }
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
+ (" ** Blocked for document %s", uri.get()));
+ }
+ if (aBFCacheCombo & BFCacheStatus::EVENT_HANDLING_SUPPRESSED) {
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
+ (" * event handling suppression"));
+ }
+ if (aBFCacheCombo & BFCacheStatus::SUSPENDED) {
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * suspended Window"));
+ }
+ if (aBFCacheCombo & BFCacheStatus::UNLOAD_LISTENER) {
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * unload listener"));
+ }
+ if (aBFCacheCombo & BFCacheStatus::REQUEST) {
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * requests in the loadgroup"));
+ }
+ if (aBFCacheCombo & BFCacheStatus::ACTIVE_GET_USER_MEDIA) {
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * GetUserMedia"));
+ }
+ if (aBFCacheCombo & BFCacheStatus::ACTIVE_PEER_CONNECTION) {
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * PeerConnection"));
+ }
+ if (aBFCacheCombo & BFCacheStatus::CONTAINS_EME_CONTENT) {
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * EME content"));
+ }
+ if (aBFCacheCombo & BFCacheStatus::CONTAINS_MSE_CONTENT) {
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * MSE use"));
+ }
+ if (aBFCacheCombo & BFCacheStatus::HAS_ACTIVE_SPEECH_SYNTHESIS) {
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * Speech use"));
+ }
+ if (aBFCacheCombo & BFCacheStatus::HAS_USED_VR) {
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * used VR"));
+ }
+ if (aBFCacheCombo & BFCacheStatus::BEFOREUNLOAD_LISTENER) {
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * beforeunload listener"));
+ }
+ if (aBFCacheCombo & BFCacheStatus::ACTIVE_LOCK) {
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * has active Web Locks"));
+ }
+}
+
+bool CanonicalBrowsingContext::AllowedInBFCache(
+ const Maybe<uint64_t>& aChannelId, nsIURI* aNewURI) {
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug))) {
+ nsAutoCString uri("[no uri]");
+ nsCOMPtr<nsIURI> currentURI = GetCurrentURI();
+ if (currentURI) {
+ uri = currentURI->GetSpecOrDefault();
+ }
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, ("Checking %s", uri.get()));
+ }
+
+ if (IsInProcess()) {
+ return false;
+ }
+
+ uint32_t bfcacheCombo = 0;
+ if (mRestoreState) {
+ bfcacheCombo |= BFCacheStatus::RESTORING;
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * during session restore"));
+ }
+
+ if (Group()->Toplevels().Length() > 1) {
+ bfcacheCombo |= BFCacheStatus::NOT_ONLY_TOPLEVEL_IN_BCG;
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
+ (" * auxiliary BrowsingContexts"));
+ }
+
+ // There are not a lot of about:* pages that are allowed to load in
+ // subframes, so it's OK to allow those few about:* pages enter BFCache.
+ MOZ_ASSERT(IsTop(), "Trying to put a non top level BC into BFCache");
+
+ WindowGlobalParent* wgp = GetCurrentWindowGlobal();
+ if (wgp && wgp->GetDocumentURI()) {
+ nsCOMPtr<nsIURI> currentURI = wgp->GetDocumentURI();
+ // Exempt about:* pages from bfcache, with the exception of about:blank
+ if (currentURI->SchemeIs("about") &&
+ !currentURI->GetSpecOrDefault().EqualsLiteral("about:blank")) {
+ bfcacheCombo |= BFCacheStatus::ABOUT_PAGE;
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * about:* page"));
+ }
+
+ if (aNewURI) {
+ bool equalUri = false;
+ aNewURI->Equals(currentURI, &equalUri);
+ if (equalUri) {
+ // When loading the same uri, disable bfcache so that
+ // nsDocShell::OnNewURI transforms the load to LOAD_NORMAL_REPLACE.
+ return false;
+ }
+ }
+ }
+
+ // For telemetry we're collecting all the flags for all the BCs hanging
+ // from this top-level BC.
+ PreOrderWalk([&](BrowsingContext* aBrowsingContext) {
+ WindowGlobalParent* wgp =
+ aBrowsingContext->Canonical()->GetCurrentWindowGlobal();
+ uint32_t subDocBFCacheCombo = wgp ? wgp->GetBFCacheStatus() : 0;
+ if (wgp) {
+ const Maybe<uint64_t>& singleChannelId = wgp->GetSingleChannelId();
+ if (singleChannelId.isSome()) {
+ if (singleChannelId.value() == 0 || aChannelId.isNothing() ||
+ singleChannelId.value() != aChannelId.value()) {
+ subDocBFCacheCombo |= BFCacheStatus::REQUEST;
+ }
+ }
+ }
+
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug))) {
+ LogBFCacheBlockingForDoc(aBrowsingContext, subDocBFCacheCombo,
+ aBrowsingContext != this);
+ }
+
+ bfcacheCombo |= subDocBFCacheCombo;
+ });
+
+ nsDocShell::ReportBFCacheComboTelemetry(bfcacheCombo);
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug))) {
+ nsAutoCString uri("[no uri]");
+ nsCOMPtr<nsIURI> currentURI = GetCurrentURI();
+ if (currentURI) {
+ uri = currentURI->GetSpecOrDefault();
+ }
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
+ (" +> %s %s be blocked from going into the BFCache", uri.get(),
+ bfcacheCombo == 0 ? "shouldn't" : "should"));
+ }
+
+ if (StaticPrefs::docshell_shistory_bfcache_allow_unload_listeners()) {
+ bfcacheCombo &= ~BFCacheStatus::UNLOAD_LISTENER;
+ }
+
+ return bfcacheCombo == 0;
+}
+
+void CanonicalBrowsingContext::SetTouchEventsOverride(
+ dom::TouchEventsOverride aOverride, ErrorResult& aRv) {
+ SetTouchEventsOverrideInternal(aOverride, aRv);
+}
+
+void CanonicalBrowsingContext::SetTargetTopLevelLinkClicksToBlank(
+ bool aTargetTopLevelLinkClicksToBlank, ErrorResult& aRv) {
+ SetTargetTopLevelLinkClicksToBlankInternal(aTargetTopLevelLinkClicksToBlank,
+ aRv);
+}
+
+void CanonicalBrowsingContext::AddPageAwakeRequest() {
+ MOZ_ASSERT(IsTop());
+ auto count = GetPageAwakeRequestCount();
+ MOZ_ASSERT(count < UINT32_MAX);
+ Unused << SetPageAwakeRequestCount(++count);
+}
+
+void CanonicalBrowsingContext::RemovePageAwakeRequest() {
+ MOZ_ASSERT(IsTop());
+ auto count = GetPageAwakeRequestCount();
+ MOZ_ASSERT(count > 0);
+ Unused << SetPageAwakeRequestCount(--count);
+}
+
+void CanonicalBrowsingContext::CloneDocumentTreeInto(
+ CanonicalBrowsingContext* aSource, const nsACString& aRemoteType,
+ embedding::PrintData&& aPrintData) {
+ NavigationIsolationOptions options;
+ options.mRemoteType = aRemoteType;
+
+ mClonePromise =
+ ChangeRemoteness(options, /* aPendingSwitchId = */ 0)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [source = MaybeDiscardedBrowsingContext{aSource},
+ data = std::move(aPrintData)](
+ BrowserParent* aBp) -> RefPtr<GenericNonExclusivePromise> {
+ RefPtr<BrowserBridgeParent> bridge =
+ aBp->GetBrowserBridgeParent();
+ return aBp->SendCloneDocumentTreeIntoSelf(source, data)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [bridge](
+ BrowserParent::CloneDocumentTreeIntoSelfPromise::
+ ResolveOrRejectValue&& aValue) {
+ // We're cloning a remote iframe, so we created a
+ // BrowserBridge which makes us register an OOP load
+ // (see Document::OOPChildLoadStarted), even though
+ // this isn't a real load. We call
+ // SendMaybeFireEmbedderLoadEvents here so that we do
+ // register the end of the load (see
+ // Document::OOPChildLoadDone).
+ if (bridge) {
+ Unused << bridge->SendMaybeFireEmbedderLoadEvents(
+ EmbedderElementEventType::NoEvent);
+ }
+ if (aValue.IsResolve() && aValue.ResolveValue()) {
+ return GenericNonExclusivePromise::CreateAndResolve(
+ true, __func__);
+ }
+ return GenericNonExclusivePromise::CreateAndReject(
+ NS_ERROR_FAILURE, __func__);
+ });
+ },
+ [](nsresult aRv) -> RefPtr<GenericNonExclusivePromise> {
+ NS_WARNING(
+ nsPrintfCString("Remote clone failed: %x\n", unsigned(aRv))
+ .get());
+ return GenericNonExclusivePromise::CreateAndReject(
+ NS_ERROR_FAILURE, __func__);
+ });
+
+ mClonePromise->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self = RefPtr{this}]() { self->mClonePromise = nullptr; });
+}
+
+bool CanonicalBrowsingContext::StartApzAutoscroll(float aAnchorX,
+ float aAnchorY,
+ nsViewID aScrollId,
+ uint32_t aPresShellId) {
+ nsCOMPtr<nsIWidget> widget;
+ mozilla::layers::LayersId layersId{0};
+
+ if (IsInProcess()) {
+ nsCOMPtr<nsPIDOMWindowOuter> outer = GetDOMWindow();
+ if (!outer) {
+ return false;
+ }
+
+ widget = widget::WidgetUtils::DOMWindowToWidget(outer);
+ if (widget) {
+ layersId = widget->GetRootLayerTreeId();
+ }
+ } else {
+ RefPtr<BrowserParent> parent = GetBrowserParent();
+ if (!parent) {
+ return false;
+ }
+
+ widget = parent->GetWidget();
+ layersId = parent->GetLayersId();
+ }
+
+ if (!widget || !widget->AsyncPanZoomEnabled()) {
+ return false;
+ }
+
+ // The anchor coordinates that are passed in are relative to the origin of the
+ // screen, but we are sending them to APZ which only knows about coordinates
+ // relative to the widget, so convert them accordingly.
+ const LayoutDeviceIntPoint anchor =
+ RoundedToInt(LayoutDevicePoint(aAnchorX, aAnchorY)) -
+ widget->WidgetToScreenOffset();
+
+ mozilla::layers::ScrollableLayerGuid guid(layersId, aPresShellId, aScrollId);
+
+ return widget->StartAsyncAutoscroll(
+ ViewAs<ScreenPixel>(
+ anchor, PixelCastJustification::LayoutDeviceIsScreenForBounds),
+ guid);
+}
+
+void CanonicalBrowsingContext::StopApzAutoscroll(nsViewID aScrollId,
+ uint32_t aPresShellId) {
+ nsCOMPtr<nsIWidget> widget;
+ mozilla::layers::LayersId layersId{0};
+
+ if (IsInProcess()) {
+ nsCOMPtr<nsPIDOMWindowOuter> outer = GetDOMWindow();
+ if (!outer) {
+ return;
+ }
+
+ widget = widget::WidgetUtils::DOMWindowToWidget(outer);
+ if (widget) {
+ layersId = widget->GetRootLayerTreeId();
+ }
+ } else {
+ RefPtr<BrowserParent> parent = GetBrowserParent();
+ if (!parent) {
+ return;
+ }
+
+ widget = parent->GetWidget();
+ layersId = parent->GetLayersId();
+ }
+
+ if (!widget || !widget->AsyncPanZoomEnabled()) {
+ return;
+ }
+
+ mozilla::layers::ScrollableLayerGuid guid(layersId, aPresShellId, aScrollId);
+ widget->StopAsyncAutoscroll(guid);
+}
+
+already_AddRefed<nsISHEntry>
+CanonicalBrowsingContext::GetMostRecentLoadingSessionHistoryEntry() {
+ if (mLoadingEntries.IsEmpty()) {
+ return nullptr;
+ }
+
+ RefPtr<SessionHistoryEntry> entry = mLoadingEntries.LastElement().mEntry;
+ return entry.forget();
+}
+
+already_AddRefed<BounceTrackingState>
+CanonicalBrowsingContext::GetBounceTrackingState() {
+ if (!mWebProgress) {
+ return nullptr;
+ }
+ return mWebProgress->GetBounceTrackingState();
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(CanonicalBrowsingContext)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(CanonicalBrowsingContext,
+ BrowsingContext)
+ tmp->mPermanentKey.setNull();
+ if (tmp->mSessionHistory) {
+ tmp->mSessionHistory->SetBrowsingContext(nullptr);
+ }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSessionHistory, mContainerFeaturePolicy,
+ mCurrentBrowserParent, mWebProgress,
+ mSessionStoreSessionStorageUpdateTimer)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(CanonicalBrowsingContext,
+ BrowsingContext)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSessionHistory, mContainerFeaturePolicy,
+ mCurrentBrowserParent, mWebProgress,
+ mSessionStoreSessionStorageUpdateTimer)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(CanonicalBrowsingContext,
+ BrowsingContext)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mPermanentKey)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_ADDREF_INHERITED(CanonicalBrowsingContext, BrowsingContext)
+NS_IMPL_RELEASE_INHERITED(CanonicalBrowsingContext, BrowsingContext)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CanonicalBrowsingContext)
+NS_INTERFACE_MAP_END_INHERITING(BrowsingContext)
+
+} // namespace mozilla::dom
diff --git a/docshell/base/CanonicalBrowsingContext.h b/docshell/base/CanonicalBrowsingContext.h
new file mode 100644
index 0000000000..132c9f2157
--- /dev/null
+++ b/docshell/base/CanonicalBrowsingContext.h
@@ -0,0 +1,620 @@
+/* -*- 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/ProcessIsolation.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/SessionHistoryEntry.h"
+#include "mozilla/dom/SessionStoreRestoreData.h"
+#include "mozilla/dom/SessionStoreUtils.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 nsIBrowserDOMWindow;
+class nsISHistory;
+class nsIWidget;
+class nsIPrintSettings;
+class nsSHistory;
+class nsBrowserStatusFilter;
+class nsSecureBrowserUI;
+class CallerWillNotifyHistoryIndexAndLengthChanges;
+class nsITimer;
+
+namespace mozilla {
+enum class CallState;
+class BounceTrackingState;
+
+namespace embedding {
+class PrintData;
+}
+
+namespace net {
+class DocumentLoadListener;
+}
+
+namespace dom {
+
+class BrowserParent;
+class BrowserBridgeParent;
+class FeaturePolicy;
+struct LoadURIOptions;
+class MediaController;
+struct LoadingSessionHistoryInfo;
+class SSCacheCopy;
+class WindowGlobalParent;
+class SessionStoreFormData;
+class SessionStoreScrollData;
+
+// 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_SCRIPT_HOLDER_CLASS_INHERITED(
+ CanonicalBrowsingContext, BrowsingContext)
+
+ static already_AddRefed<CanonicalBrowsingContext> Get(uint64_t aId);
+ static CanonicalBrowsingContext* Cast(BrowsingContext* aContext);
+ static const CanonicalBrowsingContext* Cast(const BrowsingContext* aContext);
+ static already_AddRefed<CanonicalBrowsingContext> Cast(
+ already_AddRefed<BrowsingContext>&& 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);
+
+ // 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 SetCrossGroupOpener(CanonicalBrowsingContext& aCrossGroupOpener,
+ ErrorResult& aRv);
+
+ void GetWindowGlobals(nsTArray<RefPtr<WindowGlobalParent>>& 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<nsIWidget> GetParentProcessWidgetContaining();
+ already_AddRefed<nsIBrowserDOMWindow> GetBrowserDOMWindow();
+
+ // Same as `GetParentWindowContext`, but will also cross <browser> and
+ // content/chrome boundaries.
+ already_AddRefed<WindowGlobalParent> GetEmbedderWindowGlobal() const;
+
+ CanonicalBrowsingContext* GetParentCrossChromeBoundary();
+ CanonicalBrowsingContext* TopCrossChromeBoundary();
+ Nullable<WindowProxyHolder> GetTopChromeWindow();
+
+ nsISHistory* GetSessionHistory();
+ SessionHistoryEntry* GetActiveSessionHistoryEntry();
+ void SetActiveSessionHistoryEntry(SessionHistoryEntry* aEntry);
+
+ bool ManuallyManagesActiveness() const;
+
+ UniquePtr<LoadingSessionHistoryInfo> CreateLoadingSessionHistoryEntryForLoad(
+ nsDocShellLoadState* aLoadState, SessionHistoryEntry* aExistingEntry,
+ nsIChannel* aChannel);
+
+ UniquePtr<LoadingSessionHistoryInfo> ReplaceLoadingSessionHistoryEntryForLoad(
+ LoadingSessionHistoryInfo* aInfo, nsIChannel* aNewChannel);
+
+ using PrintPromise = MozPromise</* unused */ bool, nsresult, false>;
+ MOZ_CAN_RUN_SCRIPT RefPtr<PrintPromise> Print(nsIPrintSettings*);
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> PrintJS(nsIPrintSettings*,
+ ErrorResult&);
+
+ // Call the given callback on all top-level descendant BrowsingContexts.
+ // Return Callstate::Stop from the callback to stop calling further children.
+ //
+ // If aIncludeNestedBrowsers is true, then all top descendants are included,
+ // even those inside a nested top browser.
+ void CallOnAllTopDescendants(
+ const FunctionRef<CallState(CanonicalBrowsingContext*)>& aCallback,
+ bool aIncludeNestedBrowsers);
+
+ void SessionHistoryCommit(uint64_t aLoadId, const nsID& aChangeID,
+ uint32_t aLoadType, bool aPersist,
+ bool aCloneEntryChildren, bool aChannelExpired,
+ uint32_t aCacheKey);
+
+ // 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<NotNull<RefPtr<nsDocShellLoadState>>>& aLoadState,
+ Maybe<bool>& aReloadActiveEntry);
+
+ // See BrowsingContext::SetActiveSessionHistoryEntry.
+ void SetActiveSessionHistoryEntry(const Maybe<nsPoint>& aPreviousScrollPos,
+ SessionHistoryInfo* aInfo,
+ uint32_t aLoadType,
+ uint32_t aUpdatedCacheKey,
+ const nsID& aChangeID);
+
+ void ReplaceActiveSessionHistoryEntry(SessionHistoryInfo* aInfo);
+
+ void RemoveDynEntriesFromActiveSessionHistoryEntry();
+
+ void RemoveFromSessionHistory(const nsID& aChangeID);
+
+ Maybe<int32_t> HistoryGo(int32_t aOffset, uint64_t aHistoryEpoch,
+ bool aRequireUserInteraction, bool aUserActivation,
+ Maybe<ContentParentId> aContentId);
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> 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<mozilla::OwningNonNull<BrowsingContext>>& aRoots);
+
+ // Return true if a private browsing session is active.
+ static bool IsPrivateBrowsingActive();
+
+ // 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 FixupAndLoadURIString(const nsAString& aURI,
+ const LoadURIOptions& aOptions,
+ ErrorResult& aError);
+ void LoadURI(nsIURI* aURI, const LoadURIOptions& aOptions,
+ ErrorResult& aError);
+
+ void GoBack(const Optional<int32_t>& aCancelContentJSEpoch,
+ bool aRequireUserInteraction, bool aUserActivation);
+ void GoForward(const Optional<int32_t>& aCancelContentJSEpoch,
+ bool aRequireUserInteraction, bool aUserActivation);
+ void GoToIndex(int32_t aIndex, const Optional<int32_t>& aCancelContentJSEpoch,
+ bool aUserActivation);
+ void Reload(uint32_t aReloadFlags);
+ void Stop(uint32_t aStopFlags);
+
+ // Get the publicly exposed current URI loaded in this BrowsingContext.
+ already_AddRefed<nsIURI> GetCurrentURI() const;
+ void SetCurrentRemoteURI(nsIURI* aCurrentRemoteURI);
+
+ BrowserParent* GetBrowserParent() const;
+ void SetCurrentBrowserParent(BrowserParent* aBrowserParent);
+
+ // 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<RefPtr<BrowserParent>, nsresult, false>;
+ RefPtr<RemotenessPromise> ChangeRemoteness(
+ const NavigationIsolationOptions& aOptions, uint64_t aPendingSwitchId);
+
+ // 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,
+ const NavigationIsolationOptions& aRemotenessOptions);
+
+ bool HasHistoryEntry(nsISHEntry* aEntry);
+ bool HasLoadingHistoryEntry(nsISHEntry* aEntry) {
+ for (const LoadingSessionHistoryEntry& loading : mLoadingEntries) {
+ if (loading.mEntry == aEntry) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void SwapHistoryEntries(nsISHEntry* aOldEntry, nsISHEntry* aNewEntry);
+
+ void AddLoadingSessionHistoryEntry(uint64_t aLoadId,
+ SessionHistoryEntry* aEntry);
+
+ void GetLoadingSessionHistoryInfoFromParent(
+ Maybe<LoadingSessionHistoryInfo>& aLoadingInfo);
+
+ void HistoryCommitIndexAndLength();
+
+ void SynchronizeLayoutHistoryState();
+
+ void ResetScalingZoom();
+
+ void SetContainerFeaturePolicy(FeaturePolicy* aContainerFeaturePolicy);
+ FeaturePolicy* GetContainerFeaturePolicy() const {
+ return mContainerFeaturePolicy;
+ }
+
+ void SetRestoreData(SessionStoreRestoreData* aData, ErrorResult& aError);
+ void ClearRestoreState();
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void RequestRestoreTabContent(
+ WindowGlobalParent* aWindow);
+ already_AddRefed<Promise> GetRestorePromise();
+
+ nsresult WriteSessionStorageToSessionStore(
+ const nsTArray<SSCacheCopy>& aSesssionStorage, uint32_t aEpoch);
+
+ void UpdateSessionStoreSessionStorage(const std::function<void()>& aDone);
+
+ static void UpdateSessionStoreForStorage(uint64_t aBrowsingContextId);
+
+ // Called when a BrowserParent for this BrowsingContext has been fully
+ // destroyed (i.e. `ActorDestroy` was called).
+ void BrowserParentDestroyed(BrowserParent* aBrowserParent,
+ bool aAbnormalShutdown);
+
+ void StartUnloadingHost(uint64_t aChildID);
+ void ClearUnloadingHost(uint64_t aChildID);
+
+ bool AllowedInBFCache(const Maybe<uint64_t>& aChannelId, nsIURI* aNewURI);
+
+ // Methods for getting and setting the active state for top level
+ // browsing contexts, for the process priority manager.
+ bool IsPriorityActive() const {
+ MOZ_RELEASE_ASSERT(IsTop());
+ return mPriorityActive;
+ }
+ void SetPriorityActive(bool aIsActive) {
+ MOZ_RELEASE_ASSERT(IsTop());
+ mPriorityActive = aIsActive;
+ }
+
+ void SetIsActive(bool aIsActive, ErrorResult& aRv) {
+ MOZ_ASSERT(ManuallyManagesActiveness(),
+ "Shouldn't be setting active status of this browsing context if "
+ "not manually managed");
+ SetIsActiveInternal(aIsActive, aRv);
+ }
+
+ void SetIsActiveInternal(bool aIsActive, ErrorResult& aRv) {
+ SetExplicitActive(aIsActive ? ExplicitActiveStatus::Active
+ : ExplicitActiveStatus::Inactive,
+ aRv);
+ }
+
+ void SetTouchEventsOverride(dom::TouchEventsOverride, ErrorResult& aRv);
+ void SetTargetTopLevelLinkClicksToBlank(bool aTargetTopLevelLinkClicksToBlank,
+ ErrorResult& aRv);
+
+ bool IsReplaced() const { return mIsReplaced; }
+
+ const JS::Heap<JS::Value>& PermanentKey() { return mPermanentKey; }
+ void ClearPermanentKey() { mPermanentKey.setNull(); }
+ void MaybeSetPermanentKey(Element* aEmbedder);
+
+ // When request for page awake, it would increase a count that is used to
+ // prevent whole browsing context tree from being suspended. The request can
+ // be called multiple times. When calling the revoke, it would decrease the
+ // count and once the count reaches to zero, the browsing context tree could
+ // be suspended when the tree is inactive.
+ void AddPageAwakeRequest();
+ void RemovePageAwakeRequest();
+
+ void CloneDocumentTreeInto(CanonicalBrowsingContext* aSource,
+ const nsACString& aRemoteType,
+ embedding::PrintData&& aPrintData);
+
+ // Returns a Promise which resolves when cloning documents for printing
+ // finished if this browsing context is cloning document tree.
+ RefPtr<GenericNonExclusivePromise> GetClonePromise() const {
+ return mClonePromise;
+ }
+
+ bool StartApzAutoscroll(float aAnchorX, float aAnchorY, nsViewID aScrollId,
+ uint32_t aPresShellId);
+ void StopApzAutoscroll(nsViewID aScrollId, uint32_t aPresShellId);
+
+ void AddFinalDiscardListener(std::function<void(uint64_t)>&& aListener);
+
+ bool ForceAppWindowActive() const { return mForceAppWindowActive; }
+ void SetForceAppWindowActive(bool, ErrorResult&);
+ void RecomputeAppWindowVisibility();
+
+ already_AddRefed<nsISHEntry> GetMostRecentLoadingSessionHistoryEntry();
+
+ already_AddRefed<BounceTrackingState> GetBounceTrackingState();
+
+ protected:
+ // Called when the browsing context is being discarded.
+ void CanonicalDiscard();
+
+ // Called when the browsing context is being attached.
+ void CanonicalAttach();
+
+ // Called when the browsing context private mode is changed after
+ // being attached, but before being discarded.
+ void AdjustPrivateBrowsingCount(bool aPrivateBrowsing);
+
+ 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;
+
+ virtual ~CanonicalBrowsingContext();
+
+ class PendingRemotenessChange {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(PendingRemotenessChange)
+
+ PendingRemotenessChange(CanonicalBrowsingContext* aTarget,
+ RemotenessPromise::Private* aPromise,
+ uint64_t aPendingSwitchId,
+ const NavigationIsolationOptions& aOptions);
+
+ void Cancel(nsresult aRv);
+
+ private:
+ friend class CanonicalBrowsingContext;
+
+ ~PendingRemotenessChange();
+ void ProcessLaunched();
+ void ProcessReady();
+ void MaybeFinish();
+ void Clear();
+
+ nsresult FinishTopContent();
+ nsresult FinishSubframe();
+
+ RefPtr<CanonicalBrowsingContext> mTarget;
+ RefPtr<RemotenessPromise::Private> mPromise;
+ RefPtr<ContentParent> mContentParent;
+ RefPtr<BrowsingContextGroup> mSpecificGroup;
+
+ bool mProcessReady = false;
+ bool mWaitingForPrepareToChange = false;
+
+ uint64_t mPendingSwitchId;
+ NavigationIsolationOptions mOptions;
+ };
+
+ struct RestoreState {
+ NS_INLINE_DECL_REFCOUNTING(RestoreState)
+
+ void ClearData() { mData = nullptr; }
+ void Resolve();
+
+ RefPtr<SessionStoreRestoreData> mData;
+ RefPtr<Promise> mPromise;
+ uint32_t mRequests = 0;
+ uint32_t mResolves = 0;
+
+ private:
+ ~RestoreState() = default;
+ };
+
+ 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.
+ // If aContinueNavigating it set, the reference to the DocumentLoadListener
+ // will be cleared to prevent it being cancelled, however the current load ID
+ // will be preserved until `EndDocumentLoad` is called again.
+ void EndDocumentLoad(bool aContinueNavigating);
+
+ bool SupportsLoadingInParent(nsDocShellLoadState* aLoadState,
+ uint64_t* aOuterWindowId);
+
+ void HistoryCommitIndexAndLength(
+ const nsID& aChangeID,
+ const CallerWillNotifyHistoryIndexAndLengthChanges& aProofOfCaller);
+
+ struct UnloadingHost {
+ uint64_t mChildID;
+ nsTArray<std::function<void()>> mCallbacks;
+ };
+ nsTArray<UnloadingHost>::iterator FindUnloadingHost(uint64_t aChildID);
+
+ // Called when we want to show the subframe crashed UI as our previous browser
+ // has become unloaded for one reason or another.
+ void ShowSubframeCrashedUI(BrowserBridgeParent* aBridge);
+
+ void MaybeScheduleSessionStoreUpdate();
+
+ void CancelSessionStoreUpdate();
+
+ void AddPendingDiscard();
+
+ void RemovePendingDiscard();
+
+ bool ShouldAddEntryForRefresh(const SessionHistoryEntry* aEntry) {
+ return ShouldAddEntryForRefresh(aEntry->Info().GetURI(),
+ aEntry->Info().HasPostData());
+ }
+ bool ShouldAddEntryForRefresh(nsIURI* aNewURI, bool aHasPostData) {
+ nsCOMPtr<nsIURI> currentURI = GetCurrentURI();
+ return BrowsingContext::ShouldAddEntryForRefresh(currentURI, aNewURI,
+ aHasPostData);
+ }
+
+ already_AddRefed<nsDocShellLoadState> CreateLoadInfo(
+ SessionHistoryEntry* aEntry);
+
+ // 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;
+
+ 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();
+
+ RefPtr<BrowserParent> mCurrentBrowserParent;
+
+ nsTArray<UnloadingHost> mUnloadingHosts;
+
+ // The current URI loaded in this BrowsingContext. This value is only set for
+ // BrowsingContexts loaded in content processes.
+ nsCOMPtr<nsIURI> mCurrentRemoteURI;
+
+ // The current remoteness change which is in a pending state.
+ RefPtr<PendingRemotenessChange> mPendingRemotenessChange;
+
+ RefPtr<nsSHistory> 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<MediaController> mTabMediaController;
+
+ RefPtr<net::DocumentLoadListener> mCurrentLoad;
+
+ struct LoadingSessionHistoryEntry {
+ uint64_t mLoadId = 0;
+ RefPtr<SessionHistoryEntry> mEntry;
+ };
+ nsTArray<LoadingSessionHistoryEntry> mLoadingEntries;
+ RefPtr<SessionHistoryEntry> mActiveEntry;
+
+ RefPtr<nsSecureBrowserUI> mSecureBrowserUI;
+ RefPtr<BrowsingContextWebProgress> mWebProgress;
+
+ nsCOMPtr<nsIWebProgressListener> mDocShellProgressBridge;
+ RefPtr<nsBrowserStatusFilter> mStatusFilter;
+
+ RefPtr<FeaturePolicy> mContainerFeaturePolicy;
+
+ friend class BrowserSessionStore;
+ WeakPtr<SessionStoreFormData>& GetSessionStoreFormDataRef() {
+ return mFormdata;
+ }
+ WeakPtr<SessionStoreScrollData>& GetSessionStoreScrollDataRef() {
+ return mScroll;
+ }
+
+ WeakPtr<SessionStoreFormData> mFormdata;
+ WeakPtr<SessionStoreScrollData> mScroll;
+
+ RefPtr<RestoreState> mRestoreState;
+
+ nsCOMPtr<nsITimer> mSessionStoreSessionStorageUpdateTimer;
+
+ // If this is a top level context, this is true if our browser ID is marked as
+ // active in the process priority manager.
+ bool mPriorityActive = false;
+
+ // See CanonicalBrowsingContext.forceAppWindowActive.
+ bool mForceAppWindowActive = false;
+
+ bool mIsReplaced = false;
+
+ // A Promise created when cloning documents for printing.
+ RefPtr<GenericNonExclusivePromise> mClonePromise;
+
+ JS::Heap<JS::Value> mPermanentKey;
+
+ uint32_t mPendingDiscards = 0;
+
+ bool mFullyDiscarded = false;
+
+ nsTArray<std::function<void(uint64_t)>> mFullyDiscardedListeners;
+};
+
+} // 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..628d75abb1
--- /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<ChildProcessChannelListener> sCPCLSingleton;
+
+void ChildProcessChannelListener::RegisterCallback(uint64_t aIdentifier,
+ Callback&& aCallback) {
+ if (auto args = mChannelArgs.Extract(aIdentifier)) {
+ nsresult rv =
+ aCallback(args->mLoadState, std::move(args->mStreamFilterEndpoints),
+ args->mTiming);
+ args->mResolver(rv);
+ } else {
+ mCallbacks.InsertOrUpdate(aIdentifier, std::move(aCallback));
+ }
+}
+
+void ChildProcessChannelListener::OnChannelReady(
+ nsDocShellLoadState* aLoadState, uint64_t aIdentifier,
+ nsTArray<Endpoint>&& aStreamFilterEndpoints, nsDOMNavigationTiming* aTiming,
+ Resolver&& aResolver) {
+ if (auto callback = mCallbacks.Extract(aIdentifier)) {
+ nsresult rv =
+ (*callback)(aLoadState, std::move(aStreamFilterEndpoints), aTiming);
+ aResolver(rv);
+ } else {
+ mChannelArgs.InsertOrUpdate(
+ aIdentifier, CallbackArgs{aLoadState, std::move(aStreamFilterEndpoints),
+ aTiming, std::move(aResolver)});
+ }
+}
+
+ChildProcessChannelListener::~ChildProcessChannelListener() {
+ for (const auto& args : mChannelArgs.Values()) {
+ args.mResolver(NS_ERROR_FAILURE);
+ }
+}
+
+already_AddRefed<ChildProcessChannelListener>
+ChildProcessChannelListener::GetSingleton() {
+ if (!sCPCLSingleton) {
+ sCPCLSingleton = new ChildProcessChannelListener();
+ ClearOnShutdown(&sCPCLSingleton);
+ }
+ RefPtr<ChildProcessChannelListener> 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..c00c2ff5a7
--- /dev/null
+++ b/docshell/base/ChildProcessChannelListener.h
@@ -0,0 +1,54 @@
+/* -*- 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 <functional>
+
+#include "mozilla/extensions/StreamFilterParent.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsTHashMap.h"
+#include "nsIChannel.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+
+namespace mozilla::dom {
+
+class ChildProcessChannelListener final {
+ NS_INLINE_DECL_REFCOUNTING(ChildProcessChannelListener)
+
+ using Endpoint = mozilla::ipc::Endpoint<extensions::PStreamFilterParent>;
+ using Resolver = std::function<void(const nsresult&)>;
+ using Callback = std::function<nsresult(
+ nsDocShellLoadState*, nsTArray<Endpoint>&&, nsDOMNavigationTiming*)>;
+
+ void RegisterCallback(uint64_t aIdentifier, Callback&& aCallback);
+
+ void OnChannelReady(nsDocShellLoadState* aLoadState, uint64_t aIdentifier,
+ nsTArray<Endpoint>&& aStreamFilterEndpoints,
+ nsDOMNavigationTiming* aTiming, Resolver&& aResolver);
+
+ static already_AddRefed<ChildProcessChannelListener> GetSingleton();
+
+ private:
+ ChildProcessChannelListener() = default;
+ ~ChildProcessChannelListener();
+ struct CallbackArgs {
+ RefPtr<nsDocShellLoadState> mLoadState;
+ nsTArray<Endpoint> mStreamFilterEndpoints;
+ RefPtr<nsDOMNavigationTiming> mTiming;
+ Resolver mResolver;
+ };
+
+ // TODO Backtrack.
+ nsTHashMap<nsUint64HashKey, Callback> mCallbacks;
+ nsTHashMap<nsUint64HashKey, CallbackArgs> mChannelArgs;
+};
+
+} // namespace mozilla::dom
+
+#endif // !defined(mozilla_dom_ChildProcessChannelListener_h)
diff --git a/docshell/base/IHistory.h b/docshell/base/IHistory.h
new file mode 100644
index 0000000000..30da778f45
--- /dev/null
+++ b/docshell/base/IHistory.h
@@ -0,0 +1,175 @@
+/* -*- 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 "nsURIHashKey.h"
+#include "nsTHashSet.h"
+#include "nsTObserverArray.h"
+
+class nsIURI;
+class nsIWidget;
+
+namespace mozilla {
+
+namespace dom {
+class ContentParent;
+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)
+
+ using ContentParentSet = nsTHashSet<RefPtr<dom::ContentParent>>;
+
+ /**
+ * 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;
+
+ /**
+ * Schedules a single visited query from a given child process.
+ *
+ * @param aURI the URI to query.
+ * @param ContentParent the process interested in knowing about the visited
+ * state of this URI.
+ */
+ virtual void ScheduleVisitedQuery(nsIURI* aURI, dom::ContentParent*) = 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.
+ *
+ * @param ContentParentSet a set of content processes that are interested on
+ * this change. If null, it is broadcasted to all
+ * child processes.
+ */
+ virtual void NotifyVisited(nsIURI*, VisitedStatus,
+ const ContentParentSet* = nullptr) = 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,
+ /**
+ * If REDIRECT_SOURCE is set, this indicates that this is a special redirect
+ * caused by HSTS or HTTPS-Only/First upgrading to the HTTPS version of the
+ * URI.
+ */
+ REDIRECT_SOURCE_UPGRADED = 1 << 6
+ };
+
+ /**
+ * 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.
+ * @param aBrowserId
+ * The id of browser used for this visit.
+ */
+ NS_IMETHOD VisitURI(nsIWidget* aWidget, nsIURI* aURI, nsIURI* aLastVisitedURI,
+ uint32_t aFlags, uint64_t aBrowserId) = 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..2e501be221
--- /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<dom::Element> 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::MutableHandle<JS::Value> 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<nsILoadContext*>(this);
+ NS_ADDREF_THIS();
+ return NS_OK;
+ }
+
+ return NS_NOINTERFACE;
+}
+
+static already_AddRefed<nsILoadContext> CreateInstance(bool aPrivate) {
+ OriginAttributes oa;
+ oa.mPrivateBrowsingId = aPrivate ? 1 : 0;
+
+ nsCOMPtr<nsILoadContext> lc = new LoadContext(oa);
+
+ return lc.forget();
+}
+
+already_AddRefed<nsILoadContext> CreateLoadContext() {
+ return CreateInstance(false);
+}
+
+already_AddRefed<nsILoadContext> 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<nsILoadContext> CreateLoadContext();
+already_AddRefed<nsILoadContext> 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<nsILoadContext> 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<nsIPrivateBrowsingChannel> 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<nsILoadContext> 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..d47ad08003
--- /dev/null
+++ b/docshell/base/SerializedLoadContext.h
@@ -0,0 +1,96 @@
+/* -*- 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<SerializedLoadContext> {
+ typedef SerializedLoadContext paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ nsAutoCString suffix;
+ aParam.mOriginAttributes.CreateSuffix(suffix);
+
+ WriteParam(aWriter, aParam.mIsNotNull);
+ WriteParam(aWriter, aParam.mIsContent);
+ WriteParam(aWriter, aParam.mIsPrivateBitValid);
+ WriteParam(aWriter, aParam.mUseRemoteTabs);
+ WriteParam(aWriter, aParam.mUseRemoteSubframes);
+ WriteParam(aWriter, aParam.mUseTrackingProtection);
+ WriteParam(aWriter, suffix);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ nsAutoCString suffix;
+ if (!ReadParam(aReader, &aResult->mIsNotNull) ||
+ !ReadParam(aReader, &aResult->mIsContent) ||
+ !ReadParam(aReader, &aResult->mIsPrivateBitValid) ||
+ !ReadParam(aReader, &aResult->mUseRemoteTabs) ||
+ !ReadParam(aReader, &aResult->mUseRemoteSubframes) ||
+ !ReadParam(aReader, &aResult->mUseTrackingProtection) ||
+ !ReadParam(aReader, &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..679e07edc2
--- /dev/null
+++ b/docshell/base/SyncedContext.h
@@ -0,0 +1,402 @@
+/* -*- 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 <array>
+#include <type_traits>
+#include <utility>
+#include "mozilla/Attributes.h"
+#include "mozilla/BitSet.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;
+class MessageReader;
+class MessageWriter;
+} // namespace IPC
+
+namespace mozilla {
+namespace ipc {
+class IProtocol;
+class IPCResult;
+template <typename T>
+struct IPDLParamTraits;
+} // namespace ipc
+
+namespace dom {
+class ContentParent;
+class ContentChild;
+template <typename T>
+class MaybeDiscarded;
+
+namespace syncedcontext {
+
+template <size_t I>
+using Index = typename std::integral_constant<size_t, I>;
+
+// We're going to use the empty base optimization for synced fields of different
+// sizes, so we define an empty class for that purpose.
+template <size_t I, size_t S>
+struct Empty {};
+
+// A templated container for a synced field. I is the index and T is the type.
+template <size_t I, typename T>
+struct Field {
+ T mField{};
+};
+
+// SizedField is a Field with a helper to define either an "empty" field, or a
+// field of a given type.
+template <size_t I, typename T, size_t S>
+using SizedField = std::conditional_t<((sizeof(T) > 8) ? 8 : sizeof(T)) == S,
+ Field<I, T>, Empty<I, S>>;
+
+template <typename Context>
+class Transaction {
+ public:
+ using IndexSet = EnumSet<size_t, BitSet<Context::FieldValues::count>>;
+
+ // 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 <size_t I, typename U>
+ void Set(U&& aValue) {
+ mValues.Get(Index<I>{}) = std::forward<U>(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<Context>& aOwner,
+ ContentParent* aSource);
+
+ // Called from `ContentChild` in response to a transaction from the parent.
+ mozilla::ipc::IPCResult CommitFromIPC(const MaybeDiscarded<Context>& aOwner,
+ uint64_t aEpoch, ContentChild* aSource);
+
+ // Apply the changes from this transaction to the specified Context WITHOUT
+ // syncing the changes to other processes.
+ //
+ // Unlike `Commit`, this method will NOT call the corresponding `CanSet` or
+ // `DidSet` methods, and can be performed when the target context is
+ // unattached or discarded.
+ //
+ // NOTE: YOU PROBABLY DO NOT WANT TO USE THIS METHOD
+ void CommitWithoutSyncing(Context* aOwner);
+
+ private:
+ friend struct mozilla::ipc::IPDLParamTraits<Transaction<Context>>;
+
+ void Write(IPC::MessageWriter* aWriter,
+ mozilla::ipc::IProtocol* aActor) const;
+ bool Read(IPC::MessageReader* aReader, 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, bool aFromIPC);
+
+ // Returns the set of fields which failed to validate, or an empty set if
+ // there were no validation errors.
+ //
+ // NOTE: This method mutates `this` if any changes were reverted.
+ IndexSet Validate(Context* aOwner, ContentParent* aSource);
+
+ template <typename F>
+ static void EachIndex(F&& aCallback) {
+ Context::FieldValues::EachIndex(aCallback);
+ }
+
+ template <size_t I>
+ static uint64_t& FieldEpoch(Index<I>, Context* aContext) {
+ return std::get<I>(aContext->mFields.mEpochs);
+ }
+
+ typename Context::FieldValues mValues;
+ IndexSet mModified;
+};
+
+template <typename Base, size_t Count>
+class FieldValues : public Base {
+ public:
+ // The number of fields stored by this type.
+ static constexpr size_t count = Count;
+
+ // 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<I>` for each index less than the
+ // field count.
+ template <typename F>
+ static void EachIndex(F&& aCallback) {
+ EachIndexInner(std::make_index_sequence<count>(),
+ std::forward<F>(aCallback));
+ }
+
+ private:
+ friend struct mozilla::ipc::IPDLParamTraits<FieldValues<Base, Count>>;
+
+ void Write(IPC::MessageWriter* aWriter,
+ mozilla::ipc::IProtocol* aActor) const;
+ bool Read(IPC::MessageReader* aReader, mozilla::ipc::IProtocol* aActor);
+
+ template <typename F, size_t... Indexes>
+ static void EachIndexInner(std::index_sequence<Indexes...> aIndexes,
+ F&& aCallback) {
+ (aCallback(Index<Indexes>()), ...);
+ }
+};
+
+// Storage related to synchronized context fields. Contains both a tuple of
+// individual field values, and epoch information for field synchronization.
+template <typename Values>
+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 <size_t I>
+ const auto& Get() const {
+ return RawValues().Get(Index<I>{});
+ }
+
+ // 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 <size_t I, typename U>
+ void SetWithoutSyncing(U&& aValue) {
+ GetNonSyncingReference<I>() = 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 <size_t I>
+ auto& GetNonSyncingReference() {
+ return RawValues().Get(Index<I>{});
+ }
+
+ FieldStorage() = default;
+ explicit FieldStorage(Values&& aInit) : mValues(std::move(aInit)) {}
+
+ private:
+ template <typename Context>
+ friend class Transaction;
+
+ // Data Members
+ std::array<uint64_t, Values::count> mEpochs{};
+ Values mValues;
+};
+
+// Alternative return type enum for `CanSet` validators which allows specifying
+// more behaviour.
+enum class CanSetResult : uint8_t {
+ // The set attempt is denied. This is equivalent to returning `false`.
+ Deny,
+ // The set attempt is allowed. This is equivalent to returning `true`.
+ Allow,
+ // The set attempt is reverted non-fatally.
+ Revert,
+};
+
+// 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 <typename T>
+struct GetFieldSetterType {
+ using SetterArg = T;
+};
+template <>
+struct GetFieldSetterType<nsString> {
+ using SetterArg = const nsAString&;
+};
+template <>
+struct GetFieldSetterType<nsCString> {
+ using SetterArg = const nsACString&;
+};
+template <typename T>
+using FieldSetterType = typename GetFieldSetterType<T>::SetterArg;
+
+#define MOZ_DECL_SYNCED_CONTEXT_FIELD_INDEX(name, type) IDX_##name,
+
+#define MOZ_DECL_SYNCED_CONTEXT_FIELD_GETSET(name, type) \
+ const type& Get##name() const { return mFields.template Get<IDX_##name>(); } \
+ \
+ [[nodiscard]] nsresult Set##name( \
+ ::mozilla::dom::syncedcontext::FieldSetterType<type> aValue) { \
+ Transaction txn; \
+ txn.template Set<IDX_##name>(std::move(aValue)); \
+ return txn.Commit(this); \
+ } \
+ void Set##name(::mozilla::dom::syncedcontext::FieldSetterType<type> 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 <typename U> \
+ void Set##name(U&& aValue) { \
+ this->template Set<IDX_##name>(std::forward<U>(aValue)); \
+ }
+#define MOZ_DECL_SYNCED_CONTEXT_INDEX_TO_NAME(name, type) \
+ case IDX_##name: \
+ return #name;
+
+#define MOZ_DECL_SYNCED_FIELD_INHERIT(name, type) \
+ public \
+ syncedcontext::SizedField<IDX_##name, type, Size>,
+
+#define MOZ_DECL_SYNCED_CONTEXT_BASE_FIELD_GETTER(name, type) \
+ type& Get(FieldIndex<IDX_##name>) { \
+ return Field<IDX_##name, type>::mField; \
+ } \
+ const type& Get(FieldIndex<IDX_##name>) const { \
+ return Field<IDX_##name, type>::mField; \
+ }
+
+// 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 <size_t I> \
+ using FieldIndex = typename ::mozilla::dom::syncedcontext::Index<I>; \
+ \
+ /* Fields contain all synced fields defined by \
+ * `eachfield(MOZ_DECL_SYNCED_FIELD_INHERIT)`, but only those where the size \
+ * of the field is equal to size Size will be present. We use SizedField to \
+ * remove fields of the wrong size. */ \
+ template <size_t Size> \
+ struct Fields : eachfield(MOZ_DECL_SYNCED_FIELD_INHERIT) \
+ syncedcontext::Empty<SYNCED_FIELD_COUNT, Size> {}; \
+ \
+ /* Struct containing the data for all synced fields as members. We filter \
+ * sizes to lay out fields of size 1, then 2, then 4 and last 8 or greater. \
+ * This way we don't need to consider packing when defining fields, but \
+ * we'll just reorder them here. \
+ */ \
+ struct BaseFieldValues : public Fields<1>, \
+ public Fields<2>, \
+ public Fields<4>, \
+ public Fields<8> { \
+ template <size_t I> \
+ auto& Get() { \
+ return Get(FieldIndex<I>{}); \
+ } \
+ template <size_t I> \
+ const auto& Get() const { \
+ return Get(FieldIndex<I>{}); \
+ } \
+ eachfield(MOZ_DECL_SYNCED_CONTEXT_BASE_FIELD_GETTER) \
+ }; \
+ using FieldValues = \
+ typename ::mozilla::dom::syncedcontext::FieldValues<BaseFieldValues, \
+ SYNCED_FIELD_COUNT>; \
+ \
+ protected: \
+ friend class ::mozilla::dom::syncedcontext::Transaction<clazz>; \
+ ::mozilla::dom::syncedcontext::FieldStorage<FieldValues> mFields; \
+ \
+ public: \
+ /* Transaction types for bulk mutations */ \
+ using BaseTransaction = ::mozilla::dom::syncedcontext::Transaction<clazz>; \
+ 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 "<unknown>"; \
+ } \
+ eachfield(MOZ_DECL_SYNCED_CONTEXT_FIELD_GETSET)
+
+} // namespace syncedcontext
+} // namespace dom
+
+namespace ipc {
+
+template <typename Context>
+struct IPDLParamTraits<dom::syncedcontext::Transaction<Context>> {
+ typedef dom::syncedcontext::Transaction<Context> paramType;
+
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const paramType& aParam) {
+ aParam.Write(aWriter, aActor);
+ }
+
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ paramType* aResult) {
+ return aResult->Read(aReader, aActor);
+ }
+};
+
+template <typename Base, size_t Count>
+struct IPDLParamTraits<dom::syncedcontext::FieldValues<Base, Count>> {
+ typedef dom::syncedcontext::FieldValues<Base, Count> paramType;
+
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const paramType& aParam) {
+ aParam.Write(aWriter, aActor);
+ }
+
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ paramType* aResult) {
+ return aResult->Read(aReader, 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..e386e75b35
--- /dev/null
+++ b/docshell/base/SyncedContextInlines.h
@@ -0,0 +1,359 @@
+/* -*- 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"
+#include "mozilla/HalIPCUtils.h"
+
+namespace mozilla {
+namespace dom {
+namespace syncedcontext {
+
+template <typename T>
+struct IsMozillaMaybe : std::false_type {};
+template <typename T>
+struct IsMozillaMaybe<Maybe<T>> : std::true_type {};
+
+// A super-sketchy logging only function for generating a human-readable version
+// of the value of a synced context field.
+template <typename T>
+void FormatFieldValue(nsACString& aStr, const T& aValue) {
+ if constexpr (std::is_same_v<bool, T>) {
+ if (aValue) {
+ aStr.AppendLiteral("true");
+ } else {
+ aStr.AppendLiteral("false");
+ }
+ } else if constexpr (std::is_same_v<nsID, T>) {
+ aStr.Append(nsIDToCString(aValue).get());
+ } else if constexpr (std::is_integral_v<T>) {
+ aStr.AppendInt(aValue);
+ } else if constexpr (std::is_enum_v<T>) {
+ aStr.AppendInt(static_cast<std::underlying_type_t<T>>(aValue));
+ } else if constexpr (std::is_floating_point_v<T>) {
+ aStr.AppendFloat(aValue);
+ } else if constexpr (std::is_base_of_v<nsAString, T>) {
+ AppendUTF16toUTF8(aValue, aStr);
+ } else if constexpr (std::is_base_of_v<nsACString, T>) {
+ aStr.Append(aValue);
+ } else if constexpr (IsMozillaMaybe<T>::value) {
+ if (aValue) {
+ aStr.AppendLiteral("Some(");
+ FormatFieldValue(aStr, aValue.ref());
+ aStr.AppendLiteral(")");
+ } else {
+ aStr.AppendLiteral("Nothing");
+ }
+ } else {
+ aStr.AppendLiteral("???");
+ }
+}
+
+// Sketchy logging-only logic to generate a human-readable output of the actions
+// a synced context transaction is going to perform.
+template <typename Context>
+nsAutoCString FormatTransaction(
+ typename Transaction<Context>::IndexSet aModified,
+ const typename Context::FieldValues& aOldValues,
+ const typename Context::FieldValues& aNewValues) {
+ nsAutoCString result;
+ Context::FieldValues::EachIndex([&](auto idx) {
+ if (aModified.contains(idx)) {
+ result.Append(Context::FieldIndexToName(idx));
+ result.AppendLiteral("(");
+ FormatFieldValue(result, aOldValues.Get(idx));
+ result.AppendLiteral("->");
+ FormatFieldValue(result, aNewValues.Get(idx));
+ result.AppendLiteral(") ");
+ }
+ });
+ return result;
+}
+
+template <typename Context>
+nsCString FormatValidationError(
+ typename Transaction<Context>::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 <typename Context>
+nsresult Transaction<Context>::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<Context>(
+ failedFields, "CanSet failed for field(s): ");
+ MOZ_CRASH_UNSAFE_PRINTF("%s", error.get());
+ }
+
+ if (mModified.isEmpty()) {
+ return NS_OK;
+ }
+
+ 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, /* aFromIPC */ false);
+ return NS_OK;
+}
+
+template <typename Context>
+mozilla::ipc::IPCResult Transaction<Context>::CommitFromIPC(
+ const MaybeDiscarded<Context>& aOwner, ContentParent* aSource) {
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
+ if (aOwner.IsNullOrDiscarded()) {
+ MOZ_LOG(Context::GetSyncLog(), 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<Context>(
+ failedFields,
+ "Invalid Transaction from Child - CanSet failed for field(s): ");
+ // data-review+ at https://bugzilla.mozilla.org/show_bug.cgi?id=1618992#c7
+ return IPC_FAIL_UNSAFE_PRINTF(aSource, "%s", error.get());
+ }
+
+ // Validate may have dropped some fields from the transaction, check it's not
+ // empty before continuing.
+ if (mModified.isEmpty()) {
+ return IPC_OK();
+ }
+
+ BrowsingContextGroup* group = owner->Group();
+ group->EachOtherParent(aSource, [&](ContentParent* aParent) {
+ owner->SendCommitTransaction(aParent, *this,
+ aParent->GetBrowsingContextFieldEpoch());
+ });
+
+ Apply(owner, /* aFromIPC */ true);
+ return IPC_OK();
+}
+
+template <typename Context>
+mozilla::ipc::IPCResult Transaction<Context>::CommitFromIPC(
+ const MaybeDiscarded<Context>& aOwner, uint64_t aEpoch,
+ ContentChild* aSource) {
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsContentProcess());
+ if (aOwner.IsNullOrDiscarded()) {
+ MOZ_LOG(Context::GetSyncLog(), 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) {
+ MOZ_LOG(
+ Context::GetSyncLog(), LogLevel::Debug,
+ ("Transaction::Obsoleted(#%" PRIx64 ", %" PRIu64 ">%" PRIu64 "): %s",
+ owner->Id(), FieldEpoch(idx, owner), aEpoch,
+ Context::FieldIndexToName(idx)));
+ mModified -= idx;
+ }
+ });
+
+ if (mModified.isEmpty()) {
+ return IPC_OK();
+ }
+
+ Apply(owner, /* aFromIPC */ true);
+ return IPC_OK();
+}
+
+template <typename Context>
+void Transaction<Context>::Apply(Context* aOwner, bool aFromIPC) {
+ MOZ_ASSERT(!mModified.isEmpty());
+
+ MOZ_LOG(
+ Context::GetSyncLog(), LogLevel::Debug,
+ ("Transaction::Apply(#%" PRIx64 ", %s): %s", aOwner->Id(),
+ aFromIPC ? "ipc" : "local",
+ FormatTransaction<Context>(mModified, aOwner->mFields.mValues, mValues)
+ .get()));
+
+ 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 <typename Context>
+void Transaction<Context>::CommitWithoutSyncing(Context* aOwner) {
+ MOZ_LOG(
+ Context::GetSyncLog(), LogLevel::Debug,
+ ("Transaction::CommitWithoutSyncing(#%" PRIx64 "): %s", aOwner->Id(),
+ FormatTransaction<Context>(mModified, aOwner->mFields.mValues, mValues)
+ .get()));
+
+ EachIndex([&](auto idx) {
+ if (mModified.contains(idx)) {
+ aOwner->mFields.mValues.Get(idx) = std::move(mValues.Get(idx));
+ }
+ });
+ mModified.clear();
+}
+
+inline CanSetResult AsCanSetResult(CanSetResult aValue) { return aValue; }
+inline CanSetResult AsCanSetResult(bool aValue) {
+ return aValue ? CanSetResult::Allow : CanSetResult::Deny;
+}
+
+template <typename Context>
+typename Transaction<Context>::IndexSet Transaction<Context>::Validate(
+ Context* aOwner, ContentParent* aSource) {
+ IndexSet failedFields;
+ Transaction<Context> revertTxn;
+
+ EachIndex([&](auto idx) {
+ if (!mModified.contains(idx)) {
+ return;
+ }
+
+ switch (AsCanSetResult(aOwner->CanSet(idx, mValues.Get(idx), aSource))) {
+ case CanSetResult::Allow:
+ break;
+ case CanSetResult::Deny:
+ failedFields += idx;
+ break;
+ case CanSetResult::Revert:
+ revertTxn.mValues.Get(idx) = aOwner->mFields.mValues.Get(idx);
+ revertTxn.mModified += idx;
+ break;
+ }
+ });
+
+ // If any changes need to be reverted, log them, remove them from this
+ // transaction, and optionally send a message to the source process.
+ if (!revertTxn.mModified.isEmpty()) {
+ // NOTE: Logging with modified IndexSet from revert transaction, and values
+ // from this transaction, so we log the failed values we're going to revert.
+ MOZ_LOG(
+ Context::GetSyncLog(), LogLevel::Debug,
+ ("Transaction::PartialRevert(#%" PRIx64 ", pid %" PRIPID "): %s",
+ aOwner->Id(), aSource ? aSource->OtherPid() : base::kInvalidProcessId,
+ FormatTransaction<Context>(revertTxn.mModified, mValues,
+ revertTxn.mValues)
+ .get()));
+
+ mModified -= revertTxn.mModified;
+
+ if (aSource) {
+ aOwner->SendCommitTransaction(aSource, revertTxn,
+ aSource->GetBrowsingContextFieldEpoch());
+ }
+ }
+ return failedFields;
+}
+
+template <typename Context>
+void Transaction<Context>::Write(IPC::MessageWriter* aWriter,
+ mozilla::ipc::IProtocol* aActor) const {
+ // Record which field indices will be included, and then write those fields
+ // out.
+ typename IndexSet::serializedType modified = mModified.serialize();
+ WriteIPDLParam(aWriter, aActor, modified);
+ EachIndex([&](auto idx) {
+ if (mModified.contains(idx)) {
+ WriteIPDLParam(aWriter, aActor, mValues.Get(idx));
+ }
+ });
+}
+
+template <typename Context>
+bool Transaction<Context>::Read(IPC::MessageReader* aReader,
+ mozilla::ipc::IProtocol* aActor) {
+ // Read in which field indices were sent by the remote, followed by the fields
+ // identified by those indices.
+ typename IndexSet::serializedType modified =
+ typename IndexSet::serializedType{};
+ if (!ReadIPDLParam(aReader, aActor, &modified)) {
+ return false;
+ }
+ mModified.deserialize(modified);
+
+ bool ok = true;
+ EachIndex([&](auto idx) {
+ if (ok && mModified.contains(idx)) {
+ ok = ReadIPDLParam(aReader, aActor, &mValues.Get(idx));
+ }
+ });
+ return ok;
+}
+
+template <typename Base, size_t Count>
+void FieldValues<Base, Count>::Write(IPC::MessageWriter* aWriter,
+ 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(aWriter, aActor, this->Get(idx)); });
+}
+
+template <typename Base, size_t Count>
+bool FieldValues<Base, Count>::Read(IPC::MessageReader* aReader,
+ 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(aReader, aActor, &this->Get(idx));
+ }
+ });
+ return ok;
+}
+
+} // namespace syncedcontext
+} // namespace dom
+} // namespace mozilla
+
+#endif // !defined(mozilla_dom_SyncedContextInlines_h)
diff --git a/docshell/base/URIFixup.sys.mjs b/docshell/base/URIFixup.sys.mjs
new file mode 100644
index 0000000000..6fde38a310
--- /dev/null
+++ b/docshell/base/URIFixup.sys.mjs
@@ -0,0 +1,1306 @@
+/* -*- 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] */
+
+import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
+
+import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
+
+const lazy = {};
+
+XPCOMUtils.defineLazyServiceGetter(
+ lazy,
+ "externalProtocolService",
+ "@mozilla.org/uriloader/external-protocol-service;1",
+ "nsIExternalProtocolService"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ lazy,
+ "defaultProtocolHandler",
+ "@mozilla.org/network/protocol;1?name=default",
+ "nsIProtocolHandler"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ lazy,
+ "fileProtocolHandler",
+ "@mozilla.org/network/protocol;1?name=file",
+ "nsIFileProtocolHandler"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ lazy,
+ "handlerService",
+ "@mozilla.org/uriloader/handler-service;1",
+ "nsIHandlerService"
+);
+
+XPCOMUtils.defineLazyPreferenceGetter(
+ lazy,
+ "fixupSchemeTypos",
+ "browser.fixup.typo.scheme",
+ true
+);
+XPCOMUtils.defineLazyPreferenceGetter(
+ lazy,
+ "dnsFirstForSingleWords",
+ "browser.fixup.dns_first_for_single_words",
+ false
+);
+XPCOMUtils.defineLazyPreferenceGetter(
+ lazy,
+ "keywordEnabled",
+ "keyword.enabled",
+ true
+);
+XPCOMUtils.defineLazyPreferenceGetter(
+ lazy,
+ "alternateProtocol",
+ "browser.fixup.alternate.protocol",
+ "https"
+);
+
+XPCOMUtils.defineLazyPreferenceGetter(
+ lazy,
+ "dnsResolveFullyQualifiedNames",
+ "browser.urlbar.dnsResolveFullyQualifiedNames",
+ true
+);
+
+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", "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.
+ChromeUtils.defineLazyGetter(
+ lazy,
+ "userPasswordRegex",
+ () => /^([a-z+.-]+:\/{0,3})*([^\/@]+@).+/i
+);
+
+// Regex used to identify the string that starts with port expression.
+ChromeUtils.defineLazyGetter(lazy, "portRegex", () => /^:\d{1,5}([?#/]|$)/);
+
+// Regex used to identify numbers.
+ChromeUtils.defineLazyGetter(lazy, "numberRegex", () => /^[0-9]+(\.[0-9]+)?$/);
+
+// Regex used to identify tab separated content (having at least 2 tabs).
+ChromeUtils.defineLazyGetter(lazy, "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:
+//
+// <hostname>:<port> or
+// <hostname>:<port>/
+//
+// Where <hostname> is a string of alphanumeric characters and dashes
+// separated by dots.
+// and <port> 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
+// <user>:<password>@<host>:<port>/<url-path> will be disappointed!
+//
+// Note: Parser could be a lot tighter, tossing out silly hostnames
+// such as those containing consecutive dots and so on.
+ChromeUtils.defineLazyGetter(
+ lazy,
+ "possiblyHostPortRegex",
+ () => /^[a-z0-9-]+(\.[a-z0-9-]+)*:[0-9]{1,5}([/?#]|$)/i
+);
+
+// Regex used to strip newlines.
+ChromeUtils.defineLazyGetter(lazy, "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.
+ChromeUtils.defineLazyGetter(
+ lazy,
+ "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.
+ChromeUtils.defineLazyGetter(
+ lazy,
+ "IPv4LikeRegex",
+ () => /^(?:[a-z+.-]+:\/*(?!\/))?(?:\d{1,3}\.){2,3}\d{1,3}(?::\d+|\/)?/i
+);
+ChromeUtils.defineLazyGetter(
+ lazy,
+ "IPv6LikeRegex",
+ () =>
+ /^(?:[a-z+.-]+:\/*(?!\/))?\[(?:[0-9a-f]{0,4}:){0,7}[0-9a-f]{0,4}\]?(?::\d+|\/)?/i
+);
+
+// Regex used to detect spaces in URL credentials.
+ChromeUtils.defineLazyGetter(
+ lazy,
+ "DetectSpaceInCredentialsRegex",
+ () => /^[^/]*\s[^/]*@/
+);
+
+// Cache of known domains.
+ChromeUtils.defineLazyGetter(lazy, "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.
+ChromeUtils.defineLazyGetter(lazy, "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;
+});
+
+export function URIFixup() {
+ // There are cases that nsIExternalProtocolService.externalProtocolHandlerExists() does
+ // not work well and returns always true due to flatpak. In this case, in order to
+ // fallback to nsIHandlerService.exits(), we test whether can trust
+ // nsIExternalProtocolService here.
+ this._trustExternalProtocolService =
+ !lazy.externalProtocolService.externalProtocolHandlerExists(
+ `__dummy${Date.now()}__`
+ );
+}
+
+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;
+ let untrimmedURIString = uriString;
+
+ // 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(lazy.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) != lazy.defaultProtocolHandler ||
+ this._isKnownExternalProtocol(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.
+ (!lazy.possiblyHostPortRegex.test(uriString) &&
+ !lazy.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 &&
+ lazy.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;
+ }
+ fixupConsecutiveDotsHost(info);
+ 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 &&
+ lazy.maxOneTabRegex.test(uriString) &&
+ !lazy.DetectSpaceInCredentialsRegex.test(untrimmedURIString)
+ ) {
+ let uriWithProtocol = fixupURIProtocol(uriString);
+ if (uriWithProtocol) {
+ info.fixedURI = uriWithProtocol;
+ info.fixupChangedProtocol = true;
+ info.wasSchemelessInput = 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("/")) {
+ fixupConsecutiveDotsHost(info);
+ return info;
+ }
+ }
+ }
+
+ // Handle "www.<something>" 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 (
+ lazy.keywordEnabled &&
+ fixupFlags & FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP &&
+ !inputHadDuffProtocol &&
+ !checkSuffix(info).suffix &&
+ keywordURIFixup(uriString, info, isPrivateContext)
+ ) {
+ fixupConsecutiveDotsHost(info);
+ return info;
+ }
+
+ if (
+ info.fixedURI &&
+ (!info.fixupChangedProtocol || !checkSuffix(info).hasUnknownSuffix)
+ ) {
+ fixupConsecutiveDotsHost(info);
+ return info;
+ }
+
+ // If we still haven't been able to construct a valid URI, try to force a
+ // keyword match.
+ if (lazy.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
+ );
+ }
+
+ fixupConsecutiveDotsHost(info);
+ 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();
+
+ if (!Services.search.hasSuccessfullyInitialized) {
+ return info;
+ }
+
+ // 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;
+ },
+
+ forceHttpFixup(uriString) {
+ if (!uriString) {
+ throw new Components.Exception(
+ "Should pass a non-null uri",
+ Cr.NS_ERROR_FAILURE
+ );
+ }
+
+ let info = new URIFixupInfo(uriString);
+ let { scheme, fixedSchemeUriString, fixupChangedProtocol } = extractScheme(
+ uriString,
+ FIXUP_FLAG_FIX_SCHEME_TYPOS
+ );
+
+ if (scheme != "http" && scheme != "https") {
+ throw new Components.Exception(
+ "Scheme should be either http or https",
+ Cr.NS_ERROR_FAILURE
+ );
+ }
+
+ info.fixupChangedProtocol = fixupChangedProtocol;
+ info.fixedURI = Services.io.newURI(fixedSchemeUriString);
+
+ let host = info.fixedURI.host;
+ if (host != "http" && host != "https" && host != "localhost") {
+ let modifiedHostname = maybeAddPrefixAndSuffix(host);
+ updateHostAndScheme(info, modifiedHostname);
+ info.preferredURI = info.fixedURI;
+ }
+
+ return info;
+ },
+
+ checkHost(uri, listener, originAttributes) {
+ let { displayHost, asciiHost } = uri;
+ if (!displayHost) {
+ throw new Components.Exception(
+ "URI must have displayHost",
+ Cr.NS_ERROR_FAILURE
+ );
+ }
+ if (!asciiHost) {
+ throw new Components.Exception(
+ "URI must have asciiHost",
+ Cr.NS_ERROR_FAILURE
+ );
+ }
+
+ let isIPv4Address = host => {
+ let parts = host.split(".");
+ if (parts.length != 4) {
+ return false;
+ }
+ return parts.every(part => {
+ let n = parseInt(part, 10);
+ return n >= 0 && n <= 255;
+ });
+ };
+
+ // Avoid showing fixup information if we're suggesting an IP. Note that
+ // decimal representations of IPs are normalized to a 'regular'
+ // dot-separated IP address by network code, but that only happens for
+ // numbers that don't overflow. Longer numbers do not get normalized,
+ // but still work to access IP addresses. So for instance,
+ // 1097347366913 (ff7f000001) gets resolved by using the final bytes,
+ // making it the same as 7f000001, which is 127.0.0.1 aka localhost.
+ // While 2130706433 would get normalized by network, 1097347366913
+ // does not, and we have to deal with both cases here:
+ if (isIPv4Address(asciiHost) || /^(?:\d+|0x[a-f0-9]+)$/i.test(asciiHost)) {
+ return;
+ }
+
+ // For dotless hostnames, we want to ensure this ends with a '.' but don't
+ // want the . showing up in the UI if we end up notifying the user, so we
+ // use a separate variable.
+ let lookupName = displayHost;
+ if (lazy.dnsResolveFullyQualifiedNames && !lookupName.includes(".")) {
+ lookupName += ".";
+ }
+
+ Services.obs.notifyObservers(null, "uri-fixup-check-dns");
+ Services.dns.asyncResolve(
+ lookupName,
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ 0,
+ null,
+ listener,
+ Services.tm.mainThread,
+ originAttributes
+ );
+ },
+
+ isDomainKnown,
+
+ _isKnownExternalProtocol(scheme) {
+ if (this._trustExternalProtocolService) {
+ return lazy.externalProtocolService.externalProtocolHandlerExists(scheme);
+ }
+
+ try {
+ // nsIExternalProtocolService.getProtocolHandlerInfo() on Android throws
+ // error due to not implemented.
+ return lazy.handlerService.exists(
+ lazy.externalProtocolService.getProtocolHandlerInfo(scheme)
+ );
+ } catch (e) {
+ return false;
+ }
+ },
+
+ classID: Components.ID("{c6cf88b7-452e-47eb-bdc9-86e3561648ef}"),
+ QueryInterface: ChromeUtils.generateQI(["nsIURIFixup"]),
+};
+
+export 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 wasSchemelessInput(changed) {
+ this._wasSchemelessInput = changed;
+ },
+ get wasSchemelessInput() {
+ return !!this._wasSchemelessInput;
+ },
+
+ 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 (lazy.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 (lazy.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 = lazy.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 (
+ /^\w/.test(asciiHost) &&
+ (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 || lazy.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) ||
+ // 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.
+ // Ditto for 'http' and 'https' as these are frequently the result of typos, e.g.
+ // 'https//foo' (note missing : ).
+ if (oldHost == "localhost" || oldHost == "http" || oldHost == "https") {
+ 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 newHost = maybeAddPrefixAndSuffix(oldHost);
+
+ if (newHost == oldHost) {
+ return false;
+ }
+
+ return updateHostAndScheme(info, newHost);
+}
+
+/**
+ * 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;
+ let path = uriString;
+ if (AppConstants.platform == "win") {
+ // Check for "\"" in the url-string, just a drive (e.g. C:),
+ // or 'A:/...' where the "protocol" is also a single letter.
+ attemptFixup =
+ uriString.includes("\\") ||
+ (uriString[1] == ":" && (uriString.length == 2 || uriString[2] == "/"));
+ if (uriString[1] == ":" && uriString[2] == "/") {
+ path = uriString.replace(/\//g, "\\");
+ }
+ } else {
+ // UNIX: Check if it starts with "/" or "~".
+ attemptFixup = /^[~/]/.test(uriString);
+ }
+ 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(path);
+ return Services.io.newURI(
+ lazy.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 ()
+ // "user@nonQualifiedHost"
+
+ // 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:pass@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.
+ const userPassword = lazy.userPasswordRegex.exec(uriString);
+ const ipString = userPassword
+ ? uriString.replace(userPassword[2], "")
+ : uriString;
+ if (lazy.IPv4LikeRegex.test(ipString) || lazy.IPv6LikeRegex.test(ipString)) {
+ return false;
+ }
+
+ // Avoid keyword 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;
+ }
+
+ // Avoid keyword lookup if the url seems to have password.
+ if (fixupInfo.fixedURI?.password) {
+ return false;
+ }
+
+ // Even if the host is unknown, avoid keyword lookup if the string has
+ // uri-like characteristics, unless it looks like "user@unknownHost".
+ // Note we already excluded passwords at this point.
+ if (
+ !isURILike(uriString, fixupInfo.fixedURI?.displayHost) ||
+ (fixupInfo.fixedURI?.userPass && fixupInfo.fixedURI?.pathQueryRef === "/")
+ ) {
+ 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(lazy.possibleProtocolRegex);
+ const hasColon = matches?.[2] === ":";
+ const hasSlash2 = matches?.[3] === "//";
+
+ const isFixupSchemeTypos =
+ lazy.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;
+ 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,
+ };
+}
+
+/**
+ * Fixup the host of fixedURI if it contains consecutive dots.
+ * @param {URIFixupInfo} info an URIInfo object
+ */
+function fixupConsecutiveDotsHost(fixupInfo) {
+ const uri = fixupInfo.fixedURI;
+
+ try {
+ if (!uri?.host.includes("..")) {
+ return;
+ }
+ } catch (e) {
+ return;
+ }
+
+ try {
+ const isPreferredEqualsToFixed = fixupInfo.preferredURI?.equals(uri);
+
+ fixupInfo.fixedURI = uri
+ .mutate()
+ .setHost(uri.host.replace(/\.+/g, "."))
+ .finalize();
+
+ if (isPreferredEqualsToFixed) {
+ fixupInfo.preferredURI = fixupInfo.fixedURI;
+ }
+ } catch (e) {
+ if (e.result !== Cr.NS_ERROR_MALFORMED_URI) {
+ throw e;
+ }
+ }
+}
+
+/**
+ * Return whether or not given string is uri like.
+ * This function returns true like following strings.
+ * - ":8080"
+ * - "localhost:8080" (if given host is "localhost")
+ * - "/foo?bar"
+ * - "/foo#bar"
+ * @param {string} uriString.
+ * @param {string} host.
+ * @param {boolean} true if uri like.
+ */
+function isURILike(uriString, host) {
+ const indexOfSlash = uriString.indexOf("/");
+ if (
+ indexOfSlash >= 0 &&
+ (indexOfSlash < uriString.indexOf("?", indexOfSlash) ||
+ indexOfSlash < uriString.indexOf("#", indexOfSlash))
+ ) {
+ return true;
+ }
+
+ if (uriString.startsWith(host)) {
+ uriString = uriString.substring(host.length);
+ }
+
+ return lazy.portRegex.test(uriString);
+}
+
+/**
+ * Add prefix and suffix to a hostname if both are missing.
+ *
+ * If the host does not start with the prefix, add the prefix to
+ * the hostname.
+ *
+ * By default the prefix and suffix are www. and .com but they could
+ * be any value e.g. www. and .org as they use the preferences
+ * "browser.fixup.alternate.prefix" and "browser.fixup.alternative.suffix"
+ *
+ * If no changes were made, it returns an empty string.
+ *
+ * @param {string} oldHost.
+ * @return {String} Fixed up hostname or an empty string.
+ */
+function maybeAddPrefixAndSuffix(oldHost) {
+ 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;
+ }
+ }
+ return newHost ? newHost : oldHost;
+}
+
+/**
+ * Given an instance of URIFixupInfo, update its fixedURI.
+ *
+ * First, change the protocol to the one stored in
+ * "browser.fixup.alternate.protocol".
+ *
+ * Then, try to update fixedURI's host to newHost.
+ *
+ * @param {URIFixupInfo} info.
+ * @param {string} newHost.
+ * @return {boolean}
+ * True, if info was updated without any errors.
+ * False, if NS_ERROR_MALFORMED_URI error.
+ * @throws If a non-NS_ERROR_MALFORMED_URI error occurs.
+ */
+function updateHostAndScheme(info, newHost) {
+ let oldHost = info.fixedURI.host;
+ let oldScheme = info.fixedURI.scheme;
+ try {
+ info.fixedURI = info.fixedURI
+ .mutate()
+ .setScheme(lazy.alternateProtocol)
+ .setHost(newHost)
+ .finalize();
+ } catch (ex) {
+ if (ex.result != Cr.NS_ERROR_MALFORMED_URI) {
+ throw ex;
+ }
+ return false;
+ }
+ if (oldScheme != info.fixedURI.scheme) {
+ info.fixupChangedProtocol = true;
+ }
+ if (oldHost != info.fixedURI.host) {
+ info.fixupCreatedAlternateURI = true;
+ }
+ return true;
+}
diff --git a/docshell/base/WindowContext.cpp b/docshell/base/WindowContext.cpp
new file mode 100644
index 0000000000..d2032bc551
--- /dev/null
+++ b/docshell/base/WindowContext.cpp
@@ -0,0 +1,697 @@
+/* -*- 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/RFPTargetIPCUtils.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "nsGlobalWindowInner.h"
+#include "nsIScriptError.h"
+#include "nsIWebProgressListener.h"
+#include "nsIXULRuntime.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<WindowContext>;
+
+static LazyLogModule gWindowContextLog("WindowContext");
+static LazyLogModule gWindowContextSyncLog("WindowContextSync");
+
+extern mozilla::LazyLogModule gUserInteractionPRLog;
+
+#define USER_ACTIVATION_LOG(msg, ...) \
+ MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
+
+using WindowContextByIdMap = nsTHashMap<nsUint64HashKey, WindowContext*>;
+static StaticAutoPtr<WindowContextByIdMap> gWindowContexts;
+
+/* static */
+LogModule* WindowContext::GetLog() { return gWindowContextLog; }
+
+/* static */
+LogModule* WindowContext::GetSyncLog() { return gWindowContextSyncLog; }
+
+/* static */
+already_AddRefed<WindowContext> 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<WindowGlobalParent*>(this);
+}
+
+bool WindowContext::IsCurrent() const {
+ return mBrowsingContext->mCurrentWindowContext == this;
+}
+
+bool WindowContext::IsInBFCache() {
+ if (mozilla::SessionHistoryInParent()) {
+ return mBrowsingContext->IsInBFCache();
+ }
+ return TopWindowContext()->GetWindowStateSaved();
+}
+
+nsGlobalWindowInner* WindowContext::GetInnerWindow() const {
+ return mWindowGlobalChild ? mWindowGlobalChild->GetWindowGlobal() : 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 {
+ return mWindowGlobalChild;
+}
+
+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 (!aBrowsingContext->IsEmbedderTypeObjectOrEmbed()) {
+ mNonSyntheticChildren.AppendElement(aBrowsingContext);
+ }
+
+ // If we're the current WindowContext in our BrowsingContext, make sure to
+ // clear any cached `children` value.
+ if (IsCurrent()) {
+ BrowsingContext_Binding::ClearCachedChildrenValue(mBrowsingContext);
+ }
+}
+
+void WindowContext::RemoveChildBrowsingContext(
+ BrowsingContext* aBrowsingContext) {
+ MOZ_DIAGNOSTIC_ASSERT(Group() == aBrowsingContext->Group(),
+ "Mismatched groups?");
+
+ mChildren.RemoveElement(aBrowsingContext);
+ mNonSyntheticChildren.RemoveElement(aBrowsingContext);
+
+ // If we're the current WindowContext in our BrowsingContext, make sure to
+ // clear any cached `children` value.
+ if (IsCurrent()) {
+ BrowsingContext_Binding::ClearCachedChildrenValue(mBrowsingContext);
+ }
+}
+
+void WindowContext::UpdateChildSynthetic(BrowsingContext* aBrowsingContext,
+ bool aIsSynthetic) {
+ if (aIsSynthetic) {
+ mNonSyntheticChildren.RemoveElement(aBrowsingContext);
+ } else {
+ // The same BrowsingContext will be reused for error pages, so it can be in
+ // the list already.
+ if (!mNonSyntheticChildren.Contains(aBrowsingContext)) {
+ mNonSyntheticChildren.AppendElement(aBrowsingContext);
+ }
+ }
+}
+
+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 (IsInProcess()) {
+ return true;
+ }
+
+ if (XRE_IsParentProcess() && aSource) {
+ return Canonical()->GetContentParent() == aSource;
+ }
+
+ return false;
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_IsSecure>, const bool& aIsSecure,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_AllowMixedContent>,
+ const bool& aAllowMixedContent,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_HasBeforeUnload>,
+ const bool& aHasBeforeUnload,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_CookieBehavior>,
+ const Maybe<uint32_t>& aValue,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_IsOnContentBlockingAllowList>,
+ const bool& aValue, ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_IsThirdPartyWindow>,
+ const bool& IsThirdPartyWindow,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_IsThirdPartyTrackingResourceWindow>,
+ const bool& aIsThirdPartyTrackingResourceWindow,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_UsingStorageAccess>,
+ const bool& aUsingStorageAccess,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_ShouldResistFingerprinting>,
+ const bool& aShouldResistFingerprinting,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_OverriddenFingerprintingSettings>,
+ const Maybe<RFPTarget>& aValue,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_IsSecureContext>,
+ const bool& aIsSecureContext,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_IsOriginalFrameSource>,
+ const bool& aIsOriginalFrameSource,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_DocTreeHadMedia>, const bool& aValue,
+ ContentParent* aSource) {
+ return IsTop();
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_AutoplayPermission>,
+ const uint32_t& aValue, ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_ShortcutsPermission>,
+ const uint32_t& aValue, ContentParent* aSource) {
+ return IsTop() && CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_ActiveMediaSessionContextId>,
+ const Maybe<uint64_t>& aValue,
+ ContentParent* aSource) {
+ return IsTop();
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_PopupPermission>, const uint32_t&,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(
+ FieldIndex<IDX_DelegatedPermissions>,
+ const PermissionDelegateHandler::DelegatedPermissionList& aValue,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(
+ FieldIndex<IDX_DelegatedExactHostMatchPermissions>,
+ const PermissionDelegateHandler::DelegatedPermissionList& aValue,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_IsLocalIP>, const bool& aValue,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_AllowJavascript>, bool aValue,
+ ContentParent* aSource) {
+ return (XRE_IsParentProcess() && !aSource) ||
+ CheckOnlyOwningProcessCanSet(aSource);
+}
+
+void WindowContext::DidSet(FieldIndex<IDX_AllowJavascript>, bool aOldValue) {
+ RecomputeCanExecuteScripts();
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_HasActivePeerConnections>, bool,
+ ContentParent*) {
+ return XRE_IsParentProcess() && IsTop();
+}
+
+void WindowContext::RecomputeCanExecuteScripts(bool aApplyChanges) {
+ const bool old = mCanExecuteScripts;
+ if (!AllowJavascript()) {
+ // Scripting has been explicitly disabled on our WindowContext.
+ mCanExecuteScripts = false;
+ } else {
+ // Otherwise, inherit.
+ mCanExecuteScripts = mBrowsingContext->CanExecuteScripts();
+ }
+
+ if (aApplyChanges && old != mCanExecuteScripts) {
+ // Inform our active DOM window.
+ if (nsGlobalWindowInner* window = GetInnerWindow()) {
+ // Only update scriptability if the window is current. Windows will have
+ // scriptability disabled when entering the bfcache and updated when
+ // coming out.
+ if (window->IsCurrentInnerWindow()) {
+ auto& scriptability =
+ xpc::Scriptability::Get(window->GetGlobalJSObject());
+ scriptability.SetWindowAllowsScript(mCanExecuteScripts);
+ }
+ }
+
+ for (const RefPtr<BrowsingContext>& child : Children()) {
+ child->RecomputeCanExecuteScripts();
+ }
+ }
+}
+
+void WindowContext::DidSet(FieldIndex<IDX_SHEntryHasUserInteraction>,
+ 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<IDX_UserActivationStateAndModifiers>) {
+ MOZ_ASSERT_IF(!IsInProcess(), mUserGestureStart.IsNull());
+ USER_ACTIVATION_LOG("Set user gesture activation 0x%02" PRIu8
+ " for %s browsing context 0x%08" PRIx64,
+ GetUserActivationStateAndModifiers(),
+ XRE_IsParentProcess() ? "Parent" : "Child", Id());
+ if (IsInProcess()) {
+ USER_ACTIVATION_LOG(
+ "Set user gesture start time for %s browsing context 0x%08" PRIx64,
+ XRE_IsParentProcess() ? "Parent" : "Child", Id());
+ if (GetUserActivationState() == UserActivation::State::FullActivated) {
+ mUserGestureStart = TimeStamp::Now();
+ } else if (GetUserActivationState() == UserActivation::State::None) {
+ mUserGestureStart = TimeStamp();
+ }
+ }
+}
+
+void WindowContext::DidSet(FieldIndex<IDX_HasReportedShadowDOMUsage>,
+ 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);
+ }
+ }
+ }
+ }
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_WindowStateSaved>, bool aValue,
+ ContentParent* aSource) {
+ return !mozilla::SessionHistoryInParent() && IsTop() &&
+ CheckOnlyOwningProcessCanSet(aSource);
+}
+
+void WindowContext::CreateFromIPC(IPCInitializer&& aInit) {
+ MOZ_RELEASE_ASSERT(XRE_IsContentProcess(),
+ "Should be a WindowGlobalParent in the parent");
+
+ RefPtr<BrowsingContext> 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<WindowContext> context = new WindowContext(
+ bc, aInit.mInnerWindowId, aInit.mOuterWindowId, 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->LookupOrInsert(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 |
+ nsIWebProgressListener::STATE_HTTPS_ONLY_MODE_UPGRADED_FIRST)) ==
+ aStateFlags,
+ "Invalid flags specified!");
+
+ if (XRE_IsParentProcess()) {
+ Canonical()->AddSecurityState(aStateFlags);
+ } else {
+ ContentChild* child = ContentChild::GetSingleton();
+ child->SendAddSecurityState(this, aStateFlags);
+ }
+}
+
+void WindowContext::NotifyUserGestureActivation(
+ UserActivation::Modifiers
+ aModifiers /* = UserActivation::Modifiers::None() */) {
+ UserActivation::StateAndModifiers stateAndModifiers;
+ stateAndModifiers.SetState(UserActivation::State::FullActivated);
+ stateAndModifiers.SetModifiers(aModifiers);
+ Unused << SetUserActivationStateAndModifiers(stateAndModifiers.GetRawData());
+}
+
+void WindowContext::NotifyResetUserGestureActivation() {
+ UserActivation::StateAndModifiers stateAndModifiers;
+ stateAndModifiers.SetState(UserActivation::State::None);
+ Unused << SetUserActivationStateAndModifiers(stateAndModifiers.GetRawData());
+}
+
+bool WindowContext::HasBeenUserGestureActivated() {
+ return GetUserActivationState() != UserActivation::State::None;
+}
+
+const TimeStamp& WindowContext::GetUserGestureStart() const {
+ MOZ_ASSERT(IsInProcess());
+ return mUserGestureStart;
+}
+
+bool WindowContext::HasValidTransientUserGestureActivation() {
+ MOZ_ASSERT(IsInProcess());
+
+ if (GetUserActivationState() != UserActivation::State::FullActivated) {
+ // mUserGestureStart should be null if the document hasn't ever been
+ // activated by user gesture
+ MOZ_ASSERT_IF(GetUserActivationState() == UserActivation::State::None,
+ mUserGestureStart.IsNull());
+ 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(IsInProcess());
+ MOZ_ASSERT(IsCurrent());
+
+ if (!HasValidTransientUserGestureActivation()) {
+ return false;
+ }
+
+ BrowsingContext* top = mBrowsingContext->Top();
+ top->PreOrderWalk([&](BrowsingContext* aBrowsingContext) {
+ WindowContext* windowContext = aBrowsingContext->GetCurrentWindowContext();
+ if (windowContext && windowContext->GetUserActivationState() ==
+ UserActivation::State::FullActivated) {
+ auto stateAndModifiers = UserActivation::StateAndModifiers(
+ GetUserActivationStateAndModifiers());
+ stateAndModifiers.SetState(UserActivation::State::HasBeenActivated);
+ Unused << windowContext->SetUserActivationStateAndModifiers(
+ stateAndModifiers.GetRawData());
+ }
+ });
+
+ return true;
+}
+
+bool WindowContext::GetTransientUserGestureActivationModifiers(
+ UserActivation::Modifiers* aModifiers) {
+ if (!HasValidTransientUserGestureActivation()) {
+ return false;
+ }
+
+ auto stateAndModifiers =
+ UserActivation::StateAndModifiers(GetUserActivationStateAndModifiers());
+ *aModifiers = stateAndModifiers.GetModifiers();
+ 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();
+}
+
+void WindowContext::TransientSetHasActivePeerConnections() {
+ if (!IsTop()) {
+ return;
+ }
+
+ mFields.SetWithoutSyncing<IDX_HasActivePeerConnections>(true);
+}
+
+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,
+ FieldValues&& aInit)
+ : mFields(std::move(aInit)),
+ mInnerWindowId(aInnerWindowId),
+ mOuterWindowId(aOuterWindowId),
+ mBrowsingContext(aBrowsingContext) {
+ MOZ_ASSERT(mBrowsingContext);
+ MOZ_ASSERT(mInnerWindowId);
+ MOZ_ASSERT(mOuterWindowId);
+ RecomputeCanExecuteScripts(/* aApplyChanges */ false);
+}
+
+WindowContext::~WindowContext() {
+ if (gWindowContexts) {
+ gWindowContexts->Remove(InnerWindowId());
+ }
+}
+
+JSObject* WindowContext::WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> 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_WRAPPERCACHE_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(mNonSyntheticChildren)
+ 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(mNonSyntheticChildren)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+} // namespace dom
+
+namespace ipc {
+
+void IPDLParamTraits<dom::MaybeDiscarded<dom::WindowContext>>::Write(
+ IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const dom::MaybeDiscarded<dom::WindowContext>& aParam) {
+ uint64_t id = aParam.ContextId();
+ WriteIPDLParam(aWriter, aActor, id);
+}
+
+bool IPDLParamTraits<dom::MaybeDiscarded<dom::WindowContext>>::Read(
+ IPC::MessageReader* aReader, IProtocol* aActor,
+ dom::MaybeDiscarded<dom::WindowContext>* aResult) {
+ uint64_t id = 0;
+ if (!ReadIPDLParam(aReader, aActor, &id)) {
+ return false;
+ }
+
+ if (id == 0) {
+ *aResult = nullptr;
+ } else if (RefPtr<dom::WindowContext> wc = dom::WindowContext::GetById(id)) {
+ *aResult = std::move(wc);
+ } else {
+ aResult->SetDiscarded(id);
+ }
+ return true;
+}
+
+void IPDLParamTraits<dom::WindowContext::IPCInitializer>::Write(
+ IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const dom::WindowContext::IPCInitializer& aInit) {
+ // Write actor ID parameters.
+ WriteIPDLParam(aWriter, aActor, aInit.mInnerWindowId);
+ WriteIPDLParam(aWriter, aActor, aInit.mOuterWindowId);
+ WriteIPDLParam(aWriter, aActor, aInit.mBrowsingContextId);
+ WriteIPDLParam(aWriter, aActor, aInit.mFields);
+}
+
+bool IPDLParamTraits<dom::WindowContext::IPCInitializer>::Read(
+ IPC::MessageReader* aReader, IProtocol* aActor,
+ dom::WindowContext::IPCInitializer* aInit) {
+ // Read actor ID parameters.
+ return ReadIPDLParam(aReader, aActor, &aInit->mInnerWindowId) &&
+ ReadIPDLParam(aReader, aActor, &aInit->mOuterWindowId) &&
+ ReadIPDLParam(aReader, aActor, &aInit->mBrowsingContextId) &&
+ ReadIPDLParam(aReader, aActor, &aInit->mFields);
+}
+
+template struct IPDLParamTraits<dom::WindowContext::BaseTransaction>;
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/docshell/base/WindowContext.h b/docshell/base/WindowContext.h
new file mode 100644
index 0000000000..fda4030045
--- /dev/null
+++ b/docshell/base/WindowContext.h
@@ -0,0 +1,429 @@
+/* -*- 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/WeakPtr.h"
+#include "mozilla/Span.h"
+#include "mozilla/dom/MaybeDiscarded.h"
+#include "mozilla/dom/SyncedContext.h"
+#include "mozilla/dom/UserActivation.h"
+#include "nsDOMNavigationTiming.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<uint32_t>) \
+ 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) \
+ /* Whether this window is using its unpartitioned cookies due to \
+ * the Storage Access API */ \
+ FIELD(UsingStorageAccess, bool) \
+ FIELD(ShouldResistFingerprinting, bool) \
+ FIELD(OverriddenFingerprintingSettings, Maybe<RFPTarget>) \
+ 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(UserActivationStateAndModifiers, \
+ UserActivation::StateAndModifiers::DataT) \
+ 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<uint64_t>) \
+ /* 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) \
+ /* Whether any of the windows in the subtree rooted at this window has \
+ * active peer connections or not (only set on the top window). */ \
+ FIELD(HasActivePeerConnections, bool) \
+ /* Whether we can execute scripts in this WindowContext. Has no effect \
+ * unless scripts are also allowed in the BrowsingContext. */ \
+ FIELD(AllowJavascript, bool) \
+ /* If this field is `true`, it means that this WindowContext's \
+ * WindowState was saved to be stored in the legacy (non-SHIP) BFCache \
+ * implementation. Always false for SHIP */ \
+ FIELD(WindowStateSaved, 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_WRAPPERCACHE_CLASS(WindowContext)
+
+ public:
+ static already_AddRefed<WindowContext> GetById(uint64_t aInnerWindowId);
+ static LogModule* GetLog();
+ static LogModule* GetSyncLog();
+
+ 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; }
+
+ // Returns `true` if this WindowContext is the current WindowContext in its
+ // BrowsingContext.
+ bool IsCurrent() const;
+
+ // Returns `true` if this WindowContext is currently in the BFCache.
+ bool IsInBFCache();
+
+ bool IsInProcess() const { return mIsInProcess; }
+
+ bool HasBeforeUnload() const { return GetHasBeforeUnload(); }
+
+ bool IsLocalIP() const { return GetIsLocalIP(); }
+
+ bool ShouldResistFingerprinting() const {
+ return GetShouldResistFingerprinting();
+ }
+
+ Nullable<uint64_t> GetOverriddenFingerprintingSettingsWebIDL() const {
+ Maybe<RFPTarget> overriddenFingerprintingSettings =
+ GetOverriddenFingerprintingSettings();
+
+ return overriddenFingerprintingSettings.isSome()
+ ? Nullable<uint64_t>(
+ uint64_t(overriddenFingerprintingSettings.ref()))
+ : Nullable<uint64_t>();
+ }
+
+ 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 <browser> boundaries.
+ WindowContext* GetParentWindowContext();
+ WindowContext* TopWindowContext();
+
+ bool SameOriginWithTop() const;
+
+ bool IsTop() const;
+
+ Span<RefPtr<BrowsingContext>> Children() { return mChildren; }
+
+ // The filtered version of `Children()`, which contains no browsing contexts
+ // for synthetic documents as created by object loading content.
+ Span<RefPtr<BrowsingContext>> NonSyntheticChildren() {
+ return mNonSyntheticChildren;
+ }
+
+ // Cast this object to it's parent-process canonical form.
+ WindowGlobalParent* Canonical();
+
+ nsIGlobalObject* GetParentObject() const;
+ JSObject* WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> 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);
+
+ UserActivation::State GetUserActivationState() const {
+ return UserActivation::StateAndModifiers(
+ GetUserActivationStateAndModifiers())
+ .GetState();
+ }
+
+ // This function would be called when its corresponding window is activated
+ // by user gesture.
+ void NotifyUserGestureActivation(
+ UserActivation::Modifiers aModifiers = UserActivation::Modifiers::None());
+
+ // 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();
+
+ // See `mUserGestureStart`.
+ const TimeStamp& GetUserGestureStart() const;
+
+ // 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 GetTransientUserGestureActivationModifiers(
+ UserActivation::Modifiers* aModifiers);
+
+ bool CanShowPopup();
+
+ bool AllowJavascript() const { return GetAllowJavascript(); }
+ bool CanExecuteScripts() const { return mCanExecuteScripts; }
+
+ void TransientSetHasActivePeerConnections();
+
+ protected:
+ WindowContext(BrowsingContext* aBrowsingContext, uint64_t aInnerWindowId,
+ uint64_t aOuterWindowId, 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);
+
+ // Update non-synthetic children based on whether `aBrowsingContext`
+ // is synthetic or not. Regardless the synthetic of `aBrowsingContext`, it is
+ // kept in this WindowContext's all children list.
+ void UpdateChildSynthetic(BrowsingContext* aBrowsingContext,
+ bool aIsSynthetic);
+
+ // 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<IDX_IsSecure>, const bool& aIsSecure,
+ ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_AllowMixedContent>, const bool& aAllowMixedContent,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_HasBeforeUnload>, const bool& aHasBeforeUnload,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_CookieBehavior>, const Maybe<uint32_t>& aValue,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_IsOnContentBlockingAllowList>, const bool& aValue,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_EmbedderPolicy>, const bool& aValue,
+ ContentParent* aSource) {
+ return true;
+ }
+
+ bool CanSet(FieldIndex<IDX_IsThirdPartyWindow>,
+ const bool& IsThirdPartyWindow, ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_IsThirdPartyTrackingResourceWindow>,
+ const bool& aIsThirdPartyTrackingResourceWindow,
+ ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_UsingStorageAccess>,
+ const bool& aUsingStorageAccess, ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_ShouldResistFingerprinting>,
+ const bool& aShouldResistFingerprinting, ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_OverriddenFingerprintingSettings>,
+ const Maybe<RFPTarget>& aValue, ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_IsSecureContext>, const bool& aIsSecureContext,
+ ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_IsOriginalFrameSource>,
+ const bool& aIsOriginalFrameSource, ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_DocTreeHadMedia>, const bool& aValue,
+ ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_AutoplayPermission>, const uint32_t& aValue,
+ ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_ShortcutsPermission>, const uint32_t& aValue,
+ ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_ActiveMediaSessionContextId>,
+ const Maybe<uint64_t>& aValue, ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_PopupPermission>, const uint32_t&,
+ ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_SHEntryHasUserInteraction>,
+ const bool& aSHEntryHasUserInteraction, ContentParent* aSource) {
+ return true;
+ }
+ bool CanSet(FieldIndex<IDX_DelegatedPermissions>,
+ const PermissionDelegateHandler::DelegatedPermissionList& aValue,
+ ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_DelegatedExactHostMatchPermissions>,
+ const PermissionDelegateHandler::DelegatedPermissionList& aValue,
+ ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_UserActivationStateAndModifiers>,
+ const UserActivation::StateAndModifiers::DataT&
+ aUserActivationStateAndModifiers,
+ ContentParent* aSource) {
+ return true;
+ }
+
+ bool CanSet(FieldIndex<IDX_HasReportedShadowDOMUsage>, const bool& aValue,
+ ContentParent* aSource) {
+ return true;
+ }
+
+ bool CanSet(FieldIndex<IDX_IsLocalIP>, const bool& aValue,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_AllowJavascript>, bool aValue,
+ ContentParent* aSource);
+ void DidSet(FieldIndex<IDX_AllowJavascript>, bool aOldValue);
+
+ bool CanSet(FieldIndex<IDX_HasActivePeerConnections>, bool, ContentParent*);
+
+ void DidSet(FieldIndex<IDX_HasReportedShadowDOMUsage>, bool aOldValue);
+
+ void DidSet(FieldIndex<IDX_SHEntryHasUserInteraction>, bool aOldValue);
+
+ bool CanSet(FieldIndex<IDX_WindowStateSaved>, bool aValue,
+ ContentParent* aSource);
+
+ // 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 <size_t I>
+ void DidSet(FieldIndex<I>) {}
+ template <size_t I, typename T>
+ void DidSet(FieldIndex<I>, T&& aOldValue) {}
+ void DidSet(FieldIndex<IDX_UserActivationStateAndModifiers>);
+
+ // Recomputes whether we can execute scripts in this WindowContext based on
+ // the value of AllowJavascript() and whether scripts are allowed in the
+ // BrowsingContext.
+ void RecomputeCanExecuteScripts(bool aApplyChanges = true);
+
+ const uint64_t mInnerWindowId;
+ const uint64_t mOuterWindowId;
+ RefPtr<BrowsingContext> mBrowsingContext;
+ WeakPtr<WindowGlobalChild> mWindowGlobalChild;
+
+ // --- 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<RefPtr<BrowsingContext>> mChildren;
+
+ // --- NEVER CHANGE `mNonSyntheticChildren` DIRECTLY! ---
+ // Same reason as for mChildren.
+ // mNonSyntheticChildren contains the same browsing contexts except browsing
+ // contexts created by the synthetic document for object loading contents
+ // loading images. This is used to discern browsing contexts created when
+ // loading images in <object> or <embed> elements, so that they can be hidden
+ // from named targeting, `Window.frames` etc.
+ nsTArray<RefPtr<BrowsingContext>> mNonSyntheticChildren;
+
+ bool mIsDiscarded = false;
+ bool mIsInProcess = false;
+
+ // Determines if we can execute scripts in this WindowContext. True if
+ // AllowJavascript() is true and script execution is allowed in the
+ // BrowsingContext.
+ bool mCanExecuteScripts = true;
+
+ // 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<WindowContext>;
+
+// 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<WindowContext>;
+
+} // namespace dom
+
+namespace ipc {
+template <>
+struct IPDLParamTraits<dom::MaybeDiscarded<dom::WindowContext>> {
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const dom::MaybeDiscarded<dom::WindowContext>& aParam);
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ dom::MaybeDiscarded<dom::WindowContext>* aResult);
+};
+
+template <>
+struct IPDLParamTraits<dom::WindowContext::IPCInitializer> {
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const dom::WindowContext::IPCInitializer& aInitializer);
+
+ static bool Read(IPC::MessageReader* aReader, 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 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<!--
+user_pref("browser.send_pings", true);
+-->
+<script>
+
+function boom() {
+ var aLink = document.createElement('a');
+ document.body.appendChild(aLink);
+ aLink.ping = "ping";
+ aLink.href = "href";
+ aLink.click();
+
+ var baseElement = document.createElement('base');
+ baseElement.setAttribute("href", "javascript:void 0");
+ document.head.appendChild(baseElement);
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+function boom() {
+ setTimeout(function(){
+ var o=document.getElementById('b');
+ document.getElementById('a').appendChild(o.parentNode.removeChild(o));
+ },0);
+ var o=document.getElementById('c');
+ var p=document.getElementById('b');
+ p.id=[o.id, o.id=p.id][0];
+ o=document.getElementById('b');
+ o.setAttribute('sandbox', 'disc');
+ window.location.reload(true);
+}
+</script>
+</head>
+<body onload="boom();">
+<header id='a'></header>
+<output id='b'></output>
+<iframe id='c' sandbox='allow-same-origin' src='http://a'></iframe>
+</body>
+</html>
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 @@
+<html class="reftest-wait">
+ <head>
+ <script>
+ function boom() {
+ o1 = document.createElement("script");
+ o2 = document.implementation.createDocument('', '', null);
+ o3 = document.createElement("iframe");
+ document.documentElement.appendChild(o3);
+ o4 = o3.contentWindow;
+ o5 = document.createTextNode('o2.adoptNode(o3); try { o4.location = "" } catch(e) {}');
+ o1.appendChild(o5);
+ document.documentElement.appendChild(o1);
+ document.documentElement.classList.remove("reftest-wait");
+ }
+ </script>
+ </head>
+ <body onload="boom();"></body>
+</html>
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 @@
+<script>
+window.onload = () => {
+ a.addEventListener("DOMSubtreeModified", () => {
+ document.body.appendChild(b)
+ document.body.removeChild(b)
+ window[1]
+ })
+ a.type = ""
+}
+</script>
+<embed id="a">
+<iframe id="b"></iframe>
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 @@
+<script>
+window.onload = () => {
+ b.addEventListener('DOMSubtreeModified', () => {
+ var o = document.getElementById('a')
+ var a = o.attributes
+ for (let j = 0; j < a.length; j++) {
+ o.setAttribute(a[j].name, 'i')
+ o.parentNode.appendChild(o)
+ }
+ })
+ b.setAttribute('a', b)
+}
+</script>
+<iframe id='a' sandbox='' allowfullscreen=''></iframe>
+<dfn id='b'>
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 @@
+<script>
+document.addEventListener("DOMContentLoaded", () => {
+ let o = document.getElementById('a')
+ o.setAttribute('id', '')
+ o.setAttribute('sandbox', '')
+})
+</script>
+<iframe id='a' sandbox='s' src='http://%CF'></iframe>
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 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+ <meta charset="UTF-8">
+<script>
+ function go() {
+ let win = window.open("1667491_1.html");
+ win.finish = function() {
+ document.documentElement.removeAttribute("class");
+ };
+ }
+</script>
+</head>
+<body onload="go()">
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <script>
+ function go() {
+ document.body.appendChild(a)
+ window.frames[0].onbeforeunload = document.createElement("body").onload;
+ window.requestIdleCallback(() => {
+ window.close();
+ finish();
+ });
+ }
+ </script>
+</head>
+<body onload="go()">
+<iframe id="a"></iframe>
+<iframe></iframe>
+</body>
+</html>
+
diff --git a/docshell/base/crashtests/1672873.html b/docshell/base/crashtests/1672873.html
new file mode 100644
index 0000000000..33aa92f0ef
--- /dev/null
+++ b/docshell/base/crashtests/1672873.html
@@ -0,0 +1,6 @@
+<script>
+document.addEventListener('DOMContentLoaded', () => {
+ var x = new Blob([undefined, ''], { })
+ self.history.pushState(x, 'x', 'missing.file')
+})
+</script>
diff --git a/docshell/base/crashtests/1690169-1.html b/docshell/base/crashtests/1690169-1.html
new file mode 100644
index 0000000000..6c9be20be3
--- /dev/null
+++ b/docshell/base/crashtests/1690169-1.html
@@ -0,0 +1,11 @@
+<script>
+var woff = "data:font/woff2;base64,";
+for (let i = 0; i < 20000; i++) {
+ woff += "d09GMgABAAAA";
+}
+window.onload = () => {
+ try { window.open('data:text/html,<spacer>', 'pu9', 'width=911').close() } catch (e) {}
+ document.getElementById('a').setAttribute('src', woff)
+}
+</script>
+<iframe id='a' hidden src='http://a'></iframe>
diff --git a/docshell/base/crashtests/1753136.html b/docshell/base/crashtests/1753136.html
new file mode 100644
index 0000000000..22f679309e
--- /dev/null
+++ b/docshell/base/crashtests/1753136.html
@@ -0,0 +1,2 @@
+<meta charset="UTF-8">
+<iframe sandbox='' src='http://🙅🎂งิ'></iframe>
diff --git a/docshell/base/crashtests/1804803.html b/docshell/base/crashtests/1804803.html
new file mode 100644
index 0000000000..5103c00416
--- /dev/null
+++ b/docshell/base/crashtests/1804803.html
@@ -0,0 +1,13 @@
+<html class="reftest-wait">
+<script>
+function test() {
+ // If the reload in the iframe succeeds we might crash, so wait for it.
+ setTimeout(function() {
+ document.documentElement.className = "";
+ }, 500);
+}
+</script>
+<body onload="test()">
+ <iframe src="1804803.sjs"></iframe>
+</body>
+</html>
diff --git a/docshell/base/crashtests/1804803.sjs b/docshell/base/crashtests/1804803.sjs
new file mode 100644
index 0000000000..0486e32048
--- /dev/null
+++ b/docshell/base/crashtests/1804803.sjs
@@ -0,0 +1,18 @@
+function handleRequest(request, response) {
+ let counter = Number(getState("load"));
+ const reload = counter == 0 ? "self.history.go(0);" : "";
+ setState("load", String(++counter));
+ const document = `
+<script>
+ document.addEventListener('DOMContentLoaded', () => {
+ ${reload}
+ let url = window.location.href;
+ for (let i = 0; i < 50; i++) {
+ self.history.pushState({x: i}, '', url + "#" + i);
+ }
+ });
+</script>
+`;
+
+ response.write(document);
+}
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 @@
+<html class="reftest-wait">
+<head>
+<script>
+function boom()
+{
+ document.getElementById("frameset").removeChild(document.getElementById("frame"));
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+
+<frameset id="frameset" onload="setTimeout(boom, 100)">
+ <frame id="frame" src="data:text/html,<body onUnload=&quot;location = 'http://www.mozilla.org/'&quot;>This frame's onunload tries to load another page.">
+</frameset>
+
+</html>
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 @@
+<html><head><title>Infinite Loop</title></head>
+<body onLoad="initNav(); initNav();">
+
+<script language="JavaScript">
+
+function initNav() {
+ ++parent.i;
+ if (parent.i < 10)
+ window.location.href=window.location.href;
+}
+
+</script>
+
+</body></html>
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 @@
+<html>
+<head><title>Infinite Loop</title><script>var i=0;</script></head>
+<body>
+<iframe src="40929-1-inner.html"></iframe>
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<head></head>
+<body onpagehide="document.getElementById('a').focus();"><div id="a"></div></body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body onpagehide="document.body.removeChild(document.getElementById('s'));">
+<span id="s" contenteditable="true"></span>
+</body>
+</html>
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 @@
+<html>
+<head>
+<title>Bug - Crash [@ PL_DHashTableOperate] with DOMNodeInserted event listener removing window and frameset contenteditable</title>
+</head>
+<body>
+<iframe id="content" src="data:text/html;charset=utf-8,%3Cscript%3E%0Awindow.addEventListener%28%27DOMNodeInserted%27%2C%20function%28%29%20%7Bwindow.frameElement.parentNode.removeChild%28window.frameElement%29%3B%7D%2C%20true%29%3B%0A%3C/script%3E%0A%3Cframeset%20contenteditable%3D%22true%22%3E"></iframe>
+</body>
+</html>
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 @@
+<html class="reftest-wait">
+<head>
+<title>testcase2 Bug 432114 � Crash [@ PL_DHashTableOperate] with DOMNodeInserted event listener removing window and frameset contenteditable</title>
+</head>
+<body>
+<script>
+ window.addEventListener("DOMNodeRemoved", function() {
+ setTimeout(function() {
+ document.documentElement.removeAttribute("class");
+ }, 0);
+ });
+ var iframe = document.getElementById("content");
+ iframe.onload=function() {
+ dump("iframe onload\n");
+ console.log("iframe onload");
+ };
+</script>
+<iframe id="content" src="file_432114-2.xhtml" style="width:1000px;height: 200px;"></iframe>
+
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<head>
+
+<meta http-equiv="refresh" content="0">
+
+<script language="javascript">
+
+location.hash += "+++";
+
+function done()
+{
+ parent.document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+<body onload="setTimeout(done, 10)">
+
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+</head>
+<body>
+<iframe src="436900-1-inner.html#foo"></iframe>
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<head>
+
+<meta http-equiv="refresh" content="0">
+
+<script language="javascript" id="foo+++">
+
+location.hash += "+++";
+
+function done()
+{
+ parent.document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+<body onload="setTimeout(done, 10)">
+
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+</head>
+<body>
+<iframe src="436900-2-inner.html#foo"></iframe>
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+
+<body onload="document.removeChild(document.documentElement)">
+
+<!-- The order of the two iframes matters! -->
+
+<iframe src='data:text/html,<body onload="s = parent.document.getElementById(&apos;s&apos;).contentWindow;" onunload="s.location = s.location;">'></iframe>
+
+<iframe id="s"></iframe>
+
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html>
+<body onload="test();">
+<script>
+ function test() {
+ // Test that calling pushState() with a state object which calls
+ // history.back() doesn't crash. We need to make sure that there's at least
+ // one entry in the history before we do anything else.
+ history.pushState(null, "");
+
+ x = {};
+ x.toJSON = { history.back(); return "{a:1}"; };
+ history.pushState(x, "");
+ }
+</script>
+</body>
+</html>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head></head>
+
+<body onunload="document.getElementById('tbody').appendChild(document.createElementNS('http://www.w3.org/1999/xhtml', 'span'))">
+ <iframe/>
+ <tbody contenteditable="true" id="tbody">xy</tbody>
+</body>
+
+</html>
diff --git a/docshell/base/crashtests/614499-1.html b/docshell/base/crashtests/614499-1.html
new file mode 100644
index 0000000000..7053a3f52f
--- /dev/null
+++ b/docshell/base/crashtests/614499-1.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var f = document.getElementById("f");
+
+ for (var i = 0; i < 50; ++i) {
+ f.contentWindow.history.pushState({}, "");
+ }
+
+ document.body.removeChild(f);
+}
+
+</script>
+</head>
+<body onload="boom();"><iframe id="f" src="data:text/html,1"></iframe></body>
+</html> \ 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 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+var f1, f2;
+
+function b1()
+{
+ f1 = document.getElementById("f1");
+ f2 = document.getElementById("f2");
+ f1.contentWindow.document.write("11");
+ f1.contentWindow.history.back();
+ setTimeout(b2, 0);
+}
+
+function b2()
+{
+ f2.contentWindow.history.forward();
+ f2.contentWindow.location.reload();
+ f1.remove();
+}
+
+</script>
+
+
+</script>
+</head>
+
+<body onload="setTimeout(b1, 0);">
+
+<iframe id="f1" src="data:text/html,1"></iframe>
+<iframe id="f2" src="data:text/html,2"></iframe>
+
+</body>
+</html>
diff --git a/docshell/base/crashtests/914521.html b/docshell/base/crashtests/914521.html
new file mode 100644
index 0000000000..8196e43016
--- /dev/null
+++ b/docshell/base/crashtests/914521.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<meta charset="UTF-8">
+<script>
+
+function f()
+{
+ function spin() {
+ for (var i = 0; i < 8; ++i) {
+ var x = new XMLHttpRequest();
+ x.open('GET', 'data:text/html,' + i, false);
+ x.send();
+ }
+ }
+
+ window.addEventListener("popstate", spin);
+ window.close();
+ window.location = "#c";
+ setTimeout(finish,0);
+}
+
+var win;
+function finish() {
+ win.close();
+ document.documentElement.removeAttribute("class");
+}
+
+function start()
+{
+ win = window.open("javascript:'<html><body>dummy</body></html>';", null, "width=300,height=300");
+ win.onload = f;
+}
+
+</script>
+</head>
+<body onload="start();"></body>
+</html>
diff --git a/docshell/base/crashtests/crashtests.list b/docshell/base/crashtests/crashtests.list
new file mode 100644
index 0000000000..f9b214bfa2
--- /dev/null
+++ b/docshell/base/crashtests/crashtests.list
@@ -0,0 +1,25 @@
+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
+pref(dom.disable_open_during_load,false) load 1690169-1.html
+load 1672873.html
+load 1753136.html
+HTTP load 1804803.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 @@
+<html xmlns='http://www.w3.org/1999/xhtml'><frameset contenteditable='true'/><script>function doExecCommand(){dump("doExecCommand\n");document.execCommand('formatBlock', false, 'p');}setTimeout(doExecCommand,100); window.addEventListener('DOMNodeRemoved', function() {window.frameElement.parentNode.removeChild(window.frameElement);}, true);</script></html>
diff --git a/docshell/base/metrics.yaml b/docshell/base/metrics.yaml
new file mode 100644
index 0000000000..ddb3457945
--- /dev/null
+++ b/docshell/base/metrics.yaml
@@ -0,0 +1,29 @@
+# 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/.
+
+# Adding a new metric? We have docs for that!
+# https://firefox-source-docs.mozilla.org/toolkit/components/glean/user/new_definitions_file.html
+
+---
+$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0
+$tags:
+ - 'Core :: DOM: Navigation'
+
+performance.page:
+ total_content_page_load:
+ type: timing_distribution
+ time_unit: millisecond
+ telemetry_mirror: TOTAL_CONTENT_PAGE_LOAD_TIME
+ description: >
+ Time to load all of a page's resources and render.
+ (Migrated from the geckoview metric of the same name.)
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1580077
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1877842
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1580077#c10
+ notification_emails:
+ - perf-telemetry-alerts@mozilla.com
+ - bdekoz@mozilla.com
+ expires: never
diff --git a/docshell/base/moz.build b/docshell/base/moz.build
new file mode 100644
index 0000000000..3520e9d75a
--- /dev/null
+++ b/docshell/base/moz.build
@@ -0,0 +1,126 @@
+# -*- 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")
+
+XPIDL_SOURCES += [
+ "nsIDocShell.idl",
+ "nsIDocShellTreeItem.idl",
+ "nsIDocShellTreeOwner.idl",
+ "nsIDocumentLoaderFactory.idl",
+ "nsIDocumentViewer.idl",
+ "nsIDocumentViewerEdit.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",
+ "nsDSURIContentListener.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",
+]
+
+EXTRA_JS_MODULES += ["URIFixup.sys.mjs"]
+
+include("/tools/fuzzing/libfuzzer-config.mozbuild")
diff --git a/docshell/base/nsAboutRedirector.cpp b/docshell/base/nsAboutRedirector.cpp
new file mode 100644
index 0000000000..fdae228b90
--- /dev/null
+++ b/docshell/base/nsAboutRedirector.cpp
@@ -0,0 +1,318 @@
+/* -*- 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"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/RemoteType.h"
+#include "mozilla/gfx/GPUProcessManager.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:crashgpu") && XRE_IsParentProcess()) {
+ if (auto* gpu = mozilla::gfx::GPUProcessManager::Get()) {
+ gpu->CrashProcess();
+ }
+ }
+
+ if (spec.EqualsASCII("about:crashcontent") && XRE_IsContentProcess()) {
+ MOZ_CRASH("Crash via about:crashcontent");
+ }
+
+ if (spec.EqualsASCII("about:crashextensions") && XRE_IsParentProcess()) {
+ using ContentParent = mozilla::dom::ContentParent;
+ nsTArray<RefPtr<ContentParent>> toKill;
+ for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
+ if (cp->GetRemoteType() == EXTENSION_REMOTE_TYPE) {
+ toKill.AppendElement(cp);
+ }
+ }
+ for (auto& cp : toKill) {
+ cp->KillHard("Killed via about:crashextensions");
+ }
+ }
+
+ 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.
+
+ NOTE: changes to this redir map need to be accompanied with changes to
+ docshell/build/components.conf
+ */
+static const RedirEntry kRedirMap[] = {
+ {"about", "chrome://global/content/aboutAbout.html", 0},
+ {"addons", "chrome://mozapps/content/extensions/aboutaddons.html",
+ nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::IS_SECURE_CHROME_UI},
+ {"buildconfig", "chrome://global/content/buildconfig.html",
+ nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ nsIAboutModule::IS_SECURE_CHROME_UI},
+ {"checkerboard", "chrome://global/content/aboutCheckerboard.html",
+ nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ nsIAboutModule::ALLOW_SCRIPT},
+#ifndef MOZ_WIDGET_ANDROID
+ {"config", "chrome://global/content/aboutconfig/aboutconfig.html",
+ nsIAboutModule::IS_SECURE_CHROME_UI},
+#else
+ {"config", "chrome://geckoview/content/config.xhtml",
+ nsIAboutModule::IS_SECURE_CHROME_UI},
+#endif
+#ifdef MOZ_CRASHREPORTER
+ {"crashes", "chrome://global/content/crashes.html",
+ nsIAboutModule::IS_SECURE_CHROME_UI},
+#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 |
+ nsIAboutModule::IS_SECURE_CHROME_UI},
+ {"logging", "chrome://global/content/aboutLogging.html",
+ nsIAboutModule::ALLOW_SCRIPT},
+ {"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 |
+ nsIAboutModule::IS_SECURE_CHROME_UI},
+ {"mozilla", "chrome://global/content/mozilla.html",
+ nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT},
+#if !defined(ANDROID) && !defined(XP_WIN)
+ {"webauthn", "chrome://global/content/aboutWebauthn.html",
+ nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::IS_SECURE_CHROME_UI},
+#endif
+ {"neterror", "chrome://global/content/aboutNetError.html",
+ 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", "about:processes",
+ nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::IS_SECURE_CHROME_UI |
+ nsIAboutModule::HIDE_FROM_ABOUTABOUT},
+ {"processes", "chrome://global/content/aboutProcesses.html",
+ nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::IS_SECURE_CHROME_UI},
+ // about:serviceworkers always wants to load in the parent process because
+ // 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 | nsIAboutModule::IS_SECURE_CHROME_UI},
+#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 | nsIAboutModule::IS_SECURE_CHROME_UI},
+#ifdef XP_WIN
+ {"third-party", "chrome://global/content/aboutThirdParty.html",
+ nsIAboutModule::ALLOW_SCRIPT},
+ {"windows-messages", "chrome://global/content/aboutWindowsMessages.html",
+ nsIAboutModule::ALLOW_SCRIPT},
+#endif
+#ifndef MOZ_GLEAN_ANDROID
+ {"glean", "chrome://global/content/aboutGlean.html",
+# if !defined(NIGHTLY_BUILD) && defined(MOZILLA_OFFICIAL)
+ nsIAboutModule::HIDE_FROM_ABOUTABOUT |
+# endif
+ nsIAboutModule::ALLOW_SCRIPT},
+#endif
+ {"telemetry", "chrome://global/content/aboutTelemetry.xhtml",
+ nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::IS_SECURE_CHROME_UI},
+ {"translations", "chrome://global/content/translations/translations.html",
+ nsIAboutModule::ALLOW_SCRIPT |
+ nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ nsIAboutModule::URI_MUST_LOAD_IN_CHILD |
+ nsIAboutModule::URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS |
+ nsIAboutModule::HIDE_FROM_ABOUTABOUT},
+ {"url-classifier", "chrome://global/content/aboutUrlClassifier.xhtml",
+ nsIAboutModule::ALLOW_SCRIPT},
+ {"webrtc", "chrome://global/content/aboutwebrtc/aboutWebrtc.html",
+ nsIAboutModule::ALLOW_SCRIPT},
+ {"crashparent", "about:blank", nsIAboutModule::HIDE_FROM_ABOUTABOUT},
+ {"crashcontent", "about:blank",
+ nsIAboutModule::HIDE_FROM_ABOUTABOUT |
+ nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ nsIAboutModule::URI_CAN_LOAD_IN_CHILD |
+ nsIAboutModule::URI_MUST_LOAD_IN_CHILD},
+ {"crashgpu", "about:blank", nsIAboutModule::HIDE_FROM_ABOUTABOUT},
+ {"crashextensions", "about:blank", nsIAboutModule::HIDE_FROM_ABOUTABOUT}};
+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<nsIIOService> ioService = do_GetIOService(&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (path.EqualsASCII("crashparent") || path.EqualsASCII("crashcontent") ||
+ path.EqualsASCII("crashgpu") || path.EqualsASCII("crashextensions")) {
+ bool isExternal;
+ aLoadInfo->GetLoadTriggeredFromExternal(&isExternal);
+ if (isExternal || !aLoadInfo->TriggeringPrincipal() ||
+ !aLoadInfo->TriggeringPrincipal()->IsSystemPrincipal()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsIChannel> 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<nsIChannel> tempChannel;
+ nsCOMPtr<nsIURI> 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(REFNSIID aIID, void** aResult) {
+ RefPtr<nsAboutRedirector> 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..0bf021cc31
--- /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(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..2eccb74da9
--- /dev/null
+++ b/docshell/base/nsDSURIContentListener.cpp
@@ -0,0 +1,297 @@
+/* -*- 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 "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_AMBIGUOUS(nsISupports, nsITimerCallback)
+ NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
+ NS_INTERFACE_MAP_ENTRY(nsINamed)
+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<BrowsingContext> 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<BrowsingContext>
+MaybeCloseWindowHelper::ChooseNewBrowsingContext(BrowsingContext* aBC) {
+ RefPtr<BrowsingContext> 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;
+}
+
+NS_IMETHODIMP
+MaybeCloseWindowHelper::GetName(nsACString& aName) {
+ aName.AssignLiteral("MaybeCloseWindowHelper");
+ 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);
+ RefPtr<nsDocShell> docShell = mDocShell;
+
+ *aAbortProcess = false;
+
+ // determine if the channel has just been retargeted to us...
+ nsLoadFlags loadFlags = 0;
+ if (nsCOMPtr<nsIChannel> openedChannel = do_QueryInterface(aRequest)) {
+ openedChannel->GetLoadFlags(&loadFlags);
+ }
+
+ if (loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI) {
+ // XXX: Why does this not stop the content too?
+ docShell->Stop(nsIWebNavigation::STOP_NETWORK);
+ NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE);
+ docShell->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<nsIChannel> baseChannel;
+ if (nsCOMPtr<nsIMultiPartChannel> mpchan = do_QueryInterface(aRequest)) {
+ mpchan->GetBaseChannel(getter_AddRefs(baseChannel));
+ }
+
+ bool reuseCV = baseChannel && baseChannel == mExistingJPEGRequest &&
+ aContentType.EqualsLiteral("image/jpeg");
+
+ if (mExistingJPEGStreamListener && reuseCV) {
+ RefPtr<nsIStreamListener> copy(mExistingJPEGStreamListener);
+ copy.forget(aContentHandler);
+ rv = NS_OK;
+ } else {
+ rv =
+ docShell->CreateDocumentViewer(aContentType, aRequest, aContentHandler);
+ if (NS_SUCCEEDED(rv) && reuseCV) {
+ mExistingJPEGStreamListener = *aContentHandler;
+ } else {
+ mExistingJPEGStreamListener = nullptr;
+ }
+ mExistingJPEGRequest = baseChannel;
+ }
+
+ if (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<nsIStreamListener> forget = dont_AddRef(*aContentHandler);
+ *aContentHandler = nullptr;
+ return rv;
+ }
+
+ if (loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI) {
+ nsCOMPtr<nsPIDOMWindowOuter> 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<nsIURIContentListener> 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));
+ *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<nsDocLoader> 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<nsIURIContentListener> 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..61ed36456f
--- /dev/null
+++ b/docshell/base/nsDSURIContentListener.h
@@ -0,0 +1,100 @@
+/* -*- 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 nsINamed {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ 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<mozilla::dom::BrowsingContext> ChooseNewBrowsingContext(
+ mozilla::dom::BrowsingContext* aBC);
+
+ /**
+ * The dom window associated to handle content.
+ */
+ RefPtr<mozilla::dom::BrowsingContext> 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<mozilla::dom::BrowsingContext> mBCToClose;
+ nsCOMPtr<nsITimer> 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<nsIStreamListener> mExistingJPEGStreamListener;
+ nsCOMPtr<nsIChannel> 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..f2a9e0fa59
--- /dev/null
+++ b/docshell/base/nsDocShell.cpp
@@ -0,0 +1,13763 @@
+/* -*- 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 <algorithm>
+
+#ifdef XP_WIN
+# include <process.h>
+# define getpid _getpid
+#else
+# include <unistd.h> // 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/CheckedInt.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/Preferences.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/ScrollTypes.h"
+#include "mozilla/SimpleEnumerator.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/StoragePrincipalHelper.h"
+#include "mozilla/Telemetry.h"
+
+#include "mozilla/Unused.h"
+#include "mozilla/WidgetUtils.h"
+
+#include "mozilla/dom/AutoEntryScript.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/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/SessionStoreChangeListener.h"
+#include "mozilla/dom/SessionStoreChild.h"
+#include "mozilla/dom/SessionStoreUtils.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/dom/DocumentBinding.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "mozilla/net/DocumentChannel.h"
+#include "mozilla/net/DocumentChannelChild.h"
+#include "mozilla/net/ParentChannelWrapper.h"
+#include "mozilla/net/UrlClassifierFeatureFactory.h"
+#include "ReferrerInfo.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 "nsIController.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsIDocumentViewer.h"
+#include "mozilla/dom/Document.h"
+#include "nsHTMLDocument.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 "nsIPublicKeyPinningService.h"
+#include "nsIReflowObserver.h"
+#include "nsIScriptChannel.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIScrollableFrame.h"
+#include "nsIScrollObserver.h"
+#include "nsISupportsPrimitives.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 "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 "nsGlobalWindowInner.h"
+#include "nsGlobalWindowOuter.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 "ThirdPartyUtil.h"
+#include "GeckoProfiler.h"
+#include "mozilla/NullPrincipal.h"
+#include "Navigator.h"
+#include "prenv.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "sslerr.h"
+#include "mozpkix/pkix.h"
+#include "NSSErrorsService.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
+
+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");
+extern mozilla::LazyLogModule gSHIPBFCacheLog;
+
+const char kAppstringsBundleURL[] =
+ "chrome://global/locale/appstrings.properties";
+
+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(true),
+ mContentWindowID(aContentWindowID),
+ mBrowsingContext(aBrowsingContext),
+ mParentCharset(nullptr),
+ mTreeOwner(nullptr),
+ mScrollbarPref(ScrollbarPreference::Auto),
+ mCharsetReloadState(eCharsetReloadInit),
+ mParentCharsetSource(0),
+ mFrameMargins(-1, -1),
+ mItemType(aBrowsingContext->IsContent() ? typeContent : typeChrome),
+ mPreviousEntryIndex(-1),
+ mLoadedEntryIndex(-1),
+ mBusyFlags(BUSY_FLAGS_NONE),
+ mAppType(nsIDocShell::APP_TYPE_UNKNOWN),
+ mLoadType(0),
+ mFailedLoadType(0),
+ mMetaViewportOverride(nsIDocShell::META_VIEWPORT_OVERRIDE_NONE),
+ mChannelToDisconnectOnPageHide(0),
+ mCreatingDocument(false),
+#ifdef DEBUG
+ mInEnsureScriptEnv(false),
+#endif
+ mInitialized(false),
+ mAllowSubframes(true),
+ mAllowMetaRedirects(true),
+ mAllowImages(true),
+ mAllowMedia(true),
+ mAllowDNSPrefetch(true),
+ mAllowWindowControl(true),
+ mCSSErrorReportingEnabled(false),
+ mAllowAuth(mItemType == typeContent),
+ mAllowKeywordFixup(false),
+ mDisableMetaRefreshWhenInactive(false),
+ mWindowDraggingAllowed(false),
+ mInFrameSwap(false),
+ mFiredUnloadEvent(false),
+ mEODForCurrentDocument(false),
+ mURIResultedInDocument(false),
+ mIsBeingDestroyed(false),
+ mIsExecutingOnLoadHandler(false),
+ mSavingOldViewer(false),
+ mInvisible(false),
+ mHasLoadedNonBlankURI(false),
+ mBlankTiming(false),
+ mTitleValidForCurrentURI(false),
+ mWillChangeProcess(false),
+ mIsNavigating(false),
+ mForcedAutodetection(false),
+ mCheckingSessionHistory(false),
+ mNeedToReportActiveAfterLoadingBecomesActive(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() {
+ // Avoid notifying observers while we're in the dtor.
+ mIsBeingDestroyed = true;
+
+ Destroy();
+
+ if (mDocumentViewer) {
+ mDocumentViewer->Close(nullptr);
+ mDocumentViewer->Destroy();
+ mDocumentViewer = 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);
+
+ if (nsCOMPtr<nsIObserverService> 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> nsDocShell::Create(
+ BrowsingContext* aBrowsingContext, uint64_t aContentWindowID) {
+ MOZ_ASSERT(aBrowsingContext, "DocShell without a BrowsingContext!");
+
+ nsresult rv;
+ RefPtr<nsDocShell> 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);
+
+ // We 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 (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<nsIInterfaceRequestor> 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<WindowContext> 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<Element> 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<nsIDocShell> 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<nsIDocShellTreeItem> 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_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<nsICommandManager*>(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(EnsureDocumentViewer())) {
+ RefPtr<Document> doc = mDocumentViewer->GetDocument();
+ doc.forget(aSink);
+ return *aSink ? NS_OK : NS_NOINTERFACE;
+ } else if (aIID.Equals(NS_GET_IID(nsIPrompt)) &&
+ NS_SUCCEEDED(EnsureScriptEnvironment())) {
+ nsresult rv;
+ nsCOMPtr<nsIWindowWatcher> 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<nsIDocShellTreeOwner> 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<nsIBrowserChild> browserChild = GetBrowserChild();
+ static_cast<BrowserChild*>(browserChild.get())
+ ->SetCancelContentJSEpoch(aEpoch);
+ return NS_OK;
+}
+
+nsresult nsDocShell::CheckDisallowedJavascriptLoad(
+ nsDocShellLoadState* aLoadState) {
+ if (!net::SchemeIsJavascript(aLoadState->URI())) {
+ return NS_OK;
+ }
+
+ if (nsCOMPtr<nsIPrincipal> targetPrincipal =
+ GetInheritedPrincipal(/* aConsiderCurrentDocument */ true)) {
+ if (!aLoadState->TriggeringPrincipal()->Subsumes(targetPrincipal)) {
+ return NS_ERROR_DOM_BAD_CROSS_ORIGIN_URI;
+ }
+ return NS_OK;
+ }
+ return NS_ERROR_DOM_BAD_CROSS_ORIGIN_URI;
+}
+
+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!");
+ // NOTE: This comparison between what appears to be internal/external load
+ // flags is intentional, as it's ensuring that the caller isn't using any of
+ // the flags reserved for implementations by the `nsIWebNavigation` interface.
+ // In the future, this check may be dropped.
+ 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;
+ }
+
+ MOZ_TRY(CheckDisallowedJavascriptLoad(aLoadState));
+
+ bool oldIsNavigating = mIsNavigating;
+ auto cleanupIsNavigating =
+ MakeScopeExit([&]() { mIsNavigating = oldIsNavigating; });
+ if (aSetNavigating) {
+ mIsNavigating = true;
+ }
+
+ PopupBlocker::PopupControlState popupState = PopupBlocker::openOverridden;
+ if (aLoadState->HasLoadFlags(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->HasLoadFlags(LOAD_FLAGS_FORCE_TRR)) {
+ defaultLoadFlags |= nsIRequest::LOAD_TRR_ONLY_MODE;
+ } else if (aLoadState->HasLoadFlags(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()) {
+ nsCOMPtr<nsISHEntry> entry = aLoadState->SHEntry();
+ return LoadHistoryEntry(entry, aLoadState->LoadType(),
+ aLoadState->HasValidUserGestureActivation());
+ }
+
+ // FIXME Null check aLoadState->GetLoadingSessionHistoryInfo()?
+ return LoadHistoryEntry(*aLoadState->GetLoadingSessionHistoryInfo(),
+ aLoadState->LoadType(),
+ aLoadState->HasValidUserGestureActivation());
+ }
+
+ // 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");
+
+ // If we have a system triggering principal, we can assume that this load was
+ // triggered by some UI in the browser chrome, such as the URL bar or
+ // bookmark bar. This should count as a user interaction for the current sh
+ // entry, so that the user may navigate back to the current entry, from the
+ // entry that is going to be added as part of this load.
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal =
+ aLoadState->TriggeringPrincipal();
+ if (triggeringPrincipal && triggeringPrincipal->IsSystemPrincipal()) {
+ if (mozilla::SessionHistoryInParent()) {
+ WindowContext* topWc = mBrowsingContext->GetTopWindowContext();
+ if (topWc && !topWc->IsDiscarded()) {
+ MOZ_ALWAYS_SUCCEEDS(topWc->SetSHEntryHasUserInteraction(true));
+ }
+ } else {
+ bool oshe = false;
+ nsCOMPtr<nsISHEntry> currentSHEntry;
+ GetCurrentSHEntry(getter_AddRefs(currentSHEntry), &oshe);
+ if (currentSHEntry) {
+ currentSHEntry->SetHasUserInteraction(true);
+ }
+ }
+ }
+
+ 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;
+}
+
+// StopDetector is modeled similarly to OnloadBlocker; it is a rather
+// dummy nsIRequest implementation which can be added to an nsILoadGroup to
+// detect Cancel calls.
+class StopDetector final : public nsIRequest {
+ public:
+ StopDetector() = default;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUEST
+
+ bool Canceled() { return mCanceled; }
+
+ private:
+ ~StopDetector() = default;
+
+ bool mCanceled = false;
+};
+
+NS_IMPL_ISUPPORTS(StopDetector, nsIRequest)
+
+NS_IMETHODIMP
+StopDetector::GetName(nsACString& aResult) {
+ aResult.AssignLiteral("about:stop-detector");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StopDetector::IsPending(bool* aRetVal) {
+ *aRetVal = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StopDetector::GetStatus(nsresult* aStatus) {
+ *aStatus = NS_OK;
+ return NS_OK;
+}
+
+NS_IMETHODIMP StopDetector::SetCanceledReason(const nsACString& aReason) {
+ return SetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP StopDetector::GetCanceledReason(nsACString& aReason) {
+ return GetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP StopDetector::CancelWithReason(nsresult aStatus,
+ const nsACString& aReason) {
+ return CancelWithReasonImpl(aStatus, aReason);
+}
+
+NS_IMETHODIMP
+StopDetector::Cancel(nsresult aStatus) {
+ mCanceled = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StopDetector::Suspend(void) { return NS_OK; }
+NS_IMETHODIMP
+StopDetector::Resume(void) { return NS_OK; }
+
+NS_IMETHODIMP
+StopDetector::GetLoadGroup(nsILoadGroup** aLoadGroup) {
+ *aLoadGroup = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StopDetector::SetLoadGroup(nsILoadGroup* aLoadGroup) { return NS_OK; }
+
+NS_IMETHODIMP
+StopDetector::GetLoadFlags(nsLoadFlags* aLoadFlags) {
+ *aLoadFlags = nsIRequest::LOAD_NORMAL;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StopDetector::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
+ return GetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+StopDetector::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
+ return SetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+StopDetector::SetLoadFlags(nsLoadFlags aLoadFlags) { return NS_OK; }
+
+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<nsIDocShellTreeItem> parentAsItem;
+ GetInProcessSameTypeParent(getter_AddRefs(parentAsItem));
+ nsCOMPtr<nsIDocShell> parentDS(do_QueryInterface(parentAsItem));
+
+ if (!parentDS || parentDS == static_cast<nsIDocShell*>(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!
+ if (aLoadState->IsFormSubmission()) {
+#ifdef DEBUG
+ if (!mEODForCurrentDocument) {
+ const MaybeDiscarded<BrowsingContext>& targetBC =
+ aLoadState->TargetBrowsingContext();
+ MOZ_ASSERT_IF(GetBrowsingContext() == targetBC.get(),
+ aLoadState->LoadType() == LOAD_NORMAL_REPLACE);
+ }
+#endif
+ } else {
+ 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();
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ GetLoadGroup(getter_AddRefs(loadGroup));
+ if (contentChild && loadGroup && !mCheckingSessionHistory) {
+ RefPtr<Document> parentDoc = parentDS->GetDocument();
+ parentDoc->BlockOnload();
+ RefPtr<BrowsingContext> browsingContext = mBrowsingContext;
+ Maybe<uint64_t> currentLoadIdentifier =
+ mBrowsingContext->GetCurrentLoadIdentifier();
+ RefPtr<nsDocShellLoadState> loadState = aLoadState;
+ bool isNavigating = mIsNavigating;
+ RefPtr<StopDetector> stopDetector = new StopDetector();
+ loadGroup->AddRequest(stopDetector, nullptr);
+ // Need to set mCheckingSessionHistory so that
+ // GetIsAttemptingToNavigate() returns true.
+ mCheckingSessionHistory = true;
+
+ auto resolve =
+ [currentLoadIdentifier, browsingContext, parentDoc, loadState,
+ isNavigating, loadGroup, stopDetector](
+ mozilla::Maybe<LoadingSessionHistoryInfo>&& aResult) {
+ RefPtr<nsDocShell> docShell =
+ static_cast<nsDocShell*>(browsingContext->GetDocShell());
+ auto unblockParent = MakeScopeExit(
+ [loadGroup, stopDetector, parentDoc, docShell]() {
+ if (docShell) {
+ docShell->mCheckingSessionHistory = false;
+ }
+ loadGroup->RemoveRequest(stopDetector, nullptr, NS_OK);
+ parentDoc->UnblockOnload(false);
+ });
+
+ if (!docShell || !docShell->mCheckingSessionHistory) {
+ return;
+ }
+
+ if (stopDetector->Canceled()) {
+ return;
+ }
+ if (currentLoadIdentifier ==
+ browsingContext->GetCurrentLoadIdentifier() &&
+ aResult.isSome()) {
+ loadState->SetLoadingSessionHistoryInfo(aResult.value());
+ // This is an initial subframe load from the session
+ // history, index doesn't need to be updated.
+ loadState->SetLoadIsFromSessionHistory(0, false);
+ }
+
+ // We got the results back from the parent process, call
+ // LoadURI again with the possibly updated data.
+ docShell->LoadURI(loadState, isNavigating, true);
+ };
+ auto reject = [loadGroup, stopDetector, browsingContext,
+ parentDoc](mozilla::ipc::ResponseRejectReason) {
+ RefPtr<nsDocShell> docShell =
+ static_cast<nsDocShell*>(browsingContext->GetDocShell());
+ if (docShell) {
+ docShell->mCheckingSessionHistory = false;
+ }
+ // In practise reject shouldn't be called ever.
+ loadGroup->RemoveRequest(stopDetector, nullptr, NS_OK);
+ parentDoc->UnblockOnload(false);
+ };
+ contentChild->SendGetLoadingSessionHistoryInfoFromParent(
+ mBrowsingContext, std::move(resolve), std::move(reject));
+ return true;
+ }
+ } else {
+ Maybe<LoadingSessionHistoryInfo> info;
+ mBrowsingContext->Canonical()->GetLoadingSessionHistoryInfoFromParent(
+ info);
+ if (info.isSome()) {
+ aLoadState->SetLoadingSessionHistoryInfo(info.value());
+ // This is an initial subframe load from the session
+ // history, index doesn't need to be updated.
+ aLoadState->SetLoadIsFromSessionHistory(0, false);
+ }
+ }
+ }
+ } else {
+ // Get the ShEntry for the child from the parent
+ nsCOMPtr<nsISHEntry> 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<nsISHEntry> shEntry;
+ currentSH->GetChildSHEntryIfHasNoDynamicallyAddedChild(
+ mBrowsingContext->ChildOffset(), 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<nsISHEntry> 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)) {
+ // 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 (mDocumentViewer && !mFiredUnloadEvent) {
+ // Keep an explicit reference since calling PageHide could release
+ // mDocumentViewer
+ nsCOMPtr<nsIDocumentViewer> viewer(mDocumentViewer);
+ mFiredUnloadEvent = true;
+
+ if (mTiming) {
+ mTiming->NotifyUnloadEventStart();
+ }
+
+ viewer->PageHide(aIsUnload);
+
+ if (mTiming) {
+ mTiming->NotifyUnloadEventEnd();
+ }
+
+ AutoTArray<nsCOMPtr<nsIDocShell>, 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<nsDocShell> child = static_cast<nsDocShell*>(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<ChildSHistory> 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();
+ }
+}
+
+void nsDocShell::ThawFreezeNonRecursive(bool aThaw) {
+ MOZ_ASSERT(mozilla::BFCacheInParent());
+
+ if (!mScriptGlobal) {
+ return;
+ }
+
+ if (RefPtr<nsGlobalWindowInner> inner =
+ nsGlobalWindowInner::Cast(mScriptGlobal->GetCurrentInnerWindow())) {
+ if (aThaw) {
+ inner->Thaw(false);
+ } else {
+ inner->Freeze(false);
+ }
+ }
+}
+
+void nsDocShell::FirePageHideShowNonRecursive(bool aShow) {
+ MOZ_ASSERT(mozilla::BFCacheInParent());
+
+ if (!mDocumentViewer) {
+ return;
+ }
+
+ // Emulate what non-SHIP BFCache does too. In pageshow case
+ // add and remove a request and before that call SetCurrentURI to get
+ // the location change notification.
+ // For pagehide, set mFiredUnloadEvent to true, so that unload doesn't fire.
+ nsCOMPtr<nsIDocumentViewer> viewer(mDocumentViewer);
+ if (aShow) {
+ viewer->SetIsHidden(false);
+ mRefreshURIList = std::move(mBFCachedRefreshURIList);
+ RefreshURIFromQueue();
+ mFiredUnloadEvent = false;
+ RefPtr<Document> doc = viewer->GetDocument();
+ if (doc) {
+ doc->NotifyActivityChanged();
+ nsCOMPtr<nsPIDOMWindowInner> inner =
+ mScriptGlobal ? mScriptGlobal->GetCurrentInnerWindow() : nullptr;
+ if (mBrowsingContext->IsTop()) {
+ doc->NotifyPossibleTitleChange(false);
+ doc->SetLoadingOrRestoredFromBFCacheTimeStampToNow();
+ if (inner) {
+ // 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.
+ // Traditionally this type change has been done to the top level page
+ // only.
+ Performance* performance = inner->GetPerformance();
+ if (performance) {
+ performance->GetDOMTiming()->NotifyRestoreStart();
+ }
+ }
+ }
+
+ nsCOMPtr<nsIChannel> channel = doc->GetChannel();
+ if (channel) {
+ SetLoadType(LOAD_HISTORY);
+ mEODForCurrentDocument = false;
+ mIsRestoringDocument = true;
+ mLoadGroup->AddRequest(channel, nullptr);
+ SetCurrentURI(doc->GetDocumentURI(), channel,
+ /* aFireOnLocationChange */ true,
+ /* aIsInitialAboutBlank */ false,
+ /* aLocationFlags */ 0);
+ mLoadGroup->RemoveRequest(channel, nullptr, NS_OK);
+ mIsRestoringDocument = false;
+ }
+ RefPtr<PresShell> presShell = GetPresShell();
+ if (presShell) {
+ presShell->Thaw(false);
+ }
+
+ if (inner) {
+ inner->FireDelayedDOMEvents(false);
+ }
+ }
+ } else if (!mFiredUnloadEvent) {
+ // XXXBFCache check again that the page can enter bfcache.
+ // XXXBFCache should mTiming->NotifyUnloadEventStart()/End() be called here?
+
+ if (mRefreshURIList) {
+ RefreshURIToQueue();
+ mBFCachedRefreshURIList = std::move(mRefreshURIList);
+ } else {
+ // If Stop was called, the list was moved to mSavedRefreshURIList after
+ // calling SuspendRefreshURIs, which calls RefreshURIToQueue.
+ mBFCachedRefreshURIList = std::move(mSavedRefreshURIList);
+ }
+
+ mFiredUnloadEvent = true;
+ viewer->PageHide(false);
+
+ RefPtr<PresShell> presShell = GetPresShell();
+ if (presShell) {
+ presShell->Freeze(false);
+ }
+ }
+}
+
+nsresult nsDocShell::Dispatch(already_AddRefed<nsIRunnable>&& aRunnable) {
+ nsCOMPtr<nsIRunnable> runnable(aRunnable);
+ if (NS_WARN_IF(!GetWindow())) {
+ // Window should only be unavailable after destroyed.
+ MOZ_ASSERT(mIsBeingDestroyed);
+ return NS_ERROR_FAILURE;
+ }
+ return SchedulerGroup::Dispatch(runnable.forget());
+}
+
+NS_IMETHODIMP
+nsDocShell::DispatchLocationChangeEvent() {
+ return Dispatch(NewRunnableMethod("nsDocShell::FireDummyOnLocationChange",
+ this,
+ &nsDocShell::FireDummyOnLocationChange));
+}
+
+NS_IMETHODIMP
+nsDocShell::StartDelayedAutoplayMediaComponents() {
+ RefPtr<nsPIDOMWindowOuter> outerWindow = GetWindow();
+ if (outerWindow) {
+ outerWindow->ActivateMediaComponents();
+ }
+ 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;
+}
+
+nsPresContext* nsDocShell::GetEldestPresContext() {
+ nsIDocumentViewer* viewer = mDocumentViewer;
+ while (viewer) {
+ nsIDocumentViewer* prevViewer = viewer->GetPreviousViewer();
+ if (!prevViewer) {
+ return viewer->GetPresContext();
+ }
+ viewer = prevViewer;
+ }
+
+ return nullptr;
+}
+
+nsPresContext* nsDocShell::GetPresContext() {
+ if (!mDocumentViewer) {
+ return nullptr;
+ }
+
+ return mDocumentViewer->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::GetDocViewer(nsIDocumentViewer** aDocumentViewer) {
+ NS_ENSURE_ARG_POINTER(aDocumentViewer);
+
+ *aDocumentViewer = mDocumentViewer;
+ NS_IF_ADDREF(*aDocumentViewer);
+ 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<EventTarget> handler = mChromeEventHandler;
+ handler.forget(aChromeEventHandler);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetCurrentURIForSessionStore(nsIURI* aURI) {
+ // Note that securityUI will set STATE_IS_INSECURE, even if
+ // the scheme of |aURI| is "https".
+ SetCurrentURI(aURI, nullptr,
+ /* aFireOnLocationChange */
+ true,
+ /* aIsInitialAboutBlank */
+ false,
+ /* aLocationFlags */
+ nsIWebProgressListener::LOCATION_CHANGE_SESSION_STORE);
+ return NS_OK;
+}
+
+bool nsDocShell::SetCurrentURI(nsIURI* aURI, nsIRequest* aRequest,
+ bool aFireOnLocationChange,
+ bool aIsInitialAboutBlank,
+ 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;
+ }
+
+ SetCurrentURIInternal(aURI);
+
+#ifdef DEBUG
+ mLastOpenedURI = aURI;
+#endif
+
+ if (!NS_IsAboutBlank(mCurrentURI)) {
+ mHasLoadedNonBlankURI = true;
+ }
+
+ // Don't fire onLocationChange when creating a subframe's initial about:blank
+ // document, as this can happen when it's not safe for us to run script.
+ if (aIsInitialAboutBlank && !mHasLoadedNonBlankURI &&
+ !mBrowsingContext->IsTop()) {
+ MOZ_ASSERT(!aRequest && aLocationFlags == 0);
+ return false;
+ }
+
+ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
+
+ if (aFireOnLocationChange) {
+ FireOnLocationChange(this, aRequest, aURI, aLocationFlags);
+ }
+ return !aFireOnLocationChange;
+}
+
+void nsDocShell::SetCurrentURIInternal(nsIURI* aURI) {
+ mCurrentURI = aURI;
+ if (mBrowsingContext) {
+ mBrowsingContext->ClearCachedValuesOfLocations();
+ }
+}
+
+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::ForceEncodingDetection() {
+ nsCOMPtr<nsIDocumentViewer> viewer;
+ GetDocViewer(getter_AddRefs(viewer));
+ if (!viewer) {
+ return NS_OK;
+ }
+
+ Document* doc = viewer->GetDocument();
+ if (!doc || doc->WillIgnoreCharsetOverride()) {
+ return NS_OK;
+ }
+
+ mForcedAutodetection = true;
+
+ nsIURI* url = doc->GetOriginalURI();
+ bool isFileURL = url && SchemeIsFile(url);
+
+ int32_t charsetSource = doc->GetDocumentCharacterSetSource();
+ auto encoding = doc->GetDocumentCharacterSet();
+ // AsHTMLDocument is valid, because we called
+ // WillIgnoreCharsetOverride() above.
+ if (doc->AsHTMLDocument()->IsPlainText()) {
+ switch (charsetSource) {
+ case kCharsetFromInitialAutoDetectionASCII:
+ // Deliberately no final version
+ LOGCHARSETMENU(("TEXT:UnlabeledAscii"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_TEXT::UnlabeledAscii);
+ break;
+ case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8Generic:
+ case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8Generic:
+ case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8GenericInitialWasASCII:
+ case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8Content:
+ case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8Content:
+ case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8ContentInitialWasASCII:
+ LOGCHARSETMENU(("TEXT:UnlabeledNonUtf8"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_TEXT::
+ UnlabeledNonUtf8);
+ break;
+ case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD:
+ case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD:
+ case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8DependedOnTLDInitialWasASCII:
+ LOGCHARSETMENU(("TEXT:UnlabeledNonUtf8TLD"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_TEXT::
+ UnlabeledNonUtf8TLD);
+ break;
+ case kCharsetFromInitialAutoDetectionWouldHaveBeenUTF8:
+ case kCharsetFromFinalAutoDetectionWouldHaveBeenUTF8InitialWasASCII:
+ LOGCHARSETMENU(("TEXT:UnlabeledUtf8"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_TEXT::UnlabeledUtf8);
+ break;
+ case kCharsetFromChannel:
+ if (encoding == UTF_8_ENCODING) {
+ LOGCHARSETMENU(("TEXT:ChannelUtf8"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_TEXT::ChannelUtf8);
+ } else {
+ LOGCHARSETMENU(("TEXT:ChannelNonUtf8"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_TEXT::
+ ChannelNonUtf8);
+ }
+ break;
+ default:
+ LOGCHARSETMENU(("TEXT:Bug"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_TEXT::Bug);
+ break;
+ }
+ } else {
+ switch (charsetSource) {
+ case kCharsetFromInitialAutoDetectionASCII:
+ // Deliberately no final version
+ LOGCHARSETMENU(("HTML:UnlabeledAscii"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML::UnlabeledAscii);
+ break;
+ case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8Generic:
+ case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8Generic:
+ case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8GenericInitialWasASCII:
+ case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8Content:
+ case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8Content:
+ case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8ContentInitialWasASCII:
+ LOGCHARSETMENU(("HTML:UnlabeledNonUtf8"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML::
+ UnlabeledNonUtf8);
+ break;
+ case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD:
+ case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD:
+ case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8DependedOnTLDInitialWasASCII:
+ LOGCHARSETMENU(("HTML:UnlabeledNonUtf8TLD"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML::
+ UnlabeledNonUtf8TLD);
+ break;
+ case kCharsetFromInitialAutoDetectionWouldHaveBeenUTF8:
+ case kCharsetFromFinalAutoDetectionWouldHaveBeenUTF8InitialWasASCII:
+ LOGCHARSETMENU(("HTML:UnlabeledUtf8"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML::UnlabeledUtf8);
+ break;
+ case kCharsetFromChannel:
+ if (encoding == UTF_8_ENCODING) {
+ LOGCHARSETMENU(("HTML:ChannelUtf8"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML::ChannelUtf8);
+ } else {
+ LOGCHARSETMENU(("HTML:ChannelNonUtf8"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML::
+ ChannelNonUtf8);
+ }
+ break;
+ case kCharsetFromXmlDeclaration:
+ case kCharsetFromMetaTag:
+ if (isFileURL) {
+ LOGCHARSETMENU(("HTML:LocalLabeled"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML::LocalLabeled);
+ } else if (encoding == UTF_8_ENCODING) {
+ LOGCHARSETMENU(("HTML:MetaUtf8"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML::InternalUtf8);
+ } else {
+ LOGCHARSETMENU(("HTML:MetaNonUtf8"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML::
+ InternalNonUtf8);
+ }
+ break;
+ default:
+ LOGCHARSETMENU(("HTML:Bug"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML::Bug);
+ break;
+ }
+ }
+ 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<Document> doc(GetDocument());
+ RefPtr<Promise> 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<Document::GetContentBlockingEventsPromise> 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::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::GetUsePrivateBrowsing(bool* aUsePrivateBrowsing) {
+ NS_ENSURE_ARG_POINTER(aUsePrivateBrowsing);
+ return mBrowsingContext->GetUsePrivateBrowsing(aUsePrivateBrowsing);
+}
+
+void nsDocShell::NotifyPrivateBrowsingChanged() {
+ MOZ_ASSERT(!mIsBeingDestroyed);
+
+ nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mPrivacyObservers);
+ while (iter.HasMore()) {
+ nsWeakPtr ref = iter.GetNext();
+ nsCOMPtr<nsIPrivacyTransitionObserver> 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::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<nsWeakPtr>::ForwardIterator iter(mReflowObservers);
+ while (iter.HasMore()) {
+ nsWeakPtr ref = iter.GetNext();
+ nsCOMPtr<nsIReflowObserver> 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 (!mDocumentViewer) {
+ return NS_OK;
+ }
+ Document* doc = mDocumentViewer->GetDocument();
+ if (!doc) {
+ return NS_OK;
+ }
+ if (doc->WillIgnoreCharsetOverride()) {
+ return NS_OK;
+ }
+
+ *aMayEnableCharacterEncodingMenu = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetAllDocShellsInSubtree(int32_t aItemType,
+ DocShellEnumeratorDirection aDirection,
+ nsTArray<RefPtr<nsIDocShell>>& 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<nsIWebBrowserChromeFocus> 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<nsILoadURIDelegate> delegate = GetLoadURIDelegate();
+ delegate.forget(aLoadURIDelegate);
+ return NS_OK;
+}
+
+already_AddRefed<nsILoadURIDelegate> nsDocShell::GetLoadURIDelegate() {
+ if (nsCOMPtr<nsILoadURIDelegate> 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<nsIDocShell> shell = do_QueryObject(child);
+ if (shell) {
+ shell->HistoryPurged(aNumEntries);
+ }
+ }
+
+ return NS_OK;
+}
+
+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<nsIDocShell> shell = do_QueryObject(child);
+ if (shell) {
+ static_cast<nsDocShell*>(shell.get())->HistoryEntryRemoved(aIndex);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsDocShell::Now(DOMHighResTimeStamp* aWhen) {
+ *aWhen = (TimeStamp::Now() - TimeStamp::ProcessCreation()).ToMilliseconds();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetWindowDraggingAllowed(bool aValue) {
+ RefPtr<nsDocShell> 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<nsDocShell> 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 (mDocumentViewer) {
+ Document* doc = mDocumentViewer->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<nsWeakPtr>::ForwardIterator iter(mScrollObservers);
+ while (iter.HasMore()) {
+ nsWeakPtr ref = iter.GetNext();
+ nsCOMPtr<nsIScrollObserver> obs = do_QueryReferent(ref);
+ if (obs) {
+ obs->AsyncPanZoomStarted();
+ } else {
+ iter.Remove();
+ }
+ }
+}
+
+void nsDocShell::NotifyAsyncPanZoomStopped() {
+ nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mScrollObservers);
+ while (iter.HasMore()) {
+ nsWeakPtr ref = iter.GetNext();
+ nsCOMPtr<nsIScrollObserver> obs = do_QueryReferent(ref);
+ if (obs) {
+ obs->AsyncPanZoomStopped();
+ } else {
+ iter.Remove();
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsDocShell::NotifyScrollObservers() {
+ nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mScrollObservers);
+ while (iter.HasMore()) {
+ nsWeakPtr ref = iter.GetNext();
+ nsCOMPtr<nsIScrollObserver> 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() {
+ nsCOMPtr<nsPIDOMWindowInner> win =
+ mScriptGlobal ? mScriptGlobal->GetCurrentInnerWindow() : nullptr;
+ if (win) {
+ Navigator* navigator = win->Navigator();
+ if (navigator) {
+ navigator->ClearPlatformCache();
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::ClearCachedUserAgent() {
+ nsCOMPtr<nsPIDOMWindowInner> win =
+ mScriptGlobal ? mScriptGlobal->GetCurrentInnerWindow() : 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> 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> nsDocShell::GetInProcessParentDocshell() {
+ nsCOMPtr<nsIDocShell> docshell = do_QueryInterface(GetAsSupports(mParent));
+ return docshell.forget().downcast<nsDocShell>();
+}
+
+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->GetCurrentInnerWindow() &&
+ mScriptGlobal->GetCurrentInnerWindow()->GetExtantDoc()) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ mScriptGlobal->GetCurrentInnerWindow()->GetClientInfo().isSome());
+ MOZ_DIAGNOSTIC_ASSERT(!mInitialClientSource);
+ return;
+ }
+
+ // Don't recreate the initial client source. We call this multiple times
+ // when DoChannelLoad() is called before CreateAboutBlankDocumentViewer.
+ 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;
+ }
+
+ // We cannot get inherited foreign partitioned principal here. Instead, we
+ // directly check which principal we want to inherit for the service worker.
+ nsIPrincipal* principal =
+ aPrincipal
+ ? aPrincipal
+ : GetInheritedPrincipal(
+ false, StoragePrincipalHelper::
+ ShouldUsePartitionPrincipalForServiceWorker(this));
+
+ // Sometimes there is no principal available when we are called from
+ // CreateAboutBlankDocumentViewer. 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<nsPIDOMWindowOuter> win = GetWindow();
+ if (!win) {
+ return;
+ }
+
+ mInitialClientSource = ClientManager::CreateSource(
+ ClientType::Window, GetMainThreadSerialEventTarget(), 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<nsIDocShell> parent = GetInProcessParentDocshell();
+ nsPIDOMWindowOuter* parentOuter = parent ? parent->GetWindow() : nullptr;
+ nsPIDOMWindowInner* parentInner =
+ parentOuter ? parentOuter->GetCurrentInnerWindow() : nullptr;
+ if (!parentInner) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> 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<ServiceWorkerDescriptor> controller(parentInner->GetController());
+ if (controller.isNothing() ||
+ !ServiceWorkerAllowedToControlWindow(principal, uri)) {
+ return;
+ }
+
+ mInitialClientSource->InheritController(controller.ref());
+}
+
+Maybe<ClientInfo> nsDocShell::GetInitialClientInfo() const {
+ if (mInitialClientSource) {
+ Maybe<ClientInfo> result;
+ result.emplace(mInitialClientSource->Info());
+ return result;
+ }
+
+ nsPIDOMWindowInner* innerWindow =
+ mScriptGlobal ? mScriptGlobal->GetCurrentInnerWindow() : nullptr;
+ Document* doc = innerWindow ? innerWindow->GetExtantDoc() : nullptr;
+
+ if (!doc || !doc->IsInitialDocument()) {
+ return Maybe<ClientInfo>();
+ }
+
+ return innerWindow->GetClientInfo();
+}
+
+nsresult nsDocShell::SetDocLoaderParent(nsDocLoader* aParent) {
+ bool wasFrame = IsSubframe();
+
+ nsresult rv = nsDocLoader::SetDocLoaderParent(aParent);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupportsPriority> priorityGroup = do_QueryInterface(mLoadGroup);
+ if (wasFrame != IsSubframe() && 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<nsIDocShell> parentAsDocShell(do_QueryInterface(parent));
+
+ if (parentAsDocShell) {
+ 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);
+
+ // 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<nsIURIContentListener> parentURIListener(do_GetInterface(parent));
+ if (parentURIListener) {
+ mContentListener->SetParentContentListener(parentURIListener);
+ }
+
+ return NS_OK;
+}
+
+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::GetInProcessRootTreeItem(nsIDocShellTreeItem** aRootTreeItem) {
+ NS_ENSURE_ARG_POINTER(aRootTreeItem);
+
+ RefPtr<nsDocShell> root = this;
+ RefPtr<nsDocShell> 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<nsIDocShellTreeItem*>(this);
+
+ nsCOMPtr<nsIDocShellTreeItem> 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 (!IsSubframe()) {
+ nsCOMPtr<nsIWebProgress> webProgress =
+ do_QueryInterface(GetAsSupports(this));
+
+ if (webProgress) {
+ nsCOMPtr<nsIWebProgressListener> oldListener =
+ do_QueryInterface(mTreeOwner);
+ nsCOMPtr<nsIWebProgressListener> 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<nsIDocShellTreeItem> 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<nsIBrowserChild> newBrowserChild = do_GetInterface(mTreeOwner);
+ MOZ_ASSERT(newBrowserChild,
+ "No BrowserChild actor for tree owner in Content!");
+
+ if (mBrowserChild) {
+ nsCOMPtr<nsIBrowserChild> oldBrowserChild =
+ do_QueryReferent(mBrowserChild);
+ MOZ_RELEASE_ASSERT(
+ oldBrowserChild == newBrowserChild,
+ "Cannot change BrowserChild during nsDocShell lifetime!");
+ } else {
+ mBrowserChild = do_GetWeakReference(newBrowserChild);
+ }
+ }
+
+ return NS_OK;
+}
+
+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<nsDocLoader> 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");
+
+ /* 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(aChild->GetBrowsingContext()->GetUseGlobalHistory());
+ }
+
+ if (aChild->ItemType() != mItemType) {
+ return NS_OK;
+ }
+
+ aChild->SetTreeOwner(mTreeOwner);
+
+ nsCOMPtr<nsIDocShell> 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 (!mDocumentViewer) {
+ return NS_OK;
+ }
+ Document* doc = mDocumentViewer->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<nsDocLoader> 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<nsDocShell> 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<uint32_t>(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<nsDocShell*>(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<ChildSHistory> 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<ChildSHistory> 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<nsIDocShell> 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(EnsureDocumentViewer(), nullptr);
+ return mDocumentViewer->GetDocument();
+}
+
+Document* nsDocShell::GetExtantDocument() {
+ return mDocumentViewer ? mDocumentViewer->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<nsGlobalWindowOuter> window = mScriptGlobal;
+ window.forget(aWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetMessageManager(ContentFrameMessageManager** aMessageManager) {
+ RefPtr<ContentFrameMessageManager> mm;
+ if (RefPtr<BrowserChild> 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;
+}
+
+void nsDocShell::ClearFrameHistory(nsISHEntry* aEntry) {
+ MOZ_ASSERT(!mozilla::SessionHistoryInParent());
+ RefPtr<ChildSHistory> 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 (!mDocumentViewer) {
+ return true;
+ }
+ bool firingBeforeUnload;
+ mDocumentViewer->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<ChildSHistory> 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<ChildSHistory> 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, bool aUserActivation) {
+ if (!IsNavigationAllowed()) {
+ return NS_OK; // JS may not handle returning of an error code
+ }
+
+ auto cleanupIsNavigating = MakeScopeExit([&]() { mIsNavigating = false; });
+ mIsNavigating = true;
+
+ RefPtr<ChildSHistory> rootSH = GetRootSessionHistory();
+ NS_ENSURE_TRUE(rootSH, NS_ERROR_FAILURE);
+ ErrorResult rv;
+ rootSH->Go(-1, aRequireUserInteraction, aUserActivation, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+nsDocShell::GoForward(bool aRequireUserInteraction, bool aUserActivation) {
+ if (!IsNavigationAllowed()) {
+ return NS_OK; // JS may not handle returning of an error code
+ }
+
+ auto cleanupIsNavigating = MakeScopeExit([&]() { mIsNavigating = false; });
+ mIsNavigating = true;
+
+ RefPtr<ChildSHistory> rootSH = GetRootSessionHistory();
+ NS_ENSURE_TRUE(rootSH, NS_ERROR_FAILURE);
+ ErrorResult rv;
+ rootSH->Go(1, aRequireUserInteraction, aUserActivation, 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, bool aUserActivation) {
+ if (!IsNavigationAllowed()) {
+ return NS_OK; // JS may not handle returning of an error code
+ }
+
+ auto cleanupIsNavigating = MakeScopeExit([&]() { mIsNavigating = false; });
+ mIsNavigating = true;
+
+ RefPtr<ChildSHistory> rootSH = GetRootSessionHistory();
+ NS_ENSURE_TRUE(rootSH, NS_ERROR_FAILURE);
+
+ ErrorResult rv;
+ rootSH->GotoIndex(aIndex, aIndex - rootSH->Index(), false, aUserActivation,
+ rv);
+ return rv.StealNSResult();
+}
+
+nsresult nsDocShell::LoadURI(nsIURI* aURI,
+ const LoadURIOptions& aLoadURIOptions) {
+ if (!IsNavigationAllowed()) {
+ return NS_OK; // JS may not handle returning of an error code
+ }
+ RefPtr<nsDocShellLoadState> loadState;
+ nsresult rv = nsDocShellLoadState::CreateFromLoadURIOptions(
+ mBrowsingContext, aURI, aLoadURIOptions, getter_AddRefs(loadState));
+ MOZ_ASSERT(rv != NS_ERROR_MALFORMED_URI);
+ if (NS_FAILED(rv) || !loadState) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return LoadURI(loadState, true);
+}
+
+NS_IMETHODIMP
+nsDocShell::LoadURIFromScript(nsIURI* aURI,
+ JS::Handle<JS::Value> 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);
+}
+
+nsresult nsDocShell::FixupAndLoadURIString(
+ const nsAString& aURIString, const LoadURIOptions& aLoadURIOptions) {
+ if (!IsNavigationAllowed()) {
+ return NS_OK; // JS may not handle returning of an error code
+ }
+
+ RefPtr<nsDocShellLoadState> loadState;
+ nsresult rv = nsDocShellLoadState::CreateFromLoadURIOptions(
+ mBrowsingContext, aURIString, aLoadURIOptions, getter_AddRefs(loadState));
+
+ uint32_t loadFlags = aLoadURIOptions.mLoadFlags;
+ if (NS_ERROR_MALFORMED_URI == rv) {
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("Creating an active entry on nsDocShell %p to %s (because "
+ "we're showing an error page)",
+ this, NS_ConvertUTF16toUTF8(aURIString).get()));
+
+ // We need to store a session history entry. We don't have a valid URI, so
+ // we use about:blank instead.
+ nsCOMPtr<nsIURI> uri;
+ MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), "about:blank"_ns));
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal;
+ if (aLoadURIOptions.mTriggeringPrincipal) {
+ triggeringPrincipal = aLoadURIOptions.mTriggeringPrincipal;
+ } else {
+ triggeringPrincipal = nsContentUtils::GetSystemPrincipal();
+ }
+ if (mozilla::SessionHistoryInParent()) {
+ mActiveEntry = MakeUnique<SessionHistoryInfo>(
+ uri, triggeringPrincipal, nullptr, nullptr, nullptr,
+ nsLiteralCString("text/html"));
+ mBrowsingContext->SetActiveSessionHistoryEntry(
+ Nothing(), mActiveEntry.get(), MAKE_LOAD_TYPE(LOAD_NORMAL, loadFlags),
+ /* aUpdatedCacheKey = */ 0);
+ }
+ if (DisplayLoadError(rv, nullptr, PromiseFlatString(aURIString).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::FixupAndLoadURIStringFromScript(
+ const nsAString& aURIString, JS::Handle<JS::Value> aLoadURIOptions,
+ JSContext* aCx) {
+ // generate dictionary for aLoadURIOptions and forward call
+ LoadURIOptions loadURIOptions;
+ if (!loadURIOptions.Init(aCx, aLoadURIOptions)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ return FixupAndLoadURIString(aURIString, 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> element = mBrowsingContext->GetEmbedderElement();
+ if (element) {
+ if (aFireFrameErrorEvent) {
+ if (RefPtr<nsFrameLoaderOwner> flo = do_QueryObject(element)) {
+ if (RefPtr<nsFrameLoader> 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 = BrowserChild::GetFrom(this);
+ if (browserChild &&
+ !mBrowsingContext->GetParentWindowContext()->IsInProcess()) {
+ 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<nsIPrompt> prompter;
+ nsCOMPtr<nsIStringBundle> 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<nsString, 3> formatStrs;
+ bool addHostPort = false;
+ bool isBadStsCertError = 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<nsINestedURI> nestedURI = do_QueryInterface(aURI);
+ while (nestedURI) {
+ nsCOMPtr<nsIURI> 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<nsIURI> 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_ERROR_NET_TIMEOUT_EXTERNAL == 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) {
+ // 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<nsINSSErrorsService> 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<nsITransportSecurityInfo> tsi;
+ if (aFailedChannel) {
+ aFailedChannel->GetSecurityInfo(getter_AddRefs(tsi));
+ }
+ 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).
+ bool isStsHost = false;
+ bool isPinnedHost = false;
+ OriginAttributes attrsForHSTS;
+ if (aFailedChannel) {
+ StoragePrincipalHelper::GetOriginAttributesForHSTS(aFailedChannel,
+ attrsForHSTS);
+ } else {
+ attrsForHSTS = GetOriginAttributes();
+ }
+
+ if (XRE_IsParentProcess()) {
+ nsCOMPtr<nsISiteSecurityService> sss =
+ do_GetService(NS_SSSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = sss->IsSecureURI(aURI, attrsForHSTS, &isStsHost);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ mozilla::dom::ContentChild* cc =
+ mozilla::dom::ContentChild::GetSingleton();
+ cc->SendIsSecureURI(aURI, attrsForHSTS, &isStsHost);
+ }
+ nsCOMPtr<nsIPublicKeyPinningService> pkps =
+ do_GetService(NS_PKPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = pkps->HostHasPins(aURI, &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) {
+ isBadStsCertError = true;
+ cssClass.AssignLiteral("badStsCert");
+ }
+
+ errorPage.Assign("certerror");
+ } 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<EventTarget> handler = mChromeEventHandler;
+ if (handler) {
+ nsCOMPtr<Element> 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_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:
+ case NS_ERROR_DOM_COOP_FAILED:
+ case NS_ERROR_DOM_COEP_FAILED:
+ // 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;
+ }
+ }
+
+ nsresult delegateErrorCode = aError;
+ // 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");
+ delegateErrorCode = NS_ERROR_HTTPS_ONLY;
+ } else if (isBadStsCertError) {
+ delegateErrorCode = NS_ERROR_BAD_HSTS_CERT;
+ }
+
+ if (nsCOMPtr<nsILoadURIDelegate> loadURIDelegate = GetLoadURIDelegate()) {
+ nsCOMPtr<nsIURI> errorPageURI;
+ rv = loadURIDelegate->HandleLoadError(
+ aURI, delegateErrorCode, NS_ERROR_GET_MODULE(delegateErrorCode),
+ 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(
+ IsSubframe() ? "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<nsITextToSubURI> 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) {
+ if (mIsBeingDestroyed) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+#if defined(DEBUG)
+ if (MOZ_LOG_TEST(gDocShellLog, LogLevel::Debug)) {
+ nsAutoCString chanName;
+ if (aFailedChannel) {
+ aFailedChannel->GetName(chanName);
+ } else {
+ chanName.AssignLiteral("<no channel>");
+ }
+
+ 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<nsICaptivePortalService> cps = do_GetService(NS_CAPTIVEPORTAL_CID);
+ int32_t cpsState;
+ if (cps && NS_SUCCEEDED(cps->GetState(&cpsState)) &&
+ cpsState == nsICaptivePortalService::LOCKED_PORTAL) {
+ errorPageUrl.AppendLiteral("&captive=true");
+ }
+
+ errorPageUrl.AppendLiteral("&d=");
+ errorPageUrl.AppendASCII(escapedDescription.get());
+
+ nsCOMPtr<nsIURI> 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<nsDocShellLoadState> loadState = new nsDocShellLoadState(aErrorURI);
+ loadState->SetTriggeringPrincipal(nsContentUtils::GetSystemPrincipal());
+ if (mBrowsingContext) {
+ loadState->SetTriggeringSandboxFlags(mBrowsingContext->GetSandboxFlags());
+ loadState->SetTriggeringWindowId(
+ mBrowsingContext->GetCurrentInnerWindowId());
+ nsPIDOMWindowInner* innerWin = mScriptGlobal->GetCurrentInnerWindow();
+ if (innerWin) {
+ loadState->SetTriggeringStorageAccess(innerWin->UsingStorageAccess());
+ }
+ }
+ 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<LoadingSessionHistoryInfo>(*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<ChildSHistory> rootSH = GetRootSessionHistory();
+ if (mozilla::SessionHistoryInParent()) {
+ MOZ_LOG(gSHLog, LogLevel::Debug, ("nsDocShell %p Reload", this));
+ bool forceReload = IsForceReloadType(loadType);
+ if (!XRE_IsParentProcess()) {
+ ++mPendingReloadCount;
+ RefPtr<nsDocShell> docShell(this);
+ nsCOMPtr<nsIDocumentViewer> viewer(mDocumentViewer);
+ NS_ENSURE_STATE(viewer);
+
+ bool okToUnload = true;
+ MOZ_TRY(viewer->PermitUnload(&okToUnload));
+ if (!okToUnload) {
+ return NS_OK;
+ }
+
+ RefPtr<Document> doc(GetDocument());
+ RefPtr<BrowsingContext> browsingContext(mBrowsingContext);
+ nsCOMPtr<nsIURI> currentURI(mCurrentURI);
+ nsCOMPtr<nsIReferrerInfo> referrerInfo(mReferrerInfo);
+ RefPtr<StopDetector> stopDetector = new StopDetector();
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ GetLoadGroup(getter_AddRefs(loadGroup));
+ if (loadGroup) {
+ // loadGroup may be null in theory. In that case stopDetector just
+ // doesn't do anything.
+ loadGroup->AddRequest(stopDetector, nullptr);
+ }
+
+ ContentChild::GetSingleton()->SendNotifyOnHistoryReload(
+ mBrowsingContext, forceReload,
+ [docShell, doc, loadType, browsingContext, currentURI, referrerInfo,
+ loadGroup, stopDetector](
+ std::tuple<bool, Maybe<NotNull<RefPtr<nsDocShellLoadState>>>,
+ Maybe<bool>>&& aResult) {
+ auto scopeExit = MakeScopeExit([loadGroup, stopDetector]() {
+ if (loadGroup) {
+ loadGroup->RemoveRequest(stopDetector, nullptr, NS_OK);
+ }
+ });
+
+ // Decrease mPendingReloadCount before any other early returns!
+ if (--(docShell->mPendingReloadCount) > 0) {
+ return;
+ }
+
+ if (stopDetector->Canceled()) {
+ return;
+ }
+ bool canReload;
+ Maybe<NotNull<RefPtr<nsDocShellLoadState>>> loadState;
+ Maybe<bool> reloadingActiveEntry;
+
+ std::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<NotNull<RefPtr<nsDocShellLoadState>>> loadState;
+ Maybe<bool> 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));
+ RefPtr<Document> doc = GetDocument();
+ RefPtr<BrowsingContext> bc = mBrowsingContext;
+ nsCOMPtr<nsIURI> currentURI = mCurrentURI;
+ nsCOMPtr<nsIReferrerInfo> referrerInfo = mReferrerInfo;
+ ReloadDocument(this, doc, loadType, bc, currentURI, referrerInfo);
+ }
+ }
+ }
+ 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) {
+ nsCOMPtr<nsISHEntry> oshe = mOSHE;
+ return LoadHistoryEntry(
+ oshe, loadType,
+ aReloadFlags & nsIWebNavigation::LOAD_FLAGS_USER_ACTIVATION);
+ }
+
+ if (mLSHE) { // In case a reload happened before the current load is done
+ nsCOMPtr<nsISHEntry> lshe = mLSHE;
+ return LoadHistoryEntry(
+ lshe, loadType,
+ aReloadFlags & nsIWebNavigation::LOAD_FLAGS_USER_ACTIVATION);
+ }
+
+ RefPtr<Document> doc = GetDocument();
+ RefPtr<BrowsingContext> bc = mBrowsingContext;
+ nsCOMPtr<nsIURI> currentURI = mCurrentURI;
+ nsCOMPtr<nsIReferrerInfo> referrerInfo = mReferrerInfo;
+ return ReloadDocument(this, doc, loadType, bc, currentURI, referrerInfo);
+}
+
+/* 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<nsIURI> originalURI;
+ nsCOMPtr<nsIURI> resultPrincipalURI;
+ bool loadReplace = false;
+
+ nsIPrincipal* triggeringPrincipal = aDocument->NodePrincipal();
+ nsCOMPtr<nsIContentSecurityPolicy> csp = aDocument->GetCsp();
+ uint32_t triggeringSandboxFlags = aDocument->GetSandboxFlags();
+ uint64_t triggeringWindowId = aDocument->InnerWindowID();
+ bool triggeringStorageAccess = aDocument->UsingStorageAccess();
+
+ nsAutoString contentTypeHint;
+ aDocument->GetContentType(contentTypeHint);
+
+ if (aDocument->IsSrcdocDocument()) {
+ aDocument->GetSrcdocData(srcdoc);
+ flags |= INTERNAL_LOAD_FLAGS_IS_SRCDOC;
+ baseURI = aDocument->GetBaseURI();
+ } else {
+ srcdoc = VoidString();
+ }
+ nsCOMPtr<nsIChannel> chan = aDocument->GetChannel();
+ if (chan) {
+ uint32_t loadFlags;
+ chan->GetLoadFlags(&loadFlags);
+ loadReplace = loadFlags & nsIChannel::LOAD_REPLACE;
+ nsCOMPtr<nsIHttpChannel> httpChan(do_QueryInterface(chan));
+ if (httpChan) {
+ httpChan->GetOriginalURI(getter_AddRefs(originalURI));
+ }
+
+ nsCOMPtr<nsILoadInfo> 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<nsIURI> currentURI = aCurrentURI;
+
+ // Reload always rewrites result principal URI.
+ Maybe<nsCOMPtr<nsIURI>> emplacedResultPrincipalURI;
+ emplacedResultPrincipalURI.emplace(std::move(resultPrincipalURI));
+
+ RefPtr<WindowContext> context = aBrowsingContext->GetCurrentWindowContext();
+ RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(currentURI);
+ loadState->SetReferrerInfo(aReferrerInfo);
+ loadState->SetOriginalURI(originalURI);
+ loadState->SetMaybeResultPrincipalURI(emplacedResultPrincipalURI);
+ loadState->SetLoadReplace(loadReplace);
+ loadState->SetTriggeringPrincipal(triggeringPrincipal);
+ loadState->SetTriggeringSandboxFlags(triggeringSandboxFlags);
+ loadState->SetTriggeringWindowId(triggeringWindowId);
+ loadState->SetTriggeringStorageAccess(triggeringStorageAccess);
+ loadState->SetPrincipalToInherit(triggeringPrincipal);
+ loadState->SetCsp(csp);
+ loadState->SetInternalLoadFlags(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<nsISHEntry*>(mLSHE));
+ }
+ mActiveEntryIsLoadingFromSessionHistory = false;
+
+ mFailedChannel = nullptr;
+ mFailedURI = nullptr;
+ }
+
+ if (nsIWebNavigation::STOP_CONTENT & aStopFlags) {
+ // Stop the document loading and animations
+ if (mDocumentViewer) {
+ nsCOMPtr<nsIDocumentViewer> viewer = mDocumentViewer;
+ viewer->Stop();
+ }
+ } else if (nsIWebNavigation::STOP_NETWORK & aStopFlags) {
+ // Stop the document loading only
+ if (mDocumentViewer) {
+ RefPtr<Document> doc = mDocumentViewer->GetDocument();
+ if (doc) {
+ doc->StopDocumentLoad();
+ }
+ }
+ }
+
+ if (nsIWebNavigation::STOP_NETWORK & aStopFlags) {
+ // Suspend any timers that were set for this loader. We'll clear
+ // them out for good in CreateDocumentViewer.
+ 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();
+
+ // Clear out mChannelToDisconnectOnPageHide. This page won't go in the
+ // BFCache now, and the Stop above will have removed the DocumentChannel
+ // from the loadgroup.
+ mChannelToDisconnectOnPageHide = 0;
+ }
+
+ for (auto* child : mChildList.ForwardRange()) {
+ nsCOMPtr<nsIWebNavigation> 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(EnsureDocumentViewer(), NS_ERROR_FAILURE);
+
+ RefPtr<Document> doc = mDocumentViewer->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<nsIURI> uri = mCurrentURI;
+ uri.forget(aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetSessionHistoryXPCOM(nsISupports** aSessionHistory) {
+ NS_ENSURE_ARG_POINTER(aSessionHistory);
+ RefPtr<ChildSHistory> 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<nsIURI> newURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(newURI), aURI);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ RefPtr<nsDocShellLoadState> 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<nsISHEntry> 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<nsISHEntry> 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<nsIInputStream> nsDocShell::GetPostDataFromCurrentEntry()
+ const {
+ nsCOMPtr<nsIInputStream> 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<uint32_t> 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<nsIObserverService> 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 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<nsIDocShellTreeItem> docShellParentAsItem =
+ do_QueryInterface(GetAsSupports(mParent));
+ if (docShellParentAsItem) {
+ docShellParentAsItem->RemoveChild(this);
+ }
+
+ if (mDocumentViewer) {
+ mDocumentViewer->Close(nullptr);
+ mDocumentViewer->Destroy();
+ mDocumentViewer = nullptr;
+ }
+
+ nsDocLoader::Destroy();
+
+ mParentWidget = nullptr;
+ SetCurrentURIInternal(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()->EvictLocalDocumentViewers();
+ }
+
+ if (mWillChangeProcess && !mBrowsingContext->IsDiscarded()) {
+ 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();
+
+ return NS_OK;
+}
+
+double nsDocShell::GetWidgetCSSToDeviceScale() {
+ if (mParentWidget) {
+ return mParentWidget->GetDefaultScale().scale;
+ }
+ if (nsCOMPtr<nsIBaseWindow> ownerWindow = do_QueryInterface(mTreeOwner)) {
+ return ownerWindow->GetWidgetCSSToDeviceScale();
+ }
+ return 1.0;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetDevicePixelsPerDesktopPixel(double* aScale) {
+ if (mParentWidget) {
+ *aScale = mParentWidget->GetDesktopToDeviceScale().scale;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIBaseWindow> 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 (mDocumentViewer) {
+ NS_ENSURE_SUCCESS(mDocumentViewer->Move(aX, aY), NS_ERROR_FAILURE);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetPositionDesktopPix(int32_t aX, int32_t aY) {
+ nsCOMPtr<nsIBaseWindow> 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 mDocumentViewer
+ nsCOMPtr<nsIDocumentViewer> viewer = mDocumentViewer;
+ if (viewer) {
+ uint32_t cvflags = (aFlags & nsIBaseWindow::eDelayResize)
+ ? nsIDocumentViewer::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<Document> 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::SetDimensions(DimensionRequest&& aRequest) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetDimensions(DimensionKind aDimensionKind, int32_t* aX,
+ int32_t* aY, int32_t* aCX, int32_t* aCY) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShell::Repaint(bool aForce) {
+ PresShell* presShell = GetPresShell();
+ NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
+
+ RefPtr<nsViewManager> 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 (!mDocumentViewer) {
+ 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() == ViewVisibility::Hide) {
+ 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<nsDocShell> docShell = this;
+ RefPtr<nsDocShell> 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;
+ if (frame && !frame->IsVisibleConsideringAncestors(
+ nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY)) {
+ return NS_OK;
+ }
+
+ docShell = parentItem;
+ parentItem = docShell->GetInProcessParentDocshell();
+ }
+
+ nsCOMPtr<nsIBaseWindow> 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.
+ nsresult rv = treeOwnerAsWin->GetVisibility(aVisibility);
+ if (rv == NS_ERROR_NOT_IMPLEMENTED) {
+ // The tree owner had no opinion on our visibility.
+ *aVisibility = true;
+ return NS_OK;
+ }
+ return rv;
+}
+
+void nsDocShell::ActivenessMaybeChanged() {
+ const bool isActive = mBrowsingContext->IsActive();
+ if (RefPtr<PresShell> presShell = GetPresShell()) {
+ presShell->ActivenessMaybeChanged();
+ }
+
+ // Tell the window about it
+ if (mScriptGlobal) {
+ mScriptGlobal->SetIsBackground(!isActive);
+ if (RefPtr<Document> 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.
+ auto orientation = mBrowsingContext->GetOrientationLock();
+ ScreenOrientation::UpdateActiveOrientationLock(orientation);
+ }
+
+ doc->PostVisibilityUpdateEvent();
+ }
+ }
+
+ // Tell the nsDOMNavigationTiming about it
+ RefPtr<nsDOMNavigationTiming> timing = mTiming;
+ if (!timing && mDocumentViewer) {
+ if (Document* doc = mDocumentViewer->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::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::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::SetVisibility(bool aVisibility) {
+ // Show()/Hide() may change mDocumentViewer.
+ nsCOMPtr<nsIDocumentViewer> viewer = mDocumentViewer;
+ if (!viewer) {
+ return NS_OK;
+ }
+ if (aVisibility) {
+ viewer->Show();
+ } else {
+ viewer->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::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<nsIBaseWindow> 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(true);
+ }
+
+ return NS_OK;
+}
+
+void nsDocShell::SetTitleOnHistoryEntry(bool aUpdateEntryInSessionHistory) {
+ if (mOSHE) {
+ mOSHE->SetTitle(mTitle);
+ }
+
+ if (mActiveEntry && mBrowsingContext) {
+ mActiveEntry->SetTitle(mTitle);
+ if (aUpdateEntryInSessionHistory) {
+ 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> 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->IsRootContentDocumentCrossProcess()) {
+ 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::FrameAncestorsAndDescendants,
+ NS_FRAME_IS_DIRTY);
+}
+
+//*****************************************************************************
+// nsDocShell::nsIRefreshURI
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsDocShell::RefreshURI(nsIURI* aURI, nsIPrincipal* aPrincipal,
+ uint32_t aDelay) {
+ 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<nsITimerCallback> refreshTimer =
+ new nsRefreshTimer(this, aURI, aPrincipal, aDelay);
+
+ 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<nsPIDOMWindowOuter> win = GetWindow();
+ NS_ENSURE_TRUE(win, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsITimer> 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,
+ uint32_t aDelay,
+ 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<nsITimer> timer = do_QueryElementAt(mRefreshURIList, i);
+ if (timer == aTimer) {
+ mRefreshURIList->RemoveElementAt(i);
+ break;
+ }
+ }
+ }
+
+ return ForceRefreshURI(aURI, aPrincipal, aDelay);
+}
+
+NS_IMETHODIMP
+nsDocShell::ForceRefreshURI(nsIURI* aURI, nsIPrincipal* aPrincipal,
+ uint32_t aDelay) {
+ NS_ENSURE_ARG(aURI);
+
+ RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(aURI);
+ loadState->SetOriginalURI(mCurrentURI);
+ loadState->SetResultPrincipalURI(aURI);
+ loadState->SetResultPrincipalURIIsSome(true);
+ loadState->SetKeepResultPrincipalURIIfSet(true);
+ loadState->SetIsMetaRefresh(true);
+
+ // Set the triggering pricipal to aPrincipal if available, or current
+ // document's principal otherwise.
+ nsCOMPtr<nsIPrincipal> principal = aPrincipal;
+ RefPtr<Document> 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->SetTriggeringWindowId(doc->InnerWindowID());
+ loadState->SetTriggeringStorageAccess(doc->UsingStorageAccess());
+ }
+
+ loadState->SetPrincipalIsExplicit(true);
+
+ /* Check if this META refresh causes a redirection
+ * to another site.
+ */
+ bool equalUri = false;
+ nsresult rv = aURI->Equals(mCurrentURI, &equalUri);
+
+ nsCOMPtr<nsIReferrerInfo> referrerInfo;
+ if (NS_SUCCEEDED(rv) && !equalUri && 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_REFRESH_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<ReferrerInfo*>(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;
+}
+
+static const char16_t* SkipASCIIWhitespace(const char16_t* aStart,
+ const char16_t* aEnd) {
+ const char16_t* iter = aStart;
+ while (iter != aEnd && mozilla::IsAsciiWhitespace(*iter)) {
+ ++iter;
+ }
+ return iter;
+}
+
+static std::tuple<const char16_t*, const char16_t*> ExtractURLString(
+ const char16_t* aPosition, const char16_t* aEnd) {
+ MOZ_ASSERT(aPosition != aEnd);
+
+ // 1. Let urlString be the substring of input from the code point at
+ // position to the end of the string.
+ const char16_t* urlStart = aPosition;
+ const char16_t* urlEnd = aEnd;
+
+ // 2. If the code point in input pointed to by position is U+0055 (U) or
+ // U+0075 (u), then advance position to the next code point.
+ // Otherwise, jump to the step labeled skip quotes.
+ if (*aPosition == 'U' || *aPosition == 'u') {
+ ++aPosition;
+
+ // 3. If the code point in input pointed to by position is U+0052 (R) or
+ // U+0072 (r), then advance position to the next code point.
+ // Otherwise, jump to the step labeled parse.
+ if (aPosition == aEnd || (*aPosition != 'R' && *aPosition != 'r')) {
+ return std::make_tuple(urlStart, urlEnd);
+ }
+
+ ++aPosition;
+
+ // 4. If the code point in input pointed to by position is U+004C (L) or
+ // U+006C (l), then advance position to the next code point.
+ // Otherwise, jump to the step labeled parse.
+ if (aPosition == aEnd || (*aPosition != 'L' && *aPosition != 'l')) {
+ return std::make_tuple(urlStart, urlEnd);
+ }
+
+ ++aPosition;
+
+ // 5. Skip ASCII whitespace within input given position.
+ aPosition = SkipASCIIWhitespace(aPosition, aEnd);
+
+ // 6. If the code point in input pointed to by position is U+003D (=),
+ // then advance position to the next code point. Otherwise, jump to
+ // the step labeled parse.
+ if (aPosition == aEnd || *aPosition != '=') {
+ return std::make_tuple(urlStart, urlEnd);
+ }
+
+ ++aPosition;
+
+ // 7. Skip ASCII whitespace within input given position.
+ aPosition = SkipASCIIWhitespace(aPosition, aEnd);
+ }
+
+ // 8. Skip quotes: If the code point in input pointed to by position is
+ // U+0027 (') or U+0022 ("), then let quote be that code point, and
+ // advance position to the next code point. Otherwise, let quote be
+ // the empty string.
+ Maybe<char> quote;
+ if (aPosition != aEnd && (*aPosition == '\'' || *aPosition == '"')) {
+ quote.emplace(*aPosition);
+ ++aPosition;
+ }
+
+ // 9. Set urlString to the substring of input from the code point at
+ // position to the end of the string.
+ urlStart = aPosition;
+ urlEnd = aEnd;
+
+ // 10. If quote is not the empty string, and there is a code point in
+ // urlString equal to quote, then truncate urlString at that code
+ // point, so that it and all subsequent code points are removed.
+ const char16_t* quotePos;
+ if (quote.isSome() &&
+ (quotePos = nsCharTraits<char16_t>::find(
+ urlStart, std::distance(urlStart, aEnd), quote.value()))) {
+ urlEnd = quotePos;
+ }
+
+ return std::make_tuple(urlStart, urlEnd);
+}
+
+void nsDocShell::SetupRefreshURIFromHeader(Document* aDocument,
+ const nsAString& aHeader) {
+ if (mIsBeingDestroyed) {
+ return;
+ }
+
+ const char16_t* position = aHeader.BeginReading();
+ const char16_t* end = aHeader.EndReading();
+
+ // See
+ // https://html.spec.whatwg.org/#pragma-directives:shared-declarative-refresh-steps.
+
+ // 3. Skip ASCII whitespace
+ position = SkipASCIIWhitespace(position, end);
+
+ // 4. Let time be 0.
+ CheckedInt<uint32_t> milliSeconds;
+
+ // 5. Collect a sequence of code points that are ASCII digits
+ const char16_t* digitsStart = position;
+ while (position != end && mozilla::IsAsciiDigit(*position)) {
+ ++position;
+ }
+
+ if (position == digitsStart) {
+ // 6. If timeString is the empty string, then:
+ // 1. If the code point in input pointed to by position is not U+002E
+ // (.), then return.
+ if (position == end || *position != '.') {
+ return;
+ }
+ } else {
+ // 7. Otherwise, set time to the result of parsing timeString using the
+ // rules for parsing non-negative integers.
+ nsContentUtils::ParseHTMLIntegerResultFlags result;
+ uint32_t seconds =
+ nsContentUtils::ParseHTMLInteger(digitsStart, position, &result);
+ MOZ_ASSERT(!(result & nsContentUtils::eParseHTMLInteger_Negative));
+ if (result & nsContentUtils::eParseHTMLInteger_Error) {
+ // The spec assumes no errors here (since we only pass ASCII digits in),
+ // but we can still overflow, so this block should deal with that (and
+ // only that).
+ MOZ_ASSERT(!(result & nsContentUtils::eParseHTMLInteger_ErrorOverflow));
+ return;
+ }
+ MOZ_ASSERT(
+ !(result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput));
+
+ milliSeconds = seconds;
+ milliSeconds *= 1000;
+ if (!milliSeconds.isValid()) {
+ return;
+ }
+ }
+
+ // 8. Collect a sequence of code points that are ASCII digits and U+002E FULL
+ // STOP characters (.) from input given position. Ignore any collected
+ // characters.
+ while (position != end &&
+ (mozilla::IsAsciiDigit(*position) || *position == '.')) {
+ ++position;
+ }
+
+ // 9. Let urlRecord be document's URL.
+ nsCOMPtr<nsIURI> urlRecord(aDocument->GetDocumentURI());
+
+ // 10. If position is not past the end of input
+ if (position != end) {
+ // 1. If the code point in input pointed to by position is not U+003B (;),
+ // U+002C (,), or ASCII whitespace, then return.
+ if (*position != ';' && *position != ',' &&
+ !mozilla::IsAsciiWhitespace(*position)) {
+ return;
+ }
+
+ // 2. Skip ASCII whitespace within input given position.
+ position = SkipASCIIWhitespace(position, end);
+
+ // 3. If the code point in input pointed to by position is U+003B (;) or
+ // U+002C (,), then advance position to the next code point.
+ if (position != end && (*position == ';' || *position == ',')) {
+ ++position;
+
+ // 4. Skip ASCII whitespace within input given position.
+ position = SkipASCIIWhitespace(position, end);
+ }
+
+ // 11. If position is not past the end of input, then:
+ if (position != end) {
+ const char16_t* urlStart;
+ const char16_t* urlEnd;
+
+ // 1-10. See ExtractURLString.
+ std::tie(urlStart, urlEnd) = ExtractURLString(position, end);
+
+ // 11. Parse: Parse urlString relative to document. If that fails, return.
+ // Otherwise, set urlRecord to the resulting URL record.
+ nsresult rv =
+ NS_NewURI(getter_AddRefs(urlRecord),
+ Substring(urlStart, std::distance(urlStart, urlEnd)),
+ /* charset = */ nullptr, aDocument->GetDocBaseURI());
+ NS_ENSURE_SUCCESS_VOID(rv);
+ }
+ }
+
+ nsIPrincipal* principal = aDocument->NodePrincipal();
+ nsCOMPtr<nsIScriptSecurityManager> securityManager =
+ nsContentUtils::GetSecurityManager();
+ nsresult rv = securityManager->CheckLoadURIWithPrincipal(
+ principal, urlRecord,
+ nsIScriptSecurityManager::LOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENT,
+ aDocument->InnerWindowID());
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ bool isjs = true;
+ rv = NS_URIChainHasFlags(
+ urlRecord, nsIProtocolHandler::URI_OPENING_EXECUTES_SCRIPT, &isjs);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ if (isjs) {
+ return;
+ }
+
+ RefreshURI(urlRecord, principal, milliSeconds.value());
+}
+
+static void DoCancelRefreshURITimers(nsIMutableArray* aTimerList) {
+ if (!aTimerList) {
+ return;
+ }
+
+ uint32_t n = 0;
+ aTimerList->GetLength(&n);
+
+ while (n) {
+ nsCOMPtr<nsITimer> 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);
+ DoCancelRefreshURITimers(mBFCachedRefreshURIList);
+ mRefreshURIList = nullptr;
+ mSavedRefreshURIList = nullptr;
+ mBFCachedRefreshURIList = 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;
+}
+
+void nsDocShell::RefreshURIToQueue() {
+ if (mRefreshURIList) {
+ uint32_t n = 0;
+ mRefreshURIList->GetLength(&n);
+
+ for (uint32_t i = 0; i < n; ++i) {
+ nsCOMPtr<nsITimer> timer = do_QueryElementAt(mRefreshURIList, i);
+ if (!timer) {
+ continue; // this must be a nsRefreshURI already
+ }
+
+ // Replace this timer object with a nsRefreshTimer object.
+ nsCOMPtr<nsITimerCallback> callback;
+ timer->GetCallback(getter_AddRefs(callback));
+
+ timer->Cancel();
+
+ mRefreshURIList->ReplaceElementAt(callback, i);
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsDocShell::SuspendRefreshURIs() {
+ RefreshURIToQueue();
+
+ // Suspend refresh URIs for our child shells as well.
+ for (auto* child : mChildList.ForwardRange()) {
+ nsCOMPtr<nsIDocShell> 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<nsIDocShell> 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<nsITimerCallback> 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<nsRefreshTimer*>(
+ static_cast<nsITimerCallback*>(refreshInfo))
+ ->GetDelay();
+ nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow();
+ if (win) {
+ nsCOMPtr<nsITimer> 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;
+}
+
+static bool IsFollowupPartOfMultipart(nsIRequest* aRequest) {
+ nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(aRequest);
+ bool firstPart = false;
+ return multiPartChannel &&
+ NS_SUCCEEDED(multiPartChannel->GetIsFirstPart(&firstPart)) &&
+ !firstPart;
+}
+
+nsresult nsDocShell::Embed(nsIDocumentViewer* aDocumentViewer,
+ WindowGlobalChild* aWindowActor,
+ bool aIsTransientAboutBlank, bool aPersist,
+ nsIRequest* aRequest, nsIURI* aPreviousURI) {
+ // Save the LayoutHistoryState of the previous document, before
+ // setting up new document
+ PersistLayoutHistoryState();
+
+ nsresult rv = SetupNewViewer(aDocumentViewer, 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<nsISHEntry*>(mLSHE));
+ }
+
+ if (!aIsTransientAboutBlank && mozilla::SessionHistoryInParent() &&
+ !IsFollowupPartOfMultipart(aRequest)) {
+ bool expired = false;
+ uint32_t cacheKey = 0;
+ nsCOMPtr<nsICacheInfoChannel> cacheChannel = do_QueryInterface(aRequest);
+ if (cacheChannel) {
+ // Check if the page has expired from cache
+ uint32_t expTime = 0;
+ cacheChannel->GetCacheTokenExpirationTime(&expTime);
+ uint32_t now = PRTimeToSeconds(PR_Now());
+ if (expTime <= now) {
+ expired = true;
+ }
+
+ // The checks for updating cache key are similar to the old session
+ // history in OnNewURI. Try to update the cache key if
+ // - we should update session history and aren't doing a session
+ // history load.
+ // - we're doing a forced reload.
+ if (((!mLoadingEntry || !mLoadingEntry->mLoadIsFromSessionHistory) &&
+ mBrowsingContext->ShouldUpdateSessionHistory(mLoadType)) ||
+ IsForceReloadType(mLoadType)) {
+ cacheChannel->GetCacheKey(&cacheKey);
+ }
+ }
+
+ MOZ_LOG(gSHLog, LogLevel::Debug, ("document %p Embed", this));
+ MoveLoadingToActiveEntry(aPersist, expired, cacheKey, aPreviousURI);
+ }
+
+ bool updateHistory = true;
+
+ // Determine if this type of load should update history
+ switch (mLoadType) {
+ case LOAD_NORMAL_REPLACE:
+ case LOAD_REFRESH_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<nsIChannel> channel(do_QueryInterface(aRequest));
+ nsCOMPtr<nsIURI> 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<DocumentChannel> 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) {
+ if (StaticPrefs::browser_sessionstore_platform_collection_AtStartup()) {
+ if (IsForceReloadType(mLoadType)) {
+ if (WindowContext* windowContext =
+ mBrowsingContext->GetCurrentWindowContext()) {
+ SessionStoreChild::From(windowContext->GetWindowGlobalChild())
+ ->ResetSessionStore(mBrowsingContext,
+ mBrowsingContext->GetSessionStoreEpoch());
+ }
+ }
+ }
+ }
+ } 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;
+ }
+
+ if ((~aStateFlags & (STATE_IS_DOCUMENT | STATE_STOP)) == 0) {
+ nsCOMPtr<nsIWebProgress> webProgress =
+ do_QueryInterface(GetAsSupports(this));
+ // Is the document stop notification for this document?
+ if (aProgress == webProgress.get()) {
+ nsCOMPtr<nsIChannel> 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) {
+ // 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.
+ bool isTopLevel = false;
+ if (XRE_IsParentProcess() &&
+ !(aFlags & nsIWebProgressListener::LOCATION_CHANGE_SAME_DOCUMENT) &&
+ NS_SUCCEEDED(aProgress->GetIsTopLevel(&isTopLevel)) && isTopLevel) {
+ 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 (!(aStateFlags & STATE_IS_DOCUMENT)) {
+ return; // not a toplevel document
+ }
+
+ nsCOMPtr<nsIURI> 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<DocumentChannel> 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<nsIURI> 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<nsIHttpChannel> 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);
+ }
+ }
+
+ 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<nsIURIFixupInfo> nsDocShell::KeywordToURI(
+ const nsACString& aKeyword, bool aIsPrivateContext) {
+ nsCOMPtr<nsIURIFixupInfo> info;
+ if (!XRE_IsContentProcess()) {
+ nsCOMPtr<nsIURIFixup> uriFixup = components::URIFixup::Service();
+ if (uriFixup) {
+ uriFixup->KeywordToURI(aKeyword, aIsPrivateContext, getter_AddRefs(info));
+ }
+ }
+ return info.forget();
+}
+
+/* static */
+already_AddRefed<nsIURI> 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<nsILoadInfo> 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<nsITransportSecurityInfo> tsi;
+ rv = aChannel->GetSecurityInfo(getter_AddRefs(tsi));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ if (NS_WARN_IF(!tsi)) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIX509Cert> cert;
+ rv = tsi->GetServerCert(getter_AddRefs(cert));
+ if (NS_WARN_IF(NS_FAILED(rv) || !cert)) {
+ return nullptr;
+ }
+
+ nsTArray<uint8_t> 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<const uint8_t*, const char*>(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.
+ rv1 = mozilla::pkix::CheckCertHostname(serverCertInput, newHostInput);
+ if (rv1 != mozilla::pkix::Success) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIURI> newURI;
+ Unused << NS_MutateURI(aUrl).SetHost(newHost).Finalize(
+ getter_AddRefs(newURI));
+
+ return newURI.forget();
+}
+
+/* static */
+already_AddRefed<nsIURI> nsDocShell::AttemptURIFixup(
+ nsIChannel* aChannel, nsresult aStatus,
+ const mozilla::Maybe<nsCString>& aOriginalURIString, uint32_t aLoadType,
+ bool aIsTopFrame, bool aAllowKeywordFixup, bool aUsePrivateBrowsing,
+ bool aNotifyKeywordSearchLoading, nsIInputStream** aNewPostData,
+ bool* outWasSchemelessInput) {
+ 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<nsIURI> 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<nsIURI> newURI;
+ nsCOMPtr<nsIInputStream> 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<nsIEffectiveTLDService> tldService =
+ do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+ if (tldService) {
+ nsAutoCString suffix;
+ attemptFixup =
+ NS_SUCCEEDED(tldService->GetKnownPublicSuffix(url, suffix)) &&
+ suffix.IsEmpty();
+ }
+ }
+ if (attemptFixup) {
+ nsCOMPtr<nsIURIFixupInfo> 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<nsIIDNService> 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));
+ info->GetWasSchemelessInput(outWasSchemelessInput);
+ 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) {
+ // Skip fixup for anything except a normal document load
+ // operation on the topframe.
+ bool doCreateAlternate = aLoadType == LOAD_NORMAL && aIsTopFrame;
+
+ if (doCreateAlternate) {
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ nsIPrincipal* principal = loadInfo->TriggeringPrincipal();
+ // Only do this if our channel was loaded directly by the user from the
+ // URL bar or similar (system principal) and not redirected, because we
+ // shouldn't be guessing things about links from other sites, or a
+ // post-redirect URI.
+ doCreateAlternate = principal && principal->IsSystemPrincipal() &&
+ loadInfo->RedirectChain().IsEmpty();
+ }
+ // Test if keyword lookup produced a new URI or not
+ if (doCreateAlternate && 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) {
+ newURI = nullptr;
+ newPostData = nullptr;
+ keywordProviderName.Truncate();
+ keywordAsSent.Truncate();
+ nsCOMPtr<nsIURIFixup> uriFixup = components::URIFixup::Service();
+ if (uriFixup) {
+ nsCOMPtr<nsIURIFixupInfo> fixupInfo;
+ uriFixup->GetFixupURIInfo(oldSpec, nsIURIFixup::FIXUP_FLAG_NONE,
+ 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 ||
+ aStatus == NS_ERROR_DOM_COOP_FAILED ||
+ aStatus == NS_ERROR_DOM_COEP_FAILED) &&
+ (aIsTopFrame || aUseErrorPages)) {
+ return aStatus;
+ }
+
+ if (aStatus == NS_ERROR_NET_TIMEOUT ||
+ aStatus == NS_ERROR_NET_TIMEOUT_EXTERNAL ||
+ 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_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<nsILoadInfo> 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<uint32_t>(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<nsIConsoleReportCollector> reporter = do_QueryInterface(aChannel);
+ if (reporter) {
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ aChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (loadGroup) {
+ reporter->FlushConsoleReports(loadGroup);
+ } else {
+ reporter->FlushConsoleReports(GetDocument());
+ }
+ }
+
+ nsCOMPtr<nsIURI> url;
+ nsresult rv = aChannel->GetURI(getter_AddRefs(url));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsITimedChannel> timingChannel = do_QueryInterface(aChannel);
+ if (timingChannel) {
+ TimeStamp channelCreationTime;
+ rv = timingChannel->GetChannelCreation(&channelCreationTime);
+ if (NS_SUCCEEDED(rv) && !channelCreationTime.IsNull()) {
+ glean::performance_page::total_content_page_load.AccumulateRawDuration(
+ TimeStamp::Now() - 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<nsISHEntry> 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<nsIDocShell> kungFuDeathGrip(this);
+
+ // Notify the DocumentViewer that the Document has finished loading. This
+ // will cause any OnLoad(...) and PopState(...) handlers to fire.
+ if (!mEODForCurrentDocument && mDocumentViewer) {
+ mIsExecutingOnLoadHandler = true;
+ nsCOMPtr<nsIDocumentViewer> viewer = mDocumentViewer;
+ viewer->LoadComplete(aStatus);
+ mIsExecutingOnLoadHandler = false;
+
+ mEODForCurrentDocument = true;
+ }
+ /* 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<nsIHttpChannel> 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();
+
+ bool hadErrorStatus = false;
+ // 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);
+ hadErrorStatus = true;
+ if (NS_FAILED(aStatus)) {
+ if (!mIsBeingDestroyed) {
+ DisplayLoadError(aStatus, url, nullptr, aChannel);
+ }
+ } else if (skippedUnknownProtocolNavigation) {
+ nsTArray<nsString> 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<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ PredictorLearnRedirect(url, aChannel, loadInfo->GetOriginAttributes());
+ }
+
+ if (hadErrorStatus) {
+ // Don't send session store updates if the reason EndPageLoad was called is
+ // because we are process switching. Sometimes the update takes too long and
+ // incorrectly overrides session store data from the following load.
+ return NS_OK;
+ }
+ if (StaticPrefs::browser_sessionstore_platform_collection_AtStartup()) {
+ if (WindowContext* windowContext =
+ mBrowsingContext->GetCurrentWindowContext()) {
+ using Change = SessionStoreChangeListener::Change;
+
+ // We've finished loading the page and now we want to collect all the
+ // session store state that the page is initialized with.
+ SessionStoreChangeListener::CollectSessionStoreData(
+ windowContext,
+ EnumSet<Change>(Change::Input, Change::Scroll, Change::SessionHistory,
+ Change::WireFrame));
+ }
+ }
+
+ return NS_OK;
+}
+
+//*****************************************************************************
+// nsDocShell: Content Viewer Management
+//*****************************************************************************
+
+nsresult nsDocShell::EnsureDocumentViewer() {
+ if (mDocumentViewer) {
+ return NS_OK;
+ }
+ if (mIsBeingDestroyed) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIContentSecurityPolicy> cspToInheritForAboutBlank;
+ nsCOMPtr<nsIURI> baseURI;
+ nsIPrincipal* principal = GetInheritedPrincipal(false);
+ nsIPrincipal* partitionedPrincipal = GetInheritedPrincipal(false, true);
+
+ nsCOMPtr<nsIDocShellTreeItem> parentItem;
+ GetInProcessSameTypeParent(getter_AddRefs(parentItem));
+ if (parentItem) {
+ if (nsCOMPtr<nsPIDOMWindowOuter> domWin = GetWindow()) {
+ nsCOMPtr<Element> parentElement = domWin->GetFrameElementInternal();
+ if (parentElement) {
+ baseURI = parentElement->GetBaseURI();
+ cspToInheritForAboutBlank = parentElement->GetCsp();
+ }
+ }
+ }
+
+ nsresult rv = CreateAboutBlankDocumentViewer(
+ principal, partitionedPrincipal, cspToInheritForAboutBlank, baseURI,
+ /* aIsInitialDocument */ true);
+
+ NS_ENSURE_STATE(mDocumentViewer);
+
+ if (NS_SUCCEEDED(rv)) {
+ RefPtr<Document> doc(GetDocument());
+ MOZ_ASSERT(doc,
+ "Should have doc if CreateAboutBlankDocumentViewer "
+ "succeeded!");
+ MOZ_ASSERT(doc->IsInitialDocument(), "Document should be initial document");
+
+ // Documents created using EnsureDocumentViewer 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::CreateAboutBlankDocumentViewer(
+ nsIPrincipal* aPrincipal, nsIPrincipal* aPartitionedPrincipal,
+ nsIContentSecurityPolicy* aCSP, nsIURI* aBaseURI, bool aIsInitialDocument,
+ const Maybe<nsILoadInfo::CrossOriginEmbedderPolicy>& aCOEP,
+ bool aTryToSaveOldPresentation, bool aCheckPermitUnload,
+ WindowGlobalChild* aActor) {
+ RefPtr<Document> blankDoc;
+ nsCOMPtr<nsIDocumentViewer> 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->IsInBFCache()) {
+ mBrowsingContext->RemoveRootFromBFCacheSync();
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // mDocumentViewer->PermitUnload may release |this| docshell.
+ nsCOMPtr<nsIDocShell> kungFuDeathGrip(this);
+
+ AutoRestore<bool> 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 (mDocumentViewer) {
+ 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 = mDocumentViewer->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,
+ /* aReportBFCacheComboTelemetry */ true);
+
+ // 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<nsIDocumentLoaderFactory> docFactory =
+ nsContentUtils::FindInternalDocumentViewer("text/html"_ns);
+
+ if (docFactory) {
+ nsCOMPtr<nsIPrincipal> 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::Create(GetOriginAttributes());
+ }
+ partitionedPrincipal = principal;
+ } else {
+ principal = aPrincipal;
+ partitionedPrincipal = aPartitionedPrincipal;
+ }
+
+ // We cannot get the foreign partitioned prinicpal for the initial
+ // about:blank page. So, we change to check if we need to use the
+ // partitioned principal for the service worker here.
+ MaybeCreateInitialClientSource(
+ StoragePrincipalHelper::ShouldUsePartitionPrincipalForServiceWorker(
+ this)
+ ? partitionedPrincipal
+ : 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<nsCSPContext> cspToInherit = new nsCSPContext();
+ cspToInherit->InitFromOther(static_cast<nsCSPContext*>(aCSP));
+ blankDoc->SetCsp(cspToInherit);
+ }
+
+ blankDoc->SetIsInitialDocument(aIsInitialDocument);
+
+ 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);
+
+ blankDoc->InitFeaturePolicy();
+
+ // 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, nullptr, mCurrentURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ SetCurrentURI(blankDoc->GetDocumentURI(), nullptr,
+ /* aFireLocationChange */ true,
+ /* aIsInitialAboutBlank */ true,
+ /* aLocationFlags */ 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::CreateAboutBlankDocumentViewer(nsIPrincipal* aPrincipal,
+ nsIPrincipal* aPartitionedPrincipal,
+ nsIContentSecurityPolicy* aCSP) {
+ return CreateAboutBlankDocumentViewer(aPrincipal, aPartitionedPrincipal, aCSP,
+ nullptr,
+ /* aIsInitialDocument */ false);
+}
+
+nsresult nsDocShell::CreateDocumentViewerForActor(
+ WindowGlobalChild* aWindowActor) {
+ MOZ_ASSERT(aWindowActor);
+
+ // FIXME: WindowGlobalChild should provide the PartitionedPrincipal.
+ // FIXME: We may want to support non-initial documents here.
+ nsresult rv = CreateAboutBlankDocumentViewer(
+ aWindowActor->DocumentPrincipal(), aWindowActor->DocumentPrincipal(),
+ /* aCsp */ nullptr,
+ /* aBaseURI */ nullptr,
+ /* aIsInitialDocument */ true,
+ /* aCOEP */ Nothing(),
+ /* aTryToSaveOldPresentation */ true,
+ /* aCheckPermitUnload */ true, aWindowActor);
+#ifdef DEBUG
+ if (NS_SUCCEEDED(rv)) {
+ RefPtr<Document> doc(GetDocument());
+ MOZ_ASSERT(
+ doc,
+ "Should have a document if CreateAboutBlankDocumentViewer succeeded");
+ MOZ_ASSERT(doc->GetOwnerGlobal() == aWindowActor->GetWindowGlobal(),
+ "New document should be in the same global as our actor");
+ MOZ_ASSERT(doc->IsInitialDocument(),
+ "New document should be an initial document");
+ }
+#endif
+
+ return rv;
+}
+
+bool nsDocShell::CanSavePresentation(uint32_t aLoadType,
+ nsIRequest* aNewRequest,
+ Document* aNewDocument,
+ bool aReportBFCacheComboTelemetry) {
+ if (!mOSHE) {
+ return false; // no entry to save into
+ }
+
+ MOZ_ASSERT(!mozilla::SessionHistoryInParent(),
+ "mOSHE cannot be non-null with SHIP");
+ nsCOMPtr<nsIDocumentViewer> viewer = mOSHE->GetDocumentViewer();
+ 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<Document> doc = mScriptGlobal->GetExtantDoc();
+
+ uint32_t bfCacheCombo = 0;
+ bool canSavePresentation =
+ doc->CanSavePresentation(aNewRequest, bfCacheCombo, true);
+ MOZ_ASSERT_IF(canSavePresentation, bfCacheCombo == 0);
+ if (canSavePresentation && doc->IsTopLevelContentDocument()) {
+ auto* browsingContextGroup = mBrowsingContext->Group();
+ nsTArray<RefPtr<BrowsingContext>>& 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;
+ }
+ }
+ }
+
+ if (aReportBFCacheComboTelemetry) {
+ ReportBFCacheComboTelemetry(bfCacheCombo);
+ }
+ return doc && canSavePresentation;
+}
+
+/* static */
+void nsDocShell::ReportBFCacheComboTelemetry(uint32_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 : uint32_t {
+ BFCACHE_SUCCESS,
+ NOT_ONLY_TOPLEVEL = mozilla::dom::BFCacheStatus::NOT_ONLY_TOPLEVEL_IN_BCG,
+ // If both unload and beforeunload listeners are presented, it'll be
+ // recorded as unload
+ 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,
+ BEFOREUNLOAD = mozilla::dom::BFCacheStatus::BEFOREUNLOAD_LISTENER,
+ };
+
+ // Beforeunload is recorded as a blocker only if it is the only one to block
+ // bfcache.
+ if (aCombo != mozilla::dom::BFCacheStatus::BEFOREUNLOAD_LISTENER) {
+ aCombo &= ~mozilla::dom::BFCacheStatus::BEFOREUNLOAD_LISTENER;
+ }
+ 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 BEFOREUNLOAD:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_BFCACHE_COMBO::Beforeunload);
+ 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(!mozilla::SessionHistoryInParent());
+ 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() {
+ MOZ_ASSERT(!mozilla::SessionHistoryInParent());
+
+ 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<nsISupports> windowState = mScriptGlobal->SaveWindowState();
+ NS_ENSURE_TRUE(windowState, NS_ERROR_FAILURE);
+
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Debug))) {
+ nsAutoCString spec;
+ nsCOMPtr<nsIURI> 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 (mDocumentViewer) {
+ nsIntRect bounds;
+ mDocumentViewer->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<nsIDocShellTreeItem> childShell = do_QueryInterface(ChildAt(i));
+ NS_ASSERTION(childShell, "null child shell");
+
+ mOSHE->AddChildShell(childShell);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::RestorePresentationEvent::Run() {
+ MOZ_ASSERT(!mozilla::SessionHistoryInParent());
+
+ if (mDocShell && NS_FAILED(mDocShell->RestoreFromHistory())) {
+ NS_WARNING("RestoreFromHistory failed");
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::BeginRestore(nsIDocumentViewer* aDocumentViewer, bool aTop) {
+ MOZ_ASSERT(!mozilla::SessionHistoryInParent());
+
+ nsresult rv;
+ if (!aDocumentViewer) {
+ rv = EnsureDocumentViewer();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aDocumentViewer = mDocumentViewer;
+ }
+
+ // 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<Document> doc = aDocumentViewer->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 CreateDocumentViewer 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() {
+ MOZ_ASSERT(!mozilla::SessionHistoryInParent());
+
+ for (auto* childDocLoader : mChildList.ForwardRange()) {
+ nsCOMPtr<nsIDocShell> child = do_QueryObject(childDocLoader);
+ if (child) {
+ nsresult rv = child->BeginRestore(nullptr, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::FinishRestore() {
+ MOZ_ASSERT(!mozilla::SessionHistoryInParent());
+
+ // 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<nsIDocShell> child = do_QueryObject(childDocLoader);
+ if (child) {
+ child->FinishRestore();
+ }
+ }
+
+ if (mOSHE && mOSHE->HasDetachedEditor()) {
+ ReattachEditorToWindow(mOSHE);
+ }
+
+ RefPtr<Document> 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(!mozilla::SessionHistoryInParent());
+ MOZ_ASSERT(!mIsBeingDestroyed);
+
+ NS_ASSERTION(mLoadType & LOAD_CMD_HISTORY,
+ "RestorePresentation should only be called for history loads");
+
+ nsCOMPtr<nsIDocumentViewer> viewer = aSHEntry->GetDocumentViewer();
+
+ nsAutoCString spec;
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Debug))) {
+ nsCOMPtr<nsIURI> 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<nsIDocShell> container;
+ viewer->GetContainer(getter_AddRefs(container));
+ if (!::SameCOMIdentity(container, GetAsSupports(this))) {
+ MOZ_LOG(gPageCacheLog, LogLevel::Debug,
+ ("No valid container, clearing presentation"));
+ aSHEntry->SetDocumentViewer(nullptr);
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_ASSERTION(mDocumentViewer != 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<RestorePresentationEvent> evt = new RestorePresentationEvent(this);
+ nsresult rv = Dispatch(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<nsDocShell::RestorePresentationEvent>&
+ aRestorePresentationEvent)
+ : mRestorePresentationEvent(aRestorePresentationEvent),
+ mEvent(aRestorePresentationEvent.get()) {}
+
+ ~PresentationEventForgetter() { Forget(); }
+
+ void Forget() {
+ if (mRestorePresentationEvent.get() == mEvent) {
+ mRestorePresentationEvent.Forget();
+ mEvent = nullptr;
+ }
+ }
+
+ private:
+ nsRevocableEventPtr<nsDocShell::RestorePresentationEvent>&
+ mRestorePresentationEvent;
+ RefPtr<nsDocShell::RestorePresentationEvent> mEvent;
+};
+
+} // namespace
+
+bool nsDocShell::SandboxFlagsImplyCookies(const uint32_t& aSandboxFlags) {
+ return (aSandboxFlags & (SANDBOXED_ORIGIN | SANDBOXED_SCRIPTS)) == 0;
+}
+
+nsresult nsDocShell::RestoreFromHistory() {
+ MOZ_ASSERT(!mozilla::SessionHistoryInParent());
+ MOZ_ASSERT(mRestorePresentationEvent.IsPending());
+ PresentationEventForgetter forgetter(mRestorePresentationEvent);
+
+ // This section of code follows the same ordering as CreateDocumentViewer.
+ if (!mLSHE) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIDocumentViewer> viewer = mLSHE->GetDocumentViewer();
+ 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<Document> doc = viewer->GetDocument();
+ nsIRequest* request = nullptr;
+ if (doc) {
+ request = doc->GetChannel();
+ }
+ mSavingOldViewer = CanSavePresentation(
+ mLoadType, request, doc, /* aReportBFCacheComboTelemetry */ false);
+ }
+
+ // Protect against mLSHE going away via a load triggered from
+ // pagehide or unload.
+ nsCOMPtr<nsISHEntry> 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<RestorePresentationEvent> 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<ChildSHistory> rootSH = GetRootSessionHistory();
+ if (rootSH) {
+ mPreviousEntryIndex = rootSH->Index();
+ 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 (mDocumentViewer) {
+ 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 (mDocumentViewer) {
+ // Make sure to hold a strong ref to previousViewer here while we
+ // drop the reference to it from mDocumentViewer.
+ nsCOMPtr<nsIDocumentViewer> previousViewer =
+ mDocumentViewer->GetPreviousViewer();
+ if (previousViewer) {
+ mDocumentViewer->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();
+
+ mDocumentViewer->GetBounds(newBounds);
+ }
+ }
+ }
+
+ nsCOMPtr<nsIContent> container;
+ RefPtr<Document> 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 mDocumentViewer. 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 (mDocumentViewer) {
+ mDocumentViewer->Close(mSavingOldViewer ? mOSHE.get() : nullptr);
+ viewer->SetPreviousViewer(mDocumentViewer);
+ }
+ if (mOSHE && (!mDocumentViewer || !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 mDocumentViewer setup just like Embed does.
+ mDocumentViewer = 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();
+
+ mDocumentViewer.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<nsISupports> windowState = mLSHE->GetWindowState();
+ mLSHE->SetWindowState(nullptr);
+
+ bool sticky = mLSHE->GetSticky();
+
+ RefPtr<Document> document = mDocumentViewer->GetDocument();
+
+ nsCOMArray<nsIDocShellTreeItem> childShells;
+ int32_t i = 0;
+ nsCOMPtr<nsIDocShellTreeItem> 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<nsIMutableArray> refreshURIList = mLSHE->GetRefreshURIList();
+
+ // Reattach to the window object.
+ mIsRestoringDocument = true; // for MediaDocument::BecomeInteractive
+ rv = mDocumentViewer->Open(windowState, mLSHE);
+ mIsRestoringDocument = false;
+
+ // Hack to keep nsDocShellEditorData alive across the
+ // SetContentViewer(nullptr) call below.
+ UniquePtr<nsDocShellEditorData> data(mLSHE->ForgetEditorData());
+
+ // Now remove it from the cached presentation.
+ mLSHE->SetDocumentViewer(nullptr);
+ mEODForCurrentDocument = false;
+
+ mLSHE->SetEditorData(data.release());
+
+#ifdef DEBUG
+ {
+ nsCOMPtr<nsIMutableArray> refreshURIs = mLSHE->GetRefreshURIList();
+ nsCOMPtr<nsIDocShellTreeItem> 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.
+ mDocumentViewer->SetSticky(sticky);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // mLSHE is now our currently-loaded document.
+ SetHistoryEntryAndUpdateBC(Nothing(), Some<nsISHEntry*>(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;
+
+ if (document) {
+ RefPtr<nsDocShell> parent = GetInProcessParentDocshell();
+ if (parent) {
+ RefPtr<Document> 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<nsIURI> uri = origLSHE->GetURI();
+ SetCurrentURI(uri, document->GetChannel(), /* aFireLocationChange */ true,
+ /* aIsInitialAboutBlank */ false,
+ /* aLocationFlags */ 0);
+ }
+
+ // This is the end of our CreateDocumentViewer() replacement.
+ // Now we simulate a load. First, we restore the state of the javascript
+ // window object.
+ nsCOMPtr<nsPIDOMWindowOuter> privWin = GetWindow();
+ NS_ASSERTION(privWin, "could not get nsPIDOMWindow interface");
+
+ // Now, dispatch a title change event which would happen as the
+ // <head> 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<nsIDocShell> childShell = do_QueryInterface(childItem);
+
+ // Make sure to not clobber the state of the child. Since AddChild
+ // always clobbers it, save it off first.
+ 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->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> 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 mDocumentViewer->SetBounds(newBounds) below. So we need to
+ // explicitly tell it to check for changed resolution here.
+ if (presShell) {
+ RefPtr<nsPresContext> 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<nsPIDOMWindowInner> 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.
+ Performance* performance = privWinInner->GetPerformance();
+ if (performance) {
+ performance->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<nsIDocShell> 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));
+ mDocumentViewer->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(true);
+}
+
+nsresult nsDocShell::CreateDocumentViewer(const nsACString& aContentType,
+ nsIRequest* aRequest,
+ nsIStreamListener** aContentHandler) {
+ *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->IsInBFCache()) {
+ 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<nsIDocumentViewer> viewer;
+ nsresult rv = NewDocumentViewerObj(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<Document> doc = viewer->GetDocument();
+ mSavingOldViewer = CanSavePresentation(
+ mLoadType, aRequest, doc, /* aReportBFCacheComboTelemetry */ false);
+ }
+
+ NS_ASSERTION(!mLoadingURI, "Re-entering unload?");
+
+ nsCOMPtr<nsIChannel> aOpenedChannel = do_QueryInterface(aRequest);
+ if (aOpenedChannel) {
+ aOpenedChannel->GetURI(getter_AddRefs(mLoadingURI));
+ }
+
+ // Grab the current URI, we need to pass it to Embed, and OnNewURI will reset
+ // it before we do call Embed.
+ nsCOMPtr<nsIURI> previousURI = mCurrentURI;
+
+ 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<nsIChannel> failedChannel = mFailedChannel;
+ nsCOMPtr<nsIURI> 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<nsIPrincipal> 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);
+ }
+
+ // 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<nsIURI> 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, true, false);
+ }
+
+ // let's try resetting the load group if we need to...
+ nsCOMPtr<nsILoadGroup> 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<nsILoadInfo> 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);
+ }
+
+ NS_ENSURE_SUCCESS(Embed(viewer, nullptr, false,
+ ShouldAddToSessionHistory(finalURI, aOpenedChannel),
+ aOpenedChannel, previousURI),
+ NS_ERROR_FAILURE);
+
+ if (!mBrowsingContext->GetHasLoadedNonInitialDocument()) {
+ MOZ_ALWAYS_SUCCEEDS(mBrowsingContext->SetHasLoadedNonInitialDocument(true));
+ }
+
+ 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<nsIMultiPartChannel> multiPartChannel(do_QueryInterface(aRequest));
+ if (multiPartChannel) {
+ if (PresShell* presShell = GetPresShell()) {
+ if (Document* doc = presShell->GetDocument()) {
+ uint32_t partID;
+ multiPartChannel->GetPartID(&partID);
+ doc->SetPartID(partID);
+ }
+ }
+ }
+
+ 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::NewDocumentViewerObj(const nsACString& aContentType,
+ nsIRequest* aRequest,
+ nsILoadGroup* aLoadGroup,
+ nsIStreamListener** aContentHandler,
+ nsIDocumentViewer** aViewer) {
+ nsCOMPtr<nsIChannel> aOpenedChannel = do_QueryInterface(aRequest);
+
+ nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory =
+ nsContentUtils::FindInternalDocumentViewer(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(nsIDocumentViewer* 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 <SCRIPT>s and event handlers fail! (bug 20315)
+ //
+ // In this block of code, if we get an error result, we return it
+ // but if we get a null pointer, that's perfectly legal for parent
+ // and parentContentViewer.
+ //
+
+ int32_t x = 0;
+ int32_t y = 0;
+ int32_t cx = 0;
+ int32_t cy = 0;
+
+ // This will get the size from the current content viewer or from the
+ // Init settings
+ DoGetPositionAndSize(&x, &y, &cx, &cy);
+
+ nsCOMPtr<nsIDocShellTreeItem> parentAsItem;
+ NS_ENSURE_SUCCESS(GetInProcessSameTypeParent(getter_AddRefs(parentAsItem)),
+ NS_ERROR_FAILURE);
+ nsCOMPtr<nsIDocShell> parent(do_QueryInterface(parentAsItem));
+
+ const Encoding* reloadEncoding = nullptr;
+ int32_t reloadEncodingSource = kCharsetUninitialized;
+ // |newMUDV| also serves as a flag to set the data from the above vars
+ nsCOMPtr<nsIDocumentViewer> newViewer;
+
+ if (mDocumentViewer || parent) {
+ nsCOMPtr<nsIDocumentViewer> oldViewer;
+ if (mDocumentViewer) {
+ // Get any interesting state from old content viewer
+ // XXX: it would be far better to just reuse the document viewer ,
+ // since we know we're just displaying the same document as before
+ oldViewer = mDocumentViewer;
+
+ // Tell the old content viewer to hibernate in session history when
+ // it is destroyed.
+
+ if (mSavingOldViewer && NS_FAILED(CaptureState())) {
+ if (mOSHE) {
+ mOSHE->SyncPresentationState();
+ }
+ mSavingOldViewer = false;
+ }
+ } else {
+ // No old content viewer, so get state from parent's content viewer
+ parent->GetDocViewer(getter_AddRefs(oldViewer));
+ }
+
+ if (oldViewer) {
+ newViewer = aNewViewer;
+ if (newViewer) {
+ reloadEncoding =
+ oldViewer->GetReloadEncodingAndSource(&reloadEncodingSource);
+ }
+ }
+ }
+
+ nscolor bgcolor = NS_RGBA(0, 0, 0, 0);
+ bool isUnderHiddenEmbedderElement = false;
+ // Ensure that the content viewer is destroyed *after* the GC - bug 71515
+ nsCOMPtr<nsIDocumentViewer> viewer = mDocumentViewer;
+ if (viewer) {
+ // Stop any activity that may be happening in the old document before
+ // releasing it...
+ viewer->Stop();
+
+ // Try to extract the canvas background color from the old
+ // presentation shell, so we can use it for the next document.
+ if (PresShell* presShell = viewer->GetPresShell()) {
+ bgcolor = presShell->GetCanvasBackground();
+ isUnderHiddenEmbedderElement = presShell->IsUnderHiddenEmbedderElement();
+ }
+
+ viewer->Close(mSavingOldViewer ? mOSHE.get() : nullptr);
+ aNewViewer->SetPreviousViewer(viewer);
+ }
+ if (mOSHE && (!mDocumentViewer || !mSavingOldViewer)) {
+ // We don't plan to save a viewer in mOSHE; tell it to drop
+ // any other state it's holding.
+ mOSHE->SyncPresentationState();
+ }
+
+ mDocumentViewer = 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();
+
+ mDocumentViewer = aNewViewer;
+
+ nsCOMPtr<nsIWidget> widget;
+ NS_ENSURE_SUCCESS(GetMainWidget(getter_AddRefs(widget)), NS_ERROR_FAILURE);
+
+ nsIntRect bounds(x, y, cx, cy);
+
+ mDocumentViewer->SetNavigationTiming(mTiming);
+
+ if (NS_FAILED(mDocumentViewer->Init(widget, bounds, aWindowActor))) {
+ nsCOMPtr<nsIDocumentViewer> viewer = mDocumentViewer;
+ viewer->Close(nullptr);
+ viewer->Destroy();
+ mDocumentViewer = nullptr;
+ SetCurrentURIInternal(nullptr);
+ NS_WARNING("ContentViewer Initialization failed");
+ return NS_ERROR_FAILURE;
+ }
+
+ // If we have old state to copy, set the old state onto the new content
+ // viewer
+ if (newViewer) {
+ newViewer->SetReloadEncodingAndSource(reloadEncoding, reloadEncodingSource);
+ }
+
+ NS_ENSURE_TRUE(mDocumentViewer, NS_ERROR_FAILURE);
+
+ // Stuff the bgcolor from the old pres shell into the new
+ // pres shell. This improves page load continuity.
+ if (RefPtr<PresShell> presShell = mDocumentViewer->GetPresShell()) {
+ presShell->SetCanvasBackground(bgcolor);
+ presShell->ActivenessMaybeChanged();
+ if (isUnderHiddenEmbedderElement) {
+ presShell->SetIsUnderHiddenEmbedderElement(isUnderHiddenEmbedderElement);
+ }
+ }
+
+ // XXX: It looks like the LayoutState gets restored again in Embed()
+ // right after the call to SetupNewViewer(...)
+
+ // We don't show the mDocumentViewer yet, since we want to draw the old page
+ // until we have enough of the new page to show. Just return with the new
+ // viewer still set to hidden.
+
+ return NS_OK;
+}
+
+void nsDocShell::SetDocCurrentStateObj(nsISHEntry* aShEntry,
+ SessionHistoryInfo* aInfo) {
+ NS_ENSURE_TRUE_VOID(mDocumentViewer);
+
+ RefPtr<Document> document = GetDocument();
+ NS_ENSURE_TRUE_VOID(document);
+
+ nsCOMPtr<nsIStructuredCloneContainer> scContainer;
+ if (mozilla::SessionHistoryInParent()) {
+ // If aInfo is null, just set the document's state object to null.
+ if (aInfo) {
+ scContainer = aInfo->GetStateData();
+ }
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("nsDocShell %p SetCurrentDocState %p", this, scContainer.get()));
+ } else {
+ if (aShEntry) {
+ scContainer = aShEntry->GetStateData();
+
+ // If aShEntry is null, just set the document's state object to null.
+ }
+ }
+
+ // It's OK for scContainer too be null here; that just means there's no
+ // state data associated with this history entry.
+ document->SetStateObject(scContainer);
+}
+
+nsresult nsDocShell::CheckLoadingPermissions() {
+ // This method checks whether the caller may load content into
+ // this docshell. Even though we've done our best to hide windows
+ // from code that doesn't have the right to access them, it's
+ // still possible for an evil site to open a window and access
+ // frames in the new window through window.frames[] (which is
+ // allAccess for historic reasons), so we still need to do this
+ // check on load.
+ nsresult rv = NS_OK;
+
+ if (!IsSubframe()) {
+ // We're not a frame. Permit all loads.
+ return rv;
+ }
+
+ // Note - The check for a current JSContext here isn't necessarily sensical.
+ // It's just designed to preserve the old semantics during a mass-conversion
+ // patch.
+ if (!nsContentUtils::GetCurrentJSContext()) {
+ return NS_OK;
+ }
+
+ // Check if the caller is from the same origin as this docshell,
+ // or any of its ancestors.
+ for (RefPtr<BrowsingContext> bc = mBrowsingContext; bc;
+ bc = bc->GetParent()) {
+ // If the BrowsingContext is not in process, then it
+ // is true by construction that its principal will not
+ // subsume the current docshell principal.
+ if (!bc->IsInProcess()) {
+ continue;
+ }
+
+ nsCOMPtr<nsIScriptGlobalObject> sgo =
+ bc->GetDocShell()->GetScriptGlobalObject();
+ nsCOMPtr<nsIScriptObjectPrincipal> sop(do_QueryInterface(sgo));
+
+ nsIPrincipal* p;
+ if (!sop || !(p = sop->GetPrincipal())) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (nsContentUtils::SubjectPrincipal()->Subsumes(p)) {
+ // Same origin, permit load
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_DOM_PROP_ACCESS_DENIED;
+}
+
+//*****************************************************************************
+// nsDocShell: Site Loading
+//*****************************************************************************
+
+void nsDocShell::CopyFavicon(nsIURI* aOldURI, nsIURI* aNewURI,
+ bool aInPrivateBrowsing) {
+ if (XRE_IsContentProcess()) {
+ dom::ContentChild* contentChild = dom::ContentChild::GetSingleton();
+ if (contentChild) {
+ contentChild->SendCopyFavicon(aOldURI, aNewURI, aInPrivateBrowsing);
+ }
+ return;
+ }
+
+#ifdef MOZ_PLACES
+ nsCOMPtr<nsIFaviconService> favSvc =
+ do_GetService("@mozilla.org/browser/favicon-service;1");
+ if (favSvc) {
+ favSvc->CopyFavicons(aOldURI, aNewURI,
+ aInPrivateBrowsing
+ ? nsIFaviconService::FAVICON_LOAD_PRIVATE
+ : nsIFaviconService::FAVICON_LOAD_NON_PRIVATE,
+ nullptr);
+ }
+#endif
+}
+
+class InternalLoadEvent : public Runnable {
+ public:
+ InternalLoadEvent(nsDocShell* aDocShell, nsDocShellLoadState* aLoadState)
+ : mozilla::Runnable("InternalLoadEvent"),
+ mDocShell(aDocShell),
+ mLoadState(aLoadState) {
+ // For events, both target and filename should be the version of "null" they
+ // expect. By the time the event is fired, both window targeting and file
+ // downloading have been handled, so we should never have an internal load
+ // event that retargets or had a download.
+ mLoadState->SetTarget(u""_ns);
+ mLoadState->SetFileName(VoidString());
+ }
+
+ NS_IMETHOD
+ Run() override {
+#ifndef ANDROID
+ MOZ_ASSERT(mLoadState->TriggeringPrincipal(),
+ "InternalLoadEvent: Should always have a principal here");
+#endif
+ return mDocShell->InternalLoad(mLoadState);
+ }
+
+ private:
+ RefPtr<nsDocShell> mDocShell;
+ RefPtr<nsDocShellLoadState> mLoadState;
+};
+
+/**
+ * Returns true if we started an asynchronous load (i.e., from the network), but
+ * the document we're loading there hasn't yet become this docshell's active
+ * document.
+ *
+ * When JustStartedNetworkLoad is true, you should be careful about modifying
+ * mLoadType and mLSHE. These are both set when the asynchronous load first
+ * starts, and the load expects that, when it eventually runs InternalLoad,
+ * mLoadType and mLSHE will have their original values.
+ */
+bool nsDocShell::JustStartedNetworkLoad() {
+ return mDocumentRequest && mDocumentRequest != GetCurrentDocChannel();
+}
+
+// The contentType will be INTERNAL_(I)FRAME if this docshell is for a
+// non-toplevel browsing context in spec terms. (frame, iframe, <object>,
+// <embed>, etc)
+//
+// This return value will be used when we call NS_CheckContentLoadPolicy, and
+// later when we call DoURILoad.
+nsContentPolicyType nsDocShell::DetermineContentType() {
+ if (!IsSubframe()) {
+ return nsIContentPolicy::TYPE_DOCUMENT;
+ }
+
+ const auto& maybeEmbedderElementType =
+ GetBrowsingContext()->GetEmbedderElementType();
+ if (!maybeEmbedderElementType) {
+ // If the EmbedderElementType hasn't been set yet, just assume we're
+ // an iframe since that's more common.
+ return nsIContentPolicy::TYPE_INTERNAL_IFRAME;
+ }
+
+ return maybeEmbedderElementType->EqualsLiteral("iframe")
+ ? nsIContentPolicy::TYPE_INTERNAL_IFRAME
+ : nsIContentPolicy::TYPE_INTERNAL_FRAME;
+}
+
+bool nsDocShell::NoopenerForceEnabled() {
+ // If current's top-level browsing context's active document's
+ // cross-origin-opener-policy is "same-origin" or "same-origin + COEP" then
+ // if currentDoc's origin is not same origin with currentDoc's top-level
+ // origin, noopener is force enabled, and name is cleared to "_blank".
+ auto topPolicy = mBrowsingContext->Top()->GetOpenerPolicy();
+ return (topPolicy == nsILoadInfo::OPENER_POLICY_SAME_ORIGIN ||
+ topPolicy ==
+ nsILoadInfo::
+ OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP) &&
+ !mBrowsingContext->SameOriginWithTop();
+}
+
+nsresult nsDocShell::PerformRetargeting(nsDocShellLoadState* aLoadState) {
+ MOZ_ASSERT(aLoadState, "need a load state!");
+ MOZ_ASSERT(!aLoadState->Target().IsEmpty(), "should have a target here!");
+ MOZ_ASSERT(aLoadState->TargetBrowsingContext().IsNull(),
+ "should not have picked target yet");
+
+ nsresult rv = NS_OK;
+ RefPtr<BrowsingContext> targetContext;
+
+ // Only _self, _parent, and _top are supported in noopener case. But we
+ // have to be careful to not apply that to the noreferrer case. See bug
+ // 1358469.
+ bool allowNamedTarget =
+ !aLoadState->HasInternalLoadFlags(INTERNAL_LOAD_FLAGS_NO_OPENER) ||
+ aLoadState->HasInternalLoadFlags(INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER);
+ if (allowNamedTarget ||
+ aLoadState->Target().LowerCaseEqualsLiteral("_self") ||
+ aLoadState->Target().LowerCaseEqualsLiteral("_parent") ||
+ aLoadState->Target().LowerCaseEqualsLiteral("_top")) {
+ Document* document = GetDocument();
+ NS_ENSURE_TRUE(document, NS_ERROR_FAILURE);
+ WindowGlobalChild* wgc = document->GetWindowGlobalChild();
+ NS_ENSURE_TRUE(wgc, NS_ERROR_FAILURE);
+ targetContext = wgc->FindBrowsingContextWithName(
+ aLoadState->Target(), /* aUseEntryGlobalForAccessCheck */ false);
+ }
+
+ if (!targetContext) {
+ // If the targetContext doesn't exist, then this is a new docShell and we
+ // should consider this a TYPE_DOCUMENT load
+ //
+ // For example, when target="_blank"
+
+ // If there's no targetContext, that means we are about to create a new
+ // window. Perform a content policy check before creating the window. Please
+ // note for all other docshell loads content policy checks are performed
+ // within the contentSecurityManager when the channel is about to be
+ // openend.
+ nsISupports* requestingContext = nullptr;
+ if (XRE_IsContentProcess()) {
+ // In e10s the child process doesn't have access to the element that
+ // contains the browsing context (because that element is in the chrome
+ // process). So we just pass mScriptGlobal.
+ requestingContext = ToSupports(mScriptGlobal);
+ } else {
+ // This is for loading non-e10s tabs and toplevel windows of various
+ // sorts.
+ // For the toplevel window cases, requestingElement will be null.
+ nsCOMPtr<Element> requestingElement =
+ mScriptGlobal->GetFrameElementInternal();
+ requestingContext = requestingElement;
+ }
+
+ // Ideally we should use the same loadinfo as within DoURILoad which
+ // should match this one when both are applicable.
+ nsCOMPtr<nsILoadInfo> secCheckLoadInfo =
+ new LoadInfo(mScriptGlobal, aLoadState->URI(),
+ aLoadState->TriggeringPrincipal(), requestingContext,
+ nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK, 0);
+
+ // Since Content Policy checks are performed within docShell as well as
+ // the ContentSecurityManager we need a reliable way to let certain
+ // nsIContentPolicy consumers ignore duplicate calls.
+ secCheckLoadInfo->SetSkipContentPolicyCheckForWebRequest(true);
+
+ int16_t shouldLoad = nsIContentPolicy::ACCEPT;
+ rv = NS_CheckContentLoadPolicy(aLoadState->URI(), secCheckLoadInfo,
+ &shouldLoad);
+
+ if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
+ if (NS_SUCCEEDED(rv)) {
+ if (shouldLoad == nsIContentPolicy::REJECT_TYPE) {
+ return NS_ERROR_CONTENT_BLOCKED_SHOW_ALT;
+ }
+ if (shouldLoad == nsIContentPolicy::REJECT_POLICY) {
+ return NS_ERROR_BLOCKED_BY_POLICY;
+ }
+ }
+
+ return NS_ERROR_CONTENT_BLOCKED;
+ }
+ }
+
+ //
+ // Resolve the window target before going any further...
+ // If the load has been targeted to another DocShell, then transfer the
+ // load to it...
+ //
+
+ // We've already done our owner-inheriting. Mask out that bit, so we
+ // don't try inheriting an owner from the target window if we came up
+ // with a null owner above.
+ aLoadState->UnsetInternalLoadFlag(INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL);
+
+ if (!targetContext) {
+ // If the docshell's document is sandboxed, only open a new window
+ // if the document's SANDBOXED_AUXILLARY_NAVIGATION flag is not set.
+ // (i.e. if allow-popups is specified)
+ NS_ENSURE_TRUE(mDocumentViewer, NS_ERROR_FAILURE);
+ Document* doc = mDocumentViewer->GetDocument();
+
+ const bool isDocumentAuxSandboxed =
+ doc && (doc->GetSandboxFlags() & SANDBOXED_AUXILIARY_NAVIGATION);
+
+ if (isDocumentAuxSandboxed) {
+ return NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow();
+ NS_ENSURE_TRUE(win, NS_ERROR_NOT_AVAILABLE);
+
+ RefPtr<BrowsingContext> newBC;
+ nsAutoCString spec;
+ aLoadState->URI()->GetSpec(spec);
+
+ // If we are a noopener load, we just hand the whole thing over to our
+ // window.
+ if (aLoadState->HasInternalLoadFlags(INTERNAL_LOAD_FLAGS_NO_OPENER) ||
+ NoopenerForceEnabled()) {
+ // Various asserts that we know to hold because NO_OPENER loads can only
+ // happen for links.
+ MOZ_ASSERT(!aLoadState->LoadReplace());
+ MOZ_ASSERT(aLoadState->PrincipalToInherit() ==
+ aLoadState->TriggeringPrincipal());
+ MOZ_ASSERT(!(aLoadState->InternalLoadFlags() &
+ ~(INTERNAL_LOAD_FLAGS_NO_OPENER |
+ INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER)),
+ "Only INTERNAL_LOAD_FLAGS_NO_OPENER and "
+ "INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER can be set");
+ MOZ_ASSERT_IF(aLoadState->PostDataStream(),
+ aLoadState->IsFormSubmission());
+ MOZ_ASSERT(!aLoadState->HeadersStream());
+ // If OnLinkClickSync was invoked inside the onload handler, the load
+ // type would be set to LOAD_NORMAL_REPLACE; otherwise it should be
+ // LOAD_LINK.
+ MOZ_ASSERT(aLoadState->LoadType() == LOAD_LINK ||
+ aLoadState->LoadType() == LOAD_NORMAL_REPLACE);
+ MOZ_ASSERT(!aLoadState->LoadIsFromSessionHistory());
+ MOZ_ASSERT(aLoadState->FirstParty()); // Windowwatcher will assume this.
+
+ RefPtr<nsDocShellLoadState> loadState =
+ new nsDocShellLoadState(aLoadState->URI());
+
+ // Set up our loadinfo so it will do the load as much like we would have
+ // as possible.
+ loadState->SetReferrerInfo(aLoadState->GetReferrerInfo());
+ loadState->SetOriginalURI(aLoadState->OriginalURI());
+
+ Maybe<nsCOMPtr<nsIURI>> resultPrincipalURI;
+ aLoadState->GetMaybeResultPrincipalURI(resultPrincipalURI);
+
+ loadState->SetMaybeResultPrincipalURI(resultPrincipalURI);
+ loadState->SetKeepResultPrincipalURIIfSet(
+ aLoadState->KeepResultPrincipalURIIfSet());
+ // LoadReplace will always be false due to asserts above, skip setting
+ // it.
+ loadState->SetTriggeringPrincipal(aLoadState->TriggeringPrincipal());
+ loadState->SetTriggeringSandboxFlags(
+ aLoadState->TriggeringSandboxFlags());
+ loadState->SetTriggeringWindowId(aLoadState->TriggeringWindowId());
+ loadState->SetTriggeringStorageAccess(
+ aLoadState->TriggeringStorageAccess());
+ loadState->SetCsp(aLoadState->Csp());
+ loadState->SetInheritPrincipal(aLoadState->HasInternalLoadFlags(
+ INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL));
+ // Explicit principal because we do not want any guesses as to what the
+ // principal to inherit is: it should be aTriggeringPrincipal.
+ loadState->SetPrincipalIsExplicit(true);
+ loadState->SetLoadType(aLoadState->LoadType());
+ loadState->SetForceAllowDataURI(aLoadState->HasInternalLoadFlags(
+ INTERNAL_LOAD_FLAGS_FORCE_ALLOW_DATA_URI));
+
+ loadState->SetHasValidUserGestureActivation(
+ aLoadState->HasValidUserGestureActivation());
+
+ // Propagate POST data to the new load.
+ loadState->SetPostDataStream(aLoadState->PostDataStream());
+ loadState->SetIsFormSubmission(aLoadState->IsFormSubmission());
+
+ rv = win->Open(NS_ConvertUTF8toUTF16(spec),
+ aLoadState->Target(), // window name
+ u""_ns, // Features
+ loadState,
+ true, // aForceNoOpener
+ getter_AddRefs(newBC));
+ MOZ_ASSERT(!newBC);
+ return rv;
+ }
+
+ rv = win->OpenNoNavigate(NS_ConvertUTF8toUTF16(spec),
+ aLoadState->Target(), // window name
+ u""_ns, // Features
+ getter_AddRefs(newBC));
+
+ // In some cases the Open call doesn't actually result in a new
+ // window being opened. We can detect these cases by examining the
+ // document in |newBC|, if any.
+ nsCOMPtr<nsPIDOMWindowOuter> piNewWin =
+ newBC ? newBC->GetDOMWindow() : nullptr;
+ if (piNewWin) {
+ RefPtr<Document> newDoc = piNewWin->GetExtantDoc();
+ if (!newDoc || newDoc->IsInitialDocument()) {
+ aLoadState->SetInternalLoadFlag(INTERNAL_LOAD_FLAGS_FIRST_LOAD);
+ }
+ }
+
+ if (newBC) {
+ targetContext = newBC;
+ }
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(targetContext, rv);
+
+ // If our target BrowsingContext is still pending initialization, ignore the
+ // navigation request targeting it.
+ if (NS_WARN_IF(targetContext->GetPendingInitialization())) {
+ return NS_OK;
+ }
+
+ aLoadState->SetTargetBrowsingContext(targetContext);
+ if (aLoadState->IsFormSubmission()) {
+ aLoadState->SetLoadType(
+ GetLoadTypeForFormSubmission(targetContext, aLoadState));
+ }
+
+ //
+ // Transfer the load to the target BrowsingContext... Clear the window target
+ // name to the empty string to prevent recursive retargeting!
+ //
+ // No window target
+ aLoadState->SetTarget(u""_ns);
+ // No forced download
+ aLoadState->SetFileName(VoidString());
+ return targetContext->InternalLoad(aLoadState);
+}
+
+static nsAutoCString RefMaybeNull(nsIURI* aURI) {
+ nsAutoCString result;
+ if (NS_FAILED(aURI->GetRef(result))) {
+ result.SetIsVoid(true);
+ }
+ return result;
+}
+
+uint32_t nsDocShell::GetSameDocumentNavigationFlags(nsIURI* aNewURI) {
+ uint32_t flags = LOCATION_CHANGE_SAME_DOCUMENT;
+
+ bool equal = false;
+ if (mCurrentURI &&
+ NS_SUCCEEDED(mCurrentURI->EqualsExceptRef(aNewURI, &equal)) && equal &&
+ RefMaybeNull(mCurrentURI) != RefMaybeNull(aNewURI)) {
+ flags |= LOCATION_CHANGE_HASHCHANGE;
+ }
+
+ return flags;
+}
+
+bool nsDocShell::IsSameDocumentNavigation(nsDocShellLoadState* aLoadState,
+ SameDocumentNavigationState& aState) {
+ MOZ_ASSERT(aLoadState);
+ if (!(aLoadState->LoadType() == LOAD_NORMAL ||
+ aLoadState->LoadType() == LOAD_STOP_CONTENT ||
+ LOAD_TYPE_HAS_FLAGS(aLoadState->LoadType(),
+ LOAD_FLAGS_REPLACE_HISTORY) ||
+ aLoadState->LoadType() == LOAD_HISTORY ||
+ aLoadState->LoadType() == LOAD_LINK)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> currentURI = mCurrentURI;
+
+ nsresult rvURINew = aLoadState->URI()->GetRef(aState.mNewHash);
+ if (NS_SUCCEEDED(rvURINew)) {
+ rvURINew = aLoadState->URI()->GetHasRef(&aState.mNewURIHasRef);
+ }
+
+ if (currentURI && NS_SUCCEEDED(rvURINew)) {
+ nsresult rvURIOld = currentURI->GetRef(aState.mCurrentHash);
+ if (NS_SUCCEEDED(rvURIOld)) {
+ rvURIOld = currentURI->GetHasRef(&aState.mCurrentURIHasRef);
+ }
+ if (NS_SUCCEEDED(rvURIOld)) {
+ if (NS_FAILED(currentURI->EqualsExceptRef(aLoadState->URI(),
+ &aState.mSameExceptHashes))) {
+ aState.mSameExceptHashes = false;
+ }
+ }
+ }
+
+ if (!aState.mSameExceptHashes && currentURI && NS_SUCCEEDED(rvURINew)) {
+ // Maybe aLoadState->URI() came from the exposable form of currentURI?
+ nsCOMPtr<nsIURI> currentExposableURI =
+ nsIOService::CreateExposableURI(currentURI);
+ nsresult rvURIOld = currentExposableURI->GetRef(aState.mCurrentHash);
+ if (NS_SUCCEEDED(rvURIOld)) {
+ rvURIOld = currentExposableURI->GetHasRef(&aState.mCurrentURIHasRef);
+ }
+ if (NS_SUCCEEDED(rvURIOld)) {
+ if (NS_FAILED(currentExposableURI->EqualsExceptRef(
+ aLoadState->URI(), &aState.mSameExceptHashes))) {
+ aState.mSameExceptHashes = false;
+ }
+ // HTTPS-Only Mode upgrades schemes from http to https in Necko, hence we
+ // have to perform a special check here to avoid an actual navigation. If
+ // HTTPS-Only Mode is enabled and the two URIs are same-origin (modulo the
+ // fact that the new URI is currently http), then set mSameExceptHashes to
+ // true and only perform a fragment navigation.
+ if (!aState.mSameExceptHashes) {
+ if (nsCOMPtr<nsIChannel> docChannel = GetCurrentDocChannel()) {
+ nsCOMPtr<nsILoadInfo> docLoadInfo = docChannel->LoadInfo();
+ if (!docLoadInfo->GetLoadErrorPage() &&
+ nsHTTPSOnlyUtils::IsEqualURIExceptSchemeAndRef(
+ currentExposableURI, aLoadState->URI(), docLoadInfo)) {
+ uint32_t status = docLoadInfo->GetHttpsOnlyStatus();
+ if (status & (nsILoadInfo::HTTPS_ONLY_UPGRADED_LISTENER_REGISTERED |
+ nsILoadInfo::HTTPS_ONLY_UPGRADED_HTTPS_FIRST)) {
+ // At this point the requested URI is for sure a fragment
+ // navigation via HTTP and HTTPS-Only mode or HTTPS-First is
+ // enabled. Also it is not interfering the upgrade order of
+ // https://searchfox.org/mozilla-central/source/netwerk/base/nsNetUtil.cpp#2948-2953.
+ // Since we are on an HTTPS site the fragment
+ // navigation should also be an HTTPS.
+ // For that reason we should upgrade the URI to HTTPS.
+ aState.mSecureUpgradeURI = true;
+ aState.mSameExceptHashes = true;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (mozilla::SessionHistoryInParent()) {
+ if (mActiveEntry && aLoadState->LoadIsFromSessionHistory()) {
+ aState.mHistoryNavBetweenSameDoc = mActiveEntry->SharesDocumentWith(
+ aLoadState->GetLoadingSessionHistoryInfo()->mInfo);
+ }
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("nsDocShell::IsSameDocumentNavigation %p NavBetweenSameDoc=%d",
+ this, aState.mHistoryNavBetweenSameDoc));
+ } else {
+ if (mOSHE && aLoadState->LoadIsFromSessionHistory()) {
+ // We're doing a history load.
+
+ mOSHE->SharesDocumentWith(aLoadState->SHEntry(),
+ &aState.mHistoryNavBetweenSameDoc);
+ }
+ }
+
+ // A same document navigation happens when we navigate between two SHEntries
+ // for the same document. We do a same document navigation under two
+ // circumstances. Either
+ //
+ // a) we're navigating between two different SHEntries which share a
+ // document, or
+ //
+ // b) we're navigating to a new shentry whose URI differs from the
+ // current URI only in its hash, the new hash is non-empty, and
+ // we're not doing a POST.
+ //
+ // The restriction that the SHEntries in (a) must be different ensures
+ // that history.go(0) and the like trigger full refreshes, rather than
+ // same document navigations.
+ if (!mozilla::SessionHistoryInParent()) {
+ bool doSameDocumentNavigation =
+ (aState.mHistoryNavBetweenSameDoc && mOSHE != aLoadState->SHEntry()) ||
+ (!aLoadState->SHEntry() && !aLoadState->PostDataStream() &&
+ aState.mSameExceptHashes && aState.mNewURIHasRef);
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("nsDocShell %p NavBetweenSameDoc=%d is same doc = %d", this,
+ aState.mHistoryNavBetweenSameDoc, doSameDocumentNavigation));
+ return doSameDocumentNavigation;
+ }
+
+ if (aState.mHistoryNavBetweenSameDoc &&
+ !aLoadState->GetLoadingSessionHistoryInfo()->mLoadingCurrentEntry) {
+ return true;
+ }
+
+ MOZ_LOG(
+ gSHLog, LogLevel::Debug,
+ ("nsDocShell::IsSameDocumentNavigation %p !LoadIsFromSessionHistory=%s "
+ "!PostDataStream: %s mSameExceptHashes: %s mNewURIHasRef: %s",
+ this, !aLoadState->LoadIsFromSessionHistory() ? "true" : "false",
+ !aLoadState->PostDataStream() ? "true" : "false",
+ aState.mSameExceptHashes ? "true" : "false",
+ aState.mNewURIHasRef ? "true" : "false"));
+ return !aLoadState->LoadIsFromSessionHistory() &&
+ !aLoadState->PostDataStream() && aState.mSameExceptHashes &&
+ aState.mNewURIHasRef;
+}
+
+nsresult nsDocShell::HandleSameDocumentNavigation(
+ nsDocShellLoadState* aLoadState, SameDocumentNavigationState& aState,
+ bool& aSameDocument) {
+ aSameDocument = true;
+#ifdef DEBUG
+ SameDocumentNavigationState state;
+ MOZ_ASSERT(IsSameDocumentNavigation(aLoadState, state));
+#endif
+
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("nsDocShell::HandleSameDocumentNavigation %p %s -> %s", this,
+ mCurrentURI->GetSpecOrDefault().get(),
+ aLoadState->URI()->GetSpecOrDefault().get()));
+
+ RefPtr<Document> doc = GetDocument();
+ NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+ doc->DoNotifyPossibleTitleChange();
+
+ nsCOMPtr<nsIURI> currentURI = mCurrentURI;
+
+ // We need to upgrade the new URI from http: to https:
+ nsCOMPtr<nsIURI> newURI = aLoadState->URI();
+ if (aState.mSecureUpgradeURI) {
+ MOZ_TRY(NS_GetSecureUpgradedURI(aLoadState->URI(), getter_AddRefs(newURI)));
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("Upgraded URI to %s", newURI->GetSpecOrDefault().get()));
+ }
+
+ if (StaticPrefs::dom_security_setdocumenturi()) {
+ // check if aLoadState->URI(), principalURI, mCurrentURI are same origin
+ // skip handling otherwise
+ nsCOMPtr<nsIPrincipal> origPrincipal = doc->NodePrincipal();
+ nsCOMPtr<nsIURI> principalURI = origPrincipal->GetURI();
+ if (origPrincipal->GetIsNullPrincipal()) {
+ nsCOMPtr<nsIPrincipal> precursor = origPrincipal->GetPrecursorPrincipal();
+ if (precursor) {
+ principalURI = precursor->GetURI();
+ }
+ }
+
+ auto isLoadableViaInternet = [](nsIURI* uri) {
+ return (uri && (net::SchemeIsHTTP(uri) || net::SchemeIsHTTPS(uri)));
+ };
+
+ if (isLoadableViaInternet(principalURI) &&
+ isLoadableViaInternet(mCurrentURI) && isLoadableViaInternet(newURI)) {
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ if (!NS_SUCCEEDED(
+ ssm->CheckSameOriginURI(newURI, principalURI, false, false)) ||
+ !NS_SUCCEEDED(ssm->CheckSameOriginURI(mCurrentURI, principalURI,
+ false, false))) {
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("nsDocShell[%p]: possible violation of the same origin policy "
+ "during same document navigation",
+ this));
+ aSameDocument = false;
+ return NS_OK;
+ }
+ }
+ }
+
+#ifdef DEBUG
+ if (aState.mSameExceptHashes) {
+ bool sameExceptHashes = false;
+ currentURI->EqualsExceptRef(newURI, &sameExceptHashes);
+ MOZ_ASSERT(sameExceptHashes);
+ }
+#endif
+
+ // Save the position of the scrollers.
+ nsPoint scrollPos = GetCurScrollPos();
+
+ // Reset mLoadType to its original value once we exit this block, because this
+ // same document navigation might have started after a normal, network load,
+ // and we don't want to clobber its load type. See bug 737307.
+ AutoRestore<uint32_t> loadTypeResetter(mLoadType);
+
+ // If a non-same-document-navigation (i.e., a network load) is pending, make
+ // this a replacement load, so that we don't add a SHEntry here and the
+ // network load goes into the SHEntry it expects to.
+ if (JustStartedNetworkLoad() && (aLoadState->LoadType() & LOAD_CMD_NORMAL)) {
+ mLoadType = LOAD_NORMAL_REPLACE;
+ } else {
+ mLoadType = aLoadState->LoadType();
+ }
+
+ mURIResultedInDocument = true;
+
+ nsCOMPtr<nsISHEntry> oldLSHE = mLSHE;
+
+ // we need to assign aLoadState->SHEntry() to mLSHE right here, so that on
+ // History loads, SetCurrentURI() called from OnNewURI() will send proper
+ // onLocationChange() notifications to the browser to update back/forward
+ // buttons.
+ SetHistoryEntryAndUpdateBC(Some<nsISHEntry*>(aLoadState->SHEntry()),
+ Nothing());
+ UniquePtr<mozilla::dom::LoadingSessionHistoryInfo> oldLoadingEntry;
+ mLoadingEntry.swap(oldLoadingEntry);
+ if (aLoadState->GetLoadingSessionHistoryInfo()) {
+ mLoadingEntry = MakeUnique<LoadingSessionHistoryInfo>(
+ *aLoadState->GetLoadingSessionHistoryInfo());
+ mNeedToReportActiveAfterLoadingBecomesActive = false;
+ }
+
+ // Set the doc's URI according to the new history entry's URI.
+ doc->SetDocumentURI(newURI);
+
+ /* This is a anchor traversal within the same page.
+ * call OnNewURI() so that, this traversal will be
+ * recorded in session and global history.
+ */
+ nsCOMPtr<nsIPrincipal> newURITriggeringPrincipal, newURIPrincipalToInherit,
+ newURIPartitionedPrincipalToInherit;
+ nsCOMPtr<nsIContentSecurityPolicy> newCsp;
+ if (mozilla::SessionHistoryInParent() ? !!mActiveEntry : !!mOSHE) {
+ if (mozilla::SessionHistoryInParent()) {
+ newURITriggeringPrincipal = mActiveEntry->GetTriggeringPrincipal();
+ newURIPrincipalToInherit = mActiveEntry->GetPrincipalToInherit();
+ newURIPartitionedPrincipalToInherit =
+ mActiveEntry->GetPartitionedPrincipalToInherit();
+ newCsp = mActiveEntry->GetCsp();
+ } else {
+ newURITriggeringPrincipal = mOSHE->GetTriggeringPrincipal();
+ newURIPrincipalToInherit = mOSHE->GetPrincipalToInherit();
+ newURIPartitionedPrincipalToInherit =
+ mOSHE->GetPartitionedPrincipalToInherit();
+ newCsp = mOSHE->GetCsp();
+ }
+ } else {
+ newURITriggeringPrincipal = aLoadState->TriggeringPrincipal();
+ newURIPrincipalToInherit = doc->NodePrincipal();
+ newURIPartitionedPrincipalToInherit = doc->PartitionedPrincipal();
+ newCsp = doc->GetCsp();
+ }
+
+ uint32_t locationChangeFlags = GetSameDocumentNavigationFlags(newURI);
+
+ // Pass true for aCloneSHChildren, since we're not
+ // changing documents here, so all of our subframes are
+ // still relevant to the new session history entry.
+ //
+ // It also makes OnNewURI(...) set LOCATION_CHANGE_SAME_DOCUMENT
+ // flag on firing onLocationChange(...).
+ // Anyway, aCloneSHChildren param is simply reflecting
+ // doSameDocumentNavigation in this scope.
+ //
+ // Note: we'll actually fire onLocationChange later, in order to preserve
+ // ordering of HistoryCommit() in the parent vs onLocationChange (bug
+ // 1668126)
+ bool locationChangeNeeded = OnNewURI(
+ newURI, nullptr, newURITriggeringPrincipal, newURIPrincipalToInherit,
+ newURIPartitionedPrincipalToInherit, newCsp, true, true);
+
+ nsCOMPtr<nsIInputStream> postData;
+ nsCOMPtr<nsIReferrerInfo> referrerInfo;
+ uint32_t cacheKey = 0;
+
+ bool scrollRestorationIsManual = false;
+ if (!mozilla::SessionHistoryInParent()) {
+ if (mOSHE) {
+ /* save current position of scroller(s) (bug 59774) */
+ mOSHE->SetScrollPosition(scrollPos.x, scrollPos.y);
+ scrollRestorationIsManual = mOSHE->GetScrollRestorationIsManual();
+ // Get the postdata, page ident and referrer info from the current page,
+ // if the new load is being done via normal means. Note that "normal
+ // means" can be checked for just by checking for LOAD_CMD_NORMAL, given
+ // the loadType and allowScroll check above -- it filters out some
+ // LOAD_CMD_NORMAL cases that we wouldn't want here.
+ if (aLoadState->LoadType() & LOAD_CMD_NORMAL) {
+ postData = mOSHE->GetPostData();
+ cacheKey = mOSHE->GetCacheKey();
+ referrerInfo = mOSHE->GetReferrerInfo();
+ }
+
+ // Link our new SHEntry to the old SHEntry's back/forward
+ // cache data, since the two SHEntries correspond to the
+ // same document.
+ if (mLSHE) {
+ if (!aLoadState->LoadIsFromSessionHistory()) {
+ // If we're not doing a history load, scroll restoration
+ // should be inherited from the previous session history entry.
+ SetScrollRestorationIsManualOnHistoryEntry(mLSHE,
+ scrollRestorationIsManual);
+ }
+ mLSHE->AdoptBFCacheEntry(mOSHE);
+ }
+ }
+ } else {
+ if (mActiveEntry) {
+ mActiveEntry->SetScrollPosition(scrollPos.x, scrollPos.y);
+ if (mBrowsingContext) {
+ CollectWireframe();
+ if (XRE_IsParentProcess()) {
+ SessionHistoryEntry* entry =
+ mBrowsingContext->Canonical()->GetActiveSessionHistoryEntry();
+ if (entry) {
+ entry->SetScrollPosition(scrollPos.x, scrollPos.y);
+ }
+ } else {
+ mozilla::Unused << ContentChild::GetSingleton()
+ ->SendSessionHistoryEntryScrollPosition(
+ mBrowsingContext, scrollPos.x,
+ scrollPos.y);
+ }
+ }
+ }
+ if (mLoadingEntry) {
+ if (!mLoadingEntry->mLoadIsFromSessionHistory) {
+ // If we're not doing a history load, scroll restoration
+ // should be inherited from the previous session history entry.
+ // XXX This needs most probably tweaks once fragment navigation is
+ // fixed to work with session-history-in-parent.
+ SetScrollRestorationIsManualOnHistoryEntry(nullptr,
+ scrollRestorationIsManual);
+ }
+ }
+ }
+
+ // If we're doing a history load, use its scroll restoration state.
+ if (aLoadState->LoadIsFromSessionHistory()) {
+ if (mozilla::SessionHistoryInParent()) {
+ scrollRestorationIsManual = aLoadState->GetLoadingSessionHistoryInfo()
+ ->mInfo.GetScrollRestorationIsManual();
+ } else {
+ scrollRestorationIsManual =
+ aLoadState->SHEntry()->GetScrollRestorationIsManual();
+ }
+ }
+
+ /* Assign mLSHE to mOSHE. This will either be a new entry created
+ * by OnNewURI() for normal loads or aLoadState->SHEntry() for history
+ * loads.
+ */
+ if (!mozilla::SessionHistoryInParent()) {
+ if (mLSHE) {
+ SetHistoryEntryAndUpdateBC(Nothing(), Some<nsISHEntry*>(mLSHE));
+ // Save the postData obtained from the previous page
+ // in to the session history entry created for the
+ // anchor page, so that any history load of the anchor
+ // page will restore the appropriate postData.
+ if (postData) {
+ mOSHE->SetPostData(postData);
+ }
+
+ // Make sure we won't just repost without hitting the
+ // cache first
+ if (cacheKey != 0) {
+ mOSHE->SetCacheKey(cacheKey);
+ }
+
+ // As the document has not changed, the referrer info hasn't changed too,
+ // so we can just copy it over.
+ if (referrerInfo) {
+ mOSHE->SetReferrerInfo(referrerInfo);
+ }
+ }
+
+ /* Set the title for the SH entry for this target url so that
+ * SH menus in go/back/forward buttons won't be empty for this.
+ * Note, this happens on mOSHE (and mActiveEntry in the future) because of
+ * the code above.
+ * Note, when session history lives in the parent process, this does not
+ * update the title there.
+ */
+ SetTitleOnHistoryEntry(false);
+ } else {
+ if (aLoadState->LoadIsFromSessionHistory()) {
+ MOZ_LOG(
+ gSHLog, LogLevel::Debug,
+ ("Moving the loading entry to the active entry on nsDocShell %p to "
+ "%s",
+ this, mLoadingEntry->mInfo.GetURI()->GetSpecOrDefault().get()));
+
+ nsCOMPtr<nsILayoutHistoryState> currentLayoutHistoryState;
+ if (mActiveEntry) {
+ currentLayoutHistoryState = mActiveEntry->GetLayoutHistoryState();
+ }
+
+ UniquePtr<SessionHistoryInfo> previousActiveEntry(mActiveEntry.release());
+ mActiveEntry = MakeUnique<SessionHistoryInfo>(mLoadingEntry->mInfo);
+ if (currentLayoutHistoryState) {
+ // Restore the existing nsILayoutHistoryState object, since it is
+ // possibly being used by the layout. When doing a new load, the
+ // shared state is copied from the existing active entry, so this
+ // special case is needed only with the history loads.
+ mActiveEntry->SetLayoutHistoryState(currentLayoutHistoryState);
+ }
+
+ if (cacheKey != 0) {
+ mActiveEntry->SetCacheKey(cacheKey);
+ }
+ // We're passing in mCurrentURI, which could be null. SessionHistoryCommit
+ // does require a non-null uri if this is for a refresh load of the same
+ // URI, but in that case mCurrentURI won't be null here.
+ mBrowsingContext->SessionHistoryCommit(
+ *mLoadingEntry, mLoadType, mCurrentURI, previousActiveEntry.get(),
+ true, true,
+ /* No expiration update on the same document loads*/
+ false, cacheKey);
+ // FIXME Need to set postdata.
+
+ // Set the title for the SH entry for this target url so that
+ // SH menus in go/back/forward buttons won't be empty for this.
+ // Note, when session history lives in the parent process, this does not
+ // update the title there.
+ SetTitleOnHistoryEntry(false);
+ } else {
+ Maybe<bool> scrollRestorationIsManual;
+ if (mActiveEntry) {
+ scrollRestorationIsManual.emplace(
+ mActiveEntry->GetScrollRestorationIsManual());
+
+ // Get the postdata, page ident and referrer info from the current page,
+ // if the new load is being done via normal means. Note that "normal
+ // means" can be checked for just by checking for LOAD_CMD_NORMAL, given
+ // the loadType and allowScroll check above -- it filters out some
+ // LOAD_CMD_NORMAL cases that we wouldn't want here.
+ if (aLoadState->LoadType() & LOAD_CMD_NORMAL) {
+ postData = mActiveEntry->GetPostData();
+ cacheKey = mActiveEntry->GetCacheKey();
+ referrerInfo = mActiveEntry->GetReferrerInfo();
+ }
+ }
+
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("Creating an active entry on nsDocShell %p to %s", this,
+ newURI->GetSpecOrDefault().get()));
+ if (mActiveEntry) {
+ mActiveEntry = MakeUnique<SessionHistoryInfo>(*mActiveEntry, newURI);
+ } else {
+ mActiveEntry = MakeUnique<SessionHistoryInfo>(
+ newURI, newURITriggeringPrincipal, newURIPrincipalToInherit,
+ newURIPartitionedPrincipalToInherit, newCsp, mContentTypeHint);
+ }
+
+ // Save the postData obtained from the previous page in to the session
+ // history entry created for the anchor page, so that any history load of
+ // the anchor page will restore the appropriate postData.
+ if (postData) {
+ mActiveEntry->SetPostData(postData);
+ }
+
+ // Make sure we won't just repost without hitting the
+ // cache first
+ if (cacheKey != 0) {
+ mActiveEntry->SetCacheKey(cacheKey);
+ }
+
+ // As the document has not changed, the referrer info hasn't changed too,
+ // so we can just copy it over.
+ if (referrerInfo) {
+ mActiveEntry->SetReferrerInfo(referrerInfo);
+ }
+
+ // Set the title for the SH entry for this target url so that
+ // SH menus in go/back/forward buttons won't be empty for this.
+ mActiveEntry->SetTitle(mTitle);
+
+ if (scrollRestorationIsManual.isSome()) {
+ mActiveEntry->SetScrollRestorationIsManual(
+ scrollRestorationIsManual.value());
+ }
+
+ if (LOAD_TYPE_HAS_FLAGS(mLoadType, LOAD_FLAGS_REPLACE_HISTORY)) {
+ mBrowsingContext->ReplaceActiveSessionHistoryEntry(mActiveEntry.get());
+ } else {
+ mBrowsingContext->IncrementHistoryEntryCountForBrowsingContext();
+ // FIXME We should probably just compute mChildOffset in the parent
+ // instead of passing it over IPC here.
+ mBrowsingContext->SetActiveSessionHistoryEntry(
+ Some(scrollPos), mActiveEntry.get(), mLoadType, cacheKey);
+ // FIXME Do we need to update mPreviousEntryIndex and mLoadedEntryIndex?
+ }
+ }
+ }
+
+ if (locationChangeNeeded) {
+ FireOnLocationChange(this, nullptr, newURI, locationChangeFlags);
+ }
+
+ /* Restore the original LSHE if we were loading something
+ * while same document navigation was initiated.
+ */
+ SetHistoryEntryAndUpdateBC(Some<nsISHEntry*>(oldLSHE), Nothing());
+ mLoadingEntry.swap(oldLoadingEntry);
+
+ /* Set the title for the Global History entry for this anchor url.
+ */
+ UpdateGlobalHistoryTitle(newURI);
+
+ SetDocCurrentStateObj(mOSHE, mActiveEntry.get());
+
+ // Inform the favicon service that the favicon for oldURI also
+ // applies to newURI.
+ CopyFavicon(currentURI, newURI, UsePrivateBrowsing());
+
+ RefPtr<nsGlobalWindowOuter> scriptGlobal = mScriptGlobal;
+ nsCOMPtr<nsPIDOMWindowInner> win =
+ scriptGlobal ? scriptGlobal->GetCurrentInnerWindow() : nullptr;
+
+ // ScrollToAnchor doesn't necessarily cause us to scroll the window;
+ // the function decides whether a scroll is appropriate based on the
+ // arguments it receives. But even if we don't end up scrolling,
+ // ScrollToAnchor performs other important tasks, such as informing
+ // the presShell that we have a new hash. See bug 680257.
+ nsresult rv = ScrollToAnchor(aState.mCurrentURIHasRef, aState.mNewURIHasRef,
+ aState.mNewHash, aLoadState->LoadType());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ /* restore previous position of scroller(s), if we're moving
+ * back in history (bug 59774)
+ */
+ nscoord bx = 0;
+ nscoord by = 0;
+ bool needsScrollPosUpdate = false;
+ if ((mozilla::SessionHistoryInParent() ? !!mActiveEntry : !!mOSHE) &&
+ (aLoadState->LoadType() == LOAD_HISTORY ||
+ aLoadState->LoadType() == LOAD_RELOAD_NORMAL) &&
+ !scrollRestorationIsManual) {
+ needsScrollPosUpdate = true;
+ if (mozilla::SessionHistoryInParent()) {
+ mActiveEntry->GetScrollPosition(&bx, &by);
+ } else {
+ mOSHE->GetScrollPosition(&bx, &by);
+ }
+ }
+
+ // Dispatch the popstate and hashchange events, as appropriate.
+ //
+ // The event dispatch below can cause us to re-enter script and
+ // destroy the docshell, nulling out mScriptGlobal. Hold a stack
+ // reference to avoid null derefs. See bug 914521.
+ if (win) {
+ // Fire a hashchange event URIs differ, and only in their hashes.
+ bool doHashchange = aState.mSameExceptHashes &&
+ (aState.mCurrentURIHasRef != aState.mNewURIHasRef ||
+ !aState.mCurrentHash.Equals(aState.mNewHash));
+
+ if (aState.mHistoryNavBetweenSameDoc || doHashchange) {
+ win->DispatchSyncPopState();
+ }
+
+ if (needsScrollPosUpdate && win->HasActiveDocument()) {
+ SetCurScrollPosEx(bx, by);
+ }
+
+ if (doHashchange) {
+ // Note that currentURI hasn't changed because it's on the
+ // stack, so we can just use it directly as the old URI.
+ win->DispatchAsyncHashchange(currentURI, newURI);
+ }
+ }
+
+ return NS_OK;
+}
+
+static bool NavigationShouldTakeFocus(nsDocShell* aDocShell,
+ nsDocShellLoadState* aLoadState) {
+ if (!aLoadState->AllowFocusMove()) {
+ return false;
+ }
+ if (!aLoadState->HasValidUserGestureActivation()) {
+ return false;
+ }
+ const auto& sourceBC = aLoadState->SourceBrowsingContext();
+ if (!sourceBC || !sourceBC->IsActive()) {
+ // If the navigation didn't come from a foreground tab, then we don't steal
+ // focus.
+ return false;
+ }
+ auto* bc = aDocShell->GetBrowsingContext();
+ if (sourceBC.get() == bc) {
+ // If it comes from the same tab / frame, don't steal focus either.
+ return false;
+ }
+ auto* fm = nsFocusManager::GetFocusManager();
+ if (fm && bc->IsActive() && fm->IsInActiveWindow(bc)) {
+ // If we're already on the foreground tab of the foreground window, then we
+ // don't need to do this. This helps to e.g. not steal focus from the
+ // browser chrome unnecessarily.
+ return false;
+ }
+ if (auto* doc = aDocShell->GetExtantDocument()) {
+ if (doc->IsInitialDocument()) {
+ // If we're the initial load for the browsing context, the browser
+ // chrome determines what to focus. This is important because the
+ // browser chrome may want to e.g focus the url-bar
+ return false;
+ }
+ }
+ // Take loadDivertedInBackground into account so the behavior would be the
+ // same as how the tab first opened.
+ return !Preferences::GetBool("browser.tabs.loadDivertedInBackground", false);
+}
+
+uint32_t nsDocShell::GetLoadTypeForFormSubmission(
+ BrowsingContext* aTargetBC, nsDocShellLoadState* aLoadState) {
+ MOZ_ASSERT(aLoadState->IsFormSubmission());
+
+ // https://html.spec.whatwg.org/#form-submission-algorithm
+ // 22. Let historyHandling be "push".
+ // 23. If form document equals targetNavigable's active document, and
+ // form document has not yet completely loaded, then set
+ // historyHandling to "replace".
+ return GetBrowsingContext() == aTargetBC && !mEODForCurrentDocument
+ ? LOAD_NORMAL_REPLACE
+ : LOAD_LINK;
+}
+
+nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState,
+ Maybe<uint32_t> aCacheKey) {
+ MOZ_ASSERT(aLoadState, "need a load state!");
+ MOZ_ASSERT(aLoadState->TriggeringPrincipal(),
+ "need a valid TriggeringPrincipal");
+
+ if (!aLoadState->TriggeringPrincipal()) {
+ MOZ_ASSERT(false, "InternalLoad needs a valid triggeringPrincipal");
+ return NS_ERROR_FAILURE;
+ }
+ if (NS_WARN_IF(mBrowsingContext->GetPendingInitialization())) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ const bool shouldTakeFocus = NavigationShouldTakeFocus(this, aLoadState);
+
+ mOriginalUriString.Truncate();
+
+ MOZ_LOG(gDocShellLeakLog, LogLevel::Debug,
+ ("DOCSHELL %p InternalLoad %s\n", this,
+ aLoadState->URI()->GetSpecOrDefault().get()));
+
+ NS_ENSURE_TRUE(IsValidLoadType(aLoadState->LoadType()), NS_ERROR_INVALID_ARG);
+
+ // Cancel loads coming from Docshells that are being destroyed.
+ if (mIsBeingDestroyed) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv = EnsureScriptEnvironment();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // If we have a target to move to, do that now.
+ if (!aLoadState->Target().IsEmpty()) {
+ return PerformRetargeting(aLoadState);
+ }
+
+ // This is the non-retargeting load path, we've already set the right loadtype
+ // for form submissions in nsDocShell::OnLinkClickSync.
+ if (aLoadState->TargetBrowsingContext().IsNull()) {
+ aLoadState->SetTargetBrowsingContext(GetBrowsingContext());
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(
+ aLoadState->TargetBrowsingContext() == GetBrowsingContext(),
+ "Load must be targeting this BrowsingContext");
+
+ MOZ_TRY(CheckDisallowedJavascriptLoad(aLoadState));
+
+ // If we don't have a target, we're loading into ourselves, and our load
+ // delegate may want to intercept that load.
+ SameDocumentNavigationState sameDocumentNavigationState;
+ bool sameDocument =
+ IsSameDocumentNavigation(aLoadState, sameDocumentNavigationState) &&
+ !aLoadState->GetPendingRedirectedChannel();
+
+ // Note: We do this check both here and in BrowsingContext::
+ // LoadURI/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(mBrowsingContext->CheckSandboxFlags(aLoadState));
+
+ NS_ENSURE_STATE(!HasUnloadedParent());
+
+ rv = CheckLoadingPermissions();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (mFiredUnloadEvent) {
+ if (IsOKToLoadURI(aLoadState->URI())) {
+ MOZ_ASSERT(aLoadState->Target().IsEmpty(),
+ "Shouldn't have a window target here!");
+
+ // If this is a replace load, make whatever load triggered
+ // the unload event also a replace load, so we don't
+ // create extra history entries.
+ if (LOAD_TYPE_HAS_FLAGS(aLoadState->LoadType(),
+ LOAD_FLAGS_REPLACE_HISTORY)) {
+ mLoadType = LOAD_NORMAL_REPLACE;
+ }
+
+ // Do this asynchronously
+ nsCOMPtr<nsIRunnable> ev = new InternalLoadEvent(this, aLoadState);
+ return Dispatch(ev.forget());
+ }
+
+ // Just ignore this load attempt
+ return NS_OK;
+ }
+
+ // If we are loading a URI that should inherit a security context (basically
+ // javascript: at this point), and the caller has said that principal
+ // inheritance is allowed, there are a few possible cases:
+ //
+ // 1) We are provided with the principal to inherit. In that case, we just use
+ // it.
+ //
+ // 2) The load is coming from some other application. In this case we don't
+ // want to inherit from whatever document we have loaded now, since the
+ // load is unrelated to it.
+ //
+ // 3) It's a load from our application, but does not provide an explicit
+ // principal to inherit. In that case, we want to inherit the principal of
+ // our current document, or of our parent document (if any) if we don't
+ // have a current document.
+ {
+ bool inherits;
+
+ if (!aLoadState->HasLoadFlags(LOAD_FLAGS_FROM_EXTERNAL) &&
+ !aLoadState->PrincipalToInherit() &&
+ (aLoadState->HasInternalLoadFlags(
+ INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL)) &&
+ NS_SUCCEEDED(nsContentUtils::URIInheritsSecurityContext(
+ aLoadState->URI(), &inherits)) &&
+ inherits) {
+ aLoadState->SetPrincipalToInherit(GetInheritedPrincipal(true));
+ }
+ // If principalToInherit is still null (e.g. if some of the conditions of
+ // were not satisfied), then no inheritance of any sort will happen: the
+ // load will just get a principal based on the URI being loaded.
+ }
+
+ // If this docshell is owned by a frameloader, make sure to cancel
+ // possible frameloader initialization before loading a new page.
+ nsCOMPtr<nsIDocShellTreeItem> parent = GetInProcessParentDocshell();
+ if (parent) {
+ RefPtr<Document> doc = parent->GetDocument();
+ if (doc) {
+ doc->TryCancelFrameLoaderInitialization(this);
+ }
+ }
+
+ // Before going any further vet loads initiated by external programs.
+ if (aLoadState->HasLoadFlags(LOAD_FLAGS_FROM_EXTERNAL)) {
+ MOZ_DIAGNOSTIC_ASSERT(aLoadState->LoadType() == LOAD_NORMAL);
+
+ // Disallow external chrome: loads targetted at content windows
+ if (SchemeIsChrome(aLoadState->URI())) {
+ NS_WARNING("blocked external chrome: url -- use '--chrome' option");
+ return NS_ERROR_FAILURE;
+ }
+
+ // clear the decks to prevent context bleed-through (bug 298255)
+ rv = CreateAboutBlankDocumentViewer(nullptr, nullptr, nullptr, nullptr,
+ /* aIsInitialDocument */ false);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ mAllowKeywordFixup = aLoadState->HasInternalLoadFlags(
+ INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP);
+ mURIResultedInDocument = false; // reset the clock...
+
+ // See if this is actually a load between two history entries for the same
+ // document. If the process fails, or if we successfully navigate within the
+ // same document, return.
+ if (sameDocument) {
+ nsresult rv = HandleSameDocumentNavigation(
+ aLoadState, sameDocumentNavigationState, sameDocument);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (shouldTakeFocus) {
+ mBrowsingContext->Focus(CallerType::System, IgnoreErrors());
+ }
+ if (sameDocument) {
+ return rv;
+ }
+ }
+
+ // mDocumentViewer->PermitUnload can destroy |this| docShell, which
+ // causes the next call of CanSavePresentation to crash.
+ // Hold onto |this| until we return, to prevent a crash from happening.
+ // (bug#331040)
+ nsCOMPtr<nsIDocShell> kungFuDeathGrip(this);
+
+ // Don't init timing for javascript:, since it generally doesn't
+ // actually start a load or anything. If it does, we'll init
+ // timing then, from OnStateChange.
+
+ // XXXbz mTiming should know what channel it's for, so we don't
+ // need this hackery.
+ bool toBeReset = false;
+ bool isJavaScript = SchemeIsJavascript(aLoadState->URI());
+
+ if (!isJavaScript) {
+ toBeReset = MaybeInitTiming();
+ }
+ bool isNotDownload = aLoadState->FileName().IsVoid();
+ if (mTiming && isNotDownload) {
+ mTiming->NotifyBeforeUnload();
+ }
+ // Check if the page doesn't want to be unloaded. The javascript:
+ // protocol handler deals with this for javascript: URLs.
+ if (!isJavaScript && isNotDownload &&
+ !aLoadState->NotifiedBeforeUnloadListeners() && mDocumentViewer) {
+ bool okToUnload;
+
+ // Check if request is exempted from HTTPSOnlyMode and if https-first is
+ // enabled, if so it means:
+ // * https-first failed to upgrade request to https
+ // * we already asked for permission to unload and the user accepted
+ // otherwise we wouldn't be here.
+ bool isPrivateWin = GetOriginAttributes().mPrivateBrowsingId > 0;
+ bool isHistoryOrReload = false;
+ uint32_t loadType = aLoadState->LoadType();
+
+ // Check if request is a reload.
+ if (loadType == LOAD_RELOAD_NORMAL ||
+ loadType == LOAD_RELOAD_BYPASS_CACHE ||
+ loadType == LOAD_RELOAD_BYPASS_PROXY ||
+ loadType == LOAD_RELOAD_BYPASS_PROXY_AND_CACHE ||
+ loadType == LOAD_HISTORY) {
+ isHistoryOrReload = true;
+ }
+
+ // If it isn't a reload, the request already failed to be upgraded and
+ // https-first is enabled then don't ask the user again for permission to
+ // unload and just unload.
+ if (!isHistoryOrReload && aLoadState->IsExemptFromHTTPSFirstMode() &&
+ nsHTTPSOnlyUtils::IsHttpsFirstModeEnabled(isPrivateWin)) {
+ rv = mDocumentViewer->PermitUnload(
+ nsIDocumentViewer::PermitUnloadAction::eDontPromptAndUnload,
+ &okToUnload);
+ } else {
+ rv = mDocumentViewer->PermitUnload(&okToUnload);
+ }
+
+ if (NS_SUCCEEDED(rv) && !okToUnload) {
+ // The user chose not to unload the page, interrupt the
+ // load.
+ MaybeResetInitTiming(toBeReset);
+ return NS_OK;
+ }
+ }
+
+ if (mTiming && isNotDownload) {
+ mTiming->NotifyUnloadAccepted(mCurrentURI);
+ }
+
+ // In e10s, in the parent process, we refuse to load anything other than
+ // "safe" resources that we ship or trust enough to give "special" URLs.
+ // Similar check will be performed by the ParentProcessDocumentChannel if in
+ // use.
+ if (XRE_IsE10sParentProcess() &&
+ !DocumentChannel::CanUseDocumentChannel(aLoadState->URI()) &&
+ !CanLoadInParentProcess(aLoadState->URI())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Whenever a top-level browsing context is navigated, the user agent MUST
+ // lock the orientation of the document to the document's default
+ // orientation. We don't explicitly check for a top-level browsing context
+ // here because orientation is only set on top-level browsing contexts.
+ if (mBrowsingContext->GetOrientationLock() != hal::ScreenOrientation::None) {
+ MOZ_ASSERT(mBrowsingContext->IsTop());
+ MOZ_ALWAYS_SUCCEEDS(
+ mBrowsingContext->SetOrientationLock(hal::ScreenOrientation::None));
+ if (mBrowsingContext->IsActive()) {
+ ScreenOrientation::UpdateActiveOrientationLock(
+ hal::ScreenOrientation::None);
+ }
+ }
+
+ // Check for saving the presentation here, before calling Stop().
+ // This is necessary so that we can catch any pending requests.
+ // Since the new request has not been created yet, we pass null for the
+ // new request parameter.
+ // Also pass nullptr for the document, since it doesn't affect the return
+ // value for our purposes here.
+ bool savePresentation =
+ CanSavePresentation(aLoadState->LoadType(), nullptr, nullptr,
+ /* aReportBFCacheComboTelemetry */ true);
+
+ // nsDocShell::CanSavePresentation is for non-SHIP version only. Do a
+ // separate check for SHIP so that we know if there are ongoing requests
+ // before calling Stop() below.
+ if (mozilla::SessionHistoryInParent()) {
+ Document* document = GetDocument();
+ uint32_t flags = 0;
+ if (document && !document->CanSavePresentation(nullptr, flags, true)) {
+ // This forces some flags into the WindowGlobalParent's mBFCacheStatus,
+ // which we'll then use in CanonicalBrowsingContext::AllowedInBFCache,
+ // and in particular we'll store BFCacheStatus::REQUEST if needed.
+ // Also, we want to report all the flags to the parent process here (and
+ // not just BFCacheStatus::NOT_ALLOWED), so that it can update the
+ // telemetry data correctly.
+ document->DisallowBFCaching(flags);
+ }
+ }
+
+ // Don't stop current network activity for javascript: URL's since
+ // they might not result in any data, and thus nothing should be
+ // stopped in those cases. In the case where they do result in
+ // data, the javascript: URL channel takes care of stopping
+ // current network activity.
+ if (!isJavaScript && isNotDownload) {
+ // Stop any current network activity.
+ // Also stop content if this is a zombie doc. otherwise
+ // the onload will be delayed by other loads initiated in the
+ // background by the first document that
+ // didn't fully load before the next load was initiated.
+ // If not a zombie, don't stop content until data
+ // starts arriving from the new URI...
+
+ if ((mDocumentViewer && mDocumentViewer->GetPreviousViewer()) ||
+ LOAD_TYPE_HAS_FLAGS(aLoadState->LoadType(), LOAD_FLAGS_STOP_CONTENT)) {
+ rv = Stop(nsIWebNavigation::STOP_ALL);
+ } else {
+ rv = Stop(nsIWebNavigation::STOP_NETWORK);
+ }
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ mLoadType = aLoadState->LoadType();
+
+ // aLoadState->SHEntry() should be assigned to mLSHE, only after Stop() has
+ // been called. But when loading an error page, do not clear the
+ // mLSHE for the real page.
+ if (mLoadType != LOAD_ERROR_PAGE) {
+ SetHistoryEntryAndUpdateBC(Some<nsISHEntry*>(aLoadState->SHEntry()),
+ Nothing());
+ if (aLoadState->LoadIsFromSessionHistory() &&
+ !mozilla::SessionHistoryInParent()) {
+ // We're making history navigation or a reload. Make sure our history ID
+ // points to the same ID as SHEntry's docshell ID.
+ nsID historyID = {};
+ aLoadState->SHEntry()->GetDocshellID(historyID);
+
+ Unused << mBrowsingContext->SetHistoryID(historyID);
+ }
+ }
+
+ mSavingOldViewer = savePresentation;
+
+ // If we have a saved content viewer in history, restore and show it now.
+ if (aLoadState->LoadIsFromSessionHistory() &&
+ (mLoadType & LOAD_CMD_HISTORY)) {
+ // https://html.spec.whatwg.org/#history-traversal:
+ // To traverse the history
+ // "If entry has a different Document object than the current entry, then
+ // run the following substeps: Remove any tasks queued by the history
+ // traversal task source..."
+ // Same document object case was handled already above with
+ // HandleSameDocumentNavigation call.
+ RefPtr<ChildSHistory> shistory = GetRootSessionHistory();
+ if (shistory) {
+ shistory->RemovePendingHistoryNavigations();
+ }
+ if (!mozilla::SessionHistoryInParent()) {
+ // It's possible that the previous viewer of mDocumentViewer is the
+ // viewer that will end up in aLoadState->SHEntry() when it gets closed.
+ // If that's the case, we need to go ahead and force it into its shentry
+ // so we can restore it.
+ if (mDocumentViewer) {
+ nsCOMPtr<nsIDocumentViewer> prevViewer =
+ mDocumentViewer->GetPreviousViewer();
+ if (prevViewer) {
+#ifdef DEBUG
+ nsCOMPtr<nsIDocumentViewer> prevPrevViewer =
+ prevViewer->GetPreviousViewer();
+ NS_ASSERTION(!prevPrevViewer, "Should never have viewer chain here");
+#endif
+ nsCOMPtr<nsISHEntry> viewerEntry;
+ prevViewer->GetHistoryEntry(getter_AddRefs(viewerEntry));
+ if (viewerEntry == aLoadState->SHEntry()) {
+ // Make sure this viewer ends up in the right place
+ mDocumentViewer->SetPreviousViewer(nullptr);
+ prevViewer->Destroy();
+ }
+ }
+ }
+ nsCOMPtr<nsISHEntry> oldEntry = mOSHE;
+ bool restoring;
+ rv = RestorePresentation(aLoadState->SHEntry(), &restoring);
+ if (restoring) {
+ Telemetry::Accumulate(Telemetry::BFCACHE_PAGE_RESTORED, true);
+ return rv;
+ }
+ Telemetry::Accumulate(Telemetry::BFCACHE_PAGE_RESTORED, false);
+
+ // We failed to restore the presentation, so clean up.
+ // Both the old and new history entries could potentially be in
+ // an inconsistent state.
+ if (NS_FAILED(rv)) {
+ if (oldEntry) {
+ oldEntry->SyncPresentationState();
+ }
+
+ aLoadState->SHEntry()->SyncPresentationState();
+ }
+ }
+ }
+
+ bool isTopLevelDoc = mBrowsingContext->IsTopContent();
+
+ OriginAttributes attrs = GetOriginAttributes();
+ attrs.SetFirstPartyDomain(isTopLevelDoc, aLoadState->URI());
+
+ PredictorLearn(aLoadState->URI(), nullptr,
+ nsINetworkPredictor::LEARN_LOAD_TOPLEVEL, attrs);
+ PredictorPredict(aLoadState->URI(), nullptr,
+ nsINetworkPredictor::PREDICT_LOAD, attrs, nullptr);
+
+ nsCOMPtr<nsIRequest> req;
+ rv = DoURILoad(aLoadState, aCacheKey, getter_AddRefs(req));
+
+ if (NS_SUCCEEDED(rv)) {
+ if (shouldTakeFocus) {
+ mBrowsingContext->Focus(CallerType::System, IgnoreErrors());
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ nsCOMPtr<nsIChannel> chan(do_QueryInterface(req));
+ UnblockEmbedderLoadEventForFailure();
+ nsCOMPtr<nsIURI> uri = aLoadState->URI();
+ if (DisplayLoadError(rv, uri, nullptr, chan) &&
+ // FIXME: At this point code was using internal load flags, but checking
+ // non-internal load flags?
+ aLoadState->HasLoadFlags(LOAD_FLAGS_ERROR_LOAD_CHANGES_RV)) {
+ return NS_ERROR_LOAD_SHOWED_ERRORPAGE;
+ }
+
+ // We won't report any error if this is an unknown protocol error. The
+ // reason behind this is that it will allow enumeration of external
+ // protocols if we report an error for each unknown protocol.
+ if (NS_ERROR_UNKNOWN_PROTOCOL == rv) {
+ return NS_OK;
+ }
+ }
+
+ return rv;
+}
+
+/* static */
+bool nsDocShell::CanLoadInParentProcess(nsIURI* aURI) {
+ nsCOMPtr<nsIURI> uri = aURI;
+ // In e10s, in the parent process, we refuse to load anything other than
+ // "safe" resources that we ship or trust enough to give "special" URLs.
+ bool canLoadInParent = false;
+ if (NS_SUCCEEDED(NS_URIChainHasFlags(
+ uri, nsIProtocolHandler::URI_IS_UI_RESOURCE, &canLoadInParent)) &&
+ canLoadInParent) {
+ // We allow UI resources.
+ return true;
+ }
+ // For about: and extension-based URIs, which don't get
+ // URI_IS_UI_RESOURCE, first remove layers of view-source:, if present.
+ while (uri && uri->SchemeIs("view-source")) {
+ nsCOMPtr<nsINestedURI> nested = do_QueryInterface(uri);
+ if (nested) {
+ nested->GetInnerURI(getter_AddRefs(uri));
+ } else {
+ break;
+ }
+ }
+ // Allow about: URIs, and allow moz-extension ones if we're running
+ // extension content in the parent process.
+ if (!uri || uri->SchemeIs("about") ||
+ (!StaticPrefs::extensions_webextensions_remote() &&
+ uri->SchemeIs("moz-extension"))) {
+ return true;
+ }
+#ifdef MOZ_THUNDERBIRD
+ if (uri->SchemeIs("imap") || uri->SchemeIs("mailbox") ||
+ uri->SchemeIs("news") || uri->SchemeIs("nntp") ||
+ uri->SchemeIs("snews")) {
+ return true;
+ }
+#endif
+ nsAutoCString scheme;
+ uri->GetScheme(scheme);
+ // Allow ext+foo URIs (extension-registered custom protocols). See
+ // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/protocol_handlers
+ if (StringBeginsWith(scheme, "ext+"_ns) &&
+ !StaticPrefs::extensions_webextensions_remote()) {
+ return true;
+ }
+ // Final exception for some legacy automated tests:
+ if (xpc::IsInAutomation() &&
+ StaticPrefs::security_allow_unsafe_parent_loads()) {
+ return true;
+ }
+ return false;
+}
+
+nsIPrincipal* nsDocShell::GetInheritedPrincipal(
+ bool aConsiderCurrentDocument, bool aConsiderPartitionedPrincipal) {
+ RefPtr<Document> document;
+ bool inheritedFromCurrent = false;
+
+ if (aConsiderCurrentDocument && mDocumentViewer) {
+ document = mDocumentViewer->GetDocument();
+ inheritedFromCurrent = true;
+ }
+
+ if (!document) {
+ nsCOMPtr<nsIDocShellTreeItem> parentItem;
+ GetInProcessSameTypeParent(getter_AddRefs(parentItem));
+ if (parentItem) {
+ document = parentItem->GetDocument();
+ }
+ }
+
+ if (!document) {
+ if (!aConsiderCurrentDocument) {
+ return nullptr;
+ }
+
+ // Make sure we end up with _something_ as the principal no matter
+ // what.If this fails, we'll just get a null docViewer and bail.
+ EnsureDocumentViewer();
+ if (!mDocumentViewer) {
+ return nullptr;
+ }
+ document = mDocumentViewer->GetDocument();
+ }
+
+ //-- Get the document's principal
+ if (document) {
+ nsIPrincipal* docPrincipal = aConsiderPartitionedPrincipal
+ ? document->PartitionedPrincipal()
+ : document->NodePrincipal();
+
+ // Don't allow loads in typeContent docShells to inherit the system
+ // principal from existing documents.
+ if (inheritedFromCurrent && mItemType == typeContent &&
+ docPrincipal->IsSystemPrincipal()) {
+ return nullptr;
+ }
+
+ return docPrincipal;
+ }
+
+ return nullptr;
+}
+
+/* static */ nsresult nsDocShell::CreateRealChannelForDocument(
+ nsIChannel** aChannel, nsIURI* aURI, nsILoadInfo* aLoadInfo,
+ nsIInterfaceRequestor* aCallbacks, nsLoadFlags aLoadFlags,
+ const nsAString& aSrcdoc, nsIURI* aBaseURI) {
+ nsCOMPtr<nsIChannel> channel;
+ if (aSrcdoc.IsVoid()) {
+ MOZ_TRY(NS_NewChannelInternal(getter_AddRefs(channel), aURI, aLoadInfo,
+ nullptr, // PerformanceStorage
+ nullptr, // loadGroup
+ aCallbacks, aLoadFlags));
+
+ if (aBaseURI) {
+ nsCOMPtr<nsIViewSourceChannel> vsc = do_QueryInterface(channel);
+ if (vsc) {
+ MOZ_ALWAYS_SUCCEEDS(vsc->SetBaseURI(aBaseURI));
+ }
+ }
+ } else if (SchemeIsViewSource(aURI)) {
+ // Instantiate view source handler protocol, if it doesn't exist already.
+ nsCOMPtr<nsIIOService> io(do_GetIOService());
+ MOZ_ASSERT(io);
+ nsCOMPtr<nsIProtocolHandler> handler;
+ nsresult rv =
+ io->GetProtocolHandler("view-source", getter_AddRefs(handler));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsViewSourceHandler* vsh = nsViewSourceHandler::GetInstance();
+ if (!vsh) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_TRY(vsh->NewSrcdocChannel(aURI, aBaseURI, aSrcdoc, aLoadInfo,
+ getter_AddRefs(channel)));
+ } else {
+ MOZ_TRY(NS_NewInputStreamChannelInternal(getter_AddRefs(channel), aURI,
+ aSrcdoc, "text/html"_ns, aLoadInfo,
+ true));
+ nsCOMPtr<nsIInputStreamChannel> isc = do_QueryInterface(channel);
+ MOZ_ASSERT(isc);
+ isc->SetBaseURI(aBaseURI);
+ }
+
+ if (aLoadFlags != nsIRequest::LOAD_NORMAL) {
+ nsresult rv = channel->SetLoadFlags(aLoadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ channel.forget(aChannel);
+ return NS_OK;
+}
+
+/* static */ bool nsDocShell::CreateAndConfigureRealChannelForLoadState(
+ BrowsingContext* aBrowsingContext, nsDocShellLoadState* aLoadState,
+ LoadInfo* aLoadInfo, nsIInterfaceRequestor* aCallbacks,
+ nsDocShell* aDocShell, const OriginAttributes& aOriginAttributes,
+ nsLoadFlags aLoadFlags, uint32_t aCacheKey, nsresult& aRv,
+ nsIChannel** aChannel) {
+ MOZ_ASSERT(aLoadInfo);
+
+ nsString srcdoc = VoidString();
+ bool isSrcdoc =
+ aLoadState->HasInternalLoadFlags(INTERNAL_LOAD_FLAGS_IS_SRCDOC);
+ if (isSrcdoc) {
+ srcdoc = aLoadState->SrcdocData();
+ }
+
+ aLoadInfo->SetTriggeringRemoteType(
+ aLoadState->GetEffectiveTriggeringRemoteType());
+
+ if (aLoadState->PrincipalToInherit()) {
+ aLoadInfo->SetPrincipalToInherit(aLoadState->PrincipalToInherit());
+ }
+ aLoadInfo->SetLoadTriggeredFromExternal(
+ aLoadState->HasLoadFlags(LOAD_FLAGS_FROM_EXTERNAL));
+ aLoadInfo->SetForceAllowDataURI(aLoadState->HasInternalLoadFlags(
+ INTERNAL_LOAD_FLAGS_FORCE_ALLOW_DATA_URI));
+ aLoadInfo->SetOriginalFrameSrcLoad(
+ aLoadState->HasInternalLoadFlags(INTERNAL_LOAD_FLAGS_ORIGINAL_FRAME_SRC));
+
+ bool inheritAttrs = false;
+ if (aLoadState->PrincipalToInherit()) {
+ inheritAttrs = nsContentUtils::ChannelShouldInheritPrincipal(
+ aLoadState->PrincipalToInherit(), aLoadState->URI(),
+ true, // aInheritForAboutBlank
+ isSrcdoc);
+ }
+
+ // Strip the target query parameters before creating the channel.
+ aLoadState->MaybeStripTrackerQueryStrings(aBrowsingContext);
+
+ OriginAttributes attrs;
+
+ // Inherit origin attributes from PrincipalToInherit if inheritAttrs is
+ // true. Otherwise we just use the origin attributes from docshell.
+ if (inheritAttrs) {
+ MOZ_ASSERT(aLoadState->PrincipalToInherit(),
+ "We should have PrincipalToInherit here.");
+ attrs = aLoadState->PrincipalToInherit()->OriginAttributesRef();
+ // If firstPartyIsolation is not enabled, then PrincipalToInherit should
+ // have the same origin attributes with docshell.
+ MOZ_ASSERT_IF(!OriginAttributes::IsFirstPartyEnabled(),
+ attrs == aOriginAttributes);
+ } else {
+ attrs = aOriginAttributes;
+ attrs.SetFirstPartyDomain(IsTopLevelDoc(aBrowsingContext, aLoadInfo),
+ aLoadState->URI());
+ }
+
+ aRv = aLoadInfo->SetOriginAttributes(attrs);
+ if (NS_WARN_IF(NS_FAILED(aRv))) {
+ return false;
+ }
+
+ if (aLoadState->GetIsFromProcessingFrameAttributes()) {
+ aLoadInfo->SetIsFromProcessingFrameAttributes();
+ }
+
+ // Propagate the IsFormSubmission flag to the loadInfo.
+ if (aLoadState->IsFormSubmission()) {
+ aLoadInfo->SetIsFormSubmission(true);
+ }
+
+ aLoadInfo->SetUnstrippedURI(aLoadState->GetUnstrippedURI());
+
+ nsCOMPtr<nsIChannel> channel;
+ aRv = CreateRealChannelForDocument(getter_AddRefs(channel), aLoadState->URI(),
+ aLoadInfo, aCallbacks, aLoadFlags, srcdoc,
+ aLoadState->BaseURI());
+ NS_ENSURE_SUCCESS(aRv, false);
+
+ if (!channel) {
+ return false;
+ }
+
+ // If the HTTPS-Only mode is enabled, every insecure request gets upgraded to
+ // HTTPS by default. This behavior can be disabled through the loadinfo flag
+ // HTTPS_ONLY_EXEMPT.
+ nsHTTPSOnlyUtils::TestSitePermissionAndPotentiallyAddExemption(channel);
+
+ // hack
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
+ nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal(
+ do_QueryInterface(channel));
+ nsCOMPtr<nsIURI> referrer;
+ nsIReferrerInfo* referrerInfo = aLoadState->GetReferrerInfo();
+ if (referrerInfo) {
+ referrerInfo->GetOriginalReferrer(getter_AddRefs(referrer));
+ }
+ if (httpChannelInternal) {
+ if (aLoadState->HasInternalLoadFlags(
+ INTERNAL_LOAD_FLAGS_FORCE_ALLOW_COOKIES)) {
+ aRv = httpChannelInternal->SetThirdPartyFlags(
+ nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW);
+ MOZ_ASSERT(NS_SUCCEEDED(aRv));
+ }
+ if (aLoadState->FirstParty()) {
+ aRv = httpChannelInternal->SetDocumentURI(aLoadState->URI());
+ MOZ_ASSERT(NS_SUCCEEDED(aRv));
+ } else {
+ aRv = httpChannelInternal->SetDocumentURI(referrer);
+ MOZ_ASSERT(NS_SUCCEEDED(aRv));
+ }
+ aRv = httpChannelInternal->SetRedirectMode(
+ nsIHttpChannelInternal::REDIRECT_MODE_MANUAL);
+ MOZ_ASSERT(NS_SUCCEEDED(aRv));
+ }
+
+ if (httpChannel) {
+ if (aLoadState->HeadersStream()) {
+ aRv = AddHeadersToChannel(aLoadState->HeadersStream(), httpChannel);
+ }
+ // Set the referrer explicitly
+ // Referrer is currenly only set for link clicks here.
+ if (referrerInfo) {
+ aRv = httpChannel->SetReferrerInfo(referrerInfo);
+ MOZ_ASSERT(NS_SUCCEEDED(aRv));
+ }
+
+ // Mark the http channel as UrgentStart for top level document loading in
+ // active tab.
+ if (IsUrgentStart(aBrowsingContext, aLoadInfo, aLoadState->LoadType())) {
+ nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(channel));
+ if (cos) {
+ cos->AddClassFlags(nsIClassOfService::UrgentStart);
+ }
+ }
+ }
+
+ channel->SetOriginalURI(aLoadState->OriginalURI() ? aLoadState->OriginalURI()
+ : aLoadState->URI());
+
+ const nsACString& typeHint = aLoadState->TypeHint();
+ if (!typeHint.IsVoid()) {
+ channel->SetContentType(typeHint);
+ }
+
+ const nsAString& fileName = aLoadState->FileName();
+ if (!fileName.IsVoid()) {
+ aRv = channel->SetContentDisposition(nsIChannel::DISPOSITION_ATTACHMENT);
+ NS_ENSURE_SUCCESS(aRv, false);
+ if (!fileName.IsEmpty()) {
+ aRv = channel->SetContentDispositionFilename(fileName);
+ NS_ENSURE_SUCCESS(aRv, false);
+ }
+ }
+
+ if (nsCOMPtr<nsIWritablePropertyBag2> props = do_QueryInterface(channel)) {
+ nsCOMPtr<nsIURI> referrer;
+ nsIReferrerInfo* referrerInfo = aLoadState->GetReferrerInfo();
+ if (referrerInfo) {
+ referrerInfo->GetOriginalReferrer(getter_AddRefs(referrer));
+ }
+ // save true referrer for those who need it (e.g. xpinstall whitelisting)
+ // Currently only http and ftp channels support this.
+ props->SetPropertyAsInterface(u"docshell.internalReferrer"_ns, referrer);
+
+ if (aLoadState->HasInternalLoadFlags(INTERNAL_LOAD_FLAGS_FIRST_LOAD)) {
+ props->SetPropertyAsBool(u"docshell.newWindowTarget"_ns, true);
+ }
+ }
+
+ nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(channel));
+ auto loadType = aLoadState->LoadType();
+
+ if (loadType == LOAD_RELOAD_NORMAL &&
+ StaticPrefs::
+ browser_soft_reload_only_force_validate_top_level_document()) {
+ nsCOMPtr<nsICacheInfoChannel> cachingChannel = do_QueryInterface(channel);
+ if (cachingChannel) {
+ cachingChannel->SetForceValidateCacheContent(true);
+ }
+ }
+
+ // figure out if we need to set the post data stream on the channel...
+ if (aLoadState->PostDataStream()) {
+ if (nsCOMPtr<nsIFormPOSTActionChannel> postChannel =
+ do_QueryInterface(channel)) {
+ // XXX it's a bit of a hack to rewind the postdata stream here but
+ // it has to be done in case the post data is being reused multiple
+ // times.
+ nsCOMPtr<nsISeekableStream> postDataSeekable =
+ do_QueryInterface(aLoadState->PostDataStream());
+ if (postDataSeekable) {
+ aRv = postDataSeekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+ NS_ENSURE_SUCCESS(aRv, false);
+ }
+
+ // we really need to have a content type associated with this stream!!
+ postChannel->SetUploadStream(aLoadState->PostDataStream(), ""_ns, -1);
+
+ // Ownership of the stream has transferred to the channel, clear our
+ // reference.
+ aLoadState->SetPostDataStream(nullptr);
+ }
+
+ /* If there is a valid postdata *and* it is a History Load,
+ * set up the cache key on the channel, to retrieve the
+ * data *only* from the cache. If it is a normal reload, the
+ * cache is free to go to the server for updated postdata.
+ */
+ if (cacheChannel && aCacheKey != 0) {
+ if (loadType == LOAD_HISTORY || loadType == LOAD_RELOAD_CHARSET_CHANGE) {
+ cacheChannel->SetCacheKey(aCacheKey);
+ uint32_t loadFlags;
+ if (NS_SUCCEEDED(channel->GetLoadFlags(&loadFlags))) {
+ channel->SetLoadFlags(loadFlags |
+ nsICachingChannel::LOAD_ONLY_FROM_CACHE);
+ }
+ } else if (loadType == LOAD_RELOAD_NORMAL) {
+ cacheChannel->SetCacheKey(aCacheKey);
+ }
+ }
+ } else {
+ /* If there is no postdata, set the cache key on the channel, and
+ * do not set the LOAD_ONLY_FROM_CACHE flag, so that the channel
+ * will be free to get it from net if it is not found in cache.
+ * New cache may use it creatively on CGI pages with GET
+ * method and even on those that say "no-cache"
+ */
+ if (loadType == LOAD_HISTORY || loadType == LOAD_RELOAD_NORMAL ||
+ loadType == LOAD_RELOAD_CHARSET_CHANGE ||
+ loadType == LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE ||
+ loadType == LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE) {
+ if (cacheChannel && aCacheKey != 0) {
+ cacheChannel->SetCacheKey(aCacheKey);
+ }
+ }
+ }
+
+ if (nsCOMPtr<nsIScriptChannel> scriptChannel = do_QueryInterface(channel)) {
+ // Allow execution against our context if the principals match
+ scriptChannel->SetExecutionPolicy(nsIScriptChannel::EXECUTE_NORMAL);
+ }
+
+ if (nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(channel)) {
+ timedChannel->SetTimingEnabled(true);
+
+ nsString initiatorType;
+ switch (aLoadInfo->InternalContentPolicyType()) {
+ case nsIContentPolicy::TYPE_INTERNAL_EMBED:
+ initiatorType = u"embed"_ns;
+ break;
+ case nsIContentPolicy::TYPE_INTERNAL_OBJECT:
+ initiatorType = u"object"_ns;
+ break;
+ default: {
+ const auto& embedderElementType =
+ aBrowsingContext->GetEmbedderElementType();
+ if (embedderElementType) {
+ initiatorType = *embedderElementType;
+ }
+ break;
+ }
+ }
+
+ if (!initiatorType.IsEmpty()) {
+ timedChannel->SetInitiatorType(initiatorType);
+ }
+ }
+
+ nsCOMPtr<nsIURI> rpURI;
+ aLoadInfo->GetResultPrincipalURI(getter_AddRefs(rpURI));
+ Maybe<nsCOMPtr<nsIURI>> originalResultPrincipalURI;
+ aLoadState->GetMaybeResultPrincipalURI(originalResultPrincipalURI);
+ if (originalResultPrincipalURI &&
+ (!aLoadState->KeepResultPrincipalURIIfSet() || !rpURI)) {
+ // Unconditionally override, we want the replay to be equal to what has
+ // been captured.
+ aLoadInfo->SetResultPrincipalURI(originalResultPrincipalURI.ref());
+ }
+
+ if (aLoadState->OriginalURI() && aLoadState->LoadReplace()) {
+ // The LOAD_REPLACE flag and its handling here will be removed as part
+ // of bug 1319110. For now preserve its restoration here to not break
+ // any code expecting it being set specially on redirected channels.
+ // If the flag has originally been set to change result of
+ // NS_GetFinalChannelURI it won't have any effect and also won't cause
+ // any harm.
+ uint32_t loadFlags;
+ aRv = channel->GetLoadFlags(&loadFlags);
+ NS_ENSURE_SUCCESS(aRv, false);
+ channel->SetLoadFlags(loadFlags | nsIChannel::LOAD_REPLACE);
+ }
+
+ nsCOMPtr<nsIContentSecurityPolicy> csp = aLoadState->Csp();
+ if (csp) {
+ // Navigational requests that are same origin need to be upgraded in case
+ // upgrade-insecure-requests is present. Please note that for document
+ // navigations that bit is re-computed in case we encounter a server
+ // side redirect so the navigation is not same-origin anymore.
+ bool upgradeInsecureRequests = false;
+ csp->GetUpgradeInsecureRequests(&upgradeInsecureRequests);
+ if (upgradeInsecureRequests) {
+ // only upgrade if the navigation is same origin
+ nsCOMPtr<nsIPrincipal> resultPrincipal;
+ aRv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
+ channel, getter_AddRefs(resultPrincipal));
+ NS_ENSURE_SUCCESS(aRv, false);
+ if (nsContentSecurityUtils::IsConsideredSameOriginForUIR(
+ aLoadState->TriggeringPrincipal(), resultPrincipal)) {
+ aLoadInfo->SetUpgradeInsecureRequests(true);
+ }
+ }
+
+ // For document loads we store the CSP that potentially needs to
+ // be inherited by the new document, e.g. in case we are loading
+ // an opaque origin like a data: URI. The actual inheritance
+ // check happens within Document::InitCSP().
+ // Please create an actual copy of the CSP (do not share the same
+ // reference) otherwise a Meta CSP of an opaque origin will
+ // incorrectly be propagated to the embedding document.
+ RefPtr<nsCSPContext> cspToInherit = new nsCSPContext();
+ cspToInherit->InitFromOther(static_cast<nsCSPContext*>(csp.get()));
+ aLoadInfo->SetCSPToInherit(cspToInherit);
+ }
+
+ channel.forget(aChannel);
+ return true;
+}
+
+bool nsDocShell::IsAboutBlankLoadOntoInitialAboutBlank(
+ nsIURI* aURI, bool aInheritPrincipal, nsIPrincipal* aPrincipalToInherit) {
+ return NS_IsAboutBlank(aURI) && aInheritPrincipal &&
+ (aPrincipalToInherit == GetInheritedPrincipal(false)) &&
+ (!mDocumentViewer || !mDocumentViewer->GetDocument() ||
+ mDocumentViewer->GetDocument()->IsInitialDocument());
+}
+
+nsresult nsDocShell::DoURILoad(nsDocShellLoadState* aLoadState,
+ Maybe<uint32_t> aCacheKey,
+ nsIRequest** aRequest) {
+ // Double-check that we're still around to load this URI.
+ if (mIsBeingDestroyed) {
+ // Return NS_OK despite not doing anything to avoid throwing exceptions
+ // from nsLocation::SetHref if the unload handler of the existing page
+ // tears us down.
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURILoader> uriLoader = components::URILoader::Service();
+ if (NS_WARN_IF(!uriLoader)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Persist and sync layout history state before we load a new uri, as this
+ // might be our last chance to do so, in the content process.
+ PersistLayoutHistoryState();
+ SynchronizeLayoutHistoryState();
+
+ nsresult rv;
+ nsContentPolicyType contentPolicyType = DetermineContentType();
+
+ if (IsSubframe()) {
+ MOZ_ASSERT(contentPolicyType == nsIContentPolicy::TYPE_INTERNAL_IFRAME ||
+ contentPolicyType == nsIContentPolicy::TYPE_INTERNAL_FRAME,
+ "DoURILoad thinks this is a frame and InternalLoad does not");
+
+ if (StaticPrefs::dom_block_external_protocol_in_iframes()) {
+ // Only allow URLs able to return data in iframes.
+ if (nsContentUtils::IsExternalProtocol(aLoadState->URI())) {
+ // The context to check user-interaction with for the purposes of
+ // popup-blocking.
+ //
+ // We generally want to check the context that initiated the navigation.
+ WindowContext* sourceWindowContext = [&] {
+ const MaybeDiscardedBrowsingContext& sourceBC =
+ aLoadState->SourceBrowsingContext();
+ if (!sourceBC.IsNullOrDiscarded()) {
+ if (WindowContext* wc = sourceBC.get()->GetCurrentWindowContext()) {
+ return wc;
+ }
+ }
+ return mBrowsingContext->GetParentWindowContext();
+ }();
+
+ MOZ_ASSERT(sourceWindowContext);
+ // FIXME: We can't check user-interaction against an OOP window. This is
+ // the next best thing we can really do. The load state keeps whether
+ // the navigation had a user interaction in process
+ // (aLoadState->HasValidUserGestureActivation()), but we can't really
+ // consume it, which we want to prevent popup-spamming from the same
+ // click event.
+ WindowContext* context =
+ sourceWindowContext->IsInProcess()
+ ? sourceWindowContext
+ : mBrowsingContext->GetCurrentWindowContext();
+ const bool popupBlocked = [&] {
+ const bool active = mBrowsingContext->IsActive();
+
+ // For same-origin-with-top windows, we grant a single free popup
+ // without user activation, see bug 1680721.
+ //
+ // We consume the flag now even if there's no user activation.
+ const bool hasFreePass = [&] {
+ if (!active ||
+ !(context->IsInProcess() && context->SameOriginWithTop())) {
+ return false;
+ }
+ nsGlobalWindowInner* win =
+ context->TopWindowContext()->GetInnerWindow();
+ return win && win->TryOpenExternalProtocolIframe();
+ }();
+
+ if (context->IsInProcess() &&
+ context->ConsumeTransientUserGestureActivation()) {
+ // If the user has interacted with the page, consume it.
+ return false;
+ }
+
+ // TODO(emilio): Can we remove this check? It seems like what prompted
+ // this code (bug 1514547) should be covered by transient user
+ // activation, see bug 1514547.
+ if (active &&
+ PopupBlocker::ConsumeTimerTokenForExternalProtocolIframe()) {
+ return false;
+ }
+
+ if (sourceWindowContext->CanShowPopup()) {
+ return false;
+ }
+
+ if (hasFreePass) {
+ return false;
+ }
+
+ return true;
+ }();
+
+ // No error must be returned when iframes are blocked.
+ if (popupBlocked) {
+ nsAutoString message;
+ nsresult rv = nsContentUtils::GetLocalizedString(
+ nsContentUtils::eDOM_PROPERTIES,
+ "ExternalProtocolFrameBlockedNoUserActivation", message);
+ if (NS_SUCCEEDED(rv)) {
+ nsContentUtils::ReportToConsoleByWindowID(
+ message, nsIScriptError::warningFlag, "DOM"_ns,
+ context->InnerWindowId());
+ }
+ return NS_OK;
+ }
+ }
+ }
+
+ // Only allow view-source scheme in top-level docshells. view-source is
+ // the only scheme to which this applies at the moment due to potential
+ // timing attacks to read data from cross-origin iframes. If this widens
+ // we should add a protocol flag for whether the scheme is allowed in
+ // frames and use something like nsNetUtil::NS_URIChainHasFlags.
+ nsCOMPtr<nsIURI> tempURI = aLoadState->URI();
+ nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(tempURI);
+ while (nestedURI) {
+ // view-source should always be an nsINestedURI, loop and check the
+ // scheme on this and all inner URIs that are also nested URIs.
+ if (SchemeIsViewSource(tempURI)) {
+ return NS_ERROR_UNKNOWN_PROTOCOL;
+ }
+ nestedURI->GetInnerURI(getter_AddRefs(tempURI));
+ nestedURI = do_QueryInterface(tempURI);
+ }
+ } else {
+ MOZ_ASSERT(contentPolicyType == nsIContentPolicy::TYPE_DOCUMENT,
+ "DoURILoad thinks this is a document and InternalLoad does not");
+ }
+
+ // We want to inherit aLoadState->PrincipalToInherit() when:
+ // 1. ChannelShouldInheritPrincipal returns true.
+ // 2. aLoadState->URI() is not data: URI, or data: URI is not
+ // configured as unique opaque origin.
+ bool inheritPrincipal = false;
+
+ if (aLoadState->PrincipalToInherit()) {
+ bool isSrcdoc =
+ aLoadState->HasInternalLoadFlags(INTERNAL_LOAD_FLAGS_IS_SRCDOC);
+ bool inheritAttrs = nsContentUtils::ChannelShouldInheritPrincipal(
+ aLoadState->PrincipalToInherit(), aLoadState->URI(),
+ true, // aInheritForAboutBlank
+ isSrcdoc);
+
+ inheritPrincipal = inheritAttrs && !SchemeIsData(aLoadState->URI());
+ }
+
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=1736570
+ const bool isAboutBlankLoadOntoInitialAboutBlank =
+ IsAboutBlankLoadOntoInitialAboutBlank(aLoadState->URI(), inheritPrincipal,
+ aLoadState->PrincipalToInherit());
+
+ // FIXME We still have a ton of codepaths that don't pass through
+ // DocumentLoadListener, so probably need to create session history info
+ // in more places.
+ if (aLoadState->GetLoadingSessionHistoryInfo()) {
+ SetLoadingSessionHistoryInfo(*aLoadState->GetLoadingSessionHistoryInfo());
+ } else if (isAboutBlankLoadOntoInitialAboutBlank &&
+ mozilla::SessionHistoryInParent()) {
+ // Materialize LoadingSessionHistoryInfo here, because DocumentChannel
+ // loads have it, and later history behavior depends on it existing.
+ UniquePtr<SessionHistoryInfo> entry = MakeUnique<SessionHistoryInfo>(
+ aLoadState->URI(), aLoadState->TriggeringPrincipal(),
+ aLoadState->PrincipalToInherit(),
+ aLoadState->PartitionedPrincipalToInherit(), aLoadState->Csp(),
+ mContentTypeHint);
+ mozilla::dom::LoadingSessionHistoryInfo info(*entry);
+ SetLoadingSessionHistoryInfo(info, true);
+ }
+
+ // open a channel for the url
+
+ // If we have a pending channel, use the channel we've already created here.
+ // We don't need to set up load flags for our channel, as it has already been
+ // created.
+
+ if (nsCOMPtr<nsIChannel> channel =
+ aLoadState->GetPendingRedirectedChannel()) {
+ // If we have a request outparameter, shove our channel into it.
+ if (aRequest) {
+ nsCOMPtr<nsIRequest> outRequest = channel;
+ outRequest.forget(aRequest);
+ }
+
+ return OpenRedirectedChannel(aLoadState);
+ }
+
+ // There are two cases we care about:
+ // * Top-level load: In this case, loadingNode is null, but loadingWindow
+ // is our mScriptGlobal. We pass null for loadingPrincipal in this case.
+ // * Subframe load: loadingWindow is null, but loadingNode is the frame
+ // element for the load. loadingPrincipal is the NodePrincipal of the
+ // frame element.
+ nsCOMPtr<nsINode> loadingNode;
+ nsCOMPtr<nsPIDOMWindowOuter> loadingWindow;
+ nsCOMPtr<nsIPrincipal> loadingPrincipal;
+ nsCOMPtr<nsISupports> topLevelLoadingContext;
+
+ if (contentPolicyType == nsIContentPolicy::TYPE_DOCUMENT) {
+ loadingNode = nullptr;
+ loadingPrincipal = nullptr;
+ loadingWindow = mScriptGlobal;
+ if (XRE_IsContentProcess()) {
+ // In e10s the child process doesn't have access to the element that
+ // contains the browsing context (because that element is in the chrome
+ // process).
+ nsCOMPtr<nsIBrowserChild> browserChild = GetBrowserChild();
+ topLevelLoadingContext = ToSupports(browserChild);
+ } else {
+ // This is for loading non-e10s tabs and toplevel windows of various
+ // sorts.
+ // For the toplevel window cases, requestingElement will be null.
+ nsCOMPtr<Element> requestingElement =
+ loadingWindow->GetFrameElementInternal();
+ topLevelLoadingContext = requestingElement;
+ }
+ } else {
+ loadingWindow = nullptr;
+ loadingNode = mScriptGlobal->GetFrameElementInternal();
+ if (loadingNode) {
+ // If we have a loading node, then use that as our loadingPrincipal.
+ loadingPrincipal = loadingNode->NodePrincipal();
+#ifdef DEBUG
+ // Get the docshell type for requestingElement.
+ RefPtr<Document> requestingDoc = loadingNode->OwnerDoc();
+ nsCOMPtr<nsIDocShell> elementDocShell = requestingDoc->GetDocShell();
+ // requestingElement docshell type = current docshell type.
+ MOZ_ASSERT(
+ mItemType == elementDocShell->ItemType(),
+ "subframes should have the same docshell type as their parent");
+#endif
+ } else {
+ if (mIsBeingDestroyed) {
+ // If this isn't a top-level load and mScriptGlobal's frame element is
+ // null, then the element got removed from the DOM while we were trying
+ // to load this resource. This docshell is scheduled for destruction
+ // already, so bail out here.
+ return NS_OK;
+ }
+ // If we are not being destroyed and we do not have access to the loading
+ // node, then we are a remote subframe. Set the loading principal
+ // to be a null principal and then set it correctly in the parent.
+ loadingPrincipal = NullPrincipal::Create(GetOriginAttributes(), nullptr);
+ }
+ }
+
+ if (!aLoadState->TriggeringPrincipal()) {
+ MOZ_ASSERT(false, "DoURILoad needs a valid triggeringPrincipal");
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t sandboxFlags = mBrowsingContext->GetSandboxFlags();
+ nsSecurityFlags securityFlags =
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL;
+
+ if (mLoadType == LOAD_ERROR_PAGE) {
+ securityFlags |= nsILoadInfo::SEC_LOAD_ERROR_PAGE;
+ }
+
+ if (inheritPrincipal) {
+ securityFlags |= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
+ }
+
+ // Must never have a parent for TYPE_DOCUMENT loads
+ MOZ_ASSERT_IF(contentPolicyType == nsIContentPolicy::TYPE_DOCUMENT,
+ !mBrowsingContext->GetParent());
+ // Subdocuments must have a parent
+ MOZ_ASSERT_IF(contentPolicyType == nsIContentPolicy::TYPE_SUBDOCUMENT,
+ mBrowsingContext->GetParent());
+ mBrowsingContext->SetTriggeringAndInheritPrincipals(
+ aLoadState->TriggeringPrincipal(), aLoadState->PrincipalToInherit(),
+ aLoadState->GetLoadIdentifier());
+ RefPtr<LoadInfo> loadInfo =
+ (contentPolicyType == nsIContentPolicy::TYPE_DOCUMENT)
+ ? new LoadInfo(loadingWindow, aLoadState->URI(),
+ aLoadState->TriggeringPrincipal(),
+ topLevelLoadingContext, securityFlags, sandboxFlags)
+ : new LoadInfo(loadingPrincipal, aLoadState->TriggeringPrincipal(),
+ loadingNode, securityFlags, contentPolicyType,
+ Maybe<mozilla::dom::ClientInfo>(),
+ Maybe<mozilla::dom::ServiceWorkerDescriptor>(),
+ sandboxFlags);
+ RefPtr<WindowContext> context = mBrowsingContext->GetCurrentWindowContext();
+
+ if (isAboutBlankLoadOntoInitialAboutBlank) {
+ // Match the DocumentChannel case where the default for third-partiness
+ // differs from the default in LoadInfo construction here.
+ // toolkit/components/antitracking/test/browser/browser_aboutblank.js
+ // fails without this.
+ BrowsingContext* top = mBrowsingContext->Top();
+ if (top == mBrowsingContext) {
+ // If we're at the top, this must be a window.open()ed
+ // window, and we can't be third-party relative to ourselves.
+ loadInfo->SetIsThirdPartyContextToTopWindow(false);
+ } else {
+ if (Document* topDoc = top->GetDocument()) {
+ bool thirdParty = false;
+ mozilla::Unused << topDoc->GetPrincipal()->IsThirdPartyPrincipal(
+ aLoadState->PrincipalToInherit(), &thirdParty);
+ loadInfo->SetIsThirdPartyContextToTopWindow(thirdParty);
+ } else {
+ // If top is in a different process, we have to be third-party relative
+ // to it.
+ loadInfo->SetIsThirdPartyContextToTopWindow(true);
+ }
+ }
+ }
+
+ if (mLoadType != LOAD_ERROR_PAGE && context && context->IsInProcess()) {
+ if (context->HasValidTransientUserGestureActivation()) {
+ aLoadState->SetHasValidUserGestureActivation(true);
+ }
+ if (!aLoadState->TriggeringWindowId()) {
+ aLoadState->SetTriggeringWindowId(context->Id());
+ }
+ if (!aLoadState->TriggeringStorageAccess()) {
+ Document* contextDoc = context->GetExtantDoc();
+ if (contextDoc) {
+ aLoadState->SetTriggeringStorageAccess(
+ contextDoc->UsingStorageAccess());
+ }
+ }
+ }
+
+ // in case this docshell load was triggered by a valid transient user gesture,
+ // or also the load originates from external, then we pass that information on
+ // to the loadinfo, which allows e.g. setting Sec-Fetch-User request headers.
+ if (aLoadState->HasValidUserGestureActivation() ||
+ aLoadState->HasLoadFlags(LOAD_FLAGS_FROM_EXTERNAL)) {
+ loadInfo->SetHasValidUserGestureActivation(true);
+ }
+
+ loadInfo->SetTriggeringWindowId(aLoadState->TriggeringWindowId());
+ loadInfo->SetTriggeringStorageAccess(aLoadState->TriggeringStorageAccess());
+ loadInfo->SetTriggeringSandboxFlags(aLoadState->TriggeringSandboxFlags());
+ loadInfo->SetIsMetaRefresh(aLoadState->IsMetaRefresh());
+
+ uint32_t cacheKey = 0;
+ if (aCacheKey) {
+ cacheKey = *aCacheKey;
+ } else if (mozilla::SessionHistoryInParent()) {
+ if (mLoadingEntry) {
+ cacheKey = mLoadingEntry->mInfo.GetCacheKey();
+ } else if (mActiveEntry) { // for reload cases
+ cacheKey = mActiveEntry->GetCacheKey();
+ }
+ } else {
+ if (mLSHE) {
+ cacheKey = mLSHE->GetCacheKey();
+ } else if (mOSHE) { // for reload cases
+ cacheKey = mOSHE->GetCacheKey();
+ }
+ }
+
+ bool uriModified;
+ if (mLSHE || mLoadingEntry) {
+ if (mLoadingEntry) {
+ uriModified = mLoadingEntry->mInfo.GetURIWasModified();
+ } else {
+ uriModified = mLSHE->GetURIWasModified();
+ }
+ } else {
+ uriModified = false;
+ }
+
+ bool isEmbeddingBlockedError = false;
+ if (mFailedChannel) {
+ nsresult status;
+ mFailedChannel->GetStatus(&status);
+ isEmbeddingBlockedError = status == NS_ERROR_XFO_VIOLATION ||
+ status == NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION;
+ }
+
+ nsLoadFlags loadFlags = aLoadState->CalculateChannelLoadFlags(
+ mBrowsingContext, Some(uriModified), Some(isEmbeddingBlockedError));
+
+ nsCOMPtr<nsIChannel> channel;
+ if (DocumentChannel::CanUseDocumentChannel(aLoadState->URI()) &&
+ !isAboutBlankLoadOntoInitialAboutBlank) {
+ channel = DocumentChannel::CreateForDocument(
+ aLoadState, loadInfo, loadFlags, this, cacheKey, uriModified,
+ isEmbeddingBlockedError);
+ MOZ_ASSERT(channel);
+
+ // Disable keyword fixup when using DocumentChannel, since
+ // DocumentLoadListener will handle this for us (in the parent process).
+ mAllowKeywordFixup = false;
+ } else if (!CreateAndConfigureRealChannelForLoadState(
+ mBrowsingContext, aLoadState, loadInfo, this, this,
+ GetOriginAttributes(), loadFlags, cacheKey, rv,
+ getter_AddRefs(channel))) {
+ return rv;
+ }
+
+ // Make sure to give the caller a channel if we managed to create one
+ // This is important for correct error page/session history interaction
+ if (aRequest) {
+ NS_ADDREF(*aRequest = channel);
+ }
+
+ const nsACString& typeHint = aLoadState->TypeHint();
+ if (!typeHint.IsVoid()) {
+ mContentTypeHint = typeHint;
+ } else {
+ mContentTypeHint.Truncate();
+ }
+
+ // Load attributes depend on load type...
+ if (mLoadType == LOAD_RELOAD_CHARSET_CHANGE) {
+ // Use SetAllowStaleCacheContent (not LOAD_FROM_CACHE flag) since we
+ // only want to force cache load for this channel, not the whole
+ // loadGroup.
+ nsCOMPtr<nsICacheInfoChannel> cachingChannel = do_QueryInterface(channel);
+ if (cachingChannel) {
+ cachingChannel->SetAllowStaleCacheContent(true);
+ }
+ }
+
+ uint32_t openFlags =
+ nsDocShell::ComputeURILoaderFlags(mBrowsingContext, mLoadType);
+ return OpenInitializedChannel(channel, uriLoader, openFlags);
+}
+
+static nsresult AppendSegmentToString(nsIInputStream* aIn, void* aClosure,
+ const char* aFromRawSegment,
+ uint32_t aToOffset, uint32_t aCount,
+ uint32_t* aWriteCount) {
+ // aFromSegment now contains aCount bytes of data.
+
+ nsAutoCString* buf = static_cast<nsAutoCString*>(aClosure);
+ buf->Append(aFromRawSegment, aCount);
+
+ // Indicate that we have consumed all of aFromSegment
+ *aWriteCount = aCount;
+ return NS_OK;
+}
+
+/* static */ nsresult nsDocShell::AddHeadersToChannel(
+ nsIInputStream* aHeadersData, nsIChannel* aGenericChannel) {
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aGenericChannel);
+ NS_ENSURE_STATE(httpChannel);
+
+ uint32_t numRead;
+ nsAutoCString headersString;
+ nsresult rv = aHeadersData->ReadSegments(
+ AppendSegmentToString, &headersString, UINT32_MAX, &numRead);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // used during the manipulation of the String from the InputStream
+ nsAutoCString headerName;
+ nsAutoCString headerValue;
+ int32_t crlf;
+ int32_t colon;
+
+ //
+ // Iterate over the headersString: for each "\r\n" delimited chunk,
+ // add the value as a header to the nsIHttpChannel
+ //
+
+ static const char kWhitespace[] = "\b\t\r\n ";
+ while (true) {
+ crlf = headersString.Find("\r\n");
+ if (crlf == kNotFound) {
+ return NS_OK;
+ }
+
+ const nsACString& oneHeader = StringHead(headersString, crlf);
+
+ colon = oneHeader.FindChar(':');
+ if (colon == kNotFound) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ headerName = StringHead(oneHeader, colon);
+ headerValue = Substring(oneHeader, colon + 1);
+
+ headerName.Trim(kWhitespace);
+ headerValue.Trim(kWhitespace);
+
+ headersString.Cut(0, crlf + 2);
+
+ //
+ // FINALLY: we can set the header!
+ //
+
+ rv = httpChannel->SetRequestHeader(headerName, headerValue, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ MOZ_ASSERT_UNREACHABLE("oops");
+ return NS_ERROR_UNEXPECTED;
+}
+
+/* static */ uint32_t nsDocShell::ComputeURILoaderFlags(
+ BrowsingContext* aBrowsingContext, uint32_t aLoadType) {
+ MOZ_ASSERT(aBrowsingContext);
+
+ uint32_t openFlags = 0;
+ if (aLoadType == LOAD_LINK) {
+ openFlags |= nsIURILoader::IS_CONTENT_PREFERRED;
+ }
+ if (!aBrowsingContext->GetAllowContentRetargeting()) {
+ openFlags |= nsIURILoader::DONT_RETARGET;
+ }
+
+ return openFlags;
+}
+
+nsresult nsDocShell::OpenInitializedChannel(nsIChannel* aChannel,
+ nsIURILoader* aURILoader,
+ uint32_t aOpenFlags) {
+ nsresult rv = NS_OK;
+
+ // If anything fails here, make sure to clear our initial ClientSource.
+ auto cleanupInitialClient =
+ MakeScopeExit([&] { mInitialClientSource.reset(); });
+
+ nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow();
+ NS_ENSURE_TRUE(win, NS_ERROR_FAILURE);
+
+ MaybeCreateInitialClientSource();
+
+ // Let the client channel helper know if we are using DocumentChannel,
+ // since redirects get handled in the parent process in that case.
+ RefPtr<net::DocumentChannel> docChannel = do_QueryObject(aChannel);
+ if (docChannel && XRE_IsContentProcess()) {
+ // Tell the content process nsDocumentOpenInfo to not try to do
+ // any sort of targeting.
+ aOpenFlags |= nsIURILoader::DONT_RETARGET;
+ }
+
+ // Since we are loading a document we need to make sure the proper reserved
+ // and initial client data is stored on the nsILoadInfo. The
+ // ClientChannelHelper does this and ensures that it is propagated properly
+ // on redirects. We pass no reserved client here so that the helper will
+ // create the reserved ClientSource if necessary.
+ Maybe<ClientInfo> noReservedClient;
+ if (docChannel) {
+ // When using DocumentChannel, all redirect handling is done in the parent,
+ // so we just need the child variant to watch for the internal redirect
+ // to the final channel.
+ rv = AddClientChannelHelperInChild(aChannel,
+ GetMainThreadSerialEventTarget());
+ docChannel->SetInitialClientInfo(GetInitialClientInfo());
+ } else {
+ rv = AddClientChannelHelper(aChannel, std::move(noReservedClient),
+ GetInitialClientInfo(),
+ GetMainThreadSerialEventTarget());
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aURILoader->OpenURI(aChannel, aOpenFlags, this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We're about to load a new page and it may take time before necko
+ // gives back any data, so main thread might have a chance to process a
+ // collector slice
+ nsJSContext::MaybeRunNextCollectorSlice(this, JS::GCReason::DOCSHELL);
+
+ // Success. Keep the initial ClientSource if it exists.
+ cleanupInitialClient.release();
+
+ return NS_OK;
+}
+
+nsresult nsDocShell::OpenRedirectedChannel(nsDocShellLoadState* aLoadState) {
+ nsCOMPtr<nsIChannel> channel = aLoadState->GetPendingRedirectedChannel();
+ MOZ_ASSERT(channel);
+
+ // If anything fails here, make sure to clear our initial ClientSource.
+ auto cleanupInitialClient =
+ MakeScopeExit([&] { mInitialClientSource.reset(); });
+
+ nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow();
+ NS_ENSURE_TRUE(win, NS_ERROR_FAILURE);
+
+ MaybeCreateInitialClientSource();
+
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+
+ LoadInfo* li = static_cast<LoadInfo*>(loadInfo.get());
+ if (loadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_DOCUMENT) {
+ li->UpdateBrowsingContextID(mBrowsingContext->Id());
+ } else if (loadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_SUBDOCUMENT) {
+ li->UpdateFrameBrowsingContextID(mBrowsingContext->Id());
+ }
+
+ // If we did a process switch, then we should have an existing allocated
+ // ClientInfo, so we just need to allocate a corresponding ClientSource.
+ CreateReservedSourceIfNeeded(channel, GetMainThreadSerialEventTarget());
+
+ RefPtr<nsDocumentOpenInfo> loader =
+ new nsDocumentOpenInfo(this, nsIURILoader::DONT_RETARGET, nullptr);
+ channel->SetLoadGroup(mLoadGroup);
+
+ MOZ_ALWAYS_SUCCEEDS(loader->Prepare());
+
+ nsresult rv = NS_OK;
+ if (XRE_IsParentProcess()) {
+ // If we're in the parent, the we don't have an nsIChildChannel, just
+ // the original channel, which is already open in this process.
+
+ // DocumentLoadListener expects to get an nsIParentChannel, so
+ // we create a wrapper around the channel and nsIStreamListener
+ // that forwards functionality as needed, and then we register
+ // it under the provided identifier.
+ RefPtr<ParentChannelWrapper> wrapper =
+ new ParentChannelWrapper(channel, loader);
+ wrapper->Register(aLoadState->GetPendingRedirectChannelRegistrarId());
+
+ mLoadGroup->AddRequest(channel, nullptr);
+ } else if (nsCOMPtr<nsIChildChannel> childChannel =
+ do_QueryInterface(channel)) {
+ // Our channel was redirected from another process, so doesn't need to
+ // be opened again. However, it does need its listener hooked up
+ // correctly.
+ rv = childChannel->CompleteRedirectSetup(loader);
+ } else {
+ // It's possible for the redirected channel to not implement
+ // nsIChildChannel and be entirely local (like srcdoc). In that case we
+ // can just open the local instance and it will work.
+ rv = channel->AsyncOpen(loader);
+ }
+ if (rv == NS_ERROR_NO_CONTENT) {
+ return NS_OK;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Success. Keep the initial ClientSource if it exists.
+ cleanupInitialClient.release();
+ return NS_OK;
+}
+
+// https://html.spec.whatwg.org/#scrolling-to-a-fragment
+nsresult nsDocShell::ScrollToAnchor(bool aCurHasRef, bool aNewHasRef,
+ nsACString& aNewHash, uint32_t aLoadType) {
+ if (!mCurrentURI) {
+ return NS_OK;
+ }
+
+ RefPtr<PresShell> presShell = GetPresShell();
+ if (!presShell) {
+ // If we failed to get the shell, or if there is no shell,
+ // nothing left to do here.
+ return NS_OK;
+ }
+
+ nsIScrollableFrame* rootScroll = presShell->GetRootScrollFrameAsScrollable();
+ if (rootScroll) {
+ rootScroll->ClearDidHistoryRestore();
+ }
+
+ // If we have no new anchor, we do not want to scroll, unless there is a
+ // current anchor and we are doing a history load. So return if we have no
+ // new anchor, and there is no current anchor or the load is not a history
+ // load.
+ if ((!aCurHasRef || aLoadType != LOAD_HISTORY) && !aNewHasRef) {
+ return NS_OK;
+ }
+
+ // Both the new and current URIs refer to the same page. We can now
+ // browse to the hash stored in the new URI.
+
+ // If it's a load from history, we don't have any anchor jumping to do.
+ // Scrollbar position will be restored by the caller based on positions stored
+ // in session history.
+ bool scroll = aLoadType != LOAD_HISTORY && aLoadType != LOAD_RELOAD_NORMAL;
+
+ if (aNewHash.IsEmpty()) {
+ // 2. If fragment is the empty string, then return the special value top of
+ // the document.
+ //
+ // Tell the shell it's at an anchor without scrolling.
+ presShell->GoToAnchor(u""_ns, false);
+
+ if (scroll) {
+ // Scroll to the top of the page. Ignore the return value; failure to
+ // scroll here (e.g. if there is no root scrollframe) is not grounds for
+ // canceling the load!
+ SetCurScrollPosEx(0, 0);
+ }
+
+ return NS_OK;
+ }
+
+ // 3. Let potentialIndicatedElement be the result of finding a potential
+ // indicated element given document and fragment.
+ NS_ConvertUTF8toUTF16 uStr(aNewHash);
+ auto rv = presShell->GoToAnchor(uStr, scroll, ScrollFlags::ScrollSmoothAuto);
+
+ // 4. If potentialIndicatedElement is not null, then return
+ // potentialIndicatedElement.
+ if (NS_SUCCEEDED(rv)) {
+ return NS_OK;
+ }
+
+ // 5. Let fragmentBytes be the result of percent-decoding fragment.
+ nsAutoCString fragmentBytes;
+ const bool unescaped = NS_UnescapeURL(aNewHash.Data(), aNewHash.Length(),
+ /* aFlags = */ 0, fragmentBytes);
+
+ if (!unescaped) {
+ // Another attempt is only necessary if characters were unescaped.
+ return NS_OK;
+ }
+
+ if (fragmentBytes.IsEmpty()) {
+ // When aNewHash contains "%00", the unescaped string may be empty, and
+ // GoToAnchor asserts if we ask it to scroll to an empty ref.
+ presShell->GoToAnchor(u""_ns, false);
+ return NS_OK;
+ }
+
+ // 6. Let decodedFragment be the result of running UTF-8 decode without BOM on
+ // fragmentBytes.
+ nsAutoString decodedFragment;
+ rv = UTF_8_ENCODING->DecodeWithoutBOMHandling(fragmentBytes, decodedFragment);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // 7. Set potentialIndicatedElement to the result of finding a potential
+ // indicated element given document and decodedFragment.
+ //
+ // Ignore the return value of GoToAnchor, since it will return an error if
+ // there is no such anchor in the document, which is actually a success
+ // condition for us (we want to update the session history with the new URI no
+ // matter whether we actually scrolled somewhere).
+ presShell->GoToAnchor(decodedFragment, scroll, ScrollFlags::ScrollSmoothAuto);
+
+ return NS_OK;
+}
+
+bool nsDocShell::OnNewURI(nsIURI* aURI, nsIChannel* aChannel,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit,
+ nsIPrincipal* aPartitionedPrincipalToInherit,
+ nsIContentSecurityPolicy* aCsp,
+ bool aAddToGlobalHistory, bool aCloneSHChildren) {
+ MOZ_ASSERT(aURI, "uri is null");
+ MOZ_ASSERT(!aChannel || !aTriggeringPrincipal, "Shouldn't have both set");
+
+ MOZ_ASSERT(!aPrincipalToInherit ||
+ (aPrincipalToInherit && aTriggeringPrincipal));
+
+#if defined(DEBUG)
+ if (MOZ_LOG_TEST(gDocShellLog, LogLevel::Debug)) {
+ nsAutoCString chanName;
+ if (aChannel) {
+ aChannel->GetName(chanName);
+ } else {
+ chanName.AssignLiteral("<no channel>");
+ }
+
+ MOZ_LOG(gDocShellLog, LogLevel::Debug,
+ ("nsDocShell[%p]::OnNewURI(\"%s\", [%s], 0x%x)\n", this,
+ aURI->GetSpecOrDefault().get(), chanName.get(), mLoadType));
+ }
+#endif
+
+ bool equalUri = false;
+
+ // Get the post data and the HTTP response code from the channel.
+ uint32_t responseStatus = 0;
+ nsCOMPtr<nsIInputStream> inputStream;
+ if (aChannel) {
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel));
+
+ // Check if the HTTPChannel is hiding under a multiPartChannel
+ if (!httpChannel) {
+ GetHttpChannel(aChannel, getter_AddRefs(httpChannel));
+ }
+
+ if (httpChannel) {
+ nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(httpChannel));
+ if (uploadChannel) {
+ uploadChannel->GetUploadStream(getter_AddRefs(inputStream));
+ }
+
+ // If the response status indicates an error, unlink this session
+ // history entry from any entries sharing its document.
+ nsresult rv = httpChannel->GetResponseStatus(&responseStatus);
+ if (mLSHE && NS_SUCCEEDED(rv) && responseStatus >= 400) {
+ mLSHE->AbandonBFCacheEntry();
+ // FIXME Do the same for mLoadingEntry
+ }
+ }
+ }
+
+ // Determine if this type of load should update history.
+ bool updateGHistory = ShouldUpdateGlobalHistory(mLoadType);
+
+ // We don't update session history on reload unless we're loading
+ // an iframe in shift-reload case.
+ bool updateSHistory = mBrowsingContext->ShouldUpdateSessionHistory(mLoadType);
+
+ // Create SH Entry (mLSHE) only if there is a SessionHistory object in the
+ // root browsing context.
+ // FIXME If session history in the parent is enabled then we only do this if
+ // the session history object is in process, otherwise we can't really
+ // use the mLSHE anyway. Once session history is only stored in the
+ // parent then this code will probably be removed anyway.
+ RefPtr<ChildSHistory> rootSH = GetRootSessionHistory();
+ if (!rootSH) {
+ updateSHistory = false;
+ updateGHistory = false; // XXX Why global history too?
+ }
+
+ // Check if the url to be loaded is the same as the one already loaded.
+ if (mCurrentURI) {
+ aURI->Equals(mCurrentURI, &equalUri);
+ }
+
+#ifdef DEBUG
+ bool shAvailable = (rootSH != nullptr);
+
+ // XXX This log message is almost useless because |updateSHistory|
+ // and |updateGHistory| are not correct at this point.
+
+ MOZ_LOG(gDocShellLog, LogLevel::Debug,
+ (" shAvailable=%i updateSHistory=%i updateGHistory=%i"
+ " equalURI=%i\n",
+ shAvailable, updateSHistory, updateGHistory, equalUri));
+#endif
+
+ /* If the url to be loaded is the same as the one already there,
+ * and the original loadType is LOAD_NORMAL, LOAD_LINK, or
+ * LOAD_STOP_CONTENT, set loadType to LOAD_NORMAL_REPLACE so that
+ * AddToSessionHistory() won't mess with the current SHEntry and
+ * if this page has any frame children, it also will be handled
+ * properly. see bug 83684
+ *
+ * NB: If mOSHE is null but we have a current URI, then it probably
+ * means that we must be at the transient about:blank content viewer;
+ * we should let the normal load continue, since there's nothing to
+ * replace. Sometimes this happens after a session restore (eg process
+ * switch) and mCurrentURI is not about:blank; we assume we can let the load
+ * continue (Bug 1301399).
+ *
+ * XXX Hopefully changing the loadType at this time will not hurt
+ * anywhere. The other way to take care of sequentially repeating
+ * frameset pages is to add new methods to nsIDocShellTreeItem.
+ * Hopefully I don't have to do that.
+ */
+ if (equalUri &&
+ (mozilla::SessionHistoryInParent() ? !!mActiveEntry : !!mOSHE) &&
+ (mLoadType == LOAD_NORMAL || mLoadType == LOAD_LINK ||
+ mLoadType == LOAD_STOP_CONTENT) &&
+ !inputStream) {
+ mLoadType = LOAD_NORMAL_REPLACE;
+ }
+
+ // If this is a refresh to the currently loaded url, we don't
+ // have to update session or global history.
+ if (mLoadType == LOAD_REFRESH && !inputStream && equalUri) {
+ SetHistoryEntryAndUpdateBC(Some<nsISHEntry*>(mOSHE), Nothing());
+ }
+
+ /* If the user pressed shift-reload, cache will create a new cache key
+ * for the page. Save the new cacheKey in Session History.
+ * see bug 90098
+ */
+ if (aChannel && IsForceReloadType(mLoadType)) {
+ MOZ_ASSERT(!updateSHistory || IsSubframe(),
+ "We shouldn't be updating session history for forced"
+ " reloads unless we're in a newly created iframe!");
+
+ nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(aChannel));
+ uint32_t cacheKey = 0;
+ // Get the Cache Key and store it in SH.
+ if (cacheChannel) {
+ cacheChannel->GetCacheKey(&cacheKey);
+ }
+ // If we already have a loading history entry, store the new cache key
+ // in it. Otherwise, since we're doing a reload and won't be updating
+ // our history entry, store the cache key in our current history entry.
+ SetCacheKeyOnHistoryEntry(mLSHE ? mLSHE : mOSHE, cacheKey);
+
+ if (!mozilla::SessionHistoryInParent()) {
+ // Since we're force-reloading, clear all the sub frame history.
+ ClearFrameHistory(mLSHE);
+ ClearFrameHistory(mOSHE);
+ }
+ }
+
+ if (!mozilla::SessionHistoryInParent()) {
+ // Clear subframe history on refresh.
+ // XXX: history.go(0) won't go this path as mLoadType is LOAD_HISTORY in
+ // this case. One should re-validate after bug 1331865 fixed.
+ if (mLoadType == LOAD_REFRESH) {
+ ClearFrameHistory(mLSHE);
+ ClearFrameHistory(mOSHE);
+ }
+
+ if (updateSHistory) {
+ // Update session history if necessary...
+ if (!mLSHE && (mItemType == typeContent) && mURIResultedInDocument) {
+ /* This is a fresh page getting loaded for the first time
+ *.Create a Entry for it and add it to SH, if this is the
+ * rootDocShell
+ */
+ (void)AddToSessionHistory(aURI, aChannel, aTriggeringPrincipal,
+ aPrincipalToInherit,
+ aPartitionedPrincipalToInherit, aCsp,
+ aCloneSHChildren, getter_AddRefs(mLSHE));
+ }
+ } else if (GetSessionHistory() && mLSHE && mURIResultedInDocument) {
+ // Even if we don't add anything to SHistory, ensure the current index
+ // points to the same SHEntry as our mLSHE.
+
+ GetSessionHistory()->LegacySHistory()->EnsureCorrectEntryAtCurrIndex(
+ mLSHE);
+ }
+ }
+
+ // If this is a POST request, we do not want to include this in global
+ // history.
+ if (ShouldAddURIVisit(aChannel) && updateGHistory && aAddToGlobalHistory &&
+ !net::ChannelIsPost(aChannel)) {
+ nsCOMPtr<nsIURI> previousURI;
+ uint32_t previousFlags = 0;
+
+ if (mLoadType & LOAD_CMD_RELOAD) {
+ // On a reload request, we don't set redirecting flags.
+ previousURI = aURI;
+ } else {
+ ExtractLastVisit(aChannel, getter_AddRefs(previousURI), &previousFlags);
+ }
+
+ AddURIVisit(aURI, previousURI, previousFlags, responseStatus);
+ }
+
+ // If this was a history load or a refresh, or it was a history load but
+ // later changed to LOAD_NORMAL_REPLACE due to redirection, update the index
+ // in session history.
+ if (!mozilla::SessionHistoryInParent() && rootSH &&
+ ((mLoadType & (LOAD_CMD_HISTORY | LOAD_CMD_RELOAD)) ||
+ mLoadType == LOAD_NORMAL_REPLACE || mLoadType == LOAD_REFRESH_REPLACE)) {
+ 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));
+ }
+
+ // aCloneSHChildren exactly means "we are not loading a new document".
+ uint32_t locationFlags =
+ aCloneSHChildren ? uint32_t(LOCATION_CHANGE_SAME_DOCUMENT) : 0;
+
+ bool onLocationChangeNeeded =
+ SetCurrentURI(aURI, aChannel, false,
+ /* aIsInitialAboutBlank */ false, locationFlags);
+ // Make sure to store the referrer from the channel, if any
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel));
+ if (httpChannel) {
+ mReferrerInfo = httpChannel->GetReferrerInfo();
+ }
+ return onLocationChangeNeeded;
+}
+
+Maybe<Wireframe> nsDocShell::GetWireframe() {
+ const bool collectWireFrame =
+ mozilla::SessionHistoryInParent() &&
+ StaticPrefs::browser_history_collectWireframes() &&
+ mBrowsingContext->IsTopContent() && mActiveEntry;
+
+ if (!collectWireFrame) {
+ return Nothing();
+ }
+
+ RefPtr<Document> doc = mDocumentViewer->GetDocument();
+ Nullable<Wireframe> wireframe;
+ doc->GetWireframeWithoutFlushing(false, wireframe);
+ if (wireframe.IsNull()) {
+ return Nothing();
+ }
+ return Some(wireframe.Value());
+}
+
+bool nsDocShell::CollectWireframe() {
+ Maybe<Wireframe> wireframe = GetWireframe();
+ if (wireframe.isNothing()) {
+ return false;
+ }
+
+ if (XRE_IsParentProcess()) {
+ SessionHistoryEntry* entry =
+ mBrowsingContext->Canonical()->GetActiveSessionHistoryEntry();
+ if (entry) {
+ entry->SetWireframe(wireframe);
+ }
+ } else {
+ mozilla::Unused
+ << ContentChild::GetSingleton()->SendSessionHistoryEntryWireframe(
+ mBrowsingContext, wireframe.ref());
+ }
+
+ return true;
+}
+
+//*****************************************************************************
+// nsDocShell: Session History
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsDocShell::AddState(JS::Handle<JS::Value> aData, const nsAString& aTitle,
+ const nsAString& aURL, bool aReplace, JSContext* aCx) {
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("nsDocShell[%p]: AddState(..., %s, %s, %d)", this,
+ NS_ConvertUTF16toUTF8(aTitle).get(),
+ NS_ConvertUTF16toUTF8(aURL).get(), aReplace));
+ // Implements History.pushState and History.replaceState
+
+ // Here's what we do, roughly in the order specified by HTML5. The specific
+ // steps we are executing are at
+ // <https://html.spec.whatwg.org/multipage/history.html#dom-history-pushstate>
+ // and
+ // <https://html.spec.whatwg.org/multipage/history.html#url-and-history-update-steps>.
+ // This function basically implements #dom-history-pushstate and
+ // UpdateURLAndHistory implements #url-and-history-update-steps.
+ //
+ // A. Serialize aData using structured clone. This is #dom-history-pushstate
+ // step 5.
+ // B. If the third argument is present, #dom-history-pushstate step 7.
+ // 7.1. Resolve the url, relative to our document.
+ // 7.2. If (a) fails, raise a SECURITY_ERR
+ // 7.4. Compare the resulting absolute URL to the document's address. If
+ // any part of the URLs difer other than the <path>, <query>, and
+ // <fragment> components, raise a SECURITY_ERR and abort.
+ // C. If !aReplace, #url-and-history-update-steps steps 2.1-2.3:
+ // Remove from the session history all entries after the current entry,
+ // as we would after a regular navigation, and save the current
+ // entry's scroll position (bug 590573).
+ // D. #url-and-history-update-steps step 2.4 or step 3. As apropriate,
+ // either add a state object entry to the session history after the
+ // current entry with the following properties, or modify the current
+ // session history entry to set
+ // a. cloned data as the state object,
+ // b. if the third argument was present, the absolute URL found in
+ // step 2
+ // Also clear the new history entry's POST data (see bug 580069).
+ // E. If aReplace is false (i.e. we're doing a pushState instead of a
+ // replaceState), notify bfcache that we've navigated to a new page.
+ // F. If the third argument is present, set the document's current address
+ // to the absolute URL found in step B. This is
+ // #url-and-history-update-steps step 4.
+ //
+ // It's important that this function not run arbitrary scripts after step A
+ // and before completing step E. For example, if a script called
+ // history.back() before we completed step E, bfcache might destroy an
+ // active content viewer. Since EvictOutOfRangeDocumentViewers at the end of
+ // step E might run script, we can't just put a script blocker around the
+ // critical section.
+ //
+ // Note that we completely ignore the aTitle parameter.
+
+ nsresult rv;
+
+ // Don't clobber the load type of an existing network load.
+ AutoRestore<uint32_t> loadTypeResetter(mLoadType);
+
+ // pushState effectively becomes replaceState when we've started a network
+ // load but haven't adopted its document yet. This mirrors what we do with
+ // changes to the hash at this stage of the game.
+ if (JustStartedNetworkLoad()) {
+ aReplace = true;
+ }
+
+ RefPtr<Document> document = GetDocument();
+ NS_ENSURE_TRUE(document, NS_ERROR_FAILURE);
+
+ // Step A: Serialize aData using structured clone.
+ // https://html.spec.whatwg.org/multipage/history.html#dom-history-pushstate
+ // step 5.
+ nsCOMPtr<nsIStructuredCloneContainer> scContainer;
+
+ // scContainer->Init might cause arbitrary JS to run, and this code might
+ // navigate the page we're on, potentially to a different origin! (bug
+ // 634834) To protect against this, we abort if our principal changes due
+ // to the InitFromJSVal() call.
+ {
+ RefPtr<Document> origDocument = GetDocument();
+ if (!origDocument) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+ nsCOMPtr<nsIPrincipal> origPrincipal = origDocument->NodePrincipal();
+
+ scContainer = new nsStructuredCloneContainer();
+ rv = scContainer->InitFromJSVal(aData, aCx);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<Document> newDocument = GetDocument();
+ if (!newDocument) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+ nsCOMPtr<nsIPrincipal> newPrincipal = newDocument->NodePrincipal();
+
+ bool principalsEqual = false;
+ origPrincipal->Equals(newPrincipal, &principalsEqual);
+ NS_ENSURE_TRUE(principalsEqual, NS_ERROR_DOM_SECURITY_ERR);
+ }
+
+ // Check that the state object isn't too long.
+ int32_t maxStateObjSize = StaticPrefs::browser_history_maxStateObjectSize();
+ if (maxStateObjSize < 0) {
+ maxStateObjSize = 0;
+ }
+
+ uint64_t scSize;
+ rv = scContainer->GetSerializedNBytes(&scSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ENSURE_TRUE(scSize <= (uint32_t)maxStateObjSize, NS_ERROR_ILLEGAL_VALUE);
+
+ // Step B: Resolve aURL.
+ // https://html.spec.whatwg.org/multipage/history.html#dom-history-pushstate
+ // step 7.
+ bool equalURIs = true;
+ nsCOMPtr<nsIURI> currentURI;
+ if (mCurrentURI) {
+ currentURI = nsIOService::CreateExposableURI(mCurrentURI);
+ } else {
+ currentURI = mCurrentURI;
+ }
+ nsCOMPtr<nsIURI> newURI;
+ if (aURL.Length() == 0) {
+ newURI = currentURI;
+ } else {
+ // 7.1: Resolve aURL relative to mURI
+
+ nsIURI* docBaseURI = document->GetDocBaseURI();
+ if (!docBaseURI) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoCString spec;
+ docBaseURI->GetSpec(spec);
+
+ rv = NS_NewURI(getter_AddRefs(newURI), aURL,
+ document->GetDocumentCharacterSet(), docBaseURI);
+
+ // 7.2: If 2a fails, raise a SECURITY_ERR
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ // 7.4 and 7.5: Same-origin check.
+ if (!nsContentUtils::URIIsLocalFile(newURI)) {
+ // In addition to checking that the security manager says that
+ // the new URI has the same origin as our current URI, we also
+ // check that the two URIs have the same userpass. (The
+ // security manager says that |http://foo.com| and
+ // |http://me@foo.com| have the same origin.) currentURI
+ // won't contain the password part of the userpass, so this
+ // means that it's never valid to specify a password in a
+ // pushState or replaceState URI.
+
+ nsCOMPtr<nsIScriptSecurityManager> secMan =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
+ NS_ENSURE_TRUE(secMan, NS_ERROR_FAILURE);
+
+ // It's very important that we check that newURI is of the same
+ // origin as currentURI, not docBaseURI, because a page can
+ // set docBaseURI arbitrarily to any domain.
+ nsAutoCString currentUserPass, newUserPass;
+ NS_ENSURE_SUCCESS(currentURI->GetUserPass(currentUserPass),
+ NS_ERROR_FAILURE);
+ NS_ENSURE_SUCCESS(newURI->GetUserPass(newUserPass), NS_ERROR_FAILURE);
+ bool isPrivateWin =
+ document->NodePrincipal()->OriginAttributesRef().mPrivateBrowsingId >
+ 0;
+ if (NS_FAILED(secMan->CheckSameOriginURI(currentURI, newURI, true,
+ isPrivateWin)) ||
+ !currentUserPass.Equals(newUserPass)) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+ } else {
+ // It's a file:// URI
+ nsCOMPtr<nsIPrincipal> principal = document->GetPrincipal();
+
+ if (!principal || NS_FAILED(principal->CheckMayLoadWithReporting(
+ newURI, false, document->InnerWindowID()))) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+ }
+
+ if (currentURI) {
+ currentURI->Equals(newURI, &equalURIs);
+ } else {
+ equalURIs = false;
+ }
+
+ } // end of same-origin check
+
+ // Step 8: call "URL and history update steps"
+ rv = UpdateURLAndHistory(document, newURI, scContainer, aTitle, aReplace,
+ currentURI, equalURIs);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult nsDocShell::UpdateURLAndHistory(Document* aDocument, nsIURI* aNewURI,
+ nsIStructuredCloneContainer* aData,
+ const nsAString& aTitle, bool aReplace,
+ nsIURI* aCurrentURI, bool aEqualURIs) {
+ // Implements
+ // https://html.spec.whatwg.org/multipage/history.html#url-and-history-update-steps
+
+ // If we have a pending title change, handle it before creating a new entry.
+ aDocument->DoNotifyPossibleTitleChange();
+
+ // Step 2, if aReplace is false: Create a new entry in the session
+ // history. This will erase all SHEntries after the new entry and make this
+ // entry the current one. This operation may modify mOSHE, which we need
+ // later, so we keep a reference here.
+ NS_ENSURE_TRUE(mOSHE || mActiveEntry || aReplace, NS_ERROR_FAILURE);
+ nsCOMPtr<nsISHEntry> oldOSHE = mOSHE;
+
+ // If this push/replaceState changed the document's current URI and the new
+ // URI differs from the old URI in more than the hash, or if the old
+ // SHEntry's URI was modified in this way by a push/replaceState call
+ // set URIWasModified to true for the current SHEntry (bug 669671).
+ bool sameExceptHashes = true;
+ aNewURI->EqualsExceptRef(aCurrentURI, &sameExceptHashes);
+ bool uriWasModified;
+ if (sameExceptHashes) {
+ if (mozilla::SessionHistoryInParent()) {
+ uriWasModified = mActiveEntry && mActiveEntry->GetURIWasModified();
+ } else {
+ uriWasModified = oldOSHE && oldOSHE->GetURIWasModified();
+ }
+ } else {
+ uriWasModified = true;
+ }
+
+ mLoadType = LOAD_PUSHSTATE;
+
+ nsCOMPtr<nsISHEntry> newSHEntry;
+ if (!aReplace) {
+ // Step 2.
+
+ // Step 2.2, "Remove any tasks queued by the history traversal task
+ // source that are associated with any Document objects in the
+ // top-level browsing context's document family." This is very hard in
+ // SessionHistoryInParent since we can't synchronously access the
+ // pending navigations that are already sent to the parent. We can
+ // abort any AsyncGo navigations that are waiting to be sent. If we
+ // send a message to the parent, it would be processed after any
+ // navigations previously sent. So long as we consider the "history
+ // traversal task source" to be the list in this process we match the
+ // spec. If we move the entire list to the parent, we can handle the
+ // aborting of loads there, but we don't have a way to synchronously
+ // remove entries as we do here for non-SHIP.
+ RefPtr<ChildSHistory> shistory = GetRootSessionHistory();
+ if (shistory) {
+ shistory->RemovePendingHistoryNavigations();
+ }
+
+ nsPoint scrollPos = GetCurScrollPos();
+
+ bool scrollRestorationIsManual;
+ if (mozilla::SessionHistoryInParent()) {
+ // FIXME Need to save the current scroll position on mActiveEntry.
+ scrollRestorationIsManual = mActiveEntry->GetScrollRestorationIsManual();
+ } else {
+ // Save the current scroll position (bug 590573). Step 2.3.
+ mOSHE->SetScrollPosition(scrollPos.x, scrollPos.y);
+
+ scrollRestorationIsManual = mOSHE->GetScrollRestorationIsManual();
+ }
+
+ nsCOMPtr<nsIContentSecurityPolicy> csp = aDocument->GetCsp();
+
+ if (mozilla::SessionHistoryInParent()) {
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("nsDocShell %p UpdateActiveEntry (not replacing)", this));
+ nsString title(mActiveEntry->GetTitle());
+ UpdateActiveEntry(false,
+ /* aPreviousScrollPos = */ Some(scrollPos), aNewURI,
+ /* aOriginalURI = */ nullptr,
+ /* aReferrerInfo = */ nullptr,
+ /* aTriggeringPrincipal = */ aDocument->NodePrincipal(),
+ csp, title, scrollRestorationIsManual, aData,
+ uriWasModified);
+ } else {
+ // Since we're not changing which page we have loaded, pass
+ // true for aCloneChildren.
+ nsresult rv = AddToSessionHistory(
+ aNewURI, nullptr,
+ aDocument->NodePrincipal(), // triggeringPrincipal
+ nullptr, nullptr, csp, true, getter_AddRefs(newSHEntry));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ENSURE_TRUE(newSHEntry, NS_ERROR_FAILURE);
+
+ // Session history entries created by pushState inherit scroll restoration
+ // mode from the current entry.
+ newSHEntry->SetScrollRestorationIsManual(scrollRestorationIsManual);
+
+ nsString title;
+ mOSHE->GetTitle(title);
+
+ // Set the new SHEntry's title (bug 655273).
+ newSHEntry->SetTitle(title);
+
+ // Link the new SHEntry to the old SHEntry's BFCache entry, since the
+ // two entries correspond to the same document.
+ NS_ENSURE_SUCCESS(newSHEntry->AdoptBFCacheEntry(oldOSHE),
+ NS_ERROR_FAILURE);
+
+ // AddToSessionHistory may not modify mOSHE. In case it doesn't,
+ // we'll just set mOSHE here.
+ mOSHE = newSHEntry;
+ }
+ } else if (mozilla::SessionHistoryInParent()) {
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("nsDocShell %p UpdateActiveEntry (replacing) mActiveEntry %p",
+ this, mActiveEntry.get()));
+ // Setting the resultPrincipalURI to nullptr is fine here: it will cause
+ // NS_GetFinalChannelURI to use the originalURI as the URI, which is aNewURI
+ // in our case. We could also set it to aNewURI, with the same result.
+ // We don't use aTitle here, see bug 544535.
+ nsString title;
+ nsCOMPtr<nsIReferrerInfo> referrerInfo;
+ if (mActiveEntry) {
+ title = mActiveEntry->GetTitle();
+ referrerInfo = mActiveEntry->GetReferrerInfo();
+ } else {
+ referrerInfo = nullptr;
+ }
+ UpdateActiveEntry(
+ true, /* aPreviousScrollPos = */ Nothing(), aNewURI, aNewURI,
+ /* aReferrerInfo = */ referrerInfo, aDocument->NodePrincipal(),
+ aDocument->GetCsp(), title,
+ mActiveEntry && mActiveEntry->GetScrollRestorationIsManual(), aData,
+ uriWasModified);
+ } else {
+ // Step 3.
+ newSHEntry = mOSHE;
+
+ MOZ_LOG(gSHLog, LogLevel::Debug, ("nsDocShell %p step 3", this));
+ // Since we're not changing which page we have loaded, pass
+ // true for aCloneChildren.
+ if (!newSHEntry) {
+ nsresult rv = AddToSessionHistory(
+ aNewURI, nullptr,
+ aDocument->NodePrincipal(), // triggeringPrincipal
+ nullptr, nullptr, aDocument->GetCsp(), true,
+ getter_AddRefs(newSHEntry));
+ NS_ENSURE_SUCCESS(rv, rv);
+ mOSHE = newSHEntry;
+ }
+
+ newSHEntry->SetURI(aNewURI);
+ newSHEntry->SetOriginalURI(aNewURI);
+ // We replaced the URI of the entry, clear the unstripped URI as it
+ // shouldn't be used for reloads anymore.
+ newSHEntry->SetUnstrippedURI(nullptr);
+ // Setting the resultPrincipalURI to nullptr is fine here: it will cause
+ // NS_GetFinalChannelURI to use the originalURI as the URI, which is aNewURI
+ // in our case. We could also set it to aNewURI, with the same result.
+ newSHEntry->SetResultPrincipalURI(nullptr);
+ newSHEntry->SetLoadReplace(false);
+ }
+
+ if (!mozilla::SessionHistoryInParent()) {
+ // Step 2.4 and 3: Modify new/original session history entry and clear its
+ // POST data, if there is any.
+ newSHEntry->SetStateData(aData);
+ newSHEntry->SetPostData(nullptr);
+
+ newSHEntry->SetURIWasModified(uriWasModified);
+
+ // Step E as described at the top of AddState: If aReplace is false,
+ // indicating that we're doing a pushState rather than a replaceState,
+ // notify bfcache that we've added a page to the history so it can evict
+ // content viewers if appropriate. Otherwise call ReplaceEntry so that we
+ // notify nsIHistoryListeners that an entry was replaced. We may not have a
+ // root session history if this call is coming from a document.open() in a
+ // docshell subtree that disables session history.
+ RefPtr<ChildSHistory> rootSH = GetRootSessionHistory();
+ if (rootSH) {
+ rootSH->LegacySHistory()->EvictDocumentViewersOrReplaceEntry(newSHEntry,
+ aReplace);
+ }
+ }
+
+ // Step 4: If the document's URI changed, update document's URI and update
+ // global history.
+ //
+ // We need to call FireOnLocationChange so that the browser's address bar
+ // gets updated and the back button is enabled, but we only need to
+ // explicitly call FireOnLocationChange if we're not calling SetCurrentURI,
+ // since SetCurrentURI will call FireOnLocationChange for us.
+ //
+ // Both SetCurrentURI(...) and FireDummyOnLocationChange() pass
+ // nullptr for aRequest param to FireOnLocationChange(...). Such an update
+ // notification is allowed only when we know docshell is not loading a new
+ // document and it requires LOCATION_CHANGE_SAME_DOCUMENT flag. Otherwise,
+ // FireOnLocationChange(...) breaks security UI.
+ //
+ // If the docshell is shutting down, don't update the document URI, as we
+ // can't load into a docshell that is being destroyed.
+ if (!aEqualURIs && !mIsBeingDestroyed) {
+ aDocument->SetDocumentURI(aNewURI);
+ SetCurrentURI(aNewURI, nullptr, /* aFireLocationChange */ true,
+ /* aIsInitialAboutBlank */ false,
+ GetSameDocumentNavigationFlags(aNewURI));
+
+ AddURIVisit(aNewURI, aCurrentURI, 0);
+
+ // AddURIVisit doesn't set the title for the new URI in global history,
+ // so do that here.
+ UpdateGlobalHistoryTitle(aNewURI);
+
+ // Inform the favicon service that our old favicon applies to this new
+ // URI.
+ CopyFavicon(aCurrentURI, aNewURI, UsePrivateBrowsing());
+ } else {
+ FireDummyOnLocationChange();
+ }
+ aDocument->SetStateObject(aData);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetCurrentScrollRestorationIsManual(bool* aIsManual) {
+ if (mozilla::SessionHistoryInParent()) {
+ *aIsManual = mActiveEntry && mActiveEntry->GetScrollRestorationIsManual();
+ return NS_OK;
+ }
+
+ *aIsManual = false;
+ if (mOSHE) {
+ return mOSHE->GetScrollRestorationIsManual(aIsManual);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetCurrentScrollRestorationIsManual(bool aIsManual) {
+ SetScrollRestorationIsManualOnHistoryEntry(mOSHE, aIsManual);
+
+ return NS_OK;
+}
+
+void nsDocShell::SetScrollRestorationIsManualOnHistoryEntry(
+ nsISHEntry* aSHEntry, bool aIsManual) {
+ if (aSHEntry) {
+ aSHEntry->SetScrollRestorationIsManual(aIsManual);
+ }
+
+ if (mActiveEntry && mBrowsingContext) {
+ mActiveEntry->SetScrollRestorationIsManual(aIsManual);
+ if (XRE_IsParentProcess()) {
+ SessionHistoryEntry* entry =
+ mBrowsingContext->Canonical()->GetActiveSessionHistoryEntry();
+ if (entry) {
+ entry->SetScrollRestorationIsManual(aIsManual);
+ }
+ } else {
+ mozilla::Unused << ContentChild::GetSingleton()
+ ->SendSessionHistoryEntryScrollRestorationIsManual(
+ mBrowsingContext, aIsManual);
+ }
+ }
+}
+
+void nsDocShell::SetCacheKeyOnHistoryEntry(nsISHEntry* aSHEntry,
+ uint32_t aCacheKey) {
+ if (aSHEntry) {
+ aSHEntry->SetCacheKey(aCacheKey);
+ }
+
+ if (mActiveEntry && mBrowsingContext) {
+ mActiveEntry->SetCacheKey(aCacheKey);
+ if (XRE_IsParentProcess()) {
+ SessionHistoryEntry* entry =
+ mBrowsingContext->Canonical()->GetActiveSessionHistoryEntry();
+ if (entry) {
+ entry->SetCacheKey(aCacheKey);
+ }
+ } else {
+ mozilla::Unused
+ << ContentChild::GetSingleton()->SendSessionHistoryEntryCacheKey(
+ mBrowsingContext, aCacheKey);
+ }
+ }
+}
+
+/* static */
+bool nsDocShell::ShouldAddToSessionHistory(nsIURI* aURI, nsIChannel* aChannel) {
+ // I believe none of the about: urls should go in the history. But then
+ // that could just be me... If the intent is only deny about:blank then we
+ // should just do a spec compare, rather than two gets of the scheme and
+ // then the path. -Gagan
+ nsresult rv;
+ nsAutoCString buf;
+
+ rv = aURI->GetScheme(buf);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ if (buf.EqualsLiteral("about")) {
+ rv = aURI->GetPathQueryRef(buf);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ if (buf.EqualsLiteral("blank")) {
+ return false;
+ }
+ // We only want to add about:newtab if it's not privileged, and
+ // if it is not configured to show the blank page.
+ if (buf.EqualsLiteral("newtab")) {
+ if (!StaticPrefs::browser_newtabpage_enabled()) {
+ return false;
+ }
+
+ NS_ENSURE_TRUE(aChannel, false);
+ nsCOMPtr<nsIPrincipal> resultPrincipal;
+ rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
+ aChannel, getter_AddRefs(resultPrincipal));
+ NS_ENSURE_SUCCESS(rv, false);
+ return !resultPrincipal->IsSystemPrincipal();
+ }
+ }
+
+ return true;
+}
+
+nsresult nsDocShell::AddToSessionHistory(
+ nsIURI* aURI, nsIChannel* aChannel, nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit,
+ nsIPrincipal* aPartitionedPrincipalToInherit,
+ nsIContentSecurityPolicy* aCsp, bool aCloneChildren,
+ nsISHEntry** aNewEntry) {
+ MOZ_ASSERT(aURI, "uri is null");
+ MOZ_ASSERT(!aChannel || !aTriggeringPrincipal, "Shouldn't have both set");
+ MOZ_DIAGNOSTIC_ASSERT(!mozilla::SessionHistoryInParent());
+
+#if defined(DEBUG)
+ if (MOZ_LOG_TEST(gDocShellLog, LogLevel::Debug)) {
+ nsAutoCString chanName;
+ if (aChannel) {
+ aChannel->GetName(chanName);
+ } else {
+ chanName.AssignLiteral("<no channel>");
+ }
+
+ MOZ_LOG(gDocShellLog, LogLevel::Debug,
+ ("nsDocShell[%p]::AddToSessionHistory(\"%s\", [%s])\n", this,
+ aURI->GetSpecOrDefault().get(), chanName.get()));
+ }
+#endif
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsISHEntry> entry;
+
+ /*
+ * If this is a LOAD_FLAGS_REPLACE_HISTORY in a subframe, we use
+ * the existing SH entry in the page and replace the url and
+ * other vitalities.
+ */
+ if (LOAD_TYPE_HAS_FLAGS(mLoadType, LOAD_FLAGS_REPLACE_HISTORY) &&
+ !mBrowsingContext->IsTop()) {
+ // This is a subframe
+ entry = mOSHE;
+ if (entry) {
+ entry->ClearEntry();
+ }
+ }
+
+ // Create a new entry if necessary.
+ if (!entry) {
+ entry = new nsSHEntry();
+ }
+
+ // Get the post data & referrer
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsCOMPtr<nsIURI> originalURI;
+ nsCOMPtr<nsIURI> resultPrincipalURI;
+ nsCOMPtr<nsIURI> unstrippedURI;
+ bool loadReplace = false;
+ nsCOMPtr<nsIReferrerInfo> referrerInfo;
+ uint32_t cacheKey = 0;
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal = aTriggeringPrincipal;
+ nsCOMPtr<nsIPrincipal> principalToInherit = aPrincipalToInherit;
+ nsCOMPtr<nsIPrincipal> partitionedPrincipalToInherit =
+ aPartitionedPrincipalToInherit;
+ nsCOMPtr<nsIContentSecurityPolicy> csp = aCsp;
+ bool expired = false; // by default the page is not expired
+ bool discardLayoutState = false;
+ nsCOMPtr<nsICacheInfoChannel> cacheChannel;
+ bool userActivation = false;
+
+ if (aChannel) {
+ cacheChannel = do_QueryInterface(aChannel);
+
+ /* If there is a caching channel, get the Cache Key and store it
+ * in SH.
+ */
+ if (cacheChannel) {
+ cacheChannel->GetCacheKey(&cacheKey);
+ }
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel));
+
+ // Check if the httpChannel is hiding under a multipartChannel
+ if (!httpChannel) {
+ GetHttpChannel(aChannel, getter_AddRefs(httpChannel));
+ }
+ if (httpChannel) {
+ nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(httpChannel));
+ if (uploadChannel) {
+ uploadChannel->GetUploadStream(getter_AddRefs(inputStream));
+ }
+ httpChannel->GetOriginalURI(getter_AddRefs(originalURI));
+ uint32_t loadFlags;
+ aChannel->GetLoadFlags(&loadFlags);
+ loadReplace = loadFlags & nsIChannel::LOAD_REPLACE;
+ rv = httpChannel->GetReferrerInfo(getter_AddRefs(referrerInfo));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ discardLayoutState = ShouldDiscardLayoutState(httpChannel);
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ if (!triggeringPrincipal) {
+ triggeringPrincipal = loadInfo->TriggeringPrincipal();
+ }
+ if (!csp) {
+ csp = loadInfo->GetCspToInherit();
+ }
+
+ loadInfo->GetResultPrincipalURI(getter_AddRefs(resultPrincipalURI));
+
+ loadInfo->GetUnstrippedURI(getter_AddRefs(unstrippedURI));
+
+ userActivation = loadInfo->GetHasValidUserGestureActivation();
+
+ // For now keep storing just the principal in the SHEntry.
+ if (!principalToInherit) {
+ if (loadInfo->GetLoadingSandboxed()) {
+ if (loadInfo->GetLoadingPrincipal()) {
+ principalToInherit = NullPrincipal::CreateWithInheritedAttributes(
+ loadInfo->GetLoadingPrincipal());
+ } else {
+ // get the OriginAttributes
+ OriginAttributes attrs;
+ loadInfo->GetOriginAttributes(&attrs);
+ principalToInherit = NullPrincipal::Create(attrs);
+ }
+ } else {
+ principalToInherit = loadInfo->PrincipalToInherit();
+ }
+ }
+
+ if (!partitionedPrincipalToInherit) {
+ // XXXehsan is it correct to fall back to the principal to inherit in all
+ // cases? For example, what about the cases where we are using the load
+ // info's principal to inherit? Do we need to add a similar concept to
+ // load info for partitioned principal?
+ partitionedPrincipalToInherit = principalToInherit;
+ }
+ }
+
+ nsAutoString srcdoc;
+ bool srcdocEntry = false;
+ nsCOMPtr<nsIURI> baseURI;
+
+ nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(aChannel);
+ if (inStrmChan) {
+ bool isSrcdocChannel;
+ inStrmChan->GetIsSrcdocChannel(&isSrcdocChannel);
+ if (isSrcdocChannel) {
+ inStrmChan->GetSrcdocData(srcdoc);
+ srcdocEntry = true;
+ inStrmChan->GetBaseURI(getter_AddRefs(baseURI));
+ } else {
+ srcdoc.SetIsVoid(true);
+ }
+ }
+ /* If cache got a 'no-store', ask SH not to store
+ * HistoryLayoutState. By default, SH will set this
+ * flag to true and save HistoryLayoutState.
+ */
+ bool saveLayoutState = !discardLayoutState;
+
+ if (cacheChannel) {
+ // Check if the page has expired from cache
+ uint32_t expTime = 0;
+ cacheChannel->GetCacheTokenExpirationTime(&expTime);
+ uint32_t now = PRTimeToSeconds(PR_Now());
+ if (expTime <= now) {
+ expired = true;
+ }
+ }
+
+ // Title is set in nsDocShell::SetTitle()
+ entry->Create(aURI, // uri
+ u""_ns, // Title
+ inputStream, // Post data stream
+ cacheKey, // CacheKey
+ mContentTypeHint, // Content-type
+ triggeringPrincipal, // Channel or provided principal
+ principalToInherit, partitionedPrincipalToInherit, csp,
+ HistoryID(), GetCreatedDynamically(), originalURI,
+ resultPrincipalURI, unstrippedURI, loadReplace, referrerInfo,
+ srcdoc, srcdocEntry, baseURI, saveLayoutState, expired,
+ userActivation);
+
+ if (mBrowsingContext->IsTop() && GetSessionHistory()) {
+ bool shouldPersist = ShouldAddToSessionHistory(aURI, aChannel);
+ Maybe<int32_t> previousEntryIndex;
+ Maybe<int32_t> loadedEntryIndex;
+ rv = GetSessionHistory()->LegacySHistory()->AddToRootSessionHistory(
+ aCloneChildren, mOSHE, mBrowsingContext, entry, mLoadType,
+ shouldPersist, &previousEntryIndex, &loadedEntryIndex);
+
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "Could not add entry to root session history");
+ if (previousEntryIndex.isSome()) {
+ mPreviousEntryIndex = previousEntryIndex.value();
+ }
+ if (loadedEntryIndex.isSome()) {
+ mLoadedEntryIndex = loadedEntryIndex.value();
+ }
+
+ // aCloneChildren implies that we are retaining the same document, thus we
+ // need to signal to the top WC that the new SHEntry may receive a fresh
+ // user interaction flag.
+ if (aCloneChildren) {
+ WindowContext* topWc = mBrowsingContext->GetTopWindowContext();
+ if (topWc && !topWc->IsDiscarded()) {
+ MOZ_ALWAYS_SUCCEEDS(topWc->SetSHEntryHasUserInteraction(false));
+ }
+ }
+ } else {
+ // This is a subframe, make sure that this new SHEntry will be
+ // marked with user interaction.
+ WindowContext* topWc = mBrowsingContext->GetTopWindowContext();
+ if (topWc && !topWc->IsDiscarded()) {
+ MOZ_ALWAYS_SUCCEEDS(topWc->SetSHEntryHasUserInteraction(false));
+ }
+ if (!mOSHE || !LOAD_TYPE_HAS_FLAGS(mLoadType, LOAD_FLAGS_REPLACE_HISTORY)) {
+ rv = AddChildSHEntryToParent(entry, mBrowsingContext->ChildOffset(),
+ aCloneChildren);
+ }
+ }
+
+ // Return the new SH entry...
+ if (aNewEntry) {
+ *aNewEntry = nullptr;
+ if (NS_SUCCEEDED(rv)) {
+ entry.forget(aNewEntry);
+ }
+ }
+
+ return rv;
+}
+
+void nsDocShell::UpdateActiveEntry(
+ bool aReplace, const Maybe<nsPoint>& aPreviousScrollPos, nsIURI* aURI,
+ nsIURI* aOriginalURI, nsIReferrerInfo* aReferrerInfo,
+ nsIPrincipal* aTriggeringPrincipal, nsIContentSecurityPolicy* aCsp,
+ const nsAString& aTitle, bool aScrollRestorationIsManual,
+ nsIStructuredCloneContainer* aData, bool aURIWasModified) {
+ MOZ_ASSERT(mozilla::SessionHistoryInParent());
+ MOZ_ASSERT(aURI, "uri is null");
+ MOZ_ASSERT(mLoadType == LOAD_PUSHSTATE,
+ "This code only deals with pushState");
+ MOZ_ASSERT_IF(aPreviousScrollPos.isSome(), !aReplace);
+
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("Creating an active entry on nsDocShell %p to %s", this,
+ aURI->GetSpecOrDefault().get()));
+
+ // Even if we're replacing an existing entry we create new a
+ // SessionHistoryInfo. In the parent process we'll keep the existing
+ // SessionHistoryEntry, but just replace its SessionHistoryInfo, that way the
+ // entry keeps identity but its data is replaced.
+ bool replace = aReplace && mActiveEntry;
+
+ if (!replace) {
+ CollectWireframe();
+ }
+
+ if (mActiveEntry) {
+ // Link this entry to the previous active entry.
+ mActiveEntry = MakeUnique<SessionHistoryInfo>(*mActiveEntry, aURI);
+ } else {
+ mActiveEntry = MakeUnique<SessionHistoryInfo>(
+ aURI, aTriggeringPrincipal, nullptr, nullptr, aCsp, mContentTypeHint);
+ }
+ mActiveEntry->SetOriginalURI(aOriginalURI);
+ mActiveEntry->SetUnstrippedURI(nullptr);
+ mActiveEntry->SetReferrerInfo(aReferrerInfo);
+ mActiveEntry->SetTitle(aTitle);
+ mActiveEntry->SetStateData(static_cast<nsStructuredCloneContainer*>(aData));
+ mActiveEntry->SetURIWasModified(aURIWasModified);
+ mActiveEntry->SetScrollRestorationIsManual(aScrollRestorationIsManual);
+
+ if (replace) {
+ mBrowsingContext->ReplaceActiveSessionHistoryEntry(mActiveEntry.get());
+ } else {
+ mBrowsingContext->IncrementHistoryEntryCountForBrowsingContext();
+ // FIXME We should probably just compute mChildOffset in the parent
+ // instead of passing it over IPC here.
+ mBrowsingContext->SetActiveSessionHistoryEntry(
+ aPreviousScrollPos, mActiveEntry.get(), mLoadType,
+ /* aCacheKey = */ 0);
+ // FIXME Do we need to update mPreviousEntryIndex and mLoadedEntryIndex?
+ }
+}
+
+nsresult nsDocShell::LoadHistoryEntry(nsISHEntry* aEntry, uint32_t aLoadType,
+ bool aUserActivation) {
+ NS_ENSURE_TRUE(aEntry, NS_ERROR_FAILURE);
+
+ nsresult rv;
+ RefPtr<nsDocShellLoadState> loadState;
+ rv = aEntry->CreateLoadInfo(getter_AddRefs(loadState));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Calling CreateAboutBlankDocumentViewer can set mOSHE to null, and if
+ // that's the only thing holding a ref to aEntry that will cause aEntry to
+ // die while we're loading it. So hold a strong ref to aEntry here, just
+ // in case.
+ nsCOMPtr<nsISHEntry> kungFuDeathGrip(aEntry);
+
+ loadState->SetHasValidUserGestureActivation(
+ loadState->HasValidUserGestureActivation() || aUserActivation);
+
+ return LoadHistoryEntry(loadState, aLoadType, aEntry == mOSHE);
+}
+
+nsresult nsDocShell::LoadHistoryEntry(const LoadingSessionHistoryInfo& aEntry,
+ uint32_t aLoadType,
+ bool aUserActivation) {
+ RefPtr<nsDocShellLoadState> loadState = aEntry.CreateLoadInfo();
+ loadState->SetHasValidUserGestureActivation(
+ loadState->HasValidUserGestureActivation() || aUserActivation);
+
+ return LoadHistoryEntry(loadState, aLoadType, aEntry.mLoadingCurrentEntry);
+}
+
+nsresult nsDocShell::LoadHistoryEntry(nsDocShellLoadState* aLoadState,
+ uint32_t aLoadType,
+ bool aLoadingCurrentEntry) {
+ if (!IsNavigationAllowed()) {
+ return NS_OK;
+ }
+
+ // We are setting load type afterwards so we don't have to
+ // send it in an IPC message
+ aLoadState->SetLoadType(aLoadType);
+
+ nsresult rv;
+ if (SchemeIsJavascript(aLoadState->URI())) {
+ // We're loading a URL that will execute script from inside asyncOpen.
+ // Replace the current document with about:blank now to prevent
+ // anything from the current document from leaking into any JavaScript
+ // code in the URL.
+ // Don't cache the presentation if we're going to just reload the
+ // current entry. Caching would lead to trying to save the different
+ // content viewers in the same nsISHEntry object.
+ rv = CreateAboutBlankDocumentViewer(
+ aLoadState->PrincipalToInherit(),
+ aLoadState->PartitionedPrincipalToInherit(), nullptr, nullptr,
+ /* aIsInitialDocument */ false, Nothing(), !aLoadingCurrentEntry);
+
+ if (NS_FAILED(rv)) {
+ // The creation of the intermittent about:blank content
+ // viewer failed for some reason (potentially because the
+ // user prevented it). Interrupt the history load.
+ return NS_OK;
+ }
+
+ if (!aLoadState->TriggeringPrincipal()) {
+ // Ensure that we have a triggeringPrincipal. Otherwise javascript:
+ // URIs will pick it up from the about:blank page we just loaded,
+ // and we don't really want even that in this case.
+ nsCOMPtr<nsIPrincipal> principal =
+ NullPrincipal::Create(GetOriginAttributes());
+ aLoadState->SetTriggeringPrincipal(principal);
+ }
+ }
+
+ /* If there is a valid postdata *and* the user pressed
+ * reload or shift-reload, take user's permission before we
+ * repost the data to the server.
+ */
+ if ((aLoadType & LOAD_CMD_RELOAD) && aLoadState->PostDataStream()) {
+ bool repost;
+ rv = ConfirmRepost(&repost);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // If the user pressed cancel in the dialog, return. We're done here.
+ if (!repost) {
+ return NS_BINDING_ABORTED;
+ }
+ }
+
+ // If there is no valid triggeringPrincipal, we deny the load
+ MOZ_ASSERT(aLoadState->TriggeringPrincipal(),
+ "need a valid triggeringPrincipal to load from history");
+ if (!aLoadState->TriggeringPrincipal()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return InternalLoad(aLoadState); // No nsIRequest
+}
+
+NS_IMETHODIMP
+nsDocShell::PersistLayoutHistoryState() {
+ nsresult rv = NS_OK;
+
+ if (mozilla::SessionHistoryInParent() ? !!mActiveEntry : !!mOSHE) {
+ bool scrollRestorationIsManual;
+ if (mozilla::SessionHistoryInParent()) {
+ scrollRestorationIsManual = mActiveEntry->GetScrollRestorationIsManual();
+ } else {
+ scrollRestorationIsManual = mOSHE->GetScrollRestorationIsManual();
+ }
+ nsCOMPtr<nsILayoutHistoryState> layoutState;
+ if (RefPtr<PresShell> presShell = GetPresShell()) {
+ rv = presShell->CaptureHistoryState(getter_AddRefs(layoutState));
+ } else if (scrollRestorationIsManual) {
+ // Even if we don't have layout anymore, we may want to reset the
+ // current scroll state in layout history.
+ GetLayoutHistoryState(getter_AddRefs(layoutState));
+ }
+
+ if (scrollRestorationIsManual && layoutState) {
+ layoutState->ResetScrollState();
+ }
+ }
+
+ return rv;
+}
+
+void nsDocShell::SwapHistoryEntries(nsISHEntry* aOldEntry,
+ nsISHEntry* aNewEntry) {
+ if (aOldEntry == mOSHE) {
+ mOSHE = aNewEntry;
+ }
+
+ if (aOldEntry == mLSHE) {
+ mLSHE = aNewEntry;
+ }
+}
+
+void nsDocShell::SetHistoryEntryAndUpdateBC(const Maybe<nsISHEntry*>& aLSHE,
+ const Maybe<nsISHEntry*>& aOSHE) {
+ // We want to hold on to the reference in mLSHE before we update it.
+ // Otherwise, SetHistoryEntry could release the last reference to
+ // the entry while aOSHE is pointing to it.
+ nsCOMPtr<nsISHEntry> deathGripOldLSHE;
+ if (aLSHE.isSome()) {
+ deathGripOldLSHE = SetHistoryEntry(&mLSHE, aLSHE.value());
+ MOZ_ASSERT(mLSHE.get() == aLSHE.value());
+ }
+ nsCOMPtr<nsISHEntry> deathGripOldOSHE;
+ if (aOSHE.isSome()) {
+ deathGripOldOSHE = SetHistoryEntry(&mOSHE, aOSHE.value());
+ MOZ_ASSERT(mOSHE.get() == aOSHE.value());
+ }
+}
+
+already_AddRefed<nsISHEntry> nsDocShell::SetHistoryEntry(
+ nsCOMPtr<nsISHEntry>* aPtr, nsISHEntry* aEntry) {
+ // We need to sync up the docshell and session history trees for
+ // subframe navigation. If the load was in a subframe, we forward up to
+ // the root docshell, which will then recursively sync up all docshells
+ // to their corresponding entries in the new session history tree.
+ // If we don't do this, then we can cache a content viewer on the wrong
+ // cloned entry, and subsequently restore it at the wrong time.
+ RefPtr<BrowsingContext> topBC = mBrowsingContext->Top();
+ if (topBC->IsDiscarded()) {
+ topBC = nullptr;
+ }
+ RefPtr<BrowsingContext> currBC =
+ mBrowsingContext->IsDiscarded() ? nullptr : mBrowsingContext;
+ if (topBC && *aPtr) {
+ (*aPtr)->SyncTreesForSubframeNavigation(aEntry, topBC, currBC);
+ }
+ nsCOMPtr<nsISHEntry> entry(aEntry);
+ entry.swap(*aPtr);
+ return entry.forget();
+}
+
+already_AddRefed<ChildSHistory> nsDocShell::GetRootSessionHistory() {
+ RefPtr<ChildSHistory> childSHistory =
+ mBrowsingContext->Top()->GetChildSessionHistory();
+ return childSHistory.forget();
+}
+
+nsresult nsDocShell::GetHttpChannel(nsIChannel* aChannel,
+ nsIHttpChannel** aReturn) {
+ NS_ENSURE_ARG_POINTER(aReturn);
+ if (!aChannel) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIMultiPartChannel> multiPartChannel(do_QueryInterface(aChannel));
+ if (multiPartChannel) {
+ nsCOMPtr<nsIChannel> baseChannel;
+ multiPartChannel->GetBaseChannel(getter_AddRefs(baseChannel));
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(baseChannel));
+ *aReturn = httpChannel;
+ NS_IF_ADDREF(*aReturn);
+ }
+ return NS_OK;
+}
+
+bool nsDocShell::ShouldDiscardLayoutState(nsIHttpChannel* aChannel) {
+ // By default layout State will be saved.
+ if (!aChannel) {
+ return false;
+ }
+
+ // figure out if SH should be saving layout state
+ bool noStore = false;
+ Unused << aChannel->IsNoStoreResponse(&noStore);
+ return noStore;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetEditor(nsIEditor** aEditor) {
+ NS_ENSURE_ARG_POINTER(aEditor);
+ RefPtr<HTMLEditor> htmlEditor = GetHTMLEditorInternal();
+ htmlEditor.forget(aEditor);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetEditor(nsIEditor* aEditor) {
+ HTMLEditor* htmlEditor = aEditor ? aEditor->GetAsHTMLEditor() : nullptr;
+ // If TextEditor comes, throw an error.
+ if (aEditor && !htmlEditor) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ return SetHTMLEditorInternal(htmlEditor);
+}
+
+HTMLEditor* nsDocShell::GetHTMLEditorInternal() {
+ return mEditorData ? mEditorData->GetHTMLEditor() : nullptr;
+}
+
+nsresult nsDocShell::SetHTMLEditorInternal(HTMLEditor* aHTMLEditor) {
+ if (!aHTMLEditor && !mEditorData) {
+ return NS_OK;
+ }
+
+ nsresult rv = EnsureEditorData();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return mEditorData->SetHTMLEditor(aHTMLEditor);
+}
+
+NS_IMETHODIMP
+nsDocShell::GetEditable(bool* aEditable) {
+ NS_ENSURE_ARG_POINTER(aEditable);
+ *aEditable = mEditorData && mEditorData->GetEditable();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetHasEditingSession(bool* aHasEditingSession) {
+ NS_ENSURE_ARG_POINTER(aHasEditingSession);
+
+ if (mEditorData) {
+ *aHasEditingSession = !!mEditorData->GetEditingSession();
+ } else {
+ *aHasEditingSession = false;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::MakeEditable(bool aInWaitForUriLoad) {
+ nsresult rv = EnsureEditorData();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return mEditorData->MakeEditable(aInWaitForUriLoad);
+}
+
+/* static */ bool nsDocShell::ShouldAddURIVisit(nsIChannel* aChannel) {
+ bool needToAddURIVisit = true;
+ nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(aChannel));
+ if (props) {
+ mozilla::Unused << props->GetPropertyAsBool(
+ u"docshell.needToAddURIVisit"_ns, &needToAddURIVisit);
+ }
+
+ return needToAddURIVisit;
+}
+
+/* static */ void nsDocShell::ExtractLastVisit(
+ nsIChannel* aChannel, nsIURI** aURI, uint32_t* aChannelRedirectFlags) {
+ nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(aChannel));
+ if (!props) {
+ return;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIURI> uri(do_GetProperty(props, u"docshell.previousURI"_ns, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ uri.forget(aURI);
+
+ rv = props->GetPropertyAsUint32(u"docshell.previousFlags"_ns,
+ aChannelRedirectFlags);
+
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "Could not fetch previous flags, URI will be treated like referrer");
+
+ } else {
+ // There is no last visit for this channel, so this must be the first
+ // link. Link the visit to the referrer of this request, if any.
+ // Treat referrer as null if there is an error getting it.
+ NS_GetReferrerFromChannel(aChannel, aURI);
+ }
+}
+
+void nsDocShell::SaveLastVisit(nsIChannel* aChannel, nsIURI* aURI,
+ uint32_t aChannelRedirectFlags) {
+ nsCOMPtr<nsIWritablePropertyBag2> props(do_QueryInterface(aChannel));
+ if (!props || !aURI) {
+ return;
+ }
+
+ props->SetPropertyAsInterface(u"docshell.previousURI"_ns, aURI);
+ props->SetPropertyAsUint32(u"docshell.previousFlags"_ns,
+ aChannelRedirectFlags);
+}
+
+/* static */ void nsDocShell::InternalAddURIVisit(
+ nsIURI* aURI, nsIURI* aPreviousURI, uint32_t aChannelRedirectFlags,
+ uint32_t aResponseStatus, BrowsingContext* aBrowsingContext,
+ nsIWidget* aWidget, uint32_t aLoadType, bool aWasUpgraded) {
+ MOZ_ASSERT(aURI, "Visited URI is null!");
+ MOZ_ASSERT(aLoadType != LOAD_ERROR_PAGE && aLoadType != LOAD_BYPASS_HISTORY,
+ "Do not add error or bypass pages to global history");
+
+ bool usePrivateBrowsing = false;
+ aBrowsingContext->GetUsePrivateBrowsing(&usePrivateBrowsing);
+
+ // Only content-type docshells save URI visits. Also don't do
+ // anything here if we're not supposed to use global history.
+ if (!aBrowsingContext->IsContent() ||
+ !aBrowsingContext->GetUseGlobalHistory() || usePrivateBrowsing) {
+ return;
+ }
+
+ nsCOMPtr<IHistory> history = components::History::Service();
+
+ if (history) {
+ uint32_t visitURIFlags = 0;
+
+ if (aBrowsingContext->IsTop()) {
+ visitURIFlags |= IHistory::TOP_LEVEL;
+ }
+
+ if (aChannelRedirectFlags & nsIChannelEventSink::REDIRECT_TEMPORARY) {
+ visitURIFlags |= IHistory::REDIRECT_TEMPORARY;
+ } else if (aChannelRedirectFlags &
+ nsIChannelEventSink::REDIRECT_PERMANENT) {
+ visitURIFlags |= IHistory::REDIRECT_PERMANENT;
+ } else {
+ MOZ_ASSERT(!aChannelRedirectFlags,
+ "One of REDIRECT_TEMPORARY or REDIRECT_PERMANENT must be set "
+ "if any flags in aChannelRedirectFlags is set.");
+ }
+
+ if (aResponseStatus >= 300 && aResponseStatus < 400) {
+ visitURIFlags |= IHistory::REDIRECT_SOURCE;
+ if (aResponseStatus == 301 || aResponseStatus == 308) {
+ visitURIFlags |= IHistory::REDIRECT_SOURCE_PERMANENT;
+ }
+ }
+ // Errors 400-501 and 505 are considered unrecoverable, in the sense a
+ // simple retry attempt by the user is unlikely to solve them.
+ // 408 is special cased, since may actually indicate a temporary
+ // connection problem.
+ else if (aResponseStatus != 408 &&
+ ((aResponseStatus >= 400 && aResponseStatus <= 501) ||
+ aResponseStatus == 505)) {
+ visitURIFlags |= IHistory::UNRECOVERABLE_ERROR;
+ }
+
+ if (aWasUpgraded) {
+ visitURIFlags |=
+ IHistory::REDIRECT_SOURCE | IHistory::REDIRECT_SOURCE_UPGRADED;
+ }
+
+ mozilla::Unused << history->VisitURI(aWidget, aURI, aPreviousURI,
+ visitURIFlags,
+ aBrowsingContext->BrowserId());
+ }
+}
+
+void nsDocShell::AddURIVisit(nsIURI* aURI, nsIURI* aPreviousURI,
+ uint32_t aChannelRedirectFlags,
+ uint32_t aResponseStatus) {
+ nsPIDOMWindowOuter* outer = GetWindow();
+ nsCOMPtr<nsIWidget> widget = widget::WidgetUtils::DOMWindowToWidget(outer);
+
+ InternalAddURIVisit(aURI, aPreviousURI, aChannelRedirectFlags,
+ aResponseStatus, mBrowsingContext, widget, mLoadType,
+ false);
+}
+
+//*****************************************************************************
+// nsDocShell: Helper Routines
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsDocShell::SetLoadType(uint32_t aLoadType) {
+ mLoadType = aLoadType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetLoadType(uint32_t* aLoadType) {
+ *aLoadType = mLoadType;
+ return NS_OK;
+}
+
+nsresult nsDocShell::ConfirmRepost(bool* aRepost) {
+ if (StaticPrefs::dom_confirm_repost_testing_always_accept()) {
+ *aRepost = true;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPromptCollection> prompter =
+ do_GetService("@mozilla.org/embedcomp/prompt-collection;1");
+ if (!prompter) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return prompter->ConfirmRepost(mBrowsingContext, aRepost);
+}
+
+nsresult nsDocShell::GetPromptAndStringBundle(nsIPrompt** aPrompt,
+ nsIStringBundle** aStringBundle) {
+ NS_ENSURE_SUCCESS(GetInterface(NS_GET_IID(nsIPrompt), (void**)aPrompt),
+ NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIStringBundleService> stringBundleService =
+ mozilla::components::StringBundle::Service();
+ NS_ENSURE_TRUE(stringBundleService, NS_ERROR_FAILURE);
+
+ NS_ENSURE_SUCCESS(
+ stringBundleService->CreateBundle(kAppstringsBundleURL, aStringBundle),
+ NS_ERROR_FAILURE);
+
+ return NS_OK;
+}
+
+nsIScrollableFrame* nsDocShell::GetRootScrollFrame() {
+ PresShell* presShell = GetPresShell();
+ NS_ENSURE_TRUE(presShell, nullptr);
+
+ return presShell->GetRootScrollFrameAsScrollable();
+}
+
+nsresult nsDocShell::EnsureScriptEnvironment() {
+ if (mScriptGlobal) {
+ return NS_OK;
+ }
+
+ if (mIsBeingDestroyed) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+#ifdef DEBUG
+ NS_ASSERTION(!mInEnsureScriptEnv,
+ "Infinite loop! Calling EnsureScriptEnvironment() from "
+ "within EnsureScriptEnvironment()!");
+
+ // Yeah, this isn't re-entrant safe, but that's ok since if we
+ // re-enter this method, we'll infinitely loop...
+ AutoRestore<bool> boolSetter(mInEnsureScriptEnv);
+ mInEnsureScriptEnv = true;
+#endif
+
+ nsCOMPtr<nsIWebBrowserChrome> browserChrome(do_GetInterface(mTreeOwner));
+ NS_ENSURE_TRUE(browserChrome, NS_ERROR_NOT_AVAILABLE);
+
+ uint32_t chromeFlags;
+ browserChrome->GetChromeFlags(&chromeFlags);
+
+ // If our window is modal and we're not opened as chrome, make
+ // this window a modal content window.
+ mScriptGlobal = nsGlobalWindowOuter::Create(this, mItemType == typeChrome);
+ MOZ_ASSERT(mScriptGlobal);
+
+ // Ensure the script object is set up to run script.
+ return mScriptGlobal->EnsureScriptEnvironment();
+}
+
+nsresult nsDocShell::EnsureEditorData() {
+ MOZ_ASSERT(!mIsBeingDestroyed);
+
+ bool openDocHasDetachedEditor = mOSHE && mOSHE->HasDetachedEditor();
+ if (!mEditorData && !mIsBeingDestroyed && !openDocHasDetachedEditor) {
+ // We shouldn't recreate the editor data if it already exists, or
+ // we're shutting down, or we already have a detached editor data
+ // stored in the session history. We should only have one editordata
+ // per docshell.
+ mEditorData = MakeUnique<nsDocShellEditorData>(this);
+ }
+
+ return mEditorData ? NS_OK : NS_ERROR_NOT_AVAILABLE;
+}
+
+nsresult nsDocShell::EnsureFind() {
+ if (!mFind) {
+ mFind = new nsWebBrowserFind();
+ }
+
+ // we promise that the nsIWebBrowserFind that we return has been set
+ // up to point to the focused, or content window, so we have to
+ // set that up each time.
+
+ nsIScriptGlobalObject* scriptGO = GetScriptGlobalObject();
+ NS_ENSURE_TRUE(scriptGO, NS_ERROR_UNEXPECTED);
+
+ // default to our window
+ nsCOMPtr<nsPIDOMWindowOuter> ourWindow = do_QueryInterface(scriptGO);
+ nsCOMPtr<nsPIDOMWindowOuter> windowToSearch;
+ nsFocusManager::GetFocusedDescendant(ourWindow,
+ nsFocusManager::eIncludeAllDescendants,
+ getter_AddRefs(windowToSearch));
+
+ nsCOMPtr<nsIWebBrowserFindInFrames> findInFrames = do_QueryInterface(mFind);
+ if (!findInFrames) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ nsresult rv = findInFrames->SetRootSearchFrame(ourWindow);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = findInFrames->SetCurrentSearchFrame(windowToSearch);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::IsBeingDestroyed(bool* aDoomed) {
+ NS_ENSURE_ARG(aDoomed);
+ *aDoomed = mIsBeingDestroyed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetIsExecutingOnLoadHandler(bool* aResult) {
+ NS_ENSURE_ARG(aResult);
+ *aResult = mIsExecutingOnLoadHandler;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetLayoutHistoryState(nsILayoutHistoryState** aLayoutHistoryState) {
+ nsCOMPtr<nsILayoutHistoryState> state;
+ if (mozilla::SessionHistoryInParent()) {
+ if (mActiveEntry) {
+ state = mActiveEntry->GetLayoutHistoryState();
+ }
+ } else {
+ if (mOSHE) {
+ state = mOSHE->GetLayoutHistoryState();
+ }
+ }
+ state.forget(aLayoutHistoryState);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetLayoutHistoryState(nsILayoutHistoryState* aLayoutHistoryState) {
+ if (mOSHE) {
+ mOSHE->SetLayoutHistoryState(aLayoutHistoryState);
+ }
+ if (mActiveEntry) {
+ mActiveEntry->SetLayoutHistoryState(aLayoutHistoryState);
+ }
+ return NS_OK;
+}
+
+nsDocShell::InterfaceRequestorProxy::InterfaceRequestorProxy(
+ nsIInterfaceRequestor* aRequestor) {
+ if (aRequestor) {
+ mWeakPtr = do_GetWeakReference(aRequestor);
+ }
+}
+
+nsDocShell::InterfaceRequestorProxy::~InterfaceRequestorProxy() {
+ mWeakPtr = nullptr;
+}
+
+NS_IMPL_ISUPPORTS(nsDocShell::InterfaceRequestorProxy, nsIInterfaceRequestor)
+
+NS_IMETHODIMP
+nsDocShell::InterfaceRequestorProxy::GetInterface(const nsIID& aIID,
+ void** aSink) {
+ NS_ENSURE_ARG_POINTER(aSink);
+ nsCOMPtr<nsIInterfaceRequestor> ifReq = do_QueryReferent(mWeakPtr);
+ if (ifReq) {
+ return ifReq->GetInterface(aIID, aSink);
+ }
+ *aSink = nullptr;
+ return NS_NOINTERFACE;
+}
+
+//*****************************************************************************
+// nsDocShell::nsIAuthPromptProvider
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsDocShell::GetAuthPrompt(uint32_t aPromptReason, const nsIID& aIID,
+ void** aResult) {
+ // a priority prompt request will override a false mAllowAuth setting
+ bool priorityPrompt = (aPromptReason == PROMPT_PROXY);
+
+ if (!mAllowAuth && !priorityPrompt) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // we're either allowing auth, or it's a proxy request
+ nsresult rv;
+ nsCOMPtr<nsIPromptFactory> wwatch =
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = EnsureScriptEnvironment();
+ 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.
+
+ return wwatch->GetPrompt(mScriptGlobal, aIID,
+ reinterpret_cast<void**>(aResult));
+}
+
+//*****************************************************************************
+// nsDocShell::nsILoadContext
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsDocShell::GetAssociatedWindow(mozIDOMWindowProxy** aWindow) {
+ CallGetInterface(this, aWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetTopWindow(mozIDOMWindowProxy** aWindow) {
+ return mBrowsingContext->GetTopWindow(aWindow);
+}
+
+NS_IMETHODIMP
+nsDocShell::GetTopFrameElement(Element** aElement) {
+ return mBrowsingContext->GetTopFrameElement(aElement);
+}
+
+NS_IMETHODIMP
+nsDocShell::GetUseTrackingProtection(bool* aUseTrackingProtection) {
+ return mBrowsingContext->GetUseTrackingProtection(aUseTrackingProtection);
+}
+
+NS_IMETHODIMP
+nsDocShell::SetUseTrackingProtection(bool aUseTrackingProtection) {
+ return mBrowsingContext->SetUseTrackingProtection(aUseTrackingProtection);
+}
+
+NS_IMETHODIMP
+nsDocShell::GetIsContent(bool* aIsContent) {
+ *aIsContent = (mItemType == typeContent);
+ return NS_OK;
+}
+
+bool nsDocShell::IsOKToLoadURI(nsIURI* aURI) {
+ MOZ_ASSERT(aURI, "Must have a URI!");
+
+ if (!mFiredUnloadEvent) {
+ return true;
+ }
+
+ if (!mLoadingURI) {
+ return false;
+ }
+
+ bool isPrivateWin = false;
+ Document* doc = GetDocument();
+ if (doc) {
+ isPrivateWin =
+ doc->NodePrincipal()->OriginAttributesRef().mPrivateBrowsingId > 0;
+ }
+
+ nsCOMPtr<nsIScriptSecurityManager> secMan =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
+ return secMan && NS_SUCCEEDED(secMan->CheckSameOriginURI(
+ aURI, mLoadingURI, false, isPrivateWin));
+}
+
+//
+// Routines for selection and clipboard
+//
+nsresult nsDocShell::GetControllerForCommand(const char* aCommand,
+ nsIController** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = nullptr;
+
+ NS_ENSURE_TRUE(mScriptGlobal, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsPIWindowRoot> root = mScriptGlobal->GetTopWindowRoot();
+ NS_ENSURE_TRUE(root, NS_ERROR_FAILURE);
+
+ return root->GetControllerForCommand(aCommand, false /* for any window */,
+ aResult);
+}
+
+NS_IMETHODIMP
+nsDocShell::IsCommandEnabled(const char* aCommand, bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+
+ nsresult rv = NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIController> controller;
+ rv = GetControllerForCommand(aCommand, getter_AddRefs(controller));
+ if (controller) {
+ rv = controller->IsCommandEnabled(aCommand, aResult);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDocShell::DoCommand(const char* aCommand) {
+ nsresult rv = NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIController> controller;
+ rv = GetControllerForCommand(aCommand, getter_AddRefs(controller));
+ if (controller) {
+ rv = controller->DoCommand(aCommand);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDocShell::DoCommandWithParams(const char* aCommand,
+ nsICommandParams* aParams) {
+ nsCOMPtr<nsIController> controller;
+ nsresult rv = GetControllerForCommand(aCommand, getter_AddRefs(controller));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsICommandController> commandController =
+ do_QueryInterface(controller, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return commandController->DoCommandWithParams(aCommand, aParams);
+}
+
+nsresult nsDocShell::EnsureCommandHandler() {
+ if (!mCommandManager) {
+ if (nsCOMPtr<nsPIDOMWindowOuter> domWindow = GetWindow()) {
+ mCommandManager = new nsCommandManager(domWindow);
+ }
+ }
+ return mCommandManager ? NS_OK : NS_ERROR_FAILURE;
+}
+
+// link handling
+
+class OnLinkClickEvent : public Runnable {
+ public:
+ OnLinkClickEvent(nsDocShell* aHandler, nsIContent* aContent,
+ nsDocShellLoadState* aLoadState, bool aNoOpenerImplied,
+ bool aIsTrusted, nsIPrincipal* aTriggeringPrincipal);
+
+ NS_IMETHOD Run() override {
+ AutoPopupStatePusher popupStatePusher(mPopupState);
+
+ // We need to set up an AutoJSAPI here for the following reason: When we
+ // do OnLinkClickSync we'll eventually end up in
+ // nsGlobalWindow::OpenInternal which only does popup blocking if
+ // !LegacyIsCallerChromeOrNativeCode(). So we need to fake things so that
+ // we don't look like native code as far as LegacyIsCallerNativeCode() is
+ // concerned.
+ AutoJSAPI jsapi;
+ if (mIsTrusted || jsapi.Init(mContent->OwnerDoc()->GetScopeObject())) {
+ mHandler->OnLinkClickSync(mContent, mLoadState, mNoOpenerImplied,
+ mTriggeringPrincipal);
+ }
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<nsDocShell> mHandler;
+ nsCOMPtr<nsIContent> mContent;
+ RefPtr<nsDocShellLoadState> mLoadState;
+ nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
+ PopupBlocker::PopupControlState mPopupState;
+ bool mNoOpenerImplied;
+ bool mIsTrusted;
+};
+
+OnLinkClickEvent::OnLinkClickEvent(nsDocShell* aHandler, nsIContent* aContent,
+ nsDocShellLoadState* aLoadState,
+ bool aNoOpenerImplied, bool aIsTrusted,
+ nsIPrincipal* aTriggeringPrincipal)
+ : mozilla::Runnable("OnLinkClickEvent"),
+ mHandler(aHandler),
+ mContent(aContent),
+ mLoadState(aLoadState),
+ mTriggeringPrincipal(aTriggeringPrincipal),
+ mPopupState(PopupBlocker::GetPopupControlState()),
+ mNoOpenerImplied(aNoOpenerImplied),
+ mIsTrusted(aIsTrusted) {}
+
+nsresult nsDocShell::OnLinkClick(
+ nsIContent* aContent, nsIURI* aURI, const nsAString& aTargetSpec,
+ const nsAString& aFileName, nsIInputStream* aPostDataStream,
+ nsIInputStream* aHeadersDataStream, bool aIsUserTriggered, bool aIsTrusted,
+ nsIPrincipal* aTriggeringPrincipal, nsIContentSecurityPolicy* aCsp) {
+#ifndef ANDROID
+ MOZ_ASSERT(aTriggeringPrincipal, "Need a valid triggeringPrincipal");
+#endif
+ NS_ASSERTION(NS_IsMainThread(), "wrong thread");
+
+ if (!IsNavigationAllowed() || !IsOKToLoadURI(aURI)) {
+ return NS_OK;
+ }
+
+ // On history navigation through Back/Forward buttons, don't execute
+ // automatic JavaScript redirection such as |anchorElement.click()| or
+ // |formElement.submit()|.
+ //
+ // XXX |formElement.submit()| bypasses this checkpoint because it calls
+ // nsDocShell::OnLinkClickSync(...) instead.
+ if (ShouldBlockLoadingForBackButton()) {
+ return NS_OK;
+ }
+
+ if (aContent->IsEditable()) {
+ return NS_OK;
+ }
+
+ Document* ownerDoc = aContent->OwnerDoc();
+ if (nsContentUtils::IsExternalProtocol(aURI)) {
+ ownerDoc->EnsureNotEnteringAndExitFullscreen();
+ }
+
+ bool noOpenerImplied = false;
+ nsAutoString target(aTargetSpec);
+ if (aFileName.IsVoid() &&
+ ShouldOpenInBlankTarget(aTargetSpec, aURI, aContent, aIsUserTriggered)) {
+ target = u"_blank";
+ if (!aTargetSpec.Equals(target)) {
+ noOpenerImplied = true;
+ }
+ }
+
+ RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(aURI);
+ loadState->SetTarget(target);
+ loadState->SetFileName(aFileName);
+ loadState->SetPostDataStream(aPostDataStream);
+ loadState->SetHeadersStream(aHeadersDataStream);
+ loadState->SetFirstParty(true);
+ loadState->SetTriggeringPrincipal(
+ aTriggeringPrincipal ? aTriggeringPrincipal : aContent->NodePrincipal());
+ loadState->SetPrincipalToInherit(aContent->NodePrincipal());
+ loadState->SetCsp(aCsp ? aCsp : aContent->GetCsp());
+ loadState->SetAllowFocusMove(UserActivation::IsHandlingUserInput());
+
+ nsCOMPtr<nsIRunnable> ev =
+ new OnLinkClickEvent(this, aContent, loadState, noOpenerImplied,
+ aIsTrusted, aTriggeringPrincipal);
+ return Dispatch(ev.forget());
+}
+
+bool nsDocShell::ShouldOpenInBlankTarget(const nsAString& aOriginalTarget,
+ nsIURI* aLinkURI, nsIContent* aContent,
+ bool aIsUserTriggered) {
+ if (net::SchemeIsJavascript(aLinkURI)) {
+ return false;
+ }
+
+ // External links from within app tabs should always open in new tabs
+ // instead of replacing the app tab's page (Bug 575561)
+ // nsIURI.host can throw for non-nsStandardURL nsIURIs. If we fail to
+ // get either host, just return false to use the original target.
+ nsAutoCString linkHost;
+ if (NS_FAILED(aLinkURI->GetHost(linkHost))) {
+ return false;
+ }
+
+ // The targetTopLevelLinkClicksToBlank property on BrowsingContext allows
+ // privileged code to change the default targeting behaviour. In particular,
+ // if a user-initiated link click for the (or targetting the) top-level frame
+ // is detected, we default the target to "_blank" to give it a new
+ // top-level BrowsingContext.
+ if (mBrowsingContext->TargetTopLevelLinkClicksToBlank() && aIsUserTriggered &&
+ ((aOriginalTarget.IsEmpty() && mBrowsingContext->IsTop()) ||
+ aOriginalTarget == u"_top"_ns)) {
+ return true;
+ }
+
+ // Don't modify non-default targets.
+ if (!aOriginalTarget.IsEmpty()) {
+ return false;
+ }
+
+ // Only check targets that are in extension panels or app tabs.
+ // (isAppTab will be false for app tab subframes).
+ nsString mmGroup = mBrowsingContext->Top()->GetMessageManagerGroup();
+ if (!mmGroup.EqualsLiteral("webext-browsers") &&
+ !mBrowsingContext->IsAppTab()) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> docURI = aContent->OwnerDoc()->GetDocumentURIObject();
+ if (!docURI) {
+ return false;
+ }
+
+ nsAutoCString docHost;
+ if (NS_FAILED(docURI->GetHost(docHost))) {
+ return false;
+ }
+
+ if (linkHost.Equals(docHost)) {
+ return false;
+ }
+
+ // Special case: ignore "www" prefix if it is part of host string
+ return linkHost.Length() < docHost.Length()
+ ? !docHost.Equals("www."_ns + linkHost)
+ : !linkHost.Equals("www."_ns + docHost);
+}
+
+static bool ElementCanHaveNoopener(nsIContent* aContent) {
+ // Make sure we are dealing with either an <A>, <AREA>, or <FORM> element in
+ // the HTML, XHTML, or SVG namespace.
+ return aContent->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area,
+ nsGkAtoms::form) ||
+ aContent->IsSVGElement(nsGkAtoms::a);
+}
+
+nsresult nsDocShell::OnLinkClickSync(nsIContent* aContent,
+ nsDocShellLoadState* aLoadState,
+ bool aNoOpenerImplied,
+ nsIPrincipal* aTriggeringPrincipal) {
+ if (!IsNavigationAllowed() || !IsOKToLoadURI(aLoadState->URI())) {
+ return NS_OK;
+ }
+
+ // XXX When the linking node was HTMLFormElement, it is synchronous event.
+ // That is, the caller of this method is not |OnLinkClickEvent::Run()|
+ // but |HTMLFormElement::SubmitSubmission(...)|.
+ if (aContent->IsHTMLElement(nsGkAtoms::form) &&
+ ShouldBlockLoadingForBackButton()) {
+ return NS_OK;
+ }
+
+ if (aContent->IsEditable()) {
+ return NS_OK;
+ }
+
+ // if the triggeringPrincipal is not passed explicitly, then we
+ // fall back to using doc->NodePrincipal() as the triggeringPrincipal.
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal =
+ aTriggeringPrincipal ? aTriggeringPrincipal : aContent->NodePrincipal();
+
+ {
+ // defer to an external protocol handler if necessary...
+ nsCOMPtr<nsIExternalProtocolService> extProtService =
+ do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID);
+ if (extProtService) {
+ nsAutoCString scheme;
+ aLoadState->URI()->GetScheme(scheme);
+ if (!scheme.IsEmpty()) {
+ // if the URL scheme does not correspond to an exposed protocol, then
+ // we need to hand this link click over to the external protocol
+ // handler.
+ bool isExposed;
+ nsresult rv =
+ extProtService->IsExposedProtocol(scheme.get(), &isExposed);
+ if (NS_SUCCEEDED(rv) && !isExposed) {
+ return extProtService->LoadURI(
+ aLoadState->URI(), triggeringPrincipal, nullptr, mBrowsingContext,
+ /* aTriggeredExternally */
+ false,
+ /* aHasValidUserGestureActivation */
+ aContent->OwnerDoc()->HasValidTransientUserGestureActivation());
+ }
+ }
+ }
+ }
+ uint32_t triggeringSandboxFlags = 0;
+ uint64_t triggeringWindowId = 0;
+ bool triggeringStorageAccess = false;
+ if (mBrowsingContext) {
+ triggeringSandboxFlags = aContent->OwnerDoc()->GetSandboxFlags();
+ triggeringWindowId = aContent->OwnerDoc()->InnerWindowID();
+ triggeringStorageAccess = aContent->OwnerDoc()->UsingStorageAccess();
+ }
+
+ uint32_t flags = INTERNAL_LOAD_FLAGS_NONE;
+ bool elementCanHaveNoopener = ElementCanHaveNoopener(aContent);
+ bool triggeringPrincipalIsSystemPrincipal =
+ aLoadState->TriggeringPrincipal()->IsSystemPrincipal();
+ if (elementCanHaveNoopener) {
+ MOZ_ASSERT(aContent->IsHTMLElement() || aContent->IsSVGElement());
+ nsAutoString relString;
+ aContent->AsElement()->GetAttr(nsGkAtoms::rel, relString);
+ nsWhitespaceTokenizerTemplate<nsContentUtils::IsHTMLWhitespace> tok(
+ relString);
+
+ bool targetBlank = aLoadState->Target().LowerCaseEqualsLiteral("_blank");
+ bool explicitOpenerSet = false;
+
+ // The opener behaviour follows a hierarchy, such that if a higher
+ // priority behaviour is specified, it always takes priority. That
+ // priority is currently: norefrerer > noopener > opener > default
+
+ while (tok.hasMoreTokens()) {
+ const nsAString& token = tok.nextToken();
+ if (token.LowerCaseEqualsLiteral("noreferrer")) {
+ flags |= INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER |
+ INTERNAL_LOAD_FLAGS_NO_OPENER;
+ // noreferrer cannot be overwritten by a 'rel=opener'.
+ explicitOpenerSet = true;
+ break;
+ }
+
+ if (token.LowerCaseEqualsLiteral("noopener")) {
+ flags |= INTERNAL_LOAD_FLAGS_NO_OPENER;
+ explicitOpenerSet = true;
+ }
+
+ if (targetBlank && StaticPrefs::dom_targetBlankNoOpener_enabled() &&
+ token.LowerCaseEqualsLiteral("opener") && !explicitOpenerSet) {
+ explicitOpenerSet = true;
+ }
+ }
+
+ if (targetBlank && StaticPrefs::dom_targetBlankNoOpener_enabled() &&
+ !explicitOpenerSet && !triggeringPrincipalIsSystemPrincipal) {
+ flags |= INTERNAL_LOAD_FLAGS_NO_OPENER;
+ }
+
+ if (aNoOpenerImplied) {
+ flags |= INTERNAL_LOAD_FLAGS_NO_OPENER;
+ }
+ }
+
+ // Get the owner document of the link that was clicked, this will be
+ // the document that the link is in, or the last document that the
+ // link was in. From that document, we'll get the URI to use as the
+ // referrer, since the current URI in this docshell may be a
+ // new document that we're in the process of loading.
+ RefPtr<Document> referrerDoc = aContent->OwnerDoc();
+
+ // Now check that the referrerDoc's inner window is the current inner
+ // window for mScriptGlobal. If it's not, then we don't want to
+ // follow this link.
+ nsPIDOMWindowInner* referrerInner = referrerDoc->GetInnerWindow();
+ if (!mScriptGlobal || !referrerInner ||
+ mScriptGlobal->GetCurrentInnerWindow() != referrerInner) {
+ // We're no longer the current inner window
+ return NS_OK;
+ }
+
+ // referrer could be null here in some odd cases, but that's ok,
+ // we'll just load the link w/o sending a referrer in those cases.
+
+ // If this is an anchor element, grab its type property to use as a hint
+ nsAutoString typeHint;
+ RefPtr<HTMLAnchorElement> anchor = HTMLAnchorElement::FromNode(aContent);
+ if (anchor) {
+ anchor->GetType(typeHint);
+ NS_ConvertUTF16toUTF8 utf8Hint(typeHint);
+ nsAutoCString type, dummy;
+ NS_ParseRequestContentType(utf8Hint, type, dummy);
+ CopyUTF8toUTF16(type, typeHint);
+ }
+
+ uint32_t loadType = LOAD_LINK;
+ if (aLoadState->IsFormSubmission()) {
+ if (aLoadState->Target().IsEmpty()) {
+ // We set the right load type here for form submissions with an empty
+ // target. Form submission with a non-empty target are handled in
+ // nsDocShell::PerformRetargeting after we've selected the correct target
+ // BC.
+ loadType = GetLoadTypeForFormSubmission(GetBrowsingContext(), aLoadState);
+ }
+ } else {
+ // Link click can be triggered inside an onload handler, and we don't want
+ // to add history entry in this case.
+ bool inOnLoadHandler = false;
+ GetIsExecutingOnLoadHandler(&inOnLoadHandler);
+ if (inOnLoadHandler) {
+ loadType = LOAD_NORMAL_REPLACE;
+ }
+ }
+
+ nsCOMPtr<nsIReferrerInfo> referrerInfo =
+ elementCanHaveNoopener ? new ReferrerInfo(*aContent->AsElement())
+ : new ReferrerInfo(*referrerDoc);
+ RefPtr<WindowContext> context = mBrowsingContext->GetCurrentWindowContext();
+
+ aLoadState->SetTriggeringSandboxFlags(triggeringSandboxFlags);
+ aLoadState->SetTriggeringWindowId(triggeringWindowId);
+ aLoadState->SetTriggeringStorageAccess(triggeringStorageAccess);
+ aLoadState->SetReferrerInfo(referrerInfo);
+ aLoadState->SetInternalLoadFlags(flags);
+ aLoadState->SetTypeHint(NS_ConvertUTF16toUTF8(typeHint));
+ aLoadState->SetLoadType(loadType);
+ aLoadState->SetSourceBrowsingContext(mBrowsingContext);
+ aLoadState->SetHasValidUserGestureActivation(
+ context && context->HasValidTransientUserGestureActivation());
+
+ nsresult rv = InternalLoad(aLoadState);
+
+ if (NS_SUCCEEDED(rv)) {
+ nsPingListener::DispatchPings(this, aContent, aLoadState->URI(),
+ referrerInfo);
+ }
+
+ return rv;
+}
+
+nsresult nsDocShell::OnOverLink(nsIContent* aContent, nsIURI* aURI,
+ const nsAString& aTargetSpec) {
+ if (aContent->IsEditable()) {
+ return NS_OK;
+ }
+
+ nsresult rv = NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIWebBrowserChrome> browserChrome = do_GetInterface(mTreeOwner);
+ if (!browserChrome) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIURI> exposableURI = nsIOService::CreateExposableURI(aURI);
+ nsAutoCString spec;
+ rv = exposableURI->GetDisplaySpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ConvertUTF8toUTF16 uStr(spec);
+
+ PredictorPredict(aURI, mCurrentURI, nsINetworkPredictor::PREDICT_LINK,
+ aContent->NodePrincipal()->OriginAttributesRef(), nullptr);
+
+ rv = browserChrome->SetLinkStatus(uStr);
+ return rv;
+}
+
+nsresult nsDocShell::OnLeaveLink() {
+ nsCOMPtr<nsIWebBrowserChrome> browserChrome(do_GetInterface(mTreeOwner));
+ nsresult rv = NS_ERROR_FAILURE;
+
+ if (browserChrome) {
+ rv = browserChrome->SetLinkStatus(u""_ns);
+ }
+ return rv;
+}
+
+bool nsDocShell::ShouldBlockLoadingForBackButton() {
+ if (!(mLoadType & LOAD_CMD_HISTORY) ||
+ UserActivation::IsHandlingUserInput() ||
+ !Preferences::GetBool("accessibility.blockjsredirection")) {
+ return false;
+ }
+
+ bool canGoForward = false;
+ GetCanGoForward(&canGoForward);
+ return canGoForward;
+}
+
+//----------------------------------------------------------------------
+// Web Shell Services API
+
+// This functions is only called when a new charset is detected in loading a
+// document.
+nsresult nsDocShell::CharsetChangeReloadDocument(
+ mozilla::NotNull<const mozilla::Encoding*> aEncoding, int32_t aSource) {
+ // XXX hack. keep the aCharset and aSource wait to pick it up
+ nsCOMPtr<nsIDocumentViewer> viewer;
+ NS_ENSURE_SUCCESS(GetDocViewer(getter_AddRefs(viewer)), NS_ERROR_FAILURE);
+ if (viewer) {
+ int32_t source;
+ Unused << viewer->GetReloadEncodingAndSource(&source);
+ if (aSource > source) {
+ viewer->SetReloadEncodingAndSource(aEncoding, aSource);
+ if (eCharsetReloadRequested != mCharsetReloadState) {
+ mCharsetReloadState = eCharsetReloadRequested;
+ switch (mLoadType) {
+ case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
+ return Reload(LOAD_FLAGS_CHARSET_CHANGE | LOAD_FLAGS_BYPASS_CACHE |
+ LOAD_FLAGS_BYPASS_PROXY);
+ case LOAD_RELOAD_BYPASS_CACHE:
+ return Reload(LOAD_FLAGS_CHARSET_CHANGE | LOAD_FLAGS_BYPASS_CACHE);
+ default:
+ return Reload(LOAD_FLAGS_CHARSET_CHANGE);
+ }
+ }
+ }
+ }
+ // return failure if this request is not accepted due to mCharsetReloadState
+ return NS_ERROR_DOCSHELL_REQUEST_REJECTED;
+}
+
+nsresult nsDocShell::CharsetChangeStopDocumentLoad() {
+ if (eCharsetReloadRequested != mCharsetReloadState) {
+ Stop(nsIWebNavigation::STOP_ALL);
+ return NS_OK;
+ }
+ // return failer if this request is not accepted due to mCharsetReloadState
+ return NS_ERROR_DOCSHELL_REQUEST_REJECTED;
+}
+
+NS_IMETHODIMP nsDocShell::ExitPrintPreview() {
+#if NS_PRINT_PREVIEW
+ nsCOMPtr<nsIWebBrowserPrint> viewer = do_QueryInterface(mDocumentViewer);
+ return viewer->ExitPrintPreview();
+#else
+ return NS_OK;
+#endif
+}
+
+/* [infallible] */
+NS_IMETHODIMP nsDocShell::GetIsTopLevelContentDocShell(
+ bool* aIsTopLevelContentDocShell) {
+ *aIsTopLevelContentDocShell = false;
+
+ if (mItemType == typeContent) {
+ *aIsTopLevelContentDocShell = mBrowsingContext->IsTopContent();
+ }
+
+ return NS_OK;
+}
+
+// Implements nsILoadContext.originAttributes
+NS_IMETHODIMP
+nsDocShell::GetScriptableOriginAttributes(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aVal) {
+ return mBrowsingContext->GetScriptableOriginAttributes(aCx, aVal);
+}
+
+// Implements nsIDocShell.GetOriginAttributes()
+NS_IMETHODIMP
+nsDocShell::GetOriginAttributes(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aVal) {
+ return mBrowsingContext->GetScriptableOriginAttributes(aCx, aVal);
+}
+
+bool nsDocShell::ServiceWorkerAllowedToControlWindow(nsIPrincipal* aPrincipal,
+ nsIURI* aURI) {
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(aURI);
+
+ if (UsePrivateBrowsing() || mBrowsingContext->GetSandboxFlags()) {
+ return false;
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> parent;
+ GetInProcessSameTypeParent(getter_AddRefs(parent));
+ nsPIDOMWindowOuter* parentOuter = parent ? parent->GetWindow() : nullptr;
+ nsPIDOMWindowInner* parentInner =
+ parentOuter ? parentOuter->GetCurrentInnerWindow() : nullptr;
+
+ StorageAccess storage =
+ StorageAllowedForNewWindow(aPrincipal, aURI, parentInner);
+
+ // If the partitioned service worker is enabled, service worker is allowed to
+ // control the window if partition is enabled.
+ if (StaticPrefs::privacy_partition_serviceWorkers() && parentInner) {
+ RefPtr<Document> doc = parentInner->GetExtantDoc();
+
+ if (doc && StoragePartitioningEnabled(storage, doc->CookieJarSettings())) {
+ return true;
+ }
+ }
+
+ return storage == StorageAccess::eAllow;
+}
+
+nsresult nsDocShell::SetOriginAttributes(const OriginAttributes& aAttrs) {
+ MOZ_ASSERT(!mIsBeingDestroyed);
+ return mBrowsingContext->SetOriginAttributes(aAttrs);
+}
+
+NS_IMETHODIMP
+nsDocShell::ResumeRedirectedLoad(uint64_t aIdentifier, int32_t aHistoryIndex) {
+ RefPtr<nsDocShell> self = this;
+ RefPtr<ChildProcessChannelListener> cpcl =
+ ChildProcessChannelListener::GetSingleton();
+
+ // Call into InternalLoad with the pending channel when it is received.
+ cpcl->RegisterCallback(
+ aIdentifier, [self, aHistoryIndex](
+ nsDocShellLoadState* aLoadState,
+ nsTArray<Endpoint<extensions::PStreamFilterParent>>&&
+ aStreamFilterEndpoints,
+ nsDOMNavigationTiming* aTiming) {
+ MOZ_ASSERT(aLoadState->GetPendingRedirectedChannel());
+ if (NS_WARN_IF(self->mIsBeingDestroyed)) {
+ aLoadState->GetPendingRedirectedChannel()->CancelWithReason(
+ NS_BINDING_ABORTED, "nsDocShell::mIsBeingDestroyed"_ns);
+ return NS_BINDING_ABORTED;
+ }
+
+ self->mLoadType = aLoadState->LoadType();
+ nsCOMPtr<nsIURI> previousURI;
+ uint32_t previousFlags = 0;
+ ExtractLastVisit(aLoadState->GetPendingRedirectedChannel(),
+ getter_AddRefs(previousURI), &previousFlags);
+ self->SaveLastVisit(aLoadState->GetPendingRedirectedChannel(),
+ previousURI, previousFlags);
+
+ if (aTiming) {
+ self->mTiming = new nsDOMNavigationTiming(self, aTiming);
+ self->mBlankTiming = false;
+ }
+
+ // If we're performing a history load, locate the correct history entry,
+ // and set the relevant bits on our loadState.
+ if (aHistoryIndex >= 0 && self->GetSessionHistory() &&
+ !mozilla::SessionHistoryInParent()) {
+ nsCOMPtr<nsISHistory> legacySHistory =
+ self->GetSessionHistory()->LegacySHistory();
+
+ nsCOMPtr<nsISHEntry> entry;
+ nsresult rv = legacySHistory->GetEntryAtIndex(aHistoryIndex,
+ getter_AddRefs(entry));
+ if (NS_SUCCEEDED(rv)) {
+ legacySHistory->InternalSetRequestedIndex(aHistoryIndex);
+ aLoadState->SetLoadType(LOAD_HISTORY);
+ aLoadState->SetSHEntry(entry);
+ }
+ }
+
+ self->InternalLoad(aLoadState);
+
+ if (aLoadState->GetOriginalURIString().isSome()) {
+ // Save URI string in case it's needed later when
+ // sending to search engine service in EndPageLoad()
+ self->mOriginalUriString = *aLoadState->GetOriginalURIString();
+ }
+
+ for (auto& endpoint : aStreamFilterEndpoints) {
+ extensions::StreamFilterParent::Attach(
+ aLoadState->GetPendingRedirectedChannel(), std::move(endpoint));
+ }
+
+ // If the channel isn't pending, then it means that InternalLoad
+ // never connected it, and we shouldn't try to continue. This
+ // can happen even if InternalLoad returned NS_OK.
+ bool pending = false;
+ aLoadState->GetPendingRedirectedChannel()->IsPending(&pending);
+ NS_ASSERTION(pending, "We should have connected the pending channel!");
+ if (!pending) {
+ return NS_BINDING_ABORTED;
+ }
+ return NS_OK;
+ });
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetOriginAttributes(JS::Handle<JS::Value> aOriginAttributes,
+ JSContext* aCx) {
+ OriginAttributes attrs;
+ if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return SetOriginAttributes(attrs);
+}
+
+NS_IMETHODIMP
+nsDocShell::GetAsyncPanZoomEnabled(bool* aOut) {
+ if (PresShell* presShell = GetPresShell()) {
+ *aOut = presShell->AsyncPanZoomEnabled();
+ return NS_OK;
+ }
+
+ // If we don't have a presShell, fall back to the default platform value of
+ // whether or not APZ is enabled.
+ *aOut = gfxPlatform::AsyncPanZoomEnabled();
+ return NS_OK;
+}
+
+bool nsDocShell::HasUnloadedParent() {
+ for (WindowContext* wc = GetBrowsingContext()->GetParentWindowContext(); wc;
+ wc = wc->GetParentWindowContext()) {
+ if (!wc->IsCurrent() || wc->IsDiscarded() ||
+ wc->GetBrowsingContext()->IsDiscarded()) {
+ // If a parent is OOP and the parent WindowContext is no
+ // longer current, we can assume the parent was unloaded.
+ return true;
+ }
+
+ if (wc->GetBrowsingContext()->IsInProcess() &&
+ (!wc->GetBrowsingContext()->GetDocShell() ||
+ wc->GetBrowsingContext()->GetDocShell()->GetIsInUnload())) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/* static */
+bool nsDocShell::ShouldUpdateGlobalHistory(uint32_t aLoadType) {
+ return !(aLoadType == LOAD_BYPASS_HISTORY || aLoadType == LOAD_ERROR_PAGE ||
+ aLoadType & LOAD_CMD_HISTORY);
+}
+
+void nsDocShell::UpdateGlobalHistoryTitle(nsIURI* aURI) {
+ if (!mBrowsingContext->GetUseGlobalHistory() || UsePrivateBrowsing()) {
+ return;
+ }
+
+ // Global history is interested into sub-frame visits only for link-coloring
+ // purposes, thus title updates are skipped for those.
+ //
+ // Moreover, some iframe documents (such as the ones created via
+ // document.open()) inherit the document uri of the caller, which would cause
+ // us to override a previously set page title with one from the subframe.
+ if (IsSubframe()) {
+ return;
+ }
+
+ if (nsCOMPtr<IHistory> history = components::History::Service()) {
+ history->SetURITitle(aURI, mTitle);
+ }
+}
+
+bool nsDocShell::IsInvisible() { return mInvisible; }
+
+void nsDocShell::SetInvisible(bool aInvisible) { mInvisible = aInvisible; }
+
+/* static */
+void nsDocShell::MaybeNotifyKeywordSearchLoading(const nsString& aProvider,
+ const nsString& aKeyword) {
+ if (aProvider.IsEmpty()) {
+ return;
+ }
+ nsresult rv;
+ nsCOMPtr<nsISupportsString> isupportsString =
+ do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ rv = isupportsString->SetData(aProvider);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
+ if (obsSvc) {
+ // Note that "keyword-search" refers to a search via the url
+ // bar, not a bookmarks keyword search.
+ obsSvc->NotifyObservers(isupportsString, "keyword-search", aKeyword.get());
+ }
+}
+
+NS_IMETHODIMP
+nsDocShell::ShouldPrepareForIntercept(nsIURI* aURI, nsIChannel* aChannel,
+ bool* aShouldIntercept) {
+ return mInterceptController->ShouldPrepareForIntercept(aURI, aChannel,
+ aShouldIntercept);
+}
+
+NS_IMETHODIMP
+nsDocShell::ChannelIntercepted(nsIInterceptedChannel* aChannel) {
+ return mInterceptController->ChannelIntercepted(aChannel);
+}
+
+bool nsDocShell::InFrameSwap() {
+ RefPtr<nsDocShell> shell = this;
+ do {
+ if (shell->mInFrameSwap) {
+ return true;
+ }
+ shell = shell->GetInProcessParentDocshell();
+ } while (shell);
+ return false;
+}
+
+UniquePtr<ClientSource> nsDocShell::TakeInitialClientSource() {
+ return std::move(mInitialClientSource);
+}
+
+NS_IMETHODIMP
+nsDocShell::GetEditingSession(nsIEditingSession** aEditSession) {
+ if (!NS_SUCCEEDED(EnsureEditorData())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aEditSession = do_AddRef(mEditorData->GetEditingSession()).take();
+ return *aEditSession ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetScriptableBrowserChild(nsIBrowserChild** aBrowserChild) {
+ *aBrowserChild = GetBrowserChild().take();
+ return *aBrowserChild ? NS_OK : NS_ERROR_FAILURE;
+}
+
+already_AddRefed<nsIBrowserChild> nsDocShell::GetBrowserChild() {
+ nsCOMPtr<nsIBrowserChild> tc = do_QueryReferent(mBrowserChild);
+ return tc.forget();
+}
+
+nsCommandManager* nsDocShell::GetCommandManager() {
+ NS_ENSURE_SUCCESS(EnsureCommandHandler(), nullptr);
+ return mCommandManager;
+}
+
+NS_IMETHODIMP_(void)
+nsDocShell::GetOriginAttributes(mozilla::OriginAttributes& aAttrs) {
+ mBrowsingContext->GetOriginAttributes(aAttrs);
+}
+
+HTMLEditor* nsIDocShell::GetHTMLEditor() {
+ nsDocShell* docShell = static_cast<nsDocShell*>(this);
+ return docShell->GetHTMLEditorInternal();
+}
+
+nsresult nsIDocShell::SetHTMLEditor(HTMLEditor* aHTMLEditor) {
+ nsDocShell* docShell = static_cast<nsDocShell*>(this);
+ return docShell->SetHTMLEditorInternal(aHTMLEditor);
+}
+
+#define MATRIX_LENGTH 20
+
+NS_IMETHODIMP
+nsDocShell::SetColorMatrix(const nsTArray<float>& aMatrix) {
+ if (aMatrix.Length() == MATRIX_LENGTH) {
+ mColorMatrix.reset(new gfx::Matrix5x4());
+ static_assert(
+ MATRIX_LENGTH * sizeof(float) == sizeof(mColorMatrix->components),
+ "Size mismatch for our memcpy");
+ memcpy(mColorMatrix->components, aMatrix.Elements(),
+ sizeof(mColorMatrix->components));
+ } else if (aMatrix.Length() == 0) {
+ mColorMatrix.reset();
+ } else {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ PresShell* presShell = GetPresShell();
+ if (!presShell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsIFrame* frame = presShell->GetRootFrame();
+ if (!frame) {
+ return NS_ERROR_FAILURE;
+ }
+
+ frame->SchedulePaint();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetColorMatrix(nsTArray<float>& aMatrix) {
+ if (mColorMatrix) {
+ aMatrix.SetLength(MATRIX_LENGTH);
+ static_assert(
+ MATRIX_LENGTH * sizeof(float) == sizeof(mColorMatrix->components),
+ "Size mismatch for our memcpy");
+ memcpy(aMatrix.Elements(), mColorMatrix->components,
+ MATRIX_LENGTH * sizeof(float));
+ }
+
+ return NS_OK;
+}
+
+#undef MATRIX_LENGTH
+
+NS_IMETHODIMP
+nsDocShell::GetIsForceReloading(bool* aForceReload) {
+ *aForceReload = IsForceReloading();
+ return NS_OK;
+}
+
+bool nsDocShell::IsForceReloading() { return IsForceReloadType(mLoadType); }
+
+NS_IMETHODIMP
+nsDocShell::GetBrowsingContextXPCOM(BrowsingContext** aBrowsingContext) {
+ *aBrowsingContext = do_AddRef(mBrowsingContext).take();
+ return NS_OK;
+}
+
+BrowsingContext* nsDocShell::GetBrowsingContext() { return mBrowsingContext; }
+
+bool nsDocShell::GetIsAttemptingToNavigate() {
+ // XXXbz the document.open spec says to abort even if there's just a
+ // queued navigation task, sort of. It's not clear whether browsers
+ // actually do that, and we didn't use to do it, so for now let's
+ // not do that.
+ // https://github.com/whatwg/html/issues/3447 tracks the spec side of this.
+ if (mDocumentRequest) {
+ // There's definitely a navigation in progress.
+ return true;
+ }
+
+ // javascript: channels have slightly weird behavior: they're LOAD_BACKGROUND
+ // until the script runs, which means they're not sending loadgroup
+ // notifications and hence not getting set as mDocumentRequest. Look through
+ // our loadgroup for document-level javascript: loads.
+ if (!mLoadGroup) {
+ return false;
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> requests;
+ mLoadGroup->GetRequests(getter_AddRefs(requests));
+ bool hasMore = false;
+ while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> elem;
+ requests->GetNext(getter_AddRefs(elem));
+ nsCOMPtr<nsIScriptChannel> scriptChannel(do_QueryInterface(elem));
+ if (!scriptChannel) {
+ continue;
+ }
+
+ if (scriptChannel->GetIsDocumentLoad()) {
+ // This is a javascript: load that might lead to a new document,
+ // hence a navigation.
+ return true;
+ }
+ }
+
+ return mCheckingSessionHistory;
+}
+
+void nsDocShell::SetLoadingSessionHistoryInfo(
+ const mozilla::dom::LoadingSessionHistoryInfo& aLoadingInfo,
+ bool aNeedToReportActiveAfterLoadingBecomesActive) {
+ // FIXME Would like to assert this, but can't yet.
+ // MOZ_ASSERT(!mLoadingEntry);
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("Setting the loading entry on nsDocShell %p to %s", this,
+ aLoadingInfo.mInfo.GetURI()->GetSpecOrDefault().get()));
+ mLoadingEntry = MakeUnique<LoadingSessionHistoryInfo>(aLoadingInfo);
+ mNeedToReportActiveAfterLoadingBecomesActive =
+ aNeedToReportActiveAfterLoadingBecomesActive;
+}
+
+void nsDocShell::MoveLoadingToActiveEntry(bool aPersist, bool aExpired,
+ uint32_t aCacheKey,
+ nsIURI* aPreviousURI) {
+ MOZ_ASSERT(mozilla::SessionHistoryInParent());
+
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("nsDocShell %p MoveLoadingToActiveEntry", this));
+
+ UniquePtr<SessionHistoryInfo> previousActiveEntry(mActiveEntry.release());
+ mozilla::UniquePtr<mozilla::dom::LoadingSessionHistoryInfo> loadingEntry;
+ mActiveEntryIsLoadingFromSessionHistory =
+ mLoadingEntry && mLoadingEntry->mLoadIsFromSessionHistory;
+ if (mLoadingEntry) {
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("Moving the loading entry to the active entry on nsDocShell %p "
+ "to %s",
+ this, mLoadingEntry->mInfo.GetURI()->GetSpecOrDefault().get()));
+ mActiveEntry = MakeUnique<SessionHistoryInfo>(mLoadingEntry->mInfo);
+ mLoadingEntry.swap(loadingEntry);
+ if (!mActiveEntryIsLoadingFromSessionHistory) {
+ if (mNeedToReportActiveAfterLoadingBecomesActive) {
+ // Needed to pass various history length WPTs.
+ mBrowsingContext->SetActiveSessionHistoryEntry(
+ mozilla::Nothing(), mActiveEntry.get(), mLoadType,
+ /* aUpdatedCacheKey = */ 0, false);
+ }
+ mBrowsingContext->IncrementHistoryEntryCountForBrowsingContext();
+ }
+ }
+ mNeedToReportActiveAfterLoadingBecomesActive = false;
+
+ if (mActiveEntry) {
+ if (aCacheKey != 0) {
+ mActiveEntry->SetCacheKey(aCacheKey);
+ }
+ MOZ_ASSERT(loadingEntry);
+ uint32_t loadType =
+ mLoadType == LOAD_ERROR_PAGE ? mFailedLoadType : mLoadType;
+
+ if (loadingEntry->mLoadId != UINT64_MAX) {
+ // We're passing in mCurrentURI, which could be null. SessionHistoryCommit
+ // does require a non-null uri if this is for a refresh load of the same
+ // URI, but in that case mCurrentURI won't be null here.
+ mBrowsingContext->SessionHistoryCommit(
+ *loadingEntry, loadType, aPreviousURI, previousActiveEntry.get(),
+ aPersist, false, aExpired, aCacheKey);
+ }
+ }
+}
+
+static bool IsFaviconLoad(nsIRequest* aRequest) {
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ if (!channel) {
+ return false;
+ }
+
+ nsCOMPtr<nsILoadInfo> li = channel->LoadInfo();
+ return li && li->InternalContentPolicyType() ==
+ nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON;
+}
+
+void nsDocShell::RecordSingleChannelId(bool aStartRequest,
+ nsIRequest* aRequest) {
+ // Ignore favicon loads, they don't need to block caching.
+ if (IsFaviconLoad(aRequest)) {
+ return;
+ }
+
+ MOZ_ASSERT_IF(!aStartRequest, mRequestForBlockingFromBFCacheCount > 0);
+
+ mRequestForBlockingFromBFCacheCount += aStartRequest ? 1 : -1;
+
+ if (mBrowsingContext->GetCurrentWindowContext()) {
+ // We have three states: no request, one request with an id and
+ // eiher one request without an id or multiple requests. Nothing() is no
+ // request, Some(non-zero) is one request with an id and Some(0) is one
+ // request without an id or multiple requests.
+ Maybe<uint64_t> singleChannelId;
+ if (mRequestForBlockingFromBFCacheCount > 1) {
+ singleChannelId = Some(0);
+ } else if (mRequestForBlockingFromBFCacheCount == 1) {
+ nsCOMPtr<nsIIdentChannel> identChannel;
+ if (aStartRequest) {
+ identChannel = do_QueryInterface(aRequest);
+ } else {
+ // aChannel is the channel that's being removed, but we need to check if
+ // the remaining channel in the loadgroup has an id.
+ nsCOMPtr<nsISimpleEnumerator> requests;
+ mLoadGroup->GetRequests(getter_AddRefs(requests));
+ for (const auto& request : SimpleEnumerator<nsIRequest>(requests)) {
+ if (!IsFaviconLoad(request) &&
+ !!(identChannel = do_QueryInterface(request))) {
+ break;
+ }
+ }
+ }
+
+ if (identChannel) {
+ singleChannelId = Some(identChannel->ChannelId());
+ } else {
+ singleChannelId = Some(0);
+ }
+ } else {
+ MOZ_ASSERT(mRequestForBlockingFromBFCacheCount == 0);
+ singleChannelId = Nothing();
+ }
+
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Verbose))) {
+ nsAutoCString uri("[no uri]");
+ if (mCurrentURI) {
+ uri = mCurrentURI->GetSpecOrDefault();
+ }
+ if (singleChannelId.isNothing()) {
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Verbose,
+ ("Loadgroup for %s doesn't have any requests relevant for "
+ "blocking BFCache",
+ uri.get()));
+ } else if (singleChannelId.value() == 0) {
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Verbose,
+ ("Loadgroup for %s has multiple requests relevant for blocking "
+ "BFCache",
+ uri.get()));
+ } else {
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Verbose,
+ ("Loadgroup for %s has one request with id %" PRIu64
+ " relevant for blocking BFCache",
+ uri.get(), singleChannelId.value()));
+ }
+ }
+
+ if (mSingleChannelId != singleChannelId) {
+ mSingleChannelId = singleChannelId;
+ WindowGlobalChild* wgc =
+ mBrowsingContext->GetCurrentWindowContext()->GetWindowGlobalChild();
+ if (wgc) {
+ wgc->SendSetSingleChannelId(singleChannelId);
+ }
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsDocShell::OnStartRequest(nsIRequest* aRequest) {
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Verbose))) {
+ nsAutoCString uri("[no uri]");
+ if (mCurrentURI) {
+ uri = mCurrentURI->GetSpecOrDefault();
+ }
+ nsAutoCString name;
+ aRequest->GetName(name);
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Verbose,
+ ("Adding request %s to loadgroup for %s", name.get(), uri.get()));
+ }
+ RecordSingleChannelId(true, aRequest);
+ return nsDocLoader::OnStartRequest(aRequest);
+}
+
+NS_IMETHODIMP
+nsDocShell::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Verbose))) {
+ nsAutoCString uri("[no uri]");
+ if (mCurrentURI) {
+ uri = mCurrentURI->GetSpecOrDefault();
+ }
+ nsAutoCString name;
+ aRequest->GetName(name);
+ MOZ_LOG(
+ gSHIPBFCacheLog, LogLevel::Verbose,
+ ("Removing request %s from loadgroup for %s", name.get(), uri.get()));
+ }
+ RecordSingleChannelId(false, aRequest);
+ return nsDocLoader::OnStopRequest(aRequest, aStatusCode);
+}
+
+void nsDocShell::MaybeDisconnectChildListenersOnPageHide() {
+ MOZ_RELEASE_ASSERT(XRE_IsContentProcess());
+
+ if (mChannelToDisconnectOnPageHide != 0 && mLoadGroup) {
+ nsCOMPtr<nsISimpleEnumerator> requests;
+ mLoadGroup->GetRequests(getter_AddRefs(requests));
+ for (const auto& request : SimpleEnumerator<nsIRequest>(requests)) {
+ RefPtr<DocumentChannel> channel = do_QueryObject(request);
+ if (channel && channel->ChannelId() == mChannelToDisconnectOnPageHide) {
+ static_cast<DocumentChannelChild*>(channel.get())
+ ->DisconnectChildListeners(NS_BINDING_ABORTED, NS_BINDING_ABORTED);
+ }
+ }
+ mChannelToDisconnectOnPageHide = 0;
+ }
+}
diff --git a/docshell/base/nsDocShell.h b/docshell/base/nsDocShell.h
new file mode 100644
index 0000000000..9f2d9a17dc
--- /dev/null
+++ b/docshell/base/nsDocShell.h
@@ -0,0 +1,1366 @@
+/* -*- 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 nsDocShell_h__
+#define nsDocShell_h__
+
+#include "Units.h"
+#include "mozilla/Encoding.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/NotNull.h"
+#include "mozilla/ScrollbarPreferences.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WeakPtr.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/WindowProxyHolder.h"
+#include "nsCOMPtr.h"
+#include "nsCharsetSource.h"
+#include "nsDocLoader.h"
+#include "nsIAuthPromptProvider.h"
+#include "nsIBaseWindow.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsILoadContext.h"
+#include "nsINetworkInterceptController.h"
+#include "nsIRefreshURI.h"
+#include "nsIWebNavigation.h"
+#include "nsIWebPageDescriptor.h"
+#include "nsIWebProgressListener.h"
+#include "nsPoint.h" // mCurrent/mDefaultScrollbarPreferences
+#include "nsRect.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "prtime.h"
+
+// Interfaces Needed
+
+namespace mozilla {
+class Encoding;
+class HTMLEditor;
+class ObservedDocShell;
+enum class TaskCategory;
+namespace dom {
+class ClientInfo;
+class ClientSource;
+class EventTarget;
+class SessionHistoryInfo;
+struct LoadingSessionHistoryInfo;
+struct Wireframe;
+} // namespace dom
+namespace net {
+class LoadInfo;
+class DocumentLoadListener;
+} // namespace net
+} // namespace mozilla
+
+class nsIController;
+class nsIDocShellTreeOwner;
+class nsIDocumentViewer;
+class nsIHttpChannel;
+class nsIMutableArray;
+class nsIPrompt;
+class nsIScrollableFrame;
+class nsIStringBundle;
+class nsIURIFixup;
+class nsIURIFixupInfo;
+class nsIURILoader;
+class nsIWebBrowserFind;
+class nsIWidget;
+class nsIReferrerInfo;
+
+class nsCommandManager;
+class nsDocShellEditorData;
+class nsDOMNavigationTiming;
+class nsDSURIContentListener;
+class nsGlobalWindowOuter;
+
+class FramingChecker;
+class OnLinkClickEvent;
+
+/* internally used ViewMode types */
+enum ViewMode { viewNormal = 0x0, viewSource = 0x1 };
+
+enum eCharsetReloadState {
+ eCharsetReloadInit,
+ eCharsetReloadRequested,
+ eCharsetReloadStopOrigional
+};
+
+class nsDocShell final : public nsDocLoader,
+ public nsIDocShell,
+ public nsIWebNavigation,
+ public nsIBaseWindow,
+ public nsIRefreshURI,
+ public nsIWebProgressListener,
+ public nsIWebPageDescriptor,
+ public nsIAuthPromptProvider,
+ public nsILoadContext,
+ public nsINetworkInterceptController,
+ public mozilla::SupportsWeakPtr {
+ public:
+ enum InternalLoad : uint32_t {
+ INTERNAL_LOAD_FLAGS_NONE = 0x0,
+ INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL = 0x1,
+ INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER = 0x2,
+ INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP = 0x4,
+
+ // This flag marks the first load in this object
+ // @see nsIWebNavigation::LOAD_FLAGS_FIRST_LOAD
+ INTERNAL_LOAD_FLAGS_FIRST_LOAD = 0x8,
+
+ // The set of flags that should not be set before calling into
+ // nsDocShell::LoadURI and other nsDocShell loading functions.
+ INTERNAL_LOAD_FLAGS_LOADURI_SETUP_FLAGS = 0xf,
+
+ INTERNAL_LOAD_FLAGS_BYPASS_CLASSIFIER = 0x10,
+ INTERNAL_LOAD_FLAGS_FORCE_ALLOW_COOKIES = 0x20,
+
+ // Whether the load should be treated as srcdoc load, rather than a URI one.
+ INTERNAL_LOAD_FLAGS_IS_SRCDOC = 0x40,
+
+ // Whether this is the load of a frame's original src attribute
+ INTERNAL_LOAD_FLAGS_ORIGINAL_FRAME_SRC = 0x80,
+
+ INTERNAL_LOAD_FLAGS_NO_OPENER = 0x100,
+
+ // Whether a top-level data URI navigation is allowed for that load
+ INTERNAL_LOAD_FLAGS_FORCE_ALLOW_DATA_URI = 0x200,
+
+ // Whether the load should go through LoadURIDelegate.
+ INTERNAL_LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE = 0x2000,
+ };
+
+ // Event type dispatched by RestorePresentation
+ class RestorePresentationEvent : public mozilla::Runnable {
+ public:
+ NS_DECL_NSIRUNNABLE
+ explicit RestorePresentationEvent(nsDocShell* aDs)
+ : mozilla::Runnable("nsDocShell::RestorePresentationEvent"),
+ mDocShell(aDs) {}
+ void Revoke() { mDocShell = nullptr; }
+
+ private:
+ RefPtr<nsDocShell> mDocShell;
+ };
+
+ class InterfaceRequestorProxy : public nsIInterfaceRequestor {
+ public:
+ explicit InterfaceRequestorProxy(nsIInterfaceRequestor* aRequestor);
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ private:
+ virtual ~InterfaceRequestorProxy();
+ InterfaceRequestorProxy() = default;
+ nsWeakPtr mWeakPtr;
+ };
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsDocShell, nsDocLoader)
+ NS_DECL_NSIDOCSHELL
+ NS_DECL_NSIDOCSHELLTREEITEM
+ NS_DECL_NSIWEBNAVIGATION
+ NS_DECL_NSIBASEWINDOW
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSIWEBPROGRESSLISTENER
+ NS_DECL_NSIREFRESHURI
+ NS_DECL_NSIWEBPAGEDESCRIPTOR
+ NS_DECL_NSIAUTHPROMPTPROVIDER
+ NS_DECL_NSINETWORKINTERCEPTCONTROLLER
+
+ // Create a new nsDocShell object.
+ static already_AddRefed<nsDocShell> Create(
+ mozilla::dom::BrowsingContext* aBrowsingContext,
+ uint64_t aContentWindowID = 0);
+
+ bool Initialize();
+
+ NS_IMETHOD Stop() override {
+ // Need this here because otherwise nsIWebNavigation::Stop
+ // overrides the docloader's Stop()
+ return nsDocLoader::Stop();
+ }
+
+ mozilla::ScrollbarPreference ScrollbarPreference() const {
+ return mScrollbarPref;
+ }
+ void SetScrollbarPreference(mozilla::ScrollbarPreference);
+
+ /*
+ * The size, in CSS pixels, of the margins for the <body> of an HTML document
+ * in this docshell; used to implement the marginwidth attribute on HTML
+ * <frame>/<iframe> elements. A value smaller than zero indicates that the
+ * attribute was not set.
+ */
+ const mozilla::CSSIntSize& GetFrameMargins() const { return mFrameMargins; }
+
+ bool UpdateFrameMargins(const mozilla::CSSIntSize& aMargins) {
+ if (mFrameMargins == aMargins) {
+ return false;
+ }
+ mFrameMargins = aMargins;
+ return true;
+ }
+
+ /**
+ * Process a click on a link.
+ *
+ * @param aContent the content object used for triggering the link.
+ * @param aURI a URI object that defines the destination for the link
+ * @param aTargetSpec indicates where the link is targeted (may be an empty
+ * string)
+ * @param aFileName non-null when the link should be downloaded as the given
+ * file
+ * @param aPostDataStream the POST data to send
+ * @param aHeadersDataStream ??? (only used for plugins)
+ * @param aIsTrusted false if the triggerer is an untrusted DOM event.
+ * @param aTriggeringPrincipal, if not passed explicitly we fall back to
+ * the document's principal.
+ * @param aCsp, the CSP to be used for the load, that is the CSP of the
+ * entity responsible for causing the load to occur. Most likely
+ * this is the CSP of the document that started the load. In case
+ * aCsp was not passed explicitly we fall back to using
+ * aContent's document's CSP if that document holds any.
+ */
+ nsresult OnLinkClick(nsIContent* aContent, nsIURI* aURI,
+ const nsAString& aTargetSpec, const nsAString& aFileName,
+ nsIInputStream* aPostDataStream,
+ nsIInputStream* aHeadersDataStream,
+ bool aIsUserTriggered, bool aIsTrusted,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsIContentSecurityPolicy* aCsp);
+ /**
+ * Process a click on a link.
+ *
+ * Works the same as OnLinkClick() except it happens immediately rather than
+ * through an event.
+ *
+ * @param aContent the content object used for triggering the link.
+ * @param aDocShellLoadState the extended load info for this load.
+ * @param aNoOpenerImplied if the link implies "noopener"
+ * @param aTriggeringPrincipal, if not passed explicitly we fall back to
+ * the document's principal.
+ */
+ nsresult OnLinkClickSync(nsIContent* aContent,
+ nsDocShellLoadState* aLoadState,
+ bool aNoOpenerImplied,
+ nsIPrincipal* aTriggeringPrincipal);
+
+ /**
+ * Process a mouse-over a link.
+ *
+ * @param aContent the linked content.
+ * @param aURI an URI object that defines the destination for the link
+ * @param aTargetSpec indicates where the link is targeted (it may be an empty
+ * string)
+ */
+ nsresult OnOverLink(nsIContent* aContent, nsIURI* aURI,
+ const nsAString& aTargetSpec);
+ /**
+ * Process the mouse leaving a link.
+ */
+ nsresult OnLeaveLink();
+
+ // Don't use NS_DECL_NSILOADCONTEXT because some of nsILoadContext's methods
+ // are shared with nsIDocShell and can't be declared twice.
+ NS_IMETHOD GetAssociatedWindow(mozIDOMWindowProxy**) override;
+ NS_IMETHOD GetTopWindow(mozIDOMWindowProxy**) override;
+ NS_IMETHOD GetTopFrameElement(mozilla::dom::Element**) override;
+ NS_IMETHOD GetIsContent(bool*) override;
+ NS_IMETHOD GetUsePrivateBrowsing(bool*) override;
+ NS_IMETHOD SetUsePrivateBrowsing(bool) override;
+ NS_IMETHOD SetPrivateBrowsing(bool) override;
+ NS_IMETHOD GetUseRemoteTabs(bool*) override;
+ NS_IMETHOD SetRemoteTabs(bool) override;
+ NS_IMETHOD GetUseRemoteSubframes(bool*) override;
+ NS_IMETHOD SetRemoteSubframes(bool) override;
+ NS_IMETHOD GetScriptableOriginAttributes(
+ JSContext*, JS::MutableHandle<JS::Value>) override;
+ NS_IMETHOD_(void)
+ GetOriginAttributes(mozilla::OriginAttributes& aAttrs) override;
+
+ // Restores a cached presentation from history (mLSHE).
+ // This method swaps out the content viewer and simulates loads for
+ // subframes. It then simulates the completion of the toplevel load.
+ nsresult RestoreFromHistory();
+
+ /**
+ * Parses the passed in header string and sets up a refreshURI if a "refresh"
+ * header is found. If docshell is busy loading a page currently, the request
+ * will be queued and executed when the current page finishes loading.
+ *
+ * @param aDocument document to which the refresh header applies.
+ * @param aHeader The meta refresh header string.
+ */
+ void SetupRefreshURIFromHeader(mozilla::dom::Document* aDocument,
+ const nsAString& aHeader);
+
+ // Perform a URI load from a refresh timer. This is just like the
+ // ForceRefreshURI method on nsIRefreshURI, but makes sure to take
+ // the timer involved out of mRefreshURIList if it's there.
+ // aTimer must not be null.
+ nsresult ForceRefreshURIFromTimer(nsIURI* aURI, nsIPrincipal* aPrincipal,
+ uint32_t aDelay, nsITimer* aTimer);
+
+ // We need dummy OnLocationChange in some cases to update the UI without
+ // updating security info.
+ void FireDummyOnLocationChange() {
+ FireOnLocationChange(this, nullptr, mCurrentURI,
+ LOCATION_CHANGE_SAME_DOCUMENT);
+ }
+
+ nsresult HistoryEntryRemoved(int32_t aIndex);
+
+ // Notify Scroll observers when an async panning/zooming transform
+ // has started being applied
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ void NotifyAsyncPanZoomStarted();
+
+ // Notify Scroll observers when an async panning/zooming transform
+ // is no longer applied
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ void NotifyAsyncPanZoomStopped();
+
+ void SetInFrameSwap(bool aInSwap) { mInFrameSwap = aInSwap; }
+ bool InFrameSwap();
+
+ bool GetForcedAutodetection() { return mForcedAutodetection; }
+
+ void ResetForcedAutodetection() { mForcedAutodetection = false; }
+
+ mozilla::HTMLEditor* GetHTMLEditorInternal();
+ nsresult SetHTMLEditorInternal(mozilla::HTMLEditor* aHTMLEditor);
+
+ // Handle page navigation due to charset changes
+ nsresult CharsetChangeReloadDocument(
+ mozilla::NotNull<const mozilla::Encoding*> aEncoding, int32_t aSource);
+ nsresult CharsetChangeStopDocumentLoad();
+
+ nsDOMNavigationTiming* GetNavigationTiming() const;
+
+ nsresult SetOriginAttributes(const mozilla::OriginAttributes& aAttrs);
+
+ const mozilla::OriginAttributes& GetOriginAttributes() {
+ return mBrowsingContext->OriginAttributesRef();
+ }
+
+ // Determine whether this docshell corresponds to the given history entry,
+ // via having a pointer to it in mOSHE or mLSHE.
+ bool HasHistoryEntry(nsISHEntry* aEntry) const {
+ return aEntry && (aEntry == mOSHE || aEntry == mLSHE);
+ }
+
+ // Update any pointers (mOSHE or mLSHE) to aOldEntry to point to aNewEntry
+ void SwapHistoryEntries(nsISHEntry* aOldEntry, nsISHEntry* aNewEntry);
+
+ bool GetCreatedDynamically() const {
+ return mBrowsingContext && mBrowsingContext->CreatedDynamically();
+ }
+
+ mozilla::gfx::Matrix5x4* GetColorMatrix() { return mColorMatrix.get(); }
+
+ static bool SandboxFlagsImplyCookies(const uint32_t& aSandboxFlags);
+
+ // Tell the favicon service that aNewURI has the same favicon as aOldURI.
+ static void CopyFavicon(nsIURI* aOldURI, nsIURI* aNewURI,
+ bool aInPrivateBrowsing);
+
+ static nsDocShell* Cast(nsIDocShell* aDocShell) {
+ return static_cast<nsDocShell*>(aDocShell);
+ }
+
+ static bool CanLoadInParentProcess(nsIURI* aURI);
+
+ // Returns true if the current load is a force reload (started by holding
+ // shift while triggering reload)
+ bool IsForceReloading();
+
+ mozilla::dom::WindowProxyHolder GetWindowProxy() {
+ EnsureScriptEnvironment();
+ return mozilla::dom::WindowProxyHolder(mBrowsingContext);
+ }
+
+ /**
+ * Loads the given URI. See comments on nsDocShellLoadState members for more
+ * information on information used.
+ * `aCacheKey` gets passed to DoURILoad call.
+ */
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ nsresult InternalLoad(
+ nsDocShellLoadState* aLoadState,
+ mozilla::Maybe<uint32_t> aCacheKey = mozilla::Nothing());
+
+ void MaybeRestoreWindowName();
+
+ void StoreWindowNameToSHEntries();
+
+ void SetWillChangeProcess() { mWillChangeProcess = true; }
+ bool WillChangeProcess() { return mWillChangeProcess; }
+
+ // Create a content viewer within this nsDocShell for the given
+ // `WindowGlobalChild` actor.
+ nsresult CreateDocumentViewerForActor(
+ mozilla::dom::WindowGlobalChild* aWindowActor);
+
+ // Creates a real network channel (not a DocumentChannel) using the specified
+ // parameters.
+ // Used by nsDocShell when not using DocumentChannel, by DocumentLoadListener
+ // (parent-process DocumentChannel), and by DocumentChannelChild/ContentChild
+ // to transfer the resulting channel into the final process.
+ static nsresult CreateRealChannelForDocument(
+ nsIChannel** aChannel, nsIURI* aURI, nsILoadInfo* aLoadInfo,
+ nsIInterfaceRequestor* aCallbacks, nsLoadFlags aLoadFlags,
+ const nsAString& aSrcdoc, nsIURI* aBaseURI);
+
+ // Creates a real (not DocumentChannel) channel, and configures it using the
+ // supplied nsDocShellLoadState.
+ // Configuration options here are ones that should be applied to only the
+ // real channel, especially ones that need to QI to channel subclasses.
+ static bool CreateAndConfigureRealChannelForLoadState(
+ mozilla::dom::BrowsingContext* aBrowsingContext,
+ nsDocShellLoadState* aLoadState, mozilla::net::LoadInfo* aLoadInfo,
+ nsIInterfaceRequestor* aCallbacks, nsDocShell* aDocShell,
+ const mozilla::OriginAttributes& aOriginAttributes,
+ nsLoadFlags aLoadFlags, uint32_t aCacheKey, nsresult& rv,
+ nsIChannel** aChannel);
+
+ // This is used to deal with errors resulting from a failed page load.
+ // Errors are handled as follows:
+ // 1. Check to see if it's a file not found error or bad content
+ // encoding error.
+ // 2. Send the URI to a keyword server (if enabled)
+ // 3. If the error was DNS failure, then add www and .com to the URI
+ // (if appropriate).
+ // 4. If the www .com additions don't work, try those with an HTTPS scheme
+ // (if appropriate).
+ static already_AddRefed<nsIURI> AttemptURIFixup(
+ nsIChannel* aChannel, nsresult aStatus,
+ const mozilla::Maybe<nsCString>& aOriginalURIString, uint32_t aLoadType,
+ bool aIsTopFrame, bool aAllowKeywordFixup, bool aUsePrivateBrowsing,
+ bool aNotifyKeywordSearchLoading = false,
+ nsIInputStream** aNewPostData = nullptr,
+ bool* outWasSchemelessInput = nullptr);
+
+ static already_AddRefed<nsIURI> MaybeFixBadCertDomainErrorURI(
+ nsIChannel* aChannel, nsIURI* aUrl);
+
+ // Takes aStatus and filters out results that should not display
+ // an error page.
+ // If this returns a failed result, then we should display an error
+ // page with that result.
+ // aSkippedUnknownProtocolNavigation will be set to true if we chose
+ // to skip displaying an error page for an NS_ERROR_UNKNOWN_PROTOCOL
+ // navigation.
+ static nsresult FilterStatusForErrorPage(
+ nsresult aStatus, nsIChannel* aChannel, uint32_t aLoadType,
+ bool aIsTopFrame, bool aUseErrorPages, bool aIsInitialDocument,
+ bool* aSkippedUnknownProtocolNavigation = nullptr);
+
+ // Notify consumers of a search being loaded through the observer service:
+ static void MaybeNotifyKeywordSearchLoading(const nsString& aProvider,
+ const nsString& aKeyword);
+
+ nsDocShell* GetInProcessChildAt(int32_t aIndex);
+
+ static bool ShouldAddURIVisit(nsIChannel* aChannel);
+
+ /**
+ * Helper function that finds the last URI and its transition flags for a
+ * channel.
+ *
+ * This method first checks the channel's property bag to see if previous
+ * info has been saved. If not, it gives back the referrer of the channel.
+ *
+ * @param aChannel
+ * The channel we are transitioning to
+ * @param aURI
+ * Output parameter with the previous URI, not addref'd
+ * @param aChannelRedirectFlags
+ * If a redirect, output parameter with the previous redirect flags
+ * from nsIChannelEventSink
+ */
+ static void ExtractLastVisit(nsIChannel* aChannel, nsIURI** aURI,
+ uint32_t* aChannelRedirectFlags);
+
+ bool HasDocumentViewer() const { return !!mDocumentViewer; }
+
+ static uint32_t ComputeURILoaderFlags(
+ mozilla::dom::BrowsingContext* aBrowsingContext, uint32_t aLoadType);
+
+ void SetLoadingSessionHistoryInfo(
+ const mozilla::dom::LoadingSessionHistoryInfo& aLoadingInfo,
+ bool aNeedToReportActiveAfterLoadingBecomesActive = false);
+ const mozilla::dom::LoadingSessionHistoryInfo*
+ GetLoadingSessionHistoryInfo() {
+ return mLoadingEntry.get();
+ }
+
+ already_AddRefed<nsIInputStream> GetPostDataFromCurrentEntry() const;
+ mozilla::Maybe<uint32_t> GetCacheKeyFromCurrentEntry() const;
+
+ // Loading and/or active entries are only set when session history
+ // in the parent is on.
+ bool FillLoadStateFromCurrentEntry(nsDocShellLoadState& aLoadState);
+
+ static bool ShouldAddToSessionHistory(nsIURI* aURI, nsIChannel* aChannel);
+
+ bool IsOSHE(nsISHEntry* aEntry) const { return mOSHE == aEntry; }
+
+ mozilla::dom::ChildSHistory* GetSessionHistory() {
+ return mBrowsingContext->GetChildSessionHistory();
+ }
+
+ // This returns true only when using session history in parent.
+ bool IsLoadingFromSessionHistory();
+
+ NS_IMETHODIMP OnStartRequest(nsIRequest* aRequest) override;
+ NS_IMETHODIMP OnStopRequest(nsIRequest* aRequest,
+ nsresult aStatusCode) override;
+
+ private: // member functions
+ friend class nsAppShellService;
+ friend class nsDSURIContentListener;
+ friend class FramingChecker;
+ friend class OnLinkClickEvent;
+ friend class nsIDocShell;
+ friend class mozilla::dom::BrowsingContext;
+ friend class mozilla::net::DocumentLoadListener;
+ friend class nsGlobalWindowOuter;
+
+ nsDocShell(mozilla::dom::BrowsingContext* aBrowsingContext,
+ uint64_t aContentWindowID);
+
+ static inline uint32_t PRTimeToSeconds(PRTime aTimeUsec) {
+ return uint32_t(aTimeUsec / PR_USEC_PER_SEC);
+ }
+
+ virtual ~nsDocShell();
+
+ //
+ // nsDocLoader
+ //
+
+ virtual void DestroyChildren() override;
+
+ // Overridden from nsDocLoader, this provides more information than the
+ // normal OnStateChange with flags STATE_REDIRECTING
+ virtual void OnRedirectStateChange(nsIChannel* aOldChannel,
+ nsIChannel* aNewChannel,
+ uint32_t aRedirectFlags,
+ uint32_t aStateFlags) override;
+
+ // Override the parent setter from nsDocLoader
+ virtual nsresult SetDocLoaderParent(nsDocLoader* aLoader) override;
+
+ //
+ // Content Viewer Management
+ //
+
+ nsresult EnsureDocumentViewer();
+
+ // aPrincipal can be passed in if the caller wants. If null is
+ // passed in, the about:blank principal will end up being used.
+ // aCSP, if any, will be used for the new about:blank load.
+ nsresult CreateAboutBlankDocumentViewer(
+ nsIPrincipal* aPrincipal, nsIPrincipal* aPartitionedPrincipal,
+ nsIContentSecurityPolicy* aCSP, nsIURI* aBaseURI, bool aIsInitialDocument,
+ const mozilla::Maybe<nsILoadInfo::CrossOriginEmbedderPolicy>& aCOEP =
+ mozilla::Nothing(),
+ bool aTryToSaveOldPresentation = true, bool aCheckPermitUnload = true,
+ mozilla::dom::WindowGlobalChild* aActor = nullptr);
+
+ nsresult CreateDocumentViewer(const nsACString& aContentType,
+ nsIRequest* aRequest,
+ nsIStreamListener** aContentHandler);
+
+ nsresult NewDocumentViewerObj(const nsACString& aContentType,
+ nsIRequest* aRequest, nsILoadGroup* aLoadGroup,
+ nsIStreamListener** aContentHandler,
+ nsIDocumentViewer** aViewer);
+
+ already_AddRefed<nsILoadURIDelegate> GetLoadURIDelegate();
+
+ nsresult SetupNewViewer(
+ nsIDocumentViewer* aNewViewer,
+ mozilla::dom::WindowGlobalChild* aWindowActor = nullptr);
+
+ //
+ // Session History
+ //
+
+ // Either aChannel or aOwner must be null. If aChannel is
+ // present, the owner should be gotten from it.
+ // If aCloneChildren is true, then our current session history's
+ // children will be cloned onto the new entry. This should be
+ // used when we aren't actually changing the document while adding
+ // the new session history entry.
+ // aCsp is the CSP to be used for the load. That is *not* the CSP
+ // that will be applied to subresource loads within that document
+ // but the CSP for the document load itself. E.g. if that CSP
+ // includes upgrade-insecure-requests, then the new top-level load
+ // will be upgraded to HTTPS.
+ nsresult AddToSessionHistory(nsIURI* aURI, nsIChannel* aChannel,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit,
+ nsIPrincipal* aPartitionedPrincipalToInherit,
+ nsIContentSecurityPolicy* aCsp,
+ bool aCloneChildren, nsISHEntry** aNewEntry);
+
+ void UpdateActiveEntry(
+ bool aReplace, const mozilla::Maybe<nsPoint>& aPreviousScrollPos,
+ nsIURI* aURI, nsIURI* aOriginalURI, nsIReferrerInfo* aReferrerInfo,
+ nsIPrincipal* aTriggeringPrincipal, nsIContentSecurityPolicy* aCsp,
+ const nsAString& aTitle, bool aScrollRestorationIsManual,
+ nsIStructuredCloneContainer* aData, bool aURIWasModified);
+
+ nsresult AddChildSHEntry(nsISHEntry* aCloneRef, nsISHEntry* aNewEntry,
+ int32_t aChildOffset, uint32_t aLoadType,
+ bool aCloneChildren);
+
+ nsresult AddChildSHEntryToParent(nsISHEntry* aNewEntry, int32_t aChildOffset,
+ bool aCloneChildren);
+
+ // Call this method to swap in a new history entry to m[OL]SHE, rather than
+ // setting it directly. This completes the navigation in all docshells
+ // in the case of a subframe navigation.
+ // Returns old mOSHE/mLSHE.
+ already_AddRefed<nsISHEntry> SetHistoryEntry(nsCOMPtr<nsISHEntry>* aPtr,
+ nsISHEntry* aEntry);
+
+ // This method calls SetHistoryEntry and updates mOSHE and mLSHE in BC to be
+ // the same as in docshell
+ void SetHistoryEntryAndUpdateBC(const mozilla::Maybe<nsISHEntry*>& aLSHE,
+ const mozilla::Maybe<nsISHEntry*>& aOSHE);
+
+ // If aNotifiedBeforeUnloadListeners is true, "beforeunload" event listeners
+ // were notified by the caller and given the chance to abort the navigation,
+ // and should not be notified again.
+ static nsresult ReloadDocument(
+ nsDocShell* aDocShell, mozilla::dom::Document* aDocument,
+ uint32_t aLoadType, mozilla::dom::BrowsingContext* aBrowsingContext,
+ nsIURI* aCurrentURI, nsIReferrerInfo* aReferrerInfo,
+ bool aNotifiedBeforeUnloadListeners = false);
+
+ public:
+ bool IsAboutBlankLoadOntoInitialAboutBlank(nsIURI* aURI,
+ bool aInheritPrincipal,
+ nsIPrincipal* aPrincipalToInherit);
+
+ private:
+ //
+ // URI Load
+ //
+
+ // Actually open a channel and perform a URI load. Callers need to pass a
+ // non-null aLoadState->TriggeringPrincipal() which initiated the URI load.
+ // Please note that the TriggeringPrincipal will be used for performing
+ // security checks. If aLoadState->URI() is provided by the web, then please
+ // do not pass a SystemPrincipal as the triggeringPrincipal. If
+ // aLoadState()->PrincipalToInherit is null, then no inheritance of any sort
+ // will happen and the load will get a principal based on the URI being
+ // loaded. If the Srcdoc flag is set (INTERNAL_LOAD_FLAGS_IS_SRCDOC), the load
+ // will be considered as a srcdoc load, and the contents of Srcdoc will be
+ // loaded instead of the URI. aLoadState->OriginalURI() will be set as the
+ // originalURI on the channel that does the load. If OriginalURI is null, URI
+ // will be set as the originalURI. If LoadReplace is true, LOAD_REPLACE flag
+ // will be set on the nsIChannel.
+ // If `aCacheKey` is supplied, use it for the session history entry.
+ nsresult DoURILoad(nsDocShellLoadState* aLoadState,
+ mozilla::Maybe<uint32_t> aCacheKey, nsIRequest** aRequest);
+
+ static nsresult AddHeadersToChannel(nsIInputStream* aHeadersData,
+ nsIChannel* aChannel);
+
+ nsresult OpenInitializedChannel(nsIChannel* aChannel,
+ nsIURILoader* aURILoader,
+ uint32_t aOpenFlags);
+ nsresult OpenRedirectedChannel(nsDocShellLoadState* aLoadState);
+
+ void UpdateMixedContentChannelForNewLoad(nsIChannel* aChannel);
+
+ MOZ_CAN_RUN_SCRIPT
+ nsresult ScrollToAnchor(bool aCurHasRef, bool aNewHasRef,
+ nsACString& aNewHash, uint32_t aLoadType);
+
+ // This returns the load type for a form submission (see
+ // https://html.spec.whatwg.org/#form-submission-algorithm). The load type
+ // should be set as soon as the target BC has been determined.
+ uint32_t GetLoadTypeForFormSubmission(
+ mozilla::dom::BrowsingContext* aTargetBC,
+ nsDocShellLoadState* aLoadState);
+
+ private:
+ // Returns true if it is the caller's responsibility to ensure
+ // FireOnLocationChange is called.
+ // In all other cases false is returned.
+ // Either aChannel or aTriggeringPrincipal must be null. If aChannel is
+ // present, the owner should be gotten from it.
+ // If OnNewURI calls AddToSessionHistory, it will pass its
+ // aCloneSHChildren argument as aCloneChildren.
+ // aCsp is the CSP to be used for the load. That is *not* the CSP
+ // that will be applied to subresource loads within that document
+ // but the CSP for the document load itself. E.g. if that CSP
+ // includes upgrade-insecure-requests, then the new top-level load
+ // will be upgraded to HTTPS.
+ bool OnNewURI(nsIURI* aURI, nsIChannel* aChannel,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit,
+ nsIPrincipal* aPartitionedPrincipalToInherit,
+ nsIContentSecurityPolicy* aCsp, bool aAddToGlobalHistory,
+ bool aCloneSHChildren);
+
+ public:
+ // If wireframe collection is enabled, will attempt to gather the
+ // wireframe for the document.
+ mozilla::Maybe<mozilla::dom::Wireframe> GetWireframe();
+
+ // If wireframe collection is enabled, will attempt to gather the
+ // wireframe for the document and stash it inside of the active history
+ // entry. Returns true if wireframes were collected.
+ bool CollectWireframe();
+
+ // Helper method that is called when a new document (including any
+ // sub-documents - ie. frames) has been completely loaded.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ nsresult EndPageLoad(nsIWebProgress* aProgress, nsIChannel* aChannel,
+ nsresult aResult);
+
+ // Builds an error page URI (e.g. about:neterror?etc) for the given aURI
+ // and displays it via the LoadErrorPage() overload below.
+ nsresult LoadErrorPage(nsIURI* aURI, const char16_t* aURL,
+ const char* aErrorPage, const char* aErrorType,
+ const char16_t* aDescription, const char* aCSSClass,
+ nsIChannel* aFailedChannel);
+
+ // This method directly loads aErrorURI as an error page. aFailedURI and
+ // aFailedChannel come from DisplayLoadError() or the LoadErrorPage() overload
+ // above.
+ nsresult LoadErrorPage(nsIURI* aErrorURI, nsIURI* aFailedURI,
+ nsIChannel* aFailedChannel);
+
+ bool DisplayLoadError(nsresult aError, nsIURI* aURI, const char16_t* aURL,
+ nsIChannel* aFailedChannel) {
+ bool didDisplayLoadError = false;
+ DisplayLoadError(aError, aURI, aURL, aFailedChannel, &didDisplayLoadError);
+ return didDisplayLoadError;
+ }
+
+ //
+ // Uncategorized
+ //
+
+ // Get the principal that we'll set on the channel if we're inheriting. If
+ // aConsiderCurrentDocument is true, we try to use the current document if
+ // at all possible. If that fails, we fall back on the parent document.
+ // If that fails too, we force creation of a content viewer and use the
+ // resulting principal. If aConsiderCurrentDocument is false, we just look
+ // at the parent.
+ // If aConsiderPartitionedPrincipal is true, we consider the partitioned
+ // principal instead of the node principal.
+ nsIPrincipal* GetInheritedPrincipal(
+ bool aConsiderCurrentDocument,
+ bool aConsiderPartitionedPrincipal = false);
+
+ /**
+ * Helper function that caches a URI and a transition for saving later.
+ *
+ * @param aChannel
+ * Channel that will have these properties saved
+ * @param aURI
+ * The URI to save for later
+ * @param aChannelRedirectFlags
+ * The nsIChannelEventSink redirect flags to save for later
+ */
+ static void SaveLastVisit(nsIChannel* aChannel, nsIURI* aURI,
+ uint32_t aChannelRedirectFlags);
+
+ /**
+ * Helper function for adding a URI visit using IHistory.
+ *
+ * The IHistory API maintains chains of visits, tracking both HTTP referrers
+ * and redirects for a user session. VisitURI requires the current URI and
+ * the previous URI in the chain.
+ *
+ * Visits can be saved either during a redirect or when the request has
+ * reached its final destination. The previous URI in the visit may be
+ * from another redirect.
+ *
+ * @pre aURI is not null.
+ *
+ * @param aURI
+ * The URI that was just visited
+ * @param aPreviousURI
+ * The previous URI of this visit
+ * @param aChannelRedirectFlags
+ * For redirects, the redirect flags from nsIChannelEventSink
+ * (0 otherwise)
+ * @param aResponseStatus
+ * For HTTP channels, the response code (0 otherwise).
+ */
+ void AddURIVisit(nsIURI* aURI, nsIURI* aPreviousURI,
+ uint32_t aChannelRedirectFlags,
+ uint32_t aResponseStatus = 0);
+
+ /**
+ * Internal helper funtion
+ */
+ static void InternalAddURIVisit(
+ nsIURI* aURI, nsIURI* aPreviousURI, uint32_t aChannelRedirectFlags,
+ uint32_t aResponseStatus, mozilla::dom::BrowsingContext* aBrowsingContext,
+ nsIWidget* aWidget, uint32_t aLoadType, bool aWasUpgraded);
+
+ static already_AddRefed<nsIURIFixupInfo> KeywordToURI(
+ const nsACString& aKeyword, bool aIsPrivateContext);
+
+ // Sets the current document's current state object to the given SHEntry's
+ // state object. The current state object is eventually given to the page
+ // in the PopState event.
+ void SetDocCurrentStateObj(nsISHEntry* aShEntry,
+ mozilla::dom::SessionHistoryInfo* aInfo);
+
+ // Returns true if would have called FireOnLocationChange,
+ // but did not because aFireOnLocationChange was false on entry.
+ // In this case it is the caller's responsibility to ensure
+ // FireOnLocationChange is called.
+ // In all other cases false is returned.
+ bool SetCurrentURI(nsIURI* aURI, nsIRequest* aRequest,
+ bool aFireOnLocationChange, bool aIsInitialAboutBlank,
+ uint32_t aLocationFlags);
+
+ // The following methods deal with saving and restoring content viewers
+ // in session history.
+
+ // mDocumentViewer points to the current content viewer associated with
+ // this docshell. When loading a new document, the content viewer is
+ // either destroyed or stored into a session history entry. To make sure
+ // that destruction happens in a controlled fashion, a given content viewer
+ // is always owned in exactly one of these ways:
+ // 1) The content viewer is active and owned by a docshell's
+ // mDocumentViewer.
+ // 2) The content viewer is still being displayed while we begin loading
+ // a new document. The content viewer is owned by the _new_
+ // content viewer's mPreviousViewer, and has a pointer to the
+ // nsISHEntry where it will eventually be stored. The content viewer
+ // has been close()d by the docshell, which detaches the document from
+ // the window object.
+ // 3) The content viewer is cached in session history. The nsISHEntry
+ // has the only owning reference to the content viewer. The viewer
+ // has released its nsISHEntry pointer to prevent circular ownership.
+ //
+ // When restoring a content viewer from session history, open() is called
+ // to reattach the document to the window object. The content viewer is
+ // then placed into mDocumentViewer and removed from the history entry.
+ // (mDocumentViewer is put into session history as described above, if
+ // applicable).
+
+ // Determines whether we can safely cache the current mDocumentViewer in
+ // session history. This checks a number of factors such as cache policy,
+ // pending requests, and unload handlers.
+ // |aLoadType| should be the load type that will replace the current
+ // presentation. |aNewRequest| should be the request for the document to
+ // be loaded in place of the current document, or null if such a request
+ // has not been created yet. |aNewDocument| should be the document that will
+ // replace the current document.
+ bool CanSavePresentation(uint32_t aLoadType, nsIRequest* aNewRequest,
+ mozilla::dom::Document* aNewDocument,
+ bool aReportBFCacheComboTelemetry);
+
+ static void ReportBFCacheComboTelemetry(uint32_t aCombo);
+
+ // Captures the state of the supporting elements of the presentation
+ // (the "window" object, docshell tree, meta-refresh loads, and security
+ // state) and stores them on |mOSHE|.
+ nsresult CaptureState();
+
+ // Begin the toplevel restore process for |aSHEntry|.
+ // This simulates a channel open, and defers the real work until
+ // RestoreFromHistory is called from a PLEvent.
+ nsresult RestorePresentation(nsISHEntry* aSHEntry, bool* aRestoring);
+
+ // Call BeginRestore(nullptr, false) for each child of this shell.
+ nsresult BeginRestoreChildren();
+
+ // Method to get our current position and size without flushing
+ void DoGetPositionAndSize(int32_t* aX, int32_t* aY, int32_t* aWidth,
+ int32_t* aHeight);
+
+ // Call this when a URI load is handed to us (via OnLinkClick or
+ // InternalLoad). This makes sure that we're not inside unload, or that if
+ // we are it's still OK to load this URI.
+ bool IsOKToLoadURI(nsIURI* aURI);
+
+ // helpers for executing commands
+ nsresult GetControllerForCommand(const char* aCommand,
+ nsIController** aResult);
+
+ // Possibly create a ClientSource object to represent an initial about:blank
+ // window that has not been allocated yet. Normally we try not to create
+ // this about:blank window until something calls GetDocument(). We still need
+ // the ClientSource to exist for this conceptual window, though.
+ //
+ // The ClientSource is created with the given principal if specified. If
+ // the principal is not provided we will attempt to inherit it when we
+ // are sure it will match what the real about:blank window principal
+ // would have been. There are some corner cases where we cannot easily
+ // determine the correct principal and will not create the ClientSource.
+ // In these cases the initial about:blank will appear to not exist until
+ // its real document and window are created.
+ void MaybeCreateInitialClientSource(nsIPrincipal* aPrincipal = nullptr);
+
+ // Determine if a service worker is allowed to control a window in this
+ // docshell with the given URL. If there are any reasons it should not,
+ // this will return false. If true is returned then the window *may* be
+ // controlled. The caller must still consult either the parent controller
+ // or the ServiceWorkerManager to determine if a service worker should
+ // actually control the window.
+ bool ServiceWorkerAllowedToControlWindow(nsIPrincipal* aPrincipal,
+ nsIURI* aURI);
+
+ // Return the ClientInfo for the initial about:blank window, if it exists
+ // or we have speculatively created a ClientSource in
+ // MaybeCreateInitialClientSource(). This can return a ClientInfo object
+ // even if GetExtantDoc() returns nullptr.
+ mozilla::Maybe<mozilla::dom::ClientInfo> GetInitialClientInfo() const;
+
+ /**
+ * Initializes mTiming if it isn't yet.
+ * After calling this, mTiming is non-null. This method returns true if the
+ * initialization of the Timing can be reset (basically this is true if a new
+ * Timing object is created).
+ * In case the loading is aborted, MaybeResetInitTiming() can be called
+ * passing the return value of MaybeInitTiming(): if it's possible to reset
+ * the Timing, this method will do it.
+ */
+ [[nodiscard]] bool MaybeInitTiming();
+ void MaybeResetInitTiming(bool aReset);
+
+ // Convenience method for getting our parent docshell. Can return null
+ already_AddRefed<nsDocShell> GetInProcessParentDocshell();
+
+ // Internal implementation of nsIDocShell::FirePageHideNotification.
+ // If aSkipCheckingDynEntries is true, it will not try to remove dynamic
+ // subframe entries. This is to avoid redundant RemoveDynEntries calls in all
+ // children docshells.
+ // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void FirePageHideNotificationInternal(
+ bool aIsUnload, bool aSkipCheckingDynEntries);
+
+ void ThawFreezeNonRecursive(bool aThaw);
+ // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void FirePageHideShowNonRecursive(bool aShow);
+
+ nsresult Dispatch(already_AddRefed<nsIRunnable>&& aRunnable);
+
+ void ReattachEditorToWindow(nsISHEntry* aSHEntry);
+ void ClearFrameHistory(nsISHEntry* aEntry);
+ // Determine if this type of load should update history.
+ static bool ShouldUpdateGlobalHistory(uint32_t aLoadType);
+ void UpdateGlobalHistoryTitle(nsIURI* aURI);
+ bool IsSubframe() { return mBrowsingContext->IsSubframe(); }
+ bool CanSetOriginAttributes();
+ bool ShouldBlockLoadingForBackButton();
+ static bool ShouldDiscardLayoutState(nsIHttpChannel* aChannel);
+ bool HasUnloadedParent();
+ bool JustStartedNetworkLoad();
+ bool NavigationBlockedByPrinting(bool aDisplayErrorDialog = true);
+ bool IsNavigationAllowed(bool aDisplayPrintErrorDialog = true,
+ bool aCheckIfUnloadFired = true);
+ nsIScrollableFrame* GetRootScrollFrame();
+ nsIChannel* GetCurrentDocChannel();
+ nsresult EnsureScriptEnvironment();
+ nsresult EnsureEditorData();
+ nsresult EnsureTransferableHookData();
+ nsresult EnsureFind();
+ nsresult EnsureCommandHandler();
+ nsresult RefreshURIFromQueue();
+ void RefreshURIToQueue();
+ nsresult Embed(nsIDocumentViewer* aDocumentViewer,
+ mozilla::dom::WindowGlobalChild* aWindowActor,
+ bool aIsTransientAboutBlank, bool aPersist,
+ nsIRequest* aRequest, nsIURI* aPreviousURI);
+ nsPresContext* GetEldestPresContext();
+ nsresult CheckLoadingPermissions();
+ nsresult LoadHistoryEntry(nsISHEntry* aEntry, uint32_t aLoadType,
+ bool aUserActivation);
+ nsresult LoadHistoryEntry(
+ const mozilla::dom::LoadingSessionHistoryInfo& aEntry, uint32_t aLoadType,
+ bool aUserActivation);
+ nsresult LoadHistoryEntry(nsDocShellLoadState* aLoadState, uint32_t aLoadType,
+ bool aLoadingCurrentEntry);
+ nsresult GetHttpChannel(nsIChannel* aChannel, nsIHttpChannel** aReturn);
+ nsresult ConfirmRepost(bool* aRepost);
+ nsresult GetPromptAndStringBundle(nsIPrompt** aPrompt,
+ nsIStringBundle** aStringBundle);
+ nsresult SetCurScrollPosEx(int32_t aCurHorizontalPos,
+ int32_t aCurVerticalPos);
+ nsPoint GetCurScrollPos();
+
+ already_AddRefed<mozilla::dom::ChildSHistory> GetRootSessionHistory();
+
+ bool CSSErrorReportingEnabled() const { return mCSSErrorReportingEnabled; }
+
+ // Handles retrieval of subframe session history for nsDocShell::LoadURI. If a
+ // load is requested in a subframe of the current DocShell, the subframe
+ // loadType may need to reflect the loadType of the parent document, or in
+ // some cases (like reloads), the history load may need to be cancelled. See
+ // function comments for in-depth logic descriptions.
+ // Returns true if the method itself deals with the load.
+ bool MaybeHandleSubframeHistory(nsDocShellLoadState* aLoadState,
+ bool aContinueHandlingSubframeHistory);
+
+ // If we are passed a named target during InternalLoad, this method handles
+ // moving the load to the browsing context the target name resolves to.
+ nsresult PerformRetargeting(nsDocShellLoadState* aLoadState);
+
+ // Returns one of nsIContentPolicy::TYPE_DOCUMENT,
+ // nsIContentPolicy::TYPE_INTERNAL_IFRAME, or
+ // nsIContentPolicy::TYPE_INTERNAL_FRAME depending on who is responsible for
+ // this docshell.
+ nsContentPolicyType DetermineContentType();
+
+ // If this is an iframe, and the embedder is OOP, then notifes the
+ // embedder that loading has finished and we shouldn't be blocking
+ // load of the embedder. Only called when we fail to load, as we wait
+ // for the load event of our Document before notifying success.
+ //
+ // If aFireFrameErrorEvent is true, then fires an error event at the
+ // embedder element, for both in-process and OOP embedders.
+ void UnblockEmbedderLoadEventForFailure(bool aFireFrameErrorEvent = false);
+
+ struct SameDocumentNavigationState {
+ nsAutoCString mCurrentHash;
+ nsAutoCString mNewHash;
+ bool mCurrentURIHasRef = false;
+ bool mNewURIHasRef = false;
+ bool mSameExceptHashes = false;
+ bool mSecureUpgradeURI = false;
+ bool mHistoryNavBetweenSameDoc = false;
+ };
+
+ // Check to see if we're loading a prior history entry or doing a fragment
+ // navigation in the same document.
+ // NOTE: In case we are doing a fragment navigation, and HTTPS-Only/ -First
+ // mode is enabled and upgraded the underlying document, we update the URI of
+ // aLoadState from HTTP to HTTPS (if neccessary).
+ bool IsSameDocumentNavigation(nsDocShellLoadState* aLoadState,
+ SameDocumentNavigationState& aState);
+
+ // ... If so, handle the scrolling or other action required instead of
+ // continuing with new document navigation.
+ MOZ_CAN_RUN_SCRIPT
+ nsresult HandleSameDocumentNavigation(nsDocShellLoadState* aLoadState,
+ SameDocumentNavigationState& aState,
+ bool& aSameDocument);
+
+ uint32_t GetSameDocumentNavigationFlags(nsIURI* aNewURI);
+
+ // Called when the Private Browsing state of a nsDocShell changes.
+ void NotifyPrivateBrowsingChanged();
+
+ // Internal helpers for BrowsingContext to pass update values to nsIDocShell's
+ // LoadGroup.
+ void SetLoadGroupDefaultLoadFlags(nsLoadFlags aLoadFlags);
+
+ void SetTitleOnHistoryEntry(bool aUpdateEntryInSessionHistory);
+
+ void SetScrollRestorationIsManualOnHistoryEntry(nsISHEntry* aSHEntry,
+ bool aIsManual);
+
+ void SetCacheKeyOnHistoryEntry(nsISHEntry* aSHEntry, uint32_t aCacheKey);
+
+ // If the LoadState's URI is a javascript: URI, checks that the triggering
+ // principal subsumes the principal of the current document, and returns
+ // NS_ERROR_DOM_BAD_CROSS_ORIGIN_URI if it does not.
+ nsresult CheckDisallowedJavascriptLoad(nsDocShellLoadState* aLoadState);
+
+ nsresult LoadURI(nsDocShellLoadState* aLoadState, bool aSetNavigating,
+ bool aContinueHandlingSubframeHistory);
+
+ // Sets the active entry to the current loading entry. aPersist is used in the
+ // case a new session history entry is added to the session history.
+ // aExpired is true if the relevant nsIChannel has its cache token expired.
+ // aCacheKey is the channel's cache key.
+ // aPreviousURI should be the URI that was previously loaded into the
+ // nsDocshell
+ void MoveLoadingToActiveEntry(bool aPersist, bool aExpired,
+ uint32_t aCacheKey, nsIURI* aPreviousURI);
+
+ void ActivenessMaybeChanged();
+
+ /**
+ * Returns true if `noopener` will be force-enabled by any attempt to create
+ * a popup window, even if rel="opener" is requested.
+ */
+ bool NoopenerForceEnabled();
+
+ bool ShouldOpenInBlankTarget(const nsAString& aOriginalTarget,
+ nsIURI* aLinkURI, nsIContent* aContent,
+ bool aIsUserTriggered);
+
+ void RecordSingleChannelId(bool aStartRequest, nsIRequest* aRequest);
+
+ void SetChannelToDisconnectOnPageHide(uint64_t aChannelId) {
+ MOZ_ASSERT(mChannelToDisconnectOnPageHide == 0);
+ mChannelToDisconnectOnPageHide = aChannelId;
+ }
+ void MaybeDisconnectChildListenersOnPageHide();
+
+ /**
+ * Helper for addState and document.open that does just the
+ * history-manipulation guts.
+ *
+ * Arguments the spec defines:
+ *
+ * @param aDocument the document we're manipulating. This will get the new
+ * URI.
+ * @param aNewURI the new URI.
+ * @param aData The serialized state data. May be null.
+ * @param aTitle The new title. May be empty.
+ * @param aReplace whether this should replace the exising SHEntry.
+ *
+ * Arguments we need internally because deriving them from the
+ * others is a bit complicated:
+ *
+ * @param aCurrentURI the current URI we're working with. Might be null.
+ * @param aEqualURIs whether the two URIs involved are equal.
+ */
+ nsresult UpdateURLAndHistory(mozilla::dom::Document* aDocument,
+ nsIURI* aNewURI,
+ nsIStructuredCloneContainer* aData,
+ const nsAString& aTitle, bool aReplace,
+ nsIURI* aCurrentURI, bool aEqualURIs);
+
+ private:
+ void SetCurrentURIInternal(nsIURI* aURI);
+
+ // data members
+ nsString mTitle;
+ nsCString mOriginalUriString;
+ nsTObserverArray<nsWeakPtr> mPrivacyObservers;
+ nsTObserverArray<nsWeakPtr> mReflowObservers;
+ nsTObserverArray<nsWeakPtr> mScrollObservers;
+ mozilla::UniquePtr<mozilla::dom::ClientSource> mInitialClientSource;
+ nsCOMPtr<nsINetworkInterceptController> mInterceptController;
+ RefPtr<nsDOMNavigationTiming> mTiming;
+ RefPtr<nsDSURIContentListener> mContentListener;
+ RefPtr<nsGlobalWindowOuter> mScriptGlobal;
+ nsCOMPtr<nsIPrincipal> mParentCharsetPrincipal;
+ // The following 3 lists contain either nsITimer or nsRefreshTimer objects.
+ // URIs to refresh are collected to mRefreshURIList.
+ nsCOMPtr<nsIMutableArray> mRefreshURIList;
+ // mSavedRefreshURIList is used to move the entries from mRefreshURIList to
+ // mOSHE.
+ nsCOMPtr<nsIMutableArray> mSavedRefreshURIList;
+ // BFCache-in-parent implementation caches the entries in
+ // mBFCachedRefreshURIList.
+ nsCOMPtr<nsIMutableArray> mBFCachedRefreshURIList;
+ uint64_t mContentWindowID;
+ nsCOMPtr<nsIDocumentViewer> mDocumentViewer;
+ nsCOMPtr<nsIWidget> mParentWidget;
+ RefPtr<mozilla::dom::ChildSHistory> mSessionHistory;
+ nsCOMPtr<nsIWebBrowserFind> mFind;
+ RefPtr<nsCommandManager> mCommandManager;
+ RefPtr<mozilla::dom::BrowsingContext> mBrowsingContext;
+
+ // Weak reference to our BrowserChild actor.
+ nsWeakPtr mBrowserChild;
+
+ // Dimensions of the docshell
+ nsIntRect mBounds;
+
+ /**
+ * Content-Type Hint of the most-recently initiated load. Used for
+ * session history entries.
+ */
+ nsCString mContentTypeHint;
+
+ // mCurrentURI should be marked immutable on set if possible.
+ // Change mCurrentURI only through SetCurrentURIInternal method.
+ nsCOMPtr<nsIURI> mCurrentURI;
+ nsCOMPtr<nsIReferrerInfo> mReferrerInfo;
+
+#ifdef DEBUG
+ // We're counting the number of |nsDocShells| to help find leaks
+ static unsigned long gNumberOfDocShells;
+
+ nsCOMPtr<nsIURI> mLastOpenedURI;
+#endif
+
+ // Reference to the SHEntry for this docshell until the page is destroyed.
+ // Somebody give me better name
+ // Only used when SHIP is disabled.
+ nsCOMPtr<nsISHEntry> mOSHE;
+
+ // Reference to the SHEntry for this docshell until the page is loaded
+ // Somebody give me better name.
+ // If mLSHE is non-null, non-pushState subframe loads don't create separate
+ // root history entries. That is, frames loaded during the parent page
+ // load don't generate history entries the way frame navigation after the
+ // parent has loaded does. (This isn't the only purpose of mLSHE.)
+ // Only used when SHIP is disabled.
+ nsCOMPtr<nsISHEntry> mLSHE;
+
+ // These are only set when fission.sessionHistoryInParent is set.
+ mozilla::UniquePtr<mozilla::dom::SessionHistoryInfo> mActiveEntry;
+ bool mActiveEntryIsLoadingFromSessionHistory = false;
+ // mLoadingEntry is set when we're about to start loading. Whenever
+ // setting mLoadingEntry, be sure to also set
+ // mNeedToReportActiveAfterLoadingBecomesActive.
+ mozilla::UniquePtr<mozilla::dom::LoadingSessionHistoryInfo> mLoadingEntry;
+
+ // Holds a weak pointer to a RestorePresentationEvent object if any that
+ // holds a weak pointer back to us. We use this pointer to possibly revoke
+ // the event whenever necessary.
+ nsRevocableEventPtr<RestorePresentationEvent> mRestorePresentationEvent;
+
+ // Editor data, if this document is designMode or contentEditable.
+ mozilla::UniquePtr<nsDocShellEditorData> mEditorData;
+
+ // The URI we're currently loading. This is only relevant during the
+ // firing of a pagehide/unload. The caller of FirePageHideNotification()
+ // is responsible for setting it and unsetting it. It may be null if the
+ // pagehide/unload is happening for some reason other than just loading a
+ // new URI.
+ nsCOMPtr<nsIURI> mLoadingURI;
+
+ // Set in LoadErrorPage from the method argument and used later
+ // in CreateDocumentViewer. We have to delay an shistory entry creation
+ // for which these objects are needed.
+ nsCOMPtr<nsIURI> mFailedURI;
+ nsCOMPtr<nsIChannel> mFailedChannel;
+
+ mozilla::UniquePtr<mozilla::gfx::Matrix5x4> mColorMatrix;
+
+ const mozilla::Encoding* mParentCharset;
+
+ // WEAK REFERENCES BELOW HERE.
+ // Note these are intentionally not addrefd. Doing so will create a cycle.
+ // For that reasons don't use nsCOMPtr.
+
+ nsIDocShellTreeOwner* mTreeOwner; // Weak Reference
+
+ RefPtr<mozilla::dom::EventTarget> mChromeEventHandler;
+
+ mozilla::ScrollbarPreference mScrollbarPref; // persistent across doc loads
+
+ eCharsetReloadState mCharsetReloadState;
+
+ int32_t mParentCharsetSource;
+ mozilla::CSSIntSize mFrameMargins;
+
+ // This can either be a content docshell or a chrome docshell.
+ const int32_t mItemType;
+
+ // Index into the nsISHEntry array, indicating the previous and current
+ // entry at the time that this DocShell begins to load. Consequently
+ // root docshell's indices can differ from child docshells'.
+ int32_t mPreviousEntryIndex;
+ int32_t mLoadedEntryIndex;
+
+ BusyFlags mBusyFlags;
+ AppType mAppType;
+ uint32_t mLoadType;
+ uint32_t mFailedLoadType;
+
+ // Whether or not handling of the <meta name="viewport"> tag is overridden.
+ // Possible values are defined as constants in nsIDocShell.idl.
+ MetaViewportOverride mMetaViewportOverride;
+
+ // See WindowGlobalParent::mSingleChannelId.
+ mozilla::Maybe<uint64_t> mSingleChannelId;
+ uint32_t mRequestForBlockingFromBFCacheCount = 0;
+
+ uint64_t mChannelToDisconnectOnPageHide;
+
+ uint32_t mPendingReloadCount = 0;
+
+ // The following two fields cannot be declared as bit fields
+ // because of uses with AutoRestore.
+ bool mCreatingDocument; // (should be) debugging only
+#ifdef DEBUG
+ bool mInEnsureScriptEnv;
+ uint64_t mDocShellID = 0;
+#endif
+
+ bool mInitialized : 1;
+ bool mAllowSubframes : 1;
+ bool mAllowMetaRedirects : 1;
+ bool mAllowImages : 1;
+ bool mAllowMedia : 1;
+ bool mAllowDNSPrefetch : 1;
+ bool mAllowWindowControl : 1;
+ bool mCSSErrorReportingEnabled : 1;
+ bool mAllowAuth : 1;
+ bool mAllowKeywordFixup : 1;
+ bool mDisableMetaRefreshWhenInactive : 1;
+ bool mIsAppTab : 1;
+ bool mWindowDraggingAllowed : 1;
+ bool mInFrameSwap : 1;
+
+ // This boolean is set to true right before we fire pagehide and generally
+ // unset when we embed a new content viewer. While it's true no navigation
+ // is allowed in this docshell.
+ bool mFiredUnloadEvent : 1;
+
+ // this flag is for bug #21358. a docshell may load many urls
+ // which don't result in new documents being created (i.e. a new
+ // content viewer) we want to make sure we don't call a on load
+ // event more than once for a given content viewer.
+ bool mEODForCurrentDocument : 1;
+ bool mURIResultedInDocument : 1;
+
+ bool mIsBeingDestroyed : 1;
+
+ bool mIsExecutingOnLoadHandler : 1;
+
+ // Indicates to CreateDocumentViewer() that it is safe to cache the old
+ // presentation of the page, and to SetupNewViewer() that the old viewer
+ // should be passed a SHEntry to save itself into.
+ // Only used with SHIP disabled.
+ bool mSavingOldViewer : 1;
+
+ bool mInvisible : 1;
+ bool mHasLoadedNonBlankURI : 1;
+
+ // This flag means that mTiming has been initialized but nulled out.
+ // We will check the innerWin's timing before creating a new one
+ // in MaybeInitTiming()
+ bool mBlankTiming : 1;
+
+ // This flag indicates when the title is valid for the current URI.
+ bool mTitleValidForCurrentURI : 1;
+
+ // If mWillChangeProcess is set to true, then when the docshell is destroyed,
+ // we prepare the browsing context to change process.
+ bool mWillChangeProcess : 1;
+
+ // This flag indicates whether or not the DocShell is currently executing an
+ // nsIWebNavigation navigation method.
+ bool mIsNavigating : 1;
+
+ // Whether we have a pending encoding autodetection request from the
+ // menu for all encodings.
+ bool mForcedAutodetection : 1;
+
+ /*
+ * Set to true if we're checking session history (in the parent process) for
+ * a possible history load. Used only with iframes.
+ */
+ bool mCheckingSessionHistory : 1;
+
+ // Whether mBrowsingContext->SetActiveSessionHistoryEntry() needs to be called
+ // when the loading entry becomes the active entry. This is used for the
+ // initial about:blank-replacing about:blank in order to make the history
+ // length WPTs pass.
+ bool mNeedToReportActiveAfterLoadingBecomesActive : 1;
+};
+
+inline nsISupports* ToSupports(nsDocShell* aDocShell) {
+ return static_cast<nsIDocumentLoader*>(aDocShell);
+}
+
+#endif /* nsDocShell_h__ */
diff --git a/docshell/base/nsDocShellEditorData.cpp b/docshell/base/nsDocShellEditorData.cpp
new file mode 100644
index 0000000000..6fe132a977
--- /dev/null
+++ b/docshell/base/nsDocShellEditorData.cpp
@@ -0,0 +1,139 @@
+/* -*- 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 "nsDocShellEditorData.h"
+
+#include "mozilla/dom/Document.h"
+#include "mozilla/HTMLEditor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsPIDOMWindow.h"
+#include "nsEditingSession.h"
+#include "nsIDocShell.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsDocShellEditorData::nsDocShellEditorData(nsIDocShell* aOwningDocShell)
+ : mDocShell(aOwningDocShell),
+ mDetachedEditingState(Document::EditingState::eOff),
+ mMakeEditable(false),
+ mIsDetached(false),
+ mDetachedMakeEditable(false) {
+ NS_ASSERTION(mDocShell, "Where is my docShell?");
+}
+
+nsDocShellEditorData::~nsDocShellEditorData() { TearDownEditor(); }
+
+void nsDocShellEditorData::TearDownEditor() {
+ if (mHTMLEditor) {
+ RefPtr<HTMLEditor> htmlEditor = std::move(mHTMLEditor);
+ htmlEditor->PreDestroy();
+ }
+ mEditingSession = nullptr;
+ mIsDetached = false;
+}
+
+nsresult nsDocShellEditorData::MakeEditable(bool aInWaitForUriLoad) {
+ if (mMakeEditable) {
+ return NS_OK;
+ }
+
+ // if we are already editable, and are getting turned off,
+ // nuke the editor.
+ if (mHTMLEditor) {
+ NS_WARNING("Destroying existing editor on frame");
+
+ RefPtr<HTMLEditor> htmlEditor = std::move(mHTMLEditor);
+ htmlEditor->PreDestroy();
+ }
+
+ if (aInWaitForUriLoad) {
+ mMakeEditable = true;
+ }
+ return NS_OK;
+}
+
+bool nsDocShellEditorData::GetEditable() {
+ return mMakeEditable || (mHTMLEditor != nullptr);
+}
+
+nsEditingSession* nsDocShellEditorData::GetEditingSession() {
+ EnsureEditingSession();
+
+ return mEditingSession.get();
+}
+
+nsresult nsDocShellEditorData::SetHTMLEditor(HTMLEditor* aHTMLEditor) {
+ // destroy any editor that we have. Checks for equality are
+ // necessary to ensure that assigment into the nsCOMPtr does
+ // not temporarily reduce the refCount of the editor to zero
+ if (mHTMLEditor == aHTMLEditor) {
+ return NS_OK;
+ }
+
+ if (mHTMLEditor) {
+ RefPtr<HTMLEditor> htmlEditor = std::move(mHTMLEditor);
+ htmlEditor->PreDestroy();
+ MOZ_ASSERT(!mHTMLEditor,
+ "Nested call of nsDocShellEditorData::SetHTMLEditor() detected");
+ }
+
+ mHTMLEditor = aHTMLEditor; // owning addref
+ if (!mHTMLEditor) {
+ mMakeEditable = false;
+ }
+
+ return NS_OK;
+}
+
+// This creates the editing session on the content docShell that owns 'this'.
+void nsDocShellEditorData::EnsureEditingSession() {
+ NS_ASSERTION(mDocShell, "Should have docShell here");
+ NS_ASSERTION(!mIsDetached, "This will stomp editing session!");
+
+ if (!mEditingSession) {
+ mEditingSession = new nsEditingSession();
+ }
+}
+
+nsresult nsDocShellEditorData::DetachFromWindow() {
+ NS_ASSERTION(mEditingSession,
+ "Can't detach when we don't have a session to detach!");
+
+ nsCOMPtr<nsPIDOMWindowOuter> domWindow =
+ mDocShell ? mDocShell->GetWindow() : nullptr;
+ nsresult rv = mEditingSession->DetachFromWindow(domWindow);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mIsDetached = true;
+ mDetachedMakeEditable = mMakeEditable;
+ mMakeEditable = false;
+
+ nsCOMPtr<dom::Document> doc = domWindow->GetDoc();
+ mDetachedEditingState = doc->GetEditingState();
+
+ mDocShell = nullptr;
+
+ return NS_OK;
+}
+
+nsresult nsDocShellEditorData::ReattachToWindow(nsIDocShell* aDocShell) {
+ mDocShell = aDocShell;
+
+ nsCOMPtr<nsPIDOMWindowOuter> domWindow =
+ mDocShell ? mDocShell->GetWindow() : nullptr;
+ nsresult rv = mEditingSession->ReattachToWindow(domWindow);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mIsDetached = false;
+ mMakeEditable = mDetachedMakeEditable;
+
+ RefPtr<dom::Document> doc = domWindow->GetDoc();
+ doc->SetEditingState(mDetachedEditingState);
+
+ return NS_OK;
+}
diff --git a/docshell/base/nsDocShellEditorData.h b/docshell/base/nsDocShellEditorData.h
new file mode 100644
index 0000000000..27f840675b
--- /dev/null
+++ b/docshell/base/nsDocShellEditorData.h
@@ -0,0 +1,66 @@
+/* -*- 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 nsDocShellEditorData_h__
+#define nsDocShellEditorData_h__
+
+#ifndef nsCOMPtr_h___
+# include "nsCOMPtr.h"
+#endif
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/Document.h"
+
+class nsIDocShell;
+class nsEditingSession;
+
+namespace mozilla {
+class HTMLEditor;
+}
+
+class nsDocShellEditorData {
+ public:
+ explicit nsDocShellEditorData(nsIDocShell* aOwningDocShell);
+ ~nsDocShellEditorData();
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult MakeEditable(bool aWaitForUriLoad);
+ bool GetEditable();
+ nsEditingSession* GetEditingSession();
+ mozilla::HTMLEditor* GetHTMLEditor() const { return mHTMLEditor; }
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult
+ SetHTMLEditor(mozilla::HTMLEditor* aHTMLEditor);
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void TearDownEditor();
+ nsresult DetachFromWindow();
+ nsresult ReattachToWindow(nsIDocShell* aDocShell);
+ bool WaitingForLoad() const { return mMakeEditable; }
+
+ protected:
+ void EnsureEditingSession();
+
+ // The doc shell that owns us. Weak ref, since it always outlives us.
+ nsIDocShell* mDocShell;
+
+ // Only present for the content root docShell. Session is owned here.
+ RefPtr<nsEditingSession> mEditingSession;
+
+ // If this frame is editable, store HTML editor here. It's owned here.
+ RefPtr<mozilla::HTMLEditor> mHTMLEditor;
+
+ // Backup for the corresponding HTMLDocument's editing state while
+ // the editor is detached.
+ mozilla::dom::Document::EditingState mDetachedEditingState;
+
+ // Indicates whether to make an editor after a url load.
+ bool mMakeEditable;
+
+ // Denotes if the editor is detached from its window. The editor is detached
+ // while it's stored in the session history bfcache.
+ bool mIsDetached;
+
+ // Backup for mMakeEditable while the editor is detached.
+ bool mDetachedMakeEditable;
+};
+
+#endif // nsDocShellEditorData_h__
diff --git a/docshell/base/nsDocShellEnumerator.cpp b/docshell/base/nsDocShellEnumerator.cpp
new file mode 100644
index 0000000000..5ad0ad35e6
--- /dev/null
+++ b/docshell/base/nsDocShellEnumerator.cpp
@@ -0,0 +1,85 @@
+/* -*- 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 "nsDocShellEnumerator.h"
+
+#include "nsDocShell.h"
+
+using namespace mozilla;
+
+nsDocShellEnumerator::nsDocShellEnumerator(
+ nsDocShellEnumerator::EnumerationDirection aDirection,
+ int32_t aDocShellType, nsDocShell& aRootItem)
+ : mRootItem(&aRootItem),
+ mDocShellType(aDocShellType),
+ mDirection(aDirection) {}
+
+nsresult nsDocShellEnumerator::BuildDocShellArray(
+ nsTArray<RefPtr<nsIDocShell>>& aItemArray) {
+ MOZ_ASSERT(mRootItem);
+
+ aItemArray.Clear();
+
+ if (mDirection == EnumerationDirection::Forwards) {
+ return BuildArrayRecursiveForwards(mRootItem, aItemArray);
+ }
+ MOZ_ASSERT(mDirection == EnumerationDirection::Backwards);
+ return BuildArrayRecursiveBackwards(mRootItem, aItemArray);
+}
+
+nsresult nsDocShellEnumerator::BuildArrayRecursiveForwards(
+ nsDocShell* aItem, nsTArray<RefPtr<nsIDocShell>>& aItemArray) {
+ nsresult rv;
+
+ // add this item to the array
+ if (mDocShellType == nsIDocShellTreeItem::typeAll ||
+ aItem->ItemType() == mDocShellType) {
+ if (!aItemArray.AppendElement(aItem, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ int32_t numChildren = aItem->ChildCount();
+
+ for (int32_t i = 0; i < numChildren; ++i) {
+ RefPtr<nsDocShell> curChild = aItem->GetInProcessChildAt(i);
+ MOZ_ASSERT(curChild);
+
+ rv = BuildArrayRecursiveForwards(curChild, aItemArray);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsDocShellEnumerator::BuildArrayRecursiveBackwards(
+ nsDocShell* aItem, nsTArray<RefPtr<nsIDocShell>>& aItemArray) {
+ nsresult rv;
+
+ uint32_t numChildren = aItem->ChildCount();
+
+ for (int32_t i = numChildren - 1; i >= 0; --i) {
+ RefPtr<nsDocShell> curChild = aItem->GetInProcessChildAt(i);
+ MOZ_ASSERT(curChild);
+
+ rv = BuildArrayRecursiveBackwards(curChild, aItemArray);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ // add this item to the array
+ if (mDocShellType == nsIDocShellTreeItem::typeAll ||
+ aItem->ItemType() == mDocShellType) {
+ if (!aItemArray.AppendElement(aItem, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ return NS_OK;
+}
diff --git a/docshell/base/nsDocShellEnumerator.h b/docshell/base/nsDocShellEnumerator.h
new file mode 100644
index 0000000000..668ddee7e9
--- /dev/null
+++ b/docshell/base/nsDocShellEnumerator.h
@@ -0,0 +1,39 @@
+/* -*- 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 nsDocShellEnumerator_h___
+#define nsDocShellEnumerator_h___
+
+#include "nsTArray.h"
+
+class nsDocShell;
+class nsIDocShell;
+
+class MOZ_STACK_CLASS nsDocShellEnumerator {
+ public:
+ enum class EnumerationDirection : uint8_t { Forwards, Backwards };
+
+ nsDocShellEnumerator(EnumerationDirection aDirection, int32_t aDocShellType,
+ nsDocShell& aRootItem);
+
+ public:
+ nsresult BuildDocShellArray(nsTArray<RefPtr<nsIDocShell>>& aItemArray);
+
+ private:
+ nsresult BuildArrayRecursiveForwards(
+ nsDocShell* aItem, nsTArray<RefPtr<nsIDocShell>>& aItemArray);
+ nsresult BuildArrayRecursiveBackwards(
+ nsDocShell* aItem, nsTArray<RefPtr<nsIDocShell>>& aItemArray);
+
+ private:
+ const RefPtr<nsDocShell> mRootItem;
+
+ const int32_t mDocShellType; // only want shells of this type
+
+ const EnumerationDirection mDirection;
+};
+
+#endif // nsDocShellEnumerator_h___
diff --git a/docshell/base/nsDocShellLoadState.cpp b/docshell/base/nsDocShellLoadState.cpp
new file mode 100644
index 0000000000..587617e73d
--- /dev/null
+++ b/docshell/base/nsDocShellLoadState.cpp
@@ -0,0 +1,1325 @@
+/* -*- 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 "nsDocShellLoadState.h"
+#include "nsIDocShell.h"
+#include "nsDocShell.h"
+#include "nsIProtocolHandler.h"
+#include "nsISHEntry.h"
+#include "nsIURIFixup.h"
+#include "nsIWebNavigation.h"
+#include "nsIChannel.h"
+#include "nsIURLQueryStringStripper.h"
+#include "nsIXULRuntime.h"
+#include "nsNetUtil.h"
+#include "nsQueryObject.h"
+#include "ReferrerInfo.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Components.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/LoadURIOptionsBinding.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_fission.h"
+#include "mozilla/Telemetry.h"
+
+#include "mozilla/OriginAttributes.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/StaticPtr.h"
+
+#include "mozilla/dom/PContent.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+// Global reference to the URI fixup service.
+static mozilla::StaticRefPtr<nsIURIFixup> sURIFixup;
+
+nsDocShellLoadState::nsDocShellLoadState(nsIURI* aURI)
+ : nsDocShellLoadState(aURI, nsContentUtils::GenerateLoadIdentifier()) {}
+
+nsDocShellLoadState::nsDocShellLoadState(
+ const DocShellLoadStateInit& aLoadState, mozilla::ipc::IProtocol* aActor,
+ bool* aReadSuccess)
+ : mNotifiedBeforeUnloadListeners(false),
+ mLoadIdentifier(aLoadState.LoadIdentifier()) {
+ // If we return early, we failed to read in the data.
+ *aReadSuccess = false;
+ if (!aLoadState.URI()) {
+ MOZ_ASSERT_UNREACHABLE("Cannot create a LoadState with a null URI!");
+ return;
+ }
+
+ mResultPrincipalURI = aLoadState.ResultPrincipalURI();
+ mResultPrincipalURIIsSome = aLoadState.ResultPrincipalURIIsSome();
+ mKeepResultPrincipalURIIfSet = aLoadState.KeepResultPrincipalURIIfSet();
+ mLoadReplace = aLoadState.LoadReplace();
+ mInheritPrincipal = aLoadState.InheritPrincipal();
+ mPrincipalIsExplicit = aLoadState.PrincipalIsExplicit();
+ mForceAllowDataURI = aLoadState.ForceAllowDataURI();
+ mIsExemptFromHTTPSFirstMode = aLoadState.IsExemptFromHTTPSFirstMode();
+ mOriginalFrameSrc = aLoadState.OriginalFrameSrc();
+ mIsFormSubmission = aLoadState.IsFormSubmission();
+ mLoadType = aLoadState.LoadType();
+ mTarget = aLoadState.Target();
+ mTargetBrowsingContext = aLoadState.TargetBrowsingContext();
+ mLoadFlags = aLoadState.LoadFlags();
+ mInternalLoadFlags = aLoadState.InternalLoadFlags();
+ mFirstParty = aLoadState.FirstParty();
+ mHasValidUserGestureActivation = aLoadState.HasValidUserGestureActivation();
+ mAllowFocusMove = aLoadState.AllowFocusMove();
+ mTypeHint = aLoadState.TypeHint();
+ mFileName = aLoadState.FileName();
+ mIsFromProcessingFrameAttributes =
+ aLoadState.IsFromProcessingFrameAttributes();
+ mReferrerInfo = aLoadState.ReferrerInfo();
+ mURI = aLoadState.URI();
+ mOriginalURI = aLoadState.OriginalURI();
+ mSourceBrowsingContext = aLoadState.SourceBrowsingContext();
+ mBaseURI = aLoadState.BaseURI();
+ mTriggeringPrincipal = aLoadState.TriggeringPrincipal();
+ mPrincipalToInherit = aLoadState.PrincipalToInherit();
+ mPartitionedPrincipalToInherit = aLoadState.PartitionedPrincipalToInherit();
+ mTriggeringSandboxFlags = aLoadState.TriggeringSandboxFlags();
+ mTriggeringWindowId = aLoadState.TriggeringWindowId();
+ mTriggeringStorageAccess = aLoadState.TriggeringStorageAccess();
+ mTriggeringRemoteType = aLoadState.TriggeringRemoteType();
+ mWasSchemelessInput = aLoadState.WasSchemelessInput();
+ mCsp = aLoadState.Csp();
+ mOriginalURIString = aLoadState.OriginalURIString();
+ mCancelContentJSEpoch = aLoadState.CancelContentJSEpoch();
+ mPostDataStream = aLoadState.PostDataStream();
+ mHeadersStream = aLoadState.HeadersStream();
+ mSrcdocData = aLoadState.SrcdocData();
+ mChannelInitialized = aLoadState.ChannelInitialized();
+ mIsMetaRefresh = aLoadState.IsMetaRefresh();
+ if (aLoadState.loadingSessionHistoryInfo().isSome()) {
+ mLoadingSessionHistoryInfo = MakeUnique<LoadingSessionHistoryInfo>(
+ aLoadState.loadingSessionHistoryInfo().ref());
+ }
+ mUnstrippedURI = aLoadState.UnstrippedURI();
+ mRemoteTypeOverride = aLoadState.RemoteTypeOverride();
+
+ // We know this was created remotely, as we just received it over IPC.
+ mWasCreatedRemotely = true;
+
+ // If we're in the parent process, potentially validate against a LoadState
+ // which we sent to the source content process.
+ if (XRE_IsParentProcess()) {
+ mozilla::ipc::IToplevelProtocol* top = aActor->ToplevelProtocol();
+ if (!top ||
+ top->GetProtocolId() != mozilla::ipc::ProtocolId::PContentMsgStart ||
+ top->GetSide() != mozilla::ipc::ParentSide) {
+ aActor->FatalError("nsDocShellLoadState must be received over PContent");
+ return;
+ }
+ ContentParent* cp = static_cast<ContentParent*>(top);
+
+ // If this load was sent down to the content process as a navigation
+ // request, ensure it still matches the one we sent down.
+ if (RefPtr<nsDocShellLoadState> originalState =
+ cp->TakePendingLoadStateForId(mLoadIdentifier)) {
+ if (const char* mismatch = ValidateWithOriginalState(originalState)) {
+ aActor->FatalError(
+ nsPrintfCString(
+ "nsDocShellLoadState %s changed while in content process",
+ mismatch)
+ .get());
+ return;
+ }
+ } else if (mTriggeringRemoteType != cp->GetRemoteType()) {
+ // If we don't have a previous load to compare to, the content process
+ // must be the triggering process.
+ aActor->FatalError(
+ "nsDocShellLoadState with invalid triggering remote type");
+ return;
+ }
+ }
+
+ // We successfully read in the data - return a success value.
+ *aReadSuccess = true;
+}
+
+nsDocShellLoadState::nsDocShellLoadState(const nsDocShellLoadState& aOther)
+ : mReferrerInfo(aOther.mReferrerInfo),
+ mURI(aOther.mURI),
+ mOriginalURI(aOther.mOriginalURI),
+ mResultPrincipalURI(aOther.mResultPrincipalURI),
+ mResultPrincipalURIIsSome(aOther.mResultPrincipalURIIsSome),
+ mTriggeringPrincipal(aOther.mTriggeringPrincipal),
+ mTriggeringSandboxFlags(aOther.mTriggeringSandboxFlags),
+ mTriggeringWindowId(aOther.mTriggeringWindowId),
+ mTriggeringStorageAccess(aOther.mTriggeringStorageAccess),
+ mCsp(aOther.mCsp),
+ mKeepResultPrincipalURIIfSet(aOther.mKeepResultPrincipalURIIfSet),
+ mLoadReplace(aOther.mLoadReplace),
+ mInheritPrincipal(aOther.mInheritPrincipal),
+ mPrincipalIsExplicit(aOther.mPrincipalIsExplicit),
+ mNotifiedBeforeUnloadListeners(aOther.mNotifiedBeforeUnloadListeners),
+ mPrincipalToInherit(aOther.mPrincipalToInherit),
+ mPartitionedPrincipalToInherit(aOther.mPartitionedPrincipalToInherit),
+ mForceAllowDataURI(aOther.mForceAllowDataURI),
+ mIsExemptFromHTTPSFirstMode(aOther.mIsExemptFromHTTPSFirstMode),
+ mOriginalFrameSrc(aOther.mOriginalFrameSrc),
+ mIsFormSubmission(aOther.mIsFormSubmission),
+ mLoadType(aOther.mLoadType),
+ mSHEntry(aOther.mSHEntry),
+ mTarget(aOther.mTarget),
+ mTargetBrowsingContext(aOther.mTargetBrowsingContext),
+ mPostDataStream(aOther.mPostDataStream),
+ mHeadersStream(aOther.mHeadersStream),
+ mSrcdocData(aOther.mSrcdocData),
+ mSourceBrowsingContext(aOther.mSourceBrowsingContext),
+ mBaseURI(aOther.mBaseURI),
+ mLoadFlags(aOther.mLoadFlags),
+ mInternalLoadFlags(aOther.mInternalLoadFlags),
+ mFirstParty(aOther.mFirstParty),
+ mHasValidUserGestureActivation(aOther.mHasValidUserGestureActivation),
+ mAllowFocusMove(aOther.mAllowFocusMove),
+ mTypeHint(aOther.mTypeHint),
+ mFileName(aOther.mFileName),
+ mIsFromProcessingFrameAttributes(aOther.mIsFromProcessingFrameAttributes),
+ mPendingRedirectedChannel(aOther.mPendingRedirectedChannel),
+ mOriginalURIString(aOther.mOriginalURIString),
+ mCancelContentJSEpoch(aOther.mCancelContentJSEpoch),
+ mLoadIdentifier(aOther.mLoadIdentifier),
+ mChannelInitialized(aOther.mChannelInitialized),
+ mIsMetaRefresh(aOther.mIsMetaRefresh),
+ mWasCreatedRemotely(aOther.mWasCreatedRemotely),
+ mUnstrippedURI(aOther.mUnstrippedURI),
+ mRemoteTypeOverride(aOther.mRemoteTypeOverride),
+ mTriggeringRemoteType(aOther.mTriggeringRemoteType),
+ mWasSchemelessInput(aOther.mWasSchemelessInput) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ XRE_IsParentProcess(),
+ "Cloning a nsDocShellLoadState with the same load identifier is only "
+ "allowed in the parent process, as it could break triggering remote type "
+ "tracking in content.");
+ if (aOther.mLoadingSessionHistoryInfo) {
+ mLoadingSessionHistoryInfo = MakeUnique<LoadingSessionHistoryInfo>(
+ *aOther.mLoadingSessionHistoryInfo);
+ }
+}
+
+nsDocShellLoadState::nsDocShellLoadState(nsIURI* aURI, uint64_t aLoadIdentifier)
+ : mURI(aURI),
+ mResultPrincipalURIIsSome(false),
+ mTriggeringSandboxFlags(0),
+ mTriggeringWindowId(0),
+ mTriggeringStorageAccess(false),
+ mKeepResultPrincipalURIIfSet(false),
+ mLoadReplace(false),
+ mInheritPrincipal(false),
+ mPrincipalIsExplicit(false),
+ mNotifiedBeforeUnloadListeners(false),
+ mForceAllowDataURI(false),
+ mIsExemptFromHTTPSFirstMode(false),
+ mOriginalFrameSrc(false),
+ mIsFormSubmission(false),
+ mLoadType(LOAD_NORMAL),
+ mSrcdocData(VoidString()),
+ mLoadFlags(0),
+ mInternalLoadFlags(0),
+ mFirstParty(false),
+ mHasValidUserGestureActivation(false),
+ mAllowFocusMove(false),
+ mTypeHint(VoidCString()),
+ mFileName(VoidString()),
+ mIsFromProcessingFrameAttributes(false),
+ mLoadIdentifier(aLoadIdentifier),
+ mChannelInitialized(false),
+ mIsMetaRefresh(false),
+ mWasCreatedRemotely(false),
+ mTriggeringRemoteType(XRE_IsContentProcess()
+ ? ContentChild::GetSingleton()->GetRemoteType()
+ : NOT_REMOTE_TYPE),
+ mWasSchemelessInput(false) {
+ MOZ_ASSERT(aURI, "Cannot create a LoadState with a null URI!");
+}
+
+nsDocShellLoadState::~nsDocShellLoadState() {
+ if (mWasCreatedRemotely && XRE_IsContentProcess()) {
+ ContentChild::GetSingleton()->SendCleanupPendingLoadState(mLoadIdentifier);
+ }
+}
+
+nsresult nsDocShellLoadState::CreateFromPendingChannel(
+ nsIChannel* aPendingChannel, uint64_t aLoadIdentifier,
+ uint64_t aRegistrarId, nsDocShellLoadState** aResult) {
+ // Create the nsDocShellLoadState object with default state pulled from the
+ // passed-in channel.
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = aPendingChannel->GetURI(getter_AddRefs(uri));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ RefPtr<nsDocShellLoadState> loadState =
+ new nsDocShellLoadState(uri, aLoadIdentifier);
+ loadState->mPendingRedirectedChannel = aPendingChannel;
+ loadState->mChannelRegistrarId = aRegistrarId;
+
+ // Pull relevant state from the channel, and store it on the
+ // nsDocShellLoadState.
+ nsCOMPtr<nsIURI> originalUri;
+ rv = aPendingChannel->GetOriginalURI(getter_AddRefs(originalUri));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ loadState->SetOriginalURI(originalUri);
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aPendingChannel->LoadInfo();
+ loadState->SetTriggeringPrincipal(loadInfo->TriggeringPrincipal());
+
+ // Return the newly created loadState.
+ loadState.forget(aResult);
+ return NS_OK;
+}
+
+static uint32_t WebNavigationFlagsToFixupFlags(nsIURI* aURI,
+ const nsACString& aURIString,
+ uint32_t aNavigationFlags) {
+ if (aURI) {
+ aNavigationFlags &= ~nsIWebNavigation::LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
+ }
+ uint32_t fixupFlags = nsIURIFixup::FIXUP_FLAG_NONE;
+ if (aNavigationFlags & nsIWebNavigation::LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP) {
+ fixupFlags |= nsIURIFixup::FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
+ }
+ if (aNavigationFlags & nsIWebNavigation::LOAD_FLAGS_FIXUP_SCHEME_TYPOS) {
+ fixupFlags |= nsIURIFixup::FIXUP_FLAG_FIX_SCHEME_TYPOS;
+ }
+ return fixupFlags;
+};
+
+nsresult nsDocShellLoadState::CreateFromLoadURIOptions(
+ BrowsingContext* aBrowsingContext, const nsAString& aURI,
+ const LoadURIOptions& aLoadURIOptions, nsDocShellLoadState** aResult) {
+ uint32_t loadFlags = aLoadURIOptions.mLoadFlags;
+
+ NS_ASSERTION(
+ (loadFlags & nsDocShell::INTERNAL_LOAD_FLAGS_LOADURI_SETUP_FLAGS) == 0,
+ "Unexpected flags");
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_OK;
+
+ NS_ConvertUTF16toUTF8 uriString(aURI);
+ // Cleanup the empty spaces that might be on each end.
+ uriString.Trim(" ");
+ // Eliminate embedded newlines, which single-line text fields now allow:
+ uriString.StripCRLF();
+ NS_ENSURE_TRUE(!uriString.IsEmpty(), NS_ERROR_FAILURE);
+
+ // Just create a URI and see what happens...
+ rv = NS_NewURI(getter_AddRefs(uri), uriString);
+ bool fixup = true;
+ if (NS_SUCCEEDED(rv) && uri &&
+ (uri->SchemeIs("about") || uri->SchemeIs("chrome"))) {
+ // Avoid third party fixup as a performance optimization.
+ loadFlags &= ~nsIWebNavigation::LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
+ fixup = false;
+ } else if (!sURIFixup && !XRE_IsContentProcess()) {
+ nsCOMPtr<nsIURIFixup> uriFixup = components::URIFixup::Service();
+ if (uriFixup) {
+ sURIFixup = uriFixup;
+ ClearOnShutdown(&sURIFixup);
+ } else {
+ fixup = false;
+ }
+ }
+
+ nsAutoString searchProvider, keyword;
+ RefPtr<nsIInputStream> fixupStream;
+ if (fixup) {
+ uint32_t fixupFlags =
+ WebNavigationFlagsToFixupFlags(uri, uriString, loadFlags);
+
+ // If we don't allow keyword lookups for this URL string, make sure to
+ // update loadFlags to indicate this as well.
+ if (!(fixupFlags & nsIURIFixup::FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP)) {
+ loadFlags &= ~nsIWebNavigation::LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
+ }
+ // Ensure URIFixup will use the right search engine in Private Browsing.
+ if (aBrowsingContext->UsePrivateBrowsing()) {
+ fixupFlags |= nsIURIFixup::FIXUP_FLAG_PRIVATE_CONTEXT;
+ }
+
+ if (!XRE_IsContentProcess()) {
+ nsCOMPtr<nsIURIFixupInfo> fixupInfo;
+ sURIFixup->GetFixupURIInfo(uriString, fixupFlags,
+ getter_AddRefs(fixupInfo));
+ if (fixupInfo) {
+ // We could fix the uri, clear NS_ERROR_MALFORMED_URI.
+ rv = NS_OK;
+ fixupInfo->GetPreferredURI(getter_AddRefs(uri));
+ fixupInfo->SetConsumer(aBrowsingContext);
+ fixupInfo->GetKeywordProviderName(searchProvider);
+ fixupInfo->GetKeywordAsSent(keyword);
+ // GetFixupURIInfo only returns a post data stream if it succeeded
+ // and changed the URI, in which case we should override the
+ // passed-in post data by passing this as an override arg to
+ // our internal method.
+ fixupInfo->GetPostData(getter_AddRefs(fixupStream));
+
+ if (fixupInfo &&
+ loadFlags & nsIWebNavigation::LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP) {
+ nsCOMPtr<nsIObserverService> serv = services::GetObserverService();
+ if (serv) {
+ serv->NotifyObservers(fixupInfo, "keyword-uri-fixup",
+ PromiseFlatString(aURI).get());
+ }
+ }
+ nsDocShell::MaybeNotifyKeywordSearchLoading(searchProvider, keyword);
+ }
+ }
+ }
+
+ if (rv == NS_ERROR_MALFORMED_URI) {
+ MOZ_ASSERT(!uri);
+ return rv;
+ }
+
+ if (NS_FAILED(rv) || !uri) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<nsDocShellLoadState> loadState;
+ rv = CreateFromLoadURIOptions(
+ aBrowsingContext, uri, aLoadURIOptions, loadFlags,
+ fixupStream ? fixupStream : aLoadURIOptions.mPostData,
+ getter_AddRefs(loadState));
+ NS_ENSURE_SUCCESS(rv, rv);
+ loadState->SetOriginalURIString(uriString);
+ loadState.forget(aResult);
+ return NS_OK;
+}
+
+nsresult nsDocShellLoadState::CreateFromLoadURIOptions(
+ BrowsingContext* aBrowsingContext, nsIURI* aURI,
+ const LoadURIOptions& aLoadURIOptions, nsDocShellLoadState** aResult) {
+ return CreateFromLoadURIOptions(aBrowsingContext, aURI, aLoadURIOptions,
+ aLoadURIOptions.mLoadFlags,
+ aLoadURIOptions.mPostData, aResult);
+}
+
+nsresult nsDocShellLoadState::CreateFromLoadURIOptions(
+ BrowsingContext* aBrowsingContext, nsIURI* aURI,
+ const LoadURIOptions& aLoadURIOptions, uint32_t aLoadFlagsOverride,
+ nsIInputStream* aPostDataOverride, nsDocShellLoadState** aResult) {
+ nsresult rv = NS_OK;
+ uint32_t loadFlags = aLoadFlagsOverride;
+ RefPtr<nsIInputStream> postData = aPostDataOverride;
+ uint64_t available;
+ if (postData) {
+ rv = postData->Available(&available);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (available == 0) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ if (aLoadURIOptions.mHeaders) {
+ rv = aLoadURIOptions.mHeaders->Available(&available);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (available == 0) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ bool forceAllowDataURI =
+ loadFlags & nsIWebNavigation::LOAD_FLAGS_FORCE_ALLOW_DATA_URI;
+
+ // Don't pass certain flags that aren't needed and end up confusing
+ // ConvertLoadTypeToDocShellInfoLoadType. We do need to ensure that they are
+ // passed to LoadURI though, since it uses them.
+ uint32_t extraFlags = (loadFlags & EXTRA_LOAD_FLAGS);
+ loadFlags &= ~EXTRA_LOAD_FLAGS;
+
+ RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(aURI);
+ loadState->SetReferrerInfo(aLoadURIOptions.mReferrerInfo);
+
+ loadState->SetLoadType(MAKE_LOAD_TYPE(LOAD_NORMAL, loadFlags));
+
+ loadState->SetLoadFlags(extraFlags);
+ loadState->SetFirstParty(true);
+ loadState->SetHasValidUserGestureActivation(
+ aLoadURIOptions.mHasValidUserGestureActivation);
+ loadState->SetTriggeringSandboxFlags(aLoadURIOptions.mTriggeringSandboxFlags);
+ loadState->SetTriggeringWindowId(aLoadURIOptions.mTriggeringWindowId);
+ loadState->SetTriggeringStorageAccess(
+ aLoadURIOptions.mTriggeringStorageAccess);
+ loadState->SetPostDataStream(postData);
+ loadState->SetHeadersStream(aLoadURIOptions.mHeaders);
+ loadState->SetBaseURI(aLoadURIOptions.mBaseURI);
+ loadState->SetTriggeringPrincipal(aLoadURIOptions.mTriggeringPrincipal);
+ loadState->SetCsp(aLoadURIOptions.mCsp);
+ loadState->SetForceAllowDataURI(forceAllowDataURI);
+ if (aLoadURIOptions.mCancelContentJSEpoch) {
+ loadState->SetCancelContentJSEpoch(aLoadURIOptions.mCancelContentJSEpoch);
+ }
+
+ if (aLoadURIOptions.mTriggeringRemoteType.WasPassed()) {
+ if (XRE_IsParentProcess()) {
+ loadState->SetTriggeringRemoteType(
+ aLoadURIOptions.mTriggeringRemoteType.Value());
+ } else if (ContentChild::GetSingleton()->GetRemoteType() !=
+ aLoadURIOptions.mTriggeringRemoteType.Value()) {
+ NS_WARNING("Invalid TriggeringRemoteType from LoadURIOptions in content");
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ if (aLoadURIOptions.mRemoteTypeOverride.WasPassed()) {
+ loadState->SetRemoteTypeOverride(
+ aLoadURIOptions.mRemoteTypeOverride.Value());
+ }
+
+ loadState->SetWasSchemelessInput(aLoadURIOptions.mWasSchemelessInput);
+
+ loadState.forget(aResult);
+ return NS_OK;
+}
+
+nsIReferrerInfo* nsDocShellLoadState::GetReferrerInfo() const {
+ return mReferrerInfo;
+}
+
+void nsDocShellLoadState::SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
+ mReferrerInfo = aReferrerInfo;
+}
+
+nsIURI* nsDocShellLoadState::URI() const { return mURI; }
+
+void nsDocShellLoadState::SetURI(nsIURI* aURI) { mURI = aURI; }
+
+nsIURI* nsDocShellLoadState::OriginalURI() const { return mOriginalURI; }
+
+void nsDocShellLoadState::SetOriginalURI(nsIURI* aOriginalURI) {
+ mOriginalURI = aOriginalURI;
+}
+
+nsIURI* nsDocShellLoadState::ResultPrincipalURI() const {
+ return mResultPrincipalURI;
+}
+
+void nsDocShellLoadState::SetResultPrincipalURI(nsIURI* aResultPrincipalURI) {
+ mResultPrincipalURI = aResultPrincipalURI;
+}
+
+bool nsDocShellLoadState::ResultPrincipalURIIsSome() const {
+ return mResultPrincipalURIIsSome;
+}
+
+void nsDocShellLoadState::SetResultPrincipalURIIsSome(bool aIsSome) {
+ mResultPrincipalURIIsSome = aIsSome;
+}
+
+bool nsDocShellLoadState::KeepResultPrincipalURIIfSet() const {
+ return mKeepResultPrincipalURIIfSet;
+}
+
+void nsDocShellLoadState::SetKeepResultPrincipalURIIfSet(bool aKeep) {
+ mKeepResultPrincipalURIIfSet = aKeep;
+}
+
+bool nsDocShellLoadState::LoadReplace() const { return mLoadReplace; }
+
+void nsDocShellLoadState::SetLoadReplace(bool aLoadReplace) {
+ mLoadReplace = aLoadReplace;
+}
+
+nsIPrincipal* nsDocShellLoadState::TriggeringPrincipal() const {
+ return mTriggeringPrincipal;
+}
+
+void nsDocShellLoadState::SetTriggeringPrincipal(
+ nsIPrincipal* aTriggeringPrincipal) {
+ mTriggeringPrincipal = aTriggeringPrincipal;
+}
+
+nsIPrincipal* nsDocShellLoadState::PrincipalToInherit() const {
+ return mPrincipalToInherit;
+}
+
+void nsDocShellLoadState::SetPrincipalToInherit(
+ nsIPrincipal* aPrincipalToInherit) {
+ mPrincipalToInherit = aPrincipalToInherit;
+}
+
+nsIPrincipal* nsDocShellLoadState::PartitionedPrincipalToInherit() const {
+ return mPartitionedPrincipalToInherit;
+}
+
+void nsDocShellLoadState::SetPartitionedPrincipalToInherit(
+ nsIPrincipal* aPartitionedPrincipalToInherit) {
+ mPartitionedPrincipalToInherit = aPartitionedPrincipalToInherit;
+}
+
+void nsDocShellLoadState::SetCsp(nsIContentSecurityPolicy* aCsp) {
+ mCsp = aCsp;
+}
+
+nsIContentSecurityPolicy* nsDocShellLoadState::Csp() const { return mCsp; }
+
+void nsDocShellLoadState::SetTriggeringSandboxFlags(uint32_t flags) {
+ mTriggeringSandboxFlags = flags;
+}
+
+uint32_t nsDocShellLoadState::TriggeringSandboxFlags() const {
+ return mTriggeringSandboxFlags;
+}
+
+void nsDocShellLoadState::SetTriggeringWindowId(uint64_t aTriggeringWindowId) {
+ mTriggeringWindowId = aTriggeringWindowId;
+}
+
+uint64_t nsDocShellLoadState::TriggeringWindowId() const {
+ return mTriggeringWindowId;
+}
+
+void nsDocShellLoadState::SetTriggeringStorageAccess(
+ bool aTriggeringStorageAccess) {
+ mTriggeringStorageAccess = aTriggeringStorageAccess;
+}
+
+bool nsDocShellLoadState::TriggeringStorageAccess() const {
+ return mTriggeringStorageAccess;
+}
+
+bool nsDocShellLoadState::InheritPrincipal() const { return mInheritPrincipal; }
+
+void nsDocShellLoadState::SetInheritPrincipal(bool aInheritPrincipal) {
+ mInheritPrincipal = aInheritPrincipal;
+}
+
+bool nsDocShellLoadState::PrincipalIsExplicit() const {
+ return mPrincipalIsExplicit;
+}
+
+void nsDocShellLoadState::SetPrincipalIsExplicit(bool aPrincipalIsExplicit) {
+ mPrincipalIsExplicit = aPrincipalIsExplicit;
+}
+
+bool nsDocShellLoadState::NotifiedBeforeUnloadListeners() const {
+ return mNotifiedBeforeUnloadListeners;
+}
+
+void nsDocShellLoadState::SetNotifiedBeforeUnloadListeners(
+ bool aNotifiedBeforeUnloadListeners) {
+ mNotifiedBeforeUnloadListeners = aNotifiedBeforeUnloadListeners;
+}
+
+bool nsDocShellLoadState::ForceAllowDataURI() const {
+ return mForceAllowDataURI;
+}
+
+void nsDocShellLoadState::SetForceAllowDataURI(bool aForceAllowDataURI) {
+ mForceAllowDataURI = aForceAllowDataURI;
+}
+
+bool nsDocShellLoadState::IsExemptFromHTTPSFirstMode() const {
+ return mIsExemptFromHTTPSFirstMode;
+}
+
+void nsDocShellLoadState::SetIsExemptFromHTTPSFirstMode(
+ bool aIsExemptFromHTTPSFirstMode) {
+ mIsExemptFromHTTPSFirstMode = aIsExemptFromHTTPSFirstMode;
+}
+
+bool nsDocShellLoadState::OriginalFrameSrc() const { return mOriginalFrameSrc; }
+
+void nsDocShellLoadState::SetOriginalFrameSrc(bool aOriginalFrameSrc) {
+ mOriginalFrameSrc = aOriginalFrameSrc;
+}
+
+bool nsDocShellLoadState::IsFormSubmission() const { return mIsFormSubmission; }
+
+void nsDocShellLoadState::SetIsFormSubmission(bool aIsFormSubmission) {
+ mIsFormSubmission = aIsFormSubmission;
+}
+
+uint32_t nsDocShellLoadState::LoadType() const { return mLoadType; }
+
+void nsDocShellLoadState::SetLoadType(uint32_t aLoadType) {
+ mLoadType = aLoadType;
+}
+
+nsISHEntry* nsDocShellLoadState::SHEntry() const { return mSHEntry; }
+
+void nsDocShellLoadState::SetSHEntry(nsISHEntry* aSHEntry) {
+ mSHEntry = aSHEntry;
+ nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(aSHEntry);
+ if (she) {
+ mLoadingSessionHistoryInfo = MakeUnique<LoadingSessionHistoryInfo>(she);
+ } else {
+ mLoadingSessionHistoryInfo = nullptr;
+ }
+}
+
+void nsDocShellLoadState::SetLoadingSessionHistoryInfo(
+ const mozilla::dom::LoadingSessionHistoryInfo& aLoadingInfo) {
+ SetLoadingSessionHistoryInfo(
+ MakeUnique<mozilla::dom::LoadingSessionHistoryInfo>(aLoadingInfo));
+}
+
+void nsDocShellLoadState::SetLoadingSessionHistoryInfo(
+ mozilla::UniquePtr<mozilla::dom::LoadingSessionHistoryInfo> aLoadingInfo) {
+ mLoadingSessionHistoryInfo = std::move(aLoadingInfo);
+}
+
+const mozilla::dom::LoadingSessionHistoryInfo*
+nsDocShellLoadState::GetLoadingSessionHistoryInfo() const {
+ return mLoadingSessionHistoryInfo.get();
+}
+
+void nsDocShellLoadState::SetLoadIsFromSessionHistory(
+ int32_t aOffset, bool aLoadingCurrentEntry) {
+ if (mLoadingSessionHistoryInfo) {
+ mLoadingSessionHistoryInfo->mLoadIsFromSessionHistory = true;
+ mLoadingSessionHistoryInfo->mOffset = aOffset;
+ mLoadingSessionHistoryInfo->mLoadingCurrentEntry = aLoadingCurrentEntry;
+ }
+}
+
+void nsDocShellLoadState::ClearLoadIsFromSessionHistory() {
+ if (mLoadingSessionHistoryInfo) {
+ mLoadingSessionHistoryInfo->mLoadIsFromSessionHistory = false;
+ }
+ mSHEntry = nullptr;
+}
+
+bool nsDocShellLoadState::LoadIsFromSessionHistory() const {
+ return mLoadingSessionHistoryInfo
+ ? mLoadingSessionHistoryInfo->mLoadIsFromSessionHistory
+ : !!mSHEntry;
+}
+
+void nsDocShellLoadState::MaybeStripTrackerQueryStrings(
+ BrowsingContext* aContext) {
+ MOZ_ASSERT(aContext);
+
+ // Return early if the triggering principal doesn't exist. This could happen
+ // when loading a URL by using a browsing context in the Browser Toolbox.
+ if (!TriggeringPrincipal()) {
+ return;
+ }
+
+ // We don't need to strip for sub frames because the query string has been
+ // stripped in the top-level content. Also, we don't apply stripping if it
+ // is triggered by addons.
+ //
+ // Note that we don't need to do the stripping if the channel has been
+ // initialized. This means that this has been loaded speculatively in the
+ // parent process before and the stripping was happening by then.
+ if (GetChannelInitialized() || !aContext->IsTopContent() ||
+ BasePrincipal::Cast(TriggeringPrincipal())->AddonPolicy()) {
+ return;
+ }
+
+ // We don't strip the URI if it's the same-site navigation. Note that we will
+ // consider the system principal triggered load as third-party in case the
+ // user copies and pastes a URL which has tracking query parameters or an
+ // loading from external applications, such as clicking a link in an email
+ // client.
+ bool isThirdPartyURI = false;
+ if (!TriggeringPrincipal()->IsSystemPrincipal() &&
+ (NS_FAILED(
+ TriggeringPrincipal()->IsThirdPartyURI(URI(), &isThirdPartyURI)) ||
+ !isThirdPartyURI)) {
+ return;
+ }
+
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_QUERY_STRIPPING_COUNT::Navigation);
+
+ nsCOMPtr<nsIURI> strippedURI;
+
+ nsresult rv;
+ nsCOMPtr<nsIURLQueryStringStripper> queryStripper =
+ components::URLQueryStringStripper::Service(&rv);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ uint32_t numStripped;
+
+ queryStripper->Strip(URI(), aContext->UsePrivateBrowsing(),
+ getter_AddRefs(strippedURI), &numStripped);
+ if (numStripped) {
+ if (!mUnstrippedURI) {
+ mUnstrippedURI = URI();
+ }
+ SetURI(strippedURI);
+
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_QUERY_STRIPPING_COUNT::StripForNavigation);
+ Telemetry::Accumulate(Telemetry::QUERY_STRIPPING_PARAM_COUNT, numStripped);
+ }
+
+#ifdef DEBUG
+ // Make sure that unstripped URI is the same as URI() but only the query
+ // string could be different.
+ if (mUnstrippedURI) {
+ nsCOMPtr<nsIURI> uri;
+ Unused << queryStripper->Strip(mUnstrippedURI,
+ aContext->UsePrivateBrowsing(),
+ getter_AddRefs(uri), &numStripped);
+ bool equals = false;
+ Unused << URI()->Equals(uri, &equals);
+ MOZ_ASSERT(equals);
+ }
+#endif
+}
+
+const nsString& nsDocShellLoadState::Target() const { return mTarget; }
+
+void nsDocShellLoadState::SetTarget(const nsAString& aTarget) {
+ mTarget = aTarget;
+}
+
+nsIInputStream* nsDocShellLoadState::PostDataStream() const {
+ return mPostDataStream;
+}
+
+void nsDocShellLoadState::SetPostDataStream(nsIInputStream* aStream) {
+ mPostDataStream = aStream;
+}
+
+nsIInputStream* nsDocShellLoadState::HeadersStream() const {
+ return mHeadersStream;
+}
+
+void nsDocShellLoadState::SetHeadersStream(nsIInputStream* aHeadersStream) {
+ mHeadersStream = aHeadersStream;
+}
+
+const nsString& nsDocShellLoadState::SrcdocData() const { return mSrcdocData; }
+
+void nsDocShellLoadState::SetSrcdocData(const nsAString& aSrcdocData) {
+ mSrcdocData = aSrcdocData;
+}
+
+void nsDocShellLoadState::SetSourceBrowsingContext(
+ BrowsingContext* aSourceBrowsingContext) {
+ mSourceBrowsingContext = aSourceBrowsingContext;
+}
+
+void nsDocShellLoadState::SetTargetBrowsingContext(
+ BrowsingContext* aTargetBrowsingContext) {
+ mTargetBrowsingContext = aTargetBrowsingContext;
+}
+
+nsIURI* nsDocShellLoadState::BaseURI() const { return mBaseURI; }
+
+void nsDocShellLoadState::SetBaseURI(nsIURI* aBaseURI) { mBaseURI = aBaseURI; }
+
+void nsDocShellLoadState::GetMaybeResultPrincipalURI(
+ mozilla::Maybe<nsCOMPtr<nsIURI>>& aRPURI) const {
+ bool isSome = ResultPrincipalURIIsSome();
+ aRPURI.reset();
+
+ if (!isSome) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri = ResultPrincipalURI();
+ aRPURI.emplace(std::move(uri));
+}
+
+void nsDocShellLoadState::SetMaybeResultPrincipalURI(
+ mozilla::Maybe<nsCOMPtr<nsIURI>> const& aRPURI) {
+ SetResultPrincipalURI(aRPURI.refOr(nullptr));
+ SetResultPrincipalURIIsSome(aRPURI.isSome());
+}
+
+uint32_t nsDocShellLoadState::LoadFlags() const { return mLoadFlags; }
+
+void nsDocShellLoadState::SetLoadFlags(uint32_t aLoadFlags) {
+ mLoadFlags = aLoadFlags;
+}
+
+void nsDocShellLoadState::SetLoadFlag(uint32_t aFlag) { mLoadFlags |= aFlag; }
+
+void nsDocShellLoadState::UnsetLoadFlag(uint32_t aFlag) {
+ mLoadFlags &= ~aFlag;
+}
+
+bool nsDocShellLoadState::HasLoadFlags(uint32_t aFlags) {
+ return (mLoadFlags & aFlags) == aFlags;
+}
+
+uint32_t nsDocShellLoadState::InternalLoadFlags() const {
+ return mInternalLoadFlags;
+}
+
+void nsDocShellLoadState::SetInternalLoadFlags(uint32_t aLoadFlags) {
+ mInternalLoadFlags = aLoadFlags;
+}
+
+void nsDocShellLoadState::SetInternalLoadFlag(uint32_t aFlag) {
+ mInternalLoadFlags |= aFlag;
+}
+
+void nsDocShellLoadState::UnsetInternalLoadFlag(uint32_t aFlag) {
+ mInternalLoadFlags &= ~aFlag;
+}
+
+bool nsDocShellLoadState::HasInternalLoadFlags(uint32_t aFlags) {
+ return (mInternalLoadFlags & aFlags) == aFlags;
+}
+
+bool nsDocShellLoadState::FirstParty() const { return mFirstParty; }
+
+void nsDocShellLoadState::SetFirstParty(bool aFirstParty) {
+ mFirstParty = aFirstParty;
+}
+
+bool nsDocShellLoadState::HasValidUserGestureActivation() const {
+ return mHasValidUserGestureActivation;
+}
+
+void nsDocShellLoadState::SetHasValidUserGestureActivation(
+ bool aHasValidUserGestureActivation) {
+ mHasValidUserGestureActivation = aHasValidUserGestureActivation;
+}
+
+const nsCString& nsDocShellLoadState::TypeHint() const { return mTypeHint; }
+
+void nsDocShellLoadState::SetTypeHint(const nsCString& aTypeHint) {
+ mTypeHint = aTypeHint;
+}
+
+const nsString& nsDocShellLoadState::FileName() const { return mFileName; }
+
+void nsDocShellLoadState::SetFileName(const nsAString& aFileName) {
+ MOZ_DIAGNOSTIC_ASSERT(aFileName.FindChar(char16_t(0)) == kNotFound,
+ "The filename should never contain null characters");
+ mFileName = aFileName;
+}
+
+const nsCString& nsDocShellLoadState::GetEffectiveTriggeringRemoteType() const {
+ // Consider non-errorpage loads from session history as being triggred by the
+ // parent process, as we'll validate them against the history entry.
+ //
+ // NOTE: Keep this check in-sync with the session-history validation check in
+ // `DocumentLoadListener::Open`!
+ if (LoadIsFromSessionHistory() && LoadType() != LOAD_ERROR_PAGE) {
+ return NOT_REMOTE_TYPE;
+ }
+ return mTriggeringRemoteType;
+}
+
+void nsDocShellLoadState::SetTriggeringRemoteType(
+ const nsACString& aTriggeringRemoteType) {
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess(), "only settable in parent");
+ mTriggeringRemoteType = aTriggeringRemoteType;
+}
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+void nsDocShellLoadState::AssertProcessCouldTriggerLoadIfSystem() {
+ // Early check to see if we're trying to start a file URI load with a system
+ // principal within a web content process.
+ // If this assertion fails, the load will fail later during
+ // nsContentSecurityManager checks, however this assertion should happen
+ // closer to whichever caller is triggering the system-principal load.
+ if (mozilla::SessionHistoryInParent() &&
+ TriggeringPrincipal()->IsSystemPrincipal() &&
+ mozilla::dom::IsWebRemoteType(GetEffectiveTriggeringRemoteType())) {
+ bool localFile = false;
+ if (NS_SUCCEEDED(NS_URIChainHasFlags(
+ URI(), nsIProtocolHandler::URI_IS_LOCAL_FILE, &localFile)) &&
+ localFile) {
+ NS_WARNING(nsPrintfCString("Unexpected system load of file URI (%s) from "
+ "web content process",
+ URI()->GetSpecOrDefault().get())
+ .get());
+ MOZ_CRASH("Unexpected system load of file URI from web content process");
+ }
+ }
+}
+#endif
+
+nsresult nsDocShellLoadState::SetupInheritingPrincipal(
+ BrowsingContext::Type aType,
+ const mozilla::OriginAttributes& aOriginAttributes) {
+ // We need a principalToInherit.
+ //
+ // If principalIsExplicit is not set there are 4 possibilities:
+ // (1) If the system principal or an expanded principal was passed
+ // in and we're a typeContent docshell, inherit the principal
+ // from the current document instead.
+ // (2) In all other cases when the principal passed in is not null,
+ // use that principal.
+ // (3) If the caller has allowed inheriting from the current document,
+ // or if we're being called from system code (eg chrome JS or pure
+ // C++) then inheritPrincipal should be true and InternalLoad will get
+ // a principal from the current document. If none of these things are
+ // true, then
+ // (4) we don't pass a principal into the channel, and a principal will be
+ // created later from the channel's internal data.
+ //
+ // If principalIsExplicit *is* set, there are 4 possibilities
+ // (1) If the system principal or an expanded principal was passed in
+ // and we're a typeContent docshell, return an error.
+ // (2) In all other cases when the principal passed in is not null,
+ // use that principal.
+ // (3) If the caller has allowed inheriting from the current document,
+ // then inheritPrincipal should be true and InternalLoad will get
+ // a principal from the current document. If none of these things are
+ // true, then
+ // (4) we dont' pass a principal into the channel, and a principal will be
+ // created later from the channel's internal data.
+ mPrincipalToInherit = mTriggeringPrincipal;
+ if (mPrincipalToInherit && aType != BrowsingContext::Type::Chrome) {
+ if (mPrincipalToInherit->IsSystemPrincipal()) {
+ if (mPrincipalIsExplicit) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+ mPrincipalToInherit = nullptr;
+ mInheritPrincipal = true;
+ } else if (nsContentUtils::IsExpandedPrincipal(mPrincipalToInherit)) {
+ if (mPrincipalIsExplicit) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+ // Don't inherit from the current page. Just do the safe thing
+ // and pretend that we were loaded by a nullprincipal.
+ //
+ // We didn't inherit OriginAttributes here as ExpandedPrincipal doesn't
+ // have origin attributes.
+ mPrincipalToInherit = NullPrincipal::Create(aOriginAttributes);
+ mInheritPrincipal = false;
+ }
+ }
+
+ if (!mPrincipalToInherit && !mInheritPrincipal && !mPrincipalIsExplicit) {
+ // See if there's system or chrome JS code running
+ mInheritPrincipal = nsContentUtils::LegacyIsCallerChromeOrNativeCode();
+ }
+
+ if (mLoadFlags & nsIWebNavigation::LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL) {
+ mInheritPrincipal = false;
+ // Create a new null principal URI based on our precursor principal.
+ nsCOMPtr<nsIURI> nullPrincipalURI =
+ NullPrincipal::CreateURI(mPrincipalToInherit);
+ // If mFirstParty is true and the pref 'privacy.firstparty.isolate' is
+ // enabled, we will set firstPartyDomain on the origin attributes.
+ OriginAttributes attrs(aOriginAttributes);
+ if (mFirstParty) {
+ attrs.SetFirstPartyDomain(true, nullPrincipalURI);
+ }
+ mPrincipalToInherit = NullPrincipal::Create(attrs, nullPrincipalURI);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsDocShellLoadState::SetupTriggeringPrincipal(
+ const mozilla::OriginAttributes& aOriginAttributes) {
+ // If the triggeringPrincipal is not set, we first try to create a principal
+ // from the referrer, since the referrer URI reflects the web origin that
+ // triggered the load. If there is no referrer URI, we fall back to using the
+ // SystemPrincipal. It's safe to assume that no provided triggeringPrincipal
+ // and no referrer simulate a load that was triggered by the system. It's
+ // important to note that this block of code needs to appear *after* the block
+ // where we munge the principalToInherit, because otherwise we would never
+ // enter code blocks checking if the principalToInherit is null and we will
+ // end up with a wrong inheritPrincipal flag.
+ if (!mTriggeringPrincipal) {
+ if (mReferrerInfo) {
+ nsCOMPtr<nsIURI> referrer = mReferrerInfo->GetOriginalReferrer();
+ mTriggeringPrincipal =
+ BasePrincipal::CreateContentPrincipal(referrer, aOriginAttributes);
+
+ if (!mTriggeringPrincipal) {
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ mTriggeringPrincipal = nsContentUtils::GetSystemPrincipal();
+ }
+ }
+ return NS_OK;
+}
+
+void nsDocShellLoadState::CalculateLoadURIFlags() {
+ if (mInheritPrincipal) {
+ MOZ_ASSERT(
+ !mPrincipalToInherit || !mPrincipalToInherit->IsSystemPrincipal(),
+ "Should not inherit SystemPrincipal");
+ mInternalLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL;
+ }
+
+ if (mReferrerInfo && !mReferrerInfo->GetSendReferrer()) {
+ mInternalLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER;
+ }
+ if (mLoadFlags & nsIWebNavigation::LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP) {
+ mInternalLoadFlags |=
+ nsDocShell::INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
+ }
+
+ if (mLoadFlags & nsIWebNavigation::LOAD_FLAGS_FIRST_LOAD) {
+ mInternalLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_FIRST_LOAD;
+ }
+
+ if (mLoadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_CLASSIFIER) {
+ mInternalLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_BYPASS_CLASSIFIER;
+ }
+
+ if (mLoadFlags & nsIWebNavigation::LOAD_FLAGS_FORCE_ALLOW_COOKIES) {
+ mInternalLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_FORCE_ALLOW_COOKIES;
+ }
+
+ if (mLoadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE) {
+ mInternalLoadFlags |=
+ nsDocShell::INTERNAL_LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE;
+ }
+
+ if (!mSrcdocData.IsVoid()) {
+ mInternalLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_IS_SRCDOC;
+ }
+
+ if (mForceAllowDataURI) {
+ mInternalLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_FORCE_ALLOW_DATA_URI;
+ }
+
+ if (mOriginalFrameSrc) {
+ mInternalLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_ORIGINAL_FRAME_SRC;
+ }
+}
+
+nsLoadFlags nsDocShellLoadState::CalculateChannelLoadFlags(
+ BrowsingContext* aBrowsingContext, Maybe<bool> aUriModified,
+ Maybe<bool> aIsEmbeddingBlockedError) {
+ MOZ_ASSERT(aBrowsingContext);
+
+ nsLoadFlags loadFlags = aBrowsingContext->GetDefaultLoadFlags();
+
+ if (FirstParty()) {
+ // tag first party URL loads
+ loadFlags |= nsIChannel::LOAD_INITIAL_DOCUMENT_URI;
+ }
+
+ const uint32_t loadType = LoadType();
+
+ // These values aren't available for loads initiated in the Parent process.
+ MOZ_ASSERT_IF(loadType == LOAD_HISTORY, aUriModified.isSome());
+ MOZ_ASSERT_IF(loadType == LOAD_ERROR_PAGE, aIsEmbeddingBlockedError.isSome());
+
+ if (loadType == LOAD_ERROR_PAGE) {
+ // Error pages are LOAD_BACKGROUND, unless it's an
+ // XFO / frame-ancestors error for which we want an error page to load
+ // but additionally want the onload() event to fire.
+ if (!*aIsEmbeddingBlockedError) {
+ loadFlags |= nsIChannel::LOAD_BACKGROUND;
+ }
+ }
+
+ // Mark the channel as being a document URI and allow content sniffing...
+ loadFlags |=
+ nsIChannel::LOAD_DOCUMENT_URI | nsIChannel::LOAD_CALL_CONTENT_SNIFFERS;
+
+ if (nsDocShell::SandboxFlagsImplyCookies(
+ aBrowsingContext->GetSandboxFlags())) {
+ loadFlags |= nsIRequest::LOAD_DOCUMENT_NEEDS_COOKIE;
+ }
+
+ // Load attributes depend on load type...
+ switch (loadType) {
+ case LOAD_HISTORY: {
+ // Only send VALIDATE_NEVER if mLSHE's URI was never changed via
+ // push/replaceState (bug 669671).
+ if (!*aUriModified) {
+ loadFlags |= nsIRequest::VALIDATE_NEVER;
+ }
+ break;
+ }
+
+ case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE:
+ case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE:
+ loadFlags |=
+ nsIRequest::LOAD_BYPASS_CACHE | nsIRequest::LOAD_FRESH_CONNECTION;
+ [[fallthrough]];
+
+ case LOAD_REFRESH:
+ loadFlags |= nsIRequest::VALIDATE_ALWAYS;
+ break;
+
+ case LOAD_NORMAL_BYPASS_CACHE:
+ case LOAD_NORMAL_BYPASS_PROXY:
+ case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE:
+ case LOAD_RELOAD_BYPASS_CACHE:
+ case LOAD_RELOAD_BYPASS_PROXY:
+ case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
+ case LOAD_REPLACE_BYPASS_CACHE:
+ loadFlags |=
+ nsIRequest::LOAD_BYPASS_CACHE | nsIRequest::LOAD_FRESH_CONNECTION;
+ break;
+
+ case LOAD_RELOAD_NORMAL:
+ if (!StaticPrefs::
+ browser_soft_reload_only_force_validate_top_level_document()) {
+ loadFlags |= nsIRequest::VALIDATE_ALWAYS;
+ break;
+ }
+ [[fallthrough]];
+ case LOAD_NORMAL:
+ case LOAD_LINK:
+ // Set cache checking flags
+ switch (StaticPrefs::browser_cache_check_doc_frequency()) {
+ case 0:
+ loadFlags |= nsIRequest::VALIDATE_ONCE_PER_SESSION;
+ break;
+ case 1:
+ loadFlags |= nsIRequest::VALIDATE_ALWAYS;
+ break;
+ case 2:
+ loadFlags |= nsIRequest::VALIDATE_NEVER;
+ break;
+ }
+ break;
+ }
+
+ if (HasInternalLoadFlags(nsDocShell::INTERNAL_LOAD_FLAGS_BYPASS_CLASSIFIER)) {
+ loadFlags |= nsIChannel::LOAD_BYPASS_URL_CLASSIFIER;
+ }
+
+ // If the user pressed shift-reload, then do not allow ServiceWorker
+ // interception to occur. See step 12.1 of the SW HandleFetch algorithm.
+ if (IsForceReloadType(loadType)) {
+ loadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
+ }
+
+ return loadFlags;
+}
+
+const char* nsDocShellLoadState::ValidateWithOriginalState(
+ nsDocShellLoadState* aOriginalState) {
+ MOZ_ASSERT(mLoadIdentifier == aOriginalState->mLoadIdentifier);
+
+ // Check that `aOriginalState` is sufficiently similar to this state that
+ // they're performing the same load.
+ auto uriEq = [](nsIURI* a, nsIURI* b) -> bool {
+ bool eq = false;
+ return a == b || (a && b && NS_SUCCEEDED(a->Equals(b, &eq)) && eq);
+ };
+ if (!uriEq(mURI, aOriginalState->mURI)) {
+ return "URI";
+ }
+ if (!uriEq(mUnstrippedURI, aOriginalState->mUnstrippedURI)) {
+ return "UnstrippedURI";
+ }
+ if (!uriEq(mOriginalURI, aOriginalState->mOriginalURI)) {
+ return "OriginalURI";
+ }
+ if (!uriEq(mBaseURI, aOriginalState->mBaseURI)) {
+ return "BaseURI";
+ }
+
+ if (!mTriggeringPrincipal->Equals(aOriginalState->mTriggeringPrincipal)) {
+ return "TriggeringPrincipal";
+ }
+ if (mTriggeringSandboxFlags != aOriginalState->mTriggeringSandboxFlags) {
+ return "TriggeringSandboxFlags";
+ }
+ if (mTriggeringRemoteType != aOriginalState->mTriggeringRemoteType) {
+ return "TriggeringRemoteType";
+ }
+
+ if (mOriginalURIString != aOriginalState->mOriginalURIString) {
+ return "OriginalURIString";
+ }
+
+ if (mRemoteTypeOverride != aOriginalState->mRemoteTypeOverride) {
+ return "RemoteTypeOverride";
+ }
+
+ if (mSourceBrowsingContext.ContextId() !=
+ aOriginalState->mSourceBrowsingContext.ContextId()) {
+ return "SourceBrowsingContext";
+ }
+
+ // FIXME: Consider calculating less information in the target process so that
+ // we can validate more properties more easily.
+ // FIXME: Identify what other flags will not change when sent through a
+ // content process.
+
+ return nullptr;
+}
+
+DocShellLoadStateInit nsDocShellLoadState::Serialize(
+ mozilla::ipc::IProtocol* aActor) {
+ MOZ_ASSERT(aActor);
+ DocShellLoadStateInit loadState;
+ loadState.ResultPrincipalURI() = mResultPrincipalURI;
+ loadState.ResultPrincipalURIIsSome() = mResultPrincipalURIIsSome;
+ loadState.KeepResultPrincipalURIIfSet() = mKeepResultPrincipalURIIfSet;
+ loadState.LoadReplace() = mLoadReplace;
+ loadState.InheritPrincipal() = mInheritPrincipal;
+ loadState.PrincipalIsExplicit() = mPrincipalIsExplicit;
+ loadState.ForceAllowDataURI() = mForceAllowDataURI;
+ loadState.IsExemptFromHTTPSFirstMode() = mIsExemptFromHTTPSFirstMode;
+ loadState.OriginalFrameSrc() = mOriginalFrameSrc;
+ loadState.IsFormSubmission() = mIsFormSubmission;
+ loadState.LoadType() = mLoadType;
+ loadState.Target() = mTarget;
+ loadState.TargetBrowsingContext() = mTargetBrowsingContext;
+ loadState.LoadFlags() = mLoadFlags;
+ loadState.InternalLoadFlags() = mInternalLoadFlags;
+ loadState.FirstParty() = mFirstParty;
+ loadState.HasValidUserGestureActivation() = mHasValidUserGestureActivation;
+ loadState.AllowFocusMove() = mAllowFocusMove;
+ loadState.TypeHint() = mTypeHint;
+ loadState.FileName() = mFileName;
+ loadState.IsFromProcessingFrameAttributes() =
+ mIsFromProcessingFrameAttributes;
+ loadState.URI() = mURI;
+ loadState.OriginalURI() = mOriginalURI;
+ loadState.SourceBrowsingContext() = mSourceBrowsingContext;
+ loadState.BaseURI() = mBaseURI;
+ loadState.TriggeringPrincipal() = mTriggeringPrincipal;
+ loadState.PrincipalToInherit() = mPrincipalToInherit;
+ loadState.PartitionedPrincipalToInherit() = mPartitionedPrincipalToInherit;
+ loadState.TriggeringSandboxFlags() = mTriggeringSandboxFlags;
+ loadState.TriggeringWindowId() = mTriggeringWindowId;
+ loadState.TriggeringStorageAccess() = mTriggeringStorageAccess;
+ loadState.TriggeringRemoteType() = mTriggeringRemoteType;
+ loadState.WasSchemelessInput() = mWasSchemelessInput;
+ loadState.Csp() = mCsp;
+ loadState.OriginalURIString() = mOriginalURIString;
+ loadState.CancelContentJSEpoch() = mCancelContentJSEpoch;
+ loadState.ReferrerInfo() = mReferrerInfo;
+ loadState.PostDataStream() = mPostDataStream;
+ loadState.HeadersStream() = mHeadersStream;
+ loadState.SrcdocData() = mSrcdocData;
+ loadState.ResultPrincipalURI() = mResultPrincipalURI;
+ loadState.LoadIdentifier() = mLoadIdentifier;
+ loadState.ChannelInitialized() = mChannelInitialized;
+ loadState.IsMetaRefresh() = mIsMetaRefresh;
+ if (mLoadingSessionHistoryInfo) {
+ loadState.loadingSessionHistoryInfo().emplace(*mLoadingSessionHistoryInfo);
+ }
+ loadState.UnstrippedURI() = mUnstrippedURI;
+ loadState.RemoteTypeOverride() = mRemoteTypeOverride;
+
+ if (XRE_IsParentProcess()) {
+ mozilla::ipc::IToplevelProtocol* top = aActor->ToplevelProtocol();
+ MOZ_RELEASE_ASSERT(top &&
+ top->GetProtocolId() ==
+ mozilla::ipc::ProtocolId::PContentMsgStart &&
+ top->GetSide() == mozilla::ipc::ParentSide,
+ "nsDocShellLoadState must be sent over PContent");
+ ContentParent* cp = static_cast<ContentParent*>(top);
+ cp->StorePendingLoadState(this);
+ }
+
+ return loadState;
+}
+
+nsIURI* nsDocShellLoadState::GetUnstrippedURI() const { return mUnstrippedURI; }
+
+void nsDocShellLoadState::SetUnstrippedURI(nsIURI* aUnstrippedURI) {
+ mUnstrippedURI = aUnstrippedURI;
+}
diff --git a/docshell/base/nsDocShellLoadState.h b/docshell/base/nsDocShellLoadState.h
new file mode 100644
index 0000000000..a34ca1b54b
--- /dev/null
+++ b/docshell/base/nsDocShellLoadState.h
@@ -0,0 +1,609 @@
+/* -*- 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 nsDocShellLoadState_h__
+#define nsDocShellLoadState_h__
+
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/SessionHistoryEntry.h"
+
+// Helper Classes
+#include "mozilla/Maybe.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsDocShellLoadTypes.h"
+#include "nsTArrayForwardDeclare.h"
+
+class nsIContentSecurityPolicy;
+class nsIInputStream;
+class nsISHEntry;
+class nsIURI;
+class nsIDocShell;
+class nsIChannel;
+class nsIReferrerInfo;
+namespace mozilla {
+class OriginAttributes;
+template <typename, class>
+class UniquePtr;
+namespace dom {
+class DocShellLoadStateInit;
+} // namespace dom
+} // namespace mozilla
+
+/**
+ * nsDocShellLoadState contains setup information used in a nsIDocShell::loadURI
+ * call.
+ */
+class nsDocShellLoadState final {
+ using BrowsingContext = mozilla::dom::BrowsingContext;
+ template <typename T>
+ using MaybeDiscarded = mozilla::dom::MaybeDiscarded<T>;
+
+ public:
+ NS_INLINE_DECL_REFCOUNTING(nsDocShellLoadState);
+
+ explicit nsDocShellLoadState(nsIURI* aURI);
+ explicit nsDocShellLoadState(
+ const mozilla::dom::DocShellLoadStateInit& aLoadState,
+ mozilla::ipc::IProtocol* aActor, bool* aReadSuccess);
+ explicit nsDocShellLoadState(const nsDocShellLoadState& aOther);
+ nsDocShellLoadState(nsIURI* aURI, uint64_t aLoadIdentifier);
+
+ static nsresult CreateFromPendingChannel(nsIChannel* aPendingChannel,
+ uint64_t aLoadIdentifier,
+ uint64_t aRegistarId,
+ nsDocShellLoadState** aResult);
+
+ static nsresult CreateFromLoadURIOptions(
+ BrowsingContext* aBrowsingContext, const nsAString& aURI,
+ const mozilla::dom::LoadURIOptions& aLoadURIOptions,
+ nsDocShellLoadState** aResult);
+ static nsresult CreateFromLoadURIOptions(
+ BrowsingContext* aBrowsingContext, nsIURI* aURI,
+ const mozilla::dom::LoadURIOptions& aLoadURIOptions,
+ nsDocShellLoadState** aResult);
+
+ // Getters and Setters
+
+ nsIReferrerInfo* GetReferrerInfo() const;
+
+ void SetReferrerInfo(nsIReferrerInfo* aReferrerInfo);
+
+ nsIURI* URI() const;
+
+ void SetURI(nsIURI* aURI);
+
+ nsIURI* OriginalURI() const;
+
+ void SetOriginalURI(nsIURI* aOriginalURI);
+
+ nsIURI* ResultPrincipalURI() const;
+
+ void SetResultPrincipalURI(nsIURI* aResultPrincipalURI);
+
+ bool ResultPrincipalURIIsSome() const;
+
+ void SetResultPrincipalURIIsSome(bool aIsSome);
+
+ bool KeepResultPrincipalURIIfSet() const;
+
+ void SetKeepResultPrincipalURIIfSet(bool aKeep);
+
+ nsIPrincipal* PrincipalToInherit() const;
+
+ void SetPrincipalToInherit(nsIPrincipal* aPrincipalToInherit);
+
+ nsIPrincipal* PartitionedPrincipalToInherit() const;
+
+ void SetPartitionedPrincipalToInherit(
+ nsIPrincipal* aPartitionedPrincipalToInherit);
+
+ bool LoadReplace() const;
+
+ void SetLoadReplace(bool aLoadReplace);
+
+ nsIPrincipal* TriggeringPrincipal() const;
+
+ void SetTriggeringPrincipal(nsIPrincipal* aTriggeringPrincipal);
+
+ uint32_t TriggeringSandboxFlags() const;
+
+ void SetTriggeringSandboxFlags(uint32_t aTriggeringSandboxFlags);
+
+ uint64_t TriggeringWindowId() const;
+
+ void SetTriggeringWindowId(uint64_t aTriggeringWindowId);
+
+ bool TriggeringStorageAccess() const;
+
+ void SetTriggeringStorageAccess(bool aTriggeringStorageAccess);
+
+ nsIContentSecurityPolicy* Csp() const;
+
+ void SetCsp(nsIContentSecurityPolicy* aCsp);
+
+ bool InheritPrincipal() const;
+
+ void SetInheritPrincipal(bool aInheritPrincipal);
+
+ bool PrincipalIsExplicit() const;
+
+ void SetPrincipalIsExplicit(bool aPrincipalIsExplicit);
+
+ // If true, "beforeunload" event listeners were notified by the creater of the
+ // LoadState and given the chance to abort the navigation, and should not be
+ // notified again.
+ bool NotifiedBeforeUnloadListeners() const;
+
+ void SetNotifiedBeforeUnloadListeners(bool aNotifiedBeforeUnloadListeners);
+
+ bool ForceAllowDataURI() const;
+
+ void SetForceAllowDataURI(bool aForceAllowDataURI);
+
+ bool IsExemptFromHTTPSFirstMode() const;
+
+ void SetIsExemptFromHTTPSFirstMode(bool aIsExemptFromHTTPSFirstMode);
+
+ bool OriginalFrameSrc() const;
+
+ void SetOriginalFrameSrc(bool aOriginalFrameSrc);
+
+ bool IsFormSubmission() const;
+
+ void SetIsFormSubmission(bool aIsFormSubmission);
+
+ uint32_t LoadType() const;
+
+ void SetLoadType(uint32_t aLoadType);
+
+ nsISHEntry* SHEntry() const;
+
+ void SetSHEntry(nsISHEntry* aSHEntry);
+
+ const mozilla::dom::LoadingSessionHistoryInfo* GetLoadingSessionHistoryInfo()
+ const;
+
+ // Copies aLoadingInfo and stores the copy in this nsDocShellLoadState.
+ void SetLoadingSessionHistoryInfo(
+ const mozilla::dom::LoadingSessionHistoryInfo& aLoadingInfo);
+
+ // Stores aLoadingInfo in this nsDocShellLoadState.
+ void SetLoadingSessionHistoryInfo(
+ mozilla::UniquePtr<mozilla::dom::LoadingSessionHistoryInfo> aLoadingInfo);
+
+ bool LoadIsFromSessionHistory() const;
+
+ const nsString& Target() const;
+
+ void SetTarget(const nsAString& aTarget);
+
+ nsIInputStream* PostDataStream() const;
+
+ void SetPostDataStream(nsIInputStream* aStream);
+
+ nsIInputStream* HeadersStream() const;
+
+ void SetHeadersStream(nsIInputStream* aHeadersStream);
+
+ bool IsSrcdocLoad() const;
+
+ const nsString& SrcdocData() const;
+
+ void SetSrcdocData(const nsAString& aSrcdocData);
+
+ const MaybeDiscarded<BrowsingContext>& SourceBrowsingContext() const {
+ return mSourceBrowsingContext;
+ }
+
+ void SetSourceBrowsingContext(BrowsingContext*);
+
+ void SetAllowFocusMove(bool aAllow) { mAllowFocusMove = aAllow; }
+
+ bool AllowFocusMove() const { return mAllowFocusMove; }
+
+ const MaybeDiscarded<BrowsingContext>& TargetBrowsingContext() const {
+ return mTargetBrowsingContext;
+ }
+
+ void SetTargetBrowsingContext(BrowsingContext* aTargetBrowsingContext);
+
+ nsIURI* BaseURI() const;
+
+ void SetBaseURI(nsIURI* aBaseURI);
+
+ // Helper function allowing convenient work with mozilla::Maybe in C++, hiding
+ // resultPrincipalURI and resultPrincipalURIIsSome attributes from the
+ // consumer.
+ void GetMaybeResultPrincipalURI(
+ mozilla::Maybe<nsCOMPtr<nsIURI>>& aRPURI) const;
+
+ void SetMaybeResultPrincipalURI(
+ mozilla::Maybe<nsCOMPtr<nsIURI>> const& aRPURI);
+
+ uint32_t LoadFlags() const;
+
+ void SetLoadFlags(uint32_t aFlags);
+
+ void SetLoadFlag(uint32_t aFlag);
+
+ void UnsetLoadFlag(uint32_t aFlag);
+
+ bool HasLoadFlags(uint32_t aFlag);
+
+ uint32_t InternalLoadFlags() const;
+
+ void SetInternalLoadFlags(uint32_t aFlags);
+
+ void SetInternalLoadFlag(uint32_t aFlag);
+
+ void UnsetInternalLoadFlag(uint32_t aFlag);
+
+ bool HasInternalLoadFlags(uint32_t aFlag);
+
+ bool FirstParty() const;
+
+ void SetFirstParty(bool aFirstParty);
+
+ bool HasValidUserGestureActivation() const;
+
+ void SetHasValidUserGestureActivation(bool HasValidUserGestureActivation);
+
+ const nsCString& TypeHint() const;
+
+ void SetTypeHint(const nsCString& aTypeHint);
+
+ const nsString& FileName() const;
+
+ void SetFileName(const nsAString& aFileName);
+
+ nsIURI* GetUnstrippedURI() const;
+
+ void SetUnstrippedURI(nsIURI* aUnstrippedURI);
+
+ // Give the type of DocShell we're loading into (chrome/content/etc) and
+ // origin attributes for the URI we're loading, figure out if we should
+ // inherit our principal from the document the load was requested from, or
+ // else if the principal should be set up later in the process (after loads).
+ // See comments in function for more info on principal selection algorithm
+ nsresult SetupInheritingPrincipal(
+ mozilla::dom::BrowsingContext::Type aType,
+ const mozilla::OriginAttributes& aOriginAttributes);
+
+ // If no triggering principal exists at the moment, create one using referrer
+ // information and origin attributes.
+ nsresult SetupTriggeringPrincipal(
+ const mozilla::OriginAttributes& aOriginAttributes);
+
+ void SetIsFromProcessingFrameAttributes() {
+ mIsFromProcessingFrameAttributes = true;
+ }
+ bool GetIsFromProcessingFrameAttributes() const {
+ return mIsFromProcessingFrameAttributes;
+ }
+
+ nsIChannel* GetPendingRedirectedChannel() {
+ return mPendingRedirectedChannel;
+ }
+
+ uint64_t GetPendingRedirectChannelRegistrarId() const {
+ return mChannelRegistrarId;
+ }
+
+ void SetOriginalURIString(const nsCString& aOriginalURI) {
+ mOriginalURIString.emplace(aOriginalURI);
+ }
+ const mozilla::Maybe<nsCString>& GetOriginalURIString() const {
+ return mOriginalURIString;
+ }
+
+ void SetCancelContentJSEpoch(int32_t aCancelEpoch) {
+ mCancelContentJSEpoch.emplace(aCancelEpoch);
+ }
+ const mozilla::Maybe<int32_t>& GetCancelContentJSEpoch() const {
+ return mCancelContentJSEpoch;
+ }
+
+ uint64_t GetLoadIdentifier() const { return mLoadIdentifier; }
+
+ void SetChannelInitialized(bool aInitilized) {
+ mChannelInitialized = aInitilized;
+ }
+
+ bool GetChannelInitialized() const { return mChannelInitialized; }
+
+ void SetIsMetaRefresh(bool aMetaRefresh) { mIsMetaRefresh = aMetaRefresh; }
+
+ bool IsMetaRefresh() const { return mIsMetaRefresh; }
+
+ const mozilla::Maybe<nsCString>& GetRemoteTypeOverride() const {
+ return mRemoteTypeOverride;
+ }
+
+ void SetRemoteTypeOverride(const nsCString& aRemoteTypeOverride) {
+ mRemoteTypeOverride = mozilla::Some(aRemoteTypeOverride);
+ }
+
+ void SetWasSchemelessInput(bool aWasSchemelessInput) {
+ mWasSchemelessInput = aWasSchemelessInput;
+ }
+
+ bool GetWasSchemelessInput() { return mWasSchemelessInput; }
+
+ // Determine the remote type of the process which should be considered
+ // responsible for this load for the purposes of security checks.
+ //
+ // This will generally be the process which created the nsDocShellLoadState
+ // originally, however non-errorpage history loads are always considered to be
+ // triggered by the parent process, as we can validate them against the
+ // history entry.
+ const nsCString& GetEffectiveTriggeringRemoteType() const;
+
+ void SetTriggeringRemoteType(const nsACString& aTriggeringRemoteType);
+
+ // Diagnostic assert if this is a system-principal triggered load, and it is
+ // trivial to determine that the effective triggering remote type would not be
+ // allowed to perform this load.
+ //
+ // This is called early during the load to crash as close to the cause as
+ // possible. See bug 1838686 for details.
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ void AssertProcessCouldTriggerLoadIfSystem();
+#else
+ void AssertProcessCouldTriggerLoadIfSystem() {}
+#endif
+
+ // When loading a document through nsDocShell::LoadURI(), a special set of
+ // flags needs to be set based on other values in nsDocShellLoadState. This
+ // function calculates those flags, before the LoadState is passed to
+ // nsDocShell::InternalLoad.
+ void CalculateLoadURIFlags();
+
+ // Compute the load flags to be used by creating channel. aUriModified and
+ // aIsEmbeddingBlockedError are expected to be Nothing when called from parent
+ // process.
+ nsLoadFlags CalculateChannelLoadFlags(
+ mozilla::dom::BrowsingContext* aBrowsingContext,
+ mozilla::Maybe<bool> aUriModified,
+ mozilla::Maybe<bool> aIsEmbeddingBlockedError);
+
+ mozilla::dom::DocShellLoadStateInit Serialize(
+ mozilla::ipc::IProtocol* aActor);
+
+ void SetLoadIsFromSessionHistory(int32_t aOffset, bool aLoadingCurrentEntry);
+ void ClearLoadIsFromSessionHistory();
+
+ void MaybeStripTrackerQueryStrings(mozilla::dom::BrowsingContext* aContext);
+
+ protected:
+ // Destructor can't be defaulted or inlined, as header doesn't have all type
+ // includes it needs to do so.
+ ~nsDocShellLoadState();
+
+ // Given the original `nsDocShellLoadState` which was sent to a content
+ // process, validate that they corespond to the same load.
+ // Returns a static (telemetry-safe) string naming what did not match, or
+ // nullptr if it succeeds.
+ const char* ValidateWithOriginalState(nsDocShellLoadState* aOriginalState);
+
+ static nsresult CreateFromLoadURIOptions(
+ BrowsingContext* aBrowsingContext, nsIURI* aURI,
+ const mozilla::dom::LoadURIOptions& aLoadURIOptions,
+ uint32_t aLoadFlagsOverride, nsIInputStream* aPostDataOverride,
+ nsDocShellLoadState** aResult);
+
+ // This is the referrer for the load.
+ nsCOMPtr<nsIReferrerInfo> mReferrerInfo;
+
+ // The URI we are navigating to. Will not be null once set.
+ nsCOMPtr<nsIURI> mURI;
+
+ // The URI to set as the originalURI on the channel that does the load. If
+ // null, aURI will be set as the originalURI.
+ nsCOMPtr<nsIURI> mOriginalURI;
+
+ // The URI to be set to loadInfo.resultPrincipalURI
+ // - When Nothing, there will be no change
+ // - When Some, the principal URI will overwrite even
+ // with a null value.
+ //
+ // Valid only if mResultPrincipalURIIsSome is true (has the same meaning as
+ // isSome() on mozilla::Maybe.)
+ nsCOMPtr<nsIURI> mResultPrincipalURI;
+ bool mResultPrincipalURIIsSome;
+
+ // The principal of the load, that is, the entity responsible for causing the
+ // load to occur. In most cases the referrer and the triggeringPrincipal's URI
+ // will be identical.
+ //
+ // Please note that this is the principal that is used for security checks. If
+ // the argument aURI is provided by the web, then please do not pass a
+ // SystemPrincipal as the triggeringPrincipal.
+ nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
+
+ // The SandboxFlags of the load, that are, the SandboxFlags of the entity
+ // responsible for causing the load to occur. Most likely this are the
+ // SandboxFlags of the document that started the load.
+ uint32_t mTriggeringSandboxFlags;
+
+ // The window ID and current "has storage access" value of the entity
+ // triggering the load. This allows the identification of self-initiated
+ // same-origin navigations that should propogate unpartitioned storage access.
+ uint64_t mTriggeringWindowId;
+ bool mTriggeringStorageAccess;
+
+ // The CSP of the load, that is, the CSP of the entity responsible for causing
+ // the load to occur. Most likely this is the CSP of the document that started
+ // the load. In case the entity starting the load did not use a CSP, then mCsp
+ // can be null. Please note that this is also the CSP that will be applied to
+ // the load in case the load encounters a server side redirect.
+ nsCOMPtr<nsIContentSecurityPolicy> mCsp;
+
+ // If a refresh is caused by http-equiv="refresh" we want to set
+ // aResultPrincipalURI, but we do not want to overwrite the channel's
+ // ResultPrincipalURI, if it has already been set on the channel by a protocol
+ // handler.
+ bool mKeepResultPrincipalURIIfSet;
+
+ // If set LOAD_REPLACE flag will be set on the channel. If aOriginalURI is
+ // null, this argument is ignored.
+ bool mLoadReplace;
+
+ // If this attribute is true and no triggeringPrincipal is specified,
+ // copy the principal from the referring document.
+ bool mInheritPrincipal;
+
+ // If this attribute is true only ever use the principal specified
+ // by the triggeringPrincipal and inheritPrincipal attributes.
+ // If there are security reasons for why this is unsafe, such
+ // as trying to use a systemprincipal as the triggeringPrincipal
+ // for a content docshell the load fails.
+ bool mPrincipalIsExplicit;
+
+ bool mNotifiedBeforeUnloadListeners;
+
+ // Principal we're inheriting. If null, this means the principal should be
+ // inherited from the current document. If set to NullPrincipal, the channel
+ // will fill in principal information later in the load. See internal comments
+ // of SetupInheritingPrincipal for more info.
+ //
+ // When passed to InternalLoad, If this argument is null then
+ // principalToInherit is computed differently. See nsDocShell::InternalLoad
+ // for more comments.
+
+ nsCOMPtr<nsIPrincipal> mPrincipalToInherit;
+
+ nsCOMPtr<nsIPrincipal> mPartitionedPrincipalToInherit;
+
+ // If this attribute is true, then a top-level navigation
+ // to a data URI will be allowed.
+ bool mForceAllowDataURI;
+
+ // If this attribute is true, then the top-level navigaion
+ // will be exempt from HTTPS-Only-Mode upgrades.
+ bool mIsExemptFromHTTPSFirstMode;
+
+ // If this attribute is true, this load corresponds to a frame
+ // element loading its original src (or srcdoc) attribute.
+ bool mOriginalFrameSrc;
+
+ // If this attribute is true, then the load was initiated by a
+ // form submission.
+ bool mIsFormSubmission;
+
+ // Contains a load type as specified by the nsDocShellLoadTypes::load*
+ // constants
+ uint32_t mLoadType;
+
+ // Active Session History entry (if loading from SH)
+ nsCOMPtr<nsISHEntry> mSHEntry;
+
+ // Loading session history info for the load
+ mozilla::UniquePtr<mozilla::dom::LoadingSessionHistoryInfo>
+ mLoadingSessionHistoryInfo;
+
+ // Target for load, like _content, _blank etc.
+ nsString mTarget;
+
+ // When set, this is the Target Browsing Context for the navigation
+ // after retargeting.
+ MaybeDiscarded<BrowsingContext> mTargetBrowsingContext;
+
+ // Post data stream (if POSTing)
+ nsCOMPtr<nsIInputStream> mPostDataStream;
+
+ // Additional Headers
+ nsCOMPtr<nsIInputStream> mHeadersStream;
+
+ // When set, the load will be interpreted as a srcdoc load, where contents of
+ // this string will be loaded instead of the URI. Setting srcdocData sets
+ // isSrcdocLoad to true
+ nsString mSrcdocData;
+
+ // When set, this is the Source Browsing Context for the navigation.
+ MaybeDiscarded<BrowsingContext> mSourceBrowsingContext;
+
+ // Used for srcdoc loads to give view-source knowledge of the load's base URI
+ // as this information isn't embedded in the load's URI.
+ nsCOMPtr<nsIURI> mBaseURI;
+
+ // Set of Load Flags, taken from nsDocShellLoadTypes.h and nsIWebNavigation
+ uint32_t mLoadFlags;
+
+ // Set of internal load flags
+ uint32_t mInternalLoadFlags;
+
+ // Is this a First Party Load?
+ bool mFirstParty;
+
+ // Is this load triggered by a user gesture?
+ bool mHasValidUserGestureActivation;
+
+ // Whether this load can steal the focus from the source browsing context.
+ bool mAllowFocusMove;
+
+ // A hint as to the content-type of the resulting data. If no hint, IsVoid()
+ // should return true.
+ nsCString mTypeHint;
+
+ // Non-void when the link should be downloaded as the given filename.
+ // mFileName being non-void but empty means that no filename hint was
+ // specified, but link should still trigger a download. If not a download,
+ // mFileName.IsVoid() should return true.
+ nsString mFileName;
+
+ // This will be true if this load is triggered by attribute changes.
+ // See nsILoadInfo.isFromProcessingFrameAttributes
+ bool mIsFromProcessingFrameAttributes;
+
+ // If set, a pending cross-process redirected channel should be used to
+ // perform the load. The channel will be stored in this value.
+ nsCOMPtr<nsIChannel> mPendingRedirectedChannel;
+
+ // An optional string representation of mURI, before any
+ // fixups were applied, so that we can send it to a search
+ // engine service if needed.
+ mozilla::Maybe<nsCString> mOriginalURIString;
+
+ // An optional value to pass to nsIDocShell::setCancelJSEpoch
+ // when initiating the load.
+ mozilla::Maybe<int32_t> mCancelContentJSEpoch;
+
+ // If mPendingRedirectChannel is set, then this is the identifier
+ // that the parent-process equivalent channel has been registered
+ // with using RedirectChannelRegistrar.
+ uint64_t mChannelRegistrarId;
+
+ // An identifier to make it possible to examine if two loads are
+ // equal, and which browsing context they belong to (see
+ // BrowsingContext::{Get, Set}CurrentLoadIdentifier)
+ const uint64_t mLoadIdentifier;
+
+ // Optional value to indicate that a channel has been
+ // pre-initialized in the parent process.
+ bool mChannelInitialized;
+
+ // True if the load was triggered by a meta refresh.
+ bool mIsMetaRefresh;
+
+ // True if the nsDocShellLoadState was received over IPC.
+ bool mWasCreatedRemotely = false;
+
+ // The original URI before query stripping happened. If it's present, it shows
+ // the query stripping happened. Otherwise, it will be a nullptr.
+ nsCOMPtr<nsIURI> mUnstrippedURI;
+
+ // If set, the remote type which the load should be completed within.
+ mozilla::Maybe<nsCString> mRemoteTypeOverride;
+
+ // Remote type of the process which originally requested the load.
+ nsCString mTriggeringRemoteType;
+
+ // if the to-be-loaded address had it protocol added through a fixup
+ bool mWasSchemelessInput = false;
+};
+
+#endif /* nsDocShellLoadState_h__ */
diff --git a/docshell/base/nsDocShellLoadTypes.h b/docshell/base/nsDocShellLoadTypes.h
new file mode 100644
index 0000000000..1de19e81eb
--- /dev/null
+++ b/docshell/base/nsDocShellLoadTypes.h
@@ -0,0 +1,205 @@
+/* -*- 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 nsDocShellLoadTypes_h_
+#define nsDocShellLoadTypes_h_
+
+#ifdef MOZILLA_INTERNAL_API
+
+# include "nsDOMNavigationTiming.h"
+# include "nsIDocShell.h"
+# include "nsIWebNavigation.h"
+
+/**
+ * Load flag for error pages. This uses one of the reserved flag
+ * values from nsIWebNavigation.
+ */
+# define LOAD_FLAGS_ERROR_PAGE 0x0001U
+
+# define MAKE_LOAD_TYPE(type, flags) ((type) | ((flags) << 16))
+# define LOAD_TYPE_HAS_FLAGS(type, flags) ((type) & ((flags) << 16))
+
+/**
+ * These are flags that confuse ConvertLoadTypeToDocShellLoadInfo and should
+ * not be passed to MAKE_LOAD_TYPE. In particular this includes all flags
+ * above 0xffff (e.g. LOAD_FLAGS_BYPASS_CLASSIFIER), since MAKE_LOAD_TYPE would
+ * just shift them out anyway.
+ */
+# define EXTRA_LOAD_FLAGS \
+ (nsIWebNavigation::LOAD_FLAGS_FROM_EXTERNAL | \
+ nsIWebNavigation::LOAD_FLAGS_FIRST_LOAD | \
+ nsIWebNavigation::LOAD_FLAGS_ALLOW_POPUPS | 0xffff0000)
+
+/* load types are legal combinations of load commands and flags
+ *
+ * NOTE:
+ * Remember to update the IsValidLoadType function below if you change this
+ * enum to ensure bad flag combinations will be rejected.
+ */
+enum LoadType : uint32_t {
+ LOAD_NORMAL = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL,
+ nsIWebNavigation::LOAD_FLAGS_NONE),
+ LOAD_NORMAL_REPLACE =
+ MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL,
+ nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY),
+ LOAD_HISTORY = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_HISTORY,
+ nsIWebNavigation::LOAD_FLAGS_NONE),
+ LOAD_NORMAL_BYPASS_CACHE = MAKE_LOAD_TYPE(
+ nsIDocShell::LOAD_CMD_NORMAL, nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE),
+ LOAD_NORMAL_BYPASS_PROXY = MAKE_LOAD_TYPE(
+ nsIDocShell::LOAD_CMD_NORMAL, nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY),
+ LOAD_NORMAL_BYPASS_PROXY_AND_CACHE =
+ MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL,
+ nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE |
+ nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY),
+ LOAD_RELOAD_NORMAL = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_RELOAD,
+ nsIWebNavigation::LOAD_FLAGS_NONE),
+ LOAD_RELOAD_BYPASS_CACHE = MAKE_LOAD_TYPE(
+ nsIDocShell::LOAD_CMD_RELOAD, nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE),
+ LOAD_RELOAD_BYPASS_PROXY = MAKE_LOAD_TYPE(
+ nsIDocShell::LOAD_CMD_RELOAD, nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY),
+ LOAD_RELOAD_BYPASS_PROXY_AND_CACHE =
+ MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_RELOAD,
+ nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE |
+ nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY),
+ LOAD_LINK = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL,
+ nsIWebNavigation::LOAD_FLAGS_IS_LINK),
+ LOAD_REFRESH = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL,
+ nsIWebNavigation::LOAD_FLAGS_IS_REFRESH),
+ LOAD_REFRESH_REPLACE =
+ MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL,
+ nsIWebNavigation::LOAD_FLAGS_IS_REFRESH |
+ nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY),
+ LOAD_RELOAD_CHARSET_CHANGE =
+ MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_RELOAD,
+ nsIWebNavigation::LOAD_FLAGS_CHARSET_CHANGE),
+ LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE =
+ MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_RELOAD,
+ nsIWebNavigation::LOAD_FLAGS_CHARSET_CHANGE |
+ nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE |
+ nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY),
+ LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE =
+ MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_RELOAD,
+ nsIWebNavigation::LOAD_FLAGS_CHARSET_CHANGE |
+ nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE),
+ LOAD_BYPASS_HISTORY =
+ MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL,
+ nsIWebNavigation::LOAD_FLAGS_BYPASS_HISTORY),
+ LOAD_STOP_CONTENT = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL,
+ nsIWebNavigation::LOAD_FLAGS_STOP_CONTENT),
+ LOAD_STOP_CONTENT_AND_REPLACE =
+ MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL,
+ nsIWebNavigation::LOAD_FLAGS_STOP_CONTENT |
+ nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY),
+ LOAD_PUSHSTATE = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_PUSHSTATE,
+ nsIWebNavigation::LOAD_FLAGS_NONE),
+ LOAD_REPLACE_BYPASS_CACHE =
+ MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL,
+ nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY |
+ nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE),
+ /**
+ * Load type for an error page. These loads are never triggered by users of
+ * Docshell. Instead, Docshell triggers the load itself when a
+ * consumer-triggered load failed.
+ */
+ LOAD_ERROR_PAGE =
+ MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL, LOAD_FLAGS_ERROR_PAGE)
+
+ // NOTE: Adding a new value? Remember to update IsValidLoadType!
+};
+
+static inline bool IsForceReloadType(uint32_t aLoadType) {
+ switch (aLoadType) {
+ case LOAD_RELOAD_BYPASS_CACHE:
+ case LOAD_RELOAD_BYPASS_PROXY:
+ case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
+ return true;
+ }
+ return false;
+}
+
+static inline bool IsValidLoadType(uint32_t aLoadType) {
+ switch (aLoadType) {
+ case LOAD_NORMAL:
+ case LOAD_NORMAL_REPLACE:
+ case LOAD_NORMAL_BYPASS_CACHE:
+ case LOAD_NORMAL_BYPASS_PROXY:
+ case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE:
+ case LOAD_HISTORY:
+ case LOAD_RELOAD_NORMAL:
+ case LOAD_RELOAD_BYPASS_CACHE:
+ case LOAD_RELOAD_BYPASS_PROXY:
+ case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
+ case LOAD_LINK:
+ case LOAD_REFRESH:
+ case LOAD_REFRESH_REPLACE:
+ case LOAD_RELOAD_CHARSET_CHANGE:
+ case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE:
+ case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE:
+ case LOAD_BYPASS_HISTORY:
+ case LOAD_STOP_CONTENT:
+ case LOAD_STOP_CONTENT_AND_REPLACE:
+ case LOAD_PUSHSTATE:
+ case LOAD_REPLACE_BYPASS_CACHE:
+ case LOAD_ERROR_PAGE:
+ return true;
+ }
+ return false;
+}
+
+inline nsDOMNavigationTiming::Type ConvertLoadTypeToNavigationType(
+ uint32_t aLoadType) {
+ // Not initialized, assume it's normal load.
+ if (aLoadType == 0) {
+ aLoadType = LOAD_NORMAL;
+ }
+
+ auto result = nsDOMNavigationTiming::TYPE_RESERVED;
+ switch (aLoadType) {
+ case LOAD_NORMAL:
+ case LOAD_NORMAL_BYPASS_CACHE:
+ case LOAD_NORMAL_BYPASS_PROXY:
+ case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE:
+ case LOAD_NORMAL_REPLACE:
+ case LOAD_LINK:
+ case LOAD_STOP_CONTENT:
+ // FIXME: It isn't clear that LOAD_REFRESH_REPLACE should have a different
+ // navigation type than LOAD_REFRESH. Those loads historically used the
+ // LOAD_NORMAL_REPLACE type, and therefore wound up with TYPE_NAVIGATE by
+ // default.
+ case LOAD_REFRESH_REPLACE:
+ case LOAD_REPLACE_BYPASS_CACHE:
+ result = nsDOMNavigationTiming::TYPE_NAVIGATE;
+ break;
+ case LOAD_HISTORY:
+ result = nsDOMNavigationTiming::TYPE_BACK_FORWARD;
+ break;
+ case LOAD_RELOAD_NORMAL:
+ case LOAD_RELOAD_CHARSET_CHANGE:
+ case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE:
+ case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE:
+ case LOAD_RELOAD_BYPASS_CACHE:
+ case LOAD_RELOAD_BYPASS_PROXY:
+ case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
+ result = nsDOMNavigationTiming::TYPE_RELOAD;
+ break;
+ case LOAD_STOP_CONTENT_AND_REPLACE:
+ case LOAD_REFRESH:
+ case LOAD_BYPASS_HISTORY:
+ case LOAD_ERROR_PAGE:
+ case LOAD_PUSHSTATE:
+ result = nsDOMNavigationTiming::TYPE_RESERVED;
+ break;
+ default:
+ result = nsDOMNavigationTiming::TYPE_RESERVED;
+ break;
+ }
+
+ return result;
+}
+
+#endif // MOZILLA_INTERNAL_API
+#endif
diff --git a/docshell/base/nsDocShellTelemetryUtils.cpp b/docshell/base/nsDocShellTelemetryUtils.cpp
new file mode 100644
index 0000000000..bd4ed865bd
--- /dev/null
+++ b/docshell/base/nsDocShellTelemetryUtils.cpp
@@ -0,0 +1,202 @@
+/* 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 "nsDocShellTelemetryUtils.h"
+
+namespace {
+
+using ErrorLabel = mozilla::Telemetry::LABELS_PAGE_LOAD_ERROR;
+
+struct LoadErrorTelemetryResult {
+ nsresult mValue;
+ ErrorLabel mLabel;
+};
+
+static const LoadErrorTelemetryResult sResult[] = {
+ {
+ NS_ERROR_UNKNOWN_PROTOCOL,
+ ErrorLabel::UNKNOWN_PROTOCOL,
+ },
+ {
+ NS_ERROR_FILE_NOT_FOUND,
+ ErrorLabel::FILE_NOT_FOUND,
+ },
+ {
+ NS_ERROR_FILE_ACCESS_DENIED,
+ ErrorLabel::FILE_ACCESS_DENIED,
+ },
+ {
+ NS_ERROR_UNKNOWN_HOST,
+ ErrorLabel::UNKNOWN_HOST,
+ },
+ {
+ NS_ERROR_CONNECTION_REFUSED,
+ ErrorLabel::CONNECTION_REFUSED,
+ },
+ {
+ NS_ERROR_PROXY_BAD_GATEWAY,
+ ErrorLabel::PROXY_BAD_GATEWAY,
+ },
+ {
+ NS_ERROR_NET_INTERRUPT,
+ ErrorLabel::NET_INTERRUPT,
+ },
+ {
+ NS_ERROR_NET_TIMEOUT,
+ ErrorLabel::NET_TIMEOUT,
+ },
+ {
+ NS_ERROR_PROXY_GATEWAY_TIMEOUT,
+ ErrorLabel::P_GATEWAY_TIMEOUT,
+ },
+ {
+ NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION,
+ ErrorLabel::CSP_FRAME_ANCEST,
+ },
+ {
+ NS_ERROR_CSP_FORM_ACTION_VIOLATION,
+ ErrorLabel::CSP_FORM_ACTION,
+ },
+ {
+ NS_ERROR_XFO_VIOLATION,
+ ErrorLabel::XFO_VIOLATION,
+ },
+ {
+ NS_ERROR_PHISHING_URI,
+ ErrorLabel::PHISHING_URI,
+ },
+ {
+ NS_ERROR_MALWARE_URI,
+ ErrorLabel::MALWARE_URI,
+ },
+ {
+ NS_ERROR_UNWANTED_URI,
+ ErrorLabel::UNWANTED_URI,
+ },
+ {
+ NS_ERROR_HARMFUL_URI,
+ ErrorLabel::HARMFUL_URI,
+ },
+ {
+ NS_ERROR_CONTENT_CRASHED,
+ ErrorLabel::CONTENT_CRASHED,
+ },
+ {
+ NS_ERROR_FRAME_CRASHED,
+ ErrorLabel::FRAME_CRASHED,
+ },
+ {
+ NS_ERROR_BUILDID_MISMATCH,
+ ErrorLabel::BUILDID_MISMATCH,
+ },
+ {
+ NS_ERROR_NET_RESET,
+ ErrorLabel::NET_RESET,
+ },
+ {
+ NS_ERROR_MALFORMED_URI,
+ ErrorLabel::MALFORMED_URI,
+ },
+ {
+ NS_ERROR_REDIRECT_LOOP,
+ ErrorLabel::REDIRECT_LOOP,
+ },
+ {
+ NS_ERROR_UNKNOWN_SOCKET_TYPE,
+ ErrorLabel::UNKNOWN_SOCKET,
+ },
+ {
+ NS_ERROR_DOCUMENT_NOT_CACHED,
+ ErrorLabel::DOCUMENT_N_CACHED,
+ },
+ {
+ NS_ERROR_OFFLINE,
+ ErrorLabel::OFFLINE,
+ },
+ {
+ NS_ERROR_DOCUMENT_IS_PRINTMODE,
+ ErrorLabel::DOC_PRINTMODE,
+ },
+ {
+ NS_ERROR_PORT_ACCESS_NOT_ALLOWED,
+ ErrorLabel::PORT_ACCESS,
+ },
+ {
+ NS_ERROR_UNKNOWN_PROXY_HOST,
+ ErrorLabel::UNKNOWN_PROXY_HOST,
+ },
+ {
+ NS_ERROR_PROXY_CONNECTION_REFUSED,
+ ErrorLabel::PROXY_CONNECTION,
+ },
+ {
+ NS_ERROR_PROXY_FORBIDDEN,
+ ErrorLabel::PROXY_FORBIDDEN,
+ },
+ {
+ NS_ERROR_PROXY_NOT_IMPLEMENTED,
+ ErrorLabel::P_NOT_IMPLEMENTED,
+ },
+ {
+ NS_ERROR_PROXY_AUTHENTICATION_FAILED,
+ ErrorLabel::PROXY_AUTH,
+ },
+ {
+ NS_ERROR_PROXY_TOO_MANY_REQUESTS,
+ ErrorLabel::PROXY_TOO_MANY,
+ },
+ {
+ NS_ERROR_INVALID_CONTENT_ENCODING,
+ ErrorLabel::CONTENT_ENCODING,
+ },
+ {
+ NS_ERROR_UNSAFE_CONTENT_TYPE,
+ ErrorLabel::UNSAFE_CONTENT,
+ },
+ {
+ NS_ERROR_CORRUPTED_CONTENT,
+ ErrorLabel::CORRUPTED_CONTENT,
+ },
+ {
+ NS_ERROR_INTERCEPTION_FAILED,
+ ErrorLabel::INTERCEPTION_FAIL,
+ },
+ {
+ NS_ERROR_NET_INADEQUATE_SECURITY,
+ ErrorLabel::INADEQUATE_SEC,
+ },
+ {
+ NS_ERROR_BLOCKED_BY_POLICY,
+ ErrorLabel::BLOCKED_BY_POLICY,
+ },
+ {
+ NS_ERROR_NET_HTTP2_SENT_GOAWAY,
+ ErrorLabel::HTTP2_SENT_GOAWAY,
+ },
+ {
+ NS_ERROR_NET_HTTP3_PROTOCOL_ERROR,
+ ErrorLabel::HTTP3_PROTOCOL,
+ },
+ {
+ NS_BINDING_FAILED,
+ ErrorLabel::BINDING_FAILED,
+ },
+};
+} // anonymous namespace
+
+namespace mozilla {
+namespace dom {
+mozilla::Telemetry::LABELS_PAGE_LOAD_ERROR LoadErrorToTelemetryLabel(
+ nsresult aRv) {
+ MOZ_ASSERT(aRv != NS_OK);
+
+ for (const auto& p : sResult) {
+ if (p.mValue == aRv) {
+ return p.mLabel;
+ }
+ }
+ return ErrorLabel::otherError;
+}
+} // namespace dom
+} // namespace mozilla
diff --git a/docshell/base/nsDocShellTelemetryUtils.h b/docshell/base/nsDocShellTelemetryUtils.h
new file mode 100644
index 0000000000..4e0097caec
--- /dev/null
+++ b/docshell/base/nsDocShellTelemetryUtils.h
@@ -0,0 +1,22 @@
+//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 nsDocShellTelemetryUtils_h__
+#define nsDocShellTelemetryUtils_h__
+
+#include "mozilla/Telemetry.h"
+
+namespace mozilla {
+namespace dom {
+/**
+ * Convert page load errors to telemetry labels
+ * Only select nsresults are converted, otherwise this function
+ * will return "errorOther", view the list of errors at
+ * docshell/base/nsDocShellTelemetryUtils.cpp.
+ */
+Telemetry::LABELS_PAGE_LOAD_ERROR LoadErrorToTelemetryLabel(nsresult aRv);
+} // namespace dom
+} // namespace mozilla
+#endif // nsDocShellTelemetryUtils_h__
diff --git a/docshell/base/nsDocShellTreeOwner.cpp b/docshell/base/nsDocShellTreeOwner.cpp
new file mode 100644
index 0000000000..9f1ab23a6c
--- /dev/null
+++ b/docshell/base/nsDocShellTreeOwner.cpp
@@ -0,0 +1,1337 @@
+/* -*- 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/. */
+
+// Local Includes
+#include "nsDocShellTreeOwner.h"
+#include "nsWebBrowser.h"
+
+// Helper Classes
+#include "nsContentUtils.h"
+#include "nsSize.h"
+#include "mozilla/ReflowInput.h"
+#include "mozilla/ScopeExit.h"
+#include "nsComponentManagerUtils.h"
+#include "nsString.h"
+#include "nsAtom.h"
+#include "nsReadableUtils.h"
+#include "nsUnicharUtils.h"
+#include "mozilla/LookAndFeel.h"
+
+// Interfaces needed to be included
+#include "nsPresContext.h"
+#include "nsITooltipListener.h"
+#include "nsINode.h"
+#include "Link.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/MouseEvent.h"
+#include "mozilla/dom/SVGTitleElement.h"
+#include "nsIFormControl.h"
+#include "nsIWebNavigation.h"
+#include "nsPIDOMWindow.h"
+#include "nsPIWindowRoot.h"
+#include "nsIWindowWatcher.h"
+#include "nsPIWindowWatcher.h"
+#include "nsIPrompt.h"
+#include "nsIRemoteTab.h"
+#include "nsIBrowserChild.h"
+#include "nsRect.h"
+#include "nsIWebBrowserChromeFocus.h"
+#include "nsIContent.h"
+#include "nsServiceManagerUtils.h"
+#include "nsViewManager.h"
+#include "nsView.h"
+#include "nsXULTooltipListener.h"
+#include "nsIConstraintValidation.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/Try.h"
+#include "mozilla/dom/DragEvent.h"
+#include "mozilla/dom/Event.h" // for Event
+#include "mozilla/dom/File.h" // for input type=file
+#include "mozilla/dom/FileList.h" // for input type=file
+#include "mozilla/dom/LoadURIOptionsBinding.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/TextEvents.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+// A helper routine that navigates the tricky path from a |nsWebBrowser| to
+// a |EventTarget| via the window root and chrome event handler.
+static nsresult GetDOMEventTarget(nsWebBrowser* aInBrowser,
+ EventTarget** aTarget) {
+ if (!aInBrowser) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ aInBrowser->GetContentDOMWindow(getter_AddRefs(domWindow));
+ if (!domWindow) {
+ return NS_ERROR_FAILURE;
+ }
+
+ auto* outerWindow = nsPIDOMWindowOuter::From(domWindow);
+ nsPIDOMWindowOuter* rootWindow = outerWindow->GetPrivateRoot();
+ NS_ENSURE_TRUE(rootWindow, NS_ERROR_FAILURE);
+ nsCOMPtr<EventTarget> target = rootWindow->GetChromeEventHandler();
+ NS_ENSURE_TRUE(target, NS_ERROR_FAILURE);
+ target.forget(aTarget);
+
+ return NS_OK;
+}
+
+nsDocShellTreeOwner::nsDocShellTreeOwner()
+ : mWebBrowser(nullptr),
+ mTreeOwner(nullptr),
+ mPrimaryContentShell(nullptr),
+ mWebBrowserChrome(nullptr),
+ mOwnerWin(nullptr),
+ mOwnerRequestor(nullptr) {}
+
+nsDocShellTreeOwner::~nsDocShellTreeOwner() { RemoveChromeListeners(); }
+
+NS_IMPL_ADDREF(nsDocShellTreeOwner)
+NS_IMPL_RELEASE(nsDocShellTreeOwner)
+
+NS_INTERFACE_MAP_BEGIN(nsDocShellTreeOwner)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDocShellTreeOwner)
+ NS_INTERFACE_MAP_ENTRY(nsIDocShellTreeOwner)
+ NS_INTERFACE_MAP_ENTRY(nsIBaseWindow)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END
+
+// The class that listens to the chrome events and tells the embedding chrome to
+// show tooltips, as appropriate. Handles registering itself with the DOM with
+// AddChromeListeners() and removing itself with RemoveChromeListeners().
+class ChromeTooltipListener final : public nsIDOMEventListener {
+ protected:
+ virtual ~ChromeTooltipListener();
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ ChromeTooltipListener(nsWebBrowser* aInBrowser,
+ nsIWebBrowserChrome* aInChrome);
+
+ NS_DECL_NSIDOMEVENTLISTENER
+ NS_IMETHOD MouseMove(mozilla::dom::Event* aMouseEvent);
+
+ // Add/remove the relevant listeners, based on what interfaces the embedding
+ // chrome implements.
+ NS_IMETHOD AddChromeListeners();
+ NS_IMETHOD RemoveChromeListeners();
+
+ NS_IMETHOD HideTooltip();
+
+ bool WebProgressShowedTooltip(nsIWebProgress* aWebProgress);
+
+ private:
+ // pixel tolerance for mousemove event
+ static constexpr CSSIntCoord kTooltipMouseMoveTolerance = 7;
+
+ NS_IMETHOD AddTooltipListener();
+ NS_IMETHOD RemoveTooltipListener();
+
+ NS_IMETHOD ShowTooltip(int32_t aInXCoords, int32_t aInYCoords,
+ const nsAString& aInTipText,
+ const nsAString& aDirText);
+ nsITooltipTextProvider* GetTooltipTextProvider();
+
+ nsWebBrowser* mWebBrowser;
+ nsCOMPtr<mozilla::dom::EventTarget> mEventTarget;
+ nsCOMPtr<nsITooltipTextProvider> mTooltipTextProvider;
+
+ // This must be a strong ref in order to make sure we can hide the tooltip if
+ // the window goes away while we're displaying one. If we don't hold a strong
+ // ref, the chrome might have been disposed of before we get a chance to tell
+ // it, and no one would ever tell us of that fact.
+ nsCOMPtr<nsIWebBrowserChrome> mWebBrowserChrome;
+
+ bool mTooltipListenerInstalled;
+
+ nsCOMPtr<nsITimer> mTooltipTimer;
+ static void sTooltipCallback(nsITimer* aTimer, void* aListener);
+
+ // Mouse coordinates for last mousemove event we saw
+ CSSIntPoint mMouseClientPoint;
+
+ // Mouse coordinates for tooltip event
+ LayoutDeviceIntPoint mMouseScreenPoint;
+
+ bool mShowingTooltip;
+
+ bool mTooltipShownOnce;
+
+ // The string of text that we last displayed.
+ nsString mLastShownTooltipText;
+
+ nsWeakPtr mLastDocshell;
+
+ // The node hovered over that fired the timer. This may turn into the node
+ // that triggered the tooltip, but only if the timer ever gets around to
+ // firing. This is a strong reference, because the tooltip content can be
+ // destroyed while we're waiting for the tooltip to pop up, and we need to
+ // detect that. It's set only when the tooltip timer is created and launched.
+ // The timer must either fire or be cancelled (or possibly released?), and we
+ // release this reference in each of those cases. So we don't leak.
+ nsCOMPtr<nsINode> mPossibleTooltipNode;
+};
+
+//*****************************************************************************
+// nsDocShellTreeOwner::nsIInterfaceRequestor
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetInterface(const nsIID& aIID, void** aSink) {
+ NS_ENSURE_ARG_POINTER(aSink);
+
+ if (NS_SUCCEEDED(QueryInterface(aIID, aSink))) {
+ return NS_OK;
+ }
+
+ if (aIID.Equals(NS_GET_IID(nsIWebBrowserChromeFocus))) {
+ if (mWebBrowserChromeWeak != nullptr) {
+ return mWebBrowserChromeWeak->QueryReferent(aIID, aSink);
+ }
+ return mOwnerWin->QueryInterface(aIID, aSink);
+ }
+
+ if (aIID.Equals(NS_GET_IID(nsIPrompt))) {
+ nsCOMPtr<nsIPrompt> prompt;
+ EnsurePrompter();
+ prompt = mPrompter;
+ if (prompt) {
+ prompt.forget(aSink);
+ return NS_OK;
+ }
+ return NS_NOINTERFACE;
+ }
+
+ if (aIID.Equals(NS_GET_IID(nsIAuthPrompt))) {
+ nsCOMPtr<nsIAuthPrompt> prompt;
+ EnsureAuthPrompter();
+ prompt = mAuthPrompter;
+ if (prompt) {
+ prompt.forget(aSink);
+ return NS_OK;
+ }
+ return NS_NOINTERFACE;
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> req = GetOwnerRequestor();
+ if (req) {
+ return req->GetInterface(aIID, aSink);
+ }
+
+ return NS_NOINTERFACE;
+}
+
+//*****************************************************************************
+// nsDocShellTreeOwner::nsIDocShellTreeOwner
+//*****************************************************************************
+
+void nsDocShellTreeOwner::EnsurePrompter() {
+ if (mPrompter) {
+ return;
+ }
+
+ nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+ if (wwatch && mWebBrowser) {
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ mWebBrowser->GetContentDOMWindow(getter_AddRefs(domWindow));
+ if (domWindow) {
+ wwatch->GetNewPrompter(domWindow, getter_AddRefs(mPrompter));
+ }
+ }
+}
+
+void nsDocShellTreeOwner::EnsureAuthPrompter() {
+ if (mAuthPrompter) {
+ return;
+ }
+
+ nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+ if (wwatch && mWebBrowser) {
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ mWebBrowser->GetContentDOMWindow(getter_AddRefs(domWindow));
+ if (domWindow) {
+ wwatch->GetNewAuthPrompter(domWindow, getter_AddRefs(mAuthPrompter));
+ }
+ }
+}
+
+void nsDocShellTreeOwner::AddToWatcher() {
+ if (mWebBrowser) {
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ mWebBrowser->GetContentDOMWindow(getter_AddRefs(domWindow));
+ if (domWindow) {
+ nsCOMPtr<nsPIWindowWatcher> wwatch(
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+ if (wwatch) {
+ nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome = GetWebBrowserChrome();
+ if (webBrowserChrome) {
+ wwatch->AddWindow(domWindow, webBrowserChrome);
+ }
+ }
+ }
+ }
+}
+
+void nsDocShellTreeOwner::RemoveFromWatcher() {
+ if (mWebBrowser) {
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ mWebBrowser->GetContentDOMWindow(getter_AddRefs(domWindow));
+ if (domWindow) {
+ nsCOMPtr<nsPIWindowWatcher> wwatch(
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+ if (wwatch) {
+ wwatch->RemoveWindow(domWindow);
+ }
+ }
+ }
+}
+
+void nsDocShellTreeOwner::EnsureContentTreeOwner() {
+ if (mContentTreeOwner) {
+ return;
+ }
+
+ mContentTreeOwner = new nsDocShellTreeOwner();
+ nsCOMPtr<nsIWebBrowserChrome> browserChrome = GetWebBrowserChrome();
+ if (browserChrome) {
+ mContentTreeOwner->SetWebBrowserChrome(browserChrome);
+ }
+
+ if (mWebBrowser) {
+ mContentTreeOwner->WebBrowser(mWebBrowser);
+ }
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::ContentShellAdded(nsIDocShellTreeItem* aContentShell,
+ bool aPrimary) {
+ if (mTreeOwner) return mTreeOwner->ContentShellAdded(aContentShell, aPrimary);
+
+ EnsureContentTreeOwner();
+ aContentShell->SetTreeOwner(mContentTreeOwner);
+
+ if (aPrimary) {
+ mPrimaryContentShell = aContentShell;
+ mPrimaryRemoteTab = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::ContentShellRemoved(nsIDocShellTreeItem* aContentShell) {
+ if (mTreeOwner) {
+ return mTreeOwner->ContentShellRemoved(aContentShell);
+ }
+
+ if (mPrimaryContentShell == aContentShell) {
+ mPrimaryContentShell = nullptr;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetPrimaryContentShell(nsIDocShellTreeItem** aShell) {
+ NS_ENSURE_ARG_POINTER(aShell);
+
+ if (mTreeOwner) {
+ return mTreeOwner->GetPrimaryContentShell(aShell);
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> shell;
+ if (!mPrimaryRemoteTab) {
+ shell =
+ mPrimaryContentShell ? mPrimaryContentShell : mWebBrowser->mDocShell;
+ }
+ shell.forget(aShell);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::RemoteTabAdded(nsIRemoteTab* aTab, bool aPrimary) {
+ if (mTreeOwner) {
+ return mTreeOwner->RemoteTabAdded(aTab, aPrimary);
+ }
+
+ if (aPrimary) {
+ mPrimaryRemoteTab = aTab;
+ mPrimaryContentShell = nullptr;
+ } else if (mPrimaryRemoteTab == aTab) {
+ mPrimaryRemoteTab = nullptr;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::RemoteTabRemoved(nsIRemoteTab* aTab) {
+ if (mTreeOwner) {
+ return mTreeOwner->RemoteTabRemoved(aTab);
+ }
+
+ if (aTab == mPrimaryRemoteTab) {
+ mPrimaryRemoteTab = nullptr;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetPrimaryRemoteTab(nsIRemoteTab** aTab) {
+ if (mTreeOwner) {
+ return mTreeOwner->GetPrimaryRemoteTab(aTab);
+ }
+
+ nsCOMPtr<nsIRemoteTab> tab = mPrimaryRemoteTab;
+ tab.forget(aTab);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetPrimaryContentBrowsingContext(
+ mozilla::dom::BrowsingContext** aBc) {
+ if (mTreeOwner) {
+ return mTreeOwner->GetPrimaryContentBrowsingContext(aBc);
+ }
+ if (mPrimaryRemoteTab) {
+ return mPrimaryRemoteTab->GetBrowsingContext(aBc);
+ }
+ if (mPrimaryContentShell) {
+ return mPrimaryContentShell->GetBrowsingContextXPCOM(aBc);
+ }
+ if (mWebBrowser->mDocShell) {
+ return mWebBrowser->mDocShell->GetBrowsingContextXPCOM(aBc);
+ }
+ *aBc = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetPrimaryContentSize(int32_t* aWidth, int32_t* aHeight) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetPrimaryContentSize(int32_t aWidth, int32_t aHeight) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetRootShellSize(int32_t* aWidth, int32_t* aHeight) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetRootShellSize(int32_t aWidth, int32_t aHeight) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SizeShellTo(nsIDocShellTreeItem* aShellItem, int32_t aCX,
+ int32_t aCY) {
+ nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome = GetWebBrowserChrome();
+
+ NS_ENSURE_STATE(mTreeOwner || webBrowserChrome);
+
+ if (nsCOMPtr<nsIDocShellTreeOwner> treeOwner = mTreeOwner) {
+ return treeOwner->SizeShellTo(aShellItem, aCX, aCY);
+ }
+
+ if (aShellItem == mWebBrowser->mDocShell) {
+ nsCOMPtr<nsIBrowserChild> browserChild =
+ do_QueryInterface(webBrowserChrome);
+ if (browserChild) {
+ // The XUL window to resize is in the parent process, but there we
+ // won't be able to get the size of aShellItem. We can ask the parent
+ // process to change our size instead.
+ nsCOMPtr<nsIBaseWindow> shellAsWin(do_QueryInterface(aShellItem));
+ NS_ENSURE_TRUE(shellAsWin, NS_ERROR_FAILURE);
+
+ LayoutDeviceIntSize shellSize;
+ shellAsWin->GetSize(&shellSize.width, &shellSize.height);
+ LayoutDeviceIntSize deltaSize = LayoutDeviceIntSize(aCX, aCY) - shellSize;
+
+ LayoutDeviceIntSize currentSize;
+ GetSize(&currentSize.width, &currentSize.height);
+
+ LayoutDeviceIntSize newSize = currentSize + deltaSize;
+ return SetSize(newSize.width, newSize.height, true);
+ }
+ // XXX: this is weird, but we used to call a method here
+ // (webBrowserChrome->SizeBrowserTo()) whose implementations all failed
+ // like this, so...
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("This is unimplemented, API should be cleaned up");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetPersistence(bool aPersistPosition, bool aPersistSize,
+ bool aPersistSizeMode) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetPersistence(bool* aPersistPosition, bool* aPersistSize,
+ bool* aPersistSizeMode) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetTabCount(uint32_t* aResult) {
+ if (mTreeOwner) {
+ return mTreeOwner->GetTabCount(aResult);
+ }
+
+ *aResult = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetHasPrimaryContent(bool* aResult) {
+ *aResult = mPrimaryRemoteTab || mPrimaryContentShell;
+ return NS_OK;
+}
+
+//*****************************************************************************
+// nsDocShellTreeOwner::nsIBaseWindow
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::InitWindow(nativeWindow aParentNativeWindow,
+ nsIWidget* aParentWidget, int32_t aX,
+ int32_t aY, int32_t aCX, int32_t aCY) {
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::Destroy() {
+ nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome = GetWebBrowserChrome();
+ if (webBrowserChrome) {
+ // XXX: this is weird, but we used to call a method here
+ // (webBrowserChrome->DestroyBrowserWindow()) whose implementations all
+ // failed like this, so...
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ return NS_ERROR_NULL_POINTER;
+}
+
+double nsDocShellTreeOwner::GetWidgetCSSToDeviceScale() {
+ return mWebBrowser ? mWebBrowser->GetWidgetCSSToDeviceScale() : 1.0;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetDevicePixelsPerDesktopPixel(double* aScale) {
+ if (mWebBrowser) {
+ return mWebBrowser->GetDevicePixelsPerDesktopPixel(aScale);
+ }
+
+ *aScale = 1.0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetPositionDesktopPix(int32_t aX, int32_t aY) {
+ if (mWebBrowser) {
+ nsresult rv = mWebBrowser->SetPositionDesktopPix(aX, aY);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ double scale = 1.0;
+ GetDevicePixelsPerDesktopPixel(&scale);
+ return SetPosition(NSToIntRound(aX * scale), NSToIntRound(aY * scale));
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetPosition(int32_t aX, int32_t aY) {
+ return SetDimensions(
+ {DimensionKind::Outer, Some(aX), Some(aY), Nothing(), Nothing()});
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetPosition(int32_t* aX, int32_t* aY) {
+ return GetDimensions(DimensionKind::Outer, aX, aY, nullptr, nullptr);
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetSize(int32_t aCX, int32_t aCY, bool aRepaint) {
+ return SetDimensions(
+ {DimensionKind::Outer, Nothing(), Nothing(), Some(aCX), Some(aCY)});
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetSize(int32_t* aCX, int32_t* aCY) {
+ return GetDimensions(DimensionKind::Outer, nullptr, nullptr, aCX, aCY);
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetPositionAndSize(int32_t aX, int32_t aY, int32_t aCX,
+ int32_t aCY, uint32_t aFlags) {
+ return SetDimensions(
+ {DimensionKind::Outer, Some(aX), Some(aY), Some(aCX), Some(aCY)});
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetPositionAndSize(int32_t* aX, int32_t* aY, int32_t* aCX,
+ int32_t* aCY) {
+ return GetDimensions(DimensionKind::Outer, aX, aY, aCX, aCY);
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetDimensions(DimensionRequest&& aRequest) {
+ nsCOMPtr<nsIBaseWindow> ownerWin = GetOwnerWin();
+ if (ownerWin) {
+ return ownerWin->SetDimensions(std::move(aRequest));
+ }
+
+ nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome = GetWebBrowserChrome();
+ NS_ENSURE_STATE(webBrowserChrome);
+ return webBrowserChrome->SetDimensions(std::move(aRequest));
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetDimensions(DimensionKind aDimensionKind, int32_t* aX,
+ int32_t* aY, int32_t* aCX, int32_t* aCY) {
+ nsCOMPtr<nsIBaseWindow> ownerWin = GetOwnerWin();
+ if (ownerWin) {
+ return ownerWin->GetDimensions(aDimensionKind, aX, aY, aCX, aCY);
+ }
+
+ nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome = GetWebBrowserChrome();
+ NS_ENSURE_STATE(webBrowserChrome);
+ return webBrowserChrome->GetDimensions(aDimensionKind, aX, aY, aCX, aCY);
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::Repaint(bool aForce) { return NS_ERROR_NULL_POINTER; }
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetParentWidget(nsIWidget** aParentWidget) {
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetParentWidget(nsIWidget* aParentWidget) {
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetParentNativeWindow(nativeWindow* aParentNativeWindow) {
+ nsCOMPtr<nsIBaseWindow> ownerWin = GetOwnerWin();
+ if (ownerWin) {
+ return ownerWin->GetParentNativeWindow(aParentNativeWindow);
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetParentNativeWindow(nativeWindow aParentNativeWindow) {
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetNativeHandle(nsAString& aNativeHandle) {
+ // the nativeHandle should be accessed from nsIAppWindow
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetVisibility(bool* aVisibility) {
+ nsCOMPtr<nsIBaseWindow> ownerWin = GetOwnerWin();
+ if (ownerWin) {
+ return ownerWin->GetVisibility(aVisibility);
+ }
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetVisibility(bool aVisibility) {
+ nsCOMPtr<nsIBaseWindow> ownerWin = GetOwnerWin();
+ if (ownerWin) {
+ return ownerWin->SetVisibility(aVisibility);
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetEnabled(bool* aEnabled) {
+ NS_ENSURE_ARG_POINTER(aEnabled);
+ *aEnabled = true;
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetEnabled(bool aEnabled) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetMainWidget(nsIWidget** aMainWidget) {
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetTitle(nsAString& aTitle) {
+ nsCOMPtr<nsIBaseWindow> ownerWin = GetOwnerWin();
+ if (ownerWin) {
+ return ownerWin->GetTitle(aTitle);
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetTitle(const nsAString& aTitle) {
+ nsCOMPtr<nsIBaseWindow> ownerWin = GetOwnerWin();
+ if (ownerWin) {
+ return ownerWin->SetTitle(aTitle);
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+//*****************************************************************************
+// nsDocShellTreeOwner::nsIWebProgressListener
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::OnProgressChange(nsIWebProgress* aProgress,
+ nsIRequest* aRequest,
+ int32_t aCurSelfProgress,
+ int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress,
+ int32_t aMaxTotalProgress) {
+ // In the absence of DOM document creation event, this method is the
+ // most convenient place to install the mouse listener on the
+ // DOM document.
+ return AddChromeListeners();
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::OnStateChange(nsIWebProgress* aProgress,
+ nsIRequest* aRequest,
+ uint32_t aProgressStateFlags,
+ nsresult aStatus) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::OnLocationChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, nsIURI* aURI,
+ uint32_t aFlags) {
+ if (mChromeTooltipListener && aWebProgress &&
+ !(aFlags & nsIWebProgressListener::LOCATION_CHANGE_SAME_DOCUMENT) &&
+ mChromeTooltipListener->WebProgressShowedTooltip(aWebProgress)) {
+ mChromeTooltipListener->HideTooltip();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::OnStatusChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, nsresult aStatus,
+ const char16_t* aMessage) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::OnSecurityChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, uint32_t aState) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::OnContentBlockingEvent(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t aEvent) {
+ return NS_OK;
+}
+
+//*****************************************************************************
+// nsDocShellTreeOwner: Accessors
+//*****************************************************************************
+
+void nsDocShellTreeOwner::WebBrowser(nsWebBrowser* aWebBrowser) {
+ if (!aWebBrowser) {
+ RemoveChromeListeners();
+ }
+ if (aWebBrowser != mWebBrowser) {
+ mPrompter = nullptr;
+ mAuthPrompter = nullptr;
+ }
+
+ mWebBrowser = aWebBrowser;
+
+ if (mContentTreeOwner) {
+ mContentTreeOwner->WebBrowser(aWebBrowser);
+ if (!aWebBrowser) {
+ mContentTreeOwner = nullptr;
+ }
+ }
+}
+
+nsWebBrowser* nsDocShellTreeOwner::WebBrowser() { return mWebBrowser; }
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetTreeOwner(nsIDocShellTreeOwner* aTreeOwner) {
+ if (aTreeOwner) {
+ nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome(do_GetInterface(aTreeOwner));
+ NS_ENSURE_TRUE(webBrowserChrome, NS_ERROR_INVALID_ARG);
+ NS_ENSURE_SUCCESS(SetWebBrowserChrome(webBrowserChrome),
+ NS_ERROR_INVALID_ARG);
+ mTreeOwner = aTreeOwner;
+ } else {
+ mTreeOwner = nullptr;
+ nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome = GetWebBrowserChrome();
+ if (!webBrowserChrome) {
+ NS_ENSURE_SUCCESS(SetWebBrowserChrome(nullptr), NS_ERROR_FAILURE);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetWebBrowserChrome(
+ nsIWebBrowserChrome* aWebBrowserChrome) {
+ if (!aWebBrowserChrome) {
+ mWebBrowserChrome = nullptr;
+ mOwnerWin = nullptr;
+ mOwnerRequestor = nullptr;
+ mWebBrowserChromeWeak = nullptr;
+ } else {
+ nsCOMPtr<nsISupportsWeakReference> supportsweak =
+ do_QueryInterface(aWebBrowserChrome);
+ if (supportsweak) {
+ supportsweak->GetWeakReference(getter_AddRefs(mWebBrowserChromeWeak));
+ } else {
+ nsCOMPtr<nsIBaseWindow> ownerWin(do_QueryInterface(aWebBrowserChrome));
+ nsCOMPtr<nsIInterfaceRequestor> requestor(
+ do_QueryInterface(aWebBrowserChrome));
+
+ // it's ok for ownerWin or requestor to be null.
+ mWebBrowserChrome = aWebBrowserChrome;
+ mOwnerWin = ownerWin;
+ mOwnerRequestor = requestor;
+ }
+ }
+
+ if (mContentTreeOwner) {
+ mContentTreeOwner->SetWebBrowserChrome(aWebBrowserChrome);
+ }
+
+ return NS_OK;
+}
+
+// Hook up things to the chrome like context menus and tooltips, if the chrome
+// has implemented the right interfaces.
+NS_IMETHODIMP
+nsDocShellTreeOwner::AddChromeListeners() {
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome = GetWebBrowserChrome();
+ if (!webBrowserChrome) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // install tooltips
+ if (!mChromeTooltipListener) {
+ nsCOMPtr<nsITooltipListener> tooltipListener(
+ do_QueryInterface(webBrowserChrome));
+ if (tooltipListener) {
+ mChromeTooltipListener =
+ new ChromeTooltipListener(mWebBrowser, webBrowserChrome);
+ rv = mChromeTooltipListener->AddChromeListeners();
+ }
+ }
+
+ nsCOMPtr<EventTarget> target;
+ GetDOMEventTarget(mWebBrowser, getter_AddRefs(target));
+
+ // register dragover and drop event listeners with the listener manager
+ MOZ_ASSERT(target, "how does this happen? (see bug 1659758)");
+ if (target) {
+ if (EventListenerManager* elmP = target->GetOrCreateListenerManager()) {
+ elmP->AddEventListenerByType(this, u"dragover"_ns,
+ TrustedEventsAtSystemGroupBubble());
+ elmP->AddEventListenerByType(this, u"drop"_ns,
+ TrustedEventsAtSystemGroupBubble());
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::RemoveChromeListeners() {
+ if (mChromeTooltipListener) {
+ mChromeTooltipListener->RemoveChromeListeners();
+ mChromeTooltipListener = nullptr;
+ }
+
+ nsCOMPtr<EventTarget> piTarget;
+ GetDOMEventTarget(mWebBrowser, getter_AddRefs(piTarget));
+ if (!piTarget) {
+ return NS_OK;
+ }
+
+ EventListenerManager* elmP = piTarget->GetOrCreateListenerManager();
+ if (elmP) {
+ elmP->RemoveEventListenerByType(this, u"dragover"_ns,
+ TrustedEventsAtSystemGroupBubble());
+ elmP->RemoveEventListenerByType(this, u"drop"_ns,
+ TrustedEventsAtSystemGroupBubble());
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::HandleEvent(Event* aEvent) {
+ DragEvent* dragEvent = aEvent ? aEvent->AsDragEvent() : nullptr;
+ if (NS_WARN_IF(!dragEvent)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (dragEvent->DefaultPrevented()) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDroppedLinkHandler> handler =
+ do_GetService("@mozilla.org/content/dropped-link-handler;1");
+ if (!handler) {
+ return NS_OK;
+ }
+
+ nsAutoString eventType;
+ aEvent->GetType(eventType);
+ if (eventType.EqualsLiteral("dragover")) {
+ bool canDropLink = false;
+ handler->CanDropLink(dragEvent, false, &canDropLink);
+ if (canDropLink) {
+ aEvent->PreventDefault();
+ }
+ } else if (eventType.EqualsLiteral("drop")) {
+ nsCOMPtr<nsIWebNavigation> webnav =
+ static_cast<nsIWebNavigation*>(mWebBrowser);
+
+ // The page might have cancelled the dragover event itself, so check to
+ // make sure that the link can be dropped first.
+ bool canDropLink = false;
+ handler->CanDropLink(dragEvent, false, &canDropLink);
+ if (!canDropLink) {
+ return NS_OK;
+ }
+
+ nsTArray<RefPtr<nsIDroppedLinkItem>> links;
+ if (webnav && NS_SUCCEEDED(handler->DropLinks(dragEvent, true, links))) {
+ if (links.Length() >= 1) {
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal;
+ handler->GetTriggeringPrincipal(dragEvent,
+ getter_AddRefs(triggeringPrincipal));
+ if (triggeringPrincipal) {
+ nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome =
+ GetWebBrowserChrome();
+ if (webBrowserChrome) {
+ nsCOMPtr<nsIBrowserChild> browserChild =
+ do_QueryInterface(webBrowserChrome);
+ if (browserChild) {
+ nsresult rv = browserChild->RemoteDropLinks(links);
+ return rv;
+ }
+ }
+ nsAutoString url;
+ if (NS_SUCCEEDED(links[0]->GetUrl(url))) {
+ if (!url.IsEmpty()) {
+#ifndef ANDROID
+ MOZ_ASSERT(triggeringPrincipal,
+ "nsDocShellTreeOwner::HandleEvent: Need a valid "
+ "triggeringPrincipal");
+#endif
+ LoadURIOptions loadURIOptions;
+ loadURIOptions.mTriggeringPrincipal = triggeringPrincipal;
+ nsCOMPtr<nsIContentSecurityPolicy> csp;
+ handler->GetCsp(dragEvent, getter_AddRefs(csp));
+ loadURIOptions.mCsp = csp;
+ webnav->FixupAndLoadURIString(url, loadURIOptions);
+ }
+ }
+ }
+ }
+ } else {
+ aEvent->StopPropagation();
+ aEvent->PreventDefault();
+ }
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<nsIWebBrowserChrome>
+nsDocShellTreeOwner::GetWebBrowserChrome() {
+ nsCOMPtr<nsIWebBrowserChrome> chrome;
+ if (mWebBrowserChromeWeak) {
+ chrome = do_QueryReferent(mWebBrowserChromeWeak);
+ } else if (mWebBrowserChrome) {
+ chrome = mWebBrowserChrome;
+ }
+ return chrome.forget();
+}
+
+already_AddRefed<nsIBaseWindow> nsDocShellTreeOwner::GetOwnerWin() {
+ nsCOMPtr<nsIBaseWindow> win;
+ if (mWebBrowserChromeWeak) {
+ win = do_QueryReferent(mWebBrowserChromeWeak);
+ } else if (mOwnerWin) {
+ win = mOwnerWin;
+ }
+ return win.forget();
+}
+
+already_AddRefed<nsIInterfaceRequestor>
+nsDocShellTreeOwner::GetOwnerRequestor() {
+ nsCOMPtr<nsIInterfaceRequestor> req;
+ if (mWebBrowserChromeWeak) {
+ req = do_QueryReferent(mWebBrowserChromeWeak);
+ } else if (mOwnerRequestor) {
+ req = mOwnerRequestor;
+ }
+ return req.forget();
+}
+
+NS_IMPL_ISUPPORTS(ChromeTooltipListener, nsIDOMEventListener)
+
+ChromeTooltipListener::ChromeTooltipListener(nsWebBrowser* aInBrowser,
+ nsIWebBrowserChrome* aInChrome)
+ : mWebBrowser(aInBrowser),
+ mWebBrowserChrome(aInChrome),
+ mTooltipListenerInstalled(false),
+ mShowingTooltip(false),
+ mTooltipShownOnce(false) {}
+
+ChromeTooltipListener::~ChromeTooltipListener() {}
+
+nsITooltipTextProvider* ChromeTooltipListener::GetTooltipTextProvider() {
+ if (!mTooltipTextProvider) {
+ mTooltipTextProvider = do_GetService(NS_TOOLTIPTEXTPROVIDER_CONTRACTID);
+ }
+
+ if (!mTooltipTextProvider) {
+ mTooltipTextProvider =
+ do_GetService(NS_DEFAULTTOOLTIPTEXTPROVIDER_CONTRACTID);
+ }
+
+ return mTooltipTextProvider;
+}
+
+// Hook up things to the chrome like context menus and tooltips, if the chrome
+// has implemented the right interfaces.
+NS_IMETHODIMP
+ChromeTooltipListener::AddChromeListeners() {
+ if (!mEventTarget) {
+ GetDOMEventTarget(mWebBrowser, getter_AddRefs(mEventTarget));
+ }
+
+ // Register the appropriate events for tooltips, but only if
+ // the embedding chrome cares.
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsITooltipListener> tooltipListener(
+ do_QueryInterface(mWebBrowserChrome));
+ if (tooltipListener && !mTooltipListenerInstalled) {
+ rv = AddTooltipListener();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ return rv;
+}
+
+// Subscribe to the events that will allow us to track tooltips. We need "mouse"
+// for mouseExit, "mouse motion" for mouseMove, and "key" for keyDown. As we
+// add the listeners, keep track of how many succeed so we can clean up
+// correctly in Release().
+NS_IMETHODIMP
+ChromeTooltipListener::AddTooltipListener() {
+ if (mEventTarget) {
+ MOZ_TRY(mEventTarget->AddSystemEventListener(u"keydown"_ns, this, false,
+ false));
+ MOZ_TRY(mEventTarget->AddSystemEventListener(u"mousedown"_ns, this, false,
+ false));
+ MOZ_TRY(mEventTarget->AddSystemEventListener(u"mouseout"_ns, this, false,
+ false));
+ MOZ_TRY(mEventTarget->AddSystemEventListener(u"mousemove"_ns, this, false,
+ false));
+
+ mTooltipListenerInstalled = true;
+ }
+
+ return NS_OK;
+}
+
+// Unsubscribe from the various things we've hooked up to the window root.
+NS_IMETHODIMP
+ChromeTooltipListener::RemoveChromeListeners() {
+ HideTooltip();
+
+ if (mTooltipListenerInstalled) {
+ RemoveTooltipListener();
+ }
+
+ mEventTarget = nullptr;
+
+ // it really doesn't matter if these fail...
+ return NS_OK;
+}
+
+// Unsubscribe from all the various tooltip events that we were listening to.
+NS_IMETHODIMP
+ChromeTooltipListener::RemoveTooltipListener() {
+ if (mEventTarget) {
+ mEventTarget->RemoveSystemEventListener(u"keydown"_ns, this, false);
+ mEventTarget->RemoveSystemEventListener(u"mousedown"_ns, this, false);
+ mEventTarget->RemoveSystemEventListener(u"mouseout"_ns, this, false);
+ mEventTarget->RemoveSystemEventListener(u"mousemove"_ns, this, false);
+ mTooltipListenerInstalled = false;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ChromeTooltipListener::HandleEvent(Event* aEvent) {
+ nsAutoString eventType;
+ aEvent->GetType(eventType);
+
+ if (eventType.EqualsLiteral("mousedown")) {
+ return HideTooltip();
+ } else if (eventType.EqualsLiteral("keydown")) {
+ WidgetKeyboardEvent* keyEvent = aEvent->WidgetEventPtr()->AsKeyboardEvent();
+ if (nsXULTooltipListener::KeyEventHidesTooltip(*keyEvent)) {
+ return HideTooltip();
+ }
+ return NS_OK;
+ } else if (eventType.EqualsLiteral("mouseout")) {
+ // Reset flag so that tooltip will display on the next MouseMove
+ mTooltipShownOnce = false;
+ return HideTooltip();
+ } else if (eventType.EqualsLiteral("mousemove")) {
+ return MouseMove(aEvent);
+ }
+
+ NS_ERROR("Unexpected event type");
+ return NS_OK;
+}
+
+// If we're a tooltip, fire off a timer to see if a tooltip should be shown. If
+// the timer fires, we cache the node in |mPossibleTooltipNode|.
+nsresult ChromeTooltipListener::MouseMove(Event* aMouseEvent) {
+ if (!nsXULTooltipListener::ShowTooltips()) {
+ return NS_OK;
+ }
+
+ MouseEvent* mouseEvent = aMouseEvent->AsMouseEvent();
+ if (!mouseEvent) {
+ return NS_OK;
+ }
+
+ // stash the coordinates of the event so that we can still get back to it from
+ // within the timer callback. On win32, we'll get a MouseMove event even when
+ // a popup goes away -- even when the mouse doesn't change position! To get
+ // around this, we make sure the mouse has really moved before proceeding.
+ CSSIntPoint newMouseClientPoint = mouseEvent->ClientPoint();
+ if (mMouseClientPoint == newMouseClientPoint) {
+ return NS_OK;
+ }
+
+ // Filter out minor mouse movements.
+ if (mShowingTooltip &&
+ (abs(mMouseClientPoint.x - newMouseClientPoint.x) <=
+ kTooltipMouseMoveTolerance) &&
+ (abs(mMouseClientPoint.y - newMouseClientPoint.y) <=
+ kTooltipMouseMoveTolerance)) {
+ return NS_OK;
+ }
+
+ mMouseClientPoint = newMouseClientPoint;
+ mMouseScreenPoint = mouseEvent->ScreenPointLayoutDevicePix();
+
+ if (mTooltipTimer) {
+ mTooltipTimer->Cancel();
+ mTooltipTimer = nullptr;
+ }
+
+ if (!mShowingTooltip) {
+ if (nsCOMPtr<EventTarget> eventTarget = aMouseEvent->GetComposedTarget()) {
+ mPossibleTooltipNode = nsINode::FromEventTarget(eventTarget);
+ }
+
+ if (mPossibleTooltipNode) {
+ nsresult rv = NS_NewTimerWithFuncCallback(
+ getter_AddRefs(mTooltipTimer), sTooltipCallback, this,
+ LookAndFeel::GetInt(LookAndFeel::IntID::TooltipDelay, 500),
+ nsITimer::TYPE_ONE_SHOT, "ChromeTooltipListener::MouseMove",
+ GetMainThreadSerialEventTarget());
+ if (NS_FAILED(rv)) {
+ mPossibleTooltipNode = nullptr;
+ NS_WARNING("Could not create a timer for tooltip tracking");
+ }
+ }
+ } else {
+ mTooltipShownOnce = true;
+ return HideTooltip();
+ }
+
+ return NS_OK;
+}
+
+// Tell the registered chrome that they should show the tooltip.
+NS_IMETHODIMP
+ChromeTooltipListener::ShowTooltip(int32_t aInXCoords, int32_t aInYCoords,
+ const nsAString& aInTipText,
+ const nsAString& aTipDir) {
+ nsresult rv = NS_OK;
+
+ // do the work to call the client
+ nsCOMPtr<nsITooltipListener> tooltipListener(
+ do_QueryInterface(mWebBrowserChrome));
+ if (tooltipListener) {
+ rv = tooltipListener->OnShowTooltip(aInXCoords, aInYCoords, aInTipText,
+ aTipDir);
+ if (NS_SUCCEEDED(rv)) {
+ mShowingTooltip = true;
+ }
+ }
+
+ return rv;
+}
+
+// Tell the registered chrome that they should rollup the tooltip
+// NOTE: This routine is safe to call even if the popup is already closed.
+NS_IMETHODIMP
+ChromeTooltipListener::HideTooltip() {
+ nsresult rv = NS_OK;
+
+ // shut down the relevant timers
+ if (mTooltipTimer) {
+ mTooltipTimer->Cancel();
+ mTooltipTimer = nullptr;
+ // release tooltip target
+ mPossibleTooltipNode = nullptr;
+ mLastDocshell = nullptr;
+ }
+
+ // if we're showing the tip, tell the chrome to hide it
+ if (mShowingTooltip) {
+ nsCOMPtr<nsITooltipListener> tooltipListener(
+ do_QueryInterface(mWebBrowserChrome));
+ if (tooltipListener) {
+ rv = tooltipListener->OnHideTooltip();
+ if (NS_SUCCEEDED(rv)) {
+ mShowingTooltip = false;
+ }
+ }
+ }
+
+ return rv;
+}
+
+bool ChromeTooltipListener::WebProgressShowedTooltip(
+ nsIWebProgress* aWebProgress) {
+ nsCOMPtr<nsIDocShell> docshell = do_QueryInterface(aWebProgress);
+ nsCOMPtr<nsIDocShell> lastUsed = do_QueryReferent(mLastDocshell);
+ while (lastUsed) {
+ if (lastUsed == docshell) {
+ return true;
+ }
+ // We can't use the docshell hierarchy here, because when the parent
+ // docshell is navigated, the child docshell is disconnected (ie its
+ // references to the parent are nulled out) despite it still being
+ // alive here. So we use the document hierarchy instead:
+ Document* document = lastUsed->GetDocument();
+ if (document) {
+ document = document->GetInProcessParentDocument();
+ }
+ if (!document) {
+ break;
+ }
+ lastUsed = document->GetDocShell();
+ }
+ return false;
+}
+
+// A timer callback, fired when the mouse has hovered inside of a frame for the
+// appropriate amount of time. Getting to this point means that we should show
+// the tooltip, but only after we determine there is an appropriate TITLE
+// element.
+//
+// This relies on certain things being cached into the |aChromeTooltipListener|
+// object passed to us by the timer:
+// -- the x/y coordinates of the mouse (mMouseClientY, mMouseClientX)
+// -- the dom node the user hovered over (mPossibleTooltipNode)
+void ChromeTooltipListener::sTooltipCallback(nsITimer* aTimer,
+ void* aChromeTooltipListener) {
+ auto* self = static_cast<ChromeTooltipListener*>(aChromeTooltipListener);
+ if (!self || !self->mPossibleTooltipNode) {
+ return;
+ }
+ // release tooltip target once done, no matter what we do here.
+ auto cleanup = MakeScopeExit([&] { self->mPossibleTooltipNode = nullptr; });
+ if (!self->mPossibleTooltipNode->IsInComposedDoc()) {
+ return;
+ }
+ // Check that the document or its ancestors haven't been replaced.
+ {
+ Document* doc = self->mPossibleTooltipNode->OwnerDoc();
+ while (doc) {
+ if (!doc->IsCurrentActiveDocument()) {
+ return;
+ }
+ doc = doc->GetInProcessParentDocument();
+ }
+ }
+
+ nsCOMPtr<nsIDocShell> docShell =
+ do_GetInterface(static_cast<nsIWebBrowser*>(self->mWebBrowser));
+ if (!docShell || !docShell->GetBrowsingContext()->IsActive()) {
+ return;
+ }
+
+ // if there is text associated with the node, show the tip and fire
+ // off a timer to auto-hide it.
+ nsITooltipTextProvider* tooltipProvider = self->GetTooltipTextProvider();
+ if (!tooltipProvider) {
+ return;
+ }
+ nsString tooltipText;
+ nsString directionText;
+ bool textFound = false;
+ tooltipProvider->GetNodeText(self->mPossibleTooltipNode,
+ getter_Copies(tooltipText),
+ getter_Copies(directionText), &textFound);
+
+ if (textFound && (!self->mTooltipShownOnce ||
+ tooltipText != self->mLastShownTooltipText)) {
+ // ShowTooltip expects screen-relative position.
+ self->ShowTooltip(self->mMouseScreenPoint.x, self->mMouseScreenPoint.y,
+ tooltipText, directionText);
+ self->mLastShownTooltipText = std::move(tooltipText);
+ self->mLastDocshell = do_GetWeakReference(
+ self->mPossibleTooltipNode->OwnerDoc()->GetDocShell());
+ }
+}
diff --git a/docshell/base/nsDocShellTreeOwner.h b/docshell/base/nsDocShellTreeOwner.h
new file mode 100644
index 0000000000..0627357606
--- /dev/null
+++ b/docshell/base/nsDocShellTreeOwner.h
@@ -0,0 +1,111 @@
+/* -*- 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 nsDocShellTreeOwner_h__
+#define nsDocShellTreeOwner_h__
+
+// Helper Classes
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+// Interfaces Needed
+#include "nsIBaseWindow.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIWebBrowserChrome.h"
+#include "nsIDOMEventListener.h"
+#include "nsIWebProgressListener.h"
+#include "nsWeakReference.h"
+#include "nsITimer.h"
+#include "nsIPrompt.h"
+#include "nsIAuthPrompt.h"
+#include "nsITooltipTextProvider.h"
+#include "nsCTooltipTextProvider.h"
+
+namespace mozilla {
+namespace dom {
+class Event;
+class EventTarget;
+} // namespace dom
+} // namespace mozilla
+
+class nsIDocShellTreeItem;
+class nsWebBrowser;
+class ChromeTooltipListener;
+
+class nsDocShellTreeOwner final : public nsIDocShellTreeOwner,
+ public nsIBaseWindow,
+ public nsIInterfaceRequestor,
+ public nsIWebProgressListener,
+ public nsIDOMEventListener,
+ public nsSupportsWeakReference {
+ friend class nsWebBrowser;
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSIBASEWINDOW
+ NS_DECL_NSIDOCSHELLTREEOWNER
+ NS_DECL_NSIDOMEVENTLISTENER
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSIWEBPROGRESSLISTENER
+
+ protected:
+ nsDocShellTreeOwner();
+ virtual ~nsDocShellTreeOwner();
+
+ void WebBrowser(nsWebBrowser* aWebBrowser);
+
+ nsWebBrowser* WebBrowser();
+ NS_IMETHOD SetTreeOwner(nsIDocShellTreeOwner* aTreeOwner);
+ NS_IMETHOD SetWebBrowserChrome(nsIWebBrowserChrome* aWebBrowserChrome);
+
+ NS_IMETHOD AddChromeListeners();
+ NS_IMETHOD RemoveChromeListeners();
+
+ void EnsurePrompter();
+ void EnsureAuthPrompter();
+
+ void AddToWatcher();
+ void RemoveFromWatcher();
+
+ void EnsureContentTreeOwner();
+
+ // These helper functions return the correct instances of the requested
+ // interfaces. If the object passed to SetWebBrowserChrome() implements
+ // nsISupportsWeakReference, then these functions call QueryReferent on
+ // that object. Otherwise, they return an addrefed pointer. If the
+ // WebBrowserChrome object doesn't exist, they return nullptr.
+ already_AddRefed<nsIWebBrowserChrome> GetWebBrowserChrome();
+ already_AddRefed<nsIBaseWindow> GetOwnerWin();
+ already_AddRefed<nsIInterfaceRequestor> GetOwnerRequestor();
+
+ protected:
+ // Weak References
+ nsWebBrowser* mWebBrowser;
+ nsIDocShellTreeOwner* mTreeOwner;
+ nsIDocShellTreeItem* mPrimaryContentShell;
+
+ nsIWebBrowserChrome* mWebBrowserChrome;
+ nsIBaseWindow* mOwnerWin;
+ nsIInterfaceRequestor* mOwnerRequestor;
+
+ nsWeakPtr mWebBrowserChromeWeak; // nsIWebBrowserChrome
+
+ // the objects that listen for chrome events like context menus and tooltips.
+ // They are separate objects to avoid circular references between |this|
+ // and the DOM.
+ RefPtr<ChromeTooltipListener> mChromeTooltipListener;
+
+ RefPtr<nsDocShellTreeOwner> mContentTreeOwner;
+
+ nsCOMPtr<nsIPrompt> mPrompter;
+ nsCOMPtr<nsIAuthPrompt> mAuthPrompter;
+ nsCOMPtr<nsIRemoteTab> mPrimaryRemoteTab;
+};
+
+#endif /* nsDocShellTreeOwner_h__ */
diff --git a/docshell/base/nsIDocShell.idl b/docshell/base/nsIDocShell.idl
new file mode 100644
index 0000000000..21f09a517e
--- /dev/null
+++ b/docshell/base/nsIDocShell.idl
@@ -0,0 +1,766 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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 "domstubs.idl"
+#include "nsIDocShellTreeItem.idl"
+#include "nsIRequest.idl"
+
+%{ C++
+#include "js/TypeDecls.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/NotNull.h"
+#include "mozilla/UniquePtr.h"
+#include "nsCOMPtr.h"
+#include "nsIURI.h"
+class nsCommandManager;
+class nsPresContext;
+class nsDocShellLoadState;
+namespace mozilla {
+class Encoding;
+class HTMLEditor;
+class PresShell;
+namespace dom {
+class BrowsingContext;
+class ClientSource;
+} // namespace dom
+}
+%}
+
+/**
+ * The nsIDocShell interface.
+ */
+
+[ptr] native nsPresContext(nsPresContext);
+[ptr] native nsCommandManager(nsCommandManager);
+[ptr] native PresShell(mozilla::PresShell);
+[ref] native MaybeURI(mozilla::Maybe<nsCOMPtr<nsIURI>>);
+[ref] native Encoding(const mozilla::Encoding*);
+ native UniqueClientSource(mozilla::UniquePtr<mozilla::dom::ClientSource>);
+
+interface nsIURI;
+interface nsIChannel;
+interface nsIContentSecurityPolicy;
+interface nsIDocumentViewer;
+interface nsIEditor;
+interface nsIEditingSession;
+interface nsIInputStream;
+interface nsIRequest;
+interface nsISHEntry;
+interface nsILayoutHistoryState;
+interface nsISecureBrowserUI;
+interface nsIScriptGlobalObject;
+interface nsIStructuredCloneContainer;
+interface nsIDOMStorage;
+interface nsIPrincipal;
+interface nsIPrivacyTransitionObserver;
+interface nsIReflowObserver;
+interface nsIScrollObserver;
+interface nsIRemoteTab;
+interface nsIBrowserChild;
+interface nsICommandParams;
+interface nsILoadURIDelegate;
+native BrowserChildRef(already_AddRefed<nsIBrowserChild>);
+native nsDocShellLoadStatePtr(nsDocShellLoadState*);
+
+webidl BrowsingContext;
+webidl ContentFrameMessageManager;
+webidl EventTarget;
+webidl Document;
+
+/**
+ * nsIDocShell is an interface corresponding to the native nsDocShell object,
+ * which is a legacy in-process object roughly corresponding to a 'browsing
+ * context', as created for a browser tab or an iframe, for example.
+ *
+ * nsIDocShell has a 1:1 relationship with its paired dom::BrowsingContext and
+ * nsGlobalWindowOuter. It may be replaced during navigation.
+ *
+ * See also the comment documenting dom::BrowsingContext and the documentation
+ * at:
+ *
+ * https://html.spec.whatwg.org/multipage/document-sequences.html#browsing-context
+ * https://firefox-source-docs.mozilla.org/dom/navigation/embedding.html
+ * https://firefox-source-docs.mozilla.org/dom/navigation/nav_replace.html
+ */
+[scriptable, builtinclass, uuid(049234fe-da10-478b-bc5d-bc6f9a1ba63d)]
+interface nsIDocShell : nsIDocShellTreeItem
+{
+ void setCancelContentJSEpoch(in long aEpoch);
+
+ /**
+ * Loads a given URI. This will give priority to loading the requested URI
+ * in the object implementing this interface. If it can't be loaded here
+ * however, the URL dispatcher will go through its normal process of content
+ * loading.
+ *
+ * @param aLoadState This is the extended load info for this load.
+ * @param aSetNavigating If we should set isNavigating to true while initiating
+ * the load.
+ */
+ [noscript]void loadURI(in nsDocShellLoadStatePtr aLoadState, in boolean aSetNavigating);
+
+ /**
+ * Do either a history.pushState() or history.replaceState() operation,
+ * depending on the value of aReplace.
+ */
+ [implicit_jscontext]
+ void addState(in jsval aData, in AString aTitle,
+ in AString aURL, in boolean aReplace);
+
+ /**
+ * 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().
+ */
+ void prepareForNewContentModel();
+
+ /**
+ * Helper for the session store to change the URI associated with the
+ * document.
+ */
+ void setCurrentURIForSessionStore(in nsIURI aURI);
+
+ /**
+ * Notify the associated content viewer and all child docshells that they are
+ * about to be hidden. If |isUnload| is true, then the document is being
+ * unloaded and all dynamic subframe history entries are removed as well.
+ *
+ * @param isUnload
+ * True to fire the unload event in addition to the pagehide event,
+ * and remove all dynamic subframe history entries.
+ */
+ [noscript] void firePageHideNotification(in boolean isUnload);
+
+ /**
+ * Presentation context for the currently loaded document. This may be null.
+ */
+ [notxpcom,nostdcall] readonly attribute nsPresContext presContext;
+
+ /**
+ * Presentation shell for the currently loaded document. This may be null.
+ */
+ [notxpcom,nostdcall] readonly attribute PresShell presShell;
+
+ /**
+ * Presentation shell for the oldest document, if this docshell is
+ * currently transitioning between documents.
+ */
+ [notxpcom,nostdcall] readonly attribute PresShell eldestPresShell;
+
+ /**
+ * Document Viewer that is currently loaded for this DocShell. This may
+ * change as the underlying content changes.
+ */
+ [infallible] readonly attribute nsIDocumentViewer docViewer;
+
+ /**
+ * Get the id of the outer window that is or will be in this docshell.
+ */
+ [infallible] readonly attribute unsigned long long outerWindowID;
+
+ /**
+ * This attribute allows chrome to tie in to handle DOM events that may
+ * be of interest to chrome.
+ */
+ attribute EventTarget chromeEventHandler;
+
+ /**
+ * This allows chrome to set a custom User agent on a specific docshell
+ */
+ attribute AString customUserAgent;
+
+ /**
+ * Whether CSS error reporting is enabled.
+ */
+ attribute boolean cssErrorReportingEnabled;
+
+ /**
+ * Attribute stating if refresh based redirects can be allowed
+ */
+ attribute boolean allowMetaRedirects;
+
+ /**
+ * Attribute stating if it should allow subframes (framesets/iframes) or not
+ */
+ attribute boolean allowSubframes;
+
+ /**
+ * Attribute stating whether or not images should be loaded.
+ */
+ attribute boolean allowImages;
+
+ /**
+ * Attribute stating whether or not media (audio/video) should be loaded.
+ */
+ [infallible] attribute boolean allowMedia;
+
+ /**
+ * Attribute that determines whether DNS prefetch is allowed for this subtree
+ * of the docshell tree. Defaults to true. Setting this will make it take
+ * effect starting with the next document loaded in the docshell.
+ */
+ attribute boolean allowDNSPrefetch;
+
+ /**
+ * Attribute that determines whether window control (move/resize) is allowed.
+ */
+ attribute boolean allowWindowControl;
+
+ /**
+ * True if the docshell allows its content to be handled by a content listener
+ * other than the docshell itself, including the external helper app service,
+ * and false otherwise. Defaults to true.
+ */
+ [infallible] attribute boolean allowContentRetargeting;
+
+ /**
+ * True if new child docshells should allow content retargeting.
+ * Setting allowContentRetargeting also overwrites this value.
+ */
+ [infallible] attribute boolean allowContentRetargetingOnChildren;
+
+ /**
+ * Get an array of this docShell and its children.
+ *
+ * @param aItemType - Only include docShells of this type, or if typeAll,
+ * include all child shells.
+ * Uses types from nsIDocShellTreeItem.
+ * @param aDirection - Whether to enumerate forwards or backwards.
+ */
+
+ cenum DocShellEnumeratorDirection : 8 {
+ ENUMERATE_FORWARDS = 0,
+ ENUMERATE_BACKWARDS = 1
+ };
+
+ Array<nsIDocShell> getAllDocShellsInSubtree(in long aItemType,
+ in nsIDocShell_DocShellEnumeratorDirection aDirection);
+
+ /**
+ * The type of application that created this window.
+ *
+ * DO NOT DELETE, see bug 176166. For firefox, this value will always be
+ * UNKNOWN. However, it is used heavily in Thunderbird/comm-central and we
+ * don't really have a great replacement at the moment, so we'll just leave it
+ * here.
+ */
+ cenum AppType : 8 {
+ APP_TYPE_UNKNOWN = 0,
+ APP_TYPE_MAIL = 1,
+ APP_TYPE_EDITOR = 2
+ };
+
+ [infallible] attribute nsIDocShell_AppType appType;
+
+ /**
+ * certain docshells (like the message pane)
+ * should not throw up auth dialogs
+ * because it can act as a password trojan
+ */
+ attribute boolean allowAuth;
+
+ /**
+ * Set/Get the document scale factor. When setting this attribute, a
+ * NS_ERROR_NOT_IMPLEMENTED error may be returned by implementations
+ * not supporting zoom. Implementations not supporting zoom should return
+ * 1.0 all the time for the Get operation. 1.0 by the way is the default
+ * of zoom. This means 100% of normal scaling or in other words normal size
+ * no zoom.
+ */
+ attribute float zoom;
+
+ /*
+ * Tells the docshell to offer focus to its tree owner.
+ * This is currently only necessary for embedding chrome.
+ * If forDocumentNavigation is true, then document navigation should be
+ * performed, where only the root of documents are selected. Otherwise, the
+ * next element in the parent should be returned. Returns true if focus was
+ * successfully taken by the tree owner.
+ */
+ bool tabToTreeOwner(in boolean forward, in boolean forDocumentNavigation);
+
+ /**
+ * Current busy state for DocShell
+ */
+ cenum BusyFlags : 8 {
+ BUSY_FLAGS_NONE = 0,
+ BUSY_FLAGS_BUSY = 1,
+ BUSY_FLAGS_BEFORE_PAGE_LOAD = 2,
+ BUSY_FLAGS_PAGE_LOADING = 4,
+ };
+
+ [infallible] readonly attribute nsIDocShell_BusyFlags busyFlags;
+
+ /**
+ * Load commands for the document
+ */
+ cenum LoadCommand : 8 {
+ LOAD_CMD_NORMAL = 0x1, // Normal load
+ LOAD_CMD_RELOAD = 0x2, // Reload
+ LOAD_CMD_HISTORY = 0x4, // Load from history
+ LOAD_CMD_PUSHSTATE = 0x8, // History.pushState()
+ };
+
+ /*
+ * Attribute to access the loadtype for the document. LoadType Enum is
+ * defined in nsDocShellLoadTypes.h
+ */
+ [infallible] attribute unsigned long loadType;
+
+ /*
+ * Default load flags (as defined in nsIRequest) that will be set on all
+ * requests made by this docShell and propagated to all child docShells and
+ * to nsILoadGroup::defaultLoadFlags for the docShell's loadGroup.
+ * Default is no flags. Once set, only future requests initiated by the
+ * docShell are affected, so in general, these flags should be set before
+ * the docShell loads any content.
+ */
+ attribute nsLoadFlags defaultLoadFlags;
+
+ /*
+ * returns true if the docshell is being destroyed, false otherwise
+ */
+ boolean isBeingDestroyed();
+
+ /*
+ * Returns true if the docshell is currently executing the onLoad Handler
+ */
+ readonly attribute boolean isExecutingOnLoadHandler;
+
+ attribute nsILayoutHistoryState layoutHistoryState;
+
+ /**
+ * Object used to delegate URI loading to an upper context.
+ * Currently only set for GeckoView to allow handling of load requests
+ * at the application level.
+ */
+ readonly attribute nsILoadURIDelegate loadURIDelegate;
+
+ /**
+ * Cancel the XPCOM timers for each meta-refresh URI in this docshell,
+ * and this docshell's children, recursively. The meta-refresh timers can be
+ * restarted using resumeRefreshURIs(). If the timers are already suspended,
+ * this has no effect.
+ */
+ void suspendRefreshURIs();
+
+ /**
+ * Restart the XPCOM timers for each meta-refresh URI in this docshell,
+ * and this docshell's children, recursively. If the timers are already
+ * running, this has no effect.
+ */
+ void resumeRefreshURIs();
+
+ /**
+ * Begin firing WebProgressListener notifications for restoring a page
+ * presentation. |viewer| is the content viewer whose document we are
+ * starting to load. If null, it defaults to the docshell's current content
+ * viewer, creating one if necessary. |top| should be true for the toplevel
+ * docshell that is being restored; it will be set to false when this method
+ * is called for child docshells. This method will post an event to
+ * complete the simulated load after returning to the event loop.
+ */
+ void beginRestore(in nsIDocumentViewer viewer, in boolean top);
+
+ /**
+ * Finish firing WebProgressListener notifications and DOM events for
+ * restoring a page presentation. This should only be called via
+ * beginRestore().
+ */
+ void finishRestore();
+
+ void clearCachedUserAgent();
+
+ void clearCachedPlatform();
+
+ /* Track whether we're currently restoring a document presentation. */
+ readonly attribute boolean restoringDocument;
+
+ /* attribute to access whether error pages are enabled */
+ attribute boolean useErrorPages;
+
+ /**
+ * Display a load error in a frame while keeping that frame's currentURI
+ * pointing correctly to the page where the error ocurred, rather than to
+ * the error document page. You must provide either the aURI or aURL parameter.
+ *
+ * @param aError The error code to be displayed
+ * @param aURI nsIURI of the page where the error happened
+ * @param aURL wstring of the page where the error happened
+ * @param aFailedChannel The channel related to this error
+ *
+ * Returns whether or not we displayed an error page (note: will always
+ * return false if in-content error pages are disabled!)
+ */
+ boolean displayLoadError(in nsresult aError,
+ in nsIURI aURI,
+ in wstring aURL,
+ [optional] in nsIChannel aFailedChannel);
+
+ /**
+ * The channel that failed to load and resulted in an error page.
+ * May be null. Relevant only to error pages.
+ */
+ readonly attribute nsIChannel failedChannel;
+
+ /**
+ * Keeps track of the previous nsISHEntry index and the current
+ * nsISHEntry index at the time that the doc shell begins to load.
+ * Used for ContentViewer eviction.
+ */
+ readonly attribute long previousEntryIndex;
+ readonly attribute long loadedEntryIndex;
+
+ /**
+ * Notification that entries have been removed from the beginning of a
+ * nsSHistory which has this as its rootDocShell.
+ *
+ * @param numEntries - The number of entries removed
+ */
+ void historyPurged(in long numEntries);
+
+ /**
+ * Gets the channel for the currently loaded document, if any.
+ * For a new document load, this will be the channel of the previous document
+ * until after OnLocationChange fires.
+ */
+ readonly attribute nsIChannel currentDocumentChannel;
+
+ /**
+ * Find out whether the docshell is currently in the middle of a page
+ * transition. This is set just before the pagehide/unload events fire.
+ */
+ [infallible] readonly attribute boolean isInUnload;
+
+ /**
+ * Disconnects this docshell's editor from its window, and stores the
+ * editor data in the open document's session history entry. This
+ * should be called only during page transitions.
+ */
+ [noscript, notxpcom] void DetachEditorFromWindow();
+
+ /**
+ * Propagated to the print preview document viewer. Must only be called on
+ * a document viewer that has been initialized for print preview.
+ */
+ void exitPrintPreview();
+
+ /**
+ * The ID of the docshell in the session history.
+ */
+ readonly attribute nsIDRef historyID;
+
+ /**
+ * Helper method for accessing this value from C++
+ */
+ [noscript, notxpcom] nsIDRef HistoryID();
+
+ /**
+ * Create a new about:blank document and content viewer.
+ * @param aPrincipal the principal to use for the new document.
+ * @param aPartitionedPrincipal the partitioned principal to use for the new
+ * document.
+ * @param aCsp the CSP to use for the new document.
+ */
+ void createAboutBlankDocumentViewer(in nsIPrincipal aPrincipal,
+ in nsIPrincipal aPartitionedPrincipal,
+ [optional] in nsIContentSecurityPolicy aCSP);
+
+ /**
+ * Upon getting, returns the canonical encoding label of the document
+ * currently loaded into this docshell.
+ */
+ readonly attribute ACString charset;
+
+ void forceEncodingDetection();
+
+ /**
+ * In a child docshell, this is the charset of the parent docshell
+ */
+ [noscript, notxpcom, nostdcall] void setParentCharset(
+ in Encoding parentCharset,
+ in int32_t parentCharsetSource,
+ in nsIPrincipal parentCharsetPrincipal);
+ [noscript, notxpcom, nostdcall] void getParentCharset(
+ out Encoding parentCharset,
+ out int32_t parentCharsetSource,
+ out nsIPrincipal parentCharsetPrincipal);
+
+ /**
+ * Return a DOMHighResTimeStamp representing the number of
+ * milliseconds from an arbitrary point in time. The reference
+ * point is shared by all DocShells and is also used by timestamps
+ * on markers.
+ */
+ DOMHighResTimeStamp now();
+
+ /**
+ * Add an observer to the list of parties to be notified when this docshell's
+ * private browsing status is changed. |obs| must support weak references.
+ */
+ void addWeakPrivacyTransitionObserver(in nsIPrivacyTransitionObserver obs);
+
+ /**
+ * Add an observer to the list of parties to be notified when reflows are
+ * occurring. |obs| must support weak references.
+ */
+ void addWeakReflowObserver(in nsIReflowObserver obs);
+
+ /**
+ * Remove an observer from the list of parties to be notified about reflows.
+ */
+ void removeWeakReflowObserver(in nsIReflowObserver obs);
+
+ /**
+ * Notify all attached observers that a reflow has just occurred.
+ *
+ * @param interruptible if true, the reflow was interruptible.
+ * @param start timestamp when reflow started, in milliseconds since
+ * navigationStart (accurate to 1/1000 of a ms)
+ * @param end timestamp when reflow ended, in milliseconds since
+ * navigationStart (accurate to 1/1000 of a ms)
+ */
+ [noscript] void notifyReflowObservers(in bool interruptible,
+ in DOMHighResTimeStamp start,
+ in DOMHighResTimeStamp end);
+
+ /**
+ * Add an observer to the list of parties to be notified when scroll position
+ * of some elements is changed.
+ */
+ [noscript] void addWeakScrollObserver(in nsIScrollObserver obs);
+
+ /**
+ * Add an observer to the list of parties to be notified when scroll position
+ * of some elements is changed.
+ */
+ [noscript] void removeWeakScrollObserver(in nsIScrollObserver obs);
+
+ /**
+ * Notify all attached observers that the scroll position of some element
+ * has changed.
+ */
+ [noscript] void notifyScrollObservers();
+
+ /**
+ * Returns true if this docshell is the top level content docshell.
+ */
+ [infallible] readonly attribute boolean isTopLevelContentDocShell;
+
+ /**
+ * True iff asynchronous panning and zooming is enabled for this
+ * docshell.
+ */
+ readonly attribute bool asyncPanZoomEnabled;
+
+ /**
+ * Indicates whether the UI may enable the character encoding menu. The UI
+ * must disable the menu when this property is false.
+ */
+ [infallible] readonly attribute boolean mayEnableCharacterEncodingMenu;
+
+ attribute nsIEditor editor;
+ readonly attribute boolean editable; /* this docShell is editable */
+ readonly attribute boolean hasEditingSession; /* this docShell has an editing session */
+
+ /**
+ * Make this docShell editable, setting a flag that causes
+ * an editor to get created, either immediately, or after
+ * a url has been loaded.
+ * @param inWaitForUriLoad true to wait for a URI before
+ * creating the editor.
+ */
+ void makeEditable(in boolean inWaitForUriLoad);
+
+ /**
+ * Returns false for mLSHE, true for mOSHE
+ */
+ boolean getCurrentSHEntry(out nsISHEntry aEntry);
+
+ /**
+ * Cherry picked parts of nsIController.
+ * They are here, because we want to call these functions
+ * from JS.
+ */
+ boolean isCommandEnabled(in string command);
+ [can_run_script]
+ void doCommand(in string command);
+ [can_run_script]
+ void doCommandWithParams(in string command, in nsICommandParams aParams);
+
+ /**
+ * Invisible DocShell are dummy construct to simulate DOM windows
+ * without any actual visual representation. They have to be marked
+ * at construction time, to avoid any painting activity.
+ */
+ [noscript, notxpcom] bool IsInvisible();
+ [noscript, notxpcom] void SetInvisible(in bool aIsInvisibleDocshell);
+
+/**
+ * Get the script global for the document in this docshell.
+*/
+ [noscript,notxpcom,nostdcall] nsIScriptGlobalObject GetScriptGlobalObject();
+
+ [noscript,notxpcom,nostdcall] Document getExtantDocument();
+
+ /**
+ * This attribute determines whether a document which is not about:blank has
+ * already be loaded by this docShell.
+ */
+ [infallible] readonly attribute boolean hasLoadedNonBlankURI;
+
+ /**
+ * Allow usage of -moz-window-dragging:drag for content docshells.
+ * True for top level chrome docshells. Throws if set to false with
+ * top level chrome docshell.
+ */
+ attribute boolean windowDraggingAllowed;
+
+ /**
+ * Sets/gets the current scroll restoration mode.
+ * @see https://html.spec.whatwg.org/#dom-history-scroll-restoration
+ */
+ attribute boolean currentScrollRestorationIsManual;
+
+ /**
+ * Setter and getter for the origin attributes living on this docshell.
+ */
+ [implicit_jscontext]
+ jsval getOriginAttributes();
+
+ [implicit_jscontext]
+ void setOriginAttributes(in jsval aAttrs);
+
+ /**
+ * The editing session for this docshell.
+ */
+ readonly attribute nsIEditingSession editingSession;
+
+ /**
+ * The browser child for this docshell.
+ */
+ [binaryname(ScriptableBrowserChild)] readonly attribute nsIBrowserChild browserChild;
+ [noscript,notxpcom,nostdcall] BrowserChildRef GetBrowserChild();
+
+ [noscript,nostdcall,notxpcom] nsCommandManager GetCommandManager();
+
+ cenum MetaViewportOverride: 8 {
+ /**
+ * Override platform/pref default behaviour and force-disable support for
+ * <meta name="viewport">.
+ */
+ META_VIEWPORT_OVERRIDE_DISABLED = 0,
+ /**
+ * Override platform/pref default behaviour and force-enable support for
+ * <meta name="viewport">.
+ */
+ META_VIEWPORT_OVERRIDE_ENABLED = 1,
+ /**
+ * Don't override the platform/pref default behaviour for support for
+ * <meta name="viewport">.
+ */
+ META_VIEWPORT_OVERRIDE_NONE = 2,
+ };
+
+ /**
+ * This allows chrome to override the default choice of whether the
+ * <meta name="viewport"> tag is respected in a specific docshell.
+ * Possible values are listed above.
+ */
+ [infallible, setter_can_run_script] attribute nsIDocShell_MetaViewportOverride metaViewportOverride;
+
+ /**
+ * Attribute that determines whether tracking protection is enabled.
+ */
+ attribute boolean useTrackingProtection;
+
+ /**
+ * Fire a dummy location change event asynchronously.
+ */
+ [noscript] void dispatchLocationChangeEvent();
+
+
+ /**
+ * Start delayed autoplay media which are in the current document.
+ */
+ [noscript] void startDelayedAutoplayMediaComponents();
+
+ /**
+ * Take ownership of the ClientSource representing an initial about:blank
+ * document that was never needed. As an optimization we avoid creating
+ * this document if no code calls GetDocument(), but we still need a
+ * ClientSource object to represent the about:blank window. This may return
+ * nullptr; for example if the docshell has created a real window and document
+ * already.
+ */
+ [noscript, nostdcall, notxpcom]
+ UniqueClientSource TakeInitialClientSource();
+
+ void setColorMatrix(in Array<float> aMatrix);
+
+ /**
+ * Returns true if the current load is a forced reload,
+ * e.g. started by holding shift whilst triggering reload.
+ */
+ readonly attribute bool isForceReloading;
+
+ Array<float> getColorMatrix();
+
+%{C++
+ /**
+ * These methods call nsDocShell::GetHTMLEditorInternal() and
+ * nsDocShell::SetHTMLEditorInternal() with static_cast.
+ */
+ mozilla::HTMLEditor* GetHTMLEditor();
+ nsresult SetHTMLEditor(mozilla::HTMLEditor* aHTMLEditor);
+%}
+
+ /**
+ * The message manager for this docshell. This does not throw, but
+ * can return null if the docshell has no message manager.
+ */
+ [infallible] readonly attribute ContentFrameMessageManager messageManager;
+
+ /**
+ * This returns a Promise which resolves to a boolean. True when the
+ * document has Tracking Content that has been blocked from loading, false
+ * otherwise.
+ */
+ Promise getHasTrackingContentBlocked();
+
+ /**
+ * Return whether this docshell is "attempting to navigate" in the
+ * sense that's relevant to document.open.
+ */
+ [notxpcom, nostdcall] readonly attribute boolean isAttemptingToNavigate;
+
+ /*
+ * Whether or not this docshell is executing a nsIWebNavigation navigation
+ * method.
+ *
+ * This will be true when the following methods are executing:
+ * nsIWebNavigation.binaryLoadURI
+ * nsIWebNavigation.goBack
+ * nsIWebNavigation.goForward
+ * nsIWebNavigation.gotoIndex
+ * nsIWebNavigation.loadURI
+ */
+ [infallible] readonly attribute boolean isNavigating;
+
+ /**
+ * @see nsISHEntry synchronizeLayoutHistoryState().
+ */
+ void synchronizeLayoutHistoryState();
+
+ /**
+ * This attempts to save any applicable layout history state (like
+ * scroll position) in the nsISHEntry. This is normally done
+ * automatically when transitioning from page to page in the
+ * same process. We expose this function to support transitioning
+ * from page to page across processes as a workaround for bug 1630234
+ * until session history state is moved into the parent process.
+ */
+ void persistLayoutHistoryState();
+};
diff --git a/docshell/base/nsIDocShellTreeItem.idl b/docshell/base/nsIDocShellTreeItem.idl
new file mode 100644
index 0000000000..a80373832f
--- /dev/null
+++ b/docshell/base/nsIDocShellTreeItem.idl
@@ -0,0 +1,171 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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 "nsISupports.idl"
+
+interface mozIDOMWindowProxy;
+interface nsIDocShellTreeOwner;
+interface nsPIDOMWindowOuter;
+
+webidl Document;
+webidl BrowsingContext;
+
+/**
+ * The nsIDocShellTreeItem supplies the methods that are required of any item
+ * that wishes to be able to live within the docshell tree either as a middle
+ * node or a leaf.
+ */
+
+[scriptable, builtinclass, uuid(9b7c586f-9214-480c-a2c4-49b526fff1a6)]
+interface nsIDocShellTreeItem : nsISupports
+{
+ /*
+ name of the DocShellTreeItem
+ */
+ attribute AString name;
+
+ /**
+ * Compares the provided name against the item's name and
+ * returns the appropriate result.
+ *
+ * @return <CODE>PR_TRUE</CODE> if names match;
+ * <CODE>PR_FALSE</CODE> otherwise.
+ */
+ boolean nameEquals(in AString name);
+
+ /*
+ Definitions for the item types.
+ */
+ const long typeChrome=0; // typeChrome must equal 0
+ const long typeContent=1; // typeContent must equal 1
+ const long typeContentWrapper=2; // typeContentWrapper must equal 2
+ const long typeChromeWrapper=3; // typeChromeWrapper must equal 3
+
+ const long typeAll=0x7FFFFFFF;
+
+ /*
+ The type this item is.
+ */
+ readonly attribute long itemType;
+ [noscript,notxpcom,nostdcall] long ItemType();
+
+ /*
+ Parent DocShell.
+
+ @deprecated: Use `BrowsingContext::GetParent()` instead.
+ (NOTE: `BrowsingContext::GetParent()` will not cross isolation boundaries)
+ */
+ [binaryname(InProcessParent)]
+ readonly attribute nsIDocShellTreeItem parent;
+
+ /*
+ This getter returns the same thing parent does however if the parent
+ is of a different itemType, or if the parent is an <iframe mozbrowser>.
+ It will instead return nullptr. This call is a convience function for
+ Ithose wishing to not cross the boundaries at which item types change.
+
+ @deprecated: Use `BrowsingContext::GetParent()` instead.
+ */
+ [binaryname(InProcessSameTypeParent)]
+ readonly attribute nsIDocShellTreeItem sameTypeParent;
+
+ /*
+ Returns the root DocShellTreeItem. This is a convience equivalent to
+ getting the parent and its parent until there isn't a parent.
+
+ @deprecated: Use `BrowsingContext::Top()` instead.
+ (NOTE: `BrowsingContext::Top()` will not cross isolation boundaries)
+ */
+ [binaryname(InProcessRootTreeItem)]
+ readonly attribute nsIDocShellTreeItem rootTreeItem;
+
+ /*
+ Returns the root DocShellTreeItem of the same type. This is a convience
+ equivalent to getting the parent of the same type and its parent until
+ there isn't a parent.
+
+ @deprecated: Use `BrowsingContext::Top()` instead.
+ */
+ [binaryname(InProcessSameTypeRootTreeItem)]
+ readonly attribute nsIDocShellTreeItem sameTypeRootTreeItem;
+
+ /*
+ The owner of the DocShell Tree. This interface will be called upon when
+ the docshell has things it needs to tell to the owner of the docshell.
+ Note that docShell tree ownership does not cross tree types. Meaning
+ setting ownership on a chrome tree does not set ownership on the content
+ sub-trees. A given tree's boundaries are identified by the type changes.
+ Trees of different types may be connected, but should not be traversed
+ for things such as ownership.
+
+ Note implementers of this interface should NOT effect the lifetime of the
+ parent DocShell by holding this reference as it creates a cycle. Owners
+ when releasing this interface should set the treeOwner to nullptr.
+ Implementers of this interface are guaranteed that when treeOwner is
+ set that the poitner is valid without having to addref.
+
+ Further note however when others try to get the interface it should be
+ addref'd before handing it to them.
+ */
+ readonly attribute nsIDocShellTreeOwner treeOwner;
+ [noscript] void setTreeOwner(in nsIDocShellTreeOwner treeOwner);
+
+ /*
+ The current number of DocShells which are immediate children of the
+ this object.
+
+
+ @deprecated: Prefer using `BrowsingContext::Children()`, as this count will
+ not include out-of-process iframes.
+ */
+ [binaryname(InProcessChildCount), infallible]
+ readonly attribute long childCount;
+
+ /*
+ Add a new child DocShellTreeItem. Adds to the end of the list.
+ Note that this does NOT take a reference to the child. The child stays
+ alive only as long as it's referenced from outside the docshell tree.
+
+ @throws NS_ERROR_ILLEGAL_VALUE if child corresponds to the same
+ object as this treenode or an ancestor of this treenode
+ @throws NS_ERROR_UNEXPECTED if this node is a leaf in the tree.
+ */
+ [noscript] void addChild(in nsIDocShellTreeItem child);
+
+ /*
+ Removes a child DocShellTreeItem.
+
+ @throws NS_ERROR_UNEXPECTED if this node is a leaf in the tree.
+ */
+ [noscript] void removeChild(in nsIDocShellTreeItem child);
+
+ /**
+ * Return the child at the index requested. This is 0-based.
+ *
+ * @deprecated: Prefer using `BrowsingContext::Children()`, as this will not
+ * include out-of-process iframes.
+ *
+ * @throws NS_ERROR_UNEXPECTED if the index is out of range
+ */
+ [binaryname(GetInProcessChildAt)]
+ nsIDocShellTreeItem getChildAt(in long index);
+
+ /**
+ * BrowsingContext associated with the DocShell.
+ */
+ [binaryname(BrowsingContextXPCOM)]
+ readonly attribute BrowsingContext browsingContext;
+
+ [noscript,notxpcom,nostdcall] BrowsingContext getBrowsingContext();
+
+ /**
+ * Returns the DOM outer window for the content viewer.
+ */
+ readonly attribute mozIDOMWindowProxy domWindow;
+
+ [noscript,nostdcall,notxpcom] Document getDocument();
+ [noscript,nostdcall,notxpcom] nsPIDOMWindowOuter getWindow();
+};
diff --git a/docshell/base/nsIDocShellTreeOwner.idl b/docshell/base/nsIDocShellTreeOwner.idl
new file mode 100644
index 0000000000..db6faa59c9
--- /dev/null
+++ b/docshell/base/nsIDocShellTreeOwner.idl
@@ -0,0 +1,113 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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 "nsISupports.idl"
+
+/**
+ * The nsIDocShellTreeOwner
+ */
+
+interface nsIDocShellTreeItem;
+interface nsIRemoteTab;
+webidl BrowsingContext;
+
+[scriptable, uuid(0e3dc4b1-4cea-4a37-af71-79f0afd07574)]
+interface nsIDocShellTreeOwner : nsISupports
+{
+ /**
+ * Called when a content shell is added to the docshell tree. This is
+ * _only_ called for "root" content shells (that is, ones whose parent is a
+ * chrome shell).
+ *
+ * @param aContentShell the shell being added.
+ * @param aPrimary whether the shell is primary.
+ */
+ void contentShellAdded(in nsIDocShellTreeItem aContentShell,
+ in boolean aPrimary);
+
+ /**
+ * Called when a content shell is removed from the docshell tree. This is
+ * _only_ called for "root" content shells (that is, ones whose parent is a
+ * chrome shell). Note that if aContentShell was never added,
+ * contentShellRemoved should just do nothing.
+ *
+ * @param aContentShell the shell being removed.
+ */
+ void contentShellRemoved(in nsIDocShellTreeItem aContentShell);
+
+ /*
+ Returns the Primary Content Shell
+ */
+ readonly attribute nsIDocShellTreeItem primaryContentShell;
+
+ void remoteTabAdded(in nsIRemoteTab aTab, in boolean aPrimary);
+ void remoteTabRemoved(in nsIRemoteTab aTab);
+
+ /*
+ In multiprocess case we may not have primaryContentShell but
+ primaryRemoteTab.
+ */
+ readonly attribute nsIRemoteTab primaryRemoteTab;
+
+ /*
+ Get the BrowsingContext associated with either the primary content shell or
+ primary remote tab, depending on which is available.
+ */
+ readonly attribute BrowsingContext primaryContentBrowsingContext;
+
+ /*
+ Tells the tree owner to size its window or parent window in such a way
+ that the shell passed along will be the size specified.
+ */
+ [can_run_script]
+ void sizeShellTo(in nsIDocShellTreeItem shell, in long cx, in long cy);
+
+ /*
+ Gets the size of the primary content area in device pixels. This should work
+ for both in-process and out-of-process content areas.
+ */
+ void getPrimaryContentSize(out long width, out long height);
+ /*
+ Sets the size of the primary content area in device pixels. This should work
+ for both in-process and out-of-process content areas.
+ */
+ void setPrimaryContentSize(in long width, in long height);
+
+ /*
+ Gets the size of the root docshell in device pixels.
+ */
+ void getRootShellSize(out long width, out long height);
+ /*
+ Sets the size of the root docshell in device pixels.
+ */
+ void setRootShellSize(in long width, in long height);
+
+ /*
+ Sets the persistence of different attributes of the window.
+ */
+ void setPersistence(in boolean aPersistPosition,
+ in boolean aPersistSize,
+ in boolean aPersistSizeMode);
+
+ /*
+ Gets the current persistence states of the window.
+ */
+ void getPersistence(out boolean aPersistPosition,
+ out boolean aPersistSize,
+ out boolean aPersistSizeMode);
+
+ /*
+ Gets the number of tabs currently open in our window, assuming
+ this tree owner has such a concept.
+ */
+ readonly attribute unsigned long tabCount;
+
+ /*
+ Returns true if there is a primary content shell or a primary
+ remote tab.
+ */
+ readonly attribute bool hasPrimaryContent;
+};
diff --git a/docshell/base/nsIDocumentLoaderFactory.idl b/docshell/base/nsIDocumentLoaderFactory.idl
new file mode 100644
index 0000000000..e3df2b2241
--- /dev/null
+++ b/docshell/base/nsIDocumentLoaderFactory.idl
@@ -0,0 +1,39 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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 "nsISupports.idl"
+
+interface nsIChannel;
+interface nsIDocumentViewer;
+interface nsIStreamListener;
+interface nsIDocShell;
+interface nsILoadGroup;
+interface nsIPrincipal;
+
+webidl Document;
+
+/**
+ * To get a component that implements nsIDocumentLoaderFactory
+ * for a given mimetype, use nsICategoryManager to find an entry
+ * with the mimetype as its name in the category "Gecko-Content-Viewers".
+ * The value of the entry is the contractid of the component.
+ * The component is a service, so use GetService, not CreateInstance to get it.
+ */
+
+[scriptable, uuid(e795239e-9d3c-47c4-b063-9e600fb3b287)]
+interface nsIDocumentLoaderFactory : nsISupports {
+ nsIDocumentViewer createInstance(in string aCommand,
+ in nsIChannel aChannel,
+ in nsILoadGroup aLoadGroup,
+ in ACString aContentType,
+ in nsIDocShell aContainer,
+ in nsISupports aExtraInfo,
+ out nsIStreamListener aDocListenerResult);
+
+ nsIDocumentViewer createInstanceForDocument(in nsISupports aContainer,
+ in Document aDocument,
+ in string aCommand);
+};
diff --git a/docshell/base/nsIDocumentViewer.idl b/docshell/base/nsIDocumentViewer.idl
new file mode 100644
index 0000000000..afbbdfc464
--- /dev/null
+++ b/docshell/base/nsIDocumentViewer.idl
@@ -0,0 +1,318 @@
+/* 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 "nsISupports.idl"
+
+interface nsIDocShell;
+interface nsISHEntry;
+interface nsIPrintSettings;
+webidl Document;
+webidl Node;
+
+%{ C++
+#include "mozilla/Maybe.h"
+#include "nsTArray.h"
+#include "nsRect.h"
+#include "Units.h"
+
+class nsIWidget;
+class nsPresContext;
+class nsView;
+class nsDOMNavigationTiming;
+namespace mozilla {
+class Encoding;
+class PresShell;
+namespace dom {
+class WindowGlobalChild;
+} // namespace dom
+namespace layout {
+class RemotePrintJobChild;
+} // namespace layout
+} // namespace mozilla
+%}
+
+[ptr] native nsIWidgetPtr(nsIWidget);
+[ref] native nsIntRectRef(nsIntRect);
+[ptr] native nsPresContextPtr(nsPresContext);
+[ptr] native nsViewPtr(nsView);
+[ptr] native nsDOMNavigationTimingPtr(nsDOMNavigationTiming);
+[ptr] native Encoding(const mozilla::Encoding);
+[ptr] native PresShellPtr(mozilla::PresShell);
+[ptr] native RemotePrintJobChildPtr(mozilla::layout::RemotePrintJobChild);
+[ptr] native WindowGlobalChildPtr(mozilla::dom::WindowGlobalChild);
+
+[scriptable, builtinclass, uuid(48118355-e9a5-4452-ab18-59cc426fb817)]
+interface nsIDocumentViewer : nsISupports
+{
+ [noscript] void init(in nsIWidgetPtr aParentWidget,
+ [const] in nsIntRectRef aBounds,
+ in WindowGlobalChildPtr aWindowActor);
+
+ attribute nsIDocShell container;
+
+ [noscript,notxpcom,nostdcall] void loadStart(in Document aDoc);
+ [can_run_script] void loadComplete(in nsresult aStatus);
+ [notxpcom,nostdcall] readonly attribute boolean loadCompleted;
+
+ [notxpcom,nostdcall] readonly attribute boolean isStopped;
+
+ /**
+ * aAction is passed to PermitUnload to indicate what action to take
+ * if a beforeunload handler wants to prompt the user.
+ *
+ * ePrompt: Prompt and return the user's choice (default).
+ * eDontPromptAndDontUnload: Don't prompt and return false (unload not permitted)
+ * if the document (or its children) asks us to prompt.
+ * eDontPromptAndUnload: Don't prompt and return true (unload permitted) no matter what.
+ *
+ * NOTE: Keep this in sync with PermitUnloadAction in WindowGlobalActors.webidl.
+ */
+ cenum PermitUnloadAction : 8 {
+ ePrompt = 0,
+ eDontPromptAndDontUnload = 1,
+ eDontPromptAndUnload = 2
+ };
+
+ /**
+ * The result of dispatching a "beforeunload" event. If `eAllowNavigation`,
+ * no "beforeunload" listener requested to prevent the navigation, or its
+ * request was ignored. If `eRequestBlockNavigation`, a listener did request
+ * to block the navigation, and the user should be prompted.
+ */
+ cenum PermitUnloadResult : 8 {
+ eAllowNavigation = 0,
+ eRequestBlockNavigation = 1,
+ };
+
+ /**
+ * Overload PermitUnload method for C++ consumers with no aPermitUnloadFlags
+ * argument.
+ */
+ %{C++
+ nsresult PermitUnload(bool* canUnload) {
+ return PermitUnload(ePrompt, canUnload);
+ }
+ %}
+
+ /**
+ * Checks if the document wants to prevent unloading by firing beforeunload on
+ * the document.
+ * The result is returned.
+ */
+ boolean permitUnload([optional] in nsIDocumentViewer_PermitUnloadAction aAction);
+
+ /**
+ * Exposes whether we're blocked in a call to permitUnload.
+ */
+ readonly attribute boolean inPermitUnload;
+
+ /**
+ * Dispatches the "beforeunload" event and returns the result, as documented
+ * in the `PermitUnloadResult` enum.
+ */
+ [noscript,nostdcall,notxpcom] nsIDocumentViewer_PermitUnloadResult dispatchBeforeUnload();
+
+ /**
+ * Exposes whether we're in the process of firing the beforeunload event.
+ * In this case, the corresponding docshell will not allow navigation.
+ */
+ readonly attribute boolean beforeUnloadFiring;
+
+ [can_run_script] void pageHide(in boolean isUnload);
+
+ /**
+ * All users of a content viewer are responsible for calling both
+ * close() and destroy(), in that order.
+ *
+ * close() should be called when the load of a new page for the next
+ * content viewer begins, and destroy() should be called when the next
+ * content viewer replaces this one.
+ *
+ * |historyEntry| sets the session history entry for the content viewer. If
+ * this is null, then Destroy() will be called on the document by close().
+ * If it is non-null, the document will not be destroyed, and the following
+ * actions will happen when destroy() is called (*):
+ * - Sanitize() will be called on the viewer's document
+ * - The content viewer will set the contentViewer property on the
+ * history entry, and release its reference (ownership reversal).
+ * - hide() will be called, and no further destruction will happen.
+ *
+ * (*) unless the document is currently being printed, in which case
+ * it will never be saved in session history.
+ *
+ */
+ void close(in nsISHEntry historyEntry);
+ void destroy();
+
+ void stop();
+
+ /**
+ * Returns the same thing as getDocument(), but for use from script
+ * only. C++ consumers should use getDocument().
+ */
+ readonly attribute Document DOMDocument;
+
+ /**
+ * Returns DOMDocument without addrefing.
+ */
+ [noscript,notxpcom,nostdcall] Document getDocument();
+
+ /**
+ * Allows setting the document.
+ */
+ [noscript,nostdcall] void setDocument(in Document aDocument);
+
+ [noscript] void getBounds(in nsIntRectRef aBounds);
+ [noscript] void setBounds([const] in nsIntRectRef aBounds);
+ /**
+ * The 'aFlags' argument to setBoundsWithFlags is a set of these bits.
+ */
+ const unsigned long eDelayResize = 1;
+ [noscript] void setBoundsWithFlags([const] in nsIntRectRef aBounds,
+ in unsigned long aFlags);
+
+ /**
+ * The previous content viewer, which has been |close|d but not
+ * |destroy|ed.
+ */
+ [notxpcom,nostdcall] attribute nsIDocumentViewer previousViewer;
+
+ void move(in long aX, in long aY);
+
+ void show();
+ void hide();
+
+ attribute boolean sticky;
+
+ /**
+ * Attach the content viewer to its DOM window and docshell.
+ * @param aState A state object that might be useful in attaching the DOM
+ * window.
+ * @param aSHEntry The history entry that the content viewer was stored in.
+ * The entry must have the docshells for all of the child
+ * documents stored in its child shell list.
+ */
+ void open(in nsISupports aState, in nsISHEntry aSHEntry);
+
+ /**
+ * Clears the current history entry. This is used if we need to clear out
+ * the saved presentation state.
+ */
+ void clearHistoryEntry();
+
+ /**
+ * Change the layout to view the document with page layout (like print preview), but
+ * dynamic and editable (like Galley layout).
+ */
+ void setPageModeForTesting(in boolean aPageMode,
+ in nsIPrintSettings aPrintSettings);
+
+ /**
+ * Sets the print settings for print / print-previewing a subdocument.
+ */
+ [can_run_script] void setPrintSettingsForSubdocument(in nsIPrintSettings aPrintSettings,
+ in RemotePrintJobChildPtr aRemotePrintJob);
+
+ /**
+ * Get the history entry that this viewer will save itself into when
+ * destroyed. Can return null
+ */
+ readonly attribute nsISHEntry historyEntry;
+
+ /**
+ * Indicates when we're in a state where content shouldn't be allowed to
+ * trigger a tab-modal prompt (as opposed to a window-modal prompt) because
+ * we're part way through some operation (eg beforeunload) that shouldn't be
+ * rentrant if the user closes the tab while the prompt is showing.
+ * See bug 613800.
+ */
+ readonly attribute boolean isTabModalPromptAllowed;
+
+ /**
+ * Returns whether this content viewer is in a hidden state.
+ *
+ * @note Only Gecko internal code should set the attribute!
+ */
+ attribute boolean isHidden;
+
+ // presShell can be null.
+ [notxpcom,nostdcall] readonly attribute PresShellPtr presShell;
+ // presContext can be null.
+ [notxpcom,nostdcall] readonly attribute nsPresContextPtr presContext;
+ // aDocument must not be null.
+ [noscript] void setDocumentInternal(in Document aDocument,
+ in boolean aForceReuseInnerWindow);
+ /**
+ * Find the view to use as the container view for MakeWindow. Returns
+ * null if this will be the root of a view manager hierarchy. In that
+ * case, if mParentWidget is null then this document should not even
+ * be displayed.
+ */
+ [noscript,notxpcom,nostdcall] nsViewPtr findContainerView();
+ /**
+ * Set collector for navigation timing data (load, unload events).
+ */
+ [noscript,notxpcom,nostdcall] void setNavigationTiming(in nsDOMNavigationTimingPtr aTiming);
+
+ /**
+ * The actual full zoom in effect, as modified by the device context.
+ * For a requested full zoom, the device context may choose a slightly
+ * different effectiveFullZoom to accomodate integer rounding of app units
+ * per dev pixel. This property returns the actual zoom amount in use,
+ * though it may not be good user experience to report that a requested zoom
+ * of 90% is actually 89.1%, for example. This value is provided primarily to
+ * support media queries of dppx values, because those queries are matched
+ * against the actual native device pixel ratio and the actual full zoom.
+ *
+ * You should only need this for testing.
+ */
+ readonly attribute float deviceFullZoomForTest;
+
+ /**
+ * Disable entire author style level (including HTML presentation hints),
+ * for this viewer but not any child viewers.
+ */
+ attribute boolean authorStyleDisabled;
+
+ /**
+ * Returns the preferred width and height of the content, constrained to the
+ * given maximum values. If either maxWidth or maxHeight is less than or
+ * equal to zero, that dimension is not constrained.
+ *
+ * If a pref width is provided, it is max'd with the min-content size.
+ *
+ * All input and output values are in CSS pixels.
+ */
+ void getContentSize(in long maxWidth,
+ in long maxHeight,
+ in long prefWidth,
+ out long width,
+ out long height);
+
+%{C++
+ mozilla::Maybe<mozilla::CSSIntSize> GetContentSize(int32_t aMaxWidth = 0, int32_t aMaxHeight = 0, int32_t aPrefWidth = 0) {
+ int32_t w = 0;
+ int32_t h = 0;
+ if (NS_SUCCEEDED(GetContentSize(aMaxWidth, aMaxHeight, aPrefWidth, &w, &h))) {
+ return mozilla::Some(mozilla::CSSIntSize(w, h));
+ }
+ return mozilla::Nothing();
+ }
+%}
+
+ [noscript, notxpcom] Encoding getReloadEncodingAndSource(out int32_t aSource);
+ [noscript, notxpcom] void setReloadEncodingAndSource(in Encoding aEncoding, in int32_t aSource);
+ [noscript, notxpcom] void forgetReloadEncoding();
+};
+
+%{C++
+namespace mozilla {
+namespace dom {
+
+using XPCOMPermitUnloadAction = nsIDocumentViewer::PermitUnloadAction;
+using PermitUnloadResult = nsIDocumentViewer::PermitUnloadResult;
+
+} // namespace dom
+} // namespace mozilla
+%}
diff --git a/docshell/base/nsIDocumentViewerEdit.idl b/docshell/base/nsIDocumentViewerEdit.idl
new file mode 100644
index 0000000000..10ec203df7
--- /dev/null
+++ b/docshell/base/nsIDocumentViewerEdit.idl
@@ -0,0 +1,36 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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 "nsISupports.idl"
+
+webidl Node;
+
+[scriptable, uuid(e39a0c2a-5b31-4d57-a971-66ba07fab614)]
+interface nsIDocumentViewerEdit : nsISupports
+{
+ void clearSelection();
+ void selectAll();
+
+ void copySelection();
+ readonly attribute boolean copyable;
+
+ void copyLinkLocation();
+ readonly attribute boolean inLink;
+
+ const long COPY_IMAGE_TEXT = 0x0001;
+ const long COPY_IMAGE_HTML = 0x0002;
+ const long COPY_IMAGE_DATA = 0x0004;
+ const long COPY_IMAGE_ALL = -1;
+ void copyImage(in long aCopyFlags);
+ readonly attribute boolean inImage;
+
+ AString getContents(in string aMimeType, in boolean aSelectionOnly);
+ readonly attribute boolean canGetContents;
+
+ // Set the node that will be the subject of the editing commands above.
+ // Usually this will be the node that was context-clicked.
+ void setCommandNode(in Node aNode);
+};
diff --git a/docshell/base/nsILoadContext.idl b/docshell/base/nsILoadContext.idl
new file mode 100644
index 0000000000..af71b96b34
--- /dev/null
+++ b/docshell/base/nsILoadContext.idl
@@ -0,0 +1,148 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: ft=cpp tw=78 sw=2 et ts=2 sts=2 cin
+ * 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 "nsISupports.idl"
+
+interface mozIDOMWindowProxy;
+
+webidl Element;
+
+[ref] native OriginAttributes(mozilla::OriginAttributes);
+
+%{C++
+#ifdef MOZILLA_INTERNAL_API
+namespace mozilla {
+class OriginAttributes;
+}
+#endif
+%}
+
+/**
+ * An nsILoadContext represents the context of a load. This interface
+ * can be queried for various information about where the load is
+ * happening.
+ */
+[builtinclass, scriptable, uuid(2813a7a3-d084-4d00-acd0-f76620315c02)]
+interface nsILoadContext : nsISupports
+{
+ /**
+ * associatedWindow is the window with which the load is associated, if any.
+ * Note that the load may be triggered by a document which is different from
+ * the document in associatedWindow, and in fact the source of the load need
+ * not be same-origin with the document in associatedWindow. This attribute
+ * may be null if there is no associated window.
+ */
+ readonly attribute mozIDOMWindowProxy associatedWindow;
+
+ /**
+ * topWindow is the top window which is of same type as associatedWindow.
+ * This is equivalent to associatedWindow.top, but is provided here as a
+ * convenience. All the same caveats as associatedWindow of apply, of
+ * course. This attribute may be null if there is no associated window.
+ */
+ readonly attribute mozIDOMWindowProxy topWindow;
+
+ /**
+ * topFrameElement is the <iframe>, <frame>, or <browser> element which
+ * contains the topWindow with which the load is associated.
+ *
+ * Note that we may have a topFrameElement even when we don't have an
+ * associatedWindow, if the topFrameElement's content lives out of process.
+ * topFrameElement is available in single-process and multiprocess contexts.
+ * Note that topFrameElement may be in chrome even when the nsILoadContext is
+ * associated with content.
+ */
+ readonly attribute Element topFrameElement;
+
+ /**
+ * True if the load context is content (as opposed to chrome). This is
+ * determined based on the type of window the load is performed in, NOT based
+ * on any URIs that might be around.
+ */
+ readonly attribute boolean isContent;
+
+ /*
+ * Attribute that determines if private browsing should be used. May not be
+ * changed after a document has been loaded in this context.
+ */
+ attribute boolean usePrivateBrowsing;
+
+ /**
+ * Attribute that determines if remote (out-of-process) tabs should be used.
+ */
+ readonly attribute boolean useRemoteTabs;
+
+ /**
+ * Determines if out-of-process iframes should be used.
+ */
+ readonly attribute boolean useRemoteSubframes;
+
+ /*
+ * Attribute that determines if tracking protection should be used. May not be
+ * changed after a document has been loaded in this context.
+ */
+ attribute boolean useTrackingProtection;
+
+%{C++
+ /**
+ * De-XPCOMed getter to make call-sites cleaner.
+ */
+ bool UsePrivateBrowsing()
+ {
+ bool usingPB = false;
+ GetUsePrivateBrowsing(&usingPB);
+ return usingPB;
+ }
+
+ bool UseRemoteTabs()
+ {
+ bool usingRT = false;
+ GetUseRemoteTabs(&usingRT);
+ return usingRT;
+ }
+
+ bool UseRemoteSubframes()
+ {
+ bool usingRSF = false;
+ GetUseRemoteSubframes(&usingRSF);
+ return usingRSF;
+ }
+
+ bool UseTrackingProtection()
+ {
+ bool usingTP = false;
+ GetUseTrackingProtection(&usingTP);
+ return usingTP;
+ }
+%}
+
+ /**
+ * Set the private browsing state of the load context, meant to be used internally.
+ */
+ [noscript] void SetPrivateBrowsing(in boolean aInPrivateBrowsing);
+
+ /**
+ * Set the remote tabs state of the load context, meant to be used internally.
+ */
+ [noscript] void SetRemoteTabs(in boolean aUseRemoteTabs);
+
+ /**
+ * Set the remote subframes bit of this load context. Exclusively meant to be used internally.
+ */
+ [noscript] void SetRemoteSubframes(in boolean aUseRemoteSubframes);
+
+ /**
+ * A dictionary of the non-default origin attributes associated with this
+ * nsILoadContext.
+ */
+ [binaryname(ScriptableOriginAttributes), implicit_jscontext]
+ readonly attribute jsval originAttributes;
+
+ /**
+ * The C++ getter for origin attributes.
+ */
+ [noscript, notxpcom] void GetOriginAttributes(out OriginAttributes aAttrs);
+};
diff --git a/docshell/base/nsILoadURIDelegate.idl b/docshell/base/nsILoadURIDelegate.idl
new file mode 100644
index 0000000000..eb5d4cbaf5
--- /dev/null
+++ b/docshell/base/nsILoadURIDelegate.idl
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsISupports.idl"
+
+interface nsIURI;
+interface nsIPrincipal;
+
+/**
+ * The nsILoadURIDelegate interface.
+ * Used for delegating URI loads to GeckoView's application, e.g., Custom Tabs
+ * or Progressive Web Apps.
+ */
+[scriptable, uuid(78e42d37-a34c-4d96-b901-25385669aba4)]
+interface nsILoadURIDelegate : nsISupports
+{
+ /**
+ * Delegates page load error handling. This may be called for either top-level
+ * loads or subframes.
+ *
+ * @param aURI The URI that failed to load.
+ * @param aError The error code.
+ * @param aErrorModule The error module code.
+
+ * Returns an error page URL to load, or null to show the default error page.
+ * No error page is shown at all if an error is thrown.
+ */
+ nsIURI
+ handleLoadError(in nsIURI aURI, in nsresult aError, in short aErrorModule);
+};
diff --git a/docshell/base/nsIPrivacyTransitionObserver.idl b/docshell/base/nsIPrivacyTransitionObserver.idl
new file mode 100644
index 0000000000..c85d468d33
--- /dev/null
+++ b/docshell/base/nsIPrivacyTransitionObserver.idl
@@ -0,0 +1,11 @@
+/* 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 "nsISupports.idl"
+
+[scriptable, function, uuid(b4b1449d-0ef0-47f5-b62e-adc57fd49702)]
+interface nsIPrivacyTransitionObserver : nsISupports
+{
+ void privateModeChanged(in bool enabled);
+};
diff --git a/docshell/base/nsIReflowObserver.idl b/docshell/base/nsIReflowObserver.idl
new file mode 100644
index 0000000000..fb602e2603
--- /dev/null
+++ b/docshell/base/nsIReflowObserver.idl
@@ -0,0 +1,31 @@
+/* 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 "domstubs.idl"
+
+[scriptable, uuid(832e692c-c4a6-11e2-8fd1-dce678957a39)]
+interface nsIReflowObserver : nsISupports
+{
+ /**
+ * Called when an uninterruptible reflow has occurred.
+ *
+ * @param start timestamp when reflow ended, in milliseconds since
+ * navigationStart (accurate to 1/1000 of a ms)
+ * @param end timestamp when reflow ended, in milliseconds since
+ * navigationStart (accurate to 1/1000 of a ms)
+ */
+ void reflow(in DOMHighResTimeStamp start,
+ in DOMHighResTimeStamp end);
+
+ /**
+ * Called when an interruptible reflow has occurred.
+ *
+ * @param start timestamp when reflow ended, in milliseconds since
+ * navigationStart (accurate to 1/1000 of a ms)
+ * @param end timestamp when reflow ended, in milliseconds since
+ * navigationStart (accurate to 1/1000 of a ms)
+ */
+ void reflowInterruptible(in DOMHighResTimeStamp start,
+ in DOMHighResTimeStamp end);
+};
diff --git a/docshell/base/nsIRefreshURI.idl b/docshell/base/nsIRefreshURI.idl
new file mode 100644
index 0000000000..a4a578a344
--- /dev/null
+++ b/docshell/base/nsIRefreshURI.idl
@@ -0,0 +1,52 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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 "nsISupports.idl"
+
+interface nsIChannel;
+interface nsIPrincipal;
+interface nsIURI;
+
+[scriptable, uuid(a5e61a3c-51bd-45be-ac0c-e87b71860656)]
+interface nsIRefreshURI : nsISupports {
+ /**
+ * Load a uri after waiting for aMillis milliseconds (as a result of a
+ * meta refresh). If the docshell is busy loading a page currently, the
+ * refresh request will be queued and executed when the current load
+ * finishes.
+ *
+ * @param aUri The uri to refresh.
+ * @param aPrincipal The triggeringPrincipal for the refresh load
+ * May be null, in which case the principal of current document will be
+ * applied.
+ * @param aMillis The number of milliseconds to wait.
+ */
+ void refreshURI(in nsIURI aURI, in nsIPrincipal aPrincipal,
+ in unsigned long aMillis);
+
+ /**
+ * Loads a URI immediately as if it were a meta refresh.
+ *
+ * @param aURI The URI to refresh.
+ * @param aPrincipal The triggeringPrincipal for the refresh load
+ * May be null, in which case the principal of current document will be
+ * applied.
+ * @param aMillis The number of milliseconds by which this refresh would
+ * be delayed if it were not being forced.
+ */
+ void forceRefreshURI(in nsIURI aURI, in nsIPrincipal aPrincipal,
+ in unsigned long aMillis);
+
+ /**
+ * Cancels all timer loads.
+ */
+ void cancelRefreshURITimers();
+
+ /**
+ * True when there are pending refreshes, false otherwise.
+ */
+ readonly attribute boolean refreshPending;
+};
diff --git a/docshell/base/nsIScrollObserver.h b/docshell/base/nsIScrollObserver.h
new file mode 100644
index 0000000000..9ff89002f0
--- /dev/null
+++ b/docshell/base/nsIScrollObserver.h
@@ -0,0 +1,45 @@
+/* -*- 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 nsIScrollObserver_h___
+#define nsIScrollObserver_h___
+
+#include "nsISupports.h"
+#include "Units.h"
+
+// Must be kept in sync with xpcom/rust/xpcom/src/interfaces/nonidl.rs
+#define NS_ISCROLLOBSERVER_IID \
+ { \
+ 0xaa5026eb, 0x2f88, 0x4026, { \
+ 0xa4, 0x6b, 0xf4, 0x59, 0x6b, 0x4e, 0xdf, 0x00 \
+ } \
+ }
+
+class nsIScrollObserver : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISCROLLOBSERVER_IID)
+
+ /**
+ * Called when the scroll position of some element has changed.
+ */
+ virtual void ScrollPositionChanged() = 0;
+
+ /**
+ * Called when an async panning/zooming transform has started being applied
+ * and passed the scroll offset
+ */
+ MOZ_CAN_RUN_SCRIPT virtual void AsyncPanZoomStarted(){};
+
+ /**
+ * Called when an async panning/zooming transform is no longer applied
+ * and passed the scroll offset
+ */
+ MOZ_CAN_RUN_SCRIPT virtual void AsyncPanZoomStopped(){};
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIScrollObserver, NS_ISCROLLOBSERVER_IID)
+
+#endif /* nsIScrollObserver_h___ */
diff --git a/docshell/base/nsITooltipListener.idl b/docshell/base/nsITooltipListener.idl
new file mode 100644
index 0000000000..0498aca57d
--- /dev/null
+++ b/docshell/base/nsITooltipListener.idl
@@ -0,0 +1,44 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "nsISupports.idl"
+
+/**
+ * An optional interface for embedding clients wishing to receive
+ * notifications for when a tooltip should be displayed or removed.
+ * The embedder implements this interface on the web browser chrome
+ * object associated with the window that notifications are required
+ * for.
+ *
+ * @see nsITooltipTextProvider
+ */
+[scriptable, uuid(44b78386-1dd2-11b2-9ad2-e4eee2ca1916)]
+interface nsITooltipListener : nsISupports
+{
+ /**
+ * Called when a tooltip should be displayed.
+ *
+ * @param aXCoords The tooltip left edge X coordinate.
+ * @param aYCoords The tooltip top edge Y coordinate.
+ * @param aTipText The text to display in the tooltip, typically obtained
+ * from the TITLE attribute of the node (or containing parent)
+ * over which the pointer has been positioned.
+ * @param aTipDir The direction (ltr or rtl) in which to display the text
+ *
+ * @note
+ * Coordinates are specified in device pixels, relative to the top-left
+ * corner of the browser area.
+ *
+ * @return <code>NS_OK</code> if the tooltip was displayed.
+ */
+ void onShowTooltip(in long aXCoords, in long aYCoords, in AString aTipText,
+ in AString aTipDir);
+
+ /**
+ * Called when the tooltip should be hidden, either because the pointer
+ * has moved or the tooltip has timed out.
+ */
+ void onHideTooltip();
+};
diff --git a/docshell/base/nsITooltipTextProvider.idl b/docshell/base/nsITooltipTextProvider.idl
new file mode 100644
index 0000000000..3afaddbe2a
--- /dev/null
+++ b/docshell/base/nsITooltipTextProvider.idl
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "nsISupports.idl"
+
+webidl Node;
+
+/**
+ * An interface implemented by a tooltip text provider service. This
+ * service is called to discover what tooltip text is associated
+ * with the node that the pointer is positioned over.
+ *
+ * Embedders may implement and register their own tooltip text provider
+ * service if they wish to provide different tooltip text.
+ *
+ * The default service returns the text stored in the TITLE
+ * attribute of the node or a containing parent.
+ *
+ * @note
+ * The tooltip text provider service is registered with the contract
+ * defined in NS_TOOLTIPTEXTPROVIDER_CONTRACTID.
+ *
+ * @see nsITooltipListener
+ * @see nsIComponentManager
+ * @see Node
+ */
+[scriptable, uuid(b128a1e6-44f3-4331-8fbe-5af360ff21ee)]
+interface nsITooltipTextProvider : nsISupports
+{
+ /**
+ * Called to obtain the tooltip text for a node.
+ *
+ * @arg aNode The node to obtain the text from.
+ * @arg aText The tooltip text.
+ * @arg aDirection The text direction (ltr or rtl) to use
+ *
+ * @return <CODE>PR_TRUE</CODE> if tooltip text is associated
+ * with the node and was returned in the aText argument;
+ * <CODE>PR_FALSE</CODE> otherwise.
+ */
+ boolean getNodeText(in Node aNode, out wstring aText, out wstring aDirection);
+};
diff --git a/docshell/base/nsIURIFixup.idl b/docshell/base/nsIURIFixup.idl
new file mode 100644
index 0000000000..2261c0cb40
--- /dev/null
+++ b/docshell/base/nsIURIFixup.idl
@@ -0,0 +1,204 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * 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 "nsISupports.idl"
+
+interface nsIURI;
+interface nsIInputStream;
+interface nsIDNSListener;
+webidl BrowsingContext;
+
+/**
+ * Interface indicating what we found/corrected when fixing up a URI
+ */
+[scriptable, uuid(4819f183-b532-4932-ac09-b309cd853be7)]
+interface nsIURIFixupInfo : nsISupports
+{
+ /**
+ * Consumer that asked for fixed up URI.
+ */
+ attribute BrowsingContext consumer;
+
+ /**
+ * Our best guess as to what URI the consumer will want. Might
+ * be null if we couldn't salvage anything (for instance, because
+ * the input was invalid as a URI and FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP
+ * was not passed)
+ */
+ attribute nsIURI preferredURI;
+
+ /**
+ * The fixed-up original input, *never* using a keyword search.
+ * (might be null if the original input was not recoverable as
+ * a URL, e.g. "foo bar"!)
+ */
+ attribute nsIURI fixedURI;
+
+ /**
+ * The name of the keyword search provider used to provide a keyword search;
+ * empty string if no keyword search was done.
+ */
+ attribute AString keywordProviderName;
+
+ /**
+ * The keyword as used for the search (post trimming etc.)
+ * empty string if no keyword search was done.
+ */
+ attribute AString keywordAsSent;
+
+ /**
+ * Whether there was no protocol at all and we had to add one in the first place.
+ */
+ attribute boolean wasSchemelessInput;
+
+ /**
+ * Whether we changed the protocol instead of using one from the input as-is.
+ */
+ attribute boolean fixupChangedProtocol;
+
+ /**
+ * Whether we created an alternative URI. We might have added a prefix and/or
+ * suffix, the contents of which are controlled by the
+ * browser.fixup.alternate.prefix and .suffix prefs, with the defaults being
+ * "www." and ".com", respectively.
+ */
+ attribute boolean fixupCreatedAlternateURI;
+
+ /**
+ * The original input
+ */
+ attribute AUTF8String originalInput;
+
+ /**
+ * The POST data to submit with the returned URI (see nsISearchSubmission).
+ */
+ attribute nsIInputStream postData;
+};
+
+
+/**
+ * Interface implemented by objects capable of fixing up strings into URIs
+ */
+[scriptable, uuid(1da7e9d4-620b-4949-849a-1cd6077b1b2d)]
+interface nsIURIFixup : nsISupports
+{
+ /** No fixup flags. */
+ const unsigned long FIXUP_FLAG_NONE = 0;
+
+ /**
+ * Allow the fixup to use a keyword lookup service to complete the URI.
+ * The fixup object implementer should honour this flag and only perform
+ * any lengthy keyword (or search) operation if it is set.
+ */
+ const unsigned long FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP = 1;
+
+ /**
+ * Tell the fixup to make an alternate URI from the input URI, for example
+ * to turn foo into www.foo.com.
+ */
+ const unsigned long FIXUP_FLAGS_MAKE_ALTERNATE_URI = 2;
+
+ /*
+ * Set when the fixup happens in a private context, the used search engine
+ * may differ in this case. Not all consumers care about this, because they
+ * may not want the url to be transformed in a search.
+ */
+ const unsigned long FIXUP_FLAG_PRIVATE_CONTEXT = 4;
+
+ /*
+ * Fix common scheme typos.
+ */
+ const unsigned long FIXUP_FLAG_FIX_SCHEME_TYPOS = 8;
+
+ /**
+ * Tries to converts the specified string into a URI, first attempting
+ * to correct any errors in the syntax or other vagaries.
+ * It returns information about what it corrected
+ * (e.g. whether we could rescue the URI or "just" generated a keyword
+ * search URI instead).
+ *
+ * @param aURIText Candidate URI.
+ * @param aFixupFlags Flags that govern ways the URI may be fixed up.
+ * Defaults to FIXUP_FLAG_NONE.
+ */
+ nsIURIFixupInfo getFixupURIInfo(in AUTF8String aURIText,
+ [optional] in unsigned long aFixupFlags);
+
+ /**
+ * Convert load flags from nsIWebNavigation to URI fixup flags for use in
+ * getFixupURIInfo.
+ *
+ * @param aURIText Candidate URI; used for determining whether to
+ * allow keyword lookups.
+ * @param aDocShellFlags Load flags from nsIDocShell to convert.
+ */
+ unsigned long webNavigationFlagsToFixupFlags(
+ in AUTF8String aURIText, in unsigned long aDocShellFlags);
+
+ /**
+ * Converts the specified keyword string into a URI. Note that it's the
+ * caller's responsibility to check whether keywords are enabled and
+ * whether aKeyword is a sensible keyword.
+ *
+ * @param aKeyword The keyword string to convert into a URI
+ * @param aIsPrivateContext Whether this is invoked from a private context.
+ */
+ nsIURIFixupInfo keywordToURI(in AUTF8String aKeyword,
+ [optional] in boolean aIsPrivateContext);
+
+ /**
+ * Given a uri-like string with a protocol, attempt to fix and convert it
+ * into an instance of nsIURIFixupInfo.
+ *
+ * Differently from getFixupURIInfo, this assumes the input string is an
+ * http/https uri, and can add a prefix and/or suffix to its hostname.
+ *
+ * The scheme will be changed to the scheme defined in
+ * "browser.fixup.alternate.protocol", which is by default, https.
+ *
+ * If the prefix and suffix of the host are missing, it will add them to
+ * the host using the preferences "browser.fixup.alternate.prefix" and
+ * "browser.fixup.alternate.suffix" as references.
+ *
+ * If a hostname suffix is present, but the URI doesn't contain a prefix,
+ * it will add the prefix via "browser.fixup.alternate.prefix"
+ *
+ * @param aUriString The URI to fixup and convert.
+ * @returns nsIURIFixupInfo
+ * A nsIURIFixupInfo object with the property fixedURI
+ * which contains the modified URI.
+ * @throws NS_ERROR_FAILURE
+ * If aUriString is undefined, or the scheme is not
+ * http/https.
+ */
+ nsIURIFixupInfo forceHttpFixup(in AUTF8String aUriString);
+
+ /**
+ * With the host associated with the URI, use nsIDNSService to determine
+ * if an IP address can be found for this host. This method will ignore checking
+ * hosts that are IP addresses. If the host does not contain any periods, depending
+ * on the browser.urlbar.dnsResolveFullyQualifiedNames preference value, a period
+ * may be appended in order to make it a fully qualified domain name.
+ *
+ * @param aURI The URI to parse and pass into the DNS lookup.
+ * @param aListener The listener when the result from the lookup is available.
+ * @param aOriginAttributes The originAttributes to pass the DNS lookup.
+ * @throws NS_ERROR_FAILURE if aURI does not have a displayHost or asciiHost.
+ */
+ void checkHost(in nsIURI aURI,
+ in nsIDNSListener aListener,
+ [optional] in jsval aOriginAttributes);
+
+ /**
+ * Returns true if the specified domain is known and false otherwise.
+ * A known domain is relevant when we have a single word and can't be
+ * sure whether to treat the word as a host name or should instead be
+ * treated as a search term.
+ *
+ * @param aDomain A domain name to query.
+ */
+ bool isDomainKnown(in AUTF8String aDomain);
+};
diff --git a/docshell/base/nsIWebNavigation.idl b/docshell/base/nsIWebNavigation.idl
new file mode 100644
index 0000000000..1800e7312e
--- /dev/null
+++ b/docshell/base/nsIWebNavigation.idl
@@ -0,0 +1,415 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * 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 "nsISupports.idl"
+
+interface nsIInputStream;
+interface nsISHistory;
+interface nsIURI;
+interface nsIPrincipal;
+interface nsIChildSHistory;
+webidl Document;
+
+%{ C++
+#include "mozilla/dom/ChildSHistory.h"
+namespace mozilla {
+namespace dom {
+struct LoadURIOptions;
+} // namespace dom
+} // namespace mozilla
+%}
+
+[ref] native LoadURIOptionsRef(const mozilla::dom::LoadURIOptions);
+
+/**
+ * The nsIWebNavigation interface defines an interface for navigating the web.
+ * It provides methods and attributes to direct an object to navigate to a new
+ * location, stop or restart an in process load, or determine where the object
+ * has previously gone.
+ *
+ * Even though this is builtinclass, most of the interface is also implemented
+ * in RemoteWebNavigation, so if this interface changes, the implementation
+ * there may also need to change.
+ */
+[scriptable, builtinclass, uuid(3ade79d4-8cb9-4952-b18d-4f9b63ca0d31)]
+interface nsIWebNavigation : nsISupports
+{
+ /**
+ * Indicates if the object can go back. If true this indicates that
+ * there is back session history available for navigation.
+ */
+ readonly attribute boolean canGoBack;
+
+ /**
+ * Indicates if the object can go forward. If true this indicates that
+ * there is forward session history available for navigation
+ */
+ readonly attribute boolean canGoForward;
+
+ /**
+ * Tells the object to navigate to the previous session history item. When a
+ * page is loaded from session history, all content is loaded from the cache
+ * (if available) and page state (such as form values and scroll position) is
+ * restored.
+ *
+ * @param {boolean} aRequireUserInteraction
+ * Tells goBack to skip history items that did not record any user
+ * interaction on their corresponding document while they were active.
+ * This means in case of multiple entries mapping to the same document,
+ * each entry has to have been flagged with user interaction separately.
+ * If no items have user interaction, the function will fall back
+ * to the first session history entry.
+ *
+ * @param {boolean} aUserActivation
+ * Tells goBack that the call was triggered by a user action (e.g.:
+ * The user clicked the back button).
+ *
+ * @throw NS_ERROR_UNEXPECTED
+ * Indicates that the call was unexpected at this time, which implies
+ * that canGoBack is false.
+ */
+ void goBack([optional] in boolean aRequireUserInteraction, [optional] in boolean aUserActivation);
+
+ /**
+ * Tells the object to navigate to the next session history item. When a
+ * page is loaded from session history, all content is loaded from the cache
+ * (if available) and page state (such as form values and scroll position) is
+ * restored.
+ *
+ * @param {boolean} aRequireUserInteraction
+ * Tells goForward to skip history items that did not record any user
+ * interaction on their corresponding document while they were active.
+ * This means in case of multiple entries mapping to the same document,
+ * each entry has to have been flagged with user interaction separately.
+ * If no items have user interaction, the function will fall back
+ * to the latest session history entry.
+ *
+ * @param {boolean} aUserActivation
+ * Tells goForward that the call was triggered by a user action (e.g.:
+ * The user clicked the forward button).
+ *
+ * @throw NS_ERROR_UNEXPECTED
+ * Indicates that the call was unexpected at this time, which implies
+ * that canGoForward is false.
+ */
+ void goForward([optional] in boolean aRequireUserInteraction, [optional] in boolean aUserActivation);
+
+ /**
+ * Tells the object to navigate to the session history item at a given index.
+ *
+ * @param {boolean} aUserActivation
+ * Tells goForward that the call was triggered by a user action (e.g.:
+ * The user clicked the forward button).
+ *
+ * @throw NS_ERROR_UNEXPECTED
+ * Indicates that the call was unexpected at this time, which implies
+ * that session history entry at the given index does not exist.
+ */
+ void gotoIndex(in long index, [optional] in boolean aUserActivation);
+
+ /****************************************************************************
+ * The following flags may be bitwise combined to form the load flags
+ * parameter passed to either the loadURI or reload method. Some of these
+ * flags are only applicable to loadURI.
+ */
+
+ /**
+ * This flags defines the range of bits that may be specified. Flags
+ * outside this range may be used, but may not be passed to Reload().
+ */
+ const unsigned long LOAD_FLAGS_MASK = 0xffff;
+
+ /**
+ * This is the default value for the load flags parameter.
+ */
+ const unsigned long LOAD_FLAGS_NONE = 0x0000;
+
+ /**
+ * Flags 0x1, 0x2, 0x4, 0x8 are reserved for internal use by
+ * nsIWebNavigation implementations for now.
+ */
+
+ /**
+ * This flag specifies that the load should have the semantics of an HTML
+ * Meta-refresh tag (i.e., that the cache should be bypassed). This flag
+ * is only applicable to loadURI.
+ * XXX the meaning of this flag is poorly defined.
+ * XXX no one uses this, so we should probably deprecate and remove it.
+ */
+ const unsigned long LOAD_FLAGS_IS_REFRESH = 0x0010;
+
+ /**
+ * This flag specifies that the load should have the semantics of a link
+ * click. This flag is only applicable to loadURI.
+ * XXX the meaning of this flag is poorly defined.
+ */
+ const unsigned long LOAD_FLAGS_IS_LINK = 0x0020;
+
+ /**
+ * This flag specifies that history should not be updated. This flag is only
+ * applicable to loadURI.
+ */
+ const unsigned long LOAD_FLAGS_BYPASS_HISTORY = 0x0040;
+
+ /**
+ * This flag specifies that any existing history entry should be replaced.
+ * This flag is only applicable to loadURI.
+ */
+ const unsigned long LOAD_FLAGS_REPLACE_HISTORY = 0x0080;
+
+ /**
+ * This flag specifies that the local web cache should be bypassed, but an
+ * intermediate proxy cache could still be used to satisfy the load.
+ */
+ const unsigned long LOAD_FLAGS_BYPASS_CACHE = 0x0100;
+
+ /**
+ * This flag specifies that any intermediate proxy caches should be bypassed
+ * (i.e., that the content should be loaded from the origin server).
+ */
+ const unsigned long LOAD_FLAGS_BYPASS_PROXY = 0x0200;
+
+ /**
+ * This flag specifies that a reload was triggered as a result of detecting
+ * an incorrect character encoding while parsing a previously loaded
+ * document.
+ */
+ const unsigned long LOAD_FLAGS_CHARSET_CHANGE = 0x0400;
+
+ /**
+ * If this flag is set, Stop() will be called before the load starts
+ * and will stop both content and network activity (the default is to
+ * only stop network activity). Effectively, this passes the
+ * STOP_CONTENT flag to Stop(), in addition to the STOP_NETWORK flag.
+ */
+ const unsigned long LOAD_FLAGS_STOP_CONTENT = 0x0800;
+
+ /**
+ * A hint this load was prompted by an external program: take care!
+ */
+ const unsigned long LOAD_FLAGS_FROM_EXTERNAL = 0x1000;
+
+ /**
+ * This flag specifies that this is the first load in this object.
+ * Set with care, since setting incorrectly can cause us to assume that
+ * nothing was actually loaded in this object if the load ends up being
+ * handled by an external application. This flag must not be passed to
+ * Reload.
+ */
+ const unsigned long LOAD_FLAGS_FIRST_LOAD = 0x4000;
+
+ /**
+ * This flag specifies that the load should not be subject to popup
+ * blocking checks. This flag must not be passed to Reload.
+ */
+ const unsigned long LOAD_FLAGS_ALLOW_POPUPS = 0x8000;
+
+ /**
+ * This flag specifies that the URI classifier should not be checked for
+ * this load. This flag must not be passed to Reload.
+ */
+ const unsigned long LOAD_FLAGS_BYPASS_CLASSIFIER = 0x10000;
+
+ /**
+ * Force relevant cookies to be sent with this load even if normally they
+ * wouldn't be.
+ */
+ const unsigned long LOAD_FLAGS_FORCE_ALLOW_COOKIES = 0x20000;
+
+ /**
+ * Prevent the owner principal from being inherited for this load.
+ */
+ const unsigned long LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL = 0x40000;
+
+ /**
+ * Overwrite the returned error code with a specific result code
+ * when an error page is displayed.
+ */
+ const unsigned long LOAD_FLAGS_ERROR_LOAD_CHANGES_RV = 0x80000;
+
+ /**
+ * This flag specifies that the URI may be submitted to a third-party
+ * server for correction. This should only be applied to non-sensitive
+ * URIs entered by users. This flag must not be passed to Reload.
+ */
+ const unsigned long LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP = 0x100000;
+
+ /**
+ * This flag specifies that common scheme typos should be corrected.
+ */
+ const unsigned long LOAD_FLAGS_FIXUP_SCHEME_TYPOS = 0x200000;
+
+ /**
+ * Allows a top-level data: navigation to occur. E.g. view-image
+ * is an explicit user action which should be allowed.
+ */
+ const unsigned long LOAD_FLAGS_FORCE_ALLOW_DATA_URI = 0x400000;
+
+ /**
+ * This load is the result of an HTTP redirect.
+ */
+ const unsigned long LOAD_FLAGS_IS_REDIRECT = 0x800000;
+
+ /**
+ * These flags force TRR modes 1 or 3 for the load.
+ */
+ const unsigned long LOAD_FLAGS_DISABLE_TRR = 0x1000000;
+ const unsigned long LOAD_FLAGS_FORCE_TRR = 0x2000000;
+
+ /**
+ * This load should bypass the LoadURIDelegate.loadUri.
+ */
+ const unsigned long LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE = 0x4000000;
+
+ /**
+ * This load has a user activation. (e.g: reload button was clicked)
+ */
+ const unsigned long LOAD_FLAGS_USER_ACTIVATION = 0x8000000;
+
+ /**
+ * Loads a given URI. This will give priority to loading the requested URI
+ * in the object implementing this interface. If it can't be loaded here
+ * however, the URI dispatcher will go through its normal process of content
+ * loading.
+ *
+ * @param aURI
+ * The URI to load.
+ * @param aLoadURIOptions
+ * A JSObject defined in LoadURIOptions.webidl holding info like e.g.
+ * the triggeringPrincipal, the referrer info.
+ */
+ [implicit_jscontext, binaryname(LoadURIFromScript)]
+ void loadURI(in nsIURI aURI,
+ in jsval aLoadURIOptions);
+
+ /**
+ * Parse / fix up a URI out of the string and load it.
+ * This will give priority to loading the requested URI
+ * in the object implementing this interface. If it can't be loaded here
+ * however, the URI dispatcher will go through its normal process of content
+ * loading.
+ *
+ * @param aURIString
+ * The URI string to load. For HTTP and FTP URLs and possibly others,
+ * characters above U+007F will be converted to UTF-8 and then URL-
+ * escaped per the rules of RFC 2396.
+ * This method may use nsIURIFixup to try to fix up typos etc. in the
+ * input string based on the load flag arguments in aLoadURIOptions.
+ * It can even convert the input to a search results page using the
+ * default search service.
+ * If you have an nsIURI anyway, prefer calling `loadURI`, above.
+ * @param aLoadURIOptions
+ * A JSObject defined in LoadURIOptions.webidl holding info like e.g.
+ * the triggeringPrincipal, the referrer info.
+ */
+ [implicit_jscontext, binaryname(FixupAndLoadURIStringFromScript)]
+ void fixupAndLoadURIString(in AString aURIString,
+ in jsval aLoadURIOptions);
+
+ /**
+ * A C++ friendly version of loadURI
+ */
+ [nostdcall, binaryname(LoadURI)]
+ void binaryLoadURI(in nsIURI aURI,
+ in LoadURIOptionsRef aLoadURIOptions);
+
+ /**
+ * A C++ friendly version of fixupAndLoadURIString
+ */
+ [nostdcall, binaryname(FixupAndLoadURIString)]
+ void binaryFixupAndLoadURIString(in AString aURIString,
+ in LoadURIOptionsRef aLoadURIOptions);
+
+ /**
+ * Tells the Object to reload the current page. There may be cases where the
+ * user will be asked to confirm the reload (for example, when it is
+ * determined that the request is non-idempotent).
+ *
+ * @param aReloadFlags
+ * Flags modifying load behaviour. This parameter is a bitwise
+ * combination of the Load Flags defined above. (Undefined bits are
+ * reserved for future use.) Generally you will pass LOAD_FLAGS_NONE
+ * for this parameter.
+ *
+ * @throw NS_BINDING_ABORTED
+ * Indicating that the user canceled the reload.
+ */
+ void reload(in unsigned long aReloadFlags);
+
+ /****************************************************************************
+ * The following flags may be passed as the stop flags parameter to the stop
+ * method defined on this interface.
+ */
+
+ /**
+ * This flag specifies that all network activity should be stopped. This
+ * includes both active network loads and pending META-refreshes.
+ */
+ const unsigned long STOP_NETWORK = 0x01;
+
+ /**
+ * This flag specifies that all content activity should be stopped. This
+ * includes animated images, plugins and pending Javascript timeouts.
+ */
+ const unsigned long STOP_CONTENT = 0x02;
+
+ /**
+ * This flag specifies that all activity should be stopped.
+ */
+ const unsigned long STOP_ALL = 0x03;
+
+ /**
+ * Stops a load of a URI.
+ *
+ * @param aStopFlags
+ * This parameter is one of the stop flags defined above.
+ */
+ void stop(in unsigned long aStopFlags);
+
+ /**
+ * Retrieves the current DOM document for the frame, or lazily creates a
+ * blank document if there is none. This attribute never returns null except
+ * for unexpected error situations.
+ */
+ readonly attribute Document document;
+
+ /**
+ * The currently loaded URI or null.
+ */
+ readonly attribute nsIURI currentURI;
+
+ /**
+ * The session history object used by this web navigation instance. This
+ * object will be a mozilla::dom::ChildSHistory object, but is returned as
+ * nsISupports so it can be called from JS code.
+ */
+ [binaryname(SessionHistoryXPCOM)]
+ readonly attribute nsISupports sessionHistory;
+
+ %{ C++
+ /**
+ * Get the session history object used by this nsIWebNavigation instance.
+ * Use this method instead of the XPCOM method when getting the
+ * SessionHistory from C++ code.
+ */
+ already_AddRefed<mozilla::dom::ChildSHistory>
+ GetSessionHistory()
+ {
+ nsCOMPtr<nsISupports> history;
+ GetSessionHistoryXPCOM(getter_AddRefs(history));
+ return history.forget()
+ .downcast<mozilla::dom::ChildSHistory>();
+ }
+ %}
+
+ /**
+ * Resume a load which has been redirected from another process.
+ *
+ * A negative |aHistoryIndex| value corresponds to a non-history load being
+ * resumed.
+ */
+ void resumeRedirectedLoad(in unsigned long long aLoadIdentifier,
+ in long aHistoryIndex);
+};
diff --git a/docshell/base/nsIWebNavigationInfo.idl b/docshell/base/nsIWebNavigationInfo.idl
new file mode 100644
index 0000000000..0c3d07a8ed
--- /dev/null
+++ b/docshell/base/nsIWebNavigationInfo.idl
@@ -0,0 +1,55 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "nsISupports.idl"
+
+/**
+ * The nsIWebNavigationInfo interface exposes a way to get information
+ * on the capabilities of Gecko webnavigation objects.
+ */
+[scriptable, uuid(62a93afb-93a1-465c-84c8-0432264229de)]
+interface nsIWebNavigationInfo : nsISupports
+{
+ /**
+ * Returned by isTypeSupported to indicate lack of support for a type.
+ * @note this is guaranteed not to change, so that boolean tests can be done
+ * on the return value if isTypeSupported to detect whether a type is
+ * supported at all.
+ */
+ const unsigned long UNSUPPORTED = 0;
+
+ /**
+ * Returned by isTypeSupported to indicate that a type is supported as an
+ * image.
+ */
+ const unsigned long IMAGE = 1;
+
+ /**
+ * Returned by isTypeSupported to indicate that a type is a special NPAPI
+ * plugin that render as a transparent region (we do not support NPAPI
+ * plugins).
+ */
+ const unsigned long FALLBACK = 2;
+
+ /**
+ * @note Other return types may be added here in the future as they become
+ * relevant.
+ */
+
+ /**
+ * Returned by isTypeSupported to indicate that a type is supported via some
+ * other means.
+ */
+ const unsigned long OTHER = 1 << 15;
+
+ /**
+ * Query whether aType is supported.
+ * @param aType the MIME type in question.
+ * @return an enum value indicating whether and how aType is supported.
+ * @note This method may rescan plugins to ensure that they're properly
+ * registered for the types they support.
+ */
+ unsigned long isTypeSupported(in ACString aType);
+};
diff --git a/docshell/base/nsIWebPageDescriptor.idl b/docshell/base/nsIWebPageDescriptor.idl
new file mode 100644
index 0000000000..866cb54e6b
--- /dev/null
+++ b/docshell/base/nsIWebPageDescriptor.idl
@@ -0,0 +1,30 @@
+/* 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 "nsISupports.idl"
+interface nsIDocShell;
+
+/**
+ * The nsIWebPageDescriptor interface allows content being displayed in one
+ * window to be loaded into another window without refetching it from the
+ * network.
+ */
+
+[scriptable, uuid(6f30b676-3710-4c2c-80b1-0395fb26516e)]
+interface nsIWebPageDescriptor : nsISupports
+{
+ /**
+ * Tells the object to load the page that otherDocShell is currently loading,
+ * or has loaded already, as view source, with the url being `aURL`.
+ *
+ * @throws NS_ERROR_FAILURE - NS_ERROR_INVALID_POINTER
+ */
+ void loadPageAsViewSource(in nsIDocShell otherDocShell, in AString aURL);
+
+
+ /**
+ * Retrieves the page descriptor for the curent document.
+ * @note, currentDescriptor is currently always an nsISHEntry object or null.
+ */
+ readonly attribute nsISupports currentDescriptor;
+};
diff --git a/docshell/base/nsPingListener.cpp b/docshell/base/nsPingListener.cpp
new file mode 100644
index 0000000000..094074a0b9
--- /dev/null
+++ b/docshell/base/nsPingListener.cpp
@@ -0,0 +1,345 @@
+/* -*- 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 "nsPingListener.h"
+
+#include "mozilla/Encoding.h"
+#include "mozilla/Preferences.h"
+
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/dom/Document.h"
+
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIInputStream.h"
+#include "nsIProtocolHandler.h"
+#include "nsIUploadChannel2.h"
+
+#include "nsComponentManagerUtils.h"
+#include "nsNetUtil.h"
+#include "nsStreamUtils.h"
+#include "nsStringStream.h"
+#include "nsWhitespaceTokenizer.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+NS_IMPL_ISUPPORTS(nsPingListener, nsIStreamListener, nsIRequestObserver)
+
+//*****************************************************************************
+// <a ping> support
+//*****************************************************************************
+
+#define PREF_PINGS_ENABLED "browser.send_pings"
+#define PREF_PINGS_MAX_PER_LINK "browser.send_pings.max_per_link"
+#define PREF_PINGS_REQUIRE_SAME_HOST "browser.send_pings.require_same_host"
+
+// Check prefs to see if pings are enabled and if so what restrictions might
+// be applied.
+//
+// @param maxPerLink
+// This parameter returns the number of pings that are allowed per link click
+//
+// @param requireSameHost
+// This parameter returns true if pings are restricted to the same host as
+// the document in which the click occurs. If the same host restriction is
+// imposed, then we still allow for pings to cross over to different
+// protocols and ports for flexibility and because it is not possible to send
+// a ping via FTP.
+//
+// @returns
+// true if pings are enabled and false otherwise.
+//
+static bool PingsEnabled(int32_t* aMaxPerLink, bool* aRequireSameHost) {
+ bool allow = Preferences::GetBool(PREF_PINGS_ENABLED, false);
+
+ *aMaxPerLink = 1;
+ *aRequireSameHost = true;
+
+ if (allow) {
+ Preferences::GetInt(PREF_PINGS_MAX_PER_LINK, aMaxPerLink);
+ Preferences::GetBool(PREF_PINGS_REQUIRE_SAME_HOST, aRequireSameHost);
+ }
+
+ return allow;
+}
+
+// We wait this many milliseconds before killing the ping channel...
+#define PING_TIMEOUT 10000
+
+static void OnPingTimeout(nsITimer* aTimer, void* aClosure) {
+ nsILoadGroup* loadGroup = static_cast<nsILoadGroup*>(aClosure);
+ if (loadGroup) {
+ loadGroup->Cancel(NS_ERROR_ABORT);
+ }
+}
+
+struct MOZ_STACK_CLASS SendPingInfo {
+ int32_t numPings;
+ int32_t maxPings;
+ bool requireSameHost;
+ nsIURI* target;
+ nsIReferrerInfo* referrerInfo;
+ nsIDocShell* docShell;
+};
+
+static void SendPing(void* aClosure, nsIContent* aContent, nsIURI* aURI,
+ nsIIOService* aIOService) {
+ SendPingInfo* info = static_cast<SendPingInfo*>(aClosure);
+ if (info->maxPings > -1 && info->numPings >= info->maxPings) {
+ return;
+ }
+
+ Document* doc = aContent->OwnerDoc();
+
+ nsCOMPtr<nsIChannel> chan;
+ NS_NewChannel(getter_AddRefs(chan), aURI, doc,
+ info->requireSameHost
+ ? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED
+ : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_PING,
+ nullptr, // PerformanceStorage
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_NORMAL, // aLoadFlags,
+ aIOService);
+
+ if (!chan) {
+ return;
+ }
+
+ // Don't bother caching the result of this URI load, but do not exempt
+ // it from Safe Browsing.
+ chan->SetLoadFlags(nsIRequest::INHIBIT_CACHING);
+
+ nsCOMPtr<nsIHttpChannel> httpChan = do_QueryInterface(chan);
+ if (!httpChan) {
+ return;
+ }
+
+ // This is needed in order for 3rd-party cookie blocking to work.
+ nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(httpChan);
+ nsresult rv;
+ if (httpInternal) {
+ rv = httpInternal->SetDocumentURI(doc->GetDocumentURI());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ rv = httpChan->SetRequestMethod("POST"_ns);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Remove extraneous request headers (to reduce request size)
+ rv = httpChan->SetRequestHeader("accept"_ns, ""_ns, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = httpChan->SetRequestHeader("accept-language"_ns, ""_ns, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = httpChan->SetRequestHeader("accept-encoding"_ns, ""_ns, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Always send a Ping-To header.
+ nsAutoCString pingTo;
+ if (NS_SUCCEEDED(info->target->GetSpec(pingTo))) {
+ rv = httpChan->SetRequestHeader("Ping-To"_ns, pingTo, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ nsCOMPtr<nsIScriptSecurityManager> sm =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
+
+ if (sm && info->referrerInfo) {
+ nsCOMPtr<nsIURI> referrer = info->referrerInfo->GetOriginalReferrer();
+ bool referrerIsSecure = false;
+ uint32_t flags = nsIProtocolHandler::URI_IS_POTENTIALLY_TRUSTWORTHY;
+ if (referrer) {
+ rv = NS_URIChainHasFlags(referrer, flags, &referrerIsSecure);
+ }
+
+ // Default to sending less data if NS_URIChainHasFlags() fails.
+ referrerIsSecure = NS_FAILED(rv) || referrerIsSecure;
+
+ bool isPrivateWin = false;
+ if (doc) {
+ isPrivateWin =
+ doc->NodePrincipal()->OriginAttributesRef().mPrivateBrowsingId > 0;
+ }
+
+ bool sameOrigin = NS_SUCCEEDED(
+ sm->CheckSameOriginURI(referrer, aURI, false, isPrivateWin));
+
+ // If both the address of the document containing the hyperlink being
+ // audited and "ping URL" have the same origin or the document containing
+ // the hyperlink being audited was not retrieved over an encrypted
+ // connection, send a Ping-From header.
+ if (sameOrigin || !referrerIsSecure) {
+ nsAutoCString pingFrom;
+ if (NS_SUCCEEDED(referrer->GetSpec(pingFrom))) {
+ rv = httpChan->SetRequestHeader("Ping-From"_ns, pingFrom, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+
+ // If the document containing the hyperlink being audited was not retrieved
+ // over an encrypted connection and its address does not have the same
+ // origin as "ping URL", send a referrer.
+ if (!sameOrigin && !referrerIsSecure && info->referrerInfo) {
+ rv = httpChan->SetReferrerInfo(info->referrerInfo);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+
+ nsCOMPtr<nsIUploadChannel2> uploadChan = do_QueryInterface(httpChan);
+ if (!uploadChan) {
+ return;
+ }
+
+ constexpr auto uploadData = "PING"_ns;
+
+ nsCOMPtr<nsIInputStream> uploadStream;
+ rv = NS_NewCStringInputStream(getter_AddRefs(uploadStream), uploadData);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ uploadChan->ExplicitSetUploadStream(uploadStream, "text/ping"_ns,
+ uploadData.Length(), "POST"_ns, false);
+
+ // The channel needs to have a loadgroup associated with it, so that we can
+ // cancel the channel and any redirected channels it may create.
+ nsCOMPtr<nsILoadGroup> loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID);
+ if (!loadGroup) {
+ return;
+ }
+ nsCOMPtr<nsIInterfaceRequestor> callbacks = do_QueryInterface(info->docShell);
+ loadGroup->SetNotificationCallbacks(callbacks);
+ chan->SetLoadGroup(loadGroup);
+
+ RefPtr<nsPingListener> pingListener = new nsPingListener();
+ chan->AsyncOpen(pingListener);
+
+ // Even if AsyncOpen failed, we still count this as a successful ping. It's
+ // possible that AsyncOpen may have failed after triggering some background
+ // process that may have written something to the network.
+ info->numPings++;
+
+ // Prevent ping requests from stalling and never being garbage collected...
+ if (NS_FAILED(pingListener->StartTimeout(doc->GetDocGroup()))) {
+ // If we failed to setup the timer, then we should just cancel the channel
+ // because we won't be able to ensure that it goes away in a timely manner.
+ chan->Cancel(NS_ERROR_ABORT);
+ return;
+ }
+ // if the channel openend successfully, then make the pingListener hold
+ // a strong reference to the loadgroup which is released in ::OnStopRequest
+ pingListener->SetLoadGroup(loadGroup);
+}
+
+typedef void (*ForEachPingCallback)(void* closure, nsIContent* content,
+ nsIURI* uri, nsIIOService* ios);
+
+static void ForEachPing(nsIContent* aContent, ForEachPingCallback aCallback,
+ void* aClosure) {
+ // NOTE: Using nsIDOMHTMLAnchorElement::GetPing isn't really worth it here
+ // since we'd still need to parse the resulting string. Instead, we
+ // just parse the raw attribute. It might be nice if the content node
+ // implemented an interface that exposed an enumeration of nsIURIs.
+
+ // Make sure we are dealing with either an <A> or <AREA> element in the HTML
+ // or XHTML namespace.
+ if (!aContent->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area)) {
+ return;
+ }
+
+ nsAutoString value;
+ aContent->AsElement()->GetAttr(nsGkAtoms::ping, value);
+ if (value.IsEmpty()) {
+ return;
+ }
+
+ nsCOMPtr<nsIIOService> ios = do_GetIOService();
+ if (!ios) {
+ return;
+ }
+
+ Document* doc = aContent->OwnerDoc();
+ nsAutoCString charset;
+ doc->GetDocumentCharacterSet()->Name(charset);
+
+ nsWhitespaceTokenizer tokenizer(value);
+
+ while (tokenizer.hasMoreTokens()) {
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), tokenizer.nextToken(), charset.get(),
+ aContent->GetBaseURI());
+ // if we can't generate a valid URI, then there is nothing to do
+ if (!uri) {
+ continue;
+ }
+ // Explicitly not allow loading data: URIs
+ if (!net::SchemeIsData(uri)) {
+ aCallback(aClosure, aContent, uri, ios);
+ }
+ }
+}
+
+// Spec: http://whatwg.org/specs/web-apps/current-work/#ping
+/*static*/ void nsPingListener::DispatchPings(nsIDocShell* aDocShell,
+ nsIContent* aContent,
+ nsIURI* aTarget,
+ nsIReferrerInfo* aReferrerInfo) {
+ SendPingInfo info;
+
+ if (!PingsEnabled(&info.maxPings, &info.requireSameHost)) {
+ return;
+ }
+ if (info.maxPings == 0) {
+ return;
+ }
+
+ info.numPings = 0;
+ info.target = aTarget;
+ info.referrerInfo = aReferrerInfo;
+ info.docShell = aDocShell;
+
+ ForEachPing(aContent, SendPing, &info);
+}
+
+nsPingListener::~nsPingListener() {
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+}
+
+nsresult nsPingListener::StartTimeout(DocGroup* aDocGroup) {
+ NS_ENSURE_ARG(aDocGroup);
+
+ return NS_NewTimerWithFuncCallback(
+ getter_AddRefs(mTimer), OnPingTimeout, mLoadGroup, PING_TIMEOUT,
+ nsITimer::TYPE_ONE_SHOT, "nsPingListener::StartTimeout",
+ GetMainThreadSerialEventTarget());
+}
+
+NS_IMETHODIMP
+nsPingListener::OnStartRequest(nsIRequest* aRequest) { return NS_OK; }
+
+NS_IMETHODIMP
+nsPingListener::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aStream,
+ uint64_t aOffset, uint32_t aCount) {
+ uint32_t result;
+ return aStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &result);
+}
+
+NS_IMETHODIMP
+nsPingListener::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
+ mLoadGroup = nullptr;
+
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+
+ return NS_OK;
+}
diff --git a/docshell/base/nsPingListener.h b/docshell/base/nsPingListener.h
new file mode 100644
index 0000000000..7cf6ff98b5
--- /dev/null
+++ b/docshell/base/nsPingListener.h
@@ -0,0 +1,48 @@
+/* -*- 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 nsPingListener_h__
+#define nsPingListener_h__
+
+#include "nsIStreamListener.h"
+#include "nsIReferrerInfo.h"
+#include "nsCOMPtr.h"
+
+namespace mozilla {
+namespace dom {
+class DocGroup;
+}
+} // namespace mozilla
+
+class nsIContent;
+class nsIDocShell;
+class nsILoadGroup;
+class nsITimer;
+class nsIURI;
+
+class nsPingListener final : public nsIStreamListener {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ nsPingListener() {}
+
+ void SetLoadGroup(nsILoadGroup* aLoadGroup) { mLoadGroup = aLoadGroup; }
+
+ nsresult StartTimeout(mozilla::dom::DocGroup* aDocGroup);
+
+ static void DispatchPings(nsIDocShell* aDocShell, nsIContent* aContent,
+ nsIURI* aTarget, nsIReferrerInfo* aReferrerInfo);
+
+ private:
+ ~nsPingListener();
+
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ nsCOMPtr<nsITimer> mTimer;
+};
+
+#endif /* nsPingListener_h__ */
diff --git a/docshell/base/nsRefreshTimer.cpp b/docshell/base/nsRefreshTimer.cpp
new file mode 100644
index 0000000000..867e3ba1cb
--- /dev/null
+++ b/docshell/base/nsRefreshTimer.cpp
@@ -0,0 +1,49 @@
+/* -*- 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 "nsRefreshTimer.h"
+
+#include "nsIURI.h"
+#include "nsIPrincipal.h"
+
+#include "nsDocShell.h"
+
+NS_IMPL_ADDREF(nsRefreshTimer)
+NS_IMPL_RELEASE(nsRefreshTimer)
+
+NS_INTERFACE_MAP_BEGIN(nsRefreshTimer)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITimerCallback)
+ NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
+ NS_INTERFACE_MAP_ENTRY(nsINamed)
+NS_INTERFACE_MAP_END
+
+nsRefreshTimer::nsRefreshTimer(nsDocShell* aDocShell, nsIURI* aURI,
+ nsIPrincipal* aPrincipal, int32_t aDelay)
+ : mDocShell(aDocShell),
+ mURI(aURI),
+ mPrincipal(aPrincipal),
+ mDelay(aDelay) {}
+
+nsRefreshTimer::~nsRefreshTimer() {}
+
+NS_IMETHODIMP
+nsRefreshTimer::Notify(nsITimer* aTimer) {
+ NS_ASSERTION(mDocShell, "DocShell is somehow null");
+
+ if (mDocShell && aTimer) {
+ // Get the delay count to determine load type
+ uint32_t delay = 0;
+ aTimer->GetDelay(&delay);
+ mDocShell->ForceRefreshURIFromTimer(mURI, mPrincipal, delay, aTimer);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsRefreshTimer::GetName(nsACString& aName) {
+ aName.AssignLiteral("nsRefreshTimer");
+ return NS_OK;
+}
diff --git a/docshell/base/nsRefreshTimer.h b/docshell/base/nsRefreshTimer.h
new file mode 100644
index 0000000000..423f8807eb
--- /dev/null
+++ b/docshell/base/nsRefreshTimer.h
@@ -0,0 +1,39 @@
+/* -*- 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 nsRefreshTimer_h__
+#define nsRefreshTimer_h__
+
+#include "nsINamed.h"
+#include "nsITimer.h"
+
+#include "nsCOMPtr.h"
+
+class nsDocShell;
+class nsIURI;
+class nsIPrincipal;
+
+class nsRefreshTimer : public nsITimerCallback, public nsINamed {
+ public:
+ nsRefreshTimer(nsDocShell* aDocShell, nsIURI* aURI, nsIPrincipal* aPrincipal,
+ int32_t aDelay);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ int32_t GetDelay() { return mDelay; }
+
+ RefPtr<nsDocShell> mDocShell;
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ int32_t mDelay;
+
+ private:
+ virtual ~nsRefreshTimer();
+};
+
+#endif /* nsRefreshTimer_h__ */
diff --git a/docshell/base/nsWebNavigationInfo.cpp b/docshell/base/nsWebNavigationInfo.cpp
new file mode 100644
index 0000000000..cb989a33f1
--- /dev/null
+++ b/docshell/base/nsWebNavigationInfo.cpp
@@ -0,0 +1,64 @@
+/* -*- 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 "nsWebNavigationInfo.h"
+
+#include "nsServiceManagerUtils.h"
+#include "nsIDocumentLoaderFactory.h"
+#include "nsContentUtils.h"
+#include "imgLoader.h"
+
+NS_IMPL_ISUPPORTS(nsWebNavigationInfo, nsIWebNavigationInfo)
+
+NS_IMETHODIMP
+nsWebNavigationInfo::IsTypeSupported(const nsACString& aType,
+ uint32_t* aIsTypeSupported) {
+ MOZ_ASSERT(aIsTypeSupported, "null out param?");
+
+ *aIsTypeSupported = IsTypeSupported(aType);
+ return NS_OK;
+}
+
+uint32_t nsWebNavigationInfo::IsTypeSupported(const nsACString& aType) {
+ // We want to claim that the type for PDF documents is unsupported,
+ // so that the internal PDF viewer's stream converted will get used.
+ if (aType.LowerCaseEqualsLiteral("application/pdf") &&
+ nsContentUtils::IsPDFJSEnabled()) {
+ return nsIWebNavigationInfo::UNSUPPORTED;
+ }
+
+ const nsCString& flatType = PromiseFlatCString(aType);
+ return IsTypeSupportedInternal(flatType);
+}
+
+uint32_t nsWebNavigationInfo::IsTypeSupportedInternal(const nsCString& aType) {
+ nsContentUtils::DocumentViewerType vtype = nsContentUtils::TYPE_UNSUPPORTED;
+
+ nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory =
+ nsContentUtils::FindInternalDocumentViewer(aType, &vtype);
+
+ switch (vtype) {
+ case nsContentUtils::TYPE_UNSUPPORTED:
+ return nsIWebNavigationInfo::UNSUPPORTED;
+
+ case nsContentUtils::TYPE_FALLBACK:
+ return nsIWebNavigationInfo::FALLBACK;
+
+ case nsContentUtils::TYPE_UNKNOWN:
+ return nsIWebNavigationInfo::OTHER;
+
+ case nsContentUtils::TYPE_CONTENT:
+ // XXXbz we only need this because images register for the same
+ // contractid as documents, so we can't tell them apart based on
+ // contractid.
+ if (imgLoader::SupportImageWithMimeType(aType)) {
+ return nsIWebNavigationInfo::IMAGE;
+ }
+ return nsIWebNavigationInfo::OTHER;
+ }
+
+ return nsIWebNavigationInfo::UNSUPPORTED;
+}
diff --git a/docshell/base/nsWebNavigationInfo.h b/docshell/base/nsWebNavigationInfo.h
new file mode 100644
index 0000000000..97b07c825e
--- /dev/null
+++ b/docshell/base/nsWebNavigationInfo.h
@@ -0,0 +1,34 @@
+/* -*- 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 nsWebNavigationInfo_h__
+#define nsWebNavigationInfo_h__
+
+#include "nsIWebNavigationInfo.h"
+#include "nsCOMPtr.h"
+#include "nsICategoryManager.h"
+#include "nsStringFwd.h"
+#include "mozilla/Attributes.h"
+
+class nsWebNavigationInfo final : public nsIWebNavigationInfo {
+ public:
+ nsWebNavigationInfo() {}
+
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSIWEBNAVIGATIONINFO
+
+ static uint32_t IsTypeSupported(const nsACString& aType);
+
+ private:
+ ~nsWebNavigationInfo() {}
+
+ // Check whether aType is supported, and returns an nsIWebNavigationInfo
+ // constant.
+ static uint32_t IsTypeSupportedInternal(const nsCString& aType);
+};
+
+#endif // nsWebNavigationInfo_h__