uriloader/preload/PreloaderBase.h
author Mozilla Releng Treescript <release+treescript@mozilla.org>
Fri, 28 Jan 2022 19:05:43 +0000
changeset 605836 32a2d1ce4bab2979c3be01244f100f000ca77d8e
parent 591163 79474e4c8adf9bbc705659848616c7a060060de8
permissions -rw-r--r--
no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD cy -> 0eecc3c5396a7ce0ae207243a6879f559761b7a3 de -> 9220d3a92bcdc0287d9fedf94dfff7053058ccf1 fr -> 43ef41efa9e6ff87d6b4d0672d44c6d0d02f078c hsb -> 5d446c2cb85ab7fc253ec1b4fb6ed55438b0ef0f hye -> c5edf78a6f636234bb8bba68726a237d4ff9c397 it -> aa7f02187d8cecb017436efd84bf96faf8fbf63c nl -> ce81040d4aa33f478a2b20a5762f720c94b34ae6 sl -> dc3ce6db84b4e461483b2f3f25b0c627b69e446c uk -> 3deee0c202358c3bec34a0d3975ef764abf223a4

/* 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 PreloaderBase_h__
#define PreloaderBase_h__

#include "mozilla/Maybe.h"
#include "mozilla/PreloadHashKey.h"
#include "mozilla/WeakPtr.h"
#include "nsCOMPtr.h"
#include "nsISupports.h"
#include "nsITimer.h"
#include "nsIURI.h"
#include "nsIWeakReferenceUtils.h"
#include "nsTArray.h"

class nsIChannel;
class nsINode;
class nsIRequest;
class nsIStreamListener;

namespace mozilla {

namespace dom {

class Document;

}  // namespace dom

/**
 * A half-abstract base class that resource loaders' respective
 * channel-listening classes should derive from.  Provides a unified API to
 * register the load or preload in a document scoped service, links <link
 * rel="preload"> DOM nodes with the load progress and provides API to possibly
 * consume the data by later, dynamically discovered consumers.
 *
 * This class is designed to be used only on the main thread.
 */
class PreloaderBase : public SupportsWeakPtr, public nsISupports {
 public:
  PreloaderBase() = default;

  // Called by resource loaders to register this preload in the document's
  // preload service to provide coalescing, and access to the preload when it
  // should be used for an actual load.
  void NotifyOpen(const PreloadHashKey& aKey, dom::Document* aDocument,
                  bool aIsPreload);
  void NotifyOpen(const PreloadHashKey& aKey, nsIChannel* aChannel,
                  dom::Document* aDocument, bool aIsPreload);

  // Called when the load is about to be started all over again and thus this
  // PreloaderBase will be registered again with the same key.  This method
  // taks care to deregister this preload prior to that.
  // @param aNewPreloader: If there is a new request and loader created for the
  // restarted load, we will pass any necessary information into it.
  void NotifyRestart(dom::Document* aDocument,
                     PreloaderBase* aNewPreloader = nullptr);

  // Called by the loading object when the channel started to load
  // (OnStartRequest or equal) and when it finished (OnStopRequest or equal)
  void NotifyStart(nsIRequest* aRequest);
  void NotifyStop(nsIRequest* aRequest, nsresult aStatus);
  // Use this variant only in complement to NotifyOpen without providing a
  // channel.
  void NotifyStop(nsresult aStatus);

  // Called when this currently existing load has to be asynchronously
  // revalidated before it can be used.  This prevents link preload DOM nodes
  // being notified until the validation is resolved.
  void NotifyValidating();
  // Called when the validation process has been done.  This will notify
  // associated link DOM nodes.
  void NotifyValidated(nsresult aStatus);

  // Called by resource loaders or any suitable component to notify the preload
  // has been used for an actual load.  This is intended to stop any usage
  // timers.
  // @param aDropLoadBackground: If `Keep` then the loading channel, if still in
  // progress, will not be removed the LOAD_BACKGROUND flag, for instance XHR is
  // the user here.
  enum class LoadBackground { Keep, Drop };
  void NotifyUsage(LoadBackground aLoadBackground = LoadBackground::Drop);
  // Whether this preloader has been used for a regular/actual load or not.
  bool IsUsed() const { return mIsUsed; }

