| /* 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 https://mozilla.org/MPL/2.0/. */ |
| |
| use core::cell::RefCell; |
| use core::sync::atomic::Ordering; |
| use std::cell::Ref; |
| use std::collections::HashMap; |
| |
| use base::id::PipelineId; |
| use strum::VariantArray; |
| |
| use crate::messaging::ScriptEventLoopSender; |
| use crate::task::TaskCanceller; |
| use crate::task_source::{TaskSource, TaskSourceName}; |
| |
| #[derive(JSTraceable, MallocSizeOf)] |
| enum TaskCancellers { |
| /// A shared canceller that is used for workers, which can create multiple TaskManagers, but all |
| /// of them need to have the same canceller flag for all task sources. |
| Shared(TaskCanceller), |
| /// For `Window` each `TaskSource` has its own canceller. |
| OnePerTaskSource(RefCell<HashMap<TaskSourceName, TaskCanceller>>), |
| } |
| |
| impl TaskCancellers { |
| fn get(&self, name: TaskSourceName) -> TaskCanceller { |
| match self { |
| Self::Shared(canceller) => canceller.clone(), |
| Self::OnePerTaskSource(map) => map.borrow_mut().entry(name).or_default().clone(), |
| } |
| } |
| |
| fn cancel_all_tasks_and_ignore_future_tasks(&self) { |
| match self { |
| Self::Shared(canceller) => canceller.cancelled.store(true, Ordering::SeqCst), |
| Self::OnePerTaskSource(..) => { |
| // We must create the canceller if they aren't created because we want future |
| // tasks to be ignored completely. |
| for task_source_name in TaskSourceName::VARIANTS.iter() { |
| self.get(*task_source_name) |
| .cancelled |
| .store(true, Ordering::SeqCst) |
| } |
| }, |
| } |
| } |
| |
| fn cancel_pending_tasks_for_source(&self, task_source_name: TaskSourceName) { |
| let Self::OnePerTaskSource(map) = self else { |
| unreachable!( |
| "It isn't possible to cancel pending tasks for Worker \ |
| TaskManager's without ignoring future tasks." |
| ) |
| }; |
| |
| // Remove the canceller from the map so that the next time a task like this is |
| // queued, it has a fresh, uncancelled canceller. |
| if let Some(canceller) = map.borrow_mut().remove(&task_source_name) { |
| // Cancel any tasks that use the current canceller. |
| canceller.cancelled.store(true, Ordering::SeqCst); |
| } |
| } |
| } |
| |
| macro_rules! task_source_functions { |
| ($self:ident, $task_source:ident, $task_source_name:ident) => { |
| pub(crate) fn $task_source(&$self) -> TaskSource<'_> { |
| TaskSource { |
| task_manager: $self, |
| name: TaskSourceName::$task_source_name, |
| } |
| } |
| }; |
| } |
| |
| #[derive(JSTraceable, MallocSizeOf)] |
| pub(crate) struct TaskManager { |
| sender: RefCell<Option<ScriptEventLoopSender>>, |
| #[no_trace] |
| pipeline_id: PipelineId, |
| cancellers: TaskCancellers, |
| } |
| |
| impl TaskManager { |
| pub(crate) fn new( |
| sender: Option<ScriptEventLoopSender>, |
| pipeline_id: PipelineId, |
| shared_canceller: Option<TaskCanceller>, |
| ) -> Self { |
| let cancellers = match shared_canceller { |
| Some(shared_canceller) => TaskCancellers::Shared(shared_canceller), |
| None => TaskCancellers::OnePerTaskSource(Default::default()), |
| }; |
| let sender = RefCell::new(sender); |
| |
| TaskManager { |
| sender, |
| pipeline_id, |
| cancellers, |
| } |
| } |
| |
| pub(crate) fn pipeline_id(&self) -> PipelineId { |
| self.pipeline_id |
| } |
| |
| pub(crate) fn sender(&self) -> Ref<'_, Option<ScriptEventLoopSender>> { |
| self.sender.borrow() |
| } |
| |
| pub(crate) fn canceller(&self, name: TaskSourceName) -> TaskCanceller { |
| self.cancellers.get(name) |
| } |
| |
| /// Update the sender for this [`TaskSource`]. This is used by dedicated workers, which only have a |
| /// sender while handling messages (as their sender prevents the main thread Worker object from being |
| /// garbage collected). |
| pub(crate) fn set_sender(&self, sender: Option<ScriptEventLoopSender>) { |
| *self.sender.borrow_mut() = sender; |
| } |
| |
| /// Cancel all queued but unexecuted tasks and ignore all subsequently queued tasks. |
| pub(crate) fn cancel_all_tasks_and_ignore_future_tasks(&self) { |
| self.cancellers.cancel_all_tasks_and_ignore_future_tasks(); |
| } |
| |
| /// Cancel all queued but unexecuted tasks for the given task source, but subsequently queued |
| /// tasks will not be ignored. |
| pub(crate) fn cancel_pending_tasks_for_source(&self, task_source_name: TaskSourceName) { |
| self.cancellers |
| .cancel_pending_tasks_for_source(task_source_name); |
| } |
| |
| task_source_functions!(self, bitmap_task_source, Bitmap); |
| task_source_functions!(self, canvas_blob_task_source, Canvas); |
| task_source_functions!(self, clipboard_task_source, Clipboard); |
| task_source_functions!(self, database_access_task_source, DatabaseAccess); |
| task_source_functions!(self, dom_manipulation_task_source, DOMManipulation); |
| task_source_functions!(self, file_reading_task_source, FileReading); |
| task_source_functions!(self, font_loading_task_source, FontLoading); |
| task_source_functions!(self, gamepad_task_source, Gamepad); |
| task_source_functions!(self, media_element_task_source, MediaElement); |
| task_source_functions!(self, networking_task_source, Networking); |
| task_source_functions!(self, performance_timeline_task_source, PerformanceTimeline); |
| task_source_functions!(self, port_message_queue, PortMessage); |
| task_source_functions!(self, remote_event_task_source, RemoteEvent); |
| task_source_functions!(self, timer_task_source, Timer); |
| task_source_functions!(self, user_interaction_task_source, UserInteraction); |
| task_source_functions!(self, websocket_task_source, WebSocket); |
| task_source_functions!( |
| self, |
| intersection_observer_task_source, |
| IntersectionObserver |
| ); |
| task_source_functions!(self, webgpu_task_source, WebGPU); |
| } |