From 80e5007c47f5268ac309d1c48111fb54319fe678 Mon Sep 17 00:00:00 2001 From: Jeremy Rose Date: Sun, 28 Jun 2020 18:22:55 -0700 Subject: [PATCH] chore: tsify browser-window (#24326) * chore: tsify browser-window * fix focus * also tsify top-level-window --- docs/api/browser-window.md | 2 +- filenames.auto.gni | 4 +- lib/browser/api/browser-window.js | 168 ---------------- lib/browser/api/browser-window.ts | 182 ++++++++++++++++++ ...op-level-window.js => top-level-window.ts} | 12 +- spec-main/ambient.d.ts | 15 -- spec-main/api-browser-window-spec.ts | 2 +- typings/internal-electron.d.ts | 36 ++++ 8 files changed, 227 insertions(+), 194 deletions(-) delete mode 100644 lib/browser/api/browser-window.js create mode 100644 lib/browser/api/browser-window.ts rename lib/browser/api/{top-level-window.js => top-level-window.ts} (93%) diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 1797b1541057..80d2fc5d8aea 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -692,7 +692,7 @@ Returns `BrowserWindow | null` - The window that owns the given `browserView`. I * `id` Integer -Returns `BrowserWindow` - The window with the given `id`. +Returns `BrowserWindow | null` - The window with the given `id`. #### `BrowserWindow.addExtension(path)` _Deprecated_ diff --git a/filenames.auto.gni b/filenames.auto.gni index 2406370d0581..7da200c9a07f 100644 --- a/filenames.auto.gni +++ b/filenames.auto.gni @@ -191,7 +191,7 @@ auto_filenames = { "lib/browser/api/auto-updater/auto-updater-win.js", "lib/browser/api/auto-updater/squirrel-update-win.js", "lib/browser/api/browser-view.ts", - "lib/browser/api/browser-window.js", + "lib/browser/api/browser-window.ts", "lib/browser/api/content-tracing.ts", "lib/browser/api/crash-reporter.ts", "lib/browser/api/desktop-capturer.ts", @@ -216,7 +216,7 @@ auto_filenames = { "lib/browser/api/screen.ts", "lib/browser/api/session.ts", "lib/browser/api/system-preferences.ts", - "lib/browser/api/top-level-window.js", + "lib/browser/api/top-level-window.ts", "lib/browser/api/touch-bar.js", "lib/browser/api/tray.ts", "lib/browser/api/view.ts", diff --git a/lib/browser/api/browser-window.js b/lib/browser/api/browser-window.js deleted file mode 100644 index 7901a3acacc4..000000000000 --- a/lib/browser/api/browser-window.js +++ /dev/null @@ -1,168 +0,0 @@ -'use strict'; - -const electron = require('electron'); -const { TopLevelWindow, deprecate } = electron; -const { BrowserWindow } = process._linkedBinding('electron_browser_window'); - -Object.setPrototypeOf(BrowserWindow.prototype, TopLevelWindow.prototype); - -BrowserWindow.prototype._init = function () { - // Call parent class's _init. - TopLevelWindow.prototype._init.call(this); - - // Avoid recursive require. - const { app } = electron; - - const nativeSetBounds = this.setBounds; - this.setBounds = (bounds, ...opts) => { - bounds = { - ...this.getBounds(), - ...bounds - }; - nativeSetBounds.call(this, bounds, ...opts); - }; - - // Sometimes the webContents doesn't get focus when window is shown, so we - // have to force focusing on webContents in this case. The safest way is to - // focus it when we first start to load URL, if we do it earlier it won't - // have effect, if we do it later we might move focus in the page. - // - // Though this hack is only needed on macOS when the app is launched from - // Finder, we still do it on all platforms in case of other bugs we don't - // know. - this.webContents.once('load-url', function () { - this.focus(); - }); - - // Redirect focus/blur event to app instance too. - this.on('blur', (event) => { - app.emit('browser-window-blur', event, this); - }); - this.on('focus', (event) => { - app.emit('browser-window-focus', event, this); - }); - - // Subscribe to visibilityState changes and pass to renderer process. - let isVisible = this.isVisible() && !this.isMinimized(); - const visibilityChanged = () => { - const newState = this.isVisible() && !this.isMinimized(); - if (isVisible !== newState) { - isVisible = newState; - const visibilityState = isVisible ? 'visible' : 'hidden'; - this.webContents.emit('-window-visibility-change', visibilityState); - } - }; - - const visibilityEvents = ['show', 'hide', 'minimize', 'maximize', 'restore']; - for (const event of visibilityEvents) { - this.on(event, visibilityChanged); - } - - // Notify the creation of the window. - const event = process._linkedBinding('electron_browser_event').createEmpty(); - app.emit('browser-window-created', event, this); - - Object.defineProperty(this, 'devToolsWebContents', { - enumerable: true, - configurable: false, - get () { - return this.webContents.devToolsWebContents; - } - }); -}; - -const isBrowserWindow = (win) => { - return win && win.constructor.name === 'BrowserWindow'; -}; - -BrowserWindow.fromId = (id) => { - const win = TopLevelWindow.fromId(id); - return isBrowserWindow(win) ? win : null; -}; - -BrowserWindow.getAllWindows = () => { - return TopLevelWindow.getAllWindows().filter(isBrowserWindow); -}; - -BrowserWindow.getFocusedWindow = () => { - for (const window of BrowserWindow.getAllWindows()) { - if (window.isFocused() || window.isDevToolsFocused()) return window; - } - return null; -}; - -BrowserWindow.fromWebContents = (webContents) => { - for (const window of BrowserWindow.getAllWindows()) { - if (window.webContents && window.webContents.equal(webContents)) return window; - } - - return null; -}; - -BrowserWindow.fromBrowserView = (browserView) => { - for (const window of BrowserWindow.getAllWindows()) { - if (window.getBrowserView() === browserView) return window; - } - - return null; -}; - -// Helpers. -Object.assign(BrowserWindow.prototype, { - loadURL (...args) { - return this.webContents.loadURL(...args); - }, - getURL (...args) { - return this.webContents.getURL(); - }, - loadFile (...args) { - return this.webContents.loadFile(...args); - }, - reload (...args) { - return this.webContents.reload(...args); - }, - send (...args) { - return this.webContents.send(...args); - }, - openDevTools (...args) { - return this.webContents.openDevTools(...args); - }, - closeDevTools () { - return this.webContents.closeDevTools(); - }, - isDevToolsOpened () { - return this.webContents.isDevToolsOpened(); - }, - isDevToolsFocused () { - return this.webContents.isDevToolsFocused(); - }, - toggleDevTools () { - return this.webContents.toggleDevTools(); - }, - inspectElement (...args) { - return this.webContents.inspectElement(...args); - }, - inspectSharedWorker () { - return this.webContents.inspectSharedWorker(); - }, - inspectServiceWorker () { - return this.webContents.inspectServiceWorker(); - }, - showDefinitionForSelection () { - return this.webContents.showDefinitionForSelection(); - }, - capturePage (...args) { - return this.webContents.capturePage(...args); - }, - setTouchBar (touchBar) { - electron.TouchBar._setOnWindow(touchBar, this); - }, - getBackgroundThrottling () { - return this.webContents.getBackgroundThrottling(); - }, - setBackgroundThrottling (allowed) { - this.webContents.setBackgroundThrottling(allowed); - } -}); - -module.exports = BrowserWindow; diff --git a/lib/browser/api/browser-window.ts b/lib/browser/api/browser-window.ts new file mode 100644 index 000000000000..4323f81fa7a1 --- /dev/null +++ b/lib/browser/api/browser-window.ts @@ -0,0 +1,182 @@ +import { TopLevelWindow, WebContents, Event, BrowserView, TouchBar } from 'electron'; +import type { BrowserWindow as BWT } from 'electron'; +const { BrowserWindow } = process._linkedBinding('electron_browser_window') as { BrowserWindow: typeof BWT }; + +Object.setPrototypeOf(BrowserWindow.prototype, TopLevelWindow.prototype); + +(BrowserWindow.prototype as any)._init = function (this: BWT) { + // Call parent class's _init. + (TopLevelWindow.prototype as any)._init.call(this); + + // Avoid recursive require. + const { app } = require('electron'); + + const nativeSetBounds = this.setBounds; + this.setBounds = (bounds, ...opts) => { + bounds = { + ...this.getBounds(), + ...bounds + }; + nativeSetBounds.call(this, bounds, ...opts); + }; + + // Sometimes the webContents doesn't get focus when window is shown, so we + // have to force focusing on webContents in this case. The safest way is to + // focus it when we first start to load URL, if we do it earlier it won't + // have effect, if we do it later we might move focus in the page. + // + // Though this hack is only needed on macOS when the app is launched from + // Finder, we still do it on all platforms in case of other bugs we don't + // know. + this.webContents.once('load-url' as any, function (this: WebContents) { + this.focus(); + }); + + // Redirect focus/blur event to app instance too. + this.on('blur', (event: Event) => { + app.emit('browser-window-blur', event, this); + }); + this.on('focus', (event: Event) => { + app.emit('browser-window-focus', event, this); + }); + + // Subscribe to visibilityState changes and pass to renderer process. + let isVisible = this.isVisible() && !this.isMinimized(); + const visibilityChanged = () => { + const newState = this.isVisible() && !this.isMinimized(); + if (isVisible !== newState) { + isVisible = newState; + const visibilityState = isVisible ? 'visible' : 'hidden'; + this.webContents.emit('-window-visibility-change', visibilityState); + } + }; + + const visibilityEvents = ['show', 'hide', 'minimize', 'maximize', 'restore']; + for (const event of visibilityEvents) { + this.on(event as any, visibilityChanged); + } + + // Notify the creation of the window. + const event = process._linkedBinding('electron_browser_event').createEmpty(); + app.emit('browser-window-created', event, this); + + Object.defineProperty(this, 'devToolsWebContents', { + enumerable: true, + configurable: false, + get () { + return this.webContents.devToolsWebContents; + } + }); +}; + +const isBrowserWindow = (win: any) => { + return win && win.constructor.name === 'BrowserWindow'; +}; + +BrowserWindow.fromId = (id: number) => { + const win = TopLevelWindow.fromId(id); + return isBrowserWindow(win) ? win as any as BWT : null; +}; + +BrowserWindow.getAllWindows = () => { + return TopLevelWindow.getAllWindows().filter(isBrowserWindow) as any[] as BWT[]; +}; + +BrowserWindow.getFocusedWindow = () => { + for (const window of BrowserWindow.getAllWindows()) { + if (window.isFocused() || window.isDevToolsFocused()) return window; + } + return null; +}; + +BrowserWindow.fromWebContents = (webContents: WebContents) => { + for (const window of BrowserWindow.getAllWindows()) { + if (window.webContents && window.webContents.equal(webContents)) return window; + } + + return null; +}; + +BrowserWindow.fromBrowserView = (browserView: BrowserView) => { + for (const window of BrowserWindow.getAllWindows()) { + if (window.getBrowserView() === browserView) return window; + } + + return null; +}; + +BrowserWindow.prototype.setTouchBar = function (touchBar) { + (TouchBar as any)._setOnWindow(touchBar, this); +}; + +// Forwarded to webContents: + +BrowserWindow.prototype.loadURL = function (...args) { + return this.webContents.loadURL(...args); +}; + +BrowserWindow.prototype.getURL = function () { + return this.webContents.getURL(); +}; + +BrowserWindow.prototype.loadFile = function (...args) { + return this.webContents.loadFile(...args); +}; + +BrowserWindow.prototype.reload = function (...args) { + return this.webContents.reload(...args); +}; + +BrowserWindow.prototype.send = function (...args) { + return this.webContents.send(...args); +}; + +BrowserWindow.prototype.openDevTools = function (...args) { + return this.webContents.openDevTools(...args); +}; + +BrowserWindow.prototype.closeDevTools = function () { + return this.webContents.closeDevTools(); +}; + +BrowserWindow.prototype.isDevToolsOpened = function () { + return this.webContents.isDevToolsOpened(); +}; + +BrowserWindow.prototype.isDevToolsFocused = function () { + return this.webContents.isDevToolsFocused(); +}; + +BrowserWindow.prototype.toggleDevTools = function () { + return this.webContents.toggleDevTools(); +}; + +BrowserWindow.prototype.inspectElement = function (...args) { + return this.webContents.inspectElement(...args); +}; + +BrowserWindow.prototype.inspectSharedWorker = function () { + return this.webContents.inspectSharedWorker(); +}; + +BrowserWindow.prototype.inspectServiceWorker = function () { + return this.webContents.inspectServiceWorker(); +}; + +BrowserWindow.prototype.showDefinitionForSelection = function () { + return this.webContents.showDefinitionForSelection(); +}; + +BrowserWindow.prototype.capturePage = function (...args) { + return this.webContents.capturePage(...args); +}; + +BrowserWindow.prototype.getBackgroundThrottling = function () { + return this.webContents.getBackgroundThrottling(); +}; + +BrowserWindow.prototype.setBackgroundThrottling = function (allowed: boolean) { + return this.webContents.setBackgroundThrottling(allowed); +}; + +module.exports = BrowserWindow; diff --git a/lib/browser/api/top-level-window.js b/lib/browser/api/top-level-window.ts similarity index 93% rename from lib/browser/api/top-level-window.js rename to lib/browser/api/top-level-window.ts index 83ce03141beb..084da0cb8ef3 100644 --- a/lib/browser/api/top-level-window.js +++ b/lib/browser/api/top-level-window.ts @@ -1,14 +1,12 @@ -'use strict'; - -const electron = require('electron'); -const { EventEmitter } = require('events'); -const { TopLevelWindow } = process._linkedBinding('electron_browser_top_level_window'); +import { EventEmitter } from 'events'; +import type { TopLevelWindow as TLWT } from 'electron'; +const { TopLevelWindow } = process._linkedBinding('electron_browser_top_level_window') as { TopLevelWindow: typeof TLWT }; Object.setPrototypeOf(TopLevelWindow.prototype, EventEmitter.prototype); -TopLevelWindow.prototype._init = function () { +(TopLevelWindow.prototype as any)._init = function () { // Avoid recursive require. - const { app } = electron; + const { app } = require('electron'); // Simulate the application menu on platforms other than macOS. if (process.platform !== 'darwin') { diff --git a/spec-main/ambient.d.ts b/spec-main/ambient.d.ts index ca1cd5713852..9a5ecee2c268 100644 --- a/spec-main/ambient.d.ts +++ b/spec-main/ambient.d.ts @@ -19,21 +19,6 @@ declare namespace Electron { interface Session { destroy(): void; } - - // Experimental views API - class TopLevelWindow { - constructor(args: {show: boolean}) - setContentView(view: View): void - } - class WebContentsView { - constructor(options: BrowserWindowConstructorOptions) - } - - namespace Main { - class TopLevelWindow extends Electron.TopLevelWindow {} - class View extends Electron.View {} - class WebContentsView extends Electron.WebContentsView {} - } } declare module 'dbus-native'; diff --git a/spec-main/api-browser-window-spec.ts b/spec-main/api-browser-window-spec.ts index 9742a1259b4b..78aa8ded1ba3 100644 --- a/spec-main/api-browser-window-spec.ts +++ b/spec-main/api-browser-window-spec.ts @@ -1495,7 +1495,7 @@ describe('BrowserWindow module', () => { afterEach(closeAllWindows); it('returns the window with id', () => { const w = new BrowserWindow({ show: false }); - expect(BrowserWindow.fromId(w.id).id).to.equal(w.id); + expect(BrowserWindow.fromId(w.id)!.id).to.equal(w.id); }); }); diff --git a/typings/internal-electron.d.ts b/typings/internal-electron.d.ts index 2810188d45ef..35d24f360fd6 100644 --- a/typings/internal-electron.d.ts +++ b/typings/internal-electron.d.ts @@ -34,6 +34,7 @@ declare namespace Electron { getOwnerBrowserWindow(): Electron.BrowserWindow; getLastWebPreferences(): Electron.WebPreferences; _getPreloadPaths(): string[]; + equal(other: WebContents): boolean; } interface WebPreferences { @@ -96,6 +97,41 @@ declare namespace Electron { } class View {} + + // Experimental views API + class TopLevelWindow { + constructor(args: {show: boolean}) + setContentView(view: View): void + static fromId(id: number): TopLevelWindow; + static getAllWindows(): TopLevelWindow[]; + isFocused(): boolean; + static getFocusedWindow(): TopLevelWindow | undefined; + } + class WebContentsView { + constructor(options: BrowserWindowConstructorOptions) + } + + // Deprecated / undocumented BrowserWindow methods + interface BrowserWindow { + getURL(): string; + send(channel: string, ...args: any[]): void; + openDevTools(options?: Electron.OpenDevToolsOptions): void; + closeDevTools(): void; + isDevToolsOpened(): void; + isDevToolsFocused(): void; + toggleDevTools(): void; + inspectElement(x: number, y: number): void; + inspectSharedWorker(): void; + inspectServiceWorker(): void; + getBackgroundThrottling(): void; + setBackgroundThrottling(allowed: boolean): void; + } + + namespace Main { + class TopLevelWindow extends Electron.TopLevelWindow {} + class View extends Electron.View {} + class WebContentsView extends Electron.WebContentsView {} + } } declare namespace ElectronInternal {