isorun 0.1.5.pre-x86_64-linux → 0.1.7-x86_64-linux

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 425cb5a36c48c15c8d7e05e336cefb0c12921a64ff52f1bec061270386d0bae8
4
- data.tar.gz: 4847244f7463591220b11506e11120236ad3c81f3ab7ac2b4acab4fe82063d82
3
+ metadata.gz: 8a4aeda3a39740cabd5ed2a62ca53b3b9908d518990165ddbe1271c97cd09381
4
+ data.tar.gz: 1eb2373c0cc3b4287becc08c2b304d7ad9874856ec8ec14a8212be2f764a5dc0
5
5
  SHA512:
6
- metadata.gz: 3642a8e54820ce5eca25721d5d22b4a78cf7d9df530006d5bb38c4651df26ba001375472568bb9ae49f0df543705e5c09f8f6124eb5eaf71432929ea5a3b0214
7
- data.tar.gz: 476317987740b058717dcf5041e1c0aec793366403658d161e424612029257b5e50a1fb64fe07ddabfccddf3d8bb50f1bc5d93e99a3fa180b0275da466b10cb3
6
+ metadata.gz: b1425a976b568f920a1c90ad3b47b5007d4ef95a31a319ef9e8cc33fa4f9b27c3d2a76ae98a3d5f051943df62bfa0a3f91557e1fb5e7e0897b09b74d75369f91
7
+ data.tar.gz: 421de15c0adc18b0777021be608cac34895bd1ce5c46ba6a233dc45181afba134b98a3a2155002662f2d736b36f80fafb7eedf99419b49f22c5547a6259a25a6
@@ -3,7 +3,7 @@
3
3
  module Isorun
4
4
  module AppHelper
5
5
  def isorun_app(id) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
6
- module_path = Isorun.configuration.module_resolver.call(id)
6
+ module_path = Isorun.config.module_resolver.call(id)
7
7
 
8
8
  ssr_html = Isorun::Context.create do |context|
9
9
  render_context = { environment: Rails.env.to_s }
@@ -14,11 +14,18 @@ module Isorun
14
14
  "does not have a server entrypoint. Please " \
15
15
  "check if an asset with filename " + "
16
16
  `#{id}-server.js` exists.")
17
+ return ""
17
18
  end
18
19
 
19
- Isorun.with_receiver(Isorun.configuration.receiver) do
20
- render_function.call_without_gvl(render_context)
21
- end
20
+ # set receiver to allow calling into Ruby from JavaScript
21
+ context.receiver = Isorun.config.receiver
22
+
23
+ html = render_function.call(render_context)
24
+
25
+ # reset receiver
26
+ context.receiver = nil
27
+
28
+ html
22
29
  end
23
30
 
24
31
  html = if ssr_html.present?
@@ -1,26 +1,26 @@
1
1
  [package]
2
2
  name = "isorun"
3
- version = "0.1.5"
3
+ version = "0.1.7"
4
4
  edition = "2021"
5
5
  authors = ["Hannes Moser <box@hannesmoser.at>"]
6
6
  homepage = "https://github.com/eliias/isorun"
7
7
  repository = "https://github.com/eliias/isorun"
8
8
 
9
9
  [dependencies]
10
- deno_console = "0.80.0"
11
- deno_core = "0.162.0"
12
- deno_fetch = "0.103.0"
13
- deno_runtime = "0.88.0"
14
- deno_url = "0.80.0"
15
- deno_web = "0.111.0"
16
- deno_webidl = "0.80.0"
10
+ deno_console = "0.82.0"
11
+ deno_core = "0.164.0"
12
+ deno_fetch = "0.106.0"
13
+ deno_runtime = "0.90.0"
14
+ deno_url = "0.82.0"
15
+ deno_web = "0.113.0"
16
+ deno_webidl = "0.82.0"
17
17
  lazy_static = "1.4.0"
18
- magnus = { git = "https://github.com/eliias/magnus", rev = "1833ad96919b8891c1b5d62a22f0fc2b94f5961a" } # waiting for release with full rb-sys backend
18
+ magnus = { git = "https://github.com/eliias/magnus", rev = "3289eeda7ea63831c0277b153666f1efab3866bb" }
19
19
  tokio = { version = "1.21.1", features = ["full"] }
