Skip to content
Merged
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
6 changes: 6 additions & 0 deletions .changeset/calm-needles-rule.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@lynx-js/web-constants": patch
"@lynx-js/web-mainthread-apis": patch
---

feat: add main-thread API: __QuerySelector
Original file line number Diff line number Diff line change
Expand Up @@ -319,12 +319,17 @@ export type QueryComponentPAPI = (
}) => void,
) => null;
export type InvokeUIMethodPAPI = (
element: unknown,
element: HTMLElement,
method: string,
params: object,
callback: (result: InvokeCallbackRes) => void,
) => void;

export type QuerySelectorPAPI = (
element: HTMLElement,
selector: string,
) => unknown;

export interface ElementPAPIs {
__ElementFromBinary: ElementFromBinaryPAPI;

Expand Down Expand Up @@ -390,6 +395,7 @@ export interface ElementPAPIs {
options?: FlushElementTreeOptions,
) => void;
__InvokeUIMethod: InvokeUIMethodPAPI;
__QuerySelector: QuerySelectorPAPI;
}

export interface MainThreadGlobalThis extends ElementPAPIs {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ import {
type QueryComponentPAPI,
lynxEntryNameAttribute,
ErrorCode,
type QuerySelectorPAPI,
type InvokeUIMethodPAPI,
} from '@lynx-js/web-constants';
import { createMainThreadLynx } from './createMainThreadLynx.js';
import {
Expand Down Expand Up @@ -649,6 +651,76 @@ export function createMainThreadGlobalThis(
);
};

const __InvokeUIMethod: InvokeUIMethodPAPI = (
element,
method,
params,
callback,
) => {
try {
if (method === 'boundingClientRect') {
const rect = (element as HTMLElement).getBoundingClientRect();
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,
},
});
return;
}
if (typeof (element as any)[method] === 'function') {
const data = (element as any)[method](params);
callback({
code: ErrorCode.SUCCESS,
data,
});
return;
}
callback({
code: ErrorCode.METHOD_NOT_FOUND,
});
} catch (e) {
console.error(
`[lynx-web] invokeUIMethod: apply method failed with`,
e,
element,
);
callback({
code: ErrorCode.PARAM_INVALID,
});
}
};

const __QuerySelector: QuerySelectorPAPI = (
element,
selector,
) => {
if (!element) return null;
const el = (element as HTMLElement).querySelector(selector);
if (el) {
if (!(el as any).invoke) {
(el as any).invoke = (method: string, params: object) => {
return new Promise((resolve, reject) => {
__InvokeUIMethod(el as HTMLElement, method, params, (res) => {
if (res.code === ErrorCode.SUCCESS) {
resolve(res.data);
} else {
reject(res);
}
});
});
};
}
}
return el;
};

const __GetPageElement: GetPageElementPAPI = () => {
return pageElement;
};
Expand Down Expand Up @@ -817,46 +889,8 @@ export function createMainThreadGlobalThis(
_I18nResourceTranslation: callbacks._I18nResourceTranslation,
_AddEventListener: () => {},
renderPage: undefined,
__InvokeUIMethod: (element, method, params, callback) => {
try {
if (method === 'boundingClientRect') {
const rect = (element as HTMLElement).getBoundingClientRect();
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,
},
});
return;
}
if (typeof (element as any)[method] === 'function') {
const data = (element as any)[method](params);
callback({
code: ErrorCode.SUCCESS,
data,
});
return;
}
callback({
code: ErrorCode.METHOD_NOT_FOUND,
});
} catch (e) {
console.error(
`[lynx-web] invokeUIMethod: apply method failed with`,
e,
element,
);
callback({
code: ErrorCode.PARAM_INVALID,
});
}
},
__InvokeUIMethod,
__QuerySelector,
};
Object.assign(mtsRealm.globalWindow, mtsGlobalThis);
Object.defineProperty(mtsRealm.globalWindow, 'renderPage', {
Expand Down
16 changes: 16 additions & 0 deletions packages/web-platform/web-tests/tests/react.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// Copyright 2024 The Lynx Authors. All rights reserved.
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.
Expand Down Expand Up @@ -56,7 +56,7 @@
if (testname2) {
url += `&casename2=${testname2}`;
}
await page.goto(url, {

Check failure on line 59 in packages/web-platform/web-tests/tests/react.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright ALL_ON_UI-CSR (1/3) / check

[webkit] › tests/react.spec.ts:908:5 › reactlynx3 tests › basic › basic-event-target-trigger

2) [webkit] › tests/react.spec.ts:908:5 › reactlynx3 tests › basic › basic-event-target-trigger ── Error: page.goto: Test timeout of 30000ms exceeded. Call log: - navigating to "http://localhost:3080/?casename=basic-event-trigger", waiting until "load" 57 | url += `&casename2=${testname2}`; 58 | } > 59 | await page.goto(url, { | ^ 60 | waitUntil: 'load', 61 | }); 62 | await page.evaluate(() => document.fonts.ready); at goto (/__w/lynx-stack/lynx-stack/packages/web-platform/web-tests/tests/react.spec.ts:59:14) at /__w/lynx-stack/lynx-stack/packages/web-platform/web-tests/tests/react.spec.ts:909:13
waitUntil: 'load',
});
await page.evaluate(() => document.fonts.ready);
Expand Down Expand Up @@ -464,6 +464,22 @@
expect(scrollTopAfter).toBeGreaterThan(100);
});

