From ecb403ff4cd59991cefde3e754cb76f6dd1d680a Mon Sep 17 00:00:00 2001 From: yongsk0066 Date: Fri, 14 Mar 2025 23:03:37 +0900 Subject: [PATCH 1/9] [DevTools] Use Popover API for TraceUpdates highlighting 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. --- .../src/backend/views/TraceUpdates/canvas.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/react-devtools-shared/src/backend/views/TraceUpdates/canvas.js b/packages/react-devtools-shared/src/backend/views/TraceUpdates/canvas.js index 3e01397546721..e7df2a5139950 100644 --- a/packages/react-devtools-shared/src/backend/views/TraceUpdates/canvas.js +++ b/packages/react-devtools-shared/src/backend/views/TraceUpdates/canvas.js @@ -45,6 +45,12 @@ function drawNative(nodeToData: Map, agent: Agent) { function drawWeb(nodeToData: Map) { if (canvas === null) { initialize(); + } else { + try { + if (canvas.hasAttribute('popover') && !canvas.matches(':popover-open')) { + canvas.showPopover(); + } + } catch (e) {} } const dpr = window.devicePixelRatio || 1; @@ -191,6 +197,12 @@ function destroyNative(agent: Agent) { function destroyWeb() { if (canvas !== null) { + try { + if (canvas.hasAttribute('popover')) { + canvas.hidePopover(); + } + } catch (e) {} + if (canvas.parentNode != null) { canvas.parentNode.removeChild(canvas); } @@ -204,6 +216,7 @@ export function destroy(agent: Agent): void { function initialize(): void { canvas = window.document.createElement('canvas'); + canvas.setAttribute('popover', 'auto'); canvas.style.cssText = ` xx-background-color: red; xx-opacity: 0.5; @@ -214,8 +227,13 @@ function initialize(): void { right: 0; top: 0; z-index: 1000000000; + background-color: transparent; `; const root = window.document.documentElement; root.insertBefore(canvas, root.firstChild); + + try { + canvas.showPopover(); + } catch (e) {} } From a83589c485b4d4badbe48443c28e6db08638c776 Mon Sep 17 00:00:00 2001 From: yongsk0066 Date: Mon, 17 Mar 2025 22:14:20 +0900 Subject: [PATCH 2/9] [DevTools] Improve TraceUpdates popover behavior - 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 --- .../src/backend/views/TraceUpdates/canvas.js | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/packages/react-devtools-shared/src/backend/views/TraceUpdates/canvas.js b/packages/react-devtools-shared/src/backend/views/TraceUpdates/canvas.js index e7df2a5139950..d09319e852753 100644 --- a/packages/react-devtools-shared/src/backend/views/TraceUpdates/canvas.js +++ b/packages/react-devtools-shared/src/backend/views/TraceUpdates/canvas.js @@ -43,11 +43,23 @@ function drawNative(nodeToData: Map, agent: Agent) { } function drawWeb(nodeToData: Map) { + // if there are no nodes to draw, detach from top layer + if (nodeToData.size === 0) { + if (canvas !== null) { + try { + if (canvas.matches(':popover-open')) { + canvas.hidePopover(); + } + } catch (e) {} + } + return; + } + if (canvas === null) { initialize(); } else { try { - if (canvas.hasAttribute('popover') && !canvas.matches(':popover-open')) { + if (!canvas.matches(':popover-open')) { canvas.showPopover(); } } catch (e) {} @@ -197,11 +209,7 @@ function destroyNative(agent: Agent) { function destroyWeb() { if (canvas !== null) { - try { - if (canvas.hasAttribute('popover')) { - canvas.hidePopover(); - } - } catch (e) {} + canvas.hidePopover(); if (canvas.parentNode != null) { canvas.parentNode.removeChild(canvas); @@ -216,7 +224,8 @@ export function destroy(agent: Agent): void { function initialize(): void { canvas = window.document.createElement('canvas'); - canvas.setAttribute('popover', 'auto'); + canvas.setAttribute('popover', 'manual'); + canvas.style.cssText = ` xx-background-color: red; xx-opacity: 0.5; @@ -226,8 +235,10 @@ function initialize(): void { position: fixed; right: 0; top: 0; - z-index: 1000000000; background-color: transparent; + outline: none !important; + box-shadow: none !important; + border: none !important; `; const root = window.document.documentElement; From 9215664dee108c614615bd4aa9c61e832ee5500d Mon Sep 17 00:00:00 2001 From: yongsk0066 Date: Mon, 17 Mar 2025 22:33:05 +0900 Subject: [PATCH 3/9] [DevTools] Fix Flow type errors for TraceUpdates popover implementation 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. --- .../src/backend/views/TraceUpdates/canvas.js | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/packages/react-devtools-shared/src/backend/views/TraceUpdates/canvas.js b/packages/react-devtools-shared/src/backend/views/TraceUpdates/canvas.js index d09319e852753..a0f1c62926ba8 100644 --- a/packages/react-devtools-shared/src/backend/views/TraceUpdates/canvas.js +++ b/packages/react-devtools-shared/src/backend/views/TraceUpdates/canvas.js @@ -48,6 +48,8 @@ function drawWeb(nodeToData: Map) { if (canvas !== null) { try { if (canvas.matches(':popover-open')) { + // $FlowFixMe[prop-missing] + // $FlowFixMe[incompatible-use] canvas.hidePopover(); } } catch (e) {} @@ -60,6 +62,8 @@ function drawWeb(nodeToData: Map) { } else { try { if (!canvas.matches(':popover-open')) { + // $FlowFixMe[prop-missing] + // $FlowFixMe[incompatible-use] canvas.showPopover(); } } catch (e) {} @@ -209,9 +213,15 @@ function destroyNative(agent: Agent) { function destroyWeb() { if (canvas !== null) { - canvas.hidePopover(); + try { + // $FlowFixMe[prop-missing] + // $FlowFixMe[incompatible-use] + canvas.hidePopover(); + } catch (e) {} + // $FlowFixMe[incompatible-use]: Flow doesn't recognize Popover API and loses canvas nullability tracking if (canvas.parentNode != null) { + // $FlowFixMe[incompatible-call]: Flow doesn't track that canvas is non-null here canvas.parentNode.removeChild(canvas); } canvas = null; @@ -224,8 +234,9 @@ export function destroy(agent: Agent): void { function initialize(): void { canvas = window.document.createElement('canvas'); - canvas.setAttribute('popover', 'manual'); + // canvas.setAttribute('popover', 'manual'); + // $FlowFixMe[incompatible-use] canvas.style.cssText = ` xx-background-color: red; xx-opacity: 0.5; @@ -236,15 +247,17 @@ function initialize(): void { right: 0; top: 0; background-color: transparent; - outline: none !important; - box-shadow: none !important; - border: none !important; + outline: none; + box-shadow: none; + border: none; `; const root = window.document.documentElement; root.insertBefore(canvas, root.firstChild); try { + // $FlowFixMe[prop-missing] + // $FlowFixMe[incompatible-use] canvas.showPopover(); } catch (e) {} } From f7eb1c6f138e21ca5bf2329a608691b8794cf0d0 Mon Sep 17 00:00:00 2001 From: yongsk0066 Date: Mon, 17 Mar 2025 23:22:07 +0900 Subject: [PATCH 4/9] [DevTools] Raise minimum Chrome version to 114 for Popover API support 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. --- packages/react-devtools-extensions/chrome/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-devtools-extensions/chrome/manifest.json b/packages/react-devtools-extensions/chrome/manifest.json index 5cf6397bc75ef..a773bdcaa25f9 100644 --- a/packages/react-devtools-extensions/chrome/manifest.json +++ b/packages/react-devtools-extensions/chrome/manifest.json @@ -4,7 +4,7 @@ "description": "Adds React debugging tools to the Chrome Developer Tools.", "version": "6.1.1", "version_name": "6.1.1", - "minimum_chrome_version": "102", + "minimum_chrome_version": "114", "icons": { "16": "icons/16-production.png", "32": "icons/32-production.png", From 938ad0e4d45f85da1cb7d707a24912da7cdbec1b Mon Sep 17 00:00:00 2001 From: yongsk0066 Date: Tue, 18 Mar 2025 07:20:15 +0900 Subject: [PATCH 5/9] [DevTools] Add explanatory comments to $FlowFixMe annotations for Popover 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. --- .../edge/manifest.json | 2 +- .../src/backend/views/TraceUpdates/canvas.js | 28 +++++++++---------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/packages/react-devtools-extensions/edge/manifest.json b/packages/react-devtools-extensions/edge/manifest.json index 512dd888f7d94..333f788e90e61 100644 --- a/packages/react-devtools-extensions/edge/manifest.json +++ b/packages/react-devtools-extensions/edge/manifest.json @@ -4,7 +4,7 @@ "description": "Adds React debugging tools to the Microsoft Edge Developer Tools.", "version": "6.1.1", "version_name": "6.1.1", - "minimum_chrome_version": "102", + "minimum_chrome_version": "114", "icons": { "16": "icons/16-production.png", "32": "icons/32-production.png", diff --git a/packages/react-devtools-shared/src/backend/views/TraceUpdates/canvas.js b/packages/react-devtools-shared/src/backend/views/TraceUpdates/canvas.js index a0f1c62926ba8..3f4a09bba9687 100644 --- a/packages/react-devtools-shared/src/backend/views/TraceUpdates/canvas.js +++ b/packages/react-devtools-shared/src/backend/views/TraceUpdates/canvas.js @@ -46,13 +46,11 @@ function drawWeb(nodeToData: Map) { // if there are no nodes to draw, detach from top layer if (nodeToData.size === 0) { if (canvas !== null) { - try { - if (canvas.matches(':popover-open')) { - // $FlowFixMe[prop-missing] - // $FlowFixMe[incompatible-use] - canvas.hidePopover(); - } - } catch (e) {} + 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; } @@ -62,8 +60,8 @@ function drawWeb(nodeToData: Map) { } else { try { if (!canvas.matches(':popover-open')) { - // $FlowFixMe[prop-missing] - // $FlowFixMe[incompatible-use] + // $FlowFixMe[prop-missing]: Flow doesn't recognize Popover API + // $FlowFixMe[incompatible-use]: Flow doesn't recognize Popover API canvas.showPopover(); } } catch (e) {} @@ -214,8 +212,8 @@ function destroyNative(agent: Agent) { function destroyWeb() { if (canvas !== null) { try { - // $FlowFixMe[prop-missing] - // $FlowFixMe[incompatible-use] + // $FlowFixMe[prop-missing]: Flow doesn't recognize Popover API + // $FlowFixMe[incompatible-use]: Flow doesn't recognize Popover API canvas.hidePopover(); } catch (e) {} @@ -234,9 +232,9 @@ export function destroy(agent: Agent): void { function initialize(): void { canvas = window.document.createElement('canvas'); - // canvas.setAttribute('popover', 'manual'); + canvas.setAttribute('popover', 'manual'); - // $FlowFixMe[incompatible-use] + // $FlowFixMe[incompatible-use]: Flow doesn't recognize Popover API canvas.style.cssText = ` xx-background-color: red; xx-opacity: 0.5; @@ -256,8 +254,8 @@ function initialize(): void { root.insertBefore(canvas, root.firstChild); try { - // $FlowFixMe[prop-missing] - // $FlowFixMe[incompatible-use] + // $FlowFixMe[prop-missing]: Flow doesn't recognize Popover API + // $FlowFixMe[incompatible-use]: Flow doesn't recognize Popover API canvas.showPopover(); } catch (e) {} } From 9c21b1e9cd563bb45f730db6d10b751ff97a1b36 Mon Sep 17 00:00:00 2001 From: yongsk0066 Date: Fri, 21 Mar 2025 07:43:10 +0900 Subject: [PATCH 6/9] [DevTools] Removed unnecessary try-catch blocks around Popover API method calls in the TraceUpdates canvas implementation. --- .../src/backend/views/TraceUpdates/canvas.js | 28 ++++++++----------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/packages/react-devtools-shared/src/backend/views/TraceUpdates/canvas.js b/packages/react-devtools-shared/src/backend/views/TraceUpdates/canvas.js index 3f4a09bba9687..2492b510fb3d2 100644 --- a/packages/react-devtools-shared/src/backend/views/TraceUpdates/canvas.js +++ b/packages/react-devtools-shared/src/backend/views/TraceUpdates/canvas.js @@ -58,13 +58,11 @@ function drawWeb(nodeToData: Map) { if (canvas === null) { initialize(); } else { - try { - if (!canvas.matches(':popover-open')) { - // $FlowFixMe[prop-missing]: Flow doesn't recognize Popover API - // $FlowFixMe[incompatible-use]: Flow doesn't recognize Popover API - canvas.showPopover(); - } - } catch (e) {} + if (!canvas.matches(':popover-open')) { + // $FlowFixMe[prop-missing]: Flow doesn't recognize Popover API + // $FlowFixMe[incompatible-use]: Flow doesn't recognize Popover API + canvas.showPopover(); + } } const dpr = window.devicePixelRatio || 1; @@ -211,11 +209,9 @@ function destroyNative(agent: Agent) { function destroyWeb() { if (canvas !== null) { - try { - // $FlowFixMe[prop-missing]: Flow doesn't recognize Popover API - // $FlowFixMe[incompatible-use]: Flow doesn't recognize Popover API - canvas.hidePopover(); - } catch (e) {} + // $FlowFixMe[prop-missing]: Flow doesn't recognize Popover API + // $FlowFixMe[incompatible-use]: Flow doesn't recognize Popover API + canvas.hidePopover(); // $FlowFixMe[incompatible-use]: Flow doesn't recognize Popover API and loses canvas nullability tracking if (canvas.parentNode != null) { @@ -253,9 +249,7 @@ function initialize(): void { const root = window.document.documentElement; root.insertBefore(canvas, root.firstChild); - try { - // $FlowFixMe[prop-missing]: Flow doesn't recognize Popover API - // $FlowFixMe[incompatible-use]: Flow doesn't recognize Popover API - canvas.showPopover(); - } catch (e) {} + // $FlowFixMe[prop-missing]: Flow doesn't recognize Popover API + // $FlowFixMe[incompatible-use]: Flow doesn't recognize Popover API + canvas.showPopover(); } From 6893d259abbaca73b633e4e67d13ed60f6ece8a9 Mon Sep 17 00:00:00 2001 From: yongsk0066 Date: Fri, 21 Mar 2025 22:47:52 +0900 Subject: [PATCH 7/9] [DevTools] Improve TraceUpdates popover layering behavior 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. --- .../src/backend/views/TraceUpdates/canvas.js | 41 ++++++++----------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/packages/react-devtools-shared/src/backend/views/TraceUpdates/canvas.js b/packages/react-devtools-shared/src/backend/views/TraceUpdates/canvas.js index 2492b510fb3d2..7705a250cee22 100644 --- a/packages/react-devtools-shared/src/backend/views/TraceUpdates/canvas.js +++ b/packages/react-devtools-shared/src/backend/views/TraceUpdates/canvas.js @@ -43,26 +43,8 @@ function drawNative(nodeToData: Map, agent: Agent) { } function drawWeb(nodeToData: Map) { - // if there are no nodes to draw, detach from top layer - 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; - } - if (canvas === null) { initialize(); - } else { - if (!canvas.matches(':popover-open')) { - // $FlowFixMe[prop-missing]: Flow doesn't recognize Popover API - // $FlowFixMe[incompatible-use]: Flow doesn't recognize Popover API - canvas.showPopover(); - } } const dpr = window.devicePixelRatio || 1; @@ -83,6 +65,17 @@ function drawWeb(nodeToData: Map) { drawGroupBorders(context, group); drawGroupLabel(context, group); }); + + if (canvas !== null) { + if (nodeToData.size === 0 && canvas.matches(':popover-open')) { + canvas.hidePopover(); + return; + } + if (canvas.matches(':popover-open')) { + canvas.hidePopover(); + } + canvas.showPopover(); + } } type GroupItem = { @@ -209,9 +202,11 @@ function destroyNative(agent: Agent) { function destroyWeb() { if (canvas !== null) { - // $FlowFixMe[prop-missing]: Flow doesn't recognize Popover API - // $FlowFixMe[incompatible-use]: Flow doesn't recognize Popover API - canvas.hidePopover(); + 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(); + } // $FlowFixMe[incompatible-use]: Flow doesn't recognize Popover API and loses canvas nullability tracking if (canvas.parentNode != null) { @@ -248,8 +243,4 @@ function initialize(): void { const root = window.document.documentElement; root.insertBefore(canvas, root.firstChild); - - // $FlowFixMe[prop-missing]: Flow doesn't recognize Popover API - // $FlowFixMe[incompatible-use]: Flow doesn't recognize Popover API - canvas.showPopover(); } From 862826ba0f6086ffa60ed0af66f63870bbdb4f08 Mon Sep 17 00:00:00 2001 From: yongsk0066 Date: Fri, 21 Mar 2025 23:12:56 +0900 Subject: [PATCH 8/9] [DevTools] Add TraceUpdatesTest component for manual testing --- .../src/app/TraceUpdatesTest/index.js | 87 +++++++++++++++++++ .../react-devtools-shell/src/app/index.js | 2 + 2 files changed, 89 insertions(+) create mode 100644 packages/react-devtools-shell/src/app/TraceUpdatesTest/index.js diff --git a/packages/react-devtools-shell/src/app/TraceUpdatesTest/index.js b/packages/react-devtools-shell/src/app/TraceUpdatesTest/index.js new file mode 100644 index 0000000000000..375514eda1be6 --- /dev/null +++ b/packages/react-devtools-shell/src/app/TraceUpdatesTest/index.js @@ -0,0 +1,87 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import * as React from 'react'; +import {useRef, useState} from 'react'; + +const Counter = () => { + const [count, setCount] = useState(0); + + return ( +
+

Count: {count}

+ +
+ ); +}; + +function DialogComponent() { + const dialogRef = useRef(null); + + const openDialog = () => { + if (dialogRef.current) { + dialogRef.current.showModal(); + } + }; + + const closeDialog = () => { + if (dialogRef.current) { + dialogRef.current.close(); + } + }; + + return ( +
+ + +

Dialog Content

+ + +
+
+ ); +} + +function RegularComponent() { + return ( +
+

Regular Component

+ +
+ ); +} + +export default function TraceUpdatesTest() { + return ( +
+

TraceUpdates Test

+ +
+

Standard Component

+ +
+ +
+

Dialog Component (top-layer element)

+ +
+ +
+

How to Test:

+
    +
  1. Open DevTools Components panel
  2. +
  3. Enable "Highlight updates when components render" in settings
  4. +
  5. Click increment buttons and observe highlights
  6. +
  7. Open the dialog and test increments there as well
  8. +
+
+
+ ); +} diff --git a/packages/react-devtools-shell/src/app/index.js b/packages/react-devtools-shell/src/app/index.js index 11f13bec47b22..8de2f949bb744 100644 --- a/packages/react-devtools-shell/src/app/index.js +++ b/packages/react-devtools-shell/src/app/index.js @@ -19,6 +19,7 @@ import Toggle from './Toggle'; import ErrorBoundaries from './ErrorBoundaries'; import PartiallyStrictApp from './PartiallyStrictApp'; import SuspenseTree from './SuspenseTree'; +import TraceUpdatesTest from './TraceUpdatesTest'; import {ignoreErrors, ignoreLogs, ignoreWarnings} from './console'; import './styles.css'; @@ -112,6 +113,7 @@ function mountTestApp() { mountApp(SuspenseTree); mountApp(DeeplyNestedComponents); mountApp(Iframe); + mountApp(TraceUpdatesTest); if (shouldRenderLegacy) { mountLegacyApp(PartiallyStrictApp); From c950b0d3f8b2cd9fc412485c325182acac853166 Mon Sep 17 00:00:00 2001 From: yongsk0066 Date: Fri, 21 Mar 2025 23:21:36 +0900 Subject: [PATCH 9/9] [DevTools] Add Flow annotations for TraceUpdates implementation --- .../src/backend/views/TraceUpdates/canvas.js | 7 +++++++ .../react-devtools-shell/src/app/TraceUpdatesTest/index.js | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/react-devtools-shared/src/backend/views/TraceUpdates/canvas.js b/packages/react-devtools-shared/src/backend/views/TraceUpdates/canvas.js index 7705a250cee22..b9e2cd9068135 100644 --- a/packages/react-devtools-shared/src/backend/views/TraceUpdates/canvas.js +++ b/packages/react-devtools-shared/src/backend/views/TraceUpdates/canvas.js @@ -68,12 +68,19 @@ function drawWeb(nodeToData: Map) { if (canvas !== null) { if (nodeToData.size === 0 && 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; } + // $FlowFixMe[incompatible-use]: Flow doesn't recognize Popover API 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(); } + // $FlowFixMe[prop-missing]: Flow doesn't recognize Popover API + // $FlowFixMe[incompatible-use]: Flow doesn't recognize Popover API canvas.showPopover(); } } diff --git a/packages/react-devtools-shell/src/app/TraceUpdatesTest/index.js b/packages/react-devtools-shell/src/app/TraceUpdatesTest/index.js index 375514eda1be6..dd847ad7aef45 100644 --- a/packages/react-devtools-shell/src/app/TraceUpdatesTest/index.js +++ b/packages/react-devtools-shell/src/app/TraceUpdatesTest/index.js @@ -57,7 +57,7 @@ function RegularComponent() { ); } -export default function TraceUpdatesTest() { +export default function TraceUpdatesTest(): React.Node { return (

TraceUpdates Test