Skip to content

Defer the toolbar handle position calculation to an animation frame - #2433

Open
novucs wants to merge 3 commits into
django-commons:mainfrom
novucs:defer-handle-visibility-reflow
Open

Defer the toolbar handle position calculation to an animation frame#2433
novucs wants to merge 3 commits into
django-commons:mainfrom
novucs:defer-handle-visibility-reflow

Conversation

@novucs

@novucs novucs commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Description

ensureHandleVisibility() reads handle.offsetWidth during init(), directly after $$.show(handle) takes the handle from display:none. Layout is dirty at that point, so the read forces a synchronous document-wide style and layout flush after the page has already painted. On projects with a large stylesheet this is slow enough to be visible as a flash of unstyled content in Firefox.

It only affects a collapsed toolbar, since showToolbar() does not call ensureHandleVisibility().

Deferring the body to requestAnimationFrame moves the read past the paint. Behaviour is otherwise unchanged, and the resize listener benefits too, as repeated events now coalesce to at most one reflow per frame.

No test. The only observable change is timing, and ensureHandleVisibility() isn't reachable without scaffolding the whole toolbar DOM. Happy to add one if you'd like. Now has a selenium test for this.

Checklist:

  • I have added the relevant tests for this change.
  • I have added an item to the Pending section of docs/changes.rst.

AI/LLM Usage

  • This PR includes code generated with the help of an AI/LLM

@tim-schilling

Copy link
Copy Markdown
Member

Thank you for the PR @novucs. Would you be able to reproduce this in a tool like selenium?

ensureHandleVisibility() reads handle.offsetWidth during init(), directly
after $$.show(handle) takes the handle from display:none. Layout is dirty
at that point, so the read forces a synchronous document-wide style and
layout flush after the page has already painted. On projects with a large
stylesheet this is slow enough to be visible as a flash of unstyled
content in Firefox.

It only affects a collapsed toolbar, since showToolbar() does not call
ensureHandleVisibility().

Deferring the body to requestAnimationFrame moves the read past the paint.
Behaviour is otherwise unchanged, and the resize listener benefits too, as
repeated events now coalesce to at most one reflow per frame.
@novucs

novucs commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

Thank you for the PR @novucs. Would you be able to reproduce this in a tool like selenium?

@tim-schilling great idea. Here's a minimal reproducible script, how-to for running, and attached are video comparisons of the results: https://gist.github.com/novucs/9f8c31fa5661aecb3b8a940408914865?permalink_comment_id=6299585#gistcomment-6299585

patched.webm
unpatched.webm

@novucs
novucs force-pushed the defer-handle-visibility-reflow branch from abbc581 to 56d3929 Compare August 6, 2026 10:22
@novucs

novucs commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

Re-pushed to resolve lint errors in the changelog

@tim-schilling

Copy link
Copy Markdown
Member

Thanks for sharing that! Though I should have been clear that it was a yes/no question 😅. We have selenium tests for now so we'll need to translate your gist. I'd like to switch to playwright eventually.

If you have the time, do you mind pulling that example into a selenium test in the project? No worries if you're at your limit.

@novucs

novucs commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

Oops... 😂 I'll see what I can do when I next get a minute! Thanks for letting me know

@novucs

novucs commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

@tim-schilling I've now added & pushed a small regression test for this. While there, I've found a number of failing tests due to the shadow DOM change in #2266. The Selenium tests still find elements on the document, which can't reach inside the open shadow root. Happy to open a fix for these in a separate PR I have pushed fixes in a separate PR: #2434.

@novucs novucs mentioned this pull request Aug 6, 2026
3 tasks
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown

Coverage report

This PR does not seem to contain any modification to coverable code.

Comment thread tests/views.py Outdated
The fixture serves its stylesheet from a deliberately slow view and
records, from an animation frame registered ahead of the link, whether
that sheet had applied. Firefox withholds animation frames while a
stylesheet is pending, so a first frame that sees unstyled content means
something forced layout and lifted rendering suppression early.

The toolbar has to be collapsed for hideToolbar() to reach
ensureHandleVisibility(), and localStorage is cleared around the test
because djdt.show takes precedence over SHOW_COLLAPSED and persists
across tests in the shared driver.

Reverting the requestAnimationFrame wrapper fails the test 10 times out
of 10, and restoring it passes 10 out of 10. The check is specific to
Firefox: Chrome does not lift render blocking on a forced layout, so the
unpatched code does not flash there.
@novucs
novucs force-pushed the defer-handle-visibility-reflow branch from 3481200 to 6378f61 Compare August 7, 2026 19:50

@tim-schilling tim-schilling left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. I'd like to give this one a little time to let someone else chime in though.

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