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/. */
5 
6 #include "nspr.h"
7 #include "mozilla/dom/BrowserChild.h"
8 #include "mozilla/dom/Document.h"
9 #include "mozilla/BasicEvents.h"
10 #include "mozilla/Components.h"
11 #include "mozilla/EventDispatcher.h"
12 #include "mozilla/Logging.h"
13 #include "mozilla/IntegerPrintfMacros.h"
14 #include "mozilla/PresShell.h"
15 
16 #include "nsDocLoader.h"
17 #include "nsDocShell.h"
18 #include "nsLoadGroup.h"
19 #include "nsNetUtil.h"
20 #include "nsIHttpChannel.h"
21 #include "nsIWebNavigation.h"
22 #include "nsIWebProgressListener2.h"
23 
24 #include "nsString.h"
25 
26 #include "nsCOMPtr.h"
27 #include "nscore.h"
28 #include "nsIWeakReferenceUtils.h"
29 #include "nsQueryObject.h"
30 
31 #include "nsPIDOMWindow.h"
32 #include "nsGlobalWindow.h"
33 
34 #include "nsIStringBundle.h"
35 
36 #include "nsIDocShell.h"
37 #include "mozilla/dom/Document.h"
38 #include "mozilla/dom/DocGroup.h"
39 #include "nsPresContext.h"
40 #include "nsIAsyncVerifyRedirectCallback.h"
41 #include "nsIBrowserDOMWindow.h"
42 #include "nsGlobalWindow.h"
43 #include "mozilla/ThrottledEventQueue.h"
44 using namespace mozilla;
45 using mozilla::DebugOnly;
46 using mozilla::eLoad;
47 using mozilla::EventDispatcher;
48 using mozilla::LogLevel;
49 using mozilla::WidgetEvent;
50 using mozilla::dom::BrowserChild;
51 using mozilla::dom::BrowsingContext;
52 using mozilla::dom::Document;
53 
54 //
55 // Log module for nsIDocumentLoader logging...
56 //
57 // To enable logging (see mozilla/Logging.h for full details):
58 //
59 //    set MOZ_LOG=DocLoader:5
60 //    set MOZ_LOG_FILE=debug.log
61 //
62 // this enables LogLevel::Debug level information and places all output in
63 // the file 'debug.log'.
64 //
65 mozilla::LazyLogModule gDocLoaderLog("DocLoader");
66 
67 #if defined(DEBUG)
GetURIStringFromRequest(nsIRequest * request,nsACString & name)68 void GetURIStringFromRequest(nsIRequest* request, nsACString& name) {
69   if (request)
70     request->GetName(name);
71   else
72     name.AssignLiteral("???");
73 }
74 #endif /* DEBUG */
75 
RequestInfoHashInitEntry(PLDHashEntryHdr * entry,const void * key)76 void nsDocLoader::RequestInfoHashInitEntry(PLDHashEntryHdr* entry,
77                                            const void* key) {
78   // Initialize the entry with placement new
79   new (entry) nsRequestInfo(key);
80 }
81 
RequestInfoHashClearEntry(PLDHashTable * table,PLDHashEntryHdr * entry)82 void nsDocLoader::RequestInfoHashClearEntry(PLDHashTable* table,
83                                             PLDHashEntryHdr* entry) {
84   nsRequestInfo* info = static_cast<nsRequestInfo*>(entry);
85   info->~nsRequestInfo();
86 }
87 
88 // this is used for mListenerInfoList.Contains()
89 template <>
90 class nsDefaultComparator<nsDocLoader::nsListenerInfo,
91                           nsIWebProgressListener*> {
92  public:
Equals(const nsDocLoader::nsListenerInfo & aInfo,nsIWebProgressListener * const & aListener) const93   bool Equals(const nsDocLoader::nsListenerInfo& aInfo,
94               nsIWebProgressListener* const& aListener) const {
95     nsCOMPtr<nsIWebProgressListener> listener =
96         do_QueryReferent(aInfo.mWeakListener);
97     return aListener == listener;
98   }
99 };
100 
101 /* static */ const PLDHashTableOps nsDocLoader::sRequestInfoHashOps = {
102     PLDHashTable::HashVoidPtrKeyStub, PLDHashTable::MatchEntryStub,
103     PLDHashTable::MoveEntryStub, nsDocLoader::RequestInfoHashClearEntry,
104     nsDocLoader::RequestInfoHashInitEntry};
105 
nsDocLoader(bool aNotifyAboutBackgroundRequests)106 nsDocLoader::nsDocLoader(bool aNotifyAboutBackgroundRequests)
107     : mParent(nullptr),
108       mProgressStateFlags(0),
109       mCurrentSelfProgress(0),
110       mMaxSelfProgress(0),
111       mCurrentTotalProgress(0),
112       mMaxTotalProgress(0),
113       mRequestInfoHash(&sRequestInfoHashOps, sizeof(nsRequestInfo)),
114       mCompletedTotalProgress(0),
115       mIsLoadingDocument(false),
116       mIsRestoringDocument(false),
117       mDontFlushLayout(false),
118       mIsFlushingLayout(false),
119       mTreatAsBackgroundLoad(false),
120       mHasFakeOnLoadDispatched(false),
121       mIsReadyToHandlePostMessage(false),
122       mDocumentOpenedButNotLoaded(false),
123       mNotifyAboutBackgroundRequests(aNotifyAboutBackgroundRequests) {
124   ClearInternalProgress();
125 
126   MOZ_LOG(gDocLoaderLog, LogLevel::Debug, ("DocLoader:%p: created.\n", this));
127 }
128 
SetDocLoaderParent(nsDocLoader * aParent)129 nsresult nsDocLoader::SetDocLoaderParent(nsDocLoader* aParent) {
130   mParent = aParent;
131   return NS_OK;
132 }
133 
Init()134 nsresult nsDocLoader::Init() {
135   RefPtr<net::nsLoadGroup> loadGroup = new net::nsLoadGroup();
136   nsresult rv = loadGroup->Init();
137   if (NS_FAILED(rv)) return rv;
138 
139   loadGroup->SetGroupObserver(this, mNotifyAboutBackgroundRequests);
140 
141   mLoadGroup = loadGroup;
142 
143   MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
144           ("DocLoader:%p: load group %p.\n", this, mLoadGroup.get()));
145 
146   return NS_OK;
147 }
148 
InitWithBrowsingContext(BrowsingContext * aBrowsingContext)149 nsresult nsDocLoader::InitWithBrowsingContext(
150     BrowsingContext* aBrowsingContext) {
151   RefPtr<net::nsLoadGroup> loadGroup = new net::nsLoadGroup();
152   if (!aBrowsingContext->GetRequestContextId()) {
153     return NS_ERROR_NOT_AVAILABLE;
154   }
155   nsresult rv = loadGroup->InitWithRequestContextId(
156       aBrowsingContext->GetRequestContextId());
157   if (NS_FAILED(rv)) return rv;
158 
159   loadGroup->SetGroupObserver(this, mNotifyAboutBackgroundRequests);
160 
161   mLoadGroup = loadGroup;
162 
163   MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
164           ("DocLoader:%p: load group %p.\n", this, mLoadGroup.get()));
165 
166   return NS_OK;
167 }
168 
~nsDocLoader()169 nsDocLoader::~nsDocLoader() {
170   /*
171           |ClearWeakReferences()| here is intended to prevent people holding
172      weak references from re-entering this destructor since |QueryReferent()|
173      will |AddRef()| me, and the subsequent |Release()| will try to destroy me.
174      At this point there should be only weak references remaining (otherwise, we
175      wouldn't be getting destroyed).
176 
177           An alternative would be incrementing our refcount (consider it a
178      compressed flag saying "Don't re-destroy.").  I haven't yet decided which
179      is better. [scc]
180   */
181   // XXXbz now that NS_IMPL_RELEASE stabilizes by setting refcount to 1, is
182   // this needed?
183   ClearWeakReferences();
184 
185   Destroy();
186 
187   MOZ_LOG(gDocLoaderLog, LogLevel::Debug, ("DocLoader:%p: deleted.\n", this));
188 }
189 
190 /*
191  * Implementation of ISupports methods...
192  */
193 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDocLoader)
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDocLoader)194 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDocLoader)
195 
196 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDocLoader)
197   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDocumentLoader)
198   NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
199   NS_INTERFACE_MAP_ENTRY(nsIDocumentLoader)
200   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
201   NS_INTERFACE_MAP_ENTRY(nsIWebProgress)
202   NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
203   NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
204   NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
205   NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
206   NS_INTERFACE_MAP_ENTRY_CONCRETE(nsDocLoader)
207 NS_INTERFACE_MAP_END
208 
209 NS_IMPL_CYCLE_COLLECTION_WEAK(nsDocLoader, mChildrenInOnload)
210 
211 /*
212  * Implementation of nsIInterfaceRequestor methods...
213  */
214 NS_IMETHODIMP nsDocLoader::GetInterface(const nsIID& aIID, void** aSink) {
215   nsresult rv = NS_ERROR_NO_INTERFACE;
216 
217   NS_ENSURE_ARG_POINTER(aSink);
218 
219   if (aIID.Equals(NS_GET_IID(nsILoadGroup))) {
220     *aSink = mLoadGroup;
221     NS_IF_ADDREF((nsISupports*)*aSink);
222     rv = NS_OK;
223   } else {
224     rv = QueryInterface(aIID, aSink);
225   }
226 
227   return rv;
228 }
229 
230 /* static */
GetAsDocLoader(nsISupports * aSupports)231 already_AddRefed<nsDocLoader> nsDocLoader::GetAsDocLoader(
232     nsISupports* aSupports) {
233   RefPtr<nsDocLoader> ret = do_QueryObject(aSupports);
234   return ret.forget();
235 }
236 
237 /* static */
AddDocLoaderAsChildOfRoot(nsDocLoader * aDocLoader)238 nsresult nsDocLoader::AddDocLoaderAsChildOfRoot(nsDocLoader* aDocLoader) {
239   nsCOMPtr<nsIDocumentLoader> docLoaderService =
240       components::DocLoader::Service();
241   NS_ENSURE_TRUE(docLoaderService, NS_ERROR_UNEXPECTED);
242 
243   RefPtr<nsDocLoader> rootDocLoader = GetAsDocLoader(docLoaderService);
244   NS_ENSURE_TRUE(rootDocLoader, NS_ERROR_UNEXPECTED);
245 
246   return rootDocLoader->AddChildLoader(aDocLoader);
247 }
248 
249 NS_IMETHODIMP
Stop(void)250 nsDocLoader::Stop(void) {
251   nsresult rv = NS_OK;
252 
253   MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
254           ("DocLoader:%p: Stop() called\n", this));
255 
256   NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mChildList, Stop, ());
257 
258   if (mLoadGroup) rv = mLoadGroup->Cancel(NS_BINDING_ABORTED);
259 
260   // Don't report that we're flushing layout so IsBusy returns false after a
261   // Stop call.
262   mIsFlushingLayout = false;
263 
264   // Clear out mChildrenInOnload.  We're not going to fire our onload
265   // anyway at this point, and there's no issue with mChildrenInOnload
266   // after this, since mDocumentRequest will be null after the
267   // DocLoaderIsEmpty() call.
268   mChildrenInOnload.Clear();
269   mOOPChildrenLoading.Clear();
270 
271   // Make sure to call DocLoaderIsEmpty now so that we reset mDocumentRequest,
272   // etc, as needed.  We could be getting into here from a subframe onload, in
273   // which case the call to DocLoaderIsEmpty() is coming but hasn't quite
274   // happened yet, Canceling the loadgroup did nothing (because it was already
275   // empty), and we're about to start a new load (which is what triggered this
276   // Stop() call).
277 
278   // XXXbz If the child frame loadgroups were requests in mLoadgroup, I suspect
279   // we wouldn't need the call here....
280 
281   NS_ASSERTION(!IsBusy(), "Shouldn't be busy here");
282 
283   // If Cancelling the load group only had pending subresource requests, then
284   // the group status will still be success, and we would fire the load event.
285   // We want to avoid that when we're aborting the load, so override the status
286   // with an explicit NS_BINDING_ABORTED value.
287   DocLoaderIsEmpty(false, Some(NS_BINDING_ABORTED));
288 
289   return rv;
290 }
291 
TreatAsBackgroundLoad()292 bool nsDocLoader::TreatAsBackgroundLoad() { return mTreatAsBackgroundLoad; }
293 
SetBackgroundLoadIframe()294 void nsDocLoader::SetBackgroundLoadIframe() { mTreatAsBackgroundLoad = true; }
295 
IsBusy()296 bool nsDocLoader::IsBusy() {
297   nsresult rv;
298 
299   //
300   // A document loader is busy if either:
301   //
302   //   1. One of its children is in the middle of an onload handler.  Note that
303   //      the handler may have already removed this child from mChildList!
304   //   2. It is currently loading a document and either has parts of it still
305   //      loading, or has a busy child docloader.
306   //   3. It's currently flushing layout in DocLoaderIsEmpty().
307   //
308 
309   if (!mChildrenInOnload.IsEmpty() || !mOOPChildrenLoading.IsEmpty() ||
310       mIsFlushingLayout) {
311     return true;
312   }
313 
314   /* Is this document loader busy? */
315   if (!IsBlockingLoadEvent()) {
316     return false;
317   }
318 
319   // Check if any in-process sub-document is awaiting its 'load' event:
320   bool busy;
321   rv = mLoadGroup->IsPending(&busy);
322   if (NS_FAILED(rv)) {
323     return false;
324   }
325   if (busy) {
326     return true;
327   }
328 
329   /* check its child document loaders... */
330   uint32_t count = mChildList.Length();
331   for (uint32_t i = 0; i < count; i++) {
332     nsIDocumentLoader* loader = ChildAt(i);
333 
334     // If 'dom.cross_origin_iframes_loaded_in_background' is set, the parent
335     // document treats cross domain iframes as background loading frame
336     if (loader && static_cast<nsDocLoader*>(loader)->TreatAsBackgroundLoad()) {
337       continue;
338     }
339     // This is a safe cast, because we only put nsDocLoader objects into the
340     // array
341     if (loader && static_cast<nsDocLoader*>(loader)->IsBusy()) return true;
342   }
343 
344   return false;
345 }
346 
347 NS_IMETHODIMP
GetContainer(nsISupports ** aResult)348 nsDocLoader::GetContainer(nsISupports** aResult) {
349   NS_ADDREF(*aResult = static_cast<nsIDocumentLoader*>(this));
350 
351   return NS_OK;
352 }
353 
354 NS_IMETHODIMP
GetLoadGroup(nsILoadGroup ** aResult)355 nsDocLoader::GetLoadGroup(nsILoadGroup** aResult) {
356   nsresult rv = NS_OK;
357 
358   if (nullptr == aResult) {
359     rv = NS_ERROR_NULL_POINTER;
360   } else {
361     *aResult = mLoadGroup;
362     NS_IF_ADDREF(*aResult);
363   }
364   return rv;
365 }
366 
Destroy()367 void nsDocLoader::Destroy() {
368   Stop();
369 
370   // Remove the document loader from the parent list of loaders...
371   if (mParent) {
372     DebugOnly<nsresult> rv = mParent->RemoveChildLoader(this);
373     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "RemoveChildLoader failed");
374   }
375 
376   // Release all the information about network requests...
377   ClearRequestInfoHash();
378 
379   mListenerInfoList.Clear();
380   mListenerInfoList.Compact();
381 
382   mDocumentRequest = nullptr;
383 
384   if (mLoadGroup) mLoadGroup->SetGroupObserver(nullptr);
385 
386   DestroyChildren();
387 }
388 
DestroyChildren()389 void nsDocLoader::DestroyChildren() {
390   uint32_t count = mChildList.Length();
391   // if the doc loader still has children...we need to enumerate the
392   // children and make them null out their back ptr to the parent doc
393   // loader
394   for (uint32_t i = 0; i < count; i++) {
395     nsIDocumentLoader* loader = ChildAt(i);
396 
397     if (loader) {
398       // This is a safe cast, as we only put nsDocLoader objects into the
399       // array
400       DebugOnly<nsresult> rv =
401           static_cast<nsDocLoader*>(loader)->SetDocLoaderParent(nullptr);
402       NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "SetDocLoaderParent failed");
403     }
404   }
405   mChildList.Clear();
406 }
407 
408 NS_IMETHODIMP
OnStartRequest(nsIRequest * request)409 nsDocLoader::OnStartRequest(nsIRequest* request) {
410   // called each time a request is added to the group.
411 
412   // Some docloaders deal with background requests in their OnStartRequest
413   // override, but here we don't want to do anything with them, so return early.
414   nsLoadFlags loadFlags = 0;
415   request->GetLoadFlags(&loadFlags);
416   if (loadFlags & nsIRequest::LOAD_BACKGROUND) {
417     return NS_OK;
418   }
419 
420   if (MOZ_LOG_TEST(gDocLoaderLog, LogLevel::Debug)) {
421     nsAutoCString name;
422     request->GetName(name);
423 
424     uint32_t count = 0;
425     if (mLoadGroup) mLoadGroup->GetActiveCount(&count);
426 
427     MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
428             ("DocLoader:%p: OnStartRequest[%p](%s) mIsLoadingDocument=%s, %u "
429              "active URLs",
430              this, request, name.get(), (mIsLoadingDocument ? "true" : "false"),
431              count));
432   }
433 
434   bool justStartedLoading = false;
435 
436   if (!mIsLoadingDocument && (loadFlags & nsIChannel::LOAD_DOCUMENT_URI)) {
437     justStartedLoading = true;
438     mIsLoadingDocument = true;
439     mDocumentOpenedButNotLoaded = false;
440     ClearInternalProgress();  // only clear our progress if we are starting a
441                               // new load....
442   }
443 
444   //
445   // Create a new nsRequestInfo for the request that is starting to
446   // load...
447   //
448   AddRequestInfo(request);
449 
450   //
451   // Only fire a doStartDocumentLoad(...) if the document loader
452   // has initiated a load...  Otherwise, this notification has
453   // resulted from a request being added to the load group.
454   //
455   if (mIsLoadingDocument) {
456     if (loadFlags & nsIChannel::LOAD_DOCUMENT_URI) {
457       //
458       // Make sure that the document channel is null at this point...
459       // (unless its been redirected)
460       //
461       NS_ASSERTION(
462           (loadFlags & nsIChannel::LOAD_REPLACE) || !(mDocumentRequest.get()),
463           "Overwriting an existing document channel!");
464 
465       // This request is associated with the entire document...
466       mDocumentRequest = request;
467       mLoadGroup->SetDefaultLoadRequest(request);
468 
469       // Only fire the start document load notification for the first
470       // document URI...  Do not fire it again for redirections
471       //
472       if (justStartedLoading) {
473         // Update the progress status state
474         mProgressStateFlags = nsIWebProgressListener::STATE_START;
475 
476         // Fire the start document load notification
477         doStartDocumentLoad();
478         return NS_OK;
479       }
480     }
481   }
482 
483   NS_ASSERTION(!mIsLoadingDocument || mDocumentRequest,
484                "mDocumentRequest MUST be set for the duration of a page load!");
485 
486   // This is the only way to catch document request start event after a redirect
487   // has occured without changing inherited Firefox behaviour significantly.
488   // Problem description:
489   // The combination of |STATE_START + STATE_IS_DOCUMENT| is only sent for
490   // initial request (see |doStartDocumentLoad| call above).
491   // And |STATE_REDIRECTING + STATE_IS_DOCUMENT| is sent with old channel, which
492   // makes it impossible to filter by destination URL (see
493   // |AsyncOnChannelRedirect| implementation).
494   // Fixing any of those bugs may cause unpredictable consequences in any part
495   // of the browser, so we just add a custom flag for this exact situation.
496   int32_t extraFlags = 0;
497   if (mIsLoadingDocument && !justStartedLoading &&
498       (loadFlags & nsIChannel::LOAD_DOCUMENT_URI) &&
499       (loadFlags & nsIChannel::LOAD_REPLACE)) {
500     extraFlags = nsIWebProgressListener::STATE_IS_REDIRECTED_DOCUMENT;
501   }
502   doStartURLLoad(request, extraFlags);
503 
504   return NS_OK;
505 }
506 
507 NS_IMETHODIMP
OnStopRequest(nsIRequest * aRequest,nsresult aStatus)508 nsDocLoader::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
509   // Some docloaders deal with background requests in their OnStopRequest
510   // override, but here we don't want to do anything with them, so return early.
511   nsLoadFlags lf = 0;
512   aRequest->GetLoadFlags(&lf);
513   if (lf & nsIRequest::LOAD_BACKGROUND) {
514     return NS_OK;
515   }
516 
517   nsresult rv = NS_OK;
518 
519   if (MOZ_LOG_TEST(gDocLoaderLog, LogLevel::Debug)) {
520     nsAutoCString name;
521     aRequest->GetName(name);
522 
523     uint32_t count = 0;
524     if (mLoadGroup) mLoadGroup->GetActiveCount(&count);
525 
526     MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
527             ("DocLoader:%p: OnStopRequest[%p](%s) status=%" PRIx32
528              " mIsLoadingDocument=%s, mDocumentOpenedButNotLoaded=%s,"
529              " %u active URLs",
530              this, aRequest, name.get(), static_cast<uint32_t>(aStatus),
531              (mIsLoadingDocument ? "true" : "false"),
532              (mDocumentOpenedButNotLoaded ? "true" : "false"), count));
533   }
534 
535   bool fireTransferring = false;
536 
537   //
538   // Set the Maximum progress to the same value as the current progress.
539   // Since the URI has finished loading, all the data is there.  Also,
540   // this will allow a more accurate estimation of the max progress (in case
541   // the old value was unknown ie. -1)
542   //
543   nsRequestInfo* info = GetRequestInfo(aRequest);
544   if (info) {
545     // Null out mLastStatus now so we don't find it when looking for
546     // status from now on.  This destroys the nsStatusInfo and hence
547     // removes it from our list.
548     info->mLastStatus = nullptr;
549 
550     int64_t oldMax = info->mMaxProgress;
551 
552     info->mMaxProgress = info->mCurrentProgress;
553 
554     //
555     // If a request whose content-length was previously unknown has just
556     // finished loading, then use this new data to try to calculate a
557     // mMaxSelfProgress...
558     //
559     if ((oldMax < int64_t(0)) && (mMaxSelfProgress < int64_t(0))) {
560       mMaxSelfProgress = CalculateMaxProgress();
561     }
562 
563     // As we know the total progress of this request now, save it to be part
564     // of CalculateMaxProgress() result. We need to remove the info from the
565     // hash, see bug 480713.
566     mCompletedTotalProgress += info->mMaxProgress;
567 
568     //
569     // Determine whether a STATE_TRANSFERRING notification should be
570     // 'synthesized'.
571     //
572     // If nsRequestInfo::mMaxProgress (as stored in oldMax) and
573     // nsRequestInfo::mCurrentProgress are both 0, then the
574     // STATE_TRANSFERRING notification has not been fired yet...
575     //
576     if ((oldMax == 0) && (info->mCurrentProgress == 0)) {
577       nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
578 
579       // Only fire a TRANSFERRING notification if the request is also a
580       // channel -- data transfer requires a nsIChannel!
581       //
582       if (channel) {
583         if (NS_SUCCEEDED(aStatus)) {
584           fireTransferring = true;
585         }
586         //
587         // If the request failed (for any reason other than being
588         // redirected or retargeted), the TRANSFERRING notification can
589         // still be fired if a HTTP connection was established to a server.
590         //
591         else if (aStatus != NS_BINDING_REDIRECTED &&
592                  aStatus != NS_BINDING_RETARGETED) {
593           //
594           // Only if the load has been targeted (see bug 268483)...
595           //
596           if (lf & nsIChannel::LOAD_TARGETED) {
597             nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest));
598             if (httpChannel) {
599               uint32_t responseCode;
600               rv = httpChannel->GetResponseStatus(&responseCode);
601               if (NS_SUCCEEDED(rv)) {
602                 //
603                 // A valid server status indicates that a connection was
604                 // established to the server... So, fire the notification
605                 // even though a failure occurred later...
606                 //
607                 fireTransferring = true;
608               }
609             }
610           }
611         }
612       }
613     }
614   }
615 
616   if (fireTransferring) {
617     // Send a STATE_TRANSFERRING notification for the request.
618     int32_t flags;
619 
620     flags = nsIWebProgressListener::STATE_TRANSFERRING |
621             nsIWebProgressListener::STATE_IS_REQUEST;
622     //
623     // Move the WebProgress into the STATE_TRANSFERRING state if necessary...
624     //
625     if (mProgressStateFlags & nsIWebProgressListener::STATE_START) {
626       mProgressStateFlags = nsIWebProgressListener::STATE_TRANSFERRING;
627 
628       // Send STATE_TRANSFERRING for the document too...
629       flags |= nsIWebProgressListener::STATE_IS_DOCUMENT;
630     }
631 
632     FireOnStateChange(this, aRequest, flags, NS_OK);
633   }
634 
635   //
636   // Fire the OnStateChange(...) notification for stop request
637   //
638   doStopURLLoad(aRequest, aStatus);
639 
640   // Clear this request out of the hash to avoid bypass of FireOnStateChange
641   // when address of the request is reused.
642   RemoveRequestInfo(aRequest);
643 
644   // For the special case where the current document is an initial about:blank
645   // document, we may still have subframes loading, and keeping the DocLoader
646   // busy. In that case, if we have an error, we won't show it until those
647   // frames finish loading, which is nonsensical. So stop any subframe loads
648   // now.
649   if (NS_FAILED(aStatus) && aStatus != NS_BINDING_ABORTED &&
650       aStatus != NS_BINDING_REDIRECTED && aStatus != NS_BINDING_RETARGETED) {
651     if (RefPtr<Document> doc = do_GetInterface(GetAsSupports(this))) {
652       if (doc->IsInitialDocument()) {
653         NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mChildList, Stop, ());
654       }
655     }
656   }
657 
658   //
659   // Only fire the DocLoaderIsEmpty(...) if we may need to fire onload.
660   //
661   if (IsBlockingLoadEvent()) {
662     nsCOMPtr<nsIDocShell> ds =
663         do_QueryInterface(static_cast<nsIRequestObserver*>(this));
664     bool doNotFlushLayout = false;
665     if (ds) {
666       // Don't do unexpected layout flushes while we're in process of restoring
667       // a document from the bfcache.
668       ds->GetRestoringDocument(&doNotFlushLayout);
669     }
670     DocLoaderIsEmpty(!doNotFlushLayout);
671   }
672 
673   return NS_OK;
674 }
675 
RemoveChildLoader(nsDocLoader * aChild)676 nsresult nsDocLoader::RemoveChildLoader(nsDocLoader* aChild) {
677   nsresult rv = mChildList.RemoveElement(aChild) ? NS_OK : NS_ERROR_FAILURE;
678   if (NS_SUCCEEDED(rv)) {
679     rv = aChild->SetDocLoaderParent(nullptr);
680   }
681   return rv;
682 }
683 
AddChildLoader(nsDocLoader * aChild)684 nsresult nsDocLoader::AddChildLoader(nsDocLoader* aChild) {
685   mChildList.AppendElement(aChild);
686   return aChild->SetDocLoaderParent(this);
687 }
688 
GetDocumentChannel(nsIChannel ** aChannel)689 NS_IMETHODIMP nsDocLoader::GetDocumentChannel(nsIChannel** aChannel) {
690   if (!mDocumentRequest) {
691     *aChannel = nullptr;
692     return NS_OK;
693   }
694 
695   return CallQueryInterface(mDocumentRequest, aChannel);
696 }
697 
DocLoaderIsEmpty(bool aFlushLayout,const Maybe<nsresult> & aOverrideStatus)698 void nsDocLoader::DocLoaderIsEmpty(bool aFlushLayout,
699                                    const Maybe<nsresult>& aOverrideStatus) {
700   if (IsBlockingLoadEvent()) {
701     /* In the unimagineably rude circumstance that onload event handlers
702        triggered by this function actually kill the window ... ok, it's
703        not unimagineable; it's happened ... this deathgrip keeps this object
704        alive long enough to survive this function call. */
705     nsCOMPtr<nsIDocumentLoader> kungFuDeathGrip(this);
706 
707     // Don't flush layout if we're still busy.
708     if (IsBusy()) {
709       return;
710     }
711 
712     NS_ASSERTION(!mIsFlushingLayout, "Someone screwed up");
713     // We may not have a document request if we are in a
714     // document.open() situation.
715     NS_ASSERTION(mDocumentRequest || mDocumentOpenedButNotLoaded,
716                  "No Document Request!");
717 
718     // The load group for this DocumentLoader is idle.  Flush if we need to.
719     if (aFlushLayout && !mDontFlushLayout) {
720       nsCOMPtr<Document> doc = do_GetInterface(GetAsSupports(this));
721       if (doc) {
722         // We start loads from style resolution, so we need to flush out style
723         // no matter what.  If we have user fonts, we also need to flush layout,
724         // since the reflow is what starts font loads.
725         mozilla::FlushType flushType = mozilla::FlushType::Style;
726         // Be safe in case this presshell is in teardown now
727         doc->FlushUserFontSet();
728         if (doc->GetUserFontSet()) {
729           flushType = mozilla::FlushType::Layout;
730         }
731         mDontFlushLayout = mIsFlushingLayout = true;
732         doc->FlushPendingNotifications(flushType);
733         mDontFlushLayout = mIsFlushingLayout = false;
734       }
735     }
736 
737     // And now check whether we're really busy; that might have changed with
738     // the layout flush.
739     //
740     // Note, mDocumentRequest can be null while mDocumentOpenedButNotLoaded is
741     // false if the flushing above re-entered this method.
742     if (IsBusy() || (!mDocumentRequest && !mDocumentOpenedButNotLoaded)) {
743       return;
744     }
745 
746     if (mDocumentRequest) {
747       // Clear out our request info hash, now that our load really is done and
748       // we don't need it anymore to CalculateMaxProgress().
749       ClearInternalProgress();
750 
751       MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
752               ("DocLoader:%p: Is now idle...\n", this));
753 
754       nsCOMPtr<nsIRequest> docRequest = mDocumentRequest;
755 
756       mDocumentRequest = nullptr;
757       mIsLoadingDocument = false;
758 
759       // Update the progress status state - the document is done
760       mProgressStateFlags = nsIWebProgressListener::STATE_STOP;
761 
762       nsresult loadGroupStatus = NS_OK;
763       if (aOverrideStatus) {
764         loadGroupStatus = *aOverrideStatus;
765       } else {
766         mLoadGroup->GetStatus(&loadGroupStatus);
767       }
768 
769       //
770       // New code to break the circular reference between
771       // the load group and the docloader...
772       //
773       mLoadGroup->SetDefaultLoadRequest(nullptr);
774 
775       // Take a ref to our parent now so that we can call ChildDoneWithOnload()
776       // on it even if our onload handler removes us from the docloader tree.
777       RefPtr<nsDocLoader> parent = mParent;
778 
779       // Note that if calling ChildEnteringOnload() on the parent returns false
780       // then calling our onload handler is not safe.  That can only happen on
781       // OOM, so that's ok.
782       if (!parent || parent->ChildEnteringOnload(this)) {
783         // Do nothing with our state after firing the
784         // OnEndDocumentLoad(...). The document loader may be loading a *new*
785         // document - if LoadDocument() was called from a handler!
786         //
787         doStopDocumentLoad(docRequest, loadGroupStatus);
788 
789         NotifyDoneWithOnload(parent);
790       }
791     } else {
792       MOZ_ASSERT(mDocumentOpenedButNotLoaded);
793       mDocumentOpenedButNotLoaded = false;
794 
795       // Make sure we do the ChildEnteringOnload/ChildDoneWithOnload even if we
796       // plan to skip firing our own load event, because otherwise we might
797       // never end up firing our parent's load event.
798       RefPtr<nsDocLoader> parent = mParent;
799       if (!parent || parent->ChildEnteringOnload(this)) {
800         nsresult loadGroupStatus = NS_OK;
801         mLoadGroup->GetStatus(&loadGroupStatus);
802         // Make sure we're not canceling the loadgroup.  If we are, then just
803         // like the normal navigation case we should not fire a load event.
804         if (NS_SUCCEEDED(loadGroupStatus) ||
805             loadGroupStatus == NS_ERROR_PARSED_DATA_CACHED) {
806           // Can "doc" or "window" ever come back null here?  Our state machine
807           // is complicated enough I wouldn't bet against it...
808           nsCOMPtr<Document> doc = do_GetInterface(GetAsSupports(this));
809           if (doc) {
810             doc->SetReadyStateInternal(Document::READYSTATE_COMPLETE,
811                                        /* updateTimingInformation = */ false);
812             doc->StopDocumentLoad();
813 
814             nsCOMPtr<nsPIDOMWindowOuter> window = doc->GetWindow();
815             if (window && !doc->SkipLoadEventAfterClose()) {
816               if (!mozilla::dom::DocGroup::TryToLoadIframesInBackground() ||
817                   (mozilla::dom::DocGroup::TryToLoadIframesInBackground() &&
818                    !HasFakeOnLoadDispatched())) {
819                 MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
820                         ("DocLoader:%p: Firing load event for document.open\n",
821                          this));
822 
823                 // This is a very cut-down version of
824                 // nsDocumentViewer::LoadComplete that doesn't do various things
825                 // that are not relevant here because this wasn't an actual
826                 // navigation.
827                 WidgetEvent event(true, eLoad);
828                 event.mFlags.mBubbles = false;
829                 event.mFlags.mCancelable = false;
830                 // Dispatching to |window|, but using |document| as the target,
831                 // per spec.
832                 event.mTarget = doc;
833                 nsEventStatus unused = nsEventStatus_eIgnore;
834                 doc->SetLoadEventFiring(true);
835                 EventDispatcher::Dispatch(window, nullptr, &event, nullptr,
836                                           &unused);
837                 doc->SetLoadEventFiring(false);
838 
839                 // Now unsuppress painting on the presshell, if we
840                 // haven't done that yet.
841                 RefPtr<PresShell> presShell = doc->GetPresShell();
842                 if (presShell && !presShell->IsDestroying()) {
843                   presShell->UnsuppressPainting();
844 
845                   if (!presShell->IsDestroying()) {
846                     presShell->LoadComplete();
847                   }
848                 }
849               }
850             }
851           }
852         }
853         NotifyDoneWithOnload(parent);
854       }
855     }
856   }
857 }
858 
NotifyDoneWithOnload(nsDocLoader * aParent)859 void nsDocLoader::NotifyDoneWithOnload(nsDocLoader* aParent) {
860   if (aParent) {
861     // In-process parent:
862     aParent->ChildDoneWithOnload(this);
863   }
864   nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(this);
865   if (!docShell) {
866     return;
867   }
868   BrowsingContext* bc = nsDocShell::Cast(docShell)->GetBrowsingContext();
869   if (bc->IsContentSubframe() && !bc->GetParent()->IsInProcess()) {
870     if (BrowserChild* browserChild = BrowserChild::GetFrom(docShell)) {
871       mozilla::Unused << browserChild->SendMaybeFireEmbedderLoadEvents(
872           dom::EmbedderElementEventType::NoEvent);
873     }
874   }
875 }
876 
doStartDocumentLoad(void)877 void nsDocLoader::doStartDocumentLoad(void) {
878 #if defined(DEBUG)
879   nsAutoCString buffer;
880 
881   GetURIStringFromRequest(mDocumentRequest, buffer);
882   MOZ_LOG(
883       gDocLoaderLog, LogLevel::Debug,
884       ("DocLoader:%p: ++ Firing OnStateChange for start document load (...)."
885        "\tURI: %s \n",
886        this, buffer.get()));
887 #endif /* DEBUG */
888 
889   // Fire an OnStatus(...) notification STATE_START.  This indicates
890   // that the document represented by mDocumentRequest has started to
891   // load...
892   FireOnStateChange(this, mDocumentRequest,
893                     nsIWebProgressListener::STATE_START |
894                         nsIWebProgressListener::STATE_IS_DOCUMENT |
895                         nsIWebProgressListener::STATE_IS_REQUEST |
896                         nsIWebProgressListener::STATE_IS_WINDOW |
897                         nsIWebProgressListener::STATE_IS_NETWORK,
898                     NS_OK);
899 }
900 
doStartURLLoad(nsIRequest * request,int32_t aExtraFlags)901 void nsDocLoader::doStartURLLoad(nsIRequest* request, int32_t aExtraFlags) {
902 #if defined(DEBUG)
903   nsAutoCString buffer;
904 
905   GetURIStringFromRequest(request, buffer);
906   MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
907           ("DocLoader:%p: ++ Firing OnStateChange start url load (...)."
908            "\tURI: %s\n",
909            this, buffer.get()));
910 #endif /* DEBUG */
911 
912   FireOnStateChange(this, request,
913                     nsIWebProgressListener::STATE_START |
914                         nsIWebProgressListener::STATE_IS_REQUEST | aExtraFlags,
915                     NS_OK);
916 }
917 
doStopURLLoad(nsIRequest * request,nsresult aStatus)918 void nsDocLoader::doStopURLLoad(nsIRequest* request, nsresult aStatus) {
919 #if defined(DEBUG)
920   nsAutoCString buffer;
921 
922   GetURIStringFromRequest(request, buffer);
923   MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
924           ("DocLoader:%p: ++ Firing OnStateChange for end url load (...)."
925            "\tURI: %s status=%" PRIx32 "\n",
926            this, buffer.get(), static_cast<uint32_t>(aStatus)));
927 #endif /* DEBUG */
928 
929   FireOnStateChange(this, request,
930                     nsIWebProgressListener::STATE_STOP |
931                         nsIWebProgressListener::STATE_IS_REQUEST,
932                     aStatus);
933 
934   // Fire a status change message for the most recent unfinished
935   // request to make sure that the displayed status is not outdated.
936   if (!mStatusInfoList.isEmpty()) {
937     nsStatusInfo* statusInfo = mStatusInfoList.getFirst();
938     FireOnStatusChange(this, statusInfo->mRequest, statusInfo->mStatusCode,
939                        statusInfo->mStatusMessage.get());
940   }
941 }
942 
doStopDocumentLoad(nsIRequest * request,nsresult aStatus)943 void nsDocLoader::doStopDocumentLoad(nsIRequest* request, nsresult aStatus) {
944 #if defined(DEBUG)
945   nsAutoCString buffer;
946 
947   GetURIStringFromRequest(request, buffer);
948   MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
949           ("DocLoader:%p: ++ Firing OnStateChange for end document load (...)."
950            "\tURI: %s Status=%" PRIx32 "\n",
951            this, buffer.get(), static_cast<uint32_t>(aStatus)));
952 #endif /* DEBUG */
953 
954   // Firing STATE_STOP|STATE_IS_DOCUMENT will fire onload handlers.
955   // Grab our parent chain before doing that so we can still dispatch
956   // STATE_STOP|STATE_IS_WINDW_STATE_IS_NETWORK to them all, even if
957   // the onload handlers rearrange the docshell tree.
958   WebProgressList list;
959   GatherAncestorWebProgresses(list);
960 
961   //
962   // Fire an OnStateChange(...) notification indicating the the
963   // current document has finished loading...
964   //
965   int32_t flags = nsIWebProgressListener::STATE_STOP |
966                   nsIWebProgressListener::STATE_IS_DOCUMENT;
967   for (uint32_t i = 0; i < list.Length(); ++i) {
968     list[i]->DoFireOnStateChange(this, request, flags, aStatus);
969   }
970 
971   //
972   // Fire a final OnStateChange(...) notification indicating the the
973   // current document has finished loading...
974   //
975   flags = nsIWebProgressListener::STATE_STOP |
976           nsIWebProgressListener::STATE_IS_WINDOW |
977           nsIWebProgressListener::STATE_IS_NETWORK;
978   for (uint32_t i = 0; i < list.Length(); ++i) {
979     list[i]->DoFireOnStateChange(this, request, flags, aStatus);
980   }
981 }
982 
983 ////////////////////////////////////////////////////////////////////////////////////
984 // The following section contains support for nsIWebProgress and related stuff
985 ////////////////////////////////////////////////////////////////////////////////////
986 
987 NS_IMETHODIMP
AddProgressListener(nsIWebProgressListener * aListener,uint32_t aNotifyMask)988 nsDocLoader::AddProgressListener(nsIWebProgressListener* aListener,
989                                  uint32_t aNotifyMask) {
990   if (mListenerInfoList.Contains(aListener)) {
991     // The listener is already registered!
992     return NS_ERROR_FAILURE;
993   }
994 
995   nsWeakPtr listener = do_GetWeakReference(aListener);
996   if (!listener) {
997     return NS_ERROR_INVALID_ARG;
998   }
999 
1000   mListenerInfoList.AppendElement(nsListenerInfo(listener, aNotifyMask));
1001   return NS_OK;
1002 }
1003 
1004 NS_IMETHODIMP
RemoveProgressListener(nsIWebProgressListener * aListener)1005 nsDocLoader::RemoveProgressListener(nsIWebProgressListener* aListener) {
1006   return mListenerInfoList.RemoveElement(aListener) ? NS_OK : NS_ERROR_FAILURE;
1007 }
1008 
1009 NS_IMETHODIMP
GetDOMWindow(mozIDOMWindowProxy ** aResult)1010 nsDocLoader::GetDOMWindow(mozIDOMWindowProxy** aResult) {
1011   return CallGetInterface(this, aResult);
1012 }
1013 
1014 NS_IMETHODIMP
GetIsTopLevel(bool * aResult)1015 nsDocLoader::GetIsTopLevel(bool* aResult) {
1016   nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(this);
1017   *aResult = docShell && docShell->GetBrowsingContext()->IsTop();
1018   return NS_OK;
1019 }
1020 
1021 NS_IMETHODIMP
GetIsLoadingDocument(bool * aIsLoadingDocument)1022 nsDocLoader::GetIsLoadingDocument(bool* aIsLoadingDocument) {
1023   *aIsLoadingDocument = mIsLoadingDocument;
1024 
1025   return NS_OK;
1026 }
1027 
1028 NS_IMETHODIMP
GetLoadType(uint32_t * aLoadType)1029 nsDocLoader::GetLoadType(uint32_t* aLoadType) {
1030   *aLoadType = 0;
1031 
1032   return NS_ERROR_NOT_IMPLEMENTED;
1033 }
1034 
1035 NS_IMETHODIMP
GetTarget(nsIEventTarget ** aTarget)1036 nsDocLoader::GetTarget(nsIEventTarget** aTarget) {
1037   nsCOMPtr<mozIDOMWindowProxy> window;
1038   nsresult rv = GetDOMWindow(getter_AddRefs(window));
1039   NS_ENSURE_SUCCESS(rv, rv);
1040 
1041   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(window);
1042   NS_ENSURE_STATE(global);
1043 
1044   nsCOMPtr<nsIEventTarget> target =
1045       global->EventTargetFor(mozilla::TaskCategory::Other);
1046   target.forget(aTarget);
1047   return NS_OK;
1048 }
1049 
1050 NS_IMETHODIMP
SetTarget(nsIEventTarget * aTarget)1051 nsDocLoader::SetTarget(nsIEventTarget* aTarget) {
1052   return NS_ERROR_NOT_IMPLEMENTED;
1053 }
1054 
GetMaxTotalProgress()1055 int64_t nsDocLoader::GetMaxTotalProgress() {
1056   int64_t newMaxTotal = 0;
1057 
1058   uint32_t count = mChildList.Length();
1059   for (uint32_t i = 0; i < count; i++) {
1060     int64_t individualProgress = 0;
1061     nsIDocumentLoader* docloader = ChildAt(i);
1062     if (docloader) {
1063       // Cast is safe since all children are nsDocLoader too
1064       individualProgress = ((nsDocLoader*)docloader)->GetMaxTotalProgress();
1065     }
1066     if (individualProgress < int64_t(0))  // if one of the elements doesn't know
1067                                           // it's size then none of them do
1068     {
1069       newMaxTotal = int64_t(-1);
1070       break;
1071     } else
1072       newMaxTotal += individualProgress;
1073   }
1074 
1075   int64_t progress = -1;
1076   if (mMaxSelfProgress >= int64_t(0) && newMaxTotal >= int64_t(0))
1077     progress = newMaxTotal + mMaxSelfProgress;
1078 
1079   return progress;
1080 }
1081 
1082 ////////////////////////////////////////////////////////////////////////////////////
1083 // The following section contains support for nsIProgressEventSink which is used
1084 // to pass progress and status between the actual request and the doc loader.
1085 // The doc loader then turns around and makes the right web progress calls based
1086 // on this information.
1087 ////////////////////////////////////////////////////////////////////////////////////
1088 
OnProgress(nsIRequest * aRequest,int64_t aProgress,int64_t aProgressMax)1089 NS_IMETHODIMP nsDocLoader::OnProgress(nsIRequest* aRequest, int64_t aProgress,
1090                                       int64_t aProgressMax) {
1091   int64_t progressDelta = 0;
1092 
1093   //
1094   // Update the RequestInfo entry with the new progress data
1095   //
1096   if (nsRequestInfo* info = GetRequestInfo(aRequest)) {
1097     // Update info->mCurrentProgress before we call FireOnStateChange,
1098     // since that can make the "info" pointer invalid.
1099     int64_t oldCurrentProgress = info->mCurrentProgress;
1100     progressDelta = aProgress - oldCurrentProgress;
1101     info->mCurrentProgress = aProgress;
1102 
1103     // suppress sending STATE_TRANSFERRING if this is upload progress (see bug
1104     // 240053)
1105     if (!info->mUploading && (int64_t(0) == oldCurrentProgress) &&
1106         (int64_t(0) == info->mMaxProgress)) {
1107       //
1108       // If we receive an OnProgress event from a toplevel channel that the URI
1109       // Loader has not yet targeted, then we must suppress the event.  This is
1110       // necessary to ensure that webprogresslisteners do not get confused when
1111       // the channel is finally targeted.  See bug 257308.
1112       //
1113       nsLoadFlags lf = 0;
1114       aRequest->GetLoadFlags(&lf);
1115       if ((lf & nsIChannel::LOAD_DOCUMENT_URI) &&
1116           !(lf & nsIChannel::LOAD_TARGETED)) {
1117         MOZ_LOG(
1118             gDocLoaderLog, LogLevel::Debug,
1119             ("DocLoader:%p Ignoring OnProgress while load is not targeted\n",
1120              this));
1121         return NS_OK;
1122       }
1123 
1124       //
1125       // This is the first progress notification for the entry.  If
1126       // (aMaxProgress != -1) then the content-length of the data is known,
1127       // so update mMaxSelfProgress...  Otherwise, set it to -1 to indicate
1128       // that the content-length is no longer known.
1129       //
1130       if (aProgressMax != -1) {
1131         mMaxSelfProgress += aProgressMax;
1132         info->mMaxProgress = aProgressMax;
1133       } else {
1134         mMaxSelfProgress = int64_t(-1);
1135         info->mMaxProgress = int64_t(-1);
1136       }
1137 
1138       // Send a STATE_TRANSFERRING notification for the request.
1139       int32_t flags;
1140 
1141       flags = nsIWebProgressListener::STATE_TRANSFERRING |
1142               nsIWebProgressListener::STATE_IS_REQUEST;
1143       //
1144       // Move the WebProgress into the STATE_TRANSFERRING state if necessary...
1145       //
1146       if (mProgressStateFlags & nsIWebProgressListener::STATE_START) {
1147         mProgressStateFlags = nsIWebProgressListener::STATE_TRANSFERRING;
1148 
1149         // Send STATE_TRANSFERRING for the document too...
1150         flags |= nsIWebProgressListener::STATE_IS_DOCUMENT;
1151       }
1152 
1153       FireOnStateChange(this, aRequest, flags, NS_OK);
1154     }
1155 
1156     // Update our overall current progress count.
1157     mCurrentSelfProgress += progressDelta;
1158   }
1159   //
1160   // The request is not part of the load group, so ignore its progress
1161   // information...
1162   //
1163   else {
1164 #if defined(DEBUG)
1165     nsAutoCString buffer;
1166 
1167     GetURIStringFromRequest(aRequest, buffer);
1168     MOZ_LOG(
1169         gDocLoaderLog, LogLevel::Debug,
1170         ("DocLoader:%p OOPS - No Request Info for: %s\n", this, buffer.get()));
1171 #endif /* DEBUG */
1172 
1173     return NS_OK;
1174   }
1175 
1176   //
1177   // Fire progress notifications out to any registered nsIWebProgressListeners
1178   //
1179   FireOnProgressChange(this, aRequest, aProgress, aProgressMax, progressDelta,
1180                        mCurrentTotalProgress, mMaxTotalProgress);
1181 
1182   return NS_OK;
1183 }
1184 
OnStatus(nsIRequest * aRequest,nsresult aStatus,const char16_t * aStatusArg)1185 NS_IMETHODIMP nsDocLoader::OnStatus(nsIRequest* aRequest, nsresult aStatus,
1186                                     const char16_t* aStatusArg) {
1187   //
1188   // Fire progress notifications out to any registered nsIWebProgressListeners
1189   //
1190   if (aStatus != NS_OK) {
1191     // Remember the current status for this request
1192     nsRequestInfo* info;
1193     info = GetRequestInfo(aRequest);
1194     if (info) {
1195       bool uploading = (aStatus == NS_NET_STATUS_WRITING ||
1196                         aStatus == NS_NET_STATUS_SENDING_TO);
1197       // If switching from uploading to downloading (or vice versa), then we
1198       // need to reset our progress counts.  This is designed with HTTP form
1199       // submission in mind, where an upload is performed followed by download
1200       // of possibly several documents.
1201       if (info->mUploading != uploading) {
1202         mCurrentSelfProgress = mMaxSelfProgress = 0;
1203         mCurrentTotalProgress = mMaxTotalProgress = 0;
1204         mCompletedTotalProgress = 0;
1205         info->mUploading = uploading;
1206         info->mCurrentProgress = 0;
1207         info->mMaxProgress = 0;
1208       }
1209     }
1210 
1211     nsCOMPtr<nsIStringBundleService> sbs =
1212         mozilla::components::StringBundle::Service();
1213     if (!sbs) return NS_ERROR_FAILURE;
1214     nsAutoString msg;
1215     nsresult rv = sbs->FormatStatusMessage(aStatus, aStatusArg, msg);
1216     if (NS_FAILED(rv)) return rv;
1217 
1218     // Keep around the message. In case a request finishes, we need to make sure
1219     // to send the status message of another request to our user to that we
1220     // don't display, for example, "Transferring" messages for requests that are
1221     // already done.
1222     if (info) {
1223       if (!info->mLastStatus) {
1224         info->mLastStatus = MakeUnique<nsStatusInfo>(aRequest);
1225       } else {
1226         // We're going to move it to the front of the list, so remove
1227         // it from wherever it is now.
1228         info->mLastStatus->remove();
1229       }
1230       info->mLastStatus->mStatusMessage = msg;
1231       info->mLastStatus->mStatusCode = aStatus;
1232       // Put the info at the front of the list
1233       mStatusInfoList.insertFront(info->mLastStatus.get());
1234     }
1235     FireOnStatusChange(this, aRequest, aStatus, msg.get());
1236   }
1237   return NS_OK;
1238 }
1239 
ClearInternalProgress()1240 void nsDocLoader::ClearInternalProgress() {
1241   ClearRequestInfoHash();
1242 
1243   mCurrentSelfProgress = mMaxSelfProgress = 0;
1244   mCurrentTotalProgress = mMaxTotalProgress = 0;
1245   mCompletedTotalProgress = 0;
1246 
1247   mProgressStateFlags = nsIWebProgressListener::STATE_STOP;
1248 }
1249 
1250 /**
1251  * |_code| is executed for every listener matching |_flag|
1252  * |listener| should be used inside |_code| as the nsIWebProgressListener var.
1253  */
1254 #define NOTIFY_LISTENERS(_flag, _code)                     \
1255   PR_BEGIN_MACRO                                           \
1256   nsCOMPtr<nsIWebProgressListener> listener;               \
1257   ListenerArray::BackwardIterator iter(mListenerInfoList); \
1258   while (iter.HasMore()) {                                 \
1259     nsListenerInfo& info = iter.GetNext();                 \
1260     if (!(info.mNotifyMask & (_flag))) {                   \
1261       continue;                                            \
1262     }                                                      \
1263     listener = do_QueryReferent(info.mWeakListener);       \
1264     if (!listener) {                                       \
1265       iter.Remove();                                       \
1266       continue;                                            \
1267     }                                                      \
1268     _code                                                  \
1269   }                                                        \
1270   mListenerInfoList.Compact();                             \
1271   PR_END_MACRO
1272 
FireOnProgressChange(nsDocLoader * aLoadInitiator,nsIRequest * request,int64_t aProgress,int64_t aProgressMax,int64_t aProgressDelta,int64_t aTotalProgress,int64_t aMaxTotalProgress)1273 void nsDocLoader::FireOnProgressChange(nsDocLoader* aLoadInitiator,
1274                                        nsIRequest* request, int64_t aProgress,
1275                                        int64_t aProgressMax,
1276                                        int64_t aProgressDelta,
1277                                        int64_t aTotalProgress,
1278                                        int64_t aMaxTotalProgress) {
1279   if (mIsLoadingDocument) {
1280     mCurrentTotalProgress += aProgressDelta;
1281     mMaxTotalProgress = GetMaxTotalProgress();
1282 
1283     aTotalProgress = mCurrentTotalProgress;
1284     aMaxTotalProgress = mMaxTotalProgress;
1285   }
1286 
1287 #if defined(DEBUG)
1288   nsAutoCString buffer;
1289 
1290   GetURIStringFromRequest(request, buffer);
1291   MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
1292           ("DocLoader:%p: Progress (%s): curSelf: %" PRId64 " maxSelf: %" PRId64
1293            " curTotal: %" PRId64 " maxTotal %" PRId64 "\n",
1294            this, buffer.get(), aProgress, aProgressMax, aTotalProgress,
1295            aMaxTotalProgress));
1296 #endif /* DEBUG */
1297 
1298   NOTIFY_LISTENERS(
1299       nsIWebProgress::NOTIFY_PROGRESS,
1300       // XXX truncates 64-bit to 32-bit
1301       listener->OnProgressChange(aLoadInitiator, request, int32_t(aProgress),
1302                                  int32_t(aProgressMax), int32_t(aTotalProgress),
1303                                  int32_t(aMaxTotalProgress)););
1304 
1305   // Pass the notification up to the parent...
1306   if (mParent) {
1307     mParent->FireOnProgressChange(aLoadInitiator, request, aProgress,
1308                                   aProgressMax, aProgressDelta, aTotalProgress,
1309                                   aMaxTotalProgress);
1310   }
1311 }
1312 
GatherAncestorWebProgresses(WebProgressList & aList)1313 void nsDocLoader::GatherAncestorWebProgresses(WebProgressList& aList) {
1314   for (nsDocLoader* loader = this; loader; loader = loader->mParent) {
1315     aList.AppendElement(loader);
1316   }
1317 }
1318 
FireOnStateChange(nsIWebProgress * aProgress,nsIRequest * aRequest,int32_t aStateFlags,nsresult aStatus)1319 void nsDocLoader::FireOnStateChange(nsIWebProgress* aProgress,
1320                                     nsIRequest* aRequest, int32_t aStateFlags,
1321                                     nsresult aStatus) {
1322   WebProgressList list;
1323   GatherAncestorWebProgresses(list);
1324   for (uint32_t i = 0; i < list.Length(); ++i) {
1325     list[i]->DoFireOnStateChange(aProgress, aRequest, aStateFlags, aStatus);
1326   }
1327 }
1328 
DoFireOnStateChange(nsIWebProgress * const aProgress,nsIRequest * const aRequest,int32_t & aStateFlags,const nsresult aStatus)1329 void nsDocLoader::DoFireOnStateChange(nsIWebProgress* const aProgress,
1330                                       nsIRequest* const aRequest,
1331                                       int32_t& aStateFlags,
1332                                       const nsresult aStatus) {
1333   //
1334   // Remove the STATE_IS_NETWORK bit if necessary.
1335   //
1336   // The rule is to remove this bit, if the notification has been passed
1337   // up from a child WebProgress, and the current WebProgress is already
1338   // active...
1339   //
1340   if (mIsLoadingDocument &&
1341       (aStateFlags & nsIWebProgressListener::STATE_IS_NETWORK) &&
1342       (this != aProgress)) {
1343     aStateFlags &= ~nsIWebProgressListener::STATE_IS_NETWORK;
1344   }
1345 
1346   // Add the STATE_RESTORING bit if necessary.
1347   if (mIsRestoringDocument)
1348     aStateFlags |= nsIWebProgressListener::STATE_RESTORING;
1349 
1350 #if defined(DEBUG)
1351   nsAutoCString buffer;
1352 
1353   GetURIStringFromRequest(aRequest, buffer);
1354   MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
1355           ("DocLoader:%p: Status (%s): code: %x\n", this, buffer.get(),
1356            aStateFlags));
1357 #endif /* DEBUG */
1358 
1359   NS_ASSERTION(aRequest,
1360                "Firing OnStateChange(...) notification with a NULL request!");
1361 
1362   NOTIFY_LISTENERS(
1363       ((aStateFlags >> 16) & nsIWebProgress::NOTIFY_STATE_ALL),
1364       listener->OnStateChange(aProgress, aRequest, aStateFlags, aStatus););
1365 }
1366 
FireOnLocationChange(nsIWebProgress * aWebProgress,nsIRequest * aRequest,nsIURI * aUri,uint32_t aFlags)1367 void nsDocLoader::FireOnLocationChange(nsIWebProgress* aWebProgress,
1368                                        nsIRequest* aRequest, nsIURI* aUri,
1369                                        uint32_t aFlags) {
1370   NOTIFY_LISTENERS(
1371       nsIWebProgress::NOTIFY_LOCATION,
1372       MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
1373               ("DocLoader [%p] calling %p->OnLocationChange to %s %x", this,
1374                listener.get(), aUri->GetSpecOrDefault().get(), aFlags));
1375       listener->OnLocationChange(aWebProgress, aRequest, aUri, aFlags););
1376 
1377   // Pass the notification up to the parent...
1378   if (mParent) {
1379     mParent->FireOnLocationChange(aWebProgress, aRequest, aUri, aFlags);
1380   }
1381 }
1382 
FireOnStatusChange(nsIWebProgress * aWebProgress,nsIRequest * aRequest,nsresult aStatus,const char16_t * aMessage)1383 void nsDocLoader::FireOnStatusChange(nsIWebProgress* aWebProgress,
1384                                      nsIRequest* aRequest, nsresult aStatus,
1385                                      const char16_t* aMessage) {
1386   NOTIFY_LISTENERS(
1387       nsIWebProgress::NOTIFY_STATUS,
1388       listener->OnStatusChange(aWebProgress, aRequest, aStatus, aMessage););
1389 
1390   // Pass the notification up to the parent...
1391   if (mParent) {
1392     mParent->FireOnStatusChange(aWebProgress, aRequest, aStatus, aMessage);
1393   }
1394 }
1395 
RefreshAttempted(nsIWebProgress * aWebProgress,nsIURI * aURI,int32_t aDelay,bool aSameURI)1396 bool nsDocLoader::RefreshAttempted(nsIWebProgress* aWebProgress, nsIURI* aURI,
1397                                    int32_t aDelay, bool aSameURI) {
1398   /*
1399    * Returns true if the refresh may proceed,
1400    * false if the refresh should be blocked.
1401    */
1402   bool allowRefresh = true;
1403 
1404   NOTIFY_LISTENERS(
1405       nsIWebProgress::NOTIFY_REFRESH,
1406       nsCOMPtr<nsIWebProgressListener2> listener2 =
1407           do_QueryReferent(info.mWeakListener);
1408       if (!listener2) continue;
1409 
1410       bool listenerAllowedRefresh;
1411       nsresult listenerRV = listener2->OnRefreshAttempted(
1412           aWebProgress, aURI, aDelay, aSameURI, &listenerAllowedRefresh);
1413       if (NS_FAILED(listenerRV)) continue;
1414 
1415       allowRefresh = allowRefresh && listenerAllowedRefresh;);
1416 
1417   // Pass the notification up to the parent...
1418   if (mParent) {
1419     allowRefresh = allowRefresh && mParent->RefreshAttempted(aWebProgress, aURI,
1420                                                              aDelay, aSameURI);
1421   }
1422 
1423   return allowRefresh;
1424 }
1425 
AddRequestInfo(nsIRequest * aRequest)1426 nsresult nsDocLoader::AddRequestInfo(nsIRequest* aRequest) {
1427   if (!mRequestInfoHash.Add(aRequest, mozilla::fallible)) {
1428     return NS_ERROR_OUT_OF_MEMORY;
1429   }
1430 
1431   return NS_OK;
1432 }
1433 
RemoveRequestInfo(nsIRequest * aRequest)1434 void nsDocLoader::RemoveRequestInfo(nsIRequest* aRequest) {
1435   mRequestInfoHash.Remove(aRequest);
1436 }
1437 
GetRequestInfo(nsIRequest * aRequest) const1438 nsDocLoader::nsRequestInfo* nsDocLoader::GetRequestInfo(
1439     nsIRequest* aRequest) const {
1440   return static_cast<nsRequestInfo*>(mRequestInfoHash.Search(aRequest));
1441 }
1442 
ClearRequestInfoHash(void)1443 void nsDocLoader::ClearRequestInfoHash(void) { mRequestInfoHash.Clear(); }
1444 
CalculateMaxProgress()1445 int64_t nsDocLoader::CalculateMaxProgress() {
1446   int64_t max = mCompletedTotalProgress;
1447   for (auto iter = mRequestInfoHash.Iter(); !iter.Done(); iter.Next()) {
1448     auto info = static_cast<const nsRequestInfo*>(iter.Get());
1449 
1450     if (info->mMaxProgress < info->mCurrentProgress) {
1451       return int64_t(-1);
1452     }
1453     max += info->mMaxProgress;
1454   }
1455   return max;
1456 }
1457 
AsyncOnChannelRedirect(nsIChannel * aOldChannel,nsIChannel * aNewChannel,uint32_t aFlags,nsIAsyncVerifyRedirectCallback * cb)1458 NS_IMETHODIMP nsDocLoader::AsyncOnChannelRedirect(
1459     nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
1460     nsIAsyncVerifyRedirectCallback* cb) {
1461   if (aOldChannel) {
1462     nsLoadFlags loadFlags = 0;
1463     int32_t stateFlags = nsIWebProgressListener::STATE_REDIRECTING |
1464                          nsIWebProgressListener::STATE_IS_REQUEST;
1465 
1466     aOldChannel->GetLoadFlags(&loadFlags);
1467     // If the document channel is being redirected, then indicate that the
1468     // document is being redirected in the notification...
1469     if (loadFlags & nsIChannel::LOAD_DOCUMENT_URI) {
1470       stateFlags |= nsIWebProgressListener::STATE_IS_DOCUMENT;
1471 
1472 #if defined(DEBUG)
1473       // We only set mDocumentRequest in OnStartRequest(), but its possible
1474       // to get a redirect before that for service worker interception.
1475       if (mDocumentRequest) {
1476         nsCOMPtr<nsIRequest> request(aOldChannel);
1477         NS_ASSERTION(request == mDocumentRequest, "Wrong Document Channel");
1478       }
1479 #endif /* DEBUG */
1480     }
1481 
1482     OnRedirectStateChange(aOldChannel, aNewChannel, aFlags, stateFlags);
1483     FireOnStateChange(this, aOldChannel, stateFlags, NS_OK);
1484   }
1485 
1486   cb->OnRedirectVerifyCallback(NS_OK);
1487   return NS_OK;
1488 }
1489 
OnSecurityChange(nsISupports * aContext,uint32_t aState)1490 void nsDocLoader::OnSecurityChange(nsISupports* aContext, uint32_t aState) {
1491   //
1492   // Fire progress notifications out to any registered nsIWebProgressListeners.
1493   //
1494 
1495   nsCOMPtr<nsIRequest> request = do_QueryInterface(aContext);
1496   nsIWebProgress* webProgress = static_cast<nsIWebProgress*>(this);
1497 
1498   NOTIFY_LISTENERS(nsIWebProgress::NOTIFY_SECURITY,
1499                    listener->OnSecurityChange(webProgress, request, aState););
1500 
1501   // Pass the notification up to the parent...
1502   if (mParent) {
1503     mParent->OnSecurityChange(aContext, aState);
1504   }
1505 }
1506 
1507 /*
1508  * Implementation of nsISupportsPriority methods...
1509  *
1510  * The priority of the DocLoader _is_ the priority of its LoadGroup.
1511  *
1512  * XXX(darin): Once we start storing loadgroups in loadgroups, this code will
1513  * go away.
1514  */
1515 
GetPriority(int32_t * aPriority)1516 NS_IMETHODIMP nsDocLoader::GetPriority(int32_t* aPriority) {
1517   nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mLoadGroup);
1518   if (p) return p->GetPriority(aPriority);
1519 
1520   *aPriority = 0;
1521   return NS_OK;
1522 }
1523 
SetPriority(int32_t aPriority)1524 NS_IMETHODIMP nsDocLoader::SetPriority(int32_t aPriority) {
1525   MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
1526           ("DocLoader:%p: SetPriority(%d) called\n", this, aPriority));
1527 
1528   nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mLoadGroup);
1529   if (p) p->SetPriority(aPriority);
1530 
1531   NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mChildList, SetPriority,
1532                                            (aPriority));
1533 
1534   return NS_OK;
1535 }
1536 
AdjustPriority(int32_t aDelta)1537 NS_IMETHODIMP nsDocLoader::AdjustPriority(int32_t aDelta) {
1538   MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
1539           ("DocLoader:%p: AdjustPriority(%d) called\n", this, aDelta));
1540 
1541   nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mLoadGroup);
1542   if (p) p->AdjustPriority(aDelta);
1543 
1544   NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mChildList, AdjustPriority,
1545                                            (aDelta));
1546 
1547   return NS_OK;
1548 }
1549 
1550 #if 0
1551 void nsDocLoader::DumpChannelInfo()
1552 {
1553   nsChannelInfo *info;
1554   int32_t i, count;
1555   int32_t current=0, max=0;
1556 
1557 
1558   printf("==== DocLoader=%x\n", this);
1559 
1560   count = mChannelInfoList.Count();
1561   for(i=0; i<count; i++) {
1562     info = (nsChannelInfo *)mChannelInfoList.ElementAt(i);
1563 
1564 #  if defined(DEBUG)
1565     nsAutoCString buffer;
1566     nsresult rv = NS_OK;
1567     if (info->mURI) {
1568       rv = info->mURI->GetSpec(buffer);
1569     }
1570 
1571     printf("  [%d] current=%d  max=%d [%s]\n", i,
1572            info->mCurrentProgress,
1573            info->mMaxProgress, buffer.get());
1574 #  endif /* DEBUG */
1575 
1576     current += info->mCurrentProgress;
1577     if (max >= 0) {
1578       if (info->mMaxProgress < info->mCurrentProgress) {
1579         max = -1;
1580       } else {
1581         max += info->mMaxProgress;
1582       }
1583     }
1584   }
1585 
1586   printf("\nCurrent=%d   Total=%d\n====\n", current, max);
1587 }
1588 #endif   /* 0 */
1589