From 57b3ccd38cbdf53b8e45f68e8ff6c4fd786118dc Mon Sep 17 00:00:00 2001 From: Josh Matthews Date: Mon, 5 Sep 2016 10:55:31 -0400 Subject: Support promises in workers. --- components/script/script_runtime.rs | 105 +++++++++++++++++++++++++++++++++++- 1 file changed, 103 insertions(+), 2 deletions(-) (limited to 'components/script/script_runtime.rs') diff --git a/components/script/script_runtime.rs b/components/script/script_runtime.rs index 0287d0e9c10..658ace4c46a 100644 --- a/components/script/script_runtime.rs +++ b/components/script/script_runtime.rs @@ -5,18 +5,23 @@ //! The script runtime contains common traits and structs commonly used by the //! script thread, the dom, and the worker threads. +use dom::bindings::callback::ExceptionHandling; +use dom::bindings::cell::DOMRefCell; +use dom::bindings::codegen::Bindings::PromiseBinding::PromiseJobCallback; +use dom::bindings::global::{global_root_from_object, GlobalRoot, GlobalRef}; use dom::bindings::js::{RootCollection, RootCollectionPtr, trace_roots}; use dom::bindings::refcounted::{LiveDOMReferences, trace_refcounted_objects}; use dom::bindings::trace::trace_traceables; use dom::bindings::utils::DOM_CALLBACKS; use js::glue::CollectServoSizes; -use js::jsapi::{DisableIncrementalGC, GCDescription, GCProgress}; +use js::jsapi::{DisableIncrementalGC, GCDescription, GCProgress, HandleObject}; use js::jsapi::{JSContext, JS_GetRuntime, JSRuntime, 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, RuntimeOptionsRef, SetPreserveWrapperCallback}; +use js::jsapi::{JSObject, RuntimeOptionsRef, SetPreserveWrapperCallback, SetEnqueuePromiseJobCallback}; use js::rust::Runtime; +use msg::constellation_msg::PipelineId; use profile_traits::mem::{Report, ReportKind, ReportsChan}; use script_thread::{Runnable, STACK_ROOTS, trace_thread}; use std::any::Any; @@ -24,7 +29,10 @@ use std::cell::{RefCell, Cell}; use std::io::{Write, stdout}; use std::marker::PhantomData; use std::os; +use std::os::raw::c_void; +use std::panic::{self, AssertUnwindSafe}; use std::ptr; +use std::rc::Rc; use style::thread_state; use time::{Tm, now}; use util::opts; @@ -95,6 +103,97 @@ impl<'a> Drop for StackRootTLS<'a> { } } +/// A promise callback scheduled to run during the next microtask checkpoint (#4283). +#[derive(JSTraceable, HeapSizeOf)] +pub struct EnqueuedPromiseCallback { + #[ignore_heap_size_of = "Rc has unclear ownership"] + callback: Rc, + pipeline: PipelineId, +} + +/// A collection of promise callbacks in FIFO order. +#[derive(JSTraceable, HeapSizeOf)] +pub struct PromiseJobQueue { + /// A snapshot of `promise_job_queue` that was taken at the start of the microtask checkpoint. + /// Used to work around mutability errors when appending new promise jobs while performing + /// a microtask checkpoint. + flushing_job_queue: DOMRefCell>, + /// The list of enqueued promise callbacks that will be invoked at the next microtask checkpoint. + promise_job_queue: DOMRefCell>, + /// True if there is an outstanding runnable responsible for evaluating the promise job queue. + /// This prevents runnables flooding the event queue needlessly, since the first one will + /// execute all pending runnables. + pending_promise_job_runnable: Cell, +} + +impl PromiseJobQueue { + /// Create a new PromiseJobQueue instance. + pub fn new() -> PromiseJobQueue { + PromiseJobQueue { + promise_job_queue: DOMRefCell::new(vec![]), + flushing_job_queue: DOMRefCell::new(vec![]), + pending_promise_job_runnable: Cell::new(false), + } + } + + /// Add a new promise job callback to this queue. It will be invoked as part of the next + /// microtask checkpoint. + pub fn enqueue(&self, job: EnqueuedPromiseCallback, global: GlobalRef) { + self.promise_job_queue.borrow_mut().push(job); + if !self.pending_promise_job_runnable.get() { + self.pending_promise_job_runnable.set(true); + global.flush_promise_jobs(); + } + } + + /// Perform a microtask checkpoint, by invoking all of the pending promise job callbacks in + /// FIFO order (#4283). + pub fn flush_promise_jobs(&self, target_provider: F) + where F: Fn(PipelineId) -> Option + { + self.pending_promise_job_runnable.set(false); + { + let mut pending_queue = self.promise_job_queue.borrow_mut(); + *self.flushing_job_queue.borrow_mut() = pending_queue.drain(..).collect(); + } + // N.B. borrowing this vector is safe w.r.t. mutability, since any promise job that + // is enqueued while invoking these callbacks will be placed in `pending_queue`; + // `flushing_queue` is a static snapshot during this checkpoint. + for job in &*self.flushing_job_queue.borrow() { + if let Some(target) = target_provider(job.pipeline) { + let _ = job.callback.Call_(&target.r(), ExceptionHandling::Report); + } + } + self.flushing_job_queue.borrow_mut().clear(); + } +} + +/// SM callback for promise job resolution. Adds a promise callback to the current global's +/// promise job queue, and enqueues a runnable to perform a microtask checkpoint if one +/// is not already pending. +#[allow(unsafe_code)] +unsafe extern "C" fn enqueue_job(_cx: *mut JSContext, + job: HandleObject, + _allocation_site: HandleObject, + _data: *mut c_void) -> bool { + let result = panic::catch_unwind(AssertUnwindSafe(|| { + let global = global_root_from_object(job.get()); + let pipeline = global.r().pipeline_id(); + global.r().enqueue_promise_job(EnqueuedPromiseCallback { + callback: PromiseJobCallback::new(job.get()), + pipeline: pipeline, + }); + true + })); + match result { + Ok(result) => result, + Err(error) => { + store_panic_result(error); + return false; + } + } +} + #[allow(unsafe_code)] pub unsafe fn new_rt_and_cx() -> Runtime { LiveDOMReferences::initialize(); @@ -118,6 +217,8 @@ pub unsafe fn new_rt_and_cx() -> Runtime { // Pre barriers aren't working correctly at the moment DisableIncrementalGC(runtime.rt()); + SetEnqueuePromiseJobCallback(runtime.rt(), Some(enqueue_job), ptr::null_mut()); + set_gc_zeal_options(runtime.rt()); // Enable or disable the JITs. -- cgit v1.2.3