electron/renderer/api/lib/remote.coffee
Cheng Zhao 623e0f3ae4 Release render view's remote objects when it's deleted.
Privously we release them when the window is unloaded, which is not
correct since a render view can have multiple windows (or js contexts)
and when the unload event is emitted the render view could already have
gone.

This PR does the cleaning work purely in browser, so here is no need to
worry about renderer's life time.
2013-12-06 14:44:25 +08:00

131 lines
4.9 KiB
CoffeeScript

ipc = require 'ipc'
CallbacksRegistry = require 'callbacks-registry'
v8Util = process.atomBinding 'v8_util'
callbacksRegistry = new CallbacksRegistry
# Convert the arguments object into an array of meta data.
wrapArgs = (args) ->
valueToMeta = (value) ->
if Array.isArray value
type: 'array', value: wrapArgs(value)
else if value? and typeof value is 'object' and v8Util.getHiddenValue value, 'atomId'
type: 'remote-object', id: v8Util.getHiddenValue value, 'atomId'
else if value? and typeof value is 'object'
ret = type: 'object', name: value.constructor.name, members: []
ret.members.push(name: prop, value: valueToMeta(field)) for prop, field of value
ret
else if typeof value is 'function' and v8Util.getHiddenValue value, 'returnValue'
type: 'function-with-return-value', value: valueToMeta(value())
else if typeof value is 'function'
type: 'function', id: callbacksRegistry.add(value)
else
type: 'value', value: value
Array::slice.call(args).map valueToMeta
# Convert meta data from browser into real value.
metaToValue = (meta) ->
switch meta.type
when 'value' then meta.value
when 'array' then (metaToValue(el) for el in meta.members)
when 'error'
throw new Error("#{meta.message}\n#{meta.stack}")
else
if meta.type is 'function'
# A shadow class to represent the remote function object.
ret =
class RemoteFunction
constructor: ->
if @constructor == RemoteFunction
# Constructor call.
obj = ipc.sendChannelSync 'ATOM_BROWSER_CONSTRUCTOR', meta.id, wrapArgs(arguments)
# Returning object in constructor will replace constructed object
# with the returned object.
# http://stackoverflow.com/questions/1978049/what-values-can-a-constructor-return-to-avoid-returning-this
return metaToValue obj
else
# Function call.
ret = ipc.sendChannelSync 'ATOM_BROWSER_FUNCTION_CALL', meta.id, wrapArgs(arguments)
return metaToValue ret
else
ret = v8Util.createObjectWithName meta.name
# Polulate delegate members.
for member in meta.members
do (member) ->
if member.type is 'function'
ret[member.name] =
class RemoteMemberFunction
constructor: ->
if @constructor is RemoteMemberFunction
# Constructor call.
obj = ipc.sendChannelSync 'ATOM_BROWSER_MEMBER_CONSTRUCTOR', meta.id, member.name, wrapArgs(arguments)
return metaToValue obj
else
# Call member function.
ret = ipc.sendChannelSync 'ATOM_BROWSER_MEMBER_CALL', meta.id, member.name, wrapArgs(arguments)
return metaToValue ret
else
ret.__defineSetter__ member.name, (value) ->
# Set member data.
ipc.sendChannelSync 'ATOM_BROWSER_MEMBER_SET', meta.id, member.name, value
value
ret.__defineGetter__ member.name, ->
# Get member data.
ret = ipc.sendChannelSync 'ATOM_BROWSER_MEMBER_GET', meta.id, member.name
metaToValue ret
# Track delegate object's life time, and tell the browser to clean up
# when the object is GCed.
v8Util.setDestructor ret, ->
ipc.sendChannel 'ATOM_BROWSER_DEREFERENCE', meta.storeId
# Remember object's id.
v8Util.setHiddenValue ret, 'atomId', meta.id
ret
# Browser calls a callback in renderer.
ipc.on 'ATOM_RENDERER_CALLBACK', (id, args) ->
callbacksRegistry.apply id, metaToValue(args)
# A callback in browser is released.
ipc.on 'ATOM_RENDERER_RELEASE_CALLBACK', (id) ->
callbacksRegistry.remove id
# Get remote module.
# (Just like node's require, the modules are cached permanently, note that this
# is safe leak since the object is not expected to get freed in browser)
moduleCache = {}
exports.require = (module) ->
return moduleCache[module] if moduleCache[module]?
meta = ipc.sendChannelSync 'ATOM_BROWSER_REQUIRE', module
moduleCache[module] = metaToValue meta
# Get current window object.
windowCache = null
exports.getCurrentWindow = ->
return windowCache if windowCache?
meta = ipc.sendChannelSync 'ATOM_BROWSER_CURRENT_WINDOW'
windowCache = metaToValue meta
# Get a global object in browser.
exports.getGlobal = (name) ->
meta = ipc.sendChannelSync 'ATOM_BROWSER_GLOBAL', name
metaToValue meta
# Get the process object in browser.
processCache = null
exports.__defineGetter__ 'process', ->
processCache = exports.getGlobal('process') unless processCache?
processCache
# Create a funtion that will return the specifed value when called in browser.
exports.createFunctionWithReturnValue = (returnValue) ->
func = -> returnValue
v8Util.setHiddenValue func, 'returnValue', true
func