Skip to content

[DevTools] Use Popover API for TraceUpdates highlighting #32614

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 9 commits into from
May 7, 2025

Conversation

yongsk0066
Copy link
Contributor

@yongsk0066 yongsk0066 commented Mar 14, 2025

Summary

When using React DevTools to highlight component updates, the highlights would sometimes appear behind elements that use the browser's top-layer (such as <dialog> elements or components using the Popover API). This made it difficult to see which components were updating when they were inside or behind top-layer elements.

This PR fixes the issue by using the Popover API to ensure that highlighting appears on top of all content, including elements in the top-layer. The implementation maintains backward compatibility with browsers that don't support the Popover API.

How did you test this change?

I tested this change in the following ways:

  1. Manually tested in Chrome (which supports the Popover API) with:

    • Created a test application with React components inside <dialog> elements and custom elements using the Popover API
    • Verified that component highlighting appears above these elements when they update
    • Confirmed that highlighting displays correctly for nested components within top-layer elements
  2. Verified backward compatibility:

    • Tested in browsers without Popover API support to ensure fallback behavior works correctly
    • Confirmed that no errors occur and highlighting still functions as before
  3. Ran the React DevTools test suite:

    • All tests pass successfully
    • No regressions were introduced

demo-page
demo-repo

AS-IS

witout_toplayer.mov

TO-BE

with_toplayer.mov

  This change ensures that component highlighting in React DevTools appears above toplayer elements by using
   the Popover API. This fixes an issue where highlights would appear behind elements that use the browser's
   toplayer (such as dialogs, popovers, etc).

  The implementation gracefully falls back to the previous behavior in browsers that don't support the
  Popover API.
