mirror of
https://github.com/servo/servo.git
synced 2025-08-03 12:40:06 +01:00
Auto merge of #23325 - emilio:gecko-sync, r=emilio
style: Sync changes from mozilla-central. See each individual commit for details. <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/23325) <!-- Reviewable:end -->
This commit is contained in:
commit
ea71b35220
53 changed files with 979 additions and 886 deletions
|
@ -12,7 +12,7 @@ use style::values::computed::image::{EndingShape, LineDirection};
|
||||||
use style::values::computed::{Angle, GradientItem, LengthPercentage, Percentage, Position};
|
use style::values::computed::{Angle, GradientItem, LengthPercentage, Percentage, Position};
|
||||||
use style::values::generics::image::EndingShape as GenericEndingShape;
|
use style::values::generics::image::EndingShape as GenericEndingShape;
|
||||||
use style::values::generics::image::GradientItem as GenericGradientItem;
|
use style::values::generics::image::GradientItem as GenericGradientItem;
|
||||||
use style::values::generics::image::{Circle, Ellipse, ShapeExtent};
|
use style::values::generics::image::{Circle, ColorStop, Ellipse, ShapeExtent};
|
||||||
use style::values::specified::position::{X, Y};
|
use style::values::specified::position::{X, Y};
|
||||||
use webrender_api::{ExtendMode, Gradient, GradientBuilder, GradientStop, RadialGradient};
|
use webrender_api::{ExtendMode, Gradient, GradientBuilder, GradientStop, RadialGradient};
|
||||||
|
|
||||||
|
@ -92,7 +92,14 @@ fn convert_gradient_stops(
|
||||||
let mut stop_items = gradient_items
|
let mut stop_items = gradient_items
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|item| match *item {
|
.filter_map(|item| match *item {
|
||||||
GenericGradientItem::ColorStop(ref stop) => Some(*stop),
|
GenericGradientItem::SimpleColorStop(color) => Some(ColorStop {
|
||||||
|
color,
|
||||||
|
position: None,
|
||||||
|
}),
|
||||||
|
GenericGradientItem::ComplexColorStop { color, position } => Some(ColorStop {
|
||||||
|
color,
|
||||||
|
position: Some(position),
|
||||||
|
}),
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
|
@ -61,8 +61,8 @@ use style::selector_parser::RestyleDamage;
|
||||||
use style::servo::restyle_damage::ServoRestyleDamage;
|
use style::servo::restyle_damage::ServoRestyleDamage;
|
||||||
use style::str::char_is_whitespace;
|
use style::str::char_is_whitespace;
|
||||||
use style::values::computed::counters::ContentItem;
|
use style::values::computed::counters::ContentItem;
|
||||||
use style::values::computed::{LengthPercentage, LengthPercentageOrAuto, Size};
|
use style::values::computed::{LengthPercentage, LengthPercentageOrAuto, Size, VerticalAlign};
|
||||||
use style::values::generics::box_::{Perspective, VerticalAlign};
|
use style::values::generics::box_::{Perspective, VerticalAlignKeyword};
|
||||||
use style::values::generics::transform;
|
use style::values::generics::transform;
|
||||||
use style::Zero;
|
use style::Zero;
|
||||||
use webrender_api::{self, LayoutTransform};
|
use webrender_api::{self, LayoutTransform};
|
||||||
|
@ -2397,47 +2397,49 @@ impl Fragment {
|
||||||
// FIXME(#5624, pcwalton): This passes our current reftests but isn't the right thing
|
// FIXME(#5624, pcwalton): This passes our current reftests but isn't the right thing
|
||||||
// to do.
|
// to do.
|
||||||
match style.get_box().vertical_align {
|
match style.get_box().vertical_align {
|
||||||
VerticalAlign::Baseline => {},
|
VerticalAlign::Keyword(kw) => match kw {
|
||||||
VerticalAlign::Middle => {
|
VerticalAlignKeyword::Baseline => {},
|
||||||
let font_metrics =
|
VerticalAlignKeyword::Middle => {
|
||||||
with_thread_local_font_context(layout_context, |font_context| {
|
let font_metrics =
|
||||||
text::font_metrics_for_style(font_context, self.style.clone_font())
|
with_thread_local_font_context(layout_context, |font_context| {
|
||||||
});
|
text::font_metrics_for_style(font_context, self.style.clone_font())
|
||||||
offset += (content_inline_metrics.ascent -
|
});
|
||||||
content_inline_metrics.space_below_baseline -
|
offset += (content_inline_metrics.ascent -
|
||||||
font_metrics.x_height)
|
content_inline_metrics.space_below_baseline -
|
||||||
.scale_by(0.5)
|
font_metrics.x_height)
|
||||||
},
|
.scale_by(0.5)
|
||||||
VerticalAlign::Sub => {
|
},
|
||||||
offset += minimum_line_metrics
|
VerticalAlignKeyword::Sub => {
|
||||||
.space_needed()
|
offset += minimum_line_metrics
|
||||||
.scale_by(FONT_SUBSCRIPT_OFFSET_RATIO)
|
.space_needed()
|
||||||
},
|
.scale_by(FONT_SUBSCRIPT_OFFSET_RATIO)
|
||||||
VerticalAlign::Super => {
|
},
|
||||||
offset -= minimum_line_metrics
|
VerticalAlignKeyword::Super => {
|
||||||
.space_needed()
|
offset -= minimum_line_metrics
|
||||||
.scale_by(FONT_SUPERSCRIPT_OFFSET_RATIO)
|
.space_needed()
|
||||||
},
|
.scale_by(FONT_SUPERSCRIPT_OFFSET_RATIO)
|
||||||
VerticalAlign::TextTop => {
|
},
|
||||||
offset = self.content_inline_metrics(layout_context).ascent -
|
VerticalAlignKeyword::TextTop => {
|
||||||
minimum_line_metrics.space_above_baseline
|
offset = self.content_inline_metrics(layout_context).ascent -
|
||||||
},
|
minimum_line_metrics.space_above_baseline
|
||||||
VerticalAlign::TextBottom => {
|
},
|
||||||
offset = minimum_line_metrics.space_below_baseline -
|
VerticalAlignKeyword::TextBottom => {
|
||||||
self.content_inline_metrics(layout_context)
|
offset = minimum_line_metrics.space_below_baseline -
|
||||||
.space_below_baseline
|
self.content_inline_metrics(layout_context)
|
||||||
},
|
.space_below_baseline
|
||||||
VerticalAlign::Top => {
|
},
|
||||||
if let Some(actual_line_metrics) = actual_line_metrics {
|
VerticalAlignKeyword::Top => {
|
||||||
offset =
|
if let Some(actual_line_metrics) = actual_line_metrics {
|
||||||
content_inline_metrics.ascent - actual_line_metrics.space_above_baseline
|
offset = content_inline_metrics.ascent -
|
||||||
}
|
actual_line_metrics.space_above_baseline
|
||||||
},
|
}
|
||||||
VerticalAlign::Bottom => {
|
},
|
||||||
if let Some(actual_line_metrics) = actual_line_metrics {
|
VerticalAlignKeyword::Bottom => {
|
||||||
offset = actual_line_metrics.space_below_baseline -
|
if let Some(actual_line_metrics) = actual_line_metrics {
|
||||||
content_inline_metrics.space_below_baseline
|
offset = actual_line_metrics.space_below_baseline -
|
||||||
}
|
content_inline_metrics.space_below_baseline
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
VerticalAlign::Length(ref lp) => {
|
VerticalAlign::Length(ref lp) => {
|
||||||
offset -= lp.to_used_value(minimum_line_metrics.space_needed());
|
offset -= lp.to_used_value(minimum_line_metrics.space_needed());
|
||||||
|
@ -3087,15 +3089,22 @@ impl Fragment {
|
||||||
/// Returns true if any of the inline styles associated with this fragment have
|
/// Returns true if any of the inline styles associated with this fragment have
|
||||||
/// `vertical-align` set to `top` or `bottom`.
|
/// `vertical-align` set to `top` or `bottom`.
|
||||||
pub fn is_vertically_aligned_to_top_or_bottom(&self) -> bool {
|
pub fn is_vertically_aligned_to_top_or_bottom(&self) -> bool {
|
||||||
match self.style.get_box().vertical_align {
|
fn is_top_or_bottom(v: &VerticalAlign) -> bool {
|
||||||
VerticalAlign::Top | VerticalAlign::Bottom => return true,
|
match *v {
|
||||||
_ => {},
|
VerticalAlign::Keyword(VerticalAlignKeyword::Top) |
|
||||||
|
VerticalAlign::Keyword(VerticalAlignKeyword::Bottom) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if is_top_or_bottom(&self.style.get_box().vertical_align) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(ref inline_context) = self.inline_context {
|
if let Some(ref inline_context) = self.inline_context {
|
||||||
for node in &inline_context.nodes {
|
for node in &inline_context.nodes {
|
||||||
match node.style.get_box().vertical_align {
|
if is_top_or_bottom(&node.style.get_box().vertical_align) {
|
||||||
VerticalAlign::Top | VerticalAlign::Bottom => return true,
|
return true;
|
||||||
_ => {},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ use style::logical_geometry::{LogicalRect, LogicalSize, WritingMode};
|
||||||
use style::properties::ComputedValues;
|
use style::properties::ComputedValues;
|
||||||
use style::servo::restyle_damage::ServoRestyleDamage;
|
use style::servo::restyle_damage::ServoRestyleDamage;
|
||||||
use style::values::computed::box_::VerticalAlign;
|
use style::values::computed::box_::VerticalAlign;
|
||||||
use style::values::generics::box_::VerticalAlign as GenericVerticalAlign;
|
use style::values::generics::box_::VerticalAlignKeyword;
|
||||||
use style::values::specified::text::TextOverflowSide;
|
use style::values::specified::text::TextOverflowSide;
|
||||||
use unicode_bidi as bidi;
|
use unicode_bidi as bidi;
|
||||||
|
|
||||||
|
@ -1269,13 +1269,13 @@ impl InlineFlow {
|
||||||
let mut largest_block_size_for_top_fragments = Au(0);
|
let mut largest_block_size_for_top_fragments = Au(0);
|
||||||
let mut largest_block_size_for_bottom_fragments = Au(0);
|
let mut largest_block_size_for_bottom_fragments = Au(0);
|
||||||
|
|
||||||
// We use `VerticalAlign::Baseline` here because `vertical-align` must
|
// We use `VerticalAlign::baseline()` here because `vertical-align` must
|
||||||
// not apply to the inside of inline blocks.
|
// not apply to the inside of inline blocks.
|
||||||
update_line_metrics_for_fragment(
|
update_line_metrics_for_fragment(
|
||||||
&mut line_metrics,
|
&mut line_metrics,
|
||||||
&inline_metrics,
|
&inline_metrics,
|
||||||
style.get_box().display,
|
style.get_box().display,
|
||||||
GenericVerticalAlign::Baseline,
|
VerticalAlign::baseline(),
|
||||||
&mut largest_block_size_for_top_fragments,
|
&mut largest_block_size_for_top_fragments,
|
||||||
&mut largest_block_size_for_bottom_fragments,
|
&mut largest_block_size_for_bottom_fragments,
|
||||||
);
|
);
|
||||||
|
@ -1322,11 +1322,20 @@ impl InlineFlow {
|
||||||
largest_block_size_for_top_fragments: &mut Au,
|
largest_block_size_for_top_fragments: &mut Au,
|
||||||
largest_block_size_for_bottom_fragments: &mut Au,
|
largest_block_size_for_bottom_fragments: &mut Au,
|
||||||
) {
|
) {
|
||||||
|
// FIXME(emilio): This should probably be handled.
|
||||||
|
let vertical_align_value = match vertical_align_value {
|
||||||
|
VerticalAlign::Keyword(kw) => kw,
|
||||||
|
VerticalAlign::Length(..) => {
|
||||||
|
*line_metrics = line_metrics.new_metrics_for_fragment(inline_metrics);
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
match (display_value, vertical_align_value) {
|
match (display_value, vertical_align_value) {
|
||||||
(Display::Inline, GenericVerticalAlign::Top) |
|
(Display::Inline, VerticalAlignKeyword::Top) |
|
||||||
(Display::Block, GenericVerticalAlign::Top) |
|
(Display::Block, VerticalAlignKeyword::Top) |
|
||||||
(Display::InlineFlex, GenericVerticalAlign::Top) |
|
(Display::InlineFlex, VerticalAlignKeyword::Top) |
|
||||||
(Display::InlineBlock, GenericVerticalAlign::Top)
|
(Display::InlineBlock, VerticalAlignKeyword::Top)
|
||||||
if inline_metrics.space_above_baseline >= Au(0) =>
|
if inline_metrics.space_above_baseline >= Au(0) =>
|
||||||
{
|
{
|
||||||
*largest_block_size_for_top_fragments = max(
|
*largest_block_size_for_top_fragments = max(
|
||||||
|
@ -1334,10 +1343,10 @@ impl InlineFlow {
|
||||||
inline_metrics.space_above_baseline + inline_metrics.space_below_baseline,
|
inline_metrics.space_above_baseline + inline_metrics.space_below_baseline,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
(Display::Inline, GenericVerticalAlign::Bottom) |
|
(Display::Inline, VerticalAlignKeyword::Bottom) |
|
||||||
(Display::Block, GenericVerticalAlign::Bottom) |
|
(Display::Block, VerticalAlignKeyword::Bottom) |
|
||||||
(Display::InlineFlex, GenericVerticalAlign::Bottom) |
|
(Display::InlineFlex, VerticalAlignKeyword::Bottom) |
|
||||||
(Display::InlineBlock, GenericVerticalAlign::Bottom)
|
(Display::InlineBlock, VerticalAlignKeyword::Bottom)
|
||||||
if inline_metrics.space_below_baseline >= Au(0) =>
|
if inline_metrics.space_below_baseline >= Au(0) =>
|
||||||
{
|
{
|
||||||
*largest_block_size_for_bottom_fragments = max(
|
*largest_block_size_for_bottom_fragments = max(
|
||||||
|
|
|
@ -23,7 +23,7 @@ use style::logical_geometry::{LogicalMargin, LogicalRect, LogicalSize, WritingMo
|
||||||
use style::properties::ComputedValues;
|
use style::properties::ComputedValues;
|
||||||
use style::values::computed::length::Size;
|
use style::values::computed::length::Size;
|
||||||
use style::values::computed::Color;
|
use style::values::computed::Color;
|
||||||
use style::values::generics::box_::VerticalAlign;
|
use style::values::generics::box_::{VerticalAlign, VerticalAlignKeyword};
|
||||||
use style::values::specified::BorderStyle;
|
use style::values::specified::BorderStyle;
|
||||||
|
|
||||||
#[allow(unsafe_code)]
|
#[allow(unsafe_code)]
|
||||||
|
@ -138,11 +138,11 @@ impl TableCellFlow {
|
||||||
self.block_flow.fragment.border_padding.block_start_end();
|
self.block_flow.fragment.border_padding.block_start_end();
|
||||||
let kids_self_gap = self_size - kids_size;
|
let kids_self_gap = self_size - kids_size;
|
||||||
|
|
||||||
// This offset should also account for VerticalAlign::Baseline.
|
// This offset should also account for VerticalAlign::baseline.
|
||||||
// Need max cell ascent from the first row of this cell.
|
// Need max cell ascent from the first row of this cell.
|
||||||
let offset = match self.block_flow.fragment.style().get_box().vertical_align {
|
let offset = match self.block_flow.fragment.style().get_box().vertical_align {
|
||||||
VerticalAlign::Middle => kids_self_gap / 2,
|
VerticalAlign::Keyword(VerticalAlignKeyword::Middle) => kids_self_gap / 2,
|
||||||
VerticalAlign::Bottom => kids_self_gap,
|
VerticalAlign::Keyword(VerticalAlignKeyword::Bottom) => kids_self_gap,
|
||||||
_ => Au(0),
|
_ => Au(0),
|
||||||
};
|
};
|
||||||
if offset == Au(0) {
|
if offset == Au(0) {
|
||||||
|
|
|
@ -21,13 +21,13 @@ use std::collections::LinkedList;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use style::computed_values::text_rendering::T as TextRendering;
|
use style::computed_values::text_rendering::T as TextRendering;
|
||||||
use style::computed_values::text_transform::T as TextTransform;
|
|
||||||
use style::computed_values::white_space::T as WhiteSpace;
|
use style::computed_values::white_space::T as WhiteSpace;
|
||||||
use style::computed_values::word_break::T as WordBreak;
|
use style::computed_values::word_break::T as WordBreak;
|
||||||
use style::logical_geometry::{LogicalSize, WritingMode};
|
use style::logical_geometry::{LogicalSize, WritingMode};
|
||||||
use style::properties::style_structs::Font as FontStyleStruct;
|
use style::properties::style_structs::Font as FontStyleStruct;
|
||||||
use style::properties::ComputedValues;
|
use style::properties::ComputedValues;
|
||||||
use style::values::generics::text::LineHeight;
|
use style::values::generics::text::LineHeight;
|
||||||
|
use style::values::specified::text::{TextTransform, TextTransformCase};
|
||||||
use unicode_bidi as bidi;
|
use unicode_bidi as bidi;
|
||||||
use unicode_script::{get_script, Script};
|
use unicode_script::{get_script, Script};
|
||||||
use xi_unicode::LineBreakLeafIter;
|
use xi_unicode::LineBreakLeafIter;
|
||||||
|
@ -738,23 +738,23 @@ fn apply_style_transform_if_necessary(
|
||||||
last_whitespace: bool,
|
last_whitespace: bool,
|
||||||
is_first_run: bool,
|
is_first_run: bool,
|
||||||
) {
|
) {
|
||||||
match text_transform {
|
match text_transform.case_ {
|
||||||
TextTransform::None => {},
|
TextTransformCase::None => {},
|
||||||
TextTransform::Uppercase => {
|
TextTransformCase::Uppercase => {
|
||||||
let original = string[first_character_position..].to_owned();
|
let original = string[first_character_position..].to_owned();
|
||||||
string.truncate(first_character_position);
|
string.truncate(first_character_position);
|
||||||
for ch in original.chars().flat_map(|ch| ch.to_uppercase()) {
|
for ch in original.chars().flat_map(|ch| ch.to_uppercase()) {
|
||||||
string.push(ch);
|
string.push(ch);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
TextTransform::Lowercase => {
|
TextTransformCase::Lowercase => {
|
||||||
let original = string[first_character_position..].to_owned();
|
let original = string[first_character_position..].to_owned();
|
||||||
string.truncate(first_character_position);
|
string.truncate(first_character_position);
|
||||||
for ch in original.chars().flat_map(|ch| ch.to_lowercase()) {
|
for ch in original.chars().flat_map(|ch| ch.to_lowercase()) {
|
||||||
string.push(ch);
|
string.push(ch);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
TextTransform::Capitalize => {
|
TextTransformCase::Capitalize => {
|
||||||
let original = string[first_character_position..].to_owned();
|
let original = string[first_character_position..].to_owned();
|
||||||
string.truncate(first_character_position);
|
string.truncate(first_character_position);
|
||||||
|
|
||||||
|
|
|
@ -972,6 +972,11 @@ impl<'le> ::selectors::Element for ServoLayoutElement<'le> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn is_part(&self, _name: &Atom) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
|
fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
|
||||||
unsafe { self.element.has_class_for_layout(name, case_sensitivity) }
|
unsafe { self.element.has_class_for_layout(name, case_sensitivity) }
|
||||||
|
@ -1484,6 +1489,12 @@ impl<'le> ::selectors::Element for ServoThreadSafeLayoutElement<'le> {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn is_part(&self, _name: &Atom) -> bool {
|
||||||
|
debug!("ServoThreadSafeLayoutElement::is_part called");
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn has_class(&self, _name: &Atom, _case_sensitivity: CaseSensitivity) -> bool {
|
fn has_class(&self, _name: &Atom, _case_sensitivity: CaseSensitivity) -> bool {
|
||||||
debug!("ServoThreadSafeLayoutElement::has_class called");
|
debug!("ServoThreadSafeLayoutElement::has_class called");
|
||||||
false
|
false
|
||||||
|
|
|
@ -747,6 +747,7 @@ where
|
||||||
Component::ExplicitUniversalType |
|
Component::ExplicitUniversalType |
|
||||||
Component::LocalName(..) |
|
Component::LocalName(..) |
|
||||||
Component::ID(..) |
|
Component::ID(..) |
|
||||||
|
Component::Part(..) |
|
||||||
Component::Class(..) |
|
Component::Class(..) |
|
||||||
Component::AttributeInNoNamespaceExists { .. } |
|
Component::AttributeInNoNamespaceExists { .. } |
|
||||||
Component::AttributeInNoNamespace { .. } |
|
Component::AttributeInNoNamespace { .. } |
|
||||||
|
@ -776,12 +777,6 @@ impl<Impl: selectors::parser::SelectorImpl> MallocSizeOf
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MallocSizeOf for selectors::context::QuirksMode {
|
|
||||||
fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MallocSizeOf for Void {
|
impl MallocSizeOf for Void {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
|
fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
|
||||||
|
|
|
@ -3075,6 +3075,10 @@ impl<'a> SelectorsElement for DomRoot<Element> {
|
||||||
.map_or(false, |atom| case_sensitivity.eq_atom(id, atom))
|
.map_or(false, |atom| case_sensitivity.eq_atom(id, atom))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_part(&self, _name: &Atom) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
|
fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
|
||||||
Element::has_class(&**self, name, case_sensitivity)
|
Element::has_class(&**self, name, case_sensitivity)
|
||||||
}
|
}
|
||||||
|
|
|
@ -270,7 +270,7 @@ where
|
||||||
Component::Combinator(..) => {
|
Component::Combinator(..) => {
|
||||||
unreachable!("Found combinator in simple selectors vector?");
|
unreachable!("Found combinator in simple selectors vector?");
|
||||||
},
|
},
|
||||||
Component::PseudoElement(..) | Component::LocalName(..) => {
|
Component::Part(..) | Component::PseudoElement(..) | Component::LocalName(..) => {
|
||||||
specificity.element_selectors += 1
|
specificity.element_selectors += 1
|
||||||
},
|
},
|
||||||
Component::Slotted(ref selector) => {
|
Component::Slotted(ref selector) => {
|
||||||
|
|
|
@ -279,13 +279,13 @@ where
|
||||||
/// Runs F with a given shadow host which is the root of the tree whose
|
/// Runs F with a given shadow host which is the root of the tree whose
|
||||||
/// rules we're matching.
|
/// rules we're matching.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn with_shadow_host<F, E, R>(&mut self, host: Option<E>, f: F) -> R
|
pub fn with_shadow_host<F, E, R>(&mut self, host: E, f: F) -> R
|
||||||
where
|
where
|
||||||
E: Element,
|
E: Element,
|
||||||
F: FnOnce(&mut Self) -> R,
|
F: FnOnce(&mut Self) -> R,
|
||||||
{
|
{
|
||||||
let original_host = self.current_host.take();
|
let original_host = self.current_host.take();
|
||||||
self.current_host = host.map(|h| h.opaque());
|
self.current_host = Some(host.opaque());
|
||||||
let result = f(self);
|
let result = f(self);
|
||||||
self.current_host = original_host;
|
self.current_host = original_host;
|
||||||
result
|
result
|
||||||
|
|
|
@ -450,6 +450,7 @@ where
|
||||||
|
|
||||||
element.containing_shadow_host()
|
element.containing_shadow_host()
|
||||||
},
|
},
|
||||||
|
Combinator::Part => element.containing_shadow_host(),
|
||||||
Combinator::SlotAssignment => {
|
Combinator::SlotAssignment => {
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
context.current_host.is_some(),
|
context.current_host.is_some(),
|
||||||
|
@ -517,6 +518,7 @@ where
|
||||||
Combinator::Child |
|
Combinator::Child |
|
||||||
Combinator::Descendant |
|
Combinator::Descendant |
|
||||||
Combinator::SlotAssignment |
|
Combinator::SlotAssignment |
|
||||||
|
Combinator::Part |
|
||||||
Combinator::PseudoElement => SelectorMatchingResult::NotMatchedGlobally,
|
Combinator::PseudoElement => SelectorMatchingResult::NotMatchedGlobally,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -671,6 +673,7 @@ where
|
||||||
|
|
||||||
match *selector {
|
match *selector {
|
||||||
Component::Combinator(_) => unreachable!(),
|
Component::Combinator(_) => unreachable!(),
|
||||||
|
Component::Part(ref part) => element.is_part(part),
|
||||||
Component::Slotted(ref selector) => {
|
Component::Slotted(ref selector) => {
|
||||||
// <slots> are never flattened tree slottables.
|
// <slots> are never flattened tree slottables.
|
||||||
!element.is_html_slot_element() &&
|
!element.is_html_slot_element() &&
|
||||||
|
|
|
@ -30,10 +30,7 @@ pub trait PseudoElement: Sized + ToCss {
|
||||||
|
|
||||||
/// Whether the pseudo-element supports a given state selector to the right
|
/// Whether the pseudo-element supports a given state selector to the right
|
||||||
/// of it.
|
/// of it.
|
||||||
fn supports_pseudo_class(
|
fn accepts_state_pseudo_classes(&self) -> bool {
|
||||||
&self,
|
|
||||||
_pseudo_class: &<Self::Impl as SelectorImpl>::NonTSPseudoClass,
|
|
||||||
) -> bool {
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,6 +47,11 @@ pub trait NonTSPseudoClass: Sized + ToCss {
|
||||||
|
|
||||||
/// Whether this pseudo-class is :active or :hover.
|
/// Whether this pseudo-class is :active or :hover.
|
||||||
fn is_active_or_hover(&self) -> bool;
|
fn is_active_or_hover(&self) -> bool;
|
||||||
|
|
||||||
|
/// Whether this pseudo-class belongs to:
|
||||||
|
///
|
||||||
|
/// https://drafts.csswg.org/selectors-4/#useraction-pseudos
|
||||||
|
fn is_user_action_state(&self) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a Cow::Borrowed if `s` is already ASCII lowercase, and a
|
/// Returns a Cow::Borrowed if `s` is already ASCII lowercase, and a
|
||||||
|
@ -64,6 +66,72 @@ fn to_ascii_lowercase(s: &str) -> Cow<str> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
/// Flags that indicate at which point of parsing a selector are we.
|
||||||
|
struct SelectorParsingState: u8 {
|
||||||
|
/// Whether we're inside a negation. If we're inside a negation, we're
|
||||||
|
/// not allowed to add another negation or such, for example.
|
||||||
|
const INSIDE_NEGATION = 1 << 0;
|
||||||
|
/// Whether we've parsed a ::slotted() pseudo-element already.
|
||||||
|
///
|
||||||
|
/// If so, then we can only parse a subset of pseudo-elements, and
|
||||||
|
/// whatever comes after them if so.
|
||||||
|
const AFTER_SLOTTED = 1 << 1;
|
||||||
|
/// Whether we've parsed a ::part() pseudo-element already.
|
||||||
|
///
|
||||||
|
/// If so, then we can only parse a subset of pseudo-elements, and
|
||||||
|
/// whatever comes after them if so.
|
||||||
|
const AFTER_PART = 1 << 2;
|
||||||
|
/// Whether we've parsed a pseudo-element (as in, an
|
||||||
|
/// `Impl::PseudoElement` thus not accounting for `::slotted` or
|
||||||
|
/// `::part`) already.
|
||||||
|
///
|
||||||
|
/// If so, then other pseudo-elements and most other selectors are
|
||||||
|
/// disallowed.
|
||||||
|
const AFTER_PSEUDO_ELEMENT = 1 << 3;
|
||||||
|
/// Whether we've parsed a non-stateful pseudo-element (again, as-in
|
||||||
|
/// `Impl::PseudoElement`) already. If so, then other pseudo-classes are
|
||||||
|
/// disallowed. If this flag is set, `AFTER_PSEUDO_ELEMENT` must be set
|
||||||
|
/// as well.
|
||||||
|
const AFTER_NON_STATEFUL_PSEUDO_ELEMENT = 1 << 4;
|
||||||
|
/// Whether we are after any of the pseudo-like things.
|
||||||
|
const AFTER_PSEUDO = Self::AFTER_PART.bits | Self::AFTER_SLOTTED.bits | Self::AFTER_PSEUDO_ELEMENT.bits;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SelectorParsingState {
|
||||||
|
#[inline]
|
||||||
|
fn allows_functional_pseudo_classes(self) -> bool {
|
||||||
|
!self.intersects(SelectorParsingState::AFTER_PSEUDO)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn allows_slotted(self) -> bool {
|
||||||
|
!self.intersects(SelectorParsingState::AFTER_PSEUDO)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(emilio): Should we allow other ::part()s after ::part()?
|
||||||
|
//
|
||||||
|
// See https://github.com/w3c/csswg-drafts/issues/3841
|
||||||
|
#[inline]
|
||||||
|
fn allows_part(self) -> bool {
|
||||||
|
!self.intersects(SelectorParsingState::AFTER_PSEUDO)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn allows_non_functional_pseudo_classes(self) -> bool {
|
||||||
|
!self.intersects(
|
||||||
|
SelectorParsingState::AFTER_SLOTTED |
|
||||||
|
SelectorParsingState::AFTER_NON_STATEFUL_PSEUDO_ELEMENT,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn allows_tree_structural_pseudo_classes(self) -> bool {
|
||||||
|
!self.intersects(SelectorParsingState::AFTER_PSEUDO)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub type SelectorParseError<'i> = ParseError<'i, SelectorParseErrorKind<'i>>;
|
pub type SelectorParseError<'i> = ParseError<'i, SelectorParseErrorKind<'i>>;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
@ -76,6 +144,7 @@ pub enum SelectorParseErrorKind<'i> {
|
||||||
NonCompoundSelector,
|
NonCompoundSelector,
|
||||||
NonPseudoElementAfterSlotted,
|
NonPseudoElementAfterSlotted,
|
||||||
InvalidPseudoElementAfterSlotted,
|
InvalidPseudoElementAfterSlotted,
|
||||||
|
InvalidState,
|
||||||
UnexpectedTokenInAttributeSelector(Token<'i>),
|
UnexpectedTokenInAttributeSelector(Token<'i>),
|
||||||
PseudoElementExpectedColon(Token<'i>),
|
PseudoElementExpectedColon(Token<'i>),
|
||||||
PseudoElementExpectedIdent(Token<'i>),
|
PseudoElementExpectedIdent(Token<'i>),
|
||||||
|
@ -108,6 +177,7 @@ macro_rules! with_all_bounds {
|
||||||
type AttrValue: $($InSelector)*;
|
type AttrValue: $($InSelector)*;
|
||||||
type Identifier: $($InSelector)*;
|
type Identifier: $($InSelector)*;
|
||||||
type ClassName: $($InSelector)*;
|
type ClassName: $($InSelector)*;
|
||||||
|
type PartName: $($InSelector)*;
|
||||||
type LocalName: $($InSelector)* + Borrow<Self::BorrowedLocalName>;
|
type LocalName: $($InSelector)* + Borrow<Self::BorrowedLocalName>;
|
||||||
type NamespaceUrl: $($CommonBounds)* + Default + Borrow<Self::BorrowedNamespaceUrl>;
|
type NamespaceUrl: $($CommonBounds)* + Default + Borrow<Self::BorrowedNamespaceUrl>;
|
||||||
type NamespacePrefix: $($InSelector)* + Default;
|
type NamespacePrefix: $($InSelector)* + Default;
|
||||||
|
@ -143,17 +213,16 @@ pub trait Parser<'i> {
|
||||||
type Impl: SelectorImpl;
|
type Impl: SelectorImpl;
|
||||||
type Error: 'i + From<SelectorParseErrorKind<'i>>;
|
type Error: 'i + From<SelectorParseErrorKind<'i>>;
|
||||||
|
|
||||||
/// Whether the name is a pseudo-element that can be specified with
|
|
||||||
/// the single colon syntax in addition to the double-colon syntax.
|
|
||||||
fn pseudo_element_allows_single_colon(name: &str) -> bool {
|
|
||||||
is_css2_pseudo_element(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether to parse the `::slotted()` pseudo-element.
|
/// Whether to parse the `::slotted()` pseudo-element.
|
||||||
fn parse_slotted(&self) -> bool {
|
fn parse_slotted(&self) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether to parse the `::part()` pseudo-element.
|
||||||
|
fn parse_part(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether to parse the `:host` pseudo-class.
|
/// Whether to parse the `:host` pseudo-class.
|
||||||
fn parse_host(&self) -> bool {
|
fn parse_host(&self) -> bool {
|
||||||
false
|
false
|
||||||
|
@ -694,7 +763,8 @@ impl<'a, Impl: 'a + SelectorImpl> SelectorIter<'a, Impl> {
|
||||||
/// combinators to the left.
|
/// combinators to the left.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn is_featureless_host_selector(&mut self) -> bool {
|
pub(crate) fn is_featureless_host_selector(&mut self) -> bool {
|
||||||
self.all(|component| matches!(*component, Component::Host(..))) &&
|
self.selector_length() > 0 &&
|
||||||
|
self.all(|component| matches!(*component, Component::Host(..))) &&
|
||||||
self.next_sequence().is_none()
|
self.next_sequence().is_none()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -798,6 +868,9 @@ pub enum Combinator {
|
||||||
/// Another combinator used for ::slotted(), which represent the jump from
|
/// Another combinator used for ::slotted(), which represent the jump from
|
||||||
/// a node to its assigned slot.
|
/// a node to its assigned slot.
|
||||||
SlotAssignment,
|
SlotAssignment,
|
||||||
|
/// Another combinator used for `::part()`, which represents the jump from
|
||||||
|
/// the part to the containing shadow host.
|
||||||
|
Part,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Combinator {
|
impl Combinator {
|
||||||
|
@ -891,8 +964,7 @@ pub enum Component<Impl: SelectorImpl> {
|
||||||
LastOfType,
|
LastOfType,
|
||||||
OnlyOfType,
|
OnlyOfType,
|
||||||
NonTSPseudoClass(#[shmem(field_bound)] Impl::NonTSPseudoClass),
|
NonTSPseudoClass(#[shmem(field_bound)] Impl::NonTSPseudoClass),
|
||||||
/// The ::slotted() pseudo-element (which isn't actually a pseudo-element,
|
/// The ::slotted() pseudo-element:
|
||||||
/// and probably should be a pseudo-class):
|
|
||||||
///
|
///
|
||||||
/// https://drafts.csswg.org/css-scoping/#slotted-pseudo
|
/// https://drafts.csswg.org/css-scoping/#slotted-pseudo
|
||||||
///
|
///
|
||||||
|
@ -904,6 +976,9 @@ pub enum Component<Impl: SelectorImpl> {
|
||||||
///
|
///
|
||||||
/// See https://github.com/w3c/csswg-drafts/issues/2158
|
/// See https://github.com/w3c/csswg-drafts/issues/2158
|
||||||
Slotted(Selector<Impl>),
|
Slotted(Selector<Impl>),
|
||||||
|
/// The `::part` pseudo-element.
|
||||||
|
/// https://drafts.csswg.org/css-shadow-parts/#part
|
||||||
|
Part(#[shmem(field_bound)] Impl::PartName),
|
||||||
/// The `:host` pseudo-class:
|
/// The `:host` pseudo-class:
|
||||||
///
|
///
|
||||||
/// https://drafts.csswg.org/css-scoping/#host-selector
|
/// https://drafts.csswg.org/css-scoping/#host-selector
|
||||||
|
@ -1153,8 +1228,7 @@ impl ToCss for Combinator {
|
||||||
Combinator::Descendant => dest.write_str(" "),
|
Combinator::Descendant => dest.write_str(" "),
|
||||||
Combinator::NextSibling => dest.write_str(" + "),
|
Combinator::NextSibling => dest.write_str(" + "),
|
||||||
Combinator::LaterSibling => dest.write_str(" ~ "),
|
Combinator::LaterSibling => dest.write_str(" ~ "),
|
||||||
Combinator::PseudoElement => Ok(()),
|
Combinator::PseudoElement | Combinator::Part | Combinator::SlotAssignment => Ok(()),
|
||||||
Combinator::SlotAssignment => Ok(()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1193,6 +1267,11 @@ impl<Impl: SelectorImpl> ToCss for Component<Impl> {
|
||||||
selector.to_css(dest)?;
|
selector.to_css(dest)?;
|
||||||
dest.write_char(')')
|
dest.write_char(')')
|
||||||
},
|
},
|
||||||
|
Part(ref part_name) => {
|
||||||
|
dest.write_str("::part(")?;
|
||||||
|
display_to_css_identifier(part_name, dest)?;
|
||||||
|
dest.write_char(')')
|
||||||
|
},
|
||||||
PseudoElement(ref p) => p.to_css(dest),
|
PseudoElement(ref p) => p.to_css(dest),
|
||||||
ID(ref s) => {
|
ID(ref s) => {
|
||||||
dest.write_char('#')?;
|
dest.write_char('#')?;
|
||||||
|
@ -1364,15 +1443,12 @@ where
|
||||||
{
|
{
|
||||||
let mut builder = SelectorBuilder::default();
|
let mut builder = SelectorBuilder::default();
|
||||||
|
|
||||||
let mut has_pseudo_element;
|
let mut has_pseudo_element = false;
|
||||||
let mut slotted;
|
let mut slotted = false;
|
||||||
'outer_loop: loop {
|
'outer_loop: loop {
|
||||||
// Parse a sequence of simple selectors.
|
// Parse a sequence of simple selectors.
|
||||||
match parse_compound_selector(parser, input, &mut builder)? {
|
let state = match parse_compound_selector(parser, input, &mut builder)? {
|
||||||
Some((has_pseudo, slot)) => {
|
Some(state) => state,
|
||||||
has_pseudo_element = has_pseudo;
|
|
||||||
slotted = slot;
|
|
||||||
},
|
|
||||||
None => {
|
None => {
|
||||||
return Err(input.new_custom_error(if builder.has_combinators() {
|
return Err(input.new_custom_error(if builder.has_combinators() {
|
||||||
SelectorParseErrorKind::DanglingCombinator
|
SelectorParseErrorKind::DanglingCombinator
|
||||||
|
@ -1382,7 +1458,11 @@ where
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if has_pseudo_element || slotted {
|
if state.intersects(SelectorParsingState::AFTER_PSEUDO) {
|
||||||
|
has_pseudo_element = state.intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT);
|
||||||
|
slotted = state.intersects(SelectorParsingState::AFTER_SLOTTED);
|
||||||
|
let part = state.intersects(SelectorParsingState::AFTER_PART);
|
||||||
|
debug_assert!(has_pseudo_element || slotted || part);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1420,6 +1500,8 @@ where
|
||||||
builder.push_combinator(combinator);
|
builder.push_combinator(combinator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(emilio): We'll have to flag part() somehow as well, but we need more
|
||||||
|
// bits!
|
||||||
Ok(Selector(builder.build(has_pseudo_element, slotted)))
|
Ok(Selector(builder.build(has_pseudo_element, slotted)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1510,6 +1592,7 @@ enum SimpleSelectorParseResult<Impl: SelectorImpl> {
|
||||||
SimpleSelector(Component<Impl>),
|
SimpleSelector(Component<Impl>),
|
||||||
PseudoElement(Impl::PseudoElement),
|
PseudoElement(Impl::PseudoElement),
|
||||||
SlottedPseudo(Selector<Impl>),
|
SlottedPseudo(Selector<Impl>),
|
||||||
|
PartPseudo(Impl::PartName),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -1848,7 +1931,7 @@ where
|
||||||
Err(e) => return Err(e.into()),
|
Err(e) => return Err(e.into()),
|
||||||
};
|
};
|
||||||
if !is_type_sel {
|
if !is_type_sel {
|
||||||
match parse_one_simple_selector(parser, input, /* inside_negation = */ true)? {
|
match parse_one_simple_selector(parser, input, SelectorParsingState::INSIDE_NEGATION)? {
|
||||||
Some(SimpleSelectorParseResult::SimpleSelector(s)) => {
|
Some(SimpleSelectorParseResult::SimpleSelector(s)) => {
|
||||||
sequence.push(s);
|
sequence.push(s);
|
||||||
},
|
},
|
||||||
|
@ -1856,6 +1939,7 @@ where
|
||||||
return Err(input.new_custom_error(SelectorParseErrorKind::EmptyNegation));
|
return Err(input.new_custom_error(SelectorParseErrorKind::EmptyNegation));
|
||||||
},
|
},
|
||||||
Some(SimpleSelectorParseResult::PseudoElement(_)) |
|
Some(SimpleSelectorParseResult::PseudoElement(_)) |
|
||||||
|
Some(SimpleSelectorParseResult::PartPseudo(_)) |
|
||||||
Some(SimpleSelectorParseResult::SlottedPseudo(_)) => {
|
Some(SimpleSelectorParseResult::SlottedPseudo(_)) => {
|
||||||
let e = SelectorParseErrorKind::NonSimpleSelectorInNegation;
|
let e = SelectorParseErrorKind::NonSimpleSelectorInNegation;
|
||||||
return Err(input.new_custom_error(e));
|
return Err(input.new_custom_error(e));
|
||||||
|
@ -1875,14 +1959,11 @@ where
|
||||||
///
|
///
|
||||||
/// `Err(())` means invalid selector.
|
/// `Err(())` means invalid selector.
|
||||||
/// `Ok(None)` is an empty selector
|
/// `Ok(None)` is an empty selector
|
||||||
///
|
|
||||||
/// The booleans represent whether a pseudo-element has been parsed, and whether
|
|
||||||
/// ::slotted() has been parsed, respectively.
|
|
||||||
fn parse_compound_selector<'i, 't, P, Impl>(
|
fn parse_compound_selector<'i, 't, P, Impl>(
|
||||||
parser: &P,
|
parser: &P,
|
||||||
input: &mut CssParser<'i, 't>,
|
input: &mut CssParser<'i, 't>,
|
||||||
builder: &mut SelectorBuilder<Impl>,
|
builder: &mut SelectorBuilder<Impl>,
|
||||||
) -> Result<Option<(bool, bool)>, ParseError<'i, P::Error>>
|
) -> Result<Option<SelectorParsingState>, ParseError<'i, P::Error>>
|
||||||
where
|
where
|
||||||
P: Parser<'i, Impl = Impl>,
|
P: Parser<'i, Impl = Impl>,
|
||||||
Impl: SelectorImpl,
|
Impl: SelectorImpl,
|
||||||
|
@ -1901,122 +1982,48 @@ where
|
||||||
empty = false;
|
empty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut pseudo = false;
|
let mut state = SelectorParsingState::empty();
|
||||||
let mut slot = false;
|
|
||||||
loop {
|
loop {
|
||||||
let parse_result =
|
let parse_result = match parse_one_simple_selector(parser, input, state)? {
|
||||||
match parse_one_simple_selector(parser, input, /* inside_negation = */ false)? {
|
None => break,
|
||||||
None => break,
|
Some(result) => result,
|
||||||
Some(result) => result,
|
};
|
||||||
};
|
|
||||||
|
|
||||||
empty = false;
|
empty = false;
|
||||||
|
|
||||||
let slotted_selector;
|
|
||||||
let pseudo_element;
|
|
||||||
|
|
||||||
match parse_result {
|
match parse_result {
|
||||||
SimpleSelectorParseResult::SimpleSelector(s) => {
|
SimpleSelectorParseResult::SimpleSelector(s) => {
|
||||||
builder.push_simple_selector(s);
|
builder.push_simple_selector(s);
|
||||||
continue;
|
|
||||||
},
|
},
|
||||||
SimpleSelectorParseResult::PseudoElement(p) => {
|
SimpleSelectorParseResult::PartPseudo(part_name) => {
|
||||||
slotted_selector = None;
|
state.insert(SelectorParsingState::AFTER_PART);
|
||||||
pseudo_element = Some(p);
|
builder.push_combinator(Combinator::Part);
|
||||||
|
builder.push_simple_selector(Component::Part(part_name));
|
||||||
},
|
},
|
||||||
SimpleSelectorParseResult::SlottedPseudo(selector) => {
|
SimpleSelectorParseResult::SlottedPseudo(selector) => {
|
||||||
slotted_selector = Some(selector);
|
state.insert(SelectorParsingState::AFTER_SLOTTED);
|
||||||
let maybe_pseudo =
|
if !builder.is_empty() {
|
||||||
parse_one_simple_selector(parser, input, /* inside_negation = */ false)?;
|
builder.push_combinator(Combinator::SlotAssignment);
|
||||||
|
}
|
||||||
pseudo_element = match maybe_pseudo {
|
builder.push_simple_selector(Component::Slotted(selector));
|
||||||
None => None,
|
},
|
||||||
Some(SimpleSelectorParseResult::PseudoElement(pseudo)) => {
|
SimpleSelectorParseResult::PseudoElement(p) => {
|
||||||
if !pseudo.valid_after_slotted() {
|
state.insert(SelectorParsingState::AFTER_PSEUDO_ELEMENT);
|
||||||
return Err(input.new_custom_error(
|
if !p.accepts_state_pseudo_classes() {
|
||||||
SelectorParseErrorKind::InvalidPseudoElementAfterSlotted,
|
state.insert(SelectorParsingState::AFTER_NON_STATEFUL_PSEUDO_ELEMENT);
|
||||||
));
|
}
|
||||||
}
|
if !builder.is_empty() {
|
||||||
Some(pseudo)
|
builder.push_combinator(Combinator::PseudoElement);
|
||||||
},
|
}
|
||||||
Some(SimpleSelectorParseResult::SimpleSelector(..)) |
|
builder.push_simple_selector(Component::PseudoElement(p));
|
||||||
Some(SimpleSelectorParseResult::SlottedPseudo(..)) => {
|
|
||||||
return Err(input.new_custom_error(
|
|
||||||
SelectorParseErrorKind::NonPseudoElementAfterSlotted,
|
|
||||||
));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
debug_assert!(slotted_selector.is_some() || pseudo_element.is_some());
|
|
||||||
// Try to parse state to the right of the pseudo-element.
|
|
||||||
//
|
|
||||||
// There are only 3 allowable state selectors that can go on
|
|
||||||
// pseudo-elements as of right now.
|
|
||||||
let mut state_selectors = SmallVec::<[Component<Impl>; 3]>::new();
|
|
||||||
if let Some(ref p) = pseudo_element {
|
|
||||||
loop {
|
|
||||||
let location = input.current_source_location();
|
|
||||||
match input.next_including_whitespace() {
|
|
||||||
Ok(&Token::Colon) => {},
|
|
||||||
Ok(&Token::WhiteSpace(_)) | Err(_) => break,
|
|
||||||
Ok(t) => {
|
|
||||||
let e = SelectorParseErrorKind::PseudoElementExpectedColon(t.clone());
|
|
||||||
return Err(location.new_custom_error(e));
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
let location = input.current_source_location();
|
|
||||||
// TODO(emilio): Functional pseudo-classes too?
|
|
||||||
// We don't need it for now.
|
|
||||||
let name = match input.next_including_whitespace()? {
|
|
||||||
&Token::Ident(ref name) => name.clone(),
|
|
||||||
t => {
|
|
||||||
return Err(location.new_custom_error(
|
|
||||||
SelectorParseErrorKind::NoIdentForPseudo(t.clone()),
|
|
||||||
));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let pseudo_class = P::parse_non_ts_pseudo_class(parser, location, name.clone())?;
|
|
||||||
if !p.supports_pseudo_class(&pseudo_class) {
|
|
||||||
return Err(input.new_custom_error(
|
|
||||||
SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
state_selectors.push(Component::NonTSPseudoClass(pseudo_class));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(slotted) = slotted_selector {
|
|
||||||
slot = true;
|
|
||||||
if !builder.is_empty() {
|
|
||||||
builder.push_combinator(Combinator::SlotAssignment);
|
|
||||||
}
|
|
||||||
builder.push_simple_selector(Component::Slotted(slotted));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(p) = pseudo_element {
|
|
||||||
pseudo = true;
|
|
||||||
if !builder.is_empty() {
|
|
||||||
builder.push_combinator(Combinator::PseudoElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.push_simple_selector(Component::PseudoElement(p));
|
|
||||||
|
|
||||||
for state_selector in state_selectors.drain() {
|
|
||||||
builder.push_simple_selector(state_selector);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
if empty {
|
if empty {
|
||||||
// An empty selector is invalid.
|
// An empty selector is invalid.
|
||||||
Ok(None)
|
Ok(None)
|
||||||
} else {
|
} else {
|
||||||
Ok(Some((pseudo, slot)))
|
Ok(Some(state))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2024,12 +2031,16 @@ fn parse_functional_pseudo_class<'i, 't, P, Impl>(
|
||||||
parser: &P,
|
parser: &P,
|
||||||
input: &mut CssParser<'i, 't>,
|
input: &mut CssParser<'i, 't>,
|
||||||
name: CowRcStr<'i>,
|
name: CowRcStr<'i>,
|
||||||
inside_negation: bool,
|
state: SelectorParsingState,
|
||||||
) -> Result<Component<Impl>, ParseError<'i, P::Error>>
|
) -> Result<Component<Impl>, ParseError<'i, P::Error>>
|
||||||
where
|
where
|
||||||
P: Parser<'i, Impl = Impl>,
|
P: Parser<'i, Impl = Impl>,
|
||||||
Impl: SelectorImpl,
|
Impl: SelectorImpl,
|
||||||
{
|
{
|
||||||
|
if !state.allows_functional_pseudo_classes() {
|
||||||
|
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
|
||||||
|
}
|
||||||
|
debug_assert!(state.allows_tree_structural_pseudo_classes());
|
||||||
match_ignore_ascii_case! { &name,
|
match_ignore_ascii_case! { &name,
|
||||||
"nth-child" => return Ok(parse_nth_pseudo_class(input, Component::NthChild)?),
|
"nth-child" => return Ok(parse_nth_pseudo_class(input, Component::NthChild)?),
|
||||||
"nth-of-type" => return Ok(parse_nth_pseudo_class(input, Component::NthOfType)?),
|
"nth-of-type" => return Ok(parse_nth_pseudo_class(input, Component::NthOfType)?),
|
||||||
|
@ -2037,11 +2048,12 @@ where
|
||||||
"nth-last-of-type" => return Ok(parse_nth_pseudo_class(input, Component::NthLastOfType)?),
|
"nth-last-of-type" => return Ok(parse_nth_pseudo_class(input, Component::NthLastOfType)?),
|
||||||
"host" => return Ok(Component::Host(Some(parse_inner_compound_selector(parser, input)?))),
|
"host" => return Ok(Component::Host(Some(parse_inner_compound_selector(parser, input)?))),
|
||||||
"not" => {
|
"not" => {
|
||||||
if inside_negation {
|
if state.intersects(SelectorParsingState::INSIDE_NEGATION) {
|
||||||
return Err(input.new_custom_error(
|
return Err(input.new_custom_error(
|
||||||
SelectorParseErrorKind::UnexpectedIdent("not".into())
|
SelectorParseErrorKind::UnexpectedIdent("not".into())
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
debug_assert!(state.is_empty());
|
||||||
return parse_negation(parser, input)
|
return parse_negation(parser, input)
|
||||||
},
|
},
|
||||||
_ => {}
|
_ => {}
|
||||||
|
@ -2064,7 +2076,7 @@ where
|
||||||
/// Returns whether the name corresponds to a CSS2 pseudo-element that
|
/// Returns whether the name corresponds to a CSS2 pseudo-element that
|
||||||
/// can be specified with the single colon syntax (in addition to the
|
/// can be specified with the single colon syntax (in addition to the
|
||||||
/// double-colon syntax, which can be used for all pseudo-elements).
|
/// double-colon syntax, which can be used for all pseudo-elements).
|
||||||
pub fn is_css2_pseudo_element(name: &str) -> bool {
|
fn is_css2_pseudo_element(name: &str) -> bool {
|
||||||
// ** Do not add to this list! **
|
// ** Do not add to this list! **
|
||||||
match_ignore_ascii_case! { name,
|
match_ignore_ascii_case! { name,
|
||||||
"before" | "after" | "first-line" | "first-letter" => true,
|
"before" | "after" | "first-line" | "first-letter" => true,
|
||||||
|
@ -2080,37 +2092,52 @@ pub fn is_css2_pseudo_element(name: &str) -> bool {
|
||||||
fn parse_one_simple_selector<'i, 't, P, Impl>(
|
fn parse_one_simple_selector<'i, 't, P, Impl>(
|
||||||
parser: &P,
|
parser: &P,
|
||||||
input: &mut CssParser<'i, 't>,
|
input: &mut CssParser<'i, 't>,
|
||||||
inside_negation: bool,
|
state: SelectorParsingState,
|
||||||
) -> Result<Option<SimpleSelectorParseResult<Impl>>, ParseError<'i, P::Error>>
|
) -> Result<Option<SimpleSelectorParseResult<Impl>>, ParseError<'i, P::Error>>
|
||||||
where
|
where
|
||||||
P: Parser<'i, Impl = Impl>,
|
P: Parser<'i, Impl = Impl>,
|
||||||
Impl: SelectorImpl,
|
Impl: SelectorImpl,
|
||||||
{
|
{
|
||||||
let start = input.state();
|
let start = input.state();
|
||||||
// FIXME: remove clone() when lifetimes are non-lexical
|
let token = match input.next_including_whitespace().map(|t| t.clone()) {
|
||||||
match input.next_including_whitespace().map(|t| t.clone()) {
|
Ok(t) => t,
|
||||||
Ok(Token::IDHash(id)) => {
|
Err(..) => {
|
||||||
let id = Component::ID(id.as_ref().into());
|
input.reset(&start);
|
||||||
Ok(Some(SimpleSelectorParseResult::SimpleSelector(id)))
|
return Ok(None);
|
||||||
},
|
},
|
||||||
Ok(Token::Delim('.')) => {
|
};
|
||||||
|
|
||||||
|
Ok(Some(match token {
|
||||||
|
Token::IDHash(id) => {
|
||||||
|
if state.intersects(SelectorParsingState::AFTER_PSEUDO) {
|
||||||
|
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
|
||||||
|
}
|
||||||
|
let id = Component::ID(id.as_ref().into());
|
||||||
|
SimpleSelectorParseResult::SimpleSelector(id)
|
||||||
|
},
|
||||||
|
Token::Delim('.') => {
|
||||||
|
if state.intersects(SelectorParsingState::AFTER_PSEUDO) {
|
||||||
|
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
|
||||||
|
}
|
||||||
let location = input.current_source_location();
|
let location = input.current_source_location();
|
||||||
match *input.next_including_whitespace()? {
|
let class = match *input.next_including_whitespace()? {
|
||||||
Token::Ident(ref class) => {
|
Token::Ident(ref class) => class,
|
||||||
let class = Component::Class(class.as_ref().into());
|
|
||||||
Ok(Some(SimpleSelectorParseResult::SimpleSelector(class)))
|
|
||||||
},
|
|
||||||
ref t => {
|
ref t => {
|
||||||
let e = SelectorParseErrorKind::ClassNeedsIdent(t.clone());
|
let e = SelectorParseErrorKind::ClassNeedsIdent(t.clone());
|
||||||
Err(location.new_custom_error(e))
|
return Err(location.new_custom_error(e));
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
let class = Component::Class(class.as_ref().into());
|
||||||
|
SimpleSelectorParseResult::SimpleSelector(class)
|
||||||
|
},
|
||||||
|
Token::SquareBracketBlock => {
|
||||||
|
if state.intersects(SelectorParsingState::AFTER_PSEUDO) {
|
||||||
|
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
|
||||||
}
|
}
|
||||||
},
|
|
||||||
Ok(Token::SquareBracketBlock) => {
|
|
||||||
let attr = input.parse_nested_block(|input| parse_attribute_selector(parser, input))?;
|
let attr = input.parse_nested_block(|input| parse_attribute_selector(parser, input))?;
|
||||||
Ok(Some(SimpleSelectorParseResult::SimpleSelector(attr)))
|
SimpleSelectorParseResult::SimpleSelector(attr)
|
||||||
},
|
},
|
||||||
Ok(Token::Colon) => {
|
Token::Colon => {
|
||||||
let location = input.current_source_location();
|
let location = input.current_source_location();
|
||||||
let (is_single_colon, next_token) = match input.next_including_whitespace()?.clone() {
|
let (is_single_colon, next_token) = match input.next_including_whitespace()?.clone() {
|
||||||
Token::Colon => (false, input.next_including_whitespace()?.clone()),
|
Token::Colon => (false, input.next_including_whitespace()?.clone()),
|
||||||
|
@ -2124,72 +2151,102 @@ where
|
||||||
return Err(input.new_custom_error(e));
|
return Err(input.new_custom_error(e));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
let is_pseudo_element =
|
let is_pseudo_element = !is_single_colon || is_css2_pseudo_element(&name);
|
||||||
!is_single_colon || P::pseudo_element_allows_single_colon(&name);
|
|
||||||
if is_pseudo_element {
|
if is_pseudo_element {
|
||||||
let parse_result = if is_functional {
|
if state.intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT) {
|
||||||
|
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
|
||||||
|
}
|
||||||
|
let pseudo_element = if is_functional {
|
||||||
|
if P::parse_part(parser) && name.eq_ignore_ascii_case("part") {
|
||||||
|
if !state.allows_part() {
|
||||||
|
return Err(
|
||||||
|
input.new_custom_error(SelectorParseErrorKind::InvalidState)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let name = input.parse_nested_block(|input| {
|
||||||
|
Ok(input.expect_ident()?.as_ref().into())
|
||||||
|
})?;
|
||||||
|
return Ok(Some(SimpleSelectorParseResult::PartPseudo(name)));
|
||||||
|
}
|
||||||
if P::parse_slotted(parser) && name.eq_ignore_ascii_case("slotted") {
|
if P::parse_slotted(parser) && name.eq_ignore_ascii_case("slotted") {
|
||||||
|
if !state.allows_slotted() {
|
||||||
|
return Err(
|
||||||
|
input.new_custom_error(SelectorParseErrorKind::InvalidState)
|
||||||
|
);
|
||||||
|
}
|
||||||
let selector = input.parse_nested_block(|input| {
|
let selector = input.parse_nested_block(|input| {
|
||||||
parse_inner_compound_selector(parser, input)
|
parse_inner_compound_selector(parser, input)
|
||||||
})?;
|
})?;
|
||||||
SimpleSelectorParseResult::SlottedPseudo(selector)
|
return Ok(Some(SimpleSelectorParseResult::SlottedPseudo(selector)));
|
||||||
} else {
|
|
||||||
let selector = input.parse_nested_block(|input| {
|
|
||||||
P::parse_functional_pseudo_element(parser, name, input)
|
|
||||||
})?;
|
|
||||||
SimpleSelectorParseResult::PseudoElement(selector)
|
|
||||||
}
|
}
|
||||||
|
input.parse_nested_block(|input| {
|
||||||
|
P::parse_functional_pseudo_element(parser, name, input)
|
||||||
|
})?
|
||||||
} else {
|
} else {
|
||||||
SimpleSelectorParseResult::PseudoElement(P::parse_pseudo_element(
|
P::parse_pseudo_element(parser, location, name)?
|
||||||
parser, location, name,
|
|
||||||
)?)
|
|
||||||
};
|
};
|
||||||
Ok(Some(parse_result))
|
|
||||||
|
if state.intersects(SelectorParsingState::AFTER_SLOTTED) &&
|
||||||
|
!pseudo_element.valid_after_slotted()
|
||||||
|
{
|
||||||
|
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
|
||||||
|
}
|
||||||
|
SimpleSelectorParseResult::PseudoElement(pseudo_element)
|
||||||
} else {
|
} else {
|
||||||
let pseudo_class = if is_functional {
|
let pseudo_class = if is_functional {
|
||||||
input.parse_nested_block(|input| {
|
input.parse_nested_block(|input| {
|
||||||
parse_functional_pseudo_class(parser, input, name, inside_negation)
|
parse_functional_pseudo_class(parser, input, name, state)
|
||||||
})?
|
})?
|
||||||
} else {
|
} else {
|
||||||
parse_simple_pseudo_class(parser, location, name)?
|
parse_simple_pseudo_class(parser, location, name, state)?
|
||||||
};
|
};
|
||||||
Ok(Some(SimpleSelectorParseResult::SimpleSelector(
|
SimpleSelectorParseResult::SimpleSelector(pseudo_class)
|
||||||
pseudo_class,
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
input.reset(&start);
|
input.reset(&start);
|
||||||
Ok(None)
|
return Ok(None);
|
||||||
},
|
},
|
||||||
}
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_simple_pseudo_class<'i, P, Impl>(
|
fn parse_simple_pseudo_class<'i, P, Impl>(
|
||||||
parser: &P,
|
parser: &P,
|
||||||
location: SourceLocation,
|
location: SourceLocation,
|
||||||
name: CowRcStr<'i>,
|
name: CowRcStr<'i>,
|
||||||
|
state: SelectorParsingState,
|
||||||
) -> Result<Component<Impl>, ParseError<'i, P::Error>>
|
) -> Result<Component<Impl>, ParseError<'i, P::Error>>
|
||||||
where
|
where
|
||||||
P: Parser<'i, Impl = Impl>,
|
P: Parser<'i, Impl = Impl>,
|
||||||
Impl: SelectorImpl,
|
Impl: SelectorImpl,
|
||||||
{
|
{
|
||||||
(match_ignore_ascii_case! { &name,
|
if !state.allows_non_functional_pseudo_classes() {
|
||||||
"first-child" => Ok(Component::FirstChild),
|
return Err(location.new_custom_error(SelectorParseErrorKind::InvalidState));
|
||||||
"last-child" => Ok(Component::LastChild),
|
}
|
||||||
"only-child" => Ok(Component::OnlyChild),
|
|
||||||
"root" => Ok(Component::Root),
|
if state.allows_tree_structural_pseudo_classes() {
|
||||||
"empty" => Ok(Component::Empty),
|
match_ignore_ascii_case! { &name,
|
||||||
"scope" => Ok(Component::Scope),
|
"first-child" => return Ok(Component::FirstChild),
|
||||||
"host" if P::parse_host(parser) => Ok(Component::Host(None)),
|
"last-child" => return Ok(Component::LastChild),
|
||||||
"first-of-type" => Ok(Component::FirstOfType),
|
"only-child" => return Ok(Component::OnlyChild),
|
||||||
"last-of-type" => Ok(Component::LastOfType),
|
"root" => return Ok(Component::Root),
|
||||||
"only-of-type" => Ok(Component::OnlyOfType),
|
"empty" => return Ok(Component::Empty),
|
||||||
_ => Err(())
|
"scope" => return Ok(Component::Scope),
|
||||||
})
|
"host" if P::parse_host(parser) => return Ok(Component::Host(None)),
|
||||||
.or_else(|()| {
|
"first-of-type" => return Ok(Component::FirstOfType),
|
||||||
P::parse_non_ts_pseudo_class(parser, location, name).map(Component::NonTSPseudoClass)
|
"last-of-type" => return Ok(Component::LastOfType),
|
||||||
})
|
"only-of-type" => return Ok(Component::OnlyOfType),
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let pseudo_class = P::parse_non_ts_pseudo_class(parser, location, name)?;
|
||||||
|
if state.intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT) &&
|
||||||
|
!pseudo_class.is_user_action_state()
|
||||||
|
{
|
||||||
|
return Err(location.new_custom_error(SelectorParseErrorKind::InvalidState));
|
||||||
|
}
|
||||||
|
Ok(Component::NonTSPseudoClass(pseudo_class))
|
||||||
}
|
}
|
||||||
|
|
||||||
// NB: pub module in order to access the DummyParser
|
// NB: pub module in order to access the DummyParser
|
||||||
|
@ -2218,11 +2275,8 @@ pub mod tests {
|
||||||
impl parser::PseudoElement for PseudoElement {
|
impl parser::PseudoElement for PseudoElement {
|
||||||
type Impl = DummySelectorImpl;
|
type Impl = DummySelectorImpl;
|
||||||
|
|
||||||
fn supports_pseudo_class(&self, pc: &PseudoClass) -> bool {
|
fn accepts_state_pseudo_classes(&self) -> bool {
|
||||||
match *pc {
|
true
|
||||||
PseudoClass::Hover => true,
|
|
||||||
PseudoClass::Active | PseudoClass::Lang(..) => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn valid_after_slotted(&self) -> bool {
|
fn valid_after_slotted(&self) -> bool {
|
||||||
|
@ -2237,6 +2291,11 @@ pub mod tests {
|
||||||
fn is_active_or_hover(&self) -> bool {
|
fn is_active_or_hover(&self) -> bool {
|
||||||
matches!(*self, PseudoClass::Active | PseudoClass::Hover)
|
matches!(*self, PseudoClass::Active | PseudoClass::Hover)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn is_user_action_state(&self) -> bool {
|
||||||
|
self.is_active_or_hover()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToCss for PseudoClass {
|
impl ToCss for PseudoClass {
|
||||||
|
@ -2302,6 +2361,7 @@ pub mod tests {
|
||||||
type AttrValue = DummyAtom;
|
type AttrValue = DummyAtom;
|
||||||
type Identifier = DummyAtom;
|
type Identifier = DummyAtom;
|
||||||
type ClassName = DummyAtom;
|
type ClassName = DummyAtom;
|
||||||
|
type PartName = DummyAtom;
|
||||||
type LocalName = DummyAtom;
|
type LocalName = DummyAtom;
|
||||||
type NamespaceUrl = DummyAtom;
|
type NamespaceUrl = DummyAtom;
|
||||||
type NamespacePrefix = DummyAtom;
|
type NamespacePrefix = DummyAtom;
|
||||||
|
@ -2340,6 +2400,10 @@ pub mod tests {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_part(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_non_ts_pseudo_class(
|
fn parse_non_ts_pseudo_class(
|
||||||
&self,
|
&self,
|
||||||
location: SourceLocation,
|
location: SourceLocation,
|
||||||
|
@ -2789,11 +2853,11 @@ pub mod tests {
|
||||||
specificity(0, 2, 1) | HAS_PSEUDO_BIT,
|
specificity(0, 2, 1) | HAS_PSEUDO_BIT,
|
||||||
)]))
|
)]))
|
||||||
);
|
);
|
||||||
assert!(parse("::before:hover:active").is_err());
|
assert!(parse("::before:hover:lang(foo)").is_err());
|
||||||
assert!(parse("::before:hover .foo").is_err());
|
assert!(parse("::before:hover .foo").is_err());
|
||||||
assert!(parse("::before .foo").is_err());
|
assert!(parse("::before .foo").is_err());
|
||||||
assert!(parse("::before ~ bar").is_err());
|
assert!(parse("::before ~ bar").is_err());
|
||||||
assert!(parse("::before:active").is_err());
|
assert!(parse("::before:active").is_ok());
|
||||||
|
|
||||||
// https://github.com/servo/servo/issues/15335
|
// https://github.com/servo/servo/issues/15335
|
||||||
assert!(parse(":: before").is_err());
|
assert!(parse(":: before").is_err());
|
||||||
|
@ -2914,6 +2978,14 @@ pub mod tests {
|
||||||
assert!(parse("::slotted(div).foo").is_err());
|
assert!(parse("::slotted(div).foo").is_err());
|
||||||
assert!(parse("::slotted(div + bar)").is_err());
|
assert!(parse("::slotted(div + bar)").is_err());
|
||||||
assert!(parse("::slotted(div) + foo").is_err());
|
assert!(parse("::slotted(div) + foo").is_err());
|
||||||
|
|
||||||
|
assert!(parse("::part()").is_err());
|
||||||
|
assert!(parse("::part(42)").is_err());
|
||||||
|
// Though note https://github.com/w3c/csswg-drafts/issues/3502
|
||||||
|
assert!(parse("::part(foo bar)").is_err());
|
||||||
|
assert!(parse("::part(foo):hover").is_ok());
|
||||||
|
assert!(parse("::part(foo) + bar").is_err());
|
||||||
|
|
||||||
assert!(parse("div ::slotted(div)").is_ok());
|
assert!(parse("div ::slotted(div)").is_ok());
|
||||||
assert!(parse("div + slot::slotted(div)").is_ok());
|
assert!(parse("div + slot::slotted(div)").is_ok());
|
||||||
assert!(parse("div + slot::slotted(div.foo)").is_ok());
|
assert!(parse("div + slot::slotted(div.foo)").is_ok());
|
||||||
|
|
|
@ -110,6 +110,8 @@ pub trait Element: Sized + Clone + Debug {
|
||||||
case_sensitivity: CaseSensitivity,
|
case_sensitivity: CaseSensitivity,
|
||||||
) -> bool;
|
) -> bool;
|
||||||
|
|
||||||
|
fn is_part(&self, name: &<Self::Impl as SelectorImpl>::PartName) -> bool;
|
||||||
|
|
||||||
/// Returns whether this element matches `:empty`.
|
/// Returns whether this element matches `:empty`.
|
||||||
///
|
///
|
||||||
/// That is, whether it does not contain any child element or any non-zero-length text node.
|
/// That is, whether it does not contain any child element or any non-zero-length text node.
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
//! A set of author stylesheets and their computed representation, such as the
|
//! A set of author stylesheets and their computed representation, such as the
|
||||||
//! ones used for ShadowRoot and XBL.
|
//! ones used for ShadowRoot.
|
||||||
|
|
||||||
use crate::context::QuirksMode;
|
use crate::context::QuirksMode;
|
||||||
use crate::dom::TElement;
|
use crate::dom::TElement;
|
||||||
|
@ -17,7 +17,7 @@ use crate::stylesheets::StylesheetInDocument;
|
||||||
use crate::stylist::CascadeData;
|
use crate::stylist::CascadeData;
|
||||||
|
|
||||||
/// A set of author stylesheets and their computed representation, such as the
|
/// A set of author stylesheets and their computed representation, such as the
|
||||||
/// ones used for ShadowRoot and XBL.
|
/// ones used for ShadowRoot.
|
||||||
#[derive(MallocSizeOf)]
|
#[derive(MallocSizeOf)]
|
||||||
pub struct AuthorStyles<S>
|
pub struct AuthorStyles<S>
|
||||||
where
|
where
|
||||||
|
@ -28,9 +28,6 @@ where
|
||||||
pub stylesheets: AuthorStylesheetSet<S>,
|
pub stylesheets: AuthorStylesheetSet<S>,
|
||||||
/// The actual cascade data computed from the stylesheets.
|
/// The actual cascade data computed from the stylesheets.
|
||||||
pub data: CascadeData,
|
pub data: CascadeData,
|
||||||
/// The quirks mode of the last stylesheet flush, used because XBL sucks and
|
|
||||||
/// we should really fix it, see bug 1406875.
|
|
||||||
pub quirks_mode: QuirksMode,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> AuthorStyles<S>
|
impl<S> AuthorStyles<S>
|
||||||
|
@ -43,7 +40,6 @@ where
|
||||||
Self {
|
Self {
|
||||||
stylesheets: AuthorStylesheetSet::new(),
|
stylesheets: AuthorStylesheetSet::new(),
|
||||||
data: CascadeData::new(),
|
data: CascadeData::new(),
|
||||||
quirks_mode: QuirksMode::NoQuirks,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,10 +61,6 @@ where
|
||||||
.stylesheets
|
.stylesheets
|
||||||
.flush::<E>(/* host = */ None, /* snapshot_map = */ None);
|
.flush::<E>(/* host = */ None, /* snapshot_map = */ None);
|
||||||
|
|
||||||
if flusher.sheets.dirty() {
|
|
||||||
self.quirks_mode = quirks_mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore OOM.
|
// Ignore OOM.
|
||||||
let _ = self
|
let _ = self
|
||||||
.data
|
.data
|
||||||
|
|
|
@ -761,7 +761,7 @@ pub trait TElement:
|
||||||
/// Returns the anonymous content for the current element's XBL binding,
|
/// Returns the anonymous content for the current element's XBL binding,
|
||||||
/// given if any.
|
/// given if any.
|
||||||
///
|
///
|
||||||
/// This is used in Gecko for XBL and shadow DOM.
|
/// This is used in Gecko for XBL.
|
||||||
fn xbl_binding_anonymous_content(&self) -> Option<Self::ConcreteNode> {
|
fn xbl_binding_anonymous_content(&self) -> Option<Self::ConcreteNode> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -772,11 +772,6 @@ pub trait TElement:
|
||||||
/// The shadow root which roots the subtree this element is contained in.
|
/// The shadow root which roots the subtree this element is contained in.
|
||||||
fn containing_shadow(&self) -> Option<<Self::ConcreteNode as TNode>::ConcreteShadowRoot>;
|
fn containing_shadow(&self) -> Option<<Self::ConcreteNode as TNode>::ConcreteShadowRoot>;
|
||||||
|
|
||||||
/// XBL hack for style sharing. :(
|
|
||||||
fn has_same_xbl_proto_binding_as(&self, _other: Self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the element which we can use to look up rules in the selector
|
/// Return the element which we can use to look up rules in the selector
|
||||||
/// maps.
|
/// maps.
|
||||||
///
|
///
|
||||||
|
@ -792,56 +787,34 @@ pub trait TElement:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implements Gecko's `nsBindingManager::WalkRules`.
|
|
||||||
///
|
|
||||||
/// Returns whether to cut off the binding inheritance, that is, whether
|
|
||||||
/// document rules should _not_ apply.
|
|
||||||
fn each_xbl_cascade_data<'a, F>(&self, _: F) -> bool
|
|
||||||
where
|
|
||||||
Self: 'a,
|
|
||||||
F: FnMut(&'a CascadeData, QuirksMode),
|
|
||||||
{
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Executes the callback for each applicable style rule data which isn't
|
/// Executes the callback for each applicable style rule data which isn't
|
||||||
/// the main document's data (which stores UA / author rules).
|
/// the main document's data (which stores UA / author rules).
|
||||||
///
|
///
|
||||||
/// The element passed to the callback is the containing shadow host for the
|
/// The element passed to the callback is the containing shadow host for the
|
||||||
/// data if it comes from Shadow DOM, None if it comes from XBL.
|
/// data if it comes from Shadow DOM.
|
||||||
///
|
///
|
||||||
/// Returns whether normal document author rules should apply.
|
/// Returns whether normal document author rules should apply.
|
||||||
fn each_applicable_non_document_style_rule_data<'a, F>(&self, mut f: F) -> bool
|
fn each_applicable_non_document_style_rule_data<'a, F>(&self, mut f: F) -> bool
|
||||||
where
|
where
|
||||||
Self: 'a,
|
Self: 'a,
|
||||||
F: FnMut(&'a CascadeData, QuirksMode, Option<Self>),
|
F: FnMut(&'a CascadeData, Self),
|
||||||
{
|
{
|
||||||
use rule_collector::containing_shadow_ignoring_svg_use;
|
use rule_collector::containing_shadow_ignoring_svg_use;
|
||||||
|
|
||||||
let mut doc_rules_apply = !self.each_xbl_cascade_data(|data, quirks_mode| {
|
let mut doc_rules_apply = self.matches_user_and_author_rules();
|
||||||
f(data, quirks_mode, None);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Use the same rules to look for the containing host as we do for rule
|
// Use the same rules to look for the containing host as we do for rule
|
||||||
// collection.
|
// collection.
|
||||||
if let Some(shadow) = containing_shadow_ignoring_svg_use(*self) {
|
if let Some(shadow) = containing_shadow_ignoring_svg_use(*self) {
|
||||||
doc_rules_apply = false;
|
doc_rules_apply = false;
|
||||||
if let Some(data) = shadow.style_data() {
|
if let Some(data) = shadow.style_data() {
|
||||||
f(
|
f(data, shadow.host());
|
||||||
data,
|
|
||||||
self.as_node().owner_doc().quirks_mode(),
|
|
||||||
Some(shadow.host()),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(shadow) = self.shadow_root() {
|
if let Some(shadow) = self.shadow_root() {
|
||||||
if let Some(data) = shadow.style_data() {
|
if let Some(data) = shadow.style_data() {
|
||||||
f(
|
f(data, shadow.host());
|
||||||
data,
|
|
||||||
self.as_node().owner_doc().quirks_mode(),
|
|
||||||
Some(shadow.host()),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -850,11 +823,7 @@ pub trait TElement:
|
||||||
// Slots can only have assigned nodes when in a shadow tree.
|
// Slots can only have assigned nodes when in a shadow tree.
|
||||||
let shadow = slot.containing_shadow().unwrap();
|
let shadow = slot.containing_shadow().unwrap();
|
||||||
if let Some(data) = shadow.style_data() {
|
if let Some(data) = shadow.style_data() {
|
||||||
f(
|
f(data, shadow.host());
|
||||||
data,
|
|
||||||
self.as_node().owner_doc().quirks_mode(),
|
|
||||||
Some(shadow.host()),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
current = slot.assigned_slot();
|
current = slot.assigned_slot();
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,18 +13,17 @@
|
||||||
use crate::gecko::values::GeckoStyleCoordConvertible;
|
use crate::gecko::values::GeckoStyleCoordConvertible;
|
||||||
use crate::gecko_bindings::bindings;
|
use crate::gecko_bindings::bindings;
|
||||||
use crate::gecko_bindings::structs::{self, nsStyleCoord_CalcValue, Matrix4x4Components};
|
use crate::gecko_bindings::structs::{self, nsStyleCoord_CalcValue, Matrix4x4Components};
|
||||||
use crate::gecko_bindings::structs::{nsStyleImage, nsresult, SheetType};
|
use crate::gecko_bindings::structs::{nsStyleImage, nsresult};
|
||||||
use crate::gecko_bindings::sugar::ns_style_coord::{CoordData, CoordDataMut, CoordDataValue};
|
use crate::gecko_bindings::sugar::ns_style_coord::{CoordData, CoordDataMut, CoordDataValue};
|
||||||
use crate::stylesheets::{Origin, RulesMutateError};
|
use crate::stylesheets::RulesMutateError;
|
||||||
use crate::values::computed::image::LineDirection;
|
use crate::values::computed::image::LineDirection;
|
||||||
use crate::values::computed::transform::Matrix3D;
|
use crate::values::computed::transform::Matrix3D;
|
||||||
use crate::values::computed::url::ComputedImageUrl;
|
use crate::values::computed::url::ComputedImageUrl;
|
||||||
use crate::values::computed::{Angle, Gradient, Image};
|
use crate::values::computed::{Angle, Gradient, Image};
|
||||||
use crate::values::computed::{Integer, LengthPercentage};
|
use crate::values::computed::{Integer, LengthPercentage};
|
||||||
use crate::values::computed::{Length, Percentage, TextAlign};
|
use crate::values::computed::{Length, Percentage, TextAlign};
|
||||||
use crate::values::generics::box_::VerticalAlign;
|
|
||||||
use crate::values::generics::grid::{TrackListValue, TrackSize};
|
use crate::values::generics::grid::{TrackListValue, TrackSize};
|
||||||
use crate::values::generics::image::{CompatMode, GradientItem, Image as GenericImage};
|
use crate::values::generics::image::{CompatMode, Image as GenericImage};
|
||||||
use crate::values::generics::rect::Rect;
|
use crate::values::generics::rect::Rect;
|
||||||
use crate::Zero;
|
use crate::Zero;
|
||||||
use app_units::Au;
|
use app_units::Au;
|
||||||
|
@ -154,7 +153,6 @@ impl nsStyleImage {
|
||||||
|
|
||||||
// FIXME(emilio): This is really complex, we should use cbindgen for this.
|
// FIXME(emilio): This is really complex, we should use cbindgen for this.
|
||||||
fn set_gradient(&mut self, gradient: Gradient) {
|
fn set_gradient(&mut self, gradient: Gradient) {
|
||||||
use self::structs::nsStyleCoord;
|
|
||||||
use self::structs::NS_STYLE_GRADIENT_SIZE_CLOSEST_CORNER as CLOSEST_CORNER;
|
use self::structs::NS_STYLE_GRADIENT_SIZE_CLOSEST_CORNER as CLOSEST_CORNER;
|
||||||
use self::structs::NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE as CLOSEST_SIDE;
|
use self::structs::NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE as CLOSEST_SIDE;
|
||||||
use self::structs::NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER as FARTHEST_CORNER;
|
use self::structs::NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER as FARTHEST_CORNER;
|
||||||
|
@ -329,26 +327,9 @@ impl nsStyleImage {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
for (index, item) in gradient.items.iter().enumerate() {
|
for (index, item) in gradient.items.into_iter().enumerate() {
|
||||||
// NB: stops are guaranteed to be none in the gecko side by
|
|
||||||
// default.
|
|
||||||
|
|
||||||
let gecko_stop = unsafe { &mut (*gecko_gradient).mStops[index] };
|
let gecko_stop = unsafe { &mut (*gecko_gradient).mStops[index] };
|
||||||
let mut coord = nsStyleCoord::null();
|
*gecko_stop = item;
|
||||||
|
|
||||||
match *item {
|
|
||||||
GradientItem::ColorStop(ref stop) => {
|
|
||||||
gecko_stop.mColor = stop.color.into();
|
|
||||||
gecko_stop.mIsInterpolationHint = false;
|
|
||||||
coord.set(stop.position);
|
|
||||||
},
|
|
||||||
GradientItem::InterpolationHint(hint) => {
|
|
||||||
gecko_stop.mIsInterpolationHint = true;
|
|
||||||
coord.set(Some(hint));
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
gecko_stop.mLocation.move_from(coord);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -419,7 +400,7 @@ impl nsStyleImage {
|
||||||
use self::structs::NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER as FARTHEST_CORNER;
|
use self::structs::NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER as FARTHEST_CORNER;
|
||||||
use self::structs::NS_STYLE_GRADIENT_SIZE_FARTHEST_SIDE as FARTHEST_SIDE;
|
use self::structs::NS_STYLE_GRADIENT_SIZE_FARTHEST_SIDE as FARTHEST_SIDE;
|
||||||
use crate::values::computed::position::Position;
|
use crate::values::computed::position::Position;
|
||||||
use crate::values::generics::image::{Circle, ColorStop, Ellipse};
|
use crate::values::generics::image::{Circle, Ellipse};
|
||||||
use crate::values::generics::image::{EndingShape, GradientKind, ShapeExtent};
|
use crate::values::generics::image::{EndingShape, GradientKind, ShapeExtent};
|
||||||
|
|
||||||
let gecko_gradient = bindings::Gecko_GetGradientImageValue(self)
|
let gecko_gradient = bindings::Gecko_GetGradientImageValue(self)
|
||||||
|
@ -531,24 +512,7 @@ impl nsStyleImage {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let items = gecko_gradient
|
let items = gecko_gradient.mStops.iter().cloned().collect();
|
||||||
.mStops
|
|
||||||
.iter()
|
|
||||||
.map(|ref stop| {
|
|
||||||
if stop.mIsInterpolationHint {
|
|
||||||
GradientItem::InterpolationHint(
|
|
||||||
LengthPercentage::from_gecko_style_coord(&stop.mLocation)
|
|
||||||
.expect("mLocation could not convert to LengthPercentage"),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
GradientItem::ColorStop(ColorStop {
|
|
||||||
color: stop.mColor.into(),
|
|
||||||
position: LengthPercentage::from_gecko_style_coord(&stop.mLocation),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let compat_mode = if gecko_gradient.mMozLegacySyntax {
|
let compat_mode = if gecko_gradient.mMozLegacySyntax {
|
||||||
CompatMode::Moz
|
CompatMode::Moz
|
||||||
} else if gecko_gradient.mLegacySyntax {
|
} else if gecko_gradient.mLegacySyntax {
|
||||||
|
@ -818,16 +782,6 @@ impl From<RulesMutateError> for nsresult {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Origin> for SheetType {
|
|
||||||
fn from(other: Origin) -> Self {
|
|
||||||
match other {
|
|
||||||
Origin::UserAgent => SheetType::Agent,
|
|
||||||
Origin::Author => SheetType::Doc,
|
|
||||||
Origin::User => SheetType::User,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TrackSize<LengthPercentage> {
|
impl TrackSize<LengthPercentage> {
|
||||||
/// Return TrackSize from given two nsStyleCoord
|
/// Return TrackSize from given two nsStyleCoord
|
||||||
pub fn from_gecko_style_coords<T: CoordData>(gecko_min: &T, gecko_max: &T) -> Self {
|
pub fn from_gecko_style_coords<T: CoordData>(gecko_min: &T, gecko_max: &T) -> Self {
|
||||||
|
@ -920,26 +874,6 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<L> VerticalAlign<L> {
|
|
||||||
/// Converts an enumerated value coming from Gecko to a `VerticalAlign<L>`.
|
|
||||||
pub fn from_gecko_keyword(value: u32) -> Self {
|
|
||||||
match value {
|
|
||||||
structs::NS_STYLE_VERTICAL_ALIGN_BASELINE => VerticalAlign::Baseline,
|
|
||||||
structs::NS_STYLE_VERTICAL_ALIGN_SUB => VerticalAlign::Sub,
|
|
||||||
structs::NS_STYLE_VERTICAL_ALIGN_SUPER => VerticalAlign::Super,
|
|
||||||
structs::NS_STYLE_VERTICAL_ALIGN_TOP => VerticalAlign::Top,
|
|
||||||
structs::NS_STYLE_VERTICAL_ALIGN_TEXT_TOP => VerticalAlign::TextTop,
|
|
||||||
structs::NS_STYLE_VERTICAL_ALIGN_MIDDLE => VerticalAlign::Middle,
|
|
||||||
structs::NS_STYLE_VERTICAL_ALIGN_BOTTOM => VerticalAlign::Bottom,
|
|
||||||
structs::NS_STYLE_VERTICAL_ALIGN_TEXT_BOTTOM => VerticalAlign::TextBottom,
|
|
||||||
structs::NS_STYLE_VERTICAL_ALIGN_MIDDLE_WITH_BASELINE => {
|
|
||||||
VerticalAlign::MozMiddleWithBaseline
|
|
||||||
},
|
|
||||||
_ => panic!("unexpected enumerated value for vertical-align"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TextAlign {
|
impl TextAlign {
|
||||||
/// Obtain a specified value from a Gecko keyword value
|
/// Obtain a specified value from a Gecko keyword value
|
||||||
///
|
///
|
||||||
|
|
|
@ -145,11 +145,6 @@ impl PerDocumentStyleData {
|
||||||
/// Create a `PerDocumentStyleData`.
|
/// Create a `PerDocumentStyleData`.
|
||||||
pub fn new(document: *const structs::Document) -> Self {
|
pub fn new(document: *const structs::Document) -> Self {
|
||||||
let device = Device::new(document);
|
let device = Device::new(document);
|
||||||
|
|
||||||
// FIXME(emilio, tlin): How is this supposed to work with XBL? This is
|
|
||||||
// right now not always honored, see bug 1405543...
|
|
||||||
//
|
|
||||||
// Should we just force XBL Stylists to be NoQuirks?
|
|
||||||
let quirks_mode = device.document().mCompatMode;
|
let quirks_mode = device.document().mCompatMode;
|
||||||
|
|
||||||
PerDocumentStyleData(AtomicRefCell::new(PerDocumentStyleDataImpl {
|
PerDocumentStyleData(AtomicRefCell::new(PerDocumentStyleDataImpl {
|
||||||
|
|
|
@ -169,7 +169,6 @@ impl Device {
|
||||||
self.document()
|
self.document()
|
||||||
.mPresShell
|
.mPresShell
|
||||||
.as_ref()?
|
.as_ref()?
|
||||||
._base
|
|
||||||
.mPresContext
|
.mPresContext
|
||||||
.mRawPtr
|
.mRawPtr
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
use crate::gecko_bindings::structs::{self, PseudoStyleType};
|
use crate::gecko_bindings::structs::{self, PseudoStyleType};
|
||||||
use crate::properties::longhands::display::computed_value::T as Display;
|
use crate::properties::longhands::display::computed_value::T as Display;
|
||||||
use crate::properties::{ComputedValues, PropertyFlags};
|
use crate::properties::{ComputedValues, PropertyFlags};
|
||||||
use crate::selector_parser::{NonTSPseudoClass, PseudoElementCascadeType, SelectorImpl};
|
use crate::selector_parser::{PseudoElementCascadeType, SelectorImpl};
|
||||||
use crate::str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase};
|
use crate::str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase};
|
||||||
use crate::string_cache::Atom;
|
use crate::string_cache::Atom;
|
||||||
use crate::values::serialize_atom_identifier;
|
use crate::values::serialize_atom_identifier;
|
||||||
|
@ -30,6 +30,7 @@ impl ::selectors::parser::PseudoElement for PseudoElement {
|
||||||
// ::slotted() should support all tree-abiding pseudo-elements, see
|
// ::slotted() should support all tree-abiding pseudo-elements, see
|
||||||
// https://drafts.csswg.org/css-scoping/#slotted-pseudo
|
// https://drafts.csswg.org/css-scoping/#slotted-pseudo
|
||||||
// https://drafts.csswg.org/css-pseudo-4/#treelike
|
// https://drafts.csswg.org/css-pseudo-4/#treelike
|
||||||
|
#[inline]
|
||||||
fn valid_after_slotted(&self) -> bool {
|
fn valid_after_slotted(&self) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
*self,
|
*self,
|
||||||
|
@ -40,12 +41,9 @@ impl ::selectors::parser::PseudoElement for PseudoElement {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn supports_pseudo_class(&self, pseudo_class: &NonTSPseudoClass) -> bool {
|
#[inline]
|
||||||
if !self.supports_user_action_state() {
|
fn accepts_state_pseudo_classes(&self) -> bool {
|
||||||
return false;
|
self.supports_user_action_state()
|
||||||
}
|
|
||||||
|
|
||||||
return pseudo_class.is_safe_user_action_state();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -184,16 +184,6 @@ impl NonTSPseudoClass {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <https://drafts.csswg.org/selectors-4/#useraction-pseudos>
|
|
||||||
///
|
|
||||||
/// We intentionally skip the link-related ones.
|
|
||||||
pub fn is_safe_user_action_state(&self) -> bool {
|
|
||||||
matches!(
|
|
||||||
*self,
|
|
||||||
NonTSPseudoClass::Hover | NonTSPseudoClass::Active | NonTSPseudoClass::Focus
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the state flag associated with a pseudo-class, if any.
|
/// Get the state flag associated with a pseudo-class, if any.
|
||||||
pub fn state_flag(&self) -> ElementState {
|
pub fn state_flag(&self) -> ElementState {
|
||||||
macro_rules! flag {
|
macro_rules! flag {
|
||||||
|
@ -279,6 +269,15 @@ impl ::selectors::parser::NonTSPseudoClass for NonTSPseudoClass {
|
||||||
fn is_active_or_hover(&self) -> bool {
|
fn is_active_or_hover(&self) -> bool {
|
||||||
matches!(*self, NonTSPseudoClass::Active | NonTSPseudoClass::Hover)
|
matches!(*self, NonTSPseudoClass::Active | NonTSPseudoClass::Hover)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// We intentionally skip the link-related ones.
|
||||||
|
#[inline]
|
||||||
|
fn is_user_action_state(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
*self,
|
||||||
|
NonTSPseudoClass::Hover | NonTSPseudoClass::Active | NonTSPseudoClass::Focus
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The dummy struct we use to implement our selector parsing.
|
/// The dummy struct we use to implement our selector parsing.
|
||||||
|
@ -290,6 +289,7 @@ impl ::selectors::SelectorImpl for SelectorImpl {
|
||||||
type AttrValue = Atom;
|
type AttrValue = Atom;
|
||||||
type Identifier = Atom;
|
type Identifier = Atom;
|
||||||
type ClassName = Atom;
|
type ClassName = Atom;
|
||||||
|
type PartName = Atom;
|
||||||
type LocalName = Atom;
|
type LocalName = Atom;
|
||||||
type NamespacePrefix = Atom;
|
type NamespacePrefix = Atom;
|
||||||
type NamespaceUrl = Namespace;
|
type NamespaceUrl = Namespace;
|
||||||
|
@ -352,11 +352,6 @@ impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> {
|
||||||
self.parse_slotted()
|
self.parse_slotted()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pseudo_element_allows_single_colon(name: &str) -> bool {
|
|
||||||
// FIXME: -moz-tree check should probably be ascii-case-insensitive.
|
|
||||||
::selectors::parser::is_css2_pseudo_element(name) || name.starts_with("-moz-tree-")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_non_ts_pseudo_class(
|
fn parse_non_ts_pseudo_class(
|
||||||
&self,
|
&self,
|
||||||
location: SourceLocation,
|
location: SourceLocation,
|
||||||
|
|
|
@ -183,13 +183,23 @@ impl ElementSnapshot for GeckoElementSnapshot {
|
||||||
snapshot_helpers::get_id(&*self.mAttrs)
|
snapshot_helpers::get_id(&*self.mAttrs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn is_part(&self, name: &Atom) -> bool {
|
||||||
|
let attr = match snapshot_helpers::find_attr(&*self.mAttrs, &atom!("part")) {
|
||||||
|
Some(attr) => attr,
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
|
||||||
|
snapshot_helpers::has_class_or_part(name, CaseSensitivity::CaseSensitive, attr)
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
|
fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
|
||||||
if !self.has_any(Flags::MaybeClass) {
|
if !self.has_any(Flags::MaybeClass) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
snapshot_helpers::has_class(name, case_sensitivity, &self.mClass)
|
snapshot_helpers::has_class_or_part(name, case_sensitivity, &self.mClass)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
|
@ -29,7 +29,7 @@ unsafe fn ptr<T>(attr: &structs::nsAttrValue) -> *const T {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
unsafe fn get_class_from_attr(attr: &structs::nsAttrValue) -> Class {
|
unsafe fn get_class_or_part_from_attr(attr: &structs::nsAttrValue) -> Class {
|
||||||
debug_assert!(bindings::Gecko_AssertClassAttrValueIsSane(attr));
|
debug_assert!(bindings::Gecko_AssertClassAttrValueIsSane(attr));
|
||||||
let base_type = base_type(attr);
|
let base_type = base_type(attr);
|
||||||
if base_type == structs::nsAttrValue_ValueBaseType_eStringBase {
|
if base_type == structs::nsAttrValue_ValueBaseType_eStringBase {
|
||||||
|
@ -82,15 +82,15 @@ pub fn get_id(attrs: &[structs::AttrArray_InternalAttr]) -> Option<&WeakAtom> {
|
||||||
Some(unsafe { get_id_from_attr(find_attr(attrs, &atom!("id"))?) })
|
Some(unsafe { get_id_from_attr(find_attr(attrs, &atom!("id"))?) })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Given a class name, a case sensitivity, and an array of attributes, returns
|
/// Given a class or part name, a case sensitivity, and an array of attributes,
|
||||||
/// whether the class has the attribute that name represents.
|
/// returns whether the attribute has that name.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn has_class(
|
pub fn has_class_or_part(
|
||||||
name: &Atom,
|
name: &Atom,
|
||||||
case_sensitivity: CaseSensitivity,
|
case_sensitivity: CaseSensitivity,
|
||||||
attr: &structs::nsAttrValue,
|
attr: &structs::nsAttrValue,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
match unsafe { get_class_from_attr(attr) } {
|
match unsafe { get_class_or_part_from_attr(attr) } {
|
||||||
Class::None => false,
|
Class::None => false,
|
||||||
Class::One(atom) => unsafe { case_sensitivity.eq_atom(name, WeakAtom::new(atom)) },
|
Class::One(atom) => unsafe { case_sensitivity.eq_atom(name, WeakAtom::new(atom)) },
|
||||||
Class::More(atoms) => match case_sensitivity {
|
Class::More(atoms) => match case_sensitivity {
|
||||||
|
@ -114,7 +114,7 @@ where
|
||||||
F: FnMut(&Atom),
|
F: FnMut(&Atom),
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
match get_class_from_attr(attr) {
|
match get_class_or_part_from_attr(attr) {
|
||||||
Class::None => {},
|
Class::None => {},
|
||||||
Class::One(atom) => Atom::with(atom, callback),
|
Class::One(atom) => Atom::with(atom, callback),
|
||||||
Class::More(atoms) => {
|
Class::More(atoms) => {
|
||||||
|
|
|
@ -172,15 +172,7 @@ impl<'lr> TShadowRoot for GeckoShadowRoot<'lr> {
|
||||||
Self: 'a,
|
Self: 'a,
|
||||||
{
|
{
|
||||||
let author_styles = unsafe { self.0.mServoStyles.mPtr.as_ref()? };
|
let author_styles = unsafe { self.0.mServoStyles.mPtr.as_ref()? };
|
||||||
|
|
||||||
let author_styles = AuthorStyles::<GeckoStyleSheet>::from_ffi(author_styles);
|
let author_styles = AuthorStyles::<GeckoStyleSheet>::from_ffi(author_styles);
|
||||||
|
|
||||||
debug_assert!(
|
|
||||||
author_styles.quirks_mode == self.as_node().owner_doc().quirks_mode() ||
|
|
||||||
author_styles.stylesheets.is_empty() ||
|
|
||||||
author_styles.stylesheets.dirty()
|
|
||||||
);
|
|
||||||
|
|
||||||
Some(&author_styles.data)
|
Some(&author_styles.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -536,11 +528,6 @@ impl<'lb> GeckoXBLBinding<'lb> {
|
||||||
self.0.mContent.raw::<nsIContent>()
|
self.0.mContent.raw::<nsIContent>()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn inherits_style(&self) -> bool {
|
|
||||||
unsafe { bindings::Gecko_XBLBinding_InheritsStyle(self.0) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// This duplicates the logic in Gecko's
|
// This duplicates the logic in Gecko's
|
||||||
// nsBindingManager::GetBindingWithContent.
|
// nsBindingManager::GetBindingWithContent.
|
||||||
fn binding_with_content(&self) -> Option<Self> {
|
fn binding_with_content(&self) -> Option<Self> {
|
||||||
|
@ -552,22 +539,6 @@ impl<'lb> GeckoXBLBinding<'lb> {
|
||||||
binding = binding.base_binding()?;
|
binding = binding.base_binding()?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn each_xbl_cascade_data<F>(&self, f: &mut F)
|
|
||||||
where
|
|
||||||
F: FnMut(&'lb CascadeData, QuirksMode),
|
|
||||||
{
|
|
||||||
if let Some(base) = self.base_binding() {
|
|
||||||
base.each_xbl_cascade_data(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
let data = unsafe { bindings::Gecko_XBLBinding_GetRawServoStyles(self.0).as_ref() };
|
|
||||||
|
|
||||||
if let Some(data) = data {
|
|
||||||
let data: &'lb _ = AuthorStyles::<GeckoStyleSheet>::from_ffi(data);
|
|
||||||
f(&data.data, data.quirks_mode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A simple wrapper over a non-null Gecko `Element` pointer.
|
/// A simple wrapper over a non-null Gecko `Element` pointer.
|
||||||
|
@ -602,6 +573,11 @@ impl<'le> GeckoElement<'le> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn get_part_attr(&self) -> Option<&structs::nsAttrValue> {
|
||||||
|
snapshot_helpers::find_attr(self.attrs(), &atom!("part"))
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn get_class_attr(&self) -> Option<&structs::nsAttrValue> {
|
fn get_class_attr(&self) -> Option<&structs::nsAttrValue> {
|
||||||
if !self.may_have_class() {
|
if !self.may_have_class() {
|
||||||
|
@ -894,10 +870,11 @@ impl<'le> GeckoElement<'le> {
|
||||||
if !self.as_node().is_in_shadow_tree() {
|
if !self.as_node().is_in_shadow_tree() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
match self.containing_shadow_host() {
|
if !self.parent_node_is_shadow_root() {
|
||||||
Some(e) => e.is_svg_element() && e.local_name() == &*local_name!("use"),
|
return false;
|
||||||
None => false,
|
|
||||||
}
|
}
|
||||||
|
let host = self.containing_shadow_host().unwrap();
|
||||||
|
host.is_svg_element() && host.local_name() == &*local_name!("use")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn css_transitions_info(&self) -> FxHashMap<LonghandId, Arc<AnimationValue>> {
|
fn css_transitions_info(&self) -> FxHashMap<LonghandId, Arc<AnimationValue>> {
|
||||||
|
@ -1250,14 +1227,6 @@ impl<'le> TElement for GeckoElement<'le> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_same_xbl_proto_binding_as(&self, other: Self) -> bool {
|
|
||||||
match (self.xbl_binding(), other.xbl_binding()) {
|
|
||||||
(None, None) => true,
|
|
||||||
(Some(a), Some(b)) => a.0.mPrototypeBinding == b.0.mPrototypeBinding,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn each_anonymous_content_child<F>(&self, mut f: F)
|
fn each_anonymous_content_child<F>(&self, mut f: F)
|
||||||
where
|
where
|
||||||
F: FnMut(Self),
|
F: FnMut(Self),
|
||||||
|
@ -1436,7 +1405,7 @@ impl<'le> TElement for GeckoElement<'le> {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn matches_user_and_author_rules(&self) -> bool {
|
fn matches_user_and_author_rules(&self) -> bool {
|
||||||
!self.is_in_native_anonymous_subtree()
|
!self.rule_hash_target().is_in_native_anonymous_subtree()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -1599,43 +1568,6 @@ impl<'le> TElement for GeckoElement<'le> {
|
||||||
self.may_have_animations() && unsafe { Gecko_ElementHasCSSTransitions(self.0) }
|
self.may_have_animations() && unsafe { Gecko_ElementHasCSSTransitions(self.0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn each_xbl_cascade_data<'a, F>(&self, mut f: F) -> bool
|
|
||||||
where
|
|
||||||
'le: 'a,
|
|
||||||
F: FnMut(&'a CascadeData, QuirksMode),
|
|
||||||
{
|
|
||||||
// Walk the binding scope chain, starting with the binding attached to
|
|
||||||
// our content, up till we run out of scopes or we get cut off.
|
|
||||||
//
|
|
||||||
// If we are a NAC pseudo-element, we want to get rules from our
|
|
||||||
// rule_hash_target, that is, our originating element.
|
|
||||||
let mut current = Some(self.rule_hash_target());
|
|
||||||
while let Some(element) = current {
|
|
||||||
if let Some(binding) = element.xbl_binding() {
|
|
||||||
binding.each_xbl_cascade_data(&mut f);
|
|
||||||
|
|
||||||
// If we're not looking at our original element, allow the
|
|
||||||
// binding to cut off style inheritance.
|
|
||||||
if element != *self && !binding.inherits_style() {
|
|
||||||
// Go no further; we're not inheriting style from
|
|
||||||
// anything above here.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if element.is_root_of_native_anonymous_subtree() {
|
|
||||||
// Deliberately cut off style inheritance here.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
current = element.xbl_binding_parent();
|
|
||||||
}
|
|
||||||
|
|
||||||
// If current has something, this means we cut off inheritance at some
|
|
||||||
// point in the loop.
|
|
||||||
current.is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn xbl_binding_anonymous_content(&self) -> Option<GeckoNode<'le>> {
|
fn xbl_binding_anonymous_content(&self) -> Option<GeckoNode<'le>> {
|
||||||
self.xbl_binding_with_content()
|
self.xbl_binding_with_content()
|
||||||
.map(|b| unsafe { GeckoNode::from_content(&*b.anon_content()) })
|
.map(|b| unsafe { GeckoNode::from_content(&*b.anon_content()) })
|
||||||
|
@ -2332,6 +2264,16 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
|
||||||
case_sensitivity.eq_atom(element_id, id)
|
case_sensitivity.eq_atom(element_id, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn is_part(&self, name: &Atom) -> bool {
|
||||||
|
let attr = match self.get_part_attr() {
|
||||||
|
Some(c) => c,
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
|
||||||
|
snapshot_helpers::has_class_or_part(name, CaseSensitivity::CaseSensitive, attr)
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
|
fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
|
||||||
let attr = match self.get_class_attr() {
|
let attr = match self.get_class_attr() {
|
||||||
|
@ -2339,7 +2281,7 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
|
||||||
None => return false,
|
None => return false,
|
||||||
};
|
};
|
||||||
|
|
||||||
snapshot_helpers::has_class(name, case_sensitivity, attr)
|
snapshot_helpers::has_class_or_part(name, case_sensitivity, attr)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
|
@ -58,6 +58,10 @@ pub trait ElementSnapshot: Sized {
|
||||||
/// if `has_attrs()` returns true.
|
/// if `has_attrs()` returns true.
|
||||||
fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool;
|
fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool;
|
||||||
|
|
||||||
|
/// Whether this snapshot represents the part named `name`. Should only be
|
||||||
|
/// called if `has_attrs()` returns true.
|
||||||
|
fn is_part(&self, name: &Atom) -> bool;
|
||||||
|
|
||||||
/// A callback that should be called for each class of the snapshot. Should
|
/// A callback that should be called for each class of the snapshot. Should
|
||||||
/// only be called if `has_attrs()` returns true.
|
/// only be called if `has_attrs()` returns true.
|
||||||
fn each_class<F>(&self, _: F)
|
fn each_class<F>(&self, _: F)
|
||||||
|
@ -340,6 +344,13 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_part(&self, name: &Atom) -> bool {
|
||||||
|
match self.snapshot() {
|
||||||
|
Some(snapshot) if snapshot.has_attrs() => snapshot.is_part(name),
|
||||||
|
_ => self.element.is_part(name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
|
fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
|
||||||
match self.snapshot() {
|
match self.snapshot() {
|
||||||
Some(snapshot) if snapshot.has_attrs() => snapshot.has_class(name, case_sensitivity),
|
Some(snapshot) if snapshot.has_attrs() => snapshot.has_class(name, case_sensitivity),
|
||||||
|
|
|
@ -98,6 +98,7 @@ impl Dependency {
|
||||||
// an eager pseudo, and return only Descendants here if not.
|
// an eager pseudo, and return only Descendants here if not.
|
||||||
Some(Combinator::PseudoElement) => DependencyInvalidationKind::ElementAndDescendants,
|
Some(Combinator::PseudoElement) => DependencyInvalidationKind::ElementAndDescendants,
|
||||||
Some(Combinator::SlotAssignment) => DependencyInvalidationKind::SlottedElements,
|
Some(Combinator::SlotAssignment) => DependencyInvalidationKind::SlottedElements,
|
||||||
|
Some(Combinator::Part) => unimplemented!("Need to add invalidation for shadow parts"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -158,7 +158,10 @@ impl<'a> Invalidation<'a> {
|
||||||
// We should be able to do better here!
|
// We should be able to do better here!
|
||||||
match self.selector.combinator_at_parse_order(self.offset - 1) {
|
match self.selector.combinator_at_parse_order(self.offset - 1) {
|
||||||
Combinator::Descendant | Combinator::LaterSibling | Combinator::PseudoElement => true,
|
Combinator::Descendant | Combinator::LaterSibling | Combinator::PseudoElement => true,
|
||||||
Combinator::SlotAssignment | Combinator::NextSibling | Combinator::Child => false,
|
Combinator::Part |
|
||||||
|
Combinator::SlotAssignment |
|
||||||
|
Combinator::NextSibling |
|
||||||
|
Combinator::Child => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,6 +174,9 @@ impl<'a> Invalidation<'a> {
|
||||||
Combinator::Child | Combinator::Descendant | Combinator::PseudoElement => {
|
Combinator::Child | Combinator::Descendant | Combinator::PseudoElement => {
|
||||||
InvalidationKind::Descendant(DescendantInvalidationKind::Dom)
|
InvalidationKind::Descendant(DescendantInvalidationKind::Dom)
|
||||||
},
|
},
|
||||||
|
Combinator::Part => {
|
||||||
|
unimplemented!("Need to add invalidation for shadow parts");
|
||||||
|
},
|
||||||
Combinator::SlotAssignment => {
|
Combinator::SlotAssignment => {
|
||||||
InvalidationKind::Descendant(DescendantInvalidationKind::Slotted)
|
InvalidationKind::Descendant(DescendantInvalidationKind::Slotted)
|
||||||
},
|
},
|
||||||
|
|
|
@ -224,8 +224,8 @@ where
|
||||||
|
|
||||||
let mut shadow_rule_datas = SmallVec::<[_; 3]>::new();
|
let mut shadow_rule_datas = SmallVec::<[_; 3]>::new();
|
||||||
let matches_document_author_rules =
|
let matches_document_author_rules =
|
||||||
element.each_applicable_non_document_style_rule_data(|data, quirks_mode, host| {
|
element.each_applicable_non_document_style_rule_data(|data, host| {
|
||||||
shadow_rule_datas.push((data, quirks_mode, host.map(|h| h.opaque())))
|
shadow_rule_datas.push((data, host.opaque()))
|
||||||
});
|
});
|
||||||
|
|
||||||
let invalidated_self = {
|
let invalidated_self = {
|
||||||
|
@ -258,12 +258,8 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for &(ref data, quirks_mode, ref host) in &shadow_rule_datas {
|
for &(ref data, ref host) in &shadow_rule_datas {
|
||||||
// FIXME(emilio): Replace with assert / remove when we figure
|
collector.matching_context.current_host = Some(host.clone());
|
||||||
// out what to do with the quirks mode mismatches
|
|
||||||
// (that is, when bug 1406875 is properly fixed).
|
|
||||||
collector.matching_context.set_quirks_mode(quirks_mode);
|
|
||||||
collector.matching_context.current_host = host.clone();
|
|
||||||
collector.collect_dependencies_in_invalidation_map(data.invalidation_map());
|
collector.collect_dependencies_in_invalidation_map(data.invalidation_map());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -347,6 +347,7 @@ class Longhand(object):
|
||||||
"TextAlign",
|
"TextAlign",
|
||||||
"TextDecorationLine",
|
"TextDecorationLine",
|
||||||
"TextEmphasisPosition",
|
"TextEmphasisPosition",
|
||||||
|
"TextTransform",
|
||||||
"TouchAction",
|
"TouchAction",
|
||||||
"TransformStyle",
|
"TransformStyle",
|
||||||
"UserSelect",
|
"UserSelect",
|
||||||
|
@ -399,8 +400,6 @@ class Shorthand(object):
|
||||||
and allowed_in_keyframe_block != "False"
|
and allowed_in_keyframe_block != "False"
|
||||||
|
|
||||||
def get_animatable(self):
|
def get_animatable(self):
|
||||||
if self.ident == "all":
|
|
||||||
return False
|
|
||||||
for sub in self.sub_properties:
|
for sub in self.sub_properties:
|
||||||
if sub.animatable:
|
if sub.animatable:
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -38,7 +38,7 @@ use crate::gecko_bindings::bindings::{Gecko_ResetFilters, Gecko_CopyFiltersFrom}
|
||||||
use crate::gecko_bindings::structs;
|
use crate::gecko_bindings::structs;
|
||||||
use crate::gecko_bindings::structs::nsCSSPropertyID;
|
use crate::gecko_bindings::structs::nsCSSPropertyID;
|
||||||
use crate::gecko_bindings::structs::mozilla::PseudoStyleType;
|
use crate::gecko_bindings::structs::mozilla::PseudoStyleType;
|
||||||
use crate::gecko_bindings::sugar::ns_style_coord::{CoordDataValue, CoordData, CoordDataMut};
|
use crate::gecko_bindings::sugar::ns_style_coord::{CoordDataValue, CoordDataMut};
|
||||||
use crate::gecko_bindings::sugar::refptr::RefPtr;
|
use crate::gecko_bindings::sugar::refptr::RefPtr;
|
||||||
use crate::gecko::values::GeckoStyleCoordConvertible;
|
use crate::gecko::values::GeckoStyleCoordConvertible;
|
||||||
use crate::gecko::values::round_border_to_device_pixels;
|
use crate::gecko::values::round_border_to_device_pixels;
|
||||||
|
@ -1104,9 +1104,6 @@ impl ${style_struct.gecko_struct_name} {
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
pub fn get_gecko(&self) -> &${style_struct.gecko_ffi_name} {
|
|
||||||
&self.gecko
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
impl Drop for ${style_struct.gecko_struct_name} {
|
impl Drop for ${style_struct.gecko_struct_name} {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
|
@ -2508,7 +2505,7 @@ fn static_assert() {
|
||||||
}
|
}
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
<% skip_box_longhands= """display vertical-align
|
<% skip_box_longhands= """display
|
||||||
animation-name animation-delay animation-duration
|
animation-name animation-delay animation-duration
|
||||||
animation-direction animation-fill-mode animation-play-state
|
animation-direction animation-fill-mode animation-play-state
|
||||||
animation-iteration-count animation-timing-function
|
animation-iteration-count animation-timing-function
|
||||||
|
@ -2564,47 +2561,6 @@ fn static_assert() {
|
||||||
) %>
|
) %>
|
||||||
${impl_keyword('clear', 'mBreakType', clear_keyword)}
|
${impl_keyword('clear', 'mBreakType', clear_keyword)}
|
||||||
|
|
||||||
pub fn set_vertical_align(&mut self, v: longhands::vertical_align::computed_value::T) {
|
|
||||||
use crate::values::generics::box_::VerticalAlign;
|
|
||||||
let value = match v {
|
|
||||||
VerticalAlign::Baseline => structs::NS_STYLE_VERTICAL_ALIGN_BASELINE,
|
|
||||||
VerticalAlign::Sub => structs::NS_STYLE_VERTICAL_ALIGN_SUB,
|
|
||||||
VerticalAlign::Super => structs::NS_STYLE_VERTICAL_ALIGN_SUPER,
|
|
||||||
VerticalAlign::Top => structs::NS_STYLE_VERTICAL_ALIGN_TOP,
|
|
||||||
VerticalAlign::TextTop => structs::NS_STYLE_VERTICAL_ALIGN_TEXT_TOP,
|
|
||||||
VerticalAlign::Middle => structs::NS_STYLE_VERTICAL_ALIGN_MIDDLE,
|
|
||||||
VerticalAlign::Bottom => structs::NS_STYLE_VERTICAL_ALIGN_BOTTOM,
|
|
||||||
VerticalAlign::TextBottom => structs::NS_STYLE_VERTICAL_ALIGN_TEXT_BOTTOM,
|
|
||||||
VerticalAlign::MozMiddleWithBaseline => {
|
|
||||||
structs::NS_STYLE_VERTICAL_ALIGN_MIDDLE_WITH_BASELINE
|
|
||||||
},
|
|
||||||
VerticalAlign::Length(length) => {
|
|
||||||
self.gecko.mVerticalAlign.set(length);
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
self.gecko.mVerticalAlign.set_value(CoordDataValue::Enumerated(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clone_vertical_align(&self) -> longhands::vertical_align::computed_value::T {
|
|
||||||
use crate::values::computed::LengthPercentage;
|
|
||||||
use crate::values::generics::box_::VerticalAlign;
|
|
||||||
|
|
||||||
let gecko = &self.gecko.mVerticalAlign;
|
|
||||||
match gecko.as_value() {
|
|
||||||
CoordDataValue::Enumerated(value) => VerticalAlign::from_gecko_keyword(value),
|
|
||||||
_ => {
|
|
||||||
VerticalAlign::Length(
|
|
||||||
LengthPercentage::from_gecko_style_coord(gecko).expect(
|
|
||||||
"expected <length-percentage> for vertical-align",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
<%call expr="impl_coord_copy('vertical_align', 'mVerticalAlign')"></%call>
|
|
||||||
|
|
||||||
${impl_style_coord("scroll_snap_points_x", "mScrollSnapPointsX")}
|
${impl_style_coord("scroll_snap_points_x", "mScrollSnapPointsX")}
|
||||||
${impl_style_coord("scroll_snap_points_y", "mScrollSnapPointsY")}
|
${impl_style_coord("scroll_snap_points_y", "mScrollSnapPointsY")}
|
||||||
|
|
||||||
|
@ -4455,7 +4411,7 @@ clip-path
|
||||||
fn set_counter_function(
|
fn set_counter_function(
|
||||||
data: &mut nsStyleContentData,
|
data: &mut nsStyleContentData,
|
||||||
content_type: StyleContentType,
|
content_type: StyleContentType,
|
||||||
name: &CustomIdent,
|
name: CustomIdent,
|
||||||
sep: &str,
|
sep: &str,
|
||||||
style: CounterStyleOrNone,
|
style: CounterStyleOrNone,
|
||||||
) {
|
) {
|
||||||
|
@ -4464,7 +4420,9 @@ clip-path
|
||||||
let counter_func = unsafe {
|
let counter_func = unsafe {
|
||||||
bindings::Gecko_SetCounterFunction(data, content_type).as_mut().unwrap()
|
bindings::Gecko_SetCounterFunction(data, content_type).as_mut().unwrap()
|
||||||
};
|
};
|
||||||
counter_func.mIdent.assign(name.0.as_slice());
|
counter_func.mIdent.set_move(unsafe {
|
||||||
|
RefPtr::from_addrefed(name.0.into_addrefed())
|
||||||
|
});
|
||||||
if content_type == StyleContentType::Counters {
|
if content_type == StyleContentType::Counters {
|
||||||
counter_func.mSeparator.assign_str(sep);
|
counter_func.mSeparator.assign_str(sep);
|
||||||
}
|
}
|
||||||
|
@ -4493,14 +4451,14 @@ clip-path
|
||||||
Gecko_ClearAndResizeStyleContents(&mut self.gecko,
|
Gecko_ClearAndResizeStyleContents(&mut self.gecko,
|
||||||
items.len() as u32);
|
items.len() as u32);
|
||||||
}
|
}
|
||||||
for (i, item) in items.into_iter().enumerate() {
|
for (i, item) in items.into_vec().into_iter().enumerate() {
|
||||||
// NB: Gecko compares the mString value if type is not image
|
// NB: Gecko compares the mString value if type is not image
|
||||||
// or URI independently of whatever gets there. In the quote
|
// or URI independently of whatever gets there. In the quote
|
||||||
// cases, they set it to null, so do the same here.
|
// cases, they set it to null, so do the same here.
|
||||||
unsafe {
|
unsafe {
|
||||||
*self.gecko.mContents[i].mContent.mString.as_mut() = ptr::null_mut();
|
*self.gecko.mContents[i].mContent.mString.as_mut() = ptr::null_mut();
|
||||||
}
|
}
|
||||||
match *item {
|
match item {
|
||||||
ContentItem::String(ref value) => {
|
ContentItem::String(ref value) => {
|
||||||
self.gecko.mContents[i].mType = StyleContentType::String;
|
self.gecko.mContents[i].mType = StyleContentType::String;
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -4536,22 +4494,22 @@ clip-path
|
||||||
=> self.gecko.mContents[i].mType = StyleContentType::NoOpenQuote,
|
=> self.gecko.mContents[i].mType = StyleContentType::NoOpenQuote,
|
||||||
ContentItem::NoCloseQuote
|
ContentItem::NoCloseQuote
|
||||||
=> self.gecko.mContents[i].mType = StyleContentType::NoCloseQuote,
|
=> self.gecko.mContents[i].mType = StyleContentType::NoCloseQuote,
|
||||||
ContentItem::Counter(ref name, ref style) => {
|
ContentItem::Counter(name, style) => {
|
||||||
set_counter_function(
|
set_counter_function(
|
||||||
&mut self.gecko.mContents[i],
|
&mut self.gecko.mContents[i],
|
||||||
StyleContentType::Counter,
|
StyleContentType::Counter,
|
||||||
&name,
|
name,
|
||||||
"",
|
"",
|
||||||
style.clone(),
|
style,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
ContentItem::Counters(ref name, ref sep, ref style) => {
|
ContentItem::Counters(name, sep, style) => {
|
||||||
set_counter_function(
|
set_counter_function(
|
||||||
&mut self.gecko.mContents[i],
|
&mut self.gecko.mContents[i],
|
||||||
StyleContentType::Counters,
|
StyleContentType::Counters,
|
||||||
&name,
|
name,
|
||||||
&sep,
|
&sep,
|
||||||
style.clone(),
|
style,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
ContentItem::Url(ref url) => {
|
ContentItem::Url(ref url) => {
|
||||||
|
@ -4627,7 +4585,9 @@ clip-path
|
||||||
StyleContentType::Counter | StyleContentType::Counters => {
|
StyleContentType::Counter | StyleContentType::Counters => {
|
||||||
let gecko_function =
|
let gecko_function =
|
||||||
unsafe { &**gecko_content.mContent.mCounters.as_ref() };
|
unsafe { &**gecko_content.mContent.mCounters.as_ref() };
|
||||||
let ident = CustomIdent(Atom::from(&*gecko_function.mIdent));
|
let ident = CustomIdent(unsafe {
|
||||||
|
Atom::from_raw(gecko_function.mIdent.mRawPtr)
|
||||||
|
});
|
||||||
let style =
|
let style =
|
||||||
CounterStyleOrNone::from_gecko_value(&gecko_function.mCounterStyle);
|
CounterStyleOrNone::from_gecko_value(&gecko_function.mCounterStyle);
|
||||||
let style = match style {
|
let style = match style {
|
||||||
|
@ -4664,8 +4624,10 @@ clip-path
|
||||||
) {
|
) {
|
||||||
unsafe {
|
unsafe {
|
||||||
bindings::Gecko_ClearAndResizeCounter${counter_property}s(&mut self.gecko, v.len() as u32);
|
bindings::Gecko_ClearAndResizeCounter${counter_property}s(&mut self.gecko, v.len() as u32);
|
||||||
for (i, ref pair) in v.iter().enumerate() {
|
for (i, pair) in v.0.into_vec().into_iter().enumerate() {
|
||||||
self.gecko.m${counter_property}s[i].mCounter.assign(pair.name.0.as_slice());
|
self.gecko.m${counter_property}s[i].mCounter.set_move(
|
||||||
|
RefPtr::from_addrefed(pair.name.0.into_addrefed())
|
||||||
|
);
|
||||||
self.gecko.m${counter_property}s[i].mValue = pair.value;
|
self.gecko.m${counter_property}s[i].mValue = pair.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4690,7 +4652,9 @@ clip-path
|
||||||
longhands::counter_${counter_property.lower()}::computed_value::T::new(
|
longhands::counter_${counter_property.lower()}::computed_value::T::new(
|
||||||
self.gecko.m${counter_property}s.iter().map(|ref gecko_counter| {
|
self.gecko.m${counter_property}s.iter().map(|ref gecko_counter| {
|
||||||
CounterPair {
|
CounterPair {
|
||||||
name: CustomIdent(Atom::from(gecko_counter.mCounter.to_string())),
|
name: CustomIdent(unsafe {
|
||||||
|
Atom::from_raw(gecko_counter.mCounter.mRawPtr)
|
||||||
|
}),
|
||||||
value: gecko_counter.mValue,
|
value: gecko_counter.mValue,
|
||||||
}
|
}
|
||||||
}).collect()
|
}).collect()
|
||||||
|
|
|
@ -337,11 +337,11 @@ ${helpers.predefined_type(
|
||||||
"Position",
|
"Position",
|
||||||
"computed::Position::zero()",
|
"computed::Position::zero()",
|
||||||
vector=True,
|
vector=True,
|
||||||
|
allow_empty=True,
|
||||||
products="gecko",
|
products="gecko",
|
||||||
gecko_pref="layout.css.scroll-snap.enabled",
|
gecko_pref="layout.css.scroll-snap.enabled",
|
||||||
spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-destination)",
|
spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-destination)",
|
||||||
animation_value_type="discrete",
|
animation_value_type="discrete",
|
||||||
allow_empty="NotInitial",
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<% transform_extra_prefixes = "moz:layout.css.prefixes.transforms webkit" %>
|
<% transform_extra_prefixes = "moz:layout.css.prefixes.transforms webkit" %>
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
${helpers.single_keyword(
|
${helpers.single_keyword(
|
||||||
"border-collapse",
|
"border-collapse",
|
||||||
"separate collapse",
|
"separate collapse",
|
||||||
gecko_constant_prefix="NS_STYLE_BORDER",
|
gecko_enum_prefix="StyleBorderCollapse",
|
||||||
animation_value_type="discrete",
|
animation_value_type="discrete",
|
||||||
spec="https://drafts.csswg.org/css-tables/#propdef-border-collapse",
|
spec="https://drafts.csswg.org/css-tables/#propdef-border-collapse",
|
||||||
servo_restyle_damage = "reflow",
|
servo_restyle_damage = "reflow",
|
||||||
|
|
|
@ -19,11 +19,10 @@ ${helpers.predefined_type(
|
||||||
|
|
||||||
// CSS Text Module Level 3
|
// CSS Text Module Level 3
|
||||||
|
|
||||||
// TODO(pcwalton): `full-width`
|
${helpers.predefined_type(
|
||||||
${helpers.single_keyword(
|
|
||||||
"text-transform",
|
"text-transform",
|
||||||
"none capitalize uppercase lowercase",
|
"TextTransform",
|
||||||
extra_gecko_values="full-width full-size-kana",
|
"computed::TextTransform::none()",
|
||||||
animation_value_type="discrete",
|
animation_value_type="discrete",
|
||||||
flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER",
|
flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER",
|
||||||
spec="https://drafts.csswg.org/css-text/#propdef-text-transform",
|
spec="https://drafts.csswg.org/css-text/#propdef-text-transform",
|
||||||
|
|
|
@ -27,7 +27,7 @@ use crate::context::QuirksMode;
|
||||||
#[cfg(feature = "servo")] use crate::logical_geometry::LogicalMargin;
|
#[cfg(feature = "servo")] use crate::logical_geometry::LogicalMargin;
|
||||||
#[cfg(feature = "servo")] use crate::computed_values;
|
#[cfg(feature = "servo")] use crate::computed_values;
|
||||||
use crate::logical_geometry::WritingMode;
|
use crate::logical_geometry::WritingMode;
|
||||||
#[cfg(feature = "gecko")] use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
|
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
|
||||||
use crate::media_queries::Device;
|
use crate::media_queries::Device;
|
||||||
use crate::parser::ParserContext;
|
use crate::parser::ParserContext;
|
||||||
use crate::properties::longhands::system_font::SystemFont;
|
use crate::properties::longhands::system_font::SystemFont;
|
||||||
|
@ -376,7 +376,6 @@ impl PartialEq for PropertyDeclaration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "gecko")]
|
|
||||||
impl MallocSizeOf for PropertyDeclaration {
|
impl MallocSizeOf for PropertyDeclaration {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
|
fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
|
||||||
|
@ -1086,6 +1085,8 @@ impl LonghandId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(emilio): Should we use a function table like CASCADE_PROPERTY does
|
||||||
|
// to avoid blowing up code-size here?
|
||||||
fn parse_value<'i, 't>(
|
fn parse_value<'i, 't>(
|
||||||
&self,
|
&self,
|
||||||
context: &ParserContext,
|
context: &ParserContext,
|
||||||
|
@ -1533,64 +1534,7 @@ impl UnparsedValue {
|
||||||
quirks_mode: QuirksMode,
|
quirks_mode: QuirksMode,
|
||||||
environment: &::custom_properties::CssEnvironment,
|
environment: &::custom_properties::CssEnvironment,
|
||||||
) -> PropertyDeclaration {
|
) -> PropertyDeclaration {
|
||||||
crate::custom_properties::substitute(
|
let invalid_at_computed_value_time = || {
|
||||||
&self.css,
|
|
||||||
self.first_token_type,
|
|
||||||
custom_properties,
|
|
||||||
environment,
|
|
||||||
).ok().and_then(|css| {
|
|
||||||
// As of this writing, only the base URL is used for property
|
|
||||||
// values.
|
|
||||||
//
|
|
||||||
// NOTE(emilio): we intentionally pase `None` as the rule type here.
|
|
||||||
// If something starts depending on it, it's probably a bug, since
|
|
||||||
// it'd change how values are parsed depending on whether we're in a
|
|
||||||
// @keyframes rule or not, for example... So think twice about
|
|
||||||
// whether you want to do this!
|
|
||||||
//
|
|
||||||
// FIXME(emilio): ParsingMode is slightly fishy...
|
|
||||||
let context = ParserContext::new(
|
|
||||||
Origin::Author,
|
|
||||||
&self.url_data,
|
|
||||||
None,
|
|
||||||
ParsingMode::DEFAULT,
|
|
||||||
quirks_mode,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut input = ParserInput::new(&css);
|
|
||||||
Parser::new(&mut input).parse_entirely(|input| {
|
|
||||||
match self.from_shorthand {
|
|
||||||
None => longhand_id.parse_value(&context, input),
|
|
||||||
Some(ShorthandId::All) => {
|
|
||||||
// No need to parse the 'all' shorthand as anything other than a CSS-wide
|
|
||||||
// keyword, after variable substitution.
|
|
||||||
Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent("all".into())))
|
|
||||||
}
|
|
||||||
% for shorthand in data.shorthands_except_all():
|
|
||||||
Some(ShorthandId::${shorthand.camel_case}) => {
|
|
||||||
shorthands::${shorthand.ident}::parse_value(&context, input)
|
|
||||||
.map(|longhands| {
|
|
||||||
match longhand_id {
|
|
||||||
% for property in shorthand.sub_properties:
|
|
||||||
LonghandId::${property.camel_case} => {
|
|
||||||
PropertyDeclaration::${property.camel_case}(
|
|
||||||
longhands.${property.ident}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
% endfor
|
|
||||||
_ => unreachable!()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
% endfor
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.ok()
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
// Invalid at computed-value time.
|
|
||||||
let keyword = if longhand_id.inherited() {
|
let keyword = if longhand_id.inherited() {
|
||||||
CSSWideKeyword::Inherit
|
CSSWideKeyword::Inherit
|
||||||
} else {
|
} else {
|
||||||
|
@ -1600,7 +1544,80 @@ impl UnparsedValue {
|
||||||
id: longhand_id,
|
id: longhand_id,
|
||||||
keyword,
|
keyword,
|
||||||
})
|
})
|
||||||
})
|
};
|
||||||
|
|
||||||
|
let css = match crate::custom_properties::substitute(
|
||||||
|
&self.css,
|
||||||
|
self.first_token_type,
|
||||||
|
custom_properties,
|
||||||
|
environment,
|
||||||
|
) {
|
||||||
|
Ok(css) => css,
|
||||||
|
Err(..) => return invalid_at_computed_value_time(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// As of this writing, only the base URL is used for property
|
||||||
|
// values.
|
||||||
|
//
|
||||||
|
// NOTE(emilio): we intentionally pase `None` as the rule type here.
|
||||||
|
// If something starts depending on it, it's probably a bug, since
|
||||||
|
// it'd change how values are parsed depending on whether we're in a
|
||||||
|
// @keyframes rule or not, for example... So think twice about
|
||||||
|
// whether you want to do this!
|
||||||
|
//
|
||||||
|
// FIXME(emilio): ParsingMode is slightly fishy...
|
||||||
|
let context = ParserContext::new(
|
||||||
|
Origin::Author,
|
||||||
|
&self.url_data,
|
||||||
|
None,
|
||||||
|
ParsingMode::DEFAULT,
|
||||||
|
quirks_mode,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut input = ParserInput::new(&css);
|
||||||
|
let mut input = Parser::new(&mut input);
|
||||||
|
input.skip_whitespace(); // Unnecessary for correctness, but may help try() rewind less.
|
||||||
|
if let Ok(keyword) = input.try(CSSWideKeyword::parse) {
|
||||||
|
return PropertyDeclaration::CSSWideKeyword(WideKeywordDeclaration {
|
||||||
|
id: longhand_id,
|
||||||
|
keyword,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let declaration = input.parse_entirely(|input| {
|
||||||
|
match self.from_shorthand {
|
||||||
|
None => longhand_id.parse_value(&context, input),
|
||||||
|
Some(ShorthandId::All) => {
|
||||||
|
// No need to parse the 'all' shorthand as anything other
|
||||||
|
// than a CSS-wide keyword, after variable substitution.
|
||||||
|
Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent("all".into())))
|
||||||
|
}
|
||||||
|
% for shorthand in data.shorthands_except_all():
|
||||||
|
Some(ShorthandId::${shorthand.camel_case}) => {
|
||||||
|
shorthands::${shorthand.ident}::parse_value(&context, input)
|
||||||
|
.map(|longhands| {
|
||||||
|
match longhand_id {
|
||||||
|
% for property in shorthand.sub_properties:
|
||||||
|
LonghandId::${property.camel_case} => {
|
||||||
|
PropertyDeclaration::${property.camel_case}(
|
||||||
|
longhands.${property.ident}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
% endfor
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
% endfor
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
match declaration {
|
||||||
|
Ok(decl) => decl,
|
||||||
|
Err(..) => invalid_at_computed_value_time(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3745,8 +3762,7 @@ pub fn adjust_border_width(style: &mut StyleBuilder) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An identifier for a given alias property.
|
/// An identifier for a given alias property.
|
||||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
#[derive(Clone, Copy, Eq, PartialEq, MallocSizeOf)]
|
||||||
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
|
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
pub enum AliasId {
|
pub enum AliasId {
|
||||||
% for i, property in enumerate(data.all_aliases()):
|
% for i, property in enumerate(data.all_aliases()):
|
||||||
|
|
|
@ -254,7 +254,7 @@
|
||||||
use crate::parser::Parse;
|
use crate::parser::Parse;
|
||||||
use servo_arc::Arc;
|
use servo_arc::Arc;
|
||||||
use crate::values::{Either, None_};
|
use crate::values::{Either, None_};
|
||||||
use crate::values::generics::grid::{LineNameList, TrackSize, TrackList, TrackListType};
|
use crate::values::generics::grid::{TrackSize, TrackList, TrackListType};
|
||||||
use crate::values::generics::grid::{TrackListValue, concat_serialize_idents};
|
use crate::values::generics::grid::{TrackListValue, concat_serialize_idents};
|
||||||
use crate::values::specified::{GridTemplateComponent, GenericGridTemplateComponent};
|
use crate::values::specified::{GridTemplateComponent, GenericGridTemplateComponent};
|
||||||
use crate::values::specified::grid::parse_line_names;
|
use crate::values::specified::grid::parse_line_names;
|
||||||
|
@ -265,12 +265,11 @@
|
||||||
context: &ParserContext,
|
context: &ParserContext,
|
||||||
input: &mut Parser<'i, 't>,
|
input: &mut Parser<'i, 't>,
|
||||||
) -> Result<(GridTemplateComponent, GridTemplateComponent, Either<TemplateAreasArc, None_>), ParseError<'i>> {
|
) -> Result<(GridTemplateComponent, GridTemplateComponent, Either<TemplateAreasArc, None_>), ParseError<'i>> {
|
||||||
// Other shorthand sub properties also parse `none` and `subgrid` keywords and this
|
// Other shorthand sub properties also parse the `none` keyword and this shorthand
|
||||||
// shorthand should know after these keywords there is nothing to parse. Otherwise it
|
// should know after this keyword there is nothing to parse. Otherwise it gets
|
||||||
// gets confused and rejects the sub properties that contains `none` or `subgrid`.
|
// confused and rejects the sub properties that contains `none`.
|
||||||
<% keywords = {
|
<% keywords = {
|
||||||
"none": "GenericGridTemplateComponent::None",
|
"none": "GenericGridTemplateComponent::None",
|
||||||
"subgrid": "GenericGridTemplateComponent::Subgrid(LineNameList::default())"
|
|
||||||
}
|
}
|
||||||
%>
|
%>
|
||||||
% for keyword, rust_type in keywords.items():
|
% for keyword, rust_type in keywords.items():
|
||||||
|
|
|
@ -98,7 +98,7 @@ where
|
||||||
flags_setter: &'a mut F,
|
flags_setter: &'a mut F,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let rule_hash_target = element.rule_hash_target();
|
let rule_hash_target = element.rule_hash_target();
|
||||||
let matches_user_and_author_rules = rule_hash_target.matches_user_and_author_rules();
|
let matches_user_and_author_rules = element.matches_user_and_author_rules();
|
||||||
|
|
||||||
// Gecko definitely has pseudo-elements with style attributes, like
|
// Gecko definitely has pseudo-elements with style attributes, like
|
||||||
// ::-moz-color-swatch.
|
// ::-moz-color-swatch.
|
||||||
|
@ -198,7 +198,7 @@ where
|
||||||
let rules = &mut self.rules;
|
let rules = &mut self.rules;
|
||||||
let flags_setter = &mut self.flags_setter;
|
let flags_setter = &mut self.flags_setter;
|
||||||
let shadow_cascade_order = self.shadow_cascade_order;
|
let shadow_cascade_order = self.shadow_cascade_order;
|
||||||
self.context.with_shadow_host(Some(shadow_host), |context| {
|
self.context.with_shadow_host(shadow_host, |context| {
|
||||||
map.get_all_matching_rules(
|
map.get_all_matching_rules(
|
||||||
element,
|
element,
|
||||||
rule_hash_target,
|
rule_hash_target,
|
||||||
|
@ -303,42 +303,6 @@ where
|
||||||
self.collect_stylist_rules(Origin::Author);
|
self.collect_stylist_rules(Origin::Author);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_xbl_rules(&mut self) {
|
|
||||||
let element = self.element;
|
|
||||||
let cut_xbl_binding_inheritance =
|
|
||||||
element.each_xbl_cascade_data(|cascade_data, quirks_mode| {
|
|
||||||
let map = match cascade_data.normal_rules(self.pseudo_element) {
|
|
||||||
Some(m) => m,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
// NOTE(emilio): This is needed because the XBL stylist may
|
|
||||||
// think it has a different quirks mode than the document.
|
|
||||||
let mut matching_context = MatchingContext::new(
|
|
||||||
self.context.matching_mode(),
|
|
||||||
self.context.bloom_filter,
|
|
||||||
self.context.nth_index_cache.as_mut().map(|s| &mut **s),
|
|
||||||
quirks_mode,
|
|
||||||
);
|
|
||||||
matching_context.pseudo_element_matching_fn =
|
|
||||||
self.context.pseudo_element_matching_fn;
|
|
||||||
|
|
||||||
// SameTreeAuthorNormal instead of InnerShadowNormal to
|
|
||||||
// preserve behavior, though that's kinda fishy...
|
|
||||||
map.get_all_matching_rules(
|
|
||||||
self.element,
|
|
||||||
self.rule_hash_target,
|
|
||||||
self.rules,
|
|
||||||
&mut matching_context,
|
|
||||||
self.flags_setter,
|
|
||||||
CascadeLevel::SameTreeAuthorNormal,
|
|
||||||
self.shadow_cascade_order,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
self.matches_document_author_rules &= !cut_xbl_binding_inheritance;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn collect_style_attribute_and_animation_rules(&mut self) {
|
fn collect_style_attribute_and_animation_rules(&mut self) {
|
||||||
if let Some(sa) = self.style_attribute {
|
if let Some(sa) = self.style_attribute {
|
||||||
self.rules
|
self.rules
|
||||||
|
@ -396,7 +360,6 @@ where
|
||||||
self.collect_host_rules();
|
self.collect_host_rules();
|
||||||
self.collect_slotted_rules();
|
self.collect_slotted_rules();
|
||||||
self.collect_normal_rules_from_containing_shadow_tree();
|
self.collect_normal_rules_from_containing_shadow_tree();
|
||||||
self.collect_xbl_rules();
|
|
||||||
self.collect_document_author_rules();
|
self.collect_document_author_rules();
|
||||||
self.collect_style_attribute_and_animation_rules();
|
self.collect_style_attribute_and_animation_rules();
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,10 +66,6 @@ pub const PSEUDO_COUNT: usize = PseudoElement::ServoInlineAbsolute as usize + 1;
|
||||||
|
|
||||||
impl ::selectors::parser::PseudoElement for PseudoElement {
|
impl ::selectors::parser::PseudoElement for PseudoElement {
|
||||||
type Impl = SelectorImpl;
|
type Impl = SelectorImpl;
|
||||||
|
|
||||||
fn supports_pseudo_class(&self, _: &NonTSPseudoClass) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToCss for PseudoElement {
|
impl ToCss for PseudoElement {
|
||||||
|
@ -293,6 +289,14 @@ impl ::selectors::parser::NonTSPseudoClass for NonTSPseudoClass {
|
||||||
fn is_active_or_hover(&self) -> bool {
|
fn is_active_or_hover(&self) -> bool {
|
||||||
matches!(*self, NonTSPseudoClass::Active | NonTSPseudoClass::Hover)
|
matches!(*self, NonTSPseudoClass::Active | NonTSPseudoClass::Hover)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn is_user_action_state(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
*self,
|
||||||
|
NonTSPseudoClass::Active | NonTSPseudoClass::Hover | NonTSPseudoClass::Focus
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToCss for NonTSPseudoClass {
|
impl ToCss for NonTSPseudoClass {
|
||||||
|
@ -393,6 +397,7 @@ impl ::selectors::SelectorImpl for SelectorImpl {
|
||||||
type AttrValue = String;
|
type AttrValue = String;
|
||||||
type Identifier = Atom;
|
type Identifier = Atom;
|
||||||
type ClassName = Atom;
|
type ClassName = Atom;
|
||||||
|
type PartName = Atom;
|
||||||
type LocalName = LocalName;
|
type LocalName = LocalName;
|
||||||
type NamespacePrefix = Prefix;
|
type NamespacePrefix = Prefix;
|
||||||
type NamespaceUrl = Namespace;
|
type NamespaceUrl = Namespace;
|
||||||
|
@ -679,6 +684,10 @@ impl ElementSnapshot for ServoElementSnapshot {
|
||||||
.map(|v| v.as_atom())
|
.map(|v| v.as_atom())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_part(&self, _name: &Atom) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
|
fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
|
||||||
self.get_attr(&ns!(), &local_name!("class"))
|
self.get_attr(&ns!(), &local_name!("class"))
|
||||||
.map_or(false, |v| {
|
.map_or(false, |v| {
|
||||||
|
|
|
@ -727,27 +727,6 @@ impl<E: TElement> StyleSharingCache<E> {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note that in theory we shouldn't need this XBL check. However, XBL is
|
|
||||||
// absolutely broken in all sorts of ways.
|
|
||||||
//
|
|
||||||
// A style change that changes which XBL binding applies to an element
|
|
||||||
// arrives there, with the element still having the old prototype
|
|
||||||
// binding attached. And thus we try to match revalidation selectors
|
|
||||||
// with the old XBL binding, because we can't look at the new ones of
|
|
||||||
// course. And that causes us to revalidate with the wrong selectors and
|
|
||||||
// hit assertions.
|
|
||||||
//
|
|
||||||
// Other than this, we don't need anything else like the containing XBL
|
|
||||||
// binding parent or what not, since two elements with different XBL
|
|
||||||
// bindings will necessarily end up with different style.
|
|
||||||
if !target
|
|
||||||
.element
|
|
||||||
.has_same_xbl_proto_binding_as(candidate.element)
|
|
||||||
{
|
|
||||||
trace!("Miss: Different proto bindings");
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the elements are not assigned to the same slot they could match
|
// If the elements are not assigned to the same slot they could match
|
||||||
// different ::slotted() rules in the slot scope.
|
// different ::slotted() rules in the slot scope.
|
||||||
//
|
//
|
||||||
|
|
|
@ -468,7 +468,14 @@ where
|
||||||
.fold(0, |s, (item, _)| s + item.len())
|
.fold(0, |s, (item, _)| s + item.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the count of stylesheets for a given origin.
|
||||||
|
#[inline]
|
||||||
|
pub fn sheet_count(&self, origin: Origin) -> usize {
|
||||||
|
self.collections.borrow_for_origin(&origin).len()
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the `index`th stylesheet in the set for the given origin.
|
/// Returns the `index`th stylesheet in the set for the given origin.
|
||||||
|
#[inline]
|
||||||
pub fn get(&self, origin: Origin, index: usize) -> Option<&S> {
|
pub fn get(&self, origin: Origin, index: usize) -> Option<&S> {
|
||||||
self.collections.borrow_for_origin(&origin).get(index)
|
self.collections.borrow_for_origin(&origin).get(index)
|
||||||
}
|
}
|
||||||
|
@ -539,7 +546,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The set of stylesheets effective for a given XBL binding or Shadow Root.
|
/// The set of stylesheets effective for a given Shadow Root.
|
||||||
#[derive(MallocSizeOf)]
|
#[derive(MallocSizeOf)]
|
||||||
pub struct AuthorStylesheetSet<S>
|
pub struct AuthorStylesheetSet<S>
|
||||||
where
|
where
|
||||||
|
|
|
@ -10,18 +10,17 @@ use std::ops::BitOrAssign;
|
||||||
/// Each style rule has an origin, which determines where it enters the cascade.
|
/// Each style rule has an origin, which determines where it enters the cascade.
|
||||||
///
|
///
|
||||||
/// <https://drafts.csswg.org/css-cascade/#cascading-origins>
|
/// <https://drafts.csswg.org/css-cascade/#cascading-origins>
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, ToShmem)]
|
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
|
|
||||||
pub enum Origin {
|
pub enum Origin {
|
||||||
/// <https://drafts.csswg.org/css-cascade/#cascade-origin-user-agent>
|
/// <https://drafts.csswg.org/css-cascade/#cascade-origin-user-agent>
|
||||||
UserAgent = 1 << 0,
|
UserAgent = 0x1,
|
||||||
|
|
||||||
/// <https://drafts.csswg.org/css-cascade/#cascade-origin-user>
|
/// <https://drafts.csswg.org/css-cascade/#cascade-origin-user>
|
||||||
User = 1 << 1,
|
User = 0x2,
|
||||||
|
|
||||||
/// <https://drafts.csswg.org/css-cascade/#cascade-origin-author>
|
/// <https://drafts.csswg.org/css-cascade/#cascade-origin-author>
|
||||||
Author = 1 << 2,
|
Author = 0x4,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Origin {
|
impl Origin {
|
||||||
|
@ -59,7 +58,7 @@ impl Origin {
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
/// A set of origins. This is equivalent to Gecko's OriginFlags.
|
/// A set of origins. This is equivalent to Gecko's OriginFlags.
|
||||||
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
|
#[derive(MallocSizeOf)]
|
||||||
pub struct OriginSet: u8 {
|
pub struct OriginSet: u8 {
|
||||||
/// <https://drafts.csswg.org/css-cascade/#cascade-origin-user-agent>
|
/// <https://drafts.csswg.org/css-cascade/#cascade-origin-user-agent>
|
||||||
const ORIGIN_USER_AGENT = Origin::UserAgent as u8;
|
const ORIGIN_USER_AGENT = Origin::UserAgent as u8;
|
||||||
|
|
|
@ -48,8 +48,9 @@ use selectors::visitor::SelectorVisitor;
|
||||||
use selectors::NthIndexCache;
|
use selectors::NthIndexCache;
|
||||||
use servo_arc::{Arc, ArcBorrow};
|
use servo_arc::{Arc, ArcBorrow};
|
||||||
use smallbitvec::SmallBitVec;
|
use smallbitvec::SmallBitVec;
|
||||||
use std::ops;
|
use smallvec::SmallVec;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
use std::{mem, ops};
|
||||||
use style_traits::viewport::ViewportConstraints;
|
use style_traits::viewport::ViewportConstraints;
|
||||||
|
|
||||||
/// The type of the stylesheets that the stylist contains.
|
/// The type of the stylesheets that the stylist contains.
|
||||||
|
@ -128,15 +129,28 @@ impl UserAgentCascadeDataCache {
|
||||||
Ok(new_data)
|
Ok(new_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expire_unused(&mut self) {
|
/// Returns all the cascade datas that are not being used (that is, that are
|
||||||
// is_unique() returns false for static references, but we never have
|
/// held alive just by this cache).
|
||||||
// static references to UserAgentCascadeDatas. If we did, it may not
|
///
|
||||||
// make sense to put them in the cache in the first place.
|
/// We return them instead of dropping in place because some of them may
|
||||||
self.entries.retain(|e| !e.is_unique())
|
/// keep alive some other documents (like the SVG documents kept alive by
|
||||||
|
/// URL references), and thus we don't want to drop them while locking the
|
||||||
|
/// cache to not deadlock.
|
||||||
|
fn take_unused(&mut self) -> SmallVec<[Arc<UserAgentCascadeData>; 3]> {
|
||||||
|
let mut unused = SmallVec::new();
|
||||||
|
for i in (0..self.entries.len()).rev() {
|
||||||
|
// is_unique() returns false for static references, but we never
|
||||||
|
// have static references to UserAgentCascadeDatas. If we did, it
|
||||||
|
// may not make sense to put them in the cache in the first place.
|
||||||
|
if self.entries[i].is_unique() {
|
||||||
|
unused.push(self.entries.remove(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unused
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear(&mut self) {
|
fn take_all(&mut self) -> Vec<Arc<UserAgentCascadeData>> {
|
||||||
self.entries.clear();
|
mem::replace(&mut self.entries, Vec::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
|
@ -254,13 +268,14 @@ impl DocumentCascadeData {
|
||||||
// First do UA sheets.
|
// First do UA sheets.
|
||||||
{
|
{
|
||||||
if flusher.flush_origin(Origin::UserAgent).dirty() {
|
if flusher.flush_origin(Origin::UserAgent).dirty() {
|
||||||
let mut ua_cache = UA_CASCADE_DATA_CACHE.lock().unwrap();
|
|
||||||
let origin_sheets = flusher.origin_sheets(Origin::UserAgent);
|
let origin_sheets = flusher.origin_sheets(Origin::UserAgent);
|
||||||
let ua_cascade_data =
|
let _unused_cascade_datas = {
|
||||||
ua_cache.lookup(origin_sheets, device, quirks_mode, guards.ua_or_user)?;
|
let mut ua_cache = UA_CASCADE_DATA_CACHE.lock().unwrap();
|
||||||
ua_cache.expire_unused();
|
self.user_agent =
|
||||||
debug!("User agent data cache size {:?}", ua_cache.len());
|
ua_cache.lookup(origin_sheets, device, quirks_mode, guards.ua_or_user)?;
|
||||||
self.user_agent = ua_cascade_data;
|
debug!("User agent data cache size {:?}", ua_cache.len());
|
||||||
|
ua_cache.take_unused()
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -591,6 +606,18 @@ impl Stylist {
|
||||||
.remove_stylesheet(Some(&self.device), sheet, guard)
|
.remove_stylesheet(Some(&self.device), sheet, guard)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Appends a new stylesheet to the current set.
|
||||||
|
#[inline]
|
||||||
|
pub fn sheet_count(&self, origin: Origin) -> usize {
|
||||||
|
self.stylesheets.sheet_count(origin)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Appends a new stylesheet to the current set.
|
||||||
|
#[inline]
|
||||||
|
pub fn sheet_at(&self, origin: Origin, index: usize) -> Option<&StylistSheet> {
|
||||||
|
self.stylesheets.get(origin, index)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns whether for any of the applicable style rule data a given
|
/// Returns whether for any of the applicable style rule data a given
|
||||||
/// condition is true.
|
/// condition is true.
|
||||||
pub fn any_applicable_rule_data<E, F>(&self, element: E, mut f: F) -> bool
|
pub fn any_applicable_rule_data<E, F>(&self, element: E, mut f: F) -> bool
|
||||||
|
@ -605,7 +632,7 @@ impl Stylist {
|
||||||
let mut maybe = false;
|
let mut maybe = false;
|
||||||
|
|
||||||
let doc_author_rules_apply =
|
let doc_author_rules_apply =
|
||||||
element.each_applicable_non_document_style_rule_data(|data, _, _| {
|
element.each_applicable_non_document_style_rule_data(|data, _| {
|
||||||
maybe = maybe || f(&*data);
|
maybe = maybe || f(&*data);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1041,12 +1068,6 @@ impl Stylist {
|
||||||
/// Returns whether, given a media feature change, any previously-applicable
|
/// Returns whether, given a media feature change, any previously-applicable
|
||||||
/// style has become non-applicable, or vice-versa for each origin, using
|
/// style has become non-applicable, or vice-versa for each origin, using
|
||||||
/// `device`.
|
/// `device`.
|
||||||
///
|
|
||||||
/// Passing `device` is needed because this is used for XBL in Gecko, which
|
|
||||||
/// can be stale in various ways, so we need to pass the device of the
|
|
||||||
/// document itself, which is what is kept up-to-date.
|
|
||||||
///
|
|
||||||
/// Arguably XBL should use something more lightweight than a Stylist.
|
|
||||||
pub fn media_features_change_changed_style(
|
pub fn media_features_change_changed_style(
|
||||||
&self,
|
&self,
|
||||||
guards: &StylesheetGuards,
|
guards: &StylesheetGuards,
|
||||||
|
@ -1230,11 +1251,11 @@ impl Stylist {
|
||||||
let mut results = SmallBitVec::new();
|
let mut results = SmallBitVec::new();
|
||||||
|
|
||||||
let matches_document_rules =
|
let matches_document_rules =
|
||||||
element.each_applicable_non_document_style_rule_data(|data, quirks_mode, host| {
|
element.each_applicable_non_document_style_rule_data(|data, host| {
|
||||||
matching_context.with_shadow_host(host, |matching_context| {
|
matching_context.with_shadow_host(host, |matching_context| {
|
||||||
data.selectors_for_cache_revalidation.lookup(
|
data.selectors_for_cache_revalidation.lookup(
|
||||||
element,
|
element,
|
||||||
quirks_mode,
|
self.quirks_mode,
|
||||||
|selector_and_hashes| {
|
|selector_and_hashes| {
|
||||||
results.push(matches_selector(
|
results.push(matches_selector(
|
||||||
&selector_and_hashes.selector,
|
&selector_and_hashes.selector,
|
||||||
|
@ -1356,7 +1377,7 @@ impl Stylist {
|
||||||
|
|
||||||
/// Shutdown the static data that this module stores.
|
/// Shutdown the static data that this module stores.
|
||||||
pub fn shutdown() {
|
pub fn shutdown() {
|
||||||
UA_CASCADE_DATA_CACHE.lock().unwrap().clear()
|
let _entries = UA_CASCADE_DATA_CACHE.lock().unwrap().take_all();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,7 @@ pub enum LineDirection {
|
||||||
pub type EndingShape = generic::EndingShape<Length, LengthPercentage>;
|
pub type EndingShape = generic::EndingShape<Length, LengthPercentage>;
|
||||||
|
|
||||||
/// A computed gradient item.
|
/// A computed gradient item.
|
||||||
pub type GradientItem = generic::GradientItem<Color, LengthPercentage>;
|
pub type GradientItem = generic::GenericGradientItem<Color, LengthPercentage>;
|
||||||
|
|
||||||
/// A computed color stop.
|
/// A computed color stop.
|
||||||
pub type ColorStop = generic::ColorStop<Color, LengthPercentage>;
|
pub type ColorStop = generic::ColorStop<Color, LengthPercentage>;
|
||||||
|
|
|
@ -86,6 +86,7 @@ pub use self::transform::{TransformOrigin, TransformStyle, Translate};
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
pub use self::ui::CursorImage;
|
pub use self::ui::CursorImage;
|
||||||
pub use self::ui::{Cursor, MozForceBrokenImageIcon, UserSelect};
|
pub use self::ui::{Cursor, MozForceBrokenImageIcon, UserSelect};
|
||||||
|
pub use super::specified::TextTransform;
|
||||||
pub use super::specified::{BorderStyle, TextDecorationLine};
|
pub use super::specified::{BorderStyle, TextDecorationLine};
|
||||||
pub use super::{Auto, Either, None_};
|
pub use super::{Auto, Either, None_};
|
||||||
pub use app_units::Au;
|
pub use app_units::Au;
|
||||||
|
|
|
@ -19,6 +19,7 @@ use std::fmt::{self, Write};
|
||||||
use style_traits::{CssWriter, ToCss};
|
use style_traits::{CssWriter, ToCss};
|
||||||
|
|
||||||
pub use crate::values::specified::TextAlignKeyword as TextAlign;
|
pub use crate::values::specified::TextAlignKeyword as TextAlign;
|
||||||
|
pub use crate::values::specified::TextTransform;
|
||||||
pub use crate::values::specified::{OverflowWrap, WordBreak};
|
pub use crate::values::specified::{OverflowWrap, WordBreak};
|
||||||
pub use crate::values::specified::{TextDecorationLine, TextEmphasisPosition};
|
pub use crate::values::specified::{TextDecorationLine, TextEmphasisPosition};
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,37 @@
|
||||||
|
|
||||||
use crate::values::animated::ToAnimatedZero;
|
use crate::values::animated::ToAnimatedZero;
|
||||||
|
|
||||||
|
#[derive(
|
||||||
|
Animate,
|
||||||
|
Clone,
|
||||||
|
ComputeSquaredDistance,
|
||||||
|
Copy,
|
||||||
|
Debug,
|
||||||
|
FromPrimitive,
|
||||||
|
MallocSizeOf,
|
||||||
|
Parse,
|
||||||
|
PartialEq,
|
||||||
|
SpecifiedValueInfo,
|
||||||
|
ToComputedValue,
|
||||||
|
ToCss,
|
||||||
|
ToResolvedValue,
|
||||||
|
ToShmem,
|
||||||
|
)]
|
||||||
|
#[repr(u8)]
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub enum VerticalAlignKeyword {
|
||||||
|
Baseline,
|
||||||
|
Sub,
|
||||||
|
Super,
|
||||||
|
Top,
|
||||||
|
TextTop,
|
||||||
|
Middle,
|
||||||
|
Bottom,
|
||||||
|
TextBottom,
|
||||||
|
#[cfg(feature = "gecko")]
|
||||||
|
MozMiddleWithBaseline,
|
||||||
|
}
|
||||||
|
|
||||||
/// A generic value for the `vertical-align` property.
|
/// A generic value for the `vertical-align` property.
|
||||||
#[derive(
|
#[derive(
|
||||||
Animate,
|
Animate,
|
||||||
|
@ -21,35 +52,21 @@ use crate::values::animated::ToAnimatedZero;
|
||||||
ToResolvedValue,
|
ToResolvedValue,
|
||||||
ToShmem,
|
ToShmem,
|
||||||
)]
|
)]
|
||||||
pub enum VerticalAlign<LengthPercentage> {
|
#[repr(C, u8)]
|
||||||
/// `baseline`
|
pub enum GenericVerticalAlign<LengthPercentage> {
|
||||||
Baseline,
|
/// One of the vertical-align keywords.
|
||||||
/// `sub`
|
Keyword(VerticalAlignKeyword),
|
||||||
Sub,
|
|
||||||
/// `super`
|
|
||||||
Super,
|
|
||||||
/// `top`
|
|
||||||
Top,
|
|
||||||
/// `text-top`
|
|
||||||
TextTop,
|
|
||||||
/// `middle`
|
|
||||||
Middle,
|
|
||||||
/// `bottom`
|
|
||||||
Bottom,
|
|
||||||
/// `text-bottom`
|
|
||||||
TextBottom,
|
|
||||||
/// `-moz-middle-with-baseline`
|
|
||||||
#[cfg(feature = "gecko")]
|
|
||||||
MozMiddleWithBaseline,
|
|
||||||
/// `<length-percentage>`
|
/// `<length-percentage>`
|
||||||
Length(LengthPercentage),
|
Length(LengthPercentage),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub use self::GenericVerticalAlign as VerticalAlign;
|
||||||
|
|
||||||
impl<L> VerticalAlign<L> {
|
impl<L> VerticalAlign<L> {
|
||||||
/// Returns `baseline`.
|
/// Returns `baseline`.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn baseline() -> Self {
|
pub fn baseline() -> Self {
|
||||||
VerticalAlign::Baseline
|
VerticalAlign::Keyword(VerticalAlignKeyword::Baseline)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ pub struct CounterPair<Integer> {
|
||||||
ToResolvedValue,
|
ToResolvedValue,
|
||||||
ToShmem,
|
ToShmem,
|
||||||
)]
|
)]
|
||||||
pub struct CounterIncrement<I>(Counters<I>);
|
pub struct CounterIncrement<I>(pub Counters<I>);
|
||||||
|
|
||||||
impl<I> CounterIncrement<I> {
|
impl<I> CounterIncrement<I> {
|
||||||
/// Returns a new value for `counter-increment`.
|
/// Returns a new value for `counter-increment`.
|
||||||
|
@ -77,7 +77,7 @@ impl<I> Deref for CounterIncrement<I> {
|
||||||
ToResolvedValue,
|
ToResolvedValue,
|
||||||
ToShmem,
|
ToShmem,
|
||||||
)]
|
)]
|
||||||
pub struct CounterSetOrReset<I>(Counters<I>);
|
pub struct CounterSetOrReset<I>(pub Counters<I>);
|
||||||
|
|
||||||
impl<I> CounterSetOrReset<I> {
|
impl<I> CounterSetOrReset<I> {
|
||||||
/// Returns a new value for `counter-set` / `counter-reset`.
|
/// Returns a new value for `counter-set` / `counter-reset`.
|
||||||
|
@ -102,6 +102,7 @@ impl<I> Deref for CounterSetOrReset<I> {
|
||||||
#[derive(
|
#[derive(
|
||||||
Clone,
|
Clone,
|
||||||
Debug,
|
Debug,
|
||||||
|
Default,
|
||||||
MallocSizeOf,
|
MallocSizeOf,
|
||||||
PartialEq,
|
PartialEq,
|
||||||
SpecifiedValueInfo,
|
SpecifiedValueInfo,
|
||||||
|
@ -112,10 +113,13 @@ impl<I> Deref for CounterSetOrReset<I> {
|
||||||
)]
|
)]
|
||||||
pub struct Counters<I>(#[css(iterable, if_empty = "none")] Box<[CounterPair<I>]>);
|
pub struct Counters<I>(#[css(iterable, if_empty = "none")] Box<[CounterPair<I>]>);
|
||||||
|
|
||||||
impl<I> Default for Counters<I> {
|
impl<I> Counters<I> {
|
||||||
|
/// Move out the Box into a vector. This could just return the Box<>, but
|
||||||
|
/// Vec<> is a bit more convenient because Box<[T]> doesn't implement
|
||||||
|
/// IntoIter: https://github.com/rust-lang/rust/issues/59878
|
||||||
#[inline]
|
#[inline]
|
||||||
fn default() -> Self {
|
pub fn into_vec(self) -> Vec<CounterPair<I>> {
|
||||||
Counters(vec![].into_boxed_slice())
|
self.0.into_vec()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -135,13 +135,23 @@ pub enum ShapeExtent {
|
||||||
#[derive(
|
#[derive(
|
||||||
Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem,
|
Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem,
|
||||||
)]
|
)]
|
||||||
pub enum GradientItem<Color, LengthPercentage> {
|
#[repr(C, u8)]
|
||||||
/// A color stop.
|
pub enum GenericGradientItem<Color, LengthPercentage> {
|
||||||
ColorStop(ColorStop<Color, LengthPercentage>),
|
/// A simple color stop, without position.
|
||||||
|
SimpleColorStop(Color),
|
||||||
|
/// A complex color stop, with a position.
|
||||||
|
ComplexColorStop {
|
||||||
|
/// The color for the stop.
|
||||||
|
color: Color,
|
||||||
|
/// The position for the stop.
|
||||||
|
position: LengthPercentage,
|
||||||
|
},
|
||||||
/// An interpolation hint.
|
/// An interpolation hint.
|
||||||
InterpolationHint(LengthPercentage),
|
InterpolationHint(LengthPercentage),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub use self::GenericGradientItem as GradientItem;
|
||||||
|
|
||||||
/// A color stop.
|
/// A color stop.
|
||||||
/// <https://drafts.csswg.org/css-images/#typedef-color-stop-list>
|
/// <https://drafts.csswg.org/css-images/#typedef-color-stop-list>
|
||||||
#[derive(
|
#[derive(
|
||||||
|
@ -154,6 +164,20 @@ pub struct ColorStop<Color, LengthPercentage> {
|
||||||
pub position: Option<LengthPercentage>,
|
pub position: Option<LengthPercentage>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<Color, LengthPercentage> ColorStop<Color, LengthPercentage> {
|
||||||
|
/// Convert the color stop into an appropriate `GradientItem`.
|
||||||
|
#[inline]
|
||||||
|
pub fn into_item(self) -> GradientItem<Color, LengthPercentage> {
|
||||||
|
match self.position {
|
||||||
|
Some(position) => GradientItem::ComplexColorStop {
|
||||||
|
color: self.color,
|
||||||
|
position,
|
||||||
|
},
|
||||||
|
None => GradientItem::SimpleColorStop(self.color),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Specified values for a paint worklet.
|
/// Specified values for a paint worklet.
|
||||||
/// <https://drafts.css-houdini.org/css-paint-api/>
|
/// <https://drafts.css-houdini.org/css-paint-api/>
|
||||||
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
|
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
|
||||||
|
|
|
@ -10,7 +10,7 @@ use crate::properties::{LonghandId, PropertyDeclarationId, PropertyFlags};
|
||||||
use crate::properties::{PropertyId, ShorthandId};
|
use crate::properties::{PropertyId, ShorthandId};
|
||||||
use crate::values::generics::box_::AnimationIterationCount as GenericAnimationIterationCount;
|
use crate::values::generics::box_::AnimationIterationCount as GenericAnimationIterationCount;
|
||||||
use crate::values::generics::box_::Perspective as GenericPerspective;
|
use crate::values::generics::box_::Perspective as GenericPerspective;
|
||||||
use crate::values::generics::box_::VerticalAlign as GenericVerticalAlign;
|
use crate::values::generics::box_::{GenericVerticalAlign, VerticalAlignKeyword};
|
||||||
use crate::values::specified::length::{LengthPercentage, NonNegativeLength};
|
use crate::values::specified::length::{LengthPercentage, NonNegativeLength};
|
||||||
use crate::values::specified::{AllowQuirks, Number};
|
use crate::values::specified::{AllowQuirks, Number};
|
||||||
use crate::values::{CustomIdent, KeyframesName};
|
use crate::values::{CustomIdent, KeyframesName};
|
||||||
|
@ -280,20 +280,9 @@ impl Parse for VerticalAlign {
|
||||||
return Ok(GenericVerticalAlign::Length(lp));
|
return Ok(GenericVerticalAlign::Length(lp));
|
||||||
}
|
}
|
||||||
|
|
||||||
try_match_ident_ignore_ascii_case! { input,
|
Ok(GenericVerticalAlign::Keyword(VerticalAlignKeyword::parse(
|
||||||
"baseline" => Ok(GenericVerticalAlign::Baseline),
|
input,
|
||||||
"sub" => Ok(GenericVerticalAlign::Sub),
|
)?))
|
||||||
"super" => Ok(GenericVerticalAlign::Super),
|
|
||||||
"top" => Ok(GenericVerticalAlign::Top),
|
|
||||||
"text-top" => Ok(GenericVerticalAlign::TextTop),
|
|
||||||
"middle" => Ok(GenericVerticalAlign::Middle),
|
|
||||||
"bottom" => Ok(GenericVerticalAlign::Bottom),
|
|
||||||
"text-bottom" => Ok(GenericVerticalAlign::TextBottom),
|
|
||||||
#[cfg(feature = "gecko")]
|
|
||||||
"-moz-middle-with-baseline" => {
|
|
||||||
Ok(GenericVerticalAlign::MozMiddleWithBaseline)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
//! [image]: https://drafts.csswg.org/css-images/#image-values
|
//! [image]: https://drafts.csswg.org/css-images/#image-values
|
||||||
|
|
||||||
use crate::custom_properties::SpecifiedValue;
|
use crate::custom_properties::SpecifiedValue;
|
||||||
|
#[cfg(feature = "gecko")]
|
||||||
|
use crate::gecko_bindings::structs;
|
||||||
use crate::parser::{Parse, ParserContext};
|
use crate::parser::{Parse, ParserContext};
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
use crate::values::computed::{Context, Position as ComputedPosition, ToComputedValue};
|
use crate::values::computed::{Context, Position as ComputedPosition, ToComputedValue};
|
||||||
|
@ -266,16 +268,6 @@ impl Parse for Gradient {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "gecko")]
|
|
||||||
{
|
|
||||||
use crate::gecko_bindings::structs;
|
|
||||||
if compat_mode == CompatMode::Moz &&
|
|
||||||
!unsafe { structs::StaticPrefs_sVarCache_layout_css_prefixes_gradients }
|
|
||||||
{
|
|
||||||
return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedFunction(func)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let (kind, items) = input.parse_nested_block(|i| {
|
let (kind, items) = input.parse_nested_block(|i| {
|
||||||
let shape = match shape {
|
let shape = match shape {
|
||||||
Shape::Linear => GradientKind::parse_linear(context, i, &mut compat_mode)?,
|
Shape::Linear => GradientKind::parse_linear(context, i, &mut compat_mode)?,
|
||||||
|
@ -492,24 +484,24 @@ impl Gradient {
|
||||||
if reverse_stops {
|
if reverse_stops {
|
||||||
p.reverse();
|
p.reverse();
|
||||||
}
|
}
|
||||||
Ok(generic::GradientItem::ColorStop(generic::ColorStop {
|
Ok(generic::GradientItem::ComplexColorStop {
|
||||||
color: color,
|
color,
|
||||||
position: Some(p.into()),
|
position: p.into(),
|
||||||
}))
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.unwrap_or(vec![]);
|
.unwrap_or(vec![]);
|
||||||
|
|
||||||
if items.is_empty() {
|
if items.is_empty() {
|
||||||
items = vec![
|
items = vec![
|
||||||
generic::GradientItem::ColorStop(generic::ColorStop {
|
generic::GradientItem::ComplexColorStop {
|
||||||
color: Color::transparent().into(),
|
color: Color::transparent().into(),
|
||||||
position: Some(Percentage::zero().into()),
|
position: Percentage::zero().into(),
|
||||||
}),
|
},
|
||||||
generic::GradientItem::ColorStop(generic::ColorStop {
|
generic::GradientItem::ComplexColorStop {
|
||||||
color: Color::transparent().into(),
|
color: Color::transparent().into(),
|
||||||
position: Some(Percentage::hundred().into()),
|
position: Percentage::hundred().into(),
|
||||||
}),
|
},
|
||||||
];
|
];
|
||||||
} else if items.len() == 1 {
|
} else if items.len() == 1 {
|
||||||
let first = items[0].clone();
|
let first = items[0].clone();
|
||||||
|
@ -518,13 +510,16 @@ impl Gradient {
|
||||||
items.sort_by(|a, b| {
|
items.sort_by(|a, b| {
|
||||||
match (a, b) {
|
match (a, b) {
|
||||||
(
|
(
|
||||||
&generic::GradientItem::ColorStop(ref a),
|
&generic::GradientItem::ComplexColorStop {
|
||||||
&generic::GradientItem::ColorStop(ref b),
|
position: ref a_position,
|
||||||
) => match (&a.position, &b.position) {
|
..
|
||||||
(
|
},
|
||||||
&Some(LengthPercentage::Percentage(a)),
|
&generic::GradientItem::ComplexColorStop {
|
||||||
&Some(LengthPercentage::Percentage(b)),
|
position: ref b_position,
|
||||||
) => {
|
..
|
||||||
|
},
|
||||||
|
) => match (a_position, b_position) {
|
||||||
|
(&LengthPercentage::Percentage(a), &LengthPercentage::Percentage(b)) => {
|
||||||
return a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal);
|
return a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal);
|
||||||
},
|
},
|
||||||
_ => {},
|
_ => {},
|
||||||
|
@ -548,6 +543,16 @@ impl Gradient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn simple_moz_gradient() -> bool {
|
||||||
|
#[cfg(feature = "gecko")]
|
||||||
|
unsafe {
|
||||||
|
return structs::StaticPrefs_sVarCache_layout_css_simple_moz_gradient_enabled;
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "gecko"))]
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
impl GradientKind {
|
impl GradientKind {
|
||||||
/// Parses a linear gradient.
|
/// Parses a linear gradient.
|
||||||
/// CompatMode can change during `-moz-` prefixed gradient parsing if it come across a `to` keyword.
|
/// CompatMode can change during `-moz-` prefixed gradient parsing if it come across a `to` keyword.
|
||||||
|
@ -583,16 +588,6 @@ impl GradientKind {
|
||||||
});
|
});
|
||||||
(shape, position.ok(), None, None)
|
(shape, position.ok(), None, None)
|
||||||
},
|
},
|
||||||
CompatMode::WebKit => {
|
|
||||||
let position = input.try(|i| Position::parse(context, i));
|
|
||||||
let shape = input.try(|i| {
|
|
||||||
if position.is_ok() {
|
|
||||||
i.expect_comma()?;
|
|
||||||
}
|
|
||||||
EndingShape::parse(context, i, *compat_mode)
|
|
||||||
});
|
|
||||||
(shape, position.ok(), None, None)
|
|
||||||
},
|
|
||||||
// The syntax of `-moz-` prefixed radial gradient is:
|
// The syntax of `-moz-` prefixed radial gradient is:
|
||||||
// -moz-radial-gradient(
|
// -moz-radial-gradient(
|
||||||
// [ [ <position> || <angle> ]? [ ellipse | [ <length> | <percentage> ]{2} ] , |
|
// [ [ <position> || <angle> ]? [ ellipse | [ <length> | <percentage> ]{2} ] , |
|
||||||
|
@ -603,7 +598,7 @@ impl GradientKind {
|
||||||
// where <extent-keyword> = closest-corner | closest-side | farthest-corner | farthest-side |
|
// where <extent-keyword> = closest-corner | closest-side | farthest-corner | farthest-side |
|
||||||
// cover | contain
|
// cover | contain
|
||||||
// and <color-stop> = <color> [ <percentage> | <length> ]?
|
// and <color-stop> = <color> [ <percentage> | <length> ]?
|
||||||
CompatMode::Moz => {
|
CompatMode::Moz if !simple_moz_gradient() => {
|
||||||
let mut position = input.try(|i| LegacyPosition::parse(context, i));
|
let mut position = input.try(|i| LegacyPosition::parse(context, i));
|
||||||
let angle = input.try(|i| Angle::parse(context, i)).ok();
|
let angle = input.try(|i| Angle::parse(context, i)).ok();
|
||||||
if position.is_err() {
|
if position.is_err() {
|
||||||
|
@ -619,6 +614,16 @@ impl GradientKind {
|
||||||
|
|
||||||
(shape, None, angle, position.ok())
|
(shape, None, angle, position.ok())
|
||||||
},
|
},
|
||||||
|
_ => {
|
||||||
|
let position = input.try(|i| Position::parse(context, i));
|
||||||
|
let shape = input.try(|i| {
|
||||||
|
if position.is_ok() {
|
||||||
|
i.expect_comma()?;
|
||||||
|
}
|
||||||
|
EndingShape::parse(context, i, *compat_mode)
|
||||||
|
});
|
||||||
|
(shape, position.ok(), None, None)
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if shape.is_ok() || position.is_some() || angle.is_some() || moz_position.is_some() {
|
if shape.is_ok() || position.is_some() || angle.is_some() || moz_position.is_some() {
|
||||||
|
@ -631,7 +636,7 @@ impl GradientKind {
|
||||||
|
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
{
|
{
|
||||||
if *compat_mode == CompatMode::Moz {
|
if *compat_mode == CompatMode::Moz && !simple_moz_gradient() {
|
||||||
// If this form can be represented in Modern mode, then convert the compat_mode to Modern.
|
// If this form can be represented in Modern mode, then convert the compat_mode to Modern.
|
||||||
if angle.is_none() {
|
if angle.is_none() {
|
||||||
*compat_mode = CompatMode::Modern;
|
*compat_mode = CompatMode::Modern;
|
||||||
|
@ -751,7 +756,7 @@ impl LineDirection {
|
||||||
input: &mut Parser<'i, 't>,
|
input: &mut Parser<'i, 't>,
|
||||||
compat_mode: &mut CompatMode,
|
compat_mode: &mut CompatMode,
|
||||||
) -> Result<Self, ParseError<'i>> {
|
) -> Result<Self, ParseError<'i>> {
|
||||||
let mut _angle = if *compat_mode == CompatMode::Moz {
|
let mut _angle = if *compat_mode == CompatMode::Moz && !simple_moz_gradient() {
|
||||||
input.try(|i| Angle::parse(context, i)).ok()
|
input.try(|i| Angle::parse(context, i)).ok()
|
||||||
} else {
|
} else {
|
||||||
// Gradients allow unitless zero angles as an exception, see:
|
// Gradients allow unitless zero angles as an exception, see:
|
||||||
|
@ -784,7 +789,7 @@ impl LineDirection {
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
{
|
{
|
||||||
// `-moz-` prefixed linear gradient can be both Angle and Position.
|
// `-moz-` prefixed linear gradient can be both Angle and Position.
|
||||||
if *compat_mode == CompatMode::Moz {
|
if *compat_mode == CompatMode::Moz && !simple_moz_gradient() {
|
||||||
let position = i.try(|i| LegacyPosition::parse(context, i)).ok();
|
let position = i.try(|i| LegacyPosition::parse(context, i)).ok();
|
||||||
if _angle.is_none() {
|
if _angle.is_none() {
|
||||||
_angle = i.try(|i| Angle::parse(context, i)).ok();
|
_angle = i.try(|i| Angle::parse(context, i)).ok();
|
||||||
|
@ -874,7 +879,7 @@ impl EndingShape {
|
||||||
}
|
}
|
||||||
// -moz- prefixed radial gradient doesn't allow EndingShape's Length or LengthPercentage
|
// -moz- prefixed radial gradient doesn't allow EndingShape's Length or LengthPercentage
|
||||||
// to come before shape keyword. Otherwise it conflicts with <position>.
|
// to come before shape keyword. Otherwise it conflicts with <position>.
|
||||||
if compat_mode != CompatMode::Moz {
|
if compat_mode != CompatMode::Moz || simple_moz_gradient() {
|
||||||
if let Ok(length) = input.try(|i| Length::parse(context, i)) {
|
if let Ok(length) = input.try(|i| Length::parse(context, i)) {
|
||||||
if let Ok(y) = input.try(|i| LengthPercentage::parse(context, i)) {
|
if let Ok(y) = input.try(|i| LengthPercentage::parse(context, i)) {
|
||||||
if compat_mode == CompatMode::Modern {
|
if compat_mode == CompatMode::Modern {
|
||||||
|
@ -958,13 +963,16 @@ impl GradientItem {
|
||||||
|
|
||||||
if let Ok(multi_position) = input.try(|i| LengthPercentage::parse(context, i)) {
|
if let Ok(multi_position) = input.try(|i| LengthPercentage::parse(context, i)) {
|
||||||
let stop_color = stop.color.clone();
|
let stop_color = stop.color.clone();
|
||||||
items.push(generic::GradientItem::ColorStop(stop));
|
items.push(stop.into_item());
|
||||||
items.push(generic::GradientItem::ColorStop(ColorStop {
|
items.push(
|
||||||
color: stop_color,
|
ColorStop {
|
||||||
position: Some(multi_position),
|
color: stop_color,
|
||||||
}));
|
position: Some(multi_position),
|
||||||
|
}
|
||||||
|
.into_item(),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
items.push(generic::GradientItem::ColorStop(stop));
|
items.push(stop.into_item());
|
||||||
}
|
}
|
||||||
|
|
||||||
seen_stop = true;
|
seen_stop = true;
|
||||||
|
|
|
@ -80,6 +80,7 @@ pub use self::svg::{SVGLength, SVGOpacity, SVGPaint, SVGPaintKind};
|
||||||
pub use self::svg::{SVGPaintOrder, SVGStrokeDashArray, SVGWidth};
|
pub use self::svg::{SVGPaintOrder, SVGStrokeDashArray, SVGWidth};
|
||||||
pub use self::svg_path::SVGPathData;
|
pub use self::svg_path::SVGPathData;
|
||||||
pub use self::table::XSpan;
|
pub use self::table::XSpan;
|
||||||
|
pub use self::text::TextTransform;
|
||||||
pub use self::text::{InitialLetter, LetterSpacing, LineHeight, TextAlign};
|
pub use self::text::{InitialLetter, LetterSpacing, LineHeight, TextAlign};
|
||||||
pub use self::text::{OverflowWrap, TextEmphasisPosition, TextEmphasisStyle, WordBreak};
|
pub use self::text::{OverflowWrap, TextEmphasisPosition, TextEmphasisStyle, WordBreak};
|
||||||
pub use self::text::{TextAlignKeyword, TextDecorationLine, TextOverflow, WordSpacing};
|
pub use self::text::{TextAlignKeyword, TextDecorationLine, TextOverflow, WordSpacing};
|
||||||
|
|
|
@ -460,6 +460,11 @@ pub enum AutoFlow {
|
||||||
Column,
|
Column,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If `dense` is specified, `row` is implied.
|
||||||
|
fn is_row_dense(autoflow: &AutoFlow, dense: &bool) -> bool {
|
||||||
|
*autoflow == AutoFlow::Row && *dense
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Clone,
|
Clone,
|
||||||
Copy,
|
Copy,
|
||||||
|
@ -477,6 +482,7 @@ pub enum AutoFlow {
|
||||||
/// specifying exactly how auto-placed items get flowed into the grid
|
/// specifying exactly how auto-placed items get flowed into the grid
|
||||||
pub struct GridAutoFlow {
|
pub struct GridAutoFlow {
|
||||||
/// Specifiy how auto-placement algorithm fills each `row` or `column` in turn
|
/// Specifiy how auto-placement algorithm fills each `row` or `column` in turn
|
||||||
|
#[css(contextual_skip_if = "is_row_dense")]
|
||||||
pub autoflow: AutoFlow,
|
pub autoflow: AutoFlow,
|
||||||
/// Specify use `dense` packing algorithm or not
|
/// Specify use `dense` packing algorithm or not
|
||||||
#[css(represents_keyword)]
|
#[css(represents_keyword)]
|
||||||
|
|
|
@ -351,6 +351,177 @@ impl TextDecorationLine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(
|
||||||
|
Clone,
|
||||||
|
Copy,
|
||||||
|
Debug,
|
||||||
|
Eq,
|
||||||
|
MallocSizeOf,
|
||||||
|
PartialEq,
|
||||||
|
SpecifiedValueInfo,
|
||||||
|
ToComputedValue,
|
||||||
|
ToResolvedValue,
|
||||||
|
ToShmem,
|
||||||
|
)]
|
||||||
|
#[repr(C)]
|
||||||
|
/// Specified value of the text-transform property, stored in two parts:
|
||||||
|
/// the case-related transforms (mutually exclusive, only one may be in effect), and others (non-exclusive).
|
||||||
|
pub struct TextTransform {
|
||||||
|
/// Case transform, if any.
|
||||||
|
pub case_: TextTransformCase,
|
||||||
|
/// Non-case transforms.
|
||||||
|
pub other_: TextTransformOther,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextTransform {
|
||||||
|
#[inline]
|
||||||
|
/// Returns the initial value of text-transform
|
||||||
|
pub fn none() -> Self {
|
||||||
|
TextTransform {
|
||||||
|
case_: TextTransformCase::None,
|
||||||
|
other_: TextTransformOther::empty(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
/// Returns whether the value is 'none'
|
||||||
|
pub fn is_none(&self) -> bool {
|
||||||
|
self.case_ == TextTransformCase::None && self.other_.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for TextTransform {
|
||||||
|
fn parse<'i, 't>(
|
||||||
|
_context: &ParserContext,
|
||||||
|
input: &mut Parser<'i, 't>,
|
||||||
|
) -> Result<Self, ParseError<'i>> {
|
||||||
|
let mut result = TextTransform::none();
|
||||||
|
|
||||||
|
// Case keywords are mutually exclusive; other transforms may co-occur.
|
||||||
|
loop {
|
||||||
|
let location = input.current_source_location();
|
||||||
|
let ident = match input.next() {
|
||||||
|
Ok(&Token::Ident(ref ident)) => ident,
|
||||||
|
Ok(other) => return Err(location.new_unexpected_token_error(other.clone())),
|
||||||
|
Err(..) => break,
|
||||||
|
};
|
||||||
|
|
||||||
|
match_ignore_ascii_case! { ident,
|
||||||
|
"none" if result.is_none() => {
|
||||||
|
return Ok(result);
|
||||||
|
},
|
||||||
|
"uppercase" if result.case_ == TextTransformCase::None => {
|
||||||
|
result.case_ = TextTransformCase::Uppercase
|
||||||
|
},
|
||||||
|
"lowercase" if result.case_ == TextTransformCase::None => {
|
||||||
|
result.case_ = TextTransformCase::Lowercase
|
||||||
|
},
|
||||||
|
"capitalize" if result.case_ == TextTransformCase::None => {
|
||||||
|
result.case_ = TextTransformCase::Capitalize
|
||||||
|
},
|
||||||
|
"full-width" if !result.other_.intersects(TextTransformOther::FULL_WIDTH) => {
|
||||||
|
result.other_.insert(TextTransformOther::FULL_WIDTH)
|
||||||
|
},
|
||||||
|
"full-size-kana" if !result.other_.intersects(TextTransformOther::FULL_SIZE_KANA) => {
|
||||||
|
result.other_.insert(TextTransformOther::FULL_SIZE_KANA)
|
||||||
|
}
|
||||||
|
_ => return Err(location.new_custom_error(
|
||||||
|
SelectorParseErrorKind::UnexpectedIdent(ident.clone())
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.is_none() {
|
||||||
|
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
|
||||||
|
} else {
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToCss for TextTransform {
|
||||||
|
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||||
|
where
|
||||||
|
W: Write,
|
||||||
|
{
|
||||||
|
if self.is_none() {
|
||||||
|
return dest.write_str("none");
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.case_ != TextTransformCase::None {
|
||||||
|
self.case_.to_css(dest)?;
|
||||||
|
if !self.other_.is_empty() {
|
||||||
|
dest.write_str(" ")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.other_.to_css(dest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(
|
||||||
|
Clone,
|
||||||
|
Copy,
|
||||||
|
Debug,
|
||||||
|
Eq,
|
||||||
|
MallocSizeOf,
|
||||||
|
PartialEq,
|
||||||
|
SpecifiedValueInfo,
|
||||||
|
ToComputedValue,
|
||||||
|
ToCss,
|
||||||
|
ToResolvedValue,
|
||||||
|
ToShmem,
|
||||||
|
)]
|
||||||
|
#[repr(C)]
|
||||||
|
/// Specified keyword values for case transforms in the text-transform property. (These are exclusive.)
|
||||||
|
pub enum TextTransformCase {
|
||||||
|
/// No case transform.
|
||||||
|
None,
|
||||||
|
/// All uppercase.
|
||||||
|
Uppercase,
|
||||||
|
/// All lowercase.
|
||||||
|
Lowercase,
|
||||||
|
/// Capitalize each word.
|
||||||
|
Capitalize,
|
||||||
|
}
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
#[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)]
|
||||||
|
#[value_info(other_values = "none,full-width,full-size-kana")]
|
||||||
|
#[repr(C)]
|
||||||
|
/// Specified keyword values for non-case transforms in the text-transform property. (Non-exclusive.)
|
||||||
|
pub struct TextTransformOther: u8 {
|
||||||
|
/// full-width
|
||||||
|
const FULL_WIDTH = 1 << 0;
|
||||||
|
/// full-size-kana
|
||||||
|
const FULL_SIZE_KANA = 1 << 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToCss for TextTransformOther {
|
||||||
|
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||||
|
where
|
||||||
|
W: Write,
|
||||||
|
{
|
||||||
|
let mut writer = SequenceWriter::new(dest, " ");
|
||||||
|
let mut any = false;
|
||||||
|
macro_rules! maybe_write {
|
||||||
|
($ident:ident => $str:expr) => {
|
||||||
|
if self.contains(TextTransformOther::$ident) {
|
||||||
|
writer.raw_item($str)?;
|
||||||
|
any = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
maybe_write!(FULL_WIDTH => "full-width");
|
||||||
|
maybe_write!(FULL_SIZE_KANA => "full-size-kana");
|
||||||
|
|
||||||
|
debug_assert!(any || self.is_empty());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Specified value of text-align keyword value.
|
/// Specified value of text-align keyword value.
|
||||||
#[derive(
|
#[derive(
|
||||||
Clone,
|
Clone,
|
||||||
|
@ -394,8 +565,9 @@ pub enum TextAlignKeyword {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specified value of text-align property.
|
/// Specified value of text-align property.
|
||||||
#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
|
#[derive(
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
|
Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem,
|
||||||
|
)]
|
||||||
pub enum TextAlign {
|
pub enum TextAlign {
|
||||||
/// Keyword value of text-align property.
|
/// Keyword value of text-align property.
|
||||||
Keyword(TextAlignKeyword),
|
Keyword(TextAlignKeyword),
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
[text-transform-valid.html]
|
|
||||||
[e.style['text-transform'\] = "full-size-kana full-width capitalize" should set the property value]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[e.style['text-transform'\] = "full-width" should set the property value]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[e.style['text-transform'\] = "capitalize full-width" should set the property value]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[e.style['text-transform'\] = "full-size-kana full-width" should set the property value]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[e.style['text-transform'\] = "capitalize full-width full-size-kana" should set the property value]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[e.style['text-transform'\] = "full-width full-size-kana" should set the property value]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[e.style['text-transform'\] = "full-size-kana lowercase full-width" should set the property value]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[e.style['text-transform'\] = "full-size-kana capitalize" should set the property value]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[e.style['text-transform'\] = "lowercase full-size-kana full-width" should set the property value]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[e.style['text-transform'\] = "full-width lowercase" should set the property value]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[e.style['text-transform'\] = "full-size-kana" should set the property value]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[e.style['text-transform'\] = "full-width full-size-kana uppercase" should set the property value]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[e.style['text-transform'\] = "uppercase full-size-kana" should set the property value]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[e.style['text-transform'\] = "full-width uppercase full-size-kana" should set the property value]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
[wide-keyword-fallback.html]
|
|
||||||
expected: FAIL
|
|
Loading…
Add table
Add a link
Reference in a new issue