forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			432 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			432 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* 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
 | 