20
- v8 = "0.58.0"
20
+ v8 = "0.60.0"
21
21
 
22
22
  [dev-dependencies]
23
- magnus = { git = "https://github.com/eliias/magnus", rev = "1833ad96919b8891c1b5d62a22f0fc2b94f5961a", features = ["embed"] } # waiting for release with full rb-sys backend
23
+ magnus = { git = "https://github.com/eliias/magnus", rev = "3289eeda7ea63831c0277b153666f1efab3866bb", features = ["embed"] }
24
24
 
25
25
  [lib]
26
26
  name = "isorun"
@@ -2,6 +2,7 @@ use crate::isorun;
2
2
  use crate::js::module::Module;
3
3
  use crate::js::worker::WORKER;
4
4
  use deno_core::JsRealm;
5
+ use magnus::block::Proc;
5
6
  use magnus::Error;
6
7
  use std::cell::RefCell;
7
8
  use std::rc::Rc;
@@ -22,6 +23,10 @@ impl Context {
22
23
  })
23
24
  }
24
25
 
26
+ pub(crate) fn set_receiver(&self, receiver: Option<Proc>) {
27
+ WORKER.with(|worker| worker.ruby_receiver.replace(receiver));
28
+ }
29
+
25
30
  pub(crate) fn load(
26
31
  &self,
27
32
  path: String,
@@ -3,6 +3,7 @@ use crate::js::worker::WORKER;
3
3
  use magnus::Error;
4
4
  use std::cell::RefCell;
5
5
  use std::ops::Deref;
6
+ use v8::{Global, Value};
6
7
 
7
8
  #[magnus::wrap(class = "Isorun::Function")]
8
9
  pub(crate) struct Function(pub(crate) RefCell<js::module_item::Function>);
@@ -15,46 +16,25 @@ impl Function {
15
16
  &self,
16
17
  args: &[magnus::Value],
17
18
  ) -> Result<magnus::Value, Error> {
18
- let args = WORKER.with(|worker| {
19
+ WORKER.with(|worker| {
19
20
  let func = self.0.borrow();
20
21
  let realm = func.realm.borrow();
21
22
  let realm = realm.deref();
22
23
 
23
- let mut v8_args = vec![];
24
- for arg in args {
25
- let v8_arg = worker.to_v8(realm, *arg).unwrap();
26
- v8_args.push(v8_arg);
27
- }
28
- v8_args
29
- });
24
+ let v8_args: Vec<Global<Value>> = args
25
+ .iter()
26
+ .map(|arg| worker.to_v8(realm, *arg).unwrap())
27
+ .collect();
30
28
 
31
- self.0.borrow().call(args.as_slice()).map_err(|error| {
32
- Error::runtime_error(format!("cannot call function: {}", error))
33
- })
34
- }
29
+ let result =
30
+ self.0.borrow().call(v8_args.as_slice()).map_err(|error| {
31
+ Error::runtime_error(format!(
32
+ "cannot call function: {}",
33
+ error
34
+ ))
35
+ });
35
36
 
36
- pub(crate) fn call_without_gvl(
37
- &self,
38
- args: &[magnus::Value],
39
- ) -> Result<magnus::Value, Error> {
40
- let args = WORKER.with(|worker| {
41
- let func = self.0.borrow();
42
- let realm = func.realm.borrow();
43
- let realm = realm.deref();
44
-
45
- let mut v8_args = vec![];
46
- for arg in args {
47
- let v8_arg = worker.to_v8(realm, *arg).unwrap();
48
- v8_args.push(v8_arg);
49
- }
50
- v8_args
51
- });
52
-
53
- self.0
54
- .borrow()
55
- .call_without_gvl(args.as_slice())
56
- .map_err(|error| {
57
- Error::runtime_error(format!("cannot call function: {}", error))
58
- })
37
+ result
38
+ })
59
39
  }
60
40
  }
@@ -1,4 +1,3 @@
1
- pub(crate) mod configure;
2
1
  pub(crate) mod context;
3
2
  pub(crate) mod function;
