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