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