diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 709696340274..115041d0082d 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -655,6 +655,14 @@ void App::OnGpuProcessCrashed(base::TerminationStatus status) { status == base::TERMINATION_STATUS_PROCESS_WAS_KILLED); } +base::FilePath App::GetAppPath() const { + return app_path_; +} + +void App::SetAppPath(const base::FilePath& app_path) { + app_path_ = app_path; +} + base::FilePath App::GetPath(mate::Arguments* args, const std::string& name) { bool succeed = false; base::FilePath path; @@ -959,6 +967,8 @@ void App::BuildPrototype( .SetMethod("isUnityRunning", base::Bind(&Browser::IsUnityRunning, browser)) #endif + .SetMethod("setAppPath", &App::SetAppPath) + .SetMethod("getAppPath", &App::GetAppPath) .SetMethod("setPath", &App::SetPath) .SetMethod("getPath", &App::GetPath) .SetMethod("setDesktopName", &App::SetDesktopName) diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index 8b276f334d5c..a87b88bc4642 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -70,6 +70,8 @@ class App : public AtomBrowserClient::Delegate, std::unique_ptr model); #endif + base::FilePath GetAppPath() const; + protected: explicit App(v8::Isolate* isolate); ~App() override; @@ -115,6 +117,8 @@ class App : public AtomBrowserClient::Delegate, void OnGpuProcessCrashed(base::TerminationStatus status) override; private: + void SetAppPath(const base::FilePath& app_path); + // Get/Set the pre-defined path in PathService. base::FilePath GetPath(mate::Arguments* args, const std::string& name); void SetPath(mate::Arguments* args, @@ -154,6 +158,8 @@ class App : public AtomBrowserClient::Delegate, // Tracks tasks requesting file icons. base::CancelableTaskTracker cancelable_task_tracker_; + base::FilePath app_path_; + DISALLOW_COPY_AND_ASSIGN(App); }; diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index c6707624913d..4cf2fee82f71 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -263,6 +263,14 @@ void Window::OnWindowSwipe(const std::string& direction) { Emit("swipe", direction); } +void Window::OnWindowSheetBegin() { + Emit("sheet-begin"); +} + +void Window::OnWindowSheetEnd() { + Emit("sheet-end"); +} + void Window::OnWindowEnterHtmlFullScreen() { Emit("enter-html-full-screen"); } diff --git a/atom/browser/api/atom_api_window.h b/atom/browser/api/atom_api_window.h index d464af58ea9d..1388b63de75a 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -79,6 +79,8 @@ class Window : public mate::TrackableObject, void OnWindowScrollTouchEnd() override; void OnWindowScrollTouchEdge() override; void OnWindowSwipe(const std::string& direction) override; + void OnWindowSheetBegin() override; + void OnWindowSheetEnd() override; void OnWindowEnterFullScreen() override; void OnWindowLeaveFullScreen() override; void OnWindowEnterHtmlFullScreen() override; diff --git a/atom/browser/atom_browser_client.cc b/atom/browser/atom_browser_client.cc index 9b096ae8aef6..77c3212e2e02 100644 --- a/atom/browser/atom_browser_client.cc +++ b/atom/browser/atom_browser_client.cc @@ -235,6 +235,11 @@ void AtomBrowserClient::AppendExtraCommandLineSwitches( } #endif + if (delegate_) { + auto app_path = static_cast(delegate_)->GetAppPath(); + command_line->AppendSwitchPath(switches::kAppPath, app_path); + } + content::WebContents* web_contents = GetWebContentsFromProcessID(process_id); if (!web_contents) return; diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index 316cc8dc2e3d..93bfc76c51e4 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -554,6 +554,16 @@ void NativeWindow::NotifyWindowSwipe(const std::string& direction) { observer.OnWindowSwipe(direction); } +void NativeWindow::NotifyWindowSheetBegin() { + for (NativeWindowObserver& observer : observers_) + observer.OnWindowSheetBegin(); +} + +void NativeWindow::NotifyWindowSheetEnd() { + for (NativeWindowObserver& observer : observers_) + observer.OnWindowSheetEnd(); +} + void NativeWindow::NotifyWindowLeaveFullScreen() { for (NativeWindowObserver& observer : observers_) observer.OnWindowLeaveFullScreen(); diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index 56702daef63b..48b56e8b47ea 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -233,6 +233,8 @@ class NativeWindow : public base::SupportsUserData, void NotifyWindowScrollTouchEnd(); void NotifyWindowScrollTouchEdge(); void NotifyWindowSwipe(const std::string& direction); + void NotifyWindowSheetBegin(); + void NotifyWindowSheetEnd(); void NotifyWindowEnterFullScreen(); void NotifyWindowLeaveFullScreen(); void NotifyWindowEnterHtmlFullScreen(); diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index b695f8eafa89..9dc119e239dd 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -313,6 +313,14 @@ bool ScopedDisableResize::disable_resize_ = false; return rect; } +- (void)windowWillBeginSheet:(NSNotification *)notification { + shell_->NotifyWindowSheetBegin(); +} + +- (void)windowDidEndSheet:(NSNotification *)notification { + shell_->NotifyWindowSheetEnd(); +} + @end @interface AtomPreviewItem : NSObject diff --git a/atom/browser/native_window_observer.h b/atom/browser/native_window_observer.h index 3b8d86e6fb0f..191f1bcab492 100644 --- a/atom/browser/native_window_observer.h +++ b/atom/browser/native_window_observer.h @@ -67,6 +67,8 @@ class NativeWindowObserver { virtual void OnWindowScrollTouchEnd() {} virtual void OnWindowScrollTouchEdge() {} virtual void OnWindowSwipe(const std::string& direction) {} + virtual void OnWindowSheetBegin() {} + virtual void OnWindowSheetEnd() {} virtual void OnWindowEnterFullScreen() {} virtual void OnWindowLeaveFullScreen() {} virtual void OnWindowEnterHtmlFullScreen() {} diff --git a/atom/common/options_switches.cc b/atom/common/options_switches.cc index 288fcd3a0753..2f1c0368f35e 100644 --- a/atom/common/options_switches.cc +++ b/atom/common/options_switches.cc @@ -159,6 +159,9 @@ const char kSecureSchemes[] = "secure-schemes"; // The browser process app model ID const char kAppUserModelId[] = "app-user-model-id"; +// The application path +const char kAppPath[] = "app-path"; + // The command line switch versions of the options. const char kBackgroundColor[] = "background-color"; const char kPreloadScript[] = "preload"; diff --git a/atom/common/options_switches.h b/atom/common/options_switches.h index 9e1a71ca5bda..69e7af029e15 100644 --- a/atom/common/options_switches.h +++ b/atom/common/options_switches.h @@ -81,6 +81,7 @@ extern const char kStandardSchemes[]; extern const char kRegisterServiceWorkerSchemes[]; extern const char kSecureSchemes[]; extern const char kAppUserModelId[]; +extern const char kAppPath[]; extern const char kBackgroundColor[]; extern const char kPreloadScript[]; diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 2c720fdfac11..cd55ba0bbda3 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -498,6 +498,14 @@ Returns: Emitted on 3-finger swipe. Possible directions are `up`, `right`, `down`, `left`. +#### Event: 'sheet-begin' _macOS_ + +Emitted when the window opens a sheet. + +#### Event: 'sheet-end' _macOS_ + +Emitted when the window has closed a sheet. + ### Static Methods The `BrowserWindow` class has the following static methods: diff --git a/docs/api/menu.md b/docs/api/menu.md index 72d8164270dc..beceb2059c6c 100644 --- a/docs/api/menu.md +++ b/docs/api/menu.md @@ -28,6 +28,10 @@ effect on macOS. Returns `Menu` - The application menu, if set, or `null`, if not set. +**Note:** The returned `Menu` instance doesn't support dynamic addition or +removal of menu items. [Instance properties](#instance-properties) can still +be dynamically modified. + #### `Menu.sendActionToFirstResponder(action)` _macOS_ * `action` String diff --git a/docs/api/webview-tag.md b/docs/api/webview-tag.md index 9c09c8ef730e..1da5679c18ad 100644 --- a/docs/api/webview-tag.md +++ b/docs/api/webview-tag.md @@ -12,7 +12,8 @@ rendered. Unlike an `iframe`, the `webview` runs in a separate process than your app. It doesn't have the same permissions as your web page and all interactions between your app and embedded content will be asynchronous. This keeps your app -safe from the embedded content. +safe from the embedded content. **Note:** Most methods called on the +webview from the host page require a syncronous call to the main process. For security purposes, `webview` can only be used in `BrowserWindow`s that have `nodeIntegration` enabled. diff --git a/docs/development/build-instructions-windows.md b/docs/development/build-instructions-windows.md index 4f7335e108fd..6bfd24257677 100644 --- a/docs/development/build-instructions-windows.md +++ b/docs/development/build-instructions-windows.md @@ -10,6 +10,9 @@ Follow the guidelines below for building Electron on Windows. * [Python 2.7](http://www.python.org/download/releases/2.7/) * [Node.js](http://nodejs.org/download/) * [Git](http://git-scm.com) +* [Debugging Tools for Windows](https://msdn.microsoft.com/en-us/library/windows/hardware/ff551063.aspx) + if you plan on creating a full distribution since `symstore.exe` is used for + creating a symbol store from `.pdb` files. If you don't currently have a Windows installation, [dev.microsoftedge.com](https://developer.microsoft.com/en-us/microsoft-edge/tools/vms/) diff --git a/lib/browser/api/app.js b/lib/browser/api/app.js index c5865f2242b8..d1ca60280e93 100644 --- a/lib/browser/api/app.js +++ b/lib/browser/api/app.js @@ -12,11 +12,7 @@ const {EventEmitter} = require('events') Object.setPrototypeOf(App.prototype, EventEmitter.prototype) -let appPath = null - Object.assign(app, { - getAppPath () { return appPath }, - setAppPath (path) { appPath = path }, setApplicationMenu (menu) { return Menu.setApplicationMenu(menu) }, diff --git a/lib/renderer/init.js b/lib/renderer/init.js index 38441c9ec143..0306463bc486 100644 --- a/lib/renderer/init.js +++ b/lib/renderer/init.js @@ -56,6 +56,7 @@ electron.ipcRenderer.on('ELECTRON_INTERNAL_RENDERER_ASYNC_WEB_FRAME_METHOD', (ev let nodeIntegration = 'false' let preloadScript = null let isBackgroundPage = false +let appPath = null for (let arg of process.argv) { if (arg.indexOf('--guest-instance-id=') === 0) { // This is a guest web view. @@ -69,6 +70,8 @@ for (let arg of process.argv) { preloadScript = arg.substr(arg.indexOf('=') + 1) } else if (arg === '--background-page') { isBackgroundPage = true + } else if (arg.indexOf('--app-path=') === 0) { + appPath = arg.substr(arg.indexOf('=') + 1) } } @@ -116,6 +119,11 @@ if (nodeIntegration === 'true') { } else { global.__filename = __filename global.__dirname = __dirname + + if (appPath) { + // Search for module under the app directory + module.paths = module.paths.concat(Module._nodeModulePaths(appPath)) + } } // Redirect window.onerror to uncaughtException. diff --git a/script/cibuild b/script/cibuild index dc2d9d081045..419404fca8a5 100755 --- a/script/cibuild +++ b/script/cibuild @@ -86,8 +86,6 @@ def main(): run_script('create-dist.py') run_script('upload.py') else: - if PLATFORM == 'win32': - os.environ['OUTPUT_TO_FILE'] = 'output.log' run_script('build.py', ['-c', 'D']) if PLATFORM == 'win32' or target_arch == 'x64': run_script('test.py', ['--ci']) diff --git a/spec/api-browser-window-spec.js b/spec/api-browser-window-spec.js index a50ff0bc0d07..5998768bd72f 100644 --- a/spec/api-browser-window-spec.js +++ b/spec/api-browser-window-spec.js @@ -1191,6 +1191,54 @@ describe('BrowserWindow module', function () { }) }) + describe('sheet-begin event', function () { + if (process.platform !== 'darwin') { + return + } + + let sheet = null + + afterEach(function () { + return closeWindow(sheet, {assertSingleWindow: false}).then(function () { sheet = null }) + }) + + it('emits when window opens a sheet', function (done) { + w.show() + w.once('sheet-begin', function () { + sheet.close() + done() + }) + sheet = new BrowserWindow({ + modal: true, + parent: w + }) + }) + }) + + describe('sheet-end event', function () { + if (process.platform !== 'darwin') { + return + } + + let sheet = null + + afterEach(function () { + return closeWindow(sheet, {assertSingleWindow: false}).then(function () { sheet = null }) + }) + + it('emits when window has closed a sheet', function (done) { + w.show() + sheet = new BrowserWindow({ + modal: true, + parent: w + }) + w.once('sheet-end', function () { + done() + }) + sheet.close() + }) + }) + describe('beginFrameSubscription method', function () { // This test is too slow, only test it on CI. if (!isCI) return diff --git a/spec/modules-spec.js b/spec/modules-spec.js index 5f8271752711..ba87ed3466a8 100644 --- a/spec/modules-spec.js +++ b/spec/modules-spec.js @@ -1,60 +1,63 @@ const assert = require('assert') const Module = require('module') const path = require('path') -const temp = require('temp') +const {remote} = require('electron') +const {BrowserWindow} = remote +const {closeWindow} = require('./window-helpers') -describe('third-party module', function () { +describe('modules support', function () { var fixtures = path.join(__dirname, 'fixtures') - temp.track() - if (process.platform !== 'win32' || process.execPath.toLowerCase().indexOf('\\out\\d\\') === -1) { - describe('runas', function () { - it('can be required in renderer', function () { - require('runas') + describe('third-party module', function () { + if (process.platform !== 'win32' || process.execPath.toLowerCase().indexOf('\\out\\d\\') === -1) { + describe('runas', function () { + it('can be required in renderer', function () { + require('runas') + }) + + it('can be required in node binary', function (done) { + var runas = path.join(fixtures, 'module', 'runas.js') + var child = require('child_process').fork(runas) + child.on('message', function (msg) { + assert.equal(msg, 'ok') + done() + }) + }) }) - it('can be required in node binary', function (done) { - var runas = path.join(fixtures, 'module', 'runas.js') - var child = require('child_process').fork(runas) - child.on('message', function (msg) { - assert.equal(msg, 'ok') - done() + describe('ffi', function () { + if (process.platform === 'win32') return + + it('does not crash', function () { + var ffi = require('ffi') + var libm = ffi.Library('libm', { + ceil: ['double', ['double']] + }) + assert.equal(libm.ceil(1.5), 2) + }) + }) + } + + describe('q', function () { + var Q = require('q') + describe('Q.when', function () { + it('emits the fullfil callback', function (done) { + Q(true).then(function (val) { + assert.equal(val, true) + done() + }) }) }) }) - describe('ffi', function () { - if (process.platform === 'win32') return - - it('does not crash', function () { - var ffi = require('ffi') - var libm = ffi.Library('libm', { - ceil: ['double', ['double']] + describe('coffee-script', function () { + it('can be registered and used to require .coffee files', function () { + assert.doesNotThrow(function () { + require('coffee-script').register() }) - assert.equal(libm.ceil(1.5), 2) + assert.strictEqual(require('./fixtures/module/test.coffee'), true) }) }) - } - - describe('q', function () { - var Q = require('q') - describe('Q.when', function () { - it('emits the fullfil callback', function (done) { - Q(true).then(function (val) { - assert.equal(val, true) - done() - }) - }) - }) - }) - - describe('coffee-script', function () { - it('can be registered and used to require .coffee files', function () { - assert.doesNotThrow(function () { - require('coffee-script').register() - }) - assert.strictEqual(require('./fixtures/module/test.coffee'), true) - }) }) describe('global variables', function () { @@ -76,56 +79,79 @@ describe('third-party module', function () { }) }) }) -}) -describe('Module._nodeModulePaths', function () { - describe('when the path is inside the resources path', function () { - it('does not include paths outside of the resources path', function () { - let modulePath = process.resourcesPath - assert.deepEqual(Module._nodeModulePaths(modulePath), [ - path.join(process.resourcesPath, 'node_modules') - ]) + describe('Module._nodeModulePaths', function () { + describe('when the path is inside the resources path', function () { + it('does not include paths outside of the resources path', function () { + let modulePath = process.resourcesPath + assert.deepEqual(Module._nodeModulePaths(modulePath), [ + path.join(process.resourcesPath, 'node_modules') + ]) - modulePath = process.resourcesPath + '-foo' - let nodeModulePaths = Module._nodeModulePaths(modulePath) - assert(nodeModulePaths.includes(path.join(modulePath, 'node_modules'))) - assert(nodeModulePaths.includes(path.join(modulePath, '..', 'node_modules'))) + modulePath = process.resourcesPath + '-foo' + let nodeModulePaths = Module._nodeModulePaths(modulePath) + assert(nodeModulePaths.includes(path.join(modulePath, 'node_modules'))) + assert(nodeModulePaths.includes(path.join(modulePath, '..', 'node_modules'))) - modulePath = path.join(process.resourcesPath, 'foo') - assert.deepEqual(Module._nodeModulePaths(modulePath), [ - path.join(process.resourcesPath, 'foo', 'node_modules'), - path.join(process.resourcesPath, 'node_modules') - ]) + modulePath = path.join(process.resourcesPath, 'foo') + assert.deepEqual(Module._nodeModulePaths(modulePath), [ + path.join(process.resourcesPath, 'foo', 'node_modules'), + path.join(process.resourcesPath, 'node_modules') + ]) - modulePath = path.join(process.resourcesPath, 'node_modules', 'foo') - assert.deepEqual(Module._nodeModulePaths(modulePath), [ - path.join(process.resourcesPath, 'node_modules', 'foo', 'node_modules'), - path.join(process.resourcesPath, 'node_modules') - ]) + modulePath = path.join(process.resourcesPath, 'node_modules', 'foo') + assert.deepEqual(Module._nodeModulePaths(modulePath), [ + path.join(process.resourcesPath, 'node_modules', 'foo', 'node_modules'), + path.join(process.resourcesPath, 'node_modules') + ]) - modulePath = path.join(process.resourcesPath, 'node_modules', 'foo', 'bar') - assert.deepEqual(Module._nodeModulePaths(modulePath), [ - path.join(process.resourcesPath, 'node_modules', 'foo', 'bar', 'node_modules'), - path.join(process.resourcesPath, 'node_modules', 'foo', 'node_modules'), - path.join(process.resourcesPath, 'node_modules') - ]) + modulePath = path.join(process.resourcesPath, 'node_modules', 'foo', 'bar') + assert.deepEqual(Module._nodeModulePaths(modulePath), [ + path.join(process.resourcesPath, 'node_modules', 'foo', 'bar', 'node_modules'), + path.join(process.resourcesPath, 'node_modules', 'foo', 'node_modules'), + path.join(process.resourcesPath, 'node_modules') + ]) - modulePath = path.join(process.resourcesPath, 'node_modules', 'foo', 'node_modules', 'bar') - assert.deepEqual(Module._nodeModulePaths(modulePath), [ - path.join(process.resourcesPath, 'node_modules', 'foo', 'node_modules', 'bar', 'node_modules'), - path.join(process.resourcesPath, 'node_modules', 'foo', 'node_modules'), - path.join(process.resourcesPath, 'node_modules') - ]) + modulePath = path.join(process.resourcesPath, 'node_modules', 'foo', 'node_modules', 'bar') + assert.deepEqual(Module._nodeModulePaths(modulePath), [ + path.join(process.resourcesPath, 'node_modules', 'foo', 'node_modules', 'bar', 'node_modules'), + path.join(process.resourcesPath, 'node_modules', 'foo', 'node_modules'), + path.join(process.resourcesPath, 'node_modules') + ]) + }) + }) + + describe('when the path is outside the resources path', function () { + it('includes paths outside of the resources path', function () { + let modulePath = path.resolve('/foo') + assert.deepEqual(Module._nodeModulePaths(modulePath), [ + path.join(modulePath, 'node_modules'), + path.resolve('/node_modules') + ]) + }) }) }) - describe('when the path is outside the resources path', function () { - it('includes paths outside of the resources path', function () { - let modulePath = path.resolve('/foo') - assert.deepEqual(Module._nodeModulePaths(modulePath), [ - path.join(modulePath, 'node_modules'), - path.resolve('/node_modules') - ]) + describe('require', () => { + describe('when loaded URL is not file: protocol', () => { + let w + + beforeEach(() => { + w = new BrowserWindow({ + show: false + }) + }) + + afterEach(async () => { + await closeWindow(w) + w = null + }) + + it('searches for module under app directory', async () => { + w.loadURL('about:blank') + const result = await w.webContents.executeJavaScript('typeof require("q").when') + assert.equal(result, 'function') + }) }) }) }) diff --git a/spec/node-spec.js b/spec/node-spec.js index 8f1d7163bbbb..678b8e9490fd 100644 --- a/spec/node-spec.js +++ b/spec/node-spec.js @@ -85,7 +85,7 @@ describe('node feature', function () { child.stdout.on('data', (chunk) => { data += String(chunk) }) - child.on('exit', (code) => { + child.on('close', (code) => { assert.equal(code, 0) assert.equal(data, 'pipes stdio') done()