4
3
  pub(crate) mod module;
@@ -5,12 +5,13 @@ use magnus::{
5
5
  Integer, RArray, RFloat, RHash, RString, RStruct, Symbol, Value, QFALSE,
6
6
  QNIL, QTRUE,
7
7
  };
8
- use v8::{Array, GetPropertyNamesArgs, HandleScope, Local, Object};
8
+ use v8::{Array, GetPropertyNamesArgs, Global, HandleScope, Local, Object};
9
9
 
10
10
  pub fn convert_v8_to_ruby(
11
- value: Local<v8::Value>,
11
+ value: &Global<v8::Value>,
12
12
  scope: &mut HandleScope,
13
13
  ) -> Result<Value, AnyError> {
14
+ let value = Local::new(scope, value);
14
15
  if value.is_null() {
15
16
  return Ok(Value::from(QNIL));
16
17
  }
@@ -36,8 +37,7 @@ pub fn convert_v8_to_ruby(
36
37
  }
37
38
 
38
39
  if value.is_string() {
39
- let raw = value.to_rust_string_lossy(scope);
40
- return Ok(Value::from(RString::from(raw)));
40
+ return Ok(Value::from(value.to_rust_string_lossy(scope)));
41
41
  }
42
42
 
43
43
  if value.is_array() {
@@ -46,7 +46,8 @@ pub fn convert_v8_to_ruby(
46
46
  let r_arr = RArray::with_capacity(length as usize);
47
47
  for i in 0..length {
48
48
  let raw = arr.get_index(scope, i).unwrap();
49
- let val = convert_v8_to_ruby(raw, scope).unwrap();
49
+ let global_raw = Global::<v8::Value>::new(scope, raw);
50
+ let val = convert_v8_to_ruby(&global_raw, scope).unwrap();
50
51
  r_arr.push(val).expect("cannot add item to array");
51
52
  }
52
53
  return Ok(Value::from(r_arr));
@@ -61,9 +62,11 @@ pub fn convert_v8_to_ruby(
61
62
  let r_hash = RHash::new();
62
63
  for i in 0..length {
63
64
  let raw_key = properties.get_index(scope, i).unwrap();
65
+ let global_raw_key = Global::<v8::Value>::new(scope, raw_key);
64
66
  let raw_val = obj.get(scope, raw_key).unwrap();
65
- let key = convert_v8_to_ruby(raw_key, scope).unwrap();
66
- let val = convert_v8_to_ruby(raw_val, scope).unwrap();
67
+ let global_raw_val = Global::<v8::Value>::new(scope, raw_val);
68
+ let key = convert_v8_to_ruby(&global_raw_key, scope).unwrap();
69
+ let val = convert_v8_to_ruby(&global_raw_val, scope).unwrap();
67
70
  r_hash.aset(key, val).expect("cannot set item to hash");
68
71
  }
69
72
  return Ok(Value::from(r_hash));
@@ -1,6 +1,5 @@
1
1
  use crate::js::worker::WORKER;
2
2
  use deno_core::JsRealm;
3
- use magnus::gvl::without_gvl;
4
3
  use std::cell::RefCell;
5
4
  use std::ops::Deref;
6
5
  use std::rc::Rc;
@@ -31,30 +30,6 @@ impl Function {
31
30
  .block_on(worker.call(realm, &self.binding, args))
32
31
  })
33
32
  }
34
-
35
- pub(crate) fn call_without_gvl(
36
- &self,
37
- args: &[Global<v8::Value>],
38
- ) -> Result<magnus::Value, magnus::Error> {
39
- WORKER.with(|worker| {
40
- let realm = self.realm.borrow();
41
- let realm = realm.deref();
42
- let result = without_gvl(
43
- |gvl_context| {
44
- worker.ruby_context.replace(Some(gvl_context));
45
- let result = worker
46
- .runtime
47
- // we block here instead of the worker, due to a refcell issue
48
- // when borrowing within an await
49
- .block_on(worker.call(realm, &self.binding, args));
50
- worker.ruby_context.replace(None);
51
- result
52
- },
53
- None::<fn()>,
54
- );
55
- result.0.unwrap()
56
- })
57
- }
58
33
  }
59
34
 
60
35
  pub(crate) struct Value {
@@ -8,7 +8,7 @@ use deno_runtime::worker::{MainWorker, WorkerOptions};
8
8
  use deno_runtime::BootstrapOptions;
9
9
  use deno_web::BlobStore;
10
10
  use magnus::block::Proc;
11
- use magnus::gvl::GVLContext;
11
+ use magnus::gvl::{without_gvl, GVLContext};
12
12
  use magnus::{Error, Value};
13
13
  use std::borrow::BorrowMut;
14
14
  use std::cell::RefCell;
@@ -19,7 +19,7 @@ use std::rc::Rc;
19
19
  use std::string::ToString;
20
20
  use std::sync::Arc;
21
21
  use tokio::runtime::Runtime;
22
- use v8::{Global, Local};
22
+ use v8::{Global, HandleScope, Local};
23
23
 
24
24
  fn get_error_class_name(e: &AnyError) -> &'static str {
25
25
  deno_runtime::errors::get_error_class_name(e).unwrap_or("Error")
@@ -84,9 +84,23 @@ impl Worker {
84
84
  local_args.push(local_arg);
85
85
  }
86
86
  let receiver = v8::undefined(scope.borrow_mut());
87
- let promise = callee
88
- .call(&mut scope, receiver.into(), local_args.as_slice())
89
- .unwrap();
87
+ let (promise, _cancel) = without_gvl(
88
+ |ctx| {
89
+ self.ruby_context.replace(Some(ctx));
90
+ let result = callee
91
+ .call(
92
+ &mut scope,
93
+ receiver.into(),
94
+ local_args.as_slice(),
95
+ )
96
+ .unwrap();
97
+ self.ruby_context.replace(None);
98
+ result
99
+ },
100
+ None::<fn()>,
101
+ );
102
+
103
+ let promise = promise.unwrap();
90
104
  Global::<v8::Value>::new(&mut scope, promise)
91
105
  };
92
106
 
@@ -107,7 +121,6 @@ impl Worker {
107
121
  ) -> Option<Value> {
108
122
  let mut worker = self.worker.borrow_mut();
109
123
  let mut scope = realm.handle_scope(worker.js_runtime.v8_isolate());
110
- let value = Local::new(&mut scope, value);
111
124
  let result = convert_v8_to_ruby(value, &mut scope);
112
125
 
113
126
  match result {
@@ -129,20 +142,57 @@ impl Worker {
129
142
  Some(value)
130
143
  }
131
144
 
132
- fn send(&self, value: Value) -> Result<Value, Error> {
133
- // we need to deref the receiver as mut, as it is behind an Option
134
- if let (Some(ctx), Some(rec)) = (
145
+ fn send<'a>(
146
+ &self,
147
+ scope: &mut HandleScope,
148
+ value: serde_v8::Value<'a>,
149
+ ) -> Result<serde_v8::Value<'a>, AnyError> {
150
+ if let None = self.ruby_receiver.borrow_mut().as_mut() {
151
+ return Err(AnyError::msg(
152
+ "Cannot send to ruby. The ruby receiver is missing, please \
153
+ initialize and set one before calling into Ruby?",
154
+ ));
155
+ }
156
+
157
+ // we can send with and without gvl, if no ruby context is present, we
158
+ // assume that GVL is held
159
+ if let (None, Some(rec)) = (
135
160
  self.ruby_context.borrow_mut().as_mut(),
136
161
  self.ruby_receiver.borrow_mut().as_mut(),
137
162
  ) {
163
+ let mut scope = scope; // fixme: can't figure out how to forward the borrow
164
+ let value = value.v8_value;
165
+ let value = Global::<v8::Value>::new(&mut scope, value);
166
+ let value = convert_v8_to_ruby(&value, &mut scope).unwrap();
167
+ let args: (Value,) = (value,);
168
+ return rec
169
+ .call::<(Value,), Value>(args)
170
+ .map_err(|err| AnyError::msg(format!("{:?}", err)))
171
+ .and_then(|value| convert_ruby_to_v8(value, &mut scope))
172
+ .map(|value| from_v8(&mut scope, value).unwrap());
173
+ }
174
+
175
+ // we need to deref the receiver as mut, as it is behind an Option
176
+ // TODO: make sure all operations on Ruby data happen when GVL is held
177
+ if let Some(ctx) = self.ruby_context.borrow_mut().as_mut() {
178
+ let mut scope = scope; // fixme: can't figure out how to forward the borrow
138
179
  ctx.with_gvl(|| {
139
- let args: (Value,) = (value,);
140
- rec.call::<(Value,), Value>(args)
141
- })?
180
+ if let Some(rec) = self.ruby_receiver.borrow_mut().as_mut() {
181
+ let value = value.v8_value;
182
+ let value = Global::<v8::Value>::new(&mut scope, value);
183
+ let value = convert_v8_to_ruby(&value, &mut scope)?;
184
+ let args: (Value,) = (value,);
185
+ rec.call::<(Value,), Value>(args)
186
+ .map_err(|err| AnyError::msg(format!("{:?}", err)))
187
+ } else {
188
+ Err(AnyError::msg("cannot access ruby receiver"))
189
+ }
190
+ })
191
+ .map_err(|err| AnyError::msg(format!("{:?}", err)))?
192
+ .and_then(|value| convert_ruby_to_v8(value, &mut scope))
193
+ .map(|value| from_v8(&mut scope, value).unwrap())
142
194
  } else {
143
- Err(Error::runtime_error(
144
- "Cannot send to ruby. Is the ruby receiver and context initialized and set?",
145
- ))
195
+ Err(AnyError::msg("this should never happen"))
146
196
  }
147
197
  }
148
198
  }
