therubyrhino 1.73.0 → 1.73.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/rhino.rb +27 -9
- data/lib/rhino/context.rb +127 -100
- data/lib/rhino/deprecations.rb +52 -0
- data/lib/rhino/error.rb +44 -0
- data/lib/rhino/object.rb +10 -1
- data/lib/rhino/rhino_ext.rb +237 -0
- data/lib/rhino/ruby.rb +225 -0
- data/lib/rhino/ruby/access.rb +8 -0
- data/lib/rhino/ruby/attribute_access.rb +55 -0
- data/lib/rhino/ruby/default_access.rb +54 -0
- data/lib/rhino/version.rb +1 -1
- data/lib/rhino/wormhole.rb +63 -57
- data/spec/rhino/access_spec.rb +69 -0
- data/spec/rhino/context_spec.rb +24 -10
- data/spec/rhino/deprecations_spec.rb +41 -0
- data/spec/rhino/error_spec.rb +38 -0
- data/spec/rhino/rhino_ext_spec.rb +234 -0
- data/spec/rhino/ruby_spec.rb +390 -0
- data/spec/rhino/wormhole_spec.rb +99 -78
- data/spec/spec_helper.rb +1 -0
- data/therubyrhino.gemspec +1 -0
- metadata +26 -8
- data/lib/rhino/java.rb +0 -24
- data/lib/rhino/native_function.rb +0 -29
- data/lib/rhino/native_object.rb +0 -71
- data/lib/rhino/ruby_function.rb +0 -14
- data/lib/rhino/ruby_object.rb +0 -71
data/lib/rhino/object.rb
CHANGED
@@ -1,8 +1,17 @@
|
|
1
1
|
|
2
2
|
class Object
|
3
|
+
|
4
|
+
unless method_defined?(:tap)
|
5
|
+
def tap # :nodoc:
|
6
|
+
yield self
|
7
|
+
self
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
3
11
|
def eval_js(source, options = {})
|
4
12
|
Rhino::Context.open(options.merge(:with => self)) do |cxt|
|
5
13
|
cxt.eval(source)
|
6
14
|
end
|
7
15
|
end
|
8
|
-
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,237 @@
|
|
1
|
+
|
2
|
+
# The base class for all JavaScript objects.
|
3
|
+
class Java::OrgMozillaJavascript::ScriptableObject
|
4
|
+
|
5
|
+
include_package "org.mozilla.javascript"
|
6
|
+
|
7
|
+
# get a property from this javascript object, where +k+ is a string or symbol
|
8
|
+
# corresponding to the property name e.g.
|
9
|
+
#
|
10
|
+
# jsobject = Context.open do |cxt|
|
11
|
+
# cxt.eval('({foo: 'bar', 'Take me to': 'a funky town'})')
|
12
|
+
# end
|
13
|
+
# jsobject[:foo] # => 'bar'
|
14
|
+
# jsobject['foo'] # => 'bar'
|
15
|
+
# jsobject['Take me to'] # => 'a funky town'
|
16
|
+
#
|
17
|
+
def [](name)
|
18
|
+
Rhino.to_ruby ScriptableObject.getProperty(self, name.to_s)
|
19
|
+
end
|
20
|
+
|
21
|
+
# set a property on the javascript object, where +k+ is a string or symbol corresponding
|
22
|
+
# to the property name, and +v+ is the value to set. e.g.
|
23
|
+
#
|
24
|
+
# jsobject = eval_js "new Object()"
|
25
|
+
# jsobject['foo'] = 'bar'
|
26
|
+
# Context.open(:with => jsobject) do |cxt|
|
27
|
+
# cxt.eval('foo') # => 'bar'
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
def []=(key, value)
|
31
|
+
scope = self
|
32
|
+
ScriptableObject.putProperty(self, key.to_s, Rhino.to_javascript(value, scope))
|
33
|
+
end
|
34
|
+
|
35
|
+
# enumerate the key value pairs contained in this javascript object. e.g.
|
36
|
+
#
|
37
|
+
# eval_js("{foo: 'bar', baz: 'bang'}").each do |key,value|
|
38
|
+
# puts "#{key} -> #{value} "
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# outputs foo -> bar baz -> bang
|
42
|
+
#
|
43
|
+
def each
|
44
|
+
each_raw { |key, val| yield key, Rhino.to_ruby(val) }
|
45
|
+
end
|
46
|
+
|
47
|
+
def each_key
|
48
|
+
each_raw { |key, val| yield key }
|
49
|
+
end
|
50
|
+
|
51
|
+
def each_value
|
52
|
+
each_raw { |key, val| yield Rhino.to_ruby(val) }
|
53
|
+
end
|
54
|
+
|
55
|
+
def each_raw
|
56
|
+
for id in getAllIds do
|
57
|
+
yield id, get(id, self)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def keys
|
62
|
+
keys = []
|
63
|
+
each_key { |key| keys << key }
|
64
|
+
keys
|
65
|
+
end
|
66
|
+
|
67
|
+
def values
|
68
|
+
vals = []
|
69
|
+
each_value { |val| vals << val }
|
70
|
+
vals
|
71
|
+
end
|
72
|
+
|
73
|
+
# Converts the native object to a hash. This isn't really a stretch since it's
|
74
|
+
# pretty much a hash in the first place.
|
75
|
+
def to_h
|
76
|
+
hash = {}
|
77
|
+
each do |key, val|
|
78
|
+
hash[key] = val.is_a?(ScriptableObject) ? val.to_h : val
|
79
|
+
end
|
80
|
+
hash
|
81
|
+
end
|
82
|
+
|
83
|
+
# Convert this javascript object into a json string.
|
84
|
+
def to_json(*args)
|
85
|
+
to_h.to_json(*args)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Delegate methods to JS object if possible when called from Ruby.
|
89
|
+
def method_missing(name, *args)
|
90
|
+
s_name = name.to_s
|
91
|
+
if s_name[-1, 1] == '=' && args.size == 1 # writer -> JS put
|
92
|
+
self[ s_name[0...-1] ] = args[0]
|
93
|
+
else
|
94
|
+
if property = self[s_name]
|
95
|
+
if property.is_a?(Rhino::JS::Function)
|
96
|
+
begin
|
97
|
+
context = Rhino::JS::Context.enter
|
98
|
+
scope = current_scope(context)
|
99
|
+
js_args = Rhino.args_to_javascript(args, self) # scope == self
|
100
|
+
Rhino.to_ruby property.__call__(context, scope, self, js_args)
|
101
|
+
ensure
|
102
|
+
Rhino::JS::Context.exit
|
103
|
+
end
|
104
|
+
else
|
105
|
+
if args.size > 0
|
106
|
+
raise ArgumentError, "can't #{name}(#{args.join(', ')}) as '#{name}' is a property"
|
107
|
+
end
|
108
|
+
Rhino.to_ruby property
|
109
|
+
end
|
110
|
+
else
|
111
|
+
super
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
protected
|
117
|
+
|
118
|
+
def current_scope(context)
|
119
|
+
getParentScope || context.initStandardObjects
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
class Java::OrgMozillaJavascript::NativeObject
|
125
|
+
|
126
|
+
include_package "org.mozilla.javascript"
|
127
|
+
|
128
|
+
def [](name)
|
129
|
+
value = Rhino.to_ruby(ScriptableObject.getProperty(self, s_name = name.to_s))
|
130
|
+
# handle { '5': 5 }.keys() ... [ 5 ] not [ '5' ] !
|
131
|
+
if value.nil? && (i_name = s_name.to_i) != 0
|
132
|
+
value = Rhino.to_ruby(ScriptableObject.getProperty(self, i_name))
|
133
|
+
end
|
134
|
+
value
|
135
|
+
end
|
136
|
+
|
137
|
+
# re-implement unsupported Map#put
|
138
|
+
def []=(key, value)
|
139
|
+
scope = self
|
140
|
+
ScriptableObject.putProperty(self, key.to_s, Rhino.to_javascript(value, scope))
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
# The base class for all JavaScript function objects.
|
146
|
+
class Java::OrgMozillaJavascript::BaseFunction
|
147
|
+
|
148
|
+
# Object call(Context context, Scriptable scope, Scriptable this, Object[] args)
|
149
|
+
alias_method :__call__, :call
|
150
|
+
|
151
|
+
# make JavaScript functions callable Ruby style e.g. `fn.call('42')`
|
152
|
+
#
|
153
|
+
# NOTE: That invoking #call does not have the same semantics as
|
154
|
+
# JavaScript's Function#call but rather as Ruby's Method#call !
|
155
|
+
# Use #apply or #bind before calling to achieve the same effect.
|
156
|
+
def call(*args)
|
157
|
+
context = Rhino::JS::Context.enter; scope = current_scope(context)
|
158
|
+
# calling as a (var) stored function - no this === undefined "use strict"
|
159
|
+
# TODO can't pass Undefined.instance as this - it's not a Scriptable !?
|
160
|
+
this = Rhino::JS::ScriptRuntime.getGlobal(context)
|
161
|
+
__call__(context, scope, this, Rhino.args_to_javascript(args, scope))
|
162
|
+
ensure
|
163
|
+
Rhino::JS::Context.exit
|
164
|
+
end
|
165
|
+
|
166
|
+
# bind a JavaScript function into the given (this) context
|
167
|
+
def bind(this, *args)
|
168
|
+
context = Rhino::JS::Context.enter; scope = current_scope(context)
|
169
|
+
args = Rhino.args_to_javascript(args, scope)
|
170
|
+
Rhino::JS::BoundFunction.new(context, scope, self, Rhino.to_javascript(this), args)
|
171
|
+
ensure
|
172
|
+
Rhino::JS::Context.exit
|
173
|
+
end
|
174
|
+
|
175
|
+
# use JavaScript functions constructors from Ruby as `fn.new`
|
176
|
+
def new(*args)
|
177
|
+
context = Rhino::JS::Context.enter; scope = current_scope(context)
|
178
|
+
construct(context, scope, Rhino.args_to_javascript(args, scope))
|
179
|
+
ensure
|
180
|
+
Rhino::JS::Context.exit
|
181
|
+
end
|
182
|
+
|
183
|
+
# apply a function with the given context and (optional) arguments
|
184
|
+
# e.g. `fn.apply(obj, 1, 2)`
|
185
|
+
#
|
186
|
+
# NOTE: That #call from Ruby does not have the same semantics as
|
187
|
+
# JavaScript's Function#call but rather as Ruby's Method#call !
|
188
|
+
def apply(this, *args)
|
189
|
+
context = Rhino::JS::Context.enter; scope = current_scope(context)
|
190
|
+
args = Rhino.args_to_javascript(args, scope)
|
191
|
+
__call__(context, scope, Rhino.to_javascript(this), args)
|
192
|
+
ensure
|
193
|
+
Rhino::JS::Context.exit
|
194
|
+
end
|
195
|
+
alias_method :methodcall, :apply # V8::Function compatibility
|
196
|
+
|
197
|
+
end
|
198
|
+
|
199
|
+
class Java::OrgMozillaJavascript::Context
|
200
|
+
|
201
|
+
def reset_cache!
|
202
|
+
@cache = java.util.WeakHashMap.new
|
203
|
+
end
|
204
|
+
|
205
|
+
def enable_cache!
|
206
|
+
@cache = nil unless @cache
|
207
|
+
end
|
208
|
+
|
209
|
+
def disable_cache!
|
210
|
+
@cache = false
|
211
|
+
end
|
212
|
+
|
213
|
+
# Support for caching JS data per context.
|
214
|
+
# e.g. to get === comparison's working ...
|
215
|
+
#
|
216
|
+
# NOTE: the cache only works correctly for keys following Java identity !
|
217
|
+
# (implementing #equals & #hashCode e.g. RubyStrings will work ...)
|
218
|
+
#
|
219
|
+
def cache(key)
|
220
|
+
return yield if @cache == false
|
221
|
+
reset_cache! unless @cache
|
222
|
+
fetch(key) || store(key, yield)
|
223
|
+
end
|
224
|
+
|
225
|
+
private
|
226
|
+
|
227
|
+
def fetch(key)
|
228
|
+
ref = @cache.get(key)
|
229
|
+
ref ? ref.get : nil
|
230
|
+
end
|
231
|
+
|
232
|
+
def store(key, value)
|
233
|
+
@cache.put(key, java.lang.ref.WeakReference.new(value))
|
234
|
+
value
|
235
|
+
end
|
236
|
+
|
237
|
+
end
|
data/lib/rhino/ruby.rb
ADDED
@@ -0,0 +1,225 @@
|
|
1
|
+
|
2
|
+
module Rhino
|
3
|
+
module Ruby
|
4
|
+
|
5
|
+
def self.wrap_error(e)
|
6
|
+
JS::WrappedException.new(org.jruby.exceptions.RaiseException.new(e))
|
7
|
+
end
|
8
|
+
|
9
|
+
# shared JS::Scriptable implementation
|
10
|
+
module Scriptable
|
11
|
+
|
12
|
+
# override Object Scriptable#get(String name, Scriptable start);
|
13
|
+
# override Object Scriptable#get(int index, Scriptable start);
|
14
|
+
def get(name, start)
|
15
|
+
access.get(unwrap, name, self) { super }
|
16
|
+
end
|
17
|
+
|
18
|
+
# override boolean Scriptable#has(String name, Scriptable start);
|
19
|
+
# override boolean Scriptable#has(int index, Scriptable start);
|
20
|
+
def has(name, start)
|
21
|
+
access.has(unwrap, name, self) { super }
|
22
|
+
end
|
23
|
+
|
24
|
+
# override void Scriptable#put(String name, Scriptable start, Object value);
|
25
|
+
# override void Scriptable#put(int index, Scriptable start, Object value);
|
26
|
+
def put(name, start, value)
|
27
|
+
access.put(unwrap, name, value) { super }
|
28
|
+
end
|
29
|
+
|
30
|
+
# override Object[] Scriptable#getIds();
|
31
|
+
def getIds
|
32
|
+
ids = []
|
33
|
+
unwrap.public_methods(false).each do |name|
|
34
|
+
name = name[0...-1] if name[-1, 1] == '=' # 'foo=' ... 'foo'
|
35
|
+
name = name.to_java # java.lang.String
|
36
|
+
ids << name unless ids.include?(name)
|
37
|
+
end
|
38
|
+
super.each { |id| ids.unshift(id) }
|
39
|
+
ids.to_java
|
40
|
+
end
|
41
|
+
|
42
|
+
@@access = nil
|
43
|
+
|
44
|
+
def self.access=(access)
|
45
|
+
@@access = access
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.access
|
49
|
+
@@access ||= Ruby::DefaultAccess
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def access
|
55
|
+
Scriptable.access
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
class Object < JS::ScriptableObject
|
61
|
+
include JS::Wrapper
|
62
|
+
include Scriptable
|
63
|
+
|
64
|
+
# wrap an arbitrary (ruby) object
|
65
|
+
def self.wrap(object, scope = nil)
|
66
|
+
Ruby.cache(object) { new(object, scope) }
|
67
|
+
end
|
68
|
+
|
69
|
+
TYPE = JS::TopLevel::Builtins::Object
|
70
|
+
|
71
|
+
def initialize(object, scope)
|
72
|
+
super()
|
73
|
+
@ruby = object
|
74
|
+
JS::ScriptRuntime.setBuiltinProtoAndParent(self, scope, TYPE) if scope
|
75
|
+
end
|
76
|
+
|
77
|
+
# abstract Object Wrapper#unwrap();
|
78
|
+
def unwrap
|
79
|
+
@ruby
|
80
|
+
end
|
81
|
+
|
82
|
+
# abstract String Scriptable#getClassName();
|
83
|
+
def getClassName
|
84
|
+
@ruby.class.to_s # to_s handles 'nameless' classes as well
|
85
|
+
end
|
86
|
+
|
87
|
+
def toString
|
88
|
+
"[ruby #{getClassName}]" # [object User]
|
89
|
+
end
|
90
|
+
|
91
|
+
# protected Object ScriptableObject#equivalentValues(Object value)
|
92
|
+
def equivalentValues(other) # JS == operator
|
93
|
+
other.is_a?(Object) && unwrap.eql?(other.unwrap)
|
94
|
+
end
|
95
|
+
alias_method :'==', :equivalentValues
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
class Function < JS::BaseFunction
|
100
|
+
include JS::Wrapper
|
101
|
+
include Scriptable
|
102
|
+
|
103
|
+
# wrap a callable (Method/Proc)
|
104
|
+
def self.wrap(callable, scope = nil)
|
105
|
+
# NOTE: include JS::Wrapper & Ruby.cache(callable.to_s) guarantees ===
|
106
|
+
# in Rhino although if a bind Method gets passed it might get confusing
|
107
|
+
Ruby.cache(callable.to_s) { new(callable, scope) }
|
108
|
+
end
|
109
|
+
|
110
|
+
def initialize(callable, scope)
|
111
|
+
super()
|
112
|
+
@callable = callable
|
113
|
+
JS::ScriptRuntime.setFunctionProtoAndParent(self, scope) if scope
|
114
|
+
end
|
115
|
+
|
116
|
+
def unwrap
|
117
|
+
@callable
|
118
|
+
end
|
119
|
+
|
120
|
+
# override int BaseFunction#getLength()
|
121
|
+
def getLength
|
122
|
+
arity = @callable.arity
|
123
|
+
arity < 0 ? ( arity + 1 ).abs : arity
|
124
|
+
end
|
125
|
+
|
126
|
+
# #deprecated int BaseFunction#getArity()
|
127
|
+
def getArity
|
128
|
+
getLength
|
129
|
+
end
|
130
|
+
|
131
|
+
# override String BaseFunction#getFunctionName()
|
132
|
+
def getFunctionName
|
133
|
+
@callable.is_a?(Proc) ? "" : @callable.name
|
134
|
+
end
|
135
|
+
|
136
|
+
# protected Object ScriptableObject#equivalentValues(Object value)
|
137
|
+
def equivalentValues(other) # JS == operator
|
138
|
+
return false unless other.is_a?(Function)
|
139
|
+
return true if unwrap == other.unwrap
|
140
|
+
# Method.== does check if their bind to the same object
|
141
|
+
# JS == means they might be bind to different objects :
|
142
|
+
unwrap.to_s == other.unwrap.to_s # "#<Method: Foo#bar>"
|
143
|
+
end
|
144
|
+
alias_method :'==', :equivalentValues
|
145
|
+
|
146
|
+
# override Object BaseFunction#call(Context context, Scriptable scope,
|
147
|
+
# Scriptable thisObj, Object[] args)
|
148
|
+
def call(context, scope, this, args)
|
149
|
+
args = args.to_a # java.lang.Object[] -> Array
|
150
|
+
# JS function style :
|
151
|
+
if ( arity = @callable.arity ) != -1 # (a1, *a).arity == -2
|
152
|
+
if arity > -1 && args.size > arity # omit 'redundant' arguments
|
153
|
+
args = args.slice(0, arity)
|
154
|
+
elsif arity > args.size || # fill 'missing' arguments
|
155
|
+
( arity < -1 && (arity = arity.abs - 1) > args.size )
|
156
|
+
(arity - args.size).times { args.push(nil) }
|
157
|
+
end
|
158
|
+
end
|
159
|
+
rb_args = Rhino.args_to_ruby(args)
|
160
|
+
begin
|
161
|
+
callable =
|
162
|
+
if @callable.is_a?(UnboundMethod)
|
163
|
+
@callable.bind(Rhino.to_ruby(this))
|
164
|
+
else
|
165
|
+
@callable
|
166
|
+
end
|
167
|
+
result = callable.call(*rb_args)
|
168
|
+
rescue => e
|
169
|
+
raise Ruby.wrap_error(e) # thus `try { } catch (e)` works in JS
|
170
|
+
end
|
171
|
+
Rhino.to_javascript(result, scope)
|
172
|
+
end
|
173
|
+
|
174
|
+
# make sure redefined :call is aliased not the one "inherited" from
|
175
|
+
# JS::BaseFunction#call when invoking __call__ (@see rhino_ext.rb)
|
176
|
+
alias_method :__call__, :call
|
177
|
+
|
178
|
+
end
|
179
|
+
|
180
|
+
class Constructor < Function
|
181
|
+
include JS::Wrapper
|
182
|
+
|
183
|
+
# wrap a ruby class as as constructor function
|
184
|
+
def self.wrap(klass, scope = nil)
|
185
|
+
# NOTE: caching here seems redundant since we implemented JS::Wrapper
|
186
|
+
# and a ruby class objects seems always the same ref under JRuby ...
|
187
|
+
Ruby.cache(klass) { new(klass, scope) }
|
188
|
+
end
|
189
|
+
|
190
|
+
def initialize(klass, scope)
|
191
|
+
super(klass.method(:new), scope)
|
192
|
+
@klass = klass
|
193
|
+
end
|
194
|
+
|
195
|
+
def unwrap
|
196
|
+
@klass
|
197
|
+
end
|
198
|
+
|
199
|
+
# override int BaseFunction#getLength()
|
200
|
+
def getLength
|
201
|
+
arity = @klass.instance_method(:initialize).arity
|
202
|
+
arity < 0 ? ( arity + 1 ).abs : arity
|
203
|
+
end
|
204
|
+
|
205
|
+
# override boolean Scriptable#hasInstance(Scriptable instance);
|
206
|
+
def hasInstance(instance)
|
207
|
+
return false unless instance
|
208
|
+
return true if instance.is_a?(@klass)
|
209
|
+
instance.is_a?(Object) && instance.unwrap.is_a?(@klass)
|
210
|
+
end
|
211
|
+
|
212
|
+
end
|
213
|
+
|
214
|
+
def self.cache(key, &block)
|
215
|
+
context = JS::Context.getCurrentContext
|
216
|
+
context ? context.cache(key, &block) : yield
|
217
|
+
end
|
218
|
+
|
219
|
+
end
|
220
|
+
|
221
|
+
RubyObject = Ruby::Object
|
222
|
+
RubyFunction = Ruby::Function
|
223
|
+
RubyConstructor = Ruby::Constructor
|
224
|
+
|
225
|
+
end
|