From 01ab2af4eb0a992b68bec8529138020f6b19b6f3 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Thu, 1 Jun 2017 15:34:55 -0700 Subject: [PATCH] servo: Merge #17130 - stylo: Handle attr() in `content` (from Manishearth:stylo-attr); r=heycam,emilio r=heycam https://bugzilla.mozilla.org/show_bug.cgi?id=1346693 Source-Repo: https://github.com/servo/servo Source-Revision: 373d3b91ddc3560a7de131bd5ca0f5f8030f1d7e --HG-- extra : subtree_source : https%3A//hg.mozilla.org/projects/converted-servo-linear extra : subtree_revision : 1902ce048c2c77cbee5ff949ca14fa7bbd881749 --- .../style/gecko/generated/bindings.rs | 3 + .../components/style/gecko/selector_parser.rs | 4 +- servo/components/style/parser.rs | 8 +- .../components/style/properties/gecko.mako.rs | 8 +- .../properties/longhand/counters.mako.rs | 43 ++----- .../components/style/servo/selector_parser.rs | 4 +- servo/components/style/stylesheets.rs | 78 ++++++++----- .../components/style/values/specified/mod.rs | 109 +++++++++++++++++- servo/tests/unit/style/parsing/selectors.rs | 2 +- servo/tests/unit/style/stylesheets.rs | 2 +- 10 files changed, 186 insertions(+), 75 deletions(-) diff --git a/servo/components/style/gecko/generated/bindings.rs b/servo/components/style/gecko/generated/bindings.rs index a9c5fe2bd4bd..ca5309e0c2eb 100644 --- a/servo/components/style/gecko/generated/bindings.rs +++ b/servo/components/style/gecko/generated/bindings.rs @@ -1399,6 +1399,9 @@ extern "C" { pub fn Gecko_AddPropertyToSet(arg1: nsCSSPropertyIDSetBorrowedMut, arg2: nsCSSPropertyID); } +extern "C" { + pub fn Gecko_RegisterNamespace(ns: *mut nsIAtom) -> i32; +} extern "C" { pub fn Gecko_Construct_Default_nsStyleFont(ptr: *mut nsStyleFont, pres_context: diff --git a/servo/components/style/gecko/selector_parser.rs b/servo/components/style/gecko/selector_parser.rs index 30242dc9bcbe..ff800322c90d 100644 --- a/servo/components/style/gecko/selector_parser.rs +++ b/servo/components/style/gecko/selector_parser.rs @@ -273,11 +273,11 @@ impl<'a> ::selectors::Parser for SelectorParser<'a> { } fn default_namespace(&self) -> Option { - self.namespaces.default.clone() + self.namespaces.default.clone().as_ref().map(|&(ref ns, _)| ns.clone()) } fn namespace_for_prefix(&self, prefix: &Atom) -> Option { - self.namespaces.prefixes.get(prefix).cloned() + self.namespaces.prefixes.get(prefix).map(|&(ref ns, _)| ns.clone()) } } diff --git a/servo/components/style/parser.rs b/servo/components/style/parser.rs index 8bfefc414cb8..cad388651f9f 100644 --- a/servo/components/style/parser.rs +++ b/servo/components/style/parser.rs @@ -7,8 +7,9 @@ use context::QuirksMode; use cssparser::{Parser, SourcePosition, UnicodeRange}; use error_reporting::ParseErrorReporter; +use parking_lot::RwLock; use style_traits::OneOrMoreCommaSeparated; -use stylesheets::{CssRuleType, Origin, UrlExtraData}; +use stylesheets::{CssRuleType, Origin, UrlExtraData, Namespaces}; bitflags! { /// The mode to use when parsing values. @@ -81,6 +82,8 @@ pub struct ParserContext<'a> { pub parsing_mode: ParsingMode, /// The quirks mode of this stylesheet. pub quirks_mode: QuirksMode, + /// The list of all namespaces active in the current stylesheet + pub namespaces: Option<&'a RwLock>, } impl<'a> ParserContext<'a> { @@ -100,6 +103,7 @@ impl<'a> ParserContext<'a> { line_number_offset: 0u64, parsing_mode: parsing_mode, quirks_mode: quirks_mode, + namespaces: None, } } @@ -125,6 +129,7 @@ impl<'a> ParserContext<'a> { line_number_offset: context.line_number_offset, parsing_mode: context.parsing_mode, quirks_mode: context.quirks_mode, + namespaces: context.namespaces, } } @@ -144,6 +149,7 @@ impl<'a> ParserContext<'a> { line_number_offset: line_number_offset, parsing_mode: parsing_mode, quirks_mode: quirks_mode, + namespaces: None, } } diff --git a/servo/components/style/properties/gecko.mako.rs b/servo/components/style/properties/gecko.mako.rs index 55d7cbe2dd9d..7d9db1fd3993 100644 --- a/servo/components/style/properties/gecko.mako.rs +++ b/servo/components/style/properties/gecko.mako.rs @@ -4401,12 +4401,12 @@ clip-path as_utf16_and_forget(&value); } } - ContentItem::Attr(ns, val) => { + ContentItem::Attr(attr) => { self.gecko.mContents[i].mType = eStyleContentType_Attr; - let s = if let Some(ns) = ns { - format!("{}|{}", ns, val) + let s = if let Some((_, ns)) = attr.namespace { + format!("{}|{}", ns, attr.attribute) } else { - val + attr.attribute.into() }; unsafe { // NB: we share allocators, so doing this is fine. diff --git a/servo/components/style/properties/longhand/counters.mako.rs b/servo/components/style/properties/longhand/counters.mako.rs index 9f86c3acf035..e71d80c42afd 100644 --- a/servo/components/style/properties/longhand/counters.mako.rs +++ b/servo/components/style/properties/longhand/counters.mako.rs @@ -14,6 +14,8 @@ use values::generics::CounterStyleOrNone; #[cfg(feature = "gecko")] use values::specified::url::SpecifiedUrl; + #[cfg(feature = "gecko")] + use values::specified::Attr; #[cfg(feature = "servo")] use super::list_style_type; @@ -36,6 +38,9 @@ #[cfg(feature = "gecko")] type CounterStyleType = ::values::generics::CounterStyleOrNone; + #[cfg(feature = "gecko")] + use values::specified::Attr; + #[derive(Debug, PartialEq, Eq, Clone)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub enum ContentItem { @@ -56,7 +61,7 @@ % if product == "gecko": /// `attr([namespace? `|`]? ident)` - Attr(Option, String), + Attr(Attr), /// `url(url)` Url(SpecifiedUrl), % endif @@ -90,14 +95,8 @@ ContentItem::NoCloseQuote => dest.write_str("no-close-quote"), % if product == "gecko": - ContentItem::Attr(ref ns, ref attr) => { - dest.write_str("attr(")?; - if let Some(ref ns) = *ns { - cssparser::Token::Ident((&**ns).into()).to_css(dest)?; - dest.write_str("|")?; - } - cssparser::Token::Ident((&**attr).into()).to_css(dest)?; - dest.write_str(")") + ContentItem::Attr(ref attr) => { + attr.to_css(dest) } ContentItem::Url(ref url) => url.to_css(dest), % endif @@ -203,31 +202,7 @@ }), % if product == "gecko": "attr" => input.parse_nested_block(|input| { - // Syntax is `[namespace? `|`]? ident` - // no spaces allowed - // FIXME (bug 1346693) we should be checking that - // this is a valid namespace and encoding it as a namespace - // number from the map - let first = input.try(|i| i.expect_ident()).ok().map(|i| i.into_owned()); - if let Ok(token) = input.try(|i| i.next_including_whitespace()) { - match token { - Token::Delim('|') => { - // must be followed by an ident - let tok2 = input.next_including_whitespace()?; - if let Token::Ident(second) = tok2 { - return Ok(ContentItem::Attr(first, second.into_owned())) - } else { - return Err(()) - } - } - _ => return Err(()) - } - } - if let Some(first) = first { - Ok(ContentItem::Attr(None, first)) - } else { - Err(()) - } + Ok(ContentItem::Attr(Attr::parse_function(context, input)?)) }), % endif _ => return Err(()) diff --git a/servo/components/style/servo/selector_parser.rs b/servo/components/style/servo/selector_parser.rs index 34cec12b83f3..c6f56a09b12a 100644 --- a/servo/components/style/servo/selector_parser.rs +++ b/servo/components/style/servo/selector_parser.rs @@ -430,11 +430,11 @@ impl<'a> ::selectors::Parser for SelectorParser<'a> { } fn default_namespace(&self) -> Option { - self.namespaces.default.clone() + self.namespaces.default.as_ref().map(|&(ref ns, _)| ns.clone()) } fn namespace_for_prefix(&self, prefix: &Prefix) -> Option { - self.namespaces.prefixes.get(prefix).cloned() + self.namespaces.prefixes.get(prefix).map(|&(ref ns, _)| ns.clone()) } } diff --git a/servo/components/style/stylesheets.rs b/servo/components/style/stylesheets.rs index 8c01c2adf22e..3a0da395ec81 100644 --- a/servo/components/style/stylesheets.rs +++ b/servo/components/style/stylesheets.rs @@ -38,9 +38,9 @@ use servo_config::prefs::PREFS; use servo_url::ServoUrl; use shared_lock::{SharedRwLock, Locked, ToCssWithGuard, SharedRwLockReadGuard}; use smallvec::SmallVec; +use std::{fmt, mem}; use std::borrow::Borrow; use std::cell::Cell; -use std::fmt; use std::mem::align_of; use std::os::raw::c_void; use std::slice; @@ -51,6 +51,7 @@ use stylearc::Arc; use stylist::FnvHashMap; use supports::SupportsCondition; use values::{CustomIdent, KeyframesName}; +use values::specified::NamespaceId; use values::specified::url::SpecifiedUrl; use viewport::ViewportRule; @@ -96,11 +97,13 @@ pub enum Origin { } /// A set of namespaces applying to a given stylesheet. +/// +/// The namespace id is used in gecko #[derive(Clone, Default, Debug)] #[allow(missing_docs)] pub struct Namespaces { - pub default: Option, - pub prefixes: FnvHashMap, + pub default: Option<(Namespace, NamespaceId)>, + pub prefixes: FnvHashMap, } /// Like gecko_bindings::structs::MallocSizeOf, but without the Option<> wrapper. Note that @@ -476,13 +479,13 @@ impl CssRule { loader: Option<&StylesheetLoader>) -> Result<(Self, State), SingleRuleParseError> { let error_reporter = NullReporter; - let mut namespaces = parent_stylesheet.namespaces.write(); - let context = ParserContext::new(parent_stylesheet.origin, - &parent_stylesheet.url_data, - &error_reporter, - None, - PARSING_MODE_DEFAULT, - parent_stylesheet.quirks_mode); + let mut context = ParserContext::new(parent_stylesheet.origin, + &parent_stylesheet.url_data, + &error_reporter, + None, + PARSING_MODE_DEFAULT, + parent_stylesheet.quirks_mode); + context.namespaces = Some(&parent_stylesheet.namespaces); let mut input = Parser::new(css); // nested rules are in the body state @@ -493,7 +496,6 @@ impl CssRule { shared_lock: &parent_stylesheet.shared_lock, loader: loader, state: Cell::new(state), - namespaces: &mut namespaces, }; match parse_one_rule(&mut input, &mut rule_parser) { Ok(result) => Ok((result, rule_parser.state.get())), @@ -1210,14 +1212,14 @@ impl Stylesheet { stylesheet_loader: Option<&StylesheetLoader>, error_reporter: &ParseErrorReporter, line_number_offset: u64) { - let mut namespaces = Namespaces::default(); + let namespaces = RwLock::new(Namespaces::default()); // FIXME: we really should update existing.url_data with the given url_data, // otherwise newly inserted rule may not have the right base url. let (rules, dirty_on_viewport_size_change) = Stylesheet::parse_rules( - css, url_data, existing.origin, &mut namespaces, + css, url_data, existing.origin, &namespaces, &existing.shared_lock, stylesheet_loader, error_reporter, existing.quirks_mode, line_number_offset); - *existing.namespaces.write() = namespaces; + mem::swap(&mut *existing.namespaces.write(), &mut *namespaces.write()); existing.dirty_on_viewport_size_change .store(dirty_on_viewport_size_change, Ordering::Release); @@ -1229,7 +1231,7 @@ impl Stylesheet { fn parse_rules(css: &str, url_data: &UrlExtraData, origin: Origin, - namespaces: &mut Namespaces, + namespaces: &RwLock, shared_lock: &SharedRwLock, stylesheet_loader: Option<&StylesheetLoader>, error_reporter: &ParseErrorReporter, @@ -1238,14 +1240,16 @@ impl Stylesheet { -> (Vec, bool) { let mut rules = Vec::new(); let mut input = Parser::new(css); + let mut context = ParserContext::new_with_line_number_offset(origin, url_data, error_reporter, + line_number_offset, + PARSING_MODE_DEFAULT, + quirks_mode); + context.namespaces = Some(namespaces); let rule_parser = TopLevelRuleParser { stylesheet_origin: origin, - namespaces: namespaces, shared_lock: shared_lock, loader: stylesheet_loader, - context: ParserContext::new_with_line_number_offset(origin, url_data, error_reporter, - line_number_offset, PARSING_MODE_DEFAULT, - quirks_mode), + context: context, state: Cell::new(State::Start), }; @@ -1283,15 +1287,15 @@ impl Stylesheet { quirks_mode: QuirksMode, line_number_offset: u64) -> Stylesheet { - let mut namespaces = Namespaces::default(); + let namespaces = RwLock::new(Namespaces::default()); let (rules, dirty_on_viewport_size_change) = Stylesheet::parse_rules( - css, &url_data, origin, &mut namespaces, + css, &url_data, origin, &namespaces, &shared_lock, stylesheet_loader, error_reporter, quirks_mode, line_number_offset, ); Stylesheet { origin: origin, url_data: url_data, - namespaces: RwLock::new(namespaces), + namespaces: namespaces, rules: CssRules::new(rules, &shared_lock), media: media, shared_lock: shared_lock, @@ -1470,7 +1474,6 @@ impl StylesheetLoader for NoOpLoader { struct TopLevelRuleParser<'a> { stylesheet_origin: Origin, - namespaces: &'a mut Namespaces, shared_lock: &'a SharedRwLock, loader: Option<&'a StylesheetLoader>, context: ParserContext<'a>, @@ -1483,7 +1486,6 @@ impl<'b> TopLevelRuleParser<'b> { stylesheet_origin: self.stylesheet_origin, shared_lock: self.shared_lock, context: &self.context, - namespaces: self.namespaces, } } } @@ -1528,6 +1530,21 @@ enum AtRulePrelude { } +#[cfg(feature = "gecko")] +fn register_namespace(ns: &Namespace) -> Result { + let id = unsafe { ::gecko_bindings::bindings::Gecko_RegisterNamespace(ns.0.as_ptr()) }; + if id == -1 { + Err(()) + } else { + Ok(id) + } +} + +#[cfg(feature = "servo")] +fn register_namespace(_: &Namespace) -> Result<(), ()> { + Ok(()) // servo doesn't use namespace ids +} + impl<'a> AtRuleParser for TopLevelRuleParser<'a> { type Prelude = AtRulePrelude; type AtRule = CssRule; @@ -1586,12 +1603,16 @@ impl<'a> AtRuleParser for TopLevelRuleParser<'a> { let prefix_result = input.try(|input| input.expect_ident()); let url = Namespace::from(try!(input.expect_url_or_string())); + let id = register_namespace(&url)?; + let opt_prefix = if let Ok(prefix) = prefix_result { let prefix = Prefix::from(prefix); - self.namespaces.prefixes.insert(prefix.clone(), url.clone()); + self.context.namespaces.expect("namespaces must be set whilst parsing rules") + .write().prefixes.insert(prefix.clone(), (url.clone(), id)); Some(prefix) } else { - self.namespaces.default = Some(url.clone()); + self.context.namespaces.expect("namespaces must be set whilst parsing rules") + .write().default = Some((url.clone(), id)); None }; @@ -1650,7 +1671,6 @@ struct NestedRuleParser<'a, 'b: 'a> { stylesheet_origin: Origin, shared_lock: &'a SharedRwLock, context: &'a ParserContext<'b>, - namespaces: &'b Namespaces, } impl<'a, 'b> NestedRuleParser<'a, 'b> { @@ -1660,7 +1680,6 @@ impl<'a, 'b> NestedRuleParser<'a, 'b> { stylesheet_origin: self.stylesheet_origin, shared_lock: self.shared_lock, context: &context, - namespaces: self.namespaces, }; let mut iter = RuleListParser::new_for_nested_rule(input, nested_parser); let mut rules = Vec::new(); @@ -1836,9 +1855,10 @@ impl<'a, 'b> QualifiedRuleParser for NestedRuleParser<'a, 'b> { type QualifiedRule = CssRule; fn parse_prelude(&mut self, input: &mut Parser) -> Result, ()> { + let ns = self.context.namespaces.expect("namespaces must be set when parsing rules").read(); let selector_parser = SelectorParser { stylesheet_origin: self.stylesheet_origin, - namespaces: self.namespaces, + namespaces: &*ns, }; SelectorList::parse(&selector_parser, input) } diff --git a/servo/components/style/values/specified/mod.rs b/servo/components/style/values/specified/mod.rs index 3913c5914e88..7eb637315d30 100644 --- a/servo/components/style/values/specified/mod.rs +++ b/servo/components/style/values/specified/mod.rs @@ -6,9 +6,10 @@ //! //! TODO(emilio): Enhance docs. +use Namespace; use app_units::Au; use context::QuirksMode; -use cssparser::{self, Parser, Token}; +use cssparser::{self, Parser, Token, serialize_identifier}; use itoa; use parser::{ParserContext, Parse}; use self::grid::TrackSizeOrRepeat; @@ -24,6 +25,7 @@ use super::computed::{self, Context}; use super::computed::{Shadow as ComputedShadow, ToComputedValue}; use super::generics::grid::{TrackBreadth as GenericTrackBreadth, TrackSize as GenericTrackSize}; use super::generics::grid::TrackList as GenericTrackList; +use values::computed::ComputedValueAsSpecified; use values::specified::calc::CalcNode; #[cfg(feature = "gecko")] @@ -1363,3 +1365,108 @@ impl AllowQuirks { self == AllowQuirks::Yes && quirks_mode == QuirksMode::Quirks } } + +#[cfg(feature = "gecko")] +/// A namespace ID +pub type NamespaceId = i32; + + +#[cfg(feature = "servo")] +/// A namespace ID (used by gecko only) +pub type NamespaceId = (); + +/// An attr(...) rule +/// +/// `[namespace? `|`]? ident` +#[derive(Clone, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +pub struct Attr { + /// Optional namespace + pub namespace: Option<(Namespace, NamespaceId)>, + /// Attribute name + pub attribute: String, +} + +impl Parse for Attr { + fn parse(context: &ParserContext, input: &mut Parser) -> Result { + input.expect_function_matching("attr")?; + input.parse_nested_block(|i| Attr::parse_function(context, i)) + } +} + +#[cfg(feature = "gecko")] +/// Get the namespace id from the namespace map +pub fn get_id_for_namespace(namespace: &Namespace, context: &ParserContext) -> Result { + if let Some(map) = context.namespaces { + if let Some(ref entry) = map.read().prefixes.get(&namespace.0) { + Ok(entry.1) + } else { + Err(()) + } + } else { + // if we don't have a namespace map (e.g. in inline styles) + // we can't parse namespaces + Err(()) + } +} + +#[cfg(feature = "servo")] +/// Get the namespace id from the namespace map +pub fn get_id_for_namespace(_: &Namespace, _: &ParserContext) -> Result { + Ok(()) +} + +impl Attr { + /// Parse contents of attr() assuming we have already parsed `attr` and are + /// within a parse_nested_block() + pub fn parse_function(context: &ParserContext, input: &mut Parser) -> Result { + // Syntax is `[namespace? `|`]? ident` + // no spaces allowed + let first = input.try(|i| i.expect_ident()).ok(); + if let Ok(token) = input.try(|i| i.next_including_whitespace()) { + match token { + Token::Delim('|') => { + // must be followed by an ident + let second_token = match input.next_including_whitespace()? { + Token::Ident(second) => second, + _ => return Err(()), + }; + let ns_with_id = if let Some(ns) = first { + let ns: Namespace = ns.into(); + let id = get_id_for_namespace(&ns, context)?; + Some((ns, id)) + } else { + None + }; + return Ok(Attr { + namespace: ns_with_id, + attribute: second_token.into_owned(), + }) + } + _ => return Err(()) + } + } + if let Some(first) = first { + Ok(Attr { + namespace: None, + attribute: first.into_owned(), + }) + } else { + Err(()) + } + } +} + +impl ToCss for Attr { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + dest.write_str("attr(")?; + if let Some(ref ns) = self.namespace { + serialize_identifier(&ns.0.to_string(), dest)?; + dest.write_str("|")?; + } + serialize_identifier(&self.attribute, dest)?; + dest.write_str(")") + } +} + +impl ComputedValueAsSpecified for Attr {} diff --git a/servo/tests/unit/style/parsing/selectors.rs b/servo/tests/unit/style/parsing/selectors.rs index 08c8217d8fab..b865b39e8709 100644 --- a/servo/tests/unit/style/parsing/selectors.rs +++ b/servo/tests/unit/style/parsing/selectors.rs @@ -9,7 +9,7 @@ use style::stylesheets::{Origin, Namespaces}; fn parse_selector(input: &mut Parser) -> Result, ()> { let mut ns = Namespaces::default(); - ns.prefixes.insert("svg".into(), ns!(svg)); + ns.prefixes.insert("svg".into(), (ns!(svg), ())); let parser = SelectorParser { stylesheet_origin: Origin::UserAgent, namespaces: &ns, diff --git a/servo/tests/unit/style/stylesheets.rs b/servo/tests/unit/style/stylesheets.rs index 7ca36677429e..a161c41cabe5 100644 --- a/servo/tests/unit/style/stylesheets.rs +++ b/servo/tests/unit/style/stylesheets.rs @@ -70,7 +70,7 @@ fn test_parse_stylesheet() { let stylesheet = Stylesheet::from_str(css, url.clone(), Origin::UserAgent, media, lock, None, &CSSErrorReporterTest, QuirksMode::NoQuirks, 0u64); let mut namespaces = Namespaces::default(); - namespaces.default = Some(ns!(html)); + namespaces.default = Some((ns!(html), ())); let expected = Stylesheet { origin: Origin::UserAgent, media: Arc::new(stylesheet.shared_lock.wrap(MediaList::empty())),