diff options
author | Martin Robinson <mrobinson@igalia.com> | 2024-12-12 09:43:58 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-12-12 08:43:58 +0000 |
commit | 2bcee38e521687c245222e229a05a586696013df (patch) | |
tree | 8a59973468824f354729fba1ce2ca808b3e050da /components | |
parent | 7fcde1f7a30138544f8ad1b279c5e4047e643831 (diff) | |
download | servo-2bcee38e521687c245222e229a05a586696013df.tar.gz servo-2bcee38e521687c245222e229a05a586696013df.zip |
script: Remove `note_rendering_opportunity` and `rendering_opportunity` (#34575)
A rendering opportunity is now unconditionally triggered by handling IPC
messages in the `ScriptThread`, unless animations are running in which
case it's driven by the compositor. We can now remove calls to
`note_rendering_opportunity` and `rendering_opportunity`.
There is one tricky case, which is when a promise completion during a
microtask checkpoint dirties the page again. In this case we need to
trigger a new rendering opportunity, unless animations are running. In
a followup change, when not driven by the compositor, rendering
opportunities will be driven by a timed task, meaning we can remove this
workaround.
Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Diffstat (limited to 'components')
-rw-r--r-- | components/script/dom/document.rs | 6 | ||||
-rw-r--r-- | components/script/dom/node.rs | 3 | ||||
-rw-r--r-- | components/script/dom/resizeobserver.rs | 5 | ||||
-rw-r--r-- | components/script/dom/window.rs | 4 | ||||
-rw-r--r-- | components/script/script_thread.rs | 95 | ||||
-rw-r--r-- | components/script/task_source/rendering.rs | 16 |
6 files changed, 63 insertions, 66 deletions
diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 562e8ea253f..550832a47bb 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -2064,6 +2064,12 @@ impl Document { } } + /// Whether or not this `Document` has any active requestAnimationFrame callbacks + /// registered. + pub(crate) fn has_active_request_animation_frame_callbacks(&self) -> bool { + !self.animation_frame_list.borrow().is_empty() + } + /// <https://html.spec.whatwg.org/multipage/#dom-window-requestanimationframe> pub(crate) fn request_animation_frame(&self, callback: AnimationFrameCallback) -> u32 { let ident = self.animation_frame_ident.get() + 1; diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index b8e3531508e..a7cfa0dc772 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -2129,8 +2129,6 @@ impl Node { MutationObserver::queue_a_mutation_record(parent, mutation); } node.owner_doc().remove_script_and_layout_blocker(); - - ScriptThread::note_rendering_opportunity(window_from_node(parent).pipeline_id()); } /// <https://dom.spec.whatwg.org/#concept-node-replace-all> @@ -2254,7 +2252,6 @@ impl Node { MutationObserver::queue_a_mutation_record(parent, mutation); } parent.owner_doc().remove_script_and_layout_blocker(); - ScriptThread::note_rendering_opportunity(window_from_node(parent).pipeline_id()); } /// <https://dom.spec.whatwg.org/#concept-node-clone> diff --git a/components/script/dom/resizeobserver.rs b/components/script/dom/resizeobserver.rs index c5489002824..9fd4fd58014 100644 --- a/components/script/dom/resizeobserver.rs +++ b/components/script/dom/resizeobserver.rs @@ -26,7 +26,6 @@ use crate::dom::resizeobserverentry::ResizeObserverEntry; use crate::dom::resizeobserversize::{ResizeObserverSize, ResizeObserverSizeImpl}; use crate::dom::window::Window; use crate::script_runtime::CanGc; -use crate::script_thread::ScriptThread; /// <https://drafts.csswg.org/resize-observer/#calculate-depth-for-node> #[derive(Debug, Default, PartialEq, PartialOrd)] @@ -191,10 +190,6 @@ impl ResizeObserverMethods<crate::DomTypeHolder> for ResizeObserver { self.observation_targets .borrow_mut() .push((resize_observation, Dom::from_ref(target))); - - // Note: noting a rendering opportunity here is necessary - // to make /resize-observer/iframe-same-origin.html PASS. - ScriptThread::note_rendering_opportunity(window_from_node(target).pipeline_id()); } /// <https://drafts.csswg.org/resize-observer/#dom-resizeobserver-unobserve> diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index 34447c933f2..0c2f8ef57ed 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -1809,6 +1809,10 @@ impl Window { ScriptThread::handle_tick_all_animations_for_testing(pipeline_id); } + pub(crate) fn reflows_suppressed(&self) -> bool { + self.suppress_reflow.get() + } + /// Reflows the page unconditionally if possible and not suppressed. This method will wait for /// the layout to complete. If there is no window size yet, the page is presumed invisible and /// no reflow is performed. If reflow is suppressed, no reflow will be performed for ForDisplay diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index e4c738f9b0b..095d3eefbf6 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -457,9 +457,6 @@ unsafe_no_jsmanaged_fields!(TaskQueue<MainThreadScriptMsg>); pub struct ScriptThread { /// <https://html.spec.whatwg.org/multipage/#last-render-opportunity-time> last_render_opportunity_time: DomRefCell<Option<Instant>>, - /// Used to batch rendering opportunities - #[no_trace] - update_the_rendering_task_queued_for_pipeline: DomRefCell<HashSet<PipelineId>>, /// The documents for pipelines managed by this thread documents: DomRefCell<DocumentCollection>, /// The window proxies known by this thread @@ -806,12 +803,6 @@ impl ScriptThreadFactory for ScriptThread { } impl ScriptThread { - pub fn note_rendering_opportunity(pipeline_id: PipelineId) { - with_script_thread(|script_thread| { - script_thread.rendering_opportunity(pipeline_id); - }) - } - pub fn runtime_handle() -> ParentRuntime { with_optional_script_thread(|script_thread| { script_thread.unwrap().js_runtime.prepare_for_new_child() @@ -1200,7 +1191,6 @@ impl ScriptThread { ScriptThread { documents: DomRefCell::new(DocumentCollection::default()), last_render_opportunity_time: Default::default(), - update_the_rendering_task_queued_for_pipeline: Default::default(), window_proxies: DomRefCell::new(HashMapTracedValues::new()), incomplete_loads: DomRefCell::new(vec![]), incomplete_parser_contexts: IncompleteParserContexts(RefCell::new(vec![])), @@ -1509,9 +1499,7 @@ impl ScriptThread { /// Attempt to update the rendering and then do a microtask checkpoint if rendering was actually /// updated. pub(crate) fn update_the_rendering(&self, requested_by_compositor: bool, can_gc: CanGc) { - self.update_the_rendering_task_queued_for_pipeline - .borrow_mut() - .clear(); + *self.last_render_opportunity_time.borrow_mut() = Some(Instant::now()); if !self.can_continue_running_inner() { return; @@ -1560,11 +1548,11 @@ impl ScriptThread { // Note: the spec reads: "for doc in docs" at each step // whereas this runs all steps per doc in docs. - for pipeline_id in documents_in_order { + for pipeline_id in documents_in_order.iter() { let document = self .documents .borrow() - .find_document(pipeline_id) + .find_document(*pipeline_id) .expect("Got pipeline for Document not managed by this ScriptThread."); // TODO(#31581): The steps in the "Revealing the document" section need to be implemente @@ -1573,7 +1561,7 @@ impl ScriptThread { // TODO: Should this be broken and to match the specification more closely? For instance see // https://html.spec.whatwg.org/multipage/#flush-autofocus-candidates. - self.process_pending_compositor_events(pipeline_id, can_gc); + self.process_pending_compositor_events(*pipeline_id, can_gc); // TODO(#31665): Implement the "run the scroll steps" from // https://drafts.csswg.org/cssom-view/#document-run-the-scroll-steps. @@ -1644,48 +1632,50 @@ impl ScriptThread { // https://drafts.csswg.org/css-position-4/#process-top-layer-removals. } - // Perform a microtask checkpoint as the specifications says that *update the rendering* should be - // run in a task and a microtask checkpoint is always done when running tasks. + // Perform a microtask checkpoint as the specifications says that *update the rendering* + // should be run in a task and a microtask checkpoint is always done when running tasks. self.perform_a_microtask_checkpoint(can_gc); - } - /// <https://html.spec.whatwg.org/multipage/#event-loop-processing-model:rendering-opportunity> - fn rendering_opportunity(&self, pipeline_id: PipelineId) { - *self.last_render_opportunity_time.borrow_mut() = Some(Instant::now()); - - // Note: the pipeline should be a navigable with a rendering opportunity, - // and we should use this opportunity to queue one task for each navigable with - // an opportunity in this script-thread. - let Some(document) = self.documents.borrow().find_document(pipeline_id) else { - warn!("Trying to update the rendering for closed pipeline {pipeline_id}."); + // If there are pending reflows, they were probably caused by the execution of + // the microtask checkpoint above and we should spin the event loop one more + // time to resolve them. + self.schedule_rendering_opportunity_if_necessary(); + } + + // If there are any pending reflows and we are not having rendering opportunities + // driven by the compositor, then schedule the next rendering opportunity. + // + // TODO: This is a workaround until rendering opportunities can be triggered from a + // timer in the script thread. + fn schedule_rendering_opportunity_if_necessary(&self) { + // If any Document has active animations of rAFs, then we should be receiving + // regular rendering opportunities from the compositor (or fake animation frame + // ticks). In this case, don't schedule an opportunity, just wait for the next + // one. + if self.documents.borrow().iter().any(|(_, document)| { + document.animations().running_animation_count() != 0 || + document.has_active_request_animation_frame_callbacks() + }) { return; - }; - let window = document.window(); - let task_manager = window.task_manager(); - let rendering_task_source = task_manager.rendering_task_source(); - let canceller = task_manager.task_canceller(TaskSourceName::Rendering); + } - if !self - .update_the_rendering_task_queued_for_pipeline - .borrow_mut() - .insert(pipeline_id) - { + let Some((_, document)) = self.documents.borrow().iter().find(|(_, document)| { + !document.window().reflows_suppressed() && document.needs_reflow().is_some() + }) else { return; - } + }; // Queues a task to update the rendering. // <https://html.spec.whatwg.org/multipage/#event-loop-processing-model:queue-a-global-task> // // Note: The specification says to queue a task using the navigable's active // window, but then updates the rendering for all documents. - let _ = rendering_task_source.queue_with_canceller( - task!(update_the_rendering: move || { - // This task is empty because any new IPC messages in the ScriptThread trigger a - // rendering update, unless animations are running, in which case updates are driven - // by the compositor. - }), - &canceller, - ); + // + // This task is empty because any new IPC messages in the ScriptThread trigger a + // rendering update when animations are not running. + let rendering_task_source = document.window().task_manager().rendering_task_source(); + let _ = + rendering_task_source.queue_unconditionally(task!(update_the_rendering: move || { })); } /// Handle incoming messages from other tasks and the task queue. @@ -1932,9 +1922,6 @@ impl ScriptThread { for document in docs.iter() { let _realm = enter_realm(&**document); document.maybe_queue_document_completion(); - - // Document load is a rendering opportunity. - ScriptThread::note_rendering_opportunity(document.window().pipeline_id()); } docs.clear(); } @@ -2847,7 +2834,6 @@ impl ScriptThread { self.profile_event(ScriptThreadEventCategory::Resize, Some(id), || { let window = self.documents.borrow().find_window(id); if let Some(ref window) = window { - self.rendering_opportunity(id); window.add_resize_event(size, size_type); return; } @@ -3390,12 +3376,6 @@ impl ScriptThread { // TODO: This should only dirty nodes that are waiting for a web font to finish loading! document.dirty_all_nodes(); - - // This is required because the handlers added to the promise exposed at - // `document.fonts.ready` are run by the event loop only when it performs a microtask - // checkpoint. Without the call below, this never happens and the promise is 'stuck' waiting - // to be resolved until another event forces a microtask checkpoint. - self.rendering_opportunity(pipeline_id); } /// Handles a worklet being loaded by triggering a relayout of the page. Does nothing if the @@ -3872,7 +3852,6 @@ impl ScriptThread { warn!("Compositor event sent to closed pipeline {pipeline_id}."); return; }; - self.rendering_opportunity(pipeline_id); document.note_pending_compositor_event(event); } diff --git a/components/script/task_source/rendering.rs b/components/script/task_source/rendering.rs index 43afa4abb68..0c652add2bb 100644 --- a/components/script/task_source/rendering.rs +++ b/components/script/task_source/rendering.rs @@ -43,3 +43,19 @@ impl TaskSource for RenderingTaskSource { self.0.send(msg_task).map_err(|_| ()) } } + +impl RenderingTaskSource { + /// This queues a task that will not be cancelled when its associated + /// global scope gets destroyed. + pub fn queue_unconditionally<T>(&self, task: T) -> Result<(), ()> + where + T: TaskOnce + 'static, + { + self.0.send(CommonScriptMsg::Task( + ScriptThreadEventCategory::NetworkEvent, + Box::new(task), + Some(self.1), + RenderingTaskSource::NAME, + )) + } +} |