@@ -200,19 +250,13 @@ impl Default for Worker {
200
250
  shared_array_buffer_store: None,
201
251
  compiled_wasm_module_store: None,
202
252
  stdio: Default::default(),
253
+ should_wait_for_inspector_session: false,
203
254
  };
204
255
 
205
256
  // todo: we don't use the main module at all, but it could be used as an
206
257
  // entry point for "eval" JavaScript.
207
- let default_path = Path::new(env!("CARGO_MANIFEST_DIR"))
208
- .join("../..")
209
- .canonicalize()
210
- .unwrap()
211
- .into_os_string()
212
- .into_string()
213
- .unwrap();
214
258
  let isorun_native_gem_path =
215
- env::var("ISORUN_NATIVE_GEM_PATH").unwrap_or(default_path.clone());
259
+ env::var("ISORUN_NATIVE_GEM_PATH").unwrap();
216
260
  let js_path = Path::new(isorun_native_gem_path.as_str())
217
261
  .join("ext/isorun/src/call.js");
218
262
 
@@ -261,17 +305,5 @@ fn op_send_to_ruby<'a>(
261
305
  scope: &mut v8::HandleScope,
262
306
  data: serde_v8::Value<'a>,
263
307
  ) -> Result<serde_v8::Value<'a>, AnyError> {
