servo/components/style/stylesheets/stylesheet.rs
bors-servo d4ddec8d33 Auto merge of #18209 - jdm:devirtualize, r=mbrubeck
Devirtualize CSS error reporting.

This removes a trait object from the path of reporting a CSS error.

---
- [X] `./mach build -d` does not report any errors
- [X] `./mach test-tidy` does not report any errors
- [X] There are tests for these changes

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/18209)
<!-- Reviewable:end -->
2017-08-24 13:19:25 -05:00

511 lines
17 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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 http://mozilla.org/MPL/2.0/. */
use {Prefix, Namespace};
use context::QuirksMode;
use cssparser::{Parser, RuleListParser, ParserInput};
use error_reporting::{ParseErrorReporter, ContextualParseError};
use fnv::FnvHashMap;
use invalidation::media_queries::{MediaListKey, ToMediaListKey};
use media_queries::{MediaList, Device};
use parking_lot::RwLock;
use parser::{ParserContext, ParserErrorContext};
use servo_arc::Arc;
use shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard};
use std::mem;
use std::sync::atomic::{AtomicBool, Ordering};
use style_traits::PARSING_MODE_DEFAULT;
use stylesheets::{CssRule, CssRules, Origin, UrlExtraData};
use stylesheets::loader::StylesheetLoader;
use stylesheets::memory::{MallocSizeOfFn, MallocSizeOfWithGuard};
use stylesheets::rule_parser::{State, TopLevelRuleParser};
use stylesheets::rules_iterator::{EffectiveRules, EffectiveRulesIterator, NestedRuleIterationCondition, RulesIterator};
use values::specified::NamespaceId;
/// This structure holds the user-agent and user stylesheets.
pub struct UserAgentStylesheets {
/// The lock used for user-agent stylesheets.
pub shared_lock: SharedRwLock,
/// The user or user agent stylesheets.
pub user_or_user_agent_stylesheets: Vec<Stylesheet>,
/// The quirks mode stylesheet.
pub quirks_mode_stylesheet: Stylesheet,
}
/// A set of namespaces applying to a given stylesheet.
///
/// The namespace id is used in gecko
#[derive(Clone, Debug, Default)]
#[allow(missing_docs)]
pub struct Namespaces {
pub default: Option<(Namespace, NamespaceId)>,
pub prefixes: FnvHashMap<Prefix, (Namespace, NamespaceId)>,
}
/// The contents of a given stylesheet. This effectively maps to a
/// StyleSheetInner in Gecko.
#[derive(Debug)]
pub struct StylesheetContents {
/// List of rules in the order they were found (important for
/// cascading order)
pub rules: Arc<Locked<CssRules>>,
/// The origin of this stylesheet.
pub origin: Origin,
/// The url data this stylesheet should use.
pub url_data: RwLock<UrlExtraData>,
/// The namespaces that apply to this stylesheet.
pub namespaces: RwLock<Namespaces>,
/// The quirks mode of this stylesheet.
pub quirks_mode: QuirksMode,
/// Whether this stylesheet would be dirty when the viewport size changes.
pub dirty_on_viewport_size_change: AtomicBool,
/// This stylesheet's source map URL.
pub source_map_url: RwLock<Option<String>>,
}
impl StylesheetContents {
/// Parse a given CSS string, with a given url-data, origin, and
/// quirks mode.
pub fn from_str<R: ParseErrorReporter>(
css: &str,
url_data: UrlExtraData,
origin: Origin,
shared_lock: &SharedRwLock,
stylesheet_loader: Option<&StylesheetLoader>,
error_reporter: &R,
quirks_mode: QuirksMode,
line_number_offset: u64
) -> Self {
let namespaces = RwLock::new(Namespaces::default());
let (rules, dirty_on_viewport_size_change, source_map_url) = Stylesheet::parse_rules(
css,
&url_data,
origin,
&mut *namespaces.write(),
&shared_lock,
stylesheet_loader,
error_reporter,
quirks_mode,
line_number_offset,
);
Self {
rules: CssRules::new(rules, &shared_lock),
origin: origin,
url_data: RwLock::new(url_data),
namespaces: namespaces,
dirty_on_viewport_size_change: AtomicBool::new(dirty_on_viewport_size_change),
quirks_mode: quirks_mode,
source_map_url: RwLock::new(source_map_url),
}
}
/// Return an iterator using the condition `C`.
#[inline]
pub fn iter_rules<'a, 'b, C>(
&'a self,
device: &'a Device,
guard: &'a SharedRwLockReadGuard<'b>
) -> RulesIterator<'a, 'b, C>
where
C: NestedRuleIterationCondition,
{
RulesIterator::new(
device,
self.quirks_mode,
guard,
&self.rules.read_with(guard)
)
}
}
impl DeepCloneWithLock for StylesheetContents {
fn deep_clone_with_lock(
&self,
lock: &SharedRwLock,
guard: &SharedRwLockReadGuard,
params: &DeepCloneParams,
) -> Self {
// Make a deep clone of the rules, using the new lock.
let rules =
self.rules.read_with(guard)
.deep_clone_with_lock(lock, guard, params);
let dirty_on_viewport_size_change =
AtomicBool::new(self.dirty_on_viewport_size_change.load(Ordering::Relaxed));
Self {
rules: Arc::new(lock.wrap(rules)),
dirty_on_viewport_size_change,
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()),
}
}
}
impl MallocSizeOfWithGuard for StylesheetContents {
fn malloc_size_of_children(
&self,
guard: &SharedRwLockReadGuard,
malloc_size_of: MallocSizeOfFn
) -> usize {
// Measurement of other fields may be added later.
self.rules.read_with(guard).malloc_size_of_children(guard, malloc_size_of)
}
}
/// The structure servo uses to represent a stylesheet.
#[derive(Debug)]
pub struct Stylesheet {
/// The contents of this stylesheet.
pub contents: StylesheetContents,
/// The lock used for objects inside this stylesheet
pub shared_lock: SharedRwLock,
/// List of media associated with the Stylesheet.
pub media: Arc<Locked<MediaList>>,
/// Whether this stylesheet should be disabled.
pub disabled: AtomicBool,
}
macro_rules! rule_filter {
($( $method: ident($variant:ident => $rule_type: ident), )+) => {
$(
#[allow(missing_docs)]
fn $method<F>(&self, device: &Device, guard: &SharedRwLockReadGuard, mut f: F)
where F: FnMut(&::stylesheets::$rule_type),
{
use stylesheets::CssRule;
for rule in self.effective_rules(device, guard) {
if let CssRule::$variant(ref lock) = *rule {
let rule = lock.read_with(guard);
f(&rule)
}
}
}
)+
}
}
/// A trait to represent a given stylesheet in a document.
pub trait StylesheetInDocument {
/// Get the contents of this stylesheet.
fn contents(&self, guard: &SharedRwLockReadGuard) -> &StylesheetContents;
/// Get the stylesheet origin.
fn origin(&self, guard: &SharedRwLockReadGuard) -> Origin {
self.contents(guard).origin
}
/// Get the stylesheet quirks mode.
fn quirks_mode(&self, guard: &SharedRwLockReadGuard) -> QuirksMode {
self.contents(guard).quirks_mode
}
/// Get the media associated with this stylesheet.
fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList>;
/// Returns whether the style-sheet applies for the current device.
fn is_effective_for_device(
&self,
device: &Device,
guard: &SharedRwLockReadGuard
) -> bool {
match self.media(guard) {
Some(medialist) => medialist.evaluate(device, self.quirks_mode(guard)),
None => true,
}
}
/// Get whether this stylesheet is enabled.
fn enabled(&self) -> bool;
/// Return an iterator using the condition `C`.
#[inline]
fn iter_rules<'a, 'b, C>(
&'a self,
device: &'a Device,
guard: &'a SharedRwLockReadGuard<'b>
) -> RulesIterator<'a, 'b, C>
where
C: NestedRuleIterationCondition,
{
self.contents(guard).iter_rules(device, guard)
}
/// Return an iterator over the effective rules within the style-sheet, as
/// according to the supplied `Device`.
#[inline]
fn effective_rules<'a, 'b>(
&'a self,
device: &'a Device,
guard: &'a SharedRwLockReadGuard<'b>
) -> EffectiveRulesIterator<'a, 'b> {
self.iter_rules::<EffectiveRules>(device, guard)
}
rule_filter! {
effective_style_rules(Style => StyleRule),
effective_media_rules(Media => MediaRule),
effective_font_face_rules(FontFace => FontFaceRule),
effective_font_face_feature_values_rules(FontFeatureValues => FontFeatureValuesRule),
effective_counter_style_rules(CounterStyle => CounterStyleRule),
effective_viewport_rules(Viewport => ViewportRule),
effective_keyframes_rules(Keyframes => KeyframesRule),
effective_supports_rules(Supports => SupportsRule),
effective_page_rules(Page => PageRule),
effective_document_rules(Document => DocumentRule),
}
}
impl StylesheetInDocument for Stylesheet {
fn contents(&self, _: &SharedRwLockReadGuard) -> &StylesheetContents {
&self.contents
}
fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> {
Some(self.media.read_with(guard))
}
fn enabled(&self) -> bool {
!self.disabled()
}
}
/// A simple wrapper over an `Arc<Stylesheet>`, with pointer comparison, and
/// suitable for its use in a `StylesheetSet`.
#[derive(Clone)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct DocumentStyleSheet(
#[cfg_attr(feature = "servo", ignore_heap_size_of = "Arc")]
pub Arc<Stylesheet>
);
impl PartialEq for DocumentStyleSheet {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
impl ToMediaListKey for DocumentStyleSheet {
fn to_media_list_key(&self) -> MediaListKey {
self.0.to_media_list_key()
}
}
impl StylesheetInDocument for DocumentStyleSheet {
fn contents(&self, guard: &SharedRwLockReadGuard) -> &StylesheetContents {
self.0.contents(guard)
}
fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> {
self.0.media(guard)
}
fn enabled(&self) -> bool {
self.0.enabled()
}
}
impl Stylesheet {
/// Updates an empty stylesheet from a given string of text.
pub fn update_from_str<R>(existing: &Stylesheet,
css: &str,
url_data: UrlExtraData,
stylesheet_loader: Option<&StylesheetLoader>,
error_reporter: &R,
line_number_offset: u64)
where R: ParseErrorReporter
{
let namespaces = RwLock::new(Namespaces::default());
let (rules, dirty_on_viewport_size_change, source_map_url) =
Stylesheet::parse_rules(
css,
&url_data,
existing.contents.origin,
&mut *namespaces.write(),
&existing.shared_lock,
stylesheet_loader,
error_reporter,
existing.contents.quirks_mode,
line_number_offset
);
*existing.contents.url_data.write() = url_data;
mem::swap(
&mut *existing.contents.namespaces.write(),
&mut *namespaces.write()
);
existing.contents.dirty_on_viewport_size_change
.store(dirty_on_viewport_size_change, Ordering::Release);
// Acquire the lock *after* parsing, to minimize the exclusive section.
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;
}
fn parse_rules<R: ParseErrorReporter>(
css: &str,
url_data: &UrlExtraData,
origin: Origin,
namespaces: &mut Namespaces,
shared_lock: &SharedRwLock,
stylesheet_loader: Option<&StylesheetLoader>,
error_reporter: &R,
quirks_mode: QuirksMode,
line_number_offset: u64
) -> (Vec<CssRule>, bool, Option<String>) {
let mut rules = Vec::new();
let mut input = ParserInput::new(css);
let mut input = Parser::new(&mut input);
let context =
ParserContext::new_with_line_number_offset(
origin,
url_data,
line_number_offset,
PARSING_MODE_DEFAULT,
quirks_mode
);
let error_context = ParserErrorContext { error_reporter };
let rule_parser = TopLevelRuleParser {
stylesheet_origin: origin,
shared_lock: shared_lock,
loader: stylesheet_loader,
context: context,
error_context: error_context,
state: State::Start,
had_hierarchy_error: false,
namespaces: namespaces,
};
input.look_for_viewport_percentages();
{
let mut iter =
RuleListParser::new_for_stylesheet(&mut input, rule_parser);
while let Some(result) = iter.next() {
match result {
Ok(rule) => rules.push(rule),
Err(err) => {
let error = ContextualParseError::InvalidRule(err.slice, err.error);
iter.parser.context.log_css_error(&iter.parser.error_context,
err.location, error);
}
}
}
}
let source_map_url = input.current_source_map_url().map(String::from);
(rules, input.seen_viewport_percentages(), source_map_url)
}
/// Creates an empty stylesheet and parses it with a given base url, origin
/// and media.
///
/// Effectively creates a new stylesheet and forwards the hard work to
/// `Stylesheet::update_from_str`.
pub fn from_str<R: ParseErrorReporter>(
css: &str,
url_data: UrlExtraData,
origin: Origin,
media: Arc<Locked<MediaList>>,
shared_lock: SharedRwLock,
stylesheet_loader: Option<&StylesheetLoader>,
error_reporter: &R,
quirks_mode: QuirksMode,
line_number_offset: u64)
-> Stylesheet
{
let contents = StylesheetContents::from_str(
css,
url_data,
origin,
&shared_lock,
stylesheet_loader,
error_reporter,
quirks_mode,
line_number_offset
);
Stylesheet {
contents,
shared_lock,
media,
disabled: AtomicBool::new(false),
}
}
/// Whether this stylesheet can be dirty on viewport size change.
pub fn dirty_on_viewport_size_change(&self) -> bool {
self.contents.dirty_on_viewport_size_change.load(Ordering::SeqCst)
}
/// When CSSOM inserts a rule or declaration into this stylesheet, it needs to call this method
/// with the return value of `cssparser::Parser::seen_viewport_percentages`.
///
/// FIXME: actually make these calls
///
/// Note: when *removing* a rule or declaration that contains a viewport percentage,
/// to keep the flag accurate wed need to iterator through the rest of the stylesheet to
/// check for *other* such values.
///
/// Instead, we conservatively assume there might be some.
/// Restyling will some some more work than necessary, but give correct results.
pub fn inserted_has_viewport_percentages(&self, has_viewport_percentages: bool) {
self.contents.dirty_on_viewport_size_change.fetch_or(
has_viewport_percentages,
Ordering::SeqCst
);
}
/// Returns whether the stylesheet has been explicitly disabled through the
/// CSSOM.
pub fn disabled(&self) -> bool {
self.disabled.load(Ordering::SeqCst)
}
/// Records that the stylesheet has been explicitly disabled through the
/// CSSOM.
///
/// Returns whether the the call resulted in a change in disabled state.
///
/// Disabled stylesheets remain in the document, but their rules are not
/// added to the Stylist.
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 {
// Create a new lock for our clone.
let lock = self.shared_lock.clone();
let guard = self.shared_lock.read();
// Make a deep clone of the media, using the new lock.
let media = self.media.read_with(&guard).clone();
let media = Arc::new(lock.wrap(media));
let contents = self.contents.deep_clone_with_lock(
&lock,
&guard,
&DeepCloneParams
);
Stylesheet {
contents,
media: media,
shared_lock: lock,
disabled: AtomicBool::new(self.disabled.load(Ordering::SeqCst)),
}
}
}