diff --git a/lib/v8/context.rb b/lib/v8/context.rb index 46d381b..a42f738 100644 --- a/lib/v8/context.rb +++ b/lib/v8/context.rb @@ -1,9 +1,56 @@ require 'stringio' module V8 + # All JavaScript must be executed in a context. This context consists of a global scope containing the + # standard JavaScript objects¨and functions like Object, String, Array, as well as any objects or + # functions from Ruby which have been embedded into it from the containing enviroment. E.g. + # + # V8::Context.new do |cxt| + # cxt['num'] = 5 + # cxt.eval('num + 5') #=> 10 + # end + # + # The same object may appear in any number of contexts, but only one context may be executing JavaScript code + # in any given thread. If a new context is opened in a thread in which a context is already opened, the second + # context will "mask" the old context e.g. + # + # six = 6 + # Context.new do |cxt| + # cxt['num'] = 5 + # cxt.eval('num') # => 5 + # Context.new do |cxt| + # cxt['num'] = 10 + # cxt.eval('num') # => 10 + # cxt.eval('++num') # => 11 + # end + # cxt.eval('num') # => 5 + # end class Context include V8::Error::Try - attr_reader :native, :conversion, :access + # defines conversion behavior for this context + # @see V8::Conversion + attr_reader :conversion + + # defines access to Ruby objects for this context + # @see V8::Access + attr_reader :access + + # a reference to the underlying C++ object + attr_reader :native + + # Creates a new context. + # + # If passed the `:with` option, that object will be used as + # the global scope of the newly creating context. e.g. + # + # scope = Object.new + # def scope.hello; "Hi"; end + # V8::Context.new(:with => scope) do |cxt| + # cxt['hello'] #=> 'Hi' + # end + # + # @param Hash options initial context configuration + # * :with => Object scope serves as the global scope of the new context def initialize(options = {}) @conversion = Conversion.new @access = Access.new @@ -19,6 +66,14 @@ module V8 yield self if block_given? end + # Compile and execute a string of JavaScript source. + # + # If `source` is an IO object it will be read fully before being evaluated + # + # @param Object source the source code to compile and execute + # @param String filename the name to use for this code when generating stack traces + # @param Integer line the line number to start with + # @return Object the result of the evaluation def eval(source, filename = '', line = 1) if IO === source || StringIO === source source = source.read @@ -29,12 +84,20 @@ module V8 end end + # Read a value from the global scope of this context + # + # @param Object key the name of the value to read + # @return Object value the value at `key` def [](key) enter do to_ruby(@native.Global().Get(to_v8(key))) end end + # Binds `value` to the name `key` in the global scope of this context. + # + # @param key the name to bind to + # @param value the value to bind def []=(key, value) enter do @native.Global().Set(to_v8(key), to_v8(value)) @@ -42,6 +105,11 @@ module V8 return value end + # Destroy this context and release any internal references it may + # contain to embedded Ruby objects. + # + # A disposed context may never again be used for anything, and all + # objects created with it will become unusable. def dispose return unless @native @native.Dispose() @@ -52,26 +120,68 @@ module V8 end end + # Returns this context's global object. This will be a `V8::Object` + # if no scope was provided or just an `Object` if a Ruby object + # is serving as the global scope. + # + # @return Object scope the context's global scope. def scope enter { to_ruby @native.Global() } end + # Converts a v8 C++ object into its ruby counterpart. This is method + # is used to translate all values passed to Ruby from JavaScript, either + # as return values or as callback parameters. + # + # @param V8::C::Object v8_object the native c++ object to convert. + # @return Object to pass to Ruby + # @see V8::Conversion for how to customize and extend this mechanism def to_ruby(v8_object) @conversion.to_ruby(v8_object) end + # Converts a Ruby object into a native v8 C++ object. This method is + # used to translate all values passed to JavaScript from Ruby, either + # as return value or as callback parameters. + # + # @param Object ruby_object the Ruby object to convert + # @return V8::C::Object to pass to V8 + # @see V8::Conversion for customizing and extending this mechanism def to_v8(ruby_object) @conversion.to_v8(ruby_object) end + # Marks a Ruby object and a v8 C++ Object as being the same. In other + # words whenever `ruby_object` is passed to v8, the result of the + # conversion should be `v8_object`. Conversely, whenever `v8_object` + # is passed to Ruby, the result of the conversion should be `ruby_object`. + # The Ruby Racer uses this mechanism to maintain referential integrity + # between Ruby and JavaScript peers + # + # @param Object ruby_object the Ruby half of the object identity + # @param V8::C::Object v8_object the V8 half of the object identity. + # @see V8::Conversion::Identity def link(ruby_object, v8_object) @conversion.equate ruby_object, v8_object end - def self.link(*args) - current.link *args + # Links `ruby_object` and `v8_object` inside the currently entered + # context. This is an error if no context has been entered. + # + # @param Object ruby_object the Ruby half of the object identity + # @param V8::C::Object v8_object the V8 half of the object identity. + def self.link(ruby_object, v8_object) + current.link ruby_object, v8_object end + # Run some Ruby code in the context of this context. + # + # This will acquire the V8 interpreter lock (possibly blocking + # until it is available), and prepare V8 for JavaScript execution. + # + # Only one context may be running at a time per thread. + # + # @return Object the result of executing `block` def enter(&block) if !entered? lock_scope_and_enter(&block) @@ -80,14 +190,33 @@ module V8 end end + # Indicates if this context is the currently entered context + # + # @return true if this context is currently entered def entered? Context.current == self end + # Get the currently entered context. + # + # @return V8::Context currently entered context, nil if none entered. def self.current Thread.current[:v8_context] end + # Compile and execute the contents of the file with path `filename` + # as JavaScript code. + # + # @param String filename path to the file to execute. + # @return Object the result of the evaluation. + def load(filename) + File.open(filename) do |file| + self.eval file, filename + end + end + + private + def self.current=(context) Thread.current[:v8_context] = context end @@ -108,11 +237,5 @@ module V8 ensure Context.current = current end - - def load(filename) - File.open(filename) do |file| - self.eval file, filename - end - end end end \ No newline at end of file