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