@@ -506,6 +506,8 @@ const makeFaviconWebContents = (options?: {
506506 send : webviewSend ,
507507 session : { fetch } ,
508508 navigationHistory : { canGoBack : ( ) => false , canGoForward : ( ) => false } ,
509+ isFocused : vi . fn ( ( ) => true ) ,
510+ focusedFrame : { } as object | null ,
509511 setIgnoreMenuShortcuts : vi . fn ( ) ,
510512 setWindowOpenHandler : vi . fn ( ) ,
511513 executeJavaScriptInIsolatedWorld,
@@ -643,7 +645,6 @@ describe("PreviewManager", () => {
643645 const hostWebContents = { sendInputEvent, isDestroyed : ( ) => false } ;
644646 Object . assign ( preview . webContents , { hostWebContents } ) ;
645647 fromId . mockReturnValue ( preview . webContents ) ;
646- getFocusedWebContents . mockReturnValue ( preview . webContents as never ) ;
647648 yield * manager . setMainWindow ( {
648649 isDestroyed : ( ) => false ,
649650 once : vi . fn ( ) ,
@@ -708,7 +709,6 @@ describe("PreviewManager", () => {
708709 hostWebContents : { isDestroyed : ( ) => false , send } ,
709710 } ) ;
710711 fromId . mockReturnValue ( preview . webContents ) ;
711- getFocusedWebContents . mockReturnValue ( preview . webContents as never ) ;
712712 yield * manager . createTab ( "tab_reopen" ) ;
713713 yield * manager . registerWebview ( "tab_reopen" , 42 ) ;
714714 const shortcut = parseKeybindingShortcut ( chord ) ! ;
@@ -737,12 +737,12 @@ describe("PreviewManager", () => {
737737 expect ( preventDefault ) . toHaveBeenCalledTimes ( "isAutoRepeat" in overrides ? 1 : 0 ) ;
738738 expect ( send ) . toHaveBeenCalledOnce ( ) ;
739739 }
740- getFocusedWebContents . mockReturnValue ( null ) ;
740+ vi . mocked ( ( preview . webContents as Electron . WebContents ) . isFocused ) . mockReturnValue ( false ) ;
741741 preventDefault . mockClear ( ) ;
742742 beforeInput ( { preventDefault } as never , input as never ) ;
743743 expect ( preventDefault ) . not . toHaveBeenCalled ( ) ;
744744 expect ( send ) . toHaveBeenCalledOnce ( ) ;
745- getFocusedWebContents . mockReturnValue ( preview . webContents as never ) ;
745+ vi . mocked ( ( preview . webContents as Electron . WebContents ) . isFocused ) . mockReturnValue ( true ) ;
746746 yield * manager . setForwardedShortcuts ( [ ] ) ;
747747 beforeInput ( { preventDefault } as never , input as never ) ;
748748 expect ( preventDefault ) . not . toHaveBeenCalled ( ) ;
@@ -770,7 +770,6 @@ describe("PreviewManager", () => {
770770 hostWebContents : { isDestroyed : ( ) => false , send } ,
771771 } ) ;
772772 fromId . mockReturnValue ( preview . webContents ) ;
773- getFocusedWebContents . mockReturnValue ( preview . webContents as never ) ;
774773 yield * manager . createTab ( "tab_alt_graph" ) ;
775774 yield * manager . registerWebview ( "tab_alt_graph" , 42 ) ;
776775 yield * manager . setForwardedShortcuts ( [
@@ -812,7 +811,6 @@ describe("PreviewManager", () => {
812811
813812 for ( const browser of [ preview , popup ] ) {
814813 const contents = browser . webContents as Electron . WebContents ;
815- getFocusedWebContents . mockReturnValue ( browser . webContents as never ) ;
816814 const beforeInput = browser . listeners . get ( "before-input-event" ) ! ;
817815 const preventDefault = vi . fn ( ) ;
818816 const input = {
@@ -834,7 +832,7 @@ describe("PreviewManager", () => {
834832 beforeInput ( { preventDefault } as never , { ...input , key : "w" } as never ) ;
835833 expect ( contents . setIgnoreMenuShortcuts ) . toHaveBeenLastCalledWith ( true ) ;
836834
837- getFocusedWebContents . mockReturnValue ( null ) ;
835+ vi . mocked ( contents . isFocused ) . mockReturnValue ( false ) ;
838836 beforeInput ( { preventDefault } as never , input as never ) ;
839837 expect ( contents . setIgnoreMenuShortcuts ) . toHaveBeenLastCalledWith ( true ) ;
840838 expect ( preventDefault ) . not . toHaveBeenCalled ( ) ;
@@ -843,6 +841,61 @@ describe("PreviewManager", () => {
843841 ) ,
844842 ) ;
845843
844+ effectIt . effect ( "keeps editing and forwarded shortcuts in whichever tab holds focus" , ( ) =>
845+ withManager ( ( manager ) =>
846+ Effect . gen ( function * ( ) {
847+ const send = vi . fn ( ) ;
848+ const host = { isDestroyed : ( ) => false , send } ;
849+ const older = makeFaviconWebContents ( { id : 42 } ) ;
850+ const newer = makeFaviconWebContents ( { id : 43 } ) ;
851+ for ( const tab of [ older , newer ] ) Object . assign ( tab . webContents , { hostWebContents : host } ) ;
852+ // Electron names one arbitrary guest here when several share a focused window.
853+ getFocusedWebContents . mockReturnValue ( newer . webContents as never ) ;
854+ fromId . mockReturnValue ( older . webContents ) ;
855+ yield * manager . createTab ( "tab_older" ) ;
856+ yield * manager . registerWebview ( "tab_older" , 42 ) ;
857+ fromId . mockReturnValue ( newer . webContents ) ;
858+ yield * manager . createTab ( "tab_newer" ) ;
859+ yield * manager . registerWebview ( "tab_newer" , 43 ) ;
860+ const shortcut = parseKeybindingShortcut ( "mod+shift+t" ) ! ;
861+ yield * manager . setForwardedShortcuts ( [ { command : "view.reopenClosed" , shortcut } ] ) ;
862+
863+ const beforeInput = older . listeners . get ( "before-input-event" ) ! ;
864+ const key = { type : "keyDown" , meta : true , control : false , alt : false } ;
865+ beforeInput (
866+ { preventDefault : vi . fn ( ) } as never ,
867+ { ...key , key : "v" , shift : false } as never ,
868+ ) ;
869+ expect (
870+ ( older . webContents as Electron . WebContents ) . setIgnoreMenuShortcuts ,
871+ ) . toHaveBeenLastCalledWith ( false ) ;
872+
873+ const preventDefault = vi . fn ( ) ;
874+ beforeInput ( { preventDefault } as never , { ...key , key : "T" , shift : true } as never ) ;
875+ expect ( preventDefault ) . toHaveBeenCalledOnce ( ) ;
876+ expect ( send ) . toHaveBeenCalledExactlyOnceWith ( "desktop:menu-action" , "view.reopenClosed" ) ;
877+
878+ // The person types in the older tab, so keys reaching the newer one are an agent's.
879+ Object . assign ( newer . webContents , { focusedFrame : null } ) ;
880+ const agentInput = newer . listeners . get ( "before-input-event" ) ! ;
881+ const agentPreventDefault = vi . fn ( ) ;
882+ agentInput (
883+ { preventDefault : agentPreventDefault } as never ,
884+ { ...key , key : "v" , shift : false } as never ,
885+ ) ;
886+ expect (
887+ ( newer . webContents as Electron . WebContents ) . setIgnoreMenuShortcuts ,
888+ ) . toHaveBeenLastCalledWith ( true ) ;
889+ agentInput (
890+ { preventDefault : agentPreventDefault } as never ,
891+ { ...key , key : "T" , shift : true } as never ,
892+ ) ;
893+ expect ( agentPreventDefault ) . not . toHaveBeenCalled ( ) ;
894+ expect ( send ) . toHaveBeenCalledOnce ( ) ;
895+ } ) ,
896+ ) ,
897+ ) ;
898+
846899 effectIt . effect ( "rejects a destroyed webview during registration" , ( ) =>
847900 withManager ( ( manager ) =>
848901 Effect . gen ( function * ( ) {
@@ -3322,6 +3375,85 @@ describe("PreviewManager", () => {
33223375 ) ,
33233376 ) ;
33243377
3378+ effectIt . effect (
3379+ "releases queued STOP after stalled recording warmups and ignores late completion" ,
3380+ ( ) =>
3381+ withManager ( ( manager ) =>
3382+ Effect . gen ( function * ( ) {
3383+ const warmupStarted = Promise . withResolvers < void > ( ) ;
3384+ const firstWarmup = Promise . withResolvers < TestCapturedPreviewImage > ( ) ;
3385+ const lateWarmup = Promise . withResolvers < TestCapturedPreviewImage > ( ) ;
3386+ const capturePage = vi
3387+ . fn < ( ) => Promise < TestCapturedPreviewImage > > ( )
3388+ . mockImplementationOnce ( ( ) => {
3389+ warmupStarted . resolve ( ) ;
3390+ return firstWarmup . promise ;
3391+ } )
3392+ . mockImplementation ( ( ) => lateWarmup . promise ) ;
3393+ const host = makeTestHostWebContents ( ) ;
3394+ const webContents = makeTestPreviewWebContents ( capturePage , 42 , host ) ;
3395+ const windowThrottling = vi . fn ( ) ;
3396+ fromId . mockReturnValue ( webContents ) ;
3397+ yield * manager . createTab ( "tab_recording_stalled_warmup" ) ;
3398+ yield * manager . registerWebview ( "tab_recording_stalled_warmup" , 42 ) ;
3399+ yield * manager . setMainWindow ( {
3400+ isDestroyed : ( ) => false ,
3401+ once : vi . fn ( ) ,
3402+ webContents : { setBackgroundThrottling : windowThrottling } ,
3403+ } as never ) ;
3404+
3405+ const image = {
3406+ toJPEG : ( ) => Buffer . from ( "late-recording-frame" ) ,
3407+ getSize : ( ) => ( { width : 1280 , height : 720 } ) ,
3408+ } ;
3409+ try {
3410+ const start = yield * manager
3411+ . startRecording ( "tab_recording_stalled_warmup" )
3412+ . pipe ( Effect . forkChild ( { startImmediately : true } ) ) ;
3413+ yield * Effect . promise ( ( ) => warmupStarted . promise ) ;
3414+ let stopped = false ;
3415+ const stop = yield * manager . stopRecording ( "tab_recording_stalled_warmup" ) . pipe (
3416+ Effect . andThen (
3417+ Effect . sync ( ( ) => {
3418+ stopped = true ;
3419+ } ) ,
3420+ ) ,
3421+ Effect . forkChild ( { startImmediately : true } ) ,
3422+ ) ;
3423+ expect ( webContents . setBackgroundThrottling . mock . calls ) . toEqual ( [ [ false ] ] ) ;
3424+ expect ( windowThrottling . mock . calls ) . toEqual ( [ [ false ] ] ) ;
3425+ yield * TestClock . adjust ( 1_000 ) ;
3426+ expect ( stopped ) . toBe ( false ) ;
3427+ expect ( host . executeJavaScript ) . not . toHaveBeenCalled ( ) ;
3428+ yield * TestClock . adjust ( 1_000 ) ;
3429+ expect ( stopped ) . toBe ( true ) ;
3430+ yield * Fiber . join ( start ) ;
3431+ yield * Fiber . join ( stop ) ;
3432+ expect ( capturePage ) . toHaveBeenCalledTimes ( 2 ) ;
3433+ expect ( host . executeJavaScript ) . toHaveBeenCalledOnce ( ) ;
3434+ expect ( webContents . setBackgroundThrottling . mock . calls ) . toEqual ( [ [ false ] , [ true ] ] ) ;
3435+ expect ( windowThrottling . mock . calls ) . toEqual ( [ [ false ] , [ true ] ] ) ;
3436+ expect ( webviewSend ) . toHaveBeenLastCalledWith ( "preview:recording-cursor" , false ) ;
3437+
3438+ lateWarmup . resolve ( image ) ;
3439+ yield * TestClock . adjust ( 0 ) ;
3440+ const streams : Array < { video ?: unknown } > = [ ] ;
3441+ host . displayMediaHandler ( ) ?.( { frame : host . mainFrame } , ( value ) => streams . push ( value ) ) ;
3442+ expect ( streams ) . toEqual ( [ { } ] ) ;
3443+ expect ( capturePage ) . toHaveBeenCalledTimes ( 2 ) ;
3444+ expect ( host . executeJavaScript ) . toHaveBeenCalledOnce ( ) ;
3445+ expect ( webContents . setBackgroundThrottling . mock . calls ) . toEqual ( [ [ false ] , [ true ] ] ) ;
3446+ expect ( windowThrottling . mock . calls ) . toEqual ( [ [ false ] , [ true ] ] ) ;
3447+ expect ( webviewSend ) . toHaveBeenLastCalledWith ( "preview:recording-cursor" , false ) ;
3448+ } finally {
3449+ // Release stalled native calls after a failing assertion against unchanged base.
3450+ firstWarmup . resolve ( image ) ;
3451+ lateWarmup . resolve ( image ) ;
3452+ }
3453+ } ) ,
3454+ ) ,
3455+ ) ;
3456+
33253457 effectIt . effect ( "continues native recording when the source warmup fails" , ( ) =>
33263458 withManager ( ( manager ) =>
33273459 Effect . gen ( function * ( ) {
0 commit comments