Skip to content

Commit 3fe8278

Browse files
hoxyqAndyPengc12
authored andcommitted
fix[devtools/useModalDismissSignal]: use getRootNode for shadow root case support (facebook#28145)
In our custom implementation for handling modals dismiss signal, we use element's `ownerDocument` field, which expectedly doesn't work well with shadow root. Now using [`getRootNode`](https://developer.mozilla.org/en-US/docs/Web/API/Node/getRootNode) instead of `ownerDocument` to support shadow root case. Without this, if RDT Frontend is hosted inside the shadow root, the modal gets closed after any click, including on the buttons hosted by modal: https://github.com/facebook/react/blob/00d42ac3542179c55f936f395ede7abaeb5900a3/packages/react-devtools-shared/src/devtools/views/hooks.js#L228-L238 Test plan: - Modals work as expected for Chrome DevTools integration - Modals work as expected at every other surfaces: browser extension, electron wrapper for RN, inline version for web
1 parent 65ab16f commit 3fe8278

File tree

1 file changed

+14
-11
lines changed
  • packages/react-devtools-shared/src/devtools/views

1 file changed

+14
-11
lines changed

packages/react-devtools-shared/src/devtools/views/hooks.js

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -219,15 +219,18 @@ export function useModalDismissSignal(
219219
return () => {};
220220
}
221221

222-
const handleDocumentKeyDown = (event: any) => {
222+
const handleRootNodeKeyDown = (event: KeyboardEvent) => {
223223
if (event.key === 'Escape') {
224224
dismissCallback();
225225
}
226226
};
227227

228-
const handleDocumentClick = (event: any) => {
228+
const handleRootNodeClick: MouseEventHandler = event => {
229229
if (
230230
modalRef.current !== null &&
231+
/* $FlowExpectedError[incompatible-call] Instead of dealing with possibly multiple realms
232+
and multiple Node references to comply with Flow (e.g. checking with `event.target instanceof Node`)
233+
just delegate it to contains call */
231234
!modalRef.current.contains(event.target)
232235
) {
233236
event.stopPropagation();
@@ -237,7 +240,7 @@ export function useModalDismissSignal(
237240
}
238241
};
239242

240-
let ownerDocument = null;
243+
let modalRootNode = null;
241244

242245
// Delay until after the current call stack is empty,
243246
// in case this effect is being run while an event is currently bubbling.
@@ -248,12 +251,12 @@ export function useModalDismissSignal(
248251
// It's important to listen to the ownerDocument to support the browser extension.
249252
// Here we use portals to render individual tabs (e.g. Profiler),
250253
// and the root document might belong to a different window.
251-
const div = modalRef.current;
252-
if (div != null) {
253-
ownerDocument = div.ownerDocument;
254-
ownerDocument.addEventListener('keydown', handleDocumentKeyDown);
254+
const modalDOMElement = modalRef.current;
255+
if (modalDOMElement != null) {
256+
modalRootNode = modalDOMElement.getRootNode();
257+
modalRootNode.addEventListener('keydown', handleRootNodeKeyDown);
255258
if (dismissOnClickOutside) {
256-
ownerDocument.addEventListener('click', handleDocumentClick, true);
259+
modalRootNode.addEventListener('click', handleRootNodeClick, true);
257260
}
258261
}
259262
}, 0);
@@ -263,9 +266,9 @@ export function useModalDismissSignal(
263266
clearTimeout(timeoutID);
264267
}
265268

266-
if (ownerDocument !== null) {
267-
ownerDocument.removeEventListener('keydown', handleDocumentKeyDown);
268-
ownerDocument.removeEventListener('click', handleDocumentClick, true);
269+
if (modalRootNode !== null) {
270+
modalRootNode.removeEventListener('keydown', handleRootNodeKeyDown);
271+
modalRootNode.removeEventListener('click', handleRootNodeClick, true);
269272
}
270273
};
271274
}, [modalRef, dismissCallback, dismissOnClickOutside]);

0 commit comments

Comments
 (0)