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