Skip to content

Commit 1a37acb

Browse files
authored
macOS window - leave fullscreen state if transition does not happen in 10s (microsoft#203670)
This should help with issues where upon failed fullscreen restore (e.g. after OS update), you see a window without window controls because we do not render the custom title bar.
1 parent c75fdbd commit 1a37acb

File tree

2 files changed

+66
-38
lines changed

2 files changed

+66
-38
lines changed

src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,12 @@ export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow {
3232
constructor(
3333
private readonly contents: WebContents,
3434
@IEnvironmentMainService environmentMainService: IEnvironmentMainService,
35-
@ILogService private readonly logService: ILogService,
35+
@ILogService logService: ILogService,
3636
@IConfigurationService configurationService: IConfigurationService,
3737
@IStateService stateService: IStateService,
3838
@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService
3939
) {
40-
super(configurationService, stateService, environmentMainService);
40+
super(configurationService, stateService, environmentMainService, logService);
4141

4242
// Try to claim window
4343
this.tryClaimWindow();

src/vs/platform/windows/electron-main/windowImpl.ts

Lines changed: 64 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -194,12 +194,24 @@ export abstract class BaseWindow extends Disposable implements IBaseWindow {
194194
if (this.environmentMainService.args['open-devtools'] === true) {
195195
win.webContents.openDevTools();
196196
}
197+
198+
// macOS: Window Fullscreen Transitions
199+
if (isMacintosh) {
200+
this._register(this.onDidEnterFullScreen(() => {
201+
this.joinNativeFullScreenTransition?.complete(true);
202+
}));
203+
204+
this._register(this.onDidLeaveFullScreen(() => {
205+
this.joinNativeFullScreenTransition?.complete(true);
206+
}));
207+
}
197208
}
198209

199210
constructor(
200211
protected readonly configurationService: IConfigurationService,
201212
protected readonly stateService: IStateService,
202-
protected readonly environmentMainService: IEnvironmentMainService
213+
protected readonly environmentMainService: IEnvironmentMainService,
214+
protected readonly logService: ILogService
203215
) {
204216
super();
205217
}
@@ -333,21 +345,18 @@ export abstract class BaseWindow extends Disposable implements IBaseWindow {
333345

334346
//#region Fullscreen
335347

336-
// TODO@electron workaround for https://github.com/electron/electron/issues/35360
337-
// where on macOS the window will report a wrong state for `isFullScreen()` while
338-
// transitioning into and out of native full screen.
339-
protected transientIsNativeFullScreen: boolean | undefined = undefined;
340-
protected joinNativeFullScreenTransition: DeferredPromise<void> | undefined = undefined;
348+
private transientIsNativeFullScreen: boolean | undefined = undefined;
349+
private joinNativeFullScreenTransition: DeferredPromise<boolean> | undefined = undefined;
341350

342351
toggleFullScreen(): void {
343-
this.setFullScreen(!this.isFullScreen);
352+
this.setFullScreen(!this.isFullScreen, false);
344353
}
345354

346-
protected setFullScreen(fullscreen: boolean): void {
355+
protected setFullScreen(fullscreen: boolean, fromRestore: boolean): void {
347356

348357
// Set fullscreen state
349358
if (useNativeFullScreen(this.configurationService)) {
350-
this.setNativeFullScreen(fullscreen);
359+
this.setNativeFullScreen(fullscreen, fromRestore);
351360
} else {
352361
this.setSimpleFullScreen(fullscreen);
353362
}
@@ -365,31 +374,56 @@ export abstract class BaseWindow extends Disposable implements IBaseWindow {
365374
return Boolean(isFullScreen || isSimpleFullScreen);
366375
}
367376

368-
private setNativeFullScreen(fullscreen: boolean): void {
377+
private setNativeFullScreen(fullscreen: boolean, fromRestore: boolean): void {
369378
const win = this.win;
370379
if (win?.isSimpleFullScreen()) {
371380
win?.setSimpleFullScreen(false);
372381
}
373382

374-
this.doSetNativeFullScreen(fullscreen);
383+
this.doSetNativeFullScreen(fullscreen, fromRestore);
375384
}
376385

377-
private doSetNativeFullScreen(fullscreen: boolean): void {
386+
private doSetNativeFullScreen(fullscreen: boolean, fromRestore: boolean): void {
378387
if (isMacintosh) {
388+
389+
// macOS: Electron windows report `false` for `isFullScreen()` for as long
390+
// as the fullscreen transition animation takes place. As such, we need to
391+
// listen to the transition events and carry around an intermediate state
392+
// for knowing if we are in fullscreen or not
393+
// Refs: https://github.com/electron/electron/issues/35360
394+
379395
this.transientIsNativeFullScreen = fullscreen;
380-
this.joinNativeFullScreenTransition = new DeferredPromise<void>();
381-
Promise.race([
382-
this.joinNativeFullScreenTransition.p,
383-
// still timeout after some time in case the transition is unusually slow
384-
// this can easily happen for an OS update where macOS tries to reopen
385-
// previous applications and that can take multiple seconds, probably due
386-
// to security checks. its worth noting that if this takes more than
387-
// 10 seconds, users would see a window that is not-fullscreen but without
388-
// custom titlebar...
389-
timeout(10000)
390-
]).finally(() => {
396+
397+
const joinNativeFullScreenTransition = this.joinNativeFullScreenTransition = new DeferredPromise<boolean>();
398+
(async () => {
399+
const transitioned = await Promise.race([
400+
joinNativeFullScreenTransition.p,
401+
timeout(10000).then(() => false)
402+
]);
403+
404+
if (this.joinNativeFullScreenTransition !== joinNativeFullScreenTransition) {
405+
return; // another transition was requested later
406+
}
407+
391408
this.transientIsNativeFullScreen = undefined;
392-
});
409+
this.joinNativeFullScreenTransition = undefined;
410+
411+
if (!transitioned && fullscreen && fromRestore) {
412+
413+
// We have seen requests for fullscreen failing eventually after some
414+
// time, for example when an OS update was performed and windows restore.
415+
// In those cases a user would find a window that is not in fullscreen
416+
// but also does not show any custom titlebar (and thus window controls)
417+
// because we think the window is in fullscreen.
418+
//
419+
// As a workaround in that case we emit a warning and leave fullscreen
420+
// so that at least the window controls are back.
421+
422+
this.logService.warn('window: native macOS fullscreen transition did not happen within 10s from restoring');
423+
424+
this._onDidLeaveFullScreen.fire();
425+
}
426+
})();
393427
}
394428

395429
const win = this.win;
@@ -399,7 +433,7 @@ export abstract class BaseWindow extends Disposable implements IBaseWindow {
399433
private setSimpleFullScreen(fullscreen: boolean): void {
400434
const win = this.win;
401435
if (win?.isFullScreen()) {
402-
this.doSetNativeFullScreen(false);
436+
this.doSetNativeFullScreen(false, false);
403437
}
404438

405439
win?.setSimpleFullScreen(fullscreen);
@@ -486,7 +520,7 @@ export class CodeWindow extends BaseWindow implements ICodeWindow {
486520

487521
constructor(
488522
config: IWindowCreationOptions,
489-
@ILogService private readonly logService: ILogService,
523+
@ILogService logService: ILogService,
490524
@ILoggerMainService private readonly loggerMainService: ILoggerMainService,
491525
@IEnvironmentMainService environmentMainService: IEnvironmentMainService,
492526
@IPolicyService private readonly policyService: IPolicyService,
@@ -507,7 +541,7 @@ export class CodeWindow extends BaseWindow implements ICodeWindow {
507541
@IStateService stateService: IStateService,
508542
@IInstantiationService instantiationService: IInstantiationService
509543
) {
510-
super(configurationService, stateService, environmentMainService);
544+
super(configurationService, stateService, environmentMainService, logService);
511545

512546
//#region create browser window
513547
{
@@ -570,7 +604,7 @@ export class CodeWindow extends BaseWindow implements ICodeWindow {
570604
this._win.maximize();
571605

572606
if (this.windowState.mode === WindowMode.Fullscreen) {
573-
this.setFullScreen(true);
607+
this.setFullScreen(true, true);
574608
}
575609

576610
// to reduce flicker from the default window size
@@ -682,16 +716,10 @@ export class CodeWindow extends BaseWindow implements ICodeWindow {
682716
// Window Fullscreen
683717
this._register(this.onDidEnterFullScreen(() => {
684718
this.sendWhenReady('vscode:enterFullScreen', CancellationToken.None);
685-
686-
this.joinNativeFullScreenTransition?.complete();
687-
this.joinNativeFullScreenTransition = undefined;
688719
}));
689720

690721
this._register(this.onDidLeaveFullScreen(() => {
691722
this.sendWhenReady('vscode:leaveFullScreen', CancellationToken.None);
692-
693-
this.joinNativeFullScreenTransition?.complete();
694-
this.joinNativeFullScreenTransition = undefined;
695723
}));
696724

697725
// Handle configuration changes
@@ -1384,8 +1412,8 @@ export class CodeWindow extends BaseWindow implements ICodeWindow {
13841412
return { x, y, width, height };
13851413
}
13861414

1387-
protected override setFullScreen(fullscreen: boolean): void {
1388-
super.setFullScreen(fullscreen);
1415+
protected override setFullScreen(fullscreen: boolean, fromRestore: boolean): void {
1416+
super.setFullScreen(fullscreen, fromRestore);
13891417

13901418
// Events
13911419
this.sendWhenReady(fullscreen ? 'vscode:enterFullScreen' : 'vscode:leaveFullScreen', CancellationToken.None);

0 commit comments

Comments
 (0)