Bug 1807268 - Fix verifyOpenAllInNewTabsOptionTest UI test r=ohorvath
[gecko.git] / docshell / base / BrowsingContextWebProgress.cpp
blob3b0096377c955d322d1a36f1c454cd1dc6309402
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "BrowsingContextWebProgress.h"
6 #include "mozilla/AlreadyAddRefed.h"
7 #include "mozilla/BounceTrackingState.h"
8 #include "mozilla/dom/CanonicalBrowsingContext.h"
9 #include "mozilla/ErrorNames.h"
10 #include "mozilla/Logging.h"
11 #include "nsCOMPtr.h"
12 #include "nsIWebProgressListener.h"
13 #include "nsString.h"
14 #include "nsPrintfCString.h"
15 #include "nsIChannel.h"
16 #include "xptinfo.h"
17 #include "mozilla/RefPtr.h"
19 namespace mozilla {
20 namespace dom {
22 static mozilla::LazyLogModule gBCWebProgressLog("BCWebProgress");
24 static nsCString DescribeBrowsingContext(CanonicalBrowsingContext* aContext);
25 static nsCString DescribeWebProgress(nsIWebProgress* aWebProgress);
26 static nsCString DescribeRequest(nsIRequest* aRequest);
27 static nsCString DescribeWebProgressFlags(uint32_t aFlags,
28 const nsACString& aPrefix);
29 static nsCString DescribeError(nsresult aError);
31 NS_IMPL_CYCLE_COLLECTION(BrowsingContextWebProgress, mCurrentBrowsingContext)
33 NS_IMPL_CYCLE_COLLECTING_ADDREF(BrowsingContextWebProgress)
34 NS_IMPL_CYCLE_COLLECTING_RELEASE(BrowsingContextWebProgress)
36 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BrowsingContextWebProgress)
37 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebProgress)
38 NS_INTERFACE_MAP_ENTRY(nsIWebProgress)
39 NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener)
40 NS_INTERFACE_MAP_END
42 BrowsingContextWebProgress::BrowsingContextWebProgress(
43 CanonicalBrowsingContext* aBrowsingContext)
44 : mCurrentBrowsingContext(aBrowsingContext) {}
46 BrowsingContextWebProgress::~BrowsingContextWebProgress() = default;
48 NS_IMETHODIMP BrowsingContextWebProgress::AddProgressListener(
49 nsIWebProgressListener* aListener, uint32_t aNotifyMask) {
50 nsWeakPtr listener = do_GetWeakReference(aListener);
51 if (!listener) {
52 return NS_ERROR_INVALID_ARG;
55 if (mListenerInfoList.Contains(listener)) {
56 // The listener is already registered!
57 return NS_ERROR_FAILURE;
60 mListenerInfoList.AppendElement(ListenerInfo(listener, aNotifyMask));
61 return NS_OK;
64 NS_IMETHODIMP BrowsingContextWebProgress::RemoveProgressListener(
65 nsIWebProgressListener* aListener) {
66 nsWeakPtr listener = do_GetWeakReference(aListener);
67 if (!listener) {
68 return NS_ERROR_INVALID_ARG;
71 return mListenerInfoList.RemoveElement(listener) ? NS_OK : NS_ERROR_FAILURE;
74 NS_IMETHODIMP BrowsingContextWebProgress::GetBrowsingContextXPCOM(
75 BrowsingContext** aBrowsingContext) {
76 NS_IF_ADDREF(*aBrowsingContext = mCurrentBrowsingContext);
77 return NS_OK;
80 BrowsingContext* BrowsingContextWebProgress::GetBrowsingContext() {
81 return mCurrentBrowsingContext;
84 NS_IMETHODIMP BrowsingContextWebProgress::GetDOMWindow(
85 mozIDOMWindowProxy** aDOMWindow) {
86 return NS_ERROR_NOT_AVAILABLE;
89 NS_IMETHODIMP BrowsingContextWebProgress::GetIsTopLevel(bool* aIsTopLevel) {
90 *aIsTopLevel = mCurrentBrowsingContext->IsTop();
91 return NS_OK;
94 NS_IMETHODIMP BrowsingContextWebProgress::GetIsLoadingDocument(
95 bool* aIsLoadingDocument) {
96 *aIsLoadingDocument = mIsLoadingDocument;
97 return NS_OK;
100 NS_IMETHODIMP BrowsingContextWebProgress::GetLoadType(uint32_t* aLoadType) {
101 *aLoadType = mLoadType;
102 return NS_OK;
105 NS_IMETHODIMP BrowsingContextWebProgress::GetTarget(nsIEventTarget** aTarget) {
106 return NS_ERROR_NOT_IMPLEMENTED;
109 NS_IMETHODIMP BrowsingContextWebProgress::SetTarget(nsIEventTarget* aTarget) {
110 return NS_ERROR_NOT_IMPLEMENTED;
113 void BrowsingContextWebProgress::UpdateAndNotifyListeners(
114 uint32_t aFlag,
115 const std::function<void(nsIWebProgressListener*)>& aCallback) {
116 RefPtr<BrowsingContextWebProgress> kungFuDeathGrip = this;
118 ListenerArray::ForwardIterator iter(mListenerInfoList);
119 while (iter.HasMore()) {
120 ListenerInfo& info = iter.GetNext();
121 if (!(info.mNotifyMask & aFlag)) {
122 continue;
125 nsCOMPtr<nsIWebProgressListener> listener =
126 do_QueryReferent(info.mWeakListener);
127 if (!listener) {
128 mListenerInfoList.RemoveElement(info);
129 continue;
132 aCallback(listener);
135 mListenerInfoList.Compact();
137 // Notify the parent BrowsingContextWebProgress of the event to continue
138 // propagating.
139 auto* parent = mCurrentBrowsingContext->GetParent();
140 if (parent && parent->GetWebProgress()) {
141 aCallback(parent->GetWebProgress());
145 void BrowsingContextWebProgress::ContextDiscarded() {
146 if (!mIsLoadingDocument) {
147 return;
150 // If our BrowsingContext is being discarded while still loading a document,
151 // fire a synthetic `STATE_STOP` to end the ongoing load.
152 MOZ_LOG(gBCWebProgressLog, LogLevel::Info,
153 ("Discarded while loading %s",
154 DescribeBrowsingContext(mCurrentBrowsingContext).get()));
156 // This matches what nsDocLoader::doStopDocumentLoad does, except we don't
157 // bother notifying for `STATE_STOP | STATE_IS_DOCUMENT`,
158 // nsBrowserStatusFilter would filter it out before it gets to the parent
159 // process.
160 nsCOMPtr<nsIRequest> request = mLoadingDocumentRequest;
161 OnStateChange(this, request, STATE_STOP | STATE_IS_WINDOW | STATE_IS_NETWORK,
162 NS_ERROR_ABORT);
165 void BrowsingContextWebProgress::ContextReplaced(
166 CanonicalBrowsingContext* aNewContext) {
167 mCurrentBrowsingContext = aNewContext;
170 already_AddRefed<BounceTrackingState>
171 BrowsingContextWebProgress::GetBounceTrackingState() {
172 if (!mBounceTrackingState) {
173 nsresult rv = NS_OK;
174 mBounceTrackingState = BounceTrackingState::GetOrCreate(this, rv);
175 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
176 "Failed to get BounceTrackingState.");
178 return do_AddRef(mBounceTrackingState);
181 ////////////////////////////////////////////////////////////////////////////////
182 // nsIWebProgressListener
184 NS_IMETHODIMP
185 BrowsingContextWebProgress::OnStateChange(nsIWebProgress* aWebProgress,
186 nsIRequest* aRequest,
187 uint32_t aStateFlags,
188 nsresult aStatus) {
189 MOZ_LOG(
190 gBCWebProgressLog, LogLevel::Info,
191 ("OnStateChange(%s, %s, %s, %s) on %s",
192 DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(),
193 DescribeWebProgressFlags(aStateFlags, "STATE_"_ns).get(),
194 DescribeError(aStatus).get(),
195 DescribeBrowsingContext(mCurrentBrowsingContext).get()));
197 bool targetIsThis = aWebProgress == this;
199 // We may receive a request from an in-process nsDocShell which doesn't have
200 // `aWebProgress == this` which we should still consider as targeting
201 // ourselves.
202 if (nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(aWebProgress);
203 docShell && docShell->GetBrowsingContext() == mCurrentBrowsingContext) {
204 targetIsThis = true;
205 aWebProgress->GetLoadType(&mLoadType);
208 // Track `mIsLoadingDocument` based on the notifications we've received so far
209 // if the nsIWebProgress being targeted is this one.
210 if (targetIsThis) {
211 constexpr uint32_t startFlags = nsIWebProgressListener::STATE_START |
212 nsIWebProgressListener::STATE_IS_DOCUMENT |
213 nsIWebProgressListener::STATE_IS_REQUEST |
214 nsIWebProgressListener::STATE_IS_WINDOW |
215 nsIWebProgressListener::STATE_IS_NETWORK;
216 constexpr uint32_t stopFlags = nsIWebProgressListener::STATE_STOP |
217 nsIWebProgressListener::STATE_IS_WINDOW;
218 constexpr uint32_t redirectFlags =
219 nsIWebProgressListener::STATE_IS_REDIRECTED_DOCUMENT;
220 if ((aStateFlags & startFlags) == startFlags) {
221 if (mIsLoadingDocument) {
222 // We received a duplicate `STATE_START` notification, silence this
223 // notification until we receive the matching `STATE_STOP` to not fire
224 // duplicate `STATE_START` notifications into frontend on process
225 // switches.
226 return NS_OK;
228 mIsLoadingDocument = true;
230 // Record the request we started the load with, so we can emit a synthetic
231 // `STATE_STOP` notification if the BrowsingContext is discarded before
232 // the notification arrives.
233 mLoadingDocumentRequest = aRequest;
234 } else if ((aStateFlags & stopFlags) == stopFlags) {
235 // We've received a `STATE_STOP` notification targeting this web progress,
236 // clear our loading document flag.
237 mIsLoadingDocument = false;
238 mLoadingDocumentRequest = nullptr;
239 } else if (mIsLoadingDocument &&
240 (aStateFlags & redirectFlags) == redirectFlags) {
241 // If we see a redirected document load, update the loading request which
242 // we'll emit the synthetic STATE_STOP notification with.
243 mLoadingDocumentRequest = aRequest;
247 UpdateAndNotifyListeners(
248 ((aStateFlags >> 16) & nsIWebProgress::NOTIFY_STATE_ALL),
249 [&](nsIWebProgressListener* listener) {
250 listener->OnStateChange(aWebProgress, aRequest, aStateFlags, aStatus);
252 return NS_OK;
255 NS_IMETHODIMP
256 BrowsingContextWebProgress::OnProgressChange(nsIWebProgress* aWebProgress,
257 nsIRequest* aRequest,
258 int32_t aCurSelfProgress,
259 int32_t aMaxSelfProgress,
260 int32_t aCurTotalProgress,
261 int32_t aMaxTotalProgress) {
262 MOZ_LOG(
263 gBCWebProgressLog, LogLevel::Info,
264 ("OnProgressChange(%s, %s, %d, %d, %d, %d) on %s",
265 DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(),
266 aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress,
267 DescribeBrowsingContext(mCurrentBrowsingContext).get()));
268 UpdateAndNotifyListeners(
269 nsIWebProgress::NOTIFY_PROGRESS, [&](nsIWebProgressListener* listener) {
270 listener->OnProgressChange(aWebProgress, aRequest, aCurSelfProgress,
271 aMaxSelfProgress, aCurTotalProgress,
272 aMaxTotalProgress);
274 return NS_OK;
277 NS_IMETHODIMP
278 BrowsingContextWebProgress::OnLocationChange(nsIWebProgress* aWebProgress,
279 nsIRequest* aRequest,
280 nsIURI* aLocation,
281 uint32_t aFlags) {
282 MOZ_LOG(
283 gBCWebProgressLog, LogLevel::Info,
284 ("OnLocationChange(%s, %s, %s, %s) on %s",
285 DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(),
286 aLocation ? aLocation->GetSpecOrDefault().get() : "<null>",
287 DescribeWebProgressFlags(aFlags, "LOCATION_CHANGE_"_ns).get(),
288 DescribeBrowsingContext(mCurrentBrowsingContext).get()));
289 UpdateAndNotifyListeners(
290 nsIWebProgress::NOTIFY_LOCATION, [&](nsIWebProgressListener* listener) {
291 listener->OnLocationChange(aWebProgress, aRequest, aLocation, aFlags);
293 return NS_OK;
296 NS_IMETHODIMP
297 BrowsingContextWebProgress::OnStatusChange(nsIWebProgress* aWebProgress,
298 nsIRequest* aRequest,
299 nsresult aStatus,
300 const char16_t* aMessage) {
301 MOZ_LOG(
302 gBCWebProgressLog, LogLevel::Info,
303 ("OnStatusChange(%s, %s, %s, \"%s\") on %s",
304 DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(),
305 DescribeError(aStatus).get(), NS_ConvertUTF16toUTF8(aMessage).get(),
306 DescribeBrowsingContext(mCurrentBrowsingContext).get()));
307 UpdateAndNotifyListeners(
308 nsIWebProgress::NOTIFY_STATUS, [&](nsIWebProgressListener* listener) {
309 listener->OnStatusChange(aWebProgress, aRequest, aStatus, aMessage);
311 return NS_OK;
314 NS_IMETHODIMP
315 BrowsingContextWebProgress::OnSecurityChange(nsIWebProgress* aWebProgress,
316 nsIRequest* aRequest,
317 uint32_t aState) {
318 MOZ_LOG(
319 gBCWebProgressLog, LogLevel::Info,
320 ("OnSecurityChange(%s, %s, %x) on %s",
321 DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(),
322 aState, DescribeBrowsingContext(mCurrentBrowsingContext).get()));
323 UpdateAndNotifyListeners(
324 nsIWebProgress::NOTIFY_SECURITY, [&](nsIWebProgressListener* listener) {
325 listener->OnSecurityChange(aWebProgress, aRequest, aState);
327 return NS_OK;
330 NS_IMETHODIMP
331 BrowsingContextWebProgress::OnContentBlockingEvent(nsIWebProgress* aWebProgress,
332 nsIRequest* aRequest,
333 uint32_t aEvent) {
334 MOZ_LOG(
335 gBCWebProgressLog, LogLevel::Info,
336 ("OnContentBlockingEvent(%s, %s, %x) on %s",
337 DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(),
338 aEvent, DescribeBrowsingContext(mCurrentBrowsingContext).get()));
339 UpdateAndNotifyListeners(nsIWebProgress::NOTIFY_CONTENT_BLOCKING,
340 [&](nsIWebProgressListener* listener) {
341 listener->OnContentBlockingEvent(aWebProgress,
342 aRequest, aEvent);
344 return NS_OK;
347 NS_IMETHODIMP
348 BrowsingContextWebProgress::GetDocumentRequest(nsIRequest** aRequest) {
349 NS_IF_ADDREF(*aRequest = mLoadingDocumentRequest);
350 return NS_OK;
353 ////////////////////////////////////////////////////////////////////////////////
354 // Helper methods for notification logging
356 static nsCString DescribeBrowsingContext(CanonicalBrowsingContext* aContext) {
357 if (!aContext) {
358 return "<null>"_ns;
361 nsCOMPtr<nsIURI> currentURI = aContext->GetCurrentURI();
362 return nsPrintfCString(
363 "{top:%d, id:%" PRIx64 ", url:%s}", aContext->IsTop(), aContext->Id(),
364 currentURI ? currentURI->GetSpecOrDefault().get() : "<null>");
367 static nsCString DescribeWebProgress(nsIWebProgress* aWebProgress) {
368 if (!aWebProgress) {
369 return "<null>"_ns;
372 bool isTopLevel = false;
373 aWebProgress->GetIsTopLevel(&isTopLevel);
374 bool isLoadingDocument = false;
375 aWebProgress->GetIsLoadingDocument(&isLoadingDocument);
376 return nsPrintfCString("{isTopLevel:%d, isLoadingDocument:%d}", isTopLevel,
377 isLoadingDocument);
380 static nsCString DescribeRequest(nsIRequest* aRequest) {
381 if (!aRequest) {
382 return "<null>"_ns;
385 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
386 if (!channel) {
387 return "<non-channel>"_ns;
390 nsCOMPtr<nsIURI> originalURI;
391 channel->GetOriginalURI(getter_AddRefs(originalURI));
392 nsCOMPtr<nsIURI> uri;
393 channel->GetURI(getter_AddRefs(uri));
395 return nsPrintfCString(
396 "{URI:%s, originalURI:%s}",
397 uri ? uri->GetSpecOrDefault().get() : "<null>",
398 originalURI ? originalURI->GetSpecOrDefault().get() : "<null>");
401 static nsCString DescribeWebProgressFlags(uint32_t aFlags,
402 const nsACString& aPrefix) {
403 nsCString flags;
404 uint32_t remaining = aFlags;
406 // Hackily fetch the names of each constant from the XPT information used for
407 // reflecting it into JS. This doesn't need to be reliable and just exists as
408 // a logging aid.
410 // If a change to xpt in the future breaks this code, just delete it and
411 // replace it with a normal hex log.
412 if (const auto* ifaceInfo =
413 nsXPTInterfaceInfo::ByIID(NS_GET_IID(nsIWebProgressListener))) {
414 for (uint16_t i = 0; i < ifaceInfo->ConstantCount(); ++i) {
415 const auto& constInfo = ifaceInfo->Constant(i);
416 nsDependentCString name(constInfo.Name());
417 if (!StringBeginsWith(name, aPrefix)) {
418 continue;
421 if (remaining & constInfo.mValue) {
422 remaining &= ~constInfo.mValue;
423 if (!flags.IsEmpty()) {
424 flags.AppendLiteral("|");
426 flags.Append(name);
430 if (remaining != 0 || flags.IsEmpty()) {
431 if (!flags.IsEmpty()) {
432 flags.AppendLiteral("|");
434 flags.AppendInt(remaining, 16);
436 return flags;
439 static nsCString DescribeError(nsresult aError) {
440 nsCString name;
441 GetErrorName(aError, name);
442 return name;
445 } // namespace dom
446 } // namespace mozilla