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