diff options
Diffstat (limited to '')
121 files changed, 35994 insertions, 0 deletions
diff --git a/docshell/base/BaseHistory.cpp b/docshell/base/BaseHistory.cpp new file mode 100644 index 0000000000..b39c33bbae --- /dev/null +++ b/docshell/base/BaseHistory.cpp @@ -0,0 +1,249 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "BaseHistory.h" +#include "nsThreadUtils.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/Link.h" +#include "mozilla/dom/Element.h" +#include "mozilla/StaticPrefs_browser.h" +#include "mozilla/StaticPrefs_layout.h" + +namespace mozilla { + +using mozilla::dom::ContentParent; +using mozilla::dom::Link; + +BaseHistory::BaseHistory() : mTrackedURIs(kTrackedUrisInitialSize) {} + +BaseHistory::~BaseHistory() = default; + +static constexpr nsLiteralCString kDisallowedSchemes[] = { + "about"_ns, "blob"_ns, "data"_ns, "chrome"_ns, + "imap"_ns, "javascript"_ns, "mailbox"_ns, "moz-anno"_ns, + "news"_ns, "page-icon"_ns, "resource"_ns, "view-source"_ns, + "moz-extension"_ns, +}; + +bool BaseHistory::CanStore(nsIURI* aURI) { + nsAutoCString scheme; + if (NS_WARN_IF(NS_FAILED(aURI->GetScheme(scheme)))) { + return false; + } + + if (!scheme.EqualsLiteral("http") && !scheme.EqualsLiteral("https")) { + for (const nsLiteralCString& disallowed : kDisallowedSchemes) { + if (scheme.Equals(disallowed)) { + return false; + } + } + } + + nsAutoCString spec; + aURI->GetSpec(spec); + return spec.Length() <= StaticPrefs::browser_history_maxUrlLength(); +} + +void BaseHistory::ScheduleVisitedQuery(nsIURI* aURI) { + mPendingQueries.PutEntry(aURI); + if (mStartPendingVisitedQueriesScheduled) { + return; + } + mStartPendingVisitedQueriesScheduled = + NS_SUCCEEDED(NS_DispatchToMainThreadQueue( + NS_NewRunnableFunction( + "BaseHistory::StartPendingVisitedQueries", + [self = RefPtr<BaseHistory>(this)] { + self->mStartPendingVisitedQueriesScheduled = false; + auto queries = std::move(self->mPendingQueries); + self->StartPendingVisitedQueries(queries); + MOZ_DIAGNOSTIC_ASSERT(self->mPendingQueries.IsEmpty()); + }), + EventQueuePriority::Idle)); +} + +void BaseHistory::CancelVisitedQueryIfPossible(nsIURI* aURI) { + mPendingQueries.RemoveEntry(aURI); + // TODO(bug 1591393): It could be worth to make this virtual and allow places + // to stop the existing database query? Needs some measurement. +} + +void BaseHistory::RegisterVisitedCallback(nsIURI* aURI, Link* aLink) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aURI, "Must pass a non-null URI!"); + if (XRE_IsContentProcess()) { + MOZ_ASSERT(aLink, "Must pass a non-null Link!"); + } + + if (!CanStore(aURI)) { + if (aLink) { + aLink->VisitedQueryFinished(/* visited = */ false); + } + return; + } + + // Obtain our array of observers for this URI. + auto entry = mTrackedURIs.LookupForAdd(aURI); + MOZ_DIAGNOSTIC_ASSERT(!entry || !entry.Data().mLinks.IsEmpty(), + "An empty key was kept around in our hashtable!"); + if (!entry) { + ScheduleVisitedQuery(aURI); + } + + if (!aLink) { + // In IPC builds, we are passed a nullptr Link from + // ContentParent::RecvStartVisitedQuery. All of our code after this point + // assumes aLink is non-nullptr, so we have to return now. + MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess(), + "We should only ever get a null Link " + "in the parent process!"); + // We don't want to remove if we're tracking other links. + if (!entry) { + entry.OrRemove(); + } + return; + } + + ObservingLinks& links = entry.OrInsert([] { return ObservingLinks{}; }); + + // Sanity check that Links are not registered more than once for a given URI. + // This will not catch a case where it is registered for two different URIs. + MOZ_DIAGNOSTIC_ASSERT(!links.mLinks.Contains(aLink), + "Already tracking this Link object!"); + // FIXME(emilio): We should consider changing this (see the entry.Remove() + // call in NotifyVisitedInThisProcess). + MOZ_DIAGNOSTIC_ASSERT(links.mStatus != VisitedStatus::Visited, + "We don't keep tracking known-visited links"); + + links.mLinks.AppendElement(aLink); + + // If this link has already been queried and we should notify, do so now. + switch (links.mStatus) { + case VisitedStatus::Unknown: + break; + case VisitedStatus::Unvisited: + if (!StaticPrefs::layout_css_notify_of_unvisited()) { + break; + } + [[fallthrough]]; + case VisitedStatus::Visited: + aLink->VisitedQueryFinished(links.mStatus == VisitedStatus::Visited); + break; + } +} + +void BaseHistory::UnregisterVisitedCallback(nsIURI* aURI, Link* aLink) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aURI, "Must pass a non-null URI!"); + MOZ_ASSERT(aLink, "Must pass a non-null Link object!"); + + // Get the array, and remove the item from it. + auto entry = mTrackedURIs.Lookup(aURI); + if (!entry) { + MOZ_ASSERT(!CanStore(aURI), + "Trying to unregister URI that wasn't registered, " + "and that could be visited!"); + return; + } + + ObserverArray& observers = entry.Data().mLinks; + if (!observers.RemoveElement(aLink)) { + MOZ_ASSERT_UNREACHABLE("Trying to unregister node that wasn't registered!"); + return; + } + + // If the array is now empty, we should remove it from the hashtable. + if (observers.IsEmpty()) { + entry.Remove(); + CancelVisitedQueryIfPossible(aURI); + } +} + +void BaseHistory::NotifyVisited(nsIURI* aURI, VisitedStatus aStatus) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aStatus != VisitedStatus::Unknown); + + if (aStatus == VisitedStatus::Unvisited && + !StaticPrefs::layout_css_notify_of_unvisited()) { + return; + } + + NotifyVisitedInThisProcess(aURI, aStatus); + if (XRE_IsParentProcess()) { + NotifyVisitedFromParent(aURI, aStatus); + } +} + +void BaseHistory::NotifyVisitedInThisProcess(nsIURI* aURI, + VisitedStatus aStatus) { + if (NS_WARN_IF(!aURI)) { + return; + } + + auto entry = mTrackedURIs.Lookup(aURI); + if (!entry) { + // If we have no observers for this URI, we have nothing to notify about. + return; + } + + ObservingLinks& links = entry.Data(); + links.mStatus = aStatus; + + // If we have a key, it should have at least one observer. + MOZ_ASSERT(!links.mLinks.IsEmpty()); + + // Dispatch an event to each document which has a Link observing this URL. + // These will fire asynchronously in the correct DocGroup. + + const bool visited = aStatus == VisitedStatus::Visited; + for (Link* link : links.mLinks.BackwardRange()) { + link->VisitedQueryFinished(visited); + } + + // We never go from visited -> unvisited. + // + // FIXME(emilio): It seems unfortunate to remove a link to a visited uri and + // then re-add it to the document to trigger a new visited query. It shouldn't + // if we keep track of mStatus. + if (visited) { + entry.Remove(); + } +} + +void BaseHistory::SendPendingVisitedResultsToChildProcesses() { + MOZ_ASSERT(!mPendingResults.IsEmpty()); + + mStartPendingResultsScheduled = false; + + auto results = std::move(mPendingResults); + MOZ_ASSERT(mPendingResults.IsEmpty()); + + nsTArray<ContentParent*> cplist; + ContentParent::GetAll(cplist); + for (ContentParent* cp : cplist) { + Unused << NS_WARN_IF(!cp->SendNotifyVisited(results)); + } +} + +void BaseHistory::NotifyVisitedFromParent(nsIURI* aURI, VisitedStatus aStatus) { + MOZ_ASSERT(XRE_IsParentProcess()); + auto& result = *mPendingResults.AppendElement(); + result.visited() = aStatus == VisitedStatus::Visited; + result.uri() = aURI; + + if (mStartPendingResultsScheduled) { + return; + } + + mStartPendingResultsScheduled = NS_SUCCEEDED(NS_DispatchToMainThreadQueue( + NewRunnableMethod( + "BaseHistory::SendPendingVisitedResultsToChildProcesses", this, + &BaseHistory::SendPendingVisitedResultsToChildProcesses), + EventQueuePriority::Idle)); +} + +} // namespace mozilla diff --git a/docshell/base/BaseHistory.h b/docshell/base/BaseHistory.h new file mode 100644 index 0000000000..941ec29972 --- /dev/null +++ b/docshell/base/BaseHistory.h @@ -0,0 +1,80 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_BaseHistory_h +#define mozilla_BaseHistory_h + +#include "IHistory.h" +#include "mozilla/dom/ContentParent.h" + +/* A base class for history implementations that implement link coloring. */ + +namespace mozilla { + +class BaseHistory : public IHistory { + public: + void RegisterVisitedCallback(nsIURI*, dom::Link*) final; + void UnregisterVisitedCallback(nsIURI*, dom::Link*) final; + void NotifyVisited(nsIURI*, VisitedStatus) final; + + // Some URIs like data-uris are never going to be stored in history, so we can + // avoid doing IPC roundtrips for them or what not. + static bool CanStore(nsIURI*); + + protected: + void NotifyVisitedInThisProcess(nsIURI*, VisitedStatus); + void NotifyVisitedFromParent(nsIURI*, VisitedStatus); + static constexpr const size_t kTrackedUrisInitialSize = 64; + + BaseHistory(); + ~BaseHistory(); + + using ObserverArray = nsTObserverArray<dom::Link*>; + struct ObservingLinks { + ObserverArray mLinks; + VisitedStatus mStatus = VisitedStatus::Unknown; + + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { + return mLinks.ShallowSizeOfExcludingThis(aMallocSizeOf); + } + }; + + using PendingVisitedQueries = nsTHashtable<nsURIHashKey>; + using PendingVisitedResults = nsTArray<mozilla::dom::VisitedQueryResult>; + + // Starts all the queries in the pending queries list, potentially at the same + // time. + virtual void StartPendingVisitedQueries(const PendingVisitedQueries&) = 0; + + private: + void ScheduleVisitedQuery(nsIURI*); + + // Cancels a visited query, if it is at all possible, because we know we won't + // use the results anymore. + void CancelVisitedQueryIfPossible(nsIURI*); + + void SendPendingVisitedResultsToChildProcesses(); + + protected: + // A map from URI to links that depend on that URI, and whether that URI is + // known-to-be-visited-or-unvisited already. + nsDataHashtable<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..0d9cce338f --- /dev/null +++ b/docshell/base/BrowsingContext.cpp @@ -0,0 +1,3301 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/BrowsingContext.h" + +#include "ipc/IPCMessageUtils.h" + +#ifdef ACCESSIBILITY +# include "mozilla/a11y/DocAccessibleParent.h" +# include "mozilla/a11y/Platform.h" +# include "mozilla/a11y/ProxyAccessibleBase.h" +# include "nsAccessibilityService.h" +# if defined(XP_WIN) +# include "mozilla/a11y/AccessibleWrap.h" +# include "mozilla/a11y/Compatibility.h" +# include "mozilla/a11y/nsWinUtils.h" +# endif +#endif +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/dom/BrowsingContextGroup.h" +#include "mozilla/dom/BrowsingContextBinding.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/HTMLEmbedElement.h" +#include "mozilla/dom/HTMLIFrameElement.h" +#include "mozilla/dom/Location.h" +#include "mozilla/dom/LocationBinding.h" +#include "mozilla/dom/PopupBlocker.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/SessionStorageManager.h" +#include "mozilla/dom/StructuredCloneTags.h" +#include "mozilla/dom/UserActivationIPCUtils.h" +#include "mozilla/dom/WindowBinding.h" +#include "mozilla/dom/WindowGlobalChild.h" +#include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/dom/WindowProxyHolder.h" +#include "mozilla/dom/SyncedContextInlines.h" +#include "mozilla/dom/XULFrameElement.h" +#include "mozilla/net/DocumentLoadListener.h" +#include "mozilla/net/RequestContextService.h" +#include "mozilla/Assertions.h" +#include "mozilla/AsyncEventDispatcher.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/Components.h" +#include "mozilla/HashTable.h" +#include "mozilla/Logging.h" +#include "mozilla/MediaFeatureChange.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPrefs_fission.h" +#include "mozilla/StaticPrefs_page_load.h" +#include "mozilla/StaticPtr.h" +#include "nsIURIFixup.h" +#include "nsIXULRuntime.h" + +#include "nsDocShell.h" +#include "nsDocShellLoadState.h" +#include "nsFocusManager.h" +#include "nsGlobalWindowOuter.h" +#include "nsIObserverService.h" +#include "nsISHistory.h" +#include "nsContentUtils.h" +#include "nsQueryObject.h" +#include "nsSandboxFlags.h" +#include "nsScriptError.h" +#include "nsThreadUtils.h" +#include "xpcprivate.h" + +#include "AutoplayPolicy.h" +#include "GVAutoplayRequestStatusIPC.h" + +extern mozilla::LazyLogModule gAutoplayPermissionLog; +extern mozilla::LazyLogModule gTimeoutDeferralLog; + +#define AUTOPLAY_LOG(msg, ...) \ + MOZ_LOG(gAutoplayPermissionLog, LogLevel::Debug, (msg, ##__VA_ARGS__)) + +namespace IPC { +// Allow serialization and deserialization of OrientationType over IPC +template <> +struct ParamTraits<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::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_> {}; +} // 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"); + +typedef nsDataHashtable<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->Put(aBrowsingContext->Id(), aBrowsingContext); + if (aBrowsingContext->IsTopContent()) { + sCurrentTopByBrowserId->Put(aBrowsingContext->BrowserId(), + aBrowsingContext); + } + + aBrowsingContext->Group()->Register(aBrowsingContext); +} + +BrowsingContext* BrowsingContext::GetParent() const { + return mParentWindow ? mParentWindow->GetBrowsingContext() : nullptr; +} + +bool BrowsingContext::IsInSubtreeOf(BrowsingContext* aContext) { + BrowsingContext* bc = this; + do { + if (bc == aContext) { + return true; + } + } while ((bc = bc->GetParent())); + return false; +} + +BrowsingContext* BrowsingContext::Top() { + BrowsingContext* bc = this; + while (bc->mParentWindow) { + bc = bc->GetParent(); + } + return bc; +} + +int32_t BrowsingContext::IndexOf(BrowsingContext* aChild) { + int32_t index = -1; + for (BrowsingContext* child : Children()) { + ++index; + if (child == aChild) { + break; + } + } + return index; +} + +WindowContext* BrowsingContext::GetTopWindowContext() { + if (mParentWindow) { + return mParentWindow->TopWindowContext(); + } + return mCurrentWindowContext; +} + +/* static */ +void BrowsingContext::Init() { + if (!sBrowsingContexts) { + sBrowsingContexts = new BrowsingContextMap(); + sCurrentTopByBrowserId = new BrowsingContextMap(); + ClearOnShutdown(&sBrowsingContexts); + ClearOnShutdown(&sCurrentTopByBrowserId); + } +} + +/* static */ +LogModule* BrowsingContext::GetLog() { return gBrowsingContextLog; } + +/* static */ +already_AddRefed<BrowsingContext> 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 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.mName = aName; + + if (aOpener) { + MOZ_DIAGNOSTIC_ASSERT(!aParent, + "new BC with both initial opener and parent"); + MOZ_DIAGNOSTIC_ASSERT(aOpener->Group() == group); + MOZ_DIAGNOSTIC_ASSERT(aOpener->mType == aType); + fields.mOpenerId = aOpener->Id(); + fields.mHadOriginalOpener = true; + } + + if (aParent) { + MOZ_DIAGNOSTIC_ASSERT(parentBC->Group() == group); + MOZ_DIAGNOSTIC_ASSERT(parentBC->mType == aType); + fields.mEmbedderInnerWindowId = aParent->WindowID(); + + // XXX(farre): Can/Should we check aParent->IsLoading() here? (Bug + // 1608448) Check if the parent was itself loading already + auto readystate = aParent->GetDocument()->GetReadyStateEnum(); + fields.mAncestorLoading = + parentBC->GetAncestorLoading() || + readystate == Document::ReadyState::READYSTATE_LOADING || + readystate == Document::ReadyState::READYSTATE_INTERACTIVE; + } + + fields.mBrowserId = + parentBC ? parentBC->GetBrowserId() : nsContentUtils::GenerateBrowserId(); + + fields.mOpenerPolicy = nsILoadInfo::OPENER_POLICY_UNSAFE_NONE; + if (aOpener && aOpener->SameOriginWithTop()) { + // We inherit the opener policy if there is a creator and if the creator's + // origin is same origin with the creator's top-level origin. + // If it is cross origin we should not inherit the CrossOriginOpenerPolicy + fields.mOpenerPolicy = aOpener->Top()->GetOpenerPolicy(); + } else if (aOpener) { + // They are not same origin + auto topPolicy = aOpener->Top()->GetOpenerPolicy(); + MOZ_RELEASE_ASSERT(topPolicy == nsILoadInfo::OPENER_POLICY_UNSAFE_NONE || + topPolicy == + nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_ALLOW_POPUPS); + } + + nsContentUtils::GenerateUUIDInPlace(fields.mHistoryID); + fields.mExplicitActive = [&] { + if (parentBC) { + // Non-root browsing-contexts inherit their status from its parent. + return ExplicitActiveStatus::None; + } + if (aType == Type::Content) { + // Content gets managed by the chrome front-end / embedder element and + // starts as inactive. + return ExplicitActiveStatus::Inactive; + } + // Chrome starts as active. + return ExplicitActiveStatus::Active; + }(); + + fields.mFullZoom = parentBC ? parentBC->FullZoom() : 1.0f; + fields.mTextZoom = parentBC ? parentBC->TextZoom() : 1.0f; + + bool allowContentRetargeting = + inherit ? inherit->GetAllowContentRetargetingOnChildren() : true; + fields.mAllowContentRetargeting = allowContentRetargeting; + fields.mAllowContentRetargetingOnChildren = allowContentRetargeting; + + // Assume top allows fullscreen for its children unless otherwise stated. + // Subframes start with it false unless otherwise noted in SetEmbedderElement. + fields.mFullscreenAllowedByOwner = !aParent; + + fields.mAllowPlugins = inherit ? inherit->GetAllowPlugins() : true; + + fields.mDefaultLoadFlags = + inherit ? inherit->GetDefaultLoadFlags() : nsIRequest::LOAD_NORMAL; + + fields.mOrientationLock = mozilla::hal::eScreenOrientation_None; + + fields.mUseGlobalHistory = inherit ? inherit->GetUseGlobalHistory() : false; + + fields.mUseErrorPages = true; + + fields.mTouchEventsOverrideInternal = TouchEventsOverride::None; + + RefPtr<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) { + RefPtr<BrowsingContext> bc( + CreateDetached(nullptr, nullptr, nullptr, u""_ns, aType)); + bc->mWindowless = bc->IsContent(); + bc->mEmbeddedByThisProcess = true; + bc->EnsureAttached(); + return bc.forget(); +} + +void BrowsingContext::EnsureAttached() { + if (!mEverAttached) { + Register(this); + + // Attach the browsing context to the tree. + Attach(/* aFromIPC */ false, /* aOriginProcess */ nullptr); + } +} + +/* static */ +void BrowsingContext::CreateFromIPC(BrowsingContext::IPCInitializer&& aInit, + BrowsingContextGroup* aGroup, + ContentParent* aOriginProcess) { + MOZ_DIAGNOSTIC_ASSERT(aOriginProcess || XRE_IsContentProcess()); + MOZ_DIAGNOSTIC_ASSERT(aGroup); + + uint64_t originId = 0; + if (aOriginProcess) { + originId = aOriginProcess->ChildID(); + aGroup->EnsureHostProcess(aOriginProcess); + } + + MOZ_LOG(GetLog(), LogLevel::Debug, + ("Creating 0x%08" PRIx64 " from IPC (origin=0x%08" PRIx64 ")", + aInit.mId, originId)); + + RefPtr<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; + if (context->GetHasSessionHistory()) { + context->CreateChildSHistory(); + if (mozilla::SessionHistoryInParent()) { + context->GetChildSessionHistory()->SetIndexAndLength( + aInit.mSessionHistoryIndex, aInit.mSessionHistoryCount, nsID()); + } + } + + // NOTE: Call through the `Set` methods for these values to ensure that any + // relevant process-local state is also updated. + context->SetOriginAttributes(aInit.mOriginAttributes); + context->SetRemoteTabs(aInit.mUseRemoteTabs); + context->SetRemoteSubframes(aInit.mUseRemoteSubframes); + context->mRequestContextId = aInit.mRequestContextId; + // NOTE: Private browsing ID is set by `SetOriginAttributes`. + + Register(context); + + context->Attach(/* aFromIPC */ true, aOriginProcess); +} + +BrowsingContext::BrowsingContext(WindowContext* aParentWindow, + BrowsingContextGroup* aGroup, + uint64_t aBrowsingContextId, Type aType, + FieldValues&& aInit) + : mFields(std::move(aInit)), + mType(aType), + mBrowsingContextId(aBrowsingContextId), + mGroup(aGroup), + mParentWindow(aParentWindow), + mPrivateBrowsingId(0), + mEverAttached(false), + mIsInProcess(false), + mIsDiscarded(false), + mWindowless(false), + mDanglingRemoteOuterProxies(false), + mEmbeddedByThisProcess(false), + mUseRemoteTabs(false), + mUseRemoteSubframes(false), + mCreatedDynamically(false) { + MOZ_RELEASE_ASSERT(!mParentWindow || mParentWindow->Group() == mGroup); + MOZ_RELEASE_ASSERT(mBrowsingContextId != 0); + MOZ_RELEASE_ASSERT(mGroup); +} + +void BrowsingContext::SetDocShell(nsIDocShell* aDocShell) { + // XXX(nika): We should communicate that we are now an active BrowsingContext + // process to the parent & do other validation here. + MOZ_DIAGNOSTIC_ASSERT(mEverAttached); + MOZ_RELEASE_ASSERT(aDocShell->GetBrowsingContext() == this); + mDocShell = aDocShell; + mDanglingRemoteOuterProxies = !mIsInProcess; + mIsInProcess = true; + if (mChildSessionHistory) { + mChildSessionHistory->SetIsInProcess(true); + } +} + +// This class implements a callback that will return the remote window proxy for +// mBrowsingContext in that compartment, if it has one. It also removes the +// proxy from the map, because the object will be transplanted into another kind +// of object. +class MOZ_STACK_CLASS CompartmentRemoteProxyTransplantCallback + : public js::CompartmentTransplantCallback { + public: + explicit CompartmentRemoteProxyTransplantCallback( + BrowsingContext* aBrowsingContext) + : mBrowsingContext(aBrowsingContext) {} + + virtual JSObject* getObjectToTransplant( + JS::Compartment* compartment) override { + auto* priv = xpc::CompartmentPrivate::Get(compartment); + if (!priv) { + return nullptr; + } + + auto& map = priv->GetRemoteProxyMap(); + auto result = map.lookup(mBrowsingContext); + if (!result) { + return nullptr; + } + JSObject* resultObject = result->value(); + map.remove(result); + + return resultObject; + } + + private: + BrowsingContext* mBrowsingContext; +}; + +void BrowsingContext::CleanUpDanglingRemoteOuterWindowProxies( + JSContext* aCx, JS::MutableHandle<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 (current->IsCached()) { + return false; + } + } while ((current = current->GetParent())); + + return false; +} + +bool BrowsingContext::GetIsActiveBrowserWindow() { + if (!XRE_IsParentProcess()) { + return Top()->GetIsActiveBrowserWindowInternal(); + } + + // chrome:// urls loaded in the parent won't receive + // their own activation so we defer to the top chrome + // Browsing Context when in the parent process. + RefPtr<CanonicalBrowsingContext> chromeTop = + Canonical()->TopCrossChromeBoundary(); + return chromeTop->GetIsActiveBrowserWindowInternal(); +} + +void BrowsingContext::SetIsActiveBrowserWindow(bool aActive) { + Unused << SetIsActiveBrowserWindowInternal(aActive); +} + +bool BrowsingContext::FullscreenAllowed() const { + for (auto* current = this; current; current = current->GetParent()) { + if (!current->GetFullscreenAllowedByOwner()) { + return false; + } + } + return true; +} + +static bool OwnerAllowsFullscreen(const Element& aEmbedder) { + if (aEmbedder.IsXULElement()) { + return !aEmbedder.HasAttr(nsGkAtoms::disablefullscreen); + } + if (aEmbedder.IsHTMLElement(nsGkAtoms::iframe)) { + // This is controlled by feature policy. + return true; + } + if (const auto* embed = HTMLEmbedElement::FromNode(aEmbedder)) { + return embed->AllowFullscreen(); + } + return false; +} + +void BrowsingContext::SetEmbedderElement(Element* aEmbedder) { + mEmbeddedByThisProcess = true; + + // Update embedder-element-specific fields in a shared transaction. + // Don't do this when clearing our embedder, as we're being destroyed either + // way. + if (aEmbedder) { + Transaction txn; + txn.SetEmbedderElementType(Some(aEmbedder->LocalName())); + if (nsCOMPtr<nsPIDOMWindowInner> inner = + do_QueryInterface(aEmbedder->GetOwnerGlobal())) { + txn.SetEmbedderInnerWindowId(inner->WindowID()); + } + txn.SetFullscreenAllowedByOwner(OwnerAllowsFullscreen(*aEmbedder)); + if (XRE_IsParentProcess() && IsTopContent()) { + nsAutoString messageManagerGroup; + if (aEmbedder->IsXULElement()) { + aEmbedder->GetAttr(nsGkAtoms::messagemanagergroup, messageManagerGroup); + if (!aEmbedder->AttrValueIs(kNameSpaceID_None, + nsGkAtoms::initiallyactive, + nsGkAtoms::_false, eIgnoreCase)) { + txn.SetExplicitActive(ExplicitActiveStatus::Active); + } + } + txn.SetMessageManagerGroup(messageManagerGroup); + + bool useGlobalHistory = !aEmbedder->HasAttr( + kNameSpaceID_None, nsGkAtoms::disableglobalhistory); + txn.SetUseGlobalHistory(useGlobalHistory); + } + + MOZ_ALWAYS_SUCCEEDS(txn.Commit(this)); + } + + mEmbedderElement = aEmbedder; +} + +void BrowsingContext::Embed() { + if (auto* frame = HTMLIFrameElement::FromNode(mEmbedderElement)) { + frame->BindToBrowsingContext(this); + } +} + +void BrowsingContext::Attach(bool aFromIPC, ContentParent* aOriginProcess) { + MOZ_DIAGNOSTIC_ASSERT(!mEverAttached); + mEverAttached = true; + + if (MOZ_LOG_TEST(GetLog(), LogLevel::Debug)) { + nsAutoCString suffix; + mOriginAttributes.CreateSuffix(suffix); + MOZ_LOG(GetLog(), LogLevel::Debug, + ("%s: Connecting 0x%08" PRIx64 " to 0x%08" PRIx64 + " (private=%d, remote=%d, fission=%d, oa=%s)", + XRE_IsParentProcess() ? "Parent" : "Child", Id(), + GetParent() ? GetParent()->Id() : 0, (int)mPrivateBrowsingId, + (int)mUseRemoteTabs, (int)mUseRemoteSubframes, suffix.get())); + } + + MOZ_DIAGNOSTIC_ASSERT(mGroup); + MOZ_DIAGNOSTIC_ASSERT(!mIsDiscarded); + + AssertCoherentLoadContext(); + + // Add ourselves either to our parent or BrowsingContextGroup's child list. + if (mParentWindow) { + if (!aFromIPC) { + MOZ_DIAGNOSTIC_ASSERT(!mParentWindow->IsDiscarded(), + "local attach in discarded window"); + MOZ_DIAGNOSTIC_ASSERT(!GetParent()->IsDiscarded(), + "local attach call in discarded bc"); + } + + mParentWindow->AppendChildBrowsingContext(this); + } else { + mGroup->Toplevels().AppendElement(this); + } + + if (GetIsPopupSpam()) { + PopupBlocker::RegisterOpenPopupSpam(); + } + + if (IsTop() && GetHasSessionHistory() && !mChildSessionHistory) { + CreateChildSHistory(); + } + + if (XRE_IsContentProcess() && !aFromIPC) { + // Send attach to our parent if we need to. + ContentChild::GetSingleton()->SendCreateBrowsingContext( + mGroup->Id(), GetIPCInitializer()); + } else if (XRE_IsParentProcess()) { + mGroup->EachOtherParent(aOriginProcess, [&](ContentParent* aParent) { + MOZ_DIAGNOSTIC_ASSERT(IsContent(), + "chrome BCG cannot be synced to content process"); + if (!Canonical()->IsEmbeddedInProcess(aParent->ChildID())) { + Unused << aParent->SendCreateBrowsingContext(mGroup->Id(), + GetIPCInitializer()); + } + }); + + if (IsTopContent() && !Canonical()->GetWebProgress()) { + Canonical()->mWebProgress = new BrowsingContextWebProgress(); + } + } + + if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) { + obs->NotifyWhenScriptSafe(ToSupports(this), "browsing-context-attached", + nullptr); + } +} + +void BrowsingContext::Detach(bool aFromIPC) { + MOZ_LOG(GetLog(), LogLevel::Debug, + ("%s: Detaching 0x%08" PRIx64 " from 0x%08" PRIx64, + XRE_IsParentProcess() ? "Parent" : "Child", Id(), + GetParent() ? GetParent()->Id() : 0)); + + MOZ_DIAGNOSTIC_ASSERT(mEverAttached); + MOZ_DIAGNOSTIC_ASSERT(!mIsDiscarded); + + nsCOMPtr<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)) { + return; + } + + if (mParentWindow) { + mParentWindow->RemoveChildBrowsingContext(this); + } else { + mGroup->Toplevels().RemoveElement(this); + } + + auto callSendDiscard = [&](auto* aActor) { + // Hold a strong reference to ourself, and keep our BrowsingContextGroup + // alive, until the responses comes back to ensure we don't die while + // messages relating to this context are in-flight. + // + // When the callback is called, the keepalive on our group will be + // destroyed, and the reference to the BrowsingContext will be dropped, + // which may cause it to be fully destroyed. + mGroup->AddKeepAlive(); + auto callback = [self = RefPtr{this}](auto) { + self->mGroup->RemoveKeepAlive(); + }; + + aActor->SendDiscardBrowsingContext(this, callback, callback); + }; + + if (XRE_IsParentProcess()) { + Group()->EachParent([&](ContentParent* aParent) { + // Only the embedder process is allowed to initiate a BrowsingContext + // detach, so if we've gotten here, the host process already knows we've + // been detached, and there's no need to tell it again. + // + // If the owner process is not the same as the embedder process, its + // BrowsingContext will be detached when its nsWebBrowser instance is + // destroyed. + if (!Canonical()->IsEmbeddedInProcess(aParent->ChildID()) && + !Canonical()->IsOwnedByProcess(aParent->ChildID())) { + callSendDiscard(aParent); + } + }); + } else if (!aFromIPC) { + callSendDiscard(ContentChild::GetSingleton()); + } + + mGroup->Unregister(this); + UnregisterBrowserId(this); + mIsDiscarded = true; + + if (XRE_IsParentProcess()) { + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm) { + fm->BrowsingContextDetached(this); + } + } + + if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) { + obs->NotifyObservers(ToSupports(this), "browsing-context-discarded", + nullptr); + } + + // NOTE: Doesn't use SetClosed, as it will be set in all processes + // automatically by calls to Detach() + mFields.SetWithoutSyncing<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::PrepareForProcessChange() { + MOZ_LOG(GetLog(), LogLevel::Debug, + ("%s: Preparing 0x%08" PRIx64 " for a process change", + XRE_IsParentProcess() ? "Parent" : "Child", Id())); + + MOZ_ASSERT(mIsInProcess, "Must currently be an in-process frame"); + MOZ_ASSERT(!mIsDiscarded, "We're already closed?"); + + mIsInProcess = false; + mUserGestureStart = TimeStamp(); + + // NOTE: For now, clear our nsDocShell reference, as we're primarily in a + // different process now. This may need to change in the future with + // Cross-Process BFCache. + mDocShell = nullptr; + if (mChildSessionHistory) { + // This can be removed once session history is stored exclusively in the + // parent process. + mChildSessionHistory->SetIsInProcess(false); + } + + if (!mWindowProxy) { + return; + } + + // We have to go through mWindowProxy rather than calling GetDOMWindow() on + // mDocShell because the mDocshell reference gets cleared immediately after + // the window is closed. + nsGlobalWindowOuter::PrepareForProcessChange(mWindowProxy); + MOZ_ASSERT(!mWindowProxy); +} + +bool BrowsingContext::IsCached() const { + return mParentWindow && mParentWindow->IsCached(); +} + +bool BrowsingContext::IsTargetable() const { + return !GetClosed() && !mIsDiscarded && !IsCached(); +} + +bool BrowsingContext::HasOpener() const { + return sBrowsingContexts->Contains(GetOpenerId()); +} + +bool BrowsingContext::AncestorsAreCurrent() const { + const BrowsingContext* bc = this; + while (true) { + if (bc->IsDiscarded()) { + return false; + } + + if (WindowContext* wc = bc->GetParentWindowContext()) { + if (wc->IsCached() || wc->IsDiscarded()) { + return false; + } + + bc = wc->GetBrowsingContext(); + } else { + return true; + } + } +} + +Span<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()); +} + +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::PreOrderWalk( + const std::function<void(BrowsingContext*)>& aCallback) { + aCallback(this); + + AutoTArray<RefPtr<BrowsingContext>, 8> children; + children.AppendElements(Children()); + + for (auto& child : children) { + child->PreOrderWalk(aCallback); + } +} + +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); + }); +} + +// FindWithName follows the rules for choosing a browsing context, +// with the exception of sandboxing for iframes. The implementation +// for arbitrarily choosing between two browsing contexts with the +// same name is as follows: +// +// 1) The start browsing context, i.e. 'this' +// 2) Descendants in insertion order +// 3) The parent +// 4) Siblings and their children, both in insertion order +// 5) After this we iteratively follow the parent chain, repeating 3 +// and 4 until +// 6) If there is no parent, consider all other top level browsing +// contexts and their children, both in insertion order +// +// See +// https://html.spec.whatwg.org/multipage/browsers.html#the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name +BrowsingContext* BrowsingContext::FindWithName( + const nsAString& aName, bool aUseEntryGlobalForAccessCheck) { + RefPtr<BrowsingContext> requestingContext = this; + if (aUseEntryGlobalForAccessCheck) { + if (nsCOMPtr<nsIDocShell> caller = do_GetInterface(GetEntryGlobal())) { + if (caller->GetBrowsingContext()) { + requestingContext = caller->GetBrowsingContext(); + } + } + } + + BrowsingContext* found = nullptr; + if (aName.IsEmpty()) { + // You can't find a browsing context with an empty name. + found = nullptr; + } else if (aName.LowerCaseEqualsLiteral("_blank")) { + // Just return null. Caller must handle creating a new window with + // a blank name. + found = nullptr; + } else if (nsContentUtils::IsSpecialName(aName)) { + found = FindWithSpecialName(aName, *requestingContext); + } else if (BrowsingContext* child = + FindWithNameInSubtree(aName, *requestingContext)) { + found = child; + } else { + BrowsingContext* current = this; + + do { + Span<RefPtr<BrowsingContext>> siblings; + BrowsingContext* parent = current->GetParent(); + + if (!parent) { + // We've reached the root of the tree, consider browsing + // contexts in the same browsing context group. + siblings = mGroup->Toplevels(); + } else if (parent->NameEquals(aName) && + requestingContext->CanAccess(parent) && + parent->IsTargetable()) { + found = parent; + break; + } else { + siblings = parent->Children(); + } + + for (BrowsingContext* sibling : siblings) { + if (sibling == current) { + continue; + } + + if (BrowsingContext* relative = + sibling->FindWithNameInSubtree(aName, *requestingContext)) { + found = relative; + // Breaks the outer loop + parent = nullptr; + break; + } + } + + current = parent; + } while (current); + } + + // Helpers should perform access control checks, which means that we + // only need to assert that we can access found. + MOZ_DIAGNOSTIC_ASSERT(!found || requestingContext->CanAccess(found)); + + return found; +} + +BrowsingContext* BrowsingContext::FindChildWithName( + const nsAString& aName, BrowsingContext& aRequestingContext) { + if (aName.IsEmpty()) { + // You can't find a browsing context with the empty name. + return nullptr; + } + + for (BrowsingContext* child : Children()) { + if (child->NameEquals(aName) && aRequestingContext.CanAccess(child) && + child->IsTargetable()) { + return child; + } + } + + return nullptr; +} + +BrowsingContext* BrowsingContext::FindWithSpecialName( + const nsAString& aName, BrowsingContext& aRequestingContext) { + // TODO(farre): Neither BrowsingContext nor nsDocShell checks if the + // browsing context pointed to by a special name is active. Should + // it be? See Bug 1527913. + if (aName.LowerCaseEqualsLiteral("_self")) { + return this; + } + + if (aName.LowerCaseEqualsLiteral("_parent")) { + if (BrowsingContext* parent = GetParent()) { + return aRequestingContext.CanAccess(parent) ? parent : nullptr; + } + return this; + } + + if (aName.LowerCaseEqualsLiteral("_top")) { + BrowsingContext* top = Top(); + + return aRequestingContext.CanAccess(top) ? top : nullptr; + } + + return nullptr; +} + +BrowsingContext* BrowsingContext::FindWithNameInSubtree( + const nsAString& aName, BrowsingContext& aRequestingContext) { + MOZ_DIAGNOSTIC_ASSERT(!aName.IsEmpty()); + + if (NameEquals(aName) && aRequestingContext.CanAccess(this) && + IsTargetable()) { + return this; + } + + for (BrowsingContext* child : Children()) { + if (BrowsingContext* found = + child->FindWithNameInSubtree(aName, aRequestingContext)) { + return found; + } + } + + return nullptr; +} + +// For historical context, see: +// +// Bug 13871: Prevent frameset spoofing +// Bug 103638: Targets with same name in different windows open in wrong +// window with javascript +// Bug 408052: Adopt "ancestor" frame navigation policy +// Bug 1570207: Refactor logic to rely on BrowsingContextGroups to enforce +// origin attribute isolation. +bool BrowsingContext::CanAccess(BrowsingContext* aTarget, + bool aConsiderOpener) { + MOZ_ASSERT( + mDocShell, + "CanAccess() may only be called in the process of the accessing window"); + MOZ_ASSERT(aTarget, "Must have a target"); + + MOZ_DIAGNOSTIC_ASSERT( + Group() == aTarget->Group(), + "A BrowsingContext should never see a context from a different group"); + + // A frame can navigate itself and its own root. + if (aTarget == this || aTarget == Top()) { + return true; + } + + // A frame can navigate any frame with a same-origin ancestor. + for (BrowsingContext* bc = aTarget; bc; bc = bc->GetParent()) { + if (bc->mDocShell && nsDocShell::ValidateOrigin(this, bc)) { + return true; + } + } + + // If the target is a top-level document, a frame can navigate it if it can + // navigate its opener. + if (aConsiderOpener && !aTarget->GetParent()) { + if (RefPtr<BrowsingContext> opener = aTarget->GetOpener()) { + return CanAccess(opener, false); + } + } + + return false; +} + +bool BrowsingContext::IsSandboxedFrom(BrowsingContext* aTarget) { + // If no target then not sandboxed. + if (!aTarget) { + return false; + } + + // We cannot be sandboxed from ourselves. + if (aTarget == this) { + return false; + } + + // Default the sandbox flags to our flags, so that if we can't retrieve the + // active document, we will still enforce our own. + uint32_t sandboxFlags = GetSandboxFlags(); + if (mDocShell) { + if (RefPtr<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. + if (!(sandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION_USER_ACTIVATION) && + mCurrentWindowContext && + 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)); + } +} + +Tuple<nsCOMPtr<nsIPrincipal>, nsCOMPtr<nsIPrincipal>> +BrowsingContext::GetTriggeringAndInheritPrincipalsForCurrentLoad() { + nsCOMPtr<nsIPrincipal> triggeringPrincipal = + GetSavedPrincipal(mTriggeringPrincipal); + nsCOMPtr<nsIPrincipal> principalToInherit = + GetSavedPrincipal(mPrincipalToInherit); + return MakeTuple(triggeringPrincipal, principalToInherit); +} + +nsIPrincipal* BrowsingContext::GetSavedPrincipal( + Maybe<PrincipalWithLoadIdentifierTuple> aPrincipalTuple) { + if (aPrincipalTuple) { + nsCOMPtr<nsIPrincipal> principal; + uint64_t loadIdentifier; + Tie(principal, loadIdentifier) = *aPrincipalTuple; + // We want to return a principal only if the load identifier for it + // matches the current one for this BC. + if (auto current = GetCurrentLoadIdentifier(); + current && *current == loadIdentifier) { + return principal; + } + } + return nullptr; +} + +BrowsingContext::~BrowsingContext() { + MOZ_DIAGNOSTIC_ASSERT(!mParentWindow || + !mParentWindow->mChildren.Contains(this)); + MOZ_DIAGNOSTIC_ASSERT(!mGroup || !mGroup->Toplevels().Contains(this)); + + mDeprioritizedLoadRunner.clear(); + + if (sBrowsingContexts) { + sBrowsingContexts->Remove(Id()); + } + UnregisterBrowserId(this); +} + +/* static */ +void BrowsingContext::DiscardFromContentParent(ContentParent* aCP) { + MOZ_ASSERT(XRE_IsParentProcess()); + + if (sBrowsingContexts) { + AutoTArray<RefPtr<BrowsingContext>, 8> toDiscard; + for (const auto& entry : *sBrowsingContexts) { + auto* bc = entry.GetData()->Canonical(); + if (!bc->IsDiscarded() && bc->IsEmbeddedInProcess(aCP->ChildID())) { + toDiscard.AppendElement(bc); + } + } + + for (BrowsingContext* bc : toDiscard) { + bc->Detach(/* aFromIPC */ true); + } + } +} + +nsISupports* BrowsingContext::GetParentObject() const { + return xpc::NativeGlobal(xpc::PrivilegedJunkScope()); +} + +JSObject* BrowsingContext::WrapObject(JSContext* aCx, + JS::Handle<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::RootedValue val(aCx, JS::NullValue()); + // We'll get rooting hazard errors from the RefPtr destructor if it isn't + // destroyed before we try to return a raw JSObject*, so create it in its own + // scope. + if (RefPtr<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); + } + } + 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 + +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.IsDiscarded() || (sourceBC && sourceBC->IsSandboxedFrom(this))) { + return NS_ERROR_DOM_INVALID_ACCESS_ERR; + } + return NS_OK; +} + +nsresult BrowsingContext::LoadURI(nsDocShellLoadState* aLoadState, + bool aSetNavigating) { + // Per spec, most load attempts are silently ignored when a BrowsingContext is + // null (which in our code corresponds to discarded), so we simply fail + // silently in those cases. Regardless, we cannot trigger loads in/from + // discarded BrowsingContexts via IPC, so we need to abort in any case. + if (IsDiscarded()) { + return NS_OK; + } + + MOZ_DIAGNOSTIC_ASSERT(aLoadState->TargetBrowsingContext().IsNull(), + "Targeting occurs in InternalLoad"); + + if (mDocShell) { + return mDocShell->LoadURI(aLoadState, aSetNavigating); + } + + // Note: We do this check both here and in `nsDocShell::InternalLoad`, since + // document-specific sandbox flags are only available in the process + // triggering the load, and we don't want the target process to have to trust + // the triggering process to do the appropriate checks for the + // BrowsingContext's sandbox flags. + MOZ_TRY(CheckSandboxFlags(aLoadState)); + SetTriggeringAndInheritPrincipals(aLoadState->TriggeringPrincipal(), + aLoadState->PrincipalToInherit(), + aLoadState->GetLoadIdentifier()); + + const auto& sourceBC = aLoadState->SourceBrowsingContext(); + MOZ_DIAGNOSTIC_ASSERT(!sourceBC || sourceBC->Group() == Group()); + if (sourceBC && sourceBC->IsInProcess()) { + if (!sourceBC->CanAccess(this)) { + return NS_ERROR_DOM_PROP_ACCESS_DENIED; + } + + nsCOMPtr<nsPIDOMWindowOuter> win(sourceBC->GetDOMWindow()); + if (WindowGlobalChild* wgc = + win->GetCurrentInnerWindow()->GetWindowGlobalChild()) { + wgc->SendLoadURI(this, aLoadState, aSetNavigating); + } + } else if (XRE_IsParentProcess()) { + if (Canonical()->LoadInParent(aLoadState, aSetNavigating)) { + return NS_OK; + } + + if (ContentParent* cp = Canonical()->GetContentParent()) { + // Attempt to initiate this load immediately in the parent, if it succeeds + // it'll return a unique identifier so that we can find it later. + uint64_t loadIdentifier = 0; + if (Canonical()->AttemptSpeculativeLoadInParent(aLoadState)) { + MOZ_DIAGNOSTIC_ASSERT(GetCurrentLoadIdentifier().isSome()); + loadIdentifier = GetCurrentLoadIdentifier().value(); + aLoadState->SetChannelInitialized(true); + } + + cp->TransmitBlobDataIfBlobURL(aLoadState->URI(), + aLoadState->TriggeringPrincipal()); + + // Setup a confirmation callback once the content process receives this + // load. Normally we'd expect a PDocumentChannel actor to have been + // created to claim the load identifier by that time. If not, then it + // won't be coming, so make sure we clean up and deregister. + cp->SendLoadURI(this, aLoadState, aSetNavigating) + ->Then(GetMainThreadSerialEventTarget(), __func__, + [loadIdentifier]( + const PContentParent::LoadURIPromise::ResolveOrRejectValue& + aValue) { + if (loadIdentifier) { + net::DocumentLoadListener::CleanupParentLoadAttempt( + loadIdentifier); + } + }); + } + } else { + MOZ_DIAGNOSTIC_ASSERT(sourceBC); + if (!sourceBC) { + return NS_ERROR_UNEXPECTED; + } + // If we're in a content process and the source BC is no longer in-process, + // just fail silently. + } + return NS_OK; +} + +nsresult BrowsingContext::InternalLoad(nsDocShellLoadState* aLoadState) { + if (IsDiscarded()) { + return NS_OK; + } + SetTriggeringAndInheritPrincipals(aLoadState->TriggeringPrincipal(), + aLoadState->PrincipalToInherit(), + aLoadState->GetLoadIdentifier()); + + MOZ_DIAGNOSTIC_ASSERT(aLoadState->Target().IsEmpty(), + "should already have retargeted"); + MOZ_DIAGNOSTIC_ASSERT(!aLoadState->TargetBrowsingContext().IsNull(), + "should have target bc set"); + MOZ_DIAGNOSTIC_ASSERT(aLoadState->TargetBrowsingContext() == this, + "must be targeting this BrowsingContext"); + + if (mDocShell) { + return nsDocShell::Cast(mDocShell)->InternalLoad(aLoadState); + } + + // Note: We do this check both here and in `nsDocShell::InternalLoad`, since + // document-specific sandbox flags are only available in the process + // triggering the load, and we don't want the target process to have to trust + // the triggering process to do the appropriate checks for the + // BrowsingContext's sandbox flags. + MOZ_TRY(CheckSandboxFlags(aLoadState)); + + const auto& sourceBC = aLoadState->SourceBrowsingContext(); + if (XRE_IsParentProcess()) { + ContentParent* cp = Canonical()->GetContentParent(); + if (!cp || !cp->CanSend()) { + return NS_ERROR_FAILURE; + } + + MOZ_ALWAYS_SUCCEEDS( + SetCurrentLoadIdentifier(Some(aLoadState->GetLoadIdentifier()))); + Unused << cp->SendInternalLoad(aLoadState); + } else { + MOZ_DIAGNOSTIC_ASSERT(sourceBC); + MOZ_DIAGNOSTIC_ASSERT(sourceBC->Group() == Group()); + + if (!sourceBC->CanAccess(this)) { + return NS_ERROR_DOM_PROP_ACCESS_DENIED; + } + + nsCOMPtr<nsPIDOMWindowOuter> win(sourceBC->GetDOMWindow()); + WindowGlobalChild* wgc = + win->GetCurrentInnerWindow()->GetWindowGlobalChild(); + if (!wgc || !wgc->CanSend()) { + return NS_ERROR_FAILURE; + } + + MOZ_ALWAYS_SUCCEEDS( + SetCurrentLoadIdentifier(Some(aLoadState->GetLoadIdentifier()))); + wgc->SendInternalLoad(aLoadState); + } + + return NS_OK; +} + +void BrowsingContext::DisplayLoadError(const nsAString& aURI) { + MOZ_LOG(GetLog(), LogLevel::Debug, ("DisplayLoadError")); + MOZ_DIAGNOSTIC_ASSERT(!IsDiscarded()); + MOZ_DIAGNOSTIC_ASSERT(mDocShell || XRE_IsParentProcess()); + + if (mDocShell) { + bool didDisplayLoadError = false; + mDocShell->DisplayLoadError(NS_ERROR_MALFORMED_URI, nullptr, + PromiseFlatString(aURI).get(), nullptr, + &didDisplayLoadError); + } else { + if (ContentParent* cp = Canonical()->GetContentParent()) { + Unused << cp->SendDisplayLoadError(this, PromiseFlatString(aURI)); + } + } +} + +WindowProxyHolder BrowsingContext::Window() { + return WindowProxyHolder(Self()); +} + +WindowProxyHolder BrowsingContext::GetFrames(ErrorResult& aError) { + return Window(); +} + +void BrowsingContext::Close(CallerType aCallerType, ErrorResult& aError) { + if (mIsDiscarded) { + return; + } + + if (IsFrame()) { + // .close() on frames is a no-op. + return; + } + + if (GetDOMWindow()) { + nsGlobalWindowOuter::Cast(GetDOMWindow()) + ->CloseOuter(aCallerType == CallerType::System); + return; + } + + // This is a bit of a hack for webcompat. Content needs to see an updated + // |window.closed| value as early as possible, so we set this before we + // actually send the DOMWindowClose event, which happens in the process where + // the document for this browsing context is loaded. + MOZ_ALWAYS_SUCCEEDS(SetClosed(true)); + + if (ContentChild* cc = ContentChild::GetSingleton()) { + cc->SendWindowClose(this, aCallerType == CallerType::System); + } else if (ContentParent* cp = Canonical()->GetContentParent()) { + Unused << cp->SendWindowClose(this, aCallerType == CallerType::System); + } +} + +/* + * Examine the current document state to see if we're in a way that is + * typically abused by web designers. The window.open code uses this + * routine to determine whether to allow the new window. + * Returns a value from the PopupControlState enum. + */ +PopupBlocker::PopupControlState BrowsingContext::RevisePopupAbuseLevel( + PopupBlocker::PopupControlState aControl) { + if (!IsContent()) { + return PopupBlocker::openAllowed; + } + + PopupBlocker::PopupControlState abuse = aControl; + switch (abuse) { + case PopupBlocker::openControlled: + case PopupBlocker::openBlocked: + case PopupBlocker::openOverridden: + if (IsPopupAllowed()) { + abuse = PopupBlocker::PopupControlState(abuse - 1); + } + break; + case PopupBlocker::openAbused: + if (IsPopupAllowed()) { + // Skip PopupBlocker::openBlocked + abuse = PopupBlocker::openControlled; + } + break; + case PopupBlocker::openAllowed: + break; + default: + NS_WARNING("Strange PopupControlState!"); + } + + // limit the number of simultaneously open popups + if (abuse == PopupBlocker::openAbused || abuse == PopupBlocker::openBlocked || + abuse == PopupBlocker::openControlled) { + int32_t popupMax = StaticPrefs::dom_popup_maximum(); + if (popupMax >= 0 && + PopupBlocker::GetOpenPopupSpamCount() >= (uint32_t)popupMax) { + abuse = PopupBlocker::openOverridden; + } + } + + // If we're currently in-process, attempt to consume transient user gesture + // activations. + if (RefPtr<Document> doc = GetExtantDocument()) { + // HACK: Some pages using bogus library + UA sniffing call window.open() + // from a blank iframe, only on Firefox, see bug 1685056. + // + // This is a hack-around to preserve behavior in that particular and + // specific case, by consuming activation on the parent document, so we + // don't care about the InProcessParent bits not being fission-safe or what + // not. + auto ConsumeTransientUserActivationForMultiplePopupBlocking = + [&]() -> bool { + if (doc->ConsumeTransientUserGestureActivation()) { + return true; + } + if (!doc->IsInitialDocument()) { + return false; + } + Document* parentDoc = doc->GetInProcessParentDocument(); + if (!parentDoc || + !parentDoc->NodePrincipal()->Equals(doc->NodePrincipal())) { + return false; + } + return parentDoc->ConsumeTransientUserGestureActivation(); + }; + + // If this popup is allowed, let's block any other for this event, forcing + // PopupBlocker::openBlocked state. + if ((abuse == PopupBlocker::openAllowed || + abuse == PopupBlocker::openControlled) && + StaticPrefs::dom_block_multiple_popups() && !IsPopupAllowed() && + !ConsumeTransientUserActivationForMultiplePopupBlocking()) { + nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns, + doc, nsContentUtils::eDOM_PROPERTIES, + "MultiplePopupsBlockedNoUserActivation"); + abuse = PopupBlocker::openBlocked; + } + } + + return abuse; +} + +std::tuple<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 = (RevisePopupAbuseLevel(PopupBlocker::GetPopupControlState()) < + PopupBlocker::openBlocked); + } + + bool isActive = false; + if (XRE_IsParentProcess()) { + RefPtr<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); + } +} + +void BrowsingContext::Blur(ErrorResult& aError) { + if (ContentChild* cc = ContentChild::GetSingleton()) { + cc->SendWindowBlur(this); + } else if (ContentParent* cp = Canonical()->GetContentParent()) { + Unused << cp->SendWindowBlur(this); + } +} + +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 contins a shared memory object, then CloneScope would + // return SameProcess. Otherwise, it returns DifferentProcess. + if (message.CloneScope() == + StructuredCloneHolder::StructuredCloneScope::DifferentProcess) { + ClonedMessageData clonedMessageData; + if (!message.BuildClonedMessageDataForChild(cc, clonedMessageData)) { + aError.Throw(NS_ERROR_FAILURE); + return; + } + + messageData = std::move(clonedMessageData); + } else { + MOZ_ASSERT(message.CloneScope() == + StructuredCloneHolder::StructuredCloneScope::SameProcess); + + messageData = ErrorMessageData(); + + nsContentUtils::ReportToConsole( + nsIScriptError::warningFlag, "DOM Window"_ns, + callerInnerWindow ? callerInnerWindow->GetDocument() : nullptr, + nsContentUtils::eDOM_PROPERTIES, + "PostMessageSharedMemoryObjectToCrossOriginWarning"); + } + + cc->SendWindowPostMessage(this, messageData, data); + } else if (ContentParent* cp = Canonical()->GetContentParent()) { + if (message.CloneScope() == + StructuredCloneHolder::StructuredCloneScope::DifferentProcess) { + ClonedMessageData clonedMessageData; + if (!message.BuildClonedMessageDataForParent(cp, clonedMessageData)) { + aError.Throw(NS_ERROR_FAILURE); + return; + } + + messageData = std::move(clonedMessageData); + } else { + MOZ_ASSERT(message.CloneScope() == + StructuredCloneHolder::StructuredCloneScope::SameProcess); + + messageData = ErrorMessageData(); + + nsContentUtils::ReportToConsole( + nsIScriptError::warningFlag, "DOM Window"_ns, + callerInnerWindow ? callerInnerWindow->GetDocument() : nullptr, + nsContentUtils::eDOM_PROPERTIES, + "PostMessageSharedMemoryObjectToCrossOriginWarning"); + } + + Unused << cp->SendWindowPostMessage(this, messageData, data); + } +} + +void BrowsingContext::PostMessageMoz(JSContext* aCx, + JS::Handle<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.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); +} + +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"); +} + +void BrowsingContext::DidSet(FieldIndex<IDX_ExplicitActive>, + ExplicitActiveStatus aOldValue) { + const bool isActive = IsActive(); + const bool wasActive = [&] { + if (aOldValue != ExplicitActiveStatus::None) { + return aOldValue == ExplicitActiveStatus::Active; + } + return GetParent() && GetParent()->IsActive(); + }(); + + if (isActive == wasActive) { + return; + } + + if (IsTop()) { + Group()->UpdateToplevelsSuspendedIfNeeded(); + +#if defined(XP_WIN) && defined(ACCESSIBILITY) + if (XRE_IsParentProcess() && a11y::Compatibility::IsDolphin()) { + // update active accessible documents on windows + if (BrowserParent* bp = Canonical()->GetBrowserParent()) { + if (a11y::DocAccessibleParent* tabDoc = + bp->GetTopLevelDocAccessible()) { + HWND window = tabDoc->GetEmulatedWindowHandle(); + MOZ_ASSERT(window); + if (window) { + if (isActive) { + a11y::nsWinUtils::ShowNativeWindow(window); + } else { + a11y::nsWinUtils::HideNativeWindow(window); + } + } + } + } + } +#endif + } + + PreOrderWalk([&](BrowsingContext* aContext) { + if (nsCOMPtr<nsIDocShell> ds = aContext->GetDocShell()) { + nsDocShell::Cast(ds)->ActivenessMaybeChanged(); + } + }); +} + +bool BrowsingContext::CanSet(FieldIndex<IDX_HasMainMediaController>, + bool aNewValue, ContentParent* aSource) { + return IsTop() && CheckOnlyOwningProcessCanSet(aSource); +} + +void BrowsingContext::DidSet(FieldIndex<IDX_HasMainMediaController>, + bool aOldValue) { + if (!IsTop() || aOldValue == GetHasMainMediaController()) { + return; + } + Group()->UpdateToplevelsSuspendedIfNeeded(); +} + +bool BrowsingContext::InactiveForSuspend() const { + if (!StaticPrefs::dom_suspend_inactive_enabled()) { + return false; + } + // We should suspend a page only when it's inactive and doesn't have a main + // media controller. Having a main controller in context means it might be + // playing media, or waiting media keys to control media (could be not playing + // anything currently) + return !IsActive() && !GetHasMainMediaController(); +} + +bool BrowsingContext::CanSet( + FieldIndex<IDX_TouchEventsOverrideInternal>, + const enum TouchEventsOverride& aTouchEventsOverride, + ContentParent* aSource) { + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool BrowsingContext::CanSet(FieldIndex<IDX_DisplayMode>, + const enum DisplayMode& aDisplayMOde, + ContentParent* aSource) { + return IsTop(); +} + +void BrowsingContext::DidSet(FieldIndex<IDX_DisplayMode>, + enum DisplayMode aOldValue) { + MOZ_ASSERT(IsTop()); + + if (GetDisplayMode() == aOldValue) { + return; + } + + PreOrderWalk([&](BrowsingContext* aContext) { + if (nsIDocShell* shell = aContext->GetDocShell()) { + if (nsPresContext* pc = shell->GetPresContext()) { + pc->MediaFeatureValuesChanged( + {MediaFeatureChangeReason::DisplayModeChange}, + // We're already iterating through sub documents, so we don't need + // to propagate the change again. + // + // Images and other resources don't change their display-mode + // evaluation, display-mode is a property of the browsing context. + MediaFeatureChangePropagation::JustThisDocument); + } + } + }); +} + +void BrowsingContext::DidSet(FieldIndex<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(); + } + }); +} + +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(); + } + }); +} + +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(); + } + }); +} + +bool BrowsingContext::CheckOnlyOwningProcessCanSet( + ContentParent* aSource) { + if (aSource) { + MOZ_ASSERT(XRE_IsParentProcess()); + + // Double-check ownership if we aren't the setter. + if (!Canonical()->IsOwnedByProcess(aSource->ChildID()) && + aSource->ChildID() != Canonical()->GetInFlightProcessId()) { + return false; + } + } else if (!IsInProcess() && !XRE_IsParentProcess()) { + // Don't allow this to be set from content processes that + // don't own the BrowsingContext. + return false; + } + + return true; +} + +bool BrowsingContext::CanSet(FieldIndex<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(NS_DOCUMENT_STATE_WINDOW_INACTIVE, true); + + if (XRE_IsContentProcess() && + (!aContext->GetParent() || !aContext->GetParent()->IsInProcess())) { + // Send the inner window an activate/deactivate event if + // the context is the top of a sub-tree of in-process + // contexts. + nsContentUtils::DispatchEventOnlyToChrome( + doc, doc->GetWindow()->GetCurrentInnerWindow(), + isActivateEvent ? u"activate"_ns : u"deactivate"_ns, + CanBubble::eYes, Cancelable::eYes, nullptr); + } + } + }); +} + +bool BrowsingContext::CanSet(FieldIndex<IDX_AllowContentRetargeting>, + const bool& aAllowContentRetargeting, + ContentParent* aSource) { + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool BrowsingContext::CanSet(FieldIndex<IDX_AllowContentRetargetingOnChildren>, + const bool& aAllowContentRetargetingOnChildren, + ContentParent* aSource) { + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool BrowsingContext::CanSet(FieldIndex<IDX_AllowPlugins>, + const bool& aAllowPlugins, + ContentParent* aSource) { + return CheckOnlyOwningProcessCanSet(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); +} + +mozilla::dom::TouchEventsOverride BrowsingContext::TouchEventsOverride() const { + const BrowsingContext* bc = this; + while (bc) { + mozilla::dom::TouchEventsOverride tev = + bc->GetTouchEventsOverrideInternal(); + if (tev != mozilla::dom::TouchEventsOverride::None) { + return tev; + } + + bc = bc->GetParent(); + } + + return mozilla::dom::TouchEventsOverride::None; +} + +void BrowsingContext::SetTouchEventsOverride( + const enum TouchEventsOverride aTouchEventsOverride, ErrorResult& aRv) { + SetTouchEventsOverrideInternal(aTouchEventsOverride, aRv); +} + +nsresult BrowsingContext::SetTouchEventsOverride( + const enum TouchEventsOverride aTouchEventsOverride) { + ErrorResult rv; + SetTouchEventsOverride(aTouchEventsOverride, rv); + return rv.StealNSResult(); +} + +// We map `watchedByDevTools` WebIDL attribute to `watchedByDevToolsInternal` +// BC field. And we map it to the top level BrowsingContext. +bool BrowsingContext::WatchedByDevTools() { + return Top()->GetWatchedByDevToolsInternal(); +} + +// Enforce that the watchedByDevTools BC field can only be set on the top level +// Browsing Context. +bool BrowsingContext::CanSet(FieldIndex<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); +} + +bool BrowsingContext::CanSet(FieldIndex<IDX_DefaultLoadFlags>, + const uint32_t& aDefaultLoadFlags, + ContentParent* aSource) { + // Bug 1623565 - Are these flags only used by the debugger, which makes it + // possible that this field can only be settable by the parent process? + return CheckOnlyOwningProcessCanSet(aSource); +} + +void BrowsingContext::DidSet(FieldIndex<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; +} + +bool BrowsingContext::CanSet(FieldIndex<IDX_UserAgentOverride>, + const nsString& aUserAgent, + ContentParent* aSource) { + if (!IsTop()) { + return false; + } + + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool BrowsingContext::CanSet(FieldIndex<IDX_PlatformOverride>, + const nsString& aPlatform, + ContentParent* aSource) { + if (!IsTop()) { + return false; + } + + return CheckOnlyOwningProcessCanSet(aSource); +} + +bool BrowsingContext::CheckOnlyEmbedderCanSet(ContentParent* aSource) { + if (XRE_IsParentProcess()) { + uint64_t childId = aSource ? aSource->ChildID() : 0; + return Canonical()->IsEmbeddedInProcess(childId); + } + return mEmbeddedByThisProcess; +} + +bool BrowsingContext::CanSet(FieldIndex<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); +} + +bool BrowsingContext::CanSet(FieldIndex<IDX_CurrentInnerWindowId>, + const uint64_t& aValue, ContentParent* aSource) { + // Generally allow clearing this. We may want to be more precise about this + // check in the future. + if (aValue == 0) { + return true; + } + + if (aSource) { + MOZ_ASSERT(XRE_IsParentProcess()); + + // If in the parent process, double-check ownership and WindowGlobalParent + // as well. + RefPtr<WindowGlobalParent> wgp = + WindowGlobalParent::GetByInnerWindowId(aValue); + if (NS_WARN_IF(!wgp) || NS_WARN_IF(wgp->BrowsingContext() != this)) { + return false; + } + + // Double-check ownership if we aren't the setter. + if (!Canonical()->IsOwnedByProcess(aSource->ChildID()) && + aSource->ChildID() != Canonical()->GetInFlightProcessId()) { + return false; + } + } else if (XRE_IsContentProcess() && !IsOwnedByProcess()) { + return false; + } + + // We must have access to the specified context. + RefPtr<WindowContext> window = WindowContext::GetById(aValue); + return window && window->GetBrowsingContext() == this; +} + +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) { + 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 (StaticPrefs::dom_separate_event_queue_for_post_message_enabled() && + Top() == this) { + Group()->FlushPostMessageEvents(); + } +} + +// Inform the Document for this context of the (potential) change in +// loading state +void BrowsingContext::DidSet(FieldIndex<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.jsm + // will respond to the PageStyle:Disable message in all content processes. + // + // But we store the state here on the top BrowsingContext so that the + // docshell has somewhere to look for the current author style disabling + // state when new iframes are inserted. +} + +void BrowsingContext::DidSet(FieldIndex<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()) { + auto dispatcher = MakeRefPtr<AsyncEventDispatcher>( + element, u"TextZoomChange"_ns, CanBubble::eYes, + ChromeOnlyDispatch::eYes); + dispatcher->RunDOMEventWhenSafe(); + } + } +} + +// TODO(emilio): It'd be potentially nicer and cheaper to allow to set this only +// on the Top() browsing context, but there are a lot of tests that rely on +// zooming a subframe so... +void BrowsingContext::DidSet(FieldIndex<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()) { + auto dispatcher = MakeRefPtr<AsyncEventDispatcher>( + element, u"FullZoomChange"_ns, CanBubble::eYes, + ChromeOnlyDispatch::eYes); + dispatcher->RunDOMEventWhenSafe(); + } + } +} + +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); +} + +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_DIAGNOSTIC_ASSERT(!mChildSessionHistory); + + // Because session history is global in a browsing context tree, every process + // that has access to a browsing context tree needs access to its session + // history. That is why we create the ChildSHistory object in every process + // where we have access to this browsing context (which is the top one). + mChildSessionHistory = new ChildSHistory(this); + + // If the top browsing context (this one) is loaded in this process then we + // also create the session history implementation for the child process. + // This can be removed once session history is stored exclusively in the + // parent process. + mChildSessionHistory->SetIsInProcess(IsInProcess()); +} + +void BrowsingContext::DidSet(FieldIndex<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_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; +} + +void BrowsingContext::SessionHistoryChanged(int32_t aIndexDelta, + int32_t aLengthDelta) { + if (XRE_IsParentProcess() || mozilla::SessionHistoryInParent()) { + // This method is used to test index and length for the session history + // in child process only. + return; + } + + if (!IsTop()) { + // Some tests have unexpected setup while Fission shistory is being + // implemented. + return; + } + + RefPtr<ChildSHistory> shistory = GetChildSessionHistory(); + if (!shistory || !shistory->AsyncHistoryLength()) { + return; + } + + nsID changeID = shistory->AddPendingHistoryChange(aIndexDelta, aLengthDelta); + uint32_t index = shistory->Index(); + uint32_t length = shistory->Count(); + + // Do artificial history update through parent process to test asynchronous + // history.length handling. + ContentChild::GetSingleton()->SendSessionHistoryUpdate(this, index, length, + changeID); +} + +bool BrowsingContext::IsPopupAllowed() { + for (auto* context = GetCurrentWindowContext(); context; + context = context->GetParentWindowContext()) { + if (context->CanShowPopup()) { + return true; + } + } + + return false; +} + +void BrowsingContext::SessionHistoryCommit( + const LoadingSessionHistoryInfo& aInfo, uint32_t aLoadType, + bool aHadActiveEntry, bool aPersist, bool aCloneEntryChildren) { + nsID changeID = {}; + if (XRE_IsContentProcess()) { + RefPtr<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, + // and ShouldUpdateSessionHistory(loadType) returns true. + // It is possible that this leads to wrong length temporarily, but + // so would not having the check for replace. + if (!LOAD_TYPE_HAS_FLAGS( + aLoadType, nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY) && + (IsTop() || aHadActiveEntry) && + ShouldUpdateSessionHistory(aLoadType)) { + changeID = rootSH->AddPendingHistoryChange(); + } + } else { + // This is a load from session history, so we can update + // index and length immediately. + rootSH->SetIndexAndLength(aInfo.mRequestedIndex, + aInfo.mSessionHistoryLength, changeID); + } + } + ContentChild* cc = ContentChild::GetSingleton(); + mozilla::Unused << cc->SendHistoryCommit(this, aInfo.mLoadId, changeID, + aLoadType, aPersist, + aCloneEntryChildren); + } else { + Canonical()->SessionHistoryCommit(aInfo.mLoadId, changeID, aLoadType, + aPersist, aCloneEntryChildren); + } +} + +void BrowsingContext::SetActiveSessionHistoryEntry( + const Maybe<nsPoint>& aPreviousScrollPos, SessionHistoryInfo* aInfo, + uint32_t aLoadType, uint32_t aUpdatedCacheKey) { + if (XRE_IsContentProcess()) { + // XXX Why we update cache key only in content process case? + if (aUpdatedCacheKey != 0) { + aInfo->SetCacheKey(aUpdatedCacheKey); + } + + nsID changeID = {}; + RefPtr<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() { + if (XRE_IsContentProcess()) { + ContentChild::GetSingleton()->SendRemoveFromSessionHistory(this); + } else { + Canonical()->RemoveFromSessionHistory(); + } +} + +void BrowsingContext::HistoryGo(int32_t aOffset, uint64_t aHistoryEpoch, + bool aRequireUserInteraction, + std::function<void(int32_t&&)>&& aResolver) { + if (XRE_IsContentProcess()) { + ContentChild::GetSingleton()->SendHistoryGo( + this, aOffset, aHistoryEpoch, aRequireUserInteraction, + std::move(aResolver), + [](mozilla::ipc:: + ResponseRejectReason) { /* FIXME Is ignoring this fine? */ }); + } else { + Canonical()->HistoryGo( + aOffset, aHistoryEpoch, aRequireUserInteraction, + Canonical()->GetContentParent() + ? Some(Canonical()->GetContentParent()->ChildID()) + : Nothing(), + std::move(aResolver)); + } +} + +void BrowsingContext::SetChildSHistory(ChildSHistory* aChildSHistory) { + mChildSessionHistory = aChildSHistory; + mChildSessionHistory->SetBrowsingContext(this); + mFields.SetWithoutSyncing<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) && IsFrame())); +} + +nsresult BrowsingContext::CheckLocationChangeRateLimit(CallerType aCallerType) { + // We only rate limit non system callers + if (aCallerType == CallerType::System) { + return NS_OK; + } + + // Fetch rate limiting preferences + uint32_t limitCount = + StaticPrefs::dom_navigation_locationChangeRateLimit_count(); + uint32_t timeSpanSeconds = + StaticPrefs::dom_navigation_locationChangeRateLimit_timespan(); + + // Disable throttling if either of the preferences is set to 0. + if (limitCount == 0 || timeSpanSeconds == 0) { + return NS_OK; + } + + TimeDuration throttleSpan = TimeDuration::FromSeconds(timeSpanSeconds); + + if (mLocationChangeRateLimitSpanStart.IsNull() || + ((TimeStamp::Now() - mLocationChangeRateLimitSpanStart) > throttleSpan)) { + // Initial call or timespan exceeded, reset counter and timespan. + mLocationChangeRateLimitSpanStart = TimeStamp::Now(); + mLocationChangeRateLimitCount = 1; + return NS_OK; + } + + if (mLocationChangeRateLimitCount >= limitCount) { + // Rate limit reached + + Document* doc = GetDocument(); + if (doc) { + nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, "DOM"_ns, doc, + nsContentUtils::eDOM_PROPERTIES, + "LocChangeFloodingPrevented"); + } + + return NS_ERROR_DOM_SECURITY_ERR; + } + + mLocationChangeRateLimitCount++; + return NS_OK; +} + +void BrowsingContext::ResetLocationChangeRateLimit() { + // Resetting the timestamp object will cause the check function to + // init again and reset the rate limit. + mLocationChangeRateLimitSpanStart = TimeStamp(); +} + +} // namespace dom + +namespace ipc { + +void IPDLParamTraits<dom::MaybeDiscarded<dom::BrowsingContext>>::Write( + IPC::Message* aMsg, IProtocol* aActor, + const dom::MaybeDiscarded<dom::BrowsingContext>& aParam) { + MOZ_DIAGNOSTIC_ASSERT(!aParam.GetMaybeDiscarded() || + aParam.GetMaybeDiscarded()->EverAttached()); + uint64_t id = aParam.ContextId(); + WriteIPDLParam(aMsg, aActor, id); +} + +bool IPDLParamTraits<dom::MaybeDiscarded<dom::BrowsingContext>>::Read( + const IPC::Message* aMsg, PickleIterator* aIter, IProtocol* aActor, + dom::MaybeDiscarded<dom::BrowsingContext>* aResult) { + uint64_t id = 0; + if (!ReadIPDLParam(aMsg, aIter, 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::Message* aMessage, IProtocol* aActor, + const dom::BrowsingContext::IPCInitializer& aInit) { + // Write actor ID parameters. + WriteIPDLParam(aMessage, aActor, aInit.mId); + WriteIPDLParam(aMessage, aActor, aInit.mParentId); + WriteIPDLParam(aMessage, aActor, aInit.mWindowless); + WriteIPDLParam(aMessage, aActor, aInit.mUseRemoteTabs); + WriteIPDLParam(aMessage, aActor, aInit.mUseRemoteSubframes); + WriteIPDLParam(aMessage, aActor, aInit.mCreatedDynamically); + WriteIPDLParam(aMessage, aActor, aInit.mOriginAttributes); + WriteIPDLParam(aMessage, aActor, aInit.mRequestContextId); + WriteIPDLParam(aMessage, aActor, aInit.mSessionHistoryIndex); + WriteIPDLParam(aMessage, aActor, aInit.mSessionHistoryCount); + WriteIPDLParam(aMessage, aActor, aInit.mFields); +} + +bool IPDLParamTraits<dom::BrowsingContext::IPCInitializer>::Read( + const IPC::Message* aMessage, PickleIterator* aIterator, IProtocol* aActor, + dom::BrowsingContext::IPCInitializer* aInit) { + // Read actor ID parameters. + if (!ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mId) || + !ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mParentId) || + !ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mWindowless) || + !ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mUseRemoteTabs) || + !ReadIPDLParam(aMessage, aIterator, aActor, + &aInit->mUseRemoteSubframes) || + !ReadIPDLParam(aMessage, aIterator, aActor, + &aInit->mCreatedDynamically) || + !ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mOriginAttributes) || + !ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mRequestContextId) || + !ReadIPDLParam(aMessage, aIterator, aActor, + &aInit->mSessionHistoryIndex) || + !ReadIPDLParam(aMessage, aIterator, aActor, + &aInit->mSessionHistoryCount) || + !ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mFields)) { + return false; + } + return true; +} + +template struct IPDLParamTraits<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..4b09798e01 --- /dev/null +++ b/docshell/base/BrowsingContext.h @@ -0,0 +1,1166 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_BrowsingContext_h +#define mozilla_dom_BrowsingContext_h + +#include <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/Tuple.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/LocationBase.h" +#include "mozilla/dom/MaybeDiscarded.h" +#include "mozilla/dom/PopupBlocker.h" +#include "mozilla/dom/UserActivation.h" +#include "mozilla/dom/BrowsingContextBinding.h" +#include "mozilla/dom/ScreenOrientationBinding.h" +#include "mozilla/dom/SyncedContext.h" +#include "nsCOMPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "nsIDocShell.h" +#include "nsTArray.h" +#include "nsWrapperCache.h" +#include "nsILoadInfo.h" +#include "nsILoadContext.h" +#include "nsThreadUtils.h" + +class nsDocShellLoadState; +class nsGlobalWindowInner; +class nsGlobalWindowOuter; +class nsIPrincipal; +class nsOuterWindowProxy; +struct nsPoint; +class PickleIterator; + +namespace IPC { +class Message; +} // namespace IPC + +namespace mozilla { + +class ErrorResult; +class LogModule; + +namespace ipc { +class IProtocol; + +template <typename T> +struct IPDLParamTraits; +} // namespace ipc + +namespace dom { +class BrowsingContent; +class BrowsingContextGroup; +class CanonicalBrowsingContext; +class ChildSHistory; +class ContentParent; +class Element; +struct LoadingSessionHistoryInfo; +template <typename> +struct Nullable; +template <typename T> +class Sequence; +class SessionHistoryInfo; +class SessionStorageManager; +class StructuredCloneHolder; +class WindowContext; +struct WindowPostMessageOptions; +class WindowProxyHolder; + +enum class ExplicitActiveStatus : uint8_t { + None, + Active, + Inactive, + EndGuard_, +}; + +// Fields are, by default, settable by any process and readable by any process. +// Racy sets will be resolved as-if they occurred in the order the parent +// process finds out about them. +// +// The `DidSet` and `CanSet` methods may be overloaded to provide different +// behavior for a specific field. +// * `DidSet` is called to run code in every process whenever the value is +// updated (This currently occurs even if the value didn't change, though +// this may change in the future). +// * `CanSet` is called before attempting to set the value, in both the process +// which calls `Set`, and the parent process, and will kill the misbehaving +// process if it fails. +#define MOZ_EACH_BC_FIELD(FIELD) \ + FIELD(Name, nsString) \ + FIELD(Closed, bool) \ + FIELD(ExplicitActive, ExplicitActiveStatus) \ + /* Top()-only. If true, new-playing media will be suspended when in an \ + * inactive browsing context. */ \ + FIELD(SuspendMediaWhenInactive, bool) \ + /* If true, we're within the nested event loop in window.open, and this \ + * context may not be used as the target of a load */ \ + FIELD(PendingInitialization, bool) \ + /* Indicates if the browser window is active for the purpose of the \ + * :-moz-window-inactive pseudoclass. Only read from or set on the \ + * top BrowsingContext. */ \ + FIELD(IsActiveBrowserWindowInternal, bool) \ + FIELD(OpenerPolicy, nsILoadInfo::CrossOriginOpenerPolicy) \ + /* Current opener for the BrowsingContext. Weak reference */ \ + FIELD(OpenerId, uint64_t) \ + FIELD(OnePermittedSandboxedNavigatorId, uint64_t) \ + /* WindowID of the inner window which embeds this BC */ \ + FIELD(EmbedderInnerWindowId, uint64_t) \ + FIELD(CurrentInnerWindowId, uint64_t) \ + FIELD(HadOriginalOpener, bool) \ + FIELD(IsPopupSpam, bool) \ + /* Hold the audio muted state and should be used on top level browsing \ + * contexts only */ \ + FIELD(Muted, bool) \ + /* See nsSandboxFlags.h for the possible flags. */ \ + FIELD(SandboxFlags, uint32_t) \ + FIELD(InitialSandboxFlags, uint32_t) \ + /* A non-zero unique identifier for the browser element that is hosting \ + * this \ + * BrowsingContext tree. Every BrowsingContext in the element's tree will \ + * return the same ID in all processes and it will remain stable \ + * regardless of process changes. When a browser element's frameloader is \ + * switched to another browser element this ID will remain the same but \ + * hosted under the under the new browser element. */ \ + FIELD(BrowserId, uint64_t) \ + FIELD(HistoryID, nsID) \ + FIELD(InRDMPane, bool) \ + FIELD(Loading, bool) \ + /* A field only set on top browsing contexts, which indicates that either: \ + * \ + * * This is a browsing context created explicitly for printing or print \ + * preview (thus hosting static documents). \ + * \ + * * This is a browsing context where something in this tree is calling \ + * window.print() (and thus showing a modal dialog). \ + * \ + * We use it exclusively to block navigation for both of these cases. */ \ + FIELD(IsPrinting, bool) \ + FIELD(AncestorLoading, bool) \ + FIELD(AllowPlugins, bool) \ + FIELD(AllowContentRetargeting, bool) \ + FIELD(AllowContentRetargetingOnChildren, bool) \ + FIELD(ForceEnableTrackingProtection, bool) \ + FIELD(UseGlobalHistory, bool) \ + FIELD(FullscreenAllowedByOwner, bool) \ + /* These field are used to store the states of autoplay media request on \ + * GeckoView only, and it would only be modified on the top level browsing \ + * context. */ \ + FIELD(GVAudibleAutoplayRequestStatus, GVAutoplayRequestStatus) \ + FIELD(GVInaudibleAutoplayRequestStatus, GVAutoplayRequestStatus) \ + /* ScreenOrientation-related APIs */ \ + FIELD(CurrentOrientationAngle, float) \ + FIELD(CurrentOrientationType, mozilla::dom::OrientationType) \ + FIELD(OrientationLock, mozilla::hal::ScreenOrientation) \ + FIELD(UserAgentOverride, nsString) \ + FIELD(TouchEventsOverrideInternal, mozilla::dom::TouchEventsOverride) \ + FIELD(EmbedderElementType, Maybe<nsString>) \ + FIELD(MessageManagerGroup, nsString) \ + FIELD(MaxTouchPointsOverride, uint8_t) \ + FIELD(FullZoom, float) \ + FIELD(WatchedByDevToolsInternal, bool) \ + FIELD(TextZoom, 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) \ + FIELD(HasLoadedNonInitialDocument, bool) \ + FIELD(CreatedDynamically, bool) \ + /* Default value for nsIContentViewer::authorStyleDisabled in any new \ + * browsing contexts created as a descendant of this one. Valid only for \ + * top BCs. */ \ + FIELD(AuthorStyleDisabledDefault, bool) \ + FIELD(DisplayMode, mozilla::dom::DisplayMode) \ + /* True if the top level browsing context owns a main media controller */ \ + FIELD(HasMainMediaController, bool) + +// BrowsingContext, in this context, is the cross process replicated +// environment in which information about documents is stored. In +// particular the tree structure of nested browsing contexts is +// represented by the tree of BrowsingContexts. +// +// The tree of BrowsingContexts is created in step with its +// corresponding nsDocShell, and when nsDocShells are connected +// through a parent/child relationship, so are BrowsingContexts. The +// major difference is that BrowsingContexts are replicated (synced) +// to the parent process, making it possible to traverse the +// BrowsingContext tree for a tab, in both the parent and the child +// process. +// +// Trees of BrowsingContexts should only ever contain nodes of the +// same BrowsingContext::Type. This is enforced by asserts in the +// BrowsingContext::Create* methods. +class BrowsingContext : public nsILoadContext, public nsWrapperCache { + MOZ_DECL_SYNCED_CONTEXT(BrowsingContext, MOZ_EACH_BC_FIELD) + + public: + enum class Type { Chrome, Content }; + + static void Init(); + static LogModule* GetLog(); + + // Look up a BrowsingContext in the current process by ID. + static already_AddRefed<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> 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 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); + + // Called after the BrowingContext has been embedded in a FrameLoader. This + // happens after `SetEmbedderElement` is called on the BrowsingContext and + // after the BrowsingContext has been set on the FrameLoader. + void Embed(); + + // Get the outer window object for this BrowsingContext if it is in-process + // and still has a docshell, or null otherwise. + nsPIDOMWindowOuter* GetDOMWindow() const { + return mDocShell ? mDocShell->GetWindow() : nullptr; + } + + uint64_t GetRequestContextId() const { return mRequestContextId; } + + // Detach the current BrowsingContext from its parent, in both the + // child and the parent process. + void Detach(bool aFromIPC = false); + + // Prepare this BrowsingContext to leave the current process. + void PrepareForProcessChange(); + + // Triggers a load in the process which currently owns this BrowsingContext. + nsresult LoadURI(nsDocShellLoadState* aLoadState, + bool aSetNavigating = false); + + nsresult InternalLoad(nsDocShellLoadState* aLoadState); + + // Removes the root document for this BrowsingContext tree from the BFCache, + // if it is cached, and returns true if it was. + bool RemoveRootFromBFCacheSync(); + + // If the load state includes a source BrowsingContext has been passed, check + // to see if we are sandboxed from it as the result of an iframe or CSP + // sandbox. + nsresult CheckSandboxFlags(nsDocShellLoadState* aLoadState); + + void DisplayLoadError(const nsAString& aURI); + + // Determine if the current BrowsingContext is in the BFCache. + bool IsCached() const; + + // Check that this browsing context is targetable for navigations (i.e. that + // it is neither closed, cached, nor discarded). + bool IsTargetable() const; + + // True if this browsing context is inactive and is able to be suspended. + bool InactiveForSuspend() const; + + const nsString& Name() const { return GetName(); } + void GetName(nsAString& aName) { aName = GetName(); } + bool NameEquals(const nsAString& aName) { return GetName().Equals(aName); } + + Type GetType() const { return mType; } + bool IsContent() const { return mType == Type::Content; } + bool IsChrome() const { return !IsContent(); } + + bool IsTop() const { return !GetParent(); } + bool IsFrame() const { return !IsTop(); } + + bool IsTopContent() const { return IsContent() && IsTop(); } + + bool IsInSubtreeOf(BrowsingContext* aContext); + + bool IsContentSubframe() const { return IsContent() && IsFrame(); } + // non-zero + uint64_t Id() const { return mBrowsingContextId; } + + BrowsingContext* GetParent() const; + BrowsingContext* Top(); + int32_t IndexOf(BrowsingContext* aChild); + + // NOTE: Unlike `GetEmbedderWindowGlobal`, `GetParentWindowContext` does not + // cross toplevel content browser boundaries. + WindowContext* GetParentWindowContext() const { return mParentWindow; } + WindowContext* GetTopWindowContext(); + + already_AddRefed<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) { + MOZ_DIAGNOSTIC_ASSERT(!aOpener || aOpener->Group() == Group()); + MOZ_DIAGNOSTIC_ASSERT(!aOpener || aOpener->mType == mType); + + MOZ_ALWAYS_SUCCEEDS(SetOpenerId(aOpener ? aOpener->Id() : 0)); + } + + bool HasOpener() const; + + bool HadOriginalOpener() const { return GetHadOriginalOpener(); } + + // Returns true if the browsing context and top context are same origin + bool SameOriginWithTop(); + + /** + * When a new browsing context is opened by a sandboxed document, it needs to + * keep track of the browsing context that opened it, so that it can be + * navigated by it. This is the "one permitted sandboxed navigator". + */ + already_AddRefed<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() { return GetSandboxFlags(); } + + Span<RefPtr<BrowsingContext>> Children() const; + void GetChildren(nsTArray<RefPtr<BrowsingContext>>& aChildren); + + 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. + void PreOrderWalk(const std::function<void(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); + + mozilla::dom::TouchEventsOverride TouchEventsOverride() const; + void SetTouchEventsOverride( + const enum TouchEventsOverride aTouchEventsOverride, ErrorResult& aRv); + MOZ_MUST_USE nsresult + SetTouchEventsOverride(const enum TouchEventsOverride aTouchEventsOverride); + + bool FullscreenAllowed() const; + + float FullZoom() const { return GetFullZoom(); } + float TextZoom() const { return GetTextZoom(); } + + bool SuspendMediaWhenInactive() const { + return GetSuspendMediaWhenInactive(); + } + + bool IsActive() const; + void SetIsActive(bool aIsActive, mozilla::ErrorResult& aRv) { + SetExplicitActive(aIsActive ? ExplicitActiveStatus::Active + : ExplicitActiveStatus::Inactive, + aRv); + } + + bool AuthorStyleDisabledDefault() const { + return GetAuthorStyleDisabledDefault(); + } + + bool UseGlobalHistory() const { return GetUseGlobalHistory(); } + + bool GetIsActiveBrowserWindow(); + + void SetIsActiveBrowserWindow(bool aActive); + + uint64_t BrowserId() const { return GetBrowserId(); } + + bool IsLoading(); + + void GetEmbedderElementType(nsString& aElementType) { + if (GetEmbedderElementType().isSome()) { + aElementType = GetEmbedderElementType().value(); + } + } + + bool IsLoadingIdentifier(uint64_t aLoadIdentifer) { + if (GetCurrentLoadIdentifier() && + *GetCurrentLoadIdentifier() == aLoadIdentifer) { + return true; + } + return false; + } + + // ScreenOrientation related APIs + [[nodiscard]] nsresult SetCurrentOrientation(OrientationType aType, + float aAngle) { + Transaction txn; + txn.SetCurrentOrientationType(aType); + txn.SetCurrentOrientationAngle(aAngle); + return txn.Commit(this); + } + + void SetRDMPaneOrientation(OrientationType aType, float aAngle, + ErrorResult& aRv) { + if (InRDMPane()) { + if (NS_FAILED(SetCurrentOrientation(aType, aAngle))) { + aRv.ThrowInvalidStateError("Browsing context is discarded"); + } + } + } + + void SetRDMPaneMaxTouchPoints(uint8_t aMaxTouchPoints, ErrorResult& aRv) { + if (InRDMPane()) { + SetMaxTouchPointsOverride(aMaxTouchPoints, aRv); + } + } + + // Using the rules for choosing a browsing context we try to find + // the browsing context with the given name in the set of + // transitively reachable browsing contexts. Performs access control + // checks with regard to this. + // See + // https://html.spec.whatwg.org/multipage/browsers.html#the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name. + // + // BrowsingContext::FindWithName(const nsAString&) is equivalent to + // calling nsIDocShellTreeItem::FindItemWithName(aName, nullptr, + // nullptr, false, <return value>). + BrowsingContext* FindWithName(const nsAString& aName, + bool aUseEntryGlobalForAccessCheck = true); + + // Find a browsing context in this context's list of + // children. Doesn't consider the special names, '_self', '_parent', + // '_top', or '_blank'. Performs access control checks with regard to + // 'this'. + BrowsingContext* FindChildWithName(const nsAString& aName, + BrowsingContext& aRequestingContext); + + // Find a browsing context in the subtree rooted at 'this' Doesn't + // consider the special names, '_self', '_parent', '_top', or + // '_blank'. Performs access control checks with regard to + // 'aRequestingContext'. + BrowsingContext* FindWithNameInSubtree(const nsAString& aName, + BrowsingContext& aRequestingContext); + + nsISupports* GetParentObject() const; + JSObject* WrapObject(JSContext* aCx, + JS::Handle<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_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(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.mOpenerId; } + + bool mWindowless = false; + bool mUseRemoteTabs = false; + bool mUseRemoteSubframes = false; + bool mCreatedDynamically = false; + int32_t mSessionHistoryIndex = -1; + int32_t mSessionHistoryCount = 0; + OriginAttributes mOriginAttributes; + uint64_t mRequestContextId = 0; + + FieldValues mFields; + }; + + // Create an IPCInitializer object for this BrowsingContext. + IPCInitializer GetIPCInitializer(); + + // Create a BrowsingContext object from over IPC. + static void CreateFromIPC(IPCInitializer&& aInitializer, + BrowsingContextGroup* aGroup, + ContentParent* aOriginProcess); + + // Performs access control to check that 'this' can access 'aTarget'. + bool CanAccess(BrowsingContext* aTarget, bool aConsiderOpener = true); + + bool IsSandboxedFrom(BrowsingContext* aTarget); + + // The runnable will be called once there is idle time, or the top level + // page has been loaded or if a timeout has fired. + // Must be called only on the top level BrowsingContext. + void AddDeprioritizedLoadRunner(nsIRunnable* aRunner); + + RefPtr<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; } + + 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(); + + void SessionHistoryChanged(int32_t aIndexDelta, int32_t aLengthDelta); + + // Check if it is allowed to open a popup from the current browsing + // context or any of its ancestors. + bool IsPopupAllowed(); + + void SessionHistoryCommit(const LoadingSessionHistoryInfo& aInfo, + uint32_t aLoadType, bool aHadActiveEntry, + bool aPersist, bool aCloneEntryChildren); + + // Set a new active entry on this browsing context. This is used for + // implementing history.pushState/replaceState and same document navigations. + // The new active entry will be linked to the current active entry through + // its shared state. + // aPreviousScrollPos is the scroll position that needs to be saved on the + // previous active entry. + // aUpdatedCacheKey is the cache key to set on the new active entry. If + // aUpdatedCacheKey is 0 then it will be ignored. + void SetActiveSessionHistoryEntry(const Maybe<nsPoint>& aPreviousScrollPos, + SessionHistoryInfo* aInfo, + uint32_t aLoadType, + uint32_t aUpdatedCacheKey); + + // Replace the active entry for this browsing context. This is used for + // implementing history.replaceState and same document navigations. + void ReplaceActiveSessionHistoryEntry(SessionHistoryInfo* aInfo); + + // Removes dynamic child entries of the active entry. + void RemoveDynEntriesFromActiveSessionHistoryEntry(); + + // Removes entries corresponding to this BrowsingContext from session history. + void RemoveFromSessionHistory(); + + void SetTriggeringAndInheritPrincipals(nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aPrincipalToInherit, + uint64_t aLoadIdentifier); + + // Return mTriggeringPrincipal and mPrincipalToInherit if the load id + // saved with the principal matches the current load identifier of this BC. + Tuple<nsCOMPtr<nsIPrincipal>, nsCOMPtr<nsIPrincipal>> + GetTriggeringAndInheritPrincipalsForCurrentLoad(); + + void HistoryGo(int32_t aOffset, uint64_t aHistoryEpoch, + bool aRequireUserInteraction, + std::function<void(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); + + PopupBlocker::PopupControlState RevisePopupAbuseLevel( + PopupBlocker::PopupControlState aControl); + + protected: + virtual ~BrowsingContext(); + BrowsingContext(WindowContext* aParentWindow, BrowsingContextGroup* aGroup, + uint64_t aBrowsingContextId, Type aType, FieldValues&& aInit); + + void SetChildSHistory(ChildSHistory* aChildSHistory); + already_AddRefed<ChildSHistory> ForgetChildSHistory() { + // FIXME Do we need to unset mHasSessionHistory? + return mChildSessionHistory.forget(); + } + + private: + void Attach(bool aFromIPC, ContentParent* aOriginProcess); + + // Find the special browsing context if aName is '_self', '_parent', + // '_top', but not '_blank'. The latter is handled in FindWithName + BrowsingContext* FindWithSpecialName(const nsAString& aName, + BrowsingContext& aRequestingContext); + + // Is it early enough in the BrowsingContext's lifecycle that it is still + // OK to set OriginAttributes? + bool CanSetOriginAttributes(); + + void AssertOriginAttributesMatchPrivateBrowsing(); + + // Assert that the BrowsingContext's LoadContext flags appear coherent + // relative to related BrowsingContexts. + void AssertCoherentLoadContext(); + + friend class ::nsOuterWindowProxy; + friend class ::nsGlobalWindowOuter; + friend class WindowContext; + + // Update the window proxy object that corresponds to this browsing context. + // This should be called from the window proxy object's objectMoved hook, if + // the object mWindowProxy points to was moved by the JS GC. + void UpdateWindowProxy(JSObject* obj, JSObject* old) { + if (mWindowProxy) { + MOZ_ASSERT(mWindowProxy == old); + mWindowProxy = obj; + } + } + // Clear the window proxy object that corresponds to this browsing context. + // This should be called if the window proxy object is finalized, or it can't + // reach its browsing context anymore. + void ClearWindowProxy() { mWindowProxy = nullptr; } + + friend class Location; + friend class RemoteLocationProxy; + /** + * LocationProxy is the class for the native object stored as a private in a + * RemoteLocationProxy proxy representing a Location object in a different + * process. It forwards all operations to its BrowsingContext and aggregates + * its refcount to that BrowsingContext. + */ + class LocationProxy final : public LocationBase { + public: + MozExternalRefCountType AddRef() { return GetBrowsingContext()->AddRef(); } + MozExternalRefCountType Release() { + return GetBrowsingContext()->Release(); + } + + protected: + friend class RemoteLocationProxy; + BrowsingContext* GetBrowsingContext() override { + return reinterpret_cast<BrowsingContext*>( + uintptr_t(this) - offsetof(BrowsingContext, mLocation)); + } + + already_AddRefed<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); + + // 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_SuspendMediaWhenInactive>, bool, ContentParent*) { + return IsTop(); + } + + bool CanSet(FieldIndex<IDX_TouchEventsOverrideInternal>, + const enum TouchEventsOverride& aTouchEventsOverride, + ContentParent* aSource); + + bool CanSet(FieldIndex<IDX_DisplayMode>, const enum DisplayMode& aDisplayMode, + ContentParent* aSource); + void DidSet(FieldIndex<IDX_DisplayMode>, enum DisplayMode aOldValue); + + 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_EmbedderInnerWindowId>, const uint64_t& aValue, + ContentParent* aSource); + + bool CanSet(FieldIndex<IDX_CurrentInnerWindowId>, const uint64_t& aValue, + ContentParent* aSource); + + void DidSet(FieldIndex<IDX_CurrentInnerWindowId>); + + 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>); + bool CanSet(FieldIndex<IDX_PlatformOverride>, + const nsString& aPlatformOverride, ContentParent* aSource); + + void DidSet(FieldIndex<IDX_UserAgentOverride>); + bool 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); + + bool CanSet(FieldIndex<IDX_AllowContentRetargeting>, + const bool& aAllowContentRetargeting, ContentParent* aSource); + bool CanSet(FieldIndex<IDX_AllowContentRetargetingOnChildren>, + const bool& aAllowContentRetargetingOnChildren, + ContentParent* aSource); + bool CanSet(FieldIndex<IDX_AllowPlugins>, const bool& aAllowPlugins, + ContentParent* aSource); + bool CanSet(FieldIndex<IDX_FullscreenAllowedByOwner>, const bool&, + ContentParent*); + bool CanSet(FieldIndex<IDX_WatchedByDevToolsInternal>, + const bool& aWatchedByDevToolsInternal, ContentParent* aSource); + + bool 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); + + 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_HasMainMediaController>, bool aNewValue, + ContentParent* aSource); + void DidSet(FieldIndex<IDX_HasMainMediaController>, bool aOldValue); + + 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>); + + // True if the process attemping to set field is the same as the owning + // process. + bool CheckOnlyOwningProcessCanSet(ContentParent* aSource); + + // True if the process attempting to set field is the same as the embedder's + // process. + bool CheckOnlyEmbedderCanSet(ContentParent* aSource); + + void CreateChildSHistory(); + + using PrincipalWithLoadIdentifierTuple = + Tuple<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; + + // 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; + + // Counter and time span for rate limiting Location and History API calls. + // Used by CheckLocationChangeRateLimit. Do not apply cross-process. + uint32_t mLocationChangeRateLimitCount; + mozilla::TimeStamp mLocationChangeRateLimitSpanStart; +}; + +/** + * Gets a WindowProxy object for a BrowsingContext that lives in a different + * process (creating the object if it doesn't already exist). The WindowProxy + * object will be in the compartment that aCx is currently in. This should only + * be called if aContext doesn't hold a docshell, otherwise the BrowsingContext + * lives in this process, and a same-process WindowProxy should be used (see + * nsGlobalWindowOuter). This should only be called by bindings code, ToJSValue + * is the right API to get a WindowProxy for a BrowsingContext. + * + * If aTransplantTo is non-null, then the WindowProxy object will eventually be + * transplanted onto it. Therefore it should be used as the value in the remote + * proxy map. + */ +extern bool GetRemoteOuterWindowProxy(JSContext* aCx, BrowsingContext* aContext, + JS::Handle<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::Message* aMsg, IProtocol* aActor, + const dom::MaybeDiscarded<dom::BrowsingContext>& aParam); + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, + dom::MaybeDiscarded<dom::BrowsingContext>* aResult); +}; + +template <> +struct IPDLParamTraits<dom::BrowsingContext::IPCInitializer> { + static void Write(IPC::Message* aMessage, IProtocol* aActor, + const dom::BrowsingContext::IPCInitializer& aInitializer); + + static bool Read(const IPC::Message* aMessage, PickleIterator* aIterator, + IProtocol* aActor, + dom::BrowsingContext::IPCInitializer* aInitializer); +}; +} // namespace ipc +} // namespace mozilla + +#endif // !defined(mozilla_dom_BrowsingContext_h) diff --git a/docshell/base/BrowsingContextGroup.cpp b/docshell/base/BrowsingContextGroup.cpp new file mode 100644 index 0000000000..cf3bd9504d --- /dev/null +++ b/docshell/base/BrowsingContextGroup.cpp @@ -0,0 +1,460 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/BrowsingContextGroup.h" + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/InputTaskManager.h" +#include "mozilla/dom/BrowsingContextBinding.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/DocGroup.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/ThrottledEventQueue.h" +#include "nsFocusManager.h" + +namespace mozilla { +namespace dom { + +static StaticRefPtr<BrowsingContextGroup> sChromeGroup; + +static StaticAutoPtr< + nsDataHashtable<nsUint64HashKey, RefPtr<BrowsingContextGroup>>> + sBrowsingContextGroups; + +already_AddRefed<BrowsingContextGroup> BrowsingContextGroup::GetOrCreate( + uint64_t aId) { + if (!sBrowsingContextGroups) { + sBrowsingContextGroups = + new nsDataHashtable<nsUint64HashKey, RefPtr<BrowsingContextGroup>>(); + ClearOnShutdown(&sBrowsingContextGroups); + } + + auto entry = sBrowsingContextGroups->LookupForAdd(aId); + RefPtr<BrowsingContextGroup> group = + entry.OrInsert([&] { return do_AddRef(new BrowsingContextGroup(aId)); }); + return group.forget(); +} + +already_AddRefed<BrowsingContextGroup> BrowsingContextGroup::Create() { + return GetOrCreate(nsContentUtils::GenerateBrowsingContextId()); +} + +BrowsingContextGroup::BrowsingContextGroup(uint64_t aId) : mId(aId) { + mTimerEventQueue = ThrottledEventQueue::Create( + GetMainThreadSerialEventTarget(), "BrowsingContextGroup timer queue"); + + mWorkerEventQueue = ThrottledEventQueue::Create( + GetMainThreadSerialEventTarget(), "BrowsingContextGroup worker queue"); +} + +void BrowsingContextGroup::Register(nsISupports* aContext) { + MOZ_ASSERT(!mDestroyed); + MOZ_DIAGNOSTIC_ASSERT(aContext); + mContexts.PutEntry(aContext); +} + +void BrowsingContextGroup::Unregister(nsISupports* aContext) { + MOZ_ASSERT(!mDestroyed); + MOZ_DIAGNOSTIC_ASSERT(aContext); + mContexts.RemoveEntry(aContext); + + MaybeDestroy(); +} + +void BrowsingContextGroup::EnsureHostProcess(ContentParent* aProcess) { + MOZ_ASSERT(!mDestroyed); + MOZ_DIAGNOSTIC_ASSERT(this != sChromeGroup, + "cannot have content host for chrome group"); + MOZ_DIAGNOSTIC_ASSERT(aProcess->GetRemoteType() != PREALLOC_REMOTE_TYPE, + "cannot use preallocated process as host"); + MOZ_DIAGNOSTIC_ASSERT(!aProcess->GetRemoteType().IsEmpty(), + "host process must have remote type"); + + if (!aProcess->IsDead()) { + auto entry = mHosts.LookupForAdd(aProcess->GetRemoteType()); + if (entry) { + MOZ_DIAGNOSTIC_ASSERT( + entry.Data() == aProcess, + "There's already another host process for this remote type"); + return; + } + + // This process wasn't already marked as our host, so insert it, and begin + // subscribing, unless the process is still launching. + entry.OrInsert([&] { return do_AddRef(aProcess); }); + } + + aProcess->AddBrowsingContextGroup(this); +} + +void BrowsingContextGroup::RemoveHostProcess(ContentParent* aProcess) { + MOZ_DIAGNOSTIC_ASSERT(aProcess); + MOZ_DIAGNOSTIC_ASSERT(aProcess->GetRemoteType() != PREALLOC_REMOTE_TYPE); + auto entry = mHosts.Lookup(aProcess->GetRemoteType()); + if (entry && entry.Data() == aProcess) { + entry.Remove(); + } +} + +static void CollectContextInitializers( + Span<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 (auto& window : context->GetWindowContexts()) { + aInits.AppendElement(window->GetIPCInitializer()); + CollectContextInitializers(window->Children(), aInits); + } + } +} + +void BrowsingContextGroup::Subscribe(ContentParent* aProcess) { + MOZ_ASSERT(!mDestroyed); + MOZ_DIAGNOSTIC_ASSERT(aProcess && !aProcess->IsLaunching()); + MOZ_DIAGNOSTIC_ASSERT(aProcess->GetRemoteType() != PREALLOC_REMOTE_TYPE); + + // Check if we're already subscribed to this process. + if (!mSubscribers.EnsureInserted(aProcess)) { + return; + } + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + // If the process is already marked as dead, we won't be the host, but may + // still need to subscribe to the process due to creating a popup while + // shutting down. + if (!aProcess->IsDead()) { + auto hostEntry = mHosts.Lookup(aProcess->GetRemoteType()); + MOZ_DIAGNOSTIC_ASSERT(hostEntry && hostEntry.Data() == aProcess, + "Cannot subscribe a non-host process"); + } +#endif + + // FIXME: This won't send non-discarded children of discarded BCs, but those + // BCs will be in the process of being destroyed anyway. + // FIXME: Prevent that situation from occuring. + nsTArray<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, active); + } + } +} + +void BrowsingContextGroup::Unsubscribe(ContentParent* aProcess) { + MOZ_DIAGNOSTIC_ASSERT(aProcess); + MOZ_DIAGNOSTIC_ASSERT(aProcess->GetRemoteType() != PREALLOC_REMOTE_TYPE); + mSubscribers.RemoveEntry(aProcess); + aProcess->RemoveBrowsingContextGroup(this); + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + auto hostEntry = mHosts.Lookup(aProcess->GetRemoteType()); + MOZ_DIAGNOSTIC_ASSERT(!hostEntry || hostEntry.Data() != aProcess, + "Unsubscribing existing host entry"); +#endif +} + +ContentParent* BrowsingContextGroup::GetHostProcess( + const nsACString& aRemoteType) { + return mHosts.GetWeak(aRemoteType); +} + +void BrowsingContextGroup::UpdateToplevelsSuspendedIfNeeded() { + if (!StaticPrefs::dom_suspend_inactive_enabled()) { + return; + } + + mToplevelsSuspended = ShouldSuspendAllTopLevelContexts(); + for (const auto& context : mToplevels) { + nsPIDOMWindowOuter* outer = context->GetDOMWindow(); + if (!outer) { + continue; + } + nsCOMPtr<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 DEBUG + if (mDestroyed) { + MOZ_ASSERT(mHosts.Count() == 0); + MOZ_ASSERT(mSubscribers.Count() == 0); + MOZ_ASSERT_IF(sBrowsingContextGroups, + sBrowsingContextGroups->Get(Id()) != this); + } + mDestroyed = true; +#endif + + // Make sure to call `RemoveBrowsingContextGroup` for every entry in both + // `mHosts` and `mSubscribers`. This will visit most entries twice, but + // `RemoveBrowsingContextGroup` is safe to call multiple times. + for (auto& entry : mHosts) { + entry.GetData()->RemoveBrowsingContextGroup(this); + } + for (auto& entry : mSubscribers) { + entry.GetKey()->RemoveBrowsingContextGroup(this); + } + mHosts.Clear(); + mSubscribers.Clear(); + + if (sBrowsingContextGroups) { + sBrowsingContextGroups->Remove(Id()); + } +} + +void BrowsingContextGroup::AddKeepAlive() { + MOZ_ASSERT(!mDestroyed); + mKeepAliveCount++; +} + +void BrowsingContextGroup::RemoveKeepAlive() { + MOZ_ASSERT(!mDestroyed); + MOZ_DIAGNOSTIC_ASSERT(mKeepAliveCount > 0); + mKeepAliveCount--; + + MaybeDestroy(); +} + +void BrowsingContextGroup::MaybeDestroy() { + if (mContexts.IsEmpty() && mKeepAliveCount == 0 && this != sChromeGroup) { + // There are no synced contexts still referencing this group. We can clear + // all subscribers, and destroy ourselves. + Destroy(); + + // We may have been deleted here as the ContentChild/Parent may + // have held the last references to `this`. + // Do not access any members at this point. + } +} + +nsISupports* BrowsingContextGroup::GetParentObject() const { + return xpc::NativeGlobal(xpc::PrivilegedJunkScope()); +} + +JSObject* BrowsingContextGroup::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return BrowsingContextGroup_Binding::Wrap(aCx, this, aGivenProto); +} + +nsresult BrowsingContextGroup::QueuePostMessageEvent( + already_AddRefed<nsIRunnable>&& aRunnable) { + if (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); + } + + if (mPostMessageEventQueue) { + mPostMessageEventQueue->Dispatch(std::move(aRunnable), + NS_DISPATCH_NORMAL); + return NS_OK; + } + } + return NS_ERROR_FAILURE; +} + +void BrowsingContextGroup::FlushPostMessageEvents() { + if (StaticPrefs::dom_separate_event_queue_for_post_message_enabled()) { + if (mPostMessageEventQueue) { + nsresult rv = mPostMessageEventQueue->SetIsPaused(true); + MOZ_ALWAYS_SUCCEEDS(rv); + nsCOMPtr<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()); + for (auto iter = mDocGroups.ConstIter(); !iter.Done(); iter.Next()) { + aDocGroups.AppendElement(iter.Data()); + } +} + +already_AddRefed<DocGroup> BrowsingContextGroup::AddDocument( + const nsACString& aKey, Document* aDocument) { + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr<DocGroup>& docGroup = mDocGroups.GetOrInsert(aKey); + if (!docGroup) { + docGroup = DocGroup::Create(this, aKey); + } + + docGroup->AddDocument(aDocument); + return do_AddRef(docGroup); +} + +void BrowsingContextGroup::RemoveDocument(const nsACString& aKey, + Document* aDocument) { + MOZ_ASSERT(NS_IsMainThread()); + RefPtr<DocGroup> docGroup = aDocument->GetDocGroup(); + // 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(aKey); + } +} + +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.SetCapacity(sBrowsingContextGroups->Count()); + for (auto& group : *sBrowsingContextGroups) { + aGroups.AppendElement(group.GetData()); + } +} + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(BrowsingContextGroup, mContexts, + mToplevels, mHosts, mSubscribers, + mTimerEventQueue, mWorkerEventQueue) + +NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(BrowsingContextGroup, AddRef) +NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(BrowsingContextGroup, Release) + +} // namespace dom +} // namespace mozilla diff --git a/docshell/base/BrowsingContextGroup.h b/docshell/base/BrowsingContextGroup.h new file mode 100644 index 0000000000..39fc6d24f8 --- /dev/null +++ b/docshell/base/BrowsingContextGroup.h @@ -0,0 +1,232 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_BrowsingContextGroup_h +#define mozilla_dom_BrowsingContextGroup_h + +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/FunctionRef.h" +#include "nsRefPtrHashtable.h" +#include "nsHashKeys.h" +#include "nsTArray.h" +#include "nsTHashtable.h" +#include "nsWrapperCache.h" +#include "nsXULAppAPI.h" + +namespace mozilla { +class ThrottledEventQueue; + +namespace dom { + +class BrowsingContext; +class WindowContext; +class ContentParent; +class DocGroup; + +// A BrowsingContextGroup represents the Unit of Related Browsing Contexts in +// the standard. +// +// A BrowsingContext may not hold references to other BrowsingContext objects +// which are not in the same BrowsingContextGroup. +class BrowsingContextGroup final : public nsWrapperCache { + public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(BrowsingContextGroup) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(BrowsingContextGroup) + + // Interact with the list of synced contexts. This controls the lifecycle of + // the BrowsingContextGroup and contexts loaded within them. + void Register(nsISupports* aContext); + void Unregister(nsISupports* aContext); + + // Control which processes will be used to host documents loaded in this + // BrowsingContextGroup. There should only ever be one host process per remote + // type. + // + // A new host process will be subscribed to the BrowsingContextGroup unless it + // is still launching, in which case it will subscribe itself when it is done + // launching. + void EnsureHostProcess(ContentParent* aProcess); + + // A removed host process will no longer be used to host documents loaded in + // this BrowsingContextGroup. + void RemoveHostProcess(ContentParent* aProcess); + + // Synchronize the current BrowsingContextGroup state down to the given + // content process, and continue updating it. + // + // You rarely need to call this directy, as it's automatically called by + // |EnsureHostProcess| as needed. + void Subscribe(ContentParent* aProcess); + + // Stop synchromizing the current BrowsingContextGroup state down to a given + // content process. The content process must no longer be a host process. + void Unsubscribe(ContentParent* aProcess); + + // Look up the process which should be used to host documents with this + // RemoteType. This will be a non-dead process associated with this + // BrowsingContextGroup, if possible. + ContentParent* GetHostProcess(const nsACString& aRemoteType); + + // When a BrowsingContext is being discarded, we may want to keep the + // corresponding BrowsingContextGroup alive until the other process + // acknowledges the BrowsingContext has been discarded. A `KeepAlive` will be + // added to the `BrowsingContextGroup`, delaying destruction. + void AddKeepAlive(); + void RemoveKeepAlive(); + + // Call when we want to check if we should suspend or resume all top level + // contexts. + void UpdateToplevelsSuspendedIfNeeded(); + + // Get a reference to the list of toplevel contexts in this + // BrowsingContextGroup. + nsTArray<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> Create(); + static already_AddRefed<BrowsingContextGroup> Select( + WindowContext* aParent, BrowsingContext* aOpener); + + // 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 (auto iter = mSubscribers.Iter(); !iter.Done(); iter.Next()) { + if (iter.Get()->GetKey() != aExcludedParent) { + aCallback(iter.Get()->GetKey()); + } + } + } + + // For each 'ContentParent' associated with + // this group call 'aCallback'. + template <typename Func> + void EachParent(Func&& aCallback) { + MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess()); + for (auto iter = mSubscribers.Iter(); !iter.Done(); iter.Next()) { + aCallback(iter.Get()->GetKey()); + } + } + + nsresult QueuePostMessageEvent(already_AddRefed<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 to a DocGroup. + void RemoveDocument(const nsACString& aKey, Document* aDocument); + + auto DocGroups() const { return mDocGroups.ConstIter(); } + + mozilla::ThrottledEventQueue* GetTimerEventQueue() const { + return mTimerEventQueue; + } + + mozilla::ThrottledEventQueue* GetWorkerEventQueue() const { + return mWorkerEventQueue; + } + + static void GetAllGroups(nsTArray<RefPtr<BrowsingContextGroup>>& aGroups); + + void IncInputEventSuspensionLevel(); + void DecInputEventSuspensionLevel(); + + private: + friend class CanonicalBrowsingContext; + + explicit BrowsingContextGroup(uint64_t aId); + ~BrowsingContextGroup(); + + void MaybeDestroy(); + void Destroy(); + + bool ShouldSuspendAllTopLevelContexts() const; + + bool HasActiveBC(); + void DecInputTaskManagerSuspensionLevel(); + void IncInputTaskManagerSuspensionLevel(); + + uint64_t mId; + + uint32_t mKeepAliveCount = 0; + +#ifdef DEBUG + bool mDestroyed = false; +#endif + + // A BrowsingContextGroup contains a series of {Browsing,Window}Context + // objects. They are addressed using a hashtable to avoid linear lookup when + // adding or removing elements from the set. + // + // FIXME: This list is only required over a counter to keep nested + // non-discarded contexts within discarded contexts alive. It should be + // removed in the future. + // FIXME: Consider introducing a better common base than `nsISupports`? + nsTHashtable<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; + + nsTHashtable<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; +}; +} // namespace dom +} // namespace mozilla + +#endif // !defined(mozilla_dom_BrowsingContextGroup_h) diff --git a/docshell/base/BrowsingContextWebProgress.cpp b/docshell/base/BrowsingContextWebProgress.cpp new file mode 100644 index 0000000000..0dfe9d0eb8 --- /dev/null +++ b/docshell/base/BrowsingContextWebProgress.cpp @@ -0,0 +1,217 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "BrowsingContextWebProgress.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_ADDREF(BrowsingContextWebProgress) +NS_IMPL_RELEASE(BrowsingContextWebProgress) + +NS_INTERFACE_MAP_BEGIN(BrowsingContextWebProgress) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebProgress) + NS_INTERFACE_MAP_ENTRY(nsIWebProgress) + NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener) +NS_INTERFACE_MAP_END + +NS_IMETHODIMP BrowsingContextWebProgress::AddProgressListener( + nsIWebProgressListener* aListener, uint32_t aNotifyMask) { + nsWeakPtr listener = do_GetWeakReference(aListener); + if (!listener) { + return NS_ERROR_INVALID_ARG; + } + + if (mListenerInfoList.Contains(listener)) { + // The listener is already registered! + return NS_ERROR_FAILURE; + } + + mListenerInfoList.AppendElement(ListenerInfo(listener, aNotifyMask)); + return NS_OK; +} + +NS_IMETHODIMP BrowsingContextWebProgress::RemoveProgressListener( + nsIWebProgressListener* aListener) { + nsWeakPtr listener = do_GetWeakReference(aListener); + if (!listener) { + return NS_ERROR_INVALID_ARG; + } + + return mListenerInfoList.RemoveElement(listener) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP BrowsingContextWebProgress::GetDOMWindow( + mozIDOMWindowProxy** aDOMWindow) { + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP BrowsingContextWebProgress::GetIsTopLevel(bool* aIsTopLevel) { + *aIsTopLevel = true; + return NS_OK; +} + +NS_IMETHODIMP BrowsingContextWebProgress::GetIsLoadingDocument( + bool* aIsLoadingDocument) { + *aIsLoadingDocument = false; + return NS_OK; +} + +NS_IMETHODIMP BrowsingContextWebProgress::GetLoadType(uint32_t* aLoadType) { + *aLoadType = 0; + return NS_OK; +} + +NS_IMETHODIMP BrowsingContextWebProgress::GetTarget(nsIEventTarget** aTarget) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP BrowsingContextWebProgress::SetTarget(nsIEventTarget* aTarget) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +void BrowsingContextWebProgress::UpdateAndNotifyListeners( + uint32_t aFlag, + const std::function<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(); +} + +//////////////////////////////////////////////////////////////////////////////// +// nsIWebProgressListener + +NS_IMETHODIMP +BrowsingContextWebProgress::OnStateChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + uint32_t aStateFlags, + nsresult aStatus) { + const uint32_t startDocumentFlags = + nsIWebProgressListener::STATE_START | + nsIWebProgressListener::STATE_IS_DOCUMENT | + nsIWebProgressListener::STATE_IS_REQUEST | + nsIWebProgressListener::STATE_IS_WINDOW | + nsIWebProgressListener::STATE_IS_NETWORK; + bool isTopLevel = false; + nsresult rv = aWebProgress->GetIsTopLevel(&isTopLevel); + NS_ENSURE_SUCCESS(rv, rv); + bool isTopLevelStartDocumentEvent = + (aStateFlags & startDocumentFlags) == startDocumentFlags && isTopLevel; + // If we receive a matching STATE_START for a top-level document event, + // and we are currently not suspending this event, start suspending all + // further matching STATE_START events after this one. + if (isTopLevelStartDocumentEvent && !mSuspendOnStateStartChangeEvents) { + mSuspendOnStateStartChangeEvents = true; + } else if (mSuspendOnStateStartChangeEvents) { + // If we are currently suspending matching STATE_START events, check if this + // is a corresponding STATE_STOP event. + const uint32_t stopWindowFlags = nsIWebProgressListener::STATE_STOP | + nsIWebProgressListener::STATE_IS_WINDOW; + bool isTopLevelStopWindowEvent = + (aStateFlags & stopWindowFlags) == stopWindowFlags && isTopLevel; + if (isTopLevelStopWindowEvent) { + // If this is a STATE_STOP event corresponding to the initial STATE_START + // event, stop suspending matching STATE_START events. + mSuspendOnStateStartChangeEvents = false; + } else if (isTopLevelStartDocumentEvent) { + // We have received a matching STATE_START event at least twice, but + // haven't received the corresponding STATE_STOP event for the first one. + // Don't let this event through. This is probably a duplicate event from + // the new BrowserParent due to a process switch. + return NS_OK; + } + // Allow all other progress events that don't match top-level start document + // flags. + } + + UpdateAndNotifyListeners( + ((aStateFlags >> 16) & nsIWebProgress::NOTIFY_STATE_ALL), + [&](nsIWebProgressListener* listener) { + listener->OnStateChange(aWebProgress, aRequest, aStateFlags, aStatus); + }); + return NS_OK; +} + +NS_IMETHODIMP +BrowsingContextWebProgress::OnProgressChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + int32_t aCurSelfProgress, + int32_t aMaxSelfProgress, + int32_t aCurTotalProgress, + int32_t aMaxTotalProgress) { + UpdateAndNotifyListeners( + nsIWebProgress::NOTIFY_PROGRESS, [&](nsIWebProgressListener* listener) { + listener->OnProgressChange(aWebProgress, aRequest, aCurSelfProgress, + aMaxSelfProgress, aCurTotalProgress, + aMaxTotalProgress); + }); + return NS_OK; +} + +NS_IMETHODIMP +BrowsingContextWebProgress::OnLocationChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + nsIURI* aLocation, + uint32_t aFlags) { + UpdateAndNotifyListeners( + nsIWebProgress::NOTIFY_LOCATION, [&](nsIWebProgressListener* listener) { + listener->OnLocationChange(aWebProgress, aRequest, aLocation, aFlags); + }); + return NS_OK; +} + +NS_IMETHODIMP +BrowsingContextWebProgress::OnStatusChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + nsresult aStatus, + const char16_t* aMessage) { + UpdateAndNotifyListeners( + nsIWebProgress::NOTIFY_STATUS, [&](nsIWebProgressListener* listener) { + listener->OnStatusChange(aWebProgress, aRequest, aStatus, aMessage); + }); + return NS_OK; +} + +NS_IMETHODIMP +BrowsingContextWebProgress::OnSecurityChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + uint32_t aState) { + UpdateAndNotifyListeners( + nsIWebProgress::NOTIFY_SECURITY, [&](nsIWebProgressListener* listener) { + listener->OnSecurityChange(aWebProgress, aRequest, aState); + }); + return NS_OK; +} + +NS_IMETHODIMP +BrowsingContextWebProgress::OnContentBlockingEvent(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + uint32_t aEvent) { + UpdateAndNotifyListeners(nsIWebProgress::NOTIFY_CONTENT_BLOCKING, + [&](nsIWebProgressListener* listener) { + listener->OnContentBlockingEvent(aWebProgress, + aRequest, aEvent); + }); + return NS_OK; +} + +} // namespace dom +} // namespace mozilla diff --git a/docshell/base/BrowsingContextWebProgress.h b/docshell/base/BrowsingContextWebProgress.h new file mode 100644 index 0000000000..8085da715b --- /dev/null +++ b/docshell/base/BrowsingContextWebProgress.h @@ -0,0 +1,68 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_BrowsingContextWebProgress_h +#define mozilla_dom_BrowsingContextWebProgress_h + +#include "nsIWebProgress.h" +#include "nsIWebProgressListener.h" +#include "nsTObserverArray.h" +#include "nsWeakReference.h" + +namespace mozilla { +namespace dom { + +class BrowsingContextWebProgress final : public nsIWebProgress, + public nsIWebProgressListener { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIWEBPROGRESS + NS_DECL_NSIWEBPROGRESSLISTENER + + BrowsingContextWebProgress() = default; + + struct ListenerInfo { + ListenerInfo(nsIWeakReference* aListener, unsigned long aNotifyMask) + : mWeakListener(aListener), mNotifyMask(aNotifyMask) {} + + bool operator==(const ListenerInfo& aOther) const { + return mWeakListener == aOther.mWeakListener; + } + bool operator==(const nsWeakPtr& aOther) const { + return mWeakListener == aOther; + } + + // Weak pointer for the nsIWebProgressListener... + nsWeakPtr mWeakListener; + + // Mask indicating which notifications the listener wants to receive. + unsigned long mNotifyMask; + }; + + private: + virtual ~BrowsingContextWebProgress() = default; + + void UpdateAndNotifyListeners( + uint32_t aFlag, + const std::function<void(nsIWebProgressListener*)>& aCallback); + + using ListenerArray = nsAutoTObserverArray<ListenerInfo, 4>; + ListenerArray mListenerInfoList; + + // This indicates whether we are currently suspending onStateChange top level + // STATE_START events for a document. We start suspending whenever we receive + // the first STATE_START event with the matching flags (see + // ::RecvOnStateChange for details), until we get a corresponding STATE_STOP + // event. In the meantime, if there other onStateChange events, this flag does + // not affect them. We do this to avoid duplicate onStateChange STATE_START + // events that happen during process switch. With this flag, we allow + // onStateChange STATE_START event from the old BrowserParent, but not the + // same event from the new BrowserParent during a process switch. + bool mSuspendOnStateStartChangeEvents = false; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_BrowsingContextWebProgress_h diff --git a/docshell/base/CanonicalBrowsingContext.cpp b/docshell/base/CanonicalBrowsingContext.cpp new file mode 100644 index 0000000000..7f01ff6cf8 --- /dev/null +++ b/docshell/base/CanonicalBrowsingContext.cpp @@ -0,0 +1,1694 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/CanonicalBrowsingContext.h" + +#include "mozilla/CheckedInt.h" +#include "mozilla/EventForwards.h" +#include "mozilla/AsyncEventDispatcher.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/dom/BrowsingContextBinding.h" +#include "mozilla/dom/BrowsingContextGroup.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/EventTarget.h" +#include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/dom/ContentProcessManager.h" +#include "mozilla/dom/MediaController.h" +#include "mozilla/dom/MediaControlService.h" +#include "mozilla/dom/ContentPlaybackController.h" +#include "mozilla/dom/SessionHistoryEntry.h" +#include "mozilla/dom/SessionStorageManager.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/net/DocumentLoadListener.h" +#include "mozilla/NullPrincipal.h" +#include "nsIWebNavigation.h" +#include "mozilla/MozPromiseInlines.h" +#include "nsDocShell.h" +#include "nsFrameLoader.h" +#include "nsFrameLoaderOwner.h" +#include "nsGlobalWindowOuter.h" +#include "nsIWebBrowserChrome.h" +#include "nsIXULRuntime.h" +#include "nsNetUtil.h" +#include "nsSHistory.h" +#include "nsSecureBrowserUI.h" +#include "nsQueryObject.h" +#include "nsBrowserStatusFilter.h" +#include "nsIBrowser.h" + +using namespace mozilla::ipc; + +extern mozilla::LazyLogModule gAutoplayPermissionLog; +extern mozilla::LazyLogModule gSHLog; + +#define AUTOPLAY_LOG(msg, ...) \ + MOZ_LOG(gAutoplayPermissionLog, LogLevel::Debug, (msg, ##__VA_ARGS__)) + +namespace mozilla { +namespace dom { + +extern mozilla::LazyLogModule gUserInteractionPRLog; + +#define USER_ACTIVATION_LOG(msg, ...) \ + MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug, (msg, ##__VA_ARGS__)) + +CanonicalBrowsingContext::CanonicalBrowsingContext(WindowContext* aParentWindow, + BrowsingContextGroup* aGroup, + uint64_t aBrowsingContextId, + uint64_t aOwnerProcessId, + uint64_t aEmbedderProcessId, + BrowsingContext::Type aType, + FieldValues&& aInit) + : BrowsingContext(aParentWindow, aGroup, aBrowsingContextId, aType, + std::move(aInit)), + mProcessId(aOwnerProcessId), + mEmbedderProcessId(aEmbedderProcessId) { + // You are only ever allowed to create CanonicalBrowsingContexts in the + // parent process. + MOZ_RELEASE_ASSERT(XRE_IsParentProcess()); +} + +/* static */ +already_AddRefed<CanonicalBrowsingContext> 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(); + return cpm->GetContentProcessById(ContentParentId(mProcessId)); +} + +void CanonicalBrowsingContext::GetCurrentRemoteType(nsACString& aRemoteType, + ErrorResult& aRv) const { + // If we're in the parent process, dump out the void string. + if (mProcessId == 0) { + aRemoteType = NOT_REMOTE_TYPE; + return; + } + + ContentParent* cp = GetContentParent(); + if (!cp) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return; + } + + aRemoteType = cp->GetRemoteType(); +} + +void CanonicalBrowsingContext::SetOwnerProcessId(uint64_t aProcessId) { + MOZ_LOG(GetLog(), LogLevel::Debug, + ("SetOwnerProcessId for 0x%08" PRIx64 " (0x%08" PRIx64 + " -> 0x%08" PRIx64 ")", + Id(), mProcessId, aProcessId)); + + mProcessId = aProcessId; +} + +nsISecureBrowserUI* CanonicalBrowsingContext::GetSecureBrowserUI() { + if (!IsTop()) { + return nullptr; + } + if (!mSecureBrowserUI) { + mSecureBrowserUI = new nsSecureBrowserUI(this); + } + return mSecureBrowserUI; +} + +void CanonicalBrowsingContext::MaybeAddAsProgressListener( + nsIWebProgress* aWebProgress) { + if (!GetWebProgress()) { + return; + } + if (!mStatusFilter) { + mStatusFilter = new nsBrowserStatusFilter(); + mStatusFilter->AddProgressListener(GetWebProgress(), + nsIWebProgress::NOTIFY_ALL); + } + aWebProgress->AddProgressListener(mStatusFilter, nsIWebProgress::NOTIFY_ALL); +} + +void CanonicalBrowsingContext::ReplacedBy( + CanonicalBrowsingContext* aNewContext) { + MOZ_ASSERT(!aNewContext->EverAttached()); + MOZ_ASSERT(IsTop() && aNewContext->IsTop()); + if (mStatusFilter) { + mStatusFilter->RemoveProgressListener(mWebProgress); + mStatusFilter = nullptr; + } + aNewContext->mWebProgress = std::move(mWebProgress); + aNewContext->mFields.SetWithoutSyncing<IDX_BrowserId>(GetBrowserId()); + aNewContext->mFields.SetWithoutSyncing<IDX_HistoryID>(GetHistoryID()); + aNewContext->mFields.SetWithoutSyncing<IDX_ExplicitActive>( + GetExplicitActive()); + + if (mSessionHistory) { + mSessionHistory->SetBrowsingContext(aNewContext); + mSessionHistory.swap(aNewContext->mSessionHistory); + RefPtr<ChildSHistory> childSHistory = ForgetChildSHistory(); + aNewContext->SetChildSHistory(childSHistory); + } + + if (mozilla::SessionHistoryInParent()) { + BackgroundSessionStorageManager::PropagateManager(Id(), aNewContext->Id()); + } + + MOZ_ASSERT(aNewContext->mLoadingEntries.IsEmpty()); + mLoadingEntries.SwapElements(aNewContext->mLoadingEntries); + MOZ_ASSERT(!aNewContext->mActiveEntry); + mActiveEntry.swap(aNewContext->mActiveEntry); +} + +void CanonicalBrowsingContext::UpdateSecurityState() { + if (mSecureBrowserUI) { + mSecureBrowserUI->RecomputeSecurityFlags(); + } +} + +void CanonicalBrowsingContext::SetInFlightProcessId(uint64_t aProcessId) { + MOZ_ASSERT(aProcessId); + mInFlightProcessId = aProcessId; +} + +void CanonicalBrowsingContext::ClearInFlightProcessId(uint64_t aProcessId) { + MOZ_ASSERT(aProcessId); + if (mInFlightProcessId == aProcessId) { + mInFlightProcessId = 0; + } +} + +void CanonicalBrowsingContext::GetWindowGlobals( + nsTArray<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<WindowGlobalParent> +CanonicalBrowsingContext::GetEmbedderWindowGlobal() const { + uint64_t windowId = GetEmbedderInnerWindowId(); + if (windowId == 0) { + return nullptr; + } + + return WindowGlobalParent::GetByInnerWindowId(windowId); +} + +already_AddRefed<CanonicalBrowsingContext> +CanonicalBrowsingContext::GetParentCrossChromeBoundary() { + if (GetParent()) { + return do_AddRef(Cast(GetParent())); + } + if (GetEmbedderElement()) { + return do_AddRef( + Cast(GetEmbedderElement()->OwnerDoc()->GetBrowsingContext())); + } + return nullptr; +} + +already_AddRefed<CanonicalBrowsingContext> +CanonicalBrowsingContext::TopCrossChromeBoundary() { + RefPtr<CanonicalBrowsingContext> bc(this); + while (RefPtr<CanonicalBrowsingContext> parent = + bc->GetParentCrossChromeBoundary()) { + bc = parent.forget(); + } + return bc.forget(); +} + +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; +} + +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, int32_t* aRequestedIndex, + int32_t* aLength) { + *aRequestedIndex = -1; + *aLength = 0; + + nsISHistory* shistory = GetSessionHistory(); + if (!shistory || !GetParent()) { + return; + } + + SessionHistoryEntry* parentSHE = + GetParent()->Canonical()->GetActiveSessionHistoryEntry(); + if (parentSHE) { + int32_t index = -1; + for (BrowsingContext* sibling : GetParent()->Children()) { + ++index; + if (sibling == this) { + nsCOMPtr<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()}); + *aRequestedIndex = shistory->GetRequestedIndex(); + *aLength = shistory->GetCount(); + Unused << SetHistoryID(she->DocshellID()); + } + break; + } + } + } +} + +UniquePtr<LoadingSessionHistoryInfo> +CanonicalBrowsingContext::CreateLoadingSessionHistoryEntryForLoad( + nsDocShellLoadState* aLoadState, nsIChannel* aChannel) { + RefPtr<SessionHistoryEntry> entry; + const LoadingSessionHistoryInfo* existingLoadingInfo = + aLoadState->GetLoadingSessionHistoryInfo(); + if (existingLoadingInfo) { + entry = SessionHistoryEntry::GetByLoadId(existingLoadingInfo->mLoadId); + MOZ_LOG(gSHLog, LogLevel::Verbose, + ("SHEntry::GetByLoadId(%" PRIu64 ") -> %p", + existingLoadingInfo->mLoadId, entry.get())); + if (!entry) { + return nullptr; + } + } else { + entry = new SessionHistoryEntry(aLoadState, aChannel); + if (IsTop()) { + // Only top level pages care about Get/SetPersist. + entry->SetPersist( + nsDocShell::ShouldAddToSessionHistory(aLoadState->URI(), aChannel)); + } else if (mActiveEntry || !mLoadingEntries.IsEmpty()) { + entry->SetIsSubFrame(true); + } + entry->SetDocshellID(GetHistoryID()); + entry->SetIsDynamicallyAdded(CreatedDynamically()); + entry->SetForInitialLoad(true); + } + MOZ_DIAGNOSTIC_ASSERT(entry); + + UniquePtr<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) == entry); + + return loadingInfo; +} + +UniquePtr<LoadingSessionHistoryInfo> +CanonicalBrowsingContext::ReplaceLoadingSessionHistoryEntryForLoad( + LoadingSessionHistoryInfo* aInfo, nsIChannel* aChannel) { + MOZ_ASSERT(aInfo); + MOZ_ASSERT(aChannel); + + UniquePtr<SessionHistoryInfo> newInfo = MakeUnique<SessionHistoryInfo>( + aChannel, aInfo->mInfo.LoadType(), + aInfo->mInfo.GetPartitionedPrincipalToInherit(), aInfo->mInfo.GetCsp()); + + RefPtr<SessionHistoryEntry> newEntry = new SessionHistoryEntry(newInfo.get()); + if (IsTop()) { + // Only top level pages care about Get/SetPersist. + nsCOMPtr<nsIURI> uri; + aChannel->GetURI(getter_AddRefs(uri)); + newEntry->SetPersist(nsDocShell::ShouldAddToSessionHistory(uri, aChannel)); + } else { + newEntry->SetIsSubFrame(aInfo->mInfo.IsSubFrame()); + } + newEntry->SetDocshellID(GetHistoryID()); + newEntry->SetIsDynamicallyAdded(CreatedDynamically()); + newEntry->SetForInitialLoad(true); + + // Replacing the old entry. + SessionHistoryEntry::SetByLoadId(aInfo->mLoadId, newEntry); + + for (size_t i = 0; i < mLoadingEntries.Length(); ++i) { + if (mLoadingEntries[i].mLoadId == aInfo->mLoadId) { + mLoadingEntries[i].mEntry = newEntry; + break; + } + } + + return MakeUnique<LoadingSessionHistoryInfo>(newEntry, aInfo->mLoadId); +} + +void CanonicalBrowsingContext::CallOnAllTopDescendants( + const std::function<mozilla::CallState(CanonicalBrowsingContext*)>& + aCallback) { +#ifdef DEBUG + RefPtr<CanonicalBrowsingContext> parent = GetParentCrossChromeBoundary(); + MOZ_ASSERT(!parent, "Should only call on top chrome BC"); +#endif + + nsTArray<RefPtr<BrowsingContextGroup>> groups; + BrowsingContextGroup::GetAllGroups(groups); + for (auto& browsingContextGroup : groups) { + for (auto& bc : browsingContextGroup->Toplevels()) { + if (bc == this) { + // Cannot be a descendent of myself so skip. + continue; + } + + RefPtr<CanonicalBrowsingContext> top = + bc->Canonical()->TopCrossChromeBoundary(); + if (top == this) { + if (aCallback(bc->Canonical()) == CallState::Stop) { + return; + } + } + } + } +} + +void CanonicalBrowsingContext::SessionHistoryCommit(uint64_t aLoadId, + const nsID& aChangeID, + uint32_t aLoadType, + bool aPersist, + bool aCloneEntryChildren) { + MOZ_LOG(gSHLog, LogLevel::Verbose, + ("CanonicalBrowsingContext::SessionHistoryCommit %p %" PRIu64, this, + aLoadId)); + for (size_t i = 0; i < mLoadingEntries.Length(); ++i) { + if (mLoadingEntries[i].mLoadId == aLoadId) { + nsSHistory* shistory = static_cast<nsSHistory*>(GetSessionHistory()); + if (!shistory) { + SessionHistoryEntry::RemoveLoadId(aLoadId); + mLoadingEntries.RemoveElementAt(i); + return; + } + + CallerWillNotifyHistoryIndexAndLengthChanges caller(shistory); + + RefPtr<SessionHistoryEntry> newActiveEntry = mLoadingEntries[i].mEntry; + + bool loadFromSessionHistory = !newActiveEntry->ForInitialLoad(); + newActiveEntry->SetForInitialLoad(false); + SessionHistoryEntry::RemoveLoadId(aLoadId); + mLoadingEntries.RemoveElementAt(i); + + // If there is a name in the new entry, clear the name of all contiguous + // entries. This is for https://html.spec.whatwg.org/#history-traversal + // Step 4.4.2. + nsAutoString nameOfNewEntry; + newActiveEntry->GetName(nameOfNewEntry); + if (!nameOfNewEntry.IsEmpty()) { + nsSHistory::WalkContiguousEntries( + newActiveEntry, + [](nsISHEntry* aEntry) { aEntry->SetName(EmptyString()); }); + } + + bool addEntry = ShouldUpdateSessionHistory(aLoadType); + if (IsTop()) { + mActiveEntry = newActiveEntry; + if (loadFromSessionHistory) { + // XXX Synchronize browsing context tree and session history tree? + shistory->UpdateIndex(); + } else { + if (LOAD_TYPE_HAS_FLAGS( + aLoadType, nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY)) { + // Replace the current entry with the new entry. + int32_t index = shistory->GetIndexForReplace(); + + // If we're trying to replace an inexistant shistory entry then we + // should append instead. + addEntry = index < 0; + if (!addEntry) { + shistory->ReplaceEntry(index, mActiveEntry); + } + } + + if (addEntry) { + shistory->AddEntry(mActiveEntry, aPersist); + } + } + } else { + // FIXME The old implementations adds it to the parent's mLSHE if there + // is one, need to figure out if that makes sense here (peterv + // doesn't think it would). + if (loadFromSessionHistory) { + if (mActiveEntry) { + // mActiveEntry is null if we're loading iframes from session + // history while also parent page is loading from session history. + // In that case there isn't anything to sync. + mActiveEntry->SyncTreesForSubframeNavigation(newActiveEntry, Top(), + this); + } + mActiveEntry = newActiveEntry; + // FIXME UpdateIndex() here may update index too early (but even the + // old implementation seems to have similar issues). + shistory->UpdateIndex(); + } else if (addEntry) { + if (mActiveEntry) { + if (LOAD_TYPE_HAS_FLAGS( + aLoadType, nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY)) { + // FIXME We need to make sure that when we create the info we + // make a copy of the shared state. + mActiveEntry->ReplaceWith(*newActiveEntry); + } else { + // AddChildSHEntryHelper does update the index of the session + // history! + shistory->AddChildSHEntryHelper(mActiveEntry, newActiveEntry, + Top(), aCloneEntryChildren); + mActiveEntry = newActiveEntry; + } + } else { + SessionHistoryEntry* parentEntry = GetParent()->mActiveEntry; + // XXX What should happen if parent doesn't have mActiveEntry? + // Or can that even happen ever? + if (parentEntry) { + mActiveEntry = newActiveEntry; + // FIXME Using IsInProcess for aUseRemoteSubframes isn't quite + // right, but aUseRemoteSubframes should be going away. + parentEntry->AddChild( + mActiveEntry, + CreatedDynamically() ? -1 : GetParent()->IndexOf(this), + IsInProcess()); + } + } + } + } + + ResetSHEntryHasUserInteractionCache(); + + HistoryCommitIndexAndLength(aChangeID, caller); + + shistory->LogHistory(); + + return; + } + // XXX Should the loading entries before [i] be removed? + } + // FIXME Should we throw an error if we don't find an entry for + // aSessionHistoryEntryId? +} + +static already_AddRefed<nsDocShellLoadState> 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); + loadState->SetLoadingSessionHistoryInfo(std::move(loadingInfo)); + + return loadState.forget(); +} + +void CanonicalBrowsingContext::NotifyOnHistoryReload( + bool aForceReload, bool& aCanReload, + Maybe<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(CreateLoadInfo(mActiveEntry)); + aReloadActiveEntry.emplace(true); + if (aForceReload) { + shistory->RemoveFrameEntries(mActiveEntry); + } + } else if (!mLoadingEntries.IsEmpty()) { + const LoadingSessionHistoryEntry& loadingEntry = + mLoadingEntries.LastElement(); + aLoadState.emplace(CreateLoadInfo(loadingEntry.mEntry)); + aReloadActiveEntry.emplace(false); + if (aForceReload) { + SessionHistoryEntry* entry = + SessionHistoryEntry::GetByLoadId(loadingEntry.mLoadId); + if (entry) { + shistory->RemoveFrameEntries(entry); + } + } + } + + if (aLoadState) { + int32_t index = 0; + int32_t requestedIndex = -1; + int32_t length = 0; + shistory->GetIndex(&index); + shistory->GetRequestedIndex(&requestedIndex); + shistory->GetCount(&length); + aLoadState.ref()->SetLoadIsFromSessionHistory( + requestedIndex >= 0 ? requestedIndex : index, length, + aReloadActiveEntry.value()); + } + // If we don't have an active entry and we don't have a loading entry then + // the nsDocShell will create a load state based on its document. +} + +void CanonicalBrowsingContext::SetActiveSessionHistoryEntry( + const Maybe<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(); + + // FIXME Need to do the equivalent of EvictContentViewersOrReplaceEntry. + 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(); + + // FIXME Need to do the equivalent of EvictContentViewersOrReplaceEntry. +} + +void CanonicalBrowsingContext::RemoveDynEntriesFromActiveSessionHistoryEntry() { + nsISHistory* shistory = GetSessionHistory(); + // In theory shistory can be null here if the method is called right after + // CanonicalBrowsingContext::ReplacedBy call. + NS_ENSURE_TRUE_VOID(shistory); + nsCOMPtr<nsISHEntry> root = nsSHistory::GetRootSHEntry(mActiveEntry); + shistory->RemoveDynEntries(shistory->GetIndexOfEntry(root), mActiveEntry); +} + +void CanonicalBrowsingContext::RemoveFromSessionHistory() { + nsSHistory* shistory = static_cast<nsSHistory*>(GetSessionHistory()); + if (shistory) { + nsCOMPtr<nsISHEntry> root = nsSHistory::GetRootSHEntry(mActiveEntry); + bool didRemove; + AutoTArray<nsID, 16> ids({GetHistoryID()}); + shistory->RemoveEntries(ids, shistory->GetIndexOfEntry(root), &didRemove); + if (didRemove) { + BrowsingContext* rootBC = shistory->GetBrowsingContext(); + if (rootBC) { + if (!rootBC->IsInProcess()) { + Unused << rootBC->Canonical() + ->GetContentParent() + ->SendDispatchLocationChangeEvent(rootBC); + } else if (rootBC->GetDocShell()) { + rootBC->GetDocShell()->DispatchLocationChangeEvent(); + } + } + } + } +} + +void CanonicalBrowsingContext::HistoryGo( + int32_t aOffset, uint64_t aHistoryEpoch, bool aRequireUserInteraction, + Maybe<ContentParentId> aContentId, + std::function<void(int32_t&&)>&& aResolver) { + if (aRequireUserInteraction && aOffset != -1 && aOffset != 1) { + NS_ERROR( + "aRequireUserInteraction may only be used with an offset of -1 or 1"); + return; + } + + nsSHistory* shistory = static_cast<nsSHistory*>(GetSessionHistory()); + if (!shistory) { + return; + } + + 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; + } + + // 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); + if (NS_FAILED(rv)) { + MOZ_LOG(gSHLog, LogLevel::Debug, + ("Dropping HistoryGo - bad index or same epoch (not in same doc)")); + return; + } + if (epoch < aHistoryEpoch || aContentId != id) { + MOZ_LOG(gSHLog, LogLevel::Debug, ("Set epoch")); + shistory->SetEpoch(aHistoryEpoch, aContentId); + } + aResolver(shistory->GetRequestedIndex()); + nsSHistory::LoadURIs(loadResults); +} + +JSObject* CanonicalBrowsingContext::WrapObject( + JSContext* aCx, JS::Handle<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 (IsTop()) { + BackgroundSessionStorageManager::RemoveManager(Id()); + } +} + +void CanonicalBrowsingContext::NotifyStartDelayedAutoplayMedia() { + WindowContext* windowContext = GetCurrentWindowContext(); + if (!windowContext) { + return; + } + + // As this function would only be called when user click the play icon on the + // tab bar. That's clear user intent to play, so gesture activate the window + // context so that the block-autoplay logic allows the media to autoplay. + windowContext->NotifyUserGestureActivation(); + AUTOPLAY_LOG("NotifyStartDelayedAutoplayMedia for chrome bc 0x%08" PRIx64, + Id()); + StartDelayedAutoplayMediaComponents(); + // Notfiy all content browsing contexts which are related with the canonical + // browsing content tree to start delayed autoplay media. + + Group()->EachParent([&](ContentParent* aParent) { + Unused << aParent->SendStartDelayedAutoplayMediaComponents(this); + }); +} + +void CanonicalBrowsingContext::NotifyMediaMutedChanged(bool aMuted, + ErrorResult& aRv) { + MOZ_ASSERT(!GetParent(), + "Notify media mute change on non top-level context!"); + SetMuted(aMuted, aRv); +} + +uint32_t CanonicalBrowsingContext::CountSiteOrigins( + GlobalObject& aGlobal, + const Sequence<OwningNonNull<BrowsingContext>>& aRoots) { + nsTHashtable<nsCStringHashKey> uniqueSiteOrigins; + + for (const auto& root : aRoots) { + root->PreOrderWalk([&](BrowsingContext* aContext) { + WindowGlobalParent* windowGlobalParent = + aContext->Canonical()->GetCurrentWindowGlobal(); + if (windowGlobalParent) { + nsIPrincipal* documentPrincipal = + windowGlobalParent->DocumentPrincipal(); + + bool isContentPrincipal = documentPrincipal->GetIsContentPrincipal(); + if (isContentPrincipal) { + nsCString siteOrigin; + documentPrincipal->GetSiteOrigin(siteOrigin); + uniqueSiteOrigins.PutEntry(siteOrigin); + } + } + }); + } + + return uniqueSiteOrigins.Count(); +} + +void CanonicalBrowsingContext::UpdateMediaControlAction( + const MediaControlAction& aAction) { + if (IsDiscarded()) { + return; + } + ContentMediaControlKeyHandler::HandleMediaControlAction(this, aAction); + Group()->EachParent([&](ContentParent* aParent) { + Unused << aParent->SendUpdateMediaControlAction(this, aAction); + }); +} + +void CanonicalBrowsingContext::LoadURI(const nsAString& aURI, + const LoadURIOptions& aOptions, + ErrorResult& aError) { + RefPtr<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) { + if (IsDiscarded()) { + return; + } + + // Stop any known network loads if necessary. + if (mCurrentLoad) { + mCurrentLoad->Cancel(NS_BINDING_ABORTED); + } + + if (nsDocShell* docShell = nsDocShell::Cast(GetDocShell())) { + if (aCancelContentJSEpoch.WasPassed()) { + docShell->SetCancelContentJSEpoch(aCancelContentJSEpoch.Value()); + } + docShell->GoBack(aRequireUserInteraction); + } else if (ContentParent* cp = GetContentParent()) { + Maybe<int32_t> cancelContentJSEpoch; + if (aCancelContentJSEpoch.WasPassed()) { + cancelContentJSEpoch = Some(aCancelContentJSEpoch.Value()); + } + Unused << cp->SendGoBack(this, cancelContentJSEpoch, + aRequireUserInteraction); + } +} +void CanonicalBrowsingContext::GoForward( + const Optional<int32_t>& aCancelContentJSEpoch, + bool aRequireUserInteraction) { + if (IsDiscarded()) { + return; + } + + // Stop any known network loads if necessary. + if (mCurrentLoad) { + mCurrentLoad->Cancel(NS_BINDING_ABORTED); + } + + if (auto* docShell = nsDocShell::Cast(GetDocShell())) { + if (aCancelContentJSEpoch.WasPassed()) { + docShell->SetCancelContentJSEpoch(aCancelContentJSEpoch.Value()); + } + docShell->GoForward(aRequireUserInteraction); + } else if (ContentParent* cp = GetContentParent()) { + Maybe<int32_t> cancelContentJSEpoch; + if (aCancelContentJSEpoch.WasPassed()) { + cancelContentJSEpoch.emplace(aCancelContentJSEpoch.Value()); + } + Unused << cp->SendGoForward(this, cancelContentJSEpoch, + aRequireUserInteraction); + } +} +void CanonicalBrowsingContext::GoToIndex( + int32_t aIndex, const Optional<int32_t>& aCancelContentJSEpoch) { + if (IsDiscarded()) { + return; + } + + // Stop any known network loads if necessary. + if (mCurrentLoad) { + mCurrentLoad->Cancel(NS_BINDING_ABORTED); + } + + if (auto* docShell = nsDocShell::Cast(GetDocShell())) { + if (aCancelContentJSEpoch.WasPassed()) { + docShell->SetCancelContentJSEpoch(aCancelContentJSEpoch.Value()); + } + docShell->GotoIndex(aIndex); + } else if (ContentParent* cp = GetContentParent()) { + Maybe<int32_t> cancelContentJSEpoch; + if (aCancelContentJSEpoch.WasPassed()) { + cancelContentJSEpoch.emplace(aCancelContentJSEpoch.Value()); + } + Unused << cp->SendGoToIndex(this, aIndex, cancelContentJSEpoch); + } +} +void CanonicalBrowsingContext::Reload(uint32_t aReloadFlags) { + if (IsDiscarded()) { + return; + } + + // Stop any known network loads if necessary. + if (mCurrentLoad) { + mCurrentLoad->Cancel(NS_BINDING_ABORTED); + } + + if (auto* docShell = nsDocShell::Cast(GetDocShell())) { + docShell->Reload(aReloadFlags); + } else if (ContentParent* cp = GetContentParent()) { + Unused << cp->SendReload(this, aReloadFlags); + } +} + +void CanonicalBrowsingContext::Stop(uint32_t aStopFlags) { + if (IsDiscarded()) { + return; + } + + // Stop any known network loads if necessary. + if (mCurrentLoad && (aStopFlags & nsIWebNavigation::STOP_NETWORK)) { + mCurrentLoad->Cancel(NS_BINDING_ABORTED); + } + + // Ask the docshell to stop to handle loads that haven't + // yet reached here, as well as non-network activity. + if (auto* docShell = nsDocShell::Cast(GetDocShell())) { + docShell->Stop(aStopFlags); + } else if (ContentParent* cp = GetContentParent()) { + Unused << cp->SendStopLoad(this, aStopFlags); + } +} + +void CanonicalBrowsingContext::PendingRemotenessChange::ProcessReady() { + if (!mPromise) { + return; + } + + // Wait for our blocker promise to resolve, if present. + if (mPrepareToChangePromise) { + mPrepareToChangePromise->Then( + GetMainThreadSerialEventTarget(), __func__, + [self = RefPtr{this}](bool) { self->Finish(); }, + [self = RefPtr{this}](nsresult aRv) { self->Cancel(aRv); }); + return; + } + + Finish(); +} + +void CanonicalBrowsingContext::PendingRemotenessChange::Finish() { + if (!mPromise) { + return; + } + + RefPtr<CanonicalBrowsingContext> target(mTarget); + if (target->IsDiscarded()) { + Cancel(NS_ERROR_FAILURE); + return; + } + + // While process switching, we need to check if any of our ancestors are + // discarded or no longer current, in which case the process switch needs to + // be aborted. + if (!target->AncestorsAreCurrent()) { + NS_WARNING("Ancestor context is no longer current"); + Cancel(NS_ERROR_FAILURE); + return; + } + + // If this BrowsingContext is embedded within the parent process, perform the + // process switch directly. + if (Element* browserElement = target->GetEmbedderElement()) { + MOZ_DIAGNOSTIC_ASSERT(target->IsTop(), + "We shouldn't be trying to change the remoteness of " + "non-remote iframes"); + + nsCOMPtr<nsIBrowser> browser = browserElement->AsBrowser(); + if (!browser) { + Cancel(NS_ERROR_FAILURE); + return; + } + + RefPtr<nsFrameLoaderOwner> frameLoaderOwner = + do_QueryObject(browserElement); + MOZ_RELEASE_ASSERT(frameLoaderOwner, + "embedder browser must be nsFrameLoaderOwner"); + + // Tell frontend code that this browser element is about to change process. + nsresult rv = browser->BeforeChangeRemoteness(); + if (NS_FAILED(rv)) { + Cancel(rv); + return; + } + + // Some frontend code checks the value of the `remote` attribute on the + // browser to determine if it is remote, so update the value. + browserElement->SetAttr(kNameSpaceID_None, nsGkAtoms::remote, + mContentParent ? u"true"_ns : u"false"_ns, + /* notify */ true); + + // The process has been created, hand off to nsFrameLoaderOwner to finish + // the process switch. + ErrorResult error; + frameLoaderOwner->ChangeRemotenessToProcess( + mContentParent, mReplaceBrowsingContext, mSpecificGroup, error); + if (error.Failed()) { + Cancel(error.StealNSResult()); + return; + } + + // Tell frontend the load is done. + bool loadResumed = false; + rv = browser->FinishChangeRemoteness(mPendingSwitchId, &loadResumed); + if (NS_WARN_IF(NS_FAILED(rv))) { + Cancel(rv); + return; + } + + // We did it! The process switch is complete. + RefPtr<nsFrameLoader> frameLoader = frameLoaderOwner->GetFrameLoader(); + RefPtr<BrowserParent> newBrowser = frameLoader->GetBrowserParent(); + if (!newBrowser) { + if (mContentParent) { + // Failed to create the BrowserParent somehow! Abort the process switch + // attempt. + Cancel(NS_ERROR_UNEXPECTED); + return; + } + + if (!loadResumed) { + RefPtr<nsDocShell> newDocShell = frameLoader->GetDocShell(error); + if (error.Failed()) { + Cancel(error.StealNSResult()); + return; + } + + rv = newDocShell->ResumeRedirectedLoad(mPendingSwitchId, + /* aHistoryIndex */ -1); + if (NS_FAILED(rv)) { + Cancel(error.StealNSResult()); + return; + } + } + } else if (!loadResumed) { + newBrowser->ResumeLoad(mPendingSwitchId); + } + + mPromise->Resolve(newBrowser, __func__); + Clear(); + return; + } + + if (NS_WARN_IF(!mContentParent)) { + Cancel(NS_ERROR_FAILURE); + return; + } + + RefPtr<WindowGlobalParent> embedderWindow = target->GetEmbedderWindowGlobal(); + if (NS_WARN_IF(!embedderWindow) || NS_WARN_IF(!embedderWindow->CanSend())) { + Cancel(NS_ERROR_FAILURE); + return; + } + + RefPtr<BrowserParent> embedderBrowser = embedderWindow->GetBrowserParent(); + if (NS_WARN_IF(!embedderBrowser)) { + Cancel(NS_ERROR_FAILURE); + return; + } + + // Pull load flags from our embedder browser. + nsCOMPtr<nsILoadContext> loadContext = embedderBrowser->GetLoadContext(); + MOZ_DIAGNOSTIC_ASSERT( + loadContext->UseRemoteTabs() && loadContext->UseRemoteSubframes(), + "Not supported without fission"); + + // NOTE: These are the only flags we actually care about + uint32_t chromeFlags = nsIWebBrowserChrome::CHROME_REMOTE_WINDOW | + nsIWebBrowserChrome::CHROME_FISSION_WINDOW; + if (loadContext->UsePrivateBrowsing()) { + chromeFlags |= nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW; + } + + RefPtr<WindowGlobalParent> oldWindow = target->GetCurrentWindowGlobal(); + RefPtr<BrowserParent> oldBrowser = + oldWindow ? oldWindow->GetBrowserParent() : nullptr; + bool wasRemote = oldWindow && oldWindow->IsProcessRoot(); + + // Update which process is considered the current owner + uint64_t inFlightProcessId = target->OwnerProcessId(); + target->SetInFlightProcessId(inFlightProcessId); + target->SetOwnerProcessId(mContentParent->ChildID()); + + auto resetInFlightId = [target, inFlightProcessId] { + target->ClearInFlightProcessId(inFlightProcessId); + }; + + // If we were in a remote frame, trigger unloading of the remote window. When + // the original remote window acknowledges, we can clear the in-flight ID. + if (wasRemote) { + MOZ_DIAGNOSTIC_ASSERT(oldBrowser); + MOZ_DIAGNOSTIC_ASSERT(oldBrowser != embedderBrowser); + MOZ_DIAGNOSTIC_ASSERT(oldBrowser->GetBrowserBridgeParent()); + + auto callback = [resetInFlightId](auto) { resetInFlightId(); }; + oldBrowser->SendWillChangeProcess(callback, callback); + oldBrowser->Destroy(); + } + + MOZ_ASSERT(!mReplaceBrowsingContext, "Cannot replace BC for subframe"); + nsCOMPtr<nsIPrincipal> initialPrincipal = + NullPrincipal::CreateWithInheritedAttributes( + target->OriginAttributesRef(), + /* isFirstParty */ false); + 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))) { + Cancel(rv); + return; + } + + // Tell the embedder process a remoteness change is in-process. When this is + // acknowledged, reset the in-flight ID if it used to be an in-process load. + RefPtr<BrowserParent> newBrowser = bridge->GetBrowserParent(); + { + auto callback = [wasRemote, resetInFlightId](auto) { + if (!wasRemote) { + resetInFlightId(); + } + }; + + ManagedEndpoint<PBrowserBridgeChild> endpoint = + embedderBrowser->OpenPBrowserBridgeEndpoint(bridge); + if (NS_WARN_IF(!endpoint.IsValid())) { + Cancel(NS_ERROR_UNEXPECTED); + return; + } + embedderWindow->SendMakeFrameRemote(target, std::move(endpoint), tabId, + newBrowser->GetLayersId(), callback, + callback); + } + + // Resume the pending load in our new process. + if (mPendingSwitchId) { + newBrowser->ResumeLoad(mPendingSwitchId); + } + + // We did it! The process switch is complete. + mPromise->Resolve(newBrowser, __func__); + Clear(); +} + +void CanonicalBrowsingContext::PendingRemotenessChange::Cancel(nsresult aRv) { + if (!mPromise) { + return; + } + + mPromise->Reject(aRv, __func__); + Clear(); +} + +void CanonicalBrowsingContext::PendingRemotenessChange::Clear() { + // Make sure we don't die while we're doing cleanup. + RefPtr<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; + mPrepareToChangePromise = nullptr; +} + +CanonicalBrowsingContext::PendingRemotenessChange::PendingRemotenessChange( + CanonicalBrowsingContext* aTarget, RemotenessPromise::Private* aPromise, + uint64_t aPendingSwitchId, bool aReplaceBrowsingContext) + : mTarget(aTarget), + mPromise(aPromise), + mPendingSwitchId(aPendingSwitchId), + mReplaceBrowsingContext(aReplaceBrowsingContext) {} + +CanonicalBrowsingContext::PendingRemotenessChange::~PendingRemotenessChange() { + MOZ_ASSERT(!mPromise && !mTarget && !mContentParent && !mSpecificGroup && + !mPrepareToChangePromise, + "should've already been Cancel() or Complete()-ed"); +} + +BrowserParent* CanonicalBrowsingContext::GetBrowserParent() const { + if (auto* wg = GetCurrentWindowGlobal()) { + return wg->GetBrowserParent(); + } + return nullptr; +} + +RefPtr<CanonicalBrowsingContext::RemotenessPromise> +CanonicalBrowsingContext::ChangeRemoteness(const nsACString& aRemoteType, + uint64_t aPendingSwitchId, + bool aReplaceBrowsingContext, + uint64_t aSpecificGroupId) { + MOZ_DIAGNOSTIC_ASSERT(IsContent(), + "cannot change the process of chrome contexts"); + MOZ_DIAGNOSTIC_ASSERT( + IsTop() == IsEmbeddedInProcess(0), + "toplevel content must be embedded in the parent process"); + MOZ_DIAGNOSTIC_ASSERT(!aReplaceBrowsingContext || IsTop(), + "Cannot replace BrowsingContext for subframes"); + MOZ_DIAGNOSTIC_ASSERT(aSpecificGroupId == 0 || aReplaceBrowsingContext, + "Cannot specify group ID unless replacing BC"); + MOZ_DIAGNOSTIC_ASSERT(aPendingSwitchId || !IsTop(), + "Should always have aPendingSwitchId for top-level " + "frames"); + + if (!AncestorsAreCurrent()) { + NS_WARNING("An ancestor context is no longer current"); + return RemotenessPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + + // Ensure our embedder hasn't been destroyed already. + RefPtr<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__); + } + + if (aRemoteType.IsEmpty() && (!IsTop() || !GetEmbedderElement())) { + NS_WARNING("Cannot load non-remote subframes"); + return RemotenessPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + + // Cancel ongoing remoteness changes. + if (mPendingRemotenessChange) { + mPendingRemotenessChange->Cancel(NS_ERROR_ABORT); + MOZ_DIAGNOSTIC_ASSERT(!mPendingRemotenessChange, "Should have cleared"); + } + + RefPtr<BrowserParent> embedderBrowser = + embedderWindowGlobal->GetBrowserParent(); + // Switching to local. No new process, so perform switch sync. + if (embedderBrowser && + aRemoteType == embedderBrowser->Manager()->GetRemoteType()) { + MOZ_DIAGNOSTIC_ASSERT( + aPendingSwitchId, + "We always have a PendingSwitchId, except for print-preview loads, " + "which will never perform a process-switch to being in-process with " + "their embedder"); + if (GetCurrentWindowGlobal()) { + MOZ_DIAGNOSTIC_ASSERT(GetCurrentWindowGlobal()->IsProcessRoot()); + RefPtr<BrowserParent> oldBrowser = + GetCurrentWindowGlobal()->GetBrowserParent(); + + uint64_t targetProcessId = OwnerProcessId(); + SetInFlightProcessId(targetProcessId); + auto callback = [target = RefPtr{this}, targetProcessId](auto) { + target->ClearInFlightProcessId(targetProcessId); + }; + oldBrowser->SendWillChangeProcess(callback, callback); + oldBrowser->Destroy(); + } + + // If the embedder process is remote, tell that remote process to become + // the owner. + MOZ_DIAGNOSTIC_ASSERT(!aReplaceBrowsingContext); + MOZ_DIAGNOSTIC_ASSERT(!aRemoteType.IsEmpty()); + SetOwnerProcessId(embedderBrowser->Manager()->ChildID()); + Unused << embedderWindowGlobal->SendMakeFrameLocal(this, aPendingSwitchId); + return RemotenessPromise::CreateAndResolve(embedderBrowser, __func__); + } + + // Switching to remote. Wait for new process to launch before switch. + auto promise = MakeRefPtr<RemotenessPromise::Private>(__func__); + RefPtr<PendingRemotenessChange> change = new PendingRemotenessChange( + this, promise, aPendingSwitchId, aReplaceBrowsingContext); + mPendingRemotenessChange = change; + + // If a specific BrowsingContextGroup ID was specified for this load, make + // sure to keep it alive until the process switch is completed. + if (aSpecificGroupId) { + change->mSpecificGroup = + BrowsingContextGroup::GetOrCreate(aSpecificGroupId); + change->mSpecificGroup->AddKeepAlive(); + } + + // Call `prepareToChangeRemoteness` in parallel with starting a new process + // for <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(); + } + change->mPrepareToChangePromise = GenericPromise::FromDomPromise(blocker); + } + + if (aRemoteType.IsEmpty()) { + change->ProcessReady(); + } else { + // Try to predict which BrowsingContextGroup will be used for the final load + // in this BrowsingContext. This has to be accurate if switching into an + // existing group, as it will control what pool of processes will be used + // for process selection. + // + // It's _technically_ OK to provide a group here if we're actually going to + // switch into a brand new group, though it's sub-optimal, as it can + // restrict the set of processes we're using. + BrowsingContextGroup* finalGroup = + aReplaceBrowsingContext ? change->mSpecificGroup.get() : Group(); + + change->mContentParent = ContentParent::GetNewOrUsedLaunchingBrowserProcess( + /* aRemoteType = */ aRemoteType, + /* aGroup = */ finalGroup, + /* aPriority = */ hal::PROCESS_PRIORITY_FOREGROUND, + /* aPreferUsed = */ false); + if (!change->mContentParent) { + change->Cancel(NS_ERROR_FAILURE); + return promise.forget(); + } + + // Add a KeepAlive used by this ContentParent, which will be cleared when + // the change is complete. This should prevent the process dying before + // we're ready to use it. + change->mContentParent->AddKeepAlive(); + change->mContentParent->WaitForLaunchAsync()->Then( + GetMainThreadSerialEventTarget(), __func__, + [change](ContentParent*) { change->ProcessReady(); }, + [change](LaunchError) { change->Cancel(NS_ERROR_FAILURE); }); + } + return promise.forget(); +} + +MediaController* CanonicalBrowsingContext::GetMediaController() { + // We would only create one media controller per tab, so accessing the + // controller via the top-level browsing context. + if (GetParent()) { + return Cast(Top())->GetMediaController(); + } + + MOZ_ASSERT(!GetParent(), + "Must access the controller from the top-level browsing context!"); + // Only content browsing context can create media controller, we won't create + // controller for chrome document, such as the browser UI. + if (!mTabMediaController && !IsDiscarded() && IsContent()) { + mTabMediaController = new MediaController(Id()); + } + return mTabMediaController; +} + +bool CanonicalBrowsingContext::HasCreatedMediaController() const { + return !!mTabMediaController; +} + +bool CanonicalBrowsingContext::SupportsLoadingInParent( + nsDocShellLoadState* aLoadState, uint64_t* aOuterWindowId) { + // We currently don't support initiating loads in the parent when they are + // watched by devtools. This is because devtools tracks loads using content + // process notifications, which happens after the load is initiated in this + // case. Devtools clears all prior requests when it detects a new navigation, + // so it drops the main document load that happened here. + if (WatchedByDevTools()) { + return false; + } + + // DocumentChannel currently only supports connecting channels into the + // content process, so we can only support schemes that will always be loaded + // there for now. Restrict to just http(s) for simplicity. + if (!net::SchemeIsHTTP(aLoadState->URI()) && + !net::SchemeIsHTTPS(aLoadState->URI())) { + return false; + } + + if (WindowGlobalParent* global = GetCurrentWindowGlobal()) { + nsCOMPtr<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; + } + + // Note: If successful, this will recurse into StartDocumentLoad and + // set mCurrentLoad to the DocumentLoadListener instance created. + // Ideally in the future we will only start loads from here, and we can + // just set this directly instead. + return net::DocumentLoadListener::LoadInParent(this, aLoadState, + aSetNavigating); +} + +bool CanonicalBrowsingContext::AttemptSpeculativeLoadInParent( + nsDocShellLoadState* aLoadState) { + // We currently only support starting loads directly from the + // CanonicalBrowsingContext for top-level BCs. + // We currently only support starting loads directly from the + // CanonicalBrowsingContext for top-level BCs. + if (!IsTopContent() || !GetContentParent() || + StaticPrefs::browser_tabs_documentchannel_parent_controlled()) { + return false; + } + + uint64_t outerWindowId = 0; + if (!SupportsLoadingInParent(aLoadState, &outerWindowId)) { + return false; + } + + // Session-history-in-parent implementation relies currently on getting a + // round trip through a child process. + if (aLoadState->LoadIsFromSessionHistory()) { + return false; + } + + // If we successfully open the DocumentChannel, then it'll register + // itself using aLoadIdentifier and be kept alive until it completes + // loading. + return net::DocumentLoadListener::SpeculativeLoadInParent(this, aLoadState); +} + +bool CanonicalBrowsingContext::StartDocumentLoad( + net::DocumentLoadListener* aLoad) { + // If we're controlling loads from the parent, then starting a new load means + // that we need to cancel any existing ones. + if (StaticPrefs::browser_tabs_documentchannel_parent_controlled() && + mCurrentLoad) { + mCurrentLoad->Cancel(NS_BINDING_ABORTED); + } + mCurrentLoad = aLoad; + + if (NS_FAILED(SetCurrentLoadIdentifier(Some(aLoad->GetLoadIdentifier())))) { + mCurrentLoad = nullptr; + return false; + } + + return true; +} + +void CanonicalBrowsingContext::EndDocumentLoad(bool aForProcessSwitch) { + mCurrentLoad = nullptr; + + if (!aForProcessSwitch) { + // Resetting the current load identifier on a discarded context + // has no effect when a document load has finished. + Unused << SetCurrentLoadIdentifier(Nothing()); + } +} + +void CanonicalBrowsingContext::ResetSHEntryHasUserInteractionCache() { + WindowContext* topWc = GetTopWindowContext(); + if (topWc && !topWc->IsDiscarded()) { + MOZ_ALWAYS_SUCCEEDS(topWc->SetSHEntryHasUserInteraction(false)); + } +} + +void CanonicalBrowsingContext::HistoryCommitIndexAndLength() { + nsID changeID = {}; + CallerWillNotifyHistoryIndexAndLengthChanges caller(nullptr); + HistoryCommitIndexAndLength(changeID, caller); +} +void CanonicalBrowsingContext::HistoryCommitIndexAndLength( + const nsID& aChangeID, + const CallerWillNotifyHistoryIndexAndLengthChanges& aProofOfCaller) { + if (!IsTop()) { + Cast(Top())->HistoryCommitIndexAndLength(aChangeID, aProofOfCaller); + return; + } + + nsISHistory* shistory = GetSessionHistory(); + if (!shistory) { + return; + } + int32_t index = 0; + shistory->GetIndex(&index); + int32_t length = shistory->GetCount(); + + GetChildSessionHistory()->SetIndexAndLength(index, length, aChangeID); + + Group()->EachParent([&](ContentParent* aParent) { + Unused << aParent->SendHistoryCommitIndexAndLength(this, index, length, + aChangeID); + }); +} + +void CanonicalBrowsingContext::ResetScalingZoom() { + // This currently only ever gets called in the parent process, and we + // pass the message on to the WindowGlobalChild for the rootmost browsing + // context. + if (WindowGlobalParent* topWindow = GetTopWindowContext()) { + Unused << topWindow->SendResetScalingZoom(); + } +} + +void CanonicalBrowsingContext::SetContainerFeaturePolicy( + FeaturePolicy* aContainerFeaturePolicy) { + mContainerFeaturePolicy = aContainerFeaturePolicy; + + if (WindowGlobalParent* current = GetCurrentWindowGlobal()) { + Unused << current->SendSetContainerFeaturePolicy(mContainerFeaturePolicy); + } +} + +void CanonicalBrowsingContext::SetCrossGroupOpenerId(uint64_t aOpenerId) { + MOZ_DIAGNOSTIC_ASSERT(IsTopContent()); + MOZ_DIAGNOSTIC_ASSERT(mCrossGroupOpenerId == 0, + "Can only set CrossGroupOpenerId once"); + mCrossGroupOpenerId = aOpenerId; +} + +NS_IMPL_CYCLE_COLLECTION_INHERITED(CanonicalBrowsingContext, BrowsingContext, + mSessionHistory, mContainerFeaturePolicy) + +NS_IMPL_ADDREF_INHERITED(CanonicalBrowsingContext, BrowsingContext) +NS_IMPL_RELEASE_INHERITED(CanonicalBrowsingContext, BrowsingContext) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CanonicalBrowsingContext) +NS_INTERFACE_MAP_END_INHERITING(BrowsingContext) + +} // namespace dom +} // namespace mozilla diff --git a/docshell/base/CanonicalBrowsingContext.h b/docshell/base/CanonicalBrowsingContext.h new file mode 100644 index 0000000000..dd4226d7e1 --- /dev/null +++ b/docshell/base/CanonicalBrowsingContext.h @@ -0,0 +1,383 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_CanonicalBrowsingContext_h +#define mozilla_dom_CanonicalBrowsingContext_h + +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/MediaControlKeySource.h" +#include "mozilla/dom/BrowsingContextWebProgress.h" +#include "mozilla/dom/ipc/IdType.h" +#include "mozilla/RefPtr.h" +#include "mozilla/MozPromise.h" +#include "nsCycleCollectionParticipant.h" +#include "nsWrapperCache.h" +#include "nsTArray.h" +#include "nsTHashtable.h" +#include "nsHashKeys.h" +#include "nsISecureBrowserUI.h" + +class nsISHistory; +class nsIWidget; +class nsSHistory; +class nsBrowserStatusFilter; +class nsSecureBrowserUI; +class CallerWillNotifyHistoryIndexAndLengthChanges; + +namespace mozilla { +enum class CallState; + +namespace net { +class DocumentLoadListener; +} + +namespace dom { + +class BrowserParent; +class FeaturePolicy; +struct LoadURIOptions; +class MediaController; +struct LoadingSessionHistoryInfo; +class SessionHistoryEntry; +class WindowGlobalParent; + +// CanonicalBrowsingContext is a BrowsingContext living in the parent +// process, with whatever extra data that a BrowsingContext in the +// parent needs. +class CanonicalBrowsingContext final : public BrowsingContext { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(CanonicalBrowsingContext, + BrowsingContext) + + static already_AddRefed<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); + + void SetInFlightProcessId(uint64_t aProcessId); + void ClearInFlightProcessId(uint64_t aProcessId); + uint64_t GetInFlightProcessId() const { return mInFlightProcessId; } + + // The ID of the BrowsingContext which caused this BrowsingContext to be + // opened, or `0` if this is unknown. + // Only set for toplevel content BrowsingContexts, and may be from a different + // BrowsingContextGroup. + uint64_t GetCrossGroupOpenerId() const { return mCrossGroupOpenerId; } + void SetCrossGroupOpenerId(uint64_t aOpenerId); + + void GetWindowGlobals(nsTArray<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(); + + // Same as `GetParentWindowContext`, but will also cross <browser> and + // content/chrome boundaries. + already_AddRefed<WindowGlobalParent> GetEmbedderWindowGlobal() const; + + already_AddRefed<CanonicalBrowsingContext> GetParentCrossChromeBoundary(); + + already_AddRefed<CanonicalBrowsingContext> TopCrossChromeBoundary(); + Nullable<WindowProxyHolder> GetTopChromeWindow(); + + nsISHistory* GetSessionHistory(); + SessionHistoryEntry* GetActiveSessionHistoryEntry(); + + UniquePtr<LoadingSessionHistoryInfo> CreateLoadingSessionHistoryEntryForLoad( + nsDocShellLoadState* aLoadState, nsIChannel* aChannel); + + UniquePtr<LoadingSessionHistoryInfo> ReplaceLoadingSessionHistoryEntryForLoad( + LoadingSessionHistoryInfo* aInfo, nsIChannel* aChannel); + + // Call the given callback on all top-level descendant BrowsingContexts. + // Return Callstate::Stop from the callback to stop calling + // further children. + void CallOnAllTopDescendants( + const std::function<mozilla::CallState(CanonicalBrowsingContext*)>& + aCallback); + + void SessionHistoryCommit(uint64_t aLoadId, const nsID& aChangeID, + uint32_t aLoadType, bool aPersist, + bool aCloneEntryChildren); + + // Calls the session history listeners' OnHistoryReload, storing the result in + // aCanReload. If aCanReload is set to true and we have an active or a loading + // entry then aLoadState will be initialized from that entry, and + // aReloadActiveEntry will be true if we have an active entry. If aCanReload + // is true and aLoadState and aReloadActiveEntry are not set then we should + // attempt to reload based on the current document in the docshell. + void NotifyOnHistoryReload(bool aForceReload, bool& aCanReload, + Maybe<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(); + + void HistoryGo(int32_t aIndex, uint64_t aHistoryEpoch, + bool aRequireUserInteraction, + Maybe<ContentParentId> aContentId, + std::function<void(int32_t&&)>&& aResolver); + + 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); + + // This function would propogate the action to its all child browsing contexts + // in content processes. + void UpdateMediaControlAction(const MediaControlAction& aAction); + + // Triggers a load in the process + using BrowsingContext::LoadURI; + void LoadURI(const nsAString& aURI, const LoadURIOptions& aOptions, + ErrorResult& aError); + + void GoBack(const Optional<int32_t>& aCancelContentJSEpoch, + bool aRequireUserInteraction); + void GoForward(const Optional<int32_t>& aCancelContentJSEpoch, + bool aRequireUserInteraction); + void GoToIndex(int32_t aIndex, + const Optional<int32_t>& aCancelContentJSEpoch); + void Reload(uint32_t aReloadFlags); + void Stop(uint32_t aStopFlags); + + BrowserParent* GetBrowserParent() const; + + // Internal method to change which process a BrowsingContext is being loaded + // in. The returned promise will resolve when the process switch is completed. + // + // A NOT_REMOTE_TYPE aRemoteType argument will perform a process switch into + // the parent process, and the method will resolve with a null BrowserParent. + using RemotenessPromise = MozPromise<RefPtr<BrowserParent>, nsresult, false>; + RefPtr<RemotenessPromise> ChangeRemoteness(const nsACString& aRemoteType, + uint64_t aPendingSwitchId, + bool aReplaceBrowsingContext, + uint64_t aSpecificGroupId); + + // Return a media controller from the top-level browsing context that can + // control all media belonging to this browsing context tree. Return nullptr + // if the top-level browsing context has been discarded. + MediaController* GetMediaController(); + bool HasCreatedMediaController() const; + + // Attempts to start loading the given load state in this BrowsingContext, + // without requiring any communication from a docshell. This will handle + // computing the right process to load in, and organising handoff to + // the right docshell when we get a response. + bool LoadInParent(nsDocShellLoadState* aLoadState, bool aSetNavigating); + + // Attempts to start loading the given load state in this BrowsingContext, + // in parallel with a DocumentChannelChild being created in the docshell. + // Requires the DocumentChannel to connect with this load for it to + // complete successfully. + bool AttemptSpeculativeLoadInParent(nsDocShellLoadState* aLoadState); + + // Get or create a secure browser UI for this BrowsingContext + nsISecureBrowserUI* GetSecureBrowserUI(); + + BrowsingContextWebProgress* GetWebProgress() { return mWebProgress; } + + // Called when the current URI changes (from an + // nsIWebProgressListener::OnLocationChange event, so that we + // can update our security UI for the new location, or when the + // mixed content/https-only state for our current window is changed. + void UpdateSecurityState(); + + void MaybeAddAsProgressListener(nsIWebProgress* aWebProgress); + + // Called when a navigation forces us to recreate our browsing + // context (for example, when switching in or out of the parent + // process). + // aNewContext is the newly created BrowsingContext that is replacing + // us. + void ReplacedBy(CanonicalBrowsingContext* aNewContext); + + bool HasHistoryEntry(nsISHEntry* aEntry); + + void SwapHistoryEntries(nsISHEntry* aOldEntry, nsISHEntry* aNewEntry); + + void AddLoadingSessionHistoryEntry(uint64_t aLoadId, + SessionHistoryEntry* aEntry); + + void GetLoadingSessionHistoryInfoFromParent( + Maybe<LoadingSessionHistoryInfo>& aLoadingInfo, int32_t* aRequestedIndex, + int32_t* aLength); + + void HistoryCommitIndexAndLength(); + + void ResetScalingZoom(); + + void SetContainerFeaturePolicy(FeaturePolicy* aContainerFeaturePolicy); + FeaturePolicy* GetContainerFeaturePolicy() const { + return mContainerFeaturePolicy; + } + + protected: + // Called when the browsing context is being discarded. + void CanonicalDiscard(); + + using Type = BrowsingContext::Type; + CanonicalBrowsingContext(WindowContext* aParentWindow, + BrowsingContextGroup* aGroup, + uint64_t aBrowsingContextId, + uint64_t aOwnerProcessId, + uint64_t aEmbedderProcessId, Type aType, + FieldValues&& aInit); + + private: + friend class BrowsingContext; + + ~CanonicalBrowsingContext() = default; + + class PendingRemotenessChange { + public: + NS_INLINE_DECL_REFCOUNTING(PendingRemotenessChange) + + PendingRemotenessChange(CanonicalBrowsingContext* aTarget, + RemotenessPromise::Private* aPromise, + uint64_t aPendingSwitchId, + bool aReplaceBrowsingContext); + + void Cancel(nsresult aRv); + + private: + friend class CanonicalBrowsingContext; + + ~PendingRemotenessChange(); + void ProcessReady(); + void Finish(); + void Clear(); + + RefPtr<CanonicalBrowsingContext> mTarget; + RefPtr<RemotenessPromise::Private> mPromise; + RefPtr<GenericPromise> mPrepareToChangePromise; + RefPtr<ContentParent> mContentParent; + RefPtr<BrowsingContextGroup> mSpecificGroup; + + uint64_t mPendingSwitchId; + bool mReplaceBrowsingContext; + }; + + friend class net::DocumentLoadListener; + // Called when a DocumentLoadListener is created to start a load for + // this browsing context. Returns false if a higher priority load is + // already in-progress and the new one has been rejected. + bool StartDocumentLoad(net::DocumentLoadListener* aLoad); + // Called once DocumentLoadListener completes handling a load, and it + // is either complete, or handed off to the final channel to deliver + // data to the destination docshell. + void EndDocumentLoad(bool aForProcessSwitch); + + bool SupportsLoadingInParent(nsDocShellLoadState* aLoadState, + uint64_t* aOuterWindowId); + + void HistoryCommitIndexAndLength( + const nsID& aChangeID, + const CallerWillNotifyHistoryIndexAndLengthChanges& aProofOfCaller); + + // XXX(farre): Store a ContentParent pointer here rather than mProcessId? + // Indicates which process owns the docshell. + uint64_t mProcessId; + + // Indicates which process owns the embedder element. + uint64_t mEmbedderProcessId; + + // The ID of the former owner process during an ownership change, which may + // have in-flight messages that assume it is still the owner. + uint64_t mInFlightProcessId = 0; + + uint64_t mCrossGroupOpenerId = 0; + + // This function will make the top window context reset its + // "SHEntryHasUserInteraction" cache that prevents documents from repeatedly + // setting user interaction on SH entries. Should be called anytime SH + // entries are added or replaced. + void ResetSHEntryHasUserInteractionCache(); + + // The current remoteness change which is in a pending state. + RefPtr<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; + RefPtr<nsBrowserStatusFilter> mStatusFilter; + + RefPtr<FeaturePolicy> mContainerFeaturePolicy; +}; + +} // namespace dom +} // namespace mozilla + +#endif // !defined(mozilla_dom_CanonicalBrowsingContext_h) diff --git a/docshell/base/ChildProcessChannelListener.cpp b/docshell/base/ChildProcessChannelListener.cpp new file mode 100644 index 0000000000..3580127f6a --- /dev/null +++ b/docshell/base/ChildProcessChannelListener.cpp @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/ChildProcessChannelListener.h" + +#include "mozilla/ipc/Endpoint.h" +#include "nsDocShellLoadState.h" + +namespace mozilla { +namespace dom { + +static StaticRefPtr<ChildProcessChannelListener> sCPCLSingleton; + +void ChildProcessChannelListener::RegisterCallback(uint64_t aIdentifier, + Callback&& aCallback) { + if (auto args = mChannelArgs.GetAndRemove(aIdentifier)) { + nsresult rv = + aCallback(args->mLoadState, std::move(args->mStreamFilterEndpoints), + args->mTiming); + args->mResolver(rv); + } else { + mCallbacks.Put(aIdentifier, std::move(aCallback)); + } +} + +void ChildProcessChannelListener::OnChannelReady( + nsDocShellLoadState* aLoadState, uint64_t aIdentifier, + nsTArray<Endpoint>&& aStreamFilterEndpoints, nsDOMNavigationTiming* aTiming, + Resolver&& aResolver) { + if (auto callback = mCallbacks.GetAndRemove(aIdentifier)) { + nsresult rv = + (*callback)(aLoadState, std::move(aStreamFilterEndpoints), aTiming); + aResolver(rv); + } else { + mChannelArgs.Put(aIdentifier, + {aLoadState, std::move(aStreamFilterEndpoints), aTiming, + std::move(aResolver)}); + } +} + +ChildProcessChannelListener::~ChildProcessChannelListener() { + for (auto& args : mChannelArgs) { + args.GetData().mResolver(NS_ERROR_FAILURE); + } +} + +already_AddRefed<ChildProcessChannelListener> +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..232ca12dd5 --- /dev/null +++ b/docshell/base/ChildProcessChannelListener.h @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_ChildProcessChannelListener_h +#define mozilla_dom_ChildProcessChannelListener_h + +#include <functional> + +#include "mozilla/extensions/StreamFilterParent.h" +#include "mozilla/net/NeckoChannelParams.h" +#include "nsDOMNavigationTiming.h" +#include "nsDataHashtable.h" +#include "nsIChannel.h" +#include "BackgroundUtils.h" + +namespace mozilla { +namespace dom { + +class ChildProcessChannelListener final { + NS_INLINE_DECL_REFCOUNTING(ChildProcessChannelListener) + + using Endpoint = mozilla::ipc::Endpoint<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. + nsDataHashtable<nsUint64HashKey, Callback> mCallbacks; + nsDataHashtable<nsUint64HashKey, CallbackArgs> mChannelArgs; +}; + +} // namespace dom +} // namespace mozilla + +#endif // !defined(mozilla_dom_ChildProcessChannelListener_h) diff --git a/docshell/base/IHistory.h b/docshell/base/IHistory.h new file mode 100644 index 0000000000..12609cc110 --- /dev/null +++ b/docshell/base/IHistory.h @@ -0,0 +1,150 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_IHistory_h_ +#define mozilla_IHistory_h_ + +#include "nsISupports.h" +#include "nsDataHashtable.h" +#include "nsURIHashKey.h" +#include "nsTObserverArray.h" + +class nsIURI; +class nsIWidget; + +namespace mozilla { + +namespace dom { +class Document; +class Link; +} // namespace dom + +// 0057c9d3-b98e-4933-bdc5-0275d06705e1 +#define IHISTORY_IID \ + { \ + 0x0057c9d3, 0xb98e, 0x4933, { \ + 0xbd, 0xc5, 0x02, 0x75, 0xd0, 0x67, 0x05, 0xe1 \ + } \ + } + +class IHistory : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(IHISTORY_IID) + + /** + * Registers the Link for notifications about the visited-ness of aURI. + * Consumers should assume that the URI is unvisited after calling this, and + * they will be notified if that state (unvisited) changes by having + * VisitedQueryFinished called on themselves. Note that it may call + * synchronously if the answer is already known. + * + * @note VisitedQueryFinished must not call RegisterVisitedCallback or + * UnregisterVisitedCallback. + * + * @pre aURI must not be null. + * @pre aLink may be null only in the parent (chrome) process. + * + * @param aURI + * The URI to check. + * @param aLink + * The link to update whenever the history status changes. The + * implementation will only hold onto a raw pointer, so if this + * object should be destroyed, be sure to call + * UnregisterVistedCallback first. + */ + virtual void RegisterVisitedCallback(nsIURI* aURI, dom::Link* aLink) = 0; + + /** + * Unregisters a previously registered Link object. This must be called + * before destroying the registered object, and asserts when misused. + * + * @pre aURI must not be null. + * @pre aLink must not be null. + * + * @param aURI + * The URI that aLink was registered for. + * @param aLink + * The link object to unregister for aURI. + */ + virtual void UnregisterVisitedCallback(nsIURI* aURI, dom::Link* aLink) = 0; + + enum class VisitedStatus : uint8_t { + Unknown, + Visited, + Unvisited, + }; + + /** + * Notifies about the visited status of a given URI. The visited status cannot + * be unknown, otherwise there's no point in notifying of anything. + */ + virtual void NotifyVisited(nsIURI*, VisitedStatus) = 0; + + enum VisitFlags { + /** + * Indicates whether the URI was loaded in a top-level window. + */ + TOP_LEVEL = 1 << 0, + /** + * Indicates whether the URI is the target of a permanent redirect. + */ + REDIRECT_PERMANENT = 1 << 1, + /** + * Indicates whether the URI is the target of a temporary redirect. + */ + REDIRECT_TEMPORARY = 1 << 2, + /** + * Indicates the URI will redirect (Response code 3xx). + */ + REDIRECT_SOURCE = 1 << 3, + /** + * Indicates the URI caused an error that is unlikely fixable by a + * retry, like a not found or unfetchable page. + */ + UNRECOVERABLE_ERROR = 1 << 4, + /** + * If REDIRECT_SOURCE is set, this indicates that the redirect is permanent. + * Note this differs from REDIRECT_PERMANENT because that one refers to how + * we reached the URI, while this is used when the URI itself redirects. + */ + REDIRECT_SOURCE_PERMANENT = 1 << 5 + }; + + /** + * Adds a history visit for the URI. + * + * @pre aURI must not be null. + * + * @param aWidget + * The widget for the DocShell. + * @param aURI + * The URI of the page being visited. + * @param aLastVisitedURI + * The URI of the last visit in the chain. + * @param aFlags + * The VisitFlags describing this visit. + */ + NS_IMETHOD VisitURI(nsIWidget* aWidget, nsIURI* aURI, nsIURI* aLastVisitedURI, + uint32_t aFlags) = 0; + + /** + * Set the title of the URI. + * + * @pre aURI must not be null. + * + * @param aURI + * The URI to set the title for. + * @param aTitle + * The title string. + */ + NS_IMETHOD SetURITitle(nsIURI* aURI, const nsAString& aTitle) = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(IHistory, IHISTORY_IID) + +} // namespace mozilla + +#endif // mozilla_IHistory_h_ diff --git a/docshell/base/LoadContext.cpp b/docshell/base/LoadContext.cpp new file mode 100644 index 0000000000..e90ad77f80 --- /dev/null +++ b/docshell/base/LoadContext.cpp @@ -0,0 +1,236 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Assertions.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/dom/ToJSValue.h" +#include "mozilla/LoadContext.h" +#include "mozilla/Preferences.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/ScriptSettings.h" // for AutoJSAPI +#include "mozilla/dom/BrowsingContext.h" +#include "nsContentUtils.h" +#include "xpcpublic.h" + +namespace mozilla { + +NS_IMPL_ISUPPORTS(LoadContext, nsILoadContext, nsIInterfaceRequestor) + +LoadContext::LoadContext(const IPC::SerializedLoadContext& aToCopy, + dom::Element* aTopFrameElement, + OriginAttributes& aAttrs) + : mTopFrameElement(do_GetWeakReference(aTopFrameElement)), + mIsContent(aToCopy.mIsContent), + mUseRemoteTabs(aToCopy.mUseRemoteTabs), + mUseRemoteSubframes(aToCopy.mUseRemoteSubframes), + mUseTrackingProtection(aToCopy.mUseTrackingProtection), +#ifdef DEBUG + mIsNotNull(aToCopy.mIsNotNull), +#endif + mOriginAttributes(aAttrs) { +} + +LoadContext::LoadContext(OriginAttributes& aAttrs) + : mTopFrameElement(nullptr), + mIsContent(false), + mUseRemoteTabs(false), + mUseRemoteSubframes(false), + mUseTrackingProtection(false), +#ifdef DEBUG + mIsNotNull(true), +#endif + mOriginAttributes(aAttrs) { +} + +LoadContext::LoadContext(nsIPrincipal* aPrincipal, + nsILoadContext* aOptionalBase) + : mTopFrameElement(nullptr), + mIsContent(true), + mUseRemoteTabs(false), + mUseRemoteSubframes(false), + mUseTrackingProtection(false), +#ifdef DEBUG + mIsNotNull(true), +#endif + mOriginAttributes(aPrincipal->OriginAttributesRef()) { + if (!aOptionalBase) { + return; + } + + MOZ_ALWAYS_SUCCEEDS(aOptionalBase->GetIsContent(&mIsContent)); + MOZ_ALWAYS_SUCCEEDS(aOptionalBase->GetUseRemoteTabs(&mUseRemoteTabs)); + MOZ_ALWAYS_SUCCEEDS( + aOptionalBase->GetUseRemoteSubframes(&mUseRemoteSubframes)); + MOZ_ALWAYS_SUCCEEDS( + aOptionalBase->GetUseTrackingProtection(&mUseTrackingProtection)); +} + +LoadContext::~LoadContext() = default; + +//----------------------------------------------------------------------------- +// LoadContext::nsILoadContext +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +LoadContext::GetAssociatedWindow(mozIDOMWindowProxy**) { + MOZ_ASSERT(mIsNotNull); + + // can't support this in the parent process + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +LoadContext::GetTopWindow(mozIDOMWindowProxy**) { + MOZ_ASSERT(mIsNotNull); + + // can't support this in the parent process + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +LoadContext::GetTopFrameElement(dom::Element** aElement) { + nsCOMPtr<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::MutableHandleValue aAttrs) { + bool ok = ToJSValue(aCx, mOriginAttributes, aAttrs); + NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE); + return NS_OK; +} + +NS_IMETHODIMP_(void) +LoadContext::GetOriginAttributes(mozilla::OriginAttributes& aAttrs) { + aAttrs = mOriginAttributes; +} + +NS_IMETHODIMP +LoadContext::GetUseTrackingProtection(bool* aUseTrackingProtection) { + MOZ_ASSERT(mIsNotNull); + + NS_ENSURE_ARG_POINTER(aUseTrackingProtection); + + *aUseTrackingProtection = mUseTrackingProtection; + return NS_OK; +} + +NS_IMETHODIMP +LoadContext::SetUseTrackingProtection(bool aUseTrackingProtection) { + MOZ_ASSERT_UNREACHABLE("Should only be set through nsDocShell"); + + return NS_ERROR_UNEXPECTED; +} + +//----------------------------------------------------------------------------- +// LoadContext::nsIInterfaceRequestor +//----------------------------------------------------------------------------- +NS_IMETHODIMP +LoadContext::GetInterface(const nsIID& aIID, void** aResult) { + NS_ENSURE_ARG_POINTER(aResult); + *aResult = nullptr; + + if (aIID.Equals(NS_GET_IID(nsILoadContext))) { + *aResult = static_cast<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..689934f714 --- /dev/null +++ b/docshell/base/SerializedLoadContext.h @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef SerializedLoadContext_h +#define SerializedLoadContext_h + +#include "base/basictypes.h" +#include "ipc/IPCMessageUtils.h" +#include "ipc/IPCMessageUtilsSpecializations.h" +#include "mozilla/BasePrincipal.h" + +class nsILoadContext; + +/* + * This file contains the IPC::SerializedLoadContext class, which is used to + * copy data across IPDL from Child process contexts so it is available in the + * Parent. + */ + +class nsIChannel; +class nsIWebSocketChannel; + +namespace IPC { + +class SerializedLoadContext { + public: + SerializedLoadContext() + : mIsNotNull(false), + mIsPrivateBitValid(false), + mIsContent(false), + mUseRemoteTabs(false), + mUseRemoteSubframes(false), + mUseTrackingProtection(false) { + Init(nullptr); + } + + explicit SerializedLoadContext(nsILoadContext* aLoadContext); + explicit SerializedLoadContext(nsIChannel* aChannel); + explicit SerializedLoadContext(nsIWebSocketChannel* aChannel); + + void Init(nsILoadContext* aLoadContext); + + bool IsNotNull() const { return mIsNotNull; } + bool IsPrivateBitValid() const { return mIsPrivateBitValid; } + + // used to indicate if child-side LoadContext * was null. + bool mIsNotNull; + // used to indicate if child-side mUsePrivateBrowsing flag is valid, even if + // mIsNotNull is false, i.e., child LoadContext was null. + bool mIsPrivateBitValid; + bool mIsContent; + bool mUseRemoteTabs; + bool mUseRemoteSubframes; + bool mUseTrackingProtection; + mozilla::OriginAttributes mOriginAttributes; +}; + +// Function to serialize over IPDL +template <> +struct ParamTraits<SerializedLoadContext> { + typedef SerializedLoadContext paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + nsAutoCString suffix; + aParam.mOriginAttributes.CreateSuffix(suffix); + + WriteParam(aMsg, aParam.mIsNotNull); + WriteParam(aMsg, aParam.mIsContent); + WriteParam(aMsg, aParam.mIsPrivateBitValid); + WriteParam(aMsg, aParam.mUseRemoteTabs); + WriteParam(aMsg, aParam.mUseRemoteSubframes); + WriteParam(aMsg, aParam.mUseTrackingProtection); + WriteParam(aMsg, suffix); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + nsAutoCString suffix; + if (!ReadParam(aMsg, aIter, &aResult->mIsNotNull) || + !ReadParam(aMsg, aIter, &aResult->mIsContent) || + !ReadParam(aMsg, aIter, &aResult->mIsPrivateBitValid) || + !ReadParam(aMsg, aIter, &aResult->mUseRemoteTabs) || + !ReadParam(aMsg, aIter, &aResult->mUseRemoteSubframes) || + !ReadParam(aMsg, aIter, &aResult->mUseTrackingProtection) || + !ReadParam(aMsg, aIter, &suffix)) { + return false; + } + return aResult->mOriginAttributes.PopulateFromSuffix(suffix); + } +}; + +} // namespace IPC + +#endif // SerializedLoadContext_h diff --git a/docshell/base/SyncedContext.h b/docshell/base/SyncedContext.h new file mode 100644 index 0000000000..e9ad5017cf --- /dev/null +++ b/docshell/base/SyncedContext.h @@ -0,0 +1,333 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_SyncedContext_h +#define mozilla_dom_SyncedContext_h + +#include <array> +#include <type_traits> +#include <utility> +#include "mozilla/Attributes.h" +#include "mozilla/EnumSet.h" +#include "nsStringFwd.h" +#include "nscore.h" + +// Referenced via macro definitions +#include "mozilla/ErrorResult.h" + +class PickleIterator; + +namespace IPC { +class Message; +} // namespace IPC + +namespace mozilla { +namespace ipc { +class IProtocol; +class IPCResult; +template <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>; + +using IndexSet = EnumSet<size_t, uint64_t>; + +template <typename Context> +class Transaction { + public: + // Set a field at the given index in this `Transaction`. Creating a + // `Transaction` object and setting multiple fields on it allows for + // multiple mutations to be performed atomically. + template <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); + + private: + friend struct mozilla::ipc::IPDLParamTraits<Transaction<Context>>; + + void Write(IPC::Message* aMsg, mozilla::ipc::IProtocol* aActor) const; + bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + mozilla::ipc::IProtocol* aActor); + + // You probably don't want to directly call this method - instead call + // `Commit`, which will perform the necessary synchronization. + // + // `Validate` must be called before calling this method. + void Apply(Context* aOwner); + + // Returns the set of fields which failed to validate, or an empty set if + // there were no validation errors. + IndexSet Validate(Context* aOwner, ContentParent* aSource); + + template <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; + static_assert(count < 64, + "At most 64 synced fields are supported. Please file a bug if " + "you need additional fields."); + + // The base type will define a series of `Get` methods for looking up a field + // by its field index. + using Base::Get; + + // Calls a generic lambda with an `Index<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::Message* aMsg, mozilla::ipc::IProtocol* aActor) const; + bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + 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; +}; + +// 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_FIELDS_DECL(name, type) \ + /* index based field lookup */ \ + type& Get(FieldIndex<IDX_##name>) { return m##name; } \ + const type& Get(FieldIndex<IDX_##name>) const { return m##name; } \ + \ + /* storage for the field */ \ + type m##name{}; +#define MOZ_DECL_SYNCED_CONTEXT_FIELD_GETSET(name, type) \ + const type& Get##name() const { return mFields.template Get<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; + +// 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>; \ + \ + /* Struct containing the data for all synced fields as members */ \ + struct BaseFieldValues { \ + eachfield(MOZ_DECL_SYNCED_CONTEXT_FIELDS_DECL) \ + }; \ + using FieldValues = \ + typename ::mozilla::dom::syncedcontext::FieldValues<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::Message* aMsg, IProtocol* aActor, + const paramType& aParam) { + aParam.Write(aMsg, aActor); + } + + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, paramType* aResult) { + return aResult->Read(aMsg, aIter, aActor); + } +}; + +template <typename Base, size_t Count> +struct IPDLParamTraits<dom::syncedcontext::FieldValues<Base, Count>> { + typedef dom::syncedcontext::FieldValues<Base, Count> paramType; + + static void Write(IPC::Message* aMsg, IProtocol* aActor, + const paramType& aParam) { + aParam.Write(aMsg, aActor); + } + + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, paramType* aResult) { + return aResult->Read(aMsg, aIter, aActor); + } +}; + +} // namespace ipc +} // namespace mozilla + +#endif // !defined(mozilla_dom_SyncedContext_h) diff --git a/docshell/base/SyncedContextInlines.h b/docshell/base/SyncedContextInlines.h new file mode 100644 index 0000000000..d4fda40162 --- /dev/null +++ b/docshell/base/SyncedContextInlines.h @@ -0,0 +1,213 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_SyncedContextInlines_h +#define mozilla_dom_SyncedContextInlines_h + +#include "mozilla/dom/SyncedContext.h" +#include "mozilla/dom/BrowsingContextGroup.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/ContentChild.h" +#include "nsReadableUtils.h" + +namespace mozilla { +namespace dom { +namespace syncedcontext { + +template <typename Context> +nsCString FormatValidationError(IndexSet aFailedFields, const char* prefix) { + MOZ_ASSERT(!aFailedFields.isEmpty()); + return nsDependentCString{prefix} + + StringJoin(", "_ns, aFailedFields, + [](nsACString& dest, const auto& idx) { + dest.Append(Context::FieldIndexToName(idx)); + }); +} + +template <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 (XRE_IsContentProcess()) { + ContentChild* cc = ContentChild::GetSingleton(); + + // Increment the field epoch for fields affected by this transaction. + uint64_t epoch = cc->NextBrowsingContextFieldEpoch(); + EachIndex([&](auto idx) { + if (mModified.contains(idx)) { + FieldEpoch(idx, aOwner) = epoch; + } + }); + + // Tell our derived class to send the correct "Commit" IPC message. + aOwner->SendCommitTransaction(cc, *this, epoch); + } else { + MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess()); + + // Tell our derived class to send the correct "Commit" IPC messages. + BrowsingContextGroup* group = aOwner->Group(); + group->EachParent([&](ContentParent* aParent) { + aOwner->SendCommitTransaction(aParent, *this, + aParent->GetBrowsingContextFieldEpoch()); + }); + } + + Apply(aOwner); + return NS_OK; +} + +template <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::GetLog(), LogLevel::Debug, + ("IPC: Trying to send a message to dead or detached context")); + return IPC_OK(); + } + Context* owner = aOwner.get(); + + // Validate that the set from content is allowed before continuing. + IndexSet failedFields = Validate(owner, aSource); + if (!failedFields.isEmpty()) { + nsCString error = FormatValidationError<Context>( + failedFields, + "Invalid Transaction from Child - CanSet failed for field(s): "); + return IPC_FAIL(aSource, error.get()); + } + + BrowsingContextGroup* group = owner->Group(); + group->EachOtherParent(aSource, [&](ContentParent* aParent) { + owner->SendCommitTransaction(aParent, *this, + aParent->GetBrowsingContextFieldEpoch()); + }); + + Apply(owner); + return IPC_OK(); +} + +template <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::GetLog(), LogLevel::Debug, + ("ChildIPC: Trying to send a message to dead or detached context")); + return IPC_OK(); + } + Context* owner = aOwner.get(); + + // Clear any fields which have been obsoleted by the epoch. + EachIndex([&](auto idx) { + if (mModified.contains(idx) && FieldEpoch(idx, owner) > aEpoch) { + mModified -= idx; + } + }); + + Apply(owner); + return IPC_OK(); +} + +template <typename Context> +void Transaction<Context>::Apply(Context* aOwner) { + EachIndex([&](auto idx) { + if (mModified.contains(idx)) { + auto& txnField = mValues.Get(idx); + auto& ownerField = aOwner->mFields.mValues.Get(idx); + std::swap(ownerField, txnField); + aOwner->DidSet(idx); + aOwner->DidSet(idx, std::move(txnField)); + } + }); + mModified.clear(); +} + +template <typename Context> +IndexSet Transaction<Context>::Validate(Context* aOwner, + ContentParent* aSource) { + IndexSet failedFields; + // Validate that the set from content is allowed before continuing. + EachIndex([&](auto idx) { + if (mModified.contains(idx) && + NS_WARN_IF(!aOwner->CanSet(idx, mValues.Get(idx), aSource))) { + failedFields += idx; + } + }); + return failedFields; +} + +template <typename Context> +void Transaction<Context>::Write(IPC::Message* aMsg, + mozilla::ipc::IProtocol* aActor) const { + // Record which field indices will be included, and then write those fields + // out. + uint64_t modified = mModified.serialize(); + WriteIPDLParam(aMsg, aActor, modified); + EachIndex([&](auto idx) { + if (mModified.contains(idx)) { + WriteIPDLParam(aMsg, aActor, mValues.Get(idx)); + } + }); +} + +template <typename Context> +bool Transaction<Context>::Read(const IPC::Message* aMsg, PickleIterator* aIter, + mozilla::ipc::IProtocol* aActor) { + // Read in which field indices were sent by the remote, followed by the fields + // identified by those indices. + uint64_t modified = 0; + if (!ReadIPDLParam(aMsg, aIter, aActor, &modified)) { + return false; + } + mModified.deserialize(modified); + + bool ok = true; + EachIndex([&](auto idx) { + if (ok && mModified.contains(idx)) { + ok = ReadIPDLParam(aMsg, aIter, aActor, &mValues.Get(idx)); + } + }); + return ok; +} + +template <typename Base, size_t Count> +void FieldValues<Base, Count>::Write(IPC::Message* aMsg, + mozilla::ipc::IProtocol* aActor) const { + // XXX The this-> qualification is necessary to work around a bug in older gcc + // versions causing an ICE. + EachIndex([&](auto idx) { WriteIPDLParam(aMsg, aActor, this->Get(idx)); }); +} + +template <typename Base, size_t Count> +bool FieldValues<Base, Count>::Read(const IPC::Message* aMsg, + PickleIterator* aIter, + mozilla::ipc::IProtocol* aActor) { + bool ok = true; + EachIndex([&](auto idx) { + if (ok) { + // XXX The this-> qualification is necessary to work around a bug in older + // gcc versions causing an ICE. + ok = ReadIPDLParam(aMsg, aIter, aActor, &this->Get(idx)); + } + }); + return ok; +} + +} // namespace syncedcontext +} // namespace dom +} // namespace mozilla + +#endif // !defined(mozilla_dom_SyncedContextInlines_h) diff --git a/docshell/base/URIFixup.jsm b/docshell/base/URIFixup.jsm new file mode 100644 index 0000000000..797b1ed981 --- /dev/null +++ b/docshell/base/URIFixup.jsm @@ -0,0 +1,1054 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- + * vim: sw=2 ts=2 sts=2 expandtab + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This component handles fixing up URIs, by correcting obvious typos and adding + * missing schemes. + * URI references: + * http://www.faqs.org/rfcs/rfc1738.html + * http://www.faqs.org/rfcs/rfc2396.html + */ + +// TODO (Bug 1641220) getFixupURIInfo has a complex logic, that likely could be +// simplified, but the risk of regressing its behavior is high. +/* eslint complexity: ["error", 43] */ + +var EXPORTED_SYMBOLS = ["URIFixup", "URIFixupInfo"]; + +const { ComponentUtils } = ChromeUtils.import( + "resource://gre/modules/ComponentUtils.jsm" +); +const { XPCOMUtils } = ChromeUtils.import( + "resource://gre/modules/XPCOMUtils.jsm" +); +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +const { AppConstants } = ChromeUtils.import( + "resource://gre/modules/AppConstants.jsm" +); + +XPCOMUtils.defineLazyServiceGetter( + this, + "externalProtocolService", + "@mozilla.org/uriloader/external-protocol-service;1", + "nsIExternalProtocolService" +); + +XPCOMUtils.defineLazyServiceGetter( + this, + "defaultProtocolHandler", + "@mozilla.org/network/protocol;1?name=default", + "nsIProtocolHandler" +); + +XPCOMUtils.defineLazyServiceGetter( + this, + "fileProtocolHandler", + "@mozilla.org/network/protocol;1?name=file", + "nsIFileProtocolHandler" +); + +XPCOMUtils.defineLazyPreferenceGetter( + this, + "fixupSchemeTypos", + "browser.fixup.typo.scheme", + true +); +XPCOMUtils.defineLazyPreferenceGetter( + this, + "dnsFirstForSingleWords", + "browser.fixup.dns_first_for_single_words", + false +); +XPCOMUtils.defineLazyPreferenceGetter( + this, + "keywordEnabled", + "keyword.enabled", + true +); +XPCOMUtils.defineLazyPreferenceGetter( + this, + "alternateEnabled", + "browser.fixup.alternate.enabled", + true +); +XPCOMUtils.defineLazyPreferenceGetter( + this, + "alternateProtocol", + "browser.fixup.alternate.protocol", + "https" +); + +const { + FIXUP_FLAG_NONE, + FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP, + FIXUP_FLAGS_MAKE_ALTERNATE_URI, + FIXUP_FLAG_PRIVATE_CONTEXT, + FIXUP_FLAG_FIX_SCHEME_TYPOS, +} = Ci.nsIURIFixup; + +const COMMON_PROTOCOLS = ["http", "https", "ftp", "file"]; + +// Regex used to identify user:password tokens in url strings. +// This is not a strict valid characters check, because we try to fixup this +// part of the url too. +XPCOMUtils.defineLazyGetter( + this, + "userPasswordRegex", + () => /^([a-z+.-]+:\/{0,3})*[^\/@]+@.+/i +); + +// Regex used to identify specific URI characteristics to disallow searching. +XPCOMUtils.defineLazyGetter( + this, + "uriLikeRegex", + () => /(:\d{1,5}([?#/]|$)|\/.*[?#])/ +); + +// Regex used to identify numbers. +XPCOMUtils.defineLazyGetter(this, "numberRegex", () => /^[0-9]+(\.[0-9]+)?$/); + +// Regex used to identify tab separated content (having at least 2 tabs). +XPCOMUtils.defineLazyGetter(this, "maxOneTabRegex", () => /^[^\t]*\t?[^\t]*$/); + +// Regex used to test if a string with a protocol might instead be a url +// without a protocol but with a port: +// +// <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. +XPCOMUtils.defineLazyGetter( + this, + "possiblyHostPortRegex", + () => /^[a-z0-9-]+(\.[a-z0-9-]+)*:[0-9]{1,5}([/?#]|$)/i +); + +// Regex used to strip newlines. +XPCOMUtils.defineLazyGetter(this, "newLinesRegex", () => /[\r\n]/g); + +// Regex used to match a possible protocol. +// This resembles the logic in Services.io.extractScheme, thus \t is admitted +// and stripped later. We don't use Services.io.extractScheme because of +// performance bottleneck caused by crossing XPConnect. +XPCOMUtils.defineLazyGetter( + this, + "possibleProtocolRegex", + () => /^([a-z][a-z0-9.+\t-]*)(:|;)?(\/\/)?/i +); + +// Regex used to match IPs. Note that these are not made to validate IPs, but +// just to detect strings that look like an IP. They also skip protocol. +// For IPv4 this also accepts a shorthand format with just 2 dots. +XPCOMUtils.defineLazyGetter( + this, + "IPv4LikeRegex", + () => /^(?:[a-z+.-]+:\/*(?!\/))?(?:\d{1,3}\.){2,3}\d{1,3}(?::\d+|\/)?/i +); +XPCOMUtils.defineLazyGetter( + this, + "IPv6LikeRegex", + () => + /^(?:[a-z+.-]+:\/*(?!\/))?\[(?:[0-9a-f]{0,4}:){0,7}[0-9a-f]{0,4}\]?(?::\d+|\/)?/i +); + +// Cache of known domains. +XPCOMUtils.defineLazyGetter(this, "knownDomains", () => { + const branch = "browser.fixup.domainwhitelist."; + let domains = new Set( + Services.prefs + .getChildList(branch) + .filter(p => Services.prefs.getBoolPref(p, false)) + .map(p => p.substring(branch.length)) + ); + // Hold onto the observer to avoid it being GC-ed. + domains._observer = { + observe(subject, topic, data) { + let domain = data.substring(branch.length); + if (Services.prefs.getBoolPref(data, false)) { + domains.add(domain); + } else { + domains.delete(domain); + } + }, + QueryInterface: ChromeUtils.generateQI([ + "nsIObserver", + "nsISupportsWeakReference", + ]), + }; + Services.prefs.addObserver(branch, domains._observer, true); + return domains; +}); + +// Cache of known suffixes. +// This works differently from the known domains, because when we examine a +// domain we can't tell how many dot-separated parts constitute the suffix. +// We create a Map keyed by the last dotted part, containing a Set of +// all the suffixes ending with that part: +// "two" => ["two"] +// "three" => ["some.three", "three"] +// When searching we can restrict the linear scan based on the last part. +// The ideal structure for this would be a Directed Acyclic Word Graph, but +// since we expect this list to be small it's not worth the complication. +XPCOMUtils.defineLazyGetter(this, "knownSuffixes", () => { + const branch = "browser.fixup.domainsuffixwhitelist."; + let suffixes = new Map(); + let prefs = Services.prefs + .getChildList(branch) + .filter(p => Services.prefs.getBoolPref(p, false)); + for (let pref of prefs) { + let suffix = pref.substring(branch.length); + let lastPart = suffix.substr(suffix.lastIndexOf(".") + 1); + if (lastPart) { + let entries = suffixes.get(lastPart); + if (!entries) { + entries = new Set(); + suffixes.set(lastPart, entries); + } + entries.add(suffix); + } + } + // Hold onto the observer to avoid it being GC-ed. + suffixes._observer = { + observe(subject, topic, data) { + let suffix = data.substring(branch.length); + let lastPart = suffix.substr(suffix.lastIndexOf(".") + 1); + let entries = suffixes.get(lastPart); + if (Services.prefs.getBoolPref(data, false)) { + // Add the suffix. + if (!entries) { + entries = new Set(); + suffixes.set(lastPart, entries); + } + entries.add(suffix); + } else if (entries) { + // Remove the suffix. + entries.delete(suffix); + if (!entries.size) { + suffixes.delete(lastPart); + } + } + }, + QueryInterface: ChromeUtils.generateQI([ + "nsIObserver", + "nsISupportsWeakReference", + ]), + }; + Services.prefs.addObserver(branch, suffixes._observer, true); + return suffixes; +}); + +function URIFixup() {} + +URIFixup.prototype = { + get FIXUP_FLAG_NONE() { + return FIXUP_FLAG_NONE; + }, + get FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP() { + return FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP; + }, + get FIXUP_FLAGS_MAKE_ALTERNATE_URI() { + return FIXUP_FLAGS_MAKE_ALTERNATE_URI; + }, + get FIXUP_FLAG_PRIVATE_CONTEXT() { + return FIXUP_FLAG_PRIVATE_CONTEXT; + }, + get FIXUP_FLAG_FIX_SCHEME_TYPOS() { + return FIXUP_FLAG_FIX_SCHEME_TYPOS; + }, + + getFixupURIInfo(uriString, fixupFlags = FIXUP_FLAG_NONE) { + let isPrivateContext = fixupFlags & FIXUP_FLAG_PRIVATE_CONTEXT; + + // Eliminate embedded newlines, which single-line text fields now allow, + // and cleanup the empty spaces and tabs that might be on each end. + uriString = uriString.trim().replace(newLinesRegex, ""); + + if (!uriString) { + throw new Components.Exception( + "Should pass a non-null uri", + Cr.NS_ERROR_FAILURE + ); + } + + let info = new URIFixupInfo(uriString); + + const { + scheme, + fixedSchemeUriString, + fixupChangedProtocol, + } = extractScheme(uriString, fixupFlags); + uriString = fixedSchemeUriString; + info.fixupChangedProtocol = fixupChangedProtocol; + + if (scheme == "view-source") { + let { preferredURI, postData } = fixupViewSource(uriString, fixupFlags); + info.preferredURI = info.fixedURI = preferredURI; + info.postData = postData; + return info; + } + + if (scheme.length < 2) { + // Check if it is a file path. We skip most schemes because the only case + // where a file path may look like having a scheme is "X:" on Windows. + let fileURI = fileURIFixup(uriString); + if (fileURI) { + info.preferredURI = info.fixedURI = fileURI; + info.fixupChangedProtocol = true; + return info; + } + } + + const isCommonProtocol = COMMON_PROTOCOLS.includes(scheme); + + let canHandleProtocol = + scheme && + (isCommonProtocol || + Services.io.getProtocolHandler(scheme) != defaultProtocolHandler || + externalProtocolService.externalProtocolHandlerExists(scheme)); + + if ( + canHandleProtocol || + // If it's an unknown handler and the given URL looks like host:port or + // has a user:password we can't pass it to the external protocol handler. + // We'll instead try fixing it with http later. + (!possiblyHostPortRegex.test(uriString) && + !userPasswordRegex.test(uriString)) + ) { + // Just try to create an URL out of it. + try { + info.fixedURI = Services.io.newURI(uriString); + } catch (ex) { + if (ex.result != Cr.NS_ERROR_MALFORMED_URI) { + throw ex; + } + } + } + + // We're dealing with a theoretically valid URI but we have no idea how to + // load it. (e.g. "christmas:humbug") + // It's more likely the user wants to search, and so we chuck this over to + // their preferred search provider. + // TODO (Bug 1588118): Should check FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP + // instead of FIXUP_FLAG_FIX_SCHEME_TYPOS. + if ( + info.fixedURI && + keywordEnabled && + fixupFlags & FIXUP_FLAG_FIX_SCHEME_TYPOS && + scheme && + !canHandleProtocol + ) { + tryKeywordFixupForURIInfo(uriString, info, isPrivateContext); + } + + if (info.fixedURI) { + if (!info.preferredURI) { + maybeSetAlternateFixedURI(info, fixupFlags); + info.preferredURI = info.fixedURI; + } + return info; + } + + // Fix up protocol string before calling KeywordURIFixup, because + // it cares about the hostname of such URIs. + // Prune duff protocol schemes: + // ://totallybroken.url.com + // //shorthand.url.com + let inputHadDuffProtocol = + uriString.startsWith("://") || uriString.startsWith("//"); + if (inputHadDuffProtocol) { + uriString = uriString.replace(/^:?\/\//, ""); + } + + // Avoid fixing up content that looks like tab-separated values. + // Assume that 1 tab is accidental, but more than 1 implies this is + // supposed to be tab-separated content. + if (!isCommonProtocol && maxOneTabRegex.test(uriString)) { + let uriWithProtocol = fixupURIProtocol(uriString); + if (uriWithProtocol) { + info.fixedURI = uriWithProtocol; + info.fixupChangedProtocol = true; + maybeSetAlternateFixedURI(info, fixupFlags); + info.preferredURI = info.fixedURI; + // Check if it's a forced visit. The user can enforce a visit by + // appending a slash, but the string must be in a valid uri format. + if (uriString.endsWith("/")) { + return info; + } + } + } + + // Handle "www.<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 ( + keywordEnabled && + fixupFlags & FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP && + !inputHadDuffProtocol && + !checkSuffix(info).suffix && + keywordURIFixup(uriString, info, isPrivateContext) + ) { + return info; + } + + if ( + info.fixedURI && + (!info.fixupChangedProtocol || !checkSuffix(info).hasUnknownSuffix) + ) { + return info; + } + + // If we still haven't been able to construct a valid URI, try to force a + // keyword match. + if (keywordEnabled && fixupFlags & FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP) { + tryKeywordFixupForURIInfo(info.originalInput, info, isPrivateContext); + } + + if (!info.preferredURI) { + // We couldn't salvage anything. + throw new Components.Exception( + "Couldn't build a valid uri", + Cr.NS_ERROR_MALFORMED_URI + ); + } + + return info; + }, + + webNavigationFlagsToFixupFlags(href, navigationFlags) { + try { + Services.io.newURI(href); + // Remove LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP for valid uris. + navigationFlags &= ~Ci.nsIWebNavigation + .LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP; + } catch (ex) {} + + let fixupFlags = FIXUP_FLAG_NONE; + if ( + navigationFlags & Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP + ) { + fixupFlags |= FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP; + } + if (navigationFlags & Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS) { + fixupFlags |= FIXUP_FLAG_FIX_SCHEME_TYPOS; + } + return fixupFlags; + }, + + keywordToURI(keyword, isPrivateContext) { + if (Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT) { + // There's no search service in the content process, thus all the calls + // from it that care about keywords conversion should go through the + // parent process. + throw new Components.Exception( + "Can't invoke URIFixup in the content process", + Cr.NS_ERROR_NOT_AVAILABLE + ); + } + let info = new URIFixupInfo(keyword); + + // Strip leading "?" and leading/trailing spaces from aKeyword + if (keyword.startsWith("?")) { + keyword = keyword.substring(1); + } + keyword = keyword.trim(); + + // Try falling back to the search service's default search engine + // We must use an appropriate search engine depending on the private + // context. + let engine = isPrivateContext + ? Services.search.defaultPrivateEngine + : Services.search.defaultEngine; + + // We allow default search plugins to specify alternate parameters that are + // specific to keyword searches. + let responseType = null; + if (engine.supportsResponseType("application/x-moz-keywordsearch")) { + responseType = "application/x-moz-keywordsearch"; + } + let submission = engine.getSubmission(keyword, responseType, "keyword"); + if ( + !submission || + // For security reasons (avoid redirecting to file, data, or other unsafe + // protocols) we only allow fixup to http/https search engines. + !submission.uri.scheme.startsWith("http") + ) { + throw new Components.Exception( + "Invalid search submission uri", + Cr.NS_ERROR_NOT_AVAILABLE + ); + } + let submissionPostDataStream = submission.postData; + if (submissionPostDataStream) { + info.postData = submissionPostDataStream; + } + + info.keywordProviderName = engine.name; + info.keywordAsSent = keyword; + info.preferredURI = submission.uri; + return info; + }, + + isDomainKnown, + + classID: Components.ID("{c6cf88b7-452e-47eb-bdc9-86e3561648ef}"), + _xpcom_factory: ComponentUtils.generateSingletonFactory(URIFixup), + QueryInterface: ChromeUtils.generateQI(["nsIURIFixup"]), +}; + +function URIFixupInfo(originalInput = "") { + this._originalInput = originalInput; +} + +URIFixupInfo.prototype = { + set consumer(consumer) { + this._consumer = consumer || null; + }, + get consumer() { + return this._consumer || null; + }, + + set preferredURI(uri) { + this._preferredURI = uri; + }, + get preferredURI() { + return this._preferredURI || null; + }, + + set fixedURI(uri) { + this._fixedURI = uri; + }, + get fixedURI() { + return this._fixedURI || null; + }, + + set keywordProviderName(name) { + this._keywordProviderName = name; + }, + get keywordProviderName() { + return this._keywordProviderName || ""; + }, + + set keywordAsSent(keyword) { + this._keywordAsSent = keyword; + }, + get keywordAsSent() { + return this._keywordAsSent || ""; + }, + + set fixupChangedProtocol(changed) { + this._fixupChangedProtocol = changed; + }, + get fixupChangedProtocol() { + return !!this._fixupChangedProtocol; + }, + + set fixupCreatedAlternateURI(changed) { + this._fixupCreatedAlternateURI = changed; + }, + get fixupCreatedAlternateURI() { + return !!this._fixupCreatedAlternateURI; + }, + + set originalInput(input) { + this._originalInput = input; + }, + get originalInput() { + return this._originalInput || ""; + }, + + set postData(postData) { + this._postData = postData; + }, + get postData() { + return this._postData || null; + }, + + classID: Components.ID("{33d75835-722f-42c0-89cc-44f328e56a86}"), + QueryInterface: ChromeUtils.generateQI(["nsIURIFixupInfo"]), +}; + +// Helpers + +/** + * Implementation of isDomainKnown, so we don't have to go through the + * service. + * @param {string} asciiHost + * @returns {boolean} whether the domain is known + */ +function isDomainKnown(asciiHost) { + if (dnsFirstForSingleWords) { + return true; + } + // Check if this domain is known as an actual + // domain (which will prevent a keyword query) + // Note that any processing of the host here should stay in sync with + // code in the front-end(s) that set the pref. + let lastDotIndex = asciiHost.lastIndexOf("."); + if (lastDotIndex == asciiHost.length - 1) { + asciiHost = asciiHost.substring(0, asciiHost.length - 1); + lastDotIndex = asciiHost.lastIndexOf("."); + } + if (knownDomains.has(asciiHost.toLowerCase())) { + return true; + } + // If there's no dot or only a leading dot we are done, otherwise we'll check + // against the known suffixes. + if (lastDotIndex <= 0) { + return false; + } + // Don't use getPublicSuffix here, since the suffix is not in the PSL, + // thus it couldn't tell if the suffix is made up of one or multiple + // dot-separated parts. + let lastPart = asciiHost.substr(lastDotIndex + 1); + let suffixes = knownSuffixes.get(lastPart); + if (suffixes) { + return Array.from(suffixes).some(s => asciiHost.endsWith(s)); + } + return false; +} + +/** + * Checks the suffix of info.fixedURI against the Public Suffix List. + * If the suffix is unknown due to a typo this will try to fix it up. + * @param {URIFixupInfo} info about the uri to check. + * @note this may modify the public suffix of info.fixedURI. + * @returns {object} result The lookup result. + * @returns {string} result.suffix The public suffix if one can be identified. + * @returns {boolean} result.hasUnknownSuffix True when the suffix is not in the + * Public Suffix List and it's not in knownSuffixes. False in the other cases. + */ +function checkAndFixPublicSuffix(info) { + let uri = info.fixedURI; + let asciiHost = uri?.asciiHost; + if ( + !asciiHost || + !asciiHost.includes(".") || + asciiHost.endsWith(".") || + isDomainKnown(asciiHost) + ) { + return { suffix: "", hasUnknownSuffix: false }; + } + + // Quick bailouts for most common cases, according to Alexa Top 1 million. + if ( + asciiHost.endsWith(".com") || + asciiHost.endsWith(".net") || + asciiHost.endsWith(".org") || + asciiHost.endsWith(".ru") || + asciiHost.endsWith(".de") + ) { + return { + suffix: asciiHost.substring(asciiHost.lastIndexOf(".") + 1), + hasUnknownSuffix: false, + }; + } + try { + let suffix = Services.eTLD.getKnownPublicSuffix(uri); + if (suffix) { + return { suffix, hasUnknownSuffix: false }; + } + } catch (ex) { + return { suffix: "", hasUnknownSuffix: false }; + } + // Suffix is unknown, try to fix most common 3 chars TLDs typos. + // .com is the most commonly mistyped tld, so it has more cases. + let suffix = Services.eTLD.getPublicSuffix(uri); + if (!suffix || numberRegex.test(suffix)) { + return { suffix: "", hasUnknownSuffix: false }; + } + for (let [typo, fixed] of [ + ["ocm", "com"], + ["con", "com"], + ["cmo", "com"], + ["xom", "com"], + ["vom", "com"], + ["cpm", "com"], + ["com'", "com"], + ["ent", "net"], + ["ner", "net"], + ["nte", "net"], + ["met", "net"], + ["rog", "org"], + ["ogr", "org"], + ["prg", "org"], + ["orh", "org"], + ]) { + if (suffix == typo) { + let host = uri.host.substring(0, uri.host.length - typo.length) + fixed; + let updatePreferredURI = info.preferredURI == info.fixedURI; + info.fixedURI = uri + .mutate() + .setHost(host) + .finalize(); + if (updatePreferredURI) { + info.preferredURI = info.fixedURI; + } + return { suffix: fixed, hasUnknownSuffix: false }; + } + } + return { suffix: "", hasUnknownSuffix: true }; +} + +function tryKeywordFixupForURIInfo(uriString, fixupInfo, isPrivateContext) { + try { + let keywordInfo = Services.uriFixup.keywordToURI( + uriString, + isPrivateContext + ); + fixupInfo.keywordProviderName = keywordInfo.keywordProviderName; + fixupInfo.keywordAsSent = keywordInfo.keywordAsSent; + fixupInfo.preferredURI = keywordInfo.preferredURI; + return true; + } catch (ex) {} + return false; +} + +/** + * This generates an alternate fixedURI, by adding a prefix and a suffix to + * the fixedURI host, if and only if the protocol is http. It should _never_ + * modify URIs with other protocols. + * @param {URIFixupInfo} info an URIInfo object + * @param {integer} fixupFlags the fixup flags + * @returns {boolean} Whether an alternate uri was generated + */ +function maybeSetAlternateFixedURI(info, fixupFlags) { + let uri = info.fixedURI; + if ( + !(fixupFlags & FIXUP_FLAGS_MAKE_ALTERNATE_URI) || + !alternateEnabled || + // Code only works for http. Not for any other protocol including https! + !uri.schemeIs("http") || + // Security - URLs with user / password info should NOT be fixed up + uri.userPass || + // Don't fix up hosts with ports + uri.port != -1 + ) { + return false; + } + + let oldHost = uri.host; + // Don't create an alternate uri for localhost, because it would be confusing. + if (oldHost == "localhost") { + return false; + } + + // Get the prefix and suffix to stick onto the new hostname. By default these + // are www. & .com but they could be any other value, e.g. www. & .org + let prefix = Services.prefs.getCharPref( + "browser.fixup.alternate.prefix", + "www." + ); + let suffix = Services.prefs.getCharPref( + "browser.fixup.alternate.suffix", + ".com" + ); + + let newHost = ""; + let numDots = (oldHost.match(/\./g) || []).length; + if (numDots == 0) { + newHost = prefix + oldHost + suffix; + } else if (numDots == 1) { + if (prefix && oldHost == prefix) { + newHost = oldHost + suffix; + } else if (suffix && !oldHost.startsWith(prefix)) { + newHost = prefix + oldHost; + } + } + if (!newHost) { + return false; + } + + // Assign the new host string over the old one + try { + info.fixedURI = uri + .mutate() + .setScheme(alternateProtocol) + .setHost(newHost) + .finalize(); + } catch (ex) { + if (ex.result != Cr.NS_ERROR_MALFORMED_URI) { + throw ex; + } + return false; + } + info.fixupCreatedAlternateURI = true; + return true; +} + +/** + * Try to fixup a file URI. + * @param {string} uriString The file URI to fix. + * @returns {nsIURI} a fixed uri or null. + * @note FileURIFixup only returns a URI if it has to add the file: protocol. + */ +function fileURIFixup(uriString) { + let attemptFixup = false; + if (AppConstants.platform == "win") { + // Check for "\"" in the url-string or just a drive (e.g. C:). + attemptFixup = + uriString.includes("\\") || + (uriString.length == 2 && uriString.endsWith(":")); + } else { + // UNIX: Check if it starts with "/". + attemptFixup = uriString.startsWith("/"); + } + if (attemptFixup) { + try { + // Test if this is a valid path by trying to create a local file + // object. The URL of that is returned if successful. + let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + file.initWithPath(uriString); + return Services.io.newURI( + fileProtocolHandler.getURLSpecFromActualFile(file) + ); + } catch (ex) { + // Not a file uri. + } + } + return null; +} + +/** + * Tries to fixup a string to an nsIURI by adding the default protocol. + * + * Should fix things like: + * no-scheme.com + * ftp.no-scheme.com + * ftp4.no-scheme.com + * no-scheme.com/query?foo=http://www.foo.com + * user:pass@no-scheme.com + * + * @param {string} uriString The string to fixup. + * @returns {nsIURI} an nsIURI built adding the default protocol to the string, + * or null if fixing was not possible. + */ +function fixupURIProtocol(uriString) { + let schemePos = uriString.indexOf("://"); + if (schemePos == -1 || schemePos > uriString.search(/[:\/]/)) { + uriString = "http://" + uriString; + } + try { + return Services.io.newURI(uriString); + } catch (ex) { + // We generated an invalid uri. + } + return null; +} + +/** + * Tries to fixup a string to a search url. + * @param {string} uriString the string to fixup. + * @param {URIFixupInfo} fixupInfo The fixup info object, modified in-place. + * @param {boolean} isPrivateContext Whether this happens in a private context. + * @param {nsIInputStream} postData optional POST data for the search + * @returns {boolean} Whether the keyword fixup was succesful. + */ +function keywordURIFixup(uriString, fixupInfo, isPrivateContext) { + // Here is a few examples of strings that should be searched: + // "what is mozilla" + // "what is mozilla?" + // "docshell site:mozilla.org" - has a space in the origin part + // "?site:mozilla.org - anything that begins with a question mark + // "mozilla'.org" - Things that have a quote before the first dot/colon + // "mozilla/test" - unknown host + // ".mozilla", "mozilla." - starts or ends with a dot () + + // These other strings should not be searched, because they could be URIs: + // "www.blah.com" - Domain with a standard or known suffix + // "knowndomain" - known domain + // "nonQualifiedHost:8888?something" - has a port + // "user@nonQualifiedHost" + // "blah.com." + + // We do keyword lookups if the input starts with a question mark. + if (uriString.startsWith("?")) { + return tryKeywordFixupForURIInfo( + fixupInfo.originalInput, + fixupInfo, + isPrivateContext + ); + } + + // Check for IPs. + if (IPv4LikeRegex.test(uriString) || IPv6LikeRegex.test(uriString)) { + return false; + } + + // Avoid lookup if we can identify a host and it's known, or ends with + // a dot and has some path. + // Note that if dnsFirstForSingleWords is true isDomainKnown will always + // return true, so we can avoid checking dnsFirstForSingleWords after this. + let asciiHost = fixupInfo.fixedURI?.asciiHost; + if ( + asciiHost && + (isDomainKnown(asciiHost) || + (asciiHost.endsWith(".") && + asciiHost.indexOf(".") != asciiHost.length - 1)) + ) { + return false; + } + + // Even if the host is invalid, avoid lookup if the string has uri-like + // characteristics. + // Also avoid lookup if there's a valid userPass. We only check for spaces, + // the URI parser has encoded any disallowed chars at this point, but if the + // user typed spaces before the first @, it's unlikely a valid userPass, plus + // some urlbar features use the @ char and we don't want to break them. + let userPass = fixupInfo.fixedURI?.userPass; + if ( + !uriLikeRegex.test(uriString) && + !(userPass && /^[^\s@]+@/.test(uriString)) + ) { + return tryKeywordFixupForURIInfo( + fixupInfo.originalInput, + fixupInfo, + isPrivateContext + ); + } + + return false; +} + +/** + * Mimics the logic in Services.io.extractScheme, but avoids crossing XPConnect. + * This also tries to fixup the scheme if it was clearly mistyped. + * @param {string} uriString the string to examine + * @param {integer} fixupFlags The original fixup flags + * @returns {object} + * scheme: a typo fixed scheme or empty string if one could not be identified + * fixedSchemeUriString: uri string with a typo fixed scheme + * fixupChangedProtocol: true if the scheme is fixed up + */ +function extractScheme(uriString, fixupFlags = FIXUP_FLAG_NONE) { + const matches = uriString.match(possibleProtocolRegex); + const hasColon = matches?.[2] === ":"; + const hasSlash2 = matches?.[3] === "//"; + + const isFixupSchemeTypos = + fixupSchemeTypos && fixupFlags & FIXUP_FLAG_FIX_SCHEME_TYPOS; + + if ( + !matches || + (!hasColon && !hasSlash2) || + (!hasColon && !isFixupSchemeTypos) + ) { + return { + scheme: "", + fixedSchemeUriString: uriString, + fixupChangedProtocol: false, + }; + } + + let scheme = matches[1].replace("\t", "").toLowerCase(); + let fixedSchemeUriString = uriString; + + if (isFixupSchemeTypos && hasSlash2) { + // Fix up typos for string that user would have intented as protocol. + const afterProtocol = uriString.substring(matches[0].length); + fixedSchemeUriString = `${scheme}://${afterProtocol}`; + } + + let fixupChangedProtocol = false; + + if (isFixupSchemeTypos) { + // Fix up common scheme typos. + // TODO: Use levenshtein distance here? + fixupChangedProtocol = [ + ["ttp", "http"], + ["htp", "http"], + ["ttps", "https"], + ["tps", "https"], + ["ps", "https"], + ["htps", "https"], + ["ile", "file"], + ["le", "file"], + ].some(([typo, fixed]) => { + if (scheme === typo) { + scheme = fixed; + fixedSchemeUriString = + scheme + fixedSchemeUriString.substring(typo.length); + return true; + } + return false; + }); + } + + return { + scheme, + fixedSchemeUriString, + fixupChangedProtocol, + }; +} + +/** + * View-source is a pseudo scheme. We're interested in fixing up the stuff + * after it. The easiest way to do that is to call this method again with + * the "view-source:" lopped off and then prepend it again afterwards. + * @param {string} uriString The original string to fixup + * @param {integer} fixupFlags The original fixup flags + * @param {nsIInputStream} postData Optional POST data for the search + * @returns {object} {preferredURI, postData} The fixed URI and relative postData + * @throws if it's not possible to fixup the url + */ +function fixupViewSource(uriString, fixupFlags) { + // We disable keyword lookup and alternate URIs so that small typos don't + // cause us to look at very different domains. + let newFixupFlags = + fixupFlags & + ~FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP & + ~FIXUP_FLAGS_MAKE_ALTERNATE_URI; + + let innerURIString = uriString.substring(12).trim(); + + // Prevent recursion. + const { scheme: innerScheme } = extractScheme(innerURIString); + if (innerScheme == "view-source") { + throw new Components.Exception( + "Prevent view-source recursion", + Cr.NS_ERROR_FAILURE + ); + } + + let info = Services.uriFixup.getFixupURIInfo(innerURIString, newFixupFlags); + if (!info.preferredURI) { + throw new Components.Exception( + "Couldn't build a valid uri", + Cr.NS_ERROR_MALFORMED_URI + ); + } + return { + preferredURI: Services.io.newURI("view-source:" + info.preferredURI.spec), + postData: info.postData, + }; +} diff --git a/docshell/base/WindowContext.cpp b/docshell/base/WindowContext.cpp new file mode 100644 index 0000000000..4d5b1e1781 --- /dev/null +++ b/docshell/base/WindowContext.cpp @@ -0,0 +1,569 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/WindowContext.h" +#include "mozilla/dom/WindowGlobalActorsBinding.h" +#include "mozilla/dom/WindowGlobalChild.h" +#include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/dom/SyncedContextInlines.h" +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/UserActivationIPCUtils.h" +#include "mozilla/PermissionDelegateIPCUtils.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/ClearOnShutdown.h" +#include "nsGlobalWindowInner.h" +#include "nsIScriptError.h" +#include "nsRefPtrHashtable.h" +#include "nsContentUtils.h" + +namespace mozilla { +namespace dom { + +// Explicit specialization of the `Transaction` type. Required by the `extern +// template class` declaration in the header. +template class syncedcontext::Transaction<WindowContext>; + +static LazyLogModule gWindowContextLog("WindowContext"); + +extern mozilla::LazyLogModule gUserInteractionPRLog; + +#define USER_ACTIVATION_LOG(msg, ...) \ + MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug, (msg, ##__VA_ARGS__)) + +using WindowContextByIdMap = nsDataHashtable<nsUint64HashKey, WindowContext*>; +static StaticAutoPtr<WindowContextByIdMap> gWindowContexts; + +/* static */ +LogModule* WindowContext::GetLog() { return gWindowContextLog; } + +/* 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::IsCached() const { + return mBrowsingContext->mCurrentWindowContext != this; +} + +nsGlobalWindowInner* WindowContext::GetInnerWindow() const { + if (mInProcess) { + // FIXME: Replace this with something more efficient. + return nsGlobalWindowInner::GetInnerWindowWithId(mInnerWindowId); + } + return nullptr; +} + +Document* WindowContext::GetDocument() const { + nsGlobalWindowInner* innerWindow = GetInnerWindow(); + return innerWindow ? innerWindow->GetDocument() : nullptr; +} + +Document* WindowContext::GetExtantDoc() const { + nsGlobalWindowInner* innerWindow = GetInnerWindow(); + return innerWindow ? innerWindow->GetExtantDoc() : nullptr; +} + +WindowGlobalChild* WindowContext::GetWindowGlobalChild() const { + MOZ_ASSERT(XRE_IsContentProcess()); + NS_ENSURE_TRUE(XRE_IsContentProcess(), nullptr); + nsGlobalWindowInner* innerWindow = GetInnerWindow(); + return innerWindow ? innerWindow->GetWindowGlobalChild() : nullptr; +} + +WindowContext* WindowContext::GetParentWindowContext() { + return mBrowsingContext->GetParentWindowContext(); +} + +WindowContext* WindowContext::TopWindowContext() { + WindowContext* current = this; + while (current->GetParentWindowContext()) { + current = current->GetParentWindowContext(); + } + return current; +} + +bool WindowContext::IsTop() const { return mBrowsingContext->IsTop(); } + +bool WindowContext::SameOriginWithTop() const { + return mBrowsingContext->SameOriginWithTop(); +} + +nsIGlobalObject* WindowContext::GetParentObject() const { + return xpc::NativeGlobal(xpc::PrivilegedJunkScope()); +} + +void WindowContext::AppendChildBrowsingContext( + BrowsingContext* aBrowsingContext) { + MOZ_DIAGNOSTIC_ASSERT(Group() == aBrowsingContext->Group(), + "Mismatched groups?"); + MOZ_DIAGNOSTIC_ASSERT(!mChildren.Contains(aBrowsingContext)); + + mChildren.AppendElement(aBrowsingContext); + + // If we're the current WindowContext in our BrowsingContext, make sure to + // clear any cached `children` value. + if (!IsCached()) { + BrowsingContext_Binding::ClearCachedChildrenValue(mBrowsingContext); + } +} + +void WindowContext::RemoveChildBrowsingContext( + BrowsingContext* aBrowsingContext) { + MOZ_DIAGNOSTIC_ASSERT(Group() == aBrowsingContext->Group(), + "Mismatched groups?"); + + mChildren.RemoveElement(aBrowsingContext); + + // If we're the current WindowContext in our BrowsingContext, make sure to + // clear any cached `children` value. + if (!IsCached()) { + BrowsingContext_Binding::ClearCachedChildrenValue(mBrowsingContext); + } +} + +void WindowContext::SendCommitTransaction(ContentParent* aParent, + const BaseTransaction& aTxn, + uint64_t aEpoch) { + Unused << aParent->SendCommitWindowContextTransaction(this, aTxn, aEpoch); +} + +void WindowContext::SendCommitTransaction(ContentChild* aChild, + const BaseTransaction& aTxn, + uint64_t aEpoch) { + aChild->SendCommitWindowContextTransaction(this, aTxn, aEpoch); +} + +bool WindowContext::CheckOnlyOwningProcessCanSet(ContentParent* aSource) { + if (mInProcess) { + return true; + } + + if (XRE_IsParentProcess() && aSource) { + return Canonical()->GetContentParent() == aSource; + } + + return false; +} + +bool WindowContext::CanSet(FieldIndex<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_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); +} + +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_UserActivationState>) { + MOZ_ASSERT_IF(!mInProcess, mUserGestureStart.IsNull()); + USER_ACTIVATION_LOG("Set user gesture activation %" PRIu8 + " for %s browsing context 0x%08" PRIx64, + static_cast<uint8_t>(GetUserActivationState()), + XRE_IsParentProcess() ? "Parent" : "Child", Id()); + if (mInProcess) { + USER_ACTIVATION_LOG( + "Set user gesture start time for %s browsing context 0x%08" PRIx64, + XRE_IsParentProcess() ? "Parent" : "Child", Id()); + mUserGestureStart = + (GetUserActivationState() == UserActivation::State::FullActivated) + ? TimeStamp::Now() + : TimeStamp(); + } +} + +void WindowContext::DidSet(FieldIndex<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); + } + } + } + } +} + +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, + /* aInProcess */ false, std::move(aInit.mFields)); + context->Init(); +} + +void WindowContext::Init() { + MOZ_LOG(GetLog(), LogLevel::Debug, + ("Registering 0x%" PRIx64 " (bc=0x%" PRIx64 ")", mInnerWindowId, + mBrowsingContext->Id())); + + // Register the WindowContext in the `WindowContextByIdMap`. + if (!gWindowContexts) { + gWindowContexts = new WindowContextByIdMap(); + ClearOnShutdown(&gWindowContexts); + } + auto& entry = gWindowContexts->GetOrInsert(mInnerWindowId); + MOZ_RELEASE_ASSERT(!entry, "Duplicate WindowContext for ID!"); + entry = this; + + // Register this to the browsing context. + mBrowsingContext->RegisterWindowContext(this); + Group()->Register(this); +} + +void WindowContext::Discard() { + MOZ_LOG(GetLog(), LogLevel::Debug, + ("Discarding 0x%" PRIx64 " (bc=0x%" PRIx64 ")", mInnerWindowId, + mBrowsingContext->Id())); + if (mIsDiscarded) { + return; + } + + mIsDiscarded = true; + if (gWindowContexts) { + gWindowContexts->Remove(InnerWindowId()); + } + mBrowsingContext->UnregisterWindowContext(this); + Group()->Unregister(this); +} + +void WindowContext::AddSecurityState(uint32_t aStateFlags) { + MOZ_ASSERT(TopWindowContext() == this); + MOZ_ASSERT((aStateFlags & + (nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT | + nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT | + nsIWebProgressListener::STATE_BLOCKED_MIXED_DISPLAY_CONTENT | + nsIWebProgressListener::STATE_BLOCKED_MIXED_ACTIVE_CONTENT | + nsIWebProgressListener::STATE_HTTPS_ONLY_MODE_UPGRADED | + nsIWebProgressListener::STATE_HTTPS_ONLY_MODE_UPGRADE_FAILED)) == + aStateFlags, + "Invalid flags specified!"); + + if (XRE_IsParentProcess()) { + Canonical()->AddSecurityState(aStateFlags); + } else { + ContentChild* child = ContentChild::GetSingleton(); + child->SendAddSecurityState(this, aStateFlags); + } +} + +void WindowContext::NotifyUserGestureActivation() { + Unused << SetUserActivationState(UserActivation::State::FullActivated); +} + +void WindowContext::NotifyResetUserGestureActivation() { + Unused << SetUserActivationState(UserActivation::State::None); +} + +bool WindowContext::HasBeenUserGestureActivated() { + return GetUserActivationState() != UserActivation::State::None; +} + +bool WindowContext::HasValidTransientUserGestureActivation() { + MOZ_ASSERT(mInProcess); + + if (GetUserActivationState() != UserActivation::State::FullActivated) { + MOZ_ASSERT(mUserGestureStart.IsNull(), + "mUserGestureStart should be null if the document hasn't ever " + "been activated by user gesture"); + return false; + } + + MOZ_ASSERT(!mUserGestureStart.IsNull(), + "mUserGestureStart shouldn't be null if the document has ever " + "been activated by user gesture"); + TimeDuration timeout = TimeDuration::FromMilliseconds( + StaticPrefs::dom_user_activation_transient_timeout()); + + return timeout <= TimeDuration() || + (TimeStamp::Now() - mUserGestureStart) <= timeout; +} + +bool WindowContext::ConsumeTransientUserGestureActivation() { + MOZ_ASSERT(mInProcess); + MOZ_ASSERT(!IsCached()); + + if (!HasValidTransientUserGestureActivation()) { + return false; + } + + BrowsingContext* top = mBrowsingContext->Top(); + top->PreOrderWalk([&](BrowsingContext* aBrowsingContext) { + WindowContext* windowContext = aBrowsingContext->GetCurrentWindowContext(); + if (windowContext && windowContext->GetUserActivationState() == + UserActivation::State::FullActivated) { + Unused << windowContext->SetUserActivationState( + UserActivation::State::HasBeenActivated); + } + }); + + return true; +} + +bool WindowContext::CanShowPopup() { + uint32_t permit = GetPopupPermission(); + if (permit == nsIPermissionManager::ALLOW_ACTION) { + return true; + } + if (permit == nsIPermissionManager::DENY_ACTION) { + return false; + } + + return !StaticPrefs::dom_disable_open_during_load(); +} + +WindowContext::IPCInitializer WindowContext::GetIPCInitializer() { + IPCInitializer init; + init.mInnerWindowId = mInnerWindowId; + init.mOuterWindowId = mOuterWindowId; + init.mBrowsingContextId = mBrowsingContext->Id(); + init.mFields = mFields.RawValues(); + return init; +} + +WindowContext::WindowContext(BrowsingContext* aBrowsingContext, + uint64_t aInnerWindowId, uint64_t aOuterWindowId, + bool aInProcess, FieldValues&& aInit) + : mFields(std::move(aInit)), + mInnerWindowId(aInnerWindowId), + mOuterWindowId(aOuterWindowId), + mBrowsingContext(aBrowsingContext), + mInProcess(aInProcess) { + MOZ_ASSERT(mBrowsingContext); + MOZ_ASSERT(mInnerWindowId); + MOZ_ASSERT(mOuterWindowId); +} + +WindowContext::~WindowContext() { + if (gWindowContexts) { + gWindowContexts->Remove(InnerWindowId()); + } +} + +JSObject* WindowContext::WrapObject(JSContext* cx, + JS::Handle<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_CLASS(WindowContext) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WindowContext) + if (gWindowContexts) { + gWindowContexts->Remove(tmp->InnerWindowId()); + } + + NS_IMPL_CYCLE_COLLECTION_UNLINK(mBrowsingContext) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildren) + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WindowContext) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowsingContext) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildren) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(WindowContext) + +} // namespace dom + +namespace ipc { + +void IPDLParamTraits<dom::MaybeDiscarded<dom::WindowContext>>::Write( + IPC::Message* aMsg, IProtocol* aActor, + const dom::MaybeDiscarded<dom::WindowContext>& aParam) { + uint64_t id = aParam.ContextId(); + WriteIPDLParam(aMsg, aActor, id); +} + +bool IPDLParamTraits<dom::MaybeDiscarded<dom::WindowContext>>::Read( + const IPC::Message* aMsg, PickleIterator* aIter, IProtocol* aActor, + dom::MaybeDiscarded<dom::WindowContext>* aResult) { + uint64_t id = 0; + if (!ReadIPDLParam(aMsg, aIter, 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::Message* aMessage, IProtocol* aActor, + const dom::WindowContext::IPCInitializer& aInit) { + // Write actor ID parameters. + WriteIPDLParam(aMessage, aActor, aInit.mInnerWindowId); + WriteIPDLParam(aMessage, aActor, aInit.mOuterWindowId); + WriteIPDLParam(aMessage, aActor, aInit.mBrowsingContextId); + WriteIPDLParam(aMessage, aActor, aInit.mFields); +} + +bool IPDLParamTraits<dom::WindowContext::IPCInitializer>::Read( + const IPC::Message* aMessage, PickleIterator* aIterator, IProtocol* aActor, + dom::WindowContext::IPCInitializer* aInit) { + // Read actor ID parameters. + return ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mInnerWindowId) && + ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mOuterWindowId) && + ReadIPDLParam(aMessage, aIterator, aActor, + &aInit->mBrowsingContextId) && + ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mFields); +} + +template struct IPDLParamTraits<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..f0ee15ee43 --- /dev/null +++ b/docshell/base/WindowContext.h @@ -0,0 +1,328 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_WindowContext_h +#define mozilla_dom_WindowContext_h + +#include "mozilla/PermissionDelegateHandler.h" +#include "mozilla/Span.h" +#include "mozilla/dom/MaybeDiscarded.h" +#include "mozilla/dom/SyncedContext.h" +#include "mozilla/dom/UserActivation.h" +#include "nsILoadInfo.h" +#include "nsWrapperCache.h" + +class nsIGlobalObject; + +class nsGlobalWindowInner; + +namespace mozilla { +class LogModule; + +namespace dom { + +class WindowGlobalChild; +class WindowGlobalParent; +class WindowGlobalInit; +class BrowsingContext; +class BrowsingContextGroup; + +#define MOZ_EACH_WC_FIELD(FIELD) \ + /* Whether the SHEntry associated with the current top-level \ + * window has already seen user interaction. \ + * As such, this will be reset to false when a new SHEntry is \ + * created without changing the WC (e.g. when using pushState or \ + * sub-frame navigation) \ + * This flag is set for optimization purposes, to avoid \ + * having to get the top SHEntry and update it on every \ + * user interaction. \ + * This is only meaningful on the top-level WC. */ \ + FIELD(SHEntryHasUserInteraction, bool) \ + FIELD(CookieBehavior, Maybe<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) \ + FIELD(IsSecureContext, bool) \ + FIELD(IsOriginalFrameSource, bool) \ + /* Mixed-Content: If the corresponding documentURI is https, \ + * then this flag is true. */ \ + FIELD(IsSecure, bool) \ + /* Whether the user has overriden the mixed content blocker to allow \ + * mixed content loads to happen */ \ + FIELD(AllowMixedContent, bool) \ + /* Whether this window has registered a "beforeunload" event \ + * handler */ \ + FIELD(HasBeforeUnload, bool) \ + /* Controls whether the WindowContext is currently considered to be \ + * activated by a gesture */ \ + FIELD(UserActivationState, UserActivation::State) \ + FIELD(EmbedderPolicy, nsILoadInfo::CrossOriginEmbedderPolicy) \ + /* True if this document tree contained at least a HTMLMediaElement. \ + * This should only be set on top level context. */ \ + FIELD(DocTreeHadMedia, bool) \ + FIELD(AutoplayPermission, uint32_t) \ + FIELD(ShortcutsPermission, uint32_t) \ + /* Store the Id of the browsing context where active media session \ + * exists on the top level window context */ \ + FIELD(ActiveMediaSessionContextId, Maybe<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) + +class WindowContext : public nsISupports, public nsWrapperCache { + MOZ_DECL_SYNCED_CONTEXT(WindowContext, MOZ_EACH_WC_FIELD) + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(WindowContext) + + public: + static already_AddRefed<WindowContext> GetById(uint64_t aInnerWindowId); + static LogModule* GetLog(); + + BrowsingContext* GetBrowsingContext() const { return mBrowsingContext; } + BrowsingContextGroup* Group() const; + uint64_t Id() const { return InnerWindowId(); } + uint64_t InnerWindowId() const { return mInnerWindowId; } + uint64_t OuterWindowId() const { return mOuterWindowId; } + bool IsDiscarded() const { return mIsDiscarded; } + + bool IsCached() const; + + bool IsInProcess() const { return mInProcess; } + + bool HasBeforeUnload() const { return GetHasBeforeUnload(); } + + bool IsLocalIP() const { return GetIsLocalIP(); } + + nsGlobalWindowInner* GetInnerWindow() const; + Document* GetDocument() const; + Document* GetExtantDoc() const; + + WindowGlobalChild* GetWindowGlobalChild() const; + + // Get the parent WindowContext of this WindowContext, taking the BFCache into + // account. This will not cross chrome/content <browser> boundaries. + WindowContext* GetParentWindowContext(); + WindowContext* TopWindowContext(); + + bool SameOriginWithTop() const; + + bool IsTop() const; + + Span<RefPtr<BrowsingContext>> Children() { return mChildren; } + + // 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); + + // This function would be called when its corresponding window is activated + // by user gesture. + void NotifyUserGestureActivation(); + + // This function would be called when we want to reset the user gesture + // activation flag. + void NotifyResetUserGestureActivation(); + + // Return true if its corresponding window has been activated by user + // gesture. + bool HasBeenUserGestureActivated(); + + // Return true if its corresponding window has transient user gesture + // activation and the transient user gesture activation haven't yet timed + // out. + bool HasValidTransientUserGestureActivation(); + + // Return true if the corresponding window has valid transient user gesture + // activation and the transient user gesture activation had been consumed + // successfully. + bool ConsumeTransientUserGestureActivation(); + + bool CanShowPopup(); + + protected: + WindowContext(BrowsingContext* aBrowsingContext, uint64_t aInnerWindowId, + uint64_t aOuterWindowId, bool aInProcess, + FieldValues&& aFields); + virtual ~WindowContext(); + + virtual void Init(); + + private: + friend class BrowsingContext; + friend class WindowGlobalChild; + friend class WindowGlobalActor; + + void AppendChildBrowsingContext(BrowsingContext* aBrowsingContext); + void RemoveChildBrowsingContext(BrowsingContext* aBrowsingContext); + + // Send a given `BaseTransaction` object to the correct remote. + void SendCommitTransaction(ContentParent* aParent, + const BaseTransaction& aTxn, uint64_t aEpoch); + void SendCommitTransaction(ContentChild* aChild, const BaseTransaction& aTxn, + uint64_t aEpoch); + + bool CheckOnlyOwningProcessCanSet(ContentParent* aSource); + + // Overload `CanSet` to get notifications for a particular field being set. + bool CanSet(FieldIndex<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_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_UserActivationState>, + const UserActivation::State& aUserActivationState, + 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); + + void DidSet(FieldIndex<IDX_HasReportedShadowDOMUsage>, bool aOldValue); + + void DidSet(FieldIndex<IDX_SHEntryHasUserInteraction>, bool aOldValue); + + // Overload `DidSet` to get notifications for a particular field being set. + // + // You can also overload the variant that gets the old value if you need it. + template <size_t I> + void DidSet(FieldIndex<I>) {} + template <size_t I, typename T> + void DidSet(FieldIndex<I>, T&& aOldValue) {} + void DidSet(FieldIndex<IDX_UserActivationState>); + + const uint64_t mInnerWindowId; + const uint64_t mOuterWindowId; + RefPtr<BrowsingContext> mBrowsingContext; + + // --- NEVER CHANGE `mChildren` DIRECTLY! --- + // Changes to this list need to be synchronized to the list within our + // `mBrowsingContext`, and should only be performed through the + // `AppendChildBrowsingContext` and `RemoveChildBrowsingContext` methods. + nsTArray<RefPtr<BrowsingContext>> mChildren; + + bool mIsDiscarded = false; + bool mInProcess = false; + + // The start time of user gesture, this is only available if the window + // context is in process. + TimeStamp mUserGestureStart; +}; + +using WindowContextTransaction = WindowContext::BaseTransaction; +using WindowContextInitializer = WindowContext::IPCInitializer; +using MaybeDiscardedWindowContext = MaybeDiscarded<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::Message* aMsg, IProtocol* aActor, + const dom::MaybeDiscarded<dom::WindowContext>& aParam); + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, + dom::MaybeDiscarded<dom::WindowContext>* aResult); +}; + +template <> +struct IPDLParamTraits<dom::WindowContext::IPCInitializer> { + static void Write(IPC::Message* aMessage, IProtocol* aActor, + const dom::WindowContext::IPCInitializer& aInitializer); + + static bool Read(const IPC::Message* aMessage, PickleIterator* aIterator, + IProtocol* aActor, + dom::WindowContext::IPCInitializer* aInitializer); +}; +} // namespace ipc +} // namespace mozilla + +#endif // !defined(mozilla_dom_WindowContext_h) diff --git a/docshell/base/crashtests/1257730-1.html b/docshell/base/crashtests/1257730-1.html new file mode 100644 index 0000000000..028a1adb88 --- /dev/null +++ b/docshell/base/crashtests/1257730-1.html @@ -0,0 +1,25 @@ +<!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/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..f30d78c10f --- /dev/null +++ b/docshell/base/crashtests/914521.html @@ -0,0 +1,32 @@ +<!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"; + document.documentElement.removeAttribute("class"); +} + +function start() +{ + var 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..3912fe5081 --- /dev/null +++ b/docshell/base/crashtests/crashtests.list @@ -0,0 +1,21 @@ +load 40929-1.html +load 369126-1.html +load 430124-1.html +load 430628-1.html +load 432114-1.html +load 432114-2.html +load 436900-1.html +asserts(0-1) load 436900-2.html # bug 566159 +load 443655.html +load 500328-1.html +load 514779-1.xhtml +load 614499-1.html +load 678872-1.html +skip-if(Android) pref(dom.disable_open_during_load,false) load 914521.html # Android bug 1584562 +pref(browser.send_pings,true) asserts(0-2) load 1257730-1.html # bug 566159 +load 1331295.html +load 1341657.html +load 1584467.html +load 1614211-1.html +load 1617315-1.html +skip-if(Android) pref(dom.disable_open_during_load,false) load 1667491.html diff --git a/docshell/base/crashtests/file_432114-2.xhtml b/docshell/base/crashtests/file_432114-2.xhtml new file mode 100644 index 0000000000..40bf886b8e --- /dev/null +++ b/docshell/base/crashtests/file_432114-2.xhtml @@ -0,0 +1 @@ +<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/moz.build b/docshell/base/moz.build new file mode 100644 index 0000000000..5b84c5ec9e --- /dev/null +++ b/docshell/base/moz.build @@ -0,0 +1,130 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +with Files("**"): + BUG_COMPONENT = ("Core", "DOM: Navigation") + +with Files("crashtests/430628*"): + BUG_COMPONENT = ("Core", "DOM: Editor") + +with Files("crashtests/432114*"): + BUG_COMPONENT = ("Core", "DOM: Editor") + +with Files("crashtests/500328*"): + BUG_COMPONENT = ("Firefox", "Bookmarks & History") + +with Files("IHistory.h"): + BUG_COMPONENT = ("Toolkit", "Places") + +with Files("*LoadContext.*"): + BUG_COMPONENT = ("Core", "Networking") + +with Files("nsAboutRedirector.*"): + BUG_COMPONENT = ("Core", "General") + +with Files("nsIScrollObserver.*"): + BUG_COMPONENT = ("Core", "Panning and Zooming") + +DIRS += [ + "timeline", +] + +XPIDL_SOURCES += [ + "nsIContentViewer.idl", + "nsIContentViewerEdit.idl", + "nsIDocShell.idl", + "nsIDocShellTreeItem.idl", + "nsIDocShellTreeOwner.idl", + "nsIDocumentLoaderFactory.idl", + "nsILoadContext.idl", + "nsILoadURIDelegate.idl", + "nsIPrivacyTransitionObserver.idl", + "nsIReflowObserver.idl", + "nsIRefreshURI.idl", + "nsITooltipListener.idl", + "nsITooltipTextProvider.idl", + "nsIURIFixup.idl", + "nsIWebNavigation.idl", + "nsIWebNavigationInfo.idl", + "nsIWebPageDescriptor.idl", +] + +XPIDL_MODULE = "docshell" + +EXPORTS += [ + "nsCTooltipTextProvider.h", + "nsDocShell.h", + "nsDocShellLoadState.h", + "nsDocShellLoadTypes.h", + "nsDocShellTreeOwner.h", + "nsIScrollObserver.h", + "nsWebNavigationInfo.h", + "SerializedLoadContext.h", +] + +EXPORTS.mozilla += [ + "BaseHistory.h", + "IHistory.h", + "LoadContext.h", +] + +EXPORTS.mozilla.dom += [ + "BrowsingContext.h", + "BrowsingContextGroup.h", + "BrowsingContextWebProgress.h", + "CanonicalBrowsingContext.h", + "ChildProcessChannelListener.h", + "SyncedContext.h", + "SyncedContextInlines.h", + "WindowContext.h", +] + +UNIFIED_SOURCES += [ + "BaseHistory.cpp", + "BrowsingContext.cpp", + "BrowsingContextGroup.cpp", + "BrowsingContextWebProgress.cpp", + "CanonicalBrowsingContext.cpp", + "ChildProcessChannelListener.cpp", + "LoadContext.cpp", + "nsAboutRedirector.cpp", + "nsDocShell.cpp", + "nsDocShellEditorData.cpp", + "nsDocShellEnumerator.cpp", + "nsDocShellLoadState.cpp", + "nsDocShellTelemetryUtils.cpp", + "nsDocShellTreeOwner.cpp", + "nsDSURIContentListener.cpp", + "nsPingListener.cpp", + "nsRefreshTimer.cpp", + "nsWebNavigationInfo.cpp", + "SerializedLoadContext.cpp", + "WindowContext.cpp", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" +LOCAL_INCLUDES += [ + "/docshell/shistory", + "/dom/base", + "/dom/bindings", + "/js/xpconnect/src", + "/layout/base", + "/layout/generic", + "/layout/style", + "/layout/xul", + "/netwerk/base", + "/netwerk/protocol/viewsource", + "/toolkit/components/browser", + "/toolkit/components/find", + "/tools/profiler", +] + +if CONFIG["CC_TYPE"] in ("clang", "gcc"): + CXXFLAGS += ["-Wno-error=shadow"] + +EXTRA_JS_MODULES += ["URIFixup.jsm"] diff --git a/docshell/base/nsAboutRedirector.cpp b/docshell/base/nsAboutRedirector.cpp new file mode 100644 index 0000000000..10ac77b519 --- /dev/null +++ b/docshell/base/nsAboutRedirector.cpp @@ -0,0 +1,266 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsAboutRedirector.h" +#include "nsNetUtil.h" +#include "nsAboutProtocolUtils.h" +#include "nsBaseChannel.h" +#include "mozilla/ArrayUtils.h" +#include "nsIProtocolHandler.h" +#include "nsXULAppAPI.h" +#include "mozilla/Preferences.h" + +#define ABOUT_CONFIG_ENABLED_PREF "general.aboutConfig.enable" + +NS_IMPL_ISUPPORTS(nsAboutRedirector, nsIAboutModule) + +struct RedirEntry { + const char* id; + const char* url; + uint32_t flags; +}; + +class CrashChannel final : public nsBaseChannel { + public: + explicit CrashChannel(nsIURI* aURI) { SetURI(aURI); } + + nsresult OpenContentStream(bool async, nsIInputStream** stream, + nsIChannel** channel) override { + nsAutoCString spec; + mURI->GetSpec(spec); + + if (spec.EqualsASCII("about:crashparent") && XRE_IsParentProcess()) { + MOZ_CRASH("Crash via about:crashparent"); + } + + if (spec.EqualsASCII("about:crashcontent") && XRE_IsContentProcess()) { + MOZ_CRASH("Crash via about:crashcontent"); + } + + NS_WARNING("Unhandled about:crash* URI or wrong process"); + return NS_ERROR_NOT_IMPLEMENTED; + } + + protected: + virtual ~CrashChannel() = default; +}; + +/* + Entries which do not have URI_SAFE_FOR_UNTRUSTED_CONTENT will run with chrome + privileges. This is potentially dangerous. Please use + URI_SAFE_FOR_UNTRUSTED_CONTENT in the third argument to each map item below + unless your about: page really needs chrome privileges. Security review is + required before adding new map entries without + URI_SAFE_FOR_UNTRUSTED_CONTENT. + + URI_SAFE_FOR_UNTRUSTED_CONTENT is not enough to let web pages load that page, + for that you need MAKE_LINKABLE. + */ +static const RedirEntry kRedirMap[] = { + {"about", "chrome://global/content/aboutAbout.html", 0}, + {"addons", "chrome://mozapps/content/extensions/extensions.xhtml", + nsIAboutModule::ALLOW_SCRIPT}, + {"buildconfig", "chrome://global/content/buildconfig.html", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT}, + {"checkerboard", "chrome://global/content/aboutCheckerboard.html", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::ALLOW_SCRIPT}, +#ifndef MOZ_BUILD_APP_IS_BROWSER + {"config", "chrome://global/content/config.xhtml", 0}, +#endif +#ifdef MOZ_CRASHREPORTER + {"crashes", "chrome://global/content/crashes.html", 0}, +#endif + {"credits", "https://www.mozilla.org/credits/", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::URI_MUST_LOAD_IN_CHILD}, + {"httpsonlyerror", "chrome://global/content/httpsonlyerror/errorpage.html", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::URI_CAN_LOAD_IN_CHILD | nsIAboutModule::ALLOW_SCRIPT | + nsIAboutModule::HIDE_FROM_ABOUTABOUT}, + {"license", "chrome://global/content/license.html", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT}, + {"logo", "chrome://branding/content/about.png", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + // Linkable for testing reasons. + nsIAboutModule::MAKE_LINKABLE}, + {"memory", "chrome://global/content/aboutMemory.xhtml", + nsIAboutModule::ALLOW_SCRIPT}, + {"certificate", "chrome://global/content/certviewer/certviewer.html", + nsIAboutModule::ALLOW_SCRIPT | + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::URI_MUST_LOAD_IN_CHILD | + nsIAboutModule::URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS}, + {"mozilla", "chrome://global/content/mozilla.html", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT}, + {"neterror", "chrome://global/content/netError.xhtml", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::URI_CAN_LOAD_IN_CHILD | nsIAboutModule::ALLOW_SCRIPT | + nsIAboutModule::HIDE_FROM_ABOUTABOUT}, + {"networking", "chrome://global/content/aboutNetworking.html", + nsIAboutModule::ALLOW_SCRIPT}, + {"performance", "chrome://global/content/aboutPerformance.html", + nsIAboutModule::ALLOW_SCRIPT}, + {"plugins", "chrome://global/content/plugins.html", + nsIAboutModule::URI_MUST_LOAD_IN_CHILD}, + {"processes", "chrome://global/content/aboutProcesses.html", + nsIAboutModule::ALLOW_SCRIPT}, + // about:serviceworkers always wants to load in the parent process because + // when dom.serviceWorkers.parent_intercept is set to true (the new default) + // then the only place nsIServiceWorkerManager has any data is in the + // parent process. + // + // There is overlap without about:debugging, but about:debugging is not + // available on mobile at this time, and it's useful to be able to know if + // a ServiceWorker is registered directly from the mobile browser without + // having to connect the device to a desktop machine and all that entails. + {"serviceworkers", "chrome://global/content/aboutServiceWorkers.xhtml", + nsIAboutModule::ALLOW_SCRIPT}, +#ifndef ANDROID + {"profiles", "chrome://global/content/aboutProfiles.xhtml", + nsIAboutModule::ALLOW_SCRIPT}, +#endif + // about:srcdoc is unresolvable by specification. It is included here + // because the security manager would disallow srcdoc iframes otherwise. + {"srcdoc", "about:blank", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::HIDE_FROM_ABOUTABOUT | + // Needs to be linkable so content can touch its own srcdoc frames + nsIAboutModule::MAKE_LINKABLE | nsIAboutModule::URI_CAN_LOAD_IN_CHILD}, + {"support", "chrome://global/content/aboutSupport.xhtml", + nsIAboutModule::ALLOW_SCRIPT}, +#ifdef MOZ_GLEAN + {"glean", "chrome://global/content/aboutGlean.html", + nsIAboutModule::HIDE_FROM_ABOUTABOUT | nsIAboutModule::ALLOW_SCRIPT}, +#endif + {"telemetry", "chrome://global/content/aboutTelemetry.xhtml", + nsIAboutModule::ALLOW_SCRIPT}, + {"url-classifier", "chrome://global/content/aboutUrlClassifier.xhtml", + nsIAboutModule::ALLOW_SCRIPT}, + {"webrtc", "chrome://global/content/aboutwebrtc/aboutWebrtc.html", + nsIAboutModule::ALLOW_SCRIPT}, + {"printpreview", "about:blank", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::HIDE_FROM_ABOUTABOUT | + nsIAboutModule::URI_CAN_LOAD_IN_CHILD}, + {"crashparent", "about:blank", nsIAboutModule::HIDE_FROM_ABOUTABOUT}, + {"crashcontent", "about:blank", + nsIAboutModule::HIDE_FROM_ABOUTABOUT | + nsIAboutModule::URI_CAN_LOAD_IN_CHILD | + nsIAboutModule::URI_MUST_LOAD_IN_CHILD}}; +static const int kRedirTotal = mozilla::ArrayLength(kRedirMap); + +NS_IMETHODIMP +nsAboutRedirector::NewChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo, + nsIChannel** aResult) { + NS_ENSURE_ARG_POINTER(aURI); + NS_ENSURE_ARG_POINTER(aLoadInfo); + NS_ASSERTION(aResult, "must not be null"); + + nsAutoCString path; + nsresult rv = NS_GetAboutModuleName(aURI, path); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv); + NS_ENSURE_SUCCESS(rv, rv); + + if (path.EqualsASCII("crashparent") || path.EqualsASCII("crashcontent")) { + bool isExternal; + aLoadInfo->GetLoadTriggeredFromExternal(&isExternal); + if (isExternal) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsCOMPtr<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(nsISupports* aOuter, 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..08448787e9 --- /dev/null +++ b/docshell/base/nsAboutRedirector.h @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsAboutRedirector_h__ +#define nsAboutRedirector_h__ + +#include "nsIAboutModule.h" + +class nsAboutRedirector : public nsIAboutModule { + public: + NS_DECL_ISUPPORTS + + NS_DECL_NSIABOUTMODULE + + nsAboutRedirector() {} + + static nsresult Create(nsISupports* aOuter, REFNSIID aIID, void** aResult); + + protected: + virtual ~nsAboutRedirector() {} +}; + +#endif // nsAboutRedirector_h__ diff --git a/docshell/base/nsCTooltipTextProvider.h b/docshell/base/nsCTooltipTextProvider.h new file mode 100644 index 0000000000..731edf1170 --- /dev/null +++ b/docshell/base/nsCTooltipTextProvider.h @@ -0,0 +1,15 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NSCTOOLTIPTEXTPROVIDER_H +#define NSCTOOLTIPTEXTPROVIDER_H + +#define NS_TOOLTIPTEXTPROVIDER_CONTRACTID \ + "@mozilla.org/embedcomp/tooltiptextprovider;1" +#define NS_DEFAULTTOOLTIPTEXTPROVIDER_CONTRACTID \ + "@mozilla.org/embedcomp/default-tooltiptextprovider;1" + +#endif diff --git a/docshell/base/nsDSURIContentListener.cpp b/docshell/base/nsDSURIContentListener.cpp new file mode 100644 index 0000000000..d2f339c9ec --- /dev/null +++ b/docshell/base/nsDSURIContentListener.cpp @@ -0,0 +1,307 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsDocShell.h" +#include "nsDSURIContentListener.h" +#include "nsIChannel.h" +#include "nsServiceManagerUtils.h" +#include "nsDocShellCID.h" +#include "nsIWebNavigationInfo.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/Unused.h" +#include "nsError.h" +#include "nsContentSecurityManager.h" +#include "nsDocShellLoadTypes.h" +#include "nsGlobalWindowOuter.h" +#include "nsIInterfaceRequestor.h" +#include "nsIMultiPartChannel.h" +#include "nsWebNavigationInfo.h" + +using namespace mozilla; +using namespace mozilla::dom; + +NS_IMPL_ADDREF(MaybeCloseWindowHelper) +NS_IMPL_RELEASE(MaybeCloseWindowHelper) + +NS_INTERFACE_MAP_BEGIN(MaybeCloseWindowHelper) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +MaybeCloseWindowHelper::MaybeCloseWindowHelper(BrowsingContext* aContentContext) + : mBrowsingContext(aContentContext), + mTimer(nullptr), + mShouldCloseWindow(false) {} + +MaybeCloseWindowHelper::~MaybeCloseWindowHelper() {} + +void MaybeCloseWindowHelper::SetShouldCloseWindow(bool aShouldCloseWindow) { + mShouldCloseWindow = aShouldCloseWindow; +} + +BrowsingContext* MaybeCloseWindowHelper::MaybeCloseWindow() { + if (!mShouldCloseWindow) { + return mBrowsingContext; + } + + // This method should not be called more than once, but it's better to avoid + // closing the current window again. + mShouldCloseWindow = false; + + // Reset the window context to the opener window so that the dependent + // dialogs have a parent + RefPtr<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; +} + +nsDSURIContentListener::nsDSURIContentListener(nsDocShell* aDocShell) + : mDocShell(aDocShell), + mExistingJPEGRequest(nullptr), + mParentContentListener(nullptr) {} + +nsDSURIContentListener::~nsDSURIContentListener() {} + +NS_IMPL_ADDREF(nsDSURIContentListener) +NS_IMPL_RELEASE(nsDSURIContentListener) + +NS_INTERFACE_MAP_BEGIN(nsDSURIContentListener) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIURIContentListener) + NS_INTERFACE_MAP_ENTRY(nsIURIContentListener) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) +NS_INTERFACE_MAP_END + +NS_IMETHODIMP +nsDSURIContentListener::DoContent(const nsACString& aContentType, + bool aIsContentPreferred, + nsIRequest* aRequest, + nsIStreamListener** aContentHandler, + bool* aAbortProcess) { + nsresult rv; + NS_ENSURE_ARG_POINTER(aContentHandler); + NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE); + + *aAbortProcess = false; + + // determine if the channel has just been retargeted to us... + nsLoadFlags loadFlags = 0; + nsCOMPtr<nsIChannel> aOpenedChannel = do_QueryInterface(aRequest); + + if (aOpenedChannel) { + aOpenedChannel->GetLoadFlags(&loadFlags); + + // block top-level data URI navigations if triggered by the web + if (!nsContentSecurityManager::AllowTopLevelNavigationToDataURI( + aOpenedChannel)) { + // logging to console happens within AllowTopLevelNavigationToDataURI + aRequest->Cancel(NS_ERROR_DOM_BAD_URI); + *aAbortProcess = true; + // close the window since the navigation to a data URI was blocked + if (mDocShell && mDocShell->GetBrowsingContext()) { + RefPtr<MaybeCloseWindowHelper> maybeCloseWindowHelper = + new MaybeCloseWindowHelper(mDocShell->GetBrowsingContext()); + maybeCloseWindowHelper->SetShouldCloseWindow(true); + Unused << maybeCloseWindowHelper->MaybeCloseWindow(); + } + return NS_OK; + } + } + + if (loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI) { + // XXX: Why does this not stop the content too? + mDocShell->Stop(nsIWebNavigation::STOP_NETWORK); + + mDocShell->SetLoadType(aIsContentPreferred ? LOAD_LINK : LOAD_NORMAL); + } + + // In case of multipart jpeg request (mjpeg) we don't really want to + // create new viewer since the one we already have is capable of + // rendering multipart jpeg correctly (see bug 625012) + nsCOMPtr<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 = + mDocShell->CreateContentViewer(aContentType, aRequest, aContentHandler); + if (NS_SUCCEEDED(rv) && reuseCV) { + mExistingJPEGStreamListener = *aContentHandler; + } else { + mExistingJPEGStreamListener = nullptr; + } + mExistingJPEGRequest = baseChannel; + } + + if (rv == NS_ERROR_REMOTE_XUL || rv == NS_ERROR_DOCSHELL_DYING) { + aRequest->Cancel(rv); + *aAbortProcess = true; + return NS_OK; + } + + if (NS_FAILED(rv)) { + // we don't know how to handle the content + nsCOMPtr<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), mDocShell); + *aCanHandleContent = (canHandle != nsIWebNavigationInfo::UNSUPPORTED); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDSURIContentListener::GetLoadCookie(nsISupports** aLoadCookie) { + NS_IF_ADDREF(*aLoadCookie = nsDocShell::GetAsSupports(mDocShell)); + return NS_OK; +} + +NS_IMETHODIMP +nsDSURIContentListener::SetLoadCookie(nsISupports* aLoadCookie) { +#ifdef DEBUG + RefPtr<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..a9e7da728f --- /dev/null +++ b/docshell/base/nsDSURIContentListener.h @@ -0,0 +1,99 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsDSURIContentListener_h__ +#define nsDSURIContentListener_h__ + +#include "nsCOMPtr.h" +#include "nsIURIContentListener.h" +#include "nsWeakReference.h" +#include "nsITimer.h" + +class nsDocShell; +class nsIInterfaceRequestor; +class nsIWebNavigationInfo; +class nsPIDOMWindowOuter; + +// Helper Class to eventually close an already opened window +class MaybeCloseWindowHelper final : public nsITimerCallback { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSITIMERCALLBACK + + explicit MaybeCloseWindowHelper( + mozilla::dom::BrowsingContext* aContentContext); + + /** + * Closes the provided window async (if mShouldCloseWindow is true) and + * returns a valid browsingContext to be used instead as parent for dialogs or + * similar things. + * In case mShouldCloseWindow is true, the returned BrowsingContext will be + * the window's opener (or original cross-group opener in the case of a + * `noopener` popup). + */ + mozilla::dom::BrowsingContext* MaybeCloseWindow(); + + void SetShouldCloseWindow(bool aShouldCloseWindow); + + protected: + ~MaybeCloseWindowHelper(); + + private: + already_AddRefed<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..207a757eef --- /dev/null +++ b/docshell/base/nsDocShell.cpp @@ -0,0 +1,13439 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsDocShell.h" + +#include <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/Components.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Encoding.h" +#include "mozilla/EventStateManager.h" +#include "mozilla/HTMLEditor.h" +#include "mozilla/InputTaskManager.h" +#include "mozilla/LoadInfo.h" +#include "mozilla/Logging.h" +#include "mozilla/MediaFeatureChange.h" +#include "mozilla/ObservedDocShell.h" +#include "mozilla/Preferences.h" +#include "mozilla/PresShell.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/ScrollTypes.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPrefs_browser.h" +#include "mozilla/StaticPrefs_docshell.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/StaticPrefs_extensions.h" +#include "mozilla/StaticPrefs_privacy.h" +#include "mozilla/StaticPrefs_security.h" +#include "mozilla/StaticPrefs_ui.h" +#include "mozilla/StaticPrefs_fission.h" +#include "mozilla/StartupTimeline.h" +#include "mozilla/StorageAccess.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Tuple.h" +#include "mozilla/Unused.h" +#include "mozilla/WidgetUtils.h" + +#include "mozilla/dom/ChildProcessChannelListener.h" +#include "mozilla/dom/ClientChannelHelper.h" +#include "mozilla/dom/ClientHandle.h" +#include "mozilla/dom/ClientInfo.h" +#include "mozilla/dom/ClientManager.h" +#include "mozilla/dom/ClientSource.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentFrameMessageManager.h" +#include "mozilla/dom/DocGroup.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/HTMLAnchorElement.h" +#include "mozilla/dom/HTMLIFrameElement.h" +#include "mozilla/dom/PerformanceNavigation.h" +#include "mozilla/dom/PermissionMessageUtils.h" +#include "mozilla/dom/PopupBlocker.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" +#include "mozilla/dom/ScreenOrientation.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/ServiceWorkerInterceptController.h" +#include "mozilla/dom/ServiceWorkerUtils.h" +#include "mozilla/dom/SessionHistoryEntry.h" +#include "mozilla/dom/SessionStorageManager.h" +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/dom/ToJSValue.h" +#include "mozilla/dom/UserActivation.h" +#include "mozilla/dom/ChildSHistory.h" +#include "mozilla/dom/nsCSPContext.h" +#include "mozilla/dom/nsHTTPSOnlyUtils.h" +#include "mozilla/dom/LoadURIOptionsBinding.h" +#include "mozilla/dom/JSWindowActorChild.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/net/DocumentChannel.h" +#include "mozilla/net/ParentChannelWrapper.h" +#include "mozilla/net/UrlClassifierFeatureFactory.h" +#include "ReferrerInfo.h" + +#include "nsIApplicationCacheChannel.h" +#include "nsIApplicationCacheContainer.h" +#include "nsIAppShell.h" +#include "nsIAuthPrompt.h" +#include "nsIAuthPrompt2.h" +#include "nsICachingChannel.h" +#include "nsICaptivePortalService.h" +#include "nsIChannel.h" +#include "nsIChannelEventSink.h" +#include "nsIClassOfService.h" +#include "nsIConsoleReportCollector.h" +#include "nsIContent.h" +#include "nsIContentInlines.h" +#include "nsIContentSecurityPolicy.h" +#include "nsIContentViewer.h" +#include "nsIController.h" +#include "nsIDocShellTreeItem.h" +#include "nsIDocShellTreeOwner.h" +#include "mozilla/dom/Document.h" +#include "nsIDocumentLoaderFactory.h" +#include "nsIDOMWindow.h" +#include "nsIEditingSession.h" +#include "nsIEffectiveTLDService.h" +#include "nsIExternalProtocolService.h" +#include "nsIFormPOSTActionChannel.h" +#include "nsIFrame.h" +#include "nsIGlobalObject.h" +#include "nsIHttpChannel.h" +#include "nsIHttpChannelInternal.h" +#include "nsIIDNService.h" +#include "nsIInputStreamChannel.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsILayoutHistoryState.h" +#include "nsILoadInfo.h" +#include "nsILoadURIDelegate.h" +#include "nsIMultiPartChannel.h" +#include "nsINestedURI.h" +#include "nsINetworkPredictor.h" +#include "nsINode.h" +#include "nsINSSErrorsService.h" +#include "nsIObserverService.h" +#include "nsIOService.h" +#include "nsIPrincipal.h" +#include "nsIPrivacyTransitionObserver.h" +#include "nsIPrompt.h" +#include "nsIPromptCollection.h" +#include "nsIPromptFactory.h" +#include "nsIReflowObserver.h" +#include "nsIScriptChannel.h" +#include "nsIScriptObjectPrincipal.h" +#include "nsIScriptSecurityManager.h" +#include "nsIScrollableFrame.h" +#include "nsIScrollObserver.h" +#include "nsISecureBrowserUI.h" +#include "nsISeekableStream.h" +#include "nsISelectionDisplay.h" +#include "nsISHEntry.h" +#include "nsISiteSecurityService.h" +#include "nsISocketProvider.h" +#include "nsIStringBundle.h" +#include "nsIStructuredCloneContainer.h" +#include "nsIBrowserChild.h" +#include "nsITextToSubURI.h" +#include "nsITimedChannel.h" +#include "nsITimer.h" +#include "nsITransportSecurityInfo.h" +#include "nsIUploadChannel.h" +#include "nsIURIFixup.h" +#include "nsIURIMutator.h" +#include "nsIURILoader.h" +#include "nsIViewSourceChannel.h" +#include "nsIWebBrowserChrome.h" +#include "nsIWebBrowserChrome3.h" +#include "nsIWebBrowserChromeFocus.h" +#include "nsIWebBrowserFind.h" +#include "nsIWebProgress.h" +#include "nsIWidget.h" +#include "nsIWindowWatcher.h" +#include "nsIWritablePropertyBag2.h" +#include "nsIX509Cert.h" +#include "nsIXULRuntime.h" + +#include "nsCommandManager.h" +#include "nsPIDOMWindow.h" +#include "nsPIWindowRoot.h" + +#include "IHistory.h" +#include "IUrlClassifierUITelemetry.h" + +#include "nsArray.h" +#include "nsArrayUtils.h" +#include "nsCExternalHandlerService.h" +#include "nsContentDLF.h" +#include "nsContentPolicyUtils.h" // NS_CheckContentLoadPolicy(...) +#include "nsContentSecurityManager.h" +#include "nsContentSecurityUtils.h" +#include "nsContentUtils.h" +#include "nsCURILoader.h" +#include "nsDocShellCID.h" +#include "nsDocShellEditorData.h" +#include "nsDocShellEnumerator.h" +#include "nsDocShellLoadState.h" +#include "nsDocShellLoadTypes.h" +#include "nsDOMCID.h" +#include "nsDOMNavigationTiming.h" +#include "nsDSURIContentListener.h" +#include "nsEditingSession.h" +#include "nsError.h" +#include "nsEscape.h" +#include "nsFocusManager.h" +#include "nsGlobalWindow.h" +#include "nsISearchService.h" +#include "nsJSEnvironment.h" +#include "nsNetCID.h" +#include "nsNetUtil.h" +#include "nsObjectLoadingContent.h" +#include "nsPingListener.h" +#include "nsPoint.h" +#include "nsQueryObject.h" +#include "nsQueryActor.h" +#include "nsRect.h" +#include "nsRefreshTimer.h" +#include "nsSandboxFlags.h" +#include "nsSHEntry.h" +#include "nsSHistory.h" +#include "nsSHEntry.h" +#include "nsStructuredCloneContainer.h" +#include "nsSubDocumentFrame.h" +#include "nsURILoader.h" +#include "nsURLHelper.h" +#include "nsView.h" +#include "nsViewManager.h" +#include "nsViewSourceHandler.h" +#include "nsWebBrowserFind.h" +#include "nsWhitespaceTokenizer.h" +#include "nsWidgetsCID.h" +#include "nsXULAppAPI.h" + +#include "BRNameMatchingPolicy.h" +#include "GeckoProfiler.h" +#include "mozilla/NullPrincipal.h" +#include "Navigator.h" +#include "prenv.h" +#include "URIUtils.h" +#include "sslerr.h" +#include "mozpkix/pkix.h" +#include "NSSErrorsService.h" + +#include "timeline/JavascriptTimelineMarker.h" +#include "nsDocShellTelemetryUtils.h" + +#ifdef MOZ_PLACES +# include "nsIFaviconService.h" +# include "mozIPlacesPendingOperation.h" +#endif + +#if NS_PRINT_PREVIEW +# include "nsIDocumentViewerPrint.h" +# include "nsIWebBrowserPrint.h" +#endif + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::net; + +using mozilla::ipc::Endpoint; + +// Threshold value in ms for META refresh based redirects +#define REFRESH_REDIRECT_TIMER 15000 + +// Hint for native dispatch of events on how long to delay after +// all documents have loaded in milliseconds before favoring normal +// native event dispatch priorites over performance +// Can be overridden with docshell.event_starvation_delay_hint pref. +#define NS_EVENT_STARVATION_DELAY_HINT 2000 + +static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID); + +// Number of documents currently loading +static int32_t gNumberOfDocumentsLoading = 0; + +// Global count of docshells with the private attribute set +static uint32_t gNumberOfPrivateDocShells = 0; + +static mozilla::LazyLogModule gCharsetMenuLog("CharsetMenu"); + +#define LOGCHARSETMENU(args) \ + MOZ_LOG(gCharsetMenuLog, mozilla::LogLevel::Debug, args) + +#ifdef DEBUG +unsigned long nsDocShell::gNumberOfDocShells = 0; +static uint64_t gDocshellIDCounter = 0; + +static mozilla::LazyLogModule gDocShellLog("nsDocShell"); +static mozilla::LazyLogModule gDocShellAndDOMWindowLeakLogging( + "DocShellAndDOMWindowLeak"); +#endif +static mozilla::LazyLogModule gDocShellLeakLog("nsDocShellLeak"); +extern mozilla::LazyLogModule gPageCacheLog; +mozilla::LazyLogModule gSHLog("SessionHistory"); + +const char kBrandBundleURL[] = "chrome://branding/locale/brand.properties"; +const char kAppstringsBundleURL[] = + "chrome://global/locale/appstrings.properties"; + +static void FavorPerformanceHint(bool aPerfOverStarvation) { + nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID); + if (appShell) { + appShell->FavorPerformanceHint( + aPerfOverStarvation, + Preferences::GetUint("docshell.event_starvation_delay_hint", + NS_EVENT_STARVATION_DELAY_HINT)); + } +} + +static void IncreasePrivateDocShellCount() { + gNumberOfPrivateDocShells++; + if (gNumberOfPrivateDocShells > 1 || !XRE_IsContentProcess()) { + return; + } + + mozilla::dom::ContentChild* cc = mozilla::dom::ContentChild::GetSingleton(); + cc->SendPrivateDocShellsExist(true); +} + +static void DecreasePrivateDocShellCount() { + MOZ_ASSERT(gNumberOfPrivateDocShells > 0); + gNumberOfPrivateDocShells--; + if (!gNumberOfPrivateDocShells) { + if (XRE_IsContentProcess()) { + dom::ContentChild* cc = dom::ContentChild::GetSingleton(); + cc->SendPrivateDocShellsExist(false); + return; + } + + nsCOMPtr<nsIObserverService> obsvc = services::GetObserverService(); + if (obsvc) { + obsvc->NotifyObservers(nullptr, "last-pb-context-exited", nullptr); + } + } +} + +static bool IsTopLevelDoc(BrowsingContext* aBrowsingContext, + nsILoadInfo* aLoadInfo) { + MOZ_ASSERT(aBrowsingContext); + MOZ_ASSERT(aLoadInfo); + + if (aLoadInfo->GetExternalContentPolicyType() != + ExtContentPolicy::TYPE_DOCUMENT) { + return false; + } + + return aBrowsingContext->IsTopContent(); +} + +// True if loading for top level document loading in active tab. +static bool IsUrgentStart(BrowsingContext* aBrowsingContext, + nsILoadInfo* aLoadInfo, uint32_t aLoadType) { + MOZ_ASSERT(aBrowsingContext); + MOZ_ASSERT(aLoadInfo); + + if (!IsTopLevelDoc(aBrowsingContext, aLoadInfo)) { + return false; + } + + if (aLoadType & + (nsIDocShell::LOAD_CMD_NORMAL | nsIDocShell::LOAD_CMD_HISTORY)) { + return true; + } + + return aBrowsingContext->IsActive(); +} + +nsDocShell::nsDocShell(BrowsingContext* aBrowsingContext, + uint64_t aContentWindowID) + : nsDocLoader(), + mContentWindowID(aContentWindowID), + mBrowsingContext(aBrowsingContext), + mForcedCharset(nullptr), + mParentCharset(nullptr), + mTreeOwner(nullptr), + mScrollbarPref(ScrollbarPreference::Auto), + mCharsetReloadState(eCharsetReloadInit), + mParentCharsetSource(0), + mFrameMargins(-1, -1), + mItemType(aBrowsingContext->IsContent() ? typeContent : typeChrome), + mPreviousEntryIndex(-1), + mLoadedEntryIndex(-1), + mChildOffset(0), + mBusyFlags(BUSY_FLAGS_NONE), + mAppType(nsIDocShell::APP_TYPE_UNKNOWN), + mLoadType(0), + mFailedLoadType(0), + mJSRunToCompletionDepth(0), + mMetaViewportOverride(nsIDocShell::META_VIEWPORT_OVERRIDE_NONE), + mCreatingDocument(false), +#ifdef DEBUG + mInEnsureScriptEnv(false), +#endif + mInitialized(false), + mAllowSubframes(true), + mAllowJavascript(true), + mAllowMetaRedirects(true), + mAllowImages(true), + mAllowMedia(true), + mAllowDNSPrefetch(true), + mAllowWindowControl(true), + mCSSErrorReportingEnabled(false), + mAllowAuth(mItemType == typeContent), + mAllowKeywordFixup(false), + mIsOffScreenBrowser(false), + mDisableMetaRefreshWhenInactive(false), + mIsAppTab(false), + mDeviceSizeIsPageSize(false), + mWindowDraggingAllowed(false), + mInFrameSwap(false), + mCanExecuteScripts(false), + mFiredUnloadEvent(false), + mEODForCurrentDocument(false), + mURIResultedInDocument(false), + mIsBeingDestroyed(false), + mIsExecutingOnLoadHandler(false), + mSavingOldViewer(false), + mAffectPrivateSessionLifetime(true), + mInvisible(false), + mHasLoadedNonBlankURI(false), + mBlankTiming(false), + mTitleValidForCurrentURI(false), + mWillChangeProcess(false), + mIsNavigating(false), + mSuspendMediaWhenInactive(false), + mForcedAutodetection(false) { + // If no outer window ID was provided, generate a new one. + if (aContentWindowID == 0) { + mContentWindowID = nsContentUtils::GenerateWindowId(); + } + + MOZ_LOG(gDocShellLeakLog, LogLevel::Debug, ("DOCSHELL %p created\n", this)); + +#ifdef DEBUG + mDocShellID = gDocshellIDCounter++; + // We're counting the number of |nsDocShells| to help find leaks + ++gNumberOfDocShells; + MOZ_LOG(gDocShellAndDOMWindowLeakLogging, LogLevel::Info, + ("++DOCSHELL %p == %ld [pid = %d] [id = %" PRIu64 "]\n", (void*)this, + gNumberOfDocShells, getpid(), mDocShellID)); +#endif +} + +nsDocShell::~nsDocShell() { + MOZ_ASSERT(!mObserved); + + // Avoid notifying observers while we're in the dtor. + mIsBeingDestroyed = true; + + Destroy(); + + if (mContentViewer) { + mContentViewer->Close(nullptr); + mContentViewer->Destroy(); + mContentViewer = nullptr; + } + + MOZ_LOG(gDocShellLeakLog, LogLevel::Debug, ("DOCSHELL %p destroyed\n", this)); + +#ifdef DEBUG + if (MOZ_LOG_TEST(gDocShellAndDOMWindowLeakLogging, LogLevel::Info)) { + nsAutoCString url; + if (mLastOpenedURI) { + url = mLastOpenedURI->GetSpecOrDefault(); + + // Data URLs can be very long, so truncate to avoid flooding the log. + const uint32_t maxURLLength = 1000; + if (url.Length() > maxURLLength) { + url.Truncate(maxURLLength); + } + } + + // We're counting the number of |nsDocShells| to help find leaks + --gNumberOfDocShells; + MOZ_LOG( + gDocShellAndDOMWindowLeakLogging, LogLevel::Info, + ("--DOCSHELL %p == %ld [pid = %d] [id = %" PRIu64 "] [url = %s]\n", + (void*)this, gNumberOfDocShells, getpid(), mDocShellID, url.get())); + } +#endif +} + +bool nsDocShell::Initialize() { + if (mInitialized) { + // We've already been initialized. + return true; + } + + NS_ASSERTION(mItemType == typeContent || mItemType == typeChrome, + "Unexpected item type in docshell"); + + NS_ENSURE_TRUE(Preferences::GetRootBranch(), false); + mInitialized = true; + + mDisableMetaRefreshWhenInactive = + Preferences::GetBool("browser.meta_refresh_when_inactive.disabled", + mDisableMetaRefreshWhenInactive); + + mDeviceSizeIsPageSize = Preferences::GetBool( + "docshell.device_size_is_page_size", mDeviceSizeIsPageSize); + + if (nsCOMPtr<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); + + // If parent intercept is not enabled then we must forward to + // the network controller from docshell. We also enable if we're + // in the parent process in order to support non-e10s configurations. + // Note: This check is duplicated in SharedWorkerInterfaceRequestor's + // constructor. + if (!ServiceWorkerParentInterceptEnabled() || XRE_IsParentProcess()) { + ds->mInterceptController = new ServiceWorkerInterceptController(); + } + + // We want to hold a strong ref to the loadgroup, so it better hold a weak + // ref to us... use an InterfaceRequestorProxy to do this. + nsCOMPtr<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_ENTRY(nsIDeprecationWarner) +NS_INTERFACE_MAP_END_INHERITING(nsDocLoader) + +NS_IMETHODIMP +nsDocShell::GetInterface(const nsIID& aIID, void** aSink) { + MOZ_ASSERT(aSink, "null out param"); + + *aSink = nullptr; + + if (aIID.Equals(NS_GET_IID(nsICommandManager))) { + NS_ENSURE_SUCCESS(EnsureCommandHandler(), NS_ERROR_FAILURE); + *aSink = static_cast<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(EnsureContentViewer())) { + RefPtr<Document> doc = mContentViewer->GetDocument(); + doc.forget(aSink); + return *aSink ? NS_OK : NS_NOINTERFACE; + } else if (aIID.Equals(NS_GET_IID(nsIApplicationCacheContainer))) { + *aSink = nullptr; + + // Return application cache associated with this docshell, if any + + nsCOMPtr<nsIContentViewer> contentViewer; + GetContentViewer(getter_AddRefs(contentViewer)); + if (!contentViewer) { + return NS_ERROR_NO_INTERFACE; + } + + RefPtr<Document> doc = contentViewer->GetDocument(); + NS_ASSERTION(doc, "Should have a document."); + if (!doc) { + return NS_ERROR_NO_INTERFACE; + } + +#if defined(DEBUG) + MOZ_LOG( + gDocShellLog, LogLevel::Debug, + ("nsDocShell[%p]: returning app cache container %p", this, doc.get())); +#endif + return doc->QueryInterface(aIID, aSink); + } else if (aIID.Equals(NS_GET_IID(nsIPrompt)) && + NS_SUCCEEDED(EnsureScriptEnvironment())) { + nsresult rv; + nsCOMPtr<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; +} + +NS_IMETHODIMP +nsDocShell::LoadURI(nsDocShellLoadState* aLoadState, bool aSetNavigating) { + return LoadURI(aLoadState, aSetNavigating, false); +} + +nsresult nsDocShell::LoadURI(nsDocShellLoadState* aLoadState, + bool aSetNavigating, + bool aContinueHandlingSubframeHistory) { + MOZ_ASSERT(aLoadState, "Must have a valid load state!"); + MOZ_ASSERT( + (aLoadState->LoadFlags() & INTERNAL_LOAD_FLAGS_LOADURI_SETUP_FLAGS) == 0, + "Should not have these flags set"); + MOZ_ASSERT(aLoadState->TargetBrowsingContext().IsNull(), + "Targeting doesn't occur until InternalLoad"); + + if (!aLoadState->TriggeringPrincipal()) { + MOZ_ASSERT(false, "LoadURI must have a triggering principal"); + return NS_ERROR_FAILURE; + } + + bool oldIsNavigating = mIsNavigating; + auto cleanupIsNavigating = + MakeScopeExit([&]() { mIsNavigating = oldIsNavigating; }); + if (aSetNavigating) { + mIsNavigating = true; + } + + PopupBlocker::PopupControlState popupState = PopupBlocker::openOverridden; + if (aLoadState->LoadFlags() & LOAD_FLAGS_ALLOW_POPUPS) { + popupState = PopupBlocker::openAllowed; + // If we allow popups as part of the navigation, ensure we fake a user + // interaction, so that popups can, in fact, be allowed to open. + if (WindowContext* wc = mBrowsingContext->GetCurrentWindowContext()) { + wc->NotifyUserGestureActivation(); + } + } + + AutoPopupStatePusher statePusher(popupState); + + if (aLoadState->GetCancelContentJSEpoch().isSome()) { + SetCancelContentJSEpoch(*aLoadState->GetCancelContentJSEpoch()); + } + + // Note: we allow loads to get through here even if mFiredUnloadEvent is + // true; that case will get handled in LoadInternal or LoadHistoryEntry, + // so we pass false as the second parameter to IsNavigationAllowed. + // However, we don't allow the page to change location *in the middle of* + // firing beforeunload, so we do need to check if *beforeunload* is currently + // firing, so we call IsNavigationAllowed rather than just IsPrintingOrPP. + if (!IsNavigationAllowed(true, false)) { + return NS_OK; // JS may not handle returning of an error code + } + + nsLoadFlags defaultLoadFlags = mBrowsingContext->GetDefaultLoadFlags(); + if (aLoadState->LoadFlags() & LOAD_FLAGS_FORCE_TRR) { + defaultLoadFlags |= nsIRequest::LOAD_TRR_ONLY_MODE; + } else if (aLoadState->LoadFlags() & LOAD_FLAGS_DISABLE_TRR) { + defaultLoadFlags |= nsIRequest::LOAD_TRR_DISABLED_MODE; + } + + MOZ_ALWAYS_SUCCEEDS(mBrowsingContext->SetDefaultLoadFlags(defaultLoadFlags)); + + if (!StartupTimeline::HasRecord(StartupTimeline::FIRST_LOAD_URI) && + mItemType == typeContent && !NS_IsAboutBlank(aLoadState->URI())) { + StartupTimeline::RecordOnce(StartupTimeline::FIRST_LOAD_URI); + } + + // LoadType used to be set to a default value here, if no LoadInfo/LoadState + // object was passed in. That functionality has been removed as of bug + // 1492648. LoadType should now be set up by the caller at the time they + // create their nsDocShellLoadState object to pass into LoadURI. + + MOZ_LOG( + gDocShellLeakLog, LogLevel::Debug, + ("nsDocShell[%p]: loading %s with flags 0x%08x", this, + aLoadState->URI()->GetSpecOrDefault().get(), aLoadState->LoadFlags())); + + if ((!aLoadState->LoadIsFromSessionHistory() && + !LOAD_TYPE_HAS_FLAGS(aLoadState->LoadType(), + LOAD_FLAGS_REPLACE_HISTORY)) || + aContinueHandlingSubframeHistory) { + // This is possibly a subframe, so handle it accordingly. + // + // If history exists, it will be loaded into the aLoadState object, and the + // LoadType will be changed. + if (MaybeHandleSubframeHistory(aLoadState, + aContinueHandlingSubframeHistory)) { + // MaybeHandleSubframeHistory returns true if we need to continue loading + // asynchronously. + return NS_OK; + } + } + + if (aLoadState->LoadIsFromSessionHistory()) { + MOZ_LOG(gSHLog, LogLevel::Debug, + ("nsDocShell[%p]: loading from session history", this)); + + if (!mozilla::SessionHistoryInParent()) { + return LoadHistoryEntry(aLoadState->SHEntry(), aLoadState->LoadType()); + } + + // FIXME Null check aLoadState->GetLoadingSessionHistoryInfo()? + return LoadHistoryEntry(*aLoadState->GetLoadingSessionHistoryInfo(), + aLoadState->LoadType()); + } + + // On history navigation via Back/Forward buttons, don't execute + // automatic JavaScript redirection such as |location.href = ...| or + // |window.open()| + // + // LOAD_NORMAL: window.open(...) etc. + // LOAD_STOP_CONTENT: location.href = ..., location.assign(...) + if ((aLoadState->LoadType() == LOAD_NORMAL || + aLoadState->LoadType() == LOAD_STOP_CONTENT) && + ShouldBlockLoadingForBackButton()) { + return NS_OK; + } + + BrowsingContext::Type bcType = mBrowsingContext->GetType(); + + // Set up the inheriting principal in LoadState. + nsresult rv = aLoadState->SetupInheritingPrincipal( + bcType, mBrowsingContext->OriginAttributesRef()); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aLoadState->SetupTriggeringPrincipal( + mBrowsingContext->OriginAttributesRef()); + NS_ENSURE_SUCCESS(rv, rv); + + aLoadState->CalculateLoadURIFlags(); + + MOZ_ASSERT(aLoadState->TypeHint().IsVoid(), + "Typehint should be null when calling InternalLoad from LoadURI"); + MOZ_ASSERT(aLoadState->FileName().IsVoid(), + "FileName should be null when calling InternalLoad from LoadURI"); + MOZ_ASSERT(!aLoadState->LoadIsFromSessionHistory(), + "Shouldn't be loading from an entry when calling InternalLoad " + "from LoadURI"); + + rv = InternalLoad(aLoadState); + NS_ENSURE_SUCCESS(rv, rv); + + if (aLoadState->GetOriginalURIString().isSome()) { + // Save URI string in case it's needed later when + // sending to search engine service in EndPageLoad() + mOriginalUriString = *aLoadState->GetOriginalURIString(); + } + + return NS_OK; +} + +bool nsDocShell::IsLoadingFromSessionHistory() { + return mActiveEntryIsLoadingFromSessionHistory; +} + +bool nsDocShell::MaybeHandleSubframeHistory( + nsDocShellLoadState* aLoadState, bool aContinueHandlingSubframeHistory) { + // First, verify if this is a subframe. + // Note, it is ok to rely on docshell here and not browsing context since when + // an iframe is created, it has first in-process docshell. + nsCOMPtr<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! + bool inOnLoadHandler = false; + GetIsExecutingOnLoadHandler(&inOnLoadHandler); + if (inOnLoadHandler) { + aLoadState->SetLoadType(LOAD_NORMAL_REPLACE); + } + } + return false; + } + + /* OK. It is a subframe. Checkout the parent's loadtype. If the parent was + * loaded through a history mechanism, then get the SH entry for the child + * from the parent. This is done to restore frameset navigation while going + * back/forward. If the parent was loaded through any other loadType, set the + * child's loadType too accordingly, so that session history does not get + * confused. + */ + + // Get the parent's load type + uint32_t parentLoadType; + parentDS->GetLoadType(&parentLoadType); + + if (!aContinueHandlingSubframeHistory) { + if (mozilla::SessionHistoryInParent()) { + if (nsDocShell::Cast(parentDS.get())->IsLoadingFromSessionHistory() && + !GetCreatedDynamically()) { + if (XRE_IsContentProcess()) { + dom::ContentChild* contentChild = dom::ContentChild::GetSingleton(); + if (contentChild) { + RefPtr<Document> parentDoc = parentDS->GetDocument(); + parentDoc->BlockOnload(); + RefPtr<BrowsingContext> browsingContext = mBrowsingContext; + Maybe<uint64_t> currentLoadIdentifier = + mBrowsingContext->GetCurrentLoadIdentifier(); + RefPtr<nsDocShellLoadState> loadState = aLoadState; + bool isNavigating = mIsNavigating; + + auto resolve = + [currentLoadIdentifier, browsingContext, parentDoc, loadState, + isNavigating](Tuple<mozilla::Maybe<LoadingSessionHistoryInfo>, + int32_t, int32_t>&& aResult) { + if (currentLoadIdentifier == + browsingContext->GetCurrentLoadIdentifier() && + Get<0>(aResult).isSome()) { + loadState->SetLoadingSessionHistoryInfo( + Get<0>(aResult).value()); + loadState->SetLoadIsFromSessionHistory( + Get<1>(aResult), Get<2>(aResult), false); + } + RefPtr<nsDocShell> docShell = + static_cast<nsDocShell*>(browsingContext->GetDocShell()); + if (docShell) { + // We got the results back from the parent process, call + // LoadURI again with the possibly updated data. + docShell->LoadURI(loadState, isNavigating, true); + } + parentDoc->UnblockOnload(false); + }; + auto reject = [parentDoc](mozilla::ipc::ResponseRejectReason) { + parentDoc->UnblockOnload(false); + }; + contentChild->SendGetLoadingSessionHistoryInfoFromParent( + mBrowsingContext, std::move(resolve), std::move(reject)); + return true; + } + } else { + Maybe<LoadingSessionHistoryInfo> info; + int32_t requestedIndex = -1; + int32_t sessionHistoryLength = 0; + mBrowsingContext->Canonical()->GetLoadingSessionHistoryInfoFromParent( + info, &requestedIndex, &sessionHistoryLength); + if (info.isSome()) { + aLoadState->SetLoadingSessionHistoryInfo(info.value()); + aLoadState->SetLoadIsFromSessionHistory( + requestedIndex, sessionHistoryLength, false); + } + } + } + } else { + // Get the ShEntry for the child from the parent + nsCOMPtr<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( + mChildOffset, getter_AddRefs(shEntry)); + if (shEntry) { + aLoadState->SetSHEntry(shEntry); + } + } + } + } + + // Make some decisions on the child frame's loadType based on the + // parent's loadType, if the subframe hasn't loaded anything into it. + // + // In some cases privileged scripts may try to get the DOMWindow + // reference of this docshell before the loading starts, causing the + // initial about:blank content viewer being created and mCurrentURI being + // set. To handle this case we check if mCurrentURI is about:blank and + // currentSHEntry is null. + bool oshe = false; + nsCOMPtr<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 || + parentLoadType == LOAD_NORMAL_EXTERNAL)) { + // The parent was loaded normally. In this case, this *brand new* + // child really shouldn't have a SHEntry. If it does, it could be + // because the parent is replacing an existing frame with a new frame, + // in the onLoadHandler. We don't want this url to get into session + // history. Clear off shEntry, and set load type to + // LOAD_BYPASS_HISTORY. + bool inOnLoadHandler = false; + parentDS->GetIsExecutingOnLoadHandler(&inOnLoadHandler); + if (inOnLoadHandler) { + aLoadState->SetLoadType(LOAD_NORMAL_REPLACE); + aLoadState->ClearLoadIsFromSessionHistory(); + } + } else if (parentLoadType == LOAD_REFRESH) { + // Clear shEntry. For refresh loads, we have to load + // what comes through the pipe, not what's in history. + aLoadState->ClearLoadIsFromSessionHistory(); + } else if ((parentLoadType == LOAD_BYPASS_HISTORY) || + (aLoadState->LoadIsFromSessionHistory() && + ((parentLoadType & LOAD_CMD_HISTORY) || + (parentLoadType == LOAD_RELOAD_NORMAL) || + (parentLoadType == LOAD_RELOAD_CHARSET_CHANGE) || + (parentLoadType == LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE) || + (parentLoadType == + LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE)))) { + // If the parent url, bypassed history or was loaded from + // history, pass on the parent's loadType to the new child + // frame too, so that the child frame will also + // avoid getting into history. + aLoadState->SetLoadType(parentLoadType); + } else if (parentLoadType == LOAD_ERROR_PAGE) { + // If the parent document is an error page, we don't + // want to update global/session history. However, + // this child frame is not an error page. + aLoadState->SetLoadType(LOAD_BYPASS_HISTORY); + } else if ((parentLoadType == LOAD_RELOAD_BYPASS_CACHE) || + (parentLoadType == LOAD_RELOAD_BYPASS_PROXY) || + (parentLoadType == LOAD_RELOAD_BYPASS_PROXY_AND_CACHE)) { + // the new frame should inherit the parent's load type so that it also + // bypasses the cache and/or proxy + aLoadState->SetLoadType(parentLoadType); + } + + return false; +} + +/* + * Reset state to a new content model within the current document and the + * document viewer. Called by the document before initiating an out of band + * document.write(). + */ +NS_IMETHODIMP +nsDocShell::PrepareForNewContentModel() { + // Clear out our form control state, because the state of controls + // in the pre-open() document should not affect the state of + // controls that are now going to be written. + SetLayoutHistoryState(nullptr); + mEODForCurrentDocument = false; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::FirePageHideNotification(bool aIsUnload) { + FirePageHideNotificationInternal(aIsUnload, false); + return NS_OK; +} + +void nsDocShell::FirePageHideNotificationInternal( + bool aIsUnload, bool aSkipCheckingDynEntries) { + if (mContentViewer && !mFiredUnloadEvent) { + // Keep an explicit reference since calling PageHide could release + // mContentViewer + nsCOMPtr<nsIContentViewer> contentViewer(mContentViewer); + mFiredUnloadEvent = true; + + if (mTiming) { + mTiming->NotifyUnloadEventStart(); + } + + contentViewer->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(); + } +} + +nsresult nsDocShell::Dispatch(TaskCategory aCategory, + already_AddRefed<nsIRunnable>&& aRunnable) { + nsCOMPtr<nsIRunnable> runnable(aRunnable); + nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow(); + if (NS_WARN_IF(!win)) { + // Window should only be unavailable after destroyed. + MOZ_ASSERT(mIsBeingDestroyed); + return NS_ERROR_FAILURE; + } + + if (win->GetDocGroup()) { + return win->GetDocGroup()->Dispatch(aCategory, runnable.forget()); + } + + return SchedulerGroup::Dispatch(aCategory, runnable.forget()); +} + +NS_IMETHODIMP +nsDocShell::DispatchLocationChangeEvent() { + return Dispatch( + TaskCategory::Other, + NewRunnableMethod("nsDocShell::FireDummyOnLocationChange", this, + &nsDocShell::FireDummyOnLocationChange)); +} + +NS_IMETHODIMP +nsDocShell::StartDelayedAutoplayMediaComponents() { + RefPtr<nsPIDOMWindowOuter> outerWindow = GetWindow(); + if (outerWindow) { + outerWindow->SetMediaSuspend(nsISuspendedTypes::NONE_SUSPENDED); + } + return NS_OK; +} + +bool nsDocShell::MaybeInitTiming() { + if (mTiming && !mBlankTiming) { + return false; + } + + bool canBeReset = false; + + if (mScriptGlobal && mBlankTiming) { + nsPIDOMWindowInner* innerWin = mScriptGlobal->GetCurrentInnerWindow(); + if (innerWin && innerWin->GetPerformance()) { + mTiming = innerWin->GetPerformance()->GetDOMTiming(); + mBlankTiming = false; + } + } + + if (!mTiming) { + mTiming = new nsDOMNavigationTiming(this); + canBeReset = true; + } + + mTiming->NotifyNavigationStart( + mBrowsingContext->IsActive() + ? nsDOMNavigationTiming::DocShellState::eActive + : nsDOMNavigationTiming::DocShellState::eInactive); + + return canBeReset; +} + +void nsDocShell::MaybeResetInitTiming(bool aReset) { + if (aReset) { + mTiming = nullptr; + } +} + +nsDOMNavigationTiming* nsDocShell::GetNavigationTiming() const { + return mTiming; +} + +// +// Bug 13871: Prevent frameset spoofing +// +// This routine answers: 'Is origin's document from same domain as +// target's document?' +// +// file: uris are considered the same domain for the purpose of +// frame navigation regardless of script accessibility (bug 420425) +// +/* static */ +bool nsDocShell::ValidateOrigin(BrowsingContext* aOrigin, + BrowsingContext* aTarget) { + nsIDocShell* originDocShell = aOrigin->GetDocShell(); + MOZ_ASSERT(originDocShell, "originDocShell must not be null"); + Document* originDocument = originDocShell->GetDocument(); + NS_ENSURE_TRUE(originDocument, false); + + nsIDocShell* targetDocShell = aTarget->GetDocShell(); + MOZ_ASSERT(targetDocShell, "targetDocShell must not be null"); + Document* targetDocument = targetDocShell->GetDocument(); + NS_ENSURE_TRUE(targetDocument, false); + + bool equal; + nsresult rv = originDocument->NodePrincipal()->Equals( + targetDocument->NodePrincipal(), &equal); + if (NS_SUCCEEDED(rv) && equal) { + return true; + } + // Not strictly equal, special case if both are file: uris + nsCOMPtr<nsIURI> originURI; + nsCOMPtr<nsIURI> targetURI; + nsCOMPtr<nsIURI> innerOriginURI; + nsCOMPtr<nsIURI> innerTargetURI; + + // Casting to BasePrincipal, as we can't get InnerMost URI otherwise + auto* originDocumentBasePrincipal = + BasePrincipal::Cast(originDocument->NodePrincipal()); + + rv = originDocumentBasePrincipal->GetURI(getter_AddRefs(originURI)); + if (NS_SUCCEEDED(rv) && originURI) { + innerOriginURI = NS_GetInnermostURI(originURI); + } + + auto* targetDocumentBasePrincipal = + BasePrincipal::Cast(targetDocument->NodePrincipal()); + + rv = targetDocumentBasePrincipal->GetURI(getter_AddRefs(targetURI)); + if (NS_SUCCEEDED(rv) && targetURI) { + innerTargetURI = NS_GetInnermostURI(targetURI); + } + + return innerOriginURI && innerTargetURI && SchemeIsFile(innerOriginURI) && + SchemeIsFile(innerTargetURI); +} + +nsPresContext* nsDocShell::GetEldestPresContext() { + nsIContentViewer* viewer = mContentViewer; + while (viewer) { + nsIContentViewer* prevViewer = viewer->GetPreviousViewer(); + if (!prevViewer) { + return viewer->GetPresContext(); + } + viewer = prevViewer; + } + + return nullptr; +} + +nsPresContext* nsDocShell::GetPresContext() { + if (!mContentViewer) { + return nullptr; + } + + return mContentViewer->GetPresContext(); +} + +PresShell* nsDocShell::GetPresShell() { + nsPresContext* presContext = GetPresContext(); + return presContext ? presContext->GetPresShell() : nullptr; +} + +PresShell* nsDocShell::GetEldestPresShell() { + nsPresContext* presContext = GetEldestPresContext(); + + if (presContext) { + return presContext->GetPresShell(); + } + + return nullptr; +} + +NS_IMETHODIMP +nsDocShell::GetContentViewer(nsIContentViewer** aContentViewer) { + NS_ENSURE_ARG_POINTER(aContentViewer); + + *aContentViewer = mContentViewer; + NS_IF_ADDREF(*aContentViewer); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetOuterWindowID(uint64_t* aWindowID) { + *aWindowID = mContentWindowID; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetChromeEventHandler(EventTarget* aChromeEventHandler) { + mChromeEventHandler = aChromeEventHandler; + + if (mScriptGlobal) { + mScriptGlobal->SetChromeEventHandler(mChromeEventHandler); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetChromeEventHandler(EventTarget** aChromeEventHandler) { + NS_ENSURE_ARG_POINTER(aChromeEventHandler); + RefPtr<EventTarget> handler = mChromeEventHandler; + handler.forget(aChromeEventHandler); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetCurrentURI(nsIURI* aURI) { + // Note that securityUI will set STATE_IS_INSECURE, even if + // the scheme of |aURI| is "https". + SetCurrentURI(aURI, nullptr, true, 0); + return NS_OK; +} + +bool nsDocShell::SetCurrentURI(nsIURI* aURI, nsIRequest* aRequest, + bool aFireOnLocationChange, + uint32_t aLocationFlags) { + MOZ_ASSERT(!mIsBeingDestroyed); + + MOZ_LOG(gDocShellLeakLog, LogLevel::Debug, + ("DOCSHELL %p SetCurrentURI %s\n", this, + aURI ? aURI->GetSpecOrDefault().get() : "")); + + // We don't want to send a location change when we're displaying an error + // page, and we don't want to change our idea of "current URI" either + if (mLoadType == LOAD_ERROR_PAGE) { + return false; + } + + bool uriIsEqual = false; + if (!mCurrentURI || !aURI || + NS_FAILED(mCurrentURI->Equals(aURI, &uriIsEqual)) || !uriIsEqual) { + mTitleValidForCurrentURI = false; + } + + mCurrentURI = aURI; + +#ifdef DEBUG + mLastOpenedURI = aURI; +#endif + + if (!NS_IsAboutBlank(mCurrentURI)) { + mHasLoadedNonBlankURI = true; + } + + bool isRoot = mBrowsingContext->IsTop(); + bool isSubFrame = false; // Is this a subframe navigation? + + if (mozilla::SessionHistoryInParent()) { + if (mLoadingEntry) { + isSubFrame = mLoadingEntry->mInfo.IsSubFrame(); + } else { + isSubFrame = !mBrowsingContext->IsTop() && mActiveEntry; + } + MOZ_LOG(gSHLog, LogLevel::Debug, + ("nsDocShell %p SetCurrentURI, isSubFrame=%d", this, isSubFrame)); + } else { + if (mLSHE) { + isSubFrame = mLSHE->GetIsSubFrame(); + } + } + + if (!isSubFrame && !isRoot) { + /* + * We don't want to send OnLocationChange notifications when + * a subframe is being loaded for the first time, while + * visiting a frameset page + */ + return false; + } + + if (aFireOnLocationChange) { + FireOnLocationChange(this, aRequest, aURI, aLocationFlags); + } + return !aFireOnLocationChange; +} + +NS_IMETHODIMP +nsDocShell::GetCharset(nsACString& aCharset) { + aCharset.Truncate(); + + PresShell* presShell = GetPresShell(); + NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); + Document* doc = presShell->GetDocument(); + NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); + doc->GetDocumentCharacterSet()->Name(aCharset); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GatherCharsetMenuTelemetry() { + nsCOMPtr<nsIContentViewer> viewer; + GetContentViewer(getter_AddRefs(viewer)); + if (!viewer) { + return NS_OK; + } + + Document* doc = viewer->GetDocument(); + if (!doc || doc->WillIgnoreCharsetOverride()) { + return NS_OK; + } + + if (mForcedAutodetection) { + LOGCHARSETMENU(("ENCODING_OVERRIDE_USED_AUTOMATIC")); + Telemetry::ScalarSet(Telemetry::ScalarID::ENCODING_OVERRIDE_USED_AUTOMATIC, + true); + } else { + LOGCHARSETMENU(("ENCODING_OVERRIDE_USED_MANUAL")); + Telemetry::ScalarSet(Telemetry::ScalarID::ENCODING_OVERRIDE_USED_MANUAL, + true); + } + + nsIURI* url = doc->GetOriginalURI(); + bool isFileURL = url && SchemeIsFile(url); + + int32_t charsetSource = doc->GetDocumentCharacterSetSource(); + auto encoding = doc->GetDocumentCharacterSet(); + switch (charsetSource) { + case kCharsetFromInitialUserForcedAutoDetection: + case kCharsetFromFinalUserForcedAutoDetection: + LOGCHARSETMENU(("AutoOverridden")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::AutoOverridden); + break; + case kCharsetFromUserForced: + case kCharsetFromUserForcedJapaneseAutoDetection: + LOGCHARSETMENU(("ManuallyOverridden")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::ManuallyOverridden); + break; + case kCharsetFromTopLevelDomain: + if (encoding == WINDOWS_1252_ENCODING) { + LOGCHARSETMENU(("UnlabeledInLk")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::UnlabeledInLk); + } else { + LOGCHARSETMENU(("UnlabeledJp")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::UnlabeledJp); + } + break; + case kCharsetFromFinalJapaneseAutoDetection: + LOGCHARSETMENU(("UnlabeledJp")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::UnlabeledJp); + break; + case kCharsetFromInitialAutoDetectionASCII: + // Deliberately no final version + LOGCHARSETMENU(("UnlabeledAscii")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::UnlabeledAscii); + break; + case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8Generic: + case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8Generic: + case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8Content: + case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8Content: + LOGCHARSETMENU(("UnlabeledNonUtf8")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::UnlabeledNonUtf8); + break; + case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD: + case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD: + LOGCHARSETMENU(("UnlabeledNonUtf8TLD")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::UnlabeledNonUtf8TLD); + break; + case kCharsetFromInitialAutoDetectionWouldHaveBeenUTF8: + case kCharsetFromFinalAutoDetectionWouldHaveBeenUTF8: + LOGCHARSETMENU(("UnlabeledUtf8")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::UnlabeledUtf8); + break; + case kCharsetFromChannel: + if (encoding == UTF_8_ENCODING) { + LOGCHARSETMENU(("ChannelUtf8")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::ChannelUtf8); + } else { + LOGCHARSETMENU(("ChannelNonUtf8")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::ChannelNonUtf8); + } + break; + case kCharsetFromMetaPrescan: + case kCharsetFromMetaTag: + if (isFileURL) { + LOGCHARSETMENU(("LocalLabeled")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::LocalLabeled); + } else if (encoding == UTF_8_ENCODING) { + LOGCHARSETMENU(("MetaUtf8")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::MetaUtf8); + } else { + LOGCHARSETMENU(("MetaNonUtf8")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::MetaNonUtf8); + } + break; + case kCharsetFromFinalAutoDetectionFile: + if (isFileURL) { + LOGCHARSETMENU(("LocalUnlabeled")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::LocalUnlabeled); + } else { + LOGCHARSETMENU(("Bug")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::Bug); + } + break; + default: + LOGCHARSETMENU(("Bug")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_2::Bug); + break; + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetCharset(const nsACString& aCharset) { + mForcedAutodetection = false; + if (aCharset.IsEmpty()) { + mForcedCharset = nullptr; + return NS_OK; + } + if (aCharset.EqualsLiteral("_autodetect_all")) { + mForcedCharset = WINDOWS_1252_ENCODING; + mForcedAutodetection = true; + return NS_OK; + } + const Encoding* encoding = Encoding::ForLabel(aCharset); + if (!encoding) { + // Reject unknown labels + return NS_ERROR_INVALID_ARG; + } + if (!encoding->IsAsciiCompatible() && encoding != ISO_2022_JP_ENCODING) { + // Reject XSS hazards + return NS_ERROR_INVALID_ARG; + } + mForcedCharset = encoding; + return NS_OK; +} + +void nsDocShell::SetParentCharset(const Encoding*& aCharset, + int32_t aCharsetSource, + nsIPrincipal* aPrincipal) { + mParentCharset = aCharset; + mParentCharsetSource = aCharsetSource; + mParentCharsetPrincipal = aPrincipal; +} + +void nsDocShell::GetParentCharset(const Encoding*& aCharset, + int32_t* aCharsetSource, + nsIPrincipal** aPrincipal) { + aCharset = mParentCharset; + *aCharsetSource = mParentCharsetSource; + NS_IF_ADDREF(*aPrincipal = mParentCharsetPrincipal); +} + +NS_IMETHODIMP +nsDocShell::GetHasTrackingContentBlocked(Promise** aPromise) { + MOZ_ASSERT(aPromise); + + ErrorResult rv; + RefPtr<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::GetAllowPlugins(bool* aAllowPlugins) { + NS_ENSURE_ARG_POINTER(aAllowPlugins); + + *aAllowPlugins = mBrowsingContext->GetAllowPlugins(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowPlugins(bool aAllowPlugins) { + // XXX should enable or disable a plugin host + return mBrowsingContext->SetAllowPlugins(aAllowPlugins); +} + +NS_IMETHODIMP +nsDocShell::GetAllowJavascript(bool* aAllowJavascript) { + NS_ENSURE_ARG_POINTER(aAllowJavascript); + + *aAllowJavascript = mAllowJavascript; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetCssErrorReportingEnabled(bool* aEnabled) { + MOZ_ASSERT(aEnabled); + *aEnabled = mCSSErrorReportingEnabled; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetCssErrorReportingEnabled(bool aEnabled) { + mCSSErrorReportingEnabled = aEnabled; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowJavascript(bool aAllowJavascript) { + mAllowJavascript = aAllowJavascript; + RecomputeCanExecuteScripts(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetUsePrivateBrowsing(bool* aUsePrivateBrowsing) { + NS_ENSURE_ARG_POINTER(aUsePrivateBrowsing); + return mBrowsingContext->GetUsePrivateBrowsing(aUsePrivateBrowsing); +} + +void nsDocShell::NotifyPrivateBrowsingChanged() { + MOZ_ASSERT(!mIsBeingDestroyed); + + if (mAffectPrivateSessionLifetime) { + if (UsePrivateBrowsing()) { + IncreasePrivateDocShellCount(); + } else { + DecreasePrivateDocShellCount(); + } + } + + nsTObserverArray<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::SetAffectPrivateSessionLifetime(bool aAffectLifetime) { + MOZ_ASSERT(!mIsBeingDestroyed); + + bool change = aAffectLifetime != mAffectPrivateSessionLifetime; + if (change && UsePrivateBrowsing()) { + if (aAffectLifetime) { + IncreasePrivateDocShellCount(); + } else { + DecreasePrivateDocShellCount(); + } + } + mAffectPrivateSessionLifetime = aAffectLifetime; + + for (auto* child : mChildList.ForwardRange()) { + nsCOMPtr<nsIDocShell> shell = do_QueryObject(child); + if (shell) { + shell->SetAffectPrivateSessionLifetime(aAffectLifetime); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAffectPrivateSessionLifetime(bool* aAffectLifetime) { + *aAffectLifetime = mAffectPrivateSessionLifetime; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::AddWeakPrivacyTransitionObserver( + nsIPrivacyTransitionObserver* aObserver) { + nsWeakPtr weakObs = do_GetWeakReference(aObserver); + if (!weakObs) { + return NS_ERROR_NOT_AVAILABLE; + } + mPrivacyObservers.AppendElement(weakObs); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::AddWeakReflowObserver(nsIReflowObserver* aObserver) { + nsWeakPtr weakObs = do_GetWeakReference(aObserver); + if (!weakObs) { + return NS_ERROR_FAILURE; + } + mReflowObservers.AppendElement(weakObs); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::RemoveWeakReflowObserver(nsIReflowObserver* aObserver) { + nsWeakPtr obs = do_GetWeakReference(aObserver); + return mReflowObservers.RemoveElement(obs) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsDocShell::NotifyReflowObservers(bool aInterruptible, + DOMHighResTimeStamp aStart, + DOMHighResTimeStamp aEnd) { + nsTObserverArray<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 (!mContentViewer) { + return NS_OK; + } + Document* doc = mContentViewer->GetDocument(); + if (!doc) { + return NS_OK; + } + if (doc->WillIgnoreCharsetOverride()) { + return NS_OK; + } + + *aMayEnableCharacterEncodingMenu = true; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetCharsetAutodetected(bool* aCharsetAutodetected) { + *aCharsetAutodetected = false; + if (!mContentViewer) { + return NS_OK; + } + Document* doc = mContentViewer->GetDocument(); + if (!doc) { + return NS_OK; + } + int32_t source = doc->GetDocumentCharacterSetSource(); + + if ((source >= kCharsetFromInitialAutoDetectionASCII && + source <= kCharsetFromFinalAutoDetectionFile) || + source == kCharsetFromUserForcedJapaneseAutoDetection || + source == kCharsetFromPendingUserForcedAutoDetection || + source == kCharsetFromInitialUserForcedAutoDetection || + source == kCharsetFromFinalUserForcedAutoDetection) { + *aCharsetAutodetected = true; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAllDocShellsInSubtree(int32_t aItemType, + DocShellEnumeratorDirection aDirection, + nsTArray<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; +} + +void nsDocShell::TriggerParentCheckDocShellIsEmpty() { + if (RefPtr<nsDocShell> parent = GetInProcessParentDocshell()) { + parent->DocLoaderIsEmpty(true); + } + if (GetBrowsingContext()->IsContentSubframe() && + !GetBrowsingContext()->GetParent()->IsInProcess()) { + if (BrowserChild* browserChild = BrowserChild::GetFrom(this)) { + mozilla::Unused << browserChild->SendMaybeFireEmbedderLoadEvents( + EmbedderElementEventType::NoEvent); + } + } +} + +nsresult nsDocShell::HistoryEntryRemoved(int32_t aIndex) { + // These indices are used for fastback cache eviction, to determine + // which session history entries are candidates for content viewer + // eviction. We need to adjust by the number of entries that we + // just purged from history, so that we look at the right session history + // entries during eviction. + if (aIndex == mPreviousEntryIndex) { + mPreviousEntryIndex = -1; + } else if (aIndex < mPreviousEntryIndex) { + --mPreviousEntryIndex; + } + if (mLoadedEntryIndex == aIndex) { + mLoadedEntryIndex = 0; + } else if (aIndex < mLoadedEntryIndex) { + --mLoadedEntryIndex; + } + + for (auto* child : mChildList.ForwardRange()) { + nsCOMPtr<nsIDocShell> shell = do_QueryObject(child); + if (shell) { + static_cast<nsDocShell*>(shell.get())->HistoryEntryRemoved(aIndex); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetRecordProfileTimelineMarkers(bool aValue) { + bool currentValue = nsIDocShell::GetRecordProfileTimelineMarkers(); + if (currentValue == aValue) { + return NS_OK; + } + + RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); + if (!timelines) { + return NS_OK; + } + + if (aValue) { + MOZ_ASSERT(!timelines->HasConsumer(this)); + timelines->AddConsumer(this); + MOZ_ASSERT(timelines->HasConsumer(this)); + UseEntryScriptProfiling(); + } else { + MOZ_ASSERT(timelines->HasConsumer(this)); + timelines->RemoveConsumer(this); + MOZ_ASSERT(!timelines->HasConsumer(this)); + UnuseEntryScriptProfiling(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetRecordProfileTimelineMarkers(bool* aValue) { + *aValue = !!mObserved; + return NS_OK; +} + +nsresult nsDocShell::PopProfileTimelineMarkers( + JSContext* aCx, JS::MutableHandle<JS::Value> aOut) { + RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); + if (!timelines) { + return NS_OK; + } + + nsTArray<dom::ProfileTimelineMarker> store; + SequenceRooter<dom::ProfileTimelineMarker> rooter(aCx, &store); + + timelines->PopMarkers(this, aCx, store); + + if (!ToJSValue(aCx, store, aOut)) { + JS_ClearPendingException(aCx); + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +nsresult nsDocShell::Now(DOMHighResTimeStamp* aWhen) { + *aWhen = (TimeStamp::Now() - TimeStamp::ProcessCreation()).ToMilliseconds(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetWindowDraggingAllowed(bool aValue) { + RefPtr<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 (mContentViewer) { + Document* doc = mContentViewer->GetDocument(); + if (doc) { + return doc->GetChannel(); + } + } + return nullptr; +} + +NS_IMETHODIMP +nsDocShell::AddWeakScrollObserver(nsIScrollObserver* aObserver) { + nsWeakPtr weakObs = do_GetWeakReference(aObserver); + if (!weakObs) { + return NS_ERROR_FAILURE; + } + mScrollObservers.AppendElement(weakObs); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::RemoveWeakScrollObserver(nsIScrollObserver* aObserver) { + nsWeakPtr obs = do_GetWeakReference(aObserver); + return mScrollObservers.RemoveElement(obs) ? NS_OK : NS_ERROR_FAILURE; +} + +void nsDocShell::NotifyAsyncPanZoomStarted() { + nsTObserverArray<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() { + RefPtr<nsGlobalWindowInner> win = + mScriptGlobal ? mScriptGlobal->GetCurrentInnerWindowInternal() : nullptr; + if (win) { + Navigator* navigator = win->Navigator(); + if (navigator) { + navigator->ClearPlatformCache(); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::ClearCachedUserAgent() { + RefPtr<nsGlobalWindowInner> win = + mScriptGlobal ? mScriptGlobal->GetCurrentInnerWindowInternal() : nullptr; + if (win) { + Navigator* navigator = win->Navigator(); + if (navigator) { + navigator->ClearUserAgentCache(); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetMetaViewportOverride( + MetaViewportOverride* aMetaViewportOverride) { + NS_ENSURE_ARG_POINTER(aMetaViewportOverride); + + *aMetaViewportOverride = mMetaViewportOverride; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetMetaViewportOverride( + MetaViewportOverride aMetaViewportOverride) { + // We don't have a way to verify this coming from Javascript, so this check is + // still needed. + if (!(aMetaViewportOverride == META_VIEWPORT_OVERRIDE_NONE || + aMetaViewportOverride == META_VIEWPORT_OVERRIDE_ENABLED || + aMetaViewportOverride == META_VIEWPORT_OVERRIDE_DISABLED)) { + return NS_ERROR_INVALID_ARG; + } + + mMetaViewportOverride = aMetaViewportOverride; + + // Inform our presShell that it needs to re-check its need for a viewport + // override. + if (RefPtr<PresShell> 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->GetCurrentInnerWindowInternal() && + mScriptGlobal->GetCurrentInnerWindowInternal()->GetExtantDoc()) { + MOZ_DIAGNOSTIC_ASSERT(mScriptGlobal->GetCurrentInnerWindowInternal() + ->GetClientInfo() + .isSome()); + MOZ_DIAGNOSTIC_ASSERT(!mInitialClientSource); + return; + } + + // Don't recreate the initial client source. We call this multiple times + // when DoChannelLoad() is called before CreateAboutBlankContentViewer. + if (mInitialClientSource) { + return; + } + + // Don't pre-allocate the client when we are sandboxed. The inherited + // principal does not take sandboxing into account. + // TODO: Refactor sandboxing principal code out so we can use it here. + if (!aPrincipal && mBrowsingContext->GetSandboxFlags()) { + return; + } + + nsIPrincipal* principal = + aPrincipal ? aPrincipal : GetInheritedPrincipal(false); + + // Sometimes there is no principal available when we are called from + // CreateAboutBlankContentViewer. For example, sometimes the principal + // is only extracted from the load context after the document is created + // in Document::ResetToURI(). Ideally we would do something similar + // here, but for now lets just avoid the issue by not preallocating the + // client. + if (!principal) { + return; + } + + nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow(); + if (!win) { + return; + } + + mInitialClientSource = ClientManager::CreateSource( + ClientType::Window, win->EventTargetFor(TaskCategory::Other), principal); + MOZ_DIAGNOSTIC_ASSERT(mInitialClientSource); + + // Mark the initial client as execution ready, but owned by the docshell. + // If the client is actually used this will cause ClientSource to force + // the creation of the initial about:blank by calling + // nsDocShell::GetDocument(). + mInitialClientSource->DocShellExecutionReady(this); + + // Next, check to see if the parent is controlled. + nsCOMPtr<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; + } + + nsGlobalWindowInner* innerWindow = + mScriptGlobal ? mScriptGlobal->GetCurrentInnerWindowInternal() : nullptr; + Document* doc = innerWindow ? innerWindow->GetExtantDoc() : nullptr; + + if (!doc || !doc->IsInitialDocument()) { + return Maybe<ClientInfo>(); + } + + return innerWindow->GetClientInfo(); +} + +void nsDocShell::RecomputeCanExecuteScripts() { + bool old = mCanExecuteScripts; + RefPtr<nsDocShell> parent = GetInProcessParentDocshell(); + + // If we have no tree owner, that means that we've been detached from the + // docshell tree (this is distinct from having no parent docshell, which + // is the case for root docshells). It would be nice to simply disallow + // script in detached docshells, but bug 986542 demonstrates that this + // behavior breaks at least one website. + // + // So instead, we use our previous value, unless mAllowJavascript has been + // explicitly set to false. + if (!mTreeOwner) { + mCanExecuteScripts = mCanExecuteScripts && mAllowJavascript; + // If scripting has been explicitly disabled on our docshell, we're done. + } else if (!mAllowJavascript) { + mCanExecuteScripts = false; + // If we have a parent, inherit. + } else if (parent) { + mCanExecuteScripts = parent->mCanExecuteScripts; + // Otherwise, we're the root of the tree, and we haven't explicitly disabled + // script. Allow. + } else { + mCanExecuteScripts = true; + } + + // Inform our active DOM window. + // + // This will pass the outer, which will be in the scope of the active inner. + if (mScriptGlobal && mScriptGlobal->GetGlobalJSObject()) { + xpc::Scriptability& scriptability = + xpc::Scriptability::Get(mScriptGlobal->GetGlobalJSObject()); + scriptability.SetDocShellAllowsScript(mCanExecuteScripts); + } + + // If our value has changed, our children might be affected. Recompute their + // value as well. + if (old != mCanExecuteScripts) { + for (auto* child : mChildList.ForwardRange()) { + static_cast<nsDocShell*>(child)->RecomputeCanExecuteScripts(); + } + } +} + +nsresult nsDocShell::SetDocLoaderParent(nsDocLoader* aParent) { + bool wasFrame = IsFrame(); + + nsresult rv = nsDocLoader::SetDocLoaderParent(aParent); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISupportsPriority> priorityGroup = do_QueryInterface(mLoadGroup); + if (wasFrame != IsFrame() && priorityGroup) { + priorityGroup->AdjustPriority(wasFrame ? -1 : 1); + } + + // Curse ambiguous nsISupports inheritance! + nsISupports* parent = GetAsSupports(aParent); + + // If parent is another docshell, we inherit all their flags for + // allowing plugins, scripting etc. + bool value; + nsCOMPtr<nsIDocShell> parentAsDocShell(do_QueryInterface(parent)); + + if (parentAsDocShell) { + if (mAllowJavascript && + NS_SUCCEEDED(parentAsDocShell->GetAllowJavascript(&value))) { + SetAllowJavascript(value); + } + if (mAllowMetaRedirects && + NS_SUCCEEDED(parentAsDocShell->GetAllowMetaRedirects(&value))) { + SetAllowMetaRedirects(value); + } + if (mAllowSubframes && + NS_SUCCEEDED(parentAsDocShell->GetAllowSubframes(&value))) { + SetAllowSubframes(value); + } + if (mAllowImages && + NS_SUCCEEDED(parentAsDocShell->GetAllowImages(&value))) { + SetAllowImages(value); + } + SetAllowMedia(parentAsDocShell->GetAllowMedia() && mAllowMedia); + if (mAllowWindowControl && + NS_SUCCEEDED(parentAsDocShell->GetAllowWindowControl(&value))) { + SetAllowWindowControl(value); + } + if (NS_FAILED(parentAsDocShell->GetAllowDNSPrefetch(&value))) { + value = false; + } + SetAllowDNSPrefetch(mAllowDNSPrefetch && value); + SetAffectPrivateSessionLifetime( + parentAsDocShell->GetAffectPrivateSessionLifetime()); + + // We don't need to inherit metaViewportOverride, because the viewport + // is only relevant for the outermost nsDocShell, not for any iframes + // like this that might be embedded within it. + } + + nsCOMPtr<nsIURIContentListener> parentURIListener(do_GetInterface(parent)); + if (parentURIListener) { + mContentListener->SetParentContentListener(parentURIListener); + } + + // Our parent has changed. Recompute scriptability. + RecomputeCanExecuteScripts(); + + // Inform windows when they're being removed from their parent. + if (!aParent) { + MaybeClearStorageAccessFlag(); + } + + return NS_OK; +} + +void nsDocShell::MaybeClearStorageAccessFlag() { + if (mScriptGlobal) { + // Tell our window that the parent has now changed. + mScriptGlobal->ParentWindowChanged(); + + // Tell all of our children about the change recursively as well. + for (auto* childDocLoader : mChildList.ForwardRange()) { + nsCOMPtr<nsIDocShell> child = do_QueryObject(childDocLoader); + if (child) { + static_cast<nsDocShell*>(child.get())->MaybeClearStorageAccessFlag(); + } + } + } +} + +void nsDocShell::MaybeRestoreWindowName() { + if (!StaticPrefs::privacy_window_name_update_enabled()) { + return; + } + + // We only restore window.name for the top-level content. + if (!mBrowsingContext->IsTopContent()) { + return; + } + + nsAutoString name; + + // Following implements https://html.spec.whatwg.org/#history-traversal: + // Step 4.4. Check if the loading entry has a name. + + if (mLSHE) { + mLSHE->GetName(name); + } + + if (mLoadingEntry) { + name = mLoadingEntry->mInfo.GetName(); + } + + if (name.IsEmpty()) { + return; + } + + // Step 4.4.1. Set the name to the browsing context. + Unused << mBrowsingContext->SetName(name); + + // Step 4.4.2. Clear the name of all entries that are contiguous and + // same-origin with the loading entry. + if (mLSHE) { + nsSHistory::WalkContiguousEntries( + mLSHE, [](nsISHEntry* aEntry) { aEntry->SetName(EmptyString()); }); + } + + if (mLoadingEntry) { + // Clear the name of the session entry in the child side. For parent side, + // the clearing will be done when we commit the history to the parent. + mLoadingEntry->mInfo.SetName(EmptyString()); + } +} + +void nsDocShell::StoreWindowNameToSHEntries() { + MOZ_ASSERT(mBrowsingContext->IsTopContent()); + + nsAutoString name; + mBrowsingContext->GetName(name); + + if (mOSHE) { + nsSHistory::WalkContiguousEntries( + mOSHE, [&](nsISHEntry* aEntry) { aEntry->SetName(name); }); + } + + if (mozilla::SessionHistoryInParent()) { + if (XRE_IsParentProcess()) { + SessionHistoryEntry* entry = + mBrowsingContext->Canonical()->GetActiveSessionHistoryEntry(); + if (entry) { + nsSHistory::WalkContiguousEntries( + entry, [&](nsISHEntry* aEntry) { aEntry->SetName(name); }); + } + } else { + // Ask parent process to store the name in entries. + mozilla::Unused + << ContentChild::GetSingleton() + ->SendSessionHistoryEntryStoreWindowNameInContiguousEntries( + mBrowsingContext, name); + } + } +} + +NS_IMETHODIMP +nsDocShell::GetInProcessSameTypeParent(nsIDocShellTreeItem** aParent) { + if (BrowsingContext* parentBC = mBrowsingContext->GetParent()) { + *aParent = do_AddRef(parentBC->GetDocShell()).take(); + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetSameTypeInProcessParentIgnoreBrowserBoundaries( + nsIDocShell** aParent) { + NS_ENSURE_ARG_POINTER(aParent); + *aParent = nullptr; + + nsCOMPtr<nsIDocShellTreeItem> parent = + do_QueryInterface(GetAsSupports(mParent)); + if (!parent) { + return NS_OK; + } + + if (parent->ItemType() == mItemType) { + nsCOMPtr<nsIDocShell> parentDS = do_QueryInterface(parent); + parentDS.forget(aParent); + } + 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 (!IsFrame()) { + 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); + } + } + + // Our tree owner has changed. Recompute scriptability. + // + // Note that this is near-redundant with the recomputation in + // SetDocLoaderParent(), but not so for the root DocShell, where the call to + // SetTreeOwner() happens after the initial AddDocLoaderAsChildOfRoot(), + // and we never set another parent. Given that this is neither expensive nor + // performance-critical, let's be safe and unconditionally recompute this + // state whenever dependent state changes. + RecomputeCanExecuteScripts(); + + return NS_OK; +} + +void nsDocShell::SetChildOffset(int32_t aChildOffset) { + mChildOffset = aChildOffset; +} + +int32_t nsDocShell::GetChildOffset() { return mChildOffset; } + +NS_IMETHODIMP +nsDocShell::GetHistoryID(nsID& aID) { + aID = mBrowsingContext->GetHistoryID(); + return NS_OK; +} + +const nsID& nsDocShell::HistoryID() { return mBrowsingContext->GetHistoryID(); } + +NS_IMETHODIMP +nsDocShell::GetIsInUnload(bool* aIsInUnload) { + *aIsInUnload = mFiredUnloadEvent; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetInProcessChildCount(int32_t* aChildCount) { + NS_ENSURE_ARG_POINTER(aChildCount); + *aChildCount = mChildList.Length(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::AddChild(nsIDocShellTreeItem* aChild) { + NS_ENSURE_ARG_POINTER(aChild); + + RefPtr<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"); + + nsCOMPtr<nsIDocShell> childDocShell = do_QueryInterface(aChild); + bool dynamic = nsDocShell::Cast(childDocShell)->GetCreatedDynamically(); + if (!dynamic) { + nsCOMPtr<nsISHEntry> currentSH; + bool oshe = false; + GetCurrentSHEntry(getter_AddRefs(currentSH), &oshe); + if (currentSH) { + currentSH->HasDynamicallyAddedChild(&dynamic); + } + } + childDocShell->SetChildOffset(dynamic ? -1 : mChildList.Length() - 1); + + /* Set the child's global history if the parent has one */ + if (mBrowsingContext->GetUseGlobalHistory()) { + // childDocShell->SetUseGlobalHistory(true); + // this should be set through BC inherit + MOZ_ASSERT(nsDocShell::Cast(childDocShell) + ->mBrowsingContext->GetUseGlobalHistory()); + } + + if (aChild->ItemType() != mItemType) { + return NS_OK; + } + + aChild->SetTreeOwner(mTreeOwner); + + nsCOMPtr<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 (!mContentViewer) { + return NS_OK; + } + Document* doc = mContentViewer->GetDocument(); + if (!doc) { + return NS_OK; + } + + const Encoding* parentCS = doc->GetDocumentCharacterSet(); + int32_t charsetSource = doc->GetDocumentCharacterSetSource(); + // set the child's parentCharset + childAsDocShell->SetParentCharset(parentCS, charsetSource, + doc->NodePrincipal()); + + // printf("### 1 >>> Adding child. Parent CS = %s. ItemType = %d.\n", + // NS_LossyConvertUTF16toASCII(parentCS).get(), mItemType); + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::RemoveChild(nsIDocShellTreeItem* aChild) { + NS_ENSURE_ARG_POINTER(aChild); + + RefPtr<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(EnsureContentViewer(), nullptr); + return mContentViewer->GetDocument(); +} + +Document* nsDocShell::GetExtantDocument() { + return mContentViewer ? mContentViewer->GetDocument() : nullptr; +} + +nsPIDOMWindowOuter* nsDocShell::GetWindow() { + if (NS_FAILED(EnsureScriptEnvironment())) { + return nullptr; + } + return mScriptGlobal; +} + +NS_IMETHODIMP +nsDocShell::GetDomWindow(mozIDOMWindowProxy** aWindow) { + NS_ENSURE_ARG_POINTER(aWindow); + + nsresult rv = EnsureScriptEnvironment(); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<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; +} + +NS_IMETHODIMP +nsDocShell::SetDeviceSizeIsPageSize(bool aValue) { + if (mDeviceSizeIsPageSize != aValue) { + mDeviceSizeIsPageSize = aValue; + RefPtr<nsPresContext> presContext = GetPresContext(); + if (presContext) { + presContext->MediaFeatureValuesChanged( + {MediaFeatureChangeReason::DeviceSizeIsPageSizeChange}, + MediaFeatureChangePropagation::JustThisDocument); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetDeviceSizeIsPageSize(bool* aValue) { + *aValue = mDeviceSizeIsPageSize; + return NS_OK; +} + +void nsDocShell::ClearFrameHistory(nsISHEntry* aEntry) { + MOZ_ASSERT(!mozilla::SessionHistoryInParent()); + RefPtr<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 (!mContentViewer) { + return true; + } + bool firingBeforeUnload; + mContentViewer->GetBeforeUnloadFiring(&firingBeforeUnload); + return !firingBeforeUnload; +} + +//***************************************************************************** +// nsDocShell::nsIWebNavigation +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::GetCanGoBack(bool* aCanGoBack) { + *aCanGoBack = false; + if (!IsNavigationAllowed(false)) { + return NS_OK; // JS may not handle returning of an error code + } + RefPtr<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) { + 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, rv); + return rv.StealNSResult(); +} + +NS_IMETHODIMP +nsDocShell::GoForward(bool aRequireUserInteraction) { + if (!IsNavigationAllowed()) { + return NS_OK; // JS may not handle returning of an error code + } + + auto cleanupIsNavigating = MakeScopeExit([&]() { mIsNavigating = false; }); + mIsNavigating = true; + + RefPtr<ChildSHistory> rootSH = GetRootSessionHistory(); + NS_ENSURE_TRUE(rootSH, NS_ERROR_FAILURE); + ErrorResult rv; + rootSH->Go(1, aRequireUserInteraction, rv); + return rv.StealNSResult(); +} + +// XXX(nika): We may want to stop exposing this API in the child process? Going +// to a specific index from multiple different processes could definitely race. +NS_IMETHODIMP +nsDocShell::GotoIndex(int32_t aIndex) { + if (!IsNavigationAllowed()) { + return NS_OK; // JS may not handle returning of an error code + } + + auto cleanupIsNavigating = MakeScopeExit([&]() { mIsNavigating = false; }); + mIsNavigating = true; + + RefPtr<ChildSHistory> rootSH = GetRootSessionHistory(); + NS_ENSURE_TRUE(rootSH, NS_ERROR_FAILURE); + + ErrorResult rv; + rootSH->GotoIndex(aIndex, aIndex - rootSH->Index(), false, rv); + return rv.StealNSResult(); +} + +nsresult nsDocShell::LoadURI(const nsAString& aURI, + const LoadURIOptions& aLoadURIOptions) { + if (!IsNavigationAllowed()) { + return NS_OK; // JS may not handle returning of an error code + } + + RefPtr<nsDocShellLoadState> loadState; + nsresult rv = nsDocShellLoadState::CreateFromLoadURIOptions( + mBrowsingContext, aURI, aLoadURIOptions, getter_AddRefs(loadState)); + + uint32_t loadFlags = aLoadURIOptions.mLoadFlags; + if (NS_ERROR_MALFORMED_URI == rv) { + if (DisplayLoadError(rv, nullptr, PromiseFlatString(aURI).get(), nullptr) && + (loadFlags & LOAD_FLAGS_ERROR_LOAD_CHANGES_RV) != 0) { + return NS_ERROR_LOAD_SHOWED_ERRORPAGE; + } + } + + if (NS_FAILED(rv) || !loadState) { + return NS_ERROR_FAILURE; + } + + return LoadURI(loadState, true); +} + +NS_IMETHODIMP +nsDocShell::LoadURIFromScript(const nsAString& aURI, + JS::Handle<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); +} + +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) { + 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; + 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_ENSURE_ARG_POINTER(aURI); + // Get the host + nsAutoCString host; + aURI->GetHost(host); + CopyUTF8toUTF16(host, *formatStrs.AppendElement()); + error = "netTimeout"; + } else if (NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION == aError || + NS_ERROR_CSP_FORM_ACTION_VIOLATION == aError || + NS_ERROR_CSP_NAVIGATE_TO_VIOLATION == aError) { + // CSP error + cssClass.AssignLiteral("neterror"); + error = "cspBlocked"; + } else if (NS_ERROR_XFO_VIOLATION == aError) { + // XFO error + cssClass.AssignLiteral("neterror"); + error = "xfoBlocked"; + } else if (NS_ERROR_GET_MODULE(aError) == NS_ERROR_MODULE_SECURITY) { + nsCOMPtr<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<nsISupports> securityInfo; + nsCOMPtr<nsITransportSecurityInfo> tsi; + if (aFailedChannel) { + aFailedChannel->GetSecurityInfo(getter_AddRefs(securityInfo)); + } + tsi = do_QueryInterface(securityInfo); + if (tsi) { + uint32_t securityState; + tsi->GetSecurityState(&securityState); + if (securityState & nsIWebProgressListener::STATE_USES_SSL_3) { + error = "sslv3Used"; + addHostPort = true; + } else if (securityState & + nsIWebProgressListener::STATE_USES_WEAK_CRYPTO) { + error = "weakCryptoUsed"; + addHostPort = true; + } + } else { + // No channel, let's obtain the generic error message + if (nsserr) { + nsserr->GetErrorMessage(aError, messageStr); + } + } + // We don't have a message string here anymore but DisplayLoadError + // requires a non-empty messageStr. + messageStr.Truncate(); + messageStr.AssignLiteral(u" "); + if (errorClass == nsINSSErrorsService::ERROR_CLASS_BAD_CERT) { + error = "nssBadCert"; + + // If this is an HTTP Strict Transport Security host or a pinned host + // and the certificate is bad, don't allow overrides (RFC 6797 section + // 12.1). + uint32_t flags = + UsePrivateBrowsing() ? nsISocketProvider::NO_PERMANENT_STORAGE : 0; + bool isStsHost = false; + bool isPinnedHost = false; + if (XRE_IsParentProcess()) { + nsCOMPtr<nsISiteSecurityService> sss = + do_GetService(NS_SSSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, aURI, flags, + GetOriginAttributes(), nullptr, nullptr, + &isStsHost); + NS_ENSURE_SUCCESS(rv, rv); + rv = sss->IsSecureURI(nsISiteSecurityService::STATIC_PINNING, aURI, + flags, GetOriginAttributes(), nullptr, nullptr, + &isPinnedHost); + NS_ENSURE_SUCCESS(rv, rv); + } else { + mozilla::dom::ContentChild* cc = + mozilla::dom::ContentChild::GetSingleton(); + cc->SendIsSecureURI(nsISiteSecurityService::HEADER_HSTS, aURI, flags, + GetOriginAttributes(), &isStsHost); + cc->SendIsSecureURI(nsISiteSecurityService::STATIC_PINNING, aURI, flags, + GetOriginAttributes(), &isPinnedHost); + } + + if (Preferences::GetBool("browser.xul.error_pages.expert_bad_cert", + false)) { + cssClass.AssignLiteral("expertBadCert"); + } + + // HSTS/pinning takes precedence over the expert bad cert pref. We + // never want to show the "Add Exception" button for these sites. + // In the future we should differentiate between an HSTS host and a + // pinned host and display a more informative message to the user. + if (isStsHost || isPinnedHost) { + cssClass.AssignLiteral("badStsCert"); + } + + // See if an alternate cert error page is registered + nsAutoCString alternateErrorPage; + nsresult rv = Preferences::GetCString( + "security.alternate_certificate_error_page", alternateErrorPage); + if (NS_SUCCEEDED(rv)) { + errorPage.Assign(alternateErrorPage); + } + } else { + error = "nssFailure2"; + } + } else if (NS_ERROR_PHISHING_URI == aError || + NS_ERROR_MALWARE_URI == aError || + NS_ERROR_UNWANTED_URI == aError || + NS_ERROR_HARMFUL_URI == aError) { + nsAutoCString host; + aURI->GetHost(host); + CopyUTF8toUTF16(host, *formatStrs.AppendElement()); + + // Malware and phishing detectors may want to use an alternate error + // page, but if the pref's not set, we'll fall back on the standard page + nsAutoCString alternateErrorPage; + nsresult rv = Preferences::GetCString("urlclassifier.alternate_error_page", + alternateErrorPage); + if (NS_SUCCEEDED(rv)) { + errorPage.Assign(alternateErrorPage); + } + + if (NS_ERROR_PHISHING_URI == aError) { + error = "deceptiveBlocked"; + } else if (NS_ERROR_MALWARE_URI == aError) { + error = "malwareBlocked"; + } else if (NS_ERROR_UNWANTED_URI == aError) { + error = "unwantedBlocked"; + } else if (NS_ERROR_HARMFUL_URI == aError) { + error = "harmfulBlocked"; + } + + cssClass.AssignLiteral("blacklist"); + } else if (NS_ERROR_CONTENT_CRASHED == aError) { + errorPage.AssignLiteral("tabcrashed"); + error = "tabcrashed"; + + RefPtr<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_REMOTE_XUL: + error = "remoteXUL"; + break; + case NS_ERROR_UNSAFE_CONTENT_TYPE: + // Channel refused to load from an unrecognized content type. + error = "unsafeContentType"; + break; + case NS_ERROR_CORRUPTED_CONTENT: + // Broken Content Detected. e.g. Content-MD5 check failure. + error = "corruptedContentErrorv2"; + break; + case NS_ERROR_INTERCEPTION_FAILED: + // ServiceWorker intercepted request, but something went wrong. + error = "corruptedContentErrorv2"; + break; + case NS_ERROR_NET_INADEQUATE_SECURITY: + // Server negotiated bad TLS for HTTP/2. + error = "inadequateSecurityError"; + addHostPort = true; + break; + case NS_ERROR_BLOCKED_BY_POLICY: + // Page blocked by policy + error = "blockedByPolicy"; + break; + case NS_ERROR_NET_HTTP2_SENT_GOAWAY: + case NS_ERROR_NET_HTTP3_PROTOCOL_ERROR: + // HTTP/2 or HTTP/3 stack detected a protocol error + error = "networkProtocolError"; + break; + + default: + break; + } + } + + // If the HTTPS-Only Mode upgraded this request and the upgrade might have + // caused this error, we replace the error-page with about:httpsonlyerror + if (nsHTTPSOnlyUtils::CouldBeHttpsOnlyError(aFailedChannel, aError)) { + errorPage.AssignLiteral("httpsonlyerror"); + } + + if (nsCOMPtr<nsILoadURIDelegate> loadURIDelegate = GetLoadURIDelegate()) { + nsCOMPtr<nsIURI> errorPageURI; + rv = loadURIDelegate->HandleLoadError(aURI, aError, + NS_ERROR_GET_MODULE(aError), + getter_AddRefs(errorPageURI)); + // If the docshell is going away there's no point in showing an error page. + if (NS_FAILED(rv) || mIsBeingDestroyed) { + *aDisplayedErrorPage = false; + return NS_OK; + } + + if (errorPageURI) { + *aDisplayedErrorPage = + NS_SUCCEEDED(LoadErrorPage(errorPageURI, aURI, aFailedChannel)); + return NS_OK; + } + } + + // Test if the error should be displayed + if (!error) { + return NS_OK; + } + + if (!errorDescriptionID) { + errorDescriptionID = error; + } + + Telemetry::AccumulateCategoricalKeyed( + IsFrame() ? "frame"_ns : "top"_ns, + mozilla::dom::LoadErrorToTelemetryLabel(aError)); + + // Test if the error needs to be formatted + if (!messageStr.IsEmpty()) { + // already obtained message + } else { + if (addHostPort) { + // Build up the host:port string. + nsAutoCString hostport; + if (aURI) { + aURI->GetHostPort(hostport); + } else { + hostport.Assign('?'); + } + CopyUTF8toUTF16(hostport, *formatStrs.AppendElement()); + } + + nsAutoCString spec; + rv = NS_ERROR_NOT_AVAILABLE; + auto& nextFormatStr = *formatStrs.AppendElement(); + if (aURI) { + // displaying "file://" is aesthetically unpleasing and could even be + // confusing to the user + if (SchemeIsFile(aURI)) { + aURI->GetPathQueryRef(spec); + } else { + aURI->GetSpec(spec); + } + + nsCOMPtr<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) { + MOZ_ASSERT(!mIsBeingDestroyed); + +#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"); + } + + // netError.xhtml's getDescription only handles the "d" parameter at the + // end of the URL, so append it last. + errorPageUrl.AppendLiteral("&d="); + errorPageUrl.AppendASCII(escapedDescription.get()); + + nsCOMPtr<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->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()) { + RefPtr<nsDocShell> docShell(this); + nsCOMPtr<nsIContentViewer> cv(mContentViewer); + + bool okToUnload = true; + MOZ_TRY(cv->PermitUnload(&okToUnload)); + if (!okToUnload) { + return NS_OK; + } + + RefPtr<Document> doc(GetDocument()); + RefPtr<BrowsingContext> browsingContext(mBrowsingContext); + nsCOMPtr<nsIURI> currentURI(mCurrentURI); + nsCOMPtr<nsIReferrerInfo> referrerInfo(mReferrerInfo); + + ContentChild::GetSingleton()->SendNotifyOnHistoryReload( + mBrowsingContext, forceReload, + [docShell, doc, loadType, browsingContext, currentURI, referrerInfo]( + Tuple<bool, Maybe<RefPtr<nsDocShellLoadState>>, Maybe<bool>>&& + aResult) { + bool canReload; + Maybe<RefPtr<nsDocShellLoadState>> loadState; + Maybe<bool> reloadingActiveEntry; + + Tie(canReload, loadState, reloadingActiveEntry) = aResult; + + if (!canReload) { + return; + } + + if (loadState.isSome()) { + MOZ_LOG( + gSHLog, LogLevel::Debug, + ("nsDocShell %p Reload - LoadHistoryEntry", docShell.get())); + loadState.ref()->SetNotifiedBeforeUnloadListeners(true); + docShell->LoadHistoryEntry(loadState.ref(), loadType, + reloadingActiveEntry.ref()); + } else { + MOZ_LOG(gSHLog, LogLevel::Debug, + ("nsDocShell %p ReloadDocument", docShell.get())); + ReloadDocument(docShell, doc, loadType, browsingContext, + currentURI, referrerInfo, + /* aNotifiedBeforeUnloadListeners */ true); + } + }, + [](mozilla::ipc::ResponseRejectReason) {}); + } else { + // Parent process + bool canReload = false; + Maybe<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)); + ReloadDocument(this, GetDocument(), loadType, mBrowsingContext, + mCurrentURI, mReferrerInfo); + } + } + } + return NS_OK; + } + + bool canReload = true; + if (rootSH) { + rootSH->LegacySHistory()->NotifyOnHistoryReload(&canReload); + } + + if (!canReload) { + return NS_OK; + } + + /* If you change this part of code, make sure bug 45297 does not re-occur */ + if (mOSHE) { + return LoadHistoryEntry(mOSHE, loadType); + } + + if (mLSHE) { // In case a reload happened before the current load is done + return LoadHistoryEntry(mLSHE, loadType); + } + + return ReloadDocument(this, GetDocument(), loadType, mBrowsingContext, + mCurrentURI, mReferrerInfo); +} + +/* static */ +nsresult nsDocShell::ReloadDocument(nsDocShell* aDocShell, Document* aDocument, + uint32_t aLoadType, + BrowsingContext* aBrowsingContext, + nsIURI* aCurrentURI, + nsIReferrerInfo* aReferrerInfo, + bool aNotifiedBeforeUnloadListeners) { + if (!aDocument) { + return NS_OK; + } + + // Do not inherit owner from document + uint32_t flags = INTERNAL_LOAD_FLAGS_NONE; + nsAutoString srcdoc; + nsIURI* baseURI = nullptr; + nsCOMPtr<nsIURI> originalURI; + nsCOMPtr<nsIURI> resultPrincipalURI; + bool loadReplace = false; + + nsIPrincipal* triggeringPrincipal = aDocument->NodePrincipal(); + nsCOMPtr<nsIContentSecurityPolicy> csp = aDocument->GetCsp(); + uint32_t triggeringSandboxFlags = aDocument->GetSandboxFlags(); + + nsAutoString contentTypeHint; + aDocument->GetContentType(contentTypeHint); + + if (aDocument->IsSrcdocDocument()) { + aDocument->GetSrcdocData(srcdoc); + flags |= INTERNAL_LOAD_FLAGS_IS_SRCDOC; + baseURI = aDocument->GetBaseURI(); + } else { + srcdoc = VoidString(); + } + nsCOMPtr<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->SetPrincipalToInherit(triggeringPrincipal); + loadState->SetCsp(csp); + loadState->SetLoadFlags(flags); + loadState->SetTypeHint(NS_ConvertUTF16toUTF8(contentTypeHint)); + loadState->SetLoadType(aLoadType); + loadState->SetFirstParty(true); + loadState->SetSrcdocData(srcdoc); + loadState->SetSourceBrowsingContext(aBrowsingContext); + loadState->SetBaseURI(baseURI); + loadState->SetHasValidUserGestureActivation( + context && context->HasValidTransientUserGestureActivation()); + loadState->SetNotifiedBeforeUnloadListeners(aNotifiedBeforeUnloadListeners); + return aDocShell->InternalLoad(loadState); +} + +NS_IMETHODIMP +nsDocShell::Stop(uint32_t aStopFlags) { + // Revoke any pending event related to content viewer restoration + mRestorePresentationEvent.Revoke(); + + if (mLoadType == LOAD_ERROR_PAGE) { + if (mLSHE) { + // Since error page loads never unset mLSHE, do so now + SetHistoryEntryAndUpdateBC(Some(nullptr), Some<nsISHEntry*>(mLSHE)); + } + mActiveEntryIsLoadingFromSessionHistory = false; + + mFailedChannel = nullptr; + mFailedURI = nullptr; + } + + if (nsIWebNavigation::STOP_CONTENT & aStopFlags) { + // Stop the document loading + if (mContentViewer) { + nsCOMPtr<nsIContentViewer> cv = mContentViewer; + cv->Stop(); + } + } + + if (nsIWebNavigation::STOP_NETWORK & aStopFlags) { + // Suspend any timers that were set for this loader. We'll clear + // them out for good in CreateContentViewer. + if (mRefreshURIList) { + SuspendRefreshURIs(); + mSavedRefreshURIList.swap(mRefreshURIList); + mRefreshURIList = nullptr; + } + + // XXXbz We could also pass |this| to nsIURILoader::Stop. That will + // just call Stop() on us as an nsIDocumentLoader... We need fewer + // redundant apis! + Stop(); + } + + for (auto* child : mChildList.ForwardRange()) { + nsCOMPtr<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(EnsureContentViewer(), NS_ERROR_FAILURE); + + RefPtr<Document> doc = mContentViewer->GetDocument(); + if (!doc) { + return NS_ERROR_NOT_AVAILABLE; + } + + doc.forget(aDocument); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetCurrentURI(nsIURI** aURI) { + NS_ENSURE_ARG_POINTER(aURI); + + nsCOMPtr<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 we don't record profile timeline markers anymore + SetRecordProfileTimelineMarkers(false); + + // Make sure to blow away our mLoadingURI just in case. No loads + // from inside this pagehide. + mLoadingURI = nullptr; + + // Fire unload event before we blow anything away. + (void)FirePageHideNotification(true); + + // Clear pointers to any detached nsEditorData that's lying + // around in shistory entries. Breaks cycle. See bug 430921. + if (mOSHE) { + mOSHE->SetEditorData(nullptr); + } + if (mLSHE) { + mLSHE->SetEditorData(nullptr); + } + + // Note: mContentListener can be null if Init() failed and we're being + // called from the destructor. + if (mContentListener) { + mContentListener->DropDocShellReference(); + mContentListener->SetParentContentListener(nullptr); + // Note that we do NOT set mContentListener to null here; that + // way if someone tries to do a load in us after this point + // the nsDSURIContentListener will block it. All of which + // means that we should do this before calling Stop(), of + // course. + } + + // Stop any URLs that are currently being loaded... + Stop(nsIWebNavigation::STOP_ALL); + + mEditorData = nullptr; + + // Save the state of the current document, before destroying the window. + // This is needed to capture the state of a frameset when the new document + // causes the frameset to be destroyed... + PersistLayoutHistoryState(); + + // Remove this docshell from its parent's child list + nsCOMPtr<nsIDocShellTreeItem> docShellParentAsItem = + do_QueryInterface(GetAsSupports(mParent)); + if (docShellParentAsItem) { + docShellParentAsItem->RemoveChild(this); + } + + if (mContentViewer) { + mContentViewer->Close(nullptr); + mContentViewer->Destroy(); + mContentViewer = nullptr; + } + + nsDocLoader::Destroy(); + + mParentWidget = nullptr; + mCurrentURI = nullptr; + + if (mScriptGlobal) { + mScriptGlobal->DetachFromDocShell(!mWillChangeProcess); + mScriptGlobal = nullptr; + } + + if (GetSessionHistory()) { + // We want to destroy these content viewers now rather than + // letting their destruction wait for the session history + // entries to get garbage collected. (Bug 488394) + GetSessionHistory()->EvictLocalContentViewers(); + } + + if (mWillChangeProcess) { + mBrowsingContext->PrepareForProcessChange(); + } + + SetTreeOwner(nullptr); + + mBrowserChild = nullptr; + + mChromeEventHandler = nullptr; + + // Cancel any timers that were set for this docshell; this is needed + // to break the cycle between us and the timers. + CancelRefreshURITimers(); + + if (UsePrivateBrowsing() && mAffectPrivateSessionLifetime) { + DecreasePrivateDocShellCount(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetUnscaledDevicePixelsPerCSSPixel(double* aScale) { + if (mParentWidget) { + *aScale = mParentWidget->GetDefaultScale().scale; + return NS_OK; + } + + nsCOMPtr<nsIBaseWindow> ownerWindow(do_QueryInterface(mTreeOwner)); + if (ownerWindow) { + return ownerWindow->GetUnscaledDevicePixelsPerCSSPixel(aScale); + } + + *aScale = 1.0; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetDevicePixelsPerDesktopPixel(double* aScale) { + if (mParentWidget) { + *aScale = mParentWidget->GetDesktopToDeviceScale().scale; + return NS_OK; + } + + nsCOMPtr<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 (mContentViewer) { + NS_ENSURE_SUCCESS(mContentViewer->Move(aX, aY), NS_ERROR_FAILURE); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetPositionDesktopPix(int32_t aX, int32_t aY) { + nsCOMPtr<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 mContentViewer + nsCOMPtr<nsIContentViewer> viewer = mContentViewer; + if (viewer) { + uint32_t cvflags = (aFlags & nsIBaseWindow::eDelayResize) + ? nsIContentViewer::eDelayResize + : 0; + // XXX Border figured in here or is that handled elsewhere? + nsresult rv = viewer->SetBoundsWithFlags(mBounds, cvflags); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetPositionAndSize(int32_t* aX, int32_t* aY, int32_t* aWidth, + int32_t* aHeight) { + if (mParentWidget) { + // ensure size is up-to-date if window has changed resolution + LayoutDeviceIntRect r = mParentWidget->GetClientBounds(); + SetPositionAndSize(mBounds.X(), mBounds.Y(), r.Width(), r.Height(), 0); + } + + // We should really consider just getting this information from + // our window instead of duplicating the storage and code... + if (aWidth || aHeight) { + // Caller wants to know our size; make sure to give them up to + // date information. + RefPtr<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::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 (!mContentViewer) { + return NS_OK; + } + + PresShell* presShell = GetPresShell(); + if (!presShell) { + return NS_OK; + } + + // get the view manager + nsViewManager* vm = presShell->GetViewManager(); + NS_ENSURE_TRUE(vm, NS_ERROR_FAILURE); + + // get the root view + nsView* view = vm->GetRootView(); // views are not ref counted + NS_ENSURE_TRUE(view, NS_ERROR_FAILURE); + + // if our root view is hidden, we are not visible + if (view->GetVisibility() == nsViewVisibility_kHide) { + return NS_OK; + } + + // otherwise, we must walk up the document and view trees checking + // for a hidden view, unless we're an off screen browser, which + // would make this test meaningless. + + RefPtr<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; + bool isDocShellOffScreen = false; + docShell->GetIsOffScreenBrowser(&isDocShellOffScreen); + if (frame && + !frame->IsVisibleConsideringAncestors( + nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY) && + !isDocShellOffScreen) { + return NS_OK; + } + + docShell = parentItem; + parentItem = docShell->GetInProcessParentDocshell(); + } + + nsCOMPtr<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. + return treeOwnerAsWin->GetVisibility(aVisibility); +} + +NS_IMETHODIMP +nsDocShell::SetIsOffScreenBrowser(bool aIsOffScreen) { + mIsOffScreenBrowser = aIsOffScreen; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetIsOffScreenBrowser(bool* aIsOffScreen) { + *aIsOffScreen = mIsOffScreenBrowser; + return NS_OK; +} + +void nsDocShell::ActivenessMaybeChanged() { + bool isActive = mBrowsingContext->IsActive(); + if (RefPtr<PresShell> presShell = GetPresShell()) { + presShell->SetIsActive(isActive); + } + + // 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. + uint16_t orientation = mBrowsingContext->GetOrientationLock(); + ScreenOrientation::UpdateActiveOrientationLock(orientation); + } + + doc->PostVisibilityUpdateEvent(); + } + } + + // Tell the nsDOMNavigationTiming about it + RefPtr<nsDOMNavigationTiming> timing = mTiming; + if (!timing && mContentViewer) { + if (Document* doc = mContentViewer->GetDocument()) { + timing = doc->GetNavigationTiming(); + } + } + if (timing) { + timing->NotifyDocShellStateChanged( + isActive ? nsDOMNavigationTiming::DocShellState::eActive + : nsDOMNavigationTiming::DocShellState::eInactive); + } + + // Restart or stop meta refresh timers if necessary + if (mDisableMetaRefreshWhenInactive) { + if (isActive) { + ResumeRefreshURIs(); + } else { + SuspendRefreshURIs(); + } + } + + if (InputTaskManager::CanSuspendInputEvent()) { + mBrowsingContext->Group()->UpdateInputTaskManagerIfNeeded(isActive); + } +} + +NS_IMETHODIMP +nsDocShell::SetIsAppTab(bool aIsAppTab) { + mIsAppTab = aIsAppTab; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetIsAppTab(bool* aIsAppTab) { + *aIsAppTab = mIsAppTab; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetDefaultLoadFlags(uint32_t aDefaultLoadFlags) { + if (!mWillChangeProcess) { + // Intentionally ignoring handling discarded browsing contexts. + Unused << mBrowsingContext->SetDefaultLoadFlags(aDefaultLoadFlags); + } else { + // Bug 1623565: DevTools tries to clean up defaultLoadFlags on + // shutdown. Sorry DevTools, your DocShell is in another process. + NS_WARNING("nsDocShell::SetDefaultLoadFlags called on Zombie DocShell"); + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetDefaultLoadFlags(uint32_t* aDefaultLoadFlags) { + *aDefaultLoadFlags = mBrowsingContext->GetDefaultLoadFlags(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetMixedContentChannel(nsIChannel* aMixedContentChannel) { +#ifdef DEBUG + // if the channel is non-null + if (aMixedContentChannel) { + NS_WARNING_ASSERTION(mBrowsingContext->IsTop(), + "Setting mMixedContentChannel on a docshell that is " + "not the root docshell"); + } +#endif + mMixedContentChannel = aMixedContentChannel; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetFailedChannel(nsIChannel** aFailedChannel) { + NS_ENSURE_ARG_POINTER(aFailedChannel); + Document* doc = GetDocument(); + if (!doc) { + *aFailedChannel = nullptr; + return NS_OK; + } + NS_IF_ADDREF(*aFailedChannel = doc->GetFailedChannel()); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetMixedContentChannel(nsIChannel** aMixedContentChannel) { + NS_ENSURE_ARG_POINTER(aMixedContentChannel); + NS_IF_ADDREF(*aMixedContentChannel = mMixedContentChannel); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetVisibility(bool aVisibility) { + // Show()/Hide() may change mContentViewer. + nsCOMPtr<nsIContentViewer> cv = mContentViewer; + if (!cv) { + return NS_OK; + } + if (aVisibility) { + cv->Show(); + } else { + cv->Hide(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetEnabled(bool* aEnabled) { + NS_ENSURE_ARG_POINTER(aEnabled); + *aEnabled = true; + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsDocShell::SetEnabled(bool aEnabled) { return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP +nsDocShell::SetFocus() { return NS_OK; } + +NS_IMETHODIMP +nsDocShell::GetMainWidget(nsIWidget** aMainWidget) { + // We don't create our own widget, so simply return the parent one. + return GetParentWidget(aMainWidget); +} + +NS_IMETHODIMP +nsDocShell::GetTitle(nsAString& aTitle) { + aTitle = mTitle; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetTitle(const nsAString& aTitle) { + // Avoid unnecessary updates of the title if the URI and the title haven't + // changed. + if (mTitleValidForCurrentURI && mTitle == aTitle) { + return NS_OK; + } + + // Store local title + mTitle = aTitle; + mTitleValidForCurrentURI = true; + + // When title is set on the top object it should then be passed to the + // tree owner. + if (mBrowsingContext->IsTop()) { + nsCOMPtr<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(); + } + + return NS_OK; +} + +void nsDocShell::SetTitleOnHistoryEntry() { + if (mOSHE) { + mOSHE->SetTitle(mTitle); + } + + if (mActiveEntry && mBrowsingContext) { + mActiveEntry->SetTitle(mTitle); + if (XRE_IsParentProcess()) { + SessionHistoryEntry* entry = + mBrowsingContext->Canonical()->GetActiveSessionHistoryEntry(); + if (entry) { + entry->SetTitle(mTitle); + } + } else { + mozilla::Unused + << ContentChild::GetSingleton()->SendSessionHistoryEntryTitle( + mBrowsingContext, mTitle); + } + } +} + +nsPoint nsDocShell::GetCurScrollPos() { + nsPoint scrollPos; + if (nsIScrollableFrame* sf = GetRootScrollFrame()) { + scrollPos = sf->GetVisualViewportOffset(); + } + return scrollPos; +} + +nsresult nsDocShell::SetCurScrollPosEx(int32_t aCurHorizontalPos, + int32_t aCurVerticalPos) { + nsIScrollableFrame* sf = GetRootScrollFrame(); + NS_ENSURE_TRUE(sf, NS_ERROR_FAILURE); + + ScrollMode scrollMode = + sf->IsSmoothScroll() ? ScrollMode::SmoothMsd : ScrollMode::Instant; + + nsPoint targetPos(aCurHorizontalPos, aCurVerticalPos); + sf->ScrollTo(targetPos, scrollMode); + + // Set the visual viewport offset as well. + + RefPtr<PresShell> presShell = GetPresShell(); + NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); + + nsPresContext* presContext = presShell->GetPresContext(); + NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE); + + // Only the root content document can have a distinct visual viewport offset. + if (!presContext->IsRootContentDocument()) { + return NS_OK; + } + + // Not on a platform with a distinct visual viewport - don't bother setting + // the visual viewport offset. + if (!presShell->IsVisualViewportSizeSet()) { + return NS_OK; + } + + presShell->ScrollToVisual(targetPos, layers::FrameMetrics::eMainThread, + scrollMode); + + return NS_OK; +} + +void nsDocShell::SetScrollbarPreference(mozilla::ScrollbarPreference aPref) { + if (mScrollbarPref == aPref) { + return; + } + mScrollbarPref = aPref; + auto* ps = GetPresShell(); + if (!ps) { + return; + } + nsIFrame* scrollFrame = ps->GetRootScrollFrame(); + if (!scrollFrame) { + return; + } + ps->FrameNeedsReflow(scrollFrame, IntrinsicDirty::StyleChange, + NS_FRAME_IS_DIRTY); +} + +//***************************************************************************** +// nsDocShell::nsIRefreshURI +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::RefreshURI(nsIURI* aURI, nsIPrincipal* aPrincipal, int32_t aDelay, + bool aRepeat, bool aMetaRefresh) { + MOZ_ASSERT(!mIsBeingDestroyed); + + NS_ENSURE_ARG(aURI); + + /* Check if Meta refresh/redirects are permitted. Some + * embedded applications may not want to do this. + * Must do this before sending out NOTIFY_REFRESH events + * because listeners may have side effects (e.g. displaying a + * button to manually trigger the refresh later). + */ + bool allowRedirects = true; + GetAllowMetaRedirects(&allowRedirects); + if (!allowRedirects) { + return NS_OK; + } + + // If any web progress listeners are listening for NOTIFY_REFRESH events, + // give them a chance to block this refresh. + bool sameURI; + nsresult rv = aURI->Equals(mCurrentURI, &sameURI); + if (NS_FAILED(rv)) { + sameURI = false; + } + if (!RefreshAttempted(this, aURI, aDelay, sameURI)) { + return NS_OK; + } + + nsCOMPtr<nsITimerCallback> refreshTimer = + new nsRefreshTimer(this, aURI, aPrincipal, aDelay, aRepeat, aMetaRefresh); + + BusyFlags busyFlags = GetBusyFlags(); + + if (!mRefreshURIList) { + mRefreshURIList = nsArray::Create(); + } + + if (busyFlags & BUSY_FLAGS_BUSY || + (!mBrowsingContext->IsActive() && mDisableMetaRefreshWhenInactive)) { + // We don't want to create the timer right now. Instead queue up the + // request and trigger the timer in EndPageLoad() or whenever we become + // active. + mRefreshURIList->AppendElement(refreshTimer); + } else { + // There is no page loading going on right now. Create the + // timer and fire it right away. + nsCOMPtr<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, + int32_t aDelay, bool aMetaRefresh, + nsITimer* aTimer) { + MOZ_ASSERT(aTimer, "Must have a timer here"); + + // Remove aTimer from mRefreshURIList if needed + if (mRefreshURIList) { + uint32_t n = 0; + mRefreshURIList->GetLength(&n); + + for (uint32_t i = 0; i < n; ++i) { + nsCOMPtr<nsITimer> timer = do_QueryElementAt(mRefreshURIList, i); + if (timer == aTimer) { + mRefreshURIList->RemoveElementAt(i); + break; + } + } + } + + return ForceRefreshURI(aURI, aPrincipal, aDelay, aMetaRefresh); +} + +NS_IMETHODIMP +nsDocShell::ForceRefreshURI(nsIURI* aURI, nsIPrincipal* aPrincipal, + int32_t aDelay, bool aMetaRefresh) { + NS_ENSURE_ARG(aURI); + + RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(aURI); + loadState->SetOriginalURI(mCurrentURI); + loadState->SetResultPrincipalURI(aURI); + loadState->SetResultPrincipalURIIsSome(true); + loadState->SetKeepResultPrincipalURIIfSet(true); + + // Set the triggering pricipal to aPrincipal if available, or current + // document's principal otherwise. + nsCOMPtr<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->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) && aMetaRefresh && + aDelay <= REFRESH_REDIRECT_TIMER) { + /* It is a META refresh based redirection within the threshold time + * we have in mind (15000 ms as defined by REFRESH_REDIRECT_TIMER). + * Pass a REPLACE flag to LoadURI(). + */ + loadState->SetLoadType(LOAD_NORMAL_REPLACE); + + /* For redirects we mimic HTTP, which passes the + * original referrer. + * We will pass in referrer but will not send to server + */ + if (mReferrerInfo) { + referrerInfo = static_cast<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; +} + +nsresult nsDocShell::SetupRefreshURIFromHeader(nsIURI* aBaseURI, + nsIPrincipal* aPrincipal, + uint64_t aInnerWindowID, + const nsACString& aHeader) { + if (mIsBeingDestroyed) { + return NS_ERROR_FAILURE; + } + + // Refresh headers are parsed with the following format in mind + // <META HTTP-EQUIV=REFRESH CONTENT="5; URL=http://uri"> + // By the time we are here, the following is true: + // header = "REFRESH" + // content = "5; URL=http://uri" // note the URL attribute is + // optional, if it is absent, the currently loaded url is used. + // Also note that the seconds and URL separator can be either + // a ';' or a ','. The ',' separator should be illegal but CNN + // is using it. + // + // We need to handle the following strings, where + // - X is a set of digits + // - URI is either a relative or absolute URI + // + // Note that URI should start with "url=" but we allow omission + // + // "" || ";" || "," + // empty string. use the currently loaded URI + // and refresh immediately. + // "X" || "X;" || "X," + // Refresh the currently loaded URI in X seconds. + // "X; URI" || "X, URI" + // Refresh using URI as the destination in X seconds. + // "URI" || "; URI" || ", URI" + // Refresh immediately using URI as the destination. + // + // Currently, anything immediately following the URI, if + // separated by any char in the set "'\"\t\r\n " will be + // ignored. So "10; url=go.html ; foo=bar" will work, + // and so will "10; url='go.html'; foo=bar". However, + // "10; url=go.html; foo=bar" will result in the uri + // "go.html;" since ';' and ',' are valid uri characters. + // + // Note that we need to remove any tokens wrapping the URI. + // These tokens currently include spaces, double and single + // quotes. + + // when done, seconds is 0 or the given number of seconds + // uriAttrib is empty or the URI specified + MOZ_ASSERT(aPrincipal); + + nsAutoCString uriAttrib; + int32_t seconds = 0; + bool specifiesSeconds = false; + + nsACString::const_iterator iter, tokenStart, doneIterating; + + aHeader.BeginReading(iter); + aHeader.EndReading(doneIterating); + + // skip leading whitespace + while (iter != doneIterating && nsCRT::IsAsciiSpace(*iter)) { + ++iter; + } + + tokenStart = iter; + + // skip leading + and - + if (iter != doneIterating && (*iter == '-' || *iter == '+')) { + ++iter; + } + + // parse number + while (iter != doneIterating && (*iter >= '0' && *iter <= '9')) { + seconds = seconds * 10 + (*iter - '0'); + specifiesSeconds = true; + ++iter; + } + + if (iter != doneIterating) { + // if we started with a '-', number is negative + if (*tokenStart == '-') { + seconds = -seconds; + } + + // skip to next ';' or ',' + nsACString::const_iterator iterAfterDigit = iter; + while (iter != doneIterating && !(*iter == ';' || *iter == ',')) { + if (specifiesSeconds) { + // Non-whitespace characters here mean that the string is + // malformed but tolerate sites that specify a decimal point, + // even though meta refresh only works on whole seconds. + if (iter == iterAfterDigit && !nsCRT::IsAsciiSpace(*iter) && + *iter != '.') { + // The characters between the seconds and the next + // section are just garbage! + // e.g. content="2a0z+,URL=http://www.mozilla.org/" + // Just ignore this redirect. + return NS_ERROR_FAILURE; + } else if (nsCRT::IsAsciiSpace(*iter)) { + // We've had at least one whitespace so tolerate the mistake + // and drop through. + // e.g. content="10 foo" + ++iter; + break; + } + } + ++iter; + } + + // skip any remaining whitespace + while (iter != doneIterating && nsCRT::IsAsciiSpace(*iter)) { + ++iter; + } + + // skip ';' or ',' + if (iter != doneIterating && (*iter == ';' || *iter == ',')) { + ++iter; + } + + // skip whitespace + while (iter != doneIterating && nsCRT::IsAsciiSpace(*iter)) { + ++iter; + } + } + + // possible start of URI + tokenStart = iter; + + // skip "url = " to real start of URI + if (iter != doneIterating && (*iter == 'u' || *iter == 'U')) { + ++iter; + if (iter != doneIterating && (*iter == 'r' || *iter == 'R')) { + ++iter; + if (iter != doneIterating && (*iter == 'l' || *iter == 'L')) { + ++iter; + + // skip whitespace + while (iter != doneIterating && nsCRT::IsAsciiSpace(*iter)) { + ++iter; + } + + if (iter != doneIterating && *iter == '=') { + ++iter; + + // skip whitespace + while (iter != doneIterating && nsCRT::IsAsciiSpace(*iter)) { + ++iter; + } + + // found real start of URI + tokenStart = iter; + } + } + } + } + + // skip a leading '"' or '\''. + + bool isQuotedURI = false; + if (tokenStart != doneIterating && + (*tokenStart == '"' || *tokenStart == '\'')) { + isQuotedURI = true; + ++tokenStart; + } + + // set iter to start of URI + iter = tokenStart; + + // tokenStart here points to the beginning of URI + + // grab the rest of the URI + while (iter != doneIterating) { + if (isQuotedURI && (*iter == '"' || *iter == '\'')) { + break; + } + ++iter; + } + + // move iter one back if the last character is a '"' or '\'' + if (iter != tokenStart && isQuotedURI) { + --iter; + if (!(*iter == '"' || *iter == '\'')) { + ++iter; + } + } + + // URI is whatever's contained from tokenStart to iter. + // note: if tokenStart == doneIterating, so is iter. + + nsresult rv = NS_OK; + + nsCOMPtr<nsIURI> uri; + bool specifiesURI = false; + if (tokenStart == iter) { + uri = aBaseURI; + } else { + uriAttrib = Substring(tokenStart, iter); + // NS_NewURI takes care of any whitespace surrounding the URL + rv = NS_NewURI(getter_AddRefs(uri), uriAttrib, nullptr, aBaseURI); + specifiesURI = true; + } + + // No URI or seconds were specified + if (!specifiesSeconds && !specifiesURI) { + // Do nothing because the alternative is to spin around in a refresh + // loop forever! + return NS_ERROR_FAILURE; + } + + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIScriptSecurityManager> securityManager( + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + rv = securityManager->CheckLoadURIWithPrincipal( + aPrincipal, uri, + nsIScriptSecurityManager::LOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENT, + aInnerWindowID); + + if (NS_SUCCEEDED(rv)) { + bool isjs = true; + rv = NS_URIChainHasFlags( + uri, nsIProtocolHandler::URI_OPENING_EXECUTES_SCRIPT, &isjs); + NS_ENSURE_SUCCESS(rv, rv); + + if (isjs) { + return NS_ERROR_FAILURE; + } + } + + if (NS_SUCCEEDED(rv)) { + // Since we can't travel back in time yet, just pretend + // negative numbers do nothing at all. + if (seconds < 0) { + return NS_ERROR_FAILURE; + } + + rv = RefreshURI(uri, aPrincipal, seconds * 1000, false, true); + } + } + } + return rv; +} + +NS_IMETHODIMP +nsDocShell::SetupRefreshURI(nsIChannel* aChannel) { + nsresult rv; + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel, &rv)); + if (NS_SUCCEEDED(rv)) { + nsAutoCString refreshHeader; + rv = httpChannel->GetResponseHeader("refresh"_ns, refreshHeader); + + if (!refreshHeader.IsEmpty()) { + nsCOMPtr<nsIScriptSecurityManager> secMan = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPrincipal> principal; + rv = secMan->GetChannelResultPrincipal(aChannel, + getter_AddRefs(principal)); + NS_ENSURE_SUCCESS(rv, rv); + + SetupReferrerInfoFromChannel(aChannel); + // We have no idea what window id to use for error reporting + // here, so just pass 0. + rv = SetupRefreshURIFromHeader(mCurrentURI, principal, 0, refreshHeader); + if (NS_SUCCEEDED(rv)) { + return NS_REFRESHURI_HEADER_FOUND; + } + } + } + return rv; +} + +static void DoCancelRefreshURITimers(nsIMutableArray* aTimerList) { + if (!aTimerList) { + return; + } + + uint32_t n = 0; + aTimerList->GetLength(&n); + + while (n) { + nsCOMPtr<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); + mRefreshURIList = nullptr; + mSavedRefreshURIList = nullptr; + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetRefreshPending(bool* aResult) { + if (!mRefreshURIList) { + *aResult = false; + return NS_OK; + } + + uint32_t count; + nsresult rv = mRefreshURIList->GetLength(&count); + if (NS_SUCCEEDED(rv)) { + *aResult = (count != 0); + } + return rv; +} + +NS_IMETHODIMP +nsDocShell::SuspendRefreshURIs() { + if (mRefreshURIList) { + uint32_t n = 0; + mRefreshURIList->GetLength(&n); + + for (uint32_t i = 0; i < n; ++i) { + nsCOMPtr<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); + } + } + + // 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; +} + +nsresult nsDocShell::Embed(nsIContentViewer* aContentViewer, + WindowGlobalChild* aWindowActor, + bool aIsTransientAboutBlank, bool aPersist) { + // Save the LayoutHistoryState of the previous document, before + // setting up new document + PersistLayoutHistoryState(); + + nsresult rv = SetupNewViewer(aContentViewer, aWindowActor); + NS_ENSURE_SUCCESS(rv, rv); + + // XXX What if SetupNewViewer fails? + if (mozilla::SessionHistoryInParent() ? !!mLoadingEntry : !!mLSHE) { + // Set history.state + SetDocCurrentStateObj(mLSHE, + mLoadingEntry ? &mLoadingEntry->mInfo : nullptr); + } + + if (mLSHE) { + // Restore the editing state, if it's stored in session history. + if (mLSHE->HasDetachedEditor()) { + ReattachEditorToWindow(mLSHE); + } + + SetHistoryEntryAndUpdateBC(Nothing(), Some<nsISHEntry*>(mLSHE)); + } + + if (!aIsTransientAboutBlank && mozilla::SessionHistoryInParent()) { + MOZ_LOG(gSHLog, LogLevel::Debug, ("document %p Embed", this)); + MoveLoadingToActiveEntry(aPersist); + } + + bool updateHistory = true; + + // Determine if this type of load should update history + switch (mLoadType) { + case LOAD_NORMAL_REPLACE: + case LOAD_STOP_CONTENT_AND_REPLACE: + case LOAD_RELOAD_BYPASS_CACHE: + case LOAD_RELOAD_BYPASS_PROXY: + case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE: + case LOAD_REPLACE_BYPASS_CACHE: + updateHistory = false; + break; + default: + break; + } + + if (!updateHistory) { + SetLayoutHistoryState(nullptr); + } + + return NS_OK; +} + +//***************************************************************************** +// nsDocShell::nsIWebProgressListener +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::OnProgressChange(nsIWebProgress* aProgress, nsIRequest* aRequest, + int32_t aCurSelfProgress, int32_t aMaxSelfProgress, + int32_t aCurTotalProgress, + int32_t aMaxTotalProgress) { + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::OnStateChange(nsIWebProgress* aProgress, nsIRequest* aRequest, + uint32_t aStateFlags, nsresult aStatus) { + if ((~aStateFlags & (STATE_START | STATE_IS_NETWORK)) == 0) { + // Save timing statistics. + nsCOMPtr<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) { + // Show the progress cursor if the pref is set + if (StaticPrefs::ui_use_activity_cursor()) { + nsCOMPtr<nsIWidget> mainWidget; + GetMainWidget(getter_AddRefs(mainWidget)); + if (mainWidget) { + mainWidget->SetCursor(eCursor_spinning, nullptr, 0, 0); + } + } + } + } else if ((~aStateFlags & (STATE_TRANSFERRING | STATE_IS_DOCUMENT)) == 0) { + // Page is loading + mBusyFlags = (BusyFlags)(BUSY_FLAGS_BUSY | BUSY_FLAGS_PAGE_LOADING); + } else if ((aStateFlags & STATE_STOP) && (aStateFlags & STATE_IS_NETWORK)) { + // Page has finished loading + mBusyFlags = BUSY_FLAGS_NONE; + + // Hide the progress cursor if the pref is set + if (StaticPrefs::ui_use_activity_cursor()) { + nsCOMPtr<nsIWidget> mainWidget; + GetMainWidget(getter_AddRefs(mainWidget)); + if (mainWidget) { + mainWidget->SetCursor(eCursor_standard, nullptr, 0, 0); + } + } + } + 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) { + if (XRE_IsParentProcess()) { + // Since we've now changed Documents, notify the BrowsingContext that we've + // changed. Ideally we'd just let the BrowsingContext do this when it + // changes the current window global, but that happens before this and we + // have a lot of tests that depend on the specific ordering of messages. + if (!(aFlags & nsIWebProgressListener::LOCATION_CHANGE_SAME_DOCUMENT)) { + GetBrowsingContext()->Canonical()->UpdateSecurityState(); + } + } + return NS_OK; +} + +void nsDocShell::OnRedirectStateChange(nsIChannel* aOldChannel, + nsIChannel* aNewChannel, + uint32_t aRedirectFlags, + uint32_t aStateFlags) { + NS_ASSERTION(aStateFlags & STATE_REDIRECTING, + "Calling OnRedirectStateChange when there is no redirect"); + + // If mixed content is allowed for the old channel, we forward + // the permission to the new channel if it has the same origin + // as the old one. + if (mMixedContentChannel && mMixedContentChannel == aOldChannel) { + nsresult rv = + nsContentUtils::CheckSameOrigin(mMixedContentChannel, aNewChannel); + if (NS_SUCCEEDED(rv)) { + SetMixedContentChannel(aNewChannel); // Same origin: forward permission. + } else { + SetMixedContentChannel( + nullptr); // Different origin: clear mMixedContentChannel. + } + } + + if (!(aStateFlags & STATE_IS_DOCUMENT)) { + return; // not a toplevel document + } + + nsCOMPtr<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); + } + } + + // check if the new load should go through the application cache. + nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel = + do_QueryInterface(aNewChannel); + if (appCacheChannel && !docChannel) { + if (GeckoProcessType_Default != XRE_GetProcessType()) { + // Permission will be checked in the parent process. + appCacheChannel->SetChooseApplicationCache(true); + } else { + nsCOMPtr<nsIScriptSecurityManager> secMan = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID); + + if (secMan) { + nsCOMPtr<nsIPrincipal> principal; + secMan->GetDocShellContentPrincipal(newURI, this, + getter_AddRefs(principal)); + appCacheChannel->SetChooseApplicationCache( + NS_ShouldCheckAppCache(principal)); + } + } + } + + if (!(aRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) && + mLoadType & (LOAD_CMD_RELOAD | LOAD_CMD_HISTORY)) { + mLoadType = LOAD_NORMAL_REPLACE; + SetHistoryEntryAndUpdateBC(Some(nullptr), Nothing()); + } +} + +NS_IMETHODIMP +nsDocShell::OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, + nsresult aStatus, const char16_t* aMessage) { + MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::OnSecurityChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, + uint32_t aState) { + MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::OnContentBlockingEvent(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, uint32_t aEvent) { + MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + +already_AddRefed<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<nsISupports> securityInfo; + rv = aChannel->GetSecurityInfo(getter_AddRefs(securityInfo)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + nsCOMPtr<nsITransportSecurityInfo> tsi = do_QueryInterface(securityInfo); + if (NS_WARN_IF(!tsi)) { + return nullptr; + } + + nsCOMPtr<nsIX509Cert> cert; + rv = tsi->GetServerCert(getter_AddRefs(cert)); + if (NS_WARN_IF(NS_FAILED(rv))) { + 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. + mozilla::psm::BRNameMatchingPolicy nameMatchingPolicy( + mozilla::psm::BRNameMatchingPolicy::Mode::Enforce); + rv1 = mozilla::pkix::CheckCertHostname(serverCertInput, newHostInput, + nameMatchingPolicy); + 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) { + 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)); + if (newURI) { + info->GetKeywordAsSent(keywordAsSent); + info->GetKeywordProviderName(keywordProviderName); + info->GetPostData(getter_AddRefs(newPostData)); + } + } + } + } + } + + // + // Now try change the address, e.g. turn http://foo into + // http://www.foo.com, and if that doesn't work try https with + // https://foo and https://www.foo.com. + // + if (aStatus == NS_ERROR_UNKNOWN_HOST || aStatus == NS_ERROR_NET_RESET) { + bool doCreateAlternate = true; + + // Skip fixup for anything except a normal document load + // operation on the topframe. + + if (aLoadType != LOAD_NORMAL || !aIsTopFrame) { + doCreateAlternate = false; + } else { + // Test if keyword lookup produced a new URI or not + if (newURI) { + bool sameURI = false; + url->Equals(newURI, &sameURI); + if (!sameURI) { + // Keyword lookup made a new URI so no need to try + // an alternate one. + doCreateAlternate = false; + } + } + + if (doCreateAlternate) { + nsCOMPtr<nsILoadInfo> info = aChannel->LoadInfo(); + // Skip doing this if our channel was redirected, because we + // shouldn't be guessing things about the post-redirect URI. + if (!info->RedirectChain().IsEmpty()) { + doCreateAlternate = false; + } + } + } + if (doCreateAlternate) { + newURI = nullptr; + newPostData = nullptr; + keywordProviderName.Truncate(); + keywordAsSent.Truncate(); + nsCOMPtr<nsIURIFixup> uriFixup = components::URIFixup::Service(); + if (uriFixup) { + nsCOMPtr<nsIURIFixupInfo> fixupInfo; + uriFixup->GetFixupURIInfo(oldSpec, + nsIURIFixup::FIXUP_FLAGS_MAKE_ALTERNATE_URI, + getter_AddRefs(fixupInfo)); + if (fixupInfo) { + fixupInfo->GetPreferredURI(getter_AddRefs(newURI)); + } + } + } + } else if (aStatus == NS_ERROR_CONNECTION_REFUSED && + Preferences::GetBool("browser.fixup.fallback-to-https", false)) { + // Try HTTPS, since http didn't work + if (SchemeIsHTTP(url)) { + int32_t port = 0; + url->GetPort(&port); + + // Fall back to HTTPS only if port is default + if (port == -1) { + newURI = nullptr; + newPostData = nullptr; + Unused << NS_MutateURI(url) + .SetScheme("https"_ns) + .Finalize(getter_AddRefs(newURI)); + } + } + } + + // If we have a SSL_ERROR_BAD_CERT_DOMAIN error, try prefixing the domain name + // with www. to see if we can avoid showing the cert error page. For example, + // https://example.com -> https://www.example.com. + if (aStatus == + mozilla::psm::GetXPCOMFromNSSError(SSL_ERROR_BAD_CERT_DOMAIN)) { + newPostData = nullptr; + newURI = MaybeFixBadCertDomainErrorURI(aChannel, url); + } + + // Did we make a new URI that is different to the old one? If so + // load it. + // + if (newURI) { + // Make sure the new URI is different from the old one, + // otherwise there's little point trying to load it again. + bool sameURI = false; + url->Equals(newURI, &sameURI); + if (!sameURI) { + if (aNewPostData) { + newPostData.forget(aNewPostData); + } + if (aNotifyKeywordSearchLoading) { + // This notification is meant for Firefox Health Report so it + // can increment counts from the search engine + MaybeNotifyKeywordSearchLoading(keywordProviderName, keywordAsSent); + } + return newURI.forget(); + } + } + + return nullptr; +} + +nsresult nsDocShell::FilterStatusForErrorPage( + nsresult aStatus, nsIChannel* aChannel, uint32_t aLoadType, + bool aIsTopFrame, bool aUseErrorPages, bool aIsInitialDocument, + bool* aSkippedUnknownProtocolNavigation) { + // Errors to be shown only on top-level frames + if ((aStatus == NS_ERROR_UNKNOWN_HOST || + aStatus == NS_ERROR_CONNECTION_REFUSED || + aStatus == NS_ERROR_UNKNOWN_PROXY_HOST || + aStatus == NS_ERROR_PROXY_CONNECTION_REFUSED || + aStatus == NS_ERROR_PROXY_FORBIDDEN || + aStatus == NS_ERROR_PROXY_NOT_IMPLEMENTED || + aStatus == NS_ERROR_PROXY_AUTHENTICATION_FAILED || + aStatus == NS_ERROR_PROXY_TOO_MANY_REQUESTS || + aStatus == NS_ERROR_MALFORMED_URI || + aStatus == NS_ERROR_BLOCKED_BY_POLICY) && + (aIsTopFrame || aUseErrorPages)) { + return aStatus; + } + + if (aStatus == NS_ERROR_NET_TIMEOUT || + aStatus == NS_ERROR_PROXY_GATEWAY_TIMEOUT || + aStatus == NS_ERROR_REDIRECT_LOOP || + aStatus == NS_ERROR_UNKNOWN_SOCKET_TYPE || + aStatus == NS_ERROR_NET_INTERRUPT || aStatus == NS_ERROR_NET_RESET || + aStatus == NS_ERROR_PROXY_BAD_GATEWAY || aStatus == NS_ERROR_OFFLINE || + aStatus == NS_ERROR_MALWARE_URI || aStatus == NS_ERROR_PHISHING_URI || + aStatus == NS_ERROR_UNWANTED_URI || aStatus == NS_ERROR_HARMFUL_URI || + aStatus == NS_ERROR_UNSAFE_CONTENT_TYPE || + aStatus == NS_ERROR_REMOTE_XUL || + aStatus == NS_ERROR_INTERCEPTION_FAILED || + aStatus == NS_ERROR_NET_INADEQUATE_SECURITY || + aStatus == NS_ERROR_NET_HTTP2_SENT_GOAWAY || + aStatus == NS_ERROR_NET_HTTP3_PROTOCOL_ERROR || + aStatus == NS_ERROR_DOM_BAD_URI || aStatus == NS_ERROR_FILE_NOT_FOUND || + aStatus == NS_ERROR_FILE_ACCESS_DENIED || + aStatus == NS_ERROR_CORRUPTED_CONTENT || + aStatus == NS_ERROR_INVALID_CONTENT_ENCODING || + NS_ERROR_GET_MODULE(aStatus) == NS_ERROR_MODULE_SECURITY) { + // Errors to be shown for any frame + return aStatus; + } + + if (aStatus == NS_ERROR_UNKNOWN_PROTOCOL) { + // For unknown protocols we only display an error if the load is triggered + // by the browser itself, or we're replacing the initial document (and + // nothing else). Showing the error for page-triggered navigations causes + // annoying behavior for users, see bug 1528305. + // + // We could, maybe, try to detect if this is in response to some user + // interaction (like clicking a link, or something else) and maybe show + // the error page in that case. But this allows for ctrl+clicking and such + // to see the error page. + nsCOMPtr<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()) { + Telemetry::AccumulateTimeDelta(Telemetry::TOTAL_CONTENT_PAGE_LOAD_TIME, + channelCreationTime); + } + } + + // Timing is picked up by the window, we don't need it anymore + mTiming = nullptr; + + // clean up reload state for meta charset + if (eCharsetReloadRequested == mCharsetReloadState) { + mCharsetReloadState = eCharsetReloadStopOrigional; + } else { + mCharsetReloadState = eCharsetReloadInit; + } + + // Save a pointer to the currently-loading history entry. + // nsDocShell::EndPageLoad will clear mLSHE, but we may need this history + // entry further down in this method. + nsCOMPtr<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 ContentViewer that the Document has finished loading. This + // will cause any OnLoad(...) and PopState(...) handlers to fire. + if (!mEODForCurrentDocument && mContentViewer) { + mIsExecutingOnLoadHandler = true; + nsCOMPtr<nsIContentViewer> contentViewer = mContentViewer; + contentViewer->LoadComplete(aStatus); + mIsExecutingOnLoadHandler = false; + + mEODForCurrentDocument = true; + + // If all documents have completed their loading + // favor native event dispatch priorities + // over performance + if (--gNumberOfDocumentsLoading == 0) { + // Hint to use normal native event dispatch priorities + FavorPerformanceHint(false); + } + } + /* Check if the httpChannel has any cache-control related response headers, + * like no-store, no-cache. If so, update SHEntry so that + * when a user goes back/forward to this page, we appropriately do + * form value restoration or load from server. + */ + nsCOMPtr<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(); + + // If status code indicates an error it means that DocumentChannel already + // tried to fixup the uri and failed. Throw an error dialog box here. + if (NS_FAILED(aStatus)) { + // If we got CONTENT_BLOCKED from EndPageLoad, then we need to fire + // the error event to our embedder, since tests are relying on this. + // The error event is usually fired by the caller of InternalLoad, but + // this particular error can happen asynchronously. + // Bug 1629201 is filed for having much clearer decision making around + // which cases need error events. + bool fireFrameErrorEvent = (aStatus == NS_ERROR_CONTENT_BLOCKED_SHOW_ALT || + aStatus == NS_ERROR_CONTENT_BLOCKED); + UnblockEmbedderLoadEventForFailure(fireFrameErrorEvent); + + bool isInitialDocument = + !GetExtantDocument() || GetExtantDocument()->IsInitialDocument(); + bool skippedUnknownProtocolNavigation = false; + aStatus = FilterStatusForErrorPage(aStatus, aChannel, mLoadType, isTopFrame, + mBrowsingContext->GetUseErrorPages(), + isInitialDocument, + &skippedUnknownProtocolNavigation); + if (NS_FAILED(aStatus)) { + DisplayLoadError(aStatus, url, nullptr, aChannel); + } else if (skippedUnknownProtocolNavigation) { + nsTArray<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()); + } + + return NS_OK; +} + +//***************************************************************************** +// nsDocShell: Content Viewer Management +//***************************************************************************** + +nsresult nsDocShell::EnsureContentViewer() { + if (mContentViewer) { + 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 = CreateAboutBlankContentViewer( + principal, partitionedPrincipal, cspToInheritForAboutBlank, baseURI); + + NS_ENSURE_STATE(mContentViewer); + + if (NS_SUCCEEDED(rv)) { + RefPtr<Document> doc(GetDocument()); + NS_ASSERTION(doc, + "Should have doc if CreateAboutBlankContentViewer " + "succeeded!"); + + doc->SetIsInitialDocument(true); + + // Documents created using EnsureContentViewer may be transient + // placeholders created by framescripts before content has a + // chance to load. In some cases, window.open(..., "noopener") + // will create such a document and then synchronously tear it + // down, firing a "pagehide" event. Doing so violates our + // assertions about DocGroups. It's easier to silence the + // assertion here than to avoid creating the extra document. + doc->IgnoreDocGroupMismatches(); + } + + return rv; +} + +nsresult nsDocShell::CreateAboutBlankContentViewer( + nsIPrincipal* aPrincipal, nsIPrincipal* aPartitionedPrincipal, + nsIContentSecurityPolicy* aCSP, nsIURI* aBaseURI, + const Maybe<nsILoadInfo::CrossOriginEmbedderPolicy>& aCOEP, + bool aTryToSaveOldPresentation, bool aCheckPermitUnload, + WindowGlobalChild* aActor) { + RefPtr<Document> blankDoc; + nsCOMPtr<nsIContentViewer> viewer; + nsresult rv = NS_ERROR_FAILURE; + + MOZ_ASSERT_IF(aActor, aActor->DocumentPrincipal() == aPrincipal); + + /* mCreatingDocument should never be true at this point. However, it's + a theoretical possibility. We want to know about it and make it stop, + and this sounds like a job for an assertion. */ + NS_ASSERTION(!mCreatingDocument, + "infinite(?) loop creating document averted"); + if (mCreatingDocument) { + return NS_ERROR_FAILURE; + } + + if (!mBrowsingContext->AncestorsAreCurrent()) { + mBrowsingContext->RemoveRootFromBFCacheSync(); + return NS_ERROR_NOT_AVAILABLE; + } + + // mContentViewer->PermitUnload may release |this| docshell. + nsCOMPtr<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 (mContentViewer) { + if (aCheckPermitUnload) { + // We've got a content viewer already. Make sure the user + // permits us to discard the current document and replace it + // with about:blank. And also ensure we fire the unload events + // in the current document. + + // Unload gets fired first for + // document loaded from the session history. + mTiming->NotifyBeforeUnload(); + + bool okToUnload; + rv = mContentViewer->PermitUnload(&okToUnload); + + if (NS_SUCCEEDED(rv) && !okToUnload) { + // The user chose not to unload the page, interrupt the load. + MaybeResetInitTiming(toBeReset); + return NS_ERROR_FAILURE; + } + if (mTiming) { + mTiming->NotifyUnloadAccepted(mCurrentURI); + } + } + + mSavingOldViewer = aTryToSaveOldPresentation && + CanSavePresentation(LOAD_NORMAL, nullptr, nullptr); + + // Make sure to blow away our mLoadingURI just in case. No loads + // from inside this pagehide. + mLoadingURI = nullptr; + + // Stop any in-progress loading, so that we don't accidentally trigger any + // PageShow notifications from Embed() interrupting our loading below. + Stop(); + + // Notify the current document that it is about to be unloaded!! + // + // It is important to fire the unload() notification *before* any state + // is changed within the DocShell - otherwise, javascript will get the + // wrong information :-( + // + (void)FirePageHideNotification(!mSavingOldViewer); + // pagehide notification might destroy this docshell. + if (mIsBeingDestroyed) { + return NS_ERROR_DOCSHELL_DYING; + } + } + + // Now make sure we don't think we're in the middle of firing unload after + // this point. This will make us fire unload when the about:blank document + // unloads... but that's ok, more or less. Would be nice if it fired load + // too, of course. + mFiredUnloadEvent = false; + + nsCOMPtr<nsIDocumentLoaderFactory> docFactory = + nsContentUtils::FindInternalContentViewer("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::CreateWithInheritedAttributes(this); + } + partitionedPrincipal = principal; + } else { + principal = aPrincipal; + partitionedPrincipal = aPartitionedPrincipal; + } + + MaybeCreateInitialClientSource(principal); + + // generate (about:blank) document to load + blankDoc = nsContentDLF::CreateBlankDocument(mLoadGroup, principal, + partitionedPrincipal, this); + if (blankDoc) { + // Hack: manually set the CSP for the new document + // Please create an actual copy of the CSP (do not share the same + // reference) otherwise appending a new policy within the new + // document will be incorrectly propagated to the opening doc. + if (aCSP) { + RefPtr<nsCSPContext> cspToInherit = new nsCSPContext(); + cspToInherit->InitFromOther(static_cast<nsCSPContext*>(aCSP)); + blankDoc->SetCsp(cspToInherit); + } + + blankDoc->SetEmbedderPolicy(aCOEP); + + // Hack: set the base URI manually, since this document never + // got Reset() with a channel. + blankDoc->SetBaseURI(aBaseURI); + + // Copy our sandbox flags to the document. These are immutable + // after being set here. + blankDoc->SetSandboxFlags(sandboxFlags); + + // create a content viewer for us and the new document + docFactory->CreateInstanceForDocument( + NS_ISUPPORTS_CAST(nsIDocShell*, this), blankDoc, "view", + getter_AddRefs(viewer)); + + // hook 'em up + if (viewer) { + viewer->SetContainer(this); + rv = Embed(viewer, aActor, true, false); + NS_ENSURE_SUCCESS(rv, rv); + + SetCurrentURI(blankDoc->GetDocumentURI(), nullptr, true, 0); + rv = mIsBeingDestroyed ? NS_ERROR_NOT_AVAILABLE : NS_OK; + } + } + } + + // The transient about:blank viewer doesn't have a session history entry. + SetHistoryEntryAndUpdateBC(Nothing(), Some(nullptr)); + + // Clear out our mTiming like we would in EndPageLoad, if we didn't + // have one before entering this function. + if (!hadTiming) { + mTiming = nullptr; + mBlankTiming = true; + } + + return rv; +} + +NS_IMETHODIMP +nsDocShell::CreateAboutBlankContentViewer(nsIPrincipal* aPrincipal, + nsIPrincipal* aPartitionedPrincipal, + nsIContentSecurityPolicy* aCSP) { + return CreateAboutBlankContentViewer(aPrincipal, aPartitionedPrincipal, aCSP, + nullptr); +} + +nsresult nsDocShell::CreateContentViewerForActor( + WindowGlobalChild* aWindowActor) { + MOZ_ASSERT(aWindowActor); + + // FIXME: WindowGlobalChild should provide the PartitionedPrincipal. + nsresult rv = CreateAboutBlankContentViewer( + aWindowActor->DocumentPrincipal(), aWindowActor->DocumentPrincipal(), + /* aCsp */ nullptr, + /* aBaseURI */ nullptr, + /* aCOEP */ Nothing(), + /* aTryToSaveOldPresentation */ true, + /* aCheckPermitUnload */ true, aWindowActor); + if (NS_SUCCEEDED(rv)) { + RefPtr<Document> doc(GetDocument()); + MOZ_ASSERT( + doc, + "Should have a document if CreateAboutBlankContentViewer succeeded"); + MOZ_ASSERT(doc->GetOwnerGlobal() == aWindowActor->GetWindowGlobal(), + "New document should be in the same global as our actor"); + + // FIXME: We may want to support non-initial documents here. + doc->SetIsInitialDocument(true); + } + + return rv; +} + +bool nsDocShell::CanSavePresentation(uint32_t aLoadType, + nsIRequest* aNewRequest, + Document* aNewDocument) { + if (!mOSHE) { + return false; // no entry to save into + } + + nsCOMPtr<nsIContentViewer> viewer = mOSHE->GetContentViewer(); + if (viewer) { + NS_WARNING("mOSHE already has a content viewer!"); + return false; + } + + // Only save presentation for "normal" loads and link loads. Anything else + // probably wants to refetch the page, so caching the old presentation + // would be incorrect. + if (aLoadType != LOAD_NORMAL && aLoadType != LOAD_HISTORY && + aLoadType != LOAD_LINK && aLoadType != LOAD_STOP_CONTENT && + aLoadType != LOAD_STOP_CONTENT_AND_REPLACE && + aLoadType != LOAD_ERROR_PAGE) { + return false; + } + + // If the session history entry has the saveLayoutState flag set to false, + // then we should not cache the presentation. + if (!mOSHE->GetSaveLayoutStateFlag()) { + return false; + } + + // If the document is not done loading, don't cache it. + if (!mScriptGlobal || mScriptGlobal->IsLoading()) { + MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose, + ("Blocked due to document still loading")); + return false; + } + + if (mScriptGlobal->WouldReuseInnerWindow(aNewDocument)) { + return false; + } + + // Avoid doing the work of saving the presentation state in the case where + // the content viewer cache is disabled. + if (nsSHistory::GetMaxTotalViewers() == 0) { + return false; + } + + // Don't cache the content viewer if we're in a subframe. + if (mBrowsingContext->GetParent()) { + return false; // this is a subframe load + } + + // If the document does not want its presentation cached, then don't. + RefPtr<Document> doc = mScriptGlobal->GetExtantDoc(); + + uint16_t bfCacheCombo = 0; + bool canSavePresentation = + doc->CanSavePresentation(aNewRequest, bfCacheCombo); + MOZ_ASSERT_IF(canSavePresentation, bfCacheCombo == 0); + if (canSavePresentation && doc->IsTopLevelContentDocument()) { + auto* browsingContextGroup = mBrowsingContext->Group(); + nsTArray<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; + } + } + } + ReportBFCacheComboTelemetry(bfCacheCombo); + + return doc && canSavePresentation; +} + +void nsDocShell::ReportBFCacheComboTelemetry(uint16_t aCombo) { + // There are 11 possible reasons to make a request fails to use BFCache + // (see BFCacheStatus in dom/base/Document.h), and we'd like to record + // the common combinations for reasons which make requests fail to use + // BFCache. These combinations are generated based on some local browsings, + // we need to adjust them when necessary. + enum BFCacheStatusCombo : uint16_t { + BFCACHE_SUCCESS, + NOT_ONLY_TOPLEVEL = mozilla::dom::BFCacheStatus::NOT_ONLY_TOPLEVEL_IN_BCG, + UNLOAD = mozilla::dom::BFCacheStatus::UNLOAD_LISTENER, + UNLOAD_REQUEST = mozilla::dom::BFCacheStatus::UNLOAD_LISTENER | + mozilla::dom::BFCacheStatus::REQUEST, + REQUEST = mozilla::dom::BFCacheStatus::REQUEST, + UNLOAD_REQUEST_PEER = mozilla::dom::BFCacheStatus::UNLOAD_LISTENER | + mozilla::dom::BFCacheStatus::REQUEST | + mozilla::dom::BFCacheStatus::ACTIVE_PEER_CONNECTION, + UNLOAD_REQUEST_PEER_MSE = + mozilla::dom::BFCacheStatus::UNLOAD_LISTENER | + mozilla::dom::BFCacheStatus::REQUEST | + mozilla::dom::BFCacheStatus::ACTIVE_PEER_CONNECTION | + mozilla::dom::BFCacheStatus::CONTAINS_MSE_CONTENT, + UNLOAD_REQUEST_MSE = mozilla::dom::BFCacheStatus::UNLOAD_LISTENER | + mozilla::dom::BFCacheStatus::REQUEST | + mozilla::dom::BFCacheStatus::CONTAINS_MSE_CONTENT, + SUSPENDED_UNLOAD_REQUEST_PEER = + mozilla::dom::BFCacheStatus::SUSPENDED | + mozilla::dom::BFCacheStatus::UNLOAD_LISTENER | + mozilla::dom::BFCacheStatus::REQUEST | + mozilla::dom::BFCacheStatus::ACTIVE_PEER_CONNECTION, + REMOTE_SUBFRAMES = mozilla::dom::BFCacheStatus::CONTAINS_REMOTE_SUBFRAMES + }; + + switch (aCombo) { + case BFCACHE_SUCCESS: + Telemetry::AccumulateCategorical( + Telemetry::LABELS_BFCACHE_COMBO::BFCache_Success); + break; + case NOT_ONLY_TOPLEVEL: + if (StaticPrefs::docshell_shistory_bfcache_require_no_opener()) { + Telemetry::AccumulateCategorical( + Telemetry::LABELS_BFCACHE_COMBO::Other); + break; + } + Telemetry::AccumulateCategorical( + Telemetry::LABELS_BFCACHE_COMBO::BFCache_Success); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_BFCACHE_COMBO::Success_Not_Toplevel); + break; + case UNLOAD: + Telemetry::AccumulateCategorical(Telemetry::LABELS_BFCACHE_COMBO::Unload); + break; + case UNLOAD_REQUEST: + Telemetry::AccumulateCategorical( + Telemetry::LABELS_BFCACHE_COMBO::Unload_Req); + break; + case REQUEST: + Telemetry::AccumulateCategorical(Telemetry::LABELS_BFCACHE_COMBO::Req); + break; + case UNLOAD_REQUEST_PEER: + Telemetry::AccumulateCategorical( + Telemetry::LABELS_BFCACHE_COMBO::Unload_Req_Peer); + break; + case UNLOAD_REQUEST_PEER_MSE: + Telemetry::AccumulateCategorical( + Telemetry::LABELS_BFCACHE_COMBO::Unload_Req_Peer_MSE); + break; + case UNLOAD_REQUEST_MSE: + Telemetry::AccumulateCategorical( + Telemetry::LABELS_BFCACHE_COMBO::Unload_Req_MSE); + break; + case SUSPENDED_UNLOAD_REQUEST_PEER: + Telemetry::AccumulateCategorical( + Telemetry::LABELS_BFCACHE_COMBO::SPD_Unload_Req_Peer); + break; + case REMOTE_SUBFRAMES: + Telemetry::AccumulateCategorical( + Telemetry::LABELS_BFCACHE_COMBO::Remote_Subframes); + break; + default: + Telemetry::AccumulateCategorical(Telemetry::LABELS_BFCACHE_COMBO::Other); + break; + } +}; + +void nsDocShell::ReattachEditorToWindow(nsISHEntry* aSHEntry) { + MOZ_ASSERT(!mIsBeingDestroyed); + + NS_ASSERTION(!mEditorData, + "Why reattach an editor when we already have one?"); + NS_ASSERTION(aSHEntry && aSHEntry->HasDetachedEditor(), + "Reattaching when there's not a detached editor."); + + if (mEditorData || !aSHEntry) { + return; + } + + mEditorData = WrapUnique(aSHEntry->ForgetEditorData()); + if (mEditorData) { +#ifdef DEBUG + nsresult rv = +#endif + mEditorData->ReattachToWindow(this); + NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to reattach editing session"); + } +} + +void nsDocShell::DetachEditorFromWindow() { + if (!mEditorData || mEditorData->WaitingForLoad()) { + // If there's nothing to detach, or if the editor data is actually set + // up for the _new_ page that's coming in, don't detach. + return; + } + + NS_ASSERTION(!mOSHE || !mOSHE->HasDetachedEditor(), + "Detaching editor when it's already detached."); + + nsresult res = mEditorData->DetachFromWindow(); + NS_ASSERTION(NS_SUCCEEDED(res), "Failed to detach editor"); + + if (NS_SUCCEEDED(res)) { + // Make mOSHE hold the owning ref to the editor data. + if (mOSHE) { + MOZ_ASSERT(!mIsBeingDestroyed || !mOSHE->HasDetachedEditor(), + "We should not set the editor data again once after we " + "detached the editor data during destroying this docshell"); + mOSHE->SetEditorData(mEditorData.release()); + } else { + mEditorData = nullptr; + } + } + +#ifdef DEBUG + { + bool isEditable; + GetEditable(&isEditable); + NS_ASSERTION(!isEditable, + "Window is still editable after detaching editor."); + } +#endif // DEBUG +} + +nsresult nsDocShell::CaptureState() { + if (!mOSHE || mOSHE == mLSHE) { + // No entry to save into, or we're replacing the existing entry. + return NS_ERROR_FAILURE; + } + + if (!mScriptGlobal) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<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; + if (mozilla::SessionHistoryInParent()) { + uri = mActiveEntry->GetURI(); + } else { + uri = mOSHE->GetURI(); + } + if (uri) { + uri->GetSpec(spec); + } + MOZ_LOG(gPageCacheLog, LogLevel::Debug, + ("Saving presentation into session history, URI: %s", spec.get())); + } + + mOSHE->SetWindowState(windowState); + + // Suspend refresh URIs and save off the timer queue + mOSHE->SetRefreshURIList(mSavedRefreshURIList); + + // Capture the current content viewer bounds. + if (mContentViewer) { + nsIntRect bounds; + mContentViewer->GetBounds(bounds); + mOSHE->SetViewerBounds(bounds); + } + + // Capture the docshell hierarchy. + mOSHE->ClearChildShells(); + + uint32_t childCount = mChildList.Length(); + for (uint32_t i = 0; i < childCount; ++i) { + nsCOMPtr<nsIDocShellTreeItem> childShell = do_QueryInterface(ChildAt(i)); + NS_ASSERTION(childShell, "null child shell"); + + mOSHE->AddChildShell(childShell); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::RestorePresentationEvent::Run() { + if (mDocShell && NS_FAILED(mDocShell->RestoreFromHistory())) { + NS_WARNING("RestoreFromHistory failed"); + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::BeginRestore(nsIContentViewer* aContentViewer, bool aTop) { + nsresult rv; + if (!aContentViewer) { + rv = EnsureContentViewer(); + NS_ENSURE_SUCCESS(rv, rv); + + aContentViewer = mContentViewer; + } + + // Dispatch events for restoring the presentation. We try to simulate + // the progress notifications loading the document would cause, so we add + // the document's channel to the loadgroup to initiate stateChange + // notifications. + + RefPtr<Document> doc = aContentViewer->GetDocument(); + if (doc) { + nsIChannel* channel = doc->GetChannel(); + if (channel) { + mEODForCurrentDocument = false; + mIsRestoringDocument = true; + mLoadGroup->AddRequest(channel, nullptr); + mIsRestoringDocument = false; + } + } + + if (!aTop) { + // This point corresponds to us having gotten OnStartRequest or + // STATE_START, so do the same thing that CreateContentViewer does at + // this point to ensure that unload/pagehide events for this document + // will fire when it's unloaded again. + mFiredUnloadEvent = false; + + // For non-top frames, there is no notion of making sure that the + // previous document is in the domwindow when STATE_START notifications + // happen. We can just call BeginRestore for all of the child shells + // now. + rv = BeginRestoreChildren(); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +nsresult nsDocShell::BeginRestoreChildren() { + for (auto* childDocLoader : mChildList.ForwardRange()) { + nsCOMPtr<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() { + // 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(!mIsBeingDestroyed); + + NS_ASSERTION(mLoadType & LOAD_CMD_HISTORY, + "RestorePresentation should only be called for history loads"); + + nsCOMPtr<nsIContentViewer> viewer = aSHEntry->GetContentViewer(); + + 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->SetContentViewer(nullptr); + return NS_ERROR_FAILURE; + } + + NS_ASSERTION(mContentViewer != viewer, "Restoring existing presentation"); + + MOZ_LOG(gPageCacheLog, LogLevel::Debug, + ("restoring presentation from session history: %s", spec.get())); + + SetHistoryEntryAndUpdateBC(Some(aSHEntry), Nothing()); + + // Post an event that will remove the request after we've returned + // to the event loop. This mimics the way it is called by nsIChannel + // implementations. + + // Revoke any pending restore (just in case). + NS_ASSERTION(!mRestorePresentationEvent.IsPending(), + "should only have one RestorePresentationEvent"); + mRestorePresentationEvent.Revoke(); + + RefPtr<RestorePresentationEvent> evt = new RestorePresentationEvent(this); + nsresult rv = Dispatch(TaskCategory::Other, do_AddRef(evt)); + if (NS_SUCCEEDED(rv)) { + mRestorePresentationEvent = evt.get(); + // The rest of the restore processing will happen on our event + // callback. + *aRestoring = true; + } + + return rv; +} + +namespace { +class MOZ_STACK_CLASS PresentationEventForgetter { + public: + explicit PresentationEventForgetter( + nsRevocableEventPtr<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(mRestorePresentationEvent.IsPending()); + PresentationEventForgetter forgetter(mRestorePresentationEvent); + + // This section of code follows the same ordering as CreateContentViewer. + if (!mLSHE) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIContentViewer> viewer = mLSHE->GetContentViewer(); + if (!viewer) { + return NS_ERROR_FAILURE; + } + + if (mSavingOldViewer) { + // We determined that it was safe to cache the document presentation + // at the time we initiated the new load. We need to check whether + // it's still safe to do so, since there may have been DOM mutations + // or new requests initiated. + RefPtr<Document> doc = viewer->GetDocument(); + nsIRequest* request = nullptr; + if (doc) { + request = doc->GetChannel(); + } + mSavingOldViewer = CanSavePresentation(mLoadType, request, doc); + } + + nsCOMPtr<nsIContentViewer> oldCv(mContentViewer); + nsCOMPtr<nsIContentViewer> newCv(viewer); + float overrideDPPX = 0.0f; + + if (oldCv) { + oldCv->GetOverrideDPPX(&overrideDPPX); + } + + // Protect against mLSHE going away via a load triggered from + // pagehide or unload. + nsCOMPtr<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(); + if (!mozilla::SessionHistoryInParent()) { + rootSH->LegacySHistory()->UpdateIndex(); + } + mLoadedEntryIndex = rootSH->Index(); + MOZ_LOG(gPageCacheLog, LogLevel::Verbose, + ("Previous index: %d, Loaded index: %d", mPreviousEntryIndex, + mLoadedEntryIndex)); + } + + // Rather than call Embed(), we will retrieve the viewer from the session + // history entry and swap it in. + // XXX can we refactor this so that we can just call Embed()? + PersistLayoutHistoryState(); + nsresult rv; + if (mContentViewer) { + if (mSavingOldViewer && NS_FAILED(CaptureState())) { + if (mOSHE) { + mOSHE->SyncPresentationState(); + } + mSavingOldViewer = false; + } + } + + mSavedRefreshURIList = nullptr; + + // In cases where we use a transient about:blank viewer between loads, + // we never show the transient viewer, so _its_ previous viewer is never + // unhooked from the view hierarchy. Destroy any such previous viewer now, + // before we grab the root view sibling, so that we don't grab a view + // that's about to go away. + + if (mContentViewer) { + // Make sure to hold a strong ref to previousViewer here while we + // drop the reference to it from mContentViewer. + nsCOMPtr<nsIContentViewer> previousViewer = + mContentViewer->GetPreviousViewer(); + if (previousViewer) { + mContentViewer->SetPreviousViewer(nullptr); + previousViewer->Destroy(); + } + } + + // Save off the root view's parent and sibling so that we can insert the + // new content viewer's root view at the same position. Also save the + // bounds of the root view's widget. + + nsView* rootViewSibling = nullptr; + nsView* rootViewParent = nullptr; + nsIntRect newBounds(0, 0, 0, 0); + + PresShell* oldPresShell = GetPresShell(); + if (oldPresShell) { + nsViewManager* vm = oldPresShell->GetViewManager(); + if (vm) { + nsView* oldRootView = vm->GetRootView(); + + if (oldRootView) { + rootViewSibling = oldRootView->GetNextSibling(); + rootViewParent = oldRootView->GetParent(); + + mContentViewer->GetBounds(newBounds); + } + } + } + + nsCOMPtr<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 mContentViewer. By ensuring that either the + // docshell or the session history, but not both, have references to the + // content viewer, we prevent the viewer from being torn down after + // Destroy() is called. + + if (mContentViewer) { + mContentViewer->Close(mSavingOldViewer ? mOSHE.get() : nullptr); + viewer->SetPreviousViewer(mContentViewer); + } + if (mOSHE && (!mContentViewer || !mSavingOldViewer)) { + // We don't plan to save a viewer in mOSHE; tell it to drop + // any other state it's holding. + mOSHE->SyncPresentationState(); + } + + // Order the mContentViewer setup just like Embed does. + mContentViewer = nullptr; + + // Now that we're about to switch documents, forget all of our children. + // Note that we cached them as needed up in CaptureState above. + DestroyChildren(); + + mContentViewer.swap(viewer); + + // Grab all of the related presentation from the SHEntry now. + // Clearing the viewer from the SHEntry will clear all of this state. + nsCOMPtr<nsISupports> windowState = mLSHE->GetWindowState(); + mLSHE->SetWindowState(nullptr); + + bool sticky = mLSHE->GetSticky(); + + RefPtr<Document> document = mContentViewer->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 = mContentViewer->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->SetContentViewer(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. + mContentViewer->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; + + // Tell the event loop to favor plevents over user events, see comments + // in CreateContentViewer. + if (++gNumberOfDocumentsLoading == 1) { + FavorPerformanceHint(true); + } + + if (oldCv) { + newCv->SetOverrideDPPX(overrideDPPX); + } + + if (document) { + RefPtr<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(), true, 0); + } + + // This is the end of our CreateContentViewer() replacement. + // Now we simulate a load. First, we restore the state of the javascript + // window object. + nsCOMPtr<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 allowJavascript; + childShell->GetAllowJavascript(&allowJavascript); + + bool allowRedirects; + childShell->GetAllowMetaRedirects(&allowRedirects); + + bool allowSubframes; + childShell->GetAllowSubframes(&allowSubframes); + + bool allowImages; + childShell->GetAllowImages(&allowImages); + + bool allowMedia = childShell->GetAllowMedia(); + + bool allowDNSPrefetch; + childShell->GetAllowDNSPrefetch(&allowDNSPrefetch); + + bool allowContentRetargeting = childShell->GetAllowContentRetargeting(); + bool allowContentRetargetingOnChildren = + childShell->GetAllowContentRetargetingOnChildren(); + + // this.AddChild(child) calls child.SetDocLoaderParent(this), meaning that + // the child inherits our state. Among other things, this means that the + // child inherits our mPrivateBrowsingId, which is what we want. + AddChild(childItem); + + childShell->SetAllowJavascript(allowJavascript); + childShell->SetAllowMetaRedirects(allowRedirects); + childShell->SetAllowSubframes(allowSubframes); + childShell->SetAllowImages(allowImages); + childShell->SetAllowMedia(allowMedia); + childShell->SetAllowDNSPrefetch(allowDNSPrefetch); + childShell->SetAllowContentRetargeting(allowContentRetargeting); + childShell->SetAllowContentRetargetingOnChildren( + allowContentRetargetingOnChildren); + + rv = childShell->BeginRestore(nullptr, false); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Make sure to restore the window state after adding the child shells back + // to the tree. This is necessary for Thaw() and Resume() to propagate + // properly. + rv = privWin->RestoreWindowState(windowState); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<PresShell> presShell = GetPresShell(); + + // We may be displayed on a different monitor (or in a different + // HiDPI mode) than when we got into the history list. So we need + // to check if this has happened. See bug 838239. + + // Because the prescontext normally handles resolution changes via + // a runnable (see nsPresContext::UIResolutionChanged), its device + // context won't be -immediately- updated as a result of calling + // presShell->BackingScaleFactorChanged(). + + // But we depend on that device context when adjusting the view size + // via mContentViewer->SetBounds(newBounds) below. So we need to + // explicitly tell it to check for changed resolution here. + if (presShell) { + RefPtr<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. + privWinInner->GetPerformance()->GetDOMTiming()->NotifyRestoreStart(); + + // Restore the refresh URI list. The refresh timers will be restarted + // when EndPageLoad() is called. + mRefreshURIList = refreshURIList; + + // Meta-refresh timers have been restarted for this shell, but not + // for our children. Walk the child shells and restart their timers. + for (auto* childDocLoader : mChildList.ForwardRange()) { + nsCOMPtr<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)); + mContentViewer->SetBounds(newBounds); + } else { + nsIScrollableFrame* rootScrollFrame = + presShell->GetRootScrollFrameAsScrollable(); + if (rootScrollFrame) { + rootScrollFrame->PostScrolledAreaEventForCurrentArea(); + } + } + } + + // The FinishRestore call below can kill these, null them out so we don't + // have invalid pointer lying around. + newRootView = rootViewSibling = rootViewParent = nullptr; + newVM = nullptr; + + // If the IsUnderHiddenEmbedderElement() state has been changed, we need to + // update it. + if (oldPresShell && presShell && + presShell->IsUnderHiddenEmbedderElement() != + oldPresShell->IsUnderHiddenEmbedderElement()) { + presShell->SetIsUnderHiddenEmbedderElement( + oldPresShell->IsUnderHiddenEmbedderElement()); + } + + // Simulate the completion of the load. + nsDocShell::FinishRestore(); + + // Restart plugins, and paint the content. + if (presShell) { + presShell->Thaw(); + } + + return privWin->FireDelayedDOMEvents(); +} + +nsresult nsDocShell::CreateContentViewer(const nsACString& aContentType, + nsIRequest* aRequest, + nsIStreamListener** aContentHandler) { + if (DocGroup::TryToLoadIframesInBackground()) { + ResetToFirstLoad(); + } + + *aContentHandler = nullptr; + + if (!mTreeOwner || mIsBeingDestroyed) { + // If we don't have a tree owner, then we're in the process of being + // destroyed. Rather than continue trying to load something, just give up. + return NS_ERROR_DOCSHELL_DYING; + } + + if (!mBrowsingContext->AncestorsAreCurrent()) { + mBrowsingContext->RemoveRootFromBFCacheSync(); + return NS_ERROR_NOT_AVAILABLE; + } + + // Can we check the content type of the current content viewer + // and reuse it without destroying it and re-creating it? + + NS_ASSERTION(mLoadGroup, "Someone ignored return from Init()?"); + + // Instantiate the content viewer object + nsCOMPtr<nsIContentViewer> viewer; + nsresult rv = NewContentViewerObj(aContentType, aRequest, mLoadGroup, + aContentHandler, getter_AddRefs(viewer)); + + if (NS_FAILED(rv)) { + return rv; + } + + // Notify the current document that it is about to be unloaded!! + // + // It is important to fire the unload() notification *before* any state + // is changed within the DocShell - otherwise, javascript will get the + // wrong information :-( + // + + if (mSavingOldViewer) { + // We determined that it was safe to cache the document presentation + // at the time we initiated the new load. We need to check whether + // it's still safe to do so, since there may have been DOM mutations + // or new requests initiated. + RefPtr<Document> doc = viewer->GetDocument(); + mSavingOldViewer = CanSavePresentation(mLoadType, aRequest, doc); + } + + NS_ASSERTION(!mLoadingURI, "Re-entering unload?"); + + nsCOMPtr<nsIChannel> aOpenedChannel = do_QueryInterface(aRequest); + if (aOpenedChannel) { + aOpenedChannel->GetURI(getter_AddRefs(mLoadingURI)); + } + FirePageHideNotification(!mSavingOldViewer); + if (mIsBeingDestroyed) { + // Force to stop the newly created orphaned viewer. + viewer->Stop(); + return NS_ERROR_DOCSHELL_DYING; + } + mLoadingURI = nullptr; + + // Set mFiredUnloadEvent = false so that the unload handler for the + // *new* document will fire. + mFiredUnloadEvent = false; + + // we've created a new document so go ahead and call + // OnNewURI(), but don't fire OnLocationChange() + // notifications before we've called Embed(). See bug 284993. + mURIResultedInDocument = true; + bool errorOnLocationChangeNeeded = false; + nsCOMPtr<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, 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, + false, 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); + } + + if (DocGroup::TryToLoadIframesInBackground()) { + if ((!mContentViewer || GetDocument()->IsInitialDocument()) && IsFrame()) { + // At this point, we know we just created a new iframe document based on + // the response from the server, and we check if it's a cross-domain + // iframe + + RefPtr<Document> newDoc = viewer->GetDocument(); + + RefPtr<nsDocShell> parent = GetInProcessParentDocshell(); + nsCOMPtr<nsIPrincipal> parentPrincipal = + parent->GetDocument()->NodePrincipal(); + nsCOMPtr<nsIPrincipal> thisPrincipal = newDoc->NodePrincipal(); + + SiteIdentifier parentSite; + SiteIdentifier thisSite; + + nsresult rv = + BasePrincipal::Cast(parentPrincipal)->GetSiteIdentifier(parentSite); + NS_ENSURE_SUCCESS(rv, rv); + + rv = BasePrincipal::Cast(thisPrincipal)->GetSiteIdentifier(thisSite); + NS_ENSURE_SUCCESS(rv, rv); + + if (!parentSite.Equals(thisSite)) { +#ifdef MOZ_GECKO_PROFILER + nsCOMPtr<nsIURI> prinURI; + BasePrincipal::Cast(thisPrincipal)->GetURI(getter_AddRefs(prinURI)); + nsPrintfCString marker("Iframe loaded in background: %s", + prinURI->GetSpecOrDefault().get()); + PROFILER_MARKER_TEXT("Background Iframe", DOM, {}, marker); +#endif + SetBackgroundLoadIframe(); + } + } + } + + NS_ENSURE_SUCCESS(Embed(viewer, nullptr, false, + ShouldAddToSessionHistory(finalURI, aOpenedChannel)), + NS_ERROR_FAILURE); + + if (!mBrowsingContext->GetHasLoadedNonInitialDocument()) { + MOZ_ALWAYS_SUCCEEDS(mBrowsingContext->SetHasLoadedNonInitialDocument(true)); + } + + if (TreatAsBackgroundLoad()) { + nsCOMPtr<nsIRunnable> triggerParentCheckDocShell = + NewRunnableMethod("nsDocShell::TriggerParentCheckDocShellIsEmpty", this, + &nsDocShell::TriggerParentCheckDocShellIsEmpty); + nsresult rv = NS_DispatchToCurrentThread(triggerParentCheckDocShell); + NS_ENSURE_SUCCESS(rv, rv); + } + + mSavedRefreshURIList = nullptr; + mSavingOldViewer = false; + mEODForCurrentDocument = false; + + // if this document is part of a multipart document, + // the ID can be used to distinguish it from the other parts. + nsCOMPtr<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); + } + } + } + + // Give hint to native plevent dispatch mechanism. If a document + // is loading the native plevent dispatch mechanism should favor + // performance over normal native event dispatch priorities. + if (++gNumberOfDocumentsLoading == 1) { + // Hint to favor performance for the plevent notification mechanism. + // We want the pages to load as fast as possible even if its means + // native messages might be starved. + FavorPerformanceHint(true); + } + + if (errorOnLocationChangeNeeded) { + FireOnLocationChange(this, failedChannel, failedURI, + LOCATION_CHANGE_ERROR_PAGE); + } else if (onLocationChangeNeeded) { + uint32_t locationFlags = + (mLoadType & LOAD_CMD_RELOAD) ? uint32_t(LOCATION_CHANGE_RELOAD) : 0; + FireOnLocationChange(this, aRequest, mCurrentURI, locationFlags); + } + + return NS_OK; +} + +nsresult nsDocShell::NewContentViewerObj(const nsACString& aContentType, + nsIRequest* aRequest, + nsILoadGroup* aLoadGroup, + nsIStreamListener** aContentHandler, + nsIContentViewer** aViewer) { + nsCOMPtr<nsIChannel> aOpenedChannel = do_QueryInterface(aRequest); + + nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory = + nsContentUtils::FindInternalContentViewer(aContentType); + if (!docLoaderFactory) { + return NS_ERROR_FAILURE; + } + + // Now create an instance of the content viewer nsLayoutDLF makes the + // determination if it should be a "view-source" instead of "view" + nsresult rv = docLoaderFactory->CreateInstance( + "view", aOpenedChannel, aLoadGroup, aContentType, this, nullptr, + aContentHandler, aViewer); + NS_ENSURE_SUCCESS(rv, rv); + + (*aViewer)->SetContainer(this); + return NS_OK; +} + +nsresult nsDocShell::SetupNewViewer(nsIContentViewer* aNewViewer, + WindowGlobalChild* aWindowActor) { + MOZ_ASSERT(!mIsBeingDestroyed); + + // + // Copy content viewer state from previous or parent content viewer. + // + // The following logic is mirrored in nsHTMLDocument::StartDocumentLoad! + // + // Do NOT to maintain a reference to the old content viewer outside + // of this "copying" block, or it will not be destroyed until the end of + // this routine and all <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* hintCharset = nullptr; + int32_t hintCharsetSource = kCharsetUninitialized; + float overrideDPPX = 1.0; + // |newMUDV| also serves as a flag to set the data from the above vars + nsCOMPtr<nsIContentViewer> newCv; + + if (mContentViewer || parent) { + nsCOMPtr<nsIContentViewer> oldCv; + if (mContentViewer) { + // 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 + oldCv = mContentViewer; + + // 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->GetContentViewer(getter_AddRefs(oldCv)); + } + + if (oldCv) { + newCv = aNewViewer; + if (newCv) { + hintCharset = oldCv->GetHintCharset(); + NS_ENSURE_SUCCESS(oldCv->GetHintCharacterSetSource(&hintCharsetSource), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(oldCv->GetOverrideDPPX(&overrideDPPX), + NS_ERROR_FAILURE); + } + } + } + + nscolor bgcolor = NS_RGBA(0, 0, 0, 0); + bool isActive = false; + // Ensure that the content viewer is destroyed *after* the GC - bug 71515 + nsCOMPtr<nsIContentViewer> contentViewer = mContentViewer; + if (contentViewer) { + // Stop any activity that may be happening in the old document before + // releasing it... + contentViewer->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 = contentViewer->GetPresShell()) { + bgcolor = presShell->GetCanvasBackground(); + isActive = presShell->IsActive(); + } + + contentViewer->Close(mSavingOldViewer ? mOSHE.get() : nullptr); + aNewViewer->SetPreviousViewer(contentViewer); + } + if (mOSHE && (!mContentViewer || !mSavingOldViewer)) { + // We don't plan to save a viewer in mOSHE; tell it to drop + // any other state it's holding. + mOSHE->SyncPresentationState(); + } + + mContentViewer = nullptr; + + // Now that we're about to switch documents, forget all of our children. + // Note that we cached them as needed up in CaptureState above. + DestroyChildren(); + + mContentViewer = aNewViewer; + + nsCOMPtr<nsIWidget> widget; + NS_ENSURE_SUCCESS(GetMainWidget(getter_AddRefs(widget)), NS_ERROR_FAILURE); + + nsIntRect bounds(x, y, cx, cy); + + mContentViewer->SetNavigationTiming(mTiming); + + if (NS_FAILED(mContentViewer->Init(widget, bounds, aWindowActor))) { + mContentViewer = 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 (newCv) { + newCv->SetHintCharset(hintCharset); + NS_ENSURE_SUCCESS(newCv->SetHintCharacterSetSource(hintCharsetSource), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(newCv->SetOverrideDPPX(overrideDPPX), 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 = mContentViewer->GetPresShell()) { + presShell->SetCanvasBackground(bgcolor); + if (isActive) { + presShell->SetIsActive(isActive); + } + } + + // XXX: It looks like the LayoutState gets restored again in Embed() + // right after the call to SetupNewViewer(...) + + // We don't show the mContentViewer 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(mContentViewer); + + 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 (!IsFrame()) { + // 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 (!IsFrame()) { + 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; +} + +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->HasLoadFlags(INTERNAL_LOAD_FLAGS_NO_OPENER) || + aLoadState->HasLoadFlags(INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER); + if (allowNamedTarget || + aLoadState->Target().LowerCaseEqualsLiteral("_self") || + aLoadState->Target().LowerCaseEqualsLiteral("_parent") || + aLoadState->Target().LowerCaseEqualsLiteral("_top")) { + targetContext = mBrowsingContext->FindWithName( + 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->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, + ""_ns, // mime guess + &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->UnsetLoadFlag(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(mContentViewer, NS_ERROR_FAILURE); + Document* doc = mContentViewer->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->HasLoadFlags(INTERNAL_LOAD_FLAGS_NO_OPENER)) { + // 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->LoadFlags() == INTERNAL_LOAD_FLAGS_NO_OPENER || + aLoadState->LoadFlags() == + (INTERNAL_LOAD_FLAGS_NO_OPENER | + INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER)); + MOZ_ASSERT(!aLoadState->PostDataStream()); + 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->SetCsp(aLoadState->Csp()); + loadState->SetInheritPrincipal( + aLoadState->HasLoadFlags(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(LOAD_LINK); + loadState->SetForceAllowDataURI( + aLoadState->HasLoadFlags(INTERNAL_LOAD_FLAGS_FORCE_ALLOW_DATA_URI)); + + loadState->SetHasValidUserGestureActivation( + aLoadState->HasValidUserGestureActivation()); + + 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->SetLoadFlag(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); + // + // 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); +} + +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; + } + } + } + + 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); + } + } + +#ifdef DEBUG + if (aState.mHistoryNavBetweenSameDoc) { + nsCOMPtr<nsIInputStream> currentPostData; + if (mozilla::SessionHistoryInParent()) { + currentPostData = mActiveEntry->GetPostData(); + } else { + currentPostData = mOSHE->GetPostData(); + } + NS_ASSERTION(currentPostData == aLoadState->PostDataStream(), + "Different POST data for entries for the same page?"); + } +#endif + + // 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()->mLoadingCurrentActiveEntry) { + 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) { +#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())); + nsCOMPtr<nsIURI> currentURI = mCurrentURI; + + // 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()); + } + + // Set the doc's URI according to the new history entry's URI. + RefPtr<Document> doc = GetDocument(); + NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); + doc->SetDocumentURI(aLoadState->URI()); + + /* 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(); + } + // 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(aLoadState->URI(), nullptr, newURITriggeringPrincipal, + newURIPrincipalToInherit, newURIPartitionedPrincipalToInherit, + newCsp, false, true, true); + + nsCOMPtr<nsIInputStream> postData; + 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 and page ident 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(); + } + + // 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 (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); + } + } + + /* 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. + * XXX HandleSameDocumentNavigation needs to be made work with + * session-history-in-parent, and then this might not be needed. + */ + SetTitleOnHistoryEntry(); + } 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())); + bool hadActiveEntry = !!mActiveEntry; + mActiveEntry = MakeUnique<SessionHistoryInfo>(mLoadingEntry->mInfo); + mBrowsingContext->SessionHistoryCommit(*mLoadingEntry, mLoadType, + hadActiveEntry, true, true); + // FIXME Need to set postdata. + SetCacheKeyOnHistoryEntry(nullptr, cacheKey); + + // 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. + SetTitleOnHistoryEntry(); + } else { + Maybe<bool> scrollRestorationIsManual; + if (mActiveEntry) { + scrollRestorationIsManual.emplace( + mActiveEntry->GetScrollRestorationIsManual()); + + // Get the postdata and page ident 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(); + } + } + + MOZ_LOG(gSHLog, LogLevel::Debug, + ("Creating an active entry on nsDocShell %p to %s", this, + aLoadState->URI()->GetSpecOrDefault().get())); + if (mActiveEntry) { + mActiveEntry = + MakeUnique<SessionHistoryInfo>(*mActiveEntry, aLoadState->URI()); + } else { + mActiveEntry = MakeUnique<SessionHistoryInfo>( + aLoadState->URI(), 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); + } + + // 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 { + // 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, aLoadState->URI(), + LOCATION_CHANGE_SAME_DOCUMENT); + } + + /* 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(aLoadState->URI()); + + SetDocCurrentStateObj(mOSHE, mActiveEntry.get()); + + // Inform the favicon service that the favicon for oldURI also + // applies to aLoadState->URI(). + CopyFavicon(currentURI, aLoadState->URI(), UsePrivateBrowsing()); + + RefPtr<nsGlobalWindowOuter> scriptGlobal = mScriptGlobal; + RefPtr<nsGlobalWindowInner> win = + scriptGlobal ? scriptGlobal->GetCurrentInnerWindowInternal() : 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, aLoadState->URI()); + } + } + + return NS_OK; +} + +static bool NavigationShouldTakeFocus(nsDocShell* aDocShell, + nsDocShellLoadState* aLoadState) { + if (!aLoadState->AllowFocusMove()) { + 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); +} + +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); + } + + if (aLoadState->TargetBrowsingContext().IsNull()) { + aLoadState->SetTargetBrowsingContext(GetBrowsingContext()); + } + + MOZ_DIAGNOSTIC_ASSERT( + aLoadState->TargetBrowsingContext() == GetBrowsingContext(), + "Load must be targeting this BrowsingContext"); + + // 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(TaskCategory::Other, 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->LoadType() != LOAD_NORMAL_EXTERNAL && + !aLoadState->PrincipalToInherit() && + (aLoadState->HasLoadFlags(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->LoadType() == LOAD_NORMAL_EXTERNAL) { + // 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 = CreateAboutBlankContentViewer(nullptr, nullptr, nullptr, nullptr); + if (NS_FAILED(rv)) { + return NS_ERROR_FAILURE; + } + + // reset loadType so we don't have to add lots of tests for + // LOAD_NORMAL_EXTERNAL after this point + aLoadState->SetLoadType(LOAD_NORMAL); + } + + mAllowKeywordFixup = + aLoadState->HasLoadFlags(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); + NS_ENSURE_SUCCESS(rv, rv); + if (shouldTakeFocus) { + mBrowsingContext->Focus(CallerType::System, IgnoreErrors()); + } + return rv; + } + + // mContentViewer->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() && mContentViewer) { + bool okToUnload; + rv = mContentViewer->PermitUnload(&okToUnload); + + if (NS_SUCCEEDED(rv) && !okToUnload) { + // The user chose not to unload the page, interrupt the + // load. + MaybeResetInitTiming(toBeReset); + return NS_OK; + } + } + + if (mTiming && isNotDownload) { + mTiming->NotifyUnloadAccepted(mCurrentURI); + } + + // Check if the webbrowser chrome wants the load to proceed; this can be + // used to cancel attempts to load URIs in the wrong process. testing + // GetPendingRedirectedChannel() helps to avoid revisiting an earlier + // redirect decision. + nsCOMPtr<nsIWebBrowserChrome3> browserChrome3 = do_GetInterface(mTreeOwner); + if (browserChrome3 && !aLoadState->GetPendingRedirectedChannel()) { + bool shouldLoad; + rv = browserChrome3->ShouldLoadURI( + this, aLoadState->URI(), aLoadState->GetReferrerInfo(), + !!aLoadState->PostDataStream(), aLoadState->TriggeringPrincipal(), + aLoadState->Csp(), &shouldLoad); + if (NS_SUCCEEDED(rv) && !shouldLoad) { + return NS_OK; + } + } + + // 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::eScreenOrientation_None) { + MOZ_ASSERT(mBrowsingContext->IsTop()); + MOZ_ALWAYS_SUCCEEDS( + mBrowsingContext->SetOrientationLock(hal::eScreenOrientation_None)); + if (mBrowsingContext->IsActive()) { + ScreenOrientation::UpdateActiveOrientationLock( + hal::eScreenOrientation_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); + + // 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 ((mContentViewer && mContentViewer->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 mContentViewer 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 (mContentViewer) { + nsCOMPtr<nsIContentViewer> prevViewer = + mContentViewer->GetPreviousViewer(); + if (prevViewer) { +#ifdef DEBUG + nsCOMPtr<nsIContentViewer> 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 + mContentViewer->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(); + if (DisplayLoadError(rv, aLoadState->URI(), nullptr, chan) && + 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 && mContentViewer) { + document = mContentViewer->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. + EnsureContentViewer(); + if (!mContentViewer) { + return nullptr; + } + document = mContentViewer->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->HasLoadFlags(INTERNAL_LOAD_FLAGS_IS_SRCDOC); + if (isSrcdoc) { + srcdoc = aLoadState->SrcdocData(); + } + + if (aLoadState->PrincipalToInherit()) { + aLoadInfo->SetPrincipalToInherit(aLoadState->PrincipalToInherit()); + } + aLoadInfo->SetLoadTriggeredFromExternal(aLoadState->LoadType() == + LOAD_NORMAL_EXTERNAL); + aLoadInfo->SetForceAllowDataURI( + aLoadState->HasLoadFlags(INTERNAL_LOAD_FLAGS_FORCE_ALLOW_DATA_URI)); + aLoadInfo->SetOriginalFrameSrcLoad( + aLoadState->HasLoadFlags(INTERNAL_LOAD_FLAGS_ORIGINAL_FRAME_SRC)); + + bool inheritAttrs = false; + if (aLoadState->PrincipalToInherit()) { + inheritAttrs = nsContentUtils::ChannelShouldInheritPrincipal( + aLoadState->PrincipalToInherit(), aLoadState->URI(), + true, // aInheritForAboutBlank + isSrcdoc); + } + + 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); + } + + 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); + + if (nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel = + do_QueryInterface(channel)) { + // Any document load should not inherit application cache. + appCacheChannel->SetInheritApplicationCache(false); + + // Loads with the correct permissions should check for a matching + // application cache. + if (GeckoProcessType_Default != XRE_GetProcessType()) { + // Permission will be checked in the parent process + appCacheChannel->SetChooseApplicationCache(true); + } else if (aDocShell) { + // TODO: Figure out how to handle this in the parent, + // on behalf of a content process. + nsCOMPtr<nsIScriptSecurityManager> secMan = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID); + + if (secMan) { + nsCOMPtr<nsIPrincipal> principal; + secMan->GetDocShellContentPrincipal(aLoadState->URI(), aDocShell, + getter_AddRefs(principal)); + appCacheChannel->SetChooseApplicationCache( + NS_ShouldCheckAppCache(principal)); + } + } + } + + // 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->HasLoadFlags(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->HasLoadFlags(INTERNAL_LOAD_FLAGS_FIRST_LOAD)) { + props->SetPropertyAsBool(u"docshell.newWindowTarget"_ns, true); + } + } + + nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(channel)); + auto loadType = aLoadState->LoadType(); + + // 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); + } + + /* 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); + } + } + + if (httpChannelInternal && aBrowsingContext->GetSandboxFlags() != 0) { + httpChannelInternal->SetHasNonEmptySandboxingFlag(true); + } + + 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; +} + +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; + } + + nsresult rv; + nsContentPolicyType contentPolicyType = DetermineContentType(); + + if (IsFrame()) { + 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. + bool doesNotReturnData = false; + NS_URIChainHasFlags(aLoadState->URI(), + nsIProtocolHandler::URI_DOES_NOT_RETURN_DATA, + &doesNotReturnData); + if (doesNotReturnData) { + // 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->SameOriginWithTop()) { + return false; + } + nsGlobalWindowInner* win = + context->TopWindowContext()->GetInnerWindow(); + return win && win->TryOpenExternalProtocolIframe(); + }(); + + if (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"); + } + + // 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()); + } + + // 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; + } + + // 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->HasLoadFlags(INTERNAL_LOAD_FLAGS_IS_SRCDOC); + bool inheritAttrs = nsContentUtils::ChannelShouldInheritPrincipal( + aLoadState->PrincipalToInherit(), aLoadState->URI(), + true, // aInheritForAboutBlank + isSrcdoc); + + inheritPrincipal = inheritAttrs && !SchemeIsData(aLoadState->URI()); + } + + 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->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 (mLoadType != LOAD_ERROR_PAGE && context && + context->HasValidTransientUserGestureActivation()) { + aLoadState->SetHasValidUserGestureActivation(true); + } + + // 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->SetTriggeringSandboxFlags(aLoadState->TriggeringSandboxFlags()); + + 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 isXFOError = false; + if (mFailedChannel) { + nsresult status; + mFailedChannel->GetStatus(&status); + isXFOError = status == NS_ERROR_XFO_VIOLATION; + } + + nsLoadFlags loadFlags = aLoadState->CalculateChannelLoadFlags( + mBrowsingContext, Some(uriModified), Some(isXFOError)); + + nsCOMPtr<nsIChannel> channel; + if (DocumentChannel::CanUseDocumentChannel(aLoadState->URI())) { + channel = DocumentChannel::CreateForDocument(aLoadState, loadInfo, + loadFlags, this, cacheKey, + uriModified, isXFOError); + 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); + } + + nsCOMPtr<nsIContentSecurityPolicy> csp = aLoadState->Csp(); + if (csp) { + // Check CSP navigate-to + bool allowsNavigateTo = false; + rv = csp->GetAllowsNavigateTo(aLoadState->URI(), + aLoadState->IsFormSubmission(), + false, /* aWasRedirected */ + false, /* aEnforceWhitelist */ + &allowsNavigateTo); + NS_ENSURE_SUCCESS(rv, rv); + + if (!allowsNavigateTo) { + return NS_ERROR_CSP_NAVIGATE_TO_VIOLATION; + } + } + + 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; +} + +void nsDocShell::UpdateMixedContentChannelForNewLoad(nsIChannel* aChannel) { + if (mLoadType == LOAD_NORMAL_ALLOW_MIXED_CONTENT || + mLoadType == LOAD_RELOAD_ALLOW_MIXED_CONTENT) { + SetMixedContentChannel(aChannel); + } else if (mMixedContentChannel) { + /* + * If the user "Disables Protection on This Page", we call + * SetMixedContentChannel for the first time, otherwise + * mMixedContentChannel is still null. + * Later, if the new channel passes a same orign check, we remember the + * users decision by calling SetMixedContentChannel using the new channel. + * This way, the user does not have to click the disable protection button + * over and over for browsing the same site. + */ + nsresult rv = + nsContentUtils::CheckSameOrigin(mMixedContentChannel, aChannel); + if (NS_FAILED(rv) || NS_FAILED(SetMixedContentChannel(aChannel))) { + SetMixedContentChannel(nullptr); + } + } +} + +nsresult nsDocShell::OpenInitializedChannel(nsIChannel* aChannel, + nsIURILoader* aURILoader, + uint32_t aOpenFlags) { + nsresult rv = NS_OK; + + UpdateMixedContentChannelForNewLoad(aChannel); + + // 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, win->EventTargetFor(TaskCategory::Other)); + docChannel->SetInitialClientInfo(GetInitialClientInfo()); + } else { + rv = AddClientChannelHelper(aChannel, std::move(noReservedClient), + GetInitialClientInfo(), + win->EventTargetFor(TaskCategory::Other)); + } + 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); + + UpdateMixedContentChannelForNewLoad(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()); + } + // TODO: more attributes need to be updated on the LoadInfo (bug 1561706) + + // 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, + win->EventTargetFor(TaskCategory::Other)); + + 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; +} + +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 (!aNewHash.IsEmpty()) { + // anchor is there, but if it's a load from history, + // we don't have any anchor jumping to do + bool scroll = aLoadType != LOAD_HISTORY && aLoadType != LOAD_RELOAD_NORMAL; + + // We assume that the bytes are in UTF-8, as it says in the + // spec: + // http://www.w3.org/TR/html4/appendix/notes.html#h-B.2.1 + + // We try the UTF-8 string first, and then try the document's + // charset (see below). If the string is not UTF-8, + // conversion will fail and give us an empty Unicode string. + // In that case, we should just fall through to using the + // page's charset. + nsresult rv = NS_ERROR_FAILURE; + NS_ConvertUTF8toUTF16 uStr(aNewHash); + if (!uStr.IsEmpty()) { + rv = presShell->GoToAnchor(uStr, scroll, ScrollFlags::ScrollSmoothAuto); + } + + if (NS_FAILED(rv)) { + char* str = ToNewCString(aNewHash, mozilla::fallible); + if (!str) { + return NS_ERROR_OUT_OF_MEMORY; + } + nsUnescape(str); + NS_ConvertUTF8toUTF16 utf16Str(str); + if (!utf16Str.IsEmpty()) { + rv = presShell->GoToAnchor(utf16Str, scroll, + ScrollFlags::ScrollSmoothAuto); + } + free(str); + } + + // Above will fail if the anchor name is not UTF-8. Need to + // convert from document charset to unicode. + if (NS_FAILED(rv)) { + // Get a document charset + NS_ENSURE_TRUE(mContentViewer, NS_ERROR_FAILURE); + Document* doc = mContentViewer->GetDocument(); + NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); + nsAutoCString charset; + doc->GetDocumentCharacterSet()->Name(charset); + + nsCOMPtr<nsITextToSubURI> textToSubURI = + do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Unescape and convert to unicode + nsAutoString uStr; + + rv = textToSubURI->UnEscapeAndConvert(charset, aNewHash, uStr); + NS_ENSURE_SUCCESS(rv, rv); + + // Ignore 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). + // + // When aNewHash contains "%00", unescaped string may be empty. + // And GoToAnchor asserts if we ask it to scroll to an empty ref. + presShell->GoToAnchor(uStr, scroll && !uStr.IsEmpty(), + ScrollFlags::ScrollSmoothAuto); + } + } else { + // Tell the shell it's at an anchor, without scrolling. + presShell->GoToAnchor(u""_ns, false); + + // An empty anchor was found, but if it's a load from history, + // we don't have to jump to the top of the page. Scrollbar + // position will be restored by the caller, based on positions + // stored in session history. + if (aLoadType == LOAD_HISTORY || aLoadType == LOAD_RELOAD_NORMAL) { + return NS_OK; + } + // An empty anchor. 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; +} + +void nsDocShell::SetupReferrerInfoFromChannel(nsIChannel* aChannel) { + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel)); + if (httpChannel) { + nsCOMPtr<nsIReferrerInfo> referrerInfo = httpChannel->GetReferrerInfo(); + SetReferrerInfo(referrerInfo); + } +} + +bool nsDocShell::OnNewURI(nsIURI* aURI, nsIChannel* aChannel, + nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aPrincipalToInherit, + nsIPrincipal* aPartitionedPrincipalToInherit, + nsIContentSecurityPolicy* aCsp, + bool aFireOnLocationChange, 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 || IsFrame(), + "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)) { + 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, aFireOnLocationChange, locationFlags); + // Make sure to store the referrer from the channel, if any + SetupReferrerInfoFromChannel(aChannel); + return onLocationChangeNeeded; +} + +void nsDocShell::SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) { + mReferrerInfo = aReferrerInfo; // This assigment addrefs +} + +//***************************************************************************** +// 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 EvictOutOfRangeContentViewers 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. + // Default max length: 2097152 (0x200000) bytes. + int32_t maxStateObjSize = + Preferences::GetInt("browser.history.maxStateObjectSize", 2097152); + 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 + + // 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, + /* 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; + if (mActiveEntry) { + title = mActiveEntry->GetTitle(); + } + UpdateActiveEntry( + true, /* aPreviousScrollPos = */ Nothing(), aNewURI, aNewURI, + 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); + // 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()->EvictContentViewersOrReplaceEntry(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); + // We can't trust SetCurrentURI to do always fire locationchange events + // when we expect it to, so we hack around that by doing it ourselves... + SetCurrentURI(aNewURI, nullptr, false, LOCATION_CHANGE_SAME_DOCUMENT); + if (mLoadType != LOAD_ERROR_PAGE) { + FireDummyOnLocationChange(); + } + + 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: + if (buf.EqualsLiteral("newtab")) { + 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; + 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; + 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)); + + // 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, loadReplace, referrerInfo, srcdoc, + srcdocEntry, baseURI, saveLayoutState, expired); + + 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, mChildOffset, 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, 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 (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->SetTitle(aTitle); + mActiveEntry->SetStateData(static_cast<nsStructuredCloneContainer*>(aData)); + mActiveEntry->SetURIWasModified(aURIWasModified); + mActiveEntry->SetScrollRestorationIsManual(aScrollRestorationIsManual); + + if (replace) { + mBrowsingContext->ReplaceActiveSessionHistoryEntry(mActiveEntry.get()); + } else { + // 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) { + NS_ENSURE_TRUE(aEntry, NS_ERROR_FAILURE); + + nsresult rv; + RefPtr<nsDocShellLoadState> loadState; + rv = aEntry->CreateLoadInfo(getter_AddRefs(loadState)); + NS_ENSURE_SUCCESS(rv, rv); + + // Calling CreateAboutBlankContentViewer 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); + + return LoadHistoryEntry(loadState, aLoadType, aEntry == mOSHE); +} + +nsresult nsDocShell::LoadHistoryEntry(const LoadingSessionHistoryInfo& aEntry, + uint32_t aLoadType) { + RefPtr<nsDocShellLoadState> loadState = aEntry.CreateLoadInfo(); + return LoadHistoryEntry(loadState, aLoadType, + aEntry.mLoadingCurrentActiveEntry); +} + +nsresult nsDocShell::LoadHistoryEntry(nsDocShellLoadState* aLoadState, + uint32_t aLoadType, + bool aReloadingActiveEntry) { + 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 = CreateAboutBlankContentViewer( + aLoadState->PrincipalToInherit(), + aLoadState->PartitionedPrincipalToInherit(), nullptr, nullptr, + Nothing(), !aReloadingActiveEntry); + + 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::CreateWithInheritedAttributes(this); + 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->AsHTMLEditor() : 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 = props->GetPropertyAsInterface(u"docshell.previousURI"_ns, + NS_GET_IID(nsIURI), + reinterpret_cast<void**>(aURI)); + + if (NS_FAILED(rv)) { + // 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. + (void)NS_GetReferrerFromChannel(aChannel, aURI); + } else { + 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"); + } +} + +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) { + 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 = services::GetHistory(); + + 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; + } + + mozilla::Unused << history->VisitURI(aWidget, aURI, aPreviousURI, + visitURIFlags); + } +} + +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); +} + +//***************************************************************************** +// 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::services::GetStringBundleService(); + 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; + } + + nsresult rv = NS_ERROR_FAILURE; + nsAutoString target; + + nsCOMPtr<nsIWebBrowserChrome3> browserChrome3 = do_GetInterface(mTreeOwner); + bool noOpenerImplied = false; + if (browserChrome3) { + rv = browserChrome3->OnBeforeLinkTraversal(aTargetSpec, aURI, aContent, + mIsAppTab, target); + if (!aTargetSpec.Equals(target)) { + noOpenerImplied = true; + } + } + + if (NS_FAILED(rv)) { + target = aTargetSpec; + } + + 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()); + + nsCOMPtr<nsIRunnable> ev = + new OnLinkClickEvent(this, aContent, loadState, noOpenerImplied, + aIsTrusted, aTriggeringPrincipal); + return Dispatch(TaskCategory::UI, ev.forget()); +} + +static bool IsElementAnchorOrArea(nsIContent* aContent) { + // Make sure we are dealing with either an <A> or <AREA> element in the HTML + // or XHTML namespace. + return aContent->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area); +} + +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, + mBrowsingContext); + } + } + } + } + uint32_t triggeringSandboxFlags = 0; + if (mBrowsingContext) { + triggeringSandboxFlags = mBrowsingContext->GetSandboxFlags(); + } + + uint32_t flags = INTERNAL_LOAD_FLAGS_NONE; + bool isElementAnchorOrArea = IsElementAnchorOrArea(aContent); + bool triggeringPrincipalIsSystemPrincipal = + aLoadState->TriggeringPrincipal()->IsSystemPrincipal(); + if (isElementAnchorOrArea) { + MOZ_ASSERT(aContent->IsHTMLElement()); + nsAutoString relString; + aContent->AsElement()->GetAttr(kNameSpaceID_None, 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(); + NS_ENSURE_TRUE(referrerInner, NS_ERROR_UNEXPECTED); + if (!mScriptGlobal || + 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); + } + + // Link click (or form submission) can be triggered inside an onload + // handler, and we don't want to add history entry in this case. + bool inOnLoadHandler = false; + GetIsExecutingOnLoadHandler(&inOnLoadHandler); + uint32_t loadType = inOnLoadHandler ? LOAD_NORMAL_REPLACE : LOAD_LINK; + + nsCOMPtr<nsIReferrerInfo> referrerInfo = + isElementAnchorOrArea ? new ReferrerInfo(*aContent->AsElement()) + : new ReferrerInfo(*referrerDoc); + RefPtr<WindowContext> context = mBrowsingContext->GetCurrentWindowContext(); + + aLoadState->SetTriggeringSandboxFlags(triggeringSandboxFlags); + aLoadState->SetReferrerInfo(referrerInfo); + aLoadState->SetLoadFlags(flags); + aLoadState->SetTypeHint(NS_ConvertUTF16toUTF8(typeHint)); + aLoadState->SetLoadType(loadType); + aLoadState->SetSourceBrowsingContext(mBrowsingContext); + aLoadState->SetAllowFocusMove(true); + 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; +} + +bool nsDocShell::PluginsAllowedInCurrentDoc() { + if (!mContentViewer) { + return false; + } + + Document* doc = mContentViewer->GetDocument(); + if (!doc) { + return false; + } + + return doc->GetAllowPlugins(); +} + +//---------------------------------------------------------------------- +// Web Shell Services API + +// This functions is only called when a new charset is detected in loading a +// document. +nsresult nsDocShell::CharsetChangeReloadDocument(const char* aCharset, + int32_t aSource) { + // XXX hack. keep the aCharset and aSource wait to pick it up + nsCOMPtr<nsIContentViewer> cv; + NS_ENSURE_SUCCESS(GetContentViewer(getter_AddRefs(cv)), NS_ERROR_FAILURE); + if (cv) { + int32_t hint; + cv->GetHintCharacterSetSource(&hint); + if (aSource > hint) { + nsCString charset(aCharset); + cv->SetHintCharacterSet(charset); + cv->SetHintCharacterSetSource(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(mContentViewer); + return viewer->ExitPrintPreview(); +#else + return NS_OK; +#endif +} + +NS_IMETHODIMP +nsDocShell::GetCanExecuteScripts(bool* aResult) { + *aResult = mCanExecuteScripts; + return NS_OK; +} + +/* [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); + + 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()->Cancel(NS_BINDING_ABORTED); + 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->IsCached() || 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 (IsFrame()) { + return; + } + + if (nsCOMPtr<IHistory> history = services::GetHistory()) { + history->SetURITitle(aURI, mTitle); + } +} + +bool nsDocShell::IsInvisible() { return mInvisible; } + +void nsDocShell::SetInvisible(bool aInvisible) { mInvisible = aInvisible; } + +// The caller owns |aAsyncCause| here. +void nsDocShell::NotifyJSRunToCompletionStart(const char* aReason, + const nsAString& aFunctionName, + const nsAString& aFilename, + const uint32_t aLineNumber, + JS::Handle<JS::Value> aAsyncStack, + const char* aAsyncCause) { + // If first start, mark interval start. + if (mJSRunToCompletionDepth == 0) { + RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); + if (timelines && timelines->HasConsumer(this)) { + timelines->AddMarkerForDocShell( + this, mozilla::MakeUnique<JavascriptTimelineMarker>( + aReason, aFunctionName, aFilename, aLineNumber, + MarkerTracingType::START, aAsyncStack, aAsyncCause)); + } + } + + mJSRunToCompletionDepth++; +} + +void nsDocShell::NotifyJSRunToCompletionStop() { + mJSRunToCompletionDepth--; + + // If last stop, mark interval end. + if (mJSRunToCompletionDepth == 0) { + RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); + if (timelines && timelines->HasConsumer(this)) { + timelines->AddMarkerForDocShell(this, "Javascript", + MarkerTracingType::END); + } + } +} + +/* static */ +void nsDocShell::MaybeNotifyKeywordSearchLoading(const nsString& aProvider, + const nsString& aKeyword) { + if (aProvider.IsEmpty()) { + return; + } + + nsCOMPtr<nsISearchService> searchSvc = + do_GetService("@mozilla.org/browser/search-service;1"); + if (searchSvc) { + nsCOMPtr<nsISearchEngine> searchEngine; + searchSvc->GetEngineByName(aProvider, getter_AddRefs(searchEngine)); + if (searchEngine) { + 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(searchEngine, "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::IssueWarning(uint32_t aWarning, bool aAsError) { + if (mContentViewer) { + RefPtr<Document> doc = mContentViewer->GetDocument(); + if (doc) { + doc->WarnOnceAbout(DeprecatedOperations(aWarning), aAsError); + } + } + return NS_OK; +} + +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 false; +} + +void nsDocShell::SetLoadingSessionHistoryInfo( + const mozilla::dom::LoadingSessionHistoryInfo& aLoadingInfo) { + // 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); +} + +void nsDocShell::MoveLoadingToActiveEntry(bool aPersist) { + MOZ_ASSERT(mozilla::SessionHistoryInParent()); + + MOZ_LOG(gSHLog, LogLevel::Debug, + ("nsDocShell %p MoveLoadingToActiveEntry", this)); + + bool hadActiveEntry = !!mActiveEntry; + mActiveEntry = nullptr; + 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 (mActiveEntry) { + MOZ_ASSERT(loadingEntry); + uint32_t loadType = + mLoadType == LOAD_ERROR_PAGE ? mFailedLoadType : mLoadType; + mBrowsingContext->SessionHistoryCommit(*loadingEntry, loadType, + hadActiveEntry, aPersist, false); + } +} diff --git a/docshell/base/nsDocShell.h b/docshell/base/nsDocShell.h new file mode 100644 index 0000000000..9e0d878a7e --- /dev/null +++ b/docshell/base/nsDocShell.h @@ -0,0 +1,1281 @@ +/* -*- 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/Maybe.h" +#include "mozilla/ScrollbarPreferences.h" +#include "mozilla/TimelineConsumers.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 "nsIDeprecationWarner.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; +} // namespace dom +namespace net { +class LoadInfo; +class DocumentLoadListener; +} // namespace net +} // namespace mozilla + +class nsIContentViewer; +class nsIController; +class nsIDocShellTreeOwner; +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 nsIDeprecationWarner, + 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 + NS_DECL_NSIDEPRECATIONWARNER + + // 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(); + + // 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, + int32_t aDelay, bool aMetaRefresh, + 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); + } + + // This function is created exclusively for dom.background_loading_iframe is + // set. As soon as the current DocShell knows itself can be treated as + // background loading, it triggers the parent docshell to see if the parent + // document can fire load event earlier. + void TriggerParentCheckDocShellIsEmpty(); + + 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(); + + const mozilla::Encoding* GetForcedCharset() { return mForcedCharset; } + + bool GetForcedAutodetection() { return mForcedAutodetection; } + + mozilla::HTMLEditor* GetHTMLEditorInternal(); + nsresult SetHTMLEditorInternal(mozilla::HTMLEditor* aHTMLEditor); + + // Handle page navigation due to charset changes + nsresult CharsetChangeReloadDocument(const char* aCharset = nullptr, + int32_t aSource = kCharsetUninitialized); + 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()); + + // Clear the document's storage access flag if needed. + void MaybeClearStorageAccessFlag(); + + 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 CreateContentViewerForActor( + 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); + + 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 HasContentViewer() const { return !!mContentViewer; } + + static uint32_t ComputeURILoaderFlags( + mozilla::dom::BrowsingContext* aBrowsingContext, uint32_t aLoadType); + + void SetLoadingSessionHistoryInfo( + const mozilla::dom::LoadingSessionHistoryInfo& aLoadingInfo); + + already_AddRefed<nsIInputStream> GetPostDataFromCurrentEntry() const; + mozilla::Maybe<uint32_t> GetCacheKeyFromCurrentEntry() const; + + // Loading and/or active entries are only set when pref + // fission.sessionHistoryInParent 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(); + + private: // member functions + 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; + + // It is necessary to allow adding a timeline marker wherever a docshell + // instance is available. This operation happens frequently and needs to + // be very fast, so instead of using a Map or having to search for some + // docshell-specific markers storage, a pointer to an `ObservedDocShell` is + // is stored on docshells directly. + friend void mozilla::TimelineConsumers::AddConsumer(nsDocShell*); + friend void mozilla::TimelineConsumers::RemoveConsumer(nsDocShell*); + friend void mozilla::TimelineConsumers::AddMarkerForDocShell( + nsDocShell*, const char*, MarkerTracingType, MarkerStackRequest); + friend void mozilla::TimelineConsumers::AddMarkerForDocShell( + nsDocShell*, const char*, const TimeStamp&, MarkerTracingType, + MarkerStackRequest); + friend void mozilla::TimelineConsumers::AddMarkerForDocShell( + nsDocShell*, UniquePtr<AbstractTimelineMarker>&&); + friend void mozilla::TimelineConsumers::PopMarkers( + nsDocShell*, JSContext*, nsTArray<dom::ProfileTimelineMarker>&); + + nsDocShell(mozilla::dom::BrowsingContext* aBrowsingContext, + uint64_t aContentWindowID); + + // Security check to prevent frameset spoofing. See comments at + // implementation site. + static bool ValidateOrigin(mozilla::dom::BrowsingContext* aOrigin, + mozilla::dom::BrowsingContext* aTarget); + + 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 EnsureContentViewer(); + + // 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 CreateAboutBlankContentViewer( + nsIPrincipal* aPrincipal, nsIPrincipal* aPartitionedPrincipal, + nsIContentSecurityPolicy* aCSP, nsIURI* aBaseURI, + const mozilla::Maybe<nsILoadInfo::CrossOriginEmbedderPolicy>& aCOEP = + mozilla::Nothing(), + bool aTryToSaveOldPresentation = true, bool aCheckPermitUnload = true, + mozilla::dom::WindowGlobalChild* aActor = nullptr); + + nsresult CreateContentViewer(const nsACString& aContentType, + nsIRequest* aRequest, + nsIStreamListener** aContentHandler); + + nsresult NewContentViewerObj(const nsACString& aContentType, + nsIRequest* aRequest, nsILoadGroup* aLoadGroup, + nsIStreamListener** aContentHandler, + nsIContentViewer** aViewer); + + already_AddRefed<nsILoadURIDelegate> GetLoadURIDelegate(); + + nsresult SetupNewViewer( + nsIContentViewer* 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, 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); + + // + // 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); + + private: + // 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. + // 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* aPartitionedPrincipalToInehrit, + nsIContentSecurityPolicy* aCsp, bool aFireOnLocationChange, + bool aAddToGlobalHistory, bool aCloneSHChildren); + + public: + // 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); + + 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, uint32_t aLocationFlags); + + // The following methods deal with saving and restoring content viewers + // in session history. + + // mContentViewer 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 + // mContentViewer. + // 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 mContentViewer and removed from the history entry. + // (mContentViewer is put into session history as described above, if + // applicable). + + // Determines whether we can safely cache the current mContentViewer 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); + + void ReportBFCacheComboTelemetry(uint16_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. + void FirePageHideNotificationInternal(bool aIsUnload, + bool aSkipCheckingDynEntries); + + nsresult Dispatch(mozilla::TaskCategory aCategory, + already_AddRefed<nsIRunnable>&& aRunnable); + + void SetupReferrerInfoFromChannel(nsIChannel* aChannel); + void SetReferrerInfo(nsIReferrerInfo* aReferrerInfo); + void ReattachEditorToWindow(nsISHEntry* aSHEntry); + void RecomputeCanExecuteScripts(); + void ClearFrameHistory(nsISHEntry* aEntry); + // Determine if this type of load should update history. + static bool ShouldUpdateGlobalHistory(uint32_t aLoadType); + void UpdateGlobalHistoryTitle(nsIURI* aURI); + bool IsFrame() { return mBrowsingContext->IsFrame(); } + bool CanSetOriginAttributes(); + bool ShouldBlockLoadingForBackButton(); + 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(); + nsresult Embed(nsIContentViewer* aContentViewer, + mozilla::dom::WindowGlobalChild* aWindowActor, + bool aIsTransientAboutBlank, bool aPersist); + nsPresContext* GetEldestPresContext(); + nsresult CheckLoadingPermissions(); + nsresult LoadHistoryEntry(nsISHEntry* aEntry, uint32_t aLoadType); + nsresult LoadHistoryEntry( + const mozilla::dom::LoadingSessionHistoryInfo& aEntry, + uint32_t aLoadType); + nsresult LoadHistoryEntry(nsDocShellLoadState* aLoadState, uint32_t aLoadType, + bool aReloadingActiveEntry); + 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 mHistoryNavBetweenSameDoc = false; + }; + + // Check to see if we're loading a prior history entry or doing a fragment + // navigation in the same document. + 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); + + // 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(); + + void SetScrollRestorationIsManualOnHistoryEntry(nsISHEntry* aSHEntry, + bool aIsManual); + + void SetCacheKeyOnHistoryEntry(nsISHEntry* aSHEntry, uint32_t aCacheKey); + + 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. + void MoveLoadingToActiveEntry(bool aPersist); + + void ActivenessMaybeChanged(); + + private: // 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; + nsCOMPtr<nsIMutableArray> mRefreshURIList; + nsCOMPtr<nsIMutableArray> mSavedRefreshURIList; + uint64_t mContentWindowID; + nsCOMPtr<nsIContentViewer> mContentViewer; + 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; + + // An observed docshell wrapper is created when recording markers is enabled. + mozilla::UniquePtr<mozilla::ObservedDocShell> mObserved; + + // mCurrentURI should be marked immutable on set if possible. + 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 + 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.) + 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. + 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 CreateContentViewer. We have to delay an shistory entry creation + // for which these objects are needed. + nsCOMPtr<nsIURI> mFailedURI; + nsCOMPtr<nsIChannel> mFailedChannel; + + // Set in DoURILoad when either the LOAD_RELOAD_ALLOW_MIXED_CONTENT flag or + // the LOAD_NORMAL_ALLOW_MIXED_CONTENT flag is set. + // Checked in nsMixedContentBlocker, to see if the channels match. + nsCOMPtr<nsIChannel> mMixedContentChannel; + + mozilla::UniquePtr<mozilla::gfx::Matrix5x4> mColorMatrix; + + const mozilla::Encoding* mForcedCharset; + 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; + + // Offset in the parent's child list. + // -1 if the docshell is added dynamically to the parent shell. + int32_t mChildOffset; + + BusyFlags mBusyFlags; + AppType mAppType; + uint32_t mLoadType; + uint32_t mFailedLoadType; + + // A depth count of how many times NotifyRunToCompletionStart + // has been called without a matching NotifyRunToCompletionStop. + uint32_t mJSRunToCompletionDepth; + + // Whether or not handling of the <meta name="viewport"> tag is overridden. + // Possible values are defined as constants in nsIDocShell.idl. + MetaViewportOverride mMetaViewportOverride; + + // 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 mAllowJavascript : 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 mIsOffScreenBrowser : 1; + bool mDisableMetaRefreshWhenInactive : 1; + bool mIsAppTab : 1; + bool mDeviceSizeIsPageSize : 1; + bool mWindowDraggingAllowed : 1; + bool mInFrameSwap : 1; + + // Because scriptability depends on the mAllowJavascript values of our + // ancestors, we cache the effective scriptability and recompute it when + // it might have changed; + bool mCanExecuteScripts : 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 CreateContentViewer() 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. + bool mSavingOldViewer : 1; + + bool mAffectPrivateSessionLifetime : 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; + + // This flag indicates whether the media in this docshell should be suspended + // when the docshell is inactive. + bool mSuspendMediaWhenInactive : 1; + + // Whether we have a pending encoding autodetection request from the + // menu for all encodings. + bool mForcedAutodetection : 1; +}; + +#endif /* nsDocShell_h__ */ diff --git a/docshell/base/nsDocShellEditorData.cpp b/docshell/base/nsDocShellEditorData.cpp new file mode 100644 index 0000000000..890191aaf4 --- /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(false); + } + 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(false); + } + + 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(false); + 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..b1409b3b52 --- /dev/null +++ b/docshell/base/nsDocShellLoadState.cpp @@ -0,0 +1,957 @@ +/* -*- 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 "nsISHEntry.h" +#include "nsIURIFixup.h" +#include "nsIWebNavigation.h" +#include "nsIChannel.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/LoadURIOptionsBinding.h" +#include "mozilla/StaticPrefs_browser.h" +#include "mozilla/StaticPrefs_fission.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) + : mNotifiedBeforeUnloadListeners(false), + mLoadIdentifier(aLoadState.LoadIdentifier()) { + MOZ_ASSERT(aLoadState.URI(), "Cannot create a LoadState with a null URI!"); + mResultPrincipalURI = aLoadState.ResultPrincipalURI(); + mResultPrincipalURIIsSome = aLoadState.ResultPrincipalURIIsSome(); + mKeepResultPrincipalURIIfSet = aLoadState.KeepResultPrincipalURIIfSet(); + mLoadReplace = aLoadState.LoadReplace(); + mInheritPrincipal = aLoadState.InheritPrincipal(); + mPrincipalIsExplicit = aLoadState.PrincipalIsExplicit(); + mForceAllowDataURI = aLoadState.ForceAllowDataURI(); + mOriginalFrameSrc = aLoadState.OriginalFrameSrc(); + mIsFormSubmission = aLoadState.IsFormSubmission(); + mLoadType = aLoadState.LoadType(); + mTarget = aLoadState.Target(); + mTargetBrowsingContext = aLoadState.TargetBrowsingContext(); + mLoadFlags = aLoadState.LoadFlags(); + 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(); + mCsp = aLoadState.Csp(); + mOriginalURIString = aLoadState.OriginalURIString(); + mCancelContentJSEpoch = aLoadState.CancelContentJSEpoch(); + mPostDataStream = aLoadState.PostDataStream(); + mHeadersStream = aLoadState.HeadersStream(); + mSrcdocData = aLoadState.SrcdocData(); + mChannelInitialized = aLoadState.ChannelInitialized(); + if (aLoadState.loadingSessionHistoryInfo().isSome()) { + mLoadingSessionHistoryInfo = MakeUnique<LoadingSessionHistoryInfo>( + aLoadState.loadingSessionHistoryInfo().ref()); + } +} + +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), + 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), + 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), + 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) { + if (aOther.mLoadingSessionHistoryInfo) { + mLoadingSessionHistoryInfo = MakeUnique<LoadingSessionHistoryInfo>( + *aOther.mLoadingSessionHistoryInfo); + } +} + +nsDocShellLoadState::nsDocShellLoadState(nsIURI* aURI, uint64_t aLoadIdentifier) + : mURI(aURI), + mResultPrincipalURIIsSome(false), + mTriggeringSandboxFlags(0), + mKeepResultPrincipalURIIfSet(false), + mLoadReplace(false), + mInheritPrincipal(false), + mPrincipalIsExplicit(false), + mNotifiedBeforeUnloadListeners(false), + mForceAllowDataURI(false), + mOriginalFrameSrc(false), + mIsFormSubmission(false), + mLoadType(LOAD_NORMAL), + mTarget(), + mSrcdocData(VoidString()), + mLoadFlags(0), + mFirstParty(false), + mHasValidUserGestureActivation(false), + mAllowFocusMove(false), + mTypeHint(VoidCString()), + mFileName(VoidString()), + mIsFromProcessingFrameAttributes(false), + mLoadIdentifier(aLoadIdentifier), + mChannelInitialized(false) { + MOZ_ASSERT(aURI, "Cannot create a LoadState with a null URI!"); +} + +nsDocShellLoadState::~nsDocShellLoadState() {} + +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; + nsCOMPtr<nsIInputStream> postData(aLoadURIOptions.mPostData); + 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; + bool didFixup = false; + 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; + } + + RefPtr<nsIInputStream> fixupStream; + 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); + fixupInfo->GetPostData(getter_AddRefs(fixupStream)); + didFixup = true; + + 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()); + } + } + } + } + + if (fixupStream) { + // 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. + postData = fixupStream; + } + } + + if (rv == NS_ERROR_MALFORMED_URI) { + MOZ_ASSERT(!uri); + return rv; + } + + if (NS_FAILED(rv) || !uri) { + return NS_ERROR_FAILURE; + } + + 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(uri); + loadState->SetReferrerInfo(aLoadURIOptions.mReferrerInfo); + + /* + * If the user "Disables Protection on This Page", we have to make sure to + * remember the users decision when opening links in child tabs [Bug 906190] + */ + if (loadFlags & nsIWebNavigation::LOAD_FLAGS_ALLOW_MIXED_CONTENT) { + loadState->SetLoadType( + MAKE_LOAD_TYPE(LOAD_NORMAL_ALLOW_MIXED_CONTENT, loadFlags)); + } else { + loadState->SetLoadType(MAKE_LOAD_TYPE(LOAD_NORMAL, loadFlags)); + } + + loadState->SetLoadFlags(extraFlags); + loadState->SetFirstParty(true); + loadState->SetHasValidUserGestureActivation( + aLoadURIOptions.mHasValidUserGestureActivation); + loadState->SetTriggeringSandboxFlags(aLoadURIOptions.mTriggeringSandboxFlags); + loadState->SetPostDataStream(postData); + loadState->SetHeadersStream(aLoadURIOptions.mHeaders); + loadState->SetBaseURI(aLoadURIOptions.mBaseURI); + loadState->SetTriggeringPrincipal(aLoadURIOptions.mTriggeringPrincipal); + loadState->SetCsp(aLoadURIOptions.mCsp); + loadState->SetForceAllowDataURI(forceAllowDataURI); + loadState->SetOriginalURIString(uriString); + if (aLoadURIOptions.mCancelContentJSEpoch) { + loadState->SetCancelContentJSEpoch(aLoadURIOptions.mCancelContentJSEpoch); + } + + if (didFixup) { + nsDocShell::MaybeNotifyKeywordSearchLoading(searchProvider, keyword); + } + + 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; +} + +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::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 aRequestedIndex, int32_t aSessionHistoryLength, + bool aLoadingFromActiveEntry) { + if (mLoadingSessionHistoryInfo) { + mLoadingSessionHistoryInfo->mLoadIsFromSessionHistory = true; + mLoadingSessionHistoryInfo->mRequestedIndex = aRequestedIndex; + mLoadingSessionHistoryInfo->mSessionHistoryLength = aSessionHistoryLength; + mLoadingSessionHistoryInfo->mLoadingCurrentActiveEntry = + aLoadingFromActiveEntry; + } +} + +void nsDocShellLoadState::ClearLoadIsFromSessionHistory() { + if (mLoadingSessionHistoryInfo) { + mLoadingSessionHistoryInfo->mLoadIsFromSessionHistory = false; + } + mSHEntry = nullptr; +} + +bool nsDocShellLoadState::LoadIsFromSessionHistory() const { + return mLoadingSessionHistoryInfo + ? mLoadingSessionHistoryInfo->mLoadIsFromSessionHistory + : !!mSHEntry; +} + +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; +} + +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; +} + +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::CreateWithInheritedAttributes( + aOriginAttributes, false); + 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; + // If mFirstParty is true and the pref 'privacy.firstparty.isolate' is + // enabled, we will set firstPartyDomain on the origin attributes. + mPrincipalToInherit = NullPrincipal::CreateWithInheritedAttributes( + aOriginAttributes, mFirstParty); + } + + 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() { + uint32_t oldLoadFlags = mLoadFlags; + mLoadFlags = 0; + + if (mInheritPrincipal) { + MOZ_ASSERT( + !mPrincipalToInherit || !mPrincipalToInherit->IsSystemPrincipal(), + "Should not inherit SystemPrincipal"); + mLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL; + } + + if (mReferrerInfo && !mReferrerInfo->GetSendReferrer()) { + mLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER; + } + if (oldLoadFlags & nsIWebNavigation::LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP) { + mLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP; + } + + if (oldLoadFlags & nsIWebNavigation::LOAD_FLAGS_FIRST_LOAD) { + mLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_FIRST_LOAD; + } + + if (oldLoadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_CLASSIFIER) { + mLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_BYPASS_CLASSIFIER; + } + + if (oldLoadFlags & nsIWebNavigation::LOAD_FLAGS_FORCE_ALLOW_COOKIES) { + mLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_FORCE_ALLOW_COOKIES; + } + + if (oldLoadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE) { + mLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE; + } + + if (!mSrcdocData.IsVoid()) { + mLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_IS_SRCDOC; + } + + if (mForceAllowDataURI) { + mLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_FORCE_ALLOW_DATA_URI; + } + + if (mOriginalFrameSrc) { + mLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_ORIGINAL_FRAME_SRC; + } +} + +nsLoadFlags nsDocShellLoadState::CalculateChannelLoadFlags( + BrowsingContext* aBrowsingContext, Maybe<bool> aUriModified, + Maybe<bool> aIsXFOError) { + 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, aIsXFOError.isSome()); + + if (loadType == LOAD_ERROR_PAGE) { + // Error pages are LOAD_BACKGROUND, unless it's an + // XFO error for which we want an error page to load + // but additionally want the onload() event to fire. + if (!*aIsXFOError) { + 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_RELOAD_NORMAL: + 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_NORMAL_ALLOW_MIXED_CONTENT: + case LOAD_RELOAD_BYPASS_CACHE: + case LOAD_RELOAD_BYPASS_PROXY: + case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE: + case LOAD_RELOAD_ALLOW_MIXED_CONTENT: + case LOAD_REPLACE_BYPASS_CACHE: + loadFlags |= + nsIRequest::LOAD_BYPASS_CACHE | nsIRequest::LOAD_FRESH_CONNECTION; + break; + + 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 (HasLoadFlags(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; +} + +DocShellLoadStateInit nsDocShellLoadState::Serialize() { + DocShellLoadStateInit loadState; + loadState.ResultPrincipalURI() = mResultPrincipalURI; + loadState.ResultPrincipalURIIsSome() = mResultPrincipalURIIsSome; + loadState.KeepResultPrincipalURIIfSet() = mKeepResultPrincipalURIIfSet; + loadState.LoadReplace() = mLoadReplace; + loadState.InheritPrincipal() = mInheritPrincipal; + loadState.PrincipalIsExplicit() = mPrincipalIsExplicit; + loadState.ForceAllowDataURI() = mForceAllowDataURI; + loadState.OriginalFrameSrc() = mOriginalFrameSrc; + loadState.IsFormSubmission() = mIsFormSubmission; + loadState.LoadType() = mLoadType; + loadState.Target() = mTarget; + loadState.TargetBrowsingContext() = mTargetBrowsingContext; + loadState.LoadFlags() = mLoadFlags; + 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.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; + if (mLoadingSessionHistoryInfo) { + loadState.loadingSessionHistoryInfo().emplace(*mLoadingSessionHistoryInfo); + } + return loadState; +} diff --git a/docshell/base/nsDocShellLoadState.h b/docshell/base/nsDocShellLoadState.h new file mode 100644 index 0000000000..fa4957fdec --- /dev/null +++ b/docshell/base/nsDocShellLoadState.h @@ -0,0 +1,492 @@ +/* -*- 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; +class OriginAttibutes; +namespace mozilla { +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); + 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); + + // 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); + + 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 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); + + 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); + + // 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; } + + // 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 + // aIsXFOError are expected to be Nothing when called from Parent process. + nsLoadFlags CalculateChannelLoadFlags( + mozilla::dom::BrowsingContext* aBrowsingContext, + mozilla::Maybe<bool> aUriModified, mozilla::Maybe<bool> aIsXFOError); + + mozilla::dom::DocShellLoadStateInit Serialize(); + + void SetLoadIsFromSessionHistory(int32_t aRequestedIndex, + int32_t aSessionHistoryLength, + bool aLoadingFromActiveEntry); + void ClearLoadIsFromSessionHistory(); + + protected: + // Destructor can't be defaulted or inlined, as header doesn't have all type + // includes it needs to do so. + ~nsDocShellLoadState(); + + protected: + // 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 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, 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. This is important to know for the CSP directive + // navigate-to. + 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; + + // 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; +}; + +#endif /* nsDocShellLoadState_h__ */ diff --git a/docshell/base/nsDocShellLoadTypes.h b/docshell/base/nsDocShellLoadTypes.h new file mode 100644 index 0000000000..e694843b2d --- /dev/null +++ b/docshell/base/nsDocShellLoadTypes.h @@ -0,0 +1,211 @@ +/* -*- 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_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_NORMAL_EXTERNAL = MAKE_LOAD_TYPE( + nsIDocShell::LOAD_CMD_NORMAL, nsIWebNavigation::LOAD_FLAGS_FROM_EXTERNAL), + 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_NORMAL_ALLOW_MIXED_CONTENT = + MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL, + nsIWebNavigation::LOAD_FLAGS_ALLOW_MIXED_CONTENT | + nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE), + 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_ALLOW_MIXED_CONTENT = + MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_RELOAD, + nsIWebNavigation::LOAD_FLAGS_ALLOW_MIXED_CONTENT | + nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE), + 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_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: + case LOAD_RELOAD_ALLOW_MIXED_CONTENT: + return true; + } + return false; +} + +static inline bool IsValidLoadType(uint32_t aLoadType) { + switch (aLoadType) { + case LOAD_NORMAL: + case LOAD_NORMAL_REPLACE: + case LOAD_NORMAL_EXTERNAL: + case LOAD_NORMAL_BYPASS_CACHE: + case LOAD_NORMAL_BYPASS_PROXY: + case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE: + case LOAD_NORMAL_ALLOW_MIXED_CONTENT: + 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_RELOAD_ALLOW_MIXED_CONTENT: + case LOAD_LINK: + case LOAD_REFRESH: + 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; +} + +static 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_EXTERNAL: + case LOAD_NORMAL_BYPASS_CACHE: + case LOAD_NORMAL_BYPASS_PROXY: + case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE: + case LOAD_NORMAL_REPLACE: + case LOAD_NORMAL_ALLOW_MIXED_CONTENT: + case LOAD_LINK: + case LOAD_STOP_CONTENT: + 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: + case LOAD_RELOAD_ALLOW_MIXED_CONTENT: + 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..5d06009036 --- /dev/null +++ b/docshell/base/nsDocShellTelemetryUtils.cpp @@ -0,0 +1,210 @@ +/* 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_CSP_NAVIGATE_TO_VIOLATION, + ErrorLabel::CSP_NAVIGATE_TO, + }, + { + 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_REMOTE_XUL, + ErrorLabel::REMOTE_XUL, + }, + { + 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..ff2b4dca6c --- /dev/null +++ b/docshell/base/nsDocShellTreeOwner.cpp @@ -0,0 +1,1304 @@ +/* -*- 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 "nsIConstraintValidation.h" +#include "mozilla/Attributes.h" +#include "mozilla/EventListenerManager.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 + +//***************************************************************************** +// 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::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 aShellItem to do the hack in + // AppWindow::SizeShellTo, so let's send the width and height of + // aShellItem too. + nsCOMPtr<nsIBaseWindow> shellAsWin(do_QueryInterface(aShellItem)); + NS_ENSURE_TRUE(shellAsWin, NS_ERROR_FAILURE); + + int32_t width = 0; + int32_t height = 0; + shellAsWin->GetSize(&width, &height); + return browserChild->RemoteSizeShellTo(aCX, aCY, width, height); + } + // 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; + } + + NS_ENSURE_TRUE(aShellItem, NS_ERROR_FAILURE); + + RefPtr<Document> document = aShellItem->GetDocument(); + NS_ENSURE_TRUE(document, NS_ERROR_FAILURE); + + NS_ENSURE_TRUE(document->GetDocumentElement(), NS_ERROR_FAILURE); + + // Set the preferred Size + // XXX + NS_ERROR("Implement this"); + /* + Set the preferred size on the aShellItem. + */ + + RefPtr<nsPresContext> presContext = mWebBrowser->mDocShell->GetPresContext(); + NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE); + + RefPtr<PresShell> presShell = presContext->GetPresShell(); + NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); + + NS_ENSURE_SUCCESS( + presShell->ResizeReflow(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE), + NS_ERROR_FAILURE); + + // 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; +} + +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; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::GetUnscaledDevicePixelsPerCSSPixel(double* aScale) { + if (mWebBrowser) { + return mWebBrowser->GetUnscaledDevicePixelsPerCSSPixel(aScale); + } + + *aScale = 1.0; + return NS_OK; +} + +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) { + nsCOMPtr<nsIEmbeddingSiteWindow> ownerWin = GetOwnerWin(); + if (ownerWin) { + return ownerWin->SetDimensions(nsIEmbeddingSiteWindow::DIM_FLAGS_POSITION, + aX, aY, 0, 0); + } + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::GetPosition(int32_t* aX, int32_t* aY) { + nsCOMPtr<nsIEmbeddingSiteWindow> ownerWin = GetOwnerWin(); + if (ownerWin) { + return ownerWin->GetDimensions(nsIEmbeddingSiteWindow::DIM_FLAGS_POSITION, + aX, aY, nullptr, nullptr); + } + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::SetSize(int32_t aCX, int32_t aCY, bool aRepaint) { + nsCOMPtr<nsIEmbeddingSiteWindow> ownerWin = GetOwnerWin(); + if (ownerWin) { + return ownerWin->SetDimensions(nsIEmbeddingSiteWindow::DIM_FLAGS_SIZE_OUTER, + 0, 0, aCX, aCY); + } + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::GetSize(int32_t* aCX, int32_t* aCY) { + nsCOMPtr<nsIEmbeddingSiteWindow> ownerWin = GetOwnerWin(); + if (ownerWin) { + return ownerWin->GetDimensions(nsIEmbeddingSiteWindow::DIM_FLAGS_SIZE_OUTER, + nullptr, nullptr, aCX, aCY); + } + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::SetPositionAndSize(int32_t aX, int32_t aY, int32_t aCX, + int32_t aCY, uint32_t aFlags) { + nsCOMPtr<nsIEmbeddingSiteWindow> ownerWin = GetOwnerWin(); + if (ownerWin) { + return ownerWin->SetDimensions( + nsIEmbeddingSiteWindow::DIM_FLAGS_SIZE_OUTER | + nsIEmbeddingSiteWindow::DIM_FLAGS_POSITION, + aX, aY, aCX, aCY); + } + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::GetPositionAndSize(int32_t* aX, int32_t* aY, int32_t* aCX, + int32_t* aCY) { + nsCOMPtr<nsIEmbeddingSiteWindow> ownerWin = GetOwnerWin(); + if (ownerWin) { + return ownerWin->GetDimensions( + nsIEmbeddingSiteWindow::DIM_FLAGS_SIZE_OUTER | + nsIEmbeddingSiteWindow::DIM_FLAGS_POSITION, + aX, aY, aCX, aCY); + } + return NS_ERROR_NULL_POINTER; +} + +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<nsIEmbeddingSiteWindow> ownerWin = GetOwnerWin(); + if (ownerWin) { + return ownerWin->GetSiteWindow(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<nsIEmbeddingSiteWindow> ownerWin = GetOwnerWin(); + if (ownerWin) { + return ownerWin->GetVisibility(aVisibility); + } + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::SetVisibility(bool aVisibility) { + nsCOMPtr<nsIEmbeddingSiteWindow> 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::SetFocus() { + nsCOMPtr<nsIEmbeddingSiteWindow> ownerWin = GetOwnerWin(); + if (ownerWin) { + return ownerWin->SetFocus(); + } + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::GetTitle(nsAString& aTitle) { + nsCOMPtr<nsIEmbeddingSiteWindow> ownerWin = GetOwnerWin(); + if (ownerWin) { + return ownerWin->GetTitle(aTitle); + } + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::SetTitle(const nsAString& aTitle) { + nsCOMPtr<nsIEmbeddingSiteWindow> 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<nsIEmbeddingSiteWindow> 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")) { + nsIWebNavigation* webnav = static_cast<nsIWebNavigation*>(mWebBrowser); + + 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->LoadURI(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<nsIEmbeddingSiteWindow> nsDocShellTreeOwner::GetOwnerWin() { + nsCOMPtr<nsIEmbeddingSiteWindow> 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), + mMouseClientX(0), + mMouseClientY(0), + mMouseScreenX(0), + mMouseScreenY(0), + 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) { + nsresult rv = NS_OK; +#ifndef XP_WIN + rv = + mEventTarget->AddSystemEventListener(u"keydown"_ns, this, false, false); + NS_ENSURE_SUCCESS(rv, rv); +#endif + rv = mEventTarget->AddSystemEventListener(u"mousedown"_ns, this, false, + false); + NS_ENSURE_SUCCESS(rv, rv); + rv = mEventTarget->AddSystemEventListener(u"mouseout"_ns, this, false, + false); + NS_ENSURE_SUCCESS(rv, rv); + rv = mEventTarget->AddSystemEventListener(u"mousemove"_ns, this, false, + false); + NS_ENSURE_SUCCESS(rv, rv); + + 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) { +#ifndef XP_WIN + mEventTarget->RemoveSystemEventListener(u"keydown"_ns, this, false); +#endif + 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 (!keyEvent->IsModifierKeyEvent()) { + 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) { + 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. + int32_t newMouseX = mouseEvent->ClientX(); + int32_t newMouseY = mouseEvent->ClientY(); + if (mMouseClientX == newMouseX && mMouseClientY == newMouseY) { + return NS_OK; + } + + // Filter out minor mouse movements. + if (mShowingTooltip && + (abs(mMouseClientX - newMouseX) <= kTooltipMouseMoveTolerance) && + (abs(mMouseClientY - newMouseY) <= kTooltipMouseMoveTolerance)) { + return NS_OK; + } + + mMouseClientX = newMouseX; + mMouseClientY = newMouseY; + mMouseScreenX = mouseEvent->ScreenX(CallerType::System); + mMouseScreenY = mouseEvent->ScreenY(CallerType::System); + + if (mTooltipTimer) { + mTooltipTimer->Cancel(); + mTooltipTimer = nullptr; + } + + if (!mShowingTooltip) { + nsIEventTarget* target = nullptr; + if (nsCOMPtr<EventTarget> eventTarget = aMouseEvent->GetComposedTarget()) { + mPossibleTooltipNode = do_QueryInterface(eventTarget); + nsCOMPtr<nsIGlobalObject> global(eventTarget->GetOwnerGlobal()); + if (global) { + target = global->EventTargetFor(TaskCategory::UI); + } + } + + if (mPossibleTooltipNode) { + nsresult rv = NS_NewTimerWithFuncCallback( + getter_AddRefs(mTooltipTimer), sTooltipCallback, this, + LookAndFeel::GetInt(LookAndFeel::IntID::TooltipDelay, 500), + nsITimer::TYPE_ONE_SHOT, "ChromeTooltipListener::MouseMove", target); + 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) { + // 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(); + } + + // The actual coordinates we want to put the tooltip at are relative to the + // toplevel docshell of our mWebBrowser. We know what the screen + // coordinates of the mouse event were, which means we just need the screen + // coordinates of the docshell. Unfortunately, there is no good way to + // find those short of groveling for the presentation in that docshell and + // finding the screen coords of its toplevel widget... + nsCOMPtr<nsIDocShell> docShell = + do_GetInterface(static_cast<nsIWebBrowser*>(self->mWebBrowser)); + RefPtr<PresShell> presShell = docShell ? docShell->GetPresShell() : nullptr; + + nsIWidget* widget = nullptr; + if (presShell) { + nsViewManager* vm = presShell->GetViewManager(); + if (vm) { + nsView* view = vm->GetRootView(); + if (view) { + nsPoint offset; + widget = view->GetNearestWidget(&offset); + } + } + } + + if (!widget || !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) { + 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->mMouseScreenX, self->mMouseScreenY, 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..eb7ad95288 --- /dev/null +++ b/docshell/base/nsDocShellTreeOwner.h @@ -0,0 +1,194 @@ +/* -*- 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 "nsIEmbeddingSiteWindow.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<nsIEmbeddingSiteWindow> GetOwnerWin(); + already_AddRefed<nsIInterfaceRequestor> GetOwnerRequestor(); + + protected: + // Weak References + nsWebBrowser* mWebBrowser; + nsIDocShellTreeOwner* mTreeOwner; + nsIDocShellTreeItem* mPrimaryContentShell; + + nsIWebBrowserChrome* mWebBrowserChrome; + nsIEmbeddingSiteWindow* 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; +}; + +// 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: + // various delays for tooltips + enum { + kTooltipAutoHideTime = 5000, // ms + kTooltipMouseMoveTolerance = 7 // pixel tolerance for mousemove event + }; + + 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 + int32_t mMouseClientX; + int32_t mMouseClientY; + + // Mouse coordinates for tooltip event + int32_t mMouseScreenX; + int32_t mMouseScreenY; + + 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; +}; + +#endif /* nsDocShellTreeOwner_h__ */ diff --git a/docshell/base/nsIContentViewer.idl b/docshell/base/nsIContentViewer.idl new file mode 100644 index 0000000000..d1eddcc747 --- /dev/null +++ b/docshell/base/nsIContentViewer.idl @@ -0,0 +1,348 @@ +/* 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 "nsTArray.h" +#include "nsRect.h" + +class nsIWidget; +class nsPresContext; +class nsView; +class nsDOMNavigationTiming; +namespace mozilla { +class Encoding; +class PresShell; +namespace dom { +class WindowGlobalChild; +} // namespace dom +} // 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 WindowGlobalChildPtr(mozilla::dom::WindowGlobalChild); + +[scriptable, builtinclass, uuid(2da17016-7851-4a45-a7a8-00b360e01595)] +interface nsIContentViewer : 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 nsIContentViewer_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] nsIContentViewer_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; + + 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 nsIContentViewer 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); + + /** + * 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; + + /** + * The value used to override devicePixelRatio and media queries dppx. + * Default is 0.0, that means no overriding is done (only a positive value + * is applied). + */ + attribute float overrideDPPX; + + /** + * Disable entire author style level (including HTML presentation hints), + * for this viewer but not any child viewers. + */ + attribute boolean authorStyleDisabled; + + /** + * XXX comm-central only: bug 829543. + */ + attribute ACString hintCharacterSet; + + /** + * XXX comm-central only: bug 829543. + */ + attribute int32_t hintCharacterSetSource; + + /** + * Requests the size of the content to the container. + */ + void getContentSize(out long width, out long height); + + /** + * Returns the preferred width and height of the content, constrained to the + * given maximum values. If either maxWidth or maxHeight is less than zero, + * that dimension is not constrained. + * + * All input and output values are in device pixels, rather than CSS pixels. + */ + void getContentSizeConstrained(in long maxWidth, in long maxHeight, + out long width, out long height); + + /** + * Instruct the refresh driver to discontinue painting until further + * notice. + */ + void pausePainting(); + + /** + * Instruct the refresh driver to resume painting after a previous call to + * pausePainting(). + */ + void resumePainting(); + + /* + * Render the document as if being viewed on a device with the specified + * media type. This will cause a reflow. + * + * @param mediaType The media type to be emulated + */ + void emulateMedium(in AString aMediaType); + + /* + * Restore the viewer's natural media type + */ + void stopEmulatingMedium(); + + cenum PrefersColorScheme : 8 { + PREFERS_COLOR_SCHEME_LIGHT, + PREFERS_COLOR_SCHEME_DARK, + PREFERS_COLOR_SCHEME_NONE, /* This clears the override. */ + }; + + /* + * Emulate or stop emulating the prefers color scheme on this page and + * subdocuments. + */ + void emulatePrefersColorScheme(in nsIContentViewer_PrefersColorScheme aPrefersColorScheme); + + [noscript, notxpcom] Encoding getHintCharset(); + [noscript, notxpcom] void setHintCharset(in Encoding aEncoding); +}; + +%{C++ +namespace mozilla { +namespace dom { + +using XPCOMPermitUnloadAction = nsIContentViewer::PermitUnloadAction; +using PermitUnloadResult = nsIContentViewer::PermitUnloadResult; + +} // namespace dom +} // namespace mozilla +%} diff --git a/docshell/base/nsIContentViewerEdit.idl b/docshell/base/nsIContentViewerEdit.idl new file mode 100644 index 0000000000..01b6f47806 --- /dev/null +++ b/docshell/base/nsIContentViewerEdit.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(35BE2D7E-F29B-48EC-BF7E-80A30A724DE3)] +interface nsIContentViewerEdit : 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/nsIDocShell.idl b/docshell/base/nsIDocShell.idl new file mode 100644 index 0000000000..afa1eee3a6 --- /dev/null +++ b/docshell/base/nsIDocShell.idl @@ -0,0 +1,900 @@ +/* -*- 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 nsIContentViewer; +interface nsIContentSecurityPolicy; +interface nsIDocShellLoadInfo; +interface nsIEditor; +interface nsIEditingSession; +interface nsIInputStream; +interface nsIRequest; +interface nsISHEntry; +interface nsILayoutHistoryState; +interface nsISecureBrowserUI; +interface nsIScriptGlobalObject; +interface nsIStructuredCloneContainer; +interface nsIDOMStorage; +interface nsIPrincipal; +interface nsIWebBrowserPrint; +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; + +[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); + + /** + * 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. + */ + [nostdcall] + void updateURLAndHistory(in Document aDocument, in nsIURI aNewURI, + in nsIStructuredCloneContainer aData, in AString aTitle, + in boolean aReplace, in nsIURI aCurrentURI, + in boolean aEqualURIs); + + /** + * 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(); + + /** + * For editors and suchlike who wish to change the URI associated with the + * document. Note if you want to get the current URI, use the read-only + * property on nsIWebNavigation. + */ + void setCurrentURI(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; + + /** + * Content Viewer that is currently loaded for this DocShell. This may + * change as the underlying content changes. + */ + [infallible] readonly attribute nsIContentViewer contentViewer; + + /** + * 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; + + /** + * Whether to allow plugin execution + */ + attribute boolean allowPlugins; + + /** + * Whether to allow Javascript execution + */ + attribute boolean allowJavascript; + + /** + * 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 nsIContentViewer 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; + + /** + * The original offset of this child in its container. This property is -1 for + * dynamically added docShells. + */ + [notxpcom,nostdcall] attribute long childOffset; + + /** + * 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(); + + /** + * If true, this browser is not visible in the traditional sense, but + * is actively being rendered to the screen (ex. painted on a canvas) + * and should be treated accordingly. + **/ + attribute boolean isOffScreenBrowser; + + /** + * Propagated to the print preview document viewer. Must only be called on + * a document viewer that has been initialized for print preview. + */ + void exitPrintPreview(); + + /** + * Whether this docshell can execute scripts based on its hierarchy. + * The rule of thumb here is that we disable js if this docshell or any + * of its parents disallow scripting. + */ + [infallible] readonly attribute boolean canExecuteScripts; + + /** + * 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(); + + /** + * Sets whether a docshell is an app tab. An app tab docshell may behave + * differently than a non-app tab docshell in some cases, such as when + * handling link clicks. Docshells are not app tabs unless told otherwise. + */ + attribute boolean isAppTab; + + /** + * 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 createAboutBlankContentViewer(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. + * + * Upon setting, sets the forced encoding for compatibility with legacy callers. + */ + attribute ACString charset; + + /** + * Called when the user chose an encoding override from the character + * encoding menu. Separate from the setter for the charset property to avoid + * extensions adding noise to the data. + */ + void gatherCharsetMenuTelemetry(); + + /** + * 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); + + /** + * Whether the docShell records profile timeline markers at the moment + */ + [infallible] attribute boolean recordProfileTimelineMarkers; + + /** + * 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(); + + /** + * Returns and flushes the profile timeline markers gathered by the docShell + */ + [implicit_jscontext] + jsval popProfileTimelineMarkers(); + + /** + * 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; + + /** + * Like nsIDocShellTreeItem::GetSameTypeParent, except this ignores <iframe + * mozbrowser> boundaries. Which no longer exist. + * + * @deprecated: Use `BrowsingContext::GetParent()` in the future. + */ + nsIDocShell getSameTypeInProcessParentIgnoreBrowserBoundaries(); + + /** + * True iff asynchronous panning and zooming is enabled for this + * docshell. + */ + readonly attribute bool asyncPanZoomEnabled; + + /** + * This member variable determines whether a document has Mixed Active Content that + * was initially blocked from loading, but the user has choosen to override the + * block and allow the content to load. mMixedContentChannel is set to the document's + * channel when the user allows mixed content. The nsMixedContentBlocker content policy + * checks if the document's root channel matches the mMixedContentChannel. If it matches, + * then Mixed Content is loaded. If it does match, mixed content is blocked. + * + * A match implies that there is definitely mixed active content on a page that was + * initially blocked by nsMixedContentBlocker and then allowed and loaded by the user. + * A miss imples that IF there is mixed active content on the page AND it was + * blocked by nsMixedContentBlocker.cpp, the user has not choosen to override + * the block. Note that if the about:config setting + * security.mixed_content.block_active_content is set to false, this boolean + * will be false, mMixedContentChannel will remain null since blocking active content has + * been disabled and hence mMixedContentChannel will never be set. + */ + attribute nsIChannel mixedContentChannel; + + /** + * Are plugins allowed in the current document loaded in this docshell ? + * (if there is one). This depends on whether plugins are allowed by this + * docshell itself or if the document is sandboxed and hence plugins should + * not be allowed. + */ + [noscript, notxpcom] bool pluginsAllowedInCurrentDoc(); + + [noscript, infallible] attribute boolean affectPrivateSessionLifetime; + + /** + * 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; + + /** + * Indicates that the character encoding was autodetected. + */ + [infallible] readonly attribute boolean charsetAutodetected; + + 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(); + + /** + * If deviceSizeIsPageSize is set to true, device-width/height media queries + * will be calculated from the page size, not the device size. + * + * Used by the Responsive Design Mode and B2G Simulator. + * + * Default is False. + * Default value can be overriden with + * docshell.device_size_is_page_size pref. + */ + [infallible] attribute boolean deviceSizeIsPageSize; + + /** + * Notify DocShell when the browser is about to start executing JS, and after + * that execution has stopped. This only occurs when the Timeline devtool + * is collecting information. + */ + [noscript,notxpcom,nostdcall] void notifyJSRunToCompletionStart(in string aReason, + in AString functionName, + in AString fileName, + in unsigned long lineNumber, + in jsval asyncStack, + in string asyncCause); + [noscript,notxpcom,nostdcall] void notifyJSRunToCompletionStop(); + + /** + * 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] 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..ea34cef74b --- /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..7e8aed5910 --- /dev/null +++ b/docshell/base/nsIDocShellTreeOwner.idl @@ -0,0 +1,106 @@ +/* -*- 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; + +[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; + + /* + 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 CSS 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 CSS 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 CSS pixels. + */ + void getRootShellSize(out long width, out long height); + /* + Sets the size of the root docshell in CSS 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..cd08bc0f3f --- /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 nsIContentViewer; +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 { + nsIContentViewer createInstance(in string aCommand, + in nsIChannel aChannel, + in nsILoadGroup aLoadGroup, + in ACString aContentType, + in nsIDocShell aContainer, + in nsISupports aExtraInfo, + out nsIStreamListener aDocListenerResult); + + nsIContentViewer createInstanceForDocument(in nsISupports aContainer, + in Document aDocument, + in string aCommand); +}; 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..512535cee5 --- /dev/null +++ b/docshell/base/nsILoadURIDelegate.idl @@ -0,0 +1,49 @@ +/* -*- 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 the URI load. This should only be called for top-level loads. + * + * @param aURI The URI to load. + * @param aWhere See possible values described in nsIBrowserDOMWindow. + * @param aFlags Flags which control the behavior of the load. + * @param aTriggeringPrincipal The principal that triggered the load of aURI. + * + * Returns whether the load has been successfully handled. + */ + boolean + loadURI(in nsIURI aURI, in short aWhere, in long aFlags, + in nsIPrincipal aTriggeringPrincipal); + + /** + * 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..c9a76b1fc2 --- /dev/null +++ b/docshell/base/nsIRefreshURI.idl @@ -0,0 +1,88 @@ +/* -*- 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. 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. + * @param aRepeat Flag to indicate if the uri is to be + * repeatedly refreshed every aMillis milliseconds. + * @param aMetaRefresh Flag to indicate if this is a Meta refresh. + */ + void refreshURI(in nsIURI aURI, in nsIPrincipal aPrincipal, + in long aMillis, in boolean aRepeat, + in boolean aMetaRefresh); + + /** + * Loads a URI immediately as if it were a 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. + * @param aMetaRefresh Flag to indicate if this is a meta refresh. + */ + void forceRefreshURI(in nsIURI aURI, in nsIPrincipal aPrincipal, + in long aMillis, in boolean aMetaRefresh); + + /** + * Checks the passed in channel to see if there is a refresh header, + * if there is, will setup a timer to refresh the uri found + * in the header. If docshell is busy loading a page currently, the + * request will be queued and executed when the current page + * finishes loading. + * + * Returns the NS_REFRESHURI_HEADER_FOUND success code if a refresh + * header was found and successfully setup. + * + * @param aChannel The channel to be parsed. + */ + void setupRefreshURI(in nsIChannel aChannel); + + /** + * 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 aBaseURI base URI to resolve refresh uri with. + * @param aPrincipal The triggeringPrincipal for the refresh load + * May be null, in which case the principal of current document will be + * applied. + * @param aInnerWindowID The window id to use for error reporting. + * @param aHeader The meta refresh header string. + */ + void setupRefreshURIFromHeader(in nsIURI aBaseURI, + in nsIPrincipal principal, + in unsigned long long aInnerWindowID, + in ACString aHeader); + + /** + * 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..802478481f --- /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 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..b6d46315e9 --- /dev/null +++ b/docshell/base/nsIURIFixup.idl @@ -0,0 +1,156 @@ +/* -*- 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; +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 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); + + /** + * 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..30b6dd276c --- /dev/null +++ b/docshell/base/nsIWebNavigation.idl @@ -0,0 +1,375 @@ +/* -*- 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. + * + * @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); + + /** + * 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. + * + * @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); + + /** + * Tells the object to navigate to the session history item at a given index. + * + * @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); + + /**************************************************************************** + * 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 is set when a user explicitly disables the Mixed Content + Blocker, and allows Mixed Content to load on an https page. + */ + const unsigned long LOAD_FLAGS_ALLOW_MIXED_CONTENT = 0x2000; + + /** + * 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; + + /** + * 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 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. + * @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 AString aURI, + in jsval aLoadURIOptions); + + /** + * A C++ friendly version of loadURI + */ + [nostdcall, binaryname(LoadURI)] + void binaryLoadURI(in AString aURI, + 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..ff93920723 --- /dev/null +++ b/docshell/base/nsIWebNavigationInfo.idl @@ -0,0 +1,63 @@ +/* -*- 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" + +interface nsIWebNavigation; + +/** + * 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 supported via an + * NPAPI ("Netscape 4 API") plug-in. This is not the value returned for + * "XPCOM plug-ins". + */ + const unsigned long PLUGIN = 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. + * @param aWebNav the nsIWebNavigation object for which the request + * is being made. This is allowed to be null. If it is non-null, + * the return value of this method may depend on the exact state of + * aWebNav and the values set through nsIWebBrowserSetup; otherwise + * the method will assume that the caller is interested in information + * about nsIWebNavigation objects in their default state. + * @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, in nsIWebNavigation aWebNav); +}; 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..23ac220489 --- /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(kNameSpaceID_None, 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", + aDocGroup->EventTargetFor(TaskCategory::Network)); +} + +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..4f133ce0df --- /dev/null +++ b/docshell/base/nsRefreshTimer.cpp @@ -0,0 +1,53 @@ +/* -*- 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, + bool aRepeat, bool aMetaRefresh) + : mDocShell(aDocShell), + mURI(aURI), + mPrincipal(aPrincipal), + mDelay(aDelay), + mRepeat(aRepeat), + mMetaRefresh(aMetaRefresh) {} + +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, mMetaRefresh, + 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..78dc1d5220 --- /dev/null +++ b/docshell/base/nsRefreshTimer.h @@ -0,0 +1,41 @@ +/* -*- 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, bool aRepeat, bool aMetaRefresh); + + 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; + bool mRepeat; + bool mMetaRefresh; + + private: + virtual ~nsRefreshTimer(); +}; + +#endif /* nsRefreshTimer_h__ */ diff --git a/docshell/base/nsWebNavigationInfo.cpp b/docshell/base/nsWebNavigationInfo.cpp new file mode 100644 index 0000000000..23c4ffaf16 --- /dev/null +++ b/docshell/base/nsWebNavigationInfo.cpp @@ -0,0 +1,119 @@ +/* -*- 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 "mozilla/dom/BrowsingContext.h" +#include "nsIWebNavigation.h" +#include "nsServiceManagerUtils.h" +#include "nsIDocumentLoaderFactory.h" +#include "nsIPluginHost.h" +#include "nsIDocShell.h" +#include "nsContentUtils.h" +#include "imgLoader.h" +#include "nsPluginHost.h" + +NS_IMPL_ISUPPORTS(nsWebNavigationInfo, nsIWebNavigationInfo) + +NS_IMETHODIMP +nsWebNavigationInfo::IsTypeSupported(const nsACString& aType, + nsIWebNavigation* aWebNav, + uint32_t* aIsTypeSupported) { + MOZ_ASSERT(aIsTypeSupported, "null out param?"); + + *aIsTypeSupported = IsTypeSupported(aType, aWebNav); + return NS_OK; +} + +uint32_t nsWebNavigationInfo::IsTypeSupported(const nsACString& aType, + nsIWebNavigation* aWebNav) { + // Note to self: aWebNav could be an nsWebBrowser or an nsDocShell here (or + // an nsSHistory, but not much we can do with that). So if we start using + // it here, we need to be careful to get to the docshell correctly. + nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aWebNav)); + auto* browsingContext = docShell ? docShell->GetBrowsingContext() : nullptr; + bool pluginsAllowed = + browsingContext ? browsingContext->GetAllowPlugins() : true; + + return IsTypeSupported(aType, pluginsAllowed); +} + +uint32_t nsWebNavigationInfo::IsTypeSupported(const nsACString& aType, + bool aPluginsAllowed) { + // 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); + uint32_t result = IsTypeSupportedInternal(flatType); + if (result != nsIWebNavigationInfo::UNSUPPORTED) { + return result; + } + + // As of FF 52, we only support flash and test plugins, so if the mime types + // don't match for that, exit before we start loading plugins. + if (!nsPluginHost::CanUsePluginForMIMEType(aType)) { + return nsIWebNavigationInfo::UNSUPPORTED; + } + + // If this request is for a docShell that isn't going to allow plugins, + // there's no need to try and find a plugin to handle it. + if (!aPluginsAllowed) { + return nsIWebNavigationInfo::UNSUPPORTED; + } + + // Try reloading plugins in case they've changed. + nsCOMPtr<nsIPluginHost> pluginHost = + do_GetService(MOZ_PLUGIN_HOST_CONTRACTID); + if (pluginHost) { + // false will ensure that currently running plugins will not + // be shut down + nsresult rv = pluginHost->ReloadPlugins(); + if (NS_SUCCEEDED(rv)) { + // OK, we reloaded plugins and there were new ones + // (otherwise NS_ERROR_PLUGINS_PLUGINSNOTCHANGED would have + // been returned). Try checking whether we can handle the + // content now. + return IsTypeSupportedInternal(flatType); + } + } + + return nsIWebNavigationInfo::UNSUPPORTED; +} + +uint32_t nsWebNavigationInfo::IsTypeSupportedInternal(const nsCString& aType) { + nsContentUtils::ContentViewerType vtype = nsContentUtils::TYPE_UNSUPPORTED; + + nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory = + nsContentUtils::FindInternalContentViewer(aType, &vtype); + + switch (vtype) { + case nsContentUtils::TYPE_UNSUPPORTED: + return nsIWebNavigationInfo::UNSUPPORTED; + + case nsContentUtils::TYPE_PLUGIN: + return nsIWebNavigationInfo::PLUGIN; + + 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.get())) { + return nsIWebNavigationInfo::IMAGE; + } else { + return nsIWebNavigationInfo::OTHER; + } + } + + return nsIWebNavigationInfo::UNSUPPORTED; +} diff --git a/docshell/base/nsWebNavigationInfo.h b/docshell/base/nsWebNavigationInfo.h new file mode 100644 index 0000000000..5abf2e9bab --- /dev/null +++ b/docshell/base/nsWebNavigationInfo.h @@ -0,0 +1,37 @@ +/* -*- 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, + nsIWebNavigation* aWebNav); + static uint32_t IsTypeSupported(const nsACString& aType, + bool aPluginsAllowed); + + private: + ~nsWebNavigationInfo() {} + + // Check whether aType is supported, and returns an nsIWebNavigationInfo + // constant. + static uint32_t IsTypeSupportedInternal(const nsCString& aType); +}; + +#endif // nsWebNavigationInfo_h__ diff --git a/docshell/base/timeline/AbstractTimelineMarker.cpp b/docshell/base/timeline/AbstractTimelineMarker.cpp new file mode 100644 index 0000000000..471742089c --- /dev/null +++ b/docshell/base/timeline/AbstractTimelineMarker.cpp @@ -0,0 +1,72 @@ +/* -*- 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 "AbstractTimelineMarker.h" + +#include "mozilla/TimeStamp.h" +#include "MainThreadUtils.h" +#include "nsAppRunner.h" + +namespace mozilla { + +AbstractTimelineMarker::AbstractTimelineMarker(const char* aName, + MarkerTracingType aTracingType) + : mName(aName), + mTracingType(aTracingType), + mProcessType(XRE_GetProcessType()), + mIsOffMainThread(!NS_IsMainThread()) { + MOZ_COUNT_CTOR(AbstractTimelineMarker); + SetCurrentTime(); +} + +AbstractTimelineMarker::AbstractTimelineMarker(const char* aName, + const TimeStamp& aTime, + MarkerTracingType aTracingType) + : mName(aName), + mTracingType(aTracingType), + mProcessType(XRE_GetProcessType()), + mIsOffMainThread(!NS_IsMainThread()) { + MOZ_COUNT_CTOR(AbstractTimelineMarker); + SetCustomTime(aTime); +} + +UniquePtr<AbstractTimelineMarker> AbstractTimelineMarker::Clone() { + MOZ_ASSERT(false, "Clone method not yet implemented on this marker type."); + return nullptr; +} + +bool AbstractTimelineMarker::Equals(const AbstractTimelineMarker& aOther) { + // Check whether two markers should be considered the same, for the purpose + // of pairing start and end markers. Normally this definition suffices. + return strcmp(mName, aOther.mName) == 0; +} + +AbstractTimelineMarker::~AbstractTimelineMarker() { + MOZ_COUNT_DTOR(AbstractTimelineMarker); +} + +void AbstractTimelineMarker::SetCurrentTime() { + TimeStamp now = TimeStamp::Now(); + SetCustomTime(now); +} + +void AbstractTimelineMarker::SetCustomTime(const TimeStamp& aTime) { + mTime = (aTime - TimeStamp::ProcessCreation()).ToMilliseconds(); +} + +void AbstractTimelineMarker::SetCustomTime(DOMHighResTimeStamp aTime) { + mTime = aTime; +} + +void AbstractTimelineMarker::SetProcessType(GeckoProcessType aProcessType) { + mProcessType = aProcessType; +} + +void AbstractTimelineMarker::SetOffMainThread(bool aIsOffMainThread) { + mIsOffMainThread = aIsOffMainThread; +} + +} // namespace mozilla diff --git a/docshell/base/timeline/AbstractTimelineMarker.h b/docshell/base/timeline/AbstractTimelineMarker.h new file mode 100644 index 0000000000..8581ba53f8 --- /dev/null +++ b/docshell/base/timeline/AbstractTimelineMarker.h @@ -0,0 +1,71 @@ +/* -*- 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_AbstractTimelineMarker_h_ +#define mozilla_AbstractTimelineMarker_h_ + +#include "TimelineMarkerEnums.h" // for MarkerTracingType +#include "nsDOMNavigationTiming.h" // for DOMHighResTimeStamp +#include "nsXULAppAPI.h" // for GeckoProcessType +#include "mozilla/UniquePtr.h" + +struct JSContext; +class JSObject; + +namespace mozilla { +class TimeStamp; + +namespace dom { +struct ProfileTimelineMarker; +} + +class AbstractTimelineMarker { + private: + AbstractTimelineMarker() = delete; + AbstractTimelineMarker(const AbstractTimelineMarker& aOther) = delete; + void operator=(const AbstractTimelineMarker& aOther) = delete; + + public: + AbstractTimelineMarker(const char* aName, MarkerTracingType aTracingType); + + AbstractTimelineMarker(const char* aName, const TimeStamp& aTime, + MarkerTracingType aTracingType); + + virtual ~AbstractTimelineMarker(); + + virtual UniquePtr<AbstractTimelineMarker> Clone(); + virtual bool Equals(const AbstractTimelineMarker& aOther); + + virtual void AddDetails(JSContext* aCx, + dom::ProfileTimelineMarker& aMarker) = 0; + virtual JSObject* GetStack() = 0; + + const char* GetName() const { return mName; } + DOMHighResTimeStamp GetTime() const { return mTime; } + MarkerTracingType GetTracingType() const { return mTracingType; } + + uint8_t GetProcessType() const { return mProcessType; }; + bool IsOffMainThread() const { return mIsOffMainThread; }; + + private: + const char* mName; + DOMHighResTimeStamp mTime; + MarkerTracingType mTracingType; + + uint8_t mProcessType; // @see `enum GeckoProcessType`. + bool mIsOffMainThread; + + protected: + void SetCurrentTime(); + void SetCustomTime(const TimeStamp& aTime); + void SetCustomTime(DOMHighResTimeStamp aTime); + void SetProcessType(GeckoProcessType aProcessType); + void SetOffMainThread(bool aIsOffMainThread); +}; + +} // namespace mozilla + +#endif /* mozilla_AbstractTimelineMarker_h_ */ diff --git a/docshell/base/timeline/AutoGlobalTimelineMarker.cpp b/docshell/base/timeline/AutoGlobalTimelineMarker.cpp new file mode 100644 index 0000000000..d8814a960d --- /dev/null +++ b/docshell/base/timeline/AutoGlobalTimelineMarker.cpp @@ -0,0 +1,41 @@ +/* -*- 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 "AutoGlobalTimelineMarker.h" + +#include "TimelineConsumers.h" +#include "MainThreadUtils.h" + +namespace mozilla { + +AutoGlobalTimelineMarker::AutoGlobalTimelineMarker( + const char* aName, MarkerStackRequest aStackRequest /* = STACK */ + ) + : mName(aName), mStackRequest(aStackRequest) { + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); + if (!timelines || timelines->IsEmpty()) { + return; + } + + timelines->AddMarkerForAllObservedDocShells(mName, MarkerTracingType::START, + mStackRequest); +} + +AutoGlobalTimelineMarker::~AutoGlobalTimelineMarker() { + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); + if (!timelines || timelines->IsEmpty()) { + return; + } + + timelines->AddMarkerForAllObservedDocShells(mName, MarkerTracingType::END, + mStackRequest); +} + +} // namespace mozilla diff --git a/docshell/base/timeline/AutoGlobalTimelineMarker.h b/docshell/base/timeline/AutoGlobalTimelineMarker.h new file mode 100644 index 0000000000..a9bbc92fce --- /dev/null +++ b/docshell/base/timeline/AutoGlobalTimelineMarker.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 mozilla_AutoGlobalTimelineMarker_h_ +#define mozilla_AutoGlobalTimelineMarker_h_ + +#include "mozilla/Attributes.h" +#include "TimelineMarkerEnums.h" + +namespace mozilla { + +// # AutoGlobalTimelineMarker +// +// Similar to `AutoTimelineMarker`, but adds its traced marker to all docshells, +// not a single particular one. This is useful for operations that aren't +// associated with any one particular doc shell, or when it isn't clear which +// docshell triggered the operation. +// +// Example usage: +// +// { +// AutoGlobalTimelineMarker marker("Cycle Collection"); +// nsCycleCollector* cc = GetCycleCollector(); +// cc->Collect(); +// ... +// } +class MOZ_RAII AutoGlobalTimelineMarker { + // The name of the marker we are adding. + const char* mName; + // Whether to capture the JS stack or not. + MarkerStackRequest mStackRequest; + + public: + explicit AutoGlobalTimelineMarker( + const char* aName, + MarkerStackRequest aStackRequest = MarkerStackRequest::STACK); + ~AutoGlobalTimelineMarker(); + + AutoGlobalTimelineMarker(const AutoGlobalTimelineMarker& aOther) = delete; + void operator=(const AutoGlobalTimelineMarker& aOther) = delete; +}; + +} // namespace mozilla + +#endif /* mozilla_AutoGlobalTimelineMarker_h_ */ diff --git a/docshell/base/timeline/AutoRestyleTimelineMarker.cpp b/docshell/base/timeline/AutoRestyleTimelineMarker.cpp new file mode 100644 index 0000000000..c012dfbc17 --- /dev/null +++ b/docshell/base/timeline/AutoRestyleTimelineMarker.cpp @@ -0,0 +1,53 @@ +/* -*- 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 "AutoRestyleTimelineMarker.h" + +#include "TimelineConsumers.h" +#include "MainThreadUtils.h" +#include "nsIDocShell.h" +#include "RestyleTimelineMarker.h" + +namespace mozilla { + +AutoRestyleTimelineMarker::AutoRestyleTimelineMarker(nsIDocShell* aDocShell, + bool aIsAnimationOnly) + : mDocShell(nullptr), mIsAnimationOnly(aIsAnimationOnly) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!aDocShell) { + return; + } + + RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); + if (!timelines || !timelines->HasConsumer(aDocShell)) { + return; + } + + mDocShell = aDocShell; + timelines->AddMarkerForDocShell( + mDocShell, MakeUnique<RestyleTimelineMarker>(mIsAnimationOnly, + MarkerTracingType::START)); +} + +AutoRestyleTimelineMarker::~AutoRestyleTimelineMarker() { + MOZ_ASSERT(NS_IsMainThread()); + + if (!mDocShell) { + return; + } + + RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); + if (!timelines || !timelines->HasConsumer(mDocShell)) { + return; + } + + timelines->AddMarkerForDocShell( + mDocShell, MakeUnique<RestyleTimelineMarker>(mIsAnimationOnly, + MarkerTracingType::END)); +} + +} // namespace mozilla diff --git a/docshell/base/timeline/AutoRestyleTimelineMarker.h b/docshell/base/timeline/AutoRestyleTimelineMarker.h new file mode 100644 index 0000000000..1246e1a9bd --- /dev/null +++ b/docshell/base/timeline/AutoRestyleTimelineMarker.h @@ -0,0 +1,30 @@ +/* -*- 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_AutoRestyleTimelineMarker_h_ +#define mozilla_AutoRestyleTimelineMarker_h_ + +#include "mozilla/RefPtr.h" + +class nsIDocShell; + +namespace mozilla { + +class MOZ_RAII AutoRestyleTimelineMarker { + RefPtr<nsIDocShell> mDocShell; + bool mIsAnimationOnly; + + public: + AutoRestyleTimelineMarker(nsIDocShell* aDocShell, bool aIsAnimationOnly); + ~AutoRestyleTimelineMarker(); + + AutoRestyleTimelineMarker(const AutoRestyleTimelineMarker& aOther) = delete; + void operator=(const AutoRestyleTimelineMarker& aOther) = delete; +}; + +} // namespace mozilla + +#endif /* mozilla_AutoRestyleTimelineMarker_h_ */ diff --git a/docshell/base/timeline/AutoTimelineMarker.cpp b/docshell/base/timeline/AutoTimelineMarker.cpp new file mode 100644 index 0000000000..714b818f0a --- /dev/null +++ b/docshell/base/timeline/AutoTimelineMarker.cpp @@ -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/. */ + +#include "AutoTimelineMarker.h" + +#include "nsIDocShell.h" +#include "TimelineConsumers.h" +#include "MainThreadUtils.h" + +namespace mozilla { + +AutoTimelineMarker::AutoTimelineMarker(nsIDocShell* aDocShell, + const char* aName) + : mName(aName), mDocShell(nullptr) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!aDocShell) { + return; + } + + RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); + if (!timelines || !timelines->HasConsumer(aDocShell)) { + return; + } + + mDocShell = aDocShell; + timelines->AddMarkerForDocShell(mDocShell, mName, MarkerTracingType::START); +} + +AutoTimelineMarker::~AutoTimelineMarker() { + MOZ_ASSERT(NS_IsMainThread()); + + if (!mDocShell) { + return; + } + + RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); + if (!timelines || !timelines->HasConsumer(mDocShell)) { + return; + } + + timelines->AddMarkerForDocShell(mDocShell, mName, MarkerTracingType::END); +} + +} // namespace mozilla diff --git a/docshell/base/timeline/AutoTimelineMarker.h b/docshell/base/timeline/AutoTimelineMarker.h new file mode 100644 index 0000000000..a86c741bb0 --- /dev/null +++ b/docshell/base/timeline/AutoTimelineMarker.h @@ -0,0 +1,46 @@ +/* -*- 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_AutoTimelineMarker_h_ +#define mozilla_AutoTimelineMarker_h_ + +#include "mozilla/RefPtr.h" + +class nsIDocShell; + +namespace mozilla { + +// # AutoTimelineMarker +// +// An RAII class to trace some task in the platform by adding a start and end +// timeline marker pair. These markers are then rendered in the devtools' +// performance tool's waterfall graph. +// +// Example usage: +// +// { +// AutoTimelineMarker marker(mDocShell, "Parse CSS"); +// nsresult rv = ParseTheCSSFile(mFile); +// ... +// } +class MOZ_RAII AutoTimelineMarker { + // The name of the marker we are adding. + const char* mName; + + // The docshell that is associated with this marker. + RefPtr<nsIDocShell> mDocShell; + + public: + AutoTimelineMarker(nsIDocShell* aDocShell, const char* aName); + ~AutoTimelineMarker(); + + AutoTimelineMarker(const AutoTimelineMarker& aOther) = delete; + void operator=(const AutoTimelineMarker& aOther) = delete; +}; + +} // namespace mozilla + +#endif /* mozilla_AutoTimelineMarker_h_ */ diff --git a/docshell/base/timeline/CompositeTimelineMarker.h b/docshell/base/timeline/CompositeTimelineMarker.h new file mode 100644 index 0000000000..cd2896fefa --- /dev/null +++ b/docshell/base/timeline/CompositeTimelineMarker.h @@ -0,0 +1,31 @@ +/* -*- 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_CompositeTimelineMarker_h_ +#define mozilla_CompositeTimelineMarker_h_ + +#include "TimelineMarker.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" + +namespace mozilla { + +class CompositeTimelineMarker : public TimelineMarker { + public: + CompositeTimelineMarker(const TimeStamp& aTime, + MarkerTracingType aTracingType) + : TimelineMarker("Composite", aTime, aTracingType) { + // Even though these markers end up being created on the main thread in the + // content or chrome processes, they actually trace down code in the + // compositor parent process. All the information for creating these markers + // is sent along via IPC to an nsView when a composite finishes. + // Mark this as 'off the main thread' to style it differently in frontends. + SetOffMainThread(true); + } +}; + +} // namespace mozilla + +#endif // mozilla_CompositeTimelineMarker_h_ diff --git a/docshell/base/timeline/ConsoleTimelineMarker.h b/docshell/base/timeline/ConsoleTimelineMarker.h new file mode 100644 index 0000000000..232aa1a60e --- /dev/null +++ b/docshell/base/timeline/ConsoleTimelineMarker.h @@ -0,0 +1,53 @@ +/* -*- 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_ConsoleTimelineMarker_h_ +#define mozilla_ConsoleTimelineMarker_h_ + +#include "TimelineMarker.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" + +namespace mozilla { + +class ConsoleTimelineMarker : public TimelineMarker { + public: + ConsoleTimelineMarker(const nsAString& aCause, MarkerTracingType aTracingType) + : TimelineMarker("ConsoleTime", aTracingType), mCause(aCause) { + // Stack is captured by default on the "start" marker. Explicitly also + // capture stack on the "end" marker. + if (aTracingType == MarkerTracingType::END) { + CaptureStack(); + } + } + + virtual bool Equals(const AbstractTimelineMarker& aOther) override { + if (!TimelineMarker::Equals(aOther)) { + return false; + } + // Console markers must have matching causes as well. It is safe to perform + // a static_cast here as the previous equality check ensures that this is + // a console marker instance. + return mCause == static_cast<const ConsoleTimelineMarker*>(&aOther)->mCause; + } + + virtual void AddDetails(JSContext* aCx, + dom::ProfileTimelineMarker& aMarker) override { + TimelineMarker::AddDetails(aCx, aMarker); + + if (GetTracingType() == MarkerTracingType::START) { + aMarker.mCauseName.Construct(mCause); + } else { + aMarker.mEndStack = GetStack(); + } + } + + private: + nsString mCause; +}; + +} // namespace mozilla + +#endif // mozilla_ConsoleTimelineMarker_h_ diff --git a/docshell/base/timeline/DocLoadingTimelineMarker.h b/docshell/base/timeline/DocLoadingTimelineMarker.h new file mode 100644 index 0000000000..8f6a7db780 --- /dev/null +++ b/docshell/base/timeline/DocLoadingTimelineMarker.h @@ -0,0 +1,38 @@ +/* -*- 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_DocLoadingTimelineMarker_h_ +#define mozilla_DocLoadingTimelineMarker_h_ + +#include "TimelineMarker.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" + +namespace mozilla { + +class DocLoadingTimelineMarker : public TimelineMarker { + public: + explicit DocLoadingTimelineMarker(const char* aName) + : TimelineMarker(aName, MarkerTracingType::TIMESTAMP), + mUnixTime(PR_Now()) {} + + virtual void AddDetails(JSContext* aCx, + dom::ProfileTimelineMarker& aMarker) override { + TimelineMarker::AddDetails(aCx, aMarker); + aMarker.mUnixTime.Construct(mUnixTime); + } + + private: + // Certain consumers might use Date.now() or similar for tracing time. + // However, TimelineMarkers use process creation as an epoch, which provides + // more precision. To allow syncing, attach an additional unix timestamp. + // Using this instead of `AbstractTimelineMarker::GetTime()'s` timestamp + // is strongly discouraged. + PRTime mUnixTime; +}; + +} // namespace mozilla + +#endif // mozilla_DocLoadingTimelineMarker_h_ diff --git a/docshell/base/timeline/EventTimelineMarker.h b/docshell/base/timeline/EventTimelineMarker.h new file mode 100644 index 0000000000..5e5bc5c4a6 --- /dev/null +++ b/docshell/base/timeline/EventTimelineMarker.h @@ -0,0 +1,40 @@ +/* -*- 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_EventTimelineMarker_h_ +#define mozilla_EventTimelineMarker_h_ + +#include "TimelineMarker.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" + +namespace mozilla { + +class EventTimelineMarker : public TimelineMarker { + public: + EventTimelineMarker(const nsAString& aType, uint16_t aPhase, + MarkerTracingType aTracingType) + : TimelineMarker("DOMEvent", aTracingType), + mType(aType), + mPhase(aPhase) {} + + virtual void AddDetails(JSContext* aCx, + dom::ProfileTimelineMarker& aMarker) override { + TimelineMarker::AddDetails(aCx, aMarker); + + if (GetTracingType() == MarkerTracingType::START) { + aMarker.mType.Construct(mType); + aMarker.mEventPhase.Construct(mPhase); + } + } + + private: + nsString mType; + uint16_t mPhase; +}; + +} // namespace mozilla + +#endif // mozilla_EventTimelineMarker_h_ diff --git a/docshell/base/timeline/JavascriptTimelineMarker.h b/docshell/base/timeline/JavascriptTimelineMarker.h new file mode 100644 index 0000000000..0e07f3b605 --- /dev/null +++ b/docshell/base/timeline/JavascriptTimelineMarker.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 mozilla_JavascriptTimelineMarker_h_ +#define mozilla_JavascriptTimelineMarker_h_ + +#include "TimelineMarker.h" + +#include "mozilla/Maybe.h" + +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" +#include "mozilla/dom/RootedDictionary.h" +#include "mozilla/dom/ToJSValue.h" + +namespace mozilla { + +class JavascriptTimelineMarker : public TimelineMarker { + public: + // The caller owns |aAsyncCause| here, so we must copy it into a separate + // string for use later on. + JavascriptTimelineMarker(const char* aReason, const nsAString& aFunctionName, + const nsAString& aFileName, uint32_t aLineNumber, + MarkerTracingType aTracingType, + JS::Handle<JS::Value> aAsyncStack, + const char* aAsyncCause) + : TimelineMarker("Javascript", aTracingType, + MarkerStackRequest::NO_STACK), + mCause(NS_ConvertUTF8toUTF16(aReason)), + mFunctionName(aFunctionName), + mFileName(aFileName), + mLineNumber(aLineNumber), + mAsyncCause(aAsyncCause) { + JSContext* ctx = nsContentUtils::GetCurrentJSContext(); + if (ctx) { + mAsyncStack.init(ctx, aAsyncStack); + } + } + + virtual void AddDetails(JSContext* aCx, + dom::ProfileTimelineMarker& aMarker) override { + TimelineMarker::AddDetails(aCx, aMarker); + + aMarker.mCauseName.Construct(mCause); + + if (!mFunctionName.IsEmpty() || !mFileName.IsEmpty()) { + dom::RootedDictionary<dom::ProfileTimelineStackFrame> stackFrame(aCx); + stackFrame.mLine.Construct(mLineNumber); + stackFrame.mSource.Construct(mFileName); + stackFrame.mFunctionDisplayName.Construct(mFunctionName); + + if (mAsyncStack.isObject() && !mAsyncCause.IsEmpty()) { + JS::Rooted<JSObject*> asyncStack(aCx, &mAsyncStack.toObject()); + JS::Rooted<JSObject*> parentFrame(aCx); + JS::Rooted<JSString*> asyncCause( + aCx, JS_NewUCStringCopyN(aCx, mAsyncCause.BeginReading(), + mAsyncCause.Length())); + if (!asyncCause) { + JS_ClearPendingException(aCx); + return; + } + + if (JS::IsMaybeWrappedSavedFrame(asyncStack) && + !JS::CopyAsyncStack(aCx, asyncStack, asyncCause, &parentFrame, + mozilla::Nothing())) { + JS_ClearPendingException(aCx); + } else { + stackFrame.mAsyncParent = parentFrame; + } + } + + JS::Rooted<JS::Value> newStack(aCx); + if (ToJSValue(aCx, stackFrame, &newStack)) { + if (newStack.isObject()) { + aMarker.mStack = &newStack.toObject(); + } + } else { + JS_ClearPendingException(aCx); + } + } + } + + private: + nsString mCause; + nsString mFunctionName; + nsString mFileName; + uint32_t mLineNumber; + JS::PersistentRooted<JS::Value> mAsyncStack; + NS_ConvertUTF8toUTF16 mAsyncCause; +}; + +} // namespace mozilla + +#endif // mozilla_JavascriptTimelineMarker_h_ diff --git a/docshell/base/timeline/LayerTimelineMarker.h b/docshell/base/timeline/LayerTimelineMarker.h new file mode 100644 index 0000000000..7c7336ba52 --- /dev/null +++ b/docshell/base/timeline/LayerTimelineMarker.h @@ -0,0 +1,47 @@ +/* -*- 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_LayerTimelineMarker_h_ +#define mozilla_LayerTimelineMarker_h_ + +#include "TimelineMarker.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" +#include "mozilla/mozalloc_oom.h" +#include "nsRegion.h" + +namespace mozilla { + +class LayerTimelineMarker : public TimelineMarker { + public: + explicit LayerTimelineMarker(const nsIntRegion& aRegion) + : TimelineMarker("Layer", MarkerTracingType::HELPER_EVENT), + mRegion(aRegion) {} + + void AddLayerRectangles( + dom::Sequence<dom::ProfileTimelineLayerRect>& aRectangles) { + for (auto iter = mRegion.RectIter(); !iter.Done(); iter.Next()) { + const nsIntRect& iterRect = iter.Get(); + dom::ProfileTimelineLayerRect rect; + rect.mX = iterRect.X(); + rect.mY = iterRect.Y(); + rect.mWidth = iterRect.Width(); + rect.mHeight = iterRect.Height(); + if (!aRectangles.AppendElement(rect, fallible)) { + // XXX(Bug 1632090) Instead of extending the array 1-by-1 (which might + // involve multiple reallocations) and potentially crashing here, + // SetCapacity could be called outside the loop once. + mozalloc_handle_oom(0); + } + } + } + + private: + nsIntRegion mRegion; +}; + +} // namespace mozilla + +#endif // mozilla_LayerTimelineMarker_h_ diff --git a/docshell/base/timeline/MarkersStorage.cpp b/docshell/base/timeline/MarkersStorage.cpp new file mode 100644 index 0000000000..4a84135f29 --- /dev/null +++ b/docshell/base/timeline/MarkersStorage.cpp @@ -0,0 +1,20 @@ +/* -*- 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 "MarkersStorage.h" +#include "MainThreadUtils.h" + +namespace mozilla { + +MarkersStorage::MarkersStorage(const char* aMutexName) : mLock(aMutexName) { + MOZ_ASSERT(NS_IsMainThread()); +} + +MarkersStorage::~MarkersStorage() { MOZ_ASSERT(NS_IsMainThread()); } + +Mutex& MarkersStorage::GetLock() { return mLock; } + +} // namespace mozilla diff --git a/docshell/base/timeline/MarkersStorage.h b/docshell/base/timeline/MarkersStorage.h new file mode 100644 index 0000000000..746a02e4ec --- /dev/null +++ b/docshell/base/timeline/MarkersStorage.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 mozilla_MarkersStorage_h_ +#define mozilla_MarkersStorage_h_ + +#include "TimelineMarkerEnums.h" // for MarkerReleaseRequest +#include "mozilla/Mutex.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/LinkedList.h" +#include "nsTArray.h" + +namespace mozilla { +class AbstractTimelineMarker; + +namespace dom { +struct ProfileTimelineMarker; +} + +class MarkersStorage : public LinkedListElement<MarkersStorage> { + private: + MarkersStorage() = delete; + MarkersStorage(const MarkersStorage& aOther) = delete; + void operator=(const MarkersStorage& aOther) = delete; + + public: + explicit MarkersStorage(const char* aMutexName); + virtual ~MarkersStorage(); + + virtual void AddMarker(UniquePtr<AbstractTimelineMarker>&& aMarker) = 0; + virtual void AddOTMTMarker(UniquePtr<AbstractTimelineMarker>&& aMarker) = 0; + virtual void ClearMarkers() = 0; + virtual void PopMarkers(JSContext* aCx, + nsTArray<dom::ProfileTimelineMarker>& aStore) = 0; + + protected: + Mutex& GetLock(); + + private: + Mutex mLock; +}; + +} // namespace mozilla + +#endif /* mozilla_MarkersStorage_h_ */ diff --git a/docshell/base/timeline/MessagePortTimelineMarker.h b/docshell/base/timeline/MessagePortTimelineMarker.h new file mode 100644 index 0000000000..c6d91fa7eb --- /dev/null +++ b/docshell/base/timeline/MessagePortTimelineMarker.h @@ -0,0 +1,46 @@ +/* -*- 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_MessagePortTimelineMarker_h_ +#define mozilla_MessagePortTimelineMarker_h_ + +#include "TimelineMarker.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" + +namespace mozilla { + +class MessagePortTimelineMarker : public TimelineMarker { + public: + MessagePortTimelineMarker( + dom::ProfileTimelineMessagePortOperationType aOperationType, + MarkerTracingType aTracingType) + : TimelineMarker("MessagePort", aTracingType, + MarkerStackRequest::NO_STACK), + mOperationType(aOperationType) {} + + virtual UniquePtr<AbstractTimelineMarker> Clone() override { + MessagePortTimelineMarker* clone = + new MessagePortTimelineMarker(mOperationType, GetTracingType()); + clone->SetCustomTime(GetTime()); + return UniquePtr<AbstractTimelineMarker>(clone); + } + + virtual void AddDetails(JSContext* aCx, + dom::ProfileTimelineMarker& aMarker) override { + TimelineMarker::AddDetails(aCx, aMarker); + + if (GetTracingType() == MarkerTracingType::START) { + aMarker.mMessagePortOperation.Construct(mOperationType); + } + } + + private: + dom::ProfileTimelineMessagePortOperationType mOperationType; +}; + +} // namespace mozilla + +#endif /* mozilla_MessagePortTimelineMarker_h_ */ diff --git a/docshell/base/timeline/ObservedDocShell.cpp b/docshell/base/timeline/ObservedDocShell.cpp new file mode 100644 index 0000000000..4cba68ad29 --- /dev/null +++ b/docshell/base/timeline/ObservedDocShell.cpp @@ -0,0 +1,169 @@ +/* -*- 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 "ObservedDocShell.h" + +#include <utility> + +#include "AbstractTimelineMarker.h" +#include "LayerTimelineMarker.h" +#include "MainThreadUtils.h" +#include "mozilla/AutoRestore.h" +#include "nsIDocShell.h" + +namespace mozilla { + +ObservedDocShell::ObservedDocShell(nsIDocShell* aDocShell) + : MarkersStorage("ObservedDocShellMutex"), + mDocShell(aDocShell), + mPopping(false) { + MOZ_ASSERT(NS_IsMainThread()); +} + +void ObservedDocShell::AddMarker(UniquePtr<AbstractTimelineMarker>&& aMarker) { + // Only allow main thread markers to go into this list. No need to lock + // here since `mTimelineMarkers` will only be accessed or modified on the + // main thread only. + MOZ_ASSERT(NS_IsMainThread()); + // Don't accept any markers generated by the process of popping + // markers. + if (!mPopping) { + mTimelineMarkers.AppendElement(std::move(aMarker)); + } +} + +void ObservedDocShell::AddOTMTMarker( + UniquePtr<AbstractTimelineMarker>&& aMarker) { + // Only allow off the main thread markers to go into this list. Since most + // of our markers come from the main thread, be a little more efficient and + // avoid dealing with multithreading scenarios until all the markers are + // actually cleared or popped in `ClearMarkers` or `PopMarkers`. + MOZ_ASSERT(!NS_IsMainThread()); + MutexAutoLock lock(GetLock()); // for `mOffTheMainThreadTimelineMarkers`. + mOffTheMainThreadTimelineMarkers.AppendElement(std::move(aMarker)); +} + +void ObservedDocShell::ClearMarkers() { + MOZ_ASSERT(NS_IsMainThread()); + MutexAutoLock lock(GetLock()); // for `mOffTheMainThreadTimelineMarkers`. + mTimelineMarkers.Clear(); + mOffTheMainThreadTimelineMarkers.Clear(); +} + +void ObservedDocShell::PopMarkers( + JSContext* aCx, nsTArray<dom::ProfileTimelineMarker>& aStore) { + MOZ_ASSERT(NS_IsMainThread()); + MutexAutoLock lock(GetLock()); // for `mOffTheMainThreadTimelineMarkers`. + + MOZ_RELEASE_ASSERT(!mPopping); + AutoRestore<bool> resetPopping(mPopping); + mPopping = true; + + // First, move all of our markers into a single array. We'll chose + // the `mTimelineMarkers` store because that's where we expect most of + // our markers to be. + mTimelineMarkers.AppendElements(std::move(mOffTheMainThreadTimelineMarkers)); + + // If we see an unpaired START, we keep it around for the next call + // to ObservedDocShell::PopMarkers. We store the kept START objects here. + nsTArray<UniquePtr<AbstractTimelineMarker>> keptStartMarkers; + + for (uint32_t i = 0; i < mTimelineMarkers.Length(); ++i) { + UniquePtr<AbstractTimelineMarker>& startPayload = + mTimelineMarkers.ElementAt(i); + + // If this is a TIMESTAMP marker, there's no corresponding END, + // as it's a single unit of time, not a duration. + if (startPayload->GetTracingType() == MarkerTracingType::TIMESTAMP) { + dom::ProfileTimelineMarker* marker = aStore.AppendElement(); + marker->mName = NS_ConvertUTF8toUTF16(startPayload->GetName()); + marker->mStart = startPayload->GetTime(); + marker->mEnd = startPayload->GetTime(); + marker->mStack = startPayload->GetStack(); + startPayload->AddDetails(aCx, *marker); + continue; + } + + // Whenever a START marker is found, look for the corresponding END + // and build a {name,start,end} JS object. + if (startPayload->GetTracingType() == MarkerTracingType::START) { + bool hasSeenEnd = false; + + // "Paint" markers are different because painting is handled at root + // docshell level. The information that a paint was done is stored at + // sub-docshell level, but we can only be sure that a paint did actually + // happen in if a "Layer" marker was recorded too. + bool startIsPaintType = strcmp(startPayload->GetName(), "Paint") == 0; + bool hasSeenLayerType = false; + + // If we are processing a "Paint" marker, we append information from + // all the embedded "Layer" markers to this array. + dom::Sequence<dom::ProfileTimelineLayerRect> layerRectangles; + + // DOM events can be nested, so we must take care when searching + // for the matching end. It doesn't hurt to apply this logic to + // all event types. + uint32_t markerDepth = 0; + + // The assumption is that the devtools timeline flushes markers frequently + // enough for the amount of markers to always be small enough that the + // nested for loop isn't going to be a performance problem. + for (uint32_t j = i + 1; j < mTimelineMarkers.Length(); ++j) { + UniquePtr<AbstractTimelineMarker>& endPayload = + mTimelineMarkers.ElementAt(j); + bool endIsLayerType = strcmp(endPayload->GetName(), "Layer") == 0; + + // Look for "Layer" markers to stream out "Paint" markers. + if (startIsPaintType && endIsLayerType) { + AbstractTimelineMarker* raw = endPayload.get(); + LayerTimelineMarker* layerPayload = + static_cast<LayerTimelineMarker*>(raw); + layerPayload->AddLayerRectangles(layerRectangles); + hasSeenLayerType = true; + } + if (!startPayload->Equals(*endPayload)) { + continue; + } + if (endPayload->GetTracingType() == MarkerTracingType::START) { + ++markerDepth; + continue; + } + if (endPayload->GetTracingType() == MarkerTracingType::END) { + if (markerDepth > 0) { + --markerDepth; + continue; + } + if (!startIsPaintType || (startIsPaintType && hasSeenLayerType)) { + dom::ProfileTimelineMarker* marker = aStore.AppendElement(); + marker->mName = NS_ConvertUTF8toUTF16(startPayload->GetName()); + marker->mStart = startPayload->GetTime(); + marker->mEnd = endPayload->GetTime(); + marker->mStack = startPayload->GetStack(); + if (hasSeenLayerType) { + marker->mRectangles.Construct(layerRectangles); + } + startPayload->AddDetails(aCx, *marker); + endPayload->AddDetails(aCx, *marker); + } + hasSeenEnd = true; + break; + } + } + + // If we did not see the corresponding END, keep the START. + if (!hasSeenEnd) { + keptStartMarkers.AppendElement( + std::move(mTimelineMarkers.ElementAt(i))); + mTimelineMarkers.RemoveElementAt(i); + --i; + } + } + } + + mTimelineMarkers = std::move(keptStartMarkers); +} + +} // namespace mozilla diff --git a/docshell/base/timeline/ObservedDocShell.h b/docshell/base/timeline/ObservedDocShell.h new file mode 100644 index 0000000000..88b6747efb --- /dev/null +++ b/docshell/base/timeline/ObservedDocShell.h @@ -0,0 +1,52 @@ +/* -*- 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_ObservedDocShell_h_ +#define mozilla_ObservedDocShell_h_ + +#include "MarkersStorage.h" +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" +#include "nsTArray.h" + +class nsIDocShell; + +namespace mozilla { +class AbstractTimelineMarker; + +namespace dom { +struct ProfileTimelineMarker; +} + +// # ObservedDocShell +// +// A wrapper around a docshell for which docshell-specific markers are +// allowed to exist. See TimelineConsumers for register/unregister logic. +class ObservedDocShell : public MarkersStorage { + private: + RefPtr<nsIDocShell> mDocShell; + + // Main thread only. + nsTArray<UniquePtr<AbstractTimelineMarker>> mTimelineMarkers; + bool mPopping; + + // Off the main thread only. + nsTArray<UniquePtr<AbstractTimelineMarker>> mOffTheMainThreadTimelineMarkers; + + public: + explicit ObservedDocShell(nsIDocShell* aDocShell); + nsIDocShell* operator*() const { return mDocShell.get(); } + + void AddMarker(UniquePtr<AbstractTimelineMarker>&& aMarker) override; + void AddOTMTMarker(UniquePtr<AbstractTimelineMarker>&& aMarker) override; + void ClearMarkers() override; + void PopMarkers(JSContext* aCx, + nsTArray<dom::ProfileTimelineMarker>& aStore) override; +}; + +} // namespace mozilla + +#endif /* mozilla_ObservedDocShell_h_ */ diff --git a/docshell/base/timeline/RestyleTimelineMarker.h b/docshell/base/timeline/RestyleTimelineMarker.h new file mode 100644 index 0000000000..4cce10d94b --- /dev/null +++ b/docshell/base/timeline/RestyleTimelineMarker.h @@ -0,0 +1,37 @@ +/* -*- 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_RestyleTimelineMarker_h_ +#define mozilla_RestyleTimelineMarker_h_ + +#include "TimelineMarker.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" + +namespace mozilla { + +class RestyleTimelineMarker : public TimelineMarker { + public: + RestyleTimelineMarker(bool aIsAnimationOnly, MarkerTracingType aTracingType) + : TimelineMarker("Styles", aTracingType) { + mIsAnimationOnly = aIsAnimationOnly; + } + + virtual void AddDetails(JSContext* aCx, + dom::ProfileTimelineMarker& aMarker) override { + TimelineMarker::AddDetails(aCx, aMarker); + + if (GetTracingType() == MarkerTracingType::START) { + aMarker.mIsAnimationOnly.Construct(mIsAnimationOnly); + } + } + + private: + bool mIsAnimationOnly; +}; + +} // namespace mozilla + +#endif // mozilla_RestyleTimelineMarker_h_ diff --git a/docshell/base/timeline/TimelineConsumers.cpp b/docshell/base/timeline/TimelineConsumers.cpp new file mode 100644 index 0000000000..805a54979c --- /dev/null +++ b/docshell/base/timeline/TimelineConsumers.cpp @@ -0,0 +1,287 @@ +/* -*- 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 "TimelineConsumers.h" + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/ObservedDocShell.h" +#include "mozilla/Services.h" +#include "mozilla/TimelineMarker.h" +#include "jsapi.h" +#include "nsAppRunner.h" // for XRE_IsContentProcess, XRE_IsParentProcess +#include "nsCRT.h" +#include "nsDocShell.h" +#include "nsIObserverService.h" + +namespace mozilla { + +NS_IMPL_ISUPPORTS(TimelineConsumers, nsIObserver); + +StaticMutex TimelineConsumers::sMutex; + +// Manually manage this singleton's lifetime and destroy it before shutdown. +// This avoids the leakchecker detecting false-positive memory leaks when +// using automatic memory management (i.e. statically instantiating this +// singleton inside the `Get` method), which would automatically destroy it on +// application shutdown, but too late for the leakchecker. Sigh... +StaticRefPtr<TimelineConsumers> TimelineConsumers::sInstance; + +// This flag makes sure the singleton never gets instantiated while a shutdown +// is in progress. This can actually happen, and `ClearOnShutdown` doesn't work +// in these cases. +bool TimelineConsumers::sInShutdown = false; + +already_AddRefed<TimelineConsumers> TimelineConsumers::Get() { + // Using this class is not supported yet for other processes other than + // parent or content. To avoid accidental checks to methods like `IsEmpty`, + // which would probably always be true in those cases, assert here. + // Remember, there will be different singletons available to each process. + + // TODO: we have to avoid calling this function in socket process. + MOZ_ASSERT(XRE_IsContentProcess() || XRE_IsParentProcess()); + + // If we are shutting down, don't bother doing anything. Note: we can only + // know whether or not we're in shutdown if we're instantiated. + if (sInShutdown) { + return nullptr; + } + + // Note: We don't simply check `sInstance` for null-ness here, since otherwise + // this can resurrect the TimelineConsumers pretty late during shutdown. + // We won't know if we're in shutdown or not though, because the singleton + // could have been destroyed or just never instantiated, so in the previous + // conditional `sInShutdown` would be false. + static bool firstTime = true; + if (firstTime) { + firstTime = false; + + StaticMutexAutoLock lock(sMutex); + sInstance = new TimelineConsumers(); + + // Make sure the initialization actually suceeds, otherwise don't allow + // access by destroying the instance immediately. + if (sInstance->Init()) { + ClearOnShutdown(&sInstance); + } else { + sInstance->RemoveObservers(); + sInstance = nullptr; + } + } + + RefPtr<TimelineConsumers> copy = sInstance.get(); + return copy.forget(); +} + +bool TimelineConsumers::Init() { + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (!obs) { + return false; + } + if (NS_WARN_IF(NS_FAILED( + obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false)))) { + return false; + } + return true; +} + +bool TimelineConsumers::RemoveObservers() { + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (!obs) { + return false; + } + if (NS_WARN_IF(NS_FAILED( + obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID)))) { + return false; + } + return true; +} + +nsresult TimelineConsumers::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (!nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { + sInShutdown = true; + RemoveObservers(); + return NS_OK; + } + + MOZ_ASSERT(false, "TimelineConsumers got unexpected topic!"); + return NS_ERROR_UNEXPECTED; +} + +TimelineConsumers::TimelineConsumers() : mActiveConsumers(0) {} + +void TimelineConsumers::AddConsumer(nsDocShell* aDocShell) { + MOZ_ASSERT(NS_IsMainThread()); + StaticMutexAutoLock lock( + sMutex); // for `mActiveConsumers` and `mMarkersStores`. + + UniquePtr<ObservedDocShell>& observed = aDocShell->mObserved; + MOZ_ASSERT(!observed); + + if (mActiveConsumers == 0) { + JS::SetProfileTimelineRecordingEnabled(true); + } + mActiveConsumers++; + + ObservedDocShell* obsDocShell = new ObservedDocShell(aDocShell); + MarkersStorage* storage = static_cast<MarkersStorage*>(obsDocShell); + + observed.reset(obsDocShell); + mMarkersStores.insertFront(storage); +} + +void TimelineConsumers::RemoveConsumer(nsDocShell* aDocShell) { + MOZ_ASSERT(NS_IsMainThread()); + StaticMutexAutoLock lock( + sMutex); // for `mActiveConsumers` and `mMarkersStores`. + + UniquePtr<ObservedDocShell>& observed = aDocShell->mObserved; + MOZ_ASSERT(observed); + + mActiveConsumers--; + if (mActiveConsumers == 0) { + JS::SetProfileTimelineRecordingEnabled(false); + } + + // Clear all markers from the `mTimelineMarkers` store. + observed.get()->ClearMarkers(); + // Remove self from the `mMarkersStores` store. + observed.get()->remove(); + // Prepare for becoming a consumer later. + observed.reset(nullptr); +} + +bool TimelineConsumers::HasConsumer(nsIDocShell* aDocShell) { + MOZ_ASSERT(NS_IsMainThread()); + return aDocShell ? aDocShell->GetRecordProfileTimelineMarkers() : false; +} + +bool TimelineConsumers::IsEmpty() { + StaticMutexAutoLock lock(sMutex); // for `mActiveConsumers`. + return mActiveConsumers == 0; +} + +void TimelineConsumers::AddMarkerForDocShell(nsDocShell* aDocShell, + const char* aName, + MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest) { + MOZ_ASSERT(NS_IsMainThread()); + if (HasConsumer(aDocShell)) { + aDocShell->mObserved->AddMarker( + MakeUnique<TimelineMarker>(aName, aTracingType, aStackRequest)); + } +} + +void TimelineConsumers::AddMarkerForDocShell(nsDocShell* aDocShell, + const char* aName, + const TimeStamp& aTime, + MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest) { + MOZ_ASSERT(NS_IsMainThread()); + if (HasConsumer(aDocShell)) { + aDocShell->mObserved->AddMarker( + MakeUnique<TimelineMarker>(aName, aTime, aTracingType, aStackRequest)); + } +} + +void TimelineConsumers::AddMarkerForDocShell( + nsDocShell* aDocShell, UniquePtr<AbstractTimelineMarker>&& aMarker) { + MOZ_ASSERT(NS_IsMainThread()); + if (HasConsumer(aDocShell)) { + aDocShell->mObserved->AddMarker(std::move(aMarker)); + } +} + +void TimelineConsumers::AddMarkerForDocShell(nsIDocShell* aDocShell, + const char* aName, + MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest) { + MOZ_ASSERT(NS_IsMainThread()); + AddMarkerForDocShell(static_cast<nsDocShell*>(aDocShell), aName, aTracingType, + aStackRequest); +} + +void TimelineConsumers::AddMarkerForDocShell(nsIDocShell* aDocShell, + const char* aName, + const TimeStamp& aTime, + MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest) { + MOZ_ASSERT(NS_IsMainThread()); + AddMarkerForDocShell(static_cast<nsDocShell*>(aDocShell), aName, aTime, + aTracingType, aStackRequest); +} + +void TimelineConsumers::AddMarkerForDocShell( + nsIDocShell* aDocShell, UniquePtr<AbstractTimelineMarker>&& aMarker) { + MOZ_ASSERT(NS_IsMainThread()); + AddMarkerForDocShell(static_cast<nsDocShell*>(aDocShell), std::move(aMarker)); +} + +void TimelineConsumers::AddMarkerForAllObservedDocShells( + const char* aName, MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest /* = STACK */) { + bool isMainThread = NS_IsMainThread(); + StaticMutexAutoLock lock(sMutex); // for `mMarkersStores`. + + for (MarkersStorage* storage = mMarkersStores.getFirst(); storage != nullptr; + storage = storage->getNext()) { + UniquePtr<AbstractTimelineMarker> marker = + MakeUnique<TimelineMarker>(aName, aTracingType, aStackRequest); + if (isMainThread) { + storage->AddMarker(std::move(marker)); + } else { + storage->AddOTMTMarker(std::move(marker)); + } + } +} + +void TimelineConsumers::AddMarkerForAllObservedDocShells( + const char* aName, const TimeStamp& aTime, MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest /* = STACK */) { + bool isMainThread = NS_IsMainThread(); + StaticMutexAutoLock lock(sMutex); // for `mMarkersStores`. + + for (MarkersStorage* storage = mMarkersStores.getFirst(); storage != nullptr; + storage = storage->getNext()) { + UniquePtr<AbstractTimelineMarker> marker = + MakeUnique<TimelineMarker>(aName, aTime, aTracingType, aStackRequest); + if (isMainThread) { + storage->AddMarker(std::move(marker)); + } else { + storage->AddOTMTMarker(std::move(marker)); + } + } +} + +void TimelineConsumers::AddMarkerForAllObservedDocShells( + UniquePtr<AbstractTimelineMarker>& aMarker) { + bool isMainThread = NS_IsMainThread(); + StaticMutexAutoLock lock(sMutex); // for `mMarkersStores`. + + for (MarkersStorage* storage = mMarkersStores.getFirst(); storage != nullptr; + storage = storage->getNext()) { + UniquePtr<AbstractTimelineMarker> clone = aMarker->Clone(); + if (isMainThread) { + storage->AddMarker(std::move(clone)); + } else { + storage->AddOTMTMarker(std::move(clone)); + } + } +} + +void TimelineConsumers::PopMarkers( + nsDocShell* aDocShell, JSContext* aCx, + nsTArray<dom::ProfileTimelineMarker>& aStore) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!aDocShell || !aDocShell->mObserved) { + return; + } + + aDocShell->mObserved->PopMarkers(aCx, aStore); +} + +} // namespace mozilla diff --git a/docshell/base/timeline/TimelineConsumers.h b/docshell/base/timeline/TimelineConsumers.h new file mode 100644 index 0000000000..6a152cfd55 --- /dev/null +++ b/docshell/base/timeline/TimelineConsumers.h @@ -0,0 +1,131 @@ +/* -*- 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_TimelineConsumers_h_ +#define mozilla_TimelineConsumers_h_ + +#include "nsIObserver.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/LinkedList.h" +#include "mozilla/StaticMutex.h" +#include "nsTArray.h" +#include "TimelineMarkerEnums.h" // for MarkerTracingType + +class nsDocShell; +class nsIDocShell; +struct JSContext; + +namespace mozilla { +class TimeStamp; +class MarkersStorage; +class AbstractTimelineMarker; + +namespace dom { +struct ProfileTimelineMarker; +} + +class TimelineConsumers : public nsIObserver { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOBSERVER + + private: + TimelineConsumers(); + TimelineConsumers(const TimelineConsumers& aOther) = delete; + void operator=(const TimelineConsumers& aOther) = delete; + virtual ~TimelineConsumers() = default; + + bool Init(); + bool RemoveObservers(); + + public: + static already_AddRefed<TimelineConsumers> Get(); + + // Methods for registering interested consumers (i.e. "devtools toolboxes"). + // Each consumer should be directly focused on a particular docshell, but + // timeline markers don't necessarily have to be tied to that docshell. + // See the public `AddMarker*` methods below. + // Main thread only. + void AddConsumer(nsDocShell* aDocShell); + void RemoveConsumer(nsDocShell* aDocShell); + + bool HasConsumer(nsIDocShell* aDocShell); + + // Checks if there's any existing interested consumer. + // May be called from any thread. + bool IsEmpty(); + + // Methods for adding markers relevant for particular docshells, or generic + // (meaning that they either can't be tied to a particular docshell, or one + // wasn't accessible in the part of the codebase where they're instantiated). + // These will only add markers if at least one docshell is currently being + // observed by a timeline. Markers tied to a particular docshell won't be + // created unless that docshell is specifically being currently observed. + // See nsIDocShell::recordProfileTimelineMarkers + + // These methods create a basic TimelineMarker from a name and some metadata, + // relevant for a specific docshell. + // Main thread only. + void AddMarkerForDocShell( + nsDocShell* aDocShell, const char* aName, MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest = MarkerStackRequest::STACK); + void AddMarkerForDocShell( + nsIDocShell* aDocShell, const char* aName, MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest = MarkerStackRequest::STACK); + + void AddMarkerForDocShell( + nsDocShell* aDocShell, const char* aName, const TimeStamp& aTime, + MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest = MarkerStackRequest::STACK); + void AddMarkerForDocShell( + nsIDocShell* aDocShell, const char* aName, const TimeStamp& aTime, + MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest = MarkerStackRequest::STACK); + + // These methods register and receive ownership of an already created marker, + // relevant for a specific docshell. + // Main thread only. + void AddMarkerForDocShell(nsDocShell* aDocShell, + UniquePtr<AbstractTimelineMarker>&& aMarker); + void AddMarkerForDocShell(nsIDocShell* aDocShell, + UniquePtr<AbstractTimelineMarker>&& aMarker); + + // These methods create a basic marker from a name and some metadata, + // which doesn't have to be relevant to a specific docshell. + // May be called from any thread. + void AddMarkerForAllObservedDocShells( + const char* aName, MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest = MarkerStackRequest::STACK); + void AddMarkerForAllObservedDocShells( + const char* aName, const TimeStamp& aTime, MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest = MarkerStackRequest::STACK); + + // This method clones and registers an already instantiated marker, + // which doesn't have to be relevant to a specific docshell. + // May be called from any thread. + void AddMarkerForAllObservedDocShells( + UniquePtr<AbstractTimelineMarker>& aMarker); + + void PopMarkers(nsDocShell* aDocShell, JSContext* aCx, + nsTArray<dom::ProfileTimelineMarker>& aStore); + + private: + static StaticRefPtr<TimelineConsumers> sInstance; + static bool sInShutdown; + + // Counter for how many timelines are currently interested in markers, + // and a list of the MarkersStorage interfaces representing them. + unsigned long mActiveConsumers; + LinkedList<MarkersStorage> mMarkersStores; + + // Protects this class's data structures. + static StaticMutex sMutex; +}; + +} // namespace mozilla + +#endif /* mozilla_TimelineConsumers_h_ */ diff --git a/docshell/base/timeline/TimelineMarker.cpp b/docshell/base/timeline/TimelineMarker.cpp new file mode 100644 index 0000000000..f43c892a04 --- /dev/null +++ b/docshell/base/timeline/TimelineMarker.cpp @@ -0,0 +1,65 @@ +/* -*- 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 "TimelineMarker.h" + +#include "jsapi.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" +#include "nsContentUtils.h" + +namespace mozilla { + +TimelineMarker::TimelineMarker(const char* aName, + MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest) + : AbstractTimelineMarker(aName, aTracingType) { + CaptureStackIfNecessary(aTracingType, aStackRequest); +} + +TimelineMarker::TimelineMarker(const char* aName, const TimeStamp& aTime, + MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest) + : AbstractTimelineMarker(aName, aTime, aTracingType) { + CaptureStackIfNecessary(aTracingType, aStackRequest); +} + +void TimelineMarker::AddDetails(JSContext* aCx, + dom::ProfileTimelineMarker& aMarker) { + if (GetTracingType() == MarkerTracingType::START) { + aMarker.mProcessType.Construct(GetProcessType()); + aMarker.mIsOffMainThread.Construct(IsOffMainThread()); + } +} + +JSObject* TimelineMarker::GetStack() { + if (mStackTrace.initialized()) { + return mStackTrace; + } + return nullptr; +} + +void TimelineMarker::CaptureStack() { + JSContext* ctx = nsContentUtils::GetCurrentJSContext(); + if (ctx) { + JS::RootedObject stack(ctx); + if (JS::CaptureCurrentStack(ctx, &stack)) { + mStackTrace.init(ctx, stack.get()); + } else { + JS_ClearPendingException(ctx); + } + } +} + +void TimelineMarker::CaptureStackIfNecessary(MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest) { + if ((aTracingType == MarkerTracingType::START || + aTracingType == MarkerTracingType::TIMESTAMP) && + aStackRequest != MarkerStackRequest::NO_STACK) { + CaptureStack(); + } +} + +} // namespace mozilla diff --git a/docshell/base/timeline/TimelineMarker.h b/docshell/base/timeline/TimelineMarker.h new file mode 100644 index 0000000000..b7c5a83ce9 --- /dev/null +++ b/docshell/base/timeline/TimelineMarker.h @@ -0,0 +1,47 @@ +/* -*- 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_TimelineMarker_h_ +#define mozilla_TimelineMarker_h_ + +#include "AbstractTimelineMarker.h" +#include "js/RootingAPI.h" + +namespace mozilla { + +// Objects of this type can be added to the timeline if there is an interested +// consumer. The class can also be subclassed to let a given marker creator +// provide custom details. +class TimelineMarker : public AbstractTimelineMarker { + public: + TimelineMarker(const char* aName, MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest = MarkerStackRequest::STACK); + + TimelineMarker(const char* aName, const TimeStamp& aTime, + MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest = MarkerStackRequest::STACK); + + virtual void AddDetails(JSContext* aCx, + dom::ProfileTimelineMarker& aMarker) override; + virtual JSObject* GetStack() override; + + protected: + void CaptureStack(); + + private: + // While normally it is not a good idea to make a persistent root, + // in this case changing nsDocShell to participate in cycle + // collection was deemed too invasive, and the markers are only held + // here temporarily to boot. + JS::PersistentRooted<JSObject*> mStackTrace; + + void CaptureStackIfNecessary(MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest); +}; + +} // namespace mozilla + +#endif /* mozilla_TimelineMarker_h_ */ diff --git a/docshell/base/timeline/TimelineMarkerEnums.h b/docshell/base/timeline/TimelineMarkerEnums.h new file mode 100644 index 0000000000..6c6a39245c --- /dev/null +++ b/docshell/base/timeline/TimelineMarkerEnums.h @@ -0,0 +1,18 @@ +/* -*- 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_TimelineMarkerEnums_h_ +#define mozilla_TimelineMarkerEnums_h_ + +namespace mozilla { + +enum class MarkerTracingType { START, END, TIMESTAMP, HELPER_EVENT }; + +enum class MarkerStackRequest { STACK, NO_STACK }; + +} // namespace mozilla + +#endif // mozilla_TimelineMarkerEnums_h_ diff --git a/docshell/base/timeline/TimestampTimelineMarker.h b/docshell/base/timeline/TimestampTimelineMarker.h new file mode 100644 index 0000000000..6950f85899 --- /dev/null +++ b/docshell/base/timeline/TimestampTimelineMarker.h @@ -0,0 +1,36 @@ +/* -*- 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_TimestampTimelineMarker_h_ +#define mozilla_TimestampTimelineMarker_h_ + +#include "TimelineMarker.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" + +namespace mozilla { + +class TimestampTimelineMarker : public TimelineMarker { + public: + explicit TimestampTimelineMarker(const nsAString& aCause) + : TimelineMarker("TimeStamp", MarkerTracingType::TIMESTAMP), + mCause(aCause) {} + + virtual void AddDetails(JSContext* aCx, + dom::ProfileTimelineMarker& aMarker) override { + TimelineMarker::AddDetails(aCx, aMarker); + + if (!mCause.IsEmpty()) { + aMarker.mCauseName.Construct(mCause); + } + } + + private: + nsString mCause; +}; + +} // namespace mozilla + +#endif // mozilla_TimestampTimelineMarker_h_ diff --git a/docshell/base/timeline/WorkerTimelineMarker.h b/docshell/base/timeline/WorkerTimelineMarker.h new file mode 100644 index 0000000000..c0c0517b09 --- /dev/null +++ b/docshell/base/timeline/WorkerTimelineMarker.h @@ -0,0 +1,44 @@ +/* -*- 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_WorkerTimelineMarker_h_ +#define mozilla_WorkerTimelineMarker_h_ + +#include "TimelineMarker.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" + +namespace mozilla { + +class WorkerTimelineMarker : public TimelineMarker { + public: + WorkerTimelineMarker(dom::ProfileTimelineWorkerOperationType aOperationType, + MarkerTracingType aTracingType) + : TimelineMarker("Worker", aTracingType, MarkerStackRequest::NO_STACK), + mOperationType(aOperationType) {} + + virtual UniquePtr<AbstractTimelineMarker> Clone() override { + WorkerTimelineMarker* clone = + new WorkerTimelineMarker(mOperationType, GetTracingType()); + clone->SetCustomTime(GetTime()); + return UniquePtr<AbstractTimelineMarker>(clone); + } + + virtual void AddDetails(JSContext* aCx, + dom::ProfileTimelineMarker& aMarker) override { + TimelineMarker::AddDetails(aCx, aMarker); + + if (GetTracingType() == MarkerTracingType::START) { + aMarker.mWorkerOperation.Construct(mOperationType); + } + } + + private: + dom::ProfileTimelineWorkerOperationType mOperationType; +}; + +} // namespace mozilla + +#endif /* mozilla_WorkerTimelineMarker_h_ */ diff --git a/docshell/base/timeline/moz.build b/docshell/base/timeline/moz.build new file mode 100644 index 0000000000..9147e8263c --- /dev/null +++ b/docshell/base/timeline/moz.build @@ -0,0 +1,45 @@ +# -*- 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 = ("DevTools", "Performance Tools (Profiler/Timeline)") + +EXPORTS.mozilla += [ + "AbstractTimelineMarker.h", + "AutoGlobalTimelineMarker.h", + "AutoRestyleTimelineMarker.h", + "AutoTimelineMarker.h", + "CompositeTimelineMarker.h", + "ConsoleTimelineMarker.h", + "DocLoadingTimelineMarker.h", + "EventTimelineMarker.h", + "JavascriptTimelineMarker.h", + "LayerTimelineMarker.h", + "MarkersStorage.h", + "MessagePortTimelineMarker.h", + "ObservedDocShell.h", + "RestyleTimelineMarker.h", + "TimelineConsumers.h", + "TimelineMarker.h", + "TimelineMarkerEnums.h", + "TimestampTimelineMarker.h", + "WorkerTimelineMarker.h", +] + +UNIFIED_SOURCES += [ + "AbstractTimelineMarker.cpp", + "AutoGlobalTimelineMarker.cpp", + "AutoRestyleTimelineMarker.cpp", + "AutoTimelineMarker.cpp", + "MarkersStorage.cpp", + "ObservedDocShell.cpp", + "TimelineConsumers.cpp", + "TimelineMarker.cpp", +] + +FINAL_LIBRARY = "xul" + +LOCAL_INCLUDES += ["/docshell/base"] diff --git a/docshell/base/timeline/readme.md b/docshell/base/timeline/readme.md new file mode 100644 index 0000000000..d2c3d5c215 --- /dev/null +++ b/docshell/base/timeline/readme.md @@ -0,0 +1,97 @@ + +#Timeline + +The files in this directory are concerned with providing the backend platform features required for the developer tools interested in tracking down operations done in Gecko. The mechanism we use to define these operations are `markers`. + +Examples of traced operations include: + +* Style Recalculation +* Layout +* Painting +* JavaScript run-to-completion +* HTML parsing +* etc. + +The traced operations are displayed in the DevTools Performance tool's timeline. + +This is an overview of how everything works and can be extended. + +##MarkersStorage +A `MarkersStorage` is an abstract class defining a place where timeline markers may be held. It defines an interface with pure virtual functions to highlight how this storage can be interacted with: + +- `AddMarker`: adding a marker, from the main thread only +- `AddOTMTMarker`: adding a marker off the main thread only +- `ClearMarkers`: clearing all accumulated markers (both from the main thread and off it) +- `PopMarkers`: popping all accumulated markers (both from the main thread and off it). + +Note on why we handle on/off the main thread markers separately: since most of our markers will come from the main thread, we can be a little more efficient and avoid dealing with multithreading scenarios until all the markers are actually cleared or popped in `ClearMarkers` or `PopMarkers`. Main thread markers may only be added via `AddMarker`, while off the main thread markers may only be added via `AddOTMTMarker`. Clearing and popping markers will yield until all operations involving off the main thread markers finish. When popping, the markers accumulated off the main thread will be moved over. We expect popping to be fairly infrequent (every few hundred milliseconds, currently we schedule this to happen every 200ms). + +##ObservedDocShell +The only implementation of a MarkersStorage we have right now is an `ObservedDocShell`. + +Instances of `ObservedDocShell` accumulate markers that are *mostly* about a particular docshell. At a high level, for example, an `ObservedDocshell` would be created when a timeline tool is opened on a page. It is reasonable to assume that most operations which are interesting for that particular page happen on the main thread. However certain operations may happen outside of it, yet are interesting for its developers, for which markers can be created as well (e.g. web audio stuff, service workers etc.). It is also reasonable to assume that a docshell may sometimes not be easily accessible from certain parts of the platform code, but for which markers still need to be created. + +Therefore, the following scenarios arise: + +- a). creating a marker on the main thread about a particular docshell + +- b). creating a marker on the main thread without pinpointing to an affected docshell (unlikely, but allowed; in this case such a marker would have to be stored in all currently existing `ObservedDocShell` instances) + +- c). creating a marker off the main thread about a particular docshell (impossible; docshells can't be referenced outside the main thread, in which case some other type of identification mechanism needs to be put in place). + +- d). creating a marker off the main thread without pinpointing to a particular docshell (same path as c. here, such a marker would have to be stored in all currently existing `ObservedDocShell` instances). + +An observed docshell (in other words, "a docshell for which a timeline tool was opened") can thus receive both main thread and off the main thread markers. + +Cross-process markers are unnecessary at the moment, but tracked in bug 1200120. + +##TimelineConsumers +A `TimelineConsumer` is a singleton that facilitates access to `ObservedDocShell` instances. This is where a docshell can register/unregister itself as being observed via the `AddConsumer` and `RemoveConsumer` methods. + +All markers may only be stored via this singleton. Certain helper methods are available: + +* Main thread only +`AddMarkerForDocShell(nsDocShell*, const char*, MarkerTracingType)` +`AddMarkerForDocShell(nsDocShell*, const char*, const TimeStamp&, MarkerTracingType)` +`AddMarkerForDocShell(nsDocShell*, UniquePtr<AbstractTimelineMarker>&&)` + +* Any thread +`AddMarkerForAllObservedDocShells(const char*, MarkerTracingType)` +`AddMarkerForAllObservedDocShells(const char*, const TimeStamp&, MarkerTracingType)` +`AddMarkerForAllObservedDocShells(UniquePtr<AbstractTimelineMarker>&)` + +The "main thread only" methods deal with point a). described above. The "any thread" methods deal with points b). and d). + +##AbstractTimelineMarker + +All markers inherit from this abstract class, providing a simple thread-safe extendable blueprint. + +Markers are readonly after instantiation, and will always be identified by a name, a timestamp and their tracing type (`START`, `END`, `TIMESTAMP`). It *should not* make sense to modify their data after their creation. + +There are only two accessible constructors: +`AbstractTimelineMarker(const char*, MarkerTracingType)` +`AbstractTimelineMarker(const char*, const TimeStamp&, MarkerTracingType)` +which create a marker with a name and a tracing type. If unspecified, the corresponding timestamp will be the current instantiation time. Instantiating a marker *much later* after a particular operation is possible, but be careful providing the correct timestamp. + +The `AddDetails` virtual method should be implemented by subclasses when creating WebIDL versions of these markers, which will be sent over to a JavaScript frontend. + +##TimelineMarker +A `TimelineMarker` is the main `AbstractTimelineMarker` implementation. They allow attaching a JavaScript stack on `START` and `TIMESTAMP` markers. + +These markers will be created when using the `TimelineConsumers` helper methods which take in a string, a tracing type and (optionally) a timestamp. For more complex markers, subclasses are encouraged. See `EventTimelineMarker` or `ConsoleTimelineMarker` for some examples. + +##RAII + +### mozilla::AutoTimelineMarker + +The easiest way to trace Gecko events/tasks with start and end timeline markers is to use the `mozilla::AutoTimelineMarker` RAII class. It automatically adds the start marker on construction, and adds the end marker on destruction. Don't worry too much about potential performance impact! It only actually adds the markers when the given docshell is being observed by a timeline consumer, so essentially nothing will happen if a tool to inspect those markers isn't specifically open. + +This class may only be used on the main thread, and pointer to a docshell is necessary. If the docshell is a nullptr, nothing happens and this operation fails silently. + +Example: `AutoTimelineMarker marker(aTargetNode->OwnerDoc()->GetDocShell(), "Parse HTML");` + +### mozilla::AutoGlobalTimelineMarker` + +Similar to the previous RAII class, but doesn't expect a specific docshell, and the marker will be visible in all timeline consumers. This is useful for generic operations that don't involve a particular docshell, or where a docshell isn't accessible. May also only be used on the main thread. + +Example: `AutoGlobalTimelineMarker marker("Some global operation");` |