-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Extend multi-window support to Electron (#11642) #49
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -34,7 +34,7 @@ import { FrontendApplicationStateService } from '../frontend-application-state'; | |||||||||
import { TabBarToolbarRegistry, TabBarToolbarFactory } from './tab-bar-toolbar'; | ||||||||||
import { ContextKeyService } from '../context-key-service'; | ||||||||||
import { Emitter } from '../../common/event'; | ||||||||||
import { waitForRevealed, waitForClosed, PINNED_CLASS } from '../widgets'; | ||||||||||
import { waitForRevealed, waitForClosed, PINNED_CLASS, ExtractableWidget } from '../widgets'; | ||||||||||
import { CorePreferences } from '../core-preferences'; | ||||||||||
import { BreadcrumbsRendererFactory } from '../breadcrumbs/breadcrumbs-renderer'; | ||||||||||
import { Deferred } from '../../common/promise-util'; | ||||||||||
|
@@ -1561,14 +1561,19 @@ export class ApplicationShell extends Widget { | |||||||||
if (!current) { | ||||||||||
return undefined; | ||||||||||
} | ||||||||||
const saveableOptions = options && { shouldSave: () => options.save }; | ||||||||||
const pendingClose = SaveableWidget.is(current) | ||||||||||
? current.closeWithSaving(saveableOptions) | ||||||||||
: (current.close(), waitForClosed(current)); | ||||||||||
await Promise.all([ | ||||||||||
pendingClose, | ||||||||||
this.pendingUpdates | ||||||||||
]); | ||||||||||
|
||||||||||
if (ExtractableWidget.is(current) && current.secondaryWindow && !current.isClosing) { | ||||||||||
current.secondaryWindow!.close(); | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With the
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
} else { | ||||||||||
const saveableOptions = options && { shouldSave: () => options.save }; | ||||||||||
const pendingClose = SaveableWidget.is(current) | ||||||||||
? current.closeWithSaving(saveableOptions) | ||||||||||
: (current.close(), waitForClosed(current)); | ||||||||||
await Promise.all([ | ||||||||||
pendingClose, | ||||||||||
this.pendingUpdates | ||||||||||
]); | ||||||||||
} | ||||||||||
return stack[0] || current; | ||||||||||
} | ||||||||||
|
||||||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -22,6 +22,9 @@ import { Widget } from './widget'; | |||||
export interface ExtractableWidget extends Widget { | ||||||
/** Set to `true` to mark the widget to be extractable. */ | ||||||
isExtractable: boolean; | ||||||
|
||||||
/** State variable to keep track of recursive attempty to close the secondary window */ | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
isClosing: boolean; | ||||||
/** The secondary window that the window was extracted to or `undefined` if it is not yet extracted. */ | ||||||
secondaryWindow: Window | undefined; | ||||||
} | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,32 +14,53 @@ | |
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 | ||
// ***************************************************************************** | ||
|
||
import { BrowserWindow } from '../../../electron-shared/electron'; | ||
import { ipcRenderer, BrowserWindow } from '../../../electron-shared/electron'; | ||
import * as electronRemote from '../../../electron-shared/@electron/remote'; | ||
import { injectable } from 'inversify'; | ||
import { injectable, postConstruct } from 'inversify'; | ||
import { DefaultSecondaryWindowService } from '../../browser/window/default-secondary-window-service'; | ||
import { CloseSecondaryRequestArguments, CLOSE_SECONDARY_REQUESTED_SIGNAL } from '../../electron-common/messaging/electron-messages'; | ||
|
||
@injectable() | ||
export class ElectronSecondaryWindowService extends DefaultSecondaryWindowService { | ||
protected electronWindows: Map<string, BrowserWindow> = new Map(); | ||
|
||
protected override doCreateSecondaryWindow(onClose?: (closedWin: Window) => void): Window | undefined { | ||
const id = this.nextWindowId(); | ||
private electronWindows: Map<string, BrowserWindow> = new Map(); | ||
private electronWindowsById: Map<string, () => Promise<boolean>> = new Map(); | ||
|
||
@postConstruct() | ||
override init(): void { | ||
super.init(); | ||
ipcRenderer.addListener(CLOSE_SECONDARY_REQUESTED_SIGNAL, (_sender, args: CloseSecondaryRequestArguments) => this.handleCloseRequestedEvent(args)); | ||
} | ||
|
||
protected async handleCloseRequestedEvent(event: CloseSecondaryRequestArguments): Promise<void> { | ||
const safeToClose = await this.safeToClose(event.windowId); | ||
if (safeToClose) { | ||
ipcRenderer.send(event.confirmChannel); | ||
} else { | ||
ipcRenderer.send(event.cancelChannel); | ||
} | ||
} | ||
|
||
protected override doCreateSecondaryWindow(id: string, wouldLoseStateOnClosing: () => boolean, tryCloseWidget: (trySaving: boolean) => Promise<boolean>, | ||
closed: (win: Window) => void): Window | undefined { | ||
let win: Window | undefined = undefined; | ||
electronRemote.getCurrentWindow().webContents.once('did-create-window', newElectronWindow => { | ||
newElectronWindow.setMenuBarVisibility(false); | ||
// newElectronWindow.setMenuBarVisibility(false); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Either comment this back in or remove the line completely. I think hiding the menu in secondary windows generally makes sense with the current state (i.e. it's just the default electron menu) |
||
this.electronWindows.set(id, newElectronWindow); | ||
newElectronWindow.on('closed', () => { | ||
const electronId = newElectronWindow.id.toString(); | ||
this.electronWindowsById.set(electronId, () => tryCloseWidget(true)); | ||
const closedHandler = () => { | ||
if (closed) { | ||
closed(win!); | ||
} | ||
|
||
this.electronWindows.delete(id); | ||
const browserWin = this.secondaryWindows.find(w => w.name === id); | ||
if (browserWin) { | ||
this.handleWindowClosed(browserWin, onClose); | ||
} else { | ||
console.warn(`Could not execute proper close handling for secondary window '${id}' because its frontend window could not be found.`); | ||
}; | ||
}); | ||
this.electronWindowsById.delete(electronId); | ||
}; | ||
newElectronWindow.once('closed', closedHandler); | ||
}); | ||
const win = window.open(DefaultSecondaryWindowService.SECONDARY_WINDOW_URL, id); | ||
return win ?? undefined; | ||
win = window.open(DefaultSecondaryWindowService.SECONDARY_WINDOW_URL, id, 'popup') || undefined; | ||
return win; | ||
} | ||
|
||
override focus(win: Window): void { | ||
|
@@ -54,4 +75,13 @@ export class ElectronSecondaryWindowService extends DefaultSecondaryWindowServic | |
console.warn(`There is no known secondary window '${win.name}'. Thus, the window could not be focussed.`); | ||
} | ||
} | ||
|
||
safeToClose(windowId: string): Promise<boolean> { | ||
const closingHandler = this.electronWindowsById.get(windowId); | ||
if (closingHandler) { | ||
return closingHandler!(); | ||
} else { | ||
return Promise.resolve(true); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
await this.applicationShell.closeWidget(widget.id, closeOptions);
closes the secondary window that the widget resides in with the changes at application-shell.ts https://github.com/eclipsesource/theia/pull/49/files#diff-cb4b87cc3dc274963d24f6fd5682ad9c89cd51d94726100eb2e3c92421e5c171R1565-R1566Afterwards, the widget is accessed to set its
isClosing
flag. Maybe this could cause problems withIllegalAccess
?This is especially intereseting because this was not the case on the last known working state at cdamus@2b3d733
The changes stem from the browser save handling as far as I can see but affect the electron implementation in this way, too. It is called here: https://github.com/eclipsesource/theia/pull/49/files#diff-563e180229ce37170c43089f2a51e72ab90eead76b88b8a4300365d8373f93c3R51
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All I can say is that this shouldn't be a problem, since the widget is an object that lives in the context of the main browser window. But it's a diff I should look at.