ProgressTracker.cpp (16409B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "ImageLogging.h" 8 #include "ProgressTracker.h" 9 10 #include "imgINotificationObserver.h" 11 #include "imgIRequest.h" 12 #include "Image.h" 13 #include "nsNetUtil.h" 14 #include "nsIObserverService.h" 15 16 #include "mozilla/AppShutdown.h" 17 #include "mozilla/Assertions.h" 18 #include "mozilla/SchedulerGroup.h" 19 #include "mozilla/Services.h" 20 21 using mozilla::WeakPtr; 22 23 namespace mozilla { 24 namespace image { 25 26 static void CheckProgressConsistency(Progress aOldProgress, 27 Progress aNewProgress, bool aIsMultipart) { 28 // Check preconditions for every progress bit. 29 30 // Error's do not get propagated from the tracker for each image part to the 31 // tracker for the multipart image because we don't want one bad part to 32 // prevent the remaining parts from showing. So we need to consider whether 33 // this is a tracker for a multipart image for these assertions to work. 34 35 if (aNewProgress & FLAG_SIZE_AVAILABLE) { 36 // No preconditions. 37 } 38 if (aNewProgress & FLAG_DECODE_COMPLETE) { 39 MOZ_ASSERT(aNewProgress & FLAG_SIZE_AVAILABLE); 40 MOZ_ASSERT(aIsMultipart || 41 aNewProgress & (FLAG_FRAME_COMPLETE | FLAG_HAS_ERROR)); 42 } 43 if (aNewProgress & FLAG_FRAME_COMPLETE) { 44 MOZ_ASSERT(aNewProgress & FLAG_SIZE_AVAILABLE); 45 } 46 if (aNewProgress & FLAG_LOAD_COMPLETE) { 47 MOZ_ASSERT(aIsMultipart || 48 aNewProgress & (FLAG_SIZE_AVAILABLE | FLAG_HAS_ERROR)); 49 } 50 if (aNewProgress & FLAG_IS_ANIMATED) { 51 // No preconditions; like FLAG_HAS_TRANSPARENCY, we should normally never 52 // discover this *after* FLAG_SIZE_AVAILABLE, but unfortunately some corrupt 53 // GIFs may fool us. 54 } 55 if (aNewProgress & FLAG_HAS_TRANSPARENCY) { 56 // XXX We'd like to assert that transparency is only set during metadata 57 // decode but we don't have any way to assert that until bug 1254892 is 58 // fixed. 59 } 60 if (aNewProgress & FLAG_LAST_PART_COMPLETE) { 61 MOZ_ASSERT(aNewProgress & FLAG_LOAD_COMPLETE); 62 } 63 if (aNewProgress & FLAG_HAS_ERROR) { 64 // No preconditions. 65 } 66 } 67 68 ProgressTracker::ProgressTracker() 69 : mMutex("ProgressTracker::mMutex"), 70 mImage(nullptr), 71 mObservers(new ObserverTable), 72 mProgress(NoProgress), 73 mIsMultipart(false) {} 74 75 void ProgressTracker::SetImage(Image* aImage) { 76 MutexAutoLock lock(mMutex); 77 MOZ_ASSERT(aImage, "Setting null image"); 78 MOZ_ASSERT(!mImage, "Setting image when we already have one"); 79 mImage = aImage; 80 } 81 82 void ProgressTracker::ResetImage() { 83 MutexAutoLock lock(mMutex); 84 MOZ_ASSERT(mImage, "Resetting image when it's already null!"); 85 mImage = nullptr; 86 } 87 88 uint32_t ProgressTracker::GetImageStatus() const { 89 uint32_t status = imgIRequest::STATUS_NONE; 90 91 // Translate our current state to a set of imgIRequest::STATE_* flags. 92 if (mProgress & FLAG_SIZE_AVAILABLE) { 93 status |= imgIRequest::STATUS_SIZE_AVAILABLE; 94 } 95 if (mProgress & FLAG_DECODE_COMPLETE) { 96 status |= imgIRequest::STATUS_DECODE_COMPLETE; 97 } 98 if (mProgress & FLAG_FRAME_COMPLETE) { 99 status |= imgIRequest::STATUS_FRAME_COMPLETE; 100 } 101 if (mProgress & FLAG_LOAD_COMPLETE) { 102 status |= imgIRequest::STATUS_LOAD_COMPLETE; 103 } 104 if (mProgress & FLAG_IS_ANIMATED) { 105 status |= imgIRequest::STATUS_IS_ANIMATED; 106 } 107 if (mProgress & FLAG_HAS_TRANSPARENCY) { 108 status |= imgIRequest::STATUS_HAS_TRANSPARENCY; 109 } 110 if (mProgress & FLAG_HAS_ERROR) { 111 status |= imgIRequest::STATUS_ERROR; 112 } 113 114 return status; 115 } 116 117 // A helper class to allow us to call SyncNotify asynchronously. 118 class AsyncNotifyRunnable : public Runnable { 119 public: 120 AsyncNotifyRunnable(ProgressTracker* aTracker, IProgressObserver* aObserver) 121 : Runnable("ProgressTracker::AsyncNotifyRunnable"), mTracker(aTracker) { 122 MOZ_ASSERT(NS_IsMainThread(), "Should be created on the main thread"); 123 MOZ_ASSERT(aTracker, "aTracker should not be null"); 124 MOZ_ASSERT(aObserver, "aObserver should not be null"); 125 mObservers.AppendElement(aObserver); 126 } 127 128 NS_IMETHOD Run() override { 129 MOZ_ASSERT(NS_IsMainThread(), "Should be running on the main thread"); 130 MOZ_ASSERT(mTracker, "mTracker should not be null"); 131 for (uint32_t i = 0; i < mObservers.Length(); ++i) { 132 mObservers[i]->ClearPendingNotify(); 133 mTracker->SyncNotify(mObservers[i]); 134 } 135 136 mTracker->mRunnable = nullptr; 137 return NS_OK; 138 } 139 140 void AddObserver(IProgressObserver* aObserver) { 141 mObservers.AppendElement(aObserver); 142 } 143 144 void RemoveObserver(IProgressObserver* aObserver) { 145 mObservers.RemoveElement(aObserver); 146 } 147 148 private: 149 friend class ProgressTracker; 150 151 RefPtr<ProgressTracker> mTracker; 152 nsTArray<RefPtr<IProgressObserver>> mObservers; 153 }; 154 155 ProgressTracker::RenderBlockingRunnable::RenderBlockingRunnable( 156 already_AddRefed<AsyncNotifyRunnable>&& aEvent) 157 : PrioritizableRunnable(std::move(aEvent), 158 nsIRunnablePriority::PRIORITY_RENDER_BLOCKING) {} 159 160 void ProgressTracker::RenderBlockingRunnable::AddObserver( 161 IProgressObserver* aObserver) { 162 static_cast<AsyncNotifyRunnable*>(mRunnable.get())->AddObserver(aObserver); 163 } 164 165 void ProgressTracker::RenderBlockingRunnable::RemoveObserver( 166 IProgressObserver* aObserver) { 167 static_cast<AsyncNotifyRunnable*>(mRunnable.get())->RemoveObserver(aObserver); 168 } 169 170 /* static */ 171 already_AddRefed<ProgressTracker::RenderBlockingRunnable> 172 ProgressTracker::RenderBlockingRunnable::Create( 173 already_AddRefed<AsyncNotifyRunnable>&& aEvent) { 174 MOZ_ASSERT(NS_IsMainThread()); 175 RefPtr<ProgressTracker::RenderBlockingRunnable> event( 176 new ProgressTracker::RenderBlockingRunnable(std::move(aEvent))); 177 return event.forget(); 178 } 179 180 void ProgressTracker::Notify(IProgressObserver* aObserver) { 181 MOZ_ASSERT(NS_IsMainThread()); 182 183 if (aObserver->NotificationsDeferred()) { 184 // There is a pending notification, or the observer isn't ready yet. 185 return; 186 } 187 188 if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) { 189 RefPtr<Image> image = GetImage(); 190 LOG_FUNC_WITH_PARAM(gImgLog, "ProgressTracker::Notify async", "uri", image); 191 } 192 193 aObserver->MarkPendingNotify(); 194 195 // If we have an existing runnable that we can use, we just append this 196 // observer to its list of observers to be notified. This ensures we don't 197 // unnecessarily delay onload. 198 if (mRunnable) { 199 mRunnable->AddObserver(aObserver); 200 } else if (!AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownThreads)) { 201 // Avoid dispatch if we are late in shutdown. 202 RefPtr<AsyncNotifyRunnable> ev = new AsyncNotifyRunnable(this, aObserver); 203 mRunnable = ProgressTracker::RenderBlockingRunnable::Create(ev.forget()); 204 SchedulerGroup::Dispatch(do_AddRef(mRunnable)); 205 } 206 } 207 208 // A helper class to allow us to call SyncNotify asynchronously for a given, 209 // fixed, state. 210 class AsyncNotifyCurrentStateRunnable : public Runnable { 211 public: 212 AsyncNotifyCurrentStateRunnable(ProgressTracker* aProgressTracker, 213 IProgressObserver* aObserver) 214 : Runnable("image::AsyncNotifyCurrentStateRunnable"), 215 mProgressTracker(aProgressTracker), 216 mObserver(aObserver) { 217 MOZ_ASSERT(NS_IsMainThread(), "Should be created on the main thread"); 218 MOZ_ASSERT(mProgressTracker, "mProgressTracker should not be null"); 219 MOZ_ASSERT(mObserver, "mObserver should not be null"); 220 mImage = mProgressTracker->GetImage(); 221 } 222 223 NS_IMETHOD Run() override { 224 MOZ_ASSERT(NS_IsMainThread(), "Should be running on the main thread"); 225 mObserver->ClearPendingNotify(); 226 227 mProgressTracker->SyncNotify(mObserver); 228 return NS_OK; 229 } 230 231 private: 232 RefPtr<ProgressTracker> mProgressTracker; 233 RefPtr<IProgressObserver> mObserver; 234 235 // We have to hold on to a reference to the tracker's image, just in case 236 // it goes away while we're in the event queue. 237 RefPtr<Image> mImage; 238 }; 239 240 void ProgressTracker::NotifyCurrentState(IProgressObserver* aObserver) { 241 MOZ_ASSERT(NS_IsMainThread()); 242 243 if (aObserver->NotificationsDeferred()) { 244 // There is a pending notification, or the observer isn't ready yet. 245 return; 246 } 247 248 if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) { 249 RefPtr<Image> image = GetImage(); 250 LOG_FUNC_WITH_PARAM(gImgLog, "ProgressTracker::NotifyCurrentState", "uri", 251 image); 252 } 253 254 aObserver->MarkPendingNotify(); 255 256 // Avoid dispatch if we are late in shutdown. 257 if (!AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownThreads)) { 258 nsCOMPtr<nsIRunnable> ev = 259 new AsyncNotifyCurrentStateRunnable(this, aObserver); 260 SchedulerGroup::Dispatch(ev.forget()); 261 } 262 } 263 264 /** 265 * ImageObserverNotifier is a helper type that abstracts over the difference 266 * between sending notifications to all of the observers in an ObserverTable, 267 * and sending them to a single observer. This allows the same notification code 268 * to be used for both cases. 269 */ 270 template <typename T> 271 struct ImageObserverNotifier; 272 273 template <> 274 struct MOZ_STACK_CLASS ImageObserverNotifier<const ObserverTable*> { 275 explicit ImageObserverNotifier(const ObserverTable* aObservers, 276 bool aIgnoreDeferral = false) 277 : mObservers(aObservers), mIgnoreDeferral(aIgnoreDeferral) {} 278 279 template <typename Lambda> 280 void operator()(Lambda aFunc) { 281 for (const auto& weakObserver : mObservers->Values()) { 282 RefPtr<IProgressObserver> observer = weakObserver.get(); 283 if (observer && (mIgnoreDeferral || !observer->NotificationsDeferred())) { 284 aFunc(observer); 285 } 286 } 287 } 288 289 private: 290 const ObserverTable* mObservers; 291 const bool mIgnoreDeferral; 292 }; 293 294 template <> 295 struct MOZ_STACK_CLASS ImageObserverNotifier<IProgressObserver*> { 296 explicit ImageObserverNotifier(IProgressObserver* aObserver) 297 : mObserver(aObserver) {} 298 299 template <typename Lambda> 300 void operator()(Lambda aFunc) { 301 if (mObserver && !mObserver->NotificationsDeferred()) { 302 aFunc(mObserver); 303 } 304 } 305 306 private: 307 IProgressObserver* mObserver; 308 }; 309 310 template <typename T> 311 void SyncNotifyInternal(const T& aObservers, bool aHasImage, Progress aProgress, 312 const nsIntRect& aDirtyRect) { 313 MOZ_ASSERT(NS_IsMainThread()); 314 315 typedef imgINotificationObserver I; 316 ImageObserverNotifier<T> notify(aObservers); 317 318 if (aProgress & FLAG_SIZE_AVAILABLE) { 319 notify([](IProgressObserver* aObs) { aObs->Notify(I::SIZE_AVAILABLE); }); 320 } 321 322 if (aHasImage) { 323 // OnFrameUpdate 324 // If there's any content in this frame at all (always true for 325 // vector images, true for raster images that have decoded at 326 // least one frame) then send OnFrameUpdate. 327 if (!aDirtyRect.IsEmpty()) { 328 notify([&](IProgressObserver* aObs) { 329 aObs->Notify(I::FRAME_UPDATE, &aDirtyRect); 330 }); 331 } 332 333 if (aProgress & FLAG_FRAME_COMPLETE) { 334 notify([](IProgressObserver* aObs) { aObs->Notify(I::FRAME_COMPLETE); }); 335 } 336 337 if (aProgress & FLAG_HAS_TRANSPARENCY) { 338 notify( 339 [](IProgressObserver* aObs) { aObs->Notify(I::HAS_TRANSPARENCY); }); 340 } 341 342 if (aProgress & FLAG_IS_ANIMATED) { 343 notify([](IProgressObserver* aObs) { aObs->Notify(I::IS_ANIMATED); }); 344 } 345 } 346 347 if (aProgress & FLAG_DECODE_COMPLETE) { 348 MOZ_ASSERT(aHasImage, "Stopped decoding without ever having an image?"); 349 notify([](IProgressObserver* aObs) { aObs->Notify(I::DECODE_COMPLETE); }); 350 } 351 352 if (aProgress & FLAG_LOAD_COMPLETE) { 353 notify([=](IProgressObserver* aObs) { 354 aObs->OnLoadComplete(aProgress & FLAG_LAST_PART_COMPLETE); 355 }); 356 } 357 } 358 359 void ProgressTracker::SyncNotifyProgress(Progress aProgress, 360 const nsIntRect& aInvalidRect 361 /* = nsIntRect() */) { 362 MOZ_ASSERT(NS_IsMainThread(), "Use mObservers on main thread only"); 363 364 Progress progress = Difference(aProgress); 365 CheckProgressConsistency(mProgress, mProgress | progress, mIsMultipart); 366 367 // Apply the changes. 368 mProgress |= progress; 369 370 // Send notifications. 371 mObservers.Read([&](const ObserverTable* aTable) { 372 SyncNotifyInternal(aTable, HasImage(), progress, aInvalidRect); 373 }); 374 375 if (progress & FLAG_HAS_ERROR) { 376 FireFailureNotification(); 377 } 378 } 379 380 void ProgressTracker::SyncNotify(IProgressObserver* aObserver) { 381 MOZ_ASSERT(NS_IsMainThread()); 382 383 RefPtr<Image> image = GetImage(); 384 LOG_SCOPE_WITH_PARAM(gImgLog, "ProgressTracker::SyncNotify", "uri", image); 385 386 nsIntRect rect; 387 if (image) { 388 int32_t width, height; 389 if (NS_FAILED(image->GetWidth(&width)) || 390 NS_FAILED(image->GetHeight(&height))) { 391 // Either the image has no intrinsic size, or it has an error. 392 rect = GetMaxSizedIntRect(); 393 } else { 394 rect.SizeTo(width, height); 395 } 396 } 397 398 SyncNotifyInternal(aObserver, !!image, mProgress, rect); 399 } 400 401 void ProgressTracker::EmulateRequestFinished(IProgressObserver* aObserver) { 402 MOZ_ASSERT(NS_IsMainThread(), 403 "SyncNotifyState and mObservers are not threadsafe"); 404 RefPtr<IProgressObserver> kungFuDeathGrip(aObserver); 405 406 if (!(mProgress & FLAG_LOAD_COMPLETE)) { 407 aObserver->OnLoadComplete(true); 408 } 409 } 410 411 void ProgressTracker::AddObserver(IProgressObserver* aObserver) { 412 MOZ_ASSERT(NS_IsMainThread()); 413 RefPtr<IProgressObserver> observer = aObserver; 414 mObservers.Write([=](ObserverTable* aTable) { 415 MOZ_ASSERT(!aTable->Contains(observer), 416 "Adding duplicate entry for image observer"); 417 418 WeakPtr<IProgressObserver> weakPtr = observer.get(); 419 aTable->InsertOrUpdate(observer, weakPtr); 420 }); 421 } 422 423 bool ProgressTracker::RemoveObserver(IProgressObserver* aObserver) { 424 MOZ_ASSERT(NS_IsMainThread()); 425 RefPtr<IProgressObserver> observer = aObserver; 426 427 // Remove the observer from the list. 428 bool removed = mObservers.Write( 429 [observer](ObserverTable* aTable) { return aTable->Remove(observer); }); 430 431 // Observers can get confused if they don't get all the proper teardown 432 // notifications. Part ways on good terms. 433 if (removed && !aObserver->NotificationsDeferred()) { 434 EmulateRequestFinished(aObserver); 435 } 436 437 // Make sure we don't give callbacks to an observer that isn't interested in 438 // them any more. 439 if (aObserver->NotificationsDeferred() && mRunnable) { 440 mRunnable->RemoveObserver(aObserver); 441 aObserver->ClearPendingNotify(); 442 } 443 444 return removed; 445 } 446 447 uint32_t ProgressTracker::ObserverCount() const { 448 MOZ_ASSERT(NS_IsMainThread()); 449 return mObservers.Read( 450 [](const ObserverTable* aTable) { return aTable->Count(); }); 451 } 452 453 void ProgressTracker::OnUnlockedDraw() { 454 MOZ_ASSERT(NS_IsMainThread()); 455 mObservers.Read([](const ObserverTable* aTable) { 456 ImageObserverNotifier<const ObserverTable*> notify(aTable); 457 notify([](IProgressObserver* aObs) { 458 aObs->Notify(imgINotificationObserver::UNLOCKED_DRAW); 459 }); 460 }); 461 } 462 463 void ProgressTracker::ResetForNewRequest() { 464 MOZ_ASSERT(NS_IsMainThread()); 465 mProgress = NoProgress; 466 } 467 468 void ProgressTracker::OnDiscard() { 469 MOZ_ASSERT(NS_IsMainThread()); 470 mObservers.Read([](const ObserverTable* aTable) { 471 ImageObserverNotifier<const ObserverTable*> notify(aTable); 472 notify([](IProgressObserver* aObs) { 473 aObs->Notify(imgINotificationObserver::DISCARD); 474 }); 475 }); 476 } 477 478 void ProgressTracker::OnImageAvailable() { 479 MOZ_ASSERT(NS_IsMainThread()); 480 // Notify any imgRequestProxys that are observing us that we have an Image. 481 mObservers.Read([](const ObserverTable* aTable) { 482 ImageObserverNotifier<const ObserverTable*> notify( 483 aTable, /* aIgnoreDeferral = */ true); 484 notify([](IProgressObserver* aObs) { aObs->SetHasImage(); }); 485 }); 486 } 487 488 void ProgressTracker::FireFailureNotification() { 489 MOZ_ASSERT(NS_IsMainThread()); 490 491 // Some kind of problem has happened with image decoding. 492 // Report the URI to net:failed-to-process-uri-conent observers. 493 RefPtr<Image> image = GetImage(); 494 if (image) { 495 // Should be on main thread, so ok to create a new nsIURI. 496 nsCOMPtr<nsIURI> uri = image->GetURI(); 497 if (uri) { 498 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); 499 if (os) { 500 os->NotifyObservers(uri, "net:failed-to-process-uri-content", nullptr); 501 } 502 } 503 } 504 } 505 506 } // namespace image 507 } // namespace mozilla