diff options
Diffstat (limited to '')
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="location = 'http://www.mozilla.org/'">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('s').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(¤tSize.width, ¤tSize.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__ |