use crate::context::QuirksMode;
use crate::error_reporting::{ContextualParseError, ParseErrorReporter};
use crate::media_queries::{Device, MediaList};
use crate::parser::ParserContext;
use crate::shared_lock::{DeepCloneWithLock, Locked};
use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard};
use crate::stylesheets::loader::StylesheetLoader;
use crate::stylesheets::rule_parser::{State, TopLevelRuleParser};
use crate::stylesheets::rules_iterator::{EffectiveRules, EffectiveRulesIterator};
use crate::stylesheets::rules_iterator::{NestedRuleIterationCondition, RulesIterator};
use crate::stylesheets::{CssRule, CssRules, Origin, UrlExtraData};
use crate::use_counters::UseCounters;
use crate::{Namespace, Prefix};
use cssparser::{Parser, ParserInput, StyleSheetParser};
use fxhash::FxHashMap;
#[cfg(feature = "gecko")]
use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
use parking_lot::RwLock;
use servo_arc::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use style_traits::ParsingMode;
use super::scope_rule::ImplicitScopeRoot;
pub struct UserAgentStylesheets {
pub shared_lock: SharedRwLock,
pub user_or_user_agent_stylesheets: Vec<DocumentStyleSheet>,
pub quirks_mode_stylesheet: DocumentStyleSheet,
}
#[derive(Clone, Debug, Default, MallocSizeOf)]
#[allow(missing_docs)]
pub struct Namespaces {
pub default: Option<Namespace>,
pub prefixes: FxHashMap<Prefix, Namespace>,
}
#[derive(Debug)]
pub struct StylesheetContents {
pub rules: Arc<Locked<CssRules>>,
pub origin: Origin,
pub url_data: RwLock<UrlExtraData>,
pub namespaces: RwLock<Namespaces>,
pub quirks_mode: QuirksMode,
pub source_map_url: RwLock<Option<String>>,
pub source_url: RwLock<Option<String>>,
_forbid_construction: (),
}
impl StylesheetContents {
pub fn from_str(
css: &str,
url_data: UrlExtraData,
origin: Origin,
shared_lock: &SharedRwLock,
stylesheet_loader: Option<&dyn StylesheetLoader>,
error_reporter: Option<&dyn ParseErrorReporter>,
quirks_mode: QuirksMode,
use_counters: Option<&UseCounters>,
allow_import_rules: AllowImportRules,
sanitization_data: Option<&mut SanitizationData>,
) -> Arc<Self> {
let (namespaces, rules, source_map_url, source_url) = Stylesheet::parse_rules(
css,
&url_data,
origin,
&shared_lock,
stylesheet_loader,
error_reporter,
quirks_mode,
use_counters,
allow_import_rules,
sanitization_data,
);
Arc::new(Self {
rules: CssRules::new(rules, &shared_lock),
origin,
url_data: RwLock::new(url_data),
namespaces: RwLock::new(namespaces),
quirks_mode,
source_map_url: RwLock::new(source_map_url),
source_url: RwLock::new(source_url),
_forbid_construction: (),
})
}
pub fn from_data(
rules: Arc<Locked<CssRules>>,
origin: Origin,
url_data: UrlExtraData,
quirks_mode: QuirksMode,
) -> Arc<Self> {
Arc::new(Self {
rules,
origin,
url_data: RwLock::new(url_data),
namespaces: RwLock::new(Namespaces::default()),
quirks_mode,
source_map_url: RwLock::new(None),
source_url: RwLock::new(None),
_forbid_construction: (),
})
}
pub fn from_shared_data(
rules: Arc<Locked<CssRules>>,
origin: Origin,
url_data: UrlExtraData,
quirks_mode: QuirksMode,
) -> Arc<Self> {
debug_assert!(rules.is_static());
Self::from_data(rules, origin, url_data, quirks_mode)
}
#[inline]
pub fn rules<'a, 'b: 'a>(&'a self, guard: &'b SharedRwLockReadGuard) -> &'a [CssRule] {
&self.rules.read_with(guard).0
}
#[cfg(feature = "gecko")]
pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
if self.rules.is_static() {
return 0;
}
self.rules.unconditional_shallow_size_of(ops) +
self.rules.read_with(guard).size_of(guard, ops)
}
}
impl DeepCloneWithLock for StylesheetContents {
fn deep_clone_with_lock(
&self,
lock: &SharedRwLock,
guard: &SharedRwLockReadGuard,
) -> Self {
let rules = self
.rules
.read_with(guard)
.deep_clone_with_lock(lock, guard);
Self {
rules: Arc::new(lock.wrap(rules)),
quirks_mode: self.quirks_mode,
origin: self.origin,
url_data: RwLock::new((*self.url_data.read()).clone()),
namespaces: RwLock::new((*self.namespaces.read()).clone()),
source_map_url: RwLock::new((*self.source_map_url.read()).clone()),
source_url: RwLock::new((*self.source_url.read()).clone()),
_forbid_construction: (),
}
}
}
#[derive(Debug)]
pub struct Stylesheet {
pub contents: Arc<StylesheetContents>,
pub shared_lock: SharedRwLock,
pub media: Arc<Locked<MediaList>>,
pub disabled: AtomicBool,
}
pub trait StylesheetInDocument: ::std::fmt::Debug {
fn enabled(&self) -> bool;
fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList>;
fn rules<'a, 'b: 'a>(&'a self, guard: &'b SharedRwLockReadGuard) -> &'a [CssRule] {
self.contents().rules(guard)
}
fn contents(&self) -> &StylesheetContents;
#[inline]
fn iter_rules<'a, 'b, C>(
&'a self,
device: &'a Device,
guard: &'a SharedRwLockReadGuard<'b>,
) -> RulesIterator<'a, 'b, C>
where
C: NestedRuleIterationCondition,
{
let contents = self.contents();
RulesIterator::new(
device,
contents.quirks_mode,
guard,
contents.rules(guard).iter(),
)
}
fn is_effective_for_device(&self, device: &Device, guard: &SharedRwLockReadGuard) -> bool {
match self.media(guard) {
Some(medialist) => medialist.evaluate(device, self.contents().quirks_mode),
None => true,
}
}
#[inline]
fn effective_rules<'a, 'b>(
&'a self,
device: &'a Device,
guard: &'a SharedRwLockReadGuard<'b>,
) -> EffectiveRulesIterator<'a, 'b> {
self.iter_rules::<EffectiveRules>(device, guard)
}
fn implicit_scope_root(&self) -> Option<ImplicitScopeRoot>;
}
impl StylesheetInDocument for Stylesheet {
fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> {
Some(self.media.read_with(guard))
}
fn enabled(&self) -> bool {
!self.disabled()
}
#[inline]
fn contents(&self) -> &StylesheetContents {
&self.contents
}
fn implicit_scope_root(&self) -> Option<ImplicitScopeRoot> {
None
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
pub struct DocumentStyleSheet(
#[cfg_attr(feature = "servo", ignore_malloc_size_of = "Arc")] pub Arc<Stylesheet>,
);
impl PartialEq for DocumentStyleSheet {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
impl StylesheetInDocument for DocumentStyleSheet {
fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> {
self.0.media(guard)
}
fn enabled(&self) -> bool {
self.0.enabled()
}
#[inline]
fn contents(&self) -> &StylesheetContents {
self.0.contents()
}
fn implicit_scope_root(&self) -> Option<ImplicitScopeRoot> {
None
}
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum SanitizationKind {
None,
Standard,
NoConditionalRules,
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum AllowImportRules {
Yes,
No,
}
impl SanitizationKind {
fn allows(self, rule: &CssRule) -> bool {
debug_assert_ne!(self, SanitizationKind::None);
let is_standard = matches!(self, SanitizationKind::Standard);
match *rule {
CssRule::Document(..) |
CssRule::Media(..) |
CssRule::Supports(..) |
CssRule::Import(..) |
CssRule::Container(..) |
CssRule::LayerStatement(..) |
CssRule::LayerBlock(..) |
CssRule::Scope(..) |
CssRule::StartingStyle(..) => false,
CssRule::FontFace(..) |
CssRule::Namespace(..) |
CssRule::Style(..) |
CssRule::NestedDeclarations(..) |
CssRule::PositionTry(..) => true,
CssRule::Keyframes(..) |
CssRule::Page(..) |
CssRule::Margin(..) |
CssRule::Property(..) |
CssRule::FontFeatureValues(..) |
CssRule::FontPaletteValues(..) |
CssRule::CounterStyle(..) => !is_standard,
}
}
}
#[derive(Debug)]
pub struct SanitizationData {
kind: SanitizationKind,
output: String,
}
impl SanitizationData {
#[inline]
pub fn new(kind: SanitizationKind) -> Option<Self> {
if matches!(kind, SanitizationKind::None) {
return None;
}
Some(Self {
kind,
output: String::new(),
})
}
#[inline]
pub fn take(self) -> String {
self.output
}
}
impl Stylesheet {
pub fn update_from_str(
existing: &Stylesheet,
css: &str,
url_data: UrlExtraData,
stylesheet_loader: Option<&dyn StylesheetLoader>,
error_reporter: Option<&dyn ParseErrorReporter>,
allow_import_rules: AllowImportRules,
) {
let (namespaces, rules, source_map_url, source_url) = Self::parse_rules(
css,
&url_data,
existing.contents.origin,
&existing.shared_lock,
stylesheet_loader,
error_reporter,
existing.contents.quirks_mode,
None,
allow_import_rules,
None,
);
*existing.contents.url_data.write() = url_data;
*existing.contents.namespaces.write() = namespaces;
let mut guard = existing.shared_lock.write();
*existing.contents.rules.write_with(&mut guard) = CssRules(rules);
*existing.contents.source_map_url.write() = source_map_url;
*existing.contents.source_url.write() = source_url;
}
fn parse_rules(
css: &str,
url_data: &UrlExtraData,
origin: Origin,
shared_lock: &SharedRwLock,
stylesheet_loader: Option<&dyn StylesheetLoader>,
error_reporter: Option<&dyn ParseErrorReporter>,
quirks_mode: QuirksMode,
use_counters: Option<&UseCounters>,
allow_import_rules: AllowImportRules,
mut sanitization_data: Option<&mut SanitizationData>,
) -> (Namespaces, Vec<CssRule>, Option<String>, Option<String>) {
let mut input = ParserInput::new(css);
let mut input = Parser::new(&mut input);
let context = ParserContext::new(
origin,
url_data,
None,
ParsingMode::DEFAULT,
quirks_mode,
Default::default(),
error_reporter,
use_counters,
);
let mut rule_parser = TopLevelRuleParser {
shared_lock,
loader: stylesheet_loader,
context,
state: State::Start,
dom_error: None,
insert_rule_context: None,
allow_import_rules,
declaration_parser_state: Default::default(),
first_declaration_block: Default::default(),
wants_first_declaration_block: false,
error_reporting_state: Default::default(),
rules: Vec::new(),
};
{
let mut iter = StyleSheetParser::new(&mut input, &mut rule_parser);
while let Some(result) = iter.next() {
match result {
Ok(rule_start) => {
if let Some(ref mut data) = sanitization_data {
if let Some(ref rule) = iter.parser.rules.last() {
if !data.kind.allows(rule) {
iter.parser.rules.pop();
continue;
}
}
let end = iter.input.position().byte_index();
data.output.push_str(&css[rule_start.byte_index()..end]);
}
},
Err((error, slice)) => {
let location = error.location;
let error = ContextualParseError::InvalidRule(slice, error);
iter.parser.context.log_css_error(location, error);
},
}
}
}
let source_map_url = input.current_source_map_url().map(String::from);
let source_url = input.current_source_url().map(String::from);
(
rule_parser.context.namespaces.into_owned(),
rule_parser.rules,
source_map_url,
source_url,
)
}
pub fn from_str(
css: &str,
url_data: UrlExtraData,
origin: Origin,
media: Arc<Locked<MediaList>>,
shared_lock: SharedRwLock,
stylesheet_loader: Option<&dyn StylesheetLoader>,
error_reporter: Option<&dyn ParseErrorReporter>,
quirks_mode: QuirksMode,
allow_import_rules: AllowImportRules,
) -> Self {
let contents = StylesheetContents::from_str(
css,
url_data,
origin,
&shared_lock,
stylesheet_loader,
error_reporter,
quirks_mode,
None,
allow_import_rules,
None,
);
Stylesheet {
contents,
shared_lock,
media,
disabled: AtomicBool::new(false),
}
}
pub fn disabled(&self) -> bool {
self.disabled.load(Ordering::SeqCst)
}
pub fn set_disabled(&self, disabled: bool) -> bool {
self.disabled.swap(disabled, Ordering::SeqCst) != disabled
}
}
#[cfg(feature = "servo")]
impl Clone for Stylesheet {
fn clone(&self) -> Self {
let lock = self.shared_lock.clone();
let guard = self.shared_lock.read();
let media = self.media.read_with(&guard).clone();
let media = Arc::new(lock.wrap(media));
let contents = Arc::new(self.contents.deep_clone_with_lock(&lock, &guard));
Stylesheet {
contents,
media,
shared_lock: lock,
disabled: AtomicBool::new(self.disabled.load(Ordering::SeqCst)),
}
}
}