Stylo: Bug 1355408 - add support for @-moz-document

This commit is contained in:
Fernando Jiménez Moreno 2017-05-09 10:41:03 +02:00
parent 9c9bd17871
commit b22e0e9494
12 changed files with 338 additions and 24 deletions

1
Cargo.lock generated
View file

@ -963,6 +963,7 @@ dependencies = [
"env_logger 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
"nsstring_vendor 0.1.0",
"parking_lot 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.18.0",
"style 0.0.1",

View file

@ -85,6 +85,7 @@ impl CSSRule {
StyleCssRule::Viewport(s) => Root::upcast(CSSViewportRule::new(window, parent_stylesheet, s)),
StyleCssRule::Supports(s) => Root::upcast(CSSSupportsRule::new(window, parent_stylesheet, s)),
StyleCssRule::Page(_) => unreachable!(),
StyleCssRule::Document(_) => unimplemented!(), // TODO
}
}

View file

@ -349,6 +349,7 @@ mod bindings {
"mozilla::ServoElementSnapshot.*",
"mozilla::CSSPseudoClassType",
"mozilla::css::SheetParsingMode",
"mozilla::css::URLMatchingFunction",
"mozilla::HalfCorner",
"mozilla::PropertyStyleAnimationValuePair",
"mozilla::TraversalRestyleBehavior",
@ -739,6 +740,7 @@ mod bindings {
"UpdateAnimationsTasks",
"LengthParsingMode",
"InheritTarget",
"URLMatchingFunction",
];
struct ArrayType {
cpp_type: &'static str,

View file

@ -0,0 +1,176 @@
/* 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/. */
//! [@document rules](https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#at-document)
//! initially in CSS Conditional Rules Module Level 3, @document has been postponed to the level 4.
//! We implement the prefixed `@-moz-document`.
use cssparser::{Parser, Token, serialize_string};
#[cfg(feature = "gecko")]
use gecko_bindings::bindings::Gecko_DocumentRule_UseForPresentation;
#[cfg(feature = "gecko")]
use gecko_bindings::structs::URLMatchingFunction as GeckoUrlMatchingFunction;
use media_queries::Device;
#[cfg(feature = "gecko")]
use nsstring::nsCString;
use parser::{Parse, ParserContext};
use std::fmt;
use style_traits::ToCss;
use values::specified::url::SpecifiedUrl;
/// A URL matching function for a `@document` rule's condition.
#[derive(Debug)]
pub enum UrlMatchingFunction {
/// Exact URL matching function. It evaluates to true whenever the
/// URL of the document being styled is exactly the URL given.
Url(SpecifiedUrl),
/// URL prefix matching function. It evaluates to true whenever the
/// URL of the document being styled has the argument to the
/// function as an initial substring (which is true when the two
/// strings are equal). When the argument is the empty string,
/// it evaluates to true for all documents.
UrlPrefix(String),
/// Domain matching function. It evaluates to true whenever the URL
/// of the document being styled has a host subcomponent and that
/// host subcomponent is exactly the argument to the domain()
/// function or a final substring of the host component is a
/// period (U+002E) immediately followed by the argument to the
/// domain() function.
Domain(String),
/// Regular expression matching function. It evaluates to true
/// whenever the regular expression matches the entirety of the URL
/// of the document being styled.
RegExp(String),
}
macro_rules! parse_quoted_or_unquoted_string {
($input:ident, $url_matching_function:expr) => {
$input.parse_nested_block(|input| {
let start = input.position();
input.parse_entirely(|input| {
match input.next() {
Ok(Token::QuotedString(value)) =>
Ok($url_matching_function(value.into_owned())),
_ => Err(()),
}
}).or_else(|_| {
while let Ok(_) = input.next() {}
Ok($url_matching_function(input.slice_from(start).to_string()))
})
})
}
}
impl UrlMatchingFunction {
/// Parse a URL matching function for a`@document` rule's condition.
pub fn parse(context: &ParserContext, input: &mut Parser)
-> Result<UrlMatchingFunction, ()> {
if input.try(|input| input.expect_function_matching("url-prefix")).is_ok() {
parse_quoted_or_unquoted_string!(input, UrlMatchingFunction::UrlPrefix)
} else if input.try(|input| input.expect_function_matching("domain")).is_ok() {
parse_quoted_or_unquoted_string!(input, UrlMatchingFunction::Domain)
} else if input.try(|input| input.expect_function_matching("regexp")).is_ok() {
input.parse_nested_block(|input| {
Ok(UrlMatchingFunction::RegExp(input.expect_string()?.into_owned()))
})
} else if let Ok(url) = input.try(|input| SpecifiedUrl::parse(context, input)) {
Ok(UrlMatchingFunction::Url(url))
} else {
Err(())
}
}
#[cfg(feature = "gecko")]
/// Evaluate a URL matching function.
pub fn evaluate(&self, device: &Device) -> bool {
let func = match *self {
UrlMatchingFunction::Url(_) => GeckoUrlMatchingFunction::eURL,
UrlMatchingFunction::UrlPrefix(_) => GeckoUrlMatchingFunction::eURLPrefix,
UrlMatchingFunction::Domain(_) => GeckoUrlMatchingFunction::eDomain,
UrlMatchingFunction::RegExp(_) => GeckoUrlMatchingFunction::eRegExp,
};
let pattern = nsCString::from(match *self {
UrlMatchingFunction::Url(ref url) => url.as_str(),
UrlMatchingFunction::UrlPrefix(ref pat) |
UrlMatchingFunction::Domain(ref pat) |
UrlMatchingFunction::RegExp(ref pat) => pat,
});
unsafe {
Gecko_DocumentRule_UseForPresentation(&*device.pres_context, &*pattern, func)
}
}
#[cfg(not(feature = "gecko"))]
/// Evaluate a URL matching function.
pub fn evaluate(&self, _: &Device) -> bool {
false
}
}
impl ToCss for UrlMatchingFunction {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where W: fmt::Write {
match *self {
UrlMatchingFunction::Url(ref url) => {
url.to_css(dest)
},
UrlMatchingFunction::UrlPrefix(ref url_prefix) => {
dest.write_str("url-prefix(")?;
serialize_string(url_prefix, dest)?;
dest.write_str(")")
},
UrlMatchingFunction::Domain(ref domain) => {
dest.write_str("domain(")?;
serialize_string(domain, dest)?;
dest.write_str(")")
},
UrlMatchingFunction::RegExp(ref regex) => {
dest.write_str("regexp(")?;
serialize_string(regex, dest)?;
dest.write_str(")")
},
}
}
}
/// A `@document` rule's condition.
///
/// https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#at-document
///
/// The `@document` rule's condition is written as a comma-separated list of
/// URL matching functions, and the condition evaluates to true whenever any
/// one of those functions evaluates to true.
#[derive(Debug)]
pub struct DocumentCondition(Vec<UrlMatchingFunction>);
impl DocumentCondition {
/// Parse a document condition.
pub fn parse(context: &ParserContext, input: &mut Parser)
-> Result<Self, ()> {
input.parse_comma_separated(|input| UrlMatchingFunction::parse(context, input))
.map(DocumentCondition)
}
/// Evaluate a document condition.
pub fn evaluate(&self, device: &Device) -> bool {
self.0.iter().any(|ref url_matching_function|
url_matching_function.evaluate(device)
)
}
}
impl ToCss for DocumentCondition {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where W: fmt::Write {
let mut iter = self.0.iter();
let first = iter.next()
.expect("Empty DocumentCondition, should contain at least one URL matching function");
first.to_css(dest)?;
for url_matching_function in iter {
dest.write_str(", ")?;
url_matching_function.to_css(dest)?;
}
Ok(())
}
}

View file

@ -194,6 +194,7 @@ use gecko_bindings::structs::EffectCompositor_CascadeLevel;
use gecko_bindings::structs::UpdateAnimationsTasks;
use gecko_bindings::structs::LengthParsingMode;
use gecko_bindings::structs::InheritTarget;
use gecko_bindings::structs::URLMatchingFunction;
pub type nsTArrayBorrowed_uintptr_t<'a> = &'a mut ::gecko_bindings::structs::nsTArray<usize>;
pub type ServoCssRulesStrong = ::gecko_bindings::sugar::ownership::Strong<ServoCssRules>;
pub type ServoCssRulesBorrowed<'a> = &'a ServoCssRules;
@ -1549,6 +1550,14 @@ extern "C" {
extern "C" {
pub fn Gecko_UnregisterProfilerThread();
}
extern "C" {
pub fn Gecko_DocumentRule_UseForPresentation(arg1:
RawGeckoPresContextBorrowed,
aPattern: *const nsACString,
aURLMatchingFunction:
URLMatchingFunction)
-> bool;
}
extern "C" {
pub fn Servo_Element_ClearData(node: RawGeckoElementBorrowed);
}

View file

@ -2130,6 +2130,18 @@ pub mod root {
ComplexColorValue ) , "::" , stringify ! (
_mOwningThread ) ));
}
#[repr(i32)]
/**
* Enum defining the type of URL matching function for a @-moz-document rule
* condition.
*/
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum URLMatchingFunction {
eURL = 0,
eURLPrefix = 1,
eDomain = 2,
eRegExp = 3,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct DocumentRule {

View file

@ -2036,6 +2036,18 @@ pub mod root {
ComplexColorValue ) , "::" , stringify ! ( mRefCnt
) ));
}
#[repr(i32)]
/**
* Enum defining the type of URL matching function for a @-moz-document rule
* condition.
*/
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum URLMatchingFunction {
eURL = 0,
eURLPrefix = 1,
eDomain = 2,
eRegExp = 3,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct DocumentRule {

View file

@ -93,6 +93,7 @@ pub mod context;
pub mod counter_style;
pub mod custom_properties;
pub mod data;
pub mod document_condition;
pub mod dom;
pub mod element_state;
#[cfg(feature = "servo")] mod encoding_support;

View file

@ -12,6 +12,7 @@ use counter_style::{CounterStyleRule, parse_counter_style_name, parse_counter_st
use cssparser::{AtRuleParser, Parser, QualifiedRuleParser};
use cssparser::{AtRuleType, RuleListParser, parse_one_rule};
use cssparser::ToCss as ParserToCss;
use document_condition::DocumentCondition;
use error_reporting::{ParseErrorReporter, NullReporter};
#[cfg(feature = "servo")]
use font_face::FontFaceRuleData;
@ -299,6 +300,7 @@ pub enum CssRule {
Keyframes(Arc<Locked<KeyframesRule>>),
Supports(Arc<Locked<SupportsRule>>),
Page(Arc<Locked<PageRule>>),
Document(Arc<Locked<DocumentRule>>),
}
#[allow(missing_docs)]
@ -321,12 +323,24 @@ pub enum CssRuleType {
CounterStyle = 11,
// https://drafts.csswg.org/css-conditional-3/#extentions-to-cssrule-interface
Supports = 12,
// https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#extentions-to-cssrule-interface
Document = 13,
// https://drafts.csswg.org/css-fonts-3/#om-fontfeaturevalues
FontFeatureValues = 14,
// https://drafts.csswg.org/css-device-adapt/#css-rule-interface
Viewport = 15,
}
/// Result type for with_nested_rules_mq_and_doc_rule()
pub enum NestedRulesResult<'a> {
/// Only rules
Rules(&'a [CssRule]),
/// Rules with media queries
RulesWithMediaQueries(&'a [CssRule], &'a MediaList),
/// Rules with document rule
RulesWithDocument(&'a [CssRule], &'a DocumentRule)
}
#[allow(missing_docs)]
pub enum SingleRuleParseError {
Syntax,
@ -347,6 +361,7 @@ impl CssRule {
CssRule::Viewport(_) => CssRuleType::Viewport,
CssRule::Supports(_) => CssRuleType::Supports,
CssRule::Page(_) => CssRuleType::Page,
CssRule::Document(_) => CssRuleType::Document,
}
}
@ -365,8 +380,8 @@ impl CssRule {
/// used for others.
///
/// This will not recurse down unsupported @supports rules
pub fn with_nested_rules_and_mq<F, R>(&self, guard: &SharedRwLockReadGuard, mut f: F) -> R
where F: FnMut(&[CssRule], Option<&MediaList>) -> R {
pub fn with_nested_rules_mq_and_doc_rule<F, R>(&self, guard: &SharedRwLockReadGuard, mut f: F) -> R
where F: FnMut(NestedRulesResult) -> R {
match *self {
CssRule::Import(ref lock) => {
let rule = lock.read_with(guard);
@ -374,7 +389,7 @@ impl CssRule {
let rules = rule.stylesheet.rules.read_with(guard);
// FIXME(emilio): Include the nested rules if the stylesheet is
// loaded.
f(&rules.0, Some(&media))
f(NestedRulesResult::RulesWithMediaQueries(&rules.0, &media))
}
CssRule::Namespace(_) |
CssRule::Style(_) |
@ -383,22 +398,31 @@ impl CssRule {
CssRule::Viewport(_) |
CssRule::Keyframes(_) |
CssRule::Page(_) => {
f(&[], None)
f(NestedRulesResult::Rules(&[]))
}
CssRule::Media(ref lock) => {
let media_rule = lock.read_with(guard);
let mq = media_rule.media_queries.read_with(guard);
let rules = &media_rule.rules.read_with(guard).0;
f(rules, Some(&mq))
f(NestedRulesResult::RulesWithMediaQueries(rules, &mq))
}
CssRule::Supports(ref lock) => {
let supports_rule = lock.read_with(guard);
let enabled = supports_rule.enabled;
if enabled {
let rules = &supports_rule.rules.read_with(guard).0;
f(rules, None)
f(NestedRulesResult::Rules(rules))
} else {
f(&[], None)
f(NestedRulesResult::Rules(&[]))
}
}
CssRule::Document(ref lock) => {
if cfg!(feature = "gecko") {
let document_rule = lock.read_with(guard);
let rules = &document_rule.rules.read_with(guard).0;
f(NestedRulesResult::RulesWithDocument(rules, &document_rule))
} else {
unimplemented!()
}
}
}
@ -460,6 +484,7 @@ impl ToCssWithGuard for CssRule {
CssRule::Media(ref lock) => lock.read_with(guard).to_css(guard, dest),
CssRule::Supports(ref lock) => lock.read_with(guard).to_css(guard, dest),
CssRule::Page(ref lock) => lock.read_with(guard).to_css(guard, dest),
CssRule::Document(ref lock) => lock.read_with(guard).to_css(guard, dest),
}
}
}
@ -654,6 +679,29 @@ impl ToCssWithGuard for StyleRule {
#[cfg(feature = "servo")]
pub type FontFaceRule = FontFaceRuleData;
#[derive(Debug)]
/// A @-moz-document rule
pub struct DocumentRule {
/// The parsed condition
pub condition: DocumentCondition,
/// Child rules
pub rules: Arc<Locked<CssRules>>,
}
impl ToCssWithGuard for DocumentRule {
fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
where W: fmt::Write {
try!(dest.write_str("@-moz-document "));
try!(self.condition.to_css(dest));
try!(dest.write_str(" {"));
for rule in self.rules.read_with(guard).0.iter() {
try!(dest.write_str(" "));
try!(rule.to_css(guard, dest));
}
dest.write_str(" }")
}
}
impl Stylesheet {
/// Updates an empty stylesheet from a given string of text.
pub fn update_from_str(existing: &Stylesheet,
@ -820,12 +868,24 @@ fn effective_rules<F>(rules: &[CssRule],
{
for rule in rules {
f(rule);
rule.with_nested_rules_and_mq(guard, |rules, mq| {
if let Some(media_queries) = mq {
if !media_queries.evaluate(device, quirks_mode) {
return
}
}
rule.with_nested_rules_mq_and_doc_rule(guard, |result| {
let rules = match result {
NestedRulesResult::Rules(rules) => {
rules
},
NestedRulesResult::RulesWithMediaQueries(rules, media_queries) => {
if !media_queries.evaluate(device, quirks_mode) {
return;
}
rules
},
NestedRulesResult::RulesWithDocument(rules, doc_rule) => {
if !doc_rule.condition.evaluate(device) {
return;
}
rules
},
};
effective_rules(rules, device, quirks_mode, guard, f)
})
}
@ -859,6 +919,7 @@ rule_filter! {
effective_keyframes_rules(Keyframes => KeyframesRule),
effective_supports_rules(Supports => SupportsRule),
effective_page_rules(Page => PageRule),
effective_document_rules(Document => DocumentRule),
}
/// The stylesheet loader is the abstraction used to trigger network requests
@ -948,6 +1009,8 @@ enum AtRulePrelude {
Keyframes(KeyframesName, Option<VendorPrefix>),
/// A @page rule prelude.
Page,
/// A @document rule, with its conditional.
Document(DocumentCondition),
}
@ -1171,6 +1234,14 @@ impl<'a, 'b> AtRuleParser for NestedRuleParser<'a, 'b> {
Err(())
}
},
"-moz-document" => {
if cfg!(feature = "gecko") {
let cond = DocumentCondition::parse(self.context, input)?;
Ok(AtRuleType::WithBlock(AtRulePrelude::Document(cond)))
} else {
Err(())
}
},
_ => Err(())
}
}
@ -1221,6 +1292,16 @@ impl<'a, 'b> AtRuleParser for NestedRuleParser<'a, 'b> {
Arc::new(self.shared_lock.wrap(declarations))
)))))
}
AtRulePrelude::Document(cond) => {
if cfg!(feature = "gecko") {
Ok(CssRule::Document(Arc::new(self.shared_lock.wrap(DocumentRule {
condition: cond,
rules: self.parse_nested_rules(input, CssRuleType::Document),
}))))
} else {
unreachable!()
}
}
}
}
}

View file

@ -42,6 +42,8 @@ use std::marker::PhantomData;
use style_traits::viewport::ViewportConstraints;
use stylearc::Arc;
use stylesheets::{CssRule, FontFaceRule, Origin, StyleRule, Stylesheet, UserAgentStylesheets};
#[cfg(feature = "servo")]
use stylesheets::NestedRulesResult;
use thread_state;
use viewport::{self, MaybeNew, ViewportRule};
@ -596,12 +598,23 @@ impl Stylist {
fn mq_eval_changed(guard: &SharedRwLockReadGuard, rules: &[CssRule],
before: &Device, after: &Device, quirks_mode: QuirksMode) -> bool {
for rule in rules {
let changed = rule.with_nested_rules_and_mq(guard, |rules, mq| {
if let Some(mq) = mq {
if mq.evaluate(before, quirks_mode) != mq.evaluate(after, quirks_mode) {
return true
}
}
let changed = rule.with_nested_rules_mq_and_doc_rule(guard,
|result| {
let rules = match result {
NestedRulesResult::Rules(rules) => rules,
NestedRulesResult::RulesWithMediaQueries(rules, mq) => {
if mq.evaluate(before, quirks_mode) != mq.evaluate(after, quirks_mode) {
return true;
}
rules
},
NestedRulesResult::RulesWithDocument(rules, doc_rule) => {
if !doc_rule.condition.evaluate(before) {
return false;
}
rules
},
};
mq_eval_changed(guard, rules, before, after, quirks_mode)
});
if changed {

View file

@ -20,6 +20,7 @@ cssparser = "0.13"
env_logger = {version = "0.4", default-features = false} # disable `regex` to reduce code size
libc = "0.2"
log = {version = "0.3.5", features = ["release_max_level_info"]}
nsstring_vendor = {path = "../../components/style/gecko_bindings/nsstring_vendor"}
parking_lot = "0.3"
selectors = {path = "../../components/selectors"}
style = {path = "../../components/style", features = ["gecko"]}

View file

@ -13,7 +13,7 @@ use style::media_queries::*;
use style::servo::media_queries::*;
use style::shared_lock::{SharedRwLock, SharedRwLockReadGuard};
use style::stylearc::Arc;
use style::stylesheets::{Stylesheet, Origin, CssRule};
use style::stylesheets::{Stylesheet, Origin, CssRule, NestedRulesResult};
use style::values::specified;
use style_traits::ToCss;
@ -52,11 +52,16 @@ fn media_queries<F>(guard: &SharedRwLockReadGuard, rules: &[CssRule], f: &mut F)
where F: FnMut(&MediaList),
{
for rule in rules {
rule.with_nested_rules_and_mq(guard, |rules, mq| {
if let Some(mq) = mq {
f(mq)
rule.with_nested_rules_mq_and_doc_rule(guard, |result| {
match result {
NestedRulesResult::Rules(rules) |
NestedRulesResult::RulesWithDocument(rules, _) => {
media_queries(guard, rules, f)
},
NestedRulesResult::RulesWithMediaQueries(_, mq) => {
f(mq)
}
}
media_queries(guard, rules, f)
})
}
}