From da67cbf5512c1c4e494d8c4d0c97a1adebbdf4a7 Mon Sep 17 00:00:00 2001 From: Shelley Vohr <shelley.vohr@gmail.com> Date: Wed, 1 Apr 2020 08:22:32 -0700 Subject: [PATCH] feat: add property support for remainder of BrowserWindow (#22771) Adds property-based support for the remainder of primitive gette/setter pairs on `BrowserWindow`. Namely: - `win.simpleFullScreen` - `win.title` - `win.visibleOnAllWorkspaces` - `win.documentEdited` - `win.representedFilename` - `win.shadow` - `win.kiosk` - `win.menuBarVisible` --- docs/api/browser-window.md | 45 +++- docs/api/modernization/property-updates.md | 7 - lib/browser/api/browser-window.js | 37 --- lib/browser/api/top-level-window.js | 77 +++++++ spec-main/api-browser-window-spec.ts | 254 +++++++++++++++++---- 5 files changed, 330 insertions(+), 90 deletions(-) diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 91ab2dcfc10..4a68944109b 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -177,7 +177,7 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. * `simpleFullscreen` Boolean (optional) - Use pre-Lion fullscreen on macOS. Default is `false`. * `skipTaskbar` Boolean (optional) - Whether to show the window in taskbar. Default is `false`. - * `kiosk` Boolean (optional) - The kiosk mode. Default is `false`. + * `kiosk` Boolean (optional) - Whether the window is in kiosk mode. Default is `false`. * `title` String (optional) - Default window title. Default is `"Electron"`. If the HTML tag `<title>` is defined in the HTML file loaded by `loadURL()`, this property will be ignored. * `icon` ([NativeImage](native-image.md) | String) (optional) - The window icon. On Windows it is recommended to use `ICO` icons to get best visual effects, you can also @@ -797,6 +797,47 @@ A `Boolean` property that determines whether the window menu bar should hide its If the menu bar is already visible, setting this property to `true` won't hide it immediately. +#### `win.simpleFullScreen` + +A `Boolean` property that determines whether the window is in simple (pre-Lion) fullscreen mode. + +#### `win.visibleOnAllWorkspaces` + +A `Boolean` property that determines whether the window is visible on all workspaces. + +**Note:** Always returns false on Windows. + +#### `win.shadow` + +A `Boolean` property that determines whether the window has a shadow. + +#### `win.menuBarVisible` _Windows_ _Linux_ + +A `Boolean` property that determines whether the menu bar should be visible. + +**Note:** If the menu bar is auto-hide, users can still bring up the menu bar by pressing the single `Alt` key. + +#### `win.kiosk` + +A `Boolean` property that determines whether the window is in kiosk mode. + +#### `win.documentEdited` _macOS_ + +A `Boolean` property that specifies whether the window’s document has been edited. + +The icon in title bar will become gray when set to `true`. + +#### `win.representedFilename` _macOS_ + +A `String` property that determines the pathname of the file the window represents, +and the icon of the file will show in window's title bar. + +#### `win.title` + +A `String` property that determines the title of the native window. + +**Note:** The title of the web page can be different from the title of the native window. + #### `win.minimizable` A `Boolean` property that determines whether the window can be manually minimized by user. @@ -1276,7 +1317,7 @@ Makes the window not show in the taskbar. * `flag` Boolean -Enters or leaves the kiosk mode. +Enters or leaves kiosk mode. #### `win.isKiosk()` diff --git a/docs/api/modernization/property-updates.md b/docs/api/modernization/property-updates.md index 2136e6de13e..7019982b6d5 100644 --- a/docs/api/modernization/property-updates.md +++ b/docs/api/modernization/property-updates.md @@ -5,14 +5,7 @@ The Electron team is currently undergoing an initiative to convert separate gett ## Candidates * `BrowserWindow` - * `fullscreen` - * `simpleFullscreen` - * `alwaysOnTop` - * `title` - * `documentEdited` - * `hasShadow` * `menubarVisible` - * `visibleOnAllWorkspaces` * `crashReporter` module * `uploadToServer` * `webFrame` modules diff --git a/lib/browser/api/browser-window.js b/lib/browser/api/browser-window.js index 403f5e7bfd5..4bda2972c12 100644 --- a/lib/browser/api/browser-window.js +++ b/lib/browser/api/browser-window.js @@ -72,43 +72,6 @@ BrowserWindow.prototype._init = function () { return this.webContents.devToolsWebContents; } }); - - // Properties - - Object.defineProperty(this, 'autoHideMenuBar', { - get: () => this.isMenuBarAutoHide(), - set: (autoHide) => this.setAutoHideMenuBar(autoHide) - }); - - Object.defineProperty(this, 'minimizable', { - get: () => this.isMinimizable(), - set: (min) => this.setMinimizable(min) - }); - - Object.defineProperty(this, 'maximizable', { - get: () => this.isMaximizable(), - set: (max) => this.setMaximizable(max) - }); - - Object.defineProperty(this, 'resizable', { - get: () => this.isResizable(), - set: (res) => this.setResizable(res) - }); - - Object.defineProperty(this, 'fullScreenable', { - get: () => this.isFullScreenable(), - set: (full) => this.setFullScreenable(full) - }); - - Object.defineProperty(this, 'closable', { - get: () => this.isClosable(), - set: (close) => this.setClosable(close) - }); - - Object.defineProperty(this, 'movable', { - get: () => this.isMovable(), - set: (move) => this.setMovable(move) - }); }; const isBrowserWindow = (win) => { diff --git a/lib/browser/api/top-level-window.js b/lib/browser/api/top-level-window.js index 570a5735089..a4fd8369873 100644 --- a/lib/browser/api/top-level-window.js +++ b/lib/browser/api/top-level-window.js @@ -17,6 +17,83 @@ TopLevelWindow.prototype._init = function () { } }; +// Properties + +Object.defineProperty(TopLevelWindow.prototype, 'autoHideMenuBar', { + get: function () { return this.isMenuBarAutoHide(); }, + set: function (autoHide) { this.setAutoHideMenuBar(autoHide); } +}); + +Object.defineProperty(TopLevelWindow.prototype, 'visibleOnAllWorkspaces', { + get: function () { return this.isVisibleOnAllWorkspaces(); }, + set: function (visible) { this.setVisibleOnAllWorkspaces(visible); } +}); + +Object.defineProperty(TopLevelWindow.prototype, 'simpleFullScreen', { + get: function () { return this.isSimpleFullScreen(); }, + set: function (simple) { this.setSimpleFullScreen(simple); } +}); + +Object.defineProperty(TopLevelWindow.prototype, 'kiosk', { + get: function () { return this.isKiosk(); }, + set: function (kiosk) { this.setKiosk(kiosk); } +}); + +Object.defineProperty(TopLevelWindow.prototype, 'documentEdited', { + get: function () { return this.isFullscreen(); }, + set: function (edited) { this.setDocumentEdited(edited); } +}); + +Object.defineProperty(TopLevelWindow.prototype, 'shadow', { + get: function () { return this.hasShadow(); }, + set: function (shadow) { this.setHasShadow(shadow); } +}); + +Object.defineProperty(TopLevelWindow.prototype, 'representedFilename', { + get: function () { return this.getRepresentedFilename(); }, + set: function (filename) { this.setRepresentedFilename(filename); } +}); + +Object.defineProperty(TopLevelWindow.prototype, 'minimizable', { + get: function () { return this.isMinimizable(); }, + set: function (min) { this.setMinimizable(min); } +}); + +Object.defineProperty(TopLevelWindow.prototype, 'title', { + get: function () { return this.getTitle(); }, + set: function (title) { this.setTitle(title); } +}); + +Object.defineProperty(TopLevelWindow.prototype, 'maximizable', { + get: function () { return this.isMaximizable(); }, + set: function (max) { this.setMaximizable(max); } +}); + +Object.defineProperty(TopLevelWindow.prototype, 'resizable', { + get: function () { return this.isResizable(); }, + set: function (res) { this.setResizable(res); } +}); + +Object.defineProperty(TopLevelWindow.prototype, 'menuBarVisible', { + get: function () { return this.isMenuBarVisible(); }, + set: function (visible) { this.setMenuBarVisibility(visible); } +}); + +Object.defineProperty(TopLevelWindow.prototype, 'fullScreenable', { + get: function () { return this.isFullScreenable(); }, + set: function (full) { this.setFullScreenable(full); } +}); + +Object.defineProperty(TopLevelWindow.prototype, 'closable', { + get: function () { return this.isClosable(); }, + set: function (close) { this.setClosable(close); } +}); + +Object.defineProperty(TopLevelWindow.prototype, 'movable', { + get: function () { return this.isMovable(); }, + set: function (move) { this.setMovable(move); } +}); + TopLevelWindow.getFocusedWindow = () => { return TopLevelWindow.getAllWindows().find((win) => win.isFocused()); }; diff --git a/spec-main/api-browser-window-spec.ts b/spec-main/api-browser-window-spec.ts index 4dff3bc5363..f8838dca6ae 100644 --- a/spec-main/api-browser-window-spec.ts +++ b/spec-main/api-browser-window-spec.ts @@ -959,6 +959,7 @@ describe('BrowserWindow module', () => { w.setPosition(pos[0], pos[1]); }); }); + ifdescribe(process.platform !== 'linux')('Maximized state', () => { it('checks normal bounds when maximized', (done) => { const bounds = w.getBounds(); @@ -982,6 +983,7 @@ describe('BrowserWindow module', () => { w.maximize(); }); }); + ifdescribe(process.platform !== 'linux')('Minimized state', () => { it('checks normal bounds when minimized', (done) => { const bounds = w.getBounds(); @@ -1005,8 +1007,9 @@ describe('BrowserWindow module', () => { w.minimize(); }); }); - ifdescribe(process.platform === 'win32')('Fullscreen state', () => { - it('checks normal bounds when fullscreen\'ed', (done) => { + + ifdescribe(process.platform === 'win32')(`Fullscreen state`, () => { + it(`checks normal bounds when fullscreen'ed`, (done) => { const bounds = w.getBounds(); w.once('enter-full-screen', () => { expectBoundsEqual(w.getNormalBounds(), bounds); @@ -1015,7 +1018,8 @@ describe('BrowserWindow module', () => { w.show(); w.setFullScreen(true); }); - it('checks normal bounds when unfullscreen\'ed', (done) => { + + it(`checks normal bounds when unfullscreen'ed`, (done) => { const bounds = w.getBounds(); w.once('enter-full-screen', () => { w.setFullScreen(false); @@ -3437,6 +3441,96 @@ describe('BrowserWindow module', () => { }); }); + describe('visibleOnAllWorkspaces state', () => { + it('with properties', () => { + it('can be changed', () => { + const w = new BrowserWindow({ show: false }); + expect(w.visibleOnAllWorkspaces).to.be.false(); + w.visibleOnAllWorkspaces = true; + expect(w.visibleOnAllWorkspaces).to.be.true(); + }); + }); + + it('with functions', () => { + it('can be changed', () => { + const w = new BrowserWindow({ show: false }); + expect(w.isVisibleOnAllWorkspaces()).to.be.false(); + w.setVisibleOnAllWorkspaces(true); + expect(w.isVisibleOnAllWorkspaces()).to.be.true(); + }); + }); + }); + + ifdescribe(process.platform === 'darwin')('documentEdited state', () => { + it('with properties', () => { + it('can be changed', () => { + const w = new BrowserWindow({ show: false }); + expect(w.documentEdited).to.be.false(); + w.documentEdited = true; + expect(w.documentEdited).to.be.true(); + }); + }); + + it('with functions', () => { + it('can be changed', () => { + const w = new BrowserWindow({ show: false }); + expect(w.isDocumentEdited()).to.be.false(); + w.setDocumentEdited(true); + expect(w.isDocumentEdited()).to.be.true(); + }); + }); + }); + + ifdescribe(process.platform === 'darwin')('representedFilename', () => { + it('with properties', () => { + it('can be changed', () => { + const w = new BrowserWindow({ show: false }); + expect(w.representedFilename).to.eql(''); + w.representedFilename = 'a name'; + expect(w.representedFilename).to.eql('a name'); + }); + }); + + it('with functions', () => { + it('can be changed', () => { + const w = new BrowserWindow({ show: false }); + expect(w.getRepresentedFilename()).to.eql(''); + w.setRepresentedFilename('a name'); + expect(w.getRepresentedFilename()).to.eql('a name'); + }); + }); + }); + + describe('native window title', () => { + it('with properties', () => { + it('can be set with title constructor option', () => { + const w = new BrowserWindow({ show: false, title: 'mYtItLe' }); + expect(w.title).to.eql('mYtItLe'); + }); + + it('can be changed', () => { + const w = new BrowserWindow({ show: false }); + expect(w.title).to.eql('Electron Test Main'); + w.title = 'NEW TITLE'; + expect(w.title).to.eql('NEW TITLE'); + }); + }); + + it('with functions', () => { + it('can be set with minimizable constructor option', () => { + const w = new BrowserWindow({ show: false, title: 'mYtItLe' }); + expect(w.getTitle()).to.eql('mYtItLe'); + }); + + it('can be changed', () => { + const w = new BrowserWindow({ show: false }); + expect(w.getTitle()).to.eql('Electron Test Main'); + w.setTitle('NEW TITLE'); + expect(w.getTitle()).to.eql('NEW TITLE'); + }); + }); + }); + describe('minimizable state', () => { it('with properties', () => { it('can be set with minimizable constructor option', () => { @@ -3569,23 +3663,31 @@ describe('BrowserWindow module', () => { }); }); - ifdescribe(process.platform === 'darwin')('fullscreenable state', () => { - it('with properties', () => { - it('can be set with fullscreenable constructor option', () => { - const w = new BrowserWindow({ show: false, fullscreenable: false }); - expect(w.fullScreenable).to.be.false('fullScreenable'); - }); - + ifdescribe(process.platform !== 'darwin')('menuBarVisible state', () => { + describe('with properties', () => { it('can be changed', () => { const w = new BrowserWindow({ show: false }); - expect(w.fullScreenable).to.be.true('fullScreenable'); - w.fullScreenable = false; - expect(w.fullScreenable).to.be.false('fullScreenable'); - w.fullScreenable = true; - expect(w.fullScreenable).to.be.true('fullScreenable'); + expect(w.menuBarVisible).to.be.true(); + w.menuBarVisible = false; + expect(w.menuBarVisible).to.be.false(); + w.menuBarVisible = true; + expect(w.menuBarVisible).to.be.true(); }); }); + describe('with functions', () => { + it('can be changed', () => { + const w = new BrowserWindow({ show: false }); + expect(w.isMenuBarVisible()).to.be.true('isMenuBarVisible'); + w.setMenuBarVisibility(false); + expect(w.isMenuBarVisible()).to.be.false('isMenuBarVisible'); + w.setMenuBarVisibility(true); + expect(w.isMenuBarVisible()).to.be.true('isMenuBarVisible'); + }); + }); + }); + + ifdescribe(process.platform === 'darwin')('fullscreenable state', () => { it('with functions', () => { it('can be set with fullscreenable constructor option', () => { const w = new BrowserWindow({ show: false, fullscreenable: false }); @@ -3607,18 +3709,46 @@ describe('BrowserWindow module', () => { const tick = () => new Promise(resolve => setTimeout(resolve)); ifdescribe(process.platform === 'darwin')('kiosk state', () => { - it('can be changed with setKiosk method', (done) => { - const w = new BrowserWindow(); - w.once('enter-full-screen', async () => { - await tick(); - w.setKiosk(false); - expect(w.isKiosk()).to.be.false('isKiosk'); + it('with properties', () => { + it('can be set with a constructor property', () => { + const w = new BrowserWindow({ kiosk: true }); + expect(w.kiosk).to.be.true(); }); - w.once('leave-full-screen', () => { - done(); + + it('can be changed ', (done) => { + const w = new BrowserWindow(); + w.once('enter-full-screen', async () => { + await tick(); + w.kiosk = false; + expect(w.kiosk).to.be.false(); + }); + w.once('leave-full-screen', () => { + done(); + }); + w.kiosk = true; + expect(w.kiosk).to.be.true(); + }); + }); + + it('with functions', () => { + it('can be set with a constructor property', () => { + const w = new BrowserWindow({ kiosk: true }); + expect(w.isKiosk()).to.be.true(); + }); + + it('can be changed ', (done) => { + const w = new BrowserWindow(); + w.once('enter-full-screen', async () => { + await tick(); + w.setKiosk(false); + expect(w.isKiosk()).to.be.false('isKiosk'); + }); + w.once('leave-full-screen', () => { + done(); + }); + w.setKiosk(true); + expect(w.isKiosk()).to.be.true('isKiosk'); }); - w.setKiosk(true); - expect(w.isKiosk()).to.be.true('isKiosk'); }); }); @@ -3653,7 +3783,7 @@ describe('BrowserWindow module', () => { w.setFullScreen(true); }); - it('does not crash when exiting simpleFullScreen', (done) => { + it('does not crash when exiting simpleFullScreen (properties)', (done) => { const w = new BrowserWindow(); w.setSimpleFullScreen(true); @@ -3663,6 +3793,16 @@ describe('BrowserWindow module', () => { }, 1000); }); + it('does not crash when exiting simpleFullScreen (functions)', (done) => { + const w = new BrowserWindow(); + w.simpleFullScreen = true; + + setTimeout(() => { + w.setFullScreen(!w.isFullScreen()); + done(); + }, 1000); + }); + it('should not be changed by setKiosk method', (done) => { const w = new BrowserWindow(); w.once('enter-full-screen', async () => { @@ -3717,27 +3857,53 @@ describe('BrowserWindow module', () => { }); describe('hasShadow state', () => { - it('returns a boolean on all platforms', () => { - const w = new BrowserWindow({ show: false }); - const hasShadow = w.hasShadow(); - expect(hasShadow).to.be.a('boolean'); + it('with properties', () => { + it('returns a boolean on all platforms', () => { + const w = new BrowserWindow({ show: false }); + expect(w.shadow).to.be.a('boolean'); + }); + + // On Windows there's no shadow by default & it can't be changed dynamically. + it('can be changed with hasShadow option', () => { + const hasShadow = process.platform !== 'darwin'; + const w = new BrowserWindow({ show: false, hasShadow }); + expect(w.shadow).to.equal(hasShadow); + }); + + it('can be changed with setHasShadow method', () => { + const w = new BrowserWindow({ show: false }); + w.shadow = false; + expect(w.shadow).to.be.false('hasShadow'); + w.shadow = true; + expect(w.shadow).to.be.true('hasShadow'); + w.shadow = false; + expect(w.shadow).to.be.false('hasShadow'); + }); }); - // On Windows there's no shadow by default & it can't be changed dynamically. - it('can be changed with hasShadow option', () => { - const hasShadow = process.platform !== 'darwin'; - const w = new BrowserWindow({ show: false, hasShadow }); - expect(w.hasShadow()).to.equal(hasShadow); - }); + describe('with functions', () => { + it('returns a boolean on all platforms', () => { + const w = new BrowserWindow({ show: false }); + const hasShadow = w.hasShadow(); + expect(hasShadow).to.be.a('boolean'); + }); - it('can be changed with setHasShadow method', () => { - const w = new BrowserWindow({ show: false }); - w.setHasShadow(false); - expect(w.hasShadow()).to.be.false('hasShadow'); - w.setHasShadow(true); - expect(w.hasShadow()).to.be.true('hasShadow'); - w.setHasShadow(false); - expect(w.hasShadow()).to.be.false('hasShadow'); + // On Windows there's no shadow by default & it can't be changed dynamically. + it('can be changed with hasShadow option', () => { + const hasShadow = process.platform !== 'darwin'; + const w = new BrowserWindow({ show: false, hasShadow }); + expect(w.hasShadow()).to.equal(hasShadow); + }); + + it('can be changed with setHasShadow method', () => { + const w = new BrowserWindow({ show: false }); + w.setHasShadow(false); + expect(w.hasShadow()).to.be.false('hasShadow'); + w.setHasShadow(true); + expect(w.hasShadow()).to.be.true('hasShadow'); + w.setHasShadow(false); + expect(w.hasShadow()).to.be.false('hasShadow'); + }); }); }); });