Class: JavascriptObject

Inherits:
Object
  • Object
show all
Defined in:
lib/vapir-firefox/javascript_object.rb

Overview

represents a javascript object in ruby.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(ref, firefox_socket, other = {}) ⇒ JavascriptObject

initializes a JavascriptObject with a string of javascript containing a reference to the object, and a FirefoxSocket that the object is defined on.

Raises:

  • (ArgumentError)


21
22
23
24
25
26
27
28
29
30
31
# File 'lib/vapir-firefox/javascript_object.rb', line 21

def initialize(ref, firefox_socket, other={})
  other={:debug_name => ref, :function_result => false}.merge(other)
  raise ArgumentError, "Empty object reference!" if !ref || ref==''
  raise ArgumentError, "Reference must be a string - got #{ref.inspect} (#{ref.class.name})" unless ref.is_a?(String)
  raise ArgumentError, "Not given a FirefoxSocket, instead given #{firefox_socket.inspect} (#{firefox_socket.class.name})" unless firefox_socket.is_a?(FirefoxSocket)
  @ref=ref
  @firefox_socket=firefox_socket
  @debug_name=other[:debug_name]
  @function_result=other[:function_result]
#    logger.info { "#{self.class} initialized: #{debug_name} (type #{type})" }
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *args) ⇒ Object

method_missing handles unknown method calls in a way that makes it possible to write javascript-like syntax in ruby, to some extent.

