Bug 1704628 Part 1: Make selectContextMenuItem use .activateItem() semantics. r=ochameau
[gecko.git] / image / ProgressTracker.cpp
bloba9a4b4c73f3133ffd784c9786ac6612bf044cd58
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/Assertions.h"
17 #include "mozilla/Services.h"
19 using mozilla::WeakPtr;
21 namespace mozilla {
22 namespace image {
24 static void CheckProgressConsistency(Progress aOldProgress,
25 Progress aNewProgress, bool aIsMultipart) {
26 // Check preconditions for every progress bit.
28 // Error's do not get propagated from the tracker for each image part to the
29 // tracker for the multipart image because we don't want one bad part to
30 // prevent the remaining parts from showing. So we need to consider whether
31 // this is a tracker for a multipart image for these assertions to work.
33 if (aNewProgress & FLAG_SIZE_AVAILABLE) {
34 // No preconditions.
36 if (aNewProgress & FLAG_DECODE_COMPLETE) {
37 MOZ_ASSERT(aNewProgress & FLAG_SIZE_AVAILABLE);
38 MOZ_ASSERT(aIsMultipart ||
39 aNewProgress & (FLAG_FRAME_COMPLETE | FLAG_HAS_ERROR));
41 if (aNewProgress & FLAG_FRAME_COMPLETE) {
42 MOZ_ASSERT(aNewProgress & FLAG_SIZE_AVAILABLE);
44 if (aNewProgress & FLAG_LOAD_COMPLETE) {
45 MOZ_ASSERT(aIsMultipart ||
46 aNewProgress & (FLAG_SIZE_AVAILABLE | FLAG_HAS_ERROR));
48 if (aNewProgress & FLAG_IS_ANIMATED) {
49 // No preconditions; like FLAG_HAS_TRANSPARENCY, we should normally never
50 // discover this *after* FLAG_SIZE_AVAILABLE, but unfortunately some corrupt
51 // GIFs may fool us.
53 if (aNewProgress & FLAG_HAS_TRANSPARENCY) {
54 // XXX We'd like to assert that transparency is only set during metadata
55 // decode but we don't have any way to assert that until bug 1254892 is
56 // fixed.
58 if (aNewProgress & FLAG_LAST_PART_COMPLETE) {
59 MOZ_ASSERT(aNewProgress & FLAG_LOAD_COMPLETE);
61 if (aNewProgress & FLAG_HAS_ERROR) {
62 // No preconditions.
66 ProgressTracker::ProgressTracker()
67 : mMutex("ProgressTracker::mMutex"),
68 mImage(nullptr),
69 mEventTarget(WrapNotNull(
70 nsCOMPtr<nsIEventTarget>(GetMainThreadSerialEventTarget()))),
71 mObserversWithTargets(0),
72 mObservers(new ObserverTable),
73 mProgress(NoProgress),
74 mIsMultipart(false) {}
76 void ProgressTracker::SetImage(Image* aImage) {
77 MutexAutoLock lock(mMutex);
78 MOZ_ASSERT(aImage, "Setting null image");
79 MOZ_ASSERT(!mImage, "Setting image when we already have one");
80 mImage = aImage;
83 void ProgressTracker::ResetImage() {
84 MutexAutoLock lock(mMutex);
85 MOZ_ASSERT(mImage, "Resetting image when it's already null!");
86 mImage = nullptr;
89 uint32_t ProgressTracker::GetImageStatus() const {
90 uint32_t status = imgIRequest::STATUS_NONE;
92 // Translate our current state to a set of imgIRequest::STATE_* flags.
93 if (mProgress & FLAG_SIZE_AVAILABLE) {
94 status |= imgIRequest::STATUS_SIZE_AVAILABLE;
96 if (mProgress & FLAG_DECODE_COMPLETE) {
97 status |= imgIRequest::STATUS_DECODE_COMPLETE;
99 if (mProgress & FLAG_FRAME_COMPLETE) {
100 status |= imgIRequest::STATUS_FRAME_COMPLETE;
102 if (mProgress & FLAG_LOAD_COMPLETE) {
103 status |= imgIRequest::STATUS_LOAD_COMPLETE;
105 if (mProgress & FLAG_IS_ANIMATED) {
106 status |= imgIRequest::STATUS_IS_ANIMATED;
108 if (mProgress & FLAG_HAS_TRANSPARENCY) {
109 status |= imgIRequest::STATUS_HAS_TRANSPARENCY;
111 if (mProgress & FLAG_HAS_ERROR) {
112 status |= imgIRequest::STATUS_ERROR;
115 return status;
118 // A helper class to allow us to call SyncNotify asynchronously.
119 class AsyncNotifyRunnable : public Runnable {
120 public:
121 AsyncNotifyRunnable(ProgressTracker* aTracker, IProgressObserver* aObserver)
122 : Runnable("ProgressTracker::AsyncNotifyRunnable"), mTracker(aTracker) {
123 MOZ_ASSERT(NS_IsMainThread(), "Should be created on the main thread");
124 MOZ_ASSERT(aTracker, "aTracker should not be null");
125 MOZ_ASSERT(aObserver, "aObserver should not be null");
126 mObservers.AppendElement(aObserver);
129 NS_IMETHOD Run() override {
130 MOZ_ASSERT(NS_IsMainThread(), "Should be running on the main thread");
131 MOZ_ASSERT(mTracker, "mTracker should not be null");
132 for (uint32_t i = 0; i < mObservers.Length(); ++i) {
133 mObservers[i]->ClearPendingNotify();
134 mTracker->SyncNotify(mObservers[i]);
137 mTracker->mRunnable = nullptr;
138 return NS_OK;
141 void AddObserver(IProgressObserver* aObserver) {
142 mObservers.AppendElement(aObserver);
145 void RemoveObserver(IProgressObserver* aObserver) {
146 mObservers.RemoveElement(aObserver);
149 private:
150 friend class ProgressTracker;
152 RefPtr<ProgressTracker> mTracker;
153 nsTArray<RefPtr<IProgressObserver>> mObservers;
156 ProgressTracker::MediumHighRunnable::MediumHighRunnable(
157 already_AddRefed<AsyncNotifyRunnable>&& aEvent)
158 : PrioritizableRunnable(std::move(aEvent),
159 nsIRunnablePriority::PRIORITY_MEDIUMHIGH) {}
161 void ProgressTracker::MediumHighRunnable::AddObserver(
162 IProgressObserver* aObserver) {
163 static_cast<AsyncNotifyRunnable*>(mRunnable.get())->AddObserver(aObserver);
166 void ProgressTracker::MediumHighRunnable::RemoveObserver(
167 IProgressObserver* aObserver) {
168 static_cast<AsyncNotifyRunnable*>(mRunnable.get())->RemoveObserver(aObserver);
171 /* static */
172 already_AddRefed<ProgressTracker::MediumHighRunnable>
173 ProgressTracker::MediumHighRunnable::Create(
174 already_AddRefed<AsyncNotifyRunnable>&& aEvent) {
175 MOZ_ASSERT(NS_IsMainThread());
176 RefPtr<ProgressTracker::MediumHighRunnable> event(
177 new ProgressTracker::MediumHighRunnable(std::move(aEvent)));
178 return event.forget();
181 void ProgressTracker::Notify(IProgressObserver* aObserver) {
182 MOZ_ASSERT(NS_IsMainThread());
184 if (aObserver->NotificationsDeferred()) {
185 // There is a pending notification, or the observer isn't ready yet.
186 return;
189 if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
190 RefPtr<Image> image = GetImage();
191 LOG_FUNC_WITH_PARAM(gImgLog, "ProgressTracker::Notify async", "uri", image);
194 aObserver->MarkPendingNotify();
196 // If we have an existing runnable that we can use, we just append this
197 // observer to its list of observers to be notified. This ensures we don't
198 // unnecessarily delay onload.
199 if (mRunnable) {
200 mRunnable->AddObserver(aObserver);
201 } else {
202 RefPtr<AsyncNotifyRunnable> ev = new AsyncNotifyRunnable(this, aObserver);
203 mRunnable = ProgressTracker::MediumHighRunnable::Create(ev.forget());
204 mEventTarget->Dispatch(mRunnable, NS_DISPATCH_NORMAL);
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 nsCOMPtr<nsIRunnable> ev =
257 new AsyncNotifyCurrentStateRunnable(this, aObserver);
258 mEventTarget->Dispatch(ev.forget(), NS_DISPATCH_NORMAL);
262 * ImageObserverNotifier is a helper type that abstracts over the difference
263 * between sending notifications to all of the observers in an ObserverTable,
264 * and sending them to a single observer. This allows the same notification code
265 * to be used for both cases.
267 template <typename T>
268 struct ImageObserverNotifier;
270 template <>
271 struct MOZ_STACK_CLASS ImageObserverNotifier<const ObserverTable*> {
272 explicit ImageObserverNotifier(const ObserverTable* aObservers,
273 bool aIgnoreDeferral = false)
274 : mObservers(aObservers), mIgnoreDeferral(aIgnoreDeferral) {}
276 template <typename Lambda>
277 void operator()(Lambda aFunc) {
278 for (const auto& weakObserver : mObservers->Values()) {
279 RefPtr<IProgressObserver> observer = weakObserver.get();
280 if (observer && (mIgnoreDeferral || !observer->NotificationsDeferred())) {
281 aFunc(observer);
286 private:
287 const ObserverTable* mObservers;
288 const bool mIgnoreDeferral;
291 template <>
292 struct MOZ_STACK_CLASS ImageObserverNotifier<IProgressObserver*> {
293 explicit ImageObserverNotifier(IProgressObserver* aObserver)
294 : mObserver(aObserver) {}
296 template <typename Lambda>
297 void operator()(Lambda aFunc) {
298 if (mObserver && !mObserver->NotificationsDeferred()) {
299 aFunc(mObserver);
303 private:
304 IProgressObserver* mObserver;
307 template <typename T>
308 void SyncNotifyInternal(const T& aObservers, bool aHasImage, Progress aProgress,
309 const nsIntRect& aDirtyRect) {
310 MOZ_ASSERT(NS_IsMainThread());
312 typedef imgINotificationObserver I;
313 ImageObserverNotifier<T> notify(aObservers);
315 if (aProgress & FLAG_SIZE_AVAILABLE) {
316 notify([](IProgressObserver* aObs) { aObs->Notify(I::SIZE_AVAILABLE); });
319 if (aHasImage) {
320 // OnFrameUpdate
321 // If there's any content in this frame at all (always true for
322 // vector images, true for raster images that have decoded at
323 // least one frame) then send OnFrameUpdate.
324 if (!aDirtyRect.IsEmpty()) {
325 notify([&](IProgressObserver* aObs) {
326 aObs->Notify(I::FRAME_UPDATE, &aDirtyRect);
330 if (aProgress & FLAG_FRAME_COMPLETE) {
331 notify([](IProgressObserver* aObs) { aObs->Notify(I::FRAME_COMPLETE); });
334 if (aProgress & FLAG_HAS_TRANSPARENCY) {
335 notify(
336 [](IProgressObserver* aObs) { aObs->Notify(I::HAS_TRANSPARENCY); });
339 if (aProgress & FLAG_IS_ANIMATED) {
340 notify([](IProgressObserver* aObs) { aObs->Notify(I::IS_ANIMATED); });
344 if (aProgress & FLAG_DECODE_COMPLETE) {
345 MOZ_ASSERT(aHasImage, "Stopped decoding without ever having an image?");
346 notify([](IProgressObserver* aObs) { aObs->Notify(I::DECODE_COMPLETE); });
349 if (aProgress & FLAG_LOAD_COMPLETE) {
350 notify([=](IProgressObserver* aObs) {
351 aObs->OnLoadComplete(aProgress & FLAG_LAST_PART_COMPLETE);
356 void ProgressTracker::SyncNotifyProgress(Progress aProgress,
357 const nsIntRect& aInvalidRect
358 /* = nsIntRect() */) {
359 MOZ_ASSERT(NS_IsMainThread(), "Use mObservers on main thread only");
361 Progress progress = Difference(aProgress);
362 CheckProgressConsistency(mProgress, mProgress | progress, mIsMultipart);
364 // Apply the changes.
365 mProgress |= progress;
367 // Send notifications.
368 mObservers.Read([&](const ObserverTable* aTable) {
369 SyncNotifyInternal(aTable, HasImage(), progress, aInvalidRect);
372 if (progress & FLAG_HAS_ERROR) {
373 FireFailureNotification();
377 void ProgressTracker::SyncNotify(IProgressObserver* aObserver) {
378 MOZ_ASSERT(NS_IsMainThread());
380 RefPtr<Image> image = GetImage();
381 LOG_SCOPE_WITH_PARAM(gImgLog, "ProgressTracker::SyncNotify", "uri", image);
383 nsIntRect rect;
384 if (image) {
385 int32_t width, height;
386 if (NS_FAILED(image->GetWidth(&width)) ||
387 NS_FAILED(image->GetHeight(&height))) {
388 // Either the image has no intrinsic size, or it has an error.
389 rect = GetMaxSizedIntRect();
390 } else {
391 rect.SizeTo(width, height);
395 SyncNotifyInternal(aObserver, !!image, mProgress, rect);
398 void ProgressTracker::EmulateRequestFinished(IProgressObserver* aObserver) {
399 MOZ_ASSERT(NS_IsMainThread(),
400 "SyncNotifyState and mObservers are not threadsafe");
401 RefPtr<IProgressObserver> kungFuDeathGrip(aObserver);
403 if (!(mProgress & FLAG_LOAD_COMPLETE)) {
404 aObserver->OnLoadComplete(true);
408 already_AddRefed<nsIEventTarget> ProgressTracker::GetEventTarget() const {
409 MutexAutoLock lock(mMutex);
410 nsCOMPtr<nsIEventTarget> target = mEventTarget;
411 return target.forget();
414 void ProgressTracker::AddObserver(IProgressObserver* aObserver) {
415 MOZ_ASSERT(NS_IsMainThread());
416 RefPtr<IProgressObserver> observer = aObserver;
418 nsCOMPtr<nsIEventTarget> target = observer->GetEventTarget();
419 if (target) {
420 if (mObserversWithTargets == 0) {
421 // On the first observer with a target (i.e. listener), always accept its
422 // event target; this may be for a specific DocGroup, or it may be the
423 // unlabelled main thread target.
424 MutexAutoLock lock(mMutex);
425 mEventTarget = WrapNotNull(target);
426 } else if (mEventTarget.get() != target.get()) {
427 // If a subsequent observer comes in with a different target, we need to
428 // switch to use the unlabelled main thread target, if we haven't already.
429 MutexAutoLock lock(mMutex);
430 nsCOMPtr<nsIEventTarget> mainTarget(do_GetMainThread());
431 mEventTarget = WrapNotNull(mainTarget);
433 ++mObserversWithTargets;
436 mObservers.Write([=](ObserverTable* aTable) {
437 MOZ_ASSERT(!aTable->Contains(observer),
438 "Adding duplicate entry for image observer");
440 WeakPtr<IProgressObserver> weakPtr = observer.get();
441 aTable->InsertOrUpdate(observer, weakPtr);
444 MOZ_ASSERT(mObserversWithTargets <= ObserverCount());
447 bool ProgressTracker::RemoveObserver(IProgressObserver* aObserver) {
448 MOZ_ASSERT(NS_IsMainThread());
449 RefPtr<IProgressObserver> observer = aObserver;
451 // Remove the observer from the list.
452 bool removed = mObservers.Write(
453 [observer](ObserverTable* aTable) { return aTable->Remove(observer); });
455 // Sometimes once an image is decoded, and all of its observers removed, a new
456 // document may request the same image. Thus we need to clear our event target
457 // state when the last observer is removed, so that we select the most
458 // appropriate event target when a new observer is added. Since the event
459 // target may have changed (e.g. due to the scheduler group going away before
460 // we were removed), so we should be cautious comparing this target against
461 // anything at this stage.
462 if (removed) {
463 nsCOMPtr<nsIEventTarget> target = observer->GetEventTarget();
464 if (target) {
465 MOZ_ASSERT(mObserversWithTargets > 0);
466 --mObserversWithTargets;
468 // If we've shutdown the main thread there's no need to update
469 // event targets.
470 if ((mObserversWithTargets == 0) && !gXPCOMThreadsShutDown) {
471 MutexAutoLock lock(mMutex);
472 nsCOMPtr<nsIEventTarget> target(do_GetMainThread());
473 mEventTarget = WrapNotNull(target);
477 MOZ_ASSERT(mObserversWithTargets <= ObserverCount());
480 // Observers can get confused if they don't get all the proper teardown
481 // notifications. Part ways on good terms.
482 if (removed && !aObserver->NotificationsDeferred()) {
483 EmulateRequestFinished(aObserver);
486 // Make sure we don't give callbacks to an observer that isn't interested in
487 // them any more.
488 if (aObserver->NotificationsDeferred() && mRunnable) {
489 mRunnable->RemoveObserver(aObserver);
490 aObserver->ClearPendingNotify();
493 return removed;
496 uint32_t ProgressTracker::ObserverCount() const {
497 MOZ_ASSERT(NS_IsMainThread());
498 return mObservers.Read(
499 [](const ObserverTable* aTable) { return aTable->Count(); });
502 void ProgressTracker::OnUnlockedDraw() {
503 MOZ_ASSERT(NS_IsMainThread());
504 mObservers.Read([](const ObserverTable* aTable) {
505 ImageObserverNotifier<const ObserverTable*> notify(aTable);
506 notify([](IProgressObserver* aObs) {
507 aObs->Notify(imgINotificationObserver::UNLOCKED_DRAW);
512 void ProgressTracker::ResetForNewRequest() {
513 MOZ_ASSERT(NS_IsMainThread());
514 mProgress = NoProgress;
517 void ProgressTracker::OnDiscard() {
518 MOZ_ASSERT(NS_IsMainThread());
519 mObservers.Read([](const ObserverTable* aTable) {
520 ImageObserverNotifier<const ObserverTable*> notify(aTable);
521 notify([](IProgressObserver* aObs) {
522 aObs->Notify(imgINotificationObserver::DISCARD);
527 void ProgressTracker::OnImageAvailable() {
528 MOZ_ASSERT(NS_IsMainThread());
529 // Notify any imgRequestProxys that are observing us that we have an Image.
530 mObservers.Read([](const ObserverTable* aTable) {
531 ImageObserverNotifier<const ObserverTable*> notify(
532 aTable, /* aIgnoreDeferral = */ true);
533 notify([](IProgressObserver* aObs) { aObs->SetHasImage(); });
537 void ProgressTracker::FireFailureNotification() {
538 MOZ_ASSERT(NS_IsMainThread());
540 // Some kind of problem has happened with image decoding.
541 // Report the URI to net:failed-to-process-uri-conent observers.
542 RefPtr<Image> image = GetImage();
543 if (image) {
544 // Should be on main thread, so ok to create a new nsIURI.
545 nsCOMPtr<nsIURI> uri = image->GetURI();
546 if (uri) {
547 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
548 if (os) {
549 os->NotifyObservers(uri, "net:failed-to-process-uri-content", nullptr);
555 } // namespace image
556 } // namespace mozilla