mirror of
https://github.com/rubyjs/therubyrhino
synced 2023-03-27 23:21:34 -04:00
avoid NativeFunction/NativeObject wrapping and rather customize Rhino's objects to feel Ruby-ish ...
This commit is contained in:
parent
032666b074
commit
0941f932ed
11 changed files with 517 additions and 290 deletions
34
lib/rhino.rb
34
lib/rhino.rb
|
@ -1,11 +1,27 @@
|
|||
require 'java'
|
||||
|
||||
require 'rhino/rhino-1.7R3.jar'
|
||||
|
||||
module Rhino
|
||||
require 'rhino/java'
|
||||
require 'rhino/object'
|
||||
require 'rhino/context'
|
||||
require 'rhino/wormhole'
|
||||
require 'rhino/ruby_object'
|
||||
require 'rhino/ruby_function'
|
||||
require 'rhino/native_object'
|
||||
require 'rhino/native_function'
|
||||
end
|
||||
|
||||
# This module contains all the native Rhino objects implemented in Java
|
||||
# e.g. Rhino::JS::NativeObject # => org.mozilla.javascript.NativeObject
|
||||
module JS
|
||||
import "org.mozilla.javascript"
|
||||
|
||||
module Regexp
|
||||
import "org.mozilla.javascript.regexp"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
J = JS # (deprecated) backward compatibility
|
||||
|
||||
end
|
||||
|
||||
require 'rhino/object'
|
||||
require 'rhino/context'
|
||||
require 'rhino/wormhole'
|
||||
require 'rhino/ruby_object'
|
||||
require 'rhino/ruby_function'
|
||||
require 'rhino/rhino_ext'
|
||||
|
|
|
@ -2,40 +2,38 @@ require 'stringio'
|
|||
|
||||
module Rhino
|
||||
|
||||
# ==Overview
|
||||
# All Javascript must be executed in a context which represents the execution environment in
|
||||
# which scripts will run. The environment consists of the standard javascript objects
|
||||
# and functions like Object, String, Array, etc... as well as any objects or functions which
|
||||
# have been defined in it. e.g.
|
||||
#
|
||||
# Context.open do |cxt|
|
||||
# cxt['num'] = 5
|
||||
# cxt.eval('num + 5') #=> 10
|
||||
# end
|
||||
#
|
||||
# == Multiple Contexts.
|
||||
# The same object may appear in any number of contexts, but only one context may be executing javascript code
|
||||
# in any given thread. If a new context is opened in a thread in which a context is already opened, the second
|
||||
# context will "mask" the old context e.g.
|
||||
#
|
||||
# six = 6
|
||||
# Context.open do |cxt|
|
||||
# cxt['num'] = 5
|
||||
# cxt.eval('num') # => 5
|
||||
# Context.open do |cxt|
|
||||
# cxt['num'] = 10
|
||||
# cxt.eval('num') # => 10
|
||||
# cxt.eval('++num') # => 11
|
||||
# end
|
||||
# cxt.eval('num') # => 5
|
||||
# end
|
||||
#
|
||||
# == Notes
|
||||
# While there are many similarities between Rhino::Context and Java::org.mozilla.javascript.Context, they are not
|
||||
# the same thing and should not be confused.
|
||||
|
||||
# ==Overview
|
||||
# All Javascript must be executed in a context which represents the execution environment in
|
||||
# which scripts will run. The environment consists of the standard javascript objects
|
||||
# and functions like Object, String, Array, etc... as well as any objects or functions which
|
||||
# have been defined in it. e.g.
|
||||
#
|
||||
# Context.open do |cxt|
|
||||
# cxt['num'] = 5
|
||||
# cxt.eval('num + 5') #=> 10
|
||||
# end
|
||||
#
|
||||
# == Multiple Contexts.
|
||||
# The same object may appear in any number of contexts, but only one context may be executing javascript code
|
||||
# in any given thread. If a new context is opened in a thread in which a context is already opened, the second
|
||||
# context will "mask" the old context e.g.
|
||||
#
|
||||
# six = 6
|
||||
# Context.open do |cxt|
|
||||
# cxt['num'] = 5
|
||||
# cxt.eval('num') # => 5
|
||||
# Context.open do |cxt|
|
||||
# cxt['num'] = 10
|
||||
# cxt.eval('num') # => 10
|
||||
# cxt.eval('++num') # => 11
|
||||
# end
|
||||
# cxt.eval('num') # => 5
|
||||
# end
|
||||
#
|
||||
# == Notes
|
||||
# While there are many similarities between Rhino::Context and Java::org.mozilla.javascript.Context, they are not
|
||||
# the same thing and should not be confused.
|
||||
class Context
|
||||
attr_reader :scope
|
||||
|
||||
class << self
|
||||
|
||||
|
@ -51,37 +49,40 @@ module Rhino
|
|||
|
||||
end
|
||||
|
||||
attr_reader :scope
|
||||
|
||||
# Create a new javascript environment for executing javascript and ruby code.
|
||||
# * <tt>:sealed</tt> - if this is true, then the standard objects such as Object, Function, Array will not be able to be modified
|
||||
# * <tt>:with</tt> - use this ruby object as the root scope for all javascript that is evaluated
|
||||
# * <tt>:java</tt> - if true, java packages will be accessible from within javascript
|
||||
def initialize(options = {}) #:nodoc:
|
||||
ContextFactory.new.call do |native|
|
||||
@native = native
|
||||
@global = NativeObject.new(@native.initStandardObjects(nil, options[:sealed] == true))
|
||||
@factory = ContextFactory.new
|
||||
@factory.call do |context|
|
||||
@native = context
|
||||
@global = @native.initStandardObjects(nil, options[:sealed] == true)
|
||||
if with = options[:with]
|
||||
@scope = To.javascript(with)
|
||||
@scope.setParentScope(@global.j)
|
||||
@scope.setParentScope(@global)
|
||||
else
|
||||
@scope = @global
|
||||
end
|
||||
unless options[:java]
|
||||
for package in ["Packages", "java", "javax", "org", "com", "edu", "net"]
|
||||
@global.j.delete(package)
|
||||
@global.delete(package)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Read a value from the global scope of this context
|
||||
def [](k)
|
||||
@scope[k]
|
||||
def [](key)
|
||||
@scope[key]
|
||||
end
|
||||
|
||||
# Set a value in the global scope of this context. This value will be visible to all the
|
||||
# javascript that is executed in this context.
|
||||
def []=(k, v)
|
||||
@scope[k] = v
|
||||
def []=(key, val)
|
||||
@scope[key] = val
|
||||
end
|
||||
|
||||
# Evaluate a string of javascript in this context:
|
||||
|
@ -125,8 +126,8 @@ module Rhino
|
|||
# If this instruction limit is exceeded, then a Rhino::RunawayScriptError
|
||||
# will be raised
|
||||
def instruction_limit=(limit)
|
||||
@native.setInstructionObserverThreshold(limit);
|
||||
@native.factory.instruction_limit = limit
|
||||
@native.setInstructionObserverThreshold(limit)
|
||||
@factory.instruction_limit = limit
|
||||
end
|
||||
|
||||
# Set the optimization level that this context will use. This is sometimes necessary
|
||||
|
@ -166,7 +167,7 @@ module Rhino
|
|||
# fail unless this context is open
|
||||
def open
|
||||
begin
|
||||
@native.factory.enterContext(@native)
|
||||
@factory.enterContext(@native)
|
||||
yield self
|
||||
ensure
|
||||
JS::Context.exit()
|
||||
|
@ -197,6 +198,7 @@ module Rhino
|
|||
raise java.io.IOException.new, "Failed reading from ruby IO object"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class ContextFactory < JS::ContextFactory # :nodoc:
|
||||
|
@ -208,10 +210,10 @@ module Rhino
|
|||
def instruction_limit=(count)
|
||||
@limit = count
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class ContextError < StandardError # :nodoc:
|
||||
|
||||
end
|
||||
|
||||
class JavascriptError < StandardError # :nodoc:
|
||||
|
@ -232,4 +234,5 @@ module Rhino
|
|||
|
||||
class RunawayScriptError < StandardError # :nodoc:
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
require 'java'
|
||||
require 'rhino/rhino-1.7R3.jar'
|
||||
|
||||
module Rhino
|
||||
# This module contains all the native Rhino objects implemented in Java
|
||||
# e.g.
|
||||
# Rhino::JS::NativeObject # => org.mozilla.javascript.NativeObject
|
||||
module JS
|
||||
import "org.mozilla.javascript"
|
||||
|
||||
module Regexp
|
||||
import "org.mozilla.javascript.regexp"
|
||||
end
|
||||
end
|
||||
|
||||
J = JS # (deprecated) backward compatibility
|
||||
|
||||
end
|
||||
|
||||
unless Object.method_defined?(:tap)
|
||||
class Object #:nodoc:
|
||||
def tap
|
||||
yield self
|
||||
self
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,29 +0,0 @@
|
|||
|
||||
module Rhino
|
||||
|
||||
# Wraps a function that has been defined in Javascript so that it can
|
||||
# be referenced and called from javascript. e.g.
|
||||
#
|
||||
# plus = Rhino::Context.open do |cx|
|
||||
# cx.eval('function(lhs, rhs) {return lhs + rhs}')
|
||||
# end
|
||||
# plus.call(5,4) # => 9
|
||||
#
|
||||
class NativeFunction < NativeObject
|
||||
def call(*args)
|
||||
cxt = JS::Context.enter()
|
||||
scope = @j.getParentScope() || cxt.initStandardObjects()
|
||||
@j.call(cxt, scope, scope, args.map {|o| To.javascript(o)})
|
||||
ensure
|
||||
JS::Context.exit()
|
||||
end
|
||||
|
||||
def methodcall(this, *args)
|
||||
cxt = JS::Context.enter()
|
||||
scope = @j.getParentScope() || cxt.initStandardObjects()
|
||||
@j.call(cxt, scope, To.javascript(this), args.map {|o| To.javascript(o)})
|
||||
ensure
|
||||
JS::Context.exit()
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,71 +0,0 @@
|
|||
|
||||
module Rhino
|
||||
# Wraps a javascript object and makes its properties available from ruby.
|
||||
class NativeObject
|
||||
include Enumerable
|
||||
|
||||
# The native java object wrapped by this NativeObject. This will generally
|
||||
# be an instance of org.mozilla.javascript.Scriptable
|
||||
attr_reader :j
|
||||
|
||||
def initialize(j=nil) # :nodoc:
|
||||
@j = j || JS::NativeObject.new
|
||||
end
|
||||
|
||||
# get a property from this javascript object, where +k+ is a string or symbol
|
||||
# corresponding to the property name
|
||||
# e.g.
|
||||
# jsobject = Context.open do |cxt|
|
||||
# cxt.eval('({foo: 'bar', 'Take me to': 'a funky town'})')
|
||||
# end
|
||||
#
|
||||
# jsobject[:foo] # => 'bar'
|
||||
# jsobject['foo'] # => 'bar'
|
||||
# jsobject['Take me to'] # => 'a funky town'
|
||||
def [](k)
|
||||
To.ruby JS::ScriptableObject.getProperty(@j,k.to_s)
|
||||
end
|
||||
|
||||
# set a property on the javascript object, where +k+ is a string or symbol corresponding
|
||||
# to the property name, and +v+ is the value to set. e.g.
|
||||
#
|
||||
# jsobject = eval_js "new Object()"
|
||||
# jsobject['foo'] = 'bar'
|
||||
# Context.open(:with => jsobject) do |cxt|
|
||||
# cxt.eval('foo') # => 'bar'
|
||||
# end
|
||||
|
||||
def []=(k,v)
|
||||
JS::ScriptableObject.putProperty(@j, k.to_s, To.javascript(v))
|
||||
end
|
||||
|
||||
# enumerate the key value pairs contained in this javascript object. e.g.
|
||||
#
|
||||
# eval_js("{foo: 'bar', baz: 'bang'}").each do |key,value|
|
||||
# puts "#{key} -> #{value} "
|
||||
# end
|
||||
#
|
||||
# outputs foo -> bar baz -> bang
|
||||
def each
|
||||
for id in @j.getAllIds() do
|
||||
yield id,@j.get(id,@j)
|
||||
end
|
||||
end
|
||||
|
||||
# Converts the native object to a hash. This isn't really a stretch since it's
|
||||
# pretty much a hash in the first place.
|
||||
def to_h
|
||||
{}.tap do |h|
|
||||
each do |k,v|
|
||||
v = To.ruby(v)
|
||||
h[k] = self.class === v ? v.to_h : v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Convert this javascript object into a json string.
|
||||
def to_json(*args)
|
||||
to_h.to_json(*args)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,8 +1,17 @@
|
|||
|
||||
class Object
|
||||
|
||||
unless method_defined?(:tap)
|
||||
def tap # :nodoc:
|
||||
yield self
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
def eval_js(source, options = {})
|
||||
Rhino::Context.open(options.merge(:with => self)) do |cxt|
|
||||
cxt.eval(source)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
134
lib/rhino/rhino_ext.rb
Normal file
134
lib/rhino/rhino_ext.rb
Normal file
|
@ -0,0 +1,134 @@
|
|||
|
||||
# The base class for all JavaScript objects.
|
||||
class Java::OrgMozillaJavascript::ScriptableObject
|
||||
|
||||
import "org.mozilla.javascript"
|
||||
|
||||
# get a property from this javascript object, where +k+ is a string or symbol
|
||||
# corresponding to the property name e.g.
|
||||
#
|
||||
# jsobject = Context.open do |cxt|
|
||||
# cxt.eval('({foo: 'bar', 'Take me to': 'a funky town'})')
|
||||
# end
|
||||
# jsobject[:foo] # => 'bar'
|
||||
# jsobject['foo'] # => 'bar'
|
||||
# jsobject['Take me to'] # => 'a funky town'
|
||||
#
|
||||
def [](name)
|
||||
Rhino::To.to_ruby ScriptableObject.getProperty(self, name.to_s)
|
||||
end
|
||||
|
||||
# set a property on the javascript object, where +k+ is a string or symbol corresponding
|
||||
# to the property name, and +v+ is the value to set. e.g.
|
||||
#
|
||||
# jsobject = eval_js "new Object()"
|
||||
# jsobject['foo'] = 'bar'
|
||||
# Context.open(:with => jsobject) do |cxt|
|
||||
# cxt.eval('foo') # => 'bar'
|
||||
# end
|
||||
#
|
||||
def []=(key, value)
|
||||
scope = self
|
||||
ScriptableObject.putProperty(self, key.to_s, Rhino::To.to_javascript(value, scope))
|
||||
end
|
||||
|
||||
# enumerate the key value pairs contained in this javascript object. e.g.
|
||||
#
|
||||
# eval_js("{foo: 'bar', baz: 'bang'}").each do |key,value|
|
||||
# puts "#{key} -> #{value} "
|
||||
# end
|
||||
#
|
||||
# outputs foo -> bar baz -> bang
|
||||
#
|
||||
def each
|
||||
getAllIds.each { |id| yield id, Rhino::To.to_ruby(get(id, self)) }
|
||||
end
|
||||
|
||||
def each_key
|
||||
getAllIds.each { |id| yield id }
|
||||
end
|
||||
|
||||
def each_value
|
||||
getAllIds.each { |id| yield Rhino::To.to_ruby(get(id, self)) }
|
||||
end
|
||||
|
||||
def keys
|
||||
keys = []
|
||||
each_key { |key| keys << key }
|
||||
keys
|
||||
end
|
||||
|
||||
def values
|
||||
vals = []
|
||||
each_value { |val| vals << val }
|
||||
vals
|
||||
end
|
||||
|
||||
# Converts the native object to a hash. This isn't really a stretch since it's
|
||||
# pretty much a hash in the first place.
|
||||
def to_h
|
||||
hash = {}
|
||||
each do |key, val|
|
||||
hash[key] = val.is_a?(ScriptableObject) ? val.to_h : val
|
||||
end
|
||||
hash
|
||||
end
|
||||
|
||||
# Convert this javascript object into a json string.
|
||||
def to_json(*args)
|
||||
to_h.to_json(*args)
|
||||
end
|
||||
|
||||
# Delegate methods to JS object if possible when called from Ruby.
|
||||
def method_missing(name, *args)
|
||||
if ScriptableObject.hasProperty(self, name.to_s)
|
||||
begin
|
||||
context = Context.enter
|
||||
js_args = Rhino::To.args_to_javascript(args, self) # scope == self
|
||||
ScriptableObject.callMethod(context, self, name.to_s, js_args)
|
||||
ensure
|
||||
Context.exit
|
||||
end
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Java::OrgMozillaJavascript::NativeObject
|
||||
|
||||
# re-implement Map#put
|
||||
def []=(key, value)
|
||||
scope = self
|
||||
ScriptableObject.putProperty(self, key.to_s, Rhino::To.to_javascript(value, scope))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# The base class for all JavaScript function objects.
|
||||
class Java::OrgMozillaJavascript::BaseFunction
|
||||
|
||||
import "org.mozilla.javascript"
|
||||
|
||||
alias_method :__call__, :call # Rhino's Function#call(a1, a2, a3, a4)
|
||||
|
||||
# make JavaScript functions callable Ruby style e.g. `fn.call('42')`
|
||||
def call(*args)
|
||||
context = Context.enter
|
||||
scope = getParentScope || context.initStandardObjects
|
||||
__call__(context, scope, scope, Rhino::To.args_to_javascript(args, scope))
|
||||
ensure
|
||||
Context.exit
|
||||
end
|
||||
|
||||
# use JavaScript functions constructors from Ruby as `fn.new`
|
||||
def new(*args)
|
||||
context = Context.enter
|
||||
scope = getParentScope || context.initStandardObjects
|
||||
construct(context, scope, Rhino::To.args_to_javascript(args, scope))
|
||||
ensure
|
||||
Context.exit
|
||||
end
|
||||
|
||||
end
|
|
@ -1,81 +1,67 @@
|
|||
|
||||
module Rhino
|
||||
module To
|
||||
JS_UNDEF = [JS::Scriptable::NOT_FOUND, JS::Undefined]
|
||||
|
||||
module_function
|
||||
|
||||
def ruby(object)
|
||||
def to_ruby(object)
|
||||
case object
|
||||
when *JS_UNDEF then nil
|
||||
when JS::Wrapper then object.unwrap
|
||||
when JS::NativeArray then array(object)
|
||||
when JS::NativeDate then Time.at(object.getJSTimeValue() / 1000)
|
||||
when JS::Regexp::NativeRegExp then object
|
||||
when JS::Function then j2r(object) {|o| NativeFunction.new(o)}
|
||||
when JS::Scriptable then j2r(object) {|o| NativeObject.new(o)}
|
||||
else object
|
||||
when JS::Scriptable::NOT_FOUND, JS::Undefined then nil
|
||||
when JS::Wrapper then object.unwrap
|
||||
when JS::NativeArray then array_to_ruby(object)
|
||||
when JS::NativeDate then Time.at(object.getJSTimeValue / 1000)
|
||||
else object
|
||||
end
|
||||
end
|
||||
def ruby(object); to_ruby(object); end # alias
|
||||
|
||||
def javascript(object)
|
||||
def to_javascript(object, scope = nil)
|
||||
case object
|
||||
when String,Numeric then object
|
||||
when TrueClass,FalseClass then object
|
||||
when Array then JS::NativeArray.new(object.to_java)
|
||||
when Hash then ruby_hash_to_native(object)
|
||||
when Proc,Method then r2j(object, object.to_s) {|o| RubyFunction.new(o)}
|
||||
when NativeObject then object.j
|
||||
when NilClass then object
|
||||
when String, Numeric then object
|
||||
when TrueClass, FalseClass then object
|
||||
when Array then array_to_javascript(object, scope)
|
||||
when Hash then hash_to_javascript(object, scope)
|
||||
when Proc, Method then RubyFunction.new(object)
|
||||
when JS::Scriptable then object
|
||||
else r2j(object) {|o| RubyObject.new(o)}
|
||||
else RubyObject.new(object)
|
||||
end
|
||||
end
|
||||
def javascript(object, scope = nil); to_javascript(object, scope); end # alias
|
||||
|
||||
def array(native)
|
||||
native.length.times.map {|i| ruby(native.get(i,native))}
|
||||
def array_to_ruby(js_array)
|
||||
js_array.length.times.map { |i| to_ruby( js_array.get(i, js_array) ) }
|
||||
end
|
||||
|
||||
def ruby_hash_to_native(ruby_object)
|
||||
native_object = NativeObject.new
|
||||
|
||||
ruby_object.each_pair do |k, v|
|
||||
native_object[k] = v
|
||||
end
|
||||
|
||||
native_object.j
|
||||
end
|
||||
|
||||
@@j2r = {}
|
||||
def j2r(value)
|
||||
key = value.object_id
|
||||
if ref = @@j2r[key]
|
||||
if peer = ref.get()
|
||||
return peer
|
||||
else
|
||||
@@j2r.delete(key)
|
||||
return j2r(value) {|o| yield o}
|
||||
end
|
||||
def args_to_javascript(args, scope = nil)
|
||||
args.map { |arg| to_javascript(arg, scope) }.to_java
|
||||
end
|
||||
|
||||
def array_to_javascript(rb_array, scope = nil)
|
||||
if scope
|
||||
raise "no current context" unless context = JS::Context.getCurrentContext
|
||||
context.newArray(scope, rb_array.to_java)
|
||||
else
|
||||
yield(value).tap do |peer|
|
||||
@@j2r[key] = java.lang.ref.WeakReference.new(peer)
|
||||
end
|
||||
JS::NativeArray.new(rb_array.to_java)
|
||||
end
|
||||
end
|
||||
|
||||
@@r2j = {}
|
||||
def r2j(value, key = value.object_id)
|
||||
if ref = @@r2j[key]
|
||||
if peer = ref.get()
|
||||
return peer
|
||||
|
||||
def hash_to_javascript(rb_hash, scope = nil)
|
||||
js_object =
|
||||
if scope
|
||||
raise "no current context" unless context = JS::Context.getCurrentContext
|
||||
context.newObject(scope)
|
||||
else
|
||||
@@r2j.delete(key)
|
||||
return r2j(value, key) {|o| yield o}
|
||||
end
|
||||
else
|
||||
yield(value).tap do |peer|
|
||||
@@r2j[key] = java.lang.ref.WeakReference.new(peer)
|
||||
JS::NativeObject.new
|
||||
end
|
||||
# JS::NativeObject implements Map put it's #put does :
|
||||
# throw new UnsupportedOperationException(); thus no []=
|
||||
rb_hash.each_pair do |key, val|
|
||||
js_val = to_javascript(val, scope)
|
||||
JS::ScriptableObject.putProperty(js_object, key.to_s, js_val)
|
||||
end
|
||||
js_object
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
require File.dirname(__FILE__) + '/../spec_helper'
|
||||
require File.expand_path('../spec_helper', File.dirname(__FILE__))
|
||||
|
||||
describe Rhino::Context do
|
||||
|
||||
|
|
182
spec/rhino/rhino_ext_spec.rb
Normal file
182
spec/rhino/rhino_ext_spec.rb
Normal file
|
@ -0,0 +1,182 @@
|
|||
require File.expand_path('../spec_helper', File.dirname(__FILE__))
|
||||
|
||||
shared_examples_for 'ScriptableObject', :shared => true do
|
||||
|
||||
it "acts like a hash" do
|
||||
@object['foo'] = 'bar'
|
||||
@object['foo'].should == 'bar'
|
||||
end
|
||||
|
||||
it "might be converted to a hash with string keys" do
|
||||
@object[42] = '42'
|
||||
@object[:foo] = 'bar'
|
||||
expect = @object.respond_to?(:to_h_properties) ? @object.to_h_properties : {}
|
||||
@object.to_h.should == expect.merge('42' => '42', 'foo' => 'bar')
|
||||
end
|
||||
|
||||
it "yields properties with each" do
|
||||
@object['1'] = 1
|
||||
@object['3'] = 3
|
||||
@object['2'] = 2
|
||||
@object.each do |key, val|
|
||||
case key
|
||||
when '1' then val.should == 1
|
||||
when '2' then val.should == 2
|
||||
when '3' then val.should == 3
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "NativeObject" do
|
||||
|
||||
before do
|
||||
@object = Rhino::JS::NativeObject.new
|
||||
end
|
||||
|
||||
it_should_behave_like 'ScriptableObject'
|
||||
|
||||
end
|
||||
|
||||
describe "FunctionObject" do
|
||||
|
||||
before do
|
||||
factory = Rhino::JS::ContextFactory.new
|
||||
context, scope = nil, nil
|
||||
factory.call do |ctx|
|
||||
context = ctx
|
||||
scope = context.initStandardObjects(nil, false)
|
||||
end
|
||||
factory.enterContext(context)
|
||||
|
||||
to_string = java.lang.Object.new.getClass.getMethod(:toString)
|
||||
@object = Rhino::JS::FunctionObject.new('to_string', to_string, scope)
|
||||
@object.instance_eval do
|
||||
def to_h_properties
|
||||
{ "arguments"=> nil, "prototype"=> {}, "name"=> "to_string", "arity"=> 0, "length"=> 0 }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
after do
|
||||
Rhino::JS::Context.exit
|
||||
end
|
||||
|
||||
it_should_behave_like 'ScriptableObject'
|
||||
|
||||
end
|
||||
|
||||
describe "NativeObject (scoped)" do
|
||||
|
||||
before do
|
||||
factory = Rhino::JS::ContextFactory.new
|
||||
context, scope = nil, nil
|
||||
factory.call do |ctx|
|
||||
context = ctx
|
||||
scope = context.initStandardObjects(nil, false)
|
||||
end
|
||||
factory.enterContext(context)
|
||||
|
||||
@object = context.newObject(scope)
|
||||
end
|
||||
|
||||
after do
|
||||
Rhino::JS::Context.exit
|
||||
end
|
||||
|
||||
it_should_behave_like 'ScriptableObject'
|
||||
|
||||
it 'routes rhino methods' do
|
||||
@object.prototype.should == {}
|
||||
@object.constructor.should == {}
|
||||
end
|
||||
|
||||
it 'invokes JS method' do
|
||||
@object.toLocaleString.should == '[object Object]'
|
||||
end
|
||||
|
||||
it 'raises on missing method' do
|
||||
lambda { @object.aMissingMethod }.should raise_error(NoMethodError)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "NativeFunction" do
|
||||
|
||||
before do
|
||||
factory = Rhino::JS::ContextFactory.new
|
||||
context, scope = nil, nil
|
||||
factory.call do |ctx|
|
||||
context = ctx
|
||||
scope = context.initStandardObjects(nil, false)
|
||||
end
|
||||
factory.enterContext(context)
|
||||
|
||||
object = context.newObject(scope)
|
||||
@object = Rhino::JS::ScriptableObject.getProperty(object, 'toString')
|
||||
@object.instance_eval do
|
||||
def to_h_properties
|
||||
{ "arguments"=> nil, "prototype"=> {}, "name"=> "toString", "arity"=> 0, "length"=> 0 }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
after do
|
||||
Rhino::JS::Context.exit
|
||||
end
|
||||
|
||||
it_should_behave_like 'ScriptableObject'
|
||||
|
||||
it 'is callable' do
|
||||
@object.call.should == '[object Object]'
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "NativeFunction (constructor)" do
|
||||
|
||||
before do
|
||||
factory = Rhino::JS::ContextFactory.new
|
||||
context, scope = nil, nil
|
||||
factory.call do |ctx|
|
||||
context = ctx
|
||||
scope = context.initStandardObjects(nil, false)
|
||||
end
|
||||
factory.enterContext(context)
|
||||
|
||||
@object = Rhino::JS::ScriptableObject.getProperty(context.newObject(scope), 'constructor')
|
||||
@object.instance_eval do
|
||||
def to_h_properties
|
||||
{
|
||||
"arguments"=>nil, "prototype"=>{}, "name"=>"Object", "arity"=>1, "length"=>1,
|
||||
|
||||
"getPrototypeOf"=> { "arguments"=>nil, "prototype"=>{}, "name"=>"getPrototypeOf", "arity"=>1, "length"=>1},
|
||||
"keys"=>{"arguments"=>nil, "prototype"=>{}, "name"=>"keys", "arity"=>1, "length"=>1},
|
||||
"getOwnPropertyNames"=>{"arguments"=>nil, "prototype"=>{}, "name"=>"getOwnPropertyNames", "arity"=>1, "length"=>1},
|
||||
"getOwnPropertyDescriptor"=>{"arguments"=>nil, "prototype"=>{}, "name"=>"getOwnPropertyDescriptor", "arity"=>2, "length"=>2},
|
||||
"defineProperty"=>{"arguments"=>nil, "prototype"=>{}, "name"=>"defineProperty", "arity"=>3, "length"=>3},
|
||||
"isExtensible"=>{"arguments"=>nil, "prototype"=>{}, "name"=>"isExtensible", "arity"=>1, "length"=>1},
|
||||
"preventExtensions"=>{"arguments"=>nil, "prototype"=>{}, "name"=>"preventExtensions", "arity"=>1, "length"=>1},
|
||||
"defineProperties"=>{"arguments"=>nil, "prototype"=>{}, "name"=>"defineProperties", "arity"=>2, "length"=>2},
|
||||
"create"=>{"arguments"=>nil, "prototype"=>{}, "name"=>"create", "arity"=>2, "length"=>2},
|
||||
"isSealed"=>{"arguments"=>nil, "prototype"=>{}, "name"=>"isSealed", "arity"=>1, "length"=>1},
|
||||
"isFrozen"=>{"arguments"=>nil, "prototype"=>{}, "name"=>"isFrozen", "arity"=>1, "length"=>1},
|
||||
"seal"=>{"arguments"=>nil, "prototype"=>{}, "name"=>"seal", "arity"=>1, "length"=>1},
|
||||
"freeze"=>{"arguments"=>nil, "prototype"=>{}, "name"=>"freeze", "arity"=>1, "length"=>1}
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
after do
|
||||
Rhino::JS::Context.exit
|
||||
end
|
||||
|
||||
it_should_behave_like 'ScriptableObject'
|
||||
|
||||
it 'is constructable' do
|
||||
@object.new.should == {}
|
||||
end
|
||||
|
||||
end
|
|
@ -1,42 +1,38 @@
|
|||
require File.dirname(__FILE__) + '/../spec_helper'
|
||||
require File.expand_path('../spec_helper', File.dirname(__FILE__))
|
||||
|
||||
describe Rhino::To do
|
||||
|
||||
describe "ruby translation" do
|
||||
|
||||
it "converts javascript NOT_FOUND to ruby nil" do
|
||||
Rhino::To.ruby(Rhino::JS::Scriptable::NOT_FOUND).should be_nil
|
||||
end
|
||||
|
||||
it "converts javascript arrays to ruby arrays" do
|
||||
Rhino::JS::NativeObject.new.tap do |o|
|
||||
Rhino::To.ruby(o).tap do |ruby_object|
|
||||
ruby_object.should respond_to(:j)
|
||||
ruby_object.j.should be(o)
|
||||
it "converts javascript undefined into nil" do
|
||||
Rhino::To.ruby(Rhino::JS::Undefined.instance).should be_nil
|
||||
end
|
||||
|
||||
it "does return javascript object" do
|
||||
Rhino::JS::NativeObject.new.tap do |js_obj|
|
||||
Rhino::To.ruby(js_obj).tap do |rb_obj|
|
||||
rb_obj.should be(js_obj)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "wraps native javascript arrays into a ruby NativeArray wrapper" do
|
||||
Rhino::JS::NativeArray.new([1,2,4].to_java).tap do |a|
|
||||
Rhino::To.ruby(a).should == [1,2,4]
|
||||
Rhino::JS::NativeArray.new([1,2,4].to_java).tap do |js_array|
|
||||
Rhino::To.ruby(js_array).should == [1,2,4]
|
||||
end
|
||||
end
|
||||
|
||||
it "wraps native javascript functions into a ruby NativeFunction wrapper" do
|
||||
it "does return javascript function" do
|
||||
|
||||
c = Class.new(Rhino::JS::BaseFunction).class_eval do
|
||||
self.tap do
|
||||
def call(cxt, scope, this, args)
|
||||
args.join(',')
|
||||
end
|
||||
end
|
||||
end
|
||||
klass = Class.new(Rhino::JS::BaseFunction)
|
||||
|
||||
c.new.tap do |f|
|
||||
Rhino::To.ruby(f).tap do |o|
|
||||
o.should_not be_nil
|
||||
o.should be_kind_of(Rhino::NativeObject)
|
||||
o.should be_respond_to(:call)
|
||||
o.call(1,2,3).should == "1,2,3"
|
||||
klass.new.tap do |js_fn|
|
||||
Rhino::To.ruby(js_fn).tap do |rb_fn|
|
||||
rb_fn.should be(js_fn)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -51,33 +47,24 @@ describe Rhino::To do
|
|||
it "it unwraps wrapped java objects" do
|
||||
Rhino::Context.open do |cx|
|
||||
scope = cx.scope
|
||||
java.lang.String.new("Hello World").tap do |str|
|
||||
Rhino::JS::NativeJavaObject.new(scope.j, str, str.getClass()).tap do |o|
|
||||
Rhino::To.ruby(o).should == "Hello World"
|
||||
end
|
||||
j_str = java.lang.String.new("Hello World")
|
||||
Rhino::JS::NativeJavaObject.new(scope, j_str, j_str.getClass()).tap do |o|
|
||||
Rhino::To.ruby(o).should == "Hello World"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "converts javascript undefined into nil" do
|
||||
Rhino::To.ruby(Rhino::JS::Undefined.instance).should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "javascript translation" do
|
||||
|
||||
it "passes primitives through to the js layer to let jruby and rhino do he thunking" do
|
||||
to(1).should be(1)
|
||||
to(2.5).should == 2.5
|
||||
to("foo").should == "foo"
|
||||
to(true).should be(true)
|
||||
to(false).should be(false)
|
||||
end
|
||||
|
||||
it "unwraps wrapped ruby objects before passing them to the javascript runtime" do
|
||||
Rhino::JS::NativeObject.new.tap do |o|
|
||||
Rhino::To.javascript(Rhino::NativeObject.new(o)).should be(o)
|
||||
end
|
||||
Rhino::To.javascript(1).should be(1)
|
||||
Rhino::To.javascript(2.5).should == 2.5
|
||||
Rhino::To.javascript("foo").should == "foo"
|
||||
Rhino::To.javascript(true).should be(true)
|
||||
Rhino::To.javascript(false).should be(false)
|
||||
Rhino::To.javascript(nil).should be_nil
|
||||
end
|
||||
|
||||
it "leaves native javascript objects alone" do
|
||||
|
@ -93,36 +80,76 @@ describe Rhino::To do
|
|||
a.get(1,a).should be(2)
|
||||
a.get(2,a).should be(3)
|
||||
a.get(3,a).should be(4)
|
||||
a.prototype.should be_nil # this is how Rhino works !
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
it "converts ruby hashes into native objects" do
|
||||
Rhino::To.javascript({ :bare => true }).tap do |h|
|
||||
h.should be_kind_of(Rhino::JS::NativeObject)
|
||||
h.get("bare", h).should be(true)
|
||||
h.prototype.should be_nil # this is how Rhino works !
|
||||
end
|
||||
end
|
||||
|
||||
describe "with a scope" do
|
||||
|
||||
before do
|
||||
factory = Rhino::JS::ContextFactory.new
|
||||
context = nil
|
||||
factory.call do |ctx|
|
||||
context = ctx
|
||||
@scope = context.initStandardObjects(nil, false)
|
||||
end
|
||||
factory.enterContext(context)
|
||||
end
|
||||
|
||||
after do
|
||||
Rhino::JS::Context.exit
|
||||
end
|
||||
|
||||
it "converts ruby arrays into javascript arrays" do
|
||||
Rhino::To.javascript([1,2,3,4,5], @scope).tap do |a|
|
||||
a.should be_kind_of(Rhino::JS::NativeArray)
|
||||
a.get(0,a).should be(1)
|
||||
a.get(1,a).should be(2)
|
||||
a.get(2,a).should be(3)
|
||||
a.get(3,a).should be(4)
|
||||
a.prototype.should_not be_nil
|
||||
end
|
||||
end
|
||||
|
||||
it "converts ruby hashes into native objects" do
|
||||
Rhino::To.javascript({ :bare => true }, @scope).tap do |h|
|
||||
h.should be_kind_of(Rhino::JS::NativeObject)
|
||||
h.get("bare", h).should be(true)
|
||||
h.prototype.should_not be_nil
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
it "converts procs and methods into native functions" do
|
||||
to(lambda {|lhs,rhs| lhs * rhs}).tap do |f|
|
||||
Rhino::To.javascript(lambda {|lhs,rhs| lhs * rhs}).tap do |f|
|
||||
f.should be_kind_of(Rhino::JS::Function)
|
||||
f.call(nil, nil, nil, [7,6]).should be(42)
|
||||
end
|
||||
to("foo,bar,baz".method(:split)).tap do |m|
|
||||
|
||||
Rhino::To.javascript("foo,bar,baz".method(:split)).tap do |m|
|
||||
m.should be_kind_of(Rhino::JS::Function)
|
||||
Rhino::To.ruby(m.call(nil, nil, nil, ',')).should == ['foo', 'bar', 'baz']
|
||||
end
|
||||
end
|
||||
|
||||
it "creates a prototype for the object based on its class" do
|
||||
Class.new.tap do |c|
|
||||
c.class_eval do
|
||||
Class.new.tap do |klass|
|
||||
klass.class_eval do
|
||||
def foo(one, two)
|
||||
"1: #{one}, 2: #{two}"
|
||||
end
|
||||
end
|
||||
|
||||
Rhino::To.javascript(c.new).tap do |o|
|
||||
Rhino::To.javascript(klass.new).tap do |o|
|
||||
o.should be_kind_of(Rhino::RubyObject)
|
||||
o.prototype.tap do |p|
|
||||
p.should_not be_nil
|
||||
|
@ -131,11 +158,8 @@ describe Rhino::To do
|
|||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def to(object)
|
||||
Rhino::To.javascript(object)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue