Skip to content

Many animated images in the web page can freeze its rendering - #77264

Open
LexaTang wants to merge 1 commit into
WebKit:mainfrom
LexaTang:eng/Many-animated-images-in-the-web-page-can-freeze-its-rendering
Open

LexaTang wants to merge 1 commit into
WebKit:mainfrom
LexaTang:eng/Many-animated-images-in-the-web-page-can-freeze-its-rendering

Conversation

@LexaTang

@LexaTang LexaTang commented Oct 16, 2026 •

Copy link
Copy Markdown

489ea5e

Many animated images in the web page can freeze its rendering
https://bugs.webkit.org/show_bug.cgi?id=308730

Reviewed by NOBODY (OOPS!).

Each BitmapImageSource that decoded asynchronously created its own WorkQueue. That queue ran
one work item that looped on a blocking SynchronizedFixedQueue::dequeue() until stop(). The
queue is stopped only when it is idle and the image is static or its animation has stopped.
So every animating image, and every removed image with a pending frame request, held a thread
that waited for future frames. With many animated images, these waiting workers accumulate
until later images can no longer be decoded and the page stops rendering new frames.
AVIF decoding also uses GCD internally. Allowing an unbounded number of image decodes
can exhaust the available GCD worker threads when the platform decoder dispatches its
own work, preventing decoding from making progress. Bound concurrent image decoding
as well as eliminating workers that wait for future frame requests.

Replace the long-running loop with one finite work item per decode request:
- Requests for a single image still run serially, including across stop() and restart.
- A shared scheduler runs ready images on at most 8 reusable WorkQueue workers, giving other
  ready images a turn between frames. An empty image queue never keeps a worker waiting, and
  queue pressure never turns into unbounded concurrent decodes.
- stop() increments a generation counter. Stale requests skip decoding, and their completions
  are ignored. A completion is still posted to the creation thread, so the source and the
  work queue are released there.
- ImageFrameWorkQueue keeps the decoder between requests until stop(), as the old loop did.
- The minimum decoding duration for testing is captured at dispatch time instead of being
  read on the decoding thread.
Also, when pruning decoded data, keep an already decoded next frame while the animation
timer is pending. Otherwise the frame would be decoded synchronously when the timer fires.
Keep the decoder while decoded frames are still in use.

Tests: fast/images/async-image-decoding-concurrent-images.html
       fast/images/async-image-decoding-removed-images.html

* LayoutTests/TestExpectations:
* LayoutTests/fast/images/async-image-decoding-concurrent-images-expected.txt: Added.
* LayoutTests/fast/images/async-image-decoding-concurrent-images.html: Added.
* LayoutTests/fast/images/async-image-decoding-removed-images-expected.txt: Added.
* LayoutTests/fast/images/async-image-decoding-removed-images.html: Added.
* Source/WebCore/platform/graphics/BitmapImageSource.cpp:
(WebCore::BitmapImageSource::destroyDecodedFrames):
(WebCore::BitmapImageSource::destroyDecodedData):
* Source/WebCore/platform/graphics/ImageFrameAnimator.h:
(WebCore::ImageFrameAnimator::nextFrameIndex const):
* Source/WebCore/platform/graphics/ImageFrameWorkQueue.cpp:
(WebCore::ImageDecodingQueue::create):
(WebCore::ImageDecodingQueue::WTF_GUARDED_BY_LOCK):
(WebCore::ImageDecodingScheduler::singleton):
(WebCore::ImageDecodingScheduler::dispatch):
(WebCore::ImageDecodingScheduler::WTF_GUARDED_BY_LOCK):
(WebCore::ImageDecodingQueue::dispatch):
(WebCore::ImageDecodingQueue::runOneRequest):
(WebCore::ImageFrameWorkQueue::ImageFrameWorkQueue):
(WebCore::ImageFrameWorkQueue::dispatch):
(WebCore::ImageFrameWorkQueue::stop):
(WebCore::ImageFrameWorkQueue::requestQueue): Deleted.
(WebCore::ImageFrameWorkQueue::start): Deleted.
* Source/WebCore/platform/graphics/ImageFrameWorkQueue.h:
(WebCore::ImageFrameWorkQueue::minimumDecodingDurationForTesting const): Deleted.

489ea5e

