Browse Source

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
Samuel Attard 6 years ago
parent
commit
5790869a3f

+ 43 - 32
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<v8::Value> 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<v8::Value>::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<v8::Object> exports,
                 v8::Local<v8::Value> unused,
                 v8::Local<v8::Context> context,
@@ -1392,23 +1420,6 @@ void Initialize(v8::Local<v8::Object> 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

+ 2 - 0
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<v8::Value> GetDockAPI(v8::Isolate* isolate);
+  v8::Global<v8::Value> dock_;
 #endif
 #if defined(MAS_BUILD)
   base::Callback<void()> StartAccessingSecurityScopedResource(

+ 4 - 0
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)

+ 5 - 5
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",

+ 0 - 113
lib/browser/api/app.js

@@ -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)

+ 68 - 0
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)

+ 2 - 4
lib/browser/api/ipc-main.js → 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

+ 6 - 6
lib/browser/ipc-main-internal-utils.js → 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 <T extends IPCHandler> (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 <T extends IPCHandler> (channel: string, handler: T) {
   ipcMainInternal.on(channel, (event, ...args) => {
     callHandler(handler, event, args, responseArgs => {
       event.returnValue = responseArgs

+ 2 - 6
lib/browser/ipc-main-internal.js → 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

+ 23 - 25
lib/common/api/deprecate.js → 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: <T extends (...args: any[]) => 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: <T extends (...args: any[]) => 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

+ 6 - 1
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)

+ 6 - 1
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
+    })
   })
 }

+ 7 - 1
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())
   })
 }

+ 63 - 7
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', () => {

+ 2 - 0
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;
   }

+ 19 - 0
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<T, K extends (keyof T & string)>(object: T, propertyName: K): T;
+    renameProperty<T, K extends (keyof T & string)>(object: T, oldName: string, newName: K): T;
+
+    promisify<T extends (...args: any[]) => any>(fn: T): T;
+    promisifyMultiArg<T extends (...args: any[]) => any>(fn: T): T;
+  }
 }