1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 #ifndef PreloaderBase_h__
6 #define PreloaderBase_h__
7 
8 #include "mozilla/Maybe.h"
9 #include "mozilla/PreloadHashKey.h"
10 #include "mozilla/WeakPtr.h"
11 #include "nsCOMPtr.h"
12 #include "nsISupports.h"
13 #include "nsITimer.h"
14 #include "nsIURI.h"
15 #include "nsIWeakReferenceUtils.h"
16 #include "nsTArray.h"
17 
18 class nsIChannel;
19 class nsINode;
20 class nsIRequest;
21 class nsIStreamListener;
22 
23 namespace mozilla {
24 
25 namespace dom {
26 
27 class Document;
28 
29 }  // namespace dom
30 
31 /**
32  * A half-abstract base class that resource loaders' respective
33  * channel-listening classes should derive from.  Provides a unified API to
34  * register the load or preload in a document scoped service, links <link
35  * rel="preload"> DOM nodes with the load progress and provides API to possibly
36  * consume the data by later, dynamically discovered consumers.
37  *
38  * This class is designed to be used only on the main thread.
39  */
40 class PreloaderBase : public SupportsWeakPtr, public nsISupports {
41  public:
42   PreloaderBase() = default;
43 
44   // Called by resource loaders to register this preload in the document's
45   // preload service to provide coalescing, and access to the preload when it
46   // should be used for an actual load.
47   void NotifyOpen(const PreloadHashKey& aKey, dom::Document* aDocument,
48                   bool aIsPreload);
49   void NotifyOpen(const PreloadHashKey& aKey, nsIChannel* aChannel,
50                   dom::Document* aDocument, bool aIsPreload);
51 
52   // Called when the load is about to be started all over again and thus this
53   // PreloaderBase will be registered again with the same key.  This method
54   // taks care to deregister this preload prior to that.
55   // @param aNewPreloader: If there is a new request and loader created for the
56   // restarted load, we will pass any necessary information into it.
57   void NotifyRestart(dom::Document* aDocument,
58                      PreloaderBase* aNewPreloader = nullptr);
59 
60   // Called by the loading object when the channel started to load
61   // (OnStartRequest or equal) and when it finished (OnStopRequest or equal)
62   void NotifyStart(nsIRequest* aRequest);
63   void NotifyStop(nsIRequest* aRequest, nsresult aStatus);
64   // Use this variant only in complement to NotifyOpen without providing a
65   // channel.
66   void NotifyStop(nsresult aStatus);
67 
68   // Called when this currently existing load has to be asynchronously
69   // revalidated before it can be used.  This prevents link preload DOM nodes
70   // being notified until the validation is resolved.
71   void NotifyValidating();
72   // Called when the validation process has been done.  This will notify
73   // associated link DOM nodes.
74   void NotifyValidated(nsresult aStatus);
75 
76   // Called by resource loaders or any suitable component to notify the preload
77   // has been used for an actual load.  This is intended to stop any usage
78   // timers.
79   // @param aDropLoadBackground: If `Keep` then the loading channel, if still in
80   // progress, will not be removed the LOAD_BACKGROUND flag, for instance XHR is
81   // the user here.
82   enum class LoadBackground { Keep, Drop };
83   void NotifyUsage(LoadBackground aLoadBackground = LoadBackground::Drop);
84   // Whether this preloader has been used for a regular/actual load or not.
IsUsed()85   bool IsUsed() const { return mIsUsed; }
86 
87   // Removes itself from the document's preloads hashtable
88   void RemoveSelf(dom::Document* aDocument);
89 
90   // When a loader starting an actual load finds a preload, the data can be
91   // delivered using this method.  It will deliver stream listener notifications
92   // as if it were coming from the resource loading channel.  The |request|
93   // argument will be the channel that loaded/loads the resource.
94   // This method must keep to the nsIChannel.AsyncOpen contract.  A loader is
95   // not obligated to re-implement this method when not necessarily needed.
96   virtual nsresult AsyncConsume(nsIStreamListener* aListener);
97 
98   // Accessor to the resource loading channel.
Channel()99   nsIChannel* Channel() const { return mChannel; }
100 
101   // May change priority of the resource loading channel so that it's treated as
102   // preload when this was initially representing a normal speculative load but
103   // later <link rel="preload"> was found for this resource.
104   virtual void PrioritizeAsPreload() = 0;
105 
106   // Helper function to set the LOAD_BACKGROUND flag on channel initiated by
107   // <link rel=preload>.  This MUST be used before the channel is AsyncOpen'ed.
108   static void AddLoadBackgroundFlag(nsIChannel* aChannel);
109 
110   // These are linking this preload to <link rel="preload"> DOM nodes.  If we
111   // are already loaded, immediately notify events on the node, otherwise wait
112   // for NotifyStop() call.
113   void AddLinkPreloadNode(nsINode* aNode);
114   void RemoveLinkPreloadNode(nsINode* aNode);
115 
116   // A collection of redirects, the main consumer is fetch.
117   class RedirectRecord {
118    public:
RedirectRecord(uint32_t aFlags,already_AddRefed<nsIURI> aURI)119     RedirectRecord(uint32_t aFlags, already_AddRefed<nsIURI> aURI)
120         : mFlags(aFlags), mURI(aURI) {}
121 
Flags()122     uint32_t Flags() const { return mFlags; }
123     nsCString Spec() const;
124     nsCString Fragment() const;
125 
126    private:
127     uint32_t mFlags;
128     nsCOMPtr<nsIURI> mURI;
129   };
130 
Redirects()131   const nsTArray<RedirectRecord>& Redirects() { return mRedirectRecords; }
132 
133  protected:
134   virtual ~PreloaderBase();
135 
136   // The loading channel.  This will update when a redirect occurs.
137   nsCOMPtr<nsIChannel> mChannel;
138 
139  private:
140   void NotifyNodeEvent(nsINode* aNode);
141   void CancelUsageTimer();
142 
143   void ReportUsageTelemetry();
144 
145   // A helper class that will update the PreloaderBase.mChannel member when a
146   // redirect happens, so that we can reprioritize or cancel when needed.
147   // Having a separate class instead of implementing this on PreloaderBase
148   // directly is to keep PreloaderBase as simple as possible so that derived
149   // classes don't have to deal with calling super when implementing these
150   // interfaces from some reason as well.
151   class RedirectSink;
152 
153   // A timer callback to trigger the unuse warning for this preload
154   class UsageTimer final : public nsITimerCallback, public nsINamed {
155     NS_DECL_ISUPPORTS
156     NS_DECL_NSITIMERCALLBACK
157     NS_DECL_NSINAMED
158 
159     UsageTimer(PreloaderBase* aPreload, dom::Document* aDocument);
160 
161    private:
162     ~UsageTimer() = default;
163 
164     WeakPtr<dom::Document> mDocument;
165     WeakPtr<PreloaderBase> mPreload;
166   };
167 
168  private:
169   // Reference to HTMLLinkElement DOM nodes to deliver onload and onerror
170   // notifications to.
171   nsTArray<nsWeakPtr> mNodes;
172 
173   // History of redirects.
174   nsTArray<RedirectRecord> mRedirectRecords;
175 
176   // Usage timer, emits warning when the preload is not used in time.  Started
177   // in NotifyOpen and stopped in NotifyUsage.
178   nsCOMPtr<nsITimer> mUsageTimer;
179 
180   // The key this preload has been registered under.  We want to remember it to
181   // be able to deregister itself from the document's preloads.
182   PreloadHashKey mKey;
183 
184   // This overrides the final event we send to DOM nodes to be always 'load'.
185   // Modified in NotifyStart based on LoadInfo data of the loading channel.
186   bool mShouldFireLoadEvent = false;
187 
188   // True after call to NotifyUsage.
189   bool mIsUsed = false;
190 
191   // True after we have reported the usage telemetry.  Prevent duplicates.
192   bool mUsageTelementryReported = false;
193 
194   // Emplaced when the data delivery has finished, in NotifyStop, holds the
195   // result of the load.
196   Maybe<nsresult> mOnStopStatus;
197 };
198 
199 }  // namespace mozilla
200 
201 #endif  // !PreloaderBase_h__
202