diff options
Diffstat (limited to 'components/script/dom')
30 files changed, 1026 insertions, 145 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 ca7901a56e3..cc6e1660fba 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()); + + rooted!(in(cx) let mut valueToResolve = $${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(): @@ -4842,13 +4886,12 @@ class CGDOMJSProxyHandler_defineProperty(CGAbstractExternMethod): " let this = UnwrapProxy(proxy);\n" + " let this = &*this;\n" + CGIndenter(CGProxyIndexedSetter(self.descriptor)).define() + - " return true;\n" + + " return (*opresult).succeed();\n" + "}\n") elif self.descriptor.operations['IndexedGetter']: set += ("if get_array_index_from_id(cx, id).is_some() {\n" + - " return false;\n" + - " //return ThrowErrorMessage(cx, MSG_NO_PROPERTY_SETTER, \"%s\");\n" + - "}\n") % self.descriptor.name + " return (*opresult).failNoIndexedSetter();\n" + + "}\n") namedSetter = self.descriptor.operations['NamedSetter'] if namedSetter: @@ -4864,15 +4907,10 @@ class CGDOMJSProxyHandler_defineProperty(CGAbstractExternMethod): else: set += ("if RUST_JSID_IS_STRING(id) {\n" + CGIndenter(CGProxyNamedGetter(self.descriptor)).define() + - " if (found) {\n" - # TODO(Issue 5876) - " //return js::IsInNonStrictPropertySet(cx)\n" + - " // ? opresult.succeed()\n" + - " // : ThrowErrorMessage(cx, MSG_NO_NAMED_SETTER, \"${name}\");\n" + - " return (*opresult).succeed();\n" + - " }\n" + - " return (*opresult).succeed();\n" - "}\n") % (self.descriptor.name, self.descriptor.name) + " if result.is_some() {\n" + " return (*opresult).failNoNamedSetter();\n" + " }\n" + "}\n") set += "return proxyhandler::define_property(%s);" % ", ".join(a.name for a in self.args) return set @@ -5378,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', @@ -5443,12 +5482,15 @@ def generate_imports(config, cgthings, descriptors, callbacks=None, dictionaries 'js::jsapi::JS_SetProperty', 'js::jsapi::JS_SetReservedSlot', 'js::jsapi::JS_SplicePrototype', + 'js::jsapi::JS_WrapValue', 'js::jsapi::MutableHandle', 'js::jsapi::MutableHandleObject', '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', @@ -5478,6 +5520,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', @@ -5496,6 +5539,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..9a1120769c8 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 @@ -225,7 +233,8 @@ class Descriptor(DescriptorProvider): # them as having a concrete descendant. self.concrete = (not self.interface.isCallback() and not self.interface.isNamespace() and - not self.interface.getExtendedAttribute("Abstract")) + not self.interface.getExtendedAttribute("Abstract") and + not spiderMonkeyInterface) self.hasUnforgeableMembers = (self.concrete and any(MemberIsUnforgeable(m, self) for m in self.interface.members)) diff --git a/components/script/dom/bindings/error.rs b/components/script/dom/bindings/error.rs index c9cbeb09f21..1c0d49c4976 100644 --- a/components/script/dom/bindings/error.rs +++ b/components/script/dom/bindings/error.rs @@ -19,6 +19,7 @@ use js::jsapi::JS_ErrorFromException; use js::jsapi::JS_GetPendingException; use js::jsapi::JS_IsExceptionPending; use js::jsapi::JS_SetPendingException; +use js::jsapi::MutableHandleValue; use js::jsval::UndefinedValue; use libc::c_uint; use std::slice::from_raw_parts; @@ -266,3 +267,14 @@ pub unsafe fn throw_invalid_this(cx: *mut JSContext, proto_id: u16) { proto_id_to_name(proto_id)); throw_type_error(cx, &error); } + +impl Error { + /// Convert this error value to a JS value, consuming it in the process. + pub unsafe fn to_jsval(self, cx: *mut JSContext, global: GlobalRef, rval: MutableHandleValue) { + assert!(!JS_IsExceptionPending(cx)); + throw_dom_exception(cx, global, self); + assert!(JS_IsExceptionPending(cx)); + assert!(JS_GetPendingException(cx, rval)); + JS_ClearPendingException(cx); + } +} diff --git a/components/script/dom/bindings/global.rs b/components/script/dom/bindings/global.rs index 9452972f774..9b4125bdc6a 100644 --- a/components/script/dom/bindings/global.rs +++ b/components/script/dom/bindings/global.rs @@ -18,13 +18,14 @@ 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; use msg::constellation_msg::PipelineId; use net_traits::{CoreResourceThread, IpcSend, ResourceThreads}; use profile_traits::{mem, time}; -use script_runtime::{CommonScriptMsg, ScriptChan, ScriptPort}; +use script_runtime::{CommonScriptMsg, ScriptChan, ScriptPort, EnqueuedPromiseCallback}; use script_thread::{MainThreadScriptChan, RunnableWrapper, ScriptThread}; use script_traits::{MsDuration, ScriptMsg as ConstellationMsg, TimerEventRequest}; use task_source::dom_manipulation::DOMManipulationTaskSource; @@ -289,6 +290,23 @@ impl<'a> GlobalRef<'a> { } } + /// Enqueue a promise callback for subsequent execution. + pub fn enqueue_promise_job(&self, job: EnqueuedPromiseCallback) { + match *self { + GlobalRef::Window(_) => ScriptThread::enqueue_promise_job(job, *self), + GlobalRef::Worker(ref worker) => worker.enqueue_promise_job(job), + } + } + + /// Start the process of executing the pending promise callbacks. They will be invoked + /// in FIFO order, synchronously, at some point in the future. + pub fn flush_promise_jobs(&self) { + match *self { + GlobalRef::Window(_) => ScriptThread::flush_promise_jobs(*self), + GlobalRef::Worker(ref worker) => worker.flush_promise_jobs(), + } + } + /// https://html.spec.whatwg.org/multipage/#report-the-error pub fn report_an_error(&self, error_info: ErrorInfo, value: HandleValue) { match *self { @@ -356,3 +374,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..1fd127b3b28 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 @@ -269,6 +270,12 @@ impl MutHeapJSVal { debug_assert!(thread_state::get().is_script()); unsafe { (*self.val.get()).get() } } + + /// Get the underlying unsafe pointer to the contained value. + pub unsafe fn get_unsafe(&self) -> *mut JSVal { + debug_assert!(thread_state::get().is_script()); + (*self.val.get()).get_unsafe() + } } @@ -439,6 +446,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 +465,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/bindings/mozmap.rs b/components/script/dom/bindings/mozmap.rs index 557ba0662a0..d44114cd057 100644 --- a/components/script/dom/bindings/mozmap.rs +++ b/components/script/dom/bindings/mozmap.rs @@ -23,6 +23,7 @@ use std::collections::HashMap; use std::ops::Deref; /// The `MozMap` (open-ended dictionary) type. +#[derive(Clone)] pub struct MozMap<T> { map: HashMap<DOMString, T>, } diff --git a/components/script/dom/bindings/refcounted.rs b/components/script/dom/bindings/refcounted.rs index ea4c47b1148..41cc60b0210 100644 --- a/components/script/dom/bindings/refcounted.rs +++ b/components/script/dom/bindings/refcounted.rs @@ -26,6 +26,7 @@ use core::nonzero::NonZero; use dom::bindings::js::Root; use dom::bindings::reflector::{Reflectable, Reflector}; use dom::bindings::trace::trace_reflector; +use dom::promise::Promise; use js::jsapi::JSTracer; use libc; use std::cell::RefCell; @@ -34,6 +35,7 @@ use std::collections::hash_map::HashMap; use std::hash::Hash; use std::marker::PhantomData; use std::os; +use std::rc::Rc; use std::sync::{Arc, Weak}; @@ -58,6 +60,63 @@ impl TrustedReference { } } +/// A safe wrapper around a DOM Promise object that can be shared among threads for use +/// in asynchronous operations. The underlying DOM object is guaranteed to live at least +/// as long as the last outstanding `TrustedPromise` instance. These values cannot be cloned, +/// only created from existing Rc<Promise> values. +pub struct TrustedPromise { + dom_object: *const Promise, + owner_thread: *const libc::c_void, +} + +unsafe impl Send for TrustedPromise {} + +impl TrustedPromise { + /// Create a new `TrustedPromise` instance from an existing DOM object. The object will + /// be prevented from being GCed for the duration of the resulting `TrustedPromise` object's + /// lifetime. + #[allow(unrooted_must_root)] + pub fn new(promise: Rc<Promise>) -> TrustedPromise { + LIVE_REFERENCES.with(|ref r| { + let r = r.borrow(); + let live_references = r.as_ref().unwrap(); + let ptr = &*promise as *const Promise; + live_references.addref_promise(promise); + TrustedPromise { + dom_object: ptr, + owner_thread: (&*live_references) as *const _ as *const libc::c_void, + } + }) + } + + /// Obtain a usable DOM Promise from a pinned `TrustedPromise` value. Fails if used on + /// a different thread than the original value from which this `TrustedPromise` was + /// obtained. + #[allow(unrooted_must_root)] + pub fn root(self) -> Rc<Promise> { + LIVE_REFERENCES.with(|ref r| { + let r = r.borrow(); + let live_references = r.as_ref().unwrap(); + assert!(self.owner_thread == (&*live_references) as *const _ as *const libc::c_void); + // Borrow-check error requires the redundant `let promise = ...; promise` here. + let promise = match live_references.promise_table.borrow_mut().entry(self.dom_object) { + Occupied(mut entry) => { + let promise = { + let promises = entry.get_mut(); + promises.pop().expect("rooted promise list unexpectedly empty") + }; + if entry.get().is_empty() { + entry.remove(); + } + promise + } + Vacant(_) => unreachable!(), + }; + promise + }) + } +} + /// A safe wrapper around a raw pointer to a DOM object that can be /// shared among threads for use in asynchronous operations. The underlying /// DOM object is guaranteed to live at least as long as the last outstanding @@ -117,9 +176,11 @@ impl<T: Reflectable> Clone for Trusted<T> { /// The set of live, pinned DOM objects that are currently prevented /// from being garbage collected due to outstanding references. +#[allow(unrooted_must_root)] pub struct LiveDOMReferences { // keyed on pointer to Rust DOM object - table: RefCell<HashMap<*const libc::c_void, Weak<TrustedReference>>>, + reflectable_table: RefCell<HashMap<*const libc::c_void, Weak<TrustedReference>>>, + promise_table: RefCell<HashMap<*const Promise, Vec<Rc<Promise>>>>, } impl LiveDOMReferences { @@ -127,13 +188,20 @@ impl LiveDOMReferences { pub fn initialize() { LIVE_REFERENCES.with(|ref r| { *r.borrow_mut() = Some(LiveDOMReferences { - table: RefCell::new(HashMap::new()), + reflectable_table: RefCell::new(HashMap::new()), + promise_table: RefCell::new(HashMap::new()), }) }); } + #[allow(unrooted_must_root)] + fn addref_promise(&self, promise: Rc<Promise>) { + let mut table = self.promise_table.borrow_mut(); + table.entry(&*promise).or_insert(vec![]).push(promise) + } + fn addref<T: Reflectable>(&self, ptr: *const T) -> Arc<TrustedReference> { - let mut table = self.table.borrow_mut(); + let mut table = self.reflectable_table.borrow_mut(); let capacity = table.capacity(); let len = table.len(); if (0 < capacity) && (capacity <= len) { @@ -173,17 +241,27 @@ fn remove_nulls<K: Eq + Hash + Clone, V> (table: &mut HashMap<K, Weak<V>>) { } /// A JSTraceDataOp for tracing reflectors held in LIVE_REFERENCES +#[allow(unrooted_must_root)] pub unsafe extern "C" fn trace_refcounted_objects(tracer: *mut JSTracer, _data: *mut os::raw::c_void) { info!("tracing live refcounted references"); LIVE_REFERENCES.with(|ref r| { let r = r.borrow(); let live_references = r.as_ref().unwrap(); - let mut table = live_references.table.borrow_mut(); - remove_nulls(&mut table); - for obj in table.keys() { - let reflectable = &*(*obj as *const Reflector); - trace_reflector(tracer, "refcounted", reflectable); + { + let mut table = live_references.reflectable_table.borrow_mut(); + remove_nulls(&mut table); + for obj in table.keys() { + let reflectable = &*(*obj as *const Reflector); + trace_reflector(tracer, "refcounted", reflectable); + } + } + + { + let table = live_references.promise_table.borrow_mut(); + for promise in table.keys() { + trace_reflector(tracer, "refcounted", (**promise).reflector()); + } } }); } diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index 67b8832efed..53d141d114c 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -36,7 +36,7 @@ use devtools_traits::CSSError; use devtools_traits::WorkerId; use dom::abstractworker::SharedRt; use dom::bindings::js::{JS, Root}; -use dom::bindings::refcounted::Trusted; +use dom::bindings::refcounted::{Trusted, TrustedPromise}; use dom::bindings::reflector::{Reflectable, Reflector}; use dom::bindings::str::{DOMString, USVString}; use dom::bindings::utils::WindowProxyHandler; @@ -156,7 +156,7 @@ impl<T: JSTraceable> JSTraceable for Rc<T> { } } -impl<T: JSTraceable> JSTraceable for Box<T> { +impl<T: JSTraceable + ?Sized> JSTraceable for Box<T> { fn trace(&self, trc: *mut JSTracer) { (**self).trace(trc) } @@ -303,6 +303,7 @@ no_jsmanaged_fields!(Metadata); no_jsmanaged_fields!(NetworkError); no_jsmanaged_fields!(Atom, Namespace, QualName); no_jsmanaged_fields!(Trusted<T: Reflectable>); +no_jsmanaged_fields!(TrustedPromise); no_jsmanaged_fields!(PropertyDeclarationBlock); no_jsmanaged_fields!(HashSet<T>); // These three are interdependent, if you plan to put jsmanaged data diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 15cb50aa466..2cca84a625a 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -95,9 +95,10 @@ use js::jsapi::JS_GetRuntime; use msg::constellation_msg::{ALT, CONTROL, SHIFT, SUPER}; use msg::constellation_msg::{Key, KeyModifiers, KeyState}; use msg::constellation_msg::{PipelineId, ReferrerPolicy}; -use net_traits::{AsyncResponseTarget, IpcSend, PendingAsyncLoad}; +use net_traits::{AsyncResponseTarget, FetchResponseMsg, IpcSend, PendingAsyncLoad}; use net_traits::CookieSource::NonHTTP; use net_traits::CoreResourceMsg::{GetCookiesForUrl, SetCookiesForUrl}; +use net_traits::request::RequestInit; use net_traits::response::HttpsState; use num_traits::ToPrimitive; use origin::Origin; @@ -1433,6 +1434,14 @@ impl Document { loader.load_async(load, listener, self, referrer_policy); } + pub fn fetch_async(&self, load: LoadType, + request: RequestInit, + fetch_target: IpcSender<FetchResponseMsg>, + referrer_policy: Option<ReferrerPolicy>) { + let mut loader = self.loader.borrow_mut(); + loader.fetch_async(load, request, fetch_target, self, referrer_policy); + } + pub fn finish_load(&self, load: LoadType) { debug!("Document got finish_load: {:?}", load); // The parser might need the loader, so restrict the lifetime of the borrow. @@ -1520,7 +1529,7 @@ impl Document { } /// https://html.spec.whatwg.org/multipage/#the-end step 5 and the latter parts of - /// https://html.spec.whatwg.org/multipage/#prepare-a-script 15.d and 15.e. + /// https://html.spec.whatwg.org/multipage/#prepare-a-script 20.d and 20.e. pub fn process_asap_scripts(&self) { // Execute the first in-order asap-executed script if it's ready, repeat as required. // Re-borrowing the list for each step because it can also be borrowed under execute. diff --git a/components/script/dom/extendableevent.rs b/components/script/dom/extendableevent.rs index 76756b40c71..ffb6c3baad2 100644 --- a/components/script/dom/extendableevent.rs +++ b/components/script/dom/extendableevent.rs @@ -51,7 +51,7 @@ impl ExtendableEvent { } // https://w3c.github.io/ServiceWorker/#wait-until-method - pub fn WaitUntil(&self, _cx: *mut JSContext, val: HandleValue) -> ErrorResult { + pub fn WaitUntil(&self, _cx: *mut JSContext, _val: HandleValue) -> ErrorResult { // Step 1 if !self.extensions_allowed { return Err(Error::InvalidState); diff --git a/components/script/dom/headers.rs b/components/script/dom/headers.rs index b977110955c..4b832121654 100644 --- a/components/script/dom/headers.rs +++ b/components/script/dom/headers.rs @@ -3,10 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::HeadersBinding; -use dom::bindings::codegen::Bindings::HeadersBinding::HeadersMethods; -use dom::bindings::codegen::Bindings::HeadersBinding::HeadersWrap; -use dom::bindings::codegen::UnionTypes::HeadersOrByteStringSequenceSequence; +use dom::bindings::codegen::Bindings::HeadersBinding::{HeadersInit, HeadersMethods, HeadersWrap}; use dom::bindings::error::{Error, ErrorResult, Fallible}; use dom::bindings::global::GlobalRef; use dom::bindings::iterable::Iterable; @@ -51,7 +48,7 @@ impl Headers { } // https://fetch.spec.whatwg.org/#dom-headers - pub fn Constructor(global: GlobalRef, init: Option<HeadersBinding::HeadersInit>) + pub fn Constructor(global: GlobalRef, init: Option<HeadersInit>) -> Fallible<Root<Headers>> { let dom_headers_new = Headers::new(global); try!(dom_headers_new.fill(init)); @@ -169,10 +166,10 @@ impl HeadersMethods for Headers { impl Headers { // https://fetch.spec.whatwg.org/#concept-headers-fill - pub fn fill(&self, filler: Option<HeadersBinding::HeadersInit>) -> ErrorResult { + pub fn fill(&self, filler: Option<HeadersInit>) -> ErrorResult { match filler { // Step 1 - Some(HeadersOrByteStringSequenceSequence::Headers(h)) => { + Some(HeadersInit::Headers(h)) => { for header in h.header_list.borrow().iter() { try!(self.Append( ByteString::new(Vec::from(header.name())), @@ -182,7 +179,7 @@ impl Headers { Ok(()) }, // Step 2 - Some(HeadersOrByteStringSequenceSequence::ByteStringSequenceSequence(v)) => { + Some(HeadersInit::ByteStringSequenceSequence(v)) => { for mut seq in v { if seq.len() == 2 { let val = seq.pop().unwrap(); @@ -196,7 +193,14 @@ impl Headers { } Ok(()) }, - // Step 3 TODO constructor for when init is an open-ended dictionary + Some(HeadersInit::ByteStringMozMap(m)) => { + for (key, value) in m.iter() { + let key_vec = key.as_ref().to_string().into(); + let headers_key = ByteString::new(key_vec); + try!(self.Append(headers_key, value.clone())); + } + Ok(()) + }, None => Ok(()), } } diff --git a/components/script/dom/htmllinkelement.rs b/components/script/dom/htmllinkelement.rs index bbffecaaf6a..1c84fbba784 100644 --- a/components/script/dom/htmllinkelement.rs +++ b/components/script/dom/htmllinkelement.rs @@ -368,7 +368,9 @@ impl HTMLLinkElementMethods for HTMLLinkElement { make_getter!(Rel, "rel"); // https://html.spec.whatwg.org/multipage/#dom-link-rel - make_setter!(SetRel, "rel"); + fn SetRel(&self, rel: DOMString) { + self.upcast::<Element>().set_tokenlist_attribute(&atom!("rel"), rel); + } // https://html.spec.whatwg.org/multipage/#dom-link-media make_getter!(Media, "media"); diff --git a/components/script/dom/htmlscriptelement.rs b/components/script/dom/htmlscriptelement.rs index 11f4451ad50..566b69967d4 100644 --- a/components/script/dom/htmlscriptelement.rs +++ b/components/script/dom/htmlscriptelement.rs @@ -5,6 +5,7 @@ use document_loader::LoadType; use dom::attr::Attr; use dom::bindings::cell::DOMRefCell; +use dom::bindings::codegen::Bindings::AttrBinding::AttrMethods; use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; use dom::bindings::codegen::Bindings::HTMLScriptElementBinding; use dom::bindings::codegen::Bindings::HTMLScriptElementBinding::HTMLScriptElementMethods; @@ -14,6 +15,7 @@ use dom::bindings::inheritance::Castable; use dom::bindings::js::{JS, Root}; use dom::bindings::js::RootedReference; use dom::bindings::refcounted::Trusted; +use dom::bindings::reflector::Reflectable; use dom::bindings::str::DOMString; use dom::document::Document; use dom::element::{AttributeMutation, Element, ElementCreator}; @@ -29,7 +31,8 @@ use html5ever::tree_builder::NextParserState; use ipc_channel::ipc; use ipc_channel::router::ROUTER; use js::jsval::UndefinedValue; -use net_traits::{AsyncResponseListener, AsyncResponseTarget, Metadata, NetworkError}; +use net_traits::{FetchMetadata, FetchResponseListener, Metadata, NetworkError}; +use net_traits::request::{CORSSettings, CredentialsMode, Destination, RequestInit, RequestMode, Type as RequestType}; use network_listener::{NetworkListener, PreInvoke}; use std::ascii::AsciiExt; use std::cell::Cell; @@ -151,9 +154,17 @@ struct ScriptContext { status: Result<(), NetworkError> } -impl AsyncResponseListener for ScriptContext { - fn headers_available(&mut self, metadata: Result<Metadata, NetworkError>) { - self.metadata = metadata.ok(); +impl FetchResponseListener for ScriptContext { + fn process_request_body(&mut self) {} // TODO(KiChjang): Perhaps add custom steps to perform fetch here? + + fn process_request_eof(&mut self) {} // TODO(KiChjang): Perhaps add custom steps to perform fetch here? + + fn process_response(&mut self, + metadata: Result<FetchMetadata, NetworkError>) { + self.metadata = metadata.ok().map(|meta| match meta { + FetchMetadata::Unfiltered(m) => m, + FetchMetadata::Filtered { unsafe_, .. } => unsafe_ + }); let status_code = self.metadata.as_ref().and_then(|m| { match m.status { @@ -169,18 +180,17 @@ impl AsyncResponseListener for ScriptContext { }; } - fn data_available(&mut self, payload: Vec<u8>) { + fn process_response_chunk(&mut self, mut chunk: Vec<u8>) { if self.status.is_ok() { - let mut payload = payload; - self.data.append(&mut payload); + self.data.append(&mut chunk); } } /// https://html.spec.whatwg.org/multipage/#fetch-a-classic-script /// step 4-9 - fn response_complete(&mut self, status: Result<(), NetworkError>) { + fn process_response_eof(&mut self, response: Result<(), NetworkError>) { // Step 5. - let load = status.and(self.status.clone()).map(|_| { + let load = response.and(self.status.clone()).map(|_| { let metadata = self.metadata.take().unwrap(); // Step 6. @@ -210,8 +220,38 @@ impl PreInvoke for ScriptContext {} /// https://html.spec.whatwg.org/multipage/#fetch-a-classic-script fn fetch_a_classic_script(script: &HTMLScriptElement, url: Url, + cors_setting: Option<CORSSettings>, character_encoding: EncodingRef) { - // TODO(#9186): use the fetch infrastructure. + let doc = document_from_node(script); + + // Step 1, 2. + let request = RequestInit { + url: url.clone(), + type_: RequestType::Script, + destination: Destination::Script, + // https://html.spec.whatwg.org/multipage/#create-a-potential-cors-request + // Step 1 + mode: match cors_setting { + Some(_) => RequestMode::CORSMode, + None => RequestMode::NoCORS, + }, + // https://html.spec.whatwg.org/multipage/#create-a-potential-cors-request + // Step 3-4 + credentials_mode: match cors_setting { + Some(CORSSettings::Anonymous) => CredentialsMode::CredentialsSameOrigin, + _ => CredentialsMode::Include, + }, + origin: doc.url().clone(), + pipeline_id: Some(script.global().r().pipeline_id()), + // FIXME: Set to true for now, discussion in https://github.com/whatwg/fetch/issues/381 + same_origin_data: true, + referrer_url: Some(doc.url().clone()), + referrer_policy: doc.get_referrer_policy(), + .. RequestInit::default() + }; + + // TODO: Step 3, Add custom steps to perform fetch + let context = Arc::new(Mutex::new(ScriptContext { elem: Trusted::new(script), character_encoding: character_encoding, @@ -229,14 +269,11 @@ fn fetch_a_classic_script(script: &HTMLScriptElement, script_chan: doc.window().networking_task_source(), wrapper: Some(doc.window().get_runnable_wrapper()), }; - let response_target = AsyncResponseTarget { - sender: action_sender, - }; + ROUTER.add_route(action_receiver.to_opaque(), box move |message| { - listener.notify_action(message.to().unwrap()); + listener.notify_fetch(message.to().unwrap()); }); - - doc.load_async(LoadType::Script(url), response_target, None); + doc.fetch_async(LoadType::Script(url), request, action_sender, None); } impl HTMLScriptElement { @@ -322,7 +359,13 @@ impl HTMLScriptElement { .and_then(|charset| encoding_from_whatwg_label(&charset.value())) .unwrap_or_else(|| doc.encoding()); - // TODO: Step 14: CORS. + // Step 14. + let cors_setting = match self.GetCrossOrigin() { + Some(ref s) if *s == "anonymous" => Some(CORSSettings::Anonymous), + Some(ref s) if *s == "use-credentials" => Some(CORSSettings::UseCredentials), + None => None, + _ => unreachable!() + }; // TODO: Step 15: Nonce. @@ -354,10 +397,11 @@ impl HTMLScriptElement { }; // Step 18.6. - fetch_a_classic_script(self, url, encoding); + fetch_a_classic_script(self, url, cors_setting, encoding); true }, + // TODO: Step 19. None => false, }; @@ -652,6 +696,32 @@ impl HTMLScriptElementMethods for HTMLScriptElement { // https://html.spec.whatwg.org/multipage/#dom-script-htmlfor make_setter!(SetHtmlFor, "for"); + // https://html.spec.whatwg.org/multipage/#dom-script-crossorigin + fn GetCrossOrigin(&self) -> Option<DOMString> { + let element = self.upcast::<Element>(); + let attr = element.get_attribute(&ns!(), &atom!("crossorigin")); + + if let Some(mut val) = attr.map(|v| v.Value()) { + val.make_ascii_lowercase(); + if val == "anonymous" || val == "use-credentials" { + return Some(val); + } + return Some(DOMString::from("anonymous")); + } + None + } + + // https://html.spec.whatwg.org/multipage/#dom-script-crossorigin + fn SetCrossOrigin(&self, value: Option<DOMString>) { + let element = self.upcast::<Element>(); + match value { + Some(val) => element.set_string_attribute(&atom!("crossorigin"), val), + None => { + element.remove_attribute(&ns!(), &atom!("crossorigin")); + } + } + } + // https://html.spec.whatwg.org/multipage/#dom-script-text fn Text(&self) -> DOMString { Node::collect_text_contents(self.upcast::<Node>().children()) diff --git a/components/script/dom/htmlselectelement.rs b/components/script/dom/htmlselectelement.rs index 222dff3e52c..6cf63c7e630 100644 --- a/components/script/dom/htmlselectelement.rs +++ b/components/script/dom/htmlselectelement.rs @@ -6,6 +6,7 @@ use dom::attr::Attr; use dom::bindings::codegen::Bindings::HTMLOptionElementBinding::HTMLOptionElementMethods; use dom::bindings::codegen::Bindings::HTMLSelectElementBinding; use dom::bindings::codegen::Bindings::HTMLSelectElementBinding::HTMLSelectElementMethods; +use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; use dom::bindings::codegen::UnionTypes::HTMLElementOrLong; use dom::bindings::codegen::UnionTypes::HTMLOptionElementOrHTMLOptGroupElement; use dom::bindings::inheritance::Castable; @@ -17,7 +18,7 @@ use dom::htmlelement::HTMLElement; use dom::htmlfieldsetelement::HTMLFieldSetElement; use dom::htmlformelement::{FormDatumValue, FormControl, FormDatum, HTMLFormElement}; use dom::htmloptionelement::HTMLOptionElement; -use dom::node::{Node, UnbindContext, window_from_node}; +use dom::node::{document_from_node, Node, UnbindContext, window_from_node}; use dom::nodelist::NodeList; use dom::validation::Validatable; use dom::validitystate::ValidityState; @@ -183,6 +184,50 @@ impl HTMLSelectElementMethods for HTMLSelectElement { fn Labels(&self) -> Root<NodeList> { self.upcast::<HTMLElement>().labels() } + + // https://html.spec.whatwg.org/multipage/#dom-select-length + fn SetLength(&self, value: u32) { + let length = self.Length(); + let node = self.upcast::<Node>(); + if value < length { // truncate the number of option elements + let mut iter = node.rev_children().take((length - value) as usize); + while let Some(child) = iter.next() { + if let Err(e) = node.RemoveChild(&child) { + warn!("Error removing child of HTMLSelectElement: {:?}", e); + } + } + } else if value > length { // add new blank option elements + let document = document_from_node(self); + for _ in 0..(value - length) { + let element = HTMLOptionElement::new(atom!("option"), None, &document.upcast()); + if let Err(e) = node.AppendChild(element.upcast()) { + warn!("error appending child of HTMLSelectElement: {:?}", e); + } + } + } + } + + // https://html.spec.whatwg.org/multipage/#dom-select-length + fn Length(&self) -> u32 { + self.upcast::<Node>() + .traverse_preorder() + .filter_map(Root::downcast::<HTMLOptionElement>) + .count() as u32 + } + + // https://html.spec.whatwg.org/multipage/#dom-select-item + fn Item(&self, index: u32) -> Option<Root<Element>> { + self.upcast::<Node>() + .traverse_preorder() + .filter_map(Root::downcast::<HTMLOptionElement>) + .nth(index as usize) + .map(|item| Root::from_ref(item.upcast())) + } + + // https://html.spec.whatwg.org/multipage/#dom-select-item + fn IndexedGetter(&self, index: u32) -> Option<Root<Element>> { + self.Item(index) + } } impl VirtualMethods for HTMLSelectElement { diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index 5f6b4fffbdc..24951c0c075 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -373,6 +373,8 @@ pub mod pluginarray; pub mod popstateevent; pub mod processinginstruction; pub mod progressevent; +pub mod promise; +pub mod promisenativehandler; 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..5fd1ef6f85e --- /dev/null +++ b/components/script/dom/promise.rs @@ -0,0 +1,288 @@ +/* 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/. */ + +//! Native representation of JS Promise values. +//! +//! This implementation differs from the traditional Rust DOM object, because the reflector +//! is provided by SpiderMonkey and has no knowledge of an associated native representation +//! (ie. dom::Promise). This means that native instances use native reference counting (Rc) +//! to ensure that no memory is leaked, which means that there can be multiple instances of +//! native Promise values that refer to the same JS value yet are distinct native objects +//! (ie. address equality for the native objects is meaningless). + +use dom::bindings::callback::CallbackContainer; +use dom::bindings::codegen::Bindings::PromiseBinding::AnyCallback; +use dom::bindings::conversions::root_from_object; +use dom::bindings::error::{Error, Fallible}; +use dom::bindings::global::GlobalRef; +use dom::bindings::js::MutHeapJSVal; +use dom::bindings::reflector::{Reflectable, MutReflectable, Reflector}; +use dom::promisenativehandler::PromiseNativeHandler; +use js::conversions::ToJSValConvertible; +use js::jsapi::{CallOriginalPromiseResolve, CallOriginalPromiseReject, CallOriginalPromiseThen}; +use js::jsapi::{JSAutoCompartment, CallArgs, JS_GetFunctionObject, JS_NewFunction}; +use js::jsapi::{JSContext, HandleValue, HandleObject, IsPromiseObject, GetFunctionNativeReserved}; +use js::jsapi::{JS_ClearPendingException, JSObject, AddRawValueRoot, RemoveRawValueRoot}; +use js::jsapi::{MutableHandleObject, NewPromiseObject, ResolvePromise, RejectPromise}; +use js::jsapi::{SetFunctionNativeReserved, NewFunctionWithReserved, AddPromiseReactions}; +use js::jsval::{JSVal, UndefinedValue, ObjectValue, Int32Value}; +use std::ptr; +use std::rc::Rc; + +#[dom_struct] +pub struct Promise { + reflector: Reflector, + /// Since Promise values are natively reference counted without the knowledge of + /// the SpiderMonkey GC, an explicit root for the reflector is stored while any + /// native instance exists. This ensures that the reflector will never be GCed + /// while native code could still interact with its native representation. + #[ignore_heap_size_of = "SM handles JS values"] + permanent_js_root: MutHeapJSVal, +} + +/// Private helper to enable adding new methods to Rc<Promise>. +trait PromiseHelper { + #[allow(unsafe_code)] + unsafe fn initialize(&self, cx: *mut JSContext); +} + +impl PromiseHelper for Rc<Promise> { + #[allow(unsafe_code)] + unsafe fn initialize(&self, cx: *mut JSContext) { + let obj = self.reflector().get_jsobject(); + self.permanent_js_root.set(ObjectValue(&**obj)); + assert!(AddRawValueRoot(cx, + self.permanent_js_root.get_unsafe(), + b"Promise::root\0" as *const _ as *const _)); + } +} + +impl Drop for Promise { + #[allow(unsafe_code)] + fn drop(&mut self) { + let cx = self.global().r().get_cx(); + unsafe { + RemoveRawValueRoot(cx, self.permanent_js_root.get_unsafe()); + } + } +} + +impl Promise { + #[allow(unsafe_code)] + pub fn new(global: GlobalRef) -> Rc<Promise> { + let cx = global.get_cx(); + rooted!(in(cx) let mut obj = ptr::null_mut()); + unsafe { + Promise::create_js_promise(cx, HandleObject::null(), obj.handle_mut()); + Promise::new_with_js_promise(obj.handle(), cx) + } + } + + #[allow(unsafe_code, unrooted_must_root)] + pub fn duplicate(&self) -> Rc<Promise> { + let cx = self.global().r().get_cx(); + unsafe { + Promise::new_with_js_promise(self.reflector().get_jsobject(), cx) + } + } + + #[allow(unsafe_code, unrooted_must_root)] + unsafe fn new_with_js_promise(obj: HandleObject, cx: *mut JSContext) -> Rc<Promise> { + assert!(IsPromiseObject(obj)); + let mut promise = Promise { + reflector: Reflector::new(), + permanent_js_root: MutHeapJSVal::new(), + }; + promise.init_reflector(obj.get()); + let promise = Rc::new(promise); + promise.initialize(cx); + promise + } + + #[allow(unsafe_code)] + unsafe fn create_js_promise(cx: *mut JSContext, proto: HandleObject, obj: MutableHandleObject) { + let do_nothing_func = JS_NewFunction(cx, Some(do_nothing_promise_executor), /* nargs = */ 2, + /* flags = */ 0, ptr::null()); + assert!(!do_nothing_func.is_null()); + rooted!(in(cx) let do_nothing_obj = JS_GetFunctionObject(do_nothing_func)); + assert!(!do_nothing_obj.is_null()); + obj.set(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()); + rooted!(in(cx) let p = unsafe { CallOriginalPromiseResolve(cx, value) }); + assert!(!p.handle().is_null()); + unsafe { + Ok(Promise::new_with_js_promise(p.handle(), cx)) + } + } + + #[allow(unrooted_must_root, unsafe_code)] + pub fn Reject(global: GlobalRef, + cx: *mut JSContext, + value: HandleValue) -> Fallible<Rc<Promise>> { + let _ac = JSAutoCompartment::new(cx, global.reflector().get_jsobject().get()); + rooted!(in(cx) let p = unsafe { CallOriginalPromiseReject(cx, value) }); + assert!(!p.handle().is_null()); + unsafe { + Ok(Promise::new_with_js_promise(p.handle(), cx)) + } + } + + #[allow(unsafe_code)] + pub fn resolve_native<T>(&self, cx: *mut JSContext, val: &T) where T: ToJSValConvertible { + rooted!(in(cx) let mut v = UndefinedValue()); + unsafe { + val.to_jsval(cx, v.handle_mut()); + } + self.resolve(cx, v.handle()); + } + + #[allow(unrooted_must_root, unsafe_code)] + pub fn resolve(&self, cx: *mut JSContext, value: HandleValue) { + unsafe { + if !ResolvePromise(cx, self.promise_obj(), value) { + JS_ClearPendingException(cx); + } + } + } + + #[allow(unsafe_code)] + pub fn reject_native<T>(&self, cx: *mut JSContext, val: &T) where T: ToJSValConvertible { + rooted!(in(cx) let mut v = UndefinedValue()); + unsafe { + val.to_jsval(cx, v.handle_mut()); + } + self.reject(cx, v.handle()); + } + + #[allow(unsafe_code)] + pub fn reject_error(&self, cx: *mut JSContext, error: Error) { + rooted!(in(cx) let mut v = UndefinedValue()); + unsafe { + error.to_jsval(cx, self.global().r(), v.handle_mut()); + } + self.reject(cx, v.handle()); + } + + #[allow(unrooted_must_root, unsafe_code)] + pub fn reject(&self, + cx: *mut JSContext, + value: HandleValue) { + unsafe { + if !RejectPromise(cx, self.promise_obj(), value) { + JS_ClearPendingException(cx); + } + } + } + + #[allow(unrooted_must_root, unsafe_code)] + pub fn then(&self, + cx: *mut JSContext, + _callee: HandleObject, + cb_resolve: AnyCallback, + cb_reject: AnyCallback, + result: MutableHandleObject) { + let promise = self.promise_obj(); + rooted!(in(cx) let resolve = cb_resolve.callback()); + rooted!(in(cx) let reject = cb_reject.callback()); + unsafe { + rooted!(in(cx) let res = + CallOriginalPromiseThen(cx, promise, resolve.handle(), reject.handle())); + result.set(*res); + } + } + + #[allow(unsafe_code)] + fn promise_obj(&self) -> HandleObject { + let obj = self.reflector().get_jsobject(); + unsafe { + assert!(IsPromiseObject(obj)); + } + obj + } + + #[allow(unsafe_code)] + pub fn append_native_handler(&self, handler: &PromiseNativeHandler) { + let global = self.global(); + let cx = global.r().get_cx(); + rooted!(in(cx) let resolve_func = + create_native_handler_function(cx, + handler.reflector().get_jsobject(), + NativeHandlerTask::Resolve)); + + rooted!(in(cx) let reject_func = + create_native_handler_function(cx, + handler.reflector().get_jsobject(), + NativeHandlerTask::Reject)); + + unsafe { + let ok = AddPromiseReactions(cx, + self.promise_obj(), + resolve_func.handle(), + reject_func.handle()); + assert!(ok); + } + } +} + +#[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 +} + +const SLOT_NATIVEHANDLER: usize = 0; +const SLOT_NATIVEHANDLER_TASK: usize = 1; + +#[derive(PartialEq)] +enum NativeHandlerTask { + Resolve = 0, + Reject = 1, +} + +#[allow(unsafe_code)] +unsafe extern fn native_handler_callback(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool { + let args = CallArgs::from_vp(vp, argc); + rooted!(in(cx) let v = *GetFunctionNativeReserved(args.callee(), SLOT_NATIVEHANDLER)); + assert!(v.get().is_object()); + + let handler = root_from_object::<PromiseNativeHandler>(v.to_object()) + .ok().expect("unexpected value for native handler in promise native handler callback"); + + rooted!(in(cx) let v = *GetFunctionNativeReserved(args.callee(), SLOT_NATIVEHANDLER_TASK)); + match v.to_int32() { + v if v == NativeHandlerTask::Resolve as i32 => handler.resolved_callback(cx, args.get(0)), + v if v == NativeHandlerTask::Reject as i32 => handler.rejected_callback(cx, args.get(0)), + _ => panic!("unexpected native handler task value"), + }; + + true +} + +#[allow(unsafe_code)] +fn create_native_handler_function(cx: *mut JSContext, + holder: HandleObject, + task: NativeHandlerTask) -> *mut JSObject { + unsafe { + let func = NewFunctionWithReserved(cx, Some(native_handler_callback), 1, 0, ptr::null()); + assert!(!func.is_null()); + + rooted!(in(cx) let obj = JS_GetFunctionObject(func)); + assert!(!obj.is_null()); + SetFunctionNativeReserved(obj.get(), + SLOT_NATIVEHANDLER, + &ObjectValue(&**holder)); + SetFunctionNativeReserved(obj.get(), + SLOT_NATIVEHANDLER_TASK, + &Int32Value(task as i32)); + obj.get() + } +} diff --git a/components/script/dom/promisenativehandler.rs b/components/script/dom/promisenativehandler.rs new file mode 100644 index 00000000000..603122556ef --- /dev/null +++ b/components/script/dom/promisenativehandler.rs @@ -0,0 +1,49 @@ +/* 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::codegen::Bindings::PromiseNativeHandlerBinding; +use dom::bindings::global::GlobalRef; +use dom::bindings::js::Root; +use dom::bindings::reflector::{Reflector, reflect_dom_object}; +use dom::bindings::trace::JSTraceable; +use heapsize::HeapSizeOf; +use js::jsapi::{JSContext, HandleValue}; + +pub trait Callback: JSTraceable + HeapSizeOf { + fn callback(&self, cx: *mut JSContext, v: HandleValue); +} + +#[dom_struct] +pub struct PromiseNativeHandler { + reflector: Reflector, + resolve: Option<Box<Callback>>, + reject: Option<Box<Callback>>, +} + +impl PromiseNativeHandler { + pub fn new(global: GlobalRef, + resolve: Option<Box<Callback>>, + reject: Option<Box<Callback>>) + -> Root<PromiseNativeHandler> { + reflect_dom_object(box PromiseNativeHandler { + reflector: Reflector::new(), + resolve: resolve, + reject: reject, + }, global, PromiseNativeHandlerBinding::Wrap) + } + + fn callback(callback: &Option<Box<Callback>>, cx: *mut JSContext, v: HandleValue) { + if let Some(ref callback) = *callback { + callback.callback(cx, v) + } + } + + pub fn resolved_callback(&self, cx: *mut JSContext, v: HandleValue) { + PromiseNativeHandler::callback(&self.resolve, cx, v) + } + + pub fn rejected_callback(&self, cx: *mut JSContext, v: HandleValue) { + PromiseNativeHandler::callback(&self.reject, cx, v) + } +} diff --git a/components/script/dom/request.rs b/components/script/dom/request.rs index e8eb62c3f05..149c61c96ee 100644 --- a/components/script/dom/request.rs +++ b/components/script/dom/request.rs @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use dom::bindings::cell::DOMRefCell; +use dom::bindings::codegen::Bindings::HeadersBinding::HeadersInit; use dom::bindings::codegen::Bindings::RequestBinding; use dom::bindings::codegen::Bindings::RequestBinding::ReferrerPolicy; use dom::bindings::codegen::Bindings::RequestBinding::RequestCache; @@ -14,7 +15,6 @@ use dom::bindings::codegen::Bindings::RequestBinding::RequestMethods; use dom::bindings::codegen::Bindings::RequestBinding::RequestMode; use dom::bindings::codegen::Bindings::RequestBinding::RequestRedirect; use dom::bindings::codegen::Bindings::RequestBinding::RequestType; -use dom::bindings::codegen::UnionTypes::HeadersOrByteStringSequenceSequence; use dom::bindings::error::{Error, Fallible}; use dom::bindings::global::GlobalRef; use dom::bindings::js::{JS, MutNullableHeap, Root}; @@ -312,7 +312,7 @@ impl Request { // Step 28 if let Some(possible_header) = init.headers.as_ref() { - if let &HeadersOrByteStringSequenceSequence::Headers(ref init_headers) = possible_header { + if let &HeadersInit::Headers(ref init_headers) = possible_header { headers_copy = init_headers.clone(); } } @@ -337,7 +337,7 @@ impl Request { } // Step 31 - try!(r.Headers().fill(Some(HeadersOrByteStringSequenceSequence::Headers(headers_copy)))); + try!(r.Headers().fill(Some(HeadersInit::Headers(headers_copy)))); // Step 32 let input_body = if let RequestInfo::Request(ref input_request) = input { @@ -796,13 +796,15 @@ impl Into<RequestRedirect> for NetTraitsRequestRedirect { } } -impl Clone for HeadersOrByteStringSequenceSequence { - fn clone(&self) -> HeadersOrByteStringSequenceSequence { +impl Clone for HeadersInit { + fn clone(&self) -> HeadersInit { match self { - &HeadersOrByteStringSequenceSequence::Headers(ref h) => - HeadersOrByteStringSequenceSequence::Headers(h.clone()), - &HeadersOrByteStringSequenceSequence::ByteStringSequenceSequence(ref b) => - HeadersOrByteStringSequenceSequence::ByteStringSequenceSequence(b.clone()), + &HeadersInit::Headers(ref h) => + HeadersInit::Headers(h.clone()), + &HeadersInit::ByteStringSequenceSequence(ref b) => + HeadersInit::ByteStringSequenceSequence(b.clone()), + &HeadersInit::ByteStringMozMap(ref m) => + HeadersInit::ByteStringMozMap(m.clone()), } } } diff --git a/components/script/dom/testbinding.rs b/components/script/dom/testbinding.rs index 566ff2d9375..6f7d7baf770 100644 --- a/components/script/dom/testbinding.rs +++ b/components/script/dom/testbinding.rs @@ -5,9 +5,10 @@ // check-tidy: no specs after this line use core::nonzero::NonZero; +use dom::bindings::callback::ExceptionHandling; use dom::bindings::codegen::Bindings::EventListenerBinding::EventListener; use dom::bindings::codegen::Bindings::FunctionBinding::Function; -use dom::bindings::codegen::Bindings::TestBindingBinding; +use dom::bindings::codegen::Bindings::TestBindingBinding::{self, SimpleCallback}; use dom::bindings::codegen::Bindings::TestBindingBinding::{TestBindingMethods, TestDictionary}; use dom::bindings::codegen::Bindings::TestBindingBinding::{TestDictionaryDefaults, TestEnum}; use dom::bindings::codegen::UnionTypes; @@ -19,22 +20,27 @@ use dom::bindings::codegen::UnionTypes::{EventOrUSVString, HTMLElementOrLong, Lo use dom::bindings::codegen::UnionTypes::{HTMLElementOrUnsignedLongOrStringOrBoolean, LongSequenceOrBoolean}; use dom::bindings::codegen::UnionTypes::{StringOrLongSequence, StringOrStringSequence, StringSequenceOrUnsignedLong}; use dom::bindings::codegen::UnionTypes::{StringOrUnsignedLong, StringOrBoolean, UnsignedLongOrBoolean}; -use dom::bindings::error::Fallible; -use dom::bindings::global::GlobalRef; +use dom::bindings::error::{Error, Fallible}; +use dom::bindings::global::{GlobalRef, global_root_from_context}; use dom::bindings::js::Root; use dom::bindings::mozmap::MozMap; use dom::bindings::num::Finite; +use dom::bindings::refcounted::TrustedPromise; 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::promisenativehandler::{PromiseNativeHandler, Callback}; use dom::url::URL; -use js::jsapi::{HandleObject, HandleValue, JSContext, JSObject}; +use js::jsapi::{HandleObject, HandleValue, JSContext, JSObject, JSAutoCompartment}; use js::jsapi::{JS_NewPlainObject, JS_NewUint8ClampedArray}; use js::jsval::{JSVal, NullValue}; +use script_traits::MsDuration; use std::borrow::ToOwned; use std::ptr; use std::rc::Rc; +use timers::OneshotTimerCallback; use util::prefs::PREFS; #[dom_struct] @@ -647,6 +653,81 @@ impl TestBindingMethods for TestBinding { fn ReceiveMozMapOfMozMaps(&self) -> MozMap<MozMap<i32>> { MozMap::new() } fn ReceiveAnyMozMap(&self) -> MozMap<JSVal> { MozMap::new() } + #[allow(unrooted_must_root)] + fn ReturnResolvedPromise(&self, cx: *mut JSContext, v: HandleValue) -> Fallible<Rc<Promise>> { + Promise::Resolve(self.global().r(), cx, v) + } + + #[allow(unrooted_must_root)] + fn ReturnRejectedPromise(&self, cx: *mut JSContext, v: HandleValue) -> Fallible<Rc<Promise>> { + Promise::Reject(self.global().r(), cx, v) + } + + fn PromiseResolveNative(&self, cx: *mut JSContext, p: &Promise, v: HandleValue) { + p.resolve(cx, v); + } + + fn PromiseRejectNative(&self, cx: *mut JSContext, p: &Promise, v: HandleValue) { + p.reject(cx, v); + } + + fn PromiseRejectWithTypeError(&self, p: &Promise, s: USVString) { + p.reject_error(self.global().r().get_cx(), Error::Type(s.0)); + } + + #[allow(unrooted_must_root)] + fn ResolvePromiseDelayed(&self, p: &Promise, value: DOMString, delay: u64) { + let promise = p.duplicate(); + let cb = TestBindingCallback { + promise: TrustedPromise::new(promise), + value: value, + }; + let _ = self.global().r().schedule_callback(OneshotTimerCallback::TestBindingCallback(cb), + MsDuration::new(delay)); + } + + #[allow(unrooted_must_root)] + fn PromiseNativeHandler(&self, + resolve: Option<Rc<SimpleCallback>>, + reject: Option<Rc<SimpleCallback>>) -> Rc<Promise> { + let global = self.global(); + let handler = PromiseNativeHandler::new(global.r(), + resolve.map(SimpleHandler::new), + reject.map(SimpleHandler::new)); + let p = Promise::new(global.r()); + p.append_native_handler(&handler); + return p; + + #[derive(JSTraceable, HeapSizeOf)] + struct SimpleHandler { + #[ignore_heap_size_of = "Rc has unclear ownership semantics"] + handler: Rc<SimpleCallback>, + } + impl SimpleHandler { + fn new(callback: Rc<SimpleCallback>) -> Box<Callback> { + box SimpleHandler { handler: callback } + } + } + impl Callback for SimpleHandler { + #[allow(unsafe_code)] + fn callback(&self, cx: *mut JSContext, v: HandleValue) { + let global = unsafe { global_root_from_context(cx) }; + let _ = self.handler.Call_(&global.r(), v, ExceptionHandling::Report); + } + } + } + + #[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) { @@ -693,3 +774,20 @@ impl TestBinding { pub unsafe fn condition_satisfied(_: *mut JSContext, _: HandleObject) -> bool { true } pub unsafe fn condition_unsatisfied(_: *mut JSContext, _: HandleObject) -> bool { false } } + +#[derive(JSTraceable, HeapSizeOf)] +pub struct TestBindingCallback { + #[ignore_heap_size_of = "unclear ownership semantics"] + promise: TrustedPromise, + value: DOMString, +} + +impl TestBindingCallback { + #[allow(unrooted_must_root)] + pub fn invoke(self) { + let p = self.promise.root(); + let cx = p.global().r().get_cx(); + let _ac = JSAutoCompartment::new(cx, p.reflector().get_jsobject().get()); + p.resolve_native(cx, &self.value); + } +} diff --git a/components/script/dom/webglrenderingcontext.rs b/components/script/dom/webglrenderingcontext.rs index f42eeab4dbf..d1342190f73 100644 --- a/components/script/dom/webglrenderingcontext.rs +++ b/components/script/dom/webglrenderingcontext.rs @@ -511,6 +511,19 @@ impl WebGLRenderingContext { .send(CanvasMsg::WebGL(msg)) .unwrap() } + + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14 + fn validate_feature_enum(&self, cap: u32) -> bool { + match cap { + constants::BLEND | constants::CULL_FACE | constants::DEPTH_TEST | constants::DITHER | + constants::POLYGON_OFFSET_FILL | constants::SAMPLE_ALPHA_TO_COVERAGE | constants::SAMPLE_COVERAGE | + constants::SAMPLE_COVERAGE_INVERT | constants::SCISSOR_TEST => true, + _ => { + self.webgl_error(InvalidEnum); + false + }, + } + } } impl Drop for WebGLRenderingContext { @@ -1114,27 +1127,19 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 fn Enable(&self, cap: u32) { - match cap { - constants::BLEND | constants::CULL_FACE | constants::DEPTH_TEST | constants::DITHER | - constants::POLYGON_OFFSET_FILL | constants::SAMPLE_ALPHA_TO_COVERAGE | constants::SAMPLE_COVERAGE | - constants::SAMPLE_COVERAGE_INVERT | constants::SCISSOR_TEST => - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::Enable(cap))) - .unwrap(), - _ => self.webgl_error(InvalidEnum), + if self.validate_feature_enum(cap) { + self.ipc_renderer + .send(CanvasMsg::WebGL(WebGLCommand::Enable(cap))) + .unwrap(); } } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 fn Disable(&self, cap: u32) { - match cap { - constants::BLEND | constants::CULL_FACE | constants::DEPTH_TEST | constants::DITHER | - constants::POLYGON_OFFSET_FILL | constants::SAMPLE_ALPHA_TO_COVERAGE | constants::SAMPLE_COVERAGE | - constants::SAMPLE_COVERAGE_INVERT | constants::SCISSOR_TEST => - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::Disable(cap))) - .unwrap(), - _ => self.webgl_error(InvalidEnum), + if self.validate_feature_enum(cap) { + self.ipc_renderer + .send(CanvasMsg::WebGL(WebGLCommand::Disable(cap))) + .unwrap() } } @@ -1455,6 +1460,20 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { buffer.map_or(false, |buf| buf.target().is_some() && !buf.is_deleted()) } + // TODO: We could write this without IPC, recording the calls to `enable` and `disable`. + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn IsEnabled(&self, cap: u32) -> bool { + if self.validate_feature_enum(cap) { + let (sender, receiver) = ipc::channel().unwrap(); + self.ipc_renderer + .send(CanvasMsg::WebGL(WebGLCommand::IsEnabled(cap, sender))) + .unwrap(); + return receiver.recv().unwrap(); + } + + false + } + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6 fn IsFramebuffer(&self, frame_buffer: Option<&WebGLFramebuffer>) -> bool { frame_buffer.map_or(false, |buf| buf.target().is_some() && !buf.is_deleted()) diff --git a/components/script/dom/webidls/HTMLScriptElement.webidl b/components/script/dom/webidls/HTMLScriptElement.webidl index 17df2e7e08b..1636df7a12b 100644 --- a/components/script/dom/webidls/HTMLScriptElement.webidl +++ b/components/script/dom/webidls/HTMLScriptElement.webidl @@ -10,7 +10,7 @@ interface HTMLScriptElement : HTMLElement { attribute DOMString charset; // attribute boolean async; attribute boolean defer; - // attribute DOMString crossOrigin; + attribute DOMString? crossOrigin; [Pure] attribute DOMString text; diff --git a/components/script/dom/webidls/HTMLSelectElement.webidl b/components/script/dom/webidls/HTMLSelectElement.webidl index ba84d183a72..b513806f965 100644 --- a/components/script/dom/webidls/HTMLSelectElement.webidl +++ b/components/script/dom/webidls/HTMLSelectElement.webidl @@ -15,8 +15,8 @@ interface HTMLSelectElement : HTMLElement { readonly attribute DOMString type; //readonly attribute HTMLOptionsCollection options; - // attribute unsigned long length; - //getter Element? item(unsigned long index); + attribute unsigned long length; + getter Element? item(unsigned long index); //HTMLOptionElement? namedItem(DOMString name); // Note: this function currently only exists for union.html. void add((HTMLOptionElement or HTMLOptGroupElement) element, optional (HTMLElement or long)? before = null); diff --git a/components/script/dom/webidls/Headers.webidl b/components/script/dom/webidls/Headers.webidl index ee3d4b2fe94..5ae08ad2bd7 100644 --- a/components/script/dom/webidls/Headers.webidl +++ b/components/script/dom/webidls/Headers.webidl @@ -4,8 +4,7 @@ // https://fetch.spec.whatwg.org/#headers-class -// TODO support OpenEndedDictionary<ByteString> -typedef (Headers or sequence<sequence<ByteString>>) HeadersInit; +typedef (Headers or sequence<sequence<ByteString>> or MozMap<ByteString>) HeadersInit; [Constructor(optional HeadersInit init), Exposed=(Window,Worker)] diff --git a/components/script/dom/webidls/Promise.webidl b/components/script/dom/webidls/Promise.webidl new file mode 100644 index 00000000000..5897ff422f9 --- /dev/null +++ b/components/script/dom/webidls/Promise.webidl @@ -0,0 +1,16 @@ +/* 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. + +callback PromiseJobCallback = void(); + +[TreatNonCallableAsNull] +callback AnyCallback = any (any value); + +[NoInterfaceObject, Exposed=(Window,Worker)] +// Need to escape "Promise" so it's treated as an identifier. +interface _Promise { +}; diff --git a/components/script/dom/webidls/PromiseNativeHandler.webidl b/components/script/dom/webidls/PromiseNativeHandler.webidl new file mode 100644 index 00000000000..caa1692dfd3 --- /dev/null +++ b/components/script/dom/webidls/PromiseNativeHandler.webidl @@ -0,0 +1,13 @@ +/* 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. + +// Hack to allow us to have JS owning and properly tracing/CCing/etc a +// PromiseNativeHandler. +[NoInterfaceObject, + Exposed=(Window,Worker)] +interface PromiseNativeHandler { +}; diff --git a/components/script/dom/webidls/TestBinding.webidl b/components/script/dom/webidls/TestBinding.webidl index e35b914d08e..c3274057a40 100644 --- a/components/script/dom/webidls/TestBinding.webidl +++ b/components/script/dom/webidls/TestBinding.webidl @@ -508,9 +508,24 @@ interface TestBinding { [Func="TestBinding::condition_satisfied"] const unsigned short funcControlledConstEnabled = 0; + [Throws] + Promise<any> returnResolvedPromise(any value); + [Throws] + Promise<any> returnRejectedPromise(any value); + readonly attribute Promise<boolean> promiseAttribute; + void acceptPromise(Promise<DOMString> string); + void acceptNullablePromise(Promise<DOMString>? string); + Promise<any> promiseNativeHandler(SimpleCallback? resolve, SimpleCallback? reject); + void promiseResolveNative(Promise<any> p, any value); + void promiseRejectNative(Promise<any> p, any value); + void promiseRejectWithTypeError(Promise<any> p, USVString message); + void resolvePromiseDelayed(Promise<any> p, DOMString value, unsigned long long ms); + void panic(); }; +callback SimpleCallback = void(any value); + partial interface TestBinding { [Pref="dom.testable_crash.enabled"] void crashHard(); diff --git a/components/script/dom/webidls/WebGLRenderingContext.webidl b/components/script/dom/webidls/WebGLRenderingContext.webidl index e95ce00e5e5..06727b3a536 100644 --- a/components/script/dom/webidls/WebGLRenderingContext.webidl +++ b/components/script/dom/webidls/WebGLRenderingContext.webidl @@ -603,7 +603,7 @@ interface WebGLRenderingContextBase void hint(GLenum target, GLenum mode); [WebGLHandlesContextLoss] GLboolean isBuffer(WebGLBuffer? buffer); - //[WebGLHandlesContextLoss] GLboolean isEnabled(GLenum cap); + [WebGLHandlesContextLoss] GLboolean isEnabled(GLenum cap); [WebGLHandlesContextLoss] GLboolean isFramebuffer(WebGLFramebuffer? framebuffer); [WebGLHandlesContextLoss] GLboolean isProgram(WebGLProgram? program); [WebGLHandlesContextLoss] GLboolean isRenderbuffer(WebGLRenderbuffer? renderbuffer); diff --git a/components/script/dom/workerglobalscope.rs b/components/script/dom/workerglobalscope.rs index bb9a9c3f4ab..2520f57dbac 100644 --- a/components/script/dom/workerglobalscope.rs +++ b/components/script/dom/workerglobalscope.rs @@ -7,9 +7,10 @@ use dom::bindings::codegen::Bindings::EventHandlerBinding::OnErrorEventHandlerNo use dom::bindings::codegen::Bindings::FunctionBinding::Function; use dom::bindings::codegen::Bindings::WorkerGlobalScopeBinding::WorkerGlobalScopeMethods; use dom::bindings::error::{Error, ErrorResult, Fallible, report_pending_exception, ErrorInfo}; -use dom::bindings::global::GlobalRef; +use dom::bindings::global::{GlobalRef, GlobalRoot}; use dom::bindings::inheritance::Castable; use dom::bindings::js::{JS, MutNullableHeap, Root}; +use dom::bindings::refcounted::Trusted; use dom::bindings::reflector::Reflectable; use dom::bindings::str::DOMString; use dom::console::TimerSet; @@ -29,7 +30,8 @@ use net_traits::{IpcSend, LoadOrigin}; use net_traits::{LoadContext, ResourceThreads, load_whole_resource}; use profile_traits::{mem, time}; use script_runtime::{CommonScriptMsg, ScriptChan, ScriptPort, maybe_take_panic_result}; -use script_thread::RunnableWrapper; +use script_runtime::{ScriptThreadEventCategory, PromiseJobQueue, EnqueuedPromiseCallback}; +use script_thread::{Runnable, RunnableWrapper}; use script_traits::{MsDuration, TimerEvent, TimerEventId, TimerEventRequest, TimerSource}; use script_traits::ScriptMsg as ConstellationMsg; use script_traits::WorkerGlobalScopeInit; @@ -112,6 +114,8 @@ pub struct WorkerGlobalScope { /// Timers used by the Console API. console_timers: TimerSet, + + promise_job_queue: PromiseJobQueue, } impl WorkerGlobalScope { @@ -143,6 +147,7 @@ impl WorkerGlobalScope { constellation_chan: init.constellation_chan, scheduler_chan: init.scheduler_chan, console_timers: TimerSet::new(), + promise_job_queue: PromiseJobQueue::new(), } } @@ -228,6 +233,25 @@ impl WorkerGlobalScope { cancelled: self.closing.clone().unwrap(), } } + + pub fn enqueue_promise_job(&self, job: EnqueuedPromiseCallback) { + self.promise_job_queue.enqueue(job, GlobalRef::Worker(self)); + } + + pub fn flush_promise_jobs(&self) { + self.script_chan().send(CommonScriptMsg::RunnableMsg( + ScriptThreadEventCategory::WorkerEvent, + box FlushPromiseJobs { + global: Trusted::new(self), + })).unwrap(); + } + + fn do_flush_promise_jobs(&self) { + self.promise_job_queue.flush_promise_jobs(|id| { + assert_eq!(self.pipeline_id(), id); + Some(GlobalRoot::Worker(Root::from_ref(self))) + }); + } } impl LoadOrigin for WorkerGlobalScope { @@ -466,3 +490,14 @@ impl WorkerGlobalScope { .report_an_error(error_info, value); } } + +struct FlushPromiseJobs { + global: Trusted<WorkerGlobalScope>, +} + +impl Runnable for FlushPromiseJobs { + fn handler(self: Box<FlushPromiseJobs>) { + let global = self.global.root(); + global.do_flush_promise_jobs(); + } +} diff --git a/components/script/dom/xmlhttprequest.rs b/components/script/dom/xmlhttprequest.rs index d034b60aebd..24ee2c28d36 100644 --- a/components/script/dom/xmlhttprequest.rs +++ b/components/script/dom/xmlhttprequest.rs @@ -45,8 +45,8 @@ use js::jsapi::{JSContext, JS_ParseJSON}; use js::jsapi::JS_ClearPendingException; use js::jsval::{JSVal, NullValue, UndefinedValue}; use msg::constellation_msg::{PipelineId, ReferrerPolicy}; -use net_traits::{CoreResourceThread, LoadOrigin}; -use net_traits::{FetchResponseListener, Metadata, NetworkError}; +use net_traits::{CoreResourceThread, FetchMetadata, FilteredMetadata}; +use net_traits::{FetchResponseListener, LoadOrigin, NetworkError}; use net_traits::CoreResourceMsg::Fetch; use net_traits::request::{CredentialsMode, Destination, RequestInit, RequestMode}; use net_traits::trim_http_whitespace; @@ -219,35 +219,39 @@ impl XMLHttpRequest { core_resource_thread: CoreResourceThread, init: RequestInit) { impl FetchResponseListener for XHRContext { - fn process_request_body(&mut self) { - // todo - } - fn process_request_eof(&mut self) { - // todo - } - fn process_response(&mut self, metadata: Result<Metadata, NetworkError>) { - let xhr = self.xhr.root(); - let rv = xhr.process_headers_available(self.gen_id, - metadata); - if rv.is_err() { - *self.sync_status.borrow_mut() = Some(rv); - } - } - fn process_response_chunk(&mut self, mut chunk: Vec<u8>) { - self.buf.borrow_mut().append(&mut chunk); - self.xhr.root().process_data_available(self.gen_id, self.buf.borrow().clone()); - } - fn process_response_eof(&mut self, response: Result<(), NetworkError>) { - let rv = match response { - Ok(()) => { - self.xhr.root().process_response_complete(self.gen_id, Ok(())) - } - Err(e) => { - self.xhr.root().process_response_complete(self.gen_id, Err(e)) - } - }; + fn process_request_body(&mut self) { + // todo + } + + fn process_request_eof(&mut self) { + // todo + } + + fn process_response(&mut self, + metadata: Result<FetchMetadata, NetworkError>) { + let xhr = self.xhr.root(); + let rv = xhr.process_headers_available(self.gen_id, metadata); + if rv.is_err() { *self.sync_status.borrow_mut() = Some(rv); } + } + + fn process_response_chunk(&mut self, mut chunk: Vec<u8>) { + self.buf.borrow_mut().append(&mut chunk); + self.xhr.root().process_data_available(self.gen_id, self.buf.borrow().clone()); + } + + fn process_response_eof(&mut self, response: Result<(), NetworkError>) { + let rv = match response { + Ok(()) => { + self.xhr.root().process_response_complete(self.gen_id, Ok(())) + } + Err(e) => { + self.xhr.root().process_response_complete(self.gen_id, Err(e)) + } + }; + *self.sync_status.borrow_mut() = Some(rv); + } } impl PreInvoke for XHRContext { @@ -273,9 +277,11 @@ impl LoadOrigin for XMLHttpRequest { fn referrer_url(&self) -> Option<Url> { return self.referrer_url.clone(); } + fn referrer_policy(&self) -> Option<ReferrerPolicy> { return self.referrer_policy; } + fn pipeline_id(&self) -> Option<PipelineId> { let global = self.global(); Some(global.r().pipeline_id()) @@ -596,6 +602,7 @@ impl XMLHttpRequestMethods for XMLHttpRequest { referrer_url: self.referrer_url.clone(), referrer_policy: self.referrer_policy.clone(), pipeline_id: self.pipeline_id(), + .. RequestInit::default() }; if bypass_cross_origin_check { @@ -862,10 +869,16 @@ impl XMLHttpRequest { } fn process_headers_available(&self, - gen_id: GenerationId, metadata: Result<Metadata, NetworkError>) + gen_id: GenerationId, metadata: Result<FetchMetadata, NetworkError>) -> Result<(), Error> { let metadata = match metadata { - Ok(meta) => meta, + Ok(meta) => match meta { + FetchMetadata::Unfiltered(m) => m, + FetchMetadata::Filtered { filtered, .. } => match filtered { + FilteredMetadata::Opaque => return Err(Error::Network), + FilteredMetadata::Transparent(m) => m + } + }, Err(_) => { self.process_partial_response(XHRProgress::Errored(gen_id, Error::Network)); return Err(Error::Network); |