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