From 389277fa7295ae4573688a568eed4a79941704d2 Mon Sep 17 00:00:00 2001 From: Rahul Menon Date: Sat, 16 Aug 2025 15:33:37 -0500 Subject: [PATCH] content: Make QuotaExceededError serializable (#38720) Implements (de)serialization behavior for QuotaExceededError and enables the annotation on the WebIDL spec. Testing: Adds its own WPT tests Fixes: https://github.com/servo/servo/issues/38685 --------- Signed-off-by: Rahul Menon --- .../script/dom/bindings/structuredclone.rs | 20 +++++-- components/script/dom/quotaexceedederror.rs | 56 +++++++++++++++++++ .../webidls/QuotaExceededError.webidl | 2 +- components/shared/base/id.rs | 2 + .../constellation/structured_data/mod.rs | 4 ++ .../structured_data/serializable.rs | 34 ++++++++++- tests/wpt/meta/MANIFEST.json | 2 +- .../structuredclone_0.html | 36 ++++++++++++ 8 files changed, 149 insertions(+), 7 deletions(-) diff --git a/components/script/dom/bindings/structuredclone.rs b/components/script/dom/bindings/structuredclone.rs index 2f12df0c944..7146e903e4f 100644 --- a/components/script/dom/bindings/structuredclone.rs +++ b/components/script/dom/bindings/structuredclone.rs @@ -11,12 +11,12 @@ use std::ptr; use base::id::{ BlobId, DomExceptionId, DomPointId, ImageBitmapId, Index, MessagePortId, NamespaceIndex, - OffscreenCanvasId, PipelineNamespaceId, + OffscreenCanvasId, PipelineNamespaceId, QuotaExceededErrorId, }; use constellation_traits::{ BlobImpl, DomException, DomPoint, MessagePortImpl, Serializable as SerializableInterface, - SerializableImageBitmap, StructuredSerializedData, TransferableOffscreenCanvas, - Transferrable as TransferrableInterface, TransformStreamData, + SerializableImageBitmap, SerializableQuotaExceededError, StructuredSerializedData, + TransferableOffscreenCanvas, Transferrable as TransferrableInterface, TransformStreamData, }; use js::gc::RootedVec; use js::glue::{ @@ -49,7 +49,7 @@ use crate::dom::imagebitmap::ImageBitmap; use crate::dom::messageport::MessagePort; use crate::dom::offscreencanvas::OffscreenCanvas; use crate::dom::readablestream::ReadableStream; -use crate::dom::types::{DOMException, TransformStream}; +use crate::dom::types::{DOMException, QuotaExceededError, TransformStream}; use crate::dom::writablestream::WritableStream; use crate::realms::{AlreadyInRealm, InRealm, enter_realm}; use crate::script_runtime::{CanGc, JSContext as SafeJSContext}; @@ -73,6 +73,7 @@ pub(super) enum StructuredCloneTags { TransformStream = 0xFFFF8009, ImageBitmap = 0xFFFF800A, OffscreenCanvas = 0xFFFF800B, + QuotaExceededError = 0xFFFF800C, Max = 0xFFFFFFFF, } @@ -84,6 +85,7 @@ impl From for StructuredCloneTags { SerializableInterface::DomPoint => StructuredCloneTags::DomPoint, SerializableInterface::DomException => StructuredCloneTags::DomException, SerializableInterface::ImageBitmap => StructuredCloneTags::ImageBitmap, + SerializableInterface::QuotaExceededError => StructuredCloneTags::QuotaExceededError, } } } @@ -115,6 +117,7 @@ fn reader_for_type( SerializableInterface::DomPoint => read_object::, SerializableInterface::DomException => read_object::, SerializableInterface::ImageBitmap => read_object::, + SerializableInterface::QuotaExceededError => read_object::, } } @@ -259,6 +262,7 @@ fn serialize_for_type(val: SerializableInterface) -> SerializeOperation { SerializableInterface::DomPoint => try_serialize::, SerializableInterface::DomException => try_serialize::, SerializableInterface::ImageBitmap => try_serialize::, + SerializableInterface::QuotaExceededError => try_serialize::, } } @@ -570,6 +574,9 @@ pub(crate) struct StructuredDataReader<'a> { pub(crate) points: Option>, /// A map of serialized exceptions. pub(crate) exceptions: Option>, + /// A map of serialized quota exceeded errors. + pub(crate) quota_exceeded_errors: + Option>, // A map of serialized image bitmaps. pub(crate) image_bitmaps: Option>, /// A map of transferred image bitmaps. @@ -592,6 +599,9 @@ pub(crate) struct StructuredDataWriter { pub(crate) points: Option>, /// Serialized exceptions. pub(crate) exceptions: Option>, + /// Serialized quota exceeded errors. + pub(crate) quota_exceeded_errors: + Option>, /// Serialized blobs. pub(crate) blobs: Option>, /// Serialized image bitmaps. @@ -656,6 +666,7 @@ pub(crate) fn write( transform_streams: sc_writer.transform_streams_port.take(), points: sc_writer.points.take(), exceptions: sc_writer.exceptions.take(), + quota_exceeded_errors: sc_writer.quota_exceeded_errors.take(), blobs: sc_writer.blobs.take(), image_bitmaps: sc_writer.image_bitmaps.take(), transferred_image_bitmaps: sc_writer.transferred_image_bitmaps.take(), @@ -684,6 +695,7 @@ pub(crate) fn read( blob_impls: data.blobs.take(), points: data.points.take(), exceptions: data.exceptions.take(), + quota_exceeded_errors: data.quota_exceeded_errors.take(), image_bitmaps: data.image_bitmaps.take(), transferred_image_bitmaps: data.transferred_image_bitmaps.take(), offscreen_canvases: data.offscreen_canvases.take(), diff --git a/components/script/dom/quotaexceedederror.rs b/components/script/dom/quotaexceedederror.rs index 0e573cd5bed..0b75c7aef1d 100644 --- a/components/script/dom/quotaexceedederror.rs +++ b/components/script/dom/quotaexceedederror.rs @@ -2,6 +2,10 @@ * 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/. */ +use std::collections::HashMap; + +use base::id::{QuotaExceededErrorId, QuotaExceededErrorIndex}; +use constellation_traits::SerializableQuotaExceededError; use dom_struct::dom_struct; use js::gc::HandleObject; use script_bindings::codegen::GenericBindings::QuotaExceededErrorBinding::{ @@ -14,6 +18,8 @@ use script_bindings::str::DOMString; use crate::dom::bindings::error::Error; use crate::dom::bindings::reflector::{reflect_dom_object, reflect_dom_object_with_proto}; +use crate::dom::bindings::serializable::Serializable; +use crate::dom::bindings::structuredclone::StructuredData; use crate::dom::types::{DOMException, GlobalScope}; /// @@ -116,3 +122,53 @@ impl QuotaExceededErrorMethods for QuotaExceededError { self.requested } } + +impl Serializable for QuotaExceededError { + type Index = QuotaExceededErrorIndex; + type Data = SerializableQuotaExceededError; + + /// + fn serialize(&self) -> Result<(QuotaExceededErrorId, Self::Data), ()> { + let (_, dom_exception) = self.dom_exception.serialize()?; + let serialized = SerializableQuotaExceededError { + dom_exception, + quota: self.quota.as_deref().copied(), + requested: self.requested.as_deref().copied(), + }; + Ok((QuotaExceededErrorId::new(), serialized)) + } + + /// + fn deserialize( + owner: &GlobalScope, + serialized: Self::Data, + can_gc: CanGc, + ) -> Result, ()> + where + Self: Sized, + { + Ok(Self::new( + owner, + DOMString::from(serialized.dom_exception.message), + serialized + .quota + .map(|val| Finite::new(val).ok_or(())) + .transpose()?, + serialized + .requested + .map(|val| Finite::new(val).ok_or(())) + .transpose()?, + can_gc, + )) + } + + /// + fn serialized_storage<'a>( + data: StructuredData<'a, '_>, + ) -> &'a mut Option> { + match data { + StructuredData::Reader(reader) => &mut reader.quota_exceeded_errors, + StructuredData::Writer(writer) => &mut writer.quota_exceeded_errors, + } + } +} diff --git a/components/script_bindings/webidls/QuotaExceededError.webidl b/components/script_bindings/webidls/QuotaExceededError.webidl index 73c8fdcc953..b15e0a718a0 100644 --- a/components/script_bindings/webidls/QuotaExceededError.webidl +++ b/components/script_bindings/webidls/QuotaExceededError.webidl @@ -6,7 +6,7 @@ [ Exposed=(Window,Worker,Worklet,DissimilarOriginWindow), - // Serializable + Serializable ] interface QuotaExceededError : DOMException { [Throws] constructor(optional DOMString message = "", optional QuotaExceededErrorOptions options = {}); diff --git a/components/shared/base/id.rs b/components/shared/base/id.rs index 7510bb427a2..2f2debf5dec 100644 --- a/components/shared/base/id.rs +++ b/components/shared/base/id.rs @@ -368,6 +368,8 @@ namespace_id! {DomPointId, DomPointIndex, "DomPoint"} namespace_id! {DomExceptionId, DomExceptionIndex, "DomException"} +namespace_id! {QuotaExceededErrorId, QuotaExceededErrorIndex, "QuotaExceededError"} + namespace_id! {HistoryStateId, HistoryStateIndex, "HistoryState"} namespace_id! {ImageBitmapId, ImageBitmapIndex, "ImageBitmap"} diff --git a/components/shared/constellation/structured_data/mod.rs b/components/shared/constellation/structured_data/mod.rs index 4590a7b70e8..0424cc60f94 100644 --- a/components/shared/constellation/structured_data/mod.rs +++ b/components/shared/constellation/structured_data/mod.rs @@ -12,6 +12,7 @@ use std::collections::HashMap; use base::id::{ BlobId, DomExceptionId, DomPointId, ImageBitmapId, MessagePortId, OffscreenCanvasId, + QuotaExceededErrorId, }; use log::warn; use malloc_size_of_derive::MallocSizeOf; @@ -32,6 +33,9 @@ pub struct StructuredSerializedData { pub points: Option>, /// Serialized exception objects. pub exceptions: Option>, + /// Serialized quota exceeded errors. + pub quota_exceeded_errors: + Option>, /// Transferred objects. pub ports: Option>, /// Transform streams transferred objects. diff --git a/components/shared/constellation/structured_data/serializable.rs b/components/shared/constellation/structured_data/serializable.rs index 5731db66579..db442d981b6 100644 --- a/components/shared/constellation/structured_data/serializable.rs +++ b/components/shared/constellation/structured_data/serializable.rs @@ -11,7 +11,7 @@ use std::cell::RefCell; use std::collections::HashMap; use std::path::PathBuf; -use base::id::{BlobId, DomExceptionId, DomPointId, ImageBitmapId}; +use base::id::{BlobId, DomExceptionId, DomPointId, ImageBitmapId, QuotaExceededErrorId}; use malloc_size_of_derive::MallocSizeOf; use net_traits::filemanager_thread::RelativePos; use pixels::Snapshot; @@ -38,6 +38,9 @@ where } /// All the DOM interfaces that can be serialized. +/// +/// NOTE: Variants which are derived from other serializable interfaces must come before their +/// parents because serialization is attempted in order of the variants. #[derive(Clone, Copy, Debug, EnumIter)] pub enum Serializable { /// The `Blob` interface. @@ -46,6 +49,8 @@ pub enum Serializable { DomPoint, /// The `DOMPointReadOnly` interface. DomPointReadOnly, + /// The `QuotaExceededError` interface. + QuotaExceededError, /// The `DOMException` interface. DomException, /// The `ImageBitmap` interface. @@ -68,6 +73,9 @@ impl Serializable { Serializable::ImageBitmap => { StructuredSerializedData::clone_all_of_type:: }, + Serializable::QuotaExceededError => { + StructuredSerializedData::clone_all_of_type:: + }, } } } @@ -319,6 +327,30 @@ impl BroadcastClone for DomException { } } +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] +/// A serializable version of the QuotaExceededError interface. +pub struct SerializableQuotaExceededError { + pub dom_exception: DomException, + pub quota: Option, + pub requested: Option, +} + +impl BroadcastClone for SerializableQuotaExceededError { + type Id = QuotaExceededErrorId; + + fn source(data: &StructuredSerializedData) -> &Option> { + &data.quota_exceeded_errors + } + + fn destination(data: &mut StructuredSerializedData) -> &mut Option> { + &mut data.quota_exceeded_errors + } + + fn clone_for_broadcast(&self) -> Option { + Some(self.clone()) + } +} + #[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] /// A serializable version of the ImageBitmap interface. pub struct SerializableImageBitmap { diff --git a/tests/wpt/meta/MANIFEST.json b/tests/wpt/meta/MANIFEST.json index ee6e7acd616..28787f41501 100644 --- a/tests/wpt/meta/MANIFEST.json +++ b/tests/wpt/meta/MANIFEST.json @@ -735329,7 +735329,7 @@ ] ], "structuredclone_0.html": [ - "c8a6d38393c0217447992d47c994942e873e70ad", + "0d412baa701d5f1a5efb43bb470ca6aff849e082", [ null, {} diff --git a/tests/wpt/tests/html/infrastructure/safe-passing-of-structured-data/structuredclone_0.html b/tests/wpt/tests/html/infrastructure/safe-passing-of-structured-data/structuredclone_0.html index c8a6d38393c..0d412baa701 100644 --- a/tests/wpt/tests/html/infrastructure/safe-passing-of-structured-data/structuredclone_0.html +++ b/tests/wpt/tests/html/infrastructure/safe-passing-of-structured-data/structuredclone_0.html @@ -605,6 +605,42 @@ assert_unreached("Window must not be clonable"); }); }, + function() { + var t = async_test("QuotaExceededError objects can be cloned"); + t.id = 41; + worker.onmessage = t.step_func_done(function(e) { + assert_equals(Object.getPrototypeOf(e.data), QuotaExceededError.prototype, "Checking prototype"); + assert_equals(e.data.constructor, QuotaExceededError, "Checking constructor"); + assert_equals(e.data.name, "QuotaExceededError", "Checking name"); + assert_equals(e.data.message, "some message", "Checking message"); + assert_equals(e.data.code, DOMException.QUOTA_EXCEEDED_ERR, "Checking code"); + assert_equals(e.data.quota, 0.1, "Checking quota"); + assert_equals(e.data.requested, 1.0, "Checking requested"); + assert_equals(e.data.foo, undefined, "Checking custom property"); + }); + t.step(function() { + const error = new QuotaExceededError(message = "some message", options = {quota: 0.1, requested: 1.0}); + worker.postMessage(error); + }); + }, + function() { + var t = async_test("QuotaExceededError objects with empty quota/requested can be cloned"); + t.id = 42; + worker.onmessage = t.step_func_done(function(e) { + assert_equals(Object.getPrototypeOf(e.data), QuotaExceededError.prototype, "Checking prototype"); + assert_equals(e.data.constructor, QuotaExceededError, "Checking constructor"); + assert_equals(e.data.name, "QuotaExceededError", "Checking name"); + assert_equals(e.data.message, "some message", "Checking message"); + assert_equals(e.data.code, DOMException.QUOTA_EXCEEDED_ERR, "Checking code"); + assert_equals(e.data.quota, null, "Checking quota"); + assert_equals(e.data.requested, null, "Checking requested"); + assert_equals(e.data.foo, undefined, "Checking custom property"); + }); + t.step(function() { + const error = new QuotaExceededError(message = "some message"); + worker.postMessage(error); + }); + }, ]; }, {explicit_done:true});