Skip to main content

servo_constellation/
constellation.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//! The `Constellation`, Servo's Grand Central Station
6//!
7//! The constellation tracks all information kept globally by the
8//! browser engine, which includes:
9//!
10//! * The set of all `EventLoop` objects. Each event loop is
11//!   the constellation's view of a script thread. The constellation
12//!   interacts with a script thread by message-passing.
13//!
14//! * The set of all `Pipeline` objects.  Each pipeline gives the
15//!   constellation's view of a `Window`, with its script thread and
16//!   layout.  Pipelines may share script threads.
17//!
18//! * The set of all `BrowsingContext` objects. Each browsing context
19//!   gives the constellation's view of a `WindowProxy`.
20//!   Each browsing context stores an independent
21//!   session history, created by navigation. The session
22//!   history can be traversed, for example by the back and forwards UI,
23//!   so each session history maintains a list of past and future pipelines,
24//!   as well as the current active pipeline.
25//!
26//! There are two kinds of browsing context: top-level ones (for
27//! example tabs in a browser UI), and nested ones (typically caused
28//! by `iframe` elements). Browsing contexts have a hierarchy
29//! (typically caused by `iframe`s containing `iframe`s), giving rise
30//! to a forest whose roots are top-level browsing context.  The logical
31//! relationship between these types is:
32//!
33//! ```text
34//! +------------+                      +------------+                 +---------+
35//! |  Browsing  | ------parent?------> |  Pipeline  | --event_loop--> |  Event  |
36//! |  Context   | ------current------> |            |                 |  Loop   |
37//! |            | ------prev*--------> |            | <---pipeline*-- |         |
38//! |            | ------next*--------> |            |                 +---------+
39//! |            |                      |            |
40//! |            | <-top_level--------- |            |
41//! |            | <-browsing_context-- |            |
42//! +------------+                      +------------+
43//! ```
44//
45//! The constellation also maintains channels to other parts of Servo, including:
46//!
47//! * The script thread.
48//! * The `Paint` subsystem, which runs in the same thread as the `Servo` instance.
49//! * The font cache, image cache, and resource manager, which load
50//!   and cache shared fonts, images, or other resources.
51//! * The service worker manager.
52//! * The devtools and webdriver servers.
53//!
54//! The constellation passes messages between the threads, and updates its state
55//! to track the evolving state of the browsing context tree.
56//!
57//! The constellation acts as a logger, tracking any `warn!` messages from threads,
58//! and converting any `error!` or `panic!` into a crash report.
59//!
60//! Since there is only one constellation, and its responsibilities include crash reporting,
61//! it is very important that it does not panic.
62//!
63//! It's also important that the constellation not deadlock. In particular, we need
64//! to be careful that we don't introduce any cycles in the can-block-on relation.
65//! Blocking is typically introduced by `receiver.recv()`, which blocks waiting for the
66//! sender to send some data. Servo tries to achieve deadlock-freedom by using the following
67//! can-block-on relation:
68//!
69//! * Constellation can block on `Paint`
70//! * Constellation can block on embedder
71//! * Script can block on anything (other than script)
72//! * Blocking is transitive (if T1 can block on T2 and T2 can block on T3 then T1 can block on T3)
73//! * Nothing can block on itself!
74//!
75//! There is a complexity intoduced by IPC channels, since they do not support
76//! non-blocking send. This means that as well as `receiver.recv()` blocking,
77//! `sender.send(data)` can also block when the IPC buffer is full. For this reason it is
78//! very important that all IPC receivers where we depend on non-blocking send
79//! use a router to route IPC messages to an mpsc channel. The reason why that solves
80//! the problem is that under the hood, the router uses a dedicated thread to forward
81//! messages, and:
82//!
83//! * Anything (other than a routing thread) can block on a routing thread
84//!
85//! See <https://github.com/servo/servo/issues/14704>
86
87use std::borrow::ToOwned;
88use std::cell::{Cell, OnceCell, RefCell};
89use std::collections::hash_map::Entry;
90use std::collections::{HashMap, HashSet, VecDeque};
91use std::marker::PhantomData;
92use std::mem::replace;
93use std::rc::{Rc, Weak};
94use std::sync::Arc;
95use std::thread::JoinHandle;
96use std::{process, thread};
97
98use accesskit::{ActionRequest, TreeId};
99use background_hang_monitor_api::{
100    BackgroundHangMonitorControlMsg, BackgroundHangMonitorRegister, HangAlert,
101};
102use content_security_policy::sandboxing_directive::SandboxingFlagSet;
103use crossbeam_channel::{Receiver, Select, Sender, unbounded};
104#[cfg(feature = "devtools")]
105use devtools_traits::{
106    ChromeToDevtoolsControlMsg, DevtoolsControlMsg, DevtoolsPageInfo, NavigationState,
107    ScriptToDevtoolsControlMsg,
108};
109use embedder_traits::resources::{self, Resource};
110use embedder_traits::user_contents::{UserContentManagerId, UserContents};
111use embedder_traits::{
112    AnimationState, EmbedderControlId, EmbedderControlResponse, EmbedderProxy, FocusSequenceNumber,
113    GenericEmbedderProxy, InputEvent, InputEventAndId, InputEventOutcome, JSValue,
114    JavaScriptEvaluationError, JavaScriptEvaluationId, KeyboardEvent, MediaSessionActionType,
115    MediaSessionEvent, MediaSessionPlaybackState, MouseButtonAction, MouseButtonEvent,
116    NewWebViewDetails, PaintHitTestResult, Theme, ViewportDetails, WakeLockDelegate, WakeLockType,
117    WebDriverCommandMsg, WebDriverLoadStatus, WebDriverScriptCommand,
118};
119use euclid::default::Size2D as UntypedSize2D;
120use fonts::SystemFontServiceProxy;
121use ipc_channel::router::ROUTER;
122use keyboard_types::{Key, KeyState, Modifiers, NamedKey};
123use layout_api::{LayoutFactory, ScriptThreadFactory};
124use log::{debug, error, info, trace, warn};
125use media::WindowGLContext;
126use net::image_cache::ImageCacheFactoryImpl;
127use net_traits::pub_domains::registered_domain_name;
128use net_traits::{self, AsyncRuntime, FetchThread, ResourceThreads};
129use paint_api::{
130    PaintMessage, PaintProxy, PinchZoomInfos, PipelineExitSource, SendableFrameTree,
131    WebRenderExternalImageIdManager,
132};
133use profile_traits::mem::ProfilerMsg;
134use profile_traits::{mem, time};
135use rand::rngs::SmallRng;
136use rand::seq::IndexedRandom;
137use rand::{RngExt, SeedableRng, make_rng};
138use rustc_hash::{FxHashMap, FxHashSet};
139use script_traits::{
140    ConstellationInputEvent, DiscardBrowsingContext, DocumentActivity, MouseButtons,
141    NewPipelineInfo, ScriptThreadMessage, UpdatePipelineIdReason,
142};
143use servo_background_hang_monitor::HangMonitorRegister;
144use servo_base::generic_channel;
145use servo_base::generic_channel::{
146    GenericCallback, GenericSend, GenericSender, RoutedReceiver, SendError,
147};
148use servo_base::id::{
149    BrowsingContextId, CONSTELLATION_PIPELINE_NAMESPACE_ID, FIRST_CONTENT_PIPELINE_NAMESPACE_ID,
150    HistoryStateId, MessagePortId, MessagePortRouterId, PainterId, PipelineId, PipelineNamespace,
151    PipelineNamespaceId, PipelineNamespaceRequest, ScriptEventLoopId, WebViewId, WorkerId,
152};
153use servo_base::threadboost::{BoostAffinity, ThreadPriority};
154#[cfg(feature = "bluetooth")]
155use servo_bluetooth_traits::BluetoothRequest;
156use servo_canvas::canvas_paint_thread::CanvasPaintThread;
157use servo_canvas_traits::ConstellationCanvasMsg;
158use servo_canvas_traits::canvas::{CanvasId, CanvasMsg};
159use servo_config::{opts, pref};
160use servo_constellation_traits::{
161    AuxiliaryWebViewCreationRequest, AuxiliaryWebViewCreationResponse, ConstellationInterest,
162    EmbedderToConstellationMessage, HistoryTraversalSource, IFrameLoadInfo, IFrameLoadInfoWithData,
163    IFrameSizeMsg, LoadData, LogEntry, MessagePortMsg, NavigationHistoryBehavior, PaintMetricEvent,
164    PortMessageTask, PortTransferInfo, RemoteFocusOperation, SWManagerSenders,
165    ScreenshotReadinessResponse, ScriptToConstellationMessage, ScrollStateUpdate,
166    ServiceWorkerAlgorithm, ServiceWorkerManagerFactory, ServiceWorkerMsg,
167    SessionHistoryTraversalRequest, StructuredSerializedData, TargetSnapshotParams,
168    TraversalDirection, UserContentManagerAction, WindowSizeType, WorkerAnimationFrameTick,
169};
170use servo_url::{Host, ImmutableOrigin, ServoUrl};
171use storage_traits::StorageThreads;
172use storage_traits::cache_storage::CacheStorageThreadMessage;
173use storage_traits::client_storage::ClientStorageThreadMessage;
174use storage_traits::indexeddb::{IndexedDBThreadMsg, SyncOperation};
175use storage_traits::webstorage_thread::{WebStorageThreadMsg, WebStorageType};
176#[cfg(feature = "webgpu")]
177use webgpu::canvas_context::WebGpuExternalImageMap;
178#[cfg(feature = "webgpu")]
179use webgpu_traits::{WebGPU, WebGPURequest};
180
181use super::embedder::ConstellationToEmbedderMsg;
182use crate::broadcastchannel::BroadcastChannels;
183use crate::browsingcontext::{
184    AllBrowsingContextsIterator, BrowsingContext, FullyActiveBrowsingContextsIterator,
185    NewBrowsingContextInfo,
186};
187use crate::constellation_webview::{ConstellationWebView, OngoingHistoryTraversalRequest};
188use crate::event_loop::EventLoop;
189use crate::pipeline::Pipeline;
190use crate::serviceworker::ServiceWorkerUnprivilegedContent;
191use crate::session_history::{NeedsToReload, SessionHistoryChange, SessionHistoryDiff};
192
193struct PendingApprovalNavigation {
194    load_data: LoadData,
195    history_behaviour: NavigationHistoryBehavior,
196    target_snapshot_params: TargetSnapshotParams,
197}
198
199type PendingApprovalNavigations = FxHashMap<PipelineId, PendingApprovalNavigation>;
200
201#[derive(Debug)]
202/// The state used by MessagePortInfo to represent the various states the port can be in.
203enum TransferState {
204    /// The port is currently managed by a given global,
205    /// identified by its router id.
206    Managed(MessagePortRouterId),
207    /// The port is currently in-transfer,
208    /// and incoming tasks should be buffered until it becomes managed again.
209    TransferInProgress(VecDeque<PortMessageTask>),
210    /// A global has requested the transfer to be completed,
211    /// it's pending a confirmation of either failure or success to complete the transfer.
212    CompletionInProgress(MessagePortRouterId),
213    /// While a completion of a transfer was in progress, the port was shipped,
214    /// hence the transfer failed to complete.
215    /// We start buffering incoming messages,
216    /// while awaiting the return of the previous buffer from the global
217    /// that failed to complete the transfer.
218    CompletionFailed(VecDeque<PortMessageTask>),
219    /// While a completion failed, another global requested to complete the transfer.
220    /// We are still buffering messages, and awaiting the return of the buffer from the global who failed.
221    CompletionRequested(MessagePortRouterId, VecDeque<PortMessageTask>),
222}
223
224#[derive(Debug)]
225/// Info related to a message-port tracked by the constellation.
226struct MessagePortInfo {
227    /// The current state of the messageport.
228    state: TransferState,
229
230    /// The id of the entangled port, if any.
231    entangled_with: Option<MessagePortId>,
232}
233
234#[cfg(feature = "webgpu")]
235/// WebRender related objects required by WebGPU threads
236struct WebRenderWGPU {
237    /// List of Webrender external images
238    webrender_external_image_id_manager: WebRenderExternalImageIdManager,
239
240    /// WebGPU data that supplied to Webrender for rendering
241    wgpu_image_map: WebGpuExternalImageMap,
242}
243
244/// A browsing context group.
245///
246/// <https://html.spec.whatwg.org/multipage/#browsing-context-group>
247#[derive(Default)]
248pub(crate) struct BrowsingContextGroup {
249    /// The set of all event loops in this BrowsingContextGroup.
250    /// We store the event loops in a map
251    /// indexed by registered domain name (as a `Host`) to event loops.
252    /// It is important that scripts with the same eTLD+1,
253    /// who are part of the same browsing-context group
254    /// share an event loop, since they can use `document.domain`
255    /// to become same-origin, at which point they can share DOM objects.
256    event_loops: HashMap<Host, Weak<EventLoop>>,
257
258    /// The set of all WebGPU channels in this BrowsingContextGroup.
259    #[cfg(feature = "webgpu")]
260    webgpus: HashMap<Host, WebGPU>,
261}
262
263impl Drop for BrowsingContextGroup {
264    fn drop(&mut self) {
265        #[cfg(feature = "webgpu")]
266        for webgpu in self.webgpus.values() {
267            // Request that the WebGPU exit, but do not wait for it to do so. We are explicitly
268            // avoiding a synchronous wait here in the middle of Constellation operation. If
269            // it becomes necessary in the future these receivers could be waited on asynchronously
270            // via polling.
271            if let Some((sender, _)) = generic_channel::oneshot() &&
272                let Err(error) = webgpu.exit(sender)
273            {
274                warn!("Failed to request WebGPU exit: {error}.");
275            }
276        }
277    }
278}
279
280struct WorkerAnimationFrameProvider {
281    webview_id: WebViewId,
282    pipeline_id: PipelineId,
283    sender: GenericSender<WorkerAnimationFrameTick>,
284    tick_pending: bool,
285}
286
287/// The `Constellation` itself. In the servo browser, there is one
288/// constellation, which maintains all of the browser global data.
289/// In embedded applications, there may be more than one constellation,
290/// which are independent of each other.
291///
292/// The constellation may be in a different process from the pipelines,
293/// and communicates using IPC.
294///
295/// It is parameterized over a `LayoutThreadFactory` and a
296/// `ScriptThreadFactory` (which in practice are implemented by
297/// `LayoutThread` in the `layout` crate, and `ScriptThread` in
298/// the `script` crate). Script and layout communicate using a `Message`
299/// type.
300pub struct Constellation<STF, SWF> {
301    /// An ipc-sender/threaded-receiver pair
302    /// to facilitate installing pipeline namespaces in threads
303    /// via a per-process installer.
304    namespace_receiver: RoutedReceiver<PipelineNamespaceRequest>,
305    pub(crate) namespace_ipc_sender: GenericSender<PipelineNamespaceRequest>,
306
307    /// A [`Vec`] of all [`EventLoop`]s that have been created for this [`Constellation`].
308    /// This will be cleaned up periodically. This stores weak references so that [`EventLoop`]s
309    /// can be stopped when they are no longer used.
310    event_loops: Vec<Weak<EventLoop>>,
311
312    /// An IPC channel for script threads to send messages to the constellation.
313    /// This is the script threads' view of `script_receiver`.
314    pub(crate) script_sender: GenericSender<(WebViewId, PipelineId, ScriptToConstellationMessage)>,
315
316    /// A channel for the constellation to receive messages from script threads.
317    /// This is the constellation's view of `script_sender`.
318    script_receiver:
319        Receiver<Result<(WebViewId, PipelineId, ScriptToConstellationMessage), SendError>>,
320
321    /// A handle to register components for hang monitoring.
322    /// None when in multiprocess mode.
323    pub(crate) background_monitor_register: Option<Box<dyn BackgroundHangMonitorRegister>>,
324
325    /// In single process mode, a join handle on the BHM worker thread.
326    background_monitor_register_join_handle: Option<JoinHandle<()>>,
327
328    /// When running in single-process mode, this is a channel to the shared BackgroundHangMonitor
329    /// for all [`EventLoop`]s. This will be `None` in multiprocess mode.
330    background_monitor_control_sender: Option<GenericSender<BackgroundHangMonitorControlMsg>>,
331
332    /// A channel for the background hang monitor to send messages
333    /// to the constellation.
334    #[cfg_attr(not(feature = "multiprocess"), expect(unused))]
335    pub(crate) background_hang_monitor_sender: GenericSender<HangAlert>,
336
337    /// A channel for the constellation to receiver messages
338    /// from the background hang monitor.
339    background_hang_monitor_receiver: RoutedReceiver<HangAlert>,
340
341    /// A factory for creating layouts. This allows customizing the kind
342    /// of layout created for a [`Constellation`] and prevents a circular crate
343    /// dependency between script and layout.
344    pub(crate) layout_factory: Arc<dyn LayoutFactory>,
345
346    /// A channel for the embedder (renderer and libservo) to send messages to the [`Constellation`].
347    embedder_to_constellation_receiver: Receiver<EmbedderToConstellationMessage>,
348
349    /// A channel through which messages can be sent to the embedder. This is not used by the `Constellation`
350    /// itself but only needed to create an `EventLoop`.
351    /// Messages from the `Constellation` to the embedder are sent using the `constellation_to_embedder_proxy`
352    pub(crate) embedder_proxy: EmbedderProxy,
353
354    /// A channel through which messages can be sent to the embedder.
355    pub(crate) constellation_to_embedder_proxy: GenericEmbedderProxy<ConstellationToEmbedderMsg>,
356
357    /// A channel (the implementation of which is port-specific) for the
358    /// constellation to send messages to `Paint`.
359    pub(crate) paint_proxy: PaintProxy,
360
361    /// Bookkeeping data for all webviews in the constellation.
362    webviews: FxHashMap<WebViewId, ConstellationWebView>,
363
364    /// Channels for the constellation to send messages to the public
365    /// resource-related threads. There are two groups of resource threads: one
366    /// for public browsing, and one for private browsing.
367    pub(crate) public_resource_threads: ResourceThreads,
368
369    /// Channels for the constellation to send messages to the private
370    /// resource-related threads.  There are two groups of resource
371    /// threads: one for public browsing, and one for private
372    /// browsing.
373    pub(crate) private_resource_threads: ResourceThreads,
374
375    /// Channels for the constellation to send messages to the public
376    /// storage-related threads. There are two groups of storage threads: one
377    /// for public browsing, and one for private browsing.
378    pub(crate) public_storage_threads: StorageThreads,
379
380    /// Channels for the constellation to send messages to the private
381    /// storage-related threads.  There are two groups of storage
382    /// threads: one for public browsing, and one for private
383    /// browsing.
384    pub(crate) private_storage_threads: StorageThreads,
385
386    /// A channel for the constellation to send messages to the font
387    /// cache thread.
388    pub(crate) system_font_service: Arc<SystemFontServiceProxy>,
389
390    /// A channel for the constellation to send messages to the
391    /// devtools thread.
392    #[cfg(feature = "devtools")]
393    pub(crate) devtools_sender: Option<Sender<DevtoolsControlMsg>>,
394
395    /// A (potentially) IPC-based channel to the developer tools, if enabled. This allows
396    /// `EventLoop`s to send messages to then. Shared with all `EventLoop`s.
397    #[cfg(feature = "devtools")]
398    pub script_to_devtools_callback: OnceCell<Option<GenericCallback<ScriptToDevtoolsControlMsg>>>,
399
400    /// An IPC channel for the constellation to send messages to the
401    /// bluetooth thread.
402    #[cfg(feature = "bluetooth")]
403    pub(crate) bluetooth_ipc_sender: GenericSender<BluetoothRequest>,
404
405    /// A map of origin to sender to a Service worker manager.
406    sw_managers: HashMap<ImmutableOrigin, GenericSender<ServiceWorkerMsg>>,
407
408    /// A channel for the constellation to send messages to the
409    /// time profiler thread.
410    pub(crate) time_profiler_chan: time::ProfilerChan,
411
412    /// A channel for the constellation to send messages to the
413    /// memory profiler thread.
414    pub(crate) mem_profiler_chan: mem::ProfilerChan,
415
416    /// WebRender related objects required by WebGPU threads
417    #[cfg(feature = "webgpu")]
418    webrender_wgpu: WebRenderWGPU,
419
420    /// A map of message-port Id to info.
421    message_ports: FxHashMap<MessagePortId, MessagePortInfo>,
422
423    /// A map of router-id to ipc-sender, to route messages to ports.
424    message_port_routers: FxHashMap<MessagePortRouterId, GenericCallback<MessagePortMsg>>,
425
426    /// Bookkeeping for BroadcastChannel functionnality.
427    broadcast_channels: BroadcastChannels,
428
429    /// Tracks which pipelines have registered interest in each notification category.
430    pipeline_interests: FxHashMap<ConstellationInterest, FxHashSet<PipelineId>>,
431
432    /// The set of all the pipelines in the browser.  (See the `pipeline` module
433    /// for more details.)
434    pipelines: FxHashMap<PipelineId, Pipeline>,
435
436    worker_animation_frame_providers: FxHashMap<WorkerId, WorkerAnimationFrameProvider>,
437
438    /// The set of all the browsing contexts in the browser.
439    browsing_contexts: FxHashMap<BrowsingContextId, BrowsingContext>,
440
441    /// Pipeline IDs are namespaced in order to avoid name collisions,
442    /// and the namespaces are allocated by the constellation.
443    next_pipeline_namespace_id: Cell<PipelineNamespaceId>,
444
445    /// A [`GenericSender`] to notify navigation events to webdriver.
446    webdriver_load_status_sender: Option<(GenericSender<WebDriverLoadStatus>, PipelineId)>,
447
448    /// Are we shutting down?
449    shutting_down: bool,
450
451    /// Have we seen any warnings? Hopefully always empty!
452    /// The buffer contains `(thread_name, reason)` entries.
453    handled_warnings: VecDeque<(Option<String>, String)>,
454
455    /// The random number generator and probability for closing pipelines.
456    /// This is for testing the hardening of the constellation.
457    random_pipeline_closure: Option<(SmallRng, f32)>,
458
459    /// Phantom data that keeps the Rust type system happy.
460    phantom: PhantomData<(STF, SWF)>,
461
462    /// Entry point to create and get channels to a WebGLThread.
463    #[cfg(feature = "webgl")]
464    pub(crate) webgl_threads: Option<servo_canvas_traits::webgl::WebGLThreads>,
465
466    /// The XR device registry
467    pub(crate) webxr_registry: Option<webxr_api::Registry>,
468
469    /// Lazily initialized channels for canvas paint thread.
470    canvas: OnceCell<(Sender<ConstellationCanvasMsg>, GenericSender<CanvasMsg>)>,
471
472    /// Navigation requests from script awaiting approval from the embedder.
473    pending_approval_navigations: PendingApprovalNavigations,
474
475    /// Bitmask which indicates which combination of mouse buttons are
476    /// currently being pressed.
477    pressed_mouse_buttons: MouseButtons,
478
479    /// The currently activated keyboard modifiers.
480    active_keyboard_modifiers: Modifiers,
481
482    /// If True, exits on thread failure instead of displaying about:failure
483    hard_fail: bool,
484
485    /// Pipeline ID of the active media session.
486    active_media_session: Option<PipelineId>,
487
488    /// Aggregate screen wake lock count across all webviews. The provider is notified
489    /// only when this transitions 0→1 (acquire) or N→0 (release).
490    screen_wake_lock_count: u32,
491
492    /// Provider for OS-level screen wake lock acquisition and release.
493    wake_lock_provider: Box<dyn WakeLockDelegate>,
494
495    /// The image bytes associated with the BrokenImageIcon embedder resource.
496    /// Read during startup and provided to image caches that are created
497    /// on an as-needed basis, rather than retrieving it every time.
498    #[cfg(feature = "multiprocess")]
499    pub(crate) broken_image_icon_data: Vec<u8>,
500
501    /// The process manager.
502    #[cfg(feature = "multiprocess")]
503    pub(crate) process_manager: crate::process_manager::ProcessManager,
504
505    /// The async runtime.
506    async_runtime: Box<dyn AsyncRuntime>,
507
508    /// A vector of [`JoinHandle`]s used to ensure full termination of threaded [`EventLoop`]s
509    /// which are runnning in the same process.
510    event_loop_join_handles: Vec<JoinHandle<()>>,
511
512    /// A list of URLs that can access privileged internal APIs.
513    pub(crate) privileged_urls: Vec<ServoUrl>,
514
515    /// The [`ImageCacheFactory`] to use for all `ScriptThread`s when we are running in
516    /// single-process mode. In multi-process mode, each process will create its own
517    /// [`ImageCacheFactoryImpl`].
518    pub(crate) image_cache_factory: Arc<ImageCacheFactoryImpl>,
519
520    /// A map from `UserContentManagerId` to the `UserContents` for that manager.
521    /// Multiple `WebView`s can share the same `UserContentManager` and any mutations
522    /// to the `UserContents` need to be forwared to all the `ScriptThread`s that host
523    /// the relevant `WebView`.
524    pub(crate) user_contents_for_manager_id: FxHashMap<UserContentManagerId, UserContents>,
525}
526
527/// State needed to construct a constellation.
528pub struct InitialConstellationState {
529    /// A channel through which messages can be sent to the embedder. This is not used by the `Constellation`
530    /// itself but only needed to create an `EventLoop`.
531    /// Messages from the `Constellation` to the embedder are sent using the `constellation_to_embedder_proxy`
532    pub embedder_proxy: EmbedderProxy,
533
534    /// A channel through which messages can be sent to the embedder.
535    pub constellation_to_embedder_proxy: GenericEmbedderProxy<ConstellationToEmbedderMsg>,
536
537    /// A channel through which messages can be sent to `Paint` in-process.
538    pub paint_proxy: PaintProxy,
539
540    /// A channel to the developer tools, if applicable.
541    #[cfg(feature = "devtools")]
542    pub devtools_sender: Option<Sender<DevtoolsControlMsg>>,
543
544    /// A channel to the bluetooth thread.
545    #[cfg(feature = "bluetooth")]
546    pub bluetooth_thread: GenericSender<BluetoothRequest>,
547
548    /// A proxy to the `SystemFontService` which manages the list of system fonts.
549    pub system_font_service: Arc<SystemFontServiceProxy>,
550
551    /// A channel to the resource thread.
552    pub public_resource_threads: ResourceThreads,
553
554    /// A channel to the resource thread.
555    pub private_resource_threads: ResourceThreads,
556
557    /// A channel to the storage thread.
558    pub public_storage_threads: StorageThreads,
559
560    /// A channel to the storage thread.
561    pub private_storage_threads: StorageThreads,
562
563    /// A channel to the time profiler thread.
564    pub time_profiler_chan: time::ProfilerChan,
565
566    /// A channel to the memory profiler thread.
567    pub mem_profiler_chan: mem::ProfilerChan,
568
569    /// A [`WebRenderExternalImageIdManager`] used to lazily start up the WebGPU threads.
570    pub webrender_external_image_id_manager: WebRenderExternalImageIdManager,
571
572    /// Entry point to create and get channels to a WebGLThread.
573    #[cfg(feature = "webgl")]
574    pub webgl_threads: Option<servo_canvas_traits::webgl::WebGLThreads>,
575
576    /// The XR device registry
577    pub webxr_registry: Option<webxr_api::Registry>,
578
579    #[cfg(feature = "webgpu")]
580    pub wgpu_image_map: WebGpuExternalImageMap,
581
582    /// A list of URLs that can access privileged internal APIs.
583    pub privileged_urls: Vec<ServoUrl>,
584
585    /// The async runtime.
586    pub async_runtime: Box<dyn AsyncRuntime>,
587
588    /// The wake lock provider for acquiring and releasing OS-level screen wake locks.
589    pub wake_lock_provider: Box<dyn WakeLockDelegate>,
590}
591
592/// When we are exiting a pipeline, we can either force exiting or not. A normal exit
593/// waits for `Paint` to update its state before exiting, and delegates layout exit to
594/// script. A forced exit does not notify `Paint`, and exits layout without involving
595/// script.
596#[derive(Clone, Copy, Debug)]
597enum ExitPipelineMode {
598    Normal,
599    Force,
600}
601
602/// The number of warnings to include in each crash report.
603const WARNINGS_BUFFER_SIZE: usize = 32;
604
605impl<STF, SWF> Constellation<STF, SWF>
606where
607    STF: ScriptThreadFactory,
608    SWF: ServiceWorkerManagerFactory,
609{
610    /// Create a new constellation thread.
611    #[servo_tracing::instrument(skip(state, layout_factory))]
612    pub fn start(
613        embedder_to_constellation_receiver: Receiver<EmbedderToConstellationMessage>,
614        state: InitialConstellationState,
615        layout_factory: Arc<dyn LayoutFactory>,
616        random_pipeline_closure_probability: Option<f32>,
617        random_pipeline_closure_seed: Option<usize>,
618        hard_fail: bool,
619    ) {
620        thread::Builder::new()
621            .name("Constellation".to_owned())
622            .spawn(move || {
623                servo_base::threadboost::boost_thread(ThreadPriority::Elevated, BoostAffinity::Boost);
624                let (script_ipc_sender, script_ipc_receiver) =
625                    generic_channel::channel().expect("ipc channel failure");
626                let script_receiver = script_ipc_receiver.route_preserving_errors();
627
628                let (namespace_ipc_sender, namespace_ipc_receiver) =
629                    generic_channel::channel().expect("ipc channel failure");
630                let namespace_receiver = namespace_ipc_receiver.route_preserving_errors();
631
632                let (background_hang_monitor_ipc_sender, background_hang_monitor_ipc_receiver) =
633                    generic_channel::channel().expect("ipc channel failure");
634                let background_hang_monitor_receiver =
635                    background_hang_monitor_ipc_receiver.route_preserving_errors();
636
637                // If we are in multiprocess mode,
638                // a dedicated per-process hang monitor will be initialized later inside the content process.
639                // See run_content_process in servo/lib.rs
640
641                let (
642                    background_monitor_register,
643                    background_monitor_register_join_handle,
644                    background_monitor_control_sender
645                ) = if opts::get().multiprocess {
646                    (None, None, None)
647                } else {
648                    let (
649                        background_hang_monitor_control_ipc_sender,
650                        background_hang_monitor_control_ipc_receiver,
651                    ) = generic_channel::channel().expect("ipc channel failure");
652                    let (register, join_handle) = HangMonitorRegister::init(
653                        background_hang_monitor_ipc_sender.clone(),
654                        background_hang_monitor_control_ipc_receiver,
655                        opts::get().background_hang_monitor,
656                    );
657                    (
658                        Some(register),
659                        Some(join_handle),
660                        Some(background_hang_monitor_control_ipc_sender),
661                    )
662                };
663
664                PipelineNamespace::install(CONSTELLATION_PIPELINE_NAMESPACE_ID);
665
666                #[cfg(feature = "webgpu")]
667                let webrender_wgpu = WebRenderWGPU {
668                    webrender_external_image_id_manager: state.webrender_external_image_id_manager,
669                    wgpu_image_map: state.wgpu_image_map,
670                };
671
672                let broken_image_icon_data = resources::read_bytes(Resource::BrokenImageIcon);
673
674                let mut constellation: Constellation<STF, SWF> = Constellation {
675                    event_loops: Default::default(),
676                    namespace_receiver,
677                    namespace_ipc_sender,
678                    script_sender: script_ipc_sender,
679                    background_hang_monitor_sender: background_hang_monitor_ipc_sender,
680                    background_hang_monitor_receiver,
681                    background_monitor_register,
682                    background_monitor_register_join_handle,
683                    background_monitor_control_sender,
684                    script_receiver,
685                    embedder_to_constellation_receiver,
686                    layout_factory,
687                    embedder_proxy: state.embedder_proxy,
688                    constellation_to_embedder_proxy: state.constellation_to_embedder_proxy,
689                    paint_proxy: state.paint_proxy,
690                    webviews: Default::default(),
691                    #[cfg(feature = "devtools")]
692                    devtools_sender: state.devtools_sender,
693                    #[cfg(feature = "devtools")]
694                    script_to_devtools_callback: Default::default(),
695                    #[cfg(feature = "bluetooth")]
696                    bluetooth_ipc_sender: state.bluetooth_thread,
697                    public_resource_threads: state.public_resource_threads,
698                    private_resource_threads: state.private_resource_threads,
699                    public_storage_threads: state.public_storage_threads,
700                    private_storage_threads: state.private_storage_threads,
701                    system_font_service: state.system_font_service,
702                    sw_managers: Default::default(),
703                    message_ports: Default::default(),
704                    message_port_routers: Default::default(),
705                    broadcast_channels: Default::default(),
706                    pipeline_interests: Default::default(),
707                    pipelines: Default::default(),
708                    worker_animation_frame_providers: Default::default(),
709                    browsing_contexts: Default::default(),
710                    next_pipeline_namespace_id: Cell::new(FIRST_CONTENT_PIPELINE_NAMESPACE_ID),
711                    time_profiler_chan: state.time_profiler_chan,
712                    mem_profiler_chan: state.mem_profiler_chan.clone(),
713                    phantom: PhantomData,
714                    webdriver_load_status_sender: None,
715                    #[cfg(feature = "webgpu")]
716                    webrender_wgpu,
717                    shutting_down: false,
718                    handled_warnings: VecDeque::new(),
719                    random_pipeline_closure: random_pipeline_closure_probability.map(|probability| {
720                        let rng = random_pipeline_closure_seed
721                            .map(|seed| SmallRng::seed_from_u64(seed as u64))
722                            .unwrap_or_else(make_rng);
723                        warn!("Randomly closing pipelines using seed {random_pipeline_closure_seed:?}.");
724                        (rng, probability)
725                    }),
726                    #[cfg(feature = "webgl")]
727                    webgl_threads: state.webgl_threads,
728                    webxr_registry: state.webxr_registry,
729                    canvas: OnceCell::new(),
730                    pending_approval_navigations: Default::default(),
731                    pressed_mouse_buttons: MouseButtons::empty(),
732                    active_keyboard_modifiers: Modifiers::empty(),
733                    hard_fail,
734                    active_media_session: None,
735                    screen_wake_lock_count: 0,
736                    wake_lock_provider: state.wake_lock_provider,
737                    #[cfg(feature = "multiprocess")]
738                    broken_image_icon_data: broken_image_icon_data.clone(),
739                    #[cfg(feature = "multiprocess")]
740                    process_manager: crate::process_manager::ProcessManager::new(state.mem_profiler_chan),
741                    async_runtime: state.async_runtime,
742                    event_loop_join_handles: Default::default(),
743                    privileged_urls: state.privileged_urls,
744                    image_cache_factory: Arc::new(ImageCacheFactoryImpl::new(
745                        broken_image_icon_data,
746                    )),
747                    user_contents_for_manager_id: Default::default(),
748                };
749
750                constellation.run();
751            })
752            .expect("Thread spawning failed");
753    }
754
755    fn event_loops(&self) -> Vec<Rc<EventLoop>> {
756        self.event_loops
757            .iter()
758            .filter_map(|weak_event_loop| weak_event_loop.upgrade())
759            .collect()
760    }
761
762    pub(crate) fn add_event_loop(&mut self, event_loop: &Rc<EventLoop>) {
763        self.event_loops.push(Rc::downgrade(event_loop));
764    }
765
766    pub(crate) fn add_event_loop_join_handle(&mut self, join_handle: JoinHandle<()>) {
767        self.event_loop_join_handles.push(join_handle);
768    }
769
770    fn clean_up_finished_script_event_loops(&mut self) {
771        self.event_loop_join_handles
772            .retain(|join_handle| !join_handle.is_finished());
773        self.event_loops
774            .retain(|event_loop| event_loop.upgrade().is_some());
775    }
776
777    /// The main event loop for the constellation.
778    fn run(&mut self) {
779        while !self.shutting_down || !self.pipelines.is_empty() {
780            // Randomly close a pipeline if --random-pipeline-closure-probability is set
781            // This is for testing the hardening of the constellation.
782            self.maybe_close_random_pipeline();
783            self.handle_request();
784            self.finish_completed_session_history_traversal_requests();
785            self.clean_up_finished_script_event_loops();
786        }
787        self.handle_shutdown();
788
789        if !opts::get().multiprocess {
790            style::global_style_data::StyleThreadPool::shutdown();
791        }
792
793        // Shut down the `FetchThread` if it has been started at any time.
794        FetchThread::exit();
795
796        // Note: the last thing the constellation does, is asking the embedder to
797        // shut down. This helps ensure we've shut down all our internal threads before
798        // de-initializing Servo (see the `thread_count` warning on MacOS).
799        debug!("Asking embedding layer to complete shutdown.");
800        self.constellation_to_embedder_proxy
801            .send(ConstellationToEmbedderMsg::ShutdownComplete);
802    }
803
804    /// Helper that sends a message to the event loop of a given pipeline, logging the
805    /// given failure message and returning `false` on failure.
806    fn send_message_to_pipeline(
807        &mut self,
808        pipeline_id: PipelineId,
809        message: ScriptThreadMessage,
810        failure_message: &str,
811    ) -> bool {
812        let result = match self.pipelines.get(&pipeline_id) {
813            Some(pipeline) => pipeline.event_loop.send(message),
814            None => {
815                warn!("{pipeline_id}: {failure_message}");
816                return false;
817            },
818        };
819        if let Err(err) = result {
820            self.handle_send_error(pipeline_id, err);
821        }
822        true
823    }
824
825    /// Generate a new pipeline id namespace.
826    pub(crate) fn next_pipeline_namespace_id(&self) -> PipelineNamespaceId {
827        let pipeline_namespace_id = self.next_pipeline_namespace_id.get();
828        self.next_pipeline_namespace_id
829            .set(PipelineNamespaceId(pipeline_namespace_id.0 + 1));
830        pipeline_namespace_id
831    }
832
833    fn browsing_context_group_for_webview_id(
834        &self,
835        webview_id: &WebViewId,
836    ) -> Option<Rc<RefCell<BrowsingContextGroup>>> {
837        self.webviews
838            .get(webview_id)
839            .map(ConstellationWebView::browsing_context_group)
840    }
841
842    fn get_event_loop(
843        &self,
844        host: &Host,
845        webview_id: &WebViewId,
846    ) -> Result<Weak<EventLoop>, &'static str> {
847        let Some(browsing_context_group) = self.browsing_context_group_for_webview_id(webview_id)
848        else {
849            return Err("Trying to get an event-loop for an unknown WebView");
850        };
851        browsing_context_group
852            .borrow()
853            .event_loops
854            .get(host)
855            .ok_or("Trying to get an event-loop from an unknown browsing context group")
856            .cloned()
857    }
858
859    fn set_event_loop(&mut self, event_loop: &Rc<EventLoop>, host: Host, webview_id: &WebViewId) {
860        let Some(browsing_context_group) = self.browsing_context_group_for_webview_id(webview_id)
861        else {
862            return warn!("Trying to add an event loop to an unknown WebView ({webview_id})");
863        };
864
865        if browsing_context_group
866            .borrow_mut()
867            .event_loops
868            .insert(host.clone(), Rc::downgrade(event_loop))
869            .is_some_and(|old_event_loop| old_event_loop.strong_count() != 0)
870        {
871            warn!("Double-setting an event loop for {host:?} in {webview_id:?}");
872        }
873    }
874
875    fn get_event_loop_for_new_pipeline(
876        &self,
877        load_data: &LoadData,
878        webview_id: WebViewId,
879        parent_pipeline_id: Option<PipelineId>,
880        registered_domain_name: &Option<Host>,
881    ) -> Option<Rc<EventLoop>> {
882        // Never reuse an existing EventLoop when requesting a sandboxed origin.
883        if load_data
884            .creation_sandboxing_flag_set
885            .contains(SandboxingFlagSet::SANDBOXED_ORIGIN_BROWSING_CONTEXT_FLAG)
886        {
887            return None;
888        }
889
890        // If this is an about:blank or about:srcdoc load, it must share the creator's
891        // event loop. This must match the logic in the ScriptThread when determining
892        // the proper origin.
893        if load_data.url.as_str() == "about:blank" || load_data.url.as_str() == "about:srcdoc" {
894            if let Some(parent) =
895                parent_pipeline_id.and_then(|pipeline_id| self.pipelines.get(&pipeline_id))
896            {
897                return Some(parent.event_loop.clone());
898            }
899
900            if let Some(creator) = load_data
901                .creator_pipeline_id
902                .and_then(|pipeline_id| self.pipelines.get(&pipeline_id))
903            {
904                return Some(creator.event_loop.clone());
905            }
906
907            // This might happen if a new Pipeline is requested and in the meantime the parent
908            // Pipeline has shut down. In this case, just make a new ScriptThread.
909            return None;
910        }
911
912        let Some(registered_domain_name) = registered_domain_name else {
913            return None;
914        };
915
916        self.get_event_loop(registered_domain_name, &webview_id)
917            .ok()?
918            .upgrade()
919    }
920
921    fn get_or_create_event_loop_for_new_pipeline(
922        &mut self,
923        webview_id: WebViewId,
924        parent_pipeline_id: Option<PipelineId>,
925        load_data: &LoadData,
926        is_private: bool,
927    ) -> Result<Rc<EventLoop>, SendError> {
928        let registered_domain_name = if load_data
929            .creation_sandboxing_flag_set
930            .contains(SandboxingFlagSet::SANDBOXED_ORIGIN_BROWSING_CONTEXT_FLAG)
931        {
932            None
933        } else {
934            registered_domain_name(&load_data.url)
935        };
936
937        if let Some(event_loop) = self.get_event_loop_for_new_pipeline(
938            load_data,
939            webview_id,
940            parent_pipeline_id,
941            &registered_domain_name,
942        ) {
943            return Ok(event_loop);
944        }
945
946        let event_loop = EventLoop::spawn(self, is_private)?;
947        if let Some(registered_domain_name) = registered_domain_name {
948            self.set_event_loop(&event_loop, registered_domain_name, &webview_id);
949        }
950        Ok(event_loop)
951    }
952
953    /// Helper function for creating a pipeline
954    #[expect(clippy::too_many_arguments)]
955    fn new_pipeline(
956        &mut self,
957        new_pipeline_id: PipelineId,
958        browsing_context_id: BrowsingContextId,
959        webview_id: WebViewId,
960        parent_pipeline_id: Option<PipelineId>,
961        opener: Option<BrowsingContextId>,
962        initial_viewport_details: ViewportDetails,
963        // TODO: we have to provide ownership of the LoadData
964        // here, because it will be send on an ipc channel,
965        // and ipc channels take onership of their data.
966        // https://github.com/servo/ipc-channel/issues/138
967        load_data: LoadData,
968        is_private: bool,
969        target_snapshot_params: TargetSnapshotParams,
970        name: Option<String>,
971    ) {
972        if self.shutting_down {
973            return;
974        }
975
976        debug!("Creating new pipeline ({new_pipeline_id:?}) in {browsing_context_id}");
977        let (webview_hidden, webview_state) = {
978            let Some(webview) = self.webviews.get(&webview_id) else {
979                warn!("Tried to create Pipeline for unknown WebViewId: {webview_id:?}");
980                return;
981            };
982            (webview.hidden(), webview.state())
983        };
984
985        let event_loop = match self.get_or_create_event_loop_for_new_pipeline(
986            webview_id,
987            parent_pipeline_id,
988            &load_data,
989            is_private,
990        ) {
991            Ok(event_loop) => event_loop,
992            Err(error) => return self.handle_send_error(new_pipeline_id, error),
993        };
994
995        let user_content_manager_id = self
996            .webviews
997            .get(&webview_id)
998            .and_then(|webview| webview.user_content_manager_id);
999
1000        let new_pipeline_info = NewPipelineInfo {
1001            webview_state,
1002            parent_info: parent_pipeline_id,
1003            new_pipeline_id,
1004            browsing_context_id,
1005            opener,
1006            load_data,
1007            viewport_details: initial_viewport_details,
1008            user_content_manager_id,
1009            target_snapshot_params,
1010            frame_name: name,
1011        };
1012        let pipeline = match Pipeline::spawn(new_pipeline_info, event_loop, self, webview_hidden) {
1013            Ok(pipeline) => pipeline,
1014            Err(error) => return self.handle_send_error(new_pipeline_id, error),
1015        };
1016
1017        assert!(!self.pipelines.contains_key(&new_pipeline_id));
1018        self.pipelines.insert(new_pipeline_id, pipeline);
1019    }
1020
1021    /// Get an iterator for the fully active browsing contexts in a subtree.
1022    fn fully_active_descendant_browsing_contexts_iter(
1023        &self,
1024        browsing_context_id: BrowsingContextId,
1025    ) -> FullyActiveBrowsingContextsIterator<'_> {
1026        FullyActiveBrowsingContextsIterator::new(
1027            browsing_context_id,
1028            &self.browsing_contexts,
1029            &self.pipelines,
1030        )
1031    }
1032
1033    /// Get an iterator for the fully active browsing contexts in a tree.
1034    fn fully_active_browsing_contexts_iter(
1035        &self,
1036        webview_id: WebViewId,
1037    ) -> FullyActiveBrowsingContextsIterator<'_> {
1038        self.fully_active_descendant_browsing_contexts_iter(BrowsingContextId::from(webview_id))
1039    }
1040
1041    /// Get an iterator for the browsing contexts in a subtree.
1042    fn all_descendant_browsing_contexts_iter(
1043        &self,
1044        browsing_context_id: BrowsingContextId,
1045    ) -> AllBrowsingContextsIterator<'_> {
1046        AllBrowsingContextsIterator {
1047            stack: vec![browsing_context_id],
1048            pipelines: &self.pipelines,
1049            browsing_contexts: &self.browsing_contexts,
1050        }
1051    }
1052
1053    /// Enumerate the specified browsing context's ancestor pipelines up to
1054    /// the top-level pipeline.
1055    fn ancestor_pipelines_of_browsing_context_iter(
1056        &self,
1057        browsing_context_id: BrowsingContextId,
1058    ) -> impl Iterator<Item = &Pipeline> + '_ {
1059        let mut state: Option<PipelineId> = self
1060            .browsing_contexts
1061            .get(&browsing_context_id)
1062            .and_then(|browsing_context| browsing_context.parent_pipeline_id);
1063        std::iter::from_fn(move || {
1064            if let Some(pipeline_id) = state {
1065                let pipeline = self.pipelines.get(&pipeline_id)?;
1066                let browsing_context = self.browsing_contexts.get(&pipeline.browsing_context_id)?;
1067                state = browsing_context.parent_pipeline_id;
1068                Some(pipeline)
1069            } else {
1070                None
1071            }
1072        })
1073    }
1074
1075    /// Enumerate the specified browsing context's ancestor-or-self pipelines up
1076    /// to the top-level pipeline.
1077    fn ancestor_or_self_pipelines_of_browsing_context_iter(
1078        &self,
1079        browsing_context_id: BrowsingContextId,
1080    ) -> impl Iterator<Item = &Pipeline> + '_ {
1081        let this_pipeline = self
1082            .browsing_contexts
1083            .get(&browsing_context_id)
1084            .map(|browsing_context| browsing_context.pipeline_id)
1085            .and_then(|pipeline_id| self.pipelines.get(&pipeline_id));
1086        this_pipeline
1087            .into_iter()
1088            .chain(self.ancestor_pipelines_of_browsing_context_iter(browsing_context_id))
1089    }
1090
1091    /// Create a new browsing context and update the internal bookkeeping.
1092    #[expect(clippy::too_many_arguments)]
1093    fn new_browsing_context(
1094        &mut self,
1095        browsing_context_id: BrowsingContextId,
1096        webview_id: WebViewId,
1097        pipeline_id: PipelineId,
1098        parent_pipeline_id: Option<PipelineId>,
1099        viewport_details: ViewportDetails,
1100        is_private: bool,
1101        inherited_secure_context: Option<bool>,
1102    ) {
1103        debug!("{browsing_context_id}: Creating new browsing context");
1104        let Some(webview) = self.webviews.get_mut(&webview_id) else {
1105            println!("Adding BrowsingContext for unknown WebView: {webview_id:?}");
1106            return;
1107        };
1108
1109        // Override the viewport details if we have a pending change for that browsing context.
1110        let viewport_details = webview
1111            .take_pending_viewport_details(&browsing_context_id)
1112            .unwrap_or(viewport_details);
1113        let browsing_context = BrowsingContext::new(
1114            browsing_context_id,
1115            webview_id,
1116            pipeline_id,
1117            parent_pipeline_id,
1118            viewport_details,
1119            is_private,
1120            inherited_secure_context,
1121        );
1122        self.browsing_contexts
1123            .insert(browsing_context_id, browsing_context);
1124
1125        // If this context is a nested container, attach it to parent pipeline.
1126        if let Some(parent_pipeline_id) = parent_pipeline_id &&
1127            let Some(parent) = self.pipelines.get_mut(&parent_pipeline_id)
1128        {
1129            parent.add_child(browsing_context_id);
1130        }
1131    }
1132
1133    /// Handles loading pages, navigation, and granting access to `Paint`.
1134    #[servo_tracing::instrument(skip_all)]
1135    fn handle_request(&mut self) {
1136        #[expect(clippy::large_enum_variant)]
1137        #[derive(Debug)]
1138        enum Request {
1139            PipelineNamespace(PipelineNamespaceRequest),
1140            Script((WebViewId, PipelineId, ScriptToConstellationMessage)),
1141            BackgroundHangMonitor(HangAlert),
1142            Embedder(EmbedderToConstellationMessage),
1143            #[cfg_attr(not(feature = "multiprocess"), expect(unused))]
1144            RemoveProcess(usize),
1145        }
1146        // Get one incoming request.
1147        // This is one of the few places where `Paint` is
1148        // allowed to panic. If one of the receiver.recv() calls
1149        // fails, it is because the matching sender has been
1150        // reclaimed, but this can't happen in normal execution
1151        // because the constellation keeps a pointer to the sender,
1152        // so it should never be reclaimed. A possible scenario in
1153        // which receiver.recv() fails is if some unsafe code
1154        // produces undefined behaviour, resulting in the destructor
1155        // being called. If this happens, there's not much we can do
1156        // other than panic.
1157        let mut sel = Select::new();
1158        sel.recv(&self.namespace_receiver);
1159        sel.recv(&self.script_receiver);
1160        sel.recv(&self.background_hang_monitor_receiver);
1161        sel.recv(&self.embedder_to_constellation_receiver);
1162
1163        #[cfg(feature = "multiprocess")]
1164        self.process_manager.register(&mut sel);
1165
1166        let request = {
1167            let oper = {
1168                let _span = profile_traits::trace_span!("handle_request::select").entered();
1169                sel.select()
1170            };
1171            let index = oper.index();
1172
1173            match index {
1174                0 => oper
1175                    .recv(&self.namespace_receiver)
1176                    .expect("Unexpected script channel panic in constellation")
1177                    .map(Request::PipelineNamespace),
1178                1 => oper
1179                    .recv(&self.script_receiver)
1180                    .expect("Unexpected script channel panic in constellation")
1181                    .map(Request::Script),
1182                2 => oper
1183                    .recv(&self.background_hang_monitor_receiver)
1184                    .expect("Unexpected BHM channel panic in constellation")
1185                    .map(Request::BackgroundHangMonitor),
1186                3 => Ok(Request::Embedder(
1187                    oper.recv(&self.embedder_to_constellation_receiver)
1188                        .expect("Unexpected embedder channel panic in constellation"),
1189                )),
1190                _ => {
1191                    // This can only be a error reading on a closed lifeline receiver.
1192                    let process_index = index - 4;
1193                    #[cfg(feature = "multiprocess")]
1194                    let _ = oper.recv(self.process_manager.receiver_at(process_index));
1195                    Ok(Request::RemoveProcess(process_index))
1196                },
1197            }
1198        };
1199
1200        let request = match request {
1201            Ok(request) => request,
1202            Err(err) => return error!("Deserialization failed ({}).", err),
1203        };
1204
1205        match request {
1206            Request::PipelineNamespace(message) => {
1207                self.handle_request_for_pipeline_namespace(message)
1208            },
1209            Request::Embedder(message) => self.handle_request_from_embedder(message),
1210            Request::Script(message) => {
1211                self.handle_request_from_script(message);
1212            },
1213            Request::BackgroundHangMonitor(message) => {
1214                self.handle_request_from_background_hang_monitor(message);
1215            },
1216            #[cfg(feature = "multiprocess")]
1217            Request::RemoveProcess(index) => self.process_manager.remove(index),
1218            #[cfg(not(feature = "multiprocess"))]
1219            Request::RemoveProcess(_) => {},
1220        }
1221    }
1222
1223    #[servo_tracing::instrument(skip_all)]
1224    fn handle_request_for_pipeline_namespace(&mut self, request: PipelineNamespaceRequest) {
1225        let PipelineNamespaceRequest(sender) = request;
1226        let _ = sender.send(self.next_pipeline_namespace_id());
1227    }
1228
1229    #[servo_tracing::instrument(skip_all)]
1230    fn handle_request_from_background_hang_monitor(&self, message: HangAlert) {
1231        // TODO: In case of a permanent hang being reported, add a "kill script" workflow,
1232        // via the embedder?
1233        warn!("Component hang alert: {:?}", message);
1234    }
1235
1236    #[servo_tracing::instrument(skip_all)]
1237    fn handle_request_from_embedder(&mut self, message: EmbedderToConstellationMessage) {
1238        trace_msg_from_embedder!(message, "{message:?}");
1239        match message {
1240            EmbedderToConstellationMessage::Exit => {
1241                self.handle_exit();
1242            },
1243            EmbedderToConstellationMessage::AllowNavigationResponse(pipeline_id, allowed) => {
1244                self.handle_allow_navigation_response(pipeline_id, allowed);
1245            },
1246            // Load a new page from a typed url
1247            // If there is already a pending page (self.pending_changes), it will not be overridden;
1248            // However, if the id is not encompassed by another change, it will be.
1249            EmbedderToConstellationMessage::LoadUrl(webview_id, url_request) => {
1250                let mut load_data = LoadData::new_for_new_unrelated_webview(url_request.url);
1251
1252                if !url_request.headers.is_empty() {
1253                    load_data.headers.extend(url_request.headers);
1254                }
1255
1256                let ctx_id = BrowsingContextId::from(webview_id);
1257                let pipeline_id = match self.browsing_contexts.get(&ctx_id) {
1258                    Some(ctx) => ctx.pipeline_id,
1259                    None => {
1260                        return warn!("{}: LoadUrl for unknown browsing context", webview_id);
1261                    },
1262                };
1263                // Since this is a top-level load, initiated by the embedder, go straight to load_url,
1264                // bypassing schedule_navigation.
1265                self.load_url(
1266                    webview_id,
1267                    pipeline_id,
1268                    load_data,
1269                    NavigationHistoryBehavior::Push,
1270                    TargetSnapshotParams::default(),
1271                );
1272            },
1273            // Create a new top level browsing context. Will use response_chan to return
1274            // the browsing context id.
1275            EmbedderToConstellationMessage::NewWebView(url, new_webview_details) => {
1276                self.handle_new_top_level_browsing_context(url, new_webview_details);
1277            },
1278            // Close a top level browsing context.
1279            EmbedderToConstellationMessage::CloseWebView(webview_id) => {
1280                self.handle_close_top_level_browsing_context(webview_id);
1281            },
1282            EmbedderToConstellationMessage::SetWebViewHasSystemFocus(
1283                webview_id,
1284                has_system_focus,
1285            ) => {
1286                self.handle_set_has_system_focus(webview_id, has_system_focus);
1287            },
1288            // Handle a forward or back request
1289            EmbedderToConstellationMessage::TraverseHistory(request) => {
1290                self.handle_traverse_session_history_msg(request);
1291            },
1292            EmbedderToConstellationMessage::ChangeViewportDetails(
1293                webview_id,
1294                new_viewport_details,
1295                size_type,
1296            ) => {
1297                self.handle_change_viewport_details_msg(
1298                    webview_id,
1299                    new_viewport_details,
1300                    size_type,
1301                );
1302            },
1303            EmbedderToConstellationMessage::ThemeChange(webview_id, theme) => {
1304                self.handle_theme_change(webview_id, theme);
1305            },
1306            EmbedderToConstellationMessage::TickAnimation(webview_ids) => {
1307                self.handle_tick_animation(webview_ids)
1308            },
1309            EmbedderToConstellationMessage::NoLongerWaitingOnAsynchronousImageUpdates(
1310                pipeline_ids,
1311            ) => self.handle_no_longer_waiting_on_asynchronous_image_updates(pipeline_ids),
1312            EmbedderToConstellationMessage::WebDriverCommand(command) => {
1313                self.handle_webdriver_msg(command);
1314            },
1315            EmbedderToConstellationMessage::Reload(webview_id) => {
1316                self.handle_reload_msg(webview_id);
1317            },
1318            EmbedderToConstellationMessage::LogEntry(event_loop_id, thread_name, entry) => {
1319                self.handle_log_entry(event_loop_id, thread_name, entry);
1320            },
1321            EmbedderToConstellationMessage::ForwardInputEvent(webview_id, event, hit_test) => {
1322                self.forward_input_event(webview_id, event, hit_test);
1323            },
1324            EmbedderToConstellationMessage::RefreshCursor(pipeline_id) => {
1325                self.handle_refresh_cursor(pipeline_id)
1326            },
1327            EmbedderToConstellationMessage::ExitFullScreen(webview_id) => {
1328                self.handle_exit_fullscreen_msg(webview_id);
1329            },
1330            EmbedderToConstellationMessage::MediaSessionAction(action) => {
1331                self.handle_media_session_action_msg(action);
1332            },
1333            EmbedderToConstellationMessage::SetWebViewHidden(webview_id, hidden) => {
1334                self.set_webview_hidden(webview_id, hidden);
1335            },
1336            EmbedderToConstellationMessage::SetScrollStates(pipeline_id, scroll_states) => {
1337                self.handle_set_scroll_states(pipeline_id, scroll_states)
1338            },
1339            EmbedderToConstellationMessage::PaintMetric(pipeline_id, paint_metric_event) => {
1340                self.handle_paint_metric(pipeline_id, paint_metric_event);
1341            },
1342            EmbedderToConstellationMessage::EvaluateJavaScript(
1343                webview_id,
1344                evaluation_id,
1345                script,
1346            ) => {
1347                self.handle_evaluate_javascript(webview_id, evaluation_id, script);
1348            },
1349            EmbedderToConstellationMessage::CreateMemoryReport(sender) => {
1350                self.mem_profiler_chan.send(ProfilerMsg::Report(sender));
1351            },
1352            EmbedderToConstellationMessage::SendImageKeysForPipeline(pipeline_id, image_keys) => {
1353                if let Some(pipeline) = self.pipelines.get(&pipeline_id) {
1354                    if pipeline
1355                        .event_loop
1356                        .send(ScriptThreadMessage::SendImageKeysBatch(
1357                            pipeline_id,
1358                            image_keys,
1359                        ))
1360                        .is_err()
1361                    {
1362                        warn!("Could not send image keys to pipeline {:?}", pipeline_id);
1363                    }
1364                } else {
1365                    warn!(
1366                        "Keys were generated for a pipeline ({:?}) that was
1367                            closed before the request could be fulfilled.",
1368                        pipeline_id
1369                    )
1370                }
1371            },
1372            EmbedderToConstellationMessage::PreferencesUpdated(updates) => {
1373                let event_loops = self
1374                    .pipelines
1375                    .values()
1376                    .map(|pipeline| pipeline.event_loop.clone());
1377                for event_loop in event_loops {
1378                    let _ = event_loop.send(ScriptThreadMessage::PreferencesUpdated(
1379                        updates
1380                            .iter()
1381                            .map(|(name, value)| (String::from(*name), value.clone()))
1382                            .collect(),
1383                    ));
1384                }
1385            },
1386            EmbedderToConstellationMessage::RequestScreenshotReadiness(webview_id) => {
1387                if let Some(webview) = self.webviews.get_mut(&webview_id) {
1388                    webview.handle_screenshot_readiness_request(
1389                        &self.browsing_contexts,
1390                        &self.pipelines,
1391                    );
1392                }
1393            },
1394            EmbedderToConstellationMessage::EmbedderControlResponse(id, response) => {
1395                self.handle_embedder_control_response(id, response);
1396            },
1397            EmbedderToConstellationMessage::UserContentManagerAction(
1398                user_content_manager_id,
1399                action,
1400            ) => {
1401                self.handle_user_content_manager_action(user_content_manager_id, action);
1402            },
1403            EmbedderToConstellationMessage::UpdatePinchZoomInfos(pipeline_id, pinch_zoom) => {
1404                self.handle_update_pinch_zoom_infos(pipeline_id, pinch_zoom);
1405            },
1406            EmbedderToConstellationMessage::SetAccessibilityActive(webview_id, active) => {
1407                self.set_accessibility_active(webview_id, active);
1408            },
1409            EmbedderToConstellationMessage::ForwardAccessibilityAction(action_request) => {
1410                self.forward_accessibility_action(action_request);
1411            },
1412            EmbedderToConstellationMessage::ClearSessionHistory(webview_id) => {
1413                self.handle_clear_session_history(webview_id);
1414            },
1415        }
1416    }
1417
1418    fn mutate_user_contents_for_manager_id_and_notify_script_threads(
1419        &mut self,
1420        user_content_manager_id: UserContentManagerId,
1421        callback: impl FnOnce(&mut UserContents),
1422    ) {
1423        let event_loops = self.event_loops();
1424        let user_contents = self
1425            .user_contents_for_manager_id
1426            .entry(user_content_manager_id)
1427            .or_default();
1428
1429        callback(user_contents);
1430
1431        for event_loop in event_loops {
1432            let _ = event_loop.send(ScriptThreadMessage::SetUserContents(
1433                user_content_manager_id,
1434                user_contents.clone(),
1435            ));
1436        }
1437    }
1438
1439    fn handle_user_content_manager_action(
1440        &mut self,
1441        user_content_manager_id: UserContentManagerId,
1442        action: UserContentManagerAction,
1443    ) {
1444        match action {
1445            UserContentManagerAction::AddUserScript(user_script) => {
1446                self.mutate_user_contents_for_manager_id_and_notify_script_threads(
1447                    user_content_manager_id,
1448                    |user_contents| {
1449                        user_contents.scripts.push(user_script);
1450                    },
1451                );
1452            },
1453            UserContentManagerAction::RemoveUserScript(user_script_id) => {
1454                self.mutate_user_contents_for_manager_id_and_notify_script_threads(
1455                    user_content_manager_id,
1456                    |user_contents| {
1457                        user_contents
1458                            .scripts
1459                            .retain(|user_script| user_script.id() != user_script_id);
1460                    },
1461                );
1462            },
1463            UserContentManagerAction::AddUserStyleSheet(user_stylesheet) => {
1464                self.mutate_user_contents_for_manager_id_and_notify_script_threads(
1465                    user_content_manager_id,
1466                    |user_contents| {
1467                        user_contents.stylesheets.push(user_stylesheet);
1468                    },
1469                );
1470            },
1471            UserContentManagerAction::RemoveUserStyleSheet(user_stylesheet_id) => {
1472                self.mutate_user_contents_for_manager_id_and_notify_script_threads(
1473                    user_content_manager_id,
1474                    |user_contents| {
1475                        user_contents
1476                            .stylesheets
1477                            .retain(|user_stylesheet| user_stylesheet.id() != user_stylesheet_id);
1478                    },
1479                );
1480            },
1481            UserContentManagerAction::DestroyUserContentManager => {
1482                self.user_contents_for_manager_id
1483                    .remove(&user_content_manager_id);
1484
1485                for event_loop in self.event_loops() {
1486                    let _ = event_loop.send(ScriptThreadMessage::DestroyUserContentManager(
1487                        user_content_manager_id,
1488                    ));
1489                }
1490            },
1491        }
1492    }
1493
1494    fn send_message_to_all_background_hang_monitors(
1495        &self,
1496        message: BackgroundHangMonitorControlMsg,
1497    ) {
1498        if let Some(background_monitor_control_sender) = &self.background_monitor_control_sender &&
1499            let Err(error) = background_monitor_control_sender.send(message.clone())
1500        {
1501            error!("Could not send message ({message:?}) to BHM: {error}");
1502        }
1503        for event_loop in self.event_loops() {
1504            event_loop.send_message_to_background_hang_monitor(&message);
1505        }
1506    }
1507
1508    #[servo_tracing::instrument(skip_all)]
1509    fn handle_evaluate_javascript(
1510        &mut self,
1511        webview_id: WebViewId,
1512        evaluation_id: JavaScriptEvaluationId,
1513        script: String,
1514    ) {
1515        let browsing_context_id = BrowsingContextId::from(webview_id);
1516        let Some(pipeline) = self
1517            .browsing_contexts
1518            .get(&browsing_context_id)
1519            .and_then(|browsing_context| self.pipelines.get(&browsing_context.pipeline_id))
1520        else {
1521            self.handle_finish_javascript_evaluation(
1522                evaluation_id,
1523                Err(JavaScriptEvaluationError::InternalError),
1524            );
1525            return;
1526        };
1527
1528        if pipeline
1529            .event_loop
1530            .send(ScriptThreadMessage::EvaluateJavaScript(
1531                webview_id,
1532                pipeline.id,
1533                evaluation_id,
1534                script,
1535            ))
1536            .is_err()
1537        {
1538            self.handle_finish_javascript_evaluation(
1539                evaluation_id,
1540                Err(JavaScriptEvaluationError::InternalError),
1541            );
1542        }
1543    }
1544
1545    #[servo_tracing::instrument(skip_all)]
1546    fn handle_request_from_script(
1547        &mut self,
1548        message: (WebViewId, PipelineId, ScriptToConstellationMessage),
1549    ) {
1550        let (webview_id, source_pipeline_id, content) = message;
1551        trace_script_msg!(content, "{source_pipeline_id}: {content:?}");
1552
1553        match content {
1554            ScriptToConstellationMessage::CompleteMessagePortTransfer(router_id, ports) => {
1555                self.handle_complete_message_port_transfer(router_id, ports);
1556            },
1557            ScriptToConstellationMessage::MessagePortTransferResult(
1558                router_id,
1559                succeeded,
1560                failed,
1561            ) => {
1562                self.handle_message_port_transfer_completed(router_id, succeeded);
1563                self.handle_message_port_transfer_failed(failed);
1564            },
1565            ScriptToConstellationMessage::RerouteMessagePort(port_id, task) => {
1566                self.handle_reroute_messageport(port_id, task);
1567            },
1568            ScriptToConstellationMessage::MessagePortShipped(port_id) => {
1569                self.handle_messageport_shipped(port_id);
1570            },
1571            ScriptToConstellationMessage::NewMessagePortRouter(router_id, callback) => {
1572                self.handle_new_messageport_router(router_id, callback);
1573            },
1574            ScriptToConstellationMessage::RemoveMessagePortRouter(router_id) => {
1575                self.handle_remove_messageport_router(router_id);
1576            },
1577            ScriptToConstellationMessage::NewMessagePort(router_id, port_id) => {
1578                self.handle_new_messageport(router_id, port_id);
1579            },
1580            ScriptToConstellationMessage::EntanglePorts(port1, port2) => {
1581                self.handle_entangle_messageports(port1, port2);
1582            },
1583            ScriptToConstellationMessage::DisentanglePorts(port1, port2) => {
1584                self.handle_disentangle_messageports(port1, port2);
1585            },
1586            ScriptToConstellationMessage::NewBroadcastChannelRouter(
1587                router_id,
1588                response_sender,
1589                origin,
1590            ) => {
1591                if self
1592                    .check_origin_against_pipeline(&source_pipeline_id, &origin)
1593                    .is_err()
1594                {
1595                    return warn!("Attempt to add broadcast router from an unexpected origin.");
1596                }
1597                self.broadcast_channels
1598                    .new_broadcast_channel_router(router_id, response_sender);
1599            },
1600            ScriptToConstellationMessage::NewBroadcastChannelNameInRouter(
1601                router_id,
1602                channel_name,
1603                origin,
1604            ) => {
1605                if self
1606                    .check_origin_against_pipeline(&source_pipeline_id, &origin)
1607                    .is_err()
1608                {
1609                    return warn!("Attempt to add channel name from an unexpected origin.");
1610                }
1611                self.broadcast_channels
1612                    .new_broadcast_channel_name_in_router(router_id, channel_name, origin);
1613            },
1614            ScriptToConstellationMessage::RemoveBroadcastChannelNameInRouter(
1615                router_id,
1616                channel_name,
1617                origin,
1618            ) => {
1619                if self
1620                    .check_origin_against_pipeline(&source_pipeline_id, &origin)
1621                    .is_err()
1622                {
1623                    return warn!("Attempt to remove channel name from an unexpected origin.");
1624                }
1625                self.broadcast_channels
1626                    .remove_broadcast_channel_name_in_router(router_id, channel_name, origin);
1627            },
1628            ScriptToConstellationMessage::RemoveBroadcastChannelRouter(router_id, origin) => {
1629                if self
1630                    .check_origin_against_pipeline(&source_pipeline_id, &origin)
1631                    .is_err()
1632                {
1633                    return warn!("Attempt to remove broadcast router from an unexpected origin.");
1634                }
1635                self.broadcast_channels
1636                    .remove_broadcast_channel_router(router_id);
1637            },
1638            ScriptToConstellationMessage::ScheduleBroadcast(router_id, message) => {
1639                if self
1640                    .check_origin_against_pipeline(&source_pipeline_id, &message.origin)
1641                    .is_err()
1642                {
1643                    return warn!(
1644                        "Attempt to schedule broadcast from an origin not matching the origin of the msg."
1645                    );
1646                }
1647                self.broadcast_channels
1648                    .schedule_broadcast(router_id, message);
1649            },
1650            ScriptToConstellationMessage::PipelineExited => {
1651                self.handle_pipeline_exited(source_pipeline_id, PipelineExitSource::Constellation);
1652            },
1653            ScriptToConstellationMessage::DiscardDocument => {
1654                self.handle_discard_document(webview_id, source_pipeline_id);
1655            },
1656            ScriptToConstellationMessage::DiscardTopLevelBrowsingContext => {
1657                self.handle_close_top_level_browsing_context(webview_id);
1658            },
1659            ScriptToConstellationMessage::ScriptLoadedURLInIFrame(load_info) => {
1660                self.handle_script_loaded_url_in_iframe_msg(load_info);
1661            },
1662            ScriptToConstellationMessage::ScriptNewIFrame(load_info) => {
1663                self.handle_script_new_iframe(load_info);
1664            },
1665            ScriptToConstellationMessage::CreateAuxiliaryWebView(load_info) => {
1666                self.handle_script_new_auxiliary(load_info);
1667            },
1668            ScriptToConstellationMessage::ChangeRunningAnimationsState(animation_state) => {
1669                self.handle_change_running_animations_state(source_pipeline_id, animation_state)
1670            },
1671            ScriptToConstellationMessage::RegisterWorkerAnimationFrameProvider(
1672                worker_id,
1673                sender,
1674            ) => self.handle_register_worker_animation_frame_provider(
1675                webview_id,
1676                source_pipeline_id,
1677                worker_id,
1678                sender,
1679            ),
1680            ScriptToConstellationMessage::UnregisterWorkerAnimationFrameProvider(worker_id) => {
1681                self.handle_unregister_worker_animation_frame_provider(worker_id)
1682            },
1683            ScriptToConstellationMessage::ChangeWorkerAnimationFrameProviderState(
1684                worker_id,
1685                active,
1686            ) => self.handle_change_worker_animation_frame_provider_state(
1687                source_pipeline_id,
1688                worker_id,
1689                active,
1690            ),
1691            // Ask the embedder for permission to load a new page.
1692            ScriptToConstellationMessage::LoadUrl(
1693                load_data,
1694                history_handling,
1695                target_snapshot_params,
1696            ) => {
1697                self.schedule_navigation(
1698                    webview_id,
1699                    source_pipeline_id,
1700                    load_data,
1701                    history_handling,
1702                    target_snapshot_params,
1703                );
1704            },
1705            ScriptToConstellationMessage::AbortLoadUrl => {
1706                self.handle_abort_load_url_msg(webview_id, source_pipeline_id);
1707            },
1708            // A page loaded has completed all parsing, script, and reflow messages have been sent.
1709            ScriptToConstellationMessage::LoadComplete => {
1710                self.handle_load_complete_msg(webview_id, source_pipeline_id)
1711            },
1712            // Handle navigating to a fragment
1713            ScriptToConstellationMessage::NavigatedToFragment(new_url, replacement_enabled) => {
1714                self.handle_navigated_to_fragment(source_pipeline_id, new_url, replacement_enabled);
1715            },
1716            // Handle a forward or back request
1717            ScriptToConstellationMessage::TraverseHistory(request) => {
1718                self.handle_traverse_session_history_msg(request);
1719            },
1720            // Handle a push history state request.
1721            ScriptToConstellationMessage::PushHistoryState(history_state_id, url) => {
1722                self.handle_push_history_state_msg(source_pipeline_id, history_state_id, url);
1723            },
1724            ScriptToConstellationMessage::ReplaceHistoryState(history_state_id, url) => {
1725                self.handle_replace_history_state_msg(source_pipeline_id, history_state_id, url);
1726            },
1727            // Handle a joint session history length request.
1728            ScriptToConstellationMessage::JointSessionHistoryLength(response_sender) => {
1729                self.handle_joint_session_history_length(webview_id, response_sender);
1730            },
1731            // Notification that the new document is ready to become active
1732            ScriptToConstellationMessage::ActivateDocument => {
1733                self.handle_activate_document_msg(webview_id, source_pipeline_id);
1734            },
1735            // Update pipeline url after redirections
1736            ScriptToConstellationMessage::SetFinalUrl(final_url) => {
1737                // The script may have finished loading after we already started shutting down.
1738                if let Some(ref mut pipeline) = self.pipelines.get_mut(&source_pipeline_id) {
1739                    pipeline.url = final_url;
1740                } else {
1741                    warn!("constellation got set final url message for dead pipeline");
1742                }
1743            },
1744            ScriptToConstellationMessage::PostMessage {
1745                target: browsing_context_id,
1746                source: source_pipeline_id,
1747                target_origin: origin,
1748                source_origin,
1749                data,
1750            } => {
1751                self.handle_post_message_msg(
1752                    browsing_context_id,
1753                    source_pipeline_id,
1754                    origin,
1755                    source_origin,
1756                    data,
1757                );
1758            },
1759            ScriptToConstellationMessage::FocusAncestorBrowsingContextsForFocusingSteps(
1760                focused_child_browsing_context_id,
1761                sequence,
1762            ) => {
1763                self.handle_focus_ancestor_browsing_contexts_for_focusing_steps(
1764                    source_pipeline_id,
1765                    focused_child_browsing_context_id,
1766                    sequence,
1767                );
1768            },
1769            ScriptToConstellationMessage::FocusRemoteBrowsingContext(
1770                focused_browsing_context_id,
1771                remote_focus_operation,
1772            ) => {
1773                self.handle_focus_remote_browsing_context(
1774                    focused_browsing_context_id,
1775                    remote_focus_operation,
1776                );
1777            },
1778            ScriptToConstellationMessage::RemoveIFrame(browsing_context_id, response_sender) => {
1779                let removed_pipeline_ids =
1780                    self.handle_remove_iframe_msg(webview_id, browsing_context_id);
1781                if let Err(e) = response_sender.send(removed_pipeline_ids) {
1782                    warn!("Error replying to remove iframe ({})", e);
1783                }
1784            },
1785            ScriptToConstellationMessage::CreateCanvasPaintThread(size, response_sender) => {
1786                self.handle_create_canvas_paint_thread_msg(size, response_sender)
1787            },
1788            ScriptToConstellationMessage::LogEntry(event_loop_id, thread_name, entry) => {
1789                self.handle_log_entry(event_loop_id, thread_name, entry);
1790            },
1791            ScriptToConstellationMessage::GetBrowsingContextInfo(pipeline_id, response_sender) => {
1792                let result = self
1793                    .pipelines
1794                    .get(&pipeline_id)
1795                    .and_then(|pipeline| self.browsing_contexts.get(&pipeline.browsing_context_id))
1796                    .map(|ctx| (ctx.id, ctx.parent_pipeline_id));
1797                if let Err(e) = response_sender.send(result) {
1798                    warn!(
1799                        "Sending reply to get browsing context info failed ({:?}).",
1800                        e
1801                    );
1802                }
1803            },
1804            ScriptToConstellationMessage::GetTopForBrowsingContext(
1805                browsing_context_id,
1806                response_sender,
1807            ) => {
1808                let result = self
1809                    .browsing_contexts
1810                    .get(&browsing_context_id)
1811                    .map(|bc| bc.webview_id);
1812                if let Err(e) = response_sender.send(result) {
1813                    warn!(
1814                        "Sending reply to get top for browsing context info failed ({:?}).",
1815                        e
1816                    );
1817                }
1818            },
1819            ScriptToConstellationMessage::GetChildBrowsingContextCount(
1820                browsing_context_id,
1821                response_sender,
1822            ) => {
1823                let count = self
1824                    .browsing_contexts
1825                    .get(&browsing_context_id)
1826                    .and_then(|browsing_context| self.pipelines.get(&browsing_context.pipeline_id))
1827                    .map(|pipeline| pipeline.children.len())
1828                    .unwrap_or_default();
1829                if let Err(error) = response_sender.send(count) {
1830                    warn!("Sending reply to get child browsing context count failed ({error:?}).",);
1831                }
1832            },
1833
1834            ScriptToConstellationMessage::GetChildBrowsingContextId(
1835                browsing_context_id,
1836                index,
1837                response_sender,
1838            ) => {
1839                let result = self
1840                    .browsing_contexts
1841                    .get(&browsing_context_id)
1842                    .and_then(|bc| self.pipelines.get(&bc.pipeline_id))
1843                    .and_then(|pipeline| pipeline.children.get(index))
1844                    .copied();
1845                if let Err(error) = response_sender.send(result) {
1846                    warn!("Sending reply to get child browsing context ID failed ({error:?}).",);
1847                }
1848            },
1849            ScriptToConstellationMessage::IsCurrentlyFullyActive(pipeline_id, response_sender) => {
1850                if let Err(error) = response_sender
1851                    .send(self.get_activity(pipeline_id) == DocumentActivity::FullyActive)
1852                {
1853                    warn!("Sending reply to get document activity failed ({error:?}).");
1854                }
1855            },
1856            ScriptToConstellationMessage::GetDocumentOrigin(pipeline_id, response_sender) => {
1857                self.send_message_to_pipeline(
1858                    pipeline_id,
1859                    ScriptThreadMessage::GetDocumentOrigin(pipeline_id, response_sender),
1860                    "Document origin retrieval after closure",
1861                );
1862            },
1863            ScriptToConstellationMessage::GetDocumentOriginDetails(
1864                pipeline_id,
1865                response_sender,
1866            ) => {
1867                self.send_message_to_pipeline(
1868                    pipeline_id,
1869                    ScriptThreadMessage::GetDocumentOriginDetails(pipeline_id, response_sender),
1870                    "Document ancestor origin details retrieval after closure",
1871                );
1872            },
1873            ScriptToConstellationMessage::ServiceWorkerAlgorithm(algorithm) => {
1874                self.handle_serviceworker_algorithm(source_pipeline_id, algorithm);
1875            },
1876            ScriptToConstellationMessage::ForwardDOMMessage(msg_vec, scope_url) => {
1877                if let Some(mgr) = self.sw_managers.get(&scope_url.origin()) {
1878                    let _ = mgr.send(ServiceWorkerMsg::ForwardDOMMessage(msg_vec, scope_url));
1879                } else {
1880                    warn!("Unable to forward DOMMessage for postMessage call");
1881                }
1882            },
1883            ScriptToConstellationMessage::RegisterInterest(interest) => {
1884                self.pipeline_interests
1885                    .entry(interest)
1886                    .or_default()
1887                    .insert(source_pipeline_id);
1888            },
1889            ScriptToConstellationMessage::UnregisterInterest(interest) => {
1890                if let Some(set) = self.pipeline_interests.get_mut(&interest) {
1891                    set.remove(&source_pipeline_id);
1892                    if set.is_empty() {
1893                        self.pipeline_interests.remove(&interest);
1894                    }
1895                }
1896            },
1897            ScriptToConstellationMessage::BroadcastStorageEvent(
1898                storage,
1899                url,
1900                key,
1901                old_value,
1902                new_value,
1903            ) => {
1904                self.handle_broadcast_storage_event(
1905                    source_pipeline_id,
1906                    storage,
1907                    url,
1908                    key,
1909                    old_value,
1910                    new_value,
1911                );
1912            },
1913            ScriptToConstellationMessage::MediaSessionEvent(pipeline_id, event) => {
1914                // Unlikely at this point, but we may receive events coming from
1915                // different media sessions, so we set the active media session based
1916                // on Playing events.
1917                // The last media session claiming to be in playing state is set to
1918                // the active media session.
1919                // Events coming from inactive media sessions are discarded.
1920                if self.active_media_session.is_some() &&
1921                    let MediaSessionEvent::PlaybackStateChange(ref state) = event &&
1922                    !matches!(
1923                        state,
1924                        MediaSessionPlaybackState::Playing | MediaSessionPlaybackState::Paused
1925                    )
1926                {
1927                    return;
1928                };
1929                self.active_media_session = Some(pipeline_id);
1930                self.constellation_to_embedder_proxy.send(
1931                    ConstellationToEmbedderMsg::MediaSessionEvent(webview_id, event),
1932                );
1933            },
1934            #[cfg(feature = "webgpu")]
1935            ScriptToConstellationMessage::RequestAdapter(response_sender, options, ids) => self
1936                .handle_wgpu_request(
1937                    webview_id,
1938                    source_pipeline_id,
1939                    ScriptToConstellationMessage::RequestAdapter(response_sender, options, ids),
1940                ),
1941            #[cfg(feature = "webgpu")]
1942            ScriptToConstellationMessage::GetWebGPUChan(response_sender) => self
1943                .handle_wgpu_request(
1944                    webview_id,
1945                    source_pipeline_id,
1946                    ScriptToConstellationMessage::GetWebGPUChan(response_sender),
1947                ),
1948            ScriptToConstellationMessage::TitleChanged(pipeline, title) => {
1949                if let Some(pipeline) = self.pipelines.get_mut(&pipeline) {
1950                    pipeline.title = title;
1951                }
1952            },
1953            ScriptToConstellationMessage::IFrameSizes(iframe_sizes) => {
1954                self.handle_iframe_size_msg(webview_id, iframe_sizes)
1955            },
1956            ScriptToConstellationMessage::ReportMemory(sender) => {
1957                // get memory report and send it back.
1958                self.mem_profiler_chan
1959                    .send(mem::ProfilerMsg::Report(sender));
1960            },
1961            ScriptToConstellationMessage::FinishJavaScriptEvaluation(evaluation_id, result) => {
1962                self.handle_finish_javascript_evaluation(evaluation_id, result)
1963            },
1964            ScriptToConstellationMessage::ForwardKeyboardScroll(pipeline_id, scroll) => {
1965                if let Some(pipeline) = self.pipelines.get(&pipeline_id) &&
1966                    let Err(error) =
1967                        pipeline
1968                            .event_loop
1969                            .send(ScriptThreadMessage::ForwardKeyboardScroll(
1970                                pipeline_id,
1971                                scroll,
1972                            ))
1973                {
1974                    warn!("Could not forward {scroll:?} to {pipeline_id}: {error:?}");
1975                }
1976            },
1977            ScriptToConstellationMessage::RespondToScreenshotReadinessRequest(response) => {
1978                if let Some(webview) = self.webviews.get_mut(&webview_id) {
1979                    webview.handle_screenshot_readiness_response(
1980                        source_pipeline_id,
1981                        response,
1982                        &self.paint_proxy,
1983                    );
1984                }
1985            },
1986            ScriptToConstellationMessage::TriggerGarbageCollection => {
1987                for event_loop in self.event_loops() {
1988                    let _ = event_loop.send(ScriptThreadMessage::TriggerGarbageCollection);
1989                }
1990            },
1991            ScriptToConstellationMessage::AcquireWakeLock(type_) => match type_ {
1992                WakeLockType::Screen => {
1993                    self.screen_wake_lock_count += 1;
1994                    if self.screen_wake_lock_count == 1 &&
1995                        let Err(e) = self.wake_lock_provider.acquire(type_)
1996                    {
1997                        warn!("Failed to acquire screen wake lock: {e}");
1998                    }
1999                },
2000            },
2001            ScriptToConstellationMessage::ReleaseWakeLock(type_) => match type_ {
2002                WakeLockType::Screen => {
2003                    self.screen_wake_lock_count = self.screen_wake_lock_count.saturating_sub(1);
2004                    if self.screen_wake_lock_count == 0 &&
2005                        let Err(e) = self.wake_lock_provider.release(type_)
2006                    {
2007                        warn!("Failed to release screen wake lock: {e}");
2008                    }
2009                },
2010            },
2011        }
2012    }
2013
2014    /// Check the origin of a message against that of the pipeline it came from.
2015    /// Note: this is still limited as a security check,
2016    /// see <https://github.com/servo/servo/issues/11722>
2017    fn check_origin_against_pipeline(
2018        &self,
2019        pipeline_id: &PipelineId,
2020        origin: &ImmutableOrigin,
2021    ) -> Result<(), ()> {
2022        let pipeline_origin = match self.pipelines.get(pipeline_id) {
2023            Some(pipeline) => pipeline.load_data.url.origin(),
2024            None => {
2025                warn!("Received message from closed or unknown pipeline.");
2026                return Err(());
2027            },
2028        };
2029        if &pipeline_origin == origin {
2030            return Ok(());
2031        }
2032        Err(())
2033    }
2034
2035    #[servo_tracing::instrument(skip_all)]
2036    #[cfg(feature = "webgpu")]
2037    fn handle_wgpu_request(
2038        &mut self,
2039        webview_id: WebViewId,
2040        source_pipeline_id: PipelineId,
2041        request: ScriptToConstellationMessage,
2042    ) {
2043        use webgpu::start_webgpu_thread;
2044
2045        let Some(browsing_context_group) = self.browsing_context_group_for_webview_id(&webview_id)
2046        else {
2047            return warn!("WebView ({webview_id:?}) not found when handling WebGPU request");
2048        };
2049        let Some(source_pipeline) = self.pipelines.get(&source_pipeline_id) else {
2050            return warn!("{source_pipeline_id}: ScriptMsg from closed pipeline");
2051        };
2052        let Some(host) = registered_domain_name(&source_pipeline.url) else {
2053            return warn!("Invalid host url");
2054        };
2055
2056        let mut browsing_context_group = browsing_context_group.borrow_mut();
2057        let webgpu_chan = match browsing_context_group.webgpus.entry(host) {
2058            Entry::Vacant(v) => start_webgpu_thread(
2059                self.paint_proxy.cross_process_paint_api.clone(),
2060                self.webrender_wgpu
2061                    .webrender_external_image_id_manager
2062                    .clone(),
2063                self.webrender_wgpu.wgpu_image_map.clone(),
2064            )
2065            .map(|webgpu| {
2066                let msg = ScriptThreadMessage::SetWebGPUPort(webgpu.1);
2067                if let Err(e) = source_pipeline.event_loop.send(msg) {
2068                    warn!(
2069                        "{}: Failed to send SetWebGPUPort to pipeline ({:?})",
2070                        source_pipeline_id, e
2071                    );
2072                }
2073                v.insert(webgpu.0).clone()
2074            }),
2075            Entry::Occupied(o) => Some(o.get().clone()),
2076        };
2077        match request {
2078            ScriptToConstellationMessage::RequestAdapter(response_sender, options, adapter_id) => {
2079                match webgpu_chan {
2080                    None => {
2081                        if let Err(e) = response_sender.send(None) {
2082                            warn!("Failed to send request adapter message: {}", e)
2083                        }
2084                    },
2085                    Some(webgpu_chan) => {
2086                        let adapter_request = WebGPURequest::RequestAdapter {
2087                            sender: response_sender,
2088                            options,
2089                            adapter_id,
2090                        };
2091                        if webgpu_chan.0.send(adapter_request).is_err() {
2092                            warn!("Failed to send request adapter message on WebGPU channel");
2093                        }
2094                    },
2095                }
2096            },
2097            ScriptToConstellationMessage::GetWebGPUChan(response_sender) => {
2098                if response_sender.send(webgpu_chan).is_err() {
2099                    warn!(
2100                        "{}: Failed to send WebGPU channel to pipeline",
2101                        source_pipeline_id
2102                    )
2103                }
2104            },
2105            _ => warn!("Wrong message type in handle_wgpu_request"),
2106        }
2107    }
2108
2109    #[servo_tracing::instrument(skip_all)]
2110    fn handle_message_port_transfer_completed(
2111        &mut self,
2112        router_id: Option<MessagePortRouterId>,
2113        ports: Vec<MessagePortId>,
2114    ) {
2115        let Some(router_id) = router_id else {
2116            if !ports.is_empty() {
2117                warn!(
2118                    "Constellation unable to process port transfer successes, since no router id was received"
2119                );
2120            }
2121            return;
2122        };
2123        for port_id in ports.into_iter() {
2124            let mut entry = match self.message_ports.entry(port_id) {
2125                Entry::Vacant(_) => {
2126                    warn!(
2127                        "Constellation received a port transfer completed msg for unknown messageport {port_id:?}",
2128                    );
2129                    continue;
2130                },
2131                Entry::Occupied(entry) => entry,
2132            };
2133            match entry.get().state {
2134                TransferState::CompletionInProgress(expected_router_id) => {
2135                    // Here, the transfer was normally completed.
2136
2137                    if expected_router_id != router_id {
2138                        return warn!(
2139                            "Transfer completed by an unexpected router: {:?}",
2140                            router_id
2141                        );
2142                    }
2143                    // Update the state to managed.
2144                    let new_info = MessagePortInfo {
2145                        state: TransferState::Managed(router_id),
2146                        entangled_with: entry.get().entangled_with,
2147                    };
2148                    entry.insert(new_info);
2149                },
2150                _ => warn!("Constellation received unexpected port transfer completed message"),
2151            }
2152        }
2153    }
2154
2155    fn handle_message_port_transfer_failed(
2156        &mut self,
2157        ports: FxHashMap<MessagePortId, PortTransferInfo>,
2158    ) {
2159        for (port_id, mut transfer_info) in ports.into_iter() {
2160            let Some(entry) = self.message_ports.remove(&port_id) else {
2161                warn!(
2162                    "Constellation received a port transfer completed msg for unknown messageport {port_id:?}",
2163                );
2164                continue;
2165            };
2166            let new_info = match entry.state {
2167                TransferState::CompletionFailed(mut current_buffer) => {
2168                    // The transfer failed,
2169                    // and now the global has returned us the buffer we previously sent.
2170                    // So the next update is back to a "normal" transfer in progress.
2171
2172                    // Tasks in the previous buffer are older,
2173                    // hence need to be added to the front of the current one.
2174                    while let Some(task) = transfer_info.port_message_queue.pop_back() {
2175                        current_buffer.push_front(task);
2176                    }
2177                    // Update the state to transfer-in-progress.
2178                    MessagePortInfo {
2179                        state: TransferState::TransferInProgress(current_buffer),
2180                        entangled_with: entry.entangled_with,
2181                    }
2182                },
2183                TransferState::CompletionRequested(target_router_id, mut current_buffer) => {
2184                    // Here, before the global who failed the last transfer could return us the buffer,
2185                    // another global already sent us a request to complete a new transfer.
2186                    // So we use the returned buffer to update
2187                    // the current-buffer(of new incoming messages),
2188                    // and we send everything to the global
2189                    // who is waiting for completion of the current transfer.
2190
2191                    // Tasks in the previous buffer are older,
2192                    // hence need to be added to the front of the current one.
2193                    while let Some(task) = transfer_info.port_message_queue.pop_back() {
2194                        current_buffer.push_front(task);
2195                    }
2196                    // Forward the buffered message-queue to complete the current transfer.
2197                    if let Some(ipc_sender) = self.message_port_routers.get(&target_router_id) {
2198                        if ipc_sender
2199                            .send(MessagePortMsg::CompletePendingTransfer(
2200                                port_id,
2201                                PortTransferInfo {
2202                                    port_message_queue: current_buffer,
2203                                    disentangled: entry.entangled_with.is_none(),
2204                                },
2205                            ))
2206                            .is_err()
2207                        {
2208                            warn!("Constellation failed to send complete port transfer response.");
2209                        }
2210                    } else {
2211                        warn!("No message-port sender for {:?}", target_router_id);
2212                    }
2213
2214                    // Update the state to completion-in-progress.
2215                    MessagePortInfo {
2216                        state: TransferState::CompletionInProgress(target_router_id),
2217                        entangled_with: entry.entangled_with,
2218                    }
2219                },
2220                _ => {
2221                    warn!("Unexpected port transfer failed message received");
2222                    continue;
2223                },
2224            };
2225            self.message_ports.insert(port_id, new_info);
2226        }
2227    }
2228
2229    #[servo_tracing::instrument(skip_all)]
2230    fn handle_complete_message_port_transfer(
2231        &mut self,
2232        router_id: MessagePortRouterId,
2233        ports: Vec<MessagePortId>,
2234    ) {
2235        let mut response = FxHashMap::default();
2236        for port_id in ports.into_iter() {
2237            let Some(entry) = self.message_ports.remove(&port_id) else {
2238                warn!(
2239                    "Constellation asked to complete transfer for unknown messageport {port_id:?}",
2240                );
2241                continue;
2242            };
2243            let new_info = match entry.state {
2244                TransferState::TransferInProgress(buffer) => {
2245                    response.insert(
2246                        port_id,
2247                        PortTransferInfo {
2248                            port_message_queue: buffer,
2249                            disentangled: entry.entangled_with.is_none(),
2250                        },
2251                    );
2252
2253                    // If the port was in transfer, and a global is requesting completion,
2254                    // we note the start of the completion.
2255                    MessagePortInfo {
2256                        state: TransferState::CompletionInProgress(router_id),
2257                        entangled_with: entry.entangled_with,
2258                    }
2259                },
2260                TransferState::CompletionFailed(buffer) |
2261                TransferState::CompletionRequested(_, buffer) => {
2262                    // If the completion had already failed,
2263                    // this is a request coming from a global to complete a new transfer,
2264                    // but we're still awaiting the return of the buffer
2265                    // from the first global who failed.
2266                    //
2267                    // So we note the request from the new global,
2268                    // and continue to buffer incoming messages
2269                    // and wait for the buffer used in the previous transfer to be returned.
2270                    //
2271                    // If another global requests completion in the CompletionRequested state,
2272                    // we simply swap the target router-id for the new one,
2273                    // keeping the buffer.
2274                    MessagePortInfo {
2275                        state: TransferState::CompletionRequested(router_id, buffer),
2276                        entangled_with: entry.entangled_with,
2277                    }
2278                },
2279                _ => {
2280                    warn!("Unexpected complete port transfer message received");
2281                    continue;
2282                },
2283            };
2284            self.message_ports.insert(port_id, new_info);
2285        }
2286
2287        if !response.is_empty() {
2288            // Forward the buffered message-queue.
2289            if let Some(ipc_sender) = self.message_port_routers.get(&router_id) {
2290                if ipc_sender
2291                    .send(MessagePortMsg::CompleteTransfer(response))
2292                    .is_err()
2293                {
2294                    warn!("Constellation failed to send complete port transfer response.");
2295                }
2296            } else {
2297                warn!("No message-port sender for {:?}", router_id);
2298            }
2299        }
2300    }
2301
2302    #[servo_tracing::instrument(skip_all)]
2303    fn handle_reroute_messageport(&mut self, port_id: MessagePortId, task: PortMessageTask) {
2304        let Some(info) = self.message_ports.get_mut(&port_id) else {
2305            return warn!(
2306                "Constellation asked to re-route msg to unknown messageport {:?}",
2307                port_id
2308            );
2309        };
2310        match &mut info.state {
2311            TransferState::Managed(router_id) | TransferState::CompletionInProgress(router_id) => {
2312                // In both the managed and completion of a transfer case, we forward the message.
2313                // Note that in both cases, if the port is transferred before the message is handled,
2314                // it will be sent back here and buffered while the transfer is ongoing.
2315                if let Some(ipc_sender) = self.message_port_routers.get(router_id) {
2316                    let _ = ipc_sender.send(MessagePortMsg::NewTask(port_id, task));
2317                } else {
2318                    warn!("No message-port sender for {:?}", router_id);
2319                }
2320            },
2321            TransferState::TransferInProgress(queue) => queue.push_back(task),
2322            TransferState::CompletionFailed(queue) => queue.push_back(task),
2323            TransferState::CompletionRequested(_, queue) => queue.push_back(task),
2324        }
2325    }
2326
2327    #[servo_tracing::instrument(skip_all)]
2328    fn handle_messageport_shipped(&mut self, port_id: MessagePortId) {
2329        if let Some(info) = self.message_ports.get_mut(&port_id) {
2330            match info.state {
2331                TransferState::Managed(_) => {
2332                    // If shipped while managed, note the start of a transfer.
2333                    info.state = TransferState::TransferInProgress(VecDeque::new());
2334                },
2335                TransferState::CompletionInProgress(_) => {
2336                    // If shipped while completion of a transfer was in progress,
2337                    // the completion failed.
2338                    // This will be followed by a MessagePortTransferFailed message,
2339                    // containing the buffer we previously sent.
2340                    info.state = TransferState::CompletionFailed(VecDeque::new());
2341                },
2342                _ => warn!("Unexpected messageport shipped received"),
2343            }
2344        } else {
2345            warn!(
2346                "Constellation asked to mark unknown messageport as shipped {:?}",
2347                port_id
2348            );
2349        }
2350    }
2351
2352    fn handle_new_messageport_router(
2353        &mut self,
2354        router_id: MessagePortRouterId,
2355        message_port_callbacks: GenericCallback<MessagePortMsg>,
2356    ) {
2357        self.message_port_routers
2358            .insert(router_id, message_port_callbacks);
2359    }
2360
2361    fn handle_remove_messageport_router(&mut self, router_id: MessagePortRouterId) {
2362        self.message_port_routers.remove(&router_id);
2363    }
2364
2365    fn handle_new_messageport(&mut self, router_id: MessagePortRouterId, port_id: MessagePortId) {
2366        match self.message_ports.entry(port_id) {
2367            // If it's a new port, we should not know about it.
2368            Entry::Occupied(_) => warn!(
2369                "Constellation asked to start tracking an existing messageport {:?}",
2370                port_id
2371            ),
2372            Entry::Vacant(entry) => {
2373                let info = MessagePortInfo {
2374                    state: TransferState::Managed(router_id),
2375                    entangled_with: None,
2376                };
2377                entry.insert(info);
2378            },
2379        }
2380    }
2381
2382    #[servo_tracing::instrument(skip_all)]
2383    fn handle_entangle_messageports(&mut self, port1: MessagePortId, port2: MessagePortId) {
2384        if let Some(info) = self.message_ports.get_mut(&port1) {
2385            info.entangled_with = Some(port2);
2386        } else {
2387            warn!(
2388                "Constellation asked to entangle unknown messageport: {:?}",
2389                port1
2390            );
2391        }
2392        if let Some(info) = self.message_ports.get_mut(&port2) {
2393            info.entangled_with = Some(port1);
2394        } else {
2395            warn!(
2396                "Constellation asked to entangle unknown messageport: {:?}",
2397                port2
2398            );
2399        }
2400    }
2401
2402    #[servo_tracing::instrument(skip_all)]
2403    /// <https://html.spec.whatwg.org/multipage/#disentangle>
2404    fn handle_disentangle_messageports(
2405        &mut self,
2406        port1: MessagePortId,
2407        port2: Option<MessagePortId>,
2408    ) {
2409        // Disentangle initiatorPort and otherPort,
2410        // so that they are no longer entangled or associated with each other.
2411        // Note: If `port2` is some, then this is the first message
2412        // and `port1` is the initiatorPort, `port2` is the otherPort.
2413        // We can immediately remove the initiator.
2414        let _ = self.message_ports.remove(&port1);
2415
2416        // Note: the none case is when otherPort sent this message
2417        // in response to completing its own local disentanglement.
2418        let Some(port2) = port2 else {
2419            return;
2420        };
2421
2422        // Start disentanglement of the other port.
2423        if let Some(info) = self.message_ports.get_mut(&port2) {
2424            info.entangled_with = None;
2425            match &mut info.state {
2426                TransferState::Managed(router_id) |
2427                TransferState::CompletionInProgress(router_id) => {
2428                    // We try to disentangle the other port now,
2429                    // and if it has been transfered out by the time the message is received,
2430                    // it will be ignored,
2431                    // and disentanglement will be completed as part of the transfer.
2432                    if let Some(ipc_sender) = self.message_port_routers.get(router_id) {
2433                        let _ = ipc_sender.send(MessagePortMsg::CompleteDisentanglement(port2));
2434                    } else {
2435                        warn!("No message-port sender for {:?}", router_id);
2436                    }
2437                },
2438                _ => {
2439                    // Note: the port is in transfer, disentanglement will complete along with it.
2440                },
2441            }
2442        } else {
2443            warn!(
2444                "Constellation asked to disentangle unknown messageport: {:?}",
2445                port2
2446            );
2447        }
2448    }
2449
2450    /// <https://www.w3.org/TR/service-workers/#algorithms>
2451    /// Algorithms invoked from in-parallel steps run on the service worker mananager,
2452    /// per origin and routed by the constellation.
2453    #[servo_tracing::instrument(skip_all)]
2454    fn handle_serviceworker_algorithm(
2455        &mut self,
2456        pipeline_id: PipelineId,
2457        algorithm: ServiceWorkerAlgorithm,
2458    ) {
2459        let origin = match &algorithm {
2460            ServiceWorkerAlgorithm::StartRegister(job) => job.storage_key.clone(),
2461            ServiceWorkerAlgorithm::Unregister(job) => job.storage_key.clone(),
2462            ServiceWorkerAlgorithm::MatchServiceWorkerRegistration { storage_key, .. } => {
2463                storage_key.clone()
2464            },
2465        };
2466
2467        if self
2468            .check_origin_against_pipeline(&pipeline_id, &origin)
2469            .is_err()
2470        {
2471            return warn!(
2472                "Attempt to schedule a serviceworker job from an origin not matching the origin of the job."
2473            );
2474        }
2475
2476        // This match is equivalent to Entry.or_insert_with but allows for early return.
2477        let sw_manager = match self.sw_managers.entry(origin.clone()) {
2478            Entry::Occupied(entry) => entry.into_mut(),
2479            Entry::Vacant(entry) => {
2480                let (own_sender, receiver) =
2481                    generic_channel::channel().expect("Failed to create IPC channel!");
2482
2483                let sw_senders = SWManagerSenders {
2484                    resource_threads: self.public_resource_threads.clone(),
2485                    own_sender: own_sender.clone(),
2486                    receiver,
2487                    paint_api: self.paint_proxy.cross_process_paint_api.clone(),
2488                    system_font_service_sender: self.system_font_service.to_sender(),
2489                };
2490
2491                #[cfg(feature = "multiprocess")]
2492                if opts::get().multiprocess {
2493                    let (sender, receiver) = generic_channel::channel()
2494                        .expect("Failed to create lifeline channel for sw");
2495                    let content =
2496                        ServiceWorkerUnprivilegedContent::new(sw_senders, origin, Some(sender));
2497
2498                    if let Ok(process) = content.spawn_multiprocess() {
2499                        let crossbeam_receiver = receiver.route_preserving_errors();
2500                        self.process_manager.add(crossbeam_receiver, process);
2501                    } else {
2502                        return warn!("Failed to spawn process for SW manager.");
2503                    }
2504                } else {
2505                    let content = ServiceWorkerUnprivilegedContent::new(sw_senders, origin, None);
2506                    content.start::<SWF>();
2507                }
2508                #[cfg(not(feature = "multiprocess"))]
2509                {
2510                    let content = ServiceWorkerUnprivilegedContent::new(sw_senders, origin, None);
2511                    content.start::<SWF>();
2512                }
2513
2514                entry.insert(own_sender)
2515            },
2516        };
2517        if let Err(err) = sw_manager.send(ServiceWorkerMsg::HandleAlgorithm(algorithm)) {
2518            warn!("Failed to send algorithm to SW manager: {:?}", err);
2519        }
2520    }
2521
2522    #[servo_tracing::instrument(skip_all)]
2523    fn handle_broadcast_storage_event(
2524        &self,
2525        pipeline_id: PipelineId,
2526        storage: WebStorageType,
2527        url: ServoUrl,
2528        key: Option<String>,
2529        old_value: Option<String>,
2530        new_value: Option<String>,
2531    ) {
2532        let origin = url.origin();
2533        let Some(source_pipeline) = self.pipelines.get(&pipeline_id) else {
2534            warn!("Received storage event broadcast request from closed pipeline.");
2535            return;
2536        };
2537
2538        if source_pipeline.url.origin() != origin {
2539            return warn!(
2540                "Attempt to broadcast storage event from an origin not matching the source pipeline origin."
2541            );
2542        }
2543
2544        let interested = match self
2545            .pipeline_interests
2546            .get(&ConstellationInterest::StorageEvent)
2547        {
2548            Some(set) => set,
2549            None => return,
2550        }
2551        .iter()
2552        .filter_map(|interested_id| self.pipelines.get(interested_id));
2553
2554        for pipeline in interested {
2555            if pipeline.id == pipeline_id || pipeline.url.origin() != origin {
2556                continue;
2557            }
2558
2559            // https://html.spec.whatwg.org/multipage/#concept-storage-broadcast
2560            // "Step 3. Let remoteStorages be all Storage objects excluding storage whose:
2561            // type is storage's type
2562            // relevant settings object's origin is same origin with storage's relevant settings object's origin
2563            // and, if type is "session", whose relevant settings object's associated Document's
2564            // node navigable's traversable navigable is thisDocument's node navigable's
2565            // traversable navigable."
2566            if storage == WebStorageType::Session &&
2567                pipeline.webview_id != source_pipeline.webview_id
2568            {
2569                continue;
2570            }
2571
2572            let msg = ScriptThreadMessage::DispatchStorageEvent(
2573                pipeline.id,
2574                storage,
2575                url.clone(),
2576                key.clone(),
2577                old_value.clone(),
2578                new_value.clone(),
2579            );
2580            if let Err(err) = pipeline.event_loop.send(msg) {
2581                warn!(
2582                    "{}: Failed to broadcast storage event to pipeline ({:?}).",
2583                    pipeline.id, err
2584                );
2585            }
2586        }
2587    }
2588
2589    #[servo_tracing::instrument(skip_all)]
2590    fn handle_exit(&mut self) {
2591        debug!("Handling exit.");
2592
2593        // TODO: add a timer, which forces shutdown if threads aren't responsive.
2594        if self.shutting_down {
2595            return;
2596        }
2597        self.shutting_down = true;
2598
2599        self.mem_profiler_chan.send(mem::ProfilerMsg::Exit);
2600
2601        // Tell all BHMs to exit, and to ensure their monitored components exit even when currently
2602        // hanging (on JS or sync XHR). This must be done before starting the process of closing all
2603        // pipelines.
2604        self.send_message_to_all_background_hang_monitors(BackgroundHangMonitorControlMsg::Exit);
2605
2606        // Close the top-level browsing contexts
2607        let browsing_context_ids: Vec<_> = self
2608            .browsing_contexts
2609            .values()
2610            .filter(|browsing_context| browsing_context.is_top_level())
2611            .map(|browsing_context| (browsing_context.webview_id, browsing_context.id))
2612            .collect();
2613        for (webview_id, browsing_context_id) in browsing_context_ids {
2614            debug!(
2615                "{}: Removing top-level browsing context",
2616                browsing_context_id
2617            );
2618            self.close_browsing_context(webview_id, browsing_context_id, ExitPipelineMode::Normal);
2619        }
2620
2621        // Close any pending changes and pipelines
2622        let pending_changes: Vec<_> = self
2623            .webviews
2624            .values_mut()
2625            .flat_map(|webview| webview.pending_changes.drain(..))
2626            .collect();
2627        for pending in pending_changes {
2628            debug!(
2629                "{}: Removing pending browsing context",
2630                pending.browsing_context_id
2631            );
2632            self.close_browsing_context(
2633                pending.webview_id,
2634                pending.browsing_context_id,
2635                ExitPipelineMode::Normal,
2636            );
2637            debug!("{}: Removing pending pipeline", pending.new_pipeline_id);
2638            self.close_pipeline(
2639                pending.new_pipeline_id,
2640                DiscardBrowsingContext::Yes,
2641                ExitPipelineMode::Normal,
2642            );
2643        }
2644
2645        // In case there are browsing contexts which weren't attached, we close them.
2646        let browsing_context_ids: Vec<_> = self
2647            .browsing_contexts
2648            .iter()
2649            .map(|(browsing_context_id, browsing_context)| {
2650                (*browsing_context_id, browsing_context.webview_id)
2651            })
2652            .collect();
2653
2654        for (browsing_context_id, webview_id) in browsing_context_ids {
2655            debug!(
2656                "{}: Removing detached browsing context",
2657                browsing_context_id
2658            );
2659            self.close_browsing_context(webview_id, browsing_context_id, ExitPipelineMode::Normal);
2660        }
2661
2662        // In case there are pipelines which weren't attached to the pipeline tree, we close them.
2663        let pipeline_ids: Vec<PipelineId> = self.pipelines.keys().cloned().collect();
2664        for pipeline_id in pipeline_ids {
2665            debug!("{}: Removing detached pipeline", pipeline_id);
2666            self.close_pipeline(
2667                pipeline_id,
2668                DiscardBrowsingContext::Yes,
2669                ExitPipelineMode::Normal,
2670            );
2671        }
2672    }
2673
2674    #[servo_tracing::instrument(skip_all)]
2675    fn handle_shutdown(&mut self) {
2676        debug!("Handling shutdown.");
2677
2678        for join_handle in self.event_loop_join_handles.drain(..) {
2679            if join_handle.join().is_err() {
2680                error!("Failed to join on a script-thread.");
2681            }
2682        }
2683
2684        // In single process mode, join on the background hang monitor worker thread.
2685        drop(self.background_monitor_register.take());
2686        if let Some(join_handle) = self.background_monitor_register_join_handle.take() &&
2687            join_handle.join().is_err()
2688        {
2689            error!("Failed to join on the bhm background thread.");
2690        }
2691
2692        // At this point, there are no active pipelines,
2693        // so we can safely block on other threads, without worrying about deadlock.
2694        // Channels to receive signals when threads are done exiting.
2695        let (core_ipc_sender, core_ipc_receiver) =
2696            generic_channel::oneshot().expect("Failed to create IPC channel!");
2697        let (public_client_storage_generic_sender, public_client_storage_generic_receiver) =
2698            generic_channel::channel().expect("Failed to create generic channel!");
2699        let (private_client_storage_generic_sender, private_client_storage_generic_receiver) =
2700            generic_channel::channel().expect("Failed to create generic channel!");
2701        let (private_cache_storage_generic_sender, private_cache_storage_generic_receiver) =
2702            generic_channel::channel().expect("Failed to create generic channel!");
2703        let (public_cache_storage_generic_sender, public_cache_storage_generic_receiver) =
2704            generic_channel::channel().expect("Failed to create generic channel!");
2705        let (public_indexeddb_ipc_sender, public_indexeddb_ipc_receiver) =
2706            generic_channel::channel().expect("Failed to create generic channel!");
2707        let (private_indexeddb_ipc_sender, private_indexeddb_ipc_receiver) =
2708            generic_channel::channel().expect("Failed to create generic channel!");
2709        let (public_web_storage_generic_sender, public_web_storage_generic_receiver) =
2710            generic_channel::channel().expect("Failed to create generic channel!");
2711        let (private_web_storage_generic_sender, private_web_storage_generic_receiver) =
2712            generic_channel::channel().expect("Failed to create generic channel!");
2713
2714        debug!("Exiting core resource threads.");
2715        if let Err(e) = self
2716            .public_resource_threads
2717            .send(net_traits::CoreResourceMsg::Exit(core_ipc_sender))
2718        {
2719            warn!("Exit resource thread failed ({})", e);
2720        }
2721
2722        #[cfg(feature = "devtools")]
2723        if let Some(ref chan) = self.devtools_sender {
2724            debug!("Exiting devtools.");
2725            let msg = DevtoolsControlMsg::FromChrome(ChromeToDevtoolsControlMsg::ServerExitMsg);
2726            if let Err(e) = chan.send(msg) {
2727                warn!("Exit devtools failed ({:?})", e);
2728            }
2729        }
2730
2731        debug!("Exiting public client storage thread.");
2732        if let Err(e) = generic_channel::GenericSend::send(
2733            &self.public_storage_threads,
2734            ClientStorageThreadMessage::Exit(public_client_storage_generic_sender),
2735        ) {
2736            warn!("Exit public client storage thread failed ({})", e);
2737        }
2738        debug!("Exiting private client storage thread.");
2739        if let Err(e) = generic_channel::GenericSend::send(
2740            &self.private_storage_threads,
2741            ClientStorageThreadMessage::Exit(private_client_storage_generic_sender),
2742        ) {
2743            warn!("Exit private client storage thread failed ({})", e);
2744        }
2745
2746        debug!("Exiting public cache storage thread.");
2747        if let Err(e) = generic_channel::GenericSend::send(
2748            &self.public_storage_threads,
2749            CacheStorageThreadMessage::Exit(public_cache_storage_generic_sender),
2750        ) {
2751            warn!("Exit public cache storage thread failed ({})", e);
2752        }
2753        debug!("Exiting private cache storage thread.");
2754        if let Err(e) = generic_channel::GenericSend::send(
2755            &self.private_storage_threads,
2756            CacheStorageThreadMessage::Exit(private_cache_storage_generic_sender),
2757        ) {
2758            warn!("Exit private cache storage thread failed ({})", e);
2759        }
2760
2761        debug!("Exiting public indexeddb resource threads.");
2762        if let Err(e) =
2763            self.public_storage_threads
2764                .send(IndexedDBThreadMsg::Sync(SyncOperation::Exit(
2765                    public_indexeddb_ipc_sender,
2766                )))
2767        {
2768            warn!("Exit public indexeddb thread failed ({})", e);
2769        }
2770
2771        debug!("Exiting private indexeddb resource threads.");
2772        if let Err(e) =
2773            self.private_storage_threads
2774                .send(IndexedDBThreadMsg::Sync(SyncOperation::Exit(
2775                    private_indexeddb_ipc_sender,
2776                )))
2777        {
2778            warn!("Exit private indexeddb thread failed ({})", e);
2779        }
2780
2781        debug!("Exiting public web storage thread.");
2782        if let Err(e) = generic_channel::GenericSend::send(
2783            &self.public_storage_threads,
2784            WebStorageThreadMsg::Exit(public_web_storage_generic_sender),
2785        ) {
2786            warn!("Exit public web storage thread failed ({})", e);
2787        }
2788
2789        debug!("Exiting private web storage thread.");
2790        if let Err(e) = generic_channel::GenericSend::send(
2791            &self.private_storage_threads,
2792            WebStorageThreadMsg::Exit(private_web_storage_generic_sender),
2793        ) {
2794            warn!("Exit private web storage thread failed ({})", e);
2795        }
2796
2797        #[cfg(feature = "bluetooth")]
2798        {
2799            debug!("Exiting bluetooth thread.");
2800            if let Err(e) = self.bluetooth_ipc_sender.send(BluetoothRequest::Exit) {
2801                warn!("Exit bluetooth thread failed ({})", e);
2802            }
2803        }
2804
2805        debug!("Exiting service worker manager thread.");
2806        for (_, mgr) in self.sw_managers.drain() {
2807            if let Err(e) = mgr.send(ServiceWorkerMsg::Exit) {
2808                warn!("Exit service worker manager failed ({})", e);
2809            }
2810        }
2811
2812        let canvas_exit_receiver = if let Some((canvas_sender, _)) = self.canvas.get() {
2813            debug!("Exiting Canvas Paint thread.");
2814            let (canvas_exit_sender, canvas_exit_receiver) = unbounded();
2815            if let Err(e) = canvas_sender.send(ConstellationCanvasMsg::Exit(canvas_exit_sender)) {
2816                warn!("Exit Canvas Paint thread failed ({})", e);
2817            }
2818            Some(canvas_exit_receiver)
2819        } else {
2820            None
2821        };
2822
2823        debug!("Exiting WebGPU threads.");
2824        #[cfg(feature = "webgpu")]
2825        {
2826            let receivers: Vec<_> = self
2827                .webviews
2828                .values()
2829                .flat_map(|webview| {
2830                    // This `take` is necessary as it ensures that WebViews that
2831                    // share a BrowsingContextGroup don't have their WebGPU
2832                    // entries processed more than once.
2833                    std::mem::take(&mut webview.browsing_context_group().borrow_mut().webgpus)
2834                })
2835                .filter_map(|(_, webgpu)| {
2836                    let (sender, receiver) =
2837                        generic_channel::oneshot().expect("Failed to create IPC channel!");
2838                    webgpu.exit(sender).is_ok().then_some(receiver)
2839                })
2840                .collect();
2841
2842            for receiver in receivers {
2843                if let Err(error) = receiver.recv() {
2844                    warn!("Failed to receive exit response from WebGPU: {error:?}.");
2845                }
2846            }
2847        }
2848
2849        debug!("Exiting GLPlayer thread.");
2850        WindowGLContext::get().exit();
2851
2852        // Wait for the canvas thread to exit before shutting down the font service, as
2853        // canvas might still be using the system font service before shutting down.
2854        if let Some(canvas_exit_receiver) = canvas_exit_receiver {
2855            let _ = canvas_exit_receiver.recv();
2856        }
2857
2858        debug!("Exiting the system font service thread.");
2859        self.system_font_service.exit();
2860
2861        // Receive exit signals from threads.
2862        if let Err(e) = core_ipc_receiver.recv() {
2863            warn!("Exit resource thread failed ({:?})", e);
2864        }
2865        if let Err(e) = public_client_storage_generic_receiver.recv() {
2866            warn!("Exit public client storage thread failed ({:?})", e);
2867        }
2868        if let Err(e) = private_client_storage_generic_receiver.recv() {
2869            warn!("Exit private client storage thread failed ({:?})", e);
2870        }
2871        if let Err(e) = private_cache_storage_generic_receiver.recv() {
2872            warn!("Exit private cache storage thread failed ({:?})", e);
2873        }
2874        if let Err(e) = public_cache_storage_generic_receiver.recv() {
2875            warn!("Exit public cache storage thread failed ({:?})", e);
2876        }
2877        if let Err(e) = public_indexeddb_ipc_receiver.recv() {
2878            warn!("Exit public indexeddb thread failed ({:?})", e);
2879        }
2880        if let Err(e) = private_indexeddb_ipc_receiver.recv() {
2881            warn!("Exit private indexeddb thread failed ({:?})", e);
2882        }
2883        if let Err(e) = public_web_storage_generic_receiver.recv() {
2884            warn!("Exit public web storage thread failed ({:?})", e);
2885        }
2886        if let Err(e) = private_web_storage_generic_receiver.recv() {
2887            warn!("Exit private web storage thread failed ({:?})", e);
2888        }
2889
2890        debug!("Shutting-down IPC router thread in constellation.");
2891        ROUTER.shutdown();
2892
2893        debug!("Shutting-down the async runtime in constellation.");
2894        self.async_runtime.shutdown();
2895    }
2896
2897    fn handle_pipeline_exited(&mut self, pipeline_id: PipelineId, exit_source: PipelineExitSource) {
2898        debug!("{}: Exited", pipeline_id);
2899        self.remove_worker_animation_frame_providers_for_pipeline(pipeline_id);
2900
2901        let Some(pipeline) = self.pipelines.remove(&pipeline_id) else {
2902            return;
2903        };
2904
2905        // Clean up any registered interests for this pipeline.
2906        self.pipeline_interests.retain(|_, set| {
2907            set.remove(&pipeline_id);
2908            !set.is_empty()
2909        });
2910
2911        // Now that the Script and Constellation parts of Servo no longer have a reference to
2912        // this pipeline, tell `Paint` that it has shut down. This is delayed until the
2913        // last moment.
2914        self.paint_proxy.send(PaintMessage::PipelineExited(
2915            pipeline.webview_id,
2916            pipeline.id,
2917            exit_source,
2918        ));
2919    }
2920
2921    #[servo_tracing::instrument(skip_all)]
2922    fn handle_send_error(&mut self, pipeline_id: PipelineId, error: SendError) {
2923        error!("Error sending message to {pipeline_id:?}: {error}",);
2924
2925        // Ignore errors from unknown Pipelines.
2926        let Some(pipeline) = self.pipelines.get(&pipeline_id) else {
2927            return;
2928        };
2929
2930        // Treat send error the same as receiving a panic message
2931        self.handle_panic_in_webview(
2932            pipeline.webview_id,
2933            &format!("Send failed ({error})"),
2934            &None,
2935        );
2936    }
2937
2938    #[servo_tracing::instrument(skip_all)]
2939    fn handle_panic(
2940        &mut self,
2941        event_loop_id: Option<ScriptEventLoopId>,
2942        reason: String,
2943        backtrace: Option<String>,
2944    ) {
2945        if self.hard_fail {
2946            // It's quite difficult to make Servo exit cleanly if some threads have failed.
2947            // Hard fail exists for test runners so we crash and that's good enough.
2948            error!("Pipeline failed in hard-fail mode.  Crashing!");
2949            process::exit(1);
2950        }
2951
2952        let Some(event_loop_id) = event_loop_id else {
2953            return;
2954        };
2955        debug!("Panic handler for {event_loop_id:?}: {reason:?}",);
2956
2957        let mut webview_ids = HashSet::new();
2958        let mut crashed_pipelines = Vec::new();
2959        for pipeline in self.pipelines.values() {
2960            if pipeline.event_loop.id() == event_loop_id {
2961                crashed_pipelines.push(pipeline.id);
2962                webview_ids.insert(pipeline.webview_id);
2963            }
2964        }
2965
2966        for webview_id in webview_ids {
2967            self.handle_panic_in_webview(webview_id, &reason, &backtrace);
2968        }
2969
2970        for pipeline_id in crashed_pipelines {
2971            self.close_pipeline(
2972                pipeline_id,
2973                DiscardBrowsingContext::No,
2974                ExitPipelineMode::Force,
2975            );
2976            // The pipeline is now unreachable, so we should consider it gone. We should
2977            // not try to send any subsequent messages to this pipeline. all() here ensures
2978            // that `Paint` cleans up the pipeline display immediately without waiting for
2979            // confirmation from the event loop.
2980            self.handle_pipeline_exited(pipeline_id, PipelineExitSource::all());
2981        }
2982    }
2983
2984    fn handle_panic_in_webview(
2985        &mut self,
2986        webview_id: WebViewId,
2987        reason: &String,
2988        backtrace: &Option<String>,
2989    ) {
2990        self.constellation_to_embedder_proxy
2991            .send(ConstellationToEmbedderMsg::Panic(
2992                webview_id,
2993                reason.clone(),
2994                backtrace.clone(),
2995            ));
2996
2997        let browsing_context_id = BrowsingContextId::from(webview_id);
2998        let Some(browsing_context) = self.browsing_contexts.get(&browsing_context_id) else {
2999            return warn!("failed browsing context is missing");
3000        };
3001        let viewport_details = browsing_context.viewport_details;
3002        let pipeline_id = browsing_context.pipeline_id;
3003
3004        let Some(pipeline) = self.pipelines.get(&pipeline_id) else {
3005            return warn!("failed pipeline is missing");
3006        };
3007        let opener = pipeline.opener;
3008
3009        let old_pipeline_id = pipeline_id;
3010        let Some(old_load_data) = self.refresh_load_data(pipeline_id) else {
3011            return warn!("failed pipeline is missing");
3012        };
3013
3014        self.close_browsing_context_children(
3015            webview_id,
3016            browsing_context_id,
3017            DiscardBrowsingContext::No,
3018            ExitPipelineMode::Force,
3019        );
3020
3021        if old_load_data.crash.is_some() {
3022            return error!("crash page crashed");
3023        }
3024
3025        warn!("creating replacement pipeline for crash page");
3026
3027        let new_pipeline_id = PipelineId::new();
3028        let new_load_data = LoadData {
3029            crash: Some(
3030                backtrace
3031                    .clone()
3032                    .map(|backtrace| format!("{reason}\n{backtrace}"))
3033                    .unwrap_or_else(|| reason.clone()),
3034            ),
3035            creation_sandboxing_flag_set: SandboxingFlagSet::all(),
3036            ..old_load_data.clone()
3037        };
3038
3039        let is_private = false;
3040        self.new_pipeline(
3041            new_pipeline_id,
3042            browsing_context_id,
3043            webview_id,
3044            None,
3045            opener,
3046            viewport_details,
3047            new_load_data,
3048            is_private,
3049            TargetSnapshotParams::default(),
3050            None,
3051        );
3052
3053        let Some(webview) = self.webviews.get_mut(&webview_id) else {
3054            return warn!("Adding pending change to unknown WebView: {webview_id:?}");
3055        };
3056        webview.add_pending_change(SessionHistoryChange {
3057            webview_id,
3058            browsing_context_id,
3059            new_pipeline_id,
3060            // Pipeline already closed by close_browsing_context_children, so we can pass Yes here
3061            // to avoid closing again in handle_activate_document_msg (though it would be harmless)
3062            replace: Some(NeedsToReload::Yes(old_pipeline_id, old_load_data)),
3063            new_browsing_context_info: None,
3064            viewport_details,
3065        });
3066    }
3067
3068    #[servo_tracing::instrument(skip_all)]
3069    fn handle_set_has_system_focus(&mut self, webview_id: WebViewId, has_system_focus: bool) {
3070        let Some(webview) = self.webviews.get_mut(&webview_id) else {
3071            return warn!("Tried to focus a nonexistent WebView: {webview_id:?}");
3072        };
3073        if !webview.set_has_system_focus(has_system_focus) {
3074            return;
3075        }
3076
3077        let state = webview.state();
3078        for event_loop in self.event_loops() {
3079            if let Err(error) =
3080                event_loop.send(ScriptThreadMessage::UpdateWebViewState(state.clone()))
3081            {
3082                warn!(
3083                    "Sending to closed event loop ({:?}): {error}",
3084                    event_loop.id()
3085                );
3086            }
3087        }
3088    }
3089
3090    #[servo_tracing::instrument(skip_all)]
3091    fn handle_log_entry(
3092        &mut self,
3093        event_loop_id: Option<ScriptEventLoopId>,
3094        thread_name: Option<String>,
3095        entry: LogEntry,
3096    ) {
3097        if let LogEntry::Panic(ref reason, ref backtrace) = entry {
3098            self.handle_panic(event_loop_id, reason.clone(), Some(backtrace.clone()));
3099        }
3100
3101        match entry {
3102            LogEntry::Panic(reason, _) | LogEntry::Error(reason) | LogEntry::Warn(reason) => {
3103                // VecDeque::truncate is unstable
3104                if WARNINGS_BUFFER_SIZE <= self.handled_warnings.len() {
3105                    self.handled_warnings.pop_front();
3106                }
3107                self.handled_warnings.push_back((thread_name, reason));
3108            },
3109        }
3110    }
3111
3112    fn update_pressed_mouse_buttons(&mut self, event: &MouseButtonEvent) {
3113        let Ok(buttons) = MouseButtons::try_from(event.button) else {
3114            return;
3115        };
3116        match event.action {
3117            MouseButtonAction::Down => {
3118                self.pressed_mouse_buttons |= buttons;
3119            },
3120            MouseButtonAction::Up => {
3121                self.pressed_mouse_buttons &= !(buttons);
3122            },
3123        }
3124    }
3125
3126    #[expect(deprecated)]
3127    fn update_active_keybord_modifiers(&mut self, event: &KeyboardEvent) {
3128        self.active_keyboard_modifiers = event.event.modifiers;
3129
3130        // `KeyboardEvent::modifiers` contains the pre-existing modifiers before this key was
3131        // either pressed or released, but `active_keyboard_modifiers` should track the subsequent
3132        // state. If this event will update that state, we need to ensure that we are tracking what
3133        // the event changes.
3134        let Key::Named(named_key) = event.event.key else {
3135            return;
3136        };
3137
3138        let modified_modifier = match named_key {
3139            NamedKey::Alt => Modifiers::ALT,
3140            NamedKey::AltGraph => Modifiers::ALT_GRAPH,
3141            NamedKey::CapsLock => Modifiers::CAPS_LOCK,
3142            NamedKey::Control => Modifiers::CONTROL,
3143            NamedKey::Fn => Modifiers::FN,
3144            NamedKey::FnLock => Modifiers::FN_LOCK,
3145            NamedKey::Meta => Modifiers::META,
3146            NamedKey::NumLock => Modifiers::NUM_LOCK,
3147            NamedKey::ScrollLock => Modifiers::SCROLL_LOCK,
3148            NamedKey::Shift => Modifiers::SHIFT,
3149            NamedKey::Symbol => Modifiers::SYMBOL,
3150            NamedKey::SymbolLock => Modifiers::SYMBOL_LOCK,
3151            NamedKey::Hyper => Modifiers::HYPER,
3152            // The web doesn't make a distinction between these keys (there is only
3153            // "meta") so map "super" to "meta".
3154            NamedKey::Super => Modifiers::META,
3155            _ => return,
3156        };
3157        match event.event.state {
3158            KeyState::Down => self.active_keyboard_modifiers.insert(modified_modifier),
3159            KeyState::Up => self.active_keyboard_modifiers.remove(modified_modifier),
3160        }
3161    }
3162
3163    fn set_accessibility_active(&mut self, webview_id: WebViewId, active: bool) {
3164        if !(pref!(accessibility_enabled)) {
3165            return;
3166        }
3167
3168        let Some(webview) = self.webviews.get_mut(&webview_id) else {
3169            return;
3170        };
3171
3172        webview.accessibility_active = active;
3173        let Some(pipeline_id) = webview.active_top_level_pipeline_id else {
3174            return;
3175        };
3176        let epoch = webview.active_top_level_pipeline_epoch;
3177        // Forward the activation to the webview’s active top-level pipeline, if any. For inactive
3178        // pipelines (documents in bfcache), we only need to forward the activation if and when they
3179        // become active (see set_frame_tree_for_webview()).
3180        // There are two sites like this; this is the a11y activation site.
3181        self.send_message_to_pipeline(
3182            pipeline_id,
3183            ScriptThreadMessage::SetAccessibilityActive(pipeline_id, active, epoch),
3184            "Set accessibility active after closure",
3185        );
3186    }
3187
3188    fn forward_accessibility_action(&mut self, action_request: ActionRequest) {
3189        let Some(pipeline_id) = self
3190            .pipelines
3191            .keys()
3192            .copied()
3193            .find(|&pipeline_id| TreeId::from(pipeline_id) == action_request.target_tree)
3194        else {
3195            warn!(
3196                "Could not complete accessibility action {action_request:?}: no matching pipeline."
3197            );
3198            return;
3199        };
3200
3201        self.send_message_to_pipeline(
3202            pipeline_id,
3203            ScriptThreadMessage::ForwardAccessibilityAction(pipeline_id, action_request),
3204            "Action request failed: sending message to pipeline failed.",
3205        );
3206    }
3207
3208    fn handle_clear_session_history(&mut self, webview_id: WebViewId) {
3209        let Some(webview) = self.webviews.get_mut(&webview_id) else {
3210            return;
3211        };
3212        webview.session_history.future.clear();
3213        webview.session_history.past.clear();
3214        self.notify_history_changed(webview_id);
3215    }
3216
3217    fn forward_input_event(
3218        &mut self,
3219        webview_id: WebViewId,
3220        event: InputEventAndId,
3221        hit_test_result: Option<PaintHitTestResult>,
3222    ) {
3223        if let InputEvent::MouseButton(event) = &event.event {
3224            self.update_pressed_mouse_buttons(event);
3225        }
3226
3227        if let InputEvent::Keyboard(event) = &event.event {
3228            self.update_active_keybord_modifiers(event);
3229        }
3230
3231        // The constellation tracks the state of pressed mouse buttons and keyboard
3232        // modifiers and updates the event here to reflect the current state.
3233        let pressed_mouse_buttons = self.pressed_mouse_buttons;
3234        let active_keyboard_modifiers = self.active_keyboard_modifiers;
3235
3236        let event_id = event.id;
3237        let Some(webview) = self.webviews.get_mut(&webview_id) else {
3238            warn!("Got input event for unknown WebViewId: {webview_id:?}");
3239            self.constellation_to_embedder_proxy.send(
3240                ConstellationToEmbedderMsg::InputEventsHandled(
3241                    webview_id,
3242                    vec![InputEventOutcome {
3243                        id: event_id,
3244                        result: Default::default(),
3245                    }],
3246                ),
3247            );
3248            return;
3249        };
3250
3251        let event = ConstellationInputEvent {
3252            hit_test_result,
3253            pressed_mouse_buttons,
3254            active_keyboard_modifiers,
3255            event,
3256        };
3257
3258        if !webview.forward_input_event(event, &self.pipelines, &self.browsing_contexts) {
3259            self.constellation_to_embedder_proxy.send(
3260                ConstellationToEmbedderMsg::InputEventsHandled(
3261                    webview_id,
3262                    vec![InputEventOutcome {
3263                        id: event_id,
3264                        result: Default::default(),
3265                    }],
3266                ),
3267            );
3268        }
3269    }
3270
3271    #[servo_tracing::instrument(skip_all)]
3272    fn handle_new_top_level_browsing_context(
3273        &mut self,
3274        url: ServoUrl,
3275        NewWebViewDetails {
3276            webview_id,
3277            viewport_details,
3278            user_content_manager_id,
3279        }: NewWebViewDetails,
3280    ) {
3281        let pipeline_id = PipelineId::new();
3282        let browsing_context_id = BrowsingContextId::from(webview_id);
3283        let load_data = LoadData::new_for_new_unrelated_webview(url);
3284        let is_private = false;
3285
3286        // Register this new top-level browsing context id as a webview and set
3287        // its focused browsing context to be itself.
3288        let mut new_webview = ConstellationWebView::new(
3289            webview_id,
3290            // https://html.spec.whatwg.org/multipage/#creating-a-new-browsing-context-group
3291            Rc::new(RefCell::new(BrowsingContextGroup::default())),
3292            browsing_context_id,
3293            user_content_manager_id,
3294        );
3295        new_webview.add_pending_change(SessionHistoryChange {
3296            webview_id,
3297            browsing_context_id,
3298            new_pipeline_id: pipeline_id,
3299            replace: None,
3300            new_browsing_context_info: Some(NewBrowsingContextInfo {
3301                parent_pipeline_id: None,
3302                is_private,
3303                inherited_secure_context: None,
3304            }),
3305            viewport_details,
3306        });
3307        self.webviews.insert(webview_id, new_webview);
3308
3309        self.new_pipeline(
3310            pipeline_id,
3311            browsing_context_id,
3312            webview_id,
3313            None,
3314            None,
3315            viewport_details,
3316            load_data,
3317            is_private,
3318            TargetSnapshotParams::default(),
3319            None,
3320        );
3321
3322        self.system_font_service
3323            .prefetch_font_keys_for_painter(PainterId::from(webview_id));
3324    }
3325
3326    #[servo_tracing::instrument(skip_all)]
3327    /// <https://html.spec.whatwg.org/multipage/#destroy-a-top-level-traversable>
3328    fn handle_close_top_level_browsing_context(&mut self, webview_id: WebViewId) {
3329        debug!("{webview_id}: Closing");
3330
3331        // Step 1. Let browsingContext be traversable's active browsing context.
3332        let browsing_context_id = BrowsingContextId::from(webview_id);
3333        // Step 2. For each historyEntry in traversable's session history
3334        // Step 2.1 Let document be historyEntry's document.
3335        // Step 2.2 If document is not null, then destroy a document and its
3336        // descendants given document.
3337
3338        // Step 3. Remove browsingContext.
3339        self.close_browsing_context(webview_id, browsing_context_id, ExitPipelineMode::Normal);
3340
3341        // Step 4. Remove traversable from the user interface (e.g., close or
3342        // hide its tab in a tabbed browser).
3343        self.constellation_to_embedder_proxy
3344            .send(ConstellationToEmbedderMsg::WebViewClosed(webview_id));
3345
3346        // Step 5. Remove traversable from the user agent's top-level traversable set.
3347        if let Some(mut webview) = self.webviews.remove(&webview_id) {
3348            // Any queued history traversal requests are never going to finish at this point,
3349            // so notify the embedder that they are now finished.
3350            if let Some(ongoing_request) = webview.ongoing_history_traversal_request {
3351                self.notify_embedder_of_completed_session_history_traversal_request(
3352                    &ongoing_request.traversal_request,
3353                );
3354            }
3355            for request in webview.session_history_traversal_request_queue.drain(..) {
3356                self.notify_embedder_of_completed_session_history_traversal_request(&request);
3357            }
3358        }
3359
3360        debug!("{webview_id}: Closed");
3361    }
3362
3363    #[servo_tracing::instrument(skip_all)]
3364    fn handle_iframe_size_msg(&mut self, webview_id: WebViewId, iframe_sizes: Vec<IFrameSizeMsg>) {
3365        for IFrameSizeMsg {
3366            browsing_context_id,
3367            size,
3368            type_,
3369        } in iframe_sizes
3370        {
3371            self.resize_browsing_context(webview_id, size, type_, browsing_context_id);
3372        }
3373    }
3374
3375    #[servo_tracing::instrument(skip_all)]
3376    fn handle_finish_javascript_evaluation(
3377        &mut self,
3378        evaluation_id: JavaScriptEvaluationId,
3379        result: Result<JSValue, JavaScriptEvaluationError>,
3380    ) {
3381        self.constellation_to_embedder_proxy.send(
3382            ConstellationToEmbedderMsg::FinishJavaScriptEvaluation(evaluation_id, result),
3383        );
3384    }
3385
3386    #[servo_tracing::instrument(skip_all)]
3387    fn handle_subframe_loaded(&mut self, pipeline_id: PipelineId) {
3388        let browsing_context_id = match self.pipelines.get(&pipeline_id) {
3389            Some(pipeline) => pipeline.browsing_context_id,
3390            None => return warn!("{}: Subframe loaded after closure", pipeline_id),
3391        };
3392        let parent_pipeline_id = match self.browsing_contexts.get(&browsing_context_id) {
3393            Some(browsing_context) => browsing_context.parent_pipeline_id,
3394            None => {
3395                return warn!(
3396                    "{}: Subframe loaded in closed {}",
3397                    pipeline_id, browsing_context_id,
3398                );
3399            },
3400        };
3401        let Some(parent_pipeline_id) = parent_pipeline_id else {
3402            return warn!("{}: Subframe has no parent", pipeline_id);
3403        };
3404        // https://html.spec.whatwg.org/multipage/#the-iframe-element:completely-loaded
3405        // When a Document in an iframe is marked as completely loaded,
3406        // the user agent must run the iframe load event steps.
3407        let msg = ScriptThreadMessage::DispatchIFrameLoadEvent {
3408            target: browsing_context_id,
3409            parent: parent_pipeline_id,
3410            child: pipeline_id,
3411        };
3412        let result = match self.pipelines.get(&parent_pipeline_id) {
3413            Some(parent) => parent.event_loop.send(msg),
3414            None => {
3415                return warn!(
3416                    "{}: Parent pipeline browsing context loaded after closure",
3417                    parent_pipeline_id
3418                );
3419            },
3420        };
3421        if let Err(e) = result {
3422            self.handle_send_error(parent_pipeline_id, e);
3423        }
3424    }
3425
3426    /// The script thread associated with pipeline_id has loaded a URL in an
3427    /// iframe via script. This will result in a new pipeline being spawned and
3428    /// a child being added to the parent browsing context. This message is never
3429    /// the result of a page navigation.
3430    #[servo_tracing::instrument(skip_all)]
3431    fn handle_script_loaded_url_in_iframe_msg(&mut self, load_info: IFrameLoadInfoWithData) {
3432        let IFrameLoadInfo {
3433            parent_pipeline_id,
3434            browsing_context_id,
3435            webview_id,
3436            new_pipeline_id,
3437            is_private,
3438            mut history_handling,
3439            target_snapshot_params,
3440            name,
3441            ..
3442        } = load_info.info;
3443
3444        // If no url is specified, reload.
3445        let old_pipeline = load_info
3446            .old_pipeline_id
3447            .and_then(|id| self.pipelines.get(&id));
3448
3449        // Replacement enabled also takes into account whether the document is "completely loaded",
3450        // see https://html.spec.whatwg.org/multipage/#the-iframe-element:completely-loaded
3451        if let Some(old_pipeline) = old_pipeline {
3452            if !old_pipeline.completely_loaded {
3453                history_handling = NavigationHistoryBehavior::Replace;
3454            }
3455            debug!(
3456                "{:?}: Old pipeline is {}completely loaded",
3457                load_info.old_pipeline_id,
3458                if old_pipeline.completely_loaded {
3459                    ""
3460                } else {
3461                    "not "
3462                }
3463            );
3464        }
3465
3466        let is_parent_private = {
3467            let parent_browsing_context_id = match self.pipelines.get(&parent_pipeline_id) {
3468                Some(pipeline) => pipeline.browsing_context_id,
3469                None => {
3470                    return warn!(
3471                        "{parent_pipeline_id}: Script loaded url in iframe \
3472                        {browsing_context_id} in closed parent pipeline",
3473                    );
3474                },
3475            };
3476
3477            let Some(ctx) = self.browsing_contexts.get(&parent_browsing_context_id) else {
3478                return warn!(
3479                    "{parent_browsing_context_id}: Script loaded url in \
3480                     iframe {browsing_context_id} in closed parent browsing context",
3481                );
3482            };
3483            ctx.is_private
3484        };
3485        let is_private = is_private || is_parent_private;
3486
3487        let Some(browsing_context) = self.browsing_contexts.get(&browsing_context_id) else {
3488            return warn!(
3489                "{browsing_context_id}: Script loaded url in iframe with closed browsing context",
3490            );
3491        };
3492
3493        let replace = if history_handling == NavigationHistoryBehavior::Replace {
3494            Some(NeedsToReload::No(browsing_context.pipeline_id))
3495        } else {
3496            None
3497        };
3498
3499        let browsing_context_size = browsing_context.viewport_details;
3500        debug_assert_eq!(browsing_context_size, load_info.viewport_details);
3501
3502        // Create the new pipeline, attached to the parent and push to pending changes
3503        self.new_pipeline(
3504            new_pipeline_id,
3505            browsing_context_id,
3506            webview_id,
3507            Some(parent_pipeline_id),
3508            None,
3509            browsing_context_size,
3510            load_info.load_data,
3511            is_private,
3512            target_snapshot_params,
3513            name,
3514        );
3515
3516        if let Some(webview) = self.webviews.get_mut(&webview_id) {
3517            webview.add_pending_change(SessionHistoryChange {
3518                webview_id,
3519                browsing_context_id,
3520                new_pipeline_id,
3521                replace,
3522                // Browsing context for iframe already exists.
3523                new_browsing_context_info: None,
3524                viewport_details: load_info.viewport_details,
3525            });
3526        } else {
3527            warn!("Could not find WebView for script-loaded iframe: ({webview_id:?})");
3528        }
3529    }
3530
3531    #[servo_tracing::instrument(skip_all)]
3532    fn handle_script_new_iframe(&mut self, load_info: IFrameLoadInfoWithData) {
3533        let IFrameLoadInfo {
3534            parent_pipeline_id,
3535            new_pipeline_id,
3536            browsing_context_id,
3537            webview_id,
3538            is_private,
3539            ..
3540        } = load_info.info;
3541
3542        let (script_sender, parent_browsing_context_id) =
3543            match self.pipelines.get(&parent_pipeline_id) {
3544                Some(pipeline) => (pipeline.event_loop.clone(), pipeline.browsing_context_id),
3545                None => {
3546                    return warn!(
3547                        "{}: Script loaded url in closed iframe pipeline",
3548                        parent_pipeline_id
3549                    );
3550                },
3551            };
3552        let (is_parent_private, is_parent_secure) =
3553            match self.browsing_contexts.get(&parent_browsing_context_id) {
3554                Some(ctx) => (ctx.is_private, ctx.inherited_secure_context),
3555                None => {
3556                    return warn!(
3557                        "{}: New iframe {} loaded in closed parent browsing context",
3558                        parent_browsing_context_id, browsing_context_id,
3559                    );
3560                },
3561            };
3562
3563        let webview_hidden = self
3564            .webviews
3565            .get(&webview_id)
3566            .is_none_or(|webview| webview.hidden());
3567        let is_private = is_private || is_parent_private;
3568        let pipeline = Pipeline::new_already_spawned(
3569            new_pipeline_id,
3570            browsing_context_id,
3571            webview_id,
3572            None,
3573            script_sender,
3574            self.paint_proxy.clone(),
3575            webview_hidden,
3576            load_info.load_data,
3577        );
3578
3579        assert!(!self.pipelines.contains_key(&new_pipeline_id));
3580        self.pipelines.insert(new_pipeline_id, pipeline);
3581
3582        if let Some(webview) = self.webviews.get_mut(&webview_id) {
3583            webview.add_pending_change(SessionHistoryChange {
3584                webview_id,
3585                browsing_context_id,
3586                new_pipeline_id,
3587                replace: None,
3588                // Browsing context for iframe doesn't exist yet.
3589                new_browsing_context_info: Some(NewBrowsingContextInfo {
3590                    parent_pipeline_id: Some(parent_pipeline_id),
3591                    is_private,
3592                    inherited_secure_context: is_parent_secure,
3593                }),
3594                viewport_details: load_info.viewport_details,
3595            });
3596        } else {
3597            warn!("Could not find WebView for new iframe: ({webview_id:?})");
3598        }
3599    }
3600
3601    #[servo_tracing::instrument(skip_all)]
3602    fn handle_script_new_auxiliary(&mut self, load_info: AuxiliaryWebViewCreationRequest) {
3603        let AuxiliaryWebViewCreationRequest {
3604            load_data,
3605            opener_webview_id,
3606            opener_pipeline_id,
3607            response_sender,
3608        } = load_info;
3609
3610        let Some(browsing_context_group) =
3611            self.browsing_context_group_for_webview_id(&opener_webview_id)
3612        else {
3613            warn!("Opener WebView ({opener_webview_id:?}) not found for new auxiliary WebView");
3614            let _ = response_sender.send(None);
3615            return;
3616        };
3617
3618        let Some((webview_id_sender, webview_id_receiver)) = generic_channel::channel() else {
3619            warn!("Failed to create channel");
3620            let _ = response_sender.send(None);
3621            return;
3622        };
3623        self.constellation_to_embedder_proxy
3624            .send(ConstellationToEmbedderMsg::AllowOpeningWebView(
3625                opener_webview_id,
3626                webview_id_sender,
3627            ));
3628        let NewWebViewDetails {
3629            webview_id: new_webview_id,
3630            viewport_details,
3631            user_content_manager_id,
3632        } = match webview_id_receiver.recv() {
3633            Ok(Some(new_webview_details)) => new_webview_details,
3634            Ok(None) | Err(_) => {
3635                let _ = response_sender.send(None);
3636                return;
3637            },
3638        };
3639        let new_browsing_context_id = BrowsingContextId::from(new_webview_id);
3640
3641        let (script_sender, opener_browsing_context_id) =
3642            match self.pipelines.get(&opener_pipeline_id) {
3643                Some(pipeline) => (pipeline.event_loop.clone(), pipeline.browsing_context_id),
3644                None => {
3645                    return warn!(
3646                        "{}: Auxiliary loaded url in closed iframe pipeline",
3647                        opener_pipeline_id
3648                    );
3649                },
3650            };
3651        let (is_opener_private, is_opener_secure) =
3652            match self.browsing_contexts.get(&opener_browsing_context_id) {
3653                Some(ctx) => (ctx.is_private, ctx.inherited_secure_context),
3654                None => {
3655                    return warn!(
3656                        "{}: New auxiliary {} loaded in closed opener browsing context",
3657                        opener_browsing_context_id, new_browsing_context_id,
3658                    );
3659                },
3660            };
3661        let new_pipeline_id = PipelineId::new();
3662        let pipeline = Pipeline::new_already_spawned(
3663            new_pipeline_id,
3664            new_browsing_context_id,
3665            new_webview_id,
3666            Some(opener_browsing_context_id),
3667            script_sender,
3668            self.paint_proxy.clone(),
3669            // New auxiliary WebViews start out as visible. The embedder can still
3670            // hide it with an explicit call to `WebView::hide()` in which case a
3671            // followup message will arrive to the Constellation to hide it.
3672            false, /* hidden */
3673            load_data,
3674        );
3675        let _ = response_sender.send(Some(AuxiliaryWebViewCreationResponse {
3676            new_webview_id,
3677            new_pipeline_id,
3678            user_content_manager_id,
3679        }));
3680
3681        assert!(!self.pipelines.contains_key(&new_pipeline_id));
3682        self.pipelines.insert(new_pipeline_id, pipeline);
3683
3684        let mut new_webview = ConstellationWebView::new(
3685            new_webview_id,
3686            browsing_context_group,
3687            new_browsing_context_id,
3688            user_content_manager_id,
3689        );
3690
3691        // Inherit the opener's theme, which is also what script does. This
3692        // ensures that the two states are in sync.
3693        let opener_theme = self
3694            .webviews
3695            .get(&opener_webview_id)
3696            .map_or(Theme::Light, |webview| webview.theme());
3697        new_webview.set_theme(opener_theme);
3698
3699        new_webview.add_pending_change(SessionHistoryChange {
3700            webview_id: new_webview_id,
3701            browsing_context_id: new_browsing_context_id,
3702            new_pipeline_id,
3703            replace: None,
3704            new_browsing_context_info: Some(NewBrowsingContextInfo {
3705                // Auxiliary browsing contexts are always top-level.
3706                parent_pipeline_id: None,
3707                is_private: is_opener_private,
3708                inherited_secure_context: is_opener_secure,
3709            }),
3710            viewport_details,
3711        });
3712        self.webviews.insert(new_webview_id, new_webview);
3713    }
3714
3715    #[servo_tracing::instrument(skip_all)]
3716    fn handle_refresh_cursor(&self, pipeline_id: PipelineId) {
3717        let Some(pipeline) = self.pipelines.get(&pipeline_id) else {
3718            return;
3719        };
3720
3721        if let Err(error) = pipeline
3722            .event_loop
3723            .send(ScriptThreadMessage::RefreshCursor(pipeline_id))
3724        {
3725            warn!("Could not send RefreshCursor message to pipeline: {error:?}");
3726        }
3727    }
3728
3729    #[servo_tracing::instrument(skip_all)]
3730    fn handle_change_running_animations_state(
3731        &mut self,
3732        pipeline_id: PipelineId,
3733        animation_state: AnimationState,
3734    ) {
3735        match animation_state {
3736            AnimationState::AnimationCallbacksPresent => {
3737                self.handle_change_document_animation_frame_provider_state(pipeline_id, true);
3738            },
3739            AnimationState::AnimationCallbacksAbsent => {
3740                self.handle_change_document_animation_frame_provider_state(pipeline_id, false);
3741            },
3742            AnimationState::AnimationsPresent | AnimationState::NoAnimationsPresent => {
3743                if let Some(pipeline) = self.pipelines.get_mut(&pipeline_id) &&
3744                    pipeline.animation_state != animation_state
3745                {
3746                    pipeline.animation_state = animation_state;
3747                    self.paint_proxy
3748                        .send(PaintMessage::ChangeRunningAnimationsState(
3749                            pipeline.webview_id,
3750                            pipeline_id,
3751                            animation_state,
3752                        ))
3753                }
3754            },
3755        }
3756    }
3757
3758    fn send_animation_frame_callbacks_state_if_changed(&mut self, pipeline_id: PipelineId) {
3759        let Some(pipeline) = self.pipelines.get_mut(&pipeline_id) else {
3760            return;
3761        };
3762
3763        // Paint only tracks per-pipeline callback state.
3764        let callbacks_active =
3765            pipeline.document_callbacks_active || !pipeline.worker_callbacks_active.is_empty();
3766        if pipeline.last_callbacks_active_sent_to_paint == callbacks_active {
3767            return;
3768        }
3769
3770        pipeline.last_callbacks_active_sent_to_paint = callbacks_active;
3771        let animation_state = if callbacks_active {
3772            AnimationState::AnimationCallbacksPresent
3773        } else {
3774            AnimationState::AnimationCallbacksAbsent
3775        };
3776
3777        self.paint_proxy
3778            .send(PaintMessage::ChangeRunningAnimationsState(
3779                pipeline.webview_id,
3780                pipeline_id,
3781                animation_state,
3782            ));
3783    }
3784
3785    fn handle_change_document_animation_frame_provider_state(
3786        &mut self,
3787        pipeline_id: PipelineId,
3788        active: bool,
3789    ) {
3790        let Some(pipeline) = self.pipelines.get_mut(&pipeline_id) else {
3791            return;
3792        };
3793
3794        if pipeline.document_callbacks_active == active {
3795            return;
3796        }
3797
3798        pipeline.document_callbacks_active = active;
3799        self.send_animation_frame_callbacks_state_if_changed(pipeline_id);
3800    }
3801
3802    fn handle_register_worker_animation_frame_provider(
3803        &mut self,
3804        webview_id: WebViewId,
3805        pipeline_id: PipelineId,
3806        worker_id: WorkerId,
3807        sender: GenericSender<WorkerAnimationFrameTick>,
3808    ) {
3809        if !self.pipelines.contains_key(&pipeline_id) {
3810            debug!(
3811                "Ignoring worker animation frame provider for closed pipeline: worker={worker_id:?}, pipeline={pipeline_id:?} ---->",
3812            );
3813            return;
3814        }
3815
3816        debug!(
3817            "Registering worker animation frame provider: worker={worker_id:?}, pipeline={pipeline_id:?}---->",
3818        );
3819        self.worker_animation_frame_providers.insert(
3820            worker_id,
3821            WorkerAnimationFrameProvider {
3822                webview_id,
3823                pipeline_id,
3824                sender,
3825                tick_pending: false,
3826            },
3827        );
3828    }
3829
3830    fn handle_unregister_worker_animation_frame_provider(&mut self, worker_id: WorkerId) {
3831        let Some(provider) = self.worker_animation_frame_providers.remove(&worker_id) else {
3832            return;
3833        };
3834
3835        debug!(
3836            "Unregistering worker animation frame provider: worker={worker_id:?}, pipeline={:?}",
3837            provider.pipeline_id,
3838        );
3839        if let Some(pipeline) = self.pipelines.get_mut(&provider.pipeline_id) {
3840            pipeline.worker_callbacks_active.remove(&worker_id);
3841        }
3842        self.send_animation_frame_callbacks_state_if_changed(provider.pipeline_id);
3843    }
3844
3845    fn remove_worker_animation_frame_providers_for_pipeline(&mut self, pipeline_id: PipelineId) {
3846        let worker_ids = self
3847            .worker_animation_frame_providers
3848            .iter()
3849            .filter_map(|(worker_id, provider)| {
3850                (provider.pipeline_id == pipeline_id).then_some(*worker_id)
3851            })
3852            .collect::<Vec<_>>();
3853
3854        if worker_ids.is_empty() {
3855            return;
3856        }
3857
3858        for worker_id in &worker_ids {
3859            self.worker_animation_frame_providers.remove(worker_id);
3860        }
3861
3862        if let Some(pipeline) = self.pipelines.get_mut(&pipeline_id) {
3863            let mut changed = false;
3864            for worker_id in &worker_ids {
3865                changed |= pipeline.worker_callbacks_active.remove(worker_id);
3866            }
3867            if changed {
3868                self.send_animation_frame_callbacks_state_if_changed(pipeline_id);
3869            }
3870        }
3871    }
3872
3873    fn handle_change_worker_animation_frame_provider_state(
3874        &mut self,
3875        pipeline_id: PipelineId,
3876        worker_id: WorkerId,
3877        active: bool,
3878    ) {
3879        let Some(provider) = self.worker_animation_frame_providers.get_mut(&worker_id) else {
3880            return;
3881        };
3882        let provider_pipeline_id = provider.pipeline_id;
3883        let provider_webview_id = provider.webview_id;
3884        if provider_pipeline_id != pipeline_id {
3885            warn!("Worker animation frame state arrived for an unexpected pipeline --->");
3886            return;
3887        }
3888        let tick_was_pending = std::mem::take(&mut provider.tick_pending);
3889
3890        if !self.pipelines.contains_key(&pipeline_id) {
3891            debug!(
3892                "Removing worker animation frame provider for closed pipeline: worker={worker_id:?}, pipeline={pipeline_id:?}",
3893            );
3894            self.worker_animation_frame_providers.remove(&worker_id);
3895            return;
3896        }
3897
3898        let Some(pipeline) = self.pipelines.get_mut(&pipeline_id) else {
3899            return;
3900        };
3901
3902        let changed = if active {
3903            pipeline.worker_callbacks_active.insert(worker_id)
3904        } else {
3905            pipeline.worker_callbacks_active.remove(&worker_id)
3906        };
3907        debug!(
3908            "Worker animation frame provider state: worker={worker_id:?}, pipeline={pipeline_id:?}, active={active}, changed={changed}",
3909        );
3910        if changed {
3911            self.send_animation_frame_callbacks_state_if_changed(pipeline_id);
3912        } else if active && tick_was_pending {
3913            // Worker only rAF may not produce a display list so request a frame
3914            // for the next refresh-driver tick.
3915            self.paint_proxy
3916                .send(PaintMessage::GenerateFrame(vec![PainterId::from(
3917                    provider_webview_id,
3918                )]));
3919        }
3920    }
3921
3922    fn deliver_rendering_opportunity_to_worker(&mut self, worker_id: WorkerId) {
3923        let mut send_failed = false;
3924        {
3925            let Some(provider) = self.worker_animation_frame_providers.get_mut(&worker_id) else {
3926                return;
3927            };
3928            if provider.tick_pending {
3929                debug!("Skipping pending rendering opportunity: worker={worker_id:?}");
3930                return;
3931            }
3932            if provider.sender.send(WorkerAnimationFrameTick).is_err() {
3933                send_failed = true;
3934            } else {
3935                debug!("Delivered rendering opportunity: worker={worker_id:?}");
3936                provider.tick_pending = true;
3937            }
3938        }
3939
3940        if send_failed {
3941            self.handle_unregister_worker_animation_frame_provider(worker_id);
3942        }
3943    }
3944
3945    /// <https://html.spec.whatwg.org/multipage/#rendering-opportunity>
3946    #[servo_tracing::instrument(skip_all)]
3947    fn handle_tick_animation(&mut self, webview_ids: Vec<WebViewId>) {
3948        let mut animating_event_loops = HashSet::new();
3949        let mut animating_workers = FxHashSet::default();
3950
3951        for webview_id in webview_ids.iter() {
3952            for browsing_context in self.fully_active_browsing_contexts_iter(*webview_id) {
3953                let Some(pipeline) = self.pipelines.get(&browsing_context.pipeline_id) else {
3954                    continue;
3955                };
3956
3957                animating_workers.extend(pipeline.worker_callbacks_active.iter().copied());
3958
3959                // Window rAF still follows the existing script-thread path.
3960                let event_loop = &pipeline.event_loop;
3961                if !animating_event_loops.contains(&event_loop.id()) {
3962                    // No error handling here. It's unclear what to do when this fails as the error isn't associated
3963                    // with a particular pipeline. In addition, the danger of not progressing animations is pretty
3964                    // low, so it's probably safe to ignore this error and handle the crashed ScriptThread on
3965                    // some other message.
3966                    let _ = event_loop
3967                        .send(ScriptThreadMessage::TickAllAnimations(webview_ids.clone()));
3968                    animating_event_loops.insert(event_loop.id());
3969                }
3970            }
3971        }
3972
3973        // Step 6.1.2 runs on the worker event loop; constellation only sends
3974        // tick messages for workers active on this refresh tick.
3975        for worker_id in animating_workers {
3976            let Some(provider) = self.worker_animation_frame_providers.get(&worker_id) else {
3977                error!(
3978                    "No animation frame provider found for active worker: worker={worker_id:?}, webviews={webview_ids:?}",
3979                );
3980                continue;
3981            };
3982            if webview_ids.contains(&provider.webview_id) {
3983                self.deliver_rendering_opportunity_to_worker(worker_id);
3984            } else {
3985                error!(
3986                    "Worker animation frame provider's WebView is not part of the current refresh tick: worker={worker_id:?}, provider_webview={:?}, webviews={webview_ids:?}",
3987                    provider.webview_id,
3988                );
3989            }
3990        }
3991    }
3992
3993    #[servo_tracing::instrument(skip_all)]
3994    fn handle_no_longer_waiting_on_asynchronous_image_updates(
3995        &mut self,
3996        pipeline_ids: Vec<PipelineId>,
3997    ) {
3998        for pipeline_id in pipeline_ids.into_iter() {
3999            if let Some(pipeline) = self.pipelines.get(&pipeline_id) {
4000                let _ = pipeline.event_loop.send(
4001                    ScriptThreadMessage::NoLongerWaitingOnAsychronousImageUpdates(pipeline_id),
4002                );
4003            }
4004        }
4005    }
4006
4007    /// Schedule a navigation(via load_url).
4008    /// 1: Ask the embedder for permission.
4009    /// 2: Store the details of the navigation, pending approval from the embedder.
4010    #[servo_tracing::instrument(skip_all)]
4011    fn schedule_navigation(
4012        &mut self,
4013        webview_id: WebViewId,
4014        source_id: PipelineId,
4015        load_data: LoadData,
4016        history_handling: NavigationHistoryBehavior,
4017        target_snapshot_params: TargetSnapshotParams,
4018    ) {
4019        match self.pending_approval_navigations.entry(source_id) {
4020            Entry::Occupied(_) => {
4021                return warn!(
4022                    "{}: Tried to schedule a navigation while one is already pending",
4023                    source_id
4024                );
4025            },
4026            Entry::Vacant(entry) => {
4027                let _ = entry.insert(PendingApprovalNavigation {
4028                    load_data: load_data.clone(),
4029                    history_behaviour: history_handling,
4030                    target_snapshot_params,
4031                });
4032            },
4033        };
4034
4035        if load_data.is_initial_about_blank {
4036            assert_eq!(load_data.url.as_str(), "about:blank");
4037            // The initial about:blank is not a navigation; the embedder only
4038            // cares about a navigation that follows it.
4039            self.handle_allow_navigation_response(source_id, true);
4040        } else {
4041            // Allow the embedder to handle the url itself
4042            self.constellation_to_embedder_proxy.send(
4043                ConstellationToEmbedderMsg::AllowNavigationRequest(
4044                    webview_id,
4045                    source_id,
4046                    load_data.url,
4047                ),
4048            );
4049        }
4050    }
4051
4052    /// Perform a navigation previously requested by script, if approved by the embedder.
4053    /// If there is already a pending page (self.pending_changes), it will not be overridden;
4054    /// However, if the id is not encompassed by another change, it will be.
4055    fn handle_allow_navigation_response(&mut self, pipeline_id: PipelineId, allowed: bool) {
4056        let pending = self.pending_approval_navigations.remove(&pipeline_id);
4057
4058        let webview_id = match self.pipelines.get(&pipeline_id) {
4059            Some(pipeline) => pipeline.webview_id,
4060            None => return warn!("{}: Attempted to navigate after closure", pipeline_id),
4061        };
4062
4063        match pending {
4064            Some(pending) => {
4065                if allowed {
4066                    self.load_url(
4067                        webview_id,
4068                        pipeline_id,
4069                        pending.load_data,
4070                        pending.history_behaviour,
4071                        pending.target_snapshot_params,
4072                    );
4073                } else {
4074                    if let Some((sender, id)) = &self.webdriver_load_status_sender &&
4075                        pipeline_id == *id
4076                    {
4077                        let _ = sender.send(WebDriverLoadStatus::NavigationStop);
4078                    }
4079
4080                    let pipeline_is_top_level_pipeline = self
4081                        .browsing_contexts
4082                        .get(&BrowsingContextId::from(webview_id))
4083                        .is_some_and(|ctx| ctx.pipeline_id == pipeline_id);
4084                    // If the navigation is refused, and this concerns an iframe,
4085                    // we need to take it out of it's "delaying-load-events-mode".
4086                    // https://html.spec.whatwg.org/multipage/#delaying-load-events-mode
4087                    if !pipeline_is_top_level_pipeline {
4088                        self.send_message_to_pipeline(
4089                            pipeline_id,
4090                            ScriptThreadMessage::StopDelayingLoadEventsMode(pipeline_id),
4091                            "Attempted to navigate after closure",
4092                        );
4093                    }
4094                }
4095            },
4096            None => {
4097                warn!(
4098                    "{}: AllowNavigationResponse for unknown request",
4099                    pipeline_id
4100                )
4101            },
4102        }
4103    }
4104
4105    #[servo_tracing::instrument(skip_all)]
4106    fn load_url(
4107        &mut self,
4108        webview_id: WebViewId,
4109        source_id: PipelineId,
4110        load_data: LoadData,
4111        history_handling: NavigationHistoryBehavior,
4112        target_snapshot_params: TargetSnapshotParams,
4113    ) -> Option<PipelineId> {
4114        debug!(
4115            "{}: Loading ({}replacing): {}",
4116            source_id,
4117            match history_handling {
4118                NavigationHistoryBehavior::Push => "not ",
4119                NavigationHistoryBehavior::Replace => "",
4120                NavigationHistoryBehavior::Auto => "unsure if ",
4121            },
4122            load_data.url,
4123        );
4124        // If this load targets an iframe, its framing element may exist
4125        // in a separate script thread than the framed document that initiated
4126        // the new load. The framing element must be notified about the
4127        // requested change so it can update its internal state.
4128        //
4129        // If replace is true, the current entry is replaced instead of a new entry being added.
4130        let (browsing_context_id, opener) = match self.pipelines.get(&source_id) {
4131            Some(pipeline) => (pipeline.browsing_context_id, pipeline.opener),
4132            None => {
4133                warn!("{}: Loaded after closure", source_id);
4134                return None;
4135            },
4136        };
4137        let (viewport_details, pipeline_id, parent_pipeline_id, is_private) =
4138            match self.browsing_contexts.get(&browsing_context_id) {
4139                Some(ctx) => (
4140                    ctx.viewport_details,
4141                    ctx.pipeline_id,
4142                    ctx.parent_pipeline_id,
4143                    ctx.is_private,
4144                ),
4145                None => {
4146                    // This should technically never happen (since `load_url` is
4147                    // only called on existing browsing contexts), but we prefer to
4148                    // avoid `expect`s or `unwrap`s in `Constellation` to ward
4149                    // against future changes that might break things.
4150                    warn!(
4151                        "{}: Loaded url in closed {}",
4152                        source_id, browsing_context_id,
4153                    );
4154                    return None;
4155                },
4156            };
4157
4158        #[cfg(feature = "devtools")]
4159        if let Some(ref chan) = self.devtools_sender {
4160            let state = NavigationState::Start(load_data.url.clone());
4161            let _ = chan.send(DevtoolsControlMsg::FromScript(
4162                ScriptToDevtoolsControlMsg::Navigate(browsing_context_id, state),
4163            ));
4164        }
4165
4166        match parent_pipeline_id {
4167            Some(parent_pipeline_id) => {
4168                // Find the script thread for the pipeline containing the iframe
4169                // and issue an iframe load through there.
4170                let msg = ScriptThreadMessage::NavigateIframe(
4171                    parent_pipeline_id,
4172                    browsing_context_id,
4173                    load_data,
4174                    history_handling,
4175                    target_snapshot_params,
4176                );
4177                let result = match self.pipelines.get(&parent_pipeline_id) {
4178                    Some(parent_pipeline) => parent_pipeline.event_loop.send(msg),
4179                    None => {
4180                        warn!("{}: Child loaded after closure", parent_pipeline_id);
4181                        return None;
4182                    },
4183                };
4184                if let Err(e) = result {
4185                    self.handle_send_error(parent_pipeline_id, e);
4186                } else if let Some((sender, id)) = &self.webdriver_load_status_sender &&
4187                    source_id == *id
4188                {
4189                    let _ = sender.send(WebDriverLoadStatus::NavigationStop);
4190                }
4191
4192                None
4193            },
4194            None => {
4195                // Make sure no pending page would be overridden.
4196                if let Some(webview) = self.webviews.get(&webview_id) &&
4197                    webview.pending_changes.iter().any(|pending_change| {
4198                        pending_change.browsing_context_id == browsing_context_id
4199                    })
4200                {
4201                    // id that sent load msg is being changed already; abort
4202                    return None;
4203                }
4204
4205                if self.get_activity(source_id) == DocumentActivity::Inactive {
4206                    // Disregard this load if the navigating pipeline is not actually
4207                    // active. This could be caused by a delayed navigation (eg. from
4208                    // a timer) or a race between multiple navigations (such as an
4209                    // onclick handler on an anchor element).
4210                    return None;
4211                }
4212
4213                // Being here means either there are no pending changes, or none of the pending
4214                // changes would be overridden by changing the subframe associated with source_id.
4215
4216                // Create the new pipeline
4217
4218                let replace = if history_handling == NavigationHistoryBehavior::Replace {
4219                    Some(NeedsToReload::No(pipeline_id))
4220                } else {
4221                    None
4222                };
4223
4224                let new_pipeline_id = PipelineId::new();
4225                self.new_pipeline(
4226                    new_pipeline_id,
4227                    browsing_context_id,
4228                    webview_id,
4229                    None,
4230                    opener,
4231                    viewport_details,
4232                    load_data,
4233                    is_private,
4234                    target_snapshot_params,
4235                    None,
4236                );
4237
4238                if let Some(webview) = self.webviews.get_mut(&webview_id) {
4239                    webview.add_pending_change(SessionHistoryChange {
4240                        webview_id,
4241                        browsing_context_id,
4242                        new_pipeline_id,
4243                        replace,
4244                        // `load_url` is always invoked on an existing browsing context.
4245                        new_browsing_context_info: None,
4246                        viewport_details,
4247                    });
4248                } else {
4249                    warn!("Could not find WebView for URL load: ({webview_id:?})");
4250                }
4251
4252                Some(new_pipeline_id)
4253            },
4254        }
4255    }
4256
4257    #[servo_tracing::instrument(skip_all)]
4258    fn handle_abort_load_url_msg(&mut self, webview_id: WebViewId, new_pipeline_id: PipelineId) {
4259        if self
4260            .webviews
4261            .get_mut(&webview_id)
4262            .and_then(|webview| webview.remove_pending_change_for_pipeline(new_pipeline_id))
4263            .is_none()
4264        {
4265            return;
4266        }
4267
4268        self.close_pipeline(
4269            new_pipeline_id,
4270            DiscardBrowsingContext::No,
4271            ExitPipelineMode::Normal,
4272        );
4273
4274        if let Some(webview) = self.webviews.get_mut(&webview_id) {
4275            webview.send_screenshot_readiness_requests_to_pipelines(
4276                &self.browsing_contexts,
4277                &self.pipelines,
4278            );
4279        };
4280    }
4281
4282    #[servo_tracing::instrument(skip_all)]
4283    fn handle_load_complete_msg(&mut self, webview_id: WebViewId, pipeline_id: PipelineId) {
4284        if let Some(pipeline) = self.pipelines.get_mut(&pipeline_id) {
4285            debug!("{}: Marking as loaded", pipeline_id);
4286            pipeline.completely_loaded = true;
4287        }
4288
4289        // Notify the embedder that the TopLevelBrowsingContext current document
4290        // has finished loading.
4291        // We need to make sure the pipeline that has finished loading is the current
4292        // pipeline and that no pending pipeline will replace the current one.
4293        let pipeline_is_top_level_pipeline = self
4294            .browsing_contexts
4295            .get(&BrowsingContextId::from(webview_id))
4296            .is_some_and(|ctx| ctx.pipeline_id == pipeline_id);
4297        if !pipeline_is_top_level_pipeline {
4298            self.handle_subframe_loaded(pipeline_id);
4299        }
4300    }
4301
4302    #[servo_tracing::instrument(skip_all)]
4303    fn handle_navigated_to_fragment(
4304        &mut self,
4305        pipeline_id: PipelineId,
4306        new_url: ServoUrl,
4307        history_handling: NavigationHistoryBehavior,
4308    ) {
4309        let (webview_id, old_url) = match self.pipelines.get_mut(&pipeline_id) {
4310            Some(pipeline) => {
4311                let old_url = replace(&mut pipeline.url, new_url.clone());
4312                (pipeline.webview_id, old_url)
4313            },
4314            None => {
4315                return warn!("{}: Navigated to fragment after closure", pipeline_id);
4316            },
4317        };
4318
4319        let Some(webview) = self.webviews.get_mut(&webview_id) else {
4320            return warn!("Ignoring navigation in non-existent WebView ({webview_id:?}).");
4321        };
4322
4323        match history_handling {
4324            NavigationHistoryBehavior::Replace => {},
4325            _ => {
4326                let diff = SessionHistoryDiff::Hash {
4327                    pipeline_reloader: NeedsToReload::No(pipeline_id),
4328                    new_url,
4329                    old_url,
4330                };
4331
4332                webview.session_history.push_diff(diff);
4333                self.notify_history_changed(webview_id);
4334            },
4335        }
4336    }
4337
4338    /// Step 4 of <https://html.spec.whatwg.org/multipage/#traverse-the-history-by-a-delta>
4339    ///
4340    /// This change appends a session history traversal to the session history traversal queue,
4341    /// which is stored as [`ConstellationWebView::history_traversal_request_queue`].
4342    ///
4343    /// See also: <https://html.spec.whatwg.org/multipage/#tn-session-history-traversal-queue>
4344    #[servo_tracing::instrument(skip_all)]
4345    fn handle_traverse_session_history_msg(
4346        &mut self,
4347        traversal_request: SessionHistoryTraversalRequest,
4348    ) {
4349        let webview_id = traversal_request.webview_id;
4350        let Some(webview) = self.webviews.get_mut(&webview_id) else {
4351            self.notify_embedder_of_completed_session_history_traversal_request(&traversal_request);
4352            return warn!("Ignoring history traversal in non-existent WebView ({webview_id:?}).");
4353        };
4354
4355        webview
4356            .session_history_traversal_request_queue
4357            .push_back(traversal_request);
4358        self.apply_queued_session_history_traversal_requests(webview_id);
4359    }
4360
4361    /// Apply the given [`HistoryTraversalRequest`], triggering the session history navigation.
4362    /// Return the same [`HistoryTraversalRequest`] when it completes immediately or is out
4363    /// of range, otherwise return `None` when it is still pending.
4364    fn apply_session_history_traversal_request(
4365        &mut self,
4366        traversal_request: SessionHistoryTraversalRequest,
4367    ) -> Option<SessionHistoryTraversalRequest> {
4368        let mut browsing_context_changes = FxHashMap::<BrowsingContextId, NeedsToReload>::default();
4369        let mut pipeline_changes =
4370            FxHashMap::<PipelineId, (Option<HistoryStateId>, ServoUrl)>::default();
4371        let mut url_to_load = FxHashMap::<PipelineId, ServoUrl>::default();
4372        let webview_id = traversal_request.webview_id;
4373
4374        {
4375            let Some(webview) = self.webviews.get_mut(&webview_id) else {
4376                warn!("Ignoring history traversal in non-existent WebView ({webview_id:?}).");
4377                return Some(traversal_request);
4378            };
4379
4380            match traversal_request.direction {
4381                TraversalDirection::Forward(forward) => {
4382                    let future_length = webview.session_history.future.len();
4383                    if future_length < forward {
4384                        warn!("Cannot traverse that far into the future.");
4385                        return Some(traversal_request);
4386                    }
4387
4388                    for diff in webview
4389                        .session_history
4390                        .future
4391                        .drain(future_length - forward..)
4392                        .rev()
4393                    {
4394                        match diff {
4395                            SessionHistoryDiff::BrowsingContext {
4396                                browsing_context_id,
4397                                ref new_reloader,
4398                                ..
4399                            } => {
4400                                browsing_context_changes
4401                                    .insert(browsing_context_id, new_reloader.clone());
4402                            },
4403                            SessionHistoryDiff::Pipeline {
4404                                ref pipeline_reloader,
4405                                new_history_state_id,
4406                                ref new_url,
4407                                ..
4408                            } => match *pipeline_reloader {
4409                                NeedsToReload::No(pipeline_id) => {
4410                                    pipeline_changes.insert(
4411                                        pipeline_id,
4412                                        (Some(new_history_state_id), new_url.clone()),
4413                                    );
4414                                },
4415                                NeedsToReload::Yes(pipeline_id, ..) => {
4416                                    url_to_load.insert(pipeline_id, new_url.clone());
4417                                },
4418                            },
4419                            SessionHistoryDiff::Hash {
4420                                ref pipeline_reloader,
4421                                ref new_url,
4422                                ..
4423                            } => match *pipeline_reloader {
4424                                NeedsToReload::No(pipeline_id) => {
4425                                    let state = pipeline_changes
4426                                        .get(&pipeline_id)
4427                                        .and_then(|change| change.0);
4428                                    pipeline_changes.insert(pipeline_id, (state, new_url.clone()));
4429                                },
4430                                NeedsToReload::Yes(pipeline_id, ..) => {
4431                                    url_to_load.insert(pipeline_id, new_url.clone());
4432                                },
4433                            },
4434                        }
4435                        webview.session_history.past.push(diff);
4436                    }
4437                },
4438                TraversalDirection::Back(back) => {
4439                    let past_length = webview.session_history.past.len();
4440                    if past_length < back {
4441                        warn!("Cannot traverse that far into the past.");
4442                        return Some(traversal_request);
4443                    }
4444
4445                    for diff in webview
4446                        .session_history
4447                        .past
4448                        .drain(past_length - back..)
4449                        .rev()
4450                    {
4451                        match diff {
4452                            SessionHistoryDiff::BrowsingContext {
4453                                browsing_context_id,
4454                                ref old_reloader,
4455                                ..
4456                            } => {
4457                                browsing_context_changes
4458                                    .insert(browsing_context_id, old_reloader.clone());
4459                            },
4460                            SessionHistoryDiff::Pipeline {
4461                                ref pipeline_reloader,
4462                                old_history_state_id,
4463                                ref old_url,
4464                                ..
4465                            } => match *pipeline_reloader {
4466                                NeedsToReload::No(pipeline_id) => {
4467                                    pipeline_changes.insert(
4468                                        pipeline_id,
4469                                        (old_history_state_id, old_url.clone()),
4470                                    );
4471                                },
4472                                NeedsToReload::Yes(pipeline_id, ..) => {
4473                                    url_to_load.insert(pipeline_id, old_url.clone());
4474                                },
4475                            },
4476                            SessionHistoryDiff::Hash {
4477                                ref pipeline_reloader,
4478                                ref old_url,
4479                                ..
4480                            } => match *pipeline_reloader {
4481                                NeedsToReload::No(pipeline_id) => {
4482                                    let state = pipeline_changes
4483                                        .get(&pipeline_id)
4484                                        .and_then(|change| change.0);
4485                                    pipeline_changes.insert(pipeline_id, (state, old_url.clone()));
4486                                },
4487                                NeedsToReload::Yes(pipeline_id, ..) => {
4488                                    url_to_load.insert(pipeline_id, old_url.clone());
4489                                },
4490                            },
4491                        }
4492                        webview.session_history.future.push(diff);
4493                    }
4494                },
4495            }
4496        }
4497
4498        let pipelines_awaiting_activation: FxHashSet<_> = browsing_context_changes
4499            .drain()
4500            .filter_map(|(browsing_context_id, mut pipeline_reloader)| {
4501                if let NeedsToReload::Yes(pipeline_id, ref mut load_data) = pipeline_reloader &&
4502                    let Some(url) = url_to_load.get(&pipeline_id)
4503                {
4504                    load_data.url = url.clone();
4505                }
4506                self.activate_history_entry_in_browsing_context(
4507                    browsing_context_id,
4508                    pipeline_reloader,
4509                )
4510            })
4511            .collect();
4512
4513        for (pipeline_id, (history_state_id, url)) in pipeline_changes.drain() {
4514            self.update_pipeline_history_state(pipeline_id, history_state_id, url);
4515        }
4516
4517        self.notify_history_changed(webview_id);
4518        self.trim_history(webview_id);
4519        self.set_frame_tree_for_webview(webview_id);
4520
4521        // The traversal finishes immediately when we are traversing within the same document
4522        // or between already live pipelines. Note that the script event loop may still be
4523        // completing these navigations asynchronously.
4524        if pipelines_awaiting_activation.is_empty() {
4525            return Some(traversal_request);
4526        }
4527
4528        let Some(webview) = self.webviews.get_mut(&webview_id) else {
4529            return Some(traversal_request);
4530        };
4531        webview.ongoing_history_traversal_request = Some(OngoingHistoryTraversalRequest {
4532            traversal_request,
4533            pipelines_awaiting_activation,
4534        });
4535        None
4536    }
4537
4538    /// Finish any completed ongoing history traversal request from any active
4539    /// [`ConstellationWebView`] and trigger (and possibly finish) any other queued history
4540    /// traversals on that same `WebView` until there are no remaining completed ongoing history
4541    /// traversal requests.
4542    fn finish_completed_session_history_traversal_requests(&mut self) {
4543        while let Some(traversal_request) = self
4544            .webviews
4545            .values_mut()
4546            .find_map(|webview| webview.maybe_finish_ongoing_session_history_traversal_request())
4547        {
4548            self.notify_embedder_of_completed_session_history_traversal_request(&traversal_request);
4549            self.apply_queued_session_history_traversal_requests(traversal_request.webview_id);
4550        }
4551    }
4552
4553    /// Notify the embedder that the given [`HistoryTraversalRequest`] is complete, if it was
4554    /// triggered by the embedder.
4555    fn notify_embedder_of_completed_session_history_traversal_request(
4556        &self,
4557        traversal_request: &SessionHistoryTraversalRequest,
4558    ) {
4559        if traversal_request.source == HistoryTraversalSource::Embedder {
4560            self.constellation_to_embedder_proxy.send(
4561                ConstellationToEmbedderMsg::HistoryTraversalComplete(
4562                    traversal_request.webview_id,
4563                    traversal_request.id.clone(),
4564                ),
4565            );
4566        }
4567    }
4568
4569    /// If the [`ConstellationWebView`] with the given [`WebViewId`] does not have any
4570    /// ongoing traversal history request, apply the next queued request, if one exists.
4571    fn apply_next_queued_session_history_traversal_request(
4572        &mut self,
4573        webview_id: WebViewId,
4574    ) -> Option<SessionHistoryTraversalRequest> {
4575        let webview = self.webviews.get_mut(&webview_id)?;
4576        if webview.ongoing_history_traversal_request.is_some() {
4577            return None;
4578        }
4579
4580        let traversal_request = webview
4581            .session_history_traversal_request_queue
4582            .pop_front()?;
4583        self.apply_session_history_traversal_request(traversal_request)
4584    }
4585
4586    /// If the [`ConstellationWebView`] with the given [`WebViewId`] does not have any ongoing
4587    /// traversal history request, apply the next queued request and potentially finish it
4588    /// until there is either an ongoing history request or no more queued requests.
4589    fn apply_queued_session_history_traversal_requests(&mut self, webview_id: WebViewId) {
4590        while let Some(traversal_request) =
4591            self.apply_next_queued_session_history_traversal_request(webview_id)
4592        {
4593            self.notify_embedder_of_completed_session_history_traversal_request(&traversal_request);
4594        }
4595    }
4596
4597    /// <https://html.spec.whatwg.org/multipage/#activate-history-entry>
4598    ///
4599    /// Activate the history entry indicated by the given [`NeedsToReload`]. Returns the
4600    /// [`PipelineId`] of the pipeline if the constellation is waiting for it to reload
4601    /// and `None` otherwise.
4602    #[servo_tracing::instrument(skip_all)]
4603    fn activate_history_entry_in_browsing_context(
4604        &mut self,
4605        browsing_context_id: BrowsingContextId,
4606        new_reloader: NeedsToReload,
4607    ) -> Option<PipelineId> {
4608        let new_pipeline_id = match new_reloader {
4609            NeedsToReload::No(pipeline_id) => pipeline_id,
4610            NeedsToReload::Yes(pipeline_id, mut load_data) => {
4611                debug!(
4612                    "{}: Reloading document {}",
4613                    browsing_context_id, pipeline_id,
4614                );
4615
4616                // <https://html.spec.whatwg.org/multipage/#process-a-navigate-fetch>
4617                // Step 7. If entry's document state's reload pending is true, then set request's reload-navigation flag.
4618                // Step 8. Otherwise, if entry's document state's ever populated is true, then set request's history-navigation flag.
4619                load_data.history_navigation = true;
4620                load_data.reload_navigation = false;
4621
4622                let (webview_id, old_pipeline_id, parent_pipeline_id, viewport_details, is_private) =
4623                    match self.browsing_contexts.get(&browsing_context_id) {
4624                        Some(ctx) => (
4625                            ctx.webview_id,
4626                            ctx.pipeline_id,
4627                            ctx.parent_pipeline_id,
4628                            ctx.viewport_details,
4629                            ctx.is_private,
4630                        ),
4631                        None => {
4632                            warn!("No browsing context to traverse!");
4633                            return None;
4634                        },
4635                    };
4636                let opener = match self.pipelines.get(&old_pipeline_id) {
4637                    Some(pipeline) => pipeline.opener,
4638                    None => None,
4639                };
4640                let new_pipeline_id = PipelineId::new();
4641                self.new_pipeline(
4642                    new_pipeline_id,
4643                    browsing_context_id,
4644                    webview_id,
4645                    parent_pipeline_id,
4646                    opener,
4647                    viewport_details,
4648                    load_data.clone(),
4649                    is_private,
4650                    // TODO(jdm): We need to store the original target snapshot params
4651                    // with the pipeline when it's created, so we can support reloading
4652                    // a discarded document properly.
4653                    TargetSnapshotParams::default(),
4654                    None,
4655                );
4656
4657                let webview = self
4658                    .webviews
4659                    .get_mut(&webview_id)
4660                    .expect("WebView for history traversal should always exist at this point");
4661                webview.add_pending_change(SessionHistoryChange {
4662                    webview_id,
4663                    browsing_context_id,
4664                    new_pipeline_id,
4665                    replace: Some(NeedsToReload::Yes(pipeline_id, load_data)),
4666                    // Browsing context must exist at this point.
4667                    new_browsing_context_info: None,
4668                    viewport_details,
4669                });
4670                return Some(new_pipeline_id);
4671            },
4672        };
4673
4674        let (old_pipeline_id, parent_pipeline_id, webview_id) =
4675            match self.browsing_contexts.get_mut(&browsing_context_id) {
4676                Some(browsing_context) => {
4677                    let old_pipeline_id = browsing_context.pipeline_id;
4678                    browsing_context.update_current_entry(new_pipeline_id);
4679                    (
4680                        old_pipeline_id,
4681                        browsing_context.parent_pipeline_id,
4682                        browsing_context.webview_id,
4683                    )
4684                },
4685                None => {
4686                    warn!("{browsing_context_id}: Closed during traversal");
4687                    return None;
4688                },
4689            };
4690
4691        self.unload_document(old_pipeline_id);
4692
4693        if let Some(new_pipeline) = self.pipelines.get_mut(&new_pipeline_id) {
4694            #[cfg(feature = "devtools")]
4695            if let Some(ref chan) = self.devtools_sender {
4696                let state = NavigationState::Start(new_pipeline.url.clone());
4697                let _ = chan.send(DevtoolsControlMsg::FromScript(
4698                    ScriptToDevtoolsControlMsg::Navigate(browsing_context_id, state),
4699                ));
4700                let page_info = DevtoolsPageInfo {
4701                    title: new_pipeline.title.clone(),
4702                    url: new_pipeline.url.clone(),
4703                    is_top_level_global: webview_id == browsing_context_id,
4704                    is_service_worker: false,
4705                };
4706                let state = NavigationState::Stop(new_pipeline.id, page_info);
4707                let _ = chan.send(DevtoolsControlMsg::FromScript(
4708                    ScriptToDevtoolsControlMsg::Navigate(browsing_context_id, state),
4709                ));
4710            }
4711
4712            new_pipeline.set_has_active_document(true);
4713
4714            // When navigating away from a Pipeline it is throttled, so if the WebView
4715            // is not hidden we must now unthrottle it.
4716            let webview_hidden = self
4717                .webviews
4718                .get(&webview_id)
4719                .is_none_or(|webview| webview.hidden());
4720            new_pipeline.send_throttle_messages(webview_hidden);
4721
4722            self.notify_focus_state(new_pipeline_id);
4723        }
4724
4725        self.update_activity(old_pipeline_id);
4726        self.update_activity(new_pipeline_id);
4727
4728        if let Some(parent_pipeline_id) = parent_pipeline_id {
4729            let msg = ScriptThreadMessage::UpdatePipelineId(
4730                parent_pipeline_id,
4731                browsing_context_id,
4732                webview_id,
4733                new_pipeline_id,
4734                UpdatePipelineIdReason::Traversal,
4735            );
4736            self.send_message_to_pipeline(parent_pipeline_id, msg, "Child traversed after closure");
4737        }
4738
4739        None
4740    }
4741
4742    #[servo_tracing::instrument(skip_all)]
4743    fn update_pipeline_history_state(
4744        &mut self,
4745        pipeline_id: PipelineId,
4746        history_state_id: Option<HistoryStateId>,
4747        url: ServoUrl,
4748    ) {
4749        if let Some(pipeline) = self.pipelines.get_mut(&pipeline_id) {
4750            pipeline.history_state_id = history_state_id;
4751            pipeline.url = url.clone();
4752        }
4753        let msg = ScriptThreadMessage::UpdateHistoryState(pipeline_id, history_state_id, url);
4754        self.send_message_to_pipeline(pipeline_id, msg, "History state updated after closure");
4755    }
4756
4757    #[servo_tracing::instrument(skip_all)]
4758    fn handle_joint_session_history_length(
4759        &self,
4760        webview_id: WebViewId,
4761        response_sender: GenericSender<u32>,
4762    ) {
4763        let length = self
4764            .webviews
4765            .get(&webview_id)
4766            .map(|webview| webview.session_history.history_length())
4767            .unwrap_or(1);
4768        let _ = response_sender.send(length as u32);
4769    }
4770
4771    #[servo_tracing::instrument(skip_all)]
4772    fn handle_push_history_state_msg(
4773        &mut self,
4774        pipeline_id: PipelineId,
4775        history_state_id: HistoryStateId,
4776        url: ServoUrl,
4777    ) {
4778        let (webview_id, old_state_id, old_url) = match self.pipelines.get_mut(&pipeline_id) {
4779            Some(pipeline) => {
4780                let old_history_state_id = pipeline.history_state_id;
4781                let old_url = replace(&mut pipeline.url, url.clone());
4782                pipeline.history_state_id = Some(history_state_id);
4783                pipeline.history_states.insert(history_state_id);
4784                (pipeline.webview_id, old_history_state_id, old_url)
4785            },
4786            None => {
4787                return warn!(
4788                    "{}: Push history state {} for closed pipeline",
4789                    pipeline_id, history_state_id,
4790                );
4791            },
4792        };
4793
4794        let Some(webview) = self.webviews.get_mut(&webview_id) else {
4795            return warn!("Ignoring history change in non-existent WebView ({webview_id:?}).");
4796        };
4797
4798        let diff = SessionHistoryDiff::Pipeline {
4799            pipeline_reloader: NeedsToReload::No(pipeline_id),
4800            new_history_state_id: history_state_id,
4801            new_url: url,
4802            old_history_state_id: old_state_id,
4803            old_url,
4804        };
4805        webview.session_history.push_diff(diff);
4806        self.notify_history_changed(webview_id);
4807    }
4808
4809    #[servo_tracing::instrument(skip_all)]
4810    fn handle_replace_history_state_msg(
4811        &mut self,
4812        pipeline_id: PipelineId,
4813        history_state_id: HistoryStateId,
4814        url: ServoUrl,
4815    ) {
4816        let webview_id = match self.pipelines.get_mut(&pipeline_id) {
4817            Some(pipeline) => {
4818                pipeline.history_state_id = Some(history_state_id);
4819                pipeline.url = url.clone();
4820                pipeline.webview_id
4821            },
4822            None => {
4823                return warn!(
4824                    "{}: Replace history state {} for closed pipeline",
4825                    history_state_id, pipeline_id
4826                );
4827            },
4828        };
4829
4830        let Some(webview) = self.webviews.get_mut(&webview_id) else {
4831            return warn!("Ignoring history change in non-existent WebView ({webview_id:?}).");
4832        };
4833
4834        webview
4835            .session_history
4836            .replace_history_state(pipeline_id, history_state_id, url);
4837        self.notify_history_changed(webview_id);
4838    }
4839
4840    #[servo_tracing::instrument(skip_all)]
4841    fn handle_reload_msg(&mut self, webview_id: WebViewId) {
4842        let browsing_context_id = BrowsingContextId::from(webview_id);
4843        let pipeline_id = match self.browsing_contexts.get(&browsing_context_id) {
4844            Some(browsing_context) => browsing_context.pipeline_id,
4845            None => {
4846                return warn!("{}: Got reload event after closure", browsing_context_id);
4847            },
4848        };
4849        self.send_message_to_pipeline(
4850            pipeline_id,
4851            ScriptThreadMessage::Reload(pipeline_id),
4852            "Got reload event after closure",
4853        );
4854    }
4855
4856    /// <https://html.spec.whatwg.org/multipage/#window-post-message-steps>
4857    #[servo_tracing::instrument(skip_all)]
4858    fn handle_post_message_msg(
4859        &mut self,
4860        browsing_context_id: BrowsingContextId,
4861        source_pipeline: PipelineId,
4862        origin: Option<ImmutableOrigin>,
4863        source_origin: ImmutableOrigin,
4864        data: StructuredSerializedData,
4865    ) {
4866        let pipeline_id = match self.browsing_contexts.get(&browsing_context_id) {
4867            None => {
4868                return warn!(
4869                    "{}: PostMessage to closed browsing context",
4870                    browsing_context_id
4871                );
4872            },
4873            Some(browsing_context) => browsing_context.pipeline_id,
4874        };
4875        let source_webview = match self.pipelines.get(&source_pipeline) {
4876            Some(pipeline) => pipeline.webview_id,
4877            None => return warn!("{}: PostMessage from closed pipeline", source_pipeline),
4878        };
4879
4880        let browsing_context_for_pipeline = |pipeline_id| {
4881            self.pipelines
4882                .get(&pipeline_id)
4883                .and_then(|pipeline| self.browsing_contexts.get(&pipeline.browsing_context_id))
4884        };
4885        let mut maybe_browsing_context = browsing_context_for_pipeline(source_pipeline);
4886        if maybe_browsing_context.is_none() {
4887            return warn!("{source_pipeline}: PostMessage from pipeline with closed parent");
4888        }
4889
4890        // Step 8.3: Let source be the WindowProxy object corresponding to
4891        // incumbentSettings's global object (a Window object).
4892        // Note: done here to prevent a round-trip to the constellation later,
4893        // and to prevent panic as part of that round-trip
4894        // in the case that the source would already have been closed.
4895        let mut source_with_ancestry = vec![];
4896        while let Some(browsing_context) = maybe_browsing_context {
4897            source_with_ancestry.push(browsing_context.id);
4898            maybe_browsing_context = browsing_context
4899                .parent_pipeline_id
4900                .and_then(browsing_context_for_pipeline);
4901        }
4902        let msg = ScriptThreadMessage::PostMessage {
4903            target: pipeline_id,
4904            source_webview,
4905            source_with_ancestry,
4906            target_origin: origin,
4907            source_origin,
4908            data: Box::new(data),
4909        };
4910        self.send_message_to_pipeline(pipeline_id, msg, "PostMessage to closed pipeline");
4911    }
4912
4913    #[servo_tracing::instrument(skip_all)]
4914    fn handle_focus_ancestor_browsing_contexts_for_focusing_steps(
4915        &mut self,
4916        pipeline_id: PipelineId,
4917        focused_child_browsing_context_id: Option<BrowsingContextId>,
4918        sequence: FocusSequenceNumber,
4919    ) {
4920        let browsing_context_id = match self.pipelines.get_mut(&pipeline_id) {
4921            Some(pipeline) => {
4922                pipeline.focus_sequence = sequence;
4923                pipeline.browsing_context_id
4924            },
4925            None => return warn!("{}: Focus parent after closure", pipeline_id),
4926        };
4927
4928        // Ignore if the pipeline isn't fully active.
4929        if self.get_activity(pipeline_id) != DocumentActivity::FullyActive {
4930            debug!(
4931                "Ignoring the focus request because pipeline {} is not \
4932                fully active",
4933                pipeline_id
4934            );
4935            return;
4936        }
4937
4938        // If a container with a non-null nested browsing context is focused,
4939        // the nested browsing context's active document becomes the focused
4940        // area of the top-level browsing context instead.
4941        let focused_browsing_context_id =
4942            focused_child_browsing_context_id.unwrap_or(browsing_context_id);
4943
4944        // Send focus messages to the affected pipelines, except
4945        // `pipeline_id`, which has already its local focus state
4946        // updated.
4947        self.focus_browsing_context(Some(pipeline_id), focused_browsing_context_id);
4948    }
4949
4950    fn handle_focus_remote_browsing_context(
4951        &mut self,
4952        target: BrowsingContextId,
4953        operation: RemoteFocusOperation,
4954    ) {
4955        let Some(browsing_context) = self.browsing_contexts.get(&target) else {
4956            return warn!("{target:?} not found for focus message");
4957        };
4958        let pipeline_id = browsing_context.pipeline_id;
4959        let Some(pipeline) = self.pipelines.get(&pipeline_id) else {
4960            return warn!("{pipeline_id:?} not found for focus message");
4961        };
4962        if let Err(error) = pipeline
4963            .event_loop
4964            .send(ScriptThreadMessage::FocusDocument(pipeline_id, operation))
4965        {
4966            self.handle_send_error(pipeline_id, error);
4967        }
4968    }
4969
4970    /// Perform [the focusing steps][1] for the active document of
4971    /// `focused_browsing_context_id`.
4972    ///
4973    /// If `initiator_pipeline_id` is specified, this method avoids sending
4974    /// a message to `initiator_pipeline_id`, assuming its local focus state has
4975    /// already been updated. This is necessary for performing the focusing
4976    /// steps for an object that is not the document itself but something that
4977    /// belongs to the document.
4978    ///
4979    /// [1]: https://html.spec.whatwg.org/multipage/#focusing-steps
4980    #[servo_tracing::instrument(skip_all)]
4981    fn focus_browsing_context(
4982        &mut self,
4983        initiator_pipeline_id: Option<PipelineId>,
4984        focused_browsing_context_id: BrowsingContextId,
4985    ) {
4986        let webview_id = match self.browsing_contexts.get(&focused_browsing_context_id) {
4987            Some(browsing_context) => browsing_context.webview_id,
4988            None => return warn!("Browsing context {} not found", focused_browsing_context_id),
4989        };
4990
4991        // Update the webview’s focused browsing context.
4992        let old_focused_browsing_context_id = match self.webviews.get_mut(&webview_id) {
4993            Some(browser) => replace(
4994                &mut browser.focused_browsing_context_id,
4995                focused_browsing_context_id,
4996            ),
4997            None => {
4998                return warn!(
4999                    "{}: Browsing context for focus msg does not exist",
5000                    webview_id
5001                );
5002            },
5003        };
5004
5005        // The following part is similar to [the focus update steps][1] except
5006        // that only `Document`s in the given focus chains are considered. It's
5007        // ultimately up to the script threads to fire focus events at the
5008        // affected objects.
5009        //
5010        // [1]: https://html.spec.whatwg.org/multipage/#focus-update-steps
5011        let mut old_focus_chain_pipelines: Vec<&Pipeline> = self
5012            .ancestor_or_self_pipelines_of_browsing_context_iter(old_focused_browsing_context_id)
5013            .collect();
5014        let mut new_focus_chain_pipelines: Vec<&Pipeline> = self
5015            .ancestor_or_self_pipelines_of_browsing_context_iter(focused_browsing_context_id)
5016            .collect();
5017
5018        debug!(
5019            "old_focus_chain_pipelines = {:?}",
5020            old_focus_chain_pipelines
5021                .iter()
5022                .map(|p| p.id.to_string())
5023                .collect::<Vec<_>>()
5024        );
5025        debug!(
5026            "new_focus_chain_pipelines = {:?}",
5027            new_focus_chain_pipelines
5028                .iter()
5029                .map(|p| p.id.to_string())
5030                .collect::<Vec<_>>()
5031        );
5032
5033        // At least the last entries should match. Otherwise something is wrong,
5034        // and we don't want to proceed and crash the top-level pipeline by
5035        // sending an impossible `Unfocus` message to it.
5036        match (
5037            &old_focus_chain_pipelines[..],
5038            &new_focus_chain_pipelines[..],
5039        ) {
5040            ([.., p1], [.., p2]) if p1.id == p2.id => {},
5041            _ => {
5042                warn!("Aborting the focus operation - focus chain sanity check failed");
5043                return;
5044            },
5045        }
5046
5047        // > If the last entry in `old chain` and the last entry in `new chain`
5048        // > are the same, pop the last entry from `old chain` and the last
5049        // > entry from `new chain` and redo this step.
5050        let mut first_common_pipeline_in_chain = None;
5051        while let ([.., p1], [.., p2]) = (
5052            &old_focus_chain_pipelines[..],
5053            &new_focus_chain_pipelines[..],
5054        ) {
5055            if p1.id != p2.id {
5056                break;
5057            }
5058            old_focus_chain_pipelines.pop();
5059            first_common_pipeline_in_chain = new_focus_chain_pipelines.pop();
5060        }
5061
5062        let mut send_errors = Vec::new();
5063
5064        // > For each entry `entry` in `old chain`, in order, run these
5065        // > substeps: [...]
5066        for &pipeline in old_focus_chain_pipelines.iter() {
5067            if Some(pipeline.id) != initiator_pipeline_id {
5068                let msg = ScriptThreadMessage::UnfocusDocumentAsPartOfFocusingSteps(
5069                    pipeline.id,
5070                    pipeline.focus_sequence,
5071                );
5072                trace!("Sending {:?} to {}", msg, pipeline.id);
5073                if let Err(e) = pipeline.event_loop.send(msg) {
5074                    send_errors.push((pipeline.id, e));
5075                }
5076            } else {
5077                trace!(
5078                    "Not notifying {} - it's the initiator of this focus operation",
5079                    pipeline.id
5080                );
5081            }
5082        }
5083
5084        // > For each entry entry in `new chain`, in reverse order, run these
5085        // > substeps: [...]
5086        let mut child_browsing_context_id = None;
5087        for &pipeline in new_focus_chain_pipelines.iter().rev() {
5088            // Don't send a message to the browsing context that initiated this
5089            // focus operation. It already knows that it has gotten focus.
5090            if Some(pipeline.id) != initiator_pipeline_id &&
5091                let Err(error) = pipeline.event_loop.send(
5092                    ScriptThreadMessage::FocusDocumentAsPartOfFocusingSteps(
5093                        pipeline.id,
5094                        pipeline.focus_sequence,
5095                        child_browsing_context_id,
5096                    ),
5097                )
5098            {
5099                send_errors.push((pipeline.id, error));
5100            }
5101            child_browsing_context_id = Some(pipeline.browsing_context_id);
5102        }
5103
5104        if let Some(pipeline) = first_common_pipeline_in_chain &&
5105            Some(pipeline.id) != initiator_pipeline_id &&
5106            let Err(error) = pipeline.event_loop.send(
5107                ScriptThreadMessage::FocusDocumentAsPartOfFocusingSteps(
5108                    pipeline.id,
5109                    pipeline.focus_sequence,
5110                    child_browsing_context_id,
5111                ),
5112            )
5113        {
5114            send_errors.push((pipeline.id, error));
5115        }
5116
5117        for (pipeline_id, error) in send_errors {
5118            self.handle_send_error(pipeline_id, error);
5119        }
5120    }
5121
5122    #[servo_tracing::instrument(skip_all)]
5123    fn handle_remove_iframe_msg(
5124        &mut self,
5125        webview_id: WebViewId,
5126        browsing_context_id: BrowsingContextId,
5127    ) -> Vec<PipelineId> {
5128        let result = self
5129            .all_descendant_browsing_contexts_iter(browsing_context_id)
5130            .flat_map(|browsing_context| browsing_context.pipelines.iter().cloned())
5131            .collect();
5132        self.close_browsing_context(webview_id, browsing_context_id, ExitPipelineMode::Normal);
5133        result
5134    }
5135
5136    #[servo_tracing::instrument(skip_all)]
5137    fn handle_create_canvas_paint_thread_msg(
5138        &mut self,
5139        size: UntypedSize2D<u64>,
5140        response_sender: GenericSender<Option<(GenericSender<CanvasMsg>, CanvasId)>>,
5141    ) {
5142        let (canvas_data_sender, canvas_data_receiver) = unbounded();
5143        let (canvas_sender, canvas_ipc_sender) = self
5144            .canvas
5145            .get_or_init(|| self.create_canvas_paint_thread());
5146
5147        let response = if let Err(e) = canvas_sender.send(ConstellationCanvasMsg::Create {
5148            sender: canvas_data_sender,
5149            size,
5150        }) {
5151            warn!("Create canvas paint thread failed ({})", e);
5152            None
5153        } else {
5154            match canvas_data_receiver.recv() {
5155                Ok(Some(canvas_id)) => Some((canvas_ipc_sender.clone(), canvas_id)),
5156                Ok(None) => None,
5157                Err(e) => {
5158                    warn!("Create canvas paint thread id response failed ({})", e);
5159                    None
5160                },
5161            }
5162        };
5163        if let Err(e) = response_sender.send(response) {
5164            warn!("Create canvas paint thread response failed ({})", e);
5165        }
5166    }
5167
5168    #[servo_tracing::instrument(skip_all)]
5169    fn handle_webdriver_msg(&mut self, msg: WebDriverCommandMsg) {
5170        // Find the script channel for the given parent pipeline,
5171        // and pass the event to that script thread.
5172        match msg {
5173            WebDriverCommandMsg::IsBrowsingContextOpen(browsing_context_id, response_sender) => {
5174                let is_open = self.browsing_contexts.contains_key(&browsing_context_id);
5175                let _ = response_sender.send(is_open);
5176            },
5177            WebDriverCommandMsg::FocusBrowsingContext(browsing_context_id) => {
5178                self.handle_focus_remote_browsing_context(
5179                    browsing_context_id,
5180                    RemoteFocusOperation::Viewport,
5181                );
5182            },
5183            // TODO: This should use the ScriptThreadMessage::EvaluateJavaScript command
5184            WebDriverCommandMsg::ScriptCommand(browsing_context_id, cmd) => {
5185                let pipeline_id = if let Some(browsing_context) =
5186                    self.browsing_contexts.get(&browsing_context_id)
5187                {
5188                    browsing_context.pipeline_id
5189                } else {
5190                    return warn!("{}: Browsing context is not ready", browsing_context_id);
5191                };
5192
5193                match &cmd {
5194                    WebDriverScriptCommand::AddLoadStatusSender(_, sender) => {
5195                        self.webdriver_load_status_sender = Some((sender.clone(), pipeline_id));
5196                    },
5197                    WebDriverScriptCommand::RemoveLoadStatusSender(_) => {
5198                        self.webdriver_load_status_sender = None;
5199                    },
5200                    _ => {},
5201                };
5202
5203                let control_msg = ScriptThreadMessage::WebDriverScriptCommand(pipeline_id, cmd);
5204                self.send_message_to_pipeline(
5205                    pipeline_id,
5206                    control_msg,
5207                    "ScriptCommand after closure",
5208                );
5209            },
5210            WebDriverCommandMsg::CloseWebView(..) |
5211            WebDriverCommandMsg::NewWindow(..) |
5212            WebDriverCommandMsg::SelectWebViewForInteraction(..) |
5213            WebDriverCommandMsg::IsWebViewOpen(..) |
5214            WebDriverCommandMsg::GetWindowRect(..) |
5215            WebDriverCommandMsg::GetViewportSize(..) |
5216            WebDriverCommandMsg::SetWindowRect(..) |
5217            WebDriverCommandMsg::MaximizeWebView(..) |
5218            WebDriverCommandMsg::LoadUrl(..) |
5219            WebDriverCommandMsg::Refresh(..) |
5220            WebDriverCommandMsg::InputEvent(..) |
5221            WebDriverCommandMsg::TakeScreenshot(..) => {
5222                unreachable!("This command should be send directly to the embedder.");
5223            },
5224            _ => {
5225                warn!("Unhandled WebDriver command: {:?}", msg);
5226            },
5227        }
5228    }
5229
5230    #[servo_tracing::instrument(skip_all)]
5231    fn set_webview_hidden(&mut self, webview_id: WebViewId, hidden: bool) {
5232        if self
5233            .webviews
5234            .get_mut(&webview_id)
5235            .is_none_or(|webview| !webview.set_hidden(hidden))
5236        {
5237            return;
5238        }
5239
5240        for pipeline in self.pipelines.values() {
5241            if pipeline.webview_id == webview_id {
5242                pipeline.send_throttle_messages(hidden);
5243            }
5244        }
5245    }
5246
5247    #[servo_tracing::instrument(skip_all)]
5248    fn notify_history_changed(&self, webview_id: WebViewId) {
5249        // Send a flat projection of the history to embedder.
5250        // The final vector is a concatenation of the URLs of the past
5251        // entries, the current entry and the future entries.
5252        // URLs of inner frames are ignored and replaced with the URL
5253        // of the parent.
5254
5255        let session_history = match self.webviews.get(&webview_id) {
5256            Some(webview) => &webview.session_history,
5257            None => {
5258                return warn!(
5259                    "{}: Session history does not exist for browsing context",
5260                    webview_id
5261                );
5262            },
5263        };
5264
5265        let browsing_context_id = BrowsingContextId::from(webview_id);
5266        let Some(browsing_context) = self.browsing_contexts.get(&browsing_context_id) else {
5267            return warn!("notify_history_changed error after top-level browsing context closed.");
5268        };
5269
5270        let current_url = match self.pipelines.get(&browsing_context.pipeline_id) {
5271            Some(pipeline) => pipeline.url.clone(),
5272            None => {
5273                return warn!("{}: Refresh after closure", browsing_context.pipeline_id);
5274            },
5275        };
5276
5277        // If URL was ignored, use the URL of the previous SessionHistoryEntry, which
5278        // is the URL of the parent browsing context.
5279        let resolve_url_future =
5280            |previous_url: &mut ServoUrl, diff: &SessionHistoryDiff| match *diff {
5281                SessionHistoryDiff::BrowsingContext {
5282                    browsing_context_id,
5283                    ref new_reloader,
5284                    ..
5285                } => {
5286                    if browsing_context_id == webview_id {
5287                        let url = match *new_reloader {
5288                            NeedsToReload::No(pipeline_id) => {
5289                                match self.pipelines.get(&pipeline_id) {
5290                                    Some(pipeline) => pipeline.url.clone(),
5291                                    None => previous_url.clone(),
5292                                }
5293                            },
5294                            NeedsToReload::Yes(_, ref load_data) => load_data.url.clone(),
5295                        };
5296                        *previous_url = url.clone();
5297                        Some(url)
5298                    } else {
5299                        Some(previous_url.clone())
5300                    }
5301                },
5302                SessionHistoryDiff::Hash { ref new_url, .. } => {
5303                    *previous_url = new_url.clone();
5304                    Some(new_url.clone())
5305                },
5306                _ => Some(previous_url.clone()),
5307            };
5308
5309        let resolve_url_past = |previous_url: &mut ServoUrl, diff: &SessionHistoryDiff| match *diff
5310        {
5311            SessionHistoryDiff::BrowsingContext {
5312                browsing_context_id,
5313                ref old_reloader,
5314                ..
5315            } => {
5316                if browsing_context_id == webview_id {
5317                    let url = match *old_reloader {
5318                        NeedsToReload::No(pipeline_id) => match self.pipelines.get(&pipeline_id) {
5319                            Some(pipeline) => pipeline.url.clone(),
5320                            None => previous_url.clone(),
5321                        },
5322                        NeedsToReload::Yes(_, ref load_data) => load_data.url.clone(),
5323                    };
5324                    *previous_url = url.clone();
5325                    Some(url)
5326                } else {
5327                    Some(previous_url.clone())
5328                }
5329            },
5330            SessionHistoryDiff::Hash { ref old_url, .. } => {
5331                *previous_url = old_url.clone();
5332                Some(old_url.clone())
5333            },
5334            _ => Some(previous_url.clone()),
5335        };
5336
5337        let mut entries: Vec<ServoUrl> = session_history
5338            .past
5339            .iter()
5340            .rev()
5341            .scan(current_url.clone(), &resolve_url_past)
5342            .collect();
5343
5344        entries.reverse();
5345
5346        let current_index = entries.len();
5347
5348        entries.push(current_url.clone());
5349
5350        entries.extend(
5351            session_history
5352                .future
5353                .iter()
5354                .rev()
5355                .scan(current_url, &resolve_url_future),
5356        );
5357        self.constellation_to_embedder_proxy
5358            .send(ConstellationToEmbedderMsg::HistoryChanged(
5359                webview_id,
5360                entries,
5361                current_index,
5362            ));
5363    }
5364
5365    #[servo_tracing::instrument(skip_all)]
5366    fn change_session_history(&mut self, change: SessionHistoryChange) {
5367        debug!(
5368            "{}: Setting to {}",
5369            change.browsing_context_id, change.new_pipeline_id
5370        );
5371
5372        // If the currently focused browsing context is a child of the browsing
5373        // context in which the page is being loaded, then update the focused
5374        // browsing context to be the one where the page is being loaded.
5375        if self.focused_browsing_context_is_descendant_of(&change) &&
5376            let Some(webview) = self.webviews.get_mut(&change.webview_id)
5377        {
5378            webview.focused_browsing_context_id = change.browsing_context_id;
5379        }
5380
5381        let (old_pipeline_id, webview_id) =
5382            match self.browsing_contexts.get_mut(&change.browsing_context_id) {
5383                Some(browsing_context) => {
5384                    debug!("Adding pipeline to existing browsing context.");
5385                    let old_pipeline_id = browsing_context.pipeline_id;
5386                    browsing_context.pipelines.insert(change.new_pipeline_id);
5387                    browsing_context.update_current_entry(change.new_pipeline_id);
5388                    (Some(old_pipeline_id), Some(browsing_context.webview_id))
5389                },
5390                None => {
5391                    debug!("Adding pipeline to new browsing context.");
5392                    (None, None)
5393                },
5394            };
5395
5396        if let Some(old_pipeline_id) = old_pipeline_id {
5397            self.unload_document(old_pipeline_id);
5398        }
5399
5400        let Some(webview) = self.webviews.get_mut(&change.webview_id) else {
5401            return warn!("Ignoring history change in non-existent WebView ({webview_id:?}).");
5402        };
5403
5404        match old_pipeline_id {
5405            None => {
5406                let Some(new_context_info) = change.new_browsing_context_info else {
5407                    return warn!(
5408                        "{}: No NewBrowsingContextInfo for browsing context",
5409                        change.browsing_context_id,
5410                    );
5411                };
5412                self.new_browsing_context(
5413                    change.browsing_context_id,
5414                    change.webview_id,
5415                    change.new_pipeline_id,
5416                    new_context_info.parent_pipeline_id,
5417                    change.viewport_details,
5418                    new_context_info.is_private,
5419                    new_context_info.inherited_secure_context,
5420                );
5421                self.update_activity(change.new_pipeline_id);
5422            },
5423            Some(old_pipeline_id) => {
5424                // Deactivate the old pipeline, and activate the new one.
5425                let (pipelines_to_close, states_to_close) = if let Some(replace_reloader) =
5426                    change.replace
5427                {
5428                    webview.session_history.replace_reloader(
5429                        replace_reloader.clone(),
5430                        NeedsToReload::No(change.new_pipeline_id),
5431                    );
5432
5433                    match replace_reloader {
5434                        NeedsToReload::No(pipeline_id) => (Some(vec![pipeline_id]), None),
5435                        NeedsToReload::Yes(..) => (None, None),
5436                    }
5437                } else {
5438                    let diff = SessionHistoryDiff::BrowsingContext {
5439                        browsing_context_id: change.browsing_context_id,
5440                        new_reloader: NeedsToReload::No(change.new_pipeline_id),
5441                        old_reloader: NeedsToReload::No(old_pipeline_id),
5442                    };
5443
5444                    let mut pipelines_to_close = vec![];
5445                    let mut states_to_close = FxHashMap::default();
5446
5447                    let diffs_to_close = webview.session_history.push_diff(diff);
5448                    for diff in diffs_to_close {
5449                        match diff {
5450                            SessionHistoryDiff::BrowsingContext { new_reloader, .. } => {
5451                                if let Some(pipeline_id) = new_reloader.alive_pipeline_id() {
5452                                    pipelines_to_close.push(pipeline_id);
5453                                }
5454                            },
5455                            SessionHistoryDiff::Pipeline {
5456                                pipeline_reloader,
5457                                new_history_state_id,
5458                                ..
5459                            } => {
5460                                if let Some(pipeline_id) = pipeline_reloader.alive_pipeline_id() {
5461                                    let states =
5462                                        states_to_close.entry(pipeline_id).or_insert(Vec::new());
5463                                    states.push(new_history_state_id);
5464                                }
5465                            },
5466                            _ => {},
5467                        }
5468                    }
5469
5470                    (Some(pipelines_to_close), Some(states_to_close))
5471                };
5472
5473                self.update_activity(old_pipeline_id);
5474                self.update_activity(change.new_pipeline_id);
5475
5476                if let Some(states_to_close) = states_to_close {
5477                    for (pipeline_id, states) in states_to_close {
5478                        let msg = ScriptThreadMessage::RemoveHistoryStates(pipeline_id, states);
5479                        if !self.send_message_to_pipeline(
5480                            pipeline_id,
5481                            msg,
5482                            "Removed history states after closure",
5483                        ) {
5484                            return;
5485                        }
5486                    }
5487                }
5488
5489                if let Some(pipelines_to_close) = pipelines_to_close {
5490                    for pipeline_id in pipelines_to_close {
5491                        self.close_pipeline(
5492                            pipeline_id,
5493                            DiscardBrowsingContext::No,
5494                            ExitPipelineMode::Normal,
5495                        );
5496                    }
5497                }
5498            },
5499        }
5500
5501        if let Some(webview_id) = webview_id {
5502            self.trim_history(webview_id);
5503        }
5504
5505        self.notify_focus_state(change.new_pipeline_id);
5506
5507        self.notify_history_changed(change.webview_id);
5508        self.set_frame_tree_for_webview(change.webview_id);
5509    }
5510
5511    /// Update the focus state of the specified pipeline that recently became
5512    /// active (thus doesn't have a focused container element) and may have
5513    /// out-dated information.
5514    fn notify_focus_state(&mut self, pipeline_id: PipelineId) {
5515        let Some(pipeline) = self.pipelines.get(&pipeline_id) else {
5516            return warn!("Pipeline {pipeline_id} is closed");
5517        };
5518
5519        let is_focused = match self.webviews.get(&pipeline.webview_id) {
5520            Some(webview) => webview.focused_browsing_context_id == pipeline.browsing_context_id,
5521            None => {
5522                return warn!(
5523                    "Pipeline {pipeline_id}'s top-level browsing context {} is closed",
5524                    pipeline.webview_id
5525                );
5526            },
5527        };
5528
5529        // If the browsing context is focused, focus the document
5530        let msg = if is_focused {
5531            ScriptThreadMessage::FocusDocumentAsPartOfFocusingSteps(
5532                pipeline_id,
5533                pipeline.focus_sequence,
5534                None,
5535            )
5536        } else {
5537            ScriptThreadMessage::UnfocusDocumentAsPartOfFocusingSteps(
5538                pipeline_id,
5539                pipeline.focus_sequence,
5540            )
5541        };
5542        if let Err(e) = pipeline.event_loop.send(msg) {
5543            self.handle_send_error(pipeline_id, e);
5544        }
5545    }
5546
5547    #[servo_tracing::instrument(skip_all)]
5548    fn focused_browsing_context_is_descendant_of(&self, change: &SessionHistoryChange) -> bool {
5549        let focused_browsing_context_id = self
5550            .webviews
5551            .get(&change.webview_id)
5552            .map(|webview| webview.focused_browsing_context_id);
5553        focused_browsing_context_id.is_some_and(|focused_browsing_context_id| {
5554            focused_browsing_context_id == change.browsing_context_id ||
5555                self.fully_active_descendant_browsing_contexts_iter(change.browsing_context_id)
5556                    .any(|nested_ctx| nested_ctx.id == focused_browsing_context_id)
5557        })
5558    }
5559
5560    #[servo_tracing::instrument(skip_all)]
5561    fn trim_history(&mut self, webview_id: WebViewId) {
5562        let pipelines_to_evict = {
5563            let Some(webview) = self.webviews.get_mut(&webview_id) else {
5564                return warn!("Not trimming history for non-existent WebView ({webview_id:}");
5565            };
5566            let history_length = pref!(session_history_max_length) as usize;
5567
5568            // The past is stored with older entries at the front.
5569            // We reverse the iter so that newer entries are at the front and then
5570            // skip _n_ entries and evict the remaining entries.
5571            let past_trim = webview
5572                .session_history
5573                .past
5574                .iter()
5575                .rev()
5576                .map(|diff| diff.alive_old_pipeline())
5577                .skip(history_length)
5578                .flatten();
5579
5580            // The future is stored with oldest entries front, so we must
5581            // reverse the iterator like we do for the `past`.
5582            let future_trim = webview
5583                .session_history
5584                .future
5585                .iter()
5586                .rev()
5587                .map(|diff| diff.alive_new_pipeline())
5588                .skip(history_length)
5589                .flatten();
5590
5591            past_trim.chain(future_trim).collect::<Vec<_>>()
5592        };
5593
5594        let mut dead_pipelines = vec![];
5595        for evicted_id in pipelines_to_evict {
5596            let Some(load_data) = self.refresh_load_data(evicted_id) else {
5597                continue;
5598            };
5599
5600            dead_pipelines.push((evicted_id, NeedsToReload::Yes(evicted_id, load_data)));
5601            self.close_pipeline(
5602                evicted_id,
5603                DiscardBrowsingContext::No,
5604                ExitPipelineMode::Normal,
5605            );
5606        }
5607
5608        if let Some(webview) = self.webviews.get_mut(&webview_id) {
5609            for (alive_id, dead) in dead_pipelines {
5610                webview
5611                    .session_history
5612                    .replace_reloader(NeedsToReload::No(alive_id), dead);
5613            }
5614        };
5615    }
5616
5617    #[servo_tracing::instrument(skip_all)]
5618    fn handle_activate_document_msg(&mut self, webview_id: WebViewId, pipeline_id: PipelineId) {
5619        debug!("{}: Document ready to activate", pipeline_id);
5620
5621        // Find the pending change whose new pipeline id is pipeline_id. If it is found, remove
5622        // it from the pending changes, and make it the active document of its frame.
5623        let Some(change) = self
5624            .webviews
5625            .get_mut(&webview_id)
5626            .and_then(|webview| webview.remove_pending_change_for_pipeline(pipeline_id))
5627        else {
5628            return;
5629        };
5630
5631        // Notify the parent (if there is one).
5632        let parent_pipeline_id = match change.new_browsing_context_info {
5633            // This will be a new browsing context.
5634            Some(ref info) => info.parent_pipeline_id,
5635            // This is an existing browsing context.
5636            None => match self.browsing_contexts.get(&change.browsing_context_id) {
5637                Some(ctx) => ctx.parent_pipeline_id,
5638                None => {
5639                    return warn!(
5640                        "{}: Activated document after closure of {}",
5641                        change.new_pipeline_id, change.browsing_context_id,
5642                    );
5643                },
5644            },
5645        };
5646        if let Some(parent_pipeline_id) = parent_pipeline_id &&
5647            let Some(parent_pipeline) = self.pipelines.get(&parent_pipeline_id)
5648        {
5649            let msg = ScriptThreadMessage::UpdatePipelineId(
5650                parent_pipeline_id,
5651                change.browsing_context_id,
5652                webview_id,
5653                pipeline_id,
5654                UpdatePipelineIdReason::Navigation,
5655            );
5656            let _ = parent_pipeline.event_loop.send(msg);
5657        }
5658
5659        self.change_session_history(change);
5660
5661        if let Some(webview) = self.webviews.get_mut(&webview_id) {
5662            webview.send_screenshot_readiness_requests_to_pipelines(
5663                &self.browsing_contexts,
5664                &self.pipelines,
5665            );
5666        }
5667    }
5668
5669    /// Called when the window is resized.
5670    #[servo_tracing::instrument(skip_all)]
5671    fn handle_change_viewport_details_msg(
5672        &mut self,
5673        webview_id: WebViewId,
5674        new_viewport_details: ViewportDetails,
5675        size_type: WindowSizeType,
5676    ) {
5677        debug!(
5678            "handle_change_viewport_details_msg: {:?}",
5679            new_viewport_details
5680        );
5681
5682        let browsing_context_id = BrowsingContextId::from(webview_id);
5683        self.resize_browsing_context(
5684            webview_id,
5685            new_viewport_details,
5686            size_type,
5687            browsing_context_id,
5688        );
5689    }
5690
5691    /// Called when the window exits from fullscreen mode
5692    #[servo_tracing::instrument(skip_all)]
5693    fn handle_exit_fullscreen_msg(&mut self, webview_id: WebViewId) {
5694        let browsing_context_id = BrowsingContextId::from(webview_id);
5695        self.switch_fullscreen_mode(browsing_context_id);
5696    }
5697
5698    /// Get the current activity of a pipeline.
5699    #[servo_tracing::instrument(skip_all)]
5700    fn get_activity(&self, pipeline_id: PipelineId) -> DocumentActivity {
5701        let mut ancestor_id = pipeline_id;
5702        loop {
5703            if let Some(ancestor) = self.pipelines.get(&ancestor_id) &&
5704                let Some(browsing_context) =
5705                    self.browsing_contexts.get(&ancestor.browsing_context_id) &&
5706                browsing_context.pipeline_id == ancestor_id
5707            {
5708                if let Some(parent_pipeline_id) = browsing_context.parent_pipeline_id {
5709                    ancestor_id = parent_pipeline_id;
5710                    continue;
5711                } else {
5712                    return DocumentActivity::FullyActive;
5713                }
5714            }
5715            if pipeline_id == ancestor_id {
5716                return DocumentActivity::Inactive;
5717            } else {
5718                return DocumentActivity::Active;
5719            }
5720        }
5721    }
5722
5723    /// Set the current activity of a pipeline.
5724    #[servo_tracing::instrument(skip_all)]
5725    fn set_activity(&self, pipeline_id: PipelineId, activity: DocumentActivity) {
5726        debug!("{}: Setting activity to {:?}", pipeline_id, activity);
5727        if let Some(pipeline) = self.pipelines.get(&pipeline_id) {
5728            pipeline.set_activity(activity);
5729            let child_activity = if activity == DocumentActivity::Inactive {
5730                DocumentActivity::Active
5731            } else {
5732                activity
5733            };
5734            for child_id in &pipeline.children {
5735                if let Some(child) = self.browsing_contexts.get(child_id) {
5736                    self.set_activity(child.pipeline_id, child_activity);
5737                }
5738            }
5739        }
5740    }
5741
5742    /// Update the current activity of a pipeline.
5743    #[servo_tracing::instrument(skip_all)]
5744    fn update_activity(&self, pipeline_id: PipelineId) {
5745        self.set_activity(pipeline_id, self.get_activity(pipeline_id));
5746    }
5747
5748    /// Handle updating the size of a browsing context.
5749    /// This notifies every pipeline in the context of the new size.
5750    #[servo_tracing::instrument(skip_all)]
5751    fn resize_browsing_context(
5752        &mut self,
5753        webview_id: WebViewId,
5754        new_viewport_details: ViewportDetails,
5755        size_type: WindowSizeType,
5756        browsing_context_id: BrowsingContextId,
5757    ) {
5758        let Some(webview) = self.webviews.get_mut(&webview_id) else {
5759            warn!("Resizing browsing context for unkown WebView: {webview_id:?}");
5760            return;
5761        };
5762
5763        if let Some(browsing_context) = self.browsing_contexts.get_mut(&browsing_context_id) {
5764            browsing_context.viewport_details = new_viewport_details;
5765            // Send Resize (or ResizeInactive) messages to each pipeline in the frame tree.
5766            let pipeline_id = browsing_context.pipeline_id;
5767            let Some(pipeline) = self.pipelines.get(&pipeline_id) else {
5768                return warn!("{}: Resized after closing", pipeline_id);
5769            };
5770            let _ = pipeline.event_loop.send(ScriptThreadMessage::Resize(
5771                pipeline.id,
5772                new_viewport_details,
5773                size_type,
5774            ));
5775            let pipeline_ids = browsing_context
5776                .pipelines
5777                .iter()
5778                .filter(|pipeline_id| **pipeline_id != pipeline.id);
5779            for id in pipeline_ids {
5780                if let Some(pipeline) = self.pipelines.get(id) {
5781                    let _ = pipeline
5782                        .event_loop
5783                        .send(ScriptThreadMessage::ResizeInactive(
5784                            pipeline.id,
5785                            new_viewport_details,
5786                        ));
5787                }
5788            }
5789        } else {
5790            webview.add_viewport_details(browsing_context_id, new_viewport_details);
5791        }
5792
5793        // Send resize message to any pending pipelines that aren't loaded yet.
5794        for change in &webview.pending_changes {
5795            let pipeline_id = change.new_pipeline_id;
5796            let Some(pipeline) = self.pipelines.get(&pipeline_id) else {
5797                warn!("Pending pipeline is closed: {pipeline_id}");
5798                continue;
5799            };
5800            if pipeline.browsing_context_id == browsing_context_id {
5801                let _ = pipeline.event_loop.send(ScriptThreadMessage::Resize(
5802                    pipeline.id,
5803                    new_viewport_details,
5804                    size_type,
5805                ));
5806            }
5807        }
5808    }
5809
5810    /// Handle theme change events from the embedder and forward them to all appropriate `ScriptThread`s.
5811    #[servo_tracing::instrument(skip_all)]
5812    fn handle_theme_change(&mut self, webview_id: WebViewId, theme: Theme) {
5813        let Some(webview) = self.webviews.get_mut(&webview_id) else {
5814            warn!("Received theme change request for unknown WebViewId: {webview_id:?}");
5815            return;
5816        };
5817        if !webview.set_theme(theme) {
5818            return;
5819        }
5820        let state = webview.state();
5821        for event_loop in self.event_loops() {
5822            if let Err(error) =
5823                event_loop.send(ScriptThreadMessage::UpdateWebViewState(state.clone()))
5824            {
5825                warn!(
5826                    "Sending to closed event loop ({:?}): {error}",
5827                    event_loop.id()
5828                );
5829            }
5830        }
5831    }
5832
5833    // Handle switching from fullscreen mode
5834    #[servo_tracing::instrument(skip_all)]
5835    fn switch_fullscreen_mode(&mut self, browsing_context_id: BrowsingContextId) {
5836        if let Some(browsing_context) = self.browsing_contexts.get(&browsing_context_id) {
5837            let pipeline_id = browsing_context.pipeline_id;
5838            let Some(pipeline) = self.pipelines.get(&pipeline_id) else {
5839                return warn!("{pipeline_id}: Switched from fullscreen mode after closing",);
5840            };
5841            let _ = pipeline
5842                .event_loop
5843                .send(ScriptThreadMessage::ExitFullScreen(pipeline.id));
5844        }
5845    }
5846
5847    // Close and return the browsing context with the given id (and its children), if it exists.
5848    #[servo_tracing::instrument(skip_all)]
5849    fn close_browsing_context(
5850        &mut self,
5851        webview_id: WebViewId,
5852        browsing_context_id: BrowsingContextId,
5853        exit_mode: ExitPipelineMode,
5854    ) -> Option<BrowsingContext> {
5855        debug!("{}: Closing", browsing_context_id);
5856
5857        self.close_browsing_context_children(
5858            webview_id,
5859            browsing_context_id,
5860            DiscardBrowsingContext::Yes,
5861            exit_mode,
5862        );
5863
5864        let Some(webview) = self.webviews.get_mut(&webview_id) else {
5865            warn!("Closing BrowsingContext in unknown WebView: {webview_id:?}");
5866            return self.browsing_contexts.remove(&browsing_context_id);
5867        };
5868
5869        webview.close_browsing_context(browsing_context_id);
5870
5871        let Some(browsing_context) = self.browsing_contexts.remove(&browsing_context_id) else {
5872            warn!("fn close_browsing_context: {browsing_context_id}: Closing twice");
5873            return None;
5874        };
5875
5876        if let Some(parent_pipeline_id) = browsing_context.parent_pipeline_id {
5877            match self.pipelines.get_mut(&parent_pipeline_id) {
5878                None => {
5879                    warn!("{parent_pipeline_id}: Child closed after parent");
5880                },
5881                Some(parent_pipeline) => {
5882                    parent_pipeline.remove_child(browsing_context_id);
5883
5884                    // If `browsing_context_id` has focus, focus the parent browsing context
5885                    if webview.focused_browsing_context_id == browsing_context_id {
5886                        trace!(
5887                            "About-to-be-closed browsing context {} is currently focused, so \
5888                                focusing its parent {}",
5889                            browsing_context_id, parent_pipeline.browsing_context_id
5890                        );
5891                        webview.focused_browsing_context_id = parent_pipeline.browsing_context_id;
5892                    }
5893                },
5894            };
5895        }
5896
5897        debug!("{}: Closed", browsing_context_id);
5898        Some(browsing_context)
5899    }
5900
5901    // Close the children of a browsing context
5902    #[servo_tracing::instrument(skip_all)]
5903    fn close_browsing_context_children(
5904        &mut self,
5905        webview_id: WebViewId,
5906        browsing_context_id: BrowsingContextId,
5907        dbc: DiscardBrowsingContext,
5908        exit_mode: ExitPipelineMode,
5909    ) {
5910        debug!("{}: Closing browsing context children", browsing_context_id);
5911
5912        // Store information about the pipelines to be closed. Then close the
5913        // pipelines, before removing ourself from the browsing_contexts hash map. This
5914        // ordering is vital - so that if close_pipeline() ends up closing
5915        // any child browsing contexts, they can be removed from the parent browsing context correctly.
5916        let mut pipelines_to_close: Vec<_> = {
5917            let Some(webview) = self.webviews.get(&webview_id) else {
5918                return warn!(
5919                    "Couldnot find WebView for closing browsing context id: {webview_id:?}"
5920                );
5921            };
5922            webview
5923                .pending_changes
5924                .iter()
5925                .filter(|change| change.browsing_context_id == browsing_context_id)
5926                .map(|change| change.new_pipeline_id)
5927                .collect()
5928        };
5929
5930        if let Some(browsing_context) = self.browsing_contexts.get(&browsing_context_id) {
5931            pipelines_to_close.extend(&browsing_context.pipelines)
5932        }
5933
5934        for pipeline_id in pipelines_to_close {
5935            self.close_pipeline(pipeline_id, dbc, exit_mode);
5936        }
5937
5938        debug!("{}: Closed browsing context children", browsing_context_id);
5939    }
5940
5941    /// Returns the [LoadData] associated with the given pipeline if it exists,
5942    /// containing the most recent URL associated with the given pipeline.
5943    fn refresh_load_data(&self, pipeline_id: PipelineId) -> Option<LoadData> {
5944        self.pipelines.get(&pipeline_id).map(|pipeline| {
5945            let mut load_data = pipeline.load_data.clone();
5946            load_data.url = pipeline.url.clone();
5947            load_data
5948        })
5949    }
5950
5951    // Discard the pipeline for a given document, udpdate the joint session history.
5952    #[servo_tracing::instrument(skip_all)]
5953    fn handle_discard_document(&mut self, webview_id: WebViewId, pipeline_id: PipelineId) {
5954        let Some(load_data) = self.refresh_load_data(pipeline_id) else {
5955            return warn!("{}: Discarding closed pipeline", pipeline_id);
5956        };
5957        match self.webviews.get_mut(&webview_id) {
5958            Some(webview) => {
5959                webview.session_history.replace_reloader(
5960                    NeedsToReload::No(pipeline_id),
5961                    NeedsToReload::Yes(pipeline_id, load_data),
5962                );
5963            },
5964            None => {
5965                return warn!("{pipeline_id}: Discarding after closure of {webview_id}",);
5966            },
5967        };
5968        self.close_pipeline(
5969            pipeline_id,
5970            DiscardBrowsingContext::No,
5971            ExitPipelineMode::Normal,
5972        );
5973    }
5974
5975    /// Send a message to script requesting the document associated with this pipeline runs the 'unload' algorithm.
5976    #[servo_tracing::instrument(skip_all)]
5977    fn unload_document(&mut self, pipeline_id: PipelineId) {
5978        if let Some(pipeline) = self.pipelines.get_mut(&pipeline_id) {
5979            pipeline.set_has_active_document(false);
5980            let _ = pipeline
5981                .event_loop
5982                .send(ScriptThreadMessage::UnloadDocument(pipeline_id));
5983        }
5984    }
5985
5986    // Close all pipelines at and beneath a given browsing context
5987    #[servo_tracing::instrument(skip_all)]
5988    fn close_pipeline(
5989        &mut self,
5990        pipeline_id: PipelineId,
5991        dbc: DiscardBrowsingContext,
5992        exit_mode: ExitPipelineMode,
5993    ) {
5994        debug!("{}: Closing", pipeline_id);
5995
5996        // Sever connection to browsing context
5997        let Some((webview_id, browsing_context_id)) = self
5998            .pipelines
5999            .get(&pipeline_id)
6000            .map(|pipeline| (pipeline.webview_id, pipeline.browsing_context_id))
6001        else {
6002            return warn!("Tried closing unknown pipeline: {pipeline_id:?}");
6003        };
6004
6005        if let Some(browsing_context) = self.browsing_contexts.get_mut(&browsing_context_id) {
6006            browsing_context.pipelines.remove(&pipeline_id);
6007        }
6008
6009        // Store information about the browsing contexts to be closed. Then close the
6010        // browsing contexts, before removing ourself from the pipelines hash map. This
6011        // ordering is vital - so that if close_browsing_context() ends up closing
6012        // any child pipelines, they can be removed from the parent pipeline correctly.
6013        let browsing_contexts_to_close = {
6014            let mut browsing_contexts_to_close = vec![];
6015
6016            if let Some(pipeline) = self.pipelines.get(&pipeline_id) {
6017                browsing_contexts_to_close.extend_from_slice(&pipeline.children);
6018            }
6019
6020            browsing_contexts_to_close
6021        };
6022
6023        // Remove any child browsing contexts
6024        for child_browsing_context in &browsing_contexts_to_close {
6025            self.close_browsing_context(webview_id, *child_browsing_context, exit_mode);
6026        }
6027
6028        // Note, we don't remove the pipeline now, we wait for the message to come back from
6029        // the pipeline.
6030        let Some(pipeline) = self.pipelines.get(&pipeline_id) else {
6031            return warn!("fn close_pipeline: {pipeline_id}: Closing twice");
6032        };
6033
6034        // Inform script and paint that this pipeline has exited.
6035        if !pipeline.send_exit_message_to_script(dbc) {
6036            // The pipeline is now unreachable, so we should consider it gone. We should
6037            // not try to send any subsequent messages to this pipeline. all() here ensures
6038            // that `Paint` cleans up the pipeline display immediately without waiting for
6039            // confirmation from the event loop.
6040            self.handle_pipeline_exited(pipeline_id, PipelineExitSource::all());
6041        }
6042
6043        // Remove this pipeline from pending changes if it hasn't loaded yet.
6044        if let Some(webview) = self.webviews.get_mut(&webview_id) {
6045            webview.remove_pending_change_for_pipeline(pipeline_id);
6046            webview.send_screenshot_readiness_requests_to_pipelines(
6047                &self.browsing_contexts,
6048                &self.pipelines,
6049            );
6050            webview.handle_screenshot_readiness_response(
6051                pipeline_id,
6052                ScreenshotReadinessResponse::NoLongerActive,
6053                &self.paint_proxy,
6054            );
6055        }
6056
6057        debug!("{}: Closed", pipeline_id);
6058    }
6059
6060    // Randomly close a pipeline -if --random-pipeline-closure-probability is set
6061    fn maybe_close_random_pipeline(&mut self) {
6062        match self.random_pipeline_closure {
6063            Some((ref mut rng, probability)) => {
6064                if probability <= rng.random::<f32>() {
6065                    return;
6066                }
6067            },
6068            _ => return,
6069        };
6070        // In order to get repeatability, we sort the pipeline ids.
6071        let mut pipeline_ids: Vec<&PipelineId> = self.pipelines.keys().collect();
6072        pipeline_ids.sort_unstable();
6073        if let Some((ref mut rng, probability)) = self.random_pipeline_closure &&
6074            let Some(pipeline_id) = pipeline_ids.choose(rng) &&
6075            let Some(pipeline) = self.pipelines.get(pipeline_id)
6076        {
6077            if self
6078                .webviews
6079                .values()
6080                .any(|webview| webview.pipeline_is_pending(pipeline.id)) &&
6081                probability <= rng.random::<f32>()
6082            {
6083                // We tend not to close pending pipelines, as that almost always
6084                // results in pipelines being closed early in their lifecycle,
6085                // and not stressing the constellation as much.
6086                // https://github.com/servo/servo/issues/18852
6087                info!("{}: Not closing pending pipeline", pipeline_id);
6088            } else {
6089                // Note that we deliberately do not do any of the tidying up
6090                // associated with closing a pipeline. The constellation should cope!
6091                warn!("{}: Randomly closing pipeline", pipeline_id);
6092                pipeline.send_exit_message_to_script(DiscardBrowsingContext::No);
6093            }
6094        }
6095    }
6096
6097    /// Convert a browsing context to a tree of active pipeline ids, for sending to `Paint`.
6098    #[servo_tracing::instrument(skip_all)]
6099    fn browsing_context_to_sendable(
6100        &self,
6101        browsing_context_id: BrowsingContextId,
6102    ) -> Option<SendableFrameTree> {
6103        self.browsing_contexts
6104            .get(&browsing_context_id)
6105            .and_then(|browsing_context| {
6106                self.pipelines
6107                    .get(&browsing_context.pipeline_id)
6108                    .map(|pipeline| {
6109                        let mut frame_tree = SendableFrameTree {
6110                            pipeline: pipeline.to_sendable(),
6111                            children: vec![],
6112                        };
6113
6114                        for child_browsing_context_id in &pipeline.children {
6115                            if let Some(child) =
6116                                self.browsing_context_to_sendable(*child_browsing_context_id)
6117                            {
6118                                frame_tree.children.push(child);
6119                            }
6120                        }
6121
6122                        frame_tree
6123                    })
6124            })
6125    }
6126
6127    /// Send the frame tree for the given webview to `Paint`.
6128    #[servo_tracing::instrument(skip_all)]
6129    fn set_frame_tree_for_webview(&mut self, webview_id: WebViewId) {
6130        // Note that this function can panic, due to ipc-channel creation failure.
6131        // avoiding this panic would require a mechanism for dealing
6132        // with low-resource scenarios.
6133        let browsing_context_id = BrowsingContextId::from(webview_id);
6134        let Some(frame_tree) = self.browsing_context_to_sendable(browsing_context_id) else {
6135            return;
6136        };
6137
6138        let new_pipeline_id = frame_tree.pipeline.id;
6139
6140        debug!("{}: Sending frame tree", browsing_context_id);
6141        self.paint_proxy
6142            .send(PaintMessage::SetFrameTreeForWebView(webview_id, frame_tree));
6143
6144        let Some(webview) = self.webviews.get_mut(&webview_id) else {
6145            return;
6146        };
6147        if webview.active_top_level_pipeline_id == Some(new_pipeline_id) {
6148            return;
6149        }
6150
6151        let old_pipeline_id = webview.active_top_level_pipeline_id;
6152        let old_epoch = webview.active_top_level_pipeline_epoch;
6153        let new_epoch = old_epoch.next();
6154
6155        let accessibility_active = webview.accessibility_active;
6156
6157        webview.active_top_level_pipeline_id = Some(new_pipeline_id);
6158        webview.active_top_level_pipeline_epoch = new_epoch;
6159
6160        // Deactivate accessibility in the now-inactive top-level document in the WebView.
6161        // This ensures that the document stops sending tree updates, since they will be
6162        // discarded in libservo anyway, and also ensures that when accessibility is
6163        // reactivated, the document sends the whole accessibility tree from scratch.
6164        if let Some(old_pipeline_id) = old_pipeline_id {
6165            self.send_message_to_pipeline(
6166                old_pipeline_id,
6167                ScriptThreadMessage::SetAccessibilityActive(old_pipeline_id, false, old_epoch),
6168                "Set accessibility active after closure",
6169            );
6170        }
6171
6172        // Forward activation to layout for the active top-level document in the WebView.
6173        // There are two sites like this; this is the navigation (or bfcache traversal) site.
6174        self.send_message_to_pipeline(
6175            new_pipeline_id,
6176            ScriptThreadMessage::SetAccessibilityActive(
6177                new_pipeline_id,
6178                accessibility_active,
6179                new_epoch,
6180            ),
6181            "Set accessibility active after closure",
6182        );
6183    }
6184
6185    #[servo_tracing::instrument(skip_all)]
6186    fn handle_media_session_action_msg(&mut self, action: MediaSessionActionType) {
6187        if let Some(media_session_pipeline_id) = self.active_media_session {
6188            self.send_message_to_pipeline(
6189                media_session_pipeline_id,
6190                ScriptThreadMessage::MediaSessionAction(media_session_pipeline_id, action),
6191                "Got media session action request after closure",
6192            );
6193        } else {
6194            error!("Got a media session action but no active media session is registered");
6195        }
6196    }
6197
6198    #[servo_tracing::instrument(skip_all)]
6199    fn handle_set_scroll_states(&self, pipeline_id: PipelineId, scroll_states: ScrollStateUpdate) {
6200        let Some(pipeline) = self.pipelines.get(&pipeline_id) else {
6201            warn!("Discarding scroll offset update for unknown pipeline");
6202            return;
6203        };
6204        if let Err(error) = pipeline
6205            .event_loop
6206            .send(ScriptThreadMessage::SetScrollStates(
6207                pipeline_id,
6208                scroll_states,
6209            ))
6210        {
6211            warn!("Could not send scroll offsets to pipeline: {pipeline_id:?}: {error:?}");
6212        }
6213    }
6214
6215    #[servo_tracing::instrument(skip_all)]
6216    fn handle_paint_metric(&mut self, pipeline_id: PipelineId, event: PaintMetricEvent) {
6217        let Some(pipeline) = self.pipelines.get(&pipeline_id) else {
6218            warn!("Discarding paint metric event for unknown pipeline");
6219            return;
6220        };
6221        if let Err(error) = pipeline
6222            .event_loop
6223            .send(ScriptThreadMessage::PaintMetric(pipeline_id, event))
6224        {
6225            warn!("Could not send paint metric event to pipeline: {pipeline_id:?}: {error:?}");
6226        }
6227    }
6228
6229    fn create_canvas_paint_thread(
6230        &self,
6231    ) -> (Sender<ConstellationCanvasMsg>, GenericSender<CanvasMsg>) {
6232        CanvasPaintThread::start(
6233            self.paint_proxy.cross_process_paint_api.clone(),
6234            self.mem_profiler_chan.clone(),
6235        )
6236    }
6237
6238    fn handle_embedder_control_response(
6239        &self,
6240        id: EmbedderControlId,
6241        response: EmbedderControlResponse,
6242    ) {
6243        let pipeline_id = id.pipeline_id;
6244        let Some(pipeline) = self.pipelines.get(&pipeline_id) else {
6245            warn!("Not sending embedder control response for unknown pipeline {pipeline_id:?}");
6246            return;
6247        };
6248
6249        if let Err(error) = pipeline
6250            .event_loop
6251            .send(ScriptThreadMessage::EmbedderControlResponse(id, response))
6252        {
6253            warn!(
6254                "Could not send embedder control response to pipeline {pipeline_id:?}: {error:?}"
6255            );
6256        }
6257    }
6258
6259    fn handle_update_pinch_zoom_infos(
6260        &self,
6261        pipeline_id: PipelineId,
6262        pinch_zoom_infos: PinchZoomInfos,
6263    ) {
6264        let Some(pipeline) = self.pipelines.get(&pipeline_id) else {
6265            warn!("Discarding pinch zoom update for unknown pipeline");
6266            return;
6267        };
6268        if let Err(error) = pipeline
6269            .event_loop
6270            .send(ScriptThreadMessage::UpdatePinchZoomInfos(
6271                pipeline_id,
6272                pinch_zoom_infos,
6273            ))
6274        {
6275            warn!("Could not send pinch zoom update to pipeline: {pipeline_id:?}: {error:?}");
6276        }
6277    }
6278
6279    #[cfg(feature = "devtools")]
6280    pub(crate) fn script_to_devtools_callback(
6281        &self,
6282    ) -> Option<GenericCallback<ScriptToDevtoolsControlMsg>> {
6283        self.script_to_devtools_callback
6284            .get_or_init(|| {
6285                self.devtools_sender.as_ref().and_then(|devtools_sender| {
6286                    let devtools_sender = devtools_sender.clone();
6287                    let callback = GenericCallback::new(move |message| match message {
6288                        Err(error) => {
6289                            error!("Cast to ScriptToDevtoolsControlMsg failed ({error}).")
6290                        },
6291                        Ok(message) => {
6292                            if let Err(error) =
6293                                devtools_sender.send(DevtoolsControlMsg::FromScript(message))
6294                            {
6295                                warn!("Sending to devtools failed ({error:?})")
6296                            }
6297                        },
6298                    });
6299                    match callback {
6300                        Ok(callback) => Some(callback),
6301                        Err(error) => {
6302                            error!("Could not create Devtools communication channel: {error}");
6303                            None
6304                        },
6305                    }
6306                })
6307            })
6308            .clone()
6309    }
6310}