summaryrefslogtreecommitdiffstats
path: root/uriloader/preload/PreloaderBase.h
blob: 185da5dbea86172909c03624ad007cc292fe42af (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
/* 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__