diff --git a/atom/browser/api/atom_api_event.cc b/atom/browser/api/atom_api_event.cc new file mode 100644 index 0000000000..da504b0ff2 --- /dev/null +++ b/atom/browser/api/atom_api_event.cc @@ -0,0 +1,26 @@ +// Copyright (c) 2018 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/api/event_emitter.h" +#include "atom/common/node_includes.h" +#include "native_mate/dictionary.h" + +namespace { + +v8::Local CreateWithSender(v8::Isolate* isolate, + v8::Local sender) { + return mate::internal::CreateJSEvent(isolate, sender, nullptr, nullptr); +} + +void Initialize(v8::Local exports, + v8::Local unused, + v8::Local context, + void* priv) { + mate::Dictionary dict(context->GetIsolate(), exports); + dict.SetMethod("createWithSender", &CreateWithSender); +} + +} // namespace + +NODE_BUILTIN_MODULE_CONTEXT_AWARE(atom_browser_event, Initialize) diff --git a/atom/common/node_bindings.cc b/atom/common/node_bindings.cc index 3ecc867ab0..9acc6bf02e 100644 --- a/atom/common/node_bindings.cc +++ b/atom/common/node_bindings.cc @@ -36,6 +36,7 @@ V(atom_browser_debugger) \ V(atom_browser_dialog) \ V(atom_browser_download_item) \ + V(atom_browser_event) \ V(atom_browser_global_shortcut) \ V(atom_browser_in_app_purchase) \ V(atom_browser_menu) \ diff --git a/docs/api/app.md b/docs/api/app.md index 16bde08b24..0fcbc3f263 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -399,6 +399,30 @@ non-minimized. This event is guaranteed to be emitted after the `ready` event of `app` gets emitted. +### Event: 'remote-require' + +Returns: + +* `event` Event +* `webContents` [WebContents](web-contents.md) +* `moduleName` String + +Emitted when `remote.require()` is called in the renderer process of `webContents`. +Calling `event.preventDefault()` will prevent the module from being returned. +Custom value can be returned by setting `event.returnValue`. + +### Event: 'remote-get-global' + +Returns: + +* `event` Event +* `webContents` [WebContents](web-contents.md) +* `globalName` String + +Emitted when `remote.getGlobal()` is called in the renderer process of `webContents`. +Calling `event.preventDefault()` will prevent the global from being returned. +Custom value can be returned by setting `event.returnValue`. + ## Methods The `app` object has the following methods: diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 361701653e..eb7d2b03c3 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -663,6 +663,28 @@ Returns: Emitted when the associated window logs a console message. Will not be emitted for windows with *offscreen rendering* enabled. +#### Event: 'remote-require' + +Returns: + +* `event` Event +* `moduleName` String + +Emitted when `remote.require()` is called in the renderer process. +Calling `event.preventDefault()` will prevent the module from being returned. +Custom value can be returned by setting `event.returnValue`. + +#### Event: 'remote-get-global' + +Returns: + +* `event` Event +* `globalName` String + +Emitted when `remote.getGlobal()` is called in the renderer process. +Calling `event.preventDefault()` will prevent the global from being returned. +Custom value can be returned by setting `event.returnValue`. + ### Instance Methods #### `contents.loadURL(url[, options])` diff --git a/filenames.gni b/filenames.gni index e45a0cd777..28362d87d9 100644 --- a/filenames.gni +++ b/filenames.gni @@ -133,6 +133,7 @@ filenames = { "atom/browser/api/atom_api_dialog.cc", "atom/browser/api/atom_api_download_item.cc", "atom/browser/api/atom_api_download_item.h", + "atom/browser/api/atom_api_event.cc", "atom/browser/api/atom_api_global_shortcut.cc", "atom/browser/api/atom_api_global_shortcut.h", "atom/browser/api/atom_api_in_app_purchase.cc", diff --git a/lib/browser/api/web-contents.js b/lib/browser/api/web-contents.js index b1584dbb0a..51ff1685cc 100644 --- a/lib/browser/api/web-contents.js +++ b/lib/browser/api/web-contents.js @@ -338,6 +338,14 @@ WebContents.prototype._init = function () { }) }) + this.on('remote-require', (event, ...args) => { + app.emit('remote-require', event, this, ...args) + }) + + this.on('remote-get-global', (event, ...args) => { + app.emit('remote-get-global', event, this, ...args) + }) + deprecate.event(this, 'did-get-response-details', '-did-get-response-details') deprecate.event(this, 'did-get-redirect-request', '-did-get-redirect-request') diff --git a/lib/browser/rpc-server.js b/lib/browser/rpc-server.js index 878910725e..68bc90b617 100644 --- a/lib/browser/rpc-server.js +++ b/lib/browser/rpc-server.js @@ -7,6 +7,7 @@ const fs = require('fs') const os = require('os') const path = require('path') const v8Util = process.atomBinding('v8_util') +const eventBinding = process.atomBinding('event') const { isPromise } = electron @@ -280,16 +281,38 @@ const handleRemoteCommand = function (channel, handler) { }) } -handleRemoteCommand('ELECTRON_BROWSER_REQUIRE', function (event, contextId, module) { - return valueToMeta(event.sender, contextId, process.mainModule.require(module)) +handleRemoteCommand('ELECTRON_BROWSER_REQUIRE', function (event, contextId, moduleName) { + const customEvent = eventBinding.createWithSender(event.sender) + event.sender.emit('remote-require', customEvent, moduleName) + + if (customEvent.defaultPrevented) { + if (typeof customEvent.returnValue === 'undefined') { + throw new Error(`Invalid module: ${moduleName}`) + } + } else { + customEvent.returnValue = process.mainModule.require(moduleName) + } + + return valueToMeta(event.sender, contextId, customEvent.returnValue) }) handleRemoteCommand('ELECTRON_BROWSER_GET_BUILTIN', function (event, contextId, module) { return valueToMeta(event.sender, contextId, electron[module]) }) -handleRemoteCommand('ELECTRON_BROWSER_GLOBAL', function (event, contextId, name) { - return valueToMeta(event.sender, contextId, global[name]) +handleRemoteCommand('ELECTRON_BROWSER_GLOBAL', function (event, contextId, globalName) { + const customEvent = eventBinding.createWithSender(event.sender) + event.sender.emit('remote-get-global', customEvent, globalName) + + if (customEvent.defaultPrevented) { + if (typeof customEvent.returnValue === 'undefined') { + throw new Error(`Invalid global: ${globalName}`) + } + } else { + customEvent.returnValue = global[globalName] + } + + return valueToMeta(event.sender, contextId, customEvent.returnValue) }) handleRemoteCommand('ELECTRON_BROWSER_CURRENT_WINDOW', function (event, contextId) { diff --git a/spec/api-app-spec.js b/spec/api-app-spec.js index 9f2e868b6f..f5b6c46408 100644 --- a/spec/api-app-spec.js +++ b/spec/api-app-spec.js @@ -358,6 +358,28 @@ describe('app module', () => { }) w = new BrowserWindow({ show: false }) }) + + it('should emit remote-require event when remote.require() is invoked', (done) => { + app.once('remote-require', (event, webContents, moduleName) => { + expect(webContents).to.equal(w.webContents) + expect(moduleName).to.equal('test') + done() + }) + w = new BrowserWindow({ show: false }) + w.loadURL('about:blank') + w.webContents.executeJavaScript(`require('electron').remote.require('test')`) + }) + + it('should emit remote-get-global event when remote.getGlobal() is invoked', (done) => { + app.once('remote-get-global', (event, webContents, globalName) => { + expect(webContents).to.equal(w.webContents) + expect(globalName).to.equal('test') + done() + }) + w = new BrowserWindow({ show: false }) + w.loadURL('about:blank') + w.webContents.executeJavaScript(`require('electron').remote.getGlobal('test')`) + }) }) describe('app.setBadgeCount', () => { diff --git a/spec/api-remote-spec.js b/spec/api-remote-spec.js index 43763d0f3e..080cea1f2c 100644 --- a/spec/api-remote-spec.js +++ b/spec/api-remote-spec.js @@ -1,11 +1,16 @@ 'use strict' const assert = require('assert') +const chai = require('chai') +const dirtyChai = require('dirty-chai') const path = require('path') const { closeWindow } = require('./window-helpers') const { resolveGetters } = require('./assert-helpers') -const { remote } = require('electron') +const { remote, ipcRenderer } = require('electron') +const { expect } = chai + +chai.use(dirtyChai) const comparePaths = (path1, path2) => { if (process.platform === 'win32') { @@ -22,7 +27,29 @@ describe('remote module', () => { afterEach(() => closeWindow(w).then(() => { w = null })) + describe('remote.getGlobal', () => { + it('can return custom values', () => { + ipcRenderer.send('handle-next-remote-get-global', { test: 'Hello World!' }) + expect(remote.getGlobal('test')).to.be.equal('Hello World!') + }) + + it('throws when no returnValue set', () => { + ipcRenderer.send('handle-next-remote-get-global') + expect(() => remote.getGlobal('process')).to.throw('Invalid global: process') + }) + }) + describe('remote.require', () => { + it('can return custom values', () => { + ipcRenderer.send('handle-next-remote-require', { test: 'Hello World!' }) + expect(remote.require('test')).to.be.equal('Hello World!') + }) + + it('throws when no returnValue set', () => { + ipcRenderer.send('handle-next-remote-require') + expect(() => remote.require('electron')).to.throw('Invalid module: electron') + }) + it('should returns same object for the same module', () => { const dialog1 = remote.require('electron') const dialog2 = remote.require('electron') diff --git a/spec/static/main.js b/spec/static/main.js index 17c0566e1f..16c0f202ee 100644 --- a/spec/static/main.js +++ b/spec/static/main.js @@ -242,6 +242,24 @@ app.on('ready', function () { }) }) +ipcMain.on('handle-next-remote-require', function (event, modulesMap = {}) { + event.sender.once('remote-require', (event, moduleName) => { + event.preventDefault() + if (modulesMap.hasOwnProperty(moduleName)) { + event.returnValue = modulesMap[moduleName] + } + }) +}) + +ipcMain.on('handle-next-remote-get-global', function (event, globalsMap = {}) { + event.sender.once('remote-get-global', (event, globalName) => { + event.preventDefault() + if (globalsMap.hasOwnProperty(globalName)) { + event.returnValue = globalsMap[globalName] + } + }) +}) + ipcMain.on('set-client-certificate-option', function (event, skip) { app.once('select-client-certificate', function (event, webContents, url, list, callback) { event.preventDefault()