Bug 1361985 - Implement grid-template-areas
MozReview-Commit-ID: LiPSRR3os1C
--- a/servo/components/style/build_gecko.rs
+++ b/servo/components/style/build_gecko.rs
@@ -618,16 +618,17 @@ mod bindings {
.hide_type("nsACString_internal")
.hide_type("nsAString_internal")
.raw_line("pub use nsstring::{nsACString, nsAString, nsString};")
.raw_line("type nsACString_internal = nsACString;")
.raw_line("type nsAString_internal = nsAString;")
.whitelisted_function("Servo_.*")
.whitelisted_function("Gecko_.*");
let structs_types = [
+ "mozilla::css::GridTemplateAreasValue",
"mozilla::css::URLValue",
"mozilla::Side",
"RawGeckoAnimationPropertySegment",
"RawGeckoComputedTiming",
"RawGeckoDocument",
"RawGeckoElement",
"RawGeckoKeyframeList",
"RawGeckoComputedKeyframeValuesList",
--- a/servo/components/style/gecko_bindings/sugar/refptr.rs
+++ b/servo/components/style/gecko_bindings/sugar/refptr.rs
@@ -272,8 +272,11 @@ impl_threadsafe_refcount!(::gecko_bindin
Gecko_AddRefQuoteValuesArbitraryThread,
Gecko_ReleaseQuoteValuesArbitraryThread);
impl_threadsafe_refcount!(::gecko_bindings::structs::nsCSSValueSharedList,
Gecko_AddRefCSSValueSharedListArbitraryThread,
Gecko_ReleaseCSSValueSharedListArbitraryThread);
impl_threadsafe_refcount!(::gecko_bindings::structs::mozilla::css::URLValue,
Gecko_AddRefCSSURLValueArbitraryThread,
Gecko_ReleaseCSSURLValueArbitraryThread);
+impl_threadsafe_refcount!(::gecko_bindings::structs::mozilla::css::GridTemplateAreasValue,
+ Gecko_AddRefGridTemplateAreasValueArbitraryThread,
+ Gecko_ReleaseGridTemplateAreasValueArbitraryThread);
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -1047,17 +1047,18 @@ fn static_assert() {
need_clone=True) %>
% endfor
</%self:impl_trait>
<% skip_position_longhands = " ".join(x.ident for x in SIDES + GRID_LINES) %>
<%self:impl_trait style_struct_name="Position"
skip_longhands="${skip_position_longhands} z-index box-sizing order align-content
justify-content align-self justify-self align-items
- justify-items grid-auto-rows grid-auto-columns grid-auto-flow">
+ justify-items grid-auto-rows grid-auto-columns grid-auto-flow
+ grid-template-areas">
% for side in SIDES:
<% impl_split_style_coord("%s" % side.ident,
"mOffset",
side.index,
need_clone=True) %>
% endfor
pub fn set_z_index(&mut self, v: longhands::z_index::computed_value::T) {
@@ -1223,16 +1224,52 @@ fn static_assert() {
self.gecko.mGridAutoFlow |= value as u8;
if v.dense {
self.gecko.mGridAutoFlow |= NS_STYLE_GRID_AUTO_FLOW_DENSE as u8;
}
}
${impl_simple_copy('grid_auto_flow', 'mGridAutoFlow')}
+
+ pub fn set_grid_template_areas(&mut self, v: longhands::grid_template_areas::computed_value::T) {
+ use gecko_bindings::bindings::Gecko_NewGridTemplateAreasValue;
+ use gecko_bindings::sugar::refptr::UniqueRefPtr;
+
+ let v = match v {
+ Either::First(areas) => areas,
+ Either::Second(_) => {
+ unsafe { self.gecko.mGridTemplateAreas.clear() }
+ return;
+ },
+ };
+
+ let mut refptr = unsafe {
+ UniqueRefPtr::from_addrefed(
+ Gecko_NewGridTemplateAreasValue(v.areas.len() as u32, v.strings.len() as u32, v.width))
+ };
+
+ for (servo, gecko) in v.areas.into_iter().zip(refptr.mNamedAreas.iter_mut()) {
+ gecko.mName.assign_utf8(&*servo.name);
+ gecko.mColumnStart = servo.columns.start;
+ gecko.mColumnEnd = servo.columns.end;
+ gecko.mRowStart = servo.rows.start;
+ gecko.mRowEnd = servo.rows.end;
+ }
+
+ for (servo, gecko) in v.strings.into_iter().zip(refptr.mTemplates.iter_mut()) {
+ gecko.assign_utf8(&*servo);
+ }
+
+ unsafe { self.gecko.mGridTemplateAreas.set_move(refptr.get()) }
+ }
+
+ pub fn copy_grid_template_areas_from(&mut self, other: &Self) {
+ unsafe { self.gecko.mGridTemplateAreas.set(&other.gecko.mGridTemplateAreas) }
+ }
</%self:impl_trait>
<% skip_outline_longhands = " ".join("outline-style outline-width".split() +
["-moz-outline-radius-{0}".format(x.ident.replace("_", ""))
for x in CORNERS]) %>
<%self:impl_trait style_struct_name="Outline"
skip_longhands="${skip_outline_longhands}"
skip_additionals="*">
--- a/servo/components/style/properties/longhand/position.mako.rs
+++ b/servo/components/style/properties/longhand/position.mako.rs
@@ -401,8 +401,177 @@
autoflow: value.unwrap_or(AutoFlow::Row),
dense: dense,
})
} else {
Err(())
}
}
</%helpers:longhand>
+
+<%helpers:longhand name="grid-template-areas"
+ spec="https://drafts.csswg.org/css-grid/#propdef-grid-template-areas"
+ products="gecko"
+ animation_value_type="none"
+ disable_when_testing="True">
+ use cssparser::serialize_string;
+ use std::collections::HashMap;
+ use std::fmt;
+ use std::ops::Range;
+ use str::HTML_SPACE_CHARACTERS;
+ use style_traits::ToCss;
+ use style_traits::values::Css;
+ use values::HasViewportPercentage;
+ use values::computed::ComputedValueAsSpecified;
+
+ pub mod computed_value {
+ pub use super::SpecifiedValue as T;
+ }
+
+ pub type SpecifiedValue = Either<TemplateAreas, None_>;
+
+ #[inline]
+ pub fn get_initial_value() -> computed_value::T {
+ Either::Second(None_)
+ }
+
+ pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+ SpecifiedValue::parse(context, input)
+ }
+
+ #[derive(Clone, PartialEq)]
+ pub struct TemplateAreas {
+ pub areas: Box<[NamedArea]>,
+ pub strings: Box<[Box<str>]>,
+ pub width: u32,
+ }
+
+ #[derive(Clone, PartialEq)]
+ pub struct NamedArea {
+ pub name: Box<str>,
+ pub rows: Range<u32>,
+ pub columns: Range<u32>,
+ }
+
+ no_viewport_percentage!(TemplateAreas);
+ impl ComputedValueAsSpecified for TemplateAreas {}
+
+ impl Parse for TemplateAreas {
+ fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+ let mut strings = vec![];
+ while let Ok(string) = input.try(Parser::expect_string) {
+ strings.push(string.into_owned().into_boxed_str());
+ }
+ if strings.is_empty() {
+ return Err(());
+ }
+ let mut areas: Vec<NamedArea> = vec![];
+ let mut width = 0;
+ {
+ let mut row = 0u32;
+ let mut area_indices = HashMap::<(&str), usize>::new();
+ for string in &strings {
+ let mut current_area_index: Option<usize> = None;
+ row += 1;
+ let mut column = 0u32;
+ for token in Tokenizer(string) {
+ column += 1;
+ let token = if let Some(token) = token? {
+ token
+ } else {
+ if let Some(index) = current_area_index.take() {
+ if areas[index].columns.end != column {
+ return Err(());
+ }
+ }
+ continue;
+ };
+ if let Some(index) = current_area_index {
+ if &*areas[index].name == token {
+ if areas[index].rows.start == row {
+ areas[index].columns.end += 1;
+ }
+ continue;
+ }
+ if areas[index].columns.end != column {
+ return Err(());
+ }
+ }
+ if let Some(index) = area_indices.get(token).cloned() {
+ if areas[index].columns.start != column || areas[index].rows.end != row {
+ return Err(());
+ }
+ areas[index].rows.end += 1;
+ current_area_index = Some(index);
+ continue;
+ }
+ let index = areas.len();
+ areas.push(NamedArea {
+ name: token.to_owned().into_boxed_str(),
+ columns: column..(column + 1),
+ rows: row..(row + 1),
+ });
+ assert!(area_indices.insert(token, index).is_none());
+ current_area_index = Some(index);
+ }
+ if let Some(index) = current_area_index {
+ if areas[index].columns.end != column + 1 {
+ assert!(areas[index].rows.start != row);
+ return Err(());
+ }
+ }
+ if row == 1 {
+ width = column;
+ } else if width != column {
+ return Err(());
+ }
+ }
+ }
+ Ok(TemplateAreas {
+ areas: areas.into_boxed_slice(),
+ strings: strings.into_boxed_slice(),
+ width: width,
+ })
+ }
+ }
+
+ impl ToCss for TemplateAreas {
+ fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+ for (i, string) in self.strings.iter().enumerate() {
+ if i != 0 {
+ dest.write_str(" ")?;
+ }
+ serialize_string(string, dest)?;
+ }
+ Ok(())
+ }
+ }
+
+ struct Tokenizer<'a>(&'a str);
+
+ impl<'a> Iterator for Tokenizer<'a> {
+ type Item = Result<Option<(&'a str)>, ()>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ let rest = self.0.trim_left_matches(HTML_SPACE_CHARACTERS);
+ if rest.is_empty() {
+ return None;
+ }
+ if rest.starts_with('.') {
+ self.0 = &rest[rest.find(|c| c != '.').unwrap_or(rest.len())..];
+ return Some(Ok(None));
+ }
+ if !rest.starts_with(is_name_code_point) {
+ return Some(Err(()));
+ }
+ let token_len = rest.find(|c| !is_name_code_point(c)).unwrap_or(rest.len());
+ let token = &rest[..token_len];
+ self.0 = &rest[token_len..];
+ Some(Ok(Some(token)))
+ }
+ }
+
+ fn is_name_code_point(c: char) -> bool {
+ c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' ||
+ c >= '\u{80}' || c == '_' ||
+ c >= '0' && c <= '9' || c == '-'
+ }
+</%helpers:longhand>