Class: FirefoxSocket
- Inherits:
-
Object
- Object
- FirefoxSocket
- Extended by:
- Vapir::Configurable
- Includes:
- Vapir::Configurable
- Defined in:
- lib/vapir-firefox/firefox_socket/base.rb
Overview
Base class for connecting to a firefox extension over a TCP socket. does the work of interacting with the socket and translating ruby values to javascript and back.
Direct Known Subclasses
Constant Summary collapse
- PrototypeFile =
end
File.join(File.dirname(__FILE__), "prototype.functional.js")
Instance Attribute Summary collapse
-
#temp_object ⇒ Object
readonly
returns a JavascriptObject representing a designated top-level object for temporary storage of stuff on this socket.
Class Method Summary collapse
-
.to_javascript(object) ⇒ Object
returns a string of javascript representing the given object.
Instance Method Summary collapse
-
#assert_socket ⇒ Object
raises an informative error if the socket is down for some reason.
-
#assign(js_left, js_right) ⇒ Object
assigns to the javascript reference on the left the javascript expression on the right.
-
#assign_json(js_left, rb_right) ⇒ Object
assigns to the javascript reference on the left the object on the right.
-
#call(js_function, *js_args) ⇒ Object
calls to the given function (javascript reference to a function) passing it the given arguments (javascript expressions).
-
#call_function(arguments_hash = {}, &block) ⇒ Object
takes a hash of arguments with keys that are strings or symbols that will be variables in the scope of the function in javascript, and a block which results in a string which should be the body of a javascript function.
-
#call_json(js_function, *rb_args) ⇒ Object
calls to the given function (javascript reference to a function) passing it the given arguments, each argument being converted from a ruby object to a javascript object via JSON.
-
#Components ⇒ Object
returns a JavascriptObject representing the Components top-level javascript object.
- #configuration_parent ⇒ Object
-
#function(*arg_names) ⇒ Object
Creates and returns a JavascriptObject representing a function.
-
#handle(js_expr, *args) ⇒ Object
if the given javascript expression ends with an = symbol, #handle calls to #assign assuming it is given one argument; if the expression refers to a function, calls that function with the given arguments using #call; if the expression is some other value, returns that value (its javascript toString), calling #value, assuming given no arguments.
-
#handle_json(js_expr, *args) ⇒ Object
does the same thing as #handle, but with json, calling #assign_json, #value_json, or #call_json.
-
#handling_connection_error(options = {}) ⇒ Object
takes a block, calls the block, and returns the result of the block - unless the connection fails over the course of the block.
-
#host ⇒ Object
the host to which this socket is connected.
-
#initialize(options = {}) ⇒ FirefoxSocket
constructor
Connects a new socket to firefox.
-
#inspect ⇒ Object
returns a string of basic information about this socket.
-
#instanceof(js_expression, js_interface) ⇒ Object
uses the javascript ‘instanceof’ operator, passing it the given expression and interface.
-
#object(ref, other = {}) ⇒ Object
takes a reference and returns a new JavascriptObject representing that reference on this socket.
-
#object_in_temp(ref, other = {}) ⇒ Object
takes a reference and returns a new JavascriptObject representing that reference on this socket, stored on this socket’s temporary object.
-
#parse_json(json) ⇒ Object
parses the given JSON string using JSON.parse Raises JSON::ParserError if given a blank string, something that is not a string, or a string that contains invalid JSON.
-
#port ⇒ Object
the port on which this socket is connected.
-
#root ⇒ Object
represents the root of the space seen by the FirefoxSocket, and implements #method_missing to return objects at the root level in a similar manner to JavascriptObject’s #method_missing.
-
#typeof(expression) ⇒ Object
returns the type of the given expression using javascript typeof operator, with the exception that if the expression is null, returns ‘null’ - whereas typeof(null) in javascript returns ‘object’.
-
#value(js) ⇒ Object
returns the value of the given javascript expression, as reported by the the firefox extension.
-
#value_json(js, options = {}) ⇒ Object
returns the value of the given javascript expression.
Constructor Details
#initialize(options = {}) ⇒ FirefoxSocket
Connects a new socket to firefox
Takes options:
-
:host => the ip to connect to, default localhost
-
:port => the port to connect to
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/vapir-firefox/firefox_socket/base.rb', line 138 def initialize(={}) config.update_hash require 'thread' @mutex = Mutex.new handling_connection_error(:exception => FirefoxSocketUnableToStart.new("Could not connect to Firefox on #{host}:#{port}. Ensure that Firefox is running and has the extension listening on that port, or try restarting firefox.")) do @socket = TCPSocket::new(host, port) @socket.sync = true @expecting_prompt=false # initially, the welcome message comes before the prompt, so this so this is false to start with @expecting_extra_maybe=false end initialize_environment @temp_object = object('VapirTemp') ret=send_and_read(File.read(PrototypeFile)) if ret !~ /done!/ @expecting_extra_maybe=true raise FirefoxSocketError, "Something went wrong loading Prototype - message #{ret.inspect}" end # Y combinator in javascript. # # example - recursive length function. # # >> length=firefox_socket.root.Vapir.Ycomb(firefox_socket.function(:len){ "return function(list){ return list.length==0 ? 0 : 1+len(list.slice(1)); }; " }) # => #<JavascriptObject:0x01206880 type=function, debug_name=Vapir.Ycomb(function(len){ return function(list){ return list.length==0 ? 0 : 1+len(list.slice(1)); }; })> # >> length.call(['a', 'b', 'c']) # => 3 root.Vapir.Ycomb=function(:gen){ "return function(f){ return f(f); }(function(f){ return gen(function(){ return f(f).apply(null, arguments); }); });" } end |
Instance Attribute Details
#temp_object ⇒ Object (readonly)
returns a JavascriptObject representing a designated top-level object for temporary storage of stuff on this socket.
really, temporary values could be stored anywhere. this just gives one nice consistent designated place to stick them.
769 770 771 |
# File 'lib/vapir-firefox/firefox_socket/base.rb', line 769 def temp_object @temp_object end |
Class Method Details
.to_javascript(object) ⇒ Object
returns a string of javascript representing the given object. if given an Array or Hash, operates recursively. this is like converting to JSON, but this supports more data types than can be represented in JSON. supported data types are:
-
Array, Set (converts to javascript Array)
-
Hash (converts to javascript Object)
-
JavascriptObject (just uses the reference the JavascriptObject represents)
-
Regexp (converts to javascript RegExp)
-
String, Symbol (converts to a javascript string)
-
Integer, Float
-
true, false, nil
403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 |
# File 'lib/vapir-firefox/firefox_socket/base.rb', line 403 def self.to_javascript(object) if ['Array', 'Set'].any?{|klass_name| Object.const_defined?(klass_name) && object.is_a?(Object.const_get(klass_name)) } "["+object.map{|element| to_javascript(element) }.join(", ")+"]" elsif object.is_a?(Hash) "{"+object.map{|(key, value)| to_javascript(key)+": "+to_javascript(value) }.join(", ")+"}" elsif object.is_a?(JavascriptObject) object.ref elsif [true, false, nil].include?(object) || [Integer, Float, String, Symbol].any?{|klass| object.is_a?(klass) } object.to_json elsif object.is_a?(Regexp) # get the flags javascript recognizes - not the same ones as ruby. js_flags = {Regexp::MULTILINE => 'm', Regexp::IGNORECASE => 'i'}.inject("") do |flags, (bit, flag)| flags + (object. & bit > 0 ? flag : '') end # "new RegExp("+to_javascript(object.source)+", "+to_javascript(js_flags)+")" js_source = object.source.empty? ? "/(?:)/" : object.inspect js_source.sub!(/\w*\z/, '') # drop ruby flags js_source + js_flags else raise "Unable to represent object as javascript: #{object.inspect} (#{object.class})" end end |
Instance Method Details
#assert_socket ⇒ Object
raises an informative error if the socket is down for some reason
777 778 779 780 781 782 783 784 |
# File 'lib/vapir-firefox/firefox_socket/base.rb', line 777 def assert_socket actual, expected=handling_connection_error(:exception => FirefoxSocketConnectionError.new("Encountered a socket error while checking the socket.")) do [value_json('["foo"]'), ["foo"]] end unless expected==actual raise FirefoxSocketError, "The socket seems to have a problem: sent #{expected.inspect} but got back #{actual.inspect}" end end |
#assign(js_left, js_right) ⇒ Object
assigns to the javascript reference on the left the javascript expression on the right. returns the value of the expression as reported by the firefox extension, which will be a string, the expression’s toString. Uses #value; see its documentation.
438 439 440 |
# File 'lib/vapir-firefox/firefox_socket/base.rb', line 438 def assign(js_left, js_right) value("#{js_left}= #{js_right}") end |
#assign_json(js_left, rb_right) ⇒ Object
assigns to the javascript reference on the left the object on the right. Assuming the right object can be converted to JSON, the javascript value will be the equivalent javascript data type to the ruby object. Will return the assigned value, converted from its javascript value back to ruby. So, the return value won’t be exactly equivalent if you use symbols for example.
>> jssh_socket.assign_json('bar', {:foo => [:baz, 'qux']})
=> {"foo"=>["baz", "qux"]}
Uses #value_json; see its documentation.
539 540 541 542 |
# File 'lib/vapir-firefox/firefox_socket/base.rb', line 539 def assign_json(js_left, rb_right) js_right=FirefoxSocket.to_javascript(rb_right) value_json("#{js_left}=#{js_right}") end |
#call(js_function, *js_args) ⇒ Object
calls to the given function (javascript reference to a function) passing it the given arguments (javascript expressions). returns the return value of the function, a string, the toString of the javascript value. Uses #value; see its documentation.
445 446 447 |
# File 'lib/vapir-firefox/firefox_socket/base.rb', line 445 def call(js_function, *js_args) value("#{js_function}(#{js_args.join(', ')})") end |
#call_function(arguments_hash = {}, &block) ⇒ Object
takes a hash of arguments with keys that are strings or symbols that will be variables in the scope of the function in javascript, and a block which results in a string which should be the body of a javascript function. calls the given function with the given arguments.
an example:
jssh_socket.call_function(:x => 3, :y => {:z => 'foobar'}) do
"return x + y['z'].length;"
end
will return 9.
758 759 760 761 762 763 |
# File 'lib/vapir-firefox/firefox_socket/base.rb', line 758 def call_function(arguments_hash={}, &block) argument_names, argument_vals = *arguments_hash.inject([[],[]]) do |(names, vals),(name, val)| [names + [name], vals + [val]] end function(*argument_names, &block).call(*argument_vals) end |
#call_json(js_function, *rb_args) ⇒ Object
calls to the given function (javascript reference to a function) passing it the given arguments, each argument being converted from a ruby object to a javascript object via JSON. returns the return value of the function, of equivalent type to the javascript return value, converted from javascript to ruby via JSON. Uses #value_json; see its documentation.
549 550 551 552 |
# File 'lib/vapir-firefox/firefox_socket/base.rb', line 549 def call_json(js_function, *rb_args) js_args=rb_args.map{|arg| FirefoxSocket.to_javascript(arg) } value_json("#{js_function}(#{js_args.join(', ')})") end |
#Components ⇒ Object
returns a JavascriptObject representing the Components top-level javascript object.
773 774 775 |
# File 'lib/vapir-firefox/firefox_socket/base.rb', line 773 def Components @components ||= root.Components end |
#configuration_parent ⇒ Object
120 121 122 |
# File 'lib/vapir-firefox/firefox_socket/base.rb', line 120 def configuration_parent self.class.config end |
#function(*arg_names) ⇒ Object
Creates and returns a JavascriptObject representing a function.
Takes any number of arguments, which should be strings or symbols, which are arguments to the javascript function.
The javascript function is specified as the result of a block which must be given to #function.
An example:
jssh_socket.function(:a, :b) do
"return a+b;"
end
=> #<JavascriptObject:0x0248e78c type=function, debug_name=function(a, b){ return a+b; }>
This is exactly the same as doing
jssh_socket.object("function(a, b){ return a+b; }")
but it is a bit more concise and reads a bit more ruby-like.
a longer example to return the text of a thing (rather contrived, but, it works):
jssh_socket.function(:node) do %q[
if(node.nodeType==3)
{ return node.data;
}
else if(node.nodeType==1)
{ return node.textContent;
}
else
{ return "what?";
}
]
end.call(some_node)
732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 |
# File 'lib/vapir-firefox/firefox_socket/base.rb', line 732 def function(*arg_names) unless arg_names.all?{|arg| (arg.is_a?(String) || arg.is_a?(Symbol)) && arg.to_s =~ /\A[a-z_][a-z0-9_]*\z/i } raise ArgumentError, "Arguments to \#function should be strings or symbols representing the names of arguments to the function. got #{arg_names.inspect}" end unless block_given? raise ArgumentError, "\#function should be given a block which results in a string representing the body of a javascript function. no block was given!" end function_body = yield unless function_body.is_a?(String) raise ArgumentError, "The block given to \#function must return a string representing the body of a javascript function! instead got #{function_body.inspect}" end nl = function_body.include?("\n") ? "\n" : "" description = function_body.include?("\n") ? "..." : function_body JavascriptFunction.new("function(#{arg_names.join(", ")})#{nl}{ #{function_body} #{nl}}", self, {:debug_name => "function(#{arg_names.join(", ")}){ #{description} }"}) end |
#handle(js_expr, *args) ⇒ Object
if the given javascript expression ends with an = symbol, #handle calls to #assign assuming it is given one argument; if the expression refers to a function, calls that function with the given arguments using #call; if the expression is some other value, returns that value (its javascript toString), calling #value, assuming given no arguments. Uses #value; see its documentation.
454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 |
# File 'lib/vapir-firefox/firefox_socket/base.rb', line 454 def handle(js_expr, *args) if js_expr=~/=\z/ # doing assignment js_left=$` if args.size != 1 raise ArgumentError, "Assignment (#{js_expr}) must take one argument" end assign(js_left, *args) else type=typeof(js_expr) case type when "function" call(js_expr, *args) when "undefined" raise FirefoxSocketUndefinedValueError, "undefined expression #{js_expr.inspect}" else if !args.empty? raise ArgumentError, "Cannot pass arguments to expression #{js_expr.inspect} of type #{type}" end value(js_expr) end end end |
#handle_json(js_expr, *args) ⇒ Object
does the same thing as #handle, but with json, calling #assign_json, #value_json, or #call_json.
if the given javascript expression ends with an = symbol, #handle_json calls to #assign_json assuming it is given one argument; if the expression refers to a function, calls that function with the given arguments using #call_json; if the expression is some other value, returns that value, converted to ruby via JSON, assuming given no arguments. Uses #value_json; see its documentation.
562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 |
# File 'lib/vapir-firefox/firefox_socket/base.rb', line 562 def handle_json(js_expr, *args) if js_expr=~/=\z/ # doing assignment js_left=$` if args.size != 1 raise ArgumentError, "Assignment (#{js_expr}) must take one argument" end assign_json(js_left, *args) else type=typeof(js_expr) case type when "function" call_json(js_expr, *args) when "undefined" raise FirefoxSocketUndefinedValueError, "undefined expression #{js_expr}" else if !args.empty? raise ArgumentError, "Cannot pass arguments to expression #{js_expr.inspect} of type #{type}" end value_json(js_expr) end end end |
#handling_connection_error(options = {}) ⇒ Object
takes a block, calls the block, and returns the result of the block - unless the connection fails over the course of the block. if that happens, this handles the multitude of errors that may be the cause of that, and deals with them in a customizable manner. by default, raises a FirefoxSocketConnectionError with all of the information of the original error attached.
recognizes options:
-
:exception - an instance of an exception which will be raised, e.g. :exception => RuntimError.new(‘something went wrong!’)
-
:handle may take the following values:
-
:raise (default) - raises options, by default a FirefoxSocketConnectionError
-
:ignore - discards the exception and returns nil
-
:return - returns the exception; does not raise it
-
a Proc or Method - calls the proc, giving the raised exception as an argument. this is useful to return from a function when the connection fails, e.g. connection.handling_connection_error(:handle => proc{ return false }) { [code which may cause the socket to close] }
-
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
# File 'lib/vapir-firefox/firefox_socket/base.rb', line 183 def handling_connection_error(={}) =(, :handle => :raise, :exception => FirefoxSocketConnectionError.new("Encountered an error on the socket.")) begin yield rescue FirefoxSocketConnectionError, Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ECONNABORTED, Errno::EPIPE, SystemCallError @expecting_extra_maybe = true error = [:exception].class.new([:exception]. + "\n#{$!.class}\n#{$!.}") error.set_backtrace($!.backtrace) case [:handle] when :raise raise error when :ignore nil when :return error when Proc, Method [:handle].call(error) else raise ArgumentError, "Don't know what to do when told to handle by :handle => #{[:handle].inspect}" end end end |
#host ⇒ Object
the host to which this socket is connected
125 126 127 |
# File 'lib/vapir-firefox/firefox_socket/base.rb', line 125 def host config.host end |
#inspect ⇒ Object
returns a string of basic information about this socket.
787 788 789 |
# File 'lib/vapir-firefox/firefox_socket/base.rb', line 787 def inspect "\#<#{self.class.name}:0x#{"%.8x"%(self.hash*2)} #{[:host, :port].map{|attr| attr.to_s+'='+send(attr).inspect}.join(', ')}>" end |
#instanceof(js_expression, js_interface) ⇒ Object
uses the javascript ‘instanceof’ operator, passing it the given expression and interface. this should return true or false.
598 599 600 |
# File 'lib/vapir-firefox/firefox_socket/base.rb', line 598 def instanceof(js_expression, js_interface) value_json "(#{js_expression}) instanceof (#{js_interface})" end |
#object(ref, other = {}) ⇒ Object
takes a reference and returns a new JavascriptObject representing that reference on this socket. ref should be a string representing a reference in javascript.
623 624 625 |
# File 'lib/vapir-firefox/firefox_socket/base.rb', line 623 def object(ref, other={}) JavascriptObject.new(ref, self, {:debug_name => ref}.merge(other)) end |
#object_in_temp(ref, other = {}) ⇒ Object
takes a reference and returns a new JavascriptObject representing that reference on this socket, stored on this socket’s temporary object.
628 629 630 |
# File 'lib/vapir-firefox/firefox_socket/base.rb', line 628 def object_in_temp(ref, other={}) object(ref, other).store_rand_temp end |
#parse_json(json) ⇒ Object
parses the given JSON string using JSON.parse Raises JSON::ParserError if given a blank string, something that is not a string, or a string that contains invalid JSON
605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 |
# File 'lib/vapir-firefox/firefox_socket/base.rb', line 605 def parse_json(json) err_class=JSON::ParserError decoder=JSON.method(:parse) # err_class=ActiveSupport::JSON::ParseError # decoder=ActiveSupport::JSON.method(:decode) raise err_class, "Not a string! got: #{json.inspect}" unless json.is_a?(String) raise err_class, "Blank string!" if json=='' begin return decoder.call(json) rescue err_class err=$!.class.new($!.+"\nParsing: #{json.inspect}") err.set_backtrace($!.backtrace) raise err end end |
#port ⇒ Object
the port on which this socket is connected
129 130 131 |
# File 'lib/vapir-firefox/firefox_socket/base.rb', line 129 def port config.port end |
#root ⇒ Object
represents the root of the space seen by the FirefoxSocket, and implements #method_missing to return objects at the root level in a similar manner to JavascriptObject’s #method_missing.
for example, jssh_socket.root.Components will return the top-level Components object; jssh_socket.root.ctypes will return the ctypes top-level object if that is defined, or error if not.
if the object is a function, then it will be called with any given arguments:
>> jssh_socket.root.getWindows
=> #<JavascriptObject:0x0254d150 type=object, debug_name=getWindows()>
>> jssh_socket.root.eval("3+2")
=> 5
If any arguments are given to an object that is not a function, you will get an error:
>> jssh_socket.root.Components('wat')
ArgumentError: Cannot pass arguments to Javascript object #<JavascriptObject:0x02545978 type=object, debug_name=Components>
special behaviors exist for the suffixes !, ?, and =.
-
‘?’ suffix returns nil if the object does not exist, rather than raising an exception. for example:
>> jssh_socket.root.foo FirefoxSocketUndefinedValueError: undefined expression represented by #<JavascriptObject:0x024c3ae0 type=undefined, debug_name=foo> (javascript reference is foo) >> jssh_socket.root.foo? => nil
-
‘=’ suffix sets the named object to what is given, for example:
>> jssh_socket.root.foo? => nil >> jssh_socket.root.foo={:x => ['y', 'z']} => {:x=>["y", "z"]} >> jssh_socket.root.foo => #<JavascriptObject:0x024a3510 type=object, debug_name=foo>
-
‘!’ suffix tries to convert the value to json in javascrit and back from json to ruby, even when it might be unsafe (causing infinite rucursion or other errors). for example:
>> jssh_socket.root.foo! => {"x"=>["y", "z"]}
it can be used with function results that would normally result in a JavascriptObject:
>> jssh_socket.root.eval!("[1, 2, 3]") => [1, 2, 3]
and of course it can error if you try to do something you shouldn’t:
>> jssh_socket.root.getWindows! FirefoxSocketError::NS_ERROR_FAILURE: Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsIJSON.encode]
674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 |
# File 'lib/vapir-firefox/firefox_socket/base.rb', line 674 def root firefox_socket=self @root ||= begin root = Object.new = (class << root; self; end) .send(:define_method, :method_missing) do |method, *args| method=method.to_s if method =~ /\A([a-z_][a-z0-9_]*)([=?!])?\z/i method = $1 suffix = $2 firefox_socket.object(method).assign_or_call_or_val_or_object_by_suffix(suffix, *args) else # don't deal with any special character crap super end end .send(:define_method, :[]) do |attribute| firefox_socket.object(attribute).val_or_object(:error_on_undefined => false) end .send(:define_method, :[]=) do |attribute, value| firefox_socket.object(attribute).assign(value).val_or_object(:error_on_undefined => false) end root end end |
#typeof(expression) ⇒ Object
returns the type of the given expression using javascript typeof operator, with the exception that if the expression is null, returns ‘null’ - whereas typeof(null) in javascript returns ‘object’
587 588 589 590 591 592 593 594 |
# File 'lib/vapir-firefox/firefox_socket/base.rb', line 587 def typeof(expression) js="try { nativeJSON_encode_length({errored: false, value: (function(object){ return (object===null) ? 'null' : (typeof object); })(#{expression})}); } catch(e) { nativeJSON_encode_length(e.name=='ReferenceError' ? {errored: false, value: 'undefined'} : {errored: true, value: Object.extend({}, e)}); }" error_or_val_json(send_and_read(js, :length_before_value => true),js) end |
#value(js) ⇒ Object
returns the value of the given javascript expression, as reported by the the firefox extension.
This will be a string, the given expression’s toString.
429 430 431 432 433 |
# File 'lib/vapir-firefox/firefox_socket/base.rb', line 429 def value(js) # this is wrapped in a function so that ... # dang, now I can't remember. I'm sure I had a good reason at the time. send_and_read("(function(){return #{js}})()") end |
#value_json(js, options = {}) ⇒ Object
returns the value of the given javascript expression. Assuming that it can be converted to JSON, will return the equivalent ruby data type to the javascript value. Will raise an error if the javascript errors.
480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 |
# File 'lib/vapir-firefox/firefox_socket/base.rb', line 480 def value_json(js, ={}) =[:timeout] =(, {:error_on_undefined => true}, ) raise ArgumentError, "Expected a string containing a javascript expression! received #{js.inspect} (#{js.class})" unless js.is_a?(String) ref_error=[:error_on_undefined] ? "typeof(result)=='undefined' ? {errored: true, value: {'name': 'ReferenceError', 'message': 'undefined expression in: '+result_f.toString()}} : " : "" wrapped_js= "try { var result_f=(function(){return #{js}}); var result=result_f(); nativeJSON_encode_length(#{ref_error} {errored: false, value: result}); }catch(e) { nativeJSON_encode_length({errored: true, value: Object.extend({}, e)}); }" val=send_and_read(wrapped_js, .select_keys(*).merge(:length_before_value => true)) error_or_val_json(val, js) end |