test('basic-main-query-selector', async ({ page }, { title }) => {
await goto(page, title);
await wait(100);
const scrollView = page.locator('scroll-view');
const scrollTopBefore = await scrollView.evaluate((node) =>
node.scrollTop
);
expect(scrollTopBefore).toBe(0);
await page.locator('#tap-me').click();
await wait(3000);
const scrollTopAfter = await scrollView.evaluate((node) =>
node.scrollTop
);
expect(scrollTopAfter).toBeGreaterThan(100);
});

// lazy component
test(
'basic-lazy-component',
Expand Down Expand Up @@ -866,7 +882,7 @@
'background-color',
'rgb(0, 128, 0)',
); // green
expect(page.locator('#target2'), 'mouse up event captured').toHaveCSS(

Check failure on line 885 in packages/web-platform/web-tests/tests/react.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright ALL_ON_UI-SSR (2/3) / check

[chromium] › tests/react.spec.ts:872:5 › reactlynx3 tests › basic › basic-bindmouse

2) [chromium] › tests/react.spec.ts:872:5 › reactlynx3 tests › basic › basic-bindmouse ─────────── Error: mouse up event captured expect(locator).toHaveCSS(expected) failed Locator: locator('#target2') Expected: "rgb(0, 128, 0)" Received: "rgb(255, 192, 203)" Call log: - mouse up event captured with timeout 5000ms - waiting for locator('#target2') 2 × locator resolved to <x-view l-uid="5" l-part="5" id="target2" lynx-tag="view" l-p-comp-uid="1"></x-view> - unexpected value "rgb(255, 192, 203)" 883 | 'rgb(0, 128, 0)', 884 | ); // green > 885 | expect(page.locator('#target2'), 'mouse up event captured').toHaveCSS( | ^ 886 | 'background-color', 887 | 'rgb(0, 128, 0)', 888 | ); // green at /__w/lynx-stack/lynx-stack/packages/web-platform/web-tests/tests/react.spec.ts:885:69

Check failure on line 885 in packages/web-platform/web-tests/tests/react.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright ALL_ON_UI-CSR (2/3) / check

[chromium] › tests/react.spec.ts:872:5 › reactlynx3 tests › basic › basic-bindmouse

2) [chromium] › tests/react.spec.ts:872:5 › reactlynx3 tests › basic › basic-bindmouse ─────────── Error: mouse up event captured expect(locator).toHaveCSS(expected) failed Locator: locator('#target2') Expected: "rgb(0, 128, 0)" Received: "rgb(255, 192, 203)" Call log: - mouse up event captured with timeout 5000ms - waiting for locator('#target2') 2 × locator resolved to <x-view l-uid="5" id="target2" lynx-tag="view" l-p-comp-uid="1"></x-view> - unexpected value "rgb(255, 192, 203)" 883 | 'rgb(0, 128, 0)', 884 | ); // green > 885 | expect(page.locator('#target2'), 'mouse up event captured').toHaveCSS( | ^ 886 | 'background-color', 887 | 'rgb(0, 128, 0)', 888 | ); // green at /__w/lynx-stack/lynx-stack/packages/web-platform/web-tests/tests/react.spec.ts:885:69
'background-color',
'rgb(0, 128, 0)',
); // green
Expand Down Expand Up @@ -1094,7 +1110,7 @@
// @ts-expect-error
return Object.keys(globalThis.timing);
});
expect(timingKeys).toContain('create_lynx_start');

