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');
+        });
       });
     });
   });