Skip to content

feat(overlay): replace native showModal() for performance optimization #5307

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 47 commits into from
Apr 28, 2025

Conversation

rubencarvalho
Copy link
Contributor

@rubencarvalho rubencarvalho commented Mar 31, 2025

TODO

  1. Add tests for ensuring focus-trap is working (e.g., enter modal, tab, tab, tab, focus is still in modal)
  2. Fix failing tests
    • We skipped 4 tests on chromium ci 🍡
    • forcePopover - is a popover in mobile - action-menu-responsive.test.ts
    • forcePopover - is a popover in mobile - picker-responsive.test.ts
    • Overlay Trigger - extended - manages placement on scroll - overlay-trigger-extended.ts
    • Overlay Trigger - extended - occludes content behind the overlay - overlay-trigger-extended.ts
  3. The "Modal" version is working but the "Page" version doesn't prevent background page access. Validate this by triggering the Rotor while using VO

Description

Context about the problem

Opening or closing a modal dialog in Spectrum Web Components is slow (up to 5+ seconds on lower-end devices).
You can verify it here by opening the modal overlay and setting 4×+ CPU throttling in Chrome DevTools.

The slowdown occurs when using the native dialog.showModal() API in all browsers with a large or complex DOM. We’re particularly concerned about Chromium-based browsers, which are dominant across the Android ecosystem where entry-level devices are more common. This is a known Chromium issue: opening a modal via showModal() triggers inert mode, which causes a full style recalculation across all non-dialog content. After reviewing the Chromium implementation, the “setting inertness” step appears to be the primary bottleneck.

You can observe the performance difference between showModal() and show() in this CodePen:
👉 https://codepen.io/rubencarvalho/pen/VYwgMwa
And a second demo confirming that inert propagation itself is expensive:
👉 https://codepen.io/rubencarvalho/pen/MYWLEPg

I’ve followed up on the Chromium bug report and will continue tracking progress.

The path forward

To work around the performance issues caused by showModal(), in this PR, we reimplement the native functionality using the existing popover logic:

  • popover.showPopover() instead of showModal();

This gives us the visual and stacking behavior we want without triggering the expensive full-document inertness.

