mirror of
https://github.com/servo/servo.git
synced 2025-08-04 13:10:20 +01:00
auto merge of #831 : SimonSapin/servo/newnewcss, r=metajack
I started this in a [separate repository](https://github.com/SimonSapin/servo-style), and imported it with [git-subtree](https://github.com/git/git/blob/master/contrib/subtree/git-subtree.txt) into `servo/src/components/script/style` after some Rust minor upgrades. I move this into the script crate because it’s gonna both need stuff there (the content tree, for selector matching) and be needed by stuff there (the HTML parser calls the CSS parser). As far as I know, we can not have circular dependencies between crates.
This commit is contained in:
commit
df2906fc29
27 changed files with 1799 additions and 27 deletions
|
@ -226,7 +226,10 @@ DEPS_gfx = $(CRATE_gfx) $(SRC_gfx) $(DONE_SUBMODULES) $(DONE_util) $(DONE_net) $
|
|||
RFLAGS_script = $(strip $(CFG_RUSTC_FLAGS)) $(addprefix -L $(B)src/,$(DEPS_SUBMODULES)) -L $(B)src/components/util -L $(B)src/components/net -L $(B)src/components/gfx -L $(B)src/components/msg
|
||||
WEBIDL_script = $(call rwildcard,$(S)src/components/script/,*.webidl)
|
||||
AUTOGEN_SRC_script = $(patsubst %.webidl, %Binding.rs, $(WEBIDL_script))
|
||||
SRC_script = $(call rwildcard,$(S)src/components/script/,*.rs) $(AUTOGEN_SRC_script)
|
||||
MAKO_ZIP = $(S)src/components/script/style/properties/Mako-0.8.1.zip
|
||||
MAKO_script = $(S)src/components/script/style/properties/mod.rs
|
||||
MAKO_SRC_script = $(MAKO_script).mako
|
||||
SRC_script = $(call rwildcard,$(S)src/components/script/,*.rs) $(AUTOGEN_SRC_script) $(MAKO_script)
|
||||
CRATE_script = $(S)src/components/script/script.rc
|
||||
DONE_script = $(B)src/components/script/libscript.dummy
|
||||
|
||||
|
@ -287,6 +290,10 @@ $(AUTOGEN_SRC_script): %Binding.rs: $(bindinggen_dependencies) \
|
|||
|
||||
globalgen_dependencies := $(addprefix $(BINDINGS_SRC)/, GlobalGen.py Bindings.conf Configuration.py CodegenRust.py parser/WebIDL.py) $(CACHE_DIR)/.done
|
||||
|
||||
$(MAKO_script): $(MAKO_SRC_script)
|
||||
PYTHONPATH=$(MAKO_ZIP) python -c "from mako.template import Template; print(Template(filename='$<').render())" > $@
|
||||
|
||||
|
||||
$(CACHE_DIR)/.done:
|
||||
mkdir -p $(CACHE_DIR)
|
||||
@touch $@
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit c822d1070ac39871165df30ac8d09e733a6e7fb9
|
||||
Subproject commit 0ac3e023d86fa84ed38bca3d34003b494fd28acf
|
|
@ -1,4 +1,4 @@
|
|||
# If this file is modified, then rust will be forcibly cleaned and then rebuilt.
|
||||
# The actual contents of this file do not matter, but to trigger a change on the
|
||||
# build bots then the contents should be changed so git updates the mtime.
|
||||
2013-08-30
|
||||
2013-09-03
|
||||
|
|
|
@ -86,8 +86,8 @@ impl ShapedGlyphData {
|
|||
fn byte_offset_of_glyph(&self, i: uint) -> uint {
|
||||
assert!(i < self.count);
|
||||
|
||||
let glyph_info_i = ptr::offset(self.glyph_infos, i as int);
|
||||
unsafe {
|
||||
let glyph_info_i = ptr::offset(self.glyph_infos, i as int);
|
||||
(*glyph_info_i).cluster as uint
|
||||
}
|
||||
}
|
||||
|
|
|
@ -303,7 +303,7 @@ impl LayoutTask {
|
|||
ctx: &layout_ctx,
|
||||
};
|
||||
|
||||
let display_list = ~Cell::new(DisplayList::new::<AbstractNode<()>>());
|
||||
let display_list = ~Cell::new(DisplayList::<AbstractNode<()>>::new());
|
||||
|
||||
// TODO: Set options on the builder before building.
|
||||
// TODO: Be smarter about what needs painting.
|
||||
|
|
|
@ -1247,10 +1247,10 @@ for (uint32_t i = 0; i < length; ++i) {
|
|||
dataLoc = "${declName}"
|
||||
#XXXjdm conversionBehavior should be used
|
||||
template = (
|
||||
"match JSValConvertible::from_jsval::<%s>(${val}) {\n"
|
||||
"match JSValConvertible::from_jsval(${val}) {\n"
|
||||
" None => return 0,\n"
|
||||
" Some(v) => %s = v\n"
|
||||
"}" % (typeName, dataLoc))
|
||||
"}" % (dataLoc,))
|
||||
declType = CGGeneric(typeName)
|
||||
if (defaultValue is not None and
|
||||
# We already handled IDLNullValue, so just deal with the other ones
|
||||
|
|
|
@ -17,6 +17,7 @@ extern mod hubbub;
|
|||
extern mod js;
|
||||
extern mod netsurfcss;
|
||||
extern mod newcss (name = "css");
|
||||
extern mod cssparser;
|
||||
extern mod servo_net (name = "net");
|
||||
extern mod servo_util (name = "util");
|
||||
extern mod servo_msg (name = "msg");
|
||||
|
@ -140,3 +141,6 @@ pub mod html {
|
|||
pub mod layout_interface;
|
||||
pub mod script_task;
|
||||
|
||||
|
||||
// "New" (as of 2013-08) style system, not used yet but included to avoid bitrot.
|
||||
mod style;
|
||||
|
|
6
src/components/script/style/README.md
Normal file
6
src/components/script/style/README.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
servo-style
|
||||
===========
|
||||
|
||||
Prototype replacement style system for Servo,
|
||||
based on [rust-cssparser](https://github.com/mozilla-servo/rust-cssparser)
|
||||
instead of [NetSurf’s libcss](https://github.com/mozilla-servo/libcss).
|
26
src/components/script/style/errors.rs
Normal file
26
src/components/script/style/errors.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
/* 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 cssparser::{SyntaxError, SourceLocation};
|
||||
|
||||
|
||||
pub struct ErrorLoggerIterator<I>(I);
|
||||
|
||||
impl<T, I: Iterator<Result<T, SyntaxError>>> Iterator<T> for ErrorLoggerIterator<I> {
|
||||
fn next(&mut self) -> Option<T> {
|
||||
for result in **self {
|
||||
match result {
|
||||
Ok(v) => return Some(v),
|
||||
Err(error) => log_css_error(error.location, fmt!("%?", error.reason))
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn log_css_error(location: SourceLocation, message: &str) {
|
||||
// TODO eventually this will got into a "web console" or something.
|
||||
info!("%u:%u %s", location.line, location.column, message)
|
||||
}
|
124
src/components/script/style/media_queries.rs
Normal file
124
src/components/script/style/media_queries.rs
Normal file
|
@ -0,0 +1,124 @@
|
|||
/* 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 std::ascii::StrAsciiExt;
|
||||
use cssparser::*;
|
||||
use style::errors::{ErrorLoggerIterator, log_css_error};
|
||||
use style::stylesheets::{CSSRule, CSSMediaRule, parse_style_rule, parse_nested_at_rule};
|
||||
use style::namespaces::NamespaceMap;
|
||||
|
||||
|
||||
pub struct MediaRule {
|
||||
media_queries: MediaQueryList,
|
||||
rules: ~[CSSRule],
|
||||
}
|
||||
|
||||
|
||||
pub struct MediaQueryList {
|
||||
// "not all" is omitted from the list.
|
||||
// An empty list never matches.
|
||||
media_queries: ~[MediaQuery]
|
||||
}
|
||||
|
||||
// For now, this is a "Level 2 MQ", ie. a media type.
|
||||
struct MediaQuery {
|
||||
media_type: MediaQueryType,
|
||||
// TODO: Level 3 MQ expressions
|
||||
}
|
||||
|
||||
|
||||
enum MediaQueryType {
|
||||
All, // Always true
|
||||
MediaType(MediaType),
|
||||
}
|
||||
|
||||
#[deriving(Eq)]
|
||||
pub enum MediaType {
|
||||
Screen,
|
||||
Print,
|
||||
}
|
||||
|
||||
pub struct Device {
|
||||
media_type: MediaType,
|
||||
// TODO: Level 3 MQ data: viewport size, etc.
|
||||
}
|
||||
|
||||
|
||||
pub fn parse_media_rule(rule: AtRule, parent_rules: &mut ~[CSSRule],
|
||||
namespaces: &NamespaceMap) {
|
||||
let media_queries = parse_media_query_list(rule.prelude);
|
||||
let block = match rule.block {
|
||||
Some(block) => block,
|
||||
None => {
|
||||
log_css_error(rule.location, "Invalid @media rule");
|
||||
return
|
||||
}
|
||||
};
|
||||
let mut rules = ~[];
|
||||
for rule in ErrorLoggerIterator(parse_rule_list(block.move_iter())) {
|
||||
match rule {
|
||||
QualifiedRule(rule) => parse_style_rule(rule, &mut rules, namespaces),
|
||||
AtRule(rule) => parse_nested_at_rule(
|
||||
rule.name.to_ascii_lower(), rule, &mut rules, namespaces),
|
||||
}
|
||||
}
|
||||
parent_rules.push(CSSMediaRule(MediaRule {
|
||||
media_queries: media_queries,
|
||||
rules: rules,
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
pub fn parse_media_query_list(input: &[ComponentValue]) -> MediaQueryList {
|
||||
let iter = &mut input.skip_whitespace();
|
||||
let mut next = iter.next();
|
||||
if next.is_none() {
|
||||
return MediaQueryList{ media_queries: ~[MediaQuery{media_type: All}] }
|
||||
}
|
||||
let mut queries = ~[];
|
||||
loop {
|
||||
let mq = match next {
|
||||
Some(&Ident(ref value)) => {
|
||||
match value.to_ascii_lower().as_slice() {
|
||||
"screen" => Some(MediaQuery{ media_type: MediaType(Screen) }),
|
||||
"print" => Some(MediaQuery{ media_type: MediaType(Print) }),
|
||||
"all" => Some(MediaQuery{ media_type: All }),
|
||||
_ => None
|
||||
}
|
||||
},
|
||||
_ => None
|
||||
};
|
||||
match iter.next() {
|
||||
None => {
|
||||
mq.map_move(|mq| queries.push(mq));
|
||||
return MediaQueryList{ media_queries: queries }
|
||||
},
|
||||
Some(&Comma) => {
|
||||
mq.map_move(|mq| queries.push(mq));
|
||||
},
|
||||
// Ingnore this comma-separated part
|
||||
_ => loop {
|
||||
match iter.next() {
|
||||
Some(&Comma) => break,
|
||||
None => return MediaQueryList{ media_queries: queries },
|
||||
_ => (),
|
||||
}
|
||||
},
|
||||
}
|
||||
next = iter.next();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl MediaQueryList {
|
||||
pub fn evaluate(&self, device: &Device) -> bool {
|
||||
do self.media_queries.iter().any |mq| {
|
||||
match mq.media_type {
|
||||
MediaType(media_type) => media_type == device.media_type,
|
||||
All => true,
|
||||
}
|
||||
// TODO: match Level 3 expressions
|
||||
}
|
||||
}
|
||||
}
|
11
src/components/script/style/mod.rs
Normal file
11
src/components/script/style/mod.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
/* 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/. */
|
||||
|
||||
pub mod stylesheets;
|
||||
pub mod errors;
|
||||
pub mod selectors;
|
||||
pub mod properties;
|
||||
pub mod namespaces;
|
||||
pub mod media_queries;
|
||||
pub mod parsing_utils;
|
63
src/components/script/style/namespaces.rs
Normal file
63
src/components/script/style/namespaces.rs
Normal file
|
@ -0,0 +1,63 @@
|
|||
/* 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 std::hashmap::HashMap;
|
||||
use cssparser::*;
|
||||
use style::errors::log_css_error;
|
||||
|
||||
pub struct NamespaceMap {
|
||||
default: Option<~str>, // Optional URL
|
||||
prefix_map: HashMap<~str, ~str>, // prefix -> URL
|
||||
}
|
||||
|
||||
|
||||
impl NamespaceMap {
|
||||
pub fn new() -> NamespaceMap {
|
||||
NamespaceMap { default: None, prefix_map: HashMap::new() }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn parse_namespace_rule(rule: AtRule, namespaces: &mut NamespaceMap) {
|
||||
let location = rule.location;
|
||||
macro_rules! syntax_error(
|
||||
() => {{
|
||||
log_css_error(location, "Invalid @namespace rule");
|
||||
return
|
||||
}};
|
||||
);
|
||||
if rule.block.is_some() { syntax_error!() }
|
||||
let mut prefix: Option<~str> = None;
|
||||
let mut url: Option<~str> = None;
|
||||
let mut iter = rule.prelude.move_skip_whitespace();
|
||||
for component_value in iter {
|
||||
match component_value {
|
||||
Ident(value) => {
|
||||
if prefix.is_some() { syntax_error!() }
|
||||
prefix = Some(value);
|
||||
},
|
||||
URL(value) | String(value) => {
|
||||
if url.is_some() { syntax_error!() }
|
||||
url = Some(value);
|
||||
break
|
||||
},
|
||||
_ => syntax_error!(),
|
||||
}
|
||||
}
|
||||
if iter.next().is_some() { syntax_error!() }
|
||||
match (prefix, url) {
|
||||
(Some(prefix), Some(url)) => {
|
||||
if namespaces.prefix_map.swap(prefix, url).is_some() {
|
||||
log_css_error(location, "Duplicate @namespace rule");
|
||||
}
|
||||
},
|
||||
(None, Some(url)) => {
|
||||
if namespaces.default.is_some() {
|
||||
log_css_error(location, "Duplicate @namespace rule");
|
||||
}
|
||||
namespaces.default = Some(url);
|
||||
},
|
||||
_ => syntax_error!()
|
||||
}
|
||||
}
|
21
src/components/script/style/parsing_utils.rs
Normal file
21
src/components/script/style/parsing_utils.rs
Normal file
|
@ -0,0 +1,21 @@
|
|||
/* 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 std::ascii::StrAsciiExt;
|
||||
use cssparser::*;
|
||||
|
||||
|
||||
pub fn one_component_value<'a>(input: &'a [ComponentValue]) -> Option<&'a ComponentValue> {
|
||||
let mut iter = input.skip_whitespace();
|
||||
iter.next().filtered(|_| iter.next().is_none())
|
||||
}
|
||||
|
||||
|
||||
pub fn get_ident_lower(component_value: &ComponentValue) -> Option<~str> {
|
||||
match component_value {
|
||||
&Ident(ref value) => Some(value.to_ascii_lower()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
1
src/components/script/style/properties/.gitignore
vendored
Normal file
1
src/components/script/style/properties/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
mod.rs
|
BIN
src/components/script/style/properties/Mako-0.8.1.zip
Normal file
BIN
src/components/script/style/properties/Mako-0.8.1.zip
Normal file
Binary file not shown.
182
src/components/script/style/properties/common_types.rs
Normal file
182
src/components/script/style/properties/common_types.rs
Normal file
|
@ -0,0 +1,182 @@
|
|||
/* 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/. */
|
||||
|
||||
pub type Float = f64;
|
||||
pub type Integer = i64;
|
||||
|
||||
|
||||
pub mod specified {
|
||||
use std::ascii::StrAsciiExt;
|
||||
use cssparser::*;
|
||||
use super::{Integer, Float};
|
||||
pub use CSSColor = cssparser::Color;
|
||||
|
||||
pub enum Length {
|
||||
Au(Integer), // application units
|
||||
Em(Float),
|
||||
Ex(Float),
|
||||
// XXX uncomment when supported:
|
||||
// Ch(Float),
|
||||
// Rem(Float),
|
||||
// Vw(Float),
|
||||
// Vh(Float),
|
||||
// Vmin(Float),
|
||||
// Vmax(Float),
|
||||
}
|
||||
static AU_PER_PX: Float = 60.;
|
||||
static AU_PER_IN: Float = AU_PER_PX * 96.;
|
||||
static AU_PER_CM: Float = AU_PER_IN / 2.54;
|
||||
static AU_PER_MM: Float = AU_PER_IN / 25.4;
|
||||
static AU_PER_PT: Float = AU_PER_IN / 72.;
|
||||
static AU_PER_PC: Float = AU_PER_PT * 12.;
|
||||
impl Length {
|
||||
#[inline]
|
||||
fn parse_internal(input: &ComponentValue, negative_ok: bool) -> Option<Length> {
|
||||
match input {
|
||||
&Dimension(ref value, ref unit) if negative_ok || value.value >= 0.
|
||||
=> Length::parse_dimension(value.value, unit.as_slice()),
|
||||
&Number(ref value) if value.value == 0. => Some(Au(0)),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
pub fn parse(input: &ComponentValue) -> Option<Length> {
|
||||
Length::parse_internal(input, /* negative_ok = */ true)
|
||||
}
|
||||
pub fn parse_non_negative(input: &ComponentValue) -> Option<Length> {
|
||||
Length::parse_internal(input, /* negative_ok = */ false)
|
||||
}
|
||||
pub fn parse_dimension(value: Float, unit: &str) -> Option<Length> {
|
||||
match unit.to_ascii_lower().as_slice() {
|
||||
"px" => Some(Length::from_px(value)),
|
||||
"in" => Some(Au((value * AU_PER_IN) as Integer)),
|
||||
"cm" => Some(Au((value * AU_PER_CM) as Integer)),
|
||||
"mm" => Some(Au((value * AU_PER_MM) as Integer)),
|
||||
"pt" => Some(Au((value * AU_PER_PT) as Integer)),
|
||||
"pc" => Some(Au((value * AU_PER_PC) as Integer)),
|
||||
"em" => Some(Em(value)),
|
||||
"ex" => Some(Ex(value)),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn from_px(px_value: Float) -> Length {
|
||||
Au((px_value * AU_PER_PX) as Integer)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum LengthOrPercentage {
|
||||
LP_Length(Length),
|
||||
LP_Percentage(Float),
|
||||
}
|
||||
impl LengthOrPercentage {
|
||||
fn parse_internal(input: &ComponentValue, negative_ok: bool)
|
||||
-> Option<LengthOrPercentage> {
|
||||
match input {
|
||||
&Dimension(ref value, ref unit) if negative_ok || value.value >= 0.
|
||||
=> Length::parse_dimension(value.value, unit.as_slice()).map_move(LP_Length),
|
||||
&ast::Percentage(ref value) if negative_ok || value.value >= 0.
|
||||
=> Some(LP_Percentage(value.value)),
|
||||
&Number(ref value) if value.value == 0. => Some(LP_Length(Au(0))),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn parse(input: &ComponentValue) -> Option<LengthOrPercentage> {
|
||||
LengthOrPercentage::parse_internal(input, /* negative_ok = */ true)
|
||||
}
|
||||
#[inline]
|
||||
pub fn parse_non_negative(input: &ComponentValue) -> Option<LengthOrPercentage> {
|
||||
LengthOrPercentage::parse_internal(input, /* negative_ok = */ false)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum LengthOrPercentageOrAuto {
|
||||
LPA_Length(Length),
|
||||
LPA_Percentage(Float),
|
||||
LPA_Auto,
|
||||
}
|
||||
impl LengthOrPercentageOrAuto {
|
||||
fn parse_internal(input: &ComponentValue, negative_ok: bool)
|
||||
-> Option<LengthOrPercentageOrAuto> {
|
||||
match input {
|
||||
&Dimension(ref value, ref unit) if negative_ok || value.value >= 0.
|
||||
=> Length::parse_dimension(value.value, unit.as_slice()).map_move(LPA_Length),
|
||||
&ast::Percentage(ref value) if negative_ok || value.value >= 0.
|
||||
=> Some(LPA_Percentage(value.value)),
|
||||
&Number(ref value) if value.value == 0. => Some(LPA_Length(Au(0))),
|
||||
&Ident(ref value) if value.eq_ignore_ascii_case("auto") => Some(LPA_Auto),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn parse(input: &ComponentValue) -> Option<LengthOrPercentageOrAuto> {
|
||||
LengthOrPercentageOrAuto::parse_internal(input, /* negative_ok = */ true)
|
||||
}
|
||||
#[inline]
|
||||
pub fn parse_non_negative(input: &ComponentValue) -> Option<LengthOrPercentageOrAuto> {
|
||||
LengthOrPercentageOrAuto::parse_internal(input, /* negative_ok = */ false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod computed {
|
||||
use cssparser;
|
||||
pub use CSSColor = cssparser::Color;
|
||||
pub use compute_CSSColor = std::util::id;
|
||||
use super::*;
|
||||
use super::super::longhands::font_weight;
|
||||
pub struct Context {
|
||||
current_color: cssparser::RGBA,
|
||||
has_border_top: bool,
|
||||
has_border_right: bool,
|
||||
has_border_bottom: bool,
|
||||
has_border_left: bool,
|
||||
font_size: Length,
|
||||
font_weight: font_weight::ComputedValue,
|
||||
// TODO, as needed: root font size, viewport size, etc.
|
||||
}
|
||||
pub struct Length(Integer); // in application units
|
||||
impl Length {
|
||||
pub fn times(self, factor: Float) -> Length {
|
||||
Length(((*self as Float) * factor) as Integer)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compute_Length(value: specified::Length, context: &Context) -> Length {
|
||||
match value {
|
||||
specified::Au(value) => Length(value),
|
||||
specified::Em(value) => context.font_size.times(value),
|
||||
specified::Ex(value) => {
|
||||
let x_height = 0.5; // TODO: find that from the font
|
||||
context.font_size.times(value * x_height)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub enum LengthOrPercentage {
|
||||
LP_Length(Length),
|
||||
LP_Percentage(Float),
|
||||
}
|
||||
pub fn compute_LengthOrPercentage(value: specified::LengthOrPercentage, context: &Context)
|
||||
-> LengthOrPercentage {
|
||||
match value {
|
||||
specified::LP_Length(value) => LP_Length(compute_Length(value, context)),
|
||||
specified::LP_Percentage(value) => LP_Percentage(value),
|
||||
}
|
||||
}
|
||||
|
||||
pub enum LengthOrPercentageOrAuto {
|
||||
LPA_Length(Length),
|
||||
LPA_Percentage(Float),
|
||||
LPA_Auto,
|
||||
}
|
||||
pub fn compute_LengthOrPercentageOrAuto(value: specified::LengthOrPercentageOrAuto,
|
||||
context: &Context) -> LengthOrPercentageOrAuto {
|
||||
match value {
|
||||
specified::LPA_Length(value) => LPA_Length(compute_Length(value, context)),
|
||||
specified::LPA_Percentage(value) => LPA_Percentage(value),
|
||||
specified::LPA_Auto => LPA_Auto,
|
||||
}
|
||||
}
|
||||
}
|
687
src/components/script/style/properties/mod.rs.mako
Normal file
687
src/components/script/style/properties/mod.rs.mako
Normal file
|
@ -0,0 +1,687 @@
|
|||
/* 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/. */
|
||||
|
||||
// This file is a Mako template: http://www.makotemplates.org/
|
||||
|
||||
use std::ascii::StrAsciiExt;
|
||||
pub use std::iterator;
|
||||
pub use cssparser::*;
|
||||
pub use style::errors::{ErrorLoggerIterator, log_css_error};
|
||||
pub use style::parsing_utils::*;
|
||||
pub use self::common_types::*;
|
||||
|
||||
pub mod common_types;
|
||||
|
||||
|
||||
<%!
|
||||
|
||||
def to_rust_ident(name):
|
||||
name = name.replace("-", "_")
|
||||
if name in ["static"]: # Rust keywords
|
||||
name += "_"
|
||||
return name
|
||||
|
||||
class Longhand(object):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.ident = to_rust_ident(name)
|
||||
|
||||
|
||||
class Shorthand(object):
|
||||
def __init__(self, name, sub_properties):
|
||||
self.name = name
|
||||
self.ident = to_rust_ident(name)
|
||||
self.sub_properties = [Longhand(s) for s in sub_properties]
|
||||
|
||||
LONGHANDS = []
|
||||
SHORTHANDS = []
|
||||
INHERITED = set()
|
||||
|
||||
%>
|
||||
|
||||
pub mod longhands {
|
||||
pub use super::*;
|
||||
pub use std;
|
||||
|
||||
<%def name="longhand(name, inherited=False, no_super=False)">
|
||||
<%
|
||||
property = Longhand(name)
|
||||
LONGHANDS.append(property)
|
||||
if inherited:
|
||||
INHERITED.add(name)
|
||||
%>
|
||||
pub mod ${property.ident} {
|
||||
% if not no_super:
|
||||
use super::*;
|
||||
% endif
|
||||
${caller.body()}
|
||||
}
|
||||
</%def>
|
||||
|
||||
<%def name="single_component_value(name, inherited=False)">
|
||||
<%self:longhand name="${name}" inherited="${inherited}">
|
||||
${caller.body()}
|
||||
pub fn parse(input: &[ComponentValue]) -> Option<SpecifiedValue> {
|
||||
one_component_value(input).chain(from_component_value)
|
||||
}
|
||||
</%self:longhand>
|
||||
</%def>
|
||||
|
||||
<%def name="single_keyword(name, values, inherited=False)">
|
||||
<%self:single_component_value name="${name}" inherited="${inherited}">
|
||||
// The computed value is the same as the specified value.
|
||||
pub use to_computed_value = std::util::id;
|
||||
pub enum SpecifiedValue {
|
||||
% for value in values.split():
|
||||
${to_rust_ident(value)},
|
||||
% endfor
|
||||
}
|
||||
pub type ComputedValue = SpecifiedValue;
|
||||
#[inline] pub fn get_initial_value() -> ComputedValue {
|
||||
${to_rust_ident(values.split()[0])}
|
||||
}
|
||||
pub fn from_component_value(v: &ComponentValue) -> Option<SpecifiedValue> {
|
||||
do get_ident_lower(v).chain |keyword| {
|
||||
match keyword.as_slice() {
|
||||
% for value in values.split():
|
||||
"${value}" => Some(${to_rust_ident(value)}),
|
||||
% endfor
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
</%self:single_component_value>
|
||||
</%def>
|
||||
|
||||
<%def name="predefined_type(name, type, initial_value, parse_method='parse', inherited=False)">
|
||||
<%self:longhand name="${name}" inherited="${inherited}">
|
||||
pub use to_computed_value = super::super::common_types::computed::compute_${type};
|
||||
pub type SpecifiedValue = specified::${type};
|
||||
pub type ComputedValue = computed::${type};
|
||||
#[inline] pub fn get_initial_value() -> ComputedValue { ${initial_value} }
|
||||
pub fn parse(input: &[ComponentValue]) -> Option<SpecifiedValue> {
|
||||
one_component_value(input).chain(specified::${type}::${parse_method})
|
||||
}
|
||||
</%self:longhand>
|
||||
</%def>
|
||||
|
||||
|
||||
// CSS 2.1, Section 8 - Box model
|
||||
|
||||
% for side in ["top", "right", "bottom", "left"]:
|
||||
${predefined_type("margin-" + side, "LengthOrPercentageOrAuto",
|
||||
"computed::LPA_Length(computed::Length(0))")}
|
||||
% endfor
|
||||
|
||||
% for side in ["top", "right", "bottom", "left"]:
|
||||
${predefined_type("padding-" + side, "LengthOrPercentage",
|
||||
"computed::LP_Length(computed::Length(0))",
|
||||
"parse_non_negative")}
|
||||
% endfor
|
||||
|
||||
% for side in ["top", "right", "bottom", "left"]:
|
||||
${predefined_type("border-%s-color" % side, "CSSColor", "CurrentColor")}
|
||||
% endfor
|
||||
|
||||
// dotted dashed double groove ridge insed outset hidden
|
||||
${single_keyword("border-top-style", "none solid")}
|
||||
% for side in ["right", "bottom", "left"]:
|
||||
<%self:longhand name="border-${side}-style", no_super="True">
|
||||
pub use super::border_top_style::*;
|
||||
pub type SpecifiedValue = super::border_top_style::SpecifiedValue;
|
||||
pub type ComputedValue = super::border_top_style::ComputedValue;
|
||||
</%self:longhand>
|
||||
% endfor
|
||||
|
||||
pub fn parse_border_width(component_value: &ComponentValue) -> Option<specified::Length> {
|
||||
match component_value {
|
||||
&Ident(ref value) => match value.to_ascii_lower().as_slice() {
|
||||
"thin" => Some(specified::Length::from_px(1.)),
|
||||
"medium" => Some(specified::Length::from_px(3.)),
|
||||
"thick" => Some(specified::Length::from_px(5.)),
|
||||
_ => None
|
||||
},
|
||||
_ => specified::Length::parse_non_negative(component_value)
|
||||
}
|
||||
}
|
||||
% for side in ["top", "right", "bottom", "left"]:
|
||||
<%self:longhand name="border-${side}-width">
|
||||
pub type SpecifiedValue = specified::Length;
|
||||
pub type ComputedValue = computed::Length;
|
||||
#[inline] pub fn get_initial_value() -> ComputedValue {
|
||||
computed::Length(3 * 60) // medium
|
||||
}
|
||||
pub fn parse(input: &[ComponentValue]) -> Option<SpecifiedValue> {
|
||||
one_component_value(input).chain(parse_border_width)
|
||||
}
|
||||
pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context)
|
||||
-> ComputedValue {
|
||||
if context.has_border_${side} { computed::compute_Length(value, context) }
|
||||
else { computed::Length(0) }
|
||||
}
|
||||
</%self:longhand>
|
||||
% endfor
|
||||
|
||||
// CSS 2.1, Section 9 - Visual formatting model
|
||||
|
||||
// TODO: don't parse values we don't support
|
||||
${single_keyword("display",
|
||||
"inline block list-item inline-block none "
|
||||
)}
|
||||
// "table inline-table table-row-group table-header-group table-footer-group "
|
||||
// "table-row table-column-group table-column table-cell table-caption"
|
||||
|
||||
${single_keyword("position", "static absolute relative fixed")}
|
||||
${single_keyword("float", "none left right")}
|
||||
${single_keyword("clear", "none left right both")}
|
||||
|
||||
// CSS 2.1, Section 10 - Visual formatting model details
|
||||
|
||||
${predefined_type("width", "LengthOrPercentageOrAuto",
|
||||
"computed::LPA_Auto",
|
||||
"parse_non_negative")}
|
||||
${predefined_type("height", "LengthOrPercentageOrAuto",
|
||||
"computed::LPA_Auto",
|
||||
"parse_non_negative")}
|
||||
|
||||
<%self:single_component_value name="line-height">
|
||||
pub enum SpecifiedValue {
|
||||
SpecifiedNormal,
|
||||
SpecifiedLength(specified::Length),
|
||||
SpecifiedNumber(Float),
|
||||
// percentage are the same as em.
|
||||
}
|
||||
/// normal | <number> | <length> | <percentage>
|
||||
pub fn from_component_value(input: &ComponentValue) -> Option<SpecifiedValue> {
|
||||
match input {
|
||||
&ast::Number(ref value) if value.value >= 0.
|
||||
=> Some(SpecifiedNumber(value.value)),
|
||||
&ast::Percentage(ref value) if value.value >= 0.
|
||||
=> Some(SpecifiedLength(specified::Em(value.value))),
|
||||
&Dimension(ref value, ref unit) if value.value >= 0.
|
||||
=> specified::Length::parse_dimension(value.value, unit.as_slice())
|
||||
.map_move(SpecifiedLength),
|
||||
&Ident(ref value) if value.eq_ignore_ascii_case("normal")
|
||||
=> Some(SpecifiedNormal),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub enum ComputedValue {
|
||||
Normal,
|
||||
Length(computed::Length),
|
||||
Number(Float),
|
||||
}
|
||||
#[inline] pub fn get_initial_value() -> ComputedValue { Normal }
|
||||
pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context)
|
||||
-> ComputedValue {
|
||||
match value {
|
||||
SpecifiedNormal => Normal,
|
||||
SpecifiedLength(value) => Length(computed::compute_Length(value, context)),
|
||||
SpecifiedNumber(value) => Number(value),
|
||||
}
|
||||
}
|
||||
</%self:single_component_value>
|
||||
|
||||
// CSS 2.1, Section 11 - Visual effects
|
||||
|
||||
// CSS 2.1, Section 12 - Generated content, automatic numbering, and lists
|
||||
|
||||
// CSS 2.1, Section 13 - Paged media
|
||||
|
||||
// CSS 2.1, Section 14 - Colors and Backgrounds
|
||||
|
||||
${predefined_type("background-color", "CSSColor",
|
||||
"RGBA(RGBA { red: 0., green: 0., blue: 0., alpha: 0. }) /* transparent */")}
|
||||
${predefined_type("color", "CSSColor",
|
||||
"RGBA(RGBA { red: 0., green: 0., blue: 0., alpha: 1. }) /* black */",
|
||||
inherited=True)}
|
||||
|
||||
// CSS 2.1, Section 15 - Fonts
|
||||
|
||||
<%self:longhand name="font-family" inherited="True">
|
||||
pub use to_computed_value = std::util::id;
|
||||
enum FontFamily {
|
||||
FamilyName(~str),
|
||||
// Generic
|
||||
// Serif,
|
||||
// SansSerif,
|
||||
// Cursive,
|
||||
// Fantasy,
|
||||
// Monospace,
|
||||
}
|
||||
pub type SpecifiedValue = ~[FontFamily];
|
||||
pub type ComputedValue = SpecifiedValue;
|
||||
#[inline] pub fn get_initial_value() -> ComputedValue { ~[FamilyName(~"serif")] }
|
||||
/// <familiy-name>#
|
||||
/// <familiy-name> = <string> | [ <ident>+ ]
|
||||
/// TODO: <generic-familiy>
|
||||
pub fn parse(input: &[ComponentValue]) -> Option<SpecifiedValue> {
|
||||
from_iter(input.skip_whitespace())
|
||||
}
|
||||
pub fn from_iter<'a>(mut iter: SkipWhitespaceIterator<'a>) -> Option<SpecifiedValue> {
|
||||
let mut result = ~[];
|
||||
macro_rules! add(
|
||||
($value: expr) => {
|
||||
{
|
||||
result.push($value);
|
||||
match iter.next() {
|
||||
Some(&Comma) => (),
|
||||
None => break 'outer,
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
'outer: loop {
|
||||
match iter.next() {
|
||||
// TODO: avoid copying strings?
|
||||
Some(&String(ref value)) => add!(FamilyName(value.to_owned())),
|
||||
Some(&Ident(ref value)) => {
|
||||
let value = value.as_slice();
|
||||
match value.to_ascii_lower().as_slice() {
|
||||
// "serif" => add!(Serif),
|
||||
// "sans-serif" => add!(SansSerif),
|
||||
// "cursive" => add!(Cursive),
|
||||
// "fantasy" => add!(Fantasy),
|
||||
// "monospace" => add!(Monospace),
|
||||
_ => {
|
||||
let mut idents = ~[value];
|
||||
loop {
|
||||
match iter.next() {
|
||||
Some(&Ident(ref value)) => idents.push(value.as_slice()),
|
||||
Some(&Comma) => {
|
||||
result.push(FamilyName(idents.connect(" ")));
|
||||
break
|
||||
},
|
||||
None => {
|
||||
result.push(FamilyName(idents.connect(" ")));
|
||||
break 'outer
|
||||
},
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
Some(result)
|
||||
}
|
||||
</%self:longhand>
|
||||
|
||||
|
||||
${single_keyword("font-style", "normal italic oblique", inherited=True)}
|
||||
${single_keyword("font-variant", "normal", inherited=True)} // Add small-caps when supported
|
||||
|
||||
<%self:single_component_value name="font-weight" inherited="True">
|
||||
pub enum SpecifiedValue {
|
||||
Bolder,
|
||||
Lighther,
|
||||
% for weight in range(100, 901, 100):
|
||||
SpecifiedWeight${weight},
|
||||
% endfor
|
||||
}
|
||||
/// normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900
|
||||
pub fn from_component_value(input: &ComponentValue) -> Option<SpecifiedValue> {
|
||||
match input {
|
||||
&Ident(ref value) => match value.to_ascii_lower().as_slice() {
|
||||
"bold" => Some(SpecifiedWeight700),
|
||||
"normal" => Some(SpecifiedWeight400),
|
||||
"bolder" => Some(Bolder),
|
||||
"lighter" => Some(Lighther),
|
||||
_ => None,
|
||||
},
|
||||
&Number(ref value) => match value.int_value {
|
||||
Some(100) => Some(SpecifiedWeight100),
|
||||
Some(200) => Some(SpecifiedWeight200),
|
||||
Some(300) => Some(SpecifiedWeight300),
|
||||
Some(400) => Some(SpecifiedWeight400),
|
||||
Some(500) => Some(SpecifiedWeight500),
|
||||
Some(600) => Some(SpecifiedWeight600),
|
||||
Some(700) => Some(SpecifiedWeight700),
|
||||
Some(800) => Some(SpecifiedWeight800),
|
||||
Some(900) => Some(SpecifiedWeight900),
|
||||
_ => None,
|
||||
},
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
pub enum ComputedValue {
|
||||
% for weight in range(100, 901, 100):
|
||||
Weight${weight},
|
||||
% endfor
|
||||
}
|
||||
#[inline] pub fn get_initial_value() -> ComputedValue { Weight400 } // normal
|
||||
pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context)
|
||||
-> ComputedValue {
|
||||
match value {
|
||||
% for weight in range(100, 901, 100):
|
||||
SpecifiedWeight${weight} => Weight${weight},
|
||||
% endfor
|
||||
Bolder => match context.font_weight {
|
||||
Weight100 => Weight400,
|
||||
Weight200 => Weight400,
|
||||
Weight300 => Weight400,
|
||||
Weight400 => Weight700,
|
||||
Weight500 => Weight700,
|
||||
Weight600 => Weight900,
|
||||
Weight700 => Weight900,
|
||||
Weight800 => Weight900,
|
||||
Weight900 => Weight900,
|
||||
},
|
||||
Lighther => match context.font_weight {
|
||||
Weight100 => Weight100,
|
||||
Weight200 => Weight100,
|
||||
Weight300 => Weight100,
|
||||
Weight400 => Weight100,
|
||||
Weight500 => Weight100,
|
||||
Weight600 => Weight400,
|
||||
Weight700 => Weight400,
|
||||
Weight800 => Weight700,
|
||||
Weight900 => Weight700,
|
||||
},
|
||||
}
|
||||
}
|
||||
</%self:single_component_value>
|
||||
|
||||
<%self:single_component_value name="font-size" inherited="True">
|
||||
pub use to_computed_value = super::super::common_types::computed::compute_Length;
|
||||
pub type SpecifiedValue = specified::Length; // Percentages are the same as em.
|
||||
pub type ComputedValue = computed::Length;
|
||||
#[inline] pub fn get_initial_value() -> ComputedValue {
|
||||
computed::Length(16 * 60) // medium
|
||||
}
|
||||
/// <length> | <percentage>
|
||||
/// TODO: support <absolute-size> and <relative-size>
|
||||
pub fn from_component_value(input: &ComponentValue) -> Option<SpecifiedValue> {
|
||||
do specified::LengthOrPercentage::parse_non_negative(input).map_move |value| {
|
||||
match value {
|
||||
specified::LP_Length(value) => value,
|
||||
specified::LP_Percentage(value) => specified::Em(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
</%self:single_component_value>
|
||||
|
||||
// CSS 2.1, Section 16 - Text
|
||||
|
||||
// TODO: initial value should be 'start' (CSS Text Level 3, direction-dependent.)
|
||||
${single_keyword("text-align", "left right center justify", inherited=True)}
|
||||
|
||||
<%self:longhand name="text-decoration">
|
||||
pub use to_computed_value = std::util::id;
|
||||
pub struct SpecifiedValue {
|
||||
underline: bool,
|
||||
overline: bool,
|
||||
line_through: bool,
|
||||
// 'blink' is accepted in the parser but ignored.
|
||||
// Just not blinking the text is a conforming implementation per CSS 2.1.
|
||||
}
|
||||
pub type ComputedValue = SpecifiedValue;
|
||||
#[inline] pub fn get_initial_value() -> ComputedValue {
|
||||
SpecifiedValue { underline: false, overline: false, line_through: false } // none
|
||||
}
|
||||
/// none | [ underline || overline || line-through || blink ]
|
||||
pub fn parse(input: &[ComponentValue]) -> Option<SpecifiedValue> {
|
||||
let mut result = SpecifiedValue {
|
||||
underline: false, overline: false, line_through: false,
|
||||
};
|
||||
let mut blink = false;
|
||||
let mut empty = true;
|
||||
for component_value in input.skip_whitespace() {
|
||||
match get_ident_lower(component_value) {
|
||||
None => return None,
|
||||
Some(keyword) => match keyword.as_slice() {
|
||||
"underline" => if result.underline { return None }
|
||||
else { empty = false; result.underline = true },
|
||||
"overline" => if result.overline { return None }
|
||||
else { empty = false; result.overline = true },
|
||||
"line-through" => if result.line_through { return None }
|
||||
else { empty = false; result.line_through = true },
|
||||
"blink" => if blink { return None }
|
||||
else { empty = false; blink = true },
|
||||
"none" => return if empty { Some(result) } else { None },
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
}
|
||||
if !empty { Some(result) } else { None }
|
||||
}
|
||||
</%self:longhand>
|
||||
|
||||
// CSS 2.1, Section 17 - Tables
|
||||
|
||||
// CSS 2.1, Section 18 - User interface
|
||||
}
|
||||
|
||||
|
||||
pub mod shorthands {
|
||||
pub use super::*;
|
||||
pub use super::longhands::*;
|
||||
|
||||
<%def name="shorthand(name, sub_properties)">
|
||||
<%
|
||||
shorthand = Shorthand(name, sub_properties.split())
|
||||
SHORTHANDS.append(shorthand)
|
||||
%>
|
||||
pub mod ${shorthand.ident} {
|
||||
use super::*;
|
||||
struct Longhands {
|
||||
% for sub_property in shorthand.sub_properties:
|
||||
${sub_property.ident}: Option<${sub_property.ident}::SpecifiedValue>,
|
||||
% endfor
|
||||
}
|
||||
pub fn parse(input: &[ComponentValue]) -> Option<Longhands> {
|
||||
${caller.body()}
|
||||
}
|
||||
}
|
||||
</%def>
|
||||
|
||||
<%def name="four_sides_shorthand(name, sub_property_pattern, parser_function)">
|
||||
<%self:shorthand name="${name}" sub_properties="${
|
||||
' '.join(sub_property_pattern % side
|
||||
for side in ['top', 'right', 'bottom', 'left'])}">
|
||||
let mut iter = input.skip_whitespace().map(${parser_function});
|
||||
// zero or more than four values is invalid.
|
||||
// one value sets them all
|
||||
// two values set (top, bottom) and (left, right)
|
||||
// three values set top, (left, right) and bottom
|
||||
// four values set them in order
|
||||
let top = iter.next().unwrap_or_default(None);
|
||||
let right = iter.next().unwrap_or_default(top);
|
||||
let bottom = iter.next().unwrap_or_default(top);
|
||||
let left = iter.next().unwrap_or_default(right);
|
||||
if top.is_some() && right.is_some() && bottom.is_some() && left.is_some()
|
||||
&& iter.next().is_none() {
|
||||
Some(Longhands {
|
||||
% for side in ["top", "right", "bottom", "left"]:
|
||||
${to_rust_ident(sub_property_pattern % side)}: ${side},
|
||||
% endfor
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
</%self:shorthand>
|
||||
</%def>
|
||||
|
||||
|
||||
// TODO: other background-* properties
|
||||
<%self:shorthand name="background" sub_properties="background-color">
|
||||
do one_component_value(input).chain(specified::CSSColor::parse).map_move |color| {
|
||||
Longhands { background_color: Some(color) }
|
||||
}
|
||||
</%self:shorthand>
|
||||
|
||||
${four_sides_shorthand("border-color", "border-%s-color", "specified::CSSColor::parse")}
|
||||
${four_sides_shorthand("border-style", "border-%s-style",
|
||||
"border_top_style::from_component_value")}
|
||||
${four_sides_shorthand("border-width", "border-%s-width", "parse_border_width")}
|
||||
|
||||
pub fn parse_border(input: &[ComponentValue])
|
||||
-> Option<(Option<specified::CSSColor>,
|
||||
Option<border_top_style::SpecifiedValue>,
|
||||
Option<specified::Length>)> {
|
||||
let mut color = None;
|
||||
let mut style = None;
|
||||
let mut width = None;
|
||||
let mut any = false;
|
||||
for component_value in input.skip_whitespace() {
|
||||
if color.is_none() {
|
||||
match specified::CSSColor::parse(component_value) {
|
||||
Some(c) => { color = Some(c); any = true; loop },
|
||||
None => ()
|
||||
}
|
||||
}
|
||||
if style.is_none() {
|
||||
match border_top_style::from_component_value(component_value) {
|
||||
Some(s) => { style = Some(s); any = true; loop },
|
||||
None => ()
|
||||
}
|
||||
}
|
||||
if width.is_none() {
|
||||
match parse_border_width(component_value) {
|
||||
Some(w) => { width = Some(w); any = true; loop },
|
||||
None => ()
|
||||
}
|
||||
}
|
||||
return None
|
||||
}
|
||||
if any { Some((color, style, width)) } else { None }
|
||||
}
|
||||
|
||||
|
||||
% for side in ["top", "right", "bottom", "left"]:
|
||||
<%self:shorthand name="border-${side}" sub_properties="${' '.join(
|
||||
'border-%s-%s' % (side, prop)
|
||||
for prop in ['color', 'style', 'width']
|
||||
)}">
|
||||
do parse_border(input).map_move |(color, style, width)| {
|
||||
Longhands {
|
||||
% for prop in ["color", "style", "width"]:
|
||||
${"border_%s_%s: %s," % (side, prop, prop)}
|
||||
% endfor
|
||||
}
|
||||
}
|
||||
</%self:shorthand>
|
||||
% endfor
|
||||
|
||||
<%self:shorthand name="border" sub_properties="${' '.join(
|
||||
'border-%s-%s' % (side, prop)
|
||||
for side in ['top', 'right', 'bottom', 'left']
|
||||
for prop in ['color', 'style', 'width']
|
||||
)}">
|
||||
do parse_border(input).map_move |(color, style, width)| {
|
||||
Longhands {
|
||||
% for side in ["top", "right", "bottom", "left"]:
|
||||
% for prop in ["color", "style", "width"]:
|
||||
${"border_%s_%s: %s," % (side, prop, prop)}
|
||||
% endfor
|
||||
% endfor
|
||||
}
|
||||
}
|
||||
</%self:shorthand>
|
||||
|
||||
}
|
||||
|
||||
|
||||
pub struct PropertyDeclarationBlock {
|
||||
important: ~[PropertyDeclaration],
|
||||
normal: ~[PropertyDeclaration],
|
||||
}
|
||||
|
||||
|
||||
pub fn parse_property_declaration_list(input: ~[Node]) -> PropertyDeclarationBlock {
|
||||
let mut important = ~[];
|
||||
let mut normal = ~[];
|
||||
for item in ErrorLoggerIterator(parse_declaration_list(input.move_iter())) {
|
||||
match item {
|
||||
Decl_AtRule(rule) => log_css_error(
|
||||
rule.location, fmt!("Unsupported at-rule in declaration list: @%s", rule.name)),
|
||||
Declaration(Declaration{ location: l, name: n, value: v, important: i}) => {
|
||||
let list = if i { &mut important } else { &mut normal };
|
||||
if !PropertyDeclaration::parse(n, v, list) {
|
||||
log_css_error(l, "Invalid property declaration")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
PropertyDeclarationBlock { important: important, normal: normal }
|
||||
}
|
||||
|
||||
|
||||
pub enum CSSWideKeyword {
|
||||
Initial,
|
||||
Inherit,
|
||||
Unset,
|
||||
}
|
||||
|
||||
impl CSSWideKeyword {
|
||||
pub fn parse(input: &[ComponentValue]) -> Option<CSSWideKeyword> {
|
||||
do one_component_value(input).chain(get_ident_lower).chain |keyword| {
|
||||
match keyword.as_slice() {
|
||||
"initial" => Some(Initial),
|
||||
"inherit" => Some(Inherit),
|
||||
"unset" => Some(Unset),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum DeclaredValue<T> {
|
||||
SpecifiedValue(T),
|
||||
CSSWideKeyword(CSSWideKeyword),
|
||||
}
|
||||
|
||||
pub enum PropertyDeclaration {
|
||||
% for property in LONGHANDS:
|
||||
${property.ident}_declaration(DeclaredValue<longhands::${property.ident}::SpecifiedValue>),
|
||||
% endfor
|
||||
}
|
||||
|
||||
impl PropertyDeclaration {
|
||||
pub fn parse(name: &str, value: &[ComponentValue],
|
||||
result_list: &mut ~[PropertyDeclaration]) -> bool {
|
||||
match name.to_ascii_lower().as_slice() {
|
||||
% for property in LONGHANDS:
|
||||
"${property.name}" => result_list.push(${property.ident}_declaration(
|
||||
match CSSWideKeyword::parse(value) {
|
||||
Some(keyword) => CSSWideKeyword(keyword),
|
||||
None => match longhands::${property.ident}::parse(value) {
|
||||
Some(value) => SpecifiedValue(value),
|
||||
None => return false,
|
||||
}
|
||||
}
|
||||
)),
|
||||
% endfor
|
||||
% for shorthand in SHORTHANDS:
|
||||
"${shorthand.name}" => match CSSWideKeyword::parse(value) {
|
||||
Some(keyword) => {
|
||||
% for sub_property in shorthand.sub_properties:
|
||||
result_list.push(${sub_property.ident}_declaration(
|
||||
CSSWideKeyword(keyword)
|
||||
));
|
||||
% endfor
|
||||
},
|
||||
None => match shorthands::${shorthand.ident}::parse(value) {
|
||||
Some(result) => {
|
||||
% for sub_property in shorthand.sub_properties:
|
||||
result_list.push(${sub_property.ident}_declaration(
|
||||
match result.${sub_property.ident} {
|
||||
Some(value) => SpecifiedValue(value),
|
||||
None => CSSWideKeyword(Initial),
|
||||
}
|
||||
));
|
||||
% endfor
|
||||
},
|
||||
None => return false,
|
||||
}
|
||||
},
|
||||
% endfor
|
||||
_ => return false, // Unknown property
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
482
src/components/script/style/selectors.rs
Normal file
482
src/components/script/style/selectors.rs
Normal file
|
@ -0,0 +1,482 @@
|
|||
/* 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 std::{vec, iterator};
|
||||
use std::ascii::StrAsciiExt;
|
||||
use cssparser::*;
|
||||
use style::namespaces::NamespaceMap;
|
||||
|
||||
|
||||
pub struct Selector {
|
||||
compound_selectors: CompoundSelector,
|
||||
pseudo_element: Option<PseudoElement>,
|
||||
specificity: u32,
|
||||
}
|
||||
|
||||
pub static STYLE_ATTRIBUTE_SPECIFICITY: u32 = 1 << 31;
|
||||
|
||||
|
||||
pub enum PseudoElement {
|
||||
Before,
|
||||
After,
|
||||
FirstLine,
|
||||
FirstLetter,
|
||||
}
|
||||
|
||||
|
||||
pub struct CompoundSelector {
|
||||
simple_selectors: ~[SimpleSelector],
|
||||
next: Option<(~CompoundSelector, Combinator)>, // c.next is left of c
|
||||
}
|
||||
|
||||
pub enum Combinator {
|
||||
Child, // >
|
||||
Descendant, // space
|
||||
NextSibling, // +
|
||||
LaterSibling, // ~
|
||||
}
|
||||
|
||||
pub enum SimpleSelector {
|
||||
IDSelector(~str),
|
||||
ClassSelector(~str),
|
||||
LocalNameSelector{lowercase_name: ~str, cased_name: ~str},
|
||||
NamespaceSelector(~str),
|
||||
|
||||
// Attribute selectors
|
||||
AttrExists(AttrSelector), // [foo]
|
||||
AttrEqual(AttrSelector, ~str), // [foo=bar]
|
||||
AttrIncludes(AttrSelector, ~str), // [foo~=bar]
|
||||
AttrDashMatch(AttrSelector, ~str), // [foo|=bar]
|
||||
AttrPrefixMatch(AttrSelector, ~str), // [foo^=bar]
|
||||
AttrSubstringMatch(AttrSelector, ~str), // [foo*=bar]
|
||||
AttrSuffixMatch(AttrSelector, ~str), // [foo$=bar]
|
||||
|
||||
// Pseudo-classes
|
||||
Empty,
|
||||
Root,
|
||||
Lang(~str),
|
||||
NthChild(i32, i32),
|
||||
Negation(~[SimpleSelector]),
|
||||
// ...
|
||||
}
|
||||
|
||||
pub struct AttrSelector {
|
||||
lowercase_name: ~str,
|
||||
cased_name: ~str,
|
||||
namespace: Option<~str>,
|
||||
}
|
||||
|
||||
|
||||
type Iter = iterator::Peekable<ComponentValue, vec::MoveIterator<ComponentValue>>;
|
||||
|
||||
|
||||
// None means invalid selector
|
||||
pub fn parse_selector_list(input: ~[ComponentValue], namespaces: &NamespaceMap)
|
||||
-> Option<~[Selector]> {
|
||||
let iter = &mut input.move_iter().peekable();
|
||||
let first = match parse_selector(iter, namespaces) {
|
||||
None => return None,
|
||||
Some(result) => result
|
||||
};
|
||||
let mut results = ~[first];
|
||||
|
||||
loop {
|
||||
skip_whitespace(iter);
|
||||
match iter.peek() {
|
||||
None => break, // EOF
|
||||
Some(&Comma) => (),
|
||||
_ => return None,
|
||||
}
|
||||
match parse_selector(iter, namespaces) {
|
||||
Some(selector) => results.push(selector),
|
||||
None => return None,
|
||||
}
|
||||
}
|
||||
Some(results)
|
||||
}
|
||||
|
||||
|
||||
// None means invalid selector
|
||||
fn parse_selector(iter: &mut Iter, namespaces: &NamespaceMap)
|
||||
-> Option<Selector> {
|
||||
let (first, pseudo_element) = match parse_simple_selectors(iter, namespaces) {
|
||||
None => return None,
|
||||
Some(result) => result
|
||||
};
|
||||
let mut compound = CompoundSelector{ simple_selectors: first, next: None };
|
||||
let mut pseudo_element = pseudo_element;
|
||||
|
||||
while pseudo_element.is_none() {
|
||||
let any_whitespace = skip_whitespace(iter);
|
||||
let combinator = match iter.peek() {
|
||||
None => break, // EOF
|
||||
Some(&Delim('>')) => { iter.next(); Child },
|
||||
Some(&Delim('+')) => { iter.next(); NextSibling },
|
||||
Some(&Delim('~')) => { iter.next(); LaterSibling },
|
||||
Some(_) => {
|
||||
if any_whitespace { Descendant }
|
||||
else { return None }
|
||||
}
|
||||
};
|
||||
match parse_simple_selectors(iter, namespaces) {
|
||||
None => return None,
|
||||
Some((simple_selectors, pseudo)) => {
|
||||
compound = CompoundSelector {
|
||||
simple_selectors: simple_selectors,
|
||||
next: Some((~compound, combinator))
|
||||
};
|
||||
pseudo_element = pseudo;
|
||||
}
|
||||
}
|
||||
}
|
||||
let selector = Selector{
|
||||
specificity: compute_specificity(&compound, &pseudo_element),
|
||||
compound_selectors: compound,
|
||||
pseudo_element: pseudo_element,
|
||||
};
|
||||
Some(selector)
|
||||
}
|
||||
|
||||
|
||||
fn compute_specificity(mut selector: &CompoundSelector,
|
||||
pseudo_element: &Option<PseudoElement>) -> u32 {
|
||||
struct Specificity {
|
||||
id_selectors: u32,
|
||||
class_like_selectors: u32,
|
||||
element_selectors: u32,
|
||||
}
|
||||
let mut specificity = Specificity {
|
||||
id_selectors: 0,
|
||||
class_like_selectors: 0,
|
||||
element_selectors: 0,
|
||||
};
|
||||
if pseudo_element.is_some() { specificity.element_selectors += 1 }
|
||||
|
||||
simple_selectors_specificity(selector.simple_selectors, &mut specificity);
|
||||
loop {
|
||||
match selector.next {
|
||||
None => break,
|
||||
Some((ref next_selector, _)) => {
|
||||
selector = &**next_selector;
|
||||
simple_selectors_specificity(selector.simple_selectors, &mut specificity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn simple_selectors_specificity(simple_selectors: &[SimpleSelector],
|
||||
specificity: &mut Specificity) {
|
||||
for simple_selector in simple_selectors.iter() {
|
||||
match simple_selector {
|
||||
&LocalNameSelector{_} => specificity.element_selectors += 1,
|
||||
&IDSelector(*) => specificity.id_selectors += 1,
|
||||
&ClassSelector(*)
|
||||
| &AttrExists(*) | &AttrEqual(*) | &AttrIncludes(*) | &AttrDashMatch(*)
|
||||
| &AttrPrefixMatch(*) | &AttrSubstringMatch(*) | &AttrSuffixMatch(*)
|
||||
| &Empty | &Root | &Lang(*) | &NthChild(*)
|
||||
=> specificity.class_like_selectors += 1,
|
||||
&NamespaceSelector(*) => (),
|
||||
&Negation(ref negated)
|
||||
=> simple_selectors_specificity(negated.as_slice(), specificity),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static MAX_10BIT: u32 = (1u32 << 10) - 1;
|
||||
specificity.id_selectors.min(&MAX_10BIT) << 20
|
||||
| specificity.class_like_selectors.min(&MAX_10BIT) << 10
|
||||
| specificity.id_selectors.min(&MAX_10BIT)
|
||||
}
|
||||
|
||||
|
||||
// None means invalid selector
|
||||
fn parse_simple_selectors(iter: &mut Iter, namespaces: &NamespaceMap)
|
||||
-> Option<(~[SimpleSelector], Option<PseudoElement>)> {
|
||||
let mut empty = true;
|
||||
let mut simple_selectors = match parse_type_selector(iter, namespaces) {
|
||||
None => return None, // invalid selector
|
||||
Some(None) => ~[],
|
||||
Some(Some(s)) => { empty = false; s }
|
||||
};
|
||||
|
||||
let mut pseudo_element = None;
|
||||
loop {
|
||||
match parse_one_simple_selector(iter, namespaces, /* inside_negation = */ false) {
|
||||
None => return None, // invalid selector
|
||||
Some(None) => break,
|
||||
Some(Some(Left(s))) => simple_selectors.push(s),
|
||||
Some(Some(Right(p))) => { pseudo_element = Some(p); break },
|
||||
}
|
||||
}
|
||||
if empty { None } // An empty selector is invalid
|
||||
else { Some((simple_selectors, pseudo_element)) }
|
||||
}
|
||||
|
||||
|
||||
// None means invalid selector
|
||||
// Some(None) means no type selector
|
||||
// Some(Some([...])) is a type selector. Might be empty for *|*
|
||||
fn parse_type_selector(iter: &mut Iter, namespaces: &NamespaceMap)
|
||||
-> Option<Option<~[SimpleSelector]>> {
|
||||
skip_whitespace(iter);
|
||||
match parse_qualified_name(iter, /* allow_universal = */ true, namespaces) {
|
||||
None => None, // invalid selector
|
||||
Some(None) => Some(None),
|
||||
Some(Some((namespace, local_name))) => {
|
||||
let mut simple_selectors = ~[];
|
||||
match namespace {
|
||||
Some(url) => simple_selectors.push(NamespaceSelector(url)),
|
||||
None => (),
|
||||
}
|
||||
match local_name {
|
||||
Some(name) => simple_selectors.push(LocalNameSelector{
|
||||
lowercase_name: name.to_ascii_lower(),
|
||||
cased_name: name,
|
||||
}),
|
||||
None => (),
|
||||
}
|
||||
Some(Some(simple_selectors))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Parse a simple selector other than a type selector
|
||||
fn parse_one_simple_selector(iter: &mut Iter, namespaces: &NamespaceMap, inside_negation: bool)
|
||||
-> Option<Option<Either<SimpleSelector, PseudoElement>>> {
|
||||
match iter.peek() {
|
||||
Some(&IDHash(_)) => match iter.next() {
|
||||
Some(IDHash(id)) => Some(Some(Left(IDSelector(id)))),
|
||||
_ => fail!("Implementation error, this should not happen."),
|
||||
},
|
||||
Some(&Delim('.')) => {
|
||||
iter.next();
|
||||
match iter.next() {
|
||||
Some(Ident(class)) => Some(Some(Left(ClassSelector(class)))),
|
||||
_ => None, // invalid selector
|
||||
}
|
||||
}
|
||||
Some(&SquareBracketBlock(_)) => match iter.next() {
|
||||
Some(SquareBracketBlock(content))
|
||||
=> match parse_attribute_selector(content, namespaces) {
|
||||
None => None,
|
||||
Some(simple_selector) => Some(Some(Left(simple_selector))),
|
||||
},
|
||||
_ => fail!("Implementation error, this should not happen."),
|
||||
},
|
||||
Some(&Delim(':')) => {
|
||||
iter.next();
|
||||
match iter.next() {
|
||||
Some(Ident(name)) => match parse_simple_pseudo_class(name) {
|
||||
None => None,
|
||||
Some(result) => Some(Some(result)),
|
||||
},
|
||||
Some(Function(name, arguments)) => match parse_functional_pseudo_class(
|
||||
name, arguments, namespaces, inside_negation) {
|
||||
None => None,
|
||||
Some(simple_selector) => Some(Some(Left(simple_selector))),
|
||||
},
|
||||
Some(Delim(':')) => {
|
||||
match iter.next() {
|
||||
Some(Ident(name)) => match parse_pseudo_element(name) {
|
||||
Some(pseudo_element) => Some(Some(Right(pseudo_element))),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
_ => Some(None),
|
||||
}
|
||||
}
|
||||
|
||||
// None means invalid selector
|
||||
// Some(None) means not a qualified name
|
||||
// Some(Some((None, None)) means *|*
|
||||
// Some(Some((Some(url), None)) means prefix|*
|
||||
// Some(Some((None, Some(name)) means *|name
|
||||
// Some(Some((Some(url), Some(name))) means prefix|name
|
||||
// ... or equivalent
|
||||
fn parse_qualified_name(iter: &mut Iter, allow_universal: bool, namespaces: &NamespaceMap)
|
||||
-> Option<Option<(Option<~str>, Option<~str>)>> {
|
||||
#[inline]
|
||||
fn default_namespace(namespaces: &NamespaceMap, local_name: Option<~str>)
|
||||
-> Option<Option<(Option<~str>, Option<~str>)>> {
|
||||
match namespaces.default {
|
||||
None => Some(Some((None, local_name))),
|
||||
Some(ref url) => Some(Some((Some(url.to_owned()), local_name))),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn explicit_namespace(iter: &mut Iter, allow_universal: bool, namespace_url: Option<~str>)
|
||||
-> Option<Option<(Option<~str>, Option<~str>)>> {
|
||||
assert!(iter.next() == Some(Delim('|')));
|
||||
match iter.peek() {
|
||||
Some(&Delim('*')) if allow_universal => {
|
||||
iter.next();
|
||||
Some(Some((namespace_url, None)))
|
||||
},
|
||||
Some(&Ident(_)) => {
|
||||
let local_name = get_next_ident(iter);
|
||||
Some(Some((namespace_url, Some(local_name))))
|
||||
},
|
||||
_ => None, // invalid selector
|
||||
}
|
||||
}
|
||||
|
||||
match iter.peek() {
|
||||
Some(&Ident(_)) => {
|
||||
let value = get_next_ident(iter);
|
||||
match iter.peek() {
|
||||
Some(&Delim('|')) => default_namespace(namespaces, Some(value)),
|
||||
_ => {
|
||||
let namespace_url = match namespaces.prefix_map.find(&value) {
|
||||
None => return None, // Undeclared namespace prefix: invalid selector
|
||||
Some(ref url) => url.to_owned(),
|
||||
};
|
||||
explicit_namespace(iter, allow_universal, Some(namespace_url))
|
||||
},
|
||||
}
|
||||
},
|
||||
Some(&Delim('*')) => {
|
||||
iter.next(); // Consume '*'
|
||||
match iter.peek() {
|
||||
Some(&Delim('|')) => {
|
||||
if allow_universal { default_namespace(namespaces, None) }
|
||||
else { None }
|
||||
},
|
||||
_ => explicit_namespace(iter, allow_universal, None),
|
||||
}
|
||||
},
|
||||
Some(&Delim('|')) => explicit_namespace(iter, allow_universal, Some(~"")),
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn parse_attribute_selector(content: ~[ComponentValue], namespaces: &NamespaceMap)
|
||||
-> Option<SimpleSelector> {
|
||||
let iter = &mut content.move_iter().peekable();
|
||||
let attr = match parse_qualified_name(iter, /* allow_universal = */ false, namespaces) {
|
||||
None => return None, // invalid selector
|
||||
Some(None) => return None,
|
||||
Some(Some((_, None))) => fail!("Implementation error, this should not happen."),
|
||||
Some(Some((namespace, Some(local_name)))) => AttrSelector {
|
||||
namespace: namespace,
|
||||
lowercase_name: local_name.to_ascii_lower(),
|
||||
cased_name: local_name,
|
||||
},
|
||||
};
|
||||
skip_whitespace(iter);
|
||||
macro_rules! get_value( () => {{
|
||||
skip_whitespace(iter);
|
||||
match iter.next() {
|
||||
Some(Ident(value)) | Some(String(value)) => value,
|
||||
_ => return None,
|
||||
}
|
||||
}};)
|
||||
let result = match iter.next() {
|
||||
None => AttrExists(attr), // [foo]
|
||||
Some(Delim('=')) => AttrEqual(attr, get_value!()), // [foo=bar]
|
||||
Some(IncludeMatch) => AttrIncludes(attr, get_value!()), // [foo~=bar]
|
||||
Some(DashMatch) => AttrDashMatch(attr, get_value!()), // [foo|=bar]
|
||||
Some(PrefixMatch) => AttrPrefixMatch(attr, get_value!()), // [foo^=bar]
|
||||
Some(SubstringMatch) => AttrSubstringMatch(attr, get_value!()), // [foo*=bar]
|
||||
Some(SuffixMatch) => AttrSuffixMatch(attr, get_value!()), // [foo$=bar]
|
||||
_ => return None
|
||||
};
|
||||
skip_whitespace(iter);
|
||||
if iter.next().is_none() { Some(result) } else { None }
|
||||
}
|
||||
|
||||
|
||||
fn parse_simple_pseudo_class(name: ~str) -> Option<Either<SimpleSelector, PseudoElement>> {
|
||||
match name.to_ascii_lower().as_slice() {
|
||||
"root" => Some(Left(Root)),
|
||||
"empty" => Some(Left(Empty)),
|
||||
|
||||
// Supported CSS 2.1 pseudo-elements only.
|
||||
"before" => Some(Right(Before)),
|
||||
"after" => Some(Right(After)),
|
||||
"first-line" => Some(Right(FirstLine)),
|
||||
"first-letter" => Some(Right(FirstLetter)),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn parse_functional_pseudo_class(name: ~str, arguments: ~[ComponentValue],
|
||||
namespaces: &NamespaceMap, inside_negation: bool)
|
||||
-> Option<SimpleSelector> {
|
||||
match name.to_ascii_lower().as_slice() {
|
||||
"lang" => parse_lang(arguments),
|
||||
"nth-child" => parse_nth(arguments).map(|&(a, b)| NthChild(a, b)),
|
||||
"not" => if inside_negation { None } else { parse_negation(arguments, namespaces) },
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn parse_pseudo_element(name: ~str) -> Option<PseudoElement> {
|
||||
match name.to_ascii_lower().as_slice() {
|
||||
// All supported pseudo-elements
|
||||
"before" => Some(Before),
|
||||
"after" => Some(After),
|
||||
"first-line" => Some(FirstLine),
|
||||
"first-letter" => Some(FirstLetter),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn parse_lang(arguments: ~[ComponentValue]) -> Option<SimpleSelector> {
|
||||
let mut iter = arguments.move_skip_whitespace();
|
||||
match iter.next() {
|
||||
Some(Ident(value)) => {
|
||||
if "" == value || iter.next().is_some() { None }
|
||||
else { Some(Lang(value)) }
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Level 3: Parse ONE simple_selector
|
||||
fn parse_negation(arguments: ~[ComponentValue], namespaces: &NamespaceMap)
|
||||
-> Option<SimpleSelector> {
|
||||
let iter = &mut arguments.move_iter().peekable();
|
||||
Some(Negation(match parse_type_selector(iter, namespaces) {
|
||||
None => return None, // invalid selector
|
||||
Some(Some(s)) => s,
|
||||
Some(None) => {
|
||||
match parse_one_simple_selector(iter, namespaces, /* inside_negation = */ true) {
|
||||
Some(Some(Left(s))) => ~[s],
|
||||
_ => return None
|
||||
}
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
/// Assuming the next token is an ident, consume it and return its value
|
||||
#[inline]
|
||||
fn get_next_ident(iter: &mut Iter) -> ~str {
|
||||
match iter.next() {
|
||||
Some(Ident(value)) => value,
|
||||
_ => fail!("Implementation error, this should not happen."),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[inline]
|
||||
fn skip_whitespace(iter: &mut Iter) -> bool {
|
||||
let mut any_whitespace = false;
|
||||
loop {
|
||||
if iter.peek() != Some(&WhiteSpace) { return any_whitespace }
|
||||
any_whitespace = true;
|
||||
iter.next();
|
||||
}
|
||||
}
|
152
src/components/script/style/stylesheets.rs
Normal file
152
src/components/script/style/stylesheets.rs
Normal file
|
@ -0,0 +1,152 @@
|
|||
/* 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 std::iterator::Iterator;
|
||||
use std::ascii::StrAsciiExt;
|
||||
use cssparser::*;
|
||||
use style::selectors;
|
||||
use style::properties;
|
||||
use style::errors::{ErrorLoggerIterator, log_css_error};
|
||||
use style::namespaces::{NamespaceMap, parse_namespace_rule};
|
||||
use style::media_queries::{MediaRule, parse_media_rule};
|
||||
use style::media_queries;
|
||||
|
||||
|
||||
pub struct Stylesheet {
|
||||
rules: ~[CSSRule],
|
||||
namespaces: NamespaceMap,
|
||||
}
|
||||
|
||||
|
||||
pub enum CSSRule {
|
||||
CSSStyleRule(StyleRule),
|
||||
CSSMediaRule(MediaRule),
|
||||
}
|
||||
|
||||
|
||||
pub struct StyleRule {
|
||||
selectors: ~[selectors::Selector],
|
||||
declarations: properties::PropertyDeclarationBlock,
|
||||
}
|
||||
|
||||
|
||||
fn parse_stylesheet(css: &str) -> Stylesheet {
|
||||
static STATE_CHARSET: uint = 1;
|
||||
static STATE_IMPORTS: uint = 2;
|
||||
static STATE_NAMESPACES: uint = 3;
|
||||
static STATE_BODY: uint = 4;
|
||||
let mut state: uint = STATE_CHARSET;
|
||||
|
||||
let mut rules = ~[];
|
||||
let mut namespaces = NamespaceMap::new();
|
||||
|
||||
for rule in ErrorLoggerIterator(parse_stylesheet_rules(tokenize(css))) {
|
||||
let next_state; // Unitialized to force each branch to set it.
|
||||
match rule {
|
||||
QualifiedRule(rule) => {
|
||||
next_state = STATE_BODY;
|
||||
parse_style_rule(rule, &mut rules, &namespaces)
|
||||
},
|
||||
AtRule(rule) => {
|
||||
let lower_name = rule.name.to_ascii_lower();
|
||||
match lower_name.as_slice() {
|
||||
"charset" => {
|
||||
if state > STATE_CHARSET {
|
||||
log_css_error(rule.location, "@charset must be the first rule")
|
||||
}
|
||||
// Valid @charset rules are just ignored
|
||||
next_state = STATE_IMPORTS;
|
||||
},
|
||||
"import" => {
|
||||
if state > STATE_IMPORTS {
|
||||
next_state = state;
|
||||
log_css_error(rule.location,
|
||||
"@import must be before any rule but @charset")
|
||||
} else {
|
||||
next_state = STATE_IMPORTS;
|
||||
log_css_error(rule.location, "@import is not supported yet") // TODO
|
||||
}
|
||||
},
|
||||
"namespace" => {
|
||||
if state > STATE_NAMESPACES {
|
||||
next_state = state;
|
||||
log_css_error(
|
||||
rule.location,
|
||||
"@namespace must be before any rule but @charset and @import"
|
||||
)
|
||||
} else {
|
||||
next_state = STATE_NAMESPACES;
|
||||
parse_namespace_rule(rule, &mut namespaces)
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
next_state = STATE_BODY;
|
||||
parse_nested_at_rule(lower_name, rule, &mut rules, &namespaces)
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
state = next_state;
|
||||
}
|
||||
Stylesheet{ rules: rules, namespaces: namespaces }
|
||||
}
|
||||
|
||||
|
||||
pub fn parse_style_rule(rule: QualifiedRule, parent_rules: &mut ~[CSSRule],
|
||||
namespaces: &NamespaceMap) {
|
||||
let QualifiedRule{location: location, prelude: prelude, block: block} = rule;
|
||||
match selectors::parse_selector_list(prelude, namespaces) {
|
||||
Some(selectors) => parent_rules.push(CSSStyleRule(StyleRule{
|
||||
selectors: selectors,
|
||||
declarations: properties::parse_property_declaration_list(block)
|
||||
})),
|
||||
None => log_css_error(location, "Unsupported CSS selector."),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// lower_name is passed explicitly to avoid computing it twice.
|
||||
pub fn parse_nested_at_rule(lower_name: &str, rule: AtRule,
|
||||
parent_rules: &mut ~[CSSRule], namespaces: &NamespaceMap) {
|
||||
match lower_name {
|
||||
"media" => parse_media_rule(rule, parent_rules, namespaces),
|
||||
_ => log_css_error(rule.location, fmt!("Unsupported at-rule: @%s", lower_name))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Stylesheet {
|
||||
fn iter_style_rules<'a>(&'a self, device: &'a media_queries::Device) -> StyleRuleIterator<'a> {
|
||||
StyleRuleIterator { device: device, stack: ~[(self.rules.as_slice(), 0)] }
|
||||
}
|
||||
}
|
||||
|
||||
struct StyleRuleIterator<'self> {
|
||||
device: &'self media_queries::Device,
|
||||
// FIXME: I couldn’t get this to borrow-check with a stack of VecIterator
|
||||
stack: ~[(&'self [CSSRule], uint)],
|
||||
}
|
||||
|
||||
impl<'self> Iterator<&'self StyleRule> for StyleRuleIterator<'self> {
|
||||
fn next(&mut self) -> Option<&'self StyleRule> {
|
||||
loop {
|
||||
match self.stack.pop_opt() {
|
||||
None => return None,
|
||||
Some((rule_list, i)) => {
|
||||
if i + 1 < rule_list.len() {
|
||||
self.stack.push((rule_list, i + 1))
|
||||
}
|
||||
match rule_list[i] {
|
||||
CSSStyleRule(ref rule) => return Some(rule),
|
||||
CSSMediaRule(ref rule) => {
|
||||
if rule.media_queries.evaluate(self.device) {
|
||||
self.stack.push((rule.rules.as_slice(), 0))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,13 +9,19 @@
|
|||
// and use its default methods.
|
||||
macro_rules! get(
|
||||
($node:expr, $fun:ident) => (
|
||||
TreeNodeRef::$fun::<Node,Self>($node)
|
||||
{
|
||||
let val: Option<Self> = TreeNodeRef::<Node>::$fun($node);
|
||||
val
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
macro_rules! set(
|
||||
($node:expr, $fun:ident, $val:expr) => (
|
||||
TreeNodeRef::$fun::<Node,Self>($node, $val)
|
||||
{
|
||||
let val: Option<Self> = $val;
|
||||
TreeNodeRef::<Node>::$fun($node, val)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -31,7 +37,7 @@ impl<Node, Ref: TreeNodeRef<Node>> Iterator<Ref> for ChildIterator<Ref> {
|
|||
|
||||
// FIXME: Do we need two clones here?
|
||||
let x = self.current.get_ref().clone();
|
||||
self.current = x.with_base(|n| TreeNodeRef::next_sibling::<Node, Ref>(n));
|
||||
self.current = x.with_base(|n| TreeNodeRef::<Node>::next_sibling(n));
|
||||
Some(x.clone())
|
||||
}
|
||||
}
|
||||
|
@ -246,51 +252,51 @@ fn gather<Node, Ref: TreeNodeRef<Node>>(cur: &Ref, refs: &mut ~[Ref],
|
|||
pub trait TreeNode<Ref: TreeNodeRef<Self>> {
|
||||
/// Returns the parent of this node.
|
||||
fn parent_node(&self) -> Option<Ref> {
|
||||
TreeNodeRef::parent_node::<Self,Ref>(self)
|
||||
TreeNodeRef::<Self>::parent_node(self)
|
||||
}
|
||||
|
||||
/// Returns the first child of this node.
|
||||
fn first_child(&self) -> Option<Ref> {
|
||||
TreeNodeRef::first_child::<Self,Ref>(self)
|
||||
TreeNodeRef::<Self>::first_child(self)
|
||||
}
|
||||
|
||||
/// Returns the last child of this node.
|
||||
fn last_child(&self) -> Option<Ref> {
|
||||
TreeNodeRef::last_child::<Self,Ref>(self)
|
||||
TreeNodeRef::<Self>::last_child(self)
|
||||
}
|
||||
|
||||
/// Returns the previous sibling of this node.
|
||||
fn prev_sibling(&self) -> Option<Ref> {
|
||||
TreeNodeRef::prev_sibling::<Self,Ref>(self)
|
||||
TreeNodeRef::<Self>::prev_sibling(self)
|
||||
}
|
||||
|
||||
/// Returns the next sibling of this node.
|
||||
fn next_sibling(&self) -> Option<Ref> {
|
||||
TreeNodeRef::next_sibling::<Self,Ref>(self)
|
||||
TreeNodeRef::<Self>::next_sibling(self)
|
||||
}
|
||||
|
||||
/// Sets the parent of this node.
|
||||
fn set_parent_node(&mut self, new_parent: Option<Ref>) {
|
||||
TreeNodeRef::set_parent_node::<Self,Ref>(self, new_parent)
|
||||
TreeNodeRef::<Self>::set_parent_node(self, new_parent)
|
||||
}
|
||||
|
||||
/// Sets the first child of this node.
|
||||
fn set_first_child(&mut self, new_first_child: Option<Ref>) {
|
||||
TreeNodeRef::set_first_child::<Self,Ref>(self, new_first_child)
|
||||
TreeNodeRef::<Self>::set_first_child(self, new_first_child)
|
||||
}
|
||||
|
||||
/// Sets the last child of this node.
|
||||
fn set_last_child(&mut self, new_last_child: Option<Ref>) {
|
||||
TreeNodeRef::set_last_child::<Self,Ref>(self, new_last_child)
|
||||
TreeNodeRef::<Self>::set_last_child(self, new_last_child)
|
||||
}
|
||||
|
||||
/// Sets the previous sibling of this node.
|
||||
fn set_prev_sibling(&mut self, new_prev_sibling: Option<Ref>) {
|
||||
TreeNodeRef::set_prev_sibling::<Self,Ref>(self, new_prev_sibling)
|
||||
TreeNodeRef::<Self>::set_prev_sibling(self, new_prev_sibling)
|
||||
}
|
||||
|
||||
/// Sets the next sibling of this node.
|
||||
fn set_next_sibling(&mut self, new_next_sibling: Option<Ref>) {
|
||||
TreeNodeRef::set_next_sibling::<Self,Ref>(self, new_next_sibling)
|
||||
TreeNodeRef::<Self>::set_next_sibling(self, new_next_sibling)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 9755a007a0294294740d728ba14de30b45b769e5
|
||||
Subproject commit 95f2f30981e12128e1dab0956f11c70386d85bfc
|
|
@ -1 +1 @@
|
|||
Subproject commit 4c4662a5d6077b547466d33c9a00cb33df9f6f26
|
||||
Subproject commit adb526a6ca8deee37152ef3ba1197894286e2204
|
|
@ -1 +1 @@
|
|||
Subproject commit 36b060d0aef4b3d3f644ef221a0aaca4cd31bcdb
|
||||
Subproject commit 129e99ff088fad6ea8f3187680c15a0a3e2cc403
|
|
@ -1 +1 @@
|
|||
Subproject commit e775ce635a063bf1d5d6b5b0c5339cbb818116ab
|
||||
Subproject commit d685aad6f6ab3559c8de5ca76520b8f1e0e21b64
|
|
@ -1 +1 @@
|
|||
Subproject commit 8700565717e8c94f6615761f074dafe8e6283d9f
|
||||
Subproject commit 38d1caae638b28bbfbbbc988b0b841e973345243
|
|
@ -1 +1 @@
|
|||
Subproject commit 0793c920f33c5928323358f7aba7da3253019c60
|
||||
Subproject commit f69e26f4d97316063b6c20f0c71d7568d8d42a76
|
|
@ -1 +1 @@
|
|||
Subproject commit d951d00382c7ad95d64b87f876188df7f0254f00
|
||||
Subproject commit b7121da7fcb5eba84b563118db49a43279c88ee9
|
Loading…
Add table
Add a link
Reference in a new issue