Bug 1586798 - Use WalkerFront from the currently selected element in onTagEdit()...
[gecko.git] / image / ProgressTracker.cpp
blob236a8b0849ce0c71868cc383b820d0ca560501d7
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 "imgIContainer.h"
11 #include "imgINotificationObserver.h"
12 #include "imgIRequest.h"
13 #include "Image.h"
14 #include "nsNetUtil.h"
15 #include "nsIObserverService.h"
17 #include "mozilla/Assertions.h"
18 #include "mozilla/Services.h"
19 #include "mozilla/SystemGroup.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 mEventTarget(WrapNotNull(nsCOMPtr<nsIEventTarget>(
72 SystemGroup::EventTargetFor(TaskCategory::Other)))),
73 mObserversWithTargets(0),
74 mObservers(new ObserverTable),
75 mProgress(NoProgress),
76 mIsMultipart(false) {}
78 void ProgressTracker::SetImage(Image* aImage) {
79 MutexAutoLock lock(mMutex);
80 MOZ_ASSERT(aImage, "Setting null image");
81 MOZ_ASSERT(!mImage, "Setting image when we already have one");
82 mImage = aImage;
85 void ProgressTracker::ResetImage() {
86 MutexAutoLock lock(mMutex);
87 MOZ_ASSERT(mImage, "Resetting image when it's already null!");
88 mImage = nullptr;
91 uint32_t ProgressTracker::GetImageStatus() const {
92 uint32_t status = imgIRequest::STATUS_NONE;
94 // Translate our current state to a set of imgIRequest::STATE_* flags.
95 if (mProgress & FLAG_SIZE_AVAILABLE) {
96 status |= imgIRequest::STATUS_SIZE_AVAILABLE;
98 if (mProgress & FLAG_DECODE_COMPLETE) {
99 status |= imgIRequest::STATUS_DECODE_COMPLETE;
101 if (mProgress & FLAG_FRAME_COMPLETE) {
102 status |= imgIRequest::STATUS_FRAME_COMPLETE;
104 if (mProgress & FLAG_LOAD_COMPLETE) {
105 status |= imgIRequest::STATUS_LOAD_COMPLETE;
107 if (mProgress & FLAG_IS_ANIMATED) {
108 status |= imgIRequest::STATUS_IS_ANIMATED;
110 if (mProgress & FLAG_HAS_TRANSPARENCY) {
111 status |= imgIRequest::STATUS_HAS_TRANSPARENCY;
113 if (mProgress & FLAG_HAS_ERROR) {
114 status |= imgIRequest::STATUS_ERROR;
117 return status;
120 // A helper class to allow us to call SyncNotify asynchronously.
121 class AsyncNotifyRunnable : public Runnable {
122 public:
123 AsyncNotifyRunnable(ProgressTracker* aTracker, IProgressObserver* aObserver)
124 : Runnable("ProgressTracker::AsyncNotifyRunnable"), mTracker(aTracker) {
125 MOZ_ASSERT(NS_IsMainThread(), "Should be created on the main thread");
126 MOZ_ASSERT(aTracker, "aTracker should not be null");
127 MOZ_ASSERT(aObserver, "aObserver should not be null");
128 mObservers.AppendElement(aObserver);
131 NS_IMETHOD Run() override {
132 MOZ_ASSERT(NS_IsMainThread(), "Should be running on the main thread");
133 MOZ_ASSERT(mTracker, "mTracker should not be null");
134 for (uint32_t i = 0; i < mObservers.Length(); ++i) {
135 mObservers[i]->ClearPendingNotify();
136 mTracker->SyncNotify(mObservers[i]);
139 mTracker->mRunnable = nullptr;
140 return NS_OK;
143 void AddObserver(IProgressObserver* aObserver) {
144 mObservers.AppendElement(aObserver);
147 void RemoveObserver(IProgressObserver* aObserver) {
148 mObservers.RemoveElement(aObserver);
151 private:
152 friend class ProgressTracker;
154 RefPtr<ProgressTracker> mTracker;
155 nsTArray<RefPtr<IProgressObserver>> mObservers;
158 ProgressTracker::MediumHighRunnable::MediumHighRunnable(
159 already_AddRefed<AsyncNotifyRunnable>&& aEvent)
160 : PrioritizableRunnable(std::move(aEvent),
161 nsIRunnablePriority::PRIORITY_MEDIUMHIGH) {}
163 void ProgressTracker::MediumHighRunnable::AddObserver(
164 IProgressObserver* aObserver) {
165 static_cast<AsyncNotifyRunnable*>(mRunnable.get())->AddObserver(aObserver);
168 void ProgressTracker::MediumHighRunnable::RemoveObserver(
169 IProgressObserver* aObserver) {
170 static_cast<AsyncNotifyRunnable*>(mRunnable.get())->RemoveObserver(aObserver);
173 /* static */
174 already_AddRefed<ProgressTracker::MediumHighRunnable>
175 ProgressTracker::MediumHighRunnable::Create(
176 already_AddRefed<AsyncNotifyRunnable>&& aEvent) {
177 MOZ_ASSERT(NS_IsMainThread());
178 RefPtr<ProgressTracker::MediumHighRunnable> event(
179 new ProgressTracker::MediumHighRunnable(std::move(aEvent)));
180 return event.forget();
183 void ProgressTracker::Notify(IProgressObserver* aObserver) {
184 MOZ_ASSERT(NS_IsMainThread());
186 if (aObserver->NotificationsDeferred()) {
187 // There is a pending notification, or the observer isn't ready yet.
188 return;
191 if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
192 RefPtr<Image> image = GetImage();
193 LOG_FUNC_WITH_PARAM(gImgLog, "ProgressTracker::Notify async", "uri", image);
196 aObserver->MarkPendingNotify();
198 // If we have an existing runnable that we can use, we just append this
199 // observer to its list of observers to be notified. This ensures we don't
200 // unnecessarily delay onload.
201 if (mRunnable) {
202 mRunnable->AddObserver(aObserver);
203 } else {
204 RefPtr<AsyncNotifyRunnable> ev = new AsyncNotifyRunnable(this, aObserver);
205 mRunnable = ProgressTracker::MediumHighRunnable::Create(ev.forget());
206 mEventTarget->Dispatch(mRunnable, NS_DISPATCH_NORMAL);
210 // A helper class to allow us to call SyncNotify asynchronously for a given,
211 // fixed, state.
212 class AsyncNotifyCurrentStateRunnable : public Runnable {
213 public:
214 AsyncNotifyCurrentStateRunnable(ProgressTracker* aProgressTracker,
215 IProgressObserver* aObserver)
216 : Runnable("image::AsyncNotifyCurrentStateRunnable"),
217 mProgressTracker(aProgressTracker),
218 mObserver(aObserver) {
219 MOZ_ASSERT(NS_IsMainThread(), "Should be created on the main thread");
220 MOZ_ASSERT(mProgressTracker, "mProgressTracker should not be null");
221 MOZ_ASSERT(mObserver, "mObserver should not be null");
222 mImage = mProgressTracker->GetImage();
225 NS_IMETHOD Run() override {
226 MOZ_ASSERT(NS_IsMainThread(), "Should be running on the main thread");
227 mObserver->ClearPendingNotify();
229 mProgressTracker->SyncNotify(mObserver);
230 return NS_OK;
233 private:
234 RefPtr<ProgressTracker> mProgressTracker;
235 RefPtr<IProgressObserver> mObserver;
237 // We have to hold on to a reference to the tracker's image, just in case
238 // it goes away while we're in the event queue.
239 RefPtr<Image> mImage;
242 void ProgressTracker::NotifyCurrentState(IProgressObserver* aObserver) {
243 MOZ_ASSERT(NS_IsMainThread());
245 if (aObserver->NotificationsDeferred()) {
246 // There is a pending notification, or the observer isn't ready yet.
247 return;
250 if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
251 RefPtr<Image> image = GetImage();
252 LOG_FUNC_WITH_PARAM(gImgLog, "ProgressTracker::NotifyCurrentState", "uri",
253 image);
256 aObserver->MarkPendingNotify();
258 nsCOMPtr<nsIRunnable> ev =
259 new AsyncNotifyCurrentStateRunnable(this, aObserver);
260 mEventTarget->Dispatch(ev.forget(), NS_DISPATCH_NORMAL);
264 * ImageObserverNotifier is a helper type that abstracts over the difference
265 * between sending notifications to all of the observers in an ObserverTable,
266 * and sending them to a single observer. This allows the same notification code
267 * to be used for both cases.
269 template <typename T>
270 struct ImageObserverNotifier;
272 template <>
273 struct MOZ_STACK_CLASS ImageObserverNotifier<const ObserverTable*> {
274 explicit ImageObserverNotifier(const ObserverTable* aObservers,
275 bool aIgnoreDeferral = false)
276 : mObservers(aObservers), mIgnoreDeferral(aIgnoreDeferral) {}
278 template <typename Lambda>
279 void operator()(Lambda aFunc) {
280 for (auto iter = mObservers->ConstIter(); !iter.Done(); iter.Next()) {
281 RefPtr<IProgressObserver> observer = iter.Data().get();
282 if (observer && (mIgnoreDeferral || !observer->NotificationsDeferred())) {
283 aFunc(observer);
288 private:
289 const ObserverTable* mObservers;
290 const bool mIgnoreDeferral;
293 template <>
294 struct MOZ_STACK_CLASS ImageObserverNotifier<IProgressObserver*> {
295 explicit ImageObserverNotifier(IProgressObserver* aObserver)
296 : mObserver(aObserver) {}
298 template <typename Lambda>
299 void operator()(Lambda aFunc) {
300 if (mObserver && !mObserver->NotificationsDeferred()) {
301 aFunc(mObserver);
305 private:
306 IProgressObserver* mObserver;
309 template <typename T>
310 void SyncNotifyInternal(const T& aObservers, bool aHasImage, Progress aProgress,
311 const nsIntRect& aDirtyRect) {
312 MOZ_ASSERT(NS_IsMainThread());
314 typedef imgINotificationObserver I;
315 ImageObserverNotifier<T> notify(aObservers);
317 if (aProgress & FLAG_SIZE_AVAILABLE) {
318 notify([](IProgressObserver* aObs) { aObs->Notify(I::SIZE_AVAILABLE); });
321 if (aHasImage) {
322 // OnFrameUpdate
323 // If there's any content in this frame at all (always true for
324 // vector images, true for raster images that have decoded at
325 // least one frame) then send OnFrameUpdate.
326 if (!aDirtyRect.IsEmpty()) {
327 notify([&](IProgressObserver* aObs) {
328 aObs->Notify(I::FRAME_UPDATE, &aDirtyRect);
332 if (aProgress & FLAG_FRAME_COMPLETE) {
333 notify([](IProgressObserver* aObs) { aObs->Notify(I::FRAME_COMPLETE); });
336 if (aProgress & FLAG_HAS_TRANSPARENCY) {
337 notify(
338 [](IProgressObserver* aObs) { aObs->Notify(I::HAS_TRANSPARENCY); });
341 if (aProgress & FLAG_IS_ANIMATED) {
342 notify([](IProgressObserver* aObs) { aObs->Notify(I::IS_ANIMATED); });
346 if (aProgress & FLAG_DECODE_COMPLETE) {
347 MOZ_ASSERT(aHasImage, "Stopped decoding without ever having an image?");
348 notify([](IProgressObserver* aObs) { aObs->Notify(I::DECODE_COMPLETE); });
351 if (aProgress & FLAG_LOAD_COMPLETE) {
352 notify([=](IProgressObserver* aObs) {
353 aObs->OnLoadComplete(aProgress & FLAG_LAST_PART_COMPLETE);
358 void ProgressTracker::SyncNotifyProgress(Progress aProgress,
359 const nsIntRect& aInvalidRect
360 /* = nsIntRect() */) {
361 MOZ_ASSERT(NS_IsMainThread(), "Use mObservers on main thread only");
363 Progress progress = Difference(aProgress);
364 CheckProgressConsistency(mProgress, mProgress | progress, mIsMultipart);
366 // Apply the changes.
367 mProgress |= progress;
369 // Send notifications.
370 mObservers.Read([&](const ObserverTable* aTable) {
371 SyncNotifyInternal(aTable, HasImage(), progress, aInvalidRect);
374 if (progress & FLAG_HAS_ERROR) {
375 FireFailureNotification();
379 void ProgressTracker::SyncNotify(IProgressObserver* aObserver) {
380 MOZ_ASSERT(NS_IsMainThread());
382 RefPtr<Image> image = GetImage();
383 LOG_SCOPE_WITH_PARAM(gImgLog, "ProgressTracker::SyncNotify", "uri", image);
385 nsIntRect rect;
386 if (image) {
387 int32_t width, height;
388 if (NS_FAILED(image->GetWidth(&width)) ||
389 NS_FAILED(image->GetHeight(&height))) {
390 // Either the image has no intrinsic size, or it has an error.
391 rect = GetMaxSizedIntRect();
392 } else {
393 rect.SizeTo(width, height);
397 SyncNotifyInternal(aObserver, !!image, mProgress, rect);
400 void ProgressTracker::EmulateRequestFinished(IProgressObserver* aObserver) {
401 MOZ_ASSERT(NS_IsMainThread(),
402 "SyncNotifyState and mObservers are not threadsafe");
403 RefPtr<IProgressObserver> kungFuDeathGrip(aObserver);
405 if (!(mProgress & FLAG_LOAD_COMPLETE)) {
406 aObserver->OnLoadComplete(true);
410 already_AddRefed<nsIEventTarget> ProgressTracker::GetEventTarget() const {
411 MutexAutoLock lock(mMutex);
412 nsCOMPtr<nsIEventTarget> target = mEventTarget;
413 return target.forget();
416 void ProgressTracker::AddObserver(IProgressObserver* aObserver) {
417 MOZ_ASSERT(NS_IsMainThread());
418 RefPtr<IProgressObserver> observer = aObserver;
420 nsCOMPtr<nsIEventTarget> target = observer->GetEventTarget();
421 if (target) {
422 if (mObserversWithTargets == 0) {
423 // On the first observer with a target (i.e. listener), always accept its
424 // event target; this may be for a specific DocGroup, or it may be the
425 // unlabelled main thread target.
426 MutexAutoLock lock(mMutex);
427 mEventTarget = WrapNotNull(target);
428 } else if (mEventTarget.get() != target.get()) {
429 // If a subsequent observer comes in with a different target, we need to
430 // switch to use the unlabelled main thread target, if we haven't already.
431 MutexAutoLock lock(mMutex);
432 nsCOMPtr<nsIEventTarget> mainTarget(do_GetMainThread());
433 mEventTarget = WrapNotNull(mainTarget);
435 ++mObserversWithTargets;
438 mObservers.Write([=](ObserverTable* aTable) {
439 MOZ_ASSERT(!aTable->Get(observer, nullptr),
440 "Adding duplicate entry for image observer");
442 WeakPtr<IProgressObserver> weakPtr = observer.get();
443 aTable->Put(observer, weakPtr);
446 MOZ_ASSERT(mObserversWithTargets <= ObserverCount());
449 bool ProgressTracker::RemoveObserver(IProgressObserver* aObserver) {
450 MOZ_ASSERT(NS_IsMainThread());
451 RefPtr<IProgressObserver> observer = aObserver;
453 // Remove the observer from the list.
454 bool removed = mObservers.Write(
455 [observer](ObserverTable* aTable) { return aTable->Remove(observer); });
457 // Sometimes once an image is decoded, and all of its observers removed, a new
458 // document may request the same image. Thus we need to clear our event target
459 // state when the last observer is removed, so that we select the most
460 // appropriate event target when a new observer is added. Since the event
461 // target may have changed (e.g. due to the scheduler group going away before
462 // we were removed), so we should be cautious comparing this target against
463 // anything at this stage.
464 if (removed) {
465 nsCOMPtr<nsIEventTarget> target = observer->GetEventTarget();
466 if (target) {
467 MOZ_ASSERT(mObserversWithTargets > 0);
468 --mObserversWithTargets;
470 if (mObserversWithTargets == 0) {
471 MutexAutoLock lock(mMutex);
472 nsCOMPtr<nsIEventTarget> target(
473 SystemGroup::EventTargetFor(TaskCategory::Other));
474 mEventTarget = WrapNotNull(target);
478 MOZ_ASSERT(mObserversWithTargets <= ObserverCount());
481 // Observers can get confused if they don't get all the proper teardown
482 // notifications. Part ways on good terms.
483 if (removed && !aObserver->NotificationsDeferred()) {
484 EmulateRequestFinished(aObserver);
487 // Make sure we don't give callbacks to an observer that isn't interested in
488 // them any more.
489 if (aObserver->NotificationsDeferred() && mRunnable) {
490 mRunnable->RemoveObserver(aObserver);
491 aObserver->ClearPendingNotify();
494 return removed;
497 uint32_t ProgressTracker::ObserverCount() const {
498 MOZ_ASSERT(NS_IsMainThread());
499 return mObservers.Read(
500 [](const ObserverTable* aTable) { return aTable->Count(); });
503 void ProgressTracker::OnUnlockedDraw() {
504 MOZ_ASSERT(NS_IsMainThread());
505 mObservers.Read([](const ObserverTable* aTable) {
506 ImageObserverNotifier<const ObserverTable*> notify(aTable);
507 notify([](IProgressObserver* aObs) {
508 aObs->Notify(imgINotificationObserver::UNLOCKED_DRAW);
513 void ProgressTracker::ResetForNewRequest() {
514 MOZ_ASSERT(NS_IsMainThread());
515 mProgress = NoProgress;
518 void ProgressTracker::OnDiscard() {
519 MOZ_ASSERT(NS_IsMainThread());
520 mObservers.Read([](const ObserverTable* aTable) {
521 ImageObserverNotifier<const ObserverTable*> notify(aTable);
522 notify([](IProgressObserver* aObs) {
523 aObs->Notify(imgINotificationObserver::DISCARD);
528 void ProgressTracker::OnImageAvailable() {
529 MOZ_ASSERT(NS_IsMainThread());
530 // Notify any imgRequestProxys that are observing us that we have an Image.
531 mObservers.Read([](const ObserverTable* aTable) {
532 ImageObserverNotifier<const ObserverTable*> notify(
533 aTable, /* aIgnoreDeferral = */ true);
534 notify([](IProgressObserver* aObs) { aObs->SetHasImage(); });
538 void ProgressTracker::FireFailureNotification() {
539 MOZ_ASSERT(NS_IsMainThread());
541 // Some kind of problem has happened with image decoding.
542 // Report the URI to net:failed-to-process-uri-conent observers.
543 RefPtr<Image> image = GetImage();
544 if (image) {
545 // Should be on main thread, so ok to create a new nsIURI.
546 nsCOMPtr<nsIURI> uri = image->GetURI();
547 if (uri) {
548 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
549 if (os) {
550 os->NotifyObservers(uri, "net:failed-to-process-uri-content", nullptr);
556 } // namespace image
557 } // namespace mozilla