Class: H8::Context

Inherits:
Object show all
Defined in:
lib/h8/context.rb,
ext/h8/main.cpp

Overview

Context is an environment where javscripts and coffeescripts can be executed. Context holds its state between execution and can therefore consume large amount of memory if called repeatedly for a long time. You can release some of its memory by setting to null its global level variables, but the best is to allocate new Context as need leaving old instances fo GC. Please note that Context can not be GC’d if any of it objects is gated and held by ruby somewhere.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(noglobals: false, **kwargs) ⇒ Context

Create new context optionally providing variables hash



96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/h8/context.rb', line 96

def initialize noglobals: false, **kwargs
  @idcount = 0
  set_all **kwargs
  _set_var '___create_ruby_class', -> (cls, args) {
    _do_create_ruby_class cls, args
  }

  self[:debug] = -> (*args) {
    puts args.join(' ')
  }

  noglobals or execute_script 'globals.coffee'
end

Class Method Details

.can_access?(owner) ⇒ Boolean

Returns:

  • (Boolean)


244
245
246
247
# File 'lib/h8/context.rb', line 244

def self.can_access?(owner)
  return true if owner.is_a?(Array.class)
  owner != Object.class && owner != Kernel && owner != BasicObject.class
end

.delete_handler(object, args) ⇒ Object

:nodoc: Internal handler to properly delete fields/keys from ruby Hashes or OpenStruct



235
236
237
238
239
240
241
242
# File 'lib/h8/context.rb', line 235

def self.delete_handler object, args
  name = args[0]
  if object.is_a?(OpenStruct)
    object.delete_field name
  else
    object.delete name
  end
end

.eval(script, file_name: nil, **kwargs) ⇒ Value

Execute script in a new context with optionally set vars. @see H8#set_all

Returns:

  • (Value)

    wrapped object returned by the script



153
154
155
# File 'lib/h8/context.rb', line 153

def self.eval script, file_name: nil, ** kwargs
  Context.new(** kwargs).eval script, file_name: file_name
end

.secure_call(instance, method, args = nil) ⇒ Object

Secure gate for JS to securely access ruby class properties (methods with no args) and methods. This class implements security policy. Overriding this method could breach security and provide full access to ruby object trees.

It has very complex logic so the security model update should be done somehow else.



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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/h8/context.rb', line 163

def self.secure_call instance, method, args=nil
  # p [:sc, instance, method, args]
  if instance.is_a?(Array)
    method == 'select' and method = '__js_select'
  end
  immediate_call = if method[0] == '!'
                     method = method[1..-1]
                     true
                   else
                     false
                   end
  method = method.to_sym
  begin
    m     = instance.public_method(method)
    owner = m.owner
    if can_access?(owner)
      return m.call(*args) if method[0] == '[' || method[-1] == '=' || immediate_call
      if m.arity != 0
        return ProcGate.new( -> (*args) { m.call *args } )
      else
        return m.call
      end
    end
  rescue NameError
    # No exact method, calling []/[]= if any
    method, args = if method[-1] == '='
                     [:[]=, [method[0..-2].to_s, args[0]]]
                   else
                     [:[], [method.to_s]]
                   end
    begin
      m = instance.public_method(method)
      if can_access?(owner)
        if method == :[]
          if instance.is_a?(Hash)
            return m.call(*args) || m.call(args[0].to_sym)
          else
            return m.call(*args)
          end
        else
          return m.call(*args)
        end
      end
    rescue NameError
      # It means there is no [] or []=, e.g. undefined
    rescue TypeError
      raise unless $!.to_s =~ /no implicit conversion of String into Integer/
      # This also means that property is not found
    end
  end
  H8::Undefined
end

Instance Method Details

#[]=(name, value) ⇒ Object

Set variable/class for the context. It can set variable to hold:

* primitive type (like string, integer, nil, float and like) - by value!
* any other ruby object - by reference
* ruby Class - creating a javascript constructor function that creates ruby
  class instance (any arguments) and gates it to use with js.


123
124
125
# File 'lib/h8/context.rb', line 123

def []= name, value
  set_all name.to_sym => value
end

#coffee(script, **kwargs) ⇒ Object

Compile and execute coffeescript, taking same arguments as #eval.

If you need to execute same script more than once consider first H8::Coffee.compile and cache compiled script.



146
147
148
# File 'lib/h8/context.rb', line 146

def coffee script, ** kwargs
  eval Coffee.compile script, ** kwargs
end

#eval(script, max_time: 0, timeout: 0, file_name: nil) {|_self| ... } ⇒ Value

Execute a given script on the current context with optionally limited execution time.

Parameters:

  • timeout (Int) (defaults to: 0)

    if is not 0 then maximum execution time in millis (therubyracer compatibility)

  • max_time (Float) (defaults to: 0)

    if is not 0 then maximum execution time in seconds. Has precedence over timeout

Yields:

  • (_self)

Yield Parameters:

  • _self (H8::Context)

    the object that the method was called on

Returns:

  • (Value)

    wrapped object returned by the script

Raises:



136
137
138
139
140
# File 'lib/h8/context.rb', line 136

def eval script, max_time: 0, timeout: 0, file_name: nil
  timeout = max_time * 1000 if max_time > 0
  yield(self) if block_given?
  _eval script, timeout.to_i, file_name
end

#safe_proc_call(proc, args) ⇒ Object

This is workaround for buggy rb_proc_call which produces segfaults if proc is not exactly a proc, so we call it like this:



218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/h8/context.rb', line 218

def safe_proc_call proc, args
  if proc.respond_to?(:call)
    proc.call(*args)
  else
    if args.length == 0
      proc # Popular bug: call no-arg method not like a property
    else
      raise NoMethodError, "Invalid callable"
    end
  end
  # proc.is_a?(Array) ? proc : proc.call(*args)
end

#set_all(**kwargs) ⇒ Object

set variables from keyword arguments to this context see #[]= for details



112
113
114
115
116
# File 'lib/h8/context.rb', line 112

def set_all **kwargs
  kwargs.each { |name, value|
    set_var(name.to_s, value)
  }
end