Check failure on line 1113 in packages/web-platform/web-tests/tests/react.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright ALL_ON_UI-CSR (1/3) / check

[webkit] › tests/react.spec.ts:1099:5 › reactlynx3 tests › apis › api-lynx-performance

3) [webkit] › tests/react.spec.ts:1099:5 › reactlynx3 tests › apis › api-lynx-performance ──────── Error: expect(received).toContain(expected) // indexOf Expected value: "create_lynx_start" Received array: ["load_core_start", "load_core_end", "hydrateParseSnapshotStart", "hydrateParseSnapshotEnd", "diffVdomStart", "diffVdomEnd", "packChangesStart", "packChangesEnd", "dispatch_start", "layout_start", …] 1111 | return Object.keys(globalThis.timing); 1112 | }); > 1113 | expect(timingKeys).toContain('create_lynx_start'); | ^ 1114 | expect(timingKeys).toContain('dispatch_start'); 1115 | expect(timingKeys).toContain('layout_start'); 1116 | expect(timingKeys).toContain('layout_end'); at /__w/lynx-stack/lynx-stack/packages/web-platform/web-tests/tests/react.spec.ts:1113:26
expect(timingKeys).toContain('dispatch_start');
expect(timingKeys).toContain('layout_start');
expect(timingKeys).toContain('layout_end');
Expand Down Expand Up @@ -1247,7 +1263,7 @@
document.querySelector('lynx-view')!.remove();
});
await wait(50);
expect(message).toContain('fin');

Check failure on line 1266 in packages/web-platform/web-tests/tests/react.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright ALL_ON_UI-CSR (1/3) / check

[webkit] › tests/react.spec.ts:1254:5 › reactlynx3 tests › apis › api-dispose

4) [webkit] › tests/react.spec.ts:1254:5 › reactlynx3 tests › apis › api-dispose ───────────────── Error: expect(received).toContain(expected) // indexOf Expected value: "fin" Received array: ["[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading enabled, Progress disabled, Overlay enabled.", "CUSTOM_NAPI_MODULES {color_environment: , color_methods: , event_method: }", "NYI: profileStart. This is an issue of lynx-core.", "NYI: isProfileRecording. This is an issue of lynx-core.", "NYI: profileEnd. This is an issue of lynx-core."] 1264 | }); 1265 | await wait(50); > 1266 | expect(message).toContain('fin'); | ^ 1267 | expect(page.workers().length).toStrictEqual(0); 1268 | }); 1269 | at /__w/lynx-stack/lynx-stack/packages/web-platform/web-tests/tests/react.spec.ts:1266:23
expect(page.workers().length).toStrictEqual(0);
});

Expand Down Expand Up @@ -1827,7 +1843,7 @@
});
await goto(page, title);
await wait(200);
!isSSR && expect(mts).toBe(true);

Check failure on line 1846 in packages/web-platform/web-tests/tests/react.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright ALL_ON_UI-CSR (1/3) / check

[webkit] › tests/react.spec.ts:1831:5 › reactlynx3 tests › apis › api-globalThis

5) [webkit] › tests/react.spec.ts:1831:5 › reactlynx3 tests › apis › api-globalThis ────────────── Retry #1 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(received).toBe(expected) // Object.is equality Expected: true Received: false 1844 | await goto(page, title); 1845 | await wait(200); > 1846 | !isSSR && expect(mts).toBe(true); | ^ 1847 | expect(bts).toBe(true); 1848 | }, 1849 | ); at /__w/lynx-stack/lynx-stack/packages/web-platform/web-tests/tests/react.spec.ts:1846:31

Check failure on line 1846 in packages/web-platform/web-tests/tests/react.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright ALL_ON_UI-CSR (1/3) / check

[webkit] › tests/react.spec.ts:1831:5 › reactlynx3 tests › apis › api-globalThis

