Tue, 06 Jan 2015 21:39:09 +0100
Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nspr.h"
7 #include "prlog.h"
9 #include "nsDocLoader.h"
10 #include "nsCURILoader.h"
11 #include "nsNetUtil.h"
12 #include "nsIHttpChannel.h"
13 #include "nsIWebProgressListener2.h"
15 #include "nsIServiceManager.h"
16 #include "nsXPIDLString.h"
18 #include "nsIURL.h"
19 #include "nsCOMPtr.h"
20 #include "nscore.h"
21 #include "nsWeakPtr.h"
22 #include "nsAutoPtr.h"
24 #include "nsIDOMWindow.h"
26 #include "nsIStringBundle.h"
27 #include "nsIScriptSecurityManager.h"
29 #include "nsITransport.h"
30 #include "nsISocketTransport.h"
32 #include "nsIDOMDocument.h"
33 #include "nsIDocument.h"
34 #include "nsPresContext.h"
35 #include "nsIAsyncVerifyRedirectCallback.h"
37 static NS_DEFINE_CID(kThisImplCID, NS_THIS_DOCLOADER_IMPL_CID);
39 #if defined(PR_LOGGING)
40 //
41 // Log module for nsIDocumentLoader logging...
42 //
43 // To enable logging (see prlog.h for full details):
44 //
45 // set NSPR_LOG_MODULES=DocLoader:5
46 // set NSPR_LOG_FILE=nspr.log
47 //
48 // this enables PR_LOG_DEBUG level information and places all output in
49 // the file nspr.log
50 //
51 PRLogModuleInfo* gDocLoaderLog = nullptr;
52 #endif /* PR_LOGGING */
55 #if defined(DEBUG)
56 void GetURIStringFromRequest(nsIRequest* request, nsACString &name)
57 {
58 if (request)
59 request->GetName(name);
60 else
61 name.AssignLiteral("???");
62 }
63 #endif /* DEBUG */
67 bool
68 nsDocLoader::RequestInfoHashInitEntry(PLDHashTable* table,
69 PLDHashEntryHdr* entry,
70 const void* key)
71 {
72 // Initialize the entry with placement new
73 new (entry) nsRequestInfo(key);
74 return true;
75 }
77 void
78 nsDocLoader::RequestInfoHashClearEntry(PLDHashTable* table,
79 PLDHashEntryHdr* entry)
80 {
81 nsRequestInfo* info = static_cast<nsRequestInfo *>(entry);
82 info->~nsRequestInfo();
83 }
85 struct nsListenerInfo {
86 nsListenerInfo(nsIWeakReference *aListener, unsigned long aNotifyMask)
87 : mWeakListener(aListener),
88 mNotifyMask(aNotifyMask)
89 {
90 }
92 // Weak pointer for the nsIWebProgressListener...
93 nsWeakPtr mWeakListener;
95 // Mask indicating which notifications the listener wants to receive.
96 unsigned long mNotifyMask;
97 };
100 nsDocLoader::nsDocLoader()
101 : mParent(nullptr),
102 mListenerInfoList(8),
103 mCurrentSelfProgress(0),
104 mMaxSelfProgress(0),
105 mCurrentTotalProgress(0),
106 mMaxTotalProgress(0),
107 mCompletedTotalProgress(0),
108 mIsLoadingDocument(false),
109 mIsRestoringDocument(false),
110 mDontFlushLayout(false),
111 mIsFlushingLayout(false)
112 {
113 #if defined(PR_LOGGING)
114 if (nullptr == gDocLoaderLog) {
115 gDocLoaderLog = PR_NewLogModule("DocLoader");
116 }
117 #endif /* PR_LOGGING */
119 static const PLDHashTableOps hash_table_ops =
120 {
121 PL_DHashAllocTable,
122 PL_DHashFreeTable,
123 PL_DHashVoidPtrKeyStub,
124 PL_DHashMatchEntryStub,
125 PL_DHashMoveEntryStub,
126 RequestInfoHashClearEntry,
127 PL_DHashFinalizeStub,
128 RequestInfoHashInitEntry
129 };
131 PL_DHashTableInit(&mRequestInfoHash, &hash_table_ops, nullptr,
132 sizeof(nsRequestInfo), 16);
134 ClearInternalProgress();
136 PR_LOG(gDocLoaderLog, PR_LOG_DEBUG,
137 ("DocLoader:%p: created.\n", this));
138 }
140 nsresult
141 nsDocLoader::SetDocLoaderParent(nsDocLoader *aParent)
142 {
143 mParent = aParent;
144 return NS_OK;
145 }
147 nsresult
148 nsDocLoader::Init()
149 {
150 if (!mRequestInfoHash.ops) {
151 return NS_ERROR_OUT_OF_MEMORY;
152 }
154 nsresult rv = NS_NewLoadGroup(getter_AddRefs(mLoadGroup), this);
155 if (NS_FAILED(rv)) return rv;
157 PR_LOG(gDocLoaderLog, PR_LOG_DEBUG,
158 ("DocLoader:%p: load group %x.\n", this, mLoadGroup.get()));
160 return NS_OK;
161 }
163 nsDocLoader::~nsDocLoader()
164 {
165 /*
166 |ClearWeakReferences()| here is intended to prevent people holding weak references
167 from re-entering this destructor since |QueryReferent()| will |AddRef()| me, and the
168 subsequent |Release()| will try to destroy me. At this point there should be only
169 weak references remaining (otherwise, we wouldn't be getting destroyed).
171 An alternative would be incrementing our refcount (consider it a compressed flag
172 saying "Don't re-destroy."). I haven't yet decided which is better. [scc]
173 */
174 // XXXbz now that NS_IMPL_RELEASE stabilizes by setting refcount to 1, is
175 // this needed?
176 ClearWeakReferences();
178 Destroy();
180 PR_LOG(gDocLoaderLog, PR_LOG_DEBUG,
181 ("DocLoader:%p: deleted.\n", this));
183 if (mRequestInfoHash.ops) {
184 PL_DHashTableFinish(&mRequestInfoHash);
185 }
186 }
189 /*
190 * Implementation of ISupports methods...
191 */
192 NS_IMPL_ADDREF(nsDocLoader)
193 NS_IMPL_RELEASE(nsDocLoader)
195 NS_INTERFACE_MAP_BEGIN(nsDocLoader)
196 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRequestObserver)
197 NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
198 NS_INTERFACE_MAP_ENTRY(nsIDocumentLoader)
199 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
200 NS_INTERFACE_MAP_ENTRY(nsIWebProgress)
201 NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
202 NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
203 NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
204 NS_INTERFACE_MAP_ENTRY(nsISecurityEventSink)
205 NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
206 if (aIID.Equals(kThisImplCID))
207 foundInterface = static_cast<nsIDocumentLoader *>(this);
208 else
209 NS_INTERFACE_MAP_END
212 /*
213 * Implementation of nsIInterfaceRequestor methods...
214 */
215 NS_IMETHODIMP nsDocLoader::GetInterface(const nsIID& aIID, void** aSink)
216 {
217 nsresult rv = NS_ERROR_NO_INTERFACE;
219 NS_ENSURE_ARG_POINTER(aSink);
221 if(aIID.Equals(NS_GET_IID(nsILoadGroup))) {
222 *aSink = mLoadGroup;
223 NS_IF_ADDREF((nsISupports*)*aSink);
224 rv = NS_OK;
225 } else {
226 rv = QueryInterface(aIID, aSink);
227 }
229 return rv;
230 }
232 /* static */
233 already_AddRefed<nsDocLoader>
234 nsDocLoader::GetAsDocLoader(nsISupports* aSupports)
235 {
236 nsRefPtr<nsDocLoader> ret = do_QueryObject(aSupports);
237 return ret.forget();
238 }
240 /* static */
241 nsresult
242 nsDocLoader::AddDocLoaderAsChildOfRoot(nsDocLoader* aDocLoader)
243 {
244 nsresult rv;
245 nsCOMPtr<nsIDocumentLoader> docLoaderService =
246 do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID, &rv);
247 NS_ENSURE_SUCCESS(rv, rv);
249 nsRefPtr<nsDocLoader> rootDocLoader = GetAsDocLoader(docLoaderService);
250 NS_ENSURE_TRUE(rootDocLoader, NS_ERROR_UNEXPECTED);
252 return rootDocLoader->AddChildLoader(aDocLoader);
253 }
255 NS_IMETHODIMP
256 nsDocLoader::Stop(void)
257 {
258 nsresult rv = NS_OK;
260 PR_LOG(gDocLoaderLog, PR_LOG_DEBUG,
261 ("DocLoader:%p: Stop() called\n", this));
263 NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mChildList, nsDocLoader, Stop, ());
265 if (mLoadGroup)
266 rv = mLoadGroup->Cancel(NS_BINDING_ABORTED);
268 // Don't report that we're flushing layout so IsBusy returns false after a
269 // Stop call.
270 mIsFlushingLayout = false;
272 // Clear out mChildrenInOnload. We want to make sure to fire our
273 // onload at this point, and there's no issue with mChildrenInOnload
274 // after this, since mDocumentRequest will be null after the
275 // DocLoaderIsEmpty() call.
276 mChildrenInOnload.Clear();
278 // Make sure to call DocLoaderIsEmpty now so that we reset mDocumentRequest,
279 // etc, as needed. We could be getting into here from a subframe onload, in
280 // which case the call to DocLoaderIsEmpty() is coming but hasn't quite
281 // happened yet, Canceling the loadgroup did nothing (because it was already
282 // empty), and we're about to start a new load (which is what triggered this
283 // Stop() call).
285 // XXXbz If the child frame loadgroups were requests in mLoadgroup, I suspect
286 // we wouldn't need the call here....
288 NS_ASSERTION(!IsBusy(), "Shouldn't be busy here");
289 DocLoaderIsEmpty(false);
291 return rv;
292 }
295 bool
296 nsDocLoader::IsBusy()
297 {
298 nsresult rv;
300 //
301 // A document loader is busy if either:
302 //
303 // 1. One of its children is in the middle of an onload handler. Note that
304 // the handler may have already removed this child from mChildList!
305 // 2. It is currently loading a document and either has parts of it still
306 // loading, or has a busy child docloader.
307 // 3. It's currently flushing layout in DocLoaderIsEmpty().
308 //
310 if (mChildrenInOnload.Count() || mIsFlushingLayout) {
311 return true;
312 }
314 /* Is this document loader busy? */
315 if (!mIsLoadingDocument) {
316 return false;
317 }
319 bool busy;
320 rv = mLoadGroup->IsPending(&busy);
321 if (NS_FAILED(rv)) {
322 return false;
323 }
324 if (busy) {
325 return true;
326 }
328 /* check its child document loaders... */
329 uint32_t count = mChildList.Length();
330 for (uint32_t i=0; i < count; i++) {
331 nsIDocumentLoader* loader = ChildAt(i);
333 // This is a safe cast, because we only put nsDocLoader objects into the
334 // array
335 if (loader && static_cast<nsDocLoader*>(loader)->IsBusy())
336 return true;
337 }
339 return false;
340 }
342 NS_IMETHODIMP
343 nsDocLoader::GetContainer(nsISupports** aResult)
344 {
345 NS_ADDREF(*aResult = static_cast<nsIDocumentLoader*>(this));
347 return NS_OK;
348 }
350 NS_IMETHODIMP
351 nsDocLoader::GetLoadGroup(nsILoadGroup** aResult)
352 {
353 nsresult rv = NS_OK;
355 if (nullptr == aResult) {
356 rv = NS_ERROR_NULL_POINTER;
357 } else {
358 *aResult = mLoadGroup;
359 NS_IF_ADDREF(*aResult);
360 }
361 return rv;
362 }
364 void
365 nsDocLoader::Destroy()
366 {
367 Stop();
369 // Remove the document loader from the parent list of loaders...
370 if (mParent)
371 {
372 mParent->RemoveChildLoader(this);
373 }
375 // Release all the information about network requests...
376 ClearRequestInfoHash();
378 // Release all the information about registered listeners...
379 int32_t count = mListenerInfoList.Count();
380 for(int32_t i = 0; i < count; i++) {
381 nsListenerInfo *info =
382 static_cast<nsListenerInfo*>(mListenerInfoList.ElementAt(i));
384 delete info;
385 }
387 mListenerInfoList.Clear();
388 mListenerInfoList.Compact();
390 mDocumentRequest = 0;
392 if (mLoadGroup)
393 mLoadGroup->SetGroupObserver(nullptr);
395 DestroyChildren();
396 }
398 void
399 nsDocLoader::DestroyChildren()
400 {
401 uint32_t count = mChildList.Length();
402 // if the doc loader still has children...we need to enumerate the
403 // children and make them null out their back ptr to the parent doc
404 // loader
405 for (uint32_t i=0; i < count; i++)
406 {
407 nsIDocumentLoader* loader = ChildAt(i);
409 if (loader) {
410 // This is a safe cast, as we only put nsDocLoader objects into the
411 // array
412 static_cast<nsDocLoader*>(loader)->SetDocLoaderParent(nullptr);
413 }
414 }
415 mChildList.Clear();
416 }
418 NS_IMETHODIMP
419 nsDocLoader::OnStartRequest(nsIRequest *request, nsISupports *aCtxt)
420 {
421 // called each time a request is added to the group.
423 #ifdef PR_LOGGING
424 if (PR_LOG_TEST(gDocLoaderLog, PR_LOG_DEBUG)) {
425 nsAutoCString name;
426 request->GetName(name);
428 uint32_t count = 0;
429 if (mLoadGroup)
430 mLoadGroup->GetActiveCount(&count);
432 PR_LOG(gDocLoaderLog, PR_LOG_DEBUG,
433 ("DocLoader:%p: OnStartRequest[%p](%s) mIsLoadingDocument=%s, %u active URLs",
434 this, request, name.get(),
435 (mIsLoadingDocument ? "true" : "false"),
436 count));
437 }
438 #endif /* PR_LOGGING */
439 bool bJustStartedLoading = false;
441 nsLoadFlags loadFlags = 0;
442 request->GetLoadFlags(&loadFlags);
444 if (!mIsLoadingDocument && (loadFlags & nsIChannel::LOAD_DOCUMENT_URI)) {
445 bJustStartedLoading = true;
446 mIsLoadingDocument = true;
447 ClearInternalProgress(); // only clear our progress if we are starting a new load....
448 }
450 //
451 // Create a new nsRequestInfo for the request that is starting to
452 // load...
453 //
454 AddRequestInfo(request);
456 //
457 // Only fire a doStartDocumentLoad(...) if the document loader
458 // has initiated a load... Otherwise, this notification has
459 // resulted from a request being added to the load group.
460 //
461 if (mIsLoadingDocument) {
462 if (loadFlags & nsIChannel::LOAD_DOCUMENT_URI) {
463 //
464 // Make sure that the document channel is null at this point...
465 // (unless its been redirected)
466 //
467 NS_ASSERTION((loadFlags & nsIChannel::LOAD_REPLACE) ||
468 !(mDocumentRequest.get()),
469 "Overwriting an existing document channel!");
471 // This request is associated with the entire document...
472 mDocumentRequest = request;
473 mLoadGroup->SetDefaultLoadRequest(request);
475 // Only fire the start document load notification for the first
476 // document URI... Do not fire it again for redirections
477 //
478 if (bJustStartedLoading) {
479 // Update the progress status state
480 mProgressStateFlags = nsIWebProgressListener::STATE_START;
482 // Fire the start document load notification
483 doStartDocumentLoad();
484 return NS_OK;
485 }
486 }
487 }
489 NS_ASSERTION(!mIsLoadingDocument || mDocumentRequest,
490 "mDocumentRequest MUST be set for the duration of a page load!");
492 doStartURLLoad(request);
494 return NS_OK;
495 }
497 NS_IMETHODIMP
498 nsDocLoader::OnStopRequest(nsIRequest *aRequest,
499 nsISupports *aCtxt,
500 nsresult aStatus)
501 {
502 nsresult rv = NS_OK;
504 #ifdef PR_LOGGING
505 if (PR_LOG_TEST(gDocLoaderLog, PR_LOG_DEBUG)) {
506 nsAutoCString name;
507 aRequest->GetName(name);
509 uint32_t count = 0;
510 if (mLoadGroup)
511 mLoadGroup->GetActiveCount(&count);
513 PR_LOG(gDocLoaderLog, PR_LOG_DEBUG,
514 ("DocLoader:%p: OnStopRequest[%p](%s) status=%x mIsLoadingDocument=%s, %u active URLs",
515 this, aRequest, name.get(),
516 aStatus, (mIsLoadingDocument ? "true" : "false"),
517 count));
518 }
519 #endif
521 bool bFireTransferring = false;
523 //
524 // Set the Maximum progress to the same value as the current progress.
525 // Since the URI has finished loading, all the data is there. Also,
526 // this will allow a more accurate estimation of the max progress (in case
527 // the old value was unknown ie. -1)
528 //
529 nsRequestInfo *info = GetRequestInfo(aRequest);
530 if (info) {
531 // Null out mLastStatus now so we don't find it when looking for
532 // status from now on. This destroys the nsStatusInfo and hence
533 // removes it from our list.
534 info->mLastStatus = nullptr;
536 int64_t oldMax = info->mMaxProgress;
538 info->mMaxProgress = info->mCurrentProgress;
540 //
541 // If a request whose content-length was previously unknown has just
542 // finished loading, then use this new data to try to calculate a
543 // mMaxSelfProgress...
544 //
545 if ((oldMax < int64_t(0)) && (mMaxSelfProgress < int64_t(0))) {
546 mMaxSelfProgress = CalculateMaxProgress();
547 }
549 // As we know the total progress of this request now, save it to be part
550 // of CalculateMaxProgress() result. We need to remove the info from the
551 // hash, see bug 480713.
552 mCompletedTotalProgress += info->mMaxProgress;
554 //
555 // Determine whether a STATE_TRANSFERRING notification should be
556 // 'synthesized'.
557 //
558 // If nsRequestInfo::mMaxProgress (as stored in oldMax) and
559 // nsRequestInfo::mCurrentProgress are both 0, then the
560 // STATE_TRANSFERRING notification has not been fired yet...
561 //
562 if ((oldMax == 0) && (info->mCurrentProgress == 0)) {
563 nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
565 // Only fire a TRANSFERRING notification if the request is also a
566 // channel -- data transfer requires a nsIChannel!
567 //
568 if (channel) {
569 if (NS_SUCCEEDED(aStatus)) {
570 bFireTransferring = true;
571 }
572 //
573 // If the request failed (for any reason other than being
574 // redirected or retargeted), the TRANSFERRING notification can
575 // still be fired if a HTTP connection was established to a server.
576 //
577 else if (aStatus != NS_BINDING_REDIRECTED &&
578 aStatus != NS_BINDING_RETARGETED) {
579 //
580 // Only if the load has been targeted (see bug 268483)...
581 //
582 uint32_t lf;
583 channel->GetLoadFlags(&lf);
584 if (lf & nsIChannel::LOAD_TARGETED) {
585 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest));
586 if (httpChannel) {
587 uint32_t responseCode;
588 rv = httpChannel->GetResponseStatus(&responseCode);
589 if (NS_SUCCEEDED(rv)) {
590 //
591 // A valid server status indicates that a connection was
592 // established to the server... So, fire the notification
593 // even though a failure occurred later...
594 //
595 bFireTransferring = true;
596 }
597 }
598 }
599 }
600 }
601 }
602 }
604 if (bFireTransferring) {
605 // Send a STATE_TRANSFERRING notification for the request.
606 int32_t flags;
608 flags = nsIWebProgressListener::STATE_TRANSFERRING |
609 nsIWebProgressListener::STATE_IS_REQUEST;
610 //
611 // Move the WebProgress into the STATE_TRANSFERRING state if necessary...
612 //
613 if (mProgressStateFlags & nsIWebProgressListener::STATE_START) {
614 mProgressStateFlags = nsIWebProgressListener::STATE_TRANSFERRING;
616 // Send STATE_TRANSFERRING for the document too...
617 flags |= nsIWebProgressListener::STATE_IS_DOCUMENT;
618 }
620 FireOnStateChange(this, aRequest, flags, NS_OK);
621 }
623 //
624 // Fire the OnStateChange(...) notification for stop request
625 //
626 doStopURLLoad(aRequest, aStatus);
628 // Clear this request out of the hash to avoid bypass of FireOnStateChange
629 // when address of the request is reused.
630 RemoveRequestInfo(aRequest);
632 //
633 // Only fire the DocLoaderIsEmpty(...) if the document loader has initiated a
634 // load. This will handle removing the request from our hashtable as needed.
635 //
636 if (mIsLoadingDocument) {
637 DocLoaderIsEmpty(true);
638 }
640 return NS_OK;
641 }
644 nsresult nsDocLoader::RemoveChildLoader(nsDocLoader* aChild)
645 {
646 nsresult rv = mChildList.RemoveElement(aChild) ? NS_OK : NS_ERROR_FAILURE;
647 if (NS_SUCCEEDED(rv)) {
648 aChild->SetDocLoaderParent(nullptr);
649 }
650 return rv;
651 }
653 nsresult nsDocLoader::AddChildLoader(nsDocLoader* aChild)
654 {
655 nsresult rv = mChildList.AppendElement(aChild) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
656 if (NS_SUCCEEDED(rv)) {
657 aChild->SetDocLoaderParent(this);
658 }
659 return rv;
660 }
662 NS_IMETHODIMP nsDocLoader::GetDocumentChannel(nsIChannel ** aChannel)
663 {
664 if (!mDocumentRequest) {
665 *aChannel = nullptr;
666 return NS_OK;
667 }
669 return CallQueryInterface(mDocumentRequest, aChannel);
670 }
673 void nsDocLoader::DocLoaderIsEmpty(bool aFlushLayout)
674 {
675 if (mIsLoadingDocument) {
676 /* In the unimagineably rude circumstance that onload event handlers
677 triggered by this function actually kill the window ... ok, it's
678 not unimagineable; it's happened ... this deathgrip keeps this object
679 alive long enough to survive this function call. */
680 nsCOMPtr<nsIDocumentLoader> kungFuDeathGrip(this);
682 // Don't flush layout if we're still busy.
683 if (IsBusy()) {
684 return;
685 }
687 NS_ASSERTION(!mIsFlushingLayout, "Someone screwed up");
689 // The load group for this DocumentLoader is idle. Flush if we need to.
690 if (aFlushLayout && !mDontFlushLayout) {
691 nsCOMPtr<nsIDOMDocument> domDoc = do_GetInterface(GetAsSupports(this));
692 nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
693 if (doc) {
694 // We start loads from style resolution, so we need to flush out style
695 // no matter what. If we have user fonts, we also need to flush layout,
696 // since the reflow is what starts font loads.
697 mozFlushType flushType = Flush_Style;
698 nsIPresShell* shell = doc->GetShell();
699 if (shell) {
700 // Be safe in case this presshell is in teardown now
701 nsPresContext* presContext = shell->GetPresContext();
702 if (presContext && presContext->GetUserFontSet()) {
703 flushType = Flush_Layout;
704 }
705 }
706 mDontFlushLayout = mIsFlushingLayout = true;
707 doc->FlushPendingNotifications(flushType);
708 mDontFlushLayout = mIsFlushingLayout = false;
709 }
710 }
712 // And now check whether we're really busy; that might have changed with
713 // the layout flush.
714 if (!IsBusy()) {
715 // Clear out our request info hash, now that our load really is done and
716 // we don't need it anymore to CalculateMaxProgress().
717 ClearInternalProgress();
719 PR_LOG(gDocLoaderLog, PR_LOG_DEBUG,
720 ("DocLoader:%p: Is now idle...\n", this));
722 nsCOMPtr<nsIRequest> docRequest = mDocumentRequest;
724 NS_ASSERTION(mDocumentRequest, "No Document Request!");
725 mDocumentRequest = 0;
726 mIsLoadingDocument = false;
728 // Update the progress status state - the document is done
729 mProgressStateFlags = nsIWebProgressListener::STATE_STOP;
732 nsresult loadGroupStatus = NS_OK;
733 mLoadGroup->GetStatus(&loadGroupStatus);
735 //
736 // New code to break the circular reference between
737 // the load group and the docloader...
738 //
739 mLoadGroup->SetDefaultLoadRequest(nullptr);
741 // Take a ref to our parent now so that we can call DocLoaderIsEmpty() on
742 // it even if our onload handler removes us from the docloader tree.
743 nsRefPtr<nsDocLoader> parent = mParent;
745 // Note that if calling ChildEnteringOnload() on the parent returns false
746 // then calling our onload handler is not safe. That can only happen on
747 // OOM, so that's ok.
748 if (!parent || parent->ChildEnteringOnload(this)) {
749 // Do nothing with our state after firing the
750 // OnEndDocumentLoad(...). The document loader may be loading a *new*
751 // document - if LoadDocument() was called from a handler!
752 //
753 doStopDocumentLoad(docRequest, loadGroupStatus);
755 if (parent) {
756 parent->ChildDoneWithOnload(this);
757 }
758 }
759 }
760 }
761 }
763 void nsDocLoader::doStartDocumentLoad(void)
764 {
766 #if defined(DEBUG)
767 nsAutoCString buffer;
769 GetURIStringFromRequest(mDocumentRequest, buffer);
770 PR_LOG(gDocLoaderLog, PR_LOG_DEBUG,
771 ("DocLoader:%p: ++ Firing OnStateChange for start document load (...)."
772 "\tURI: %s \n",
773 this, buffer.get()));
774 #endif /* DEBUG */
776 // Fire an OnStatus(...) notification STATE_START. This indicates
777 // that the document represented by mDocumentRequest has started to
778 // load...
779 FireOnStateChange(this,
780 mDocumentRequest,
781 nsIWebProgressListener::STATE_START |
782 nsIWebProgressListener::STATE_IS_DOCUMENT |
783 nsIWebProgressListener::STATE_IS_REQUEST |
784 nsIWebProgressListener::STATE_IS_WINDOW |
785 nsIWebProgressListener::STATE_IS_NETWORK,
786 NS_OK);
787 }
789 void nsDocLoader::doStartURLLoad(nsIRequest *request)
790 {
791 #if defined(DEBUG)
792 nsAutoCString buffer;
794 GetURIStringFromRequest(request, buffer);
795 PR_LOG(gDocLoaderLog, PR_LOG_DEBUG,
796 ("DocLoader:%p: ++ Firing OnStateChange start url load (...)."
797 "\tURI: %s\n",
798 this, buffer.get()));
799 #endif /* DEBUG */
801 FireOnStateChange(this,
802 request,
803 nsIWebProgressListener::STATE_START |
804 nsIWebProgressListener::STATE_IS_REQUEST,
805 NS_OK);
806 }
808 void nsDocLoader::doStopURLLoad(nsIRequest *request, nsresult aStatus)
809 {
810 #if defined(DEBUG)
811 nsAutoCString buffer;
813 GetURIStringFromRequest(request, buffer);
814 PR_LOG(gDocLoaderLog, PR_LOG_DEBUG,
815 ("DocLoader:%p: ++ Firing OnStateChange for end url load (...)."
816 "\tURI: %s status=%x\n",
817 this, buffer.get(), aStatus));
818 #endif /* DEBUG */
820 FireOnStateChange(this,
821 request,
822 nsIWebProgressListener::STATE_STOP |
823 nsIWebProgressListener::STATE_IS_REQUEST,
824 aStatus);
826 // Fire a status change message for the most recent unfinished
827 // request to make sure that the displayed status is not outdated.
828 if (!mStatusInfoList.isEmpty()) {
829 nsStatusInfo* statusInfo = mStatusInfoList.getFirst();
830 FireOnStatusChange(this, statusInfo->mRequest,
831 statusInfo->mStatusCode,
832 statusInfo->mStatusMessage.get());
833 }
834 }
836 void nsDocLoader::doStopDocumentLoad(nsIRequest *request,
837 nsresult aStatus)
838 {
839 #if defined(DEBUG)
840 nsAutoCString buffer;
842 GetURIStringFromRequest(request, buffer);
843 PR_LOG(gDocLoaderLog, PR_LOG_DEBUG,
844 ("DocLoader:%p: ++ Firing OnStateChange for end document load (...)."
845 "\tURI: %s Status=%x\n",
846 this, buffer.get(), aStatus));
847 #endif /* DEBUG */
849 // Firing STATE_STOP|STATE_IS_DOCUMENT will fire onload handlers.
850 // Grab our parent chain before doing that so we can still dispatch
851 // STATE_STOP|STATE_IS_WINDW_STATE_IS_NETWORK to them all, even if
852 // the onload handlers rearrange the docshell tree.
853 WebProgressList list;
854 GatherAncestorWebProgresses(list);
856 //
857 // Fire an OnStateChange(...) notification indicating the the
858 // current document has finished loading...
859 //
860 int32_t flags = nsIWebProgressListener::STATE_STOP |
861 nsIWebProgressListener::STATE_IS_DOCUMENT;
862 for (uint32_t i = 0; i < list.Length(); ++i) {
863 list[i]->DoFireOnStateChange(this, request, flags, aStatus);
864 }
866 //
867 // Fire a final OnStateChange(...) notification indicating the the
868 // current document has finished loading...
869 //
870 flags = nsIWebProgressListener::STATE_STOP |
871 nsIWebProgressListener::STATE_IS_WINDOW |
872 nsIWebProgressListener::STATE_IS_NETWORK;
873 for (uint32_t i = 0; i < list.Length(); ++i) {
874 list[i]->DoFireOnStateChange(this, request, flags, aStatus);
875 }
876 }
878 ////////////////////////////////////////////////////////////////////////////////////
879 // The following section contains support for nsIWebProgress and related stuff
880 ////////////////////////////////////////////////////////////////////////////////////
882 NS_IMETHODIMP
883 nsDocLoader::AddProgressListener(nsIWebProgressListener *aListener,
884 uint32_t aNotifyMask)
885 {
886 nsresult rv;
888 nsListenerInfo* info = GetListenerInfo(aListener);
889 if (info) {
890 // The listener is already registered!
891 return NS_ERROR_FAILURE;
892 }
894 nsWeakPtr listener = do_GetWeakReference(aListener);
895 if (!listener) {
896 return NS_ERROR_INVALID_ARG;
897 }
899 info = new nsListenerInfo(listener, aNotifyMask);
900 if (!info) {
901 return NS_ERROR_OUT_OF_MEMORY;
902 }
904 rv = mListenerInfoList.AppendElement(info) ? NS_OK : NS_ERROR_FAILURE;
905 return rv;
906 }
908 NS_IMETHODIMP
909 nsDocLoader::RemoveProgressListener(nsIWebProgressListener *aListener)
910 {
911 nsresult rv;
913 nsListenerInfo* info = GetListenerInfo(aListener);
914 if (info) {
915 rv = mListenerInfoList.RemoveElement(info) ? NS_OK : NS_ERROR_FAILURE;
916 delete info;
917 } else {
918 // The listener is not registered!
919 rv = NS_ERROR_FAILURE;
920 }
921 return rv;
922 }
924 NS_IMETHODIMP
925 nsDocLoader::GetDOMWindow(nsIDOMWindow **aResult)
926 {
927 return CallGetInterface(this, aResult);
928 }
930 NS_IMETHODIMP
931 nsDocLoader::GetDOMWindowID(uint64_t *aResult)
932 {
933 *aResult = 0;
935 nsCOMPtr<nsIDOMWindow> window;
936 nsresult rv = GetDOMWindow(getter_AddRefs(window));
937 NS_ENSURE_SUCCESS(rv, rv);
939 nsCOMPtr<nsPIDOMWindow> piwindow = do_QueryInterface(window);
940 NS_ENSURE_STATE(piwindow);
942 MOZ_ASSERT(piwindow->IsOuterWindow());
943 *aResult = piwindow->WindowID();
944 return NS_OK;
945 }
947 NS_IMETHODIMP
948 nsDocLoader::GetIsTopLevel(bool *aResult)
949 {
950 *aResult = false;
952 nsCOMPtr<nsIDOMWindow> window;
953 GetDOMWindow(getter_AddRefs(window));
954 if (window) {
955 nsCOMPtr<nsPIDOMWindow> piwindow = do_QueryInterface(window);
956 NS_ENSURE_STATE(piwindow);
958 nsCOMPtr<nsIDOMWindow> topWindow;
959 nsresult rv = piwindow->GetTop(getter_AddRefs(topWindow));
960 NS_ENSURE_SUCCESS(rv, rv);
962 *aResult = piwindow == topWindow;
963 }
965 return NS_OK;
966 }
968 NS_IMETHODIMP
969 nsDocLoader::GetIsLoadingDocument(bool *aIsLoadingDocument)
970 {
971 *aIsLoadingDocument = mIsLoadingDocument;
973 return NS_OK;
974 }
976 NS_IMETHODIMP
977 nsDocLoader::GetLoadType(uint32_t *aLoadType)
978 {
979 *aLoadType = 0;
981 return NS_ERROR_NOT_IMPLEMENTED;
982 }
984 int64_t nsDocLoader::GetMaxTotalProgress()
985 {
986 int64_t newMaxTotal = 0;
988 uint32_t count = mChildList.Length();
989 nsCOMPtr<nsIWebProgress> webProgress;
990 for (uint32_t i=0; i < count; i++)
991 {
992 int64_t individualProgress = 0;
993 nsIDocumentLoader* docloader = ChildAt(i);
994 if (docloader)
995 {
996 // Cast is safe since all children are nsDocLoader too
997 individualProgress = ((nsDocLoader *) docloader)->GetMaxTotalProgress();
998 }
999 if (individualProgress < int64_t(0)) // if one of the elements doesn't know it's size
1000 // then none of them do
1001 {
1002 newMaxTotal = int64_t(-1);
1003 break;
1004 }
1005 else
1006 newMaxTotal += individualProgress;
1007 }
1009 int64_t progress = -1;
1010 if (mMaxSelfProgress >= int64_t(0) && newMaxTotal >= int64_t(0))
1011 progress = newMaxTotal + mMaxSelfProgress;
1013 return progress;
1014 }
1016 ////////////////////////////////////////////////////////////////////////////////////
1017 // The following section contains support for nsIProgressEventSink which is used to
1018 // pass progress and status between the actual request and the doc loader. The doc loader
1019 // then turns around and makes the right web progress calls based on this information.
1020 ////////////////////////////////////////////////////////////////////////////////////
1022 NS_IMETHODIMP nsDocLoader::OnProgress(nsIRequest *aRequest, nsISupports* ctxt,
1023 uint64_t aProgress, uint64_t aProgressMax)
1024 {
1025 int64_t progressDelta = 0;
1027 //
1028 // Update the RequestInfo entry with the new progress data
1029 //
1030 if (nsRequestInfo* info = GetRequestInfo(aRequest)) {
1031 // Update info->mCurrentProgress before we call FireOnStateChange,
1032 // since that can make the "info" pointer invalid.
1033 int64_t oldCurrentProgress = info->mCurrentProgress;
1034 progressDelta = int64_t(aProgress) - oldCurrentProgress;
1035 info->mCurrentProgress = int64_t(aProgress);
1037 // suppress sending STATE_TRANSFERRING if this is upload progress (see bug 240053)
1038 if (!info->mUploading && (int64_t(0) == oldCurrentProgress) && (int64_t(0) == info->mMaxProgress)) {
1039 //
1040 // If we receive an OnProgress event from a toplevel channel that the URI Loader
1041 // has not yet targeted, then we must suppress the event. This is necessary to
1042 // ensure that webprogresslisteners do not get confused when the channel is
1043 // finally targeted. See bug 257308.
1044 //
1045 nsLoadFlags lf = 0;
1046 aRequest->GetLoadFlags(&lf);
1047 if ((lf & nsIChannel::LOAD_DOCUMENT_URI) && !(lf & nsIChannel::LOAD_TARGETED)) {
1048 PR_LOG(gDocLoaderLog, PR_LOG_DEBUG,
1049 ("DocLoader:%p Ignoring OnProgress while load is not targeted\n", this));
1050 return NS_OK;
1051 }
1053 //
1054 // This is the first progress notification for the entry. If
1055 // (aMaxProgress > 0) then the content-length of the data is known,
1056 // so update mMaxSelfProgress... Otherwise, set it to -1 to indicate
1057 // that the content-length is no longer known.
1058 //
1059 if (uint64_t(aProgressMax) != UINT64_MAX) {
1060 mMaxSelfProgress += int64_t(aProgressMax);
1061 info->mMaxProgress = int64_t(aProgressMax);
1062 } else {
1063 mMaxSelfProgress = int64_t(-1);
1064 info->mMaxProgress = int64_t(-1);
1065 }
1067 // Send a STATE_TRANSFERRING notification for the request.
1068 int32_t flags;
1070 flags = nsIWebProgressListener::STATE_TRANSFERRING |
1071 nsIWebProgressListener::STATE_IS_REQUEST;
1072 //
1073 // Move the WebProgress into the STATE_TRANSFERRING state if necessary...
1074 //
1075 if (mProgressStateFlags & nsIWebProgressListener::STATE_START) {
1076 mProgressStateFlags = nsIWebProgressListener::STATE_TRANSFERRING;
1078 // Send STATE_TRANSFERRING for the document too...
1079 flags |= nsIWebProgressListener::STATE_IS_DOCUMENT;
1080 }
1082 FireOnStateChange(this, aRequest, flags, NS_OK);
1083 }
1085 // Update our overall current progress count.
1086 mCurrentSelfProgress += progressDelta;
1087 }
1088 //
1089 // The request is not part of the load group, so ignore its progress
1090 // information...
1091 //
1092 else {
1093 #if defined(DEBUG)
1094 nsAutoCString buffer;
1096 GetURIStringFromRequest(aRequest, buffer);
1097 PR_LOG(gDocLoaderLog, PR_LOG_DEBUG,
1098 ("DocLoader:%p OOPS - No Request Info for: %s\n",
1099 this, buffer.get()));
1100 #endif /* DEBUG */
1102 return NS_OK;
1103 }
1105 //
1106 // Fire progress notifications out to any registered nsIWebProgressListeners
1107 //
1108 FireOnProgressChange(this, aRequest, aProgress, aProgressMax, progressDelta,
1109 mCurrentTotalProgress, mMaxTotalProgress);
1111 return NS_OK;
1112 }
1114 NS_IMETHODIMP nsDocLoader::OnStatus(nsIRequest* aRequest, nsISupports* ctxt,
1115 nsresult aStatus, const char16_t* aStatusArg)
1116 {
1117 //
1118 // Fire progress notifications out to any registered nsIWebProgressListeners
1119 //
1120 if (aStatus != NS_OK) {
1121 // Remember the current status for this request
1122 nsRequestInfo *info;
1123 info = GetRequestInfo(aRequest);
1124 if (info) {
1125 bool uploading = (aStatus == NS_NET_STATUS_WRITING ||
1126 aStatus == NS_NET_STATUS_SENDING_TO);
1127 // If switching from uploading to downloading (or vice versa), then we
1128 // need to reset our progress counts. This is designed with HTTP form
1129 // submission in mind, where an upload is performed followed by download
1130 // of possibly several documents.
1131 if (info->mUploading != uploading) {
1132 mCurrentSelfProgress = mMaxSelfProgress = 0;
1133 mCurrentTotalProgress = mMaxTotalProgress = 0;
1134 mCompletedTotalProgress = 0;
1135 info->mUploading = uploading;
1136 info->mCurrentProgress = 0;
1137 info->mMaxProgress = 0;
1138 }
1139 }
1141 nsCOMPtr<nsIStringBundleService> sbs =
1142 mozilla::services::GetStringBundleService();
1143 if (!sbs)
1144 return NS_ERROR_FAILURE;
1145 nsXPIDLString msg;
1146 nsresult rv = sbs->FormatStatusMessage(aStatus, aStatusArg,
1147 getter_Copies(msg));
1148 if (NS_FAILED(rv))
1149 return rv;
1151 // Keep around the message. In case a request finishes, we need to make sure
1152 // to send the status message of another request to our user to that we
1153 // don't display, for example, "Transferring" messages for requests that are
1154 // already done.
1155 if (info) {
1156 if (!info->mLastStatus) {
1157 info->mLastStatus = new nsStatusInfo(aRequest);
1158 } else {
1159 // We're going to move it to the front of the list, so remove
1160 // it from wherever it is now.
1161 info->mLastStatus->remove();
1162 }
1163 info->mLastStatus->mStatusMessage = msg;
1164 info->mLastStatus->mStatusCode = aStatus;
1165 // Put the info at the front of the list
1166 mStatusInfoList.insertFront(info->mLastStatus);
1167 }
1168 FireOnStatusChange(this, aRequest, aStatus, msg);
1169 }
1170 return NS_OK;
1171 }
1173 void nsDocLoader::ClearInternalProgress()
1174 {
1175 ClearRequestInfoHash();
1177 mCurrentSelfProgress = mMaxSelfProgress = 0;
1178 mCurrentTotalProgress = mMaxTotalProgress = 0;
1179 mCompletedTotalProgress = 0;
1181 mProgressStateFlags = nsIWebProgressListener::STATE_STOP;
1182 }
1185 void nsDocLoader::FireOnProgressChange(nsDocLoader *aLoadInitiator,
1186 nsIRequest *request,
1187 int64_t aProgress,
1188 int64_t aProgressMax,
1189 int64_t aProgressDelta,
1190 int64_t aTotalProgress,
1191 int64_t aMaxTotalProgress)
1192 {
1193 if (mIsLoadingDocument) {
1194 mCurrentTotalProgress += aProgressDelta;
1195 mMaxTotalProgress = GetMaxTotalProgress();
1197 aTotalProgress = mCurrentTotalProgress;
1198 aMaxTotalProgress = mMaxTotalProgress;
1199 }
1201 #if defined(DEBUG)
1202 nsAutoCString buffer;
1204 GetURIStringFromRequest(request, buffer);
1205 PR_LOG(gDocLoaderLog, PR_LOG_DEBUG,
1206 ("DocLoader:%p: Progress (%s): curSelf: %d maxSelf: %d curTotal: %d maxTotal %d\n",
1207 this, buffer.get(), aProgress, aProgressMax, aTotalProgress, aMaxTotalProgress));
1208 #endif /* DEBUG */
1210 /*
1211 * First notify any listeners of the new progress info...
1212 *
1213 * Operate the elements from back to front so that if items get
1214 * get removed from the list it won't affect our iteration
1215 */
1216 nsCOMPtr<nsIWebProgressListener> listener;
1217 int32_t count = mListenerInfoList.Count();
1219 while (--count >= 0) {
1220 nsListenerInfo *info;
1222 info = static_cast<nsListenerInfo*>(mListenerInfoList.SafeElementAt(count));
1223 if (!info || !(info->mNotifyMask & nsIWebProgress::NOTIFY_PROGRESS)) {
1224 continue;
1225 }
1227 listener = do_QueryReferent(info->mWeakListener);
1228 if (!listener) {
1229 // the listener went away. gracefully pull it out of the list.
1230 mListenerInfoList.RemoveElementAt(count);
1231 delete info;
1232 continue;
1233 }
1235 // XXX truncates 64-bit to 32-bit
1236 listener->OnProgressChange(aLoadInitiator,request,
1237 int32_t(aProgress), int32_t(aProgressMax),
1238 int32_t(aTotalProgress), int32_t(aMaxTotalProgress));
1239 }
1241 mListenerInfoList.Compact();
1243 // Pass the notification up to the parent...
1244 if (mParent) {
1245 mParent->FireOnProgressChange(aLoadInitiator, request,
1246 aProgress, aProgressMax,
1247 aProgressDelta,
1248 aTotalProgress, aMaxTotalProgress);
1249 }
1250 }
1252 void nsDocLoader::GatherAncestorWebProgresses(WebProgressList& aList)
1253 {
1254 for (nsDocLoader* loader = this; loader; loader = loader->mParent) {
1255 aList.AppendElement(loader);
1256 }
1257 }
1259 void nsDocLoader::FireOnStateChange(nsIWebProgress *aProgress,
1260 nsIRequest *aRequest,
1261 int32_t aStateFlags,
1262 nsresult aStatus)
1263 {
1264 WebProgressList list;
1265 GatherAncestorWebProgresses(list);
1266 for (uint32_t i = 0; i < list.Length(); ++i) {
1267 list[i]->DoFireOnStateChange(aProgress, aRequest, aStateFlags, aStatus);
1268 }
1269 }
1271 void nsDocLoader::DoFireOnStateChange(nsIWebProgress * const aProgress,
1272 nsIRequest * const aRequest,
1273 int32_t &aStateFlags,
1274 const nsresult aStatus)
1275 {
1276 //
1277 // Remove the STATE_IS_NETWORK bit if necessary.
1278 //
1279 // The rule is to remove this bit, if the notification has been passed
1280 // up from a child WebProgress, and the current WebProgress is already
1281 // active...
1282 //
1283 if (mIsLoadingDocument &&
1284 (aStateFlags & nsIWebProgressListener::STATE_IS_NETWORK) &&
1285 (this != aProgress)) {
1286 aStateFlags &= ~nsIWebProgressListener::STATE_IS_NETWORK;
1287 }
1289 // Add the STATE_RESTORING bit if necessary.
1290 if (mIsRestoringDocument)
1291 aStateFlags |= nsIWebProgressListener::STATE_RESTORING;
1293 #if defined(DEBUG)
1294 nsAutoCString buffer;
1296 GetURIStringFromRequest(aRequest, buffer);
1297 PR_LOG(gDocLoaderLog, PR_LOG_DEBUG,
1298 ("DocLoader:%p: Status (%s): code: %x\n",
1299 this, buffer.get(), aStateFlags));
1300 #endif /* DEBUG */
1302 NS_ASSERTION(aRequest, "Firing OnStateChange(...) notification with a NULL request!");
1304 /*
1305 * First notify any listeners of the new state info...
1306 *
1307 * Operate the elements from back to front so that if items get
1308 * get removed from the list it won't affect our iteration
1309 */
1310 nsCOMPtr<nsIWebProgressListener> listener;
1311 int32_t count = mListenerInfoList.Count();
1312 int32_t notifyMask = (aStateFlags >> 16) & nsIWebProgress::NOTIFY_STATE_ALL;
1314 while (--count >= 0) {
1315 nsListenerInfo *info;
1317 info = static_cast<nsListenerInfo*>(mListenerInfoList.SafeElementAt(count));
1318 if (!info || !(info->mNotifyMask & notifyMask)) {
1319 continue;
1320 }
1322 listener = do_QueryReferent(info->mWeakListener);
1323 if (!listener) {
1324 // the listener went away. gracefully pull it out of the list.
1325 mListenerInfoList.RemoveElementAt(count);
1326 delete info;
1327 continue;
1328 }
1330 listener->OnStateChange(aProgress, aRequest, aStateFlags, aStatus);
1331 }
1333 mListenerInfoList.Compact();
1334 }
1338 void
1339 nsDocLoader::FireOnLocationChange(nsIWebProgress* aWebProgress,
1340 nsIRequest* aRequest,
1341 nsIURI *aUri,
1342 uint32_t aFlags)
1343 {
1344 /*
1345 * First notify any listeners of the new state info...
1346 *
1347 * Operate the elements from back to front so that if items get
1348 * get removed from the list it won't affect our iteration
1349 */
1350 nsCOMPtr<nsIWebProgressListener> listener;
1351 int32_t count = mListenerInfoList.Count();
1353 while (--count >= 0) {
1354 nsListenerInfo *info;
1356 info = static_cast<nsListenerInfo*>(mListenerInfoList.SafeElementAt(count));
1357 if (!info || !(info->mNotifyMask & nsIWebProgress::NOTIFY_LOCATION)) {
1358 continue;
1359 }
1361 listener = do_QueryReferent(info->mWeakListener);
1362 if (!listener) {
1363 // the listener went away. gracefully pull it out of the list.
1364 mListenerInfoList.RemoveElementAt(count);
1365 delete info;
1366 continue;
1367 }
1369 PR_LOG(gDocLoaderLog, PR_LOG_DEBUG, ("DocLoader [%p] calling %p->OnLocationChange", this, listener.get()));
1370 listener->OnLocationChange(aWebProgress, aRequest, aUri, aFlags);
1371 }
1373 mListenerInfoList.Compact();
1375 // Pass the notification up to the parent...
1376 if (mParent) {
1377 mParent->FireOnLocationChange(aWebProgress, aRequest, aUri, aFlags);
1378 }
1379 }
1381 void
1382 nsDocLoader::FireOnStatusChange(nsIWebProgress* aWebProgress,
1383 nsIRequest* aRequest,
1384 nsresult aStatus,
1385 const char16_t* aMessage)
1386 {
1387 /*
1388 * First notify any listeners of the new state info...
1389 *
1390 * Operate the elements from back to front so that if items get
1391 * get removed from the list it won't affect our iteration
1392 */
1393 nsCOMPtr<nsIWebProgressListener> listener;
1394 int32_t count = mListenerInfoList.Count();
1396 while (--count >= 0) {
1397 nsListenerInfo *info;
1399 info = static_cast<nsListenerInfo*>(mListenerInfoList.SafeElementAt(count));
1400 if (!info || !(info->mNotifyMask & nsIWebProgress::NOTIFY_STATUS)) {
1401 continue;
1402 }
1404 listener = do_QueryReferent(info->mWeakListener);
1405 if (!listener) {
1406 // the listener went away. gracefully pull it out of the list.
1407 mListenerInfoList.RemoveElementAt(count);
1408 delete info;
1409 continue;
1410 }
1412 listener->OnStatusChange(aWebProgress, aRequest, aStatus, aMessage);
1413 }
1414 mListenerInfoList.Compact();
1416 // Pass the notification up to the parent...
1417 if (mParent) {
1418 mParent->FireOnStatusChange(aWebProgress, aRequest, aStatus, aMessage);
1419 }
1420 }
1422 bool
1423 nsDocLoader::RefreshAttempted(nsIWebProgress* aWebProgress,
1424 nsIURI *aURI,
1425 int32_t aDelay,
1426 bool aSameURI)
1427 {
1428 /*
1429 * Returns true if the refresh may proceed,
1430 * false if the refresh should be blocked.
1431 *
1432 * First notify any listeners of the refresh attempt...
1433 *
1434 * Iterate the elements from back to front so that if items
1435 * get removed from the list it won't affect our iteration
1436 */
1437 bool allowRefresh = true;
1438 int32_t count = mListenerInfoList.Count();
1440 while (--count >= 0) {
1441 nsListenerInfo *info;
1443 info = static_cast<nsListenerInfo*>(mListenerInfoList.SafeElementAt(count));
1444 if (!info || !(info->mNotifyMask & nsIWebProgress::NOTIFY_REFRESH)) {
1445 continue;
1446 }
1448 nsCOMPtr<nsIWebProgressListener> listener =
1449 do_QueryReferent(info->mWeakListener);
1450 if (!listener) {
1451 // the listener went away. gracefully pull it out of the list.
1452 mListenerInfoList.RemoveElementAt(count);
1453 delete info;
1454 continue;
1455 }
1457 nsCOMPtr<nsIWebProgressListener2> listener2 =
1458 do_QueryReferent(info->mWeakListener);
1459 if (!listener2)
1460 continue;
1462 bool listenerAllowedRefresh;
1463 nsresult listenerRV = listener2->OnRefreshAttempted(
1464 aWebProgress, aURI, aDelay, aSameURI, &listenerAllowedRefresh);
1465 if (NS_FAILED(listenerRV))
1466 continue;
1468 allowRefresh = allowRefresh && listenerAllowedRefresh;
1469 }
1471 mListenerInfoList.Compact();
1473 // Pass the notification up to the parent...
1474 if (mParent) {
1475 allowRefresh = allowRefresh &&
1476 mParent->RefreshAttempted(aWebProgress, aURI, aDelay, aSameURI);
1477 }
1479 return allowRefresh;
1480 }
1482 nsListenerInfo *
1483 nsDocLoader::GetListenerInfo(nsIWebProgressListener *aListener)
1484 {
1485 int32_t i, count;
1486 nsListenerInfo *info;
1488 nsCOMPtr<nsISupports> listener1 = do_QueryInterface(aListener);
1489 count = mListenerInfoList.Count();
1490 for (i=0; i<count; i++) {
1491 info = static_cast<nsListenerInfo*>(mListenerInfoList.SafeElementAt(i));
1493 NS_ASSERTION(info, "There should NEVER be a null listener in the list");
1494 if (info) {
1495 nsCOMPtr<nsISupports> listener2 = do_QueryReferent(info->mWeakListener);
1496 if (listener1 == listener2)
1497 return info;
1498 }
1499 }
1500 return nullptr;
1501 }
1503 nsresult nsDocLoader::AddRequestInfo(nsIRequest *aRequest)
1504 {
1505 if (!PL_DHashTableOperate(&mRequestInfoHash, aRequest, PL_DHASH_ADD)) {
1506 return NS_ERROR_OUT_OF_MEMORY;
1507 }
1509 return NS_OK;
1510 }
1512 void nsDocLoader::RemoveRequestInfo(nsIRequest *aRequest)
1513 {
1514 PL_DHashTableOperate(&mRequestInfoHash, aRequest, PL_DHASH_REMOVE);
1515 }
1517 nsDocLoader::nsRequestInfo* nsDocLoader::GetRequestInfo(nsIRequest* aRequest)
1518 {
1519 nsRequestInfo* info =
1520 static_cast<nsRequestInfo*>
1521 (PL_DHashTableOperate(&mRequestInfoHash, aRequest,
1522 PL_DHASH_LOOKUP));
1524 if (PL_DHASH_ENTRY_IS_FREE(info)) {
1525 // Nothing found in the hash, return null.
1527 return nullptr;
1528 }
1530 // Return what we found in the hash...
1532 return info;
1533 }
1535 // PLDHashTable enumeration callback that just removes every entry
1536 // from the hash.
1537 static PLDHashOperator
1538 RemoveInfoCallback(PLDHashTable *table, PLDHashEntryHdr *hdr, uint32_t number,
1539 void *arg)
1540 {
1541 return PL_DHASH_REMOVE;
1542 }
1544 void nsDocLoader::ClearRequestInfoHash(void)
1545 {
1546 if (!mRequestInfoHash.ops || !mRequestInfoHash.entryCount) {
1547 // No hash, or the hash is empty, nothing to do here then...
1549 return;
1550 }
1552 PL_DHashTableEnumerate(&mRequestInfoHash, RemoveInfoCallback, nullptr);
1553 }
1555 // PLDHashTable enumeration callback that calculates the max progress.
1556 PLDHashOperator
1557 nsDocLoader::CalcMaxProgressCallback(PLDHashTable* table, PLDHashEntryHdr* hdr,
1558 uint32_t number, void* arg)
1559 {
1560 const nsRequestInfo* info = static_cast<const nsRequestInfo*>(hdr);
1561 int64_t* max = static_cast<int64_t* >(arg);
1563 if (info->mMaxProgress < info->mCurrentProgress) {
1564 *max = int64_t(-1);
1566 return PL_DHASH_STOP;
1567 }
1569 *max += info->mMaxProgress;
1571 return PL_DHASH_NEXT;
1572 }
1574 int64_t nsDocLoader::CalculateMaxProgress()
1575 {
1576 int64_t max = mCompletedTotalProgress;
1577 PL_DHashTableEnumerate(&mRequestInfoHash, CalcMaxProgressCallback, &max);
1578 return max;
1579 }
1581 NS_IMETHODIMP nsDocLoader::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
1582 nsIChannel *aNewChannel,
1583 uint32_t aFlags,
1584 nsIAsyncVerifyRedirectCallback *cb)
1585 {
1586 if (aOldChannel)
1587 {
1588 nsLoadFlags loadFlags = 0;
1589 int32_t stateFlags = nsIWebProgressListener::STATE_REDIRECTING |
1590 nsIWebProgressListener::STATE_IS_REQUEST;
1592 aOldChannel->GetLoadFlags(&loadFlags);
1593 // If the document channel is being redirected, then indicate that the
1594 // document is being redirected in the notification...
1595 if (loadFlags & nsIChannel::LOAD_DOCUMENT_URI)
1596 {
1597 stateFlags |= nsIWebProgressListener::STATE_IS_DOCUMENT;
1599 #if defined(DEBUG)
1600 nsCOMPtr<nsIRequest> request(do_QueryInterface(aOldChannel));
1601 NS_ASSERTION(request == mDocumentRequest, "Wrong Document Channel");
1602 #endif /* DEBUG */
1603 }
1605 OnRedirectStateChange(aOldChannel, aNewChannel, aFlags, stateFlags);
1606 FireOnStateChange(this, aOldChannel, stateFlags, NS_OK);
1607 }
1609 cb->OnRedirectVerifyCallback(NS_OK);
1610 return NS_OK;
1611 }
1613 /*
1614 * Implementation of nsISecurityEventSink method...
1615 */
1617 NS_IMETHODIMP nsDocLoader::OnSecurityChange(nsISupports * aContext,
1618 uint32_t aState)
1619 {
1620 //
1621 // Fire progress notifications out to any registered nsIWebProgressListeners.
1622 //
1624 nsCOMPtr<nsIRequest> request = do_QueryInterface(aContext);
1625 nsIWebProgress* webProgress = static_cast<nsIWebProgress*>(this);
1627 /*
1628 * First notify any listeners of the new state info...
1629 *
1630 * Operate the elements from back to front so that if items get
1631 * get removed from the list it won't affect our iteration
1632 */
1633 nsCOMPtr<nsIWebProgressListener> listener;
1634 int32_t count = mListenerInfoList.Count();
1636 while (--count >= 0) {
1637 nsListenerInfo *info;
1639 info = static_cast<nsListenerInfo*>(mListenerInfoList.SafeElementAt(count));
1640 if (!info || !(info->mNotifyMask & nsIWebProgress::NOTIFY_SECURITY)) {
1641 continue;
1642 }
1644 listener = do_QueryReferent(info->mWeakListener);
1645 if (!listener) {
1646 // the listener went away. gracefully pull it out of the list.
1647 mListenerInfoList.RemoveElementAt(count);
1648 delete info;
1649 continue;
1650 }
1652 listener->OnSecurityChange(webProgress, request, aState);
1653 }
1655 mListenerInfoList.Compact();
1657 // Pass the notification up to the parent...
1658 if (mParent) {
1659 mParent->OnSecurityChange(aContext, aState);
1660 }
1661 return NS_OK;
1662 }
1664 /*
1665 * Implementation of nsISupportsPriority methods...
1666 *
1667 * The priority of the DocLoader _is_ the priority of its LoadGroup.
1668 *
1669 * XXX(darin): Once we start storing loadgroups in loadgroups, this code will
1670 * go away.
1671 */
1673 NS_IMETHODIMP nsDocLoader::GetPriority(int32_t *aPriority)
1674 {
1675 nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mLoadGroup);
1676 if (p)
1677 return p->GetPriority(aPriority);
1679 *aPriority = 0;
1680 return NS_OK;
1681 }
1683 NS_IMETHODIMP nsDocLoader::SetPriority(int32_t aPriority)
1684 {
1685 PR_LOG(gDocLoaderLog, PR_LOG_DEBUG,
1686 ("DocLoader:%p: SetPriority(%d) called\n", this, aPriority));
1688 nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mLoadGroup);
1689 if (p)
1690 p->SetPriority(aPriority);
1692 NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mChildList, nsDocLoader,
1693 SetPriority, (aPriority));
1695 return NS_OK;
1696 }
1698 NS_IMETHODIMP nsDocLoader::AdjustPriority(int32_t aDelta)
1699 {
1700 PR_LOG(gDocLoaderLog, PR_LOG_DEBUG,
1701 ("DocLoader:%p: AdjustPriority(%d) called\n", this, aDelta));
1703 nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mLoadGroup);
1704 if (p)
1705 p->AdjustPriority(aDelta);
1707 NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mChildList, nsDocLoader,
1708 AdjustPriority, (aDelta));
1710 return NS_OK;
1711 }
1716 #if 0
1717 void nsDocLoader::DumpChannelInfo()
1718 {
1719 nsChannelInfo *info;
1720 int32_t i, count;
1721 int32_t current=0, max=0;
1724 printf("==== DocLoader=%x\n", this);
1726 count = mChannelInfoList.Count();
1727 for(i=0; i<count; i++) {
1728 info = (nsChannelInfo *)mChannelInfoList.ElementAt(i);
1730 #if defined(DEBUG)
1731 nsAutoCString buffer;
1732 nsresult rv = NS_OK;
1733 if (info->mURI) {
1734 rv = info->mURI->GetSpec(buffer);
1735 }
1737 printf(" [%d] current=%d max=%d [%s]\n", i,
1738 info->mCurrentProgress,
1739 info->mMaxProgress, buffer.get());
1740 #endif /* DEBUG */
1742 current += info->mCurrentProgress;
1743 if (max >= 0) {
1744 if (info->mMaxProgress < info->mCurrentProgress) {
1745 max = -1;
1746 } else {
1747 max += info->mMaxProgress;
1748 }
1749 }
1750 }
1752 printf("\nCurrent=%d Total=%d\n====\n", current, max);
1753 }
1754 #endif /* 0 */