From af74db4e926aff1dfef854d9856a4549ad5128ea Mon Sep 17 00:00:00 2001 From: Andrei Volykhin Date: Mon, 29 Sep 2025 12:00:51 +0300 Subject: [PATCH] html: Count attributes state changes as the relevant mutations (#39483) Follow the HTML specification and take into account that state changes of the 'crossorigin' and 'referrerpolicy' content attributes (not 'crossOrigin' and 'referrerPolicy' IDL attributes) should be counted as relevant mutations. See https://html.spec.whatwg.org/multipage/#relevant-mutations Testing: Improvements in the following tests - html/dom/reflection-embedded.html - html/semantics/embedded-content/the-img-element/relevant-mutations.html Signed-off-by: Andrei Volykhin --- components/script/dom/element.rs | 71 ++++---- .../script/dom/html/htmlimageelement.rs | 86 ++++++---- .../script_bindings/codegen/Bindings.conf | 2 +- components/shared/net/request.rs | 11 ++ .../html/dom/reflection-embedded.html.ini | 162 ------------------ .../relevant-mutations.html.ini | 9 - 6 files changed, 96 insertions(+), 245 deletions(-) delete mode 100644 tests/wpt/meta/html/semantics/embedded-content/the-img-element/relevant-mutations.html.ini diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index 0086bf518f4..5cb0ddf6e28 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -5428,17 +5428,18 @@ impl TaskOnce for ElementPerformFullscreenExit { } } +/// pub(crate) fn reflect_cross_origin_attribute(element: &Element) -> Option { - let attr = element.get_attribute(&ns!(), &local_name!("crossorigin")); - - if let Some(mut val) = attr.map(|v| v.Value()) { - val.make_ascii_lowercase(); - if val == "anonymous" || val == "use-credentials" { - return Some(val); - } - return Some(DOMString::from("anonymous")); - } - None + element + .get_attribute(&ns!(), &local_name!("crossorigin")) + .map(|attribute| { + let value = attribute.value().to_ascii_lowercase(); + if value == "anonymous" || value == "use-credentials" { + DOMString::from(value) + } else { + DOMString::from("anonymous") + } + }) } pub(crate) fn set_cross_origin_attribute( @@ -5454,38 +5455,38 @@ pub(crate) fn set_cross_origin_attribute( } } +/// pub(crate) fn reflect_referrer_policy_attribute(element: &Element) -> DOMString { - let attr = - element.get_attribute_by_name(DOMString::from_string(String::from("referrerpolicy"))); - - if let Some(mut val) = attr.map(|v| v.Value()) { - val.make_ascii_lowercase(); - if val == "no-referrer" || - val == "no-referrer-when-downgrade" || - val == "same-origin" || - val == "origin" || - val == "strict-origin" || - val == "origin-when-cross-origin" || - val == "strict-origin-when-cross-origin" || - val == "unsafe-url" - { - return val; - } - } - DOMString::new() + element + .get_attribute(&ns!(), &local_name!("referrerpolicy")) + .map(|attribute| { + let value = attribute.value().to_ascii_lowercase(); + if value == "no-referrer" || + value == "no-referrer-when-downgrade" || + value == "same-origin" || + value == "origin" || + value == "strict-origin" || + value == "origin-when-cross-origin" || + value == "strict-origin-when-cross-origin" || + value == "unsafe-url" + { + DOMString::from(value) + } else { + DOMString::new() + } + }) + .unwrap_or_default() } pub(crate) fn referrer_policy_for_element(element: &Element) -> ReferrerPolicy { element - .get_attribute_by_name(DOMString::from_string(String::from("referrerpolicy"))) - .map(|attribute: DomRoot| determine_policy_for_token(attribute.Value().str())) + .get_attribute(&ns!(), &local_name!("referrerpolicy")) + .map(|attribute| determine_policy_for_token(&attribute.value())) .unwrap_or(element.owner_document().get_referrer_policy()) } pub(crate) fn cors_setting_for_element(element: &Element) -> Option { - reflect_cross_origin_attribute(element).and_then(|attr| match attr.str() { - "anonymous" => Some(CorsSettings::Anonymous), - "use-credentials" => Some(CorsSettings::UseCredentials), - _ => unreachable!(), - }) + element + .get_attribute(&ns!(), &local_name!("crossorigin")) + .map(|attribute| CorsSettings::from_enumerated_attribute(&attribute.value())) } diff --git a/components/script/dom/html/htmlimageelement.rs b/components/script/dom/html/htmlimageelement.rs index aa1fd130a81..1502e9753bf 100644 --- a/components/script/dom/html/htmlimageelement.rs +++ b/components/script/dom/html/htmlimageelement.rs @@ -22,7 +22,7 @@ use net_traits::image_cache::{ Image, ImageCache, ImageCacheResult, ImageLoadListener, ImageOrMetadataAvailable, ImageResponse, PendingImageId, UsePlaceholder, }; -use net_traits::request::{Destination, Initiator, RequestId}; +use net_traits::request::{CorsSettings, Destination, Initiator, RequestId}; use net_traits::{ FetchMetadata, FetchResponseListener, FetchResponseMsg, NetworkError, ReferrerPolicy, ResourceFetchTiming, ResourceTimingType, @@ -1603,22 +1603,6 @@ fn parse_a_sizes_attribute(value: &str) -> SourceSizeList { SourceSizeList::parse(&context, &mut parser) } -fn get_correct_referrerpolicy_from_raw_token(token: &DOMString) -> DOMString { - if token.is_empty() { - // Empty token is treated as the default referrer policy inside determine_policy_for_token, - // so it should remain unchanged. - DOMString::new() - } else { - let policy = determine_policy_for_token(token.str()); - - if policy == ReferrerPolicy::EmptyString { - return DOMString::new(); - } - - DOMString::from_string(policy.to_string()) - } -} - #[allow(non_snake_case)] impl HTMLImageElementMethods for HTMLImageElement { // https://html.spec.whatwg.org/multipage/#dom-image @@ -1784,24 +1768,8 @@ impl HTMLImageElementMethods for HTMLImageElement { reflect_referrer_policy_attribute(self.upcast::()) } - // https://html.spec.whatwg.org/multipage/#dom-img-referrerpolicy - fn SetReferrerPolicy(&self, value: DOMString, can_gc: CanGc) { - let referrerpolicy_attr_name = local_name!("referrerpolicy"); - let element = self.upcast::(); - let previous_correct_attribute_value = get_correct_referrerpolicy_from_raw_token( - &element.get_string_attribute(&referrerpolicy_attr_name), - ); - let correct_value_or_empty_string = get_correct_referrerpolicy_from_raw_token(&value); - if previous_correct_attribute_value != correct_value_or_empty_string { - // Setting the attribute to the same value will update the image. - // We don't want to start an update if referrerpolicy is set to the same value. - element.set_string_attribute( - &referrerpolicy_attr_name, - correct_value_or_empty_string, - can_gc, - ); - } - } + // + make_setter!(SetReferrerPolicy, "referrerpolicy"); /// fn Decode(&self, can_gc: CanGc) -> Rc { @@ -1874,9 +1842,51 @@ impl VirtualMethods for HTMLImageElement { &local_name!("src") | &local_name!("srcset") | &local_name!("width") | - &local_name!("crossorigin") | - &local_name!("sizes") | - &local_name!("referrerpolicy") => self.update_the_image_data(can_gc), + &local_name!("sizes") => { + // + // The element's src, srcset, width, or sizes attributes are set, changed, or + // removed. + self.update_the_image_data(can_gc); + }, + &local_name!("crossorigin") => { + // + // The element's crossorigin attribute's state is changed. + let cross_origin_state_changed = match mutation { + AttributeMutation::Removed | AttributeMutation::Set(None) => true, + AttributeMutation::Set(Some(old_value)) => { + let new_cors_setting = + CorsSettings::from_enumerated_attribute(&attr.value()); + let old_cors_setting = CorsSettings::from_enumerated_attribute(old_value); + + new_cors_setting != old_cors_setting + }, + }; + + if cross_origin_state_changed { + self.update_the_image_data(can_gc); + } + }, + &local_name!("referrerpolicy") => { + // + // The element's referrerpolicy attribute's state is changed. + let referrer_policy_state_changed = match mutation { + AttributeMutation::Removed | AttributeMutation::Set(None) => { + let referrer_policy = determine_policy_for_token(&attr.value()); + + referrer_policy != ReferrerPolicy::EmptyString + }, + AttributeMutation::Set(Some(old_value)) => { + let new_referrer_policy = determine_policy_for_token(&attr.value()); + let old_referrer_policy = determine_policy_for_token(old_value); + + new_referrer_policy != old_referrer_policy + }, + }; + + if referrer_policy_state_changed { + self.update_the_image_data(can_gc); + } + }, _ => {}, } } diff --git a/components/script_bindings/codegen/Bindings.conf b/components/script_bindings/codegen/Bindings.conf index 6a7ac7dbfb0..2b96a6ffa1d 100644 --- a/components/script_bindings/codegen/Bindings.conf +++ b/components/script_bindings/codegen/Bindings.conf @@ -394,7 +394,7 @@ DOMInterfaces = { }, 'HTMLImageElement': { - 'canGc': ['RequestSubmit', 'ReportValidity', 'Reset','SetRel', 'Decode', 'SetCrossOrigin', 'SetWidth', 'SetHeight', 'SetReferrerPolicy'], + 'canGc': ['RequestSubmit', 'ReportValidity', 'Reset','SetRel', 'Decode', 'SetCrossOrigin', 'SetWidth', 'SetHeight'], }, 'HTMLInputElement': { diff --git a/components/shared/net/request.rs b/components/shared/net/request.rs index 5fafa8ada00..3947e58ce54 100644 --- a/components/shared/net/request.rs +++ b/components/shared/net/request.rs @@ -137,6 +137,17 @@ pub enum CorsSettings { UseCredentials, } +impl CorsSettings { + /// + pub fn from_enumerated_attribute(value: &str) -> CorsSettings { + match value.to_ascii_lowercase().as_str() { + "anonymous" => CorsSettings::Anonymous, + "use-credentials" => CorsSettings::UseCredentials, + _ => CorsSettings::Anonymous, + } + } +} + /// [Parser Metadata](https://fetch.spec.whatwg.org/#concept-request-parser-metadata) #[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] pub enum ParserMetadata { diff --git a/tests/wpt/meta/html/dom/reflection-embedded.html.ini b/tests/wpt/meta/html/dom/reflection-embedded.html.ini index 679057f2794..aed03a46f56 100644 --- a/tests/wpt/meta/html/dom/reflection-embedded.html.ini +++ b/tests/wpt/meta/html/dom/reflection-embedded.html.ini @@ -503,168 +503,6 @@ [img.srcset: IDL set to object "test-valueOf"] expected: FAIL - [img.referrerPolicy: IDL set to ""] - expected: FAIL - - [img.referrerPolicy: IDL set to " \\0\\x01\\x02\\x03\\x04\\x05\\x06\\x07 \\b\\t\\n\\v\\f\\r\\x0e\\x0f \\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17 \\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f foo "] - expected: FAIL - - [img.referrerPolicy: IDL set to undefined] - expected: FAIL - - [img.referrerPolicy: IDL set to 7] - expected: FAIL - - [img.referrerPolicy: IDL set to 1.5] - expected: FAIL - - [img.referrerPolicy: IDL set to "5%"] - expected: FAIL - - [img.referrerPolicy: IDL set to "+100"] - expected: FAIL - - [img.referrerPolicy: IDL set to ".5"] - expected: FAIL - - [img.referrerPolicy: IDL set to true] - expected: FAIL - - [img.referrerPolicy: IDL set to false] - expected: FAIL - - [img.referrerPolicy: IDL set to object "[object Object\]"] - expected: FAIL - - [img.referrerPolicy: IDL set to NaN] - expected: FAIL - - [img.referrerPolicy: IDL set to Infinity] - expected: FAIL - - [img.referrerPolicy: IDL set to -Infinity] - expected: FAIL - - [img.referrerPolicy: IDL set to "\\0"] - expected: FAIL - - [img.referrerPolicy: IDL set to object "test-toString"] - expected: FAIL - - [img.referrerPolicy: IDL set to object "test-valueOf"] - expected: FAIL - - [img.referrerPolicy: IDL set to "xno-referrer"] - expected: FAIL - - [img.referrerPolicy: IDL set to "no-referrer\\0"] - expected: FAIL - - [img.referrerPolicy: IDL set to "o-referrer"] - expected: FAIL - - [img.referrerPolicy: IDL set to "NO-REFERRER"] - expected: FAIL - - [img.referrerPolicy: IDL set to "xno-referrer-when-downgrade"] - expected: FAIL - - [img.referrerPolicy: IDL set to "no-referrer-when-downgrade\\0"] - expected: FAIL - - [img.referrerPolicy: IDL set to "o-referrer-when-downgrade"] - expected: FAIL - - [img.referrerPolicy: IDL set to "NO-REFERRER-WHEN-DOWNGRADE"] - expected: FAIL - - [img.referrerPolicy: IDL set to "xsame-origin"] - expected: FAIL - - [img.referrerPolicy: IDL set to "same-origin\\0"] - expected: FAIL - - [img.referrerPolicy: IDL set to "ame-origin"] - expected: FAIL - - [img.referrerPolicy: IDL set to "SAME-ORIGIN"] - expected: FAIL - - [img.referrerPolicy: IDL set to "ſame-origin"] - expected: FAIL - - [img.referrerPolicy: IDL set to "xorigin"] - expected: FAIL - - [img.referrerPolicy: IDL set to "origin\\0"] - expected: FAIL - - [img.referrerPolicy: IDL set to "rigin"] - expected: FAIL - - [img.referrerPolicy: IDL set to "ORIGIN"] - expected: FAIL - - [img.referrerPolicy: IDL set to "xstrict-origin"] - expected: FAIL - - [img.referrerPolicy: IDL set to "strict-origin\\0"] - expected: FAIL - - [img.referrerPolicy: IDL set to "trict-origin"] - expected: FAIL - - [img.referrerPolicy: IDL set to "STRICT-ORIGIN"] - expected: FAIL - - [img.referrerPolicy: IDL set to "ſtrict-origin"] - expected: FAIL - - [img.referrerPolicy: IDL set to "xorigin-when-cross-origin"] - expected: FAIL - - [img.referrerPolicy: IDL set to "origin-when-cross-origin\\0"] - expected: FAIL - - [img.referrerPolicy: IDL set to "rigin-when-cross-origin"] - expected: FAIL - - [img.referrerPolicy: IDL set to "ORIGIN-WHEN-CROSS-ORIGIN"] - expected: FAIL - - [img.referrerPolicy: IDL set to "origin-when-croſſ-origin"] - expected: FAIL - - [img.referrerPolicy: IDL set to "xstrict-origin-when-cross-origin"] - expected: FAIL - - [img.referrerPolicy: IDL set to "strict-origin-when-cross-origin\\0"] - expected: FAIL - - [img.referrerPolicy: IDL set to "trict-origin-when-cross-origin"] - expected: FAIL - - [img.referrerPolicy: IDL set to "STRICT-ORIGIN-WHEN-CROSS-ORIGIN"] - expected: FAIL - - [img.referrerPolicy: IDL set to "ſtrict-origin-when-croſſ-origin"] - expected: FAIL - - [img.referrerPolicy: IDL set to "xunsafe-url"] - expected: FAIL - - [img.referrerPolicy: IDL set to "unsafe-url\\0"] - expected: FAIL - - [img.referrerPolicy: IDL set to "nsafe-url"] - expected: FAIL - - [img.referrerPolicy: IDL set to "UNSAFE-URL"] - expected: FAIL - - [img.referrerPolicy: IDL set to "unſafe-url"] - expected: FAIL - [img.decoding: typeof IDL attribute] expected: FAIL diff --git a/tests/wpt/meta/html/semantics/embedded-content/the-img-element/relevant-mutations.html.ini b/tests/wpt/meta/html/semantics/embedded-content/the-img-element/relevant-mutations.html.ini deleted file mode 100644 index 089c3ea5915..00000000000 --- a/tests/wpt/meta/html/semantics/embedded-content/the-img-element/relevant-mutations.html.ini +++ /dev/null @@ -1,9 +0,0 @@ -[relevant-mutations.html] - [crossorigin state not changed: empty to anonymous] - expected: FAIL - - [crossorigin state not changed: use-credentials to USE-CREDENTIALS] - expected: FAIL - - [crossorigin state not changed: anonymous to foobar] - expected: FAIL