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