servo: Merge #15189 - Implement microtask checkpoints (from jdm:microtasks); r=nox

This generalizes the work previously done for Promise job callbacks. There is now a microtask queue that correctly processes all queued microtasks after each turn of the event loop, as well as after a scripted callback finishes executing, and after a classic script executes.

---
- [X] `./mach build -d` does not report any errors
- [X] `./mach test-tidy` does not report any errors
- [X] These changes fix #4283
- [X] There are tests for these changes

Source-Repo: https://github.com/servo/servo
Source-Revision: cbcafd18f4cb5973948b081b4c104d99735e2789
This commit is contained in:
Josh Matthews
2017-02-03 07:53:17 -08:00
parent 908e301a06
commit 22c551f228
11 changed files with 339 additions and 292 deletions

View File

@@ -35,7 +35,6 @@ use dom::bindings::inheritance::Castable;
use dom::bindings::js::{JS, MutNullableJS, Root, RootCollection};
use dom::bindings::js::{RootCollectionPtr, RootedReference};
use dom::bindings::num::Finite;
use dom::bindings::refcounted::Trusted;
use dom::bindings::reflector::DomObject;
use dom::bindings::str::DOMString;
use dom::bindings::trace::JSTraceable;
@@ -70,6 +69,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 +81,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};
@@ -93,11 +93,12 @@ use script_traits::CompositorEvent::{KeyEvent, MouseButtonEvent, MouseMoveEvent,
use script_traits::CompositorEvent::{TouchEvent, TouchpadPressureEvent};
use script_traits::WebVREventMsg;
use script_traits::webdriver_msg::WebDriverScriptCommand;
use serviceworkerjob::{Job, JobQueue, AsyncJobHandler, FinishJobHandler, InvokeType, SettleType};
use serviceworkerjob::{Job, JobQueue, AsyncJobHandler};
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;
@@ -110,7 +111,6 @@ use std::thread;
use style::context::ReflowGoal;
use style::dom::{TNode, UnsafeNode};
use style::thread_state;
use task_source::TaskSource;
use task_source::dom_manipulation::{DOMManipulationTask, DOMManipulationTaskSource};
use task_source::file_reading::FileReadingTaskSource;
use task_source::history_traversal::HistoryTraversalTaskSource;
@@ -477,7 +477,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>>,
@@ -565,6 +565,13 @@ impl ScriptThreadFactory for ScriptThread {
}
impl ScriptThread {
pub fn invoke_perform_a_microtask_checkpoint() {
SCRIPT_THREAD_ROOT.with(|root| {
let script_thread = unsafe { &*root.get().unwrap() };
script_thread.perform_a_microtask_checkpoint()
})
}
pub fn page_headers_available(id: &PipelineId, metadata: Option<Metadata>)
-> Option<Root<ServoParser>> {
SCRIPT_THREAD_ROOT.with(|root| {
@@ -694,7 +701,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 +783,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 +796,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 +813,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 +878,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.
@@ -1452,49 +1467,11 @@ impl ScriptThread {
}
pub fn dispatch_job_queue(&self, job_handler: Box<AsyncJobHandler>) {
let scope_url = job_handler.scope_url.clone();
let queue_ref = self.job_queue_map.0.borrow();
let front_job = {
let job_vec = queue_ref.get(&scope_url);
job_vec.unwrap().first().unwrap()
};
match job_handler.invoke_type {
InvokeType::Run => (&*self.job_queue_map).run_job(job_handler, self),
InvokeType::Register => self.job_queue_map.run_register(front_job, job_handler, self),
InvokeType::Update => self.job_queue_map.update(front_job, &*front_job.client.global(), self),
InvokeType::Settle(settle_type) => {
let promise = &front_job.promise;
let global = &*front_job.client.global();
let trusted_global = Trusted::new(global);
let _ac = JSAutoCompartment::new(global.get_cx(), promise.reflector().get_jsobject().get());
match settle_type {
SettleType::Resolve(reg) => promise.resolve_native(global.get_cx(), &*reg.root()),
SettleType::Reject(err) => promise.reject_error(global.get_cx(), err)
}
let finish_job_handler = box FinishJobHandler::new(scope_url, trusted_global);
self.queue_finish_job(finish_job_handler, global);
}
}
self.job_queue_map.run_job(job_handler, self);
}
pub fn queue_serviceworker_job(&self, async_job_handler: Box<AsyncJobHandler>, global: &GlobalScope) {
let _ = self.dom_manipulation_task_source.queue(async_job_handler, &*global);
}
pub fn queue_finish_job(&self, finish_job_handler: Box<FinishJobHandler>, global: &GlobalScope) {
let _ = self.dom_manipulation_task_source.queue(finish_job_handler, global);
}
pub fn invoke_finish_job(&self, finish_job_handler: Box<FinishJobHandler>) {
let job_queue = &*self.job_queue_map;
let global = &*finish_job_handler.global.root();
let scope_url = (*finish_job_handler).scope_url;
job_queue.finish_job(scope_url, global, self);
}
pub fn invoke_job_update(&self, job: &Job, global: &GlobalScope) {
let job_queue = &*self.job_queue_map;
job_queue.update(job, global, self);
pub fn dom_manipulation_task_source(&self) -> &DOMManipulationTaskSource {
&self.dom_manipulation_task_source
}
/// Handles a request for the window title.
@@ -2108,30 +2085,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))
}
}