5) [webkit] › tests/react.spec.ts:1831:5 › reactlynx3 tests › apis › api-globalThis ────────────── Error: expect(received).toBe(expected) // Object.is equality Expected: true Received: false 1844 | await goto(page, title); 1845 | await wait(200); > 1846 | !isSSR && expect(mts).toBe(true); | ^ 1847 | expect(bts).toBe(true); 1848 | }, 1849 | ); at /__w/lynx-stack/lynx-stack/packages/web-platform/web-tests/tests/react.spec.ts:1846:31
expect(bts).toBe(true);
},
);
Expand Down Expand Up @@ -2090,7 +2106,7 @@
const elementName = 'lynx-view';
test('basic-element-lynx-view-not-auto', async ({ page }, { title }) => {
await goto(page, title);
await page.evaluate(() => {

Check failure on line 2109 in packages/web-platform/web-tests/tests/react.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright ALL_ON_UI-CSR (1/3) / check

[webkit] › tests/react.spec.ts:2107:7 › reactlynx3 tests › elements › lynx-view › basic-element-lynx-view-not-auto

6) [webkit] › tests/react.spec.ts:2107:7 › reactlynx3 tests › elements › lynx-view › basic-element-lynx-view-not-auto Error: page.evaluate: TypeError: null is not an object (evaluating 'document.querySelector('lynx-view').setAttribute') 2107 | test('basic-element-lynx-view-not-auto', async ({ page }, { title }) => { 2108 | await goto(page, title); > 2109 | await page.evaluate(() => { | ^ 2110 | document.querySelector('lynx-view')!.setAttribute('width', '100vw'); 2111 | document.querySelector('lynx-view')!.setAttribute('height', '100vh'); 2112 | document.querySelector('lynx-view')!.setAttribute( at /__w/lynx-stack/lynx-stack/packages/web-platform/web-tests/tests/react.spec.ts:2109:20
document.querySelector('lynx-view')!.setAttribute('width', '100vw');
document.querySelector('lynx-view')!.setAttribute('height', '100vh');
document.querySelector('lynx-view')!.setAttribute(
Expand Down Expand Up @@ -3037,7 +3053,7 @@
await page.locator('.focus').click({ force: true });
await wait(100);
const result = await page.locator('.result').first().innerText();
expect(result).toBe('bindfocus');

Check failure on line 3056 in packages/web-platform/web-tests/tests/react.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright ALL_ON_UI-CSR (1/3) / check

[webkit] › tests/react.spec.ts:3047:7 › reactlynx3 tests › elements › x-input › basic-element-x-input-focus

7) [webkit] › tests/react.spec.ts:3047:7 › reactlynx3 tests › elements › x-input › basic-element-x-input-focus Error: expect(received).toBe(expected) // Object.is equality Expected: "bindfocus" Received: "" 3054 | await wait(100); 3055 | const result = await page.locator('.result').first().innerText(); > 3056 | expect(result).toBe('bindfocus'); | ^ 3057 | }, 3058 | ); 3059 | // input/focus test-case end at /__w/lynx-stack/lynx-stack/packages/web-platform/web-tests/tests/react.spec.ts:3056:26
},
);
// input/focus test-case end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { root } from '@lynx-js/react';

export const App = () => {
const handleTap = () => {
'main thread';
const scrollView = lynx.querySelector('#scroll-view');
scrollView?.invoke('autoScroll', {
rate: 120,
start: true,
});
};

return (
<view
style={{
width: '100%',
height: '100%',
padding: '10px',
display: 'linear',
marginTop: '20px',
}}
>
<view main-thread:bindtap={handleTap} id='tap-me'>
<text
style={{
fontSize: '20px',
height: '40px',
paddingLeft: '10px',
marginTop: '10px',
}}
>
Tap me to enable auto-scroll
</text>
</view>
<scroll-view
id='scroll-view'
scroll-orientation='vertical'
style={{ width: '100%', height: '300px', paddingLeft: '5px' }}
>
{Array.from({ length: 20 }).map((item, index) => (
<view
style={{
width: 'calc(100% - 10px)',
height: '100px',
backgroundColor: index % 2 === 0 ? 'red' : 'blue',
margin: '5px',
}}
>
<text>Item {index}</text>
</view>
))}
Comment thread
Sherry-hue marked this conversation as resolved.
</scroll-view>
</view>
);
};

root.render(<App />);
Loading