Bug 1869043 assert that graph set access is main thread only r=padenot
[gecko.git] / image / ProgressTracker.cpp
blob7cc378323105090112ce9684ce3c130ef5db886f
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
7 #include "ImageLogging.h"
8 #include "ProgressTracker.h"
10 #include "imgINotificationObserver.h"
11 #include "imgIRequest.h"
12 #include "Image.h"
13 #include "nsNetUtil.h"
14 #include "nsIObserverService.h"
16 #include "mozilla/AppShutdown.h"
17 #include "mozilla/Assertions.h"
18 #include "mozilla/SchedulerGroup.h"
19 #include "mozilla/Services.h"
21 using mozilla::WeakPtr;
23 namespace mozilla {
24 namespace image {
26 static void CheckProgressConsistency(Progress aOldProgress,
27 Progress aNewProgress, bool aIsMultipart) {
28 // Check preconditions for every progress bit.
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.
35 if (aNewProgress & FLAG_SIZE_AVAILABLE) {
36 // No preconditions.
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));
43 if (aNewProgress & FLAG_FRAME_COMPLETE) {
44 MOZ_ASSERT(aNewProgress & FLAG_SIZE_AVAILABLE);
46 if (aNewProgress & FLAG_LOAD_COMPLETE) {
47 MOZ_ASSERT(aIsMultipart ||
48 aNewProgress & (FLAG_SIZE_AVAILABLE | FLAG_HAS_ERROR));
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.
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.
60 if (aNewProgress & FLAG_LAST_PART_COMPLETE) {
61 MOZ_ASSERT(aNewProgress & FLAG_LOAD_COMPLETE);
63 if (aNewProgress & FLAG_HAS_ERROR) {
64 // No preconditions.
68 ProgressTracker::ProgressTracker()
69 : mMutex("ProgressTracker::mMutex"),
70 mImage(nullptr),
71 mObservers(new ObserverTable),
72 mProgress(NoProgress),
73 mIsMultipart(false) {}
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;
82 void ProgressTracker::ResetImage() {
83 MutexAutoLock lock(mMutex);
84 MOZ_ASSERT(mImage, "Resetting image when it's already null!");
85 mImage = nullptr;
88 uint32_t ProgressTracker::GetImageStatus() const {
89 uint32_t status = imgIRequest::STATUS_NONE;
91 // Translate our current state to a set of imgIRequest::STATE_* flags.
92 if (mProgress & FLAG_SIZE_AVAILABLE) {
93 status |= imgIRequest::STATUS_SIZE_AVAILABLE;
95 if (mProgress & FLAG_DECODE_COMPLETE) {
96 status |= imgIRequest::STATUS_DECODE_COMPLETE;
98 if (mProgress & FLAG_FRAME_COMPLETE) {
99 status |= imgIRequest::STATUS_FRAME_COMPLETE;
101 if (mProgress & FLAG_LOAD_COMPLETE) {
102 status |= imgIRequest::STATUS_LOAD_COMPLETE;
104 if (mProgress & FLAG_IS_ANIMATED) {
105 status |= imgIRequest::STATUS_IS_ANIMATED;
107 if (mProgress & FLAG_HAS_TRANSPARENCY) {
108 status |= imgIRequest::STATUS_HAS_TRANSPARENCY;
110 if (mProgress & FLAG_HAS_ERROR) {
111 status |= imgIRequest::STATUS_ERROR;
114 return status;
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);
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]);
136 mTracker->mRunnable = nullptr;
137 return NS_OK;
140 void AddObserver(IProgressObserver* aObserver) {
141 mObservers.AppendElement(aObserver);
144 void RemoveObserver(IProgressObserver* aObserver) {
145 mObservers.RemoveElement(aObserver);
148 private:
149 friend class ProgressTracker;
151 RefPtr<ProgressTracker> mTracker;
152 nsTArray<RefPtr<IProgressObserver>> mObservers;
155 ProgressTracker::RenderBlockingRunnable::RenderBlockingRunnable(
156 already_AddRefed<AsyncNotifyRunnable>&& aEvent)
157 : PrioritizableRunnable(std::move(aEvent),
158 nsIRunnablePriority::PRIORITY_RENDER_BLOCKING) {}
160 void ProgressTracker::RenderBlockingRunnable::AddObserver(
161 IProgressObserver* aObserver) {
162 static_cast<AsyncNotifyRunnable*>(mRunnable.get())->AddObserver(aObserver);
165 void ProgressTracker::RenderBlockingRunnable::RemoveObserver(
166 IProgressObserver* aObserver) {
167 static_cast<AsyncNotifyRunnable*>(mRunnable.get())->RemoveObserver(aObserver);
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();
180 void ProgressTracker::Notify(IProgressObserver* aObserver) {
181 MOZ_ASSERT(NS_IsMainThread());
183 if (aObserver->NotificationsDeferred()) {
184 // There is a pending notification, or the observer isn't ready yet.
185 return;
188 if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
189 RefPtr<Image> image = GetImage();
190 LOG_FUNC_WITH_PARAM(gImgLog, "ProgressTracker::Notify async", "uri", image);
193 aObserver->MarkPendingNotify();
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));
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();
223 NS_IMETHOD Run() override {
224 MOZ_ASSERT(NS_IsMainThread(), "Should be running on the main thread");
225 mObserver->ClearPendingNotify();
227 mProgressTracker->SyncNotify(mObserver);
228 return NS_OK;
231 private:
232 RefPtr<ProgressTracker> mProgressTracker;
233 RefPtr<IProgressObserver> mObserver;
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;
240 void ProgressTracker::NotifyCurrentState(IProgressObserver* aObserver) {
241 MOZ_ASSERT(NS_IsMainThread());
243 if (aObserver->NotificationsDeferred()) {
244 // There is a pending notification, or the observer isn't ready yet.
245 return;
248 if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
249 RefPtr<Image> image = GetImage();
250 LOG_FUNC_WITH_PARAM(gImgLog, "ProgressTracker::NotifyCurrentState", "uri",
251 image);
254 aObserver->MarkPendingNotify();
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());
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.
270 template <typename T>
271 struct ImageObserverNotifier;
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) {}
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);
289 private:
290 const ObserverTable* mObservers;
291 const bool mIgnoreDeferral;
294 template <>
295 struct MOZ_STACK_CLASS ImageObserverNotifier<IProgressObserver*> {
296 explicit ImageObserverNotifier(IProgressObserver* aObserver)
297 : mObserver(aObserver) {}
299 template <typename Lambda>
300 void operator()(Lambda aFunc) {
301 if (mObserver && !mObserver->NotificationsDeferred()) {
302 aFunc(mObserver);
306 private:
307 IProgressObserver* mObserver;
310 template <typename T>
311 void SyncNotifyInternal(const T& aObservers, bool aHasImage, Progress aProgress,
312 const nsIntRect& aDirtyRect) {
313 MOZ_ASSERT(NS_IsMainThread());
315 typedef imgINotificationObserver I;
316 ImageObserverNotifier<T> notify(aObservers);
318 if (aProgress & FLAG_SIZE_AVAILABLE) {
319 notify([](IProgressObserver* aObs) { aObs->Notify(I::SIZE_AVAILABLE); });
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);
333 if (aProgress & FLAG_FRAME_COMPLETE) {
334 notify([](IProgressObserver* aObs) { aObs->Notify(I::FRAME_COMPLETE); });
337 if (aProgress & FLAG_HAS_TRANSPARENCY) {
338 notify(
339 [](IProgressObserver* aObs) { aObs->Notify(I::HAS_TRANSPARENCY); });
342 if (aProgress & FLAG_IS_ANIMATED) {
343 notify([](IProgressObserver* aObs) { aObs->Notify(I::IS_ANIMATED); });
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); });
352 if (aProgress & FLAG_LOAD_COMPLETE) {
353 notify([=](IProgressObserver* aObs) {
354 aObs->OnLoadComplete(aProgress & FLAG_LAST_PART_COMPLETE);
359 void ProgressTracker::SyncNotifyProgress(Progress aProgress,
360 const nsIntRect& aInvalidRect
361 /* = nsIntRect() */) {
362 MOZ_ASSERT(NS_IsMainThread(), "Use mObservers on main thread only");
364 Progress progress = Difference(aProgress);
365 CheckProgressConsistency(mProgress, mProgress | progress, mIsMultipart);
367 // Apply the changes.
368 mProgress |= progress;
370 // Send notifications.
371 mObservers.Read([&](const ObserverTable* aTable) {
372 SyncNotifyInternal(aTable, HasImage(), progress, aInvalidRect);
375 if (progress & FLAG_HAS_ERROR) {
376 FireFailureNotification();
380 void ProgressTracker::SyncNotify(IProgressObserver* aObserver) {
381 MOZ_ASSERT(NS_IsMainThread());
383 RefPtr<Image> image = GetImage();
384 LOG_SCOPE_WITH_PARAM(gImgLog, "ProgressTracker::SyncNotify", "uri", image);
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);
398 SyncNotifyInternal(aObserver, !!image, mProgress, rect);
401 void ProgressTracker::EmulateRequestFinished(IProgressObserver* aObserver) {
402 MOZ_ASSERT(NS_IsMainThread(),
403 "SyncNotifyState and mObservers are not threadsafe");
404 RefPtr<IProgressObserver> kungFuDeathGrip(aObserver);
406 if (!(mProgress & FLAG_LOAD_COMPLETE)) {
407 aObserver->OnLoadComplete(true);
411 already_AddRefed<nsIEventTarget> ProgressTracker::GetEventTarget() const {
412 return do_AddRef(GetMainThreadSerialEventTarget());
415 void ProgressTracker::AddObserver(IProgressObserver* aObserver) {
416 MOZ_ASSERT(NS_IsMainThread());
417 RefPtr<IProgressObserver> observer = aObserver;
418 mObservers.Write([=](ObserverTable* aTable) {
419 MOZ_ASSERT(!aTable->Contains(observer),
420 "Adding duplicate entry for image observer");
422 WeakPtr<IProgressObserver> weakPtr = observer.get();
423 aTable->InsertOrUpdate(observer, weakPtr);
427 bool ProgressTracker::RemoveObserver(IProgressObserver* aObserver) {
428 MOZ_ASSERT(NS_IsMainThread());
429 RefPtr<IProgressObserver> observer = aObserver;
431 // Remove the observer from the list.
432 bool removed = mObservers.Write(
433 [observer](ObserverTable* aTable) { return aTable->Remove(observer); });
435 // Observers can get confused if they don't get all the proper teardown
436 // notifications. Part ways on good terms.
437 if (removed && !aObserver->NotificationsDeferred()) {
438 EmulateRequestFinished(aObserver);
441 // Make sure we don't give callbacks to an observer that isn't interested in
442 // them any more.
443 if (aObserver->NotificationsDeferred() && mRunnable) {
444 mRunnable->RemoveObserver(aObserver);
445 aObserver->ClearPendingNotify();
448 return removed;
451 uint32_t ProgressTracker::ObserverCount() const {
452 MOZ_ASSERT(NS_IsMainThread());
453 return mObservers.Read(
454 [](const ObserverTable* aTable) { return aTable->Count(); });
457 void ProgressTracker::OnUnlockedDraw() {
458 MOZ_ASSERT(NS_IsMainThread());
459 mObservers.Read([](const ObserverTable* aTable) {
460 ImageObserverNotifier<const ObserverTable*> notify(aTable);
461 notify([](IProgressObserver* aObs) {
462 aObs->Notify(imgINotificationObserver::UNLOCKED_DRAW);
467 void ProgressTracker::ResetForNewRequest() {
468 MOZ_ASSERT(NS_IsMainThread());
469 mProgress = NoProgress;
472 void ProgressTracker::OnDiscard() {
473 MOZ_ASSERT(NS_IsMainThread());
474 mObservers.Read([](const ObserverTable* aTable) {
475 ImageObserverNotifier<const ObserverTable*> notify(aTable);
476 notify([](IProgressObserver* aObs) {
477 aObs->Notify(imgINotificationObserver::DISCARD);
482 void ProgressTracker::OnImageAvailable() {
483 MOZ_ASSERT(NS_IsMainThread());
484 // Notify any imgRequestProxys that are observing us that we have an Image.
485 mObservers.Read([](const ObserverTable* aTable) {
486 ImageObserverNotifier<const ObserverTable*> notify(
487 aTable, /* aIgnoreDeferral = */ true);
488 notify([](IProgressObserver* aObs) { aObs->SetHasImage(); });
492 void ProgressTracker::FireFailureNotification() {
493 MOZ_ASSERT(NS_IsMainThread());
495 // Some kind of problem has happened with image decoding.
496 // Report the URI to net:failed-to-process-uri-conent observers.
497 RefPtr<Image> image = GetImage();
498 if (image) {
499 // Should be on main thread, so ok to create a new nsIURI.
500 nsCOMPtr<nsIURI> uri = image->GetURI();
501 if (uri) {
502 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
503 if (os) {
504 os->NotifyObservers(uri, "net:failed-to-process-uri-content", nullptr);
510 } // namespace image
511 } // namespace mozilla