Implement disabled attribute for <link rel="stylesheet"> (#36446)

Adds support for both the content and the IDL attribute.
Note this doesn't cover dynamic updates to `document.styleSheets` and
the owner node of the sheet.

Testing: Covered by WPT
Fixes: #26739

Signed-off-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
Oriol Brufau 2025-04-10 09:28:01 -07:00 committed by GitHub
parent e62aecb103
commit 2d40fb7fe2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 46 additions and 28 deletions

View file

@ -114,6 +114,8 @@ pub(crate) struct HTMLLinkElement {
any_failed_load: Cell<bool>, any_failed_load: Cell<bool>,
/// A monotonically increasing counter that keeps track of which stylesheet to apply. /// A monotonically increasing counter that keeps track of which stylesheet to apply.
request_generation_id: Cell<RequestGenerationId>, request_generation_id: Cell<RequestGenerationId>,
/// <https://html.spec.whatwg.org/multipage/#explicitly-enabled>
is_explicitly_enabled: Cell<bool>,
} }
impl HTMLLinkElement { impl HTMLLinkElement {
@ -133,6 +135,7 @@ impl HTMLLinkElement {
pending_loads: Cell::new(0), pending_loads: Cell::new(0),
any_failed_load: Cell::new(false), any_failed_load: Cell::new(false),
request_generation_id: Cell::new(RequestGenerationId(0)), request_generation_id: Cell::new(RequestGenerationId(0)),
is_explicitly_enabled: Cell::new(false),
} }
} }
@ -196,6 +199,12 @@ impl HTMLLinkElement {
self.relations.get().contains(LinkRelations::ALTERNATE) self.relations.get().contains(LinkRelations::ALTERNATE)
} }
pub(crate) fn is_effectively_disabled(&self) -> bool {
(self.is_alternate() && !self.is_explicitly_enabled.get()) ||
self.upcast::<Element>()
.has_attribute(&local_name!("disabled"))
}
fn clean_stylesheet_ownership(&self) { fn clean_stylesheet_ownership(&self) {
if let Some(cssom_stylesheet) = self.cssom_stylesheet.get() { if let Some(cssom_stylesheet) = self.cssom_stylesheet.get() {
cssom_stylesheet.set_owner(None); cssom_stylesheet.set_owner(None);
@ -221,11 +230,18 @@ impl VirtualMethods for HTMLLinkElement {
self.super_type() self.super_type()
.unwrap() .unwrap()
.attribute_mutated(attr, mutation, can_gc); .attribute_mutated(attr, mutation, can_gc);
if !self.upcast::<Node>().is_connected() || mutation.is_removal() {
let local_name = attr.local_name();
let is_removal = mutation.is_removal();
if *local_name == local_name!("disabled") {
self.handle_disabled_attribute_change(!is_removal);
return; return;
} }
match *attr.local_name() { if !self.upcast::<Node>().is_connected() || is_removal {
return;
}
match *local_name {
local_name!("rel") | local_name!("rev") => { local_name!("rel") | local_name!("rev") => {
self.relations self.relations
.set(LinkRelations::for_element(self.upcast())); .set(LinkRelations::for_element(self.upcast()));
@ -472,6 +488,18 @@ impl HTMLLinkElement {
); );
} }
/// <https://html.spec.whatwg.org/multipage/#attr-link-disabled>
fn handle_disabled_attribute_change(&self, disabled: bool) {
if !disabled {
self.is_explicitly_enabled.set(true);
}
if let Some(stylesheet) = self.get_stylesheet() {
if stylesheet.set_disabled(disabled) {
self.stylesheet_list_owner().invalidate_stylesheets();
}
}
}
fn handle_favicon_url(&self, href: &str, _sizes: &Option<String>) { fn handle_favicon_url(&self, href: &str, _sizes: &Option<String>) {
let document = self.owner_document(); let document = self.owner_document();
match document.base_url().join(href) { match document.base_url().join(href) {
@ -567,6 +595,12 @@ impl HTMLLinkElementMethods<crate::DomTypeHolder> for HTMLLinkElement {
// https://html.spec.whatwg.org/multipage/#dom-link-type // https://html.spec.whatwg.org/multipage/#dom-link-type
make_setter!(SetType, "type"); make_setter!(SetType, "type");
// https://html.spec.whatwg.org/multipage/#dom-link-disabled
make_bool_getter!(Disabled, "disabled");
// https://html.spec.whatwg.org/multipage/#dom-link-disabled
make_bool_setter!(SetDisabled, "disabled");
// https://html.spec.whatwg.org/multipage/#dom-link-rellist // https://html.spec.whatwg.org/multipage/#dom-link-rellist
fn RelList(&self) -> DomRoot<DOMTokenList> { fn RelList(&self) -> DomRoot<DOMTokenList> {
self.rel_list.or_init(|| { self.rel_list.or_init(|| {

View file

@ -214,7 +214,7 @@ impl FetchResponseListener for StylesheetContext {
document.quirks_mode(), document.quirks_mode(),
)); ));
if link.is_alternate() { if link.is_effectively_disabled() {
sheet.set_disabled(true); sheet.set_disabled(true);
} }

View file

@ -13,17 +13,24 @@ interface HTMLLinkElement : HTMLElement {
attribute DOMString? crossOrigin; attribute DOMString? crossOrigin;
[CEReactions] [CEReactions]
attribute DOMString rel; attribute DOMString rel;
// [CEReactions] attribute DOMString as;
[SameObject, PutForwards=value] readonly attribute DOMTokenList relList; [SameObject, PutForwards=value] readonly attribute DOMTokenList relList;
[CEReactions] [CEReactions]
attribute DOMString media; attribute DOMString media;
[CEReactions]
attribute DOMString integrity;
[CEReactions] [CEReactions]
attribute DOMString hreflang; attribute DOMString hreflang;
[CEReactions] [CEReactions]
attribute DOMString type; attribute DOMString type;
[CEReactions] // [SameObject, PutForwards=value] readonly attribute DOMTokenList sizes;
attribute DOMString integrity; // [CEReactions] attribute USVString imageSrcset;
// [CEReactions] attribute DOMString imageSizes;
[CEReactions] [CEReactions]
attribute DOMString referrerPolicy; attribute DOMString referrerPolicy;
// [SameObject, PutForwards=value] readonly attribute DOMTokenList blocking;
[CEReactions] attribute boolean disabled;
// [CEReactions] attribute DOMString fetchPriority;
// also has obsolete members // also has obsolete members
}; };

View file

@ -1,4 +0,0 @@
[HTMLLinkElement-disabled-003.html]
[HTMLLinkElement.disabled's explicitly enabled state persists when disconnected and connected again]
expected: FAIL

View file

@ -1,4 +0,0 @@
[HTMLLinkElement-disabled-004.html]
[HTMLLinkElement.disabled's explicitly enabled state doesn't persist on clones]
expected: FAIL

View file

@ -1,4 +0,0 @@
[HTMLLinkElement-disabled-007.html]
[HTMLLinkElement.disabled setter sets the explicitly enabled state if toggled back and forth.]
expected: FAIL

View file

@ -1,2 +0,0 @@
[HTMLLinkElement-disabled-alternate.html]
expected: FAIL

View file

@ -6052,9 +6052,6 @@
[HTMLLinkElement interface: attribute blocking] [HTMLLinkElement interface: attribute blocking]
expected: FAIL expected: FAIL
[HTMLLinkElement interface: attribute disabled]
expected: FAIL
[HTMLLinkElement interface: attribute fetchPriority] [HTMLLinkElement interface: attribute fetchPriority]
expected: FAIL expected: FAIL
@ -6073,9 +6070,6 @@
[HTMLLinkElement interface: document.createElement("link") must inherit property "blocking" with the proper type] [HTMLLinkElement interface: document.createElement("link") must inherit property "blocking" with the proper type]
expected: FAIL expected: FAIL
[HTMLLinkElement interface: document.createElement("link") must inherit property "disabled" with the proper type]
expected: FAIL
[HTMLLinkElement interface: document.createElement("link") must inherit property "fetchPriority" with the proper type] [HTMLLinkElement interface: document.createElement("link") must inherit property "fetchPriority" with the proper type]
expected: FAIL expected: FAIL

View file

@ -1,7 +1,4 @@
[subresource-integrity.html] [subresource-integrity.html]
[Style: Same-origin with correct sha256 and sha512 hash, rel='alternate stylesheet' enabled]
expected: FAIL
[Script: Same-origin with non-Base64 hash.] [Script: Same-origin with non-Base64 hash.]
expected: FAIL expected: FAIL