From 5790869a3fad62d3181c5aa631ec27874e4cc0aa Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Thu, 14 Feb 2019 14:29:20 -0800 Subject: [PATCH] chore: refactor browser IPC into TS and app API into TS (#16921) * chore: refactor browser IPC into typescript * chore: refactor app.ts into Typescript * Refactors app.dock into cpp * Removes app.launcher which has not existed for 3 years * Removes 2 deprecated APIs (that have been deprecated for more than one major) * Refactors deprecate.ts as well --- atom/browser/api/atom_api_app.cc | 75 +++++++----- atom/browser/api/atom_api_app.h | 2 + docs/api/app.md | 4 + filenames.gni | 10 +- lib/browser/api/app.js | 113 ------------------ lib/browser/api/app.ts | 68 +++++++++++ lib/browser/api/{ipc-main.js => ipc-main.ts} | 6 +- ...al-utils.js => ipc-main-internal-utils.ts} | 12 +- ...-main-internal.js => ipc-main-internal.ts} | 8 +- lib/common/api/{deprecate.js => deprecate.ts} | 48 ++++---- lib/common/api/exports/electron.js | 7 +- lib/renderer/api/exports/electron.js | 7 +- .../api/exports/electron.js | 8 +- spec/api-app-spec.js | 70 +++++++++-- typings/internal-ambient.d.ts | 2 + typings/internal-electron.d.ts | 19 +++ 16 files changed, 258 insertions(+), 201 deletions(-) delete mode 100644 lib/browser/api/app.js create mode 100644 lib/browser/api/app.ts rename lib/browser/api/{ipc-main.js => ipc-main.ts} (60%) rename lib/browser/{ipc-main-internal-utils.js => ipc-main-internal-utils.ts} (51%) rename lib/browser/{ipc-main-internal.js => ipc-main-internal.ts} (54%) rename lib/common/api/{deprecate.js => deprecate.ts} (77%) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 98da6cd79b22..11aeab5928f3 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -1259,6 +1259,46 @@ bool App::MoveToApplicationsFolder(mate::Arguments* args) { bool App::IsInApplicationsFolder() { return ui::cocoa::AtomBundleMover::IsCurrentAppInApplicationsFolder(); } + +int DockBounce(const std::string& type) { + int request_id = -1; + if (type == "critical") + request_id = Browser::Get()->DockBounce(Browser::BOUNCE_CRITICAL); + else if (type == "informational") + request_id = Browser::Get()->DockBounce(Browser::BOUNCE_INFORMATIONAL); + return request_id; +} + +void DockSetMenu(atom::api::Menu* menu) { + Browser::Get()->DockSetMenu(menu->model()); +} + +v8::Local App::GetDockAPI(v8::Isolate* isolate) { + if (dock_.IsEmpty()) { + // Initialize the Dock API, the methods are bound to "dock" which exists + // for the lifetime of "app" + auto browser = base::Unretained(Browser::Get()); + mate::Dictionary dock_obj = mate::Dictionary::CreateEmpty(isolate); + dock_obj.SetMethod("bounce", &DockBounce); + dock_obj.SetMethod("cancelBounce", + base::Bind(&Browser::DockCancelBounce, browser)); + dock_obj.SetMethod("downloadFinished", + base::Bind(&Browser::DockDownloadFinished, browser)); + dock_obj.SetMethod("setBadge", + base::Bind(&Browser::DockSetBadgeText, browser)); + dock_obj.SetMethod("getBadge", + base::Bind(&Browser::DockGetBadgeText, browser)); + dock_obj.SetMethod("hide", base::Bind(&Browser::DockHide, browser)); + dock_obj.SetMethod("show", base::Bind(&Browser::DockShow, browser)); + dock_obj.SetMethod("isVisible", + base::Bind(&Browser::DockIsVisible, browser)); + dock_obj.SetMethod("setMenu", &DockSetMenu); + dock_obj.SetMethod("setIcon", base::Bind(&Browser::DockSetIcon, browser)); + + dock_.Reset(isolate, dock_obj.GetHandle()); + } + return v8::Local::New(isolate, dock_); +} #endif // static @@ -1357,6 +1397,9 @@ void App::BuildPrototype(v8::Isolate* isolate, #if defined(MAS_BUILD) .SetMethod("startAccessingSecurityScopedResource", &App::StartAccessingSecurityScopedResource) +#endif +#if defined(OS_MACOSX) + .SetProperty("dock", &App::GetDockAPI) #endif .SetMethod("enableSandbox", &App::EnableSandbox); } @@ -1367,21 +1410,6 @@ void App::BuildPrototype(v8::Isolate* isolate, namespace { -#if defined(OS_MACOSX) -int DockBounce(const std::string& type) { - int request_id = -1; - if (type == "critical") - request_id = Browser::Get()->DockBounce(Browser::BOUNCE_CRITICAL); - else if (type == "informational") - request_id = Browser::Get()->DockBounce(Browser::BOUNCE_INFORMATIONAL); - return request_id; -} - -void DockSetMenu(atom::api::Menu* menu) { - Browser::Get()->DockSetMenu(menu->model()); -} -#endif - void Initialize(v8::Local exports, v8::Local unused, v8::Local context, @@ -1392,23 +1420,6 @@ void Initialize(v8::Local exports, ->GetFunction(context) .ToLocalChecked()); dict.Set("app", atom::api::App::Create(isolate)); -#if defined(OS_MACOSX) - auto browser = base::Unretained(Browser::Get()); - dict.SetMethod("dockBounce", &DockBounce); - dict.SetMethod("dockCancelBounce", - base::Bind(&Browser::DockCancelBounce, browser)); - dict.SetMethod("dockDownloadFinished", - base::Bind(&Browser::DockDownloadFinished, browser)); - dict.SetMethod("dockSetBadgeText", - base::Bind(&Browser::DockSetBadgeText, browser)); - dict.SetMethod("dockGetBadgeText", - base::Bind(&Browser::DockGetBadgeText, browser)); - dict.SetMethod("dockHide", base::Bind(&Browser::DockHide, browser)); - dict.SetMethod("dockShow", base::Bind(&Browser::DockShow, browser)); - dict.SetMethod("dockIsVisible", base::Bind(&Browser::DockIsVisible, browser)); - dict.SetMethod("dockSetMenu", &DockSetMenu); - dict.SetMethod("dockSetIcon", base::Bind(&Browser::DockSetIcon, browser)); -#endif } } // namespace diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index bda2a75b3d41..e844b4bf51d6 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -210,6 +210,8 @@ class App : public AtomBrowserClient::Delegate, #if defined(OS_MACOSX) bool MoveToApplicationsFolder(mate::Arguments* args); bool IsInApplicationsFolder(); + v8::Local GetDockAPI(v8::Isolate* isolate); + v8::Global dock_; #endif #if defined(MAS_BUILD) base::Callback StartAccessingSecurityScopedResource( diff --git a/docs/api/app.md b/docs/api/app.md index b30a9efb93bc..ba6ed5624b09 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -1307,6 +1307,10 @@ Returns `Boolean` - Whether the dock icon is visible. Sets the application's [dock menu][dock-menu]. +### `app.dock.getMenu()` _macOS_ + +Returns `Menu | null` - The application's [dock menu][dock-menu]. + ### `app.dock.setIcon(image)` _macOS_ * `image` ([NativeImage](native-image.md) | String) diff --git a/filenames.gni b/filenames.gni index 81df0d54dce4..a1a2d153d0eb 100644 --- a/filenames.gni +++ b/filenames.gni @@ -1,6 +1,6 @@ filenames = { js_sources = [ - "lib/browser/api/app.js", + "lib/browser/api/app.ts", "lib/browser/api/auto-updater.js", "lib/browser/api/auto-updater/auto-updater-native.js", "lib/browser/api/auto-updater/auto-updater-win.js", @@ -12,7 +12,7 @@ filenames = { "lib/browser/api/dialog.js", "lib/browser/api/exports/electron.js", "lib/browser/api/global-shortcut.js", - "lib/browser/api/ipc-main.js", + "lib/browser/api/ipc-main.ts", "lib/browser/api/in-app-purchase.js", "lib/browser/api/menu-item-roles.js", "lib/browser/api/menu-item.js", @@ -41,13 +41,13 @@ filenames = { "lib/browser/guest-view-manager.js", "lib/browser/guest-window-manager.js", "lib/browser/init.ts", - "lib/browser/ipc-main-internal-utils.js", - "lib/browser/ipc-main-internal.js", + "lib/browser/ipc-main-internal-utils.ts", + "lib/browser/ipc-main-internal.ts", "lib/browser/navigation-controller.js", "lib/browser/objects-registry.js", "lib/browser/rpc-server.js", "lib/common/api/clipboard.js", - "lib/common/api/deprecate.js", + "lib/common/api/deprecate.ts", "lib/common/api/deprecations.js", "lib/common/api/is-promise.js", "lib/common/api/exports/electron.js", diff --git a/lib/browser/api/app.js b/lib/browser/api/app.js deleted file mode 100644 index 8c51a02ae9a3..000000000000 --- a/lib/browser/api/app.js +++ /dev/null @@ -1,113 +0,0 @@ -'use strict' - -const bindings = process.atomBinding('app') -const commandLine = process.atomBinding('command_line') -const path = require('path') -const { app, App } = bindings - -// Only one app object permitted. -module.exports = app - -const electron = require('electron') -const { deprecate, Menu } = electron -const { EventEmitter } = require('events') - -let dockMenu = null - -// App is an EventEmitter. -Object.setPrototypeOf(App.prototype, EventEmitter.prototype) -EventEmitter.call(app) - -Object.assign(app, { - setApplicationMenu (menu) { - return Menu.setApplicationMenu(menu) - }, - getApplicationMenu () { - return Menu.getApplicationMenu() - }, - commandLine: { - hasSwitch: (...args) => commandLine.hasSwitch(...args.map(String)), - getSwitchValue: (...args) => commandLine.getSwitchValue(...args.map(String)), - appendSwitch: (...args) => commandLine.appendSwitch(...args.map(String)), - appendArgument: (...args) => commandLine.appendArgument(...args.map(String)) - }, - enableMixedSandbox () { - deprecate.log(`'enableMixedSandbox' is deprecated. Mixed-sandbox mode is now enabled by default. You can safely remove the call to enableMixedSandbox().`) - } -}) - -app.getFileIcon = deprecate.promisify(app.getFileIcon) - -const nativeAppMetrics = app.getAppMetrics -app.getAppMetrics = () => { - const metrics = nativeAppMetrics.call(app) - for (const metric of metrics) { - if ('memory' in metric) { - deprecate.removeProperty(metric, 'memory') - } - } - - return metrics -} - -app.isPackaged = (() => { - const execFile = path.basename(process.execPath).toLowerCase() - if (process.platform === 'win32') { - return execFile !== 'electron.exe' - } - return execFile !== 'electron' -})() - -if (process.platform === 'darwin') { - app.dock = { - bounce (type = 'informational') { - return bindings.dockBounce(type) - }, - cancelBounce: bindings.dockCancelBounce, - downloadFinished: bindings.dockDownloadFinished, - setBadge: bindings.dockSetBadgeText, - getBadge: bindings.dockGetBadgeText, - hide: bindings.dockHide, - show: bindings.dockShow, - isVisible: bindings.dockIsVisible, - setMenu (menu) { - dockMenu = menu - bindings.dockSetMenu(menu) - }, - getMenu () { - return dockMenu - }, - setIcon: bindings.dockSetIcon - } -} - -if (process.platform === 'linux') { - app.launcher = { - setBadgeCount: bindings.unityLauncherSetBadgeCount, - getBadgeCount: bindings.unityLauncherGetBadgeCount, - isCounterBadgeAvailable: bindings.unityLauncherAvailable, - isUnityRunning: bindings.unityLauncherAvailable - } -} - -app.allowNTLMCredentialsForAllDomains = function (allow) { - deprecate.warn('app.allowNTLMCredentialsForAllDomains', 'session.allowNTLMCredentialsForDomains') - const domains = allow ? '*' : '' - if (!this.isReady()) { - this.commandLine.appendSwitch('auth-server-whitelist', domains) - } else { - electron.session.defaultSession.allowNTLMCredentialsForDomains(domains) - } -} - -// Routes the events to webContents. -const events = ['login', 'certificate-error', 'select-client-certificate'] -for (const name of events) { - app.on(name, (event, webContents, ...args) => { - webContents.emit(name, event, ...args) - }) -} - -// Wrappers for native classes. -const { DownloadItem } = process.atomBinding('download_item') -Object.setPrototypeOf(DownloadItem.prototype, EventEmitter.prototype) diff --git a/lib/browser/api/app.ts b/lib/browser/api/app.ts new file mode 100644 index 000000000000..2069bf893948 --- /dev/null +++ b/lib/browser/api/app.ts @@ -0,0 +1,68 @@ +import * as path from 'path' + +import * as electron from 'electron' +import { EventEmitter } from 'events' + +const bindings = process.atomBinding('app') +const commandLine = process.atomBinding('command_line') +const { app, App } = bindings + +// Only one app object permitted. +export default app + +const { deprecate, Menu } = electron + +let dockMenu: Electron.Menu | null = null + +// App is an EventEmitter. +Object.setPrototypeOf(App.prototype, EventEmitter.prototype) +EventEmitter.call(app as any) + +Object.assign(app, { + setApplicationMenu (menu: Electron.Menu | null) { + return Menu.setApplicationMenu(menu) + }, + getApplicationMenu () { + return Menu.getApplicationMenu() + }, + commandLine: { + hasSwitch: (theSwitch: string) => commandLine.hasSwitch(String(theSwitch)), + getSwitchValue: (theSwitch: string) => commandLine.getSwitchValue(String(theSwitch)), + appendSwitch: (theSwitch: string, value?: string) => commandLine.appendSwitch(String(theSwitch), typeof value === 'undefined' ? value : String(value)), + appendArgument: (arg: string) => commandLine.appendArgument(String(arg)) + } as Electron.CommandLine, + enableMixedSandbox () { + deprecate.log(`'enableMixedSandbox' is deprecated. Mixed-sandbox mode is now enabled by default. You can safely remove the call to enableMixedSandbox().`) + } +}) + +app.getFileIcon = deprecate.promisify(app.getFileIcon) + +app.isPackaged = (() => { + const execFile = path.basename(process.execPath).toLowerCase() + if (process.platform === 'win32') { + return execFile !== 'electron.exe' + } + return execFile !== 'electron' +})() + +if (process.platform === 'darwin') { + const setDockMenu = app.dock.setMenu + app.dock.setMenu = (menu) => { + dockMenu = menu + setDockMenu(menu) + } + app.dock.getMenu = () => dockMenu +} + +// Routes the events to webContents. +const events = ['login', 'certificate-error', 'select-client-certificate'] +for (const name of events) { + app.on(name as 'login', (event, webContents, ...args: any[]) => { + webContents.emit(name, event, ...args) + }) +} + +// Wrappers for native classes. +const { DownloadItem } = process.atomBinding('download_item') +Object.setPrototypeOf(DownloadItem.prototype, EventEmitter.prototype) diff --git a/lib/browser/api/ipc-main.js b/lib/browser/api/ipc-main.ts similarity index 60% rename from lib/browser/api/ipc-main.js rename to lib/browser/api/ipc-main.ts index 9ab956931080..8229f257afe3 100644 --- a/lib/browser/api/ipc-main.js +++ b/lib/browser/api/ipc-main.ts @@ -1,10 +1,8 @@ -'use strict' - -const { EventEmitter } = require('events') +import { EventEmitter } from 'events' const emitter = new EventEmitter() // Do not throw exception when channel name is "error". emitter.on('error', () => {}) -module.exports = emitter +export default emitter diff --git a/lib/browser/ipc-main-internal-utils.js b/lib/browser/ipc-main-internal-utils.ts similarity index 51% rename from lib/browser/ipc-main-internal-utils.js rename to lib/browser/ipc-main-internal-utils.ts index fd806db5c4e2..adb17bca017e 100644 --- a/lib/browser/ipc-main-internal-utils.js +++ b/lib/browser/ipc-main-internal-utils.ts @@ -1,9 +1,9 @@ -'use strict' +import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal' +import * as errorUtils from '@electron/internal/common/error-utils' -const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal') -const errorUtils = require('@electron/internal/common/error-utils') +type IPCHandler = (...args: any[]) => any -const callHandler = async function (handler, event, args, reply) { +const callHandler = async function (handler: IPCHandler, event: Electron.Event, args: any[], reply: (args: any[]) => void) { try { const result = await handler(event, ...args) reply([null, result]) @@ -12,7 +12,7 @@ const callHandler = async function (handler, event, args, reply) { } } -exports.handle = function (channel, handler) { +export const handle = function (channel: string, handler: T) { ipcMainInternal.on(channel, (event, requestId, ...args) => { callHandler(handler, event, args, responseArgs => { event._replyInternal(`${channel}_RESPONSE_${requestId}`, ...responseArgs) @@ -20,7 +20,7 @@ exports.handle = function (channel, handler) { }) } -exports.handleSync = function (channel, handler) { +export const handleSync = function (channel: string, handler: T) { ipcMainInternal.on(channel, (event, ...args) => { callHandler(handler, event, args, responseArgs => { event.returnValue = responseArgs diff --git a/lib/browser/ipc-main-internal.js b/lib/browser/ipc-main-internal.ts similarity index 54% rename from lib/browser/ipc-main-internal.js rename to lib/browser/ipc-main-internal.ts index 660bce9b02b5..0a162bd01752 100644 --- a/lib/browser/ipc-main-internal.js +++ b/lib/browser/ipc-main-internal.ts @@ -1,12 +1,8 @@ -'use strict' - -const { EventEmitter } = require('events') +import { EventEmitter } from 'events' const emitter = new EventEmitter() // Do not throw exception when channel name is "error". emitter.on('error', () => {}) -module.exports = { - ipcMainInternal: emitter -} +export const ipcMainInternal = emitter diff --git a/lib/common/api/deprecate.js b/lib/common/api/deprecate.ts similarity index 77% rename from lib/common/api/deprecate.js rename to lib/common/api/deprecate.ts index 06c5f89b4016..15a5438a0975 100644 --- a/lib/common/api/deprecate.js +++ b/lib/common/api/deprecate.ts @@ -1,8 +1,6 @@ -'use strict' +let deprecationHandler: ElectronInternal.DeprecationHandler | null = null -let deprecationHandler = null - -function warnOnce (oldName, newName) { +function warnOnce (oldName: string, newName?: string) { let warned = false const msg = newName ? `'${oldName}' is deprecated and will be removed. Please use '${newName}' instead.` @@ -15,7 +13,7 @@ function warnOnce (oldName, newName) { } } -const deprecate = { +const deprecate: ElectronInternal.DeprecationUtil = { setHandler: (handler) => { deprecationHandler = handler }, getHandler: () => deprecationHandler, warn: (oldName, newName) => { @@ -37,7 +35,7 @@ const deprecate = { function: (fn, newName) => { const warn = warnOnce(fn.name, newName) - return function () { + return function (this: any) { warn() fn.apply(this, arguments) } @@ -47,7 +45,7 @@ const deprecate = { const warn = newName.startsWith('-') /* internal event */ ? warnOnce(`${oldName} event`) : warnOnce(`${oldName} event`, `${newName} event`) - return emitter.on(newName, function (...args) { + return emitter.on(newName, function (this: NodeJS.EventEmitter, ...args) { if (this.listenerCount(oldName) !== 0) { warn() this.emit(oldName, ...args) @@ -77,14 +75,14 @@ const deprecate = { }) }, - promisify: (fn) => { + promisify: any>(fn: T): T => { const fnName = fn.name || 'function' const oldName = `${fnName} with callbacks` const newName = `${fnName} with Promises` const warn = warnOnce(oldName, newName) - return function (...params) { - let cb + return function (this: any, ...params: any[]) { + let cb: Function | undefined if (params.length > 0 && typeof params[params.length - 1] === 'function') { cb = params.pop() } @@ -92,26 +90,26 @@ const deprecate = { if (!cb) return promise if (process.enablePromiseAPIs) warn() return promise - .then(res => { + .then((res: any) => { process.nextTick(() => { - cb.length === 2 ? cb(null, res) : cb(res) + cb!.length === 2 ? cb!(null, res) : cb!(res) }) - }, err => { + }, (err: Error) => { process.nextTick(() => { - cb.length === 2 ? cb(err) : cb() + cb!.length === 2 ? cb!(err) : cb!() }) }) - } + } as T }, - promisifyMultiArg: (fn) => { + promisifyMultiArg: any>(fn: T): T => { const fnName = fn.name || 'function' const oldName = `${fnName} with callbacks` const newName = `${fnName} with Promises` const warn = warnOnce(oldName, newName) - return function (...params) { - let cb + return function (this: any, ...params) { + let cb: Function | undefined if (params.length > 0 && typeof params[params.length - 1] === 'function') { cb = params.pop() } @@ -119,15 +117,15 @@ const deprecate = { if (!cb) return promise if (process.enablePromiseAPIs) warn() return promise - .then(res => { + .then((res: any) => { process.nextTick(() => { // eslint-disable-next-line standard/no-callback-literal - cb.length > 2 ? cb(null, ...res) : cb(...res) + cb!.length > 2 ? cb!(null, ...res) : cb!(...res) }) - }, err => { - process.nextTick(() => cb(err)) + }, (err: Error) => { + process.nextTick(() => cb!(err)) }) - } + } as T }, renameProperty: (o, oldName, newName) => { @@ -137,7 +135,7 @@ const deprecate = { // inject it and warn about it if ((oldName in o) && !(newName in o)) { warn() - o[newName] = o[oldName] + o[newName] = (o as any)[oldName] } // wrap the deprecated property in an accessor to warn @@ -155,4 +153,4 @@ const deprecate = { } } -module.exports = deprecate +export default deprecate diff --git a/lib/common/api/exports/electron.js b/lib/common/api/exports/electron.js index 49c670201ee8..9947aaa7d98a 100644 --- a/lib/common/api/exports/electron.js +++ b/lib/common/api/exports/electron.js @@ -24,7 +24,12 @@ exports.defineProperties = function (targetExports) { for (const module of moduleList) { descriptors[module.name] = { enumerable: !module.private, - get: exports.memoizedGetter(() => require(`@electron/internal/common/api/${module.file}`)) + get: exports.memoizedGetter(() => { + const value = require(`@electron/internal/common/api/${module.file}.js`) + // Handle Typescript modules with an "export default X" statement + if (value.__esModule) return value.default + return value + }) } } return Object.defineProperties(targetExports, descriptors) diff --git a/lib/renderer/api/exports/electron.js b/lib/renderer/api/exports/electron.js index 63aa8d466202..879119effd49 100644 --- a/lib/renderer/api/exports/electron.js +++ b/lib/renderer/api/exports/electron.js @@ -18,6 +18,11 @@ for (const { Object.defineProperty(exports, name, { enumerable: !isPrivate, - get: common.memoizedGetter(() => require(`@electron/internal/renderer/api/${file}`)) + get: common.memoizedGetter(() => { + const value = require(`@electron/internal/renderer/api/${file}.js`) + // Handle Typescript modules with an "export default X" statement + if (value.__esModule) return value.default + return value + }) }) } diff --git a/lib/sandboxed_renderer/api/exports/electron.js b/lib/sandboxed_renderer/api/exports/electron.js index 2992797b7249..a4d3e840dd77 100644 --- a/lib/sandboxed_renderer/api/exports/electron.js +++ b/lib/sandboxed_renderer/api/exports/electron.js @@ -2,6 +2,12 @@ const moduleList = require('@electron/internal/sandboxed_renderer/api/module-list') +const handleESModule = (m) => { + // Handle Typescript modules with an "export default X" statement + if (m.__esModule) return m.default + return m +} + for (const { name, load, @@ -14,6 +20,6 @@ for (const { Object.defineProperty(exports, name, { enumerable: !isPrivate, - get: load + get: () => handleESModule(load()) }) } diff --git a/spec/api-app-spec.js b/spec/api-app-spec.js index bdc0688d482a..7deeccce7271 100644 --- a/spec/api-app-spec.js +++ b/spec/api-app-spec.js @@ -1110,20 +1110,69 @@ describe('app module', () => { }) }) - describe('dock APIs', () => { - describe('dock.setMenu()', () => { - it('keeps references to the menu', function () { - if (process.platform !== 'darwin') this.skip() + const dockDescribe = process.platform === 'darwin' ? describe : describe.skip + dockDescribe('dock APIs', () => { + describe('dock.setMenu', () => { + it('can be retrieved via dock.getMenu', () => { + expect(app.dock.getMenu()).to.equal(null) + const menu = new Menu() + app.dock.setMenu(menu) + expect(app.dock.getMenu()).to.equal(menu) + }) + it('keeps references to the menu', () => { app.dock.setMenu(new Menu()) const v8Util = process.atomBinding('v8_util') v8Util.requestGarbageCollectionForTesting() }) }) - describe('dock.show()', () => { - before(function () { - if (process.platform !== 'darwin') this.skip() + describe('dock.bounce', () => { + it('should return -1 for unknown bounce type', () => { + expect(app.dock.bounce('bad type')).to.equal(-1) + }) + + it('should return a positive number for informational type', () => { + const appHasFocus = !!BrowserWindow.getFocusedWindow() + if (!appHasFocus) { + expect(app.dock.bounce('informational')).to.be.at.least(0) + } + }) + + it('should return a positive number for critical type', () => { + const appHasFocus = !!BrowserWindow.getFocusedWindow() + if (!appHasFocus) { + expect(app.dock.bounce('critical')).to.be.at.least(0) + } + }) + }) + + describe('dock.cancelBounce', () => { + it('should not throw', () => { + app.dock.cancelBounce(app.dock.bounce('critical')) + }) + }) + + describe('dock.setBadge', () => { + after(() => { + app.dock.setBadge('') + }) + + it('should not throw', () => { + app.dock.setBadge('1') + }) + + it('should be retrievable via getBadge', () => { + app.dock.setBadge('test') + expect(app.dock.getBadge()).to.equal('test') + }) + }) + + describe('dock.show', () => { + it('should not throw', () => { + return app.dock.show().then(() => { + expect(app.dock.isVisible()).to.equal(true) + }) }) it('returns a Promise', () => { @@ -1134,6 +1183,13 @@ describe('app module', () => { expect(app.dock.show()).to.be.eventually.fulfilled() }) }) + + describe('dock.hide', () => { + it('should not throw', () => { + app.dock.hide() + expect(app.dock.isVisible()).to.equal(false) + }) + }) }) describe('whenReady', () => { diff --git a/typings/internal-ambient.d.ts b/typings/internal-ambient.d.ts index df74ed869b2e..5c01fe9f57ed 100644 --- a/typings/internal-ambient.d.ts +++ b/typings/internal-ambient.d.ts @@ -22,6 +22,8 @@ declare namespace NodeJS { atomBinding(name: string): any; atomBinding(name: 'features'): FeaturesBinding; atomBinding(name: 'v8_util'): V8UtilBinding; + atomBinding(name: 'app'): { app: Electron.App, App: Function }; + atomBinding(name: 'command_line'): Electron.CommandLine; log: NodeJS.WriteStream['write']; activateUvLoop(): void; } diff --git a/typings/internal-electron.d.ts b/typings/internal-electron.d.ts index ffd0c2083814..68cfbe32eec3 100644 --- a/typings/internal-electron.d.ts +++ b/typings/internal-electron.d.ts @@ -26,4 +26,23 @@ declare namespace Electron { cause: SerializedError, __ELECTRON_SERIALIZED_ERROR__: true } + + const deprecate: ElectronInternal.DeprecationUtil; +} + +declare namespace ElectronInternal { + type DeprecationHandler = (message: string) => void; + interface DeprecationUtil { + setHandler(handler: DeprecationHandler): void; + getHandler(): DeprecationHandler | null; + warn(oldName: string, newName: string): void; + log(message: string): void; + function(fn: Function, newName: string): Function; + event(emitter: NodeJS.EventEmitter, oldName: string, newName: string): void; + removeProperty(object: T, propertyName: K): T; + renameProperty(object: T, oldName: string, newName: K): T; + + promisify any>(fn: T): T; + promisifyMultiArg any>(fn: T): T; + } }