use std::cell::{Cell, Ref};
use std::rc::Rc;
use dom_struct::dom_struct;
use js::realm::CurrentRealm;
use js::rust::HandleObject;
use script_bindings::inheritance::Castable;
use script_bindings::root::Dom;
use servo_arc::Arc;
use style::media_queries::MediaList as StyleMediaList;
use style::shared_lock::{SharedRwLock, SharedRwLockReadGuard};
use style::stylesheets::{
AllowImportRules, CssRuleTypes, Origin, Stylesheet as StyleStyleSheet, StylesheetContents,
StylesheetInDocument, UrlExtraData,
};
use super::cssrulelist::{CSSRuleList, RulesSource};
use super::stylesheet::StyleSheet;
use super::stylesheetlist::StyleSheetListOwner;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::CSSStyleSheetBinding::{
CSSStyleSheetInit, CSSStyleSheetMethods,
};
use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use crate::dom::bindings::codegen::GenericBindings::CSSRuleListBinding::CSSRuleList_Binding::CSSRuleListMethods;
use crate::dom::bindings::codegen::UnionTypes::MediaListOrString;
use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
use crate::dom::bindings::refcounted::Trusted;
use crate::dom::bindings::reflector::{
DomGlobal, reflect_dom_object, reflect_dom_object_with_proto,
};
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
use crate::dom::bindings::str::{DOMString, USVString};
use crate::dom::document::Document;
use crate::dom::element::Element;
use crate::dom::html::htmlstyleelement::HTMLStyleElement;
use crate::dom::medialist::MediaList;
use crate::dom::node::NodeTraits;
use crate::dom::types::Promise;
use crate::dom::window::Window;
use crate::script_runtime::CanGc;
use crate::test::TrustedPromise;
#[dom_struct]
pub(crate) struct CSSStyleSheet {
stylesheet: StyleSheet,
owner_node: MutNullableDom<Element>,
rule_list: MutNullableDom<CSSRuleList>,
#[ignore_malloc_size_of = "Stylo"]
#[no_trace]
style_stylesheet: DomRefCell<Arc<StyleStyleSheet>>,
#[no_trace]
style_shared_lock: SharedRwLock,
origin_clean: Cell<bool>,
constructor_document: Option<Dom<Document>>,
disallow_modification: Cell<bool>,
adopters: DomRefCell<Vec<StyleSheetListOwner>>,
}
impl CSSStyleSheet {
fn new_inherited(
owner: Option<&Element>,
type_: DOMString,
href: Option<DOMString>,
title: Option<DOMString>,
stylesheet: Arc<StyleStyleSheet>,
constructor_document: Option<&Document>,
) -> CSSStyleSheet {
CSSStyleSheet {
stylesheet: StyleSheet::new_inherited(type_, href, title),
owner_node: MutNullableDom::new(owner),
rule_list: MutNullableDom::new(None),
style_shared_lock: stylesheet.shared_lock.clone(),
style_stylesheet: DomRefCell::new(stylesheet),
origin_clean: Cell::new(true),
constructor_document: constructor_document.map(Dom::from_ref),
adopters: Default::default(),
disallow_modification: Cell::new(false),
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
window: &Window,
owner: Option<&Element>,
type_: DOMString,
href: Option<DOMString>,
title: Option<DOMString>,
stylesheet: Arc<StyleStyleSheet>,
constructor_document: Option<&Document>,
can_gc: CanGc,
) -> DomRoot<CSSStyleSheet> {
reflect_dom_object(
Box::new(CSSStyleSheet::new_inherited(
owner,
type_,
href,
title,
stylesheet,
constructor_document,
)),
window,
can_gc,
)
}
#[allow(clippy::too_many_arguments)]
fn new_with_proto(
window: &Window,
proto: Option<HandleObject>,
owner: Option<&Element>,
type_: DOMString,
href: Option<DOMString>,
title: Option<DOMString>,
stylesheet: Arc<StyleStyleSheet>,
constructor_document: Option<&Document>,
can_gc: CanGc,
) -> DomRoot<CSSStyleSheet> {
reflect_dom_object_with_proto(
Box::new(CSSStyleSheet::new_inherited(
owner,
type_,
href,
title,
stylesheet,
constructor_document,
)),
window,
proto,
can_gc,
)
}
fn rulelist(&self, can_gc: CanGc) -> DomRoot<CSSRuleList> {
self.rule_list.or_init(|| {
let sheet = self.style_stylesheet.borrow();
let guard = sheet.shared_lock.read();
let rules = sheet.contents(&guard).rules.clone();
CSSRuleList::new(
self.global().as_window(),
self,
RulesSource::Rules(rules),
can_gc,
)
})
}
pub(crate) fn disabled(&self) -> bool {
self.style_stylesheet.borrow().disabled()
}
pub(crate) fn owner_node(&self) -> Option<DomRoot<Element>> {
self.owner_node.get()
}
pub(crate) fn set_disabled(&self, disabled: bool) {
if self.style_stylesheet.borrow().set_disabled(disabled) {
self.notify_invalidations();
}
}
pub(crate) fn set_owner_node(&self, value: Option<&Element>) {
self.owner_node.set(value);
}
pub(crate) fn shared_lock(&self) -> &SharedRwLock {
&self.style_shared_lock
}
pub(crate) fn style_stylesheet(&self) -> Ref<'_, Arc<StyleStyleSheet>> {
self.style_stylesheet.borrow()
}
pub(crate) fn set_origin_clean(&self, origin_clean: bool) {
self.origin_clean.set(origin_clean);
}
pub(crate) fn medialist(&self, can_gc: CanGc) -> DomRoot<MediaList> {
MediaList::new(
self.global().as_window(),
self,
self.style_stylesheet().media.clone(),
can_gc,
)
}
#[inline]
pub(crate) fn is_constructed(&self) -> bool {
self.constructor_document.is_some()
}
pub(crate) fn constructor_document_matches(&self, other_doc: &Document) -> bool {
match &self.constructor_document {
Some(doc) => *doc == other_doc,
None => false,
}
}
#[cfg_attr(crown, expect(crown::unrooted_must_root))]
pub(crate) fn add_adopter(&self, owner: StyleSheetListOwner) {
debug_assert!(self.is_constructed());
self.adopters.borrow_mut().push(owner);
}
pub(crate) fn remove_adopter(&self, owner: &StyleSheetListOwner) {
let adopters = &mut *self.adopters.borrow_mut();
if let Some(index) = adopters.iter().position(|o| o == owner) {
adopters.swap_remove(index);
}
}
pub(crate) fn will_modify(&self) {
let Some(node) = self.owner_node.get() else {
return;
};
let Some(node) = node.downcast::<HTMLStyleElement>() else {
return;
};
node.will_modify_stylesheet();
}
pub(crate) fn update_style_stylesheet(
&self,
style_stylesheet: &Arc<StyleStyleSheet>,
guard: &SharedRwLockReadGuard,
) {
*self.style_stylesheet.borrow_mut() = style_stylesheet.clone();
if let Some(rulelist) = self.rule_list.get() {
let rules = style_stylesheet.contents(guard).rules.clone();
rulelist.update_rules(RulesSource::Rules(rules), guard);
}
}
pub(crate) fn notify_invalidations(&self) {
if let Some(owner) = self.owner_node() {
owner.stylesheet_list_owner().invalidate_stylesheets();
}
for adopter in self.adopters.borrow().iter() {
adopter.invalidate_stylesheets();
}
}
pub(crate) fn disallow_modification(&self) -> bool {
self.disallow_modification.get()
}
fn do_replace_sync(&self, text: USVString) {
let global = self.global();
let window = global.as_window();
self.will_modify();
let _span = profile_traits::trace_span!("ParseStylesheet").entered();
let sheet = self.style_stylesheet();
let new_contents = StylesheetContents::from_str(
&text,
UrlExtraData(window.get_url().get_arc()),
Origin::Author,
&self.style_shared_lock,
None,
Some(window.css_error_reporter()),
window.Document().quirks_mode(),
AllowImportRules::No, None,
);
{
let mut write_guard = self.style_shared_lock.write();
*sheet.contents.write_with(&mut write_guard) = new_contents;
}
self.rule_list.set(None);
self.notify_invalidations();
}
}
impl CSSStyleSheetMethods<crate::DomTypeHolder> for CSSStyleSheet {
fn Constructor(
window: &Window,
proto: Option<HandleObject>,
can_gc: CanGc,
options: &CSSStyleSheetInit,
) -> DomRoot<Self> {
let doc = window.Document();
let shared_lock = doc.style_shared_lock().clone();
let media = Arc::new(shared_lock.wrap(match &options.media {
Some(media) => match media {
MediaListOrString::MediaList(media_list) => media_list.clone_media_list(),
MediaListOrString::String(str) => MediaList::parse_media_list(&str.str(), window),
},
None => StyleMediaList::empty(),
}));
let stylesheet = Arc::new(StyleStyleSheet::from_str(
"",
UrlExtraData(window.get_url().get_arc()),
Origin::Author,
media,
shared_lock,
None,
Some(window.css_error_reporter()),
doc.quirks_mode(),
AllowImportRules::No,
));
if options.disabled {
stylesheet.set_disabled(true);
}
Self::new_with_proto(
window,
proto,
None, "text/css".into(),
None, None, stylesheet,
Some(&window.Document()), can_gc,
)
}
fn GetCssRules(&self, can_gc: CanGc) -> Fallible<DomRoot<CSSRuleList>> {
if !self.origin_clean.get() {
return Err(Error::Security(None));
}
Ok(self.rulelist(can_gc))
}
fn InsertRule(&self, rule: DOMString, index: u32, can_gc: CanGc) -> Fallible<u32> {
if !self.origin_clean.get() {
return Err(Error::Security(None));
}
if self.disallow_modification() {
return Err(Error::NotAllowed(None));
}
self.rulelist(can_gc)
.insert_rule(&rule, index, CssRuleTypes::default(), None, can_gc)
}
fn DeleteRule(&self, index: u32, can_gc: CanGc) -> ErrorResult {
if !self.origin_clean.get() {
return Err(Error::Security(None));
}
if self.disallow_modification() {
return Err(Error::NotAllowed(None));
}
self.rulelist(can_gc).remove_rule(index)
}
fn GetRules(&self, can_gc: CanGc) -> Fallible<DomRoot<CSSRuleList>> {
self.GetCssRules(can_gc)
}
fn RemoveRule(&self, index: u32, can_gc: CanGc) -> ErrorResult {
self.DeleteRule(index, can_gc)
}
fn AddRule(
&self,
cx: &mut js::context::JSContext,
selector: DOMString,
block: DOMString,
optional_index: Option<u32>,
) -> Fallible<i32> {
let mut rule = selector;
if block.is_empty() {
rule.push_str(" { }");
} else {
rule.push_str(" { ");
rule.push_str(&block.str());
rule.push_str(" }");
};
let index = optional_index.unwrap_or_else(|| self.rulelist(CanGc::from_cx(cx)).Length());
self.InsertRule(rule, index, CanGc::from_cx(cx))?;
Ok(-1)
}
fn Replace(&self, cx: &mut CurrentRealm, text: USVString) -> Fallible<Rc<Promise>> {
let promise = Promise::new_in_realm(cx);
if !self.is_constructed() || self.disallow_modification() {
return Err(Error::NotAllowed(None));
}
self.disallow_modification.set(true);
let trusted_sheet = Trusted::new(self);
let trusted_promise = TrustedPromise::new(promise.clone());
self.global()
.task_manager()
.dom_manipulation_task_source()
.queue(task!(cssstylesheet_replace: move |cx| {
let sheet = trusted_sheet.root();
sheet.do_replace_sync(text);
sheet.disallow_modification.set(false);
trusted_promise.root().resolve_native(&sheet, CanGc::from_cx(cx));
}));
Ok(promise)
}
fn ReplaceSync(&self, text: USVString) -> Result<(), Error> {
if !self.is_constructed() || self.disallow_modification() {
return Err(Error::NotAllowed(None));
}
self.do_replace_sync(text);
Ok(())
}
}