mirror of
https://github.com/servo/servo
synced 2026-05-09 16:42:16 +02:00
Instead of running "update the rendering" at every IPC message, only run it when a timeout has occured in script. In addition, avoid updating the rendering if a rendering update isn't necessary. This should greatly reduce the amount of processing that has to happen in script. Because we are running many fewer calls to "update the rendering" it is reasonable now to ensure that these always work the same way. In particular, we always run rAF and update the animation timeline when updating the ernder In addition, pull the following things out of reflow: - Code dealing with informing the Constellation that a Pipeline has become Idle when waiting for a screenshot. - Detecting when it is time to fulfill the `document.fonts.ready` promise. The latter means that reflow can never cause a garbage collection, making timing of reflows more consistent and simplifying many callsites that need to do script queries. Followup changes will seek to simplify the way that ScriptThread-driven animation timeouts happen even simpler. Testing: In general, this should not change testable behavior so much, though it does seem to fix one test. The main improvement here should be that the ScriptThread does less work. Signed-off-by: Martin Robinson <mrobinson@igalia.com> Co-authored-by: Oriol Brufau <obrufau@igalia.com>
579 lines
20 KiB
Rust
579 lines
20 KiB
Rust
/* 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::cmp::Ordering;
|
|
use std::sync::LazyLock;
|
|
|
|
use dom_struct::dom_struct;
|
|
use html5ever::local_name;
|
|
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, UrlExtraData};
|
|
use style_traits::ParsingMode;
|
|
|
|
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, Reflector, reflect_dom_object};
|
|
use crate::dom::bindings::root::{Dom, DomRoot};
|
|
use crate::dom::bindings::str::DOMString;
|
|
use crate::dom::cssrule::CSSRule;
|
|
use crate::dom::element::Element;
|
|
use crate::dom::node::{Node, NodeTraits};
|
|
use crate::dom::window::Window;
|
|
use crate::script_runtime::CanGc;
|
|
|
|
// 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 = "Arc"]
|
|
#[no_trace]
|
|
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, f: F, can_gc: CanGc) -> 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 el) => {
|
|
let document = el.owner_document();
|
|
let shared_lock = document.style_shared_lock();
|
|
let mut attr = el.style_attribute().borrow_mut().take();
|
|
let result = if attr.is_some() {
|
|
let lock = attr.as_ref().unwrap();
|
|
let mut guard = shared_lock.write();
|
|
let pdb = lock.write_with(&mut guard);
|
|
f(pdb, &mut changed)
|
|
} else {
|
|
let mut pdb = PropertyDeclarationBlock::new();
|
|
let result = f(&mut pdb, &mut changed);
|
|
|
|
// Here `changed` is somewhat silly, because we know the
|
|
// exact conditions under it changes.
|
|
changed = !pdb.declarations().is_empty();
|
|
if changed {
|
|
attr = Some(Arc::new(shared_lock.wrap(pdb)));
|
|
}
|
|
|
|
result
|
|
};
|
|
|
|
if changed {
|
|
// Note that there's no need to remove the attribute here if
|
|
// the declaration block is empty[1], and if `attr` is
|
|
// `None` it means that it necessarily didn't change, so no
|
|
// need to go through all the set_attribute machinery.
|
|
//
|
|
// [1]: https://github.com/whatwg/html/issues/2306
|
|
if let Some(pdb) = attr {
|
|
let guard = shared_lock.read();
|
|
let mut serialization = String::new();
|
|
pdb.read_with(&guard).to_css(&mut serialization).unwrap();
|
|
el.set_attribute(
|
|
&local_name!("style"),
|
|
AttrValue::Declaration(serialization, pdb),
|
|
can_gc,
|
|
);
|
|
}
|
|
} else {
|
|
// Remember to put it back.
|
|
*el.style_attribute().borrow_mut() = attr;
|
|
}
|
|
|
|
result
|
|
},
|
|
CSSStyleOwner::CSSRule(ref rule, ref pdb) => {
|
|
let result = {
|
|
let mut guard = rule.shared_lock().write();
|
|
f(&mut *pdb.write_with(&mut guard), &mut changed)
|
|
};
|
|
if changed {
|
|
// If this is changed, see also
|
|
// CSSStyleRule::SetSelectorText, which does the same thing.
|
|
rule.parent_stylesheet().notify_invalidations();
|
|
}
|
|
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_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.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(
|
|
rule.parent_stylesheet()
|
|
.style_stylesheet()
|
|
.contents
|
|
.url_data
|
|
.read()
|
|
.0
|
|
.clone(),
|
|
)
|
|
.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, value: DOMString) -> ErrorResult {
|
|
debug_assert!(
|
|
$id.enabled_for_all_content(),
|
|
"Someone forgot a #[Pref] annotation"
|
|
);
|
|
self.set_property($id, value, DOMString::new(), CanGc::note())
|
|
}
|
|
)*
|
|
);
|
|
);
|
|
|
|
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, allow(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, allow(crown::unrooted_must_root))]
|
|
pub(crate) fn new(
|
|
global: &Window,
|
|
owner: CSSStyleOwner,
|
|
pseudo: Option<PseudoElement>,
|
|
modification_access: CSSModificationAccess,
|
|
can_gc: CanGc,
|
|
) -> DomRoot<CSSStyleDeclaration> {
|
|
reflect_dom_object(
|
|
Box::new(CSSStyleDeclaration::new_inherited(
|
|
owner,
|
|
pseudo,
|
|
modification_access,
|
|
)),
|
|
global,
|
|
can_gc,
|
|
)
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
fn set_property(
|
|
&self,
|
|
id: PropertyId,
|
|
value: DOMString,
|
|
priority: DOMString,
|
|
can_gc: CanGc,
|
|
) -> ErrorResult {
|
|
// Step 1
|
|
if self.readonly {
|
|
return Err(Error::NoModificationAllowed);
|
|
}
|
|
|
|
if !id.enabled_for_all_content() {
|
|
return Ok(());
|
|
}
|
|
|
|
self.owner.mutate_associated_block(
|
|
|pdb, changed| {
|
|
if value.is_empty() {
|
|
// Step 3
|
|
*changed = remove_property(pdb, &id);
|
|
return Ok(());
|
|
}
|
|
|
|
// Step 4
|
|
let importance = match &*priority {
|
|
"" => 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,
|
|
Origin::Author,
|
|
&UrlExtraData(self.owner.base_url().get_arc()),
|
|
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(())
|
|
},
|
|
can_gc,
|
|
)
|
|
}
|
|
}
|
|
|
|
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
|
|
});
|
|
|
|
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) {
|
|
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) {
|
|
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,
|
|
property: DOMString,
|
|
value: DOMString,
|
|
priority: DOMString,
|
|
can_gc: CanGc,
|
|
) -> ErrorResult {
|
|
// Step 3
|
|
let id = match PropertyId::parse_enabled_for_all_content(&property) {
|
|
Ok(id) => id,
|
|
Err(..) => return Ok(()),
|
|
};
|
|
self.set_property(id, value, priority, can_gc)
|
|
}
|
|
|
|
// https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-removeproperty
|
|
fn RemoveProperty(&self, property: DOMString, can_gc: CanGc) -> Fallible<DOMString> {
|
|
// Step 1
|
|
if self.readonly {
|
|
return Err(Error::NoModificationAllowed);
|
|
}
|
|
|
|
let id = match PropertyId::parse_enabled_for_all_content(&property) {
|
|
Ok(id) => id,
|
|
Err(..) => return Ok(DOMString::new()),
|
|
};
|
|
|
|
let mut string = String::new();
|
|
self.owner.mutate_associated_block(
|
|
|pdb, changed| {
|
|
pdb.property_value_to_css(&id, &mut string).unwrap();
|
|
*changed = remove_property(pdb, &id);
|
|
},
|
|
can_gc,
|
|
);
|
|
|
|
// Step 6
|
|
Ok(DOMString::from(string))
|
|
}
|
|
|
|
// https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-cssfloat
|
|
fn CssFloat(&self) -> DOMString {
|
|
self.get_property_value(PropertyId::NonCustom(LonghandId::Float.into()))
|
|
}
|
|
|
|
// https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-cssfloat
|
|
fn SetCssFloat(&self, value: DOMString, can_gc: CanGc) -> ErrorResult {
|
|
self.set_property(
|
|
PropertyId::NonCustom(LonghandId::Float.into()),
|
|
value,
|
|
DOMString::new(),
|
|
can_gc,
|
|
)
|
|
}
|
|
|
|
// 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, value: DOMString, can_gc: CanGc) -> ErrorResult {
|
|
let window = self.owner.window();
|
|
|
|
// Step 1
|
|
if self.readonly {
|
|
return Err(Error::NoModificationAllowed);
|
|
}
|
|
|
|
let quirks_mode = window.Document().quirks_mode();
|
|
self.owner.mutate_associated_block(
|
|
|pdb, _changed| {
|
|
// Step 3
|
|
*pdb = parse_style_attribute(
|
|
&value,
|
|
&UrlExtraData(self.owner.base_url().get_arc()),
|
|
window.css_error_reporter(),
|
|
quirks_mode,
|
|
CssRuleType::Style,
|
|
);
|
|
},
|
|
can_gc,
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-_camel_cased_attribute
|
|
style::css_properties_accessors!(css_properties);
|
|
}
|