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