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