| /* 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 cssparser::{Parser, ParserInput}; |
| use dom_struct::dom_struct; |
| use servo_arc::Arc; |
| use style::shared_lock::{Locked, SharedRwLockReadGuard, ToCssWithGuard}; |
| use style::stylesheets::CssRuleType; |
| use style::stylesheets::keyframes_rule::{Keyframe, KeyframeSelector, KeyframesRule}; |
| use style::values::KeyframesName; |
| |
| use crate::dom::bindings::codegen::Bindings::CSSKeyframesRuleBinding::CSSKeyframesRuleMethods; |
| use crate::dom::bindings::error::ErrorResult; |
| use crate::dom::bindings::inheritance::Castable; |
| use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object}; |
| use crate::dom::bindings::root::{DomRoot, MutNullableDom}; |
| use crate::dom::bindings::str::DOMString; |
| use crate::dom::csskeyframerule::CSSKeyframeRule; |
| use crate::dom::cssrule::{CSSRule, SpecificCSSRule}; |
| use crate::dom::cssrulelist::{CSSRuleList, RulesSource}; |
| use crate::dom::cssstylesheet::CSSStyleSheet; |
| use crate::dom::window::Window; |
| use crate::script_runtime::CanGc; |
| |
| #[dom_struct] |
| pub(crate) struct CSSKeyframesRule { |
| cssrule: CSSRule, |
| #[ignore_malloc_size_of = "Arc"] |
| #[no_trace] |
| keyframesrule: RefCell<Arc<Locked<KeyframesRule>>>, |
| rulelist: MutNullableDom<CSSRuleList>, |
| } |
| |
| impl CSSKeyframesRule { |
| fn new_inherited( |
| parent_stylesheet: &CSSStyleSheet, |
| keyframesrule: Arc<Locked<KeyframesRule>>, |
| ) -> CSSKeyframesRule { |
| CSSKeyframesRule { |
| cssrule: CSSRule::new_inherited(parent_stylesheet), |
| keyframesrule: RefCell::new(keyframesrule), |
| rulelist: MutNullableDom::new(None), |
| } |
| } |
| |
| #[cfg_attr(crown, allow(crown::unrooted_must_root))] |
| pub(crate) fn new( |
| window: &Window, |
| parent_stylesheet: &CSSStyleSheet, |
| keyframesrule: Arc<Locked<KeyframesRule>>, |
| can_gc: CanGc, |
| ) -> DomRoot<CSSKeyframesRule> { |
| reflect_dom_object( |
| Box::new(CSSKeyframesRule::new_inherited( |
| parent_stylesheet, |
| keyframesrule, |
| )), |
| window, |
| can_gc, |
| ) |
| } |
| |
| fn rulelist(&self, can_gc: CanGc) -> DomRoot<CSSRuleList> { |
| self.rulelist.or_init(|| { |
| let parent_stylesheet = &self.upcast::<CSSRule>().parent_stylesheet(); |
| CSSRuleList::new( |
| self.global().as_window(), |
| parent_stylesheet, |
| RulesSource::Keyframes(self.keyframesrule.borrow().clone()), |
| can_gc, |
| ) |
| }) |
| } |
| |
| /// Given a keyframe selector, finds the index of the first corresponding rule if any |
| fn find_rule(&self, selector: &str) -> Option<usize> { |
| let mut input = ParserInput::new(selector); |
| let mut input = Parser::new(&mut input); |
| if let Ok(sel) = KeyframeSelector::parse(&mut input) { |
| let guard = self.cssrule.shared_lock().read(); |
| // This finds the *last* element matching a selector |
| // because that's the rule that applies. Thus, rposition |
| self.keyframesrule |
| .borrow() |
| .read_with(&guard) |
| .keyframes |
| .iter() |
| .rposition(|frame| frame.read_with(&guard).selector == sel) |
| } else { |
| None |
| } |
| } |
| |
| pub(crate) fn update_rule( |
| &self, |
| keyframesrule: Arc<Locked<KeyframesRule>>, |
| guard: &SharedRwLockReadGuard, |
| ) { |
| if let Some(rulelist) = self.rulelist.get() { |
| rulelist.update_rules(RulesSource::Keyframes(keyframesrule.clone()), guard); |
| } |
| |
| *self.keyframesrule.borrow_mut() = keyframesrule; |
| } |
| } |
| |
| impl CSSKeyframesRuleMethods<crate::DomTypeHolder> for CSSKeyframesRule { |
| // https://drafts.csswg.org/css-animations/#dom-csskeyframesrule-cssrules |
| fn CssRules(&self, can_gc: CanGc) -> DomRoot<CSSRuleList> { |
| self.rulelist(can_gc) |
| } |
| |
| // https://drafts.csswg.org/css-animations/#dom-csskeyframesrule-appendrule |
| fn AppendRule(&self, rule: DOMString, can_gc: CanGc) { |
| let style_stylesheet = self.cssrule.parent_stylesheet().style_stylesheet(); |
| let rule = Keyframe::parse( |
| &rule, |
| &style_stylesheet.contents, |
| &style_stylesheet.shared_lock, |
| ); |
| |
| if let Ok(rule) = rule { |
| self.cssrule.parent_stylesheet().will_modify(); |
| let mut guard = self.cssrule.shared_lock().write(); |
| self.keyframesrule |
| .borrow() |
| .write_with(&mut guard) |
| .keyframes |
| .push(rule); |
| self.rulelist(can_gc).append_lazy_dom_rule(); |
| self.cssrule.parent_stylesheet().notify_invalidations(); |
| } |
| } |
| |
| // https://drafts.csswg.org/css-animations/#dom-csskeyframesrule-deleterule |
| fn DeleteRule(&self, selector: DOMString, can_gc: CanGc) { |
| if let Some(idx) = self.find_rule(&selector) { |
| let _ = self.rulelist(can_gc).remove_rule(idx as u32); |
| } |
| } |
| |
| // https://drafts.csswg.org/css-animations/#dom-csskeyframesrule-findrule |
| fn FindRule(&self, selector: DOMString, can_gc: CanGc) -> Option<DomRoot<CSSKeyframeRule>> { |
| self.find_rule(&selector) |
| .and_then(|idx| self.rulelist(can_gc).item(idx as u32, can_gc)) |
| .and_then(DomRoot::downcast) |
| } |
| |
| // https://drafts.csswg.org/css-animations/#dom-csskeyframesrule-name |
| fn Name(&self) -> DOMString { |
| let guard = self.cssrule.shared_lock().read(); |
| DOMString::from(&**self.keyframesrule.borrow().read_with(&guard).name.as_atom()) |
| } |
| |
| // https://drafts.csswg.org/css-animations/#dom-csskeyframesrule-name |
| fn SetName(&self, value: DOMString) -> ErrorResult { |
| // Spec deviation: https://github.com/w3c/csswg-drafts/issues/801 |
| // Setting this property to a CSS-wide keyword or `none` does not throw, |
| // it stores a value that serializes as a quoted string. |
| self.cssrule.parent_stylesheet().will_modify(); |
| let name = KeyframesName::from_ident(&value); |
| let mut guard = self.cssrule.shared_lock().write(); |
| self.keyframesrule.borrow().write_with(&mut guard).name = name; |
| self.cssrule.parent_stylesheet().notify_invalidations(); |
| Ok(()) |
| } |
| } |
| |
| impl SpecificCSSRule for CSSKeyframesRule { |
| fn ty(&self) -> CssRuleType { |
| CssRuleType::Keyframes |
| } |
| |
| fn get_css(&self) -> DOMString { |
| let guard = self.cssrule.shared_lock().read(); |
| self.keyframesrule |
| .borrow() |
| .read_with(&guard) |
| .to_css_string(&guard) |
| .into() |
| } |
| |
| fn deparent_children(&self) { |
| if let Some(list) = self.rulelist.get() { |
| list.deparent_all() |
| } |
| } |
| } |