Browse Source

Refactor sandboxed renderer init scripts

This change gives sandboxed renderer scripts a similar structure to what already
exists in the lib/{browser,renderer,common} directories.

It also allows sandboxed renderer initialization to share more code with
non-sandboxed renderers (ipcRenderer is now imported directly from
lib/renderer/api/ipc-renderer).
Thiago de Arruda 8 years ago
parent
commit
cd05834d96

+ 1 - 1
atom/renderer/atom_sandboxed_renderer_client.cc

@@ -34,7 +34,7 @@ namespace atom {
 
 namespace {
 
-const std::string kIpcKey = "ipc";
+const std::string kIpcKey = "ipcNative";
 const std::string kModuleCacheKey = "native-module-cache";
 
 

+ 2 - 0
electron.gyp

@@ -452,6 +452,8 @@
             'browserify',
             '--',
             'lib/sandboxed_renderer/init.js',
+            '-r',
+            './lib/sandboxed_renderer/api/exports/electron.js:electron',
             '-o',
             '<@(_outputs)',
           ],

+ 1 - 0
filenames.gypi

@@ -81,6 +81,7 @@
     'browserify_entries': [
       'lib/renderer/api/ipc-renderer-setup.js',
       'lib/sandboxed_renderer/init.js',
+      'lib/sandboxed_renderer/api/exports/electron.js',
     ],
     'isolated_context_browserify_entries': [
       'lib/renderer/window-setup.js',

+ 8 - 0
lib/sandboxed_renderer/api/exports/electron.js

@@ -0,0 +1,8 @@
+Object.defineProperties(exports, {
+  ipcRenderer: {
+    enumerable: true,
+    get: function () {
+      return require('../ipc-renderer')
+    }
+  }
+})

+ 20 - 0
lib/sandboxed_renderer/api/ipc-renderer.js

@@ -0,0 +1,20 @@
+const ipcRenderer = require('../../renderer/api/ipc-renderer')
+
+const v8Util = process.atomBinding('v8_util')
+const ipcNative = process.atomBinding('ipc')
+
+// AtomSandboxedRendererClient will look for the "ipcNative" hidden object when
+// invoking the `onMessage`/`onExit` callbacks. We could reuse "ipc" and assign
+// `onMessage`/`onExit` directly to `ipcRenderer`, but it is better to separate
+// private/public APIs.
+v8Util.setHiddenValue(global, 'ipcNative', ipcNative)
+
+ipcNative.onMessage = function (channel, args) {
+  ipcRenderer.emit(channel, {sender: ipcRenderer}, ...args)
+}
+
+ipcNative.onExit = function () {
+  process.emit('exit')
+}
+
+module.exports = ipcRenderer

+ 45 - 36
lib/sandboxed_renderer/init.js

@@ -5,35 +5,39 @@
 /* global binding, preloadPath, Buffer */
 const events = require('events')
 
-const atomBinding = require('../common/atom-binding-setup')(binding.get, 'renderer')
+process.atomBinding = require('../common/atom-binding-setup')(binding.get, 'renderer')
 
-const v8Util = atomBinding('v8_util')
-const ipc = atomBinding('ipc')
-
-const ipcRenderer = new events.EventEmitter()
-const proc = new events.EventEmitter()
-// eval in window scope:
-// http://www.ecma-international.org/ecma-262/5.1/#sec-10.4.2
-const geval = eval
-
-require('../renderer/api/ipc-renderer-setup')(ipcRenderer, ipc)
-
-ipc.onMessage = function (channel, args) {
-  ipcRenderer.emit(channel, {sender: ipcRenderer}, ...args)
-}
-
-ipc.onExit = function () {
-  proc.emit('exit')
+const v8Util = process.atomBinding('v8_util')
+// The `lib/renderer/api/ipc-renderer.js` module looks for the ipc object in the
+// "ipc" hidden value
+v8Util.setHiddenValue(global, 'ipc', new events.EventEmitter())
+// The process object created by browserify is not an event emitter, fix it so
+// the API is more compatible with non-sandboxed renderers.
+for (let prop of Object.keys(events.EventEmitter.prototype)) {
+  if (process.hasOwnProperty(prop)) {
+    delete process[prop]
+  }
 }
+Object.setPrototypeOf(process, events.EventEmitter.prototype)
 
-v8Util.setHiddenValue(global, 'ipc', ipc)
-
+const electron = require('electron')
 const preloadModules = new Map([
-  ['electron', {
-    ipcRenderer: ipcRenderer
-  }]
+  ['electron', electron]
 ])
 
+// Fetch the preload script. This needs to be done through the browser process
+// since we may not have filesystem access in a sandboxed renderer.
+let preloadSrc = electron.ipcRenderer.sendSync('ELECTRON_BROWSER_READ_FILE', preloadPath)
+if (preloadSrc.err) {
+  throw new Error(preloadSrc.err)
+}
+
+// Pass different process object to the preload script(which should not have
+// access to things like `process.atomBinding`).
+const preloadProcess = new events.EventEmitter()
+process.on('exit', () => preloadProcess.emit('exit'))
+
+// This is the `require` function that will be visible to the preload script
 function preloadRequire (module) {
   if (preloadModules.has(module)) {
     return preloadModules.get(module)
@@ -41,26 +45,31 @@ function preloadRequire (module) {
   throw new Error('module not found')
 }
 
-// Fetch the source for the preload
-let preloadSrc = ipcRenderer.sendSync('ELECTRON_BROWSER_READ_FILE', preloadPath)
-if (preloadSrc.err) {
-  throw new Error(preloadSrc.err)
-}
-
-// Wrap the source into a function receives a `require` function as argument.
-// Browserify bundles can make use of this, as explained in:
-// https://github.com/substack/node-browserify#multiple-bundles
+// Wrap the script into a function executed in global scope. It won't have
+// access to the current scope, so we'll expose a few objects as arguments:
+//
+// - `require`: The `preloadRequire` function
+// - `process`: The `preloadProcess` object
+// - `Buffer`: Browserify `Buffer` implementation
+// - `global`: The window object, which is aliased to `global` by browserify.
+//
+// Browserify bundles can make use of an external require function as explained
+// in https://github.com/substack/node-browserify#multiple-bundles, so electron
+// apps can use multi-module preload scripts in sandboxed renderers.
 //
-// For example, the user can create a browserify bundle with:
+// For example, the user can create a bundle with:
 //
 //     $ browserify -x electron preload.js > renderer.js
 //
 // and any `require('electron')` calls in `preload.js` will work as expected
-// since browserify won't try to include `electron` in the bundle and will fall
-// back to the `preloadRequire` function above.
+// since browserify won't try to include `electron` in the bundle, falling back
+// to the `preloadRequire` function above.
 let preloadWrapperSrc = `(function(require, process, Buffer, global) {
 ${preloadSrc.data}
 })`
 
+// eval in window scope:
+// http://www.ecma-international.org/ecma-262/5.1/#sec-10.4.2
+const geval = eval
 let preloadFn = geval(preloadWrapperSrc)
-preloadFn(preloadRequire, proc, Buffer, global)
+preloadFn(preloadRequire, preloadProcess, Buffer, global)