@@ -1797,6 +1797,216 @@ describe("PreviewManager", () => {
17971797 ) ,
17981798 ) ;
17991799
1800+ effectIt . effect ( "keeps window unthrottled until the final frame capture stops" , ( ) =>
1801+ withManager ( ( manager ) =>
1802+ Effect . gen ( function * ( ) {
1803+ const setBackgroundThrottling = vi . fn ( ) ;
1804+ const capturePage = vi . fn ( async ( ) => ( {
1805+ toJPEG : ( ) => Buffer . from ( "recording-frame" ) ,
1806+ getSize : ( ) => ( { width : 1280 , height : 720 } ) ,
1807+ } ) ) ;
1808+ const webContentsById = new Map ( [
1809+ [ 41 , makeTestPreviewWebContents ( capturePage , 41 ) ] ,
1810+ [ 42 , makeTestPreviewWebContents ( capturePage , 42 ) ] ,
1811+ ] ) ;
1812+ fromId . mockImplementation ( ( id ) =>
1813+ id === undefined ? null : ( webContentsById . get ( id ) ?? null ) ,
1814+ ) ;
1815+
1816+ yield * manager . createTab ( "tab_capture_throttling_1" ) ;
1817+ yield * manager . createTab ( "tab_capture_throttling_2" ) ;
1818+ yield * manager . registerWebview ( "tab_capture_throttling_1" , 41 ) ;
1819+ yield * manager . registerWebview ( "tab_capture_throttling_2" , 42 ) ;
1820+ yield * manager . setMainWindow ( {
1821+ isDestroyed : ( ) => false ,
1822+ once : vi . fn ( ) ,
1823+ webContents : { setBackgroundThrottling } ,
1824+ } as never ) ;
1825+
1826+ yield * manager . startRecording ( "tab_capture_throttling_1" ) ;
1827+ yield * manager . startRecording ( "tab_capture_throttling_2" ) ;
1828+ expect ( setBackgroundThrottling . mock . calls ) . toEqual ( [ [ false ] ] ) ;
1829+
1830+ yield * manager . stopRecording ( "tab_capture_throttling_1" ) ;
1831+ expect ( setBackgroundThrottling . mock . calls ) . toEqual ( [ [ false ] ] ) ;
1832+
1833+ yield * manager . stopRecording ( "tab_capture_throttling_2" ) ;
1834+ expect ( setBackgroundThrottling . mock . calls ) . toEqual ( [ [ false ] , [ true ] ] ) ;
1835+ } ) ,
1836+ ) ,
1837+ ) ;
1838+
1839+ effectIt . effect ( "does not commit failed starts and retries throttle restoration" , ( ) =>
1840+ withManager ( ( manager ) =>
1841+ Effect . gen ( function * ( ) {
1842+ const setBackgroundThrottling = vi . fn < ( enabled : boolean ) => void > ( ) ;
1843+ const capturePage = vi . fn ( async ( ) => ( {
1844+ toJPEG : ( ) => Buffer . from ( "recording-frame" ) ,
1845+ getSize : ( ) => ( { width : 1280 , height : 720 } ) ,
1846+ } ) ) ;
1847+ fromId . mockReturnValue ( makeTestPreviewWebContents ( capturePage ) ) ;
1848+
1849+ yield * manager . createTab ( "tab_capture_throttling_failure" ) ;
1850+ yield * manager . registerWebview ( "tab_capture_throttling_failure" , 42 ) ;
1851+ yield * manager . setMainWindow ( {
1852+ isDestroyed : ( ) => false ,
1853+ once : vi . fn ( ) ,
1854+ webContents : { setBackgroundThrottling } ,
1855+ } as never ) ;
1856+
1857+ setBackgroundThrottling . mockImplementationOnce ( ( ) => {
1858+ throw new Error ( "start throttling update failed" ) ;
1859+ } ) ;
1860+ const failedStart = yield * Effect . exit (
1861+ manager . startRecording ( "tab_capture_throttling_failure" ) ,
1862+ ) ;
1863+ expect ( Exit . isFailure ( failedStart ) ) . toBe ( true ) ;
1864+
1865+ yield * manager . startRecording ( "tab_capture_throttling_failure" ) ;
1866+ expect ( setBackgroundThrottling . mock . calls ) . toEqual ( [ [ false ] , [ false ] ] ) ;
1867+
1868+ setBackgroundThrottling . mockImplementationOnce ( ( ) => {
1869+ throw new Error ( "stop throttling update failed" ) ;
1870+ } ) ;
1871+ yield * manager . stopRecording ( "tab_capture_throttling_failure" ) ;
1872+ expect ( setBackgroundThrottling . mock . calls ) . toEqual ( [ [ false ] , [ false ] , [ true ] , [ true ] ] ) ;
1873+
1874+ yield * manager . startRecording ( "tab_capture_throttling_failure" ) ;
1875+ yield * manager . stopRecording ( "tab_capture_throttling_failure" ) ;
1876+ expect ( setBackgroundThrottling . mock . calls ) . toEqual ( [
1877+ [ false ] ,
1878+ [ false ] ,
1879+ [ true ] ,
1880+ [ true ] ,
1881+ [ false ] ,
1882+ [ true ] ,
1883+ ] ) ;
1884+ } ) ,
1885+ ) ,
1886+ ) ;
1887+
1888+ effectIt . effect ( "does not publish a replacement window when capture reconciliation fails" , ( ) =>
1889+ withManager ( ( manager ) =>
1890+ Effect . gen ( function * ( ) {
1891+ const setBackgroundThrottling = vi . fn ( ( ) => {
1892+ throw new Error ( "replacement throttling update failed" ) ;
1893+ } ) ;
1894+ const capturePage = vi . fn ( async ( ) => ( {
1895+ toJPEG : ( ) => Buffer . from ( "recording-frame" ) ,
1896+ getSize : ( ) => ( { width : 1280 , height : 720 } ) ,
1897+ } ) ) ;
1898+ fromId . mockReturnValue ( makeTestPreviewWebContents ( capturePage ) ) ;
1899+
1900+ yield * manager . createTab ( "tab_capture_replacement_failure" ) ;
1901+ yield * manager . registerWebview ( "tab_capture_replacement_failure" , 42 ) ;
1902+ yield * manager . startRecording ( "tab_capture_replacement_failure" ) ;
1903+
1904+ const failedReplacement = yield * Effect . exit (
1905+ manager . setMainWindow ( {
1906+ isDestroyed : ( ) => false ,
1907+ once : vi . fn ( ) ,
1908+ webContents : { setBackgroundThrottling } ,
1909+ } as never ) ,
1910+ ) ;
1911+ expect ( Exit . isFailure ( failedReplacement ) ) . toBe ( true ) ;
1912+
1913+ yield * manager . stopRecording ( "tab_capture_replacement_failure" ) ;
1914+ expect ( setBackgroundThrottling . mock . calls ) . toEqual ( [ [ false ] ] ) ;
1915+ } ) ,
1916+ ) ,
1917+ ) ;
1918+
1919+ effectIt . effect ( "ignores close events from replaced main windows" , ( ) =>
1920+ withManager ( ( manager ) =>
1921+ Effect . gen ( function * ( ) {
1922+ let closeFirstWindow : ( ( ) => void ) | undefined ;
1923+ const firstWindowThrottling = vi . fn ( ) ;
1924+ const replacementWindowThrottling = vi . fn ( ) ;
1925+ const capturePage = vi . fn ( async ( ) => ( {
1926+ toJPEG : ( ) => Buffer . from ( "recording-frame" ) ,
1927+ getSize : ( ) => ( { width : 1280 , height : 720 } ) ,
1928+ } ) ) ;
1929+ fromId . mockReturnValue ( makeTestPreviewWebContents ( capturePage ) ) ;
1930+
1931+ yield * manager . createTab ( "tab_replaced_window_close" ) ;
1932+ yield * manager . registerWebview ( "tab_replaced_window_close" , 42 ) ;
1933+ yield * manager . setMainWindow ( {
1934+ isDestroyed : ( ) => false ,
1935+ once : vi . fn ( ( event : string , listener : ( ) => void ) => {
1936+ if ( event === "closed" ) closeFirstWindow = listener ;
1937+ } ) ,
1938+ webContents : { setBackgroundThrottling : firstWindowThrottling } ,
1939+ } as never ) ;
1940+ yield * manager . setMainWindow ( {
1941+ isDestroyed : ( ) => false ,
1942+ once : vi . fn ( ) ,
1943+ webContents : { setBackgroundThrottling : replacementWindowThrottling } ,
1944+ } as never ) ;
1945+
1946+ closeFirstWindow ?.( ) ;
1947+ yield * manager . startRecording ( "tab_replaced_window_close" ) ;
1948+ expect ( firstWindowThrottling ) . not . toHaveBeenCalled ( ) ;
1949+ expect ( replacementWindowThrottling . mock . calls ) . toEqual ( [ [ false ] ] ) ;
1950+ yield * manager . stopRecording ( "tab_replaced_window_close" ) ;
1951+ expect ( replacementWindowThrottling . mock . calls ) . toEqual ( [ [ false ] , [ true ] ] ) ;
1952+ } ) ,
1953+ ) ,
1954+ ) ;
1955+
1956+ effectIt . effect ( "releases frame capture when the main window closes" , ( ) =>
1957+ withManager ( ( manager ) =>
1958+ Effect . gen ( function * ( ) {
1959+ let closeMainWindow : ( ( ) => void ) | undefined ;
1960+ const firstWindowThrottling = vi . fn ( ) ;
1961+ const replacementWindowThrottling = vi . fn ( ) ;
1962+ const capturePage = vi . fn ( async ( ) => ( {
1963+ toJPEG : ( ) => Buffer . from ( "recording-frame" ) ,
1964+ getSize : ( ) => ( { width : 1280 , height : 720 } ) ,
1965+ } ) ) ;
1966+ const webContentsById = new Map ( [
1967+ [ 42 , makeTestPreviewWebContents ( capturePage , 42 ) ] ,
1968+ [ 43 , makeTestPreviewWebContents ( capturePage , 43 ) ] ,
1969+ ] ) ;
1970+ fromId . mockImplementation ( ( id ) =>
1971+ id === undefined ? null : ( webContentsById . get ( id ) ?? null ) ,
1972+ ) ;
1973+
1974+ yield * manager . createTab ( "tab_window_close_recording" ) ;
1975+ yield * manager . createTab ( "tab_window_close_race" ) ;
1976+ yield * manager . registerWebview ( "tab_window_close_recording" , 42 ) ;
1977+ yield * manager . registerWebview ( "tab_window_close_race" , 43 ) ;
1978+ yield * manager . setMainWindow ( {
1979+ isDestroyed : ( ) => false ,
1980+ once : vi . fn ( ( event : string , listener : ( ) => void ) => {
1981+ if ( event === "closed" ) closeMainWindow = listener ;
1982+ } ) ,
1983+ webContents : { setBackgroundThrottling : firstWindowThrottling } ,
1984+ } as never ) ;
1985+ yield * manager . startRecording ( "tab_window_close_recording" ) ;
1986+ expect ( firstWindowThrottling . mock . calls ) . toEqual ( [ [ false ] ] ) ;
1987+
1988+ closeMainWindow ?.( ) ;
1989+ const racedStart = yield * Effect . exit ( manager . startRecording ( "tab_window_close_race" ) ) ;
1990+ expect ( Exit . isFailure ( racedStart ) ) . toBe ( true ) ;
1991+ if ( Exit . isFailure ( racedStart ) ) {
1992+ expect ( Option . getOrThrow ( Cause . findErrorOption ( racedStart . cause ) ) ) . toMatchObject ( {
1993+ _tag : "PreviewMainWindowClosedError" ,
1994+ tabId : "tab_window_close_race" ,
1995+ } ) ;
1996+ }
1997+ yield * Effect . yieldNow ;
1998+ yield * Effect . yieldNow ;
1999+
2000+ yield * manager . setMainWindow ( {
2001+ isDestroyed : ( ) => false ,
2002+ once : vi . fn ( ) ,
2003+ webContents : { setBackgroundThrottling : replacementWindowThrottling } ,
2004+ } as never ) ;
2005+ expect ( replacementWindowThrottling ) . not . toHaveBeenCalled ( ) ;
2006+ } ) ,
2007+ ) ,
2008+ ) ;
2009+
18002010 effectIt . effect ( "captures hidden preview recordings independently for concurrent tabs" , ( ) =>
18012011 withManager ( ( manager ) =>
18022012 Effect . gen ( function * ( ) {
@@ -2106,13 +2316,16 @@ describe("PreviewManager", () => {
21062316 effectIt . effect ( "shares background frame capture between recording and picture-in-picture" , ( ) =>
21072317 withManager ( ( manager ) =>
21082318 Effect . gen ( function * ( ) {
2319+ const setBackgroundThrottling = vi . fn ( ) ;
2320+ const mainWindowWebContents = { setBackgroundThrottling } ;
21092321 const jpeg = Buffer . from ( "shared-preview-frame" ) ;
21102322 const capturePage = vi . fn ( async ( ) => ( {
21112323 toJPEG : ( ) => jpeg ,
21122324 getSize : ( ) => ( { width : 1280 , height : 720 } ) ,
21132325 } ) ) ;
21142326 fromId . mockReturnValue ( {
21152327 id : 42 ,
2328+ hostWebContents : mainWindowWebContents ,
21162329 isDestroyed : ( ) => false ,
21172330 getType : ( ) => "webview" ,
21182331 getURL : ( ) => "https://example.com" ,
@@ -2165,6 +2378,12 @@ describe("PreviewManager", () => {
21652378 const states : PreviewManager . PreviewTabState [ ] = [ ] ;
21662379 const recordingFrames : DesktopPreviewRecordingFrame [ ] = [ ] ;
21672380
2381+ yield * manager . setMainWindow ( {
2382+ isDestroyed : ( ) => false ,
2383+ once : vi . fn ( ) ,
2384+ webContents : mainWindowWebContents ,
2385+ } as never ) ;
2386+
21682387 yield * manager . subscribeStateChanges ( ( _tabId , state ) =>
21692388 Effect . sync ( ( ) => {
21702389 states . push ( state ) ;
@@ -2179,6 +2398,7 @@ describe("PreviewManager", () => {
21792398 yield * manager . registerWebview ( "tab_pip" , 42 ) ;
21802399 yield * manager . openPictureInPicture ( "tab_pip" ) ;
21812400
2401+ expect ( setBackgroundThrottling . mock . calls ) . toEqual ( [ [ false ] ] ) ;
21822402 expect ( browserWindowConstructor ) . toHaveBeenCalledWith (
21832403 expect . objectContaining ( {
21842404 alwaysOnTop : true ,
@@ -2224,6 +2444,7 @@ describe("PreviewManager", () => {
22242444 expect ( recordingFrames ) . toHaveLength ( 1 ) ;
22252445
22262446 yield * manager . stopRecording ( "tab_pip" ) ;
2447+ expect ( setBackgroundThrottling . mock . calls ) . toEqual ( [ [ false ] ] ) ;
22272448 const framesBeforePictureInPictureOnlyTick = pictureInPictureSend . mock . calls . length ;
22282449 yield * TestClock . adjust ( 100 ) ;
22292450 expect ( capturePage ) . toHaveBeenCalledTimes ( 3 ) ;
@@ -2232,7 +2453,11 @@ describe("PreviewManager", () => {
22322453 ) ;
22332454 expect ( recordingFrames ) . toHaveLength ( 1 ) ;
22342455
2456+ setBackgroundThrottling . mockImplementationOnce ( ( ) => {
2457+ throw new Error ( "picture-in-picture throttling restore failed" ) ;
2458+ } ) ;
22352459 yield * manager . closePictureInPicture ( "tab_pip" ) ;
2460+ expect ( setBackgroundThrottling . mock . calls ) . toEqual ( [ [ false ] , [ true ] , [ true ] ] ) ;
22362461 expect ( pictureInPictureWindow . close ) . toHaveBeenCalledOnce ( ) ;
22372462 expect ( states . at ( - 1 ) ?. pictureInPicture ) . toBe ( false ) ;
22382463 const capturesAfterClose = capturePage . mock . calls . length ;
0 commit comments