264
- let value = convert_v8_to_ruby(data.v8_value, scope)?;
265
-
266
- WORKER.with(|worker| {
267
- worker
268
- .send(value)
269
- .map(|v| {
270
- let v = convert_ruby_to_v8(v, scope).unwrap();
271
- from_v8(scope, v).unwrap()
272
- })
273
- .map_err(|error| {
274
- AnyError::msg(format!("failed to send to ruby: {}", error))
275
- })
276
- })
308
+ WORKER.with(|worker| worker.send(scope, data))
277
309
  }
@@ -1,5 +1,4 @@
1
1
  use crate::isorun::context::Context;
2
- use isorun::configure::set_receiver;
3
2
  use isorun::function::Function;
4
3
  use isorun::module::Module;
5
4
  use magnus::{define_module, function, method, Error, Module as M, Object};
@@ -11,9 +10,6 @@ mod js;
11
10
  fn init() -> Result<(), Error> {
12
11
  let root = define_module("Isorun").expect("cannot define module: Isorun");
13
12
 
14
- root.define_module_function("receiver=", function!(set_receiver, 1))
15
- .expect("cannot define module function: receiver=");
16
-
17
13
  let context = root
18
14
  .define_class("Context", Default::default())
19
15
  .expect("cannot define class: Isorun::Context");
@@ -22,7 +18,10 @@ fn init() -> Result<(), Error> {
22
18
  .expect("cannot define singelton method: new");
23
19
  context
24
20
  .define_method("load", method!(Context::load, 1))
25
- .expect("cannot load module");
21
+ .expect("cannot define method: load");
22
+ context
23
+ .define_method("receiver=", method!(Context::set_receiver, 1))
24
+ .expect("cannot define method: receiver=");
26
25
 
27
26
  let module = root
28
27
  .define_class("Module", Default::default())
@@ -40,12 +39,6 @@ fn init() -> Result<(), Error> {
40
39
  function
41
40
  .define_method("call", method!(Function::call, -1))
42
41
  .expect("cannot define method: call");
43
- function
44
- .define_method(
45
- "call_without_gvl",
46
- method!(Function::call_without_gvl, -1),
47
- )
48
- .expect("cannot define method: call_without_gvl");
49
42
 
50
43
  Ok(())
51
44
  }
Binary file
Binary file
Binary file
@@ -76,9 +76,9 @@ module Isorun
76
76
  export_names = [:default.to_s] if export_names.empty?
77
77
  Import.new(self, export_names)
78
78
  end
79
- end
80
79
 
81
- # @!method receiver=(receiver)
82
- # @param receiver [Proc]
83
- # @return [Isorun::Context] the newly created context
80
+ # @!method receiver=(receiver)
81
+ # @param receiver [Proc, nil]
82
+ # @return [Isorun::Context] the newly created context
83
+ end
84
84
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Isorun
4
- VERSION = "0.1.5.pre"
4
+ VERSION = "0.1.7"
5
5
  end
data/lib/isorun.rb CHANGED
@@ -6,7 +6,7 @@ ENV["ISORUN_NATIVE_GEM_PATH"] = File.expand_path("..", __dir__)
6
6
 
7
7
  # load native extension
8
8
  begin
9
- ruby_version = /(\d+\.\d+)/.match(::RUBY_VERSION)
9
+ ruby_version = /(\d+\.\d+)/.match(RUBY_VERSION)
10
10
  require "isorun/#{ruby_version}/isorun"
11
11
  rescue LoadError
12
12
  require "isorun/isorun"
@@ -23,11 +23,4 @@ module Isorun
23
23
  extend ActiveSupport::Autoload
24
24
 
25
25
  class Error < StandardError; end
26
-
27
- def self.with_receiver(receiver)
28
- self.receiver = receiver
29
- result = yield
30
- self.receiver = nil
31
- result
32
- end
33
26
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: isorun
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5.pre
4
+ version: 0.1.7
5
5
  platform: x86_64-linux
6
6
  authors:
7
7
  - Hannes Moser
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-12-10 00:00:00.000000000 Z
11
+ date: 2022-12-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -112,7 +112,6 @@ files:
112
112
  - ext/isorun/Cargo.toml
113
113
  - ext/isorun/extconf.rb
114
114
  - ext/isorun/src/call.js
115
- - ext/isorun/src/isorun/configure.rs
116
115
  - ext/isorun/src/isorun/context.rs
117
116
  - ext/isorun/src/isorun/function.rs
118
117
  - ext/isorun/src/isorun/mod.rs
@@ -161,9 +160,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
161
160
  version: 3.2.dev
162
161
  required_rubygems_version: !ruby/object:Gem::Requirement
163
162
  requirements:
164
- - - ">"
163
+ - - ">="
165
164
  - !ruby/object:Gem::Version
166
- version: 1.3.1
165
+ version: '0'
167
166
  requirements: []
168
167
  rubygems_version: 3.3.22
169
168
  signing_key:
@@ -1,9 +0,0 @@
1
- use crate::js::worker::WORKER;
2
- use magnus::block::Proc;
3
-
4
- pub(crate) fn set_receiver(receiver: Option<Proc>) {
5
- WORKER.with(|worker| match receiver {
6
- None => worker.ruby_receiver.replace(None),
7
- Some(r) => worker.ruby_receiver.replace(Some(r)),
8
- });
9
- }