From d723173bc701cda9a4de6d4864ffda92a5036593 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 24 Apr 2013 16:43:01 +0800 Subject: [PATCH] Initial RPC API implementation. Basic usage is: remote = require 'remote' Window = remote.require 'window' w = new Window { width: 800, height: 600 } Still need to do: * Beter support for Array type. * Remote objects should cheat devtools. * Support cross-process callbacks. --- atom.gyp | 4 ++ browser/api/lib/window.coffee | 20 --------- browser/atom/atom.coffee | 11 ++++- browser/atom/objects_registry.coffee | 22 ++++++++++ browser/atom/rpc_server.coffee | 61 ++++++++++++++++++++++++++++ browser/default_app/main.js | 4 +- renderer/api/lib/remote.coffee | 46 +++++++++++++++++++++ vendor/node | 2 +- 8 files changed, 145 insertions(+), 25 deletions(-) create mode 100644 browser/atom/objects_registry.coffee create mode 100644 browser/atom/rpc_server.coffee create mode 100644 renderer/api/lib/remote.coffee diff --git a/atom.gyp b/atom.gyp index 832e3e563..2b4bb996c 100644 --- a/atom.gyp +++ b/atom.gyp @@ -8,9 +8,13 @@ 'coffee_sources': [ 'browser/api/lib/atom.coffee', 'browser/api/lib/ipc.coffee', + 'browser/api/lib/.coffee', 'browser/api/lib/window.coffee', 'browser/atom/atom.coffee', + 'browser/atom/objects_registry.coffee', + 'browser/atom/rpc_server.coffee', 'renderer/api/lib/ipc.coffee', + 'renderer/api/lib/remote.coffee', ], 'lib_sources': [ 'app/atom_main_delegate.cc', diff --git a/browser/api/lib/window.coffee b/browser/api/lib/window.coffee index 753233946..9e6deabf0 100644 --- a/browser/api/lib/window.coffee +++ b/browser/api/lib/window.coffee @@ -3,24 +3,4 @@ EventEmitter = require('events').EventEmitter Window = process.atom_binding('window').Window Window.prototype.__proto__ = EventEmitter.prototype -# Convient accessors. -setupGetterAndSetter = (constructor, name, getter, setter) -> - if getter? - constructor.prototype.__defineGetter__ name, -> - this[getter].apply(this, arguments) - if setter? - constructor.prototype.__defineSetter__ name, -> - this[setter].apply(this, arguments) - -setupGetterAndSetter Window, 'fullscreen', 'isFullscreen', 'setFullscreen' -setupGetterAndSetter Window, 'size', 'getSize', 'setSize' -setupGetterAndSetter Window, 'maximumSize', 'getMaximumSize', 'setMaximumSize' -setupGetterAndSetter Window, 'minimumSize', 'getMinimumSize', 'setMinimumSize' -setupGetterAndSetter Window, 'resizable', 'isResizable', 'setResizable' -setupGetterAndSetter Window, 'alwaysOnTop', 'isAlwaysOnTop', 'setAlwaysOnTop' -setupGetterAndSetter Window, 'position', 'getPosition', 'setPosition' -setupGetterAndSetter Window, 'title', 'getTitle', 'setTitle' -setupGetterAndSetter Window, 'kiosk', 'isKiosk', 'setKiosk' -setupGetterAndSetter Window, 'url', 'getURL', 'loadURL' - module.exports = Window diff --git a/browser/atom/atom.coffee b/browser/atom/atom.coffee index 56b5367fe..eb20dbeaf 100644 --- a/browser/atom/atom.coffee +++ b/browser/atom/atom.coffee @@ -14,8 +14,12 @@ atom.browserMainParts = global.__atom = atom # Add Atom.app/Contents/Resources/browser/api/lib to require's search paths, -# which contains javascript of Atom's built-in libraries. -require('module').globalPaths.push path.join(__dirname, '..', 'api', 'lib') +# which contains javascript part of Atom's built-in libraries. +globalPaths = require('module').globalPaths +globalPaths.push path.join(__dirname, '..', 'api', 'lib') + +# And Atom.app/Contents/Resources/common/api/lib +globalPaths.push path.join(__dirname, '..', '..', 'common', 'api', 'lib') # Don't quit on fatal error. process.on 'uncaughtException', (error) -> @@ -24,6 +28,9 @@ process.on 'uncaughtException', (error) -> console.error 'uncaughtException:' console.error message +# Load the RPC server. +require './rpc_server.js' + # Now we try to load app's package.json. packageJson = null diff --git a/browser/atom/objects_registry.coffee b/browser/atom/objects_registry.coffee new file mode 100644 index 000000000..9bc0875db --- /dev/null +++ b/browser/atom/objects_registry.coffee @@ -0,0 +1,22 @@ +module.exports = +class ObjectsRegistry + @nextId = 0 + + constructor: -> + @objects = [] + + getNextId: -> + ++ObjectsRegistry.nextId + + add: (obj) -> + id = @getNextId() + @objects[id] = obj + id + + remove: (id) -> + obj = @objects[id] + delete @objects[id] + obj + + get: (id) -> + @objects[id] diff --git a/browser/atom/rpc_server.coffee b/browser/atom/rpc_server.coffee new file mode 100644 index 000000000..94528b11c --- /dev/null +++ b/browser/atom/rpc_server.coffee @@ -0,0 +1,61 @@ +ipc = require 'ipc' +path = require 'path' +ObjectsRegistry = require './objects_registry.js' + +objectsRegistry = new ObjectsRegistry + +class PlainObject + constructor: (value) -> + @type = typeof value + @type = 'value' if value is null + + if @type is 'object' or @type is 'function' + @name = value.constructor.name + @id = objectsRegistry.add value + + @members = [] + for prop, field of value + @members.push { name: prop, type: typeof field } + else + @type = 'value' + @value = value + +ipc.on 'ATOM_INTERNAL_REQUIRE', (event, process_id, routing_id, module) -> + event.result = new PlainObject(require(module)) + +ipc.on 'ATOM_INTERNAL_CONSTRUCTOR', (event, process_id, routing_id, id, args) -> + try + # Call new with array of arguments. + # TODO(zcbenz): Paste the URL of the StackOverflow question. + constructor = objectsRegistry.get id + obj = new (Function::bind.apply(constructor, [null].concat(args))) + event.result = new PlainObject(obj) + catch e + event.result = type: 'error', value: e.message + +ipc.on 'ATOM_INTERNAL_FUNCTION_CALL', (event, process_id, routing_id, id, args) -> + try + ret = objectsRegistry.get(id).apply global, args + event.result = new PlainObject(ret) + catch e + event.result = type: 'error', value: e.message + +ipc.on 'ATOM_INTERNAL_MEMBER_CALL', (event, process_id, routing_id, id, method, args) -> + try + obj = objectsRegistry.get id + ret = obj[method].apply(obj, args) + event.result = new PlainObject(ret) + catch e + event.result = type: 'error', value: e.message + +ipc.on 'ATOM_INTERNAL_MEMBER_SET', (event, process_id, routing_id, id, name, value) -> + try + objectsRegistry.get(id)[name] = value + catch e + event.result = type: 'error', value: e.message + +ipc.on 'ATOM_INTERNAL_MEMBER_GET', (event, process_id, routing_id, id, name) -> + try + event.result = new PlainObject(objectsRegistry.get(id)[name]) + catch e + event.result = type: 'error', value: e.message diff --git a/browser/default_app/main.js b/browser/default_app/main.js index a0c14fe16..5238372bb 100644 --- a/browser/default_app/main.js +++ b/browser/default_app/main.js @@ -15,11 +15,11 @@ ipc.on('sync-message', function(event, process_id, routing_id) { atom.browserMainParts.preMainMessageLoopRun = function() { mainWindow = new Window({ width: 800, height: 600 }); - mainWindow.url = 'file://' + __dirname + '/index.html'; + mainWindow.loadURL('file://' + __dirname + '/index.html'); mainWindow.on('page-title-updated', function(event, title) { event.preventDefault(); - this.title = 'Atom Shell - ' + title; + this.setTitle('Atom Shell - ' + title); }); } diff --git a/renderer/api/lib/remote.coffee b/renderer/api/lib/remote.coffee new file mode 100644 index 000000000..f18948489 --- /dev/null +++ b/renderer/api/lib/remote.coffee @@ -0,0 +1,46 @@ +ipc = require 'ipc' + +generateFromPainObject = (plain) -> + if plain.type is 'value' + return plain.value + else if plain.type is 'error' + throw new Error('Remote Error: ' + plain.value) + else + # A shadow class to represent the remote object. + class RemoteObject + constructor: () -> + if @constructor == RemoteObject + # Constructor call. + obj = ipc.sendChannelSync 'ATOM_INTERNAL_CONSTRUCTOR', plain.id, Array::slice.call(arguments) + # Returning object in constructor will replace constructed object + # with returned object. + return generateFromPainObject obj + else + # Function call. + ret = ipc.sendChannelSync 'ATOM_INTERNAL_FUNCTION_CALL', plain.id, Array::slice.call(arguments) + generateFromPainObject ret + + # Polulate shadow members. + for member in plain.members + do (member) -> + if member.type is 'function' + RemoteObject[member.name] = -> + # Call member function. + ret = ipc.sendChannelSync 'ATOM_INTERNAL_MEMBER_CALL', plain.id, member.name, Array::slice.call(arguments) + generateFromPainObject ret + else + RemoteObject.__defineSetter__ member.name, (value) -> + # Set member data. + ipc.sendChannelSync 'ATOM_INTERNAL_MEMBER_SET', plain.id, member.name, value + undefined + + RemoteObject.__defineGetter__ member.name, -> + # Get member data. + ret = ipc.sendChannelSync 'ATOM_INTERNAL_MEMBER_GET', plain.id, member.name + generateFromPainObject ret + + RemoteObject + +exports.require = (module) -> + plain = ipc.sendChannelSync 'ATOM_INTERNAL_REQUIRE', module + generateFromPainObject plain diff --git a/vendor/node b/vendor/node index 027d18bde..5c651db8f 160000 --- a/vendor/node +++ b/vendor/node @@ -1 +1 @@ -Subproject commit 027d18bde8c24534215095aadceb78e7a61b2b5c +Subproject commit 5c651db8ff28c710bedecd0599ce8a1249ae843f