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