diff options
-rw-r--r-- | components/script/dom/dedicatedworkerglobalscope.rs | 8 | ||||
-rw-r--r-- | components/script/dom/globalscope.rs | 23 | ||||
-rw-r--r-- | components/script/dom/serviceworkerglobalscope.rs | 5 | ||||
-rw-r--r-- | components/script/dom/workerglobalscope.rs | 38 | ||||
-rw-r--r-- | components/script/lib.rs | 1 | ||||
-rw-r--r-- | components/script/microtask.rs | 83 | ||||
-rw-r--r-- | components/script/script_runtime.rs | 82 | ||||
-rw-r--r-- | components/script/script_thread.rs | 41 |
8 files changed, 137 insertions, 144 deletions
diff --git a/components/script/dom/dedicatedworkerglobalscope.rs b/components/script/dom/dedicatedworkerglobalscope.rs index 2d6f5ea14af..03e3cebe5e1 100644 --- a/components/script/dom/dedicatedworkerglobalscope.rs +++ b/components/script/dom/dedicatedworkerglobalscope.rs @@ -230,17 +230,23 @@ impl DedicatedWorkerGlobalScope { } { - let _ar = AutoWorkerReset::new(&global, worker); + let _ar = AutoWorkerReset::new(&global, worker.clone()); scope.execute_script(DOMString::from(source)); } let reporter_name = format!("dedicated-worker-reporter-{}", random::<u64>()); scope.upcast::<GlobalScope>().mem_profiler_chan().run_with_memory_reporting(|| { + // https://html.spec.whatwg.org/multipage/#event-loop-processing-model + // Step 1 while let Ok(event) = global.receive_event() { if scope.is_closing() { break; } + // Step 3 global.handle_event(event); + // Step 6 + let _ar = AutoWorkerReset::new(&global, worker.clone()); + global.upcast::<WorkerGlobalScope>().perform_a_microtask_checkpoint(); } }, reporter_name, parent_sender, CommonScriptMsg::CollectReports); }).expect("Thread spawning failed"); diff --git a/components/script/dom/globalscope.rs b/components/script/dom/globalscope.rs index f513d4285ad..aaebf091c6d 100644 --- a/components/script/dom/globalscope.rs +++ b/components/script/dom/globalscope.rs @@ -29,10 +29,11 @@ use js::jsapi::{JS_GetObjectRuntime, MutableHandleValue}; use js::panic::maybe_resume_unwind; use js::rust::{CompileOptionsWrapper, Runtime, get_object_class}; use libc; +use microtask::Microtask; use msg::constellation_msg::PipelineId; use net_traits::{CoreResourceThread, ResourceThreads, IpcSend}; use profile_traits::{mem, time}; -use script_runtime::{CommonScriptMsg, EnqueuedPromiseCallback, ScriptChan, ScriptPort}; +use script_runtime::{CommonScriptMsg, ScriptChan, ScriptPort}; use script_thread::{MainThreadScriptChan, RunnableWrapper, ScriptThread}; use script_traits::{MsDuration, ScriptMsg as ConstellationMsg, TimerEvent}; use script_traits::{TimerEventId, TimerEventRequest, TimerSource}; @@ -448,25 +449,13 @@ impl GlobalScope { unreachable!(); } - /// 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) { + /// Enqueue a microtask for subsequent execution. + pub fn enqueue_microtask(&self, job: Microtask) { if self.is::<Window>() { - return ScriptThread::flush_promise_jobs(self); + return ScriptThread::enqueue_microtask(job); } if let Some(worker) = self.downcast::<WorkerGlobalScope>() { - return worker.flush_promise_jobs(); - } - unreachable!(); - } - - /// Enqueue a promise callback for subsequent execution. - pub fn enqueue_promise_job(&self, job: EnqueuedPromiseCallback) { - if self.is::<Window>() { - return ScriptThread::enqueue_promise_job(job, self); - } - if let Some(worker) = self.downcast::<WorkerGlobalScope>() { - return worker.enqueue_promise_job(job); + return worker.enqueue_microtask(job); } unreachable!(); } diff --git a/components/script/dom/serviceworkerglobalscope.rs b/components/script/dom/serviceworkerglobalscope.rs index 854a0ee0a75..553b016f0f8 100644 --- a/components/script/dom/serviceworkerglobalscope.rs +++ b/components/script/dom/serviceworkerglobalscope.rs @@ -210,10 +210,15 @@ impl ServiceWorkerGlobalScope { global.dispatch_activate(); let reporter_name = format!("service-worker-reporter-{}", random::<u64>()); scope.upcast::<GlobalScope>().mem_profiler_chan().run_with_memory_reporting(|| { + // https://html.spec.whatwg.org/multipage/#event-loop-processing-model + // Step 1 while let Ok(event) = global.receive_event() { + // Step 3 if !global.handle_event(event) { break; } + // Step 6 + global.upcast::<WorkerGlobalScope>().perform_a_microtask_checkpoint(); } }, reporter_name, scope.script_chan(), CommonScriptMsg::CollectReports); }).expect("Thread spawning failed"); diff --git a/components/script/dom/workerglobalscope.rs b/components/script/dom/workerglobalscope.rs index 6e9b919d98c..16bd699da7c 100644 --- a/components/script/dom/workerglobalscope.rs +++ b/components/script/dom/workerglobalscope.rs @@ -11,7 +11,6 @@ use dom::bindings::codegen::UnionTypes::RequestOrUSVString; use dom::bindings::error::{Error, ErrorResult, Fallible, report_pending_exception}; use dom::bindings::inheritance::Castable; use dom::bindings::js::{MutNullableJS, Root}; -use dom::bindings::refcounted::Trusted; use dom::bindings::reflector::DomObject; use dom::bindings::settings_stack::AutoEntryScript; use dom::bindings::str::DOMString; @@ -29,11 +28,11 @@ use js::jsapi::{HandleValue, JSAutoCompartment, JSContext, JSRuntime}; use js::jsval::UndefinedValue; use js::panic::maybe_resume_unwind; use js::rust::Runtime; +use microtask::{MicrotaskQueue, Microtask}; use net_traits::{IpcSend, load_whole_resource}; use net_traits::request::{CredentialsMode, Destination, RequestInit as NetRequestInit, Type as RequestType}; use script_runtime::{CommonScriptMsg, ScriptChan, ScriptPort}; -use script_runtime::{ScriptThreadEventCategory, PromiseJobQueue, EnqueuedPromiseCallback}; -use script_thread::{Runnable, RunnableWrapper}; +use script_thread::RunnableWrapper; use script_traits::{TimerEvent, TimerEventId}; use script_traits::WorkerGlobalScopeInit; use servo_url::ServoUrl; @@ -87,7 +86,7 @@ pub struct WorkerGlobalScope { /// `IpcSender` doesn't exist from_devtools_receiver: Receiver<DevtoolScriptControlMsg>, - promise_job_queue: PromiseJobQueue, + microtask_queue: MicrotaskQueue, } impl WorkerGlobalScope { @@ -117,7 +116,7 @@ impl WorkerGlobalScope { navigator: Default::default(), from_devtools_sender: init.from_devtools_sender, from_devtools_receiver: from_devtools_receiver, - promise_job_queue: PromiseJobQueue::new(), + microtask_queue: MicrotaskQueue::default(), } } @@ -159,20 +158,12 @@ impl WorkerGlobalScope { } } - pub fn enqueue_promise_job(&self, job: EnqueuedPromiseCallback) { - self.promise_job_queue.enqueue(job, self.upcast()); + pub fn enqueue_microtask(&self, job: Microtask) { + self.microtask_queue.enqueue(job); } - 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| { + pub fn perform_a_microtask_checkpoint(&self) { + self.microtask_queue.checkpoint(|id| { let global = self.upcast::<GlobalScope>(); assert_eq!(global.pipeline_id(), id); Some(Root::from_ref(global)) @@ -393,6 +384,8 @@ impl WorkerGlobalScope { } else { panic!("need to implement a sender for SharedWorker") } + + //XXXjdm should we do a microtask checkpoint here? } pub fn handle_fire_timer(&self, timer_id: TimerEventId) { @@ -405,14 +398,3 @@ impl WorkerGlobalScope { } } } - -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/lib.rs b/components/script/lib.rs index 15d1d2d322b..033e6415879 100644 --- a/components/script/lib.rs +++ b/components/script/lib.rs @@ -108,6 +108,7 @@ mod dom; pub mod fetch; pub mod layout_wrapper; mod mem; +mod microtask; mod network_listener; pub mod origin; pub mod script_runtime; diff --git a/components/script/microtask.rs b/components/script/microtask.rs new file mode 100644 index 00000000000..da04d1ed6ab --- /dev/null +++ b/components/script/microtask.rs @@ -0,0 +1,83 @@ +/* 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/. */ + +//! Implementation of [microtasks](https://html.spec.whatwg.org/multipage/#microtask) and +//! microtask queues. It is up to implementations of event loops to store a queue and +//! perform checkpoints at appropriate times, as well as enqueue microtasks as required. + +use dom::bindings::callback::ExceptionHandling; +use dom::bindings::cell::DOMRefCell; +use dom::bindings::codegen::Bindings::PromiseBinding::PromiseJobCallback; +use dom::bindings::js::Root; +use dom::globalscope::GlobalScope; +use msg::constellation_msg::PipelineId; +use std::cell::Cell; +use std::mem; +use std::rc::Rc; + +/// A collection of microtasks in FIFO order. +#[derive(JSTraceable, HeapSizeOf, Default)] +pub struct MicrotaskQueue { + /// The list of enqueued microtasks that will be invoked at the next microtask checkpoint. + microtask_queue: DOMRefCell<Vec<Microtask>>, + /// https://html.spec.whatwg.org/multipage/#performing-a-microtask-checkpoint + performing_a_microtask_checkpoint: Cell<bool>, +} + +#[derive(JSTraceable, HeapSizeOf)] +pub enum Microtask { + Promise(EnqueuedPromiseCallback), +} + +/// 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"] + pub callback: Rc<PromiseJobCallback>, + pub pipeline: PipelineId, +} + +impl MicrotaskQueue { + /// Add a new microtask to this queue. It will be invoked as part of the next + /// microtask checkpoint. + pub fn enqueue(&self, job: Microtask) { + self.microtask_queue.borrow_mut().push(job); + } + + /// 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) + where F: Fn(PipelineId) -> Option<Root<GlobalScope>> + { + if self.performing_a_microtask_checkpoint.get() { + return; + } + + // Step 1 + self.performing_a_microtask_checkpoint.set(true); + + // Steps 2-7 + while !self.microtask_queue.borrow().is_empty() { + rooted_vec!(let mut pending_queue); + mem::swap( + &mut *pending_queue, + &mut *self.microtask_queue.borrow_mut()); + + for job in pending_queue.iter() { + match *job { + Microtask::Promise(ref job) => { + if let Some(target) = target_provider(job.pipeline) { + let _ = job.callback.Call_(&*target, ExceptionHandling::Report); + } + } + } + } + } + + //TODO: Step 8 - notify about rejected promises + + // Step 9 + self.performing_a_microtask_checkpoint.set(false); + } +} diff --git a/components/script/script_runtime.rs b/components/script/script_runtime.rs index b7a0374428b..303353d53ea 100644 --- a/components/script/script_runtime.rs +++ b/components/script/script_runtime.rs @@ -5,10 +5,8 @@ //! 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::js::{Root, RootCollection, RootCollectionPtr, trace_roots}; +use dom::bindings::js::{RootCollection, RootCollectionPtr, trace_roots}; use dom::bindings::refcounted::{LiveDOMReferences, trace_refcounted_objects}; use dom::bindings::settings_stack; use dom::bindings::trace::{JSTraceable, trace_traceables}; @@ -23,7 +21,7 @@ use js::jsapi::{JSJitCompilerOption, JS_SetOffthreadIonCompilationEnabled, JS_Se use js::jsapi::{JSObject, RuntimeOptionsRef, SetPreserveWrapperCallback, SetEnqueuePromiseJobCallback}; use js::panic::wrap_panic; use js::rust::Runtime; -use msg::constellation_msg::PipelineId; +use microtask::{EnqueuedPromiseCallback, Microtask}; use profile_traits::mem::{Report, ReportKind, ReportsChan}; use script_thread::{Runnable, STACK_ROOTS, trace_thread}; use servo_config::opts; @@ -35,7 +33,6 @@ use std::os; use std::os::raw::c_void; use std::panic::AssertUnwindSafe; use std::ptr; -use std::rc::Rc; use style::thread_state; use time::{Tm, now}; @@ -107,86 +104,21 @@ 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<PromiseJobCallback>, - 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<Vec<EnqueuedPromiseCallback>>, - /// The list of enqueued promise callbacks that will be invoked at the next microtask checkpoint. - promise_job_queue: DOMRefCell<Vec<EnqueuedPromiseCallback>>, - /// 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<bool>, -} - -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: &GlobalScope) { - 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<F>(&self, target_provider: F) - where F: Fn(PipelineId) -> Option<Root<GlobalScope>> - { - 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, 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. +/// SM callback for promise job resolution. Adds a promise callback to the current +/// global's microtask queue. #[allow(unsafe_code)] unsafe extern "C" fn enqueue_job(cx: *mut JSContext, job: HandleObject, _allocation_site: HandleObject, _data: *mut c_void) -> bool { wrap_panic(AssertUnwindSafe(|| { + //XXXjdm - use a different global now? let global = GlobalScope::from_object(job.get()); let pipeline = global.pipeline_id(); - global.enqueue_promise_job(EnqueuedPromiseCallback { + global.enqueue_microtask(Microtask::Promise(EnqueuedPromiseCallback { callback: PromiseJobCallback::new(cx, job.get()), pipeline: pipeline, - }); + })); true }), false) } diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 865fd389330..d204ab776b8 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -70,6 +70,7 @@ use js::jsval::UndefinedValue; use js::rust::Runtime; use layout_wrapper::ServoLayoutNode; use mem::heap_size_of_self_and_children; +use microtask::{MicrotaskQueue, Microtask}; use msg::constellation_msg::{FrameId, FrameType, PipelineId, PipelineNamespace}; use net_traits::{CoreResourceMsg, FetchMetadata, FetchResponseListener}; use net_traits::{IpcSend, Metadata, ReferrerPolicy, ResourceThreads}; @@ -81,8 +82,8 @@ use origin::Origin; use profile_traits::mem::{self, OpaqueSender, Report, ReportKind, ReportsChan}; use profile_traits::time::{self, ProfilerCategory, profile}; use script_layout_interface::message::{self, NewLayoutThreadInfo, ReflowQueryType}; -use script_runtime::{CommonScriptMsg, ScriptChan, ScriptThreadEventCategory, EnqueuedPromiseCallback}; -use script_runtime::{ScriptPort, StackRootTLS, get_reports, new_rt_and_cx, PromiseJobQueue}; +use script_runtime::{CommonScriptMsg, ScriptChan, ScriptThreadEventCategory}; +use script_runtime::{ScriptPort, StackRootTLS, get_reports, new_rt_and_cx}; use script_traits::{CompositorEvent, ConstellationControlMsg}; use script_traits::{DocumentActivity, DiscardBrowsingContext, EventResult}; use script_traits::{InitialScriptState, LayoutMsg, LoadData, MouseButton, MouseEventType, MozBrowserEvent}; @@ -98,6 +99,7 @@ use servo_config::opts; use servo_url::ServoUrl; use std::cell::Cell; use std::collections::{hash_map, HashMap, HashSet}; +use std::default::Default; use std::ops::Deref; use std::option::Option; use std::ptr; @@ -477,7 +479,7 @@ pub struct ScriptThread { content_process_shutdown_chan: IpcSender<()>, - promise_job_queue: PromiseJobQueue, + microtask_queue: MicrotaskQueue, /// A handle to the webvr thread, if available webvr_thread: Option<IpcSender<WebVRMsg>>, @@ -694,7 +696,7 @@ impl ScriptThread { content_process_shutdown_chan: state.content_process_shutdown_chan, - promise_job_queue: PromiseJobQueue::new(), + microtask_queue: MicrotaskQueue::default(), layout_to_constellation_chan: state.layout_to_constellation_chan, @@ -776,6 +778,7 @@ impl ScriptThread { let mut mouse_move_event_index = None; let mut animation_ticks = HashSet::new(); loop { + // https://html.spec.whatwg.org/multipage/#event-loop-processing-model step 7 match event { // This has to be handled before the ResizeMsg below, // otherwise the page may not have been added to the @@ -788,6 +791,7 @@ impl ScriptThread { }) } FromConstellation(ConstellationControlMsg::Resize(id, size, size_type)) => { + // step 7.7 self.profile_event(ScriptThreadEventCategory::Resize, || { self.handle_resize(id, size, size_type); }) @@ -804,6 +808,7 @@ impl ScriptThread { } FromConstellation(ConstellationControlMsg::TickAllAnimations( pipeline_id)) => { + // step 7.8 if !animation_ticks.contains(&pipeline_id) { animation_ticks.insert(pipeline_id); sequential.push(event); @@ -868,11 +873,16 @@ impl ScriptThread { None }); + // https://html.spec.whatwg.org/multipage/#event-loop-processing-model step 6 + self.perform_a_microtask_checkpoint(); + if let Some(retval) = result { return retval } } + // https://html.spec.whatwg.org/multipage/#event-loop-processing-model step 7.12 + // Issue batched reflows on any pages that require it (e.g. if images loaded) // TODO(gw): In the future we could probably batch other types of reflows // into this loop too, but for now it's only images. @@ -2108,30 +2118,15 @@ impl ScriptThread { } } - pub fn enqueue_promise_job(job: EnqueuedPromiseCallback, global: &GlobalScope) { + pub fn enqueue_microtask(job: Microtask) { SCRIPT_THREAD_ROOT.with(|root| { let script_thread = unsafe { &*root.get().unwrap() }; - script_thread.promise_job_queue.enqueue(job, global); + script_thread.microtask_queue.enqueue(job); }); } - pub fn flush_promise_jobs(global: &GlobalScope) { - SCRIPT_THREAD_ROOT.with(|root| { - let script_thread = unsafe { &*root.get().unwrap() }; - let _ = script_thread.dom_manipulation_task_source.queue( - box FlushPromiseJobs, global); - }) - } - - fn do_flush_promise_jobs(&self) { - self.promise_job_queue.flush_promise_jobs(|id| self.documents.borrow().find_global(id)) - } -} - -struct FlushPromiseJobs; -impl Runnable for FlushPromiseJobs { - fn main_thread_handler(self: Box<FlushPromiseJobs>, script_thread: &ScriptThread) { - script_thread.do_flush_promise_jobs(); + fn perform_a_microtask_checkpoint(&self) { + self.microtask_queue.checkpoint(|id| self.documents.borrow().find_global(id)) } } |