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/Assertions.h"
17 #include "mozilla/Services.h"
18 
19 using mozilla::WeakPtr;
20 
21 namespace mozilla {
22 namespace image {
23 
CheckProgressConsistency(Progress aOldProgress,Progress aNewProgress,bool aIsMultipart)24 static void CheckProgressConsistency(Progress aOldProgress,
25                                      Progress aNewProgress, bool aIsMultipart) {
26   // Check preconditions for every progress bit.
27 
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.
32 
33   if (aNewProgress & FLAG_SIZE_AVAILABLE) {
34     // No preconditions.
35   }
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));
40   }
41   if (aNewProgress & FLAG_FRAME_COMPLETE) {
42     MOZ_ASSERT(aNewProgress & FLAG_SIZE_AVAILABLE);
43   }
44   if (aNewProgress & FLAG_LOAD_COMPLETE) {
45     MOZ_ASSERT(aIsMultipart ||
46                aNewProgress & (FLAG_SIZE_AVAILABLE | FLAG_HAS_ERROR));
47   }
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.
52   }
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.
57   }
58   if (aNewProgress & FLAG_LAST_PART_COMPLETE) {
59     MOZ_ASSERT(aNewProgress & FLAG_LOAD_COMPLETE);
60   }
61   if (aNewProgress & FLAG_HAS_ERROR) {
62     // No preconditions.
63   }
64 }
65 
ProgressTracker()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) {}
75 
SetImage(Image * aImage)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;
81 }
82 
ResetImage()83 void ProgressTracker::ResetImage() {
84   MutexAutoLock lock(mMutex);
85   MOZ_ASSERT(mImage, "Resetting image when it's already null!");
86   mImage = nullptr;
87 }
88 
GetImageStatus() const89 uint32_t ProgressTracker::GetImageStatus() const {
90   uint32_t status = imgIRequest::STATUS_NONE;
91 
92   // Translate our current state to a set of imgIRequest::STATE_* flags.
93   if (mProgress & FLAG_SIZE_AVAILABLE) {
94     status |= imgIRequest::STATUS_SIZE_AVAILABLE;
95   }
96   if (mProgress & FLAG_DECODE_COMPLETE) {
97     status |= imgIRequest::STATUS_DECODE_COMPLETE;
98   }
99   if (mProgress & FLAG_FRAME_COMPLETE) {
100     status |= imgIRequest::STATUS_FRAME_COMPLETE;
101   }
102   if (mProgress & FLAG_LOAD_COMPLETE) {
103     status |= imgIRequest::STATUS_LOAD_COMPLETE;
104   }
105   if (mProgress & FLAG_IS_ANIMATED) {
106     status |= imgIRequest::STATUS_IS_ANIMATED;
107   }
108   if (mProgress & FLAG_HAS_TRANSPARENCY) {
109     status |= imgIRequest::STATUS_HAS_TRANSPARENCY;
110   }
111   if (mProgress & FLAG_HAS_ERROR) {
112     status |= imgIRequest::STATUS_ERROR;
113   }
114 
115   return status;
116 }
117 
118 // A helper class to allow us to call SyncNotify asynchronously.
119 class AsyncNotifyRunnable : public Runnable {
120  public:
AsyncNotifyRunnable(ProgressTracker * aTracker,IProgressObserver * aObserver)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);
127   }
128 
Run()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]);
135     }
136 
137     mTracker->mRunnable = nullptr;
138     return NS_OK;
139   }
140 
AddObserver(IProgressObserver * aObserver)141   void AddObserver(IProgressObserver* aObserver) {
142     mObservers.AppendElement(aObserver);
143   }
144 
RemoveObserver(IProgressObserver * aObserver)145   void RemoveObserver(IProgressObserver* aObserver) {
146     mObservers.RemoveElement(aObserver);
147   }
148 
149  private:
150   friend class ProgressTracker;
151 
152   RefPtr<ProgressTracker> mTracker;
153   nsTArray<RefPtr<IProgressObserver>> mObservers;
154 };
155 
MediumHighRunnable(already_AddRefed<AsyncNotifyRunnable> && aEvent)156 ProgressTracker::MediumHighRunnable::MediumHighRunnable(
157     already_AddRefed<AsyncNotifyRunnable>&& aEvent)
158     : PrioritizableRunnable(std::move(aEvent),
159                             nsIRunnablePriority::PRIORITY_MEDIUMHIGH) {}
160 
AddObserver(IProgressObserver * aObserver)161 void ProgressTracker::MediumHighRunnable::AddObserver(
162     IProgressObserver* aObserver) {
163   static_cast<AsyncNotifyRunnable*>(mRunnable.get())->AddObserver(aObserver);
164 }
165 
RemoveObserver(IProgressObserver * aObserver)166 void ProgressTracker::MediumHighRunnable::RemoveObserver(
167     IProgressObserver* aObserver) {
168   static_cast<AsyncNotifyRunnable*>(mRunnable.get())->RemoveObserver(aObserver);
169 }
170 
171 /* static */
172 already_AddRefed<ProgressTracker::MediumHighRunnable>
Create(already_AddRefed<AsyncNotifyRunnable> && aEvent)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();
179 }
180 
Notify(IProgressObserver * aObserver)181 void ProgressTracker::Notify(IProgressObserver* aObserver) {
182   MOZ_ASSERT(NS_IsMainThread());
183 
184   if (aObserver->NotificationsDeferred()) {
185     // There is a pending notification, or the observer isn't ready yet.
186     return;
187   }
188 
189   if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
190     RefPtr<Image> image = GetImage();
191     LOG_FUNC_WITH_PARAM(gImgLog, "ProgressTracker::Notify async", "uri", image);
192   }
193 
194   aObserver->MarkPendingNotify();
195 
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);
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:
AsyncNotifyCurrentStateRunnable(ProgressTracker * aProgressTracker,IProgressObserver * aObserver)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 
Run()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 
NotifyCurrentState(IProgressObserver * aObserver)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   nsCOMPtr<nsIRunnable> ev =
257       new AsyncNotifyCurrentStateRunnable(this, aObserver);
258   mEventTarget->Dispatch(ev.forget(), NS_DISPATCH_NORMAL);
259 }
260 
261 /**
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.
266  */
267 template <typename T>
268 struct ImageObserverNotifier;
269 
270 template <>
271 struct MOZ_STACK_CLASS ImageObserverNotifier<const ObserverTable*> {
ImageObserverNotifiermozilla::image::ImageObserverNotifier272   explicit ImageObserverNotifier(const ObserverTable* aObservers,
273                                  bool aIgnoreDeferral = false)
274       : mObservers(aObservers), mIgnoreDeferral(aIgnoreDeferral) {}
275 
276   template <typename Lambda>
operator ()mozilla::image::ImageObserverNotifier277   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);
282       }
283     }
284   }
285 
286  private:
287   const ObserverTable* mObservers;
288   const bool mIgnoreDeferral;
289 };
290 
291 template <>
292 struct MOZ_STACK_CLASS ImageObserverNotifier<IProgressObserver*> {
ImageObserverNotifiermozilla::image::ImageObserverNotifier293   explicit ImageObserverNotifier(IProgressObserver* aObserver)
294       : mObserver(aObserver) {}
295 
296   template <typename Lambda>
operator ()mozilla::image::ImageObserverNotifier297   void operator()(Lambda aFunc) {
298     if (mObserver && !mObserver->NotificationsDeferred()) {
299       aFunc(mObserver);
300     }
301   }
302 
303  private:
304   IProgressObserver* mObserver;
305 };
306 
307 template <typename T>
SyncNotifyInternal(const T & aObservers,bool aHasImage,Progress aProgress,const nsIntRect & aDirtyRect)308 void SyncNotifyInternal(const T& aObservers, bool aHasImage, Progress aProgress,
309                         const nsIntRect& aDirtyRect) {
310   MOZ_ASSERT(NS_IsMainThread());
311 
312   typedef imgINotificationObserver I;
313   ImageObserverNotifier<T> notify(aObservers);
314 
315   if (aProgress & FLAG_SIZE_AVAILABLE) {
316     notify([](IProgressObserver* aObs) { aObs->Notify(I::SIZE_AVAILABLE); });
317   }
318 
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);
327       });
328     }
329 
330     if (aProgress & FLAG_FRAME_COMPLETE) {
331       notify([](IProgressObserver* aObs) { aObs->Notify(I::FRAME_COMPLETE); });
332     }
333 
334     if (aProgress & FLAG_HAS_TRANSPARENCY) {
335       notify(
336           [](IProgressObserver* aObs) { aObs->Notify(I::HAS_TRANSPARENCY); });
337     }
338 
339     if (aProgress & FLAG_IS_ANIMATED) {
340       notify([](IProgressObserver* aObs) { aObs->Notify(I::IS_ANIMATED); });
341     }
342   }
343 
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); });
347   }
348 
349   if (aProgress & FLAG_LOAD_COMPLETE) {
350     notify([=](IProgressObserver* aObs) {
351       aObs->OnLoadComplete(aProgress & FLAG_LAST_PART_COMPLETE);
352     });
353   }
354 }
355 
SyncNotifyProgress(Progress aProgress,const nsIntRect & aInvalidRect)356 void ProgressTracker::SyncNotifyProgress(Progress aProgress,
357                                          const nsIntRect& aInvalidRect
358                                          /* = nsIntRect() */) {
359   MOZ_ASSERT(NS_IsMainThread(), "Use mObservers on main thread only");
360 
361   Progress progress = Difference(aProgress);
362   CheckProgressConsistency(mProgress, mProgress | progress, mIsMultipart);
363 
364   // Apply the changes.
365   mProgress |= progress;
366 
367   // Send notifications.
368   mObservers.Read([&](const ObserverTable* aTable) {
369     SyncNotifyInternal(aTable, HasImage(), progress, aInvalidRect);
370   });
371 
372   if (progress & FLAG_HAS_ERROR) {
373     FireFailureNotification();
374   }
375 }
376 
SyncNotify(IProgressObserver * aObserver)377 void ProgressTracker::SyncNotify(IProgressObserver* aObserver) {
378   MOZ_ASSERT(NS_IsMainThread());
379 
380   RefPtr<Image> image = GetImage();
381   LOG_SCOPE_WITH_PARAM(gImgLog, "ProgressTracker::SyncNotify", "uri", image);
382 
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);
392     }
393   }
394 
395   SyncNotifyInternal(aObserver, !!image, mProgress, rect);
396 }
397 
EmulateRequestFinished(IProgressObserver * aObserver)398 void ProgressTracker::EmulateRequestFinished(IProgressObserver* aObserver) {
399   MOZ_ASSERT(NS_IsMainThread(),
400              "SyncNotifyState and mObservers are not threadsafe");
401   RefPtr<IProgressObserver> kungFuDeathGrip(aObserver);
402 
403   if (!(mProgress & FLAG_LOAD_COMPLETE)) {
404     aObserver->OnLoadComplete(true);
405   }
406 }
407 
GetEventTarget() const408 already_AddRefed<nsIEventTarget> ProgressTracker::GetEventTarget() const {
409   MutexAutoLock lock(mMutex);
410   nsCOMPtr<nsIEventTarget> target = mEventTarget;
411   return target.forget();
412 }
413 
AddObserver(IProgressObserver * aObserver)414 void ProgressTracker::AddObserver(IProgressObserver* aObserver) {
415   MOZ_ASSERT(NS_IsMainThread());
416   RefPtr<IProgressObserver> observer = aObserver;
417 
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);
432     }
433     ++mObserversWithTargets;
434   }
435 
436   mObservers.Write([=](ObserverTable* aTable) {
437     MOZ_ASSERT(!aTable->Contains(observer),
438                "Adding duplicate entry for image observer");
439 
440     WeakPtr<IProgressObserver> weakPtr = observer.get();
441     aTable->InsertOrUpdate(observer, weakPtr);
442   });
443 
444   MOZ_ASSERT(mObserversWithTargets <= ObserverCount());
445 }
446 
RemoveObserver(IProgressObserver * aObserver)447 bool ProgressTracker::RemoveObserver(IProgressObserver* aObserver) {
448   MOZ_ASSERT(NS_IsMainThread());
449   RefPtr<IProgressObserver> observer = aObserver;
450 
451   // Remove the observer from the list.
452   bool removed = mObservers.Write(
453       [observer](ObserverTable* aTable) { return aTable->Remove(observer); });
454 
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;
467 
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);
474       }
475     }
476 
477     MOZ_ASSERT(mObserversWithTargets <= ObserverCount());
478   }
479 
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);
484   }
485 
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();
491   }
492 
493   return removed;
494 }
495 
ObserverCount() const496 uint32_t ProgressTracker::ObserverCount() const {
497   MOZ_ASSERT(NS_IsMainThread());
498   return mObservers.Read(
499       [](const ObserverTable* aTable) { return aTable->Count(); });
500 }
501 
OnUnlockedDraw()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);
508     });
509   });
510 }
511 
ResetForNewRequest()512 void ProgressTracker::ResetForNewRequest() {
513   MOZ_ASSERT(NS_IsMainThread());
514   mProgress = NoProgress;
515 }
516 
OnDiscard()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);
523     });
524   });
525 }
526 
OnImageAvailable()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(); });
534   });
535 }
536 
FireFailureNotification()537 void ProgressTracker::FireFailureNotification() {
538   MOZ_ASSERT(NS_IsMainThread());
539 
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);
550       }
551     }
552   }
553 }
554 
555 }  // namespace image
556 }  // namespace mozilla
557