Initial RPC API implementation.
authorCheng Zhao <zcbenz@gmail.com>
Wed, 24 Apr 2013 08:43:01 +0000 (16:43 +0800)
committerCheng Zhao <zcbenz@gmail.com>
Wed, 24 Apr 2013 08:43:01 +0000 (16:43 +0800)
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
browser/api/lib/window.coffee
browser/atom/atom.coffee
browser/atom/objects_registry.coffee [new file with mode: 0644]
browser/atom/rpc_server.coffee [new file with mode: 0644]
browser/default_app/main.js
renderer/api/lib/remote.coffee [new file with mode: 0644]
vendor/node

index 832e3e5..2b4bb99 100644 (file)
--- 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',
index 7532339..9e6deab 100644 (file)
@@ -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
index 56b5367..eb20dbe 100644 (file)
@@ -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 (file)
index 0000000..9bc0875
--- /dev/null
@@ -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 (file)
index 0000000..94528b1
--- /dev/null
@@ -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
index a0c14fe..5238372 100644 (file)
@@ -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 (file)
index 0000000..f189484
--- /dev/null
@@ -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
index 027d18b..5c651db 160000 (submodule)
@@ -1 +1 @@
-Subproject commit 027d18bde8c24534215095aadceb78e7a61b2b5c
+Subproject commit 5c651db8ff28c710bedecd0599ce8a1249ae843f