2010-08-16 16:20:04 -04:00
|
|
|
|
|
|
|
module V8
|
2010-08-28 11:46:30 -04:00
|
|
|
class Portal
|
2011-04-22 12:07:49 -04:00
|
|
|
attr_reader :context, :proxies
|
|
|
|
|
|
|
|
def js_constructor_for(ruby_class)
|
|
|
|
@proxies.rb2js(ruby_class, &method(:make_js_constructor))
|
|
|
|
end
|
|
|
|
|
|
|
|
def invoke_non_callable_constructor(arguments)
|
|
|
|
unless arguments.Length() == 1 && arguments[0].kind_of?(C::External)
|
|
|
|
C::ThrowException(C::Exception::Error(C::String::New("cannot call native constructor from javascript")))
|
|
|
|
else
|
|
|
|
object = arguments[0].Value()
|
|
|
|
@proxies.register_javascript_proxy arguments.This(), :for => object
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def invoke_callable_constructor(arguments)
|
|
|
|
instance = nil
|
|
|
|
if arguments.Length() > 0 && arguments[0].kind_of?(C::External)
|
|
|
|
instance = arguments[0].Value()
|
|
|
|
else
|
|
|
|
rbargs = []
|
|
|
|
for i in 0..arguments.Length() - 1
|
|
|
|
rbargs << rb(arguments[i])
|
|
|
|
end
|
|
|
|
instance = rubysend(cls, :new, *rbargs)
|
|
|
|
end
|
|
|
|
@proxies.register_javascript_proxy arguments.This(), :for => instance
|
|
|
|
end
|
|
|
|
|
|
|
|
def make_js_constructor(cls)
|
|
|
|
template = C::FunctionTemplate::New(&method(:invoke_non_callable_constructor))
|
|
|
|
setuptemplate(template.InstanceTemplate())
|
|
|
|
if cls != ::Object && cls.superclass != ::Object && cls.superclass != ::Class
|
|
|
|
template.Inherit(js_constructor_for(cls.superclass))
|
|
|
|
end
|
|
|
|
if cls.name && cls.name =~ /(::)?(\w+?)$/
|
|
|
|
template.SetClassName(C::String::NewSymbol("rb::" + $2))
|
|
|
|
else
|
|
|
|
template.SetClassName("Ruby")
|
|
|
|
end
|
|
|
|
return template
|
|
|
|
end
|
|
|
|
|
|
|
|
def callable_js_constructor_for(ruby_class)
|
|
|
|
constructor = js_constructor_for(ruby_class)
|
|
|
|
function = constructor.GetFunction()
|
|
|
|
unless constructor.embedded?
|
|
|
|
constructor.SetCallHandler(&method(:invoke_callable_constructor))
|
|
|
|
#create a prototype so that this constructor also acts like a ruby object
|
|
|
|
prototype = rubytemplate.NewInstance()
|
|
|
|
#set *that* object's prototype to an empty function so that it will look and behave like a function.
|
|
|
|
prototype.SetPrototype(C::FunctionTemplate::New() {}.GetFunction())
|
|
|
|
function.SetPrototype(prototype)
|
|
|
|
def constructor.embedded?
|
|
|
|
true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return function
|
|
|
|
end
|
|
|
|
|
|
|
|
def js_instance_for(ruby_object)
|
|
|
|
constructor = js_constructor_for(ruby_object.class)
|
|
|
|
arguments = C::Array::New(1)
|
|
|
|
arguments.Set(0, C::External::New(ruby_object))
|
|
|
|
constructor.GetFunction().NewInstance(arguments)
|
|
|
|
end
|
2010-08-28 11:46:30 -04:00
|
|
|
|
2010-08-28 17:53:41 -04:00
|
|
|
def initialize(context, access)
|
|
|
|
@context, @access = context, access
|
2011-04-22 12:07:49 -04:00
|
|
|
@proxies = Proxies.new
|
2010-08-28 11:46:30 -04:00
|
|
|
@named_property_getter = Interceptor(NamedPropertyGetter)
|
|
|
|
@named_property_setter = Interceptor(NamedPropertySetter)
|
2010-08-16 16:20:04 -04:00
|
|
|
@named_property_query = nil
|
|
|
|
@named_property_deleter = nil
|
2010-08-28 11:46:30 -04:00
|
|
|
@named_property_enumerator = Interceptor(NamedPropertyEnumerator)
|
2010-08-28 16:35:09 -04:00
|
|
|
|
2010-08-28 11:46:30 -04:00
|
|
|
@indexed_property_getter = Interceptor(IndexedPropertyGetter)
|
|
|
|
@indexed_property_setter = Interceptor(IndexedPropertySetter)
|
2010-08-16 16:20:04 -04:00
|
|
|
@indexed_property_query = nil
|
|
|
|
@indexed_property_deleter = nil
|
2010-08-28 11:46:30 -04:00
|
|
|
@indexed_property_enumerator = Interceptor(IndexedPropertyEnumerator)
|
2010-08-28 16:35:09 -04:00
|
|
|
|
2010-08-30 14:58:59 -04:00
|
|
|
@functions = Functions.new(self)
|
2010-08-28 11:46:30 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def open
|
|
|
|
@context.native.enter do
|
|
|
|
yield(self)
|
|
|
|
end if block_given?
|
2010-08-16 16:20:04 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def rb(value)
|
2011-04-22 12:07:49 -04:00
|
|
|
@proxies.js2rb(value) do
|
|
|
|
case value
|
|
|
|
when V8::C::Function then V8::Function.new(value, self)
|
|
|
|
when V8::C::Array then V8::Array.new(value, self)
|
|
|
|
when V8::C::Object then V8::Object.new(value, self)
|
|
|
|
when V8::C::String then value.Utf8Value.tap {|s| return s.respond_to?(:force_encoding) ? s.force_encoding("UTF-8") : s}
|
|
|
|
when V8::C::Date then Time.at(value.NumberValue() / 1000)
|
|
|
|
when V8::C::StackTrace then V8::StackTrace.new(self, value)
|
|
|
|
when V8::C::Value then nil if value.IsEmpty()
|
|
|
|
else
|
|
|
|
value
|
|
|
|
end
|
2010-08-16 16:20:04 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def v8(value)
|
|
|
|
case value
|
|
|
|
when V8::Object
|
|
|
|
value.instance_eval {@native}
|
|
|
|
when String
|
2011-03-09 11:08:30 -05:00
|
|
|
C::String::New(value)
|
2010-08-16 16:20:04 -04:00
|
|
|
when Symbol
|
|
|
|
C::String::NewSymbol(value.to_s)
|
2010-08-30 15:23:50 -04:00
|
|
|
when Proc,Method,UnboundMethod
|
2010-08-28 18:36:48 -04:00
|
|
|
@functions[value]
|
2010-08-16 16:20:04 -04:00
|
|
|
when ::Array
|
|
|
|
C::Array::New(value.length).tap do |a|
|
|
|
|
value.each_with_index do |item, i|
|
|
|
|
a.Set(i, v8(item))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
when ::Hash
|
|
|
|
C::Object::New().tap do |o|
|
2011-03-09 11:08:30 -05:00
|
|
|
value.each do |key, val|
|
|
|
|
o.Set(v8(key), v8(val))
|
2010-08-16 16:20:04 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
when ::Time
|
2010-10-11 10:19:34 -04:00
|
|
|
C::Date::New(value.to_f * 1000)
|
2010-08-16 16:20:04 -04:00
|
|
|
when ::Class
|
2010-08-28 17:41:13 -04:00
|
|
|
@embedded_constructors[value].GetFunction().tap do |f|
|
2010-08-16 16:20:04 -04:00
|
|
|
f.SetHiddenValue(C::String::NewSymbol("TheRubyRacer::RubyObject"), C::External::New(value))
|
2010-08-28 16:41:00 -04:00
|
|
|
#set the function's prototype object to the object that will have the named property handlers
|
|
|
|
prototype = rubytemplate.NewInstance()
|
|
|
|
#set *that* object's prototype to an empty function so that it will look and behave like a function.
|
|
|
|
prototype.SetPrototype(C::FunctionTemplate::New() {}.GetFunction())
|
|
|
|
f.SetPrototype(prototype)
|
2010-08-16 16:20:04 -04:00
|
|
|
end
|
|
|
|
when nil,Numeric,TrueClass,FalseClass, C::Value
|
|
|
|
value
|
|
|
|
else
|
2011-04-22 12:07:49 -04:00
|
|
|
@proxies.rb2js(value, &method(:js_instance_for))
|
2010-08-16 16:20:04 -04:00
|
|
|
end
|
|
|
|
end
|
2010-08-28 11:46:30 -04:00
|
|
|
|
2010-08-28 17:41:13 -04:00
|
|
|
def rubyprotect
|
2010-08-28 11:46:30 -04:00
|
|
|
begin
|
2010-08-28 17:41:13 -04:00
|
|
|
v8 yield
|
2010-08-28 11:46:30 -04:00
|
|
|
rescue Exception => e
|
|
|
|
case e
|
|
|
|
when SystemExit, NoMemoryError
|
|
|
|
raise e
|
|
|
|
else
|
|
|
|
error = V8::C::Exception::Error(V8::C::String::New(e.message))
|
|
|
|
error.SetHiddenValue("TheRubyRacer::Cause", C::External::New(e))
|
|
|
|
V8::C::ThrowException(error)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def rubycall(rubycode, *args, &block)
|
|
|
|
rubyprotect do
|
|
|
|
rubycode.call(*args, &block)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def rubysend(obj, message, *args, &block)
|
|
|
|
rubyprotect do
|
|
|
|
obj.send(message, *args, &block)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2010-08-28 15:04:51 -04:00
|
|
|
def rubytemplate
|
|
|
|
C::ObjectTemplate::New().tap do |t|
|
|
|
|
setuptemplate(t)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2010-08-28 11:46:30 -04:00
|
|
|
def setuptemplate(t)
|
|
|
|
t.SetNamedPropertyHandler(
|
|
|
|
@named_property_getter,
|
|
|
|
@named_property_setter,
|
|
|
|
nil,
|
|
|
|
nil,
|
|
|
|
@named_property_enumerator
|
|
|
|
)
|
|
|
|
t.SetIndexedPropertyHandler(
|
|
|
|
@indexed_property_getter,
|
|
|
|
@indexed_property_setter,
|
|
|
|
nil,
|
|
|
|
nil,
|
|
|
|
@indexed_property_enumerator
|
|
|
|
)
|
|
|
|
end
|
2010-08-28 17:41:13 -04:00
|
|
|
|
2010-08-28 11:46:30 -04:00
|
|
|
private
|
2010-08-16 16:20:04 -04:00
|
|
|
|
2011-04-22 12:07:49 -04:00
|
|
|
# def peer(value)
|
|
|
|
# external = value.GetHiddenValue(C::String::NewSymbol("TheRubyRacer::RubyObject"))
|
|
|
|
# if external && !external.IsEmpty()
|
|
|
|
# external.Value()
|
|
|
|
# else
|
|
|
|
# yield.new(value, self).tap do |object|
|
|
|
|
# value.SetHiddenValue(C::String::NewSymbol("TheRubyRacer::RubyObject"), C::External::New(object))
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
# end
|
2010-08-16 16:20:04 -04:00
|
|
|
|
|
|
|
class Interceptor
|
2010-08-28 17:53:41 -04:00
|
|
|
def initialize(portal, access)
|
|
|
|
@to, @access = portal, access
|
2010-08-16 16:20:04 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def intercept(info, retval = nil, &code)
|
|
|
|
obj = @to.rb(info.This())
|
|
|
|
intercepts = true
|
2010-08-28 11:46:30 -04:00
|
|
|
result = @to.rubyprotect do
|
2010-08-16 16:20:04 -04:00
|
|
|
dontintercept = proc do
|
|
|
|
intercepts = false
|
|
|
|
end
|
|
|
|
code.call(obj, dontintercept)
|
|
|
|
end
|
|
|
|
intercepts ? (retval || result) : C::Empty
|
|
|
|
end
|
2010-08-28 17:53:41 -04:00
|
|
|
|
2010-08-28 11:46:30 -04:00
|
|
|
end
|
2010-08-28 17:53:41 -04:00
|
|
|
|
2010-08-28 11:46:30 -04:00
|
|
|
def Interceptor(cls)
|
2010-08-28 17:53:41 -04:00
|
|
|
cls.new self, @access
|
2010-08-16 16:20:04 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
class PropertyAttributes
|
|
|
|
attr_reader :flags
|
|
|
|
def initialize
|
|
|
|
@flags = 0
|
|
|
|
end
|
|
|
|
|
|
|
|
def read_only
|
|
|
|
tap do
|
|
|
|
@flags |= V8::C::ReadOnly
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def dont_enum
|
|
|
|
tap do
|
|
|
|
@flags |= V8::C::DontEnum
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def dont_delete
|
|
|
|
tap do
|
|
|
|
@flags |= V8::C::DontDelete
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class NamedPropertyGetter < Interceptor
|
|
|
|
def call(property, info)
|
|
|
|
intercept(info) do |obj, dontintercept|
|
2010-08-28 17:53:41 -04:00
|
|
|
@access.get(obj, @to.rb(property), &dontintercept)
|
2010-08-16 16:20:04 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class NamedPropertySetter < Interceptor
|
|
|
|
def call(property, value, info)
|
|
|
|
intercept(info, value) do |obj, dontintercept|
|
2010-08-28 17:53:41 -04:00
|
|
|
@access.set(obj, @to.rb(property), @to.rb(value), &dontintercept)
|
2010-08-16 16:20:04 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class NamedPropertyQuery
|
|
|
|
def call(property, info)
|
|
|
|
attributes = PropertyAttributes.new
|
|
|
|
result = intercept(info) do |obj, dontintercept|
|
2010-08-28 17:53:41 -04:00
|
|
|
@access.query(obj, @to.rb(property), attributes, &dontintercept)
|
2010-08-16 16:20:04 -04:00
|
|
|
end
|
|
|
|
return result == C::Empty ? result : C::Integer::New(attributes.flags)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class NamedPropertyEnumerator < Interceptor
|
|
|
|
def call(info)
|
|
|
|
intercept(info) do |obj, dontintercept|
|
2010-08-28 17:53:41 -04:00
|
|
|
@access.names(obj, &dontintercept).to_a
|
2010-08-16 16:20:04 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class NamedPropertyDeleter < Interceptor
|
|
|
|
def call(property, info)
|
|
|
|
intercept(info) do |obj, dontintercept|
|
2010-08-28 17:53:41 -04:00
|
|
|
@access.delete(obj, property, &dontintercept)
|
2010-08-16 16:20:04 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class IndexedPropertyGetter < Interceptor
|
|
|
|
def call(index, info)
|
|
|
|
intercept(info) do |obj, dontintercept|
|
2010-08-28 17:53:41 -04:00
|
|
|
@access.iget(obj, index, &dontintercept)
|
2010-08-16 16:20:04 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class IndexedPropertySetter < Interceptor
|
|
|
|
def call(index, value, info)
|
|
|
|
intercept(info, value) do |obj, dontintercept|
|
2010-08-28 17:53:41 -04:00
|
|
|
@access.iset(obj, index, @to.rb(value), &dontintercept)
|
2010-08-16 16:20:04 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class IndexedPropertyQuery < Interceptor
|
|
|
|
def call(property, info)
|
|
|
|
attributes = PropertyAttributes.new
|
|
|
|
result = intercept(info) do |obj, dontintercept|
|
2010-08-28 17:53:41 -04:00
|
|
|
@access.indices(obj, &dontintercept)
|
2010-08-16 16:20:04 -04:00
|
|
|
end
|
|
|
|
result == C::Empty ? C::Empty : C::Integer::New(attributes.flags)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class IndexedPropertyDeleter < Interceptor
|
|
|
|
def call(index, info)
|
|
|
|
intercept(info) do |obj, dontintercept|
|
2010-08-28 17:53:41 -04:00
|
|
|
@access.idelete(obj, index, &dontintercept)
|
2010-08-16 16:20:04 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class IndexedPropertyEnumerator < Interceptor
|
|
|
|
def call(info)
|
|
|
|
intercept(info) do |obj, dontintercept|
|
2010-08-28 17:53:41 -04:00
|
|
|
@access.indices(obj, &dontintercept)
|
2010-08-16 16:20:04 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|