Line data Source code
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 "imgIContainer.h"
11 : #include "imgINotificationObserver.h"
12 : #include "imgIRequest.h"
13 : #include "Image.h"
14 : #include "nsNetUtil.h"
15 : #include "nsIObserverService.h"
16 :
17 : #include "mozilla/Assertions.h"
18 : #include "mozilla/Services.h"
19 :
20 : using mozilla::WeakPtr;
21 :
22 : namespace mozilla {
23 : namespace image {
24 :
25 : static void
26 195 : CheckProgressConsistency(Progress aOldProgress, Progress aNewProgress, bool aIsMultipart)
27 : {
28 : // Check preconditions for every progress bit.
29 :
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.
34 :
35 195 : if (aNewProgress & FLAG_SIZE_AVAILABLE) {
36 : // No preconditions.
37 : }
38 195 : if (aNewProgress & FLAG_DECODE_COMPLETE) {
39 61 : MOZ_ASSERT(aNewProgress & FLAG_SIZE_AVAILABLE);
40 61 : MOZ_ASSERT(aIsMultipart || aNewProgress & (FLAG_FRAME_COMPLETE | FLAG_HAS_ERROR));
41 : }
42 195 : if (aNewProgress & FLAG_FRAME_COMPLETE) {
43 95 : MOZ_ASSERT(aNewProgress & FLAG_SIZE_AVAILABLE);
44 : }
45 195 : if (aNewProgress & FLAG_LOAD_COMPLETE) {
46 135 : MOZ_ASSERT(aIsMultipart || aNewProgress & (FLAG_SIZE_AVAILABLE | FLAG_HAS_ERROR));
47 : }
48 195 : if (aNewProgress & FLAG_ONLOAD_BLOCKED) {
49 : // No preconditions.
50 : }
51 195 : if (aNewProgress & FLAG_ONLOAD_UNBLOCKED) {
52 114 : MOZ_ASSERT(aNewProgress & FLAG_ONLOAD_BLOCKED);
53 114 : MOZ_ASSERT(aIsMultipart || aNewProgress & (FLAG_SIZE_AVAILABLE | FLAG_HAS_ERROR));
54 : }
55 195 : if (aNewProgress & FLAG_IS_ANIMATED) {
56 : // No preconditions; like FLAG_HAS_TRANSPARENCY, we should normally never
57 : // discover this *after* FLAG_SIZE_AVAILABLE, but unfortunately some corrupt
58 : // GIFs may fool us.
59 : }
60 195 : if (aNewProgress & FLAG_HAS_TRANSPARENCY) {
61 : // XXX We'd like to assert that transparency is only set during metadata
62 : // decode but we don't have any way to assert that until bug 1254892 is fixed.
63 : }
64 195 : if (aNewProgress & FLAG_LAST_PART_COMPLETE) {
65 135 : MOZ_ASSERT(aNewProgress & FLAG_LOAD_COMPLETE);
66 : }
67 195 : if (aNewProgress & FLAG_HAS_ERROR) {
68 : // No preconditions.
69 : }
70 195 : }
71 :
72 : void
73 41 : ProgressTracker::SetImage(Image* aImage)
74 : {
75 82 : MutexAutoLock lock(mImageMutex);
76 41 : MOZ_ASSERT(aImage, "Setting null image");
77 41 : MOZ_ASSERT(!mImage, "Setting image when we already have one");
78 41 : mImage = aImage;
79 41 : }
80 :
81 : void
82 1 : ProgressTracker::ResetImage()
83 : {
84 2 : MutexAutoLock lock(mImageMutex);
85 1 : MOZ_ASSERT(mImage, "Resetting image when it's already null!");
86 1 : mImage = nullptr;
87 1 : }
88 :
89 : uint32_t
90 659 : ProgressTracker::GetImageStatus() const
91 : {
92 659 : uint32_t status = imgIRequest::STATUS_NONE;
93 :
94 : // Translate our current state to a set of imgIRequest::STATE_* flags.
95 659 : if (mProgress & FLAG_SIZE_AVAILABLE) {
96 644 : status |= imgIRequest::STATUS_SIZE_AVAILABLE;
97 : }
98 659 : if (mProgress & FLAG_DECODE_COMPLETE) {
99 613 : status |= imgIRequest::STATUS_DECODE_COMPLETE;
100 : }
101 659 : if (mProgress & FLAG_FRAME_COMPLETE) {
102 614 : status |= imgIRequest::STATUS_FRAME_COMPLETE;
103 : }
104 659 : if (mProgress & FLAG_LOAD_COMPLETE) {
105 645 : status |= imgIRequest::STATUS_LOAD_COMPLETE;
106 : }
107 659 : if (mProgress & FLAG_IS_ANIMATED) {
108 13 : status |= imgIRequest::STATUS_IS_ANIMATED;
109 : }
110 659 : if (mProgress & FLAG_HAS_TRANSPARENCY) {
111 644 : status |= imgIRequest::STATUS_HAS_TRANSPARENCY;
112 : }
113 659 : if (mProgress & FLAG_HAS_ERROR) {
114 1 : status |= imgIRequest::STATUS_ERROR;
115 : }
116 :
117 659 : return status;
118 : }
119 :
120 : // A helper class to allow us to call SyncNotify asynchronously.
121 6 : class AsyncNotifyRunnable : public Runnable
122 : {
123 : public:
124 2 : AsyncNotifyRunnable(ProgressTracker* aTracker,
125 : IProgressObserver* aObserver)
126 2 : : Runnable("ProgressTracker::AsyncNotifyRunnable")
127 2 : , mTracker(aTracker)
128 : {
129 2 : MOZ_ASSERT(NS_IsMainThread(), "Should be created on the main thread");
130 2 : MOZ_ASSERT(aTracker, "aTracker should not be null");
131 2 : MOZ_ASSERT(aObserver, "aObserver should not be null");
132 2 : mObservers.AppendElement(aObserver);
133 2 : }
134 :
135 2 : NS_IMETHOD Run() override
136 : {
137 2 : MOZ_ASSERT(NS_IsMainThread(), "Should be running on the main thread");
138 2 : MOZ_ASSERT(mTracker, "mTracker should not be null");
139 6 : for (uint32_t i = 0; i < mObservers.Length(); ++i) {
140 4 : mObservers[i]->SetNotificationsDeferred(false);
141 4 : mTracker->SyncNotify(mObservers[i]);
142 : }
143 :
144 2 : mTracker->mRunnable = nullptr;
145 2 : return NS_OK;
146 : }
147 :
148 2 : void AddObserver(IProgressObserver* aObserver)
149 : {
150 2 : mObservers.AppendElement(aObserver);
151 2 : }
152 :
153 0 : void RemoveObserver(IProgressObserver* aObserver)
154 : {
155 0 : mObservers.RemoveElement(aObserver);
156 0 : }
157 :
158 : private:
159 : friend class ProgressTracker;
160 :
161 : RefPtr<ProgressTracker> mTracker;
162 : nsTArray<RefPtr<IProgressObserver>> mObservers;
163 : };
164 :
165 : void
166 4 : ProgressTracker::Notify(IProgressObserver* aObserver)
167 : {
168 4 : MOZ_ASSERT(NS_IsMainThread());
169 :
170 4 : if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
171 0 : RefPtr<Image> image = GetImage();
172 0 : if (image && image->GetURI()) {
173 0 : RefPtr<ImageURL> uri(image->GetURI());
174 0 : nsAutoCString spec;
175 0 : uri->GetSpec(spec);
176 : LOG_FUNC_WITH_PARAM(gImgLog,
177 0 : "ProgressTracker::Notify async", "uri", spec.get());
178 : } else {
179 : LOG_FUNC_WITH_PARAM(gImgLog,
180 0 : "ProgressTracker::Notify async", "uri", "<unknown>");
181 : }
182 : }
183 :
184 4 : aObserver->SetNotificationsDeferred(true);
185 :
186 : // If we have an existing runnable that we can use, we just append this
187 : // observer to its list of observers to be notified. This ensures we don't
188 : // unnecessarily delay onload.
189 : AsyncNotifyRunnable* runnable =
190 4 : static_cast<AsyncNotifyRunnable*>(mRunnable.get());
191 :
192 4 : if (runnable) {
193 2 : runnable->AddObserver(aObserver);
194 : } else {
195 2 : mRunnable = new AsyncNotifyRunnable(this, aObserver);
196 2 : NS_DispatchToCurrentThread(mRunnable);
197 : }
198 4 : }
199 :
200 : // A helper class to allow us to call SyncNotify asynchronously for a given,
201 : // fixed, state.
202 0 : class AsyncNotifyCurrentStateRunnable : public Runnable
203 : {
204 : public:
205 0 : AsyncNotifyCurrentStateRunnable(ProgressTracker* aProgressTracker,
206 : IProgressObserver* aObserver)
207 0 : : Runnable("image::AsyncNotifyCurrentStateRunnable")
208 : , mProgressTracker(aProgressTracker)
209 0 : , mObserver(aObserver)
210 : {
211 0 : MOZ_ASSERT(NS_IsMainThread(), "Should be created on the main thread");
212 0 : MOZ_ASSERT(mProgressTracker, "mProgressTracker should not be null");
213 0 : MOZ_ASSERT(mObserver, "mObserver should not be null");
214 0 : mImage = mProgressTracker->GetImage();
215 0 : }
216 :
217 0 : NS_IMETHOD Run() override
218 : {
219 0 : MOZ_ASSERT(NS_IsMainThread(), "Should be running on the main thread");
220 0 : mObserver->SetNotificationsDeferred(false);
221 :
222 0 : mProgressTracker->SyncNotify(mObserver);
223 0 : return NS_OK;
224 : }
225 :
226 : private:
227 : RefPtr<ProgressTracker> mProgressTracker;
228 : RefPtr<IProgressObserver> mObserver;
229 :
230 : // We have to hold on to a reference to the tracker's image, just in case
231 : // it goes away while we're in the event queue.
232 : RefPtr<Image> mImage;
233 : };
234 :
235 : void
236 0 : ProgressTracker::NotifyCurrentState(IProgressObserver* aObserver)
237 : {
238 0 : MOZ_ASSERT(NS_IsMainThread());
239 :
240 0 : if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
241 0 : RefPtr<Image> image = GetImage();
242 0 : nsAutoCString spec;
243 0 : if (image && image->GetURI()) {
244 0 : image->GetURI()->GetSpec(spec);
245 : }
246 : LOG_FUNC_WITH_PARAM(gImgLog,
247 0 : "ProgressTracker::NotifyCurrentState", "uri", spec.get());
248 : }
249 :
250 0 : aObserver->SetNotificationsDeferred(true);
251 :
252 : nsCOMPtr<nsIRunnable> ev = new AsyncNotifyCurrentStateRunnable(this,
253 0 : aObserver);
254 0 : NS_DispatchToCurrentThread(ev);
255 0 : }
256 :
257 : /**
258 : * ImageObserverNotifier is a helper type that abstracts over the difference
259 : * between sending notifications to all of the observers in an ObserverTable,
260 : * and sending them to a single observer. This allows the same notification code
261 : * to be used for both cases.
262 : */
263 : template <typename T> struct ImageObserverNotifier;
264 :
265 : template <>
266 : struct MOZ_STACK_CLASS ImageObserverNotifier<const ObserverTable*>
267 : {
268 236 : explicit ImageObserverNotifier(const ObserverTable* aObservers,
269 : bool aIgnoreDeferral = false)
270 236 : : mObservers(aObservers)
271 236 : , mIgnoreDeferral(aIgnoreDeferral)
272 236 : { }
273 :
274 : template <typename Lambda>
275 403 : void operator()(Lambda aFunc)
276 : {
277 1577 : for (auto iter = mObservers->ConstIter(); !iter.Done(); iter.Next()) {
278 2348 : RefPtr<IProgressObserver> observer = iter.Data().get();
279 3518 : if (observer &&
280 2232 : (mIgnoreDeferral || !observer->NotificationsDeferred())) {
281 1170 : aFunc(observer);
282 : }
283 : }
284 403 : }
285 :
286 : private:
287 : const ObserverTable* mObservers;
288 : const bool mIgnoreDeferral;
289 : };
290 :
291 : template <>
292 : struct MOZ_STACK_CLASS ImageObserverNotifier<IProgressObserver*>
293 : {
294 96 : explicit ImageObserverNotifier(IProgressObserver* aObserver)
295 96 : : mObserver(aObserver)
296 96 : { }
297 :
298 : template <typename Lambda>
299 144 : void operator()(Lambda aFunc)
300 : {
301 144 : if (mObserver && !mObserver->NotificationsDeferred()) {
302 144 : aFunc(mObserver);
303 : }
304 144 : }
305 :
306 : private:
307 : IProgressObserver* mObserver;
308 : };
309 :
310 : template <typename T> void
311 291 : SyncNotifyInternal(const T& aObservers,
312 : bool aHasImage,
313 : Progress aProgress,
314 : const nsIntRect& aDirtyRect)
315 : {
316 291 : MOZ_ASSERT(NS_IsMainThread());
317 :
318 : typedef imgINotificationObserver I;
319 291 : ImageObserverNotifier<T> notify(aObservers);
320 :
321 291 : if (aProgress & FLAG_SIZE_AVAILABLE) {
322 189 : notify([](IProgressObserver* aObs) { aObs->Notify(I::SIZE_AVAILABLE); });
323 : }
324 :
325 291 : if (aProgress & FLAG_ONLOAD_BLOCKED) {
326 248 : notify([](IProgressObserver* aObs) { aObs->BlockOnload(); });
327 : }
328 :
329 291 : if (aHasImage) {
330 : // OnFrameUpdate
331 : // If there's any content in this frame at all (always true for
332 : // vector images, true for raster images that have decoded at
333 : // least one frame) then send OnFrameUpdate.
334 216 : if (!aDirtyRect.IsEmpty()) {
335 287 : notify([&](IProgressObserver* aObs) {
336 205 : aObs->Notify(I::FRAME_UPDATE, &aDirtyRect);
337 205 : });
338 : }
339 :
340 216 : if (aProgress & FLAG_FRAME_COMPLETE) {
341 174 : notify([](IProgressObserver* aObs) { aObs->Notify(I::FRAME_COMPLETE); });
342 : }
343 :
344 216 : if (aProgress & FLAG_HAS_TRANSPARENCY) {
345 189 : notify([](IProgressObserver* aObs) { aObs->Notify(I::HAS_TRANSPARENCY); });
346 : }
347 :
348 216 : if (aProgress & FLAG_IS_ANIMATED) {
349 8 : notify([](IProgressObserver* aObs) { aObs->Notify(I::IS_ANIMATED); });
350 : }
351 : }
352 :
353 : // Send UnblockOnload before OnStopDecode and OnStopRequest. This allows
354 : // observers that can fire events when they receive those notifications to do
355 : // so then, instead of being forced to wait for UnblockOnload.
356 291 : if (aProgress & FLAG_ONLOAD_UNBLOCKED) {
357 244 : notify([](IProgressObserver* aObs) { aObs->UnblockOnload(); });
358 : }
359 :
360 291 : if (aProgress & FLAG_DECODE_COMPLETE) {
361 52 : MOZ_ASSERT(aHasImage, "Stopped decoding without ever having an image?");
362 174 : notify([](IProgressObserver* aObs) { aObs->Notify(I::DECODE_COMPLETE); });
363 : }
364 :
365 291 : if (aProgress & FLAG_LOAD_COMPLETE) {
366 191 : notify([=](IProgressObserver* aObs) {
367 133 : aObs->OnLoadComplete(aProgress & FLAG_LAST_PART_COMPLETE);
368 133 : });
369 : }
370 291 : }
371 :
372 : void
373 195 : ProgressTracker::SyncNotifyProgress(Progress aProgress,
374 : const nsIntRect& aInvalidRect
375 : /* = nsIntRect() */)
376 : {
377 195 : MOZ_ASSERT(NS_IsMainThread(), "Use mObservers on main thread only");
378 :
379 : // Don't unblock onload if we're not blocked.
380 195 : Progress progress = Difference(aProgress);
381 195 : if (!((mProgress | progress) & FLAG_ONLOAD_BLOCKED)) {
382 3 : progress &= ~FLAG_ONLOAD_UNBLOCKED;
383 : }
384 :
385 195 : CheckProgressConsistency(mProgress, mProgress | progress, mIsMultipart);
386 :
387 : // XXX(seth): Hack to work around the fact that some observers have bugs and
388 : // need to get onload blocking notifications multiple times. We should fix
389 : // those observers and remove this.
390 230 : if ((aProgress & FLAG_DECODE_COMPLETE) &&
391 70 : (mProgress & FLAG_ONLOAD_BLOCKED) &&
392 35 : (mProgress & FLAG_ONLOAD_UNBLOCKED)) {
393 14 : progress |= FLAG_ONLOAD_BLOCKED | FLAG_ONLOAD_UNBLOCKED;
394 : }
395 :
396 : // Apply the changes.
397 195 : mProgress |= progress;
398 :
399 : // Send notifications.
400 390 : mObservers.Read([&](const ObserverTable* aTable) {
401 195 : SyncNotifyInternal(aTable, HasImage(), progress, aInvalidRect);
402 585 : });
403 :
404 195 : if (progress & FLAG_HAS_ERROR) {
405 1 : FireFailureNotification();
406 : }
407 195 : }
408 :
409 : void
410 96 : ProgressTracker::SyncNotify(IProgressObserver* aObserver)
411 : {
412 96 : MOZ_ASSERT(NS_IsMainThread());
413 :
414 192 : RefPtr<Image> image = GetImage();
415 :
416 192 : nsAutoCString spec;
417 96 : if (image && image->GetURI()) {
418 21 : image->GetURI()->GetSpec(spec);
419 : }
420 192 : LOG_SCOPE_WITH_PARAM(gImgLog,
421 : "ProgressTracker::SyncNotify", "uri", spec.get());
422 :
423 96 : nsIntRect rect;
424 96 : if (image) {
425 38 : if (NS_FAILED(image->GetWidth(&rect.width)) ||
426 17 : NS_FAILED(image->GetHeight(&rect.height))) {
427 : // Either the image has no intrinsic size, or it has an error.
428 4 : rect = GetMaxSizedIntRect();
429 : }
430 : }
431 :
432 96 : SyncNotifyInternal(aObserver, !!image, mProgress, rect);
433 96 : }
434 :
435 : void
436 29 : ProgressTracker::EmulateRequestFinished(IProgressObserver* aObserver)
437 : {
438 29 : MOZ_ASSERT(NS_IsMainThread(),
439 : "SyncNotifyState and mObservers are not threadsafe");
440 58 : RefPtr<IProgressObserver> kungFuDeathGrip(aObserver);
441 :
442 29 : if (mProgress & FLAG_ONLOAD_BLOCKED && !(mProgress & FLAG_ONLOAD_UNBLOCKED)) {
443 0 : aObserver->UnblockOnload();
444 : }
445 :
446 29 : if (!(mProgress & FLAG_LOAD_COMPLETE)) {
447 4 : aObserver->OnLoadComplete(true);
448 : }
449 29 : }
450 :
451 : void
452 137 : ProgressTracker::AddObserver(IProgressObserver* aObserver)
453 : {
454 137 : MOZ_ASSERT(NS_IsMainThread());
455 274 : RefPtr<IProgressObserver> observer = aObserver;
456 :
457 548 : mObservers.Write([=](ObserverTable* aTable) {
458 137 : MOZ_ASSERT(!aTable->Get(observer, nullptr),
459 : "Adding duplicate entry for image observer");
460 :
461 274 : WeakPtr<IProgressObserver> weakPtr = observer.get();
462 137 : aTable->Put(observer, weakPtr);
463 274 : });
464 137 : }
465 :
466 : bool
467 58 : ProgressTracker::RemoveObserver(IProgressObserver* aObserver)
468 : {
469 58 : MOZ_ASSERT(NS_IsMainThread());
470 116 : RefPtr<IProgressObserver> observer = aObserver;
471 :
472 : // Remove the observer from the list.
473 232 : bool removed = mObservers.Write([=](ObserverTable* aTable) {
474 58 : return aTable->Remove(observer);
475 116 : });
476 :
477 : // Observers can get confused if they don't get all the proper teardown
478 : // notifications. Part ways on good terms.
479 58 : if (removed && !aObserver->NotificationsDeferred()) {
480 29 : EmulateRequestFinished(aObserver);
481 : }
482 :
483 : // Make sure we don't give callbacks to an observer that isn't interested in
484 : // them any more.
485 : AsyncNotifyRunnable* runnable =
486 58 : static_cast<AsyncNotifyRunnable*>(mRunnable.get());
487 :
488 58 : if (aObserver->NotificationsDeferred() && runnable) {
489 0 : runnable->RemoveObserver(aObserver);
490 0 : aObserver->SetNotificationsDeferred(false);
491 : }
492 :
493 116 : return removed;
494 : }
495 :
496 : uint32_t
497 207 : ProgressTracker::ObserverCount() const
498 : {
499 207 : MOZ_ASSERT(NS_IsMainThread());
500 621 : return mObservers.Read([](const ObserverTable* aTable) {
501 207 : return aTable->Count();
502 621 : });
503 : }
504 :
505 : void
506 0 : ProgressTracker::OnUnlockedDraw()
507 : {
508 0 : MOZ_ASSERT(NS_IsMainThread());
509 0 : mObservers.Read([](const ObserverTable* aTable) {
510 0 : ImageObserverNotifier<const ObserverTable*> notify(aTable);
511 0 : notify([](IProgressObserver* aObs) {
512 0 : aObs->Notify(imgINotificationObserver::UNLOCKED_DRAW);
513 0 : });
514 0 : });
515 0 : }
516 :
517 : void
518 0 : ProgressTracker::ResetForNewRequest()
519 : {
520 0 : MOZ_ASSERT(NS_IsMainThread());
521 0 : mProgress = NoProgress;
522 0 : }
523 :
524 : void
525 0 : ProgressTracker::OnDiscard()
526 : {
527 0 : MOZ_ASSERT(NS_IsMainThread());
528 0 : mObservers.Read([](const ObserverTable* aTable) {
529 0 : ImageObserverNotifier<const ObserverTable*> notify(aTable);
530 0 : notify([](IProgressObserver* aObs) {
531 0 : aObs->Notify(imgINotificationObserver::DISCARD);
532 0 : });
533 0 : });
534 0 : }
535 :
536 : void
537 41 : ProgressTracker::OnImageAvailable()
538 : {
539 41 : MOZ_ASSERT(NS_IsMainThread());
540 : // Notify any imgRequestProxys that are observing us that we have an Image.
541 123 : mObservers.Read([](const ObserverTable* aTable) {
542 : ImageObserverNotifier<const ObserverTable*>
543 41 : notify(aTable, /* aIgnoreDeferral = */ true);
544 157 : notify([](IProgressObserver* aObs) {
545 116 : aObs->SetHasImage();
546 157 : });
547 82 : });
548 41 : }
549 :
550 : void
551 1 : ProgressTracker::FireFailureNotification()
552 : {
553 1 : MOZ_ASSERT(NS_IsMainThread());
554 :
555 : // Some kind of problem has happened with image decoding.
556 : // Report the URI to net:failed-to-process-uri-conent observers.
557 2 : RefPtr<Image> image = GetImage();
558 1 : if (image) {
559 : // Should be on main thread, so ok to create a new nsIURI.
560 2 : nsCOMPtr<nsIURI> uri;
561 : {
562 2 : RefPtr<ImageURL> threadsafeUriData = image->GetURI();
563 1 : uri = threadsafeUriData ? threadsafeUriData->ToIURI() : nullptr;
564 : }
565 1 : if (uri) {
566 2 : nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
567 1 : if (os) {
568 1 : os->NotifyObservers(uri, "net:failed-to-process-uri-content", nullptr);
569 : }
570 : }
571 : }
572 1 : }
573 :
574 : } // namespace image
575 : } // namespace mozilla
|