diff --git a/.changeset/brave-ants-report.md b/.changeset/brave-ants-report.md new file mode 100644 index 0000000000..78a3ae1929 --- /dev/null +++ b/.changeset/brave-ants-report.md @@ -0,0 +1,5 @@ +--- +"@lynx-js/offscreen-document": patch +--- + +feat: support touch events diff --git a/.changeset/empty-worms-buy.md b/.changeset/empty-worms-buy.md new file mode 100644 index 0000000000..e927bc75a5 --- /dev/null +++ b/.changeset/empty-worms-buy.md @@ -0,0 +1,15 @@ +--- +"@lynx-js/web-mainthread-apis": patch +"@lynx-js/web-worker-runtime": patch +"@lynx-js/web-constants": patch +"@lynx-js/web-core": patch +--- + +feat: support touch events for MTS + +now we support + +- main-thread:bindtouchstart +- main-thread:bindtouchend +- main-thread:bindtouchmove +- main-thread:bindtouchcancel diff --git a/.changeset/loud-mangos-enjoy.md b/.changeset/loud-mangos-enjoy.md new file mode 100644 index 0000000000..3c7d2cc2d8 --- /dev/null +++ b/.changeset/loud-mangos-enjoy.md @@ -0,0 +1,5 @@ +--- +"@lynx-js/web-core": patch +--- + +feat: add SystemInfo.screenWidth and SystemInfo.screenHeight diff --git a/.cspell/lynx.txt b/.cspell/lynx.txt index 4c037824af..28094453b8 100644 --- a/.cspell/lynx.txt +++ b/.cspell/lynx.txt @@ -6,6 +6,9 @@ bindinput bindload bindtap bindtapuser +bindtouchstart +bindtouchend +bindtouchcancel bindtouchmove catchlongpress catchtap diff --git a/packages/web-platform/offscreen-document/src/main/initOffscreenDocument.ts b/packages/web-platform/offscreen-document/src/main/initOffscreenDocument.ts index 3300be9d20..a1446e5d9c 100644 --- a/packages/web-platform/offscreen-document/src/main/initOffscreenDocument.ts +++ b/packages/web-platform/offscreen-document/src/main/initOffscreenDocument.ts @@ -10,7 +10,6 @@ import { function emptyHandler() { // no-op } - const otherPropertyNames = [ 'detail', 'keyCode', @@ -19,7 +18,41 @@ const otherPropertyNames = [ 'propertyName', 'pseudoElement', 'animationName', + 'touches', + 'targetTouches', + 'changedTouches', ]; +const blockList = new Set([ + 'isTrusted', + 'target', + 'currentTarget', + 'type', + 'bubbles', + 'window', + 'self', + 'view', + 'srcElement', + 'eventPhase', +]); + +function transferToCloneable(value: any): any { + if ( + typeof value === 'string' || typeof value === 'number' + || typeof value === 'boolean' || value === null || value === undefined + ) { + return value; + } else if (value[Symbol.iterator]) { + return [...value].map(transferToCloneable); + } else if (typeof value === 'object' && !(value instanceof EventTarget)) { + const obj: Record = {}; + for (const key in value) { + if (!blockList.has(key)) { + obj[key] = transferToCloneable(value[key]); + } + } + return obj; + } +} export function initOffscreenDocument(options: { shadowRoot: ShadowRoot; @@ -64,7 +97,8 @@ export function initOffscreenDocument(options: { const otherProperties: Record = {}; for (const propertyName of otherPropertyNames) { if (propertyName in ev) { - otherProperties[propertyName] = (ev as any)[propertyName]; + // @ts-expect-error + otherProperties[propertyName] = transferToCloneable(ev[propertyName]); } } onEvent(eventType, targetUniqueId, ev.bubbles, otherProperties); diff --git a/packages/web-platform/web-constants/src/types/Cloneable.ts b/packages/web-platform/web-constants/src/types/Cloneable.ts index 3f06bcb3aa..62931e1e51 100644 --- a/packages/web-platform/web-constants/src/types/Cloneable.ts +++ b/packages/web-platform/web-constants/src/types/Cloneable.ts @@ -1,12 +1,13 @@ // Copyright 2023 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. -export type Cloneable = +export type Cloneable = | T | Record | T[]; -export type CloneableObject = Record< - string, - T ->; +export type CloneableObject = + Record< + string, + T | T[] + >; diff --git a/packages/web-platform/web-constants/src/types/PageConfig.ts b/packages/web-platform/web-constants/src/types/PageConfig.ts index 47559b345c..07f0b95752 100644 --- a/packages/web-platform/web-constants/src/types/PageConfig.ts +++ b/packages/web-platform/web-constants/src/types/PageConfig.ts @@ -10,4 +10,6 @@ export interface PageConfig { export interface BrowserConfig { pixelRatio: number; + pixelWidth: number; + pixelHeight: number; } diff --git a/packages/web-platform/web-core/src/apis/createLynxView.ts b/packages/web-platform/web-core/src/apis/createLynxView.ts index 468aa2f862..dd76943b4c 100644 --- a/packages/web-platform/web-core/src/apis/createLynxView.ts +++ b/packages/web-platform/web-core/src/apis/createLynxView.ts @@ -14,6 +14,9 @@ import { type StartUIThreadCallbacks, } from '../uiThread/startUIThread.js'; import type { RpcCallType } from '@lynx-js/web-worker-rpc'; +const pixelRatio = window.devicePixelRatio; +const screenWidth = window.screen.availWidth * pixelRatio; +const screenHeight = window.screen.availHeight * pixelRatio; export interface LynxViewConfigs { templateUrl: string; @@ -61,6 +64,8 @@ export function createLynxView(configs: LynxViewConfigs): LynxView { napiModulesMap, browserConfig: { pixelRatio: window.devicePixelRatio, + pixelWidth: screenWidth, + pixelHeight: screenHeight, }, }, shadowRoot, diff --git a/packages/web-platform/web-mainthread-apis/src/MainThreadRuntime.ts b/packages/web-platform/web-mainthread-apis/src/MainThreadRuntime.ts index efd9e0ef6e..936637ffba 100644 --- a/packages/web-platform/web-mainthread-apis/src/MainThreadRuntime.ts +++ b/packages/web-platform/web-mainthread-apis/src/MainThreadRuntime.ts @@ -147,7 +147,7 @@ export class MainThreadRuntime { this.__OnLifecycleEvent = this.config.callbacks.__OnLifecycleEvent; this.SystemInfo = { ...systemInfo, - pixelRatio: config.browserConfig.pixelRatio, + ...config.browserConfig, }; /** * Start the exposure service diff --git a/packages/web-platform/web-mainthread-apis/src/utils/createCrossThreadEvent.ts b/packages/web-platform/web-mainthread-apis/src/utils/createCrossThreadEvent.ts index 122d9a9659..282cb5041a 100644 --- a/packages/web-platform/web-mainthread-apis/src/utils/createCrossThreadEvent.ts +++ b/packages/web-platform/web-mainthread-apis/src/utils/createCrossThreadEvent.ts @@ -1,12 +1,30 @@ // Copyright 2023 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. -import type { Cloneable, LynxCrossThreadEvent } from '@lynx-js/web-constants'; +import type { + Cloneable, + CloneableObject, + LynxCrossThreadEvent, +} from '@lynx-js/web-constants'; import { elementToRuntimeInfoMap, type MainThreadRuntime, } from '../MainThreadRuntime.js'; +function toCloneableObject(obj: any): CloneableObject { + const cloneableObj: CloneableObject = {}; + for (const key in obj) { + const value = obj[key]; + if ( + typeof value === 'boolean' || typeof value === 'number' + || typeof value === 'string' || value === null + ) { + cloneableObj[key] = value; + } + } + return cloneableObj; +} + export function createCrossThreadEvent( runtime: MainThreadRuntime, domEvent: Event, @@ -17,6 +35,8 @@ export function createCrossThreadEvent( .currentTarget! as HTMLElement; const type = domEvent.type; const params: Cloneable = {}; + const isTrusted = domEvent.isTrusted; + const otherProperties: CloneableObject = {}; if (type.match(/^transition/)) { Object.assign(params, { 'animation_type': 'keyframe-animation', @@ -29,6 +49,24 @@ export function createCrossThreadEvent( 'animation_name': (domEvent as AnimationEvent).animationName, new_animator: true, // we support the new_animator only }); + } else if (type.startsWith('touch')) { + const touchEvent = domEvent as TouchEvent; + const touch = [...touchEvent.touches as unknown as Touch[]]; + const targetTouches = [...touchEvent.targetTouches as unknown as Touch[]]; + const changedTouches = [...touchEvent.changedTouches as unknown as Touch[]]; + Object.assign(otherProperties, { + touches: isTrusted ? touch.map(toCloneableObject) : touch, + targetTouches: isTrusted + ? targetTouches.map( + toCloneableObject, + ) + : targetTouches, + changedTouches: isTrusted + ? changedTouches.map( + toCloneableObject, + ) + : changedTouches, + }); } const targetElementRuntimeInfo = runtime[elementToRuntimeInfoMap].get( targetElement, @@ -54,5 +92,6 @@ export function createCrossThreadEvent( // @ts-expect-error detail: domEvent.detail ?? {}, params, + ...otherProperties, }; } diff --git a/packages/web-platform/web-tests/tests/react.spec.ts b/packages/web-platform/web-tests/tests/react.spec.ts index 0a3bb4ce3c..1bcf3b78aa 100644 --- a/packages/web-platform/web-tests/tests/react.spec.ts +++ b/packages/web-platform/web-tests/tests/react.spec.ts @@ -294,6 +294,35 @@ test.describe('reactlynx3 tests', () => { await wait(100); expect(eventHandlerTriggered).toBe(true); }); + + test( + 'basic-mts-bindtouchstart', + async ({ page, browserName, context }, { title }) => { + test.skip(browserName !== 'chromium', 'not support CDPsession'); + await goto(page, title); + await wait(300); + const cdpSession = await context.newCDPSession(page); + await swipe(cdpSession, { + x: 20, + y: 20, + xDistance: 10, + yDistance: 0, + }); + expect(page.locator('#target1'), 'has touches').toHaveCSS( + 'background-color', + 'rgb(0, 128, 0)', + ); // green + expect(page.locator('#target2'), 'has target touches').toHaveCSS( + 'background-color', + 'rgb(0, 128, 0)', + ); // green + expect(page.locator('#target3'), 'has changed touches').toHaveCSS( + 'background-color', + 'rgb(0, 128, 0)', + ); // green + }, + ); + test( 'basic-mts-bindtap-change-element-background', async ({ page }, { title }) => { @@ -458,6 +487,16 @@ test.describe('reactlynx3 tests', () => { }, ); + test( + 'api-SystemInfo-height-width', + async ({ page }, { title }) => { + await goto(page, title); + await wait(200); + const target = page.locator('#target'); + await expect(target).toHaveCSS('background-color', 'rgb(0, 128, 0)'); // green + }, + ); + test('api-initdata', async ({ page }, { title }) => { await goto(page, title); await wait(100); diff --git a/packages/web-platform/web-tests/tests/react/api-SystemInfo-height-width/index.jsx b/packages/web-platform/web-tests/tests/react/api-SystemInfo-height-width/index.jsx new file mode 100644 index 0000000000..ceb32e719e --- /dev/null +++ b/packages/web-platform/web-tests/tests/react/api-SystemInfo-height-width/index.jsx @@ -0,0 +1,28 @@ +// Copyright 2023 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. +import { root, useEffect, useState } from '@lynx-js/react'; + +function App() { + const [color, setColor] = useState('pink'); + useEffect(() => { + if ( + typeof SystemInfo.pixelHeight === 'number' + && typeof SystemInfo.pixelWidth === 'number' + ) { + setColor('green'); + } + }, []); + return ( + + ); +} + +root.render(); diff --git a/packages/web-platform/web-tests/tests/react/basic-mts-bindtouchstart/index.jsx b/packages/web-platform/web-tests/tests/react/basic-mts-bindtouchstart/index.jsx new file mode 100644 index 0000000000..4009819fe1 --- /dev/null +++ b/packages/web-platform/web-tests/tests/react/basic-mts-bindtouchstart/index.jsx @@ -0,0 +1,52 @@ +// Copyright 2023 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. +import { useState, root, runOnBackground } from '@lynx-js/react'; +function App() { + const [hasTouch, setHasTouch] = useState(false); + const [hasTargetTouches, setHasTargetTouches] = useState(false); + const [hasChangedTouches, setHasChangedTouches] = useState(false); + return ( + { + 'main thread'; + if (ev.touches[0]) { + runOnBackground(setHasTouch)(true); + } + if (ev.targetTouches[0]) { + runOnBackground(setHasTargetTouches)(true); + } + if (ev.changedTouches[0]) { + runOnBackground(setHasChangedTouches)(true); + } + }} + > + + + + + ); +} +root.render(); diff --git a/packages/web-platform/web-worker-runtime/src/backgroundThread/background-apis/createNativeApp.ts b/packages/web-platform/web-worker-runtime/src/backgroundThread/background-apis/createNativeApp.ts index 6ab5bb97a1..3b55553d60 100644 --- a/packages/web-platform/web-worker-runtime/src/backgroundThread/background-apis/createNativeApp.ts +++ b/packages/web-platform/web-worker-runtime/src/backgroundThread/background-apis/createNativeApp.ts @@ -68,7 +68,7 @@ export async function createNativeApp(config: { lynxCoreInject.tt.lynxCoreInject = lynxCoreInject; lynxCoreInject.tt.globalThis ??= lynxCoreInject; Object.assign(lynxCoreInject.tt, { - SystemInfo: { ...systemInfo, pixelRatio: browserConfig.pixelRatio }, + SystemInfo: { ...systemInfo, ...browserConfig }, }); const ret = entry?.(lynxCoreInject.tt); return ret;