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