Source code
Revision control
Copy as Markdown
Other Tools
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
#include "nspr.h"
#include "mozilla/dom/BrowserChild.h"
#include "mozilla/dom/Document.h"
#include "mozilla/BasicEvents.h"
#include "mozilla/Components.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/Logging.h"
#include "mozilla/IntegerPrintfMacros.h"
#include "mozilla/PresShell.h"
#include "nsDocLoader.h"
#include "nsDocShell.h"
#include "nsLoadGroup.h"
#include "nsNetUtil.h"
#include "nsIHttpChannel.h"
#include "nsIWebNavigation.h"
#include "nsIWebProgressListener2.h"
#include "nsString.h"
#include "nsCOMPtr.h"
#include "nscore.h"
#include "nsIWeakReferenceUtils.h"
#include "nsQueryObject.h"
#include "nsPIDOMWindow.h"
#include "nsIStringBundle.h"
#include "nsIDocShell.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/DocGroup.h"
#include "nsPresContext.h"
#include "nsIAsyncVerifyRedirectCallback.h"
#include "nsIBrowserDOMWindow.h"
#include "mozilla/ThrottledEventQueue.h"
using namespace mozilla;
using mozilla::DebugOnly;
using mozilla::eLoad;
using mozilla::EventDispatcher;
using mozilla::LogLevel;
using mozilla::WidgetEvent;
using mozilla::dom::BrowserChild;
using mozilla::dom::BrowsingContext;
using mozilla::dom::Document;
//
// Log module for nsIDocumentLoader logging...
//
// To enable logging (see mozilla/Logging.h for full details):
//
// set MOZ_LOG=DocLoader:5
// set MOZ_LOG_FILE=debug.log
//
// this enables LogLevel::Debug level information and places all output in
// the file 'debug.log'.
//
mozilla::LazyLogModule gDocLoaderLog("DocLoader");
#if defined(DEBUG)
void GetURIStringFromRequest(nsIRequest* request, nsACString& name) {
if (request)
request->GetName(name);
else
name.AssignLiteral("???");
}
#endif /* DEBUG */
void nsDocLoader::RequestInfoHashInitEntry(PLDHashEntryHdr* entry,
const void* key) {
// Initialize the entry with placement new
new (entry) nsRequestInfo(key);
}
void nsDocLoader::RequestInfoHashClearEntry(PLDHashTable* table,
PLDHashEntryHdr* entry) {
nsRequestInfo* info = static_cast<nsRequestInfo*>(entry);
info->~nsRequestInfo();
}
// this is used for mListenerInfoList.Contains()
template <>
class nsDefaultComparator<nsDocLoader::nsListenerInfo,
nsIWebProgressListener*> {
public:
bool Equals(const nsDocLoader::nsListenerInfo& aInfo,
nsIWebProgressListener* const& aListener) const {
nsCOMPtr<nsIWebProgressListener> listener =
do_QueryReferent(aInfo.mWeakListener);
return aListener == listener;
}
};
/* static */ const PLDHashTableOps nsDocLoader::sRequestInfoHashOps = {
PLDHashTable::HashVoidPtrKeyStub, PLDHashTable::MatchEntryStub,
PLDHashTable::MoveEntryStub, nsDocLoader::RequestInfoHashClearEntry,
nsDocLoader::RequestInfoHashInitEntry};
nsDocLoader::nsDocLoader(bool aNotifyAboutBackgroundRequests)
: mParent(nullptr),
mProgressStateFlags(0),
mCurrentSelfProgress(0),
mMaxSelfProgress(0),
mCurrentTotalProgress(0),
mMaxTotalProgress(0),
mRequestInfoHash(&sRequestInfoHashOps, sizeof(nsRequestInfo)),
mCompletedTotalProgress(0),
mIsLoadingDocument(false),
mIsRestoringDocument(false),
mDontFlushLayout(false),
mIsFlushingLayout(false),
mDocumentOpenedButNotLoaded(false),
mNotifyAboutBackgroundRequests(aNotifyAboutBackgroundRequests) {
ClearInternalProgress();
MOZ_LOG(gDocLoaderLog, LogLevel::Debug, ("DocLoader:%p: created.\n", this));
}
nsresult nsDocLoader::SetDocLoaderParent(nsDocLoader* aParent) {
mParent = aParent;
return NS_OK;
}
nsresult nsDocLoader::Init() {
RefPtr<net::nsLoadGroup> loadGroup = new net::nsLoadGroup();
nsresult rv = loadGroup->Init();
if (NS_FAILED(rv)) return rv;
loadGroup->SetGroupObserver(this, mNotifyAboutBackgroundRequests);
mLoadGroup = loadGroup;
MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
("DocLoader:%p: load group %p.\n", this, mLoadGroup.get()));
return NS_OK;
}
nsresult nsDocLoader::InitWithBrowsingContext(
BrowsingContext* aBrowsingContext) {
RefPtr<net::nsLoadGroup> loadGroup = new net::nsLoadGroup();
if (!aBrowsingContext->GetRequestContextId()) {
return NS_ERROR_NOT_AVAILABLE;
}
nsresult rv = loadGroup->InitWithRequestContextId(
aBrowsingContext->GetRequestContextId());
if (NS_FAILED(rv)) return rv;
loadGroup->SetGroupObserver(this, mNotifyAboutBackgroundRequests);
mLoadGroup = loadGroup;
MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
("DocLoader:%p: load group %p.\n", this, mLoadGroup.get()));
return NS_OK;
}
nsDocLoader::~nsDocLoader() {
/*
|ClearWeakReferences()| here is intended to prevent people holding
weak references from re-entering this destructor since |QueryReferent()|
will |AddRef()| me, and the subsequent |Release()| will try to destroy me.
At this point there should be only weak references remaining (otherwise, we
wouldn't be getting destroyed).
An alternative would be incrementing our refcount (consider it a
compressed flag saying "Don't re-destroy."). I haven't yet decided which
is better. [scc]
*/
// XXXbz now that NS_IMPL_RELEASE stabilizes by setting refcount to 1, is
// this needed?
ClearWeakReferences();
Destroy();
MOZ_LOG(gDocLoaderLog, LogLevel::Debug, ("DocLoader:%p: deleted.\n", this));
}
/*
* Implementation of ISupports methods...
*/
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDocLoader)
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDocLoader)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDocLoader)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDocumentLoader)
NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
NS_INTERFACE_MAP_ENTRY(nsIDocumentLoader)
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_ENTRY(nsIWebProgress)
NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
NS_INTERFACE_MAP_ENTRY_CONCRETE(nsDocLoader)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTION_WEAK(nsDocLoader, mChildrenInOnload)
/*
* Implementation of nsIInterfaceRequestor methods...
*/
NS_IMETHODIMP nsDocLoader::GetInterface(const nsIID& aIID, void** aSink) {
nsresult rv = NS_ERROR_NO_INTERFACE;
NS_ENSURE_ARG_POINTER(aSink);
if (aIID.Equals(NS_GET_IID(nsILoadGroup))) {
*aSink = mLoadGroup;
NS_IF_ADDREF((nsISupports*)*aSink);
rv = NS_OK;
} else {
rv = QueryInterface(aIID, aSink);
}
return rv;
}
/* static */
already_AddRefed<nsDocLoader> nsDocLoader::GetAsDocLoader(
nsISupports* aSupports) {
RefPtr<nsDocLoader> ret = do_QueryObject(aSupports);
return ret.forget();
}
/* static */
nsresult nsDocLoader::AddDocLoaderAsChildOfRoot(nsDocLoader* aDocLoader) {
nsCOMPtr<nsIDocumentLoader> docLoaderService =
components::DocLoader::Service();
NS_ENSURE_TRUE(docLoaderService, NS_ERROR_UNEXPECTED);
RefPtr<nsDocLoader> rootDocLoader = GetAsDocLoader(docLoaderService);
NS_ENSURE_TRUE(rootDocLoader, NS_ERROR_UNEXPECTED);
return rootDocLoader->AddChildLoader(aDocLoader);
}
MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP nsDocLoader::Stop() {
nsresult rv = NS_OK;
MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
("DocLoader:%p: Stop() called\n", this));
NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mChildList, Stop, ());
if (mLoadGroup) {
rv = mLoadGroup->CancelWithReason(NS_BINDING_ABORTED,
"nsDocLoader::Stop"_ns);
}
// Don't report that we're flushing layout so IsBusy returns false after a
// Stop call.
mIsFlushingLayout = false;
// Clear out mChildrenInOnload. We're not going to fire our onload
// anyway at this point, and there's no issue with mChildrenInOnload
// after this, since mDocumentRequest will be null after the
// DocLoaderIsEmpty() call.
mChildrenInOnload.Clear();
nsCOMPtr<nsIDocShell> ds = do_QueryInterface(GetAsSupports(this));
Document* doc = ds ? ds->GetExtantDocument() : nullptr;
if (doc) {
doc->ClearOOPChildrenLoading();
}
// Make sure to call DocLoaderIsEmpty now so that we reset mDocumentRequest,
// etc, as needed. We could be getting into here from a subframe onload, in
// which case the call to DocLoaderIsEmpty() is coming but hasn't quite
// happened yet, Canceling the loadgroup did nothing (because it was already
// empty), and we're about to start a new load (which is what triggered this
// Stop() call).
// XXXbz If the child frame loadgroups were requests in mLoadgroup, I suspect
// we wouldn't need the call here....
NS_ASSERTION(!IsBusy(), "Shouldn't be busy here");
// If Cancelling the load group only had pending subresource requests, then
// the group status will still be success, and we would fire the load event.
// We want to avoid that when we're aborting the load, so override the status
// with an explicit NS_BINDING_ABORTED value.
DocLoaderIsEmpty(false, Some(NS_BINDING_ABORTED));
return rv;
}
bool nsDocLoader::IsBusy() {
nsresult rv;
//
// A document loader is busy if either:
//
// 1. One of its children is in the middle of an onload handler. Note that
// the handler may have already removed this child from mChildList!
// 2. It is currently loading a document and either has parts of it still
// loading, or has a busy child docloader.
// 3. It's currently flushing layout in DocLoaderIsEmpty().
//
nsCOMPtr<nsIDocShell> ds = do_QueryInterface(GetAsSupports(this));
Document* doc = ds ? ds->GetExtantDocument() : nullptr;
if (!mChildrenInOnload.IsEmpty() || (doc && doc->HasOOPChildrenLoading()) ||
mIsFlushingLayout) {
return true;
}
/* Is this document loader busy? */
if (!IsBlockingLoadEvent()) {
return false;
}
// Check if any in-process sub-document is awaiting its 'load' event:
bool busy;
rv = mLoadGroup->IsPending(&busy);
if (NS_FAILED(rv)) {
return false;
}
if (busy) {
return true;
}
/* check its child document loaders... */
uint32_t count = mChildList.Length();
for (uint32_t i = 0; i < count; i++) {
nsIDocumentLoader* loader = ChildAt(i);
// This is a safe cast, because we only put nsDocLoader objects into the
// array
if (loader && static_cast<nsDocLoader*>(loader)->IsBusy()) {
return true;
}
}
return false;
}
NS_IMETHODIMP
nsDocLoader::GetContainer(nsISupports** aResult) {
NS_ADDREF(*aResult = static_cast<nsIDocumentLoader*>(this));
return NS_OK;
}
NS_IMETHODIMP
nsDocLoader::GetLoadGroup(nsILoadGroup** aResult) {
nsresult rv = NS_OK;
if (nullptr == aResult) {
rv = NS_ERROR_NULL_POINTER;
} else {
*aResult = mLoadGroup;
NS_IF_ADDREF(*aResult);
}
return rv;
}
void nsDocLoader::Destroy() {
Stop();
// Remove the document loader from the parent list of loaders...
if (mParent) {
DebugOnly<nsresult> rv = mParent->RemoveChildLoader(this);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "RemoveChildLoader failed");
}
// Release all the information about network requests...
ClearRequestInfoHash();
mListenerInfoList.Clear();
mListenerInfoList.Compact();
mDocumentRequest = nullptr;
if (mLoadGroup) mLoadGroup->SetGroupObserver(nullptr);
DestroyChildren();
}
void nsDocLoader::DestroyChildren() {
uint32_t count = mChildList.Length();
// if the doc loader still has children...we need to enumerate the
// children and make them null out their back ptr to the parent doc
// loader
for (uint32_t i = 0; i < count; i++) {
nsIDocumentLoader* loader = ChildAt(i);
if (loader) {
// This is a safe cast, as we only put nsDocLoader objects into the
// array
DebugOnly<nsresult> rv =
static_cast<nsDocLoader*>(loader)->SetDocLoaderParent(nullptr);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "SetDocLoaderParent failed");
}
}
mChildList.Clear();
}
NS_IMETHODIMP
nsDocLoader::OnStartRequest(nsIRequest* request) {
// called each time a request is added to the group.
// Some docloaders deal with background requests in their OnStartRequest
// override, but here we don't want to do anything with them, so return early.
nsLoadFlags loadFlags = 0;
request->GetLoadFlags(&loadFlags);
if (loadFlags & nsIRequest::LOAD_BACKGROUND) {
return NS_OK;
}
if (MOZ_LOG_TEST(gDocLoaderLog, LogLevel::Debug)) {
nsAutoCString name;
request->GetName(name);
uint32_t count = 0;
if (mLoadGroup) mLoadGroup->GetActiveCount(&count);
MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
("DocLoader:%p: OnStartRequest[%p](%s) mIsLoadingDocument=%s, %u "
"active URLs",
this, request, name.get(), (mIsLoadingDocument ? "true" : "false"),
count));
}
bool justStartedLoading = false;
if (!mIsLoadingDocument && (loadFlags & nsIChannel::LOAD_DOCUMENT_URI)) {
justStartedLoading = true;
mIsLoadingDocument = true;
mDocumentOpenedButNotLoaded = false;
ClearInternalProgress(); // only clear our progress if we are starting a
// new load....
}
//
// Create a new nsRequestInfo for the request that is starting to
// load...
//
AddRequestInfo(request);
//
// Only fire a doStartDocumentLoad(...) if the document loader
// has initiated a load... Otherwise, this notification has
// resulted from a request being added to the load group.
//
if (mIsLoadingDocument) {
if (loadFlags & nsIChannel::LOAD_DOCUMENT_URI) {
//
// Make sure that the document channel is null at this point...
// (unless its been redirected)
//
NS_ASSERTION((loadFlags & nsIChannel::LOAD_REPLACE) || !mDocumentRequest,
"Overwriting an existing document channel!");
// This request is associated with the entire document...
mDocumentRequest = request;
mLoadGroup->SetDefaultLoadRequest(request);
// Only fire the start document load notification for the first
// document URI... Do not fire it again for redirections
//
if (justStartedLoading) {
// Update the progress status state
mProgressStateFlags = nsIWebProgressListener::STATE_START;
// Fire the start document load notification
doStartDocumentLoad();
return NS_OK;
}
}
}
NS_ASSERTION(!mIsLoadingDocument || mDocumentRequest,
"mDocumentRequest MUST be set for the duration of a page load!");
// This is the only way to catch document request start event after a redirect
// has occurred without changing inherited Firefox behaviour significantly.
// Problem description:
// The combination of |STATE_START + STATE_IS_DOCUMENT| is only sent for
// initial request (see |doStartDocumentLoad| call above).
// And |STATE_REDIRECTING + STATE_IS_DOCUMENT| is sent with old channel, which
// makes it impossible to filter by destination URL (see
// |AsyncOnChannelRedirect| implementation).
// Fixing any of those bugs may cause unpredictable consequences in any part
// of the browser, so we just add a custom flag for this exact situation.
int32_t extraFlags = 0;
if (mIsLoadingDocument && !justStartedLoading &&
(loadFlags & nsIChannel::LOAD_DOCUMENT_URI) &&
(loadFlags & nsIChannel::LOAD_REPLACE)) {
extraFlags = nsIWebProgressListener::STATE_IS_REDIRECTED_DOCUMENT;
}
doStartURLLoad(request, extraFlags);
return NS_OK;
}
MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
nsDocLoader::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
// Some docloaders deal with background requests in their OnStopRequest
// override, but here we don't want to do anything with them, so return early.
nsLoadFlags lf = 0;
aRequest->GetLoadFlags(&lf);
if (lf & nsIRequest::LOAD_BACKGROUND) {
return NS_OK;
}
nsresult rv = NS_OK;
if (MOZ_LOG_TEST(gDocLoaderLog, LogLevel::Debug)) {
nsAutoCString name;
aRequest->GetName(name);
uint32_t count = 0;
if (mLoadGroup) mLoadGroup->GetActiveCount(&count);
MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
("DocLoader:%p: OnStopRequest[%p](%s) status=%" PRIx32
" mIsLoadingDocument=%s, mDocumentOpenedButNotLoaded=%s,"
" %u active URLs",
this, aRequest, name.get(), static_cast<uint32_t>(aStatus),
(mIsLoadingDocument ? "true" : "false"),
(mDocumentOpenedButNotLoaded ? "true" : "false"), count));
}
bool fireTransferring = false;
//
// Set the Maximum progress to the same value as the current progress.
// Since the URI has finished loading, all the data is there. Also,
// this will allow a more accurate estimation of the max progress (in case
// the old value was unknown ie. -1)
//
nsRequestInfo* info = GetRequestInfo(aRequest);
if (info) {
// Null out mLastStatus now so we don't find it when looking for
// status from now on. This destroys the nsStatusInfo and hence
// removes it from our list.
info->mLastStatus = nullptr;
int64_t oldMax = info->mMaxProgress;
info->mMaxProgress = info->mCurrentProgress;
//
// If a request whose content-length was previously unknown has just
// finished loading, then use this new data to try to calculate a
// mMaxSelfProgress...
//
if ((oldMax < int64_t(0)) && (mMaxSelfProgress < int64_t(0))) {
mMaxSelfProgress = CalculateMaxProgress();
}
// As we know the total progress of this request now, save it to be part
// of CalculateMaxProgress() result. We need to remove the info from the
mCompletedTotalProgress += info->mMaxProgress;
//
// Determine whether a STATE_TRANSFERRING notification should be
// 'synthesized'.
//
// If nsRequestInfo::mMaxProgress (as stored in oldMax) and
// nsRequestInfo::mCurrentProgress are both 0, then the
// STATE_TRANSFERRING notification has not been fired yet...
//
if ((oldMax == 0) && (info->mCurrentProgress == 0)) {
nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
// Only fire a TRANSFERRING notification if the request is also a
// channel -- data transfer requires a nsIChannel!
//
if (channel) {
if (NS_SUCCEEDED(aStatus)) {
fireTransferring = true;
}
//
// If the request failed (for any reason other than being
// redirected or retargeted), the TRANSFERRING notification can
// still be fired if a HTTP connection was established to a server.
//
else if (aStatus != NS_BINDING_REDIRECTED &&
aStatus != NS_BINDING_RETARGETED) {
//
//
if (lf & nsIChannel::LOAD_TARGETED) {
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest));
if (httpChannel) {
uint32_t responseCode;
rv = httpChannel->GetResponseStatus(&responseCode);
if (NS_SUCCEEDED(rv)) {
//
// A valid server status indicates that a connection was
// established to the server... So, fire the notification
// even though a failure occurred later...
//
fireTransferring = true;
}
}
}
}
}
}
}
if (fireTransferring) {
// Send a STATE_TRANSFERRING notification for the request.
int32_t flags;
flags = nsIWebProgressListener::STATE_TRANSFERRING |
nsIWebProgressListener::STATE_IS_REQUEST;
//
// Move the WebProgress into the STATE_TRANSFERRING state if necessary...
//
if (mProgressStateFlags & nsIWebProgressListener::STATE_START) {
mProgressStateFlags = nsIWebProgressListener::STATE_TRANSFERRING;
// Send STATE_TRANSFERRING for the document too...
flags |= nsIWebProgressListener::STATE_IS_DOCUMENT;
}
FireOnStateChange(this, aRequest, flags, NS_OK);
}
//
// Fire the OnStateChange(...) notification for stop request
//
doStopURLLoad(aRequest, aStatus);
// Clear this request out of the hash to avoid bypass of FireOnStateChange
// when address of the request is reused.
RemoveRequestInfo(aRequest);
// For the special case where the current document is an initial about:blank
// document, we may still have subframes loading, and keeping the DocLoader
// busy. In that case, if we have an error, we won't show it until those
// frames finish loading, which is nonsensical. So stop any subframe loads
// now.
if (NS_FAILED(aStatus) && aStatus != NS_BINDING_ABORTED &&
aStatus != NS_BINDING_REDIRECTED && aStatus != NS_BINDING_RETARGETED) {
if (RefPtr<Document> doc = do_GetInterface(GetAsSupports(this))) {
if (doc->IsInitialDocument()) {
NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mChildList, Stop, ());
}
}
}
//
// Only fire the DocLoaderIsEmpty(...) if we may need to fire onload.
//
if (IsBlockingLoadEvent()) {
nsCOMPtr<nsIDocShell> ds =
do_QueryInterface(static_cast<nsIRequestObserver*>(this));
bool doNotFlushLayout = false;
if (ds) {
// Don't do unexpected layout flushes while we're in process of restoring
// a document from the bfcache.
ds->GetRestoringDocument(&doNotFlushLayout);
}
DocLoaderIsEmpty(!doNotFlushLayout);
}
return NS_OK;
}
nsresult nsDocLoader::RemoveChildLoader(nsDocLoader* aChild) {
nsresult rv = mChildList.RemoveElement(aChild) ? NS_OK : NS_ERROR_FAILURE;
if (NS_SUCCEEDED(rv)) {
rv = aChild->SetDocLoaderParent(nullptr);
}
return rv;
}
nsresult nsDocLoader::AddChildLoader(nsDocLoader* aChild) {
mChildList.AppendElement(aChild);
return aChild->SetDocLoaderParent(this);
}
NS_IMETHODIMP nsDocLoader::GetDocumentChannel(nsIChannel** aChannel) {
if (!mDocumentRequest) {
*aChannel = nullptr;
return NS_OK;
}
return CallQueryInterface(mDocumentRequest, aChannel);
}
void nsDocLoader::DocLoaderIsEmpty(bool aFlushLayout,
const Maybe<nsresult>& aOverrideStatus) {
if (IsBlockingLoadEvent()) {
/* In the unimagineably rude circumstance that onload event handlers
triggered by this function actually kill the window ... ok, it's
not unimagineable; it's happened ... this deathgrip keeps this object
alive long enough to survive this function call. */
nsCOMPtr<nsIDocumentLoader> kungFuDeathGrip(this);
// Don't flush layout if we're still busy.
if (IsBusy()) {
return;
}
NS_ASSERTION(!mIsFlushingLayout, "Someone screwed up");
// We may not have a document request if we are in a
// document.open() situation.
NS_ASSERTION(mDocumentRequest || mDocumentOpenedButNotLoaded,
"No Document Request!");
// The load group for this DocumentLoader is idle. Flush if we need to.
if (aFlushLayout && !mDontFlushLayout) {
nsCOMPtr<Document> doc = do_GetInterface(GetAsSupports(this));
if (doc) {
// We start loads from style resolution, so we need to flush out style
// no matter what. If we have user fonts, we also need to flush layout,
// since the reflow is what starts font loads.
mozilla::FlushType flushType = mozilla::FlushType::Style;
// Be safe in case this presshell is in teardown now
doc->FlushUserFontSet();
if (doc->GetUserFontSet()) {
flushType = mozilla::FlushType::Layout;
}
mDontFlushLayout = mIsFlushingLayout = true;
doc->FlushPendingNotifications(flushType);
mDontFlushLayout = mIsFlushingLayout = false;
}
}
// And now check whether we're really busy; that might have changed with
// the layout flush.
//
// Note, mDocumentRequest can be null while mDocumentOpenedButNotLoaded is
// false if the flushing above re-entered this method.
if (IsBusy() || (!mDocumentRequest && !mDocumentOpenedButNotLoaded)) {
return;
}
if (mDocumentRequest) {
// Clear out our request info hash, now that our load really is done and
// we don't need it anymore to CalculateMaxProgress().
ClearInternalProgress();
MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
("DocLoader:%p: Is now idle...\n", this));
nsCOMPtr<nsIRequest> docRequest = mDocumentRequest;
mDocumentRequest = nullptr;
mIsLoadingDocument = false;
// Update the progress status state - the document is done
mProgressStateFlags = nsIWebProgressListener::STATE_STOP;
nsresult loadGroupStatus = NS_OK;
if (aOverrideStatus) {
loadGroupStatus = *aOverrideStatus;
} else {
mLoadGroup->GetStatus(&loadGroupStatus);
}
//
// New code to break the circular reference between
// the load group and the docloader...
//
mLoadGroup->SetDefaultLoadRequest(nullptr);
// Take a ref to our parent now so that we can call ChildDoneWithOnload()
// on it even if our onload handler removes us from the docloader tree.
RefPtr<nsDocLoader> parent = mParent;
// Note that if calling ChildEnteringOnload() on the parent returns false
// then calling our onload handler is not safe. That can only happen on
// OOM, so that's ok.
if (!parent || parent->ChildEnteringOnload(this)) {
// Do nothing with our state after firing the
// OnEndDocumentLoad(...). The document loader may be loading a *new*
// document - if LoadDocument() was called from a handler!
//
doStopDocumentLoad(docRequest, loadGroupStatus);
NotifyDoneWithOnload(parent);
}
} else {
MOZ_ASSERT(mDocumentOpenedButNotLoaded);
mDocumentOpenedButNotLoaded = false;
// Make sure we do the ChildEnteringOnload/ChildDoneWithOnload even if we
// plan to skip firing our own load event, because otherwise we might
// never end up firing our parent's load event.
RefPtr<nsDocLoader> parent = mParent;
if (!parent || parent->ChildEnteringOnload(this)) {
nsresult loadGroupStatus = NS_OK;
mLoadGroup->GetStatus(&loadGroupStatus);
// Make sure we're not canceling the loadgroup. If we are, then just
// like the normal navigation case we should not fire a load event.
if (NS_SUCCEEDED(loadGroupStatus) ||
loadGroupStatus == NS_ERROR_PARSED_DATA_CACHED) {
// Can "doc" or "window" ever come back null here? Our state machine
// is complicated enough I wouldn't bet against it...
if (nsCOMPtr<Document> doc = do_GetInterface(GetAsSupports(this))) {
doc->SetReadyStateInternal(Document::READYSTATE_COMPLETE,
/* updateTimingInformation = */ false);
doc->StopDocumentLoad();
nsCOMPtr<nsPIDOMWindowOuter> window = doc->GetWindow();
if (window && !doc->SkipLoadEventAfterClose()) {
MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
("DocLoader:%p: Firing load event for document.open\n",
this));
// This is a very cut-down version of
// nsDocumentViewer::LoadComplete that doesn't do various things
// that are not relevant here because this wasn't an actual
// navigation.
WidgetEvent event(true, eLoad);
event.mFlags.mBubbles = false;
event.mFlags.mCancelable = false;
// Dispatching to |window|, but using |document| as the target,
// per spec.
event.mTarget = doc;
nsEventStatus unused = nsEventStatus_eIgnore;
doc->SetLoadEventFiring(true);
EventDispatcher::Dispatch(
MOZ_KnownLive(nsGlobalWindowOuter::Cast(window)), nullptr,
&event, nullptr, &unused);
doc->SetLoadEventFiring(false);
// Now unsuppress painting on the presshell, if we
// haven't done that yet.
RefPtr<PresShell> presShell = doc->GetPresShell();
if (presShell && !presShell->IsDestroying()) {
presShell->UnsuppressPainting();
if (!presShell->IsDestroying()) {
presShell->LoadComplete();
}
}
}
}
}
NotifyDoneWithOnload(parent);
}
}
}
}
void nsDocLoader::NotifyDoneWithOnload(nsDocLoader* aParent) {
if (aParent) {
// In-process parent:
aParent->ChildDoneWithOnload(this);
}
nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(this);
if (!docShell) {
return;
}
BrowsingContext* bc = nsDocShell::Cast(docShell)->GetBrowsingContext();
if (bc->IsContentSubframe() && !bc->GetParentWindowContext()->IsInProcess()) {
if (BrowserChild* browserChild = BrowserChild::GetFrom(docShell)) {
mozilla::Unused << browserChild->SendMaybeFireEmbedderLoadEvents(
dom::EmbedderElementEventType::NoEvent);
}
}
}
void nsDocLoader::doStartDocumentLoad(void) {
#if defined(DEBUG)
nsAutoCString buffer;
GetURIStringFromRequest(mDocumentRequest, buffer);
MOZ_LOG(
gDocLoaderLog, LogLevel::Debug,
("DocLoader:%p: ++ Firing OnStateChange for start document load (...)."
"\tURI: %s \n",
this, buffer.get()));
#endif /* DEBUG */
// Fire an OnStatus(...) notification STATE_START. This indicates
// that the document represented by mDocumentRequest has started to
// load...
FireOnStateChange(this, mDocumentRequest,
nsIWebProgressListener::STATE_START |
nsIWebProgressListener::STATE_IS_DOCUMENT |
nsIWebProgressListener::STATE_IS_REQUEST |
nsIWebProgressListener::STATE_IS_WINDOW |
nsIWebProgressListener::STATE_IS_NETWORK,
NS_OK);
}
void nsDocLoader::doStartURLLoad(nsIRequest* request, int32_t aExtraFlags) {
#if defined(DEBUG)
nsAutoCString buffer;
GetURIStringFromRequest(request, buffer);
MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
("DocLoader:%p: ++ Firing OnStateChange start url load (...)."
"\tURI: %s\n",
this, buffer.get()));
#endif /* DEBUG */
FireOnStateChange(this, request,
nsIWebProgressListener::STATE_START |
nsIWebProgressListener::STATE_IS_REQUEST | aExtraFlags,
NS_OK);
}
void nsDocLoader::doStopURLLoad(nsIRequest* request, nsresult aStatus) {
#if defined(DEBUG)
nsAutoCString buffer;
GetURIStringFromRequest(request, buffer);
MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
("DocLoader:%p: ++ Firing OnStateChange for end url load (...)."
"\tURI: %s status=%" PRIx32 "\n",
this, buffer.get(), static_cast<uint32_t>(aStatus)));
#endif /* DEBUG */
FireOnStateChange(this, request,
nsIWebProgressListener::STATE_STOP |
nsIWebProgressListener::STATE_IS_REQUEST,
aStatus);
// Fire a status change message for the most recent unfinished
// request to make sure that the displayed status is not outdated.
if (!mStatusInfoList.isEmpty()) {
nsStatusInfo* statusInfo = mStatusInfoList.getFirst();
FireOnStatusChange(this, statusInfo->mRequest, statusInfo->mStatusCode,
statusInfo->mStatusMessage.get());
}
}
void nsDocLoader::doStopDocumentLoad(nsIRequest* request, nsresult aStatus) {
#if defined(DEBUG)
nsAutoCString buffer;
GetURIStringFromRequest(request, buffer);
MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
("DocLoader:%p: ++ Firing OnStateChange for end document load (...)."
"\tURI: %s Status=%" PRIx32 "\n",
this, buffer.get(), static_cast<uint32_t>(aStatus)));
#endif /* DEBUG */
// Firing STATE_STOP|STATE_IS_DOCUMENT will fire onload handlers.
// Grab our parent chain before doing that so we can still dispatch
// STATE_STOP|STATE_IS_WINDW_STATE_IS_NETWORK to them all, even if
// the onload handlers rearrange the docshell tree.
WebProgressList list;
GatherAncestorWebProgresses(list);
//
// Fire an OnStateChange(...) notification indicating the the
// current document has finished loading...
//
int32_t flags = nsIWebProgressListener::STATE_STOP |
nsIWebProgressListener::STATE_IS_DOCUMENT;
for (uint32_t i = 0; i < list.Length(); ++i) {
list[i]->DoFireOnStateChange(this, request, flags, aStatus);
}
//
// Fire a final OnStateChange(...) notification indicating the the
// current document has finished loading...
//
flags = nsIWebProgressListener::STATE_STOP |
nsIWebProgressListener::STATE_IS_WINDOW |
nsIWebProgressListener::STATE_IS_NETWORK;
for (uint32_t i = 0; i < list.Length(); ++i) {
list[i]->DoFireOnStateChange(this, request, flags, aStatus);
}
}
////////////////////////////////////////////////////////////////////////////////////
// The following section contains support for nsIWebProgress and related stuff
////////////////////////////////////////////////////////////////////////////////////
NS_IMETHODIMP
nsDocLoader::AddProgressListener(nsIWebProgressListener* aListener,
uint32_t aNotifyMask) {
if (mListenerInfoList.Contains(aListener)) {
// The listener is already registered!
return NS_ERROR_FAILURE;
}
nsWeakPtr listener = do_GetWeakReference(aListener);
if (!listener) {
return NS_ERROR_INVALID_ARG;
}
mListenerInfoList.AppendElement(nsListenerInfo(listener, aNotifyMask));
return NS_OK;
}
NS_IMETHODIMP
nsDocLoader::RemoveProgressListener(nsIWebProgressListener* aListener) {
return mListenerInfoList.RemoveElement(aListener) ? NS_OK : NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsDocLoader::GetBrowsingContextXPCOM(BrowsingContext** aResult) {
*aResult = nullptr;
return NS_OK;
}
BrowsingContext* nsDocLoader::GetBrowsingContext() { return nullptr; }
NS_IMETHODIMP
nsDocLoader::GetDOMWindow(mozIDOMWindowProxy** aResult) {
return CallGetInterface(this, aResult);
}
NS_IMETHODIMP
nsDocLoader::GetIsTopLevel(bool* aResult) {
nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(this);
*aResult = docShell && docShell->GetBrowsingContext()->IsTop();
return NS_OK;
}
NS_IMETHODIMP
nsDocLoader::GetIsLoadingDocument(bool* aIsLoadingDocument) {
*aIsLoadingDocument = mIsLoadingDocument;
return NS_OK;
}
NS_IMETHODIMP
nsDocLoader::GetLoadType(uint32_t* aLoadType) {
*aLoadType = 0;
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsDocLoader::GetTarget(nsIEventTarget** aTarget) {
nsCOMPtr<nsIEventTarget> target = GetMainThreadSerialEventTarget();
target.forget(aTarget);
return NS_OK;
}
NS_IMETHODIMP
nsDocLoader::SetTarget(nsIEventTarget* aTarget) {
return NS_ERROR_NOT_IMPLEMENTED;
}
int64_t nsDocLoader::GetMaxTotalProgress() {
int64_t newMaxTotal = 0;
uint32_t count = mChildList.Length();
for (uint32_t i = 0; i < count; i++) {
int64_t individualProgress = 0;
nsIDocumentLoader* docloader = ChildAt(i);
if (docloader) {
// Cast is safe since all children are nsDocLoader too
individualProgress = ((nsDocLoader*)docloader)->GetMaxTotalProgress();
}
if (individualProgress < int64_t(0)) // if one of the elements doesn't know
// it's size then none of them do
{
newMaxTotal = int64_t(-1);
break;
} else
newMaxTotal += individualProgress;
}
int64_t progress = -1;
if (mMaxSelfProgress >= int64_t(0) && newMaxTotal >= int64_t(0))
progress = newMaxTotal + mMaxSelfProgress;
return progress;
}
////////////////////////////////////////////////////////////////////////////////////
// The following section contains support for nsIProgressEventSink which is used
// to pass progress and status between the actual request and the doc loader.
// The doc loader then turns around and makes the right web progress calls based
// on this information.
////////////////////////////////////////////////////////////////////////////////////
NS_IMETHODIMP nsDocLoader::OnProgress(nsIRequest* aRequest, int64_t aProgress,
int64_t aProgressMax) {
int64_t progressDelta = 0;
//
// Update the RequestInfo entry with the new progress data
//
if (nsRequestInfo* info = GetRequestInfo(aRequest)) {
// Update info->mCurrentProgress before we call FireOnStateChange,
// since that can make the "info" pointer invalid.
int64_t oldCurrentProgress = info->mCurrentProgress;
progressDelta = aProgress - oldCurrentProgress;
info->mCurrentProgress = aProgress;
// suppress sending STATE_TRANSFERRING if this is upload progress (see bug
// 240053)
if (!info->mUploading && (int64_t(0) == oldCurrentProgress) &&
(int64_t(0) == info->mMaxProgress)) {
//
// If we receive an OnProgress event from a toplevel channel that the URI
// Loader has not yet targeted, then we must suppress the event. This is
// necessary to ensure that webprogresslisteners do not get confused when
//
nsLoadFlags lf = 0;
aRequest->GetLoadFlags(&lf);
if ((lf & nsIChannel::LOAD_DOCUMENT_URI) &&
!(lf & nsIChannel::LOAD_TARGETED)) {
MOZ_LOG(
gDocLoaderLog, LogLevel::Debug,
("DocLoader:%p Ignoring OnProgress while load is not targeted\n",
this));
return NS_OK;
}
//
// This is the first progress notification for the entry. If
// (aMaxProgress != -1) then the content-length of the data is known,
// so update mMaxSelfProgress... Otherwise, set it to -1 to indicate
// that the content-length is no longer known.
//
if (aProgressMax != -1) {
mMaxSelfProgress += aProgressMax;
info->mMaxProgress = aProgressMax;
} else {
mMaxSelfProgress = int64_t(-1);
info->mMaxProgress = int64_t(-1);
}
// Send a STATE_TRANSFERRING notification for the request.
int32_t flags;
flags = nsIWebProgressListener::STATE_TRANSFERRING |
nsIWebProgressListener::STATE_IS_REQUEST;
//
// Move the WebProgress into the STATE_TRANSFERRING state if necessary...
//
if (mProgressStateFlags & nsIWebProgressListener::STATE_START) {
mProgressStateFlags = nsIWebProgressListener::STATE_TRANSFERRING;
// Send STATE_TRANSFERRING for the document too...
flags |= nsIWebProgressListener::STATE_IS_DOCUMENT;
}
FireOnStateChange(this, aRequest, flags, NS_OK);
}
// Update our overall current progress count.
mCurrentSelfProgress += progressDelta;
}
//
// The request is not part of the load group, so ignore its progress
// information...
//
else {
#if defined(DEBUG)
nsAutoCString buffer;
GetURIStringFromRequest(aRequest, buffer);
MOZ_LOG(
gDocLoaderLog, LogLevel::Debug,
("DocLoader:%p OOPS - No Request Info for: %s\n", this, buffer.get()));
#endif /* DEBUG */
return NS_OK;
}
//
// Fire progress notifications out to any registered nsIWebProgressListeners
//
FireOnProgressChange(this, aRequest, aProgress, aProgressMax, progressDelta,
mCurrentTotalProgress, mMaxTotalProgress);
return NS_OK;
}
NS_IMETHODIMP nsDocLoader::OnStatus(nsIRequest* aRequest, nsresult aStatus,
const char16_t* aStatusArg) {
//
// Fire progress notifications out to any registered nsIWebProgressListeners
//
if (aStatus != NS_OK) {
// Remember the current status for this request
nsRequestInfo* info;
info = GetRequestInfo(aRequest);
if (info) {
bool uploading = (aStatus == NS_NET_STATUS_WRITING ||
aStatus == NS_NET_STATUS_SENDING_TO);
// If switching from uploading to downloading (or vice versa), then we
// need to reset our progress counts. This is designed with HTTP form
// submission in mind, where an upload is performed followed by download
// of possibly several documents.
if (info->mUploading != uploading) {
mCurrentSelfProgress = mMaxSelfProgress = 0;
mCurrentTotalProgress = mMaxTotalProgress = 0;
mCompletedTotalProgress = 0;
info->mUploading = uploading;
info->mCurrentProgress = 0;
info->mMaxProgress = 0;
}
}
nsAutoString host;
host.Append(aStatusArg);
nsAutoString msg;
nsresult rv = FormatStatusMessage(aStatus, host, msg);
if (NS_FAILED(rv)) return rv;
// Keep around the message. In case a request finishes, we need to make sure
// to send the status message of another request to our user to that we
// don't display, for example, "Transferring" messages for requests that are
// already done.
if (info) {
if (!info->mLastStatus) {
info->mLastStatus = MakeUnique<nsStatusInfo>(aRequest);
} else {
// We're going to move it to the front of the list, so remove
// it from wherever it is now.
info->mLastStatus->remove();
}
info->mLastStatus->mStatusMessage = msg;
info->mLastStatus->mStatusCode = aStatus;
// Put the info at the front of the list
mStatusInfoList.insertFront(info->mLastStatus.get());
}
FireOnStatusChange(this, aRequest, aStatus, msg.get());
}
return NS_OK;
}
void nsDocLoader::ClearInternalProgress() {
ClearRequestInfoHash();
mCurrentSelfProgress = mMaxSelfProgress = 0;
mCurrentTotalProgress = mMaxTotalProgress = 0;
mCompletedTotalProgress = 0;
mProgressStateFlags = nsIWebProgressListener::STATE_STOP;
}
/* static */
mozilla::Maybe<nsLiteralCString> nsDocLoader::StatusCodeToL10nId(
nsresult aStatus) {
switch (aStatus) {
case NS_NET_STATUS_WRITING:
return mozilla::Some("network-connection-status-wrote"_ns);
case NS_NET_STATUS_READING:
return mozilla::Some("network-connection-status-read"_ns);
case NS_NET_STATUS_RESOLVING_HOST:
return mozilla::Some("network-connection-status-looking-up"_ns);
case NS_NET_STATUS_RESOLVED_HOST:
return mozilla::Some("network-connection-status-looked-up"_ns);
case NS_NET_STATUS_CONNECTING_TO:
return mozilla::Some("network-connection-status-connecting"_ns);
case NS_NET_STATUS_CONNECTED_TO:
return mozilla::Some("network-connection-status-connected"_ns);
case NS_NET_STATUS_TLS_HANDSHAKE_STARTING:
return mozilla::Some("network-connection-status-tls-handshake"_ns);
case NS_NET_STATUS_TLS_HANDSHAKE_ENDED:
return mozilla::Some(
"network-connection-status-tls-handshake-finished"_ns);
case NS_NET_STATUS_SENDING_TO:
return mozilla::Some("network-connection-status-sending-request"_ns);
case NS_NET_STATUS_WAITING_FOR:
return mozilla::Some("network-connection-status-waiting"_ns);
case NS_NET_STATUS_RECEIVING_FROM:
return mozilla::Some("network-connection-status-transferring-data"_ns);
default:
return mozilla::Nothing();
}
}