diff --git a/components/script/dom/bindings/DESIGN.md b/components/script/dom/bindings/DESIGN.md index b97bac77e68..9ee7a50a7ac 100644 --- a/components/script/dom/bindings/DESIGN.md +++ b/components/script/dom/bindings/DESIGN.md @@ -34,6 +34,7 @@ For supporting SpiderMonkey’s exact GC rooting, we introduce [some types](http - `JS` is used for the DOM typed field in a DOM type structure. The GC can trace them recursively while the enclosing DOM object (maybe root) is alive. - `LayoutJS` is specialized `JS` to use in layout. `Layout*Helper` must be implemented on this type to prevent calling methods from non layout code. +- `Unrooted` is used to wrap raw pointers to DOM objects extracted from their reflectors. - `Temporary` is used as a return value for functions returning a DOM type. They are rooted for the duration of their lifetime. But a retun value gets moved around which can break the LIFO ordering constraint. Thus we need to introduce `Root`. - `Root` contains the pointer to `JSObject` which the represented DOM type has. SpiderMonkey's conservative stack scanner scans it's pointers and marks a pointed `JSObject` as GC root. - `JSRef` is just a reference to the value rooted by `Root`. diff --git a/components/script/dom/bindings/codegen/CodegenRust.py b/components/script/dom/bindings/codegen/CodegenRust.py index cfa760a7679..edb64705c3e 100644 --- a/components/script/dom/bindings/codegen/CodegenRust.py +++ b/components/script/dom/bindings/codegen/CodegenRust.py @@ -1673,7 +1673,7 @@ def UnionTypes(descriptors, dictionaries, callbacks, config): 'dom::bindings::conversions::unwrap_jsmanaged', 'dom::bindings::conversions::StringificationBehavior::Default', 'dom::bindings::error::throw_not_in_union', - 'dom::bindings::js::JS', + 'dom::bindings::js::Unrooted', 'dom::types::*', 'js::jsapi::JSContext', 'js::jsval::JSVal', @@ -1795,7 +1795,7 @@ class CGAbstractMethod(CGThing): assert(False) # Override me! def CreateBindingJSObject(descriptor, parent=None): - create = "let mut raw: JS<%s> = JS::from_raw(&*object);\n" % descriptor.concreteType + create = "let mut raw: Unrooted<%s> = Unrooted::from_raw(&*object);\n" % descriptor.concreteType if descriptor.proxy: assert not descriptor.isGlobal() create += """ @@ -1853,7 +1853,7 @@ assert!(!proto.is_null()); raw.reflector().set_jsobject(obj); -Temporary::new(raw)""" % CreateBindingJSObject(self.descriptor, "scope")) +Temporary::from_unrooted(raw)""" % CreateBindingJSObject(self.descriptor, "scope")) else: return CGGeneric("""\ %s @@ -1866,7 +1866,7 @@ with_compartment(cx, obj, || { RegisterBindings::Register(cx, obj); }); -Temporary::new(raw)""" % CreateBindingJSObject(self.descriptor)) +Temporary::from_unrooted(raw)""" % CreateBindingJSObject(self.descriptor)) class CGIDLInterface(CGThing): @@ -2447,7 +2447,7 @@ class CGAbstractBindingMethod(CGAbstractExternMethod): " return false as JSBool;\n" "}\n" "\n" - "let this: JS<%s> = %s;\n" % (self.descriptor.concreteType, unwrapThis)) + "let this: Unrooted<%s> = %s;\n" % (self.descriptor.concreteType, unwrapThis)) return CGList([ unwrapThis, self.generate_code() ], "\n") def generate_code(self): @@ -2509,7 +2509,7 @@ class CGSpecializedMethod(CGAbstractExternMethod): self.method) return CGWrapper(CGMethodCall([], nativeName, self.method.isStatic(), self.descriptor, self.method), - pre="let this = JS::from_raw(this);\n" + pre="let this = Unrooted::from_raw(this);\n" "let this = this.root();\n") @staticmethod @@ -2575,7 +2575,7 @@ class CGSpecializedGetter(CGAbstractExternMethod): return CGWrapper(CGGetterCall([], self.attr.type, nativeName, self.descriptor, self.attr), - pre="let this = JS::from_raw(this);\n" + pre="let this = Unrooted::from_raw(this);\n" "let this = this.root();\n") @staticmethod @@ -2653,7 +2653,7 @@ class CGSpecializedSetter(CGAbstractExternMethod): self.attr) return CGWrapper(CGSetterCall([], self.attr.type, nativeName, self.descriptor, self.attr), - pre="let this = JS::from_raw(this);\n" + pre="let this = Unrooted::from_raw(this);\n" "let this = this.root();\n") @staticmethod @@ -3636,7 +3636,7 @@ class CGDOMJSProxyHandler_getOwnPropertyDescriptor(CGAbstractExternMethod): get = ("if index.is_some() {\n" + " let index = index.unwrap();\n" + " let this = UnwrapProxy(proxy);\n" + - " let this = JS::from_raw(this);\n" + + " let this = Unrooted::from_raw(this);\n" + " let this = this.root();\n" + CGIndenter(CGProxyIndexedGetter(self.descriptor, templateValues)).define() + "\n" + "}\n") @@ -3683,7 +3683,7 @@ class CGDOMJSProxyHandler_getOwnPropertyDescriptor(CGAbstractExternMethod): "if !set && RUST_JSID_IS_STRING(id) != 0 && !has_property_on_prototype(cx, proxy, id) {\n" + " let name = jsid_to_str(cx, id);\n" + " let this = UnwrapProxy(proxy);\n" + - " let this = JS::from_raw(this);\n" + + " let this = Unrooted::from_raw(this);\n" + " let this = this.root();\n" + CGIndenter(CGProxyNamedGetter(self.descriptor, templateValues)).define() + "\n" + "}\n") @@ -3729,7 +3729,7 @@ class CGDOMJSProxyHandler_defineProperty(CGAbstractExternMethod): "if index.is_some() {\n" + " let index = index.unwrap();\n" + " let this = UnwrapProxy(proxy);\n" + - " let this = JS::from_raw(this);\n" + + " let this = Unrooted::from_raw(this);\n" + " let this = this.root();\n" + CGIndenter(CGProxyIndexedSetter(self.descriptor)).define() + " return true;\n" + @@ -3747,7 +3747,7 @@ class CGDOMJSProxyHandler_defineProperty(CGAbstractExternMethod): set += ("if RUST_JSID_IS_STRING(id) != 0 {\n" + " let name = jsid_to_str(cx, id);\n" + " let this = UnwrapProxy(proxy);\n" + - " let this = JS::from_raw(this);\n" + + " let this = Unrooted::from_raw(this);\n" + " let this = this.root();\n" + CGIndenter(CGProxyNamedSetter(self.descriptor)).define() + "}\n") @@ -3755,7 +3755,7 @@ class CGDOMJSProxyHandler_defineProperty(CGAbstractExternMethod): set += ("if RUST_JSID_IS_STRING(id) != 0 {\n" + " let name = jsid_to_str(cx, id);\n" + " let this = UnwrapProxy(proxy);\n" + - " let this = JS::from_raw(this);\n" + + " let this = Unrooted::from_raw(this);\n" + " let this = this.root();\n" + CGIndenter(CGProxyNamedGetter(self.descriptor)).define() + " if (found) {\n" @@ -3782,7 +3782,7 @@ class CGDOMJSProxyHandler_delete(CGAbstractExternMethod): if self.descriptor.operations['NamedDeleter']: set += ("let name = jsid_to_str(cx, id);\n" + "let this = UnwrapProxy(proxy);\n" + - "let this = JS::from_raw(this);\n" + + "let this = Unrooted::from_raw(this);\n" + "let this = this.root();\n" + "%s") % (CGProxyNamedDeleter(self.descriptor).define()) set += "return proxyhandler::delete(%s);" % ", ".join(a.name for a in self.args) @@ -3804,7 +3804,7 @@ class CGDOMJSProxyHandler_hasOwn(CGAbstractExternMethod): "if index.is_some() {\n" + " let index = index.unwrap();\n" + " let this = UnwrapProxy(proxy);\n" + - " let this = JS::from_raw(this);\n" + + " let this = Unrooted::from_raw(this);\n" + " let this = this.root();\n" + CGIndenter(CGProxyIndexedGetter(self.descriptor)).define() + "\n" + " *bp = found;\n" + @@ -3818,7 +3818,7 @@ class CGDOMJSProxyHandler_hasOwn(CGAbstractExternMethod): named = ("if RUST_JSID_IS_STRING(id) != 0 && !has_property_on_prototype(cx, proxy, id) {\n" + " let name = jsid_to_str(cx, id);\n" + " let this = UnwrapProxy(proxy);\n" + - " let this = JS::from_raw(this);\n" + + " let this = Unrooted::from_raw(this);\n" + " let this = this.root();\n" + CGIndenter(CGProxyNamedGetter(self.descriptor)).define() + "\n" + " *bp = found;\n" @@ -3877,7 +3877,7 @@ if !expando.is_null() { "if index.is_some() {\n" + " let index = index.unwrap();\n" + " let this = UnwrapProxy(proxy);\n" + - " let this = JS::from_raw(this);\n" + + " let this = Unrooted::from_raw(this);\n" + " let this = this.root();\n" + CGIndenter(CGProxyIndexedGetter(self.descriptor, templateValues)).define()) getIndexedOrExpando += """\ @@ -3895,7 +3895,7 @@ if !expando.is_null() { getNamed = ("if (RUST_JSID_IS_STRING(id) != 0) {\n" + " let name = jsid_to_str(cx, id);\n" + " let this = UnwrapProxy(proxy);\n" + - " let this = JS::from_raw(this);\n" + + " let this = Unrooted::from_raw(this);\n" + " let this = this.root();\n" + CGIndenter(CGProxyNamedGetter(self.descriptor, templateValues)).define() + "}\n") @@ -4545,7 +4545,7 @@ class CGBindingRoot(CGThing): 'dom::bindings', 'dom::bindings::global::GlobalRef', 'dom::bindings::global::global_object_for_js_object', - 'dom::bindings::js::{JS, JSRef, Root, RootedReference, Temporary}', + 'dom::bindings::js::{JS, JSRef, Root, RootedReference, Temporary, Unrooted}', 'dom::bindings::js::{OptionalRootable, OptionalRootedRootable, ResultRootable}', 'dom::bindings::js::{OptionalRootedReference, OptionalOptionalRootedRootable}', 'dom::bindings::utils::{create_dom_global, do_create_interface_objects}', diff --git a/components/script/dom/bindings/codegen/Configuration.py b/components/script/dom/bindings/codegen/Configuration.py index ac103c86378..22efa56f6d7 100644 --- a/components/script/dom/bindings/codegen/Configuration.py +++ b/components/script/dom/bindings/codegen/Configuration.py @@ -157,7 +157,7 @@ class Descriptor(DescriptorProvider): self.returnType = "Temporary<%s>" % ifaceName self.argumentType = "JSRef<%s>" % ifaceName self.memberType = "Root<%s>" % ifaceName - self.nativeType = "JS<%s>" % ifaceName + self.nativeType = "Unrooted<%s>" % ifaceName self.concreteType = ifaceName self.register = desc.get('register', True) diff --git a/components/script/dom/bindings/conversions.rs b/components/script/dom/bindings/conversions.rs index 4b66acd0ec0..529c24ac2d6 100644 --- a/components/script/dom/bindings/conversions.rs +++ b/components/script/dom/bindings/conversions.rs @@ -30,7 +30,7 @@ //! | union types | `T` | use dom::bindings::codegen::PrototypeList; -use dom::bindings::js::{JS, JSRef, Root}; +use dom::bindings::js::{JS, JSRef, Root, Unrooted}; use dom::bindings::str::ByteString; use dom::bindings::utils::{Reflectable, Reflector, DOMClass}; use util::str::DOMString; @@ -458,13 +458,13 @@ unsafe fn get_dom_class(obj: *mut JSObject) -> Result { return Err(()); } -/// Get a `JS` for the given DOM object, unwrapping any wrapper around it -/// first, and checking if the object is of the correct type. +/// Get an `Unrooted` for the given DOM object, unwrapping any wrapper +/// around it first, and checking if the object is of the correct type. /// /// Returns Err(()) if `obj` is an opaque security wrapper or if the object is /// not a reflector for a DOM object of the given type (as defined by the /// proto_id and proto_depth). -pub fn unwrap_jsmanaged(mut obj: *mut JSObject) -> Result, ()> +pub fn unwrap_jsmanaged(mut obj: *mut JSObject) -> Result, ()> where T: Reflectable + IDLInterface { use js::glue::{IsWrapper, UnwrapObject}; @@ -493,7 +493,7 @@ pub fn unwrap_jsmanaged(mut obj: *mut JSObject) -> Result, ()> let proto_depth = IDLInterface::get_prototype_depth(None::); if dom_class.interface_chain[proto_depth] == proto_id { debug!("good prototype"); - Ok(JS::from_raw(unwrap(obj))) + Ok(Unrooted::from_raw(unwrap(obj))) } else { debug!("bad prototype"); Err(()) @@ -501,9 +501,10 @@ pub fn unwrap_jsmanaged(mut obj: *mut JSObject) -> Result, ()> } } -impl FromJSValConvertible for JS { +impl FromJSValConvertible for Unrooted { type Config = (); - fn from_jsval(_cx: *mut JSContext, value: JSVal, _option: ()) -> Result, ()> { + fn from_jsval(_cx: *mut JSContext, value: JSVal, _option: ()) + -> Result, ()> { if !value.is_object() { return Err(()); } @@ -529,6 +530,12 @@ impl<'a, T: Reflectable> ToJSValConvertible for JS { } } +impl<'a, T: Reflectable> ToJSValConvertible for Unrooted { + fn to_jsval(&self, cx: *mut JSContext) -> JSVal { + self.reflector().to_jsval(cx) + } +} + impl ToJSValConvertible for Option { fn to_jsval(&self, cx: *mut JSContext) -> JSVal { match self { diff --git a/components/script/dom/bindings/global.rs b/components/script/dom/bindings/global.rs index b53ab37c880..f706d175a39 100644 --- a/components/script/dom/bindings/global.rs +++ b/components/script/dom/bindings/global.rs @@ -8,7 +8,7 @@ //! code that works in workers as well as window scopes. use dom::bindings::conversions::FromJSValConvertible; -use dom::bindings::js::{JS, JSRef, Root}; +use dom::bindings::js::{JS, JSRef, Root, Unrooted}; use dom::bindings::utils::{Reflectable, Reflector}; use dom::workerglobalscope::{WorkerGlobalScope, WorkerGlobalScopeHelpers}; use dom::window; @@ -53,6 +53,15 @@ pub enum GlobalField { Worker(JS), } +/// An unrooted reference to a global object. +#[must_root] +pub enum GlobalUnrooted { + /// An unrooted reference to a `Window` object. + Window(Unrooted), + /// An unrooted reference to a `WorkerGlobalScope` object. + Worker(Unrooted), +} + impl<'a> GlobalRef<'a> { /// Get the `JSContext` for the `JSRuntime` associated with the thread /// this global object is on. @@ -136,20 +145,30 @@ impl GlobalField { } } +impl GlobalUnrooted { + /// Create a stack-bounded root for this reference. + pub fn root(&self) -> GlobalRoot { + match *self { + GlobalUnrooted::Window(ref window) => GlobalRoot::Window(window.root()), + GlobalUnrooted::Worker(ref worker) => GlobalRoot::Worker(worker.root()), + } + } +} + /// Returns the global object of the realm that the given JS object was created in. #[allow(unrooted_must_root)] -pub fn global_object_for_js_object(obj: *mut JSObject) -> GlobalField { +pub fn global_object_for_js_object(obj: *mut JSObject) -> GlobalUnrooted { unsafe { let global = GetGlobalForObjectCrossCompartment(obj); let clasp = JS_GetClass(global); assert!(((*clasp).flags & (JSCLASS_IS_DOMJSCLASS | JSCLASS_IS_GLOBAL)) != 0); match FromJSValConvertible::from_jsval(ptr::null_mut(), ObjectOrNullValue(global), ()) { - Ok(window) => return GlobalField::Window(window), + Ok(window) => return GlobalUnrooted::Window(window), Err(_) => (), } match FromJSValConvertible::from_jsval(ptr::null_mut(), ObjectOrNullValue(global), ()) { - Ok(worker) => return GlobalField::Worker(worker), + Ok(worker) => return GlobalUnrooted::Worker(worker), Err(_) => (), } diff --git a/components/script/dom/bindings/js.rs b/components/script/dom/bindings/js.rs index 216c38604ab..bfc8f9cae29 100644 --- a/components/script/dom/bindings/js.rs +++ b/components/script/dom/bindings/js.rs @@ -61,6 +61,46 @@ use std::marker::ContravariantLifetime; use std::mem; use std::ops::Deref; +/// An unrooted, JS-owned value. Must not be held across a GC. +#[must_root] +pub struct Unrooted { + ptr: NonZero<*const T> +} + +impl Unrooted { + /// Create a new JS-owned value wrapped from a raw Rust pointer. + pub unsafe fn from_raw(raw: *const T) -> Unrooted { + assert!(!raw.is_null()); + Unrooted { + ptr: NonZero::new(raw) + } + } + + /// Get the `Reflector` for this pointer. + pub fn reflector<'a>(&'a self) -> &'a Reflector { + unsafe { + (**self.ptr).reflector() + } + } + + /// Returns an unsafe pointer to the interior of this object. + pub unsafe fn unsafe_get(&self) -> *const T { + *self.ptr + } + + /// Create a stack-bounded root for this value. + pub fn root(self) -> Root { + STACK_ROOTS.with(|ref collection| { + let RootCollectionPtr(collection) = collection.get().unwrap(); + unsafe { + Root::new(&*collection, self.ptr) + } + }) + } +} + +impl Copy for Unrooted {} + /// A type that represents a JS-owned value that is rooted for the lifetime of this value. /// Importantly, it requires explicit rooting in order to interact with the inner value. /// Can be assigned into JS-owned member fields (i.e. `JS` types) safely via the @@ -96,6 +136,15 @@ impl Temporary { } } + /// Create a new `Temporary` value from an unrooted value. + #[allow(unrooted_must_root)] + pub fn from_unrooted(unrooted: Unrooted) -> Temporary { + Temporary { + inner: JS { ptr: unrooted.ptr }, + _js_ptr: unrooted.reflector().get_jsobject(), + } + } + /// Create a new `Temporary` value from a rooted value. pub fn from_rooted<'a>(root: JSRef<'a, T>) -> Temporary { Temporary::new(JS::from_rooted(root)) @@ -106,7 +155,7 @@ impl Temporary { STACK_ROOTS.with(|ref collection| { let RootCollectionPtr(collection) = collection.get().unwrap(); unsafe { - Root::new(&*collection, &self.inner) + Root::new(&*collection, self.inner.ptr) } }) } @@ -198,21 +247,12 @@ impl LayoutJS { } impl JS { - /// Create a new JS-owned value wrapped from a raw Rust pointer. - pub unsafe fn from_raw(raw: *const T) -> JS { - assert!(!raw.is_null()); - JS { - ptr: NonZero::new(raw) - } - } - - /// Root this JS-owned value to prevent its collection as garbage. pub fn root(&self) -> Root { STACK_ROOTS.with(|ref collection| { let RootCollectionPtr(collection) = collection.get().unwrap(); unsafe { - Root::new(&*collection, self) + Root::new(&*collection, self.ptr) } }) } @@ -484,6 +524,12 @@ impl OptionalRootedRootable for Option> { } } +impl OptionalRootedRootable for Option> { + fn root(&self) -> Option> { + self.as_ref().map(|inner| inner.root()) + } +} + /// Root a rootable `Option