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
5use crossbeam_channel::{Receiver, select};
6use devtools_traits::DevtoolScriptControlMsg;
7use rustc_hash::FxHashSet;
8use script_bindings::reflector::DomObject;
9use servo_base::generic_channel::RoutedReceiver;
10use servo_constellation_traits::WorkerAnimationFrameTick;
11
12use crate::dom::bindings::conversions::DerivedFrom;
13use crate::dom::dedicatedworkerglobalscope::AutoWorkerReset;
14use crate::dom::globalscope::GlobalScope;
15use crate::dom::worker::TrustedWorkerAddress;
16use crate::dom::workerglobalscope::WorkerGlobalScope;
17use crate::realms::enter_auto_realm;
18use crate::tasks::task_queue::{QueuedTaskConversion, TaskQueue};
19
20pub(crate) trait WorkerEventLoopMethods {
21    type WorkerMsg: QueuedTaskConversion + Send;
22    type ControlMsg;
23    type Event;
24    fn task_queue(&self) -> &TaskQueue<Self::WorkerMsg>;
25    fn handle_event(&self, event: Self::Event, cx: &mut js::context::JSContext) -> bool;
26    fn handle_worker_post_event(
27        &self,
28        worker: &TrustedWorkerAddress,
29    ) -> Option<AutoWorkerReset<'_>>;
30    fn from_control_msg(msg: Self::ControlMsg) -> Self::Event;
31    fn from_worker_msg(msg: Self::WorkerMsg) -> Self::Event;
32    fn from_devtools_msg(msg: DevtoolScriptControlMsg) -> Self::Event;
33    fn from_timer_msg() -> Self::Event;
34    fn from_animation_frame_tick_msg(_msg: WorkerAnimationFrameTick) -> Option<Self::Event> {
35        None
36    }
37    fn animation_frame_tick_receiver(&self) -> Option<&RoutedReceiver<WorkerAnimationFrameTick>> {
38        None
39    }
40    fn control_receiver(&self) -> &Receiver<Self::ControlMsg>;
41}
42
43// https://html.spec.whatwg.org/multipage/#worker-event-loop
44pub(crate) fn run_worker_event_loop<T, WorkerMsg, Event>(
45    worker_scope: &T,
46    worker: Option<&TrustedWorkerAddress>,
47    cx: &mut js::context::JSContext,
48) where
49    WorkerMsg: QueuedTaskConversion + Send,
50    T: WorkerEventLoopMethods<WorkerMsg = WorkerMsg, Event = Event>
51        + DerivedFrom<WorkerGlobalScope>
52        + DerivedFrom<GlobalScope>
53        + DomObject,
54{
55    let scope = worker_scope.upcast::<WorkerGlobalScope>();
56    let task_queue = worker_scope.task_queue();
57
58    let devtools_never = crossbeam_channel::never();
59    let devtools_receiver = scope.devtools_receiver().unwrap_or(&devtools_never);
60    let animation_frame_tick_never = crossbeam_channel::never();
61    let animation_frame_tick_receiver = worker_scope
62        .animation_frame_tick_receiver()
63        .unwrap_or(&animation_frame_tick_never);
64
65    let event = select! {
66        recv(worker_scope.control_receiver()) -> msg => match msg {
67            Ok(msg) => Some(T::from_control_msg(msg)),
68            Err(_) => None,
69        },
70        recv(task_queue.select()) -> msg => match msg {
71            Ok(msg) => {
72                task_queue.take_tasks(msg, &FxHashSet::default());
73                task_queue.recv().ok().map(T::from_worker_msg)
74            },
75            Err(_) => None,
76        },
77        recv(devtools_receiver) -> msg => match msg {
78            Ok(msg) => msg.ok().map(T::from_devtools_msg),
79            Err(_) => None,
80        },
81        recv(animation_frame_tick_receiver) -> msg => match msg {
82            Ok(Ok(msg)) => T::from_animation_frame_tick_msg(msg),
83            Ok(Err(_)) | Err(_) => None,
84        },
85        recv(scope.timer_scheduler().wait_channel()) -> _ => Some(T::from_timer_msg()),
86    };
87
88    // Worker channels can be closed during teardown (for example, after an
89    // explicit close/exit Sharedworker path). Treat that as a clean shutdown signal rather
90    // than panicking on RecvError.
91    let Some(event) = event else {
92        return;
93    };
94
95    scope.timer_scheduler().dispatch_completed_timers();
96
97    let mut sequential = vec![event];
98
99    // https://html.spec.whatwg.org/multipage/#worker-event-loop
100    // Once the WorkerGlobalScope's closing flag is set to true,
101    // the event loop's task queues must discard any further tasks
102    // that would be added to them
103    // (tasks already on the queue are unaffected except where otherwise specified).
104    while !scope.is_closing() {
105        // Batch all events that are ready.
106        // The task queue will throttle non-priority tasks if necessary.
107        match task_queue.take_tasks_and_recv(&FxHashSet::default()) {
108            Err(_) => match devtools_receiver.try_recv() {
109                Ok(message) => sequential.push(T::from_devtools_msg(message.unwrap())),
110                Err(_) => break,
111            },
112            Ok(ev) => sequential.push(T::from_worker_msg(ev)),
113        }
114    }
115
116    // Step 3
117    for event in sequential {
118        let mut realm = enter_auto_realm(cx, worker_scope);
119        let cx = &mut realm.current_realm();
120        let _ar = match worker {
121            Some(worker) => worker_scope.handle_worker_post_event(worker),
122            None => None,
123        };
124        if !worker_scope.handle_event(event, cx) {
125            // Shutdown
126            return;
127        }
128        // Step 6
129        scope.perform_a_microtask_checkpoint(cx);
130    }
131    worker_scope
132        .upcast::<GlobalScope>()
133        .perform_a_dom_garbage_collection_checkpoint();
134}