Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et 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 "DocumentLoadListener.h"
#include "imgLoader.h"
#include "NeckoCommon.h"
#include "nsLoadGroup.h"
#include "mozilla/AntiTrackingUtils.h"
#include "mozilla/AppShutdown.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/DynamicFpiNavigationHeuristic.h"
#include "mozilla/Components.h"
#include "mozilla/LoadInfo.h"
#include "mozilla/NullPrincipal.h"
#include "mozilla/RefPtr.h"
#include "mozilla/ResultVariant.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/StaticPrefs_extensions.h"
#include "mozilla/StaticPrefs_fission.h"
#include "mozilla/StaticPrefs_security.h"
#include "mozilla/dom/BrowserParent.h"
#include "mozilla/dom/BrowsingContextGroup.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/ChildProcessChannelListener.h"
#include "mozilla/dom/ClientChannelHelper.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/ContentProcessManager.h"
#include "mozilla/dom/ProcessIsolation.h"
#include "mozilla/dom/SessionHistoryEntry.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/dom/ipc/IdType.h"
#include "mozilla/net/CookieJarSettings.h"
#include "mozilla/net/HttpChannelParent.h"
#include "mozilla/net/RedirectChannelRegistrar.h"
#include "nsContentSecurityUtils.h"
#include "nsContentSecurityManager.h"
#include "nsDocShell.h"
#include "nsDocShellLoadState.h"
#include "nsDocShellLoadTypes.h"
#include "nsDOMNavigationTiming.h"
#include "nsDSURIContentListener.h"
#include "nsObjectLoadingContent.h"
#include "nsOpenWindowInfo.h"
#include "nsExternalHelperAppService.h"
#include "nsHttpChannel.h"
#include "nsIBrowser.h"
#include "nsIHttpChannelInternal.h"
#include "nsIStreamConverterService.h"
#include "nsIViewSourceChannel.h"
#include "nsImportModule.h"
#include "nsIXULRuntime.h"
#include "nsMimeTypes.h"
#include "nsQueryObject.h"
#include "nsRedirectHistoryEntry.h"
#include "nsSandboxFlags.h"
#include "nsSHistory.h"
#include "nsStringStream.h"
#include "nsURILoader.h"
#include "nsWebNavigationInfo.h"
#include "mozilla/dom/BrowserParent.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/nsHTTPSOnlyUtils.h"
#include "mozilla/dom/ReferrerInfo.h"
#include "mozilla/dom/RemoteWebProgressRequest.h"
#include "mozilla/net/UrlClassifierFeatureFactory.h"
#include "mozilla/ExtensionPolicyService.h"
#ifdef ANDROID
# include "mozilla/widget/nsWindow.h"
#endif /* ANDROID */
mozilla::LazyLogModule gDocumentChannelLog("DocumentChannel");
#define LOG(fmt) MOZ_LOG(gDocumentChannelLog, mozilla::LogLevel::Verbose, fmt)
extern mozilla::LazyLogModule gSHIPBFCacheLog;
// Bug 136580: Limit to the number of nested content frames that can have the
// same URL. This is to stop content that is recursively loading
// itself. Note that "#foo" on the end of URL doesn't affect
// whether it's considered identical, but "?foo" or ";foo" are
// considered and compared.
// Limit this to 2, like chromium does.
static constexpr int kMaxSameURLContentFrames = 2;
using namespace mozilla::dom;
namespace mozilla {
namespace net {
static ContentParentId GetContentProcessId(ContentParent* aContentParent) {
return aContentParent ? aContentParent->ChildID() : ContentParentId{0};
}
static void SetNeedToAddURIVisit(nsIChannel* aChannel,
bool aNeedToAddURIVisit) {
nsCOMPtr<nsIWritablePropertyBag2> props(do_QueryInterface(aChannel));
if (!props) {
return;
}
props->SetPropertyAsBool(u"docshell.needToAddURIVisit"_ns,
aNeedToAddURIVisit);
}
static auto SecurityFlagsForLoadInfo(nsDocShellLoadState* aLoadState)
-> nsSecurityFlags {
// TODO: Block copied from nsDocShell::DoURILoad, refactor out somewhere
nsSecurityFlags securityFlags =
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL;
if (aLoadState->LoadType() == LOAD_ERROR_PAGE) {
securityFlags |= nsILoadInfo::SEC_LOAD_ERROR_PAGE;
}
if (aLoadState->PrincipalToInherit()) {
bool isSrcdoc = aLoadState->HasInternalLoadFlags(
nsDocShell::INTERNAL_LOAD_FLAGS_IS_SRCDOC);
bool inheritAttrs = nsContentUtils::ChannelShouldInheritPrincipal(
aLoadState->PrincipalToInherit(), aLoadState->URI(),
true, // aInheritForAboutBlank
isSrcdoc);
bool isData = SchemeIsData(aLoadState->URI());
if (inheritAttrs && !isData) {
securityFlags |= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
}
}
return securityFlags;
}
// Construct a LoadInfo object to use when creating the internal channel for a
// Document/SubDocument load.
static auto CreateDocumentLoadInfo(CanonicalBrowsingContext* aBrowsingContext,
nsDocShellLoadState* aLoadState)
-> already_AddRefed<LoadInfo> {
uint32_t sandboxFlags = aBrowsingContext->GetSandboxFlags();
RefPtr<LoadInfo> loadInfo;
auto securityFlags = SecurityFlagsForLoadInfo(aLoadState);
if (aBrowsingContext->GetParent()) {
loadInfo = LoadInfo::CreateForFrame(
aBrowsingContext, aLoadState->TriggeringPrincipal(),
aLoadState->GetEffectiveTriggeringRemoteType(), securityFlags,
sandboxFlags);
} else {
OriginAttributes attrs;
aBrowsingContext->GetOriginAttributes(attrs);
loadInfo = LoadInfo::CreateForDocument(
aBrowsingContext, aLoadState->URI(), aLoadState->TriggeringPrincipal(),
aLoadState->GetEffectiveTriggeringRemoteType(), attrs, securityFlags,
sandboxFlags);
}
bool isPrivateWin = aBrowsingContext->UsePrivateBrowsing();
if (aLoadState->IsExemptFromHTTPSFirstMode() &&
nsHTTPSOnlyUtils::IsHttpsFirstModeEnabled(isPrivateWin)) {
uint32_t httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
httpsOnlyStatus |= nsILoadInfo::HTTPS_ONLY_EXEMPT;
loadInfo->SetHttpsOnlyStatus(httpsOnlyStatus);
}
loadInfo->SetSchemelessInput(aLoadState->GetSchemelessInput());
loadInfo->SetHttpsUpgradeTelemetry(aLoadState->GetHttpsUpgradeTelemetry());
loadInfo->SetTriggeringSandboxFlags(aLoadState->TriggeringSandboxFlags());
loadInfo->SetTriggeringWindowId(aLoadState->TriggeringWindowId());
loadInfo->SetTriggeringStorageAccess(aLoadState->TriggeringStorageAccess());
loadInfo->SetHasValidUserGestureActivation(
aLoadState->HasValidUserGestureActivation());
loadInfo->SetTextDirectiveUserActivation(
aLoadState->GetTextDirectiveUserActivation());
loadInfo->SetIsMetaRefresh(aLoadState->IsMetaRefresh());
return loadInfo.forget();
}
// Construct a LoadInfo object to use when creating the internal channel for an
// Object/Embed load.
static auto CreateObjectLoadInfo(nsDocShellLoadState* aLoadState,
uint64_t aInnerWindowId,
nsContentPolicyType aContentPolicyType,
uint32_t aSandboxFlags)
-> already_AddRefed<LoadInfo> {
RefPtr<WindowGlobalParent> wgp =
WindowGlobalParent::GetByInnerWindowId(aInnerWindowId);
MOZ_RELEASE_ASSERT(wgp);
auto securityFlags = SecurityFlagsForLoadInfo(aLoadState);
RefPtr<LoadInfo> loadInfo = LoadInfo::CreateForNonDocument(
wgp, wgp->DocumentPrincipal(), aContentPolicyType, securityFlags,
aSandboxFlags);
loadInfo->SetHasValidUserGestureActivation(
aLoadState->HasValidUserGestureActivation());
loadInfo->SetTextDirectiveUserActivation(
aLoadState->GetTextDirectiveUserActivation());
loadInfo->SetTriggeringSandboxFlags(aLoadState->TriggeringSandboxFlags());
loadInfo->SetTriggeringWindowId(aLoadState->TriggeringWindowId());
loadInfo->SetTriggeringStorageAccess(aLoadState->TriggeringStorageAccess());
loadInfo->SetIsMetaRefresh(aLoadState->IsMetaRefresh());
return loadInfo.forget();
}
/**
* An extension to nsDocumentOpenInfo that we run in the parent process, so
* that we can make the decision to retarget to content handlers or the external
* helper app, before we make process switching decisions.
*
* This modifies the behaviour of nsDocumentOpenInfo so that it can do
* retargeting, but doesn't do stream conversion (but confirms that we will be
* able to do so later).
*
* We still run nsDocumentOpenInfo in the content process, but disable
* retargeting, so that it can only apply stream conversion, and then send data
* to the docshell.
*/
class ParentProcessDocumentOpenInfo final : public nsDocumentOpenInfo,
public nsIMultiPartChannelListener {
public:
ParentProcessDocumentOpenInfo(ParentChannelListener* aListener,
uint32_t aFlags,
mozilla::dom::BrowsingContext* aBrowsingContext,
const nsACString& aTypeHint,
bool aIsDocumentLoad)
: nsDocumentOpenInfo(aFlags, false),
mBrowsingContext(aBrowsingContext),
mListener(aListener),
mTypeHint(aTypeHint),
mIsDocumentLoad(aIsDocumentLoad) {
LOG(("ParentProcessDocumentOpenInfo ctor [this=%p]", this));
}
NS_DECL_ISUPPORTS_INHERITED
// The default content listener is always a docshell, so this manually
// implements the same checks, and if it succeeds, uses the parent
// channel listener so that we forward onto DocumentLoadListener.
bool TryDefaultContentListener(nsIChannel* aChannel,
const nsCString& aContentType) {
uint32_t canHandle = nsWebNavigationInfo::IsTypeSupported(aContentType);
if (canHandle != nsIWebNavigationInfo::UNSUPPORTED) {
m_targetStreamListener = mListener;
nsLoadFlags loadFlags = 0;
aChannel->GetLoadFlags(&loadFlags);
aChannel->SetLoadFlags(loadFlags | nsIChannel::LOAD_TARGETED);
return true;
}
return false;
}
bool TryDefaultContentListener(nsIChannel* aChannel) override {
return TryDefaultContentListener(aChannel, mContentType);
}
// Generally we only support stream converters that can tell
// use exactly what type they'll output. If we find one, then
// we just target to our default listener directly (without
// conversion), and the content process nsDocumentOpenInfo will
// run and do the actual conversion.
nsresult TryStreamConversion(nsIChannel* aChannel) override {
// The one exception is nsUnknownDecoder, which works in the parent
// (and we need to know what the content type is before we can
// decide if it will be handled in the parent), so we run that here.
if (mContentType.LowerCaseEqualsASCII(UNKNOWN_CONTENT_TYPE) ||
mContentType.IsEmpty()) {
return nsDocumentOpenInfo::TryStreamConversion(aChannel);
}
if (nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
loadInfo->GetSandboxFlags() &&
mContentType.LowerCaseEqualsLiteral(APPLICATION_PDF)) {
// Sandboxed iframes are just never allowed to display plugins. In the
// modern world, this just means "application/pdf".
return NS_ERROR_FAILURE;
}
nsresult rv;
nsCOMPtr<nsIStreamConverterService> streamConvService;
nsAutoCString str;
streamConvService = mozilla::components::StreamConverter::Service(&rv);
rv = streamConvService->ConvertedType(mContentType, aChannel, str);
NS_ENSURE_SUCCESS(rv, rv);
// We only support passing data to the default content listener
// (docshell), and we don't supported chaining converters.
if (TryDefaultContentListener(aChannel, str)) {
mContentType = str;
return NS_OK;
}
// This is the same result as nsStreamConverterService uses when it
// can't find a converter
return NS_ERROR_FAILURE;
}
nsresult TryExternalHelperApp(nsIExternalHelperAppService* aHelperAppService,
nsIChannel* aChannel) override {
RefPtr<nsIStreamListener> listener;
nsresult rv = aHelperAppService->CreateListener(
mContentType, aChannel, mBrowsingContext, false, nullptr,
getter_AddRefs(listener));
if (NS_SUCCEEDED(rv)) {
m_targetStreamListener = listener;
}
return rv;
}
nsDocumentOpenInfo* Clone() override {
mCloned = true;
return new ParentProcessDocumentOpenInfo(
mListener, mFlags, mBrowsingContext, mTypeHint, mIsDocumentLoad);
}
nsresult OnDocumentStartRequest(nsIRequest* request) {
LOG(("ParentProcessDocumentOpenInfo OnDocumentStartRequest [this=%p]",
this));
nsresult rv = nsDocumentOpenInfo::OnStartRequest(request);
// If we didn't find a content handler, and we don't have a listener, then
// just forward to our default listener. This happens when the channel is in
// an error state, and we want to just forward that on to be handled in the
// content process, or when DONT_RETARGET is set.
if (!mUsedContentHandler && !m_targetStreamListener) {
m_targetStreamListener = mListener;
if (NS_FAILED(rv)) {
LOG(("nsDocumentOpenInfo OnStartRequest Failed [this=%p, rv=%s]", this,
GetStaticErrorName(rv)));
request->CancelWithReason(
rv, "nsDocumentOpenInfo::OnStartRequest failed"_ns);
}
return m_targetStreamListener->OnStartRequest(request);
}
if (m_targetStreamListener != mListener) {
LOG(
("ParentProcessDocumentOpenInfo targeted to non-default listener "
"[this=%p]",
this));
// If this is the only part, then we can immediately tell our listener
// that it won't be getting any content and disconnect it. For multipart
// channels we have to wait until we've handled all parts before we know.
// This does mean that the content process can still Cancel() a multipart
// response while the response is being handled externally, but this
// matches the single-process behaviour.
// If we got cloned, then we don't need to do this, as only the last link
// needs to do it.
// Multi-part channels are guaranteed to call OnAfterLastPart, which we
// forward to the listeners, so it will handle disconnection at that
// point.
nsCOMPtr<nsIMultiPartChannel> multiPartChannel =
do_QueryInterface(request);
if (!multiPartChannel && !mCloned) {
DisconnectChildListeners(NS_FAILED(rv) ? rv : NS_BINDING_RETARGETED,
rv);
}
}
return rv;
}
nsresult OnObjectStartRequest(nsIRequest* request) {
LOG(("ParentProcessDocumentOpenInfo OnObjectStartRequest [this=%p]", this));
// If this load will be treated as a document load, run through
// nsDocumentOpenInfo for consistency with other document loads.
//
// If the dom.navigation.object_embed.allow_retargeting pref is enabled,
// this may lead to the resource being downloaded.
if (nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
channel && channel->IsDocument()) {
// Respect the specified image MIME type if loading binary content type
// into an object/embed element.
nsAutoCString channelType;
channel->GetContentType(channelType);
if (!mTypeHint.IsEmpty() &&
imgLoader::SupportImageWithMimeType(mTypeHint) &&
(channelType.EqualsASCII(APPLICATION_GUESS_FROM_EXT) ||
channelType.EqualsASCII(APPLICATION_OCTET_STREAM) ||
channelType.EqualsASCII(BINARY_OCTET_STREAM))) {
channel->SetContentType(mTypeHint);
}
return OnDocumentStartRequest(request);
}
// Just redirect to the nsObjectLoadingContent in the content process.
m_targetStreamListener = mListener;
return m_targetStreamListener->OnStartRequest(request);
}
NS_IMETHOD OnStartRequest(nsIRequest* request) override {
LOG(("ParentProcessDocumentOpenInfo OnStartRequest [this=%p]", this));
if (mIsDocumentLoad) {
return OnDocumentStartRequest(request);
}
return OnObjectStartRequest(request);
}
NS_IMETHOD OnAfterLastPart(nsresult aStatus) override {
mListener->OnAfterLastPart(aStatus);
return NS_OK;
}
private:
virtual ~ParentProcessDocumentOpenInfo() {
LOG(("ParentProcessDocumentOpenInfo dtor [this=%p]", this));
}
void DisconnectChildListeners(nsresult aStatus, nsresult aLoadGroupStatus) {
// Tell the DocumentLoadListener to notify the content process that it's
// been entirely retargeted, and to stop waiting.
// Clear mListener's pointer to the DocumentLoadListener to break the
// reference cycle.
RefPtr<DocumentLoadListener> doc = do_GetInterface(ToSupports(mListener));
MOZ_ASSERT(doc);
doc->DisconnectListeners(aStatus, aLoadGroupStatus);
mListener->SetListenerAfterRedirect(nullptr);
}
RefPtr<mozilla::dom::BrowsingContext> mBrowsingContext;
RefPtr<ParentChannelListener> mListener;
nsCString mTypeHint;
const bool mIsDocumentLoad;
/**
* Set to true if we got cloned to create a chained listener.
*/
bool mCloned = false;
};
NS_IMPL_ADDREF_INHERITED(ParentProcessDocumentOpenInfo, nsDocumentOpenInfo)
NS_IMPL_RELEASE_INHERITED(ParentProcessDocumentOpenInfo, nsDocumentOpenInfo)
NS_INTERFACE_MAP_BEGIN(ParentProcessDocumentOpenInfo)
NS_INTERFACE_MAP_ENTRY(nsIMultiPartChannelListener)
NS_INTERFACE_MAP_END_INHERITING(nsDocumentOpenInfo)
NS_IMPL_ADDREF(DocumentLoadListener)
NS_IMPL_RELEASE(DocumentLoadListener)
NS_INTERFACE_MAP_BEGIN(DocumentLoadListener)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInterfaceRequestor)
NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
NS_INTERFACE_MAP_ENTRY(nsIParentChannel)
NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectReadyCallback)
NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
NS_INTERFACE_MAP_ENTRY(nsIMultiPartChannelListener)
NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
NS_INTERFACE_MAP_ENTRY(nsIEarlyHintObserver)
NS_INTERFACE_MAP_ENTRY_CONCRETE(DocumentLoadListener)
NS_INTERFACE_MAP_END
DocumentLoadListener::DocumentLoadListener(
CanonicalBrowsingContext* aLoadingBrowsingContext, bool aIsDocumentLoad)
: mIsDocumentLoad(aIsDocumentLoad) {
LOG(("DocumentLoadListener ctor [this=%p]", this));
mParentChannelListener =
new ParentChannelListener(this, aLoadingBrowsingContext);
}
DocumentLoadListener::~DocumentLoadListener() {
LOG(("DocumentLoadListener dtor [this=%p]", this));
}
void DocumentLoadListener::AddURIVisit(nsIChannel* aChannel,
uint32_t aLoadFlags) {
if (mLoadStateLoadType == LOAD_ERROR_PAGE ||
mLoadStateLoadType == LOAD_BYPASS_HISTORY) {
return;
}
nsCOMPtr<nsIURI> uri;
NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
nsCOMPtr<nsIURI> previousURI;
uint32_t previousFlags = 0;
if (mLoadStateLoadType & nsIDocShell::LOAD_CMD_RELOAD) {
previousURI = uri;
} else {
nsDocShell::ExtractLastVisit(aChannel, getter_AddRefs(previousURI),
&previousFlags);
}
// Get the HTTP response code, if available.
uint32_t responseStatus = 0;
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
if (httpChannel) {
Unused << httpChannel->GetResponseStatus(&responseStatus);
}
RefPtr<CanonicalBrowsingContext> browsingContext =
GetDocumentBrowsingContext();
nsCOMPtr<nsIWidget> widget =
browsingContext->GetParentProcessWidgetContaining();
nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
// Check if the URI has a http scheme and if either HSTS is enabled for this
// host, or if we were upgraded by HTTPS-Only/First. If this is the case, we
// want to mark this URI specially, as it will be followed shortly by an
// almost identical https history entry. That way the global history
// implementation can handle the visit appropriately (e.g. by marking it as
// `hidden`, so only the https url appears in default history views).
bool wasUpgraded =
uri->SchemeIs("http") &&
(loadInfo->GetHstsStatus() ||
(loadInfo->GetHttpsOnlyStatus() &
(nsILoadInfo::HTTPS_ONLY_UPGRADED_HTTPS_FIRST |
nsILoadInfo::HTTPS_ONLY_UPGRADED_LISTENER_NOT_REGISTERED |
nsILoadInfo::HTTPS_ONLY_UPGRADED_LISTENER_REGISTERED)));
nsDocShell::InternalAddURIVisit(uri, previousURI, previousFlags,
responseStatus, browsingContext, widget,
mLoadStateLoadType, wasUpgraded);
}
CanonicalBrowsingContext* DocumentLoadListener::GetLoadingBrowsingContext()
const {
return mParentChannelListener ? mParentChannelListener->GetBrowsingContext()
: nullptr;
}
CanonicalBrowsingContext* DocumentLoadListener::GetDocumentBrowsingContext()
const {
return mIsDocumentLoad ? GetLoadingBrowsingContext() : nullptr;
}
CanonicalBrowsingContext* DocumentLoadListener::GetTopBrowsingContext() const {
auto* loadingContext = GetLoadingBrowsingContext();
return loadingContext ? loadingContext->Top() : nullptr;
}
WindowGlobalParent* DocumentLoadListener::GetParentWindowContext() const {
return mParentWindowContext;
}
bool CheckRecursiveLoad(CanonicalBrowsingContext* aLoadingContext,
nsDocShellLoadState* aLoadState, bool aIsDocumentLoad) {
if (!aLoadState->ShouldCheckForRecursion()) {
return true;
}
// Bug 136580: Check for recursive frame loading excluding about:srcdoc URIs.
// srcdoc URIs require their contents to be specified inline, so it isn't
// possible for undesirable recursion to occur without the aid of a
// non-srcdoc URI, which this method will block normally.
// Besides, URI is not enough to guarantee uniqueness of srcdoc documents.
nsAutoCString buffer;
if (aLoadState->URI()->SchemeIs("about")) {
nsresult rv = aLoadState->URI()->GetPathQueryRef(buffer);
if (NS_SUCCEEDED(rv) && buffer.EqualsLiteral("srcdoc")) {
// Duplicates allowed up to depth limits
return true;
}
}
RefPtr<WindowGlobalParent> parent;
if (!aIsDocumentLoad) { // object load
parent = aLoadingContext->GetCurrentWindowGlobal();
} else {
parent = aLoadingContext->GetParentWindowContext();
}
int matchCount = 0;
CanonicalBrowsingContext* ancestorBC;
for (WindowGlobalParent* ancestorWGP = parent; ancestorWGP;
ancestorWGP = ancestorBC->GetParentWindowContext()) {
ancestorBC = ancestorWGP->BrowsingContext();
MOZ_ASSERT(ancestorBC);
if (nsCOMPtr<nsIURI> parentURI = ancestorWGP->GetDocumentURI()) {
bool equal;
nsresult rv = aLoadState->URI()->EqualsExceptRef(parentURI, &equal);
NS_ENSURE_SUCCESS(rv, false);
if (equal) {
matchCount++;
if (matchCount >= kMaxSameURLContentFrames) {
NS_WARNING(
"Too many nested content frames/objects have the same url "
"(recursion?) "
"so giving up");
return false;
}
}
}
}
return true;
}
// Check that the load state, potentially received from a child process, appears
// to be performing a load of the specified LoadingSessionHistoryInfo.
// Returns a Result<…> containing the SessionHistoryEntry found for the
// LoadingSessionHistoryInfo as success value if the validation succeeded, or a
// static (telemetry-safe) string naming what did not match as a failure value
// if the validation failed.
static Result<SessionHistoryEntry*, const char*> ValidateHistoryLoad(
CanonicalBrowsingContext* aLoadingContext,
nsDocShellLoadState* aLoadState) {
MOZ_ASSERT(SessionHistoryInParent());
MOZ_ASSERT(aLoadState->LoadIsFromSessionHistory());
if (!aLoadState->GetLoadingSessionHistoryInfo()) {
return Err("Missing LoadingSessionHistoryInfo");
}
SessionHistoryEntry::LoadingEntry* loading = SessionHistoryEntry::GetByLoadId(
aLoadState->GetLoadingSessionHistoryInfo()->mLoadId);
if (!loading) {
return Err("Missing SessionHistoryEntry");
}
SessionHistoryInfo* snapshot = loading->mInfoSnapshotForValidation.get();
// History loads do not inherit principal.
if (aLoadState->HasInternalLoadFlags(
nsDocShell::INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL)) {
return Err("LOAD_FLAGS_INHERIT_PRINCIPAL");
}
auto uriEq = [](nsIURI* a, nsIURI* b) -> bool {
bool eq = false;
return a == b || (a && b && NS_SUCCEEDED(a->Equals(b, &eq)) && eq);
};
auto principalEq = [](nsIPrincipal* a, nsIPrincipal* b) -> bool {
return a == b || (a && b && a->Equals(b));
};
// XXX: Needing to do all of this validation manually is kinda gross.
if (!uriEq(snapshot->GetURI(), aLoadState->URI())) {
return Err("URI");
}
if (!uriEq(snapshot->GetOriginalURI(), aLoadState->OriginalURI())) {
return Err("OriginalURI");
}
if (!aLoadState->ResultPrincipalURIIsSome() ||
!uriEq(snapshot->GetResultPrincipalURI(),
aLoadState->ResultPrincipalURI())) {
return Err("ResultPrincipalURI");
}
if (!uriEq(snapshot->GetUnstrippedURI(), aLoadState->GetUnstrippedURI())) {
return Err("UnstrippedURI");
}
if (!principalEq(snapshot->GetTriggeringPrincipal(),
aLoadState->TriggeringPrincipal())) {
return Err("TriggeringPrincipal");
}
if (!principalEq(snapshot->GetPrincipalToInherit(),
aLoadState->PrincipalToInherit())) {
return Err("PrincipalToInherit");
}
if (!principalEq(snapshot->GetPartitionedPrincipalToInherit(),
aLoadState->PartitionedPrincipalToInherit())) {
return Err("PartitionedPrincipalToInherit");
}
// Everything matches!
return loading->mEntry;
}
auto DocumentLoadListener::Open(nsDocShellLoadState* aLoadState,
LoadInfo* aLoadInfo, nsLoadFlags aLoadFlags,
uint32_t aCacheKey,
const Maybe<uint64_t>& aChannelId,
const TimeStamp& aAsyncOpenTime,
nsDOMNavigationTiming* aTiming,
Maybe<ClientInfo>&& aInfo, bool aUrgentStart,
dom::ContentParent* aContentParent,
nsresult* aRv) -> RefPtr<OpenPromise> {
auto* loadingContext = GetLoadingBrowsingContext();
MOZ_DIAGNOSTIC_ASSERT_IF(loadingContext->GetParent(),
loadingContext->GetParentWindowContext());
OriginAttributes attrs;
loadingContext->GetOriginAttributes(attrs);
aLoadInfo->SetContinerFeaturePolicy(
loadingContext->GetContainerFeaturePolicy());
mLoadIdentifier = aLoadState->GetLoadIdentifier();
// See description of mFileName in nsDocShellLoadState.h
mIsDownload = !aLoadState->FileName().IsVoid();
mIsLoadingJSURI = net::SchemeIsJavascript(aLoadState->URI());
mHTTPSFirstDowngradeData = aLoadState->GetHttpsFirstDowngradeData().forget();
// Check for infinite recursive object or iframe loads
if (!CheckRecursiveLoad(loadingContext, aLoadState, mIsDocumentLoad)) {
*aRv = NS_ERROR_RECURSIVE_DOCUMENT_LOAD;
mParentChannelListener = nullptr;
return nullptr;
}
auto* documentContext = GetDocumentBrowsingContext();
// If we are using SHIP and this load is from session history, validate that
// the load matches our local copy of the loading history entry.
//
// NOTE: Keep this check in-sync with the check in
// `nsDocShellLoadState::GetEffectiveTriggeringRemoteType()`!
RefPtr<SessionHistoryEntry> existingEntry;
if (SessionHistoryInParent() && aLoadState->LoadIsFromSessionHistory() &&
aLoadState->LoadType() != LOAD_ERROR_PAGE) {
Result<SessionHistoryEntry*, const char*> result =
ValidateHistoryLoad(loadingContext, aLoadState);
if (result.isErr()) {
const char* mismatch = result.unwrapErr();
LOG(
("DocumentLoadListener::Open with invalid loading history entry "
"[this=%p, mismatch=%s]",
this, mismatch));
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
MOZ_CRASH_UNSAFE_PRINTF(
"DocumentLoadListener::Open for invalid history entry due to "
"mismatch of '%s'",
mismatch);
#endif
*aRv = NS_ERROR_DOM_SECURITY_ERR;
mParentChannelListener = nullptr;
return nullptr;
}
existingEntry = result.unwrap();
if (!existingEntry->IsInSessionHistory() &&
!documentContext->HasLoadingHistoryEntry(existingEntry)) {
SessionHistoryEntry::RemoveLoadId(
aLoadState->GetLoadingSessionHistoryInfo()->mLoadId);
LOG(
("DocumentLoadListener::Open with disconnected history entry "
"[this=%p]",
this));
*aRv = NS_BINDING_ABORTED;
mParentChannelListener = nullptr;
mChannel = nullptr;
return nullptr;
}
}
if (aLoadState->GetRemoteTypeOverride()) {
if (!mIsDocumentLoad || !NS_IsAboutBlank(aLoadState->URI()) ||
!loadingContext->IsTopContent()) {
LOG(
("DocumentLoadListener::Open with invalid remoteTypeOverride "
"[this=%p]",
this));
*aRv = NS_ERROR_DOM_SECURITY_ERR;
mParentChannelListener = nullptr;
return nullptr;
}
mRemoteTypeOverride = aLoadState->GetRemoteTypeOverride();
}
if (NS_WARN_IF(!loadingContext->IsOwnedByProcess(
GetContentProcessId(aContentParent)))) {
LOG(
("DocumentLoadListener::Open called from non-current content process "
"[this=%p, current=%" PRIu64 ", caller=%" PRIu64 "]",
this, loadingContext->OwnerProcessId(),
uint64_t(GetContentProcessId(aContentParent))));
*aRv = NS_BINDING_ABORTED;
mParentChannelListener = nullptr;
return nullptr;
}
if (mIsDocumentLoad && loadingContext->IsContent() &&
NS_WARN_IF(loadingContext->IsReplaced())) {
LOG(
("DocumentLoadListener::Open called from replaced BrowsingContext "
"[this=%p, browserid=%" PRIx64 ", bcid=%" PRIx64 "]",
this, loadingContext->BrowserId(), loadingContext->Id()));
*aRv = NS_BINDING_ABORTED;
mParentChannelListener = nullptr;
return nullptr;
}
if (!nsDocShell::CreateAndConfigureRealChannelForLoadState(
loadingContext, aLoadState, aLoadInfo, mParentChannelListener,
nullptr, attrs, aLoadFlags, aCacheKey, *aRv,
getter_AddRefs(mChannel))) {
LOG(("DocumentLoadListener::Open failed to create channel [this=%p]",
this));
mParentChannelListener = nullptr;
return nullptr;
}
if (documentContext && aLoadState->LoadType() != LOAD_ERROR_PAGE &&
mozilla::SessionHistoryInParent()) {
// It's hard to know at this point whether session history will be enabled
// in the browsing context, so we always create an entry for a load here.
mLoadingSessionHistoryInfo =
documentContext->CreateLoadingSessionHistoryEntryForLoad(
aLoadState, existingEntry, mChannel);
MOZ_ASSERT(mLoadingSessionHistoryInfo);
}
nsCOMPtr<nsIURI> uriBeingLoaded;
Unused << NS_WARN_IF(
NS_FAILED(mChannel->GetURI(getter_AddRefs(uriBeingLoaded))));
RefPtr<HttpBaseChannel> httpBaseChannel = do_QueryObject(mChannel, aRv);
if (uriBeingLoaded && httpBaseChannel) {
nsCOMPtr<nsIURI> topWindowURI;
if (mIsDocumentLoad && loadingContext->IsTop()) {
// If this is for the top level loading, the top window URI should be the
// URI which we are loading.
topWindowURI = uriBeingLoaded;
} else if (RefPtr<WindowGlobalParent> topWindow = AntiTrackingUtils::
GetTopWindowExcludingExtensionAccessibleContentFrames(
loadingContext, uriBeingLoaded)) {
nsCOMPtr<nsIPrincipal> topWindowPrincipal =
topWindow->DocumentPrincipal();
if (topWindowPrincipal && !topWindowPrincipal->GetIsNullPrincipal()) {
auto* basePrin = BasePrincipal::Cast(topWindowPrincipal);
basePrin->GetURI(getter_AddRefs(topWindowURI));
}
}
httpBaseChannel->SetTopWindowURI(topWindowURI);
}
nsCOMPtr<nsIIdentChannel> identChannel = do_QueryInterface(mChannel);
if (identChannel && aChannelId) {
Unused << identChannel->SetChannelId(*aChannelId);
}
mDocumentChannelId = aChannelId;
RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel);
if (httpChannelImpl) {
httpChannelImpl->SetWarningReporter(this);
if (mIsDocumentLoad && loadingContext->IsTop()) {
httpChannelImpl->SetEarlyHintObserver(this);
}
}
nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(mChannel);
if (timedChannel) {
timedChannel->SetAsyncOpen(aAsyncOpenTime);
}
if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel)) {
Unused << httpChannel->SetRequestContextID(
loadingContext->GetRequestContextId());
nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(httpChannel));
if (cos && aUrgentStart) {
cos->AddClassFlags(nsIClassOfService::UrgentStart);
}
// ClientChannelHelper below needs us to have finalized the principal for
// the channel because it will request that StoragePrincipalHelper mint us a
// principal that needs to match the same principal that a later call to
// StoragePrincipalHelper will mint when determining the right origin to
// look up the ServiceWorker.
//
// Because nsHttpChannel::AsyncOpen calls UpdateAntiTrackingInfoForChannel
// which potentially flips the third party bit/flag on the partition key on
// the cookie jar which impacts the principal that will be minted, it is
// essential that UpdateAntiTrackingInfoForChannel is called before
// AddClientChannelHelperInParent below.
//
// Because the call to UpdateAntiTrackingInfoForChannel is largely
// idempotent, we currently just make the call ourselves right now. The one
// caveat is that the RFPRandomKey may be spuriously regenerated for
// top-level documents.
AntiTrackingUtils::UpdateAntiTrackingInfoForChannel(httpChannel);
}
// Setup a ClientChannelHelper to watch for redirects, and copy
// across any serviceworker related data between channels as needed.
AddClientChannelHelperInParent(mChannel, std::move(aInfo));
if (documentContext && !documentContext->StartDocumentLoad(this)) {
LOG(("DocumentLoadListener::Open failed StartDocumentLoad [this=%p]",
this));
*aRv = NS_BINDING_ABORTED;
mParentChannelListener = nullptr;
mChannel = nullptr;
return nullptr;
}
// Recalculate the openFlags, matching the logic in use in Content process.
MOZ_ASSERT(!aLoadState->GetPendingRedirectedChannel());
uint32_t openFlags = nsDocShell::ComputeURILoaderFlags(
loadingContext, aLoadState->LoadType(), mIsDocumentLoad);
RefPtr<ParentProcessDocumentOpenInfo> openInfo =
new ParentProcessDocumentOpenInfo(mParentChannelListener, openFlags,
loadingContext, aLoadState->TypeHint(),
mIsDocumentLoad);
openInfo->Prepare();
#ifdef ANDROID
RefPtr<MozPromise<bool, bool, false>> promise;
if (documentContext && aLoadState->LoadType() != LOAD_ERROR_PAGE &&
!(aLoadState->HasInternalLoadFlags(
nsDocShell::INTERNAL_LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE)) &&
!(aLoadState->LoadType() & LOAD_HISTORY)) {
nsCOMPtr<nsIWidget> widget =
documentContext->GetParentProcessWidgetContaining();
RefPtr<nsWindow> window = nsWindow::From(widget);
if (window) {
promise = window->OnLoadRequest(
aLoadState->URI(), nsIBrowserDOMWindow::OPEN_CURRENTWINDOW,
aLoadState->InternalLoadFlags(), aLoadState->TriggeringPrincipal(),
aLoadState->HasValidUserGestureActivation(),
documentContext->IsTopContent());
}
}
if (promise) {
RefPtr<DocumentLoadListener> self = this;
promise->Then(
GetCurrentSerialEventTarget(), __func__,
[=](const MozPromise<bool, bool, false>::ResolveOrRejectValue& aValue) {
if (aValue.IsResolve()) {
bool handled = aValue.ResolveValue();
if (handled) {
self->DisconnectListeners(NS_ERROR_ABORT, NS_ERROR_ABORT);
mParentChannelListener = nullptr;
} else {
nsresult rv = mChannel->AsyncOpen(openInfo);
if (NS_FAILED(rv)) {
self->DisconnectListeners(rv, rv);
mParentChannelListener = nullptr;
}
}
}
});
} else
#endif /* ANDROID */
{
*aRv = mChannel->AsyncOpen(openInfo);
if (NS_FAILED(*aRv)) {
LOG(("DocumentLoadListener::Open failed AsyncOpen [this=%p rv=%" PRIx32
"]",
this, static_cast<uint32_t>(*aRv)));
if (documentContext) {
documentContext->EndDocumentLoad(false);
}
mParentChannelListener = nullptr;
return nullptr;
}
}
// HTTPS-Only Mode fights potential timeouts caused by upgrades. Instantly
// after opening the document channel we have to kick off countermeasures.
nsHTTPSOnlyUtils::PotentiallyFireHttpRequestToShortenTimout(this);
nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
loadInfo->SetChannelCreationOriginalURI(aLoadState->URI());
mContentParent = aContentParent;
mLoadStateExternalLoadFlags = aLoadState->LoadFlags();
mLoadStateInternalLoadFlags = aLoadState->InternalLoadFlags();
mLoadStateLoadType = aLoadState->LoadType();
mTiming = aTiming;
mSrcdocData = aLoadState->SrcdocData();
mBaseURI = aLoadState->BaseURI();
mOriginalUriString = aLoadState->GetOriginalURIString();
if (documentContext) {
mParentWindowContext = documentContext->GetParentWindowContext();
} else {
mParentWindowContext =
WindowGlobalParent::GetByInnerWindowId(aLoadInfo->GetInnerWindowID());
MOZ_RELEASE_ASSERT(mParentWindowContext->GetBrowsingContext() ==
GetLoadingBrowsingContext(),
"mismatched parent window context?");
}
// For content-initiated loads, this flag is set in nsDocShell::LoadURI.
// For parent-initiated loads, we have to set it here.
// Below comment is copied from nsDocShell::LoadURI -
// If we have a system triggering principal, we can assume that this load was
// triggered by some UI in the browser chrome, such as the URL bar or
// bookmark bar. This should count as a user interaction for the current sh
// entry, so that the user may navigate back to the current entry, from the
// entry that is going to be added as part of this load.
if (!mSupportsRedirectToRealChannel && aLoadState->TriggeringPrincipal() &&
aLoadState->TriggeringPrincipal()->IsSystemPrincipal()) {
WindowContext* topWc = loadingContext->GetTopWindowContext();
if (topWc && !topWc->IsDiscarded()) {
MOZ_ALWAYS_SUCCEEDS(topWc->SetSHEntryHasUserInteraction(true));
}
}
*aRv = NS_OK;
mOpenPromise = new OpenPromise::Private(__func__);
// We make the promise use direct task dispatch in order to reduce the number
// of event loops iterations.
mOpenPromise->UseDirectTaskDispatch(__func__);