no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / uriloader / preload / PreloaderBase.cpp
blob2c3d4fb60bee0e2c840b2a013926bf87ec1c8f73
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/. */
5 #include "PreloaderBase.h"
7 #include "mozilla/dom/Document.h"
8 #include "mozilla/Telemetry.h"
9 #include "nsContentUtils.h"
10 #include "nsIAsyncVerifyRedirectCallback.h"
11 #include "nsIHttpChannel.h"
12 #include "nsILoadGroup.h"
13 #include "nsIInterfaceRequestorUtils.h"
14 #include "nsIRedirectResultListener.h"
15 #include "nsNetUtil.h"
17 // Change this if we want to cancel and remove the associated preload on removal
18 // of all <link rel=preload> tags from the tree.
19 constexpr static bool kCancelAndRemovePreloadOnZeroReferences = false;
21 namespace mozilla {
23 PreloaderBase::UsageTimer::UsageTimer(PreloaderBase* aPreload,
24 dom::Document* aDocument)
25 : mDocument(aDocument), mPreload(aPreload) {}
27 class PreloaderBase::RedirectSink final : public nsIInterfaceRequestor,
28 public nsIChannelEventSink,
29 public nsIRedirectResultListener {
30 RedirectSink() = delete;
31 virtual ~RedirectSink();
33 public:
34 NS_DECL_THREADSAFE_ISUPPORTS
35 NS_DECL_NSIINTERFACEREQUESTOR
36 NS_DECL_NSICHANNELEVENTSINK
37 NS_DECL_NSIREDIRECTRESULTLISTENER
39 RedirectSink(PreloaderBase* aPreloader, nsIInterfaceRequestor* aCallbacks);
41 private:
42 MainThreadWeakPtr<PreloaderBase> mPreloader;
43 nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
44 nsCOMPtr<nsIChannel> mRedirectChannel;
47 PreloaderBase::RedirectSink::RedirectSink(PreloaderBase* aPreloader,
48 nsIInterfaceRequestor* aCallbacks)
49 : mPreloader(aPreloader), mCallbacks(aCallbacks) {}
51 PreloaderBase::RedirectSink::~RedirectSink() = default;
53 NS_IMPL_ISUPPORTS(PreloaderBase::RedirectSink, nsIInterfaceRequestor,
54 nsIChannelEventSink, nsIRedirectResultListener)
56 NS_IMETHODIMP PreloaderBase::RedirectSink::AsyncOnChannelRedirect(
57 nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
58 nsIAsyncVerifyRedirectCallback* aCallback) {
59 MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
61 mRedirectChannel = aNewChannel;
63 // Deliberately adding this before confirmation.
64 nsCOMPtr<nsIURI> uri;
65 aNewChannel->GetOriginalURI(getter_AddRefs(uri));
66 if (mPreloader) {
67 mPreloader->mRedirectRecords.AppendElement(
68 RedirectRecord(aFlags, uri.forget()));
71 if (mCallbacks) {
72 nsCOMPtr<nsIChannelEventSink> sink(do_GetInterface(mCallbacks));
73 if (sink) {
74 return sink->AsyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags,
75 aCallback);
79 aCallback->OnRedirectVerifyCallback(NS_OK);
80 return NS_OK;
83 NS_IMETHODIMP PreloaderBase::RedirectSink::OnRedirectResult(nsresult status) {
84 if (NS_SUCCEEDED(status) && mRedirectChannel) {
85 mPreloader->mChannel = std::move(mRedirectChannel);
86 } else {
87 mRedirectChannel = nullptr;
90 if (mCallbacks) {
91 nsCOMPtr<nsIRedirectResultListener> sink(do_GetInterface(mCallbacks));
92 if (sink) {
93 return sink->OnRedirectResult(status);
97 return NS_OK;
100 NS_IMETHODIMP PreloaderBase::RedirectSink::GetInterface(const nsIID& aIID,
101 void** aResult) {
102 NS_ENSURE_ARG_POINTER(aResult);
104 if (aIID.Equals(NS_GET_IID(nsIChannelEventSink)) ||
105 aIID.Equals(NS_GET_IID(nsIRedirectResultListener))) {
106 return QueryInterface(aIID, aResult);
109 if (mCallbacks) {
110 return mCallbacks->GetInterface(aIID, aResult);
113 *aResult = nullptr;
114 return NS_ERROR_NO_INTERFACE;
117 PreloaderBase::~PreloaderBase() { MOZ_ASSERT(NS_IsMainThread()); }
119 // static
120 void PreloaderBase::AddLoadBackgroundFlag(nsIChannel* aChannel) {
121 nsLoadFlags loadFlags;
122 aChannel->GetLoadFlags(&loadFlags);
123 aChannel->SetLoadFlags(loadFlags | nsIRequest::LOAD_BACKGROUND);
126 void PreloaderBase::NotifyOpen(const PreloadHashKey& aKey,
127 dom::Document* aDocument, bool aIsPreload,
128 bool aIsModule) {
129 if (aDocument) {
130 DebugOnly<bool> alreadyRegistered =
131 aDocument->Preloads().RegisterPreload(aKey, this);
132 // This means there is already a preload registered under this key in this
133 // document. We only allow replacement when this is a regular load.
134 // Otherwise, this should never happen and is a suspected misuse of the API.
135 MOZ_ASSERT_IF(alreadyRegistered, !aIsPreload);
138 mKey = aKey;
139 mIsUsed = !aIsPreload;
141 // Start usage timer for rel="preload", but not for rel="modulepreload"
142 // because modules may be loaded for functionality the user does not
143 // immediately interact with after page load (e.g. a docs search box)
144 if (!aIsModule && !mIsUsed && !mUsageTimer) {
145 auto callback = MakeRefPtr<UsageTimer>(this, aDocument);
146 NS_NewTimerWithCallback(getter_AddRefs(mUsageTimer), callback, 10000,
147 nsITimer::TYPE_ONE_SHOT);
150 ReportUsageTelemetry();
153 void PreloaderBase::NotifyOpen(const PreloadHashKey& aKey, nsIChannel* aChannel,
154 dom::Document* aDocument, bool aIsPreload,
155 bool aIsModule) {
156 NotifyOpen(aKey, aDocument, aIsPreload, aIsModule);
157 mChannel = aChannel;
159 nsCOMPtr<nsIInterfaceRequestor> callbacks;
160 mChannel->GetNotificationCallbacks(getter_AddRefs(callbacks));
161 RefPtr<RedirectSink> sink(new RedirectSink(this, callbacks));
162 mChannel->SetNotificationCallbacks(sink);
165 void PreloaderBase::NotifyUsage(dom::Document* aDocument,
166 LoadBackground aLoadBackground) {
167 if (!mIsUsed && mChannel && aLoadBackground == LoadBackground::Drop) {
168 nsLoadFlags loadFlags;
169 mChannel->GetLoadFlags(&loadFlags);
171 // Preloads are initially set the LOAD_BACKGROUND flag. When becoming
172 // regular loads by hitting its consuming tag, we need to drop that flag,
173 // which also means to re-add the request from/to it's loadgroup to reflect
174 // that flag change.
175 if (loadFlags & nsIRequest::LOAD_BACKGROUND) {
176 nsCOMPtr<nsILoadGroup> loadGroup;
177 mChannel->GetLoadGroup(getter_AddRefs(loadGroup));
179 if (loadGroup) {
180 nsresult status;
181 mChannel->GetStatus(&status);
183 nsresult rv = loadGroup->RemoveRequest(mChannel, nullptr, status);
184 mChannel->SetLoadFlags(loadFlags & ~nsIRequest::LOAD_BACKGROUND);
185 if (NS_SUCCEEDED(rv)) {
186 loadGroup->AddRequest(mChannel, nullptr);
192 mIsUsed = true;
193 ReportUsageTelemetry();
194 CancelUsageTimer();
195 if (mIsEarlyHintsPreload) {
196 aDocument->Preloads().SetEarlyHintUsed();
200 void PreloaderBase::RemoveSelf(dom::Document* aDocument) {
201 if (aDocument) {
202 aDocument->Preloads().DeregisterPreload(mKey);
206 void PreloaderBase::NotifyRestart(dom::Document* aDocument,
207 PreloaderBase* aNewPreloader) {
208 RemoveSelf(aDocument);
209 mKey = PreloadHashKey();
211 CancelUsageTimer();
213 if (aNewPreloader) {
214 aNewPreloader->mNodes = std::move(mNodes);
218 void PreloaderBase::NotifyStart(nsIRequest* aRequest) {
219 // If there is no channel assigned on this preloader, we are not between
220 // channel switching, so we can freely update the mShouldFireLoadEvent using
221 // the given channel.
222 if (mChannel && !SameCOMIdentity(aRequest, mChannel)) {
223 return;
226 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
227 if (!httpChannel) {
228 return;
231 // if the load is cross origin without CORS, or the CORS access is rejected,
232 // always fire load event to avoid leaking site information.
233 nsresult rv;
234 nsCOMPtr<nsILoadInfo> loadInfo = httpChannel->LoadInfo();
235 mShouldFireLoadEvent =
236 loadInfo->GetTainting() == LoadTainting::Opaque ||
237 (loadInfo->GetTainting() == LoadTainting::CORS &&
238 (NS_FAILED(httpChannel->GetStatus(&rv)) || NS_FAILED(rv)));
241 void PreloaderBase::NotifyStop(nsIRequest* aRequest, nsresult aStatus) {
242 // Filter out notifications that may be arriving from the old channel before
243 // restarting this request.
244 if (!SameCOMIdentity(aRequest, mChannel)) {
245 return;
248 NotifyStop(aStatus);
251 void PreloaderBase::NotifyStop(nsresult aStatus) {
252 mOnStopStatus.emplace(aStatus);
254 nsTArray<nsWeakPtr> nodes = std::move(mNodes);
256 for (nsWeakPtr& weak : nodes) {
257 nsCOMPtr<nsINode> node = do_QueryReferent(weak);
258 if (node) {
259 NotifyNodeEvent(node);
263 mChannel = nullptr;
266 void PreloaderBase::AddLinkPreloadNode(nsINode* aNode) {
267 if (mOnStopStatus) {
268 return NotifyNodeEvent(aNode);
271 mNodes.AppendElement(do_GetWeakReference(aNode));
274 void PreloaderBase::RemoveLinkPreloadNode(nsINode* aNode) {
275 // Note that do_GetWeakReference returns the internal weak proxy, which is
276 // always the same, so we can use it to search the array using default
277 // comparator.
278 nsWeakPtr node = do_GetWeakReference(aNode);
279 mNodes.RemoveElement(node);
281 if (kCancelAndRemovePreloadOnZeroReferences && mNodes.Length() == 0 &&
282 !mIsUsed) {
283 // Keep a reference, because the following call may release us. The caller
284 // may use a WeakPtr to access this.
285 RefPtr<PreloaderBase> self(this);
286 RemoveSelf(aNode->OwnerDoc());
288 if (mChannel) {
289 mChannel->CancelWithReason(NS_BINDING_ABORTED,
290 "PreloaderBase::RemoveLinkPreloadNode"_ns);
295 void PreloaderBase::NotifyNodeEvent(nsINode* aNode) {
296 PreloadService::NotifyNodeEvent(
297 aNode, mShouldFireLoadEvent || NS_SUCCEEDED(*mOnStopStatus));
300 void PreloaderBase::CancelUsageTimer() {
301 if (mUsageTimer) {
302 mUsageTimer->Cancel();
303 mUsageTimer = nullptr;
307 void PreloaderBase::ReportUsageTelemetry() {
308 if (mUsageTelementryReported) {
309 return;
311 mUsageTelementryReported = true;
313 if (mKey.As() == PreloadHashKey::ResourceType::NONE) {
314 return;
317 // The labels are structured as type1-used, type1-unused, type2-used, ...
318 // The first "as" resource type is NONE with value 0.
319 auto index = (static_cast<uint32_t>(mKey.As()) - 1) * 2;
320 if (!mIsUsed) {
321 ++index;
324 auto label = static_cast<Telemetry::LABELS_REL_PRELOAD_MISS_RATIO>(index);
325 Telemetry::AccumulateCategorical(label);
328 nsresult PreloaderBase::AsyncConsume(nsIStreamListener* aListener) {
329 // We want to return an error so that consumers can't ever use a preload to
330 // consume data unless it's properly implemented.
331 return NS_ERROR_NOT_IMPLEMENTED;
334 // PreloaderBase::RedirectRecord
336 nsCString PreloaderBase::RedirectRecord::Spec() const {
337 nsCOMPtr<nsIURI> noFragment;
338 NS_GetURIWithoutRef(mURI, getter_AddRefs(noFragment));
339 MOZ_ASSERT(noFragment);
340 return noFragment->GetSpecOrDefault();
343 nsCString PreloaderBase::RedirectRecord::Fragment() const {
344 nsCString fragment;
345 mURI->GetRef(fragment);
346 return fragment;
349 // PreloaderBase::UsageTimer
351 NS_IMPL_ISUPPORTS(PreloaderBase::UsageTimer, nsITimerCallback, nsINamed)
353 NS_IMETHODIMP PreloaderBase::UsageTimer::Notify(nsITimer* aTimer) {
354 if (!mPreload || !mDocument) {
355 return NS_OK;
358 MOZ_ASSERT(aTimer == mPreload->mUsageTimer);
359 mPreload->mUsageTimer = nullptr;
361 if (mPreload->IsUsed()) {
362 // Left in the hashtable, but marked as used. This is a valid case, and we
363 // don't want to emit a warning for this preload then.
364 return NS_OK;
367 mPreload->ReportUsageTelemetry();
369 // PreloadHashKey overrides GetKey, we need to use the nsURIHashKey one to get
370 // the URI.
371 nsIURI* uri = static_cast<nsURIHashKey*>(&mPreload->mKey)->GetKey();
372 if (!uri) {
373 return NS_OK;
376 nsString spec;
377 NS_GetSanitizedURIStringFromURI(uri, spec);
378 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
379 mDocument, nsContentUtils::eDOM_PROPERTIES,
380 "UnusedLinkPreloadPending",
381 nsTArray<nsString>({std::move(spec)}));
382 return NS_OK;
385 NS_IMETHODIMP
386 PreloaderBase::UsageTimer::GetName(nsACString& aName) {
387 aName.AssignLiteral("PreloaderBase::UsageTimer");
388 return NS_OK;
391 } // namespace mozilla