Skip to main content

servo_constellation/
constellation.rs

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