Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,32 @@ import { ErrorCode } from '../../../constants.js';
import { invokeUIMethodEndpoint } from '../../endpoints.js';
import type { LynxViewInstance } from '../LynxViewInstance.js';

const methodAlias: Record<
string,
(element: Element, params: unknown) => unknown
> = {
'boundingClientRect': (element) => {
const rect = element.getBoundingClientRect();
return {
id: element.id,
width: rect.width,
height: rect.height,
left: rect.left,
right: rect.right,
top: rect.top,
bottom: rect.bottom,
};
},
};
function createMethodAlias(
lynxViewInstance: LynxViewInstance,
): Record<string, (element: Element, params: unknown) => unknown> {
return {
'boundingClientRect': (element) => {
const rect = element.getBoundingClientRect();
const containerRect = lynxViewInstance.rootDom.host
.getBoundingClientRect();
return {
id: element.id,
width: rect.width,
height: rect.height,
left: rect.left - containerRect.left,
right: rect.right - containerRect.left,
top: rect.top - containerRect.top,
bottom: rect.bottom - containerRect.top,
};
},
};
}

export function registerInvokeUIMethodHandler(
rpc: Rpc,
lynxViewInstance: LynxViewInstance,
) {
const methodAlias = createMethodAlias(lynxViewInstance);
rpc.registerHandler(
invokeUIMethodEndpoint,
(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,10 @@ export class WASMJSBinding implements RustMainthreadContextBinding {
| DecoratedHTMLElement
| null;
}
const eventObject = createCrossThreadEvent(event);
const eventObject = createCrossThreadEvent(
event,
() => this.lynxViewInstance.rootDom.host.getBoundingClientRect(),
);
this.wasmContext?.common_event_handler(
eventObject,
bubblePath.slice(0, bubblePathLength),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ function toCloneableObject(obj: any): CloneableObject {

export function createCrossThreadEvent(
domEvent: MinimalRawEventObject,
getContainerRect?: () => DOMRect,
): LynxCrossThreadEvent {
Comment on lines 26 to 29
const type = domEvent.type;
const params: Cloneable = {};
Expand All @@ -48,35 +49,52 @@ export function createCrossThreadEvent(
const touch = [...touchEvent.touches as unknown as Touch[]];
const targetTouches = [...touchEvent.targetTouches as unknown as Touch[]];
const changedTouches = [...touchEvent.changedTouches as unknown as Touch[]];
const adjustTouch = (t: CloneableObject): CloneableObject => {
if (getContainerRect) {
const rect = getContainerRect();
return {
...t,
clientX: (t['clientX'] as number) - rect.left,
clientY: (t['clientY'] as number) - rect.top,
pageX: (t['pageX'] as number) - rect.left,
pageY: (t['pageY'] as number) - rect.top,
Comment on lines +57 to +60
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Touch pageX/pageY adjustment is scroll-sensitive and can be wrong.

Touch.pageX/pageY are document-based. Subtracting only rect.left/top (viewport-based) can miscompute coordinates when the page is scrolled. Derive adjusted pageX/pageY from adjusted clientX/clientY (or subtract scroll as well).

Proposed fix
-    const adjustTouch = (t: CloneableObject): CloneableObject => {
-      if (getContainerRect) {
-        const rect = getContainerRect();
-        return {
-          ...t,
-          clientX: (t['clientX'] as number) - rect.left,
-          clientY: (t['clientY'] as number) - rect.top,
-          pageX: (t['pageX'] as number) - rect.left,
-          pageY: (t['pageY'] as number) - rect.top,
-        };
-      }
-      return t;
-    };
+    const rect = getContainerRect?.();
+    const offsetX = rect?.left ?? 0;
+    const offsetY = rect?.top ?? 0;
+    const adjustTouch = (t: CloneableObject): CloneableObject => {
+      const cx = (t['clientX'] as number) - offsetX;
+      const cy = (t['clientY'] as number) - offsetY;
+      return {
+        ...t,
+        clientX: cx,
+        clientY: cy,
+        pageX: cx,
+        pageY: cy,
+      };
+    };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
clientX: (t['clientX'] as number) - rect.left,
clientY: (t['clientY'] as number) - rect.top,
pageX: (t['pageX'] as number) - rect.left,
pageY: (t['pageY'] as number) - rect.top,
const rect = getContainerRect?.();
const offsetX = rect?.left ?? 0;
const offsetY = rect?.top ?? 0;
const adjustTouch = (t: CloneableObject): CloneableObject => {
const cx = (t['clientX'] as number) - offsetX;
const cy = (t['clientY'] as number) - offsetY;
return {
...t,
clientX: cx,
clientY: cy,
pageX: cx,
pageY: cy,
};
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/web-platform/web-core-wasm/ts/client/mainthread/elementAPIs/createCrossThreadEvent.ts`
around lines 57 - 60, The touch pageX/pageY adjustment is wrong because
pageX/pageY are document (scroll) coordinates but the code only subtracts
rect.left/top (viewport), causing errors when scrolled; in
createCrossThreadEvent derive adjusted pageX/pageY from the adjusted client
coordinates instead of mirroring t.pageX/t.pageY: compute adjustedClientX =
(t['clientX'] as number) - rect.left and adjustedClientY = (t['clientY'] as
number) - rect.top (these are already used for clientX/clientY) and then set
pageX = adjustedClientX + (window.scrollX || 0) and pageY = adjustedClientY +
(window.scrollY || 0) so page coordinates account for scroll. Use the same local
names (t, rect, clientX/clientY/pageX/pageY) in the createCrossThreadEvent
function to locate and replace the existing pageX/pageY lines.

};
}
return t;
};
Object.assign(otherProperties, {
touches: isTrusted ? touch.map(toCloneableObject) : touch,
touches: isTrusted
? touch.map(toCloneableObject).map(adjustTouch)
: touch,
targetTouches: isTrusted
? targetTouches.map(
toCloneableObject,
)
? targetTouches.map(toCloneableObject).map(adjustTouch)
: targetTouches,
changedTouches: isTrusted
? changedTouches.map(
toCloneableObject,
)
? changedTouches.map(toCloneableObject).map(adjustTouch)
: changedTouches,
});
} else if (type.startsWith('mouse')) {
const mouseEvent = domEvent as MouseEvent;
const rect = getContainerRect?.();
const offsetX = rect?.left ?? 0;
const offsetY = rect?.top ?? 0;
Object.assign(otherProperties, {
button: mouseEvent.button,
buttons: mouseEvent.buttons,
x: mouseEvent.x,
y: mouseEvent.y,
pageX: mouseEvent.pageX,
pageY: mouseEvent.pageY,
clientX: mouseEvent.clientX,
clientY: mouseEvent.clientY,
x: mouseEvent.clientX - offsetX,
y: mouseEvent.clientY - offsetY,
pageX: mouseEvent.clientX - offsetX,
pageY: mouseEvent.clientY - offsetY,
clientX: mouseEvent.clientX - offsetX,
clientY: mouseEvent.clientY - offsetY,
});
} else if (type === 'click') {
const rect = getContainerRect?.();
const offsetX = rect?.left ?? 0;
const offsetY = rect?.top ?? 0;
detail = {
x: (domEvent as MouseEvent).x,
y: (domEvent as MouseEvent).y,
x: (domEvent as MouseEvent).clientX - offsetX,
y: (domEvent as MouseEvent).clientY - offsetY,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,9 @@ export function createMainThreadGlobalThis(
mtsRealm,
document,
} = config;
const getContainerRect = (): DOMRect =>
((rootDom as ShadowRoot).host ?? rootDom as Element)
.getBoundingClientRect();
const { elementTemplate, lepusCode } = lynxTemplate;
const lynxUniqueIdToElement: WeakRef<HTMLElement>[] =
ssrHydrateInfo?.lynxUniqueIdToElement ?? [];
Expand Down Expand Up @@ -222,6 +225,7 @@ export function createMainThreadGlobalThis(
const crossThreadEvent = createCrossThreadEvent(
event as MinimalRawEventObject,
lynxEventName,
getContainerRect,
);
if (typeof hname === 'string') {
const parentComponentUniqueId = Number(
Expand Down Expand Up @@ -720,16 +724,17 @@ export function createMainThreadGlobalThis(
try {
if (method === 'boundingClientRect') {
const rect = (element as HTMLElement).getBoundingClientRect();
const containerRect = getContainerRect();
callback({
code: ErrorCode.SUCCESS,
data: {
id: (element as HTMLElement).id,
width: rect.width,
height: rect.height,
left: rect.left,
right: rect.right,
top: rect.top,
bottom: rect.bottom,
left: rect.left - containerRect.left,
right: rect.right - containerRect.left,
top: rect.top - containerRect.top,
bottom: rect.bottom - containerRect.top,
},
});
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ function toCloneableObject(obj: any): CloneableObject {
export function createCrossThreadEvent(
domEvent: MinimalRawEventObject,
eventName: string,
getContainerRect?: () => DOMRect,
): LynxCrossThreadEvent {
const targetElement = domEvent.target as HTMLElement;
const currentTargetElement = (domEvent
Expand Down Expand Up @@ -55,35 +56,52 @@ export function createCrossThreadEvent(
const touch = [...touchEvent.touches as unknown as Touch[]];
const targetTouches = [...touchEvent.targetTouches as unknown as Touch[]];
const changedTouches = [...touchEvent.changedTouches as unknown as Touch[]];
const adjustTouch = (t: CloneableObject): CloneableObject => {
if (getContainerRect) {
const rect = getContainerRect();
return {
...t,
clientX: (t['clientX'] as number) - rect.left,
clientY: (t['clientY'] as number) - rect.top,
pageX: (t['pageX'] as number) - rect.left,
pageY: (t['pageY'] as number) - rect.top,
};
}
return t;
};
Object.assign(otherProperties, {
touches: isTrusted ? touch.map(toCloneableObject) : touch,
touches: isTrusted
? touch.map(toCloneableObject).map(adjustTouch)
: touch,
targetTouches: isTrusted
? targetTouches.map(
toCloneableObject,
)
? targetTouches.map(toCloneableObject).map(adjustTouch)
: targetTouches,
changedTouches: isTrusted
? changedTouches.map(
toCloneableObject,
)
? changedTouches.map(toCloneableObject).map(adjustTouch)
: changedTouches,
});
} else if (type.startsWith('mouse')) {
const mouseEvent = domEvent as MouseEvent;
const rect = getContainerRect?.();
const offsetX = rect?.left ?? 0;
const offsetY = rect?.top ?? 0;
Object.assign(otherProperties, {
button: mouseEvent.button,
buttons: mouseEvent.buttons,
x: mouseEvent.x,
y: mouseEvent.y,
pageX: mouseEvent.pageX,
pageY: mouseEvent.pageY,
clientX: mouseEvent.clientX,
clientY: mouseEvent.clientY,
x: mouseEvent.clientX - offsetX,
y: mouseEvent.clientY - offsetY,
pageX: mouseEvent.clientX - offsetX,
pageY: mouseEvent.clientY - offsetY,
clientX: mouseEvent.clientX - offsetX,
clientY: mouseEvent.clientY - offsetY,
});
} else if (type === 'click') {
const rect = getContainerRect?.();
const offsetX = rect?.left ?? 0;
const offsetY = rect?.top ?? 0;
detail = {
x: (domEvent as MouseEvent).x,
y: (domEvent as MouseEvent).y,
x: (domEvent as MouseEvent).clientX - offsetX,
y: (domEvent as MouseEvent).clientY - offsetY,
};
}
const currentTargetDatasetString = currentTargetElement?.getAttribute(
Expand Down
Loading