2011-12-07 13:59:39 -05:00
|
|
|
|
|
|
|
# The base class for all JavaScript objects.
|
|
|
|
class Java::OrgMozillaJavascript::ScriptableObject
|
|
|
|
|
2011-12-14 05:49:35 -05:00
|
|
|
include_package "org.mozilla.javascript"
|
2011-12-07 13:59:39 -05:00
|
|
|
|
|
|
|
# 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)
|
2011-12-09 01:52:47 -05:00
|
|
|
Rhino.to_ruby ScriptableObject.getProperty(self, name.to_s)
|
2011-12-07 13:59:39 -05:00
|
|
|
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
|
2011-12-09 01:52:47 -05:00
|
|
|
ScriptableObject.putProperty(self, key.to_s, Rhino.to_javascript(value, scope))
|
2011-12-07 13:59:39 -05:00
|
|
|
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
|
2011-12-12 16:12:12 -05:00
|
|
|
each_raw { |key, val| yield key, Rhino.to_ruby(val) }
|
2011-12-07 13:59:39 -05:00
|
|
|
end
|
2011-12-12 16:12:12 -05:00
|
|
|
|
2011-12-07 13:59:39 -05:00
|
|
|
def each_key
|
2011-12-12 16:12:12 -05:00
|
|
|
each_raw { |key, val| yield key }
|
2011-12-07 13:59:39 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def each_value
|
2011-12-12 16:12:12 -05:00
|
|
|
each_raw { |key, val| yield Rhino.to_ruby(val) }
|
|
|
|
end
|
|
|
|
|
|
|
|
def each_raw
|
|
|
|
for id in getAllIds do
|
|
|
|
yield id, get(id, self)
|
|
|
|
end
|
2011-12-07 13:59:39 -05:00
|
|
|
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
|
|
|
|
|
2012-05-29 06:23:55 -04:00
|
|
|
# make sure inspect prints the same as to_s (on --1.8)
|
|
|
|
# otherwise JRuby might play it a little smart e.g. :
|
|
|
|
# "#<#<Class:0xd790a8>:0x557c15>" instead of "Error: bar"
|
|
|
|
def inspect
|
|
|
|
toString
|
|
|
|
end
|
|
|
|
|
2011-12-07 13:59:39 -05:00
|
|
|
# Delegate methods to JS object if possible when called from Ruby.
|
|
|
|
def method_missing(name, *args)
|
2012-02-15 06:36:16 -05:00
|
|
|
name_str = name.to_s
|
|
|
|
if name_str[-1, 1] == '=' && args.size == 1 # writer -> JS put
|
|
|
|
self[ name_str[0...-1] ] = args[0]
|
2011-12-07 13:59:39 -05:00
|
|
|
else
|
2012-02-15 06:36:16 -05:00
|
|
|
if property = self[name_str]
|
2011-12-12 16:12:12 -05:00
|
|
|
if property.is_a?(Rhino::JS::Function)
|
|
|
|
begin
|
|
|
|
context = Rhino::JS::Context.enter
|
2012-01-10 04:43:55 -05:00
|
|
|
scope = current_scope(context)
|
2011-12-12 16:12:12 -05:00
|
|
|
js_args = Rhino.args_to_javascript(args, self) # scope == self
|
2012-01-10 04:43:55 -05:00
|
|
|
Rhino.to_ruby property.__call__(context, scope, self, js_args)
|
2011-12-12 16:12:12 -05:00
|
|
|
ensure
|
|
|
|
Rhino::JS::Context.exit
|
|
|
|
end
|
|
|
|
else
|
|
|
|
if args.size > 0
|
2012-02-15 06:36:16 -05:00
|
|
|
raise ArgumentError, "can't call '#{name_str}' with args: #{args.inspect} as it's a property"
|
2011-12-12 16:12:12 -05:00
|
|
|
end
|
|
|
|
Rhino.to_ruby property
|
|
|
|
end
|
|
|
|
else
|
|
|
|
super
|
|
|
|
end
|
2011-12-07 13:59:39 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-01-10 04:43:55 -05:00
|
|
|
protected
|
|
|
|
|
|
|
|
def current_scope(context)
|
|
|
|
getParentScope || context.initStandardObjects
|
|
|
|
end
|
|
|
|
|
2011-12-07 13:59:39 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
class Java::OrgMozillaJavascript::NativeObject
|
|
|
|
|
2011-12-14 05:49:35 -05:00
|
|
|
include_package "org.mozilla.javascript"
|
2011-12-09 13:02:08 -05:00
|
|
|
|
2011-12-09 13:04:23 -05:00
|
|
|
def [](name)
|
|
|
|
value = Rhino.to_ruby(ScriptableObject.getProperty(self, s_name = name.to_s))
|
|
|
|
# handle { '5': 5 }.keys() ... [ 5 ] not [ '5' ] !
|
|
|
|
if value.nil? && (i_name = s_name.to_i) != 0
|
|
|
|
value = Rhino.to_ruby(ScriptableObject.getProperty(self, i_name))
|
|
|
|
end
|
|
|
|
value
|
|
|
|
end
|
|
|
|
|
|
|
|
# re-implement unsupported Map#put
|
2011-12-07 13:59:39 -05:00
|
|
|
def []=(key, value)
|
|
|
|
scope = self
|
2011-12-09 01:52:47 -05:00
|
|
|
ScriptableObject.putProperty(self, key.to_s, Rhino.to_javascript(value, scope))
|
2011-12-07 13:59:39 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
# The base class for all JavaScript function objects.
|
|
|
|
class Java::OrgMozillaJavascript::BaseFunction
|
|
|
|
|
2012-01-10 04:43:55 -05:00
|
|
|
# Object call(Context context, Scriptable scope, Scriptable this, Object[] args)
|
|
|
|
alias_method :__call__, :call
|
2011-12-07 13:59:39 -05:00
|
|
|
|
|
|
|
# make JavaScript functions callable Ruby style e.g. `fn.call('42')`
|
2012-01-13 04:19:02 -05:00
|
|
|
#
|
|
|
|
# NOTE: That invoking #call does not have the same semantics as
|
|
|
|
# JavaScript's Function#call but rather as Ruby's Method#call !
|
|
|
|
# Use #apply or #bind before calling to achieve the same effect.
|
2011-12-07 13:59:39 -05:00
|
|
|
def call(*args)
|
2012-01-10 04:43:55 -05:00
|
|
|
context = Rhino::JS::Context.enter; scope = current_scope(context)
|
2012-01-11 02:28:06 -05:00
|
|
|
# calling as a (var) stored function - no this === undefined "use strict"
|
|
|
|
# TODO can't pass Undefined.instance as this - it's not a Scriptable !?
|
|
|
|
this = Rhino::JS::ScriptRuntime.getGlobal(context)
|
|
|
|
__call__(context, scope, this, Rhino.args_to_javascript(args, scope))
|
2012-05-29 06:23:42 -04:00
|
|
|
rescue Rhino::JS::JavaScriptException => e
|
|
|
|
raise Rhino::JSError.new(e)
|
2011-12-07 13:59:39 -05:00
|
|
|
ensure
|
2011-12-09 13:19:55 -05:00
|
|
|
Rhino::JS::Context.exit
|
2011-12-07 13:59:39 -05:00
|
|
|
end
|
|
|
|
|
2012-01-13 04:19:02 -05:00
|
|
|
# bind a JavaScript function into the given (this) context
|
2012-01-11 02:28:06 -05:00
|
|
|
def bind(this, *args)
|
|
|
|
context = Rhino::JS::Context.enter; scope = current_scope(context)
|
|
|
|
args = Rhino.args_to_javascript(args, scope)
|
|
|
|
Rhino::JS::BoundFunction.new(context, scope, self, Rhino.to_javascript(this), args)
|
|
|
|
ensure
|
|
|
|
Rhino::JS::Context.exit
|
|
|
|
end
|
|
|
|
|
2011-12-07 13:59:39 -05:00
|
|
|
# use JavaScript functions constructors from Ruby as `fn.new`
|
|
|
|
def new(*args)
|
2012-01-10 04:43:55 -05:00
|
|
|
context = Rhino::JS::Context.enter; scope = current_scope(context)
|
2011-12-09 01:52:47 -05:00
|
|
|
construct(context, scope, Rhino.args_to_javascript(args, scope))
|
2012-05-29 06:23:42 -04:00
|
|
|
rescue Rhino::JS::JavaScriptException => e
|
|
|
|
raise Rhino::JSError.new(e)
|
2011-12-07 13:59:39 -05:00
|
|
|
ensure
|
2011-12-09 13:19:55 -05:00
|
|
|
Rhino::JS::Context.exit
|
2011-12-07 13:59:39 -05:00
|
|
|
end
|
|
|
|
|
2012-01-13 04:19:02 -05:00
|
|
|
# apply a function with the given context and (optional) arguments
|
|
|
|
# e.g. `fn.apply(obj, 1, 2)`
|
|
|
|
#
|
|
|
|
# NOTE: That #call from Ruby does not have the same semantics as
|
|
|
|
# JavaScript's Function#call but rather as Ruby's Method#call !
|
|
|
|
def apply(this, *args)
|
2012-01-10 04:43:55 -05:00
|
|
|
context = Rhino::JS::Context.enter; scope = current_scope(context)
|
2012-01-11 02:28:06 -05:00
|
|
|
args = Rhino.args_to_javascript(args, scope)
|
|
|
|
__call__(context, scope, Rhino.to_javascript(this), args)
|
2012-05-30 01:42:46 -04:00
|
|
|
rescue Rhino::JS::JavaScriptException => e
|
|
|
|
raise Rhino::JSError.new(e)
|
2011-12-14 05:49:35 -05:00
|
|
|
ensure
|
|
|
|
Rhino::JS::Context.exit
|
|
|
|
end
|
2012-01-13 04:19:02 -05:00
|
|
|
alias_method :methodcall, :apply # V8::Function compatibility
|
2011-12-14 05:49:35 -05:00
|
|
|
|
2011-12-07 13:59:39 -05:00
|
|
|
end
|
2012-01-10 04:52:53 -05:00
|
|
|
|
2012-05-18 15:46:27 -04:00
|
|
|
class Java::OrgMozillaJavascript::ScriptStackElement
|
|
|
|
|
|
|
|
def file_name; fileName; end # public final String fileName;
|
|
|
|
def function_name; functionName; end # public final String functionName;
|
|
|
|
def line_number; lineNumber; end # public final int lineNumber;
|
|
|
|
|
|
|
|
def to_s
|
|
|
|
str = "at #{fileName}"
|
|
|
|
str << ':' << lineNumber.to_s if lineNumber > -1
|
2012-05-29 05:52:33 -04:00
|
|
|
str << " (#{functionName})" if functionName
|
2012-05-18 15:46:27 -04:00
|
|
|
str
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
|
2012-01-10 04:52:53 -05:00
|
|
|
class Java::OrgMozillaJavascript::Context
|
|
|
|
|
2012-05-18 10:49:40 -04:00
|
|
|
CACHE = java.util.WeakHashMap.new
|
|
|
|
|
2012-01-10 04:52:53 -05:00
|
|
|
def reset_cache!
|
2012-05-18 10:49:40 -04:00
|
|
|
CACHE[self] = java.util.WeakHashMap.new
|
2012-01-10 04:52:53 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def enable_cache!
|
2012-05-18 10:49:40 -04:00
|
|
|
CACHE[self] = nil unless CACHE[self]
|
2012-01-10 04:52:53 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def disable_cache!
|
2012-05-18 10:49:40 -04:00
|
|
|
CACHE[self] = false
|
2012-01-10 04:52:53 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
# Support for caching JS data per context.
|
|
|
|
# e.g. to get === comparison's working ...
|
|
|
|
#
|
|
|
|
# NOTE: the cache only works correctly for keys following Java identity !
|
|
|
|
# (implementing #equals & #hashCode e.g. RubyStrings will work ...)
|
|
|
|
#
|
|
|
|
def cache(key)
|
2012-05-18 10:49:40 -04:00
|
|
|
return yield if (cache = CACHE[self]) == false
|
|
|
|
cache = reset_cache! unless cache
|
|
|
|
fetch(key, cache) || store(key, yield, cache)
|
2012-01-10 04:52:53 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2012-05-18 10:49:40 -04:00
|
|
|
def fetch(key, cache = CACHE[self])
|
|
|
|
ref = cache.get(key)
|
2012-01-10 04:52:53 -05:00
|
|
|
ref ? ref.get : nil
|
|
|
|
end
|
|
|
|
|
2012-05-18 10:49:40 -04:00
|
|
|
def store(key, value, cache = CACHE[self])
|
|
|
|
cache.put(key, java.lang.ref.WeakReference.new(value))
|
2012-01-10 04:52:53 -05:00
|
|
|
value
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|