ProgressTracker.cpp 16 KB

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