summaryrefslogtreecommitdiffstats
path: root/netwerk/protocol/http/EarlyHintPreloader.h
blob: c60f97dbcf638fce5db7ea02e49f2156e281ec8e (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
/* 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 mozilla_net_EarlyHintPreloader_h
#define mozilla_net_EarlyHintPreloader_h

#include "mozilla/Maybe.h"
#include "mozilla/PreloadHashKey.h"
#include "NeckoCommon.h"
#include "mozilla/net/NeckoChannelParams.h"
#include "nsHashtablesFwd.h"
#include "nsIChannelEventSink.h"
#include "nsIInterfaceRequestor.h"
#include "nsIMultiPartChannel.h"
#include "nsIRedirectResultListener.h"
#include "nsIStreamListener.h"
#include "nsITimer.h"
#include "nsNetUtil.h"

class nsAttrValue;
class nsICookieJarSettings;
class nsIPrincipal;
class nsIReferrerInfo;

namespace mozilla::net {

class EarlyHintPreloader;
class EarlyHintConnectArgs;
class ParentChannelListener;
struct LinkHeader;

// class keeping track of all ongoing early hints
class OngoingEarlyHints final {
 public:
  NS_INLINE_DECL_REFCOUNTING(OngoingEarlyHints)

  OngoingEarlyHints() = default;

  // returns whether a preload with that key already existed
  bool Contains(const PreloadHashKey& aKey);
  bool Add(const PreloadHashKey& aKey, RefPtr<EarlyHintPreloader> aPreloader);

  void CancelAllOngoingPreloads(const nsACString& aReason);

  // registers all channels and returns the ids
  void RegisterLinksAndGetConnectArgs(
      nsTArray<EarlyHintConnectArgs>& aOutLinks);

 private:
  ~OngoingEarlyHints() = default;

  // We need to do two things requiring two separate variables to keep track of
  // preloads:
  //  - deduplicate Link headers when starting preloads, therefore we store them
  //    hashset with PreloadHashKey to look up whether we started the preload
  //    already
  //  - pass link headers in order they were received when passing all started
  //    preloads to the content process, therefore we store them in a nsTArray
  nsTHashSet<PreloadHashKey> mStartedPreloads;
  nsTArray<RefPtr<EarlyHintPreloader>> mPreloaders;
};

class EarlyHintPreloader final : public nsIStreamListener,
                                 public nsIChannelEventSink,
                                 public nsIRedirectResultListener,
                                 public nsIInterfaceRequestor,
                                 public nsIMultiPartChannelListener,
                                 public nsINamed,
                                 public nsITimerCallback {
 public:
  NS_DECL_ISUPPORTS
  NS_DECL_NSIREQUESTOBSERVER
  NS_DECL_NSISTREAMLISTENER
  NS_DECL_NSICHANNELEVENTSINK
  NS_DECL_NSIREDIRECTRESULTLISTENER
  NS_DECL_NSIINTERFACEREQUESTOR
  NS_DECL_NSIMULTIPARTCHANNELLISTENER
  // required by NS_DECL_NSITIMERCALLBACK
  NS_DECL_NSINAMED
  NS_DECL_NSITIMERCALLBACK

 public:
  // Create and insert a preload into OngoingEarlyHints if the same preload
  // wasn't already issued and the LinkHeader can be parsed correctly.
  static void MaybeCreateAndInsertPreload(
      OngoingEarlyHints* aOngoingEarlyHints, const LinkHeader& aHeader,
      nsIURI* aBaseURI, nsIPrincipal* aPrincipal,
      nsICookieJarSettings* aCookieJarSettings,
      const nsACString& aReferrerPolicy, const nsACString& aCSPHeader);

  // register Channel to EarlyHintRegistrar. Returns true and sets connect args
  // if successful
  bool Register(EarlyHintConnectArgs& aOut);

  // Should be called by the preloader service when the preload is not
  // needed after all, because the final response returns a non-2xx status
  // code. If aDeleteEntry is false, the calling function MUST make sure that
  // the EarlyHintPreloader is not in the EarlyHintRegistrar anymore. Because
  // after this function, the EarlyHintPreloader can't connect back to the
  // parent anymore.
  nsresult CancelChannel(nsresult aStatus, const nsACString& aReason,
                         bool aDeleteEntry);

  void OnParentReady(nsIParentChannel* aParent, uint64_t aChannelId);

 private:
  void SetParentChannel();
  void InvokeStreamListenerFunctions();

  EarlyHintPreloader();
  ~EarlyHintPreloader();

  static Maybe<PreloadHashKey> GenerateHashKey(ASDestination aAs, nsIURI* aURI,
                                               nsIPrincipal* aPrincipal,
                                               CORSMode corsMode,
                                               const nsAString& aType);

  static nsSecurityFlags ComputeSecurityFlags(CORSMode aCORSMode,
                                              ASDestination aAs,
                                              bool aIsModule);

  // call to start the preload
  nsresult OpenChannel(nsIURI* aURI, nsIPrincipal* aPrincipal,
                       nsSecurityFlags aSecurityFlags,
                       nsContentPolicyType aContentPolicyType,
                       nsIReferrerInfo* aReferrerInfo,
                       nsICookieJarSettings* aCookieJarSettings);
  void PriorizeAsPreload();
  void SetLinkHeader(const LinkHeader& aLinkHeader);

  static void CollectResourcesTypeTelemetry(ASDestination aASDestination);
  nsCOMPtr<nsIChannel> mChannel;
  nsCOMPtr<nsIChannel> mRedirectChannel;
  uint64_t mChannelId = 0;

  EarlyHintConnectArgs mConnectArgs;

  // Copy behavior from DocumentLoadListener.h:
  // https://searchfox.org/mozilla-central/rev/c0bed29d643393af6ebe77aa31455f283f169202/netwerk/ipc/DocumentLoadListener.h#487-512
  // 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;

  // Set to true once OnStartRequest is called
  bool mOnStartRequestCalled = false;
  // Set to true if we suspended mChannel in the OnStartRequest call
  bool mSuspended = false;
  nsCOMPtr<nsIParentChannel> mParent;
  // Set to true after we've received the last OnStopRequest, and shouldn't
  // setup a reference from the ParentChannelListener to the replacement
  // channel.
  bool mIsFinished = false;

  RefPtr<ParentChannelListener> mParentListener;
  nsCOMPtr<nsITimer> mTimer;

 private:
  // IMPORTANT: when adding new values, always add them to the end, otherwise
  // it will mess up telemetry.
  enum EHPreloaderState : uint32_t {
    ePreloaderCreated = 0,
    ePreloaderOpened,
    ePreloaderUsed,
    ePreloaderCancelled,
    ePreloaderTimeout,
  };
  EHPreloaderState mState = ePreloaderCreated;
  void SetState(EHPreloaderState aState) { mState = aState; }
};

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

}  // namespace mozilla::net

#endif  // mozilla_net_EarlyHintPreloader_h