Skip to content

Commit

Permalink
fix(devtools): resolve errors once document is inspected (#2727)
Browse files Browse the repository at this point in the history
  • Loading branch information
bsunderhus committed Jan 12, 2024
1 parent e231bd3 commit 180d1ad
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 26 deletions.
5 changes: 5 additions & 0 deletions .changeset/thin-mayflies-deliver.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@floating-ui/devtools": patch
---

fix: devtools controller emits event once the selected element is removed
18 changes: 11 additions & 7 deletions extension/src/contexts/devtools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,7 @@ export const useChromeDevtoolsContextValue = (): DevtoolsContextValue => {
window.addEventListener(
'message',
(event) => {
if (
event.data === LOCAL_SERIALIZED_DATA_CHANGE
) {
if (event.data === LOCAL_SERIALIZED_DATA_CHANGE) {
// biome-ignore lint/style/noRestrictedGlobals:
chrome.runtime.sendMessage(event.data);
}
Expand All @@ -114,9 +112,10 @@ export const useChromeDevtoolsContextValue = (): DevtoolsContextValue => {
// biome-ignore lint/style/noRestrictedGlobals: @see top of file
theme: chrome.devtools.panels.themeName,
inspectDocument: React.useCallback(async () => {
await evalInspectedWindow<void>(`void inspect($0.ownerDocument);`).catch(
setError,
);
// inspect of ownerDocument works as a "reset" mechanism, it should be used to remove errors and to set the inspected window to the current document
await evalInspectedWindow<void>(
`void inspect($0.ownerDocument);`,
).finally(() => setError(undefined));
}, []),
dangerouslyEvalInspectedWindow: React.useCallback(
<Result,>(expression: string) =>
Expand All @@ -130,7 +129,12 @@ export const useChromeDevtoolsContextValue = (): DevtoolsContextValue => {
}, []),
inspectByReferenceId: React.useCallback(async (referenceId) => {
evalInspectedWindow<void>(
`void inspect($0.ownerDocument.defaultView['${CONTROLLER}'].selectedElement['${ELEMENT_METADATA}'].references.get('${referenceId}'));`,
`{
const selectedElement = $0.ownerDocument?.defaultView?.['${CONTROLLER}'].selectedElement;
if (selectedElement) {
void inspect(selectedElement['${ELEMENT_METADATA}'].references.get('${referenceId}'));
}
}`,
).catch(setError);
}, []),
// biome-ignore lint/style/noRestrictedGlobals: @see top of file
Expand Down
43 changes: 24 additions & 19 deletions packages/devtools/src/controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import {CONTROLLER, ELEMENT_METADATA} from 'extension/utils/constants';
import {
CONTROLLER,
ELEMENT_METADATA,
SERIALIZED_DATA_CHANGE,
} from 'extension/utils/constants';

import type {HTMLElementWithMetadata} from './types';
import {isHTMLElementWithMetadata} from './utils/isHTMLElement';
Expand All @@ -9,8 +13,21 @@ export type Controller = {
readonly selectedElement: HTMLElementWithMetadata | null;
};

export const createController = (): Controller => {
export const createController = (defaultView: Window): Controller => {
let selectedElement: HTMLElementWithMetadata | null = null;
const observer = new MutationObserver((mutations) => {
if (!selectedElement) {
return;
}
for (const mutation of mutations) {
if (
mutation.type === 'childList' &&
Array.from(mutation.removedNodes).includes(selectedElement)
) {
controller.withdraw();
}
}
});
const controller: Controller = {
get selectedElement() {
return selectedElement;
Expand All @@ -35,30 +52,18 @@ export const createController = (): Controller => {
withdraw: () => {
selectedElement = null;
observer.disconnect();
defaultView.postMessage(SERIALIZED_DATA_CHANGE);
},
};
const observer = new MutationObserver((mutations) => {
if (!selectedElement) {
return;
}
for (const mutation of mutations) {
if (
mutation.type === 'childList' &&
Array.from(mutation.removedNodes).includes(selectedElement)
) {
controller.withdraw();
}
}
});
return controller;
};

export const injectController = (targetDocument: Document) => {
if (!targetDocument.defaultView) {
export const injectController = ({defaultView}: Document) => {
if (!defaultView) {
return;
}
if (!targetDocument.defaultView[CONTROLLER]) {
targetDocument.defaultView[CONTROLLER] = createController();
if (!defaultView[CONTROLLER]) {
defaultView[CONTROLLER] = createController(defaultView);
}
};

Expand Down

0 comments on commit 180d1ad

Please sign in to comment.