1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 : *
3 : * ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is mozilla.org code.
17 : *
18 : * The Initial Developer of the Original Code is Mozilla Foundation.
19 : *
20 : * Portions created by the Initial Developer are Copyright (C) 2010
21 : * the Initial Developer. All Rights Reserved.
22 : *
23 : * Contributor(s):
24 : * Joe Drew <joe@drew.ca> (original author)
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either the GNU General Public License Version 2 or later (the "GPL"), or
28 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : #include "imgStatusTracker.h"
41 :
42 : #include "imgRequest.h"
43 : #include "imgIContainer.h"
44 : #include "imgRequestProxy.h"
45 : #include "Image.h"
46 : #include "ImageLogging.h"
47 : #include "RasterImage.h"
48 :
49 : using namespace mozilla::image;
50 :
51 : static nsresult
52 29 : GetResultFromImageStatus(PRUint32 aStatus)
53 : {
54 29 : if (aStatus & imgIRequest::STATUS_ERROR)
55 9 : return NS_IMAGELIB_ERROR_FAILURE;
56 20 : if (aStatus & imgIRequest::STATUS_LOAD_COMPLETE)
57 20 : return NS_IMAGELIB_SUCCESS_LOAD_FINISHED;
58 0 : return NS_OK;
59 : }
60 :
61 24 : imgStatusTracker::imgStatusTracker(Image* aImage)
62 : : mImage(aImage),
63 : mState(0),
64 : mImageStatus(imgIRequest::STATUS_NONE),
65 24 : mHadLastPart(false)
66 24 : {}
67 :
68 0 : imgStatusTracker::imgStatusTracker(const imgStatusTracker& aOther)
69 : : mImage(aOther.mImage),
70 : mState(aOther.mState),
71 : mImageStatus(aOther.mImageStatus),
72 0 : mHadLastPart(aOther.mHadLastPart)
73 : // Note: we explicitly don't copy mRequestRunnable, because it won't be
74 : // nulled out when the mRequestRunnable's Run function eventually gets
75 : // called.
76 0 : {}
77 :
78 : void
79 8 : imgStatusTracker::SetImage(Image* aImage)
80 : {
81 8 : NS_ABORT_IF_FALSE(aImage, "Setting null image");
82 8 : NS_ABORT_IF_FALSE(!mImage, "Setting image when we already have one");
83 8 : mImage = aImage;
84 8 : }
85 :
86 : bool
87 12 : imgStatusTracker::IsLoading() const
88 : {
89 : // Checking for whether OnStopRequest has fired allows us to say we're
90 : // loading before OnStartRequest gets called, letting the request properly
91 : // get removed from the cache in certain cases.
92 12 : return !(mState & stateRequestStopped);
93 : }
94 :
95 : PRUint32
96 0 : imgStatusTracker::GetImageStatus() const
97 : {
98 0 : return mImageStatus;
99 : }
100 :
101 : // A helper class to allow us to call SyncNotify asynchronously.
102 : class imgRequestNotifyRunnable : public nsRunnable
103 20 : {
104 : public:
105 5 : imgRequestNotifyRunnable(imgRequest* request, imgRequestProxy* requestproxy)
106 5 : : mRequest(request)
107 : {
108 5 : mProxies.AppendElement(requestproxy);
109 5 : }
110 :
111 5 : NS_IMETHOD Run()
112 : {
113 5 : imgStatusTracker& statusTracker = mRequest->GetStatusTracker();
114 :
115 12 : for (PRUint32 i = 0; i < mProxies.Length(); ++i) {
116 7 : mProxies[i]->SetNotificationsDeferred(false);
117 7 : statusTracker.SyncNotify(mProxies[i]);
118 : }
119 :
120 5 : statusTracker.mRequestRunnable = nsnull;
121 5 : return NS_OK;
122 : }
123 :
124 2 : void AddProxy(imgRequestProxy* aRequestProxy)
125 : {
126 2 : mProxies.AppendElement(aRequestProxy);
127 2 : }
128 :
129 : private:
130 : friend class imgStatusTracker;
131 :
132 : nsRefPtr<imgRequest> mRequest;
133 : nsTArray<nsRefPtr<imgRequestProxy> > mProxies;
134 : };
135 :
136 : void
137 7 : imgStatusTracker::Notify(imgRequest* request, imgRequestProxy* proxy)
138 : {
139 : #ifdef PR_LOGGING
140 14 : nsCOMPtr<nsIURI> uri;
141 7 : request->GetURI(getter_AddRefs(uri));
142 14 : nsCAutoString spec;
143 7 : uri->GetSpec(spec);
144 7 : LOG_FUNC_WITH_PARAM(gImgLog, "imgStatusTracker::Notify async", "uri", spec.get());
145 : #endif
146 :
147 7 : proxy->SetNotificationsDeferred(true);
148 :
149 : // If we have an existing runnable that we can use, we just append this proxy
150 : // to its list of proxies to be notified. This ensures we don't unnecessarily
151 : // delay onload.
152 7 : imgRequestNotifyRunnable* runnable = static_cast<imgRequestNotifyRunnable*>(mRequestRunnable.get());
153 7 : if (runnable && runnable->mRequest == request) {
154 2 : runnable->AddProxy(proxy);
155 : } else {
156 : // It's okay to overwrite an existing mRequestRunnable, because adding a
157 : // new proxy is strictly a performance optimization. The notification will
158 : // always happen, regardless of whether we hold a reference to a runnable.
159 5 : mRequestRunnable = new imgRequestNotifyRunnable(request, proxy);
160 5 : NS_DispatchToCurrentThread(mRequestRunnable);
161 : }
162 7 : }
163 :
164 : // A helper class to allow us to call SyncNotify asynchronously for a given,
165 : // fixed, state.
166 : class imgStatusNotifyRunnable : public nsRunnable
167 0 : {
168 : public:
169 0 : imgStatusNotifyRunnable(imgStatusTracker& status,
170 : imgRequestProxy* requestproxy)
171 0 : : mStatus(status), mImage(status.mImage), mProxy(requestproxy)
172 0 : {}
173 :
174 0 : NS_IMETHOD Run()
175 : {
176 0 : mProxy->SetNotificationsDeferred(false);
177 :
178 0 : mStatus.SyncNotify(mProxy);
179 0 : return NS_OK;
180 : }
181 :
182 : private:
183 : imgStatusTracker mStatus;
184 : // We have to hold on to a reference to the tracker's image, just in case
185 : // it goes away while we're in the event queue.
186 : nsRefPtr<Image> mImage;
187 : nsRefPtr<imgRequestProxy> mProxy;
188 : };
189 :
190 : void
191 0 : imgStatusTracker::NotifyCurrentState(imgRequestProxy* proxy)
192 : {
193 : #ifdef PR_LOGGING
194 0 : nsCOMPtr<nsIURI> uri;
195 0 : proxy->GetURI(getter_AddRefs(uri));
196 0 : nsCAutoString spec;
197 0 : uri->GetSpec(spec);
198 0 : LOG_FUNC_WITH_PARAM(gImgLog, "imgStatusTracker::NotifyCurrentState", "uri", spec.get());
199 : #endif
200 :
201 0 : proxy->SetNotificationsDeferred(true);
202 :
203 : // We don't keep track of
204 0 : nsCOMPtr<nsIRunnable> ev = new imgStatusNotifyRunnable(*this, proxy);
205 0 : NS_DispatchToCurrentThread(ev);
206 0 : }
207 :
208 : void
209 21 : imgStatusTracker::SyncNotify(imgRequestProxy* proxy)
210 : {
211 21 : NS_ABORT_IF_FALSE(!proxy->NotificationsDeferred(),
212 : "Calling imgStatusTracker::Notify() on a proxy that doesn't want notifications!");
213 :
214 : #ifdef PR_LOGGING
215 42 : nsCOMPtr<nsIURI> uri;
216 21 : proxy->GetURI(getter_AddRefs(uri));
217 42 : nsCAutoString spec;
218 21 : uri->GetSpec(spec);
219 42 : LOG_SCOPE_WITH_PARAM(gImgLog, "imgStatusTracker::SyncNotify", "uri", spec.get());
220 : #endif
221 :
222 42 : nsCOMPtr<imgIRequest> kungFuDeathGrip(proxy);
223 :
224 : // OnStartRequest
225 21 : if (mState & stateRequestStarted)
226 21 : proxy->OnStartRequest();
227 :
228 : // OnStartContainer
229 21 : if (mState & stateHasSize)
230 12 : proxy->OnStartContainer(mImage);
231 :
232 : // OnStartDecode
233 21 : if (mState & stateDecodeStarted)
234 2 : proxy->OnStartDecode();
235 :
236 21 : if (mImage) {
237 13 : PRInt16 imageType = mImage->GetType();
238 : // Send frame messages (OnStartFrame, OnDataAvailable, OnStopFrame)
239 26 : if (imageType == imgIContainer::TYPE_VECTOR ||
240 13 : static_cast<RasterImage*>(mImage)->GetNumFrames() > 0) {
241 :
242 : PRUint32 frame = (imageType == imgIContainer::TYPE_VECTOR) ?
243 2 : 0 : static_cast<RasterImage*>(mImage)->GetCurrentFrameIndex();
244 :
245 2 : proxy->OnStartFrame(frame);
246 :
247 : // OnDataAvailable
248 : // XXX - Should only send partial rects here, but that needs to
249 : // wait until we fix up the observer interface
250 2 : nsIntRect r;
251 2 : mImage->GetCurrentFrameRect(r);
252 2 : proxy->OnDataAvailable(frame, &r);
253 :
254 2 : if (mState & stateFrameStopped)
255 2 : proxy->OnStopFrame(frame);
256 : }
257 :
258 : // OnImageIsAnimated
259 13 : bool isAnimated = false;
260 :
261 13 : nsresult rv = mImage->GetAnimated(&isAnimated);
262 13 : if (NS_SUCCEEDED(rv) && isAnimated) {
263 0 : proxy->OnImageIsAnimated();
264 : }
265 : }
266 :
267 : // See bug 505385 and imgRequest::OnStopDecode for more information on why we
268 : // call OnStopContainer based on stateDecodeStopped, and why OnStopDecode is
269 : // called with OnStopRequest.
270 21 : if (mState & stateDecodeStopped) {
271 2 : NS_ABORT_IF_FALSE(mImage, "stopped decoding without ever having an image?");
272 2 : proxy->OnStopContainer(mImage);
273 : }
274 :
275 21 : if (mState & stateRequestStopped) {
276 13 : proxy->OnStopDecode(GetResultFromImageStatus(mImageStatus), nsnull);
277 13 : proxy->OnStopRequest(mHadLastPart);
278 : }
279 21 : }
280 :
281 : void
282 27 : imgStatusTracker::EmulateRequestFinished(imgRequestProxy* aProxy, nsresult aStatus,
283 : bool aOnlySendStopRequest)
284 : {
285 54 : nsCOMPtr<imgIRequest> kungFuDeathGrip(aProxy);
286 :
287 27 : if (!aOnlySendStopRequest) {
288 : // The "real" OnStopDecode - fix this with bug 505385.
289 0 : if (!(mState & stateDecodeStopped)) {
290 0 : aProxy->OnStopContainer(mImage);
291 : }
292 :
293 0 : if (!(mState & stateRequestStopped)) {
294 0 : aProxy->OnStopDecode(aStatus, nsnull);
295 : }
296 : }
297 :
298 27 : if (!(mState & stateRequestStopped)) {
299 0 : aProxy->OnStopRequest(true);
300 : }
301 27 : }
302 :
303 : void
304 8 : imgStatusTracker::RecordCancel()
305 : {
306 8 : if (!(mImageStatus & imgIRequest::STATUS_LOAD_PARTIAL))
307 8 : mImageStatus |= imgIRequest::STATUS_ERROR;
308 8 : }
309 :
310 : void
311 2 : imgStatusTracker::RecordLoaded()
312 : {
313 2 : NS_ABORT_IF_FALSE(mImage, "RecordLoaded called before we have an Image");
314 2 : mState |= stateRequestStarted | stateHasSize | stateRequestStopped;
315 2 : mImageStatus |= imgIRequest::STATUS_SIZE_AVAILABLE | imgIRequest::STATUS_LOAD_COMPLETE;
316 2 : mHadLastPart = true;
317 2 : }
318 :
319 : void
320 2 : imgStatusTracker::RecordDecoded()
321 : {
322 2 : NS_ABORT_IF_FALSE(mImage, "RecordDecoded called before we have an Image");
323 2 : mState |= stateDecodeStarted | stateDecodeStopped | stateFrameStopped;
324 2 : mImageStatus |= imgIRequest::STATUS_FRAME_COMPLETE | imgIRequest::STATUS_DECODE_COMPLETE;
325 2 : }
326 :
327 : /* non-virtual imgIDecoderObserver methods */
328 : void
329 2 : imgStatusTracker::RecordStartDecode()
330 : {
331 2 : NS_ABORT_IF_FALSE(mImage, "RecordStartDecode without an Image");
332 2 : mState |= stateDecodeStarted;
333 2 : }
334 :
335 : void
336 10 : imgStatusTracker::SendStartDecode(imgRequestProxy* aProxy)
337 : {
338 10 : if (!aProxy->NotificationsDeferred())
339 10 : aProxy->OnStartDecode();
340 10 : }
341 :
342 : void
343 6 : imgStatusTracker::RecordStartContainer(imgIContainer* aContainer)
344 : {
345 6 : NS_ABORT_IF_FALSE(mImage,
346 : "RecordStartContainer called before we have an Image");
347 6 : NS_ABORT_IF_FALSE(mImage == aContainer,
348 : "RecordStartContainer called with wrong Image");
349 6 : mState |= stateHasSize;
350 6 : mImageStatus |= imgIRequest::STATUS_SIZE_AVAILABLE;
351 6 : }
352 :
353 : void
354 20 : imgStatusTracker::SendStartContainer(imgRequestProxy* aProxy, imgIContainer* aContainer)
355 : {
356 20 : if (!aProxy->NotificationsDeferred())
357 18 : aProxy->OnStartContainer(aContainer);
358 20 : }
359 :
360 : void
361 4 : imgStatusTracker::RecordStartFrame(PRUint32 aFrame)
362 : {
363 4 : NS_ABORT_IF_FALSE(mImage, "RecordStartFrame called before we have an Image");
364 : // no bookkeeping necessary here - this is implied by imgIContainer's number
365 : // of frames
366 4 : }
367 :
368 : void
369 20 : imgStatusTracker::SendStartFrame(imgRequestProxy* aProxy, PRUint32 aFrame)
370 : {
371 20 : if (!aProxy->NotificationsDeferred())
372 20 : aProxy->OnStartFrame(aFrame);
373 20 : }
374 :
375 : void
376 2 : imgStatusTracker::RecordDataAvailable(bool aCurrentFrame, const nsIntRect* aRect)
377 : {
378 2 : NS_ABORT_IF_FALSE(mImage,
379 : "RecordDataAvailable called before we have an Image");
380 : // no bookkeeping necessary here - this is implied by imgIContainer's
381 : // number of frames and frame rect
382 2 : }
383 :
384 : void
385 10 : imgStatusTracker::SendDataAvailable(imgRequestProxy* aProxy, bool aCurrentFrame,
386 : const nsIntRect* aRect)
387 : {
388 10 : if (!aProxy->NotificationsDeferred())
389 10 : aProxy->OnDataAvailable(aCurrentFrame, aRect);
390 10 : }
391 :
392 :
393 : void
394 4 : imgStatusTracker::RecordStopFrame(PRUint32 aFrame)
395 : {
396 4 : NS_ABORT_IF_FALSE(mImage, "RecordStopFrame called before we have an Image");
397 4 : mState |= stateFrameStopped;
398 4 : mImageStatus |= imgIRequest::STATUS_FRAME_COMPLETE;
399 4 : }
400 :
401 : void
402 20 : imgStatusTracker::SendStopFrame(imgRequestProxy* aProxy, PRUint32 aFrame)
403 : {
404 20 : if (!aProxy->NotificationsDeferred())
405 20 : aProxy->OnStopFrame(aFrame);
406 20 : }
407 :
408 : void
409 2 : imgStatusTracker::RecordStopContainer(imgIContainer* aContainer)
410 : {
411 2 : NS_ABORT_IF_FALSE(mImage,
412 : "RecordStopContainer called before we have an Image");
413 : // No-op: see imgRequest::OnStopDecode for more information
414 2 : }
415 :
416 : void
417 10 : imgStatusTracker::SendStopContainer(imgRequestProxy* aProxy, imgIContainer* aContainer)
418 : {
419 : // No-op: see imgRequest::OnStopDecode for more information
420 10 : }
421 :
422 : void
423 2 : imgStatusTracker::RecordStopDecode(nsresult aStatus, const PRUnichar* statusArg)
424 : {
425 2 : NS_ABORT_IF_FALSE(mImage,
426 : "RecordStopDecode called before we have an Image");
427 2 : mState |= stateDecodeStopped;
428 :
429 2 : if (NS_SUCCEEDED(aStatus))
430 2 : mImageStatus |= imgIRequest::STATUS_DECODE_COMPLETE;
431 : // If we weren't successful, clear all success status bits and set error.
432 : else
433 0 : mImageStatus = imgIRequest::STATUS_ERROR;
434 2 : }
435 :
436 : void
437 10 : imgStatusTracker::SendStopDecode(imgRequestProxy* aProxy, nsresult aStatus,
438 : const PRUnichar* statusArg)
439 : {
440 : // See imgRequest::OnStopDecode for more information on why we call
441 : // OnStopContainer from here this, and why imgRequestProxy::OnStopDecode() is
442 : // called from OnStopRequest() and SyncNotify().
443 10 : if (!aProxy->NotificationsDeferred())
444 10 : aProxy->OnStopContainer(mImage);
445 10 : }
446 :
447 : void
448 0 : imgStatusTracker::RecordDiscard()
449 : {
450 0 : NS_ABORT_IF_FALSE(mImage,
451 : "RecordDiscard called before we have an Image");
452 : // Clear the state bits we no longer deserve.
453 0 : PRUint32 stateBitsToClear = stateDecodeStarted | stateDecodeStopped;
454 0 : mState &= ~stateBitsToClear;
455 :
456 : // Clear the status bits we no longer deserve.
457 : PRUint32 statusBitsToClear = imgIRequest::STATUS_FRAME_COMPLETE
458 0 : | imgIRequest::STATUS_DECODE_COMPLETE;
459 0 : mImageStatus &= ~statusBitsToClear;
460 0 : }
461 :
462 : void
463 5 : imgStatusTracker::SendImageIsAnimated(imgRequestProxy* aProxy)
464 : {
465 5 : if (!aProxy->NotificationsDeferred())
466 5 : aProxy->OnImageIsAnimated();
467 5 : }
468 :
469 : void
470 2 : imgStatusTracker::RecordImageIsAnimated()
471 : {
472 2 : NS_ABORT_IF_FALSE(mImage,
473 : "RecordImageIsAnimated called before we have an Image");
474 : // No bookkeeping necessary here - once decoding is complete, GetAnimated()
475 : // will accurately return that this is an animated image. Until that time,
476 : // the OnImageIsAnimated notification is the only indication an observer
477 : // will have that an image has more than 1 frame.
478 2 : }
479 :
480 : void
481 0 : imgStatusTracker::SendDiscard(imgRequestProxy* aProxy)
482 : {
483 0 : if (!aProxy->NotificationsDeferred())
484 0 : aProxy->OnDiscard();
485 0 : }
486 :
487 : /* non-virtual imgIContainerObserver methods */
488 : void
489 0 : imgStatusTracker::RecordFrameChanged(imgIContainer* aContainer,
490 : const nsIntRect* aDirtyRect)
491 : {
492 0 : NS_ABORT_IF_FALSE(mImage,
493 : "RecordFrameChanged called before we have an Image");
494 : // no bookkeeping necessary here - this is only for in-frame updates, which we
495 : // don't fire while we're recording
496 0 : }
497 :
498 : void
499 0 : imgStatusTracker::SendFrameChanged(imgRequestProxy* aProxy, imgIContainer* aContainer,
500 : const nsIntRect* aDirtyRect)
501 : {
502 0 : if (!aProxy->NotificationsDeferred())
503 0 : aProxy->FrameChanged(aContainer, aDirtyRect);
504 0 : }
505 :
506 : /* non-virtual sort-of-nsIRequestObserver methods */
507 : void
508 8 : imgStatusTracker::RecordStartRequest()
509 : {
510 : // We're starting a new load, so clear any status and state bits indicating
511 : // load/decode
512 8 : mImageStatus &= ~imgIRequest::STATUS_LOAD_PARTIAL;
513 8 : mImageStatus &= ~imgIRequest::STATUS_LOAD_COMPLETE;
514 8 : mImageStatus &= ~imgIRequest::STATUS_FRAME_COMPLETE;
515 8 : mState &= ~stateRequestStarted;
516 8 : mState &= ~stateDecodeStarted;
517 8 : mState &= ~stateDecodeStopped;
518 8 : mState &= ~stateRequestStopped;
519 :
520 8 : mState |= stateRequestStarted;
521 8 : }
522 :
523 : void
524 19 : imgStatusTracker::SendStartRequest(imgRequestProxy* aProxy)
525 : {
526 19 : if (!aProxy->NotificationsDeferred())
527 16 : aProxy->OnStartRequest();
528 19 : }
529 :
530 : void
531 8 : imgStatusTracker::RecordStopRequest(bool aLastPart, nsresult aStatus)
532 : {
533 8 : mHadLastPart = aLastPart;
534 8 : mState |= stateRequestStopped;
535 :
536 : // If we were successful in loading, note that the image is complete.
537 8 : if (NS_SUCCEEDED(aStatus))
538 4 : mImageStatus |= imgIRequest::STATUS_LOAD_COMPLETE;
539 8 : }
540 :
541 : void
542 23 : imgStatusTracker::SendStopRequest(imgRequestProxy* aProxy, bool aLastPart, nsresult aStatus)
543 : {
544 : // See bug 505385 and imgRequest::OnStopDecode for more information on why
545 : // OnStopDecode is called with OnStopRequest.
546 23 : if (!aProxy->NotificationsDeferred()) {
547 16 : aProxy->OnStopDecode(GetResultFromImageStatus(mImageStatus), nsnull);
548 16 : aProxy->OnStopRequest(aLastPart);
549 : }
550 23 : }
|