From 621178f5588d36336dd7adb0fbda70b2e8b62ec5 Mon Sep 17 00:00:00 2001 From: Patrick Detlefsen Date: Mon, 15 Jun 2015 20:28:47 +0200 Subject: [PATCH 001/146] initial idea --- atom/browser/api/lib/auto-updater.coffee | 7 ++- .../lib/auto-updater/auto-updater-win.coffee | 60 +++++++++++++++++++ .../auto-updater/squirrel-update-win.coffee | 42 +++++++++++++ 3 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 atom/browser/api/lib/auto-updater/auto-updater-win.coffee create mode 100644 atom/browser/api/lib/auto-updater/squirrel-update-win.coffee diff --git a/atom/browser/api/lib/auto-updater.coffee b/atom/browser/api/lib/auto-updater.coffee index 8b6f7ffd0d..7d7feea7ac 100644 --- a/atom/browser/api/lib/auto-updater.coffee +++ b/atom/browser/api/lib/auto-updater.coffee @@ -1,4 +1,9 @@ -autoUpdater = process.atomBinding('auto_updater').autoUpdater +switch process.platform + when 'darwin' + autoUpdater = process.atomBinding('auto_updater').autoUpdater + when 'win32' + autoUpdater = require('./auto-updater-win') + EventEmitter = require('events').EventEmitter autoUpdater.__proto__ = EventEmitter.prototype diff --git a/atom/browser/api/lib/auto-updater/auto-updater-win.coffee b/atom/browser/api/lib/auto-updater/auto-updater-win.coffee new file mode 100644 index 0000000000..043d4c7c41 --- /dev/null +++ b/atom/browser/api/lib/auto-updater/auto-updater-win.coffee @@ -0,0 +1,60 @@ +{EventEmitter} = require 'events' +SquirrelUpdate = require './auto-updater/squirrel-update-win' + +class AutoUpdater + @__proto__ = EventEmitter.prototype + + quitAndInstall: -> + # TODO + + setFeedUrl: (@updateUrl) -> + + checkForUpdates: -> + throw new Error('Update URL is not set') unless @updateUrl + + @emit 'checking-for-update' + + unless SquirrelUpdate.existsSync() + @emit 'update-not-available' + return + + @downloadUpdate (error, update) => + if error? + @emit 'update-not-available' + return + + unless update? + @emit 'update-not-available' + return + + @emit 'update-available' + + @installUpdate (error) => + if error? + @emit 'update-not-available' + return + + @emit 'update-downloaded', {}, update.releaseNotes, update.version, new Date(), @updateUrl, => @quitAndInstall() + + downloadUpdate: (callback) -> + SquirrelUpdate.spawn ['--download', @updateUrl], (error, stdout) -> + return callback(error) if error? + + try + # Last line of output is the JSON details about the releases + json = stdout.trim().split('\n').pop() + update = JSON.parse(json)?.releasesToApply?.pop?() + catch error + error.stdout = stdout + return callback(error) + + callback(null, update) + + installUpdate: (callback) -> + SquirrelUpdate.spawn(['--update', @updateUrl], callback) + + supportsUpdates: -> + SquirrelUpdate.existsSync() + + +module.exports = new AutoUpdater() diff --git a/atom/browser/api/lib/auto-updater/squirrel-update-win.coffee b/atom/browser/api/lib/auto-updater/squirrel-update-win.coffee new file mode 100644 index 0000000000..faaa907f67 --- /dev/null +++ b/atom/browser/api/lib/auto-updater/squirrel-update-win.coffee @@ -0,0 +1,42 @@ +ChildProcess = require 'child_process' +fs = require 'fs' +path = require 'path' + +appFolder = path.resolve(process.execPath, '..') # i.e. my-app/app-0.1.13/ +rootApplicationFolder = path.resolve(appFolder, '..') # i.e. my-app/ +updateDotExe = path.join(rootApplicationFolder, 'Update.exe') +exeName = path.basename(process.execPath) + +# Spawn a command and invoke the callback when it completes with an error +# and the output from standard out. +spawn = (command, args, callback) -> + stdout = '' + + try + spawnedProcess = ChildProcess.spawn(command, args) + catch error + # Spawn can throw an error + process.nextTick -> callback?(error, stdout) + return + + spawnedProcess.stdout.on 'data', (data) -> stdout += data + + error = null + spawnedProcess.on 'error', (processError) -> error ?= processError + spawnedProcess.on 'close', (code, signal) -> + error ?= new Error("Command failed: #{signal ? code}") if code isnt 0 + error?.code ?= code + error?.stdout ?= stdout + callback?(error, stdout) + +# Spawn the Update.exe with the given arguments and invoke the callback when +# the command completes. +spawnUpdate = (args, callback) -> + spawn(updateDotExe, args, callback) + +# Exports +exports.spawn = spawnUpdate + +# Is the Update.exe installed with Atom? +exports.existsSync = -> + fs.accessSync(updateDotExe, fs.R_OK) From 99f352228c62100069df90146536faea82edd981 Mon Sep 17 00:00:00 2001 From: Patrick Detlefsen Date: Tue, 16 Jun 2015 11:04:37 +0200 Subject: [PATCH 002/146] addresses suggestions from pull-request --- atom/browser/api/lib/auto-updater.coffee | 7 ++++--- atom/browser/api/lib/auto-updater/auto-updater-win.coffee | 5 ++++- .../api/lib/auto-updater/squirrel-update-win.coffee | 4 ++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/atom/browser/api/lib/auto-updater.coffee b/atom/browser/api/lib/auto-updater.coffee index 7d7feea7ac..0b7e84ba7e 100644 --- a/atom/browser/api/lib/auto-updater.coffee +++ b/atom/browser/api/lib/auto-updater.coffee @@ -1,8 +1,9 @@ switch process.platform - when 'darwin' - autoUpdater = process.atomBinding('auto_updater').autoUpdater when 'win32' - autoUpdater = require('./auto-updater-win') + autoUpdater = require('./auto-updater/auto-updater-win') + default + # take the default binding for the current platform + autoUpdater = process.atomBinding('auto_updater').autoUpdater EventEmitter = require('events').EventEmitter diff --git a/atom/browser/api/lib/auto-updater/auto-updater-win.coffee b/atom/browser/api/lib/auto-updater/auto-updater-win.coffee index 043d4c7c41..556b86cbc9 100644 --- a/atom/browser/api/lib/auto-updater/auto-updater-win.coffee +++ b/atom/browser/api/lib/auto-updater/auto-updater-win.coffee @@ -7,7 +7,10 @@ class AutoUpdater quitAndInstall: -> # TODO - setFeedUrl: (@updateUrl) -> + setFeedUrl: (updateUrl) -> + # set feed URL only when it hasn't been set before + unless @updateUrl + @updateUrl = updateUrl checkForUpdates: -> throw new Error('Update URL is not set') unless @updateUrl diff --git a/atom/browser/api/lib/auto-updater/squirrel-update-win.coffee b/atom/browser/api/lib/auto-updater/squirrel-update-win.coffee index faaa907f67..d57a013078 100644 --- a/atom/browser/api/lib/auto-updater/squirrel-update-win.coffee +++ b/atom/browser/api/lib/auto-updater/squirrel-update-win.coffee @@ -2,8 +2,8 @@ ChildProcess = require 'child_process' fs = require 'fs' path = require 'path' -appFolder = path.resolve(process.execPath, '..') # i.e. my-app/app-0.1.13/ -rootApplicationFolder = path.resolve(appFolder, '..') # i.e. my-app/ +appFolder = path.dirname(process.execPath) # i.e. my-app/app-0.1.13/ +rootApplicationFolder = path.resolve(appFolder, '..') # i.e. my-app/ updateDotExe = path.join(rootApplicationFolder, 'Update.exe') exeName = path.basename(process.execPath) From 7aa60baafb0749075ac33ff77dc183f2e0bc91cc Mon Sep 17 00:00:00 2001 From: Patrick Detlefsen Date: Tue, 16 Jun 2015 11:48:39 +0200 Subject: [PATCH 003/146] switch statement coffee else instead of default --- atom/browser/api/lib/auto-updater.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom/browser/api/lib/auto-updater.coffee b/atom/browser/api/lib/auto-updater.coffee index 0b7e84ba7e..1b6c935f34 100644 --- a/atom/browser/api/lib/auto-updater.coffee +++ b/atom/browser/api/lib/auto-updater.coffee @@ -1,7 +1,7 @@ switch process.platform when 'win32' autoUpdater = require('./auto-updater/auto-updater-win') - default + else # take the default binding for the current platform autoUpdater = process.atomBinding('auto_updater').autoUpdater From a8469fc79d4f5c87c7d6f2b1e1b877fccd9c2500 Mon Sep 17 00:00:00 2001 From: Patrick Detlefsen Date: Tue, 16 Jun 2015 12:31:55 +0200 Subject: [PATCH 004/146] auto-updater extends event-emitter --- atom/browser/api/lib/auto-updater/auto-updater-win.coffee | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/atom/browser/api/lib/auto-updater/auto-updater-win.coffee b/atom/browser/api/lib/auto-updater/auto-updater-win.coffee index 556b86cbc9..1f9fcd185f 100644 --- a/atom/browser/api/lib/auto-updater/auto-updater-win.coffee +++ b/atom/browser/api/lib/auto-updater/auto-updater-win.coffee @@ -1,8 +1,7 @@ {EventEmitter} = require 'events' SquirrelUpdate = require './auto-updater/squirrel-update-win' -class AutoUpdater - @__proto__ = EventEmitter.prototype +class AutoUpdater extends EventEmitter quitAndInstall: -> # TODO From 62882fe49e6910710b17cb186192fdf67df5544d Mon Sep 17 00:00:00 2001 From: Patrick Detlefsen Date: Tue, 16 Jun 2015 16:13:23 +0200 Subject: [PATCH 005/146] auto updater win is an EventEmitter already --- atom/browser/api/lib/auto-updater.coffee | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/atom/browser/api/lib/auto-updater.coffee b/atom/browser/api/lib/auto-updater.coffee index 1b6c935f34..a479f5fccf 100644 --- a/atom/browser/api/lib/auto-updater.coffee +++ b/atom/browser/api/lib/auto-updater.coffee @@ -3,11 +3,9 @@ switch process.platform autoUpdater = require('./auto-updater/auto-updater-win') else # take the default binding for the current platform - autoUpdater = process.atomBinding('auto_updater').autoUpdater - -EventEmitter = require('events').EventEmitter - -autoUpdater.__proto__ = EventEmitter.prototype + autoUpdater = process.atomBinding('auto_updater').autoUpdater + EventEmitter = require('events').EventEmitter + autoUpdater.__proto__ = EventEmitter.prototype autoUpdater.on 'update-downloaded-raw', (args...) -> args[3] = new Date(args[3]) # releaseDate From ae5411c37b9c712e0a6b973eaf729753cd21e7c1 Mon Sep 17 00:00:00 2001 From: Patrick Detlefsen Date: Mon, 22 Jun 2015 15:16:50 +0200 Subject: [PATCH 006/146] move squirrel bahaviour into proper place --- .../lib/auto-updater/auto-updater-win.coffee | 23 ++++-------- .../auto-updater/squirrel-update-win.coffee | 35 +++++++++++++------ 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/atom/browser/api/lib/auto-updater/auto-updater-win.coffee b/atom/browser/api/lib/auto-updater/auto-updater-win.coffee index 1f9fcd185f..a3fbd8822a 100644 --- a/atom/browser/api/lib/auto-updater/auto-updater-win.coffee +++ b/atom/browser/api/lib/auto-updater/auto-updater-win.coffee @@ -1,10 +1,12 @@ {EventEmitter} = require 'events' SquirrelUpdate = require './auto-updater/squirrel-update-win' +app = require('app') class AutoUpdater extends EventEmitter quitAndInstall: -> - # TODO + SquirrelUpdate.processStart -> + app.quit() setFeedUrl: (updateUrl) -> # set feed URL only when it hasn't been set before @@ -36,27 +38,16 @@ class AutoUpdater extends EventEmitter @emit 'update-not-available' return + # info about the newly installed version and a function any of the event listeners can call to restart the application @emit 'update-downloaded', {}, update.releaseNotes, update.version, new Date(), @updateUrl, => @quitAndInstall() downloadUpdate: (callback) -> - SquirrelUpdate.spawn ['--download', @updateUrl], (error, stdout) -> - return callback(error) if error? - - try - # Last line of output is the JSON details about the releases - json = stdout.trim().split('\n').pop() - update = JSON.parse(json)?.releasesToApply?.pop?() - catch error - error.stdout = stdout - return callback(error) - - callback(null, update) + SquirrelUpdate.download(callback) installUpdate: (callback) -> - SquirrelUpdate.spawn(['--update', @updateUrl], callback) + SquirrelUpdate.update(@updateUrl, callback) supportsUpdates: -> - SquirrelUpdate.existsSync() - + SquirrelUpdate.supported() module.exports = new AutoUpdater() diff --git a/atom/browser/api/lib/auto-updater/squirrel-update-win.coffee b/atom/browser/api/lib/auto-updater/squirrel-update-win.coffee index d57a013078..f07584f4c5 100644 --- a/atom/browser/api/lib/auto-updater/squirrel-update-win.coffee +++ b/atom/browser/api/lib/auto-updater/squirrel-update-win.coffee @@ -9,11 +9,11 @@ exeName = path.basename(process.execPath) # Spawn a command and invoke the callback when it completes with an error # and the output from standard out. -spawn = (command, args, callback) -> +spawnUpdate = (args, callback) -> stdout = '' try - spawnedProcess = ChildProcess.spawn(command, args) + spawnedProcess = ChildProcess.spawn(updateDotExe, args) catch error # Spawn can throw an error process.nextTick -> callback?(error, stdout) @@ -29,14 +29,29 @@ spawn = (command, args, callback) -> error?.stdout ?= stdout callback?(error, stdout) -# Spawn the Update.exe with the given arguments and invoke the callback when -# the command completes. -spawnUpdate = (args, callback) -> - spawn(updateDotExe, args, callback) +processStart = (callback) -> + spawnUpdate(['--processStart', exeName], callback) -# Exports -exports.spawn = spawnUpdate +download = (callback) -> + spawnUpdate ['--download', @updateUrl], (error, stdout) -> + return callback(error) if error? -# Is the Update.exe installed with Atom? -exports.existsSync = -> + try + # Last line of output is the JSON details about the releases + json = stdout.trim().split('\n').pop() + update = JSON.parse(json)?.releasesToApply?.pop?() + catch error + error.stdout = stdout + return callback(error) + + callback(null, update) + +update = (updateUrl, callback) -> + spawnUpdate ['--update', updateUrl], callback + +# Is the Update.exe installed with the current application? +exports.supported = -> fs.accessSync(updateDotExe, fs.R_OK) +exports.processStart = processStart +exports.download = download +exports.update = update From 154ca8575ca450405307958004e5c862a7cdda10 Mon Sep 17 00:00:00 2001 From: Patrick Detlefsen Date: Mon, 22 Jun 2015 15:33:08 +0200 Subject: [PATCH 007/146] limit the auto-updater-win api to very few methods --- .../api/lib/auto-updater/auto-updater-win.coffee | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/atom/browser/api/lib/auto-updater/auto-updater-win.coffee b/atom/browser/api/lib/auto-updater/auto-updater-win.coffee index a3fbd8822a..2da37ddb03 100644 --- a/atom/browser/api/lib/auto-updater/auto-updater-win.coffee +++ b/atom/browser/api/lib/auto-updater/auto-updater-win.coffee @@ -18,11 +18,11 @@ class AutoUpdater extends EventEmitter @emit 'checking-for-update' - unless SquirrelUpdate.existsSync() + unless SquirrelUpdate.supported() @emit 'update-not-available' return - @downloadUpdate (error, update) => + SquirrelUpdate.download (error, update) => if error? @emit 'update-not-available' return @@ -33,7 +33,7 @@ class AutoUpdater extends EventEmitter @emit 'update-available' - @installUpdate (error) => + SquirrelUpdate.update @updateUrl, (error) => if error? @emit 'update-not-available' return @@ -41,13 +41,4 @@ class AutoUpdater extends EventEmitter # info about the newly installed version and a function any of the event listeners can call to restart the application @emit 'update-downloaded', {}, update.releaseNotes, update.version, new Date(), @updateUrl, => @quitAndInstall() - downloadUpdate: (callback) -> - SquirrelUpdate.download(callback) - - installUpdate: (callback) -> - SquirrelUpdate.update(@updateUrl, callback) - - supportsUpdates: -> - SquirrelUpdate.supported() - module.exports = new AutoUpdater() From 6c4016af462d9f449b7f823ba80eb19f4511ef5e Mon Sep 17 00:00:00 2001 From: Patrick Detlefsen Date: Mon, 22 Jun 2015 15:51:47 +0200 Subject: [PATCH 008/146] make sure the query params are stripped from the updateUrl --- .../api/lib/auto-updater/auto-updater-win.coffee | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/atom/browser/api/lib/auto-updater/auto-updater-win.coffee b/atom/browser/api/lib/auto-updater/auto-updater-win.coffee index 2da37ddb03..9a2431577e 100644 --- a/atom/browser/api/lib/auto-updater/auto-updater-win.coffee +++ b/atom/browser/api/lib/auto-updater/auto-updater-win.coffee @@ -1,6 +1,7 @@ {EventEmitter} = require 'events' SquirrelUpdate = require './auto-updater/squirrel-update-win' app = require('app') +url = require('url') class AutoUpdater extends EventEmitter @@ -11,7 +12,14 @@ class AutoUpdater extends EventEmitter setFeedUrl: (updateUrl) -> # set feed URL only when it hasn't been set before unless @updateUrl - @updateUrl = updateUrl + # See https://github.com/Squirrel/Squirrel.Windows/issues/132 + # This way the Mac and Windows Update URL can be the same, even when + # the Mac version is sending additional data in the query string. + parsedUrl = url.parse(updateUrl) + delete parsedUrl.search + delete parsedUrl.query + + @updateUrl = url.format(parsedUrl) checkForUpdates: -> throw new Error('Update URL is not set') unless @updateUrl From af8049941e434332b149d9bf4d942d7748ed6e7a Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Tue, 13 Oct 2015 20:53:04 +0900 Subject: [PATCH 009/146] Update as upstream --- .../ko-KR/tutorial/desktop-environment-integration.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs-translations/ko-KR/tutorial/desktop-environment-integration.md b/docs-translations/ko-KR/tutorial/desktop-environment-integration.md index 94f0ffa013..8e73d5940f 100644 --- a/docs-translations/ko-KR/tutorial/desktop-environment-integration.md +++ b/docs-translations/ko-KR/tutorial/desktop-environment-integration.md @@ -194,10 +194,10 @@ var window = new BrowserWindow({...}); window.setProgressBar(0.5); ``` -## 윈도우 대표 파일 제시 (OS X) +## 대표 파일 제시 (OS X) -OS X는 윈도우에서 대표 파일을 설정할 수 있습니다. 쉽게 말해 타이틀바에서 파일 아이콘을 볼 수 있을 때 사용자가 Command-Click 또는 Control-Click 할 경우 파일 경로 팝업이 보여집니다. -또한 윈도우의 상태도 지정할 수 있습니다. 쉽게 말해 로드된 문서의 수정여부를 타이틀바 파일 아이콘에 표시할 수 있습니다. +OS X는 창에서 대표 파일을 설정할 수 있습니다. 타이틀바에서 파일 아이콘이 있고, 사용자가 Command-Click 또는 Control-Click 키를 누를 경우 파일 경로 팝업이 보여집니다. +또한 창의 상태도 지정할 수 있습니다. 쉽게 말해 로드된 문서의 수정여부를 타이틀바 파일 아이콘에 표시할 수 있습니다. __대표 파일 팝업 메뉴:__ From 79311af8eb6fc1d6764751fde572ac566c140988 Mon Sep 17 00:00:00 2001 From: Yegun Kim Date: Thu, 15 Oct 2015 16:26:24 -0500 Subject: [PATCH 010/146] translate "/docs-translations/ko-KR/api/app.md" --- docs-translations/ko-KR/api/app.md | 342 +++++++++++++++++++++++++++++ 1 file changed, 342 insertions(+) create mode 100644 docs-translations/ko-KR/api/app.md diff --git a/docs-translations/ko-KR/api/app.md b/docs-translations/ko-KR/api/app.md new file mode 100644 index 0000000000..2fa04368ec --- /dev/null +++ b/docs-translations/ko-KR/api/app.md @@ -0,0 +1,342 @@ +# app + +앱 모듈은 어플리케이션의 생명주기(?) 제어를 책임집니다. + +밑의 예제는 마지막 윈도우가 종료되었을 때, 어떻게 어플리케이션을 종료하는지 설명합니다. + +```javascript +var app = require('app'); +app.on('window-all-closed', function() { + app.quit(); +}); +``` + +## Events + +앱 객체는 밑의 이벤트들을 발현(?)시킵니다. + +### Event: 'will-finish-launching' + +'will-finish-launching' 이벤트는 어플리케이션이 기본적인 시동준비(?)를 마치면 발현(?)됩니다. + 윈도우 운영체제와 리눅스 운영체제 안에서, 'will-finish-launching' 이벤트는 'ready' 이벤트와 동일합니다. +OS X 운영체제 안에서는, 이 이벤트는 'NSApplication'의 'applicationWillFinishLaunching' 알림으로 표현됩니다. +유저는 대개 'open-file'과 'open-url' 이벤트를 위한 listeners을 세팅합니다. +그리고, crash reporter와 auto updater를 시작합니다. + +대부분의 경우, 유저는 모든 것을 'ready' 이벤트 handler로 해결합니다. + +### Event: 'ready' + +Electron이 초기화를 끝냈을 때, 이벤트가 발현합니다. + +### Event: 'window-all-closed' + +모든 윈도우창이 종료되었을 때, 이벤트가 발현합니다. + +이 이벤트는 어플리케이션이 정상 종료되지 않았을 때만 발현됩니다. +만약 유저가 'Cmd + Q' 또는 개발자가 'app.quit()'를 호출했다면, Electron은 먼저 모든 윈도우창 종료를 시도합니다. +그 다음에, 'will-quit' 이벤트를 발현시킵니다. +그리고, 'will-quit'가 발현된 경우에는 'window-all-closed' 이벤트는 발현되지 않습니다. + +### Event: 'before-quit' + +Returns: + +* `event` Event + +어플리케이션이 어플리케이션의 윈도우 종료를 시작하기 전에, 발현됩니다. +'event.preventDefault()' 호출은 어플리케이션의 강제종료 행위를 방지합니다. + +### Event: 'will-quit' + +Returns: + +* `event` Event + +모든 윈도우창이 종료되고 어플리케이션이 종료하고자 할 때, 이벤트가 발현됩니다. +'event.preventDefault()'의 호출은 어플리케이션 종료를 방지합니다. +'will-quit' 이벤트와 'window-all-closed' 이벤트와의 차이점을 확인하려면, +'window-all-close' 이벤트의 설명을 참조합시다. + +### Event: 'quit' + +어플리케이션이 종료될 때, 발현됩니다. + +### Event: 'open-file' + +Returns: + +* `event` Event +* `path` String + +어플리케이션을 이용하여 파일을 열고자 할 때, 발현됩니다. + +'open-file' 이벤트는 보통 어플리케이션이 열려 있을 때와, +파일을 열기 위해 OS가 어플리케이션을 재사용할 때 발현됩니다. +'open-file'은 파일이 dock에 추가될 때와 어플리케이션이 실행하기 전에도 발현됩니다. +위의 케이스를 처리하기 위해서는, 'ready' 이벤트가 발현되기도 전에, 어플리케이션 시작 초기 시 +'open-file' 이벤트에 linsten을 걸어 놨는지 반드시 확인해야 합니다. + +이 이벤트를 처리하기 위한다면, 유저는 'even.preventDefault()'를 호출해야 합니다. + +### Event: 'open-url' + +Returns: + +* `event` Event +* `url` String + +유저가 어플리케이션을 이용하여 URL을 열고자 할 경우, 발현됩니다. +어플리케이션을 통해 열기 위해서는 URL 구조가 반드시 등록되어 있어야 합니다. +이 이벤트를 처리하기 위해선, 유저가 'event.preventDefault()'을 호출해야 합니다. + +### Event: 'activate' _OS X_ + +Returns: + +* `event` Event +* `hasVisibleWindows` Bool + +어플리케이션이 활성화 되었을 때 발현됩니다. +(어플리케이션은 dock 아이콘을 click했을 때 주로 활성화됩니다.) + +### Event: 'browser-window-blur' + +Returns: + +* `event` Event +* `window` BrowserWindow + +[browserWindow](browser-window.md)가 흐려졌을 때, 호출됩니다. + +### Event: 'browser-window-focus' + +Returns: + +* `event` Event +* `window` BrowserWindow + +[browserWindow](browser-window.md)을 포커싱했을 때, 호출됩니다. +(포커싱이란? 클릭 또는 활성화했을 때를 의미) + +### Event: 'browser-window-created' + +Returns: + +* `event` Event +* `window` BrowserWindow + +새로운 [browserWindow](browser-window.md)가 생성될 때 발현됩니다. + +### Event: 'select-certificate' + +유저인증이 요청되었을 때 발현됩니다. + +Returns: + +* `event` Event +* `webContents` [WebContents](browser-window.md#class-webcontents) +* `url` String +* `certificateList` [Objects] + * `data` PEM encoded data + * `issuerName` Issuer's Common Name +* `callback` Function + +```javascript +app.on('select-certificate', function(event, host, url, list, callback) { + event.preventDefault(); + callback(list[0]); +}) +``` + +'url' 유저인증 요청의 탐색 항목에 대응합니다. +그리고, 'callback' 는 리스트로 필터링된 항목과 함께 호출될 필요가 있습니다. + +'event.preventDefault()' 호출은 기록소로부터 처음인증된 정보를 사용하는 +어플리케이션을 막습니다. + +### Event: 'gpu-process-crashed' + +GPU가 충돌을 일으켰을 때, 발현됩니다. + +## Methods + +'app' 객체는 밑의 함수를 포함하고 있습니다: + +**Note:** 어떤 함수들은 표시된 특정한 운영체제에서만 사용가능합니다. + +### `app.quit()` + +모든 윈도우창 종료를 시도합니다. 'before-quit' 이벤트가 먼저 발현됩니다. +모든 윈도우창이 성공적으로 종료되었다면, 'will-quit' 이벤트가 발현하고, +디폴트설정으로 어플리케이션이 종료됩니다. + +이 함수는 모든 'beforeunload'과 'unload' 이벤트 처리기가 제대로 실행되었음을 보장합니다. +(즉, 'beforeunload'와 'unload'가 정상 실행되었을 때, 실행가능합니다.) + +'beforeunload' 이벤트 처리기가 'false'를 반환하였을 경우, 윈도우 창 종료가 취소 될 수 있습니다. + + +### `app.getAppPath()` + +현재 어플리케이션의 디렉토리를 반환합니다. + +### `app.getPath(name)` + +* `name` String + +특정한 디렉토리의 경로나 'name' 파일의 경로를 찾아 반환합니다. +실패할 경우, 'Error'를 반환합니다. + +유저는 다음과 같은 이름으로 경로를 요쳥할 수 있습니다: + +* `home` = 유저의 홈 디렉토리. +* `appData` = 유저의 어플리케이션 데이터 디렉토리, 디폴트설정으로: + * `%APPDATA%` = on Windows + * `$XDG_CONFIG_HOME` 또는 `~/.config` = on Linux + * `~/Library/Application Support` = on OS X +* `userData` = 유저 app의 설정파일을 저장하는 디렉토리, +디폴트설정으로 'appData' 디렉토리에 유저 app의 이름을 추가한 형식. +* `temp` = 임시 디렉토리. +* `userDesktop` = 현재 로그인한 유저의 데스트탑 디렉토리. +* `exe` = 현재 실행가능한 파일. +* `module` = `libchromiumcontent` 라이브러리. + +### `app.setPath(name, path)` + +* `name` String +* `path` String + +특정한 디렉토리나 파일이름이 'name'인 'path'를 재정의합니다. +만약 지정된 디렉토리의 경로가 존재하지 않는다면, 디렉토리가 새로 생성됩니다. +실패시, 'Error'를 반환합니다. + +유저는 'app.getPath'에 정의되어 있는 'name' 경로만 재정의할 수 있습니다. + +디폴트설정으로, 웹페이지의 쿠키와 캐시는 'userData' 디렉토리 밑에 저장됩니다. + +만약 유저가 이 위치를 변경하고자 한다면, 'app' 모듈의 'ready' 이벤트가 발현되기 전, +유저는 반드시 'userData' 경로를 재정의해야 합니다. + +### `app.getVersion()` + +로드된 어플리케이션의 버전을 반환합니다. + +만약 'package.json' 파일에서 어플리케이션의 버전을 찾지 못한다면, +현재 번들 또는 실행 파일의 버전이 반환됩니다. + +### `app.getName()` + +응용프로그램(=어플리케이션)의 'package.json' 파일에 있는 +현재 응용프로그램의 이름을 반환합니다. + +NPM 모듈 스펙에 따라, 대부분 'package.json'의 'name' 필드는 소문자 이름입니다. +유저는 'productName'의 필드를 지정해야 합니다. +'productName'는 응용프로그램의 대문자 이름입니다. +'productName'는 Electron에서 선호하는 'name' 입니다. + +### `app.getLocale()` + +현재 응용프로그램의 locale을 반환합니다. + +### `app.resolveProxy(url, callback)` + +* `url` URL +* `callback` Function + +'url'을 위해 프록시 정보를 해석합니다. +'callback'은 'callback(proxy)' 요청이 수행될 때, +호출됩니다. + +### `app.addRecentDocument(path)` + +* `path` String + +최근 문서 목록에 'path'를 추가합니다. + +목록은 운영체제에 의해 관리됩니다. +Windows 운영체제에서는, 작업표시줄에서 참조할 수 있습니다. +OS X 운영체제에서는, dock 메뉴에서 참조할 수 있습니다. + +### `app.clearRecentDocuments()` + +최근 문서 목록을 모두 지웁니다. + +### `app.setUserTasks(tasks)` _Windows_ + +* `tasks` Array - 'Task' 객체의 배열 + +Windows 운영체제에서 JumpList의 [Tasks][tasks] 카테고리로 'tasks'를 추가합니다. + +'tasks'는 밑의 형식을 따르는 'Task' 객체의 배열입니다: + +`Task` Object +* `program` String - 프로그램을 실행할 경로, 대부분 유저는 현재 프로그램을 여는 'process.execPath'를 지정합니다. +* `arguments` String - 'program'이 실행될 때의 명령문 인자. +* `title` String - JumpList에 표시할 문장. +* `description` String - 이 task 객체에 대한 설명. +* `iconPath` String - 아이콘을 포함한 임의의 리소스 파일이며, JumpList에 표시될 아이콘의 절대 경로. +유저는 대부분 프로그램의 아이콘을 표시하기 위해 'process.execPath'를 지정합니다. +* `iconIndex` Integer - 아이콘 파일안의 아이콘 색인(index). 만약, 아이콘 파일 안에 두 개 이상의 아이콘이 있을 경우, + 아이콘을 특정하기 위해 이 값을 설정합니다. 단, 하나의 아이콘만 있을 경우는 0 값을 가집니다. + +### `app.commandLine.appendSwitch(switch[, value])` + +Append a switch (with optional `value`) to Chromium's command line. +Chromium의 명령문에 (선택적으로, 'value'와 함께) switch를 추가합니다. + +**Note:** 이 함수는 'process.argv'에 영향을 주지 않습니다. +그리고, 보통 이 함수는 개발자들이 하드웨어 수준의 Chrominum의 행동을 제어하기 위해 주로 사용됩니다. + +### `app.commandLine.appendArgument(value)` + +Chromium의 명령문에 인자를 추가합니다. 인자는 올바르게 인용될 것입니다. + +**Note:** 'process.argv'에 영향을 주지는 않습니다. + +### `app.dock.bounce([type])` _OS X_ + +* `type` String (optional) - 'critical'이나 'informational'. 디폴트 설정은 'informational' + +'critical'이 input으로 전달되면, dock 아이콘은 응용프로그램이 활성화되거나, 요청이 취소될 때까지 +계속 bounce합니다. + +'informational'이 input으로 전달되면, dock 아이콘은 1초만 bounce합니다. +그러나, 응용프로그램이 활성화되거나 요청이 취소될 때까지 요청은 계속 활성화되어 있습니다. + +요청을 대표하는 ID를 반환합니다. + +### `app.dock.cancelBounce(id)` _OS X_ + +* `id` Integer + +'id'의 반송을 취소합니다. + + +### `app.dock.setBadge(text)` _OS X_ + +* `text` String + +dock의 badge 구역에 표현될 문장을 설정합니다. + +### `app.dock.getBadge()` _OS X_ + +dock의 badge 라인을 반환합니다. + + +### `app.dock.hide()` _OS X_ + +dock 아이콘을 숨깁니다. + +### `app.dock.show()` _OS X_ + +dock 아이콘을 보여줍니다. + +### `app.dock.setMenu(menu)` _OS X_ + +* `menu` Menu + +어플리케이션의 [dock menu][dock-menu]을 결정합니다. + +[dock-menu]:https://developer.apple.com/library/mac/documentation/Carbon/Conceptual/customizing_docktile/concepts/dockconcepts.html#//apple_ref/doc/uid/TP30000986-CH2-TPXREF103 +[tasks]:http://msdn.microsoft.com/en-us/library/windows/desktop/dd378460(v=vs.85).aspx#tasks From f05fc3c0ea5f12a0eb764f17f32443de74aa5e5d Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Fri, 16 Oct 2015 22:23:31 +0900 Subject: [PATCH 011/146] Improve grammar, some fixes * Improve grammar * Fix typos * Fix some mistranslations * Fix quotes (' to `) --- docs-translations/ko-KR/api/app.md | 248 ++++++++++++++--------------- 1 file changed, 121 insertions(+), 127 deletions(-) diff --git a/docs-translations/ko-KR/api/app.md b/docs-translations/ko-KR/api/app.md index 2fa04368ec..18354a70bd 100644 --- a/docs-translations/ko-KR/api/app.md +++ b/docs-translations/ko-KR/api/app.md @@ -1,8 +1,8 @@ # app -앱 모듈은 어플리케이션의 생명주기(?) 제어를 책임집니다. +`app` 모듈은 어플리케이션의 생명주기 제어를 책임집니다. -밑의 예제는 마지막 윈도우가 종료되었을 때, 어떻게 어플리케이션을 종료하는지 설명합니다. +밑의 예제는 마지막 윈도우창가 종료되었을 때, 어플리케이션을 종료시키는 예제입니다: ```javascript var app = require('app'); @@ -13,30 +13,32 @@ app.on('window-all-closed', function() { ## Events -앱 객체는 밑의 이벤트들을 발현(?)시킵니다. +`app` 객체는 다음과 같은 이벤트를 가지고 있습니다: ### Event: 'will-finish-launching' -'will-finish-launching' 이벤트는 어플리케이션이 기본적인 시동준비(?)를 마치면 발현(?)됩니다. - 윈도우 운영체제와 리눅스 운영체제 안에서, 'will-finish-launching' 이벤트는 'ready' 이벤트와 동일합니다. -OS X 운영체제 안에서는, 이 이벤트는 'NSApplication'의 'applicationWillFinishLaunching' 알림으로 표현됩니다. -유저는 대개 'open-file'과 'open-url' 이벤트를 위한 listeners을 세팅합니다. -그리고, crash reporter와 auto updater를 시작합니다. +어플리케이션이 기본적인 시작 준비를 마치면 발생하는 이벤트입니다. +Windows, Linux 운영체제에서의 `will-finish-launching` 이벤트는 `ready` 이벤트와 동일합니다. +OS X에서의 이벤트는 `NSApplication`의 `applicationWillFinishLaunching`에 대한 알림으로 표현됩니다. +대개 이곳에서 `open-file`과 `open-url` 이벤트 리스너를 설정하고 crash reporter와 auto updater를 시작합니다. -대부분의 경우, 유저는 모든 것을 'ready' 이벤트 handler로 해결합니다. +대부분의 경우, 모든 것을 `ready` 이벤트 핸들러로 해결해야 합니다. ### Event: 'ready' -Electron이 초기화를 끝냈을 때, 이벤트가 발현합니다. +Electron이 초기화를 끝냈을 때 발생하는 이벤트입니다. ### Event: 'window-all-closed' -모든 윈도우창이 종료되었을 때, 이벤트가 발현합니다. +모든 윈도우창이 종료되었을 때 발생하는 이벤트입니다. -이 이벤트는 어플리케이션이 정상 종료되지 않았을 때만 발현됩니다. -만약 유저가 'Cmd + Q' 또는 개발자가 'app.quit()'를 호출했다면, Electron은 먼저 모든 윈도우창 종료를 시도합니다. -그 다음에, 'will-quit' 이벤트를 발현시킵니다. -그리고, 'will-quit'가 발현된 경우에는 'window-all-closed' 이벤트는 발현되지 않습니다. +이 이벤트는 어플리케이션이 완전히 종료되지 않았을 때만 발생합니다. +만약 사용자가 `Cmd + Q`를 입력했거나 개발자가 `app.quit()`를 호출했다면, +Electron은 먼저 모든 윈도우창의 종료를 시도하고 `will-quit` 이벤트를 발생시킵니다. +그리고 `will-quit` 이벤트가 발생했을 땐 `window-all-closed` 이벤트가 발생하지 않습니다. + +**역주:** 이 이벤트는 말 그대로 현재 어플리케이션에서 윈도우창만 완전히 종료됬을 때 발생하는 이벤트 입니다. + 따라서 어플리케이션을 완전히 종료하려면 이 이벤트에서 `app.quit()`를 호출해 주어야 합니다. ### Event: 'before-quit' @@ -44,8 +46,9 @@ Returns: * `event` Event -어플리케이션이 어플리케이션의 윈도우 종료를 시작하기 전에, 발현됩니다. -'event.preventDefault()' 호출은 어플리케이션의 강제종료 행위를 방지합니다. +어플리케이션 윈도우창들이 닫히기 시작할 때 발생하는 이벤트입니다. +`event.preventDefault()` 호출은 이벤트의 기본 동작을 방지하기 때문에 +이를 통해 어플리케이션의 종료를 방지할 수 있습니다. ### Event: 'will-quit' @@ -53,14 +56,14 @@ Returns: * `event` Event -모든 윈도우창이 종료되고 어플리케이션이 종료하고자 할 때, 이벤트가 발현됩니다. -'event.preventDefault()'의 호출은 어플리케이션 종료를 방지합니다. -'will-quit' 이벤트와 'window-all-closed' 이벤트와의 차이점을 확인하려면, -'window-all-close' 이벤트의 설명을 참조합시다. +모든 윈도우창들이 종료되고 어플리케이션이 종료되기 시작할 때 발생하는 이벤트 입니다. +`event.preventDefault()` 호출을 통해 어플리케이션의 종료를 방지할 수 있습니다. + +`will-quit` 와 `window-all-closed` 이벤트의 차이점을 확인하려면 `window-all-close` 이벤트의 설명을 참고하세요. ### Event: 'quit' -어플리케이션이 종료될 때, 발현됩니다. +어플리케이션이 종료될 때 발생하는 이벤트입니다. ### Event: 'open-file' @@ -69,15 +72,13 @@ Returns: * `event` Event * `path` String -어플리케이션을 이용하여 파일을 열고자 할 때, 발현됩니다. +사용자가 어플리케이션을 통해 파일을 열고자 할 때 발생하는 이벤트입니다. -'open-file' 이벤트는 보통 어플리케이션이 열려 있을 때와, -파일을 열기 위해 OS가 어플리케이션을 재사용할 때 발현됩니다. -'open-file'은 파일이 dock에 추가될 때와 어플리케이션이 실행하기 전에도 발현됩니다. -위의 케이스를 처리하기 위해서는, 'ready' 이벤트가 발현되기도 전에, 어플리케이션 시작 초기 시 -'open-file' 이벤트에 linsten을 걸어 놨는지 반드시 확인해야 합니다. +`open-file` 이벤트는 보통 어플리케이션이 열려 있을 때와 OS가 파일을 열기 위해 어플리케이션을 재사용할 때 발생합니다. +`open-file` 이벤트는 파일을 dock에 떨어트릴 때와 어플리케이션이 실행되기 전에도 발생합니다. +이 경우엔 확실히 `open-file` 이벤트 리스너를 어플리케이션이 시작하기 전에 설정해 놓았는지 확인해야 합니다. (`ready` 이벤트가 발생하기 전에) -이 이벤트를 처리하기 위한다면, 유저는 'even.preventDefault()'를 호출해야 합니다. +이 이벤트를 처리하려면 반드시 `event.preventDefault()`를 호출해야 합니다. ### Event: 'open-url' @@ -86,9 +87,11 @@ Returns: * `event` Event * `url` String -유저가 어플리케이션을 이용하여 URL을 열고자 할 경우, 발현됩니다. -어플리케이션을 통해 열기 위해서는 URL 구조가 반드시 등록되어 있어야 합니다. -이 이벤트를 처리하기 위해선, 유저가 'event.preventDefault()'을 호출해야 합니다. +유저가 어플리케이션을 통해 URL을 열고자 할 때 발생하는 이벤트입니다. + +어플리케이션에서 URL을 열기 위해선 반드시 URL 스킴이 등록되어 있어야 합니다. + +이 이벤트를 처리하려면 반드시 `event.preventDefault()`를 호출해야 합니다. ### Event: 'activate' _OS X_ @@ -97,8 +100,8 @@ Returns: * `event` Event * `hasVisibleWindows` Bool -어플리케이션이 활성화 되었을 때 발현됩니다. -(어플리케이션은 dock 아이콘을 click했을 때 주로 활성화됩니다.) +어플리케이션이 활성화 되었을 때 발생하는 이벤트 입니다. +이 이벤트는 어플리케이션의 dock 아이콘을 클릭했을 때 주로 발생합니다. ### Event: 'browser-window-blur' @@ -107,7 +110,7 @@ Returns: * `event` Event * `window` BrowserWindow -[browserWindow](browser-window.md)가 흐려졌을 때, 호출됩니다. +[browserWindow](browser-window.md)에 대한 포커스가 사라졌을 때 발생하는 이벤트 입니다. ### Event: 'browser-window-focus' @@ -116,8 +119,9 @@ Returns: * `event` Event * `window` BrowserWindow -[browserWindow](browser-window.md)을 포커싱했을 때, 호출됩니다. -(포커싱이란? 클릭 또는 활성화했을 때를 의미) +[browserWindow](browser-window.md)에 대한 포커스가 발생했을 때 발생하는 이벤트 입니다. + +**역주:** _포커스_는 창을 클릭해서 활성화 시켰을 때를 말합니다. ### Event: 'browser-window-created' @@ -126,11 +130,11 @@ Returns: * `event` Event * `window` BrowserWindow -새로운 [browserWindow](browser-window.md)가 생성될 때 발현됩니다. +새로운 [browserWindow](browser-window.md)가 생성되었을 때 발생하는 이벤트 입니다. ### Event: 'select-certificate' -유저인증이 요청되었을 때 발현됩니다. +사용자 인증이 요청되었을 때 발생하는 이벤트 입니다. Returns: @@ -138,8 +142,8 @@ Returns: * `webContents` [WebContents](browser-window.md#class-webcontents) * `url` String * `certificateList` [Objects] - * `data` PEM encoded data - * `issuerName` Issuer's Common Name + * `data` PEM으로 인코딩된 데이터 + * `issuerName` 발급자의 공통 이름 * `callback` Function ```javascript @@ -149,180 +153,170 @@ app.on('select-certificate', function(event, host, url, list, callback) { }) ``` -'url' 유저인증 요청의 탐색 항목에 대응합니다. -그리고, 'callback' 는 리스트로 필터링된 항목과 함께 호출될 필요가 있습니다. +`url`에 대한 -'event.preventDefault()' 호출은 기록소로부터 처음인증된 정보를 사용하는 -어플리케이션을 막습니다. +`url`은 클라이언트 인증서를 요청하는 탐색 항목에 해당합니다. +그리고 `callback`은 목록에서 필터링된 항목과 함께 호출될 필요가 있습니다. + +이 이벤트에서의 `event.preventDefault()` 호출은 초기 인증에 사용한 저장된 데이터를 사용하는 것을 막습니다. ### Event: 'gpu-process-crashed' -GPU가 충돌을 일으켰을 때, 발현됩니다. +GPU가 작동하던 중 크래시가 일어났을 때 발생하는 이벤트입니다. ## Methods -'app' 객체는 밑의 함수를 포함하고 있습니다: +`app` 객체는 다음과 같은 메서드를 가지고 있습니다: -**Note:** 어떤 함수들은 표시된 특정한 운영체제에서만 사용가능합니다. +**참고:** 몇몇 메서드는 특정 플랫폼에서만 작동합니다. ### `app.quit()` -모든 윈도우창 종료를 시도합니다. 'before-quit' 이벤트가 먼저 발현됩니다. -모든 윈도우창이 성공적으로 종료되었다면, 'will-quit' 이벤트가 발현하고, -디폴트설정으로 어플리케이션이 종료됩니다. - -이 함수는 모든 'beforeunload'과 'unload' 이벤트 처리기가 제대로 실행되었음을 보장합니다. -(즉, 'beforeunload'와 'unload'가 정상 실행되었을 때, 실행가능합니다.) - -'beforeunload' 이벤트 처리기가 'false'를 반환하였을 경우, 윈도우 창 종료가 취소 될 수 있습니다. +모든 윈도우창 종료를 시도합니다. `before-quit` 이벤트가 먼저 발생합니다. +모든 윈도우창이 성공적으로 종료되면 `will-quit` 이벤트가 발생하고 기본 동작에 따라 어플리케이션이 종료됩니다. +이 함수는 모든 `beforeunload`와 `unload` 이벤트 핸들러가 제대로 실행됨을 보장합니다. +`beforeunload` 이벤트 핸들러에서 `false`를 반환했을 때 윈도우창 종료가 취소 될 수 있습니다. ### `app.getAppPath()` -현재 어플리케이션의 디렉토리를 반환합니다. +현재 어플리케이션의 디렉터리를 반환합니다. ### `app.getPath(name)` * `name` String -특정한 디렉토리의 경로나 'name' 파일의 경로를 찾아 반환합니다. -실패할 경우, 'Error'를 반환합니다. +`name`에 관련한 특정 디렉터리 또는 파일의 경로를 반환합니다. +경로를 가져오는 데 실패할 경우 `Error`를 반환합니다. -유저는 다음과 같은 이름으로 경로를 요쳥할 수 있습니다: +**역주:** 이 메서드는 운영체제에서 지정한 특수 디렉터리를 가져오는데 사용할 수 있습니다. -* `home` = 유저의 홈 디렉토리. -* `appData` = 유저의 어플리케이션 데이터 디렉토리, 디폴트설정으로: - * `%APPDATA%` = on Windows - * `$XDG_CONFIG_HOME` 또는 `~/.config` = on Linux - * `~/Library/Application Support` = on OS X -* `userData` = 유저 app의 설정파일을 저장하는 디렉토리, -디폴트설정으로 'appData' 디렉토리에 유저 app의 이름을 추가한 형식. -* `temp` = 임시 디렉토리. -* `userDesktop` = 현재 로그인한 유저의 데스트탑 디렉토리. -* `exe` = 현재 실행가능한 파일. -* `module` = `libchromiumcontent` 라이브러리. +`name`은 다음 목록에 있는 경로 중 하나를 선택해 사용할 수 있습니다: + +* `home` - 사용자의 홈 디렉터리. +* `appData` - 각 사용자의 어플리케이션 데이터 디렉터리. 기본 경로는 다음과 같습니다: + * `%APPDATA%` - Windows + * `$XDG_CONFIG_HOME` 또는 `~/.config` - Linux + * `~/Library/Application Support` - OS X +* `userData` - 어플리케이션의 설정을 저장하는 디렉터리. + 이 디렉터리는 기본적으로 `appData`에 어플리케이션 이름으로 생성된 폴더가 지정됩니다. +* `temp` - 임시 폴더 디렉터리. +* `userDesktop` - 현재 사용자의 데스트탑 디렉터리. +* `exe` - 현재 실행중인 Electron 바이너리 파일. +* `module` - `libchromiumcontent` 라이브러리. ### `app.setPath(name, path)` * `name` String * `path` String -특정한 디렉토리나 파일이름이 'name'인 'path'를 재정의합니다. -만약 지정된 디렉토리의 경로가 존재하지 않는다면, 디렉토리가 새로 생성됩니다. -실패시, 'Error'를 반환합니다. +`name`에 대한 특정 디렉터리나 파일의 경로인 `path`를 재정의합니다. +만약 지정한 디렉터리의 경로가 존재하지 않으면 디렉터리가 이 메서드를 통해 새로 생성됩니다. +재정의에 실패했을 땐 `Error`를 반환합니다. -유저는 'app.getPath'에 정의되어 있는 'name' 경로만 재정의할 수 있습니다. +이 메서드는 `app.getPath`에 정의되어 있는 `name`의 경로만 재정의할 수 있습니다. -디폴트설정으로, 웹페이지의 쿠키와 캐시는 'userData' 디렉토리 밑에 저장됩니다. - -만약 유저가 이 위치를 변경하고자 한다면, 'app' 모듈의 'ready' 이벤트가 발현되기 전, -유저는 반드시 'userData' 경로를 재정의해야 합니다. +기본적으로, 웹 페이지의 쿠키와 캐시는 `userData` 디렉터리에 저장됩니다. +만약 이 위치를 변경하고자 한다면, 반드시 `app` 모듈의 `ready` 이벤트가 발생하기 전에 `userData` 경로를 재정의해야 합니다. ### `app.getVersion()` 로드된 어플리케이션의 버전을 반환합니다. -만약 'package.json' 파일에서 어플리케이션의 버전을 찾지 못한다면, -현재 번들 또는 실행 파일의 버전이 반환됩니다. +만약 `package.json` 파일에서 어플리케이션의 버전을 찾을 수 없는 경우, 현재 번들 또는 실행 파일의 버전을 반환합니다. ### `app.getName()` -응용프로그램(=어플리케이션)의 'package.json' 파일에 있는 -현재 응용프로그램의 이름을 반환합니다. +`package.json`에서 기술된 현재 어플리케이션의 이름을 반환합니다. -NPM 모듈 스펙에 따라, 대부분 'package.json'의 'name' 필드는 소문자 이름입니다. -유저는 'productName'의 필드를 지정해야 합니다. -'productName'는 응용프로그램의 대문자 이름입니다. -'productName'는 Electron에서 선호하는 'name' 입니다. +npm 모듈 규칙에 따라 대부분의 경우 `package.json`의 `name` 필드는 소문자 이름을 사용합니다. +하지만 Electron은 `name`대신 `productName` 필드를 주로 사용하기 때문에 반드시 이 필드도 같이 지정해야 합니다. +이 필드는 맨 앞글자가 대문자인 어플리케이션 전체 이름을 지정해야 합니다. ### `app.getLocale()` -현재 응용프로그램의 locale을 반환합니다. +현재 어플리케이션의 [로케일](https://ko.wikipedia.org/wiki/%EB%A1%9C%EC%BC%80%EC%9D%BC)을 반환합니다. ### `app.resolveProxy(url, callback)` * `url` URL * `callback` Function -'url'을 위해 프록시 정보를 해석합니다. -'callback'은 'callback(proxy)' 요청이 수행될 때, -호출됩니다. +`url`의 프록시 정보를 해석합니다. +`callback`은 요청이 수행되었을 때 `callback(proxy)` 형태로 호출됩니다. ### `app.addRecentDocument(path)` * `path` String -최근 문서 목록에 'path'를 추가합니다. +최근 문서 목록에 `path`를 추가합니다. -목록은 운영체제에 의해 관리됩니다. -Windows 운영체제에서는, 작업표시줄에서 참조할 수 있습니다. -OS X 운영체제에서는, dock 메뉴에서 참조할 수 있습니다. +이 목록은 OS에 의해 관리됩니다. +최근 문서 목록은 Windows의 경우 작업 표시줄에서 찾을 수 있고, OS X의 경우 dock 메뉴에서 찾을 수 있습니다. ### `app.clearRecentDocuments()` -최근 문서 목록을 모두 지웁니다. +최근 문서 목록을 모두 비웁니다. ### `app.setUserTasks(tasks)` _Windows_ -* `tasks` Array - 'Task' 객체의 배열 +* `tasks` Array - `Task` 객체의 배열 -Windows 운영체제에서 JumpList의 [Tasks][tasks] 카테고리로 'tasks'를 추가합니다. +Windows에서 사용할 수 있는 JumpList의 [Tasks][tasks] 카테고리에 `task`를 추가합니다. -'tasks'는 밑의 형식을 따르는 'Task' 객체의 배열입니다: +`tasks`는 다음과 같은 구조를 가지는 `Task` 객체의 배열입니다: `Task` Object -* `program` String - 프로그램을 실행할 경로, 대부분 유저는 현재 프로그램을 여는 'process.execPath'를 지정합니다. -* `arguments` String - 'program'이 실행될 때의 명령문 인자. -* `title` String - JumpList에 표시할 문장. -* `description` String - 이 task 객체에 대한 설명. -* `iconPath` String - 아이콘을 포함한 임의의 리소스 파일이며, JumpList에 표시될 아이콘의 절대 경로. -유저는 대부분 프로그램의 아이콘을 표시하기 위해 'process.execPath'를 지정합니다. -* `iconIndex` Integer - 아이콘 파일안의 아이콘 색인(index). 만약, 아이콘 파일 안에 두 개 이상의 아이콘이 있을 경우, - 아이콘을 특정하기 위해 이 값을 설정합니다. 단, 하나의 아이콘만 있을 경우는 0 값을 가집니다. - +* `program` String - 실행할 프로그램의 경로. + 보통 현재 작동중인 어플리케이션의 경로인 `process.execPath`를 지정합니다. +* `arguments` String - `program`이 실행될 때 사용될 명령줄 인자. +* `title` String - JumpList에 표시할 문자열. +* `description` String - 이 작업에 대한 설명. +* `iconPath` String - JumpList에 표시될 아이콘의 절대 경로. + 아이콘을 포함하고 있는 임의의 리소스 파일을 사용할 수 있습니다. + 보통 어플리케이션의 아이콘을 그대로 사용하기 위해 `process.execPath`를 지정합니다. +* `iconIndex` Integer - 아이콘 파일의 인덱스. 만약 아이콘 파일이 두 개 이상의 아이콘을 가지고 있을 경우, + 사용할 아이콘의 인덱스를 이 옵션으로 지정해 주어야 합니다. 단, 아이콘을 하나만 포함하고 있는 경우 0을 지정하면 됩니다. + ### `app.commandLine.appendSwitch(switch[, value])` -Append a switch (with optional `value`) to Chromium's command line. -Chromium의 명령문에 (선택적으로, 'value'와 함께) switch를 추가합니다. +Chrominum의 명령줄에 스위치를 추가합니다. `value`는 추가적인 값을 뜻하며 옵션입니다. -**Note:** 이 함수는 'process.argv'에 영향을 주지 않습니다. -그리고, 보통 이 함수는 개발자들이 하드웨어 수준의 Chrominum의 행동을 제어하기 위해 주로 사용됩니다. +**참고:** 이 메서드는 `process.argv`에 영향을 주지 않습니다. + 개발자들은 보통 Chrominum의 로우 레벨 수준의 동작을 제어하기 위해 주로 사용합니다. ### `app.commandLine.appendArgument(value)` -Chromium의 명령문에 인자를 추가합니다. 인자는 올바르게 인용될 것입니다. +Chrominum의 명령줄에 인자를 추가합니다. 인자는 올바르게 인용됩니다. -**Note:** 'process.argv'에 영향을 주지는 않습니다. +**참고:** 이 메서드는 `process.argv`에 영향을 주지 않습니다. ### `app.dock.bounce([type])` _OS X_ -* `type` String (optional) - 'critical'이나 'informational'. 디폴트 설정은 'informational' +* `type` String (optional) - `critical` 또는 `informational`을 지정할 수 있습니다. 기본값은 `informational` 입니다. -'critical'이 input으로 전달되면, dock 아이콘은 응용프로그램이 활성화되거나, 요청이 취소될 때까지 -계속 bounce합니다. +`critical`이 전달되면 dock 아이콘이 어플리케이션이 활성화되거나 요청이 중지되기 전까지 통통 튑니다. -'informational'이 input으로 전달되면, dock 아이콘은 1초만 bounce합니다. -그러나, 응용프로그램이 활성화되거나 요청이 취소될 때까지 요청은 계속 활성화되어 있습니다. +`informational`이 전달되면 dock 아이콘이 1초만 통통 튑니다. +하지만 어플리케이션이 활성화되거나 요청이 중지되기 전까지 요청은 계속 활성화로 유지 됩니다. -요청을 대표하는 ID를 반환합니다. +또한 요청을 취소할 때 사용할 수 있는 ID를 반환합니다. ### `app.dock.cancelBounce(id)` _OS X_ * `id` Integer -'id'의 반송을 취소합니다. - +`app.dock.bounce([type])` 메서드에서 반환한 `id`의 통통 튀는 효과를 취소합니다. ### `app.dock.setBadge(text)` _OS X_ * `text` String -dock의 badge 구역에 표현될 문장을 설정합니다. +dock의 badge에 표시할 문자열을 설정합니다. ### `app.dock.getBadge()` _OS X_ -dock의 badge 라인을 반환합니다. - +dock의 badge에 설정된 문자열을 반환합니다. ### `app.dock.hide()` _OS X_ @@ -330,13 +324,13 @@ dock 아이콘을 숨깁니다. ### `app.dock.show()` _OS X_ -dock 아이콘을 보여줍니다. +dock 아이콘을 표시합니다. ### `app.dock.setMenu(menu)` _OS X_ * `menu` Menu -어플리케이션의 [dock menu][dock-menu]을 결정합니다. +어플리케이션의 [dock menu][dock-menu]를 설정합니다. [dock-menu]:https://developer.apple.com/library/mac/documentation/Carbon/Conceptual/customizing_docktile/concepts/dockconcepts.html#//apple_ref/doc/uid/TP30000986-CH2-TPXREF103 [tasks]:http://msdn.microsoft.com/en-us/library/windows/desktop/dd378460(v=vs.85).aspx#tasks From edd6032ed0b869d0c17cce114475dccd13ce50fd Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Fri, 16 Oct 2015 22:26:27 +0900 Subject: [PATCH 012/146] Improve grammar for more readability --- docs-translations/ko-KR/README.md | 2 +- docs-translations/ko-KR/api/chrome-command-line-switches.md | 4 ++-- docs-translations/ko-KR/api/process.md | 2 +- docs-translations/ko-KR/api/tray.md | 2 +- .../ko-KR/tutorial/desktop-environment-integration.md | 4 ++-- docs-translations/ko-KR/tutorial/quick-start.md | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs-translations/ko-KR/README.md b/docs-translations/ko-KR/README.md index 9b88aa6e17..e77ee9b693 100644 --- a/docs-translations/ko-KR/README.md +++ b/docs-translations/ko-KR/README.md @@ -19,7 +19,7 @@ * [개요](api/synopsis.md) * [Process 객체](api/process.md) -* [크롬 Command Line 스위치 지원](api/chrome-command-line-switches.md) +* [크롬 명령줄 스위치 지원](api/chrome-command-line-switches.md) ### 커스텀 DOM elements: diff --git a/docs-translations/ko-KR/api/chrome-command-line-switches.md b/docs-translations/ko-KR/api/chrome-command-line-switches.md index 777ce4612d..41517e08ca 100644 --- a/docs-translations/ko-KR/api/chrome-command-line-switches.md +++ b/docs-translations/ko-KR/api/chrome-command-line-switches.md @@ -1,6 +1,6 @@ -# 크롬 Command-Line 스위치 지원 +# 크롬 명령줄 스위치 지원 -크롬 Command-Line 스위치는 크롬 브라우저에서 제공되는 추가 옵션이며 Electron에서도 지원합니다. +크롬 명령줄(Command-Line) 스위치는 크롬 브라우저에서 제공되는 추가 옵션이며 Electron에서도 지원합니다. [app][app]의 [ready][ready]이벤트가 작동하기 전에 [app.commandLine.appendSwitch][append-switch] API를 사용하면 어플리케이션 내부에서 스위치들을 추가할 수 있습니다: diff --git a/docs-translations/ko-KR/api/process.md b/docs-translations/ko-KR/api/process.md index a35c8b4e93..109c443c49 100644 --- a/docs-translations/ko-KR/api/process.md +++ b/docs-translations/ko-KR/api/process.md @@ -37,4 +37,4 @@ process.once('loaded', function() { * `maxDescriptors` Integer -`maxDescriptors`에 file descriptor 소프트 리미트를 설정하거나 OS 하드 리미트를 설정합니다. 값은 현재 프로세스에 대해 낮은 값이어야 합니다. +현재 프로세스의 파일 기술자의 제한 값을 소프트 제한 `maxDescriptors`의 값이나 OS 하드 제한 중 낮은 값으로 설정합니다. diff --git a/docs-translations/ko-KR/api/tray.md b/docs-translations/ko-KR/api/tray.md index beccf6d8b3..cd821f581b 100644 --- a/docs-translations/ko-KR/api/tray.md +++ b/docs-translations/ko-KR/api/tray.md @@ -118,7 +118,7 @@ __주의:__ `bounds`는 OS X 와 Windows에서만 작동합니다. `Tray` 모듈은 다음과 같은 메서드를 가지고 있습니다: -**참고:** 몇가지 메서드는 특정한 플랫폼에서만 작동합니다. +**참고:** 몇몇 메서드는 특정 플랫폼에서만 작동합니다. ### `Tray.destroy()` diff --git a/docs-translations/ko-KR/tutorial/desktop-environment-integration.md b/docs-translations/ko-KR/tutorial/desktop-environment-integration.md index 8e73d5940f..b721f5a4a3 100644 --- a/docs-translations/ko-KR/tutorial/desktop-environment-integration.md +++ b/docs-translations/ko-KR/tutorial/desktop-environment-integration.md @@ -1,7 +1,7 @@ # 데스크톱 환경 통합 -어플리케이션을 배포할 서로 다른 운영체제 시스템의 환경과 기능에 맞춰 사용 환경을 통합할 수 있습니다. -예를 들어 Windows에선 태스크바의 JumpList에 바로가기를 추가할 수 있고 Mac(OS X)에선 dock menu에 커스텀 메뉴를 추가할 수 있습니다. +어플리케이션 배포의 대상이 되는 서로 다른 운영체제 시스템의 환경에 맞춰 어플리케이션의 기능을 통합할 수 있습니다. +예를 들어 Windows에선 태스크바의 JumpList에 바로가기를 추가할 수 있고 Mac(OS X)에선 dock 메뉴에 커스텀 메뉴를 추가할 수 있습니다. 이 가이드는 Electron API를 이용하여 각 운영체제 시스템의 기능을 활용하는 방법을 설명합니다. diff --git a/docs-translations/ko-KR/tutorial/quick-start.md b/docs-translations/ko-KR/tutorial/quick-start.md index 6eaad74769..3a0c38f04b 100644 --- a/docs-translations/ko-KR/tutorial/quick-start.md +++ b/docs-translations/ko-KR/tutorial/quick-start.md @@ -71,7 +71,7 @@ var BrowserWindow = require('browser-window'); // 네이티브 브라우저 창 require('crash-reporter').start(); // 윈도우 객체를 전역에 유지합니다. 만약 이렇게 하지 않으면 -// 자바스크립트 GC가 일어날 때 창이 자동으로 닫혀버립니다. +// 자바스크립트 GC가 일어날 때 창이 멋대로 닫혀버립니다. var mainWindow = null; // 모든 창이 닫히면 어플리케이션 종료. From 32523ae352b7735b0bcac52accbc0800dc0d8546 Mon Sep 17 00:00:00 2001 From: Roman Klauke Date: Fri, 16 Oct 2015 22:03:47 +0200 Subject: [PATCH 013/146] replace Mac with OS X Commit 691d8dd replaced "Mac" with "OS X". This commit replaces some other occurrences. --- docs/README.md | 2 +- docs/api/frameless-window.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/README.md b/docs/README.md index a0d91a5459..754048f5e3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -66,7 +66,7 @@ * [Source Code Directory Structure](development/source-code-directory-structure.md) * [Technical Differences to NW.js (formerly node-webkit)](development/atom-shell-vs-node-webkit.md) * [Build System Overview](development/build-system-overview.md) -* [Build Instructions (Mac)](development/build-instructions-osx.md) +* [Build Instructions (OS X)](development/build-instructions-osx.md) * [Build Instructions (Windows)](development/build-instructions-windows.md) * [Build Instructions (Linux)](development/build-instructions-linux.md) * [Setting Up Symbol Server in debugger](development/setting-up-symbol-server.md) diff --git a/docs/api/frameless-window.md b/docs/api/frameless-window.md index 8d64a6fcd7..bc37aafca8 100644 --- a/docs/api/frameless-window.md +++ b/docs/api/frameless-window.md @@ -13,7 +13,7 @@ var BrowserWindow = require('browser-window'); var win = new BrowserWindow({ width: 800, height: 600, frame: false }); ``` -### Alternatives on Mac +### Alternatives on OS X On Mac OS X 10.10 Yosemite and newer, there's an alternative way to specify a chromeless window. Instead of setting `frame` to `false` which disables From b5478eaef76063f65bda41da65461f03017d989d Mon Sep 17 00:00:00 2001 From: Greg Gamel Date: Fri, 16 Oct 2015 17:50:41 -0500 Subject: [PATCH 014/146] Update MAS tutorial to improve readability Adjusted tutorial body copy and headings to improve readability, clarify sentences, and fix some (minor) grammatical missteps. --- .../mac-app-store-submission-guide.md | 53 +++++++++---------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/docs/tutorial/mac-app-store-submission-guide.md b/docs/tutorial/mac-app-store-submission-guide.md index ccd0ead858..b67e6ff54d 100644 --- a/docs/tutorial/mac-app-store-submission-guide.md +++ b/docs/tutorial/mac-app-store-submission-guide.md @@ -1,29 +1,28 @@ # Mac App Store Submission Guide -Since v0.34.0, Electron allows submitting packaged apps to Mac App Store (MAS), -this guide provides information on how to submit your app, and the limitations +Since v0.34.0, Electron allows submitting packaged apps to the Mac App Store (MAS). +This guide provides information on: how to submit your app & the limitations of the MAS build. -## How to submit your app +## How to Submit Your App -Following steps introduces a simple way to submit your app to Mac App Store, but -it doesn't make sure your app gets approved by Apple, you still have to read -apple's [Submitting Your App][submitting-your-app] guide on how to meet Mac -App Store's requirements. +The following steps introduce a simple way to submit your app to Mac App Store. However, these steps +do not ensure sure your app will be approved by Apple; you still need to read +Apple's [Submitting Your App][submitting-your-app] guide on how to meet the Mac +App Store requirements. -### Get certificate +### Get Certificate -To submit your app to Mac App Store, you have to get a certificate from Apple -first, you can follow [existing guides][nwjs-guide] on web. +To submit your app to the Mac App Store, you first must get a certificate from Apple. You can follow these [existing guides][nwjs-guide] on web. -### Sign your app +### Sign Your App -After getting the certificate, you can package your app by following -[Application Distribution](application-distribution.md), and then sign your app. -The step is basically the same with other programs, the key is to sign every +After getting the certificate from Apple, you can package your app by following +[Application Distribution](application-distribution.md), and then proceed to signing your app. +This step is basically the same with other programs, but the key is to sign every dependency of Electron one by one. -First you need to prepare two entitlements files. +First, you need to prepare two entitlements files. `child.plist`: @@ -53,7 +52,7 @@ First you need to prepare two entitlements files. ``` -And then sign your app with following script: +...and then sign your app with the following script: ```bash #!/bin/bash @@ -80,32 +79,32 @@ codesign -fs "$APP_KEY" --entitlements parent.plist "$APP_PATH" productbuild --component "$APP_PATH" /Applications --sign "$INSTALLER_KEY" "$APP_PATH" ``` -If you are new to app sandboxing of OS X, you should also go through Apple's -[Enabling App Sandbox][enable-app-sandbox] to have a basic idea, and add keys +If you are new to app sandboxing under OS X, you should also read through Apple's +[Enabling App Sandbox][enable-app-sandbox] to have a basic idea, then add keys for the permissions needed by your app to the entitlements files. -### Upload your app and submit for review +### Upload Your App and Submit for Review -After signing your app you can use Application Loader to upload it to iTunes -Connect for processing, make sure you have [created a record][create-record] +After signing your app, you can use Application Loader to upload it to iTunes +Connect for processing, making sure you have [created a record][create-record] before uploading. Then you can [submit your app for review][submit-for-review]. -## Limitations of MAS build +## Limitations of MAS Build -In order to satisfy requirements for app sandboxing, following modules have been -disabled in MAS build: +In order to satisfy all requirements for app sandboxing, the following modules have been +disabled in the MAS build: * `crash-reporter` * `auto-updater` -and following behaviors have been changed: +and the following behaviors have been changed: * Video capture may not work for some machines. * Certain accessibility features may not work. * Apps will not be aware of DNS changes. -Also due to the usage of app sandboxing, the resources can be accessed by the -app is strictly limited, you can read [App Sandboxing][app-sandboxing] for more. +Also, due to the usage of app sandboxing, the resources which can be accessed by the +app are strictly limited; you can read [App Sandboxing][app-sandboxing] for more information. [submitting-your-app]: https://developer.apple.com/library/mac/documentation/IDEs/Conceptual/AppDistributionGuide/SubmittingYourApp/SubmittingYourApp.html [nwjs-guide]: https://github.com/nwjs/nw.js/wiki/Mac-App-Store-%28MAS%29-Submission-Guideline#first-steps From 72078f7b027088b004895e9582d204a23f80a03a Mon Sep 17 00:00:00 2001 From: max_devjs Date: Fri, 16 Oct 2015 19:56:45 -0300 Subject: [PATCH 015/146] [truly humble correction of a misspelling] Substituting webp with web in frameless-window.md . --- docs/api/frameless-window.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/frameless-window.md b/docs/api/frameless-window.md index bc37aafca8..707a928f9d 100644 --- a/docs/api/frameless-window.md +++ b/docs/api/frameless-window.md @@ -1,6 +1,6 @@ # Frameless Window -A frameless window is a window that has no [chrome](https://developer.mozilla.org/en-US/docs/Glossary/Chrome), the parts of the window, like toolbars, that are not a part of the webp page. These are options on the [`BrowserWindow`](browser-window.md) class. +A frameless window is a window that has no [chrome](https://developer.mozilla.org/en-US/docs/Glossary/Chrome), the parts of the window, like toolbars, that are not a part of the web page. These are options on the [`BrowserWindow`](browser-window.md) class. ## Create a frameless window From 5e7a30deeab7639e9f76458690d488ca9c6d93f2 Mon Sep 17 00:00:00 2001 From: Greg Gamel Date: Fri, 16 Oct 2015 18:25:30 -0500 Subject: [PATCH 016/146] Additional updates to adhere to styleguide Per @jlord's recommendations. --- .../mac-app-store-submission-guide.md | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/docs/tutorial/mac-app-store-submission-guide.md b/docs/tutorial/mac-app-store-submission-guide.md index b67e6ff54d..fb04a5573b 100644 --- a/docs/tutorial/mac-app-store-submission-guide.md +++ b/docs/tutorial/mac-app-store-submission-guide.md @@ -1,26 +1,27 @@ # Mac App Store Submission Guide -Since v0.34.0, Electron allows submitting packaged apps to the Mac App Store (MAS). -This guide provides information on: how to submit your app & the limitations -of the MAS build. +Since v0.34.0, Electron allows submitting packaged apps to the Mac App Store +(MAS). This guide provides information on: how to submit your app and the +limitations of the MAS build. ## How to Submit Your App -The following steps introduce a simple way to submit your app to Mac App Store. However, these steps -do not ensure sure your app will be approved by Apple; you still need to read -Apple's [Submitting Your App][submitting-your-app] guide on how to meet the Mac -App Store requirements. +The following steps introduce a simple way to submit your app to Mac App Store. +However, these steps do not ensure sure your app will be approved by Apple; you +still need to read Apple's [Submitting Your App][submitting-your-app] guide on +how to meet the Mac App Store requirements. ### Get Certificate -To submit your app to the Mac App Store, you first must get a certificate from Apple. You can follow these [existing guides][nwjs-guide] on web. +To submit your app to the Mac App Store, you first must get a certificate from +Apple. You can follow these [existing guides][nwjs-guide] on web. ### Sign Your App After getting the certificate from Apple, you can package your app by following -[Application Distribution](application-distribution.md), and then proceed to signing your app. -This step is basically the same with other programs, but the key is to sign every -dependency of Electron one by one. +[Application Distribution](application-distribution.md), and then proceed to +signing your app. This step is basically the same with other programs, but the +key is to sign every dependency of Electron one by one. First, you need to prepare two entitlements files. @@ -52,7 +53,7 @@ First, you need to prepare two entitlements files. ``` -...and then sign your app with the following script: +And then sign your app with the following script: ```bash #!/bin/bash @@ -78,10 +79,10 @@ codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP codesign -fs "$APP_KEY" --entitlements parent.plist "$APP_PATH" productbuild --component "$APP_PATH" /Applications --sign "$INSTALLER_KEY" "$APP_PATH" ``` - -If you are new to app sandboxing under OS X, you should also read through Apple's -[Enabling App Sandbox][enable-app-sandbox] to have a basic idea, then add keys -for the permissions needed by your app to the entitlements files. +a +If you are new to app sandboxing under OS X, you should also read through +Apple's [Enabling App Sandbox][enable-app-sandbox] to have a basic idea, then +add keys for the permissions needed by your app to the entitlements files. ### Upload Your App and Submit for Review @@ -91,8 +92,8 @@ before uploading. Then you can [submit your app for review][submit-for-review]. ## Limitations of MAS Build -In order to satisfy all requirements for app sandboxing, the following modules have been -disabled in the MAS build: +In order to satisfy all requirements for app sandboxing, the following modules +have been disabled in the MAS build: * `crash-reporter` * `auto-updater` @@ -103,8 +104,9 @@ and the following behaviors have been changed: * Certain accessibility features may not work. * Apps will not be aware of DNS changes. -Also, due to the usage of app sandboxing, the resources which can be accessed by the -app are strictly limited; you can read [App Sandboxing][app-sandboxing] for more information. +Also, due to the usage of app sandboxing, the resources which can be accessed by + the app are strictly limited; you can read [App Sandboxing][app-sandboxing] for + more information. [submitting-your-app]: https://developer.apple.com/library/mac/documentation/IDEs/Conceptual/AppDistributionGuide/SubmittingYourApp/SubmittingYourApp.html [nwjs-guide]: https://github.com/nwjs/nw.js/wiki/Mac-App-Store-%28MAS%29-Submission-Guideline#first-steps From 7ac643d5f85028b126b52ad956c67af78f3417af Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Fri, 16 Oct 2015 16:45:54 -0700 Subject: [PATCH 017/146] Create a new method on App to override URL Security Manager's decisions wrt NTLM --- atom/browser/api/atom_api_app.cc | 6 ++++++ atom/browser/api/atom_api_app.h | 2 ++ vendor/brightray | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 70595856c4..47c3b1dd46 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -251,6 +251,12 @@ void App::SetAppUserModelId(const std::string& app_id) { #endif } +void App::AllowNTLMCredentialsForAllDomains(bool should_allow) { + auto browser_context = static_cast( + AtomBrowserMainParts::Get()->browser_context()); + browser_context->AllowNTLMCredentialsForAllDomains(should_allow); +} + std::string App::GetLocale() { return l10n_util::GetApplicationLocale(""); } diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index 4896a5f066..75889d2454 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -65,6 +65,8 @@ class App : public mate::EventEmitter, void SetDesktopName(const std::string& desktop_name); void SetAppUserModelId(const std::string& app_id); + void AllowNTLMCredentialsForAllDomains(bool should_allow); + std::string GetLocale(); v8::Local DefaultSession(v8::Isolate* isolate); diff --git a/vendor/brightray b/vendor/brightray index fe2dd437c9..2fc67433d1 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit fe2dd437c9ef7877bf9d454db8ae401965cd7cb0 +Subproject commit 2fc67433d18fa336c2f76e94c1a04177360cb126 From 5ae6bd2ef4e3892b84121f6d92b3de6a2f89fdf1 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Fri, 16 Oct 2015 16:46:05 -0700 Subject: [PATCH 018/146] Wire it up --- atom/browser/api/atom_api_app.cc | 1 + atom/browser/api/atom_api_app.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 47c3b1dd46..158dd6ae34 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -291,6 +291,7 @@ mate::ObjectTemplateBuilder App::GetObjectTemplateBuilder( .SetMethod("getPath", &App::GetPath) .SetMethod("setDesktopName", &App::SetDesktopName) .SetMethod("setAppUserModelId", &App::SetAppUserModelId) + .SetMethod("allowNTLMCredentialsForAllDomains", &App::AllowNTLMCredentialsForAllDomains) .SetMethod("getLocale", &App::GetLocale) .SetProperty("defaultSession", &App::DefaultSession); } diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index 75889d2454..52f134169f 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -66,7 +66,7 @@ class App : public mate::EventEmitter, void SetDesktopName(const std::string& desktop_name); void SetAppUserModelId(const std::string& app_id); void AllowNTLMCredentialsForAllDomains(bool should_allow); - + std::string GetLocale(); v8::Local DefaultSession(v8::Isolate* isolate); From a3fc25110eed8e61bbf37c6935350c3747b5aea7 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Fri, 16 Oct 2015 16:55:28 -0700 Subject: [PATCH 019/146] Linter fixes --- atom/browser/api/atom_api_app.cc | 3 ++- atom/browser/api/atom_api_app.h | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 158dd6ae34..f124b00e80 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -291,7 +291,8 @@ mate::ObjectTemplateBuilder App::GetObjectTemplateBuilder( .SetMethod("getPath", &App::GetPath) .SetMethod("setDesktopName", &App::SetDesktopName) .SetMethod("setAppUserModelId", &App::SetAppUserModelId) - .SetMethod("allowNTLMCredentialsForAllDomains", &App::AllowNTLMCredentialsForAllDomains) + .SetMethod("allowNTLMCredentialsForAllDomains", + &App::AllowNTLMCredentialsForAllDomains) .SetMethod("getLocale", &App::GetLocale) .SetProperty("defaultSession", &App::DefaultSession); } diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index 52f134169f..75889d2454 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -66,7 +66,7 @@ class App : public mate::EventEmitter, void SetDesktopName(const std::string& desktop_name); void SetAppUserModelId(const std::string& app_id); void AllowNTLMCredentialsForAllDomains(bool should_allow); - + std::string GetLocale(); v8::Local DefaultSession(v8::Isolate* isolate); From b958982d9921712732c9db056a55674133626b15 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Fri, 16 Oct 2015 17:00:31 -0700 Subject: [PATCH 020/146] Rollback submodule change --- vendor/brightray | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/brightray b/vendor/brightray index 2fc67433d1..fe2dd437c9 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit 2fc67433d18fa336c2f76e94c1a04177360cb126 +Subproject commit fe2dd437c9ef7877bf9d454db8ae401965cd7cb0 From c46ed897dd5714322e7fe0ed151deb289fa2df6e Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Sun, 18 Oct 2015 03:22:35 +0900 Subject: [PATCH 021/146] Update translation progress in README.md --- docs-translations/ko-KR/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs-translations/ko-KR/README.md b/docs-translations/ko-KR/README.md index e77ee9b693..9df89ead50 100644 --- a/docs-translations/ko-KR/README.md +++ b/docs-translations/ko-KR/README.md @@ -29,7 +29,7 @@ ### 메인 프로세스에서 사용할 수 있는 모듈: -* [app (0% 번역됨)](api/app.md) +* [app](api/app.md) * [auto-updater](api/auto-updater.md) * [browser-window (0% 번역됨)](api/browser-window.md) * [content-tracing](api/content-tracing.md) From 82e6058607fa0b1ca24b83a5084392422fec12f9 Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Sun, 18 Oct 2015 03:30:37 +0900 Subject: [PATCH 022/146] Update as upstream --- docs-translations/ko-KR/README.md | 3 ++- docs-translations/ko-KR/api/frameless-window.md | 2 +- docs-translations/ko-KR/api/process.md | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs-translations/ko-KR/README.md b/docs-translations/ko-KR/README.md index 9df89ead50..bfe08e5e10 100644 --- a/docs-translations/ko-KR/README.md +++ b/docs-translations/ko-KR/README.md @@ -2,6 +2,7 @@ * [지원하는 플랫폼](tutorial/supported-platforms.md) * [어플리케이션 배포](tutorial/application-distribution.md) +* [맥 앱스토어 제출 가이드 (0% 번역됨)](tutorial/mac-app-store-submission-guide.md) * [어플리케이션 패키징](tutorial/application-packaging.md) * [네이티브 Node 모듈 사용하기](tutorial/using-native-node-modules.md) * [메인 프로세스 디버깅하기](tutorial/debugging-main-process.md) @@ -65,7 +66,7 @@ * [소스 코드 디렉터리 구조](development/source-code-directory-structure.md) * [NW.js(node-webkit)와 기술적으로 다른점](development/atom-shell-vs-node-webkit.md) * [빌드 시스템 개요](development/build-system-overview.md) -* [빌드 설명서 (Mac)](development/build-instructions-osx.md) +* [빌드 설명서 (OS X)](development/build-instructions-osx.md) * [빌드 설명서 (Windows)](development/build-instructions-windows.md) * [빌드 설명서 (Linux)](development/build-instructions-linux.md) * [디버거에서 디버그 심볼 서버 설정](development/setting-up-symbol-server.md) diff --git a/docs-translations/ko-KR/api/frameless-window.md b/docs-translations/ko-KR/api/frameless-window.md index 6442246077..28662c311b 100644 --- a/docs-translations/ko-KR/api/frameless-window.md +++ b/docs-translations/ko-KR/api/frameless-window.md @@ -13,7 +13,7 @@ var BrowserWindow = require('browser-window'); var win = new BrowserWindow({ width: 800, height: 600, frame: false }); ``` -### 최신 Mac에서 사용할 수 있는 대안 +### 최신 OS X에서 사용할 수 있는 대안 OS X 10.10 Yosemite 이후의 최신 버전부터는 테두리가 없는 창을 만들 때 새로운 방법을 사용할 수 있습니다. `frame` 옵션을 `false`로 지정하여 제목과 창 구성 요소를 모두 비활성화하는 대신 새로운 `title-bar-style` diff --git a/docs-translations/ko-KR/api/process.md b/docs-translations/ko-KR/api/process.md index 109c443c49..c66eb84cfe 100644 --- a/docs-translations/ko-KR/api/process.md +++ b/docs-translations/ko-KR/api/process.md @@ -5,7 +5,8 @@ Electron의 `process` 객체는 기존의 node와는 달리 약간의 차이점 * `process.type` String - 프로세스의 타입, `browser` (메인 프로세스) 또는 `renderer`가 됩니다. * `process.versions['electron']` String - Electron의 버전. * `process.versions['chrome']` String - Chromium의 버전. -* `process.resourcesPath` String - JavaScript 소스코드의 경로. +* `process.resourcesPath` String - JavaScript 소스 코드의 경로. +* `process.mas` Boolean - Mac 앱 스토어용 빌드일 때 `true`로 지정됩니다. 다른 빌드일 땐 `undefined`로 지정됩니다. ## Events From 49d25d00697047faed54e0e8080520d73cce629e Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Sun, 18 Oct 2015 11:28:05 +0800 Subject: [PATCH 023/146] Fix: savePage API saves external resources in a wrong directory. --- atom/browser/api/save_page_handler.cc | 7 ++++++- spec/api-browser-window-spec.coffee | 19 ++++++++++++++----- spec/fixtures/pages/save_page/index.html | 6 ++++++ spec/fixtures/pages/save_page/test.css | 1 + spec/fixtures/pages/save_page/test.js | 1 + 5 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 spec/fixtures/pages/save_page/index.html create mode 100644 spec/fixtures/pages/save_page/test.css create mode 100644 spec/fixtures/pages/save_page/test.js diff --git a/atom/browser/api/save_page_handler.cc b/atom/browser/api/save_page_handler.cc index 42b93befdd..1e5bc094cf 100644 --- a/atom/browser/api/save_page_handler.cc +++ b/atom/browser/api/save_page_handler.cc @@ -36,8 +36,13 @@ bool SavePageHandler::Handle(const base::FilePath& full_path, auto download_manager = content::BrowserContext::GetDownloadManager( web_contents_->GetBrowserContext()); download_manager->AddObserver(this); + // Chromium will create a 'foo_files' directory under the directory of saving + // page 'foo.html' for holding other resource files of 'foo.html'. + base::FilePath saved_main_directory_path = full_path.DirName().Append( + full_path.RemoveExtension().BaseName().value() + + FILE_PATH_LITERAL("_files")); bool result = web_contents_->SavePage(full_path, - full_path.DirName(), + saved_main_directory_path, save_type); download_manager->RemoveObserver(this); // If initialization fails which means fail to create |DownloadItem|, we need diff --git a/spec/api-browser-window-spec.coffee b/spec/api-browser-window-spec.coffee index 0f9b376784..1218d27b7f 100644 --- a/spec/api-browser-window-spec.coffee +++ b/spec/api-browser-window-spec.coffee @@ -303,13 +303,22 @@ describe 'browser-window module', -> done() describe 'save page', -> - savePagePath = path.join fixtures, 'save_page.html' + savePageDir = path.join fixtures, 'save_page' + savePageHtmlPath = path.join savePageDir, 'save_page.html' + savePageJsPath = path.join savePageDir, 'save_page_files', 'test.js' + savePageCssPath = path.join savePageDir, 'save_page_files', 'test.css' it 'should save page', (done) -> w.webContents.on 'did-finish-load', -> - w.webContents.savePage savePagePath, 'HTMLComplete', (error) -> + w.webContents.savePage savePageHtmlPath, 'HTMLComplete', (error) -> assert.equal error, null - assert fs.existsSync savePagePath - fs.unlinkSync savePagePath + assert fs.existsSync savePageHtmlPath + assert fs.existsSync savePageJsPath + assert fs.existsSync savePageCssPath + fs.unlinkSync savePageCssPath + fs.unlinkSync savePageJsPath + fs.unlinkSync savePageHtmlPath + fs.rmdirSync path.join savePageDir, 'save_page_files' + fs.rmdirSync savePageDir done() - w.loadUrl "file://#{fixtures}/api/blank.html" + w.loadUrl "file://#{fixtures}/pages/save_page/index.html" diff --git a/spec/fixtures/pages/save_page/index.html b/spec/fixtures/pages/save_page/index.html new file mode 100644 index 0000000000..829233bb86 --- /dev/null +++ b/spec/fixtures/pages/save_page/index.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/spec/fixtures/pages/save_page/test.css b/spec/fixtures/pages/save_page/test.css new file mode 100644 index 0000000000..f7df9d4fda --- /dev/null +++ b/spec/fixtures/pages/save_page/test.css @@ -0,0 +1 @@ +h1 { } diff --git a/spec/fixtures/pages/save_page/test.js b/spec/fixtures/pages/save_page/test.js new file mode 100644 index 0000000000..3d79457383 --- /dev/null +++ b/spec/fixtures/pages/save_page/test.js @@ -0,0 +1 @@ +console.log('save_page'); From 60b14d03e98dcbcc73098b36c337cb0ad47c828e Mon Sep 17 00:00:00 2001 From: Brandon Cash Date: Sun, 18 Oct 2015 00:32:13 -0500 Subject: [PATCH 024/146] Fix: tray NotfiyDropFiles typo --- atom/browser/ui/tray_icon.cc | 2 +- atom/browser/ui/tray_icon.h | 2 +- atom/browser/ui/tray_icon_cocoa.mm | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/atom/browser/ui/tray_icon.cc b/atom/browser/ui/tray_icon.cc index 12c6be2ea7..d2c6bc1b25 100644 --- a/atom/browser/ui/tray_icon.cc +++ b/atom/browser/ui/tray_icon.cc @@ -55,7 +55,7 @@ void TrayIcon::NotifyRightClicked(const gfx::Rect& bounds, int modifiers) { OnRightClicked(bounds, modifiers)); } -void TrayIcon::NotfiyDropFiles(const std::vector& files) { +void TrayIcon::NotifyDropFiles(const std::vector& files) { FOR_EACH_OBSERVER(TrayIconObserver, observers_, OnDropFiles(files)); } diff --git a/atom/browser/ui/tray_icon.h b/atom/browser/ui/tray_icon.h index af774ddbfb..539fe04f4b 100644 --- a/atom/browser/ui/tray_icon.h +++ b/atom/browser/ui/tray_icon.h @@ -61,7 +61,7 @@ class TrayIcon { void NotifyBalloonClosed(); void NotifyRightClicked(const gfx::Rect& bounds = gfx::Rect(), int modifiers = 0); - void NotfiyDropFiles(const std::vector& files); + void NotifyDropFiles(const std::vector& files); protected: TrayIcon(); diff --git a/atom/browser/ui/tray_icon_cocoa.mm b/atom/browser/ui/tray_icon_cocoa.mm index ec6a6a3e19..34ca4e9a91 100644 --- a/atom/browser/ui/tray_icon_cocoa.mm +++ b/atom/browser/ui/tray_icon_cocoa.mm @@ -265,7 +265,7 @@ const CGFloat kVerticalTitleMargin = 2; NSArray* files = [pboard propertyListForType:NSFilenamesPboardType]; for (NSString* file in files) dropFiles.push_back(base::SysNSStringToUTF8(file)); - trayIcon_->NotfiyDropFiles(dropFiles); + trayIcon_->NotifyDropFiles(dropFiles); return YES; } return NO; From 0aefa98e5ad545761f14c28bc57d957e317b6dd2 Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Mon, 19 Oct 2015 11:34:13 +0900 Subject: [PATCH 025/146] Small fix in README --- README-ko.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README-ko.md b/README-ko.md index c722243eb5..4432c96f82 100644 --- a/README-ko.md +++ b/README-ko.md @@ -49,7 +49,7 @@ Electron을 빌드 하는 방법과 프로젝트에 기여하는 방법도 문 ## 커뮤니티 -다음 링크를 통해 커뮤니티에 질문을 올리거나 토론을 나누실 수 있습니다: +다음 링크를 통해 커뮤니티에 질문을 올리거나 토론을 나눌 수 있습니다: - Atom 포럼의 [`electron`](http://discuss.atom.io/category/electron) 카테고리 - Freenode 채팅의 `#atom-shell` 채널 From 810af2f95d34632b82d9a26dad833ce854272627 Mon Sep 17 00:00:00 2001 From: Prince J Wesley Date: Mon, 19 Oct 2015 08:33:22 +0530 Subject: [PATCH 026/146] :bug: Honor fs.readFileSync contract for empty packed asar file --- atom/common/lib/asar.coffee | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/atom/common/lib/asar.coffee b/atom/common/lib/asar.coffee index 22d0e70b34..d2a7799fa6 100644 --- a/atom/common/lib/asar.coffee +++ b/atom/common/lib/asar.coffee @@ -263,7 +263,9 @@ exports.wrapFsWithAsar = (fs) -> info = archive.getFileInfo filePath notFoundError asarPath, filePath unless info - return new Buffer(0) if info.size is 0 + + if info.size is 0 + return if options then '' else new Buffer(0) if info.unpacked realPath = archive.copyFileOut filePath From d9c7401d0b7e5e8aa73ee5d315d0b09d191c0f63 Mon Sep 17 00:00:00 2001 From: Jhen Date: Mon, 19 Oct 2015 21:29:10 +0800 Subject: [PATCH 027/146] Replace io.js with node.js for zh-TW docs translation --- docs-translations/zh-TW/tutorial/quick-start.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs-translations/zh-TW/tutorial/quick-start.md b/docs-translations/zh-TW/tutorial/quick-start.md index aee2baa713..4655549167 100644 --- a/docs-translations/zh-TW/tutorial/quick-start.md +++ b/docs-translations/zh-TW/tutorial/quick-start.md @@ -3,7 +3,7 @@ ## 簡介 Electron 可以讓你使用純 JavaScript 提供豐富的原生的 APIs 來創造桌面應用程式。 -你可以把它視為一個 io.js 的變體,專注於桌面應用程式而不是 web 伺服器。 +你可以把它視為一個 Node.js 的變體,專注於桌面應用程式而不是 web 伺服器。 這不表示 Electron 是一個用 JavaScript 去綁定 GUI 函式庫。取而代之的,Electron 是使用網頁介面來作為它的 GUI , 所以你可以把它看作是一個被 JavaScript 所控制且精簡化的 Chromium 瀏覽器。 @@ -19,7 +19,7 @@ Electron 可以讓你使用純 JavaScript 提供豐富的原生的 APIs 來創 每一個網頁在 Electron 裏執行各自的行程,被稱為 __渲染行程__。 在一般瀏覽器中,網頁通常會在沙盒環境下運行,並且不允許存取原生資源。然而, -Electron 的用戶擁有在網頁中呼叫 io.js APIs 的能力,允許低級別操作與作業系統的交互作用。 +Electron 的用戶擁有在網頁中呼叫 Node.js APIs 的能力,允許低級別操作與作業系統的交互作用。 ## 主行程與渲染行程的區別 @@ -110,7 +110,7 @@ app.on('ready', function() {           

Hello World!

-    We are using io.js +    We are using Node.js     and Electron .    From 09ee24514e0a19b22a66e2b0e34a9fe51b88181d Mon Sep 17 00:00:00 2001 From: Jhen Date: Mon, 19 Oct 2015 21:49:57 +0800 Subject: [PATCH 028/146] Replace Mac with OS X for zh-TW docs translation --- docs-translations/zh-TW/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs-translations/zh-TW/README.md b/docs-translations/zh-TW/README.md index cc75ba51c1..bf2fc33867 100644 --- a/docs-translations/zh-TW/README.md +++ b/docs-translations/zh-TW/README.md @@ -64,7 +64,7 @@ * [源碼目錄結構](development/source-code-directory-structure.md) * [與 NW.js (原名node-webkit) 在技術上的差異](development/atom-shell-vs-node-webkit.md) * [構建系統概況](development/build-system-overview.md) -* [構建步驟 (Mac)](development/build-instructions-mac.md) +* [構建步驟 (OS X)](development/build-instructions-osx.md) * [構建步驟 (Windows)](development/build-instructions-windows.md) * [構建步驟 (Linux)](development/build-instructions-linux.md) * [在 debugger 中使用 symbol server](development/setting-up-symbol-server.md) From 7f15d63552bc9de71535246b64bddf267f4f5a31 Mon Sep 17 00:00:00 2001 From: Jhen Date: Mon, 19 Oct 2015 21:54:29 +0800 Subject: [PATCH 029/146] Update README.md for zh-TW docs-translations --- docs-translations/zh-TW/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs-translations/zh-TW/README.md b/docs-translations/zh-TW/README.md index bf2fc33867..1e69c97443 100644 --- a/docs-translations/zh-TW/README.md +++ b/docs-translations/zh-TW/README.md @@ -1,7 +1,9 @@ ## 導引 +* [支援平台](tutorial/supported-platforms.md) * [應用程式發布](tutorial/application-distribution.md) * [應用程式打包](tutorial/application-packaging.md) +* [Mac App Store 上架指引](tutorial/mac-app-store-submission-guide.md) * [使用原生 node 模組](tutorial/using-native-node-modules.md) * [主行程 Debug](tutorial/debugging-main-process.md) * [使用 Selenium 和 WebDriver](tutorial/using-selenium-and-webdriver.md) From 489090fcf82bf3f7f3bf5a65326b7e176e37340d Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Mon, 19 Oct 2015 13:03:20 -0700 Subject: [PATCH 030/146] Set up our browser context to use new delegate-based API --- atom/browser/atom_browser_context.cc | 10 ++++++++-- atom/browser/atom_browser_context.h | 3 +++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/atom/browser/atom_browser_context.cc b/atom/browser/atom_browser_context.cc index 6823fbaee9..13be18ce9b 100644 --- a/atom/browser/atom_browser_context.cc +++ b/atom/browser/atom_browser_context.cc @@ -60,8 +60,8 @@ std::string RemoveWhitespace(const std::string& str) { AtomBrowserContext::AtomBrowserContext(const std::string& partition, bool in_memory) : brightray::BrowserContext(partition, in_memory), - job_factory_(new AtomURLRequestJobFactory) { -} + job_factory_(new AtomURLRequestJobFactory), + allow_ntlm_everywhere_(false) : { } AtomBrowserContext::~AtomBrowserContext() { } @@ -168,6 +168,12 @@ void AtomBrowserContext::RegisterPrefs(PrefRegistrySimple* pref_registry) { base::FilePath()); } + +bool AtomBrowserContext::AllowNTLMCredentialsForDomain(const GURL& auth_origin) { + if (allow_ntlm_everywhere_) return true; + return brightray::URLRequestContextGetter::Delegate::AllowNTLMCredentialsForDomain(auth_origin); +} + } // namespace atom namespace brightray { diff --git a/atom/browser/atom_browser_context.h b/atom/browser/atom_browser_context.h index 839359c1ef..9ec3928272 100644 --- a/atom/browser/atom_browser_context.h +++ b/atom/browser/atom_browser_context.h @@ -28,6 +28,7 @@ class AtomBrowserContext : public brightray::BrowserContext { net::HttpCache::BackendFactory* CreateHttpCacheBackendFactory( const base::FilePath& base_path) override; net::SSLConfigService* CreateSSLConfigService() override; + bool AllowNTLMCredentialsForDomain(const GURL& auth_origin) override; // content::BrowserContext: content::DownloadManagerDelegate* GetDownloadManagerDelegate() override; @@ -44,6 +45,8 @@ class AtomBrowserContext : public brightray::BrowserContext { // Managed by brightray::BrowserContext. AtomURLRequestJobFactory* job_factory_; + + bool allow_ntlm_everywhere_; DISALLOW_COPY_AND_ASSIGN(AtomBrowserContext); }; From b3c7e2bf337fe1a013b46acd625ae16e733004d3 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Mon, 19 Oct 2015 13:07:14 -0700 Subject: [PATCH 031/146] Plumb the method back --- atom/browser/atom_browser_context.cc | 8 +++++++- atom/browser/atom_browser_context.h | 4 +++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/atom/browser/atom_browser_context.cc b/atom/browser/atom_browser_context.cc index 13be18ce9b..75742f8c8e 100644 --- a/atom/browser/atom_browser_context.cc +++ b/atom/browser/atom_browser_context.cc @@ -61,7 +61,8 @@ AtomBrowserContext::AtomBrowserContext(const std::string& partition, bool in_memory) : brightray::BrowserContext(partition, in_memory), job_factory_(new AtomURLRequestJobFactory), - allow_ntlm_everywhere_(false) : { } + allow_ntlm_everywhere_(false) { +} AtomBrowserContext::~AtomBrowserContext() { } @@ -174,6 +175,11 @@ bool AtomBrowserContext::AllowNTLMCredentialsForDomain(const GURL& auth_origin) return brightray::URLRequestContextGetter::Delegate::AllowNTLMCredentialsForDomain(auth_origin); } + +void AtomBrowserContext::AllowNTLMCredentialsForAllDomains(bool should_allow) { + allow_ntlm_everywhere_ = should_allow; +} + } // namespace atom namespace brightray { diff --git a/atom/browser/atom_browser_context.h b/atom/browser/atom_browser_context.h index 9ec3928272..d4b7d724b4 100644 --- a/atom/browser/atom_browser_context.h +++ b/atom/browser/atom_browser_context.h @@ -19,7 +19,7 @@ class AtomBrowserContext : public brightray::BrowserContext { public: AtomBrowserContext(const std::string& partition, bool in_memory); ~AtomBrowserContext() override; - + // brightray::URLRequestContextGetter::Delegate: std::string GetUserAgent() override; net::URLRequestJobFactory* CreateURLRequestJobFactory( @@ -36,6 +36,8 @@ class AtomBrowserContext : public brightray::BrowserContext { // brightray::BrowserContext: void RegisterPrefs(PrefRegistrySimple* pref_registry) override; + + void AllowNTLMCredentialsForAllDomains(bool should_allow); AtomURLRequestJobFactory* job_factory() const { return job_factory_; } From e3535d664b49bcc0a88b9d73dd297840e66d4f61 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Mon, 19 Oct 2015 13:12:17 -0700 Subject: [PATCH 032/146] Fixed linter issues but now the code looks awful --- atom/browser/atom_browser_context.cc | 8 +++++--- atom/browser/atom_browser_context.h | 6 +++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/atom/browser/atom_browser_context.cc b/atom/browser/atom_browser_context.cc index 75742f8c8e..b0a9897352 100644 --- a/atom/browser/atom_browser_context.cc +++ b/atom/browser/atom_browser_context.cc @@ -61,7 +61,7 @@ AtomBrowserContext::AtomBrowserContext(const std::string& partition, bool in_memory) : brightray::BrowserContext(partition, in_memory), job_factory_(new AtomURLRequestJobFactory), - allow_ntlm_everywhere_(false) { + allow_ntlm_everywhere_(false) { } AtomBrowserContext::~AtomBrowserContext() { @@ -170,9 +170,11 @@ void AtomBrowserContext::RegisterPrefs(PrefRegistrySimple* pref_registry) { } -bool AtomBrowserContext::AllowNTLMCredentialsForDomain(const GURL& auth_origin) { +bool AtomBrowserContext::AllowNTLMCredentialsForDomain + (const GURL& auth_origin) { if (allow_ntlm_everywhere_) return true; - return brightray::URLRequestContextGetter::Delegate::AllowNTLMCredentialsForDomain(auth_origin); + return brightray::URLRequestContextGetter + ::Delegate::AllowNTLMCredentialsForDomain(auth_origin); } diff --git a/atom/browser/atom_browser_context.h b/atom/browser/atom_browser_context.h index d4b7d724b4..aafa092442 100644 --- a/atom/browser/atom_browser_context.h +++ b/atom/browser/atom_browser_context.h @@ -19,7 +19,7 @@ class AtomBrowserContext : public brightray::BrowserContext { public: AtomBrowserContext(const std::string& partition, bool in_memory); ~AtomBrowserContext() override; - + // brightray::URLRequestContextGetter::Delegate: std::string GetUserAgent() override; net::URLRequestJobFactory* CreateURLRequestJobFactory( @@ -36,7 +36,7 @@ class AtomBrowserContext : public brightray::BrowserContext { // brightray::BrowserContext: void RegisterPrefs(PrefRegistrySimple* pref_registry) override; - + void AllowNTLMCredentialsForAllDomains(bool should_allow); AtomURLRequestJobFactory* job_factory() const { return job_factory_; } @@ -47,7 +47,7 @@ class AtomBrowserContext : public brightray::BrowserContext { // Managed by brightray::BrowserContext. AtomURLRequestJobFactory* job_factory_; - + bool allow_ntlm_everywhere_; DISALLOW_COPY_AND_ASSIGN(AtomBrowserContext); From 9f8479e9d8e56c82871c663f0114bc8044d44a9f Mon Sep 17 00:00:00 2001 From: Robo Date: Tue, 20 Oct 2015 02:26:01 +0530 Subject: [PATCH 033/146] browser: flag to diable specified cipher suites --- atom/browser/atom_ssl_config_service.cc | 27 ++++++++++++++++++++++++ atom/common/options_switches.cc | 3 +++ atom/common/options_switches.h | 1 + docs/api/chrome-command-line-switches.md | 4 ++++ 4 files changed, 35 insertions(+) diff --git a/atom/browser/atom_ssl_config_service.cc b/atom/browser/atom_ssl_config_service.cc index f19dbacf7d..0a47067b0a 100644 --- a/atom/browser/atom_ssl_config_service.cc +++ b/atom/browser/atom_ssl_config_service.cc @@ -5,11 +5,14 @@ #include "atom/browser/atom_ssl_config_service.h" #include +#include #include "base/command_line.h" +#include "base/strings/string_split.h" #include "atom/common/options_switches.h" #include "content/public/browser/browser_thread.h" #include "net/socket/ssl_client_socket.h" +#include "net/ssl/ssl_cipher_suite_names.h" namespace atom { @@ -26,6 +29,23 @@ uint16 GetSSLProtocolVersion(const std::string& version_string) { return version; } +std::vector ParseCipherSuites( + const std::vector& cipher_strings) { + std::vector cipher_suites; + cipher_suites.reserve(cipher_strings.size()); + + for (auto& cipher_string : cipher_strings) { + uint16 cipher_suite = 0; + if (!net::ParseSSLCipherString(cipher_string, &cipher_suite)) { + LOG(ERROR) << "Ignoring unrecognised cipher suite : " + << cipher_string; + continue; + } + cipher_suites.push_back(cipher_suite); + } + return cipher_suites; +} + } // namespace AtomSSLConfigService::AtomSSLConfigService() { @@ -35,6 +55,13 @@ AtomSSLConfigService::AtomSSLConfigService() { cmd_line->GetSwitchValueASCII(switches::kSSLVersionFallbackMin); config_.version_fallback_min = GetSSLProtocolVersion(version_string); } + + if (cmd_line->HasSwitch(switches::kCipherSuiteBlacklist)) { + auto cipher_strings = base::SplitString( + cmd_line->GetSwitchValueASCII(switches::kCipherSuiteBlacklist), + ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + config_.disabled_cipher_suites = ParseCipherSuites(cipher_strings); + } } AtomSSLConfigService::~AtomSSLConfigService() { diff --git a/atom/common/options_switches.cc b/atom/common/options_switches.cc index 46687becf8..84d63c29a0 100644 --- a/atom/common/options_switches.cc +++ b/atom/common/options_switches.cc @@ -117,6 +117,9 @@ const char kRegisterStandardSchemes[] = "register-standard-schemes"; // TLS fallback will accept. const char kSSLVersionFallbackMin[] = "ssl-version-fallback-min"; +// Comma-separated list of SSL cipher suites to disable. +const char kCipherSuiteBlacklist[] = "cipher-suite-blacklist"; + // The browser process app model ID const char kAppUserModelId[] = "app-user-model-id"; diff --git a/atom/common/options_switches.h b/atom/common/options_switches.h index 16046d19c8..73312d028d 100644 --- a/atom/common/options_switches.h +++ b/atom/common/options_switches.h @@ -60,6 +60,7 @@ extern const char kPageVisibility[]; extern const char kDisableHttpCache[]; extern const char kRegisterStandardSchemes[]; extern const char kSSLVersionFallbackMin[]; +extern const char kCipherSuiteBlacklist[]; extern const char kAppUserModelId[]; diff --git a/docs/api/chrome-command-line-switches.md b/docs/api/chrome-command-line-switches.md index c2a39126f6..abf8726b68 100644 --- a/docs/api/chrome-command-line-switches.md +++ b/docs/api/chrome-command-line-switches.md @@ -92,6 +92,10 @@ Enables net log events to be saved and writes them to `path`. Sets the minimum SSL/TLS version ("tls1", "tls1.1" or "tls1.2") that TLS fallback will accept. +## --cipher-suite-blacklist=`cipher_suites` + +Specify comma-separated list of SSL cipher suites to disable. + ## --enable-logging Prints Chromium's logging into console. From bc7c7da799eb7ce1d066b48db7819702084868da Mon Sep 17 00:00:00 2001 From: Jessica Lord Date: Mon, 19 Oct 2015 15:52:02 -0700 Subject: [PATCH 034/146] Add links to electron-quick-start --- README.md | 5 +++++ docs/tutorial/quick-start.md | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/README.md b/README.md index 94d4c09982..dd03f7099e 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,11 @@ contains documents describing how to build and contribute to Electron. - [Simplified Chinese](https://github.com/atom/electron/tree/master/docs-translations/zh-CN) - [Traditional Chinese](https://github.com/atom/electron/tree/master/docs-translations/zh-TW) +## Quick Start + +Clone and run the [`atom/electron-quick-start`](https://github.com/atom/electron-quick-start) +repository to see a minimal Electron app in action. + ## Community You can ask questions and interact with the community in the following diff --git a/docs/tutorial/quick-start.md b/docs/tutorial/quick-start.md index 5300d754a6..40988d92df 100644 --- a/docs/tutorial/quick-start.md +++ b/docs/tutorial/quick-start.md @@ -188,3 +188,19 @@ it from [here](https://github.com/atom/electron/releases). After you're done writing your app, you can create a distribution by following the [Application Distribution](./application-distribution.md) guide and then executing the packaged app. + +### Try this Example + +Clone and run the code in this tutorial by using the [`atom/electron-quick-start`](https://github.com/atom/electron-quick-start) +repository. + +**Note**: Running this requires [Git](https://git-scm.com) and [Node.js](https://nodejs.org/en/download/) (which includes [npm](https://npmjs.org)) on your system. + +```bash +# Clone the repository +$ git clone https://github.com/atom/electron-quick-start +# Go into the repository +$ cd electron-quick-start +# Install dependencies and run the app +$ npm install && npm start +``` From d09b09b744ac65c649106add357ab69dac72ae40 Mon Sep 17 00:00:00 2001 From: Jessica Lord Date: Mon, 19 Oct 2015 16:37:18 -0700 Subject: [PATCH 035/146] Match the electron-quick-start repo --- docs/tutorial/quick-start.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/tutorial/quick-start.md b/docs/tutorial/quick-start.md index 5300d754a6..9ad477d0c7 100644 --- a/docs/tutorial/quick-start.md +++ b/docs/tutorial/quick-start.md @@ -130,8 +130,9 @@ Finally the `index.html` is the web page you want to show:

Hello World!

- We are using Node.js - and Electron . + We are using node , + Chrome , + and Electron . ``` From 5a46147e9b3f8a7a8c7d07570891d9b9a2339d06 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 20 Oct 2015 10:34:33 +0800 Subject: [PATCH 036/146] Update brightray for atom/brightray#158 --- vendor/brightray | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/brightray b/vendor/brightray index fe2dd437c9..c3c6c3b311 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit fe2dd437c9ef7877bf9d454db8ae401965cd7cb0 +Subproject commit c3c6c3b311be326611a4018af9feadf6fb5e03e8 From 2c4a24d26b8b2353c13fb3bc1ccc963c1107d377 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 20 Oct 2015 13:00:28 +0800 Subject: [PATCH 037/146] Add ELECTRON_ENABLE_LOGGING env It is the same with --enable-logging. --- atom/app/atom_main_delegate.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/atom/app/atom_main_delegate.cc b/atom/app/atom_main_delegate.cc index 44f2048c09..392e4dcb36 100644 --- a/atom/app/atom_main_delegate.cc +++ b/atom/app/atom_main_delegate.cc @@ -59,7 +59,9 @@ bool AtomMainDelegate::BasicStartupComplete(int* exit_code) { #endif // !defined(OS_WIN) // Only enable logging when --enable-logging is specified. - if (!command_line->HasSwitch(switches::kEnableLogging)) { + scoped_ptr env(base::Environment::Create()); + if (!command_line->HasSwitch(switches::kEnableLogging) && + !env->HasVar("ELECTRON_ENABLE_LOGGING")) { settings.logging_dest = logging::LOG_NONE; logging::SetMinLogLevel(logging::LOG_NUM_SEVERITIES); } From fb8ca33d5d88ff09713dd6dde65930a9051cb58a Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 20 Oct 2015 13:02:48 +0800 Subject: [PATCH 038/146] Add ELECTRON_ENABLE_STACK_DUMPING env It enables in-process stack dumping, which is convenient for debugging. --- atom/app/atom_main_delegate.cc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/atom/app/atom_main_delegate.cc b/atom/app/atom_main_delegate.cc index 392e4dcb36..e4ac7cfc0a 100644 --- a/atom/app/atom_main_delegate.cc +++ b/atom/app/atom_main_delegate.cc @@ -71,10 +71,13 @@ bool AtomMainDelegate::BasicStartupComplete(int* exit_code) { // Logging with pid and timestamp. logging::SetLogItems(true, false, true, false); -#if defined(DEBUG) && defined(OS_LINUX) // Enable convient stack printing. - base::debug::EnableInProcessStackDumping(); + bool enable_stack_dumping = env->HasVar("ELECTRON_ENABLE_STACK_DUMPING"); +#if defined(DEBUG) && defined(OS_LINUX) + enable_stack_dumping = true; #endif + if (enable_stack_dumping) + base::debug::EnableInProcessStackDumping(); return brightray::MainDelegate::BasicStartupComplete(exit_code); } From be36a17dbf6ce69a3341077d6c955c09c1244e57 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 20 Oct 2015 13:06:41 +0800 Subject: [PATCH 039/146] docs: Mention the ELECTRON_ENABLE_LOGGING env --- docs/api/chrome-command-line-switches.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/api/chrome-command-line-switches.md b/docs/api/chrome-command-line-switches.md index abf8726b68..69e785f79e 100644 --- a/docs/api/chrome-command-line-switches.md +++ b/docs/api/chrome-command-line-switches.md @@ -100,7 +100,9 @@ Specify comma-separated list of SSL cipher suites to disable. Prints Chromium's logging into console. -This switch can not be used in `app.commandLine.appendSwitch` since it is parsed earlier than user's app is loaded. +This switch can not be used in `app.commandLine.appendSwitch` since it is parsed +earlier than user's app is loaded, but you can set the `ELECTRON_ENABLE_LOGGING` +environment variable to achieve the same effect. ## --v=`log_level` From 14e2bbe4c76f86b4e8b6822eaf463216cb3bdd8a Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 20 Oct 2015 13:50:19 +0800 Subject: [PATCH 040/146] Update brightray for #3120 --- vendor/brightray | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/brightray b/vendor/brightray index c3c6c3b311..f1cbfd1d45 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit c3c6c3b311be326611a4018af9feadf6fb5e03e8 +Subproject commit f1cbfd1d457f9b40bca23a2f30948a093d6048a9 From a7a399dcd8ba60ff7aaf4c5c8de14009de69657f Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 20 Oct 2015 13:52:39 +0800 Subject: [PATCH 041/146] Code cleanup --- atom/browser/atom_browser_context.cc | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/atom/browser/atom_browser_context.cc b/atom/browser/atom_browser_context.cc index b0a9897352..6cfb160489 100644 --- a/atom/browser/atom_browser_context.cc +++ b/atom/browser/atom_browser_context.cc @@ -169,15 +169,12 @@ void AtomBrowserContext::RegisterPrefs(PrefRegistrySimple* pref_registry) { base::FilePath()); } - -bool AtomBrowserContext::AllowNTLMCredentialsForDomain - (const GURL& auth_origin) { - if (allow_ntlm_everywhere_) return true; - return brightray::URLRequestContextGetter - ::Delegate::AllowNTLMCredentialsForDomain(auth_origin); +bool AtomBrowserContext::AllowNTLMCredentialsForDomain(const GURL& origin) { + if (allow_ntlm_everywhere_) + return true; + return Delegate::AllowNTLMCredentialsForDomain(origin); } - void AtomBrowserContext::AllowNTLMCredentialsForAllDomains(bool should_allow) { allow_ntlm_everywhere_ = should_allow; } From c92889462789b540f6f3f583a10ddd80af8e95b7 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 20 Oct 2015 13:56:31 +0800 Subject: [PATCH 042/146] docs: app.allowNTLMCredentialsForAllDomains --- docs/api/app.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/api/app.md b/docs/api/app.md index 0b5780aab7..bd49919ce7 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -99,7 +99,7 @@ You should call `event.preventDefault()` if you want to handle this event. Returns: * `event` Event -* `hasVisibleWindows` Bool +* `hasVisibleWindows` Boolean Emitted when the application is activated, which usually happens when clicks on the applications's dock icon. @@ -280,6 +280,13 @@ Adds `tasks` to the [Tasks][tasks] category of the JumpList on Windows. consists of two or more icons, set this value to identify the icon. If an icon file consists of one icon, this value is 0. +### `app.allowNTLMCredentialsForAllDomains(allow)` + +* `allow` Boolean + +Dynamically sets whether to always send credentials for HTTP NTLM or Negotiate +authentication. + ### `app.commandLine.appendSwitch(switch[, value])` Append a switch (with optional `value`) to Chromium's command line. From 7a0db35d914928c759667b99d2a8813dc3b00356 Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Wed, 21 Oct 2015 00:25:24 +0900 Subject: [PATCH 043/146] Update as upstream --- README-ko.md | 4 ++++ docs-translations/ko-KR/api/app.md | 10 ++++++-- .../ko-KR/api/chrome-command-line-switches.md | 9 +++++-- .../ko-KR/tutorial/quick-start.md | 24 ++++++++++++++++--- 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/README-ko.md b/README-ko.md index 4432c96f82..c8a3e6c59f 100644 --- a/README-ko.md +++ b/README-ko.md @@ -47,6 +47,10 @@ Electron을 빌드 하는 방법과 프로젝트에 기여하는 방법도 문 - [중국어 간체](https://github.com/atom/electron/tree/master/docs-translations/zh-CN) - [중국어 번체](https://github.com/atom/electron/tree/master/docs-translations/zh-TW) +## 시작하기 + +[`atom/electron-quick-start`](https://github.com/atom/electron-quick-start) 저장소를 클론하여 Electron을 간단히 접해볼 수 있습니다. + ## 커뮤니티 다음 링크를 통해 커뮤니티에 질문을 올리거나 토론을 나눌 수 있습니다: diff --git a/docs-translations/ko-KR/api/app.md b/docs-translations/ko-KR/api/app.md index 18354a70bd..c364d9dc60 100644 --- a/docs-translations/ko-KR/api/app.md +++ b/docs-translations/ko-KR/api/app.md @@ -98,7 +98,7 @@ Returns: Returns: * `event` Event -* `hasVisibleWindows` Bool +* `hasVisibleWindows` Boolean 어플리케이션이 활성화 되었을 때 발생하는 이벤트 입니다. 이 이벤트는 어플리케이션의 dock 아이콘을 클릭했을 때 주로 발생합니다. @@ -277,7 +277,13 @@ Windows에서 사용할 수 있는 JumpList의 [Tasks][tasks] 카테고리에 `t 보통 어플리케이션의 아이콘을 그대로 사용하기 위해 `process.execPath`를 지정합니다. * `iconIndex` Integer - 아이콘 파일의 인덱스. 만약 아이콘 파일이 두 개 이상의 아이콘을 가지고 있을 경우, 사용할 아이콘의 인덱스를 이 옵션으로 지정해 주어야 합니다. 단, 아이콘을 하나만 포함하고 있는 경우 0을 지정하면 됩니다. - + +### `app.allowNTLMCredentialsForAllDomains(allow)` + +* `allow` Boolean + +항상 동적으로 HTTP NTLM 또는 Negotiate 인증에 자격 증명을 보낼 것인지 설정합니다. + ### `app.commandLine.appendSwitch(switch[, value])` Chrominum의 명령줄에 스위치를 추가합니다. `value`는 추가적인 값을 뜻하며 옵션입니다. diff --git a/docs-translations/ko-KR/api/chrome-command-line-switches.md b/docs-translations/ko-KR/api/chrome-command-line-switches.md index 41517e08ca..c3ef6ee33b 100644 --- a/docs-translations/ko-KR/api/chrome-command-line-switches.md +++ b/docs-translations/ko-KR/api/chrome-command-line-switches.md @@ -44,7 +44,7 @@ HTTP 요청 캐시를 비활성화 합니다. ## --host-rules=`rules` -Hostname 맵핑 규칙을 설정합니다. (`,`로 분리) +Hostname 맵핑 규칙을 설정합니다. (`,`로 구분) 예시: @@ -86,11 +86,16 @@ Net log 이벤트를 활성화하고 `path`에 로그를 기록합니다. TLS fallback에서 사용할 SSL/TLS 최소 버전을 지정합니다. ("tls1", "tls1.1", "tls1.2") +## --cipher-suite-blacklist=`cipher_suites` + +SSL 암호화를 비활성화할 대상 목록을 지정합니다. (`,`로 구분) + ## --enable-logging Chromium의 로그를 콘솔에 출력합니다. -이 스위치는 어플리케이션이 로드되기 전에 파싱 되므로 `app.commandLine.appendSwitch`에서 사용할 수 없습니다. +이 스위치는 어플리케이션이 로드되기 전에 인식 되므로 `app.commandLine.appendSwitch` 메서드에선 사용할 수 없습니다. +하지만 `ELECTRON_ENABLE_LOGGING` 환경 변수를 설정하면 본 스위치와 같은 효과를 낼 수 있습니다. ## --v=`log_level` diff --git a/docs-translations/ko-KR/tutorial/quick-start.md b/docs-translations/ko-KR/tutorial/quick-start.md index 3a0c38f04b..ce3c7e29e0 100644 --- a/docs-translations/ko-KR/tutorial/quick-start.md +++ b/docs-translations/ko-KR/tutorial/quick-start.md @@ -116,7 +116,8 @@ app.on('ready', function() {

헬로 월드!

- 이 어플리케이션은 Node.js 과 + 이 어플리케이션은 node , + Chrome , Electron 을 사용합니다. @@ -174,6 +175,23 @@ $ ./Electron.app/Contents/MacOS/Electron your-app/ 어플리케이션 실행파일은 `Electron`의 release 패키지에 포함되어 있습니다. [여기](https://github.com/atom/electron/releases)에서 다운로드 받을 수 있습니다. -### 배포용 파일 만들기 +### 배포용 실행 파일 만들기 -어플리케이션 작성을 완료했다면 [어플리케이션 배포](application-distribution.md) 가이드를 통해 제작한 앱을 본격적으로 배포할 수 있습니다. \ No newline at end of file +어플리케이션 작성을 모두 끝냈다면 [어플리케이션 배포](application-distribution.md) 가이드를 통해 제작한 앱을 패키징하고 배포할 수 있습니다. + +### 미리 작성된 앱 실행하기 + +[`atom/electron-quick-start`](https://github.com/atom/electron-quick-start) 저장소를 클론하면 이 가이드에서 작성한 예제 앱을 바로 실행해 볼 수 있습니다. + +**참고**: 이 예제를 실행시키려면 [Git](https://git-scm.com)과 [Node.js](https://nodejs.org/en/download/)가 필요합니다. (CLI에서 실행 가능한 [npm](https://npmjs.org)이 있어야 합니다) + +**역주**: `npm`은 보통 Node.js를 설치하면 자동으로 같이 설치됩니다. + +```bash +# 저장소를 클론합니다 +$ git clone https://github.com/atom/electron-quick-start +# 저장소 안으로 들어갑니다 +$ cd electron-quick-start +# 어플리케이션의 종속성 모듈을 설치한 후 실행합니다 +$ npm install && npm start +``` \ No newline at end of file From 1ad979f9bda5b7f1e7977ddf35319d58221b1fb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionic=C4=83=20Biz=C4=83u?= Date: Tue, 20 Oct 2015 19:44:01 +0300 Subject: [PATCH 044/146] Add some missing words ("be" and "the") --- docs/api/app.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/api/app.md b/docs/api/app.md index bd49919ce7..d59e0e40d0 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -169,7 +169,7 @@ The `app` object has the following methods: ### `app.quit()` -Try to close all windows. The `before-quit` event will emitted first. If all +Try to close all windows. The `before-quit` event will be emitted first. If all windows are successfully closed, the `will-quit` event will be emitted and by default the application will terminate. @@ -213,7 +213,7 @@ created by this method. On failure an `Error` is thrown. You can only override paths of a `name` defined in `app.getPath`. -By default, web pages's cookies and caches will be stored under the `userData` +By default, web pages' cookies and caches will be stored under the `userData` directory. If you want to change this location, you have to override the `userData` path before the `ready` event of the `app` module is emitted. @@ -264,7 +264,7 @@ Clears the recent documents list. Adds `tasks` to the [Tasks][tasks] category of the JumpList on Windows. -`tasks` is an array of `Task` objects in following format: +`tasks` is an array of `Task` objects in the following format: `Task` Object * `program` String - Path of the program to execute, usually you should From db3bc289379840b466517abb0f16a8d2e349073f Mon Sep 17 00:00:00 2001 From: Patrick Detlefsen Date: Tue, 20 Oct 2015 19:25:03 +0200 Subject: [PATCH 045/146] PR feedback adressed --- atom/browser/api/lib/auto-updater.coffee | 2 +- .../api/lib/auto-updater/auto-updater-win.coffee | 13 +++---------- .../api/lib/auto-updater/squirrel-update-win.coffee | 8 ++++---- filenames.gypi | 2 ++ 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/atom/browser/api/lib/auto-updater.coffee b/atom/browser/api/lib/auto-updater.coffee index a479f5fccf..c039bd12dc 100644 --- a/atom/browser/api/lib/auto-updater.coffee +++ b/atom/browser/api/lib/auto-updater.coffee @@ -1,6 +1,6 @@ switch process.platform when 'win32' - autoUpdater = require('./auto-updater/auto-updater-win') + autoUpdater = require './auto-updater/auto-updater-win' else # take the default binding for the current platform autoUpdater = process.atomBinding('auto_updater').autoUpdater diff --git a/atom/browser/api/lib/auto-updater/auto-updater-win.coffee b/atom/browser/api/lib/auto-updater/auto-updater-win.coffee index 9a2431577e..764f76a225 100644 --- a/atom/browser/api/lib/auto-updater/auto-updater-win.coffee +++ b/atom/browser/api/lib/auto-updater/auto-updater-win.coffee @@ -1,7 +1,7 @@ {EventEmitter} = require 'events' SquirrelUpdate = require './auto-updater/squirrel-update-win' -app = require('app') -url = require('url') +app = require 'app' +url = require 'url' class AutoUpdater extends EventEmitter @@ -12,14 +12,7 @@ class AutoUpdater extends EventEmitter setFeedUrl: (updateUrl) -> # set feed URL only when it hasn't been set before unless @updateUrl - # See https://github.com/Squirrel/Squirrel.Windows/issues/132 - # This way the Mac and Windows Update URL can be the same, even when - # the Mac version is sending additional data in the query string. - parsedUrl = url.parse(updateUrl) - delete parsedUrl.search - delete parsedUrl.query - - @updateUrl = url.format(parsedUrl) + @updateUrl = updateUrl checkForUpdates: -> throw new Error('Update URL is not set') unless @updateUrl diff --git a/atom/browser/api/lib/auto-updater/squirrel-update-win.coffee b/atom/browser/api/lib/auto-updater/squirrel-update-win.coffee index f07584f4c5..95d9e1465d 100644 --- a/atom/browser/api/lib/auto-updater/squirrel-update-win.coffee +++ b/atom/browser/api/lib/auto-updater/squirrel-update-win.coffee @@ -2,10 +2,10 @@ ChildProcess = require 'child_process' fs = require 'fs' path = require 'path' -appFolder = path.dirname(process.execPath) # i.e. my-app/app-0.1.13/ -rootApplicationFolder = path.resolve(appFolder, '..') # i.e. my-app/ -updateDotExe = path.join(rootApplicationFolder, 'Update.exe') -exeName = path.basename(process.execPath) +appFolder = path.dirname process.execPath # i.e. my-app/app-0.1.13/ +rootApplicationFolder = path.resolve appFolder, '..' # i.e. my-app/ +updateDotExe = path.join rootApplicationFolder, 'Update.exe' +exeName = path.basename process.execPath # Spawn a command and invoke the callback when it completes with an error # and the output from standard out. diff --git a/filenames.gypi b/filenames.gypi index ebd1fb41aa..35139dbdfc 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -11,6 +11,8 @@ 'atom/browser/api/lib/app.coffee', 'atom/browser/api/lib/atom-delegate.coffee', 'atom/browser/api/lib/auto-updater.coffee', + 'atom/browser/api/lib/auto-updater/auto-updater-win.coffee', + 'atom/browser/api/lib/auto-updater/squirrel-update-win.coffee', 'atom/browser/api/lib/browser-window.coffee', 'atom/browser/api/lib/content-tracing.coffee', 'atom/browser/api/lib/dialog.coffee', From da7161d5a7fcc3c7804eb22f51a705444d114f22 Mon Sep 17 00:00:00 2001 From: Nate Goldman Date: Tue, 20 Oct 2015 10:44:02 -0700 Subject: [PATCH 046/146] use webContents.openDevTools https://github.com/atom/electron/issues/3125#issuecomment-148975593 --- docs/tutorial/quick-start.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/quick-start.md b/docs/tutorial/quick-start.md index 052da3d91b..b023deccda 100644 --- a/docs/tutorial/quick-start.md +++ b/docs/tutorial/quick-start.md @@ -107,7 +107,7 @@ app.on('ready', function() { mainWindow.loadUrl('file://' + __dirname + '/index.html'); // Open the DevTools. - mainWindow.openDevTools(); + mainWindow.webContents.openDevTools(); // Emitted when the window is closed. mainWindow.on('closed', function() { From 285a4789b30feaa905ac61a642e309a7408e3d2c Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Tue, 20 Oct 2015 12:35:56 -0700 Subject: [PATCH 047/146] Update app.md --- docs/api/app.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/api/app.md b/docs/api/app.md index d59e0e40d0..41098d4279 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -285,7 +285,10 @@ Adds `tasks` to the [Tasks][tasks] category of the JumpList on Windows. * `allow` Boolean Dynamically sets whether to always send credentials for HTTP NTLM or Negotiate -authentication. +authentication - normally, Electron will only send NTLM/Kerberos credentials for +URLs that fall under "Local Intranet" sites (i.e. are in the same domain as you). +However, this detection often fails when corporate networks are badly configured, +so this lets you co-opt this behavior and enable it for all URLs. ### `app.commandLine.appendSwitch(switch[, value])` From 27fa5d880a7183e61c954014d637be82128e40f0 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Mon, 19 Oct 2015 11:16:51 -0700 Subject: [PATCH 048/146] Copy some relevant files over from Chromium --- atom/browser/process_singleton.h | 183 +++ atom/browser/process_singleton_posix.cc | 1062 +++++++++++++++++ .../browser/process_singleton_startup_lock.cc | 53 + atom/browser/process_singleton_startup_lock.h | 57 + atom/browser/process_singleton_win.cc | 441 +++++++ 5 files changed, 1796 insertions(+) create mode 100644 atom/browser/process_singleton.h create mode 100644 atom/browser/process_singleton_posix.cc create mode 100644 atom/browser/process_singleton_startup_lock.cc create mode 100644 atom/browser/process_singleton_startup_lock.h create mode 100644 atom/browser/process_singleton_win.cc diff --git a/atom/browser/process_singleton.h b/atom/browser/process_singleton.h new file mode 100644 index 0000000000..3260c4ef24 --- /dev/null +++ b/atom/browser/process_singleton.h @@ -0,0 +1,183 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_PROCESS_SINGLETON_H_ +#define CHROME_BROWSER_PROCESS_SINGLETON_H_ + +#include "build/build_config.h" + +#if defined(OS_WIN) +#include +#endif // defined(OS_WIN) + +#include +#include + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/process/process.h" +#include "base/threading/non_thread_safe.h" +#include "ui/gfx/native_widget_types.h" + +#if defined(OS_POSIX) && !defined(OS_ANDROID) +#include "base/files/scoped_temp_dir.h" +#endif + +#if defined(OS_WIN) +#include "base/win/message_window.h" +#endif // defined(OS_WIN) + +namespace base { +class CommandLine; +} + +// ProcessSingleton ---------------------------------------------------------- +// +// This class allows different browser processes to communicate with +// each other. It is named according to the user data directory, so +// we can be sure that no more than one copy of the application can be +// running at once with a given data directory. +// +// Implementation notes: +// - the Windows implementation uses an invisible global message window; +// - the Linux implementation uses a Unix domain socket in the user data dir. + +class ProcessSingleton : public base::NonThreadSafe { + public: + enum NotifyResult { + PROCESS_NONE, + PROCESS_NOTIFIED, + PROFILE_IN_USE, + LOCK_ERROR, + }; + + // Implement this callback to handle notifications from other processes. The + // callback will receive the command line and directory with which the other + // Chrome process was launched. Return true if the command line will be + // handled within the current browser instance or false if the remote process + // should handle it (i.e., because the current process is shutting down). + using NotificationCallback = + base::Callback; + + ProcessSingleton(const base::FilePath& user_data_dir, + const NotificationCallback& notification_callback); + ~ProcessSingleton(); + + // Notify another process, if available. Otherwise sets ourselves as the + // singleton instance. Returns PROCESS_NONE if we became the singleton + // instance. Callers are guaranteed to either have notified an existing + // process or have grabbed the singleton (unless the profile is locked by an + // unreachable process). + // TODO(brettw): Make the implementation of this method non-platform-specific + // by making Linux re-use the Windows implementation. + NotifyResult NotifyOtherProcessOrCreate(); + + // Sets ourself up as the singleton instance. Returns true on success. If + // false is returned, we are not the singleton instance and the caller must + // exit. + // NOTE: Most callers should generally prefer NotifyOtherProcessOrCreate() to + // this method, only callers for whom failure is preferred to notifying + // another process should call this directly. + bool Create(); + + // Clear any lock state during shutdown. + void Cleanup(); + +#if defined(OS_POSIX) && !defined(OS_ANDROID) + static void DisablePromptForTesting(); +#endif +#if defined(OS_WIN) + // Called to query whether to kill a hung browser process that has visible + // windows. Return true to allow killing the hung process. + using ShouldKillRemoteProcessCallback = base::Callback; + void OverrideShouldKillRemoteProcessCallbackForTesting( + const ShouldKillRemoteProcessCallback& display_dialog_callback); +#endif + + protected: + // Notify another process, if available. + // Returns true if another process was found and notified, false if we should + // continue with the current process. + // On Windows, Create() has to be called before this. + NotifyResult NotifyOtherProcess(); + +#if defined(OS_POSIX) && !defined(OS_ANDROID) + // Exposed for testing. We use a timeout on Linux, and in tests we want + // this timeout to be short. + NotifyResult NotifyOtherProcessWithTimeout( + const base::CommandLine& command_line, + int retry_attempts, + const base::TimeDelta& timeout, + bool kill_unresponsive); + NotifyResult NotifyOtherProcessWithTimeoutOrCreate( + const base::CommandLine& command_line, + int retry_attempts, + const base::TimeDelta& timeout); + void OverrideCurrentPidForTesting(base::ProcessId pid); + void OverrideKillCallbackForTesting( + const base::Callback& callback); +#endif + + private: + NotificationCallback notification_callback_; // Handler for notifications. + +#if defined(OS_WIN) + bool EscapeVirtualization(const base::FilePath& user_data_dir); + + HWND remote_window_; // The HWND_MESSAGE of another browser. + base::win::MessageWindow window_; // The message-only window. + bool is_virtualized_; // Stuck inside Microsoft Softricity VM environment. + HANDLE lock_file_; + base::FilePath user_data_dir_; + ShouldKillRemoteProcessCallback should_kill_remote_process_callback_; +#elif defined(OS_POSIX) && !defined(OS_ANDROID) + // Return true if the given pid is one of our child processes. + // Assumes that the current pid is the root of all pids of the current + // instance. + bool IsSameChromeInstance(pid_t pid); + + // Extract the process's pid from a symbol link path and if it is on + // the same host, kill the process, unlink the lock file and return true. + // If the process is part of the same chrome instance, unlink the lock file + // and return true without killing it. + // If the process is on a different host, return false. + bool KillProcessByLockPath(); + + // Default function to kill a process, overridable by tests. + void KillProcess(int pid); + + // Allow overriding for tests. + base::ProcessId current_pid_; + + // Function to call when the other process is hung and needs to be killed. + // Allows overriding for tests. + base::Callback kill_callback_; + + // Path in file system to the socket. + base::FilePath socket_path_; + + // Path in file system to the lock. + base::FilePath lock_path_; + + // Path in file system to the cookie file. + base::FilePath cookie_path_; + + // Temporary directory to hold the socket. + base::ScopedTempDir socket_dir_; + + // Helper class for linux specific messages. LinuxWatcher is ref counted + // because it posts messages between threads. + class LinuxWatcher; + scoped_refptr watcher_; +#endif + + DISALLOW_COPY_AND_ASSIGN(ProcessSingleton); +}; + +#endif // CHROME_BROWSER_PROCESS_SINGLETON_H_ diff --git a/atom/browser/process_singleton_posix.cc b/atom/browser/process_singleton_posix.cc new file mode 100644 index 0000000000..81a5c5fec3 --- /dev/null +++ b/atom/browser/process_singleton_posix.cc @@ -0,0 +1,1062 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// On Linux, when the user tries to launch a second copy of chrome, we check +// for a socket in the user's profile directory. If the socket file is open we +// send a message to the first chrome browser process with the current +// directory and second process command line flags. The second process then +// exits. +// +// Because many networked filesystem implementations do not support unix domain +// sockets, we create the socket in a temporary directory and create a symlink +// in the profile. This temporary directory is no longer bound to the profile, +// and may disappear across a reboot or login to a separate session. To bind +// them, we store a unique cookie in the profile directory, which must also be +// present in the remote directory to connect. The cookie is checked both before +// and after the connection. /tmp is sticky, and different Chrome sessions use +// different cookies. Thus, a matching cookie before and after means the +// connection was to a directory with a valid cookie. +// +// We also have a lock file, which is a symlink to a non-existent destination. +// The destination is a string containing the hostname and process id of +// chrome's browser process, eg. "SingletonLock -> example.com-9156". When the +// first copy of chrome exits it will delete the lock file on shutdown, so that +// a different instance on a different host may then use the profile directory. +// +// If writing to the socket fails, the hostname in the lock is checked to see if +// another instance is running a different host using a shared filesystem (nfs, +// etc.) If the hostname differs an error is displayed and the second process +// exits. Otherwise the first process (if any) is killed and the second process +// starts as normal. +// +// When the second process sends the current directory and command line flags to +// the first process, it waits for an ACK message back from the first process +// for a certain time. If there is no ACK message back in time, then the first +// process will be considered as hung for some reason. The second process then +// retrieves the process id from the symbol link and kills it by sending +// SIGKILL. Then the second process starts as normal. + +#include "chrome/browser/process_singleton.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "base/base_paths.h" +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/path_service.h" +#include "base/posix/eintr_wrapper.h" +#include "base/posix/safe_strerror.h" +#include "base/rand_util.h" +#include "base/sequenced_task_runner_helpers.h" +#include "base/single_thread_task_runner.h" +#include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/sys_string_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/threading/platform_thread.h" +#include "base/time/time.h" +#include "base/timer/timer.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/grit/chromium_strings.h" +#include "chrome/grit/generated_resources.h" +#include "content/public/browser/browser_thread.h" +#include "net/base/net_util.h" +#include "ui/base/l10n/l10n_util.h" + +#if defined(OS_LINUX) +#include "chrome/browser/ui/process_singleton_dialog_linux.h" +#endif + +#if defined(TOOLKIT_VIEWS) && defined(OS_LINUX) && !defined(OS_CHROMEOS) +#include "ui/views/linux_ui/linux_ui.h" +#endif + +using content::BrowserThread; + +namespace { + +// Timeout for the current browser process to respond. 20 seconds should be +// enough. +const int kTimeoutInSeconds = 20; +// Number of retries to notify the browser. 20 retries over 20 seconds = 1 try +// per second. +const int kRetryAttempts = 20; +static bool g_disable_prompt; +const char kStartToken[] = "START"; +const char kACKToken[] = "ACK"; +const char kShutdownToken[] = "SHUTDOWN"; +const char kTokenDelimiter = '\0'; +const int kMaxMessageLength = 32 * 1024; +const int kMaxACKMessageLength = arraysize(kShutdownToken) - 1; + +const char kLockDelimiter = '-'; + +// Set the close-on-exec bit on a file descriptor. +// Returns 0 on success, -1 on failure. +int SetCloseOnExec(int fd) { + int flags = fcntl(fd, F_GETFD, 0); + if (-1 == flags) + return flags; + if (flags & FD_CLOEXEC) + return 0; + return fcntl(fd, F_SETFD, flags | FD_CLOEXEC); +} + +// Close a socket and check return value. +void CloseSocket(int fd) { + int rv = IGNORE_EINTR(close(fd)); + DCHECK_EQ(0, rv) << "Error closing socket: " << base::safe_strerror(errno); +} + +// Write a message to a socket fd. +bool WriteToSocket(int fd, const char *message, size_t length) { + DCHECK(message); + DCHECK(length); + size_t bytes_written = 0; + do { + ssize_t rv = HANDLE_EINTR( + write(fd, message + bytes_written, length - bytes_written)); + if (rv < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // The socket shouldn't block, we're sending so little data. Just give + // up here, since NotifyOtherProcess() doesn't have an asynchronous api. + LOG(ERROR) << "ProcessSingleton would block on write(), so it gave up."; + return false; + } + PLOG(ERROR) << "write() failed"; + return false; + } + bytes_written += rv; + } while (bytes_written < length); + + return true; +} + +struct timeval TimeDeltaToTimeVal(const base::TimeDelta& delta) { + struct timeval result; + result.tv_sec = delta.InSeconds(); + result.tv_usec = delta.InMicroseconds() % base::Time::kMicrosecondsPerSecond; + return result; +} + +// Wait a socket for read for a certain timeout. +// Returns -1 if error occurred, 0 if timeout reached, > 0 if the socket is +// ready for read. +int WaitSocketForRead(int fd, const base::TimeDelta& timeout) { + fd_set read_fds; + struct timeval tv = TimeDeltaToTimeVal(timeout); + + FD_ZERO(&read_fds); + FD_SET(fd, &read_fds); + + return HANDLE_EINTR(select(fd + 1, &read_fds, NULL, NULL, &tv)); +} + +// Read a message from a socket fd, with an optional timeout. +// If |timeout| <= 0 then read immediately. +// Return number of bytes actually read, or -1 on error. +ssize_t ReadFromSocket(int fd, + char* buf, + size_t bufsize, + const base::TimeDelta& timeout) { + if (timeout > base::TimeDelta()) { + int rv = WaitSocketForRead(fd, timeout); + if (rv <= 0) + return rv; + } + + size_t bytes_read = 0; + do { + ssize_t rv = HANDLE_EINTR(read(fd, buf + bytes_read, bufsize - bytes_read)); + if (rv < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + PLOG(ERROR) << "read() failed"; + return rv; + } else { + // It would block, so we just return what has been read. + return bytes_read; + } + } else if (!rv) { + // No more data to read. + return bytes_read; + } else { + bytes_read += rv; + } + } while (bytes_read < bufsize); + + return bytes_read; +} + +// Set up a sockaddr appropriate for messaging. +void SetupSockAddr(const std::string& path, struct sockaddr_un* addr) { + addr->sun_family = AF_UNIX; + CHECK(path.length() < arraysize(addr->sun_path)) + << "Socket path too long: " << path; + base::strlcpy(addr->sun_path, path.c_str(), arraysize(addr->sun_path)); +} + +// Set up a socket appropriate for messaging. +int SetupSocketOnly() { + int sock = socket(PF_UNIX, SOCK_STREAM, 0); + PCHECK(sock >= 0) << "socket() failed"; + + int rv = net::SetNonBlocking(sock); + DCHECK_EQ(0, rv) << "Failed to make non-blocking socket."; + rv = SetCloseOnExec(sock); + DCHECK_EQ(0, rv) << "Failed to set CLOEXEC on socket."; + + return sock; +} + +// Set up a socket and sockaddr appropriate for messaging. +void SetupSocket(const std::string& path, int* sock, struct sockaddr_un* addr) { + *sock = SetupSocketOnly(); + SetupSockAddr(path, addr); +} + +// Read a symbolic link, return empty string if given path is not a symbol link. +base::FilePath ReadLink(const base::FilePath& path) { + base::FilePath target; + if (!base::ReadSymbolicLink(path, &target)) { + // The only errno that should occur is ENOENT. + if (errno != 0 && errno != ENOENT) + PLOG(ERROR) << "readlink(" << path.value() << ") failed"; + } + return target; +} + +// Unlink a path. Return true on success. +bool UnlinkPath(const base::FilePath& path) { + int rv = unlink(path.value().c_str()); + if (rv < 0 && errno != ENOENT) + PLOG(ERROR) << "Failed to unlink " << path.value(); + + return rv == 0; +} + +// Create a symlink. Returns true on success. +bool SymlinkPath(const base::FilePath& target, const base::FilePath& path) { + if (!base::CreateSymbolicLink(target, path)) { + // Double check the value in case symlink suceeded but we got an incorrect + // failure due to NFS packet loss & retry. + int saved_errno = errno; + if (ReadLink(path) != target) { + // If we failed to create the lock, most likely another instance won the + // startup race. + errno = saved_errno; + PLOG(ERROR) << "Failed to create " << path.value(); + return false; + } + } + return true; +} + +// Extract the hostname and pid from the lock symlink. +// Returns true if the lock existed. +bool ParseLockPath(const base::FilePath& path, + std::string* hostname, + int* pid) { + std::string real_path = ReadLink(path).value(); + if (real_path.empty()) + return false; + + std::string::size_type pos = real_path.rfind(kLockDelimiter); + + // If the path is not a symbolic link, or doesn't contain what we expect, + // bail. + if (pos == std::string::npos) { + *hostname = ""; + *pid = -1; + return true; + } + + *hostname = real_path.substr(0, pos); + + const std::string& pid_str = real_path.substr(pos + 1); + if (!base::StringToInt(pid_str, pid)) + *pid = -1; + + return true; +} + +// Returns true if the user opted to unlock the profile. +bool DisplayProfileInUseError(const base::FilePath& lock_path, + const std::string& hostname, + int pid) { + base::string16 error = l10n_util::GetStringFUTF16( + IDS_PROFILE_IN_USE_POSIX, + base::IntToString16(pid), + base::ASCIIToUTF16(hostname)); + LOG(ERROR) << error; + + if (g_disable_prompt) + return false; + +#if defined(OS_LINUX) + base::string16 relaunch_button_text = l10n_util::GetStringUTF16( + IDS_PROFILE_IN_USE_LINUX_RELAUNCH); + return ShowProcessSingletonDialog(error, relaunch_button_text); +#elif defined(OS_MACOSX) + // On Mac, always usurp the lock. + return true; +#endif + + NOTREACHED(); + return false; +} + +bool IsChromeProcess(pid_t pid) { + base::FilePath other_chrome_path(base::GetProcessExecutablePath(pid)); + return (!other_chrome_path.empty() && + other_chrome_path.BaseName() == + base::FilePath(chrome::kBrowserProcessExecutableName)); +} + +// A helper class to hold onto a socket. +class ScopedSocket { + public: + ScopedSocket() : fd_(-1) { Reset(); } + ~ScopedSocket() { Close(); } + int fd() { return fd_; } + void Reset() { + Close(); + fd_ = SetupSocketOnly(); + } + void Close() { + if (fd_ >= 0) + CloseSocket(fd_); + fd_ = -1; + } + private: + int fd_; +}; + +// Returns a random string for uniquifying profile connections. +std::string GenerateCookie() { + return base::Uint64ToString(base::RandUint64()); +} + +bool CheckCookie(const base::FilePath& path, const base::FilePath& cookie) { + return (cookie == ReadLink(path)); +} + +bool ConnectSocket(ScopedSocket* socket, + const base::FilePath& socket_path, + const base::FilePath& cookie_path) { + base::FilePath socket_target; + if (base::ReadSymbolicLink(socket_path, &socket_target)) { + // It's a symlink. Read the cookie. + base::FilePath cookie = ReadLink(cookie_path); + if (cookie.empty()) + return false; + base::FilePath remote_cookie = socket_target.DirName(). + Append(chrome::kSingletonCookieFilename); + // Verify the cookie before connecting. + if (!CheckCookie(remote_cookie, cookie)) + return false; + // Now we know the directory was (at that point) created by the profile + // owner. Try to connect. + sockaddr_un addr; + SetupSockAddr(socket_target.value(), &addr); + int ret = HANDLE_EINTR(connect(socket->fd(), + reinterpret_cast(&addr), + sizeof(addr))); + if (ret != 0) + return false; + // Check the cookie again. We only link in /tmp, which is sticky, so, if the + // directory is still correct, it must have been correct in-between when we + // connected. POSIX, sadly, lacks a connectat(). + if (!CheckCookie(remote_cookie, cookie)) { + socket->Reset(); + return false; + } + // Success! + return true; + } else if (errno == EINVAL) { + // It exists, but is not a symlink (or some other error we detect + // later). Just connect to it directly; this is an older version of Chrome. + sockaddr_un addr; + SetupSockAddr(socket_path.value(), &addr); + int ret = HANDLE_EINTR(connect(socket->fd(), + reinterpret_cast(&addr), + sizeof(addr))); + return (ret == 0); + } else { + // File is missing, or other error. + if (errno != ENOENT) + PLOG(ERROR) << "readlink failed"; + return false; + } +} + +#if defined(OS_MACOSX) +bool ReplaceOldSingletonLock(const base::FilePath& symlink_content, + const base::FilePath& lock_path) { + // Try taking an flock(2) on the file. Failure means the lock is taken so we + // should quit. + base::ScopedFD lock_fd(HANDLE_EINTR( + open(lock_path.value().c_str(), O_RDWR | O_CREAT | O_SYMLINK, 0644))); + if (!lock_fd.is_valid()) { + PLOG(ERROR) << "Could not open singleton lock"; + return false; + } + + int rc = HANDLE_EINTR(flock(lock_fd.get(), LOCK_EX | LOCK_NB)); + if (rc == -1) { + if (errno == EWOULDBLOCK) { + LOG(ERROR) << "Singleton lock held by old process."; + } else { + PLOG(ERROR) << "Error locking singleton lock"; + } + return false; + } + + // Successfully taking the lock means we can replace it with the a new symlink + // lock. We never flock() the lock file from now on. I.e. we assume that an + // old version of Chrome will not run with the same user data dir after this + // version has run. + if (!base::DeleteFile(lock_path, false)) { + PLOG(ERROR) << "Could not delete old singleton lock."; + return false; + } + + return SymlinkPath(symlink_content, lock_path); +} +#endif // defined(OS_MACOSX) + +} // namespace + +/////////////////////////////////////////////////////////////////////////////// +// ProcessSingleton::LinuxWatcher +// A helper class for a Linux specific implementation of the process singleton. +// This class sets up a listener on the singleton socket and handles parsing +// messages that come in on the singleton socket. +class ProcessSingleton::LinuxWatcher + : public base::MessageLoopForIO::Watcher, + public base::MessageLoop::DestructionObserver, + public base::RefCountedThreadSafe { + public: + // A helper class to read message from an established socket. + class SocketReader : public base::MessageLoopForIO::Watcher { + public: + SocketReader(ProcessSingleton::LinuxWatcher* parent, + base::MessageLoop* ui_message_loop, + int fd) + : parent_(parent), + ui_message_loop_(ui_message_loop), + fd_(fd), + bytes_read_(0) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + // Wait for reads. + base::MessageLoopForIO::current()->WatchFileDescriptor( + fd, true, base::MessageLoopForIO::WATCH_READ, &fd_reader_, this); + // If we haven't completed in a reasonable amount of time, give up. + timer_.Start(FROM_HERE, base::TimeDelta::FromSeconds(kTimeoutInSeconds), + this, &SocketReader::CleanupAndDeleteSelf); + } + + ~SocketReader() override { CloseSocket(fd_); } + + // MessageLoopForIO::Watcher impl. + void OnFileCanReadWithoutBlocking(int fd) override; + void OnFileCanWriteWithoutBlocking(int fd) override { + // SocketReader only watches for accept (read) events. + NOTREACHED(); + } + + // Finish handling the incoming message by optionally sending back an ACK + // message and removing this SocketReader. + void FinishWithACK(const char *message, size_t length); + + private: + void CleanupAndDeleteSelf() { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + + parent_->RemoveSocketReader(this); + // We're deleted beyond this point. + } + + base::MessageLoopForIO::FileDescriptorWatcher fd_reader_; + + // The ProcessSingleton::LinuxWatcher that owns us. + ProcessSingleton::LinuxWatcher* const parent_; + + // A reference to the UI message loop. + base::MessageLoop* const ui_message_loop_; + + // The file descriptor we're reading. + const int fd_; + + // Store the message in this buffer. + char buf_[kMaxMessageLength]; + + // Tracks the number of bytes we've read in case we're getting partial + // reads. + size_t bytes_read_; + + base::OneShotTimer timer_; + + DISALLOW_COPY_AND_ASSIGN(SocketReader); + }; + + // We expect to only be constructed on the UI thread. + explicit LinuxWatcher(ProcessSingleton* parent) + : ui_message_loop_(base::MessageLoop::current()), + parent_(parent) { + } + + // Start listening for connections on the socket. This method should be + // called from the IO thread. + void StartListening(int socket); + + // This method determines if we should use the same process and if we should, + // opens a new browser tab. This runs on the UI thread. + // |reader| is for sending back ACK message. + void HandleMessage(const std::string& current_dir, + const std::vector& argv, + SocketReader* reader); + + // MessageLoopForIO::Watcher impl. These run on the IO thread. + void OnFileCanReadWithoutBlocking(int fd) override; + void OnFileCanWriteWithoutBlocking(int fd) override { + // ProcessSingleton only watches for accept (read) events. + NOTREACHED(); + } + + // MessageLoop::DestructionObserver + void WillDestroyCurrentMessageLoop() override { + fd_watcher_.StopWatchingFileDescriptor(); + } + + private: + friend struct BrowserThread::DeleteOnThread; + friend class base::DeleteHelper; + + ~LinuxWatcher() override { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + STLDeleteElements(&readers_); + + base::MessageLoopForIO* ml = base::MessageLoopForIO::current(); + ml->RemoveDestructionObserver(this); + } + + // Removes and deletes the SocketReader. + void RemoveSocketReader(SocketReader* reader); + + base::MessageLoopForIO::FileDescriptorWatcher fd_watcher_; + + // A reference to the UI message loop (i.e., the message loop we were + // constructed on). + base::MessageLoop* ui_message_loop_; + + // The ProcessSingleton that owns us. + ProcessSingleton* const parent_; + + std::set readers_; + + DISALLOW_COPY_AND_ASSIGN(LinuxWatcher); +}; + +void ProcessSingleton::LinuxWatcher::OnFileCanReadWithoutBlocking(int fd) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + // Accepting incoming client. + sockaddr_un from; + socklen_t from_len = sizeof(from); + int connection_socket = HANDLE_EINTR(accept( + fd, reinterpret_cast(&from), &from_len)); + if (-1 == connection_socket) { + PLOG(ERROR) << "accept() failed"; + return; + } + int rv = net::SetNonBlocking(connection_socket); + DCHECK_EQ(0, rv) << "Failed to make non-blocking socket."; + SocketReader* reader = new SocketReader(this, + ui_message_loop_, + connection_socket); + readers_.insert(reader); +} + +void ProcessSingleton::LinuxWatcher::StartListening(int socket) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + // Watch for client connections on this socket. + base::MessageLoopForIO* ml = base::MessageLoopForIO::current(); + ml->AddDestructionObserver(this); + ml->WatchFileDescriptor(socket, true, base::MessageLoopForIO::WATCH_READ, + &fd_watcher_, this); +} + +void ProcessSingleton::LinuxWatcher::HandleMessage( + const std::string& current_dir, const std::vector& argv, + SocketReader* reader) { + DCHECK(ui_message_loop_ == base::MessageLoop::current()); + DCHECK(reader); + + if (parent_->notification_callback_.Run(base::CommandLine(argv), + base::FilePath(current_dir))) { + // Send back "ACK" message to prevent the client process from starting up. + reader->FinishWithACK(kACKToken, arraysize(kACKToken) - 1); + } else { + LOG(WARNING) << "Not handling interprocess notification as browser" + " is shutting down"; + // Send back "SHUTDOWN" message, so that the client process can start up + // without killing this process. + reader->FinishWithACK(kShutdownToken, arraysize(kShutdownToken) - 1); + return; + } +} + +void ProcessSingleton::LinuxWatcher::RemoveSocketReader(SocketReader* reader) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + DCHECK(reader); + readers_.erase(reader); + delete reader; +} + +/////////////////////////////////////////////////////////////////////////////// +// ProcessSingleton::LinuxWatcher::SocketReader +// + +void ProcessSingleton::LinuxWatcher::SocketReader::OnFileCanReadWithoutBlocking( + int fd) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + DCHECK_EQ(fd, fd_); + while (bytes_read_ < sizeof(buf_)) { + ssize_t rv = HANDLE_EINTR( + read(fd, buf_ + bytes_read_, sizeof(buf_) - bytes_read_)); + if (rv < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + PLOG(ERROR) << "read() failed"; + CloseSocket(fd); + return; + } else { + // It would block, so we just return and continue to watch for the next + // opportunity to read. + return; + } + } else if (!rv) { + // No more data to read. It's time to process the message. + break; + } else { + bytes_read_ += rv; + } + } + + // Validate the message. The shortest message is kStartToken\0x\0x + const size_t kMinMessageLength = arraysize(kStartToken) + 4; + if (bytes_read_ < kMinMessageLength) { + buf_[bytes_read_] = 0; + LOG(ERROR) << "Invalid socket message (wrong length):" << buf_; + CleanupAndDeleteSelf(); + return; + } + + std::string str(buf_, bytes_read_); + std::vector tokens = base::SplitString( + str, std::string(1, kTokenDelimiter), + base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + + if (tokens.size() < 3 || tokens[0] != kStartToken) { + LOG(ERROR) << "Wrong message format: " << str; + CleanupAndDeleteSelf(); + return; + } + + // Stop the expiration timer to prevent this SocketReader object from being + // terminated unexpectly. + timer_.Stop(); + + std::string current_dir = tokens[1]; + // Remove the first two tokens. The remaining tokens should be the command + // line argv array. + tokens.erase(tokens.begin()); + tokens.erase(tokens.begin()); + + // Return to the UI thread to handle opening a new browser tab. + ui_message_loop_->task_runner()->PostTask( + FROM_HERE, base::Bind(&ProcessSingleton::LinuxWatcher::HandleMessage, + parent_, current_dir, tokens, this)); + fd_reader_.StopWatchingFileDescriptor(); + + // LinuxWatcher::HandleMessage() is in charge of destroying this SocketReader + // object by invoking SocketReader::FinishWithACK(). +} + +void ProcessSingleton::LinuxWatcher::SocketReader::FinishWithACK( + const char *message, size_t length) { + if (message && length) { + // Not necessary to care about the return value. + WriteToSocket(fd_, message, length); + } + + if (shutdown(fd_, SHUT_WR) < 0) + PLOG(ERROR) << "shutdown() failed"; + + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + base::Bind(&ProcessSingleton::LinuxWatcher::RemoveSocketReader, + parent_, + this)); + // We will be deleted once the posted RemoveSocketReader task runs. +} + +/////////////////////////////////////////////////////////////////////////////// +// ProcessSingleton +// +ProcessSingleton::ProcessSingleton( + const base::FilePath& user_data_dir, + const NotificationCallback& notification_callback) + : notification_callback_(notification_callback), + current_pid_(base::GetCurrentProcId()), + watcher_(new LinuxWatcher(this)) { + socket_path_ = user_data_dir.Append(chrome::kSingletonSocketFilename); + lock_path_ = user_data_dir.Append(chrome::kSingletonLockFilename); + cookie_path_ = user_data_dir.Append(chrome::kSingletonCookieFilename); + + kill_callback_ = base::Bind(&ProcessSingleton::KillProcess, + base::Unretained(this)); +} + +ProcessSingleton::~ProcessSingleton() { +} + +ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() { + return NotifyOtherProcessWithTimeout( + *base::CommandLine::ForCurrentProcess(), kRetryAttempts, + base::TimeDelta::FromSeconds(kTimeoutInSeconds), true); +} + +ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout( + const base::CommandLine& cmd_line, + int retry_attempts, + const base::TimeDelta& timeout, + bool kill_unresponsive) { + DCHECK_GE(retry_attempts, 0); + DCHECK_GE(timeout.InMicroseconds(), 0); + + base::TimeDelta sleep_interval = timeout / retry_attempts; + + ScopedSocket socket; + for (int retries = 0; retries <= retry_attempts; ++retries) { + // Try to connect to the socket. + if (ConnectSocket(&socket, socket_path_, cookie_path_)) + break; + + // If we're in a race with another process, they may be in Create() and have + // created the lock but not attached to the socket. So we check if the + // process with the pid from the lockfile is currently running and is a + // chrome browser. If so, we loop and try again for |timeout|. + + std::string hostname; + int pid; + if (!ParseLockPath(lock_path_, &hostname, &pid)) { + // No lockfile exists. + return PROCESS_NONE; + } + + if (hostname.empty()) { + // Invalid lockfile. + UnlinkPath(lock_path_); + return PROCESS_NONE; + } + + if (hostname != net::GetHostName() && !IsChromeProcess(pid)) { + // Locked by process on another host. If the user selected to unlock + // the profile, try to continue; otherwise quit. + if (DisplayProfileInUseError(lock_path_, hostname, pid)) { + UnlinkPath(lock_path_); + return PROCESS_NONE; + } + return PROFILE_IN_USE; + } + + if (!IsChromeProcess(pid)) { + // Orphaned lockfile (no process with pid, or non-chrome process.) + UnlinkPath(lock_path_); + return PROCESS_NONE; + } + + if (IsSameChromeInstance(pid)) { + // Orphaned lockfile (pid is part of same chrome instance we are, even + // though we haven't tried to create a lockfile yet). + UnlinkPath(lock_path_); + return PROCESS_NONE; + } + + if (retries == retry_attempts) { + // Retries failed. Kill the unresponsive chrome process and continue. + if (!kill_unresponsive || !KillProcessByLockPath()) + return PROFILE_IN_USE; + return PROCESS_NONE; + } + + base::PlatformThread::Sleep(sleep_interval); + } + + timeval socket_timeout = TimeDeltaToTimeVal(timeout); + setsockopt(socket.fd(), + SOL_SOCKET, + SO_SNDTIMEO, + &socket_timeout, + sizeof(socket_timeout)); + + // Found another process, prepare our command line + // format is "START\0\0\0...\0". + std::string to_send(kStartToken); + to_send.push_back(kTokenDelimiter); + + base::FilePath current_dir; + if (!PathService::Get(base::DIR_CURRENT, ¤t_dir)) + return PROCESS_NONE; + to_send.append(current_dir.value()); + + const std::vector& argv = cmd_line.argv(); + for (std::vector::const_iterator it = argv.begin(); + it != argv.end(); ++it) { + to_send.push_back(kTokenDelimiter); + to_send.append(*it); + } + + // Send the message + if (!WriteToSocket(socket.fd(), to_send.data(), to_send.length())) { + // Try to kill the other process, because it might have been dead. + if (!kill_unresponsive || !KillProcessByLockPath()) + return PROFILE_IN_USE; + return PROCESS_NONE; + } + + if (shutdown(socket.fd(), SHUT_WR) < 0) + PLOG(ERROR) << "shutdown() failed"; + + // Read ACK message from the other process. It might be blocked for a certain + // timeout, to make sure the other process has enough time to return ACK. + char buf[kMaxACKMessageLength + 1]; + ssize_t len = ReadFromSocket(socket.fd(), buf, kMaxACKMessageLength, timeout); + + // Failed to read ACK, the other process might have been frozen. + if (len <= 0) { + if (!kill_unresponsive || !KillProcessByLockPath()) + return PROFILE_IN_USE; + return PROCESS_NONE; + } + + buf[len] = '\0'; + if (strncmp(buf, kShutdownToken, arraysize(kShutdownToken) - 1) == 0) { + // The other process is shutting down, it's safe to start a new process. + return PROCESS_NONE; + } else if (strncmp(buf, kACKToken, arraysize(kACKToken) - 1) == 0) { +#if defined(TOOLKIT_VIEWS) && defined(OS_LINUX) && !defined(OS_CHROMEOS) + // Likely NULL in unit tests. + views::LinuxUI* linux_ui = views::LinuxUI::instance(); + if (linux_ui) + linux_ui->NotifyWindowManagerStartupComplete(); +#endif + + // Assume the other process is handling the request. + return PROCESS_NOTIFIED; + } + + NOTREACHED() << "The other process returned unknown message: " << buf; + return PROCESS_NOTIFIED; +} + +ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessOrCreate() { + return NotifyOtherProcessWithTimeoutOrCreate( + *base::CommandLine::ForCurrentProcess(), kRetryAttempts, + base::TimeDelta::FromSeconds(kTimeoutInSeconds)); +} + +ProcessSingleton::NotifyResult +ProcessSingleton::NotifyOtherProcessWithTimeoutOrCreate( + const base::CommandLine& command_line, + int retry_attempts, + const base::TimeDelta& timeout) { + NotifyResult result = NotifyOtherProcessWithTimeout( + command_line, retry_attempts, timeout, true); + if (result != PROCESS_NONE) + return result; + if (Create()) + return PROCESS_NONE; + // If the Create() failed, try again to notify. (It could be that another + // instance was starting at the same time and managed to grab the lock before + // we did.) + // This time, we don't want to kill anything if we aren't successful, since we + // aren't going to try to take over the lock ourselves. + result = NotifyOtherProcessWithTimeout( + command_line, retry_attempts, timeout, false); + if (result != PROCESS_NONE) + return result; + + return LOCK_ERROR; +} + +void ProcessSingleton::OverrideCurrentPidForTesting(base::ProcessId pid) { + current_pid_ = pid; +} + +void ProcessSingleton::OverrideKillCallbackForTesting( + const base::Callback& callback) { + kill_callback_ = callback; +} + +void ProcessSingleton::DisablePromptForTesting() { + g_disable_prompt = true; +} + +bool ProcessSingleton::Create() { + int sock; + sockaddr_un addr; + + // The symlink lock is pointed to the hostname and process id, so other + // processes can find it out. + base::FilePath symlink_content(base::StringPrintf( + "%s%c%u", + net::GetHostName().c_str(), + kLockDelimiter, + current_pid_)); + + // Create symbol link before binding the socket, to ensure only one instance + // can have the socket open. + if (!SymlinkPath(symlink_content, lock_path_)) { + // TODO(jackhou): Remove this case once this code is stable on Mac. + // http://crbug.com/367612 +#if defined(OS_MACOSX) + // On Mac, an existing non-symlink lock file means the lock could be held by + // the old process singleton code. If we can successfully replace the lock, + // continue as normal. + if (base::IsLink(lock_path_) || + !ReplaceOldSingletonLock(symlink_content, lock_path_)) { + return false; + } +#else + // If we failed to create the lock, most likely another instance won the + // startup race. + return false; +#endif + } + + // Create the socket file somewhere in /tmp which is usually mounted as a + // normal filesystem. Some network filesystems (notably AFS) are screwy and + // do not support Unix domain sockets. + if (!socket_dir_.CreateUniqueTempDir()) { + LOG(ERROR) << "Failed to create socket directory."; + return false; + } + + // Check that the directory was created with the correct permissions. + int dir_mode = 0; + CHECK(base::GetPosixFilePermissions(socket_dir_.path(), &dir_mode) && + dir_mode == base::FILE_PERMISSION_USER_MASK) + << "Temp directory mode is not 700: " << std::oct << dir_mode; + + // Setup the socket symlink and the two cookies. + base::FilePath socket_target_path = + socket_dir_.path().Append(chrome::kSingletonSocketFilename); + base::FilePath cookie(GenerateCookie()); + base::FilePath remote_cookie_path = + socket_dir_.path().Append(chrome::kSingletonCookieFilename); + UnlinkPath(socket_path_); + UnlinkPath(cookie_path_); + if (!SymlinkPath(socket_target_path, socket_path_) || + !SymlinkPath(cookie, cookie_path_) || + !SymlinkPath(cookie, remote_cookie_path)) { + // We've already locked things, so we can't have lost the startup race, + // but something doesn't like us. + LOG(ERROR) << "Failed to create symlinks."; + if (!socket_dir_.Delete()) + LOG(ERROR) << "Encountered a problem when deleting socket directory."; + return false; + } + + SetupSocket(socket_target_path.value(), &sock, &addr); + + if (bind(sock, reinterpret_cast(&addr), sizeof(addr)) < 0) { + PLOG(ERROR) << "Failed to bind() " << socket_target_path.value(); + CloseSocket(sock); + return false; + } + + if (listen(sock, 5) < 0) + NOTREACHED() << "listen failed: " << base::safe_strerror(errno); + + DCHECK(BrowserThread::IsMessageLoopValid(BrowserThread::IO)); + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + base::Bind(&ProcessSingleton::LinuxWatcher::StartListening, + watcher_.get(), + sock)); + + return true; +} + +void ProcessSingleton::Cleanup() { + UnlinkPath(socket_path_); + UnlinkPath(cookie_path_); + UnlinkPath(lock_path_); +} + +bool ProcessSingleton::IsSameChromeInstance(pid_t pid) { + pid_t cur_pid = current_pid_; + while (pid != cur_pid) { + pid = base::GetParentProcessId(pid); + if (pid < 0) + return false; + if (!IsChromeProcess(pid)) + return false; + } + return true; +} + +bool ProcessSingleton::KillProcessByLockPath() { + std::string hostname; + int pid; + ParseLockPath(lock_path_, &hostname, &pid); + + if (!hostname.empty() && hostname != net::GetHostName()) { + return DisplayProfileInUseError(lock_path_, hostname, pid); + } + UnlinkPath(lock_path_); + + if (IsSameChromeInstance(pid)) + return true; + + if (pid > 0) { + kill_callback_.Run(pid); + return true; + } + + LOG(ERROR) << "Failed to extract pid from path: " << lock_path_.value(); + return true; +} + +void ProcessSingleton::KillProcess(int pid) { + // TODO(james.su@gmail.com): Is SIGKILL ok? + int rv = kill(static_cast(pid), SIGKILL); + // ESRCH = No Such Process (can happen if the other process is already in + // progress of shutting down and finishes before we try to kill it). + DCHECK(rv == 0 || errno == ESRCH) << "Error killing process: " + << base::safe_strerror(errno); +} diff --git a/atom/browser/process_singleton_startup_lock.cc b/atom/browser/process_singleton_startup_lock.cc new file mode 100644 index 0000000000..f564de7dca --- /dev/null +++ b/atom/browser/process_singleton_startup_lock.cc @@ -0,0 +1,53 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/process_singleton_startup_lock.h" + +#include "base/bind.h" +#include "base/logging.h" + +ProcessSingletonStartupLock::ProcessSingletonStartupLock( + const ProcessSingleton::NotificationCallback& original_callback) + : locked_(true), + original_callback_(original_callback) {} + +ProcessSingletonStartupLock::~ProcessSingletonStartupLock() {} + +ProcessSingleton::NotificationCallback +ProcessSingletonStartupLock::AsNotificationCallback() { + return base::Bind(&ProcessSingletonStartupLock::NotificationCallbackImpl, + base::Unretained(this)); +} + +void ProcessSingletonStartupLock::Unlock() { + DCHECK(CalledOnValidThread()); + locked_ = false; + + // Replay the command lines of the messages which were received while the + // ProcessSingleton was locked. Only replay each message once. + std::set replayed_messages; + for (std::vector::const_iterator it = + saved_startup_messages_.begin(); + it != saved_startup_messages_.end(); ++it) { + if (replayed_messages.find(*it) != replayed_messages.end()) + continue; + original_callback_.Run(base::CommandLine(it->first), it->second); + replayed_messages.insert(*it); + } + saved_startup_messages_.clear(); +} + +bool ProcessSingletonStartupLock::NotificationCallbackImpl( + const base::CommandLine& command_line, + const base::FilePath& current_directory) { + if (locked_) { + // If locked, it means we are not ready to process this message because + // we are probably in a first run critical phase. + saved_startup_messages_.push_back( + std::make_pair(command_line.argv(), current_directory)); + return true; + } else { + return original_callback_.Run(command_line, current_directory); + } +} diff --git a/atom/browser/process_singleton_startup_lock.h b/atom/browser/process_singleton_startup_lock.h new file mode 100644 index 0000000000..187dd35bce --- /dev/null +++ b/atom/browser/process_singleton_startup_lock.h @@ -0,0 +1,57 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_PROCESS_SINGLETON_STARTUP_LOCK_H_ +#define CHROME_BROWSER_PROCESS_SINGLETON_STARTUP_LOCK_H_ + +#include +#include +#include + +#include "base/basictypes.h" +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/threading/non_thread_safe.h" +#include "chrome/browser/process_singleton.h" + +// Provides a ProcessSingleton::NotificationCallback that can queue up +// command-line invocations during startup and execute them when startup +// completes. +// +// The object starts in a locked state. |Unlock()| must be called +// when the process is prepared to handle command-line invocations. +// +// Once unlocked, notifications are forwarded to a wrapped NotificationCallback. +class ProcessSingletonStartupLock : public base::NonThreadSafe { + public: + explicit ProcessSingletonStartupLock( + const ProcessSingleton::NotificationCallback& original_callback); + ~ProcessSingletonStartupLock(); + + // Returns the ProcessSingleton::NotificationCallback. + // The callback is only valid during the lifetime of the + // ProcessSingletonStartupLock instance. + ProcessSingleton::NotificationCallback AsNotificationCallback(); + + // Executes previously queued command-line invocations and allows future + // invocations to be executed immediately. + void Unlock(); + + bool locked() { return locked_; } + + private: + typedef std::pair + DelayedStartupMessage; + + bool NotificationCallbackImpl(const base::CommandLine& command_line, + const base::FilePath& current_directory); + + bool locked_; + std::vector saved_startup_messages_; + ProcessSingleton::NotificationCallback original_callback_; + + DISALLOW_COPY_AND_ASSIGN(ProcessSingletonStartupLock); +}; + +#endif // CHROME_BROWSER_PROCESS_SINGLETON_STARTUP_LOCK_H_ diff --git a/atom/browser/process_singleton_win.cc b/atom/browser/process_singleton_win.cc new file mode 100644 index 0000000000..623aa7f42f --- /dev/null +++ b/atom/browser/process_singleton_win.cc @@ -0,0 +1,441 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/process_singleton.h" + +#include + +#include "base/base_paths.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/process/process.h" +#include "base/process/process_info.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" +#include "base/win/metro.h" +#include "base/win/registry.h" +#include "base/win/scoped_handle.h" +#include "base/win/windows_version.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/browser_process_platform_part.h" +#include "chrome/browser/chrome_process_finder_win.h" +#include "chrome/browser/metro_utils/metro_chrome_win.h" +#include "chrome/browser/shell_integration.h" +#include "chrome/browser/ui/simple_message_box.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/chrome_paths_internal.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/grit/chromium_strings.h" +#include "chrome/installer/util/wmi.h" +#include "content/public/common/result_codes.h" +#include "net/base/escape.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/gfx/win/hwnd_util.h" + +namespace { + +const char kLockfile[] = "lockfile"; + +const int kMetroChromeActivationTimeoutMs = 3000; + +// A helper class that acquires the given |mutex| while the AutoLockMutex is in +// scope. +class AutoLockMutex { + public: + explicit AutoLockMutex(HANDLE mutex) : mutex_(mutex) { + DWORD result = ::WaitForSingleObject(mutex_, INFINITE); + DPCHECK(result == WAIT_OBJECT_0) << "Result = " << result; + } + + ~AutoLockMutex() { + BOOL released = ::ReleaseMutex(mutex_); + DPCHECK(released); + } + + private: + HANDLE mutex_; + DISALLOW_COPY_AND_ASSIGN(AutoLockMutex); +}; + +// A helper class that releases the given |mutex| while the AutoUnlockMutex is +// in scope and immediately re-acquires it when going out of scope. +class AutoUnlockMutex { + public: + explicit AutoUnlockMutex(HANDLE mutex) : mutex_(mutex) { + BOOL released = ::ReleaseMutex(mutex_); + DPCHECK(released); + } + + ~AutoUnlockMutex() { + DWORD result = ::WaitForSingleObject(mutex_, INFINITE); + DPCHECK(result == WAIT_OBJECT_0) << "Result = " << result; + } + + private: + HANDLE mutex_; + DISALLOW_COPY_AND_ASSIGN(AutoUnlockMutex); +}; + +// Checks the visibility of the enumerated window and signals once a visible +// window has been found. +BOOL CALLBACK BrowserWindowEnumeration(HWND window, LPARAM param) { + bool* result = reinterpret_cast(param); + *result = ::IsWindowVisible(window) != 0; + // Stops enumeration if a visible window has been found. + return !*result; +} + +bool ParseCommandLine(const COPYDATASTRUCT* cds, + base::CommandLine* parsed_command_line, + base::FilePath* current_directory) { + // We should have enough room for the shortest command (min_message_size) + // and also be a multiple of wchar_t bytes. The shortest command + // possible is L"START\0\0" (empty current directory and command line). + static const int min_message_size = 7; + if (cds->cbData < min_message_size * sizeof(wchar_t) || + cds->cbData % sizeof(wchar_t) != 0) { + LOG(WARNING) << "Invalid WM_COPYDATA, length = " << cds->cbData; + return false; + } + + // We split the string into 4 parts on NULLs. + DCHECK(cds->lpData); + const std::wstring msg(static_cast(cds->lpData), + cds->cbData / sizeof(wchar_t)); + const std::wstring::size_type first_null = msg.find_first_of(L'\0'); + if (first_null == 0 || first_null == std::wstring::npos) { + // no NULL byte, don't know what to do + LOG(WARNING) << "Invalid WM_COPYDATA, length = " << msg.length() << + ", first null = " << first_null; + return false; + } + + // Decode the command, which is everything until the first NULL. + if (msg.substr(0, first_null) == L"START") { + // Another instance is starting parse the command line & do what it would + // have done. + VLOG(1) << "Handling STARTUP request from another process"; + const std::wstring::size_type second_null = + msg.find_first_of(L'\0', first_null + 1); + if (second_null == std::wstring::npos || + first_null == msg.length() - 1 || second_null == msg.length()) { + LOG(WARNING) << "Invalid format for start command, we need a string in 4 " + "parts separated by NULLs"; + return false; + } + + // Get current directory. + *current_directory = base::FilePath(msg.substr(first_null + 1, + second_null - first_null)); + + const std::wstring::size_type third_null = + msg.find_first_of(L'\0', second_null + 1); + if (third_null == std::wstring::npos || + third_null == msg.length()) { + LOG(WARNING) << "Invalid format for start command, we need a string in 4 " + "parts separated by NULLs"; + } + + // Get command line. + const std::wstring cmd_line = + msg.substr(second_null + 1, third_null - second_null); + *parsed_command_line = base::CommandLine::FromString(cmd_line); + return true; + } + return false; +} + +bool ProcessLaunchNotification( + const ProcessSingleton::NotificationCallback& notification_callback, + UINT message, + WPARAM wparam, + LPARAM lparam, + LRESULT* result) { + if (message != WM_COPYDATA) + return false; + + // Handle the WM_COPYDATA message from another process. + const COPYDATASTRUCT* cds = reinterpret_cast(lparam); + + base::CommandLine parsed_command_line(base::CommandLine::NO_PROGRAM); + base::FilePath current_directory; + if (!ParseCommandLine(cds, &parsed_command_line, ¤t_directory)) { + *result = TRUE; + return true; + } + + *result = notification_callback.Run(parsed_command_line, current_directory) ? + TRUE : FALSE; + return true; +} + +// Returns true if Chrome needs to be relaunched into Windows 8 immersive mode. +// Following conditions apply:- +// 1. Windows 8 or greater. +// 2. Not in Windows 8 immersive mode. +// 3. Chrome is default browser. +// 4. Process integrity level is not high. +// 5. The profile data directory is the default directory. +// 6. Last used mode was immersive/machine is a tablet. +// TODO(ananta) +// Move this function to a common place as the Windows 8 delegate_execute +// handler can possibly use this. +bool ShouldLaunchInWindows8ImmersiveMode(const base::FilePath& user_data_dir) { + // Returning false from this function doesn't mean we don't launch immersive + // mode in Aura. This function is specifically called in case when we need + // to relaunch desktop launched chrome into immersive mode through 'relaunch' + // menu. In case of Aura, we will use delegate_execute to do the relaunch. + return false; +} + +bool DisplayShouldKillMessageBox() { + return chrome::ShowMessageBox( + NULL, l10n_util::GetStringUTF16(IDS_PRODUCT_NAME), + l10n_util::GetStringUTF16(IDS_BROWSER_HUNGBROWSER_MESSAGE), + chrome::MESSAGE_BOX_TYPE_QUESTION) != + chrome::MESSAGE_BOX_RESULT_NO; +} + +} // namespace + +// Microsoft's Softricity virtualization breaks the sandbox processes. +// So, if we detect the Softricity DLL we use WMI Win32_Process.Create to +// break out of the virtualization environment. +// http://code.google.com/p/chromium/issues/detail?id=43650 +bool ProcessSingleton::EscapeVirtualization( + const base::FilePath& user_data_dir) { + if (::GetModuleHandle(L"sftldr_wow64.dll") || + ::GetModuleHandle(L"sftldr.dll")) { + int process_id; + if (!installer::WMIProcess::Launch(::GetCommandLineW(), &process_id)) + return false; + is_virtualized_ = true; + // The new window was spawned from WMI, and won't be in the foreground. + // So, first we sleep while the new chrome.exe instance starts (because + // WaitForInputIdle doesn't work here). Then we poll for up to two more + // seconds and make the window foreground if we find it (or we give up). + HWND hwnd = 0; + ::Sleep(90); + for (int tries = 200; tries; --tries) { + hwnd = chrome::FindRunningChromeWindow(user_data_dir); + if (hwnd) { + ::SetForegroundWindow(hwnd); + break; + } + ::Sleep(10); + } + return true; + } + return false; +} + +ProcessSingleton::ProcessSingleton( + const base::FilePath& user_data_dir, + const NotificationCallback& notification_callback) + : notification_callback_(notification_callback), + is_virtualized_(false), + lock_file_(INVALID_HANDLE_VALUE), + user_data_dir_(user_data_dir), + should_kill_remote_process_callback_( + base::Bind(&DisplayShouldKillMessageBox)) { +} + +ProcessSingleton::~ProcessSingleton() { + if (lock_file_ != INVALID_HANDLE_VALUE) + ::CloseHandle(lock_file_); +} + +// Code roughly based on Mozilla. +ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() { + if (is_virtualized_) + return PROCESS_NOTIFIED; // We already spawned the process in this case. + if (lock_file_ == INVALID_HANDLE_VALUE && !remote_window_) { + return LOCK_ERROR; + } else if (!remote_window_) { + return PROCESS_NONE; + } + + switch (chrome::AttemptToNotifyRunningChrome(remote_window_, false)) { + case chrome::NOTIFY_SUCCESS: + return PROCESS_NOTIFIED; + case chrome::NOTIFY_FAILED: + remote_window_ = NULL; + return PROCESS_NONE; + case chrome::NOTIFY_WINDOW_HUNG: + // Fall through and potentially terminate the hung browser. + break; + } + + DWORD process_id = 0; + DWORD thread_id = ::GetWindowThreadProcessId(remote_window_, &process_id); + if (!thread_id || !process_id) { + remote_window_ = NULL; + return PROCESS_NONE; + } + base::Process process = base::Process::Open(process_id); + + // The window is hung. Scan for every window to find a visible one. + bool visible_window = false; + ::EnumThreadWindows(thread_id, + &BrowserWindowEnumeration, + reinterpret_cast(&visible_window)); + + // If there is a visible browser window, ask the user before killing it. + if (visible_window && !should_kill_remote_process_callback_.Run()) { + // The user denied. Quit silently. + return PROCESS_NOTIFIED; + } + + // Time to take action. Kill the browser process. + process.Terminate(content::RESULT_CODE_HUNG, true); + remote_window_ = NULL; + return PROCESS_NONE; +} + +ProcessSingleton::NotifyResult +ProcessSingleton::NotifyOtherProcessOrCreate() { + ProcessSingleton::NotifyResult result = PROCESS_NONE; + if (!Create()) { + result = NotifyOtherProcess(); + if (result == PROCESS_NONE) + result = PROFILE_IN_USE; + } else { + g_browser_process->platform_part()->PlatformSpecificCommandLineProcessing( + *base::CommandLine::ForCurrentProcess()); + } + return result; +} + +// Look for a Chrome instance that uses the same profile directory. If there +// isn't one, create a message window with its title set to the profile +// directory path. +bool ProcessSingleton::Create() { + static const wchar_t kMutexName[] = L"Local\\ChromeProcessSingletonStartup!"; + static const wchar_t kMetroActivationEventName[] = + L"Local\\ChromeProcessSingletonStartupMetroActivation!"; + + remote_window_ = chrome::FindRunningChromeWindow(user_data_dir_); + if (!remote_window_ && !EscapeVirtualization(user_data_dir_)) { + // Make sure we will be the one and only process creating the window. + // We use a named Mutex since we are protecting against multi-process + // access. As documented, it's clearer to NOT request ownership on creation + // since it isn't guaranteed we will get it. It is better to create it + // without ownership and explicitly get the ownership afterward. + base::win::ScopedHandle only_me(::CreateMutex(NULL, FALSE, kMutexName)); + if (!only_me.IsValid()) { + DPLOG(FATAL) << "CreateMutex failed"; + return false; + } + + AutoLockMutex auto_lock_only_me(only_me.Get()); + + // We now own the mutex so we are the only process that can create the + // window at this time, but we must still check if someone created it + // between the time where we looked for it above and the time the mutex + // was given to us. + remote_window_ = chrome::FindRunningChromeWindow(user_data_dir_); + + + // In Win8+, a new Chrome process launched in Desktop mode may need to be + // transmuted into Metro Chrome (see ShouldLaunchInWindows8ImmersiveMode for + // heuristics). To accomplish this, the current Chrome activates Metro + // Chrome, releases the startup mutex, and waits for metro Chrome to take + // the singleton. From that point onward, the command line for this Chrome + // process will be sent to Metro Chrome by the usual channels. + if (!remote_window_ && base::win::GetVersion() >= base::win::VERSION_WIN8 && + !base::win::IsMetroProcess()) { + // |metro_activation_event| is created right before activating a Metro + // Chrome (note that there can only be one Metro Chrome process; by OS + // design); all following Desktop processes will then wait for this event + // to be signaled by Metro Chrome which will do so as soon as it grabs + // this singleton (should any of the waiting processes timeout waiting for + // the signal they will try to grab the singleton for themselves which + // will result in a forced Desktop Chrome launch in the worst case). + base::win::ScopedHandle metro_activation_event( + ::OpenEvent(SYNCHRONIZE, FALSE, kMetroActivationEventName)); + if (!metro_activation_event.IsValid() && + ShouldLaunchInWindows8ImmersiveMode(user_data_dir_)) { + // No Metro activation is under way, but the desire is to launch in + // Metro mode: activate and rendez-vous with the activated process. + metro_activation_event.Set( + ::CreateEvent(NULL, TRUE, FALSE, kMetroActivationEventName)); + if (!chrome::ActivateMetroChrome()) { + // Failed to launch immersive Chrome, default to launching on Desktop. + LOG(ERROR) << "Failed to launch immersive chrome"; + metro_activation_event.Close(); + } + } + + if (metro_activation_event.IsValid()) { + // Release |only_me| (to let Metro Chrome grab this singleton) and wait + // until the event is signaled (i.e. Metro Chrome was successfully + // activated). Ignore timeout waiting for |metro_activation_event|. + { + AutoUnlockMutex auto_unlock_only_me(only_me.Get()); + + DWORD result = ::WaitForSingleObject(metro_activation_event.Get(), + kMetroChromeActivationTimeoutMs); + DPCHECK(result == WAIT_OBJECT_0 || result == WAIT_TIMEOUT) + << "Result = " << result; + } + + // Check if this singleton was successfully grabbed by another process + // (hopefully Metro Chrome). Failing to do so, this process will grab + // the singleton and launch in Desktop mode. + remote_window_ = chrome::FindRunningChromeWindow(user_data_dir_); + } + } + + if (!remote_window_) { + // We have to make sure there is no Chrome instance running on another + // machine that uses the same profile. + base::FilePath lock_file_path = user_data_dir_.AppendASCII(kLockfile); + lock_file_ = ::CreateFile(lock_file_path.value().c_str(), + GENERIC_WRITE, + FILE_SHARE_READ, + NULL, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL | + FILE_FLAG_DELETE_ON_CLOSE, + NULL); + DWORD error = ::GetLastError(); + LOG_IF(WARNING, lock_file_ != INVALID_HANDLE_VALUE && + error == ERROR_ALREADY_EXISTS) << "Lock file exists but is writable."; + LOG_IF(ERROR, lock_file_ == INVALID_HANDLE_VALUE) + << "Lock file can not be created! Error code: " << error; + + if (lock_file_ != INVALID_HANDLE_VALUE) { + // Set the window's title to the path of our user data directory so + // other Chrome instances can decide if they should forward to us. + bool result = window_.CreateNamed( + base::Bind(&ProcessLaunchNotification, notification_callback_), + user_data_dir_.value()); + CHECK(result && window_.hwnd()); + } + + if (base::win::GetVersion() >= base::win::VERSION_WIN8) { + // Make sure no one is still waiting on Metro activation whether it + // succeeded (i.e., this is the Metro process) or failed. + base::win::ScopedHandle metro_activation_event( + ::OpenEvent(EVENT_MODIFY_STATE, FALSE, kMetroActivationEventName)); + if (metro_activation_event.IsValid()) + ::SetEvent(metro_activation_event.Get()); + } + } + } + + return window_.hwnd() != NULL; +} + +void ProcessSingleton::Cleanup() { +} + +void ProcessSingleton::OverrideShouldKillRemoteProcessCallbackForTesting( + const ShouldKillRemoteProcessCallback& display_dialog_callback) { + should_kill_remote_process_callback_ = display_dialog_callback; +} From 90b997ef084284907164ccbd17235d5aa38ce567 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Mon, 19 Oct 2015 11:21:24 -0700 Subject: [PATCH 049/146] Add the files to filenames.gypi --- filenames.gypi | 3 +++ 1 file changed, 3 insertions(+) diff --git a/filenames.gypi b/filenames.gypi index aadc1a092f..17d1b3597c 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -182,6 +182,9 @@ 'atom/browser/net/url_request_fetch_job.h', 'atom/browser/node_debugger.cc', 'atom/browser/node_debugger.h', + 'atom/browser/process_singleton_posix.cc', + 'atom/browser/process_singleton_startup_lock.cc', + 'atom/browser/process_singleton_win.cc', 'atom/browser/ui/accelerator_util.cc', 'atom/browser/ui/accelerator_util.h', 'atom/browser/ui/accelerator_util_mac.mm', From ff2d9759d5829c1332e8df205a763f290ccede04 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Mon, 19 Oct 2015 11:28:29 -0700 Subject: [PATCH 050/146] Start to remove / rearrange some header files --- atom/browser/process_singleton.h | 2 -- atom/browser/process_singleton_posix.cc | 13 +------------ atom/browser/process_singleton_startup_lock.cc | 2 +- atom/browser/process_singleton_startup_lock.h | 2 +- atom/browser/process_singleton_win.cc | 18 +++++------------- 5 files changed, 8 insertions(+), 29 deletions(-) diff --git a/atom/browser/process_singleton.h b/atom/browser/process_singleton.h index 3260c4ef24..451414613e 100644 --- a/atom/browser/process_singleton.h +++ b/atom/browser/process_singleton.h @@ -5,8 +5,6 @@ #ifndef CHROME_BROWSER_PROCESS_SINGLETON_H_ #define CHROME_BROWSER_PROCESS_SINGLETON_H_ -#include "build/build_config.h" - #if defined(OS_WIN) #include #endif // defined(OS_WIN) diff --git a/atom/browser/process_singleton_posix.cc b/atom/browser/process_singleton_posix.cc index 81a5c5fec3..b7ebd2de58 100644 --- a/atom/browser/process_singleton_posix.cc +++ b/atom/browser/process_singleton_posix.cc @@ -37,7 +37,7 @@ // retrieves the process id from the symbol link and kills it by sending // SIGKILL. Then the second process starts as normal. -#include "chrome/browser/process_singleton.h" +#include "process_singleton.h" #include #include @@ -77,21 +77,10 @@ #include "base/threading/platform_thread.h" #include "base/time/time.h" #include "base/timer/timer.h" -#include "chrome/common/chrome_constants.h" -#include "chrome/grit/chromium_strings.h" -#include "chrome/grit/generated_resources.h" #include "content/public/browser/browser_thread.h" #include "net/base/net_util.h" #include "ui/base/l10n/l10n_util.h" -#if defined(OS_LINUX) -#include "chrome/browser/ui/process_singleton_dialog_linux.h" -#endif - -#if defined(TOOLKIT_VIEWS) && defined(OS_LINUX) && !defined(OS_CHROMEOS) -#include "ui/views/linux_ui/linux_ui.h" -#endif - using content::BrowserThread; namespace { diff --git a/atom/browser/process_singleton_startup_lock.cc b/atom/browser/process_singleton_startup_lock.cc index f564de7dca..58d0fd1231 100644 --- a/atom/browser/process_singleton_startup_lock.cc +++ b/atom/browser/process_singleton_startup_lock.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "chrome/browser/process_singleton_startup_lock.h" +#include "process_singleton_startup_lock.h" #include "base/bind.h" #include "base/logging.h" diff --git a/atom/browser/process_singleton_startup_lock.h b/atom/browser/process_singleton_startup_lock.h index 187dd35bce..2e8c975499 100644 --- a/atom/browser/process_singleton_startup_lock.h +++ b/atom/browser/process_singleton_startup_lock.h @@ -13,7 +13,7 @@ #include "base/command_line.h" #include "base/files/file_path.h" #include "base/threading/non_thread_safe.h" -#include "chrome/browser/process_singleton.h" +#include "process_singleton.h" // Provides a ProcessSingleton::NotificationCallback that can queue up // command-line invocations during startup and execute them when startup diff --git a/atom/browser/process_singleton_win.cc b/atom/browser/process_singleton_win.cc index 623aa7f42f..95a5b7d9bb 100644 --- a/atom/browser/process_singleton_win.cc +++ b/atom/browser/process_singleton_win.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "chrome/browser/process_singleton.h" +#include "process_singleton.h" #include @@ -20,18 +20,10 @@ #include "base/win/registry.h" #include "base/win/scoped_handle.h" #include "base/win/windows_version.h" -#include "chrome/browser/browser_process.h" -#include "chrome/browser/browser_process_platform_part.h" -#include "chrome/browser/chrome_process_finder_win.h" -#include "chrome/browser/metro_utils/metro_chrome_win.h" -#include "chrome/browser/shell_integration.h" -#include "chrome/browser/ui/simple_message_box.h" -#include "chrome/common/chrome_constants.h" -#include "chrome/common/chrome_paths.h" -#include "chrome/common/chrome_paths_internal.h" -#include "chrome/common/chrome_switches.h" -#include "chrome/grit/chromium_strings.h" -#include "chrome/installer/util/wmi.h" +//#include "chrome/browser/browser_process.h" +//#include "chrome/browser/browser_process_platform_part.h" +#include "chrome_process_finder_win.h" // TODO: Pull this in +//#include "chrome/browser/shell_integration.h" // TODO: Maybe pull this in? #include "content/public/common/result_codes.h" #include "net/base/escape.h" #include "ui/base/l10n/l10n_util.h" From 05b22b9372ddacea832c3c13e917ef5fd0842d52 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Mon, 19 Oct 2015 11:43:45 -0700 Subject: [PATCH 051/146] Import process_finder verbatim --- atom/browser/chrome_process_finder_win.cc | 97 +++++++++++++++++++++++ atom/browser/chrome_process_finder_win.h | 39 +++++++++ 2 files changed, 136 insertions(+) create mode 100644 atom/browser/chrome_process_finder_win.cc create mode 100644 atom/browser/chrome_process_finder_win.h diff --git a/atom/browser/chrome_process_finder_win.cc b/atom/browser/chrome_process_finder_win.cc new file mode 100644 index 0000000000..13c4a0eb5a --- /dev/null +++ b/atom/browser/chrome_process_finder_win.cc @@ -0,0 +1,97 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/chrome_process_finder_win.h" + +#include +#include + +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/logging.h" +#include "base/process/process.h" +#include "base/process/process_info.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/win/message_window.h" +#include "base/win/scoped_handle.h" +#include "base/win/win_util.h" +#include "base/win/windows_version.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/chrome_switches.h" + + +namespace { + +int timeout_in_milliseconds = 20 * 1000; + +} // namespace + +namespace chrome { + +HWND FindRunningChromeWindow(const base::FilePath& user_data_dir) { + return base::win::MessageWindow::FindWindow(user_data_dir.value()); +} + +NotifyChromeResult AttemptToNotifyRunningChrome(HWND remote_window, + bool fast_start) { + DCHECK(remote_window); + DWORD process_id = 0; + DWORD thread_id = GetWindowThreadProcessId(remote_window, &process_id); + if (!thread_id || !process_id) + return NOTIFY_FAILED; + + base::CommandLine command_line(*base::CommandLine::ForCurrentProcess()); + command_line.AppendSwitchASCII( + switches::kOriginalProcessStartTime, + base::Int64ToString( + base::CurrentProcessInfo::CreationTime().ToInternalValue())); + + if (fast_start) + command_line.AppendSwitch(switches::kFastStart); + + // Send the command line to the remote chrome window. + // Format is "START\0<<>>\0<<>>". + std::wstring to_send(L"START\0", 6); // want the NULL in the string. + base::FilePath cur_dir; + if (!base::GetCurrentDirectory(&cur_dir)) + return NOTIFY_FAILED; + to_send.append(cur_dir.value()); + to_send.append(L"\0", 1); // Null separator. + to_send.append(command_line.GetCommandLineString()); + to_send.append(L"\0", 1); // Null separator. + + // Allow the current running browser window to make itself the foreground + // window (otherwise it will just flash in the taskbar). + ::AllowSetForegroundWindow(process_id); + + COPYDATASTRUCT cds; + cds.dwData = 0; + cds.cbData = static_cast((to_send.length() + 1) * sizeof(wchar_t)); + cds.lpData = const_cast(to_send.c_str()); + DWORD_PTR result = 0; + if (::SendMessageTimeout(remote_window, WM_COPYDATA, NULL, + reinterpret_cast(&cds), SMTO_ABORTIFHUNG, + timeout_in_milliseconds, &result)) { + return result ? NOTIFY_SUCCESS : NOTIFY_FAILED; + } + + // It is possible that the process owning this window may have died by now. + if (!::IsWindow(remote_window)) + return NOTIFY_FAILED; + + // If the window couldn't be notified but still exists, assume it is hung. + return NOTIFY_WINDOW_HUNG; +} + +base::TimeDelta SetNotificationTimeoutForTesting(base::TimeDelta new_timeout) { + base::TimeDelta old_timeout = + base::TimeDelta::FromMilliseconds(timeout_in_milliseconds); + timeout_in_milliseconds = new_timeout.InMilliseconds(); + return old_timeout; +} + +} // namespace chrome diff --git a/atom/browser/chrome_process_finder_win.h b/atom/browser/chrome_process_finder_win.h new file mode 100644 index 0000000000..a66429de5e --- /dev/null +++ b/atom/browser/chrome_process_finder_win.h @@ -0,0 +1,39 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_CHROME_PROCESS_FINDER_WIN_H_ +#define CHROME_BROWSER_CHROME_PROCESS_FINDER_WIN_H_ + +#include + +#include "base/time/time.h" + +namespace base { +class FilePath; +} + +namespace chrome { + +enum NotifyChromeResult { + NOTIFY_SUCCESS, + NOTIFY_FAILED, + NOTIFY_WINDOW_HUNG, +}; + +// Finds an already running Chrome window if it exists. +HWND FindRunningChromeWindow(const base::FilePath& user_data_dir); + +// Attempts to send the current command line to an already running instance of +// Chrome via a WM_COPYDATA message. +// Returns true if a running Chrome is found and successfully notified. +// |fast_start| is true when this is being called on the window fast start path. +NotifyChromeResult AttemptToNotifyRunningChrome(HWND remote_window, + bool fast_start); + +// Changes the notification timeout to |new_timeout|, returns the old timeout. +base::TimeDelta SetNotificationTimeoutForTesting(base::TimeDelta new_timeout); + +} // namespace chrome + +#endif // CHROME_BROWSER_CHROME_PROCESS_FINDER_WIN_H_ From d3b23a2032cc44345fef4d565c08933d7c28c204 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Mon, 19 Oct 2015 11:47:38 -0700 Subject: [PATCH 052/146] Renames to avoid confusion, add file --- ...cess_finder_win.cc => atom_process_finder_win.cc} | 12 +++++------- ...rocess_finder_win.h => atom_process_finder_win.h} | 12 ++++++------ filenames.gypi | 1 + 3 files changed, 12 insertions(+), 13 deletions(-) rename atom/browser/{chrome_process_finder_win.cc => atom_process_finder_win.cc} (89%) rename atom/browser/{chrome_process_finder_win.h => atom_process_finder_win.h} (74%) diff --git a/atom/browser/chrome_process_finder_win.cc b/atom/browser/atom_process_finder_win.cc similarity index 89% rename from atom/browser/chrome_process_finder_win.cc rename to atom/browser/atom_process_finder_win.cc index 13c4a0eb5a..df2d736d3e 100644 --- a/atom/browser/chrome_process_finder_win.cc +++ b/atom/browser/atom_process_finder_win.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "chrome/browser/chrome_process_finder_win.h" +#include "atom/browser/atom_process_finder_win.h" #include #include @@ -20,8 +20,6 @@ #include "base/win/scoped_handle.h" #include "base/win/win_util.h" #include "base/win/windows_version.h" -#include "chrome/common/chrome_constants.h" -#include "chrome/common/chrome_switches.h" namespace { @@ -30,14 +28,14 @@ int timeout_in_milliseconds = 20 * 1000; } // namespace -namespace chrome { +namespace atom { -HWND FindRunningChromeWindow(const base::FilePath& user_data_dir) { +HWND FindRunningAtomWindow(const base::FilePath& user_data_dir) { return base::win::MessageWindow::FindWindow(user_data_dir.value()); } -NotifyChromeResult AttemptToNotifyRunningChrome(HWND remote_window, - bool fast_start) { +NotifyChromeResult AttemptToNotifyRunningAtom(HWND remote_window, + bool fast_start) { DCHECK(remote_window); DWORD process_id = 0; DWORD thread_id = GetWindowThreadProcessId(remote_window, &process_id); diff --git a/atom/browser/chrome_process_finder_win.h b/atom/browser/atom_process_finder_win.h similarity index 74% rename from atom/browser/chrome_process_finder_win.h rename to atom/browser/atom_process_finder_win.h index a66429de5e..f8aa72fdd9 100644 --- a/atom/browser/chrome_process_finder_win.h +++ b/atom/browser/atom_process_finder_win.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef CHROME_BROWSER_CHROME_PROCESS_FINDER_WIN_H_ -#define CHROME_BROWSER_CHROME_PROCESS_FINDER_WIN_H_ +#ifndef ATOM_BROWSER_ATOM_PROCESS_FINDER_WIN_H_ +#define ATOM_BROWSER_ATOM_PROCESS_FINDER_WIN_H_ #include @@ -13,7 +13,7 @@ namespace base { class FilePath; } -namespace chrome { +namespace atom { enum NotifyChromeResult { NOTIFY_SUCCESS, @@ -22,13 +22,13 @@ enum NotifyChromeResult { }; // Finds an already running Chrome window if it exists. -HWND FindRunningChromeWindow(const base::FilePath& user_data_dir); +HWND FindRunningAtomWindow(const base::FilePath& user_data_dir); // Attempts to send the current command line to an already running instance of // Chrome via a WM_COPYDATA message. // Returns true if a running Chrome is found and successfully notified. // |fast_start| is true when this is being called on the window fast start path. -NotifyChromeResult AttemptToNotifyRunningChrome(HWND remote_window, +NotifyChromeResult AttemptToNotifyRunningAtom(HWND remote_window, bool fast_start); // Changes the notification timeout to |new_timeout|, returns the old timeout. @@ -36,4 +36,4 @@ base::TimeDelta SetNotificationTimeoutForTesting(base::TimeDelta new_timeout); } // namespace chrome -#endif // CHROME_BROWSER_CHROME_PROCESS_FINDER_WIN_H_ +#endif // ATOM_BROWSER_ATOM_PROCESS_FINDER_WIN_H_ diff --git a/filenames.gypi b/filenames.gypi index 17d1b3597c..0dce079b5e 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -128,6 +128,7 @@ 'atom/browser/atom_browser_main_parts.h', 'atom/browser/atom_browser_main_parts_mac.mm', 'atom/browser/atom_browser_main_parts_posix.cc', + 'atom/browser/atom_process_finder_win.cc', 'atom/browser/atom_javascript_dialog_manager.cc', 'atom/browser/atom_javascript_dialog_manager.h', 'atom/browser/atom_quota_permission_context.cc', From daa65a138bc8dff4e576bbda53ab0145a734e7b3 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Mon, 19 Oct 2015 12:16:37 -0700 Subject: [PATCH 053/146] Come Correct with headers --- atom/browser/process_singleton_posix.cc | 2 +- atom/browser/process_singleton_startup_lock.cc | 2 +- atom/browser/process_singleton_startup_lock.h | 6 +++--- atom/browser/process_singleton_win.cc | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/atom/browser/process_singleton_posix.cc b/atom/browser/process_singleton_posix.cc index b7ebd2de58..119b18e11a 100644 --- a/atom/browser/process_singleton_posix.cc +++ b/atom/browser/process_singleton_posix.cc @@ -37,7 +37,7 @@ // retrieves the process id from the symbol link and kills it by sending // SIGKILL. Then the second process starts as normal. -#include "process_singleton.h" +#include "atom/browser/process_singleton.h" #include #include diff --git a/atom/browser/process_singleton_startup_lock.cc b/atom/browser/process_singleton_startup_lock.cc index 58d0fd1231..fa53284f05 100644 --- a/atom/browser/process_singleton_startup_lock.cc +++ b/atom/browser/process_singleton_startup_lock.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "process_singleton_startup_lock.h" +#include "atom/browser/process_singleton_startup_lock.h" #include "base/bind.h" #include "base/logging.h" diff --git a/atom/browser/process_singleton_startup_lock.h b/atom/browser/process_singleton_startup_lock.h index 2e8c975499..aecff8414e 100644 --- a/atom/browser/process_singleton_startup_lock.h +++ b/atom/browser/process_singleton_startup_lock.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef CHROME_BROWSER_PROCESS_SINGLETON_STARTUP_LOCK_H_ -#define CHROME_BROWSER_PROCESS_SINGLETON_STARTUP_LOCK_H_ +#ifndef ATOM_BROWSER_PROCESS_SINGLETON_STARTUP_LOCK_H_ +#define ATOM_BROWSER_PROCESS_SINGLETON_STARTUP_LOCK_H_ #include #include @@ -13,7 +13,7 @@ #include "base/command_line.h" #include "base/files/file_path.h" #include "base/threading/non_thread_safe.h" -#include "process_singleton.h" +#include "atom/browser/process_singleton.h" // Provides a ProcessSingleton::NotificationCallback that can queue up // command-line invocations during startup and execute them when startup diff --git a/atom/browser/process_singleton_win.cc b/atom/browser/process_singleton_win.cc index 95a5b7d9bb..902a4530d9 100644 --- a/atom/browser/process_singleton_win.cc +++ b/atom/browser/process_singleton_win.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "process_singleton.h" +#include "atom/browser/process_singleton.h" #include @@ -22,7 +22,7 @@ #include "base/win/windows_version.h" //#include "chrome/browser/browser_process.h" //#include "chrome/browser/browser_process_platform_part.h" -#include "chrome_process_finder_win.h" // TODO: Pull this in +#include "atom/browser/atom_process_finder_win.h" //#include "chrome/browser/shell_integration.h" // TODO: Maybe pull this in? #include "content/public/common/result_codes.h" #include "net/base/escape.h" From 1b3363c8114bcacabddcc928ff340b21ada440e9 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Mon, 19 Oct 2015 12:17:14 -0700 Subject: [PATCH 054/146] Get process finder working --- atom/browser/atom_process_finder_win.cc | 7 ------- 1 file changed, 7 deletions(-) diff --git a/atom/browser/atom_process_finder_win.cc b/atom/browser/atom_process_finder_win.cc index df2d736d3e..a658b7163b 100644 --- a/atom/browser/atom_process_finder_win.cc +++ b/atom/browser/atom_process_finder_win.cc @@ -43,13 +43,6 @@ NotifyChromeResult AttemptToNotifyRunningAtom(HWND remote_window, return NOTIFY_FAILED; base::CommandLine command_line(*base::CommandLine::ForCurrentProcess()); - command_line.AppendSwitchASCII( - switches::kOriginalProcessStartTime, - base::Int64ToString( - base::CurrentProcessInfo::CreationTime().ToInternalValue())); - - if (fast_start) - command_line.AppendSwitch(switches::kFastStart); // Send the command line to the remote chrome window. // Format is "START\0<<>>\0<<>>". From c46579b1acadf1270cd389f827e25bff4013eadf Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Mon, 19 Oct 2015 12:28:06 -0700 Subject: [PATCH 055/146] Remove a bunch of stuff we don't need in process_singleton_win --- atom/browser/process_singleton_win.cc | 146 +++----------------------- 1 file changed, 15 insertions(+), 131 deletions(-) diff --git a/atom/browser/process_singleton_win.cc b/atom/browser/process_singleton_win.cc index 902a4530d9..cf667f11e4 100644 --- a/atom/browser/process_singleton_win.cc +++ b/atom/browser/process_singleton_win.cc @@ -33,8 +33,6 @@ namespace { const char kLockfile[] = "lockfile"; -const int kMetroChromeActivationTimeoutMs = 3000; - // A helper class that acquires the given |mutex| while the AutoLockMutex is in // scope. class AutoLockMutex { @@ -166,66 +164,14 @@ bool ProcessLaunchNotification( return true; } -// Returns true if Chrome needs to be relaunched into Windows 8 immersive mode. -// Following conditions apply:- -// 1. Windows 8 or greater. -// 2. Not in Windows 8 immersive mode. -// 3. Chrome is default browser. -// 4. Process integrity level is not high. -// 5. The profile data directory is the default directory. -// 6. Last used mode was immersive/machine is a tablet. -// TODO(ananta) -// Move this function to a common place as the Windows 8 delegate_execute -// handler can possibly use this. -bool ShouldLaunchInWindows8ImmersiveMode(const base::FilePath& user_data_dir) { - // Returning false from this function doesn't mean we don't launch immersive - // mode in Aura. This function is specifically called in case when we need - // to relaunch desktop launched chrome into immersive mode through 'relaunch' - // menu. In case of Aura, we will use delegate_execute to do the relaunch. +bool TerminateAppWithError() { + // TODO: This is called when the secondary process can't ping the primary + // process. Need to find out what to do here. return false; } -bool DisplayShouldKillMessageBox() { - return chrome::ShowMessageBox( - NULL, l10n_util::GetStringUTF16(IDS_PRODUCT_NAME), - l10n_util::GetStringUTF16(IDS_BROWSER_HUNGBROWSER_MESSAGE), - chrome::MESSAGE_BOX_TYPE_QUESTION) != - chrome::MESSAGE_BOX_RESULT_NO; -} - } // namespace -// Microsoft's Softricity virtualization breaks the sandbox processes. -// So, if we detect the Softricity DLL we use WMI Win32_Process.Create to -// break out of the virtualization environment. -// http://code.google.com/p/chromium/issues/detail?id=43650 -bool ProcessSingleton::EscapeVirtualization( - const base::FilePath& user_data_dir) { - if (::GetModuleHandle(L"sftldr_wow64.dll") || - ::GetModuleHandle(L"sftldr.dll")) { - int process_id; - if (!installer::WMIProcess::Launch(::GetCommandLineW(), &process_id)) - return false; - is_virtualized_ = true; - // The new window was spawned from WMI, and won't be in the foreground. - // So, first we sleep while the new chrome.exe instance starts (because - // WaitForInputIdle doesn't work here). Then we poll for up to two more - // seconds and make the window foreground if we find it (or we give up). - HWND hwnd = 0; - ::Sleep(90); - for (int tries = 200; tries; --tries) { - hwnd = chrome::FindRunningChromeWindow(user_data_dir); - if (hwnd) { - ::SetForegroundWindow(hwnd); - break; - } - ::Sleep(10); - } - return true; - } - return false; -} - ProcessSingleton::ProcessSingleton( const base::FilePath& user_data_dir, const NotificationCallback& notification_callback) @@ -234,7 +180,7 @@ ProcessSingleton::ProcessSingleton( lock_file_(INVALID_HANDLE_VALUE), user_data_dir_(user_data_dir), should_kill_remote_process_callback_( - base::Bind(&DisplayShouldKillMessageBox)) { + base::Bind(&TerminateAppWithError)) { } ProcessSingleton::~ProcessSingleton() { @@ -252,13 +198,13 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() { return PROCESS_NONE; } - switch (chrome::AttemptToNotifyRunningChrome(remote_window_, false)) { - case chrome::NOTIFY_SUCCESS: + switch (atom::AttemptToNotifyRunningAtom(remote_window_, false)) { + case atom::NOTIFY_SUCCESS: return PROCESS_NOTIFIED; - case chrome::NOTIFY_FAILED: + case atom::NOTIFY_FAILED: remote_window_ = NULL; return PROCESS_NONE; - case chrome::NOTIFY_WINDOW_HUNG: + case atom::NOTIFY_WINDOW_HUNG: // Fall through and potentially terminate the hung browser. break; } @@ -297,8 +243,9 @@ ProcessSingleton::NotifyOtherProcessOrCreate() { if (result == PROCESS_NONE) result = PROFILE_IN_USE; } else { - g_browser_process->platform_part()->PlatformSpecificCommandLineProcessing( - *base::CommandLine::ForCurrentProcess()); + // TODO: Figure out how to implement this + //g_browser_process->platform_part()->PlatformSpecificCommandLineProcessing( + // *base::CommandLine::ForCurrentProcess()); } return result; } @@ -307,12 +254,10 @@ ProcessSingleton::NotifyOtherProcessOrCreate() { // isn't one, create a message window with its title set to the profile // directory path. bool ProcessSingleton::Create() { - static const wchar_t kMutexName[] = L"Local\\ChromeProcessSingletonStartup!"; - static const wchar_t kMetroActivationEventName[] = - L"Local\\ChromeProcessSingletonStartupMetroActivation!"; + static const wchar_t kMutexName[] = L"Local\\AtomProcessSingletonStartup!"; - remote_window_ = chrome::FindRunningChromeWindow(user_data_dir_); - if (!remote_window_ && !EscapeVirtualization(user_data_dir_)) { + remote_window_ = atom::FindRunningAtomWindow(user_data_dir_); + if (!remote_window_) { // Make sure we will be the one and only process creating the window. // We use a named Mutex since we are protecting against multi-process // access. As documented, it's clearer to NOT request ownership on creation @@ -330,59 +275,7 @@ bool ProcessSingleton::Create() { // window at this time, but we must still check if someone created it // between the time where we looked for it above and the time the mutex // was given to us. - remote_window_ = chrome::FindRunningChromeWindow(user_data_dir_); - - - // In Win8+, a new Chrome process launched in Desktop mode may need to be - // transmuted into Metro Chrome (see ShouldLaunchInWindows8ImmersiveMode for - // heuristics). To accomplish this, the current Chrome activates Metro - // Chrome, releases the startup mutex, and waits for metro Chrome to take - // the singleton. From that point onward, the command line for this Chrome - // process will be sent to Metro Chrome by the usual channels. - if (!remote_window_ && base::win::GetVersion() >= base::win::VERSION_WIN8 && - !base::win::IsMetroProcess()) { - // |metro_activation_event| is created right before activating a Metro - // Chrome (note that there can only be one Metro Chrome process; by OS - // design); all following Desktop processes will then wait for this event - // to be signaled by Metro Chrome which will do so as soon as it grabs - // this singleton (should any of the waiting processes timeout waiting for - // the signal they will try to grab the singleton for themselves which - // will result in a forced Desktop Chrome launch in the worst case). - base::win::ScopedHandle metro_activation_event( - ::OpenEvent(SYNCHRONIZE, FALSE, kMetroActivationEventName)); - if (!metro_activation_event.IsValid() && - ShouldLaunchInWindows8ImmersiveMode(user_data_dir_)) { - // No Metro activation is under way, but the desire is to launch in - // Metro mode: activate and rendez-vous with the activated process. - metro_activation_event.Set( - ::CreateEvent(NULL, TRUE, FALSE, kMetroActivationEventName)); - if (!chrome::ActivateMetroChrome()) { - // Failed to launch immersive Chrome, default to launching on Desktop. - LOG(ERROR) << "Failed to launch immersive chrome"; - metro_activation_event.Close(); - } - } - - if (metro_activation_event.IsValid()) { - // Release |only_me| (to let Metro Chrome grab this singleton) and wait - // until the event is signaled (i.e. Metro Chrome was successfully - // activated). Ignore timeout waiting for |metro_activation_event|. - { - AutoUnlockMutex auto_unlock_only_me(only_me.Get()); - - DWORD result = ::WaitForSingleObject(metro_activation_event.Get(), - kMetroChromeActivationTimeoutMs); - DPCHECK(result == WAIT_OBJECT_0 || result == WAIT_TIMEOUT) - << "Result = " << result; - } - - // Check if this singleton was successfully grabbed by another process - // (hopefully Metro Chrome). Failing to do so, this process will grab - // the singleton and launch in Desktop mode. - remote_window_ = chrome::FindRunningChromeWindow(user_data_dir_); - } - } - + remote_window_ = atom::FindRunningAtomWindow(user_data_dir_); if (!remote_window_) { // We have to make sure there is no Chrome instance running on another // machine that uses the same profile. @@ -409,15 +302,6 @@ bool ProcessSingleton::Create() { user_data_dir_.value()); CHECK(result && window_.hwnd()); } - - if (base::win::GetVersion() >= base::win::VERSION_WIN8) { - // Make sure no one is still waiting on Metro activation whether it - // succeeded (i.e., this is the Metro process) or failed. - base::win::ScopedHandle metro_activation_event( - ::OpenEvent(EVENT_MODIFY_STATE, FALSE, kMetroActivationEventName)); - if (metro_activation_event.IsValid()) - ::SetEvent(metro_activation_event.Get()); - } } } From 88dd1480cc7601cf6f96923018eeaffdcbeef9c9 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Mon, 19 Oct 2015 13:51:27 -0700 Subject: [PATCH 056/146] Get POSIX mostly compiling --- atom/browser/process_singleton_posix.cc | 50 ++++++++++++------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/atom/browser/process_singleton_posix.cc b/atom/browser/process_singleton_posix.cc index 119b18e11a..2df9da631b 100644 --- a/atom/browser/process_singleton_posix.cc +++ b/atom/browser/process_singleton_posix.cc @@ -81,6 +81,10 @@ #include "net/base/net_util.h" #include "ui/base/l10n/l10n_util.h" +#if defined(TOOLKIT_VIEWS) && defined(OS_LINUX) && !defined(OS_CHROMEOS) +#include "ui/views/linux_ui/linux_ui.h" +#endif + using content::BrowserThread; namespace { @@ -101,6 +105,13 @@ const int kMaxACKMessageLength = arraysize(kShutdownToken) - 1; const char kLockDelimiter = '-'; +const base::FilePath::CharType kSingletonCookieFilename[] = + FILE_PATH_LITERAL("SingletonCookie"); + +const base::FilePath::CharType kSingletonLockFilename[] = FILE_PATH_LITERAL("SingletonLock"); +const base::FilePath::CharType kSingletonSocketFilename[] = + FILE_PATH_LITERAL("SingletonSocket"); + // Set the close-on-exec bit on a file descriptor. // Returns 0 on success, -1 on failure. int SetCloseOnExec(int fd) { @@ -293,33 +304,20 @@ bool ParseLockPath(const base::FilePath& path, bool DisplayProfileInUseError(const base::FilePath& lock_path, const std::string& hostname, int pid) { - base::string16 error = l10n_util::GetStringFUTF16( - IDS_PROFILE_IN_USE_POSIX, - base::IntToString16(pid), - base::ASCIIToUTF16(hostname)); - LOG(ERROR) << error; - - if (g_disable_prompt) - return false; - -#if defined(OS_LINUX) - base::string16 relaunch_button_text = l10n_util::GetStringUTF16( - IDS_PROFILE_IN_USE_LINUX_RELAUNCH); - return ShowProcessSingletonDialog(error, relaunch_button_text); -#elif defined(OS_MACOSX) - // On Mac, always usurp the lock. + // TODO: yolo return true; -#endif - - NOTREACHED(); - return false; } bool IsChromeProcess(pid_t pid) { base::FilePath other_chrome_path(base::GetProcessExecutablePath(pid)); + + auto command_line = base::CommandLine::ForCurrentProcess(); + base::FilePath exec_path(command_line->GetProgram()); + PathService::Get(base::FILE_EXE, &exec_path); + return (!other_chrome_path.empty() && other_chrome_path.BaseName() == - base::FilePath(chrome::kBrowserProcessExecutableName)); + exec_path.BaseName()); } // A helper class to hold onto a socket. @@ -360,7 +358,7 @@ bool ConnectSocket(ScopedSocket* socket, if (cookie.empty()) return false; base::FilePath remote_cookie = socket_target.DirName(). - Append(chrome::kSingletonCookieFilename); + Append(kSingletonCookieFilename); // Verify the cookie before connecting. if (!CheckCookie(remote_cookie, cookie)) return false; @@ -720,9 +718,9 @@ ProcessSingleton::ProcessSingleton( : notification_callback_(notification_callback), current_pid_(base::GetCurrentProcId()), watcher_(new LinuxWatcher(this)) { - socket_path_ = user_data_dir.Append(chrome::kSingletonSocketFilename); - lock_path_ = user_data_dir.Append(chrome::kSingletonLockFilename); - cookie_path_ = user_data_dir.Append(chrome::kSingletonCookieFilename); + socket_path_ = user_data_dir.Append(kSingletonSocketFilename); + lock_path_ = user_data_dir.Append(kSingletonLockFilename); + cookie_path_ = user_data_dir.Append(kSingletonCookieFilename); kill_callback_ = base::Bind(&ProcessSingleton::KillProcess, base::Unretained(this)); @@ -962,10 +960,10 @@ bool ProcessSingleton::Create() { // Setup the socket symlink and the two cookies. base::FilePath socket_target_path = - socket_dir_.path().Append(chrome::kSingletonSocketFilename); + socket_dir_.path().Append(kSingletonSocketFilename); base::FilePath cookie(GenerateCookie()); base::FilePath remote_cookie_path = - socket_dir_.path().Append(chrome::kSingletonCookieFilename); + socket_dir_.path().Append(kSingletonCookieFilename); UnlinkPath(socket_path_); UnlinkPath(cookie_path_); if (!SymlinkPath(socket_target_path, socket_path_) || From 8288a224587a5634a7057dbbbeafe838d012a4a4 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Mon, 19 Oct 2015 14:07:23 -0700 Subject: [PATCH 057/146] Fix build against old OneShotTimer --- atom/browser/process_singleton_posix.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom/browser/process_singleton_posix.cc b/atom/browser/process_singleton_posix.cc index 2df9da631b..5810af2a2a 100644 --- a/atom/browser/process_singleton_posix.cc +++ b/atom/browser/process_singleton_posix.cc @@ -503,7 +503,7 @@ class ProcessSingleton::LinuxWatcher // reads. size_t bytes_read_; - base::OneShotTimer timer_; + base::OneShotTimer timer_; DISALLOW_COPY_AND_ASSIGN(SocketReader); }; From 717aba9631583d160db6062b0fb424bf90d40c3d Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Mon, 19 Oct 2015 14:43:04 -0700 Subject: [PATCH 058/146] Create a dummy method in app that we'll twerk --- atom/browser/api/atom_api_app.cc | 5 +++++ atom/browser/api/atom_api_app.h | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index f124b00e80..27cc137d22 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -268,6 +268,10 @@ v8::Local App::DefaultSession(v8::Isolate* isolate) { return v8::Local::New(isolate, default_session_); } +bool App::MakeSingleInstance(const SingleInstanceCallback& callback) { + return false; +} + mate::ObjectTemplateBuilder App::GetObjectTemplateBuilder( v8::Isolate* isolate) { auto browser = base::Unretained(Browser::Get()); @@ -294,6 +298,7 @@ mate::ObjectTemplateBuilder App::GetObjectTemplateBuilder( .SetMethod("allowNTLMCredentialsForAllDomains", &App::AllowNTLMCredentialsForAllDomains) .SetMethod("getLocale", &App::GetLocale) + .SetMethod("MakeSingleInstance", &App::MakeSingleInstance) .SetProperty("defaultSession", &App::DefaultSession); } diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index 75889d2454..ae79abe8f2 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -9,6 +9,7 @@ #include "atom/browser/api/event_emitter.h" #include "atom/browser/browser_observer.h" +#include "atom/common/native_mate_converters/callback.h" #include "content/public/browser/gpu_data_manager_observer.h" #include "native_mate/handle.h" @@ -24,6 +25,8 @@ namespace atom { namespace api { +using SingleInstanceCallback = base::Callback; + class App : public mate::EventEmitter, public BrowserObserver, public content::GpuDataManagerObserver { @@ -65,7 +68,10 @@ class App : public mate::EventEmitter, void SetDesktopName(const std::string& desktop_name); void SetAppUserModelId(const std::string& app_id); + void AllowNTLMCredentialsForAllDomains(bool should_allow); + + bool MakeSingleInstance(const SingleInstanceCallback& callback); std::string GetLocale(); v8::Local DefaultSession(v8::Isolate* isolate); From 4d5495a0a0460eb6838031a39b9ee958e13b30cf Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Mon, 19 Oct 2015 14:50:18 -0700 Subject: [PATCH 059/146] Bring in chrome_process_singleton but nuke the active dialog bits --- atom/browser/atom_process_singleton.cc | 29 ++++++++++++++ atom/browser/atom_process_singleton.h | 52 ++++++++++++++++++++++++++ filenames.gypi | 1 + 3 files changed, 82 insertions(+) create mode 100644 atom/browser/atom_process_singleton.cc create mode 100644 atom/browser/atom_process_singleton.h diff --git a/atom/browser/atom_process_singleton.cc b/atom/browser/atom_process_singleton.cc new file mode 100644 index 0000000000..e9e4dc727b --- /dev/null +++ b/atom/browser/atom_process_singleton.cc @@ -0,0 +1,29 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "atom/browser/atom_process_singleton.h" + +AtomProcessSingleton::AtomProcessSingleton( + const base::FilePath& user_data_dir, + const ProcessSingleton::NotificationCallback& notification_callback) + : startup_lock_(notification_callback), + process_singleton_(user_data_dir, + startup_lock_.AsNotificationCallback()) { +} + +AtomProcessSingleton::~AtomProcessSingleton() { +} + +ProcessSingleton::NotifyResult + AtomProcessSingleton::NotifyOtherProcessOrCreate() { + return process_singleton_.NotifyOtherProcessOrCreate(); +} + +void AtomProcessSingleton::Cleanup() { + process_singleton_.Cleanup(); +} + +void AtomProcessSingleton::Unlock() { + startup_lock_.Unlock(); +} diff --git a/atom/browser/atom_process_singleton.h b/atom/browser/atom_process_singleton.h new file mode 100644 index 0000000000..de4e2463a0 --- /dev/null +++ b/atom/browser/atom_process_singleton.h @@ -0,0 +1,52 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_ATOM_PROCESS_SINGLETON_H_ +#define ATOM_BROWSER_ATOM_PROCESS_SINGLETON_H_ + +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "atom/browser/process_singleton.h" +#include "atom/browser/process_singleton_startup_lock.h" + +// Composes a basic ProcessSingleton with ProcessSingletonStartupLock +class AtomProcessSingleton { + public: + AtomProcessSingleton( + const base::FilePath& user_data_dir, + const ProcessSingleton::NotificationCallback& notification_callback); + + ~AtomProcessSingleton(); + + // Notify another process, if available. Otherwise sets ourselves as the + // singleton instance. Returns PROCESS_NONE if we became the singleton + // instance. Callers are guaranteed to either have notified an existing + // process or have grabbed the singleton (unless the profile is locked by an + // unreachable process). + ProcessSingleton::NotifyResult NotifyOtherProcessOrCreate(); + + // Clear any lock state during shutdown. + void Cleanup(); + + // Executes previously queued command-line invocations and allows future + // invocations to be executed immediately. + // This only has an effect the first time it is called. + void Unlock(); + + private: + // We compose these two locks with the client-supplied notification callback. + // First |modal_dialog_lock_| will discard any notifications that arrive while + // a modal dialog is active. Otherwise, it will pass the notification to + // |startup_lock_|, which will queue notifications until |Unlock()| is called. + // Notifications passing through both locks are finally delivered to our + // client. + ProcessSingletonStartupLock startup_lock_; + + // The basic ProcessSingleton + ProcessSingleton process_singleton_; + + DISALLOW_COPY_AND_ASSIGN(AtomProcessSingleton); +}; + +#endif // ATOM_BROWSER_ATOM_PROCESS_SINGLETON_H_ diff --git a/filenames.gypi b/filenames.gypi index 0dce079b5e..54fedb9f41 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -129,6 +129,7 @@ 'atom/browser/atom_browser_main_parts_mac.mm', 'atom/browser/atom_browser_main_parts_posix.cc', 'atom/browser/atom_process_finder_win.cc', + 'atom/browser/atom_process_singleton.cc', 'atom/browser/atom_javascript_dialog_manager.cc', 'atom/browser/atom_javascript_dialog_manager.h', 'atom/browser/atom_quota_permission_context.cc', From 7491ae40007018acc7953c1757fae99d342e61f4 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Mon, 19 Oct 2015 16:00:30 -0700 Subject: [PATCH 060/146] Set up Browser to create the process singleton --- atom/browser/browser.cc | 37 +++++++++++++++++++++++++++++++++++-- atom/browser/browser.h | 13 +++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/atom/browser/browser.cc b/atom/browser/browser.cc index d8bb94103c..10d6cc8330 100644 --- a/atom/browser/browser.cc +++ b/atom/browser/browser.cc @@ -9,6 +9,8 @@ #include "atom/browser/atom_browser_main_parts.h" #include "atom/browser/window_list.h" #include "base/message_loop/message_loop.h" +#include "base/path_service.h" +#include "brightray/browser/brightray_paths.h" #include "content/public/browser/client_certificate_delegate.h" #include "net/ssl/ssl_cert_request_info.h" @@ -17,7 +19,18 @@ namespace atom { Browser::Browser() : is_quiting_(false), is_ready_(false), - is_shutdown_(false) { + is_shutdown_(false), + process_notify_callback_(NULL) { + base::FilePath userDir; + PathService::Get(brightray::DIR_USER_DATA, &userDir); + + auto no_refcount_this = base::Unretained(this); + process_singleton_.reset(new AtomProcessSingleton( + userDir, + base::Bind(&Browser::OnProcessSingletonNotification, no_refcount_this))); + + process_notify_result_ = process_singleton_->NotifyOtherProcessOrCreate(); + WindowList::AddObserver(this); } @@ -114,6 +127,7 @@ void Browser::WillFinishLaunching() { void Browser::DidFinishLaunching() { is_ready_ = true; + process_singleton_->Unlock() ; FOR_EACH_OBSERVER(BrowserObserver, observers_, OnFinishLaunching()); } @@ -139,7 +153,8 @@ void Browser::NotifyAndShutdown() { is_quiting_ = false; return; } - + + process_singleton_->Cleanup(); Shutdown(); } @@ -152,6 +167,14 @@ bool Browser::HandleBeforeQuit() { return !prevent_default; } +ProcessSingleton::NotifyResult Browser::GetSingleInstanceResult() { + return process_notify_result_; +} + +void Browser::SetSingleInstanceCallback(ProcessSingleton::NotificationCallback* callback) { + process_notify_callback_ = callback; +} + void Browser::OnWindowCloseCancelled(NativeWindow* window) { if (is_quiting_) // Once a beforeunload handler has prevented the closing, we think the quit @@ -166,4 +189,14 @@ void Browser::OnWindowAllClosed() { FOR_EACH_OBSERVER(BrowserObserver, observers_, OnWindowAllClosed()); } +bool Browser::OnProcessSingletonNotification( + const base::CommandLine& command_line, + const base::FilePath& current_directory) { + if (process_notify_callback_) { + return (*process_notify_callback_).Run(command_line, current_directory); + } else { + return true; // We'll handle this, not a different process + } +} + } // namespace atom diff --git a/atom/browser/browser.h b/atom/browser/browser.h index 3c5abd2f04..4dffeb093f 100644 --- a/atom/browser/browser.h +++ b/atom/browser/browser.h @@ -9,8 +9,10 @@ #include #include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" #include "base/compiler_specific.h" #include "base/observer_list.h" +#include "atom/browser/atom_process_singleton.h" #include "atom/browser/browser_observer.h" #include "atom/browser/window_list_observer.h" @@ -63,6 +65,9 @@ class Browser : public WindowListObserver { // Clear the recent documents list. void ClearRecentDocuments(); + + ProcessSingleton::NotifyResult GetSingleInstanceResult(); + void SetSingleInstanceCallback(ProcessSingleton::NotificationCallback* callback); #if defined(OS_MACOSX) // Bounce the dock icon. @@ -152,6 +157,10 @@ class Browser : public WindowListObserver { // WindowListObserver implementations: void OnWindowCloseCancelled(NativeWindow* window) override; void OnWindowAllClosed() override; + + bool OnProcessSingletonNotification( + const base::CommandLine& command_line, + const base::FilePath& current_directory); // Observers of the browser. base::ObserverList observers_; @@ -164,6 +173,10 @@ class Browser : public WindowListObserver { std::string version_override_; std::string name_override_; + + scoped_ptr process_singleton_; + ProcessSingleton::NotifyResult process_notify_result_; + ProcessSingleton::NotificationCallback* process_notify_callback_; #if defined(OS_WIN) base::string16 app_user_model_id_; From c38f2fcf759f84fc1b0c5e1946270fbcea9f38ff Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Mon, 19 Oct 2015 16:20:51 -0700 Subject: [PATCH 061/146] Add a native mate converter for command lines --- .../command_line_converter.h | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 atom/common/native_mate_converters/command_line_converter.h diff --git a/atom/common/native_mate_converters/command_line_converter.h b/atom/common/native_mate_converters/command_line_converter.h new file mode 100644 index 0000000000..7941c9a096 --- /dev/null +++ b/atom/common/native_mate_converters/command_line_converter.h @@ -0,0 +1,37 @@ +// Copyright (c) 2014 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_COMMON_NATIVE_MATE_CONVERTERS_COMMAND_LINE_CONVERTER_H_ +#define ATOM_COMMON_NATIVE_MATE_CONVERTERS_COMMAND_LINE_CONVERTER_H_ + +#include + +#include "atom/common/native_mate_converters/string16_converter.h" +#include "base/command_line.h" + +namespace mate { + +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + const base::CommandLine& val) { + return Converter::ToV8(isolate, val.GetCommandLineString()); + } + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + base::CommandLine* out) { + base::FilePath::StringType path; + + if (Converter::FromV8(isolate, val, &path)) { + *out = base::CommandLine(base::FilePath(path)); + return true; + } else { + return false; + } + } +}; + +} // namespace mate + +#endif // ATOM_COMMON_NATIVE_MATE_CONVERTERS_FILE_PATH_CONVERTER_H_ From cdd51fa96db2aa274d53486d740016ddd9cbbb30 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Mon, 19 Oct 2015 16:21:03 -0700 Subject: [PATCH 062/146] Wire everything up --- atom/browser/api/atom_api_app.cc | 15 ++++++++++++++- atom/browser/api/atom_api_app.h | 7 +++---- atom/browser/browser.cc | 2 +- atom/browser/browser.h | 4 ++-- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 27cc137d22..3c858c2f34 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -19,6 +19,7 @@ #include "atom/browser/api/atom_api_web_contents.h" #include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/file_path_converter.h" +#include "atom/common/native_mate_converters/command_line_converter.h" #include "atom/common/node_includes.h" #include "atom/common/options_switches.h" #include "base/command_line.h" @@ -268,7 +269,19 @@ v8::Local App::DefaultSession(v8::Isolate* isolate) { return v8::Local::New(isolate, default_session_); } -bool App::MakeSingleInstance(const SingleInstanceCallback& callback) { +bool App::MakeSingleInstance(const ProcessSingleton::NotificationCallback& callback) { + auto browser = Browser::Get(); + browser->SetSingleInstanceCallback(&callback); + + switch(browser->GetSingleInstanceResult()) { + case ProcessSingleton::NotifyResult::PROCESS_NONE: + return false; + case ProcessSingleton::NotifyResult::LOCK_ERROR: + case ProcessSingleton::NotifyResult::PROFILE_IN_USE: + case ProcessSingleton::NotifyResult::PROCESS_NOTIFIED: + return true; + } + return false; } diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index ae79abe8f2..10d4edf117 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -9,6 +9,7 @@ #include "atom/browser/api/event_emitter.h" #include "atom/browser/browser_observer.h" +#include "atom/browser/process_singleton.h" #include "atom/common/native_mate_converters/callback.h" #include "content/public/browser/gpu_data_manager_observer.h" #include "native_mate/handle.h" @@ -25,8 +26,6 @@ namespace atom { namespace api { -using SingleInstanceCallback = base::Callback; - class App : public mate::EventEmitter, public BrowserObserver, public content::GpuDataManagerObserver { @@ -70,8 +69,8 @@ class App : public mate::EventEmitter, void SetAppUserModelId(const std::string& app_id); void AllowNTLMCredentialsForAllDomains(bool should_allow); - - bool MakeSingleInstance(const SingleInstanceCallback& callback); + + bool MakeSingleInstance(const ProcessSingleton::NotificationCallback& callback); std::string GetLocale(); v8::Local DefaultSession(v8::Isolate* isolate); diff --git a/atom/browser/browser.cc b/atom/browser/browser.cc index 10d6cc8330..3c55be6882 100644 --- a/atom/browser/browser.cc +++ b/atom/browser/browser.cc @@ -171,7 +171,7 @@ ProcessSingleton::NotifyResult Browser::GetSingleInstanceResult() { return process_notify_result_; } -void Browser::SetSingleInstanceCallback(ProcessSingleton::NotificationCallback* callback) { +void Browser::SetSingleInstanceCallback(const ProcessSingleton::NotificationCallback* callback) { process_notify_callback_ = callback; } diff --git a/atom/browser/browser.h b/atom/browser/browser.h index 4dffeb093f..f14062232c 100644 --- a/atom/browser/browser.h +++ b/atom/browser/browser.h @@ -67,7 +67,7 @@ class Browser : public WindowListObserver { void ClearRecentDocuments(); ProcessSingleton::NotifyResult GetSingleInstanceResult(); - void SetSingleInstanceCallback(ProcessSingleton::NotificationCallback* callback); + void SetSingleInstanceCallback(const ProcessSingleton::NotificationCallback* callback); #if defined(OS_MACOSX) // Bounce the dock icon. @@ -176,7 +176,7 @@ class Browser : public WindowListObserver { scoped_ptr process_singleton_; ProcessSingleton::NotifyResult process_notify_result_; - ProcessSingleton::NotificationCallback* process_notify_callback_; + const ProcessSingleton::NotificationCallback* process_notify_callback_; #if defined(OS_WIN) base::string16 app_user_model_id_; From b02f89e63be64ec122dd6c933d174366f16c41fd Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Mon, 19 Oct 2015 17:36:21 -0700 Subject: [PATCH 063/146] Typo --- atom/browser/api/atom_api_app.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 3c858c2f34..2b60d2db96 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -311,7 +311,7 @@ mate::ObjectTemplateBuilder App::GetObjectTemplateBuilder( .SetMethod("allowNTLMCredentialsForAllDomains", &App::AllowNTLMCredentialsForAllDomains) .SetMethod("getLocale", &App::GetLocale) - .SetMethod("MakeSingleInstance", &App::MakeSingleInstance) + .SetMethod("makeSingleInstance", &App::MakeSingleInstance) .SetProperty("defaultSession", &App::DefaultSession); } From 50fab0733bbfcb6c504e9230347971c00c27acec Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Mon, 19 Oct 2015 17:37:26 -0700 Subject: [PATCH 064/146] At Browser ctor time, DIR_USER_DATA is unset, wait --- atom/browser/browser.cc | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/atom/browser/browser.cc b/atom/browser/browser.cc index 3c55be6882..39c348bcd6 100644 --- a/atom/browser/browser.cc +++ b/atom/browser/browser.cc @@ -21,16 +21,6 @@ Browser::Browser() is_ready_(false), is_shutdown_(false), process_notify_callback_(NULL) { - base::FilePath userDir; - PathService::Get(brightray::DIR_USER_DATA, &userDir); - - auto no_refcount_this = base::Unretained(this); - process_singleton_.reset(new AtomProcessSingleton( - userDir, - base::Bind(&Browser::OnProcessSingletonNotification, no_refcount_this))); - - process_notify_result_ = process_singleton_->NotifyOtherProcessOrCreate(); - WindowList::AddObserver(this); } @@ -122,6 +112,17 @@ void Browser::Activate(bool has_visible_windows) { } void Browser::WillFinishLaunching() { + base::FilePath userDir; + PathService::Get(brightray::DIR_USER_DATA, &userDir); + + auto no_refcount_this = base::Unretained(this); + process_singleton_.reset(new AtomProcessSingleton( + userDir, + base::Bind(&Browser::OnProcessSingletonNotification, no_refcount_this))); + + LOG(ERROR) << "Setting up Process Singleton: " << userDir.value(); + process_notify_result_ = process_singleton_->NotifyOtherProcessOrCreate(); + FOR_EACH_OBSERVER(BrowserObserver, observers_, OnWillFinishLaunching()); } From 4a4b829cfc91ff50534b9679e3ef3f2c192cdce2 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Mon, 19 Oct 2015 18:02:54 -0700 Subject: [PATCH 065/146] Fix callback handle usage --- atom/browser/api/atom_api_app.cc | 17 ++++++++++++++--- atom/browser/api/atom_api_app.h | 7 ++++++- atom/browser/browser.cc | 12 +++++++----- atom/browser/browser.h | 9 +++++---- 4 files changed, 32 insertions(+), 13 deletions(-) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 2b60d2db96..7792fa6bc3 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -269,10 +269,21 @@ v8::Local App::DefaultSession(v8::Isolate* isolate) { return v8::Local::New(isolate, default_session_); } -bool App::MakeSingleInstance(const ProcessSingleton::NotificationCallback& callback) { +bool App::OnProcessSingletonNotification( + const base::CommandLine& command_line, + const base::FilePath& current_directory) { + return true; +} + +bool App::MakeSingleInstance(v8::Local callback) { + single_instance_callback_ = callback; + auto browser = Browser::Get(); - browser->SetSingleInstanceCallback(&callback); - + + auto no_refcount_this = base::Unretained(this); + browser->SetSingleInstanceCallback( + base::Bind(&App::OnProcessSingletonNotification, no_refcount_this)); + switch(browser->GetSingleInstanceResult()) { case ProcessSingleton::NotifyResult::PROCESS_NONE: return false; diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index 10d4edf117..2d6a9f7f66 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -70,11 +70,16 @@ class App : public mate::EventEmitter, void AllowNTLMCredentialsForAllDomains(bool should_allow); - bool MakeSingleInstance(const ProcessSingleton::NotificationCallback& callback); + bool MakeSingleInstance(v8::Local callback); + + bool OnProcessSingletonNotification( + const base::CommandLine& command_line, + const base::FilePath& current_directory); std::string GetLocale(); v8::Local DefaultSession(v8::Isolate* isolate); + v8::Local single_instance_callback_; v8::Global default_session_; DISALLOW_COPY_AND_ASSIGN(App); diff --git a/atom/browser/browser.cc b/atom/browser/browser.cc index 39c348bcd6..f2ee8b4235 100644 --- a/atom/browser/browser.cc +++ b/atom/browser/browser.cc @@ -20,7 +20,7 @@ Browser::Browser() : is_quiting_(false), is_ready_(false), is_shutdown_(false), - process_notify_callback_(NULL) { + process_notify_callback_set_(false) { WindowList::AddObserver(this); } @@ -169,11 +169,13 @@ bool Browser::HandleBeforeQuit() { } ProcessSingleton::NotifyResult Browser::GetSingleInstanceResult() { + LOG(ERROR) << "Process Notify Result: " << process_notify_result_; return process_notify_result_; } - -void Browser::SetSingleInstanceCallback(const ProcessSingleton::NotificationCallback* callback) { + +void Browser::SetSingleInstanceCallback(const ProcessSingleton::NotificationCallback& callback) { process_notify_callback_ = callback; + process_notify_callback_set_ = true; } void Browser::OnWindowCloseCancelled(NativeWindow* window) { @@ -193,8 +195,8 @@ void Browser::OnWindowAllClosed() { bool Browser::OnProcessSingletonNotification( const base::CommandLine& command_line, const base::FilePath& current_directory) { - if (process_notify_callback_) { - return (*process_notify_callback_).Run(command_line, current_directory); + if (process_notify_callback_set_) { + return process_notify_callback_.Run(command_line, current_directory); } else { return true; // We'll handle this, not a different process } diff --git a/atom/browser/browser.h b/atom/browser/browser.h index f14062232c..b84e220d27 100644 --- a/atom/browser/browser.h +++ b/atom/browser/browser.h @@ -65,9 +65,9 @@ class Browser : public WindowListObserver { // Clear the recent documents list. void ClearRecentDocuments(); - + ProcessSingleton::NotifyResult GetSingleInstanceResult(); - void SetSingleInstanceCallback(const ProcessSingleton::NotificationCallback* callback); + void SetSingleInstanceCallback(const ProcessSingleton::NotificationCallback& callback); #if defined(OS_MACOSX) // Bounce the dock icon. @@ -173,10 +173,11 @@ class Browser : public WindowListObserver { std::string version_override_; std::string name_override_; - + scoped_ptr process_singleton_; ProcessSingleton::NotifyResult process_notify_result_; - const ProcessSingleton::NotificationCallback* process_notify_callback_; + const ProcessSingleton::NotificationCallback process_notify_callback_; + bool process_notify_callback_set_; #if defined(OS_WIN) base::string16 app_user_model_id_; From 63417bc975317e0b9c16e2f8c5a50847ac73bcbc Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Mon, 19 Oct 2015 18:23:13 -0700 Subject: [PATCH 066/146] I am not good at C++ at all --- atom/browser/api/atom_api_app.h | 2 +- atom/browser/browser.cc | 2 +- atom/browser/browser.h | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index 2d6a9f7f66..6654046e82 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -21,7 +21,7 @@ class FilePath; namespace mate { class Arguments; } - + namespace atom { namespace api { diff --git a/atom/browser/browser.cc b/atom/browser/browser.cc index f2ee8b4235..84b149330a 100644 --- a/atom/browser/browser.cc +++ b/atom/browser/browser.cc @@ -173,7 +173,7 @@ ProcessSingleton::NotifyResult Browser::GetSingleInstanceResult() { return process_notify_result_; } -void Browser::SetSingleInstanceCallback(const ProcessSingleton::NotificationCallback& callback) { +void Browser::SetSingleInstanceCallback(ProcessSingleton::NotificationCallback callback) { process_notify_callback_ = callback; process_notify_callback_set_ = true; } diff --git a/atom/browser/browser.h b/atom/browser/browser.h index b84e220d27..b3de4d169a 100644 --- a/atom/browser/browser.h +++ b/atom/browser/browser.h @@ -67,7 +67,7 @@ class Browser : public WindowListObserver { void ClearRecentDocuments(); ProcessSingleton::NotifyResult GetSingleInstanceResult(); - void SetSingleInstanceCallback(const ProcessSingleton::NotificationCallback& callback); + void SetSingleInstanceCallback(ProcessSingleton::NotificationCallback callback); #if defined(OS_MACOSX) // Bounce the dock icon. @@ -176,7 +176,7 @@ class Browser : public WindowListObserver { scoped_ptr process_singleton_; ProcessSingleton::NotifyResult process_notify_result_; - const ProcessSingleton::NotificationCallback process_notify_callback_; + ProcessSingleton::NotificationCallback process_notify_callback_; bool process_notify_callback_set_; #if defined(OS_WIN) From a2eedcc027f5f58a2069c1cc12c40ed2086f2157 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Mon, 19 Oct 2015 18:42:31 -0700 Subject: [PATCH 067/146] Wire up the final callback to JS --- atom/browser/api/atom_api_app.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 7792fa6bc3..072686dd8d 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -272,7 +272,10 @@ v8::Local App::DefaultSession(v8::Isolate* isolate) { bool App::OnProcessSingletonNotification( const base::CommandLine& command_line, const base::FilePath& current_directory) { - return true; + ProcessSingleton::NotificationCallback cb; + mate::Converter::FromV8(isolate(), single_instance_callback_, &cb); + + return cb.Run(command_line, current_directory); } bool App::MakeSingleInstance(v8::Local callback) { From 5d4c29a1e32fe58221e98be534f73da3902f054a Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Tue, 20 Oct 2015 13:27:34 -0700 Subject: [PATCH 068/146] Ditch double-callback, just use mate directly --- atom/browser/api/atom_api_app.cc | 19 +++++-------------- atom/browser/api/atom_api_app.h | 8 ++------ 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 072686dd8d..24c8063bc8 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -269,23 +269,14 @@ v8::Local App::DefaultSession(v8::Isolate* isolate) { return v8::Local::New(isolate, default_session_); } -bool App::OnProcessSingletonNotification( - const base::CommandLine& command_line, - const base::FilePath& current_directory) { - ProcessSingleton::NotificationCallback cb; - mate::Converter::FromV8(isolate(), single_instance_callback_, &cb); - - return cb.Run(command_line, current_directory); -} - bool App::MakeSingleInstance(v8::Local callback) { single_instance_callback_ = callback; - auto browser = Browser::Get(); + ProcessSingleton::NotificationCallback cb; + mate::Converter::FromV8(isolate(), single_instance_callback_, &cb); - auto no_refcount_this = base::Unretained(this); - browser->SetSingleInstanceCallback( - base::Bind(&App::OnProcessSingletonNotification, no_refcount_this)); + auto browser = Browser::Get(); + browser->SetSingleInstanceCallback(cb); switch(browser->GetSingleInstanceResult()) { case ProcessSingleton::NotifyResult::PROCESS_NONE: @@ -295,7 +286,7 @@ bool App::MakeSingleInstance(v8::Local callback) { case ProcessSingleton::NotifyResult::PROCESS_NOTIFIED: return true; } - + return false; } diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index 6654046e82..b8285553b4 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -21,7 +21,7 @@ class FilePath; namespace mate { class Arguments; } - + namespace atom { namespace api { @@ -71,11 +71,7 @@ class App : public mate::EventEmitter, void AllowNTLMCredentialsForAllDomains(bool should_allow); bool MakeSingleInstance(v8::Local callback); - - bool OnProcessSingletonNotification( - const base::CommandLine& command_line, - const base::FilePath& current_directory); - + std::string GetLocale(); v8::Local DefaultSession(v8::Isolate* isolate); From b6f1729acda0172bc86b6d21a6a31020113504dc Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Tue, 20 Oct 2015 13:38:57 -0700 Subject: [PATCH 069/146] Move initialization of Single Instance into MakeSingleInstance call --- atom/browser/api/atom_api_app.cc | 4 +++- atom/browser/browser.cc | 25 +++++++++++++------------ atom/browser/browser.h | 3 ++- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 24c8063bc8..f3955d2d03 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -270,12 +270,14 @@ v8::Local App::DefaultSession(v8::Isolate* isolate) { } bool App::MakeSingleInstance(v8::Local callback) { + auto browser = Browser::Get(); single_instance_callback_ = callback; + browser->InitializeSingleInstance(); + ProcessSingleton::NotificationCallback cb; mate::Converter::FromV8(isolate(), single_instance_callback_, &cb); - auto browser = Browser::Get(); browser->SetSingleInstanceCallback(cb); switch(browser->GetSingleInstanceResult()) { diff --git a/atom/browser/browser.cc b/atom/browser/browser.cc index 84b149330a..5c12a0d338 100644 --- a/atom/browser/browser.cc +++ b/atom/browser/browser.cc @@ -112,17 +112,6 @@ void Browser::Activate(bool has_visible_windows) { } void Browser::WillFinishLaunching() { - base::FilePath userDir; - PathService::Get(brightray::DIR_USER_DATA, &userDir); - - auto no_refcount_this = base::Unretained(this); - process_singleton_.reset(new AtomProcessSingleton( - userDir, - base::Bind(&Browser::OnProcessSingletonNotification, no_refcount_this))); - - LOG(ERROR) << "Setting up Process Singleton: " << userDir.value(); - process_notify_result_ = process_singleton_->NotifyOtherProcessOrCreate(); - FOR_EACH_OBSERVER(BrowserObserver, observers_, OnWillFinishLaunching()); } @@ -154,7 +143,7 @@ void Browser::NotifyAndShutdown() { is_quiting_ = false; return; } - + process_singleton_->Cleanup(); Shutdown(); } @@ -168,6 +157,18 @@ bool Browser::HandleBeforeQuit() { return !prevent_default; } +void Browser::InitializeSingleInstance() { + base::FilePath userDir; + PathService::Get(brightray::DIR_USER_DATA, &userDir); + + auto no_refcount_this = base::Unretained(this); + process_singleton_.reset(new AtomProcessSingleton( + userDir, + base::Bind(&Browser::OnProcessSingletonNotification, no_refcount_this))); + + process_notify_result_ = process_singleton_->NotifyOtherProcessOrCreate(); +} + ProcessSingleton::NotifyResult Browser::GetSingleInstanceResult() { LOG(ERROR) << "Process Notify Result: " << process_notify_result_; return process_notify_result_; diff --git a/atom/browser/browser.h b/atom/browser/browser.h index b3de4d169a..9113c58dd1 100644 --- a/atom/browser/browser.h +++ b/atom/browser/browser.h @@ -66,6 +66,7 @@ class Browser : public WindowListObserver { // Clear the recent documents list. void ClearRecentDocuments(); + void InitializeSingleInstance(); ProcessSingleton::NotifyResult GetSingleInstanceResult(); void SetSingleInstanceCallback(ProcessSingleton::NotificationCallback callback); @@ -157,7 +158,7 @@ class Browser : public WindowListObserver { // WindowListObserver implementations: void OnWindowCloseCancelled(NativeWindow* window) override; void OnWindowAllClosed() override; - + bool OnProcessSingletonNotification( const base::CommandLine& command_line, const base::FilePath& current_directory); From 7491d5cfb5a680afa03c09561315f17279a45c87 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Tue, 20 Oct 2015 14:05:07 -0700 Subject: [PATCH 070/146] Copy back the original versions of Chromium files in --- .../browser/chrome_process_finder_win.cc | 97 ++ .../browser/chrome_process_finder_win.h | 39 + .../chrome/browser/process_singleton.h | 183 +++ .../chrome/browser/process_singleton_posix.cc | 1062 +++++++++++++++++ .../browser/process_singleton_startup_lock.cc | 53 + .../browser/process_singleton_startup_lock.h | 57 + .../chrome/browser/process_singleton_win.cc | 441 +++++++ 7 files changed, 1932 insertions(+) create mode 100644 chromium_src/chrome/browser/chrome_process_finder_win.cc create mode 100644 chromium_src/chrome/browser/chrome_process_finder_win.h create mode 100644 chromium_src/chrome/browser/process_singleton.h create mode 100644 chromium_src/chrome/browser/process_singleton_posix.cc create mode 100644 chromium_src/chrome/browser/process_singleton_startup_lock.cc create mode 100644 chromium_src/chrome/browser/process_singleton_startup_lock.h create mode 100644 chromium_src/chrome/browser/process_singleton_win.cc diff --git a/chromium_src/chrome/browser/chrome_process_finder_win.cc b/chromium_src/chrome/browser/chrome_process_finder_win.cc new file mode 100644 index 0000000000..13c4a0eb5a --- /dev/null +++ b/chromium_src/chrome/browser/chrome_process_finder_win.cc @@ -0,0 +1,97 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/chrome_process_finder_win.h" + +#include +#include + +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/logging.h" +#include "base/process/process.h" +#include "base/process/process_info.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/win/message_window.h" +#include "base/win/scoped_handle.h" +#include "base/win/win_util.h" +#include "base/win/windows_version.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/chrome_switches.h" + + +namespace { + +int timeout_in_milliseconds = 20 * 1000; + +} // namespace + +namespace chrome { + +HWND FindRunningChromeWindow(const base::FilePath& user_data_dir) { + return base::win::MessageWindow::FindWindow(user_data_dir.value()); +} + +NotifyChromeResult AttemptToNotifyRunningChrome(HWND remote_window, + bool fast_start) { + DCHECK(remote_window); + DWORD process_id = 0; + DWORD thread_id = GetWindowThreadProcessId(remote_window, &process_id); + if (!thread_id || !process_id) + return NOTIFY_FAILED; + + base::CommandLine command_line(*base::CommandLine::ForCurrentProcess()); + command_line.AppendSwitchASCII( + switches::kOriginalProcessStartTime, + base::Int64ToString( + base::CurrentProcessInfo::CreationTime().ToInternalValue())); + + if (fast_start) + command_line.AppendSwitch(switches::kFastStart); + + // Send the command line to the remote chrome window. + // Format is "START\0<<>>\0<<>>". + std::wstring to_send(L"START\0", 6); // want the NULL in the string. + base::FilePath cur_dir; + if (!base::GetCurrentDirectory(&cur_dir)) + return NOTIFY_FAILED; + to_send.append(cur_dir.value()); + to_send.append(L"\0", 1); // Null separator. + to_send.append(command_line.GetCommandLineString()); + to_send.append(L"\0", 1); // Null separator. + + // Allow the current running browser window to make itself the foreground + // window (otherwise it will just flash in the taskbar). + ::AllowSetForegroundWindow(process_id); + + COPYDATASTRUCT cds; + cds.dwData = 0; + cds.cbData = static_cast((to_send.length() + 1) * sizeof(wchar_t)); + cds.lpData = const_cast(to_send.c_str()); + DWORD_PTR result = 0; + if (::SendMessageTimeout(remote_window, WM_COPYDATA, NULL, + reinterpret_cast(&cds), SMTO_ABORTIFHUNG, + timeout_in_milliseconds, &result)) { + return result ? NOTIFY_SUCCESS : NOTIFY_FAILED; + } + + // It is possible that the process owning this window may have died by now. + if (!::IsWindow(remote_window)) + return NOTIFY_FAILED; + + // If the window couldn't be notified but still exists, assume it is hung. + return NOTIFY_WINDOW_HUNG; +} + +base::TimeDelta SetNotificationTimeoutForTesting(base::TimeDelta new_timeout) { + base::TimeDelta old_timeout = + base::TimeDelta::FromMilliseconds(timeout_in_milliseconds); + timeout_in_milliseconds = new_timeout.InMilliseconds(); + return old_timeout; +} + +} // namespace chrome diff --git a/chromium_src/chrome/browser/chrome_process_finder_win.h b/chromium_src/chrome/browser/chrome_process_finder_win.h new file mode 100644 index 0000000000..a66429de5e --- /dev/null +++ b/chromium_src/chrome/browser/chrome_process_finder_win.h @@ -0,0 +1,39 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_CHROME_PROCESS_FINDER_WIN_H_ +#define CHROME_BROWSER_CHROME_PROCESS_FINDER_WIN_H_ + +#include + +#include "base/time/time.h" + +namespace base { +class FilePath; +} + +namespace chrome { + +enum NotifyChromeResult { + NOTIFY_SUCCESS, + NOTIFY_FAILED, + NOTIFY_WINDOW_HUNG, +}; + +// Finds an already running Chrome window if it exists. +HWND FindRunningChromeWindow(const base::FilePath& user_data_dir); + +// Attempts to send the current command line to an already running instance of +// Chrome via a WM_COPYDATA message. +// Returns true if a running Chrome is found and successfully notified. +// |fast_start| is true when this is being called on the window fast start path. +NotifyChromeResult AttemptToNotifyRunningChrome(HWND remote_window, + bool fast_start); + +// Changes the notification timeout to |new_timeout|, returns the old timeout. +base::TimeDelta SetNotificationTimeoutForTesting(base::TimeDelta new_timeout); + +} // namespace chrome + +#endif // CHROME_BROWSER_CHROME_PROCESS_FINDER_WIN_H_ diff --git a/chromium_src/chrome/browser/process_singleton.h b/chromium_src/chrome/browser/process_singleton.h new file mode 100644 index 0000000000..9d99b02b9f --- /dev/null +++ b/chromium_src/chrome/browser/process_singleton.h @@ -0,0 +1,183 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_PROCESS_SINGLETON_H_ +#define CHROME_BROWSER_PROCESS_SINGLETON_H_ + +#include "build/build_config.h" + +#if defined(OS_WIN) +#include +#endif // defined(OS_WIN) + +#include +#include + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/process/process.h" +#include "base/threading/non_thread_safe.h" +#include "ui/gfx/native_widget_types.h" + +#if defined(OS_POSIX) && !defined(OS_ANDROID) +#include "base/files/scoped_temp_dir.h" +#endif + +#if defined(OS_WIN) +#include "base/win/message_window.h" +#endif // defined(OS_WIN) + +namespace base { +class CommandLine; +} + +// ProcessSingleton ---------------------------------------------------------- +// +// This class allows different browser processes to communicate with +// each other. It is named according to the user data directory, so +// we can be sure that no more than one copy of the application can be +// running at once with a given data directory. +// +// Implementation notes: +// - the Windows implementation uses an invisible global message window; +// - the Linux implementation uses a Unix domain socket in the user data dir. + +class ProcessSingleton : public base::NonThreadSafe { + public: + enum NotifyResult { + PROCESS_NONE, + PROCESS_NOTIFIED, + PROFILE_IN_USE, + LOCK_ERROR, + }; + + // Implement this callback to handle notifications from other processes. The + // callback will receive the command line and directory with which the other + // Chrome process was launched. Return true if the command line will be + // handled within the current browser instance or false if the remote process + // should handle it (i.e., because the current process is shutting down). + using NotificationCallback = + base::Callback; + + ProcessSingleton(const base::FilePath& user_data_dir, + const NotificationCallback& notification_callback); + ~ProcessSingleton(); + + // Notify another process, if available. Otherwise sets ourselves as the + // singleton instance. Returns PROCESS_NONE if we became the singleton + // instance. Callers are guaranteed to either have notified an existing + // process or have grabbed the singleton (unless the profile is locked by an + // unreachable process). + // TODO(brettw): Make the implementation of this method non-platform-specific + // by making Linux re-use the Windows implementation. + NotifyResult NotifyOtherProcessOrCreate(); + + // Sets ourself up as the singleton instance. Returns true on success. If + // false is returned, we are not the singleton instance and the caller must + // exit. + // NOTE: Most callers should generally prefer NotifyOtherProcessOrCreate() to + // this method, only callers for whom failure is preferred to notifying + // another process should call this directly. + bool Create(); + + // Clear any lock state during shutdown. + void Cleanup(); + +#if defined(OS_POSIX) && !defined(OS_ANDROID) + static void DisablePromptForTesting(); +#endif +#if defined(OS_WIN) + // Called to query whether to kill a hung browser process that has visible + // windows. Return true to allow killing the hung process. + using ShouldKillRemoteProcessCallback = base::Callback; + void OverrideShouldKillRemoteProcessCallbackForTesting( + const ShouldKillRemoteProcessCallback& display_dialog_callback); +#endif + + protected: + // Notify another process, if available. + // Returns true if another process was found and notified, false if we should + // continue with the current process. + // On Windows, Create() has to be called before this. + NotifyResult NotifyOtherProcess(); + +#if defined(OS_POSIX) && !defined(OS_ANDROID) + // Exposed for testing. We use a timeout on Linux, and in tests we want + // this timeout to be short. + NotifyResult NotifyOtherProcessWithTimeout( + const base::CommandLine& command_line, + int retry_attempts, + const base::TimeDelta& timeout, + bool kill_unresponsive); + NotifyResult NotifyOtherProcessWithTimeoutOrCreate( + const base::CommandLine& command_line, + int retry_attempts, + const base::TimeDelta& timeout); + void OverrideCurrentPidForTesting(base::ProcessId pid); + void OverrideKillCallbackForTesting( + const base::Callback& callback); +#endif + + private: + NotificationCallback notification_callback_; // Handler for notifications. + +#if defined(OS_WIN) + bool EscapeVirtualization(const base::FilePath& user_data_dir); + + HWND remote_window_; // The HWND_MESSAGE of another browser. + base::win::MessageWindow window_; // The message-only window. + bool is_virtualized_; // Stuck inside Microsoft Softricity VM environment. + HANDLE lock_file_; + base::FilePath user_data_dir_; + ShouldKillRemoteProcessCallback should_kill_remote_process_callback_; +#elif defined(OS_POSIX) && !defined(OS_ANDROID) + // Return true if the given pid is one of our child processes. + // Assumes that the current pid is the root of all pids of the current + // instance. + bool IsSameChromeInstance(pid_t pid); + + // Extract the process's pid from a symbol link path and if it is on + // the same host, kill the process, unlink the lock file and return true. + // If the process is part of the same chrome instance, unlink the lock file + // and return true without killing it. + // If the process is on a different host, return false. + bool KillProcessByLockPath(); + + // Default function to kill a process, overridable by tests. + void KillProcess(int pid); + + // Allow overriding for tests. + base::ProcessId current_pid_; + + // Function to call when the other process is hung and needs to be killed. + // Allows overriding for tests. + base::Callback kill_callback_; + + // Path in file system to the socket. + base::FilePath socket_path_; + + // Path in file system to the lock. + base::FilePath lock_path_; + + // Path in file system to the cookie file. + base::FilePath cookie_path_; + + // Temporary directory to hold the socket. + base::ScopedTempDir socket_dir_; + + // Helper class for linux specific messages. LinuxWatcher is ref counted + // because it posts messages between threads. + class LinuxWatcher; + scoped_refptr watcher_; +#endif + + DISALLOW_COPY_AND_ASSIGN(ProcessSingleton); +}; + +#endif // CHROME_BROWSER_PROCESS_SINGLETON_H_ \ No newline at end of file diff --git a/chromium_src/chrome/browser/process_singleton_posix.cc b/chromium_src/chrome/browser/process_singleton_posix.cc new file mode 100644 index 0000000000..e855207ea7 --- /dev/null +++ b/chromium_src/chrome/browser/process_singleton_posix.cc @@ -0,0 +1,1062 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// On Linux, when the user tries to launch a second copy of chrome, we check +// for a socket in the user's profile directory. If the socket file is open we +// send a message to the first chrome browser process with the current +// directory and second process command line flags. The second process then +// exits. +// +// Because many networked filesystem implementations do not support unix domain +// sockets, we create the socket in a temporary directory and create a symlink +// in the profile. This temporary directory is no longer bound to the profile, +// and may disappear across a reboot or login to a separate session. To bind +// them, we store a unique cookie in the profile directory, which must also be +// present in the remote directory to connect. The cookie is checked both before +// and after the connection. /tmp is sticky, and different Chrome sessions use +// different cookies. Thus, a matching cookie before and after means the +// connection was to a directory with a valid cookie. +// +// We also have a lock file, which is a symlink to a non-existent destination. +// The destination is a string containing the hostname and process id of +// chrome's browser process, eg. "SingletonLock -> example.com-9156". When the +// first copy of chrome exits it will delete the lock file on shutdown, so that +// a different instance on a different host may then use the profile directory. +// +// If writing to the socket fails, the hostname in the lock is checked to see if +// another instance is running a different host using a shared filesystem (nfs, +// etc.) If the hostname differs an error is displayed and the second process +// exits. Otherwise the first process (if any) is killed and the second process +// starts as normal. +// +// When the second process sends the current directory and command line flags to +// the first process, it waits for an ACK message back from the first process +// for a certain time. If there is no ACK message back in time, then the first +// process will be considered as hung for some reason. The second process then +// retrieves the process id from the symbol link and kills it by sending +// SIGKILL. Then the second process starts as normal. + +#include "chrome/browser/process_singleton.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "base/base_paths.h" +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/path_service.h" +#include "base/posix/eintr_wrapper.h" +#include "base/posix/safe_strerror.h" +#include "base/rand_util.h" +#include "base/sequenced_task_runner_helpers.h" +#include "base/single_thread_task_runner.h" +#include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/sys_string_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/threading/platform_thread.h" +#include "base/time/time.h" +#include "base/timer/timer.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/grit/chromium_strings.h" +#include "chrome/grit/generated_resources.h" +#include "content/public/browser/browser_thread.h" +#include "net/base/net_util.h" +#include "ui/base/l10n/l10n_util.h" + +#if defined(OS_LINUX) +#include "chrome/browser/ui/process_singleton_dialog_linux.h" +#endif + +#if defined(TOOLKIT_VIEWS) && defined(OS_LINUX) && !defined(OS_CHROMEOS) +#include "ui/views/linux_ui/linux_ui.h" +#endif + +using content::BrowserThread; + +namespace { + +// Timeout for the current browser process to respond. 20 seconds should be +// enough. +const int kTimeoutInSeconds = 20; +// Number of retries to notify the browser. 20 retries over 20 seconds = 1 try +// per second. +const int kRetryAttempts = 20; +static bool g_disable_prompt; +const char kStartToken[] = "START"; +const char kACKToken[] = "ACK"; +const char kShutdownToken[] = "SHUTDOWN"; +const char kTokenDelimiter = '\0'; +const int kMaxMessageLength = 32 * 1024; +const int kMaxACKMessageLength = arraysize(kShutdownToken) - 1; + +const char kLockDelimiter = '-'; + +// Set the close-on-exec bit on a file descriptor. +// Returns 0 on success, -1 on failure. +int SetCloseOnExec(int fd) { + int flags = fcntl(fd, F_GETFD, 0); + if (-1 == flags) + return flags; + if (flags & FD_CLOEXEC) + return 0; + return fcntl(fd, F_SETFD, flags | FD_CLOEXEC); +} + +// Close a socket and check return value. +void CloseSocket(int fd) { + int rv = IGNORE_EINTR(close(fd)); + DCHECK_EQ(0, rv) << "Error closing socket: " << base::safe_strerror(errno); +} + +// Write a message to a socket fd. +bool WriteToSocket(int fd, const char *message, size_t length) { + DCHECK(message); + DCHECK(length); + size_t bytes_written = 0; + do { + ssize_t rv = HANDLE_EINTR( + write(fd, message + bytes_written, length - bytes_written)); + if (rv < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // The socket shouldn't block, we're sending so little data. Just give + // up here, since NotifyOtherProcess() doesn't have an asynchronous api. + LOG(ERROR) << "ProcessSingleton would block on write(), so it gave up."; + return false; + } + PLOG(ERROR) << "write() failed"; + return false; + } + bytes_written += rv; + } while (bytes_written < length); + + return true; +} + +struct timeval TimeDeltaToTimeVal(const base::TimeDelta& delta) { + struct timeval result; + result.tv_sec = delta.InSeconds(); + result.tv_usec = delta.InMicroseconds() % base::Time::kMicrosecondsPerSecond; + return result; +} + +// Wait a socket for read for a certain timeout. +// Returns -1 if error occurred, 0 if timeout reached, > 0 if the socket is +// ready for read. +int WaitSocketForRead(int fd, const base::TimeDelta& timeout) { + fd_set read_fds; + struct timeval tv = TimeDeltaToTimeVal(timeout); + + FD_ZERO(&read_fds); + FD_SET(fd, &read_fds); + + return HANDLE_EINTR(select(fd + 1, &read_fds, NULL, NULL, &tv)); +} + +// Read a message from a socket fd, with an optional timeout. +// If |timeout| <= 0 then read immediately. +// Return number of bytes actually read, or -1 on error. +ssize_t ReadFromSocket(int fd, + char* buf, + size_t bufsize, + const base::TimeDelta& timeout) { + if (timeout > base::TimeDelta()) { + int rv = WaitSocketForRead(fd, timeout); + if (rv <= 0) + return rv; + } + + size_t bytes_read = 0; + do { + ssize_t rv = HANDLE_EINTR(read(fd, buf + bytes_read, bufsize - bytes_read)); + if (rv < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + PLOG(ERROR) << "read() failed"; + return rv; + } else { + // It would block, so we just return what has been read. + return bytes_read; + } + } else if (!rv) { + // No more data to read. + return bytes_read; + } else { + bytes_read += rv; + } + } while (bytes_read < bufsize); + + return bytes_read; +} + +// Set up a sockaddr appropriate for messaging. +void SetupSockAddr(const std::string& path, struct sockaddr_un* addr) { + addr->sun_family = AF_UNIX; + CHECK(path.length() < arraysize(addr->sun_path)) + << "Socket path too long: " << path; + base::strlcpy(addr->sun_path, path.c_str(), arraysize(addr->sun_path)); +} + +// Set up a socket appropriate for messaging. +int SetupSocketOnly() { + int sock = socket(PF_UNIX, SOCK_STREAM, 0); + PCHECK(sock >= 0) << "socket() failed"; + + int rv = net::SetNonBlocking(sock); + DCHECK_EQ(0, rv) << "Failed to make non-blocking socket."; + rv = SetCloseOnExec(sock); + DCHECK_EQ(0, rv) << "Failed to set CLOEXEC on socket."; + + return sock; +} + +// Set up a socket and sockaddr appropriate for messaging. +void SetupSocket(const std::string& path, int* sock, struct sockaddr_un* addr) { + *sock = SetupSocketOnly(); + SetupSockAddr(path, addr); +} + +// Read a symbolic link, return empty string if given path is not a symbol link. +base::FilePath ReadLink(const base::FilePath& path) { + base::FilePath target; + if (!base::ReadSymbolicLink(path, &target)) { + // The only errno that should occur is ENOENT. + if (errno != 0 && errno != ENOENT) + PLOG(ERROR) << "readlink(" << path.value() << ") failed"; + } + return target; +} + +// Unlink a path. Return true on success. +bool UnlinkPath(const base::FilePath& path) { + int rv = unlink(path.value().c_str()); + if (rv < 0 && errno != ENOENT) + PLOG(ERROR) << "Failed to unlink " << path.value(); + + return rv == 0; +} + +// Create a symlink. Returns true on success. +bool SymlinkPath(const base::FilePath& target, const base::FilePath& path) { + if (!base::CreateSymbolicLink(target, path)) { + // Double check the value in case symlink suceeded but we got an incorrect + // failure due to NFS packet loss & retry. + int saved_errno = errno; + if (ReadLink(path) != target) { + // If we failed to create the lock, most likely another instance won the + // startup race. + errno = saved_errno; + PLOG(ERROR) << "Failed to create " << path.value(); + return false; + } + } + return true; +} + +// Extract the hostname and pid from the lock symlink. +// Returns true if the lock existed. +bool ParseLockPath(const base::FilePath& path, + std::string* hostname, + int* pid) { + std::string real_path = ReadLink(path).value(); + if (real_path.empty()) + return false; + + std::string::size_type pos = real_path.rfind(kLockDelimiter); + + // If the path is not a symbolic link, or doesn't contain what we expect, + // bail. + if (pos == std::string::npos) { + *hostname = ""; + *pid = -1; + return true; + } + + *hostname = real_path.substr(0, pos); + + const std::string& pid_str = real_path.substr(pos + 1); + if (!base::StringToInt(pid_str, pid)) + *pid = -1; + + return true; +} + +// Returns true if the user opted to unlock the profile. +bool DisplayProfileInUseError(const base::FilePath& lock_path, + const std::string& hostname, + int pid) { + base::string16 error = l10n_util::GetStringFUTF16( + IDS_PROFILE_IN_USE_POSIX, + base::IntToString16(pid), + base::ASCIIToUTF16(hostname)); + LOG(ERROR) << error; + + if (g_disable_prompt) + return false; + +#if defined(OS_LINUX) + base::string16 relaunch_button_text = l10n_util::GetStringUTF16( + IDS_PROFILE_IN_USE_LINUX_RELAUNCH); + return ShowProcessSingletonDialog(error, relaunch_button_text); +#elif defined(OS_MACOSX) + // On Mac, always usurp the lock. + return true; +#endif + + NOTREACHED(); + return false; +} + +bool IsChromeProcess(pid_t pid) { + base::FilePath other_chrome_path(base::GetProcessExecutablePath(pid)); + return (!other_chrome_path.empty() && + other_chrome_path.BaseName() == + base::FilePath(chrome::kBrowserProcessExecutableName)); +} + +// A helper class to hold onto a socket. +class ScopedSocket { + public: + ScopedSocket() : fd_(-1) { Reset(); } + ~ScopedSocket() { Close(); } + int fd() { return fd_; } + void Reset() { + Close(); + fd_ = SetupSocketOnly(); + } + void Close() { + if (fd_ >= 0) + CloseSocket(fd_); + fd_ = -1; + } + private: + int fd_; +}; + +// Returns a random string for uniquifying profile connections. +std::string GenerateCookie() { + return base::Uint64ToString(base::RandUint64()); +} + +bool CheckCookie(const base::FilePath& path, const base::FilePath& cookie) { + return (cookie == ReadLink(path)); +} + +bool ConnectSocket(ScopedSocket* socket, + const base::FilePath& socket_path, + const base::FilePath& cookie_path) { + base::FilePath socket_target; + if (base::ReadSymbolicLink(socket_path, &socket_target)) { + // It's a symlink. Read the cookie. + base::FilePath cookie = ReadLink(cookie_path); + if (cookie.empty()) + return false; + base::FilePath remote_cookie = socket_target.DirName(). + Append(chrome::kSingletonCookieFilename); + // Verify the cookie before connecting. + if (!CheckCookie(remote_cookie, cookie)) + return false; + // Now we know the directory was (at that point) created by the profile + // owner. Try to connect. + sockaddr_un addr; + SetupSockAddr(socket_target.value(), &addr); + int ret = HANDLE_EINTR(connect(socket->fd(), + reinterpret_cast(&addr), + sizeof(addr))); + if (ret != 0) + return false; + // Check the cookie again. We only link in /tmp, which is sticky, so, if the + // directory is still correct, it must have been correct in-between when we + // connected. POSIX, sadly, lacks a connectat(). + if (!CheckCookie(remote_cookie, cookie)) { + socket->Reset(); + return false; + } + // Success! + return true; + } else if (errno == EINVAL) { + // It exists, but is not a symlink (or some other error we detect + // later). Just connect to it directly; this is an older version of Chrome. + sockaddr_un addr; + SetupSockAddr(socket_path.value(), &addr); + int ret = HANDLE_EINTR(connect(socket->fd(), + reinterpret_cast(&addr), + sizeof(addr))); + return (ret == 0); + } else { + // File is missing, or other error. + if (errno != ENOENT) + PLOG(ERROR) << "readlink failed"; + return false; + } +} + +#if defined(OS_MACOSX) +bool ReplaceOldSingletonLock(const base::FilePath& symlink_content, + const base::FilePath& lock_path) { + // Try taking an flock(2) on the file. Failure means the lock is taken so we + // should quit. + base::ScopedFD lock_fd(HANDLE_EINTR( + open(lock_path.value().c_str(), O_RDWR | O_CREAT | O_SYMLINK, 0644))); + if (!lock_fd.is_valid()) { + PLOG(ERROR) << "Could not open singleton lock"; + return false; + } + + int rc = HANDLE_EINTR(flock(lock_fd.get(), LOCK_EX | LOCK_NB)); + if (rc == -1) { + if (errno == EWOULDBLOCK) { + LOG(ERROR) << "Singleton lock held by old process."; + } else { + PLOG(ERROR) << "Error locking singleton lock"; + } + return false; + } + + // Successfully taking the lock means we can replace it with the a new symlink + // lock. We never flock() the lock file from now on. I.e. we assume that an + // old version of Chrome will not run with the same user data dir after this + // version has run. + if (!base::DeleteFile(lock_path, false)) { + PLOG(ERROR) << "Could not delete old singleton lock."; + return false; + } + + return SymlinkPath(symlink_content, lock_path); +} +#endif // defined(OS_MACOSX) + +} // namespace + +/////////////////////////////////////////////////////////////////////////////// +// ProcessSingleton::LinuxWatcher +// A helper class for a Linux specific implementation of the process singleton. +// This class sets up a listener on the singleton socket and handles parsing +// messages that come in on the singleton socket. +class ProcessSingleton::LinuxWatcher + : public base::MessageLoopForIO::Watcher, + public base::MessageLoop::DestructionObserver, + public base::RefCountedThreadSafe { + public: + // A helper class to read message from an established socket. + class SocketReader : public base::MessageLoopForIO::Watcher { + public: + SocketReader(ProcessSingleton::LinuxWatcher* parent, + base::MessageLoop* ui_message_loop, + int fd) + : parent_(parent), + ui_message_loop_(ui_message_loop), + fd_(fd), + bytes_read_(0) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + // Wait for reads. + base::MessageLoopForIO::current()->WatchFileDescriptor( + fd, true, base::MessageLoopForIO::WATCH_READ, &fd_reader_, this); + // If we haven't completed in a reasonable amount of time, give up. + timer_.Start(FROM_HERE, base::TimeDelta::FromSeconds(kTimeoutInSeconds), + this, &SocketReader::CleanupAndDeleteSelf); + } + + ~SocketReader() override { CloseSocket(fd_); } + + // MessageLoopForIO::Watcher impl. + void OnFileCanReadWithoutBlocking(int fd) override; + void OnFileCanWriteWithoutBlocking(int fd) override { + // SocketReader only watches for accept (read) events. + NOTREACHED(); + } + + // Finish handling the incoming message by optionally sending back an ACK + // message and removing this SocketReader. + void FinishWithACK(const char *message, size_t length); + + private: + void CleanupAndDeleteSelf() { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + + parent_->RemoveSocketReader(this); + // We're deleted beyond this point. + } + + base::MessageLoopForIO::FileDescriptorWatcher fd_reader_; + + // The ProcessSingleton::LinuxWatcher that owns us. + ProcessSingleton::LinuxWatcher* const parent_; + + // A reference to the UI message loop. + base::MessageLoop* const ui_message_loop_; + + // The file descriptor we're reading. + const int fd_; + + // Store the message in this buffer. + char buf_[kMaxMessageLength]; + + // Tracks the number of bytes we've read in case we're getting partial + // reads. + size_t bytes_read_; + + base::OneShotTimer timer_; + + DISALLOW_COPY_AND_ASSIGN(SocketReader); + }; + + // We expect to only be constructed on the UI thread. + explicit LinuxWatcher(ProcessSingleton* parent) + : ui_message_loop_(base::MessageLoop::current()), + parent_(parent) { + } + + // Start listening for connections on the socket. This method should be + // called from the IO thread. + void StartListening(int socket); + + // This method determines if we should use the same process and if we should, + // opens a new browser tab. This runs on the UI thread. + // |reader| is for sending back ACK message. + void HandleMessage(const std::string& current_dir, + const std::vector& argv, + SocketReader* reader); + + // MessageLoopForIO::Watcher impl. These run on the IO thread. + void OnFileCanReadWithoutBlocking(int fd) override; + void OnFileCanWriteWithoutBlocking(int fd) override { + // ProcessSingleton only watches for accept (read) events. + NOTREACHED(); + } + + // MessageLoop::DestructionObserver + void WillDestroyCurrentMessageLoop() override { + fd_watcher_.StopWatchingFileDescriptor(); + } + + private: + friend struct BrowserThread::DeleteOnThread; + friend class base::DeleteHelper; + + ~LinuxWatcher() override { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + STLDeleteElements(&readers_); + + base::MessageLoopForIO* ml = base::MessageLoopForIO::current(); + ml->RemoveDestructionObserver(this); + } + + // Removes and deletes the SocketReader. + void RemoveSocketReader(SocketReader* reader); + + base::MessageLoopForIO::FileDescriptorWatcher fd_watcher_; + + // A reference to the UI message loop (i.e., the message loop we were + // constructed on). + base::MessageLoop* ui_message_loop_; + + // The ProcessSingleton that owns us. + ProcessSingleton* const parent_; + + std::set readers_; + + DISALLOW_COPY_AND_ASSIGN(LinuxWatcher); +}; + +void ProcessSingleton::LinuxWatcher::OnFileCanReadWithoutBlocking(int fd) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + // Accepting incoming client. + sockaddr_un from; + socklen_t from_len = sizeof(from); + int connection_socket = HANDLE_EINTR(accept( + fd, reinterpret_cast(&from), &from_len)); + if (-1 == connection_socket) { + PLOG(ERROR) << "accept() failed"; + return; + } + int rv = net::SetNonBlocking(connection_socket); + DCHECK_EQ(0, rv) << "Failed to make non-blocking socket."; + SocketReader* reader = new SocketReader(this, + ui_message_loop_, + connection_socket); + readers_.insert(reader); +} + +void ProcessSingleton::LinuxWatcher::StartListening(int socket) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + // Watch for client connections on this socket. + base::MessageLoopForIO* ml = base::MessageLoopForIO::current(); + ml->AddDestructionObserver(this); + ml->WatchFileDescriptor(socket, true, base::MessageLoopForIO::WATCH_READ, + &fd_watcher_, this); +} + +void ProcessSingleton::LinuxWatcher::HandleMessage( + const std::string& current_dir, const std::vector& argv, + SocketReader* reader) { + DCHECK(ui_message_loop_ == base::MessageLoop::current()); + DCHECK(reader); + + if (parent_->notification_callback_.Run(base::CommandLine(argv), + base::FilePath(current_dir))) { + // Send back "ACK" message to prevent the client process from starting up. + reader->FinishWithACK(kACKToken, arraysize(kACKToken) - 1); + } else { + LOG(WARNING) << "Not handling interprocess notification as browser" + " is shutting down"; + // Send back "SHUTDOWN" message, so that the client process can start up + // without killing this process. + reader->FinishWithACK(kShutdownToken, arraysize(kShutdownToken) - 1); + return; + } +} + +void ProcessSingleton::LinuxWatcher::RemoveSocketReader(SocketReader* reader) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + DCHECK(reader); + readers_.erase(reader); + delete reader; +} + +/////////////////////////////////////////////////////////////////////////////// +// ProcessSingleton::LinuxWatcher::SocketReader +// + +void ProcessSingleton::LinuxWatcher::SocketReader::OnFileCanReadWithoutBlocking( + int fd) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + DCHECK_EQ(fd, fd_); + while (bytes_read_ < sizeof(buf_)) { + ssize_t rv = HANDLE_EINTR( + read(fd, buf_ + bytes_read_, sizeof(buf_) - bytes_read_)); + if (rv < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + PLOG(ERROR) << "read() failed"; + CloseSocket(fd); + return; + } else { + // It would block, so we just return and continue to watch for the next + // opportunity to read. + return; + } + } else if (!rv) { + // No more data to read. It's time to process the message. + break; + } else { + bytes_read_ += rv; + } + } + + // Validate the message. The shortest message is kStartToken\0x\0x + const size_t kMinMessageLength = arraysize(kStartToken) + 4; + if (bytes_read_ < kMinMessageLength) { + buf_[bytes_read_] = 0; + LOG(ERROR) << "Invalid socket message (wrong length):" << buf_; + CleanupAndDeleteSelf(); + return; + } + + std::string str(buf_, bytes_read_); + std::vector tokens = base::SplitString( + str, std::string(1, kTokenDelimiter), + base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + + if (tokens.size() < 3 || tokens[0] != kStartToken) { + LOG(ERROR) << "Wrong message format: " << str; + CleanupAndDeleteSelf(); + return; + } + + // Stop the expiration timer to prevent this SocketReader object from being + // terminated unexpectly. + timer_.Stop(); + + std::string current_dir = tokens[1]; + // Remove the first two tokens. The remaining tokens should be the command + // line argv array. + tokens.erase(tokens.begin()); + tokens.erase(tokens.begin()); + + // Return to the UI thread to handle opening a new browser tab. + ui_message_loop_->task_runner()->PostTask( + FROM_HERE, base::Bind(&ProcessSingleton::LinuxWatcher::HandleMessage, + parent_, current_dir, tokens, this)); + fd_reader_.StopWatchingFileDescriptor(); + + // LinuxWatcher::HandleMessage() is in charge of destroying this SocketReader + // object by invoking SocketReader::FinishWithACK(). +} + +void ProcessSingleton::LinuxWatcher::SocketReader::FinishWithACK( + const char *message, size_t length) { + if (message && length) { + // Not necessary to care about the return value. + WriteToSocket(fd_, message, length); + } + + if (shutdown(fd_, SHUT_WR) < 0) + PLOG(ERROR) << "shutdown() failed"; + + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + base::Bind(&ProcessSingleton::LinuxWatcher::RemoveSocketReader, + parent_, + this)); + // We will be deleted once the posted RemoveSocketReader task runs. +} + +/////////////////////////////////////////////////////////////////////////////// +// ProcessSingleton +// +ProcessSingleton::ProcessSingleton( + const base::FilePath& user_data_dir, + const NotificationCallback& notification_callback) + : notification_callback_(notification_callback), + current_pid_(base::GetCurrentProcId()), + watcher_(new LinuxWatcher(this)) { + socket_path_ = user_data_dir.Append(chrome::kSingletonSocketFilename); + lock_path_ = user_data_dir.Append(chrome::kSingletonLockFilename); + cookie_path_ = user_data_dir.Append(chrome::kSingletonCookieFilename); + + kill_callback_ = base::Bind(&ProcessSingleton::KillProcess, + base::Unretained(this)); +} + +ProcessSingleton::~ProcessSingleton() { +} + +ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() { + return NotifyOtherProcessWithTimeout( + *base::CommandLine::ForCurrentProcess(), kRetryAttempts, + base::TimeDelta::FromSeconds(kTimeoutInSeconds), true); +} + +ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout( + const base::CommandLine& cmd_line, + int retry_attempts, + const base::TimeDelta& timeout, + bool kill_unresponsive) { + DCHECK_GE(retry_attempts, 0); + DCHECK_GE(timeout.InMicroseconds(), 0); + + base::TimeDelta sleep_interval = timeout / retry_attempts; + + ScopedSocket socket; + for (int retries = 0; retries <= retry_attempts; ++retries) { + // Try to connect to the socket. + if (ConnectSocket(&socket, socket_path_, cookie_path_)) + break; + + // If we're in a race with another process, they may be in Create() and have + // created the lock but not attached to the socket. So we check if the + // process with the pid from the lockfile is currently running and is a + // chrome browser. If so, we loop and try again for |timeout|. + + std::string hostname; + int pid; + if (!ParseLockPath(lock_path_, &hostname, &pid)) { + // No lockfile exists. + return PROCESS_NONE; + } + + if (hostname.empty()) { + // Invalid lockfile. + UnlinkPath(lock_path_); + return PROCESS_NONE; + } + + if (hostname != net::GetHostName() && !IsChromeProcess(pid)) { + // Locked by process on another host. If the user selected to unlock + // the profile, try to continue; otherwise quit. + if (DisplayProfileInUseError(lock_path_, hostname, pid)) { + UnlinkPath(lock_path_); + return PROCESS_NONE; + } + return PROFILE_IN_USE; + } + + if (!IsChromeProcess(pid)) { + // Orphaned lockfile (no process with pid, or non-chrome process.) + UnlinkPath(lock_path_); + return PROCESS_NONE; + } + + if (IsSameChromeInstance(pid)) { + // Orphaned lockfile (pid is part of same chrome instance we are, even + // though we haven't tried to create a lockfile yet). + UnlinkPath(lock_path_); + return PROCESS_NONE; + } + + if (retries == retry_attempts) { + // Retries failed. Kill the unresponsive chrome process and continue. + if (!kill_unresponsive || !KillProcessByLockPath()) + return PROFILE_IN_USE; + return PROCESS_NONE; + } + + base::PlatformThread::Sleep(sleep_interval); + } + + timeval socket_timeout = TimeDeltaToTimeVal(timeout); + setsockopt(socket.fd(), + SOL_SOCKET, + SO_SNDTIMEO, + &socket_timeout, + sizeof(socket_timeout)); + + // Found another process, prepare our command line + // format is "START\0\0\0...\0". + std::string to_send(kStartToken); + to_send.push_back(kTokenDelimiter); + + base::FilePath current_dir; + if (!PathService::Get(base::DIR_CURRENT, ¤t_dir)) + return PROCESS_NONE; + to_send.append(current_dir.value()); + + const std::vector& argv = cmd_line.argv(); + for (std::vector::const_iterator it = argv.begin(); + it != argv.end(); ++it) { + to_send.push_back(kTokenDelimiter); + to_send.append(*it); + } + + // Send the message + if (!WriteToSocket(socket.fd(), to_send.data(), to_send.length())) { + // Try to kill the other process, because it might have been dead. + if (!kill_unresponsive || !KillProcessByLockPath()) + return PROFILE_IN_USE; + return PROCESS_NONE; + } + + if (shutdown(socket.fd(), SHUT_WR) < 0) + PLOG(ERROR) << "shutdown() failed"; + + // Read ACK message from the other process. It might be blocked for a certain + // timeout, to make sure the other process has enough time to return ACK. + char buf[kMaxACKMessageLength + 1]; + ssize_t len = ReadFromSocket(socket.fd(), buf, kMaxACKMessageLength, timeout); + + // Failed to read ACK, the other process might have been frozen. + if (len <= 0) { + if (!kill_unresponsive || !KillProcessByLockPath()) + return PROFILE_IN_USE; + return PROCESS_NONE; + } + + buf[len] = '\0'; + if (strncmp(buf, kShutdownToken, arraysize(kShutdownToken) - 1) == 0) { + // The other process is shutting down, it's safe to start a new process. + return PROCESS_NONE; + } else if (strncmp(buf, kACKToken, arraysize(kACKToken) - 1) == 0) { +#if defined(TOOLKIT_VIEWS) && defined(OS_LINUX) && !defined(OS_CHROMEOS) + // Likely NULL in unit tests. + views::LinuxUI* linux_ui = views::LinuxUI::instance(); + if (linux_ui) + linux_ui->NotifyWindowManagerStartupComplete(); +#endif + + // Assume the other process is handling the request. + return PROCESS_NOTIFIED; + } + + NOTREACHED() << "The other process returned unknown message: " << buf; + return PROCESS_NOTIFIED; +} + +ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessOrCreate() { + return NotifyOtherProcessWithTimeoutOrCreate( + *base::CommandLine::ForCurrentProcess(), kRetryAttempts, + base::TimeDelta::FromSeconds(kTimeoutInSeconds)); +} + +ProcessSingleton::NotifyResult +ProcessSingleton::NotifyOtherProcessWithTimeoutOrCreate( + const base::CommandLine& command_line, + int retry_attempts, + const base::TimeDelta& timeout) { + NotifyResult result = NotifyOtherProcessWithTimeout( + command_line, retry_attempts, timeout, true); + if (result != PROCESS_NONE) + return result; + if (Create()) + return PROCESS_NONE; + // If the Create() failed, try again to notify. (It could be that another + // instance was starting at the same time and managed to grab the lock before + // we did.) + // This time, we don't want to kill anything if we aren't successful, since we + // aren't going to try to take over the lock ourselves. + result = NotifyOtherProcessWithTimeout( + command_line, retry_attempts, timeout, false); + if (result != PROCESS_NONE) + return result; + + return LOCK_ERROR; +} + +void ProcessSingleton::OverrideCurrentPidForTesting(base::ProcessId pid) { + current_pid_ = pid; +} + +void ProcessSingleton::OverrideKillCallbackForTesting( + const base::Callback& callback) { + kill_callback_ = callback; +} + +void ProcessSingleton::DisablePromptForTesting() { + g_disable_prompt = true; +} + +bool ProcessSingleton::Create() { + int sock; + sockaddr_un addr; + + // The symlink lock is pointed to the hostname and process id, so other + // processes can find it out. + base::FilePath symlink_content(base::StringPrintf( + "%s%c%u", + net::GetHostName().c_str(), + kLockDelimiter, + current_pid_)); + + // Create symbol link before binding the socket, to ensure only one instance + // can have the socket open. + if (!SymlinkPath(symlink_content, lock_path_)) { + // TODO(jackhou): Remove this case once this code is stable on Mac. + // http://crbug.com/367612 +#if defined(OS_MACOSX) + // On Mac, an existing non-symlink lock file means the lock could be held by + // the old process singleton code. If we can successfully replace the lock, + // continue as normal. + if (base::IsLink(lock_path_) || + !ReplaceOldSingletonLock(symlink_content, lock_path_)) { + return false; + } +#else + // If we failed to create the lock, most likely another instance won the + // startup race. + return false; +#endif + } + + // Create the socket file somewhere in /tmp which is usually mounted as a + // normal filesystem. Some network filesystems (notably AFS) are screwy and + // do not support Unix domain sockets. + if (!socket_dir_.CreateUniqueTempDir()) { + LOG(ERROR) << "Failed to create socket directory."; + return false; + } + + // Check that the directory was created with the correct permissions. + int dir_mode = 0; + CHECK(base::GetPosixFilePermissions(socket_dir_.path(), &dir_mode) && + dir_mode == base::FILE_PERMISSION_USER_MASK) + << "Temp directory mode is not 700: " << std::oct << dir_mode; + + // Setup the socket symlink and the two cookies. + base::FilePath socket_target_path = + socket_dir_.path().Append(chrome::kSingletonSocketFilename); + base::FilePath cookie(GenerateCookie()); + base::FilePath remote_cookie_path = + socket_dir_.path().Append(chrome::kSingletonCookieFilename); + UnlinkPath(socket_path_); + UnlinkPath(cookie_path_); + if (!SymlinkPath(socket_target_path, socket_path_) || + !SymlinkPath(cookie, cookie_path_) || + !SymlinkPath(cookie, remote_cookie_path)) { + // We've already locked things, so we can't have lost the startup race, + // but something doesn't like us. + LOG(ERROR) << "Failed to create symlinks."; + if (!socket_dir_.Delete()) + LOG(ERROR) << "Encountered a problem when deleting socket directory."; + return false; + } + + SetupSocket(socket_target_path.value(), &sock, &addr); + + if (bind(sock, reinterpret_cast(&addr), sizeof(addr)) < 0) { + PLOG(ERROR) << "Failed to bind() " << socket_target_path.value(); + CloseSocket(sock); + return false; + } + + if (listen(sock, 5) < 0) + NOTREACHED() << "listen failed: " << base::safe_strerror(errno); + + DCHECK(BrowserThread::IsMessageLoopValid(BrowserThread::IO)); + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + base::Bind(&ProcessSingleton::LinuxWatcher::StartListening, + watcher_.get(), + sock)); + + return true; +} + +void ProcessSingleton::Cleanup() { + UnlinkPath(socket_path_); + UnlinkPath(cookie_path_); + UnlinkPath(lock_path_); +} + +bool ProcessSingleton::IsSameChromeInstance(pid_t pid) { + pid_t cur_pid = current_pid_; + while (pid != cur_pid) { + pid = base::GetParentProcessId(pid); + if (pid < 0) + return false; + if (!IsChromeProcess(pid)) + return false; + } + return true; +} + +bool ProcessSingleton::KillProcessByLockPath() { + std::string hostname; + int pid; + ParseLockPath(lock_path_, &hostname, &pid); + + if (!hostname.empty() && hostname != net::GetHostName()) { + return DisplayProfileInUseError(lock_path_, hostname, pid); + } + UnlinkPath(lock_path_); + + if (IsSameChromeInstance(pid)) + return true; + + if (pid > 0) { + kill_callback_.Run(pid); + return true; + } + + LOG(ERROR) << "Failed to extract pid from path: " << lock_path_.value(); + return true; +} + +void ProcessSingleton::KillProcess(int pid) { + // TODO(james.su@gmail.com): Is SIGKILL ok? + int rv = kill(static_cast(pid), SIGKILL); + // ESRCH = No Such Process (can happen if the other process is already in + // progress of shutting down and finishes before we try to kill it). + DCHECK(rv == 0 || errno == ESRCH) << "Error killing process: " + << base::safe_strerror(errno); +} \ No newline at end of file diff --git a/chromium_src/chrome/browser/process_singleton_startup_lock.cc b/chromium_src/chrome/browser/process_singleton_startup_lock.cc new file mode 100644 index 0000000000..b97ada658c --- /dev/null +++ b/chromium_src/chrome/browser/process_singleton_startup_lock.cc @@ -0,0 +1,53 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/process_singleton_startup_lock.h" + +#include "base/bind.h" +#include "base/logging.h" + +ProcessSingletonStartupLock::ProcessSingletonStartupLock( + const ProcessSingleton::NotificationCallback& original_callback) + : locked_(true), + original_callback_(original_callback) {} + +ProcessSingletonStartupLock::~ProcessSingletonStartupLock() {} + +ProcessSingleton::NotificationCallback +ProcessSingletonStartupLock::AsNotificationCallback() { + return base::Bind(&ProcessSingletonStartupLock::NotificationCallbackImpl, + base::Unretained(this)); +} + +void ProcessSingletonStartupLock::Unlock() { + DCHECK(CalledOnValidThread()); + locked_ = false; + + // Replay the command lines of the messages which were received while the + // ProcessSingleton was locked. Only replay each message once. + std::set replayed_messages; + for (std::vector::const_iterator it = + saved_startup_messages_.begin(); + it != saved_startup_messages_.end(); ++it) { + if (replayed_messages.find(*it) != replayed_messages.end()) + continue; + original_callback_.Run(base::CommandLine(it->first), it->second); + replayed_messages.insert(*it); + } + saved_startup_messages_.clear(); +} + +bool ProcessSingletonStartupLock::NotificationCallbackImpl( + const base::CommandLine& command_line, + const base::FilePath& current_directory) { + if (locked_) { + // If locked, it means we are not ready to process this message because + // we are probably in a first run critical phase. + saved_startup_messages_.push_back( + std::make_pair(command_line.argv(), current_directory)); + return true; + } else { + return original_callback_.Run(command_line, current_directory); + } +} \ No newline at end of file diff --git a/chromium_src/chrome/browser/process_singleton_startup_lock.h b/chromium_src/chrome/browser/process_singleton_startup_lock.h new file mode 100644 index 0000000000..3df51d8b17 --- /dev/null +++ b/chromium_src/chrome/browser/process_singleton_startup_lock.h @@ -0,0 +1,57 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_PROCESS_SINGLETON_STARTUP_LOCK_H_ +#define CHROME_BROWSER_PROCESS_SINGLETON_STARTUP_LOCK_H_ + +#include +#include +#include + +#include "base/basictypes.h" +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/threading/non_thread_safe.h" +#include "chrome/browser/process_singleton.h" + +// Provides a ProcessSingleton::NotificationCallback that can queue up +// command-line invocations during startup and execute them when startup +// completes. +// +// The object starts in a locked state. |Unlock()| must be called +// when the process is prepared to handle command-line invocations. +// +// Once unlocked, notifications are forwarded to a wrapped NotificationCallback. +class ProcessSingletonStartupLock : public base::NonThreadSafe { + public: + explicit ProcessSingletonStartupLock( + const ProcessSingleton::NotificationCallback& original_callback); + ~ProcessSingletonStartupLock(); + + // Returns the ProcessSingleton::NotificationCallback. + // The callback is only valid during the lifetime of the + // ProcessSingletonStartupLock instance. + ProcessSingleton::NotificationCallback AsNotificationCallback(); + + // Executes previously queued command-line invocations and allows future + // invocations to be executed immediately. + void Unlock(); + + bool locked() { return locked_; } + + private: + typedef std::pair + DelayedStartupMessage; + + bool NotificationCallbackImpl(const base::CommandLine& command_line, + const base::FilePath& current_directory); + + bool locked_; + std::vector saved_startup_messages_; + ProcessSingleton::NotificationCallback original_callback_; + + DISALLOW_COPY_AND_ASSIGN(ProcessSingletonStartupLock); +}; + +#endif // CHROME_BROWSER_PROCESS_SINGLETON_STARTUP_LOCK_H_ \ No newline at end of file diff --git a/chromium_src/chrome/browser/process_singleton_win.cc b/chromium_src/chrome/browser/process_singleton_win.cc new file mode 100644 index 0000000000..623aa7f42f --- /dev/null +++ b/chromium_src/chrome/browser/process_singleton_win.cc @@ -0,0 +1,441 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/process_singleton.h" + +#include + +#include "base/base_paths.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/process/process.h" +#include "base/process/process_info.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" +#include "base/win/metro.h" +#include "base/win/registry.h" +#include "base/win/scoped_handle.h" +#include "base/win/windows_version.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/browser_process_platform_part.h" +#include "chrome/browser/chrome_process_finder_win.h" +#include "chrome/browser/metro_utils/metro_chrome_win.h" +#include "chrome/browser/shell_integration.h" +#include "chrome/browser/ui/simple_message_box.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/chrome_paths_internal.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/grit/chromium_strings.h" +#include "chrome/installer/util/wmi.h" +#include "content/public/common/result_codes.h" +#include "net/base/escape.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/gfx/win/hwnd_util.h" + +namespace { + +const char kLockfile[] = "lockfile"; + +const int kMetroChromeActivationTimeoutMs = 3000; + +// A helper class that acquires the given |mutex| while the AutoLockMutex is in +// scope. +class AutoLockMutex { + public: + explicit AutoLockMutex(HANDLE mutex) : mutex_(mutex) { + DWORD result = ::WaitForSingleObject(mutex_, INFINITE); + DPCHECK(result == WAIT_OBJECT_0) << "Result = " << result; + } + + ~AutoLockMutex() { + BOOL released = ::ReleaseMutex(mutex_); + DPCHECK(released); + } + + private: + HANDLE mutex_; + DISALLOW_COPY_AND_ASSIGN(AutoLockMutex); +}; + +// A helper class that releases the given |mutex| while the AutoUnlockMutex is +// in scope and immediately re-acquires it when going out of scope. +class AutoUnlockMutex { + public: + explicit AutoUnlockMutex(HANDLE mutex) : mutex_(mutex) { + BOOL released = ::ReleaseMutex(mutex_); + DPCHECK(released); + } + + ~AutoUnlockMutex() { + DWORD result = ::WaitForSingleObject(mutex_, INFINITE); + DPCHECK(result == WAIT_OBJECT_0) << "Result = " << result; + } + + private: + HANDLE mutex_; + DISALLOW_COPY_AND_ASSIGN(AutoUnlockMutex); +}; + +// Checks the visibility of the enumerated window and signals once a visible +// window has been found. +BOOL CALLBACK BrowserWindowEnumeration(HWND window, LPARAM param) { + bool* result = reinterpret_cast(param); + *result = ::IsWindowVisible(window) != 0; + // Stops enumeration if a visible window has been found. + return !*result; +} + +bool ParseCommandLine(const COPYDATASTRUCT* cds, + base::CommandLine* parsed_command_line, + base::FilePath* current_directory) { + // We should have enough room for the shortest command (min_message_size) + // and also be a multiple of wchar_t bytes. The shortest command + // possible is L"START\0\0" (empty current directory and command line). + static const int min_message_size = 7; + if (cds->cbData < min_message_size * sizeof(wchar_t) || + cds->cbData % sizeof(wchar_t) != 0) { + LOG(WARNING) << "Invalid WM_COPYDATA, length = " << cds->cbData; + return false; + } + + // We split the string into 4 parts on NULLs. + DCHECK(cds->lpData); + const std::wstring msg(static_cast(cds->lpData), + cds->cbData / sizeof(wchar_t)); + const std::wstring::size_type first_null = msg.find_first_of(L'\0'); + if (first_null == 0 || first_null == std::wstring::npos) { + // no NULL byte, don't know what to do + LOG(WARNING) << "Invalid WM_COPYDATA, length = " << msg.length() << + ", first null = " << first_null; + return false; + } + + // Decode the command, which is everything until the first NULL. + if (msg.substr(0, first_null) == L"START") { + // Another instance is starting parse the command line & do what it would + // have done. + VLOG(1) << "Handling STARTUP request from another process"; + const std::wstring::size_type second_null = + msg.find_first_of(L'\0', first_null + 1); + if (second_null == std::wstring::npos || + first_null == msg.length() - 1 || second_null == msg.length()) { + LOG(WARNING) << "Invalid format for start command, we need a string in 4 " + "parts separated by NULLs"; + return false; + } + + // Get current directory. + *current_directory = base::FilePath(msg.substr(first_null + 1, + second_null - first_null)); + + const std::wstring::size_type third_null = + msg.find_first_of(L'\0', second_null + 1); + if (third_null == std::wstring::npos || + third_null == msg.length()) { + LOG(WARNING) << "Invalid format for start command, we need a string in 4 " + "parts separated by NULLs"; + } + + // Get command line. + const std::wstring cmd_line = + msg.substr(second_null + 1, third_null - second_null); + *parsed_command_line = base::CommandLine::FromString(cmd_line); + return true; + } + return false; +} + +bool ProcessLaunchNotification( + const ProcessSingleton::NotificationCallback& notification_callback, + UINT message, + WPARAM wparam, + LPARAM lparam, + LRESULT* result) { + if (message != WM_COPYDATA) + return false; + + // Handle the WM_COPYDATA message from another process. + const COPYDATASTRUCT* cds = reinterpret_cast(lparam); + + base::CommandLine parsed_command_line(base::CommandLine::NO_PROGRAM); + base::FilePath current_directory; + if (!ParseCommandLine(cds, &parsed_command_line, ¤t_directory)) { + *result = TRUE; + return true; + } + + *result = notification_callback.Run(parsed_command_line, current_directory) ? + TRUE : FALSE; + return true; +} + +// Returns true if Chrome needs to be relaunched into Windows 8 immersive mode. +// Following conditions apply:- +// 1. Windows 8 or greater. +// 2. Not in Windows 8 immersive mode. +// 3. Chrome is default browser. +// 4. Process integrity level is not high. +// 5. The profile data directory is the default directory. +// 6. Last used mode was immersive/machine is a tablet. +// TODO(ananta) +// Move this function to a common place as the Windows 8 delegate_execute +// handler can possibly use this. +bool ShouldLaunchInWindows8ImmersiveMode(const base::FilePath& user_data_dir) { + // Returning false from this function doesn't mean we don't launch immersive + // mode in Aura. This function is specifically called in case when we need + // to relaunch desktop launched chrome into immersive mode through 'relaunch' + // menu. In case of Aura, we will use delegate_execute to do the relaunch. + return false; +} + +bool DisplayShouldKillMessageBox() { + return chrome::ShowMessageBox( + NULL, l10n_util::GetStringUTF16(IDS_PRODUCT_NAME), + l10n_util::GetStringUTF16(IDS_BROWSER_HUNGBROWSER_MESSAGE), + chrome::MESSAGE_BOX_TYPE_QUESTION) != + chrome::MESSAGE_BOX_RESULT_NO; +} + +} // namespace + +// Microsoft's Softricity virtualization breaks the sandbox processes. +// So, if we detect the Softricity DLL we use WMI Win32_Process.Create to +// break out of the virtualization environment. +// http://code.google.com/p/chromium/issues/detail?id=43650 +bool ProcessSingleton::EscapeVirtualization( + const base::FilePath& user_data_dir) { + if (::GetModuleHandle(L"sftldr_wow64.dll") || + ::GetModuleHandle(L"sftldr.dll")) { + int process_id; + if (!installer::WMIProcess::Launch(::GetCommandLineW(), &process_id)) + return false; + is_virtualized_ = true; + // The new window was spawned from WMI, and won't be in the foreground. + // So, first we sleep while the new chrome.exe instance starts (because + // WaitForInputIdle doesn't work here). Then we poll for up to two more + // seconds and make the window foreground if we find it (or we give up). + HWND hwnd = 0; + ::Sleep(90); + for (int tries = 200; tries; --tries) { + hwnd = chrome::FindRunningChromeWindow(user_data_dir); + if (hwnd) { + ::SetForegroundWindow(hwnd); + break; + } + ::Sleep(10); + } + return true; + } + return false; +} + +ProcessSingleton::ProcessSingleton( + const base::FilePath& user_data_dir, + const NotificationCallback& notification_callback) + : notification_callback_(notification_callback), + is_virtualized_(false), + lock_file_(INVALID_HANDLE_VALUE), + user_data_dir_(user_data_dir), + should_kill_remote_process_callback_( + base::Bind(&DisplayShouldKillMessageBox)) { +} + +ProcessSingleton::~ProcessSingleton() { + if (lock_file_ != INVALID_HANDLE_VALUE) + ::CloseHandle(lock_file_); +} + +// Code roughly based on Mozilla. +ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() { + if (is_virtualized_) + return PROCESS_NOTIFIED; // We already spawned the process in this case. + if (lock_file_ == INVALID_HANDLE_VALUE && !remote_window_) { + return LOCK_ERROR; + } else if (!remote_window_) { + return PROCESS_NONE; + } + + switch (chrome::AttemptToNotifyRunningChrome(remote_window_, false)) { + case chrome::NOTIFY_SUCCESS: + return PROCESS_NOTIFIED; + case chrome::NOTIFY_FAILED: + remote_window_ = NULL; + return PROCESS_NONE; + case chrome::NOTIFY_WINDOW_HUNG: + // Fall through and potentially terminate the hung browser. + break; + } + + DWORD process_id = 0; + DWORD thread_id = ::GetWindowThreadProcessId(remote_window_, &process_id); + if (!thread_id || !process_id) { + remote_window_ = NULL; + return PROCESS_NONE; + } + base::Process process = base::Process::Open(process_id); + + // The window is hung. Scan for every window to find a visible one. + bool visible_window = false; + ::EnumThreadWindows(thread_id, + &BrowserWindowEnumeration, + reinterpret_cast(&visible_window)); + + // If there is a visible browser window, ask the user before killing it. + if (visible_window && !should_kill_remote_process_callback_.Run()) { + // The user denied. Quit silently. + return PROCESS_NOTIFIED; + } + + // Time to take action. Kill the browser process. + process.Terminate(content::RESULT_CODE_HUNG, true); + remote_window_ = NULL; + return PROCESS_NONE; +} + +ProcessSingleton::NotifyResult +ProcessSingleton::NotifyOtherProcessOrCreate() { + ProcessSingleton::NotifyResult result = PROCESS_NONE; + if (!Create()) { + result = NotifyOtherProcess(); + if (result == PROCESS_NONE) + result = PROFILE_IN_USE; + } else { + g_browser_process->platform_part()->PlatformSpecificCommandLineProcessing( + *base::CommandLine::ForCurrentProcess()); + } + return result; +} + +// Look for a Chrome instance that uses the same profile directory. If there +// isn't one, create a message window with its title set to the profile +// directory path. +bool ProcessSingleton::Create() { + static const wchar_t kMutexName[] = L"Local\\ChromeProcessSingletonStartup!"; + static const wchar_t kMetroActivationEventName[] = + L"Local\\ChromeProcessSingletonStartupMetroActivation!"; + + remote_window_ = chrome::FindRunningChromeWindow(user_data_dir_); + if (!remote_window_ && !EscapeVirtualization(user_data_dir_)) { + // Make sure we will be the one and only process creating the window. + // We use a named Mutex since we are protecting against multi-process + // access. As documented, it's clearer to NOT request ownership on creation + // since it isn't guaranteed we will get it. It is better to create it + // without ownership and explicitly get the ownership afterward. + base::win::ScopedHandle only_me(::CreateMutex(NULL, FALSE, kMutexName)); + if (!only_me.IsValid()) { + DPLOG(FATAL) << "CreateMutex failed"; + return false; + } + + AutoLockMutex auto_lock_only_me(only_me.Get()); + + // We now own the mutex so we are the only process that can create the + // window at this time, but we must still check if someone created it + // between the time where we looked for it above and the time the mutex + // was given to us. + remote_window_ = chrome::FindRunningChromeWindow(user_data_dir_); + + + // In Win8+, a new Chrome process launched in Desktop mode may need to be + // transmuted into Metro Chrome (see ShouldLaunchInWindows8ImmersiveMode for + // heuristics). To accomplish this, the current Chrome activates Metro + // Chrome, releases the startup mutex, and waits for metro Chrome to take + // the singleton. From that point onward, the command line for this Chrome + // process will be sent to Metro Chrome by the usual channels. + if (!remote_window_ && base::win::GetVersion() >= base::win::VERSION_WIN8 && + !base::win::IsMetroProcess()) { + // |metro_activation_event| is created right before activating a Metro + // Chrome (note that there can only be one Metro Chrome process; by OS + // design); all following Desktop processes will then wait for this event + // to be signaled by Metro Chrome which will do so as soon as it grabs + // this singleton (should any of the waiting processes timeout waiting for + // the signal they will try to grab the singleton for themselves which + // will result in a forced Desktop Chrome launch in the worst case). + base::win::ScopedHandle metro_activation_event( + ::OpenEvent(SYNCHRONIZE, FALSE, kMetroActivationEventName)); + if (!metro_activation_event.IsValid() && + ShouldLaunchInWindows8ImmersiveMode(user_data_dir_)) { + // No Metro activation is under way, but the desire is to launch in + // Metro mode: activate and rendez-vous with the activated process. + metro_activation_event.Set( + ::CreateEvent(NULL, TRUE, FALSE, kMetroActivationEventName)); + if (!chrome::ActivateMetroChrome()) { + // Failed to launch immersive Chrome, default to launching on Desktop. + LOG(ERROR) << "Failed to launch immersive chrome"; + metro_activation_event.Close(); + } + } + + if (metro_activation_event.IsValid()) { + // Release |only_me| (to let Metro Chrome grab this singleton) and wait + // until the event is signaled (i.e. Metro Chrome was successfully + // activated). Ignore timeout waiting for |metro_activation_event|. + { + AutoUnlockMutex auto_unlock_only_me(only_me.Get()); + + DWORD result = ::WaitForSingleObject(metro_activation_event.Get(), + kMetroChromeActivationTimeoutMs); + DPCHECK(result == WAIT_OBJECT_0 || result == WAIT_TIMEOUT) + << "Result = " << result; + } + + // Check if this singleton was successfully grabbed by another process + // (hopefully Metro Chrome). Failing to do so, this process will grab + // the singleton and launch in Desktop mode. + remote_window_ = chrome::FindRunningChromeWindow(user_data_dir_); + } + } + + if (!remote_window_) { + // We have to make sure there is no Chrome instance running on another + // machine that uses the same profile. + base::FilePath lock_file_path = user_data_dir_.AppendASCII(kLockfile); + lock_file_ = ::CreateFile(lock_file_path.value().c_str(), + GENERIC_WRITE, + FILE_SHARE_READ, + NULL, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL | + FILE_FLAG_DELETE_ON_CLOSE, + NULL); + DWORD error = ::GetLastError(); + LOG_IF(WARNING, lock_file_ != INVALID_HANDLE_VALUE && + error == ERROR_ALREADY_EXISTS) << "Lock file exists but is writable."; + LOG_IF(ERROR, lock_file_ == INVALID_HANDLE_VALUE) + << "Lock file can not be created! Error code: " << error; + + if (lock_file_ != INVALID_HANDLE_VALUE) { + // Set the window's title to the path of our user data directory so + // other Chrome instances can decide if they should forward to us. + bool result = window_.CreateNamed( + base::Bind(&ProcessLaunchNotification, notification_callback_), + user_data_dir_.value()); + CHECK(result && window_.hwnd()); + } + + if (base::win::GetVersion() >= base::win::VERSION_WIN8) { + // Make sure no one is still waiting on Metro activation whether it + // succeeded (i.e., this is the Metro process) or failed. + base::win::ScopedHandle metro_activation_event( + ::OpenEvent(EVENT_MODIFY_STATE, FALSE, kMetroActivationEventName)); + if (metro_activation_event.IsValid()) + ::SetEvent(metro_activation_event.Get()); + } + } + } + + return window_.hwnd() != NULL; +} + +void ProcessSingleton::Cleanup() { +} + +void ProcessSingleton::OverrideShouldKillRemoteProcessCallbackForTesting( + const ShouldKillRemoteProcessCallback& display_dialog_callback) { + should_kill_remote_process_callback_ = display_dialog_callback; +} From 5886398f22bbdc1ed9e0bb3c4fca44bdaa82090b Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Tue, 20 Oct 2015 14:21:06 -0700 Subject: [PATCH 071/146] Port our changes over --- .../browser/chrome_process_finder_win.cc | 9 -- .../chrome/browser/process_singleton.h | 6 +- .../chrome/browser/process_singleton_posix.cc | 55 +++---- .../chrome/browser/process_singleton_win.cc | 145 ++---------------- 4 files changed, 31 insertions(+), 184 deletions(-) diff --git a/chromium_src/chrome/browser/chrome_process_finder_win.cc b/chromium_src/chrome/browser/chrome_process_finder_win.cc index 13c4a0eb5a..299877a65d 100644 --- a/chromium_src/chrome/browser/chrome_process_finder_win.cc +++ b/chromium_src/chrome/browser/chrome_process_finder_win.cc @@ -20,8 +20,6 @@ #include "base/win/scoped_handle.h" #include "base/win/win_util.h" #include "base/win/windows_version.h" -#include "chrome/common/chrome_constants.h" -#include "chrome/common/chrome_switches.h" namespace { @@ -45,13 +43,6 @@ NotifyChromeResult AttemptToNotifyRunningChrome(HWND remote_window, return NOTIFY_FAILED; base::CommandLine command_line(*base::CommandLine::ForCurrentProcess()); - command_line.AppendSwitchASCII( - switches::kOriginalProcessStartTime, - base::Int64ToString( - base::CurrentProcessInfo::CreationTime().ToInternalValue())); - - if (fast_start) - command_line.AppendSwitch(switches::kFastStart); // Send the command line to the remote chrome window. // Format is "START\0<<>>\0<<>>". diff --git a/chromium_src/chrome/browser/process_singleton.h b/chromium_src/chrome/browser/process_singleton.h index 9d99b02b9f..3265c9597a 100644 --- a/chromium_src/chrome/browser/process_singleton.h +++ b/chromium_src/chrome/browser/process_singleton.h @@ -5,8 +5,6 @@ #ifndef CHROME_BROWSER_PROCESS_SINGLETON_H_ #define CHROME_BROWSER_PROCESS_SINGLETON_H_ -#include "build/build_config.h" - #if defined(OS_WIN) #include #endif // defined(OS_WIN) @@ -128,8 +126,6 @@ class ProcessSingleton : public base::NonThreadSafe { NotificationCallback notification_callback_; // Handler for notifications. #if defined(OS_WIN) - bool EscapeVirtualization(const base::FilePath& user_data_dir); - HWND remote_window_; // The HWND_MESSAGE of another browser. base::win::MessageWindow window_; // The message-only window. bool is_virtualized_; // Stuck inside Microsoft Softricity VM environment. @@ -180,4 +176,4 @@ class ProcessSingleton : public base::NonThreadSafe { DISALLOW_COPY_AND_ASSIGN(ProcessSingleton); }; -#endif // CHROME_BROWSER_PROCESS_SINGLETON_H_ \ No newline at end of file +#endif // CHROME_BROWSER_PROCESS_SINGLETON_H_ diff --git a/chromium_src/chrome/browser/process_singleton_posix.cc b/chromium_src/chrome/browser/process_singleton_posix.cc index e855207ea7..5ab6eb6d8a 100644 --- a/chromium_src/chrome/browser/process_singleton_posix.cc +++ b/chromium_src/chrome/browser/process_singleton_posix.cc @@ -77,17 +77,10 @@ #include "base/threading/platform_thread.h" #include "base/time/time.h" #include "base/timer/timer.h" -#include "chrome/common/chrome_constants.h" -#include "chrome/grit/chromium_strings.h" -#include "chrome/grit/generated_resources.h" #include "content/public/browser/browser_thread.h" #include "net/base/net_util.h" #include "ui/base/l10n/l10n_util.h" -#if defined(OS_LINUX) -#include "chrome/browser/ui/process_singleton_dialog_linux.h" -#endif - #if defined(TOOLKIT_VIEWS) && defined(OS_LINUX) && !defined(OS_CHROMEOS) #include "ui/views/linux_ui/linux_ui.h" #endif @@ -112,6 +105,13 @@ const int kMaxACKMessageLength = arraysize(kShutdownToken) - 1; const char kLockDelimiter = '-'; +const base::FilePath::CharType kSingletonCookieFilename[] = + FILE_PATH_LITERAL("SingletonCookie"); + +const base::FilePath::CharType kSingletonLockFilename[] = FILE_PATH_LITERAL("SingletonLock"); +const base::FilePath::CharType kSingletonSocketFilename[] = + FILE_PATH_LITERAL("SingletonSocket"); + // Set the close-on-exec bit on a file descriptor. // Returns 0 on success, -1 on failure. int SetCloseOnExec(int fd) { @@ -304,33 +304,20 @@ bool ParseLockPath(const base::FilePath& path, bool DisplayProfileInUseError(const base::FilePath& lock_path, const std::string& hostname, int pid) { - base::string16 error = l10n_util::GetStringFUTF16( - IDS_PROFILE_IN_USE_POSIX, - base::IntToString16(pid), - base::ASCIIToUTF16(hostname)); - LOG(ERROR) << error; - - if (g_disable_prompt) - return false; - -#if defined(OS_LINUX) - base::string16 relaunch_button_text = l10n_util::GetStringUTF16( - IDS_PROFILE_IN_USE_LINUX_RELAUNCH); - return ShowProcessSingletonDialog(error, relaunch_button_text); -#elif defined(OS_MACOSX) - // On Mac, always usurp the lock. + // TODO: yolo return true; -#endif - - NOTREACHED(); - return false; } bool IsChromeProcess(pid_t pid) { base::FilePath other_chrome_path(base::GetProcessExecutablePath(pid)); + + auto command_line = base::CommandLine::ForCurrentProcess(); + base::FilePath exec_path(command_line->GetProgram()); + PathService::Get(base::FILE_EXE, &exec_path); + return (!other_chrome_path.empty() && other_chrome_path.BaseName() == - base::FilePath(chrome::kBrowserProcessExecutableName)); + exec_path.BaseName()); } // A helper class to hold onto a socket. @@ -371,7 +358,7 @@ bool ConnectSocket(ScopedSocket* socket, if (cookie.empty()) return false; base::FilePath remote_cookie = socket_target.DirName(). - Append(chrome::kSingletonCookieFilename); + Append(kSingletonCookieFilename); // Verify the cookie before connecting. if (!CheckCookie(remote_cookie, cookie)) return false; @@ -516,7 +503,7 @@ class ProcessSingleton::LinuxWatcher // reads. size_t bytes_read_; - base::OneShotTimer timer_; + base::OneShotTimer timer_; DISALLOW_COPY_AND_ASSIGN(SocketReader); }; @@ -731,9 +718,9 @@ ProcessSingleton::ProcessSingleton( : notification_callback_(notification_callback), current_pid_(base::GetCurrentProcId()), watcher_(new LinuxWatcher(this)) { - socket_path_ = user_data_dir.Append(chrome::kSingletonSocketFilename); - lock_path_ = user_data_dir.Append(chrome::kSingletonLockFilename); - cookie_path_ = user_data_dir.Append(chrome::kSingletonCookieFilename); + socket_path_ = user_data_dir.Append(kSingletonSocketFilename); + lock_path_ = user_data_dir.Append(kSingletonLockFilename); + cookie_path_ = user_data_dir.Append(kSingletonCookieFilename); kill_callback_ = base::Bind(&ProcessSingleton::KillProcess, base::Unretained(this)); @@ -973,10 +960,10 @@ bool ProcessSingleton::Create() { // Setup the socket symlink and the two cookies. base::FilePath socket_target_path = - socket_dir_.path().Append(chrome::kSingletonSocketFilename); + socket_dir_.path().Append(kSingletonSocketFilename); base::FilePath cookie(GenerateCookie()); base::FilePath remote_cookie_path = - socket_dir_.path().Append(chrome::kSingletonCookieFilename); + socket_dir_.path().Append(kSingletonCookieFilename); UnlinkPath(socket_path_); UnlinkPath(cookie_path_); if (!SymlinkPath(socket_target_path, socket_path_) || diff --git a/chromium_src/chrome/browser/process_singleton_win.cc b/chromium_src/chrome/browser/process_singleton_win.cc index 623aa7f42f..bedc861c40 100644 --- a/chromium_src/chrome/browser/process_singleton_win.cc +++ b/chromium_src/chrome/browser/process_singleton_win.cc @@ -20,18 +20,7 @@ #include "base/win/registry.h" #include "base/win/scoped_handle.h" #include "base/win/windows_version.h" -#include "chrome/browser/browser_process.h" -#include "chrome/browser/browser_process_platform_part.h" #include "chrome/browser/chrome_process_finder_win.h" -#include "chrome/browser/metro_utils/metro_chrome_win.h" -#include "chrome/browser/shell_integration.h" -#include "chrome/browser/ui/simple_message_box.h" -#include "chrome/common/chrome_constants.h" -#include "chrome/common/chrome_paths.h" -#include "chrome/common/chrome_paths_internal.h" -#include "chrome/common/chrome_switches.h" -#include "chrome/grit/chromium_strings.h" -#include "chrome/installer/util/wmi.h" #include "content/public/common/result_codes.h" #include "net/base/escape.h" #include "ui/base/l10n/l10n_util.h" @@ -41,8 +30,6 @@ namespace { const char kLockfile[] = "lockfile"; -const int kMetroChromeActivationTimeoutMs = 3000; - // A helper class that acquires the given |mutex| while the AutoLockMutex is in // scope. class AutoLockMutex { @@ -174,66 +161,14 @@ bool ProcessLaunchNotification( return true; } -// Returns true if Chrome needs to be relaunched into Windows 8 immersive mode. -// Following conditions apply:- -// 1. Windows 8 or greater. -// 2. Not in Windows 8 immersive mode. -// 3. Chrome is default browser. -// 4. Process integrity level is not high. -// 5. The profile data directory is the default directory. -// 6. Last used mode was immersive/machine is a tablet. -// TODO(ananta) -// Move this function to a common place as the Windows 8 delegate_execute -// handler can possibly use this. -bool ShouldLaunchInWindows8ImmersiveMode(const base::FilePath& user_data_dir) { - // Returning false from this function doesn't mean we don't launch immersive - // mode in Aura. This function is specifically called in case when we need - // to relaunch desktop launched chrome into immersive mode through 'relaunch' - // menu. In case of Aura, we will use delegate_execute to do the relaunch. +bool TerminateAppWithError() { + // TODO: This is called when the secondary process can't ping the primary + // process. Need to find out what to do here. return false; } -bool DisplayShouldKillMessageBox() { - return chrome::ShowMessageBox( - NULL, l10n_util::GetStringUTF16(IDS_PRODUCT_NAME), - l10n_util::GetStringUTF16(IDS_BROWSER_HUNGBROWSER_MESSAGE), - chrome::MESSAGE_BOX_TYPE_QUESTION) != - chrome::MESSAGE_BOX_RESULT_NO; -} - } // namespace -// Microsoft's Softricity virtualization breaks the sandbox processes. -// So, if we detect the Softricity DLL we use WMI Win32_Process.Create to -// break out of the virtualization environment. -// http://code.google.com/p/chromium/issues/detail?id=43650 -bool ProcessSingleton::EscapeVirtualization( - const base::FilePath& user_data_dir) { - if (::GetModuleHandle(L"sftldr_wow64.dll") || - ::GetModuleHandle(L"sftldr.dll")) { - int process_id; - if (!installer::WMIProcess::Launch(::GetCommandLineW(), &process_id)) - return false; - is_virtualized_ = true; - // The new window was spawned from WMI, and won't be in the foreground. - // So, first we sleep while the new chrome.exe instance starts (because - // WaitForInputIdle doesn't work here). Then we poll for up to two more - // seconds and make the window foreground if we find it (or we give up). - HWND hwnd = 0; - ::Sleep(90); - for (int tries = 200; tries; --tries) { - hwnd = chrome::FindRunningChromeWindow(user_data_dir); - if (hwnd) { - ::SetForegroundWindow(hwnd); - break; - } - ::Sleep(10); - } - return true; - } - return false; -} - ProcessSingleton::ProcessSingleton( const base::FilePath& user_data_dir, const NotificationCallback& notification_callback) @@ -242,7 +177,7 @@ ProcessSingleton::ProcessSingleton( lock_file_(INVALID_HANDLE_VALUE), user_data_dir_(user_data_dir), should_kill_remote_process_callback_( - base::Bind(&DisplayShouldKillMessageBox)) { + base::Bind(&TerminateAppWithError)) { } ProcessSingleton::~ProcessSingleton() { @@ -305,8 +240,9 @@ ProcessSingleton::NotifyOtherProcessOrCreate() { if (result == PROCESS_NONE) result = PROFILE_IN_USE; } else { - g_browser_process->platform_part()->PlatformSpecificCommandLineProcessing( - *base::CommandLine::ForCurrentProcess()); + // TODO: Figure out how to implement this + //g_browser_process->platform_part()->PlatformSpecificCommandLineProcessing( + // *base::CommandLine::ForCurrentProcess()); } return result; } @@ -315,12 +251,10 @@ ProcessSingleton::NotifyOtherProcessOrCreate() { // isn't one, create a message window with its title set to the profile // directory path. bool ProcessSingleton::Create() { - static const wchar_t kMutexName[] = L"Local\\ChromeProcessSingletonStartup!"; - static const wchar_t kMetroActivationEventName[] = - L"Local\\ChromeProcessSingletonStartupMetroActivation!"; + static const wchar_t kMutexName[] = L"Local\\AtomProcessSingletonStartup!"; remote_window_ = chrome::FindRunningChromeWindow(user_data_dir_); - if (!remote_window_ && !EscapeVirtualization(user_data_dir_)) { + if (!remote_window_) { // Make sure we will be the one and only process creating the window. // We use a named Mutex since we are protecting against multi-process // access. As documented, it's clearer to NOT request ownership on creation @@ -339,58 +273,6 @@ bool ProcessSingleton::Create() { // between the time where we looked for it above and the time the mutex // was given to us. remote_window_ = chrome::FindRunningChromeWindow(user_data_dir_); - - - // In Win8+, a new Chrome process launched in Desktop mode may need to be - // transmuted into Metro Chrome (see ShouldLaunchInWindows8ImmersiveMode for - // heuristics). To accomplish this, the current Chrome activates Metro - // Chrome, releases the startup mutex, and waits for metro Chrome to take - // the singleton. From that point onward, the command line for this Chrome - // process will be sent to Metro Chrome by the usual channels. - if (!remote_window_ && base::win::GetVersion() >= base::win::VERSION_WIN8 && - !base::win::IsMetroProcess()) { - // |metro_activation_event| is created right before activating a Metro - // Chrome (note that there can only be one Metro Chrome process; by OS - // design); all following Desktop processes will then wait for this event - // to be signaled by Metro Chrome which will do so as soon as it grabs - // this singleton (should any of the waiting processes timeout waiting for - // the signal they will try to grab the singleton for themselves which - // will result in a forced Desktop Chrome launch in the worst case). - base::win::ScopedHandle metro_activation_event( - ::OpenEvent(SYNCHRONIZE, FALSE, kMetroActivationEventName)); - if (!metro_activation_event.IsValid() && - ShouldLaunchInWindows8ImmersiveMode(user_data_dir_)) { - // No Metro activation is under way, but the desire is to launch in - // Metro mode: activate and rendez-vous with the activated process. - metro_activation_event.Set( - ::CreateEvent(NULL, TRUE, FALSE, kMetroActivationEventName)); - if (!chrome::ActivateMetroChrome()) { - // Failed to launch immersive Chrome, default to launching on Desktop. - LOG(ERROR) << "Failed to launch immersive chrome"; - metro_activation_event.Close(); - } - } - - if (metro_activation_event.IsValid()) { - // Release |only_me| (to let Metro Chrome grab this singleton) and wait - // until the event is signaled (i.e. Metro Chrome was successfully - // activated). Ignore timeout waiting for |metro_activation_event|. - { - AutoUnlockMutex auto_unlock_only_me(only_me.Get()); - - DWORD result = ::WaitForSingleObject(metro_activation_event.Get(), - kMetroChromeActivationTimeoutMs); - DPCHECK(result == WAIT_OBJECT_0 || result == WAIT_TIMEOUT) - << "Result = " << result; - } - - // Check if this singleton was successfully grabbed by another process - // (hopefully Metro Chrome). Failing to do so, this process will grab - // the singleton and launch in Desktop mode. - remote_window_ = chrome::FindRunningChromeWindow(user_data_dir_); - } - } - if (!remote_window_) { // We have to make sure there is no Chrome instance running on another // machine that uses the same profile. @@ -417,15 +299,6 @@ bool ProcessSingleton::Create() { user_data_dir_.value()); CHECK(result && window_.hwnd()); } - - if (base::win::GetVersion() >= base::win::VERSION_WIN8) { - // Make sure no one is still waiting on Metro activation whether it - // succeeded (i.e., this is the Metro process) or failed. - base::win::ScopedHandle metro_activation_event( - ::OpenEvent(EVENT_MODIFY_STATE, FALSE, kMetroActivationEventName)); - if (metro_activation_event.IsValid()) - ::SetEvent(metro_activation_event.Get()); - } } } From 4bc54ac5e376a6022b37f16f79460055893bdc2a Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Tue, 20 Oct 2015 14:33:34 -0700 Subject: [PATCH 072/146] Fix up filenames to include Chrome source --- filenames.gypi | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/filenames.gypi b/filenames.gypi index 54fedb9f41..3f3d1019c2 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -128,10 +128,9 @@ 'atom/browser/atom_browser_main_parts.h', 'atom/browser/atom_browser_main_parts_mac.mm', 'atom/browser/atom_browser_main_parts_posix.cc', - 'atom/browser/atom_process_finder_win.cc', - 'atom/browser/atom_process_singleton.cc', 'atom/browser/atom_javascript_dialog_manager.cc', 'atom/browser/atom_javascript_dialog_manager.h', + 'atom/browser/atom_process_singleton.cc', 'atom/browser/atom_quota_permission_context.cc', 'atom/browser/atom_quota_permission_context.h', 'atom/browser/atom_resource_dispatcher_host_delegate.cc', @@ -184,9 +183,6 @@ 'atom/browser/net/url_request_fetch_job.h', 'atom/browser/node_debugger.cc', 'atom/browser/node_debugger.h', - 'atom/browser/process_singleton_posix.cc', - 'atom/browser/process_singleton_startup_lock.cc', - 'atom/browser/process_singleton_win.cc', 'atom/browser/ui/accelerator_util.cc', 'atom/browser/ui/accelerator_util.h', 'atom/browser/ui/accelerator_util_mac.mm', @@ -351,6 +347,7 @@ 'atom/utility/atom_content_utility_client.h', 'chromium_src/chrome/browser/browser_process.cc', 'chromium_src/chrome/browser/browser_process.h', + 'chromium_src/chrome/browser/chrome_process_finder_win.cc', 'chromium_src/chrome/browser/chrome_notification_types.h', 'chromium_src/chrome/browser/extensions/global_shortcut_listener.cc', 'chromium_src/chrome/browser/extensions/global_shortcut_listener.h', @@ -379,6 +376,9 @@ 'chromium_src/chrome/browser/printing/printing_message_filter.h', 'chromium_src/chrome/browser/printing/print_preview_message_handler.cc', 'chromium_src/chrome/browser/printing/print_preview_message_handler.h', + 'chromium_src/chrome/browser/process_singleton_posix.cc', + 'chromium_src/chrome/browser/process_singleton_startup_lock.cc', + 'chromium_src/chrome/browser/process_singleton_win.cc', 'chromium_src/chrome/browser/renderer_host/pepper/chrome_browser_pepper_host_factory.cc', 'chromium_src/chrome/browser/renderer_host/pepper/chrome_browser_pepper_host_factory.h', 'chromium_src/chrome/browser/renderer_host/pepper/pepper_broker_message_filter.cc', From f759471e015f89acca76ef163a6599b0312a0a8d Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Tue, 20 Oct 2015 14:36:01 -0700 Subject: [PATCH 073/146] Update to use Chromium sources --- atom/browser/api/atom_api_app.h | 2 +- atom/browser/atom_process_finder_win.cc | 88 -- atom/browser/atom_process_finder_win.h | 39 - atom/browser/atom_process_singleton.h | 4 +- atom/browser/process_singleton.h | 181 --- atom/browser/process_singleton_posix.cc | 1049 ----------------- .../browser/process_singleton_startup_lock.cc | 53 - atom/browser/process_singleton_startup_lock.h | 57 - atom/browser/process_singleton_win.cc | 317 ----- 9 files changed, 3 insertions(+), 1787 deletions(-) delete mode 100644 atom/browser/atom_process_finder_win.cc delete mode 100644 atom/browser/atom_process_finder_win.h delete mode 100644 atom/browser/process_singleton.h delete mode 100644 atom/browser/process_singleton_posix.cc delete mode 100644 atom/browser/process_singleton_startup_lock.cc delete mode 100644 atom/browser/process_singleton_startup_lock.h delete mode 100644 atom/browser/process_singleton_win.cc diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index b8285553b4..3dafdbc284 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -9,8 +9,8 @@ #include "atom/browser/api/event_emitter.h" #include "atom/browser/browser_observer.h" -#include "atom/browser/process_singleton.h" #include "atom/common/native_mate_converters/callback.h" +#include "chrome/browser/process_singleton.h" #include "content/public/browser/gpu_data_manager_observer.h" #include "native_mate/handle.h" diff --git a/atom/browser/atom_process_finder_win.cc b/atom/browser/atom_process_finder_win.cc deleted file mode 100644 index a658b7163b..0000000000 --- a/atom/browser/atom_process_finder_win.cc +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "atom/browser/atom_process_finder_win.h" - -#include -#include - -#include "base/command_line.h" -#include "base/files/file_path.h" -#include "base/files/file_util.h" -#include "base/logging.h" -#include "base/process/process.h" -#include "base/process/process_info.h" -#include "base/strings/string_number_conversions.h" -#include "base/strings/stringprintf.h" -#include "base/strings/utf_string_conversions.h" -#include "base/win/message_window.h" -#include "base/win/scoped_handle.h" -#include "base/win/win_util.h" -#include "base/win/windows_version.h" - - -namespace { - -int timeout_in_milliseconds = 20 * 1000; - -} // namespace - -namespace atom { - -HWND FindRunningAtomWindow(const base::FilePath& user_data_dir) { - return base::win::MessageWindow::FindWindow(user_data_dir.value()); -} - -NotifyChromeResult AttemptToNotifyRunningAtom(HWND remote_window, - bool fast_start) { - DCHECK(remote_window); - DWORD process_id = 0; - DWORD thread_id = GetWindowThreadProcessId(remote_window, &process_id); - if (!thread_id || !process_id) - return NOTIFY_FAILED; - - base::CommandLine command_line(*base::CommandLine::ForCurrentProcess()); - - // Send the command line to the remote chrome window. - // Format is "START\0<<>>\0<<>>". - std::wstring to_send(L"START\0", 6); // want the NULL in the string. - base::FilePath cur_dir; - if (!base::GetCurrentDirectory(&cur_dir)) - return NOTIFY_FAILED; - to_send.append(cur_dir.value()); - to_send.append(L"\0", 1); // Null separator. - to_send.append(command_line.GetCommandLineString()); - to_send.append(L"\0", 1); // Null separator. - - // Allow the current running browser window to make itself the foreground - // window (otherwise it will just flash in the taskbar). - ::AllowSetForegroundWindow(process_id); - - COPYDATASTRUCT cds; - cds.dwData = 0; - cds.cbData = static_cast((to_send.length() + 1) * sizeof(wchar_t)); - cds.lpData = const_cast(to_send.c_str()); - DWORD_PTR result = 0; - if (::SendMessageTimeout(remote_window, WM_COPYDATA, NULL, - reinterpret_cast(&cds), SMTO_ABORTIFHUNG, - timeout_in_milliseconds, &result)) { - return result ? NOTIFY_SUCCESS : NOTIFY_FAILED; - } - - // It is possible that the process owning this window may have died by now. - if (!::IsWindow(remote_window)) - return NOTIFY_FAILED; - - // If the window couldn't be notified but still exists, assume it is hung. - return NOTIFY_WINDOW_HUNG; -} - -base::TimeDelta SetNotificationTimeoutForTesting(base::TimeDelta new_timeout) { - base::TimeDelta old_timeout = - base::TimeDelta::FromMilliseconds(timeout_in_milliseconds); - timeout_in_milliseconds = new_timeout.InMilliseconds(); - return old_timeout; -} - -} // namespace chrome diff --git a/atom/browser/atom_process_finder_win.h b/atom/browser/atom_process_finder_win.h deleted file mode 100644 index f8aa72fdd9..0000000000 --- a/atom/browser/atom_process_finder_win.h +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef ATOM_BROWSER_ATOM_PROCESS_FINDER_WIN_H_ -#define ATOM_BROWSER_ATOM_PROCESS_FINDER_WIN_H_ - -#include - -#include "base/time/time.h" - -namespace base { -class FilePath; -} - -namespace atom { - -enum NotifyChromeResult { - NOTIFY_SUCCESS, - NOTIFY_FAILED, - NOTIFY_WINDOW_HUNG, -}; - -// Finds an already running Chrome window if it exists. -HWND FindRunningAtomWindow(const base::FilePath& user_data_dir); - -// Attempts to send the current command line to an already running instance of -// Chrome via a WM_COPYDATA message. -// Returns true if a running Chrome is found and successfully notified. -// |fast_start| is true when this is being called on the window fast start path. -NotifyChromeResult AttemptToNotifyRunningAtom(HWND remote_window, - bool fast_start); - -// Changes the notification timeout to |new_timeout|, returns the old timeout. -base::TimeDelta SetNotificationTimeoutForTesting(base::TimeDelta new_timeout); - -} // namespace chrome - -#endif // ATOM_BROWSER_ATOM_PROCESS_FINDER_WIN_H_ diff --git a/atom/browser/atom_process_singleton.h b/atom/browser/atom_process_singleton.h index de4e2463a0..5654862143 100644 --- a/atom/browser/atom_process_singleton.h +++ b/atom/browser/atom_process_singleton.h @@ -7,8 +7,8 @@ #include "base/basictypes.h" #include "base/files/file_path.h" -#include "atom/browser/process_singleton.h" -#include "atom/browser/process_singleton_startup_lock.h" +#include "chrome/browser/process_singleton.h" +#include "chrome/browser/process_singleton_startup_lock.h" // Composes a basic ProcessSingleton with ProcessSingletonStartupLock class AtomProcessSingleton { diff --git a/atom/browser/process_singleton.h b/atom/browser/process_singleton.h deleted file mode 100644 index 451414613e..0000000000 --- a/atom/browser/process_singleton.h +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef CHROME_BROWSER_PROCESS_SINGLETON_H_ -#define CHROME_BROWSER_PROCESS_SINGLETON_H_ - -#if defined(OS_WIN) -#include -#endif // defined(OS_WIN) - -#include -#include - -#include "base/basictypes.h" -#include "base/callback.h" -#include "base/command_line.h" -#include "base/files/file_path.h" -#include "base/logging.h" -#include "base/memory/ref_counted.h" -#include "base/process/process.h" -#include "base/threading/non_thread_safe.h" -#include "ui/gfx/native_widget_types.h" - -#if defined(OS_POSIX) && !defined(OS_ANDROID) -#include "base/files/scoped_temp_dir.h" -#endif - -#if defined(OS_WIN) -#include "base/win/message_window.h" -#endif // defined(OS_WIN) - -namespace base { -class CommandLine; -} - -// ProcessSingleton ---------------------------------------------------------- -// -// This class allows different browser processes to communicate with -// each other. It is named according to the user data directory, so -// we can be sure that no more than one copy of the application can be -// running at once with a given data directory. -// -// Implementation notes: -// - the Windows implementation uses an invisible global message window; -// - the Linux implementation uses a Unix domain socket in the user data dir. - -class ProcessSingleton : public base::NonThreadSafe { - public: - enum NotifyResult { - PROCESS_NONE, - PROCESS_NOTIFIED, - PROFILE_IN_USE, - LOCK_ERROR, - }; - - // Implement this callback to handle notifications from other processes. The - // callback will receive the command line and directory with which the other - // Chrome process was launched. Return true if the command line will be - // handled within the current browser instance or false if the remote process - // should handle it (i.e., because the current process is shutting down). - using NotificationCallback = - base::Callback; - - ProcessSingleton(const base::FilePath& user_data_dir, - const NotificationCallback& notification_callback); - ~ProcessSingleton(); - - // Notify another process, if available. Otherwise sets ourselves as the - // singleton instance. Returns PROCESS_NONE if we became the singleton - // instance. Callers are guaranteed to either have notified an existing - // process or have grabbed the singleton (unless the profile is locked by an - // unreachable process). - // TODO(brettw): Make the implementation of this method non-platform-specific - // by making Linux re-use the Windows implementation. - NotifyResult NotifyOtherProcessOrCreate(); - - // Sets ourself up as the singleton instance. Returns true on success. If - // false is returned, we are not the singleton instance and the caller must - // exit. - // NOTE: Most callers should generally prefer NotifyOtherProcessOrCreate() to - // this method, only callers for whom failure is preferred to notifying - // another process should call this directly. - bool Create(); - - // Clear any lock state during shutdown. - void Cleanup(); - -#if defined(OS_POSIX) && !defined(OS_ANDROID) - static void DisablePromptForTesting(); -#endif -#if defined(OS_WIN) - // Called to query whether to kill a hung browser process that has visible - // windows. Return true to allow killing the hung process. - using ShouldKillRemoteProcessCallback = base::Callback; - void OverrideShouldKillRemoteProcessCallbackForTesting( - const ShouldKillRemoteProcessCallback& display_dialog_callback); -#endif - - protected: - // Notify another process, if available. - // Returns true if another process was found and notified, false if we should - // continue with the current process. - // On Windows, Create() has to be called before this. - NotifyResult NotifyOtherProcess(); - -#if defined(OS_POSIX) && !defined(OS_ANDROID) - // Exposed for testing. We use a timeout on Linux, and in tests we want - // this timeout to be short. - NotifyResult NotifyOtherProcessWithTimeout( - const base::CommandLine& command_line, - int retry_attempts, - const base::TimeDelta& timeout, - bool kill_unresponsive); - NotifyResult NotifyOtherProcessWithTimeoutOrCreate( - const base::CommandLine& command_line, - int retry_attempts, - const base::TimeDelta& timeout); - void OverrideCurrentPidForTesting(base::ProcessId pid); - void OverrideKillCallbackForTesting( - const base::Callback& callback); -#endif - - private: - NotificationCallback notification_callback_; // Handler for notifications. - -#if defined(OS_WIN) - bool EscapeVirtualization(const base::FilePath& user_data_dir); - - HWND remote_window_; // The HWND_MESSAGE of another browser. - base::win::MessageWindow window_; // The message-only window. - bool is_virtualized_; // Stuck inside Microsoft Softricity VM environment. - HANDLE lock_file_; - base::FilePath user_data_dir_; - ShouldKillRemoteProcessCallback should_kill_remote_process_callback_; -#elif defined(OS_POSIX) && !defined(OS_ANDROID) - // Return true if the given pid is one of our child processes. - // Assumes that the current pid is the root of all pids of the current - // instance. - bool IsSameChromeInstance(pid_t pid); - - // Extract the process's pid from a symbol link path and if it is on - // the same host, kill the process, unlink the lock file and return true. - // If the process is part of the same chrome instance, unlink the lock file - // and return true without killing it. - // If the process is on a different host, return false. - bool KillProcessByLockPath(); - - // Default function to kill a process, overridable by tests. - void KillProcess(int pid); - - // Allow overriding for tests. - base::ProcessId current_pid_; - - // Function to call when the other process is hung and needs to be killed. - // Allows overriding for tests. - base::Callback kill_callback_; - - // Path in file system to the socket. - base::FilePath socket_path_; - - // Path in file system to the lock. - base::FilePath lock_path_; - - // Path in file system to the cookie file. - base::FilePath cookie_path_; - - // Temporary directory to hold the socket. - base::ScopedTempDir socket_dir_; - - // Helper class for linux specific messages. LinuxWatcher is ref counted - // because it posts messages between threads. - class LinuxWatcher; - scoped_refptr watcher_; -#endif - - DISALLOW_COPY_AND_ASSIGN(ProcessSingleton); -}; - -#endif // CHROME_BROWSER_PROCESS_SINGLETON_H_ diff --git a/atom/browser/process_singleton_posix.cc b/atom/browser/process_singleton_posix.cc deleted file mode 100644 index 5810af2a2a..0000000000 --- a/atom/browser/process_singleton_posix.cc +++ /dev/null @@ -1,1049 +0,0 @@ -// Copyright 2014 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// On Linux, when the user tries to launch a second copy of chrome, we check -// for a socket in the user's profile directory. If the socket file is open we -// send a message to the first chrome browser process with the current -// directory and second process command line flags. The second process then -// exits. -// -// Because many networked filesystem implementations do not support unix domain -// sockets, we create the socket in a temporary directory and create a symlink -// in the profile. This temporary directory is no longer bound to the profile, -// and may disappear across a reboot or login to a separate session. To bind -// them, we store a unique cookie in the profile directory, which must also be -// present in the remote directory to connect. The cookie is checked both before -// and after the connection. /tmp is sticky, and different Chrome sessions use -// different cookies. Thus, a matching cookie before and after means the -// connection was to a directory with a valid cookie. -// -// We also have a lock file, which is a symlink to a non-existent destination. -// The destination is a string containing the hostname and process id of -// chrome's browser process, eg. "SingletonLock -> example.com-9156". When the -// first copy of chrome exits it will delete the lock file on shutdown, so that -// a different instance on a different host may then use the profile directory. -// -// If writing to the socket fails, the hostname in the lock is checked to see if -// another instance is running a different host using a shared filesystem (nfs, -// etc.) If the hostname differs an error is displayed and the second process -// exits. Otherwise the first process (if any) is killed and the second process -// starts as normal. -// -// When the second process sends the current directory and command line flags to -// the first process, it waits for an ACK message back from the first process -// for a certain time. If there is no ACK message back in time, then the first -// process will be considered as hung for some reason. The second process then -// retrieves the process id from the symbol link and kills it by sending -// SIGKILL. Then the second process starts as normal. - -#include "atom/browser/process_singleton.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "base/base_paths.h" -#include "base/basictypes.h" -#include "base/bind.h" -#include "base/command_line.h" -#include "base/files/file_path.h" -#include "base/files/file_util.h" -#include "base/location.h" -#include "base/logging.h" -#include "base/message_loop/message_loop.h" -#include "base/path_service.h" -#include "base/posix/eintr_wrapper.h" -#include "base/posix/safe_strerror.h" -#include "base/rand_util.h" -#include "base/sequenced_task_runner_helpers.h" -#include "base/single_thread_task_runner.h" -#include "base/stl_util.h" -#include "base/strings/string_number_conversions.h" -#include "base/strings/string_split.h" -#include "base/strings/string_util.h" -#include "base/strings/stringprintf.h" -#include "base/strings/sys_string_conversions.h" -#include "base/strings/utf_string_conversions.h" -#include "base/threading/platform_thread.h" -#include "base/time/time.h" -#include "base/timer/timer.h" -#include "content/public/browser/browser_thread.h" -#include "net/base/net_util.h" -#include "ui/base/l10n/l10n_util.h" - -#if defined(TOOLKIT_VIEWS) && defined(OS_LINUX) && !defined(OS_CHROMEOS) -#include "ui/views/linux_ui/linux_ui.h" -#endif - -using content::BrowserThread; - -namespace { - -// Timeout for the current browser process to respond. 20 seconds should be -// enough. -const int kTimeoutInSeconds = 20; -// Number of retries to notify the browser. 20 retries over 20 seconds = 1 try -// per second. -const int kRetryAttempts = 20; -static bool g_disable_prompt; -const char kStartToken[] = "START"; -const char kACKToken[] = "ACK"; -const char kShutdownToken[] = "SHUTDOWN"; -const char kTokenDelimiter = '\0'; -const int kMaxMessageLength = 32 * 1024; -const int kMaxACKMessageLength = arraysize(kShutdownToken) - 1; - -const char kLockDelimiter = '-'; - -const base::FilePath::CharType kSingletonCookieFilename[] = - FILE_PATH_LITERAL("SingletonCookie"); - -const base::FilePath::CharType kSingletonLockFilename[] = FILE_PATH_LITERAL("SingletonLock"); -const base::FilePath::CharType kSingletonSocketFilename[] = - FILE_PATH_LITERAL("SingletonSocket"); - -// Set the close-on-exec bit on a file descriptor. -// Returns 0 on success, -1 on failure. -int SetCloseOnExec(int fd) { - int flags = fcntl(fd, F_GETFD, 0); - if (-1 == flags) - return flags; - if (flags & FD_CLOEXEC) - return 0; - return fcntl(fd, F_SETFD, flags | FD_CLOEXEC); -} - -// Close a socket and check return value. -void CloseSocket(int fd) { - int rv = IGNORE_EINTR(close(fd)); - DCHECK_EQ(0, rv) << "Error closing socket: " << base::safe_strerror(errno); -} - -// Write a message to a socket fd. -bool WriteToSocket(int fd, const char *message, size_t length) { - DCHECK(message); - DCHECK(length); - size_t bytes_written = 0; - do { - ssize_t rv = HANDLE_EINTR( - write(fd, message + bytes_written, length - bytes_written)); - if (rv < 0) { - if (errno == EAGAIN || errno == EWOULDBLOCK) { - // The socket shouldn't block, we're sending so little data. Just give - // up here, since NotifyOtherProcess() doesn't have an asynchronous api. - LOG(ERROR) << "ProcessSingleton would block on write(), so it gave up."; - return false; - } - PLOG(ERROR) << "write() failed"; - return false; - } - bytes_written += rv; - } while (bytes_written < length); - - return true; -} - -struct timeval TimeDeltaToTimeVal(const base::TimeDelta& delta) { - struct timeval result; - result.tv_sec = delta.InSeconds(); - result.tv_usec = delta.InMicroseconds() % base::Time::kMicrosecondsPerSecond; - return result; -} - -// Wait a socket for read for a certain timeout. -// Returns -1 if error occurred, 0 if timeout reached, > 0 if the socket is -// ready for read. -int WaitSocketForRead(int fd, const base::TimeDelta& timeout) { - fd_set read_fds; - struct timeval tv = TimeDeltaToTimeVal(timeout); - - FD_ZERO(&read_fds); - FD_SET(fd, &read_fds); - - return HANDLE_EINTR(select(fd + 1, &read_fds, NULL, NULL, &tv)); -} - -// Read a message from a socket fd, with an optional timeout. -// If |timeout| <= 0 then read immediately. -// Return number of bytes actually read, or -1 on error. -ssize_t ReadFromSocket(int fd, - char* buf, - size_t bufsize, - const base::TimeDelta& timeout) { - if (timeout > base::TimeDelta()) { - int rv = WaitSocketForRead(fd, timeout); - if (rv <= 0) - return rv; - } - - size_t bytes_read = 0; - do { - ssize_t rv = HANDLE_EINTR(read(fd, buf + bytes_read, bufsize - bytes_read)); - if (rv < 0) { - if (errno != EAGAIN && errno != EWOULDBLOCK) { - PLOG(ERROR) << "read() failed"; - return rv; - } else { - // It would block, so we just return what has been read. - return bytes_read; - } - } else if (!rv) { - // No more data to read. - return bytes_read; - } else { - bytes_read += rv; - } - } while (bytes_read < bufsize); - - return bytes_read; -} - -// Set up a sockaddr appropriate for messaging. -void SetupSockAddr(const std::string& path, struct sockaddr_un* addr) { - addr->sun_family = AF_UNIX; - CHECK(path.length() < arraysize(addr->sun_path)) - << "Socket path too long: " << path; - base::strlcpy(addr->sun_path, path.c_str(), arraysize(addr->sun_path)); -} - -// Set up a socket appropriate for messaging. -int SetupSocketOnly() { - int sock = socket(PF_UNIX, SOCK_STREAM, 0); - PCHECK(sock >= 0) << "socket() failed"; - - int rv = net::SetNonBlocking(sock); - DCHECK_EQ(0, rv) << "Failed to make non-blocking socket."; - rv = SetCloseOnExec(sock); - DCHECK_EQ(0, rv) << "Failed to set CLOEXEC on socket."; - - return sock; -} - -// Set up a socket and sockaddr appropriate for messaging. -void SetupSocket(const std::string& path, int* sock, struct sockaddr_un* addr) { - *sock = SetupSocketOnly(); - SetupSockAddr(path, addr); -} - -// Read a symbolic link, return empty string if given path is not a symbol link. -base::FilePath ReadLink(const base::FilePath& path) { - base::FilePath target; - if (!base::ReadSymbolicLink(path, &target)) { - // The only errno that should occur is ENOENT. - if (errno != 0 && errno != ENOENT) - PLOG(ERROR) << "readlink(" << path.value() << ") failed"; - } - return target; -} - -// Unlink a path. Return true on success. -bool UnlinkPath(const base::FilePath& path) { - int rv = unlink(path.value().c_str()); - if (rv < 0 && errno != ENOENT) - PLOG(ERROR) << "Failed to unlink " << path.value(); - - return rv == 0; -} - -// Create a symlink. Returns true on success. -bool SymlinkPath(const base::FilePath& target, const base::FilePath& path) { - if (!base::CreateSymbolicLink(target, path)) { - // Double check the value in case symlink suceeded but we got an incorrect - // failure due to NFS packet loss & retry. - int saved_errno = errno; - if (ReadLink(path) != target) { - // If we failed to create the lock, most likely another instance won the - // startup race. - errno = saved_errno; - PLOG(ERROR) << "Failed to create " << path.value(); - return false; - } - } - return true; -} - -// Extract the hostname and pid from the lock symlink. -// Returns true if the lock existed. -bool ParseLockPath(const base::FilePath& path, - std::string* hostname, - int* pid) { - std::string real_path = ReadLink(path).value(); - if (real_path.empty()) - return false; - - std::string::size_type pos = real_path.rfind(kLockDelimiter); - - // If the path is not a symbolic link, or doesn't contain what we expect, - // bail. - if (pos == std::string::npos) { - *hostname = ""; - *pid = -1; - return true; - } - - *hostname = real_path.substr(0, pos); - - const std::string& pid_str = real_path.substr(pos + 1); - if (!base::StringToInt(pid_str, pid)) - *pid = -1; - - return true; -} - -// Returns true if the user opted to unlock the profile. -bool DisplayProfileInUseError(const base::FilePath& lock_path, - const std::string& hostname, - int pid) { - // TODO: yolo - return true; -} - -bool IsChromeProcess(pid_t pid) { - base::FilePath other_chrome_path(base::GetProcessExecutablePath(pid)); - - auto command_line = base::CommandLine::ForCurrentProcess(); - base::FilePath exec_path(command_line->GetProgram()); - PathService::Get(base::FILE_EXE, &exec_path); - - return (!other_chrome_path.empty() && - other_chrome_path.BaseName() == - exec_path.BaseName()); -} - -// A helper class to hold onto a socket. -class ScopedSocket { - public: - ScopedSocket() : fd_(-1) { Reset(); } - ~ScopedSocket() { Close(); } - int fd() { return fd_; } - void Reset() { - Close(); - fd_ = SetupSocketOnly(); - } - void Close() { - if (fd_ >= 0) - CloseSocket(fd_); - fd_ = -1; - } - private: - int fd_; -}; - -// Returns a random string for uniquifying profile connections. -std::string GenerateCookie() { - return base::Uint64ToString(base::RandUint64()); -} - -bool CheckCookie(const base::FilePath& path, const base::FilePath& cookie) { - return (cookie == ReadLink(path)); -} - -bool ConnectSocket(ScopedSocket* socket, - const base::FilePath& socket_path, - const base::FilePath& cookie_path) { - base::FilePath socket_target; - if (base::ReadSymbolicLink(socket_path, &socket_target)) { - // It's a symlink. Read the cookie. - base::FilePath cookie = ReadLink(cookie_path); - if (cookie.empty()) - return false; - base::FilePath remote_cookie = socket_target.DirName(). - Append(kSingletonCookieFilename); - // Verify the cookie before connecting. - if (!CheckCookie(remote_cookie, cookie)) - return false; - // Now we know the directory was (at that point) created by the profile - // owner. Try to connect. - sockaddr_un addr; - SetupSockAddr(socket_target.value(), &addr); - int ret = HANDLE_EINTR(connect(socket->fd(), - reinterpret_cast(&addr), - sizeof(addr))); - if (ret != 0) - return false; - // Check the cookie again. We only link in /tmp, which is sticky, so, if the - // directory is still correct, it must have been correct in-between when we - // connected. POSIX, sadly, lacks a connectat(). - if (!CheckCookie(remote_cookie, cookie)) { - socket->Reset(); - return false; - } - // Success! - return true; - } else if (errno == EINVAL) { - // It exists, but is not a symlink (or some other error we detect - // later). Just connect to it directly; this is an older version of Chrome. - sockaddr_un addr; - SetupSockAddr(socket_path.value(), &addr); - int ret = HANDLE_EINTR(connect(socket->fd(), - reinterpret_cast(&addr), - sizeof(addr))); - return (ret == 0); - } else { - // File is missing, or other error. - if (errno != ENOENT) - PLOG(ERROR) << "readlink failed"; - return false; - } -} - -#if defined(OS_MACOSX) -bool ReplaceOldSingletonLock(const base::FilePath& symlink_content, - const base::FilePath& lock_path) { - // Try taking an flock(2) on the file. Failure means the lock is taken so we - // should quit. - base::ScopedFD lock_fd(HANDLE_EINTR( - open(lock_path.value().c_str(), O_RDWR | O_CREAT | O_SYMLINK, 0644))); - if (!lock_fd.is_valid()) { - PLOG(ERROR) << "Could not open singleton lock"; - return false; - } - - int rc = HANDLE_EINTR(flock(lock_fd.get(), LOCK_EX | LOCK_NB)); - if (rc == -1) { - if (errno == EWOULDBLOCK) { - LOG(ERROR) << "Singleton lock held by old process."; - } else { - PLOG(ERROR) << "Error locking singleton lock"; - } - return false; - } - - // Successfully taking the lock means we can replace it with the a new symlink - // lock. We never flock() the lock file from now on. I.e. we assume that an - // old version of Chrome will not run with the same user data dir after this - // version has run. - if (!base::DeleteFile(lock_path, false)) { - PLOG(ERROR) << "Could not delete old singleton lock."; - return false; - } - - return SymlinkPath(symlink_content, lock_path); -} -#endif // defined(OS_MACOSX) - -} // namespace - -/////////////////////////////////////////////////////////////////////////////// -// ProcessSingleton::LinuxWatcher -// A helper class for a Linux specific implementation of the process singleton. -// This class sets up a listener on the singleton socket and handles parsing -// messages that come in on the singleton socket. -class ProcessSingleton::LinuxWatcher - : public base::MessageLoopForIO::Watcher, - public base::MessageLoop::DestructionObserver, - public base::RefCountedThreadSafe { - public: - // A helper class to read message from an established socket. - class SocketReader : public base::MessageLoopForIO::Watcher { - public: - SocketReader(ProcessSingleton::LinuxWatcher* parent, - base::MessageLoop* ui_message_loop, - int fd) - : parent_(parent), - ui_message_loop_(ui_message_loop), - fd_(fd), - bytes_read_(0) { - DCHECK_CURRENTLY_ON(BrowserThread::IO); - // Wait for reads. - base::MessageLoopForIO::current()->WatchFileDescriptor( - fd, true, base::MessageLoopForIO::WATCH_READ, &fd_reader_, this); - // If we haven't completed in a reasonable amount of time, give up. - timer_.Start(FROM_HERE, base::TimeDelta::FromSeconds(kTimeoutInSeconds), - this, &SocketReader::CleanupAndDeleteSelf); - } - - ~SocketReader() override { CloseSocket(fd_); } - - // MessageLoopForIO::Watcher impl. - void OnFileCanReadWithoutBlocking(int fd) override; - void OnFileCanWriteWithoutBlocking(int fd) override { - // SocketReader only watches for accept (read) events. - NOTREACHED(); - } - - // Finish handling the incoming message by optionally sending back an ACK - // message and removing this SocketReader. - void FinishWithACK(const char *message, size_t length); - - private: - void CleanupAndDeleteSelf() { - DCHECK_CURRENTLY_ON(BrowserThread::IO); - - parent_->RemoveSocketReader(this); - // We're deleted beyond this point. - } - - base::MessageLoopForIO::FileDescriptorWatcher fd_reader_; - - // The ProcessSingleton::LinuxWatcher that owns us. - ProcessSingleton::LinuxWatcher* const parent_; - - // A reference to the UI message loop. - base::MessageLoop* const ui_message_loop_; - - // The file descriptor we're reading. - const int fd_; - - // Store the message in this buffer. - char buf_[kMaxMessageLength]; - - // Tracks the number of bytes we've read in case we're getting partial - // reads. - size_t bytes_read_; - - base::OneShotTimer timer_; - - DISALLOW_COPY_AND_ASSIGN(SocketReader); - }; - - // We expect to only be constructed on the UI thread. - explicit LinuxWatcher(ProcessSingleton* parent) - : ui_message_loop_(base::MessageLoop::current()), - parent_(parent) { - } - - // Start listening for connections on the socket. This method should be - // called from the IO thread. - void StartListening(int socket); - - // This method determines if we should use the same process and if we should, - // opens a new browser tab. This runs on the UI thread. - // |reader| is for sending back ACK message. - void HandleMessage(const std::string& current_dir, - const std::vector& argv, - SocketReader* reader); - - // MessageLoopForIO::Watcher impl. These run on the IO thread. - void OnFileCanReadWithoutBlocking(int fd) override; - void OnFileCanWriteWithoutBlocking(int fd) override { - // ProcessSingleton only watches for accept (read) events. - NOTREACHED(); - } - - // MessageLoop::DestructionObserver - void WillDestroyCurrentMessageLoop() override { - fd_watcher_.StopWatchingFileDescriptor(); - } - - private: - friend struct BrowserThread::DeleteOnThread; - friend class base::DeleteHelper; - - ~LinuxWatcher() override { - DCHECK_CURRENTLY_ON(BrowserThread::IO); - STLDeleteElements(&readers_); - - base::MessageLoopForIO* ml = base::MessageLoopForIO::current(); - ml->RemoveDestructionObserver(this); - } - - // Removes and deletes the SocketReader. - void RemoveSocketReader(SocketReader* reader); - - base::MessageLoopForIO::FileDescriptorWatcher fd_watcher_; - - // A reference to the UI message loop (i.e., the message loop we were - // constructed on). - base::MessageLoop* ui_message_loop_; - - // The ProcessSingleton that owns us. - ProcessSingleton* const parent_; - - std::set readers_; - - DISALLOW_COPY_AND_ASSIGN(LinuxWatcher); -}; - -void ProcessSingleton::LinuxWatcher::OnFileCanReadWithoutBlocking(int fd) { - DCHECK_CURRENTLY_ON(BrowserThread::IO); - // Accepting incoming client. - sockaddr_un from; - socklen_t from_len = sizeof(from); - int connection_socket = HANDLE_EINTR(accept( - fd, reinterpret_cast(&from), &from_len)); - if (-1 == connection_socket) { - PLOG(ERROR) << "accept() failed"; - return; - } - int rv = net::SetNonBlocking(connection_socket); - DCHECK_EQ(0, rv) << "Failed to make non-blocking socket."; - SocketReader* reader = new SocketReader(this, - ui_message_loop_, - connection_socket); - readers_.insert(reader); -} - -void ProcessSingleton::LinuxWatcher::StartListening(int socket) { - DCHECK_CURRENTLY_ON(BrowserThread::IO); - // Watch for client connections on this socket. - base::MessageLoopForIO* ml = base::MessageLoopForIO::current(); - ml->AddDestructionObserver(this); - ml->WatchFileDescriptor(socket, true, base::MessageLoopForIO::WATCH_READ, - &fd_watcher_, this); -} - -void ProcessSingleton::LinuxWatcher::HandleMessage( - const std::string& current_dir, const std::vector& argv, - SocketReader* reader) { - DCHECK(ui_message_loop_ == base::MessageLoop::current()); - DCHECK(reader); - - if (parent_->notification_callback_.Run(base::CommandLine(argv), - base::FilePath(current_dir))) { - // Send back "ACK" message to prevent the client process from starting up. - reader->FinishWithACK(kACKToken, arraysize(kACKToken) - 1); - } else { - LOG(WARNING) << "Not handling interprocess notification as browser" - " is shutting down"; - // Send back "SHUTDOWN" message, so that the client process can start up - // without killing this process. - reader->FinishWithACK(kShutdownToken, arraysize(kShutdownToken) - 1); - return; - } -} - -void ProcessSingleton::LinuxWatcher::RemoveSocketReader(SocketReader* reader) { - DCHECK_CURRENTLY_ON(BrowserThread::IO); - DCHECK(reader); - readers_.erase(reader); - delete reader; -} - -/////////////////////////////////////////////////////////////////////////////// -// ProcessSingleton::LinuxWatcher::SocketReader -// - -void ProcessSingleton::LinuxWatcher::SocketReader::OnFileCanReadWithoutBlocking( - int fd) { - DCHECK_CURRENTLY_ON(BrowserThread::IO); - DCHECK_EQ(fd, fd_); - while (bytes_read_ < sizeof(buf_)) { - ssize_t rv = HANDLE_EINTR( - read(fd, buf_ + bytes_read_, sizeof(buf_) - bytes_read_)); - if (rv < 0) { - if (errno != EAGAIN && errno != EWOULDBLOCK) { - PLOG(ERROR) << "read() failed"; - CloseSocket(fd); - return; - } else { - // It would block, so we just return and continue to watch for the next - // opportunity to read. - return; - } - } else if (!rv) { - // No more data to read. It's time to process the message. - break; - } else { - bytes_read_ += rv; - } - } - - // Validate the message. The shortest message is kStartToken\0x\0x - const size_t kMinMessageLength = arraysize(kStartToken) + 4; - if (bytes_read_ < kMinMessageLength) { - buf_[bytes_read_] = 0; - LOG(ERROR) << "Invalid socket message (wrong length):" << buf_; - CleanupAndDeleteSelf(); - return; - } - - std::string str(buf_, bytes_read_); - std::vector tokens = base::SplitString( - str, std::string(1, kTokenDelimiter), - base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); - - if (tokens.size() < 3 || tokens[0] != kStartToken) { - LOG(ERROR) << "Wrong message format: " << str; - CleanupAndDeleteSelf(); - return; - } - - // Stop the expiration timer to prevent this SocketReader object from being - // terminated unexpectly. - timer_.Stop(); - - std::string current_dir = tokens[1]; - // Remove the first two tokens. The remaining tokens should be the command - // line argv array. - tokens.erase(tokens.begin()); - tokens.erase(tokens.begin()); - - // Return to the UI thread to handle opening a new browser tab. - ui_message_loop_->task_runner()->PostTask( - FROM_HERE, base::Bind(&ProcessSingleton::LinuxWatcher::HandleMessage, - parent_, current_dir, tokens, this)); - fd_reader_.StopWatchingFileDescriptor(); - - // LinuxWatcher::HandleMessage() is in charge of destroying this SocketReader - // object by invoking SocketReader::FinishWithACK(). -} - -void ProcessSingleton::LinuxWatcher::SocketReader::FinishWithACK( - const char *message, size_t length) { - if (message && length) { - // Not necessary to care about the return value. - WriteToSocket(fd_, message, length); - } - - if (shutdown(fd_, SHUT_WR) < 0) - PLOG(ERROR) << "shutdown() failed"; - - BrowserThread::PostTask( - BrowserThread::IO, - FROM_HERE, - base::Bind(&ProcessSingleton::LinuxWatcher::RemoveSocketReader, - parent_, - this)); - // We will be deleted once the posted RemoveSocketReader task runs. -} - -/////////////////////////////////////////////////////////////////////////////// -// ProcessSingleton -// -ProcessSingleton::ProcessSingleton( - const base::FilePath& user_data_dir, - const NotificationCallback& notification_callback) - : notification_callback_(notification_callback), - current_pid_(base::GetCurrentProcId()), - watcher_(new LinuxWatcher(this)) { - socket_path_ = user_data_dir.Append(kSingletonSocketFilename); - lock_path_ = user_data_dir.Append(kSingletonLockFilename); - cookie_path_ = user_data_dir.Append(kSingletonCookieFilename); - - kill_callback_ = base::Bind(&ProcessSingleton::KillProcess, - base::Unretained(this)); -} - -ProcessSingleton::~ProcessSingleton() { -} - -ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() { - return NotifyOtherProcessWithTimeout( - *base::CommandLine::ForCurrentProcess(), kRetryAttempts, - base::TimeDelta::FromSeconds(kTimeoutInSeconds), true); -} - -ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout( - const base::CommandLine& cmd_line, - int retry_attempts, - const base::TimeDelta& timeout, - bool kill_unresponsive) { - DCHECK_GE(retry_attempts, 0); - DCHECK_GE(timeout.InMicroseconds(), 0); - - base::TimeDelta sleep_interval = timeout / retry_attempts; - - ScopedSocket socket; - for (int retries = 0; retries <= retry_attempts; ++retries) { - // Try to connect to the socket. - if (ConnectSocket(&socket, socket_path_, cookie_path_)) - break; - - // If we're in a race with another process, they may be in Create() and have - // created the lock but not attached to the socket. So we check if the - // process with the pid from the lockfile is currently running and is a - // chrome browser. If so, we loop and try again for |timeout|. - - std::string hostname; - int pid; - if (!ParseLockPath(lock_path_, &hostname, &pid)) { - // No lockfile exists. - return PROCESS_NONE; - } - - if (hostname.empty()) { - // Invalid lockfile. - UnlinkPath(lock_path_); - return PROCESS_NONE; - } - - if (hostname != net::GetHostName() && !IsChromeProcess(pid)) { - // Locked by process on another host. If the user selected to unlock - // the profile, try to continue; otherwise quit. - if (DisplayProfileInUseError(lock_path_, hostname, pid)) { - UnlinkPath(lock_path_); - return PROCESS_NONE; - } - return PROFILE_IN_USE; - } - - if (!IsChromeProcess(pid)) { - // Orphaned lockfile (no process with pid, or non-chrome process.) - UnlinkPath(lock_path_); - return PROCESS_NONE; - } - - if (IsSameChromeInstance(pid)) { - // Orphaned lockfile (pid is part of same chrome instance we are, even - // though we haven't tried to create a lockfile yet). - UnlinkPath(lock_path_); - return PROCESS_NONE; - } - - if (retries == retry_attempts) { - // Retries failed. Kill the unresponsive chrome process and continue. - if (!kill_unresponsive || !KillProcessByLockPath()) - return PROFILE_IN_USE; - return PROCESS_NONE; - } - - base::PlatformThread::Sleep(sleep_interval); - } - - timeval socket_timeout = TimeDeltaToTimeVal(timeout); - setsockopt(socket.fd(), - SOL_SOCKET, - SO_SNDTIMEO, - &socket_timeout, - sizeof(socket_timeout)); - - // Found another process, prepare our command line - // format is "START\0\0\0...\0". - std::string to_send(kStartToken); - to_send.push_back(kTokenDelimiter); - - base::FilePath current_dir; - if (!PathService::Get(base::DIR_CURRENT, ¤t_dir)) - return PROCESS_NONE; - to_send.append(current_dir.value()); - - const std::vector& argv = cmd_line.argv(); - for (std::vector::const_iterator it = argv.begin(); - it != argv.end(); ++it) { - to_send.push_back(kTokenDelimiter); - to_send.append(*it); - } - - // Send the message - if (!WriteToSocket(socket.fd(), to_send.data(), to_send.length())) { - // Try to kill the other process, because it might have been dead. - if (!kill_unresponsive || !KillProcessByLockPath()) - return PROFILE_IN_USE; - return PROCESS_NONE; - } - - if (shutdown(socket.fd(), SHUT_WR) < 0) - PLOG(ERROR) << "shutdown() failed"; - - // Read ACK message from the other process. It might be blocked for a certain - // timeout, to make sure the other process has enough time to return ACK. - char buf[kMaxACKMessageLength + 1]; - ssize_t len = ReadFromSocket(socket.fd(), buf, kMaxACKMessageLength, timeout); - - // Failed to read ACK, the other process might have been frozen. - if (len <= 0) { - if (!kill_unresponsive || !KillProcessByLockPath()) - return PROFILE_IN_USE; - return PROCESS_NONE; - } - - buf[len] = '\0'; - if (strncmp(buf, kShutdownToken, arraysize(kShutdownToken) - 1) == 0) { - // The other process is shutting down, it's safe to start a new process. - return PROCESS_NONE; - } else if (strncmp(buf, kACKToken, arraysize(kACKToken) - 1) == 0) { -#if defined(TOOLKIT_VIEWS) && defined(OS_LINUX) && !defined(OS_CHROMEOS) - // Likely NULL in unit tests. - views::LinuxUI* linux_ui = views::LinuxUI::instance(); - if (linux_ui) - linux_ui->NotifyWindowManagerStartupComplete(); -#endif - - // Assume the other process is handling the request. - return PROCESS_NOTIFIED; - } - - NOTREACHED() << "The other process returned unknown message: " << buf; - return PROCESS_NOTIFIED; -} - -ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessOrCreate() { - return NotifyOtherProcessWithTimeoutOrCreate( - *base::CommandLine::ForCurrentProcess(), kRetryAttempts, - base::TimeDelta::FromSeconds(kTimeoutInSeconds)); -} - -ProcessSingleton::NotifyResult -ProcessSingleton::NotifyOtherProcessWithTimeoutOrCreate( - const base::CommandLine& command_line, - int retry_attempts, - const base::TimeDelta& timeout) { - NotifyResult result = NotifyOtherProcessWithTimeout( - command_line, retry_attempts, timeout, true); - if (result != PROCESS_NONE) - return result; - if (Create()) - return PROCESS_NONE; - // If the Create() failed, try again to notify. (It could be that another - // instance was starting at the same time and managed to grab the lock before - // we did.) - // This time, we don't want to kill anything if we aren't successful, since we - // aren't going to try to take over the lock ourselves. - result = NotifyOtherProcessWithTimeout( - command_line, retry_attempts, timeout, false); - if (result != PROCESS_NONE) - return result; - - return LOCK_ERROR; -} - -void ProcessSingleton::OverrideCurrentPidForTesting(base::ProcessId pid) { - current_pid_ = pid; -} - -void ProcessSingleton::OverrideKillCallbackForTesting( - const base::Callback& callback) { - kill_callback_ = callback; -} - -void ProcessSingleton::DisablePromptForTesting() { - g_disable_prompt = true; -} - -bool ProcessSingleton::Create() { - int sock; - sockaddr_un addr; - - // The symlink lock is pointed to the hostname and process id, so other - // processes can find it out. - base::FilePath symlink_content(base::StringPrintf( - "%s%c%u", - net::GetHostName().c_str(), - kLockDelimiter, - current_pid_)); - - // Create symbol link before binding the socket, to ensure only one instance - // can have the socket open. - if (!SymlinkPath(symlink_content, lock_path_)) { - // TODO(jackhou): Remove this case once this code is stable on Mac. - // http://crbug.com/367612 -#if defined(OS_MACOSX) - // On Mac, an existing non-symlink lock file means the lock could be held by - // the old process singleton code. If we can successfully replace the lock, - // continue as normal. - if (base::IsLink(lock_path_) || - !ReplaceOldSingletonLock(symlink_content, lock_path_)) { - return false; - } -#else - // If we failed to create the lock, most likely another instance won the - // startup race. - return false; -#endif - } - - // Create the socket file somewhere in /tmp which is usually mounted as a - // normal filesystem. Some network filesystems (notably AFS) are screwy and - // do not support Unix domain sockets. - if (!socket_dir_.CreateUniqueTempDir()) { - LOG(ERROR) << "Failed to create socket directory."; - return false; - } - - // Check that the directory was created with the correct permissions. - int dir_mode = 0; - CHECK(base::GetPosixFilePermissions(socket_dir_.path(), &dir_mode) && - dir_mode == base::FILE_PERMISSION_USER_MASK) - << "Temp directory mode is not 700: " << std::oct << dir_mode; - - // Setup the socket symlink and the two cookies. - base::FilePath socket_target_path = - socket_dir_.path().Append(kSingletonSocketFilename); - base::FilePath cookie(GenerateCookie()); - base::FilePath remote_cookie_path = - socket_dir_.path().Append(kSingletonCookieFilename); - UnlinkPath(socket_path_); - UnlinkPath(cookie_path_); - if (!SymlinkPath(socket_target_path, socket_path_) || - !SymlinkPath(cookie, cookie_path_) || - !SymlinkPath(cookie, remote_cookie_path)) { - // We've already locked things, so we can't have lost the startup race, - // but something doesn't like us. - LOG(ERROR) << "Failed to create symlinks."; - if (!socket_dir_.Delete()) - LOG(ERROR) << "Encountered a problem when deleting socket directory."; - return false; - } - - SetupSocket(socket_target_path.value(), &sock, &addr); - - if (bind(sock, reinterpret_cast(&addr), sizeof(addr)) < 0) { - PLOG(ERROR) << "Failed to bind() " << socket_target_path.value(); - CloseSocket(sock); - return false; - } - - if (listen(sock, 5) < 0) - NOTREACHED() << "listen failed: " << base::safe_strerror(errno); - - DCHECK(BrowserThread::IsMessageLoopValid(BrowserThread::IO)); - BrowserThread::PostTask( - BrowserThread::IO, - FROM_HERE, - base::Bind(&ProcessSingleton::LinuxWatcher::StartListening, - watcher_.get(), - sock)); - - return true; -} - -void ProcessSingleton::Cleanup() { - UnlinkPath(socket_path_); - UnlinkPath(cookie_path_); - UnlinkPath(lock_path_); -} - -bool ProcessSingleton::IsSameChromeInstance(pid_t pid) { - pid_t cur_pid = current_pid_; - while (pid != cur_pid) { - pid = base::GetParentProcessId(pid); - if (pid < 0) - return false; - if (!IsChromeProcess(pid)) - return false; - } - return true; -} - -bool ProcessSingleton::KillProcessByLockPath() { - std::string hostname; - int pid; - ParseLockPath(lock_path_, &hostname, &pid); - - if (!hostname.empty() && hostname != net::GetHostName()) { - return DisplayProfileInUseError(lock_path_, hostname, pid); - } - UnlinkPath(lock_path_); - - if (IsSameChromeInstance(pid)) - return true; - - if (pid > 0) { - kill_callback_.Run(pid); - return true; - } - - LOG(ERROR) << "Failed to extract pid from path: " << lock_path_.value(); - return true; -} - -void ProcessSingleton::KillProcess(int pid) { - // TODO(james.su@gmail.com): Is SIGKILL ok? - int rv = kill(static_cast(pid), SIGKILL); - // ESRCH = No Such Process (can happen if the other process is already in - // progress of shutting down and finishes before we try to kill it). - DCHECK(rv == 0 || errno == ESRCH) << "Error killing process: " - << base::safe_strerror(errno); -} diff --git a/atom/browser/process_singleton_startup_lock.cc b/atom/browser/process_singleton_startup_lock.cc deleted file mode 100644 index fa53284f05..0000000000 --- a/atom/browser/process_singleton_startup_lock.cc +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "atom/browser/process_singleton_startup_lock.h" - -#include "base/bind.h" -#include "base/logging.h" - -ProcessSingletonStartupLock::ProcessSingletonStartupLock( - const ProcessSingleton::NotificationCallback& original_callback) - : locked_(true), - original_callback_(original_callback) {} - -ProcessSingletonStartupLock::~ProcessSingletonStartupLock() {} - -ProcessSingleton::NotificationCallback -ProcessSingletonStartupLock::AsNotificationCallback() { - return base::Bind(&ProcessSingletonStartupLock::NotificationCallbackImpl, - base::Unretained(this)); -} - -void ProcessSingletonStartupLock::Unlock() { - DCHECK(CalledOnValidThread()); - locked_ = false; - - // Replay the command lines of the messages which were received while the - // ProcessSingleton was locked. Only replay each message once. - std::set replayed_messages; - for (std::vector::const_iterator it = - saved_startup_messages_.begin(); - it != saved_startup_messages_.end(); ++it) { - if (replayed_messages.find(*it) != replayed_messages.end()) - continue; - original_callback_.Run(base::CommandLine(it->first), it->second); - replayed_messages.insert(*it); - } - saved_startup_messages_.clear(); -} - -bool ProcessSingletonStartupLock::NotificationCallbackImpl( - const base::CommandLine& command_line, - const base::FilePath& current_directory) { - if (locked_) { - // If locked, it means we are not ready to process this message because - // we are probably in a first run critical phase. - saved_startup_messages_.push_back( - std::make_pair(command_line.argv(), current_directory)); - return true; - } else { - return original_callback_.Run(command_line, current_directory); - } -} diff --git a/atom/browser/process_singleton_startup_lock.h b/atom/browser/process_singleton_startup_lock.h deleted file mode 100644 index aecff8414e..0000000000 --- a/atom/browser/process_singleton_startup_lock.h +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef ATOM_BROWSER_PROCESS_SINGLETON_STARTUP_LOCK_H_ -#define ATOM_BROWSER_PROCESS_SINGLETON_STARTUP_LOCK_H_ - -#include -#include -#include - -#include "base/basictypes.h" -#include "base/command_line.h" -#include "base/files/file_path.h" -#include "base/threading/non_thread_safe.h" -#include "atom/browser/process_singleton.h" - -// Provides a ProcessSingleton::NotificationCallback that can queue up -// command-line invocations during startup and execute them when startup -// completes. -// -// The object starts in a locked state. |Unlock()| must be called -// when the process is prepared to handle command-line invocations. -// -// Once unlocked, notifications are forwarded to a wrapped NotificationCallback. -class ProcessSingletonStartupLock : public base::NonThreadSafe { - public: - explicit ProcessSingletonStartupLock( - const ProcessSingleton::NotificationCallback& original_callback); - ~ProcessSingletonStartupLock(); - - // Returns the ProcessSingleton::NotificationCallback. - // The callback is only valid during the lifetime of the - // ProcessSingletonStartupLock instance. - ProcessSingleton::NotificationCallback AsNotificationCallback(); - - // Executes previously queued command-line invocations and allows future - // invocations to be executed immediately. - void Unlock(); - - bool locked() { return locked_; } - - private: - typedef std::pair - DelayedStartupMessage; - - bool NotificationCallbackImpl(const base::CommandLine& command_line, - const base::FilePath& current_directory); - - bool locked_; - std::vector saved_startup_messages_; - ProcessSingleton::NotificationCallback original_callback_; - - DISALLOW_COPY_AND_ASSIGN(ProcessSingletonStartupLock); -}; - -#endif // CHROME_BROWSER_PROCESS_SINGLETON_STARTUP_LOCK_H_ diff --git a/atom/browser/process_singleton_win.cc b/atom/browser/process_singleton_win.cc deleted file mode 100644 index cf667f11e4..0000000000 --- a/atom/browser/process_singleton_win.cc +++ /dev/null @@ -1,317 +0,0 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "atom/browser/process_singleton.h" - -#include - -#include "base/base_paths.h" -#include "base/bind.h" -#include "base/command_line.h" -#include "base/files/file_path.h" -#include "base/process/process.h" -#include "base/process/process_info.h" -#include "base/strings/string_number_conversions.h" -#include "base/strings/stringprintf.h" -#include "base/strings/utf_string_conversions.h" -#include "base/time/time.h" -#include "base/win/metro.h" -#include "base/win/registry.h" -#include "base/win/scoped_handle.h" -#include "base/win/windows_version.h" -//#include "chrome/browser/browser_process.h" -//#include "chrome/browser/browser_process_platform_part.h" -#include "atom/browser/atom_process_finder_win.h" -//#include "chrome/browser/shell_integration.h" // TODO: Maybe pull this in? -#include "content/public/common/result_codes.h" -#include "net/base/escape.h" -#include "ui/base/l10n/l10n_util.h" -#include "ui/gfx/win/hwnd_util.h" - -namespace { - -const char kLockfile[] = "lockfile"; - -// A helper class that acquires the given |mutex| while the AutoLockMutex is in -// scope. -class AutoLockMutex { - public: - explicit AutoLockMutex(HANDLE mutex) : mutex_(mutex) { - DWORD result = ::WaitForSingleObject(mutex_, INFINITE); - DPCHECK(result == WAIT_OBJECT_0) << "Result = " << result; - } - - ~AutoLockMutex() { - BOOL released = ::ReleaseMutex(mutex_); - DPCHECK(released); - } - - private: - HANDLE mutex_; - DISALLOW_COPY_AND_ASSIGN(AutoLockMutex); -}; - -// A helper class that releases the given |mutex| while the AutoUnlockMutex is -// in scope and immediately re-acquires it when going out of scope. -class AutoUnlockMutex { - public: - explicit AutoUnlockMutex(HANDLE mutex) : mutex_(mutex) { - BOOL released = ::ReleaseMutex(mutex_); - DPCHECK(released); - } - - ~AutoUnlockMutex() { - DWORD result = ::WaitForSingleObject(mutex_, INFINITE); - DPCHECK(result == WAIT_OBJECT_0) << "Result = " << result; - } - - private: - HANDLE mutex_; - DISALLOW_COPY_AND_ASSIGN(AutoUnlockMutex); -}; - -// Checks the visibility of the enumerated window and signals once a visible -// window has been found. -BOOL CALLBACK BrowserWindowEnumeration(HWND window, LPARAM param) { - bool* result = reinterpret_cast(param); - *result = ::IsWindowVisible(window) != 0; - // Stops enumeration if a visible window has been found. - return !*result; -} - -bool ParseCommandLine(const COPYDATASTRUCT* cds, - base::CommandLine* parsed_command_line, - base::FilePath* current_directory) { - // We should have enough room for the shortest command (min_message_size) - // and also be a multiple of wchar_t bytes. The shortest command - // possible is L"START\0\0" (empty current directory and command line). - static const int min_message_size = 7; - if (cds->cbData < min_message_size * sizeof(wchar_t) || - cds->cbData % sizeof(wchar_t) != 0) { - LOG(WARNING) << "Invalid WM_COPYDATA, length = " << cds->cbData; - return false; - } - - // We split the string into 4 parts on NULLs. - DCHECK(cds->lpData); - const std::wstring msg(static_cast(cds->lpData), - cds->cbData / sizeof(wchar_t)); - const std::wstring::size_type first_null = msg.find_first_of(L'\0'); - if (first_null == 0 || first_null == std::wstring::npos) { - // no NULL byte, don't know what to do - LOG(WARNING) << "Invalid WM_COPYDATA, length = " << msg.length() << - ", first null = " << first_null; - return false; - } - - // Decode the command, which is everything until the first NULL. - if (msg.substr(0, first_null) == L"START") { - // Another instance is starting parse the command line & do what it would - // have done. - VLOG(1) << "Handling STARTUP request from another process"; - const std::wstring::size_type second_null = - msg.find_first_of(L'\0', first_null + 1); - if (second_null == std::wstring::npos || - first_null == msg.length() - 1 || second_null == msg.length()) { - LOG(WARNING) << "Invalid format for start command, we need a string in 4 " - "parts separated by NULLs"; - return false; - } - - // Get current directory. - *current_directory = base::FilePath(msg.substr(first_null + 1, - second_null - first_null)); - - const std::wstring::size_type third_null = - msg.find_first_of(L'\0', second_null + 1); - if (third_null == std::wstring::npos || - third_null == msg.length()) { - LOG(WARNING) << "Invalid format for start command, we need a string in 4 " - "parts separated by NULLs"; - } - - // Get command line. - const std::wstring cmd_line = - msg.substr(second_null + 1, third_null - second_null); - *parsed_command_line = base::CommandLine::FromString(cmd_line); - return true; - } - return false; -} - -bool ProcessLaunchNotification( - const ProcessSingleton::NotificationCallback& notification_callback, - UINT message, - WPARAM wparam, - LPARAM lparam, - LRESULT* result) { - if (message != WM_COPYDATA) - return false; - - // Handle the WM_COPYDATA message from another process. - const COPYDATASTRUCT* cds = reinterpret_cast(lparam); - - base::CommandLine parsed_command_line(base::CommandLine::NO_PROGRAM); - base::FilePath current_directory; - if (!ParseCommandLine(cds, &parsed_command_line, ¤t_directory)) { - *result = TRUE; - return true; - } - - *result = notification_callback.Run(parsed_command_line, current_directory) ? - TRUE : FALSE; - return true; -} - -bool TerminateAppWithError() { - // TODO: This is called when the secondary process can't ping the primary - // process. Need to find out what to do here. - return false; -} - -} // namespace - -ProcessSingleton::ProcessSingleton( - const base::FilePath& user_data_dir, - const NotificationCallback& notification_callback) - : notification_callback_(notification_callback), - is_virtualized_(false), - lock_file_(INVALID_HANDLE_VALUE), - user_data_dir_(user_data_dir), - should_kill_remote_process_callback_( - base::Bind(&TerminateAppWithError)) { -} - -ProcessSingleton::~ProcessSingleton() { - if (lock_file_ != INVALID_HANDLE_VALUE) - ::CloseHandle(lock_file_); -} - -// Code roughly based on Mozilla. -ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() { - if (is_virtualized_) - return PROCESS_NOTIFIED; // We already spawned the process in this case. - if (lock_file_ == INVALID_HANDLE_VALUE && !remote_window_) { - return LOCK_ERROR; - } else if (!remote_window_) { - return PROCESS_NONE; - } - - switch (atom::AttemptToNotifyRunningAtom(remote_window_, false)) { - case atom::NOTIFY_SUCCESS: - return PROCESS_NOTIFIED; - case atom::NOTIFY_FAILED: - remote_window_ = NULL; - return PROCESS_NONE; - case atom::NOTIFY_WINDOW_HUNG: - // Fall through and potentially terminate the hung browser. - break; - } - - DWORD process_id = 0; - DWORD thread_id = ::GetWindowThreadProcessId(remote_window_, &process_id); - if (!thread_id || !process_id) { - remote_window_ = NULL; - return PROCESS_NONE; - } - base::Process process = base::Process::Open(process_id); - - // The window is hung. Scan for every window to find a visible one. - bool visible_window = false; - ::EnumThreadWindows(thread_id, - &BrowserWindowEnumeration, - reinterpret_cast(&visible_window)); - - // If there is a visible browser window, ask the user before killing it. - if (visible_window && !should_kill_remote_process_callback_.Run()) { - // The user denied. Quit silently. - return PROCESS_NOTIFIED; - } - - // Time to take action. Kill the browser process. - process.Terminate(content::RESULT_CODE_HUNG, true); - remote_window_ = NULL; - return PROCESS_NONE; -} - -ProcessSingleton::NotifyResult -ProcessSingleton::NotifyOtherProcessOrCreate() { - ProcessSingleton::NotifyResult result = PROCESS_NONE; - if (!Create()) { - result = NotifyOtherProcess(); - if (result == PROCESS_NONE) - result = PROFILE_IN_USE; - } else { - // TODO: Figure out how to implement this - //g_browser_process->platform_part()->PlatformSpecificCommandLineProcessing( - // *base::CommandLine::ForCurrentProcess()); - } - return result; -} - -// Look for a Chrome instance that uses the same profile directory. If there -// isn't one, create a message window with its title set to the profile -// directory path. -bool ProcessSingleton::Create() { - static const wchar_t kMutexName[] = L"Local\\AtomProcessSingletonStartup!"; - - remote_window_ = atom::FindRunningAtomWindow(user_data_dir_); - if (!remote_window_) { - // Make sure we will be the one and only process creating the window. - // We use a named Mutex since we are protecting against multi-process - // access. As documented, it's clearer to NOT request ownership on creation - // since it isn't guaranteed we will get it. It is better to create it - // without ownership and explicitly get the ownership afterward. - base::win::ScopedHandle only_me(::CreateMutex(NULL, FALSE, kMutexName)); - if (!only_me.IsValid()) { - DPLOG(FATAL) << "CreateMutex failed"; - return false; - } - - AutoLockMutex auto_lock_only_me(only_me.Get()); - - // We now own the mutex so we are the only process that can create the - // window at this time, but we must still check if someone created it - // between the time where we looked for it above and the time the mutex - // was given to us. - remote_window_ = atom::FindRunningAtomWindow(user_data_dir_); - if (!remote_window_) { - // We have to make sure there is no Chrome instance running on another - // machine that uses the same profile. - base::FilePath lock_file_path = user_data_dir_.AppendASCII(kLockfile); - lock_file_ = ::CreateFile(lock_file_path.value().c_str(), - GENERIC_WRITE, - FILE_SHARE_READ, - NULL, - CREATE_ALWAYS, - FILE_ATTRIBUTE_NORMAL | - FILE_FLAG_DELETE_ON_CLOSE, - NULL); - DWORD error = ::GetLastError(); - LOG_IF(WARNING, lock_file_ != INVALID_HANDLE_VALUE && - error == ERROR_ALREADY_EXISTS) << "Lock file exists but is writable."; - LOG_IF(ERROR, lock_file_ == INVALID_HANDLE_VALUE) - << "Lock file can not be created! Error code: " << error; - - if (lock_file_ != INVALID_HANDLE_VALUE) { - // Set the window's title to the path of our user data directory so - // other Chrome instances can decide if they should forward to us. - bool result = window_.CreateNamed( - base::Bind(&ProcessLaunchNotification, notification_callback_), - user_data_dir_.value()); - CHECK(result && window_.hwnd()); - } - } - } - - return window_.hwnd() != NULL; -} - -void ProcessSingleton::Cleanup() { -} - -void ProcessSingleton::OverrideShouldKillRemoteProcessCallbackForTesting( - const ShouldKillRemoteProcessCallback& display_dialog_callback) { - should_kill_remote_process_callback_ = display_dialog_callback; -} From 4a7a09aae1779e548d505741d3d2fbe1f257dcb5 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Tue, 20 Oct 2015 15:00:10 -0700 Subject: [PATCH 074/146] Infinite Linting --- atom/browser/api/atom_api_app.cc | 5 +++-- atom/browser/api/atom_api_app.h | 2 +- atom/browser/atom_process_singleton.h | 2 +- atom/browser/browser.cc | 5 +++-- atom/browser/browser.h | 3 ++- .../common/native_mate_converters/command_line_converter.h | 7 ++++--- 6 files changed, 14 insertions(+), 10 deletions(-) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index f3955d2d03..b16e346065 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -276,11 +276,12 @@ bool App::MakeSingleInstance(v8::Local callback) { browser->InitializeSingleInstance(); ProcessSingleton::NotificationCallback cb; - mate::Converter::FromV8(isolate(), single_instance_callback_, &cb); + mate::Converter::FromV8( + isolate(), single_instance_callback_, &cb); browser->SetSingleInstanceCallback(cb); - switch(browser->GetSingleInstanceResult()) { + switch (browser->GetSingleInstanceResult()) { case ProcessSingleton::NotifyResult::PROCESS_NONE: return false; case ProcessSingleton::NotifyResult::LOCK_ERROR: diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index 3dafdbc284..b10b40e5c9 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -71,7 +71,7 @@ class App : public mate::EventEmitter, void AllowNTLMCredentialsForAllDomains(bool should_allow); bool MakeSingleInstance(v8::Local callback); - + std::string GetLocale(); v8::Local DefaultSession(v8::Isolate* isolate); diff --git a/atom/browser/atom_process_singleton.h b/atom/browser/atom_process_singleton.h index 5654862143..89feeaecb3 100644 --- a/atom/browser/atom_process_singleton.h +++ b/atom/browser/atom_process_singleton.h @@ -16,7 +16,7 @@ class AtomProcessSingleton { AtomProcessSingleton( const base::FilePath& user_data_dir, const ProcessSingleton::NotificationCallback& notification_callback); - + ~AtomProcessSingleton(); // Notify another process, if available. Otherwise sets ourselves as the diff --git a/atom/browser/browser.cc b/atom/browser/browser.cc index 5c12a0d338..4308395eb7 100644 --- a/atom/browser/browser.cc +++ b/atom/browser/browser.cc @@ -117,7 +117,7 @@ void Browser::WillFinishLaunching() { void Browser::DidFinishLaunching() { is_ready_ = true; - process_singleton_->Unlock() ; + process_singleton_->Unlock(); FOR_EACH_OBSERVER(BrowserObserver, observers_, OnFinishLaunching()); } @@ -174,7 +174,8 @@ ProcessSingleton::NotifyResult Browser::GetSingleInstanceResult() { return process_notify_result_; } -void Browser::SetSingleInstanceCallback(ProcessSingleton::NotificationCallback callback) { +void Browser::SetSingleInstanceCallback( + ProcessSingleton::NotificationCallback callback) { process_notify_callback_ = callback; process_notify_callback_set_ = true; } diff --git a/atom/browser/browser.h b/atom/browser/browser.h index 9113c58dd1..1ac4d03e09 100644 --- a/atom/browser/browser.h +++ b/atom/browser/browser.h @@ -68,7 +68,8 @@ class Browser : public WindowListObserver { void InitializeSingleInstance(); ProcessSingleton::NotifyResult GetSingleInstanceResult(); - void SetSingleInstanceCallback(ProcessSingleton::NotificationCallback callback); + void SetSingleInstanceCallback( + ProcessSingleton::NotificationCallback callback); #if defined(OS_MACOSX) // Bounce the dock icon. diff --git a/atom/common/native_mate_converters/command_line_converter.h b/atom/common/native_mate_converters/command_line_converter.h index 7941c9a096..a704ed7e92 100644 --- a/atom/common/native_mate_converters/command_line_converter.h +++ b/atom/common/native_mate_converters/command_line_converter.h @@ -16,13 +16,14 @@ template<> struct Converter { static v8::Local ToV8(v8::Isolate* isolate, const base::CommandLine& val) { - return Converter::ToV8(isolate, val.GetCommandLineString()); + return Converter::ToV8( + isolate, val.GetCommandLineString()); } static bool FromV8(v8::Isolate* isolate, v8::Local val, base::CommandLine* out) { base::FilePath::StringType path; - + if (Converter::FromV8(isolate, val, &path)) { *out = base::CommandLine(base::FilePath(path)); return true; @@ -34,4 +35,4 @@ struct Converter { } // namespace mate -#endif // ATOM_COMMON_NATIVE_MATE_CONVERTERS_FILE_PATH_CONVERTER_H_ +#endif // ATOM_COMMON_NATIVE_MATE_CONVERTERS_COMMAND_LINE_CONVERTER_H_ From 15f00db1bf3bef8266d78ca582a0810d2068f52e Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Tue, 20 Oct 2015 15:03:59 -0700 Subject: [PATCH 075/146] I don't think we need this --- chromium_src/chrome/browser/process_singleton_win.cc | 4 ---- 1 file changed, 4 deletions(-) diff --git a/chromium_src/chrome/browser/process_singleton_win.cc b/chromium_src/chrome/browser/process_singleton_win.cc index bedc861c40..b1008c3760 100644 --- a/chromium_src/chrome/browser/process_singleton_win.cc +++ b/chromium_src/chrome/browser/process_singleton_win.cc @@ -239,10 +239,6 @@ ProcessSingleton::NotifyOtherProcessOrCreate() { result = NotifyOtherProcess(); if (result == PROCESS_NONE) result = PROFILE_IN_USE; - } else { - // TODO: Figure out how to implement this - //g_browser_process->platform_part()->PlatformSpecificCommandLineProcessing( - // *base::CommandLine::ForCurrentProcess()); } return result; } From fe25f3e747fa64475423acd2ff25cc1171162422 Mon Sep 17 00:00:00 2001 From: Juan Roa Date: Tue, 20 Oct 2015 18:10:08 -0500 Subject: [PATCH 076/146] grammatical corrections for spanish translations at api, styleguide and README --- docs-translations/es/README.md | 6 ++-- .../es/api/chrome-command-line-switches.md | 28 +++++++++---------- docs-translations/es/api/process.md | 8 +++--- docs-translations/es/api/synopsis.md | 14 +++++----- docs-translations/es/styleguide.md | 10 +++---- 5 files changed, 33 insertions(+), 33 deletions(-) diff --git a/docs-translations/es/README.md b/docs-translations/es/README.md index 6e2374cfd4..706261c417 100644 --- a/docs-translations/es/README.md +++ b/docs-translations/es/README.md @@ -1,8 +1,8 @@ ## Guías -* [Platfaformas Soportadas](tutorial/supported-platforms.md) -* [Distribución de la Aplicacion](tutorial/application-distribution.md) -* [Empaquetamiento de la Aplicacion](tutorial/application-packaging.md) +* [Plataformas Soportadas](tutorial/supported-platforms.md) +* [Distribución de la Aplicación](tutorial/application-distribution.md) +* [Empaquetamiento de la Aplicación](tutorial/application-packaging.md) * [Utilizando Módulos Node Nativos](tutorial/using-native-node-modules.md) * [Depurando el Proceso Principal](tutorial/debugging-main-process.md) * [Utilizando Selenium y WebDriver](tutorial/using-selenium-and-webdriver.md) diff --git a/docs-translations/es/api/chrome-command-line-switches.md b/docs-translations/es/api/chrome-command-line-switches.md index c063869adf..56973ec06a 100644 --- a/docs-translations/es/api/chrome-command-line-switches.md +++ b/docs-translations/es/api/chrome-command-line-switches.md @@ -1,9 +1,9 @@ # Parámetros CLI soportados (Chrome) -Esta página lista las líneas de comandos usadas por el navegador Chrome que también son +Esta página lista las líneas de comandos usadas por el navegador Chrome que también son soportadas por Electron. Puedes usar [app.commandLine.appendSwitch][append-switch] para anexarlas en el script principal de tu aplicación antes de que el evento [ready][ready] del -modulo [app][app] sea emitido: +módulo [app][app] sea emitido: ```javascript var app = require('app'); @@ -25,7 +25,7 @@ Ignora el límite de conexiones para la lista de `domains` separados por `,`. ## --disable-http-cache -Deshabilita la cacheé del disco para las peticiones HTTP. +Deshabilita la caché del disco para las peticiones HTTP. ## --remote-debugging-port=`port` @@ -42,7 +42,7 @@ Utiliza el script PAC en la `url` especificada. ## --no-proxy-server -No usa un servidor proxy y siempre establece conexiones directas. Anula cualquier +No usa un servidor proxy y siempre establece conexiones directas. Anula cualquier otra bandera de servidor proxy bandera que se pase. ## --host-rules=`rules` @@ -55,12 +55,12 @@ Por ejemplo: * `MAP * 127.0.0.1` Obliga a todos los nombres de host a ser asignados a 127.0.0.1 * `MAP *.google.com proxy` Obliga todos los subdominios google.com a resolverse con "proxy". -* `MAP test.com [::1]:77` Obliga a resolver "test.com" con un bucle invertido de IPv6. +* `MAP test.com [::1]:77` Obliga a resolver "test.com" con un bucle invertido de IPv6. También obligará a que el puerto de la dirección respuesta sea 77. * `MAP * baz, EXCLUDE www.google.com` Reasigna todo a "baz", excepto a "www.google.com". Estas asignaciones especifican el host final en una petición de red (Anfitrión de la conexión TCP -y de resolución de conexión directa, y el `CONNECT` en una conexión proxy HTTP, y el host final de +y de resolución de conexión directa, y el `CONNECT` en una conexión proxy HTTP, y el host final de la conexión proxy `SOCKS`). ## --host-resolver-rules=`rules` @@ -89,30 +89,30 @@ Permite guardar y escribir eventos de registros de red en `path`. ## --ssl-version-fallback-min=`version` -Establece la versión mínima de SSL/TLS ("tls1", "tls1.1" o "tls1.2") que +Establece la versión mínima de SSL/TLS ("tls1", "tls1.1" o "tls1.2") que el repliegue de TLC aceptará. ## --enable-logging Imprime el registro de Chromium en consola. -Este cambio no puede ser usado en `app.commandLine.appendSwitch` ya que se analiza antes de que la -aplicación del usuario este cargada. +Este cambio no puede ser usado en `app.commandLine.appendSwitch` ya que se analiza antes de que la +aplicación del usuario esté cargada. ## --v=`log_level` -Da el maximo nivel activo de V-logging por defecto; 0 es el predeterminado. Valores positivos +Da el máximo nivel activo de V-logging por defecto; 0 es el predeterminado. Valores positivos son normalmente usados para los niveles de V-logging. Este modificador sólo funciona cuando también se pasa `--enable-logging`. ## --vmodule=`pattern` -Da los niveles máximos de V-logging por módulo para sobreescribir el valor dado por -`--v`. Ej. `my_module=2,foo*=3` cambiaria el nivel de registro para todo el código -el archivos de origen `my_module.*` y `foo*.*`. +Da los niveles máximos de V-logging por módulo para sobreescribir el valor dado por +`--v`. Ej. `my_module=2,foo*=3` cambiaría el nivel de registro para todo el código, +los archivos de origen `my_module.*` y `foo*.*`. -Cualquier patron que contiene un slash o un slash invertido será probado contra toda la ruta +Cualquier patrón que contiene un slash o un slash invertido será probado contra toda la ruta y no sólo con el módulo. Ej. `*/foo/bar/*=2` cambiaría el nivel de registro para todo el código en los archivos origen bajo un directorio `foo/bar`. diff --git a/docs-translations/es/api/process.md b/docs-translations/es/api/process.md index e03ef5fa1c..9e95ba9885 100644 --- a/docs-translations/es/api/process.md +++ b/docs-translations/es/api/process.md @@ -1,7 +1,7 @@ # process -El objeto `process` en Electron tiene las siguientes diferencias con respecto -al node convencional: +El objeto `process` en Electron tiene las siguientes diferencias con respecto +al node convencional: * `process.type` String - El tipo del proceso puede ser `browser` (ej. proceso principal) o `renderer`. @@ -16,7 +16,7 @@ al node convencional: Se emite cuando Electron ha cargado su script de inicialización interna y está comenzando a cargar la página web o el script principal. -Puede ser usado por el script precargado para añadir de nuevo los símbolos globales +Puede ser usado por el script precargado para añadir de nuevo los símbolos globales de Node eliminados, al alcance global cuando la integración de Node está apagada: ```js @@ -43,5 +43,5 @@ Interrumpe el hilo principal del proceso actual. * `maxDescriptors` Integer Establece el límite dinámico del descriptor del archivo en `maxDescriptors` -o en el límite estricto del Sistema Operativo, el que sea menor para el +o en el límite estricto del Sistema Operativo, el que sea menor para el proceso actual. diff --git a/docs-translations/es/api/synopsis.md b/docs-translations/es/api/synopsis.md index 0da368dea4..eb4fcb39f6 100644 --- a/docs-translations/es/api/synopsis.md +++ b/docs-translations/es/api/synopsis.md @@ -4,14 +4,14 @@ Todos los [Módulos integrados de Node.js](http://nodejs.org/api/) se encuentran disponibles en Electron y módulos de terceros son támbien totalmente compatibles (incluyendo los [módulos nativos](../tutorial/using-native-node-modules.md)). -Electron también provee algunos módulos integrados adicionales para desarrollar +Electron también provee algunos módulos integrados adicionales para desarrollar aplicaciones nativas de escritorio. Algunos módulos sólo se encuentran disponibles -en el proceso principal, algunos sólo en el proceso renderer (pagina web), y +en el proceso principal, algunos sólo en el proceso renderer (página web), y algunos pueden ser usados en ambos procesos. -La regla básica es: Si un módulo es -[GUI](https://es.wikipedia.org/wiki/Interfaz_gráfica_de_usuario) o de bajo nivel, -entonces solo estará disponible en el proceso principal. Necesitas familiarizarte +La regla básica es: Si un módulo es +[GUI](https://es.wikipedia.org/wiki/Interfaz_gráfica_de_usuario) o de bajo nivel, +entonces solo estará disponible en el proceso principal. Necesitas familiarizarte con el concepto de [scripts para proceso principal vs scripts para proceso renderer] (../tutorial/quick-start.md#the-main-process) para ser capaz de usar esos módulos. @@ -29,7 +29,7 @@ app.on('ready', function() { }); ``` -El proceso renderer no es diferente de una página web normal, excepto por la +El proceso renderer no es diferente de una página web normal, excepto por la capacidad extra de utilizar módulos de node: ```html @@ -44,4 +44,4 @@ capacidad extra de utilizar módulos de node: ``` -Para ejecutar tu aplicación, lee [Ejecutar la aplicación](../tutorial/quick-start.md#run-your-app). \ No newline at end of file +Para ejecutar tu aplicación, lee [Ejecutar la aplicación](../tutorial/quick-start.md#run-your-app). diff --git a/docs-translations/es/styleguide.md b/docs-translations/es/styleguide.md index 2e8e7c6f4a..4948035fff 100644 --- a/docs-translations/es/styleguide.md +++ b/docs-translations/es/styleguide.md @@ -15,7 +15,7 @@ Estas son las maneras en las que construimos la documentación de Electron. - Archivos separados por guiones, mas sin embargo, es correcto. - No subtítulos seguidos por otros subtítulos, añadir por lo menos un enunciado de descripción. -- Métodos de cabecera son delimitados con apóstrofes: `codigo`. +- Métodos de cabecera son delimitados con apóstrofes: `código`. - Cabeceras de Eventos son delimitados con 'comillas' simples. - No generar listas de mas de dos niveles (debido al renderizador de Markdown desafortunadamente). @@ -25,7 +25,7 @@ Estas son las maneras en las que construimos la documentación de Electron. - Argumentos opcionales escritos como `function (required[, optional])`. - Argumentos opcionales son denotados cuando se llaman en listas. - Delimitador de línea de 80-columnas. -- Métodos específicos de Plataformas son denotados en italicas seguidas por la cabecera del método. +- Métodos específicos de Plataformas son denotados en itálicas seguidas por la cabecera del método. - ```### `method(foo, bar)` _OS X_``` - Preferir 'en el ___ proceso' en lugar de 'sobre el' @@ -47,7 +47,7 @@ Para agregar otro set (o un set parcial): ## Leyendo la Documentación de Electron -Estos son algunos consejos para entender la syntaxis de la documentación de +Estos son algunos consejos para entender la sintaxis de la documentación de Electron. ### Métodos @@ -67,7 +67,7 @@ El nombre del método es seguido por los argumentos que recibe. Argumentos opcionales son denotados por corchetes rodeados por el argumento opcional y la coma requerida si el argumento opcional fuera seguido por otro argumento. -Debajo del método se encuentra mas información detallada de cada uno de los +Debajo del método se encuentra más información detallada de cada uno de los argumentos. El tipo de argumento es denotado por los tipos comúnes: [`String`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String), [`Number`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number), @@ -90,7 +90,7 @@ Returns: --- El evento es una cadena que es utilizada luego de un método observador `.on`. Si -regresa un valor, el y su tipo son denotados abajo. Si se estaba a la escucha y +regresa un valor, él y su tipo son denotados abajo. Si se estaba a la escucha y respondió a este evento se debería ver así: ```javascript From e5094fff3eb06b9feae8ac5e6bf22b7cffe90b87 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Tue, 20 Oct 2015 17:27:28 -0700 Subject: [PATCH 077/146] Since setting up process_singleton is now conditional, its cleanup / unlock must be too --- atom/browser/browser.cc | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/atom/browser/browser.cc b/atom/browser/browser.cc index 4308395eb7..483e58a988 100644 --- a/atom/browser/browser.cc +++ b/atom/browser/browser.cc @@ -117,7 +117,11 @@ void Browser::WillFinishLaunching() { void Browser::DidFinishLaunching() { is_ready_ = true; - process_singleton_->Unlock(); + + if (process_notify_callback_set_) { + process_singleton_->Unlock(); + } + FOR_EACH_OBSERVER(BrowserObserver, observers_, OnFinishLaunching()); } @@ -144,7 +148,10 @@ void Browser::NotifyAndShutdown() { return; } - process_singleton_->Cleanup(); + if (process_notify_callback_set_) { + process_singleton_->Cleanup(); + } + Shutdown(); } @@ -167,6 +174,10 @@ void Browser::InitializeSingleInstance() { base::Bind(&Browser::OnProcessSingletonNotification, no_refcount_this))); process_notify_result_ = process_singleton_->NotifyOtherProcessOrCreate(); + + if (is_ready_) { + process_singleton_->Unlock(); + } } ProcessSingleton::NotifyResult Browser::GetSingleInstanceResult() { From 99c496471b665ac81e42e71603f14b87cfc6b637 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Tue, 20 Oct 2015 17:36:05 -0700 Subject: [PATCH 078/146] Allow WM_COPYDATA from all processes --- chromium_src/chrome/browser/process_singleton_win.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/chromium_src/chrome/browser/process_singleton_win.cc b/chromium_src/chrome/browser/process_singleton_win.cc index b1008c3760..0007b713b1 100644 --- a/chromium_src/chrome/browser/process_singleton_win.cc +++ b/chromium_src/chrome/browser/process_singleton_win.cc @@ -293,6 +293,11 @@ bool ProcessSingleton::Create() { bool result = window_.CreateNamed( base::Bind(&ProcessLaunchNotification, notification_callback_), user_data_dir_.value()); + + // NB: Ensure that if the primary app gets started as elevated + // admin inadvertently, secondary windows running not as elevated + // will still be able to send messages + ::ChangeWindowMessageFilterEx(window_.hwnd(), WM_COPYDATA, MSGFLT_ALLOW, NULL); CHECK(result && window_.hwnd()); } } From d092c6acc956fd5b2c4e1431f9a25dbac17e681b Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 21 Oct 2015 07:33:43 +0800 Subject: [PATCH 079/146] osx: Implement draggable region with mouseDownCanMoveWindow Previously we implemented draggable region by tracking mouse position, it is buggy and causing some problems. But it is interesting that until this didn't cause troubles until recently. --- atom/browser/native_window.cc | 34 +++---- atom/browser/native_window.h | 13 ++- atom/browser/native_window_mac.h | 25 ++--- atom/browser/native_window_mac.mm | 148 ++++++++++++++++-------------- 4 files changed, 116 insertions(+), 104 deletions(-) diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index 7f30aa0038..8f3af83885 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -43,26 +43,6 @@ DEFINE_WEB_CONTENTS_USER_DATA_KEY(atom::NativeWindowRelay); namespace atom { -namespace { - -// Convert draggable regions in raw format to SkRegion format. Caller is -// responsible for deleting the returned SkRegion instance. -scoped_ptr DraggableRegionsToSkRegion( - const std::vector& regions) { - scoped_ptr sk_region(new SkRegion); - for (const DraggableRegion& region : regions) { - sk_region->op( - region.bounds.x(), - region.bounds.y(), - region.bounds.right(), - region.bounds.bottom(), - region.draggable ? SkRegion::kUnion_Op : SkRegion::kDifference_Op); - } - return sk_region.Pass(); -} - -} // namespace - NativeWindow::NativeWindow( brightray::InspectableWebContents* inspectable_web_contents, const mate::Dictionary& options) @@ -480,6 +460,20 @@ void NativeWindow::NotifyWindowExecuteWindowsCommand( OnExecuteWindowsCommand(command)); } +scoped_ptr NativeWindow::DraggableRegionsToSkRegion( + const std::vector& regions) { + scoped_ptr sk_region(new SkRegion); + for (const DraggableRegion& region : regions) { + sk_region->op( + region.bounds.x(), + region.bounds.y(), + region.bounds.right(), + region.bounds.bottom(), + region.draggable ? SkRegion::kUnion_Op : SkRegion::kDifference_Op); + } + return sk_region.Pass(); +} + void NativeWindow::RenderViewCreated( content::RenderViewHost* render_view_host) { if (!transparent_) diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index 379c23837b..67bac88f32 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -241,10 +241,19 @@ class NativeWindow : public base::SupportsUserData, NativeWindow(brightray::InspectableWebContents* inspectable_web_contents, const mate::Dictionary& options); + // Convert draggable regions in raw format to SkRegion format. Caller is + // responsible for deleting the returned SkRegion instance. + scoped_ptr DraggableRegionsToSkRegion( + const std::vector& regions); + // Converts between content size to window size. virtual gfx::Size ContentSizeToWindowSize(const gfx::Size& size) = 0; virtual gfx::Size WindowSizeToContentSize(const gfx::Size& size) = 0; + // Called when the window needs to update its draggable region. + virtual void UpdateDraggableRegions( + const std::vector& regions); + // content::WebContentsObserver: void RenderViewCreated(content::RenderViewHost* render_view_host) override; void BeforeUnloadDialogCancelled() override; @@ -252,10 +261,6 @@ class NativeWindow : public base::SupportsUserData, bool OnMessageReceived(const IPC::Message& message) override; private: - // Called when the window needs to update its draggable region. - void UpdateDraggableRegions( - const std::vector& regions); - // Schedule a notification unresponsive event. void ScheduleUnresponsiveEvent(int ms); diff --git a/atom/browser/native_window_mac.h b/atom/browser/native_window_mac.h index 60581a2d97..7a97032d75 100644 --- a/atom/browser/native_window_mac.h +++ b/atom/browser/native_window_mac.h @@ -71,12 +71,10 @@ class NativeWindowMac : public NativeWindow { void SetVisibleOnAllWorkspaces(bool visible) override; bool IsVisibleOnAllWorkspaces() override; - // Returns true if |point| in local Cocoa coordinate system falls within - // the draggable region. - bool IsWithinDraggableRegion(NSPoint point) const; - - // Called to handle a mouse event. - void HandleMouseEvent(NSEvent* event); + // Refresh the DraggableRegion views. + void UpdateDraggableRegionViews() { + UpdateDraggableRegionViews(draggable_regions_); + } protected: // NativeWindow: @@ -84,17 +82,24 @@ class NativeWindowMac : public NativeWindow { content::WebContents*, const content::NativeWebKeyboardEvent&) override; + // Return a vector of non-draggable regions that fill a window of size + // |width| by |height|, but leave gaps where the window should be draggable. + std::vector CalculateNonDraggableRegions( + const std::vector& regions, int width, int height); + private: // NativeWindow: gfx::Size ContentSizeToWindowSize(const gfx::Size& size) override; gfx::Size WindowSizeToContentSize(const gfx::Size& size) override; + void UpdateDraggableRegions( + const std::vector& regions) override; void InstallView(); void UninstallView(); // Install the drag view, which will cover the whole window and decides // whehter we can drag. - void InstallDraggableRegionView(); + void UpdateDraggableRegionViews(const std::vector& regions); base::scoped_nsobject window_; base::scoped_nsobject window_delegate_; @@ -102,6 +107,8 @@ class NativeWindowMac : public NativeWindow { // The view that will fill the whole frameless window. base::scoped_nsobject content_view_; + std::vector draggable_regions_; + bool is_kiosk_; NSInteger attention_request_id_; // identifier from requestUserAttention @@ -109,10 +116,6 @@ class NativeWindowMac : public NativeWindow { // The presentation options before entering kiosk mode. NSApplicationPresentationOptions kiosk_options_; - // Mouse location since the last mouse event, in screen coordinates. This is - // used in custom drag to compute the window movement. - NSPoint last_mouse_offset_; - DISALLOW_COPY_AND_ASSIGN(NativeWindowMac); }; diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 6420345da9..9724930c48 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -19,6 +19,7 @@ #include "content/public/browser/render_view_host.h" #include "content/public/browser/render_widget_host_view.h" #include "native_mate/dictionary.h" +#include "ui/gfx/skia_util.h" namespace { @@ -146,6 +147,7 @@ bool ScopedDisableResize::disable_resize_ = false; } - (void)windowDidResize:(NSNotification*)notification { + shell_->UpdateDraggableRegionViews(); shell_->NotifyWindowResize(); } @@ -257,43 +259,23 @@ bool ScopedDisableResize::disable_resize_ = false; @end -@interface ControlRegionView : NSView { - @private - atom::NativeWindowMac* shellWindow_; // Weak; owns self. -} +@interface ControlRegionView : NSView @end @implementation ControlRegionView -- (id)initWithShellWindow:(atom::NativeWindowMac*)shellWindow { - if ((self = [super init])) - shellWindow_ = shellWindow; - return self; -} - - (BOOL)mouseDownCanMoveWindow { return NO; } - (NSView*)hitTest:(NSPoint)aPoint { - if (!shellWindow_->IsWithinDraggableRegion(aPoint)) { - return nil; - } - return self; + return nil; } -- (void)mouseDown:(NSEvent*)event { - shellWindow_->HandleMouseEvent(event); -} - -- (void)mouseDragged:(NSEvent*)event { - shellWindow_->HandleMouseEvent(event); -} - -- (BOOL)acceptsFirstMouse:(NSEvent*)event { - return YES; -} +@end +@interface NSView (WebContentsView) +- (void)setMouseDownCanMoveWindow:(BOOL)can_move; @end @interface AtomProgressBar : NSProgressIndicator @@ -439,11 +421,6 @@ NativeWindowMac::NativeWindowMac( [view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; InstallView(); - - // Install the DraggableRegionView if it is forced to use draggable regions - // for normal window. - if (has_frame() && force_using_draggable_region()) - InstallDraggableRegionView(); } NativeWindowMac::~NativeWindowMac() { @@ -746,36 +723,6 @@ bool NativeWindowMac::IsVisibleOnAllWorkspaces() { return collectionBehavior & NSWindowCollectionBehaviorCanJoinAllSpaces; } -bool NativeWindowMac::IsWithinDraggableRegion(NSPoint point) const { - if (!draggable_region()) - return false; - if (!web_contents()) - return false; - NSView* webView = web_contents()->GetNativeView(); - NSInteger webViewHeight = NSHeight([webView bounds]); - // |draggable_region_| is stored in local platform-indepdent coordiate system - // while |point| is in local Cocoa coordinate system. Do the conversion - // to match these two. - return draggable_region()->contains(point.x, webViewHeight - point.y); -} - -void NativeWindowMac::HandleMouseEvent(NSEvent* event) { - NSPoint eventLoc = [event locationInWindow]; - NSRect mouseRect = [window_ convertRectToScreen:NSMakeRect(eventLoc.x, eventLoc.y, 0, 0)]; - NSPoint current_mouse_location = mouseRect.origin; - - if ([event type] == NSLeftMouseDown) { - NSPoint frame_origin = [window_ frame].origin; - last_mouse_offset_ = NSMakePoint( - frame_origin.x - current_mouse_location.x, - frame_origin.y - current_mouse_location.y); - } else if ([event type] == NSLeftMouseDragged) { - [window_ setFrameOrigin:NSMakePoint( - current_mouse_location.x + last_mouse_offset_.x, - current_mouse_location.y + last_mouse_offset_.y)]; - } -} - void NativeWindowMac::HandleKeyboardEvent( content::WebContents*, const content::NativeWebKeyboardEvent& event) { @@ -800,6 +747,23 @@ void NativeWindowMac::HandleKeyboardEvent( } } +std::vector NativeWindowMac::CalculateNonDraggableRegions( + const std::vector& regions, int width, int height) { + std::vector result; + if (regions.empty()) { + result.push_back(gfx::Rect(0, 0, width, height)); + } else { + scoped_ptr draggable(DraggableRegionsToSkRegion(regions)); + scoped_ptr non_draggable(new SkRegion); + non_draggable->op(0, 0, width, height, SkRegion::kUnion_Op); + non_draggable->op(*draggable, SkRegion::kDifference_Op); + for (SkRegion::Iterator it(*non_draggable); !it.done(); it.next()) { + result.push_back(gfx::SkIRectToRect(it.rect())); + } + } + return result; +} + gfx::Size NativeWindowMac::ContentSizeToWindowSize(const gfx::Size& size) { if (!has_frame()) return size; @@ -818,6 +782,13 @@ gfx::Size NativeWindowMac::WindowSizeToContentSize(const gfx::Size& size) { return gfx::Size(content.size); } +void NativeWindowMac::UpdateDraggableRegions( + const std::vector& regions) { + NativeWindow::UpdateDraggableRegions(regions); + draggable_regions_ = regions; + UpdateDraggableRegionViews(regions); +} + void NativeWindowMac::InstallView() { // Make sure the bottom corner is rounded: http://crbug.com/396264. [[window_ contentView] setWantsLayer:YES]; @@ -840,8 +811,6 @@ void NativeWindowMac::InstallView() { [view setFrame:[content_view_ bounds]]; [content_view_ addSubview:view]; - InstallDraggableRegionView(); - [[window_ standardWindowButton:NSWindowZoomButton] setHidden:YES]; [[window_ standardWindowButton:NSWindowMiniaturizeButton] setHidden:YES]; [[window_ standardWindowButton:NSWindowCloseButton] setHidden:YES]; @@ -858,14 +827,55 @@ void NativeWindowMac::UninstallView() { [view removeFromSuperview]; } -void NativeWindowMac::InstallDraggableRegionView() { +void NativeWindowMac::UpdateDraggableRegionViews( + const std::vector& regions) { + if (has_frame() && !force_using_draggable_region()) + return; + + // All ControlRegionViews should be added as children of the WebContentsView, + // because WebContentsView will be removed and re-added when entering and + // leaving fullscreen mode. NSView* webView = web_contents()->GetNativeView(); - base::scoped_nsobject controlRegion( - [[ControlRegionView alloc] initWithShellWindow:this]); - [controlRegion setFrame:NSMakeRect(0, 0, - NSWidth([webView bounds]), - NSHeight([webView bounds]))]; - [webView addSubview:controlRegion]; + NSInteger webViewWidth = NSWidth([webView bounds]); + NSInteger webViewHeight = NSHeight([webView bounds]); + + [webView setMouseDownCanMoveWindow:YES]; + + // Remove all ControlRegionViews that are added last time. + // Note that [webView subviews] returns the view's mutable internal array and + // it should be copied to avoid mutating the original array while enumerating + // it. + base::scoped_nsobject subviews([[webView subviews] copy]); + for (NSView* subview in subviews.get()) + if ([subview isKindOfClass:[ControlRegionView class]]) + [subview removeFromSuperview]; + + // Draggable regions is implemented by having the whole web view draggable + // (mouseDownCanMoveWindow) and overlaying regions that are not draggable. + std::vector system_drag_exclude_areas = + CalculateNonDraggableRegions(regions, webViewWidth, webViewHeight); + + // Create and add a ControlRegionView for each region that needs to be + // excluded from the dragging. + for (std::vector::const_iterator iter = + system_drag_exclude_areas.begin(); + iter != system_drag_exclude_areas.end(); + ++iter) { + base::scoped_nsobject controlRegion( + [[ControlRegionView alloc] initWithFrame:NSZeroRect]); + [controlRegion setFrame:NSMakeRect(iter->x(), + webViewHeight - iter->bottom(), + iter->width(), + iter->height())]; + [webView addSubview:controlRegion]; + } + + // AppKit will not update its cache of mouseDownCanMoveWindow unless something + // changes. Previously we tried adding an NSView and removing it, but for some + // reason it required reposting the mouse-down event, and didn't always work. + // Calling the below seems to be an effective solution. + [window_ setMovableByWindowBackground:NO]; + [window_ setMovableByWindowBackground:YES]; } // static From 2d410ede4890551240f7745a967c67213ca29b3e Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 21 Oct 2015 07:34:15 +0800 Subject: [PATCH 080/146] Initialize things in the same sequence with main process --- atom/app/atom_library_main.mm | 2 ++ atom/app/node_main.cc | 17 +++++++---------- atom/browser/javascript_environment.cc | 1 - vendor/brightray | 2 +- vendor/node | 2 +- 5 files changed, 11 insertions(+), 13 deletions(-) diff --git a/atom/app/atom_library_main.mm b/atom/app/atom_library_main.mm index 885500beb5..7ee7522934 100644 --- a/atom/app/atom_library_main.mm +++ b/atom/app/atom_library_main.mm @@ -10,6 +10,7 @@ #include "base/at_exit.h" #include "base/i18n/icu_util.h" #include "base/mac/bundle_locations.h" +#include "base/mac/scoped_nsautorelease_pool.h" #include "brightray/common/mac/main_application_bundle.h" #include "content/public/app/content_main.h" @@ -25,6 +26,7 @@ int AtomMain(int argc, const char* argv[]) { int AtomInitializeICUandStartNode(int argc, char *argv[]) { base::AtExitManager atexit_manager; + base::mac::ScopedNSAutoreleasePool pool; base::mac::SetOverrideFrameworkBundlePath( brightray::MainApplicationBundlePath() .Append("Contents") diff --git a/atom/app/node_main.cc b/atom/app/node_main.cc index 7efb1b40ee..b946ae28ff 100644 --- a/atom/app/node_main.cc +++ b/atom/app/node_main.cc @@ -7,8 +7,8 @@ #include "atom/app/uv_task_runner.h" #include "atom/browser/javascript_environment.h" #include "atom/browser/node_debugger.h" -#include "atom/common/node_includes.h" #include "base/command_line.h" +#include "atom/common/node_includes.h" #include "base/thread_task_runner_handle.h" #include "gin/array_buffer.h" #include "gin/public/isolate_holder.h" @@ -19,25 +19,22 @@ namespace atom { int NodeMain(int argc, char *argv[]) { base::CommandLine::Init(argc, argv); - argv = uv_setup_args(argc, argv); - int exec_argc; - const char** exec_argv; - node::Init(&argc, const_cast(argv), &exec_argc, &exec_argv); - int exit_code = 1; { // Feed gin::PerIsolateData with a task runner. + argv = uv_setup_args(argc, argv); uv_loop_t* loop = uv_default_loop(); scoped_refptr uv_task_runner(new UvTaskRunner(loop)); base::ThreadTaskRunnerHandle handle(uv_task_runner); gin::V8Initializer::LoadV8Snapshot(); gin::V8Initializer::LoadV8Natives(); - gin::IsolateHolder::Initialize( - gin::IsolateHolder::kNonStrictMode, - gin::ArrayBufferAllocator::SharedInstance()); - JavascriptEnvironment gin_env; + + int exec_argc; + const char** exec_argv; + node::Init(&argc, const_cast(argv), &exec_argc, &exec_argv); + node::Environment* env = node::CreateEnvironment( gin_env.isolate(), loop, gin_env.context(), argc, argv, exec_argc, exec_argv); diff --git a/atom/browser/javascript_environment.cc b/atom/browser/javascript_environment.cc index 3788fcad8b..4e825e3814 100644 --- a/atom/browser/javascript_environment.cc +++ b/atom/browser/javascript_environment.cc @@ -20,7 +20,6 @@ JavascriptEnvironment::JavascriptEnvironment() } bool JavascriptEnvironment::Initialize() { - gin::V8Initializer::LoadV8Snapshot(); gin::IsolateHolder::Initialize(gin::IsolateHolder::kNonStrictMode, gin::ArrayBufferAllocator::SharedInstance()); return true; diff --git a/vendor/brightray b/vendor/brightray index f1cbfd1d45..49a86c123f 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit f1cbfd1d457f9b40bca23a2f30948a093d6048a9 +Subproject commit 49a86c123f4cc43f4dca886ded612104a8a1fec6 diff --git a/vendor/node b/vendor/node index f4243f5c84..edfbc29d09 160000 --- a/vendor/node +++ b/vendor/node @@ -1 +1 @@ -Subproject commit f4243f5c84a371632d8d72a1a2210a0e994afdcc +Subproject commit edfbc29d09425f2f387c52d77f6351b6ce101659 From 444f461269f77c67d4ce1bb6009baba15d98b71e Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 21 Oct 2015 20:46:16 +0800 Subject: [PATCH 081/146] Fix memory corruption when cleaning timer --- atom/app/uv_task_runner.cc | 9 +++++++-- atom/app/uv_task_runner.h | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/atom/app/uv_task_runner.cc b/atom/app/uv_task_runner.cc index 23463e44e2..f49ba259ba 100644 --- a/atom/app/uv_task_runner.cc +++ b/atom/app/uv_task_runner.cc @@ -48,8 +48,13 @@ void UvTaskRunner::OnTimeout(uv_timer_t* timer) { self->tasks_[timer].Run(); self->tasks_.erase(timer); - uv_unref(reinterpret_cast(timer)); - delete timer; + uv_timer_stop(timer); + uv_close(reinterpret_cast(timer), UvTaskRunner::OnClose); +} + +// static +void UvTaskRunner::OnClose(uv_handle_t* handle) { + delete reinterpret_cast(handle); } } // namespace atom diff --git a/atom/app/uv_task_runner.h b/atom/app/uv_task_runner.h index e4eac01553..c7302766c2 100644 --- a/atom/app/uv_task_runner.h +++ b/atom/app/uv_task_runner.h @@ -31,6 +31,7 @@ class UvTaskRunner : public base::SingleThreadTaskRunner { private: static void OnTimeout(uv_timer_t* timer); + static void OnClose(uv_handle_t* handle); uv_loop_t* loop_; From 38d6ff79c806a8ce7e3da6770b058eeb11a384a2 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Wed, 21 Oct 2015 11:06:42 -0700 Subject: [PATCH 082/146] Need to free this early or get a SIGSEGV on Linux --- atom/browser/browser.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/atom/browser/browser.cc b/atom/browser/browser.cc index 483e58a988..c8d6104bea 100644 --- a/atom/browser/browser.cc +++ b/atom/browser/browser.cc @@ -150,6 +150,7 @@ void Browser::NotifyAndShutdown() { if (process_notify_callback_set_) { process_singleton_->Cleanup(); + process_notify_callback_.Reset(); } Shutdown(); From acbd7d7541a76d5496fcaa2049c1c9b0334a6ee3 Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Thu, 22 Oct 2015 03:36:14 +0900 Subject: [PATCH 083/146] Update as upstream --- docs-translations/ko-KR/api/app.md | 4 ++++ docs-translations/ko-KR/tutorial/quick-start.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs-translations/ko-KR/api/app.md b/docs-translations/ko-KR/api/app.md index c364d9dc60..498fbb59bf 100644 --- a/docs-translations/ko-KR/api/app.md +++ b/docs-translations/ko-KR/api/app.md @@ -284,6 +284,10 @@ Windows에서 사용할 수 있는 JumpList의 [Tasks][tasks] 카테고리에 `t 항상 동적으로 HTTP NTLM 또는 Negotiate 인증에 자격 증명을 보낼 것인지 설정합니다. +기본적으로 Electron은 "로컬 인터넷" 사이트 URL에서 NTLM/Kerberos 자격 증명만을 보냅니다. (같은 도메인 내에서) +그러나 기업 네트워크가 잘못 구성된 경우 종종 작업에 실패할 수 있습니다. +이때 이 메서드를 통해 모든 URL을 허용할 수 있습니다. + ### `app.commandLine.appendSwitch(switch[, value])` Chrominum의 명령줄에 스위치를 추가합니다. `value`는 추가적인 값을 뜻하며 옵션입니다. diff --git a/docs-translations/ko-KR/tutorial/quick-start.md b/docs-translations/ko-KR/tutorial/quick-start.md index ce3c7e29e0..5c2cc80f40 100644 --- a/docs-translations/ko-KR/tutorial/quick-start.md +++ b/docs-translations/ko-KR/tutorial/quick-start.md @@ -93,7 +93,7 @@ app.on('ready', function() { mainWindow.loadUrl('file://' + __dirname + '/index.html'); // 개발자 콘솔을 엽니다. - mainWindow.openDevTools(); + mainWindow.webContents.openDevTools(); // 창이 닫히면 호출됩니다. mainWindow.on('closed', function() { From a160891a27ddfa7ad68860ed45a61300f2ef2c5e Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Wed, 21 Oct 2015 12:29:00 -0700 Subject: [PATCH 084/146] If a user calls makeSingleInstance more than once, just ignore it --- atom/browser/api/atom_api_app.cc | 14 +++++++------- atom/browser/browser.cc | 7 ++++--- atom/browser/browser.h | 2 +- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index b16e346065..96f1ce0253 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -271,15 +271,15 @@ v8::Local App::DefaultSession(v8::Isolate* isolate) { bool App::MakeSingleInstance(v8::Local callback) { auto browser = Browser::Get(); - single_instance_callback_ = callback; + if (browser->InitializeSingleInstance()) { + single_instance_callback_ = callback; - browser->InitializeSingleInstance(); + ProcessSingleton::NotificationCallback cb; + mate::Converter::FromV8( + isolate(), single_instance_callback_, &cb); - ProcessSingleton::NotificationCallback cb; - mate::Converter::FromV8( - isolate(), single_instance_callback_, &cb); - - browser->SetSingleInstanceCallback(cb); + browser->SetSingleInstanceCallback(cb); + } switch (browser->GetSingleInstanceResult()) { case ProcessSingleton::NotifyResult::PROCESS_NONE: diff --git a/atom/browser/browser.cc b/atom/browser/browser.cc index c8d6104bea..6bfeae3c56 100644 --- a/atom/browser/browser.cc +++ b/atom/browser/browser.cc @@ -165,7 +165,7 @@ bool Browser::HandleBeforeQuit() { return !prevent_default; } -void Browser::InitializeSingleInstance() { +bool Browser::InitializeSingleInstance() { base::FilePath userDir; PathService::Get(brightray::DIR_USER_DATA, &userDir); @@ -174,11 +174,12 @@ void Browser::InitializeSingleInstance() { userDir, base::Bind(&Browser::OnProcessSingletonNotification, no_refcount_this))); - process_notify_result_ = process_singleton_->NotifyOtherProcessOrCreate(); - if (is_ready_) { process_singleton_->Unlock(); } + + process_notify_result_ = process_singleton_->NotifyOtherProcessOrCreate(); + return true; } ProcessSingleton::NotifyResult Browser::GetSingleInstanceResult() { diff --git a/atom/browser/browser.h b/atom/browser/browser.h index 1ac4d03e09..f9b86b6924 100644 --- a/atom/browser/browser.h +++ b/atom/browser/browser.h @@ -66,7 +66,7 @@ class Browser : public WindowListObserver { // Clear the recent documents list. void ClearRecentDocuments(); - void InitializeSingleInstance(); + bool InitializeSingleInstance(); ProcessSingleton::NotifyResult GetSingleInstanceResult(); void SetSingleInstanceCallback( ProcessSingleton::NotificationCallback callback); From 658a9872fb681c331f154cadc508380959e36df3 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Wed, 21 Oct 2015 12:29:21 -0700 Subject: [PATCH 085/146] Only cleanup if we are the main process --- atom/browser/browser.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/atom/browser/browser.cc b/atom/browser/browser.cc index 6bfeae3c56..6d39cdcc8b 100644 --- a/atom/browser/browser.cc +++ b/atom/browser/browser.cc @@ -149,8 +149,11 @@ void Browser::NotifyAndShutdown() { } if (process_notify_callback_set_) { - process_singleton_->Cleanup(); process_notify_callback_.Reset(); + + if (process_notify_result_ == ProcessSingleton::PROCESS_NONE) { + process_singleton_->Cleanup(); + } } Shutdown(); From b61c2e6ff7333f172dca600ee8bbd419cf00f9f6 Mon Sep 17 00:00:00 2001 From: Thomas J Fox Date: Wed, 21 Oct 2015 15:03:12 -0500 Subject: [PATCH 086/146] quick fix removing excess character --- .../mac-app-store-submission-guide.md | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/tutorial/mac-app-store-submission-guide.md b/docs/tutorial/mac-app-store-submission-guide.md index fb04a5573b..1131859188 100644 --- a/docs/tutorial/mac-app-store-submission-guide.md +++ b/docs/tutorial/mac-app-store-submission-guide.md @@ -1,26 +1,26 @@ # Mac App Store Submission Guide -Since v0.34.0, Electron allows submitting packaged apps to the Mac App Store -(MAS). This guide provides information on: how to submit your app and the +Since v0.34.0, Electron allows submitting packaged apps to the Mac App Store +(MAS). This guide provides information on: how to submit your app and the limitations of the MAS build. ## How to Submit Your App -The following steps introduce a simple way to submit your app to Mac App Store. -However, these steps do not ensure sure your app will be approved by Apple; you -still need to read Apple's [Submitting Your App][submitting-your-app] guide on +The following steps introduce a simple way to submit your app to Mac App Store. +However, these steps do not ensure sure your app will be approved by Apple; you +still need to read Apple's [Submitting Your App][submitting-your-app] guide on how to meet the Mac App Store requirements. ### Get Certificate -To submit your app to the Mac App Store, you first must get a certificate from +To submit your app to the Mac App Store, you first must get a certificate from Apple. You can follow these [existing guides][nwjs-guide] on web. ### Sign Your App After getting the certificate from Apple, you can package your app by following -[Application Distribution](application-distribution.md), and then proceed to -signing your app. This step is basically the same with other programs, but the +[Application Distribution](application-distribution.md), and then proceed to +signing your app. This step is basically the same with other programs, but the key is to sign every dependency of Electron one by one. First, you need to prepare two entitlements files. @@ -79,9 +79,9 @@ codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP codesign -fs "$APP_KEY" --entitlements parent.plist "$APP_PATH" productbuild --component "$APP_PATH" /Applications --sign "$INSTALLER_KEY" "$APP_PATH" ``` -a + If you are new to app sandboxing under OS X, you should also read through -Apple's [Enabling App Sandbox][enable-app-sandbox] to have a basic idea, then +Apple's [Enabling App Sandbox][enable-app-sandbox] to have a basic idea, then add keys for the permissions needed by your app to the entitlements files. ### Upload Your App and Submit for Review @@ -92,7 +92,7 @@ before uploading. Then you can [submit your app for review][submit-for-review]. ## Limitations of MAS Build -In order to satisfy all requirements for app sandboxing, the following modules +In order to satisfy all requirements for app sandboxing, the following modules have been disabled in the MAS build: * `crash-reporter` From 938d68eb3607140313f4048dfc87be3075c7e1c6 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Wed, 21 Oct 2015 13:04:50 -0700 Subject: [PATCH 087/146] Move all the browser.cc code over to atom_api_app.cc --- atom/browser/api/atom_api_app.cc | 40 ++++++++++++++++------- atom/browser/api/atom_api_app.h | 7 ++-- atom/browser/browser.cc | 56 +------------------------------- atom/browser/browser.h | 16 --------- 4 files changed, 34 insertions(+), 85 deletions(-) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 96f1ce0253..97d5cf6aa2 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -161,6 +161,14 @@ void App::OnWindowAllClosed() { void App::OnQuit() { Emit("quit"); + + if (process_singleton_.get()) { + if (process_notify_result_ == ProcessSingleton::PROCESS_NONE) { + process_singleton_->Cleanup(); + } + + process_singleton_.reset(); + } } void App::OnOpenFile(bool* prevent_default, const std::string& file_path) { @@ -187,6 +195,10 @@ void App::OnFinishLaunching() { AtomBrowserMainParts::Get()->browser_context()); auto handle = Session::CreateFrom(isolate(), browser_context); default_session_.Reset(isolate(), handle.ToV8()); + + if (process_singleton_.get()) { + process_singleton_->Unlock(); + } Emit("ready"); } @@ -269,28 +281,32 @@ v8::Local App::DefaultSession(v8::Isolate* isolate) { return v8::Local::New(isolate, default_session_); } -bool App::MakeSingleInstance(v8::Local callback) { - auto browser = Browser::Get(); - if (browser->InitializeSingleInstance()) { - single_instance_callback_ = callback; +bool App::MakeSingleInstance(ProcessSingleton::NotificationCallback callback) { + + base::FilePath userDir; + PathService::Get(brightray::DIR_USER_DATA, &userDir); + + if (!process_singleton_.get()) { + auto browser = Browser::Get(); + process_singleton_.reset(new AtomProcessSingleton(userDir, callback)); + + if (browser->is_ready()) { + process_singleton_->Unlock(); + } - ProcessSingleton::NotificationCallback cb; - mate::Converter::FromV8( - isolate(), single_instance_callback_, &cb); - - browser->SetSingleInstanceCallback(cb); + process_notify_result_ = process_singleton_->NotifyOtherProcessOrCreate(); } - switch (browser->GetSingleInstanceResult()) { + switch (process_notify_result_) { case ProcessSingleton::NotifyResult::PROCESS_NONE: return false; case ProcessSingleton::NotifyResult::LOCK_ERROR: case ProcessSingleton::NotifyResult::PROFILE_IN_USE: case ProcessSingleton::NotifyResult::PROCESS_NOTIFIED: return true; + default: + return false; } - - return false; } mate::ObjectTemplateBuilder App::GetObjectTemplateBuilder( diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index b10b40e5c9..82ae045020 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -8,6 +8,7 @@ #include #include "atom/browser/api/event_emitter.h" +#include "atom/browser/atom_process_singleton.h" #include "atom/browser/browser_observer.h" #include "atom/common/native_mate_converters/callback.h" #include "chrome/browser/process_singleton.h" @@ -70,13 +71,15 @@ class App : public mate::EventEmitter, void AllowNTLMCredentialsForAllDomains(bool should_allow); - bool MakeSingleInstance(v8::Local callback); + bool MakeSingleInstance(ProcessSingleton::NotificationCallback callback); std::string GetLocale(); v8::Local DefaultSession(v8::Isolate* isolate); - v8::Local single_instance_callback_; v8::Global default_session_; + + scoped_ptr process_singleton_; + ProcessSingleton::NotifyResult process_notify_result_; DISALLOW_COPY_AND_ASSIGN(App); }; diff --git a/atom/browser/browser.cc b/atom/browser/browser.cc index 6d39cdcc8b..d8bb94103c 100644 --- a/atom/browser/browser.cc +++ b/atom/browser/browser.cc @@ -9,8 +9,6 @@ #include "atom/browser/atom_browser_main_parts.h" #include "atom/browser/window_list.h" #include "base/message_loop/message_loop.h" -#include "base/path_service.h" -#include "brightray/browser/brightray_paths.h" #include "content/public/browser/client_certificate_delegate.h" #include "net/ssl/ssl_cert_request_info.h" @@ -19,8 +17,7 @@ namespace atom { Browser::Browser() : is_quiting_(false), is_ready_(false), - is_shutdown_(false), - process_notify_callback_set_(false) { + is_shutdown_(false) { WindowList::AddObserver(this); } @@ -117,11 +114,6 @@ void Browser::WillFinishLaunching() { void Browser::DidFinishLaunching() { is_ready_ = true; - - if (process_notify_callback_set_) { - process_singleton_->Unlock(); - } - FOR_EACH_OBSERVER(BrowserObserver, observers_, OnFinishLaunching()); } @@ -148,14 +140,6 @@ void Browser::NotifyAndShutdown() { return; } - if (process_notify_callback_set_) { - process_notify_callback_.Reset(); - - if (process_notify_result_ == ProcessSingleton::PROCESS_NONE) { - process_singleton_->Cleanup(); - } - } - Shutdown(); } @@ -168,34 +152,6 @@ bool Browser::HandleBeforeQuit() { return !prevent_default; } -bool Browser::InitializeSingleInstance() { - base::FilePath userDir; - PathService::Get(brightray::DIR_USER_DATA, &userDir); - - auto no_refcount_this = base::Unretained(this); - process_singleton_.reset(new AtomProcessSingleton( - userDir, - base::Bind(&Browser::OnProcessSingletonNotification, no_refcount_this))); - - if (is_ready_) { - process_singleton_->Unlock(); - } - - process_notify_result_ = process_singleton_->NotifyOtherProcessOrCreate(); - return true; -} - -ProcessSingleton::NotifyResult Browser::GetSingleInstanceResult() { - LOG(ERROR) << "Process Notify Result: " << process_notify_result_; - return process_notify_result_; -} - -void Browser::SetSingleInstanceCallback( - ProcessSingleton::NotificationCallback callback) { - process_notify_callback_ = callback; - process_notify_callback_set_ = true; -} - void Browser::OnWindowCloseCancelled(NativeWindow* window) { if (is_quiting_) // Once a beforeunload handler has prevented the closing, we think the quit @@ -210,14 +166,4 @@ void Browser::OnWindowAllClosed() { FOR_EACH_OBSERVER(BrowserObserver, observers_, OnWindowAllClosed()); } -bool Browser::OnProcessSingletonNotification( - const base::CommandLine& command_line, - const base::FilePath& current_directory) { - if (process_notify_callback_set_) { - return process_notify_callback_.Run(command_line, current_directory); - } else { - return true; // We'll handle this, not a different process - } -} - } // namespace atom diff --git a/atom/browser/browser.h b/atom/browser/browser.h index f9b86b6924..3c5abd2f04 100644 --- a/atom/browser/browser.h +++ b/atom/browser/browser.h @@ -9,10 +9,8 @@ #include #include "base/basictypes.h" -#include "base/memory/scoped_ptr.h" #include "base/compiler_specific.h" #include "base/observer_list.h" -#include "atom/browser/atom_process_singleton.h" #include "atom/browser/browser_observer.h" #include "atom/browser/window_list_observer.h" @@ -66,11 +64,6 @@ class Browser : public WindowListObserver { // Clear the recent documents list. void ClearRecentDocuments(); - bool InitializeSingleInstance(); - ProcessSingleton::NotifyResult GetSingleInstanceResult(); - void SetSingleInstanceCallback( - ProcessSingleton::NotificationCallback callback); - #if defined(OS_MACOSX) // Bounce the dock icon. enum BounceType { @@ -160,10 +153,6 @@ class Browser : public WindowListObserver { void OnWindowCloseCancelled(NativeWindow* window) override; void OnWindowAllClosed() override; - bool OnProcessSingletonNotification( - const base::CommandLine& command_line, - const base::FilePath& current_directory); - // Observers of the browser. base::ObserverList observers_; @@ -176,11 +165,6 @@ class Browser : public WindowListObserver { std::string version_override_; std::string name_override_; - scoped_ptr process_singleton_; - ProcessSingleton::NotifyResult process_notify_result_; - ProcessSingleton::NotificationCallback process_notify_callback_; - bool process_notify_callback_set_; - #if defined(OS_WIN) base::string16 app_user_model_id_; #endif From d020a7dc86f7c628ea3a78fe2c0719c4b72bb48e Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Wed, 21 Oct 2015 13:17:56 -0700 Subject: [PATCH 088/146] cpplinting --- atom/browser/api/atom_api_app.cc | 11 +++++------ atom/browser/api/atom_api_app.h | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 97d5cf6aa2..6906608fb7 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -161,12 +161,12 @@ void App::OnWindowAllClosed() { void App::OnQuit() { Emit("quit"); - + if (process_singleton_.get()) { if (process_notify_result_ == ProcessSingleton::PROCESS_NONE) { process_singleton_->Cleanup(); } - + process_singleton_.reset(); } } @@ -195,7 +195,7 @@ void App::OnFinishLaunching() { AtomBrowserMainParts::Get()->browser_context()); auto handle = Session::CreateFrom(isolate(), browser_context); default_session_.Reset(isolate(), handle.ToV8()); - + if (process_singleton_.get()) { process_singleton_->Unlock(); } @@ -282,14 +282,13 @@ v8::Local App::DefaultSession(v8::Isolate* isolate) { } bool App::MakeSingleInstance(ProcessSingleton::NotificationCallback callback) { - base::FilePath userDir; PathService::Get(brightray::DIR_USER_DATA, &userDir); - + if (!process_singleton_.get()) { auto browser = Browser::Get(); process_singleton_.reset(new AtomProcessSingleton(userDir, callback)); - + if (browser->is_ready()) { process_singleton_->Unlock(); } diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index 82ae045020..77944d3441 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -77,7 +77,7 @@ class App : public mate::EventEmitter, v8::Local DefaultSession(v8::Isolate* isolate); v8::Global default_session_; - + scoped_ptr process_singleton_; ProcessSingleton::NotifyResult process_notify_result_; From de668880518525fd2430f00278516681a7b746fa Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Wed, 21 Oct 2015 13:38:32 -0700 Subject: [PATCH 089/146] Remove atom_process_singleton, just use the Chrome classes directly --- atom/browser/api/atom_api_app.cc | 12 ++++-- atom/browser/api/atom_api_app.h | 5 ++- atom/browser/atom_process_singleton.cc | 29 -------------- atom/browser/atom_process_singleton.h | 52 -------------------------- filenames.gypi | 1 - 5 files changed, 12 insertions(+), 87 deletions(-) delete mode 100644 atom/browser/atom_process_singleton.cc delete mode 100644 atom/browser/atom_process_singleton.h diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 6906608fb7..c46f0748e3 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -197,7 +197,7 @@ void App::OnFinishLaunching() { default_session_.Reset(isolate(), handle.ToV8()); if (process_singleton_.get()) { - process_singleton_->Unlock(); + process_singleton_startup_lock_->Unlock(); } Emit("ready"); @@ -287,10 +287,16 @@ bool App::MakeSingleInstance(ProcessSingleton::NotificationCallback callback) { if (!process_singleton_.get()) { auto browser = Browser::Get(); - process_singleton_.reset(new AtomProcessSingleton(userDir, callback)); + process_singleton_startup_lock_.reset( + new ProcessSingletonStartupLock(callback)); + + process_singleton_.reset( + new ProcessSingleton( + userDir, + process_singleton_startup_lock_->AsNotificationCallback())); if (browser->is_ready()) { - process_singleton_->Unlock(); + process_singleton_startup_lock_->Unlock(); } process_notify_result_ = process_singleton_->NotifyOtherProcessOrCreate(); diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index 77944d3441..314eeea881 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -8,10 +8,10 @@ #include #include "atom/browser/api/event_emitter.h" -#include "atom/browser/atom_process_singleton.h" #include "atom/browser/browser_observer.h" #include "atom/common/native_mate_converters/callback.h" #include "chrome/browser/process_singleton.h" +#include "chrome/browser/process_singleton_startup_lock.h" #include "content/public/browser/gpu_data_manager_observer.h" #include "native_mate/handle.h" @@ -78,7 +78,8 @@ class App : public mate::EventEmitter, v8::Global default_session_; - scoped_ptr process_singleton_; + scoped_ptr process_singleton_; + scoped_ptr process_singleton_startup_lock_; ProcessSingleton::NotifyResult process_notify_result_; DISALLOW_COPY_AND_ASSIGN(App); diff --git a/atom/browser/atom_process_singleton.cc b/atom/browser/atom_process_singleton.cc deleted file mode 100644 index e9e4dc727b..0000000000 --- a/atom/browser/atom_process_singleton.cc +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "atom/browser/atom_process_singleton.h" - -AtomProcessSingleton::AtomProcessSingleton( - const base::FilePath& user_data_dir, - const ProcessSingleton::NotificationCallback& notification_callback) - : startup_lock_(notification_callback), - process_singleton_(user_data_dir, - startup_lock_.AsNotificationCallback()) { -} - -AtomProcessSingleton::~AtomProcessSingleton() { -} - -ProcessSingleton::NotifyResult - AtomProcessSingleton::NotifyOtherProcessOrCreate() { - return process_singleton_.NotifyOtherProcessOrCreate(); -} - -void AtomProcessSingleton::Cleanup() { - process_singleton_.Cleanup(); -} - -void AtomProcessSingleton::Unlock() { - startup_lock_.Unlock(); -} diff --git a/atom/browser/atom_process_singleton.h b/atom/browser/atom_process_singleton.h deleted file mode 100644 index 89feeaecb3..0000000000 --- a/atom/browser/atom_process_singleton.h +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef ATOM_BROWSER_ATOM_PROCESS_SINGLETON_H_ -#define ATOM_BROWSER_ATOM_PROCESS_SINGLETON_H_ - -#include "base/basictypes.h" -#include "base/files/file_path.h" -#include "chrome/browser/process_singleton.h" -#include "chrome/browser/process_singleton_startup_lock.h" - -// Composes a basic ProcessSingleton with ProcessSingletonStartupLock -class AtomProcessSingleton { - public: - AtomProcessSingleton( - const base::FilePath& user_data_dir, - const ProcessSingleton::NotificationCallback& notification_callback); - - ~AtomProcessSingleton(); - - // Notify another process, if available. Otherwise sets ourselves as the - // singleton instance. Returns PROCESS_NONE if we became the singleton - // instance. Callers are guaranteed to either have notified an existing - // process or have grabbed the singleton (unless the profile is locked by an - // unreachable process). - ProcessSingleton::NotifyResult NotifyOtherProcessOrCreate(); - - // Clear any lock state during shutdown. - void Cleanup(); - - // Executes previously queued command-line invocations and allows future - // invocations to be executed immediately. - // This only has an effect the first time it is called. - void Unlock(); - - private: - // We compose these two locks with the client-supplied notification callback. - // First |modal_dialog_lock_| will discard any notifications that arrive while - // a modal dialog is active. Otherwise, it will pass the notification to - // |startup_lock_|, which will queue notifications until |Unlock()| is called. - // Notifications passing through both locks are finally delivered to our - // client. - ProcessSingletonStartupLock startup_lock_; - - // The basic ProcessSingleton - ProcessSingleton process_singleton_; - - DISALLOW_COPY_AND_ASSIGN(AtomProcessSingleton); -}; - -#endif // ATOM_BROWSER_ATOM_PROCESS_SINGLETON_H_ diff --git a/filenames.gypi b/filenames.gypi index 3f3d1019c2..ec7ab3291b 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -130,7 +130,6 @@ 'atom/browser/atom_browser_main_parts_posix.cc', 'atom/browser/atom_javascript_dialog_manager.cc', 'atom/browser/atom_javascript_dialog_manager.h', - 'atom/browser/atom_process_singleton.cc', 'atom/browser/atom_quota_permission_context.cc', 'atom/browser/atom_quota_permission_context.h', 'atom/browser/atom_resource_dispatcher_host_delegate.cc', From 759b6a15341d1eedefe67882e9d24251696da02c Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Wed, 21 Oct 2015 13:52:17 -0700 Subject: [PATCH 090/146] :memo: for makeSingleInstance --- docs/api/app.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/docs/api/app.md b/docs/api/app.md index 41098d4279..9c9afdee76 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -290,6 +290,51 @@ URLs that fall under "Local Intranet" sites (i.e. are in the same domain as you) However, this detection often fails when corporate networks are badly configured, so this lets you co-opt this behavior and enable it for all URLs. +### `app.makeSingleInstance(callback)` + +* `callback` Function + +This method makes your application a Single Instance Application - instead of +allowing multiple instances of your app to run, this will ensure that only a +single instance of your app is running, and other instances signal this +instance and exit. + +`callback` is called when a second instance has been executed, and provides +the command-line (including Chromium flags) and the working directory of the +secondary instance. Usually applications respond to this by making their +primary window focused and non-minimized. + +`callback` should return `true` if the message was successfully handled, or +`false` if the secondary process should retry sending it or it failed. + +```js +app.on('ready', function() { + var myWindow; + + var shouldQuit = app.makeSingleInstance(function(command_line, working_directory) { + // Someone tried to run a second instance, we should focus our window + if (myWindow) { + if (myWindow.isMinimized()) myWindow.restore(); + myWindow.focus(); + } + + // We successfully handled the command line + return true; + }); + + if (shouldQuit) { + app.quit(); + return; + } + + // Create myWindow, load the rest of the app, etc... +}); +``` + +Returns a Boolean - if `false`, your process is the primary instance of the +application and your app should continue loading. If `true`, your process has +sent its parameters to another instance, and you should immediately quit. + ### `app.commandLine.appendSwitch(switch[, value])` Append a switch (with optional `value`) to Chromium's command line. From bac3d2e372b68a252c89adad91cac64b9801a9e4 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Wed, 21 Oct 2015 17:07:36 -0700 Subject: [PATCH 091/146] Update crash-reporter.md --- docs/api/crash-reporter.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/api/crash-reporter.md b/docs/api/crash-reporter.md index 86670fcc90..64a3b2cf74 100644 --- a/docs/api/crash-reporter.md +++ b/docs/api/crash-reporter.md @@ -59,11 +59,10 @@ ID. The crash reporter will send the following data to the `submitUrl` as `POST`: -* `rept` String - e.g. 'electron-crash-service'. * `ver` String - The version of Electron. * `platform` String - e.g. 'win32'. * `process_type` String - e.g. 'renderer'. -* `ptime` Number +* `guid` String - e.g. '5e1286fc-da97-479e-918b-6bfb0c3d1c72' * `_version` String - The version in `package.json`. * `_productName` String - The product name in the `crashReporter` `options` object. From 9411508d3e435fccc082fee4a5c0d18b79e39686 Mon Sep 17 00:00:00 2001 From: Robo Date: Wed, 21 Oct 2015 15:47:28 +0530 Subject: [PATCH 092/146] browser: option to set window background color --- atom/browser/native_window_views.cc | 31 ++++++++++++++++++++++++++++- atom/common/options_switches.cc | 3 +++ atom/common/options_switches.h | 1 + docs/api/browser-window.md | 7 +++++++ 4 files changed, 41 insertions(+), 1 deletion(-) diff --git a/atom/browser/native_window_views.cc b/atom/browser/native_window_views.cc index e3688235bd..1e4207bf0c 100644 --- a/atom/browser/native_window_views.cc +++ b/atom/browser/native_window_views.cc @@ -77,6 +77,29 @@ bool IsAltModifier(const content::NativeWebKeyboardEvent& event) { (modifiers == (Modifiers::AltKey | Modifiers::IsRight)); } +SkColor ParseHexColor(const std::string& name) { + SkColor result = 0xFF000000; + unsigned value = 0; + auto color = name.substr(1); + unsigned length = color.size(); + if (length != 3 && length != 6) + return result; + for (unsigned i = 0; i < length; ++i) { + if (!base::IsHexDigit(color[i])) + return result; + value <<= 4; + value |= (color[i] < 'A' ? color[i] - '0' : (color[i] - 'A' + 10) & 0xF); + } + if (length == 6) { + result |= value; + return result; + } + result |= (value & 0xF00) << 12 | (value & 0xF00) << 8 + | (value & 0xF0) << 8 | (value & 0xF0) << 4 + | (value & 0xF) << 4 | (value & 0xF); + return result; +} + class NativeWindowClientView : public views::ClientView { public: NativeWindowClientView(views::Widget* widget, @@ -205,7 +228,13 @@ NativeWindowViews::NativeWindowViews( // Add web view. SetLayoutManager(new MenuLayout(this, kMenuBarHeight)); - set_background(views::Background::CreateStandardPanelBackground()); + + // web views' background color. + std::string background_color = "#fff"; + options.Get(switches::kBackgroundColor, &background_color); + set_background(views::Background::CreateSolidBackground( + ParseHexColor(background_color))); + AddChildView(web_view_); #if defined(OS_WIN) diff --git a/atom/common/options_switches.cc b/atom/common/options_switches.cc index 8ea16f27b4..903c15ee6d 100644 --- a/atom/common/options_switches.cc +++ b/atom/common/options_switches.cc @@ -93,6 +93,9 @@ const char kDisableAutoHideCursor[] = "disable-auto-hide-cursor"; // Use the OS X's standard window instead of the textured window. const char kStandardWindow[] = "standard-window"; +// Default browser window background color. +const char kBackgroundColor[] = "background-color"; + // Path to client certificate. const char kClientCertificate[] = "client-certificate"; diff --git a/atom/common/options_switches.h b/atom/common/options_switches.h index 33c2790cc1..9887359a50 100644 --- a/atom/common/options_switches.h +++ b/atom/common/options_switches.h @@ -47,6 +47,7 @@ extern const char kTransparent[]; extern const char kType[]; extern const char kDisableAutoHideCursor[]; extern const char kStandardWindow[]; +extern const char kBackgroundColor[]; extern const char kClientCertificate[]; extern const char kExperimentalFeatures[]; diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index fd25f8cdc9..c59fbbdbcb 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -61,6 +61,13 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. key is pressed. * `enable-larger-than-screen` Boolean - Enable the window to be resized larger than screen. +* `background-color` String - Window's background color as Hexadecimal value. +```javascript +var win = new BrowserWindow({ width: 800, height: 600, 'background-color': '#66CD00' }) + +// #abc will be expanded to #aabbcc +var win = new BrowserWindow({ width: 800, height: 600, 'background-color': '#fff' }) +``` * `dark-theme` Boolean - Forces using dark theme for the window, only works on some GTK+3 desktop environments. * `transparent` Boolean - Makes the window [transparent](frameless-window.md). From f7840e7379783973b99542b4d8e8b505f8b327e4 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 22 Oct 2015 14:16:17 +0800 Subject: [PATCH 093/146] docs: background-color is Linux-only for now --- docs/api/browser-window.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index c59fbbdbcb..165ce70a97 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -61,13 +61,8 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. key is pressed. * `enable-larger-than-screen` Boolean - Enable the window to be resized larger than screen. -* `background-color` String - Window's background color as Hexadecimal value. -```javascript -var win = new BrowserWindow({ width: 800, height: 600, 'background-color': '#66CD00' }) - -// #abc will be expanded to #aabbcc -var win = new BrowserWindow({ width: 800, height: 600, 'background-color': '#fff' }) -``` +* `background-color` String - Window's background color as Hexadecimal value, + like `#66CD00` or `#FFF`. This is only implemented on Linux. * `dark-theme` Boolean - Forces using dark theme for the window, only works on some GTK+3 desktop environments. * `transparent` Boolean - Makes the window [transparent](frameless-window.md). From 63cc2ec3691324df91ae3814bae21327baf16ffd Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 22 Oct 2015 14:24:09 +0800 Subject: [PATCH 094/146] docs: Make code more JS-style --- docs/api/app.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/api/app.md b/docs/api/app.md index 9c9afdee76..95aa80b6dc 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -245,7 +245,7 @@ Returns the current application locale. Resolves the proxy information for `url`. The `callback` will be called with `callback(proxy)` when the request is performed. -### `app.addRecentDocument(path)` +### `app.addRecentDocument(path)` _OS X_ _Windows_ * `path` String @@ -254,7 +254,7 @@ Adds `path` to the recent documents list. This list is managed by the OS. On Windows you can visit the list from the task bar, and on OS X you can visit it from dock menu. -### `app.clearRecentDocuments()` +### `app.clearRecentDocuments()` _OS X_ _Windows_ Clears the recent documents list. @@ -308,10 +308,9 @@ primary window focused and non-minimized. `false` if the secondary process should retry sending it or it failed. ```js +var myWindow; app.on('ready', function() { - var myWindow; - - var shouldQuit = app.makeSingleInstance(function(command_line, working_directory) { + var shouldQuit = app.makeSingleInstance(function(commandLine, workingDirectory) { // Someone tried to run a second instance, we should focus our window if (myWindow) { if (myWindow.isMinimized()) myWindow.restore(); From acb2c099f6dde04bffa8e3c2cf422409abee8987 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 22 Oct 2015 14:56:57 +0800 Subject: [PATCH 095/146] Make sure current task runner is available When calling makeSingleInstance we have to ensure current task runnder is available, otherwise crash may happen. --- atom/browser/atom_browser_main_parts.cc | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/atom/browser/atom_browser_main_parts.cc b/atom/browser/atom_browser_main_parts.cc index 4c11176997..dfbdf4477f 100644 --- a/atom/browser/atom_browser_main_parts.cc +++ b/atom/browser/atom_browser_main_parts.cc @@ -62,17 +62,15 @@ void AtomBrowserMainParts::PreEarlyInitialization() { void AtomBrowserMainParts::PostEarlyInitialization() { brightray::BrowserMainParts::PostEarlyInitialization(); - { - // Temporary set the bridge_task_runner_ as current thread's task runner, - // so we can fool gin::PerIsolateData to use it as its task runner, instead - // of getting current message loop's task runner, which is null for now. - bridge_task_runner_ = new BridgeTaskRunner; - base::ThreadTaskRunnerHandle handle(bridge_task_runner_); + // Temporary set the bridge_task_runner_ as current thread's task runner, + // so we can fool gin::PerIsolateData to use it as its task runner, instead + // of getting current message loop's task runner, which is null for now. + bridge_task_runner_ = new BridgeTaskRunner; + base::ThreadTaskRunnerHandle handle(bridge_task_runner_); - // The ProxyResolverV8 has setup a complete V8 environment, in order to - // avoid conflicts we only initialize our V8 environment after that. - js_env_.reset(new JavascriptEnvironment); - } + // The ProxyResolverV8 has setup a complete V8 environment, in order to + // avoid conflicts we only initialize our V8 environment after that. + js_env_.reset(new JavascriptEnvironment); node_bindings_->Initialize(); From 310954713f96245549f4f9cfc28c01ade72fde15 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 22 Oct 2015 14:59:12 +0800 Subject: [PATCH 096/146] Simplify the usage of singleton --- atom/browser/api/atom_api_app.cc | 38 ++++++++----------- atom/browser/api/atom_api_app.h | 1 - .../chrome/browser/process_singleton_posix.cc | 2 +- .../browser/process_singleton_startup_lock.cc | 2 +- .../browser/process_singleton_startup_lock.h | 2 +- 5 files changed, 18 insertions(+), 27 deletions(-) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index c46f0748e3..2dfc3e056d 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -163,10 +163,7 @@ void App::OnQuit() { Emit("quit"); if (process_singleton_.get()) { - if (process_notify_result_ == ProcessSingleton::PROCESS_NONE) { - process_singleton_->Cleanup(); - } - + process_singleton_->Cleanup(); process_singleton_.reset(); } } @@ -196,9 +193,8 @@ void App::OnFinishLaunching() { auto handle = Session::CreateFrom(isolate(), browser_context); default_session_.Reset(isolate(), handle.ToV8()); - if (process_singleton_.get()) { + if (process_singleton_startup_lock_.get()) process_singleton_startup_lock_->Unlock(); - } Emit("ready"); } @@ -282,35 +278,31 @@ v8::Local App::DefaultSession(v8::Isolate* isolate) { } bool App::MakeSingleInstance(ProcessSingleton::NotificationCallback callback) { - base::FilePath userDir; - PathService::Get(brightray::DIR_USER_DATA, &userDir); + if (process_singleton_.get()) + return false; - if (!process_singleton_.get()) { - auto browser = Browser::Get(); - process_singleton_startup_lock_.reset( + base::FilePath user_dir; + PathService::Get(brightray::DIR_USER_DATA, &user_dir); + + process_singleton_startup_lock_.reset( new ProcessSingletonStartupLock(callback)); - - process_singleton_.reset( + process_singleton_.reset( new ProcessSingleton( - userDir, + user_dir, process_singleton_startup_lock_->AsNotificationCallback())); - if (browser->is_ready()) { - process_singleton_startup_lock_->Unlock(); - } + if (Browser::Get()->is_ready()) + process_singleton_startup_lock_->Unlock(); - process_notify_result_ = process_singleton_->NotifyOtherProcessOrCreate(); - } - - switch (process_notify_result_) { + switch (process_singleton_->NotifyOtherProcessOrCreate()) { case ProcessSingleton::NotifyResult::PROCESS_NONE: return false; case ProcessSingleton::NotifyResult::LOCK_ERROR: case ProcessSingleton::NotifyResult::PROFILE_IN_USE: case ProcessSingleton::NotifyResult::PROCESS_NOTIFIED: + process_singleton_.reset(); + process_singleton_startup_lock_.reset(); return true; - default: - return false; } } diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index 314eeea881..8d55fc27c3 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -80,7 +80,6 @@ class App : public mate::EventEmitter, scoped_ptr process_singleton_; scoped_ptr process_singleton_startup_lock_; - ProcessSingleton::NotifyResult process_notify_result_; DISALLOW_COPY_AND_ASSIGN(App); }; diff --git a/chromium_src/chrome/browser/process_singleton_posix.cc b/chromium_src/chrome/browser/process_singleton_posix.cc index 5ab6eb6d8a..4117dff886 100644 --- a/chromium_src/chrome/browser/process_singleton_posix.cc +++ b/chromium_src/chrome/browser/process_singleton_posix.cc @@ -1046,4 +1046,4 @@ void ProcessSingleton::KillProcess(int pid) { // progress of shutting down and finishes before we try to kill it). DCHECK(rv == 0 || errno == ESRCH) << "Error killing process: " << base::safe_strerror(errno); -} \ No newline at end of file +} diff --git a/chromium_src/chrome/browser/process_singleton_startup_lock.cc b/chromium_src/chrome/browser/process_singleton_startup_lock.cc index b97ada658c..f564de7dca 100644 --- a/chromium_src/chrome/browser/process_singleton_startup_lock.cc +++ b/chromium_src/chrome/browser/process_singleton_startup_lock.cc @@ -50,4 +50,4 @@ bool ProcessSingletonStartupLock::NotificationCallbackImpl( } else { return original_callback_.Run(command_line, current_directory); } -} \ No newline at end of file +} diff --git a/chromium_src/chrome/browser/process_singleton_startup_lock.h b/chromium_src/chrome/browser/process_singleton_startup_lock.h index 3df51d8b17..187dd35bce 100644 --- a/chromium_src/chrome/browser/process_singleton_startup_lock.h +++ b/chromium_src/chrome/browser/process_singleton_startup_lock.h @@ -54,4 +54,4 @@ class ProcessSingletonStartupLock : public base::NonThreadSafe { DISALLOW_COPY_AND_ASSIGN(ProcessSingletonStartupLock); }; -#endif // CHROME_BROWSER_PROCESS_SINGLETON_STARTUP_LOCK_H_ \ No newline at end of file +#endif // CHROME_BROWSER_PROCESS_SINGLETON_STARTUP_LOCK_H_ From bcb78ebc00e9f04299fd26955b3aa0e97c20a2e4 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 22 Oct 2015 15:02:32 +0800 Subject: [PATCH 097/146] app.makeSingleInstance is not available on OS X --- atom/browser/api/atom_api_app.cc | 4 ++++ docs/api/app.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 2dfc3e056d..5ce918be78 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -278,6 +278,10 @@ v8::Local App::DefaultSession(v8::Isolate* isolate) { } bool App::MakeSingleInstance(ProcessSingleton::NotificationCallback callback) { +#if defined(OS_MACOSX) + LOG(ERROR) << "MakeSingleInstance is not implemnted on OS X"; + return false; +#endif if (process_singleton_.get()) return false; diff --git a/docs/api/app.md b/docs/api/app.md index 95aa80b6dc..bd017456a7 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -290,7 +290,7 @@ URLs that fall under "Local Intranet" sites (i.e. are in the same domain as you) However, this detection often fails when corporate networks are badly configured, so this lets you co-opt this behavior and enable it for all URLs. -### `app.makeSingleInstance(callback)` +### `app.makeSingleInstance(callback)` _Windows_ _Linux_ * `callback` Function From 05c63003294ccd8ef8b8b25524e713b1a0de9752 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 22 Oct 2015 15:54:27 +0800 Subject: [PATCH 098/146] Don't discard tasks in BridgeTaskRunner --- atom/browser/atom_browser_main_parts.cc | 1 + atom/browser/bridge_task_runner.cc | 32 +++++++++++++++++++++---- atom/browser/bridge_task_runner.h | 13 +++++++++- 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/atom/browser/atom_browser_main_parts.cc b/atom/browser/atom_browser_main_parts.cc index dfbdf4477f..5fae5bfdbe 100644 --- a/atom/browser/atom_browser_main_parts.cc +++ b/atom/browser/atom_browser_main_parts.cc @@ -105,6 +105,7 @@ void AtomBrowserMainParts::PreMainMessageLoopRun() { 1000)); brightray::BrowserMainParts::PreMainMessageLoopRun(); + BridgeTaskRunner::MessageLoopIsReady(); #if defined(USE_X11) libgtk2ui::GtkInitFromCommandLine(*base::CommandLine::ForCurrentProcess()); diff --git a/atom/browser/bridge_task_runner.cc b/atom/browser/bridge_task_runner.cc index 24572f3990..882a3050de 100644 --- a/atom/browser/bridge_task_runner.cc +++ b/atom/browser/bridge_task_runner.cc @@ -8,13 +8,33 @@ namespace atom { +// static +std::vector BridgeTaskRunner::tasks_; +std::vector BridgeTaskRunner::non_nestable_tasks_; + +// static +void BridgeTaskRunner::MessageLoopIsReady() { + auto message_loop = base::MessageLoop::current(); + CHECK(message_loop); + for (const TaskPair& task : tasks_) { + message_loop->task_runner()->PostDelayedTask( + base::get<0>(task), base::get<1>(task), base::get<2>(task)); + } + for (const TaskPair& task : non_nestable_tasks_) { + message_loop->task_runner()->PostNonNestableDelayedTask( + base::get<0>(task), base::get<1>(task), base::get<2>(task)); + } +} + bool BridgeTaskRunner::PostDelayedTask( const tracked_objects::Location& from_here, const base::Closure& task, base::TimeDelta delay) { auto message_loop = base::MessageLoop::current(); - if (!message_loop) - return false; + if (!message_loop) { + tasks_.push_back(base::MakeTuple(from_here, task, delay)); + return true; + } return message_loop->task_runner()->PostDelayedTask(from_here, task, delay); } @@ -22,7 +42,7 @@ bool BridgeTaskRunner::PostDelayedTask( bool BridgeTaskRunner::RunsTasksOnCurrentThread() const { auto message_loop = base::MessageLoop::current(); if (!message_loop) - return false; + return true; return message_loop->task_runner()->RunsTasksOnCurrentThread(); } @@ -32,8 +52,10 @@ bool BridgeTaskRunner::PostNonNestableDelayedTask( const base::Closure& task, base::TimeDelta delay) { auto message_loop = base::MessageLoop::current(); - if (!message_loop) - return false; + if (!message_loop) { + non_nestable_tasks_.push_back(base::MakeTuple(from_here, task, delay)); + return true; + } return message_loop->task_runner()->PostNonNestableDelayedTask( from_here, task, delay); diff --git a/atom/browser/bridge_task_runner.h b/atom/browser/bridge_task_runner.h index fb42aa3852..12508f009d 100644 --- a/atom/browser/bridge_task_runner.h +++ b/atom/browser/bridge_task_runner.h @@ -5,17 +5,23 @@ #ifndef ATOM_BROWSER_BRIDGE_TASK_RUNNER_H_ #define ATOM_BROWSER_BRIDGE_TASK_RUNNER_H_ +#include + #include "base/single_thread_task_runner.h" +#include "base/tuple.h" namespace atom { // Post all tasks to the current message loop's task runner if available, -// otherwise fail silently. +// otherwise delay the work until message loop is ready. class BridgeTaskRunner : public base::SingleThreadTaskRunner { public: BridgeTaskRunner() {} ~BridgeTaskRunner() override {} + // Called when message loop is ready. + static void MessageLoopIsReady(); + // base::SingleThreadTaskRunner: bool PostDelayedTask(const tracked_objects::Location& from_here, const base::Closure& task, @@ -27,6 +33,11 @@ class BridgeTaskRunner : public base::SingleThreadTaskRunner { base::TimeDelta delay) override; private: + using TaskPair = base::Tuple< + tracked_objects::Location, base::Closure, base::TimeDelta>; + static std::vector tasks_; + static std::vector non_nestable_tasks_; + DISALLOW_COPY_AND_ASSIGN(BridgeTaskRunner); }; From f01e84a4182eb2ce7aa86fb987b14b30cdfdabae Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 22 Oct 2015 15:54:58 +0800 Subject: [PATCH 099/146] linux: Delay listening to socket until message loop is ready --- .../chrome/browser/process_singleton.h | 3 +++ .../chrome/browser/process_singleton_posix.cc | 27 +++++++++++++------ 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/chromium_src/chrome/browser/process_singleton.h b/chromium_src/chrome/browser/process_singleton.h index 3265c9597a..05288dc37c 100644 --- a/chromium_src/chrome/browser/process_singleton.h +++ b/chromium_src/chrome/browser/process_singleton.h @@ -133,6 +133,9 @@ class ProcessSingleton : public base::NonThreadSafe { base::FilePath user_data_dir_; ShouldKillRemoteProcessCallback should_kill_remote_process_callback_; #elif defined(OS_POSIX) && !defined(OS_ANDROID) + // Start listening to the socket. + void StartListening(int sock); + // Return true if the given pid is one of our child processes. // Assumes that the current pid is the root of all pids of the current // instance. diff --git a/chromium_src/chrome/browser/process_singleton_posix.cc b/chromium_src/chrome/browser/process_singleton_posix.cc index 4117dff886..5a8aac9079 100644 --- a/chromium_src/chrome/browser/process_singleton_posix.cc +++ b/chromium_src/chrome/browser/process_singleton_posix.cc @@ -316,8 +316,7 @@ bool IsChromeProcess(pid_t pid) { PathService::Get(base::FILE_EXE, &exec_path); return (!other_chrome_path.empty() && - other_chrome_path.BaseName() == - exec_path.BaseName()); + other_chrome_path.BaseName() == exec_path.BaseName()); } // A helper class to hold onto a socket. @@ -988,13 +987,15 @@ bool ProcessSingleton::Create() { if (listen(sock, 5) < 0) NOTREACHED() << "listen failed: " << base::safe_strerror(errno); - DCHECK(BrowserThread::IsMessageLoopValid(BrowserThread::IO)); - BrowserThread::PostTask( - BrowserThread::IO, + // In Electron the ProcessSingleton is created earlier than the IO + // thread gets created, so we have to postpone the call until message + // loop is up an running. + scoped_refptr task_runner( + base::ThreadTaskRunnerHandle::Get()); + task_runner->PostTask( FROM_HERE, - base::Bind(&ProcessSingleton::LinuxWatcher::StartListening, - watcher_.get(), - sock)); + base::Bind(&ProcessSingleton::StartListening, + base::Unretained(this), sock)); return true; } @@ -1005,6 +1006,16 @@ void ProcessSingleton::Cleanup() { UnlinkPath(lock_path_); } +void ProcessSingleton::StartListening(int sock) { + DCHECK(BrowserThread::IsMessageLoopValid(BrowserThread::IO)); + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + base::Bind(&ProcessSingleton::LinuxWatcher::StartListening, + watcher_.get(), + sock)); +} + bool ProcessSingleton::IsSameChromeInstance(pid_t pid) { pid_t cur_pid = current_pid_; while (pid != cur_pid) { From 230f2760e7c9136f94b2893d94b90339a7e0208c Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 22 Oct 2015 15:57:37 +0800 Subject: [PATCH 100/146] linux: Delay creating watcher until message loop is ready --- chromium_src/chrome/browser/process_singleton_posix.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chromium_src/chrome/browser/process_singleton_posix.cc b/chromium_src/chrome/browser/process_singleton_posix.cc index 5a8aac9079..811c5c16a2 100644 --- a/chromium_src/chrome/browser/process_singleton_posix.cc +++ b/chromium_src/chrome/browser/process_singleton_posix.cc @@ -715,8 +715,7 @@ ProcessSingleton::ProcessSingleton( const base::FilePath& user_data_dir, const NotificationCallback& notification_callback) : notification_callback_(notification_callback), - current_pid_(base::GetCurrentProcId()), - watcher_(new LinuxWatcher(this)) { + current_pid_(base::GetCurrentProcId()) { socket_path_ = user_data_dir.Append(kSingletonSocketFilename); lock_path_ = user_data_dir.Append(kSingletonLockFilename); cookie_path_ = user_data_dir.Append(kSingletonCookieFilename); @@ -1007,6 +1006,7 @@ void ProcessSingleton::Cleanup() { } void ProcessSingleton::StartListening(int sock) { + watcher_ = new LinuxWatcher(this); DCHECK(BrowserThread::IsMessageLoopValid(BrowserThread::IO)); BrowserThread::PostTask( BrowserThread::IO, From ca876e424bd71de5fe09d6efb56e79482cfffed9 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 22 Oct 2015 16:06:37 +0800 Subject: [PATCH 101/146] Fix crash when calling app.quit() before app is ready --- atom/browser/browser.cc | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/atom/browser/browser.cc b/atom/browser/browser.cc index d8bb94103c..79ebe91a87 100644 --- a/atom/browser/browser.cc +++ b/atom/browser/browser.cc @@ -53,8 +53,14 @@ void Browser::Shutdown() { is_quiting_ = true; FOR_EACH_OBSERVER(BrowserObserver, observers_, OnQuit()); - base::MessageLoop::current()->PostTask( - FROM_HERE, base::MessageLoop::QuitWhenIdleClosure()); + + if (base::MessageLoop::current()) { + base::MessageLoop::current()->PostTask( + FROM_HERE, base::MessageLoop::QuitWhenIdleClosure()); + } else { + // There is no message loop available so we are in early stage. + exit(0); + } } std::string Browser::GetVersion() const { From e14fd62f4626bb016f3bf2f2e48ec7d892924304 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 22 Oct 2015 16:24:35 +0800 Subject: [PATCH 102/146] Correctly handle notification callback when shutting down When returning false in the notification callback the ProcessSingleton will assume current process is quitting, we should met its expectation. --- atom/browser/api/atom_api_app.cc | 15 +++++++++++++-- atom/browser/api/atom_api_app.h | 6 ++---- atom/browser/browser.h | 1 + 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 5ce918be78..fa2a97de4d 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -112,6 +112,15 @@ int GetPathConstant(const std::string& name) { return -1; } +// Run the NotificationCallback and returns whether browser is shuting down. +bool NotificationCallbackWrapper( + const ProcessSingleton::NotificationCallback& callback, + const base::CommandLine& command_line, + const base::FilePath& current_directory) { + callback.Run(command_line, current_directory); + return !Browser::Get()->is_shutting_down(); +} + void OnClientCertificateSelected( v8::Isolate* isolate, std::shared_ptr delegate, @@ -277,7 +286,8 @@ v8::Local App::DefaultSession(v8::Isolate* isolate) { return v8::Local::New(isolate, default_session_); } -bool App::MakeSingleInstance(ProcessSingleton::NotificationCallback callback) { +bool App::MakeSingleInstance( + const ProcessSingleton::NotificationCallback& callback) { #if defined(OS_MACOSX) LOG(ERROR) << "MakeSingleInstance is not implemnted on OS X"; return false; @@ -289,7 +299,8 @@ bool App::MakeSingleInstance(ProcessSingleton::NotificationCallback callback) { PathService::Get(brightray::DIR_USER_DATA, &user_dir); process_singleton_startup_lock_.reset( - new ProcessSingletonStartupLock(callback)); + new ProcessSingletonStartupLock( + base::Bind(NotificationCallbackWrapper, callback))); process_singleton_.reset( new ProcessSingleton( user_dir, diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index 8d55fc27c3..bd87b38850 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -68,11 +68,9 @@ class App : public mate::EventEmitter, void SetDesktopName(const std::string& desktop_name); void SetAppUserModelId(const std::string& app_id); - void AllowNTLMCredentialsForAllDomains(bool should_allow); - - bool MakeSingleInstance(ProcessSingleton::NotificationCallback callback); - + bool MakeSingleInstance( + const ProcessSingleton::NotificationCallback& callback); std::string GetLocale(); v8::Local DefaultSession(v8::Isolate* isolate); diff --git a/atom/browser/browser.h b/atom/browser/browser.h index 3c5abd2f04..bae281d4d3 100644 --- a/atom/browser/browser.h +++ b/atom/browser/browser.h @@ -130,6 +130,7 @@ class Browser : public WindowListObserver { observers_.RemoveObserver(obs); } + bool is_shutting_down() const { return is_shutdown_; } bool is_quiting() const { return is_quiting_; } bool is_ready() const { return is_ready_; } From f9d797d1ea00e9239a7b1c74bae3861cefb8e023 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 22 Oct 2015 16:55:54 +0800 Subject: [PATCH 103/146] win: Fix compiler warning --- atom/browser/api/atom_api_app.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index fa2a97de4d..a9011c6e2e 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -310,14 +310,15 @@ bool App::MakeSingleInstance( process_singleton_startup_lock_->Unlock(); switch (process_singleton_->NotifyOtherProcessOrCreate()) { - case ProcessSingleton::NotifyResult::PROCESS_NONE: - return false; case ProcessSingleton::NotifyResult::LOCK_ERROR: case ProcessSingleton::NotifyResult::PROFILE_IN_USE: case ProcessSingleton::NotifyResult::PROCESS_NOTIFIED: process_singleton_.reset(); process_singleton_startup_lock_.reset(); return true; + case ProcessSingleton::NotifyResult::PROCESS_NONE: + default: // Shouldn't be needed, but VS warns if it is not there. + return false; } } From 70e74d05e0b4cca5824b1d69b705207025c251b7 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 22 Oct 2015 17:03:18 +0800 Subject: [PATCH 104/146] Revert "app.makeSingleInstance is not available on OS X" This reverts commit bcb78ebc00e9f04299fd26955b3aa0e97c20a2e4. --- atom/browser/api/atom_api_app.cc | 4 ---- docs/api/app.md | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index a9011c6e2e..42feaf8dc4 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -288,10 +288,6 @@ v8::Local App::DefaultSession(v8::Isolate* isolate) { bool App::MakeSingleInstance( const ProcessSingleton::NotificationCallback& callback) { -#if defined(OS_MACOSX) - LOG(ERROR) << "MakeSingleInstance is not implemnted on OS X"; - return false; -#endif if (process_singleton_.get()) return false; diff --git a/docs/api/app.md b/docs/api/app.md index bd017456a7..95aa80b6dc 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -290,7 +290,7 @@ URLs that fall under "Local Intranet" sites (i.e. are in the same domain as you) However, this detection often fails when corporate networks are badly configured, so this lets you co-opt this behavior and enable it for all URLs. -### `app.makeSingleInstance(callback)` _Windows_ _Linux_ +### `app.makeSingleInstance(callback)` * `callback` Function From afc1fff7920da5c7dc5c7ea526b5705309128b7a Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 22 Oct 2015 17:12:09 +0800 Subject: [PATCH 105/146] Remove the need for ProcessSingletonStartupLock --- atom/browser/api/atom_api_app.cc | 33 +++++------ atom/browser/api/atom_api_app.h | 2 - .../browser/process_singleton_startup_lock.cc | 53 ----------------- .../browser/process_singleton_startup_lock.h | 57 ------------------- filenames.gypi | 3 +- 5 files changed, 16 insertions(+), 132 deletions(-) delete mode 100644 chromium_src/chrome/browser/process_singleton_startup_lock.cc delete mode 100644 chromium_src/chrome/browser/process_singleton_startup_lock.h diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 42feaf8dc4..84b9301316 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -112,12 +112,20 @@ int GetPathConstant(const std::string& name) { return -1; } -// Run the NotificationCallback and returns whether browser is shuting down. bool NotificationCallbackWrapper( const ProcessSingleton::NotificationCallback& callback, - const base::CommandLine& command_line, - const base::FilePath& current_directory) { - callback.Run(command_line, current_directory); + const base::CommandLine& cmd, + const base::FilePath& cwd) { + // Make sure the callback is called after app gets ready. + if (Browser::Get()->is_ready()) { + callback.Run(cmd, cwd); + } else { + scoped_refptr task_runner( + base::ThreadTaskRunnerHandle::Get()); + task_runner->PostTask( + FROM_HERE, base::Bind(base::IgnoreResult(callback), cmd, cwd)); + } + // ProcessSingleton needs to know whether current process is quiting. return !Browser::Get()->is_shutting_down(); } @@ -202,9 +210,6 @@ void App::OnFinishLaunching() { auto handle = Session::CreateFrom(isolate(), browser_context); default_session_.Reset(isolate(), handle.ToV8()); - if (process_singleton_startup_lock_.get()) - process_singleton_startup_lock_->Unlock(); - Emit("ready"); } @@ -293,24 +298,14 @@ bool App::MakeSingleInstance( base::FilePath user_dir; PathService::Get(brightray::DIR_USER_DATA, &user_dir); - - process_singleton_startup_lock_.reset( - new ProcessSingletonStartupLock( - base::Bind(NotificationCallbackWrapper, callback))); - process_singleton_.reset( - new ProcessSingleton( - user_dir, - process_singleton_startup_lock_->AsNotificationCallback())); - - if (Browser::Get()->is_ready()) - process_singleton_startup_lock_->Unlock(); + process_singleton_.reset(new ProcessSingleton( + user_dir, base::Bind(NotificationCallbackWrapper, callback))); switch (process_singleton_->NotifyOtherProcessOrCreate()) { case ProcessSingleton::NotifyResult::LOCK_ERROR: case ProcessSingleton::NotifyResult::PROFILE_IN_USE: case ProcessSingleton::NotifyResult::PROCESS_NOTIFIED: process_singleton_.reset(); - process_singleton_startup_lock_.reset(); return true; case ProcessSingleton::NotifyResult::PROCESS_NONE: default: // Shouldn't be needed, but VS warns if it is not there. diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index bd87b38850..94cd9bbc68 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -11,7 +11,6 @@ #include "atom/browser/browser_observer.h" #include "atom/common/native_mate_converters/callback.h" #include "chrome/browser/process_singleton.h" -#include "chrome/browser/process_singleton_startup_lock.h" #include "content/public/browser/gpu_data_manager_observer.h" #include "native_mate/handle.h" @@ -77,7 +76,6 @@ class App : public mate::EventEmitter, v8::Global default_session_; scoped_ptr process_singleton_; - scoped_ptr process_singleton_startup_lock_; DISALLOW_COPY_AND_ASSIGN(App); }; diff --git a/chromium_src/chrome/browser/process_singleton_startup_lock.cc b/chromium_src/chrome/browser/process_singleton_startup_lock.cc deleted file mode 100644 index f564de7dca..0000000000 --- a/chromium_src/chrome/browser/process_singleton_startup_lock.cc +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chrome/browser/process_singleton_startup_lock.h" - -#include "base/bind.h" -#include "base/logging.h" - -ProcessSingletonStartupLock::ProcessSingletonStartupLock( - const ProcessSingleton::NotificationCallback& original_callback) - : locked_(true), - original_callback_(original_callback) {} - -ProcessSingletonStartupLock::~ProcessSingletonStartupLock() {} - -ProcessSingleton::NotificationCallback -ProcessSingletonStartupLock::AsNotificationCallback() { - return base::Bind(&ProcessSingletonStartupLock::NotificationCallbackImpl, - base::Unretained(this)); -} - -void ProcessSingletonStartupLock::Unlock() { - DCHECK(CalledOnValidThread()); - locked_ = false; - - // Replay the command lines of the messages which were received while the - // ProcessSingleton was locked. Only replay each message once. - std::set replayed_messages; - for (std::vector::const_iterator it = - saved_startup_messages_.begin(); - it != saved_startup_messages_.end(); ++it) { - if (replayed_messages.find(*it) != replayed_messages.end()) - continue; - original_callback_.Run(base::CommandLine(it->first), it->second); - replayed_messages.insert(*it); - } - saved_startup_messages_.clear(); -} - -bool ProcessSingletonStartupLock::NotificationCallbackImpl( - const base::CommandLine& command_line, - const base::FilePath& current_directory) { - if (locked_) { - // If locked, it means we are not ready to process this message because - // we are probably in a first run critical phase. - saved_startup_messages_.push_back( - std::make_pair(command_line.argv(), current_directory)); - return true; - } else { - return original_callback_.Run(command_line, current_directory); - } -} diff --git a/chromium_src/chrome/browser/process_singleton_startup_lock.h b/chromium_src/chrome/browser/process_singleton_startup_lock.h deleted file mode 100644 index 187dd35bce..0000000000 --- a/chromium_src/chrome/browser/process_singleton_startup_lock.h +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef CHROME_BROWSER_PROCESS_SINGLETON_STARTUP_LOCK_H_ -#define CHROME_BROWSER_PROCESS_SINGLETON_STARTUP_LOCK_H_ - -#include -#include -#include - -#include "base/basictypes.h" -#include "base/command_line.h" -#include "base/files/file_path.h" -#include "base/threading/non_thread_safe.h" -#include "chrome/browser/process_singleton.h" - -// Provides a ProcessSingleton::NotificationCallback that can queue up -// command-line invocations during startup and execute them when startup -// completes. -// -// The object starts in a locked state. |Unlock()| must be called -// when the process is prepared to handle command-line invocations. -// -// Once unlocked, notifications are forwarded to a wrapped NotificationCallback. -class ProcessSingletonStartupLock : public base::NonThreadSafe { - public: - explicit ProcessSingletonStartupLock( - const ProcessSingleton::NotificationCallback& original_callback); - ~ProcessSingletonStartupLock(); - - // Returns the ProcessSingleton::NotificationCallback. - // The callback is only valid during the lifetime of the - // ProcessSingletonStartupLock instance. - ProcessSingleton::NotificationCallback AsNotificationCallback(); - - // Executes previously queued command-line invocations and allows future - // invocations to be executed immediately. - void Unlock(); - - bool locked() { return locked_; } - - private: - typedef std::pair - DelayedStartupMessage; - - bool NotificationCallbackImpl(const base::CommandLine& command_line, - const base::FilePath& current_directory); - - bool locked_; - std::vector saved_startup_messages_; - ProcessSingleton::NotificationCallback original_callback_; - - DISALLOW_COPY_AND_ASSIGN(ProcessSingletonStartupLock); -}; - -#endif // CHROME_BROWSER_PROCESS_SINGLETON_STARTUP_LOCK_H_ diff --git a/filenames.gypi b/filenames.gypi index ec7ab3291b..1c52cf50b2 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -347,6 +347,7 @@ 'chromium_src/chrome/browser/browser_process.cc', 'chromium_src/chrome/browser/browser_process.h', 'chromium_src/chrome/browser/chrome_process_finder_win.cc', + 'chromium_src/chrome/browser/chrome_process_finder_win.h', 'chromium_src/chrome/browser/chrome_notification_types.h', 'chromium_src/chrome/browser/extensions/global_shortcut_listener.cc', 'chromium_src/chrome/browser/extensions/global_shortcut_listener.h', @@ -376,8 +377,8 @@ 'chromium_src/chrome/browser/printing/print_preview_message_handler.cc', 'chromium_src/chrome/browser/printing/print_preview_message_handler.h', 'chromium_src/chrome/browser/process_singleton_posix.cc', - 'chromium_src/chrome/browser/process_singleton_startup_lock.cc', 'chromium_src/chrome/browser/process_singleton_win.cc', + 'chromium_src/chrome/browser/process_singleton.h', 'chromium_src/chrome/browser/renderer_host/pepper/chrome_browser_pepper_host_factory.cc', 'chromium_src/chrome/browser/renderer_host/pepper/chrome_browser_pepper_host_factory.h', 'chromium_src/chrome/browser/renderer_host/pepper/pepper_broker_message_filter.cc', From 93a3a946f3c7e6e4156d9491c3ba52b4cbf6df5f Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 22 Oct 2015 17:43:55 +0800 Subject: [PATCH 106/146] posix: Pass original command line --- chromium_src/chrome/browser/process_singleton_posix.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/chromium_src/chrome/browser/process_singleton_posix.cc b/chromium_src/chrome/browser/process_singleton_posix.cc index 811c5c16a2..e338b55c5f 100644 --- a/chromium_src/chrome/browser/process_singleton_posix.cc +++ b/chromium_src/chrome/browser/process_singleton_posix.cc @@ -52,6 +52,7 @@ #include #include +#include "atom/common/atom_command_line.h" #include "base/base_paths.h" #include "base/basictypes.h" #include "base/bind.h" @@ -817,7 +818,7 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout( return PROCESS_NONE; to_send.append(current_dir.value()); - const std::vector& argv = cmd_line.argv(); + const std::vector& argv = atom::AtomCommandLine::argv(); for (std::vector::const_iterator it = argv.begin(); it != argv.end(); ++it) { to_send.push_back(kTokenDelimiter); From d52ef50b017725f5b20daf8f168d45268eab564f Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 22 Oct 2015 17:51:51 +0800 Subject: [PATCH 107/146] win: Pass original command line --- chromium_src/chrome/browser/chrome_process_finder_win.cc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/chromium_src/chrome/browser/chrome_process_finder_win.cc b/chromium_src/chrome/browser/chrome_process_finder_win.cc index 299877a65d..5a662258a0 100644 --- a/chromium_src/chrome/browser/chrome_process_finder_win.cc +++ b/chromium_src/chrome/browser/chrome_process_finder_win.cc @@ -42,8 +42,6 @@ NotifyChromeResult AttemptToNotifyRunningChrome(HWND remote_window, if (!thread_id || !process_id) return NOTIFY_FAILED; - base::CommandLine command_line(*base::CommandLine::ForCurrentProcess()); - // Send the command line to the remote chrome window. // Format is "START\0<<>>\0<<>>". std::wstring to_send(L"START\0", 6); // want the NULL in the string. @@ -52,7 +50,7 @@ NotifyChromeResult AttemptToNotifyRunningChrome(HWND remote_window, return NOTIFY_FAILED; to_send.append(cur_dir.value()); to_send.append(L"\0", 1); // Null separator. - to_send.append(command_line.GetCommandLineString()); + to_send.append(::GetCommandLineW()); to_send.append(L"\0", 1); // Null separator. // Allow the current running browser window to make itself the foreground From 7b5a1b06ba40e2faa11b2be48af2c1856bd2fad7 Mon Sep 17 00:00:00 2001 From: Robo Date: Thu, 22 Oct 2015 16:20:48 +0530 Subject: [PATCH 108/146] debugger: expose v8debug --- atom/browser/javascript_environment.cc | 7 +++++++ atom/browser/node_debugger.cc | 8 +------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/atom/browser/javascript_environment.cc b/atom/browser/javascript_environment.cc index 4e825e3814..cc06bb6ff9 100644 --- a/atom/browser/javascript_environment.cc +++ b/atom/browser/javascript_environment.cc @@ -4,6 +4,7 @@ #include "atom/browser/javascript_environment.h" +#include "base/command_line.h" #include "gin/array_buffer.h" #include "gin/v8_initializer.h" @@ -20,6 +21,12 @@ JavascriptEnvironment::JavascriptEnvironment() } bool JavascriptEnvironment::Initialize() { + auto cmd = base::CommandLine::ForCurrentProcess(); + if (cmd->HasSwitch("debug-brk")) { + // Need to be called before v8::Initialize(). + const char expose_debug_as[] = "--expose_debug_as=v8debug"; + v8::V8::SetFlagsFromString(expose_debug_as, sizeof(expose_debug_as) - 1); + } gin::IsolateHolder::Initialize(gin::IsolateHolder::kNonStrictMode, gin::ArrayBufferAllocator::SharedInstance()); return true; diff --git a/atom/browser/node_debugger.cc b/atom/browser/node_debugger.cc index da5602a881..2cfcdb222a 100644 --- a/atom/browser/node_debugger.cc +++ b/atom/browser/node_debugger.cc @@ -35,17 +35,14 @@ NodeDebugger::NodeDebugger(v8::Isolate* isolate) weak_factory_(this) { bool use_debug_agent = false; int port = 5858; - bool wait_for_connection = false; std::string port_str; base::CommandLine* cmd = base::CommandLine::ForCurrentProcess(); if (cmd->HasSwitch("debug")) { use_debug_agent = true; port_str = cmd->GetSwitchValueASCII("debug"); - } - if (cmd->HasSwitch("debug-brk")) { + } else if (cmd->HasSwitch("debug-brk")) { use_debug_agent = true; - wait_for_connection = true; port_str = cmd->GetSwitchValueASCII("debug-brk"); } @@ -56,9 +53,6 @@ NodeDebugger::NodeDebugger(v8::Isolate* isolate) isolate_->SetData(kIsolateSlot, this); v8::Debug::SetMessageHandler(DebugMessageHandler); - if (wait_for_connection) - v8::Debug::DebugBreak(isolate_); - uv_async_init(uv_default_loop(), &weak_up_ui_handle_, ProcessMessageInUI); // Start a new IO thread. From 6bfe06ec4e5bc1dac93542bb920f680c7898698c Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 22 Oct 2015 19:02:21 +0800 Subject: [PATCH 109/146] Pass original argv in callback --- atom/browser/api/atom_api_app.cc | 3 +- atom/common/api/atom_bindings.cc | 2 +- .../command_line_converter.h | 38 ------------------- .../chrome/browser/process_singleton.h | 2 +- .../chrome/browser/process_singleton_posix.cc | 2 +- .../chrome/browser/process_singleton_win.cc | 19 ++++++++-- 6 files changed, 20 insertions(+), 46 deletions(-) delete mode 100644 atom/common/native_mate_converters/command_line_converter.h diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 84b9301316..c79dea9f7c 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -19,7 +19,6 @@ #include "atom/browser/api/atom_api_web_contents.h" #include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/file_path_converter.h" -#include "atom/common/native_mate_converters/command_line_converter.h" #include "atom/common/node_includes.h" #include "atom/common/options_switches.h" #include "base/command_line.h" @@ -114,7 +113,7 @@ int GetPathConstant(const std::string& name) { bool NotificationCallbackWrapper( const ProcessSingleton::NotificationCallback& callback, - const base::CommandLine& cmd, + const base::CommandLine::StringVector& cmd, const base::FilePath& cwd) { // Make sure the callback is called after app gets ready. if (Browser::Get()->is_ready()) { diff --git a/atom/common/api/atom_bindings.cc b/atom/common/api/atom_bindings.cc index 06fc30e7e3..60052a7216 100644 --- a/atom/common/api/atom_bindings.cc +++ b/atom/common/api/atom_bindings.cc @@ -41,7 +41,7 @@ void FatalErrorCallback(const char* location, const char* message) { } void Log(const base::string16& message) { - std::cout << message; + std::cout << message << std::flush; } } // namespace diff --git a/atom/common/native_mate_converters/command_line_converter.h b/atom/common/native_mate_converters/command_line_converter.h deleted file mode 100644 index a704ed7e92..0000000000 --- a/atom/common/native_mate_converters/command_line_converter.h +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) 2014 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#ifndef ATOM_COMMON_NATIVE_MATE_CONVERTERS_COMMAND_LINE_CONVERTER_H_ -#define ATOM_COMMON_NATIVE_MATE_CONVERTERS_COMMAND_LINE_CONVERTER_H_ - -#include - -#include "atom/common/native_mate_converters/string16_converter.h" -#include "base/command_line.h" - -namespace mate { - -template<> -struct Converter { - static v8::Local ToV8(v8::Isolate* isolate, - const base::CommandLine& val) { - return Converter::ToV8( - isolate, val.GetCommandLineString()); - } - static bool FromV8(v8::Isolate* isolate, - v8::Local val, - base::CommandLine* out) { - base::FilePath::StringType path; - - if (Converter::FromV8(isolate, val, &path)) { - *out = base::CommandLine(base::FilePath(path)); - return true; - } else { - return false; - } - } -}; - -} // namespace mate - -#endif // ATOM_COMMON_NATIVE_MATE_CONVERTERS_COMMAND_LINE_CONVERTER_H_ diff --git a/chromium_src/chrome/browser/process_singleton.h b/chromium_src/chrome/browser/process_singleton.h index 05288dc37c..3eeb53393e 100644 --- a/chromium_src/chrome/browser/process_singleton.h +++ b/chromium_src/chrome/browser/process_singleton.h @@ -60,7 +60,7 @@ class ProcessSingleton : public base::NonThreadSafe { // handled within the current browser instance or false if the remote process // should handle it (i.e., because the current process is shutting down). using NotificationCallback = - base::Callback; ProcessSingleton(const base::FilePath& user_data_dir, diff --git a/chromium_src/chrome/browser/process_singleton_posix.cc b/chromium_src/chrome/browser/process_singleton_posix.cc index e338b55c5f..b03ce431e4 100644 --- a/chromium_src/chrome/browser/process_singleton_posix.cc +++ b/chromium_src/chrome/browser/process_singleton_posix.cc @@ -600,7 +600,7 @@ void ProcessSingleton::LinuxWatcher::HandleMessage( DCHECK(ui_message_loop_ == base::MessageLoop::current()); DCHECK(reader); - if (parent_->notification_callback_.Run(base::CommandLine(argv), + if (parent_->notification_callback_.Run(argv, base::FilePath(current_dir))) { // Send back "ACK" message to prevent the client process from starting up. reader->FinishWithACK(kACKToken, arraysize(kACKToken) - 1); diff --git a/chromium_src/chrome/browser/process_singleton_win.cc b/chromium_src/chrome/browser/process_singleton_win.cc index 0007b713b1..14e53bec5f 100644 --- a/chromium_src/chrome/browser/process_singleton_win.cc +++ b/chromium_src/chrome/browser/process_singleton_win.cc @@ -77,8 +77,21 @@ BOOL CALLBACK BrowserWindowEnumeration(HWND window, LPARAM param) { return !*result; } +// Convert Command line string to argv. +base::CommandLine::StringVector CommandLineStringToArgv( + const std::wstring& command_line_string) { + int num_args = 0; + wchar_t** args = NULL; + args = ::CommandLineToArgvW(command_line_string.c_str(), &num_args); + base::CommandLine::StringVector argv; + for (int i = 0; i < num_args; ++i) + argv.push_back(std::wstring(args[i])); + LocalFree(args); + return argv; +} + bool ParseCommandLine(const COPYDATASTRUCT* cds, - base::CommandLine* parsed_command_line, + base::CommandLine::StringVector* parsed_command_line, base::FilePath* current_directory) { // We should have enough room for the shortest command (min_message_size) // and also be a multiple of wchar_t bytes. The shortest command @@ -131,7 +144,7 @@ bool ParseCommandLine(const COPYDATASTRUCT* cds, // Get command line. const std::wstring cmd_line = msg.substr(second_null + 1, third_null - second_null); - *parsed_command_line = base::CommandLine::FromString(cmd_line); + *parsed_command_line = CommandLineStringToArgv(cmd_line); return true; } return false; @@ -149,7 +162,7 @@ bool ProcessLaunchNotification( // Handle the WM_COPYDATA message from another process. const COPYDATASTRUCT* cds = reinterpret_cast(lparam); - base::CommandLine parsed_command_line(base::CommandLine::NO_PROGRAM); + base::CommandLine::StringVector parsed_command_line; base::FilePath current_directory; if (!ParseCommandLine(cds, &parsed_command_line, ¤t_directory)) { *result = TRUE; From 61f07307cb7c15b36e26cb4f099708b07609121b Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 22 Oct 2015 19:26:05 +0800 Subject: [PATCH 110/146] docs: New behaviors of makeSingleInstance --- docs/api/app.md | 62 ++++++++++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/docs/api/app.md b/docs/api/app.md index 95aa80b6dc..bb1509b686 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -299,41 +299,51 @@ allowing multiple instances of your app to run, this will ensure that only a single instance of your app is running, and other instances signal this instance and exit. -`callback` is called when a second instance has been executed, and provides -the command-line (including Chromium flags) and the working directory of the -secondary instance. Usually applications respond to this by making their -primary window focused and non-minimized. +`callback` will be called with `callback(argv, workingDirectory)` when a second +instance has been executed. `argv` is an Array of the second instance's command +line arguments, and `workingDirectory` is its current working directory. Usually +applications respond to this by making their primary window focused and +non-minimized. -`callback` should return `true` if the message was successfully handled, or -`false` if the secondary process should retry sending it or it failed. +The `callback` is guaranteed to be executed after the `ready` event of `app` +gets emitted. + +This method returns `false` if your process is the primary instance of the +application and your app should continue loading. And returns `true` if your +process has sent its parameters to another instance, and you should immediately +quit. + +On OS X the system enforces single instance automatically when users try to open +a second instance of your app in Finder, and the `open-file` and `open-url` +events will be emitted for that. However when users start your app in command +line the system's single instance machanism will be bypassed and you have to +use this method to ensure single instance. + +An example of activating the window of primary instance when a second instance +starts: ```js -var myWindow; -app.on('ready', function() { - var shouldQuit = app.makeSingleInstance(function(commandLine, workingDirectory) { - // Someone tried to run a second instance, we should focus our window - if (myWindow) { - if (myWindow.isMinimized()) myWindow.restore(); - myWindow.focus(); - } +var myWindow = null; - // We successfully handled the command line - return true; - }); - - if (shouldQuit) { - app.quit(); - return; +var shouldQuit = app.makeSingleInstance(function(commandLine, workingDirectory) { + // Someone tried to run a second instance, we should focus our window + if (myWindow) { + if (myWindow.isMinimized()) myWindow.restore(); + myWindow.focus(); } + return true; +}); - // Create myWindow, load the rest of the app, etc... +if (shouldQuit) { + app.quit(); + return; +} + +// Create myWindow, load the rest of the app, etc... +app.on('ready', function() { }); ``` -Returns a Boolean - if `false`, your process is the primary instance of the -application and your app should continue loading. If `true`, your process has -sent its parameters to another instance, and you should immediately quit. - ### `app.commandLine.appendSwitch(switch[, value])` Append a switch (with optional `value`) to Chromium's command line. From e9a5d05b279d60322c21ceb7220060a6d3a2ea36 Mon Sep 17 00:00:00 2001 From: Robo Date: Thu, 22 Oct 2015 22:56:36 +0530 Subject: [PATCH 111/146] session: allow setproxy to use external pac script --- atom/browser/api/atom_api_session.cc | 28 +++++++++++++++++++++++----- atom/browser/api/atom_api_session.h | 6 +++++- docs/api/session.md | 4 +++- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/atom/browser/api/atom_api_session.cc b/atom/browser/api/atom_api_session.cc index 3c92266be3..5bb96710f2 100644 --- a/atom/browser/api/atom_api_session.cc +++ b/atom/browser/api/atom_api_session.cc @@ -105,6 +105,24 @@ struct Converter { } }; +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + net::ProxyConfig* out) { + std::string proxy; + if (!ConvertFromV8(isolate, val, &proxy)) + return false; + auto pac_url = GURL(proxy); + if (pac_url.is_valid()) { + out->set_pac_url(pac_url); + } else { + out->proxy_rules().ParseFromString(proxy); + } + return true; + } +}; + } // namespace mate namespace atom { @@ -209,12 +227,12 @@ void ClearHttpCacheInIO( } void SetProxyInIO(net::URLRequestContextGetter* getter, - const std::string& proxy, + const net::ProxyConfig& config, const base::Closure& callback) { - net::ProxyConfig config; - config.proxy_rules().ParseFromString(proxy); auto proxy_service = getter->GetURLRequestContext()->proxy_service(); proxy_service->ResetConfigService(new net::ProxyConfigServiceFixed(config)); + // Refetches and applies the new pac script if provided. + proxy_service->ForceReloadProxyConfig(); RunCallbackInUI(callback); } @@ -287,11 +305,11 @@ void Session::ClearStorageData(mate::Arguments* args) { base::Time(), base::Time::Max(), callback); } -void Session::SetProxy(const std::string& proxy, +void Session::SetProxy(const net::ProxyConfig& config, const base::Closure& callback) { auto getter = browser_context_->GetRequestContext(); BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, - base::Bind(&SetProxyInIO, base::Unretained(getter), proxy, callback)); + base::Bind(&SetProxyInIO, base::Unretained(getter), config, callback)); } void Session::SetDownloadPath(const base::FilePath& path) { diff --git a/atom/browser/api/atom_api_session.h b/atom/browser/api/atom_api_session.h index 68ee3634e6..39712f6c84 100644 --- a/atom/browser/api/atom_api_session.h +++ b/atom/browser/api/atom_api_session.h @@ -23,6 +23,10 @@ class Arguments; class Dictionary; } +namespace net { +class ProxyConfig; +} + namespace atom { class AtomBrowserContext; @@ -64,7 +68,7 @@ class Session: public mate::TrackableObject, void ResolveProxy(const GURL& url, ResolveProxyCallback callback); void ClearCache(const net::CompletionCallback& callback); void ClearStorageData(mate::Arguments* args); - void SetProxy(const std::string& proxy, const base::Closure& callback); + void SetProxy(const net::ProxyConfig& config, const base::Closure& callback); void SetDownloadPath(const base::FilePath& path); void EnableNetworkEmulation(const mate::Dictionary& options); void DisableNetworkEmulation(); diff --git a/docs/api/session.md b/docs/api/session.md index 392b682641..25db92b73b 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -154,7 +154,9 @@ Clears the data of web storages. * `config` String * `callback` Function - Called when operation is done. -Parses the `config` indicating which proxies to use for the session. +If `config` is a PAC url, it is used directly otherwise +`config` is parsed based on the following rules indicating which +proxies to use for the session. ``` config = scheme-proxies[";"] From 4a6134f3f71ab7cd7e1406d588f74480b870a3da Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 23 Oct 2015 11:17:14 +0800 Subject: [PATCH 112/146] win: Set native window's background color --- atom/browser/native_window_views.cc | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/atom/browser/native_window_views.cc b/atom/browser/native_window_views.cc index 1e4207bf0c..ab4df82df7 100644 --- a/atom/browser/native_window_views.cc +++ b/atom/browser/native_window_views.cc @@ -42,6 +42,7 @@ #elif defined(OS_WIN) #include "atom/browser/ui/views/win_frame_view.h" #include "atom/browser/ui/win/atom_desktop_window_tree_host_win.h" +#include "skia/ext/skia_utils_win.h" #include "ui/base/win/shell.h" #include "ui/gfx/win/dpi.h" #include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h" @@ -230,14 +231,22 @@ NativeWindowViews::NativeWindowViews( SetLayoutManager(new MenuLayout(this, kMenuBarHeight)); // web views' background color. - std::string background_color = "#fff"; - options.Get(switches::kBackgroundColor, &background_color); - set_background(views::Background::CreateSolidBackground( - ParseHexColor(background_color))); + SkColor background_color = SK_ColorWHITE; + std::string color_name; + if (options.Get(switches::kBackgroundColor, &color_name)) + background_color = ParseHexColor(color_name); + set_background(views::Background::CreateSolidBackground(background_color)); AddChildView(web_view_); #if defined(OS_WIN) + // Set the background color of native window. + HBRUSH brush = CreateSolidBrush(skia::SkColorToCOLORREF(background_color)); + ULONG_PTR previous_brush = SetClassLongPtr( + GetAcceleratedWidget(), GCLP_HBRBACKGROUND, (LONG)brush); + if (previous_brush) + DeleteObject((HBRUSH)previous_brush); + // Save initial window state. if (fullscreen) last_window_state_ = ui::SHOW_STATE_FULLSCREEN; From e36d455d518cf79bc06a33a27df6e597dbc39485 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 23 Oct 2015 11:35:33 +0800 Subject: [PATCH 113/146] Add setBackgroundColor method --- atom/browser/api/atom_api_window.cc | 5 +++++ atom/browser/api/atom_api_window.h | 1 + atom/browser/native_window.cc | 4 ++++ atom/browser/native_window.h | 1 + atom/browser/native_window_mac.h | 1 + atom/browser/native_window_mac.mm | 3 +++ atom/browser/native_window_views.cc | 29 +++++++++++++++-------------- atom/browser/native_window_views.h | 1 + 8 files changed, 31 insertions(+), 14 deletions(-) diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index c4beaace67..62994c9c93 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -385,6 +385,10 @@ bool Window::IsKiosk() { return window_->IsKiosk(); } +void Window::SetBackgroundColor(const std::string& color_name) { + window_->SetBackgroundColor(color_name); +} + void Window::FocusOnWebView() { window_->FocusOnWebView(); } @@ -564,6 +568,7 @@ void Window::BuildPrototype(v8::Isolate* isolate, .SetMethod("setSkipTaskbar", &Window::SetSkipTaskbar) .SetMethod("setKiosk", &Window::SetKiosk) .SetMethod("isKiosk", &Window::IsKiosk) + .SetMethod("setBackgroundColor", &Window::SetBackgroundColor) .SetMethod("setRepresentedFilename", &Window::SetRepresentedFilename) .SetMethod("getRepresentedFilename", &Window::GetRepresentedFilename) .SetMethod("setDocumentEdited", &Window::SetDocumentEdited) diff --git a/atom/browser/api/atom_api_window.h b/atom/browser/api/atom_api_window.h index d2886b5fac..8a8ff266a8 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -122,6 +122,7 @@ class Window : public mate::TrackableObject, void SetSkipTaskbar(bool skip); void SetKiosk(bool kiosk); bool IsKiosk(); + void SetBackgroundColor(const std::string& color_name); void FocusOnWebView(); void BlurWebView(); bool IsWebViewFocused(); diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index 8f3af83885..c19bcdf467 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -139,6 +139,10 @@ void NativeWindow::InitFromOptions(const mate::Dictionary& options) { if (options.Get(switches::kKiosk, &kiosk) && kiosk) { SetKiosk(kiosk); } + std::string color; + if (options.Get(switches::kBackgroundColor, &color)) { + SetBackgroundColor(color); + } std::string title("Electron"); options.Get(switches::kTitle, &title); SetTitle(title); diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index 67bac88f32..37a59ff8a9 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -134,6 +134,7 @@ class NativeWindow : public base::SupportsUserData, virtual void SetSkipTaskbar(bool skip) = 0; virtual void SetKiosk(bool kiosk) = 0; virtual bool IsKiosk() = 0; + virtual void SetBackgroundColor(const std::string& color_name) = 0; virtual void SetRepresentedFilename(const std::string& filename); virtual std::string GetRepresentedFilename(); virtual void SetDocumentEdited(bool edited); diff --git a/atom/browser/native_window_mac.h b/atom/browser/native_window_mac.h index 7a97032d75..08f9198e4f 100644 --- a/atom/browser/native_window_mac.h +++ b/atom/browser/native_window_mac.h @@ -57,6 +57,7 @@ class NativeWindowMac : public NativeWindow { void SetSkipTaskbar(bool skip) override; void SetKiosk(bool kiosk) override; bool IsKiosk() override; + void SetBackgroundColor(const std::string& color_name) override; void SetRepresentedFilename(const std::string& filename) override; std::string GetRepresentedFilename() override; void SetDocumentEdited(bool edited) override; diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 9724930c48..28c8a5ae59 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -635,6 +635,9 @@ bool NativeWindowMac::IsKiosk() { return is_kiosk_; } +void NativeWindowMac::SetBackgroundColor(const std::string& color_name) { +} + void NativeWindowMac::SetRepresentedFilename(const std::string& filename) { [window_ setRepresentedFilename:base::SysUTF8ToNSString(filename)]; } diff --git a/atom/browser/native_window_views.cc b/atom/browser/native_window_views.cc index ab4df82df7..876058789d 100644 --- a/atom/browser/native_window_views.cc +++ b/atom/browser/native_window_views.cc @@ -230,23 +230,9 @@ NativeWindowViews::NativeWindowViews( // Add web view. SetLayoutManager(new MenuLayout(this, kMenuBarHeight)); - // web views' background color. - SkColor background_color = SK_ColorWHITE; - std::string color_name; - if (options.Get(switches::kBackgroundColor, &color_name)) - background_color = ParseHexColor(color_name); - set_background(views::Background::CreateSolidBackground(background_color)); - AddChildView(web_view_); #if defined(OS_WIN) - // Set the background color of native window. - HBRUSH brush = CreateSolidBrush(skia::SkColorToCOLORREF(background_color)); - ULONG_PTR previous_brush = SetClassLongPtr( - GetAcceleratedWidget(), GCLP_HBRBACKGROUND, (LONG)brush); - if (previous_brush) - DeleteObject((HBRUSH)previous_brush); - // Save initial window state. if (fullscreen) last_window_state_ = ui::SHOW_STATE_FULLSCREEN; @@ -534,6 +520,21 @@ bool NativeWindowViews::IsKiosk() { return IsFullscreen(); } +void NativeWindowViews::SetBackgroundColor(const std::string& color_name) { + // web views' background color. + SkColor background_color = ParseHexColor(color_name); + set_background(views::Background::CreateSolidBackground(background_color)); + +#if defined(OS_WIN) + // Set the background color of native window. + HBRUSH brush = CreateSolidBrush(skia::SkColorToCOLORREF(background_color)); + ULONG_PTR previous_brush = SetClassLongPtr( + GetAcceleratedWidget(), GCLP_HBRBACKGROUND, (LONG)brush); + if (previous_brush) + DeleteObject((HBRUSH)previous_brush); +#endif +} + void NativeWindowViews::SetMenu(ui::MenuModel* menu_model) { if (menu_model == nullptr) { // Remove accelerators diff --git a/atom/browser/native_window_views.h b/atom/browser/native_window_views.h index 0014acd073..6c47c74331 100644 --- a/atom/browser/native_window_views.h +++ b/atom/browser/native_window_views.h @@ -77,6 +77,7 @@ class NativeWindowViews : public NativeWindow, void SetSkipTaskbar(bool skip) override; void SetKiosk(bool kiosk) override; bool IsKiosk() override; + void SetBackgroundColor(const std::string& color_name) override; void SetMenu(ui::MenuModel* menu_model) override; gfx::NativeWindow GetNativeWindow() override; void SetOverlayIcon(const gfx::Image& overlay, From 83ef23ff8def8913ead0329164c1890532647997 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 23 Oct 2015 13:32:04 +0800 Subject: [PATCH 114/146] docs: background-color on Windows --- docs/api/browser-window.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 165ce70a97..aa46590c74 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -62,7 +62,7 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. * `enable-larger-than-screen` Boolean - Enable the window to be resized larger than screen. * `background-color` String - Window's background color as Hexadecimal value, - like `#66CD00` or `#FFF`. This is only implemented on Linux. + like `#66CD00` or `#FFF`. This is only implemented on Linux and Windows. * `dark-theme` Boolean - Forces using dark theme for the window, only works on some GTK+3 desktop environments. * `transparent` Boolean - Makes the window [transparent](frameless-window.md). From d5c964c68c6cee99af54a0053322bda6d793cda8 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 23 Oct 2015 14:23:05 +0800 Subject: [PATCH 115/146] Fix passing Error object in remote Closes #3089 --- atom/browser/lib/rpc-server.coffee | 27 +++++++++++++++------------ atom/renderer/api/lib/remote.coffee | 3 ++- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/atom/browser/lib/rpc-server.coffee b/atom/browser/lib/rpc-server.coffee index a1e159a16a..6a20d09d11 100644 --- a/atom/browser/lib/rpc-server.coffee +++ b/atom/browser/lib/rpc-server.coffee @@ -10,6 +10,7 @@ valueToMeta = (sender, value, optimizeSimpleObject=false) -> meta.type = 'buffer' if Buffer.isBuffer value meta.type = 'value' if value is null meta.type = 'array' if Array.isArray value + meta.type = 'error' if value instanceof Error meta.type = 'promise' if value? and value.constructor.name is 'Promise' # Treat simple objects as value. @@ -36,6 +37,8 @@ valueToMeta = (sender, value, optimizeSimpleObject=false) -> meta.value = Array::slice.call value, 0 else if meta.type is 'promise' meta.then = valueToMeta(sender, value.then.bind(value)) + else if meta.type is 'error' + meta.message = value.message else meta.type = 'value' meta.value = value @@ -43,8 +46,8 @@ valueToMeta = (sender, value, optimizeSimpleObject=false) -> meta # Convert Error into meta data. -errorToMeta = (error) -> - type: 'error', message: error.message, stack: (error.stack || error) +exceptionToMeta = (error) -> + type: 'exception', message: error.message, stack: (error.stack || error) # Convert array of meta data from renderer into array of real values. unwrapArgs = (sender, args) -> @@ -100,19 +103,19 @@ ipc.on 'ATOM_BROWSER_REQUIRE', (event, module) -> try event.returnValue = valueToMeta event.sender, process.mainModule.require(module) catch e - event.returnValue = errorToMeta e + event.returnValue = exceptionToMeta e ipc.on 'ATOM_BROWSER_GLOBAL', (event, name) -> try event.returnValue = valueToMeta event.sender, global[name] catch e - event.returnValue = errorToMeta e + event.returnValue = exceptionToMeta e ipc.on 'ATOM_BROWSER_CURRENT_WINDOW', (event) -> try event.returnValue = valueToMeta event.sender, event.sender.getOwnerBrowserWindow() catch e - event.returnValue = errorToMeta e + event.returnValue = exceptionToMeta e ipc.on 'ATOM_BROWSER_CURRENT_WEB_CONTENTS', (event) -> event.returnValue = valueToMeta event.sender, event.sender @@ -126,7 +129,7 @@ ipc.on 'ATOM_BROWSER_CONSTRUCTOR', (event, id, args) -> obj = new (Function::bind.apply(constructor, [null].concat(args))) event.returnValue = valueToMeta event.sender, obj catch e - event.returnValue = errorToMeta e + event.returnValue = exceptionToMeta e ipc.on 'ATOM_BROWSER_FUNCTION_CALL', (event, id, args) -> try @@ -134,7 +137,7 @@ ipc.on 'ATOM_BROWSER_FUNCTION_CALL', (event, id, args) -> func = objectsRegistry.get id callFunction event, func, global, args catch e - event.returnValue = errorToMeta e + event.returnValue = exceptionToMeta e ipc.on 'ATOM_BROWSER_MEMBER_CONSTRUCTOR', (event, id, method, args) -> try @@ -144,7 +147,7 @@ ipc.on 'ATOM_BROWSER_MEMBER_CONSTRUCTOR', (event, id, method, args) -> obj = new (Function::bind.apply(constructor, [null].concat(args))) event.returnValue = valueToMeta event.sender, obj catch e - event.returnValue = errorToMeta e + event.returnValue = exceptionToMeta e ipc.on 'ATOM_BROWSER_MEMBER_CALL', (event, id, method, args) -> try @@ -152,7 +155,7 @@ ipc.on 'ATOM_BROWSER_MEMBER_CALL', (event, id, method, args) -> obj = objectsRegistry.get id callFunction event, obj[method], obj, args catch e - event.returnValue = errorToMeta e + event.returnValue = exceptionToMeta e ipc.on 'ATOM_BROWSER_MEMBER_SET', (event, id, name, value) -> try @@ -160,14 +163,14 @@ ipc.on 'ATOM_BROWSER_MEMBER_SET', (event, id, name, value) -> obj[name] = value event.returnValue = null catch e - event.returnValue = errorToMeta e + event.returnValue = exceptionToMeta e ipc.on 'ATOM_BROWSER_MEMBER_GET', (event, id, name) -> try obj = objectsRegistry.get id event.returnValue = valueToMeta event.sender, obj[name] catch e - event.returnValue = errorToMeta e + event.returnValue = exceptionToMeta e ipc.on 'ATOM_BROWSER_DEREFERENCE', (event, id) -> objectsRegistry.remove event.sender.getId(), id @@ -177,4 +180,4 @@ ipc.on 'ATOM_BROWSER_GUEST_WEB_CONTENTS', (event, guestInstanceId) -> guestViewManager = require './guest-view-manager' event.returnValue = valueToMeta event.sender, guestViewManager.getGuest(guestInstanceId) catch e - event.returnValue = errorToMeta e + event.returnValue = exceptionToMeta e diff --git a/atom/renderer/api/lib/remote.coffee b/atom/renderer/api/lib/remote.coffee index 1f17cf3400..4eb56bcf02 100644 --- a/atom/renderer/api/lib/remote.coffee +++ b/atom/renderer/api/lib/remote.coffee @@ -46,7 +46,8 @@ metaToValue = (meta) -> when 'array' then (metaToValue(el) for el in meta.members) when 'buffer' then new Buffer(meta.value) when 'promise' then Promise.resolve(then: metaToValue(meta.then)) - when 'error' + when 'error' then new Error(meta.message) + when 'exception' throw new Error("#{meta.message}\n#{meta.stack}") else if meta.type is 'function' From 95fe4beda83318949c51e0100d46f221ab2b265e Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 23 Oct 2015 14:36:36 +0800 Subject: [PATCH 116/146] Pass real Error object in error event --- atom/browser/api/atom_api_auto_updater.cc | 11 +++++++++-- docs/api/auto-updater.md | 3 +-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/atom/browser/api/atom_api_auto_updater.cc b/atom/browser/api/atom_api_auto_updater.cc index 250aa3e45f..d8407f60b3 100644 --- a/atom/browser/api/atom_api_auto_updater.cc +++ b/atom/browser/api/atom_api_auto_updater.cc @@ -23,8 +23,15 @@ AutoUpdater::~AutoUpdater() { auto_updater::AutoUpdater::SetDelegate(NULL); } -void AutoUpdater::OnError(const std::string& error) { - Emit("error", error); +void AutoUpdater::OnError(const std::string& message) { + v8::Locker locker(isolate()); + v8::HandleScope handle_scope(isolate()); + auto error = v8::Exception::Error(mate::StringToV8(isolate(), message)); + EmitCustomEvent( + "error", + error->ToObject(isolate()->GetCurrentContext()).ToLocalChecked(), + // Message is also emitted to keep compatibility with old code. + message); } void AutoUpdater::OnCheckingForUpdate() { diff --git a/docs/api/auto-updater.md b/docs/api/auto-updater.md index 9734f592c8..ea731cd869 100644 --- a/docs/api/auto-updater.md +++ b/docs/api/auto-updater.md @@ -110,8 +110,7 @@ The `autoUpdater` object emits the following events: Returns: -* `event` Event -* `message` String +* `error` Error Emitted when there is an error while updating. From a3f62da615221a3e98c43d518b0c03a45783aa4d Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 23 Oct 2015 14:51:25 +0800 Subject: [PATCH 117/146] Fix passing Date object in remote Close #2017. --- atom/browser/lib/rpc-server.coffee | 3 +++ atom/renderer/api/lib/remote.coffee | 1 + 2 files changed, 4 insertions(+) diff --git a/atom/browser/lib/rpc-server.coffee b/atom/browser/lib/rpc-server.coffee index 6a20d09d11..6b1f95a841 100644 --- a/atom/browser/lib/rpc-server.coffee +++ b/atom/browser/lib/rpc-server.coffee @@ -11,6 +11,7 @@ valueToMeta = (sender, value, optimizeSimpleObject=false) -> meta.type = 'value' if value is null meta.type = 'array' if Array.isArray value meta.type = 'error' if value instanceof Error + meta.type = 'date' if value instanceof Date meta.type = 'promise' if value? and value.constructor.name is 'Promise' # Treat simple objects as value. @@ -39,6 +40,8 @@ valueToMeta = (sender, value, optimizeSimpleObject=false) -> meta.then = valueToMeta(sender, value.then.bind(value)) else if meta.type is 'error' meta.message = value.message + else if meta.type is 'date' + meta.value = value.getTime() else meta.type = 'value' meta.value = value diff --git a/atom/renderer/api/lib/remote.coffee b/atom/renderer/api/lib/remote.coffee index 4eb56bcf02..8a5565f065 100644 --- a/atom/renderer/api/lib/remote.coffee +++ b/atom/renderer/api/lib/remote.coffee @@ -47,6 +47,7 @@ metaToValue = (meta) -> when 'buffer' then new Buffer(meta.value) when 'promise' then Promise.resolve(then: metaToValue(meta.then)) when 'error' then new Error(meta.message) + when 'date' then new Date(meta.value) when 'exception' throw new Error("#{meta.message}\n#{meta.stack}") else From 85c84a0eb034ce552e4ed48ed56ed513d365a555 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 23 Oct 2015 14:58:19 +0800 Subject: [PATCH 118/146] Emit Date object in C++ directly --- atom/browser/api/atom_api_auto_updater.cc | 22 +++++++++++++++++++--- atom/browser/api/lib/auto-updater.coffee | 19 ++++++++----------- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/atom/browser/api/atom_api_auto_updater.cc b/atom/browser/api/atom_api_auto_updater.cc index d8407f60b3..9a240a2790 100644 --- a/atom/browser/api/atom_api_auto_updater.cc +++ b/atom/browser/api/atom_api_auto_updater.cc @@ -11,6 +11,23 @@ #include "native_mate/dictionary.h" #include "native_mate/object_template_builder.h" +namespace mate { + +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + const base::Time& val) { + v8::MaybeLocal date = v8::Date::New( + isolate->GetCurrentContext(), val.ToJsTime()); + if (date.IsEmpty()) + return v8::Null(isolate); + else + return date.ToLocalChecked(); + } +}; + +} // namespace mate + namespace atom { namespace api { @@ -49,11 +66,10 @@ void AutoUpdater::OnUpdateNotAvailable() { void AutoUpdater::OnUpdateDownloaded(const std::string& release_notes, const std::string& release_name, const base::Time& release_date, - const std::string& update_url, + const std::string& url, const base::Closure& quit_and_install) { quit_and_install_ = quit_and_install; - Emit("update-downloaded-raw", release_notes, release_name, - release_date.ToJsTime(), update_url); + Emit("update-downloaded", release_notes, release_name, release_date, url); } mate::ObjectTemplateBuilder AutoUpdater::GetObjectTemplateBuilder( diff --git a/atom/browser/api/lib/auto-updater.coffee b/atom/browser/api/lib/auto-updater.coffee index c039bd12dc..7e3fa99a76 100644 --- a/atom/browser/api/lib/auto-updater.coffee +++ b/atom/browser/api/lib/auto-updater.coffee @@ -1,15 +1,12 @@ -switch process.platform - when 'win32' - autoUpdater = require './auto-updater/auto-updater-win' - else - # take the default binding for the current platform - autoUpdater = process.atomBinding('auto_updater').autoUpdater - EventEmitter = require('events').EventEmitter - autoUpdater.__proto__ = EventEmitter.prototype +if process.platform is 'win32' + module.exports = require './auto-updater/auto-updater-win' + return -autoUpdater.on 'update-downloaded-raw', (args...) -> - args[3] = new Date(args[3]) # releaseDate - @emit 'update-downloaded', args..., => @quitAndInstall() +# Implementation on OS X. +autoUpdater = process.atomBinding('auto_updater').autoUpdater +EventEmitter = require('events').EventEmitter + +autoUpdater.__proto__ = EventEmitter.prototype autoUpdater.quitAndInstall = -> # If we don't have any window then quitAndInstall immediately. From d74ef5c078e2a13074147281cbed51378ee5315d Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 23 Oct 2015 15:13:24 +0800 Subject: [PATCH 119/146] Move implementation of auto-updater on OS X to another file --- atom/browser/api/lib/auto-updater.coffee | 32 ++++--------------- .../lib/auto-updater/auto-updater-mac.coffee | 20 ++++++++++++ filenames.gypi | 1 + 3 files changed, 28 insertions(+), 25 deletions(-) create mode 100644 atom/browser/api/lib/auto-updater/auto-updater-mac.coffee diff --git a/atom/browser/api/lib/auto-updater.coffee b/atom/browser/api/lib/auto-updater.coffee index 7e3fa99a76..41b78a00d7 100644 --- a/atom/browser/api/lib/auto-updater.coffee +++ b/atom/browser/api/lib/auto-updater.coffee @@ -1,25 +1,7 @@ -if process.platform is 'win32' - module.exports = require './auto-updater/auto-updater-win' - return - -# Implementation on OS X. -autoUpdater = process.atomBinding('auto_updater').autoUpdater -EventEmitter = require('events').EventEmitter - -autoUpdater.__proto__ = EventEmitter.prototype - -autoUpdater.quitAndInstall = -> - # If we don't have any window then quitAndInstall immediately. - BrowserWindow = require 'browser-window' - windows = BrowserWindow.getAllWindows() - if windows.length is 0 - @_quitAndInstall() - return - - # Do the restart after all windows have been closed. - app = require 'app' - app.removeAllListeners 'window-all-closed' - app.once 'window-all-closed', @_quitAndInstall.bind(this) - win.close() for win in windows - -module.exports = autoUpdater +switch process.platform + when 'win32' + module.exports = require './auto-updater/auto-updater-win' + when 'darwin' + module.exports = require './auto-updater/auto-updater-mac' + else + throw new Error('auto-updater is not implemented on this platform') diff --git a/atom/browser/api/lib/auto-updater/auto-updater-mac.coffee b/atom/browser/api/lib/auto-updater/auto-updater-mac.coffee new file mode 100644 index 0000000000..9ec9f3f917 --- /dev/null +++ b/atom/browser/api/lib/auto-updater/auto-updater-mac.coffee @@ -0,0 +1,20 @@ +{EventEmitter} = require 'events' +{autoUpdater} = process.atomBinding 'auto_updater' + +autoUpdater.__proto__ = EventEmitter.prototype + +autoUpdater.quitAndInstall = -> + # If we don't have any window then quitAndInstall immediately. + BrowserWindow = require 'browser-window' + windows = BrowserWindow.getAllWindows() + if windows.length is 0 + @_quitAndInstall() + return + + # Do the restart after all windows have been closed. + app = require 'app' + app.removeAllListeners 'window-all-closed' + app.once 'window-all-closed', @_quitAndInstall.bind(this) + win.close() for win in windows + +module.exports = autoUpdater diff --git a/filenames.gypi b/filenames.gypi index 389c245ffa..f6a7dbd086 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -11,6 +11,7 @@ 'atom/browser/api/lib/app.coffee', 'atom/browser/api/lib/atom-delegate.coffee', 'atom/browser/api/lib/auto-updater.coffee', + 'atom/browser/api/lib/auto-updater/auto-updater-mac.coffee', 'atom/browser/api/lib/auto-updater/auto-updater-win.coffee', 'atom/browser/api/lib/auto-updater/squirrel-update-win.coffee', 'atom/browser/api/lib/browser-window.coffee', From f89d28a63ebdaff666a2d22cec50f27a753562da Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 23 Oct 2015 15:40:56 +0800 Subject: [PATCH 120/146] Simplify the auto-updater implementations We used to use Sparkle on OS X, and the design was reserved to be extended to all platforms, which are all wrong now. --- atom/browser/api/atom_api_auto_updater.cc | 30 ++++++++----- atom/browser/api/atom_api_auto_updater.h | 24 +++++----- .../lib/auto-updater/auto-updater-mac.coffee | 14 ------ atom/browser/auto_updater.cc | 11 +++-- atom/browser/auto_updater.h | 35 +++++++++++++-- atom/browser/auto_updater_delegate.h | 45 ------------------- atom/browser/auto_updater_linux.cc | 17 ------- atom/browser/auto_updater_mac.mm | 26 +++++------ atom/browser/auto_updater_win.cc | 17 ------- filenames.gypi | 3 -- 10 files changed, 81 insertions(+), 141 deletions(-) delete mode 100644 atom/browser/auto_updater_delegate.h delete mode 100644 atom/browser/auto_updater_linux.cc delete mode 100644 atom/browser/auto_updater_win.cc diff --git a/atom/browser/api/atom_api_auto_updater.cc b/atom/browser/api/atom_api_auto_updater.cc index 9a240a2790..1b7c0cee7d 100644 --- a/atom/browser/api/atom_api_auto_updater.cc +++ b/atom/browser/api/atom_api_auto_updater.cc @@ -5,8 +5,9 @@ #include "atom/browser/api/atom_api_auto_updater.h" #include "base/time/time.h" -#include "atom/browser/auto_updater.h" #include "atom/browser/browser.h" +#include "atom/browser/native_window.h" +#include "atom/browser/window_list.h" #include "atom/common/node_includes.h" #include "native_mate/dictionary.h" #include "native_mate/object_template_builder.h" @@ -37,7 +38,7 @@ AutoUpdater::AutoUpdater() { } AutoUpdater::~AutoUpdater() { - auto_updater::AutoUpdater::SetDelegate(NULL); + auto_updater::AutoUpdater::SetDelegate(nullptr); } void AutoUpdater::OnError(const std::string& message) { @@ -66,25 +67,34 @@ void AutoUpdater::OnUpdateNotAvailable() { void AutoUpdater::OnUpdateDownloaded(const std::string& release_notes, const std::string& release_name, const base::Time& release_date, - const std::string& url, - const base::Closure& quit_and_install) { - quit_and_install_ = quit_and_install; + const std::string& url) { Emit("update-downloaded", release_notes, release_name, release_date, url); } +void AutoUpdater::OnWindowAllClosed() { + QuitAndInstall(); +} + mate::ObjectTemplateBuilder AutoUpdater::GetObjectTemplateBuilder( v8::Isolate* isolate) { return mate::ObjectTemplateBuilder(isolate) .SetMethod("setFeedUrl", &auto_updater::AutoUpdater::SetFeedURL) .SetMethod("checkForUpdates", &auto_updater::AutoUpdater::CheckForUpdates) - .SetMethod("_quitAndInstall", &AutoUpdater::QuitAndInstall); + .SetMethod("quitAndInstall", &AutoUpdater::QuitAndInstall); } void AutoUpdater::QuitAndInstall() { - if (quit_and_install_.is_null()) - Browser::Get()->Shutdown(); - else - quit_and_install_.Run(); + // If we don't have any window then quitAndInstall immediately. + WindowList* window_list = WindowList::GetInstance(); + if (window_list->size() == 0) { + auto_updater::AutoUpdater::QuitAndInstall(); + return; + } + + // Otherwise do the restart after all windows have been closed. + window_list->AddObserver(this); + for (NativeWindow* window : *window_list) + window->Close(); } // static diff --git a/atom/browser/api/atom_api_auto_updater.h b/atom/browser/api/atom_api_auto_updater.h index 50c3989703..95b91041e9 100644 --- a/atom/browser/api/atom_api_auto_updater.h +++ b/atom/browser/api/atom_api_auto_updater.h @@ -7,9 +7,9 @@ #include -#include "base/callback.h" #include "atom/browser/api/event_emitter.h" -#include "atom/browser/auto_updater_delegate.h" +#include "atom/browser/auto_updater.h" +#include "atom/browser/window_list_observer.h" #include "native_mate/handle.h" namespace atom { @@ -17,7 +17,8 @@ namespace atom { namespace api { class AutoUpdater : public mate::EventEmitter, - public auto_updater::AutoUpdaterDelegate { + public auto_updater::Delegate, + public WindowListObserver { public: static mate::Handle Create(v8::Isolate* isolate); @@ -25,17 +26,18 @@ class AutoUpdater : public mate::EventEmitter, AutoUpdater(); virtual ~AutoUpdater(); - // AutoUpdaterDelegate implementations. + // Delegate implementations. void OnError(const std::string& error) override; void OnCheckingForUpdate() override; void OnUpdateAvailable() override; void OnUpdateNotAvailable() override; - void OnUpdateDownloaded( - const std::string& release_notes, - const std::string& release_name, - const base::Time& release_date, - const std::string& update_url, - const base::Closure& quit_and_install) override; + void OnUpdateDownloaded(const std::string& release_notes, + const std::string& release_name, + const base::Time& release_date, + const std::string& update_url) override; + + // WindowListObserver: + void OnWindowAllClosed() override; // mate::Wrappable implementations: mate::ObjectTemplateBuilder GetObjectTemplateBuilder( @@ -44,8 +46,6 @@ class AutoUpdater : public mate::EventEmitter, private: void QuitAndInstall(); - base::Closure quit_and_install_; - DISALLOW_COPY_AND_ASSIGN(AutoUpdater); }; diff --git a/atom/browser/api/lib/auto-updater/auto-updater-mac.coffee b/atom/browser/api/lib/auto-updater/auto-updater-mac.coffee index 9ec9f3f917..187be64f5a 100644 --- a/atom/browser/api/lib/auto-updater/auto-updater-mac.coffee +++ b/atom/browser/api/lib/auto-updater/auto-updater-mac.coffee @@ -3,18 +3,4 @@ autoUpdater.__proto__ = EventEmitter.prototype -autoUpdater.quitAndInstall = -> - # If we don't have any window then quitAndInstall immediately. - BrowserWindow = require 'browser-window' - windows = BrowserWindow.getAllWindows() - if windows.length is 0 - @_quitAndInstall() - return - - # Do the restart after all windows have been closed. - app = require 'app' - app.removeAllListeners 'window-all-closed' - app.once 'window-all-closed', @_quitAndInstall.bind(this) - win.close() for win in windows - module.exports = autoUpdater diff --git a/atom/browser/auto_updater.cc b/atom/browser/auto_updater.cc index 7ebae510e9..7dbfc5a6fe 100644 --- a/atom/browser/auto_updater.cc +++ b/atom/browser/auto_updater.cc @@ -6,22 +6,25 @@ namespace auto_updater { -AutoUpdaterDelegate* AutoUpdater::delegate_ = NULL; +Delegate* AutoUpdater::delegate_ = nullptr; -AutoUpdaterDelegate* AutoUpdater::GetDelegate() { +Delegate* AutoUpdater::GetDelegate() { return delegate_; } -void AutoUpdater::SetDelegate(AutoUpdaterDelegate* delegate) { +void AutoUpdater::SetDelegate(Delegate* delegate) { delegate_ = delegate; } -#if defined(OS_MACOSX) && defined(MAS_BUILD) +#if !defined(OS_MACOSX) || defined(MAS_BUILD) void AutoUpdater::SetFeedURL(const std::string& url) { } void AutoUpdater::CheckForUpdates() { } + +void AutoUpdater::QuitAndInstall() { +} #endif } // namespace auto_updater diff --git a/atom/browser/auto_updater.h b/atom/browser/auto_updater.h index e31aa0978a..9e479d4220 100644 --- a/atom/browser/auto_updater.h +++ b/atom/browser/auto_updater.h @@ -9,21 +9,48 @@ #include "base/basictypes.h" +namespace base { +class Time; +} + namespace auto_updater { -class AutoUpdaterDelegate; +class Delegate { + public: + // An error happened. + virtual void OnError(const std::string& error) {} + + // Checking to see if there is an update + virtual void OnCheckingForUpdate() {} + + // There is an update available and it is being downloaded + virtual void OnUpdateAvailable() {} + + // There is no available update. + virtual void OnUpdateNotAvailable() {} + + // There is a new update which has been downloaded. + virtual void OnUpdateDownloaded(const std::string& release_notes, + const std::string& release_name, + const base::Time& release_date, + const std::string& update_url) {} + + protected: + virtual ~Delegate() {} +}; class AutoUpdater { public: // Gets/Sets the delegate. - static AutoUpdaterDelegate* GetDelegate(); - static void SetDelegate(AutoUpdaterDelegate* delegate); + static Delegate* GetDelegate(); + static void SetDelegate(Delegate* delegate); static void SetFeedURL(const std::string& url); static void CheckForUpdates(); + static void QuitAndInstall(); private: - static AutoUpdaterDelegate* delegate_; + static Delegate* delegate_; DISALLOW_IMPLICIT_CONSTRUCTORS(AutoUpdater); }; diff --git a/atom/browser/auto_updater_delegate.h b/atom/browser/auto_updater_delegate.h deleted file mode 100644 index 804bc8503a..0000000000 --- a/atom/browser/auto_updater_delegate.h +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) 2013 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#ifndef ATOM_BROWSER_AUTO_UPDATER_DELEGATE_H_ -#define ATOM_BROWSER_AUTO_UPDATER_DELEGATE_H_ - -#include - -#include "base/callback_forward.h" - -namespace base { -class Time; -} - -namespace auto_updater { - -class AutoUpdaterDelegate { - public: - // An error happened. - virtual void OnError(const std::string& error) {} - - // Checking to see if there is an update - virtual void OnCheckingForUpdate() {} - - // There is an update available and it is being downloaded - virtual void OnUpdateAvailable() {} - - // There is no available update. - virtual void OnUpdateNotAvailable() {} - - // There is a new update which has been downloaded. - virtual void OnUpdateDownloaded(const std::string& release_notes, - const std::string& release_name, - const base::Time& release_date, - const std::string& update_url, - const base::Closure& quit_and_install) {} - - protected: - virtual ~AutoUpdaterDelegate() {} -}; - -} // namespace auto_updater - -#endif // ATOM_BROWSER_AUTO_UPDATER_DELEGATE_H_ diff --git a/atom/browser/auto_updater_linux.cc b/atom/browser/auto_updater_linux.cc deleted file mode 100644 index 00c95d0d45..0000000000 --- a/atom/browser/auto_updater_linux.cc +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) 2013 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#include "atom/browser/auto_updater.h" - -namespace auto_updater { - -// static -void AutoUpdater::SetFeedURL(const std::string& url) { -} - -// static -void AutoUpdater::CheckForUpdates() { -} - -} // namespace auto_updater diff --git a/atom/browser/auto_updater_mac.mm b/atom/browser/auto_updater_mac.mm index 42ee841363..a55cdd2812 100644 --- a/atom/browser/auto_updater_mac.mm +++ b/atom/browser/auto_updater_mac.mm @@ -12,9 +12,6 @@ #include "base/bind.h" #include "base/time/time.h" #include "base/strings/sys_string_conversions.h" -#include "atom/browser/auto_updater_delegate.h" - -#include namespace auto_updater { @@ -23,20 +20,12 @@ namespace { // The gloal SQRLUpdater object. SQRLUpdater* g_updater = nil; -void RelaunchToInstallUpdate() { - [[g_updater relaunchToInstallUpdate] subscribeError:^(NSError* error) { - AutoUpdaterDelegate* delegate = AutoUpdater::GetDelegate(); - if (delegate) - delegate->OnError(base::SysNSStringToUTF8(error.localizedDescription)); - }]; -} - } // namespace // static void AutoUpdater::SetFeedURL(const std::string& feed) { if (g_updater == nil) { - AutoUpdaterDelegate* delegate = GetDelegate(); + Delegate* delegate = GetDelegate(); if (!delegate) return; @@ -67,7 +56,7 @@ void AutoUpdater::SetFeedURL(const std::string& feed) { // static void AutoUpdater::CheckForUpdates() { - AutoUpdaterDelegate* delegate = GetDelegate(); + Delegate* delegate = GetDelegate(); if (!delegate) return; @@ -86,8 +75,7 @@ void AutoUpdater::CheckForUpdates() { base::SysNSStringToUTF8(update.releaseNotes), base::SysNSStringToUTF8(update.releaseName), base::Time::FromDoubleT(update.releaseDate.timeIntervalSince1970), - base::SysNSStringToUTF8(update.updateURL.absoluteString), - base::Bind(RelaunchToInstallUpdate)); + base::SysNSStringToUTF8(update.updateURL.absoluteString)); } else { // When the completed event is sent with no update, then we know there // is no update available. @@ -100,4 +88,12 @@ void AutoUpdater::CheckForUpdates() { }]; } +void AutoUpdater::QuitAndInstall() { + [[g_updater relaunchToInstallUpdate] subscribeError:^(NSError* error) { + Delegate* delegate = AutoUpdater::GetDelegate(); + if (delegate) + delegate->OnError(base::SysNSStringToUTF8(error.localizedDescription)); + }]; +} + } // namespace auto_updater diff --git a/atom/browser/auto_updater_win.cc b/atom/browser/auto_updater_win.cc deleted file mode 100644 index 00c95d0d45..0000000000 --- a/atom/browser/auto_updater_win.cc +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) 2013 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#include "atom/browser/auto_updater.h" - -namespace auto_updater { - -// static -void AutoUpdater::SetFeedURL(const std::string& url) { -} - -// static -void AutoUpdater::CheckForUpdates() { -} - -} // namespace auto_updater diff --git a/filenames.gypi b/filenames.gypi index f6a7dbd086..e6d180d652 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -115,10 +115,7 @@ 'atom/browser/api/save_page_handler.h', 'atom/browser/auto_updater.cc', 'atom/browser/auto_updater.h', - 'atom/browser/auto_updater_delegate.h', - 'atom/browser/auto_updater_linux.cc', 'atom/browser/auto_updater_mac.mm', - 'atom/browser/auto_updater_win.cc', 'atom/browser/atom_access_token_store.cc', 'atom/browser/atom_access_token_store.h', 'atom/browser/atom_browser_client.cc', From aeafc46dede25f05e28ca87efa961637e6118011 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 23 Oct 2015 15:50:55 +0800 Subject: [PATCH 121/146] Keep compatibility with old API style --- atom/browser/api/atom_api_auto_updater.cc | 5 ++++- docs/api/auto-updater.md | 13 ++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/atom/browser/api/atom_api_auto_updater.cc b/atom/browser/api/atom_api_auto_updater.cc index 1b7c0cee7d..1c80f73f7a 100644 --- a/atom/browser/api/atom_api_auto_updater.cc +++ b/atom/browser/api/atom_api_auto_updater.cc @@ -8,6 +8,7 @@ #include "atom/browser/browser.h" #include "atom/browser/native_window.h" #include "atom/browser/window_list.h" +#include "atom/common/native_mate_converters/callback.h" #include "atom/common/node_includes.h" #include "native_mate/dictionary.h" #include "native_mate/object_template_builder.h" @@ -68,7 +69,9 @@ void AutoUpdater::OnUpdateDownloaded(const std::string& release_notes, const std::string& release_name, const base::Time& release_date, const std::string& url) { - Emit("update-downloaded", release_notes, release_name, release_date, url); + Emit("update-downloaded", release_notes, release_name, release_date, url, + // Keep compatibility with old APIs. + base::Bind(&AutoUpdater::QuitAndInstall, base::Unretained(this))); } void AutoUpdater::OnWindowAllClosed() { diff --git a/docs/api/auto-updater.md b/docs/api/auto-updater.md index ea731cd869..55c605762e 100644 --- a/docs/api/auto-updater.md +++ b/docs/api/auto-updater.md @@ -136,10 +136,8 @@ Returns: * `releaseName` String * `releaseDate` Date * `updateUrl` String -* `quitAndUpdate` Function -Emitted when an update has been downloaded. Calling `quitAndUpdate()` will -restart the application and install the update. +Emitted when an update has been downloaded. ## Methods @@ -149,10 +147,15 @@ The `autoUpdater` object has the following methods: * `url` String -Set the `url` and initialize the auto updater. The `url` cannot be changed +Sets the `url` and initialize the auto updater. The `url` cannot be changed once it is set. ### `autoUpdater.checkForUpdates()` -Ask the server whether there is an update. You must call `setFeedUrl` before +Asks the server whether there is an update. You must call `setFeedUrl` before using this API. + +### `autoUpdater.quitAndUpdate()` + +Restarts the app and install the update after it has been downloaded. It should +only be called after `update-downloaded` has been emitted. From 79e593aca67beea3950852eb101f140972db44b1 Mon Sep 17 00:00:00 2001 From: Alexandru Bau Date: Fri, 23 Oct 2015 11:37:51 +0300 Subject: [PATCH 122/146] Updated docs Updated docs to better understand how to use electron-rebuild on Windows --- docs/tutorial/using-native-node-modules.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/tutorial/using-native-node-modules.md b/docs/tutorial/using-native-node-modules.md index 491415b900..305f30e02e 100644 --- a/docs/tutorial/using-native-node-modules.md +++ b/docs/tutorial/using-native-node-modules.md @@ -31,8 +31,13 @@ which handles the manual steps of downloading headers and building native module ```sh npm install --save-dev electron-rebuild -# Every time you run npm install, run this +# Every time you run "npm install", run this + +# Linux or Mac node ./node_modules/.bin/electron-rebuild + +# Windows +./node_modules/.bin/electron-rebuild ``` ### The npm Way From 96130dd4c2dc3937e76b9bb77025aca61f531d85 Mon Sep 17 00:00:00 2001 From: Alexandru Bau Date: Fri, 23 Oct 2015 14:15:39 +0300 Subject: [PATCH 123/146] Updated docs Updated docs to better understand how to use electron-rebuild on all platforms --- docs/tutorial/using-native-node-modules.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/tutorial/using-native-node-modules.md b/docs/tutorial/using-native-node-modules.md index 305f30e02e..6954fc64b1 100644 --- a/docs/tutorial/using-native-node-modules.md +++ b/docs/tutorial/using-native-node-modules.md @@ -32,11 +32,6 @@ which handles the manual steps of downloading headers and building native module npm install --save-dev electron-rebuild # Every time you run "npm install", run this - -# Linux or Mac -node ./node_modules/.bin/electron-rebuild - -# Windows ./node_modules/.bin/electron-rebuild ``` From fae2c7bc7a129e86b17e8222b61e3c6aca96afbd Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 23 Oct 2015 19:41:54 +0800 Subject: [PATCH 124/146] win: Make auto-updater really work Apparently that PR was never tested. --- .../lib/auto-updater/auto-updater-win.coffee | 55 ++++++------- .../auto-updater/squirrel-update-win.coffee | 80 +++++++++++-------- 2 files changed, 71 insertions(+), 64 deletions(-) diff --git a/atom/browser/api/lib/auto-updater/auto-updater-win.coffee b/atom/browser/api/lib/auto-updater/auto-updater-win.coffee index 764f76a225..a9a61d8efe 100644 --- a/atom/browser/api/lib/auto-updater/auto-updater-win.coffee +++ b/atom/browser/api/lib/auto-updater/auto-updater-win.coffee @@ -1,45 +1,42 @@ +app = require 'app' +url = require 'url' {EventEmitter} = require 'events' -SquirrelUpdate = require './auto-updater/squirrel-update-win' -app = require 'app' -url = require 'url' + +squirrelUpdate = require './squirrel-update-win' class AutoUpdater extends EventEmitter - quitAndInstall: -> - SquirrelUpdate.processStart -> - app.quit() + squirrelUpdate.processStart() + app.quit() setFeedUrl: (updateUrl) -> - # set feed URL only when it hasn't been set before - unless @updateUrl - @updateUrl = updateUrl + @updateUrl = updateUrl checkForUpdates: -> - throw new Error('Update URL is not set') unless @updateUrl + return @emitError 'Update URL is not set' unless @updateUrl + return @emitError 'Can not find Squirrel' unless squirrelUpdate.supported() @emit 'checking-for-update' - unless SquirrelUpdate.supported() - @emit 'update-not-available' - return - - SquirrelUpdate.download (error, update) => - if error? - @emit 'update-not-available' - return - - unless update? - @emit 'update-not-available' - return + squirrelUpdate.download @updateUrl, (error, update) => + return @emitError error if error? + return @emit 'update-not-available' unless update? @emit 'update-available' - SquirrelUpdate.update @updateUrl, (error) => - if error? - @emit 'update-not-available' - return + squirrelUpdate.update @updateUrl, (error) => + return @emitError error if error? - # info about the newly installed version and a function any of the event listeners can call to restart the application - @emit 'update-downloaded', {}, update.releaseNotes, update.version, new Date(), @updateUrl, => @quitAndInstall() + {releaseNotes, version} = update + # Following information is not available on Windows, so fake them. + date = new Date + url = @updateUrl -module.exports = new AutoUpdater() + @emit 'update-downloaded', {}, releaseNotes, version, date, url, => @quitAndInstall() + + # Private: Emit both error object and message, this is to keep compatibility + # with Old APIs. + emitError: (message) -> + @emit 'error', new Error(message), message + +module.exports = new AutoUpdater diff --git a/atom/browser/api/lib/auto-updater/squirrel-update-win.coffee b/atom/browser/api/lib/auto-updater/squirrel-update-win.coffee index 95d9e1465d..ed302124e5 100644 --- a/atom/browser/api/lib/auto-updater/squirrel-update-win.coffee +++ b/atom/browser/api/lib/auto-updater/squirrel-update-win.coffee @@ -1,57 +1,67 @@ -ChildProcess = require 'child_process' -fs = require 'fs' -path = require 'path' +fs = require 'fs' +path = require 'path' +{spawn} = require 'child_process' -appFolder = path.dirname process.execPath # i.e. my-app/app-0.1.13/ -rootApplicationFolder = path.resolve appFolder, '..' # i.e. my-app/ -updateDotExe = path.join rootApplicationFolder, 'Update.exe' -exeName = path.basename process.execPath +appFolder = path.dirname process.execPath # i.e. my-app/app-0.1.13/ +updateExe = path.resolve appFolder, '..', 'Update.exe' # i.e. my-app/Update.exe +exeName = path.basename process.execPath # Spawn a command and invoke the callback when it completes with an error # and the output from standard out. -spawnUpdate = (args, callback) -> - stdout = '' - +spawnUpdate = (args, detached, callback) -> try - spawnedProcess = ChildProcess.spawn(updateDotExe, args) + spawnedProcess = spawn updateExe, args, {detached} catch error - # Spawn can throw an error - process.nextTick -> callback?(error, stdout) + # Shouldn't happen, but still guard it. + process.nextTick -> callback error return + stdout = '' + stderr = '' spawnedProcess.stdout.on 'data', (data) -> stdout += data + spawnedProcess.stderr.on 'data', (data) -> stderr += data - error = null - spawnedProcess.on 'error', (processError) -> error ?= processError - spawnedProcess.on 'close', (code, signal) -> - error ?= new Error("Command failed: #{signal ? code}") if code isnt 0 - error?.code ?= code - error?.stdout ?= stdout - callback?(error, stdout) + errorEmitted = false + spawnedProcess.on 'error', (error) -> + errorEmitted = true + callback error + spawnedProcess.on 'exit', (code, signal) -> + # We may have already emitted an error. + return if errorEmitted -processStart = (callback) -> - spawnUpdate(['--processStart', exeName], callback) + # Process terminated with error. + if code isnt 0 + return callback "Command failed: #{signal ? code}\n#{stderr}" -download = (callback) -> - spawnUpdate ['--download', @updateUrl], (error, stdout) -> + # Success. + callback null, stdout + +# Start an instance of the installed app. +exports.processStart = (callback) -> + spawnUpdate ['--processStart', exeName], true, -> + +# Download the releases specified by the URL and write new results to stdout. +exports.download = (updateUrl, callback) -> + spawnUpdate ['--download', updateUrl], false, (error, stdout) -> return callback(error) if error? try # Last line of output is the JSON details about the releases - json = stdout.trim().split('\n').pop() + json = stdout.trim().split('\n').pop() update = JSON.parse(json)?.releasesToApply?.pop?() - catch error - error.stdout = stdout - return callback(error) + catch + return callback "Invalid result:\n#{stdout}" - callback(null, update) + callback null, update -update = (updateUrl, callback) -> - spawnUpdate ['--update', updateUrl], callback +# Update the application to the latest remote version specified by URL. +exports.update = (updateUrl, callback) -> + spawnUpdate ['--update', updateUrl], false, callback # Is the Update.exe installed with the current application? exports.supported = -> - fs.accessSync(updateDotExe, fs.R_OK) -exports.processStart = processStart -exports.download = download -exports.update = update + try + fs.accessSync updateExe, fs.R_OK + return true + catch + return false From 88b05cff3e21627fcf0ed1a7223cf325f68cd48a Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 23 Oct 2015 20:11:59 +0800 Subject: [PATCH 125/146] docs: auto-updater on Windows --- docs/api/auto-updater.md | 116 ++++++++------------------------------- 1 file changed, 24 insertions(+), 92 deletions(-) diff --git a/docs/api/auto-updater.md b/docs/api/auto-updater.md index 55c605762e..7649f0f104 100644 --- a/docs/api/auto-updater.md +++ b/docs/api/auto-updater.md @@ -1,106 +1,31 @@ # autoUpdater -**This module has only been implemented for OS X.** +This module provides an interface for the `Squirrel` auto-updater framework. -Check out [atom/grunt-electron-installer](https://github.com/atom/grunt-electron-installer) -to build a Windows installer for your app. +## Platform notices -The `auto-updater` module is a simple wrapper around the -[Squirrel.Mac](https://github.com/Squirrel/Squirrel.Mac) framework. +Though `autoUpdater` provides an uniform API for different platforms, there are +still some subtle differences on each platform. -Squirrel.Mac requires that your `.app` folder is signed using the -[codesign](https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/codesign.1.html) -utility for updates to be installed. +### OS X -## Squirrel +On OS X the `autoUpdater` module is built upon [Squirrel.Mac][squirrel-mac], you +don't need any special setup to make it work. For server-side requirements, you +can read [Server Support][server-support]. -Squirrel is an OS X framework focused on making application updates **as safe -and transparent as updates to a website**. +### Windows -Instead of publishing a feed of versions from which your app must select, -Squirrel updates to the version your server tells it to. This allows you to -intelligently update your clients based on the request you give to Squirrel. +On Windows you have to install your app into user's machine before you can use +the auto-updater, it is recommended to use [grunt-electron-installer][installer] +module to generate a Windows installer. -Your request can include authentication details, custom headers or a request -body so that your server has the context it needs in order to supply the most -suitable update. +The server-side setup is also different from OS X, you can read the documents of +[Squirrel.Windows][squirrel-windows] to get more details. -The update JSON Squirrel requests should be dynamically generated based on -criteria in the request and whether an update is required. Squirrel relies -on server-side support to determine whether an update is required. See -[Server Support](#server-support). +### Linux -Squirrel's installer is designed to be fault tolerant and ensures that any -updates installed are valid. - -## Update Requests - -Squirrel is indifferent to the request the client application provides for -update checking. `Accept: application/json` is added to the request headers -because Squirrel is responsible for parsing the response. - -For the requirements imposed on the responses and the body format of an update -response, see [Server Support](#server-support). - -Your update request must *at least* include a version identifier so that the -server can determine whether an update for this specific version is required. It -may also include other identifying criteria, such as operating system version or -username, to allow the server to deliver as fine grained an update as you -would like. - -How you include the version identifier or other criteria is specific to the -server that you are requesting updates from. A common approach is to use query -parameters, like this: - -```javascript -// In the main process -var app = require('app'); -var autoUpdater = require('auto-updater'); -autoUpdater.setFeedUrl('http://mycompany.com/myapp/latest?version=' + app.getVersion()); -``` - -## Server Support - -Your server should determine whether an update is required based on the -[Update Request](#update-requests) your client issues. - -If an update is required, your server should respond with a status code of -[200 OK](http://tools.ietf.org/html/rfc2616#section-10.2.1) and include the -[update JSON](#update-json-format) in the body. Squirrel **will** download and -install this update, even if the version of the update is the same as the -currently running version. To save redundantly downloading the same version -multiple times your server must not inform the client to update. - -If no update is required your server must respond with a status code of -[204 No Content](http://tools.ietf.org/html/rfc2616#section-10.2.5). Squirrel -will check for an update again at the interval you specify. - -## Update JSON Format - -When an update is available, Squirrel expects the following schema in response -to the update request provided: - -```json -{ - "url": "http://mycompany.com/myapp/releases/myrelease", - "name": "My Release Name", - "notes": "Theses are some release notes innit", - "pub_date": "2013-09-18T12:29:53+01:00" -} -``` - -The only required key is "url"; the others are optional. - -Squirrel will request "url" with `Accept: application/zip` and only supports -installing ZIP updates. If future update formats are supported their MIME type -will be added to the `Accept` header so that your server can return the -appropriate format. - -`pub_date` (if present) must be formatted according to ISO 8601. - -## Update server implementations - -[Nuts](https://github.com/GitbookIO/nuts) is an open source implementation of the update server described above, it integrates beautifully with GitHub releases. Nuts manages downloads and updates, it’s compatible with `Squirrel.Mac` and `Squirrel.Windows` so you get cross-platform support out of the box. +There is not built-in support for auto-updater on Linux, it is recommended to +use the distribution's package manager to update your app. ## Events @@ -139,6 +64,8 @@ Returns: Emitted when an update has been downloaded. +On Windows only `releaseName` is available. + ## Methods The `autoUpdater` object has the following methods: @@ -159,3 +86,8 @@ using this API. Restarts the app and install the update after it has been downloaded. It should only be called after `update-downloaded` has been emitted. + +[squirrel-mac]: https://github.com/Squirrel/Squirrel.Mac +[server-support]: https://github.com/Squirrel/Squirrel.Mac#server-support +[squirrel-windows]: https://github.com/Squirrel/Squirrel.Windows +[installer]: https://github.com/atom/grunt-electron-installer From 30ec7fdeed715f4a8e234bb2557497081f8d1171 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 23 Oct 2015 20:32:02 +0800 Subject: [PATCH 126/146] Bump v0.34.1 --- atom.gyp | 2 +- atom/browser/resources/mac/Info.plist | 4 ++-- atom/browser/resources/win/atom.rc | 8 ++++---- atom/common/atom_version.h | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/atom.gyp b/atom.gyp index d6d9e2e226..d4b4f67b63 100644 --- a/atom.gyp +++ b/atom.gyp @@ -4,7 +4,7 @@ 'product_name%': 'Electron', 'company_name%': 'GitHub, Inc', 'company_abbr%': 'github', - 'version%': '0.34.0', + 'version%': '0.34.1', }, 'includes': [ 'filenames.gypi', diff --git a/atom/browser/resources/mac/Info.plist b/atom/browser/resources/mac/Info.plist index 01c4bc7e62..aff4685000 100644 --- a/atom/browser/resources/mac/Info.plist +++ b/atom/browser/resources/mac/Info.plist @@ -17,9 +17,9 @@ CFBundleIconFile atom.icns CFBundleVersion - 0.34.0 + 0.34.1 CFBundleShortVersionString - 0.34.0 + 0.34.1 LSApplicationCategoryType public.app-category.developer-tools LSMinimumSystemVersion diff --git a/atom/browser/resources/win/atom.rc b/atom/browser/resources/win/atom.rc index 5a25256831..3adf196e8b 100644 --- a/atom/browser/resources/win/atom.rc +++ b/atom/browser/resources/win/atom.rc @@ -56,8 +56,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,34,0,0 - PRODUCTVERSION 0,34,0,0 + FILEVERSION 0,34,1,0 + PRODUCTVERSION 0,34,1,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -74,12 +74,12 @@ BEGIN BEGIN VALUE "CompanyName", "GitHub, Inc." VALUE "FileDescription", "Electron" - VALUE "FileVersion", "0.34.0" + VALUE "FileVersion", "0.34.1" VALUE "InternalName", "electron.exe" VALUE "LegalCopyright", "Copyright (C) 2015 GitHub, Inc. All rights reserved." VALUE "OriginalFilename", "electron.exe" VALUE "ProductName", "Electron" - VALUE "ProductVersion", "0.34.0" + VALUE "ProductVersion", "0.34.1" VALUE "SquirrelAwareVersion", "1" END END diff --git a/atom/common/atom_version.h b/atom/common/atom_version.h index 8351fff18c..1f8b8dec71 100644 --- a/atom/common/atom_version.h +++ b/atom/common/atom_version.h @@ -7,7 +7,7 @@ #define ATOM_MAJOR_VERSION 0 #define ATOM_MINOR_VERSION 34 -#define ATOM_PATCH_VERSION 0 +#define ATOM_PATCH_VERSION 1 #define ATOM_VERSION_IS_RELEASE 1 From 8555aea40f300418b957509631e8c1a065925e70 Mon Sep 17 00:00:00 2001 From: Plusb Preco Date: Sat, 24 Oct 2015 05:57:37 +0900 Subject: [PATCH 127/146] Update as upstream --- docs-translations/ko-KR/api/app.md | 64 +++++++++-- docs-translations/ko-KR/api/auto-updater.md | 108 +++++------------- .../ko-KR/api/chrome-command-line-switches.md | 4 +- docs-translations/ko-KR/api/crash-reporter.md | 3 +- docs-translations/ko-KR/api/session.md | 2 + .../tutorial/using-native-node-modules.md | 2 +- 6 files changed, 92 insertions(+), 91 deletions(-) diff --git a/docs-translations/ko-KR/api/app.md b/docs-translations/ko-KR/api/app.md index 498fbb59bf..d1263d00ab 100644 --- a/docs-translations/ko-KR/api/app.md +++ b/docs-translations/ko-KR/api/app.md @@ -74,11 +74,11 @@ Returns: 사용자가 어플리케이션을 통해 파일을 열고자 할 때 발생하는 이벤트입니다. -`open-file` 이벤트는 보통 어플리케이션이 열려 있을 때와 OS가 파일을 열기 위해 어플리케이션을 재사용할 때 발생합니다. -`open-file` 이벤트는 파일을 dock에 떨어트릴 때와 어플리케이션이 실행되기 전에도 발생합니다. -이 경우엔 확실히 `open-file` 이벤트 리스너를 어플리케이션이 시작하기 전에 설정해 놓았는지 확인해야 합니다. (`ready` 이벤트가 발생하기 전에) +`open-file` 이벤트는 보통 어플리케이션이 열려 있을 때 OS가 파일을 열기 위해 어플리케이션을 재사용할 때 발생합니다. +이 이벤트는 파일을 dock에 떨어트릴 때, 어플리케이션이 실행되기 전에도 발생합니다. +따라서 이 이벤트를 제대로 처리하려면 `open-file` 이벤트 핸들러를 어플리케이션이 시작하기 전에 등록해 놓았는지 확실히 확인해야 합니다. (`ready` 이벤트가 발생하기 전에) -이 이벤트를 처리하려면 반드시 `event.preventDefault()`를 호출해야 합니다. +이 이벤트를 처리할 땐 반드시 `event.preventDefault()`를 호출해야 합니다. ### Event: 'open-url' @@ -89,9 +89,9 @@ Returns: 유저가 어플리케이션을 통해 URL을 열고자 할 때 발생하는 이벤트입니다. -어플리케이션에서 URL을 열기 위해선 반드시 URL 스킴이 등록되어 있어야 합니다. +어플리케이션에서 URL을 열기 위해 반드시 URL 스킴이 등록되어 있어야 합니다. -이 이벤트를 처리하려면 반드시 `event.preventDefault()`를 호출해야 합니다. +이 이벤트를 처리할 땐 반드시 `event.preventDefault()`를 호출해야 합니다. ### Event: 'activate' _OS X_ @@ -245,7 +245,7 @@ npm 모듈 규칙에 따라 대부분의 경우 `package.json`의 `name` 필드 `url`의 프록시 정보를 해석합니다. `callback`은 요청이 수행되었을 때 `callback(proxy)` 형태로 호출됩니다. -### `app.addRecentDocument(path)` +### `app.addRecentDocument(path)` _OS X_ _Windows_ * `path` String @@ -254,7 +254,7 @@ npm 모듈 규칙에 따라 대부분의 경우 `package.json`의 `name` 필드 이 목록은 OS에 의해 관리됩니다. 최근 문서 목록은 Windows의 경우 작업 표시줄에서 찾을 수 있고, OS X의 경우 dock 메뉴에서 찾을 수 있습니다. -### `app.clearRecentDocuments()` +### `app.clearRecentDocuments()` _OS X_ _Windows_ 최근 문서 목록을 모두 비웁니다. @@ -288,6 +288,52 @@ Windows에서 사용할 수 있는 JumpList의 [Tasks][tasks] 카테고리에 `t 그러나 기업 네트워크가 잘못 구성된 경우 종종 작업에 실패할 수 있습니다. 이때 이 메서드를 통해 모든 URL을 허용할 수 있습니다. +### `app.makeSingleInstance(callback)` + +* `callback` Function + +현재 어플리케이션을 **Single Instance Application**으로 만들어줍니다. +이 메서드는 어플리케이션이 여러 번 실행됐을 때 다중 인스턴스가 생성되는 대신 한 개의 주 인스턴스만 유지되도록 만들 수 있습니다. +이때 중복 생성된 인스턴스는 주 인스턴스에 신호를 보내고 종료됩니다. + +`callback`은 주 인스턴스가 생성된 이후 또 다른 인스턴스가 생성됐을 때 `callback(argv, workingDirectory)` 형식으로 호출됩니다. +`argv`는 두 번째 인스턴스의 명령줄 인수이며 `workingDirectory`는 현재 작업중인 디렉터리입니다. +보통 대부분의 어플리케이션은 이러한 콜백이 호출될 때 주 윈도우창을 포커스하고 최소화되어있으면 창 복구를 실행합니다. + +`callback`은 `app`의 `ready` 이벤트가 발생한 후 실행됨을 보장합니다. + +이 메서드는 현재 실행된 어플리케이션이 주 인스턴스인 경우 `false`를 반환하고 어플리케이션의 로드가 계속 진행 되도록 합니다. +그리고 두 번째 중복된 인스턴스 생성인 경우 `true`를 반환합니다. (다른 인스턴스에 인수가 전달됬을 때) +이 불리언 값을 통해 중복 생성된 인스턴스는 즉시 종료시켜야 합니다. + +OS X에선 사용자가 Finder에서 어플리케이션의 두 번째 인스턴스를 열려고 했을 때 자동으로 **Single Instance**화 하고 `open-file`과 `open-url` 이벤트를 발생시킵니다. +그러나 사용자가 어플리케이션을 CLI 터미널에서 실행하면 운영체제 시스템의 싱글 인스턴스 메커니즘이 무시되며 그대로 중복 실행됩니다. +따라서 OS X에서도 이 메서드를 통해 확실히 중복 실행을 방지하는 것이 좋습니다. + +다음 예제는 두 번째 인스턴스가 생성되었을 때 중복된 인스턴스를 종료하고 주 어플리케이션 인스턴스의 윈도우창을 활성화 시키는 예제입니다: + +```javascript +var myWindow = null; + +var shouldQuit = app.makeSingleInstance(function(commandLine, workingDirectory) { + // 어플리케이션을 중복 실행했습니다. 주 어플리케이션 인스턴스를 활성화 합니다. + if (myWindow) { + if (myWindow.isMinimized()) myWindow.restore(); + myWindow.focus(); + } + return true; +}); + +if (shouldQuit) { + app.quit(); + return; +} + +// 윈도우창을 생성하고 각종 리소스를 로드하고 작업합니다.. +app.on('ready', function() { +}); +``` + ### `app.commandLine.appendSwitch(switch[, value])` Chrominum의 명령줄에 스위치를 추가합니다. `value`는 추가적인 값을 뜻하며 옵션입니다. @@ -297,7 +343,7 @@ Chrominum의 명령줄에 스위치를 추가합니다. `value`는 추가적인 ### `app.commandLine.appendArgument(value)` -Chrominum의 명령줄에 인자를 추가합니다. 인자는 올바르게 인용됩니다. +Chrominum의 명령줄에 인수를 추가합니다. 인수는 올바르게 인용됩니다. **참고:** 이 메서드는 `process.argv`에 영향을 주지 않습니다. diff --git a/docs-translations/ko-KR/api/auto-updater.md b/docs-translations/ko-KR/api/auto-updater.md index 98bd65dd0a..b940bb5361 100644 --- a/docs-translations/ko-KR/api/auto-updater.md +++ b/docs-translations/ko-KR/api/auto-updater.md @@ -1,86 +1,28 @@ # autoUpdater -**이 모듈은 현재 OS X에서만 사용할 수 있습니다.** +이 모듈은 `Squirrel` 자동 업데이트 프레임워크의 인터페이스를 제공합니다. -Windows 인스톨러를 생성하려면 [atom/grunt-electron-installer](https://github.com/atom/grunt-electron-installer)를 참고하세요. +## 플랫폼별 참고 사항 -`auto-updater` 모듈은 [Squirrel.Mac](https://github.com/Squirrel/Squirrel.Mac) 프레임워크의 간단한 wrapper 입니다. +`autoUpdater`는 기본적으로 모든 플랫폼에 대해 같은 API를 제공하지만, 여전히 플랫폼별로 약간씩 다른 점이 있습니다. -Squirrel.Mac은 업데이트 설치를 위해 `.app` 폴더에 -[codesign](https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/codesign.1.html) -툴을 사용한 서명을 요구합니다. +### OS X -## Squirrel +OS X에선 `auto-updater` 모듈이 [Squirrel.Mac][squirrel-mac]를 기반으로 작동합니다. +따라서 이 모듈을 작동시키기 위해 특별히 준비해야 할 작업은 없습니다. +서버 사이드 요구 사항은 [서버 지원][server-support]을 참고하세요. -Squirrel은 어플리케이션이 **안전하고 투명한 업데이트**를 제공할 수 있도록 하는데 초점이 맞춰진 OS X 프레임워크입니다. +### Windows -Squirrel은 사용자에게 어플리케이션의 업데이트를 알릴 필요 없이 자동으로 서버가 지시하는 버전을 받아 어플리케이션을 업데이트합니다. -지능적으로 클라이언트 어플리케이션을 업데이트 할 수 있습니다. +Windows에선 `auto-updater` 모듈을 사용하기 전에 어플리케이션을 사용자의 장치에 설치해야 합니다. +[grunt-electron-installer][installer]를 사용하여 어플리케이션 셋업을 만드는 것을 권장합니다. -업데이트 요청은 커스텀 헤더 또는 요청 본문에 인증 정보를 포함시킬 수 있습니다. -이에 따라 서버에선 이러한 요청을 분석 처리하여 사용자에게 적당한 업데이트를 제공할 수 있습니다. +서버 사이드 요구 사항 또한 OS X와 다르게 적용됩니다. 자세한 내용은 [Squirrel.Windows][squirrel-windows]를 참고하세요. -Squirrel JSON 업데이트 요청시 처리는 반드시 어떤 업데이트가 필요한지 요청의 기준에 맞춰 동적으로 생성되어야 합니다. -Squirrel은 사용해야 하는 업데이트 선택하는 과정을 서버에 의존합니다. [서버 지원](#서버-지원)을 참고하세요. +### Linux -Squirrel의 인스톨러는 오류에 관대하게 설계되었습니다. 그리고 업데이트가 유효한지 확인합니다. - -## 업데이트 요청 - -Squirrel은 클라이언트 어플리케이션이 업데이트 확인을 위해 제공하는 요청을 무시합니다. -Squirrel이 응답을 분석할 수 있어야하기 때문에 요청 헤더에 `Accept: application/json` 헤더가 추가됩니다. - -업데이트 응답과 본문 포맷에 대한 요구 사항은 [서버 지원](#서버-지원)를 참고하세요. - -업데이트 요청에는 서버가 해당 어플리케이션이 어떤 버전을 사용해야 하는지 판단하기 위해 *반드시* 버전 식별자를 포함시켜야 합니다. -추가로 OS 버전, 사용자 이름 같은 다른 식별 기준을 포함하여 서버에서 적합한 어플리케이션을 제공할 수 있도록 할 수 있습니다. - -버전 식별자와 다른 기준을 특정하는 업데이트 요청 폼을 서버로 전달하기 위한 공통적인 방법으로 쿼리 인자를 사용하는 방법이 있습니다: - -```javascript -// In the main process -var app = require('app'); -var autoUpdater = require('auto-updater'); -autoUpdater.setFeedUrl('http://mycompany.com/myapp/latest?version=' + app.getVersion()); -``` - -## 서버 지원 - -업데이트를 제공하는 서버는 반드시 클라이언트로부터 받은 [업데이트 요청](#업데이트-요청)을 기반으로 업데이트를 처리할 수 있어야 합니다. - -만약 업데이트 요청이 들어오면 서버는 반드시 [200 OK](http://tools.ietf.org/html/rfc2616#section-10.2.1) 상태 코드를 포함한 -[업데이트 JSON](#update-json-format)을 본문으로 보내야 합니다. -이 응답을 받으면 Squirrel은 이 업데이트를 다운로드할 것입니다. 참고로 현재 설치된 버전과 서버에서 받아온 새로운 버전이 같아도 상관하지 않고 무조건 받습니다. -업데이트시 버전 중복을 피하려면 서버에서 클라이언트 업데이트 요청에 대해 통보하지 않으면 됩니다. - -만약 따로 업데이트가 없다면 [204 No Content](http://tools.ietf.org/html/rfc2616#section-10.2.5) 상태 코드를 반환해야 합니다. -Squirrel은 지정한 시간이 지난 후 다시 업데이트를 확인합니다. - -## JSON 포맷 업데이트 - -업데이트가 사용 가능한 경우 Squirrel은 다음과 같은 구조의 json 데이터를 응답으로 받습니다: - -```json -{ - "url": "http://mycompany.com/myapp/releases/myrelease", - "name": "My Release Name", - "notes": "Theses are some release notes innit", - "pub_date": "2013-09-18T12:29:53+01:00" -} -``` - -응답 json 데이터에서 "url" 키는 필수적으로 포함해야 하고 다른 키들은 옵션입니다. - -Squirrel은 "url"로 `Accept: application/zip` 헤더와 함께 업데이트 zip 파일을 요청합니다. -향후 업데이트 포맷에 대해 서버에서 적절한 포맷을 반환할 수 있도록 MIME 타입을 `Accept` 헤더에 담아 요청합니다. - -`pub_date`은 ISO 8601 표준에 따라 포맷된 날짜입니다. - -## 업데이트 서버 구현 - -[Nuts](https://github.com/GitbookIO/nuts)는 위에서 설명한 업데이트 서버의 오픈 소스 구현입니다. -이 구현은 Github 릴리즈와 완벽하게 통합되어 있습니다. Nuts는 `Squirrel.Mac`과 `Squirrel.Windows`를 지원하고 다운로드와 업데이트를 관리합니다. -이 구현을 사용하면 cross-platform 지원을 신경 쓸 필요가 없습니다. +Linux는 따로 `auto-updater`를 지원하지 않습니다. +각 배포판의 패키지 관리자를 통해 어플리케이션 업데이트를 제공하는 것을 권장합니다. ## Events @@ -88,10 +30,11 @@ Squirrel은 "url"로 `Accept: application/zip` 헤더와 함께 업데이트 zip ### Event: 'error' -* `event` Event -* `message` String +Returns: -업데이트시 에러가 나면 발생하는 이벤트입니다. +* `error` Error + +업데이트에 문제가 생기면 발생하는 이벤트입니다. ### Event: 'checking-for-update' @@ -107,14 +50,15 @@ Squirrel은 "url"로 `Accept: application/zip` 헤더와 함께 업데이트 zip ### Event: 'update-downloaded' +Returns: + * `event` Event * `releaseNotes` String * `releaseName` String * `releaseDate` Date * `updateUrl` String -* `quitAndUpdate` Function -업데이트의 다운로드가 완료되었을 때 발생하는 이벤트입니다. `quitAndUpdate()`를 호출하면 어플리케이션을 종료하고 업데이트를 설치합니다. +업데이트의 다운로드가 완료되었을 때 발생하는 이벤트입니다. ## Methods @@ -129,3 +73,13 @@ Squirrel은 "url"로 `Accept: application/zip` 헤더와 함께 업데이트 zip ### `autoUpdater.checkForUpdates()` 서버에 새로운 업데이트가 있는지 요청을 보내 확인합니다. API를 사용하기 전에 `setFeedUrl`를 호출해야 합니다. + +### `autoUpdater.quitAndUpdate()` + +어플리케이션을 다시 시작하고 다운로드된 업데이트를 설치합니다. +이 메서드는 `update-downloaded` 이벤트가 발생한 이후에만 사용할 수 있습니다. + +[squirrel-mac]: https://github.com/Squirrel/Squirrel.Mac +[server-support]: https://github.com/Squirrel/Squirrel.Mac#server-support +[squirrel-windows]: https://github.com/Squirrel/Squirrel.Windows +[installer]: https://github.com/atom/grunt-electron-installer diff --git a/docs-translations/ko-KR/api/chrome-command-line-switches.md b/docs-translations/ko-KR/api/chrome-command-line-switches.md index c3ef6ee33b..5b055c49ec 100644 --- a/docs-translations/ko-KR/api/chrome-command-line-switches.md +++ b/docs-translations/ko-KR/api/chrome-command-line-switches.md @@ -94,8 +94,8 @@ SSL 암호화를 비활성화할 대상 목록을 지정합니다. (`,`로 구 Chromium의 로그를 콘솔에 출력합니다. -이 스위치는 어플리케이션이 로드되기 전에 인식 되므로 `app.commandLine.appendSwitch` 메서드에선 사용할 수 없습니다. -하지만 `ELECTRON_ENABLE_LOGGING` 환경 변수를 설정하면 본 스위치와 같은 효과를 낼 수 있습니다. +이 스위치는 어플리케이션이 로드되기 전에 분석 되므로 `app.commandLine.appendSwitch` 메서드에선 사용할 수 없습니다. +하지만 `ELECTRON_ENABLE_LOGGING` 환경 변수를 설정하면 본 스위치를 지정한 것과 같은 효과를 낼 수 있습니다. ## --v=`log_level` diff --git a/docs-translations/ko-KR/api/crash-reporter.md b/docs-translations/ko-KR/api/crash-reporter.md index 3a7bc35f09..88cd3b82f3 100644 --- a/docs-translations/ko-KR/api/crash-reporter.md +++ b/docs-translations/ko-KR/api/crash-reporter.md @@ -54,11 +54,10 @@ crashReporter.start({ Crash Reporter는 다음과 같은 데이터를 `submitUrl`에 `POST` 방식으로 전송합니다: -* `rept` String - 예시 'electron-crash-service' * `ver` String - Electron의 버전 * `platform` String - 예시 'win32' * `process_type` String - 예시 'renderer' -* `ptime` Number +* `guid` String - e.g. '5e1286fc-da97-479e-918b-6bfb0c3d1c72' * `_version` String - `package.json`내의 `version` 필드 * `_productName` String - Crash Reporter의 `options` 객체에서 정의한 제품명. * `prod` String - 기본 제품의 이름. 이 경우 Electron으로 표시됩니다. diff --git a/docs-translations/ko-KR/api/session.md b/docs-translations/ko-KR/api/session.md index 96d38dda5d..b5cd79cbb9 100644 --- a/docs-translations/ko-KR/api/session.md +++ b/docs-translations/ko-KR/api/session.md @@ -147,6 +147,8 @@ win.webContents.on('did-finish-load', function() { 세션에 사용할 프록시 `config`를 분석하고 프록시를 적용합니다. +세션에 사용할 프록시는 `config`가 PAC 주소일 경우 그대로 적용하고, 다른 형식일 경우 다음 규칙에 따라 적용합니다. + ``` config = scheme-proxies[";"] scheme-proxies = ["="] diff --git a/docs-translations/ko-KR/tutorial/using-native-node-modules.md b/docs-translations/ko-KR/tutorial/using-native-node-modules.md index 6a0c67770b..0691fefc86 100644 --- a/docs-translations/ko-KR/tutorial/using-native-node-modules.md +++ b/docs-translations/ko-KR/tutorial/using-native-node-modules.md @@ -28,7 +28,7 @@ Electron도 이 모듈을 통해 포팅된 네이티브 모듈을 사용할 수 npm install --save-dev electron-rebuild # 필요한 네이티브 모듈을 `npm install`로 설치한 후 다음 명령을 실행하세요: -node ./node_modules/.bin/electron-rebuild +./node_modules/.bin/electron-rebuild ``` ### `npm`을 이용한 방법 From 8e5c1a505e985d5234ec649b9e723e8ca690aaab Mon Sep 17 00:00:00 2001 From: Jhen Date: Sun, 25 Oct 2015 01:34:17 +0800 Subject: [PATCH 128/146] Update tutorial/quick-start.md for zh-TW docs --- .../zh-TW/tutorial/quick-start.md | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/docs-translations/zh-TW/tutorial/quick-start.md b/docs-translations/zh-TW/tutorial/quick-start.md index 4655549167..068138587f 100644 --- a/docs-translations/zh-TW/tutorial/quick-start.md +++ b/docs-translations/zh-TW/tutorial/quick-start.md @@ -30,7 +30,7 @@ Electron 的用戶擁有在網頁中呼叫 Node.js APIs 的能力,允許低級 在網頁中,是不允許呼叫原生 GUI 相關 APIs 因為管理原生 GUI 資源在網頁上是非常危險而且容易造成資源洩露。 如果你想要在網頁中呼叫 GUI 相關的 APIs 的操作,網頁的渲染行程必須與主行程進行通訊,請求主行程進行相關的操作。 -在 Electron ,我們提供用於在主行程與渲染行程之間通訊的 [ipc][1] 模組。並且也有一個遠端模使用 RPC 通訊方式 [remote][2]。 +在 Electron,我們提供用於在主行程與渲染行程之間通訊的 [ipc](../api/ipc-renderer.md) 模組。並且也有一個遠端模組使用 RPC 通訊方式 [remote](../api/remote.md)。 # 打造你第一個 Electron 應用 @@ -43,7 +43,7 @@ your-app/ └── index.html ``` -`package.json ` 的格式與 Node 的模組完全一樣,並且有個腳本被指定為 `main` 是用來啟動你的應用程式,它運行在主行程上。 +`package.json` 的格式與 Node 的模組完全一樣,並且有個腳本被指定為 `main` 是用來啟動你的應用程式,它運行在主行程上。 你應用裡的 一個範例在你的 `package.json` 看起來可能像這樣: ```json @@ -88,7 +88,7 @@ app.on('ready', function() {   mainWindow.loadUrl('file://' + __dirname + '/index.html');   // 打開開發者工具 -  mainWindow.openDevTools(); +  mainWindow.webContents.openDevTools();   // 當window 被關閉,這個事件會被觸發   mainWindow.on('closed', function() { @@ -110,8 +110,9 @@ app.on('ready', function() {           

Hello World!

-    We are using Node.js -    and Electron . + We are using node , + Chrome , + and Electron .    ``` @@ -160,6 +161,17 @@ $ ./Electron.app/Contents/MacOS/Electron your-app/ # 作為版本發行 在你完成了你的應用程式後,你可以依照 [應用部署](https://github.com/atom/electron/blob/master/docs/tutorial/application-distribution.md) 指南發布一個版本,並且運行已經打包好的應用程式。 -[1]: https://github.com/atom/electron/blob/master/docs/api/ipc-renderer.md +# 試試這個範例 -[2]: https://github.com/atom/electron/blob/master/docs/api/remote.md +Clone 與執行本篇教學的程式碼,它們都放在 [`atom/electron-quick-start`](https://github.com/atom/electron-quick-start) 這個 repository。 + +**Note**: 執行這個範例需要 [Git](https://git-scm.com) 以及 [Node.js](https://nodejs.org/en/download/) (其中包括 [npm](https://npmjs.org)) 在你的作業系統。 + +```bash +# Clone the repository +$ git clone https://github.com/atom/electron-quick-start +# Go into the repository +$ cd electron-quick-start +# Install dependencies and run the app +$ npm install && npm start +``` \ No newline at end of file From 9b36abc8167af721a0128fadda04800a31a8132b Mon Sep 17 00:00:00 2001 From: Eran Tiktin Date: Sun, 25 Oct 2015 17:00:29 +0200 Subject: [PATCH 129/146] Update web-contents.md The `did-fail-load` event returns an error code and error description. The error description is at least sometimes empty (e.g. when the error code is -102 on Windows), so you're left with an error code without knowing what happened. So I add a link to Chromium's net_error_list.h. --- docs/api/web-contents.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 006d7e3934..7633efbfb3 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -36,6 +36,7 @@ Returns: This event is like `did-finish-load` but emitted when the load failed or was cancelled, e.g. `window.stop()` is invoked. +The full list of error codes and their meaning is available [here](https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h). ### Event: 'did-frame-finish-load' From fd94ff97e714b0feac8a0f64015dd20bba86a5ed Mon Sep 17 00:00:00 2001 From: Diego Ponce Date: Sun, 25 Oct 2015 14:04:03 -0600 Subject: [PATCH 130/146] Remove english words in description --- docs-translations/es/tutorial/quick-start.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs-translations/es/tutorial/quick-start.md b/docs-translations/es/tutorial/quick-start.md index 5c3095deb3..3db92177ca 100644 --- a/docs-translations/es/tutorial/quick-start.md +++ b/docs-translations/es/tutorial/quick-start.md @@ -65,7 +65,6 @@ a ser ejecutado por el proceso principal. Un ejemplo de `package.json` podría v ``` El `main.js` debería crear las ventanas y gestionar los eventos del sistema, un ejemplo típico sería: -example being: ```javascript var app = require('app'); // Módulo para controlar el ciclo de vida de la aplicación. From da82eaefa797f3a91e8a3b5239fe79ba77f613a2 Mon Sep 17 00:00:00 2001 From: Everardo Medina Date: Sun, 25 Oct 2015 22:37:06 -0600 Subject: [PATCH 131/146] :memo: Add coding style spanish translation --- .../es/development/coding-style.md | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 docs-translations/es/development/coding-style.md diff --git a/docs-translations/es/development/coding-style.md b/docs-translations/es/development/coding-style.md new file mode 100644 index 0000000000..de02a33a86 --- /dev/null +++ b/docs-translations/es/development/coding-style.md @@ -0,0 +1,37 @@ +# Guía de estilo de código + +Esta es la guía de estilo de código para Electron. + +## C++ y Python + +Para C++ y Python, nosotros seguimos la [guía de estilo](http://www.chromium.org/developers/coding-style) de Chromium. +Además hay un script `script/cpplint.py` para verificar si todos los archivos +siguen el estilo. + +La versión de Python que estamos usando ahora es Python 2.7. + +El código C++ usa muchas abstracciones y tipos de Chromium, por eso +se recomienda familiarizarse con ellos. Un buen lugar para iniciar es +el documento de Chromium sobre [Abstracciones importantes y estructras de datos](https://www.chromium.org/developers/coding-style/important-abstractions-and-data-structures). El documento menciona algunos tipos especiales, tipos por alcance (que +automaticamente liberan su memoria cuando salen de su alcance), mecanismos de +registro de eventos, etcétera. + +## CoffeeScript + +Para CoffeeScript, nosotros seguimos la [guía de estilo](https://github.com/styleguide/javascript) de Github y también las +siguientes reglas: + +* Los archivos **NO** deberían terminar con una nueva línea, por que se busca + seguir los estilos que usa Google. +* Los nombres de los archivos debén estar concatenados con `-` en vez de `_`, + por ejemplo `nombre-de-archivo.coffee` en vez de `nombre_de_archivo.coffee`, + esto es por que en [github/atom](https://github.com/github/atom) + los nombres de los módulos usualmente estan en la forma `nombre-de-modulo`. + Esta regla aplica únicamente a los archivos `.coffee`. + +## Nombres de las API + +Al crear una nueva API, nosotros deberíamos preferir usar metodos `get` y `set` +en vez de usar el estilo de jQuery que utiliza una sola función. Por ejemplo, +se prefiere `.getText()` y `.setText()` por sobre `.text([text])`. Hay una +[discusión](https://github.com/atom/electron/issues/46) sobre esto. From 70fe169b0f2c091c32ace2b5455e5023ad0bf143 Mon Sep 17 00:00:00 2001 From: Robo Date: Sat, 24 Oct 2015 22:10:38 +0530 Subject: [PATCH 132/146] nativemate: use maybe version of v8::Function::Call to avoid exceptions --- atom/common/native_mate_converters/callback.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/atom/common/native_mate_converters/callback.h b/atom/common/native_mate_converters/callback.h index 6e51cda79c..68ea911fe1 100644 --- a/atom/common/native_mate_converters/callback.h +++ b/atom/common/native_mate_converters/callback.h @@ -70,10 +70,13 @@ struct V8FunctionInvoker { v8::Local holder = function->NewHandle(); v8::Local context = holder->CreationContext(); v8::Context::Scope context_scope(context); - ReturnType ret; + ReturnType ret = ReturnType(); std::vector> args = { ConvertToV8(isolate, raw)... }; - v8::Local val(holder->Call(holder, args.size(), &args.front())); - Converter::FromV8(isolate, val, &ret); + v8::Local result; + auto maybe_result = + holder->Call(context, holder, args.size(), &args.front()); + if (maybe_result.ToLocal(&result)) + Converter::FromV8(isolate, result, &ret); return ret; } }; From b71ec748ad173cc6f1a4c06d7eaa53a80e69da67 Mon Sep 17 00:00:00 2001 From: Everardo Medina Date: Mon, 26 Oct 2015 00:04:11 -0600 Subject: [PATCH 133/146] :memo: Add source code directory structure spanish translation --- docs-translations/es/README.md | 2 +- .../source-code-directory-structure.md | 62 +++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 docs-translations/es/development/source-code-directory-structure.md diff --git a/docs-translations/es/README.md b/docs-translations/es/README.md index 706261c417..687dff4bde 100644 --- a/docs-translations/es/README.md +++ b/docs-translations/es/README.md @@ -62,7 +62,7 @@ ## Desarrollo * [Guía de Estilo](development/coding-style.md) -* [Estructura de los directorios del Código Fuente](../../development/source-code-directory-structure.md) +* [Estructura de los directorios del Código Fuente](development/source-code-directory-structure.md) * [Diferencias Técnicas con NW.js (anteriormente conocido como node-webkit)](../../development/atom-shell-vs-node-webkit.md) * [Repaso del Sistema de Compilación](../../development/build-system-overview.md) * [Instrucciones de Compilación (Mac)](../../development/build-instructions-osx.md) diff --git a/docs-translations/es/development/source-code-directory-structure.md b/docs-translations/es/development/source-code-directory-structure.md new file mode 100644 index 0000000000..cc738d2412 --- /dev/null +++ b/docs-translations/es/development/source-code-directory-structure.md @@ -0,0 +1,62 @@ +# Estructura de los directorios del código fuente + +El código fuente de electron es separado en pocas partes, en su mayoría +siguiendo las especificaciones para separar archivos que usa Chromium. + +Quizá necesites familiarizarte con la [arquitectura multiprocesos](http://dev.chromium.org/developers/design-documents/multi-process-architecture) de Chromium para comprender mejor el código fuente. + +## Estructura del código fuente + +``` +Electron +├──atom - Código fuente de Electron. +| ├── app - Código de arranque. +| ├── browser - La interfaz incluyendo la ventana principal, UI, +| | y todas las cosas del proceso principal. Este le habla al renderizador +| | para manejar las páginas web. +| | ├── lib - Código Javascript para inicializar el proceso principal. +| | ├── ui - Implementaciones de UI para distintas plataformas. +| | | ├── cocoa - Código fuente específico para Cocoa. +| | | ├── gtk - Código fuente específico para GTK+. +| | | └── win - Código fuente específico para Windows GUI. +| | ├── default_app - La página por defecto para mostrar cuando Electron +| | | es iniciado sin proveer una app. +| | ├── api - La implementación de las APIs para el proceso principal. +| | | └── lib - Código Javascript parte de la implementación de la API. +| | ├── net - Código relacionado a la red. +| | ├── mac - Código fuente de Objective-C específico para Mac. +| | └── resources - Iconos, archivos específicos de plataforma, etc. +| ├── renderer - Código que se ejecuta en el proceso de renderizado. +| | ├── lib - Código Javascript del proceso de inicio del renderizador. +| | └── api - La implementación de las APIs para el proceso de renderizado. +| | └── lib - Código Javascript parte de la implementación de la API. +| └── common - Código que se utiliza en ambos procesos, el principal y el de +| renderizado. Incluye algunas funciones de utilidad y código para integrar +| el ciclo de mensajes de Node en el ciclo de mensajes de Chromium. +| ├── lib - Código Javascript común para la inicialización. +| └── api - La implementación de APIs comunes, y los fundamentos de +| los módulos integrados de Electron. +| └── lib - Código Javascript parte de la implementación de la API. +├── chromium_src - Código fuente copiado de Chromium. +├── docs - Documentación. +├── spec - Pruebas automaticas. +├── atom.gyp - Reglas de compilado de Electron. +└── common.gypi - Configuración específica para compilar y reglas + de empaquetado para otros componentes como `node` y `breakpad`. +``` + +## Estructura de otros directorios + +* **script** - Scripts usados para propositos de desarrollo + como compilar, empaquetar, realizar pruebas, etc. +* **tools** - Scripts de ayuda usados por los archivos gyp, contrario a la + carpeta `scripts`, estos scripts nunca deberían ser llamados por los usuarios. +* **vendor** - Código fuente de dependencias externas, no usamos `third_party` + como nombre por que se podría confundir con el mismo directorio + en las carpetas del código fuente de Chromium. +* **node_modules** - Módulos de node usados para la compilación. +* **out** - Directorio temporal de salida usado por `ninja`. +* **dist** - Directorio temporal creado por `script/create-dist.py` cuando + se esta creando una distribución. +* **external_binaries** - Binarios descargados de frameworks externos que no + soportan la compilación con `gyp`. From adb11225d22e1c1d0a1d6848e28105d15118bf4b Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Mon, 26 Oct 2015 16:16:50 +0800 Subject: [PATCH 134/146] Ship LICENSES.chromium.html in zip archive --- script/create-dist.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/script/create-dist.py b/script/create-dist.py index 5f47b2d358..d79d963c12 100755 --- a/script/create-dist.py +++ b/script/create-dist.py @@ -176,7 +176,8 @@ def create_dist_zip(): zip_file = os.path.join(SOURCE_ROOT, 'dist', dist_name) with scoped_cwd(DIST_DIR): - files = TARGET_BINARIES[PLATFORM] + ['LICENSE', 'version'] + files = TARGET_BINARIES[PLATFORM] + ['LICENSE', 'LICENSES.chromium.html', + 'version'] if PLATFORM == 'linux': files += [lib for lib in SYSTEM_LIBRARIES if os.path.exists(lib)] dirs = TARGET_DIRECTORIES[PLATFORM] @@ -189,7 +190,7 @@ def create_chrome_binary_zip(binary, version): zip_file = os.path.join(SOURCE_ROOT, 'dist', dist_name) with scoped_cwd(DIST_DIR): - files = ['LICENSE'] + files = ['LICENSE', 'LICENSES.chromium.html'] if PLATFORM == 'win32': files += [binary + '.exe'] else: @@ -205,7 +206,7 @@ def create_symbols_zip(): zip_file = os.path.join(SOURCE_ROOT, 'dist', dist_name) with scoped_cwd(DIST_DIR): - files = ['LICENSE', 'version'] + files = ['LICENSE', 'LICENSES.chromium.html', 'version'] dirs = ['{0}.breakpad.syms'.format(PROJECT_NAME)] make_zip(zip_file, files, dirs) From f399f50e9ee11f51a2d02eeef3eb1672e95434bc Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Mon, 26 Oct 2015 16:48:27 +0800 Subject: [PATCH 135/146] Fix calling showItemInFolder in renderer The remote method always assumes the existence of the remote object but doesn't keep a reference to it, so if we only keep the reference of a remote method we will get into troubles once the remote object is garbage collected. We should probably fix this in remote module, but since most things are already working quite good, I'm using the work-around for now. Fix #3139. --- atom/common/api/lib/shell.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/atom/common/api/lib/shell.coffee b/atom/common/api/lib/shell.coffee index 8e06826f70..bd7109e345 100644 --- a/atom/common/api/lib/shell.coffee +++ b/atom/common/api/lib/shell.coffee @@ -1,4 +1,5 @@ module.exports = process.atomBinding 'shell' if process.platform is 'win32' and process.type is 'renderer' - module.exports.showItemInFolder = require('remote').process.atomBinding('shell').showItemInFolder + module.exports.showItemInFolder = (item) -> + require('remote').require('shell').showItemInFolder item From b29a8c94b927ecb16ba7c83748a692332310f836 Mon Sep 17 00:00:00 2001 From: Gohy Leandre Date: Mon, 26 Oct 2015 22:18:16 +0100 Subject: [PATCH 136/146] Add extra headers option to webContents.loadUrl --- atom/browser/api/atom_api_web_contents.cc | 4 ++++ docs/api/web-contents.md | 1 + 2 files changed, 5 insertions(+) diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 5b3ef2f4aa..900c3e65a3 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -618,6 +618,10 @@ void WebContents::LoadURL(const GURL& url, const mate::Dictionary& options) { if (options.Get("userAgent", &user_agent)) SetUserAgent(user_agent); + std::string extra_headers; + if (options.Get("extraHeaders", &extra_headers)) + params.extra_headers = extra_headers; + params.transition_type = ui::PAGE_TRANSITION_TYPED; params.should_clear_history_list = true; params.override_user_agent = content::NavigationController::UA_OVERRIDE_TRUE; diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 7633efbfb3..3113356e34 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -183,6 +183,7 @@ See [session documentation](session.md) for this object's methods. * `options` Object (optional), properties: * `httpReferrer` String - A HTTP Referrer url. * `userAgent` String - A user agent originating the request. + * `extraHeaders` String - Extra headers separated by "\n" Loads the `url` in the window, the `url` must contain the protocol prefix, e.g. the `http://` or `file://`. From edbebf84b9d5bf7d3dde71f7337e4ce48d7de7c6 Mon Sep 17 00:00:00 2001 From: Omri Litov Date: Tue, 27 Oct 2015 03:12:01 +0200 Subject: [PATCH 137/146] Added browser-window event 'message' --- atom/browser/api/atom_api_window.cc | 6 ++++++ atom/browser/api/atom_api_window.h | 5 +++++ atom/browser/native_window.cc | 8 ++++++++ atom/browser/native_window.h | 4 ++++ atom/browser/native_window_observer.h | 9 +++++++++ atom/browser/native_window_views_win.cc | 2 ++ docs/api/browser-window.md | 4 ++++ 7 files changed, 38 insertions(+) diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index 62994c9c93..7daab4d1a0 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -189,6 +189,12 @@ void Window::OnExecuteWindowsCommand(const std::string& command_name) { Emit("app-command", command_name); } +#if defined(OS_WIN) +void Window::OnWindowMessage(UINT message, LPARAM l_param, WPARAM w_param) { + Emit("message", message, l_param, w_param); +} +#endif + // static mate::Wrappable* Window::New(v8::Isolate* isolate, const mate::Dictionary& options) { diff --git a/atom/browser/api/atom_api_window.h b/atom/browser/api/atom_api_window.h index 8a8ff266a8..4195641461 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -75,6 +75,11 @@ class Window : public mate::TrackableObject, void OnRendererResponsive() override; void OnExecuteWindowsCommand(const std::string& command_name) override; + #if defined(OS_WIN) + void OnWindowMessage(unsigned int message, LPARAM l_param, + WPARAM w_param) override; + #endif + // mate::Wrappable: bool IsDestroyed() const override; diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index c19bcdf467..e3abed473e 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -464,6 +464,14 @@ void NativeWindow::NotifyWindowExecuteWindowsCommand( OnExecuteWindowsCommand(command)); } +#if defined(OS_WIN) +void NativeWindow::NotifyWindowMessage(UINT message, WPARAM w_param, + LPARAM l_param) { + FOR_EACH_OBSERVER(NativeWindowObserver, observers_, + OnWindowMessage(message, w_param, l_param)); +} +#endif + scoped_ptr NativeWindow::DraggableRegionsToSkRegion( const std::vector& regions) { scoped_ptr sk_region(new SkRegion); diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index 37a59ff8a9..e32b948118 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -210,6 +210,10 @@ class NativeWindow : public base::SupportsUserData, void NotifyWindowLeaveHtmlFullScreen(); void NotifyWindowExecuteWindowsCommand(const std::string& command); + #if defined(OS_WIN) + void NotifyWindowMessage(UINT message, WPARAM w_param, LPARAM l_param); + #endif + void AddObserver(NativeWindowObserver* obs) { observers_.AddObserver(obs); } diff --git a/atom/browser/native_window_observer.h b/atom/browser/native_window_observer.h index 33ab1ecb6b..db693b47d5 100644 --- a/atom/browser/native_window_observer.h +++ b/atom/browser/native_window_observer.h @@ -11,6 +11,10 @@ #include "ui/base/window_open_disposition.h" #include "url/gurl.h" +#if defined(OS_WIN) +#include +#endif + namespace atom { class NativeWindowObserver { @@ -55,6 +59,11 @@ class NativeWindowObserver { virtual void OnWindowEnterHtmlFullScreen() {} virtual void OnWindowLeaveHtmlFullScreen() {} + // Called when window message received + #if defined(OS_WIN) + virtual void OnWindowMessage(UINT message, LPARAM l_param, WPARAM w_param) {} + #endif + // Called when renderer is hung. virtual void OnRendererUnresponsive() {} diff --git a/atom/browser/native_window_views_win.cc b/atom/browser/native_window_views_win.cc index 6092a2242a..d49683acb3 100644 --- a/atom/browser/native_window_views_win.cc +++ b/atom/browser/native_window_views_win.cc @@ -80,6 +80,8 @@ bool NativeWindowViews::ExecuteWindowsCommand(int command_id) { bool NativeWindowViews::PreHandleMSG( UINT message, WPARAM w_param, LPARAM l_param, LRESULT* result) { + NotifyWindowMessage(message, w_param, l_param); + switch (message) { case WM_COMMAND: // Handle thumbar button click message. diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index aa46590c74..99578c902e 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -232,6 +232,10 @@ Emitted when the window enters full screen state triggered by html api. Emitted when the window leaves full screen state triggered by html api. +### Event: 'message' _WINDOWS_ + +Emitted when the window receives a message from the operating system. + ### Event: 'app-command': Emitted when an [App Command](https://msdn.microsoft.com/en-us/library/windows/desktop/ms646275(v=vs.85).aspx) From 2e3a5f5a3ef9677eb19030304f31d42e12da4c6e Mon Sep 17 00:00:00 2001 From: Sitdhibong Laokok Date: Tue, 27 Oct 2015 12:09:49 +0700 Subject: [PATCH 138/146] Translate README.md --- docs-translations/th-TH/README.md | 75 +++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 docs-translations/th-TH/README.md diff --git a/docs-translations/th-TH/README.md b/docs-translations/th-TH/README.md new file mode 100644 index 0000000000..47c5082f36 --- /dev/null +++ b/docs-translations/th-TH/README.md @@ -0,0 +1,75 @@ +## คู่มือ + +* [แพลตฟอร์มที่รองรับ](tutorial/supported-platforms.md) +* [การเผยแพร่แอปพลิเคชัน](tutorial/application-distribution.md) +* [แนวทางการส่งแอปเข้า Mac App Store](tutorial/mac-app-store-submission-guide.md) +* [การบรรจุแอปพลิเคชัน](tutorial/application-packaging.md) +* [การใช้โมดูลของ Node](tutorial/using-native-node-modules.md) +* [การหาข้อผิดพลาดในกระบวนการหลัก](tutorial/debugging-main-process.md) +* [การใช้งาน Selenium และ WebDriver](tutorial/using-selenium-and-webdriver.md) +* [ส่วนเสริมของ DevTools](tutorial/devtools-extension.md) +* [การใช้งานส่วนเสริม Pepper Flash](tutorial/using-pepper-flash-plugin.md) + +## แนะนำ + +* [เริ่มต้นอย่างคราวๆ](tutorial/quick-start.md) +* [การร่วมกันของสภาพแวดล้อมบนเดสทอป](tutorial/desktop-environment-integration.md) +* [การตรวจจับเหตุการณ์ออนไลน์หรือออฟไลน์](tutorial/online-offline-events.md) + +## แหล่งอ้างอิงของ API + +* [สรุปความ](api/synopsis.md) +* [Process Object](api/process.md) +* [คำสั่งของ Chrome Command Line Switches ที่รองรับ](api/chrome-command-line-switches.md) + +### การปรับแต่ง DOM: + +* [วัตถุ `File`](api/file-object.md) +* [แท็ก `` Tag](api/web-view-tag.md) +* [ฟังก์ชัน `window.open`](api/window-open.md) + +### โมดูลสำหรับโปรเซสหลัก : + +* [app](api/app.md) +* [auto-updater](api/auto-updater.md) +* [browser-window](api/browser-window.md) +* [content-tracing](api/content-tracing.md) +* [dialog](api/dialog.md) +* [global-shortcut](api/global-shortcut.md) +* [ipc (main process)](api/ipc-main-process.md) +* [menu](api/menu.md) +* [menu-item](api/menu-item.md) +* [power-monitor](api/power-monitor.md) +* [power-save-blocker](api/power-save-blocker.md) +* [protocol](api/protocol.md) +* [session](api/session.md) +* [web-contents](api/web-contents.md) +* [tray](api/tray.md) + +### Modules for the Renderer Process (Web Page): +### โมดูลสำหรับโปรเซส Renderer (Web Page): + +* [ipc (renderer)](api/ipc-renderer.md) +* [remote](api/remote.md) +* [web-frame](api/web-frame.md) + +### Modules for Both Processes: + +* [clipboard](api/clipboard.md) +* [crash-reporter](api/crash-reporter.md) +* [native-image](api/native-image.md) +* [screen](api/screen.md) +* [shell](api/shell.md) + +## การพัฒนา + +* [ลักษณะการเขียนโค้ด](development/coding-style.md) +* [โครงสร้างไดเรคทอรี่ของซอร์สโค้ด](development/source-code-directory-structure.md) +* [Technical Differences to NW.js (formerly node-webkit)](development/atom-shell-vs-node-webkit.md) +* [ความแตกต่างทางเทคนิคจาก NW.js (หรือ node-webkit)](development/atom-shell-vs-node-webkit.md) +* [ภาพรวมการสร้างระบบ](development/build-system-overview.md) +* [ขั้นตอนการสร้าง (OS X)](development/build-instructions-osx.md) +* [ขั้นตอนการสร้าง (Windows)](development/build-instructions-windows.md) +* [ขั้นตอนการสร้าง (Linux)](development/build-instructions-linux.md) +* [Setting Up Symbol Server in debugger](development/setting-up-symbol-server.md) +* [การติดตั้งเซิร์ฟเวอร์ Symbol Server ใน debugger](development/setting-up-symbol-server.md) From 00493f64b766051279eedd686b63800cd2955f88 Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Tue, 27 Oct 2015 13:10:34 +0800 Subject: [PATCH 139/146] Win: Remove adding default file filter(*.*) in a save dialog. --- atom/browser/ui/file_dialog_win.cc | 6 ------ 1 file changed, 6 deletions(-) diff --git a/atom/browser/ui/file_dialog_win.cc b/atom/browser/ui/file_dialog_win.cc index da00dc54e2..9620c44e42 100644 --- a/atom/browser/ui/file_dialog_win.cc +++ b/atom/browser/ui/file_dialog_win.cc @@ -34,12 +34,6 @@ bool IsDirectory(const base::FilePath& path) { void ConvertFilters(const Filters& filters, std::vector* buffer, std::vector* filterspec) { - if (filters.empty()) { - COMDLG_FILTERSPEC spec = { L"All Files (*.*)", L"*.*" }; - filterspec->push_back(spec); - return; - } - buffer->reserve(filters.size() * 2); for (size_t i = 0; i < filters.size(); ++i) { const Filter& filter = filters[i]; From a84f4dc3e62a84e2cae0c90329b28ab89bd63660 Mon Sep 17 00:00:00 2001 From: Sitdhibong Laokok Date: Tue, 27 Oct 2015 12:20:54 +0700 Subject: [PATCH 140/146] Remove original msg --- docs-translations/th-TH/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs-translations/th-TH/README.md b/docs-translations/th-TH/README.md index 47c5082f36..29c8523ccc 100644 --- a/docs-translations/th-TH/README.md +++ b/docs-translations/th-TH/README.md @@ -46,7 +46,6 @@ * [web-contents](api/web-contents.md) * [tray](api/tray.md) -### Modules for the Renderer Process (Web Page): ### โมดูลสำหรับโปรเซส Renderer (Web Page): * [ipc (renderer)](api/ipc-renderer.md) From f1fad96b568687e98c6d48b0a05e54f60b1dcadb Mon Sep 17 00:00:00 2001 From: Sitdhibong Laokok Date: Tue, 27 Oct 2015 12:36:43 +0700 Subject: [PATCH 141/146] Fix missing spell --- docs-translations/th-TH/README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/docs-translations/th-TH/README.md b/docs-translations/th-TH/README.md index 29c8523ccc..3b9f520771 100644 --- a/docs-translations/th-TH/README.md +++ b/docs-translations/th-TH/README.md @@ -19,16 +19,16 @@ ## แหล่งอ้างอิงของ API * [สรุปความ](api/synopsis.md) -* [Process Object](api/process.md) -* [คำสั่งของ Chrome Command Line Switches ที่รองรับ](api/chrome-command-line-switches.md) +* [โปรเซสออบเจค](api/process.md) +* [คำสั่งสำหรับเปลี่ยนแปลงค่าของ Chrome ที่รองรับ](api/chrome-command-line-switches.md) ### การปรับแต่ง DOM: * [วัตถุ `File`](api/file-object.md) -* [แท็ก `` Tag](api/web-view-tag.md) +* [แท็ก ``](api/web-view-tag.md) * [ฟังก์ชัน `window.open`](api/window-open.md) -### โมดูลสำหรับโปรเซสหลัก : +### โมดูลสำหรับกระบวนการหลัก : * [app](api/app.md) * [auto-updater](api/auto-updater.md) @@ -46,7 +46,7 @@ * [web-contents](api/web-contents.md) * [tray](api/tray.md) -### โมดูลสำหรับโปรเซส Renderer (Web Page): +### โมดูลสำหรับกระบวนการ Renderer (เว็บเพจ): * [ipc (renderer)](api/ipc-renderer.md) * [remote](api/remote.md) @@ -64,7 +64,6 @@ * [ลักษณะการเขียนโค้ด](development/coding-style.md) * [โครงสร้างไดเรคทอรี่ของซอร์สโค้ด](development/source-code-directory-structure.md) -* [Technical Differences to NW.js (formerly node-webkit)](development/atom-shell-vs-node-webkit.md) * [ความแตกต่างทางเทคนิคจาก NW.js (หรือ node-webkit)](development/atom-shell-vs-node-webkit.md) * [ภาพรวมการสร้างระบบ](development/build-system-overview.md) * [ขั้นตอนการสร้าง (OS X)](development/build-instructions-osx.md) From 2a85bd3360e6cedad8607bc1c5ade2d635332e72 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 27 Oct 2015 14:25:42 +0800 Subject: [PATCH 142/146] Update libchromiumcontent: fix private API call --- script/lib/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/lib/config.py b/script/lib/config.py index 83468f8199..047ecf4faa 100644 --- a/script/lib/config.py +++ b/script/lib/config.py @@ -8,7 +8,7 @@ import sys BASE_URL = os.getenv('LIBCHROMIUMCONTENT_MIRROR') or \ 'http://gh-contractor-zcbenz.s3.amazonaws.com/libchromiumcontent' -LIBCHROMIUMCONTENT_COMMIT = '78e54bc39a04b758ed5167cd980cc4d9951bd629' +LIBCHROMIUMCONTENT_COMMIT = '464aff2398f619b1d4d91b9187de69803919dca2' PLATFORM = { 'cygwin': 'win32', From dacd921a045d7155252a5b8912a7286cd349d3da Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 27 Oct 2015 14:58:44 +0800 Subject: [PATCH 143/146] Revert "Win: Remove adding default file filter(*.*) in a save dialog." This reverts commit 00493f64b766051279eedd686b63800cd2955f88. --- atom/browser/ui/file_dialog_win.cc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/atom/browser/ui/file_dialog_win.cc b/atom/browser/ui/file_dialog_win.cc index 9620c44e42..da00dc54e2 100644 --- a/atom/browser/ui/file_dialog_win.cc +++ b/atom/browser/ui/file_dialog_win.cc @@ -34,6 +34,12 @@ bool IsDirectory(const base::FilePath& path) { void ConvertFilters(const Filters& filters, std::vector* buffer, std::vector* filterspec) { + if (filters.empty()) { + COMDLG_FILTERSPEC spec = { L"All Files (*.*)", L"*.*" }; + filterspec->push_back(spec); + return; + } + buffer->reserve(filters.size() * 2); for (size_t i = 0; i < filters.size(); ++i) { const Filter& filter = filters[i]; From e355532d27c333862bfac3f38a4d6e520d2dd581 Mon Sep 17 00:00:00 2001 From: Omri Litov Date: Tue, 27 Oct 2015 14:00:08 +0200 Subject: [PATCH 144/146] Change windows messages api to hook instead of emitter --- atom/browser/api/atom_api_window.cc | 35 +++++++++++++++++++++++++-- atom/browser/api/atom_api_window.h | 17 +++++++++++-- atom/browser/native_window_observer.h | 2 +- docs/api/browser-window.md | 28 ++++++++++++++++++--- 4 files changed, 73 insertions(+), 9 deletions(-) diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index 7daab4d1a0..45b57f5307 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -190,8 +190,10 @@ void Window::OnExecuteWindowsCommand(const std::string& command_name) { } #if defined(OS_WIN) -void Window::OnWindowMessage(UINT message, LPARAM l_param, WPARAM w_param) { - Emit("message", message, l_param, w_param); +void Window::OnWindowMessage(UINT message, WPARAM w_param, LPARAM l_param) { + if (IsWindowMessageHooked(message)) { + messages_callback_map_[message].Run(w_param, l_param); + } } #endif @@ -498,6 +500,29 @@ bool Window::IsMenuBarVisible() { return window_->IsMenuBarVisible(); } +#if defined(OS_WIN) +bool Window::HookWindowMessage(UINT message, + const MessageCallback& callback) { + messages_callback_map_[message] = callback; + return true; +} + +void Window::UnhookWindowMessage(UINT message) { + if (!ContainsKey(messages_callback_map_, message)) + return; + + messages_callback_map_.erase(message); +} + +bool Window::IsWindowMessageHooked(UINT message) { + return ContainsKey(messages_callback_map_, message); +} + +void Window::UnhookAllWindowMessages() { + messages_callback_map_.clear(); +} +#endif + #if defined(OS_MACOSX) void Window::ShowDefinitionForSelection() { window_->ShowDefinitionForSelection(); @@ -596,6 +621,12 @@ void Window::BuildPrototype(v8::Isolate* isolate, &Window::SetVisibleOnAllWorkspaces) .SetMethod("isVisibleOnAllWorkspaces", &Window::IsVisibleOnAllWorkspaces) +#if defined(OS_WIN) + .SetMethod("hookWindowMessage", &Window::HookWindowMessage) + .SetMethod("isWindowMessageHooked", &Window::IsWindowMessageHooked) + .SetMethod("unhookWindowMessage", &Window::UnhookWindowMessage) + .SetMethod("unhookAllWindowMessages", &Window::UnhookAllWindowMessages) +#endif #if defined(OS_MACOSX) .SetMethod("showDefinitionForSelection", &Window::ShowDefinitionForSelection) diff --git a/atom/browser/api/atom_api_window.h b/atom/browser/api/atom_api_window.h index 4195641461..870f8e1342 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -5,6 +5,7 @@ #ifndef ATOM_BROWSER_API_ATOM_API_WINDOW_H_ #define ATOM_BROWSER_API_ATOM_API_WINDOW_H_ +#include #include #include @@ -76,8 +77,8 @@ class Window : public mate::TrackableObject, void OnExecuteWindowsCommand(const std::string& command_name) override; #if defined(OS_WIN) - void OnWindowMessage(unsigned int message, LPARAM l_param, - WPARAM w_param) override; + void OnWindowMessage(UINT message, WPARAM w_param, + LPARAM l_param) override; #endif // mate::Wrappable: @@ -148,6 +149,18 @@ class Window : public mate::TrackableObject, bool IsMenuBarVisible(); void SetAspectRatio(double aspect_ratio, mate::Arguments* args); +#if defined(OS_WIN) + typedef base::Callback MessageCallback; + typedef std::map MessageCallbackMap; + MessageCallbackMap messages_callback_map_; + + bool HookWindowMessage(UINT message, + const MessageCallback& callback); + bool IsWindowMessageHooked(UINT message); + void UnhookWindowMessage(UINT message); + void UnhookAllWindowMessages(); +#endif + #if defined(OS_MACOSX) void ShowDefinitionForSelection(); #endif diff --git a/atom/browser/native_window_observer.h b/atom/browser/native_window_observer.h index db693b47d5..54004a300d 100644 --- a/atom/browser/native_window_observer.h +++ b/atom/browser/native_window_observer.h @@ -61,7 +61,7 @@ class NativeWindowObserver { // Called when window message received #if defined(OS_WIN) - virtual void OnWindowMessage(UINT message, LPARAM l_param, WPARAM w_param) {} + virtual void OnWindowMessage(UINT message, WPARAM w_param, LPARAM l_param) {} #endif // Called when renderer is hung. diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 99578c902e..d7bf5be374 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -232,10 +232,6 @@ Emitted when the window enters full screen state triggered by html api. Emitted when the window leaves full screen state triggered by html api. -### Event: 'message' _WINDOWS_ - -Emitted when the window receives a message from the operating system. - ### Event: 'app-command': Emitted when an [App Command](https://msdn.microsoft.com/en-us/library/windows/desktop/ms646275(v=vs.85).aspx) @@ -556,6 +552,30 @@ Enters or leaves the kiosk mode. Returns whether the window is in kiosk mode. +### `win.hookWindowMessage(message, callback)` _WINDOWS_ + +* `message` Integer +* `callback` Function + +Hooks a windows message. The `callback` is called when +the message is received in the WndProc. + +### `win.isWindowMessageHooked(message)` _WINDOWS_ + +* `message` Integer + +Returns `true` or `false` depending on whether the message is hooked. + +### `win.unhookWindowMessage(message)` _WINDOWS_ + +* `message` Integer + +Unhook the window message. + +### `win.unhookAllWindowMessages()` _WINDOWS_ + +Unhooks all of the window messages. + ### `win.setRepresentedFilename(filename)` _OS X_ * `filename` String From fa24e15d8bfd5371050d89a47b711aefc247566e Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Tue, 27 Oct 2015 13:10:34 +0800 Subject: [PATCH 145/146] Win: Remove adding '*.*' to file name in a save dialog. --- atom/browser/ui/file_dialog_win.cc | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/atom/browser/ui/file_dialog_win.cc b/atom/browser/ui/file_dialog_win.cc index da00dc54e2..edbb4b332b 100644 --- a/atom/browser/ui/file_dialog_win.cc +++ b/atom/browser/ui/file_dialog_win.cc @@ -79,8 +79,25 @@ class FileDialog { if (!title.empty()) GetPtr()->SetTitle(base::UTF8ToUTF16(title).c_str()); - if (!filterspec.empty()) - GetPtr()->SetDefaultExtension(filterspec.front().pszSpec); + // By default, *.* will be added to the file name if file type is "*.*". In + // Electron, we disable it to make a better experience. + // + // From MSDN: https://msdn.microsoft.com/en-us/library/windows/desktop/ + // bb775970(v=vs.85).aspx + // + // If SetDefaultExtension is not called, the dialog will not update + // automatically when user choose a new file type in the file dialog. + // + // We set file extension to the first none-wildcard extension to make + // sure the dialog will update file extension automatically. + for (size_t i = 0; i < filterspec.size(); ++i) { + if (std::wstring(filterspec[i].pszSpec) != L"*.*") { + // SetFileTypeIndex is regarded as one-based index. + GetPtr()->SetFileTypeIndex(i+1); + GetPtr()->SetDefaultExtension(filterspec[i].pszSpec); + break; + } + } SetDefaultFolder(default_path); } From f69bafd48a69054908f91b3a419491c2c504a96e Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Tue, 27 Oct 2015 20:03:28 +0800 Subject: [PATCH 146/146] Win: Fix adding '.*' to filename in callback. --- atom/browser/ui/file_dialog_win.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/atom/browser/ui/file_dialog_win.cc b/atom/browser/ui/file_dialog_win.cc index edbb4b332b..d218beaa27 100644 --- a/atom/browser/ui/file_dialog_win.cc +++ b/atom/browser/ui/file_dialog_win.cc @@ -272,7 +272,8 @@ bool ShowSaveDialog(atom::NativeWindow* parent_window, bool matched = false; for (size_t i = 0; i < filter.second.size(); ++i) { - if (base::EndsWith(file_name, filter.second[i], false)) { + if (filter.second[i] == "*" || + base::EndsWith(file_name, filter.second[i], false)) { matched = true; break;; }