From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- netwerk/ipc/DocumentLoadListener.h | 630 +++++++++++++++++++++++++++++++++++++ 1 file changed, 630 insertions(+) create mode 100644 netwerk/ipc/DocumentLoadListener.h (limited to 'netwerk/ipc/DocumentLoadListener.h') diff --git a/netwerk/ipc/DocumentLoadListener.h b/netwerk/ipc/DocumentLoadListener.h new file mode 100644 index 0000000000..c2c443fef6 --- /dev/null +++ b/netwerk/ipc/DocumentLoadListener.h @@ -0,0 +1,630 @@ +/* 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/. */ + +#ifndef mozilla_net_DocumentLoadListener_h +#define mozilla_net_DocumentLoadListener_h + +#include "mozilla/MozPromise.h" +#include "mozilla/Variant.h" +#include "mozilla/WeakPtr.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/dom/SessionHistoryEntry.h" +#include "EarlyHintsService.h" +#include "mozilla/net/NeckoCommon.h" +#include "mozilla/net/NeckoParent.h" +#include "mozilla/net/PDocumentChannelParent.h" +#include "mozilla/net/ParentChannelListener.h" +#include "nsDOMNavigationTiming.h" +#include "nsIBrowser.h" +#include "nsIChannelEventSink.h" +#include "nsIEarlyHintObserver.h" +#include "nsIInterfaceRequestor.h" +#include "nsIMultiPartChannel.h" +#include "nsIParentChannel.h" +#include "nsIParentRedirectingChannel.h" +#include "nsIProgressEventSink.h" +#include "nsIRedirectResultListener.h" + +#define DOCUMENT_LOAD_LISTENER_IID \ + { \ + 0x3b393c56, 0x9e01, 0x11e9, { \ + 0xa2, 0xa3, 0x2a, 0x2a, 0xe2, 0xdb, 0xcc, 0xe4 \ + } \ + } + +namespace mozilla { +namespace dom { +class CanonicalBrowsingContext; +struct NavigationIsolationOptions; +} // namespace dom +namespace net { +using ChildEndpointPromise = + MozPromise, bool, + true>; + +// If we've been asked to attach a stream filter to our channel, +// then we return this promise and defer until we know the final +// content process. At that point we setup Endpoints between +// mStramFilterProcessId and the new content process, and send +// the parent Endpoint to the new process. +// Once we have confirmation of that being bound in the content +// process, we resolve the promise the child Endpoint. +struct StreamFilterRequest { + StreamFilterRequest() = default; + StreamFilterRequest(StreamFilterRequest&&) = default; + ~StreamFilterRequest() { + if (mPromise) { + mPromise->Reject(false, __func__); + } + } + RefPtr mPromise; + mozilla::ipc::Endpoint mChildEndpoint; +}; +} // namespace net +} // namespace mozilla +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(mozilla::net::StreamFilterRequest) + +namespace mozilla { +namespace net { + +class LoadInfo; + +/** + * DocumentLoadListener represents a connecting document load for a + * CanonicalBrowsingContext (in the parent process). + * + * It creates a network channel for the document load and then waits for it to + * receive a response (after all redirects are resolved). It then decides where + * to handle that load (could be in a different process from the initiator), + * and then sets up a real network nsIChannel to deliver the data to the final + * destination docshell, maybe through an nsIParentChannel/nsIChildChannel IPDL + * layer. + * + * In the case where this was initiated from an nsDocShell, we also create an + * nsIChannel to act as a placeholder within the docshell while this process + * completes, and then notify the docshell of a 'redirect' when we replace this + * channel with the real one. + */ + +// TODO: We currently don't implement nsIProgressEventSink and forward those +// to the child. Should we? We get the interface requested. +class DocumentLoadListener : public nsIInterfaceRequestor, + public nsIAsyncVerifyRedirectReadyCallback, + public nsIParentChannel, + public nsIChannelEventSink, + public HttpChannelSecurityWarningReporter, + public nsIMultiPartChannelListener, + public nsIProgressEventSink, + public nsIEarlyHintObserver { + public: + // See the comment on GetLoadingBrowsingContext for explanation of + // aLoadingBrowsingContext. + DocumentLoadListener(dom::CanonicalBrowsingContext* aLoadingBrowsingContext, + bool aIsDocumentLoad); + + struct OpenPromiseSucceededType { + nsTArray> + mStreamFilterEndpoints; + uint32_t mRedirectFlags; + uint32_t mLoadFlags; + uint32_t mEarlyHintLinkType; + RefPtr + mPromise; + }; + struct OpenPromiseFailedType { + nsresult mStatus; + nsresult mLoadGroupStatus; + // This is set to true if the navigation in the content process should not + // be cancelled, as the load is logically continuing within the current + // browsing session, just within a different process or browsing context. + bool mContinueNavigating = false; + }; + + using OpenPromise = + MozPromise; + + // Interface which may be provided when performing an or load + // with `DocumentLoadListener`, to allow upgrading the Object load to a proper + // Document load. + struct ObjectUpgradeHandler : public SupportsWeakPtr { + using ObjectUpgradePromise = + MozPromise, nsresult, + true /* isExclusive */>; + + // Upgrade an object load to be a potentially remote document. + // + // The returned promise will resolve with the BrowsingContext which has been + // created in the or element to finish the load with. + virtual RefPtr UpgradeObjectLoad() = 0; + }; + + private: + // Creates the channel, and then calls AsyncOpen on it. + // The DocumentLoadListener will require additional process from the consumer + // in order to complete the redirect to the end channel. This is done by + // returning a RedirectToRealChannelPromise and then waiting for it to be + // resolved or rejected accordingly. + // Once that promise is resolved; the consumer no longer needs to hold a + // reference to the DocumentLoadListener nor will the consumer required to be + // used again. + RefPtr Open(nsDocShellLoadState* aLoadState, LoadInfo* aLoadInfo, + nsLoadFlags aLoadFlags, uint32_t aCacheKey, + const Maybe& aChannelId, + const TimeStamp& aAsyncOpenTime, + nsDOMNavigationTiming* aTiming, + Maybe&& aInfo, bool aUrgentStart, + dom::ContentParent* aContentParent, nsresult* aRv); + + public: + RefPtr OpenDocument( + nsDocShellLoadState* aLoadState, uint32_t aCacheKey, + const Maybe& aChannelId, const TimeStamp& aAsyncOpenTime, + nsDOMNavigationTiming* aTiming, Maybe&& aInfo, + Maybe aUriModified, Maybe aIsXFOError, + dom::ContentParent* aContentParent, nsresult* aRv); + + RefPtr OpenObject( + nsDocShellLoadState* aLoadState, uint32_t aCacheKey, + const Maybe& aChannelId, const TimeStamp& aAsyncOpenTime, + nsDOMNavigationTiming* aTiming, Maybe&& aInfo, + uint64_t aInnerWindowId, nsLoadFlags aLoadFlags, + nsContentPolicyType aContentPolicyType, bool aUrgentStart, + dom::ContentParent* aContentParent, + ObjectUpgradeHandler* aObjectUpgradeHandler, nsresult* aRv); + + // Creates a DocumentLoadListener entirely in the parent process and opens it, + // and never needs a DocumentChannel to connect to an existing docshell. + // Once we get a response it takes the 'process switch' path to find the right + // process and docshell, and delivers the response there directly. + static bool LoadInParent(dom::CanonicalBrowsingContext* aBrowsingContext, + nsDocShellLoadState* aLoadState, + bool aSetNavigating); + + // Creates a DocumentLoadListener directly in the parent process and opens it, + // without needing an existing DocumentChannel. + // If successful it registers a unique identifier (return in aOutIdent) to + // keep it alive until a future DocumentChannel can attach to it, or we fail + // and clean up. + static bool SpeculativeLoadInParent( + dom::CanonicalBrowsingContext* aBrowsingContext, + nsDocShellLoadState* aLoadState); + + // Ensures that a load identifier allocated by OpenFromParent has + // been deregistered if it hasn't already been claimed. + // This also cancels the load. + static void CleanupParentLoadAttempt(uint64_t aLoadIdent); + + // Looks up aLoadIdent to find the associated, cleans up the registration + static RefPtr ClaimParentLoad(DocumentLoadListener** aListener, + uint64_t aLoadIdent, + Maybe aChannelId); + + // Called by the DocumentChannelParent if actor got destroyed or the parent + // channel got deleted. + void Abort(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIPARENTCHANNEL + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSIASYNCVERIFYREDIRECTREADYCALLBACK + NS_DECL_NSICHANNELEVENTSINK + NS_DECL_NSIMULTIPARTCHANNELLISTENER + NS_DECL_NSIPROGRESSEVENTSINK + NS_DECL_NSIEARLYHINTOBSERVER + + // We suspend the underlying channel when replacing ourselves with + // the real listener channel. + // This helper resumes the underlying channel again, and manually + // forwards any nsIStreamListener messages that arrived while we + // were suspended (which might have failed). + // Returns true if the channel was finished before we could resume it. + bool ResumeSuspendedChannel(nsIStreamListener* aListener); + + NS_DECLARE_STATIC_IID_ACCESSOR(DOCUMENT_LOAD_LISTENER_IID) + + // Called by the DocumentChannel if cancelled. + void Cancel(const nsresult& aStatusCode, const nsACString& aReason); + + nsIChannel* GetChannel() const { return mChannel; } + + uint32_t GetRedirectChannelId() const { return mRedirectChannelId; } + + nsresult ReportSecurityMessage(const nsAString& aMessageTag, + const nsAString& aMessageCategory) override { + ReportSecurityMessageParams params; + params.mMessageTag = aMessageTag; + params.mMessageCategory = aMessageCategory; + mSecurityWarningFunctions.AppendElement( + SecurityWarningFunction{VariantIndex<0>{}, std::move(params)}); + return NS_OK; + } + + nsresult LogBlockedCORSRequest(const nsAString& aMessage, + const nsACString& aCategory, + bool aIsWarning) override { + LogBlockedCORSRequestParams params; + params.mMessage = aMessage; + params.mCategory = aCategory; + params.mIsWarning = aIsWarning; + mSecurityWarningFunctions.AppendElement( + SecurityWarningFunction{VariantIndex<1>{}, std::move(params)}); + return NS_OK; + } + + nsresult LogMimeTypeMismatch(const nsACString& aMessageName, bool aWarning, + const nsAString& aURL, + const nsAString& aContentType) override { + LogMimeTypeMismatchParams params; + params.mMessageName = aMessageName; + params.mWarning = aWarning; + params.mURL = aURL; + params.mContentType = aContentType; + mSecurityWarningFunctions.AppendElement( + SecurityWarningFunction{VariantIndex<2>{}, std::move(params)}); + return NS_OK; + } + + // The content process corresponding to this DocumentLoadListener, or nullptr + // if connected to the parent process. + dom::ContentParent* GetContentParent() const { return mContentParent; } + + // The process id of the content process that we are being called from + // or 0 initiated from a parent process load. + base::ProcessId OtherPid() const; + + [[nodiscard]] RefPtr AttachStreamFilter(); + + // EarlyHints aren't supported on ParentProcessDocumentChannels yet, allow + // EarlyHints to be cancelled from there (Bug 1819886) + void CancelEarlyHintPreloads(); + + // Gets the EarlyHint preloads for this document to pass them to the + // ContentProcess. Registers them in the EarlyHintRegister + void RegisterEarlyHintLinksAndGetConnectArgs( + dom::ContentParentId aCpId, nsTArray& aOutLinks); + + // Serializes all data needed to setup the new replacement channel + // in the content process into the RedirectToRealChannelArgs struct. + void SerializeRedirectData(RedirectToRealChannelArgs& aArgs, + bool aIsCrossProcess, uint32_t aRedirectFlags, + uint32_t aLoadFlags, dom::ContentParent* aParent, + nsTArray&& aEarlyHints, + uint32_t aEarlyHintLinkType) const; + + uint64_t GetLoadIdentifier() const { return mLoadIdentifier; } + uint32_t GetLoadType() const { return mLoadStateLoadType; } + bool IsDownload() const { return mIsDownload; } + bool IsLoadingJSURI() const { return mIsLoadingJSURI; } + + mozilla::dom::LoadingSessionHistoryInfo* GetLoadingSessionHistoryInfo() { + return mLoadingSessionHistoryInfo.get(); + } + + bool IsDocumentLoad() const { return mIsDocumentLoad; } + + // Determine what process switching behavior a browser element should have. + enum ProcessBehavior : uint8_t { + // Gecko won't automatically change which process this frame, or it's + // subframes, are loaded in. + PROCESS_BEHAVIOR_DISABLED, + + // If `useRemoteTabs` is enabled, Gecko will change which process this frame + // is loaded in automatically, without calling `performProcessSwitch`. + // When `useRemoteSubframes` is enabled, subframes will change processes. + PROCESS_BEHAVIOR_STANDARD, + + // Gecko won't automatically change which process this frame is loaded, but + // when `useRemoteSubframes` is enabled, subframes will change processes. + // + // NOTE: This configuration is included only for backwards compatibility, + // and will be removed, as it can easily lead to invalid behavior. + PROCESS_BEHAVIOR_SUBFRAME_ONLY, + }; + + protected: + virtual ~DocumentLoadListener(); + + private: + RefPtr OpenInParent(nsDocShellLoadState* aLoadState, + bool aSupportsRedirectToRealChannel); + + friend class ParentProcessDocumentOpenInfo; + + // Will reject the promise to notify the DLL consumer that we are done. + // + // If `aContinueNavigating` is true, the navigation in the content process + // will not be aborted, as navigation is logically continuing in the existing + // browsing session (e.g. due to a process switch or entering the bfcache). + void DisconnectListeners(nsresult aStatus, nsresult aLoadGroupStatus, + bool aContinueNavigating = false); + + // Called when we were created without a document channel, and creation has + // failed, and won't ever be attached. + void NotifyDocumentChannelFailed(); + + // Initiates the switch from DocumentChannel to the real protocol-specific + // channel, and ensures that RedirectToRealChannelFinished is called when + // this is complete. + void TriggerRedirectToRealChannel( + const Maybe& aDestinationProcess, + nsTArray aStreamFilterRequests); + + // Called once the content-process side on setting up a replacement + // channel is complete. May wait for the new parent channel to + // finish, and then calls into FinishReplacementChannelSetup. + void RedirectToRealChannelFinished(nsresult aRv); + + // Completes the replacement of the new channel. + // This redirects the ParentChannelListener to forward any future + // messages to the new channel, manually forwards any being held + // by us, and resumes the underlying source channel. + void FinishReplacementChannelSetup(nsresult aResult); + + // Called from `OnStartRequest` to make the decision about whether or not to + // change process. This method will return `nullptr` if the current target + // process is appropriate. + // aWillSwitchToRemote is set to true if we initiate a process switch, + // and that the new remote type will be something other than NOT_REMOTE + bool MaybeTriggerProcessSwitch(bool* aWillSwitchToRemote); + + // Called when the process switch is going to happen, potentially + // asynchronously, from `MaybeTriggerProcessSwitch`. + // + // aContext should be the target context for the navigation. This will either + // be the loading BrowsingContext, the newly created BrowsingContext for an + // object or embed element load, or a newly created tab for new tab load. + // + // If `aIsNewTab` is specified, the navigation in the original process will be + // aborted immediately, rather than waiting for a process switch to happen and + // the previous page to be unloaded or hidden. + void TriggerProcessSwitch(dom::CanonicalBrowsingContext* aContext, + const dom::NavigationIsolationOptions& aOptions, + bool aIsNewTab = false); + + // A helper for TriggerRedirectToRealChannel that abstracts over + // the same-process and cross-process switch cases and returns + // a single promise to wait on. + using ParentEndpoint = + mozilla::ipc::Endpoint; + RefPtr + RedirectToRealChannel(uint32_t aRedirectFlags, uint32_t aLoadFlags, + const Maybe& aDestinationProcess, + nsTArray&& aStreamFilterEndpoints); + + // A helper for RedirectToRealChannel that handles the case where we started + // from a content process and are process switching into the parent process. + RefPtr + RedirectToParentProcess(uint32_t aRedirectFlags, uint32_t aLoadFlags); + + // Return the Browsing Context that is performing the load. + // For document loads, the BC is the one that the (sub)doc + // will load into. For /, it's the embedder document's BC. + dom::CanonicalBrowsingContext* GetLoadingBrowsingContext() const; + + // Return the Browsing Context that document is being loaded into. For + // non-document loads, this will return nullptr. + dom::CanonicalBrowsingContext* GetDocumentBrowsingContext() const; + dom::CanonicalBrowsingContext* GetTopBrowsingContext() const; + + // Return the Window Context which which contains the element which the load + // is being performed in. For toplevel loads, this will return `nullptr`. + dom::WindowGlobalParent* GetParentWindowContext() const; + + void AddURIVisit(nsIChannel* aChannel, uint32_t aLoadFlags); + bool HasCrossOriginOpenerPolicyMismatch() const; + void ApplyPendingFunctions(nsIParentChannel* aChannel) const; + + void Disconnect(bool aContinueNavigating); + + void MaybeReportBlockedByURLClassifier(nsresult aStatus); + + // Returns true if a channel with aStatus will display + // some sort of content (could be the actual channel data, + // attempt a uri fixup and new load, or an error page). + // Returns false if the docshell will ignore the load entirely. + bool DocShellWillDisplayContent(nsresult aStatus); + + void FireStateChange(uint32_t aStateFlags, nsresult aStatus); + + // Returns true if this is a failed load, where we have successfully + // created a fixed URI to attempt loading instead. + // If successful, this calls DisconnectListeners to completely finish + // the current load, and calls BrowsingContext::LoadURI to start the new one. + bool MaybeHandleLoadErrorWithURIFixup(nsresult aStatus); + + // This defines a variant that describes all the attribute setters (and their + // parameters) from nsIParentChannel + // + // SetClassifierMatchedInfo(const nsACString& aList, const nsACString& + // aProvider, const nsACString& aFullHash) = 0; + // SetClassifierMatchedTrackingInfo(const nsACString& aLists, const + // nsACString& aFullHashes) = 0; NotifyClassificationFlags(uint32_t + // aClassificationFlags, bool aIsThirdParty) = 0; + struct ClassifierMatchedInfoParams { + nsCString mList; + nsCString mProvider; + nsCString mFullHash; + }; + + struct ClassifierMatchedTrackingInfoParams { + nsCString mLists; + nsCString mFullHashes; + }; + + struct ClassificationFlagsParams { + uint32_t mClassificationFlags; + bool mIsThirdParty; + }; + + using IParentChannelFunction = + mozilla::Variant; + + // Store a list of all the attribute setters that have been called on this + // channel, so that we can repeat them on the real channel that we redirect + // to. + nsTArray mIParentChannelFunctions; + + // This defines a variant this describes all the functions + // from HttpChannelSecurityWarningReporter so that we can forward + // them on to the real channel. + + struct ReportSecurityMessageParams { + nsString mMessageTag; + nsString mMessageCategory; + }; + + struct LogBlockedCORSRequestParams { + nsString mMessage; + nsCString mCategory; + bool mIsWarning; + }; + + struct LogMimeTypeMismatchParams { + nsCString mMessageName; + bool mWarning = false; + nsString mURL; + nsString mContentType; + }; + + using SecurityWarningFunction = + mozilla::Variant; + nsTArray mSecurityWarningFunctions; + + // TODO Backtrack this. + // The set of nsIStreamListener functions that got called on this + // listener, so that we can replay them onto the replacement channel's + // listener. This should generally only be OnStartRequest, since we + // Suspend() the channel at that point, but it can fail sometimes + // so we have to support holding a list. + nsTArray mStreamListenerFunctions; + + nsCOMPtr mChannel; + + Maybe mDocumentChannelId; + + // An instance of ParentChannelListener that we use as a listener + // between mChannel (and any future redirected mChannels) and us. + // This handles service worker interception, and retargetting + // OnDataAvailable/OnStopRequest messages onto the listener that + // replaces us. + RefPtr mParentChannelListener; + + // Get the channel creation URI for constructing the channel in the content + // process. See the function for more details. + nsIURI* GetChannelCreationURI() const; + + // The original navigation timing information containing various timestamps + // such as when the original load started. + // This will be passed back to the new content process should a process + // switch occurs. + RefPtr mTiming; + + net::EarlyHintsService mEarlyHintsService; + + // An optional ObjectUpgradeHandler which can be used to upgrade an + // or element to contain a nsFrameLoader, allowing us to switch them + // into a different process. + // + // A weak pointer is held in order to avoid reference cycles. + WeakPtr mObjectUpgradeHandler; + + // Used to identify an internal redirect in redirect chain. + // True when we have seen at least one non-interal redirect. + bool mHaveVisibleRedirect = false; + + // Pending stream filter requests which should be attached when redirecting to + // the real channel. Moved into `TriggerRedirectToRealChannel` when the + // connection is ready. + nsTArray mStreamFilterRequests; + + nsString mSrcdocData; + nsCOMPtr mBaseURI; + + mozilla::UniquePtr + mLoadingSessionHistoryInfo; + + RefPtr mParentWindowContext; + + // Flags from nsDocShellLoadState::LoadFlags/Type that we want to make + // available to the new docshell if we switch processes. + uint32_t mLoadStateExternalLoadFlags = 0; + uint32_t mLoadStateInternalLoadFlags = 0; + uint32_t mLoadStateLoadType = 0; + + // Indicates if this load is a download. + bool mIsDownload = false; + + // Indicates if we are loading a javascript URI. + bool mIsLoadingJSURI = false; + + // Corresponding redirect channel registrar Id for the final channel that + // we want to use when redirecting the child, or doing a process switch. + // 0 means redirection is not started. + uint64_t mRedirectChannelId = 0; + // Set to true once we initiate the redirect to a real channel (either + // via a process switch or a same-process redirect, and Suspend the + // underlying channel. + bool mInitiatedRedirectToRealChannel = false; + // The value of GetApplyConversion on mChannel when OnStartRequest + // was called. We override it to false to prevent a conversion + // helper from being installed, but we need to restore the value + // later. + bool mOldApplyConversion = false; + // Set to true if any previous channel that we redirected away + // from had a COOP mismatch. + bool mHasCrossOriginOpenerPolicyMismatch = false; + // Set to true if we've received OnStopRequest, and shouldn't + // setup a reference from the ParentChannelListener to the replacement + // channel. + bool mIsFinished = false; + + // The id of the currently pending load which is + // passed to the childChannel in order to identify it in the new process. + uint64_t mLoadIdentifier = 0; + + Maybe mOriginalUriString; + + // Parent-initiated loads do not support redirects to real channels. + bool mSupportsRedirectToRealChannel = true; + + Maybe mRemoteTypeOverride; + + // The ContentParent which this channel is currently connected to, or nullptr + // if connected to the parent process. + RefPtr mContentParent; + + void RejectOpenPromise(nsresult aStatus, nsresult aLoadGroupStatus, + bool aContinueNavigating, const char* aLocation) { + // It is possible for mOpenPromise to not be set if AsyncOpen failed and + // the DocumentChannel got canceled. + if (!mOpenPromiseResolved && mOpenPromise) { + mOpenPromise->Reject(OpenPromiseFailedType({aStatus, aLoadGroupStatus, + aContinueNavigating}), + aLocation); + mOpenPromiseResolved = true; + } + } + RefPtr mOpenPromise; + bool mOpenPromiseResolved = false; + + const bool mIsDocumentLoad; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(DocumentLoadListener, DOCUMENT_LOAD_LISTENER_IID) + +inline nsISupports* ToSupports(DocumentLoadListener* aObj) { + return static_cast(aObj); +} + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_DocumentChannelParent_h -- cgit v1.2.3