diff --git a/components/script/dom/bindings/settings_stack.rs b/components/script/dom/bindings/settings_stack.rs index 10afc833cb4..dec63924915 100644 --- a/components/script/dom/bindings/settings_stack.rs +++ b/components/script/dom/bindings/settings_stack.rs @@ -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> = 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, } 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(); + } } } 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::()); scope.upcast::().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::().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..cc42ef18d06 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,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::() { - return ScriptThread::flush_promise_jobs(self); + return ScriptThread::invoke_perform_a_microtask_checkpoint(); } if let Some(worker) = self.downcast::() { - 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::() { - return ScriptThread::enqueue_promise_job(job, self); + return ScriptThread::enqueue_microtask(job); } if let Some(worker) = self.downcast::() { - return worker.enqueue_promise_job(job); + return worker.enqueue_microtask(job); } unreachable!(); } diff --git a/components/script/dom/htmlscriptelement.rs b/components/script/dom/htmlscriptelement.rs index e1e8dabac7b..8703a45ea82 100644 --- a/components/script/dom/htmlscriptelement.rs +++ b/components/script/dom/htmlscriptelement.rs @@ -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::().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::(); + 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); 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::()); scope.upcast::().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::().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, - 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::(); 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, -} - -impl Runnable for FlushPromiseJobs { - fn handler(self: Box) { - let global = self.global.root(); - global.do_flush_promise_jobs(); - } -} diff --git a/components/script/lib.rs b/components/script/lib.rs index 8c85efb489f..9cc8f84bbde 100644 --- a/components/script/lib.rs +++ b/components/script/lib.rs @@ -109,6 +109,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>, + /// https://html.spec.whatwg.org/multipage/#performing-a-microtask-checkpoint + performing_a_microtask_checkpoint: Cell, +} + +#[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, + 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(&self, target_provider: F) + where F: Fn(PipelineId) -> Option> + { + 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, - 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: &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(&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, 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..c50011bf19c 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -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>, @@ -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) -> Option> { 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) { - 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, global: &GlobalScope) { - let _ = self.dom_manipulation_task_source.queue(async_job_handler, &*global); - } - - pub fn queue_finish_job(&self, finish_job_handler: Box, global: &GlobalScope) { - let _ = self.dom_manipulation_task_source.queue(finish_job_handler, global); - } - - pub fn invoke_finish_job(&self, finish_job_handler: Box) { - 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, 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)) } } diff --git a/components/script/serviceworkerjob.rs b/components/script/serviceworkerjob.rs index 1eb489ac4a9..69151c621ff 100644 --- a/components/script/serviceworkerjob.rs +++ b/components/script/serviceworkerjob.rs @@ -10,20 +10,23 @@ use dom::bindings::cell::DOMRefCell; use dom::bindings::error::Error; use dom::bindings::js::JS; -use dom::bindings::refcounted::Trusted; +use dom::bindings::refcounted::{Trusted, TrustedPromise}; use dom::bindings::reflector::DomObject; use dom::client::Client; use dom::globalscope::GlobalScope; use dom::promise::Promise; use dom::serviceworkerregistration::ServiceWorkerRegistration; use dom::urlhelper::UrlHelper; +use js::jsapi::JSAutoCompartment; use script_thread::{ScriptThread, Runnable}; use servo_url::ServoUrl; use std::cmp::PartialEq; use std::collections::HashMap; use std::rc::Rc; +use task_source::TaskSource; +use task_source::dom_manipulation::DOMManipulationTaskSource; -#[derive(PartialEq, Clone, Debug, JSTraceable)] +#[derive(PartialEq, Copy, Clone, Debug, JSTraceable)] pub enum JobType { Register, Unregister, @@ -36,14 +39,6 @@ pub enum SettleType { Reject(Error) } -// This encapsulates what operation to invoke of JobQueue from script thread -pub enum InvokeType { - Settle(SettleType), - Run, - Register, - Update -} - #[must_root] #[derive(JSTraceable)] pub struct Job { @@ -98,36 +93,14 @@ impl PartialEq for Job { } } -pub struct FinishJobHandler { - pub scope_url: ServoUrl, - pub global: Trusted, -} - -impl FinishJobHandler { - pub fn new(scope_url: ServoUrl, global: Trusted) -> FinishJobHandler { - FinishJobHandler { - scope_url: scope_url, - global: global - } - } -} - -impl Runnable for FinishJobHandler { - fn main_thread_handler(self: Box, script_thread: &ScriptThread) { - script_thread.invoke_finish_job(self); - } -} - pub struct AsyncJobHandler { pub scope_url: ServoUrl, - pub invoke_type: InvokeType } impl AsyncJobHandler { - fn new(scope_url: ServoUrl, invoke_type: InvokeType) -> AsyncJobHandler { + fn new(scope_url: ServoUrl) -> AsyncJobHandler { AsyncJobHandler { scope_url: scope_url, - invoke_type: invoke_type } } } @@ -153,25 +126,29 @@ impl JobQueue { job: Job, global: &GlobalScope, script_thread: &ScriptThread) { + debug!("scheduling {:?} job", job.job_type); let mut queue_ref = self.0.borrow_mut(); let job_queue = queue_ref.entry(job.scope_url.clone()).or_insert(vec![]); // Step 1 if job_queue.is_empty() { let scope_url = job.scope_url.clone(); job_queue.push(job); - let run_job_handler = AsyncJobHandler::new(scope_url, InvokeType::Run); - script_thread.queue_serviceworker_job(box run_job_handler, global); + let run_job_handler = box AsyncJobHandler::new(scope_url); + let _ = script_thread.dom_manipulation_task_source().queue(run_job_handler, global); + debug!("queued task to run newly-queued job"); } else { // Step 2 let mut last_job = job_queue.pop().unwrap(); if job == last_job && !last_job.promise.is_settled() { last_job.append_equivalent_job(job); job_queue.push(last_job); + debug!("appended equivalent job"); } else { // restore the popped last_job job_queue.push(last_job); // and push this new job to job queue job_queue.push(job); + debug!("pushed onto job queue job"); } } } @@ -179,41 +156,46 @@ impl JobQueue { #[allow(unrooted_must_root)] // https://w3c.github.io/ServiceWorker/#run-job-algorithm pub fn run_job(&self, run_job_handler: Box, script_thread: &ScriptThread) { - let queue_ref = &*self.0.borrow(); - let front_job = { - let job_vec = queue_ref.get(&run_job_handler.scope_url); - job_vec.unwrap().first().unwrap() + debug!("running a job"); + let url = { + let queue_ref = self.0.borrow(); + let front_job = { + let job_vec = queue_ref.get(&run_job_handler.scope_url); + job_vec.unwrap().first().unwrap() + }; + let scope_url = front_job.scope_url.clone(); + match front_job.job_type { + JobType::Register => self.run_register(front_job, run_job_handler, script_thread), + JobType::Update => self.update(front_job, script_thread), + JobType::Unregister => unreachable!(), + }; + scope_url }; - let global = &*front_job.client.global(); - let handler = *run_job_handler; - match front_job.job_type { - JobType::Register => { - let register_job_handler = AsyncJobHandler::new(handler.scope_url, InvokeType::Register); - script_thread.queue_serviceworker_job(box register_job_handler, global); - }, - JobType::Update => { - let update_job_handler = AsyncJobHandler::new(handler.scope_url, InvokeType::Update); - script_thread.queue_serviceworker_job(box update_job_handler, global); - } - _ => { /* TODO implement Unregister */ } - } + self.finish_job(url, script_thread); } #[allow(unrooted_must_root)] // https://w3c.github.io/ServiceWorker/#register-algorithm - pub fn run_register(&self, job: &Job, register_job_handler: Box, script_thread: &ScriptThread) { - let global = &*job.client.global(); + fn run_register(&self, job: &Job, register_job_handler: Box, script_thread: &ScriptThread) { + debug!("running register job"); let AsyncJobHandler { scope_url, .. } = *register_job_handler; // Step 1-3 if !UrlHelper::is_origin_trustworthy(&job.script_url) { - let settle_type = SettleType::Reject(Error::Type("Invalid script ServoURL".to_owned())); - let async_job_handler = AsyncJobHandler::new(scope_url, InvokeType::Settle(settle_type)); - return script_thread.queue_serviceworker_job(box async_job_handler, global); + // Step 1.1 + reject_job_promise(job, + Error::Type("Invalid script ServoURL".to_owned()), + script_thread.dom_manipulation_task_source()); + // Step 1.2 (see run_job) + return; } else if job.script_url.origin() != job.referrer.origin() || job.scope_url.origin() != job.referrer.origin() { - let settle_type = SettleType::Reject(Error::Security); - let async_job_handler = AsyncJobHandler::new(scope_url, InvokeType::Settle(settle_type)); - return script_thread.queue_serviceworker_job(box async_job_handler, global); + // Step 2.1/3.1 + reject_job_promise(job, + Error::Security, + script_thread.dom_manipulation_task_source()); + // Step 2.2/3.2 (see run_job) + return; } + // Step 4-5 if let Some(reg) = script_thread.handle_get_registration(&job.scope_url) { // Step 5.1 @@ -223,70 +205,144 @@ impl JobQueue { // Step 5.3 if let Some(ref newest_worker) = reg.get_newest_worker() { if (&*newest_worker).get_script_url() == job.script_url { - let settle_type = SettleType::Resolve(Trusted::new(&*reg)); - let async_job_handler = AsyncJobHandler::new(scope_url, InvokeType::Settle(settle_type)); - script_thread.queue_serviceworker_job(box async_job_handler, global); - let finish_job_handler = box FinishJobHandler::new(job.scope_url.clone(), Trusted::new(&*global)); - script_thread.queue_finish_job(finish_job_handler, &*global); + // Step 5.3.1 + resolve_job_promise(job, &*reg, script_thread.dom_manipulation_task_source()); + // Step 5.3.2 (see run_job) + return; } } } else { // Step 6.1 + let global = &*job.client.global(); let pipeline = global.pipeline_id(); let new_reg = ServiceWorkerRegistration::new(&*global, &job.script_url, scope_url); script_thread.handle_serviceworker_registration(&job.scope_url, &*new_reg, pipeline); } // Step 7 - script_thread.invoke_job_update(job, &*global); + self.update(job, script_thread) } #[allow(unrooted_must_root)] // https://w3c.github.io/ServiceWorker/#finish-job-algorithm - pub fn finish_job(&self, scope_url: ServoUrl, global: &GlobalScope, script_thread: &ScriptThread) { - if let Some(job_vec) = (*self.0.borrow_mut()).get_mut(&scope_url) { - if job_vec.first().map_or(false, |job| job.scope_url == scope_url) { - let _ = job_vec.remove(0); - } - if !job_vec.is_empty() { - let run_job_handler = AsyncJobHandler::new(scope_url, InvokeType::Run); - script_thread.queue_serviceworker_job(box run_job_handler, global); - } + pub fn finish_job(&self, scope_url: ServoUrl, script_thread: &ScriptThread) { + debug!("finishing previous job"); + let run_job = if let Some(job_vec) = (*self.0.borrow_mut()).get_mut(&scope_url) { + assert_eq!(job_vec.first().as_ref().unwrap().scope_url, scope_url); + let _ = job_vec.remove(0); + !job_vec.is_empty() } else { warn!("non-existent job vector for Servourl: {:?}", scope_url); + false + }; + + if run_job { + debug!("further jobs in queue after finishing"); + let handler = box AsyncJobHandler::new(scope_url); + self.run_job(handler, script_thread); } } // https://w3c.github.io/ServiceWorker/#update-algorithm - pub fn update(&self, job: &Job, global: &GlobalScope, script_thread: &ScriptThread) { + fn update(&self, job: &Job, script_thread: &ScriptThread) { + debug!("running update job"); + // Step 1 let reg = match script_thread.handle_get_registration(&job.scope_url) { Some(reg) => reg, - None => return - }; - // Step 1 - if reg.get_uninstalling() { - let err_type = Error::Type("Update called on an uninstalling registration".to_owned()); - let settle_type = SettleType::Reject(err_type); - let async_job_handler = AsyncJobHandler::new(job.scope_url.clone(), InvokeType::Settle(settle_type)); - return script_thread.queue_serviceworker_job(box async_job_handler, global); - } - let newest_worker = match reg.get_newest_worker() { - Some(worker) => worker, - None => return + None => { + let err_type = Error::Type("No registration to update".to_owned()); + // Step 2.1 + reject_job_promise(job, err_type, script_thread.dom_manipulation_task_source()); + // Step 2.2 (see run_job) + return; + } }; // Step 2 - if (&*newest_worker).get_script_url() == job.script_url && job.job_type == JobType::Update { - // Step 4 - let err_type = Error::Type("Invalid script ServoURL".to_owned()); - let settle_type = SettleType::Reject(err_type); - let async_job_handler = AsyncJobHandler::new(job.scope_url.clone(), InvokeType::Settle(settle_type)); - script_thread.queue_serviceworker_job(box async_job_handler, global); - } else { - job.client.set_controller(&*newest_worker); - let settle_type = SettleType::Resolve(Trusted::new(&*reg)); - let async_job_handler = AsyncJobHandler::new(job.scope_url.clone(), InvokeType::Settle(settle_type)); - script_thread.queue_serviceworker_job(box async_job_handler, global); + if reg.get_uninstalling() { + let err_type = Error::Type("Update called on an uninstalling registration".to_owned()); + // Step 2.1 + reject_job_promise(job, err_type, script_thread.dom_manipulation_task_source()); + // Step 2.2 (see run_job) + return; } - let finish_job_handler = box FinishJobHandler::new(job.scope_url.clone(), Trusted::new(global)); - script_thread.queue_finish_job(finish_job_handler, global); + // Step 3 + let newest_worker = reg.get_newest_worker(); + let newest_worker_url = newest_worker.as_ref().map(|w| w.get_script_url()); + // Step 4 + if newest_worker_url.as_ref() == Some(&job.script_url) && job.job_type == JobType::Update { + let err_type = Error::Type("Invalid script ServoURL".to_owned()); + // Step 4.1 + reject_job_promise(job, err_type, script_thread.dom_manipulation_task_source()); + // Step 4.2 (see run_job) + return; + } + // Step 8 + if let Some(newest_worker) = newest_worker { + job.client.set_controller(&*newest_worker); + // Step 8.1 + resolve_job_promise(job, &*reg, script_thread.dom_manipulation_task_source()); + // Step 8.2 present in run_job + } + // TODO Step 9 (create new service worker) } } + +struct AsyncPromiseSettle { + global: Trusted, + promise: TrustedPromise, + settle_type: SettleType, +} + +impl Runnable for AsyncPromiseSettle { + #[allow(unrooted_must_root)] + fn handler(self: Box) { + let global = self.global.root(); + let settle_type = self.settle_type.clone(); + let promise = self.promise.root(); + settle_job_promise(&*global, &*promise, settle_type) + } +} + +impl AsyncPromiseSettle { + #[allow(unrooted_must_root)] + fn new(promise: Rc, settle_type: SettleType) -> AsyncPromiseSettle { + AsyncPromiseSettle { + global: Trusted::new(&*promise.global()), + promise: TrustedPromise::new(promise), + settle_type: settle_type, + } + } +} + +fn settle_job_promise(global: &GlobalScope, promise: &Promise, settle: SettleType) { + let _ac = JSAutoCompartment::new(global.get_cx(), promise.reflector().get_jsobject().get()); + match settle { + SettleType::Resolve(reg) => promise.resolve_native(global.get_cx(), &*reg.root()), + SettleType::Reject(err) => promise.reject_error(global.get_cx(), err), + }; +} + +fn queue_settle_promise_for_job(job: &Job, settle: SettleType, task_source: &DOMManipulationTaskSource) { + let task = box AsyncPromiseSettle::new(job.promise.clone(), settle); + let global = job.client.global(); + let _ = task_source.queue(task, &*global); + +} + +// https://w3c.github.io/ServiceWorker/#reject-job-promise-algorithm +// https://w3c.github.io/ServiceWorker/#resolve-job-promise-algorithm +fn queue_settle_promise(job: &Job, settle: SettleType, task_source: &DOMManipulationTaskSource) { + // Step 1 + queue_settle_promise_for_job(job, settle.clone(), task_source); + // Step 2 + for job in &job.equivalent_jobs { + queue_settle_promise_for_job(job, settle.clone(), task_source); + } +} + +fn reject_job_promise(job: &Job, err: Error, task_source: &DOMManipulationTaskSource) { + queue_settle_promise(job, SettleType::Reject(err), task_source) +} + +fn resolve_job_promise(job: &Job, reg: &ServiceWorkerRegistration, task_source: &DOMManipulationTaskSource) { + queue_settle_promise(job, SettleType::Resolve(Trusted::new(reg)), task_source) +} diff --git a/tests/wpt/metadata/html/webappapis/scripting/event-loops/microtask_after_raf.html.ini b/tests/wpt/metadata/html/webappapis/scripting/event-loops/microtask_after_raf.html.ini deleted file mode 100644 index 9ebe79a9879..00000000000 --- a/tests/wpt/metadata/html/webappapis/scripting/event-loops/microtask_after_raf.html.ini +++ /dev/null @@ -1,6 +0,0 @@ -[microtask_after_raf.html] - type: testharness - disabled: https://github.com/servo/servo/issues/13501 - [Microtask execute immediately after script] - expected: FAIL - diff --git a/tests/wpt/metadata/html/webappapis/scripting/event-loops/microtask_after_script.html.ini b/tests/wpt/metadata/html/webappapis/scripting/event-loops/microtask_after_script.html.ini deleted file mode 100644 index 49871c85772..00000000000 --- a/tests/wpt/metadata/html/webappapis/scripting/event-loops/microtask_after_script.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[microtask_after_script.html] - type: testharness - [Microtask immediately after script] - expected: FAIL - diff --git a/tests/wpt/metadata/html/webappapis/scripting/event-loops/task_microtask_ordering.html.ini b/tests/wpt/metadata/html/webappapis/scripting/event-loops/task_microtask_ordering.html.ini index 8d063ebb67f..a9a640fee27 100644 --- a/tests/wpt/metadata/html/webappapis/scripting/event-loops/task_microtask_ordering.html.ini +++ b/tests/wpt/metadata/html/webappapis/scripting/event-loops/task_microtask_ordering.html.ini @@ -1,8 +1,5 @@ [task_microtask_ordering.html] type: testharness - [Basic task and microtask ordering] - expected: FAIL - [Level 1 bossfight (synthetic click)] expected: FAIL - + bug: https://github.com/servo/servo/issues/1980