Implement unhandledrejection event

This commit is contained in:
CYBAI 2018-05-07 20:36:18 +08:00
parent 8b28921136
commit 924a78c6c6
12 changed files with 342 additions and 20 deletions

14
Cargo.lock generated
View file

@ -2026,7 +2026,7 @@ dependencies = [
"hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper_serde 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"keyboard-types 0.4.2-servo (registry+https://github.com/rust-lang/crates.io-index)",
"mozjs 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
"mozjs 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.20.0",
"serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_bytes 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2227,20 +2227,20 @@ dependencies = [
[[package]]
name = "mozjs"
version = "0.9.2"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cc 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
"mozjs_sys 0.61.0 (registry+https://github.com/rust-lang/crates.io-index)",
"mozjs_sys 0.61.1 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "mozjs_sys"
version = "0.61.0"
version = "0.61.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bindgen 0.39.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -3004,7 +3004,7 @@ dependencies = [
"mime_guess 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"mitochondria 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"mozangle 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"mozjs 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
"mozjs 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
"msg 0.0.1",
"net_traits 0.0.1",
"num-traits 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
@ -4505,8 +4505,8 @@ dependencies = [
"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919"
"checksum mitochondria 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9de3eca27871df31c33b807f834b94ef7d000956f57aa25c5aed9c5f0aae8f6f"
"checksum mozangle 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "45a8a18a41cfab0fde25cc2f43ea89064d211a0fbb33225b8ff93ab20406e0e7"
"checksum mozjs 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b9f85c1120b07d7a2acc9d1d62df1fe16f64162399448fb5307bf2bc3bd066c9"
"checksum mozjs_sys 0.61.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1ff07b0f0a2371dc08d75d55371ca311be67e1fdfa6c146fc8ad154c340f70c9"
"checksum mozjs 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bd0bdded611cb03c8ca638e0b663befe67eb7fbcb1fe2dfd25061656ee4ff365"
"checksum mozjs_sys 0.61.1 (registry+https://github.com/rust-lang/crates.io-index)" = "070dd9dcd0be8b524fe4f6fcc791d56d630518652f98c2b152fdebe28eb71a5b"
"checksum msdos_time 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "aad9dfe950c057b1bfe9c1f2aa51583a8468ef2a5baba2ebbe06d775efeb7729"
"checksum muldiv 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "451a9a05d2a32c566c897835e0ea95cf79ed2fdfe957924045a1721a36c9980f"
"checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88"

View file

@ -86,6 +86,7 @@ time
timeupdate
toggle
transitionend
unhandledrejection
unload
url
waiting

View file

@ -32,7 +32,7 @@ hashglobe = { path = "../hashglobe" }
hyper = { version = "0.10", optional = true }
hyper_serde = { version = "0.8", optional = true }
keyboard-types = {version = "0.4.2-servo", features = ["serde"], optional = true}
mozjs = { version = "0.9.0", optional = true }
mozjs = { version = "0.9.3", optional = true }
selectors = { path = "../selectors" }
serde = { version = "1.0.27", optional = true }
serde_bytes = { version = "0.10", optional = true }

View file

@ -68,7 +68,7 @@ metrics = {path = "../metrics"}
mitochondria = "1.1.2"
mime = "0.2.1"
mime_guess = "1.8.0"
mozjs = "0.9.0"
mozjs = "0.9.3"
msg = {path = "../msg"}
net_traits = {path = "../net_traits"}
num-traits = "0.2"

View file

@ -30,6 +30,7 @@ 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::{Heap, HandleObject};
use js::jsapi::{JSAutoCompartment, JSContext};
use js::jsapi::JSObject;
use js::panic::maybe_resume_unwind;
@ -135,6 +136,23 @@ pub struct GlobalScope {
/// Vector storing references of all eventsources.
event_source_tracker: DOMTracker<EventSource>,
/// Storage for watching rejected promises waiting for some client to
/// consume their rejection.
/// Promises in this list have been rejected in the last turn of the
/// event loop without the rejection being handled.
/// Note that this can contain nullptrs in place of promises removed because
/// they're consumed before it'd be reported.
///
/// <https://html.spec.whatwg.org/multipage/#about-to-be-notified-rejected-promises-list>
uncaught_rejections: DomRefCell<Vec<Box<Heap<*mut JSObject>>>>,
/// Promises in this list have previously been reported as rejected
/// (because they were in the above list), but the rejection was handled
/// in the last turn of the event loop.
///
/// <https://html.spec.whatwg.org/multipage/#outstanding-rejected-promises-weak-set>
consumed_rejections: DomRefCell<Vec<Box<Heap<*mut JSObject>>>>,
}
impl GlobalScope {
@ -169,6 +187,8 @@ impl GlobalScope {
microtask_queue,
list_auto_close_worker: Default::default(),
event_source_tracker: DOMTracker::new(),
uncaught_rejections: Default::default(),
consumed_rejections: Default::default(),
}
}
@ -230,6 +250,39 @@ impl GlobalScope {
GlobalScope::from_object(obj)
}
pub fn add_uncaught_rejection(&self, rejection: HandleObject) {
self.uncaught_rejections.borrow_mut().push(Heap::boxed(rejection.get()));
}
pub fn remove_uncaught_rejection(&self, rejection: HandleObject) {
let mut uncaught_rejections = self.uncaught_rejections.borrow_mut();
if let Some(index) = uncaught_rejections.iter().position(|promise| *promise == Heap::boxed(rejection.get())) {
uncaught_rejections.remove(index);
}
}
pub fn get_uncaught_rejections(&self) -> &DomRefCell<Vec<Box<Heap<*mut JSObject>>>> {
&self.uncaught_rejections
}
pub fn add_consumed_rejection(&self, rejection: HandleObject) {
self.consumed_rejections.borrow_mut().push(Heap::boxed(rejection.get()));
}
pub fn remove_consumed_rejection(&self, rejection: HandleObject) {
let mut consumed_rejections = self.consumed_rejections.borrow_mut();
if let Some(index) = consumed_rejections.iter().position(|promise| *promise == Heap::boxed(rejection.get())) {
consumed_rejections.remove(index);
}
}
pub fn get_consumed_rejections(&self) -> &DomRefCell<Vec<Box<Heap<*mut JSObject>>>> {
&self.consumed_rejections
}
#[allow(unsafe_code)]
pub fn get_cx(&self) -> *mut JSContext {
Runtime::get()
}
@ -586,7 +639,10 @@ impl GlobalScope {
/// Perform a microtask checkpoint.
pub fn perform_a_microtask_checkpoint(&self) {
self.microtask_queue
.checkpoint(|_| Some(DomRoot::from_ref(self)));
.checkpoint(
|_| Some(DomRoot::from_ref(self)),
vec![DomRoot::from_ref(self)]
);
}
/// Enqueue a microtask for subsequent execution.

View file

@ -428,6 +428,7 @@ pub mod processinginstruction;
pub mod progressevent;
pub mod promise;
pub mod promisenativehandler;
pub mod promiserejectionevent;
pub mod radionodelist;
pub mod range;
pub mod request;

View file

@ -93,7 +93,7 @@ impl Promise {
}
#[allow(unsafe_code, unrooted_must_root)]
unsafe fn new_with_js_promise(obj: HandleObject, cx: *mut JSContext) -> Rc<Promise> {
pub unsafe fn new_with_js_promise(obj: HandleObject, cx: *mut JSContext) -> Rc<Promise> {
assert!(IsPromiseObject(obj));
let promise = Promise {
reflector: Reflector::new(),

View file

@ -0,0 +1,114 @@
/* 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::EventBinding::EventMethods;
use dom::bindings::codegen::Bindings::PromiseRejectionEventBinding;
use dom::bindings::codegen::Bindings::PromiseRejectionEventBinding::PromiseRejectionEventMethods;
use dom::bindings::error::Fallible;
use dom::bindings::inheritance::Castable;
use dom::bindings::reflector::reflect_dom_object;
use dom::bindings::root::DomRoot;
use dom::bindings::str::DOMString;
use dom::bindings::trace::RootedTraceableBox;
use dom::event::{Event, EventBubbles, EventCancelable};
use dom::globalscope::GlobalScope;
use dom::promise::Promise;
use dom_struct::dom_struct;
use js::jsapi::{Heap, JSContext};
use js::jsval::JSVal;
use js::rust::HandleValue;
use servo_atoms::Atom;
use std::rc::Rc;
#[dom_struct]
pub struct PromiseRejectionEvent {
event: Event,
#[ignore_malloc_size_of = "Rc"]
promise: Rc<Promise>,
#[ignore_malloc_size_of = "Defined in rust-mozjs"]
reason: Heap<JSVal>,
}
impl PromiseRejectionEvent {
#[allow(unrooted_must_root)]
fn new_inherited(promise: Rc<Promise>) -> Self {
PromiseRejectionEvent {
event: Event::new_inherited(),
promise,
reason: Heap::default()
}
}
#[allow(unrooted_must_root)]
pub fn new(
global: &GlobalScope,
type_: Atom,
bubbles: EventBubbles,
cancelable: EventCancelable,
promise: Rc<Promise>,
reason: HandleValue
) -> DomRoot<Self> {
let ev = reflect_dom_object(
Box::new(PromiseRejectionEvent::new_inherited(promise)),
global,
PromiseRejectionEventBinding::Wrap
);
{
let event = ev.upcast::<Event>();
event.init_event(
type_,
bool::from(bubbles),
bool::from(cancelable)
);
ev.reason.set(reason.get());
}
ev
}
#[allow(unrooted_must_root)]
pub fn Constructor(
global: &GlobalScope,
type_: DOMString,
init: RootedTraceableBox<PromiseRejectionEventBinding::PromiseRejectionEventInit>
) -> Fallible<DomRoot<Self>> {
let reason = init.reason.handle();
let promise = match init.promise.as_ref() {
Some(promise) => promise.clone(),
None => Promise::new(global)
};
let bubbles = EventBubbles::from(init.parent.bubbles);
let cancelable = EventCancelable::from(init.parent.cancelable);
let event = PromiseRejectionEvent::new(
global,
Atom::from(type_),
bubbles,
cancelable,
promise,
reason
);
Ok(event)
}
}
impl PromiseRejectionEventMethods for PromiseRejectionEvent {
#[allow(unrooted_must_root)]
// https://html.spec.whatwg.org/multipage/#dom-promiserejectionevent-promise
fn Promise(&self) -> Rc<Promise> {
self.promise.clone()
}
#[allow(unsafe_code)]
// https://html.spec.whatwg.org/multipage/#dom-promiserejectionevent-reason
unsafe fn Reason(&self, _cx: *mut JSContext) -> JSVal {
self.reason.get()
}
// https://dom.spec.whatwg.org/#dom-event-istrusted
fn IsTrusted(&self) -> bool {
self.event.IsTrusted()
}
}

View file

@ -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/. */
// https://html.spec.whatwg.org/multipage/#the-promiserejectionevent-interface
[Constructor(DOMString type, optional PromiseRejectionEventInit eventInitDict), Exposed=(Window,Worker)]
interface PromiseRejectionEvent : Event {
readonly attribute Promise<any> promise;
readonly attribute any reason;
};
dictionary PromiseRejectionEventInit : EventInit {
/* required */ Promise<any> promise;
any reason;
};

View file

@ -15,6 +15,7 @@ use dom::htmlimageelement::ImageElementMicrotask;
use dom::htmlmediaelement::MediaElementMicrotask;
use dom::mutationobserver::MutationObserver;
use msg::constellation_msg::PipelineId;
use script_runtime::notify_about_rejected_promises;
use script_thread::ScriptThread;
use std::cell::Cell;
use std::mem;
@ -59,7 +60,7 @@ impl MicrotaskQueue {
/// <https://html.spec.whatwg.org/multipage/#perform-a-microtask-checkpoint>
/// Perform a microtask checkpoint, executing all queued microtasks until the queue is empty.
pub fn checkpoint<F>(&self, target_provider: F)
pub fn checkpoint<F>(&self, target_provider: F, globalscopes: Vec<DomRoot<GlobalScope>>)
where
F: Fn(PipelineId) -> Option<DomRoot<GlobalScope>>,
{
@ -70,7 +71,7 @@ impl MicrotaskQueue {
// Step 1
self.performing_a_microtask_checkpoint.set(true);
// Steps 2-7
// Steps 2
while !self.microtask_queue.borrow().is_empty() {
rooted_vec!(let mut pending_queue);
mem::swap(&mut *pending_queue, &mut *self.microtask_queue.borrow_mut());
@ -98,9 +99,14 @@ impl MicrotaskQueue {
}
}
//TODO: Step 8 - notify about rejected promises
// Step 3
for global in globalscopes.into_iter() {
notify_about_rejected_promises(&global);
}
// Step 9
// TODO: Step 4 - Cleanup Indexed Database transactions.
// Step 5
self.performing_a_microtask_checkpoint.set(false);
}
}

View file

@ -8,24 +8,34 @@
use dom::bindings::codegen::Bindings::PromiseBinding::PromiseJobCallback;
use dom::bindings::conversions::get_dom_class;
use dom::bindings::conversions::private_from_object;
use dom::bindings::inheritance::Castable;
use dom::bindings::refcounted::{LiveDOMReferences, trace_refcounted_objects};
use dom::bindings::refcounted::{Trusted, TrustedPromise};
use dom::bindings::reflector::DomObject;
use dom::bindings::root::trace_roots;
use dom::bindings::settings_stack;
use dom::bindings::trace::{JSTraceable, trace_traceables};
use dom::bindings::utils::DOM_CALLBACKS;
use dom::event::{Event, EventBubbles, EventCancelable, EventStatus};
use dom::eventtarget::EventTarget;
use dom::globalscope::GlobalScope;
use dom::promise::Promise;
use dom::promiserejectionevent::PromiseRejectionEvent;
use js::glue::CollectServoSizes;
use js::glue::SetBuildId;
use js::jsapi::{DisableIncrementalGC, GCDescription, GCProgress, HandleObject};
use js::jsapi::{BuildIdCharVector, DisableIncrementalGC, GCDescription, GCProgress};
use js::jsapi::{Heap, HandleObject};
use js::jsapi::{JSContext, JSTracer, SetDOMCallbacks, SetGCSliceCallback};
use js::jsapi::{JSGCInvocationKind, JSGCStatus, JS_AddExtraGCRootsTracer, JS_SetGCCallback};
use js::jsapi::{JSGCMode, JSGCParamKey, JS_SetGCParameter, JS_SetGlobalJitCompilerOption};
use js::jsapi::{JSJitCompilerOption, JS_SetOffthreadIonCompilationEnabled, JS_SetParallelParsingEnabled};
use js::jsapi::{JSObject, SetPreserveWrapperCallback, SetEnqueuePromiseJobCallback};
use js::jsapi::{SetBuildIdOp, BuildIdCharVector};
use js::jsapi::{JSObject, PromiseRejectionHandlingState, SetPreserveWrapperCallback};
use js::jsapi::{SetBuildIdOp, SetEnqueuePromiseJobCallback, SetPromiseRejectionTrackerCallback};
use js::jsapi::ContextOptionsRef;
use js::panic::wrap_panic;
use js::rust::Handle;
use js::rust::Runtime as RustRuntime;
use js::rust::wrappers::{GetPromiseIsHandled, GetPromiseResult};
use malloc_size_of::MallocSizeOfOps;
use microtask::{EnqueuedPromiseCallback, Microtask};
use msg::constellation_msg::PipelineId;
@ -43,7 +53,7 @@ use std::panic::AssertUnwindSafe;
use std::ptr;
use style::thread_state::{self, ThreadState};
use task::TaskBox;
use task_source::TaskSourceName;
use task_source::{TaskSource, TaskSourceName};
use time::{Tm, now};
/// Common messages used to control the event loops in both the script and the worker
@ -140,6 +150,115 @@ unsafe extern "C" fn enqueue_job(
)
}
#[allow(unsafe_code)]
/// https://html.spec.whatwg.org/multipage/#the-hostpromiserejectiontracker-implementation
unsafe extern "C" fn promise_rejection_tracker(
cx: *mut JSContext,
promise: HandleObject,
state: PromiseRejectionHandlingState,
_data: *mut c_void
) {
// TODO: Step 2 - If script's muted errors is true, terminate these steps.
// Step 3.
let global = GlobalScope::from_context(cx);
wrap_panic(AssertUnwindSafe(|| {
match state {
// Step 4.
PromiseRejectionHandlingState::Unhandled => {
global.add_uncaught_rejection(promise);
},
// Step 5.
PromiseRejectionHandlingState::Handled => {
// Step 5-1.
if global.get_uncaught_rejections().borrow().contains(&Heap::boxed(promise.get())) {
global.remove_uncaught_rejection(promise);
return;
}
// Step 5-2.
if !global.get_consumed_rejections().borrow().contains(&Heap::boxed(promise.get())) {
global.add_consumed_rejection(promise);
return;
}
// Step 5-3.
global.remove_consumed_rejection(promise);
// TODO: Step 5-4 - Queue a task to fire `rejectionhandled` event
}
};
}), ());
}
#[allow(unsafe_code, unrooted_must_root)]
/// https://html.spec.whatwg.org/multipage/#notify-about-rejected-promises
pub fn notify_about_rejected_promises(global: &GlobalScope) {
unsafe {
let cx = global.get_cx();
// Step 2.
if global.get_uncaught_rejections().borrow().len() > 0 {
// Step 1.
let uncaught_rejections: Vec<TrustedPromise> = global.get_uncaught_rejections()
.borrow()
.iter()
.map(|promise| {
let promise = Promise::new_with_js_promise(Handle::from_raw(promise.handle()), cx);
TrustedPromise::new(promise)
})
.collect();
// Step 3.
global.get_uncaught_rejections().borrow_mut().clear();
let target = Trusted::new(global.upcast::<EventTarget>());
// Step 4.
global.as_window().dom_manipulation_task_source().queue(
task!(unhandled_rejection_event: move || {
let target = target.root();
let cx = target.global().get_cx();
for promise in uncaught_rejections {
let promise = promise.root();
// Step 4-1.
let promise_is_handled = GetPromiseIsHandled(promise.reflector().get_jsobject());
if promise_is_handled {
continue;
}
// Step 4-2.
rooted!(in(cx) let reason = GetPromiseResult(promise.reflector().get_jsobject()));
let event = PromiseRejectionEvent::new(
&target.global(),
atom!("unhandledrejection"),
EventBubbles::DoesNotBubble,
EventCancelable::Cancelable,
promise,
reason.handle()
);
let event_status = event.upcast::<Event>().fire(&target);
// Step 4-3.
if event_status == EventStatus::Canceled {
// TODO: The promise rejection is not handled; we need to add it back to the list.
}
// TODO: Step 4-4 - If [[PromiseIsHandled]] is false, add promise to consumed_rejections
}
}),
global.upcast(),
).unwrap();
}
}
}
#[derive(JSTraceable)]
pub struct Runtime(RustRuntime);
@ -182,6 +301,7 @@ pub unsafe fn new_rt_and_cx() -> Runtime {
DisableIncrementalGC(cx);
SetEnqueuePromiseJobCallback(cx, Some(enqueue_job), ptr::null_mut());
SetPromiseRejectionTrackerCallback(cx, Some(promise_rejection_tracker), ptr::null_mut());
set_gc_zeal_options(cx);

View file

@ -3200,8 +3200,16 @@ impl ScriptThread {
}
fn perform_a_microtask_checkpoint(&self) {
let globals = self.documents.borrow()
.iter()
.map(|(_id, document)| document.global())
.collect();
self.microtask_queue
.checkpoint(|id| self.documents.borrow().find_global(id))
.checkpoint(
|id| self.documents.borrow().find_global(id),
globals
)
}
}