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