@@ -65,6 +65,7 @@ export class WVPage implements PageDelegate {
6565 private _sessionListeners : RegisteredListener [ ] = [ ] ;
6666 private _eventListeners : RegisteredListener [ ] ;
6767 private _pendingMainFrameLoaderResolvers = new Map < string , ( loaderId : string ) => void > ( ) ;
68+ private _lastHoveredFrames : frames . Frame [ ] = [ ] ;
6869 readonly _browserContext : WVBrowserContext ;
6970 private _firstNonInitialNavigationCommittedPromise : Promise < void > ;
7071 private _firstNonInitialNavigationCommittedFulfill = ( ) => { } ;
@@ -76,6 +77,7 @@ export class WVPage implements PageDelegate {
7677
7778 private readonly _dialogEndpoint : string | undefined ;
7879
80+
7981 constructor ( browserContext : WVBrowserContext , outerSession : WVSession , dialogEndpoint ?: string ) {
8082 this . _outerSession = outerSession ;
8183 this . _dialogEndpoint = dialogEndpoint ;
@@ -916,37 +918,81 @@ export class WVPage implements PageDelegate {
916918 }
917919
918920 private async _dispatchWebViewInput ( progress : Progress , method : string , params : any ) : Promise < void > {
921+ if ( method === 'mouseMove' ) {
922+ await this . _dispatchMouseMove ( progress , params ) ;
923+ return ;
924+ }
919925 const positional = typeof params === 'object' && params !== null && 'x' in params ;
920926 let frame : frames . Frame = this . _page . mainFrame ( ) ;
921- let x = positional ? params . x as number : 0 ;
922- let y = positional ? params . y as number : 0 ;
927+ let x = 0 ;
928+ let y = 0 ;
929+ if ( positional ) {
930+ const path = await this . _pointerPath ( progress , params . x as number , params . y as number ) ;
931+ ( { frame, x, y} = path [ path . length - 1 ] ) ;
932+ } else {
933+ // Non-positional actions (keyboard, insertText) follow the focused <iframe> chain.
934+ for ( ; ; ) {
935+ const context = await progress . race ( frame . mainContext ( ) ) ;
936+ const iframe = await progress . race ( context . evaluateHandle ( ( ) => ( globalThis as any ) . __pwWebViewInput . activeIFrame ( ) ) ) as dom . ElementHandle ;
937+ const childFrame = await this . _childFrame ( progress , iframe ) ;
938+ if ( ! childFrame )
939+ break ;
940+ frame = childFrame ;
941+ }
942+ }
943+ const context = await progress . race ( frame . mainContext ( ) ) ;
944+ // This will automatically await if the result is a promise, so async dispatchers deliver every event before this resolves.
945+ await progress . race ( context . evaluate ( ( { method, params } ) => ( globalThis as any ) . __pwWebViewInput [ method ] ( params ) , { method, params : positional ? { ...params , x, y } : params } ) ) ;
946+ }
947+
948+ private async _childFrame ( progress : Progress , iframe : dom . ElementHandle ) : Promise < frames . Frame | null > {
949+ try {
950+ return await progress . race ( this . getContentFrame ( iframe ) ) ;
951+ } finally {
952+ iframe . dispose ( ) ;
953+ }
954+ }
955+
956+ // Walk from the main frame into the deepest <iframe> that contains the point,
957+ // translating the point into each frame's coordinates along the way.
958+ private async _pointerPath ( progress : Progress , x : number , y : number ) : Promise < { frame : frames . Frame , x : number , y : number } [ ] > {
959+ const path : { frame : frames . Frame , x : number , y : number } [ ] = [ ] ;
960+ let frame : frames . Frame = this . _page . mainFrame ( ) ;
923961 for ( ; ; ) {
962+ path . push ( { frame, x, y } ) ;
924963 const context = await progress . race ( frame . mainContext ( ) ) ;
925- const position = positional
926- ? await progress . race ( context . evaluateHandle ( ( [ px , py ] ) => ( globalThis as any ) . __pwWebViewInput . positionInIFrame ( px , py ) , [ x , y ] ) )
927- : undefined ;
964+ const position = await progress . race ( context . evaluateHandle ( ( [ px , py ] ) => ( globalThis as any ) . __pwWebViewInput . positionInIFrame ( px , py ) , [ x , y ] ) ) ;
928965 try {
929- const iframe = ( position
930- ? await position . getProperty ( progress , 'iframe' )
931- : await progress . race ( context . evaluateHandle ( ( ) => ( globalThis as any ) . __pwWebViewInput . activeIFrame ( ) ) ) ) as dom . ElementHandle ;
932- let childFrame : frames . Frame | null ;
933- try {
934- childFrame = await progress . race ( this . getContentFrame ( iframe ) ) ;
935- } finally {
936- iframe . dispose ( ) ;
937- }
966+ const iframe = await position . getProperty ( progress , 'iframe' ) as dom . ElementHandle ;
967+ const childFrame = await this . _childFrame ( progress , iframe ) ;
938968 if ( ! childFrame )
939969 break ;
940- if ( position )
941- ( { x, y } = await progress . race ( position . evaluate ( result => ( { x : result . x , y : result . y } ) ) ) ) ;
970+ ( { x, y } = await progress . race ( position . evaluate ( result => ( { x : result . x , y : result . y } ) ) ) ) ;
942971 frame = childFrame ;
943972 } finally {
944- position ? .dispose ( ) ;
973+ position . dispose ( ) ;
945974 }
946975 }
947- const context = await progress . race ( frame . mainContext ( ) ) ;
948- // This will automatically await if the result is a promise, so async dispatchers deliver every event before this resolves.
949- await progress . race ( context . evaluate ( ( { method, params } ) => ( globalThis as any ) . __pwWebViewInput [ method ] ( params ) , { method, params : positional ? { ...params , x, y } : params } ) ) ;
976+ return path ;
977+ }
978+
979+ // Mouse move maintains hover state across frames: as the pointer crosses an <iframe>
980+ // boundary it leaves the elements in one frame and enters elements in another.
981+ private async _dispatchMouseMove ( progress : Progress , params : any ) : Promise < void > {
982+ const path = await this . _pointerPath ( progress , params . x as number , params . y as number ) ;
983+ const framesToHover = path . map ( entry => entry . frame ) ;
984+ const frames = this . _page . frameManager . frames ( ) ;
985+ for ( const hoveredFrame of this . _lastHoveredFrames . reverse ( ) ) {
986+ if ( framesToHover . includes ( hoveredFrame ) || ! frames . includes ( hoveredFrame ) )
987+ continue ;
988+ const context = await progress . race ( hoveredFrame . mainContext ( ) ) ;
989+ await progress . race ( context . evaluate ( ( ) => ( globalThis as any ) . __pwWebViewInput . clearHover ( ) ) ) ;
990+ }
991+ for ( const entry of path ) {
992+ const context = await progress . race ( entry . frame . mainContext ( ) ) ;
993+ await progress . race ( context . evaluate ( p => ( globalThis as any ) . __pwWebViewInput . mouseMove ( p ) , { ...params , x : entry . x , y : entry . y } ) ) ;
994+ }
995+ this . _lastHoveredFrames = framesToHover ;
950996 }
951997
952998 async resetForReuse ( progress : Progress ) : Promise < void > {
0 commit comments