File
Blob: src/rust/jsg/resource.rs
| 1 | // Copyright (c) 2026 Cloudflare, Inc. |
| 2 | // Licensed under the Apache 2.0 license found in the LICENSE file or at: |
| 3 | // https://opensource.org/licenses/Apache-2.0 |
| 4 | |
| 5 | use std::any::TypeId; |
| 6 | use std::cell::Cell; |
| 7 | use std::collections::HashMap; |
| 8 | use std::fmt; |
| 9 | use std::ops::Deref; |
| 10 | use std::ptr::NonNull; |
| 11 | |
| 12 | use kj_rs::KjMaybe; |
| 13 | |
| 14 | use crate::ConstantValue; |
| 15 | use crate::Error; |
| 16 | use crate::FromJS; |
| 17 | use crate::GarbageCollected; |
| 18 | use crate::Lock; |
| 19 | use crate::Member; |
| 20 | use crate::ToJS; |
| 21 | use crate::Traced; |
| 22 | use crate::Type; |
| 23 | use crate::v8; |
| 24 | use crate::v8::ffi::Wrappable; |
| 25 | |
| 26 | /// A reference-counted smart pointer to a Rust resource that integrates with |
| 27 | /// V8's garbage collector. |
| 28 | /// |
| 29 | /// Named `Rc` to mirror both [`std::rc::Rc`] (reference-counted shared ownership) |
| 30 | /// and C++ `jsg::Ref` (the GC-integrated reference type in workerd's C++ JSG layer). |
| 31 | /// Like `std::rc::Rc`, cloning is cheap (increments a refcount). Unlike `std::rc::Rc`, |
| 32 | /// the destructor also notifies the GC so JavaScript wrappers can be collected. |
| 33 | /// |
| 34 | /// Each `Rc<R>` holds: |
| 35 | /// - An `std::rc::Rc<R>` for Rust-side shared ownership (enables `Weak`) |
| 36 | /// - A [`v8::WrappableRc`] for `kj::Rc` refcounting and GC integration |
| 37 | /// |
| 38 | /// **GC integration:** |
| 39 | /// - On `Clone`, calls `wrappable_add_strong_ref` (C++ `addStrongRef`). |
| 40 | /// - On `Drop`, calls `wrappable_remove_strong_ref` with the ref's current |
| 41 | /// `strong` flag (C++ `maybeDeferDestruction`), then fields drop in |
| 42 | /// declaration order. |
| 43 | pub struct Rc<R: Resource> { |
| 44 | /// Shared ownership of the Rust resource. |
| 45 | handle: std::rc::Rc<R>, |
| 46 | /// Owned, reference-counted handle to the C++ Wrappable. |
| 47 | wrappable: v8::WrappableRc, |
| 48 | /// Per-Rc GC state: pointer to the parent `Wrappable`. |
| 49 | /// `None` until first visited. Set by `visitRef` during GC tracing. |
| 50 | parent: Cell<Option<NonNull<Wrappable>>>, |
| 51 | /// Per-Rc GC state: whether this ref is currently strong. |
| 52 | /// Starts `true` (addStrongRef was called). Toggled by `visitRef` during GC tracing. |
| 53 | strong: Cell<bool>, |
| 54 | } |
| 55 | |
| 56 | impl<R: Resource> Rc<R> { |
| 57 | /// Creates a new `Rc<R>` that ties a Rust resource's lifetime to the |
| 58 | /// JavaScript virtual machine's garbage collector. |
| 59 | /// |
| 60 | /// This is the primary way to create a Rust resource that can be exposed |
| 61 | /// to JavaScript. The returned `Rc` can then be converted to a JS object |
| 62 | /// via [`ToJS::to_js`](crate::ToJS::to_js). |
| 63 | /// |
| 64 | /// The resource stays alive as long as at least one `Rc` exists **or** a |
| 65 | /// JavaScript wrapper object is reachable from the JS heap. When all Rust |
| 66 | /// `Rc`s are dropped and the JS wrapper becomes unreachable, V8's garbage |
| 67 | /// collector will destroy the resource. |
| 68 | pub fn new(resource: R) -> Self { |
| 69 | let handle = std::rc::Rc::new(resource); |
| 70 | |
| 71 | // Leak an Rc clone as a fat pointer to dyn GarbageCollected and hand |
| 72 | // ownership to a new Wrappable on the KJ heap. Since R: GarbageCollected, |
| 73 | // the vtable dispatches trace/get_name directly to R's implementations. |
| 74 | let raw: *const R = std::rc::Rc::into_raw(std::rc::Rc::clone(&handle)); |
| 75 | let fat: *mut dyn GarbageCollected = raw as *const dyn GarbageCollected as *mut _; |
| 76 | let trait_object = v8::ffi::TraitObjectPtr::from_raw(fat, std::any::TypeId::of::<R>()); |
| 77 | let wrappable: v8::WrappableRc = trait_object.into(); |
| 78 | |
| 79 | Self { |
| 80 | handle, |
| 81 | wrappable, |
| 82 | parent: Cell::new(None), |
| 83 | strong: Cell::new(true), |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | /// Visits this Rc during GC tracing. |
| 88 | /// |
| 89 | /// Delegates to C++ `Wrappable::visitRef()` which handles strong/traced switching |
| 90 | /// and transitive tracing. |
| 91 | /// |
| 92 | /// Takes `&self` because `Traced::trace(&self)` receives a shared |
| 93 | /// reference to `R` inside the `Rc` allocation. The `parent` and `strong` |
| 94 | /// fields already use `Cell` for interior mutability. |
| 95 | /// `WrappableRc::visit_rc(&self)` produces `Pin<&mut Wrappable>` from |
| 96 | /// the raw pointer inside `KjRc` — the mutation target is the C++ heap |
| 97 | /// object, not the `WrappableRc` wrapper itself. |
| 98 | pub(crate) fn visit(&self, visitor: &mut v8::GcVisitor) { |
| 99 | self.wrappable.visit_rc( |
| 100 | self.parent.as_ptr().cast::<usize>(), |
| 101 | self.strong.as_ptr(), |
| 102 | visitor, |
| 103 | ); |
| 104 | } |
| 105 | |
| 106 | /// Creates a [`Weak`] reference to this resource. |
| 107 | /// |
| 108 | /// The weak reference does not prevent GC collection. Use |
| 109 | /// [`Weak::upgrade`] to obtain a strong `Rc<R>` if the resource is |
| 110 | /// still alive. |
| 111 | /// |
| 112 | /// Mirrors [`std::rc::Rc::downgrade`](std::rc::std::rc::Rc::downgrade). |
| 113 | pub fn downgrade(&self) -> Weak<R> { |
| 114 | Weak::from(self) |
| 115 | } |
| 116 | |
| 117 | /// Attaches this resource to the `this` object in a V8 constructor callback. |
| 118 | /// |
| 119 | /// Called from `#[jsg_constructor]`-generated code. V8 has already created |
| 120 | /// the `this` object from the `FunctionTemplate`'s `InstanceTemplate`; |
| 121 | /// this method attaches the `Wrappable` to it so that instance methods |
| 122 | /// can resolve the resource via `resolve_resource`. |
| 123 | pub fn attach_to_this(&self, info: &mut v8::FunctionCallbackInfo) { |
| 124 | self.wrappable.attach_to_this(info); |
| 125 | } |
| 126 | |
| 127 | /// Returns the C++ Wrappable's strong reference count. |
| 128 | #[cfg(debug_assertions)] |
| 129 | pub fn strong_refcount(&self) -> u32 { |
| 130 | self.wrappable.strong_refcount() |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | impl<R: Resource> Deref for Rc<R> { |
| 135 | type Target = R; |
| 136 | |
| 137 | #[inline] |
| 138 | fn deref(&self) -> &Self::Target { |
| 139 | &self.handle |
| 140 | } |
| 141 | } |
| 142 | |
| 143 | impl<R: Resource> Clone for Rc<R> { |
| 144 | fn clone(&self) -> Self { |
| 145 | let mut wrappable = self.wrappable.clone(); |
| 146 | wrappable.add_strong_ref(); |
| 147 | Self { |
| 148 | handle: std::rc::Rc::clone(&self.handle), |
| 149 | wrappable, |
| 150 | parent: Cell::new(None), |
| 151 | strong: Cell::new(true), |
| 152 | } |
| 153 | } |
| 154 | } |
| 155 | |
| 156 | impl<R: Resource> Drop for Rc<R> { |
| 157 | fn drop(&mut self) { |
| 158 | self.wrappable.remove_strong_ref(self.strong.get()); |
| 159 | } |
| 160 | } |
| 161 | |
| 162 | impl<R: Resource + 'static> ToJS for Rc<R> { |
| 163 | fn to_js<'a, 'b>(self, lock: &'a mut Lock) -> v8::Local<'b, v8::Value> |
| 164 | where |
| 165 | 'b: 'a, |
| 166 | { |
| 167 | wrap(lock, self) |
| 168 | } |
| 169 | } |
| 170 | |
| 171 | impl<R: Resource + 'static> FromJS for Rc<R> { |
| 172 | type ResultType = Self; |
| 173 | |
| 174 | fn from_js(lock: &mut Lock, value: v8::Local<v8::Value>) -> Result<Self, Error> { |
| 175 | // Capture the JS type name before consuming the value, for error messages. |
| 176 | let type_name = value.type_of(); |
| 177 | |
| 178 | let mut wrappable = v8::WrappableRc::from_js(lock.isolate(), value).ok_or_else(|| { |
| 179 | Error::new_type_error(format!("expected {}, got {type_name}", R::class_name())) |
| 180 | })?; |
| 181 | |
| 182 | let resource_ptr = wrappable.resolve_resource::<R>().ok_or_else(|| { |
| 183 | Error::new_type_error(format!("expected {}, got {type_name}", R::class_name())) |
| 184 | })?; |
| 185 | |
| 186 | // SAFETY: The pointer came from std::rc::Rc::into_raw in Rc::new(), and the |
| 187 | // Wrappable being alive guarantees the Rc allocation is still valid. |
| 188 | // increment_strong_count bumps the count so from_raw can safely create |
| 189 | // a second Rc handle without double-freeing. |
| 190 | let handle = unsafe { |
| 191 | std::rc::Rc::increment_strong_count(resource_ptr.as_ptr()); |
| 192 | std::rc::Rc::from_raw(resource_ptr.as_ptr()) |
| 193 | }; |
| 194 | |
| 195 | wrappable.add_strong_ref(); |
| 196 | Ok(Self { |
| 197 | handle, |
| 198 | wrappable, |
| 199 | parent: Cell::new(None), |
| 200 | strong: Cell::new(true), |
| 201 | }) |
| 202 | } |
| 203 | } |
| 204 | |
| 205 | /// Equality by pointer identity — two `Rc`s are equal iff they point to the |
| 206 | /// same allocation, matching the behaviour of [`std::rc::Rc`]. |
| 207 | impl<R: Resource> PartialEq for Rc<R> { |
| 208 | fn eq(&self, other: &Self) -> bool { |
| 209 | std::rc::Rc::ptr_eq(&self.handle, &other.handle) |
| 210 | } |
| 211 | } |
| 212 | |
| 213 | impl<R: Resource> Eq for Rc<R> {} |
| 214 | |
| 215 | /// Ordering by pointer address — consistent with `PartialEq` and allows |
| 216 | /// `Rc<T>` to be used as a `BTreeSet`/`BTreeMap` key. |
| 217 | impl<R: Resource> PartialOrd for Rc<R> { |
| 218 | fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { |
| 219 | Some(self.cmp(other)) |
| 220 | } |
| 221 | } |
| 222 | |
| 223 | impl<R: Resource> Ord for Rc<R> { |
| 224 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { |
| 225 | std::rc::Rc::as_ptr(&self.handle).cmp(&std::rc::Rc::as_ptr(&other.handle)) |
| 226 | } |
| 227 | } |
| 228 | |
| 229 | /// Hashing by pointer address — consistent with `PartialEq` and allows |
| 230 | /// `Rc<T>` to be used as a `HashSet`/`HashMap` key. |
| 231 | impl<R: Resource> std::hash::Hash for Rc<R> { |
| 232 | fn hash<H: std::hash::Hasher>(&self, state: &mut H) { |
| 233 | std::rc::Rc::as_ptr(&self.handle).hash(state); |
| 234 | } |
| 235 | } |
| 236 | |
| 237 | impl<R: Resource> fmt::Debug for Rc<R> { |
| 238 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 239 | f.debug_struct("Rc") |
| 240 | .field("type", &std::any::type_name::<R>()) |
| 241 | .field("rc_strong", &std::rc::Rc::strong_count(&self.handle)) |
| 242 | .finish_non_exhaustive() |
| 243 | } |
| 244 | } |
| 245 | |
| 246 | /// A weak reference to a resource that doesn't prevent resource destruction. |
| 247 | /// |
| 248 | /// Uses `Weak<R>` for liveness detection: when all `Rc`s drop and |
| 249 | /// `~Wrappable` drops the `Rc` (via `std::rc::Rc::from_raw`), the `std::rc::Rc<R>` reaches |
| 250 | /// 0 and all `Weak`s expire. |
| 251 | /// |
| 252 | /// Does NOT hold a `WrappableRc` — does not keep the `kj::std::rc::Rc<Wrappable>` alive. |
| 253 | /// Stores a non-owning pointer to the Wrappable so that `upgrade()` can |
| 254 | /// reconstruct a `WrappableRc` via `wrappable_to_rc`. The pointer is only |
| 255 | /// dereferenced after `Weak::upgrade` confirms the resource (and thus the |
| 256 | /// Wrappable) is still alive. |
| 257 | pub struct Weak<R: Resource> { |
| 258 | weak: std::rc::Weak<R>, |
| 259 | /// Non-owning pointer to the C++ Wrappable. `None` for default-constructed |
| 260 | /// `Weak`s (which can never be upgraded). Valid as long as the resource |
| 261 | /// is alive (verified by `Weak::upgrade` before use). |
| 262 | wrappable: Option<NonNull<Wrappable>>, |
| 263 | } |
| 264 | |
| 265 | impl<R: Resource> Weak<R> { |
| 266 | /// Returns `true` if the resource is still alive. |
| 267 | #[inline] |
| 268 | pub fn is_alive(&self) -> bool { |
| 269 | self.weak.strong_count() > 0 |
| 270 | } |
| 271 | |
| 272 | /// Upgrades to a strong `Rc<R>` if the resource is still alive. |
| 273 | pub fn upgrade(&self) -> Option<Rc<R>> { |
| 274 | let handle = self.weak.upgrade()?; |
| 275 | let wrappable_ptr = self.wrappable?; |
| 276 | // Reconstruct a WrappableRc from the stored wrappable pointer. |
| 277 | // SAFETY: The resource is alive (Weak::upgrade succeeded), so the Wrappable |
| 278 | // is alive too (the Wrappable's Rc clone keeps the resource alive, which |
| 279 | // means the Wrappable must still exist — its destruction via std::rc::Rc::from_raw |
| 280 | // would have released the Rc and thus the resource). |
| 281 | let mut wrappable = unsafe { v8::WrappableRc::from_raw_wrappable(wrappable_ptr.as_ptr()) }; |
| 282 | wrappable.add_strong_ref(); |
| 283 | Some(Rc { |
| 284 | handle, |
| 285 | wrappable, |
| 286 | parent: Cell::new(None), |
| 287 | strong: Cell::new(true), |
| 288 | }) |
| 289 | } |
| 290 | } |
| 291 | |
| 292 | impl<R: Resource> From<&Rc<R>> for Weak<R> { |
| 293 | fn from(r: &Rc<R>) -> Self { |
| 294 | Self { |
| 295 | weak: std::rc::Rc::downgrade(&r.handle), |
| 296 | wrappable: Some(r.wrappable.as_ptr()), |
| 297 | } |
| 298 | } |
| 299 | } |
| 300 | |
| 301 | impl<R: Resource> Clone for Weak<R> { |
| 302 | fn clone(&self) -> Self { |
| 303 | Self { |
| 304 | weak: std::rc::Weak::clone(&self.weak), |
| 305 | wrappable: self.wrappable, |
| 306 | } |
| 307 | } |
| 308 | } |
| 309 | |
| 310 | /// `Rc<R>` is a strong GC edge — visited via `GcVisitor::visit_rc`. |
| 311 | impl<R: Resource> Traced for Rc<R> { |
| 312 | fn trace(&self, visitor: &mut v8::GcVisitor) { |
| 313 | visitor.visit_rc(self); |
| 314 | } |
| 315 | } |
| 316 | |
| 317 | /// Weak references don't keep the target alive and have no GC edges to trace. |
| 318 | impl<R: Resource> Traced for Weak<R> {} |
| 319 | |
| 320 | impl<R: Resource> GarbageCollected for Weak<R> { |
| 321 | fn memory_name(&self) -> &'static std::ffi::CStr { |
| 322 | // jsgGetMemoryName is only called on live Wrappables, never on Weak<R>. |
| 323 | // Delegate to the concrete R via a live upgrade. In the (unreachable) |
| 324 | // case where the referent is already gone, return an empty string. |
| 325 | self.weak.upgrade().as_deref().map_or(c"", R::memory_name) |
| 326 | } |
| 327 | } |
| 328 | |
| 329 | impl<R: Resource> Default for Weak<R> { |
| 330 | fn default() -> Self { |
| 331 | Self { |
| 332 | weak: std::rc::Weak::new(), |
| 333 | wrappable: None, |
| 334 | } |
| 335 | } |
| 336 | } |
| 337 | |
| 338 | /// Wraps a `Rc<R>` as a JavaScript object backed by the resource's `FunctionTemplate`. |
| 339 | /// |
| 340 | /// **Identity**: Multiple calls with `Rc`s that share the same underlying `Wrappable` |
| 341 | /// (i.e. clones of the same `Rc`) return the **same** JS object — V8 caches the |
| 342 | /// wrapper on the `Wrappable` via `CppgcShim`. This means: |
| 343 | /// |
| 344 | /// ```text |
| 345 | /// let a = resource.clone().to_js(lock); |
| 346 | /// let b = resource.to_js(lock); |
| 347 | /// // a === b in JavaScript (same object identity) |
| 348 | /// ``` |
| 349 | /// |
| 350 | /// **Prototype**: The returned object's prototype is set up from the resource's |
| 351 | /// [`FunctionTemplate`], which includes all `#[jsg_method]` instance methods and |
| 352 | /// `#[jsg_static_constant]` values registered via [`Resource::members()`]. |
| 353 | /// |
| 354 | /// **Ownership**: The `Rc` is consumed. The JS wrapper keeps the `Wrappable` |
| 355 | /// alive via `CppgcShim`. When the JS object is garbage collected and all Rust |
| 356 | /// `Rc`s are dropped, the resource is destroyed. |
| 357 | #[expect(clippy::needless_pass_by_value)] |
| 358 | pub fn wrap<'a, R: Resource + 'static>( |
| 359 | lock: &mut Lock, |
| 360 | resource: Rc<R>, |
| 361 | ) -> v8::Local<'a, v8::Value> { |
| 362 | let isolate = lock.isolate(); |
| 363 | let constructor = lock.realm().resources.get_constructor::<R>(isolate); |
| 364 | |
| 365 | resource.wrappable.to_js(isolate, constructor) |
| 366 | } |
| 367 | |
| 368 | /// Returns the constructor function for a resource type as a `Local<Function>`. |
| 369 | /// |
| 370 | /// Lazily creates and caches the `FunctionTemplate` in the `Realm` on first call. |
| 371 | /// The returned function can be exposed as a global (e.g., `ctx.set_global("MyClass", func)`). |
| 372 | pub fn function_template_of<'a, R: Resource + 'static>( |
| 373 | lock: &mut Lock, |
| 374 | ) -> v8::Local<'a, v8::Function> { |
| 375 | let isolate = lock.isolate(); |
| 376 | let constructor = lock.realm().resources.get_constructor::<R>(isolate); |
| 377 | // SAFETY: `isolate` is valid and locked (Lock invariant). `constructor` is a valid |
| 378 | // Global<FunctionTemplate> cached in the Realm. Inlined from `as_local_function` to |
| 379 | // avoid re-borrowing `self` while `constructor` holds an immutable borrow through `realm()`. |
| 380 | unsafe { |
| 381 | v8::Local::from_ffi( |
| 382 | isolate, |
| 383 | v8::ffi::function_template_get_function(isolate.as_ffi(), constructor.as_ffi_ref()), |
| 384 | ) |
| 385 | } |
| 386 | } |
| 387 | |
| 388 | /// Builds a [`ResourceDescriptor`] from `R`'s [`Resource::members()`] list. |
| 389 | /// The descriptor is passed to C++ `create_resource_template` to set up the |
| 390 | /// V8 `FunctionTemplate` with the correct methods, constants, and constructor. |
| 391 | fn get_resource_descriptor<R: Resource>() -> v8::ffi::ResourceDescriptor { |
| 392 | let mut descriptor = v8::ffi::ResourceDescriptor { |
| 393 | name: R::class_name().to_owned(), |
| 394 | constructor: KjMaybe::None, |
| 395 | methods: Vec::new(), |
| 396 | properties: Vec::new(), |
| 397 | static_methods: Vec::new(), |
| 398 | static_constants: Vec::new(), |
| 399 | }; |
| 400 | |
| 401 | for m in R::members() { |
| 402 | match m { |
| 403 | Member::Constructor { callback } => { |
| 404 | descriptor.constructor = KjMaybe::Some(v8::ffi::ConstructorDescriptor { |
| 405 | callback: callback as usize, |
| 406 | }); |
| 407 | } |
| 408 | Member::Method { name, callback } => { |
| 409 | descriptor.methods.push(v8::ffi::MethodDescriptor { |
| 410 | name, |
| 411 | callback: callback as usize, |
| 412 | }); |
| 413 | } |
| 414 | Member::Property { |
| 415 | name, |
| 416 | kind, |
| 417 | getter_callback, |
| 418 | setter_callback, |
| 419 | } => { |
| 420 | descriptor.properties.push(v8::ffi::PropertyDescriptor { |
| 421 | name, |
| 422 | kind, |
| 423 | getter_callback: getter_callback as usize, |
| 424 | setter_callback: setter_callback.map(|f| f as usize).into(), |
| 425 | }); |
| 426 | } |
| 427 | Member::StaticMethod { name, callback } => { |
| 428 | descriptor.static_methods.push(v8::ffi::MethodDescriptor { |
| 429 | name, |
| 430 | callback: callback as usize, |
| 431 | }); |
| 432 | } |
| 433 | Member::StaticConstant { name, value } => { |
| 434 | let ConstantValue::Number(number_value) = value; |
| 435 | descriptor |
| 436 | .static_constants |
| 437 | .push(v8::ffi::StaticConstantDescriptor { |
| 438 | name, |
| 439 | value: number_value, |
| 440 | }); |
| 441 | } |
| 442 | } |
| 443 | } |
| 444 | |
| 445 | descriptor |
| 446 | } |
| 447 | |
| 448 | #[derive(Default)] |
| 449 | pub struct Resources { |
| 450 | /// Cached V8 `FunctionTemplate`s keyed by resource `TypeId`. |
| 451 | templates: HashMap<TypeId, v8::Global<v8::FunctionTemplate>>, |
| 452 | } |
| 453 | |
| 454 | impl Resources { |
| 455 | /// Gets or creates the cached `FunctionTemplate` for a resource type. |
| 456 | pub fn get_constructor<R: Resource + 'static>( |
| 457 | &mut self, |
| 458 | isolate: v8::IsolatePtr, |
| 459 | ) -> &v8::Global<v8::FunctionTemplate> { |
| 460 | self.templates |
| 461 | .entry(TypeId::of::<R>()) |
| 462 | .or_insert_with(|| Self::create_resource_constructor::<R>(isolate)) |
| 463 | } |
| 464 | |
| 465 | /// Creates a new V8 `FunctionTemplate` for resource type `R` via the C++ FFI. |
| 466 | fn create_resource_constructor<R: Resource>( |
| 467 | isolate: v8::IsolatePtr, |
| 468 | ) -> v8::Global<v8::FunctionTemplate> { |
| 469 | // SAFETY: Caller guarantees the isolate is valid and locked. |
| 470 | unsafe { |
| 471 | v8::ffi::create_resource_template(isolate.as_ffi(), &get_resource_descriptor::<R>()) |
| 472 | .into() |
| 473 | } |
| 474 | } |
| 475 | } |
| 476 | |
| 477 | /// Rust types exposed to JavaScript as resource types. |
| 478 | /// |
| 479 | /// Resource types are passed by reference and call back into Rust when JavaScript accesses |
| 480 | /// their members. This is analogous to `JSG_RESOURCE_TYPE` in C++ JSG. Resources must provide |
| 481 | /// member declarations, a cleanup function for GC, and access to their V8 wrapper state. |
| 482 | pub trait Resource: Type + GarbageCollected + Sized + 'static { |
| 483 | /// Returns the list of methods, properties, and constructors exposed to JavaScript. |
| 484 | fn members() -> Vec<Member> |
| 485 | where |
| 486 | Self: Sized; |
| 487 | } |