mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
Implement binding support for returning and accepting Promises in WebIDL.
This commit is contained in:
parent
73b2963509
commit
a1091772ec
12 changed files with 217 additions and 20 deletions
|
@ -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"
|
||||
|
|
|
@ -14,6 +14,10 @@
|
|||
|
||||
DOMInterfaces = {
|
||||
|
||||
'Promise': {
|
||||
'spiderMonkeyInterface': True,
|
||||
},
|
||||
|
||||
'Range': {
|
||||
'weakReferenceable': True,
|
||||
},
|
||||
|
|
|
@ -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)
|
||||
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.
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
templateBody = unwrapCastableObject(
|
||||
descriptor, "${val}", unwrapFailureCode, conversionFunction)
|
||||
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)
|
||||
|
||||
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',
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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;
|
||||
|
|
71
components/script/dom/promise.rs
Normal file
71
components/script/dom/promise.rs
Normal file
|
@ -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
|
||||
}
|
|
@ -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) {
|
||||
|
|
11
components/script/dom/webidls/Promise.webidl
Normal file
11
components/script/dom/webidls/Promise.webidl
Normal file
|
@ -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 {
|
||||
};
|
|
@ -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();
|
||||
};
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ function test_interfaces(interfaceNamesInGlobalScope) {
|
|||
"NaN",
|
||||
"Number",
|
||||
"Object",
|
||||
"Promise",
|
||||
"Proxy",
|
||||
"RangeError",
|
||||
"ReferenceError",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue