therubyrhino 1.73.0 → 1.73.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|