diff --git a/components/style/build_gecko.rs b/components/style/build_gecko.rs index 7b223e4eac4..85d80fae1d4 100644 --- a/components/style/build_gecko.rs +++ b/components/style/build_gecko.rs @@ -623,6 +623,7 @@ mod bindings { .whitelisted_function("Servo_.*") .whitelisted_function("Gecko_.*"); let structs_types = [ + "mozilla::css::GridTemplateAreasValue", "mozilla::css::URLValue", "mozilla::Side", "RawGeckoAnimationPropertySegment", diff --git a/components/style/gecko_bindings/bindings.rs b/components/style/gecko_bindings/bindings.rs index 4099761c6c3..026881bf609 100644 --- a/components/style/gecko_bindings/bindings.rs +++ b/components/style/gecko_bindings/bindings.rs @@ -3,6 +3,7 @@ pub use nsstring::{nsACString, nsAString, nsString}; type nsACString_internal = nsACString; type nsAString_internal = nsAString; +use gecko_bindings::structs::mozilla::css::GridTemplateAreasValue; use gecko_bindings::structs::mozilla::css::URLValue; use gecko_bindings::structs::mozilla::Side; use gecko_bindings::structs::RawGeckoAnimationPropertySegment; @@ -893,6 +894,19 @@ extern "C" { other: *const nsStyleGridTemplate); } +extern "C" { + pub fn Gecko_NewGridTemplateAreasValue(areas: u32, templates: u32, + columns: u32) + -> *mut GridTemplateAreasValue; +} +extern "C" { + pub fn Gecko_AddRefGridTemplateAreasValueArbitraryThread(aPtr: + *mut GridTemplateAreasValue); +} +extern "C" { + pub fn Gecko_ReleaseGridTemplateAreasValueArbitraryThread(aPtr: + *mut GridTemplateAreasValue); +} extern "C" { pub fn Gecko_ClearAndResizeStyleContents(content: *mut nsStyleContent, how_many: u32); diff --git a/components/style/gecko_bindings/sugar/refptr.rs b/components/style/gecko_bindings/sugar/refptr.rs index 9ca1d3dc7b6..2b3776e70fd 100644 --- a/components/style/gecko_bindings/sugar/refptr.rs +++ b/components/style/gecko_bindings/sugar/refptr.rs @@ -277,3 +277,6 @@ impl_threadsafe_refcount!(::gecko_bindings::structs::nsCSSValueSharedList, 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); diff --git a/components/style/properties/gecko.mako.rs b/components/style/properties/gecko.mako.rs index e6aab102506..92fae5d5b66 100644 --- a/components/style/properties/gecko.mako.rs +++ b/components/style/properties/gecko.mako.rs @@ -1052,7 +1052,8 @@ fn static_assert() { <%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", @@ -1228,6 +1229,42 @@ fn static_assert() { } ${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) } + } <% skip_outline_longhands = " ".join("outline-style outline-width".split() + diff --git a/components/style/properties/longhand/position.mako.rs b/components/style/properties/longhand/position.mako.rs index 757c270bb49..370a538e893 100644 --- a/components/style/properties/longhand/position.mako.rs +++ b/components/style/properties/longhand/position.mako.rs @@ -406,3 +406,173 @@ ${helpers.predefined_type("object-position", } } + +<%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" + boxed="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; + + #[inline] + pub fn get_initial_value() -> computed_value::T { + Either::Second(None_) + } + + pub fn parse(context: &ParserContext, input: &mut Parser) -> Result { + SpecifiedValue::parse(context, input) + } + + #[derive(Clone, PartialEq)] + pub struct TemplateAreas { + pub areas: Box<[NamedArea]>, + pub strings: Box<[Box]>, + pub width: u32, + } + + #[derive(Clone, PartialEq)] + pub struct NamedArea { + pub name: Box, + pub rows: Range, + pub columns: Range, + } + + no_viewport_percentage!(TemplateAreas); + impl ComputedValueAsSpecified for TemplateAreas {} + + impl Parse for TemplateAreas { + fn parse(_context: &ParserContext, input: &mut Parser) -> Result { + 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 = 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 = 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(&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, ()>; + + fn next(&mut self) -> Option { + 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 == '-' + } +