Comment on lines 49 to 52
try {
if (canvas.hasAttribute('popover') && !canvas.matches(':popover-open')) {
canvas.showPopover();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Are you gating it with try/catch because for feature checking?

We might bump the minimum required version for Chrome / other browsers here:
https://github.com/facebook/react/blob/main/packages/react-devtools-extensions/chrome/manifest.json#L7

So you won't need to worry about if its supported or not

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, that's right. I've handled it in case popover isn't supported. Would it be okay to raise the minimum version? If we do raise it, I plan to increase it to version 114 (https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/showPopover#browser_compatibility) which supports the popover API.

Copy link
Contributor

Choose a reason for hiding this comment

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

Please do it for Edge browser as well. I believe Firefox should already list version that is higher than needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've added Edge as well! Yes, Firefox requires 125 or higher, but it's already set to 128, so we're good.

@hoxyq
Copy link
Contributor

hoxyq commented Mar 17, 2025

Please see the failed Flow checks, overall the changes are seemed necessary, thanks for fixing this

  - Use manual popover mode to prevent closing with ESC key
  - Hide popover automatically when all trace updates expire
  - Remove focus styles and focus-related behaviors
  Added $FlowFixMe annotations to address Flow type errors with the Popover API:

  - prop-missing: Added annotations for Popover API methods (showPopover, hidePopover)
    that Flow doesn't yet recognize in HTMLCanvasElement
  - incompatible-use: Fixed type errors related to null checking and property access

  Flow doesn't have built-in types for the new Popover API yet, so we need to
  tell it to ignore these specific errors while maintaining type safety
  for the rest of the codebase.
@yongsk0066
Copy link
Contributor Author

@hoxyq Thanks for the review! i've fixed all the Flow errors and updated the implementation. I've added appropriate $FlowFixMe annotations since Flow doesn't recognize the Popover API yet. All tests are now passing!

  The TraceUpdates highlighting feature now uses the Popover API which was introduced in Chrome 114. This change ensures that users have the required browser version to fully support all DevTools features.

This is related to the recent changes for TraceUpdates highlighting which allows component highlights to appear above toplayer elements.
…over API

  Added descriptive comments to all $FlowFixMe annotations related to the Popover API,
  specifying that "Flow doesn't recognize Popover API" to make the code more maintainable and improve clarity for other developers.
@yongsk0066
Copy link
Contributor Author

Hi @hoxyq, just wanted to check in to see if there's anything else needed from my side.
Let me know if I can clarify or help further. Thanks!

By the way, I've also added Popover-related type definitions to flow-typed.
However, since the version of Flow used in the React repository is still behind,
we can't apply them here immediately. Perhaps we can in the future once it's updated.

Copy link
Contributor

@hoxyq hoxyq left a comment

Choose a reason for hiding this comment

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

Thanks for updating flow-typed as well!

Flow team does upgrades frequently, so it might be updated soon, but shouldn't block you from landing this change.

Overall looks good to me, see my comment, I will try to manually test this tomorrow.

if (canvas === null) {
initialize();
} else {
try {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as above, you don't need it to wrap it in try / catch, since we bumped minimum versions for the extension

Please also check other calls below

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for pointing this out! I've removed all try/catch blocks around Popover API calls.

…thod calls in the TraceUpdates canvas implementation.
Copy link
Contributor

@hoxyq hoxyq left a comment

Choose a reason for hiding this comment

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

Had another iteration on this, few points:

  1. Thanks for providing a really useful repro, could you copy it to react-devtools-shell package, so we can later use it for manual tests?
  2. I believe we would still fail to highlight updates if the dialog was opened while other components were highlighted. Imagine if you have an app, where something constantly re-renders in an interval that is less then React DevTools for clearing up highlights - drawWeb will never be called with empty nodeToData map, and we will re-open popover. What if we would re-open popover everytime drawWeb was called with non-empty nodeToData, how would that look?

Comment on lines 47 to 56
if (nodeToData.size === 0) {
if (canvas !== null) {
if (canvas.matches(':popover-open')) {
// $FlowFixMe[prop-missing]: Flow doesn't recognize Popover API
// $FlowFixMe[incompatible-use]: Flow doesn't recognize Popover API
canvas.hidePopover();
}
}
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this new code changes the behaviour of the drawWeb function.

Before this change, we would clear out canvas and draw nothing. Also, even if canvas was not initialized yet and drawWeb was called, we would initialize it as empty.

I think we should preserve the previous approach:

  1. Remove canvas.showPopover() from initialize.
  2. In the the end of drawWeb function call, add canvas.showPopover or canvas.hidePopover calls, based on nodeToData.size and popover state.

Something like this:

function drawWeb(nodeToData: Map<HostInstance, Data>) {
 <all code before your changes>
 ...
 
 if (nodeToData.size === 0 && canvas.matches(':popover-open')) {
   canvas.hidePopover();
 } else if (!canvas.matches(':popover-open') && nodeToData.size !== 0) {
   canvas.openPopover();
 }
 }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, I see. Totally agree that we should first finish all drawWeb logic and then decide at the end whether or not to promote it to the top-layer.
I think that approach better fits the responsibilities of each method. Thanks for clarifying this!
Considering your comments, I've tested two different approaches:

1. Hiding the canvas only when there's nothing to draw (the current method):

function drawWeb(nodeToData: Map<HostInstance, Data>) {
  // ... previous code
  if (canvas !== null) {
    if (nodeToData.size === 0 && canvas.matches(':popover-open')) {
      canvas.hidePopover();
    } else if (nodeToData.size > 0 && !canvas.matches(':popover-open')) {
      canvas.showPopover();
    }
  }
} // end of drawWeb

Result: This method avoids frequent updates to the top-layer.
However, as you mentioned in your comment, if another top-layer element appears while highlights still remain, the highlight canvas might fall behind the new element.

2. Re-promoting canvas to top-layer on every update call:

function drawWeb(nodeToData: Map<HostInstance, Data>) {
  // ... previous code
  if (canvas !== null) {
    if (nodeToData.size === 0 && canvas.matches(':popover-open')) {
      canvas.hidePopover();
      return;
    }
    if (canvas.matches(':popover-open')) {
      canvas.hidePopover();
    }
    canvas.showPopover();
  }
} // end of drawWeb

Result: Frequent changes to the top-layer occur, but the highlights consistently remain on top, ensuring they never get hidden by other elements.
I think method #2 seems preferable in terms of clearly maintaining highlight visibility too.
However, I couldn't find any mentions of performance issues in either the W3C review or blink-dev discussions regarding frequent top-layer changes. Unless highlights are triggered continuously at extremely high frequency over a prolonged period, there shouldn't be any noticeable performance impact.

  Modifies the TraceUpdates canvas implementation to consistently reposition
  the highlight overlay at the top of the top-layer stack with each update.
  To ensure highlights remain visible even when other top-layer elements
  (like dialogs) appear after the highlights are already active.
@yongsk0066
Copy link
Contributor Author

Thanks again for the review, @hoxyq !
I've added the test to react-devtools-shell as suggested.

@yongsk0066
Copy link
Contributor Author

How have you been, @hoxyq ?

It's been about a month, so I'm just leaving a comment to check if there's any additional feedback. I've been using this version for about a month without any major issues. No rush at all, just checking in. Thanks!

@hoxyq
Copy link
Contributor

hoxyq commented Apr 25, 2025

How have you been, @hoxyq ?

It's been about a month, so I'm just leaving a comment to check if there's any additional feedback. I've been using this version for about a month without any major issues. No rush at all, just checking in. Thanks!

Hey @yongsk0066, apologies for the delay. Don't worry, this will be in the next release of React DevTools, I was just prioritising other things. I have now finished other things and planning to get this tested next week.

Thank you for contributing to React DevTools.

Copy link
Contributor

@hoxyq hoxyq left a comment

Choose a reason for hiding this comment

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

Sorry for the delay, looks good. Thanks again!

#32703 is in my queue as well, I will also review and test it, probably worth rebasing it on main, once I merge this one.

@hoxyq hoxyq merged commit 53c9f81 into facebook:main May 7, 2025
241 checks passed
github-actions bot pushed a commit that referenced this pull request May 7, 2025
## Summary

When using React DevTools to highlight component updates, the highlights
would sometimes appear behind elements that use the browser's
[top-layer](https://developer.mozilla.org/en-US/docs/Glossary/Top_layer)
(such as `<dialog>` elements or components using the Popover API). This
made it difficult to see which components were updating when they were
inside or behind top-layer elements.

This PR fixes the issue by using the Popover API to ensure that
highlighting appears on top of all content, including elements in the
top-layer. The implementation maintains backward compatibility with
browsers that don't support the Popover API.

## How did you test this change?

I tested this change in the following ways:

1. Manually tested in Chrome (which supports the Popover API) with:
- Created a test application with React components inside `<dialog>`
elements and custom elements using the Popover API
- Verified that component highlighting appears above these elements when
they update
- Confirmed that highlighting displays correctly for nested components
within top-layer elements

2. Verified backward compatibility:
- Tested in browsers without Popover API support to ensure fallback
behavior works correctly
- Confirmed that no errors occur and highlighting still functions as
before

3. Ran the React DevTools test suite:
   - All tests pass successfully
   - No regressions were introduced

[demo-page](https://devtools-toplayer-demo.vercel.app/)
[demo-repo](https://github.com/yongsk0066/devtools-toplayer-demo)

### AS-IS

https://github.com/user-attachments/assets/dc2e1281-969f-4f61-82c3-480153916969

### TO-BE

https://github.com/user-attachments/assets/dd52ce35-816c-42f0-819b-0d5d0a8a21e5

DiffTrain build for [53c9f81](53c9f81)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants