script: Measure heap usage of various ignored fields (#38791)

These changes allow using MallocSizeOf/`#[conditional_malloc_size_of]`
on WebIDL callback values, and then fix a grab bag of places in the
script crate that previously ignored those values. There are also some
commits removing ignored fields that involved Arc/Rc that are not WebIDL
callbacks, since they are now easier to support with the
`#[conditional_malloc_size_of]` attribute.

Testing: Manual testing on about:memory for servo.org.

---------

Signed-off-by: Josh Matthews <josh@joshmatthews.net>
This commit is contained in:
Josh Matthews 2025-08-20 04:43:58 -04:00 committed by GitHub
parent 604b6ea26d
commit 636e7211e0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 44 additions and 38 deletions

View file

@ -6,6 +6,7 @@ use base::id::WebViewId;
use constellation_traits::{ScriptToConstellationChan, ScriptToConstellationMessage};
use embedder_traits::EmbedderMsg;
use ipc_channel::ipc::channel;
use malloc_size_of_derive::MallocSizeOf;
/// A trait which abstracts access to the embedder's clipboard in order to allow unit
/// testing clipboard-dependent parts of `script`.
@ -16,6 +17,7 @@ pub trait ClipboardProvider {
fn set_text(&mut self, _: String);
}
#[derive(MallocSizeOf)]
pub(crate) struct EmbedderClipboardProvider {
pub constellation_sender: ScriptToConstellationChan,
pub webview_id: WebViewId,

View file

@ -823,7 +823,7 @@ pub(crate) fn create_array_buffer_with_size(
#[cfg(feature = "webgpu")]
#[derive(JSTraceable, MallocSizeOf)]
pub(crate) struct DataBlock {
#[ignore_malloc_size_of = "Arc"]
#[conditional_malloc_size_of]
data: Arc<Box<[u8]>>,
/// Data views (mutable subslices of data)
data_views: Vec<DataView>,

View file

@ -614,28 +614,28 @@ impl CustomElementRegistryMethods<crate::DomTypeHolder> for CustomElementRegistr
#[derive(Clone, JSTraceable, MallocSizeOf)]
pub(crate) struct LifecycleCallbacks {
#[ignore_malloc_size_of = "Rc"]
#[conditional_malloc_size_of]
connected_callback: Option<Rc<Function>>,
#[ignore_malloc_size_of = "Rc"]
#[conditional_malloc_size_of]
disconnected_callback: Option<Rc<Function>>,
#[ignore_malloc_size_of = "Rc"]
#[conditional_malloc_size_of]
adopted_callback: Option<Rc<Function>>,
#[ignore_malloc_size_of = "Rc"]
#[conditional_malloc_size_of]
attribute_changed_callback: Option<Rc<Function>>,
#[ignore_malloc_size_of = "Rc"]
#[conditional_malloc_size_of]
form_associated_callback: Option<Rc<Function>>,
#[ignore_malloc_size_of = "Rc"]
#[conditional_malloc_size_of]
form_reset_callback: Option<Rc<Function>>,
#[ignore_malloc_size_of = "Rc"]
#[conditional_malloc_size_of]
form_disabled_callback: Option<Rc<Function>>,
#[ignore_malloc_size_of = "Rc"]
#[conditional_malloc_size_of]
form_state_restore_callback: Option<Rc<Function>>,
}
@ -657,7 +657,7 @@ pub(crate) struct CustomElementDefinition {
pub(crate) local_name: LocalName,
/// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-constructor>
#[ignore_malloc_size_of = "Rc"]
#[conditional_malloc_size_of]
pub(crate) constructor: Rc<CustomElementConstructor>,
/// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-observed-attributes>
@ -988,9 +988,9 @@ pub(crate) fn try_upgrade_element(element: &Element) {
#[derive(JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
pub(crate) enum CustomElementReaction {
Upgrade(#[ignore_malloc_size_of = "Rc"] Rc<CustomElementDefinition>),
Upgrade(#[conditional_malloc_size_of] Rc<CustomElementDefinition>),
Callback(
#[ignore_malloc_size_of = "Rc"] Rc<Function>,
#[conditional_malloc_size_of] Rc<Function>,
#[ignore_malloc_size_of = "mozjs"] Box<[Heap<JSVal>]>,
),
}

View file

@ -163,11 +163,11 @@ static CONTENT_EVENT_HANDLER_NAMES: [&str; 83] = [
#[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)]
#[allow(clippy::enum_variant_names)]
pub(crate) enum CommonEventHandler {
EventHandler(#[ignore_malloc_size_of = "Rc"] Rc<EventHandlerNonNull>),
EventHandler(#[conditional_malloc_size_of] Rc<EventHandlerNonNull>),
ErrorEventHandler(#[ignore_malloc_size_of = "Rc"] Rc<OnErrorEventHandlerNonNull>),
ErrorEventHandler(#[conditional_malloc_size_of] Rc<OnErrorEventHandlerNonNull>),
BeforeUnloadEventHandler(#[ignore_malloc_size_of = "Rc"] Rc<OnBeforeUnloadEventHandlerNonNull>),
BeforeUnloadEventHandler(#[conditional_malloc_size_of] Rc<OnBeforeUnloadEventHandlerNonNull>),
}
impl CommonEventHandler {
@ -231,7 +231,7 @@ fn get_compiled_handler(
#[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)]
enum EventListenerType {
Additive(#[ignore_malloc_size_of = "Rc"] Rc<EventListener>),
Additive(#[conditional_malloc_size_of] Rc<EventListener>),
Inline(RefCell<InlineEventListener>),
}
@ -439,7 +439,7 @@ impl std::cmp::PartialEq for EventListenerEntry {
#[derive(Clone, JSTraceable, MallocSizeOf)]
/// A mix of potentially uncompiled and compiled event listeners of the same type.
pub(crate) struct EventListeners(
#[ignore_malloc_size_of = "Rc"] Vec<Rc<RefCell<EventListenerEntry>>>,
#[conditional_malloc_size_of] Vec<Rc<RefCell<EventListenerEntry>>>,
);
impl Deref for EventListeners {

View file

@ -361,7 +361,6 @@ pub(crate) struct HTMLInputElement {
size: Cell<u32>,
maxlength: Cell<i32>,
minlength: Cell<i32>,
#[ignore_malloc_size_of = "TextInput contains an IPCSender which cannot be measured"]
#[no_trace]
textinput: DomRefCell<TextInput<EmbedderClipboardProvider>>,
// https://html.spec.whatwg.org/multipage/#concept-input-value-dirty-flag

View file

@ -111,13 +111,17 @@ static MEDIA_CONTROL_CSS: &str = include_str!("../resources/media-controls.css")
/// A JS file to control the media controls.
static MEDIA_CONTROL_JS: &str = include_str!("../resources/media-controls.js");
#[derive(PartialEq)]
#[derive(MallocSizeOf, PartialEq)]
enum FrameStatus {
Locked,
Unlocked,
}
struct FrameHolder(FrameStatus, VideoFrame);
#[derive(MallocSizeOf)]
struct FrameHolder(
FrameStatus,
#[ignore_malloc_size_of = "defined in servo-media"] VideoFrame,
);
impl FrameHolder {
fn new(frame: VideoFrame) -> FrameHolder {
@ -159,6 +163,7 @@ impl FrameHolder {
}
}
#[derive(MallocSizeOf)]
pub(crate) struct MediaFrameRenderer {
player_id: Option<u64>,
compositor_api: CrossProcessCompositorApi,
@ -389,7 +394,7 @@ pub(crate) struct HTMLMediaElement {
#[ignore_malloc_size_of = "servo_media"]
#[no_trace]
player: DomRefCell<Option<Arc<Mutex<dyn Player>>>>,
#[ignore_malloc_size_of = "Arc"]
#[conditional_malloc_size_of]
#[no_trace]
video_renderer: Arc<Mutex<MediaFrameRenderer>>,
#[ignore_malloc_size_of = "Arc"]
@ -417,7 +422,6 @@ pub(crate) struct HTMLMediaElement {
#[no_trace]
blob_url: DomRefCell<Option<ServoUrl>>,
/// <https://html.spec.whatwg.org/multipage/#dom-media-played>
#[ignore_malloc_size_of = "Rc"]
played: DomRefCell<TimeRangesContainer>,
// https://html.spec.whatwg.org/multipage/#dom-media-audiotracks
audio_tracks_list: MutNullableDom<AudioTrackList>,

View file

@ -50,7 +50,6 @@ use crate::textinput::{
#[dom_struct]
pub(crate) struct HTMLTextAreaElement {
htmlelement: HTMLElement,
#[ignore_malloc_size_of = "TextInput contains an IPCSender which cannot be measured"]
#[no_trace]
textinput: DomRefCell<TextInput<EmbedderClipboardProvider>>,
placeholder: DomRefCell<DOMString>,

View file

@ -66,7 +66,7 @@ pub(crate) struct ElementRareData {
/// <https://html.spec.whatwg.org/multipage/#custom-element-reaction-queue>
pub(crate) custom_element_reaction_queue: Vec<CustomElementReaction>,
/// <https://dom.spec.whatwg.org/#concept-element-custom-element-definition>
#[ignore_malloc_size_of = "Rc"]
#[conditional_malloc_size_of]
pub(crate) custom_element_definition: Option<Rc<CustomElementDefinition>>,
/// <https://dom.spec.whatwg.org/#concept-element-custom-element-state>
pub(crate) custom_element_state: CustomElementState,

View file

@ -115,17 +115,18 @@ impl Callback for StartAlgorithmRejectionHandler {
}
/// <https://streams.spec.whatwg.org/#value-with-size>
#[derive(Debug, JSTraceable, PartialEq)]
#[derive(Debug, JSTraceable, MallocSizeOf, PartialEq)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
pub(crate) struct ValueWithSize {
/// <https://streams.spec.whatwg.org/#value-with-size-value>
#[ignore_malloc_size_of = "Heap is measured by mozjs"]
pub(crate) value: Box<Heap<JSVal>>,
/// <https://streams.spec.whatwg.org/#value-with-size-size>
pub(crate) size: f64,
}
/// <https://streams.spec.whatwg.org/#value-with-size>
#[derive(Debug, JSTraceable, PartialEq)]
#[derive(Debug, JSTraceable, MallocSizeOf, PartialEq)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
pub(crate) enum EnqueuedValue {
/// A value enqueued from Rust.
@ -191,7 +192,6 @@ fn is_non_negative_number(value: &EnqueuedValue) -> bool {
#[derive(Default, JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
pub(crate) struct QueueWithSizes {
#[ignore_malloc_size_of = "EnqueuedValue::Js"]
queue: VecDeque<EnqueuedValue>,
/// <https://streams.spec.whatwg.org/#readablestreamdefaultcontroller-queuetotalsize>
pub(crate) total_size: f64,

View file

@ -55,7 +55,7 @@ pub(crate) trait MicrotaskRunnable {
/// A promise callback scheduled to run during the next microtask checkpoint (#4283).
#[derive(JSTraceable, MallocSizeOf)]
pub(crate) struct EnqueuedPromiseCallback {
#[ignore_malloc_size_of = "Rc has unclear ownership"]
#[conditional_malloc_size_of]
pub(crate) callback: Rc<PromiseJobCallback>,
#[no_trace]
pub(crate) pipeline: PipelineId,
@ -66,7 +66,7 @@ pub(crate) struct EnqueuedPromiseCallback {
/// identical to EnqueuedPromiseCallback once it's on the queue
#[derive(JSTraceable, MallocSizeOf)]
pub(crate) struct UserMicrotask {
#[ignore_malloc_size_of = "Rc has unclear ownership"]
#[conditional_malloc_size_of]
pub(crate) callback: Rc<VoidFunction>,
#[no_trace]
pub(crate) pipeline: PipelineId,

View file

@ -118,7 +118,7 @@ impl fmt::Debug for dyn TaskBox {
/// Encapsulated state required to create cancellable tasks from non-script threads.
#[derive(Clone, Default, JSTraceable, MallocSizeOf)]
pub(crate) struct TaskCanceller {
#[ignore_malloc_size_of = "This is difficult, because only one of them should be measured"]
#[conditional_malloc_size_of]
pub(crate) cancelled: Arc<AtomicBool>,
}

View file

@ -381,7 +381,6 @@ struct JsTimerEntry {
// TODO: Handle rooting during invocation when movable GC is turned on
#[derive(JSTraceable, MallocSizeOf)]
pub(crate) struct JsTimerTask {
#[ignore_malloc_size_of = "Because it is non-owning"]
handle: JsTimerHandle,
#[no_trace]
source: TimerSource,
@ -409,7 +408,7 @@ pub(crate) enum TimerCallback {
enum InternalTimerCallback {
StringTimerCallback(DOMString),
FunctionTimerCallback(
#[ignore_malloc_size_of = "Rc"] Rc<Function>,
#[conditional_malloc_size_of] Rc<Function>,
#[ignore_malloc_size_of = "Rc"] Rc<Box<[Heap<JSVal>]>>,
),
}

View file

@ -55,11 +55,13 @@ pub enum ExceptionHandling {
/// A common base class for representing IDL callback function and
/// callback interface types.
#[derive(JSTraceable)]
#[derive(JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
pub struct CallbackObject<D: DomTypes> {
/// The underlying `JSObject`.
#[ignore_malloc_size_of = "measured by mozjs"]
callback: Heap<*mut JSObject>,
#[ignore_malloc_size_of = "measured by mozjs"]
permanent_js_root: Heap<JSVal>,
/// The ["callback context"], that is, the global to use as incumbent
@ -147,7 +149,7 @@ pub trait CallbackContainer<D: DomTypes> {
}
/// A common base class for representing IDL callback function types.
#[derive(JSTraceable, PartialEq)]
#[derive(JSTraceable, MallocSizeOf, PartialEq)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
pub struct CallbackFunction<D: DomTypes> {
object: CallbackObject<D>,
@ -180,7 +182,7 @@ impl<D: DomTypes> CallbackFunction<D> {
}
/// A common base class for representing IDL callback interface types.
#[derive(JSTraceable, PartialEq)]
#[derive(JSTraceable, MallocSizeOf, PartialEq)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
pub struct CallbackInterface<D: DomTypes> {
object: CallbackObject<D>,

View file

@ -8057,7 +8057,7 @@ class CGCallback(CGClass):
constructors=self.getConstructors(),
methods=realMethods,
templateSpecialization=['D: DomTypes'],
decorators="#[derive(JSTraceable, PartialEq)]\n"
decorators="#[derive(JSTraceable, MallocSizeOf, PartialEq)]\n"
"#[cfg_attr(crown, allow(crown::unrooted_must_root))]\n"
"#[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_interior)]")

View file

@ -23,6 +23,7 @@ use euclid::default::Size2D as UntypedSize2D;
use http::{HeaderMap, Method};
use ipc_channel::Error as IpcError;
use ipc_channel::ipc::{IpcReceiver, IpcSender};
use malloc_size_of_derive::MallocSizeOf;
use net_traits::policy_container::PolicyContainer;
use net_traits::request::{Destination, InsecureRequestsPolicy, Referrer, RequestBody};
use net_traits::storage_thread::StorageType;
@ -42,7 +43,7 @@ use crate::{
};
/// A Script to Constellation channel.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
pub struct ScriptToConstellationChan {
/// Sender for communicating with constellation thread.
pub sender: IpcSender<(PipelineId, ScriptToConstellationMessage)>,

View file

@ -175,7 +175,7 @@ pub struct PendingRasterizationImage {
pub size: DeviceIntSize,
}
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, MallocSizeOf)]
pub struct MediaFrame {
pub image_key: webrender_api::ImageKey,
pub width: i32,