From 72c67efe9631153a015af4a8d5b01bfbaa92068d Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Sun, 18 Oct 2015 13:53:12 +0200 Subject: [PATCH] Introduce trait WeakReferenceable This allows to take weak references of JS-managed DOM objects. --- .../script/dom/bindings/codegen/Bindings.conf | 4 + .../dom/bindings/codegen/CodegenRust.py | 46 ++++- .../dom/bindings/codegen/Configuration.py | 1 + components/script/dom/bindings/conversions.rs | 3 +- components/script/dom/bindings/mod.rs | 1 + components/script/dom/bindings/weakref.rs | 175 ++++++++++++++++++ components/script/dom/testbinding.rs | 10 + .../script/dom/webidls/TestBinding.webidl | 1 + tests/wpt/mozilla/meta/MANIFEST.json | 4 + tests/wpt/mozilla/tests/mozilla/weakref.html | 28 +++ 10 files changed, 268 insertions(+), 5 deletions(-) create mode 100644 components/script/dom/bindings/weakref.rs create mode 100644 tests/wpt/mozilla/tests/mozilla/weakref.html diff --git a/components/script/dom/bindings/codegen/Bindings.conf b/components/script/dom/bindings/codegen/Bindings.conf index 48b6067d00c..00ccea7fb63 100644 --- a/components/script/dom/bindings/codegen/Bindings.conf +++ b/components/script/dom/bindings/codegen/Bindings.conf @@ -21,4 +21,8 @@ DOMInterfaces = { #FIXME(jdm): This should be 'register': False, but then we don't generate enum types 'TestBinding': {}, +'URL': { + 'weakReferenceable': True, +} + } diff --git a/components/script/dom/bindings/codegen/CodegenRust.py b/components/script/dom/bindings/codegen/CodegenRust.py index 260ef1e9006..d9894dad386 100644 --- a/components/script/dom/bindings/codegen/CodegenRust.py +++ b/components/script/dom/bindings/codegen/CodegenRust.py @@ -1765,12 +1765,16 @@ class CGDOMJSClass(CGThing): def define(self): traceHook = 'Some(%s)' % TRACE_HOOK_NAME if self.descriptor.isGlobal(): + assert not self.descriptor.weakReferenceable traceHook = "Some(js::jsapi::JS_GlobalObjectTraceHook)" flags = "JSCLASS_IS_GLOBAL | JSCLASS_DOM_GLOBAL" slots = "JSCLASS_GLOBAL_SLOT_COUNT + 1" else: flags = "0" - slots = "1" + if self.descriptor.weakReferenceable: + slots = "2" + else: + slots = "1" return """\ static Class: DOMJSClass = DOMJSClass { base: js::jsapi::Class { @@ -2175,6 +2179,9 @@ let obj = RootedObject::new(cx, obj);\ "\n" "JS_SetReservedSlot(obj.ptr, DOM_OBJECT_SLOT,\n" " PrivateValue(raw as *const libc::c_void));") + if descriptor.weakReferenceable: + create += """ +JS_SetReservedSlot(obj.ptr, DOM_WEAK_SLOT, PrivateValue(ptr::null()));""" return create @@ -4521,8 +4528,8 @@ class CGAbstractClassHook(CGAbstractExternMethod): args) def definition_body_prologue(self): - return CGGeneric("""\ -let this = private_from_object(obj) as *const %s; + return CGGeneric(""" +let this = native_from_object::<%s>(obj).unwrap(); """ % self.descriptor.concreteType) def definition_body(self): @@ -4541,6 +4548,24 @@ def finalizeHook(descriptor, hookName, context): release += """\ finalize_global(obj); """ + elif descriptor.weakReferenceable: + release += """\ +let weak_box_ptr = JS_GetReservedSlot(obj, DOM_WEAK_SLOT).to_private() as *mut WeakBox<%s>; +if !weak_box_ptr.is_null() { + let count = { + let weak_box = &*weak_box_ptr; + assert!(weak_box.value.get().is_some()); + assert!(weak_box.count.get() > 0); + weak_box.value.set(None); + let count = weak_box.count.get() - 1; + weak_box.count.set(count); + count + }; + if count == 0 { + mem::drop(Box::from_raw(weak_box_ptr)); + } +} +""" % descriptor.concreteType release += """\ let _ = Box::from_raw(this as *mut %s); debug!("%s finalize: {:p}", this);\ @@ -4718,6 +4743,16 @@ class CGInterfaceTrait(CGThing): return self.cgRoot.define() +class CGWeakReferenceableTrait(CGThing): + def __init__(self, descriptor): + CGThing.__init__(self) + assert descriptor.weakReferenceable + self.code = "impl WeakReferenceable for %s {}" % descriptor.interface.identifier.name + + def define(self): + return self.code + + class CGDescriptor(CGThing): def __init__(self, descriptor): CGThing.__init__(self) @@ -4826,6 +4861,8 @@ class CGDescriptor(CGThing): if not descriptor.interface.isCallback(): cgThings.append(CGIDLInterface(descriptor)) cgThings.append(CGInterfaceTrait(descriptor)) + if descriptor.weakReferenceable: + cgThings.append(CGWeakReferenceableTrait(descriptor)) cgThings = CGList(cgThings, "\n") # self.cgRoot = CGWrapper(CGNamespace(toBindingNamespace(descriptor.name), @@ -5215,7 +5252,7 @@ class CGBindingRoot(CGThing): 'dom::bindings::conversions::{ConversionBehavior, DOM_OBJECT_SLOT, IDLInterface}', 'dom::bindings::conversions::{FromJSValConvertible, StringificationBehavior}', 'dom::bindings::conversions::{ToJSValConvertible, jsid_to_str, native_from_handlevalue}', - 'dom::bindings::conversions::{private_from_object, root_from_object}', + 'dom::bindings::conversions::{native_from_object, private_from_object, root_from_object}', 'dom::bindings::conversions::{root_from_handleobject, root_from_handlevalue}', 'dom::bindings::codegen::{PrototypeList, RegisterBindings, UnionTypes}', 'dom::bindings::codegen::Bindings::*', @@ -5230,6 +5267,7 @@ class CGBindingRoot(CGThing): 'dom::bindings::str::ByteString', 'dom::bindings::str::USVString', 'dom::bindings::trace::RootedVec', + 'dom::bindings::weakref::{DOM_WEAK_SLOT, WeakBox, WeakReferenceable}', 'mem::heap_size_of_raw_self_and_children', 'libc', 'util::str::DOMString', diff --git a/components/script/dom/bindings/codegen/Configuration.py b/components/script/dom/bindings/codegen/Configuration.py index 715719aae60..4f59ff09f5b 100644 --- a/components/script/dom/bindings/codegen/Configuration.py +++ b/components/script/dom/bindings/codegen/Configuration.py @@ -168,6 +168,7 @@ class Descriptor(DescriptorProvider): self.register = desc.get('register', True) self.outerObjectHook = desc.get('outerObjectHook', 'None') self.proxy = False + self.weakReferenceable = desc.get('weakReferenceable', False) # If we're concrete, we need to crawl our ancestor interfaces and mark # them as having a concrete descendant. diff --git a/components/script/dom/bindings/conversions.rs b/components/script/dom/bindings/conversions.rs index 493ed4c0c27..7244681ae24 100644 --- a/components/script/dom/bindings/conversions.rs +++ b/components/script/dom/bindings/conversions.rs @@ -729,7 +729,8 @@ pub unsafe fn private_from_proto_check(mut obj: *mut JSObject, proto_check: F } } -fn native_from_object(obj: *mut JSObject) -> Result<*const T, ()> +/// Get a `*const T` for a DOM object accessible from a `JSObject`. +pub fn native_from_object(obj: *mut JSObject) -> Result<*const T, ()> where T: Reflectable + IDLInterface { unsafe { diff --git a/components/script/dom/bindings/mod.rs b/components/script/dom/bindings/mod.rs index 410f7af9dda..df5f37ad07f 100644 --- a/components/script/dom/bindings/mod.rs +++ b/components/script/dom/bindings/mod.rs @@ -147,6 +147,7 @@ pub mod str; pub mod structuredclone; pub mod trace; pub mod utils; +pub mod weakref; pub mod xmlname; /// Generated JS-Rust bindings. diff --git a/components/script/dom/bindings/weakref.rs b/components/script/dom/bindings/weakref.rs new file mode 100644 index 00000000000..cf57b59a660 --- /dev/null +++ b/components/script/dom/bindings/weakref.rs @@ -0,0 +1,175 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Weak-referenceable JS-managed DOM objects. +//! +//! IDL interfaces marked as `weakReferenceable` in `Bindings.conf` +//! automatically implement the `WeakReferenceable` trait in codegen. +//! The instance object is responsible for setting `None` in its own +//! own `WeakBox` when it is collected, through the `DOM_WEAK_SLOT` +//! slot. When all associated `WeakRef` values are dropped, the +//! `WeakBox` itself is dropped too. + +use core::nonzero::NonZero; +use dom::bindings::js::Root; +use dom::bindings::reflector::Reflectable; +use dom::bindings::trace::JSTraceable; +use js::jsapi::{JSTracer, JS_GetReservedSlot, JS_SetReservedSlot}; +use js::jsval::PrivateValue; +use libc::c_void; +use std::cell::{Cell, UnsafeCell}; +use std::mem; +use util::mem::HeapSizeOf; + +/// The index of the slot wherein a pointer to the weak holder cell is +/// stored for weak-referenceable bindings. We use slot 1 for holding it, +/// this is unsafe for globals, we disallow weak-referenceable globals +/// directly in codegen. +pub const DOM_WEAK_SLOT: u32 = 1; + +/// A weak reference to a JS-managed DOM object. +#[allow_unrooted_interior] +pub struct WeakRef { + ptr: NonZero<*mut WeakBox>, +} + +/// The inner box of weak references, public for the finalization in codegen. +#[must_root] +pub struct WeakBox { + /// The reference count. When it reaches zero, the `value` field should + /// have already been set to `None`. The pointee contributes one to the count. + pub count: Cell, + /// The pointer to the JS-managed object, set to None when it is collected. + pub value: Cell>>, +} + +/// Trait implemented by weak-referenceable interfaces. +pub trait WeakReferenceable: Reflectable + Sized { + /// Downgrade a DOM object reference to a weak one. + fn downgrade(&self) -> WeakRef { + unsafe { + let object = self.reflector().get_jsobject().get(); + let mut ptr = + JS_GetReservedSlot(object, DOM_WEAK_SLOT).to_private() as *mut WeakBox; + if ptr.is_null() { + debug!("Creating new WeakBox holder for {:p}.", self); + ptr = Box::into_raw(box WeakBox { + count: Cell::new(1), + value: Cell::new(Some(NonZero::new(self))), + }); + JS_SetReservedSlot(object, DOM_WEAK_SLOT, PrivateValue(ptr as *const c_void)); + } + let box_ = &*ptr; + assert!(box_.value.get().is_some()); + let new_count = box_.count.get() + 1; + debug!("Incrementing WeakBox refcount for {:p} to {}.", self, new_count); + box_.count.set(new_count); + WeakRef { ptr: NonZero::new(ptr) } + } + } +} + +impl WeakRef { + /// Create a new weak reference from a `WeakReferenceable` interface instance. + /// This is just a convenience wrapper around `::downgrade` + /// to not have to import `WeakReferenceable`. + pub fn new(value: &T) -> Self { + value.downgrade() + } + + /// Root a weak reference. Returns `None` if the object was already collected. + pub fn root(&self) -> Option> { + unsafe { &**self.ptr }.value.get().map(Root::new) + } + + /// Return whether the weakly-referenced object is still alive. + pub fn is_alive(&self) -> bool { + unsafe { &**self.ptr }.value.get().is_some() + } +} + +impl Clone for WeakRef { + fn clone(&self) -> WeakRef { + unsafe { + let box_ = &**self.ptr; + let new_count = box_.count.get() + 1; + box_.count.set(new_count); + WeakRef { ptr: self.ptr } + } + } +} + +impl HeapSizeOf for WeakRef { + fn heap_size_of_children(&self) -> usize { + 0 + } +} + +no_jsmanaged_fields!(WeakRef); + +impl Drop for WeakRef { + fn drop(&mut self) { + unsafe { + let (count, value) = { + let weak_box = &**self.ptr; + assert!(weak_box.count.get() > 0); + let count = weak_box.count.get() - 1; + weak_box.count.set(count); + (count, weak_box.value.get()) + }; + if count == 0 { + assert!(value.is_none()); + mem::drop(Box::from_raw(*self.ptr)); + } + } + } +} + +/// A mutable weak reference to a JS-managed DOM object. On tracing, +/// the contained weak reference is dropped if the pointee was already +/// collected. +pub struct MutableWeakRef { + cell: UnsafeCell>>, +} + +impl MutableWeakRef { + /// Create a new mutable weak reference. + pub fn new(value: Option<&T>) -> MutableWeakRef { + MutableWeakRef { + cell: UnsafeCell::new(value.map(WeakRef::new)), + } + } + + /// Set the pointee of a mutable weak reference. + pub fn set(&self, value: Option<&T>) { + unsafe { *self.cell.get() = value.map(WeakRef::new); } + } + + /// Root a mutable weak reference. Returns `None` if the object + /// was already collected. + pub fn root(&self) -> Option> { + unsafe { &*self.cell.get() }.as_ref().and_then(WeakRef::root) + } +} + +impl HeapSizeOf for MutableWeakRef { + fn heap_size_of_children(&self) -> usize { + 0 + } +} + +impl JSTraceable for MutableWeakRef { + fn trace(&self, _: *mut JSTracer) { + let ptr = self.cell.get(); + unsafe { + let should_drop = match *ptr { + Some(ref value) => !value.is_alive(), + None => false, + }; + if should_drop { + mem::drop((*ptr).take().unwrap()); + } + } + } +} diff --git a/components/script/dom/testbinding.rs b/components/script/dom/testbinding.rs index df098b98aa5..b7b07966fcb 100644 --- a/components/script/dom/testbinding.rs +++ b/components/script/dom/testbinding.rs @@ -15,7 +15,9 @@ use dom::bindings::js::Root; use dom::bindings::num::Finite; use dom::bindings::reflector::{Reflector, reflect_dom_object}; use dom::bindings::str::{ByteString, USVString}; +use dom::bindings::weakref::MutableWeakRef; use dom::blob::Blob; +use dom::url::URL; use js::jsapi::{HandleValue, JSContext, JSObject}; use js::jsval::{JSVal, NullValue}; use std::borrow::ToOwned; @@ -26,12 +28,14 @@ use util::str::DOMString; #[dom_struct] pub struct TestBinding { reflector_: Reflector, + url: MutableWeakRef, } impl TestBinding { fn new_inherited() -> TestBinding { TestBinding { reflector_: Reflector::new(), + url: MutableWeakRef::new(None), } } @@ -142,6 +146,12 @@ impl TestBindingMethods for TestBinding { Some(Blob::new(global_root_from_reflector(self).r(), None, "")) } fn SetInterfaceAttributeNullable(&self, _: Option<&Blob>) {} + fn GetInterfaceAttributeWeak(&self) -> Option> { + self.url.root() + } + fn SetInterfaceAttributeWeak(&self, url: Option<&URL>) { + self.url.set(url); + } fn GetObjectAttributeNullable(&self, _: *mut JSContext) -> *mut JSObject { ptr::null_mut() } fn SetObjectAttributeNullable(&self, _: *mut JSContext, _: *mut JSObject) {} fn GetUnionAttributeNullable(&self) -> Option { diff --git a/components/script/dom/webidls/TestBinding.webidl b/components/script/dom/webidls/TestBinding.webidl index 803944b75fa..6f10b8c35c3 100644 --- a/components/script/dom/webidls/TestBinding.webidl +++ b/components/script/dom/webidls/TestBinding.webidl @@ -113,6 +113,7 @@ interface TestBinding { attribute ByteString? byteStringAttributeNullable; readonly attribute TestEnum? enumAttributeNullable; attribute Blob? interfaceAttributeNullable; + attribute URL? interfaceAttributeWeak; attribute object? objectAttributeNullable; attribute (HTMLElement or long)? unionAttributeNullable; attribute (Event or DOMString)? union2AttributeNullable; diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index 30c6d7f71a4..537c3f4662a 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -7,6 +7,10 @@ { "path": "mozilla/prototypes.html", "url": "/_mozilla/mozilla/prototypes.html" + }, + { + "path": "mozilla/weakref.html", + "url": "/_mozilla/mozilla/weakref.html" } ], "wdspec": [] diff --git a/tests/wpt/mozilla/tests/mozilla/weakref.html b/tests/wpt/mozilla/tests/mozilla/weakref.html new file mode 100644 index 00000000000..4deccbe1e26 --- /dev/null +++ b/tests/wpt/mozilla/tests/mozilla/weakref.html @@ -0,0 +1,28 @@ + + + + + + +