aboutsummaryrefslogtreecommitdiffstats
path: root/components/script/dom
diff options
context:
space:
mode:
authorJosh Matthews <josh@joshmatthews.net>2016-06-21 19:35:36 -0400
committerJosh Matthews <josh@joshmatthews.net>2016-09-22 16:16:48 -0400
commita1091772ec9258d4e2c4184e07edab730e4d559c (patch)
treef9a92d79c1d844a288f64c20260dabe5675047a5 /components/script/dom
parent73b296350927bad6d526cce21434ce68a75216fa (diff)
downloadservo-a1091772ec9258d4e2c4184e07edab730e4d559c.tar.gz
servo-a1091772ec9258d4e2c4184e07edab730e4d559c.zip
Implement binding support for returning and accepting Promises in WebIDL.
Diffstat (limited to 'components/script/dom')
-rw-r--r--components/script/dom/bindings/codegen/Bindings.conf4
-rw-r--r--components/script/dom/bindings/codegen/CodegenRust.py85
-rw-r--r--components/script/dom/bindings/codegen/Configuration.py12
-rw-r--r--components/script/dom/bindings/global.rs11
-rw-r--r--components/script/dom/bindings/js.rs19
-rw-r--r--components/script/dom/mod.rs1
-rw-r--r--components/script/dom/promise.rs71
-rw-r--r--components/script/dom/testbinding.rs17
-rw-r--r--components/script/dom/webidls/Promise.webidl11
-rw-r--r--components/script/dom/webidls/TestBinding.webidl5
10 files changed, 216 insertions, 20 deletions
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();
};