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