servo/components/shared/script/lib.rs
Martin Robinson d3e57a513c
constellation: Pass system theme to new Pipelines (#37132)
Previously, when the theme was set it was only set on currently active
`Window`s. This change makes setting the `Theme` stateful. Now the
`Constellation` tracks what theme is applied to a `WebView` and properly
passes that value to new `Pipeline`s when they are constructed. In
addition, the value is passed to layout when that is constructed as
well.

Testing: this change adds a unit test.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
2025-05-26 12:05:38 +00:00

394 lines
16 KiB
Rust

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! This module contains traits in script used generically in the rest of Servo.
//! The traits are here instead of in script so that these modules won't have
//! to depend on script.
#![deny(missing_docs)]
#![deny(unsafe_code)]
use std::fmt;
use std::sync::Arc;
use background_hang_monitor_api::BackgroundHangMonitorRegister;
use base::cross_process_instant::CrossProcessInstant;
use base::id::{BrowsingContextId, HistoryStateId, PipelineId, PipelineNamespaceId, WebViewId};
#[cfg(feature = "bluetooth")]
use bluetooth_traits::BluetoothRequest;
use canvas_traits::webgl::WebGLPipeline;
use compositing_traits::CrossProcessCompositorApi;
use constellation_traits::{
LoadData, NavigationHistoryBehavior, ScriptToConstellationChan, ScrollState,
StructuredSerializedData, WindowSizeType,
};
use crossbeam_channel::{RecvTimeoutError, Sender};
use devtools_traits::ScriptToDevtoolsControlMsg;
use embedder_traits::user_content_manager::UserContentManager;
use embedder_traits::{
CompositorHitTestResult, FocusSequenceNumber, InputEvent, JavaScriptEvaluationId,
MediaSessionActionType, Theme, ViewportDetails, WebDriverScriptCommand,
};
use euclid::{Rect, Scale, Size2D, UnknownUnit};
use ipc_channel::ipc::{IpcReceiver, IpcSender};
use keyboard_types::Modifiers;
use malloc_size_of_derive::MallocSizeOf;
use media::WindowGLContext;
use net_traits::ResourceThreads;
use net_traits::image_cache::ImageCache;
use net_traits::storage_thread::StorageType;
use pixels::PixelFormat;
use profile_traits::mem;
use serde::{Deserialize, Serialize};
use servo_url::{ImmutableOrigin, ServoUrl};
use strum_macros::IntoStaticStr;
use style_traits::{CSSPixel, SpeculativePainter};
use stylo_atoms::Atom;
#[cfg(feature = "webgpu")]
use webgpu_traits::WebGPUMsg;
use webrender_api::ImageKey;
use webrender_api::units::DevicePixel;
/// The initial data required to create a new layout attached to an existing script thread.
#[derive(Debug, Deserialize, Serialize)]
pub struct NewLayoutInfo {
/// The ID of the parent pipeline and frame type, if any.
/// If `None`, this is a root pipeline.
pub parent_info: Option<PipelineId>,
/// Id of the newly-created pipeline.
pub new_pipeline_id: PipelineId,
/// Id of the browsing context associated with this pipeline.
pub browsing_context_id: BrowsingContextId,
/// Id of the top-level browsing context associated with this pipeline.
pub webview_id: WebViewId,
/// Id of the opener, if any
pub opener: Option<BrowsingContextId>,
/// Network request data which will be initiated by the script thread.
pub load_data: LoadData,
/// Initial [`ViewportDetails`] for this layout.
pub viewport_details: ViewportDetails,
/// The [`Theme`] of the new layout.
pub theme: Theme,
}
/// When a pipeline is closed, should its browsing context be discarded too?
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub enum DiscardBrowsingContext {
/// Discard the browsing context
Yes,
/// Don't discard the browsing context
No,
}
/// Is a document fully active, active or inactive?
/// A document is active if it is the current active document in its session history,
/// it is fuly active if it is active and all of its ancestors are active,
/// and it is inactive otherwise.
///
/// * <https://html.spec.whatwg.org/multipage/#active-document>
/// * <https://html.spec.whatwg.org/multipage/#fully-active>
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
pub enum DocumentActivity {
/// An inactive document
Inactive,
/// An active but not fully active document
Active,
/// A fully active document
FullyActive,
}
/// Type of recorded progressive web metric
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
pub enum ProgressiveWebMetricType {
/// Time to first Paint
FirstPaint,
/// Time to first contentful paint
FirstContentfulPaint,
/// Time to interactive
TimeToInteractive,
}
/// The reason why the pipeline id of an iframe is being updated.
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
pub enum UpdatePipelineIdReason {
/// The pipeline id is being updated due to a navigation.
Navigation,
/// The pipeline id is being updated due to a history traversal.
Traversal,
}
/// Messages sent to the `ScriptThread` event loop from the `Constellation`, `Compositor`, and (for
/// now) `Layout`.
#[derive(Deserialize, IntoStaticStr, Serialize)]
pub enum ScriptThreadMessage {
/// Takes the associated window proxy out of "delaying-load-events-mode",
/// used if a scheduled navigated was refused by the embedder.
/// <https://html.spec.whatwg.org/multipage/#delaying-load-events-mode>
StopDelayingLoadEventsMode(PipelineId),
/// Gives a channel and ID to a layout, as well as the ID of that layout's parent
AttachLayout(NewLayoutInfo),
/// Window resized. Sends a DOM event eventually, but first we combine events.
Resize(PipelineId, ViewportDetails, WindowSizeType),
/// Theme changed.
ThemeChange(PipelineId, Theme),
/// Notifies script that window has been resized but to not take immediate action.
ResizeInactive(PipelineId, ViewportDetails),
/// Window switched from fullscreen mode.
ExitFullScreen(PipelineId),
/// Notifies the script that the document associated with this pipeline should 'unload'.
UnloadDocument(PipelineId),
/// Notifies the script that a pipeline should be closed.
ExitPipeline(PipelineId, DiscardBrowsingContext),
/// Notifies the script that the whole thread should be closed.
ExitScriptThread,
/// Sends a DOM event.
SendInputEvent(PipelineId, ConstellationInputEvent),
/// Notifies script of the viewport.
Viewport(PipelineId, Rect<f32, UnknownUnit>),
/// Requests that the script thread immediately send the constellation the title of a pipeline.
GetTitle(PipelineId),
/// Notifies script thread of a change to one of its document's activity
SetDocumentActivity(PipelineId, DocumentActivity),
/// Set whether to use less resources by running timers at a heavily limited rate.
SetThrottled(PipelineId, bool),
/// Notify the containing iframe (in PipelineId) that the nested browsing context (BrowsingContextId) is throttled.
SetThrottledInContainingIframe(PipelineId, BrowsingContextId, bool),
/// Notifies script thread that a url should be loaded in this iframe.
/// PipelineId is for the parent, BrowsingContextId is for the nested browsing context
NavigateIframe(
PipelineId,
BrowsingContextId,
LoadData,
NavigationHistoryBehavior,
),
/// Post a message to a given window.
PostMessage {
/// The target of the message.
target: PipelineId,
/// The source of the message.
source: PipelineId,
/// The top level browsing context associated with the source pipeline.
source_browsing_context: WebViewId,
/// The expected origin of the target.
target_origin: Option<ImmutableOrigin>,
/// The source origin of the message.
/// <https://html.spec.whatwg.org/multipage/#dom-messageevent-origin>
source_origin: ImmutableOrigin,
/// The data to be posted.
data: StructuredSerializedData,
},
/// Updates the current pipeline ID of a given iframe.
/// First PipelineId is for the parent, second is the new PipelineId for the frame.
UpdatePipelineId(
PipelineId,
BrowsingContextId,
WebViewId,
PipelineId,
UpdatePipelineIdReason,
),
/// Updates the history state and url of a given pipeline.
UpdateHistoryState(PipelineId, Option<HistoryStateId>, ServoUrl),
/// Removes inaccesible history states.
RemoveHistoryStates(PipelineId, Vec<HistoryStateId>),
/// Set an iframe to be focused. Used when an element in an iframe gains focus.
/// PipelineId is for the parent, BrowsingContextId is for the nested browsing context
FocusIFrame(PipelineId, BrowsingContextId, FocusSequenceNumber),
/// Focus the document. Used when the container gains focus.
FocusDocument(PipelineId, FocusSequenceNumber),
/// Notifies that the document's container (e.g., an iframe) is not included
/// in the top-level browsing context's focus chain (not considering system
/// focus) anymore.
///
/// Obviously, this message is invalid for a top-level document.
Unfocus(PipelineId, FocusSequenceNumber),
/// Passes a webdriver command to the script thread for execution
WebDriverScriptCommand(PipelineId, WebDriverScriptCommand),
/// Notifies script thread that all animations are done
TickAllAnimations(Vec<WebViewId>),
/// Notifies the script thread that a new Web font has been loaded, and thus the page should be
/// reflowed.
WebFontLoaded(PipelineId, bool /* success */),
/// Cause a `load` event to be dispatched at the appropriate iframe element.
DispatchIFrameLoadEvent {
/// The frame that has been marked as loaded.
target: BrowsingContextId,
/// The pipeline that contains a frame loading the target pipeline.
parent: PipelineId,
/// The pipeline that has completed loading.
child: PipelineId,
},
/// Cause a `storage` event to be dispatched at the appropriate window.
/// The strings are key, old value and new value.
DispatchStorageEvent(
PipelineId,
StorageType,
ServoUrl,
Option<String>,
Option<String>,
Option<String>,
),
/// Report an error from a CSS parser for the given pipeline
ReportCSSError(PipelineId, String, u32, u32, String),
/// Reload the given page.
Reload(PipelineId),
/// Notifies the script thread about a new recorded paint metric.
PaintMetric(
PipelineId,
ProgressiveWebMetricType,
CrossProcessInstant,
bool, /* first_reflow */
),
/// Notifies the media session about a user requested media session action.
MediaSessionAction(PipelineId, MediaSessionActionType),
/// Notifies script thread that WebGPU server has started
#[cfg(feature = "webgpu")]
SetWebGPUPort(IpcReceiver<WebGPUMsg>),
/// The compositor scrolled and is updating the scroll states of the nodes in the given
/// pipeline via the Constellation.
SetScrollStates(PipelineId, Vec<ScrollState>),
/// Evaluate the given JavaScript and return a result via a corresponding message
/// to the Constellation.
EvaluateJavaScript(PipelineId, JavaScriptEvaluationId, String),
}
impl fmt::Debug for ScriptThreadMessage {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
let variant_string: &'static str = self.into();
write!(formatter, "ConstellationControlMsg::{variant_string}")
}
}
/// Used to determine if a script has any pending asynchronous activity.
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub enum DocumentState {
/// The document has been loaded and is idle.
Idle,
/// The document is either loading or waiting on an event.
Pending,
}
/// Input events from the embedder that are sent via the `Constellation`` to the `ScriptThread`.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ConstellationInputEvent {
/// The hit test result of this input event, if any.
pub hit_test_result: Option<CompositorHitTestResult>,
/// The pressed mouse button state of the constellation when this input
/// event was triggered.
pub pressed_mouse_buttons: u16,
/// The currently active keyboard modifiers.
pub active_keyboard_modifiers: Modifiers,
/// The [`InputEvent`] itself.
pub event: InputEvent,
}
/// Data needed to construct a script thread.
///
/// NB: *DO NOT* add any Senders or Receivers here! pcwalton will have to rewrite your code if you
/// do! Use IPC senders and receivers instead.
pub struct InitialScriptState {
/// The ID of the pipeline with which this script thread is associated.
pub id: PipelineId,
/// The subpage ID of this pipeline to create in its pipeline parent.
/// If `None`, this is the root.
pub parent_info: Option<PipelineId>,
/// The ID of the browsing context this script is part of.
pub browsing_context_id: BrowsingContextId,
/// The ID of the top-level browsing context this script is part of.
pub webview_id: WebViewId,
/// The ID of the opener, if any.
pub opener: Option<BrowsingContextId>,
/// Loading into a Secure Context
pub inherited_secure_context: Option<bool>,
/// A channel with which messages can be sent to us (the script thread).
pub constellation_sender: IpcSender<ScriptThreadMessage>,
/// A port on which messages sent by the constellation to script can be received.
pub constellation_receiver: IpcReceiver<ScriptThreadMessage>,
/// A channel on which messages can be sent to the constellation from script.
pub pipeline_to_constellation_sender: ScriptToConstellationChan,
/// A handle to register script-(and associated layout-)threads for hang monitoring.
pub background_hang_monitor_register: Box<dyn BackgroundHangMonitorRegister>,
/// A channel to the resource manager thread.
pub resource_threads: ResourceThreads,
/// A channel to the bluetooth thread.
#[cfg(feature = "bluetooth")]
pub bluetooth_sender: IpcSender<BluetoothRequest>,
/// The image cache for this script thread.
pub image_cache: Arc<dyn ImageCache>,
/// A channel to the time profiler thread.
pub time_profiler_sender: profile_traits::time::ProfilerChan,
/// A channel to the memory profiler thread.
pub memory_profiler_sender: mem::ProfilerChan,
/// A channel to the developer tools, if applicable.
pub devtools_server_sender: Option<IpcSender<ScriptToDevtoolsControlMsg>>,
/// Initial [`ViewportDetails`] for the frame that is initiating this `ScriptThread`.
pub viewport_details: ViewportDetails,
/// Initial [`Theme`] for the frame that is initiating this `ScriptThread`.
pub theme: Theme,
/// The ID of the pipeline namespace for this script thread.
pub pipeline_namespace_id: PipelineNamespaceId,
/// A ping will be sent on this channel once the script thread shuts down.
pub content_process_shutdown_sender: Sender<()>,
/// A channel to the WebGL thread used in this pipeline.
pub webgl_chan: Option<WebGLPipeline>,
/// The XR device registry
pub webxr_registry: Option<webxr_api::Registry>,
/// Access to the compositor across a process boundary.
pub compositor_api: CrossProcessCompositorApi,
/// Application window's GL Context for Media player
pub player_context: WindowGLContext,
/// User content manager
pub user_content_manager: UserContentManager,
}
/// Errors from executing a paint worklet
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum PaintWorkletError {
/// Execution timed out.
Timeout,
/// No such worklet.
WorkletNotFound,
}
impl From<RecvTimeoutError> for PaintWorkletError {
fn from(_: RecvTimeoutError) -> PaintWorkletError {
PaintWorkletError::Timeout
}
}
/// Execute paint code in the worklet thread pool.
pub trait Painter: SpeculativePainter {
/// <https://drafts.css-houdini.org/css-paint-api/#draw-a-paint-image>
fn draw_a_paint_image(
&self,
size: Size2D<f32, CSSPixel>,
zoom: Scale<f32, CSSPixel, DevicePixel>,
properties: Vec<(Atom, String)>,
arguments: Vec<String>,
) -> Result<DrawAPaintImageResult, PaintWorkletError>;
}
impl fmt::Debug for dyn Painter {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_tuple("Painter")
.field(&format_args!(".."))
.finish()
}
}
/// The result of executing paint code: the image together with any image URLs that need to be loaded.
///
/// TODO: this should return a WR display list. <https://github.com/servo/servo/issues/17497>
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
pub struct DrawAPaintImageResult {
/// The image height
pub width: u32,
/// The image width
pub height: u32,
/// The image format
pub format: PixelFormat,
/// The image drawn, or None if an invalid paint image was drawn
pub image_key: Option<ImageKey>,
/// Drawing the image might have requested loading some image URLs.
pub missing_image_urls: Vec<ServoUrl>,
}