Skip to main content

script/dom/workers/
abstractworkerglobalscope.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5#![cfg_attr(crown, allow(crown::jscontext_first_arg))]
6
7use crossbeam_channel::{Receiver, Select};
8#[cfg(feature = "devtools")]
9use devtools_traits::DevtoolScriptControlMsg;
10use rustc_hash::FxHashSet;
11use script_bindings::reflector::DomObject;
12use servo_base::generic_channel::RoutedReceiver;
13use servo_constellation_traits::WorkerAnimationFrameTick;
14
15use crate::dom::bindings::conversions::DerivedFrom;
16use crate::dom::dedicatedworkerglobalscope::AutoWorkerReset;
17use crate::dom::globalscope::GlobalScope;
18use crate::dom::worker::TrustedWorkerAddress;
19use crate::dom::workerglobalscope::WorkerGlobalScope;
20use crate::realms::enter_auto_realm;
21use crate::tasks::task_queue::{QueuedTaskConversion, TaskQueue};
22
23pub(crate) trait WorkerEventLoopMethods {
24    type WorkerMsg: QueuedTaskConversion + Send;
25    type ControlMsg;
26    type Event;
27    fn task_queue(&self) -> &TaskQueue<Self::WorkerMsg>;
28    fn handle_event(&self, event: Self::Event, cx: &mut js::context::JSContext) -> bool;
29    fn handle_worker_post_event(
30        &self,
31        worker: &TrustedWorkerAddress,
32    ) -> Option<AutoWorkerReset<'_>>;
33    fn from_control_msg(msg: Self::ControlMsg) -> Self::Event;
34    fn from_worker_msg(msg: Self::WorkerMsg) -> Self::Event;
35    #[cfg(feature = "devtools")]
36    fn from_devtools_msg(msg: DevtoolScriptControlMsg) -> Self::Event;
37    fn from_timer_msg() -> Self::Event;
38    fn from_animation_frame_tick_msg(_msg: WorkerAnimationFrameTick) -> Option<Self::Event> {
39        None
40    }
41    fn animation_frame_tick_receiver(&self) -> Option<&RoutedReceiver<WorkerAnimationFrameTick>> {
42        None
43    }
44    fn control_receiver(&self) -> &Receiver<Self::ControlMsg>;
45}
46
47/// <https://html.spec.whatwg.org/multipage/#worker-event-loop>
48pub(crate) fn run_worker_event_loop<T, WorkerMsg, Event>(
49    worker_scope: &T,
50    worker: Option<&TrustedWorkerAddress>,
51    cx: &mut js::context::JSContext,
52) where
53    WorkerMsg: QueuedTaskConversion + Send,
54    T: WorkerEventLoopMethods<WorkerMsg = WorkerMsg, Event = Event>
55        + DerivedFrom<WorkerGlobalScope>
56        + DerivedFrom<GlobalScope>
57        + DomObject,
58{
59    let scope = worker_scope.upcast::<WorkerGlobalScope>();
60    let task_queue = worker_scope.task_queue();
61
62    #[cfg(feature = "devtools")]
63    let devtools_never = crossbeam_channel::never();
64    #[cfg(feature = "devtools")]
65    let devtools_receiver = scope.devtools_receiver().unwrap_or(&devtools_never);
66    let animation_frame_tick_never = crossbeam_channel::never();
67    let animation_frame_tick_receiver = worker_scope
68        .animation_frame_tick_receiver()
69        .unwrap_or(&animation_frame_tick_never);
70
71    let task_receiver = task_queue.select();
72    let timer_receiver = scope.timer_scheduler().wait_channel();
73    let mut select = Select::new();
74    let control_index = select.recv(worker_scope.control_receiver());
75    let task_index = select.recv(task_receiver);
76    #[cfg(feature = "devtools")]
77    let devtools_index = select.recv(devtools_receiver);
78    let animation_frame_tick_index = select.recv(animation_frame_tick_receiver);
79    let timer_index = select.recv(&timer_receiver);
80
81    let operation = select.select();
82    let event = match operation.index() {
83        index if index == control_index => operation
84            .recv(worker_scope.control_receiver())
85            .ok()
86            .map(T::from_control_msg),
87        index if index == task_index => match operation.recv(task_receiver) {
88            Ok(msg) => {
89                task_queue.take_tasks(msg, &FxHashSet::default());
90                task_queue.recv().ok().map(T::from_worker_msg)
91            },
92            Err(_) => None,
93        },
94        #[cfg(feature = "devtools")]
95        index if index == devtools_index => operation
96            .recv(devtools_receiver)
97            .ok()
98            .and_then(|msg| msg.ok().map(T::from_devtools_msg)),
99        index if index == animation_frame_tick_index => {
100            match operation.recv(animation_frame_tick_receiver) {
101                Ok(Ok(msg)) => T::from_animation_frame_tick_msg(msg),
102                Ok(Err(_)) | Err(_) => None,
103            }
104        },
105        index if index == timer_index => operation
106            .recv(&timer_receiver)
107            .ok()
108            .map(|_| T::from_timer_msg()),
109        _ => unreachable!("selected an unregistered worker event-loop receiver"),
110    };
111
112    // Worker channels can be closed during teardown (for example, after an
113    // explicit close/exit Sharedworker path). Treat that as a clean shutdown signal rather
114    // than panicking on RecvError.
115    let Some(event) = event else {
116        scope.close();
117        return;
118    };
119
120    scope.timer_scheduler().dispatch_completed_timers();
121
122    let mut sequential = vec![event];
123
124    // https://html.spec.whatwg.org/multipage/#worker-event-loop
125    // Once the WorkerGlobalScope's closing flag is set to true,
126    // the event loop's task queues must discard any further tasks
127    // that would be added to them
128    // (tasks already on the queue are unaffected except where otherwise specified).
129    while !scope.is_closing() {
130        // Batch all events that are ready.
131        // The task queue will throttle non-priority tasks if necessary.
132        match task_queue.take_tasks_and_recv(&FxHashSet::default()) {
133            Err(_) => {
134                #[cfg(feature = "devtools")]
135                if let Ok(message) = devtools_receiver.try_recv() {
136                    sequential.push(T::from_devtools_msg(message.unwrap()));
137                    continue;
138                }
139
140                break;
141            },
142            Ok(ev) => sequential.push(T::from_worker_msg(ev)),
143        }
144    }
145
146    // Step 3
147    for event in sequential {
148        let mut realm = enter_auto_realm(cx, worker_scope);
149        let cx = &mut realm.current_realm();
150        let _ar = match worker {
151            Some(worker) => worker_scope.handle_worker_post_event(worker),
152            None => None,
153        };
154        if !worker_scope.handle_event(event, cx) {
155            // Shutdown
156            return;
157        }
158        // Step 6
159        scope.perform_a_microtask_checkpoint(cx);
160    }
161    worker_scope
162        .upcast::<GlobalScope>()
163        .perform_a_dom_garbage_collection_checkpoint();
164}