summaryrefslogtreecommitdiffstats
path: root/docshell/base/BrowsingContextWebProgress.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--docshell/base/BrowsingContextWebProgress.cpp432
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