Skip to content

Commit

Permalink
fix: [Inspector] drop Proxies, set selective Object.defineProperties …
Browse files Browse the repository at this point in the history
…traps

Proxies introduce a delay and differences in update graph behaviour which break the RTT parent/child updates on nodes itself, to avoid the issue only enable traps for inspector elements that we're actually interested in.

Fixes #429
  • Loading branch information
wouterlucas committed Nov 4, 2024
1 parent 9081f09 commit 74b6b7b
Showing 1 changed file with 67 additions and 44 deletions.
111 changes: 67 additions & 44 deletions src/main-api/Inspector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,14 @@ const gradientColorPropertyMap = [
'colorBr',
];

const knownProperties = new Set<string>([
...Object.keys(stylePropertyMap),
...Object.keys(domPropertyMap),
// ...gradientColorPropertyMap,
'src',
'parent',
]);

export class Inspector {
private root: HTMLElement | null = null;
private canvas: HTMLCanvasElement | null = null;
Expand Down Expand Up @@ -239,72 +247,87 @@ export class Inspector {

createNode(node: CoreNode): CoreNode {
const div = this.createDiv(node.id, node.props);

// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
(div as any).node = node;

// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
(node as any).div = div;

node.on('inViewport', () => {
div.setAttribute('state', 'inViewport');
});

node.on('inBounds', () => {
div.setAttribute('state', 'inBounds');
});

node.on('outOfBounds', () => {
div.setAttribute('state', 'outOfBounds');
});
node.on('inViewport', () => div.setAttribute('state', 'inViewport'));
node.on('inBounds', () => div.setAttribute('state', 'inBounds'));
node.on('outOfBounds', () => div.setAttribute('state', 'outOfBounds'));

// Monitor only relevant properties by trapping with selective assignment
return this.createProxy(node, div);
}

createTextNode(node: CoreNode): CoreTextNode {
const div = this.createDiv(node.id, node.props);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
(div as any).node = node;

// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
(node as any).div = div;

return this.createProxy(node, div) as CoreTextNode;
}

createProxy(
node: CoreNode | CoreTextNode,
div: HTMLElement,
): CoreNode | CoreTextNode {
return new Proxy(node, {
set: (target, property: keyof CoreNodeProps, value) => {
this.updateNodeProperty(div, property, value);
return Reflect.set(target, property, value);
},
get: (target, property: keyof CoreNode, receiver: any): any => {
if (property === 'destroy') {
this.destroyNode(target.id);
}
const updateNodePropertyWrapper = (
property: keyof CoreNodeProps,
value: any,
) => {
queueMicrotask(() => this.updateNodeProperty(div, property, value));
};

// Define traps for each property in knownProperties
knownProperties.forEach((property) => {
const originalProp = Object.getOwnPropertyDescriptor(node, property);
if (!originalProp) {
return;
}

if (property === 'animate') {
return (props: CoreNodeAnimateProps, settings: AnimationSettings) => {
const anim = target.animate(props, settings);

// Trap the animate start function so we can update the inspector accordingly
return new Proxy(anim, {
get: (target, property: keyof IAnimationController, receiver) => {
if (property === 'start') {
this.animateNode(div, props, settings);
}

return Reflect.get(target, property, receiver);
},
});
};
}
Object.defineProperties(node, {
[property]: {
get() {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return originalProp.get?.call(node);
},
set(value) {
originalProp.set?.call(node, value);
updateNodePropertyWrapper(property as keyof CoreNodeProps, value);
},
configurable: true,
enumerable: true,
},
});
});

return Reflect.get(target, property, receiver);
const originalDestroy = node.destroy;
Object.defineProperty(node, 'destroy', {
value: () => {
this.destroyNode(node.id);
originalDestroy.call(node);
},
});

// eslint-disable-next-line @typescript-eslint/unbound-method
const originalAnimate = node.animate;
Object.defineProperty(node, 'animate', {
value: (
props: CoreNodeAnimateProps,
settings: AnimationSettings,
): IAnimationController => {
const animationController = originalAnimate.call(node, props, settings);

return {
...animationController,
start: () => {
this.animateNode(div, props, settings);
return animationController.start();
},
};
},
});

return node;
}

destroyNode(id: number) {
Expand Down

0 comments on commit 74b6b7b

Please sign in to comment.