aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--components/script/dom/dedicatedworkerglobalscope.rs8
-rw-r--r--components/script/dom/globalscope.rs23
-rw-r--r--components/script/dom/serviceworkerglobalscope.rs5
-rw-r--r--components/script/dom/workerglobalscope.rs38
-rw-r--r--components/script/lib.rs1
-rw-r--r--components/script/microtask.rs83
-rw-r--r--components/script/script_runtime.rs82
-rw-r--r--components/script/script_thread.rs41
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))
}
}