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