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"
12 #include "nsIWebProgressListener.h"
14 #include "nsPrintfCString.h"
15 #include "nsIChannel.h"
17 #include "mozilla/RefPtr.h"
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
)
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
);
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
));
64 NS_IMETHODIMP
BrowsingContextWebProgress::RemoveProgressListener(
65 nsIWebProgressListener
* aListener
) {
66 nsWeakPtr listener
= do_GetWeakReference(aListener
);
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
);
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();
94 NS_IMETHODIMP
BrowsingContextWebProgress::GetIsLoadingDocument(
95 bool* aIsLoadingDocument
) {
96 *aIsLoadingDocument
= mIsLoadingDocument
;
100 NS_IMETHODIMP
BrowsingContextWebProgress::GetLoadType(uint32_t* aLoadType
) {
101 *aLoadType
= mLoadType
;
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(
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
)) {
125 nsCOMPtr
<nsIWebProgressListener
> listener
=
126 do_QueryReferent(info
.mWeakListener
);
128 mListenerInfoList
.RemoveElement(info
);
135 mListenerInfoList
.Compact();
137 // Notify the parent BrowsingContextWebProgress of the event to continue
139 auto* parent
= mCurrentBrowsingContext
->GetParent();
140 if (parent
&& parent
->GetWebProgress()) {
141 aCallback(parent
->GetWebProgress());
145 void BrowsingContextWebProgress::ContextDiscarded() {
146 if (!mIsLoadingDocument
) {
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
160 nsCOMPtr
<nsIRequest
> request
= mLoadingDocumentRequest
;
161 OnStateChange(this, request
, STATE_STOP
| STATE_IS_WINDOW
| STATE_IS_NETWORK
,
165 void BrowsingContextWebProgress::ContextReplaced(
166 CanonicalBrowsingContext
* aNewContext
) {
167 mCurrentBrowsingContext
= aNewContext
;
170 already_AddRefed
<BounceTrackingState
>
171 BrowsingContextWebProgress::GetBounceTrackingState() {
172 if (!mBounceTrackingState
) {
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
185 BrowsingContextWebProgress::OnStateChange(nsIWebProgress
* aWebProgress
,
186 nsIRequest
* aRequest
,
187 uint32_t aStateFlags
,
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
202 if (nsCOMPtr
<nsIDocShell
> docShell
= do_QueryInterface(aWebProgress
);
203 docShell
&& docShell
->GetBrowsingContext() == mCurrentBrowsingContext
) {
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.
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
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
);
256 BrowsingContextWebProgress::OnProgressChange(nsIWebProgress
* aWebProgress
,
257 nsIRequest
* aRequest
,
258 int32_t aCurSelfProgress
,
259 int32_t aMaxSelfProgress
,
260 int32_t aCurTotalProgress
,
261 int32_t aMaxTotalProgress
) {
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
,
278 BrowsingContextWebProgress::OnLocationChange(nsIWebProgress
* aWebProgress
,
279 nsIRequest
* aRequest
,
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
);
297 BrowsingContextWebProgress::OnStatusChange(nsIWebProgress
* aWebProgress
,
298 nsIRequest
* aRequest
,
300 const char16_t
* aMessage
) {
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
);
315 BrowsingContextWebProgress::OnSecurityChange(nsIWebProgress
* aWebProgress
,
316 nsIRequest
* aRequest
,
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
);
331 BrowsingContextWebProgress::OnContentBlockingEvent(nsIWebProgress
* aWebProgress
,
332 nsIRequest
* aRequest
,
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
,
348 BrowsingContextWebProgress::GetDocumentRequest(nsIRequest
** aRequest
) {
349 NS_IF_ADDREF(*aRequest
= mLoadingDocumentRequest
);
353 ////////////////////////////////////////////////////////////////////////////////
354 // Helper methods for notification logging
356 static nsCString
DescribeBrowsingContext(CanonicalBrowsingContext
* aContext
) {
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
) {
372 bool isTopLevel
= false;
373 aWebProgress
->GetIsTopLevel(&isTopLevel
);
374 bool isLoadingDocument
= false;
375 aWebProgress
->GetIsLoadingDocument(&isLoadingDocument
);
376 return nsPrintfCString("{isTopLevel:%d, isLoadingDocument:%d}", isTopLevel
,
380 static nsCString
DescribeRequest(nsIRequest
* aRequest
) {
385 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(aRequest
);
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
) {
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
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
)) {
421 if (remaining
& constInfo
.mValue
) {
422 remaining
&= ~constInfo
.mValue
;
423 if (!flags
.IsEmpty()) {
424 flags
.AppendLiteral("|");
430 if (remaining
!= 0 || flags
.IsEmpty()) {
431 if (!flags
.IsEmpty()) {
432 flags
.AppendLiteral("|");
434 flags
.AppendInt(remaining
, 16);
439 static nsCString
DescribeError(nsresult aError
) {
441 GetErrorName(aError
, name
);
446 } // namespace mozilla