method_missing checks the attribute of the represented javascript object with with the name of the given method. if that attribute refers to a function, then that function is called with any given arguments (like #invoke does). If that attribute is undefined, an error will be raised, unless a ‘?’ suffix is used (see below).

method_missing will only try to deal with methods that look like /^[a-z_]*$/i - no special characters, only alphanumeric/underscores, starting with alpha or underscore - with the exception of three special behaviors:

If the method ends with an equals sign (=), it does assignment - it calls #assign on the given attribute, with the given (single) argument, to do the assignment and returns the assigned value.

If the method ends with a bang (!), then it will attempt to get the value of the reference, using JavascriptObject#val, which converts the javascript to json and then to ruby. For simple types (null, string, boolean, number), this is what gets returned anyway. With other types (usually the ‘object’ type), attempting to convert to json can raise errors or cause infinite recursion, so is not attempted. but if you have an object or an array that you know you can json-ize, you can use ! to force that.

If the method ends with a question mark (?), then if the attribute is undefined, no error is raised (as usually happens) - instead nil is just returned.

otherwise, method_missing behaves like #invoke, and returns a JavascriptObject, a string, a boolean, a number, or null.

Since method_missing returns a JavascriptObject for javascript objects, this means that you can string together method_missings and the result looks rather like javascript. – $A and $H, used below, are methods of the Prototype javascript library, which add nice functional methods to arrays and hashes - see www.prototypejs.org/ You can use these methods with method_missing just like any other:

>> js_hash=firefox_socket.object('$H')
=> #<JavascriptObject:0x2beb598 @ref="$H" ...>
>> js_arr=firefox_socket.object('$A')
=> #<JavascriptObject:0x2be40e0 @ref="$A" ...>

>> js_arr.call(document.body.childNodes).pluck! :tagName
=> ["TEXTAREA", "DIV", "NOSCRIPT", "DIV", "DIV", "DIV", "BR", "TABLE", "DIV", "DIV", "DIV", "TEXTAREA", "DIV", "DIV", "SCRIPT"]
>> js_arr.call(document.body.childNodes).pluck! :id
=> ["csi", "header", "", "ssb", "tbd", "res", "", "nav", "wml", "", "", "hcache", "xjsd", "xjsi", ""]
>> js_hash.call(document.getElementById('tbd')).keys!
=> ["addEventListener", "appendChild", "className", "parentNode", "getElementsByTagName", "title", ...]


460
461
462
463
464
465
466
467
468
469
470
# File 'lib/vapir-firefox/javascript_object.rb', line 460

def method_missing(method, *args)
  method=method.to_s
  if method =~ /\A([a-z_][a-z0-9_]*)([=?!])?\z/i
    method = $1
    suffix = $2
    attr(method).assign_or_call_or_val_or_object_by_suffix(suffix, *args)
  else
    # don't deal with any special character crap 
    super
  end
end

Instance Attribute Details

#debug_nameObject (readonly)

this tracks the origins of this object - what calls were made along the way to get it.



10
11
12
# File 'lib/vapir-firefox/javascript_object.rb', line 10

def debug_name
  @debug_name
end

#firefox_socketObject (readonly)

the FirefoxSocket this JavascriptObject is on



6
7
8
# File 'lib/vapir-firefox/javascript_object.rb', line 6

def firefox_socket
  @firefox_socket
end

#function_resultObject (readonly)

whether this represents the result of a function call (if it does, then FirefoxSocket#typeof won’t be called on it)



8
9
10
# File 'lib/vapir-firefox/javascript_object.rb', line 8

def function_result
  @function_result
end

#refObject (readonly)

the reference to the javascript object this JavascriptObject represents



4
5
6
# File 'lib/vapir-firefox/javascript_object.rb', line 4

def ref
  @ref
end

Class Method Details

.always_define_methodsObject

whether JavascriptObject shall try to dynamically define methods on initialization, using #define_methods! default is false.



40
41
42
43
44
45
46
# File 'lib/vapir-firefox/javascript_object.rb', line 40

def self.always_define_methods
  unless class_variable_defined?('@@always_define_methods')
    # if not defined, set the default. 
    @@always_define_methods=false
  end
  @@always_define_methods
end

.always_define_methods=(val) ⇒ Object

set whether JavascriptObject shall try to dynamically define methods in #val_or_object, using #define_methods!

I find this useful to set to true in irb, for tab-completion of methods. it may cause operations to be considerably slower, however.

for always setting this in irb, I set this beforehand, overriding the default, by including in my .irbrc the following (which doesn’t require this file to be required):

class JavascriptObject
  @@always_define_methods=true
end


60
61
62
# File 'lib/vapir-firefox/javascript_object.rb', line 60

def self.always_define_methods=(val)
  @@always_define_methods = val
end

Instance Method Details

#%(operand) ⇒ Object

modulus, using the % operator in javascript



384
385
386
# File 'lib/vapir-firefox/javascript_object.rb', line 384

def %(operand)
  binary_operator('%', operand)
end

#*(operand) ⇒ Object

multiplication, using the * operator in javascript



380
381
382
# File 'lib/vapir-firefox/javascript_object.rb', line 380

def *(operand)
  binary_operator('*', operand)
end

#+(operand) ⇒ Object

addition, using the + operator in javascript



368
369
370
# File 'lib/vapir-firefox/javascript_object.rb', line 368

def +(operand)
  binary_operator('+', operand)
end

#-(operand) ⇒ Object

subtraction, using the - operator in javascript



372
373
374
# File 'lib/vapir-firefox/javascript_object.rb', line 372

def -(operand)
  binary_operator('-', operand)
end

#/(operand) ⇒ Object

division, using the / operator in javascript



376
377
378
# File 'lib/vapir-firefox/javascript_object.rb', line 376

def /(operand)
  binary_operator('/', operand)
end

#<(operand) ⇒ Object

inequality, using the < operator in javascript



401
402
403
# File 'lib/vapir-firefox/javascript_object.rb', line 401

def <(operand)
  binary_operator('<', operand)
end

#<=(operand) ⇒ Object

inequality, using the <= operator in javascript



409
410
411
# File 'lib/vapir-firefox/javascript_object.rb', line 409

def <=(operand)
  binary_operator('<=', operand)
end

#==(operand) ⇒ Object

returns true if the javascript object represented by this is equal to the given operand.



388
389
390
# File 'lib/vapir-firefox/javascript_object.rb', line 388

def ==(operand)
  operand.is_a?(JavascriptObject) && binary_operator('==', operand)
end

#>(operand) ⇒ Object

inequality, using the > operator in javascript



397
398
399
# File 'lib/vapir-firefox/javascript_object.rb', line 397

def >(operand)
  binary_operator('>', operand)
end

#>=(operand) ⇒ Object

inequality, using the >= operator in javascript



405
406
407
# File 'lib/vapir-firefox/javascript_object.rb', line 405

def >=(operand)
  binary_operator('>=', operand)
end

#[](key) ⇒ Object

returns a JavascriptObject referring to a subscript of this object, or a value if it is simple (see #val_or_object)

subscript is specified as ruby (converted to javascript).



351
352
353
# File 'lib/vapir-firefox/javascript_object.rb', line 351

def [](key)
  sub(key).val_or_object(:error_on_undefined => false)
end

#[]=(key, value) ⇒ Object

assigns the given ruby value (which is converted to javascript) to the given subscript (the key is also converted to javascript).



357
358
359
# File 'lib/vapir-firefox/javascript_object.rb', line 357

def []=(key, value)
  self.sub(key).assign(value)
end

#assign(val) ⇒ Object

assigns the given ruby value (converted to javascript) to the reference for this object. returns self.



243
244
245
246
247
248
# File 'lib/vapir-firefox/javascript_object.rb', line 243

def assign(val)
  @debug_name="(#{debug_name}=#{val.is_a?(JavascriptObject) ? val.debug_name : FirefoxSocket.to_javascript(val)})"
  result=assign_expr(FirefoxSocket.to_javascript(val))
#    logger.info { "#{self.class} assigned: #{debug_name} (type #{type})" }
  result
end

#assign_expr(javascript_expression) ⇒ Object

assigns the given javascript expression (string) to the reference for this object



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/vapir-firefox/javascript_object.rb', line 250

def assign_expr(javascript_expression)
  # don't want to use FirefoxSocket#assign_json because converting the result of the assignment 
  # (that is, the expression assigned) to json is error-prone and we don't really care about the 
  # result. 
  #
  # don't want to use FirefoxSocket#assign because the result can be blank and cause send_and_read 
  # to wait for data that's not coming - also using a json function is better because it catches 
  # errors much more elegantly. 
  #
  # so, wrap it in its own function, whose return value is unrelated to the actual assignment. 
  # for efficiency, it returns the type (it used to just return nil and uncache type for later 
  # retrieval), as this is desirable information, and since there's no other information we 
  # particularly desire from this call, that is a good thing to return. 
  @type=firefox_socket.value_json("(function(val){#{ref}=val; return (val===null) ? 'null' : (typeof val);}(#{javascript_expression}))")
  self
end

#assign_or_call_or_val_or_object_by_suffix(suffix, *args) ⇒ Object

does the work of #method_missing to determine whether to call a function what to return based on the defined behavior of the given suffix. see #method_missing for more information.



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/vapir-firefox/javascript_object.rb', line 196

def assign_or_call_or_val_or_object_by_suffix(suffix, *args)
  if suffix=='='
    assign(*args)
  else
    obj = if !args.empty? || type=='function'
      pass(*args)
    else
      self
    end
    case suffix
    when nil
      obj.val_or_object
    when '?'
      obj.val_or_object(:error_on_undefined => false)
    when '!'
      obj.val
    else
      raise ArgumentError, "suffix should be one of: nil, '?', '!', '='; got: #{suffix.inspect}"
    end
  end
end

#attr(attribute) ⇒ Object

returns a JavascriptObject referencing the given attribute of this object



234
235
236
237
238
239
# File 'lib/vapir-firefox/javascript_object.rb', line 234

def attr(attribute)
  unless (attribute.is_a?(String) || attribute.is_a?(Symbol)) && attribute.to_s =~ /\A[a-z_][a-z0-9_]*\z/i
    raise FirefoxSocketSyntaxError, "#{attribute.inspect} (#{attribute.class.inspect}) is not a valid attribute!"
  end
  JavascriptObject.new("#{ref}.#{attribute}", firefox_socket, :debug_name => "#{debug_name}.#{attribute}")
end

#binary_operator(operator, operand) ⇒ Object

calls a binary operator (in javascript) with self and another operand.

the operator should be string of javascript; the operand will be converted to javascript.



364
365
366
# File 'lib/vapir-firefox/javascript_object.rb', line 364

def binary_operator(operator, operand)
  JavascriptObject.new("(#{ref}#{operator}#{FirefoxSocket.to_javascript(operand)})", firefox_socket, :debug_name => "(#{debug_name}#{operator}#{operand.is_a?(JavascriptObject) ? operand.debug_name : FirefoxSocket.to_javascript(operand)})").val_or_object
end

#call(*args) ⇒ Object

returns the value (via FirefoxSocket#value_json) or a JavascriptObject (see #val_or_object) of the return value of this function (assumes this object is a function) passing it the given arguments (which are converted to javascript).

simply, it just calls self.pass(*args).val_or_object



278
279
280
# File 'lib/vapir-firefox/javascript_object.rb', line 278

def call(*args)
  pass(*args).val_or_object
end

#define_methods!Object

calls define_method for each key of this object as a hash. useful for tab-completing attributes in irb, mostly.



473
474
475
476
477
478
479
480
481
482
483
484
# File 'lib/vapir-firefox/javascript_object.rb', line 473

def define_methods! # :nodoc:
  metaclass=(class << self; self; end)
  # the following needs the try/catch because sometimes it raises NS_ERROR_NOT_AVAILABLE: Component is not available
  # bug: https://bugzilla.mozilla.org/show_bug.cgi?id=683978
  keys=firefox_socket.object("function(obj) { var keys=[]; try { for(var key in obj) { keys.push(key); } } catch(e) {} return keys; }").pass(self).val
  
  keys.grep(/\A[a-z_][a-z0-9_]*\z/i).reject{|k| self.class.method_defined?(k)}.each do |key|
    metaclass.send(:define_method, key) do |*args|
      invoke(key, *args)
    end
  end
end

#implemented_interfacesObject

returns an array of interfaces which this object is an instance of. this is achieved by looping over each value of Components.interfaces (see developer.mozilla.org/en/Components.interfaces ) and calling the #instanceof operator with this and the interface.

this may be rather slow.



100
101
102
103
104
105
# File 'lib/vapir-firefox/javascript_object.rb', line 100

def implemented_interfaces
  firefox_socket.Components.interfaces.to_hash.inject([]) do |list, (key, interface)|
    list << interface if (instanceof(interface) rescue false)
    list
  end
end

#inspectObject

represents this javascript object in one line, displaying the type and debug name.



601
602
603
# File 'lib/vapir-firefox/javascript_object.rb', line 601

def inspect
  "\#<#{self.class.name}:0x#{"%.8x"%(self.hash*2)} #{[:type, :debug_name].map{|attr| attr.to_s+'='+send(attr).to_s}.join(', ')}>"
end

#instanceof(interface) ⇒ Object

calls the javascript instanceof operator on this object and the given interface (expected to be a JavascriptObject) note that the javascript instanceof operator is not to be confused with ruby’s #instance_of? method - this takes a javascript interface; #instance_of? takes a ruby module.

example:

window.instanceof(window.firefox_socket.Components.interfaces.nsIDOMChromeWindow)
=> true


92
93
94
# File 'lib/vapir-firefox/javascript_object.rb', line 92

def instanceof(interface)
  firefox_socket.instanceof(self.ref, interface.ref)
end

#invoke(attribute, *args) ⇒ Object

returns a JavascriptObject representing the given attribute. Checks the type, and if it is a function, calls the function with any arguments given (which are converted to javascript) and returns the return value of the function (or nil if the function returns undefined).

If the attribute is undefined, raises an error (if you want an attribute even if it’s undefined, use #invoke? or #attr).



224
225
226
# File 'lib/vapir-firefox/javascript_object.rb', line 224

def invoke(attribute, *args)
  attr(attribute).assign_or_call_or_val_or_object_by_suffix(nil, *args)
end

#invoke?(attribute, *args) ⇒ Boolean

same as #invoke, but returns nil for undefined attributes rather than raising an error.

Returns:

  • (Boolean)


229
230
231
# File 'lib/vapir-firefox/javascript_object.rb', line 229

def invoke?(attribute, *args)
  attr(attribute).assign_or_call_or_val_or_object_by_suffix('?', *args)
end

#new(*args) ⇒ Object

assuming the javascript object represented is a constructor, this returns a new instance passing the given arguments.

date_class = firefox_socket.object('Date')
=> #<JavascriptObject:0x0118eee8 type=function, debug_name=Date>
date = date_class.new
=> #<JavascriptObject:0x01188a84 type=object, debug_name=new Date()>
date.getFullYear
=> 2010
date_class.new('october 4, 1978').getFullYear
=> 1978


293
294
295
# File 'lib/vapir-firefox/javascript_object.rb', line 293

def new(*args)
  JavascriptObject.new("new #{ref}", firefox_socket, :debug_name => "new #{debug_name}").call(*args)
end

#object_respond_to?(method) ⇒ Boolean

returns true if the javascript object this represents responds to the given method. this does not pay attention to any defined ruby methods, just javascript.

Returns:

  • (Boolean)


492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
# File 'lib/vapir-firefox/javascript_object.rb', line 492

def object_respond_to?(method)
  method=method.to_s
  if method =~ /^([a-z_][a-z0-9_]*)([=?!])?$/i
    method = $1
    suffix = $2
  else # don't deal with any special character crap 
    return false
  end

  if self.type=='undefined'
    return false
  elsif suffix=='='
    if self.type=='object'
      return true # yeah, you can generally assign attributes to objects
    else
      return false # no, you can't generally assign attributes to (boolean, number, string, null)
    end
  else
    attr=attr(method)
    return attr.type!='undefined'
  end
end

#object_typeObject

returns the type of object that is reported by the javascript toString() method, which returns such as “[object Object]” or “[object XPCNativeWrapper [object HTMLDocument]]” This method returns ‘Object’ or ‘XPCNativeWrapper [object HTMLDocument]’ respectively. Raises an error if this JavascriptObject points to something other than a javascript ‘object’ type (‘function’ or ‘number’ or whatever)

this isn’t used, doesn’t seem useful, and may go away in the future.



114
115
116
117
118
119
120
121
122
123
124
# File 'lib/vapir-firefox/javascript_object.rb', line 114

def object_type
  @object_type ||= begin
    case type
    when 'object'
      self.toString! =~ /\A\[object\s+(.*)\]\Z/
      $1
    else
      raise FirefoxSocketJavascriptError, "Type is #{type}, not object"
    end
  end
end

#pass(*args) ⇒ Object

returns a JavascriptObject for the result of calling the function represented by this object, passing the given arguments, which are converted to javascript. if this is not a function, javascript will raise an error.



269
270
271
# File 'lib/vapir-firefox/javascript_object.rb', line 269

def pass(*args)
  JavascriptObject.new("#{ref}(#{args.map{|arg| FirefoxSocket.to_javascript(arg)}.join(', ')})", firefox_socket, :function_result => true, :debug_name => "#{debug_name}(#{args.map{|arg| arg.is_a?(JavascriptObject) ? arg.debug_name : FirefoxSocket.to_javascript(arg)}.join(', ')})")
end

#pretty_print(pp) ⇒ Object

:nodoc:



604
605
606
607
608
609
610
611
612
613
614
615
616
# File 'lib/vapir-firefox/javascript_object.rb', line 604

def pretty_print(pp) # :nodoc:
  pp.object_address_group(self) do
    pp.seplist([:type, :debug_name], lambda { pp.text ',' }) do |attr|
      pp.breakable ' '
      pp.group(0) do
        pp.text attr.to_s
        pp.text ': '
        #pp.breakable
        pp.text send(attr)
      end
    end
  end
end

#respond_to?(method, include_private = false) ⇒ Boolean

returns true if this object responds to the given method (that is, it’s a defined ruby method) or if #method_missing will handle it

Returns:

  • (Boolean)


487
488
489
# File 'lib/vapir-firefox/javascript_object.rb', line 487

def respond_to?(method, include_private = false)
  super || object_respond_to?(method)
end

#store(js_variable, somewhere_meaningful = true) ⇒ Object

sets the given javascript variable to this object, and returns a JavascriptObject referring to the variable.

>> foo=document.getElementById('guser').store('foo')
=> #<JavascriptObject:0x2dff870 @ref="foo" ...>
>> foo.tagName
=> "DIV"

the second argument is only used internally and shouldn’t be used.



306
307
308
309
310
# File 'lib/vapir-firefox/javascript_object.rb', line 306

def store(js_variable, somewhere_meaningful=true)
  stored=JavascriptObject.new(js_variable, firefox_socket, :function_result => false, :debug_name => somewhere_meaningful ? "(#{js_variable}=#{debug_name})" : debug_name)
  stored.assign_expr(self.ref)
  stored
end

#store_rand_object_key(object) ⇒ Object

stores this object in a random key of the given object and returns the stored object.



328
329
330
331
332
333
# File 'lib/vapir-firefox/javascript_object.rb', line 328

def store_rand_object_key(object)
  raise ArgumentError("Object is not a JavascriptObject: got #{object.inspect}") unless object.is_a?(JavascriptObject)
  store_rand_named do |r|
    object.sub(r).ref
  end
end

#store_rand_tempObject

stores this object in a random key of the designated temporary object for this socket and returns the stored object.



336
337
338
# File 'lib/vapir-firefox/javascript_object.rb', line 336

def store_rand_temp
  store_rand_object_key(firefox_socket.temp_object)
end

#sub(key) ⇒ Object

returns a JavascriptObject referring to a subscript of this object, specified as a ruby object converted to javascript.

similar to [], but [] calls #val_or_object; this always returns a JavascriptObject.



344
345
346
# File 'lib/vapir-firefox/javascript_object.rb', line 344

def sub(key)
  JavascriptObject.new("#{ref}[#{FirefoxSocket.to_javascript(key)}]", firefox_socket, :debug_name => "#{debug_name}[#{key.is_a?(JavascriptObject) ? key.debug_name : FirefoxSocket.to_javascript(key)}]")
end

#to_arrayObject

returns a JavascriptArray representing this object



535
536
537
# File 'lib/vapir-firefox/javascript_object.rb', line 535

def to_array
  JavascriptArray.new(self.ref, self.firefox_socket, :debug_name => debug_name)
end

#to_domObject

returns a JavascriptDOMNode representing this object



543
544
545
# File 'lib/vapir-firefox/javascript_object.rb', line 543

def to_dom
  JavascriptDOMNode.new(self.ref, self.firefox_socket, :debug_name => debug_name)
end

#to_functionObject

returns a JavascriptFunction representing this object



547
548
549
# File 'lib/vapir-firefox/javascript_object.rb', line 547

def to_function
  JavascriptFunction.new(self.ref, self.firefox_socket, :debug_name => debug_name)
end

#to_hashObject

returns a JavascriptHash representing this object



539
540
541
# File 'lib/vapir-firefox/javascript_object.rb', line 539

def to_hash
  JavascriptHash.new(self.ref, self.firefox_socket, :debug_name => debug_name)
end

#to_js_arrayObject

returns this object passed through the $A function of the prototype javascript library.



523
524
525
# File 'lib/vapir-firefox/javascript_object.rb', line 523

def to_js_array
  firefox_socket.object('$A').call(self)
end

#to_js_hashObject

returns this object passed through the $H function of the prototype javascript library.



527
528
529
# File 'lib/vapir-firefox/javascript_object.rb', line 527

def to_js_hash
  firefox_socket.object('$H').call(self)
end

#to_js_hash_safeObject

returns this object passed through a javascript function which copies each key onto a blank object and rescues any errors.



531
532
533
# File 'lib/vapir-firefox/javascript_object.rb', line 531

def to_js_hash_safe
  firefox_socket.object('$_H').call(self)
end

#to_ruby_arrayObject

returns an Array in which each element is the #val_or_Object of each element of this javascript array.



596
597
598
# File 'lib/vapir-firefox/javascript_object.rb', line 596

def to_ruby_array
  self.to_array.to_a
end

#to_ruby_hash(options = {}) ⇒ Object

returns a ruby Hash. each key/value pair of this object is represented in the returned hash.

if an error is encountered trying to access the value for an attribute, then in the returned hash, that attribute is set to the error that was encountered rather than the actual value (since the value wasn’t successfully retrieved).

options may be specified. the only option currently supported is:

  • :recurse => a number or nil. if it’s a number, then this will recurse to that depth. If it’s nil, this won’t recurse at all.

below the specified recursion level, this will return this JavascriptObject rather than recursing down into it.

this function isn’t expected to raise any errors, since encountered errors are set as attribute values.



570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
# File 'lib/vapir-firefox/javascript_object.rb', line 570

def to_ruby_hash(options={})
  options={:recurse => 1}.merge(options)
  return self if !options[:recurse] || options[:recurse]==0
  return self if self.type!='object'
  next_options=options.merge(:recurse => options[:recurse]-1)
  begin
    keys=self.to_hash.keys
  rescue FirefoxSocketError
    return self
  end
  keys.inject({}) do |hash, key|
    val=begin
      self[key]
    rescue FirefoxSocketError
      $!
    end
    hash[key]=if val.is_a?(JavascriptObject)
      val.to_ruby_hash(next_options)
    else
      val
    end
    hash
  end
end

#to_simple_enumeratorObject



550
551
552
# File 'lib/vapir-firefox/javascript_object.rb', line 550

def to_simple_enumerator
  JavascriptSimpleEnumerator.new(self.ref, self.firefox_socket, :debug_name => debug_name)
end

#triple_equals(operand) ⇒ Object

javascript triple-equals (===) operator. very different from ruby’s tripl-equals operator - in javascript this means “really really equal”; in ruby it means “sort of equal-ish”



393
394
395
# File 'lib/vapir-firefox/javascript_object.rb', line 393

def triple_equals(operand)
  operand.is_a?(JavascriptObject) && binary_operator('===', operand)
end

#typeObject

returns javascript typeof this object



75
76
77
78
79
80
81
82
# File 'lib/vapir-firefox/javascript_object.rb', line 75

def type
  if function_result # don't get type for function results, causes function evaluations when you probably didn't want that. 
    nil
  else
#      logger.add(-1) { "retrieving type for #{debug_name}" }
    @type||= firefox_socket.typeof(ref)
  end
end

#valObject

returns the value, via FirefoxSocket#value_json



34
35
36
# File 'lib/vapir-firefox/javascript_object.rb', line 34

def val
  firefox_socket.value_json(ref, :error_on_undefined => !function_result)
end

#val_or_object(options = {}) ⇒ Object

checks the type of this object, and if it is a type that can be simply converted to a ruby object via json, returns the ruby value. that occurs if the type is one of:

‘boolean’,‘number’,‘string’,‘null’

otherwise - if the type is something else (probably ‘function’ or ‘object’; or maybe something else) then this JavascriptObject is returned.

if the object this refers to is undefined in javascript, then behavor depends on the options hash. if :error_on_undefined is true, then nil is returned; otherwise FirefoxSocketUndefinedValueError is raised.

if this is a function result, this will store the result in a temporary location (thereby calling the function to acquire the result) before making the above decision.

this method also calls #define_methods! on this if JavascriptObject.always_define_methods is true. this can be overridden in the options hash using the :define_methods key (true or false).



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/vapir-firefox/javascript_object.rb', line 143

def val_or_object(options={})
  options={:error_on_undefined=>true, :define_methods => self.class.always_define_methods}.merge(options)
  if function_result # calling functions multiple times is bad, so store in temp before figuring out what to do with it
    store_rand_temp.val_or_object(options.merge(:error_on_undefined => false))
  else
    # if we don't know our type, stick everything into one call to avoid multiple socket calls 
    types_to_convert = ['boolean', 'number', 'string', 'null']
    if !@type
      type_and_value = firefox_socket.value_json(%Q((function()
      { var result={};
        var object;
        try
        { result.type=(function(object){ return (object===null) ? 'null' : (typeof object); })(object=#{ref});
        }
        catch(e)
        { if(e.name=='ReferenceError')
          { result.type='undefined';
          }
          else
          { throw(e);
          };
        }
        if($A(#{FirefoxSocket.to_javascript(types_to_convert)}).include(result.type))
        { result.value = object;
        }
        return result;
      })()))
      @type = type_and_value['type']
    end
    
    if type=='undefined'
      if !options[:error_on_undefined]
        nil
      else
        raise FirefoxSocketUndefinedValueError, "undefined expression represented by #{self.inspect} (javascript reference is #{@ref})"
      end
    elsif types_to_convert.include?(type)
      if type_and_value
        raise "internal error - type_and_value had no value key; was #{type_and_value.inspect}" unless type_and_value.key?('value') # this shouldn't happen 
        type_and_value['value']
      else
        val
      end
    else # 'function','object', or anything else 
      if options[:define_methods] && type=='object'
        define_methods!
      end
      self
    end
  end
end

#val_strObject

returns the value just as a string with no attempt to deal with type using json. via FirefoxSocket#value

note that this can be slow if it evaluates to a blank string. for example, if ref is just “” then FirefoxSocket#value will wait DEFAULT_SOCKET_TIMEOUT seconds for data that is not to come. this also happens with functions that return undefined. if ref=“function()do_some_stuff;” (with no return), it will also wait DEFAULT_SOCKET_TIMEOUT.



70
71
72
# File 'lib/vapir-firefox/javascript_object.rb', line 70

def val_str
  firefox_socket.value(ref)
end