However, by avoiding showModal(), we lose some of the browser’s built-in functionality. We needed to manually reimplement the following behaviors:

  • Focus trapping
  • A11Y (e.g., settingaria-modal="true", ensuring screen-readers don't read content in the supposedly inert DOM)

Related issue(s)

  • SWC-614

How has this been tested?

  • No changes to the perceived overlay behaviour

    1. Go to the modal documentation
    2. Expect all the overlays to look and behave as before
  • Test focus trap on modal

    1. Tab inside the modal overlay
    2. Notice how the focus never leaves the dialog
    3. Escape dismisses the overlay
    4. Clicking outside dismisses the overlay
  • Test focus trap on page overlay

    1. Tab inside the modal overlay
    2. Notice how the focus never leaves the dialog
    3. Escape does not dismiss the overlay
  • Test VO or other screen assist (e.g. use the VoiceOver rotor on Mac)

    1. Focus inside the modal overlay
    2. Expect no other content to be read other than the one in the dialog
  • Did it pass in Desktop?

  • Did it pass in Mobile?

  • Did it pass in iPad?

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Chore (minor updates related to the tooling or maintenance of the repository, does not impact compiled assets)

Checklist

  • I have signed the Adobe Open Source CLA.
  • My code follows the code style of this project.
  • If my change required a change to the documentation, I have updated the documentation in this pull request.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.
  • I have reviewed at the Accessibility Practices for this feature, see: Aria Practices

Best practices

This repository uses conventional commit syntax for each commit message; note that the GitHub UI does not use this by default so be cautious when accepting suggested changes. Avoid the "Update branch" button on the pull request and opt instead for rebasing your branch against main.

Sorry, something went wrong.

Copy link

changeset-bot bot commented Mar 31, 2025

⚠️ No Changeset found

Latest commit: 03b4759

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link

github-actions bot commented Mar 31, 2025

Branch preview

Review the following VRT differences

When a visual regression test fails (or has previously failed while working on this branch), its results can be found in the following URLs:

If the changes are expected, update the current_golden_images_cache hash in the circleci config to accept the new images. Instructions are included in that file.
If the changes are unexpected, you can investigate the cause of the differences and update the code accordingly.

Copy link

Tachometer results

Currently, no packages are changed by this PR...

@rubencarvalho rubencarvalho force-pushed the ruben/overlay-performance branch 2 times, most recently from b2d28a0 to 50ff91c Compare April 12, 2025 07:34
@rubencarvalho rubencarvalho changed the title Ruben/overlay performance feat(overlay): replace native showModal() for performance optimization Apr 13, 2025
@rubencarvalho rubencarvalho force-pushed the ruben/overlay-performance branch from 59b702f to 4e2228c Compare April 14, 2025 12:36
@rubencarvalho rubencarvalho marked this pull request as ready for review April 14, 2025 16:04
@Rajdeepc Rajdeepc force-pushed the ruben/overlay-performance branch 4 times, most recently from 6ff9288 to 810eaa7 Compare April 23, 2025 07:19
@5t3ph 5t3ph self-requested a review April 24, 2025 14:26
Copy link
Contributor

@nikkimk nikkimk left a comment

Choose a reason for hiding this comment

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

With a fast follow ticket for the skipped flaky tests, LGTM

Copy link
Contributor

@caseyisonit caseyisonit left a comment

Choose a reason for hiding this comment

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

lgtm

@caseyisonit caseyisonit dismissed 5t3ph’s stale review April 28, 2025 16:07

she already confirmed the voiceover worked after cache fix. Nikki and I also confirmed this works so removing the changes requested review to unblock merging.

@caseyisonit caseyisonit merged commit f79cd52 into main Apr 28, 2025
24 checks passed
@caseyisonit caseyisonit deleted the ruben/overlay-performance branch April 28, 2025 16:07
castastrophe pushed a commit that referenced this pull request May 6, 2025
#5307)

* chore: add test page

* chore: add comment

* chore: add a fast page (no dom complexity)

* chore: update test page

* chore: update stuff

* chore: use popover as dialog wrapper

* chore: try focus trap

* chore: fix focus trap

* chore: add Escape event listener to dismiss modal

* chore: add AI JSDocs

* chore: fixing some focustrap behaviour

* chore: adding some comments

* chore: fix some styles

* chore: fix comment

* chore: check if is topmost overlay before closing

* chore: add aria modal

* chore: display block fine

* chore: update focusable slectors based on focus-trap

* chore: remove overlay dialog functionality

* chore: fix stuff

* chore: fix stuff

* chore: ensure all types are correct

* chore: remove Safari focus ring handling from HoverController and related tests

* chore: update aria-modal handling to include 'page' type overlays

* chore: add focus trapping tests for modal and page overlay types

* chore: debugging broken ci tests

* chore: try adding parallelism

* revert: remove parallelism from chrome

* chore: improved overlay test

* fix: test with waitUntill removing nextFrame

* chore: correct button reference in overlay trigger test

* chore: removed unnecessary lint updates

* chore: add proper stories

* chore: update overlay doc

* chore: skip some flaky tests in ci

* chore: update golden image hash

* fix(overlay): ensure picker opens correctly in a modal in safari

* chore: add missing package dependency

* chore: update golden image hash

* fix(overlay): ensure picker opens correctly in a modal in safari

* chore: update golden image hash

* chore: skip action group mouse test

---------

Co-authored-by: TarunAdobe <ttomar@adobe.com>
Co-authored-by: Rajdeep Chandra <rajdeepchandra@Rajdeeps-MacBook-Pro-2.local>
@nikkimk nikkimk mentioned this pull request Jun 4, 2025
15 tasks
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.

None yet

5 participants