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