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