/
githubmirror
/
servo
Обзор
Документация
Войти
/
githubmirror
/
servo
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
components/script/dom/css/cssstyledeclaration.rs
618 строк
22 KB
Narfinger
script: Cleanup custom node dirty inclusive ancestor walk (#46981)
04 авг 2026, 20:35
Не верифицирован
04 авг 2026, 20:35
21faece
Код
Авторство
О чём код?
/* This Source Code Form is subject to the terms of the Mozilla Public * 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::cell::RefCell; use std::cmp::Ordering; use std::sync::LazyLock; use dom_struct::dom_struct; use html5ever::local_name; use js::context::JSContext; use script_bindings::reflector::{Reflector, reflect_dom_object_with_cx}; use servo_arc::Arc; use servo_url::ServoUrl; use style::attr::AttrValue; use style::properties::{ Importance, LonghandId, PropertyDeclarationBlock, PropertyId, ShorthandId, SourcePropertyDeclaration, parse_one_declaration_into, parse_style_attribute, }; use style::selector_parser::PseudoElement; use style::shared_lock::Locked; use style::stylesheets::{CssRuleType, Origin, StylesheetInDocument, UrlExtraData}; use style_traits::ParsingMode; use super::cssrule::CSSRule; use crate::dom::bindings::codegen::Bindings::CSSStyleDeclarationBinding::CSSStyleDeclarationMethods; use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::reflector::DomGlobal; use crate::dom::bindings::root::{Dom, DomRoot}; use crate::dom::bindings::str::DOMString; use crate::dom::element::Element; use crate::dom::node::{Node, NodeTraits}; use crate::dom::window::Window; // http://dev.w3.org/csswg/cssom/#the-cssstyledeclaration-interface #[dom_struct] pub(crate) struct CSSStyleDeclaration { reflector_: Reflector, owner: CSSStyleOwner, readonly: bool, #[no_trace] pseudo: Option<PseudoElement>, } #[derive(JSTraceable, MallocSizeOf)] #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] pub(crate) enum CSSStyleOwner { /// Used when calling `getComputedStyle()` with an invalid pseudo-element selector. /// See <https://drafts.csswg.org/cssom/#dom-window-getcomputedstyle> Null, Element(Dom<Element>), CSSRule( Dom<CSSRule>, #[ignore_malloc_size_of = "Stylo"] #[no_trace] RefCell<Arc<Locked<PropertyDeclarationBlock>>>, ), } impl CSSStyleOwner { // Mutate the declaration block associated to this style owner, and // optionally indicate if it has changed (assumed to be true). fn mutate_associated_block<F, R>(&self, cx: &mut JSContext, f: F) -> R where F: FnOnce(&mut PropertyDeclarationBlock, &mut bool) -> R, { // TODO(emilio): This has some duplication just to avoid dummy clones. // // This is somewhat complex but the complexity is encapsulated. let mut changed = true; match *self { CSSStyleOwner::Null => unreachable!( "CSSStyleDeclaration should always be read-only when CSSStyleOwner is Null" ), CSSStyleOwner::Element(ref element) => { let document = element.owner_document(); let shared_lock = document.style_shared_author_lock(); let mut attribute_pdb = element.style_attribute().safe_borrow_mut(cx.no_gc()).take(); // When there are mutation observers, the old PDB and the new PDB need to // co-exist so that both attribute values can be serialized and sent to // the observer. In that case, first clone the block and mutate that instead // of mutating the existing one. if element.needs_preserved_style_attribute_after_change() { attribute_pdb = attribute_pdb.map(|attribute_pdb| { let new_pdb = attribute_pdb.read_with(&shared_lock.read()).clone(); Arc::new(shared_lock.wrap(new_pdb)) }); } let (attribute_pdb, result) = if let Some(attribute_pdb) = attribute_pdb { let mut guard = shared_lock.write(); let writable_pdb = attribute_pdb.write_with(&mut guard); let result = f(writable_pdb, &mut changed); (attribute_pdb, result) } else { let mut new_pdb = PropertyDeclarationBlock::new(); let result = f(&mut new_pdb, &mut changed); changed = !new_pdb.declarations().is_empty(); if !changed { return result; } (Arc::new(shared_lock.wrap(new_pdb)), result) }; // If nothing changed, replace the unchanged PDB in the attribute and just // return. Note that there's no need to remove the attribute here if the // declaration block is empty (see // https://github.com/whatwg/html/issues/2306). if !changed { *element.style_attribute().safe_borrow_mut(cx.no_gc()) = Some(attribute_pdb); return result; } element.set_attribute( cx, &local_name!("style"), AttrValue::from_declaration(attribute_pdb, shared_lock.clone()), ); result }, CSSStyleOwner::CSSRule(ref rule, ref pdb) => { rule.parent_stylesheet().will_modify(); let result = { let mut guard = rule.shared_lock().write(); f(&mut *pdb.borrow().write_with(&mut guard), &mut changed) }; if changed { rule.parent_stylesheet().notify_invalidations(cx.no_gc()); } result }, } } fn with_block<F, R>(&self, f: F) -> R where F: FnOnce(&PropertyDeclarationBlock) -> R, { match *self { CSSStyleOwner::Null => { unreachable!("Should never call with_block for CSStyleOwner::Null") }, CSSStyleOwner::Element(ref el) => match *el.style_attribute().borrow() { Some(ref pdb) => { let document = el.owner_document(); let guard = document.style_shared_author_lock().read(); f(pdb.read_with(&guard)) }, None => { let pdb = PropertyDeclarationBlock::new(); f(&pdb) }, }, CSSStyleOwner::CSSRule(ref rule, ref pdb) => { let guard = rule.shared_lock().read(); f(pdb.borrow().read_with(&guard)) }, } } fn window(&self) -> DomRoot<Window> { match *self { CSSStyleOwner::Null => { unreachable!("Should never try to access window of CSStyleOwner::Null") }, CSSStyleOwner::Element(ref el) => el.owner_window(), CSSStyleOwner::CSSRule(ref rule, _) => DomRoot::from_ref(rule.global().as_window()), } } fn base_url(&self) -> ServoUrl { match *self { CSSStyleOwner::Null => { unreachable!("Should never try to access base URL of CSStyleOwner::Null") }, CSSStyleOwner::Element(ref el) => el.owner_document().base_url(), CSSStyleOwner::CSSRule(ref rule, _) => ServoUrl::from({ let guard = rule.shared_lock().read(); rule.parent_stylesheet() .style_stylesheet() .contents(&guard) .url_data .0 .clone() }), } } } #[derive(MallocSizeOf, PartialEq)] pub(crate) enum CSSModificationAccess { ReadWrite, Readonly, } macro_rules! css_properties( ( $([$getter:ident, $setter:ident, $id:expr],)* ) => ( $( fn $getter(&self) -> DOMString { debug_assert!( $id.enabled_for_all_content(), "Someone forgot a #[Pref] annotation" ); self.get_property_value($id) } fn $setter(&self, cx: &mut JSContext, value: DOMString) -> ErrorResult { debug_assert!( $id.enabled_for_all_content(), "Someone forgot a #[Pref] annotation" ); self.set_property(cx, $id, value, DOMString::new()) } )* ); ); fn remove_property(decls: &mut PropertyDeclarationBlock, id: &PropertyId) -> bool { let first_declaration = decls.first_declaration_to_remove(id); let first_declaration = match first_declaration { Some(i) => i, None => return false, }; decls.remove_property(id, first_declaration); true } impl CSSStyleDeclaration { #[cfg_attr(crown, expect(crown::unrooted_must_root))] pub(crate) fn new_inherited( owner: CSSStyleOwner, pseudo: Option<PseudoElement>, modification_access: CSSModificationAccess, ) -> CSSStyleDeclaration { // If creating a CSSStyleDeclaration with CSSSStyleOwner::Null, this should always // be in read-only mode. assert!( !matches!(owner, CSSStyleOwner::Null) || modification_access == CSSModificationAccess::Readonly ); CSSStyleDeclaration { reflector_: Reflector::new(), owner, readonly: modification_access == CSSModificationAccess::Readonly, pseudo, } } #[cfg_attr(crown, expect(crown::unrooted_must_root))] pub(crate) fn new( cx: &mut JSContext, global: &Window, owner: CSSStyleOwner, pseudo: Option<PseudoElement>, modification_access: CSSModificationAccess, ) -> DomRoot<CSSStyleDeclaration> { reflect_dom_object_with_cx( Box::new(CSSStyleDeclaration::new_inherited( owner, pseudo, modification_access, )), global, cx, ) } pub(crate) fn update_property_declaration_block( &self, pdb: &Arc<Locked<PropertyDeclarationBlock>>, ) { if let CSSStyleOwner::CSSRule(_, pdb_cell) = &self.owner { *pdb_cell.borrow_mut() = pdb.clone(); } else { panic!("update_rule called on CSSStyleDeclaration with a Element owner"); } } fn get_computed_style(&self, property: PropertyId) -> DOMString { match self.owner { CSSStyleOwner::CSSRule(..) => { panic!("get_computed_style called on CSSStyleDeclaration with a CSSRule owner") }, CSSStyleOwner::Element(ref el) => { let node = el.upcast::<Node>(); if !node.is_connected() { return DOMString::new(); } let addr = node.to_trusted_node_address(); node.owner_window() .resolved_style_query(addr, self.pseudo, property) }, CSSStyleOwner::Null => DOMString::new(), } } fn get_property_value(&self, id: PropertyId) -> DOMString { if matches!(self.owner, CSSStyleOwner::Null) { return DOMString::new(); } if self.readonly { // Readonly style declarations are used for getComputedStyle. return self.get_computed_style(id); } let mut string = String::new(); self.owner.with_block(|pdb| { pdb.property_value_to_css(&id, &mut string).unwrap(); }); DOMString::from(string) } /// <https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-setproperty> fn set_property( &self, cx: &mut JSContext, id: PropertyId, value: DOMString, priority: DOMString, ) -> ErrorResult { self.set_property_inner(cx, PotentiallyParsedPropertyId::Parsed(id), value, priority) } /// <https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-setproperty> /// /// This function receives a `PotentiallyParsedPropertyId` instead of a `DOMString` in case /// the caller already has a parsed property ID. fn set_property_inner( &self, cx: &mut JSContext, id: PotentiallyParsedPropertyId, value: DOMString, priority: DOMString, ) -> ErrorResult { // Step 1. If the readonly flag is set, then throw a NoModificationAllowedError exception. if self.readonly { return Err(Error::NoModificationAllowed(None)); } let id = match id { PotentiallyParsedPropertyId::Parsed(id) => { if !id.enabled_for_all_content() { return Ok(()); } id }, PotentiallyParsedPropertyId::NotParsed(unparsed) => { match PropertyId::parse_enabled_for_all_content(&unparsed.str()) { Ok(id) => id, Err(..) => return Ok(()), } }, }; let base_url = UrlExtraData(self.owner.base_url().get_arc()); self.owner.mutate_associated_block(cx, |pdb, changed| { // Step 3. If value is the empty string, invoke removeProperty() // with property as argument and return. if value.is_empty() { *changed = remove_property(pdb, &id); return Ok(()); } // Step 4. If priority is not the empty string and is not an ASCII case-insensitive // match for the string "important", then return. let importance = match &*priority.str() { "" => Importance::Normal, p if p.eq_ignore_ascii_case("important") => Importance::Important, _ => { *changed = false; return Ok(()); }, }; // Step 5 let window = self.owner.window(); let quirks_mode = window.Document().quirks_mode(); let mut declarations = SourcePropertyDeclaration::default(); let result = parse_one_declaration_into( &mut declarations, id, &value.str(), Origin::Author, &base_url, Some(window.css_error_reporter()), ParsingMode::DEFAULT, quirks_mode, CssRuleType::Style, ); // Step 6 match result { Ok(()) => {}, Err(_) => { *changed = false; return Ok(()); }, } let mut updates = Default::default(); *changed = pdb.prepare_for_update(&declarations, importance, &mut updates); if !*changed { return Ok(()); } // Step 7 // Step 8 pdb.update(declarations.drain(), importance, &mut updates); Ok(()) }) } } pub(crate) static ENABLED_LONGHAND_PROPERTIES: LazyLock<Vec<LonghandId>> = LazyLock::new(|| { // The 'all' shorthand contains all the enabled longhands with 2 exceptions: // 'direction' and 'unicode-bidi', so these must be added afterward. let mut enabled_longhands: Vec<LonghandId> = ShorthandId::All.longhands().collect(); if PropertyId::NonCustom(LonghandId::Direction.into()).enabled_for_all_content() { enabled_longhands.push(LonghandId::Direction); } if PropertyId::NonCustom(LonghandId::UnicodeBidi.into()).enabled_for_all_content() { enabled_longhands.push(LonghandId::UnicodeBidi); } // Sort lexicographically, but with vendor-prefixed properties after standard ones. enabled_longhands.sort_unstable_by(|a, b| { let a = a.name(); let b = b.name(); let is_a_vendor_prefixed = a.starts_with('-'); let is_b_vendor_prefixed = b.starts_with('-'); if is_a_vendor_prefixed == is_b_vendor_prefixed { a.partial_cmp(b).unwrap() } else if is_b_vendor_prefixed { Ordering::Less } else { Ordering::Greater } }); enabled_longhands }); enum PotentiallyParsedPropertyId { Parsed(PropertyId), NotParsed(DOMString), } impl CSSStyleDeclarationMethods<crate::DomTypeHolder> for CSSStyleDeclaration { /// <https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-length> fn Length(&self) -> u32 { if matches!(self.owner, CSSStyleOwner::Null) { return 0; } if self.readonly { // Readonly style declarations are used for getComputedStyle. // TODO: include custom properties whose computed value is not the guaranteed-invalid value. return ENABLED_LONGHAND_PROPERTIES.len() as u32; } self.owner.with_block(|pdb| pdb.declarations().len() as u32) } /// <https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-item> fn Item(&self, index: u32) -> DOMString { self.IndexedGetter(index).unwrap_or_default() } /// <https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-getpropertyvalue> fn GetPropertyValue(&self, property: DOMString) -> DOMString { let id = match PropertyId::parse_enabled_for_all_content(&property.str()) { Ok(id) => id, Err(..) => return DOMString::new(), }; self.get_property_value(id) } /// <https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-getpropertypriority> fn GetPropertyPriority(&self, property: DOMString) -> DOMString { if self.readonly { // Readonly style declarations are used for getComputedStyle. return DOMString::new(); } let id = match PropertyId::parse_enabled_for_all_content(&property.str()) { Ok(id) => id, Err(..) => return DOMString::new(), }; self.owner.with_block(|pdb| { if pdb.property_priority(&id).important() { DOMString::from("important") } else { // Step 4 DOMString::new() } }) } /// <https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-setproperty> fn SetProperty( &self, cx: &mut JSContext, property: DOMString, value: DOMString, priority: DOMString, ) -> ErrorResult { self.set_property_inner( cx, PotentiallyParsedPropertyId::NotParsed(property), value, priority, ) } /// <https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-removeproperty> fn RemoveProperty(&self, cx: &mut JSContext, property: DOMString) -> Fallible<DOMString> { // Step 1 if self.readonly { return Err(Error::NoModificationAllowed(None)); } let id = match PropertyId::parse_enabled_for_all_content(&property.str()) { Ok(id) => id, Err(..) => return Ok(DOMString::new()), }; let mut string = String::new(); self.owner.mutate_associated_block(cx, |pdb, changed| { pdb.property_value_to_css(&id, &mut string).unwrap(); *changed = remove_property(pdb, &id); }); // Step 6 Ok(DOMString::from(string)) } /// <https://drafts.csswg.org/cssom/#dom-cssstyleproperties-cssfloat> fn CssFloat(&self) -> DOMString { self.get_property_value(PropertyId::NonCustom(LonghandId::Float.into())) } /// <https://drafts.csswg.org/cssom/#dom-cssstyleproperties-cssfloat> fn SetCssFloat(&self, cx: &mut JSContext, value: DOMString) -> ErrorResult { self.set_property( cx, PropertyId::NonCustom(LonghandId::Float.into()), value, DOMString::new(), ) } /// <https://dev.w3.org/csswg/cssom/#the-cssstyledeclaration-interface> fn IndexedGetter(&self, index: u32) -> Option<DOMString> { if matches!(self.owner, CSSStyleOwner::Null) { return None; } if self.readonly { // Readonly style declarations are used for getComputedStyle. // TODO: include custom properties whose computed value is not the guaranteed-invalid value. let longhand = ENABLED_LONGHAND_PROPERTIES.get(index as usize)?; return Some(DOMString::from(longhand.name())); } self.owner.with_block(|pdb| { let declaration = pdb.declarations().get(index as usize)?; Some(DOMString::from(declaration.id().name())) }) } /// <https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-csstext> fn CssText(&self) -> DOMString { if self.readonly { // Readonly style declarations are used for getComputedStyle. return DOMString::new(); } self.owner.with_block(|pdb| { let mut serialization = String::new(); pdb.to_css(&mut serialization).unwrap(); DOMString::from(serialization) }) } /// <https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-csstext> fn SetCssText(&self, cx: &mut JSContext, value: DOMString) -> ErrorResult { let window = self.owner.window(); // Step 1 if self.readonly { return Err(Error::NoModificationAllowed(None)); } let quirks_mode = window.Document().quirks_mode(); let base_url = UrlExtraData(self.owner.base_url().get_arc()); self.owner.mutate_associated_block(cx, |pdb, _changed| { // Step 3 *pdb = parse_style_attribute( &value.str(), &base_url, Some(window.css_error_reporter()), quirks_mode, CssRuleType::Style, ); }); Ok(()) } // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-_camel_cased_attribute style::css_properties_accessors!(css_properties); }