Skip to main content

script_traits/
lib.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//! This module contains traits in script used generically in the rest of Servo.
6//! The traits are here instead of in script so that these modules won't have
7//! to depend on script.
8
9#![deny(missing_docs)]
10#![deny(unsafe_code)]
11
12use std::fmt;
13
14use bitflags::bitflags;
15use crossbeam_channel::RecvTimeoutError;
16use devtools_traits::ScriptToDevtoolsControlMsg;
17use embedder_traits::user_contents::{UserContentManagerId, UserContents};
18use embedder_traits::{
19    EmbedderControlId, EmbedderControlResponse, FocusSequenceNumber, InputEventAndId,
20    JavaScriptEvaluationId, MediaSessionActionType, MouseButton, PaintHitTestResult,
21    ScriptToEmbedderChan, Theme, ViewportDetails, WebDriverScriptCommand,
22};
23use euclid::{Scale, Size2D};
24use fonts_traits::{SystemFontServiceProxySender, WebFontLoadEvent};
25use keyboard_types::Modifiers;
26use malloc_size_of::malloc_size_of_is_0;
27use malloc_size_of_derive::MallocSizeOf;
28use media::WindowGLContext;
29use net_traits::ResourceThreads;
30use paint_api::{CrossProcessPaintApi, PinchZoomInfos};
31use pixels::PixelFormat;
32use profile_traits::mem;
33use rustc_hash::FxHashMap;
34use serde::{Deserialize, Serialize};
35use servo_base::Epoch;
36use servo_base::generic_channel::{GenericCallback, GenericReceiver, GenericSender};
37use servo_base::id::{
38    BrowsingContextId, HistoryStateId, LCPCandidateID, PipelineId, PipelineNamespaceId,
39    PipelineNamespaceRequest, ScriptEventLoopId, WebViewId,
40};
41#[cfg(feature = "bluetooth")]
42use servo_bluetooth_traits::BluetoothRequest;
43#[cfg(feature = "webgl")]
44use servo_canvas_traits::webgl::WebGLPipeline;
45use servo_config::prefs::PrefValue;
46use servo_constellation_traits::{
47    KeyboardScroll, LoadData, NavigationHistoryBehavior, PaintMetricEvent, RemoteFocusOperation,
48    ScriptToConstellationSender, ScrollStateUpdate, StructuredSerializedData, TargetSnapshotParams,
49    WindowSizeType,
50};
51use servo_url::{ImmutableOrigin, OriginSnapshot, ServoUrl};
52use storage_traits::StorageThreads;
53use storage_traits::webstorage_thread::WebStorageType;
54use strum::IntoStaticStr;
55use style_traits::{CSSPixel, SpeculativePainter};
56use stylo_atoms::Atom;
57#[cfg(feature = "webgpu")]
58use webgpu_traits::WebGPUMsg;
59use webrender_api::ImageKey;
60use webrender_api::units::DevicePixel;
61
62/// The initial data required to create a new `Pipeline` attached to an existing `ScriptThread`.
63#[derive(Clone, Debug, Deserialize, Serialize)]
64pub struct NewPipelineInfo {
65    /// The ID of the parent pipeline and frame type, if any.
66    /// If `None`, this is a root pipeline.
67    pub parent_info: Option<PipelineId>,
68    /// Id of the newly-created pipeline.
69    pub new_pipeline_id: PipelineId,
70    /// Id of the browsing context associated with this pipeline.
71    pub browsing_context_id: BrowsingContextId,
72    /// Id of the top-level browsing context associated with this pipeline.
73    pub webview_id: WebViewId,
74    /// Id of the opener, if any
75    pub opener: Option<BrowsingContextId>,
76    /// Network request data which will be initiated by the script thread.
77    pub load_data: LoadData,
78    /// Initial [`ViewportDetails`] for this layout.
79    pub viewport_details: ViewportDetails,
80    /// The ID of the `UserContentManager` associated with this new pipeline's `WebView`.
81    pub user_content_manager_id: Option<UserContentManagerId>,
82    /// The [`Theme`] of the new layout.
83    pub embedder_theme: Theme,
84    /// A snapshot of the navigation parameters of the target of this navigation.
85    pub target_snapshot_params: TargetSnapshotParams,
86    /// Name of this iframe, if any
87    pub frame_name: Option<String>,
88}
89
90/// When a pipeline is closed, should its browsing context be discarded too?
91#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
92pub enum DiscardBrowsingContext {
93    /// Discard the browsing context
94    Yes,
95    /// Don't discard the browsing context
96    No,
97}
98
99/// Is a document fully active, active or inactive?
100/// A document is active if it is the current active document in its session history,
101/// it is fuly active if it is active and all of its ancestors are active,
102/// and it is inactive otherwise.
103///
104/// * <https://html.spec.whatwg.org/multipage/#active-document>
105/// * <https://html.spec.whatwg.org/multipage/#fully-active>
106#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
107pub enum DocumentActivity {
108    /// An inactive document
109    Inactive,
110    /// An active but not fully active document
111    Active,
112    /// A fully active document
113    FullyActive,
114}
115
116/// Type of recorded progressive web metric
117#[derive(Clone, Debug, Deserialize, Serialize)]
118pub enum ProgressiveWebMetricType {
119    /// Time to first Paint
120    FirstPaint,
121    /// Time to first contentful paint
122    FirstContentfulPaint,
123    /// Time for the largest contentful paint
124    LargestContentfulPaint {
125        /// The identity of the element, if any.
126        id: LCPCandidateID,
127    },
128    /// Time to interactive
129    TimeToInteractive,
130}
131
132/// The reason why the pipeline id of an iframe is being updated.
133#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
134pub enum UpdatePipelineIdReason {
135    /// The pipeline id is being updated due to a navigation.
136    Navigation,
137    /// The pipeline id is being updated due to a history traversal.
138    Traversal,
139}
140
141/// Messages sent to the `ScriptThread` event loop from the `Constellation`, `Paint`, and (for
142/// now) `Layout`.
143#[derive(Deserialize, IntoStaticStr, Serialize)]
144pub enum ScriptThreadMessage {
145    /// Span a new `Pipeline` in this `ScriptThread` and start fetching the contents
146    /// according to the provided `LoadData`. This will ultimately create a `Window`
147    /// and all associated data structures such as `Layout` in the `ScriptThread`.
148    SpawnPipeline(NewPipelineInfo),
149    /// Takes the associated window proxy out of "delaying-load-events-mode",
150    /// used if a scheduled navigated was refused by the embedder.
151    /// <https://html.spec.whatwg.org/multipage/#delaying-load-events-mode>
152    StopDelayingLoadEventsMode(PipelineId),
153    /// Window resized.  Sends a DOM event eventually, but first we combine events.
154    Resize(PipelineId, ViewportDetails, WindowSizeType),
155    /// Theme changed.
156    ThemeChange(PipelineId, Theme),
157    /// Notifies script that window has been resized but to not take immediate action.
158    ResizeInactive(PipelineId, ViewportDetails),
159    /// Window switched from fullscreen mode.
160    ExitFullScreen(PipelineId),
161    /// Notifies the script that the document associated with this pipeline should 'unload'.
162    UnloadDocument(PipelineId),
163    /// Notifies the script that a pipeline should be closed.
164    ExitPipeline(WebViewId, PipelineId, DiscardBrowsingContext),
165    /// Notifies the script that the whole thread should be closed.
166    ExitScriptThread,
167    /// Sends a DOM event.
168    SendInputEvent(WebViewId, PipelineId, ConstellationInputEvent),
169    /// Request that the given pipeline refresh the cursor by doing a hit test at the most
170    /// recently hovered cursor position and resetting the cursor. This happens after a
171    /// display list update is rendered.
172    RefreshCursor(PipelineId),
173    /// Requests that the script thread immediately send the constellation the title of a pipeline.
174    GetTitle(PipelineId),
175    /// Retrieve the origin of a document for a pipeline, in case a child needs to retrieve the
176    /// origin of a parent in a different script thread.
177    GetDocumentOrigin(PipelineId, GenericSender<Option<OriginSnapshot>>),
178    /// Retrieve the origin and internal ancestor origin objects list of a
179    /// `Document` for a given `PipelineId`, in case a child needs to retrieve
180    /// the origin of a parent in a different event loop.
181    GetDocumentOriginDetails(
182        PipelineId,
183        GenericSender<Option<(OriginSnapshot, Vec<ImmutableOrigin>)>>,
184    ),
185    /// Notifies script thread of a change to one of its document's activity
186    SetDocumentActivity(PipelineId, DocumentActivity),
187    /// Set whether to use less resources by running timers at a heavily limited rate.
188    SetThrottled(PipelineId, bool),
189    /// Notifies script thread that a url should be loaded in this iframe.
190    /// PipelineId is for the parent, BrowsingContextId is for the nested browsing context
191    NavigateIframe(
192        PipelineId,
193        BrowsingContextId,
194        LoadData,
195        NavigationHistoryBehavior,
196        TargetSnapshotParams,
197    ),
198    /// Post a message to a given window.
199    PostMessage {
200        /// The target of the message.
201        target: PipelineId,
202        /// The webview associated with the source pipeline.
203        source_webview: WebViewId,
204        /// The ancestry of browsing context associated with the source,
205        /// starting with the source itself.
206        source_with_ancestry: Vec<BrowsingContextId>,
207        /// The expected origin of the target.
208        target_origin: Option<ImmutableOrigin>,
209        /// The source origin of the message.
210        /// <https://html.spec.whatwg.org/multipage/#dom-messageevent-origin>
211        source_origin: ImmutableOrigin,
212        /// The data to be posted.
213        data: Box<StructuredSerializedData>,
214    },
215    /// Updates the current pipeline ID of a given iframe.
216    /// First PipelineId is for the parent, second is the new PipelineId for the frame.
217    UpdatePipelineId(
218        PipelineId,
219        BrowsingContextId,
220        WebViewId,
221        PipelineId,
222        UpdatePipelineIdReason,
223    ),
224    /// Updates the history state and url of a given pipeline.
225    UpdateHistoryState(PipelineId, Option<HistoryStateId>, ServoUrl),
226    /// Removes inaccesible history states.
227    RemoveHistoryStates(PipelineId, Vec<HistoryStateId>),
228    /// Focus a `Document` as part of the focusing steps which focuses all parent `Document`s of a
229    /// newly focused `<iframe>`. Note that this is not used for the `Document` and `Element` that
230    /// is gaining focus as that is handled locally in the originating `ScriptThread`.
231    FocusDocumentAsPartOfFocusingSteps(PipelineId, FocusSequenceNumber, Option<BrowsingContextId>),
232    /// Unfocus a `Document` as part of the focusing steps which unfocuses all parent `Document`s of an
233    /// `<iframe>` losing focus. This does not do anything for a top-level `Document`, which can never
234    /// lose focus (apart from losing system focus, which is a separate concept).
235    UnfocusDocumentAsPartOfFocusingSteps(PipelineId, FocusSequenceNumber),
236    /// Focus a `Document` and run the focusing steps. This is used in two situations:
237    /// - When calling the DOM `focus()` API on a remote `Window` as well as from
238    ///   WebDriver. The difference between this and `FocusDocumentAsPartOfFocusingSteps` is that this
239    ///   version actually does run the focusing steps and may result in blur and focus events firing
240    ///   up the frame tree.
241    /// - When doing sequential focus navigation into and out of frames.
242    FocusDocument(PipelineId, RemoteFocusOperation),
243    /// Passes a webdriver command to the script thread for execution
244    WebDriverScriptCommand(PipelineId, WebDriverScriptCommand),
245    /// Notifies script thread that all animations are done
246    TickAllAnimations(Vec<WebViewId>),
247    /// Notifies the script thread that a web font has finished loading.
248    ///
249    /// This is sent if either the web font loaded successfully, or to notify the script thread
250    /// that it should try to resolve `document.fonts.ready` because the font was the last one
251    /// loading.
252    WebFontLoadFinished(PipelineId, WebFontLoadEvent),
253    /// Cause a `load` event to be dispatched at the appropriate iframe element.
254    DispatchIFrameLoadEvent {
255        /// The frame that has been marked as loaded.
256        target: BrowsingContextId,
257        /// The pipeline that contains a frame loading the target pipeline.
258        parent: PipelineId,
259        /// The pipeline that has completed loading.
260        child: PipelineId,
261    },
262    /// Cause a `storage` event to be dispatched at the appropriate window.
263    /// The strings are key, old value and new value.
264    DispatchStorageEvent(
265        PipelineId,
266        WebStorageType,
267        ServoUrl,
268        Option<String>,
269        Option<String>,
270        Option<String>,
271    ),
272    /// Report an error from a CSS parser for the given pipeline
273    ReportCSSError(PipelineId, String, u32, u32, String),
274    /// Reload the given page.
275    Reload(PipelineId),
276    /// Notifies the script thread about a new recorded paint metric.
277    PaintMetric(PipelineId, PaintMetricEvent),
278    /// Notifies the media session about a user requested media session action.
279    MediaSessionAction(PipelineId, MediaSessionActionType),
280    /// Notifies script thread that WebGPU server has started
281    #[cfg(feature = "webgpu")]
282    SetWebGPUPort(GenericReceiver<WebGPUMsg>),
283    /// `Paint` scrolled and is updating the scroll states of the nodes in the given
284    /// pipeline via the Constellation.
285    SetScrollStates(PipelineId, ScrollStateUpdate),
286    /// Evaluate the given JavaScript and return a result via a corresponding message
287    /// to the Constellation.
288    EvaluateJavaScript(WebViewId, PipelineId, JavaScriptEvaluationId, String),
289    /// A new batch of keys for the image cache for the specific pipeline.
290    SendImageKeysBatch(PipelineId, Vec<ImageKey>),
291    /// Preferences were updated in the parent process.
292    PreferencesUpdated(Vec<(String, PrefValue)>),
293    /// Notify the `ScriptThread` that the Servo renderer is no longer waiting on
294    /// asynchronous image uploads for the given `Pipeline`. These are mainly used
295    /// by canvas to perform uploads while the display list is being built.
296    NoLongerWaitingOnAsychronousImageUpdates(PipelineId),
297    /// Forward a keyboard scroll operation from an `<iframe>` to a parent pipeline.
298    ForwardKeyboardScroll(PipelineId, KeyboardScroll),
299    /// Request readiness for a screenshot from the given pipeline. The pipeline will
300    /// respond when it is ready to take the screenshot or will not be able to take it
301    /// in the future.
302    RequestScreenshotReadiness(WebViewId, PipelineId),
303    /// A response to a request to show an embedder user interface control.
304    EmbedderControlResponse(EmbedderControlId, EmbedderControlResponse),
305    /// Set the `UserContents` for the given `UserContentManagerId`. A `ScriptThread` can host many
306    /// `WebView`s which share the same `UserContentManager`. Only documents loaded after
307    /// the processing of this message will observe the new `UserContents` of the specified
308    /// `UserContentManagerId`.
309    SetUserContents(UserContentManagerId, UserContents),
310    /// Release all data for the given `UserContentManagerId` from the `ScriptThread`'s
311    /// `user_contents_for_manager_id` map.
312    DestroyUserContentManager(UserContentManagerId),
313    /// Update the pinch zoom details of a pipeline. Each `Window` stores a `VisualViewport` DOM
314    /// instance that gets updated according to the changes from the `Compositor``.
315    UpdatePinchZoomInfos(PipelineId, PinchZoomInfos),
316    /// Activate or deactivate accessibility features for the given pipeline, assuming it represents
317    /// a document.
318    ///
319    /// Why only one pipeline? In the Servo API, accessibility is activated on a per-webview basis,
320    /// and webviews have a simple one-to-many mapping to pipelines that represent documents. But
321    /// those pipelines run in script threads, which complicates things: the pipelines in a webview
322    /// may be split across multiple script threads, and the pipelines in a script thread may belong
323    /// to multiple webviews. So the simplest approach is to activate it for one pipeline at a time.
324    SetAccessibilityActive(PipelineId, bool, Epoch),
325    /// Force a garbage collection in this script thread.
326    TriggerGarbageCollection,
327}
328
329impl fmt::Debug for ScriptThreadMessage {
330    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
331        let variant_string: &'static str = self.into();
332        write!(formatter, "ConstellationControlMsg::{variant_string}")
333    }
334}
335
336/// Used to determine if a script has any pending asynchronous activity.
337#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
338pub enum DocumentState {
339    /// The document has been loaded and is idle.
340    Idle,
341    /// The document is either loading or waiting on an event.
342    Pending,
343}
344
345bitflags! {
346    #[derive(Clone, Copy, Default, Debug, Deserialize, Eq, PartialEq, Serialize)]
347    /// <https://w3c.github.io/pointerevents/#dom-mouseevent-buttons>
348    pub struct MouseButtons: u16 {
349        /// > 1 MUST indicate the primary button of the device (in general, the left
350        /// > button or the only button on single-button devices, used to activate a user
351        /// > interface control or select text).
352        const Primary = 1;
353        /// > 2 MUST indicate the secondary button (in general, the right button, often
354        /// > used to display a context menu), if present.
355        const Secondary = 2;
356        /// > 4 MUST indicate the auxiliary button (in general, the middle button, often
357        /// > combined with a mouse wheel).
358        const Auxiliary = 4;
359        /// The 'back' button:
360        ///
361        /// > Some pointing devices provide or simulate more buttons. To represent such
362        /// > buttons, the value MUST be doubled for each successive button (in the binary
363        /// > series 8, 16, 32, ... ).
364        const Back = 8;
365        /// The 'forward' button:
366        ///
367        /// > Some pointing devices provide or simulate more buttons. To represent such
368        /// > buttons, the value MUST be doubled for each successive button (in the binary
369        /// > series 8, 16, 32, ... ).
370        const Forward = 16;
371    }
372}
373
374impl MouseButtons {
375    /// Returns whether exactly one button is pressed.
376    pub fn exactly_one_button_pressed(&self) -> bool {
377        // Exactly one button is pressed iff mouse_button_state is a power of 2
378        !self.is_empty() && (self.bits() & (self.bits() - 1)) == 0
379    }
380}
381
382malloc_size_of_is_0!(MouseButtons);
383
384impl TryFrom<MouseButton> for MouseButtons {
385    type Error = ();
386
387    fn try_from(button: MouseButton) -> Result<Self, Self::Error> {
388        match button {
389            MouseButton::Primary => Ok(Self::Primary),
390            MouseButton::Secondary => Ok(Self::Secondary),
391            MouseButton::Auxiliary => Ok(Self::Auxiliary),
392            MouseButton::Back => Ok(Self::Back),
393            MouseButton::Forward => Ok(Self::Forward),
394            MouseButton::None | MouseButton::Other(_) => Err(()),
395        }
396    }
397}
398
399/// Input events from the embedder that are sent via the `Constellation`` to the `ScriptThread`.
400#[derive(Clone, Debug, Deserialize, Serialize)]
401pub struct ConstellationInputEvent {
402    /// The hit test result of this input event, if any.
403    pub hit_test_result: Option<PaintHitTestResult>,
404    /// The pressed mouse button state of the constellation when this input
405    /// event was triggered.
406    pub pressed_mouse_buttons: MouseButtons,
407    /// The currently active keyboard modifiers.
408    pub active_keyboard_modifiers: Modifiers,
409    /// The [`InputEventAndId`] itself.
410    pub event: InputEventAndId,
411}
412
413impl ConstellationInputEvent {
414    /// Returns whether `pressed_mouse_buttons` includes the primary button
415    pub fn primary_button_is_pressed(&self) -> bool {
416        self.pressed_mouse_buttons.contains(MouseButtons::Primary)
417    }
418
419    /// Returns whether `pressed_mouse_buttons` includes the auxiliary (middle) button
420    pub fn auxiliary_button_is_pressed(&self) -> bool {
421        self.pressed_mouse_buttons.contains(MouseButtons::Auxiliary)
422    }
423}
424
425/// All of the information necessary to create a new [`ScriptThread`] for a new [`EventLoop`].
426///
427/// NB: *DO NOT* add any Senders or Receivers here! pcwalton will have to rewrite your code if you
428/// do! Use IPC senders and receivers instead.
429#[derive(Deserialize, Serialize)]
430pub struct InitialScriptState {
431    /// The id of the script event loop that this state will start. This is used to uniquely
432    /// identify an event loop.
433    pub id: ScriptEventLoopId,
434    /// The sender to use to install the `Pipeline` namespace into this process (if necessary).
435    pub namespace_request_sender: GenericSender<PipelineNamespaceRequest>,
436    /// A channel with which messages can be sent to us (the script thread).
437    pub constellation_to_script_sender: GenericSender<ScriptThreadMessage>,
438    /// A port on which messages sent by the constellation to script can be received.
439    pub constellation_to_script_receiver: GenericReceiver<ScriptThreadMessage>,
440    /// A channel on which messages can be sent to the constellation from script.
441    pub script_to_constellation_sender: ScriptToConstellationSender,
442    /// A channel which allows script to send messages directly to the Embedder
443    /// This will pump the embedder event loop.
444    pub script_to_embedder_sender: ScriptToEmbedderChan,
445    /// An IpcSender to the `SystemFontService` used to create a `SystemFontServiceProxy`.
446    pub system_font_service: SystemFontServiceProxySender,
447    /// A channel to the resource manager thread.
448    pub resource_threads: ResourceThreads,
449    /// A channel to the storage manager thread.
450    pub storage_threads: StorageThreads,
451    /// A channel to the bluetooth thread.
452    #[cfg(feature = "bluetooth")]
453    pub bluetooth_sender: GenericSender<BluetoothRequest>,
454    /// A channel to the time profiler thread.
455    pub time_profiler_sender: profile_traits::time::ProfilerChan,
456    /// A channel to the memory profiler thread.
457    pub memory_profiler_sender: mem::ProfilerChan,
458    /// A channel to the developer tools, if applicable.
459    pub devtools_server_sender: Option<GenericCallback<ScriptToDevtoolsControlMsg>>,
460    /// The ID of the pipeline namespace for this script thread.
461    pub pipeline_namespace_id: PipelineNamespaceId,
462    /// A channel to the WebGL thread used in this pipeline.
463    #[cfg(feature = "webgl")]
464    pub webgl_chan: Option<WebGLPipeline>,
465    /// The XR device registry
466    pub webxr_registry: Option<webxr_api::Registry>,
467    /// Access to `Paint` across a process boundary.
468    pub cross_process_paint_api: CrossProcessPaintApi,
469    /// Application window's GL Context for Media player
470    pub player_context: WindowGLContext,
471    /// A list of URLs that can access privileged internal APIs.
472    pub privileged_urls: Vec<ServoUrl>,
473    /// A copy of constellation's `UserContentManagerId` to `UserContents` map.
474    pub user_contents_for_manager_id: FxHashMap<UserContentManagerId, UserContents>,
475}
476
477/// Errors from executing a paint worklet
478#[derive(Clone, Debug, Deserialize, Serialize)]
479pub enum PaintWorkletError {
480    /// Execution timed out.
481    Timeout,
482    /// No such worklet.
483    WorkletNotFound,
484}
485
486impl From<RecvTimeoutError> for PaintWorkletError {
487    fn from(_: RecvTimeoutError) -> PaintWorkletError {
488        PaintWorkletError::Timeout
489    }
490}
491
492/// Execute paint code in the worklet thread pool.
493pub trait Painter: SpeculativePainter {
494    /// <https://drafts.css-houdini.org/css-paint-api/#draw-a-paint-image>
495    fn draw_a_paint_image(
496        &self,
497        size: Size2D<f32, CSSPixel>,
498        zoom: Scale<f32, CSSPixel, DevicePixel>,
499        properties: Vec<(Atom, String)>,
500        arguments: Vec<String>,
501    ) -> Result<DrawAPaintImageResult, PaintWorkletError>;
502}
503
504impl fmt::Debug for dyn Painter {
505    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
506        fmt.debug_tuple("Painter")
507            .field(&format_args!(".."))
508            .finish()
509    }
510}
511
512/// The result of executing paint code: the image together with any image URLs that need to be loaded.
513///
514/// TODO: this should return a WR display list. <https://github.com/servo/servo/issues/17497>
515#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
516pub struct DrawAPaintImageResult {
517    /// The image height
518    pub width: u32,
519    /// The image width
520    pub height: u32,
521    /// The image format
522    pub format: PixelFormat,
523    /// The image drawn, or None if an invalid paint image was drawn
524    pub image_key: Option<ImageKey>,
525    /// Drawing the image might have requested loading some image URLs.
526    pub missing_image_urls: Vec<ServoUrl>,
527}