Skip to content
Permalink
Browse files

feat: use default-app behavior in packaged apps (default menu / windo…

…w-all-closed handling)
  • Loading branch information...
miniak committed Jan 7, 2019
1 parent eb02a42 commit f908d8889c65762ac796a9544198390f1ec1373d
@@ -5,8 +5,6 @@ const Module = require('module')
const path = require('path')
const url = require('url')

const { setDefaultApplicationMenu } = require('./menu')

// Parse command line options.
const argv = process.argv.slice(1)

@@ -57,18 +55,6 @@ if (nextArgIsRequire) {
process.exit(1)
}

// Quit when all windows are closed and no other one is listening to this.
app.on('window-all-closed', () => {
if (app.listeners('window-all-closed').length === 1 && !option.interactive) {
app.quit()
}
})

// Create default menu.
app.once('ready', () => {
setDefaultApplicationMenu()
})

// Set up preload modules
if (option.modules.length > 0) {
Module._preloadModules(option.modules)
@@ -140,6 +126,9 @@ function startRepl () {
process.exit(1)
}

// prevent quitting
app.on('window-all-closed', () => {})

const repl = require('repl')
repl.start('> ').on('exit', () => {
process.exit(0)
@@ -30,6 +30,9 @@ effect on macOS.

**Note:** This API has to be called after the `ready` event of `app` module.

**Note:** Default menu is created automatically if the app does not set one.
You can also set `process.noDefaultMenu` to `true` to disable this behavior.

#### `Menu.getApplicationMenu()`

Returns `Menu | null` - The application menu, if set, or `null`, if not set.
@@ -69,6 +69,11 @@ A `Boolean`. For Mac App Store build, this property is `true`, for other builds
A `Boolean` that controls ASAR support inside your application. Setting this to `true`
will disable the support for `asar` archives in Node's built-in modules.

### `process.noDefaultMenu`

A `Boolean` that controls whether the default menu is created when no menu is created
by the app.

### `process.noDeprecation`

A `Boolean` that controls whether or not deprecation warnings are printed to `stderr`.
@@ -78,7 +83,7 @@ instead of the `--no-deprecation` command line flag.
### `process.enablePromiseAPIs`

A `Boolean` that controls whether or not deprecation warnings are printed to `stderr` when
formerly callback-based APIs converted to Promises are invoked using callbacks. Setting this to `true`
formerly callback-based APIs converted to Promises are invoked using callbacks. Setting this to `true`
will enable deprecation warnings.

### `process.resourcesPath`
@@ -168,7 +173,7 @@ Returns an object with V8 heap statistics. Note that all statistics are reported

Returns `Object`:

* `residentSet` Integer _Linux_ and _Windows_ - The amount of memory
* `residentSet` Integer _Linux_ and _Windows_ - The amount of memory
currently pinned to actual physical RAM in Kilobytes.
* `private` Integer - The amount of memory not shared by other processes, such as
JS heap or HTML content in Kilobytes.
@@ -179,7 +184,7 @@ Returns an object giving memory usage statistics about the current process. Note
that all statistics are reported in Kilobytes.
This api should be called after app ready.

Chromium does not provide `residentSet` value for macOS. This is because macOS
Chromium does not provide `residentSet` value for macOS. This is because macOS
performs in-memory compression of pages that haven't been recently used. As a
result the resident set size value is not what one would expect. `private` memory
is more representative of the actual pre-compression memory usage of the process
@@ -40,6 +40,7 @@ filenames = {
"lib/browser/api/web-contents.js",
"lib/browser/api/web-contents-view.js",
"lib/browser/chrome-extension.js",
"lib/browser/default-menu.js",
"lib/browser/guest-view-manager.js",
"lib/browser/guest-window-manager.js",
"lib/browser/init.js",
@@ -96,7 +97,6 @@ filenames = {
"default_app/icon.png",
"default_app/index.html",
"default_app/main.js",
"default_app/menu.js",
"default_app/package.json",
"default_app/renderer.js",
"default_app/styles.css",
@@ -2,6 +2,7 @@

const { TopLevelWindow, MenuItem, webContents } = require('electron')
const { sortMenuItems } = require('@electron/internal/browser/api/menu-utils')
const { setApplicationHasMenu } = require('@electron/internal/browser/default-menu')
const EventEmitter = require('events').EventEmitter
const v8Util = process.atomBinding('v8_util')
const bindings = process.atomBinding('menu')
@@ -145,6 +146,8 @@ Menu.setApplicationMenu = function (menu) {
}

applicationMenu = menu
setApplicationHasMenu()

if (process.platform === 'darwin') {
if (!menu) return
menu._callMenuWillShow()
@@ -1,9 +1,17 @@
'use strict'

const { shell, Menu } = require('electron')

const isMac = process.platform === 'darwin'

let hasMenu = false

const setApplicationHasMenu = () => {
hasMenu = true
}

const setDefaultApplicationMenu = () => {
if (Menu.getApplicationMenu()) return
if (process.noDefaultMenu || hasMenu) return

const helpMenu = {
role: 'help',
@@ -51,5 +59,6 @@ const setDefaultApplicationMenu = () => {
}

module.exports = {
setApplicationHasMenu,
setDefaultApplicationMenu
}
@@ -184,5 +184,19 @@ if (currentPlatformSupportsAppIndicator()) {
process.env.XDG_CURRENT_DESKTOP = 'Unity'
}

// Quit when all windows are closed and no other one is listening to this.
app.on('window-all-closed', () => {
if (app.listenerCount('window-all-closed') === 1) {
app.quit()
}
})

const { setDefaultApplicationMenu } = require('@electron/internal/browser/default-menu')

// Create default menu.
app.once('ready', () => {
setDefaultApplicationMenu()
})

// Finally load app's main.js and transfer control to C++.
Module._load(path.join(packagePath, mainStartupScript), Module, true)
@@ -1182,12 +1182,12 @@ describe('app module', () => {

describe('commandLine.hasSwitch (existing argv)', () => {
it('returns true when present', async () => {
const { hasSwitch } = await runCommandLineTestApp('--foobar')
const { hasSwitch } = await runTestApp('command-line', '--foobar')
expect(hasSwitch).to.be.true()
})

it('returns false when not present', async () => {
const { hasSwitch } = await runCommandLineTestApp()
const { hasSwitch } = await runTestApp('command-line')
expect(hasSwitch).to.be.false()
})
})
@@ -1210,31 +1210,62 @@ describe('app module', () => {

describe('commandLine.getSwitchValue (existing argv)', () => {
it('returns the value when present', async () => {
const { getSwitchValue } = await runCommandLineTestApp('--foobar=test')
const { getSwitchValue } = await runTestApp('command-line', '--foobar=test')
expect(getSwitchValue).to.equal('test')
})

it('returns an empty string when present without value', async () => {
const { getSwitchValue } = await runCommandLineTestApp('--foobar')
const { getSwitchValue } = await runTestApp('command-line', '--foobar')
expect(getSwitchValue).to.equal('')
})

it('returns an empty string when not present', async () => {
const { getSwitchValue } = await runCommandLineTestApp()
const { getSwitchValue } = await runTestApp('command-line')
expect(getSwitchValue).to.equal('')
})
})
})

async function runCommandLineTestApp (...args) {
const appPath = path.join(__dirname, 'fixtures', 'api', 'command-line')
const electronPath = remote.getGlobal('process').execPath
const appProcess = cp.spawn(electronPath, [appPath, ...args])
describe('default behavior', () => {
describe('application menu', () => {
it('creates the default menu if the app does not set it', async () => {
const result = await runTestApp('default-menu')
expect(result).to.equal(false)
})

let output = ''
appProcess.stdout.on('data', (data) => { output += data })
it('does not create the default menu if the app sets a custom menu', async () => {
const result = await runTestApp('default-menu', '--custom-menu')
expect(result).to.equal(true)
})

await emittedOnce(appProcess.stdout, 'end')
it('does not create the default menu if the app sets a null menu', async () => {
const result = await runTestApp('default-menu', '--null-menu')
expect(result).to.equal(true)
})
})

describe('window-all-closed', () => {
it('quits when the app does not handle the event', async () => {
const result = await runTestApp('window-all-closed')
expect(result).to.equal(false)
})

return JSON.parse(output)
}
it('does not quit when the app handles the event', async () => {
const result = await runTestApp('window-all-closed', '--handle-event')
expect(result).to.equal(true)
})
})
})

async function runTestApp (name, ...args) {
const appPath = path.join(__dirname, 'fixtures', 'api', name)
const electronPath = remote.getGlobal('process').execPath
const appProcess = cp.spawn(electronPath, [appPath, ...args])

let output = ''
appProcess.stdout.on('data', (data) => { output += data })

await emittedOnce(appProcess.stdout, 'end')

return JSON.parse(output)
}
@@ -0,0 +1,20 @@
const { app, Menu } = require('electron')

let expectedMenu

if (app.commandLine.hasSwitch('custom-menu')) {
expectedMenu = new Menu()
Menu.setApplicationMenu(expectedMenu)
} else if (app.commandLine.hasSwitch('null-menu')) {
expectedMenu = null
Menu.setApplicationMenu(null)
}

app.on('ready', () => {
setImmediate(() => {
process.stdout.write(JSON.stringify(Menu.getApplicationMenu() === expectedMenu))
process.stdout.end()

app.quit()
})
})
@@ -0,0 +1,4 @@
{
"name": "default-menu",
"main": "main.js"
}
@@ -0,0 +1,20 @@
const { app, BrowserWindow } = require('electron')

let handled = false

if (app.commandLine.hasSwitch('handle-event')) {
app.on('window-all-closed', () => {
handled = true
app.quit()
})
}

app.on('quit', () => {
process.stdout.write(JSON.stringify(handled))
process.stdout.end()
})

app.on('ready', () => {
const win = new BrowserWindow()
win.close()
})
@@ -0,0 +1,4 @@
{
"name": "window-all-closed",
"main": "main.js"
}

0 comments on commit f908d88

Please sign in to comment.
You can’t perform that action at this time.