1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set sw=2 ts=4 sts=2 et cin: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "mozilla/DebugOnly.h"
8 
9 #include "nsLoadGroup.h"
10 
11 #include "nsArrayEnumerator.h"
12 #include "nsCOMArray.h"
13 #include "nsCOMPtr.h"
14 #include "nsContentUtils.h"
15 #include "mozilla/Logging.h"
16 #include "nsString.h"
17 #include "nsTArray.h"
18 #include "mozilla/Telemetry.h"
19 #include "nsIHttpChannelInternal.h"
20 #include "nsITimedChannel.h"
21 #include "nsIInterfaceRequestor.h"
22 #include "nsIRequestObserver.h"
23 #include "CacheObserver.h"
24 #include "MainThreadUtils.h"
25 #include "RequestContextService.h"
26 #include "mozilla/StoragePrincipalHelper.h"
27 #include "mozilla/Unused.h"
28 
29 namespace mozilla {
30 namespace net {
31 
32 //
33 // Log module for nsILoadGroup logging...
34 //
35 // To enable logging (see prlog.h for full details):
36 //
37 //    set MOZ_LOG=LoadGroup:5
38 //    set MOZ_LOG_FILE=network.log
39 //
40 // This enables LogLevel::Debug level information and places all output in
41 // the file network.log.
42 //
43 static LazyLogModule gLoadGroupLog("LoadGroup");
44 #undef LOG
45 #define LOG(args) MOZ_LOG(gLoadGroupLog, mozilla::LogLevel::Debug, args)
46 
47 ////////////////////////////////////////////////////////////////////////////////
48 
49 class RequestMapEntry : public PLDHashEntryHdr {
50  public:
RequestMapEntry(nsIRequest * aRequest)51   explicit RequestMapEntry(nsIRequest* aRequest) : mKey(aRequest) {}
52 
53   nsCOMPtr<nsIRequest> mKey;
54 };
55 
RequestHashMatchEntry(const PLDHashEntryHdr * entry,const void * key)56 static bool RequestHashMatchEntry(const PLDHashEntryHdr* entry,
57                                   const void* key) {
58   const RequestMapEntry* e = static_cast<const RequestMapEntry*>(entry);
59   const nsIRequest* request = static_cast<const nsIRequest*>(key);
60 
61   return e->mKey == request;
62 }
63 
RequestHashClearEntry(PLDHashTable * table,PLDHashEntryHdr * entry)64 static void RequestHashClearEntry(PLDHashTable* table, PLDHashEntryHdr* entry) {
65   RequestMapEntry* e = static_cast<RequestMapEntry*>(entry);
66 
67   // An entry is being cleared, let the entry do its own cleanup.
68   e->~RequestMapEntry();
69 }
70 
RequestHashInitEntry(PLDHashEntryHdr * entry,const void * key)71 static void RequestHashInitEntry(PLDHashEntryHdr* entry, const void* key) {
72   const nsIRequest* const_request = static_cast<const nsIRequest*>(key);
73   nsIRequest* request = const_cast<nsIRequest*>(const_request);
74 
75   // Initialize the entry with placement new
76   new (entry) RequestMapEntry(request);
77 }
78 
79 static const PLDHashTableOps sRequestHashOps = {
80     PLDHashTable::HashVoidPtrKeyStub, RequestHashMatchEntry,
81     PLDHashTable::MoveEntryStub, RequestHashClearEntry, RequestHashInitEntry};
82 
RescheduleRequest(nsIRequest * aRequest,int32_t delta)83 static void RescheduleRequest(nsIRequest* aRequest, int32_t delta) {
84   nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(aRequest);
85   if (p) p->AdjustPriority(delta);
86 }
87 
nsLoadGroup()88 nsLoadGroup::nsLoadGroup()
89     : mRequests(&sRequestHashOps, sizeof(RequestMapEntry)) {
90   LOG(("LOADGROUP [%p]: Created.\n", this));
91 }
92 
~nsLoadGroup()93 nsLoadGroup::~nsLoadGroup() {
94   DebugOnly<nsresult> rv = Cancel(NS_BINDING_ABORTED);
95   NS_ASSERTION(NS_SUCCEEDED(rv), "Cancel failed");
96 
97   mDefaultLoadRequest = nullptr;
98 
99   if (mRequestContext && !mExternalRequestContext) {
100     mRequestContextService->RemoveRequestContext(mRequestContext->GetID());
101     if (IsNeckoChild() && gNeckoChild) {
102       gNeckoChild->SendRemoveRequestContext(mRequestContext->GetID());
103     }
104   }
105 
106   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
107   if (os) {
108     Unused << os->RemoveObserver(this, "last-pb-context-exited");
109   }
110 
111   LOG(("LOADGROUP [%p]: Destroyed.\n", this));
112 }
113 
114 ////////////////////////////////////////////////////////////////////////////////
115 // nsISupports methods:
116 
NS_IMPL_ISUPPORTS(nsLoadGroup,nsILoadGroup,nsILoadGroupChild,nsIRequest,nsISupportsPriority,nsISupportsWeakReference,nsIObserver)117 NS_IMPL_ISUPPORTS(nsLoadGroup, nsILoadGroup, nsILoadGroupChild, nsIRequest,
118                   nsISupportsPriority, nsISupportsWeakReference, nsIObserver)
119 
120 ////////////////////////////////////////////////////////////////////////////////
121 // nsIRequest methods:
122 
123 NS_IMETHODIMP
124 nsLoadGroup::GetName(nsACString& result) {
125   // XXX is this the right "name" for a load group?
126 
127   if (!mDefaultLoadRequest) {
128     result.Truncate();
129     return NS_OK;
130   }
131 
132   return mDefaultLoadRequest->GetName(result);
133 }
134 
135 NS_IMETHODIMP
IsPending(bool * aResult)136 nsLoadGroup::IsPending(bool* aResult) {
137   *aResult = mForegroundCount > 0;
138   return NS_OK;
139 }
140 
141 NS_IMETHODIMP
GetStatus(nsresult * status)142 nsLoadGroup::GetStatus(nsresult* status) {
143   if (NS_SUCCEEDED(mStatus) && mDefaultLoadRequest) {
144     return mDefaultLoadRequest->GetStatus(status);
145   }
146 
147   *status = mStatus;
148   return NS_OK;
149 }
150 
AppendRequestsToArray(PLDHashTable * aTable,nsTArray<nsIRequest * > * aArray)151 static bool AppendRequestsToArray(PLDHashTable* aTable,
152                                   nsTArray<nsIRequest*>* aArray) {
153   for (auto iter = aTable->Iter(); !iter.Done(); iter.Next()) {
154     auto* e = static_cast<RequestMapEntry*>(iter.Get());
155     nsIRequest* request = e->mKey;
156     NS_ASSERTION(request, "What? Null key in PLDHashTable entry?");
157 
158     // XXX(Bug 1631371) Check if this should use a fallible operation as it
159     // pretended earlier.
160     aArray->AppendElement(request);
161     NS_ADDREF(request);
162   }
163 
164   if (aArray->Length() != aTable->EntryCount()) {
165     for (uint32_t i = 0, len = aArray->Length(); i < len; ++i) {
166       NS_RELEASE((*aArray)[i]);
167     }
168     return false;
169   }
170   return true;
171 }
172 
173 NS_IMETHODIMP
Cancel(nsresult status)174 nsLoadGroup::Cancel(nsresult status) {
175   MOZ_ASSERT(NS_IsMainThread());
176 
177   NS_ASSERTION(NS_FAILED(status), "shouldn't cancel with a success code");
178   nsresult rv;
179   uint32_t count = mRequests.EntryCount();
180 
181   AutoTArray<nsIRequest*, 8> requests;
182 
183   if (!AppendRequestsToArray(&mRequests, &requests)) {
184     return NS_ERROR_OUT_OF_MEMORY;
185   }
186 
187   // set the load group status to our cancel status while we cancel
188   // all our requests...once the cancel is done, we'll reset it...
189   //
190   mStatus = status;
191 
192   // Set the flag indicating that the loadgroup is being canceled...  This
193   // prevents any new channels from being added during the operation.
194   //
195   mIsCanceling = true;
196 
197   nsresult firstError = NS_OK;
198   while (count > 0) {
199     nsCOMPtr<nsIRequest> request = requests.ElementAt(--count);
200 
201     NS_ASSERTION(request, "NULL request found in list.");
202 
203     if (!mRequests.Search(request)) {
204       // |request| was removed already
205       // We need to null out the entry in the request array so we don't try
206       // to notify the observers for this request.
207       nsCOMPtr<nsIRequest> request = dont_AddRef(requests.ElementAt(count));
208       requests.ElementAt(count) = nullptr;
209 
210       continue;
211     }
212 
213     if (MOZ_LOG_TEST(gLoadGroupLog, LogLevel::Debug)) {
214       nsAutoCString nameStr;
215       request->GetName(nameStr);
216       LOG(("LOADGROUP [%p]: Canceling request %p %s.\n", this, request.get(),
217            nameStr.get()));
218     }
219 
220     // Cancel the request...
221     rv = request->Cancel(status);
222 
223     // Remember the first failure and return it...
224     if (NS_FAILED(rv) && NS_SUCCEEDED(firstError)) firstError = rv;
225 
226     if (NS_FAILED(RemoveRequestFromHashtable(request, status))) {
227       // It's possible that request->Cancel causes the request to be removed
228       // from the loadgroup causing RemoveRequestFromHashtable to fail.
229       // In that case we shouldn't call NotifyRemovalObservers or decrement
230       // mForegroundCount since that has already happened.
231       nsCOMPtr<nsIRequest> request = dont_AddRef(requests.ElementAt(count));
232       requests.ElementAt(count) = nullptr;
233 
234       continue;
235     }
236   }
237 
238   for (count = requests.Length(); count > 0;) {
239     nsCOMPtr<nsIRequest> request = dont_AddRef(requests.ElementAt(--count));
240     (void)NotifyRemovalObservers(request, status);
241   }
242 
243   if (mRequestContext) {
244     Unused << mRequestContext->CancelTailPendingRequests(status);
245   }
246 
247 #if defined(DEBUG)
248   NS_ASSERTION(mRequests.EntryCount() == 0, "Request list is not empty.");
249   NS_ASSERTION(mForegroundCount == 0, "Foreground URLs are active.");
250 #endif
251 
252   mStatus = NS_OK;
253   mIsCanceling = false;
254 
255   return firstError;
256 }
257 
258 NS_IMETHODIMP
Suspend()259 nsLoadGroup::Suspend() {
260   nsresult rv, firstError;
261   uint32_t count = mRequests.EntryCount();
262 
263   AutoTArray<nsIRequest*, 8> requests;
264 
265   if (!AppendRequestsToArray(&mRequests, &requests)) {
266     return NS_ERROR_OUT_OF_MEMORY;
267   }
268 
269   firstError = NS_OK;
270   //
271   // Operate the elements from back to front so that if items get
272   // get removed from the list it won't affect our iteration
273   //
274   while (count > 0) {
275     nsCOMPtr<nsIRequest> request = dont_AddRef(requests.ElementAt(--count));
276 
277     NS_ASSERTION(request, "NULL request found in list.");
278     if (!request) continue;
279 
280     if (MOZ_LOG_TEST(gLoadGroupLog, LogLevel::Debug)) {
281       nsAutoCString nameStr;
282       request->GetName(nameStr);
283       LOG(("LOADGROUP [%p]: Suspending request %p %s.\n", this, request.get(),
284            nameStr.get()));
285     }
286 
287     // Suspend the request...
288     rv = request->Suspend();
289 
290     // Remember the first failure and return it...
291     if (NS_FAILED(rv) && NS_SUCCEEDED(firstError)) firstError = rv;
292   }
293 
294   return firstError;
295 }
296 
297 NS_IMETHODIMP
Resume()298 nsLoadGroup::Resume() {
299   nsresult rv, firstError;
300   uint32_t count = mRequests.EntryCount();
301 
302   AutoTArray<nsIRequest*, 8> requests;
303 
304   if (!AppendRequestsToArray(&mRequests, &requests)) {
305     return NS_ERROR_OUT_OF_MEMORY;
306   }
307 
308   firstError = NS_OK;
309   //
310   // Operate the elements from back to front so that if items get
311   // get removed from the list it won't affect our iteration
312   //
313   while (count > 0) {
314     nsCOMPtr<nsIRequest> request = dont_AddRef(requests.ElementAt(--count));
315 
316     NS_ASSERTION(request, "NULL request found in list.");
317     if (!request) continue;
318 
319     if (MOZ_LOG_TEST(gLoadGroupLog, LogLevel::Debug)) {
320       nsAutoCString nameStr;
321       request->GetName(nameStr);
322       LOG(("LOADGROUP [%p]: Resuming request %p %s.\n", this, request.get(),
323            nameStr.get()));
324     }
325 
326     // Resume the request...
327     rv = request->Resume();
328 
329     // Remember the first failure and return it...
330     if (NS_FAILED(rv) && NS_SUCCEEDED(firstError)) firstError = rv;
331   }
332 
333   return firstError;
334 }
335 
336 NS_IMETHODIMP
GetLoadFlags(uint32_t * aLoadFlags)337 nsLoadGroup::GetLoadFlags(uint32_t* aLoadFlags) {
338   *aLoadFlags = mLoadFlags;
339   return NS_OK;
340 }
341 
342 NS_IMETHODIMP
SetLoadFlags(uint32_t aLoadFlags)343 nsLoadGroup::SetLoadFlags(uint32_t aLoadFlags) {
344   mLoadFlags = aLoadFlags;
345   return NS_OK;
346 }
347 
348 NS_IMETHODIMP
GetTRRMode(nsIRequest::TRRMode * aTRRMode)349 nsLoadGroup::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
350   return GetTRRModeImpl(aTRRMode);
351 }
352 
353 NS_IMETHODIMP
SetTRRMode(nsIRequest::TRRMode aTRRMode)354 nsLoadGroup::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
355   return SetTRRModeImpl(aTRRMode);
356 }
357 
358 NS_IMETHODIMP
GetLoadGroup(nsILoadGroup ** loadGroup)359 nsLoadGroup::GetLoadGroup(nsILoadGroup** loadGroup) {
360   nsCOMPtr<nsILoadGroup> result = mLoadGroup;
361   result.forget(loadGroup);
362   return NS_OK;
363 }
364 
365 NS_IMETHODIMP
SetLoadGroup(nsILoadGroup * loadGroup)366 nsLoadGroup::SetLoadGroup(nsILoadGroup* loadGroup) {
367   mLoadGroup = loadGroup;
368   return NS_OK;
369 }
370 
371 ////////////////////////////////////////////////////////////////////////////////
372 // nsILoadGroup methods:
373 
374 NS_IMETHODIMP
GetDefaultLoadRequest(nsIRequest ** aRequest)375 nsLoadGroup::GetDefaultLoadRequest(nsIRequest** aRequest) {
376   nsCOMPtr<nsIRequest> result = mDefaultLoadRequest;
377   result.forget(aRequest);
378   return NS_OK;
379 }
380 
381 NS_IMETHODIMP
SetDefaultLoadRequest(nsIRequest * aRequest)382 nsLoadGroup::SetDefaultLoadRequest(nsIRequest* aRequest) {
383   LOG(("nsLoadGroup::SetDefaultLoadRequest this=%p default-request=%p", this,
384        aRequest));
385 
386   mDefaultLoadRequest = aRequest;
387   // Inherit the group load flags from the default load request
388   if (mDefaultLoadRequest) {
389     mDefaultLoadRequest->GetLoadFlags(&mLoadFlags);
390     //
391     // Mask off any bits that are not part of the nsIRequest flags.
392     // in particular, nsIChannel::LOAD_DOCUMENT_URI...
393     //
394     mLoadFlags &= nsIRequest::LOAD_REQUESTMASK;
395 
396     nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(aRequest);
397     mDefaultLoadIsTimed = timedChannel != nullptr;
398     if (mDefaultLoadIsTimed) {
399       timedChannel->GetChannelCreation(&mDefaultRequestCreationTime);
400       timedChannel->SetTimingEnabled(true);
401     }
402   }
403   // Else, do not change the group's load flags (see bug 95981)
404   return NS_OK;
405 }
406 
407 NS_IMETHODIMP
AddRequest(nsIRequest * request,nsISupports * ctxt)408 nsLoadGroup::AddRequest(nsIRequest* request, nsISupports* ctxt) {
409   nsresult rv;
410 
411   if (MOZ_LOG_TEST(gLoadGroupLog, LogLevel::Debug)) {
412     nsAutoCString nameStr;
413     request->GetName(nameStr);
414     LOG(("LOADGROUP [%p]: Adding request %p %s (count=%d).\n", this, request,
415          nameStr.get(), mRequests.EntryCount()));
416   }
417 
418   NS_ASSERTION(!mRequests.Search(request),
419                "Entry added to loadgroup twice, don't do that");
420 
421   //
422   // Do not add the channel, if the loadgroup is being canceled...
423   //
424   if (mIsCanceling) {
425     LOG(
426         ("LOADGROUP [%p]: AddChannel() ABORTED because LoadGroup is"
427          " being canceled!!\n",
428          this));
429 
430     return NS_BINDING_ABORTED;
431   }
432 
433   nsLoadFlags flags;
434   // if the request is the default load request or if the default load
435   // request is null, then the load group should inherit its load flags from
436   // the request, but also we need to enforce defaultLoadFlags.
437   if (mDefaultLoadRequest == request || !mDefaultLoadRequest) {
438     rv = MergeDefaultLoadFlags(request, flags);
439   } else {
440     rv = MergeLoadFlags(request, flags);
441   }
442   if (NS_FAILED(rv)) return rv;
443 
444   //
445   // Add the request to the list of active requests...
446   //
447 
448   auto* entry = static_cast<RequestMapEntry*>(mRequests.Add(request, fallible));
449   if (!entry) {
450     return NS_ERROR_OUT_OF_MEMORY;
451   }
452 
453   if (mPriority != 0) RescheduleRequest(request, mPriority);
454 
455   nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(request);
456   if (timedChannel) timedChannel->SetTimingEnabled(true);
457 
458   bool foreground = !(flags & nsIRequest::LOAD_BACKGROUND);
459   if (foreground) {
460     // Update the count of foreground URIs..
461     mForegroundCount += 1;
462   }
463 
464   if (foreground || mNotifyObserverAboutBackgroundRequests) {
465     //
466     // Fire the OnStartRequest notification out to the observer...
467     //
468     // If the notification fails then DO NOT add the request to
469     // the load group.
470     //
471     nsCOMPtr<nsIRequestObserver> observer = do_QueryReferent(mObserver);
472     if (observer) {
473       LOG(
474           ("LOADGROUP [%p]: Firing OnStartRequest for request %p."
475            "(foreground count=%d).\n",
476            this, request, mForegroundCount));
477 
478       rv = observer->OnStartRequest(request);
479       if (NS_FAILED(rv)) {
480         LOG(("LOADGROUP [%p]: OnStartRequest for request %p FAILED.\n", this,
481              request));
482         //
483         // The URI load has been canceled by the observer.  Clean up
484         // the damage...
485         //
486 
487         mRequests.Remove(request);
488 
489         rv = NS_OK;
490 
491         if (foreground) {
492           mForegroundCount -= 1;
493         }
494       }
495     }
496 
497     // Ensure that we're part of our loadgroup while pending
498     if (foreground && mForegroundCount == 1 && mLoadGroup) {
499       mLoadGroup->AddRequest(this, nullptr);
500     }
501   }
502 
503   return rv;
504 }
505 
506 NS_IMETHODIMP
RemoveRequest(nsIRequest * request,nsISupports * ctxt,nsresult aStatus)507 nsLoadGroup::RemoveRequest(nsIRequest* request, nsISupports* ctxt,
508                            nsresult aStatus) {
509   // Make sure we have a owning reference to the request we're about
510   // to remove.
511   nsCOMPtr<nsIRequest> kungFuDeathGrip(request);
512 
513   nsresult rv = RemoveRequestFromHashtable(request, aStatus);
514   if (NS_FAILED(rv)) {
515     return rv;
516   }
517 
518   return NotifyRemovalObservers(request, aStatus);
519 }
520 
RemoveRequestFromHashtable(nsIRequest * request,nsresult aStatus)521 nsresult nsLoadGroup::RemoveRequestFromHashtable(nsIRequest* request,
522                                                  nsresult aStatus) {
523   NS_ENSURE_ARG_POINTER(request);
524   nsresult rv;
525 
526   if (MOZ_LOG_TEST(gLoadGroupLog, LogLevel::Debug)) {
527     nsAutoCString nameStr;
528     request->GetName(nameStr);
529     LOG(("LOADGROUP [%p]: Removing request %p %s status %" PRIx32
530          " (count=%d).\n",
531          this, request, nameStr.get(), static_cast<uint32_t>(aStatus),
532          mRequests.EntryCount() - 1));
533   }
534 
535   //
536   // Remove the request from the group.  If this fails, it means that
537   // the request was *not* in the group so do not update the foreground
538   // count or it will get messed up...
539   //
540   auto* entry = static_cast<RequestMapEntry*>(mRequests.Search(request));
541 
542   if (!entry) {
543     LOG(("LOADGROUP [%p]: Unable to remove request %p. Not in group!\n", this,
544          request));
545 
546     return NS_ERROR_FAILURE;
547   }
548 
549   mRequests.RemoveEntry(entry);
550 
551   // Collect telemetry stats only when default request is a timed channel.
552   // Don't include failed requests in the timing statistics.
553   if (mDefaultLoadIsTimed && NS_SUCCEEDED(aStatus)) {
554     nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(request);
555     if (timedChannel) {
556       // Figure out if this request was served from the cache
557       ++mTimedRequests;
558       TimeStamp timeStamp;
559       rv = timedChannel->GetCacheReadStart(&timeStamp);
560       if (NS_SUCCEEDED(rv) && !timeStamp.IsNull()) {
561         ++mCachedRequests;
562       }
563 
564       rv = timedChannel->GetAsyncOpen(&timeStamp);
565       if (NS_SUCCEEDED(rv) && !timeStamp.IsNull()) {
566         Telemetry::AccumulateTimeDelta(
567             Telemetry::HTTP_SUBITEM_OPEN_LATENCY_TIME,
568             mDefaultRequestCreationTime, timeStamp);
569       }
570 
571       rv = timedChannel->GetResponseStart(&timeStamp);
572       if (NS_SUCCEEDED(rv) && !timeStamp.IsNull()) {
573         Telemetry::AccumulateTimeDelta(
574             Telemetry::HTTP_SUBITEM_FIRST_BYTE_LATENCY_TIME,
575             mDefaultRequestCreationTime, timeStamp);
576       }
577 
578       TelemetryReportChannel(timedChannel, false);
579     }
580   }
581 
582   if (mRequests.EntryCount() == 0) {
583     TelemetryReport();
584   }
585 
586   return NS_OK;
587 }
588 
NotifyRemovalObservers(nsIRequest * request,nsresult aStatus)589 nsresult nsLoadGroup::NotifyRemovalObservers(nsIRequest* request,
590                                              nsresult aStatus) {
591   NS_ENSURE_ARG_POINTER(request);
592   // Undo any group priority delta...
593   if (mPriority != 0) RescheduleRequest(request, -mPriority);
594 
595   nsLoadFlags flags;
596   nsresult rv = request->GetLoadFlags(&flags);
597   if (NS_FAILED(rv)) return rv;
598 
599   bool foreground = !(flags & nsIRequest::LOAD_BACKGROUND);
600   if (foreground) {
601     NS_ASSERTION(mForegroundCount > 0, "ForegroundCount messed up");
602     mForegroundCount -= 1;
603   }
604 
605   if (foreground || mNotifyObserverAboutBackgroundRequests) {
606     // Fire the OnStopRequest out to the observer...
607     nsCOMPtr<nsIRequestObserver> observer = do_QueryReferent(mObserver);
608     if (observer) {
609       LOG(
610           ("LOADGROUP [%p]: Firing OnStopRequest for request %p."
611            "(foreground count=%d).\n",
612            this, request, mForegroundCount));
613 
614       rv = observer->OnStopRequest(request, aStatus);
615 
616       if (NS_FAILED(rv)) {
617         LOG(("LOADGROUP [%p]: OnStopRequest for request %p FAILED.\n", this,
618              request));
619       }
620     }
621 
622     // If that was the last request -> remove ourselves from loadgroup
623     if (foreground && mForegroundCount == 0 && mLoadGroup) {
624       mLoadGroup->RemoveRequest(this, nullptr, aStatus);
625     }
626   }
627 
628   return rv;
629 }
630 
631 NS_IMETHODIMP
GetRequests(nsISimpleEnumerator ** aRequests)632 nsLoadGroup::GetRequests(nsISimpleEnumerator** aRequests) {
633   nsCOMArray<nsIRequest> requests;
634   requests.SetCapacity(mRequests.EntryCount());
635 
636   for (auto iter = mRequests.Iter(); !iter.Done(); iter.Next()) {
637     auto* e = static_cast<RequestMapEntry*>(iter.Get());
638     requests.AppendObject(e->mKey);
639   }
640 
641   return NS_NewArrayEnumerator(aRequests, requests, NS_GET_IID(nsIRequest));
642 }
643 
644 NS_IMETHODIMP
SetGroupObserver(nsIRequestObserver * aObserver)645 nsLoadGroup::SetGroupObserver(nsIRequestObserver* aObserver) {
646   SetGroupObserver(aObserver, false);
647   return NS_OK;
648 }
649 
SetGroupObserver(nsIRequestObserver * aObserver,bool aIncludeBackgroundRequests)650 void nsLoadGroup::SetGroupObserver(nsIRequestObserver* aObserver,
651                                    bool aIncludeBackgroundRequests) {
652   mObserver = do_GetWeakReference(aObserver);
653   mNotifyObserverAboutBackgroundRequests = aIncludeBackgroundRequests;
654 }
655 
656 NS_IMETHODIMP
GetGroupObserver(nsIRequestObserver ** aResult)657 nsLoadGroup::GetGroupObserver(nsIRequestObserver** aResult) {
658   nsCOMPtr<nsIRequestObserver> observer = do_QueryReferent(mObserver);
659   observer.forget(aResult);
660   return NS_OK;
661 }
662 
663 NS_IMETHODIMP
GetActiveCount(uint32_t * aResult)664 nsLoadGroup::GetActiveCount(uint32_t* aResult) {
665   *aResult = mForegroundCount;
666   return NS_OK;
667 }
668 
669 NS_IMETHODIMP
GetNotificationCallbacks(nsIInterfaceRequestor ** aCallbacks)670 nsLoadGroup::GetNotificationCallbacks(nsIInterfaceRequestor** aCallbacks) {
671   NS_ENSURE_ARG_POINTER(aCallbacks);
672   nsCOMPtr<nsIInterfaceRequestor> callbacks = mCallbacks;
673   callbacks.forget(aCallbacks);
674   return NS_OK;
675 }
676 
677 NS_IMETHODIMP
SetNotificationCallbacks(nsIInterfaceRequestor * aCallbacks)678 nsLoadGroup::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks) {
679   mCallbacks = aCallbacks;
680   return NS_OK;
681 }
682 
683 NS_IMETHODIMP
GetRequestContextID(uint64_t * aRCID)684 nsLoadGroup::GetRequestContextID(uint64_t* aRCID) {
685   if (!mRequestContext) {
686     return NS_ERROR_NOT_AVAILABLE;
687   }
688   *aRCID = mRequestContext->GetID();
689   return NS_OK;
690 }
691 
692 ////////////////////////////////////////////////////////////////////////////////
693 // nsILoadGroupChild methods:
694 
695 NS_IMETHODIMP
GetParentLoadGroup(nsILoadGroup ** aParentLoadGroup)696 nsLoadGroup::GetParentLoadGroup(nsILoadGroup** aParentLoadGroup) {
697   *aParentLoadGroup = nullptr;
698   nsCOMPtr<nsILoadGroup> parent = do_QueryReferent(mParentLoadGroup);
699   if (!parent) return NS_OK;
700   parent.forget(aParentLoadGroup);
701   return NS_OK;
702 }
703 
704 NS_IMETHODIMP
SetParentLoadGroup(nsILoadGroup * aParentLoadGroup)705 nsLoadGroup::SetParentLoadGroup(nsILoadGroup* aParentLoadGroup) {
706   mParentLoadGroup = do_GetWeakReference(aParentLoadGroup);
707   return NS_OK;
708 }
709 
710 NS_IMETHODIMP
GetChildLoadGroup(nsILoadGroup ** aChildLoadGroup)711 nsLoadGroup::GetChildLoadGroup(nsILoadGroup** aChildLoadGroup) {
712   *aChildLoadGroup = do_AddRef(this).take();
713   return NS_OK;
714 }
715 
716 NS_IMETHODIMP
GetRootLoadGroup(nsILoadGroup ** aRootLoadGroup)717 nsLoadGroup::GetRootLoadGroup(nsILoadGroup** aRootLoadGroup) {
718   // first recursively try the root load group of our parent
719   nsCOMPtr<nsILoadGroupChild> ancestor = do_QueryReferent(mParentLoadGroup);
720   if (ancestor) return ancestor->GetRootLoadGroup(aRootLoadGroup);
721 
722   // next recursively try the root load group of our own load grop
723   ancestor = do_QueryInterface(mLoadGroup);
724   if (ancestor) return ancestor->GetRootLoadGroup(aRootLoadGroup);
725 
726   // finally just return this
727   *aRootLoadGroup = do_AddRef(this).take();
728   return NS_OK;
729 }
730 
731 ////////////////////////////////////////////////////////////////////////////////
732 // nsISupportsPriority methods:
733 
734 NS_IMETHODIMP
GetPriority(int32_t * aValue)735 nsLoadGroup::GetPriority(int32_t* aValue) {
736   *aValue = mPriority;
737   return NS_OK;
738 }
739 
740 NS_IMETHODIMP
SetPriority(int32_t aValue)741 nsLoadGroup::SetPriority(int32_t aValue) {
742   return AdjustPriority(aValue - mPriority);
743 }
744 
745 NS_IMETHODIMP
AdjustPriority(int32_t aDelta)746 nsLoadGroup::AdjustPriority(int32_t aDelta) {
747   // Update the priority for each request that supports nsISupportsPriority
748   if (aDelta != 0) {
749     mPriority += aDelta;
750     for (auto iter = mRequests.Iter(); !iter.Done(); iter.Next()) {
751       auto* e = static_cast<RequestMapEntry*>(iter.Get());
752       RescheduleRequest(e->mKey, aDelta);
753     }
754   }
755   return NS_OK;
756 }
757 
758 NS_IMETHODIMP
GetDefaultLoadFlags(uint32_t * aFlags)759 nsLoadGroup::GetDefaultLoadFlags(uint32_t* aFlags) {
760   *aFlags = mDefaultLoadFlags;
761   return NS_OK;
762 }
763 
764 NS_IMETHODIMP
SetDefaultLoadFlags(uint32_t aFlags)765 nsLoadGroup::SetDefaultLoadFlags(uint32_t aFlags) {
766   mDefaultLoadFlags = aFlags;
767   return NS_OK;
768 }
769 
770 ////////////////////////////////////////////////////////////////////////////////
771 
TelemetryReport()772 void nsLoadGroup::TelemetryReport() {
773   nsresult defaultStatus = NS_ERROR_INVALID_ARG;
774   // We should only report HTTP_PAGE_* telemetry if the defaultRequest was
775   // actually successful.
776   if (mDefaultLoadRequest) {
777     mDefaultLoadRequest->GetStatus(&defaultStatus);
778   }
779   if (mDefaultLoadIsTimed && NS_SUCCEEDED(defaultStatus)) {
780     Telemetry::Accumulate(Telemetry::HTTP_REQUEST_PER_PAGE, mTimedRequests);
781     if (mTimedRequests) {
782       Telemetry::Accumulate(Telemetry::HTTP_REQUEST_PER_PAGE_FROM_CACHE,
783                             mCachedRequests * 100 / mTimedRequests);
784     }
785 
786     nsCOMPtr<nsITimedChannel> timedChannel =
787         do_QueryInterface(mDefaultLoadRequest);
788     if (timedChannel) TelemetryReportChannel(timedChannel, true);
789   }
790 
791   mTimedRequests = 0;
792   mCachedRequests = 0;
793   mDefaultLoadIsTimed = false;
794 }
795 
TelemetryReportChannel(nsITimedChannel * aTimedChannel,bool aDefaultRequest)796 void nsLoadGroup::TelemetryReportChannel(nsITimedChannel* aTimedChannel,
797                                          bool aDefaultRequest) {
798   nsresult rv;
799   bool timingEnabled;
800   rv = aTimedChannel->GetTimingEnabled(&timingEnabled);
801   if (NS_FAILED(rv) || !timingEnabled) return;
802 
803   TimeStamp asyncOpen;
804   rv = aTimedChannel->GetAsyncOpen(&asyncOpen);
805   // We do not check !asyncOpen.IsNull() bellow, prevent ASSERTIONs this way
806   if (NS_FAILED(rv) || asyncOpen.IsNull()) return;
807 
808   TimeStamp cacheReadStart;
809   rv = aTimedChannel->GetCacheReadStart(&cacheReadStart);
810   if (NS_FAILED(rv)) return;
811 
812   TimeStamp cacheReadEnd;
813   rv = aTimedChannel->GetCacheReadEnd(&cacheReadEnd);
814   if (NS_FAILED(rv)) return;
815 
816   TimeStamp domainLookupStart;
817   rv = aTimedChannel->GetDomainLookupStart(&domainLookupStart);
818   if (NS_FAILED(rv)) return;
819 
820   TimeStamp domainLookupEnd;
821   rv = aTimedChannel->GetDomainLookupEnd(&domainLookupEnd);
822   if (NS_FAILED(rv)) return;
823 
824   TimeStamp connectStart;
825   rv = aTimedChannel->GetConnectStart(&connectStart);
826   if (NS_FAILED(rv)) return;
827 
828   TimeStamp secureConnectionStart;
829   rv = aTimedChannel->GetSecureConnectionStart(&secureConnectionStart);
830   if (NS_FAILED(rv)) return;
831 
832   TimeStamp connectEnd;
833   rv = aTimedChannel->GetConnectEnd(&connectEnd);
834   if (NS_FAILED(rv)) return;
835 
836   TimeStamp requestStart;
837   rv = aTimedChannel->GetRequestStart(&requestStart);
838   if (NS_FAILED(rv)) return;
839 
840   TimeStamp responseStart;
841   rv = aTimedChannel->GetResponseStart(&responseStart);
842   if (NS_FAILED(rv)) return;
843 
844   TimeStamp responseEnd;
845   rv = aTimedChannel->GetResponseEnd(&responseEnd);
846   if (NS_FAILED(rv)) return;
847 
848   bool useHttp3 = false;
849   bool supportHttp3 = false;
850   nsCOMPtr<nsIHttpChannelInternal> httpChannel =
851       do_QueryInterface(aTimedChannel);
852   if (httpChannel) {
853     uint32_t major;
854     uint32_t minor;
855     if (NS_SUCCEEDED(httpChannel->GetResponseVersion(&major, &minor))) {
856       useHttp3 = major == 3;
857       if (major == 2) {
858         if (NS_FAILED(httpChannel->GetSupportsHTTP3(&supportHttp3))) {
859           supportHttp3 = false;
860         }
861       }
862     }
863   }
864 
865 #define HTTP_REQUEST_HISTOGRAMS(prefix)                                        \
866   if (!domainLookupStart.IsNull()) {                                           \
867     Telemetry::AccumulateTimeDelta(Telemetry::HTTP_##prefix##_DNS_ISSUE_TIME,  \
868                                    asyncOpen, domainLookupStart);              \
869   }                                                                            \
870                                                                                \
871   if (!domainLookupStart.IsNull() && !domainLookupEnd.IsNull()) {              \
872     Telemetry::AccumulateTimeDelta(Telemetry::HTTP_##prefix##_DNS_LOOKUP_TIME, \
873                                    domainLookupStart, domainLookupEnd);        \
874   }                                                                            \
875                                                                                \
876   if (!secureConnectionStart.IsNull() && !connectEnd.IsNull()) {               \
877     Telemetry::AccumulateTimeDelta(Telemetry::HTTP_##prefix##_TLS_HANDSHAKE,   \
878                                    secureConnectionStart, connectEnd);         \
879   }                                                                            \
880                                                                                \
881   if (!connectStart.IsNull() && !connectEnd.IsNull()) {                        \
882     Telemetry::AccumulateTimeDelta(                                            \
883         Telemetry::HTTP_##prefix##_TCP_CONNECTION_2, connectStart,             \
884         connectEnd);                                                           \
885   }                                                                            \
886                                                                                \
887   if (!requestStart.IsNull() && !responseEnd.IsNull()) {                       \
888     Telemetry::AccumulateTimeDelta(                                            \
889         Telemetry::HTTP_##prefix##_OPEN_TO_FIRST_SENT, asyncOpen,              \
890         requestStart);                                                         \
891                                                                                \
892     Telemetry::AccumulateTimeDelta(                                            \
893         Telemetry::HTTP_##prefix##_FIRST_SENT_TO_LAST_RECEIVED, requestStart,  \
894         responseEnd);                                                          \
895                                                                                \
896     if (cacheReadStart.IsNull() && !responseStart.IsNull()) {                  \
897       Telemetry::AccumulateTimeDelta(                                          \
898           Telemetry::HTTP_##prefix##_OPEN_TO_FIRST_RECEIVED, asyncOpen,        \
899           responseStart);                                                      \
900     }                                                                          \
901   }                                                                            \
902                                                                                \
903   if (!cacheReadStart.IsNull() && !cacheReadEnd.IsNull()) {                    \
904     Telemetry::AccumulateTimeDelta(                                            \
905         Telemetry::HTTP_##prefix##_OPEN_TO_FIRST_FROM_CACHE_V2, asyncOpen,     \
906         cacheReadStart);                                                       \
907                                                                                \
908     Telemetry::AccumulateTimeDelta(                                            \
909         Telemetry::HTTP_##prefix##_CACHE_READ_TIME_V2, cacheReadStart,         \
910         cacheReadEnd);                                                         \
911                                                                                \
912     if (!requestStart.IsNull() && !responseEnd.IsNull()) {                     \
913       Telemetry::AccumulateTimeDelta(Telemetry::HTTP_##prefix##_REVALIDATION,  \
914                                      requestStart, responseEnd);               \
915     }                                                                          \
916   }                                                                            \
917                                                                                \
918   if (!cacheReadEnd.IsNull()) {                                                \
919     Telemetry::AccumulateTimeDelta(                                            \
920         Telemetry::HTTP_##prefix##_COMPLETE_LOAD_V2, asyncOpen, cacheReadEnd); \
921     Telemetry::AccumulateTimeDelta(                                            \
922         Telemetry::HTTP_##prefix##_COMPLETE_LOAD_CACHED_V2, asyncOpen,         \
923         cacheReadEnd);                                                         \
924   } else if (!responseEnd.IsNull()) {                                          \
925     Telemetry::AccumulateTimeDelta(                                            \
926         Telemetry::HTTP_##prefix##_COMPLETE_LOAD_V2, asyncOpen, responseEnd);  \
927     Telemetry::AccumulateTimeDelta(                                            \
928         Telemetry::HTTP_##prefix##_COMPLETE_LOAD_NET_V2, asyncOpen,            \
929         responseEnd);                                                          \
930   }
931 
932   if (aDefaultRequest) {
933     HTTP_REQUEST_HISTOGRAMS(PAGE)
934   } else {
935     HTTP_REQUEST_HISTOGRAMS(SUB)
936   }
937 
938   if ((useHttp3 || supportHttp3) && cacheReadStart.IsNull() &&
939       cacheReadEnd.IsNull()) {
940     nsCString key = (useHttp3) ? ((aDefaultRequest) ? "uses_http3_page"_ns
941                                                     : "uses_http3_sub"_ns)
942                                : ((aDefaultRequest) ? "supports_http3_page"_ns
943                                                     : "supports_http3_sub"_ns);
944 
945     if (!secureConnectionStart.IsNull() && !connectEnd.IsNull()) {
946       Telemetry::AccumulateTimeDelta(Telemetry::HTTP3_TLS_HANDSHAKE, key,
947                                      secureConnectionStart, connectEnd);
948     }
949 
950     if (supportHttp3 && !connectStart.IsNull() && !connectEnd.IsNull()) {
951       Telemetry::AccumulateTimeDelta(Telemetry::SUP_HTTP3_TCP_CONNECTION, key,
952                                      connectStart, connectEnd);
953     }
954 
955     if (!requestStart.IsNull() && !responseEnd.IsNull()) {
956       Telemetry::AccumulateTimeDelta(Telemetry::HTTP3_OPEN_TO_FIRST_SENT, key,
957                                      asyncOpen, requestStart);
958 
959       Telemetry::AccumulateTimeDelta(
960           Telemetry::HTTP3_FIRST_SENT_TO_LAST_RECEIVED, key, requestStart,
961           responseEnd);
962 
963       if (!responseStart.IsNull()) {
964         Telemetry::AccumulateTimeDelta(Telemetry::HTTP3_OPEN_TO_FIRST_RECEIVED,
965                                        key, asyncOpen, responseStart);
966       }
967 
968       if (!responseEnd.IsNull()) {
969         Telemetry::AccumulateTimeDelta(Telemetry::HTTP3_COMPLETE_LOAD, key,
970                                        asyncOpen, responseEnd);
971       }
972     }
973   }
974 
975   bool hasHTTPSRR = false;
976   if (httpChannel && NS_SUCCEEDED(httpChannel->GetHasHTTPSRR(&hasHTTPSRR)) &&
977       cacheReadStart.IsNull() && cacheReadEnd.IsNull() &&
978       !requestStart.IsNull()) {
979     nsCString key = (hasHTTPSRR) ? ((aDefaultRequest) ? "uses_https_rr_page"_ns
980                                                       : "uses_https_rr_sub"_ns)
981                                  : ((aDefaultRequest) ? "no_https_rr_page"_ns
982                                                       : "no_https_rr_sub"_ns);
983     Telemetry::AccumulateTimeDelta(Telemetry::HTTPS_RR_OPEN_TO_FIRST_SENT, key,
984                                    asyncOpen, requestStart);
985   }
986 
987   if (StaticPrefs::network_trr_odoh_enabled() && !domainLookupStart.IsNull() &&
988       !domainLookupEnd.IsNull()) {
989     nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
990     bool ODoHActivated = false;
991     if (dns && NS_SUCCEEDED(dns->GetODoHActivated(&ODoHActivated)) &&
992         ODoHActivated) {
993       if (aDefaultRequest) {
994         Telemetry::AccumulateTimeDelta(
995             Telemetry::HTTP_PAGE_DNS_ODOH_LOOKUP_TIME, domainLookupStart,
996             domainLookupEnd);
997       } else {
998         Telemetry::AccumulateTimeDelta(Telemetry::HTTP_SUB_DNS_ODOH_LOOKUP_TIME,
999                                        domainLookupStart, domainLookupEnd);
1000       }
1001     }
1002   }
1003 
1004 #undef HTTP_REQUEST_HISTOGRAMS
1005 }
1006 
MergeLoadFlags(nsIRequest * aRequest,nsLoadFlags & outFlags)1007 nsresult nsLoadGroup::MergeLoadFlags(nsIRequest* aRequest,
1008                                      nsLoadFlags& outFlags) {
1009   nsresult rv;
1010   nsLoadFlags flags, oldFlags;
1011 
1012   rv = aRequest->GetLoadFlags(&flags);
1013   if (NS_FAILED(rv)) {
1014     return rv;
1015   }
1016 
1017   oldFlags = flags;
1018 
1019   // Inherit the following bits...
1020   flags |= (mLoadFlags &
1021             (LOAD_BACKGROUND | LOAD_BYPASS_CACHE | LOAD_FROM_CACHE |
1022              VALIDATE_ALWAYS | VALIDATE_ONCE_PER_SESSION | VALIDATE_NEVER));
1023 
1024   // ... and force the default flags.
1025   flags |= mDefaultLoadFlags;
1026 
1027   if (flags != oldFlags) {
1028     rv = aRequest->SetLoadFlags(flags);
1029   }
1030 
1031   outFlags = flags;
1032   return rv;
1033 }
1034 
MergeDefaultLoadFlags(nsIRequest * aRequest,nsLoadFlags & outFlags)1035 nsresult nsLoadGroup::MergeDefaultLoadFlags(nsIRequest* aRequest,
1036                                             nsLoadFlags& outFlags) {
1037   nsresult rv;
1038   nsLoadFlags flags, oldFlags;
1039 
1040   rv = aRequest->GetLoadFlags(&flags);
1041   if (NS_FAILED(rv)) {
1042     return rv;
1043   }
1044 
1045   oldFlags = flags;
1046   // ... and force the default flags.
1047   flags |= mDefaultLoadFlags;
1048 
1049   if (flags != oldFlags) {
1050     rv = aRequest->SetLoadFlags(flags);
1051   }
1052   outFlags = flags;
1053   return rv;
1054 }
1055 
Init()1056 nsresult nsLoadGroup::Init() {
1057   mRequestContextService = RequestContextService::GetOrCreate();
1058   if (mRequestContextService) {
1059     Unused << mRequestContextService->NewRequestContext(
1060         getter_AddRefs(mRequestContext));
1061   }
1062 
1063   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
1064   NS_ENSURE_STATE(os);
1065 
1066   Unused << os->AddObserver(this, "last-pb-context-exited", true);
1067 
1068   return NS_OK;
1069 }
1070 
InitWithRequestContextId(const uint64_t & aRequestContextId)1071 nsresult nsLoadGroup::InitWithRequestContextId(
1072     const uint64_t& aRequestContextId) {
1073   mRequestContextService = RequestContextService::GetOrCreate();
1074   if (mRequestContextService) {
1075     Unused << mRequestContextService->GetRequestContext(
1076         aRequestContextId, getter_AddRefs(mRequestContext));
1077   }
1078   mExternalRequestContext = true;
1079 
1080   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
1081   NS_ENSURE_STATE(os);
1082 
1083   Unused << os->AddObserver(this, "last-pb-context-exited", true);
1084 
1085   return NS_OK;
1086 }
1087 
1088 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)1089 nsLoadGroup::Observe(nsISupports* aSubject, const char* aTopic,
1090                      const char16_t* aData) {
1091   MOZ_ASSERT(!strcmp(aTopic, "last-pb-context-exited"));
1092 
1093   OriginAttributes attrs;
1094   StoragePrincipalHelper::GetRegularPrincipalOriginAttributes(this, attrs);
1095   if (attrs.mPrivateBrowsingId == 0) {
1096     return NS_OK;
1097   }
1098 
1099   mBrowsingContextDiscarded = true;
1100   return NS_OK;
1101 }
1102 
1103 NS_IMETHODIMP
GetIsBrowsingContextDiscarded(bool * aIsBrowsingContextDiscarded)1104 nsLoadGroup::GetIsBrowsingContextDiscarded(bool* aIsBrowsingContextDiscarded) {
1105   *aIsBrowsingContextDiscarded = mBrowsingContextDiscarded;
1106   return NS_OK;
1107 }
1108 
1109 }  // namespace net
1110 }  // namespace mozilla
1111 
1112 #undef LOG
1113