1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "MultipartImage.h"
7 
8 #include "imgINotificationObserver.h"
9 
10 namespace mozilla {
11 
12 using gfx::IntSize;
13 using gfx::SourceSurface;
14 
15 namespace image {
16 
17 ///////////////////////////////////////////////////////////////////////////////
18 // Helpers
19 ///////////////////////////////////////////////////////////////////////////////
20 
21 class NextPartObserver : public IProgressObserver {
22  public:
23   MOZ_DECLARE_REFCOUNTED_TYPENAME(NextPartObserver)
NS_INLINE_DECL_REFCOUNTING(NextPartObserver,override)24   NS_INLINE_DECL_REFCOUNTING(NextPartObserver, override)
25 
26   explicit NextPartObserver(MultipartImage* aOwner) : mOwner(aOwner) {
27     MOZ_ASSERT(mOwner);
28   }
29 
BeginObserving(Image * aImage)30   void BeginObserving(Image* aImage) {
31     MOZ_ASSERT(aImage);
32     mImage = aImage;
33 
34     RefPtr<ProgressTracker> tracker = mImage->GetProgressTracker();
35     tracker->AddObserver(this);
36   }
37 
BlockUntilDecodedAndFinishObserving()38   void BlockUntilDecodedAndFinishObserving() {
39     // Use GetFrame() to block until our image finishes decoding.
40     RefPtr<SourceSurface> surface = mImage->GetFrame(
41         imgIContainer::FRAME_CURRENT, imgIContainer::FLAG_SYNC_DECODE);
42 
43     // GetFrame() should've sent synchronous notifications that would have
44     // caused us to call FinishObserving() (and null out mImage) already. If for
45     // some reason it didn't, we should do so here.
46     if (mImage) {
47       FinishObserving();
48     }
49   }
50 
Notify(int32_t aType,const nsIntRect * aRect=nullptr)51   virtual void Notify(int32_t aType,
52                       const nsIntRect* aRect = nullptr) override {
53     if (!mImage) {
54       // We've already finished observing the last image we were given.
55       return;
56     }
57 
58     if (aType == imgINotificationObserver::FRAME_COMPLETE) {
59       FinishObserving();
60     }
61   }
62 
OnLoadComplete(bool aLastPart)63   virtual void OnLoadComplete(bool aLastPart) override {
64     if (!mImage) {
65       // We've already finished observing the last image we were given.
66       return;
67     }
68 
69     // Retrieve the image's intrinsic size.
70     int32_t width = 0;
71     int32_t height = 0;
72     mImage->GetWidth(&width);
73     mImage->GetHeight(&height);
74 
75     // Request decoding at the intrinsic size.
76     mImage->RequestDecodeForSize(IntSize(width, height),
77                                  imgIContainer::DECODE_FLAGS_DEFAULT);
78 
79     // If there's already an error, we may never get a FRAME_COMPLETE
80     // notification, so go ahead and notify our owner right away.
81     RefPtr<ProgressTracker> tracker = mImage->GetProgressTracker();
82     if (tracker->GetProgress() & FLAG_HAS_ERROR) {
83       FinishObserving();
84     }
85   }
86 
87   // Other notifications are ignored.
SetHasImage()88   virtual void SetHasImage() override {}
NotificationsDeferred() const89   virtual bool NotificationsDeferred() const override { return false; }
MarkPendingNotify()90   virtual void MarkPendingNotify() override {}
ClearPendingNotify()91   virtual void ClearPendingNotify() override {}
92 
93  private:
~NextPartObserver()94   virtual ~NextPartObserver() {}
95 
FinishObserving()96   void FinishObserving() {
97     MOZ_ASSERT(mImage);
98 
99     RefPtr<ProgressTracker> tracker = mImage->GetProgressTracker();
100     tracker->RemoveObserver(this);
101     mImage = nullptr;
102 
103     mOwner->FinishTransition();
104   }
105 
106   MultipartImage* mOwner;
107   RefPtr<Image> mImage;
108 };
109 
110 ///////////////////////////////////////////////////////////////////////////////
111 // Implementation
112 ///////////////////////////////////////////////////////////////////////////////
113 
MultipartImage(Image * aFirstPart)114 MultipartImage::MultipartImage(Image* aFirstPart)
115     : ImageWrapper(aFirstPart), mPendingNotify(false) {
116   mNextPartObserver = new NextPartObserver(this);
117 }
118 
Init()119 void MultipartImage::Init() {
120   MOZ_ASSERT(NS_IsMainThread());
121   MOZ_ASSERT(mTracker, "Should've called SetProgressTracker() by now");
122 
123   // Start observing the first part.
124   RefPtr<ProgressTracker> firstPartTracker = InnerImage()->GetProgressTracker();
125   firstPartTracker->AddObserver(this);
126   InnerImage()->IncrementAnimationConsumers();
127 }
128 
~MultipartImage()129 MultipartImage::~MultipartImage() {
130   // Ask our ProgressTracker to drop its weak reference to us.
131   mTracker->ResetImage();
132 }
133 
NS_IMPL_ISUPPORTS_INHERITED0(MultipartImage,ImageWrapper)134 NS_IMPL_ISUPPORTS_INHERITED0(MultipartImage, ImageWrapper)
135 
136 void MultipartImage::BeginTransitionToPart(Image* aNextPart) {
137   MOZ_ASSERT(NS_IsMainThread());
138   MOZ_ASSERT(aNextPart);
139 
140   if (mNextPart) {
141     // Let the decoder catch up so we don't drop frames.
142     mNextPartObserver->BlockUntilDecodedAndFinishObserving();
143     MOZ_ASSERT(!mNextPart);
144   }
145 
146   mNextPart = aNextPart;
147 
148   // Start observing the next part; we'll complete the transition when
149   // NextPartObserver calls FinishTransition.
150   mNextPartObserver->BeginObserving(mNextPart);
151   mNextPart->IncrementAnimationConsumers();
152 }
153 
FilterProgress(Progress aProgress)154 static Progress FilterProgress(Progress aProgress) {
155   // Filter out onload blocking notifications, since we don't want to block
156   // onload for multipart images.
157   // Filter out errors, since we don't want errors in one part to error out
158   // the whole stream.
159   return aProgress & ~FLAG_HAS_ERROR;
160 }
161 
FinishTransition()162 void MultipartImage::FinishTransition() {
163   MOZ_ASSERT(NS_IsMainThread());
164   MOZ_ASSERT(mNextPart, "Should have a next part here");
165 
166   RefPtr<ProgressTracker> newCurrentPartTracker =
167       mNextPart->GetProgressTracker();
168   if (newCurrentPartTracker->GetProgress() & FLAG_HAS_ERROR) {
169     // This frame has an error; drop it.
170     mNextPart = nullptr;
171 
172     // We still need to notify, though.
173     mTracker->ResetForNewRequest();
174     RefPtr<ProgressTracker> currentPartTracker =
175         InnerImage()->GetProgressTracker();
176     mTracker->SyncNotifyProgress(
177         FilterProgress(currentPartTracker->GetProgress()));
178 
179     return;
180   }
181 
182   // Stop observing the current part.
183   {
184     RefPtr<ProgressTracker> currentPartTracker =
185         InnerImage()->GetProgressTracker();
186     currentPartTracker->RemoveObserver(this);
187   }
188 
189   // Make the next part become the current part.
190   mTracker->ResetForNewRequest();
191   SetInnerImage(mNextPart);
192   mNextPart = nullptr;
193   newCurrentPartTracker->AddObserver(this);
194 
195   // Finally, send all the notifications for the new current part and send a
196   // FRAME_UPDATE notification so that observers know to redraw.
197   mTracker->SyncNotifyProgress(
198       FilterProgress(newCurrentPartTracker->GetProgress()),
199       GetMaxSizedIntRect());
200 }
201 
Unwrap()202 already_AddRefed<imgIContainer> MultipartImage::Unwrap() {
203   // Although we wrap another image, we don't allow callers to unwrap as. As far
204   // as external code is concerned, MultipartImage is atomic.
205   nsCOMPtr<imgIContainer> image = this;
206   return image.forget();
207 }
208 
GetProgressTracker()209 already_AddRefed<ProgressTracker> MultipartImage::GetProgressTracker() {
210   MOZ_ASSERT(mTracker);
211   RefPtr<ProgressTracker> tracker = mTracker;
212   return tracker.forget();
213 }
214 
SetProgressTracker(ProgressTracker * aTracker)215 void MultipartImage::SetProgressTracker(ProgressTracker* aTracker) {
216   MOZ_ASSERT(aTracker);
217   MOZ_ASSERT(!mTracker);
218   mTracker = aTracker;
219 }
220 
OnImageDataAvailable(nsIRequest * aRequest,nsISupports * aContext,nsIInputStream * aInStr,uint64_t aSourceOffset,uint32_t aCount)221 nsresult MultipartImage::OnImageDataAvailable(nsIRequest* aRequest,
222                                               nsISupports* aContext,
223                                               nsIInputStream* aInStr,
224                                               uint64_t aSourceOffset,
225                                               uint32_t aCount) {
226   // Note that this method is special in that we forward it to the next part if
227   // one exists, and *not* the current part.
228 
229   // We may trigger notifications that will free mNextPart, so keep it alive.
230   RefPtr<Image> nextPart = mNextPart;
231   if (nextPart) {
232     nextPart->OnImageDataAvailable(aRequest, aContext, aInStr, aSourceOffset,
233                                    aCount);
234   } else {
235     InnerImage()->OnImageDataAvailable(aRequest, aContext, aInStr,
236                                        aSourceOffset, aCount);
237   }
238 
239   return NS_OK;
240 }
241 
OnImageDataComplete(nsIRequest * aRequest,nsISupports * aContext,nsresult aStatus,bool aLastPart)242 nsresult MultipartImage::OnImageDataComplete(nsIRequest* aRequest,
243                                              nsISupports* aContext,
244                                              nsresult aStatus, bool aLastPart) {
245   // Note that this method is special in that we forward it to the next part if
246   // one exists, and *not* the current part.
247 
248   // We may trigger notifications that will free mNextPart, so keep it alive.
249   RefPtr<Image> nextPart = mNextPart;
250   if (nextPart) {
251     nextPart->OnImageDataComplete(aRequest, aContext, aStatus, aLastPart);
252   } else {
253     InnerImage()->OnImageDataComplete(aRequest, aContext, aStatus, aLastPart);
254   }
255 
256   return NS_OK;
257 }
258 
Notify(int32_t aType,const nsIntRect * aRect)259 void MultipartImage::Notify(int32_t aType,
260                             const nsIntRect* aRect /* = nullptr*/) {
261   if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
262     mTracker->SyncNotifyProgress(FLAG_SIZE_AVAILABLE);
263   } else if (aType == imgINotificationObserver::FRAME_UPDATE) {
264     mTracker->SyncNotifyProgress(NoProgress, *aRect);
265   } else if (aType == imgINotificationObserver::FRAME_COMPLETE) {
266     mTracker->SyncNotifyProgress(FLAG_FRAME_COMPLETE);
267   } else if (aType == imgINotificationObserver::LOAD_COMPLETE) {
268     mTracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE);
269   } else if (aType == imgINotificationObserver::DECODE_COMPLETE) {
270     mTracker->SyncNotifyProgress(FLAG_DECODE_COMPLETE);
271   } else if (aType == imgINotificationObserver::DISCARD) {
272     mTracker->OnDiscard();
273   } else if (aType == imgINotificationObserver::UNLOCKED_DRAW) {
274     mTracker->OnUnlockedDraw();
275   } else if (aType == imgINotificationObserver::IS_ANIMATED) {
276     mTracker->SyncNotifyProgress(FLAG_IS_ANIMATED);
277   } else if (aType == imgINotificationObserver::HAS_TRANSPARENCY) {
278     mTracker->SyncNotifyProgress(FLAG_HAS_TRANSPARENCY);
279   } else {
280     NS_NOTREACHED("Notification list should be exhaustive");
281   }
282 }
283 
OnLoadComplete(bool aLastPart)284 void MultipartImage::OnLoadComplete(bool aLastPart) {
285   Progress progress = FLAG_LOAD_COMPLETE;
286   if (aLastPart) {
287     progress |= FLAG_LAST_PART_COMPLETE;
288   }
289   mTracker->SyncNotifyProgress(progress);
290 }
291 
SetHasImage()292 void MultipartImage::SetHasImage() { mTracker->OnImageAvailable(); }
293 
NotificationsDeferred() const294 bool MultipartImage::NotificationsDeferred() const { return mPendingNotify; }
295 
MarkPendingNotify()296 void MultipartImage::MarkPendingNotify() { mPendingNotify = true; }
297 
ClearPendingNotify()298 void MultipartImage::ClearPendingNotify() { mPendingNotify = false; }
299 
300 }  // namespace image
301 }  // namespace mozilla
302