therubyracer 0.8.2 → 0.9.0beta1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of therubyracer might be problematic. Click here for more details.
- data/Changelog.md +1 -1
- data/ext/v8/rr.cpp +14 -7
- data/ext/v8/rr.h +1 -0
- data/ext/v8/v8.cpp +27 -25
- data/ext/v8/v8_array.cpp +7 -9
- data/ext/v8/v8_callbacks.cpp +1 -1
- data/ext/v8/{v8_cxt.cpp → v8_context.cpp} +11 -11
- data/ext/v8/{v8_cxt.h → v8_context.h} +1 -1
- data/ext/v8/v8_date.cpp +6 -6
- data/ext/v8/v8_exception.cpp +10 -11
- data/ext/v8/v8_external.cpp +7 -24
- data/ext/v8/v8_external.h +0 -1
- data/ext/v8/{v8_func.cpp → v8_function.cpp} +14 -14
- data/ext/v8/{v8_func.h → v8_function.h} +1 -2
- data/ext/v8/v8_handle.cpp +119 -0
- data/ext/v8/v8_handle.h +27 -0
- data/ext/v8/{v8_msg.cpp → v8_message.cpp} +8 -9
- data/ext/v8/{v8_msg.h → v8_message.h} +1 -1
- data/ext/v8/{v8_obj.cpp → v8_object.cpp} +51 -29
- data/ext/v8/{v8_obj.h → v8_object.h} +3 -4
- data/ext/v8/v8_script.cpp +5 -5
- data/ext/v8/{v8_str.cpp → v8_string.cpp} +9 -11
- data/ext/v8/{v8_str.h → v8_string.h} +1 -1
- data/ext/v8/v8_template.cpp +113 -98
- data/ext/v8/v8_try_catch.cpp +1 -1
- data/ext/v8/v8_v8.cpp +7 -0
- data/ext/v8/v8_value.cpp +44 -36
- data/ext/v8/v8_value.h +2 -2
- data/ext/v8/v8_weakref.cpp +51 -0
- data/ext/v8/v8_weakref.h +30 -0
- data/lib/v8.rb +6 -1
- data/lib/v8/context.rb +13 -3
- data/lib/v8/error.rb +1 -1
- data/lib/v8/portal.rb +26 -277
- data/lib/v8/portal/caller.rb +36 -0
- data/lib/v8/portal/constructor.rb +98 -0
- data/lib/v8/portal/function.rb +48 -0
- data/lib/v8/portal/interceptors.rb +153 -0
- data/lib/v8/portal/proxies.rb +102 -0
- data/lib/v8/portal/templates.rb +73 -0
- data/lib/v8/version.rb +1 -1
- data/spec/ext/array_spec.rb +15 -0
- data/spec/ext/cxt_spec.rb +4 -4
- data/spec/ext/ext_spec_helper.rb +43 -0
- data/spec/ext/mem_spec.rb +42 -0
- data/spec/ext/object_spec.rb +22 -0
- data/spec/redjs/jsapi_spec.rb +4 -4
- data/spec/spec_helper.rb +1 -1
- data/spec/v8/portal/proxies_spec.rb +189 -0
- metadata +38 -42
- data/ext/v8/v8_ref.cpp +0 -37
- data/ext/v8/v8_ref.h +0 -28
- data/lib/v8/portal/functions.rb +0 -45
@@ -0,0 +1,36 @@
|
|
1
|
+
|
2
|
+
module V8
|
3
|
+
class Portal
|
4
|
+
class Caller
|
5
|
+
|
6
|
+
def initialize(portal)
|
7
|
+
@portal = portal
|
8
|
+
end
|
9
|
+
|
10
|
+
def raw
|
11
|
+
begin
|
12
|
+
yield
|
13
|
+
rescue Exception => e
|
14
|
+
case e
|
15
|
+
when SystemExit, NoMemoryError
|
16
|
+
raise e
|
17
|
+
else
|
18
|
+
error = V8::C::Exception::Error(V8::C::String::New(e.message))
|
19
|
+
error.SetHiddenValue("TheRubyRacer::Cause", C::External::New(e))
|
20
|
+
V8::C::ThrowException(error)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def protect(*args, &block)
|
26
|
+
@portal.v8 raw(*args, &block)
|
27
|
+
end
|
28
|
+
|
29
|
+
def invoke(code, *args, &block)
|
30
|
+
protect do
|
31
|
+
code.call(*args, &block)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
|
2
|
+
module V8
|
3
|
+
class Portal
|
4
|
+
class ConstructorAdapter
|
5
|
+
attr_reader :template, :function, :exposed
|
6
|
+
alias_method :exposed?, :exposed
|
7
|
+
|
8
|
+
def initialize(templates, class_id)
|
9
|
+
@exposed = false
|
10
|
+
@class_id = class_id
|
11
|
+
@templates = templates
|
12
|
+
@invoke = method(:invoke)
|
13
|
+
@template = C::FunctionTemplate::New(@invoke)
|
14
|
+
portal.interceptors.setup(@template.InstanceTemplate())
|
15
|
+
cls = self.ruby_class
|
16
|
+
superclass = cls.superclass
|
17
|
+
if cls != ::Object && superclass != ::Object && superclass != ::Class
|
18
|
+
@template.Inherit(templates.to_constructor(superclass).template)
|
19
|
+
end
|
20
|
+
if cls.name && cls.name =~ /(::)?(\w+?)$/
|
21
|
+
template.SetClassName(C::String::NewSymbol("rb::" + $2))
|
22
|
+
else
|
23
|
+
template.SetClassName("Ruby")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def function
|
28
|
+
@template.GetFunction()
|
29
|
+
end
|
30
|
+
|
31
|
+
def allocate(object)
|
32
|
+
arguments = C::Array::New(1)
|
33
|
+
arguments.Set(0, C::External::New(object))
|
34
|
+
function.NewInstance(arguments)
|
35
|
+
end
|
36
|
+
|
37
|
+
def disable
|
38
|
+
@disabled = true
|
39
|
+
end
|
40
|
+
|
41
|
+
def enable
|
42
|
+
@disabled = nil
|
43
|
+
end
|
44
|
+
|
45
|
+
def invoke(arguments)
|
46
|
+
return if @disabled
|
47
|
+
if !@exposed
|
48
|
+
unless arguments.Length() == 1 && arguments[0].kind_of?(C::External)
|
49
|
+
C::ThrowException(C::Exception::Error(C::String::New("cannot call native constructor from javascript")))
|
50
|
+
else
|
51
|
+
object = arguments[0].Value()
|
52
|
+
proxies.register_javascript_proxy arguments.This(), :for => object
|
53
|
+
end
|
54
|
+
else
|
55
|
+
instance = nil
|
56
|
+
if arguments.Length() > 0 && arguments[0].kind_of?(C::External)
|
57
|
+
instance = arguments[0].Value()
|
58
|
+
else
|
59
|
+
rbargs = []
|
60
|
+
for i in 0..arguments.Length() - 1
|
61
|
+
rbargs << @templates.portal.rb(arguments[i])
|
62
|
+
end
|
63
|
+
instance = portal.caller.raw do
|
64
|
+
self.ruby_class.new(*rbargs)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
proxies.register_javascript_proxy arguments.This(), :for => instance
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def exposed=(exposed)
|
72
|
+
if exposed && !@augmented
|
73
|
+
#create a prototype so that this constructor also acts like a ruby object
|
74
|
+
prototype_template = C::ObjectTemplate::New()
|
75
|
+
portal.interceptors.setup(prototype_template)
|
76
|
+
prototype = prototype_template.NewInstance()
|
77
|
+
#set *that* object's prototype to an empty function so that it will look and behave like a function.
|
78
|
+
prototype.SetPrototype(C::FunctionTemplate::New().GetFunction())
|
79
|
+
template.GetFunction().SetPrototype(prototype)
|
80
|
+
@augmented = true
|
81
|
+
end
|
82
|
+
@exposed = exposed
|
83
|
+
end
|
84
|
+
|
85
|
+
def ruby_class
|
86
|
+
ObjectSpace._id2ref(@class_id)
|
87
|
+
end
|
88
|
+
|
89
|
+
def proxies
|
90
|
+
@templates.proxies
|
91
|
+
end
|
92
|
+
|
93
|
+
def portal
|
94
|
+
@templates.portal
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module V8
|
2
|
+
class Portal
|
3
|
+
class FunctionAdapter
|
4
|
+
|
5
|
+
attr_reader :template, :function
|
6
|
+
|
7
|
+
def initialize(portal, code)
|
8
|
+
@portal = portal
|
9
|
+
@caller = code.respond_to?(:call) ? Call.new(portal) : BindAndCall.new(portal)
|
10
|
+
@code = code
|
11
|
+
@template = V8::C::FunctionTemplate::New(@caller, @code)
|
12
|
+
end
|
13
|
+
|
14
|
+
def function
|
15
|
+
@template.GetFunction()
|
16
|
+
end
|
17
|
+
|
18
|
+
class Call
|
19
|
+
def initialize(portal)
|
20
|
+
@portal = portal
|
21
|
+
end
|
22
|
+
|
23
|
+
def call(arguments)
|
24
|
+
proc = arguments.Data()
|
25
|
+
rbargs = []
|
26
|
+
for i in 0..arguments.Length() - 1
|
27
|
+
rbargs << @portal.rb(arguments[i])
|
28
|
+
end
|
29
|
+
@portal.caller.invoke(proc, *rbargs)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class BindAndCall < Call
|
34
|
+
def call(arguments)
|
35
|
+
method = arguments.Data()
|
36
|
+
rbargs = []
|
37
|
+
for i in 0..arguments.Length() - 1
|
38
|
+
rbargs << @portal.rb(arguments[i])
|
39
|
+
end
|
40
|
+
this = @portal.rb(arguments.This())
|
41
|
+
@portal.caller.protect do
|
42
|
+
method.bind(this).call(*rbargs)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
module V8
|
2
|
+
class Portal
|
3
|
+
class Interceptors
|
4
|
+
def initialize(portal)
|
5
|
+
@portal
|
6
|
+
@getter = NamedPropertyGetter.new(portal)
|
7
|
+
@setter = NamedPropertySetter.new(portal)
|
8
|
+
@query = nil
|
9
|
+
@deleter = nil
|
10
|
+
@enumerator = NamedPropertyEnumerator.new(portal)
|
11
|
+
@igetter = IndexedPropertyGetter.new(portal)
|
12
|
+
@isetter = IndexedPropertySetter.new(portal)
|
13
|
+
@iquery = nil
|
14
|
+
@ideleter = nil
|
15
|
+
@ienumerator = IndexedPropertyEnumerator.new(portal)
|
16
|
+
end
|
17
|
+
|
18
|
+
def setup(template)
|
19
|
+
template.SetNamedPropertyHandler(@getter,@setter,@query,@deleter,@enumerator, nil)
|
20
|
+
template.SetIndexedPropertyHandler(@igetter,@isetter,@iquery,@ideleter,@ienumerator, nil)
|
21
|
+
end
|
22
|
+
|
23
|
+
class PropertyAttributes
|
24
|
+
attr_reader :flags
|
25
|
+
def initialize
|
26
|
+
@flags = 0
|
27
|
+
end
|
28
|
+
|
29
|
+
def read_only
|
30
|
+
tap do
|
31
|
+
@flags |= V8::C::ReadOnly
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def dont_enum
|
36
|
+
tap do
|
37
|
+
@flags |= V8::C::DontEnum
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def dont_delete
|
42
|
+
tap do
|
43
|
+
@flags |= V8::C::DontDelete
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class Interceptor
|
49
|
+
|
50
|
+
def initialize(portal)
|
51
|
+
@to, @access = portal, portal.access
|
52
|
+
end
|
53
|
+
|
54
|
+
def intercept(info, retval = nil, &code)
|
55
|
+
obj = @to.rb(info.This())
|
56
|
+
intercepts = true
|
57
|
+
result = @to.caller.protect do
|
58
|
+
dontintercept = proc do
|
59
|
+
intercepts = false
|
60
|
+
end
|
61
|
+
code.call(obj, dontintercept)
|
62
|
+
end
|
63
|
+
intercepts ? (retval || result) : C::Empty
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class NamedPropertyGetter < Interceptor
|
68
|
+
def call(property, info)
|
69
|
+
intercept(info) do |obj, dontintercept|
|
70
|
+
@access.get(obj, @to.rb(property), &dontintercept)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class NamedPropertySetter < Interceptor
|
76
|
+
def call(property, value, info)
|
77
|
+
intercept(info, value) do |obj, dontintercept|
|
78
|
+
@access.set(obj, @to.rb(property), @to.rb(value), &dontintercept)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class NamedPropertyQuery
|
84
|
+
def call(property, info)
|
85
|
+
attributes = PropertyAttributes.new
|
86
|
+
result = intercept(info) do |obj, dontintercept|
|
87
|
+
@access.query(obj, @to.rb(property), attributes, &dontintercept)
|
88
|
+
end
|
89
|
+
return result == C::Empty ? result : C::Integer::New(attributes.flags)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
class NamedPropertyEnumerator < Interceptor
|
94
|
+
def call(info)
|
95
|
+
intercept(info) do |obj, dontintercept|
|
96
|
+
@access.names(obj, &dontintercept).to_a
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
class NamedPropertyDeleter < Interceptor
|
102
|
+
def call(property, info)
|
103
|
+
intercept(info) do |obj, dontintercept|
|
104
|
+
@access.delete(obj, property, &dontintercept)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
class IndexedPropertyGetter < Interceptor
|
110
|
+
def call(index, info)
|
111
|
+
intercept(info) do |obj, dontintercept|
|
112
|
+
@access.iget(obj, index, &dontintercept)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
class IndexedPropertySetter < Interceptor
|
118
|
+
def call(index, value, info)
|
119
|
+
intercept(info, value) do |obj, dontintercept|
|
120
|
+
@access.iset(obj, index, @to.rb(value), &dontintercept)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
class IndexedPropertyQuery < Interceptor
|
126
|
+
def call(property, info)
|
127
|
+
attributes = PropertyAttributes.new
|
128
|
+
result = intercept(info) do |obj, dontintercept|
|
129
|
+
@access.indices(obj, &dontintercept)
|
130
|
+
end
|
131
|
+
result == C::Empty ? C::Empty : C::Integer::New(attributes.flags)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
class IndexedPropertyDeleter < Interceptor
|
136
|
+
def call(index, info)
|
137
|
+
intercept(info) do |obj, dontintercept|
|
138
|
+
@access.idelete(obj, index, &dontintercept)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
class IndexedPropertyEnumerator < Interceptor
|
144
|
+
def call(info)
|
145
|
+
intercept(info) do |obj, dontintercept|
|
146
|
+
@access.indices(obj, &dontintercept)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module V8
|
2
|
+
class Portal
|
3
|
+
class Proxies
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@js_proxies_rb2js = {}
|
7
|
+
@js_proxies_js2rb = {}
|
8
|
+
@rb_proxies_rb2js = {}
|
9
|
+
@rb_proxies_js2rb = {}
|
10
|
+
@clear_js_proxy = ClearJSProxy.new(@js_proxies_rb2js, @js_proxies_js2rb)
|
11
|
+
@clear_rb_proxy = ClearRubyProxy.new(@rb_proxies_rb2js, @rb_proxies_js2rb)
|
12
|
+
end
|
13
|
+
|
14
|
+
def js2rb(js)
|
15
|
+
if rb = js_proxy_2_rb_object(js)
|
16
|
+
return rb
|
17
|
+
elsif rb = js_object_2_rb_proxy(js)
|
18
|
+
return rb
|
19
|
+
else
|
20
|
+
proxy = block_given? ? yield(js) : Object.new
|
21
|
+
register_ruby_proxy proxy, :for => js if proxy && js && js.kind_of?(V8::C::Handle)
|
22
|
+
return proxy
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def rb2js(rb)
|
27
|
+
if js = rb_proxy_2_js_object(rb)
|
28
|
+
return js
|
29
|
+
elsif js = rb_object_2_js_proxy(rb)
|
30
|
+
return js
|
31
|
+
else
|
32
|
+
proxy = block_given? ? yield(rb) : V8::C::Object::New()
|
33
|
+
register_javascript_proxy proxy, :for => rb unless @js_proxies_rb2js[rb]
|
34
|
+
return proxy
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def register_javascript_proxy(proxy, options = {})
|
39
|
+
target = options[:for] or fail ArgumentError, "must specify the object that you're proxying with the :for => param"
|
40
|
+
fail ArgumentError, "javascript proxy must be a V8::C::Handle, not #{proxy.class}" unless proxy.kind_of?(V8::C::Handle)
|
41
|
+
fail DoubleProxyError, "target already has a registered proxy" if @js_proxies_rb2js[target]
|
42
|
+
|
43
|
+
@js_proxies_js2rb[proxy] = target
|
44
|
+
@js_proxies_rb2js[target] = proxy
|
45
|
+
proxy.MakeWeak(nil, @clear_js_proxy)
|
46
|
+
end
|
47
|
+
|
48
|
+
def rb_object_2_js_proxy(object)
|
49
|
+
@js_proxies_rb2js[object]
|
50
|
+
end
|
51
|
+
|
52
|
+
def js_proxy_2_rb_object(proxy)
|
53
|
+
@js_proxies_js2rb[proxy]
|
54
|
+
end
|
55
|
+
|
56
|
+
def register_ruby_proxy(proxy, options = {})
|
57
|
+
target = options[:for] or fail ArgumentError, "must specify the object that you're proxying with the :for => param"
|
58
|
+
fail ArgumentError, "'#{proxy.inspect}' is not a Handle to an actual V8 object" unless target.kind_of?(V8::C::Handle)
|
59
|
+
@rb_proxies_rb2js[proxy.object_id] = target
|
60
|
+
@rb_proxies_js2rb[target] = proxy.object_id
|
61
|
+
ObjectSpace.define_finalizer(proxy, @clear_rb_proxy)
|
62
|
+
end
|
63
|
+
|
64
|
+
def js_object_2_rb_proxy(object)
|
65
|
+
if id = @rb_proxies_js2rb[object]
|
66
|
+
ObjectSpace._id2ref id
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def rb_proxy_2_js_object(proxy)
|
71
|
+
@rb_proxies_rb2js[proxy.object_id]
|
72
|
+
end
|
73
|
+
|
74
|
+
DoubleProxyError = Class.new(StandardError)
|
75
|
+
|
76
|
+
class ClearJSProxy
|
77
|
+
|
78
|
+
def initialize(rb2js, js2rb)
|
79
|
+
@rb2js, @js2rb = rb2js, js2rb
|
80
|
+
end
|
81
|
+
|
82
|
+
def call(proxy, parameter)
|
83
|
+
rb = @js2rb[proxy]
|
84
|
+
@js2rb.delete(proxy)
|
85
|
+
@rb2js.delete(rb)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class ClearRubyProxy
|
90
|
+
def initialize(rb2js, js2rb)
|
91
|
+
@rb2js, @js2rb = rb2js, js2rb
|
92
|
+
end
|
93
|
+
|
94
|
+
def call(proxy_id)
|
95
|
+
js = @rb2js[proxy_id]
|
96
|
+
@rb2js.delete(proxy_id)
|
97
|
+
@js2rb.delete(js)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
|
2
|
+
module V8
|
3
|
+
class Portal
|
4
|
+
class Templates
|
5
|
+
|
6
|
+
attr_reader :portal
|
7
|
+
|
8
|
+
def initialize(portal)
|
9
|
+
@portal = portal
|
10
|
+
@constructors = {}
|
11
|
+
@methods = {}
|
12
|
+
@procs = {}
|
13
|
+
@releases = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_constructor(ruby_class)
|
17
|
+
class_id = ruby_class.object_id
|
18
|
+
if constructor = @constructors[class_id]
|
19
|
+
return constructor
|
20
|
+
else
|
21
|
+
constructor = @constructors[class_id] = ConstructorAdapter.new(self, class_id)
|
22
|
+
ObjectSpace.define_finalizer(ruby_class, release(@constructors, class_id))
|
23
|
+
return constructor
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_function(code)
|
28
|
+
case code
|
29
|
+
when Method, UnboundMethod
|
30
|
+
if fn = @methods[code.to_s]
|
31
|
+
return fn
|
32
|
+
else
|
33
|
+
function = @methods[code.to_s] = FunctionAdapter.new(@portal, code)
|
34
|
+
#TODO: test this weak behavior
|
35
|
+
function.template.MakeWeak(0, release(@methods, code.to_s))
|
36
|
+
return function
|
37
|
+
end
|
38
|
+
else
|
39
|
+
if fn = @procs[code]
|
40
|
+
return fn
|
41
|
+
else
|
42
|
+
function = @procs[code] = FunctionAdapter.new(@portal, code)
|
43
|
+
#TODO: test this weak behavior
|
44
|
+
function.template.MakeWeak(0, release(@procs, code))
|
45
|
+
return function
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def proxies
|
51
|
+
@portal.proxies
|
52
|
+
end
|
53
|
+
|
54
|
+
def release(refs, id)
|
55
|
+
release = Release.new(@releases, refs, id)
|
56
|
+
@releases[release] = true
|
57
|
+
return release
|
58
|
+
end
|
59
|
+
|
60
|
+
class Release
|
61
|
+
def initialize(releases, refs, id)
|
62
|
+
@releases, @refs, @id = releases, refs, id
|
63
|
+
end
|
64
|
+
|
65
|
+
def call(*args)
|
66
|
+
@refs.delete(@id)
|
67
|
+
@releases.delete(self)
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|