html: Count <image> attributes state changes as the relevant mutations (#39483)

Follow the HTML specification and take into account that state changes
of the <image> '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 <andrei.volykhin@gmail.com>
This commit is contained in:
Andrei Volykhin 2025-09-29 12:00:51 +03:00 committed by GitHub
parent aaa7f83176
commit af74db4e92
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 96 additions and 245 deletions

View file

@ -5428,17 +5428,18 @@ impl TaskOnce for ElementPerformFullscreenExit {
}
}
/// <https://html.spec.whatwg.org/multipage/#cors-settings-attribute>
pub(crate) fn reflect_cross_origin_attribute(element: &Element) -> Option<DOMString> {
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(
}
}
/// <https://html.spec.whatwg.org/multipage/#referrer-policy-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<Attr>| 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<CorsSettings> {
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()))
}

View file

@ -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<crate::DomTypeHolder> for HTMLImageElement {
// https://html.spec.whatwg.org/multipage/#dom-image
@ -1784,24 +1768,8 @@ impl HTMLImageElementMethods<crate::DomTypeHolder> for HTMLImageElement {
reflect_referrer_policy_attribute(self.upcast::<Element>())
}
// 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::<Element>();
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,
);
}
}
// <https://html.spec.whatwg.org/multipage/#dom-img-referrerpolicy>
make_setter!(SetReferrerPolicy, "referrerpolicy");
/// <https://html.spec.whatwg.org/multipage/#dom-img-decode>
fn Decode(&self, can_gc: CanGc) -> Rc<Promise> {
@ -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") => {
// <https://html.spec.whatwg.org/multipage/#reacting-to-dom-mutations>
// The element's src, srcset, width, or sizes attributes are set, changed, or
// removed.
self.update_the_image_data(can_gc);
},
&local_name!("crossorigin") => {
// <https://html.spec.whatwg.org/multipage/#reacting-to-dom-mutations>
// 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") => {
// <https://html.spec.whatwg.org/multipage/#reacting-to-dom-mutations>
// 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);
}
},
_ => {},
}
}

View file

@ -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': {

View file

@ -137,6 +137,17 @@ pub enum CorsSettings {
UseCredentials,
}
impl CorsSettings {
/// <https://html.spec.whatwg.org/multipage/#cors-settings-attribute>
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 {

View file

@ -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

View file

@ -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