  // Removes itself from the document's preloads hashtable
  void RemoveSelf(dom::Document* aDocument);

  // When a loader starting an actual load finds a preload, the data can be
  // delivered using this method.  It will deliver stream listener notifications
  // as if it were coming from the resource loading channel.  The |request|
  // argument will be the channel that loaded/loads the resource.
  // This method must keep to the nsIChannel.AsyncOpen contract.  A loader is
  // not obligated to re-implement this method when not necessarily needed.
  virtual nsresult AsyncConsume(nsIStreamListener* aListener);

  // Accessor to the resource loading channel.
  nsIChannel* Channel() const { return mChannel; }

  // May change priority of the resource loading channel so that it's treated as
  // preload when this was initially representing a normal speculative load but
  // later <link rel="preload"> was found for this resource.
  virtual void PrioritizeAsPreload() = 0;

  // Helper function to set the LOAD_BACKGROUND flag on channel initiated by
  // <link rel=preload>.  This MUST be used before the channel is AsyncOpen'ed.
  static void AddLoadBackgroundFlag(nsIChannel* aChannel);

  // These are linking this preload to <link rel="preload"> DOM nodes.  If we
  // are already loaded, immediately notify events on the node, otherwise wait
  // for NotifyStop() call.
  void AddLinkPreloadNode(nsINode* aNode);
  void RemoveLinkPreloadNode(nsINode* aNode);

  // A collection of redirects, the main consumer is fetch.
  class RedirectRecord {
   public:
    RedirectRecord(uint32_t aFlags, already_AddRefed<nsIURI> aURI)
        : mFlags(aFlags), mURI(aURI) {}

    uint32_t Flags() const { return mFlags; }
    nsCString Spec() const;
    nsCString Fragment() const;

   private:
    uint32_t mFlags;
    nsCOMPtr<nsIURI> mURI;
  };

  const nsTArray<RedirectRecord>& Redirects() { return mRedirectRecords; }

 protected:
  virtual ~PreloaderBase();

  // The loading channel.  This will update when a redirect occurs.
  nsCOMPtr<nsIChannel> mChannel;

 private:
  void NotifyNodeEvent(nsINode* aNode);
  void CancelUsageTimer();

  void ReportUsageTelemetry();

  // A helper class that will update the PreloaderBase.mChannel member when a
  // redirect happens, so that we can reprioritize or cancel when needed.
  // Having a separate class instead of implementing this on PreloaderBase
  // directly is to keep PreloaderBase as simple as possible so that derived
  // classes don't have to deal with calling super when implementing these
  // interfaces from some reason as well.
  class RedirectSink;

  // A timer callback to trigger the unuse warning for this preload
  class UsageTimer final : public nsITimerCallback, public nsINamed {
    NS_DECL_ISUPPORTS
    NS_DECL_NSITIMERCALLBACK
    NS_DECL_NSINAMED

    UsageTimer(PreloaderBase* aPreload, dom::Document* aDocument);

   private:
    ~UsageTimer() = default;

    WeakPtr<dom::Document> mDocument;
    WeakPtr<PreloaderBase> mPreload;
  };

 private:
  // Reference to HTMLLinkElement DOM nodes to deliver onload and onerror
  // notifications to.
  nsTArray<nsWeakPtr> mNodes;

  // History of redirects.
  nsTArray<RedirectRecord> mRedirectRecords;

  // Usage timer, emits warning when the preload is not used in time.  Started
  // in NotifyOpen and stopped in NotifyUsage.
  nsCOMPtr<nsITimer> mUsageTimer;

  // The key this preload has been registered under.  We want to remember it to
  // be able to deregister itself from the document's preloads.
  PreloadHashKey mKey;

  // This overrides the final event we send to DOM nodes to be always 'load'.
  // Modified in NotifyStart based on LoadInfo data of the loading channel.
  bool mShouldFireLoadEvent = false;

  // True after call to NotifyUsage.
  bool mIsUsed = false;

  // True after we have reported the usage telemetry.  Prevent duplicates.
  bool mUsageTelementryReported = false;

  // Emplaced when the data delivery has finished, in NotifyStop, holds the
  // result of the load.
  Maybe<nsresult> mOnStopStatus;
};

}  // namespace mozilla

#endif  // !PreloaderBase_h__