diff options
author | Josh Matthews <josh@joshmatthews.net> | 2016-06-21 19:35:36 -0400 |
---|---|---|
committer | Josh Matthews <josh@joshmatthews.net> | 2016-09-22 16:16:48 -0400 |
commit | a1091772ec9258d4e2c4184e07edab730e4d559c (patch) | |
tree | f9a92d79c1d844a288f64c20260dabe5675047a5 /components | |
parent | 73b296350927bad6d526cce21434ce68a75216fa (diff) | |
download | servo-a1091772ec9258d4e2c4184e07edab730e4d559c.tar.gz servo-a1091772ec9258d4e2c4184e07edab730e4d559c.zip |
Implement binding support for returning and accepting Promises in WebIDL.
Diffstat (limited to 'components')
-rw-r--r-- | components/script/Cargo.toml | 2 | ||||
-rw-r--r-- | components/script/dom/bindings/codegen/Bindings.conf | 4 | ||||
-rw-r--r-- | components/script/dom/bindings/codegen/CodegenRust.py | 85 | ||||
-rw-r--r-- | components/script/dom/bindings/codegen/Configuration.py | 12 | ||||
-rw-r--r-- | components/script/dom/bindings/global.rs | 11 | ||||
-rw-r--r-- | components/script/dom/bindings/js.rs | 19 | ||||
-rw-r--r-- | components/script/dom/mod.rs | 1 | ||||
-rw-r--r-- | components/script/dom/promise.rs | 71 | ||||
-rw-r--r-- | components/script/dom/testbinding.rs | 17 | ||||
-rw-r--r-- | components/script/dom/webidls/Promise.webidl | 11 | ||||
-rw-r--r-- | components/script/dom/webidls/TestBinding.webidl | 5 |
11 files changed, 217 insertions, 21 deletions
diff --git a/components/script/Cargo.toml b/components/script/Cargo.toml index ee380e92854..626eb4e2288 100644 --- a/components/script/Cargo.toml +++ b/components/script/Cargo.toml @@ -41,7 +41,7 @@ hyper = "0.9.9" hyper_serde = "0.1.4" image = "0.10" ipc-channel = "0.5" -js = {git = "https://github.com/servo/rust-mozjs"} +js = {git = "https://github.com/servo/rust-mozjs", features = ["promises"]} libc = "0.2" log = "0.3.5" mime = "0.2.1" diff --git a/components/script/dom/bindings/codegen/Bindings.conf b/components/script/dom/bindings/codegen/Bindings.conf index 44d835589c4..434450d2bcc 100644 --- a/components/script/dom/bindings/codegen/Bindings.conf +++ b/components/script/dom/bindings/codegen/Bindings.conf @@ -14,6 +14,10 @@ DOMInterfaces = { +'Promise': { + 'spiderMonkeyInterface': True, +}, + 'Range': { 'weakReferenceable': True, }, diff --git a/components/script/dom/bindings/codegen/CodegenRust.py b/components/script/dom/bindings/codegen/CodegenRust.py index 7dc3e5eb3f0..9e8d6514327 100644 --- a/components/script/dom/bindings/codegen/CodegenRust.py +++ b/components/script/dom/bindings/codegen/CodegenRust.py @@ -658,7 +658,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, # failureCode will prevent pending exceptions from being set in cases when # they really should be! if exceptionCode is None: - exceptionCode = "return false;" + exceptionCode = "return false;\n" if failureCode is None: failOrPropagate = "throw_type_error(cx, &error);\n%s" % exceptionCode @@ -797,25 +797,69 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, descriptorType = descriptor.argumentType templateBody = "" - if descriptor.interface.isConsequential(): - raise TypeError("Consequential interface %s being used as an " - "argument" % descriptor.interface.identifier.name) - - if failureCode is None: - substitutions = { - "sourceDescription": sourceDescription, - "interface": descriptor.interface.identifier.name, - "exceptionCode": exceptionCode, - } - unwrapFailureCode = string.Template( - 'throw_type_error(cx, "${sourceDescription} does not ' - 'implement interface ${interface}.");\n' - '${exceptionCode}').substitute(substitutions) + isPromise = descriptor.interface.identifier.name == "Promise" + if isPromise: + # Per spec, what we're supposed to do is take the original + # Promise.resolve and call it with the original Promise as this + # value to make a Promise out of whatever value we actually have + # here. The question is which global we should use. There are + # a couple cases to consider: + # + # 1) Normal call to API with a Promise argument. This is a case the + # spec covers, and we should be using the current Realm's + # Promise. That means the current compartment. + # 2) Promise return value from a callback or callback interface. + # This is in theory a case the spec covers but in practice it + # really doesn't define behavior here because it doesn't define + # what Realm we're in after the callback returns, which is when + # the argument conversion happens. We will use the current + # compartment, which is the compartment of the callable (which + # may itself be a cross-compartment wrapper itself), which makes + # as much sense as anything else. In practice, such an API would + # once again be providing a Promise to signal completion of an + # operation, which would then not be exposed to anyone other than + # our own implementation code. + templateBody = fill( + """ + { // Scope for our JSAutoCompartment. + + rooted!(in(cx) let globalObj = CurrentGlobalOrNull(cx)); + let promiseGlobal = global_root_from_object_maybe_wrapped(globalObj.handle().get()); + + let mut valueToResolve = RootedValue::new(cx, $${val}.get()); + if !JS_WrapValue(cx, valueToResolve.handle_mut()) { + $*{exceptionCode} + } + match Promise::Resolve(promiseGlobal.r(), cx, valueToResolve.handle()) { + Ok(value) => value, + Err(error) => { + throw_dom_exception(cx, promiseGlobal.r(), error); + $*{exceptionCode} + } + } + } + """, + exceptionCode=exceptionCode) else: - unwrapFailureCode = failureCode + if descriptor.interface.isConsequential(): + raise TypeError("Consequential interface %s being used as an " + "argument" % descriptor.interface.identifier.name) + + if failureCode is None: + substitutions = { + "sourceDescription": sourceDescription, + "interface": descriptor.interface.identifier.name, + "exceptionCode": exceptionCode, + } + unwrapFailureCode = string.Template( + 'throw_type_error(cx, "${sourceDescription} does not ' + 'implement interface ${interface}.");\n' + '${exceptionCode}').substitute(substitutions) + else: + unwrapFailureCode = failureCode - templateBody = unwrapCastableObject( - descriptor, "${val}", unwrapFailureCode, conversionFunction) + templateBody = unwrapCastableObject( + descriptor, "${val}", unwrapFailureCode, conversionFunction) declType = CGGeneric(descriptorType) if type.nullable(): @@ -5372,6 +5416,7 @@ def generate_imports(config, cgthings, descriptors, callbacks=None, dictionaries 'js::jsapi::AutoIdVector', 'js::jsapi::Call', 'js::jsapi::CallArgs', + 'js::jsapi::CurrentGlobalOrNull', 'js::jsapi::FreeOp', 'js::jsapi::GetPropertyKeys', 'js::jsapi::GetWellKnownSymbol', @@ -5442,7 +5487,9 @@ def generate_imports(config, cgthings, descriptors, callbacks=None, dictionaries 'js::jsapi::MutableHandleValue', 'js::jsapi::ObjectOpResult', 'js::jsapi::PropertyDescriptor', + 'js::jsapi::RootedId', 'js::jsapi::RootedObject', + 'js::jsapi::RootedString', 'js::jsapi::SymbolCode', 'js::jsapi::jsid', 'js::jsval::JSVal', @@ -5472,6 +5519,7 @@ def generate_imports(config, cgthings, descriptors, callbacks=None, dictionaries 'dom::bindings::constant::ConstantVal', 'dom::bindings::global::GlobalRef', 'dom::bindings::global::global_root_from_object', + 'dom::bindings::global::global_root_from_object_maybe_wrapped', 'dom::bindings::global::global_root_from_reflector', 'dom::bindings::interface::ConstructorClassHook', 'dom::bindings::interface::InterfaceConstructorBehavior', @@ -5490,6 +5538,7 @@ def generate_imports(config, cgthings, descriptors, callbacks=None, dictionaries 'dom::bindings::js::JS', 'dom::bindings::js::OptionalRootedReference', 'dom::bindings::js::Root', + 'dom::bindings::js::RootedRcReference', 'dom::bindings::js::RootedReference', 'dom::bindings::namespace::NamespaceObjectClass', 'dom::bindings::namespace::create_namespace_object', diff --git a/components/script/dom/bindings/codegen/Configuration.py b/components/script/dom/bindings/codegen/Configuration.py index 6e71bd4bd00..835bcbe4774 100644 --- a/components/script/dom/bindings/codegen/Configuration.py +++ b/components/script/dom/bindings/codegen/Configuration.py @@ -194,9 +194,17 @@ class Descriptor(DescriptorProvider): typeName = desc.get('nativeType', nativeTypeDefault) - # Callback types do not use JS smart pointers, so we should not use the + spiderMonkeyInterface = desc.get('spiderMonkeyInterface', False) + + # Callback and SpiderMonkey types do not use JS smart pointers, so we should not use the # built-in rooting mechanisms for them. - if self.interface.isCallback(): + if spiderMonkeyInterface: + self.needsRooting = False + self.returnType = 'Rc<%s>' % typeName + self.argumentType = '&%s' % typeName + self.nativeType = typeName + pathDefault = 'dom::types::%s' % typeName + elif self.interface.isCallback(): self.needsRooting = False ty = 'dom::bindings::codegen::Bindings::%sBinding::%s' % (ifaceName, ifaceName) pathDefault = ty diff --git a/components/script/dom/bindings/global.rs b/components/script/dom/bindings/global.rs index 9452972f774..baec394b7fb 100644 --- a/components/script/dom/bindings/global.rs +++ b/components/script/dom/bindings/global.rs @@ -18,6 +18,7 @@ use dom::window::{self, ScriptHelpers}; use dom::workerglobalscope::WorkerGlobalScope; use ipc_channel::ipc::IpcSender; use js::{JSCLASS_IS_DOMJSCLASS, JSCLASS_IS_GLOBAL}; +use js::glue::{IsWrapper, UnwrapObject}; use js::jsapi::{CurrentGlobalOrNull, GetGlobalForObjectCrossCompartment}; use js::jsapi::{JSContext, JSObject, JS_GetClass, MutableHandleValue}; use js::jsapi::HandleValue; @@ -356,3 +357,13 @@ pub unsafe fn global_root_from_context(cx: *mut JSContext) -> GlobalRoot { let global = CurrentGlobalOrNull(cx); global_root_from_global(global) } + +/// Returns the global object of the realm that the given JS object was created in, +/// after unwrapping any wrappers. +pub unsafe fn global_root_from_object_maybe_wrapped(mut obj: *mut JSObject) -> GlobalRoot { + if IsWrapper(obj) { + obj = UnwrapObject(obj, /* stopAtWindowProxy = */ 0); + assert!(!obj.is_null()); + } + global_root_from_object(obj) +} diff --git a/components/script/dom/bindings/js.rs b/components/script/dom/bindings/js.rs index a7ecc975129..50cae7e1f04 100644 --- a/components/script/dom/bindings/js.rs +++ b/components/script/dom/bindings/js.rs @@ -43,6 +43,7 @@ use std::intrinsics::type_name; use std::mem; use std::ops::Deref; use std::ptr; +use std::rc::Rc; use style::thread_state; /// A traced reference to a DOM object @@ -439,6 +440,18 @@ impl<T: Reflectable> LayoutJS<T> { } } +/// Get an `&T` out of a `Rc<T>` +pub trait RootedRcReference<T> { + /// Obtain a safe reference to the wrapped non-JS owned value. + fn r(&self) -> &T; +} + +impl<T: Reflectable> RootedRcReference<T> for Rc<T> { + fn r(&self) -> &T { + &*self + } +} + /// Get an `Option<&T>` out of an `Option<Root<T>>` pub trait RootedReference<T> { /// Obtain a safe optional reference to the wrapped JS owned-value that @@ -446,6 +459,12 @@ pub trait RootedReference<T> { fn r(&self) -> Option<&T>; } +impl<T: Reflectable> RootedReference<T> for Option<Rc<T>> { + fn r(&self) -> Option<&T> { + self.as_ref().map(|root| &**root) + } +} + impl<T: Reflectable> RootedReference<T> for Option<Root<T>> { fn r(&self) -> Option<&T> { self.as_ref().map(|root| root.r()) diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index 5f6b4fffbdc..3861e5123e1 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -373,6 +373,7 @@ pub mod pluginarray; pub mod popstateevent; pub mod processinginstruction; pub mod progressevent; +pub mod promise; pub mod radionodelist; pub mod range; pub mod request; diff --git a/components/script/dom/promise.rs b/components/script/dom/promise.rs new file mode 100644 index 00000000000..5caa6797c5b --- /dev/null +++ b/components/script/dom/promise.rs @@ -0,0 +1,71 @@ +/* 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/. */ + +use dom::bindings::error::Fallible; +use dom::bindings::global::GlobalRef; +use dom::bindings::reflector::{Reflectable, Reflector}; +use js::jsapi::{JSAutoCompartment, RootedObject, CallArgs, JS_GetFunctionObject, JS_NewFunction}; +use js::jsapi::{JSContext, HandleValue, HandleObject, IsPromiseObject, CallOriginalPromiseResolve}; +use js::jsapi::{MutableHandleObject, NewPromiseObject}; +use js::jsval::{JSVal, UndefinedValue}; +use std::ptr; +use std::rc::Rc; + +#[dom_struct] +pub struct Promise { + reflector: Reflector +} + +impl Promise { + #[allow(unsafe_code)] + pub fn new(global: GlobalRef) -> Rc<Promise> { + let cx = global.get_cx(); + let mut obj = RootedObject::new(cx, ptr::null_mut()); + unsafe { + Promise::create_js_promise(cx, HandleObject::null(), obj.handle_mut()); + } + Promise::new_with_js_promise(obj.handle()) + } + + #[allow(unsafe_code, unrooted_must_root)] + fn new_with_js_promise(obj: HandleObject) -> Rc<Promise> { + unsafe { + assert!(IsPromiseObject(obj)); + } + let mut promise = Promise { + reflector: Reflector::new(), + }; + promise.init_reflector(obj.get()); + Rc::new(promise) + } + + #[allow(unsafe_code)] + unsafe fn create_js_promise(cx: *mut JSContext, proto: HandleObject, mut obj: MutableHandleObject) { + let do_nothing_func = JS_NewFunction(cx, Some(do_nothing_promise_executor), 2, 0, ptr::null()); + assert!(!do_nothing_func.is_null()); + let do_nothing_obj = RootedObject::new(cx, JS_GetFunctionObject(do_nothing_func)); + assert!(!do_nothing_obj.handle().is_null()); + *obj = NewPromiseObject(cx, do_nothing_obj.handle(), proto); + assert!(!obj.is_null()); + } + + #[allow(unrooted_must_root, unsafe_code)] + pub fn Resolve(global: GlobalRef, + cx: *mut JSContext, + value: HandleValue) -> Fallible<Rc<Promise>> { + let _ac = JSAutoCompartment::new(cx, global.reflector().get_jsobject().get()); + let p = unsafe { + RootedObject::new(cx, CallOriginalPromiseResolve(cx, value)) + }; + assert!(!p.handle().is_null()); + Ok(Promise::new_with_js_promise(p.handle())) + } +} + +#[allow(unsafe_code)] +unsafe extern fn do_nothing_promise_executor(_cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool { + let args = CallArgs::from_vp(vp, argc); + *args.rval() = UndefinedValue(); + true +} diff --git a/components/script/dom/testbinding.rs b/components/script/dom/testbinding.rs index 566ff2d9375..3ac2e7c2c26 100644 --- a/components/script/dom/testbinding.rs +++ b/components/script/dom/testbinding.rs @@ -28,6 +28,7 @@ use dom::bindings::reflector::{Reflectable, Reflector, reflect_dom_object}; use dom::bindings::str::{ByteString, DOMString, USVString}; use dom::bindings::weakref::MutableWeakRef; use dom::blob::{Blob, BlobImpl}; +use dom::promise::Promise; use dom::url::URL; use js::jsapi::{HandleObject, HandleValue, JSContext, JSObject}; use js::jsapi::{JS_NewPlainObject, JS_NewUint8ClampedArray}; @@ -647,6 +648,22 @@ impl TestBindingMethods for TestBinding { fn ReceiveMozMapOfMozMaps(&self) -> MozMap<MozMap<i32>> { MozMap::new() } fn ReceiveAnyMozMap(&self) -> MozMap<JSVal> { MozMap::new() } + #[allow(unrooted_must_root)] + fn ReturnPromise(&self) -> Rc<Promise> { + Promise::new(self.global().r()) + } + + #[allow(unrooted_must_root)] + fn PromiseAttribute(&self) -> Rc<Promise> { + Promise::new(self.global().r()) + } + + fn AcceptPromise(&self, _promise: &Promise) { + } + + fn AcceptNullablePromise(&self, _promise: Option<&Promise>) { + } + fn PassSequenceSequence(&self, _seq: Vec<Vec<i32>>) {} fn ReturnSequenceSequence(&self) -> Vec<Vec<i32>> { vec![] } fn PassUnionSequenceSequence(&self, seq: LongOrLongSequenceSequence) { diff --git a/components/script/dom/webidls/Promise.webidl b/components/script/dom/webidls/Promise.webidl new file mode 100644 index 00000000000..d437d244d58 --- /dev/null +++ b/components/script/dom/webidls/Promise.webidl @@ -0,0 +1,11 @@ +/* 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/. */ + +// This interface is entirely internal to Servo, and should not be accessible to +// web pages. + +[NoInterfaceObject] +// Need to escape "Promise" so it's treated as an identifier. +interface _Promise { +}; diff --git a/components/script/dom/webidls/TestBinding.webidl b/components/script/dom/webidls/TestBinding.webidl index e35b914d08e..dc58b9b0645 100644 --- a/components/script/dom/webidls/TestBinding.webidl +++ b/components/script/dom/webidls/TestBinding.webidl @@ -508,6 +508,11 @@ interface TestBinding { [Func="TestBinding::condition_satisfied"] const unsigned short funcControlledConstEnabled = 0; + Promise<DOMString> returnPromise(); + readonly attribute Promise<boolean> promiseAttribute; + void acceptPromise(Promise<DOMString> string); + void acceptNullablePromise(Promise<DOMString>? string); + void panic(); }; |