diff options
Diffstat (limited to 'docshell/base/BrowsingContextWebProgress.cpp')
-rw-r--r-- | docshell/base/BrowsingContextWebProgress.cpp | 432 |
1 files changed, 432 insertions, 0 deletions
diff --git a/docshell/base/BrowsingContextWebProgress.cpp b/docshell/base/BrowsingContextWebProgress.cpp new file mode 100644 index 0000000000..2c6baf4fa8 --- /dev/null +++ b/docshell/base/BrowsingContextWebProgress.cpp @@ -0,0 +1,432 @@ +/* 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 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "BrowsingContextWebProgress.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "mozilla/ErrorNames.h" +#include "mozilla/Logging.h" +#include "nsCOMPtr.h" +#include "nsIWebProgressListener.h" +#include "nsString.h" +#include "nsPrintfCString.h" +#include "nsIChannel.h" +#include "xptinfo.h" + +namespace mozilla { +namespace dom { + +static mozilla::LazyLogModule gBCWebProgressLog("BCWebProgress"); + +static nsCString DescribeBrowsingContext(CanonicalBrowsingContext* aContext); +static nsCString DescribeWebProgress(nsIWebProgress* aWebProgress); +static nsCString DescribeRequest(nsIRequest* aRequest); +static nsCString DescribeWebProgressFlags(uint32_t aFlags, + const nsACString& aPrefix); +static nsCString DescribeError(nsresult aError); + +NS_IMPL_CYCLE_COLLECTION(BrowsingContextWebProgress, mCurrentBrowsingContext) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(BrowsingContextWebProgress) +NS_IMPL_CYCLE_COLLECTING_RELEASE(BrowsingContextWebProgress) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BrowsingContextWebProgress) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebProgress) + NS_INTERFACE_MAP_ENTRY(nsIWebProgress) + NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener) +NS_INTERFACE_MAP_END + +BrowsingContextWebProgress::BrowsingContextWebProgress( + CanonicalBrowsingContext* aBrowsingContext) + : mCurrentBrowsingContext(aBrowsingContext) {} + +BrowsingContextWebProgress::~BrowsingContextWebProgress() = default; + +NS_IMETHODIMP BrowsingContextWebProgress::AddProgressListener( + nsIWebProgressListener* aListener, uint32_t aNotifyMask) { + nsWeakPtr listener = do_GetWeakReference(aListener); + if (!listener) { + return NS_ERROR_INVALID_ARG; + } + + if (mListenerInfoList.Contains(listener)) { + // The listener is already registered! + return NS_ERROR_FAILURE; + } + + mListenerInfoList.AppendElement(ListenerInfo(listener, aNotifyMask)); + return NS_OK; +} + +NS_IMETHODIMP BrowsingContextWebProgress::RemoveProgressListener( + nsIWebProgressListener* aListener) { + nsWeakPtr listener = do_GetWeakReference(aListener); + if (!listener) { + return NS_ERROR_INVALID_ARG; + } + + return mListenerInfoList.RemoveElement(listener) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP BrowsingContextWebProgress::GetBrowsingContextXPCOM( + BrowsingContext** aBrowsingContext) { + NS_IF_ADDREF(*aBrowsingContext = mCurrentBrowsingContext); + return NS_OK; +} + +BrowsingContext* BrowsingContextWebProgress::GetBrowsingContext() { + return mCurrentBrowsingContext; +} + +NS_IMETHODIMP BrowsingContextWebProgress::GetDOMWindow( + mozIDOMWindowProxy** aDOMWindow) { + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP BrowsingContextWebProgress::GetIsTopLevel(bool* aIsTopLevel) { + *aIsTopLevel = mCurrentBrowsingContext->IsTop(); + return NS_OK; +} + +NS_IMETHODIMP BrowsingContextWebProgress::GetIsLoadingDocument( + bool* aIsLoadingDocument) { + *aIsLoadingDocument = mIsLoadingDocument; + return NS_OK; +} + +NS_IMETHODIMP BrowsingContextWebProgress::GetLoadType(uint32_t* aLoadType) { + *aLoadType = mLoadType; + return NS_OK; +} + +NS_IMETHODIMP BrowsingContextWebProgress::GetTarget(nsIEventTarget** aTarget) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP BrowsingContextWebProgress::SetTarget(nsIEventTarget* aTarget) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +void BrowsingContextWebProgress::UpdateAndNotifyListeners( + uint32_t aFlag, + const std::function<void(nsIWebProgressListener*)>& aCallback) { + RefPtr<BrowsingContextWebProgress> kungFuDeathGrip = this; + + ListenerArray::ForwardIterator iter(mListenerInfoList); + while (iter.HasMore()) { + ListenerInfo& info = iter.GetNext(); + if (!(info.mNotifyMask & aFlag)) { + continue; + } + + nsCOMPtr<nsIWebProgressListener> listener = + do_QueryReferent(info.mWeakListener); + if (!listener) { + mListenerInfoList.RemoveElement(info); + continue; + } + + aCallback(listener); + } + + mListenerInfoList.Compact(); + + // Notify the parent BrowsingContextWebProgress of the event to continue + // propagating. + auto* parent = mCurrentBrowsingContext->GetParent(); + if (parent && parent->GetWebProgress()) { + aCallback(parent->GetWebProgress()); + } +} + +void BrowsingContextWebProgress::ContextDiscarded() { + if (!mIsLoadingDocument) { + return; + } + + // If our BrowsingContext is being discarded while still loading a document, + // fire a synthetic `STATE_STOP` to end the ongoing load. + MOZ_LOG(gBCWebProgressLog, LogLevel::Info, + ("Discarded while loading %s", + DescribeBrowsingContext(mCurrentBrowsingContext).get())); + + // This matches what nsDocLoader::doStopDocumentLoad does, except we don't + // bother notifying for `STATE_STOP | STATE_IS_DOCUMENT`, + // nsBrowserStatusFilter would filter it out before it gets to the parent + // process. + nsCOMPtr<nsIRequest> request = mLoadingDocumentRequest; + OnStateChange(this, request, STATE_STOP | STATE_IS_WINDOW | STATE_IS_NETWORK, + NS_ERROR_ABORT); +} + +void BrowsingContextWebProgress::ContextReplaced( + CanonicalBrowsingContext* aNewContext) { + mCurrentBrowsingContext = aNewContext; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsIWebProgressListener + +NS_IMETHODIMP +BrowsingContextWebProgress::OnStateChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + uint32_t aStateFlags, + nsresult aStatus) { + MOZ_LOG( + gBCWebProgressLog, LogLevel::Info, + ("OnStateChange(%s, %s, %s, %s) on %s", + DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(), + DescribeWebProgressFlags(aStateFlags, "STATE_"_ns).get(), + DescribeError(aStatus).get(), + DescribeBrowsingContext(mCurrentBrowsingContext).get())); + + bool targetIsThis = aWebProgress == this; + + // We may receive a request from an in-process nsDocShell which doesn't have + // `aWebProgress == this` which we should still consider as targeting + // ourselves. + if (nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(aWebProgress); + docShell && docShell->GetBrowsingContext() == mCurrentBrowsingContext) { + targetIsThis = true; + aWebProgress->GetLoadType(&mLoadType); + } + + // Track `mIsLoadingDocument` based on the notifications we've received so far + // if the nsIWebProgress being targeted is this one. + if (targetIsThis) { + constexpr uint32_t startFlags = nsIWebProgressListener::STATE_START | + nsIWebProgressListener::STATE_IS_DOCUMENT | + nsIWebProgressListener::STATE_IS_REQUEST | + nsIWebProgressListener::STATE_IS_WINDOW | + nsIWebProgressListener::STATE_IS_NETWORK; + constexpr uint32_t stopFlags = nsIWebProgressListener::STATE_STOP | + nsIWebProgressListener::STATE_IS_WINDOW; + constexpr uint32_t redirectFlags = + nsIWebProgressListener::STATE_IS_REDIRECTED_DOCUMENT; + if ((aStateFlags & startFlags) == startFlags) { + if (mIsLoadingDocument) { + // We received a duplicate `STATE_START` notification, silence this + // notification until we receive the matching `STATE_STOP` to not fire + // duplicate `STATE_START` notifications into frontend on process + // switches. + return NS_OK; + } + mIsLoadingDocument = true; + + // Record the request we started the load with, so we can emit a synthetic + // `STATE_STOP` notification if the BrowsingContext is discarded before + // the notification arrives. + mLoadingDocumentRequest = aRequest; + } else if ((aStateFlags & stopFlags) == stopFlags) { + // We've received a `STATE_STOP` notification targeting this web progress, + // clear our loading document flag. + mIsLoadingDocument = false; + mLoadingDocumentRequest = nullptr; + } else if (mIsLoadingDocument && + (aStateFlags & redirectFlags) == redirectFlags) { + // If we see a redirected document load, update the loading request which + // we'll emit the synthetic STATE_STOP notification with. + mLoadingDocumentRequest = aRequest; + } + } + + UpdateAndNotifyListeners( + ((aStateFlags >> 16) & nsIWebProgress::NOTIFY_STATE_ALL), + [&](nsIWebProgressListener* listener) { + listener->OnStateChange(aWebProgress, aRequest, aStateFlags, aStatus); + }); + return NS_OK; +} + +NS_IMETHODIMP +BrowsingContextWebProgress::OnProgressChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + int32_t aCurSelfProgress, + int32_t aMaxSelfProgress, + int32_t aCurTotalProgress, + int32_t aMaxTotalProgress) { + MOZ_LOG( + gBCWebProgressLog, LogLevel::Info, + ("OnProgressChange(%s, %s, %d, %d, %d, %d) on %s", + DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(), + aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress, + DescribeBrowsingContext(mCurrentBrowsingContext).get())); + UpdateAndNotifyListeners( + nsIWebProgress::NOTIFY_PROGRESS, [&](nsIWebProgressListener* listener) { + listener->OnProgressChange(aWebProgress, aRequest, aCurSelfProgress, + aMaxSelfProgress, aCurTotalProgress, + aMaxTotalProgress); + }); + return NS_OK; +} + +NS_IMETHODIMP +BrowsingContextWebProgress::OnLocationChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + nsIURI* aLocation, + uint32_t aFlags) { + MOZ_LOG( + gBCWebProgressLog, LogLevel::Info, + ("OnLocationChange(%s, %s, %s, %s) on %s", + DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(), + aLocation ? aLocation->GetSpecOrDefault().get() : "<null>", + DescribeWebProgressFlags(aFlags, "LOCATION_CHANGE_"_ns).get(), + DescribeBrowsingContext(mCurrentBrowsingContext).get())); + UpdateAndNotifyListeners( + nsIWebProgress::NOTIFY_LOCATION, [&](nsIWebProgressListener* listener) { + listener->OnLocationChange(aWebProgress, aRequest, aLocation, aFlags); + }); + return NS_OK; +} + +NS_IMETHODIMP +BrowsingContextWebProgress::OnStatusChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + nsresult aStatus, + const char16_t* aMessage) { + MOZ_LOG( + gBCWebProgressLog, LogLevel::Info, + ("OnStatusChange(%s, %s, %s, \"%s\") on %s", + DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(), + DescribeError(aStatus).get(), NS_ConvertUTF16toUTF8(aMessage).get(), + DescribeBrowsingContext(mCurrentBrowsingContext).get())); + UpdateAndNotifyListeners( + nsIWebProgress::NOTIFY_STATUS, [&](nsIWebProgressListener* listener) { + listener->OnStatusChange(aWebProgress, aRequest, aStatus, aMessage); + }); + return NS_OK; +} + +NS_IMETHODIMP +BrowsingContextWebProgress::OnSecurityChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + uint32_t aState) { + MOZ_LOG( + gBCWebProgressLog, LogLevel::Info, + ("OnSecurityChange(%s, %s, %x) on %s", + DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(), + aState, DescribeBrowsingContext(mCurrentBrowsingContext).get())); + UpdateAndNotifyListeners( + nsIWebProgress::NOTIFY_SECURITY, [&](nsIWebProgressListener* listener) { + listener->OnSecurityChange(aWebProgress, aRequest, aState); + }); + return NS_OK; +} + +NS_IMETHODIMP +BrowsingContextWebProgress::OnContentBlockingEvent(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + uint32_t aEvent) { + MOZ_LOG( + gBCWebProgressLog, LogLevel::Info, + ("OnContentBlockingEvent(%s, %s, %x) on %s", + DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(), + aEvent, DescribeBrowsingContext(mCurrentBrowsingContext).get())); + UpdateAndNotifyListeners(nsIWebProgress::NOTIFY_CONTENT_BLOCKING, + [&](nsIWebProgressListener* listener) { + listener->OnContentBlockingEvent(aWebProgress, + aRequest, aEvent); + }); + return NS_OK; +} + +NS_IMETHODIMP +BrowsingContextWebProgress::GetDocumentRequest(nsIRequest** aRequest) { + NS_IF_ADDREF(*aRequest = mLoadingDocumentRequest); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +// Helper methods for notification logging + +static nsCString DescribeBrowsingContext(CanonicalBrowsingContext* aContext) { + if (!aContext) { + return "<null>"_ns; + } + + nsCOMPtr<nsIURI> currentURI = aContext->GetCurrentURI(); + return nsPrintfCString( + "{top:%d, id:%" PRIx64 ", url:%s}", aContext->IsTop(), aContext->Id(), + currentURI ? currentURI->GetSpecOrDefault().get() : "<null>"); +} + +static nsCString DescribeWebProgress(nsIWebProgress* aWebProgress) { + if (!aWebProgress) { + return "<null>"_ns; + } + + bool isTopLevel = false; + aWebProgress->GetIsTopLevel(&isTopLevel); + bool isLoadingDocument = false; + aWebProgress->GetIsLoadingDocument(&isLoadingDocument); + return nsPrintfCString("{isTopLevel:%d, isLoadingDocument:%d}", isTopLevel, + isLoadingDocument); +} + +static nsCString DescribeRequest(nsIRequest* aRequest) { + if (!aRequest) { + return "<null>"_ns; + } + + nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); + if (!channel) { + return "<non-channel>"_ns; + } + + nsCOMPtr<nsIURI> originalURI; + channel->GetOriginalURI(getter_AddRefs(originalURI)); + nsCOMPtr<nsIURI> uri; + channel->GetURI(getter_AddRefs(uri)); + + return nsPrintfCString( + "{URI:%s, originalURI:%s}", + uri ? uri->GetSpecOrDefault().get() : "<null>", + originalURI ? originalURI->GetSpecOrDefault().get() : "<null>"); +} + +static nsCString DescribeWebProgressFlags(uint32_t aFlags, + const nsACString& aPrefix) { + nsCString flags; + uint32_t remaining = aFlags; + + // Hackily fetch the names of each constant from the XPT information used for + // reflecting it into JS. This doesn't need to be reliable and just exists as + // a logging aid. + // + // If a change to xpt in the future breaks this code, just delete it and + // replace it with a normal hex log. + if (const auto* ifaceInfo = + nsXPTInterfaceInfo::ByIID(NS_GET_IID(nsIWebProgressListener))) { + for (uint16_t i = 0; i < ifaceInfo->ConstantCount(); ++i) { + const auto& constInfo = ifaceInfo->Constant(i); + nsDependentCString name(constInfo.Name()); + if (!StringBeginsWith(name, aPrefix)) { + continue; + } + + if (remaining & constInfo.mValue) { + remaining &= ~constInfo.mValue; + if (!flags.IsEmpty()) { + flags.AppendLiteral("|"); + } + flags.Append(name); + } + } + } + if (remaining != 0 || flags.IsEmpty()) { + if (!flags.IsEmpty()) { + flags.AppendLiteral("|"); + } + flags.AppendInt(remaining, 16); + } + return flags; +} + +static nsCString DescribeError(nsresult aError) { + nsCString name; + GetErrorName(aError, name); + return name; +} + +} // namespace dom +} // namespace mozilla |