servo/components/style/stylesheets/document_rule.rs
Emilio Cobos Álvarez 322113a393
style: Add another pref to control the url-prefix hack.
MozReview-Commit-ID: D4qL0oO69Uh
Bug: 1446470
Reviewed-by: xidorn
2018-03-19 11:24:19 +01:00

244 lines
8.7 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/. */
//! [@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, SourceLocation};
#[cfg(feature = "gecko")]
use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
use media_queries::Device;
use parser::{Parse, ParserContext};
use servo_arc::Arc;
use shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
use std::fmt::{self, Write};
use str::CssStringWriter;
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
use stylesheets::CssRules;
use values::CssUrl;
#[derive(Debug)]
/// A @-moz-document rule
pub struct DocumentRule {
/// The parsed condition
pub condition: DocumentCondition,
/// Child rules
pub rules: Arc<Locked<CssRules>>,
/// The line and column of the rule's source code.
pub source_location: SourceLocation,
}
impl DocumentRule {
/// Measure heap usage.
#[cfg(feature = "gecko")]
pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
// Measurement of other fields may be added later.
self.rules.unconditional_shallow_size_of(ops) +
self.rules.read_with(guard).size_of(guard, ops)
}
}
impl ToCssWithGuard for DocumentRule {
fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
dest.write_str("@-moz-document ")?;
self.condition.to_css(&mut CssWriter::new(dest))?;
dest.write_str(" {")?;
for rule in self.rules.read_with(guard).0.iter() {
dest.write_str(" ")?;
rule.to_css(guard, dest)?;
}
dest.write_str(" }")
}
}
impl DeepCloneWithLock for DocumentRule {
/// Deep clones this DocumentRule.
fn deep_clone_with_lock(
&self,
lock: &SharedRwLock,
guard: &SharedRwLockReadGuard,
params: &DeepCloneParams,
) -> Self {
let rules = self.rules.read_with(guard);
DocumentRule {
condition: self.condition.clone(),
rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard, params))),
source_location: self.source_location.clone(),
}
}
}
/// A URL matching function for a `@document` rule's condition.
#[derive(Clone, Debug, ToCss)]
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(CssUrl),
/// 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.
#[css(function)]
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.
#[css(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.
#[css(function)]
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| {
let string = input.expect_string()?;
Ok($url_matching_function(string.as_ref().to_owned()))
}).or_else(|_: ParseError| {
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<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
if input.try(|input| input.expect_function_matching("url-prefix")).is_ok() {
return parse_quoted_or_unquoted_string!(input, UrlMatchingFunction::UrlPrefix)
}
if input.try(|input| input.expect_function_matching("domain")).is_ok() {
return parse_quoted_or_unquoted_string!(input, UrlMatchingFunction::Domain)
}
if input.try(|input| input.expect_function_matching("regexp")).is_ok() {
return input.parse_nested_block(|input| {
Ok(UrlMatchingFunction::Regexp(input.expect_string()?.as_ref().to_owned()))
});
}
let url = CssUrl::parse(context, input)?;
Ok(UrlMatchingFunction::Url(url))
}
#[cfg(feature = "gecko")]
/// Evaluate a URL matching function.
pub fn evaluate(&self, device: &Device) -> bool {
use gecko_bindings::bindings::Gecko_DocumentRule_UseForPresentation;
use gecko_bindings::structs::URLMatchingFunction as GeckoUrlMatchingFunction;
use nsstring::nsCStr;
let func = match *self {
UrlMatchingFunction::Url(_) => GeckoUrlMatchingFunction::eURL,
UrlMatchingFunction::UrlPrefix(_) => GeckoUrlMatchingFunction::eURLPrefix,
UrlMatchingFunction::Domain(_) => GeckoUrlMatchingFunction::eDomain,
UrlMatchingFunction::Regexp(_) => GeckoUrlMatchingFunction::eRegExp,
};
let pattern = nsCStr::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
}
}
/// 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.
#[css(comma)]
#[derive(Clone, Debug, ToCss)]
pub struct DocumentCondition(#[css(iterable)] Vec<UrlMatchingFunction>);
impl DocumentCondition {
/// Parse a document condition.
pub fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let conditions = input.parse_comma_separated(|input| {
UrlMatchingFunction::parse(context, input)
})?;
let condition = DocumentCondition(conditions);
if !condition.allowed_in(context) {
return Err(input.new_custom_error(
StyleParseErrorKind::UnsupportedAtRule("-moz-document".into())
))
}
Ok(condition)
}
/// Evaluate a document condition.
pub fn evaluate(&self, device: &Device) -> bool {
self.0.iter().any(|url_matching_function| {
url_matching_function.evaluate(device)
})
}
#[cfg(feature = "servo")]
fn allowed_in(&self, _: &ParserContext) -> bool {
false
}
#[cfg(feature = "gecko")]
fn allowed_in(&self, context: &ParserContext) -> bool {
use gecko_bindings::structs;
use stylesheets::Origin;
if context.stylesheet_origin != Origin::Author {
return true;
}
if unsafe { structs::StylePrefs_sMozDocumentEnabledInContent } {
return true;
}
if !unsafe { structs::StylePrefs_sMozDocumentURLPrefixHackEnabled } {
return false;
}
// Allow a single url-prefix() for compatibility.
//
// See bug 1446470 and dependencies.
if self.0.len() != 1 {
return false;
}
// NOTE(emilio): This technically allows url-prefix("") too, but...
match self.0[0] {
UrlMatchingFunction::UrlPrefix(ref prefix) => prefix.is_empty(),
_ => false
}
}
}