author Jim Blandy <>
Thu, 30 Mar 2023 16:23:26 +0000
changeset 658645 9754c55103ef774188e1e813a3ef5d4f5487b35c
parent 644532 8575cd92e1d8cd783e6cca3f6feadef11f0c4cc8
permissions -rw-r--r--
Bug 1825449: Don't warn on mismatched WebGPU error scope push/pop. r=webgpu-reviewers,teoxoy In the graphics process, don't warn on receipt of mismatched DevicePushErrorScope/DevicePopErrorScope messages, since these can be easily caused by ordinary content and do not indicate anything wrong with Firefox. Differential Revision:

/* 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 */

#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<mozilla::ipc::Endpoint<extensions::PStreamFilterChild>, bool,

// 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<ChildEndpointPromise::Private> mPromise;
  mozilla::ipc::Endpoint<extensions::PStreamFilterChild> mChildEndpoint;
}  // namespace net
}  // namespace mozilla

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 {
  // See the comment on GetLoadingBrowsingContext for explanation of
  // aLoadingBrowsingContext.
  DocumentLoadListener(dom::CanonicalBrowsingContext* aLoadingBrowsingContext,
                       bool aIsDocumentLoad);

  struct OpenPromiseSucceededType {
    uint32_t mRedirectFlags;
    uint32_t mLoadFlags;
    nsTArray<EarlyHintConnectArgs> mEarlyHints;
  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<OpenPromiseSucceededType, OpenPromiseFailedType, true>;

  // Interface which may be provided when performing an <object> or <embed> load
  // with `DocumentLoadListener`, to allow upgrading the Object load to a proper
  // Document load.
  struct ObjectUpgradeHandler : public SupportsWeakPtr {
    using ObjectUpgradePromise =
        MozPromise<RefPtr<dom::CanonicalBrowsingContext>, 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 <object> or <embed> element to finish the load with.
    virtual RefPtr<ObjectUpgradePromise> UpgradeObjectLoad() = 0;

  // 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<OpenPromise> Open(nsDocShellLoadState* aLoadState, LoadInfo* aLoadInfo,
                           nsLoadFlags aLoadFlags, uint32_t aCacheKey,
                           const Maybe<uint64_t>& aChannelId,
                           const TimeStamp& aAsyncOpenTime,
                           nsDOMNavigationTiming* aTiming,
                           Maybe<dom::ClientInfo>&& aInfo, bool aUrgentStart,
                           dom::ContentParent* aContentParent, nsresult* aRv);

  RefPtr<OpenPromise> OpenDocument(
      nsDocShellLoadState* aLoadState, uint32_t aCacheKey,
      const Maybe<uint64_t>& aChannelId, const TimeStamp& aAsyncOpenTime,
      nsDOMNavigationTiming* aTiming, Maybe<dom::ClientInfo>&& aInfo,
      Maybe<bool> aUriModified, Maybe<bool> aIsXFOError,
      dom::ContentParent* aContentParent, nsresult* aRv);

  RefPtr<OpenPromise> OpenObject(
      nsDocShellLoadState* aLoadState, uint32_t aCacheKey,
      const Maybe<uint64_t>& aChannelId, const TimeStamp& aAsyncOpenTime,
      nsDOMNavigationTiming* aTiming, Maybe<dom::ClientInfo>&& 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<OpenPromise> ClaimParentLoad(DocumentLoadListener** aListener,
                                             uint64_t aLoadIdent,
                                             Maybe<uint64_t> aChannelId);

  // Called by the DocumentChannelParent if actor got destroyed or the parent
  // channel got deleted.
  void Abort();


  // 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);


  // 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;
        SecurityWarningFunction{VariantIndex<0>{}, std::move(params)});
    return NS_OK;

  nsresult LogBlockedCORSRequest(const nsAString& aMessage,
                                 const nsACString& aCategory) override {
    LogBlockedCORSRequestParams params;
    params.mMessage = aMessage;
    params.mCategory = aCategory;
        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;
        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<ChildEndpointPromise> AttachStreamFilter();

  // 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<EarlyHintConnectArgs>&& aEarlyHints) 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.

    // 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.

    // 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.

  virtual ~DocumentLoadListener();

  RefPtr<OpenPromise> 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<dom::ContentParent*>& aDestinationProcess,
      nsTArray<StreamFilterRequest> 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 =
  RedirectToRealChannel(uint32_t aRedirectFlags, uint32_t aLoadFlags,
                        const Maybe<dom::ContentParent*>& aDestinationProcess,
                        nsTArray<ParentEndpoint>&& aStreamFilterEndpoints);

  // A helper for RedirectToRealChannel that handles the case where we started
  // from a content process and are process switching into the parent process.
  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 <object>/<embed>, 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 =

  // 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<IParentChannelFunction> 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;

  struct LogMimeTypeMismatchParams {
    nsCString mMessageName;
    bool mWarning = false;
    nsString mURL;
    nsString mContentType;

  using SecurityWarningFunction =
      mozilla::Variant<ReportSecurityMessageParams, LogBlockedCORSRequestParams,
  nsTArray<SecurityWarningFunction> 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<StreamListenerFunction> mStreamListenerFunctions;

  nsCOMPtr<nsIChannel> mChannel;

  Maybe<uint64_t> 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<ParentChannelListener> 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<nsDOMNavigationTiming> mTiming;

  net::EarlyHintsService mEarlyHintsService;

  // An optional ObjectUpgradeHandler which can be used to upgrade an <object>
  // or <embed> 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<ObjectUpgradeHandler> 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<StreamFilterRequest> mStreamFilterRequests;

  nsString mSrcdocData;
  nsCOMPtr<nsIURI> mBaseURI;


  RefPtr<dom::WindowGlobalParent> 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<nsCString> mOriginalUriString;

  // Parent-initiated loads do not support redirects to real channels.
  bool mSupportsRedirectToRealChannel = true;

  Maybe<nsCString> mRemoteTypeOverride;

  // The ContentParent which this channel is currently connected to, or nullptr
  // if connected to the parent process.
  RefPtr<dom::ContentParent> 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,
      mOpenPromiseResolved = true;
  RefPtr<OpenPromise::Private> mOpenPromise;
  bool mOpenPromiseResolved = false;

  const bool mIsDocumentLoad;


inline nsISupports* ToSupports(DocumentLoadListener* aObj) {
  return static_cast<nsIInterfaceRequestor*>(aObj);

}  // namespace net
}  // namespace mozilla

#endif  // mozilla_net_DocumentChannelParent_h