mirror of
https://github.com/servo/servo.git
synced 2025-08-06 14:10:11 +01:00
Auto merge of #15189 - jdm:microtasks, r=nox
Implement microtask checkpoints 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 <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/15189) <!-- Reviewable:end -->
This commit is contained in:
commit
cbcafd18f4
14 changed files with 340 additions and 307 deletions
|
@ -11,6 +11,7 @@ use js::jsapi::JSTracer;
|
|||
use js::jsapi::UnhideScriptedCaller;
|
||||
use js::rust::Runtime;
|
||||
use std::cell::RefCell;
|
||||
use std::thread;
|
||||
|
||||
thread_local!(static STACK: RefCell<Vec<StackEntry>> = RefCell::new(Vec::new()));
|
||||
|
||||
|
@ -36,7 +37,7 @@ pub unsafe fn trace(tracer: *mut JSTracer) {
|
|||
|
||||
/// RAII struct that pushes and pops entries from the script settings stack.
|
||||
pub struct AutoEntryScript {
|
||||
global: usize,
|
||||
global: Root<GlobalScope>,
|
||||
}
|
||||
|
||||
impl AutoEntryScript {
|
||||
|
@ -50,7 +51,7 @@ impl AutoEntryScript {
|
|||
kind: StackEntryKind::Entry,
|
||||
});
|
||||
AutoEntryScript {
|
||||
global: global as *const _ as usize,
|
||||
global: Root::from_ref(global),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -62,12 +63,17 @@ impl Drop for AutoEntryScript {
|
|||
STACK.with(|stack| {
|
||||
let mut stack = stack.borrow_mut();
|
||||
let entry = stack.pop().unwrap();
|
||||
assert_eq!(&*entry.global as *const GlobalScope as usize,
|
||||
self.global,
|
||||
assert_eq!(&*entry.global as *const GlobalScope,
|
||||
&*self.global as *const GlobalScope,
|
||||
"Dropped AutoEntryScript out of order.");
|
||||
assert_eq!(entry.kind, StackEntryKind::Entry);
|
||||
trace!("Clean up after running script with {:p}", &*entry.global);
|
||||
})
|
||||
});
|
||||
|
||||
// Step 5
|
||||
if !thread::panicking() && incumbent_global().is_none() {
|
||||
self.global.perform_a_microtask_checkpoint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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,24 @@ 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) {
|
||||
/// Perform a microtask checkpoint.
|
||||
pub fn perform_a_microtask_checkpoint(&self) {
|
||||
if self.is::<Window>() {
|
||||
return ScriptThread::flush_promise_jobs(self);
|
||||
return ScriptThread::invoke_perform_a_microtask_checkpoint();
|
||||
}
|
||||
if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
|
||||
return worker.flush_promise_jobs();
|
||||
return worker.perform_a_microtask_checkpoint();
|
||||
}
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
/// Enqueue a promise callback for subsequent execution.
|
||||
pub fn enqueue_promise_job(&self, job: EnqueuedPromiseCallback) {
|
||||
/// Enqueue a microtask for subsequent execution.
|
||||
pub fn enqueue_microtask(&self, job: Microtask) {
|
||||
if self.is::<Window>() {
|
||||
return ScriptThread::enqueue_promise_job(job, self);
|
||||
return ScriptThread::enqueue_microtask(job);
|
||||
}
|
||||
if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
|
||||
return worker.enqueue_promise_job(job);
|
||||
return worker.enqueue_microtask(job);
|
||||
}
|
||||
unreachable!();
|
||||
}
|
||||
|
|
|
@ -486,11 +486,7 @@ impl HTMLScriptElement {
|
|||
document.set_current_script(Some(self));
|
||||
|
||||
// Step 5.a.2.
|
||||
let window = window_from_node(self);
|
||||
let line_number = if script.external { 1 } else { self.line_number as u32 };
|
||||
rooted!(in(window.get_cx()) let mut rval = UndefinedValue());
|
||||
window.upcast::<GlobalScope>().evaluate_script_on_global_with_result(
|
||||
&script.text, script.url.as_str(), rval.handle_mut(), line_number);
|
||||
self.run_a_classic_script(&script);
|
||||
|
||||
// Step 6.
|
||||
document.set_current_script(old_script.r());
|
||||
|
@ -506,6 +502,24 @@ impl HTMLScriptElement {
|
|||
}
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#run-a-classic-script
|
||||
pub fn run_a_classic_script(&self, script: &ClassicScript) {
|
||||
// TODO use a settings object rather than this element's document/window
|
||||
// Step 2
|
||||
let document = document_from_node(self);
|
||||
if !document.is_fully_active() || !document.is_scripting_enabled() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Steps 4-10
|
||||
let window = window_from_node(self);
|
||||
let line_number = if script.external { 1 } else { self.line_number as u32 };
|
||||
rooted!(in(window.get_cx()) let mut rval = UndefinedValue());
|
||||
let global = window.upcast::<GlobalScope>();
|
||||
global.evaluate_script_on_global_with_result(
|
||||
&script.text, script.url.as_str(), rval.handle_mut(), line_number);
|
||||
}
|
||||
|
||||
pub fn queue_error_event(&self) {
|
||||
let window = window_from_node(self);
|
||||
window.dom_manipulation_task_source().queue_simple_event(self.upcast(), atom!("error"), &window);
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue