Skip to main content

servo_constellation/
constellation.rs

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