Class: JsshSocket
- Inherits:
-
Object
- Object
- JsshSocket
- Defined in:
- lib/vapir-firefox/jssh_socket.rb
Overview
A JsshSocket represents a connection to Firefox over a socket opened to the JSSH extension. It does the work of interacting with the socket and translating ruby values to javascript and back.
Constant Summary collapse
- PROMPT =
end
"\n> "
- PrototypeFile =
File.join(File.dirname(__FILE__), "prototype.functional.js")
- DEFAULT_IP =
default IP Address of the machine where the script is to be executed. Default to localhost.
"127.0.0.1"
- DEFAULT_PORT =
default port to connect to.
9997
- DEFAULT_SOCKET_TIMEOUT =
maximum time JsshSocket waits for a value to be sent before giving up
64
- SHORT_SOCKET_TIMEOUT =
maximum time JsshSocket will wait for additional reads on a socket that is actively sending
(2**-2).to_f
- READ_SIZE =
the number of bytes to read from the socket at a time
4096
Instance Attribute Summary collapse
-
#ip ⇒ Object
readonly
the IP to which this socket is connected.
-
#port ⇒ Object
readonly
the port on which this socket is connected.
-
#prototype ⇒ Object
readonly
whether the prototye javascript library is loaded.
Class Method Summary collapse
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 JsshObject representing the Components top-level javascript object.
-
#ensure_prototype ⇒ Object
raises error if the prototype library (needed for JSON stuff in javascript) has not been loaded.
-
#function(*arg_names) ⇒ Object
Creates and returns a JsshObject representing a function.
-
#getWindows ⇒ Object
returns a JsshObject representing the return value of JSSH’s builtin getWindows() 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.
-
#initialize(options = {}) ⇒ JsshSocket
constructor
Connects a new socket to jssh.
-
#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 JsshObject representing that reference on this socket.
-
#object_in_temp(ref, other = {}) ⇒ Object
takes a reference and returns a new JsshObject 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.
-
#root ⇒ Object
represents the root of the space seen by the JsshSocket, and implements #method_missing to return objects at the root level in a similar manner to JsshObject’s #method_missing.
-
#temp_object ⇒ Object
returns a JsshObject representing a designated top-level object for temporary storage of stuff on this socket.
-
#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 JSSH.
-
#value_json(js, options = {}) ⇒ Object
returns the value of the given javascript expression.
Constructor Details
#initialize(options = {}) ⇒ JsshSocket
Connects a new socket to jssh
Takes options:
-
:jssh_ip => the ip to connect to, default 127.0.0.1
-
:jssh_port => the port to connect to, default 9997
-
:send_prototype => true|false, whether to load and send the Prototype library (the functional programming part of it anyway, and JSON bits)
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/vapir-firefox/jssh_socket.rb', line 104 def initialize(={}) @ip=[:jssh_ip] || DEFAULT_IP @port=[:jssh_port] || DEFAULT_PORT @prototype=.key?(:send_prototype) ? [:send_prototype] : true begin @socket = TCPSocket::new(@ip, @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 welcome="Welcome to the Mozilla JavaScript Shell!\n" read=read_value if !read @expecting_extra_maybe=true raise JsshUnableToStart, "Something went wrong initializing - no response" elsif read != welcome @expecting_extra_maybe=true raise JsshUnableToStart, "Something went wrong initializing - message #{read.inspect} != #{welcome.inspect}" end rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ECONNABORTED, Errno::EPIPE err=JsshUnableToStart.new("Could not connect to JSSH sever #{@ip}:#{@port}. Ensure that Firefox is running and has JSSH configured, or try restarting firefox.\nMessage from TCPSocket:\n#{$!.}") err.set_backtrace($!.backtrace) raise err end if @prototype ret=send_and_read(File.read(PrototypeFile)) if ret != "done!" @expecting_extra_maybe=true raise JsshError, "Something went wrong loading Prototype - message #{ret.inspect}" end end ret=send_and_read("(function() { nativeJSON=Components.classes['@mozilla.org/dom/json;1'].createInstance(Components.interfaces.nsIJSON); nativeJSON_encode_length=function(object) { var encoded=nativeJSON.encode(object); return encoded.length.toString()+\"\\n\"+encoded; } return 'done!'; })()") if ret != "done!" @expecting_extra_maybe=true raise JsshError, "Something went wrong initializing native JSON - message #{ret.inspect}" end root.JsshTemp={} end |
Instance Attribute Details
#ip ⇒ Object (readonly)
the IP to which this socket is connected
92 93 94 |
# File 'lib/vapir-firefox/jssh_socket.rb', line 92 def ip @ip end |
#port ⇒ Object (readonly)
the port on which this socket is connected
94 95 96 |
# File 'lib/vapir-firefox/jssh_socket.rb', line 94 def port @port end |
#prototype ⇒ Object (readonly)
whether the prototye javascript library is loaded
96 97 98 |
# File 'lib/vapir-firefox/jssh_socket.rb', line 96 def prototype @prototype end |
Class Method Details
.to_javascript(object) ⇒ Object
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 |
# File 'lib/vapir-firefox/jssh_socket.rb', line 335 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?(JsshObject) 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
723 724 725 726 727 728 729 730 731 732 733 734 735 736 |
# File 'lib/vapir-firefox/jssh_socket.rb', line 723 def assert_socket begin actual, expected=if prototype [value_json('["foo"]'), ["foo"]] else [value('"foo"'), "foo"] end rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ECONNABORTED, Errno::EPIPE raise(JsshConnectionError, "Encountered a socket error while checking the socket.\n#{$!.class}\n#{$!.}", $!.backtrace) end unless expected==actual raise JsshError, "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 JSSH, which will be a string, the expression’s toString. Uses #value; see its documentation.
370 371 372 |
# File 'lib/vapir-firefox/jssh_socket.rb', line 370 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.
471 472 473 474 475 |
# File 'lib/vapir-firefox/jssh_socket.rb', line 471 def assign_json(js_left, rb_right) ensure_prototype js_right=JsshSocket.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.
377 378 379 |
# File 'lib/vapir-firefox/jssh_socket.rb', line 377 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.
698 699 700 701 702 703 |
# File 'lib/vapir-firefox/jssh_socket.rb', line 698 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.
482 483 484 485 486 |
# File 'lib/vapir-firefox/jssh_socket.rb', line 482 def call_json(js_function, *rb_args) ensure_prototype js_args=rb_args.map{|arg| JsshSocket.to_javascript(arg) } value_json("#{js_function}(#{js_args.join(', ')})") end |
#Components ⇒ Object
returns a JsshObject representing the Components top-level javascript object.
715 716 717 |
# File 'lib/vapir-firefox/jssh_socket.rb', line 715 def Components @components ||= root.Components end |
#ensure_prototype ⇒ Object
raises error if the prototype library (needed for JSON stuff in javascript) has not been loaded
521 522 523 524 525 |
# File 'lib/vapir-firefox/jssh_socket.rb', line 521 def ensure_prototype unless prototype raise JsshError, "This functionality requires the prototype library; cannot be called on a Jssh session that has not loaded the Prototype library" end end |
#function(*arg_names) ⇒ Object
Creates and returns a JsshObject 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
=> #<JsshObject: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)
673 674 675 676 677 678 679 680 681 682 683 684 685 686 |
# File 'lib/vapir-firefox/jssh_socket.rb', line 673 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" : "" object("function(#{arg_names.join(", ")})#{nl}{ #{function_body} #{nl}}") end |
#getWindows ⇒ Object
returns a JsshObject representing the return value of JSSH’s builtin getWindows() function.
719 720 721 |
# File 'lib/vapir-firefox/jssh_socket.rb', line 719 def getWindows root.getWindows 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.
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 |
# File 'lib/vapir-firefox/jssh_socket.rb', line 386 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 JsshUndefinedValueError, "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.
496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 |
# File 'lib/vapir-firefox/jssh_socket.rb', line 496 def handle_json(js_expr, *args) ensure_prototype 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 JsshUndefinedValueError, "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 |
#inspect ⇒ Object
returns a string of basic information about this socket.
739 740 741 |
# File 'lib/vapir-firefox/jssh_socket.rb', line 739 def inspect "\#<#{self.class.name}:0x#{"%.8x"%(self.hash*2)} #{[:ip, :port, :prototype].map{|attr| aa="@#{attr}";aa+'='+instance_variable_get(aa).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.
546 547 548 |
# File 'lib/vapir-firefox/jssh_socket.rb', line 546 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 JsshObject representing that reference on this socket. ref should be a string representing a reference in javascript.
571 572 573 |
# File 'lib/vapir-firefox/jssh_socket.rb', line 571 def object(ref, other={}) JsshObject.new(ref, self, {:debug_name => ref}.merge(other)) end |
#object_in_temp(ref, other = {}) ⇒ Object
takes a reference and returns a new JsshObject representing that reference on this socket, stored on this socket’s temporary object.
576 577 578 |
# File 'lib/vapir-firefox/jssh_socket.rb', line 576 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
553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 |
# File 'lib/vapir-firefox/jssh_socket.rb', line 553 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 |
#root ⇒ Object
represents the root of the space seen by the JsshSocket, and implements #method_missing to return objects at the root level in a similar manner to JsshObject’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
=> #<JsshObject: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 #<JsshObject: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 JsshUndefinedValueError: undefined expression represented by #<JsshObject: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 => #<JsshObject: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 JsshObject:
>> 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! JsshError::NS_ERROR_FAILURE: Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsIJSON.encode]
622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 |
# File 'lib/vapir-firefox/jssh_socket.rb', line 622 def root jssh_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 jssh_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 root # end end |
#temp_object ⇒ Object
returns a JsshObject 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.
709 710 711 |
# File 'lib/vapir-firefox/jssh_socket.rb', line 709 def temp_object @temp_object ||= root.JsshTemp 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’
529 530 531 532 533 534 535 536 537 538 539 540 541 542 |
# File 'lib/vapir-firefox/jssh_socket.rb', line 529 def typeof(expression) ensure_prototype js="try { nativeJSON_encode_length({errored: false, value: (function(object){ return (object===null) ? 'null' : (typeof object); })(#{expression})}); } catch(e) { if(e.name=='ReferenceError') { nativeJSON_encode_length({errored: false, value: 'undefined'}); } else { nativeJSON_encode_length({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 JSSH.
This will be a string, the given expression’s toString.
361 362 363 364 365 |
# File 'lib/vapir-firefox/jssh_socket.rb', line 361 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.
412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 |
# File 'lib/vapir-firefox/jssh_socket.rb', line 412 def value_json(js, ={}) ={:error_on_undefined => true}.merge() raise ArgumentError, "Expected a string containing a javascript expression! received #{js.inspect} (#{js.class})" unless js.is_a?(String) ensure_prototype 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, .merge(:length_before_value => true)) error_or_val_json(val, js) end |