Skip to content
This repository was archived by the owner on Dec 4, 2023. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions ext/v8/context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ void Context::Init() {
defineSingletonMethod("GetCalling", &GetCalling).
defineSingletonMethod("InContext", &InContext).
defineMethod("Dispose", &Dispose).
defineMethod("Clone", &Clone).
defineMethod("Global", &Global).
defineMethod("DetachGlobal", &Global).
defineMethod("ReattachGlobal", &ReattachGlobal).
Expand All @@ -33,6 +34,53 @@ VALUE Context::Dispose(VALUE self) {
Void(Context(self).dispose())
}

VALUE Context::Clone(VALUE self, VALUE new_rr_context) {
// acquire the lock
v8::Locker locker;
v8::HandleScope handle_scope;

// unwrap V8 contexts
v8::Context* original_context = *((v8::Handle<v8::Context>) Context(self));
v8::Context* new_context = *((v8::Handle<v8::Context>) Context(new_rr_context));

// get the original global context
v8::Local<v8::Value> key, value;

original_context->Enter();

v8::Local<v8::Object> original_global(original_context->Global());
v8::Local<v8::Array> property_names = original_global->GetPropertyNames();
uint32_t i, length = property_names->Length();
std::vector< v8::Local<v8::Value> > values;

for (i = 0; i < length; i++) {
key = property_names->Get(i);
value = original_global->Get(key);
values.push_back(value);
}

original_context->Exit();

// and copy everything to the new context
new_context->Enter();

v8::Local<v8::Object> new_global(new_context->Global());
std::vector< v8::Local<v8::Value> >::iterator it;

for (i = 0, it = values.begin(); i < length; i++, it++) {
key = property_names->Get(i);
value = *it;
new_global->Set(key, value);
}

new_context->Exit();

// unlock
v8::Unlocker unlocker;

return Qnil;
}

VALUE Context::Global(VALUE self) {
return Object(Context(self)->Global());
}
Expand Down
1 change: 1 addition & 0 deletions ext/v8/rr.h
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ class Context : public Ref<v8::Context> {
static void Init();
static VALUE New(int argc, VALUE argv[], VALUE self);
static VALUE Dispose(VALUE self);
static VALUE Clone(VALUE self, VALUE new_context);
static VALUE Enter(VALUE self);
static VALUE Exit(VALUE self);
static VALUE Global(VALUE self);
Expand Down
11 changes: 11 additions & 0 deletions lib/v8/context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,12 @@ class Context
# * :with scope serves as the global scope of the new context
# @yield [V8::Context] the newly created context
def initialize(options = {})
@options = options

@conversion = Conversion.new
@access = Access.new
@timeout = options[:timeout]

if global = options[:with]
Context.new.enter do
global_template = global.class.to_template.InstanceTemplate()
Expand Down Expand Up @@ -137,6 +140,14 @@ def self.enter
end
end

def deep_clone
fail ArgumentError, 'cannot clone an empty context' unless @native

self.class.new(@options).tap do |new_context|
native.Clone(new_context.native)
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.
Expand Down
120 changes: 110 additions & 10 deletions spec/v8/context_spec.rb
Original file line number Diff line number Diff line change
@@ -1,19 +1,119 @@
require 'spec_helper'

describe V8::Context do
it "can be disposed of" do
cxt = V8::Context.new
cxt.enter do
cxt['object'] = V8::Object.new
describe '.dispose' do
it "can be disposed of" do
cxt = V8::Context.new
cxt.enter do
cxt['object'] = V8::Object.new
end
cxt.dispose()

lambda {cxt.eval('1 + 1')}.should raise_error
lambda {cxt['object']}.should raise_error
end
cxt.dispose()

lambda {cxt.eval('1 + 1')}.should raise_error
lambda {cxt['object']}.should raise_error
it "can be disposed of any number of times" do
cxt = V8::Context.new
10.times {cxt.dispose()}
end
end

it "can be disposed of any number of times" do
cxt = V8::Context.new
10.times {cxt.dispose()}
describe '.deep_clone' do
let(:js) do
<<-js
if (typeof x === 'undefined') {
x = 1;
} else {
x = x + 1;
}
js
end

it 'duplicates the underlying native object' do
cxt = V8::Context.new

cxt.eval(js)

cloned_cxt = cxt.deep_clone

cxt.eval(js)
cxt["x"].should == 2

cloned_cxt["x"].should == 1
cloned_cxt.eval(js)
cloned_cxt["x"].should == 2

cxt.eval(js)
cxt["x"].should == 3
end

it 'duplicates functions' do
cxt = V8::Context.new

cxt.eval('var val = {num: 5, isTruthy: function (arg) { return !!arg }}')

cloned_cxt = cxt.deep_clone

cloned_cxt["val"].isTruthy(1).should be(true)
end

it "keeps closures' contexts" do
js = <<-js
function wrapper(x) { return function() { return x; } };
var f = wrapper(42);
js

cxt = V8::Context.new

cxt.eval(js)

cloned_cxt = cxt.deep_clone

cloned_cxt.eval('x = f();')

cloned_cxt["x"].should == 42
end

it 'works on empty contexts' do
cxt = V8::Context.new

cloned_cxt = cxt.deep_clone

cxt.eval(js)
cxt["x"].should == 1

cloned_cxt["x"].should be(nil)
end

it 'keeps original options' do
cxt = V8::Context.new(:timeout => 10)

cloned_cxt = cxt.deep_clone

cloned_cxt.timeout.should == 10
end

context 'when the original context has been disposed of' do
it 'should not allow cloning any more' do
cxt = V8::Context.new

cxt.dispose

lambda { cxt.deep_clone }.should raise_error
end

it 'should not affect previously cloned contexts' do
cxt = V8::Context.new
cxt.eval(js)

cloned_cxt = cxt.deep_clone

cxt.dispose

cloned_cxt.eval(js)
cloned_cxt["x"].should == 2
end
end
end
end