Misc iOS, visionOS, tvOS & watchOS macOS Linux Windows
✅ 🧪 style ✅ 🛠 ios ✅ 🛠 mac ✅ 🛠 wpe ✅ 🛠 win
✅ 🧪 bindings ✅ 🛠 ios-sim ✅ 🛠 mac-AS-debug ✅ 🧪 wpe-wk2 loading 🧪 win-tests
✅ 🧪 webkitperl ✅ 🧪 ios-wk2 ✅ 🧪 api-mac ✅ 🧪 api-wpe
✅ 🧪 ios-wk2-wpt ⏳ 🧪 api-mac-debug
⏳ 🧪 api-ios ✅ 🧪 mac-wk2 ✅ 🛠 gtk3-gcc
✅ 🛠 ios-safer-cpp ⏳ 🧪 mac-AS-debug-wk2 ✅ 🛠 gtk
✅ 🛠 vision ✅ 🧪 mac-wk2-stress ✅ 🧪 gtk-wk2
✅ 🛠 vision-sim ⏳ 🧪 mac-intel-wk2 ✅ 🧪 api-gtk
✅ 🧪 vision-wk2 ✅ 🛠 mac-safer-cpp   🛠 playstation
✅ 🛠 tv ✅ 🧪 mac-site-isolation
✅ 🛠 tv-sim
✅ 🛠 watch
✅ 🛠 watch-sim

https://bugs.webkit.org/show_bug.cgi?id=308730

Reviewed by NOBODY (OOPS!).

Each BitmapImageSource that decoded asynchronously created its own WorkQueue. That queue ran
one work item that looped on a blocking SynchronizedFixedQueue::dequeue() until stop(). The
queue is stopped only when it is idle and the image is static or its animation has stopped.
So every animating image, and every removed image with a pending frame request, held a thread
that waited for future frames. With many animated images, these waiting workers accumulate
until later images can no longer be decoded and the page stops rendering new frames.
AVIF decoding also uses GCD internally. Allowing an unbounded number of image decodes
can exhaust the available GCD worker threads when the platform decoder dispatches its
own work, preventing decoding from making progress. Bound concurrent image decoding
as well as eliminating workers that wait for future frame requests.

Replace the long-running loop with one finite work item per decode request:
- Requests for a single image still run serially, including across stop() and restart.
- A shared scheduler runs ready images on at most 8 reusable WorkQueue workers, giving other
  ready images a turn between frames. An empty image queue never keeps a worker waiting, and
  queue pressure never turns into unbounded concurrent decodes.
- stop() increments a generation counter. Stale requests skip decoding, and their completions
  are ignored. A completion is still posted to the creation thread, so the source and the
  work queue are released there.
- ImageFrameWorkQueue keeps the decoder between requests until stop(), as the old loop did.
- The minimum decoding duration for testing is captured at dispatch time instead of being
  read on the decoding thread.
Also, when pruning decoded data, keep an already decoded next frame while the animation
timer is pending. Otherwise the frame would be decoded synchronously when the timer fires.
Keep the decoder while decoded frames are still in use.

Tests: fast/images/async-image-decoding-concurrent-images.html
       fast/images/async-image-decoding-removed-images.html

* LayoutTests/TestExpectations:
* LayoutTests/fast/images/async-image-decoding-concurrent-images-expected.txt: Added.
* LayoutTests/fast/images/async-image-decoding-concurrent-images.html: Added.
* LayoutTests/fast/images/async-image-decoding-removed-images-expected.txt: Added.
* LayoutTests/fast/images/async-image-decoding-removed-images.html: Added.
* Source/WebCore/platform/graphics/BitmapImageSource.cpp:
(WebCore::BitmapImageSource::destroyDecodedFrames):
(WebCore::BitmapImageSource::destroyDecodedData):
* Source/WebCore/platform/graphics/ImageFrameAnimator.h:
(WebCore::ImageFrameAnimator::nextFrameIndex const):
* Source/WebCore/platform/graphics/ImageFrameWorkQueue.cpp:
(WebCore::ImageDecodingQueue::create):
(WebCore::ImageDecodingQueue::WTF_GUARDED_BY_LOCK):
(WebCore::ImageDecodingScheduler::singleton):
(WebCore::ImageDecodingScheduler::dispatch):
(WebCore::ImageDecodingScheduler::WTF_GUARDED_BY_LOCK):
(WebCore::ImageDecodingQueue::dispatch):
(WebCore::ImageDecodingQueue::runOneRequest):
(WebCore::ImageFrameWorkQueue::ImageFrameWorkQueue):
(WebCore::ImageFrameWorkQueue::dispatch):
(WebCore::ImageFrameWorkQueue::stop):
(WebCore::ImageFrameWorkQueue::requestQueue): Deleted.
(WebCore::ImageFrameWorkQueue::start): Deleted.
* Source/WebCore/platform/graphics/ImageFrameWorkQueue.h:
(WebCore::ImageFrameWorkQueue::minimumDecodingDurationForTesting const): Deleted.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants