BrowsingContextWebProgress.cpp (17873B)
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/. */ 4 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" 18 19 mozilla::LazyLogModule gBCWebProgressLog("BCWebProgress"); 20 21 namespace mozilla { 22 namespace dom { 23 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); 30 31 NS_IMPL_CYCLE_COLLECTION(BrowsingContextWebProgress, mCurrentBrowsingContext) 32 33 NS_IMPL_CYCLE_COLLECTING_ADDREF(BrowsingContextWebProgress) 34 NS_IMPL_CYCLE_COLLECTING_RELEASE(BrowsingContextWebProgress) 35 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 41 42 BrowsingContextWebProgress::BrowsingContextWebProgress( 43 CanonicalBrowsingContext* aBrowsingContext) 44 : mCurrentBrowsingContext(aBrowsingContext) {} 45 46 BrowsingContextWebProgress::~BrowsingContextWebProgress() = default; 47 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; 53 } 54 55 if (mListenerInfoList.Contains(listener)) { 56 // The listener is already registered! 57 return NS_ERROR_FAILURE; 58 } 59 60 mListenerInfoList.AppendElement(ListenerInfo(listener, aNotifyMask)); 61 return NS_OK; 62 } 63 64 NS_IMETHODIMP BrowsingContextWebProgress::RemoveProgressListener( 65 nsIWebProgressListener* aListener) { 66 nsWeakPtr listener = do_GetWeakReference(aListener); 67 if (!listener) { 68 return NS_ERROR_INVALID_ARG; 69 } 70 71 return mListenerInfoList.RemoveElement(listener) ? NS_OK : NS_ERROR_FAILURE; 72 } 73 74 NS_IMETHODIMP BrowsingContextWebProgress::GetBrowsingContextXPCOM( 75 BrowsingContext** aBrowsingContext) { 76 NS_IF_ADDREF(*aBrowsingContext = mCurrentBrowsingContext); 77 return NS_OK; 78 } 79 80 BrowsingContext* BrowsingContextWebProgress::GetBrowsingContext() { 81 return mCurrentBrowsingContext; 82 } 83 84 NS_IMETHODIMP BrowsingContextWebProgress::GetDOMWindow( 85 mozIDOMWindowProxy** aDOMWindow) { 86 return NS_ERROR_NOT_AVAILABLE; 87 } 88 89 NS_IMETHODIMP BrowsingContextWebProgress::GetIsTopLevel(bool* aIsTopLevel) { 90 *aIsTopLevel = mCurrentBrowsingContext->IsTop(); 91 return NS_OK; 92 } 93 94 NS_IMETHODIMP BrowsingContextWebProgress::GetIsLoadingDocument( 95 bool* aIsLoadingDocument) { 96 *aIsLoadingDocument = mIsLoadingDocument; 97 return NS_OK; 98 } 99 100 NS_IMETHODIMP BrowsingContextWebProgress::GetLoadType(uint32_t* aLoadType) { 101 *aLoadType = mLoadType; 102 return NS_OK; 103 } 104 105 NS_IMETHODIMP BrowsingContextWebProgress::GetTarget(nsIEventTarget** aTarget) { 106 return NS_ERROR_NOT_IMPLEMENTED; 107 } 108 109 NS_IMETHODIMP BrowsingContextWebProgress::SetTarget(nsIEventTarget* aTarget) { 110 return NS_ERROR_NOT_IMPLEMENTED; 111 } 112 113 void BrowsingContextWebProgress::UpdateAndNotifyListeners( 114 uint32_t aFlag, 115 const std::function<void(nsIWebProgressListener*)>& aCallback) { 116 RefPtr<BrowsingContextWebProgress> kungFuDeathGrip = this; 117 118 ListenerArray::ForwardIterator iter(mListenerInfoList); 119 while (iter.HasMore()) { 120 ListenerInfo& info = iter.GetNext(); 121 if (!(info.mNotifyMask & aFlag)) { 122 continue; 123 } 124 125 nsCOMPtr<nsIWebProgressListener> listener = 126 do_QueryReferent(info.mWeakListener); 127 if (!listener) { 128 mListenerInfoList.RemoveElement(info); 129 continue; 130 } 131 132 aCallback(listener); 133 } 134 135 mListenerInfoList.Compact(); 136 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()); 142 } 143 } 144 145 already_AddRefed<nsIWebProgress> BrowsingContextWebProgress::ResolveWebProgress( 146 nsIWebProgress* aWebProgress) { 147 // If we are receiving this notifcation directly from the docshell, 148 // `nsIWebProgress` will be the docshell, instead of the 149 // BrowsingContextWebProgress object. 150 nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(aWebProgress); 151 if (docShell && docShell->GetBrowsingContext()) { 152 if (RefPtr<BrowsingContextWebProgress> progress = 153 docShell->GetBrowsingContext()->Canonical()->GetWebProgress()) { 154 aWebProgress->GetLoadType(&progress->mLoadType); 155 return progress.forget(); 156 } 157 } 158 159 return do_AddRef(aWebProgress); 160 } 161 162 void BrowsingContextWebProgress::ContextDiscarded() { 163 if (mBounceTrackingState) { 164 mBounceTrackingState->OnBrowsingContextDiscarded(); 165 } 166 167 if (!mIsLoadingDocument) { 168 return; 169 } 170 171 // If our BrowsingContext is being discarded while still loading a document, 172 // fire a synthetic `STATE_STOP` to end the ongoing load. 173 MOZ_LOG(gBCWebProgressLog, LogLevel::Info, 174 ("Discarded while loading %s", 175 DescribeBrowsingContext(mCurrentBrowsingContext).get())); 176 177 // This matches what nsDocLoader::doStopDocumentLoad does, except we don't 178 // bother notifying for `STATE_STOP | STATE_IS_DOCUMENT`, 179 // nsBrowserStatusFilter would filter it out before it gets to the parent 180 // process. 181 nsCOMPtr<nsIRequest> request = mLoadingDocumentRequest; 182 OnStateChange(this, request, STATE_STOP | STATE_IS_WINDOW | STATE_IS_NETWORK, 183 NS_ERROR_ABORT); 184 } 185 186 void BrowsingContextWebProgress::ContextReplaced( 187 CanonicalBrowsingContext* aNewContext) { 188 mCurrentBrowsingContext = aNewContext; 189 } 190 191 already_AddRefed<BounceTrackingState> 192 BrowsingContextWebProgress::GetBounceTrackingState() { 193 if (!mBounceTrackingState) { 194 nsresult rv = NS_OK; 195 mBounceTrackingState = BounceTrackingState::GetOrCreate(this, rv); 196 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 197 "Failed to get BounceTrackingState."); 198 } 199 return do_AddRef(mBounceTrackingState); 200 } 201 202 void BrowsingContextWebProgress::DropBounceTrackingState() { 203 mBounceTrackingState = nullptr; 204 } 205 206 //////////////////////////////////////////////////////////////////////////////// 207 // nsIWebProgressListener 208 209 NS_IMETHODIMP 210 BrowsingContextWebProgress::OnStateChange(nsIWebProgress* aWebProgress, 211 nsIRequest* aRequest, 212 uint32_t aStateFlags, 213 nsresult aStatus) { 214 MOZ_LOG( 215 gBCWebProgressLog, LogLevel::Info, 216 ("OnStateChange(%s, %s, %s, %s) on %s", 217 DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(), 218 DescribeWebProgressFlags(aStateFlags, "STATE_"_ns).get(), 219 DescribeError(aStatus).get(), 220 DescribeBrowsingContext(mCurrentBrowsingContext).get())); 221 222 nsCOMPtr<nsIWebProgress> webProgress = ResolveWebProgress(aWebProgress); 223 224 // Track `mIsLoadingDocument` based on the notifications we've received so far 225 // if the nsIWebProgress being targeted is this one. 226 if (webProgress == this) { 227 constexpr uint32_t startFlags = nsIWebProgressListener::STATE_START | 228 nsIWebProgressListener::STATE_IS_DOCUMENT | 229 nsIWebProgressListener::STATE_IS_REQUEST | 230 nsIWebProgressListener::STATE_IS_WINDOW | 231 nsIWebProgressListener::STATE_IS_NETWORK; 232 constexpr uint32_t stopFlags = nsIWebProgressListener::STATE_STOP | 233 nsIWebProgressListener::STATE_IS_WINDOW; 234 constexpr uint32_t redirectFlags = 235 nsIWebProgressListener::STATE_IS_REDIRECTED_DOCUMENT; 236 if ((aStateFlags & startFlags) == startFlags) { 237 if (mIsLoadingDocument) { 238 // We received a duplicate `STATE_START` notification, silence this 239 // notification until we receive the matching `STATE_STOP` to not fire 240 // duplicate `STATE_START` notifications into frontend on process 241 // switches. 242 return NS_OK; 243 } 244 mIsLoadingDocument = true; 245 246 // Record the request we started the load with, so we can emit a synthetic 247 // `STATE_STOP` notification if the BrowsingContext is discarded before 248 // the notification arrives. 249 mLoadingDocumentRequest = aRequest; 250 } else if ((aStateFlags & stopFlags) == stopFlags) { 251 // We've received a `STATE_STOP` notification targeting this web progress, 252 // clear our loading document flag. 253 mIsLoadingDocument = false; 254 mLoadingDocumentRequest = nullptr; 255 } else if (mIsLoadingDocument && 256 (aStateFlags & redirectFlags) == redirectFlags) { 257 // If we see a redirected document load, update the loading request which 258 // we'll emit the synthetic STATE_STOP notification with. 259 mLoadingDocumentRequest = aRequest; 260 } 261 } 262 263 // Remove the STATE_IS_NETWORK bit if necessary. 264 // 265 // The rule is to remove this bit if the notification has been passed up from 266 // a child WebProgress, and the current WebProgress is already active. 267 // 268 // NOTE: Keep this in-sync with the logic in nsDocLoader::DoFireOnStateChange. 269 if (webProgress != this && mIsLoadingDocument && 270 aStateFlags & nsIWebProgressListener::STATE_IS_NETWORK) { 271 aStateFlags &= ~nsIWebProgressListener::STATE_IS_NETWORK; 272 } 273 274 UpdateAndNotifyListeners( 275 ((aStateFlags >> 16) & nsIWebProgress::NOTIFY_STATE_ALL), 276 [&](nsIWebProgressListener* listener) { 277 listener->OnStateChange(webProgress, aRequest, aStateFlags, aStatus); 278 }); 279 return NS_OK; 280 } 281 282 NS_IMETHODIMP 283 BrowsingContextWebProgress::OnProgressChange(nsIWebProgress* aWebProgress, 284 nsIRequest* aRequest, 285 int32_t aCurSelfProgress, 286 int32_t aMaxSelfProgress, 287 int32_t aCurTotalProgress, 288 int32_t aMaxTotalProgress) { 289 MOZ_LOG( 290 gBCWebProgressLog, LogLevel::Info, 291 ("OnProgressChange(%s, %s, %d, %d, %d, %d) on %s", 292 DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(), 293 aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress, 294 DescribeBrowsingContext(mCurrentBrowsingContext).get())); 295 296 nsCOMPtr<nsIWebProgress> webProgress = ResolveWebProgress(aWebProgress); 297 298 UpdateAndNotifyListeners( 299 nsIWebProgress::NOTIFY_PROGRESS, [&](nsIWebProgressListener* listener) { 300 listener->OnProgressChange(webProgress, aRequest, aCurSelfProgress, 301 aMaxSelfProgress, aCurTotalProgress, 302 aMaxTotalProgress); 303 }); 304 return NS_OK; 305 } 306 307 NS_IMETHODIMP 308 BrowsingContextWebProgress::OnLocationChange(nsIWebProgress* aWebProgress, 309 nsIRequest* aRequest, 310 nsIURI* aLocation, 311 uint32_t aFlags) { 312 MOZ_LOG( 313 gBCWebProgressLog, LogLevel::Info, 314 ("OnLocationChange(%s, %s, %s, %s) on %s", 315 DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(), 316 aLocation ? aLocation->GetSpecOrDefault().get() : "<null>", 317 DescribeWebProgressFlags(aFlags, "LOCATION_CHANGE_"_ns).get(), 318 DescribeBrowsingContext(mCurrentBrowsingContext).get())); 319 320 nsCOMPtr<nsIWebProgress> webProgress = ResolveWebProgress(aWebProgress); 321 322 UpdateAndNotifyListeners( 323 nsIWebProgress::NOTIFY_LOCATION, [&](nsIWebProgressListener* listener) { 324 listener->OnLocationChange(webProgress, aRequest, aLocation, aFlags); 325 }); 326 return NS_OK; 327 } 328 329 NS_IMETHODIMP 330 BrowsingContextWebProgress::OnStatusChange(nsIWebProgress* aWebProgress, 331 nsIRequest* aRequest, 332 nsresult aStatus, 333 const char16_t* aMessage) { 334 MOZ_LOG( 335 gBCWebProgressLog, LogLevel::Info, 336 ("OnStatusChange(%s, %s, %s, \"%s\") on %s", 337 DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(), 338 DescribeError(aStatus).get(), NS_ConvertUTF16toUTF8(aMessage).get(), 339 DescribeBrowsingContext(mCurrentBrowsingContext).get())); 340 341 nsCOMPtr<nsIWebProgress> webProgress = ResolveWebProgress(aWebProgress); 342 343 UpdateAndNotifyListeners( 344 nsIWebProgress::NOTIFY_STATUS, [&](nsIWebProgressListener* listener) { 345 listener->OnStatusChange(webProgress, aRequest, aStatus, aMessage); 346 }); 347 return NS_OK; 348 } 349 350 NS_IMETHODIMP 351 BrowsingContextWebProgress::OnSecurityChange(nsIWebProgress* aWebProgress, 352 nsIRequest* aRequest, 353 uint32_t aState) { 354 MOZ_LOG( 355 gBCWebProgressLog, LogLevel::Info, 356 ("OnSecurityChange(%s, %s, %x) on %s", 357 DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(), 358 aState, DescribeBrowsingContext(mCurrentBrowsingContext).get())); 359 360 nsCOMPtr<nsIWebProgress> webProgress = ResolveWebProgress(aWebProgress); 361 362 UpdateAndNotifyListeners( 363 nsIWebProgress::NOTIFY_SECURITY, [&](nsIWebProgressListener* listener) { 364 listener->OnSecurityChange(webProgress, aRequest, aState); 365 }); 366 return NS_OK; 367 } 368 369 NS_IMETHODIMP 370 BrowsingContextWebProgress::OnContentBlockingEvent(nsIWebProgress* aWebProgress, 371 nsIRequest* aRequest, 372 uint32_t aEvent) { 373 MOZ_LOG( 374 gBCWebProgressLog, LogLevel::Info, 375 ("OnContentBlockingEvent(%s, %s, %x) on %s", 376 DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(), 377 aEvent, DescribeBrowsingContext(mCurrentBrowsingContext).get())); 378 379 nsCOMPtr<nsIWebProgress> webProgress = ResolveWebProgress(aWebProgress); 380 381 UpdateAndNotifyListeners(nsIWebProgress::NOTIFY_CONTENT_BLOCKING, 382 [&](nsIWebProgressListener* listener) { 383 listener->OnContentBlockingEvent(webProgress, 384 aRequest, aEvent); 385 }); 386 return NS_OK; 387 } 388 389 NS_IMETHODIMP 390 BrowsingContextWebProgress::GetDocumentRequest(nsIRequest** aRequest) { 391 NS_IF_ADDREF(*aRequest = mLoadingDocumentRequest); 392 return NS_OK; 393 } 394 395 //////////////////////////////////////////////////////////////////////////////// 396 // Helper methods for notification logging 397 398 static nsCString DescribeBrowsingContext(CanonicalBrowsingContext* aContext) { 399 if (!aContext) { 400 return "<null>"_ns; 401 } 402 403 nsCOMPtr<nsIURI> currentURI = aContext->GetCurrentURI(); 404 return nsPrintfCString( 405 "{top:%d, id:%" PRIx64 ", url:%s}", aContext->IsTop(), aContext->Id(), 406 currentURI ? currentURI->GetSpecOrDefault().get() : "<null>"); 407 } 408 409 static nsCString DescribeWebProgress(nsIWebProgress* aWebProgress) { 410 if (!aWebProgress) { 411 return "<null>"_ns; 412 } 413 414 bool isTopLevel = false; 415 aWebProgress->GetIsTopLevel(&isTopLevel); 416 bool isLoadingDocument = false; 417 aWebProgress->GetIsLoadingDocument(&isLoadingDocument); 418 return nsPrintfCString("{isTopLevel:%d, isLoadingDocument:%d}", isTopLevel, 419 isLoadingDocument); 420 } 421 422 static nsCString DescribeRequest(nsIRequest* aRequest) { 423 if (!aRequest) { 424 return "<null>"_ns; 425 } 426 427 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); 428 if (!channel) { 429 return "<non-channel>"_ns; 430 } 431 432 nsCOMPtr<nsIURI> originalURI; 433 channel->GetOriginalURI(getter_AddRefs(originalURI)); 434 nsCOMPtr<nsIURI> uri; 435 channel->GetURI(getter_AddRefs(uri)); 436 437 return nsPrintfCString( 438 "{URI:%s, originalURI:%s}", 439 uri ? uri->GetSpecOrDefault().get() : "<null>", 440 originalURI ? originalURI->GetSpecOrDefault().get() : "<null>"); 441 } 442 443 static nsCString DescribeWebProgressFlags(uint32_t aFlags, 444 const nsACString& aPrefix) { 445 nsCString flags; 446 uint32_t remaining = aFlags; 447 448 // Hackily fetch the names of each constant from the XPT information used for 449 // reflecting it into JS. This doesn't need to be reliable and just exists as 450 // a logging aid. 451 // 452 // If a change to xpt in the future breaks this code, just delete it and 453 // replace it with a normal hex log. 454 if (const auto* ifaceInfo = 455 nsXPTInterfaceInfo::ByIID(NS_GET_IID(nsIWebProgressListener))) { 456 for (uint16_t i = 0; i < ifaceInfo->ConstantCount(); ++i) { 457 const auto& constInfo = ifaceInfo->Constant(i); 458 nsDependentCString name(constInfo.Name()); 459 if (!StringBeginsWith(name, aPrefix)) { 460 continue; 461 } 462 463 if (remaining & constInfo.mValue) { 464 remaining &= ~constInfo.mValue; 465 if (!flags.IsEmpty()) { 466 flags.AppendLiteral("|"); 467 } 468 flags.Append(name); 469 } 470 } 471 } 472 if (remaining != 0 || flags.IsEmpty()) { 473 if (!flags.IsEmpty()) { 474 flags.AppendLiteral("|"); 475 } 476 flags.AppendInt(remaining, 16); 477 } 478 return flags; 479 } 480 481 static nsCString DescribeError(nsresult aError) { 482 nsCString name; 483 GetErrorName(aError, name); 484 return name; 485 } 486 487 } // namespace dom 488 } // namespace mozilla