mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
This reverts commit 8e15389cae
.
This commit is contained in:
parent
8e15389cae
commit
d6ae8dc112
152 changed files with 4622 additions and 5862 deletions
17
Cargo.lock
generated
17
Cargo.lock
generated
|
@ -5372,7 +5372,6 @@ dependencies = [
|
|||
"phf_codegen",
|
||||
"precomputed-hash",
|
||||
"servo_arc",
|
||||
"size_of_test",
|
||||
"smallvec",
|
||||
"to_shmem",
|
||||
"to_shmem_derive",
|
||||
|
@ -5916,9 +5915,6 @@ checksum = "fa8f3741c7372e75519bd9346068370c9cdaabcc1f9599cbcf2a2719352286b7"
|
|||
[[package]]
|
||||
name = "size_of_test"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
|
@ -6046,12 +6042,6 @@ version = "1.1.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8"
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "std_test_override"
|
||||
version = "0.0.1"
|
||||
|
@ -6155,7 +6145,6 @@ dependencies = [
|
|||
"servo_url",
|
||||
"smallbitvec",
|
||||
"smallvec",
|
||||
"static_assertions",
|
||||
"string_cache",
|
||||
"style_derive",
|
||||
"style_traits",
|
||||
|
@ -6197,6 +6186,7 @@ dependencies = [
|
|||
"servo_atoms",
|
||||
"servo_config",
|
||||
"servo_url",
|
||||
"size_of_test",
|
||||
"std_test_override",
|
||||
"style",
|
||||
"style_traits",
|
||||
|
@ -6218,7 +6208,6 @@ dependencies = [
|
|||
"servo_arc",
|
||||
"servo_atoms",
|
||||
"servo_url",
|
||||
"size_of_test",
|
||||
"to_shmem",
|
||||
"to_shmem_derive",
|
||||
"webrender_api",
|
||||
|
@ -6702,9 +6691,9 @@ checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81"
|
|||
|
||||
[[package]]
|
||||
name = "uluru"
|
||||
version = "3.0.0"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "794a32261a1f5eb6a4462c81b59cec87b5c27d5deea7dd1ac8fc781c41d226db"
|
||||
checksum = "308dcc9d947b227796f851adb99936fb060681a89c002c9c1928404a3b6c2d72"
|
||||
dependencies = [
|
||||
"arrayvec 0.7.1",
|
||||
]
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
-moz-content-preferred-color-scheme
|
||||
-moz-gtk-csd-close-button-position
|
||||
-moz-gtk-csd-maximize-button-position
|
||||
-moz-gtk-csd-menu-radius
|
||||
|
@ -13,9 +12,7 @@ animationcancel
|
|||
animationend
|
||||
animationiteration
|
||||
animationstart
|
||||
aspect-ratio
|
||||
beforeunload
|
||||
block-size
|
||||
button
|
||||
canplay
|
||||
canplaythrough
|
||||
|
@ -53,13 +50,11 @@ fullscreenchange
|
|||
fullscreenerror
|
||||
gattserverdisconnected
|
||||
hashchange
|
||||
height
|
||||
hidden
|
||||
icecandidate
|
||||
iceconnectionstatechange
|
||||
icegatheringstatechange
|
||||
image
|
||||
inline-size
|
||||
input
|
||||
inputsourceschange
|
||||
invalid
|
||||
|
@ -87,7 +82,6 @@ none
|
|||
number
|
||||
onchange
|
||||
open
|
||||
orientation
|
||||
pagehide
|
||||
pageshow
|
||||
password
|
||||
|
|
|
@ -373,11 +373,6 @@ pub fn to_css_identifier(mut camel_case: &str) -> String {
|
|||
result
|
||||
}
|
||||
|
||||
/// Transforms foo-bar to FOO_BAR.
|
||||
pub fn to_scream_case(css_case: &str) -> String {
|
||||
css_case.to_uppercase().replace('-', "_")
|
||||
}
|
||||
|
||||
/// Given "FooBar", returns "Foo" and sets `camel_case` to "Bar".
|
||||
fn split_camel_segment<'input>(camel_case: &mut &'input str) -> Option<&'input str> {
|
||||
let index = match camel_case.chars().next() {
|
||||
|
|
|
@ -44,7 +44,6 @@ servo_atoms = { path = "../atoms" }
|
|||
servo_config = { path = "../config" }
|
||||
servo_geometry = { path = "../geometry" }
|
||||
servo_url = { path = "../url" }
|
||||
size_of_test = { path = "../size_of_test" }
|
||||
smallvec = { workspace = true, features = ["union"] }
|
||||
style = { path = "../style", features = ["servo"] }
|
||||
style_traits = { path = "../style_traits" }
|
||||
|
@ -53,3 +52,6 @@ unicode-script = { workspace = true }
|
|||
webrender_api = { workspace = true }
|
||||
xi-unicode = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
size_of_test = { path = "../size_of_test" }
|
||||
|
||||
|
|
|
@ -653,7 +653,7 @@ impl Fragment {
|
|||
absolute_bounds: Rect<Au>,
|
||||
) {
|
||||
let background = style.get_background();
|
||||
let background_color = style.resolve_color(background.background_color.clone());
|
||||
let background_color = style.resolve_color(background.background_color);
|
||||
// XXXManishearth the below method should ideally use an iterator over
|
||||
// backgrounds
|
||||
self.build_display_list_for_background_if_applicable_with_background(
|
||||
|
@ -1037,9 +1037,7 @@ impl Fragment {
|
|||
webrender_api::BoxShadowDisplayItem {
|
||||
common: items::empty_common_item_properties(),
|
||||
box_bounds: absolute_bounds.to_layout(),
|
||||
color: style
|
||||
.resolve_color(box_shadow.base.color.clone())
|
||||
.to_layout(),
|
||||
color: style.resolve_color(box_shadow.base.color).to_layout(),
|
||||
offset: LayoutVector2D::new(
|
||||
box_shadow.base.horizontal.px(),
|
||||
box_shadow.base.vertical.px(),
|
||||
|
@ -1085,10 +1083,10 @@ impl Fragment {
|
|||
|
||||
let border_style_struct = style.get_border();
|
||||
let mut colors = SideOffsets2D::new(
|
||||
border_style_struct.border_top_color.clone(),
|
||||
border_style_struct.border_right_color.clone(),
|
||||
border_style_struct.border_bottom_color.clone(),
|
||||
border_style_struct.border_left_color.clone(),
|
||||
border_style_struct.border_top_color,
|
||||
border_style_struct.border_right_color,
|
||||
border_style_struct.border_bottom_color,
|
||||
border_style_struct.border_left_color,
|
||||
);
|
||||
let mut border_style = SideOffsets2D::new(
|
||||
border_style_struct.border_top_style,
|
||||
|
@ -1318,7 +1316,7 @@ impl Fragment {
|
|||
|
||||
// Append the outline to the display list.
|
||||
let color = style
|
||||
.resolve_color(style.get_outline().outline_color.clone())
|
||||
.resolve_color(style.get_outline().outline_color)
|
||||
.to_layout();
|
||||
let base = state.create_base_display_item(
|
||||
clip,
|
||||
|
@ -1453,8 +1451,7 @@ impl Fragment {
|
|||
// TODO: Allow non-text fragments to be selected too.
|
||||
if scanned_text_fragment_info.selected() {
|
||||
let style = self.selected_style();
|
||||
let background_color =
|
||||
style.resolve_color(style.get_background().background_color.clone());
|
||||
let background_color = style.resolve_color(style.get_background().background_color);
|
||||
let base = state.create_base_display_item(
|
||||
stacking_relative_border_box,
|
||||
self.node,
|
||||
|
@ -2058,7 +2055,7 @@ impl Fragment {
|
|||
base: base.clone(),
|
||||
shadow: webrender_api::Shadow {
|
||||
offset: LayoutVector2D::new(shadow.horizontal.px(), shadow.vertical.px()),
|
||||
color: self.style.resolve_color(shadow.color.clone()).to_layout(),
|
||||
color: self.style.resolve_color(shadow.color).to_layout(),
|
||||
blur_radius: shadow.blur.px(),
|
||||
},
|
||||
},
|
||||
|
|
|
@ -86,7 +86,7 @@ fn convert_gradient_stops(
|
|||
// Only keep the color stops, discard the color interpolation hints.
|
||||
let mut stop_items = gradient_items
|
||||
.iter()
|
||||
.filter_map(|item| match item {
|
||||
.filter_map(|item| match *item {
|
||||
GradientItem::SimpleColorStop(color) => Some(ColorStop {
|
||||
color,
|
||||
position: None,
|
||||
|
@ -191,7 +191,7 @@ fn convert_gradient_stops(
|
|||
assert!(offset.is_finite());
|
||||
stops.push(GradientStop {
|
||||
offset: offset,
|
||||
color: style.resolve_color(stop.color.clone()).to_layout(),
|
||||
color: style.resolve_color(stop.color).to_layout(),
|
||||
})
|
||||
}
|
||||
stops
|
||||
|
|
|
@ -158,11 +158,6 @@ pub struct Fragment {
|
|||
pub established_reference_frame: Option<ClipScrollNodeIndex>,
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
size_of_test!(Fragment, 176);
|
||||
#[cfg(not(debug_assertions))]
|
||||
size_of_test!(Fragment, 152);
|
||||
|
||||
impl Serialize for Fragment {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
let mut serializer = serializer.serialize_struct("fragment", 3)?;
|
||||
|
@ -217,8 +212,6 @@ pub enum SpecificFragmentInfo {
|
|||
TruncatedFragment(Box<TruncatedFragmentInfo>),
|
||||
}
|
||||
|
||||
size_of_test!(SpecificFragmentInfo, 24);
|
||||
|
||||
impl SpecificFragmentInfo {
|
||||
fn restyle_damage(&self) -> RestyleDamage {
|
||||
let flow = match *self {
|
||||
|
|
|
@ -16,8 +16,6 @@ extern crate log;
|
|||
extern crate range;
|
||||
#[macro_use]
|
||||
extern crate serde;
|
||||
#[macro_use]
|
||||
extern crate size_of_test;
|
||||
|
||||
#[macro_use]
|
||||
pub mod layout_debug;
|
||||
|
|
|
@ -860,7 +860,7 @@ where
|
|||
shared: &context.style_context,
|
||||
thread_local: &mut tlc,
|
||||
};
|
||||
let styles = resolve_style(&mut context, element, RuleInclusion::All, None, None);
|
||||
let styles = resolve_style(&mut context, element, RuleInclusion::All, None);
|
||||
styles.primary().clone()
|
||||
}
|
||||
} else {
|
||||
|
@ -916,13 +916,7 @@ pub fn process_resolved_style_request<'dom>(
|
|||
thread_local: &mut tlc,
|
||||
};
|
||||
|
||||
let styles = resolve_style(
|
||||
&mut context,
|
||||
element,
|
||||
RuleInclusion::All,
|
||||
pseudo.as_ref(),
|
||||
None,
|
||||
);
|
||||
let styles = resolve_style(&mut context, element, RuleInclusion::All, pseudo.as_ref());
|
||||
let style = styles.primary();
|
||||
let longhand_id = match *property {
|
||||
PropertyId::LonghandAlias(id, _) | PropertyId::Longhand(id) => id,
|
||||
|
|
|
@ -794,7 +794,7 @@ fn perform_border_collapse_for_row(
|
|||
child_table_row
|
||||
.final_collapsed_borders
|
||||
.inline
|
||||
.push_or_set(i, this_inline_border.clone());
|
||||
.push_or_set(i, *this_inline_border);
|
||||
if i == 0 {
|
||||
child_table_row.final_collapsed_borders.inline[i].combine(&table_inline_borders.start);
|
||||
} else if i + 1 == number_of_borders_inline_direction {
|
||||
|
@ -821,7 +821,7 @@ fn perform_border_collapse_for_row(
|
|||
this_border.combine(&previous_block_borders[i]);
|
||||
}
|
||||
},
|
||||
PreviousBlockCollapsedBorders::FromTable(ref table_border) => {
|
||||
PreviousBlockCollapsedBorders::FromTable(table_border) => {
|
||||
this_border.combine(&table_border);
|
||||
},
|
||||
}
|
||||
|
@ -837,7 +837,7 @@ fn perform_border_collapse_for_row(
|
|||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
let next_block = next_block.push_or_set(i, this_block_border.clone());
|
||||
let next_block = next_block.push_or_set(i, *this_block_border);
|
||||
match next_block_borders {
|
||||
NextBlockCollapsedBorders::FromNextRow(next_block_borders) => {
|
||||
if next_block_borders.len() > i {
|
||||
|
@ -1358,7 +1358,7 @@ impl<'table> TableCellStyleInfo<'table> {
|
|||
if background as *const Background == initial.get_background() as *const _ {
|
||||
return;
|
||||
}
|
||||
let background_color = sty.resolve_color(background.background_color.clone());
|
||||
let background_color = sty.resolve_color(background.background_color);
|
||||
cell_flow.build_display_list_for_background_if_applicable_with_background(
|
||||
state,
|
||||
background,
|
||||
|
|
|
@ -375,7 +375,7 @@ impl fmt::Debug for TableCellFlow {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[derive(Clone, Copy, Debug, Serialize)]
|
||||
pub struct CollapsedBordersForCell {
|
||||
pub inline_start_border: CollapsedBorder,
|
||||
pub inline_end_border: CollapsedBorder,
|
||||
|
@ -494,10 +494,10 @@ impl CollapsedBordersForCell {
|
|||
) {
|
||||
let logical_border_colors = LogicalMargin::new(
|
||||
writing_mode,
|
||||
self.block_start_border.color.clone(),
|
||||
self.inline_end_border.color.clone(),
|
||||
self.block_end_border.color.clone(),
|
||||
self.inline_start_border.color.clone(),
|
||||
self.block_start_border.color,
|
||||
self.inline_end_border.color,
|
||||
self.block_end_border.color,
|
||||
self.inline_start_border.color,
|
||||
);
|
||||
*border_colors = logical_border_colors.to_physical(writing_mode);
|
||||
|
||||
|
|
|
@ -718,7 +718,7 @@ impl CollapsedBorderSpacingForRow {
|
|||
}
|
||||
|
||||
/// All aspects of a border that can collapse with adjacent borders. See CSS 2.1 § 17.6.2.1.
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct CollapsedBorder {
|
||||
/// The style of the border.
|
||||
pub style: BorderStyle,
|
||||
|
@ -771,7 +771,7 @@ impl CollapsedBorder {
|
|||
CollapsedBorder {
|
||||
style: css_style.get_border().border_top_style,
|
||||
width: Au::from(css_style.get_border().border_top_width),
|
||||
color: css_style.get_border().border_top_color.clone(),
|
||||
color: css_style.get_border().border_top_color,
|
||||
provenance: provenance,
|
||||
}
|
||||
}
|
||||
|
@ -782,7 +782,7 @@ impl CollapsedBorder {
|
|||
CollapsedBorder {
|
||||
style: css_style.get_border().border_right_style,
|
||||
width: Au::from(css_style.get_border().border_right_width),
|
||||
color: css_style.get_border().border_right_color.clone(),
|
||||
color: css_style.get_border().border_right_color,
|
||||
provenance: provenance,
|
||||
}
|
||||
}
|
||||
|
@ -796,7 +796,7 @@ impl CollapsedBorder {
|
|||
CollapsedBorder {
|
||||
style: css_style.get_border().border_bottom_style,
|
||||
width: Au::from(css_style.get_border().border_bottom_width),
|
||||
color: css_style.get_border().border_bottom_color.clone(),
|
||||
color: css_style.get_border().border_bottom_color,
|
||||
provenance: provenance,
|
||||
}
|
||||
}
|
||||
|
@ -807,7 +807,7 @@ impl CollapsedBorder {
|
|||
CollapsedBorder {
|
||||
style: css_style.get_border().border_left_style,
|
||||
width: Au::from(css_style.get_border().border_left_width),
|
||||
color: css_style.get_border().border_left_color.clone(),
|
||||
color: css_style.get_border().border_left_color,
|
||||
provenance: provenance,
|
||||
}
|
||||
}
|
||||
|
@ -883,18 +883,18 @@ impl CollapsedBorder {
|
|||
match (self.style, other.style) {
|
||||
// Step 1.
|
||||
(BorderStyle::Hidden, _) => {},
|
||||
(_, BorderStyle::Hidden) => *self = other.clone(),
|
||||
(_, BorderStyle::Hidden) => *self = *other,
|
||||
// Step 2.
|
||||
(BorderStyle::None, _) => *self = other.clone(),
|
||||
(BorderStyle::None, _) => *self = *other,
|
||||
(_, BorderStyle::None) => {},
|
||||
// Step 3.
|
||||
_ if self.width > other.width => {},
|
||||
_ if self.width < other.width => *self = other.clone(),
|
||||
_ if self.width < other.width => *self = *other,
|
||||
(this_style, other_style) if this_style > other_style => {},
|
||||
(this_style, other_style) if this_style < other_style => *self = other.clone(),
|
||||
(this_style, other_style) if this_style < other_style => *self = *other,
|
||||
// Step 4.
|
||||
_ if (self.provenance as i8) >= other.provenance as i8 => {},
|
||||
_ => *self = other.clone(),
|
||||
_ => *self = *other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1013,22 +1013,22 @@ fn set_inline_position_of_child_flow(
|
|||
.collapsed_borders_for_row
|
||||
.inline
|
||||
.get(child_index)
|
||||
.map_or(CollapsedBorder::new(), |x| x.clone()),
|
||||
.map_or(CollapsedBorder::new(), |x| *x),
|
||||
inline_end_border: border_collapse_info
|
||||
.collapsed_borders_for_row
|
||||
.inline
|
||||
.get(child_index + 1)
|
||||
.map_or(CollapsedBorder::new(), |x| x.clone()),
|
||||
.map_or(CollapsedBorder::new(), |x| *x),
|
||||
block_start_border: border_collapse_info
|
||||
.collapsed_borders_for_row
|
||||
.block_start
|
||||
.get(child_index)
|
||||
.map_or(CollapsedBorder::new(), |x| x.clone()),
|
||||
.map_or(CollapsedBorder::new(), |x| *x),
|
||||
block_end_border: border_collapse_info
|
||||
.collapsed_borders_for_row
|
||||
.block_end
|
||||
.get(child_index)
|
||||
.map_or(CollapsedBorder::new(), |x| x.clone()),
|
||||
.map_or(CollapsedBorder::new(), |x| *x),
|
||||
inline_start_width: border_collapse_info
|
||||
.collapsed_border_spacing_for_row
|
||||
.inline
|
||||
|
|
21
components/layout/tests/size_of.rs
Normal file
21
components/layout/tests/size_of.rs
Normal file
|
@ -0,0 +1,21 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#![cfg(target_pointer_width = "64")]
|
||||
|
||||
#[macro_use]
|
||||
extern crate size_of_test;
|
||||
|
||||
use layout_2013::Fragment;
|
||||
use layout_2013::SpecificFragmentInfo;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
size_of_test!(test_size_of_fragment, Fragment, 176);
|
||||
#[cfg(not(debug_assertions))]
|
||||
size_of_test!(test_size_of_fragment, Fragment, 152);
|
||||
size_of_test!(
|
||||
test_size_of_specific_fragment_info,
|
||||
SpecificFragmentInfo,
|
||||
24
|
||||
);
|
|
@ -275,11 +275,11 @@ fn fixup_stops(
|
|||
for item in items {
|
||||
match item {
|
||||
GradientItem::SimpleColorStop(color) => stops.push(ColorStop {
|
||||
color: super::rgba(style.resolve_color(color.clone())),
|
||||
color: super::rgba(style.resolve_color(*color)),
|
||||
position: None,
|
||||
}),
|
||||
GradientItem::ComplexColorStop { color, position } => stops.push(ColorStop {
|
||||
color: super::rgba(style.resolve_color(color.clone())),
|
||||
color: super::rgba(style.resolve_color(*color)),
|
||||
position: Some(if gradient_line_length.px() == 0. {
|
||||
0.
|
||||
} else {
|
||||
|
|
|
@ -352,7 +352,7 @@ impl Fragment {
|
|||
let text_decoration_color = fragment
|
||||
.parent_style
|
||||
.clone_text_decoration_color()
|
||||
.into_rgba(color);
|
||||
.to_rgba(color);
|
||||
let text_decoration_style = fragment.parent_style.clone_text_decoration_style();
|
||||
if text_decoration_style == ComputedTextDecorationStyle::MozNone {
|
||||
return;
|
||||
|
@ -535,7 +535,7 @@ impl<'a> BuilderForBoxFragment<'a> {
|
|||
let source = background::Source::Fragment;
|
||||
let style = &self.fragment.style;
|
||||
let b = style.get_background();
|
||||
let background_color = style.resolve_color(b.background_color.clone());
|
||||
let background_color = style.resolve_color(b.background_color);
|
||||
if background_color.alpha > 0 {
|
||||
// https://drafts.csswg.org/css-backgrounds/#background-color
|
||||
// “The background color is clipped according to the background-clip
|
||||
|
@ -685,15 +685,10 @@ impl<'a> BuilderForBoxFragment<'a> {
|
|||
}
|
||||
let common = builder.common_properties(self.border_rect, &self.fragment.style);
|
||||
let details = wr::BorderDetails::Normal(wr::NormalBorder {
|
||||
top: self.build_border_side(border.border_top_style, border.border_top_color.clone()),
|
||||
right: self
|
||||
.build_border_side(border.border_right_style, border.border_right_color.clone()),
|
||||
bottom: self.build_border_side(
|
||||
border.border_bottom_style,
|
||||
border.border_bottom_color.clone(),
|
||||
),
|
||||
left: self
|
||||
.build_border_side(border.border_left_style, border.border_left_color.clone()),
|
||||
top: self.build_border_side(border.border_top_style, border.border_top_color),
|
||||
right: self.build_border_side(border.border_right_style, border.border_right_color),
|
||||
bottom: self.build_border_side(border.border_bottom_style, border.border_bottom_color),
|
||||
left: self.build_border_side(border.border_left_style, border.border_left_color),
|
||||
radius: self.border_radius,
|
||||
do_aa: true,
|
||||
});
|
||||
|
@ -723,7 +718,7 @@ impl<'a> BuilderForBoxFragment<'a> {
|
|||
OutlineStyle::Auto => BorderStyle::Solid,
|
||||
OutlineStyle::BorderStyle(s) => s,
|
||||
};
|
||||
let side = self.build_border_side(style, outline.outline_color.clone());
|
||||
let side = self.build_border_side(style, outline.outline_color);
|
||||
let details = wr::BorderDetails::Normal(wr::NormalBorder {
|
||||
top: side,
|
||||
right: side,
|
||||
|
|
|
@ -404,7 +404,7 @@ impl StackingContext {
|
|||
.union(&fragment_tree.scrollable_overflow)
|
||||
.to_webrender();
|
||||
|
||||
let background_color = style.resolve_color(style.get_background().background_color.clone());
|
||||
let background_color = style.resolve_color(style.get_background().background_color);
|
||||
if background_color.alpha > 0 {
|
||||
let common = builder.common_properties(painting_area, &style);
|
||||
let color = super::rgba(background_color);
|
||||
|
|
|
@ -361,13 +361,7 @@ pub fn process_resolved_style_request_for_unstyled_node<'dom>(
|
|||
};
|
||||
|
||||
let element = node.as_element().unwrap();
|
||||
let styles = resolve_style(
|
||||
&mut context,
|
||||
element,
|
||||
RuleInclusion::All,
|
||||
pseudo.as_ref(),
|
||||
None,
|
||||
);
|
||||
let styles = resolve_style(&mut context, element, RuleInclusion::All, pseudo.as_ref());
|
||||
let style = styles.primary();
|
||||
let longhand_id = match *property {
|
||||
PropertyId::LonghandAlias(id, _) | PropertyId::Longhand(id) => id,
|
||||
|
|
|
@ -465,7 +465,7 @@ impl ComputedValuesExt for ComputedValues {
|
|||
/// Whether or not this style specifies a non-transparent background.
|
||||
fn background_is_transparent(&self) -> bool {
|
||||
let background = self.get_background();
|
||||
let color = self.resolve_color(background.background_color.clone());
|
||||
let color = self.resolve_color(background.background_color);
|
||||
color.alpha == 0 &&
|
||||
background
|
||||
.background_image
|
||||
|
|
|
@ -1796,8 +1796,7 @@ fn get_root_flow_background_color(flow: &mut dyn Flow) -> ColorF {
|
|||
.fragment
|
||||
.style
|
||||
.get_background()
|
||||
.background_color
|
||||
.clone(),
|
||||
.background_color,
|
||||
);
|
||||
ColorF::new(
|
||||
color.red_f32(),
|
||||
|
|
|
@ -19,5 +19,7 @@ malloc_size_of = { path = "../malloc_size_of" }
|
|||
malloc_size_of_derive = { workspace = true }
|
||||
parking_lot = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
size_of_test = { path = "../size_of_test" }
|
||||
webrender_api = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
size_of_test = { path = "../size_of_test" }
|
||||
|
|
|
@ -198,9 +198,6 @@ pub struct PipelineNamespaceId(pub u32);
|
|||
|
||||
namespace_id! {PipelineId, PipelineIndex}
|
||||
|
||||
size_of_test!(PipelineId, 8);
|
||||
size_of_test!(Option<PipelineId>, 8);
|
||||
|
||||
impl PipelineId {
|
||||
pub fn new() -> PipelineId {
|
||||
PIPELINE_NAMESPACE.with(|tls| {
|
||||
|
@ -243,9 +240,6 @@ impl fmt::Display for PipelineId {
|
|||
|
||||
namespace_id! {BrowsingContextId, BrowsingContextIndex}
|
||||
|
||||
size_of_test!(BrowsingContextId, 8);
|
||||
size_of_test!(Option<BrowsingContextId>, 8);
|
||||
|
||||
impl BrowsingContextId {
|
||||
pub fn new() -> BrowsingContextId {
|
||||
PIPELINE_NAMESPACE.with(|tls| {
|
||||
|
@ -275,9 +269,6 @@ thread_local!(pub static TOP_LEVEL_BROWSING_CONTEXT_ID: Cell<Option<TopLevelBrow
|
|||
)]
|
||||
pub struct TopLevelBrowsingContextId(pub BrowsingContextId);
|
||||
|
||||
size_of_test!(TopLevelBrowsingContextId, 8);
|
||||
size_of_test!(Option<TopLevelBrowsingContextId>, 8);
|
||||
|
||||
impl TopLevelBrowsingContextId {
|
||||
pub fn new() -> TopLevelBrowsingContextId {
|
||||
TopLevelBrowsingContextId(BrowsingContextId::new())
|
||||
|
|
|
@ -12,7 +12,5 @@ extern crate malloc_size_of;
|
|||
extern crate malloc_size_of_derive;
|
||||
#[macro_use]
|
||||
extern crate serde;
|
||||
#[macro_use]
|
||||
extern crate size_of_test;
|
||||
|
||||
pub mod constellation_msg;
|
||||
|
|
31
components/msg/tests/size_of.rs
Normal file
31
components/msg/tests/size_of.rs
Normal file
|
@ -0,0 +1,31 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#![cfg(target_pointer_width = "64")]
|
||||
|
||||
#[macro_use]
|
||||
extern crate size_of_test;
|
||||
|
||||
use msg::constellation_msg::BrowsingContextId;
|
||||
use msg::constellation_msg::PipelineId;
|
||||
use msg::constellation_msg::TopLevelBrowsingContextId;
|
||||
|
||||
size_of_test!(test_size_of_pipeline_id, PipelineId, 8);
|
||||
size_of_test!(test_size_of_optional_pipeline_id, Option<PipelineId>, 8);
|
||||
size_of_test!(test_size_of_browsing_context_id, BrowsingContextId, 8);
|
||||
size_of_test!(
|
||||
test_size_of_optional_browsing_context_id,
|
||||
Option<BrowsingContextId>,
|
||||
8
|
||||
);
|
||||
size_of_test!(
|
||||
test_size_of_top_level_browsing_context_id,
|
||||
TopLevelBrowsingContextId,
|
||||
8
|
||||
);
|
||||
size_of_test!(
|
||||
test_size_of_top_level_optional_browsing_context_id,
|
||||
Option<TopLevelBrowsingContextId>,
|
||||
8
|
||||
);
|
|
@ -98,9 +98,8 @@ impl CSSRule {
|
|||
DomRoot::upcast(CSSSupportsRule::new(window, parent_stylesheet, s))
|
||||
},
|
||||
StyleCssRule::Page(_) => unreachable!(),
|
||||
StyleCssRule::Container(_) => unimplemented!(), // TODO
|
||||
StyleCssRule::Document(_) => unimplemented!(), // TODO
|
||||
StyleCssRule::Viewport(_) => unimplemented!(), // TODO
|
||||
StyleCssRule::Document(_) => unimplemented!(), // TODO
|
||||
StyleCssRule::Viewport(_) => unimplemented!(), // TODO
|
||||
StyleCssRule::LayerBlock(_) => unimplemented!(), // TODO
|
||||
StyleCssRule::LayerStatement(_) => unimplemented!(), // TODO
|
||||
StyleCssRule::ScrollTimeline(_) => unimplemented!(), // TODO
|
||||
|
|
|
@ -111,7 +111,6 @@ use std::cell::Cell;
|
|||
use std::default::Default;
|
||||
use std::fmt;
|
||||
use std::mem;
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
use std::str::FromStr;
|
||||
use style::applicable_declarations::ApplicableDeclarationBlock;
|
||||
|
@ -3219,11 +3218,15 @@ impl<'a> SelectorsElement for DomRoot<Element> {
|
|||
Element::namespace(self) == Element::namespace(other)
|
||||
}
|
||||
|
||||
fn match_non_ts_pseudo_class(
|
||||
fn match_non_ts_pseudo_class<F>(
|
||||
&self,
|
||||
pseudo_class: &NonTSPseudoClass,
|
||||
_: &mut MatchingContext<Self::Impl>,
|
||||
) -> bool {
|
||||
_: &mut F,
|
||||
) -> bool
|
||||
where
|
||||
F: FnMut(&Self, ElementSelectorFlags),
|
||||
{
|
||||
match *pseudo_class {
|
||||
// https://github.com/servo/servo/issues/8718
|
||||
NonTSPseudoClass::Link | NonTSPseudoClass::AnyLink => self.is_link(),
|
||||
|
@ -3304,15 +3307,6 @@ impl<'a> SelectorsElement for DomRoot<Element> {
|
|||
fn is_html_slot_element(&self) -> bool {
|
||||
self.is_html_element() && self.local_name() == &local_name!("slot")
|
||||
}
|
||||
|
||||
fn set_selector_flags(&self, flags: ElementSelectorFlags) {
|
||||
#[allow(unsafe_code)]
|
||||
unsafe {
|
||||
Dom::from_ref(self.deref())
|
||||
.to_layout()
|
||||
.insert_selector_flags(flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Element {
|
||||
|
|
|
@ -79,9 +79,7 @@ use script_layout_interface::{HTMLCanvasData, HTMLMediaData, LayoutElementType,
|
|||
use script_layout_interface::{SVGSVGData, StyleAndOpaqueLayoutData, TrustedNodeAddress};
|
||||
use script_traits::DocumentActivity;
|
||||
use script_traits::UntrustedNodeAddress;
|
||||
use selectors::matching::{
|
||||
matches_selector_list, MatchingContext, MatchingMode, NeedsSelectorFlags,
|
||||
};
|
||||
use selectors::matching::{matches_selector_list, MatchingContext, MatchingMode};
|
||||
use selectors::parser::SelectorList;
|
||||
use servo_arc::Arc;
|
||||
use servo_url::ServoUrl;
|
||||
|
@ -475,7 +473,6 @@ impl<'a> Iterator for QuerySelectorIterator {
|
|||
None,
|
||||
None,
|
||||
node.owner_doc().quirks_mode(),
|
||||
NeedsSelectorFlags::No,
|
||||
);
|
||||
if let Some(element) = DomRoot::downcast(node) {
|
||||
if matches_selector_list(selectors, &element, &mut ctx) {
|
||||
|
@ -959,13 +956,8 @@ impl Node {
|
|||
// Step 3.
|
||||
Ok(selectors) => {
|
||||
// FIXME(bholley): Consider an nth-index cache here.
|
||||
let mut ctx = MatchingContext::new(
|
||||
MatchingMode::Normal,
|
||||
None,
|
||||
None,
|
||||
doc.quirks_mode(),
|
||||
NeedsSelectorFlags::No,
|
||||
);
|
||||
let mut ctx =
|
||||
MatchingContext::new(MatchingMode::Normal, None, None, doc.quirks_mode());
|
||||
Ok(self
|
||||
.traverse_preorder(ShadowIncluding::No)
|
||||
.filter_map(DomRoot::downcast)
|
||||
|
|
|
@ -358,6 +358,14 @@ impl<'dom, LayoutDataType: LayoutDataTrait> style::dom::TElement
|
|||
false
|
||||
}
|
||||
|
||||
unsafe fn set_selector_flags(&self, flags: ElementSelectorFlags) {
|
||||
self.element.insert_selector_flags(flags);
|
||||
}
|
||||
|
||||
fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool {
|
||||
self.element.has_selector_flags(flags)
|
||||
}
|
||||
|
||||
fn has_animations(&self, context: &SharedStyleContext) -> bool {
|
||||
// This is not used for pseudo elements currently so we can pass None.
|
||||
return self.has_css_animations(context, /* pseudo_element = */ None) ||
|
||||
|
@ -452,10 +460,6 @@ impl<'dom, LayoutDataType: LayoutDataTrait> style::dom::TElement
|
|||
fn namespace(&self) -> &Namespace {
|
||||
self.element.namespace()
|
||||
}
|
||||
|
||||
fn primary_box_size(&self) -> euclid::default::Size2D<app_units::Au> {
|
||||
todo!();
|
||||
}
|
||||
}
|
||||
|
||||
impl<'dom, LayoutDataType: LayoutDataTrait> ::selectors::Element
|
||||
|
@ -569,11 +573,15 @@ impl<'dom, LayoutDataType: LayoutDataTrait> ::selectors::Element
|
|||
false
|
||||
}
|
||||
|
||||
fn match_non_ts_pseudo_class(
|
||||
fn match_non_ts_pseudo_class<F>(
|
||||
&self,
|
||||
pseudo_class: &NonTSPseudoClass,
|
||||
_: &mut MatchingContext<Self::Impl>,
|
||||
) -> bool {
|
||||
_: &mut F,
|
||||
) -> bool
|
||||
where
|
||||
F: FnMut(&Self, ElementSelectorFlags),
|
||||
{
|
||||
match *pseudo_class {
|
||||
// https://github.com/servo/servo/issues/8718
|
||||
NonTSPseudoClass::Link | NonTSPseudoClass::AnyLink => self.is_link(),
|
||||
|
@ -662,10 +670,6 @@ impl<'dom, LayoutDataType: LayoutDataTrait> ::selectors::Element
|
|||
fn is_html_element_in_html_document(&self) -> bool {
|
||||
self.element.is_html_element() && self.as_node().owner_doc().is_html_document()
|
||||
}
|
||||
|
||||
fn set_selector_flags(&self, flags: ElementSelectorFlags) {
|
||||
self.element.insert_selector_flags(flags);
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper around elements that ensures layout can only
|
||||
|
@ -854,11 +858,15 @@ impl<'dom, LayoutDataType: LayoutDataTrait> ::selectors::Element
|
|||
}
|
||||
}
|
||||
|
||||
fn match_non_ts_pseudo_class(
|
||||
fn match_non_ts_pseudo_class<F>(
|
||||
&self,
|
||||
_: &NonTSPseudoClass,
|
||||
_: &mut MatchingContext<Self::Impl>,
|
||||
) -> bool {
|
||||
_: &mut F,
|
||||
) -> bool
|
||||
where
|
||||
F: FnMut(&Self, ElementSelectorFlags),
|
||||
{
|
||||
// NB: This could maybe be implemented
|
||||
warn!("ServoThreadSafeLayoutElement::match_non_ts_pseudo_class called");
|
||||
false
|
||||
|
@ -899,10 +907,6 @@ impl<'dom, LayoutDataType: LayoutDataTrait> ::selectors::Element
|
|||
warn!("ServoThreadSafeLayoutElement::is_root called");
|
||||
false
|
||||
}
|
||||
|
||||
fn set_selector_flags(&self, flags: ElementSelectorFlags) {
|
||||
self.element.element.insert_selector_flags(flags);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'dom, LayoutDataType: LayoutDataTrait> GetStyleAndOpaqueLayoutData<'dom>
|
||||
|
|
|
@ -28,7 +28,6 @@ log = "0.4"
|
|||
phf = "0.10"
|
||||
precomputed-hash = "0.1"
|
||||
servo_arc = { version = "0.2", path = "../servo_arc" }
|
||||
size_of_test = { path = "../size_of_test" }
|
||||
smallvec = "1.0"
|
||||
to_shmem = { version = "0.0.0", path = "../to_shmem", optional = true }
|
||||
to_shmem_derive = { version = "0.0.0", path = "../to_shmem_derive", optional = true }
|
||||
|
|
|
@ -68,14 +68,6 @@ impl VisitedHandlingMode {
|
|||
}
|
||||
}
|
||||
|
||||
/// Whether we need to set selector invalidation flags on elements for this
|
||||
/// match request.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum NeedsSelectorFlags {
|
||||
No,
|
||||
Yes,
|
||||
}
|
||||
|
||||
/// Which quirks mode is this document in.
|
||||
///
|
||||
/// See: https://quirks.spec.whatwg.org/
|
||||
|
@ -148,7 +140,6 @@ where
|
|||
pub extra_data: Impl::ExtraMatchingData,
|
||||
|
||||
quirks_mode: QuirksMode,
|
||||
needs_selector_flags: NeedsSelectorFlags,
|
||||
classes_and_ids_case_sensitivity: CaseSensitivity,
|
||||
_impl: ::std::marker::PhantomData<Impl>,
|
||||
}
|
||||
|
@ -163,7 +154,6 @@ where
|
|||
bloom_filter: Option<&'a BloomFilter>,
|
||||
nth_index_cache: Option<&'a mut NthIndexCache>,
|
||||
quirks_mode: QuirksMode,
|
||||
needs_selector_flags: NeedsSelectorFlags,
|
||||
) -> Self {
|
||||
Self::new_for_visited(
|
||||
matching_mode,
|
||||
|
@ -171,7 +161,6 @@ where
|
|||
nth_index_cache,
|
||||
VisitedHandlingMode::AllLinksUnvisited,
|
||||
quirks_mode,
|
||||
needs_selector_flags,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -182,7 +171,6 @@ where
|
|||
nth_index_cache: Option<&'a mut NthIndexCache>,
|
||||
visited_handling: VisitedHandlingMode,
|
||||
quirks_mode: QuirksMode,
|
||||
needs_selector_flags: NeedsSelectorFlags,
|
||||
) -> Self {
|
||||
Self {
|
||||
matching_mode,
|
||||
|
@ -191,7 +179,6 @@ where
|
|||
nth_index_cache,
|
||||
quirks_mode,
|
||||
classes_and_ids_case_sensitivity: quirks_mode.classes_and_ids_case_sensitivity(),
|
||||
needs_selector_flags,
|
||||
scope_element: None,
|
||||
current_host: None,
|
||||
nesting_level: 0,
|
||||
|
@ -226,12 +213,6 @@ where
|
|||
self.matching_mode
|
||||
}
|
||||
|
||||
/// Whether we need to set selector flags.
|
||||
#[inline]
|
||||
pub fn needs_selector_flags(&self) -> bool {
|
||||
self.needs_selector_flags == NeedsSelectorFlags::Yes
|
||||
}
|
||||
|
||||
/// The case-sensitivity for class and ID selectors
|
||||
#[inline]
|
||||
pub fn classes_and_ids_case_sensitivity(&self) -> CaseSensitivity {
|
||||
|
|
|
@ -5,9 +5,6 @@
|
|||
// Make |cargo bench| work.
|
||||
#![cfg_attr(feature = "bench", feature(test))]
|
||||
|
||||
#[macro_use]
|
||||
extern crate size_of_test;
|
||||
|
||||
pub mod attr;
|
||||
pub mod bloom;
|
||||
mod builder;
|
||||
|
|
|
@ -78,7 +78,8 @@ where
|
|||
// This is pretty much any(..) but manually inlined because the compiler
|
||||
// refuses to do so from querySelector / querySelectorAll.
|
||||
for selector in &selector_list.0 {
|
||||
let matches = matches_selector(selector, 0, None, element, context);
|
||||
let matches = matches_selector(selector, 0, None, element, context, &mut |_, _| {});
|
||||
|
||||
if matches {
|
||||
return true;
|
||||
}
|
||||
|
@ -183,15 +184,17 @@ enum MatchesHoverAndActiveQuirk {
|
|||
/// unncessary cache miss for cases when we can fast-reject with AncestorHashes
|
||||
/// (which the caller can store inline with the selector pointer).
|
||||
#[inline(always)]
|
||||
pub fn matches_selector<E>(
|
||||
pub fn matches_selector<E, F>(
|
||||
selector: &Selector<E::Impl>,
|
||||
offset: usize,
|
||||
hashes: Option<&AncestorHashes>,
|
||||
element: &E,
|
||||
context: &mut MatchingContext<E::Impl>,
|
||||
flags_setter: &mut F,
|
||||
) -> bool
|
||||
where
|
||||
E: Element,
|
||||
F: FnMut(&E, ElementSelectorFlags),
|
||||
{
|
||||
// Use the bloom filter to fast-reject.
|
||||
if let Some(hashes) = hashes {
|
||||
|
@ -202,7 +205,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
matches_complex_selector(selector.iter_from(offset), element, context)
|
||||
matches_complex_selector(selector.iter_from(offset), element, context, flags_setter)
|
||||
}
|
||||
|
||||
/// Whether a compound selector matched, and whether it was the rightmost
|
||||
|
@ -274,7 +277,7 @@ where
|
|||
);
|
||||
|
||||
for component in iter {
|
||||
if !matches_simple_selector(component, element, &mut local_context) {
|
||||
if !matches_simple_selector(component, element, &mut local_context, &mut |_, _| {}) {
|
||||
return CompoundSelectorMatchingResult::NotMatched;
|
||||
}
|
||||
}
|
||||
|
@ -290,13 +293,15 @@ where
|
|||
|
||||
/// Matches a complex selector.
|
||||
#[inline(always)]
|
||||
pub fn matches_complex_selector<E>(
|
||||
pub fn matches_complex_selector<E, F>(
|
||||
mut iter: SelectorIter<E::Impl>,
|
||||
element: &E,
|
||||
context: &mut MatchingContext<E::Impl>,
|
||||
flags_setter: &mut F,
|
||||
) -> bool
|
||||
where
|
||||
E: Element,
|
||||
F: FnMut(&E, ElementSelectorFlags),
|
||||
{
|
||||
// If this is the special pseudo-element mode, consume the ::pseudo-element
|
||||
// before proceeding, since the caller has already handled that part.
|
||||
|
@ -329,7 +334,7 @@ where
|
|||
}
|
||||
|
||||
let result =
|
||||
matches_complex_selector_internal(iter, element, context, Rightmost::Yes);
|
||||
matches_complex_selector_internal(iter, element, context, flags_setter, Rightmost::Yes);
|
||||
|
||||
matches!(result, SelectorMatchingResult::Matched)
|
||||
}
|
||||
|
@ -453,14 +458,16 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn matches_complex_selector_internal<E>(
|
||||
fn matches_complex_selector_internal<E, F>(
|
||||
mut selector_iter: SelectorIter<E::Impl>,
|
||||
element: &E,
|
||||
context: &mut MatchingContext<E::Impl>,
|
||||
flags_setter: &mut F,
|
||||
rightmost: Rightmost,
|
||||
) -> SelectorMatchingResult
|
||||
where
|
||||
E: Element,
|
||||
F: FnMut(&E, ElementSelectorFlags),
|
||||
{
|
||||
debug!(
|
||||
"Matching complex selector {:?} for {:?}",
|
||||
|
@ -471,16 +478,16 @@ where
|
|||
&mut selector_iter,
|
||||
element,
|
||||
context,
|
||||
flags_setter,
|
||||
rightmost,
|
||||
);
|
||||
|
||||
let combinator = selector_iter.next_sequence();
|
||||
if combinator.map_or(false, |c| c.is_sibling()) {
|
||||
if context.needs_selector_flags() {
|
||||
element.apply_selector_flags(
|
||||
ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS
|
||||
);
|
||||
}
|
||||
flags_setter(
|
||||
element,
|
||||
ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS,
|
||||
);
|
||||
}
|
||||
|
||||
if !matches_compound_selector {
|
||||
|
@ -525,6 +532,7 @@ where
|
|||
selector_iter.clone(),
|
||||
&element,
|
||||
context,
|
||||
flags_setter,
|
||||
Rightmost::No,
|
||||
)
|
||||
});
|
||||
|
@ -587,14 +595,16 @@ where
|
|||
|
||||
/// Determines whether the given element matches the given compound selector.
|
||||
#[inline]
|
||||
fn matches_compound_selector<E>(
|
||||
fn matches_compound_selector<E, F>(
|
||||
selector_iter: &mut SelectorIter<E::Impl>,
|
||||
element: &E,
|
||||
context: &mut MatchingContext<E::Impl>,
|
||||
flags_setter: &mut F,
|
||||
rightmost: Rightmost,
|
||||
) -> bool
|
||||
where
|
||||
E: Element,
|
||||
F: FnMut(&E, ElementSelectorFlags),
|
||||
{
|
||||
let matches_hover_and_active_quirk =
|
||||
matches_hover_and_active_quirk(&selector_iter, context, rightmost);
|
||||
|
@ -633,17 +643,19 @@ where
|
|||
};
|
||||
iter::once(selector)
|
||||
.chain(selector_iter)
|
||||
.all(|simple| matches_simple_selector(simple, element, &mut local_context))
|
||||
.all(|simple| matches_simple_selector(simple, element, &mut local_context, flags_setter))
|
||||
}
|
||||
|
||||
/// Determines whether the given element matches the given single selector.
|
||||
fn matches_simple_selector<E>(
|
||||
fn matches_simple_selector<E, F>(
|
||||
selector: &Component<E::Impl>,
|
||||
element: &E,
|
||||
context: &mut LocalMatchingContext<E::Impl>,
|
||||
flags_setter: &mut F,
|
||||
) -> bool
|
||||
where
|
||||
E: Element,
|
||||
F: FnMut(&E, ElementSelectorFlags),
|
||||
{
|
||||
debug_assert!(context.shared.is_nested() || !context.shared.in_negation());
|
||||
|
||||
|
@ -691,7 +703,7 @@ where
|
|||
// <slots> are never flattened tree slottables.
|
||||
!element.is_html_slot_element() &&
|
||||
context.shared.nest(|context| {
|
||||
matches_complex_selector(selector.iter(), element, context)
|
||||
matches_complex_selector(selector.iter(), element, context, flags_setter)
|
||||
})
|
||||
},
|
||||
Component::PseudoElement(ref pseudo) => {
|
||||
|
@ -783,18 +795,16 @@ where
|
|||
return false;
|
||||
}
|
||||
|
||||
element.match_non_ts_pseudo_class(pc, &mut context.shared)
|
||||
element.match_non_ts_pseudo_class(pc, &mut context.shared, flags_setter)
|
||||
},
|
||||
Component::FirstChild => matches_first_child(element, context.shared),
|
||||
Component::LastChild => matches_last_child(element, context.shared),
|
||||
Component::FirstChild => matches_first_child(element, flags_setter),
|
||||
Component::LastChild => matches_last_child(element, flags_setter),
|
||||
Component::OnlyChild => {
|
||||
matches_first_child(element, context.shared) && matches_last_child(element, context.shared)
|
||||
matches_first_child(element, flags_setter) && matches_last_child(element, flags_setter)
|
||||
},
|
||||
Component::Root => element.is_root(),
|
||||
Component::Empty => {
|
||||
if context.shared.needs_selector_flags() {
|
||||
element.apply_selector_flags(ElementSelectorFlags::HAS_EMPTY_SELECTOR);
|
||||
}
|
||||
flags_setter(element, ElementSelectorFlags::HAS_EMPTY_SELECTOR);
|
||||
element.is_empty()
|
||||
},
|
||||
Component::Host(ref selector) => {
|
||||
|
@ -804,7 +814,7 @@ where
|
|||
.map_or(false, |host| host == element.opaque()) &&
|
||||
selector.as_ref().map_or(true, |selector| {
|
||||
context.shared.nest(|context| {
|
||||
matches_complex_selector(selector.iter(), element, context)
|
||||
matches_complex_selector(selector.iter(), element, context, flags_setter)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
@ -813,30 +823,30 @@ where
|
|||
None => element.is_root(),
|
||||
},
|
||||
Component::NthChild(a, b) => {
|
||||
matches_generic_nth_child(element, context.shared, a, b, false, false)
|
||||
matches_generic_nth_child(element, context, a, b, false, false, flags_setter)
|
||||
},
|
||||
Component::NthLastChild(a, b) => {
|
||||
matches_generic_nth_child(element, context.shared, a, b, false, true)
|
||||
matches_generic_nth_child(element, context, a, b, false, true, flags_setter)
|
||||
},
|
||||
Component::NthOfType(a, b) => {
|
||||
matches_generic_nth_child(element, context.shared, a, b, true, false)
|
||||
matches_generic_nth_child(element, context, a, b, true, false, flags_setter)
|
||||
},
|
||||
Component::NthLastOfType(a, b) => {
|
||||
matches_generic_nth_child(element, context.shared, a, b, true, true)
|
||||
matches_generic_nth_child(element, context, a, b, true, true, flags_setter)
|
||||
},
|
||||
Component::FirstOfType => {
|
||||
matches_generic_nth_child(element, context.shared, 0, 1, true, false)
|
||||
matches_generic_nth_child(element, context, 0, 1, true, false, flags_setter)
|
||||
},
|
||||
Component::LastOfType => {
|
||||
matches_generic_nth_child(element, context.shared, 0, 1, true, true)
|
||||
matches_generic_nth_child(element, context, 0, 1, true, true, flags_setter)
|
||||
},
|
||||
Component::OnlyOfType => {
|
||||
matches_generic_nth_child(element, context.shared, 0, 1, true, false) &&
|
||||
matches_generic_nth_child(element, context.shared, 0, 1, true, true)
|
||||
matches_generic_nth_child(element, context, 0, 1, true, false, flags_setter) &&
|
||||
matches_generic_nth_child(element, context, 0, 1, true, true, flags_setter)
|
||||
},
|
||||
Component::Is(ref list) | Component::Where(ref list) => context.shared.nest(|context| {
|
||||
for selector in &**list {
|
||||
if matches_complex_selector(selector.iter(), element, context) {
|
||||
if matches_complex_selector(selector.iter(), element, context, flags_setter) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -844,7 +854,7 @@ where
|
|||
}),
|
||||
Component::Negation(ref list) => context.shared.nest_for_negation(|context| {
|
||||
for selector in &**list {
|
||||
if matches_complex_selector(selector.iter(), element, context) {
|
||||
if matches_complex_selector(selector.iter(), element, context, flags_setter) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -863,31 +873,35 @@ fn select_name<'a, T>(is_html: bool, local_name: &'a T, local_name_lower: &'a T)
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn matches_generic_nth_child<E>(
|
||||
fn matches_generic_nth_child<E, F>(
|
||||
element: &E,
|
||||
context: &mut MatchingContext<E::Impl>,
|
||||
context: &mut LocalMatchingContext<E::Impl>,
|
||||
a: i32,
|
||||
b: i32,
|
||||
is_of_type: bool,
|
||||
is_from_end: bool,
|
||||
flags_setter: &mut F,
|
||||
) -> bool
|
||||
where
|
||||
E: Element,
|
||||
F: FnMut(&E, ElementSelectorFlags),
|
||||
{
|
||||
if element.ignores_nth_child_selectors() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if context.needs_selector_flags() {
|
||||
element.apply_selector_flags(if is_from_end {
|
||||
flags_setter(
|
||||
element,
|
||||
if is_from_end {
|
||||
ElementSelectorFlags::HAS_SLOW_SELECTOR
|
||||
} else {
|
||||
ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Grab a reference to the appropriate cache.
|
||||
let mut cache = context
|
||||
.shared
|
||||
.nth_index_cache
|
||||
.as_mut()
|
||||
.map(|c| c.get(is_of_type, is_from_end));
|
||||
|
@ -978,23 +992,21 @@ where
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn matches_first_child<E>(element: &E, context: &MatchingContext<E::Impl>) -> bool
|
||||
fn matches_first_child<E, F>(element: &E, flags_setter: &mut F) -> bool
|
||||
where
|
||||
E: Element,
|
||||
F: FnMut(&E, ElementSelectorFlags),
|
||||
{
|
||||
if context.needs_selector_flags() {
|
||||
element.apply_selector_flags(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR);
|
||||
}
|
||||
flags_setter(element, ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR);
|
||||
element.prev_sibling_element().is_none()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn matches_last_child<E>(element: &E, context: &MatchingContext<E::Impl>) -> bool
|
||||
fn matches_last_child<E, F>(element: &E, flags_setter: &mut F) -> bool
|
||||
where
|
||||
E: Element,
|
||||
F: FnMut(&E, ElementSelectorFlags),
|
||||
{
|
||||
if context.needs_selector_flags() {
|
||||
element.apply_selector_flags(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR);
|
||||
}
|
||||
flags_setter(element, ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR);
|
||||
element.next_sibling_element().is_none()
|
||||
}
|
||||
|
|
|
@ -160,8 +160,6 @@ impl SelectorParsingState {
|
|||
|
||||
pub type SelectorParseError<'i> = ParseError<'i, SelectorParseErrorKind<'i>>;
|
||||
|
||||
size_of_test!(SelectorParseError, 48);
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum SelectorParseErrorKind<'i> {
|
||||
NoQualifiedNameInAttributeSelector(Token<'i>),
|
||||
|
@ -186,8 +184,6 @@ pub enum SelectorParseErrorKind<'i> {
|
|||
ClassNeedsIdent(Token<'i>),
|
||||
}
|
||||
|
||||
size_of_test!(SelectorParseErrorKind, 40);
|
||||
|
||||
macro_rules! with_all_bounds {
|
||||
(
|
||||
[ $( $InSelector: tt )* ]
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
disable_all_formatting = true
|
|
@ -77,11 +77,14 @@ pub trait Element: Sized + Clone + Debug {
|
|||
operation: &AttrSelectorOperation<&<Self::Impl as SelectorImpl>::AttrValue>,
|
||||
) -> bool;
|
||||
|
||||
fn match_non_ts_pseudo_class(
|
||||
fn match_non_ts_pseudo_class<F>(
|
||||
&self,
|
||||
pc: &<Self::Impl as SelectorImpl>::NonTSPseudoClass,
|
||||
context: &mut MatchingContext<Self::Impl>,
|
||||
) -> bool;
|
||||
flags_setter: &mut F,
|
||||
) -> bool
|
||||
where
|
||||
F: FnMut(&Self, ElementSelectorFlags);
|
||||
|
||||
fn match_pseudo_element(
|
||||
&self,
|
||||
|
@ -89,30 +92,6 @@ pub trait Element: Sized + Clone + Debug {
|
|||
context: &mut MatchingContext<Self::Impl>,
|
||||
) -> bool;
|
||||
|
||||
/// Sets selector flags, which indicate what kinds of selectors may have
|
||||
/// matched on this element and therefore what kind of work may need to
|
||||
/// be performed when DOM state changes.
|
||||
///
|
||||
/// You probably don't want to use this directly and want to use
|
||||
/// apply_selector_flags, since that sets flags on the parent as needed.
|
||||
fn set_selector_flags(&self, flags: ElementSelectorFlags);
|
||||
|
||||
fn apply_selector_flags(&self, flags: ElementSelectorFlags) {
|
||||
// Handle flags that apply to the element.
|
||||
let self_flags = flags.for_self();
|
||||
if !self_flags.is_empty() {
|
||||
self.set_selector_flags(self_flags);
|
||||
}
|
||||
|
||||
// Handle flags that apply to the parent.
|
||||
let parent_flags = flags.for_parent();
|
||||
if !parent_flags.is_empty() {
|
||||
if let Some(p) = self.parent_element() {
|
||||
p.set_selector_flags(parent_flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether this element is a `link`.
|
||||
fn is_link(&self) -> bool;
|
||||
|
||||
|
|
|
@ -8,6 +8,3 @@ publish = false
|
|||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
static_assertions = "1.1"
|
||||
|
|
|
@ -2,13 +2,33 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
pub use static_assertions::const_assert_eq;
|
||||
|
||||
/// Asserts the size of a type at compile time.
|
||||
#[macro_export]
|
||||
macro_rules! size_of_test {
|
||||
($t: ty, $expected_size: expr) => {
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
$crate::const_assert_eq!(std::mem::size_of::<$t>(), $expected_size);
|
||||
($testname: ident, $t: ty, $expected_size: expr) => {
|
||||
#[test]
|
||||
fn $testname() {
|
||||
let new = ::std::mem::size_of::<$t>();
|
||||
let old = $expected_size;
|
||||
if new < old {
|
||||
panic!(
|
||||
"Your changes have decreased the stack size of {} from {} to {}. \
|
||||
Good work! Please update the expected size in {}.",
|
||||
stringify!($t),
|
||||
old,
|
||||
new,
|
||||
file!()
|
||||
)
|
||||
} else if new > old {
|
||||
panic!(
|
||||
"Your changes have increased the stack size of {} from {} to {}. \
|
||||
Please consider choosing a design which avoids this increase. \
|
||||
If you feel that the increase is necessary, update the size in {}.",
|
||||
stringify!($t),
|
||||
old,
|
||||
new,
|
||||
file!()
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -62,14 +62,13 @@ servo_config = { path = "../config", optional = true }
|
|||
servo_url = { path = "../url", optional = true }
|
||||
smallbitvec = "2.3.0"
|
||||
smallvec = "1.0"
|
||||
static_assertions = "1.1"
|
||||
string_cache = { version = "0.8", optional = true }
|
||||
style_derive = { path = "../style_derive" }
|
||||
style_traits = { path = "../style_traits" }
|
||||
time = "0.1"
|
||||
to_shmem = { path = "../to_shmem" }
|
||||
to_shmem_derive = { path = "../to_shmem_derive" }
|
||||
uluru = "3.0"
|
||||
uluru = "2"
|
||||
unicode-bidi = "0.3"
|
||||
unicode-segmentation = "1.0"
|
||||
void = "1.0.2"
|
||||
|
|
|
@ -455,8 +455,8 @@ pub struct Animation {
|
|||
impl Animation {
|
||||
/// Whether or not this animation is cancelled by changes from a new style.
|
||||
fn is_cancelled_in_new_style(&self, new_style: &Arc<ComputedValues>) -> bool {
|
||||
let new_ui = new_style.get_ui();
|
||||
let index = new_ui
|
||||
let index = new_style
|
||||
.get_box()
|
||||
.animation_name_iter()
|
||||
.position(|animation_name| Some(&self.name) == animation_name.as_atom());
|
||||
let index = match index {
|
||||
|
@ -464,7 +464,7 @@ impl Animation {
|
|||
None => return true,
|
||||
};
|
||||
|
||||
new_ui.animation_duration_mod(index).seconds() == 0.
|
||||
new_style.get_box().animation_duration_mod(index).seconds() == 0.
|
||||
}
|
||||
|
||||
/// Given the current time, advances this animation to the next iteration,
|
||||
|
@ -1073,10 +1073,10 @@ impl ElementAnimationSet {
|
|||
old_style: &ComputedValues,
|
||||
new_style: &Arc<ComputedValues>,
|
||||
) {
|
||||
let style = new_style.get_ui();
|
||||
let timing_function = style.transition_timing_function_mod(index);
|
||||
let duration = style.transition_duration_mod(index);
|
||||
let delay = style.transition_delay_mod(index).seconds() as f64;
|
||||
let box_style = new_style.get_box();
|
||||
let timing_function = box_style.transition_timing_function_mod(index);
|
||||
let duration = box_style.transition_duration_mod(index);
|
||||
let delay = box_style.transition_delay_mod(index).seconds() as f64;
|
||||
let now = context.current_time_for_animations;
|
||||
|
||||
// Only start a new transition if the style actually changes between
|
||||
|
@ -1344,15 +1344,15 @@ pub fn maybe_start_animations<E>(
|
|||
) where
|
||||
E: TElement,
|
||||
{
|
||||
let style = new_style.get_ui();
|
||||
for (i, name) in style.animation_name_iter().enumerate() {
|
||||
let box_style = new_style.get_box();
|
||||
for (i, name) in box_style.animation_name_iter().enumerate() {
|
||||
let name = match name.as_atom() {
|
||||
Some(atom) => atom,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
debug!("maybe_start_animations: name={}", name);
|
||||
let duration = style.animation_duration_mod(i).seconds() as f64;
|
||||
let duration = box_style.animation_duration_mod(i).seconds() as f64;
|
||||
if duration == 0. {
|
||||
continue;
|
||||
}
|
||||
|
@ -1375,14 +1375,14 @@ pub fn maybe_start_animations<E>(
|
|||
// NB: This delay may be negative, meaning that the animation may be created
|
||||
// in a state where we have advanced one or more iterations or even that the
|
||||
// animation begins in a finished state.
|
||||
let delay = style.animation_delay_mod(i).seconds();
|
||||
let delay = box_style.animation_delay_mod(i).seconds();
|
||||
|
||||
let iteration_state = match style.animation_iteration_count_mod(i) {
|
||||
let iteration_state = match box_style.animation_iteration_count_mod(i) {
|
||||
AnimationIterationCount::Infinite => KeyframesIterationState::Infinite(0.0),
|
||||
AnimationIterationCount::Number(n) => KeyframesIterationState::Finite(0.0, n.into()),
|
||||
};
|
||||
|
||||
let animation_direction = style.animation_direction_mod(i);
|
||||
let animation_direction = box_style.animation_direction_mod(i);
|
||||
|
||||
let initial_direction = match animation_direction {
|
||||
AnimationDirection::Normal | AnimationDirection::Alternate => {
|
||||
|
@ -1396,7 +1396,7 @@ pub fn maybe_start_animations<E>(
|
|||
let now = context.current_time_for_animations;
|
||||
let started_at = now + delay as f64;
|
||||
let mut starting_progress = (now - started_at) / duration;
|
||||
let state = match style.animation_play_state_mod(i) {
|
||||
let state = match box_style.animation_play_state_mod(i) {
|
||||
AnimationPlayState::Paused => AnimationState::Paused(starting_progress),
|
||||
AnimationPlayState::Running => AnimationState::Pending,
|
||||
};
|
||||
|
@ -1406,7 +1406,7 @@ pub fn maybe_start_animations<E>(
|
|||
&keyframe_animation,
|
||||
context,
|
||||
new_style,
|
||||
style.animation_timing_function_mod(i),
|
||||
new_style.get_box().animation_timing_function_mod(i),
|
||||
resolver,
|
||||
);
|
||||
|
||||
|
@ -1416,7 +1416,7 @@ pub fn maybe_start_animations<E>(
|
|||
computed_steps,
|
||||
started_at,
|
||||
duration,
|
||||
fill_mode: style.animation_fill_mode_mod(i),
|
||||
fill_mode: box_style.animation_fill_mode_mod(i),
|
||||
delay: delay as f64,
|
||||
iteration_state,
|
||||
state,
|
||||
|
|
|
@ -42,7 +42,13 @@ pub struct CascadePriority {
|
|||
layer_order: LayerOrder,
|
||||
}
|
||||
|
||||
const_assert_eq!(std::mem::size_of::<CascadePriority>(), std::mem::size_of::<u32>());
|
||||
#[allow(dead_code)]
|
||||
fn size_assert() {
|
||||
#[allow(unsafe_code)]
|
||||
unsafe {
|
||||
std::mem::transmute::<u32, CascadePriority>(0u32)
|
||||
};
|
||||
}
|
||||
|
||||
impl PartialOrd for CascadePriority {
|
||||
#[inline]
|
||||
|
@ -202,6 +208,3 @@ impl ApplicableDeclarationBlock {
|
|||
(self.source, self.cascade_priority)
|
||||
}
|
||||
}
|
||||
|
||||
// Size of this struct determines sorting and selector-matching performance.
|
||||
size_of_test!(ApplicableDeclarationBlock, 24);
|
||||
|
|
|
@ -285,11 +285,11 @@ fn generate_structs() {
|
|||
let mut fixups = vec![];
|
||||
let builder = BuilderWithConfig::new(builder, CONFIG["structs"].as_table().unwrap())
|
||||
.handle_common(&mut fixups)
|
||||
.handle_str_items("allowlist-functions", |b, item| b.allowlist_function(item))
|
||||
.handle_str_items("whitelist-functions", |b, item| b.allowlist_function(item))
|
||||
.handle_str_items("bitfield-enums", |b, item| b.bitfield_enum(item))
|
||||
.handle_str_items("rusty-enums", |b, item| b.rustified_enum(item))
|
||||
.handle_str_items("allowlist-vars", |b, item| b.allowlist_var(item))
|
||||
.handle_str_items("allowlist-types", |b, item| b.allowlist_type(item))
|
||||
.handle_str_items("whitelist-vars", |b, item| b.allowlist_var(item))
|
||||
.handle_str_items("whitelist-types", |b, item| b.allowlist_type(item))
|
||||
.handle_str_items("opaque-types", |b, item| b.opaque_type(item))
|
||||
.handle_table_items("cbindgen-types", |b, item| {
|
||||
let gecko = item["gecko"].as_str().unwrap();
|
||||
|
|
|
@ -27,8 +27,8 @@ use crate::traversal_flags::TraversalFlags;
|
|||
use app_units::Au;
|
||||
use euclid::default::Size2D;
|
||||
use euclid::Scale;
|
||||
#[cfg(feature = "servo")]
|
||||
use fxhash::FxHashMap;
|
||||
use selectors::matching::ElementSelectorFlags;
|
||||
use selectors::NthIndexCache;
|
||||
#[cfg(feature = "gecko")]
|
||||
use servo_arc::Arc;
|
||||
|
@ -41,6 +41,7 @@ use style_traits::DevicePixel;
|
|||
#[cfg(feature = "servo")]
|
||||
use style_traits::SpeculativePainter;
|
||||
use time;
|
||||
use uluru::LRUCache;
|
||||
|
||||
pub use selectors::matching::QuirksMode;
|
||||
|
||||
|
@ -516,6 +517,65 @@ impl<E: TElement> SequentialTask<E> {
|
|||
}
|
||||
}
|
||||
|
||||
type CacheItem<E> = (SendElement<E>, ElementSelectorFlags);
|
||||
|
||||
/// Map from Elements to ElementSelectorFlags. Used to defer applying selector
|
||||
/// flags until after the traversal.
|
||||
pub struct SelectorFlagsMap<E: TElement> {
|
||||
/// The hashmap storing the flags to apply.
|
||||
map: FxHashMap<SendElement<E>, ElementSelectorFlags>,
|
||||
/// An LRU cache to avoid hashmap lookups, which can be slow if the map
|
||||
/// gets big.
|
||||
cache: LRUCache<CacheItem<E>, { 4 + 1 }>,
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
impl<E: TElement> Drop for SelectorFlagsMap<E> {
|
||||
fn drop(&mut self) {
|
||||
debug_assert!(self.map.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: TElement> SelectorFlagsMap<E> {
|
||||
/// Creates a new empty SelectorFlagsMap.
|
||||
pub fn new() -> Self {
|
||||
SelectorFlagsMap {
|
||||
map: FxHashMap::default(),
|
||||
cache: LRUCache::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Inserts some flags into the map for a given element.
|
||||
pub fn insert_flags(&mut self, element: E, flags: ElementSelectorFlags) {
|
||||
let el = unsafe { SendElement::new(element) };
|
||||
// Check the cache. If the flags have already been noted, we're done.
|
||||
if let Some(item) = self.cache.find(|x| x.0 == el) {
|
||||
if !item.1.contains(flags) {
|
||||
item.1.insert(flags);
|
||||
self.map.get_mut(&el).unwrap().insert(flags);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let f = self.map.entry(el).or_insert(ElementSelectorFlags::empty());
|
||||
*f |= flags;
|
||||
|
||||
self.cache
|
||||
.insert((unsafe { SendElement::new(element) }, *f))
|
||||
}
|
||||
|
||||
/// Applies the flags. Must be called on the main thread.
|
||||
fn apply_flags(&mut self) {
|
||||
debug_assert_eq!(thread_state::get(), ThreadState::LAYOUT);
|
||||
self.cache.clear();
|
||||
for (el, flags) in self.map.drain() {
|
||||
unsafe {
|
||||
el.set_selector_flags(flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A list of SequentialTasks that get executed on Drop.
|
||||
pub struct SequentialTaskList<E>(Vec<SequentialTask<E>>)
|
||||
where
|
||||
|
@ -637,6 +697,11 @@ pub struct ThreadLocalStyleContext<E: TElement> {
|
|||
/// filter, to ensure they're dropped before we execute the tasks, which
|
||||
/// could create another ThreadLocalStyleContext for style computation.
|
||||
pub tasks: SequentialTaskList<E>,
|
||||
/// ElementSelectorFlags that need to be applied after the traversal is
|
||||
/// complete. This map is used in cases where the matching algorithm needs
|
||||
/// to set flags on elements it doesn't have exclusive access to (i.e. other
|
||||
/// than the current element).
|
||||
pub selector_flags: SelectorFlagsMap<E>,
|
||||
/// Statistics about the traversal.
|
||||
pub statistics: PerThreadTraversalStatistics,
|
||||
/// A checker used to ensure that parallel.rs does not recurse indefinitely
|
||||
|
@ -654,6 +719,7 @@ impl<E: TElement> ThreadLocalStyleContext<E> {
|
|||
rule_cache: RuleCache::new(),
|
||||
bloom_filter: StyleBloom::new(),
|
||||
tasks: SequentialTaskList(Vec::new()),
|
||||
selector_flags: SelectorFlagsMap::new(),
|
||||
statistics: PerThreadTraversalStatistics::default(),
|
||||
stack_limit_checker: StackLimitChecker::new(
|
||||
(STYLE_THREAD_STACK_SIZE_KB - STACK_SAFETY_MARGIN_KB) * 1024,
|
||||
|
@ -663,6 +729,15 @@ impl<E: TElement> ThreadLocalStyleContext<E> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<E: TElement> Drop for ThreadLocalStyleContext<E> {
|
||||
fn drop(&mut self) {
|
||||
debug_assert_eq!(thread_state::get(), ThreadState::LAYOUT);
|
||||
|
||||
// Apply any slow selector flags that need to be set on parents.
|
||||
self.selector_flags.apply_flags();
|
||||
}
|
||||
}
|
||||
|
||||
/// A `StyleContext` is just a simple container for a immutable reference to a
|
||||
/// shared style context, and a mutable reference to a local one.
|
||||
pub struct StyleContext<'a, E: TElement + 'a> {
|
||||
|
|
|
@ -64,27 +64,6 @@ fn get_safearea_inset_right(device: &Device) -> VariableValue {
|
|||
VariableValue::pixels(device.safe_area_insets().right)
|
||||
}
|
||||
|
||||
#[cfg(feature = "gecko")]
|
||||
fn get_content_preferred_color_scheme(device: &Device) -> VariableValue {
|
||||
use crate::gecko::media_features::PrefersColorScheme;
|
||||
let prefers_color_scheme = unsafe {
|
||||
crate::gecko_bindings::bindings::Gecko_MediaFeatures_PrefersColorScheme(
|
||||
device.document(),
|
||||
/* use_content = */ true,
|
||||
)
|
||||
};
|
||||
VariableValue::ident(match prefers_color_scheme {
|
||||
PrefersColorScheme::Light => "light",
|
||||
PrefersColorScheme::Dark => "dark",
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "servo")]
|
||||
fn get_content_preferred_color_scheme(_device: &Device) -> VariableValue {
|
||||
// TODO: implement this.
|
||||
VariableValue::ident("light")
|
||||
}
|
||||
|
||||
static ENVIRONMENT_VARIABLES: [EnvironmentVariable; 4] = [
|
||||
make_variable!(atom!("safe-area-inset-top"), get_safearea_inset_top),
|
||||
make_variable!(atom!("safe-area-inset-bottom"), get_safearea_inset_bottom),
|
||||
|
@ -120,7 +99,7 @@ macro_rules! lnf_int_variable {
|
|||
}};
|
||||
}
|
||||
|
||||
static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 6] = [
|
||||
static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 5] = [
|
||||
lnf_int_variable!(
|
||||
atom!("-moz-gtk-csd-titlebar-radius"),
|
||||
TitlebarRadius,
|
||||
|
@ -142,7 +121,6 @@ static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 6] = [
|
|||
GTKCSDMaximizeButtonPosition,
|
||||
integer
|
||||
),
|
||||
make_variable!(atom!("-moz-content-preferred-color-scheme"), get_content_preferred_color_scheme),
|
||||
];
|
||||
|
||||
impl CssEnvironment {
|
||||
|
@ -350,11 +328,6 @@ impl VariableValue {
|
|||
})
|
||||
}
|
||||
|
||||
/// Create VariableValue from an int.
|
||||
fn ident(ident: &'static str) -> Self {
|
||||
Self::from_token(Token::Ident(ident.into()))
|
||||
}
|
||||
|
||||
/// Create VariableValue from a float amount of CSS pixels.
|
||||
fn pixels(number: f32) -> Self {
|
||||
// FIXME (https://github.com/servo/rust-cssparser/issues/266):
|
||||
|
|
|
@ -159,9 +159,6 @@ pub struct ElementStyles {
|
|||
pub pseudos: EagerPseudoStyles,
|
||||
}
|
||||
|
||||
// There's one of these per rendered elements so it better be small.
|
||||
size_of_test!(ElementStyles, 16);
|
||||
|
||||
impl ElementStyles {
|
||||
/// Returns the primary style.
|
||||
pub fn get_primary(&self) -> Option<&Arc<ComputedValues>> {
|
||||
|
@ -252,9 +249,6 @@ pub struct ElementData {
|
|||
pub flags: ElementDataFlags,
|
||||
}
|
||||
|
||||
// There's one of these per rendered elements so it better be small.
|
||||
size_of_test!(ElementData, 24);
|
||||
|
||||
/// The kind of restyle that a single element should do.
|
||||
#[derive(Debug)]
|
||||
pub enum RestyleKind {
|
||||
|
|
|
@ -22,7 +22,7 @@ use crate::traversal_flags::TraversalFlags;
|
|||
use crate::values::AtomIdent;
|
||||
use crate::{LocalName, Namespace, WeakAtom};
|
||||
use atomic_refcell::{AtomicRef, AtomicRefMut};
|
||||
use selectors::matching::{QuirksMode, VisitedHandlingMode};
|
||||
use selectors::matching::{ElementSelectorFlags, QuirksMode, VisitedHandlingMode};
|
||||
use selectors::sink::Push;
|
||||
use selectors::Element as SelectorsElement;
|
||||
use servo_arc::{Arc, ArcBorrow};
|
||||
|
@ -734,6 +734,19 @@ pub trait TElement:
|
|||
/// native anonymous content can opt out of this style fixup.)
|
||||
fn skip_item_display_fixup(&self) -> bool;
|
||||
|
||||
/// Sets selector flags, which indicate what kinds of selectors may have
|
||||
/// matched on this element and therefore what kind of work may need to
|
||||
/// be performed when DOM state changes.
|
||||
///
|
||||
/// This is unsafe, like all the flag-setting methods, because it's only safe
|
||||
/// to call with exclusive access to the element. When setting flags on the
|
||||
/// parent during parallel traversal, we use SequentialTask to queue up the
|
||||
/// set to run after the threads join.
|
||||
unsafe fn set_selector_flags(&self, flags: ElementSelectorFlags);
|
||||
|
||||
/// Returns true if the element has all the specified selector flags.
|
||||
fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool;
|
||||
|
||||
/// In Gecko, element has a flag that represents the element may have
|
||||
/// any type of animations or not to bail out animation stuff early.
|
||||
/// Whereas Servo doesn't have such flag.
|
||||
|
@ -928,9 +941,6 @@ pub trait TElement:
|
|||
/// Returns element's namespace.
|
||||
fn namespace(&self)
|
||||
-> &<SelectorImpl as selectors::parser::SelectorImpl>::BorrowedNamespaceUrl;
|
||||
|
||||
/// Returns the size of the primary box of the element.
|
||||
fn primary_box_size(&self) -> euclid::default::Size2D<app_units::Au>;
|
||||
}
|
||||
|
||||
/// TNode and TElement aren't Send because we want to be careful and explicit
|
||||
|
|
|
@ -12,7 +12,7 @@ use crate::invalidation::element::invalidator::{DescendantInvalidationLists, Inv
|
|||
use crate::invalidation::element::invalidator::{InvalidationProcessor, InvalidationVector};
|
||||
use crate::values::AtomIdent;
|
||||
use selectors::attr::CaseSensitivity;
|
||||
use selectors::matching::{self, MatchingContext, MatchingMode, NeedsSelectorFlags};
|
||||
use selectors::matching::{self, MatchingContext, MatchingMode};
|
||||
use selectors::parser::{Combinator, Component, LocalName, SelectorImpl};
|
||||
use selectors::{Element, NthIndexCache, SelectorList};
|
||||
use smallvec::SmallVec;
|
||||
|
@ -26,13 +26,7 @@ pub fn element_matches<E>(
|
|||
where
|
||||
E: Element,
|
||||
{
|
||||
let mut context = MatchingContext::new(
|
||||
MatchingMode::Normal,
|
||||
None,
|
||||
None,
|
||||
quirks_mode,
|
||||
NeedsSelectorFlags::No,
|
||||
);
|
||||
let mut context = MatchingContext::new(MatchingMode::Normal, None, None, quirks_mode);
|
||||
context.scope_element = Some(element.opaque());
|
||||
context.current_host = element.containing_shadow_host().map(|e| e.opaque());
|
||||
matching::matches_selector_list(selector_list, element, &mut context)
|
||||
|
@ -54,7 +48,6 @@ where
|
|||
None,
|
||||
Some(&mut nth_index_cache),
|
||||
quirks_mode,
|
||||
NeedsSelectorFlags::No,
|
||||
);
|
||||
context.scope_element = Some(element.opaque());
|
||||
context.current_host = element.containing_shadow_host().map(|e| e.opaque());
|
||||
|
@ -625,8 +618,8 @@ pub fn query_selector<E, Q>(
|
|||
None,
|
||||
Some(&mut nth_index_cache),
|
||||
quirks_mode,
|
||||
NeedsSelectorFlags::No,
|
||||
);
|
||||
|
||||
let root_element = root.as_element();
|
||||
matching_context.scope_element = root_element.map(|e| e.opaque());
|
||||
matching_context.current_host = match root_element {
|
||||
|
|
|
@ -118,8 +118,8 @@ bitflags! {
|
|||
const IN_MODAL_DIALOG_STATE = 1 << 42;
|
||||
/// <https://html.spec.whatwg.org/multipage/#inert-subtrees>
|
||||
const IN_MOZINERT_STATE = 1 << 43;
|
||||
/// State for the topmost modal element in top layer
|
||||
const IN_TOPMOST_MODAL_TOP_LAYER_STATE = 1 << 44;
|
||||
/// State for the topmost dialog element in top layer
|
||||
const IN_TOPMOST_MODAL_DIALOG_STATE = 1 << 44;
|
||||
/// Initially used for the devtools highlighter, but now somehow only
|
||||
/// used for the devtools accessibility inspector.
|
||||
const IN_DEVTOOLS_HIGHLIGHTED_STATE = 1 << 45;
|
||||
|
@ -148,5 +148,9 @@ bitflags! {
|
|||
const LTR_LOCALE = 1 << 2;
|
||||
/// LWTheme status
|
||||
const LWTHEME = 1 << 3;
|
||||
/// LWTheme status
|
||||
const LWTHEME_BRIGHTTEXT = 1 << 4;
|
||||
/// LWTheme status
|
||||
const LWTHEME_DARKTEXT = 1 << 5;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ use crate::gecko_bindings::structs::{
|
|||
RawServoKeyframesRule, RawServoLayerBlockRule, RawServoLayerStatementRule, RawServoMediaList,
|
||||
RawServoMediaRule, RawServoMozDocumentRule, RawServoNamespaceRule, RawServoPageRule,
|
||||
RawServoScrollTimelineRule, RawServoStyleRule, RawServoStyleSheetContents,
|
||||
RawServoSupportsRule, RawServoContainerRule, ServoCssRules,
|
||||
RawServoSupportsRule, ServoCssRules,
|
||||
};
|
||||
use crate::gecko_bindings::sugar::ownership::{HasArcFFI, HasFFI, Strong};
|
||||
use crate::media_queries::MediaList;
|
||||
|
@ -26,7 +26,7 @@ use crate::stylesheets::keyframes_rule::Keyframe;
|
|||
use crate::stylesheets::{
|
||||
CounterStyleRule, CssRules, DocumentRule, FontFaceRule, FontFeatureValuesRule, ImportRule,
|
||||
KeyframesRule, LayerBlockRule, LayerStatementRule, MediaRule, NamespaceRule, PageRule,
|
||||
ScrollTimelineRule, StyleRule, StylesheetContents, SupportsRule, ContainerRule,
|
||||
ScrollTimelineRule, StyleRule, StylesheetContents, SupportsRule,
|
||||
};
|
||||
use servo_arc::{Arc, ArcBorrow};
|
||||
use std::{mem, ptr};
|
||||
|
@ -98,9 +98,6 @@ impl_arc_ffi!(Locked<ScrollTimelineRule> => RawServoScrollTimelineRule
|
|||
impl_arc_ffi!(Locked<SupportsRule> => RawServoSupportsRule
|
||||
[Servo_SupportsRule_AddRef, Servo_SupportsRule_Release]);
|
||||
|
||||
impl_arc_ffi!(Locked<ContainerRule> => RawServoContainerRule
|
||||
[Servo_ContainerRule_AddRef, Servo_ContainerRule_Release]);
|
||||
|
||||
impl_arc_ffi!(Locked<DocumentRule> => RawServoMozDocumentRule
|
||||
[Servo_DocumentRule_AddRef, Servo_DocumentRule_Release]);
|
||||
|
||||
|
|
|
@ -129,12 +129,6 @@ impl StylesheetInDocument for GeckoStyleSheet {
|
|||
pub struct PerDocumentStyleDataImpl {
|
||||
/// Rule processor.
|
||||
pub stylist: Stylist,
|
||||
|
||||
/// A cache from element to resolved style.
|
||||
pub undisplayed_style_cache: crate::traversal::UndisplayedStyleCache,
|
||||
|
||||
/// The generation for which our cache is valid.
|
||||
pub undisplayed_style_cache_generation: u64,
|
||||
}
|
||||
|
||||
/// The data itself is an `AtomicRefCell`, which guarantees the proper semantics
|
||||
|
@ -149,8 +143,6 @@ impl PerDocumentStyleData {
|
|||
|
||||
PerDocumentStyleData(AtomicRefCell::new(PerDocumentStyleDataImpl {
|
||||
stylist: Stylist::new(device, quirks_mode.into()),
|
||||
undisplayed_style_cache: Default::default(),
|
||||
undisplayed_style_cache_generation: 0,
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -185,6 +177,12 @@ impl PerDocumentStyleDataImpl {
|
|||
self.stylist.device().default_computed_values_arc()
|
||||
}
|
||||
|
||||
/// Returns whether visited styles are enabled.
|
||||
#[inline]
|
||||
pub fn visited_styles_enabled(&self) -> bool {
|
||||
unsafe { bindings::Gecko_VisitedStylesEnabled(self.stylist.device().document()) }
|
||||
}
|
||||
|
||||
/// Measure heap usage.
|
||||
pub fn add_size_of(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes) {
|
||||
self.stylist.add_size_of(ops, sizes);
|
||||
|
|
|
@ -6,10 +6,13 @@
|
|||
|
||||
use crate::gecko_bindings::bindings;
|
||||
use crate::gecko_bindings::structs;
|
||||
use crate::queries::feature::{AllowsRanges, Evaluator, FeatureFlags, QueryFeatureDescription};
|
||||
use crate::queries::values::Orientation;
|
||||
use crate::media_queries::media_feature::{AllowsRanges, ParsingRequirements};
|
||||
use crate::media_queries::media_feature::{Evaluator, MediaFeatureDescription};
|
||||
use crate::media_queries::media_feature_expression::RangeOrOperator;
|
||||
use crate::media_queries::{Device, MediaType};
|
||||
use crate::values::computed::{Context, CSSPixelLength, Ratio, Resolution};
|
||||
use crate::values::computed::CSSPixelLength;
|
||||
use crate::values::computed::Ratio;
|
||||
use crate::values::computed::Resolution;
|
||||
use app_units::Au;
|
||||
use euclid::default::Size2D;
|
||||
|
||||
|
@ -23,56 +26,150 @@ fn device_size(device: &Device) -> Size2D<Au> {
|
|||
}
|
||||
|
||||
/// https://drafts.csswg.org/mediaqueries-4/#width
|
||||
fn eval_width(context: &Context) -> CSSPixelLength {
|
||||
CSSPixelLength::new(context.device().au_viewport_size().width.to_f32_px())
|
||||
fn eval_width(
|
||||
device: &Device,
|
||||
value: Option<CSSPixelLength>,
|
||||
range_or_operator: Option<RangeOrOperator>,
|
||||
) -> bool {
|
||||
RangeOrOperator::evaluate(
|
||||
range_or_operator,
|
||||
value.map(Au::from),
|
||||
device.au_viewport_size().width,
|
||||
)
|
||||
}
|
||||
|
||||
/// https://drafts.csswg.org/mediaqueries-4/#device-width
|
||||
fn eval_device_width(context: &Context) -> CSSPixelLength {
|
||||
CSSPixelLength::new(device_size(context.device()).width.to_f32_px())
|
||||
fn eval_device_width(
|
||||
device: &Device,
|
||||
value: Option<CSSPixelLength>,
|
||||
range_or_operator: Option<RangeOrOperator>,
|
||||
) -> bool {
|
||||
RangeOrOperator::evaluate(
|
||||
range_or_operator,
|
||||
value.map(Au::from),
|
||||
device_size(device).width,
|
||||
)
|
||||
}
|
||||
|
||||
/// https://drafts.csswg.org/mediaqueries-4/#height
|
||||
fn eval_height(context: &Context) -> CSSPixelLength {
|
||||
CSSPixelLength::new(context.device().au_viewport_size().height.to_f32_px())
|
||||
fn eval_height(
|
||||
device: &Device,
|
||||
value: Option<CSSPixelLength>,
|
||||
range_or_operator: Option<RangeOrOperator>,
|
||||
) -> bool {
|
||||
RangeOrOperator::evaluate(
|
||||
range_or_operator,
|
||||
value.map(Au::from),
|
||||
device.au_viewport_size().height,
|
||||
)
|
||||
}
|
||||
|
||||
/// https://drafts.csswg.org/mediaqueries-4/#device-height
|
||||
fn eval_device_height(context: &Context) -> CSSPixelLength {
|
||||
CSSPixelLength::new(device_size(context.device()).height.to_f32_px())
|
||||
fn eval_device_height(
|
||||
device: &Device,
|
||||
value: Option<CSSPixelLength>,
|
||||
range_or_operator: Option<RangeOrOperator>,
|
||||
) -> bool {
|
||||
RangeOrOperator::evaluate(
|
||||
range_or_operator,
|
||||
value.map(Au::from),
|
||||
device_size(device).height,
|
||||
)
|
||||
}
|
||||
|
||||
fn eval_aspect_ratio_for<F>(context: &Context, get_size: F) -> Ratio
|
||||
fn eval_aspect_ratio_for<F>(
|
||||
device: &Device,
|
||||
query_value: Option<Ratio>,
|
||||
range_or_operator: Option<RangeOrOperator>,
|
||||
get_size: F,
|
||||
) -> bool
|
||||
where
|
||||
F: FnOnce(&Device) -> Size2D<Au>,
|
||||
{
|
||||
let size = get_size(context.device());
|
||||
Ratio::new(size.width.0 as f32, size.height.0 as f32)
|
||||
// A ratio of 0/0 behaves as the ratio 1/0, so we need to call used_value()
|
||||
// to convert it if necessary.
|
||||
// FIXME: we may need to update here once
|
||||
// https://github.com/w3c/csswg-drafts/issues/4954 got resolved.
|
||||
let query_value = match query_value {
|
||||
Some(v) => v.used_value(),
|
||||
None => return true,
|
||||
};
|
||||
|
||||
let size = get_size(device);
|
||||
let value = Ratio::new(size.width.0 as f32, size.height.0 as f32);
|
||||
RangeOrOperator::evaluate_with_query_value(range_or_operator, query_value, value)
|
||||
}
|
||||
|
||||
/// https://drafts.csswg.org/mediaqueries-4/#aspect-ratio
|
||||
fn eval_aspect_ratio(context: &Context) -> Ratio {
|
||||
eval_aspect_ratio_for(context, Device::au_viewport_size)
|
||||
fn eval_aspect_ratio(
|
||||
device: &Device,
|
||||
query_value: Option<Ratio>,
|
||||
range_or_operator: Option<RangeOrOperator>,
|
||||
) -> bool {
|
||||
eval_aspect_ratio_for(
|
||||
device,
|
||||
query_value,
|
||||
range_or_operator,
|
||||
Device::au_viewport_size,
|
||||
)
|
||||
}
|
||||
|
||||
/// https://drafts.csswg.org/mediaqueries-4/#device-aspect-ratio
|
||||
fn eval_device_aspect_ratio(context: &Context) -> Ratio {
|
||||
eval_aspect_ratio_for(context, device_size)
|
||||
fn eval_device_aspect_ratio(
|
||||
device: &Device,
|
||||
query_value: Option<Ratio>,
|
||||
range_or_operator: Option<RangeOrOperator>,
|
||||
) -> bool {
|
||||
eval_aspect_ratio_for(device, query_value, range_or_operator, device_size)
|
||||
}
|
||||
|
||||
/// https://compat.spec.whatwg.org/#css-media-queries-webkit-device-pixel-ratio
|
||||
fn eval_device_pixel_ratio(context: &Context) -> f32 {
|
||||
eval_resolution(context).dppx()
|
||||
fn eval_device_pixel_ratio(
|
||||
device: &Device,
|
||||
query_value: Option<f32>,
|
||||
range_or_operator: Option<RangeOrOperator>,
|
||||
) -> bool {
|
||||
eval_resolution(
|
||||
device,
|
||||
query_value.map(Resolution::from_dppx),
|
||||
range_or_operator,
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
|
||||
#[repr(u8)]
|
||||
enum Orientation {
|
||||
Landscape,
|
||||
Portrait,
|
||||
}
|
||||
|
||||
fn eval_orientation_for<F>(device: &Device, value: Option<Orientation>, get_size: F) -> bool
|
||||
where
|
||||
F: FnOnce(&Device) -> Size2D<Au>,
|
||||
{
|
||||
let query_orientation = match value {
|
||||
Some(v) => v,
|
||||
None => return true,
|
||||
};
|
||||
|
||||
let size = get_size(device);
|
||||
|
||||
// Per spec, square viewports should be 'portrait'
|
||||
let is_landscape = size.width > size.height;
|
||||
match query_orientation {
|
||||
Orientation::Landscape => is_landscape,
|
||||
Orientation::Portrait => !is_landscape,
|
||||
}
|
||||
}
|
||||
|
||||
/// https://drafts.csswg.org/mediaqueries-4/#orientation
|
||||
fn eval_orientation(context: &Context, value: Option<Orientation>) -> bool {
|
||||
Orientation::eval(context.device().au_viewport_size(), value)
|
||||
fn eval_orientation(device: &Device, value: Option<Orientation>) -> bool {
|
||||
eval_orientation_for(device, value, Device::au_viewport_size)
|
||||
}
|
||||
|
||||
/// FIXME: There's no spec for `-moz-device-orientation`.
|
||||
fn eval_device_orientation(context: &Context, value: Option<Orientation>) -> bool {
|
||||
Orientation::eval(device_size(context.device()), value)
|
||||
fn eval_device_orientation(device: &Device, value: Option<Orientation>) -> bool {
|
||||
eval_orientation_for(device, value, device_size)
|
||||
}
|
||||
|
||||
/// Values for the display-mode media feature.
|
||||
|
@ -87,23 +184,25 @@ pub enum DisplayMode {
|
|||
}
|
||||
|
||||
/// https://w3c.github.io/manifest/#the-display-mode-media-feature
|
||||
fn eval_display_mode(context: &Context, query_value: Option<DisplayMode>) -> bool {
|
||||
fn eval_display_mode(device: &Device, query_value: Option<DisplayMode>) -> bool {
|
||||
match query_value {
|
||||
Some(v) => v == unsafe { bindings::Gecko_MediaFeatures_GetDisplayMode(context.device().document()) },
|
||||
Some(v) => v == unsafe { bindings::Gecko_MediaFeatures_GetDisplayMode(device.document()) },
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// https://drafts.csswg.org/mediaqueries-4/#grid
|
||||
fn eval_grid(_: &Context) -> bool {
|
||||
fn eval_grid(_: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>) -> bool {
|
||||
// Gecko doesn't support grid devices (e.g., ttys), so the 'grid' feature
|
||||
// is always 0.
|
||||
false
|
||||
let supports_grid = false;
|
||||
query_value.map_or(supports_grid, |v| v == supports_grid)
|
||||
}
|
||||
|
||||
/// https://compat.spec.whatwg.org/#css-media-queries-webkit-transform-3d
|
||||
fn eval_transform_3d(_: &Context) -> bool {
|
||||
true
|
||||
fn eval_transform_3d(_: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>) -> bool {
|
||||
let supports_transforms = true;
|
||||
query_value.map_or(supports_transforms, |v| v == supports_transforms)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
|
||||
|
@ -114,33 +213,58 @@ enum Scan {
|
|||
}
|
||||
|
||||
/// https://drafts.csswg.org/mediaqueries-4/#scan
|
||||
fn eval_scan(_: &Context, _: Option<Scan>) -> bool {
|
||||
fn eval_scan(_: &Device, _: Option<Scan>) -> bool {
|
||||
// Since Gecko doesn't support the 'tv' media type, the 'scan' feature never
|
||||
// matches.
|
||||
false
|
||||
}
|
||||
|
||||
/// https://drafts.csswg.org/mediaqueries-4/#color
|
||||
fn eval_color(context: &Context) -> u32 {
|
||||
unsafe { bindings::Gecko_MediaFeatures_GetColorDepth(context.device().document()) }
|
||||
fn eval_color(
|
||||
device: &Device,
|
||||
query_value: Option<u32>,
|
||||
range_or_operator: Option<RangeOrOperator>,
|
||||
) -> bool {
|
||||
let color_bits_per_channel =
|
||||
unsafe { bindings::Gecko_MediaFeatures_GetColorDepth(device.document()) };
|
||||
RangeOrOperator::evaluate(range_or_operator, query_value, color_bits_per_channel)
|
||||
}
|
||||
|
||||
/// https://drafts.csswg.org/mediaqueries-4/#color-index
|
||||
fn eval_color_index(_: &Context) -> u32 {
|
||||
fn eval_color_index(
|
||||
_: &Device,
|
||||
query_value: Option<u32>,
|
||||
range_or_operator: Option<RangeOrOperator>,
|
||||
) -> bool {
|
||||
// We should return zero if the device does not use a color lookup table.
|
||||
0
|
||||
let index = 0;
|
||||
RangeOrOperator::evaluate(range_or_operator, query_value, index)
|
||||
}
|
||||
|
||||
/// https://drafts.csswg.org/mediaqueries-4/#monochrome
|
||||
fn eval_monochrome(context: &Context) -> u32 {
|
||||
fn eval_monochrome(
|
||||
device: &Device,
|
||||
query_value: Option<u32>,
|
||||
range_or_operator: Option<RangeOrOperator>,
|
||||
) -> bool {
|
||||
// For color devices we should return 0.
|
||||
unsafe { bindings::Gecko_MediaFeatures_GetMonochromeBitsPerPixel(context.device().document()) }
|
||||
let depth =
|
||||
unsafe { bindings::Gecko_MediaFeatures_GetMonochromeBitsPerPixel(device.document()) };
|
||||
RangeOrOperator::evaluate(range_or_operator, query_value, depth)
|
||||
}
|
||||
|
||||
/// https://drafts.csswg.org/mediaqueries-4/#resolution
|
||||
fn eval_resolution(context: &Context) -> Resolution {
|
||||
let resolution_dppx = unsafe { bindings::Gecko_MediaFeatures_GetResolution(context.device().document()) };
|
||||
Resolution::from_dppx(resolution_dppx)
|
||||
fn eval_resolution(
|
||||
device: &Device,
|
||||
query_value: Option<Resolution>,
|
||||
range_or_operator: Option<RangeOrOperator>,
|
||||
) -> bool {
|
||||
let resolution_dppx = unsafe { bindings::Gecko_MediaFeatures_GetResolution(device.document()) };
|
||||
RangeOrOperator::evaluate(
|
||||
range_or_operator,
|
||||
query_value.map(|r| r.dppx()),
|
||||
resolution_dppx,
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
|
||||
|
@ -159,22 +283,10 @@ pub enum PrefersColorScheme {
|
|||
Dark,
|
||||
}
|
||||
|
||||
/// Values for the dynamic-range and video-dynamic-range media features.
|
||||
/// https://drafts.csswg.org/mediaqueries-5/#dynamic-range
|
||||
/// This implements PartialOrd so that lower values will correctly match
|
||||
/// higher capabilities.
|
||||
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, PartialOrd, ToCss)]
|
||||
#[repr(u8)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum DynamicRange {
|
||||
Standard,
|
||||
High,
|
||||
}
|
||||
|
||||
/// https://drafts.csswg.org/mediaqueries-5/#prefers-reduced-motion
|
||||
fn eval_prefers_reduced_motion(context: &Context, query_value: Option<PrefersReducedMotion>) -> bool {
|
||||
fn eval_prefers_reduced_motion(device: &Device, query_value: Option<PrefersReducedMotion>) -> bool {
|
||||
let prefers_reduced =
|
||||
unsafe { bindings::Gecko_MediaFeatures_PrefersReducedMotion(context.device().document()) };
|
||||
unsafe { bindings::Gecko_MediaFeatures_PrefersReducedMotion(device.document()) };
|
||||
let query_value = match query_value {
|
||||
Some(v) => v,
|
||||
None => return prefers_reduced,
|
||||
|
@ -191,20 +303,19 @@ fn eval_prefers_reduced_motion(context: &Context, query_value: Option<PrefersRed
|
|||
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)]
|
||||
#[repr(u8)]
|
||||
pub enum PrefersContrast {
|
||||
/// More contrast is preferred.
|
||||
/// More contrast is preferred. Corresponds to an accessibility theme
|
||||
/// being enabled or Firefox forcing high contrast colors.
|
||||
More,
|
||||
/// Low contrast is preferred.
|
||||
Less,
|
||||
/// Custom (not more, not less).
|
||||
Custom,
|
||||
/// The default value if neither high or low contrast is enabled.
|
||||
NoPreference,
|
||||
}
|
||||
|
||||
/// https://drafts.csswg.org/mediaqueries-5/#prefers-contrast
|
||||
fn eval_prefers_contrast(context: &Context, query_value: Option<PrefersContrast>) -> bool {
|
||||
fn eval_prefers_contrast(device: &Device, query_value: Option<PrefersContrast>) -> bool {
|
||||
let prefers_contrast =
|
||||
unsafe { bindings::Gecko_MediaFeatures_PrefersContrast(context.device().document()) };
|
||||
unsafe { bindings::Gecko_MediaFeatures_PrefersContrast(device.document()) };
|
||||
match query_value {
|
||||
Some(v) => v == prefers_contrast,
|
||||
None => prefers_contrast != PrefersContrast::NoPreference,
|
||||
|
@ -223,8 +334,8 @@ pub enum ForcedColors {
|
|||
}
|
||||
|
||||
/// https://drafts.csswg.org/mediaqueries-5/#forced-colors
|
||||
fn eval_forced_colors(context: &Context, query_value: Option<ForcedColors>) -> bool {
|
||||
let forced = !context.device().use_document_colors();
|
||||
fn eval_forced_colors(device: &Device, query_value: Option<ForcedColors>) -> bool {
|
||||
let forced = !device.use_document_colors();
|
||||
match query_value {
|
||||
Some(query_value) => forced == (query_value == ForcedColors::Active),
|
||||
None => forced,
|
||||
|
@ -241,7 +352,7 @@ enum OverflowBlock {
|
|||
}
|
||||
|
||||
/// https://drafts.csswg.org/mediaqueries-4/#mf-overflow-block
|
||||
fn eval_overflow_block(context: &Context, query_value: Option<OverflowBlock>) -> bool {
|
||||
fn eval_overflow_block(device: &Device, query_value: Option<OverflowBlock>) -> bool {
|
||||
// For the time being, assume that printing (including previews)
|
||||
// is the only time when we paginate, and we are otherwise always
|
||||
// scrolling. This is true at the moment in Firefox, but may need
|
||||
|
@ -249,7 +360,7 @@ fn eval_overflow_block(context: &Context, query_value: Option<OverflowBlock>) ->
|
|||
// billboard mode that doesn't support overflow at all).
|
||||
//
|
||||
// If this ever changes, don't forget to change eval_overflow_inline too.
|
||||
let scrolling = context.device().media_type() != MediaType::print();
|
||||
let scrolling = device.media_type() != MediaType::print();
|
||||
let query_value = match query_value {
|
||||
Some(v) => v,
|
||||
None => return true,
|
||||
|
@ -270,9 +381,9 @@ enum OverflowInline {
|
|||
}
|
||||
|
||||
/// https://drafts.csswg.org/mediaqueries-4/#mf-overflow-inline
|
||||
fn eval_overflow_inline(context: &Context, query_value: Option<OverflowInline>) -> bool {
|
||||
fn eval_overflow_inline(device: &Device, query_value: Option<OverflowInline>) -> bool {
|
||||
// See the note in eval_overflow_block.
|
||||
let scrolling = context.device().media_type() != MediaType::print();
|
||||
let scrolling = device.media_type() != MediaType::print();
|
||||
let query_value = match query_value {
|
||||
Some(v) => v,
|
||||
None => return scrolling,
|
||||
|
@ -284,41 +395,13 @@ fn eval_overflow_inline(context: &Context, query_value: Option<OverflowInline>)
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
|
||||
#[repr(u8)]
|
||||
enum Update {
|
||||
None,
|
||||
Slow,
|
||||
Fast,
|
||||
}
|
||||
|
||||
/// https://drafts.csswg.org/mediaqueries-4/#update
|
||||
fn eval_update(context: &Context, query_value: Option<Update>) -> bool {
|
||||
// This has similar caveats to those described in eval_overflow_block.
|
||||
// For now, we report that print (incl. print media simulation,
|
||||
// which can in fact update but is limited to the developer tools)
|
||||
// is `update: none` and that all other contexts are `update: fast`,
|
||||
// which may not be true for future platforms, like e-ink devices.
|
||||
let can_update = context.device().media_type() != MediaType::print();
|
||||
let query_value = match query_value {
|
||||
Some(v) => v,
|
||||
None => return can_update,
|
||||
};
|
||||
|
||||
match query_value {
|
||||
Update::None => !can_update,
|
||||
Update::Slow => false,
|
||||
Update::Fast => can_update,
|
||||
}
|
||||
}
|
||||
|
||||
fn do_eval_prefers_color_scheme(
|
||||
context: &Context,
|
||||
device: &Device,
|
||||
use_content: bool,
|
||||
query_value: Option<PrefersColorScheme>,
|
||||
) -> bool {
|
||||
let prefers_color_scheme =
|
||||
unsafe { bindings::Gecko_MediaFeatures_PrefersColorScheme(context.device().document(), use_content) };
|
||||
unsafe { bindings::Gecko_MediaFeatures_PrefersColorScheme(device.document(), use_content) };
|
||||
match query_value {
|
||||
Some(v) => prefers_color_scheme == v,
|
||||
None => true,
|
||||
|
@ -326,34 +409,15 @@ fn do_eval_prefers_color_scheme(
|
|||
}
|
||||
|
||||
/// https://drafts.csswg.org/mediaqueries-5/#prefers-color-scheme
|
||||
fn eval_prefers_color_scheme(context: &Context, query_value: Option<PrefersColorScheme>) -> bool {
|
||||
do_eval_prefers_color_scheme(context, /* use_content = */ false, query_value)
|
||||
fn eval_prefers_color_scheme(device: &Device, query_value: Option<PrefersColorScheme>) -> bool {
|
||||
do_eval_prefers_color_scheme(device, /* use_content = */ false, query_value)
|
||||
}
|
||||
|
||||
fn eval_content_prefers_color_scheme(
|
||||
context: &Context,
|
||||
device: &Device,
|
||||
query_value: Option<PrefersColorScheme>,
|
||||
) -> bool {
|
||||
do_eval_prefers_color_scheme(context, /* use_content = */ true, query_value)
|
||||
}
|
||||
|
||||
/// https://drafts.csswg.org/mediaqueries-5/#dynamic-range
|
||||
fn eval_dynamic_range(context: &Context, query_value: Option<DynamicRange>) -> bool {
|
||||
let dynamic_range =
|
||||
unsafe { bindings::Gecko_MediaFeatures_DynamicRange(context.device().document()) };
|
||||
match query_value {
|
||||
Some(v) => dynamic_range >= v,
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
/// https://drafts.csswg.org/mediaqueries-5/#video-dynamic-range
|
||||
fn eval_video_dynamic_range(context: &Context, query_value: Option<DynamicRange>) -> bool {
|
||||
let dynamic_range =
|
||||
unsafe { bindings::Gecko_MediaFeatures_VideoDynamicRange(context.device().document()) };
|
||||
match query_value {
|
||||
Some(v) => dynamic_range >= v,
|
||||
None => false,
|
||||
}
|
||||
do_eval_prefers_color_scheme(device, /* use_content = */ true, query_value)
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
|
@ -365,15 +429,15 @@ bitflags! {
|
|||
}
|
||||
}
|
||||
|
||||
fn primary_pointer_capabilities(context: &Context) -> PointerCapabilities {
|
||||
fn primary_pointer_capabilities(device: &Device) -> PointerCapabilities {
|
||||
PointerCapabilities::from_bits_truncate(unsafe {
|
||||
bindings::Gecko_MediaFeatures_PrimaryPointerCapabilities(context.device().document())
|
||||
bindings::Gecko_MediaFeatures_PrimaryPointerCapabilities(device.document())
|
||||
})
|
||||
}
|
||||
|
||||
fn all_pointer_capabilities(context: &Context) -> PointerCapabilities {
|
||||
fn all_pointer_capabilities(device: &Device) -> PointerCapabilities {
|
||||
PointerCapabilities::from_bits_truncate(unsafe {
|
||||
bindings::Gecko_MediaFeatures_AllPointerCapabilities(context.device().document())
|
||||
bindings::Gecko_MediaFeatures_AllPointerCapabilities(device.document())
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -402,13 +466,13 @@ fn eval_pointer_capabilities(
|
|||
}
|
||||
|
||||
/// https://drafts.csswg.org/mediaqueries-4/#pointer
|
||||
fn eval_pointer(context: &Context, query_value: Option<Pointer>) -> bool {
|
||||
eval_pointer_capabilities(query_value, primary_pointer_capabilities(context))
|
||||
fn eval_pointer(device: &Device, query_value: Option<Pointer>) -> bool {
|
||||
eval_pointer_capabilities(query_value, primary_pointer_capabilities(device))
|
||||
}
|
||||
|
||||
/// https://drafts.csswg.org/mediaqueries-4/#descdef-media-any-pointer
|
||||
fn eval_any_pointer(context: &Context, query_value: Option<Pointer>) -> bool {
|
||||
eval_pointer_capabilities(query_value, all_pointer_capabilities(context))
|
||||
fn eval_any_pointer(device: &Device, query_value: Option<Pointer>) -> bool {
|
||||
eval_pointer_capabilities(query_value, all_pointer_capabilities(device))
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
|
||||
|
@ -435,33 +499,54 @@ fn eval_hover_capabilities(
|
|||
}
|
||||
|
||||
/// https://drafts.csswg.org/mediaqueries-4/#hover
|
||||
fn eval_hover(context: &Context, query_value: Option<Hover>) -> bool {
|
||||
eval_hover_capabilities(query_value, primary_pointer_capabilities(context))
|
||||
fn eval_hover(device: &Device, query_value: Option<Hover>) -> bool {
|
||||
eval_hover_capabilities(query_value, primary_pointer_capabilities(device))
|
||||
}
|
||||
|
||||
/// https://drafts.csswg.org/mediaqueries-4/#descdef-media-any-hover
|
||||
fn eval_any_hover(context: &Context, query_value: Option<Hover>) -> bool {
|
||||
eval_hover_capabilities(query_value, all_pointer_capabilities(context))
|
||||
fn eval_any_hover(device: &Device, query_value: Option<Hover>) -> bool {
|
||||
eval_hover_capabilities(query_value, all_pointer_capabilities(device))
|
||||
}
|
||||
|
||||
fn eval_moz_is_glyph(context: &Context) -> bool {
|
||||
context.device().document().mIsSVGGlyphsDocument()
|
||||
fn eval_moz_is_glyph(
|
||||
device: &Device,
|
||||
query_value: Option<bool>,
|
||||
_: Option<RangeOrOperator>,
|
||||
) -> bool {
|
||||
let is_glyph = device.document().mIsSVGGlyphsDocument();
|
||||
query_value.map_or(is_glyph, |v| v == is_glyph)
|
||||
}
|
||||
|
||||
fn eval_moz_print_preview(context: &Context) -> bool {
|
||||
let is_print_preview = context.device().is_print_preview();
|
||||
fn eval_moz_print_preview(
|
||||
device: &Device,
|
||||
query_value: Option<bool>,
|
||||
_: Option<RangeOrOperator>,
|
||||
) -> bool {
|
||||
let is_print_preview = device.is_print_preview();
|
||||
if is_print_preview {
|
||||
debug_assert_eq!(context.device().media_type(), MediaType::print());
|
||||
debug_assert_eq!(device.media_type(), MediaType::print());
|
||||
}
|
||||
is_print_preview
|
||||
query_value.map_or(is_print_preview, |v| v == is_print_preview)
|
||||
}
|
||||
|
||||
fn eval_moz_non_native_content_theme(context: &Context) -> bool {
|
||||
unsafe { bindings::Gecko_MediaFeatures_ShouldAvoidNativeTheme(context.device().document()) }
|
||||
fn eval_moz_non_native_content_theme(
|
||||
device: &Device,
|
||||
query_value: Option<bool>,
|
||||
_: Option<RangeOrOperator>,
|
||||
) -> bool {
|
||||
let non_native_theme =
|
||||
unsafe { bindings::Gecko_MediaFeatures_ShouldAvoidNativeTheme(device.document()) };
|
||||
query_value.map_or(non_native_theme, |v| v == non_native_theme)
|
||||
}
|
||||
|
||||
fn eval_moz_is_resource_document(context: &Context) -> bool {
|
||||
unsafe { bindings::Gecko_MediaFeatures_IsResourceDocument(context.device().document()) }
|
||||
fn eval_moz_is_resource_document(
|
||||
device: &Device,
|
||||
query_value: Option<bool>,
|
||||
_: Option<RangeOrOperator>,
|
||||
) -> bool {
|
||||
let is_resource_doc =
|
||||
unsafe { bindings::Gecko_MediaFeatures_IsResourceDocument(device.document()) };
|
||||
query_value.map_or(is_resource_doc, |v| v == is_resource_doc)
|
||||
}
|
||||
|
||||
/// Allows front-end CSS to discern platform via media queries.
|
||||
|
@ -487,7 +572,7 @@ pub enum Platform {
|
|||
WindowsWin10,
|
||||
}
|
||||
|
||||
fn eval_moz_platform(_: &Context, query_value: Option<Platform>) -> bool {
|
||||
fn eval_moz_platform(_: &Device, query_value: Option<Platform>) -> bool {
|
||||
let query_value = match query_value {
|
||||
Some(v) => v,
|
||||
None => return false,
|
||||
|
@ -496,22 +581,32 @@ fn eval_moz_platform(_: &Context, query_value: Option<Platform>) -> bool {
|
|||
unsafe { bindings::Gecko_MediaFeatures_MatchesPlatform(query_value) }
|
||||
}
|
||||
|
||||
fn eval_moz_windows_non_native_menus(context: &Context) -> bool {
|
||||
fn eval_moz_windows_non_native_menus(
|
||||
device: &Device,
|
||||
query_value: Option<bool>,
|
||||
_: Option<RangeOrOperator>,
|
||||
) -> bool {
|
||||
let use_non_native_menus = match static_prefs::pref!("browser.display.windows.non_native_menus")
|
||||
{
|
||||
0 => false,
|
||||
1 => true,
|
||||
_ => {
|
||||
eval_moz_platform(context, Some(Platform::WindowsWin10)) &&
|
||||
eval_moz_platform(device, Some(Platform::WindowsWin10)) &&
|
||||
get_lnf_int_as_bool(bindings::LookAndFeel_IntID::WindowsDefaultTheme as i32)
|
||||
},
|
||||
};
|
||||
|
||||
use_non_native_menus
|
||||
query_value.map_or(use_non_native_menus, |v| v == use_non_native_menus)
|
||||
}
|
||||
|
||||
fn eval_moz_overlay_scrollbars(context: &Context) -> bool {
|
||||
unsafe { bindings::Gecko_MediaFeatures_UseOverlayScrollbars(context.device().document()) }
|
||||
fn eval_moz_overlay_scrollbars(
|
||||
device: &Device,
|
||||
query_value: Option<bool>,
|
||||
_: Option<RangeOrOperator>,
|
||||
) -> bool {
|
||||
let use_overlay =
|
||||
unsafe { bindings::Gecko_MediaFeatures_UseOverlayScrollbars(device.document()) };
|
||||
query_value.map_or(use_overlay, |v| v == use_overlay)
|
||||
}
|
||||
|
||||
fn get_lnf_int(int_id: i32) -> i32 {
|
||||
|
@ -540,15 +635,16 @@ fn get_scrollbar_end_forward(int_id: i32) -> bool {
|
|||
|
||||
macro_rules! lnf_int_feature {
|
||||
($feature_name:expr, $int_id:ident, $get_value:ident) => {{
|
||||
fn __eval(_: &Context) -> bool {
|
||||
$get_value(bindings::LookAndFeel_IntID::$int_id as i32)
|
||||
fn __eval(_: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>) -> bool {
|
||||
let value = $get_value(bindings::LookAndFeel_IntID::$int_id as i32);
|
||||
query_value.map_or(value, |v| v == value)
|
||||
}
|
||||
|
||||
feature!(
|
||||
$feature_name,
|
||||
AllowsRanges::No,
|
||||
Evaluator::BoolInteger(__eval),
|
||||
FeatureFlags::CHROME_AND_UA_ONLY,
|
||||
ParsingRequirements::CHROME_AND_UA_ONLY,
|
||||
)
|
||||
}};
|
||||
($feature_name:expr, $int_id:ident) => {{
|
||||
|
@ -565,18 +661,18 @@ macro_rules! lnf_int_feature {
|
|||
/// pref, with `rust: true`. The feature name needs to be defined in
|
||||
/// `StaticAtoms.py` just like the others. In order to support dynamic changes,
|
||||
/// you also need to add them to kMediaQueryPrefs in nsXPLookAndFeel.cpp
|
||||
#[allow(unused)]
|
||||
macro_rules! bool_pref_feature {
|
||||
($feature_name:expr, $pref:tt) => {{
|
||||
fn __eval(_: &Context) -> bool {
|
||||
static_prefs::pref!($pref)
|
||||
fn __eval(_: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>) -> bool {
|
||||
let value = static_prefs::pref!($pref);
|
||||
query_value.map_or(value, |v| v == value)
|
||||
}
|
||||
|
||||
feature!(
|
||||
$feature_name,
|
||||
AllowsRanges::No,
|
||||
Evaluator::BoolInteger(__eval),
|
||||
FeatureFlags::CHROME_AND_UA_ONLY,
|
||||
ParsingRequirements::CHROME_AND_UA_ONLY,
|
||||
)
|
||||
}};
|
||||
}
|
||||
|
@ -586,54 +682,54 @@ macro_rules! bool_pref_feature {
|
|||
/// to support new types in these entries and (2) ensuring that either
|
||||
/// nsPresContext::MediaFeatureValuesChanged is called when the value that
|
||||
/// would be returned by the evaluator function could change.
|
||||
pub static MEDIA_FEATURES: [QueryFeatureDescription; 59] = [
|
||||
pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [
|
||||
feature!(
|
||||
atom!("width"),
|
||||
AllowsRanges::Yes,
|
||||
Evaluator::Length(eval_width),
|
||||
FeatureFlags::empty(),
|
||||
ParsingRequirements::empty(),
|
||||
),
|
||||
feature!(
|
||||
atom!("height"),
|
||||
AllowsRanges::Yes,
|
||||
Evaluator::Length(eval_height),
|
||||
FeatureFlags::empty(),
|
||||
ParsingRequirements::empty(),
|
||||
),
|
||||
feature!(
|
||||
atom!("aspect-ratio"),
|
||||
AllowsRanges::Yes,
|
||||
Evaluator::NumberRatio(eval_aspect_ratio),
|
||||
FeatureFlags::empty(),
|
||||
ParsingRequirements::empty(),
|
||||
),
|
||||
feature!(
|
||||
atom!("orientation"),
|
||||
AllowsRanges::No,
|
||||
keyword_evaluator!(eval_orientation, Orientation),
|
||||
FeatureFlags::empty(),
|
||||
ParsingRequirements::empty(),
|
||||
),
|
||||
feature!(
|
||||
atom!("device-width"),
|
||||
AllowsRanges::Yes,
|
||||
Evaluator::Length(eval_device_width),
|
||||
FeatureFlags::empty(),
|
||||
ParsingRequirements::empty(),
|
||||
),
|
||||
feature!(
|
||||
atom!("device-height"),
|
||||
AllowsRanges::Yes,
|
||||
Evaluator::Length(eval_device_height),
|
||||
FeatureFlags::empty(),
|
||||
ParsingRequirements::empty(),
|
||||
),
|
||||
feature!(
|
||||
atom!("device-aspect-ratio"),
|
||||
AllowsRanges::Yes,
|
||||
Evaluator::NumberRatio(eval_device_aspect_ratio),
|
||||
FeatureFlags::empty(),
|
||||
ParsingRequirements::empty(),
|
||||
),
|
||||
feature!(
|
||||
atom!("-moz-device-orientation"),
|
||||
AllowsRanges::No,
|
||||
keyword_evaluator!(eval_device_orientation, Orientation),
|
||||
FeatureFlags::empty(),
|
||||
ParsingRequirements::empty(),
|
||||
),
|
||||
// Webkit extensions that we support for de-facto web compatibility.
|
||||
// -webkit-{min|max}-device-pixel-ratio (controlled with its own pref):
|
||||
|
@ -641,68 +737,68 @@ pub static MEDIA_FEATURES: [QueryFeatureDescription; 59] = [
|
|||
atom!("device-pixel-ratio"),
|
||||
AllowsRanges::Yes,
|
||||
Evaluator::Float(eval_device_pixel_ratio),
|
||||
FeatureFlags::WEBKIT_PREFIX,
|
||||
ParsingRequirements::WEBKIT_PREFIX,
|
||||
),
|
||||
// -webkit-transform-3d.
|
||||
feature!(
|
||||
atom!("transform-3d"),
|
||||
AllowsRanges::No,
|
||||
Evaluator::BoolInteger(eval_transform_3d),
|
||||
FeatureFlags::WEBKIT_PREFIX,
|
||||
ParsingRequirements::WEBKIT_PREFIX,
|
||||
),
|
||||
feature!(
|
||||
atom!("-moz-device-pixel-ratio"),
|
||||
AllowsRanges::Yes,
|
||||
Evaluator::Float(eval_device_pixel_ratio),
|
||||
FeatureFlags::empty(),
|
||||
ParsingRequirements::empty(),
|
||||
),
|
||||
feature!(
|
||||
atom!("resolution"),
|
||||
AllowsRanges::Yes,
|
||||
Evaluator::Resolution(eval_resolution),
|
||||
FeatureFlags::empty(),
|
||||
ParsingRequirements::empty(),
|
||||
),
|
||||
feature!(
|
||||
atom!("display-mode"),
|
||||
AllowsRanges::No,
|
||||
keyword_evaluator!(eval_display_mode, DisplayMode),
|
||||
FeatureFlags::empty(),
|
||||
ParsingRequirements::empty(),
|
||||
),
|
||||
feature!(
|
||||
atom!("grid"),
|
||||
AllowsRanges::No,
|
||||
Evaluator::BoolInteger(eval_grid),
|
||||
FeatureFlags::empty(),
|
||||
ParsingRequirements::empty(),
|
||||
),
|
||||
feature!(
|
||||
atom!("scan"),
|
||||
AllowsRanges::No,
|
||||
keyword_evaluator!(eval_scan, Scan),
|
||||
FeatureFlags::empty(),
|
||||
ParsingRequirements::empty(),
|
||||
),
|
||||
feature!(
|
||||
atom!("color"),
|
||||
AllowsRanges::Yes,
|
||||
Evaluator::Integer(eval_color),
|
||||
FeatureFlags::empty(),
|
||||
ParsingRequirements::empty(),
|
||||
),
|
||||
feature!(
|
||||
atom!("color-index"),
|
||||
AllowsRanges::Yes,
|
||||
Evaluator::Integer(eval_color_index),
|
||||
FeatureFlags::empty(),
|
||||
ParsingRequirements::empty(),
|
||||
),
|
||||
feature!(
|
||||
atom!("monochrome"),
|
||||
AllowsRanges::Yes,
|
||||
Evaluator::Integer(eval_monochrome),
|
||||
FeatureFlags::empty(),
|
||||
ParsingRequirements::empty(),
|
||||
),
|
||||
feature!(
|
||||
atom!("prefers-reduced-motion"),
|
||||
AllowsRanges::No,
|
||||
keyword_evaluator!(eval_prefers_reduced_motion, PrefersReducedMotion),
|
||||
FeatureFlags::empty(),
|
||||
ParsingRequirements::empty(),
|
||||
),
|
||||
feature!(
|
||||
atom!("prefers-contrast"),
|
||||
|
@ -713,49 +809,31 @@ pub static MEDIA_FEATURES: [QueryFeatureDescription; 59] = [
|
|||
// layout.css.prefers-contrast.enabled preference. See
|
||||
// disabed_by_pref in media_feature_expression.rs for how that
|
||||
// is done.
|
||||
FeatureFlags::empty(),
|
||||
ParsingRequirements::empty(),
|
||||
),
|
||||
feature!(
|
||||
atom!("forced-colors"),
|
||||
AllowsRanges::No,
|
||||
keyword_evaluator!(eval_forced_colors, ForcedColors),
|
||||
FeatureFlags::empty(),
|
||||
ParsingRequirements::empty(),
|
||||
),
|
||||
feature!(
|
||||
atom!("overflow-block"),
|
||||
AllowsRanges::No,
|
||||
keyword_evaluator!(eval_overflow_block, OverflowBlock),
|
||||
FeatureFlags::empty(),
|
||||
ParsingRequirements::empty(),
|
||||
),
|
||||
feature!(
|
||||
atom!("overflow-inline"),
|
||||
AllowsRanges::No,
|
||||
keyword_evaluator!(eval_overflow_inline, OverflowInline),
|
||||
FeatureFlags::empty(),
|
||||
),
|
||||
feature!(
|
||||
atom!("update"),
|
||||
AllowsRanges::No,
|
||||
keyword_evaluator!(eval_update, Update),
|
||||
FeatureFlags::empty(),
|
||||
ParsingRequirements::empty(),
|
||||
),
|
||||
feature!(
|
||||
atom!("prefers-color-scheme"),
|
||||
AllowsRanges::No,
|
||||
keyword_evaluator!(eval_prefers_color_scheme, PrefersColorScheme),
|
||||
FeatureFlags::empty(),
|
||||
),
|
||||
feature!(
|
||||
atom!("dynamic-range"),
|
||||
AllowsRanges::No,
|
||||
keyword_evaluator!(eval_dynamic_range, DynamicRange),
|
||||
FeatureFlags::empty(),
|
||||
),
|
||||
feature!(
|
||||
atom!("video-dynamic-range"),
|
||||
AllowsRanges::No,
|
||||
keyword_evaluator!(eval_video_dynamic_range, DynamicRange),
|
||||
FeatureFlags::empty(),
|
||||
ParsingRequirements::empty(),
|
||||
),
|
||||
// Evaluates to the preferred color scheme for content. Only useful in
|
||||
// chrome context, where the chrome color-scheme and the content
|
||||
|
@ -764,31 +842,31 @@ pub static MEDIA_FEATURES: [QueryFeatureDescription; 59] = [
|
|||
atom!("-moz-content-prefers-color-scheme"),
|
||||
AllowsRanges::No,
|
||||
keyword_evaluator!(eval_content_prefers_color_scheme, PrefersColorScheme),
|
||||
FeatureFlags::CHROME_AND_UA_ONLY,
|
||||
ParsingRequirements::CHROME_AND_UA_ONLY,
|
||||
),
|
||||
feature!(
|
||||
atom!("pointer"),
|
||||
AllowsRanges::No,
|
||||
keyword_evaluator!(eval_pointer, Pointer),
|
||||
FeatureFlags::empty(),
|
||||
ParsingRequirements::empty(),
|
||||
),
|
||||
feature!(
|
||||
atom!("any-pointer"),
|
||||
AllowsRanges::No,
|
||||
keyword_evaluator!(eval_any_pointer, Pointer),
|
||||
FeatureFlags::empty(),
|
||||
ParsingRequirements::empty(),
|
||||
),
|
||||
feature!(
|
||||
atom!("hover"),
|
||||
AllowsRanges::No,
|
||||
keyword_evaluator!(eval_hover, Hover),
|
||||
FeatureFlags::empty(),
|
||||
ParsingRequirements::empty(),
|
||||
),
|
||||
feature!(
|
||||
atom!("any-hover"),
|
||||
AllowsRanges::No,
|
||||
keyword_evaluator!(eval_any_hover, Hover),
|
||||
FeatureFlags::empty(),
|
||||
ParsingRequirements::empty(),
|
||||
),
|
||||
// Internal -moz-is-glyph media feature: applies only inside SVG glyphs.
|
||||
// Internal because it is really only useful in the user agent anyway
|
||||
|
@ -797,43 +875,43 @@ pub static MEDIA_FEATURES: [QueryFeatureDescription; 59] = [
|
|||
atom!("-moz-is-glyph"),
|
||||
AllowsRanges::No,
|
||||
Evaluator::BoolInteger(eval_moz_is_glyph),
|
||||
FeatureFlags::CHROME_AND_UA_ONLY,
|
||||
ParsingRequirements::CHROME_AND_UA_ONLY,
|
||||
),
|
||||
feature!(
|
||||
atom!("-moz-is-resource-document"),
|
||||
AllowsRanges::No,
|
||||
Evaluator::BoolInteger(eval_moz_is_resource_document),
|
||||
FeatureFlags::CHROME_AND_UA_ONLY,
|
||||
ParsingRequirements::CHROME_AND_UA_ONLY,
|
||||
),
|
||||
feature!(
|
||||
atom!("-moz-platform"),
|
||||
AllowsRanges::No,
|
||||
keyword_evaluator!(eval_moz_platform, Platform),
|
||||
FeatureFlags::CHROME_AND_UA_ONLY,
|
||||
ParsingRequirements::CHROME_AND_UA_ONLY,
|
||||
),
|
||||
feature!(
|
||||
atom!("-moz-print-preview"),
|
||||
AllowsRanges::No,
|
||||
Evaluator::BoolInteger(eval_moz_print_preview),
|
||||
FeatureFlags::CHROME_AND_UA_ONLY,
|
||||
ParsingRequirements::CHROME_AND_UA_ONLY,
|
||||
),
|
||||
feature!(
|
||||
atom!("-moz-non-native-content-theme"),
|
||||
AllowsRanges::No,
|
||||
Evaluator::BoolInteger(eval_moz_non_native_content_theme),
|
||||
FeatureFlags::CHROME_AND_UA_ONLY,
|
||||
ParsingRequirements::CHROME_AND_UA_ONLY,
|
||||
),
|
||||
feature!(
|
||||
atom!("-moz-windows-non-native-menus"),
|
||||
AllowsRanges::No,
|
||||
Evaluator::BoolInteger(eval_moz_windows_non_native_menus),
|
||||
FeatureFlags::CHROME_AND_UA_ONLY,
|
||||
ParsingRequirements::CHROME_AND_UA_ONLY,
|
||||
),
|
||||
feature!(
|
||||
atom!("-moz-overlay-scrollbars"),
|
||||
AllowsRanges::No,
|
||||
Evaluator::BoolInteger(eval_moz_overlay_scrollbars),
|
||||
FeatureFlags::CHROME_AND_UA_ONLY,
|
||||
ParsingRequirements::CHROME_AND_UA_ONLY,
|
||||
),
|
||||
lnf_int_feature!(
|
||||
atom!("-moz-scrollbar-start-backward"),
|
||||
|
@ -855,6 +933,10 @@ pub static MEDIA_FEATURES: [QueryFeatureDescription; 59] = [
|
|||
ScrollArrowStyle,
|
||||
get_scrollbar_end_forward
|
||||
),
|
||||
lnf_int_feature!(
|
||||
atom!("-moz-scrollbar-thumb-proportional"),
|
||||
ScrollSliderStyle
|
||||
),
|
||||
lnf_int_feature!(atom!("-moz-menubar-drag"), MenuBarDrag),
|
||||
lnf_int_feature!(atom!("-moz-windows-default-theme"), WindowsDefaultTheme),
|
||||
lnf_int_feature!(atom!("-moz-mac-graphite-theme"), MacGraphiteTheme),
|
||||
|
@ -877,4 +959,8 @@ pub static MEDIA_FEATURES: [QueryFeatureDescription; 59] = [
|
|||
GTKCSDReversedPlacement
|
||||
),
|
||||
lnf_int_feature!(atom!("-moz-system-dark-theme"), SystemUsesDarkTheme),
|
||||
bool_pref_feature!(
|
||||
atom!("-moz-proton-places-tooltip"),
|
||||
"browser.proton.places-tooltip.enabled"
|
||||
),
|
||||
];
|
||||
|
|
|
@ -17,7 +17,6 @@ use crate::values::computed::font::GenericFontFamily;
|
|||
use crate::values::computed::{ColorScheme, Length};
|
||||
use crate::values::specified::color::SystemColor;
|
||||
use crate::values::specified::font::FONT_MEDIUM_PX;
|
||||
use crate::values::specified::ViewportVariant;
|
||||
use crate::values::{CustomIdent, KeyframesName};
|
||||
use app_units::{Au, AU_PER_PX};
|
||||
use cssparser::RGBA;
|
||||
|
@ -59,9 +58,6 @@ pub struct Device {
|
|||
/// Whether any styles computed in the document relied on the viewport size
|
||||
/// by using vw/vh/vmin/vmax units.
|
||||
used_viewport_size: AtomicBool,
|
||||
/// Whether any styles computed in the document relied on the viewport size
|
||||
/// by using dvw/dvh/dvmin/dvmax units.
|
||||
used_dynamic_viewport_size: AtomicBool,
|
||||
/// The CssEnvironment object responsible of getting CSS environment
|
||||
/// variables.
|
||||
environment: CssEnvironment,
|
||||
|
@ -104,7 +100,6 @@ impl Device {
|
|||
used_root_font_size: AtomicBool::new(false),
|
||||
used_font_metrics: AtomicBool::new(false),
|
||||
used_viewport_size: AtomicBool::new(false),
|
||||
used_dynamic_viewport_size: AtomicBool::new(false),
|
||||
environment: CssEnvironment,
|
||||
}
|
||||
}
|
||||
|
@ -272,8 +267,6 @@ impl Device {
|
|||
self.used_root_font_size.store(false, Ordering::Relaxed);
|
||||
self.used_font_metrics.store(false, Ordering::Relaxed);
|
||||
self.used_viewport_size.store(false, Ordering::Relaxed);
|
||||
self.used_dynamic_viewport_size
|
||||
.store(false, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Returns whether we ever looked up the root font size of the Device.
|
||||
|
@ -344,10 +337,7 @@ impl Device {
|
|||
|
||||
/// Returns the current viewport size in app units, recording that it's been
|
||||
/// used for viewport unit resolution.
|
||||
pub fn au_viewport_size_for_viewport_unit_resolution(
|
||||
&self,
|
||||
variant: ViewportVariant,
|
||||
) -> Size2D<Au> {
|
||||
pub fn au_viewport_size_for_viewport_unit_resolution(&self) -> Size2D<Au> {
|
||||
self.used_viewport_size.store(true, Ordering::Relaxed);
|
||||
let pc = match self.pres_context() {
|
||||
Some(pc) => pc,
|
||||
|
@ -358,42 +348,8 @@ impl Device {
|
|||
return self.page_size_minus_default_margin(pc);
|
||||
}
|
||||
|
||||
match variant {
|
||||
ViewportVariant::UADefault => {
|
||||
let size = &pc.mSizeForViewportUnits;
|
||||
Size2D::new(Au(size.width), Au(size.height))
|
||||
},
|
||||
ViewportVariant::Small => {
|
||||
let size = &pc.mVisibleArea;
|
||||
Size2D::new(Au(size.width), Au(size.height))
|
||||
},
|
||||
ViewportVariant::Large => {
|
||||
let size = &pc.mVisibleArea;
|
||||
// Looks like IntCoordTyped is treated as if it's u32 in Rust.
|
||||
debug_assert!(
|
||||
/* pc.mDynamicToolbarMaxHeight >=0 && */
|
||||
pc.mDynamicToolbarMaxHeight < i32::MAX as u32
|
||||
);
|
||||
Size2D::new(
|
||||
Au(size.width),
|
||||
Au(size.height
|
||||
+ pc.mDynamicToolbarMaxHeight as i32 * pc.mCurAppUnitsPerDevPixel),
|
||||
)
|
||||
},
|
||||
ViewportVariant::Dynamic => {
|
||||
self.used_dynamic_viewport_size.store(true, Ordering::Relaxed);
|
||||
let size = &pc.mVisibleArea;
|
||||
// Looks like IntCoordTyped is treated as if it's u32 in Rust.
|
||||
debug_assert!(
|
||||
/* pc.mDynamicToolbarHeight >=0 && */
|
||||
pc.mDynamicToolbarHeight < i32::MAX as u32
|
||||
);
|
||||
Size2D::new(
|
||||
Au(size.width),
|
||||
Au(size.height + pc.mDynamicToolbarHeight as i32 * pc.mCurAppUnitsPerDevPixel),
|
||||
)
|
||||
},
|
||||
}
|
||||
let size = &pc.mSizeForViewportUnits;
|
||||
Size2D::new(Au(size.width), Au(size.height))
|
||||
}
|
||||
|
||||
/// Returns whether we ever looked up the viewport size of the Device.
|
||||
|
@ -401,21 +357,11 @@ impl Device {
|
|||
self.used_viewport_size.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Returns whether we ever looked up the dynamic viewport size of the Device.
|
||||
pub fn used_dynamic_viewport_size(&self) -> bool {
|
||||
self.used_dynamic_viewport_size.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Returns whether font metrics have been queried.
|
||||
pub fn used_font_metrics(&self) -> bool {
|
||||
self.used_font_metrics.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Returns whether visited styles are enabled.
|
||||
pub fn visited_styles_enabled(&self) -> bool {
|
||||
unsafe { bindings::Gecko_VisitedStylesEnabled(self.document()) }
|
||||
}
|
||||
|
||||
/// Returns the device pixel ratio.
|
||||
pub fn device_pixel_ratio(&self) -> Scale<f32, CSSPixel, DevicePixel> {
|
||||
let pc = match self.pres_context() {
|
||||
|
|
|
@ -54,7 +54,7 @@ macro_rules! apply_non_ts_list {
|
|||
("-moz-styleeditor-transitioning", MozStyleeditorTransitioning, IN_STYLEEDITOR_TRANSITIONING_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
|
||||
("fullscreen", Fullscreen, IN_FULLSCREEN_STATE, _),
|
||||
("-moz-modal-dialog", MozModalDialog, IN_MODAL_DIALOG_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
|
||||
("-moz-topmost-modal", MozTopmostModal, IN_TOPMOST_MODAL_TOP_LAYER_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
|
||||
("-moz-topmost-modal-dialog", MozTopmostModalDialog, IN_TOPMOST_MODAL_DIALOG_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
|
||||
("-moz-broken", MozBroken, IN_BROKEN_STATE, _),
|
||||
("-moz-loading", MozLoading, IN_LOADING_STATE, _),
|
||||
("-moz-has-dir-attr", MozHasDirAttr, IN_HAS_DIR_ATTR_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
|
||||
|
@ -92,6 +92,8 @@ macro_rules! apply_non_ts_list {
|
|||
("-moz-is-html", MozIsHTML, _, _),
|
||||
("-moz-placeholder", MozPlaceholder, _, _),
|
||||
("-moz-lwtheme", MozLWTheme, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME),
|
||||
("-moz-lwtheme-brighttext", MozLWThemeBrightText, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME),
|
||||
("-moz-lwtheme-darktext", MozLWThemeDarkText, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME),
|
||||
("-moz-window-inactive", MozWindowInactive, _, _),
|
||||
]
|
||||
}
|
||||
|
|
|
@ -139,6 +139,15 @@ impl NonTSPseudoClass {
|
|||
/// Returns whether the pseudo-class is enabled in content sheets.
|
||||
#[inline]
|
||||
fn is_enabled_in_content(&self) -> bool {
|
||||
if matches!(
|
||||
*self,
|
||||
Self::MozLWTheme | Self::MozLWThemeBrightText | Self::MozLWThemeDarkText
|
||||
) {
|
||||
return static_prefs::pref!("layout.css.moz-lwtheme.content.enabled");
|
||||
}
|
||||
if let NonTSPseudoClass::MozLocaleDir(..) = *self {
|
||||
return static_prefs::pref!("layout.css.moz-locale-dir.content.enabled");
|
||||
}
|
||||
!self.has_any_flag(NonTSPseudoClassFlag::PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME)
|
||||
}
|
||||
|
||||
|
@ -175,6 +184,8 @@ impl NonTSPseudoClass {
|
|||
},
|
||||
NonTSPseudoClass::MozWindowInactive => DocumentState::WINDOW_INACTIVE,
|
||||
NonTSPseudoClass::MozLWTheme => DocumentState::LWTHEME,
|
||||
NonTSPseudoClass::MozLWThemeBrightText => DocumentState::LWTHEME_BRIGHTTEXT,
|
||||
NonTSPseudoClass::MozLWThemeDarkText => DocumentState::LWTHEME_DARKTEXT,
|
||||
_ => DocumentState::empty(),
|
||||
}
|
||||
}
|
||||
|
@ -197,13 +208,15 @@ impl NonTSPseudoClass {
|
|||
NonTSPseudoClass::MozNativeAnonymous |
|
||||
// :-moz-placeholder is parsed but never matches.
|
||||
NonTSPseudoClass::MozPlaceholder |
|
||||
// :-moz-lwtheme, :-moz-locale-dir and
|
||||
// :-moz-window-inactive depend only on the state of the
|
||||
// document, which is invariant across all the elements
|
||||
// involved in a given style cache.
|
||||
NonTSPseudoClass::MozLWTheme |
|
||||
// :-moz-locale-dir and :-moz-window-inactive depend only on
|
||||
// the state of the document, which is invariant across all
|
||||
// the elements involved in a given style cache.
|
||||
NonTSPseudoClass::MozLocaleDir(_) |
|
||||
NonTSPseudoClass::MozWindowInactive
|
||||
NonTSPseudoClass::MozWindowInactive |
|
||||
// Similar for the document themes.
|
||||
NonTSPseudoClass::MozLWTheme |
|
||||
NonTSPseudoClass::MozLWThemeBrightText |
|
||||
NonTSPseudoClass::MozLWThemeDarkText
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -445,9 +458,3 @@ unsafe impl HasFFI for SelectorList<SelectorImpl> {
|
|||
}
|
||||
unsafe impl HasSimpleFFI for SelectorList<SelectorImpl> {}
|
||||
unsafe impl HasBoxFFI for SelectorList<SelectorImpl> {}
|
||||
|
||||
// Selector and component sizes are important for matching performance.
|
||||
size_of_test!(selectors::parser::Selector<SelectorImpl>, 8);
|
||||
size_of_test!(selectors::parser::Component<SelectorImpl>, 24);
|
||||
size_of_test!(PseudoElement, 16);
|
||||
size_of_test!(NonTSPseudoClass, 16);
|
||||
|
|
|
@ -44,14 +44,14 @@ unsafe fn get_class_or_part_from_attr(attr: &structs::nsAttrValue) -> Class {
|
|||
structs::nsAttrValue_ValueType_eAtomArray
|
||||
);
|
||||
// NOTE: Bindgen doesn't deal with AutoTArray, so cast it below.
|
||||
let attr_array: *mut _ = *(*container)
|
||||
let array: *mut u8 = *(*container)
|
||||
.__bindgen_anon_1
|
||||
.mValue
|
||||
.as_ref()
|
||||
.__bindgen_anon_1
|
||||
.mAtomArray
|
||||
.as_ref();
|
||||
let array = (*attr_array).mArray.as_ptr() as *const structs::nsTArray<structs::RefPtr<nsAtom>>;
|
||||
let array = array as *const structs::nsTArray<structs::RefPtr<nsAtom>>;
|
||||
return Class::More(&**array);
|
||||
}
|
||||
debug_assert_eq!(base_type, structs::nsAttrValue_ValueBaseType_eStringBase);
|
||||
|
|
|
@ -66,8 +66,6 @@ use crate::stylist::CascadeData;
|
|||
use crate::values::{AtomIdent, AtomString};
|
||||
use crate::CaseSensitivityExt;
|
||||
use crate::LocalName;
|
||||
use app_units::Au;
|
||||
use euclid::default::Size2D;
|
||||
use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
|
||||
use fxhash::FxHashMap;
|
||||
use selectors::attr::{AttrSelectorOperation, AttrSelectorOperator};
|
||||
|
@ -323,11 +321,6 @@ impl<'ln> GeckoNode<'ln> {
|
|||
self.flags() & (NODE_IS_IN_SHADOW_TREE as u32) != 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_connected(&self) -> bool {
|
||||
self.get_bool_flag(nsINode_BooleanFlag::IsConnected)
|
||||
}
|
||||
|
||||
/// WARNING: This logic is duplicated in Gecko's FlattenedTreeParentIsParent.
|
||||
/// Make sure to mirror any modifications in both places.
|
||||
#[inline]
|
||||
|
@ -1037,21 +1030,6 @@ impl<'le> TElement for GeckoElement<'le> {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn primary_box_size(&self) -> Size2D<Au> {
|
||||
if !self.as_node().is_connected() {
|
||||
return Size2D::zero();
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let frame = self.0._base._base._base.__bindgen_anon_1.mPrimaryFrame.as_ref();
|
||||
if frame.is_null() {
|
||||
return Size2D::zero();
|
||||
}
|
||||
Size2D::new(Au((**frame).mRect.width), Au((**frame).mRect.height))
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the list of slotted nodes of this node.
|
||||
#[inline]
|
||||
fn slotted_nodes(&self) -> &[Self::ConcreteNode] {
|
||||
|
@ -1415,6 +1393,16 @@ impl<'le> TElement for GeckoElement<'le> {
|
|||
self.is_root_of_native_anonymous_subtree()
|
||||
}
|
||||
|
||||
unsafe fn set_selector_flags(&self, flags: ElementSelectorFlags) {
|
||||
debug_assert!(!flags.is_empty());
|
||||
self.set_flags(selector_flags_to_node_flags(flags));
|
||||
}
|
||||
|
||||
fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool {
|
||||
let node_flags = selector_flags_to_node_flags(flags);
|
||||
(self.flags() & node_flags) == node_flags
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn may_have_animations(&self) -> bool {
|
||||
if let Some(pseudo) = self.implemented_pseudo_element() {
|
||||
|
@ -1513,7 +1501,7 @@ impl<'le> TElement for GeckoElement<'le> {
|
|||
) -> bool {
|
||||
use crate::properties::LonghandIdSet;
|
||||
|
||||
let after_change_ui_style = after_change_style.get_ui();
|
||||
let after_change_box_style = after_change_style.get_box();
|
||||
let existing_transitions = self.css_transitions_info();
|
||||
let mut transitions_to_keep = LonghandIdSet::new();
|
||||
for transition_property in after_change_style.transition_properties() {
|
||||
|
@ -1523,7 +1511,7 @@ impl<'le> TElement for GeckoElement<'le> {
|
|||
transitions_to_keep.insert(physical_longhand);
|
||||
if self.needs_transitions_update_per_property(
|
||||
physical_longhand,
|
||||
after_change_ui_style.transition_combined_duration_at(transition_property.index),
|
||||
after_change_box_style.transition_combined_duration_at(transition_property.index),
|
||||
before_change_style,
|
||||
after_change_style,
|
||||
&existing_transitions,
|
||||
|
@ -1605,9 +1593,23 @@ impl<'le> TElement for GeckoElement<'le> {
|
|||
use crate::properties::longhands::_x_lang::SpecifiedValue as SpecifiedLang;
|
||||
use crate::properties::longhands::_x_text_zoom::SpecifiedValue as SpecifiedZoom;
|
||||
use crate::properties::longhands::color::SpecifiedValue as SpecifiedColor;
|
||||
use crate::properties::longhands::text_align::SpecifiedValue as SpecifiedTextAlign;
|
||||
use crate::stylesheets::layer_rule::LayerOrder;
|
||||
use crate::values::specified::color::Color;
|
||||
lazy_static! {
|
||||
static ref TH_RULE: ApplicableDeclarationBlock = {
|
||||
let global_style_data = &*GLOBAL_STYLE_DATA;
|
||||
let pdb = PropertyDeclarationBlock::with_one(
|
||||
PropertyDeclaration::TextAlign(SpecifiedTextAlign::MozCenterOrInherit),
|
||||
Importance::Normal,
|
||||
);
|
||||
let arc = Arc::new_leaked(global_style_data.shared_lock.wrap(pdb));
|
||||
ApplicableDeclarationBlock::from_declarations(
|
||||
arc,
|
||||
ServoCascadeLevel::PresHints,
|
||||
LayerOrder::root(),
|
||||
)
|
||||
};
|
||||
static ref TABLE_COLOR_RULE: ApplicableDeclarationBlock = {
|
||||
let global_style_data = &*GLOBAL_STYLE_DATA;
|
||||
let pdb = PropertyDeclarationBlock::with_one(
|
||||
|
@ -1652,7 +1654,9 @@ impl<'le> TElement for GeckoElement<'le> {
|
|||
let ns = self.namespace_id();
|
||||
// <th> elements get a default MozCenterOrInherit which may get overridden
|
||||
if ns == structs::kNameSpaceID_XHTML as i32 {
|
||||
if self.local_name().as_ptr() == atom!("table").as_ptr() &&
|
||||
if self.local_name().as_ptr() == atom!("th").as_ptr() {
|
||||
hints.push(TH_RULE.clone());
|
||||
} else if self.local_name().as_ptr() == atom!("table").as_ptr() &&
|
||||
self.as_node().owner_doc().quirks_mode() == QuirksMode::Quirks
|
||||
{
|
||||
hints.push(TABLE_COLOR_RULE.clone());
|
||||
|
@ -1844,11 +1848,6 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
|
|||
None
|
||||
}
|
||||
|
||||
fn set_selector_flags(&self, flags: ElementSelectorFlags) {
|
||||
debug_assert!(!flags.is_empty());
|
||||
self.set_flags(selector_flags_to_node_flags(flags));
|
||||
}
|
||||
|
||||
fn attr_matches(
|
||||
&self,
|
||||
ns: &NamespaceConstraint<&Namespace>,
|
||||
|
@ -1961,11 +1960,15 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
|
|||
self.local_name() == other.local_name() && self.namespace() == other.namespace()
|
||||
}
|
||||
|
||||
fn match_non_ts_pseudo_class(
|
||||
fn match_non_ts_pseudo_class<F>(
|
||||
&self,
|
||||
pseudo_class: &NonTSPseudoClass,
|
||||
context: &mut MatchingContext<Self::Impl>,
|
||||
) -> bool {
|
||||
flags_setter: &mut F,
|
||||
) -> bool
|
||||
where
|
||||
F: FnMut(&Self, ElementSelectorFlags),
|
||||
{
|
||||
use selectors::matching::*;
|
||||
match *pseudo_class {
|
||||
NonTSPseudoClass::Autofill |
|
||||
|
@ -2006,7 +2009,7 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
|
|||
NonTSPseudoClass::MozDirAttrRTL |
|
||||
NonTSPseudoClass::MozDirAttrLikeAuto |
|
||||
NonTSPseudoClass::MozModalDialog |
|
||||
NonTSPseudoClass::MozTopmostModal |
|
||||
NonTSPseudoClass::MozTopmostModalDialog |
|
||||
NonTSPseudoClass::Active |
|
||||
NonTSPseudoClass::Hover |
|
||||
NonTSPseudoClass::MozAutofillPreview |
|
||||
|
@ -2021,9 +2024,7 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
|
|||
self.is_link() && context.visited_handling().matches_visited()
|
||||
},
|
||||
NonTSPseudoClass::MozFirstNode => {
|
||||
if context.needs_selector_flags() {
|
||||
self.apply_selector_flags(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR);
|
||||
}
|
||||
flags_setter(self, ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR);
|
||||
let mut elem = self.as_node();
|
||||
while let Some(prev) = elem.prev_sibling() {
|
||||
if prev.contains_non_whitespace_content() {
|
||||
|
@ -2034,9 +2035,7 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
|
|||
true
|
||||
},
|
||||
NonTSPseudoClass::MozLastNode => {
|
||||
if context.needs_selector_flags() {
|
||||
self.apply_selector_flags(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR);
|
||||
}
|
||||
flags_setter(self, ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR);
|
||||
let mut elem = self.as_node();
|
||||
while let Some(next) = elem.next_sibling() {
|
||||
if next.contains_non_whitespace_content() {
|
||||
|
@ -2047,9 +2046,7 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
|
|||
true
|
||||
},
|
||||
NonTSPseudoClass::MozOnlyWhitespace => {
|
||||
if context.needs_selector_flags() {
|
||||
self.apply_selector_flags(ElementSelectorFlags::HAS_EMPTY_SELECTOR);
|
||||
}
|
||||
flags_setter(self, ElementSelectorFlags::HAS_EMPTY_SELECTOR);
|
||||
if self
|
||||
.as_node()
|
||||
.dom_children()
|
||||
|
@ -2071,6 +2068,8 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
|
|||
NonTSPseudoClass::MozIsHTML => self.is_html_element_in_html_document(),
|
||||
|
||||
NonTSPseudoClass::MozLWTheme |
|
||||
NonTSPseudoClass::MozLWThemeBrightText |
|
||||
NonTSPseudoClass::MozLWThemeDarkText |
|
||||
NonTSPseudoClass::MozLocaleDir(..) |
|
||||
NonTSPseudoClass::MozWindowInactive => {
|
||||
let state_bit = pseudo_class.document_state_flag();
|
||||
|
|
|
@ -12,11 +12,11 @@ use crate::shared_lock::SharedRwLock;
|
|||
use crate::thread_state;
|
||||
#[cfg(feature = "gecko")]
|
||||
use gecko_profiler;
|
||||
use parking_lot::{Mutex, RwLock, RwLockReadGuard};
|
||||
use parking_lot::{RwLock, RwLockReadGuard};
|
||||
use rayon;
|
||||
use std::env;
|
||||
use std::io;
|
||||
use std::thread;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Mutex;
|
||||
|
||||
/// Global style data
|
||||
pub struct GlobalStyleData {
|
||||
|
@ -43,32 +43,12 @@ fn thread_name(index: usize) -> String {
|
|||
format!("Style#{}", index)
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
/// JoinHandles for spawned style threads. These will be joined during
|
||||
/// StyleThreadPool::shutdown() after exiting the thread pool.
|
||||
///
|
||||
/// This would be quite inefficient if rayon destroyed and re-created
|
||||
/// threads regularly during threadpool operation in response to demand,
|
||||
/// however rayon actually never destroys its threads until the entire
|
||||
/// thread pool is shut-down, so the size of this list is bounded.
|
||||
static ref STYLE_THREAD_JOIN_HANDLES: Mutex<Vec<thread::JoinHandle<()>>> =
|
||||
Mutex::new(Vec::new());
|
||||
}
|
||||
|
||||
fn thread_spawn(options: rayon::ThreadBuilder) -> io::Result<()> {
|
||||
let mut b = thread::Builder::new();
|
||||
if let Some(name) = options.name() {
|
||||
b = b.name(name.to_owned());
|
||||
}
|
||||
if let Some(stack_size) = options.stack_size() {
|
||||
b = b.stack_size(stack_size);
|
||||
}
|
||||
let join_handle = b.spawn(|| options.run())?;
|
||||
STYLE_THREAD_JOIN_HANDLES.lock().push(join_handle);
|
||||
Ok(())
|
||||
}
|
||||
// A counter so that we can wait for shutdown of all threads. See
|
||||
// StyleThreadPool::shutdown.
|
||||
static ALIVE_WORKER_THREADS: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
fn thread_startup(_index: usize) {
|
||||
ALIVE_WORKER_THREADS.fetch_add(1, Ordering::Relaxed);
|
||||
thread_state::initialize_layout_worker_thread();
|
||||
#[cfg(feature = "gecko")]
|
||||
unsafe {
|
||||
|
@ -84,24 +64,33 @@ fn thread_shutdown(_: usize) {
|
|||
gecko_profiler::unregister_thread();
|
||||
bindings::Gecko_SetJemallocThreadLocalArena(false);
|
||||
}
|
||||
ALIVE_WORKER_THREADS.fetch_sub(1, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
impl StyleThreadPool {
|
||||
/// Shuts down the thread pool, waiting for all work to complete.
|
||||
pub fn shutdown() {
|
||||
if STYLE_THREAD_JOIN_HANDLES.lock().is_empty() {
|
||||
if ALIVE_WORKER_THREADS.load(Ordering::Relaxed) == 0 {
|
||||
return;
|
||||
}
|
||||
{
|
||||
// Drop the pool.
|
||||
let _ = STYLE_THREAD_POOL.lock().unwrap().style_thread_pool.write().take();
|
||||
}
|
||||
|
||||
// Join spawned threads until all of the threads have been joined. This
|
||||
// will usually be pretty fast, as on shutdown there should be basically
|
||||
// no threads left running.
|
||||
while let Some(join_handle) = STYLE_THREAD_JOIN_HANDLES.lock().pop() {
|
||||
let _ = join_handle.join();
|
||||
// Spin until all our threads are done. This will usually be pretty
|
||||
// fast, as on shutdown there should be basically no threads left
|
||||
// running.
|
||||
//
|
||||
// This still _technically_ doesn't give us the guarantee of TLS
|
||||
// destructors running on the worker threads. For that we'd need help
|
||||
// from rayon to properly join the threads.
|
||||
//
|
||||
// See https://github.com/rayon-rs/rayon/issues/688
|
||||
//
|
||||
// So we instead intentionally leak TLS stuff (see BLOOM_KEY and co) for
|
||||
// now until that's fixed.
|
||||
while ALIVE_WORKER_THREADS.load(Ordering::Relaxed) != 0 {
|
||||
std::thread::yield_now();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -116,7 +105,7 @@ impl StyleThreadPool {
|
|||
|
||||
lazy_static! {
|
||||
/// Global thread pool
|
||||
pub static ref STYLE_THREAD_POOL: std::sync::Mutex<StyleThreadPool> = {
|
||||
pub static ref STYLE_THREAD_POOL: Mutex<StyleThreadPool> = {
|
||||
let stylo_threads = env::var("STYLO_THREADS")
|
||||
.map(|s| s.parse::<usize>().expect("invalid STYLO_THREADS value"));
|
||||
let mut num_threads = match stylo_threads {
|
||||
|
@ -160,7 +149,6 @@ lazy_static! {
|
|||
None
|
||||
} else {
|
||||
let workers = rayon::ThreadPoolBuilder::new()
|
||||
.spawn_handler(thread_spawn)
|
||||
.num_threads(num_threads)
|
||||
.thread_name(thread_name)
|
||||
.start_handler(thread_startup)
|
||||
|
@ -170,7 +158,7 @@ lazy_static! {
|
|||
workers.ok()
|
||||
};
|
||||
|
||||
std::sync::Mutex::new(StyleThreadPool {
|
||||
Mutex::new(StyleThreadPool {
|
||||
num_threads: if num_threads > 0 {
|
||||
Some(num_threads)
|
||||
} else {
|
||||
|
|
|
@ -11,7 +11,7 @@ use crate::invalidation::element::invalidator::{DescendantInvalidationLists, Inv
|
|||
use crate::invalidation::element::invalidator::{Invalidation, InvalidationProcessor};
|
||||
use crate::invalidation::element::state_and_attributes;
|
||||
use crate::stylist::CascadeData;
|
||||
use selectors::matching::{MatchingContext, MatchingMode, QuirksMode, VisitedHandlingMode, NeedsSelectorFlags};
|
||||
use selectors::matching::{MatchingContext, MatchingMode, QuirksMode, VisitedHandlingMode};
|
||||
|
||||
/// A struct holding the members necessary to invalidate document state
|
||||
/// selectors.
|
||||
|
@ -47,7 +47,6 @@ impl<'a, E: TElement, I> DocumentStateInvalidationProcessor<'a, E, I> {
|
|||
None,
|
||||
VisitedHandlingMode::AllLinksVisitedAndUnvisited,
|
||||
quirks_mode,
|
||||
NeedsSelectorFlags::No,
|
||||
);
|
||||
|
||||
matching_context.extra_data = InvalidationMatchingData {
|
||||
|
|
|
@ -166,11 +166,15 @@ where
|
|||
{
|
||||
type Impl = SelectorImpl;
|
||||
|
||||
fn match_non_ts_pseudo_class(
|
||||
fn match_non_ts_pseudo_class<F>(
|
||||
&self,
|
||||
pseudo_class: &NonTSPseudoClass,
|
||||
context: &mut MatchingContext<Self::Impl>,
|
||||
) -> bool {
|
||||
_setter: &mut F,
|
||||
) -> bool
|
||||
where
|
||||
F: FnMut(&Self, ElementSelectorFlags),
|
||||
{
|
||||
// Some pseudo-classes need special handling to evaluate them against
|
||||
// the snapshot.
|
||||
match *pseudo_class {
|
||||
|
@ -228,20 +232,16 @@ where
|
|||
if flag.is_empty() {
|
||||
return self
|
||||
.element
|
||||
.match_non_ts_pseudo_class(pseudo_class, context);
|
||||
.match_non_ts_pseudo_class(pseudo_class, context, &mut |_, _| {});
|
||||
}
|
||||
match self.snapshot().and_then(|s| s.state()) {
|
||||
Some(snapshot_state) => snapshot_state.intersects(flag),
|
||||
None => self
|
||||
.element
|
||||
.match_non_ts_pseudo_class(pseudo_class, context),
|
||||
.match_non_ts_pseudo_class(pseudo_class, context, &mut |_, _| {}),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_selector_flags(&self, _flags: ElementSelectorFlags) {
|
||||
debug_assert!(false, "Shouldn't need selector flags for invalidation");
|
||||
}
|
||||
|
||||
fn match_pseudo_element(
|
||||
&self,
|
||||
pseudo_element: &PseudoElement,
|
||||
|
|
|
@ -67,8 +67,6 @@ pub struct Dependency {
|
|||
pub parent: Option<Box<Dependency>>,
|
||||
}
|
||||
|
||||
size_of_test!(Dependency, 24);
|
||||
|
||||
/// The kind of elements down the tree this dependency may affect.
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub enum DependencyInvalidationKind {
|
||||
|
|
|
@ -19,7 +19,8 @@ use crate::selector_parser::Snapshot;
|
|||
use crate::stylesheets::origin::OriginSet;
|
||||
use crate::{Atom, WeakAtom};
|
||||
use selectors::attr::CaseSensitivity;
|
||||
use selectors::matching::{matches_selector, MatchingContext, MatchingMode, VisitedHandlingMode, NeedsSelectorFlags};
|
||||
use selectors::matching::matches_selector;
|
||||
use selectors::matching::{MatchingContext, MatchingMode, VisitedHandlingMode};
|
||||
use selectors::NthIndexCache;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
|
@ -66,7 +67,6 @@ impl<'a, 'b: 'a, E: TElement + 'b> StateAndAttrInvalidationProcessor<'a, 'b, E>
|
|||
Some(nth_index_cache),
|
||||
VisitedHandlingMode::AllLinksVisitedAndUnvisited,
|
||||
shared_context.quirks_mode(),
|
||||
NeedsSelectorFlags::No,
|
||||
);
|
||||
|
||||
Self {
|
||||
|
@ -84,7 +84,7 @@ pub fn check_dependency<E, W>(
|
|||
dependency: &Dependency,
|
||||
element: &E,
|
||||
wrapper: &W,
|
||||
context: &mut MatchingContext<'_, E::Impl>,
|
||||
mut context: &mut MatchingContext<'_, E::Impl>,
|
||||
) -> bool
|
||||
where
|
||||
E: TElement,
|
||||
|
@ -95,7 +95,8 @@ where
|
|||
dependency.selector_offset,
|
||||
None,
|
||||
element,
|
||||
context,
|
||||
&mut context,
|
||||
&mut |_, _| {},
|
||||
);
|
||||
|
||||
let matched_then = matches_selector(
|
||||
|
@ -103,7 +104,8 @@ where
|
|||
dependency.selector_offset,
|
||||
None,
|
||||
wrapper,
|
||||
context,
|
||||
&mut context,
|
||||
&mut |_, _| {},
|
||||
);
|
||||
|
||||
matched_then != matches_now
|
||||
|
|
|
@ -556,7 +556,6 @@ impl StylesheetInvalidationSet {
|
|||
FontFace(..) |
|
||||
Keyframes(..) |
|
||||
ScrollTimeline(..) |
|
||||
Container(..) |
|
||||
Style(..) => {
|
||||
if is_generic_change {
|
||||
// TODO(emilio): We need to do this for selector / keyframe
|
||||
|
@ -611,7 +610,7 @@ impl StylesheetInvalidationSet {
|
|||
}
|
||||
},
|
||||
Document(..) | Namespace(..) | Import(..) | Media(..) | Supports(..) |
|
||||
Container(..) | LayerStatement(..) | LayerBlock(..) => {
|
||||
LayerStatement(..) | LayerBlock(..) => {
|
||||
// Do nothing, relevant nested rules are visited as part of the
|
||||
// iteration.
|
||||
},
|
||||
|
|
|
@ -63,8 +63,6 @@ pub use servo_arc;
|
|||
#[macro_use]
|
||||
extern crate servo_atoms;
|
||||
#[macro_use]
|
||||
extern crate static_assertions;
|
||||
#[macro_use]
|
||||
extern crate style_derive;
|
||||
#[macro_use]
|
||||
extern crate to_shmem_derive;
|
||||
|
@ -103,12 +101,10 @@ pub mod invalidation;
|
|||
#[allow(missing_docs)] // TODO.
|
||||
pub mod logical_geometry;
|
||||
pub mod matching;
|
||||
#[macro_use]
|
||||
pub mod media_queries;
|
||||
pub mod parallel;
|
||||
pub mod parser;
|
||||
pub mod piecewise_linear;
|
||||
#[macro_use]
|
||||
pub mod queries;
|
||||
pub mod rule_cache;
|
||||
pub mod rule_collector;
|
||||
pub mod rule_tree;
|
||||
|
@ -188,7 +184,7 @@ pub mod gecko_properties {
|
|||
}
|
||||
|
||||
macro_rules! reexport_computed_values {
|
||||
( $( { $name: ident } )+ ) => {
|
||||
( $( { $name: ident, $boxed: expr } )+ ) => {
|
||||
/// Types for [computed values][computed].
|
||||
///
|
||||
/// [computed]: https://drafts.csswg.org/css-cascade/#computed
|
||||
|
@ -202,6 +198,7 @@ macro_rules! reexport_computed_values {
|
|||
}
|
||||
}
|
||||
longhand_properties_idents!(reexport_computed_values);
|
||||
|
||||
#[cfg(feature = "gecko")]
|
||||
use crate::gecko_string_cache::WeakAtom;
|
||||
#[cfg(feature = "servo")]
|
||||
|
|
|
@ -168,11 +168,6 @@ impl WritingMode {
|
|||
flags
|
||||
}
|
||||
|
||||
/// Returns the `horizontal-tb` value.
|
||||
pub fn horizontal_tb() -> Self {
|
||||
Self::from_bits_truncate(0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_vertical(&self) -> bool {
|
||||
self.intersects(WritingMode::VERTICAL)
|
||||
|
@ -877,10 +872,10 @@ impl<T> LogicalMargin<T> {
|
|||
inline_start: T,
|
||||
) -> LogicalMargin<T> {
|
||||
LogicalMargin {
|
||||
block_start,
|
||||
inline_end,
|
||||
block_end,
|
||||
inline_start,
|
||||
block_start: block_start,
|
||||
inline_end: inline_end,
|
||||
block_end: block_end,
|
||||
inline_start: inline_start,
|
||||
debug_writing_mode: DebugWritingMode::new(mode),
|
||||
}
|
||||
}
|
||||
|
@ -1055,18 +1050,6 @@ impl<T: Copy> LogicalMargin<T> {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn convert(&self, mode_from: WritingMode, mode_to: WritingMode) -> LogicalMargin<T> {
|
||||
if mode_from == mode_to {
|
||||
self.debug_writing_mode.check(mode_from);
|
||||
*self
|
||||
} else {
|
||||
LogicalMargin::from_physical(mode_to, self.to_physical(mode_from))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> LogicalMargin<T> {
|
||||
#[inline]
|
||||
pub fn to_physical(&self, mode: WritingMode) -> SideOffsets2D<T> {
|
||||
self.debug_writing_mode.check(mode);
|
||||
|
@ -1076,32 +1059,42 @@ impl<T: Clone> LogicalMargin<T> {
|
|||
let left;
|
||||
if mode.is_vertical() {
|
||||
if mode.is_vertical_lr() {
|
||||
left = self.block_start.clone();
|
||||
right = self.block_end.clone();
|
||||
left = self.block_start;
|
||||
right = self.block_end;
|
||||
} else {
|
||||
right = self.block_start.clone();
|
||||
left = self.block_end.clone();
|
||||
right = self.block_start;
|
||||
left = self.block_end;
|
||||
}
|
||||
if mode.is_inline_tb() {
|
||||
top = self.inline_start.clone();
|
||||
bottom = self.inline_end.clone();
|
||||
top = self.inline_start;
|
||||
bottom = self.inline_end;
|
||||
} else {
|
||||
bottom = self.inline_start.clone();
|
||||
top = self.inline_end.clone();
|
||||
bottom = self.inline_start;
|
||||
top = self.inline_end;
|
||||
}
|
||||
} else {
|
||||
top = self.block_start.clone();
|
||||
bottom = self.block_end.clone();
|
||||
top = self.block_start;
|
||||
bottom = self.block_end;
|
||||
if mode.is_bidi_ltr() {
|
||||
left = self.inline_start.clone();
|
||||
right = self.inline_end.clone();
|
||||
left = self.inline_start;
|
||||
right = self.inline_end;
|
||||
} else {
|
||||
right = self.inline_start.clone();
|
||||
left = self.inline_end.clone();
|
||||
right = self.inline_start;
|
||||
left = self.inline_end;
|
||||
}
|
||||
}
|
||||
SideOffsets2D::new(top, right, bottom, left)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn convert(&self, mode_from: WritingMode, mode_to: WritingMode) -> LogicalMargin<T> {
|
||||
if mode_from == mode_to {
|
||||
self.debug_writing_mode.check(mode_from);
|
||||
*self
|
||||
} else {
|
||||
LogicalMargin::from_physical(mode_to, self.to_physical(mode_from))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PartialEq + Zero> LogicalMargin<T> {
|
||||
|
|
|
@ -128,11 +128,3 @@ macro_rules! local_name {
|
|||
$crate::values::AtomIdent(atom!($s))
|
||||
};
|
||||
}
|
||||
|
||||
/// Asserts the size of a type at compile time.
|
||||
macro_rules! size_of_test {
|
||||
($t: ty, $expected_size: expr) => {
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
const_assert_eq!(std::mem::size_of::<$t>(), $expected_size);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
#![deny(missing_docs)]
|
||||
|
||||
use crate::computed_value_flags::ComputedValueFlags;
|
||||
use crate::context::{CascadeInputs, ElementCascadeInputs, QuirksMode};
|
||||
use crate::context::{CascadeInputs, ElementCascadeInputs, QuirksMode, SelectorFlagsMap};
|
||||
use crate::context::{SharedStyleContext, StyleContext};
|
||||
use crate::data::{ElementData, ElementStyles};
|
||||
use crate::dom::TElement;
|
||||
|
@ -26,6 +26,7 @@ use crate::style_resolver::{PseudoElementResolution, StyleResolverForElement};
|
|||
use crate::stylesheets::layer_rule::LayerOrder;
|
||||
use crate::stylist::RuleInclusion;
|
||||
use crate::traversal_flags::TraversalFlags;
|
||||
use selectors::matching::ElementSelectorFlags;
|
||||
use servo_arc::{Arc, ArcBorrow};
|
||||
|
||||
/// Represents the result of comparing an element's old and new style.
|
||||
|
@ -254,8 +255,8 @@ trait PrivateMatchMethods: TElement {
|
|||
new_style: &ComputedValues,
|
||||
pseudo_element: Option<PseudoElement>,
|
||||
) -> bool {
|
||||
let new_ui_style = new_style.get_ui();
|
||||
let new_style_specifies_animations = new_ui_style.specifies_animations();
|
||||
let new_box_style = new_style.get_box();
|
||||
let new_style_specifies_animations = new_box_style.specifies_animations();
|
||||
|
||||
let has_animations = self.has_css_animations(&context.shared, pseudo_element);
|
||||
if !new_style_specifies_animations && !has_animations {
|
||||
|
@ -282,7 +283,7 @@ trait PrivateMatchMethods: TElement {
|
|||
},
|
||||
};
|
||||
|
||||
let old_ui_style = old_style.get_ui();
|
||||
let old_box_style = old_style.get_box();
|
||||
|
||||
let keyframes_or_timeline_could_have_changed = context
|
||||
.shared
|
||||
|
@ -301,12 +302,12 @@ trait PrivateMatchMethods: TElement {
|
|||
}
|
||||
|
||||
// If the animations changed, well...
|
||||
if !old_ui_style.animations_equals(new_ui_style) {
|
||||
if !old_box_style.animations_equals(new_box_style) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let old_display = old_style.clone_display();
|
||||
let new_display = new_style.clone_display();
|
||||
let old_display = old_box_style.clone_display();
|
||||
let new_display = new_box_style.clone_display();
|
||||
|
||||
// If we were display: none, we may need to trigger animations.
|
||||
if old_display == Display::None && new_display != Display::None {
|
||||
|
@ -341,13 +342,14 @@ trait PrivateMatchMethods: TElement {
|
|||
None => return false,
|
||||
};
|
||||
|
||||
let new_box_style = new_style.get_box();
|
||||
if !self.has_css_transitions(context.shared, pseudo_element) &&
|
||||
!new_style.get_ui().specifies_transitions()
|
||||
!new_box_style.specifies_transitions()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if new_style.clone_display().is_none() || old_style.clone_display().is_none() {
|
||||
if new_box_style.clone_display().is_none() || old_style.clone_display().is_none() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -764,8 +766,8 @@ trait PrivateMatchMethods: TElement {
|
|||
},
|
||||
}
|
||||
|
||||
let old_display = old_values.clone_display();
|
||||
let new_display = new_values.clone_display();
|
||||
let old_display = old_values.get_box().clone_display();
|
||||
let new_display = new_values.get_box().clone_display();
|
||||
|
||||
if old_display != new_display {
|
||||
// If we used to be a display: none element, and no longer are, our
|
||||
|
@ -1005,6 +1007,51 @@ pub trait MatchMethods: TElement {
|
|||
cascade_requirement
|
||||
}
|
||||
|
||||
/// Applies selector flags to an element, deferring mutations of the parent
|
||||
/// until after the traversal.
|
||||
///
|
||||
/// TODO(emilio): This is somewhat inefficient, because it doesn't take
|
||||
/// advantage of us knowing that the traversal is sequential.
|
||||
fn apply_selector_flags(
|
||||
&self,
|
||||
map: &mut SelectorFlagsMap<Self>,
|
||||
element: &Self,
|
||||
flags: ElementSelectorFlags,
|
||||
) {
|
||||
// Handle flags that apply to the element.
|
||||
let self_flags = flags.for_self();
|
||||
if !self_flags.is_empty() {
|
||||
if element == self {
|
||||
// If this is the element we're styling, we have exclusive
|
||||
// access to the element, and thus it's fine inserting them,
|
||||
// even from the worker.
|
||||
unsafe {
|
||||
element.set_selector_flags(self_flags);
|
||||
}
|
||||
} else {
|
||||
// Otherwise, this element is an ancestor of the current element
|
||||
// we're styling, and thus multiple children could write to it
|
||||
// if we did from here.
|
||||
//
|
||||
// Instead, we can read them, and post them if necessary as a
|
||||
// sequential task in order for them to be processed later.
|
||||
if !element.has_selector_flags(self_flags) {
|
||||
map.insert_flags(*element, self_flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle flags that apply to the parent.
|
||||
let parent_flags = flags.for_parent();
|
||||
if !parent_flags.is_empty() {
|
||||
if let Some(p) = element.parent_element() {
|
||||
if !p.has_selector_flags(parent_flags) {
|
||||
map.insert_flags(p, parent_flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the rule nodes without re-running selector matching, using just
|
||||
/// the rule tree.
|
||||
///
|
||||
|
|
|
@ -2,14 +2,13 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
//! A query condition:
|
||||
//! A media query condition:
|
||||
//!
|
||||
//! https://drafts.csswg.org/mediaqueries-4/#typedef-media-condition
|
||||
//! https://drafts.csswg.org/css-contain-3/#typedef-container-condition
|
||||
|
||||
use super::{QueryFeatureExpression, FeatureType, FeatureFlags};
|
||||
use super::{Device, MediaFeatureExpression};
|
||||
use crate::context::QuirksMode;
|
||||
use crate::parser::ParserContext;
|
||||
use crate::values::computed;
|
||||
use cssparser::{Parser, Token};
|
||||
use std::fmt::{self, Write};
|
||||
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
|
||||
|
@ -29,38 +28,38 @@ enum AllowOr {
|
|||
No,
|
||||
}
|
||||
|
||||
/// Represents a condition.
|
||||
/// Represents a media condition.
|
||||
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
|
||||
pub enum QueryCondition {
|
||||
/// A simple feature expression, implicitly parenthesized.
|
||||
Feature(QueryFeatureExpression),
|
||||
pub enum MediaCondition {
|
||||
/// A simple media feature expression, implicitly parenthesized.
|
||||
Feature(MediaFeatureExpression),
|
||||
/// A negation of a condition.
|
||||
Not(Box<QueryCondition>),
|
||||
Not(Box<MediaCondition>),
|
||||
/// A set of joint operations.
|
||||
Operation(Box<[QueryCondition]>, Operator),
|
||||
Operation(Box<[MediaCondition]>, Operator),
|
||||
/// A condition wrapped in parenthesis.
|
||||
InParens(Box<QueryCondition>),
|
||||
InParens(Box<MediaCondition>),
|
||||
}
|
||||
|
||||
impl ToCss for QueryCondition {
|
||||
impl ToCss for MediaCondition {
|
||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||
where
|
||||
W: fmt::Write,
|
||||
{
|
||||
match *self {
|
||||
// NOTE(emilio): QueryFeatureExpression already includes the
|
||||
// NOTE(emilio): MediaFeatureExpression already includes the
|
||||
// parenthesis.
|
||||
QueryCondition::Feature(ref f) => f.to_css(dest),
|
||||
QueryCondition::Not(ref c) => {
|
||||
MediaCondition::Feature(ref f) => f.to_css(dest),
|
||||
MediaCondition::Not(ref c) => {
|
||||
dest.write_str("not ")?;
|
||||
c.to_css(dest)
|
||||
},
|
||||
QueryCondition::InParens(ref c) => {
|
||||
MediaCondition::InParens(ref c) => {
|
||||
dest.write_char('(')?;
|
||||
c.to_css(dest)?;
|
||||
dest.write_char(')')
|
||||
},
|
||||
QueryCondition::Operation(ref list, op) => {
|
||||
MediaCondition::Operation(ref list, op) => {
|
||||
let mut iter = list.iter();
|
||||
iter.next().unwrap().to_css(dest)?;
|
||||
for item in iter {
|
||||
|
@ -75,60 +74,28 @@ impl ToCss for QueryCondition {
|
|||
}
|
||||
}
|
||||
|
||||
impl QueryCondition {
|
||||
/// Parse a single condition.
|
||||
impl MediaCondition {
|
||||
/// Parse a single media condition.
|
||||
pub fn parse<'i, 't>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
feature_type: FeatureType,
|
||||
) -> Result<Self, ParseError<'i>> {
|
||||
Self::parse_internal(context, input, feature_type, AllowOr::Yes)
|
||||
Self::parse_internal(context, input, AllowOr::Yes)
|
||||
}
|
||||
|
||||
fn visit<F>(&self, visitor: &mut F)
|
||||
where
|
||||
F: FnMut(&Self),
|
||||
{
|
||||
visitor(self);
|
||||
match *self {
|
||||
Self::Feature(..) => {},
|
||||
Self::Not(ref cond) => cond.visit(visitor),
|
||||
Self::Operation(ref conds, _op) => {
|
||||
for cond in conds.iter() {
|
||||
cond.visit(visitor);
|
||||
}
|
||||
},
|
||||
Self::InParens(ref cond) => cond.visit(visitor),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the union of all flags in the expression. This is useful for
|
||||
/// container queries.
|
||||
pub fn cumulative_flags(&self) -> FeatureFlags {
|
||||
let mut result = FeatureFlags::empty();
|
||||
self.visit(&mut |condition| {
|
||||
if let Self::Feature(ref f) = condition {
|
||||
result.insert(f.feature_flags())
|
||||
}
|
||||
});
|
||||
result
|
||||
}
|
||||
|
||||
/// Parse a single condition, disallowing `or` expressions.
|
||||
/// Parse a single media condition, disallowing `or` expressions.
|
||||
///
|
||||
/// To be used from the legacy query syntax.
|
||||
/// To be used from the legacy media query syntax.
|
||||
pub fn parse_disallow_or<'i, 't>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
feature_type: FeatureType,
|
||||
) -> Result<Self, ParseError<'i>> {
|
||||
Self::parse_internal(context, input, feature_type, AllowOr::No)
|
||||
Self::parse_internal(context, input, AllowOr::No)
|
||||
}
|
||||
|
||||
fn parse_internal<'i, 't>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
feature_type: FeatureType,
|
||||
allow_or: AllowOr,
|
||||
) -> Result<Self, ParseError<'i>> {
|
||||
let location = input.current_source_location();
|
||||
|
@ -141,12 +108,12 @@ impl QueryCondition {
|
|||
};
|
||||
|
||||
if is_negation {
|
||||
let inner_condition = Self::parse_in_parens(context, input, feature_type)?;
|
||||
return Ok(QueryCondition::Not(Box::new(inner_condition)));
|
||||
let inner_condition = Self::parse_in_parens(context, input)?;
|
||||
return Ok(MediaCondition::Not(Box::new(inner_condition)));
|
||||
}
|
||||
|
||||
// ParenthesisBlock.
|
||||
let first_condition = Self::parse_paren_block(context, input, feature_type)?;
|
||||
let first_condition = Self::parse_paren_block(context, input)?;
|
||||
let operator = match input.try_parse(Operator::parse) {
|
||||
Ok(op) => op,
|
||||
Err(..) => return Ok(first_condition),
|
||||
|
@ -158,7 +125,7 @@ impl QueryCondition {
|
|||
|
||||
let mut conditions = vec![];
|
||||
conditions.push(first_condition);
|
||||
conditions.push(Self::parse_in_parens(context, input, feature_type)?);
|
||||
conditions.push(Self::parse_in_parens(context, input)?);
|
||||
|
||||
let delim = match operator {
|
||||
Operator::And => "and",
|
||||
|
@ -167,52 +134,50 @@ impl QueryCondition {
|
|||
|
||||
loop {
|
||||
if input.try_parse(|i| i.expect_ident_matching(delim)).is_err() {
|
||||
return Ok(QueryCondition::Operation(
|
||||
return Ok(MediaCondition::Operation(
|
||||
conditions.into_boxed_slice(),
|
||||
operator,
|
||||
));
|
||||
}
|
||||
|
||||
conditions.push(Self::parse_in_parens(context, input, feature_type)?);
|
||||
conditions.push(Self::parse_in_parens(context, input)?);
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a condition in parentheses.
|
||||
/// Parse a media condition in parentheses.
|
||||
pub fn parse_in_parens<'i, 't>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
feature_type: FeatureType,
|
||||
) -> Result<Self, ParseError<'i>> {
|
||||
input.expect_parenthesis_block()?;
|
||||
Self::parse_paren_block(context, input, feature_type)
|
||||
Self::parse_paren_block(context, input)
|
||||
}
|
||||
|
||||
fn parse_paren_block<'i, 't>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
feature_type: FeatureType,
|
||||
) -> Result<Self, ParseError<'i>> {
|
||||
input.parse_nested_block(|input| {
|
||||
// Base case.
|
||||
if let Ok(inner) = input.try_parse(|i| Self::parse(context, i, feature_type)) {
|
||||
return Ok(QueryCondition::InParens(Box::new(inner)));
|
||||
if let Ok(inner) = input.try_parse(|i| Self::parse(context, i)) {
|
||||
return Ok(MediaCondition::InParens(Box::new(inner)));
|
||||
}
|
||||
let expr = QueryFeatureExpression::parse_in_parenthesis_block(context, input, feature_type)?;
|
||||
Ok(QueryCondition::Feature(expr))
|
||||
let expr = MediaFeatureExpression::parse_in_parenthesis_block(context, input)?;
|
||||
Ok(MediaCondition::Feature(expr))
|
||||
})
|
||||
}
|
||||
|
||||
/// Whether this condition matches the device and quirks mode.
|
||||
pub fn matches(&self, context: &computed::Context) -> bool {
|
||||
pub fn matches(&self, device: &Device, quirks_mode: QuirksMode) -> bool {
|
||||
match *self {
|
||||
QueryCondition::Feature(ref f) => f.matches(context),
|
||||
QueryCondition::InParens(ref c) => c.matches(context),
|
||||
QueryCondition::Not(ref c) => !c.matches(context),
|
||||
QueryCondition::Operation(ref conditions, op) => {
|
||||
MediaCondition::Feature(ref f) => f.matches(device, quirks_mode),
|
||||
MediaCondition::InParens(ref c) => c.matches(device, quirks_mode),
|
||||
MediaCondition::Not(ref c) => !c.matches(device, quirks_mode),
|
||||
MediaCondition::Operation(ref conditions, op) => {
|
||||
let mut iter = conditions.iter();
|
||||
match op {
|
||||
Operator::And => iter.all(|c| c.matches(context)),
|
||||
Operator::Or => iter.any(|c| c.matches(context)),
|
||||
Operator::And => iter.all(|c| c.matches(device, quirks_mode)),
|
||||
Operator::Or => iter.any(|c| c.matches(device, quirks_mode)),
|
||||
}
|
||||
},
|
||||
}
|
|
@ -2,10 +2,13 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
//! Query features.
|
||||
//! Media features.
|
||||
|
||||
use super::media_feature_expression::RangeOrOperator;
|
||||
use super::Device;
|
||||
use crate::parser::ParserContext;
|
||||
use crate::values::computed::{self, CSSPixelLength, Resolution, Ratio};
|
||||
use crate::values::computed::Ratio;
|
||||
use crate::values::computed::{CSSPixelLength, Resolution};
|
||||
use crate::Atom;
|
||||
use cssparser::Parser;
|
||||
use std::fmt;
|
||||
|
@ -14,7 +17,12 @@ use style_traits::ParseError;
|
|||
/// A generic discriminant for an enum value.
|
||||
pub type KeywordDiscriminant = u8;
|
||||
|
||||
type QueryFeatureGetter<T> = fn(device: &computed::Context) -> T;
|
||||
type MediaFeatureEvaluator<T> = fn(
|
||||
device: &Device,
|
||||
// null == no value was given in the query.
|
||||
value: Option<T>,
|
||||
range_or_operator: Option<RangeOrOperator>,
|
||||
) -> bool;
|
||||
|
||||
/// Serializes a given discriminant.
|
||||
///
|
||||
|
@ -28,19 +36,19 @@ pub type KeywordParser = for<'a, 'i, 't> fn(
|
|||
input: &'a mut Parser<'i, 't>,
|
||||
) -> Result<KeywordDiscriminant, ParseError<'i>>;
|
||||
|
||||
/// An evaluator for a given feature.
|
||||
/// An evaluator for a given media feature.
|
||||
///
|
||||
/// This determines the kind of values that get parsed, too.
|
||||
#[allow(missing_docs)]
|
||||
pub enum Evaluator {
|
||||
Length(QueryFeatureGetter<CSSPixelLength>),
|
||||
Integer(QueryFeatureGetter<u32>),
|
||||
Float(QueryFeatureGetter<f32>),
|
||||
BoolInteger(QueryFeatureGetter<bool>),
|
||||
Length(MediaFeatureEvaluator<CSSPixelLength>),
|
||||
Integer(MediaFeatureEvaluator<u32>),
|
||||
Float(MediaFeatureEvaluator<f32>),
|
||||
BoolInteger(MediaFeatureEvaluator<bool>),
|
||||
/// A non-negative number ratio, such as the one from device-pixel-ratio.
|
||||
NumberRatio(QueryFeatureGetter<Ratio>),
|
||||
NumberRatio(MediaFeatureEvaluator<Ratio>),
|
||||
/// A resolution.
|
||||
Resolution(QueryFeatureGetter<Resolution>),
|
||||
Resolution(MediaFeatureEvaluator<Resolution>),
|
||||
/// A keyword value.
|
||||
Enumerated {
|
||||
/// The parser to get a discriminant given a string.
|
||||
|
@ -52,8 +60,9 @@ pub enum Evaluator {
|
|||
serializer: KeywordSerializer,
|
||||
/// The evaluator itself. This is guaranteed to be called with a
|
||||
/// keyword that `parser` has produced.
|
||||
evaluator: fn(&computed::Context, Option<KeywordDiscriminant>) -> bool,
|
||||
evaluator: MediaFeatureEvaluator<KeywordDiscriminant>,
|
||||
},
|
||||
Ident(MediaFeatureEvaluator<Atom>),
|
||||
}
|
||||
|
||||
/// A simple helper macro to create a keyword evaluator.
|
||||
|
@ -67,14 +76,14 @@ macro_rules! keyword_evaluator {
|
|||
context: &$crate::parser::ParserContext,
|
||||
input: &mut $crate::cssparser::Parser<'i, 't>,
|
||||
) -> Result<
|
||||
$crate::queries::feature::KeywordDiscriminant,
|
||||
$crate::media_queries::media_feature::KeywordDiscriminant,
|
||||
::style_traits::ParseError<'i>,
|
||||
> {
|
||||
let kw = <$keyword_type as $crate::parser::Parse>::parse(context, input)?;
|
||||
Ok(kw as $crate::queries::feature::KeywordDiscriminant)
|
||||
Ok(kw as $crate::media_queries::media_feature::KeywordDiscriminant)
|
||||
}
|
||||
|
||||
fn __serialize(kw: $crate::queries::feature::KeywordDiscriminant) -> String {
|
||||
fn __serialize(kw: $crate::media_queries::media_feature::KeywordDiscriminant) -> String {
|
||||
// This unwrap is ok because the only discriminants that get
|
||||
// back to us is the ones that `parse` produces.
|
||||
let value: $keyword_type = ::num_traits::cast::FromPrimitive::from_u8(kw).unwrap();
|
||||
|
@ -82,17 +91,24 @@ macro_rules! keyword_evaluator {
|
|||
}
|
||||
|
||||
fn __evaluate(
|
||||
context: &$crate::values::computed::Context,
|
||||
value: Option<$crate::queries::feature::KeywordDiscriminant>,
|
||||
device: &$crate::media_queries::Device,
|
||||
value: Option<$crate::media_queries::media_feature::KeywordDiscriminant>,
|
||||
range_or_operator: Option<
|
||||
$crate::media_queries::media_feature_expression::RangeOrOperator,
|
||||
>,
|
||||
) -> bool {
|
||||
debug_assert!(
|
||||
range_or_operator.is_none(),
|
||||
"Since when do keywords accept ranges?"
|
||||
);
|
||||
// This unwrap is ok because the only discriminants that get
|
||||
// back to us is the ones that `parse` produces.
|
||||
let value: Option<$keyword_type> =
|
||||
value.map(|kw| ::num_traits::cast::FromPrimitive::from_u8(kw).unwrap());
|
||||
$actual_evaluator(context, value)
|
||||
$actual_evaluator(device, value)
|
||||
}
|
||||
|
||||
$crate::queries::feature::Evaluator::Enumerated {
|
||||
$crate::media_queries::media_feature::Evaluator::Enumerated {
|
||||
parser: __parse,
|
||||
serializer: __serialize,
|
||||
evaluator: __evaluate,
|
||||
|
@ -101,46 +117,17 @@ macro_rules! keyword_evaluator {
|
|||
}
|
||||
|
||||
bitflags! {
|
||||
/// Different flags or toggles that change how a expression is parsed or
|
||||
/// evaluated.
|
||||
#[derive(ToShmem)]
|
||||
pub struct FeatureFlags : u8 {
|
||||
/// Different requirements or toggles that change how a expression is
|
||||
/// parsed.
|
||||
pub struct ParsingRequirements: u8 {
|
||||
/// The feature should only be parsed in chrome and ua sheets.
|
||||
const CHROME_AND_UA_ONLY = 1 << 0;
|
||||
/// The feature requires a -webkit- prefix.
|
||||
const WEBKIT_PREFIX = 1 << 1;
|
||||
/// The feature requires the inline-axis containment.
|
||||
const CONTAINER_REQUIRES_INLINE_AXIS = 1 << 2;
|
||||
/// The feature requires the block-axis containment.
|
||||
const CONTAINER_REQUIRES_BLOCK_AXIS = 1 << 3;
|
||||
/// The feature requires containment in the physical width axis.
|
||||
const CONTAINER_REQUIRES_WIDTH_AXIS = 1 << 4;
|
||||
/// The feature requires containment in the physical height axis.
|
||||
const CONTAINER_REQUIRES_HEIGHT_AXIS = 1 << 5;
|
||||
}
|
||||
}
|
||||
|
||||
impl FeatureFlags {
|
||||
/// Returns parsing requirement flags.
|
||||
pub fn parsing_requirements(self) -> Self {
|
||||
self.intersection(Self::CHROME_AND_UA_ONLY | Self::WEBKIT_PREFIX)
|
||||
}
|
||||
|
||||
/// Returns all the container axis flags.
|
||||
pub fn all_container_axes() -> Self {
|
||||
Self::CONTAINER_REQUIRES_INLINE_AXIS |
|
||||
Self::CONTAINER_REQUIRES_BLOCK_AXIS |
|
||||
Self::CONTAINER_REQUIRES_WIDTH_AXIS |
|
||||
Self::CONTAINER_REQUIRES_HEIGHT_AXIS
|
||||
}
|
||||
|
||||
/// Returns our subset of container axis flags.
|
||||
pub fn container_axes(self) -> Self {
|
||||
self.intersection(Self::all_container_axes())
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a feature allows ranges or not.
|
||||
/// Whether a media feature allows ranges or not.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum AllowsRanges {
|
||||
|
@ -148,45 +135,46 @@ pub enum AllowsRanges {
|
|||
No,
|
||||
}
|
||||
|
||||
/// A description of a feature.
|
||||
pub struct QueryFeatureDescription {
|
||||
/// The feature name, in ascii lowercase.
|
||||
/// A description of a media feature.
|
||||
pub struct MediaFeatureDescription {
|
||||
/// The media feature name, in ascii lowercase.
|
||||
pub name: Atom,
|
||||
/// Whether min- / max- prefixes are allowed or not.
|
||||
pub allows_ranges: AllowsRanges,
|
||||
/// The evaluator, which we also use to determine which kind of value to
|
||||
/// parse.
|
||||
pub evaluator: Evaluator,
|
||||
/// Different feature-specific flags.
|
||||
pub flags: FeatureFlags,
|
||||
/// Different requirements that need to hold for the feature to be
|
||||
/// successfully parsed.
|
||||
pub requirements: ParsingRequirements,
|
||||
}
|
||||
|
||||
impl QueryFeatureDescription {
|
||||
/// Whether this feature allows ranges.
|
||||
impl MediaFeatureDescription {
|
||||
/// Whether this media feature allows ranges.
|
||||
#[inline]
|
||||
pub fn allows_ranges(&self) -> bool {
|
||||
self.allows_ranges == AllowsRanges::Yes
|
||||
}
|
||||
}
|
||||
|
||||
/// A simple helper to construct a `QueryFeatureDescription`.
|
||||
/// A simple helper to construct a `MediaFeatureDescription`.
|
||||
macro_rules! feature {
|
||||
($name:expr, $allows_ranges:expr, $evaluator:expr, $flags:expr,) => {
|
||||
$crate::queries::feature::QueryFeatureDescription {
|
||||
($name:expr, $allows_ranges:expr, $evaluator:expr, $reqs:expr,) => {
|
||||
$crate::media_queries::media_feature::MediaFeatureDescription {
|
||||
name: $name,
|
||||
allows_ranges: $allows_ranges,
|
||||
evaluator: $evaluator,
|
||||
flags: $flags,
|
||||
requirements: $reqs,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl fmt::Debug for QueryFeatureDescription {
|
||||
impl fmt::Debug for MediaFeatureDescription {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("QueryFeatureDescription")
|
||||
f.debug_struct("MediaFeatureExpression")
|
||||
.field("name", &self.name)
|
||||
.field("allows_ranges", &self.allows_ranges)
|
||||
.field("flags", &self.flags)
|
||||
.field("requirements", &self.requirements)
|
||||
.finish()
|
||||
}
|
||||
}
|
535
components/style/media_queries/media_feature_expression.rs
Normal file
535
components/style/media_queries/media_feature_expression.rs
Normal file
|
@ -0,0 +1,535 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
//! Parsing for media feature expressions, like `(foo: bar)` or
|
||||
//! `(width >= 400px)`.
|
||||
|
||||
use super::media_feature::{Evaluator, MediaFeatureDescription};
|
||||
use super::media_feature::{KeywordDiscriminant, ParsingRequirements};
|
||||
use super::Device;
|
||||
use crate::context::QuirksMode;
|
||||
#[cfg(feature = "gecko")]
|
||||
use crate::gecko::media_features::MEDIA_FEATURES;
|
||||
use crate::parser::{Parse, ParserContext};
|
||||
#[cfg(feature = "servo")]
|
||||
use crate::servo::media_queries::MEDIA_FEATURES;
|
||||
use crate::str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase};
|
||||
use crate::values::computed::{self, Ratio, ToComputedValue};
|
||||
use crate::values::specified::{Integer, Length, Number, Resolution};
|
||||
use crate::values::{serialize_atom_identifier, CSSFloat};
|
||||
use crate::{Atom, Zero};
|
||||
use cssparser::{Parser, Token};
|
||||
use std::cmp::{Ordering, PartialOrd};
|
||||
use std::fmt::{self, Write};
|
||||
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
|
||||
|
||||
/// The kind of matching that should be performed on a media feature value.
|
||||
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
|
||||
pub enum Range {
|
||||
/// At least the specified value.
|
||||
Min,
|
||||
/// At most the specified value.
|
||||
Max,
|
||||
}
|
||||
|
||||
/// The operator that was specified in this media feature.
|
||||
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
|
||||
pub enum Operator {
|
||||
/// =
|
||||
Equal,
|
||||
/// >
|
||||
GreaterThan,
|
||||
/// >=
|
||||
GreaterThanEqual,
|
||||
/// <
|
||||
LessThan,
|
||||
/// <=
|
||||
LessThanEqual,
|
||||
}
|
||||
|
||||
impl ToCss for Operator {
|
||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||
where
|
||||
W: fmt::Write,
|
||||
{
|
||||
dest.write_str(match *self {
|
||||
Operator::Equal => "=",
|
||||
Operator::LessThan => "<",
|
||||
Operator::LessThanEqual => "<=",
|
||||
Operator::GreaterThan => ">",
|
||||
Operator::GreaterThanEqual => ">=",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Either a `Range` or an `Operator`.
|
||||
///
|
||||
/// Ranged media features are not allowed with operations (that'd make no
|
||||
/// sense).
|
||||
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
|
||||
pub enum RangeOrOperator {
|
||||
/// A `Range`.
|
||||
Range(Range),
|
||||
/// An `Operator`.
|
||||
Operator(Operator),
|
||||
}
|
||||
|
||||
impl RangeOrOperator {
|
||||
/// Evaluate a given range given an optional query value and a value from
|
||||
/// the browser.
|
||||
pub fn evaluate<T>(range_or_op: Option<Self>, query_value: Option<T>, value: T) -> bool
|
||||
where
|
||||
T: PartialOrd + Zero,
|
||||
{
|
||||
match query_value {
|
||||
Some(v) => Self::evaluate_with_query_value(range_or_op, v, value),
|
||||
None => !value.is_zero(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate a given range given a non-optional query value and a value from
|
||||
/// the browser.
|
||||
pub fn evaluate_with_query_value<T>(range_or_op: Option<Self>, query_value: T, value: T) -> bool
|
||||
where
|
||||
T: PartialOrd,
|
||||
{
|
||||
let cmp = match value.partial_cmp(&query_value) {
|
||||
Some(c) => c,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
let range_or_op = match range_or_op {
|
||||
Some(r) => r,
|
||||
None => return cmp == Ordering::Equal,
|
||||
};
|
||||
|
||||
match range_or_op {
|
||||
RangeOrOperator::Range(range) => {
|
||||
cmp == Ordering::Equal ||
|
||||
match range {
|
||||
Range::Min => cmp == Ordering::Greater,
|
||||
Range::Max => cmp == Ordering::Less,
|
||||
}
|
||||
},
|
||||
RangeOrOperator::Operator(op) => match op {
|
||||
Operator::Equal => cmp == Ordering::Equal,
|
||||
Operator::GreaterThan => cmp == Ordering::Greater,
|
||||
Operator::GreaterThanEqual => cmp == Ordering::Equal || cmp == Ordering::Greater,
|
||||
Operator::LessThan => cmp == Ordering::Less,
|
||||
Operator::LessThanEqual => cmp == Ordering::Equal || cmp == Ordering::Less,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A feature expression contains a reference to the media feature, the value
|
||||
/// the media query contained, and the range to evaluate.
|
||||
#[derive(Clone, Debug, MallocSizeOf, ToShmem)]
|
||||
pub struct MediaFeatureExpression {
|
||||
feature_index: usize,
|
||||
value: Option<MediaExpressionValue>,
|
||||
range_or_operator: Option<RangeOrOperator>,
|
||||
}
|
||||
|
||||
impl PartialEq for MediaFeatureExpression {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.feature_index == other.feature_index &&
|
||||
self.value == other.value &&
|
||||
self.range_or_operator == other.range_or_operator
|
||||
}
|
||||
}
|
||||
|
||||
impl ToCss for MediaFeatureExpression {
|
||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||
where
|
||||
W: fmt::Write,
|
||||
{
|
||||
dest.write_str("(")?;
|
||||
|
||||
let feature = self.feature();
|
||||
|
||||
if feature
|
||||
.requirements
|
||||
.contains(ParsingRequirements::WEBKIT_PREFIX)
|
||||
{
|
||||
dest.write_str("-webkit-")?;
|
||||
}
|
||||
|
||||
if let Some(RangeOrOperator::Range(range)) = self.range_or_operator {
|
||||
match range {
|
||||
Range::Min => dest.write_str("min-")?,
|
||||
Range::Max => dest.write_str("max-")?,
|
||||
}
|
||||
}
|
||||
|
||||
// NB: CssStringWriter not needed, feature names are under control.
|
||||
write!(dest, "{}", feature.name)?;
|
||||
|
||||
if let Some(RangeOrOperator::Operator(op)) = self.range_or_operator {
|
||||
dest.write_char(' ')?;
|
||||
op.to_css(dest)?;
|
||||
dest.write_char(' ')?;
|
||||
} else if self.value.is_some() {
|
||||
dest.write_str(": ")?;
|
||||
}
|
||||
|
||||
if let Some(ref val) = self.value {
|
||||
val.to_css(dest, self)?;
|
||||
}
|
||||
|
||||
dest.write_str(")")
|
||||
}
|
||||
}
|
||||
|
||||
/// Consumes an operation or a colon, or returns an error.
|
||||
fn consume_operation_or_colon(input: &mut Parser) -> Result<Option<Operator>, ()> {
|
||||
let first_delim = {
|
||||
let next_token = match input.next() {
|
||||
Ok(t) => t,
|
||||
Err(..) => return Err(()),
|
||||
};
|
||||
|
||||
match *next_token {
|
||||
Token::Colon => return Ok(None),
|
||||
Token::Delim(oper) => oper,
|
||||
_ => return Err(()),
|
||||
}
|
||||
};
|
||||
let operator = match first_delim {
|
||||
'=' => return Ok(Some(Operator::Equal)),
|
||||
'>' => Operator::GreaterThan,
|
||||
'<' => Operator::LessThan,
|
||||
_ => return Err(()),
|
||||
};
|
||||
|
||||
// https://drafts.csswg.org/mediaqueries-4/#mq-syntax:
|
||||
//
|
||||
// No whitespace is allowed between the “<” or “>”
|
||||
// <delim-token>s and the following “=” <delim-token>, if it’s
|
||||
// present.
|
||||
//
|
||||
// TODO(emilio): Maybe we should ignore comments as well?
|
||||
// https://github.com/w3c/csswg-drafts/issues/6248
|
||||
let parsed_equal = input
|
||||
.try_parse(|i| {
|
||||
let t = i.next_including_whitespace().map_err(|_| ())?;
|
||||
if !matches!(t, Token::Delim('=')) {
|
||||
return Err(());
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.is_ok();
|
||||
|
||||
if !parsed_equal {
|
||||
return Ok(Some(operator));
|
||||
}
|
||||
|
||||
Ok(Some(match operator {
|
||||
Operator::GreaterThan => Operator::GreaterThanEqual,
|
||||
Operator::LessThan => Operator::LessThanEqual,
|
||||
_ => unreachable!(),
|
||||
}))
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn disabled_by_pref(feature: &Atom, context: &ParserContext) -> bool {
|
||||
#[cfg(feature = "gecko")]
|
||||
{
|
||||
if *feature == atom!("forced-colors") {
|
||||
// forced-colors is always enabled in the ua and chrome. On
|
||||
// the web it is hidden behind a preference, which is defaulted
|
||||
// to 'true' as of bug 1659511.
|
||||
return !context.in_ua_or_chrome_sheet() &&
|
||||
!static_prefs::pref!("layout.css.forced-colors.enabled");
|
||||
}
|
||||
// prefers-contrast is always enabled in the ua and chrome. On
|
||||
// the web it is hidden behind a preference.
|
||||
if *feature == atom!("prefers-contrast") {
|
||||
return !context.in_ua_or_chrome_sheet() &&
|
||||
!static_prefs::pref!("layout.css.prefers-contrast.enabled");
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
impl MediaFeatureExpression {
|
||||
fn new(
|
||||
feature_index: usize,
|
||||
value: Option<MediaExpressionValue>,
|
||||
range_or_operator: Option<RangeOrOperator>,
|
||||
) -> Self {
|
||||
debug_assert!(feature_index < MEDIA_FEATURES.len());
|
||||
Self {
|
||||
feature_index,
|
||||
value,
|
||||
range_or_operator,
|
||||
}
|
||||
}
|
||||
|
||||
fn feature(&self) -> &'static MediaFeatureDescription {
|
||||
&MEDIA_FEATURES[self.feature_index]
|
||||
}
|
||||
|
||||
/// Parse a media expression of the form:
|
||||
///
|
||||
/// ```
|
||||
/// (media-feature: media-value)
|
||||
/// ```
|
||||
pub fn parse<'i, 't>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
) -> Result<Self, ParseError<'i>> {
|
||||
input.expect_parenthesis_block()?;
|
||||
input.parse_nested_block(|input| Self::parse_in_parenthesis_block(context, input))
|
||||
}
|
||||
|
||||
/// Parse a media feature expression where we've already consumed the
|
||||
/// parenthesis.
|
||||
pub fn parse_in_parenthesis_block<'i, 't>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
) -> Result<Self, ParseError<'i>> {
|
||||
let mut requirements = ParsingRequirements::empty();
|
||||
let location = input.current_source_location();
|
||||
let ident = input.expect_ident()?;
|
||||
|
||||
if context.in_ua_or_chrome_sheet() {
|
||||
requirements.insert(ParsingRequirements::CHROME_AND_UA_ONLY);
|
||||
}
|
||||
|
||||
let mut feature_name = &**ident;
|
||||
|
||||
if starts_with_ignore_ascii_case(feature_name, "-webkit-") {
|
||||
feature_name = &feature_name[8..];
|
||||
requirements.insert(ParsingRequirements::WEBKIT_PREFIX);
|
||||
}
|
||||
|
||||
let range = if starts_with_ignore_ascii_case(feature_name, "min-") {
|
||||
feature_name = &feature_name[4..];
|
||||
Some(Range::Min)
|
||||
} else if starts_with_ignore_ascii_case(feature_name, "max-") {
|
||||
feature_name = &feature_name[4..];
|
||||
Some(Range::Max)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let atom = Atom::from(string_as_ascii_lowercase(feature_name));
|
||||
|
||||
let (feature_index, feature) = match MEDIA_FEATURES
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, f)| f.name == atom)
|
||||
{
|
||||
Some((i, f)) => (i, f),
|
||||
None => {
|
||||
return Err(location.new_custom_error(
|
||||
StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()),
|
||||
))
|
||||
},
|
||||
};
|
||||
|
||||
if disabled_by_pref(&feature.name, context) ||
|
||||
!requirements.contains(feature.requirements) ||
|
||||
(range.is_some() && !feature.allows_ranges())
|
||||
{
|
||||
return Err(location.new_custom_error(
|
||||
StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()),
|
||||
));
|
||||
}
|
||||
|
||||
let operator = input.try_parse(consume_operation_or_colon);
|
||||
let operator = match operator {
|
||||
Err(..) => {
|
||||
// If there's no colon, this is a media query of the
|
||||
// form '(<feature>)', that is, there's no value
|
||||
// specified.
|
||||
//
|
||||
// Gecko doesn't allow ranged expressions without a
|
||||
// value, so just reject them here too.
|
||||
if range.is_some() {
|
||||
return Err(
|
||||
input.new_custom_error(StyleParseErrorKind::RangedExpressionWithNoValue)
|
||||
);
|
||||
}
|
||||
|
||||
return Ok(Self::new(feature_index, None, None));
|
||||
},
|
||||
Ok(operator) => operator,
|
||||
};
|
||||
|
||||
let range_or_operator = match range {
|
||||
Some(range) => {
|
||||
if operator.is_some() {
|
||||
return Err(
|
||||
input.new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator)
|
||||
);
|
||||
}
|
||||
Some(RangeOrOperator::Range(range))
|
||||
},
|
||||
None => match operator {
|
||||
Some(operator) => {
|
||||
if !feature.allows_ranges() {
|
||||
return Err(input
|
||||
.new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator));
|
||||
}
|
||||
Some(RangeOrOperator::Operator(operator))
|
||||
},
|
||||
None => None,
|
||||
},
|
||||
};
|
||||
|
||||
let value = MediaExpressionValue::parse(feature, context, input).map_err(|err| {
|
||||
err.location
|
||||
.new_custom_error(StyleParseErrorKind::MediaQueryExpectedFeatureValue)
|
||||
})?;
|
||||
|
||||
Ok(Self::new(feature_index, Some(value), range_or_operator))
|
||||
}
|
||||
|
||||
/// Returns whether this media query evaluates to true for the given device.
|
||||
pub fn matches(&self, device: &Device, quirks_mode: QuirksMode) -> bool {
|
||||
let value = self.value.as_ref();
|
||||
|
||||
macro_rules! expect {
|
||||
($variant:ident) => {
|
||||
value.map(|value| match *value {
|
||||
MediaExpressionValue::$variant(ref v) => v,
|
||||
_ => unreachable!("Unexpected MediaExpressionValue"),
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
match self.feature().evaluator {
|
||||
Evaluator::Length(eval) => {
|
||||
let computed = expect!(Length).map(|specified| {
|
||||
computed::Context::for_media_query_evaluation(device, quirks_mode, |context| {
|
||||
specified.to_computed_value(context)
|
||||
})
|
||||
});
|
||||
eval(device, computed, self.range_or_operator)
|
||||
},
|
||||
Evaluator::Integer(eval) => {
|
||||
eval(device, expect!(Integer).cloned(), self.range_or_operator)
|
||||
},
|
||||
Evaluator::Float(eval) => eval(device, expect!(Float).cloned(), self.range_or_operator),
|
||||
Evaluator::NumberRatio(eval) => eval(
|
||||
device,
|
||||
expect!(NumberRatio).cloned(),
|
||||
self.range_or_operator,
|
||||
),
|
||||
Evaluator::Resolution(eval) => {
|
||||
let computed = expect!(Resolution).map(|specified| {
|
||||
computed::Context::for_media_query_evaluation(device, quirks_mode, |context| {
|
||||
specified.to_computed_value(context)
|
||||
})
|
||||
});
|
||||
eval(device, computed, self.range_or_operator)
|
||||
},
|
||||
Evaluator::Enumerated { evaluator, .. } => {
|
||||
evaluator(device, expect!(Enumerated).cloned(), self.range_or_operator)
|
||||
},
|
||||
Evaluator::Ident(eval) => eval(device, expect!(Ident).cloned(), self.range_or_operator),
|
||||
Evaluator::BoolInteger(eval) => eval(
|
||||
device,
|
||||
expect!(BoolInteger).cloned(),
|
||||
self.range_or_operator,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A value found or expected in a media expression.
|
||||
///
|
||||
/// FIXME(emilio): How should calc() serialize in the Number / Integer /
|
||||
/// BoolInteger / NumberRatio case, as computed or as specified value?
|
||||
///
|
||||
/// If the first, this would need to store the relevant values.
|
||||
///
|
||||
/// See: https://github.com/w3c/csswg-drafts/issues/1968
|
||||
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
|
||||
pub enum MediaExpressionValue {
|
||||
/// A length.
|
||||
Length(Length),
|
||||
/// A (non-negative) integer.
|
||||
Integer(u32),
|
||||
/// A floating point value.
|
||||
Float(CSSFloat),
|
||||
/// A boolean value, specified as an integer (i.e., either 0 or 1).
|
||||
BoolInteger(bool),
|
||||
/// A single non-negative number or two non-negative numbers separated by '/',
|
||||
/// with optional whitespace on either side of the '/'.
|
||||
NumberRatio(Ratio),
|
||||
/// A resolution.
|
||||
Resolution(Resolution),
|
||||
/// An enumerated value, defined by the variant keyword table in the
|
||||
/// feature's `mData` member.
|
||||
Enumerated(KeywordDiscriminant),
|
||||
/// An identifier.
|
||||
Ident(Atom),
|
||||
}
|
||||
|
||||
impl MediaExpressionValue {
|
||||
fn to_css<W>(&self, dest: &mut CssWriter<W>, for_expr: &MediaFeatureExpression) -> fmt::Result
|
||||
where
|
||||
W: fmt::Write,
|
||||
{
|
||||
match *self {
|
||||
MediaExpressionValue::Length(ref l) => l.to_css(dest),
|
||||
MediaExpressionValue::Integer(v) => v.to_css(dest),
|
||||
MediaExpressionValue::Float(v) => v.to_css(dest),
|
||||
MediaExpressionValue::BoolInteger(v) => dest.write_str(if v { "1" } else { "0" }),
|
||||
MediaExpressionValue::NumberRatio(ratio) => ratio.to_css(dest),
|
||||
MediaExpressionValue::Resolution(ref r) => r.to_css(dest),
|
||||
MediaExpressionValue::Ident(ref ident) => serialize_atom_identifier(ident, dest),
|
||||
MediaExpressionValue::Enumerated(value) => match for_expr.feature().evaluator {
|
||||
Evaluator::Enumerated { serializer, .. } => dest.write_str(&*serializer(value)),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn parse<'i, 't>(
|
||||
for_feature: &MediaFeatureDescription,
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
) -> Result<MediaExpressionValue, ParseError<'i>> {
|
||||
Ok(match for_feature.evaluator {
|
||||
Evaluator::Length(..) => {
|
||||
let length = Length::parse_non_negative(context, input)?;
|
||||
MediaExpressionValue::Length(length)
|
||||
},
|
||||
Evaluator::Integer(..) => {
|
||||
let integer = Integer::parse_non_negative(context, input)?;
|
||||
MediaExpressionValue::Integer(integer.value() as u32)
|
||||
},
|
||||
Evaluator::BoolInteger(..) => {
|
||||
let integer = Integer::parse_non_negative(context, input)?;
|
||||
let value = integer.value();
|
||||
if value > 1 {
|
||||
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
|
||||
}
|
||||
MediaExpressionValue::BoolInteger(value == 1)
|
||||
},
|
||||
Evaluator::Float(..) => {
|
||||
let number = Number::parse(context, input)?;
|
||||
MediaExpressionValue::Float(number.get())
|
||||
},
|
||||
Evaluator::NumberRatio(..) => {
|
||||
use crate::values::specified::Ratio as SpecifiedRatio;
|
||||
let ratio = SpecifiedRatio::parse(context, input)?;
|
||||
MediaExpressionValue::NumberRatio(Ratio::new(ratio.0.get(), ratio.1.get()))
|
||||
},
|
||||
Evaluator::Resolution(..) => {
|
||||
MediaExpressionValue::Resolution(Resolution::parse(context, input)?)
|
||||
},
|
||||
Evaluator::Enumerated { parser, .. } => {
|
||||
MediaExpressionValue::Enumerated(parser(context, input)?)
|
||||
},
|
||||
Evaluator::Ident(..) => {
|
||||
MediaExpressionValue::Ident(Atom::from(input.expect_ident()?.as_ref()))
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
|
@ -10,7 +10,6 @@ use super::{Device, MediaQuery, Qualifier};
|
|||
use crate::context::QuirksMode;
|
||||
use crate::error_reporting::ContextualParseError;
|
||||
use crate::parser::ParserContext;
|
||||
use crate::values::computed;
|
||||
use cssparser::{Delimiter, Parser};
|
||||
use cssparser::{ParserInput, Token};
|
||||
|
||||
|
@ -75,17 +74,15 @@ impl MediaList {
|
|||
pub fn evaluate(&self, device: &Device, quirks_mode: QuirksMode) -> bool {
|
||||
// Check if it is an empty media query list or any queries match.
|
||||
// https://drafts.csswg.org/mediaqueries-4/#mq-list
|
||||
if self.media_queries.is_empty() {
|
||||
return true;
|
||||
}
|
||||
|
||||
computed::Context::for_media_query_evaluation(device, quirks_mode, |context| {
|
||||
self.media_queries.is_empty() ||
|
||||
self.media_queries.iter().any(|mq| {
|
||||
let media_match = mq.media_type.matches(device.media_type());
|
||||
|
||||
// Check if the media condition match.
|
||||
let query_match = media_match &&
|
||||
mq.condition.as_ref().map_or(true, |c| c.matches(context));
|
||||
mq.condition
|
||||
.as_ref()
|
||||
.map_or(true, |c| c.matches(device, quirks_mode));
|
||||
|
||||
// Apply the logical NOT qualifier to the result
|
||||
match mq.qualifier {
|
||||
|
@ -93,7 +90,6 @@ impl MediaList {
|
|||
_ => query_match,
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Whether this `MediaList` contains no media queries.
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
//!
|
||||
//! https://drafts.csswg.org/mediaqueries/#typedef-media-query
|
||||
|
||||
use crate::queries::{QueryCondition, FeatureType};
|
||||
use super::media_condition::MediaCondition;
|
||||
use crate::parser::ParserContext;
|
||||
use crate::str::string_as_ascii_lowercase;
|
||||
use crate::values::CustomIdent;
|
||||
|
@ -66,7 +66,7 @@ pub struct MediaQuery {
|
|||
pub media_type: MediaQueryType,
|
||||
/// The condition that this media query contains. This cannot have `or`
|
||||
/// in the first level.
|
||||
pub condition: Option<QueryCondition>,
|
||||
pub condition: Option<MediaCondition>,
|
||||
}
|
||||
|
||||
impl ToCss for MediaQuery {
|
||||
|
@ -134,9 +134,9 @@ impl MediaQuery {
|
|||
.unwrap_or_default();
|
||||
|
||||
let condition = if explicit_media_type.is_none() {
|
||||
Some(QueryCondition::parse(context, input, FeatureType::Media)?)
|
||||
Some(MediaCondition::parse(context, input)?)
|
||||
} else if input.try_parse(|i| i.expect_ident_matching("and")).is_ok() {
|
||||
Some(QueryCondition::parse_disallow_or(context, input, FeatureType::Media)?)
|
||||
Some(MediaCondition::parse_disallow_or(context, input)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
|
|
@ -6,9 +6,15 @@
|
|||
//!
|
||||
//! [mq]: https://drafts.csswg.org/mediaqueries/
|
||||
|
||||
mod media_condition;
|
||||
mod media_list;
|
||||
mod media_query;
|
||||
#[macro_use]
|
||||
pub mod media_feature;
|
||||
pub mod media_feature_expression;
|
||||
|
||||
pub use self::media_condition::MediaCondition;
|
||||
pub use self::media_feature_expression::MediaFeatureExpression;
|
||||
pub use self::media_list::MediaList;
|
||||
pub use self::media_query::{MediaQuery, MediaQueryType, MediaType, Qualifier};
|
||||
|
||||
|
|
|
@ -1,223 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
//! A piecewise linear function, following CSS linear easing
|
||||
/// draft as in https://github.com/w3c/csswg-drafts/pull/6533.
|
||||
use euclid::approxeq::ApproxEq;
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::values::CSSFloat;
|
||||
|
||||
type ValueType = CSSFloat;
|
||||
/// a single entry in a piecewise linear function.
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
struct Entry {
|
||||
x: ValueType,
|
||||
y: ValueType,
|
||||
}
|
||||
|
||||
/// Representation of a piecewise linear function, a series of linear functions.
|
||||
#[derive(Default)]
|
||||
#[repr(C)]
|
||||
pub struct PiecewiseLinearFunction {
|
||||
entries: crate::OwnedSlice<Entry>,
|
||||
}
|
||||
|
||||
impl PiecewiseLinearFunction {
|
||||
/// Interpolate y value given x and two points. The linear function will be rooted at the asymptote.
|
||||
fn interpolate(x: ValueType, prev: Entry, next: Entry, asymptote: &Entry) -> ValueType {
|
||||
// Line is vertical, or the two points are identical. Avoid infinite slope by pretending
|
||||
// the line is flat.
|
||||
if prev.x.approx_eq(&next.x) {
|
||||
return asymptote.y;
|
||||
}
|
||||
let slope = (next.y - prev.y) / (next.x - prev.x);
|
||||
return slope * (x - asymptote.x) + asymptote.y;
|
||||
}
|
||||
|
||||
/// Get the y value of the piecewise linear function given the x value.
|
||||
pub fn at(&self, x: ValueType) -> ValueType {
|
||||
if !x.is_finite() {
|
||||
return if x > 0.0 { 1.0 } else { 0.0 };
|
||||
}
|
||||
if self.entries.is_empty() {
|
||||
// Implied y = x, as per spec.
|
||||
return x;
|
||||
}
|
||||
if self.entries.len() == 1 {
|
||||
// Implied y = <constant>, as per spec.
|
||||
return self.entries[0].y;
|
||||
}
|
||||
// Spec dictates the valid input domain is [0, 1]. Outside of this range, the output
|
||||
// should be calculated as if the slopes at start and end extend to infinity. However, if the
|
||||
// start/end have two points of the same position, the line should extend along the x-axis.
|
||||
// The function doesn't have to cover the input domain, in which case the extension logic
|
||||
// applies even if the input falls in the input domain.
|
||||
// Also, we're guaranteed to have at least two elements at this point.
|
||||
if x < self.entries[0].x {
|
||||
return Self::interpolate(x, self.entries[0], self.entries[1], &self.entries[0]);
|
||||
}
|
||||
let mut rev_iter = self.entries.iter().rev();
|
||||
let last = rev_iter.next().unwrap();
|
||||
if x > last.x {
|
||||
let second_last = rev_iter.next().unwrap();
|
||||
return Self::interpolate(x, *second_last, *last, last);
|
||||
}
|
||||
|
||||
// Now we know the input sits within the domain explicitly defined by our function.
|
||||
for (prev, next) in self.entries.iter().tuple_windows() {
|
||||
if x > next.x {
|
||||
continue;
|
||||
}
|
||||
// Prefer left hand side value
|
||||
if x.approx_eq(&prev.x) {
|
||||
return prev.y;
|
||||
}
|
||||
if x.approx_eq(&next.x) {
|
||||
return next.y;
|
||||
}
|
||||
return Self::interpolate(x, *prev, *next, prev);
|
||||
}
|
||||
unreachable!("Input is supposed to be within the entries' min & max!");
|
||||
}
|
||||
}
|
||||
|
||||
/// Entry of a piecewise linear function while building, where the calculation of x value can be deferred.
|
||||
#[derive(Clone, Copy)]
|
||||
struct BuildEntry {
|
||||
x: Option<ValueType>,
|
||||
y: ValueType,
|
||||
}
|
||||
|
||||
/// Builder object to generate a linear function.
|
||||
#[derive(Default)]
|
||||
pub struct PiecewiseLinearFunctionBuilder {
|
||||
largest_x: Option<ValueType>,
|
||||
smallest_x: Option<ValueType>,
|
||||
entries: Vec<BuildEntry>,
|
||||
}
|
||||
|
||||
impl PiecewiseLinearFunctionBuilder {
|
||||
#[allow(missing_docs)]
|
||||
pub fn new() -> Self {
|
||||
PiecewiseLinearFunctionBuilder::default()
|
||||
}
|
||||
|
||||
fn create_entry(&mut self, y: ValueType, x: Option<ValueType>) {
|
||||
let x = match x {
|
||||
Some(x) if x.is_finite() => x,
|
||||
_ => {
|
||||
self.entries.push(BuildEntry { x: None, y });
|
||||
return;
|
||||
},
|
||||
};
|
||||
// Specified x value cannot regress, as per spec.
|
||||
let x = match self.largest_x {
|
||||
Some(largest_x) => x.max(largest_x),
|
||||
None => x,
|
||||
};
|
||||
self.largest_x = Some(x);
|
||||
// Whatever we see the earliest is the smallest value.
|
||||
if self.smallest_x.is_none() {
|
||||
self.smallest_x = Some(x);
|
||||
}
|
||||
self.entries.push(BuildEntry { x: Some(x), y });
|
||||
}
|
||||
|
||||
/// Add a new entry into the piecewise linear function with specified y value.
|
||||
/// If the start x value is given, that is where the x value will be. Otherwise,
|
||||
/// the x value is calculated later. If the end x value is specified, a flat segment
|
||||
/// is generated. If start x value is not specified but end x is, it is treated as
|
||||
/// start x.
|
||||
pub fn push(mut self, y: CSSFloat, x_start: Option<CSSFloat>, x_end: Option<CSSFloat>) -> Self {
|
||||
self.create_entry(y, x_start);
|
||||
if x_end.is_some() {
|
||||
self.create_entry(y, x_end.map(|x| x));
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Finish building the piecewise linear function by resolving all undefined x values,
|
||||
/// then return the result.
|
||||
pub fn build(mut self) -> PiecewiseLinearFunction {
|
||||
if self.entries.is_empty() {
|
||||
return PiecewiseLinearFunction::default();
|
||||
}
|
||||
if self.entries.len() == 1 {
|
||||
// Don't bother resolving anything.
|
||||
return PiecewiseLinearFunction {
|
||||
entries: crate::OwnedSlice::from_slice(&[Entry {
|
||||
x: 0.,
|
||||
y: self.entries[0].y,
|
||||
}]),
|
||||
};
|
||||
}
|
||||
// Guaranteed at least two elements.
|
||||
// Start and end elements guaranteed to have defined x value.
|
||||
// Note(dshin): Spec asserts that start/end elements are supposed to have 0/1 assigned
|
||||
// respectively if their x values are undefined at this time; however, the spec does
|
||||
// not disallow negative/100%+ inputs, and inputs like `linear(0, 0.1 -10%, 0.9 110%, 1.0)`
|
||||
// would break the assumption that the x values in the list increase monotonically.
|
||||
// Otherwise, we still want 0/1 assigned to the start/end values regardless of
|
||||
// adjacent x values (i.e. `linear(0, 0.1 10%, 0.9 90%, 1.0)` ==
|
||||
// `linear(0 0%, 0.1 10%, 0.9 90%, 1.0)` != `linear(0 10%, 0.1 10%, 0.9 90%, 1.0 90%)`)
|
||||
self.entries[0]
|
||||
.x
|
||||
.get_or_insert(self.smallest_x.filter(|x| x < &0.0).unwrap_or(0.0));
|
||||
self.entries
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.x
|
||||
.get_or_insert(self.largest_x.filter(|x| x > &1.0).unwrap_or(1.0));
|
||||
|
||||
let mut result = Vec::with_capacity(self.entries.len());
|
||||
result.push(Entry {
|
||||
x: self.entries[0].x.unwrap(),
|
||||
y: self.entries[0].y,
|
||||
});
|
||||
for (i, e) in self.entries.iter().enumerate().skip(1) {
|
||||
if e.x.is_none() {
|
||||
// Need to calculate x values by first finding an entry with the first
|
||||
// defined x value (Guaranteed to exist as the list end has it defined).
|
||||
continue;
|
||||
}
|
||||
// x is defined for this element.
|
||||
let divisor = i - result.len() + 1;
|
||||
// Any element(s) with undefined x to assign?
|
||||
if divisor != 1 {
|
||||
// Have at least one element in result at all times.
|
||||
let start_x = result.last().unwrap().x;
|
||||
let increment = (e.x.unwrap() - start_x) / divisor as ValueType;
|
||||
// Grab every element with undefined x to this point, which starts at the end of the result
|
||||
// array, and ending right before the current index. Then, assigned the evenly divided
|
||||
// x values.
|
||||
result.extend(
|
||||
self.entries[result.len()..i]
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(j, e)| {
|
||||
debug_assert!(e.x.is_none(), "Expected an entry with x undefined!");
|
||||
Entry {
|
||||
x: increment * (j + 1) as ValueType + start_x,
|
||||
y: e.y,
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
result.push(Entry {
|
||||
x: e.x.unwrap(),
|
||||
y: e.y,
|
||||
});
|
||||
}
|
||||
debug_assert_eq!(
|
||||
result.len(),
|
||||
self.entries.len(),
|
||||
"Should've mapped one-to-one!"
|
||||
);
|
||||
PiecewiseLinearFunction {
|
||||
entries: result.into(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -28,8 +28,34 @@ use smallvec::SmallVec;
|
|||
use std::borrow::Cow;
|
||||
use std::cell::RefCell;
|
||||
|
||||
/// We split the cascade in two phases: 'early' properties, and 'late'
|
||||
/// properties.
|
||||
///
|
||||
/// Early properties are the ones that don't have dependencies _and_ other
|
||||
/// properties depend on, for example, writing-mode related properties, color
|
||||
/// (for currentColor), or font-size (for em, etc).
|
||||
///
|
||||
/// Late properties are all the others.
|
||||
trait CascadePhase {
|
||||
fn is_early() -> bool;
|
||||
}
|
||||
|
||||
struct EarlyProperties;
|
||||
impl CascadePhase for EarlyProperties {
|
||||
fn is_early() -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
struct LateProperties;
|
||||
impl CascadePhase for LateProperties {
|
||||
fn is_early() -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum CanHaveLogicalProperties {
|
||||
enum ApplyResetProperties {
|
||||
No,
|
||||
Yes,
|
||||
}
|
||||
|
@ -257,7 +283,6 @@ where
|
|||
let inherited_style = parent_style.unwrap_or(device.default_computed_values());
|
||||
|
||||
let mut declarations = SmallVec::<[(&_, CascadePriority); 32]>::new();
|
||||
let mut referenced_properties = LonghandIdSet::default();
|
||||
let custom_properties = {
|
||||
let mut builder = CustomPropertiesBuilder::new(inherited_style.custom_properties(), device);
|
||||
|
||||
|
@ -265,8 +290,6 @@ where
|
|||
declarations.push((declaration, priority));
|
||||
if let PropertyDeclaration::Custom(ref declaration) = *declaration {
|
||||
builder.cascade(declaration, priority);
|
||||
} else {
|
||||
referenced_properties.insert(declaration.id().as_longhand().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -292,74 +315,46 @@ where
|
|||
in_media_query: false,
|
||||
for_smil_animation: false,
|
||||
for_non_inherited_property: None,
|
||||
container_info: None,
|
||||
quirks_mode,
|
||||
rule_cache_conditions: RefCell::new(rule_cache_conditions),
|
||||
};
|
||||
|
||||
let using_cached_reset_properties;
|
||||
let mut cascade = Cascade::new(&mut context, cascade_mode, &referenced_properties);
|
||||
let mut shorthand_cache = ShorthandsWithPropertyReferencesCache::default();
|
||||
let using_cached_reset_properties = {
|
||||
let mut cascade = Cascade::new(&mut context, cascade_mode);
|
||||
let mut shorthand_cache = ShorthandsWithPropertyReferencesCache::default();
|
||||
|
||||
let properties_to_apply = match cascade.cascade_mode {
|
||||
CascadeMode::Visited { writing_mode } => {
|
||||
cascade.context.builder.writing_mode = writing_mode;
|
||||
// We never insert visited styles into the cache so we don't need to
|
||||
// try looking it up. It also wouldn't be super-profitable, only a
|
||||
// handful reset properties are non-inherited.
|
||||
using_cached_reset_properties = false;
|
||||
LonghandIdSet::visited_dependent()
|
||||
},
|
||||
CascadeMode::Unvisited { visited_rules } => {
|
||||
if cascade.apply_properties(
|
||||
CanHaveLogicalProperties::No,
|
||||
LonghandIdSet::writing_mode_group(),
|
||||
declarations.iter().cloned(),
|
||||
&mut shorthand_cache,
|
||||
) {
|
||||
cascade.compute_writing_mode();
|
||||
}
|
||||
cascade.apply_properties::<EarlyProperties, _>(
|
||||
ApplyResetProperties::Yes,
|
||||
declarations.iter().cloned(),
|
||||
&mut shorthand_cache,
|
||||
);
|
||||
|
||||
if cascade.apply_properties(
|
||||
CanHaveLogicalProperties::No,
|
||||
LonghandIdSet::fonts_and_color_group(),
|
||||
declarations.iter().cloned(),
|
||||
&mut shorthand_cache,
|
||||
) {
|
||||
cascade.fixup_font_stuff();
|
||||
}
|
||||
cascade.compute_visited_style_if_needed(
|
||||
element,
|
||||
parent_style,
|
||||
parent_style_ignoring_first_line,
|
||||
layout_parent_style,
|
||||
guards,
|
||||
);
|
||||
|
||||
if let Some(visited_rules) = visited_rules {
|
||||
cascade.compute_visited_style_if_needed(
|
||||
element,
|
||||
parent_style,
|
||||
parent_style_ignoring_first_line,
|
||||
layout_parent_style,
|
||||
visited_rules,
|
||||
guards,
|
||||
);
|
||||
}
|
||||
let using_cached_reset_properties =
|
||||
cascade.try_to_use_cached_reset_properties(rule_cache, guards);
|
||||
|
||||
using_cached_reset_properties =
|
||||
cascade.try_to_use_cached_reset_properties(rule_cache, guards);
|
||||
let apply_reset = if using_cached_reset_properties {
|
||||
ApplyResetProperties::No
|
||||
} else {
|
||||
ApplyResetProperties::Yes
|
||||
};
|
||||
|
||||
if using_cached_reset_properties {
|
||||
LonghandIdSet::late_group_only_inherited()
|
||||
} else {
|
||||
LonghandIdSet::late_group()
|
||||
}
|
||||
}
|
||||
cascade.apply_properties::<LateProperties, _>(
|
||||
apply_reset,
|
||||
declarations.iter().cloned(),
|
||||
&mut shorthand_cache,
|
||||
);
|
||||
|
||||
using_cached_reset_properties
|
||||
};
|
||||
|
||||
cascade.apply_properties(
|
||||
CanHaveLogicalProperties::Yes,
|
||||
properties_to_apply,
|
||||
declarations.iter().cloned(),
|
||||
&mut shorthand_cache,
|
||||
);
|
||||
|
||||
cascade.finished_applying_properties();
|
||||
|
||||
context.builder.clear_modified_reset();
|
||||
|
||||
if matches!(cascade_mode, CascadeMode::Unvisited { .. }) {
|
||||
|
@ -418,7 +413,7 @@ fn tweak_when_ignoring_colors(
|
|||
|
||||
fn alpha_channel(color: &Color, context: &computed::Context) -> u8 {
|
||||
// We assume here currentColor is opaque.
|
||||
let color = color.to_computed_value(context).into_rgba(RGBA::new(0, 0, 0, 255));
|
||||
let color = color.to_computed_value(context).to_rgba(RGBA::new(0, 0, 0, 255));
|
||||
color.alpha
|
||||
}
|
||||
|
||||
|
@ -433,17 +428,14 @@ fn tweak_when_ignoring_colors(
|
|||
// otherwise, this is needed to preserve semi-transparent
|
||||
// backgrounds.
|
||||
//
|
||||
// NOTE(emilio): We honor transparent unconditionally, like we do
|
||||
// for color, even though it causes issues like bug 1625036. The
|
||||
// reasoning is that the conditions that trigger that (having
|
||||
// mismatched widget and default backgrounds) are both uncommon, and
|
||||
// broken in other applications as well, and not honoring
|
||||
// transparent makes stuff uglier or break unconditionally
|
||||
// NOTE(emilio): We revert even for alpha == 0. Not doing so would
|
||||
// be a bit special casey, even though it causes issues like
|
||||
// bug 1625036. The reasoning is that the conditions that trigger
|
||||
// that (having mismatched widget and default backgrounds) are both
|
||||
// uncommon, and broken in other applications as well, and not
|
||||
// honoring transparent makes stuff uglier or break unconditionally
|
||||
// (bug 1666059, bug 1755713).
|
||||
let alpha = alpha_channel(color, context);
|
||||
if alpha == 0 {
|
||||
return;
|
||||
}
|
||||
let mut color = context.builder.device.default_background_color();
|
||||
color.alpha = alpha;
|
||||
declarations_to_apply_unless_overriden
|
||||
|
@ -505,8 +497,6 @@ fn tweak_when_ignoring_colors(
|
|||
struct Cascade<'a, 'b: 'a> {
|
||||
context: &'a mut computed::Context<'b>,
|
||||
cascade_mode: CascadeMode<'a>,
|
||||
/// All the properties that have a declaration in the cascade.
|
||||
referenced: &'a LonghandIdSet,
|
||||
seen: LonghandIdSet,
|
||||
author_specified: LonghandIdSet,
|
||||
reverted_set: LonghandIdSet,
|
||||
|
@ -514,15 +504,10 @@ struct Cascade<'a, 'b: 'a> {
|
|||
}
|
||||
|
||||
impl<'a, 'b: 'a> Cascade<'a, 'b> {
|
||||
fn new(
|
||||
context: &'a mut computed::Context<'b>,
|
||||
cascade_mode: CascadeMode<'a>,
|
||||
referenced: &'a LonghandIdSet,
|
||||
) -> Self {
|
||||
fn new(context: &'a mut computed::Context<'b>, cascade_mode: CascadeMode<'a>) -> Self {
|
||||
Self {
|
||||
context,
|
||||
cascade_mode,
|
||||
referenced,
|
||||
seen: LonghandIdSet::default(),
|
||||
author_specified: LonghandIdSet::default(),
|
||||
reverted_set: Default::default(),
|
||||
|
@ -590,21 +575,23 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
|
|||
(CASCADE_PROPERTY[discriminant])(declaration, &mut self.context);
|
||||
}
|
||||
|
||||
fn apply_properties<'decls, I>(
|
||||
fn apply_properties<'decls, Phase, I>(
|
||||
&mut self,
|
||||
can_have_logical_properties: CanHaveLogicalProperties,
|
||||
properties_to_apply: &'a LonghandIdSet,
|
||||
apply_reset: ApplyResetProperties,
|
||||
declarations: I,
|
||||
mut shorthand_cache: &mut ShorthandsWithPropertyReferencesCache,
|
||||
) -> bool
|
||||
where
|
||||
) where
|
||||
Phase: CascadePhase,
|
||||
I: Iterator<Item = (&'decls PropertyDeclaration, CascadePriority)>,
|
||||
{
|
||||
if !self.referenced.contains_any(properties_to_apply) {
|
||||
return false;
|
||||
}
|
||||
let apply_reset = apply_reset == ApplyResetProperties::Yes;
|
||||
|
||||
let can_have_logical_properties = can_have_logical_properties == CanHaveLogicalProperties::Yes;
|
||||
debug_assert!(
|
||||
!Phase::is_early() || apply_reset,
|
||||
"Should always apply reset properties in the early phase, since we \
|
||||
need to know font-size / writing-mode to decide whether to use the \
|
||||
cached reset properties"
|
||||
);
|
||||
|
||||
let ignore_colors = !self.context.builder.device.use_document_colors();
|
||||
let mut declarations_to_apply_unless_overriden = DeclarationsToApplyUnlessOverriden::new();
|
||||
|
@ -618,15 +605,20 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
|
|||
PropertyDeclarationId::Custom(..) => continue,
|
||||
};
|
||||
|
||||
if !properties_to_apply.contains(longhand_id) {
|
||||
let inherited = longhand_id.inherited();
|
||||
if !apply_reset && !inherited {
|
||||
continue;
|
||||
}
|
||||
|
||||
debug_assert!(can_have_logical_properties || !longhand_id.is_logical());
|
||||
let physical_longhand_id = if can_have_logical_properties {
|
||||
longhand_id.to_physical(self.context.builder.writing_mode)
|
||||
} else {
|
||||
if Phase::is_early() != longhand_id.is_early_property() {
|
||||
continue;
|
||||
}
|
||||
|
||||
debug_assert!(!Phase::is_early() || !longhand_id.is_logical());
|
||||
let physical_longhand_id = if Phase::is_early() {
|
||||
longhand_id
|
||||
} else {
|
||||
longhand_id.to_physical(self.context.builder.writing_mode)
|
||||
};
|
||||
|
||||
if self.seen.contains(physical_longhand_id) {
|
||||
|
@ -641,6 +633,15 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
|
|||
}
|
||||
}
|
||||
|
||||
// Only a few properties are allowed to depend on the visited state
|
||||
// of links. When cascading visited styles, we can save time by
|
||||
// only processing these properties.
|
||||
if matches!(self.cascade_mode, CascadeMode::Visited { .. }) &&
|
||||
!physical_longhand_id.is_visited_dependent()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut declaration =
|
||||
self.substitute_variables_if_needed(declaration, &mut shorthand_cache);
|
||||
|
||||
|
@ -674,8 +675,8 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
|
|||
continue;
|
||||
},
|
||||
CSSWideKeyword::Unset => true,
|
||||
CSSWideKeyword::Inherit => longhand_id.inherited(),
|
||||
CSSWideKeyword::Initial => !longhand_id.inherited(),
|
||||
CSSWideKeyword::Inherit => inherited,
|
||||
CSSWideKeyword::Initial => !inherited,
|
||||
},
|
||||
None => false,
|
||||
};
|
||||
|
@ -709,13 +710,22 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
|
|||
}
|
||||
}
|
||||
|
||||
true
|
||||
if Phase::is_early() {
|
||||
self.fixup_font_stuff();
|
||||
self.compute_writing_mode();
|
||||
} else {
|
||||
self.finished_applying_properties();
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_writing_mode(&mut self) {
|
||||
debug_assert!(matches!(self.cascade_mode, CascadeMode::Unvisited { .. }));
|
||||
self.context.builder.writing_mode =
|
||||
WritingMode::new(self.context.builder.get_inherited_box())
|
||||
let writing_mode = match self.cascade_mode {
|
||||
CascadeMode::Unvisited { .. } => {
|
||||
WritingMode::new(self.context.builder.get_inherited_box())
|
||||
},
|
||||
CascadeMode::Visited { writing_mode } => writing_mode,
|
||||
};
|
||||
self.context.builder.writing_mode = writing_mode;
|
||||
}
|
||||
|
||||
fn compute_visited_style_if_needed<E>(
|
||||
|
@ -724,12 +734,20 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
|
|||
parent_style: Option<&ComputedValues>,
|
||||
parent_style_ignoring_first_line: Option<&ComputedValues>,
|
||||
layout_parent_style: Option<&ComputedValues>,
|
||||
visited_rules: &StrongRuleNode,
|
||||
guards: &StylesheetGuards,
|
||||
) where
|
||||
E: TElement,
|
||||
{
|
||||
debug_assert!(matches!(self.cascade_mode, CascadeMode::Unvisited { .. }));
|
||||
let visited_rules = match self.cascade_mode {
|
||||
CascadeMode::Unvisited { visited_rules } => visited_rules,
|
||||
CascadeMode::Visited { .. } => return,
|
||||
};
|
||||
|
||||
let visited_rules = match visited_rules {
|
||||
Some(rules) => rules,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let is_link = self.context.builder.pseudo.is_none() && element.unwrap().is_link();
|
||||
|
||||
macro_rules! visited_parent {
|
||||
|
|
|
@ -73,6 +73,7 @@ COUNTED_UNKNOWN_PROPERTIES = [
|
|||
"-webkit-perspective-origin-y",
|
||||
"-webkit-margin-before-collapse",
|
||||
"-webkit-border-before-style",
|
||||
"scroll-snap-stop",
|
||||
"-webkit-margin-bottom-collapse",
|
||||
"-webkit-ruby-position",
|
||||
"-webkit-column-break-after",
|
||||
|
|
|
@ -444,7 +444,6 @@ class Longhand(Property):
|
|||
"ColumnCount",
|
||||
"Contain",
|
||||
"ContentVisibility",
|
||||
"ContainerType",
|
||||
"Display",
|
||||
"FillRule",
|
||||
"Float",
|
||||
|
@ -492,7 +491,6 @@ class Longhand(Property):
|
|||
"ScrollbarGutter",
|
||||
"ScrollSnapAlign",
|
||||
"ScrollSnapAxis",
|
||||
"ScrollSnapStop",
|
||||
"ScrollSnapStrictness",
|
||||
"ScrollSnapType",
|
||||
"TextAlign",
|
||||
|
|
|
@ -755,6 +755,7 @@ fn static_assert() {
|
|||
<%self:impl_trait style_struct_name="Margin"
|
||||
skip_longhands="${skip_margin_longhands}
|
||||
${skip_scroll_margin_longhands}">
|
||||
|
||||
% for side in SIDES:
|
||||
<% impl_split_style_coord("margin_%s" % side.ident,
|
||||
"mMargin",
|
||||
|
@ -1180,7 +1181,11 @@ fn static_assert() {
|
|||
</%def>
|
||||
|
||||
<% skip_box_longhands= """display
|
||||
clear
|
||||
animation-name animation-delay animation-duration
|
||||
animation-direction animation-fill-mode animation-play-state
|
||||
animation-iteration-count animation-timeline animation-timing-function
|
||||
clear transition-duration transition-delay
|
||||
transition-timing-function transition-property
|
||||
-webkit-line-clamp""" %>
|
||||
<%self:impl_trait style_struct_name="Box" skip_longhands="${skip_box_longhands}">
|
||||
#[inline]
|
||||
|
@ -1222,6 +1227,245 @@ fn static_assert() {
|
|||
) %>
|
||||
${impl_keyword('clear', 'mBreakType', clear_keyword)}
|
||||
|
||||
${impl_transition_time_value('delay', 'Delay')}
|
||||
${impl_transition_time_value('duration', 'Duration')}
|
||||
${impl_animation_or_transition_timing_function('transition')}
|
||||
|
||||
pub fn transition_combined_duration_at(&self, index: usize) -> f32 {
|
||||
// https://drafts.csswg.org/css-transitions/#transition-combined-duration
|
||||
self.gecko.mTransitions[index % self.gecko.mTransitionDurationCount as usize].mDuration.max(0.0)
|
||||
+ self.gecko.mTransitions[index % self.gecko.mTransitionDelayCount as usize].mDelay
|
||||
}
|
||||
|
||||
pub fn set_transition_property<I>(&mut self, v: I)
|
||||
where I: IntoIterator<Item = longhands::transition_property::computed_value::single_value::T>,
|
||||
I::IntoIter: ExactSizeIterator
|
||||
{
|
||||
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_no_properties;
|
||||
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_variable;
|
||||
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSProperty_UNKNOWN;
|
||||
|
||||
let v = v.into_iter();
|
||||
|
||||
if v.len() != 0 {
|
||||
self.gecko.mTransitions.ensure_len(v.len());
|
||||
self.gecko.mTransitionPropertyCount = v.len() as u32;
|
||||
for (servo, gecko) in v.zip(self.gecko.mTransitions.iter_mut()) {
|
||||
unsafe { gecko.mUnknownProperty.clear() };
|
||||
|
||||
match servo {
|
||||
TransitionProperty::Unsupported(ident) => {
|
||||
gecko.mProperty = eCSSProperty_UNKNOWN;
|
||||
gecko.mUnknownProperty.mRawPtr = ident.0.into_addrefed();
|
||||
},
|
||||
TransitionProperty::Custom(name) => {
|
||||
gecko.mProperty = eCSSPropertyExtra_variable;
|
||||
gecko.mUnknownProperty.mRawPtr = name.into_addrefed();
|
||||
}
|
||||
_ => gecko.mProperty = servo.to_nscsspropertyid().unwrap(),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// In gecko |none| is represented by eCSSPropertyExtra_no_properties.
|
||||
self.gecko.mTransitionPropertyCount = 1;
|
||||
self.gecko.mTransitions[0].mProperty = eCSSPropertyExtra_no_properties;
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether there are any transitions specified.
|
||||
pub fn specifies_transitions(&self) -> bool {
|
||||
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_all_properties;
|
||||
if self.gecko.mTransitionPropertyCount == 1 &&
|
||||
self.gecko.mTransitions[0].mProperty == eCSSPropertyExtra_all_properties &&
|
||||
self.transition_combined_duration_at(0) <= 0.0f32 {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.gecko.mTransitionPropertyCount > 0
|
||||
}
|
||||
|
||||
pub fn transition_property_at(&self, index: usize)
|
||||
-> longhands::transition_property::computed_value::SingleComputedValue {
|
||||
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_no_properties;
|
||||
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_variable;
|
||||
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSProperty_UNKNOWN;
|
||||
|
||||
let property = self.gecko.mTransitions[index].mProperty;
|
||||
if property == eCSSProperty_UNKNOWN {
|
||||
let atom = self.gecko.mTransitions[index].mUnknownProperty.mRawPtr;
|
||||
debug_assert!(!atom.is_null());
|
||||
TransitionProperty::Unsupported(CustomIdent(unsafe{
|
||||
Atom::from_raw(atom)
|
||||
}))
|
||||
} else if property == eCSSPropertyExtra_variable {
|
||||
let atom = self.gecko.mTransitions[index].mUnknownProperty.mRawPtr;
|
||||
debug_assert!(!atom.is_null());
|
||||
TransitionProperty::Custom(unsafe{
|
||||
Atom::from_raw(atom)
|
||||
})
|
||||
} else if property == eCSSPropertyExtra_no_properties {
|
||||
// Actually, we don't expect TransitionProperty::Unsupported also
|
||||
// represents "none", but if the caller wants to convert it, it is
|
||||
// fine. Please use it carefully.
|
||||
//
|
||||
// FIXME(emilio): This is a hack, is this reachable?
|
||||
TransitionProperty::Unsupported(CustomIdent(atom!("none")))
|
||||
} else {
|
||||
property.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn transition_nscsspropertyid_at(&self, index: usize) -> nsCSSPropertyID {
|
||||
self.gecko.mTransitions[index].mProperty
|
||||
}
|
||||
|
||||
pub fn copy_transition_property_from(&mut self, other: &Self) {
|
||||
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_variable;
|
||||
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSProperty_UNKNOWN;
|
||||
self.gecko.mTransitions.ensure_len(other.gecko.mTransitions.len());
|
||||
|
||||
let count = other.gecko.mTransitionPropertyCount;
|
||||
self.gecko.mTransitionPropertyCount = count;
|
||||
|
||||
for (index, transition) in self.gecko.mTransitions.iter_mut().enumerate().take(count as usize) {
|
||||
transition.mProperty = other.gecko.mTransitions[index].mProperty;
|
||||
unsafe { transition.mUnknownProperty.clear() };
|
||||
if transition.mProperty == eCSSProperty_UNKNOWN ||
|
||||
transition.mProperty == eCSSPropertyExtra_variable {
|
||||
let atom = other.gecko.mTransitions[index].mUnknownProperty.mRawPtr;
|
||||
debug_assert!(!atom.is_null());
|
||||
transition.mUnknownProperty.mRawPtr = unsafe { Atom::from_raw(atom) }.into_addrefed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset_transition_property(&mut self, other: &Self) {
|
||||
self.copy_transition_property_from(other)
|
||||
}
|
||||
|
||||
${impl_transition_count('property', 'Property')}
|
||||
|
||||
pub fn animations_equals(&self, other: &Self) -> bool {
|
||||
return self.gecko.mAnimationNameCount == other.gecko.mAnimationNameCount
|
||||
&& self.gecko.mAnimationDelayCount == other.gecko.mAnimationDelayCount
|
||||
&& self.gecko.mAnimationDirectionCount == other.gecko.mAnimationDirectionCount
|
||||
&& self.gecko.mAnimationDurationCount == other.gecko.mAnimationDurationCount
|
||||
&& self.gecko.mAnimationFillModeCount == other.gecko.mAnimationFillModeCount
|
||||
&& self.gecko.mAnimationIterationCountCount == other.gecko.mAnimationIterationCountCount
|
||||
&& self.gecko.mAnimationPlayStateCount == other.gecko.mAnimationPlayStateCount
|
||||
&& self.gecko.mAnimationTimingFunctionCount == other.gecko.mAnimationTimingFunctionCount
|
||||
&& unsafe { bindings::Gecko_StyleAnimationsEquals(&self.gecko.mAnimations, &other.gecko.mAnimations) }
|
||||
}
|
||||
|
||||
pub fn set_animation_name<I>(&mut self, v: I)
|
||||
where I: IntoIterator<Item = longhands::animation_name::computed_value::single_value::T>,
|
||||
I::IntoIter: ExactSizeIterator
|
||||
{
|
||||
let v = v.into_iter();
|
||||
debug_assert_ne!(v.len(), 0);
|
||||
self.gecko.mAnimations.ensure_len(v.len());
|
||||
|
||||
self.gecko.mAnimationNameCount = v.len() as u32;
|
||||
for (servo, gecko) in v.zip(self.gecko.mAnimations.iter_mut()) {
|
||||
let atom = match servo.0 {
|
||||
None => atom!(""),
|
||||
Some(ref name) => name.as_atom().clone(),
|
||||
};
|
||||
unsafe { bindings::Gecko_SetAnimationName(gecko, atom.into_addrefed()); }
|
||||
}
|
||||
}
|
||||
pub fn animation_name_at(&self, index: usize)
|
||||
-> longhands::animation_name::computed_value::SingleComputedValue {
|
||||
use crate::properties::longhands::animation_name::single_value::SpecifiedValue as AnimationName;
|
||||
|
||||
let atom = self.gecko.mAnimations[index].mName.mRawPtr;
|
||||
if atom == atom!("").as_ptr() {
|
||||
return AnimationName(None)
|
||||
}
|
||||
AnimationName(Some(KeyframesName::from_atom(unsafe { Atom::from_raw(atom) })))
|
||||
}
|
||||
pub fn copy_animation_name_from(&mut self, other: &Self) {
|
||||
self.gecko.mAnimationNameCount = other.gecko.mAnimationNameCount;
|
||||
unsafe { bindings::Gecko_CopyAnimationNames(&mut self.gecko.mAnimations, &other.gecko.mAnimations); }
|
||||
}
|
||||
|
||||
pub fn reset_animation_name(&mut self, other: &Self) {
|
||||
self.copy_animation_name_from(other)
|
||||
}
|
||||
|
||||
${impl_animation_count('name', 'Name')}
|
||||
|
||||
${impl_animation_time_value('delay', 'Delay')}
|
||||
${impl_animation_time_value('duration', 'Duration')}
|
||||
|
||||
${impl_animation_keyword('direction', 'Direction',
|
||||
data.longhands_by_name["animation-direction"].keyword)}
|
||||
${impl_animation_keyword('fill_mode', 'FillMode',
|
||||
data.longhands_by_name["animation-fill-mode"].keyword)}
|
||||
${impl_animation_keyword('play_state', 'PlayState',
|
||||
data.longhands_by_name["animation-play-state"].keyword)}
|
||||
|
||||
pub fn set_animation_iteration_count<I>(&mut self, v: I)
|
||||
where
|
||||
I: IntoIterator<Item = values::computed::AnimationIterationCount>,
|
||||
I::IntoIter: ExactSizeIterator + Clone
|
||||
{
|
||||
use std::f32;
|
||||
use crate::values::generics::box_::AnimationIterationCount;
|
||||
|
||||
let v = v.into_iter();
|
||||
|
||||
debug_assert_ne!(v.len(), 0);
|
||||
let input_len = v.len();
|
||||
self.gecko.mAnimations.ensure_len(input_len);
|
||||
|
||||
self.gecko.mAnimationIterationCountCount = input_len as u32;
|
||||
for (gecko, servo) in self.gecko.mAnimations.iter_mut().take(input_len as usize).zip(v) {
|
||||
match servo {
|
||||
AnimationIterationCount::Number(n) => gecko.mIterationCount = n,
|
||||
AnimationIterationCount::Infinite => gecko.mIterationCount = f32::INFINITY,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn animation_iteration_count_at(
|
||||
&self,
|
||||
index: usize,
|
||||
) -> values::computed::AnimationIterationCount {
|
||||
use crate::values::generics::box_::AnimationIterationCount;
|
||||
|
||||
if self.gecko.mAnimations[index].mIterationCount.is_infinite() {
|
||||
AnimationIterationCount::Infinite
|
||||
} else {
|
||||
AnimationIterationCount::Number(self.gecko.mAnimations[index].mIterationCount)
|
||||
}
|
||||
}
|
||||
|
||||
${impl_animation_count('iteration_count', 'IterationCount')}
|
||||
${impl_copy_animation_value('iteration_count', 'IterationCount')}
|
||||
${impl_animation_or_transition_timing_function('animation')}
|
||||
|
||||
pub fn set_animation_timeline<I>(&mut self, v: I)
|
||||
where
|
||||
I: IntoIterator<Item = longhands::animation_timeline::computed_value::single_value::T>,
|
||||
I::IntoIter: ExactSizeIterator
|
||||
{
|
||||
let v = v.into_iter();
|
||||
debug_assert_ne!(v.len(), 0);
|
||||
let input_len = v.len();
|
||||
self.gecko.mAnimations.ensure_len(input_len);
|
||||
|
||||
self.gecko.mAnimationTimelineCount = input_len as u32;
|
||||
for (gecko, servo) in self.gecko.mAnimations.iter_mut().take(input_len as usize).zip(v) {
|
||||
gecko.mTimeline = servo;
|
||||
}
|
||||
}
|
||||
pub fn animation_timeline_at(&self, index: usize) -> values::specified::box_::AnimationTimeline {
|
||||
self.gecko.mAnimations[index].mTimeline.clone()
|
||||
}
|
||||
${impl_animation_count('timeline', 'Timeline')}
|
||||
${impl_copy_animation_value('timeline', 'Timeline')}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn set__webkit_line_clamp(&mut self, v: longhands::_webkit_line_clamp::computed_value::T) {
|
||||
self.gecko.mLineClamp = match v {
|
||||
|
@ -1776,247 +2020,7 @@ mask-mode mask-repeat mask-clip mask-origin mask-composite mask-position-x mask-
|
|||
}
|
||||
</%self:impl_trait>
|
||||
|
||||
<% skip_ui_longhands = """animation-name animation-delay animation-duration
|
||||
animation-direction animation-fill-mode
|
||||
animation-play-state animation-iteration-count
|
||||
animation-timeline animation-timing-function
|
||||
transition-duration transition-delay
|
||||
transition-timing-function transition-property""" %>
|
||||
|
||||
<%self:impl_trait style_struct_name="UI" skip_longhands="${skip_ui_longhands}">
|
||||
${impl_transition_time_value('delay', 'Delay')}
|
||||
${impl_transition_time_value('duration', 'Duration')}
|
||||
${impl_animation_or_transition_timing_function('transition')}
|
||||
|
||||
pub fn transition_combined_duration_at(&self, index: usize) -> f32 {
|
||||
// https://drafts.csswg.org/css-transitions/#transition-combined-duration
|
||||
self.gecko.mTransitions[index % self.gecko.mTransitionDurationCount as usize].mDuration.max(0.0)
|
||||
+ self.gecko.mTransitions[index % self.gecko.mTransitionDelayCount as usize].mDelay
|
||||
}
|
||||
|
||||
pub fn set_transition_property<I>(&mut self, v: I)
|
||||
where I: IntoIterator<Item = longhands::transition_property::computed_value::single_value::T>,
|
||||
I::IntoIter: ExactSizeIterator
|
||||
{
|
||||
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_no_properties;
|
||||
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_variable;
|
||||
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSProperty_UNKNOWN;
|
||||
|
||||
let v = v.into_iter();
|
||||
|
||||
if v.len() != 0 {
|
||||
self.gecko.mTransitions.ensure_len(v.len());
|
||||
self.gecko.mTransitionPropertyCount = v.len() as u32;
|
||||
for (servo, gecko) in v.zip(self.gecko.mTransitions.iter_mut()) {
|
||||
unsafe { gecko.mUnknownProperty.clear() };
|
||||
|
||||
match servo {
|
||||
TransitionProperty::Unsupported(ident) => {
|
||||
gecko.mProperty = eCSSProperty_UNKNOWN;
|
||||
gecko.mUnknownProperty.mRawPtr = ident.0.into_addrefed();
|
||||
},
|
||||
TransitionProperty::Custom(name) => {
|
||||
gecko.mProperty = eCSSPropertyExtra_variable;
|
||||
gecko.mUnknownProperty.mRawPtr = name.into_addrefed();
|
||||
}
|
||||
_ => gecko.mProperty = servo.to_nscsspropertyid().unwrap(),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// In gecko |none| is represented by eCSSPropertyExtra_no_properties.
|
||||
self.gecko.mTransitionPropertyCount = 1;
|
||||
self.gecko.mTransitions[0].mProperty = eCSSPropertyExtra_no_properties;
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether there are any transitions specified.
|
||||
pub fn specifies_transitions(&self) -> bool {
|
||||
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_all_properties;
|
||||
if self.gecko.mTransitionPropertyCount == 1 &&
|
||||
self.gecko.mTransitions[0].mProperty == eCSSPropertyExtra_all_properties &&
|
||||
self.transition_combined_duration_at(0) <= 0.0f32 {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.gecko.mTransitionPropertyCount > 0
|
||||
}
|
||||
|
||||
pub fn transition_property_at(&self, index: usize)
|
||||
-> longhands::transition_property::computed_value::SingleComputedValue {
|
||||
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_no_properties;
|
||||
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_variable;
|
||||
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSProperty_UNKNOWN;
|
||||
|
||||
let property = self.gecko.mTransitions[index].mProperty;
|
||||
if property == eCSSProperty_UNKNOWN {
|
||||
let atom = self.gecko.mTransitions[index].mUnknownProperty.mRawPtr;
|
||||
debug_assert!(!atom.is_null());
|
||||
TransitionProperty::Unsupported(CustomIdent(unsafe{
|
||||
Atom::from_raw(atom)
|
||||
}))
|
||||
} else if property == eCSSPropertyExtra_variable {
|
||||
let atom = self.gecko.mTransitions[index].mUnknownProperty.mRawPtr;
|
||||
debug_assert!(!atom.is_null());
|
||||
TransitionProperty::Custom(unsafe{
|
||||
Atom::from_raw(atom)
|
||||
})
|
||||
} else if property == eCSSPropertyExtra_no_properties {
|
||||
// Actually, we don't expect TransitionProperty::Unsupported also
|
||||
// represents "none", but if the caller wants to convert it, it is
|
||||
// fine. Please use it carefully.
|
||||
//
|
||||
// FIXME(emilio): This is a hack, is this reachable?
|
||||
TransitionProperty::Unsupported(CustomIdent(atom!("none")))
|
||||
} else {
|
||||
property.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn transition_nscsspropertyid_at(&self, index: usize) -> nsCSSPropertyID {
|
||||
self.gecko.mTransitions[index].mProperty
|
||||
}
|
||||
|
||||
pub fn copy_transition_property_from(&mut self, other: &Self) {
|
||||
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_variable;
|
||||
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSProperty_UNKNOWN;
|
||||
self.gecko.mTransitions.ensure_len(other.gecko.mTransitions.len());
|
||||
|
||||
let count = other.gecko.mTransitionPropertyCount;
|
||||
self.gecko.mTransitionPropertyCount = count;
|
||||
|
||||
for (index, transition) in self.gecko.mTransitions.iter_mut().enumerate().take(count as usize) {
|
||||
transition.mProperty = other.gecko.mTransitions[index].mProperty;
|
||||
unsafe { transition.mUnknownProperty.clear() };
|
||||
if transition.mProperty == eCSSProperty_UNKNOWN ||
|
||||
transition.mProperty == eCSSPropertyExtra_variable {
|
||||
let atom = other.gecko.mTransitions[index].mUnknownProperty.mRawPtr;
|
||||
debug_assert!(!atom.is_null());
|
||||
transition.mUnknownProperty.mRawPtr = unsafe { Atom::from_raw(atom) }.into_addrefed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset_transition_property(&mut self, other: &Self) {
|
||||
self.copy_transition_property_from(other)
|
||||
}
|
||||
|
||||
${impl_transition_count('property', 'Property')}
|
||||
|
||||
pub fn animations_equals(&self, other: &Self) -> bool {
|
||||
return self.gecko.mAnimationNameCount == other.gecko.mAnimationNameCount
|
||||
&& self.gecko.mAnimationDelayCount == other.gecko.mAnimationDelayCount
|
||||
&& self.gecko.mAnimationDirectionCount == other.gecko.mAnimationDirectionCount
|
||||
&& self.gecko.mAnimationDurationCount == other.gecko.mAnimationDurationCount
|
||||
&& self.gecko.mAnimationFillModeCount == other.gecko.mAnimationFillModeCount
|
||||
&& self.gecko.mAnimationIterationCountCount == other.gecko.mAnimationIterationCountCount
|
||||
&& self.gecko.mAnimationPlayStateCount == other.gecko.mAnimationPlayStateCount
|
||||
&& self.gecko.mAnimationTimingFunctionCount == other.gecko.mAnimationTimingFunctionCount
|
||||
&& unsafe { bindings::Gecko_StyleAnimationsEquals(&self.gecko.mAnimations, &other.gecko.mAnimations) }
|
||||
}
|
||||
|
||||
pub fn set_animation_name<I>(&mut self, v: I)
|
||||
where I: IntoIterator<Item = longhands::animation_name::computed_value::single_value::T>,
|
||||
I::IntoIter: ExactSizeIterator
|
||||
{
|
||||
let v = v.into_iter();
|
||||
debug_assert_ne!(v.len(), 0);
|
||||
self.gecko.mAnimations.ensure_len(v.len());
|
||||
|
||||
self.gecko.mAnimationNameCount = v.len() as u32;
|
||||
for (servo, gecko) in v.zip(self.gecko.mAnimations.iter_mut()) {
|
||||
let atom = servo.0.as_atom().clone();
|
||||
unsafe { bindings::Gecko_SetAnimationName(gecko, atom.into_addrefed()); }
|
||||
}
|
||||
}
|
||||
pub fn animation_name_at(&self, index: usize)
|
||||
-> longhands::animation_name::computed_value::SingleComputedValue {
|
||||
use crate::properties::longhands::animation_name::single_value::SpecifiedValue as AnimationName;
|
||||
|
||||
let atom = self.gecko.mAnimations[index].mName.mRawPtr;
|
||||
AnimationName(KeyframesName::from_atom(unsafe { Atom::from_raw(atom) }))
|
||||
}
|
||||
pub fn copy_animation_name_from(&mut self, other: &Self) {
|
||||
self.gecko.mAnimationNameCount = other.gecko.mAnimationNameCount;
|
||||
unsafe { bindings::Gecko_CopyAnimationNames(&mut self.gecko.mAnimations, &other.gecko.mAnimations); }
|
||||
}
|
||||
|
||||
pub fn reset_animation_name(&mut self, other: &Self) {
|
||||
self.copy_animation_name_from(other)
|
||||
}
|
||||
|
||||
${impl_animation_count('name', 'Name')}
|
||||
|
||||
${impl_animation_time_value('delay', 'Delay')}
|
||||
${impl_animation_time_value('duration', 'Duration')}
|
||||
|
||||
${impl_animation_keyword('direction', 'Direction',
|
||||
data.longhands_by_name["animation-direction"].keyword)}
|
||||
${impl_animation_keyword('fill_mode', 'FillMode',
|
||||
data.longhands_by_name["animation-fill-mode"].keyword)}
|
||||
${impl_animation_keyword('play_state', 'PlayState',
|
||||
data.longhands_by_name["animation-play-state"].keyword)}
|
||||
|
||||
pub fn set_animation_iteration_count<I>(&mut self, v: I)
|
||||
where
|
||||
I: IntoIterator<Item = values::computed::AnimationIterationCount>,
|
||||
I::IntoIter: ExactSizeIterator + Clone
|
||||
{
|
||||
use std::f32;
|
||||
use crate::values::generics::box_::AnimationIterationCount;
|
||||
|
||||
let v = v.into_iter();
|
||||
|
||||
debug_assert_ne!(v.len(), 0);
|
||||
let input_len = v.len();
|
||||
self.gecko.mAnimations.ensure_len(input_len);
|
||||
|
||||
self.gecko.mAnimationIterationCountCount = input_len as u32;
|
||||
for (gecko, servo) in self.gecko.mAnimations.iter_mut().take(input_len as usize).zip(v) {
|
||||
match servo {
|
||||
AnimationIterationCount::Number(n) => gecko.mIterationCount = n,
|
||||
AnimationIterationCount::Infinite => gecko.mIterationCount = f32::INFINITY,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn animation_iteration_count_at(
|
||||
&self,
|
||||
index: usize,
|
||||
) -> values::computed::AnimationIterationCount {
|
||||
use crate::values::generics::box_::AnimationIterationCount;
|
||||
|
||||
if self.gecko.mAnimations[index].mIterationCount.is_infinite() {
|
||||
AnimationIterationCount::Infinite
|
||||
} else {
|
||||
AnimationIterationCount::Number(self.gecko.mAnimations[index].mIterationCount)
|
||||
}
|
||||
}
|
||||
|
||||
${impl_animation_count('iteration_count', 'IterationCount')}
|
||||
${impl_copy_animation_value('iteration_count', 'IterationCount')}
|
||||
${impl_animation_or_transition_timing_function('animation')}
|
||||
|
||||
pub fn set_animation_timeline<I>(&mut self, v: I)
|
||||
where
|
||||
I: IntoIterator<Item = longhands::animation_timeline::computed_value::single_value::T>,
|
||||
I::IntoIter: ExactSizeIterator
|
||||
{
|
||||
let v = v.into_iter();
|
||||
debug_assert_ne!(v.len(), 0);
|
||||
let input_len = v.len();
|
||||
self.gecko.mAnimations.ensure_len(input_len);
|
||||
|
||||
self.gecko.mAnimationTimelineCount = input_len as u32;
|
||||
for (gecko, servo) in self.gecko.mAnimations.iter_mut().take(input_len as usize).zip(v) {
|
||||
gecko.mTimeline = servo;
|
||||
}
|
||||
}
|
||||
pub fn animation_timeline_at(&self, index: usize) -> values::specified::box_::AnimationTimeline {
|
||||
self.gecko.mAnimations[index].mTimeline.clone()
|
||||
}
|
||||
${impl_animation_count('timeline', 'Timeline')}
|
||||
${impl_copy_animation_value('timeline', 'Timeline')}
|
||||
|
||||
<%self:impl_trait style_struct_name="UI">
|
||||
</%self:impl_trait>
|
||||
|
||||
<%self:impl_trait style_struct_name="XUL">
|
||||
|
|
|
@ -799,7 +799,7 @@ impl<'a> TransitionPropertyIterator<'a> {
|
|||
pub fn from_style(style: &'a ComputedValues) -> Self {
|
||||
Self {
|
||||
style,
|
||||
index_range: 0..style.get_ui().transition_property_count(),
|
||||
index_range: 0..style.get_box().transition_property_count(),
|
||||
longhand_iterator: None,
|
||||
}
|
||||
}
|
||||
|
@ -832,7 +832,7 @@ impl<'a> Iterator for TransitionPropertyIterator<'a> {
|
|||
}
|
||||
|
||||
let index = self.index_range.next()?;
|
||||
match self.style.get_ui().transition_property_at(index) {
|
||||
match self.style.get_box().transition_property_at(index) {
|
||||
TransitionProperty::Longhand(longhand_id) => {
|
||||
return Some(TransitionPropertyIteration {
|
||||
longhand_id,
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
<%namespace name="helpers" file="/helpers.mako.rs" />
|
||||
<% from data import ALL_AXES, Keyword, Method, to_rust_ident, to_camel_case%>
|
||||
<% from data import ALL_AXES, DEFAULT_RULES_EXCEPT_KEYFRAME, Keyword, Method, to_rust_ident, to_camel_case%>
|
||||
|
||||
<% data.new_style_struct("Box",
|
||||
inherited=False,
|
||||
|
@ -150,6 +150,193 @@ ${helpers.predefined_type(
|
|||
animation_value_type="discrete",
|
||||
)}
|
||||
|
||||
<% transition_extra_prefixes = "moz:layout.css.prefixes.transitions webkit" %>
|
||||
|
||||
${helpers.predefined_type(
|
||||
"transition-duration",
|
||||
"Time",
|
||||
"computed::Time::zero()",
|
||||
engines="gecko servo",
|
||||
initial_specified_value="specified::Time::zero()",
|
||||
parse_method="parse_non_negative",
|
||||
vector=True,
|
||||
need_index=True,
|
||||
animation_value_type="none",
|
||||
extra_prefixes=transition_extra_prefixes,
|
||||
spec="https://drafts.csswg.org/css-transitions/#propdef-transition-duration",
|
||||
)}
|
||||
|
||||
${helpers.predefined_type(
|
||||
"transition-timing-function",
|
||||
"TimingFunction",
|
||||
"computed::TimingFunction::ease()",
|
||||
engines="gecko servo",
|
||||
initial_specified_value="specified::TimingFunction::ease()",
|
||||
vector=True,
|
||||
need_index=True,
|
||||
animation_value_type="none",
|
||||
extra_prefixes=transition_extra_prefixes,
|
||||
spec="https://drafts.csswg.org/css-transitions/#propdef-transition-timing-function",
|
||||
)}
|
||||
|
||||
${helpers.predefined_type(
|
||||
"transition-property",
|
||||
"TransitionProperty",
|
||||
"computed::TransitionProperty::all()",
|
||||
engines="gecko servo",
|
||||
initial_specified_value="specified::TransitionProperty::all()",
|
||||
vector=True,
|
||||
allow_empty="NotInitial",
|
||||
need_index=True,
|
||||
animation_value_type="none",
|
||||
extra_prefixes=transition_extra_prefixes,
|
||||
spec="https://drafts.csswg.org/css-transitions/#propdef-transition-property",
|
||||
)}
|
||||
|
||||
${helpers.predefined_type(
|
||||
"transition-delay",
|
||||
"Time",
|
||||
"computed::Time::zero()",
|
||||
engines="gecko servo",
|
||||
initial_specified_value="specified::Time::zero()",
|
||||
vector=True,
|
||||
need_index=True,
|
||||
animation_value_type="none",
|
||||
extra_prefixes=transition_extra_prefixes,
|
||||
spec="https://drafts.csswg.org/css-transitions/#propdef-transition-delay",
|
||||
)}
|
||||
|
||||
<% animation_extra_prefixes = "moz:layout.css.prefixes.animations webkit" %>
|
||||
|
||||
${helpers.predefined_type(
|
||||
"animation-name",
|
||||
"AnimationName",
|
||||
"computed::AnimationName::none()",
|
||||
engines="gecko servo",
|
||||
initial_specified_value="specified::AnimationName::none()",
|
||||
vector=True,
|
||||
need_index=True,
|
||||
animation_value_type="none",
|
||||
extra_prefixes=animation_extra_prefixes,
|
||||
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
|
||||
spec="https://drafts.csswg.org/css-animations/#propdef-animation-name",
|
||||
)}
|
||||
|
||||
${helpers.predefined_type(
|
||||
"animation-duration",
|
||||
"Time",
|
||||
"computed::Time::zero()",
|
||||
engines="gecko servo",
|
||||
initial_specified_value="specified::Time::zero()",
|
||||
parse_method="parse_non_negative",
|
||||
vector=True,
|
||||
need_index=True,
|
||||
animation_value_type="none",
|
||||
extra_prefixes=animation_extra_prefixes,
|
||||
spec="https://drafts.csswg.org/css-transitions/#propdef-transition-duration",
|
||||
)}
|
||||
|
||||
// animation-timing-function is the exception to the rule for allowed_in_keyframe_block:
|
||||
// https://drafts.csswg.org/css-animations/#keyframes
|
||||
${helpers.predefined_type(
|
||||
"animation-timing-function",
|
||||
"TimingFunction",
|
||||
"computed::TimingFunction::ease()",
|
||||
engines="gecko servo",
|
||||
initial_specified_value="specified::TimingFunction::ease()",
|
||||
vector=True,
|
||||
need_index=True,
|
||||
animation_value_type="none",
|
||||
extra_prefixes=animation_extra_prefixes,
|
||||
spec="https://drafts.csswg.org/css-transitions/#propdef-animation-timing-function",
|
||||
)}
|
||||
|
||||
${helpers.predefined_type(
|
||||
"animation-iteration-count",
|
||||
"AnimationIterationCount",
|
||||
"computed::AnimationIterationCount::one()",
|
||||
engines="gecko servo",
|
||||
initial_specified_value="specified::AnimationIterationCount::one()",
|
||||
vector=True,
|
||||
need_index=True,
|
||||
animation_value_type="none",
|
||||
extra_prefixes=animation_extra_prefixes,
|
||||
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
|
||||
spec="https://drafts.csswg.org/css-animations/#propdef-animation-iteration-count",
|
||||
)}
|
||||
|
||||
<% animation_direction_custom_consts = { "alternate-reverse": "Alternate_reverse" } %>
|
||||
${helpers.single_keyword(
|
||||
"animation-direction",
|
||||
"normal reverse alternate alternate-reverse",
|
||||
engines="gecko servo",
|
||||
need_index=True,
|
||||
animation_value_type="none",
|
||||
vector=True,
|
||||
gecko_enum_prefix="PlaybackDirection",
|
||||
custom_consts=animation_direction_custom_consts,
|
||||
extra_prefixes=animation_extra_prefixes,
|
||||
gecko_inexhaustive=True,
|
||||
spec="https://drafts.csswg.org/css-animations/#propdef-animation-direction",
|
||||
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
|
||||
)}
|
||||
|
||||
${helpers.single_keyword(
|
||||
"animation-play-state",
|
||||
"running paused",
|
||||
engines="gecko servo",
|
||||
need_index=True,
|
||||
animation_value_type="none",
|
||||
vector=True,
|
||||
extra_prefixes=animation_extra_prefixes,
|
||||
gecko_enum_prefix="StyleAnimationPlayState",
|
||||
spec="https://drafts.csswg.org/css-animations/#propdef-animation-play-state",
|
||||
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
|
||||
)}
|
||||
|
||||
${helpers.single_keyword(
|
||||
"animation-fill-mode",
|
||||
"none forwards backwards both",
|
||||
engines="gecko servo",
|
||||
need_index=True,
|
||||
animation_value_type="none",
|
||||
vector=True,
|
||||
gecko_enum_prefix="FillMode",
|
||||
extra_prefixes=animation_extra_prefixes,
|
||||
gecko_inexhaustive=True,
|
||||
spec="https://drafts.csswg.org/css-animations/#propdef-animation-fill-mode",
|
||||
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
|
||||
)}
|
||||
|
||||
${helpers.predefined_type(
|
||||
"animation-delay",
|
||||
"Time",
|
||||
"computed::Time::zero()",
|
||||
engines="gecko servo",
|
||||
initial_specified_value="specified::Time::zero()",
|
||||
vector=True,
|
||||
need_index=True,
|
||||
animation_value_type="none",
|
||||
extra_prefixes=animation_extra_prefixes,
|
||||
spec="https://drafts.csswg.org/css-animations/#propdef-animation-delay",
|
||||
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
|
||||
)}
|
||||
|
||||
${helpers.predefined_type(
|
||||
"animation-timeline",
|
||||
"AnimationTimeline",
|
||||
"computed::AnimationTimeline::auto()",
|
||||
engines="gecko servo",
|
||||
servo_pref="layout.unimplemented",
|
||||
initial_specified_value="specified::AnimationTimeline::auto()",
|
||||
vector=True,
|
||||
need_index=True,
|
||||
animation_value_type="none",
|
||||
gecko_pref="layout.css.scroll-linked-animations.enabled",
|
||||
spec="https://drafts.csswg.org/css-animations-2/#propdef-animation-timeline",
|
||||
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
|
||||
)}
|
||||
|
||||
<% transform_extra_prefixes = "moz:layout.css.prefixes.transforms webkit" %>
|
||||
|
||||
${helpers.predefined_type(
|
||||
|
@ -285,15 +472,6 @@ ${helpers.predefined_type(
|
|||
animation_value_type="discrete",
|
||||
)}
|
||||
|
||||
${helpers.predefined_type(
|
||||
"scroll-snap-stop",
|
||||
"ScrollSnapStop",
|
||||
"computed::ScrollSnapStop::Normal",
|
||||
engines="gecko",
|
||||
spec="https://drafts.csswg.org/css-scroll-snap-1/#scroll-snap-stop",
|
||||
animation_value_type="discrete",
|
||||
)}
|
||||
|
||||
% for (axis, logical) in ALL_AXES:
|
||||
${helpers.predefined_type(
|
||||
"overscroll-behavior-" + axis,
|
||||
|
@ -445,28 +623,6 @@ ${helpers.predefined_type(
|
|||
animation_value_type="none",
|
||||
)}
|
||||
|
||||
${helpers.predefined_type(
|
||||
"container-type",
|
||||
"ContainerType",
|
||||
"computed::ContainerType::NONE",
|
||||
engines="gecko servo",
|
||||
animation_value_type="none",
|
||||
gecko_pref="layout.css.container-queries.enabled",
|
||||
servo_pref="layout.container-queries.enabled",
|
||||
spec="https://drafts.csswg.org/css-contain-3/#container-type",
|
||||
)}
|
||||
|
||||
${helpers.predefined_type(
|
||||
"container-name",
|
||||
"ContainerName",
|
||||
"computed::ContainerName::none()",
|
||||
engines="gecko servo",
|
||||
animation_value_type="none",
|
||||
gecko_pref="layout.css.container-queries.enabled",
|
||||
servo_pref="layout.container-queries.enabled",
|
||||
spec="https://drafts.csswg.org/css-contain-3/#container-name",
|
||||
)}
|
||||
|
||||
${helpers.predefined_type(
|
||||
"appearance",
|
||||
"Appearance",
|
||||
|
|
|
@ -28,16 +28,6 @@
|
|||
)}
|
||||
% endfor
|
||||
|
||||
${helpers.predefined_type(
|
||||
"overflow-clip-margin",
|
||||
"Length",
|
||||
"computed::Length::zero()",
|
||||
parse_method="parse_non_negative",
|
||||
engines="gecko",
|
||||
spec="https://drafts.csswg.org/css-overflow/#propdef-overflow-clip-margin",
|
||||
animation_value_type="ComputedValue",
|
||||
)}
|
||||
|
||||
% for side in ALL_SIDES:
|
||||
${helpers.predefined_type(
|
||||
"scroll-margin-%s" % side[0],
|
||||
|
|
|
@ -20,7 +20,7 @@ ${helpers.single_keyword(
|
|||
${helpers.predefined_type(
|
||||
"stop-color",
|
||||
"Color",
|
||||
"computed::Color::black()",
|
||||
"RGBA::new(0, 0, 0, 255).into()",
|
||||
engines="gecko",
|
||||
animation_value_type="AnimatedRGBA",
|
||||
spec="https://www.w3.org/TR/SVGTiny12/painting.html#StopColorProperty",
|
||||
|
@ -40,7 +40,7 @@ ${helpers.predefined_type(
|
|||
${helpers.predefined_type(
|
||||
"flood-color",
|
||||
"Color",
|
||||
"computed::Color::black()",
|
||||
"RGBA::new(0, 0, 0, 255).into()",
|
||||
engines="gecko",
|
||||
animation_value_type="AnimatedColor",
|
||||
spec="https://www.w3.org/TR/SVG/filters.html#FloodColorProperty",
|
||||
|
@ -58,7 +58,7 @@ ${helpers.predefined_type(
|
|||
${helpers.predefined_type(
|
||||
"lighting-color",
|
||||
"Color",
|
||||
"computed::Color::white()",
|
||||
"RGBA::new(255, 255, 255, 255).into()",
|
||||
engines="gecko",
|
||||
animation_value_type="AnimatedColor",
|
||||
spec="https://www.w3.org/TR/SVG/filters.html#LightingColorProperty",
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
<%namespace name="helpers" file="/helpers.mako.rs" />
|
||||
<% from data import DEFAULT_RULES_EXCEPT_KEYFRAME, Method %>
|
||||
<% from data import Method %>
|
||||
|
||||
// CSS Basic User Interface Module Level 1
|
||||
// https://drafts.csswg.org/css-ui-3/
|
||||
|
@ -51,15 +51,12 @@ ${helpers.single_keyword(
|
|||
spec="None (Nonstandard Firefox-only property)",
|
||||
)}
|
||||
|
||||
// TODO(emilio): Maybe make shadow behavior on macOS match Linux / Windows, and remove this
|
||||
// property.
|
||||
${helpers.single_keyword(
|
||||
"-moz-window-shadow",
|
||||
"default none",
|
||||
"default none menu tooltip sheet cliprounded",
|
||||
engines="gecko",
|
||||
gecko_ffi_name="mWindowShadow",
|
||||
gecko_enum_prefix="StyleWindowShadow",
|
||||
gecko_inexhaustive=True,
|
||||
animation_value_type="discrete",
|
||||
enabled_in="chrome",
|
||||
spec="None (Nonstandard internal property)",
|
||||
|
@ -98,16 +95,6 @@ ${helpers.predefined_type(
|
|||
enabled_in="chrome",
|
||||
)}
|
||||
|
||||
${helpers.predefined_type(
|
||||
"-moz-window-input-region-margin",
|
||||
"Length",
|
||||
"computed::Length::zero()",
|
||||
engines="gecko",
|
||||
animation_value_type="ComputedValue",
|
||||
spec="None (Nonstandard internal property)",
|
||||
enabled_in="chrome",
|
||||
)}
|
||||
|
||||
// TODO(emilio): Probably also should be hidden from content.
|
||||
${helpers.predefined_type(
|
||||
"-moz-force-broken-image-icon",
|
||||
|
@ -117,190 +104,3 @@ ${helpers.predefined_type(
|
|||
animation_value_type="discrete",
|
||||
spec="None (Nonstandard Firefox-only property)",
|
||||
)}
|
||||
|
||||
<% transition_extra_prefixes = "moz:layout.css.prefixes.transitions webkit" %>
|
||||
|
||||
${helpers.predefined_type(
|
||||
"transition-duration",
|
||||
"Time",
|
||||
"computed::Time::zero()",
|
||||
engines="gecko servo",
|
||||
initial_specified_value="specified::Time::zero()",
|
||||
parse_method="parse_non_negative",
|
||||
vector=True,
|
||||
need_index=True,
|
||||
animation_value_type="none",
|
||||
extra_prefixes=transition_extra_prefixes,
|
||||
spec="https://drafts.csswg.org/css-transitions/#propdef-transition-duration",
|
||||
)}
|
||||
|
||||
${helpers.predefined_type(
|
||||
"transition-timing-function",
|
||||
"TimingFunction",
|
||||
"computed::TimingFunction::ease()",
|
||||
engines="gecko servo",
|
||||
initial_specified_value="specified::TimingFunction::ease()",
|
||||
vector=True,
|
||||
need_index=True,
|
||||
animation_value_type="none",
|
||||
extra_prefixes=transition_extra_prefixes,
|
||||
spec="https://drafts.csswg.org/css-transitions/#propdef-transition-timing-function",
|
||||
)}
|
||||
|
||||
${helpers.predefined_type(
|
||||
"transition-property",
|
||||
"TransitionProperty",
|
||||
"computed::TransitionProperty::all()",
|
||||
engines="gecko servo",
|
||||
initial_specified_value="specified::TransitionProperty::all()",
|
||||
vector=True,
|
||||
allow_empty="NotInitial",
|
||||
need_index=True,
|
||||
animation_value_type="none",
|
||||
extra_prefixes=transition_extra_prefixes,
|
||||
spec="https://drafts.csswg.org/css-transitions/#propdef-transition-property",
|
||||
)}
|
||||
|
||||
${helpers.predefined_type(
|
||||
"transition-delay",
|
||||
"Time",
|
||||
"computed::Time::zero()",
|
||||
engines="gecko servo",
|
||||
initial_specified_value="specified::Time::zero()",
|
||||
vector=True,
|
||||
need_index=True,
|
||||
animation_value_type="none",
|
||||
extra_prefixes=transition_extra_prefixes,
|
||||
spec="https://drafts.csswg.org/css-transitions/#propdef-transition-delay",
|
||||
)}
|
||||
|
||||
<% animation_extra_prefixes = "moz:layout.css.prefixes.animations webkit" %>
|
||||
|
||||
${helpers.predefined_type(
|
||||
"animation-name",
|
||||
"AnimationName",
|
||||
"computed::AnimationName::none()",
|
||||
engines="gecko servo",
|
||||
initial_specified_value="specified::AnimationName::none()",
|
||||
vector=True,
|
||||
need_index=True,
|
||||
animation_value_type="none",
|
||||
extra_prefixes=animation_extra_prefixes,
|
||||
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
|
||||
spec="https://drafts.csswg.org/css-animations/#propdef-animation-name",
|
||||
)}
|
||||
|
||||
${helpers.predefined_type(
|
||||
"animation-duration",
|
||||
"Time",
|
||||
"computed::Time::zero()",
|
||||
engines="gecko servo",
|
||||
initial_specified_value="specified::Time::zero()",
|
||||
parse_method="parse_non_negative",
|
||||
vector=True,
|
||||
need_index=True,
|
||||
animation_value_type="none",
|
||||
extra_prefixes=animation_extra_prefixes,
|
||||
spec="https://drafts.csswg.org/css-transitions/#propdef-transition-duration",
|
||||
)}
|
||||
|
||||
// animation-timing-function is the exception to the rule for allowed_in_keyframe_block:
|
||||
// https://drafts.csswg.org/css-animations/#keyframes
|
||||
${helpers.predefined_type(
|
||||
"animation-timing-function",
|
||||
"TimingFunction",
|
||||
"computed::TimingFunction::ease()",
|
||||
engines="gecko servo",
|
||||
initial_specified_value="specified::TimingFunction::ease()",
|
||||
vector=True,
|
||||
need_index=True,
|
||||
animation_value_type="none",
|
||||
extra_prefixes=animation_extra_prefixes,
|
||||
spec="https://drafts.csswg.org/css-transitions/#propdef-animation-timing-function",
|
||||
)}
|
||||
|
||||
${helpers.predefined_type(
|
||||
"animation-iteration-count",
|
||||
"AnimationIterationCount",
|
||||
"computed::AnimationIterationCount::one()",
|
||||
engines="gecko servo",
|
||||
initial_specified_value="specified::AnimationIterationCount::one()",
|
||||
vector=True,
|
||||
need_index=True,
|
||||
animation_value_type="none",
|
||||
extra_prefixes=animation_extra_prefixes,
|
||||
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
|
||||
spec="https://drafts.csswg.org/css-animations/#propdef-animation-iteration-count",
|
||||
)}
|
||||
|
||||
<% animation_direction_custom_consts = { "alternate-reverse": "Alternate_reverse" } %>
|
||||
${helpers.single_keyword(
|
||||
"animation-direction",
|
||||
"normal reverse alternate alternate-reverse",
|
||||
engines="gecko servo",
|
||||
need_index=True,
|
||||
animation_value_type="none",
|
||||
vector=True,
|
||||
gecko_enum_prefix="PlaybackDirection",
|
||||
custom_consts=animation_direction_custom_consts,
|
||||
extra_prefixes=animation_extra_prefixes,
|
||||
gecko_inexhaustive=True,
|
||||
spec="https://drafts.csswg.org/css-animations/#propdef-animation-direction",
|
||||
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
|
||||
)}
|
||||
|
||||
${helpers.single_keyword(
|
||||
"animation-play-state",
|
||||
"running paused",
|
||||
engines="gecko servo",
|
||||
need_index=True,
|
||||
animation_value_type="none",
|
||||
vector=True,
|
||||
extra_prefixes=animation_extra_prefixes,
|
||||
gecko_enum_prefix="StyleAnimationPlayState",
|
||||
spec="https://drafts.csswg.org/css-animations/#propdef-animation-play-state",
|
||||
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
|
||||
)}
|
||||
|
||||
${helpers.single_keyword(
|
||||
"animation-fill-mode",
|
||||
"none forwards backwards both",
|
||||
engines="gecko servo",
|
||||
need_index=True,
|
||||
animation_value_type="none",
|
||||
vector=True,
|
||||
gecko_enum_prefix="FillMode",
|
||||
extra_prefixes=animation_extra_prefixes,
|
||||
gecko_inexhaustive=True,
|
||||
spec="https://drafts.csswg.org/css-animations/#propdef-animation-fill-mode",
|
||||
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
|
||||
)}
|
||||
|
||||
${helpers.predefined_type(
|
||||
"animation-delay",
|
||||
"Time",
|
||||
"computed::Time::zero()",
|
||||
engines="gecko servo",
|
||||
initial_specified_value="specified::Time::zero()",
|
||||
vector=True,
|
||||
need_index=True,
|
||||
animation_value_type="none",
|
||||
extra_prefixes=animation_extra_prefixes,
|
||||
spec="https://drafts.csswg.org/css-animations/#propdef-animation-delay",
|
||||
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
|
||||
)}
|
||||
|
||||
${helpers.predefined_type(
|
||||
"animation-timeline",
|
||||
"AnimationTimeline",
|
||||
"computed::AnimationTimeline::auto()",
|
||||
engines="gecko servo",
|
||||
servo_pref="layout.unimplemented",
|
||||
initial_specified_value="specified::AnimationTimeline::auto()",
|
||||
vector=True,
|
||||
need_index=True,
|
||||
animation_value_type="none",
|
||||
gecko_pref="layout.css.scroll-linked-animations.enabled",
|
||||
spec="https://drafts.csswg.org/css-animations-2/#propdef-animation-timeline",
|
||||
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
|
||||
)}
|
||||
|
|
|
@ -267,9 +267,6 @@ pub enum PropertyDeclaration {
|
|||
% endfor
|
||||
}
|
||||
|
||||
// There's one of these for each parsed declaration so it better be small.
|
||||
size_of_test!(PropertyDeclaration, 32);
|
||||
|
||||
#[repr(C)]
|
||||
struct PropertyDeclarationVariantRepr<T> {
|
||||
tag: u16,
|
||||
|
@ -477,10 +474,9 @@ impl NonCustomPropertyId {
|
|||
self.0
|
||||
}
|
||||
|
||||
/// Convert a `NonCustomPropertyId` into a `nsCSSPropertyID`.
|
||||
#[cfg(feature = "gecko")]
|
||||
#[inline]
|
||||
pub fn to_nscsspropertyid(self) -> nsCSSPropertyID {
|
||||
fn to_nscsspropertyid(self) -> nsCSSPropertyID {
|
||||
// unsafe: guaranteed by static_assert_nscsspropertyid above.
|
||||
unsafe { std::mem::transmute(self.0 as i32) }
|
||||
}
|
||||
|
@ -896,72 +892,6 @@ impl<'a> Iterator for LonghandIdSetIterator<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
<%
|
||||
|
||||
CASCADE_GROUPS = {
|
||||
# The writing-mode group has the most priority of all property groups, as
|
||||
# sizes like font-size can depend on it.
|
||||
"writing_mode": [
|
||||
"writing-mode",
|
||||
"direction",
|
||||
"text-orientation",
|
||||
],
|
||||
# The fonts and colors group has the second priority, as all other lengths
|
||||
# and colors depend on them.
|
||||
#
|
||||
# There are some interdependencies between these, but we fix them up in
|
||||
# Cascade::fixup_font_stuff.
|
||||
"fonts_and_color": [
|
||||
# Needed to properly compute the zoomed font-size.
|
||||
# FIXME(emilio): This could probably just be a cascade flag
|
||||
# like IN_SVG_SUBTREE or such, and we could nuke this property.
|
||||
"-x-text-zoom",
|
||||
# Needed to do font-size computation in a language-dependent way.
|
||||
"-x-lang",
|
||||
# Needed for ruby to respect language-dependent min-font-size
|
||||
# preferences properly, see bug 1165538.
|
||||
"-moz-min-font-size-ratio",
|
||||
# font-size depends on math-depth's computed value.
|
||||
"math-depth",
|
||||
# Needed to compute the first available font, in order to
|
||||
# compute font-relative units correctly.
|
||||
"font-size",
|
||||
"font-weight",
|
||||
"font-stretch",
|
||||
"font-style",
|
||||
"font-family",
|
||||
# color-scheme affects how system colors resolve.
|
||||
"color-scheme",
|
||||
],
|
||||
}
|
||||
def in_late_group(p):
|
||||
return p.name not in CASCADE_GROUPS["writing_mode"] and p.name not in CASCADE_GROUPS["fonts_and_color"]
|
||||
|
||||
def is_visited_dependent(p):
|
||||
return p.name in [
|
||||
"column-rule-color",
|
||||
"text-emphasis-color",
|
||||
"-webkit-text-fill-color",
|
||||
"-webkit-text-stroke-color",
|
||||
"text-decoration-color",
|
||||
"fill",
|
||||
"stroke",
|
||||
"caret-color",
|
||||
"background-color",
|
||||
"border-top-color",
|
||||
"border-right-color",
|
||||
"border-bottom-color",
|
||||
"border-left-color",
|
||||
"border-block-start-color",
|
||||
"border-inline-end-color",
|
||||
"border-block-end-color",
|
||||
"border-inline-start-color",
|
||||
"outline-color",
|
||||
"color",
|
||||
]
|
||||
|
||||
%>
|
||||
|
||||
impl LonghandIdSet {
|
||||
#[inline]
|
||||
fn reset() -> &'static Self {
|
||||
|
@ -990,7 +920,7 @@ impl LonghandIdSet {
|
|||
/// Returns the set of longhands that are ignored when document colors are
|
||||
/// disabled.
|
||||
#[inline]
|
||||
fn ignored_when_colors_disabled() -> &'static Self {
|
||||
pub fn ignored_when_colors_disabled() -> &'static Self {
|
||||
${static_longhand_id_set(
|
||||
"IGNORED_WHEN_COLORS_DISABLED",
|
||||
lambda p: p.ignored_when_colors_disabled
|
||||
|
@ -998,48 +928,6 @@ impl LonghandIdSet {
|
|||
&IGNORED_WHEN_COLORS_DISABLED
|
||||
}
|
||||
|
||||
/// Only a few properties are allowed to depend on the visited state of
|
||||
/// links. When cascading visited styles, we can save time by only
|
||||
/// processing these properties.
|
||||
fn visited_dependent() -> &'static Self {
|
||||
${static_longhand_id_set(
|
||||
"VISITED_DEPENDENT",
|
||||
lambda p: is_visited_dependent(p)
|
||||
)}
|
||||
debug_assert!(Self::late_group().contains_all(&VISITED_DEPENDENT));
|
||||
&VISITED_DEPENDENT
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn writing_mode_group() -> &'static Self {
|
||||
${static_longhand_id_set(
|
||||
"WRITING_MODE_GROUP",
|
||||
lambda p: p.name in CASCADE_GROUPS["writing_mode"]
|
||||
)}
|
||||
&WRITING_MODE_GROUP
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn fonts_and_color_group() -> &'static Self {
|
||||
${static_longhand_id_set(
|
||||
"FONTS_AND_COLOR_GROUP",
|
||||
lambda p: p.name in CASCADE_GROUPS["fonts_and_color"]
|
||||
)}
|
||||
&FONTS_AND_COLOR_GROUP
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn late_group_only_inherited() -> &'static Self {
|
||||
${static_longhand_id_set("LATE_GROUP_ONLY_INHERITED", lambda p: p.style_struct.inherited and in_late_group(p))}
|
||||
&LATE_GROUP_ONLY_INHERITED
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn late_group() -> &'static Self {
|
||||
${static_longhand_id_set("LATE_GROUP", lambda p: in_late_group(p))}
|
||||
&LATE_GROUP
|
||||
}
|
||||
|
||||
/// Returns the set of properties that are declared as having no effect on
|
||||
/// Gecko <scrollbar> elements or their descendant scrollbar parts.
|
||||
#[cfg(debug_assertions)]
|
||||
|
@ -1442,12 +1330,91 @@ impl LonghandId {
|
|||
PropertyFlags::from_bits_truncate(FLAGS[self as usize])
|
||||
}
|
||||
|
||||
/// Only a few properties are allowed to depend on the visited state of
|
||||
/// links. When cascading visited styles, we can save time by only
|
||||
/// processing these properties.
|
||||
fn is_visited_dependent(&self) -> bool {
|
||||
matches!(*self,
|
||||
% if engine == "gecko":
|
||||
LonghandId::ColumnRuleColor |
|
||||
LonghandId::TextEmphasisColor |
|
||||
LonghandId::WebkitTextFillColor |
|
||||
LonghandId::WebkitTextStrokeColor |
|
||||
LonghandId::TextDecorationColor |
|
||||
LonghandId::Fill |
|
||||
LonghandId::Stroke |
|
||||
LonghandId::CaretColor |
|
||||
% endif
|
||||
LonghandId::BackgroundColor |
|
||||
LonghandId::BorderTopColor |
|
||||
LonghandId::BorderRightColor |
|
||||
LonghandId::BorderBottomColor |
|
||||
LonghandId::BorderLeftColor |
|
||||
LonghandId::OutlineColor |
|
||||
LonghandId::Color
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns true if the property is one that is ignored when document
|
||||
/// colors are disabled.
|
||||
#[inline]
|
||||
fn ignored_when_document_colors_disabled(self) -> bool {
|
||||
LonghandIdSet::ignored_when_colors_disabled().contains(self)
|
||||
}
|
||||
|
||||
/// The computed value of some properties depends on the (sometimes
|
||||
/// computed) value of *other* properties.
|
||||
///
|
||||
/// So we classify properties into "early" and "other", such that the only
|
||||
/// dependencies can be from "other" to "early".
|
||||
///
|
||||
/// Unfortunately, it’s not easy to check that this classification is
|
||||
/// correct.
|
||||
fn is_early_property(&self) -> bool {
|
||||
matches!(*self,
|
||||
% if engine == "gecko":
|
||||
|
||||
// Needed to properly compute the writing mode, to resolve logical
|
||||
// properties, and similar stuff. In this block instead of along
|
||||
// `WritingMode` and `Direction` just for convenience, since it's
|
||||
// Gecko-only (for now at least).
|
||||
//
|
||||
// see WritingMode::new.
|
||||
LonghandId::TextOrientation |
|
||||
|
||||
// Needed to properly compute the zoomed font-size.
|
||||
//
|
||||
// FIXME(emilio): This could probably just be a cascade flag like
|
||||
// IN_SVG_SUBTREE or such, and we could nuke this property.
|
||||
LonghandId::XTextZoom |
|
||||
|
||||
// Needed to do font-size computation in a language-dependent way.
|
||||
LonghandId::XLang |
|
||||
// Needed for ruby to respect language-dependent min-font-size
|
||||
// preferences properly, see bug 1165538.
|
||||
LonghandId::MozMinFontSizeRatio |
|
||||
|
||||
// font-size depends on math-depth's computed value.
|
||||
LonghandId::MathDepth |
|
||||
|
||||
// color-scheme affects how system colors resolve.
|
||||
LonghandId::ColorScheme |
|
||||
% endif
|
||||
|
||||
// Needed to compute the first available font, in order to
|
||||
// compute font-relative units correctly.
|
||||
LonghandId::FontSize |
|
||||
LonghandId::FontWeight |
|
||||
LonghandId::FontStretch |
|
||||
LonghandId::FontStyle |
|
||||
LonghandId::FontFamily |
|
||||
|
||||
// Needed to properly compute the writing mode, to resolve logical
|
||||
// properties, and similar stuff.
|
||||
LonghandId::WritingMode |
|
||||
LonghandId::Direction
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over all the property ids that are enabled for a given
|
||||
|
@ -2593,11 +2560,9 @@ impl PropertyDeclaration {
|
|||
}
|
||||
}
|
||||
|
||||
const SUB_PROPERTIES_ARRAY_CAP: usize =
|
||||
${max(len(s.sub_properties) for s in data.shorthands_except_all()) \
|
||||
if data.shorthands_except_all() else 0};
|
||||
|
||||
type SubpropertiesVec<T> = ArrayVec<T, SUB_PROPERTIES_ARRAY_CAP>;
|
||||
type SubpropertiesVec<T> = ArrayVec<T, ${max(len(s.sub_properties) \
|
||||
for s in data.shorthands_except_all()) \
|
||||
if data.shorthands_except_all() else 0}>;
|
||||
|
||||
/// A stack-allocated vector of `PropertyDeclaration`
|
||||
/// large enough to parse one CSS `key: value` declaration.
|
||||
|
@ -2609,10 +2574,6 @@ pub struct SourcePropertyDeclaration {
|
|||
all_shorthand: AllShorthand,
|
||||
}
|
||||
|
||||
// This is huge, but we allocate it on the stack and then never move it,
|
||||
// we only pass `&mut SourcePropertyDeclaration` references around.
|
||||
size_of_test!(SourcePropertyDeclaration, 568);
|
||||
|
||||
impl SourcePropertyDeclaration {
|
||||
/// Create one. It’s big, try not to move it around.
|
||||
#[inline]
|
||||
|
@ -2657,7 +2618,11 @@ impl SourcePropertyDeclaration {
|
|||
|
||||
/// Return type of SourcePropertyDeclaration::drain
|
||||
pub struct SourcePropertyDeclarationDrain<'a> {
|
||||
declarations: ArrayVecDrain<'a, PropertyDeclaration, SUB_PROPERTIES_ARRAY_CAP>,
|
||||
declarations: ArrayVecDrain<
|
||||
'a, PropertyDeclaration,
|
||||
${max(len(s.sub_properties) for s in data.shorthands_except_all()) \
|
||||
if data.shorthands_except_all() else 0}
|
||||
>,
|
||||
all_shorthand: AllShorthand,
|
||||
}
|
||||
|
||||
|
@ -2938,11 +2903,11 @@ pub mod style_structs {
|
|||
% endif
|
||||
% endfor
|
||||
|
||||
% if style_struct.name == "UI":
|
||||
% if style_struct.name == "Box":
|
||||
/// Returns whether there is any animation specified with
|
||||
/// animation-name other than `none`.
|
||||
pub fn specifies_animations(&self) -> bool {
|
||||
self.animation_name_iter().any(|name| !name.is_none())
|
||||
self.animation_name_iter().any(|name| name.0.is_some())
|
||||
}
|
||||
|
||||
/// Returns whether there are any transitions specified.
|
||||
|
@ -3070,7 +3035,7 @@ impl ComputedValues {
|
|||
|
||||
/// Returns whether this style's display value is equal to contents.
|
||||
pub fn is_display_contents(&self) -> bool {
|
||||
self.clone_display().is_contents()
|
||||
self.get_box().clone_display().is_contents()
|
||||
}
|
||||
|
||||
/// Gets a reference to the rule node. Panic if no rule node exists.
|
||||
|
@ -3219,7 +3184,7 @@ impl ComputedValues {
|
|||
/// style.resolve_color(style.get_border().clone_border_top_color());
|
||||
#[inline]
|
||||
pub fn resolve_color(&self, color: computed::Color) -> RGBA {
|
||||
color.into_rgba(self.get_inherited_text().clone_color())
|
||||
color.to_rgba(self.get_inherited_text().clone_color())
|
||||
}
|
||||
|
||||
/// Returns which longhand properties have different values in the two
|
||||
|
@ -4223,7 +4188,7 @@ macro_rules! css_properties_accessors {
|
|||
/// Call the given macro with tokens like this for each longhand properties:
|
||||
///
|
||||
/// ```
|
||||
/// { snake_case_ident }
|
||||
/// { snake_case_ident, true }
|
||||
/// ```
|
||||
///
|
||||
/// … where the boolean indicates whether the property value type
|
||||
|
@ -4233,39 +4198,12 @@ macro_rules! longhand_properties_idents {
|
|||
($macro_name: ident) => {
|
||||
$macro_name! {
|
||||
% for property in data.longhands:
|
||||
{ ${property.ident} }
|
||||
{ ${property.ident}, ${"true" if property.boxed else "false"} }
|
||||
% endfor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Large pages generate tens of thousands of ComputedValues.
|
||||
size_of_test!(ComputedValues, 192);
|
||||
// FFI relies on this.
|
||||
size_of_test!(Option<Arc<ComputedValues>>, 8);
|
||||
|
||||
// There are two reasons for this test to fail:
|
||||
//
|
||||
// * Your changes made a specified value type for a given property go
|
||||
// over the threshold. In that case, you should try to shrink it again
|
||||
// or, if not possible, mark the property as boxed in the property
|
||||
// definition.
|
||||
//
|
||||
// * Your changes made a specified value type smaller, so that it no
|
||||
// longer needs to be boxed. In this case you just need to remove
|
||||
// boxed=True from the property definition. Nice job!
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
#[allow(dead_code)] // https://github.com/rust-lang/rust/issues/96952
|
||||
const BOX_THRESHOLD: usize = 24;
|
||||
% for longhand in data.longhands:
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
% if longhand.boxed:
|
||||
const_assert!(std::mem::size_of::<longhands::${longhand.ident}::SpecifiedValue>() > BOX_THRESHOLD);
|
||||
% else:
|
||||
const_assert!(std::mem::size_of::<longhands::${longhand.ident}::SpecifiedValue>() <= BOX_THRESHOLD);
|
||||
% endif
|
||||
% endfor
|
||||
|
||||
% if engine == "servo":
|
||||
% for effect_name in ["repaint", "reflow_out_of_flow", "reflow", "rebuild_and_reflow_inline", "rebuild_and_reflow"]:
|
||||
macro_rules! restyle_damage_${effect_name} {
|
||||
|
|
|
@ -24,6 +24,318 @@ ${helpers.two_properties_shorthand(
|
|||
"(https://developer.mozilla.org/en-US/docs/Web/CSS/overflow-clip-box)",
|
||||
)}
|
||||
|
||||
macro_rules! try_parse_one {
|
||||
($context: expr, $input: expr, $var: ident, $prop_module: ident) => {
|
||||
if $var.is_none() {
|
||||
if let Ok(value) = $input.try_parse(|i| {
|
||||
$prop_module::single_value::parse($context, i)
|
||||
}) {
|
||||
$var = Some(value);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
<%helpers:shorthand name="transition"
|
||||
engines="gecko servo"
|
||||
extra_prefixes="moz:layout.css.prefixes.transitions webkit"
|
||||
sub_properties="transition-property transition-duration
|
||||
transition-timing-function
|
||||
transition-delay"
|
||||
spec="https://drafts.csswg.org/css-transitions/#propdef-transition">
|
||||
use crate::parser::Parse;
|
||||
% for prop in "delay duration property timing_function".split():
|
||||
use crate::properties::longhands::transition_${prop};
|
||||
% endfor
|
||||
use crate::values::specified::TransitionProperty;
|
||||
|
||||
pub fn parse_value<'i, 't>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
) -> Result<Longhands, ParseError<'i>> {
|
||||
struct SingleTransition {
|
||||
% for prop in "duration timing_function delay".split():
|
||||
transition_${prop}: transition_${prop}::SingleSpecifiedValue,
|
||||
% endfor
|
||||
// Unlike other properties, transition-property uses an Option<> to
|
||||
// represent 'none' as `None`.
|
||||
transition_property: Option<TransitionProperty>,
|
||||
}
|
||||
|
||||
fn parse_one_transition<'i, 't>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
) -> Result<SingleTransition,ParseError<'i>> {
|
||||
% for prop in "property duration timing_function delay".split():
|
||||
let mut ${prop} = None;
|
||||
% endfor
|
||||
|
||||
let mut parsed = 0;
|
||||
loop {
|
||||
parsed += 1;
|
||||
|
||||
try_parse_one!(context, input, duration, transition_duration);
|
||||
try_parse_one!(context, input, timing_function, transition_timing_function);
|
||||
try_parse_one!(context, input, delay, transition_delay);
|
||||
// Must check 'transition-property' after 'transition-timing-function' since
|
||||
// 'transition-property' accepts any keyword.
|
||||
if property.is_none() {
|
||||
if let Ok(value) = input.try_parse(|i| TransitionProperty::parse(context, i)) {
|
||||
property = Some(Some(value));
|
||||
continue;
|
||||
}
|
||||
|
||||
if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
|
||||
// 'none' is not a valid value for <single-transition-property>,
|
||||
// so it's not acceptable in the function above.
|
||||
property = Some(None);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
parsed -= 1;
|
||||
break
|
||||
}
|
||||
|
||||
if parsed != 0 {
|
||||
Ok(SingleTransition {
|
||||
% for prop in "duration timing_function delay".split():
|
||||
transition_${prop}: ${prop}.unwrap_or_else(transition_${prop}::single_value
|
||||
::get_initial_specified_value),
|
||||
% endfor
|
||||
transition_property: property.unwrap_or(
|
||||
Some(transition_property::single_value::get_initial_specified_value())),
|
||||
})
|
||||
} else {
|
||||
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
|
||||
}
|
||||
}
|
||||
|
||||
% for prop in "property duration timing_function delay".split():
|
||||
let mut ${prop}s = Vec::new();
|
||||
% endfor
|
||||
|
||||
let results = input.parse_comma_separated(|i| parse_one_transition(context, i))?;
|
||||
let multiple_items = results.len() >= 2;
|
||||
for result in results {
|
||||
if let Some(value) = result.transition_property {
|
||||
propertys.push(value);
|
||||
} else if multiple_items {
|
||||
// If there is more than one item, and any of transitions has 'none',
|
||||
// then it's invalid. Othersize, leave propertys to be empty (which
|
||||
// means "transition-property: none");
|
||||
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
|
||||
}
|
||||
|
||||
% for prop in "duration timing_function delay".split():
|
||||
${prop}s.push(result.transition_${prop});
|
||||
% endfor
|
||||
}
|
||||
|
||||
Ok(expanded! {
|
||||
% for prop in "property duration timing_function delay".split():
|
||||
transition_${prop}: transition_${prop}::SpecifiedValue(${prop}s.into()),
|
||||
% endfor
|
||||
})
|
||||
}
|
||||
|
||||
impl<'a> ToCss for LonghandsToSerialize<'a> {
|
||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write {
|
||||
let property_len = self.transition_property.0.len();
|
||||
|
||||
// There are two cases that we can do shorthand serialization:
|
||||
// * when all value lists have the same length, or
|
||||
// * when transition-property is none, and other value lists have exactly one item.
|
||||
if property_len == 0 {
|
||||
% for name in "duration delay timing_function".split():
|
||||
if self.transition_${name}.0.len() != 1 {
|
||||
return Ok(());
|
||||
}
|
||||
% endfor
|
||||
} else {
|
||||
% for name in "duration delay timing_function".split():
|
||||
if self.transition_${name}.0.len() != property_len {
|
||||
return Ok(());
|
||||
}
|
||||
% endfor
|
||||
}
|
||||
|
||||
// Representative length.
|
||||
let len = self.transition_duration.0.len();
|
||||
|
||||
for i in 0..len {
|
||||
if i != 0 {
|
||||
dest.write_str(", ")?;
|
||||
}
|
||||
if property_len == 0 {
|
||||
dest.write_str("none")?;
|
||||
} else {
|
||||
self.transition_property.0[i].to_css(dest)?;
|
||||
}
|
||||
% for name in "duration timing_function delay".split():
|
||||
dest.write_str(" ")?;
|
||||
self.transition_${name}.0[i].to_css(dest)?;
|
||||
% endfor
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
</%helpers:shorthand>
|
||||
|
||||
<%helpers:shorthand name="animation"
|
||||
engines="gecko servo"
|
||||
extra_prefixes="moz:layout.css.prefixes.animations webkit"
|
||||
sub_properties="animation-name animation-duration
|
||||
animation-timing-function animation-delay
|
||||
animation-iteration-count animation-direction
|
||||
animation-fill-mode animation-play-state animation-timeline"
|
||||
rule_types_allowed="Style"
|
||||
spec="https://drafts.csswg.org/css-animations/#propdef-animation">
|
||||
<%
|
||||
props = "name timeline duration timing_function delay iteration_count \
|
||||
direction fill_mode play_state".split()
|
||||
%>
|
||||
% for prop in props:
|
||||
use crate::properties::longhands::animation_${prop};
|
||||
% endfor
|
||||
|
||||
pub fn parse_value<'i, 't>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
) -> Result<Longhands, ParseError<'i>> {
|
||||
struct SingleAnimation {
|
||||
% for prop in props:
|
||||
animation_${prop}: animation_${prop}::SingleSpecifiedValue,
|
||||
% endfor
|
||||
}
|
||||
|
||||
fn scroll_linked_animations_enabled() -> bool {
|
||||
#[cfg(feature = "gecko")]
|
||||
return static_prefs::pref!("layout.css.scroll-linked-animations.enabled");
|
||||
#[cfg(feature = "servo")]
|
||||
return false;
|
||||
}
|
||||
|
||||
fn parse_one_animation<'i, 't>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
) -> Result<SingleAnimation, ParseError<'i>> {
|
||||
% for prop in props:
|
||||
let mut ${prop} = None;
|
||||
% endfor
|
||||
|
||||
let mut parsed = 0;
|
||||
// NB: Name must be the last one here so that keywords valid for other
|
||||
// longhands are not interpreted as names.
|
||||
//
|
||||
// Also, duration must be before delay, see
|
||||
// https://drafts.csswg.org/css-animations/#typedef-single-animation
|
||||
loop {
|
||||
parsed += 1;
|
||||
try_parse_one!(context, input, duration, animation_duration);
|
||||
try_parse_one!(context, input, timing_function, animation_timing_function);
|
||||
try_parse_one!(context, input, delay, animation_delay);
|
||||
try_parse_one!(context, input, iteration_count, animation_iteration_count);
|
||||
try_parse_one!(context, input, direction, animation_direction);
|
||||
try_parse_one!(context, input, fill_mode, animation_fill_mode);
|
||||
try_parse_one!(context, input, play_state, animation_play_state);
|
||||
try_parse_one!(context, input, name, animation_name);
|
||||
if scroll_linked_animations_enabled() {
|
||||
try_parse_one!(context, input, timeline, animation_timeline);
|
||||
}
|
||||
|
||||
parsed -= 1;
|
||||
break
|
||||
}
|
||||
|
||||
// If nothing is parsed, this is an invalid entry.
|
||||
if parsed == 0 {
|
||||
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
|
||||
} else {
|
||||
Ok(SingleAnimation {
|
||||
% for prop in props:
|
||||
animation_${prop}: ${prop}.unwrap_or_else(animation_${prop}::single_value
|
||||
::get_initial_specified_value),
|
||||
% endfor
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
% for prop in props:
|
||||
let mut ${prop}s = vec![];
|
||||
% endfor
|
||||
|
||||
let results = input.parse_comma_separated(|i| parse_one_animation(context, i))?;
|
||||
for result in results.into_iter() {
|
||||
% for prop in props:
|
||||
${prop}s.push(result.animation_${prop});
|
||||
% endfor
|
||||
}
|
||||
|
||||
Ok(expanded! {
|
||||
% for prop in props:
|
||||
animation_${prop}: animation_${prop}::SpecifiedValue(${prop}s.into()),
|
||||
% endfor
|
||||
})
|
||||
}
|
||||
|
||||
impl<'a> ToCss for LonghandsToSerialize<'a> {
|
||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write {
|
||||
let len = self.animation_name.0.len();
|
||||
// There should be at least one declared value
|
||||
if len == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// If any value list length is differs then we don't do a shorthand serialization
|
||||
// either.
|
||||
% for name in props[2:]:
|
||||
if len != self.animation_${name}.0.len() {
|
||||
return Ok(())
|
||||
}
|
||||
% endfor
|
||||
|
||||
// If the preference of animation-timeline is disabled, `self.animation_timeline` is
|
||||
// None.
|
||||
if self.animation_timeline.map_or(false, |v| len != v.0.len()) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for i in 0..len {
|
||||
if i != 0 {
|
||||
dest.write_str(", ")?;
|
||||
}
|
||||
|
||||
% for name in props[2:]:
|
||||
self.animation_${name}.0[i].to_css(dest)?;
|
||||
dest.write_str(" ")?;
|
||||
% endfor
|
||||
|
||||
self.animation_name.0[i].to_css(dest)?;
|
||||
|
||||
// Based on the spec, the default values of other properties must be output in at
|
||||
// least the cases necessary to distinguish an animation-name. The serialization
|
||||
// order of animation-timeline is always later than animation-name, so it's fine
|
||||
// to not serialize it if it is the default value. It's still possible to
|
||||
// distinguish them (because we always serialize animation-name).
|
||||
// https://drafts.csswg.org/css-animations-1/#animation
|
||||
// https://drafts.csswg.org/css-animations-2/#typedef-single-animation
|
||||
//
|
||||
// Note: it's also fine to always serialize this. However, it seems Blink
|
||||
// doesn't serialize default animation-timeline now, so we follow the same rule.
|
||||
if let Some(ref timeline) = self.animation_timeline {
|
||||
if !timeline.0[i].is_auto() {
|
||||
dest.write_char(' ')?;
|
||||
timeline.0[i].to_css(dest)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
</%helpers:shorthand>
|
||||
|
||||
${helpers.two_properties_shorthand(
|
||||
"overscroll-behavior",
|
||||
"overscroll-behavior-x",
|
||||
|
@ -33,45 +345,6 @@ ${helpers.two_properties_shorthand(
|
|||
spec="https://wicg.github.io/overscroll-behavior/#overscroll-behavior-properties",
|
||||
)}
|
||||
|
||||
<%helpers:shorthand
|
||||
engines="gecko"
|
||||
name="container"
|
||||
sub_properties="container-name container-type"
|
||||
gecko_pref="layout.css.container-queries.enabled",
|
||||
spec="https://drafts.csswg.org/css-contain-3/#container-shorthand"
|
||||
>
|
||||
pub fn parse_value<'i>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, '_>,
|
||||
) -> Result<Longhands, ParseError<'i>> {
|
||||
use crate::parser::Parse;
|
||||
use crate::values::specified::box_::{ContainerName, ContainerType};
|
||||
// See https://github.com/w3c/csswg-drafts/issues/7180 for why we don't
|
||||
// match the spec.
|
||||
let container_name = ContainerName::parse(context, input)?;
|
||||
let container_type = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
|
||||
ContainerType::parse(context, input)?
|
||||
} else {
|
||||
ContainerType::NONE
|
||||
};
|
||||
Ok(expanded! {
|
||||
container_name: container_name,
|
||||
container_type: container_type,
|
||||
})
|
||||
}
|
||||
|
||||
impl<'a> ToCss for LonghandsToSerialize<'a> {
|
||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write {
|
||||
self.container_name.to_css(dest)?;
|
||||
if !self.container_type.is_empty() {
|
||||
dest.write_str(" / ")?;
|
||||
self.container_type.to_css(dest)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
</%helpers:shorthand>
|
||||
|
||||
<%helpers:shorthand
|
||||
engines="gecko"
|
||||
name="page-break-before"
|
||||
|
|
|
@ -1,317 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
<%namespace name="helpers" file="/helpers.mako.rs" />
|
||||
|
||||
macro_rules! try_parse_one {
|
||||
($context: expr, $input: expr, $var: ident, $prop_module: ident) => {
|
||||
if $var.is_none() {
|
||||
if let Ok(value) = $input.try_parse(|i| {
|
||||
$prop_module::single_value::parse($context, i)
|
||||
}) {
|
||||
$var = Some(value);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
<%helpers:shorthand name="transition"
|
||||
engines="gecko servo"
|
||||
extra_prefixes="moz:layout.css.prefixes.transitions webkit"
|
||||
sub_properties="transition-property transition-duration
|
||||
transition-timing-function
|
||||
transition-delay"
|
||||
spec="https://drafts.csswg.org/css-transitions/#propdef-transition">
|
||||
use crate::parser::Parse;
|
||||
% for prop in "delay duration property timing_function".split():
|
||||
use crate::properties::longhands::transition_${prop};
|
||||
% endfor
|
||||
use crate::values::specified::TransitionProperty;
|
||||
|
||||
pub fn parse_value<'i, 't>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
) -> Result<Longhands, ParseError<'i>> {
|
||||
struct SingleTransition {
|
||||
% for prop in "duration timing_function delay".split():
|
||||
transition_${prop}: transition_${prop}::SingleSpecifiedValue,
|
||||
% endfor
|
||||
// Unlike other properties, transition-property uses an Option<> to
|
||||
// represent 'none' as `None`.
|
||||
transition_property: Option<TransitionProperty>,
|
||||
}
|
||||
|
||||
fn parse_one_transition<'i, 't>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
) -> Result<SingleTransition,ParseError<'i>> {
|
||||
% for prop in "property duration timing_function delay".split():
|
||||
let mut ${prop} = None;
|
||||
% endfor
|
||||
|
||||
let mut parsed = 0;
|
||||
loop {
|
||||
parsed += 1;
|
||||
|
||||
try_parse_one!(context, input, duration, transition_duration);
|
||||
try_parse_one!(context, input, timing_function, transition_timing_function);
|
||||
try_parse_one!(context, input, delay, transition_delay);
|
||||
// Must check 'transition-property' after 'transition-timing-function' since
|
||||
// 'transition-property' accepts any keyword.
|
||||
if property.is_none() {
|
||||
if let Ok(value) = input.try_parse(|i| TransitionProperty::parse(context, i)) {
|
||||
property = Some(Some(value));
|
||||
continue;
|
||||
}
|
||||
|
||||
if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
|
||||
// 'none' is not a valid value for <single-transition-property>,
|
||||
// so it's not acceptable in the function above.
|
||||
property = Some(None);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
parsed -= 1;
|
||||
break
|
||||
}
|
||||
|
||||
if parsed != 0 {
|
||||
Ok(SingleTransition {
|
||||
% for prop in "duration timing_function delay".split():
|
||||
transition_${prop}: ${prop}.unwrap_or_else(transition_${prop}::single_value
|
||||
::get_initial_specified_value),
|
||||
% endfor
|
||||
transition_property: property.unwrap_or(
|
||||
Some(transition_property::single_value::get_initial_specified_value())),
|
||||
})
|
||||
} else {
|
||||
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
|
||||
}
|
||||
}
|
||||
|
||||
% for prop in "property duration timing_function delay".split():
|
||||
let mut ${prop}s = Vec::new();
|
||||
% endfor
|
||||
|
||||
let results = input.parse_comma_separated(|i| parse_one_transition(context, i))?;
|
||||
let multiple_items = results.len() >= 2;
|
||||
for result in results {
|
||||
if let Some(value) = result.transition_property {
|
||||
propertys.push(value);
|
||||
} else if multiple_items {
|
||||
// If there is more than one item, and any of transitions has 'none',
|
||||
// then it's invalid. Othersize, leave propertys to be empty (which
|
||||
// means "transition-property: none");
|
||||
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
|
||||
}
|
||||
|
||||
% for prop in "duration timing_function delay".split():
|
||||
${prop}s.push(result.transition_${prop});
|
||||
% endfor
|
||||
}
|
||||
|
||||
Ok(expanded! {
|
||||
% for prop in "property duration timing_function delay".split():
|
||||
transition_${prop}: transition_${prop}::SpecifiedValue(${prop}s.into()),
|
||||
% endfor
|
||||
})
|
||||
}
|
||||
|
||||
impl<'a> ToCss for LonghandsToSerialize<'a> {
|
||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write {
|
||||
let property_len = self.transition_property.0.len();
|
||||
|
||||
// There are two cases that we can do shorthand serialization:
|
||||
// * when all value lists have the same length, or
|
||||
// * when transition-property is none, and other value lists have exactly one item.
|
||||
if property_len == 0 {
|
||||
% for name in "duration delay timing_function".split():
|
||||
if self.transition_${name}.0.len() != 1 {
|
||||
return Ok(());
|
||||
}
|
||||
% endfor
|
||||
} else {
|
||||
% for name in "duration delay timing_function".split():
|
||||
if self.transition_${name}.0.len() != property_len {
|
||||
return Ok(());
|
||||
}
|
||||
% endfor
|
||||
}
|
||||
|
||||
// Representative length.
|
||||
let len = self.transition_duration.0.len();
|
||||
|
||||
for i in 0..len {
|
||||
if i != 0 {
|
||||
dest.write_str(", ")?;
|
||||
}
|
||||
if property_len == 0 {
|
||||
dest.write_str("none")?;
|
||||
} else {
|
||||
self.transition_property.0[i].to_css(dest)?;
|
||||
}
|
||||
% for name in "duration timing_function delay".split():
|
||||
dest.write_str(" ")?;
|
||||
self.transition_${name}.0[i].to_css(dest)?;
|
||||
% endfor
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
</%helpers:shorthand>
|
||||
|
||||
<%helpers:shorthand name="animation"
|
||||
engines="gecko servo"
|
||||
extra_prefixes="moz:layout.css.prefixes.animations webkit"
|
||||
sub_properties="animation-name animation-duration
|
||||
animation-timing-function animation-delay
|
||||
animation-iteration-count animation-direction
|
||||
animation-fill-mode animation-play-state animation-timeline"
|
||||
rule_types_allowed="Style"
|
||||
spec="https://drafts.csswg.org/css-animations/#propdef-animation">
|
||||
<%
|
||||
props = "name timeline duration timing_function delay iteration_count \
|
||||
direction fill_mode play_state".split()
|
||||
%>
|
||||
% for prop in props:
|
||||
use crate::properties::longhands::animation_${prop};
|
||||
% endfor
|
||||
|
||||
pub fn parse_value<'i, 't>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
) -> Result<Longhands, ParseError<'i>> {
|
||||
struct SingleAnimation {
|
||||
% for prop in props:
|
||||
animation_${prop}: animation_${prop}::SingleSpecifiedValue,
|
||||
% endfor
|
||||
}
|
||||
|
||||
fn scroll_linked_animations_enabled() -> bool {
|
||||
#[cfg(feature = "gecko")]
|
||||
return static_prefs::pref!("layout.css.scroll-linked-animations.enabled");
|
||||
#[cfg(feature = "servo")]
|
||||
return false;
|
||||
}
|
||||
|
||||
fn parse_one_animation<'i, 't>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
) -> Result<SingleAnimation, ParseError<'i>> {
|
||||
% for prop in props:
|
||||
let mut ${prop} = None;
|
||||
% endfor
|
||||
|
||||
let mut parsed = 0;
|
||||
// NB: Name must be the last one here so that keywords valid for other
|
||||
// longhands are not interpreted as names.
|
||||
//
|
||||
// Also, duration must be before delay, see
|
||||
// https://drafts.csswg.org/css-animations/#typedef-single-animation
|
||||
loop {
|
||||
parsed += 1;
|
||||
try_parse_one!(context, input, duration, animation_duration);
|
||||
try_parse_one!(context, input, timing_function, animation_timing_function);
|
||||
try_parse_one!(context, input, delay, animation_delay);
|
||||
try_parse_one!(context, input, iteration_count, animation_iteration_count);
|
||||
try_parse_one!(context, input, direction, animation_direction);
|
||||
try_parse_one!(context, input, fill_mode, animation_fill_mode);
|
||||
try_parse_one!(context, input, play_state, animation_play_state);
|
||||
try_parse_one!(context, input, name, animation_name);
|
||||
if scroll_linked_animations_enabled() {
|
||||
try_parse_one!(context, input, timeline, animation_timeline);
|
||||
}
|
||||
|
||||
parsed -= 1;
|
||||
break
|
||||
}
|
||||
|
||||
// If nothing is parsed, this is an invalid entry.
|
||||
if parsed == 0 {
|
||||
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
|
||||
} else {
|
||||
Ok(SingleAnimation {
|
||||
% for prop in props:
|
||||
animation_${prop}: ${prop}.unwrap_or_else(animation_${prop}::single_value
|
||||
::get_initial_specified_value),
|
||||
% endfor
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
% for prop in props:
|
||||
let mut ${prop}s = vec![];
|
||||
% endfor
|
||||
|
||||
let results = input.parse_comma_separated(|i| parse_one_animation(context, i))?;
|
||||
for result in results.into_iter() {
|
||||
% for prop in props:
|
||||
${prop}s.push(result.animation_${prop});
|
||||
% endfor
|
||||
}
|
||||
|
||||
Ok(expanded! {
|
||||
% for prop in props:
|
||||
animation_${prop}: animation_${prop}::SpecifiedValue(${prop}s.into()),
|
||||
% endfor
|
||||
})
|
||||
}
|
||||
|
||||
impl<'a> ToCss for LonghandsToSerialize<'a> {
|
||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write {
|
||||
let len = self.animation_name.0.len();
|
||||
// There should be at least one declared value
|
||||
if len == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// If any value list length is differs then we don't do a shorthand serialization
|
||||
// either.
|
||||
% for name in props[2:]:
|
||||
if len != self.animation_${name}.0.len() {
|
||||
return Ok(())
|
||||
}
|
||||
% endfor
|
||||
|
||||
// If the preference of animation-timeline is disabled, `self.animation_timeline` is
|
||||
// None.
|
||||
if self.animation_timeline.map_or(false, |v| len != v.0.len()) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for i in 0..len {
|
||||
if i != 0 {
|
||||
dest.write_str(", ")?;
|
||||
}
|
||||
|
||||
% for name in props[2:]:
|
||||
self.animation_${name}.0[i].to_css(dest)?;
|
||||
dest.write_str(" ")?;
|
||||
% endfor
|
||||
|
||||
self.animation_name.0[i].to_css(dest)?;
|
||||
|
||||
// Based on the spec, the default values of other properties must be output in at
|
||||
// least the cases necessary to distinguish an animation-name. The serialization
|
||||
// order of animation-timeline is always later than animation-name, so it's fine
|
||||
// to not serialize it if it is the default value. It's still possible to
|
||||
// distinguish them (because we always serialize animation-name).
|
||||
// https://drafts.csswg.org/css-animations-1/#animation
|
||||
// https://drafts.csswg.org/css-animations-2/#typedef-single-animation
|
||||
//
|
||||
// Note: it's also fine to always serialize this. However, it seems Blink
|
||||
// doesn't serialize default animation-timeline now, so we follow the same rule.
|
||||
if let Some(ref timeline) = self.animation_timeline {
|
||||
if !timeline.0[i].is_auto() {
|
||||
dest.write_char(' ')?;
|
||||
timeline.0[i].to_css(dest)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
</%helpers:shorthand>
|
|
@ -1,711 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
//! Parsing for query feature expressions, like `(foo: bar)` or
|
||||
//! `(width >= 400px)`.
|
||||
|
||||
use super::feature::{Evaluator, QueryFeatureDescription};
|
||||
use super::feature::{KeywordDiscriminant, FeatureFlags};
|
||||
use crate::parser::{Parse, ParserContext};
|
||||
use crate::str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase};
|
||||
use crate::values::computed::{self, Ratio, ToComputedValue};
|
||||
use crate::values::specified::{Integer, Length, Number, Resolution};
|
||||
use crate::values::CSSFloat;
|
||||
use crate::{Atom, Zero};
|
||||
use cssparser::{Parser, Token};
|
||||
use std::cmp::{Ordering, PartialOrd};
|
||||
use std::fmt::{self, Write};
|
||||
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
|
||||
|
||||
/// Whether we're parsing a media or container query feature.
|
||||
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
|
||||
pub enum FeatureType {
|
||||
/// We're parsing a media feature.
|
||||
Media,
|
||||
/// We're parsing a container feature.
|
||||
Container,
|
||||
}
|
||||
|
||||
impl FeatureType {
|
||||
fn features(&self) -> &'static [QueryFeatureDescription] {
|
||||
#[cfg(feature = "gecko")]
|
||||
let media_features = &crate::gecko::media_features::MEDIA_FEATURES;
|
||||
#[cfg(feature = "servo")]
|
||||
let media_features = &*crate::servo::media_queries::MEDIA_FEATURES;
|
||||
|
||||
use crate::stylesheets::container_rule::CONTAINER_FEATURES;
|
||||
|
||||
match *self {
|
||||
FeatureType::Media => media_features,
|
||||
FeatureType::Container => &CONTAINER_FEATURES,
|
||||
}
|
||||
}
|
||||
|
||||
fn find_feature(&self, name: &Atom) -> Option<(usize, &'static QueryFeatureDescription)> {
|
||||
self.features().iter().enumerate().find(|(_, f)| f.name == *name)
|
||||
}
|
||||
}
|
||||
|
||||
/// The kind of matching that should be performed on a feature value.
|
||||
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
|
||||
enum LegacyRange {
|
||||
/// At least the specified value.
|
||||
Min,
|
||||
/// At most the specified value.
|
||||
Max,
|
||||
}
|
||||
|
||||
/// The operator that was specified in this feature.
|
||||
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
|
||||
enum Operator {
|
||||
/// =
|
||||
Equal,
|
||||
/// >
|
||||
GreaterThan,
|
||||
/// >=
|
||||
GreaterThanEqual,
|
||||
/// <
|
||||
LessThan,
|
||||
/// <=
|
||||
LessThanEqual,
|
||||
}
|
||||
|
||||
impl ToCss for Operator {
|
||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||
where
|
||||
W: fmt::Write,
|
||||
{
|
||||
dest.write_str(match *self {
|
||||
Self::Equal => "=",
|
||||
Self::LessThan => "<",
|
||||
Self::LessThanEqual => "<=",
|
||||
Self::GreaterThan => ">",
|
||||
Self::GreaterThanEqual => ">=",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Operator {
|
||||
fn is_compatible_with(self, right_op: Self) -> bool {
|
||||
// Some operators are not compatible with each other in multi-range
|
||||
// context.
|
||||
match self {
|
||||
Self::Equal => false,
|
||||
Self::GreaterThan | Self::GreaterThanEqual => matches!(right_op, Self::GreaterThan | Self::GreaterThanEqual),
|
||||
Self::LessThan | Self::LessThanEqual => matches!(right_op, Self::LessThan | Self::LessThanEqual),
|
||||
}
|
||||
}
|
||||
|
||||
fn evaluate(&self, cmp: Ordering) -> bool {
|
||||
match *self {
|
||||
Self::Equal => cmp == Ordering::Equal,
|
||||
Self::GreaterThan => cmp == Ordering::Greater,
|
||||
Self::GreaterThanEqual => cmp == Ordering::Equal || cmp == Ordering::Greater,
|
||||
Self::LessThan => cmp == Ordering::Less,
|
||||
Self::LessThanEqual => cmp == Ordering::Equal || cmp == Ordering::Less,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse<'i>(input: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
|
||||
let location = input.current_source_location();
|
||||
let operator = match *input.next()? {
|
||||
Token::Delim('=') => return Ok(Operator::Equal),
|
||||
Token::Delim('>') => Operator::GreaterThan,
|
||||
Token::Delim('<') => Operator::LessThan,
|
||||
ref t => return Err(location.new_unexpected_token_error(t.clone())),
|
||||
};
|
||||
|
||||
// https://drafts.csswg.org/mediaqueries-4/#mq-syntax:
|
||||
//
|
||||
// No whitespace is allowed between the “<” or “>”
|
||||
// <delim-token>s and the following “=” <delim-token>, if it’s
|
||||
// present.
|
||||
//
|
||||
// TODO(emilio): Maybe we should ignore comments as well?
|
||||
// https://github.com/w3c/csswg-drafts/issues/6248
|
||||
let parsed_equal = input
|
||||
.try_parse(|i| {
|
||||
let t = i.next_including_whitespace().map_err(|_| ())?;
|
||||
if !matches!(t, Token::Delim('=')) {
|
||||
return Err(());
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.is_ok();
|
||||
|
||||
if !parsed_equal {
|
||||
return Ok(operator);
|
||||
}
|
||||
|
||||
Ok(match operator {
|
||||
Operator::GreaterThan => Operator::GreaterThanEqual,
|
||||
Operator::LessThan => Operator::LessThanEqual,
|
||||
_ => unreachable!(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, MallocSizeOf, ToShmem, PartialEq)]
|
||||
enum QueryFeatureExpressionKind {
|
||||
/// Just the media feature name.
|
||||
Empty,
|
||||
|
||||
/// A single value.
|
||||
Single(QueryExpressionValue),
|
||||
|
||||
/// Legacy range syntax (min-*: value) or so.
|
||||
LegacyRange(LegacyRange, QueryExpressionValue),
|
||||
|
||||
/// Modern range context syntax:
|
||||
/// https://drafts.csswg.org/mediaqueries-5/#mq-range-context
|
||||
Range {
|
||||
left: Option<(Operator, QueryExpressionValue)>,
|
||||
right: Option<(Operator, QueryExpressionValue)>,
|
||||
},
|
||||
}
|
||||
|
||||
impl QueryFeatureExpressionKind {
|
||||
/// Evaluate a given range given an optional query value and a value from
|
||||
/// the browser.
|
||||
fn evaluate<T>(
|
||||
&self,
|
||||
context_value: T,
|
||||
mut compute: impl FnMut(&QueryExpressionValue) -> T,
|
||||
) -> bool
|
||||
where
|
||||
T: PartialOrd + Zero,
|
||||
{
|
||||
match *self {
|
||||
Self::Empty => return !context_value.is_zero(),
|
||||
Self::Single(ref value) => {
|
||||
let value = compute(value);
|
||||
let cmp = match context_value.partial_cmp(&value) {
|
||||
Some(c) => c,
|
||||
None => return false,
|
||||
};
|
||||
cmp == Ordering::Equal
|
||||
},
|
||||
Self::LegacyRange(ref range, ref value) => {
|
||||
let value = compute(value);
|
||||
let cmp = match context_value.partial_cmp(&value) {
|
||||
Some(c) => c,
|
||||
None => return false,
|
||||
};
|
||||
cmp == Ordering::Equal ||
|
||||
match range {
|
||||
LegacyRange::Min => cmp == Ordering::Greater,
|
||||
LegacyRange::Max => cmp == Ordering::Less,
|
||||
}
|
||||
},
|
||||
Self::Range {
|
||||
ref left,
|
||||
ref right,
|
||||
} => {
|
||||
debug_assert!(left.is_some() || right.is_some());
|
||||
if let Some((ref op, ref value)) = left {
|
||||
let value = compute(value);
|
||||
let cmp = match value.partial_cmp(&context_value) {
|
||||
Some(c) => c,
|
||||
None => return false,
|
||||
};
|
||||
if !op.evaluate(cmp) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Some((ref op, ref value)) = right {
|
||||
let value = compute(value);
|
||||
let cmp = match context_value.partial_cmp(&value) {
|
||||
Some(c) => c,
|
||||
None => return false,
|
||||
};
|
||||
if !op.evaluate(cmp) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Non-ranged features only need to compare to one value at most.
|
||||
fn non_ranged_value(&self) -> Option<&QueryExpressionValue> {
|
||||
match *self {
|
||||
Self::Empty => None,
|
||||
Self::Single(ref v) => Some(v),
|
||||
Self::LegacyRange(..) | Self::Range { .. } => {
|
||||
debug_assert!(false, "Unexpected ranged value in non-ranged feature!");
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A feature expression contains a reference to the feature, the value the
|
||||
/// query contained, and the range to evaluate.
|
||||
#[derive(Clone, Debug, MallocSizeOf, ToShmem, PartialEq)]
|
||||
pub struct QueryFeatureExpression {
|
||||
feature_type: FeatureType,
|
||||
feature_index: usize,
|
||||
kind: QueryFeatureExpressionKind,
|
||||
}
|
||||
|
||||
impl ToCss for QueryFeatureExpression {
|
||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||
where
|
||||
W: fmt::Write,
|
||||
{
|
||||
dest.write_str("(")?;
|
||||
|
||||
match self.kind {
|
||||
QueryFeatureExpressionKind::Empty => self.write_name(dest)?,
|
||||
QueryFeatureExpressionKind::Single(ref v) |
|
||||
QueryFeatureExpressionKind::LegacyRange(_, ref v) => {
|
||||
self.write_name(dest)?;
|
||||
dest.write_str(": ")?;
|
||||
v.to_css(dest, self)?;
|
||||
},
|
||||
QueryFeatureExpressionKind::Range {
|
||||
ref left,
|
||||
ref right,
|
||||
} => {
|
||||
if let Some((ref op, ref val)) = left {
|
||||
val.to_css(dest, self)?;
|
||||
dest.write_char(' ')?;
|
||||
op.to_css(dest)?;
|
||||
dest.write_char(' ')?;
|
||||
}
|
||||
self.write_name(dest)?;
|
||||
if let Some((ref op, ref val)) = right {
|
||||
dest.write_char(' ')?;
|
||||
op.to_css(dest)?;
|
||||
dest.write_char(' ')?;
|
||||
val.to_css(dest, self)?;
|
||||
}
|
||||
},
|
||||
}
|
||||
dest.write_char(')')
|
||||
}
|
||||
}
|
||||
|
||||
fn consume_operation_or_colon<'i>(
|
||||
input: &mut Parser<'i, '_>,
|
||||
) -> Result<Option<Operator>, ParseError<'i>> {
|
||||
if input.try_parse(|input| input.expect_colon()).is_ok() {
|
||||
return Ok(None);
|
||||
}
|
||||
Operator::parse(input).map(|op| Some(op))
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn disabled_by_pref(feature: &Atom, context: &ParserContext) -> bool {
|
||||
#[cfg(feature = "gecko")]
|
||||
{
|
||||
if *feature == atom!("forced-colors") {
|
||||
// forced-colors is always enabled in the ua and chrome. On
|
||||
// the web it is hidden behind a preference, which is defaulted
|
||||
// to 'true' as of bug 1659511.
|
||||
return !context.in_ua_or_chrome_sheet() &&
|
||||
!static_prefs::pref!("layout.css.forced-colors.enabled");
|
||||
}
|
||||
// prefers-contrast is always enabled in the ua and chrome. On
|
||||
// the web it is hidden behind a preference.
|
||||
if *feature == atom!("prefers-contrast") {
|
||||
return !context.in_ua_or_chrome_sheet() &&
|
||||
!static_prefs::pref!("layout.css.prefers-contrast.enabled");
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
impl QueryFeatureExpression {
|
||||
fn new(
|
||||
feature_type: FeatureType,
|
||||
feature_index: usize,
|
||||
kind: QueryFeatureExpressionKind,
|
||||
) -> Self {
|
||||
debug_assert!(feature_index < feature_type.features().len());
|
||||
Self {
|
||||
feature_type,
|
||||
feature_index,
|
||||
kind,
|
||||
}
|
||||
}
|
||||
|
||||
fn write_name<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||
where
|
||||
W: fmt::Write,
|
||||
{
|
||||
let feature = self.feature();
|
||||
if feature.flags.contains(FeatureFlags::WEBKIT_PREFIX) {
|
||||
dest.write_str("-webkit-")?;
|
||||
}
|
||||
|
||||
if let QueryFeatureExpressionKind::LegacyRange(range, _) = self.kind {
|
||||
match range {
|
||||
LegacyRange::Min => dest.write_str("min-")?,
|
||||
LegacyRange::Max => dest.write_str("max-")?,
|
||||
}
|
||||
}
|
||||
|
||||
// NB: CssStringWriter not needed, feature names are under control.
|
||||
write!(dest, "{}", feature.name)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn feature(&self) -> &'static QueryFeatureDescription {
|
||||
&self.feature_type.features()[self.feature_index]
|
||||
}
|
||||
|
||||
/// Returns the feature flags for our feature.
|
||||
pub fn feature_flags(&self) -> FeatureFlags {
|
||||
self.feature().flags
|
||||
}
|
||||
|
||||
/// Parse a feature expression of the form:
|
||||
///
|
||||
/// ```
|
||||
/// (media-feature: media-value)
|
||||
/// ```
|
||||
pub fn parse<'i, 't>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
feature_type: FeatureType,
|
||||
) -> Result<Self, ParseError<'i>> {
|
||||
input.expect_parenthesis_block()?;
|
||||
input.parse_nested_block(|input| {
|
||||
Self::parse_in_parenthesis_block(context, input, feature_type)
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_feature_name<'i, 't>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
feature_type: FeatureType,
|
||||
) -> Result<(usize, Option<LegacyRange>), ParseError<'i>> {
|
||||
let mut flags = FeatureFlags::empty();
|
||||
let location = input.current_source_location();
|
||||
let ident = input.expect_ident()?;
|
||||
|
||||
if context.in_ua_or_chrome_sheet() {
|
||||
flags.insert(FeatureFlags::CHROME_AND_UA_ONLY);
|
||||
}
|
||||
|
||||
let mut feature_name = &**ident;
|
||||
if starts_with_ignore_ascii_case(feature_name, "-webkit-") {
|
||||
feature_name = &feature_name[8..];
|
||||
flags.insert(FeatureFlags::WEBKIT_PREFIX);
|
||||
}
|
||||
|
||||
let range = if starts_with_ignore_ascii_case(feature_name, "min-") {
|
||||
feature_name = &feature_name[4..];
|
||||
Some(LegacyRange::Min)
|
||||
} else if starts_with_ignore_ascii_case(feature_name, "max-") {
|
||||
feature_name = &feature_name[4..];
|
||||
Some(LegacyRange::Max)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let atom = Atom::from(string_as_ascii_lowercase(feature_name));
|
||||
let (feature_index, feature) = match feature_type.find_feature(&atom) {
|
||||
Some((i, f)) => (i, f),
|
||||
None => {
|
||||
return Err(location.new_custom_error(
|
||||
StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()),
|
||||
))
|
||||
},
|
||||
};
|
||||
|
||||
if disabled_by_pref(&feature.name, context) ||
|
||||
!flags.contains(feature.flags.parsing_requirements()) ||
|
||||
(range.is_some() && !feature.allows_ranges())
|
||||
{
|
||||
return Err(location.new_custom_error(
|
||||
StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()),
|
||||
));
|
||||
}
|
||||
|
||||
Ok((feature_index, range))
|
||||
}
|
||||
|
||||
/// Parses the following range syntax:
|
||||
///
|
||||
/// (feature-value <operator> feature-name)
|
||||
/// (feature-value <operator> feature-name <operator> feature-value)
|
||||
fn parse_multi_range_syntax<'i, 't>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
feature_type: FeatureType,
|
||||
) -> Result<Self, ParseError<'i>> {
|
||||
let start = input.state();
|
||||
|
||||
// To parse the values, we first need to find the feature name. We rely
|
||||
// on feature values for ranged features not being able to be top-level
|
||||
// <ident>s, which holds.
|
||||
let feature_index = loop {
|
||||
// NOTE: parse_feature_name advances the input.
|
||||
if let Ok((index, range)) = Self::parse_feature_name(context, input, feature_type) {
|
||||
if range.is_some() {
|
||||
// Ranged names are not allowed here.
|
||||
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
|
||||
}
|
||||
break index;
|
||||
}
|
||||
if input.is_exhausted() {
|
||||
return Err(start
|
||||
.source_location()
|
||||
.new_custom_error(StyleParseErrorKind::UnspecifiedError));
|
||||
}
|
||||
};
|
||||
|
||||
input.reset(&start);
|
||||
|
||||
let feature = &feature_type.features()[feature_index];
|
||||
let left_val = QueryExpressionValue::parse(feature, context, input)?;
|
||||
let left_op = Operator::parse(input)?;
|
||||
|
||||
{
|
||||
let (parsed_index, _) = Self::parse_feature_name(context, input, feature_type)?;
|
||||
debug_assert_eq!(
|
||||
parsed_index, feature_index,
|
||||
"How did we find a different feature?"
|
||||
);
|
||||
}
|
||||
|
||||
let right_op = input.try_parse(Operator::parse).ok();
|
||||
let right = match right_op {
|
||||
Some(op) => {
|
||||
if !left_op.is_compatible_with(op) {
|
||||
return Err(
|
||||
input.new_custom_error(StyleParseErrorKind::UnspecifiedError)
|
||||
);
|
||||
}
|
||||
Some((op, QueryExpressionValue::parse(feature, context, input)?))
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
Ok(Self::new(
|
||||
feature_type,
|
||||
feature_index,
|
||||
QueryFeatureExpressionKind::Range {
|
||||
left: Some((left_op, left_val)),
|
||||
right,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
/// Parse a feature expression where we've already consumed the parenthesis.
|
||||
pub fn parse_in_parenthesis_block<'i, 't>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
feature_type: FeatureType,
|
||||
) -> Result<Self, ParseError<'i>> {
|
||||
let (feature_index, range) =
|
||||
match input.try_parse(|input| Self::parse_feature_name(context, input, feature_type)) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
if let Ok(expr) = Self::parse_multi_range_syntax(context, input, feature_type) {
|
||||
return Ok(expr);
|
||||
}
|
||||
return Err(e);
|
||||
},
|
||||
};
|
||||
let operator = input.try_parse(consume_operation_or_colon);
|
||||
let operator = match operator {
|
||||
Err(..) => {
|
||||
// If there's no colon, this is a query of the form
|
||||
// '(<feature>)', that is, there's no value specified.
|
||||
//
|
||||
// Gecko doesn't allow ranged expressions without a
|
||||
// value, so just reject them here too.
|
||||
if range.is_some() {
|
||||
return Err(
|
||||
input.new_custom_error(StyleParseErrorKind::RangedExpressionWithNoValue)
|
||||
);
|
||||
}
|
||||
|
||||
return Ok(Self::new(
|
||||
feature_type,
|
||||
feature_index,
|
||||
QueryFeatureExpressionKind::Empty,
|
||||
));
|
||||
},
|
||||
Ok(operator) => operator,
|
||||
};
|
||||
|
||||
let feature = &feature_type.features()[feature_index];
|
||||
|
||||
let value = QueryExpressionValue::parse(feature, context, input).map_err(|err| {
|
||||
err.location
|
||||
.new_custom_error(StyleParseErrorKind::MediaQueryExpectedFeatureValue)
|
||||
})?;
|
||||
|
||||
let kind = match range {
|
||||
Some(range) => {
|
||||
if operator.is_some() {
|
||||
return Err(
|
||||
input.new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator)
|
||||
);
|
||||
}
|
||||
QueryFeatureExpressionKind::LegacyRange(range, value)
|
||||
},
|
||||
None => match operator {
|
||||
Some(operator) => {
|
||||
if !feature.allows_ranges() {
|
||||
return Err(input
|
||||
.new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator));
|
||||
}
|
||||
QueryFeatureExpressionKind::Range {
|
||||
left: None,
|
||||
right: Some((operator, value)),
|
||||
}
|
||||
},
|
||||
None => QueryFeatureExpressionKind::Single(value),
|
||||
},
|
||||
};
|
||||
|
||||
Ok(Self::new(feature_type, feature_index, kind))
|
||||
}
|
||||
|
||||
/// Returns whether this query evaluates to true for the given device.
|
||||
pub fn matches(&self, context: &computed::Context) -> bool {
|
||||
macro_rules! expect {
|
||||
($variant:ident, $v:expr) => {
|
||||
match *$v {
|
||||
QueryExpressionValue::$variant(ref v) => v,
|
||||
_ => unreachable!("Unexpected QueryExpressionValue"),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
match self.feature().evaluator {
|
||||
Evaluator::Length(eval) => {
|
||||
let v = eval(context);
|
||||
self.kind.evaluate(v, |v| {
|
||||
expect!(Length, v).to_computed_value(context)
|
||||
})
|
||||
},
|
||||
Evaluator::Integer(eval) => {
|
||||
let v = eval(context);
|
||||
self.kind.evaluate(v, |v| *expect!(Integer, v))
|
||||
},
|
||||
Evaluator::Float(eval) => {
|
||||
let v = eval(context);
|
||||
self.kind.evaluate(v, |v| *expect!(Float, v))
|
||||
}
|
||||
Evaluator::NumberRatio(eval) => {
|
||||
let ratio = eval(context);
|
||||
// A ratio of 0/0 behaves as the ratio 1/0, so we need to call used_value()
|
||||
// to convert it if necessary.
|
||||
// FIXME: we may need to update here once
|
||||
// https://github.com/w3c/csswg-drafts/issues/4954 got resolved.
|
||||
self.kind.evaluate(ratio, |v| expect!(NumberRatio, v).used_value())
|
||||
},
|
||||
Evaluator::Resolution(eval) => {
|
||||
let v = eval(context).dppx();
|
||||
self.kind.evaluate(v, |v| {
|
||||
expect!(Resolution, v).to_computed_value(context).dppx()
|
||||
})
|
||||
},
|
||||
Evaluator::Enumerated { evaluator, .. } => {
|
||||
let computed = self.kind.non_ranged_value().map(|v| *expect!(Enumerated, v));
|
||||
evaluator(context, computed)
|
||||
},
|
||||
Evaluator::BoolInteger(eval) => {
|
||||
let computed = self.kind.non_ranged_value().map(|v| *expect!(BoolInteger, v));
|
||||
let boolean = eval(context);
|
||||
computed.map_or(boolean, |v| v == boolean)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A value found or expected in a expression.
|
||||
///
|
||||
/// FIXME(emilio): How should calc() serialize in the Number / Integer /
|
||||
/// BoolInteger / NumberRatio case, as computed or as specified value?
|
||||
///
|
||||
/// If the first, this would need to store the relevant values.
|
||||
///
|
||||
/// See: https://github.com/w3c/csswg-drafts/issues/1968
|
||||
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
|
||||
pub enum QueryExpressionValue {
|
||||
/// A length.
|
||||
Length(Length),
|
||||
/// A (non-negative) integer.
|
||||
Integer(u32),
|
||||
/// A floating point value.
|
||||
Float(CSSFloat),
|
||||
/// A boolean value, specified as an integer (i.e., either 0 or 1).
|
||||
BoolInteger(bool),
|
||||
/// A single non-negative number or two non-negative numbers separated by '/',
|
||||
/// with optional whitespace on either side of the '/'.
|
||||
NumberRatio(Ratio),
|
||||
/// A resolution.
|
||||
Resolution(Resolution),
|
||||
/// An enumerated value, defined by the variant keyword table in the
|
||||
/// feature's `mData` member.
|
||||
Enumerated(KeywordDiscriminant),
|
||||
}
|
||||
|
||||
impl QueryExpressionValue {
|
||||
fn to_css<W>(&self, dest: &mut CssWriter<W>, for_expr: &QueryFeatureExpression) -> fmt::Result
|
||||
where
|
||||
W: fmt::Write,
|
||||
{
|
||||
match *self {
|
||||
QueryExpressionValue::Length(ref l) => l.to_css(dest),
|
||||
QueryExpressionValue::Integer(v) => v.to_css(dest),
|
||||
QueryExpressionValue::Float(v) => v.to_css(dest),
|
||||
QueryExpressionValue::BoolInteger(v) => dest.write_str(if v { "1" } else { "0" }),
|
||||
QueryExpressionValue::NumberRatio(ratio) => ratio.to_css(dest),
|
||||
QueryExpressionValue::Resolution(ref r) => r.to_css(dest),
|
||||
QueryExpressionValue::Enumerated(value) => match for_expr.feature().evaluator {
|
||||
Evaluator::Enumerated { serializer, .. } => dest.write_str(&*serializer(value)),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn parse<'i, 't>(
|
||||
for_feature: &QueryFeatureDescription,
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
) -> Result<QueryExpressionValue, ParseError<'i>> {
|
||||
Ok(match for_feature.evaluator {
|
||||
Evaluator::Length(..) => {
|
||||
let length = Length::parse_non_negative(context, input)?;
|
||||
QueryExpressionValue::Length(length)
|
||||
},
|
||||
Evaluator::Integer(..) => {
|
||||
let integer = Integer::parse_non_negative(context, input)?;
|
||||
QueryExpressionValue::Integer(integer.value() as u32)
|
||||
},
|
||||
Evaluator::BoolInteger(..) => {
|
||||
let integer = Integer::parse_non_negative(context, input)?;
|
||||
let value = integer.value();
|
||||
if value > 1 {
|
||||
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
|
||||
}
|
||||
QueryExpressionValue::BoolInteger(value == 1)
|
||||
},
|
||||
Evaluator::Float(..) => {
|
||||
let number = Number::parse(context, input)?;
|
||||
QueryExpressionValue::Float(number.get())
|
||||
},
|
||||
Evaluator::NumberRatio(..) => {
|
||||
use crate::values::specified::Ratio as SpecifiedRatio;
|
||||
let ratio = SpecifiedRatio::parse(context, input)?;
|
||||
QueryExpressionValue::NumberRatio(Ratio::new(ratio.0.get(), ratio.1.get()))
|
||||
},
|
||||
Evaluator::Resolution(..) => {
|
||||
QueryExpressionValue::Resolution(Resolution::parse(context, input)?)
|
||||
},
|
||||
Evaluator::Enumerated { parser, .. } => {
|
||||
QueryExpressionValue::Enumerated(parser(context, input)?)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
//! Code shared between [media queries][mq] and [container queries][cq].
|
||||
//!
|
||||
//! [mq]: https://drafts.csswg.org/mediaqueries/
|
||||
//! [cq]: https://drafts.csswg.org/css-contain-3/#container-rule
|
||||
|
||||
mod condition;
|
||||
|
||||
#[macro_use]
|
||||
pub mod feature;
|
||||
pub mod feature_expression;
|
||||
pub mod values;
|
||||
|
||||
pub use self::condition::QueryCondition;
|
||||
pub use self::feature::FeatureFlags;
|
||||
pub use self::feature_expression::{QueryFeatureExpression, FeatureType};
|
|
@ -1,36 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
//! Common feature values between media and container features.
|
||||
|
||||
/// The orientation media / container feature.
|
||||
/// https://drafts.csswg.org/mediaqueries-5/#orientation
|
||||
/// https://drafts.csswg.org/css-contain-3/#orientation
|
||||
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
|
||||
#[repr(u8)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum Orientation {
|
||||
Portrait,
|
||||
Landscape,
|
||||
}
|
||||
|
||||
impl Orientation {
|
||||
/// A helper to evaluate a orientation query given a generic size getter.
|
||||
pub fn eval<T>(size: euclid::default::Size2D<T>, value: Option<Self>) -> bool
|
||||
where
|
||||
T: PartialOrd,
|
||||
{
|
||||
let query_orientation = match value {
|
||||
Some(v) => v,
|
||||
None => return true,
|
||||
};
|
||||
|
||||
// Per spec, square viewports should be 'portrait'
|
||||
let is_landscape = size.width > size.height;
|
||||
match query_orientation {
|
||||
Self::Landscape => is_landscape,
|
||||
Self::Portrait => !is_landscape,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@ use crate::selector_parser::PseudoElement;
|
|||
use crate::shared_lock::Locked;
|
||||
use crate::stylesheets::{layer_rule::LayerOrder, Origin};
|
||||
use crate::stylist::{AuthorStylesEnabled, CascadeData, Rule, RuleInclusion, Stylist};
|
||||
use selectors::matching::{MatchingContext, MatchingMode};
|
||||
use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode};
|
||||
use servo_arc::ArcBorrow;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
|
@ -59,7 +59,7 @@ pub fn containing_shadow_ignoring_svg_use<E: TElement>(
|
|||
///
|
||||
/// This is done basically to be able to organize the cascade in smaller
|
||||
/// functions, and be able to reason about it easily.
|
||||
pub struct RuleCollector<'a, 'b: 'a, E>
|
||||
pub struct RuleCollector<'a, 'b: 'a, E, F: 'a>
|
||||
where
|
||||
E: TElement,
|
||||
{
|
||||
|
@ -73,14 +73,16 @@ where
|
|||
rule_inclusion: RuleInclusion,
|
||||
rules: &'a mut ApplicableDeclarationList,
|
||||
context: &'a mut MatchingContext<'b, E::Impl>,
|
||||
flags_setter: &'a mut F,
|
||||
matches_user_and_author_rules: bool,
|
||||
matches_document_author_rules: bool,
|
||||
in_sort_scope: bool,
|
||||
}
|
||||
|
||||
impl<'a, 'b: 'a, E> RuleCollector<'a, 'b, E>
|
||||
impl<'a, 'b: 'a, E, F: 'a> RuleCollector<'a, 'b, E, F>
|
||||
where
|
||||
E: TElement,
|
||||
F: FnMut(&E, ElementSelectorFlags),
|
||||
{
|
||||
/// Trivially construct a new collector.
|
||||
pub fn new(
|
||||
|
@ -93,6 +95,7 @@ where
|
|||
rule_inclusion: RuleInclusion,
|
||||
rules: &'a mut ApplicableDeclarationList,
|
||||
context: &'a mut MatchingContext<'b, E::Impl>,
|
||||
flags_setter: &'a mut F,
|
||||
) -> Self {
|
||||
// When we're matching with matching_mode =
|
||||
// `ForStatelessPseudoeElement`, the "target" for the rule hash is the
|
||||
|
@ -122,6 +125,7 @@ where
|
|||
animation_declarations,
|
||||
rule_inclusion,
|
||||
context,
|
||||
flags_setter,
|
||||
rules,
|
||||
matches_user_and_author_rules,
|
||||
matches_document_author_rules: matches_user_and_author_rules,
|
||||
|
@ -223,9 +227,9 @@ where
|
|||
part_rules,
|
||||
&mut self.rules,
|
||||
&mut self.context,
|
||||
&mut self.flags_setter,
|
||||
cascade_level,
|
||||
cascade_data,
|
||||
&self.stylist,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -242,9 +246,9 @@ where
|
|||
self.rule_hash_target,
|
||||
&mut self.rules,
|
||||
&mut self.context,
|
||||
&mut self.flags_setter,
|
||||
cascade_level,
|
||||
cascade_data,
|
||||
&self.stylist,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -203,6 +203,9 @@ impl RuleTree {
|
|||
/// where it likely did not result from a rigorous performance analysis.)
|
||||
const RULE_TREE_GC_INTERVAL: usize = 300;
|
||||
|
||||
/// Used for some size assertions.
|
||||
pub const RULE_NODE_SIZE: usize = std::mem::size_of::<RuleNode>();
|
||||
|
||||
/// A node in the rule tree.
|
||||
struct RuleNode {
|
||||
/// The root node. Only the root has no root pointer, for obvious reasons.
|
||||
|
@ -765,8 +768,3 @@ impl hash::Hash for StrongRuleNode {
|
|||
(&*self.p as *const RuleNode).hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
// Large pages generate thousands of RuleNode objects.
|
||||
size_of_test!(RuleNode, 80);
|
||||
// StrongRuleNode should be pointer-sized even inside an option.
|
||||
size_of_test!(Option<StrongRuleNode>, 8);
|
||||
|
|
|
@ -20,7 +20,7 @@ mod map;
|
|||
mod source;
|
||||
mod unsafe_box;
|
||||
|
||||
pub use self::core::{RuleTree, StrongRuleNode};
|
||||
pub use self::core::{RuleTree, StrongRuleNode, RULE_NODE_SIZE};
|
||||
pub use self::level::{CascadeLevel, ShadowCascadeOrder};
|
||||
pub use self::source::StyleSource;
|
||||
|
||||
|
|
|
@ -10,11 +10,11 @@ use crate::context::QuirksMode;
|
|||
use crate::dom::TElement;
|
||||
use crate::rule_tree::CascadeLevel;
|
||||
use crate::selector_parser::SelectorImpl;
|
||||
use crate::stylist::{Stylist, CascadeData, Rule, ContainerConditionId};
|
||||
use crate::stylist::{CascadeData, Rule};
|
||||
use crate::AllocErr;
|
||||
use crate::{Atom, LocalName, Namespace, ShrinkIfNeeded, WeakAtom};
|
||||
use precomputed_hash::PrecomputedHash;
|
||||
use selectors::matching::{matches_selector, MatchingContext};
|
||||
use selectors::matching::{matches_selector, ElementSelectorFlags, MatchingContext};
|
||||
use selectors::parser::{Combinator, Component, SelectorIter};
|
||||
use smallvec::SmallVec;
|
||||
use std::collections::hash_map;
|
||||
|
@ -186,33 +186,34 @@ impl SelectorMap<Rule> {
|
|||
///
|
||||
/// Extract matching rules as per element's ID, classes, tag name, etc..
|
||||
/// Sort the Rules at the end to maintain cascading order.
|
||||
pub fn get_all_matching_rules<E>(
|
||||
pub fn get_all_matching_rules<E, F>(
|
||||
&self,
|
||||
element: E,
|
||||
rule_hash_target: E,
|
||||
matching_rules_list: &mut ApplicableDeclarationList,
|
||||
matching_context: &mut MatchingContext<E::Impl>,
|
||||
context: &mut MatchingContext<E::Impl>,
|
||||
flags_setter: &mut F,
|
||||
cascade_level: CascadeLevel,
|
||||
cascade_data: &CascadeData,
|
||||
stylist: &Stylist,
|
||||
) where
|
||||
E: TElement,
|
||||
F: FnMut(&E, ElementSelectorFlags),
|
||||
{
|
||||
if self.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let quirks_mode = matching_context.quirks_mode();
|
||||
let quirks_mode = context.quirks_mode();
|
||||
|
||||
if rule_hash_target.is_root() {
|
||||
SelectorMap::get_matching_rules(
|
||||
element,
|
||||
&self.root,
|
||||
matching_rules_list,
|
||||
matching_context,
|
||||
context,
|
||||
flags_setter,
|
||||
cascade_level,
|
||||
cascade_data,
|
||||
stylist,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -222,10 +223,10 @@ impl SelectorMap<Rule> {
|
|||
element,
|
||||
rules,
|
||||
matching_rules_list,
|
||||
matching_context,
|
||||
context,
|
||||
flags_setter,
|
||||
cascade_level,
|
||||
cascade_data,
|
||||
stylist,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -236,10 +237,10 @@ impl SelectorMap<Rule> {
|
|||
element,
|
||||
rules,
|
||||
matching_rules_list,
|
||||
matching_context,
|
||||
context,
|
||||
flags_setter,
|
||||
cascade_level,
|
||||
cascade_data,
|
||||
stylist,
|
||||
)
|
||||
}
|
||||
});
|
||||
|
@ -251,10 +252,10 @@ impl SelectorMap<Rule> {
|
|||
element,
|
||||
rules,
|
||||
matching_rules_list,
|
||||
matching_context,
|
||||
context,
|
||||
flags_setter,
|
||||
cascade_level,
|
||||
cascade_data,
|
||||
stylist,
|
||||
)
|
||||
}
|
||||
});
|
||||
|
@ -265,10 +266,10 @@ impl SelectorMap<Rule> {
|
|||
element,
|
||||
rules,
|
||||
matching_rules_list,
|
||||
matching_context,
|
||||
context,
|
||||
flags_setter,
|
||||
cascade_level,
|
||||
cascade_data,
|
||||
stylist,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -277,10 +278,10 @@ impl SelectorMap<Rule> {
|
|||
element,
|
||||
rules,
|
||||
matching_rules_list,
|
||||
matching_context,
|
||||
context,
|
||||
flags_setter,
|
||||
cascade_level,
|
||||
cascade_data,
|
||||
stylist,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -288,44 +289,38 @@ impl SelectorMap<Rule> {
|
|||
element,
|
||||
&self.other,
|
||||
matching_rules_list,
|
||||
matching_context,
|
||||
context,
|
||||
flags_setter,
|
||||
cascade_level,
|
||||
cascade_data,
|
||||
stylist,
|
||||
);
|
||||
}
|
||||
|
||||
/// Adds rules in `rules` that match `element` to the `matching_rules` list.
|
||||
pub(crate) fn get_matching_rules<E>(
|
||||
pub(crate) fn get_matching_rules<E, F>(
|
||||
element: E,
|
||||
rules: &[Rule],
|
||||
matching_rules: &mut ApplicableDeclarationList,
|
||||
matching_context: &mut MatchingContext<E::Impl>,
|
||||
context: &mut MatchingContext<E::Impl>,
|
||||
flags_setter: &mut F,
|
||||
cascade_level: CascadeLevel,
|
||||
cascade_data: &CascadeData,
|
||||
stylist: &Stylist,
|
||||
) where
|
||||
E: TElement,
|
||||
F: FnMut(&E, ElementSelectorFlags),
|
||||
{
|
||||
for rule in rules {
|
||||
if !matches_selector(
|
||||
if matches_selector(
|
||||
&rule.selector,
|
||||
0,
|
||||
Some(&rule.hashes),
|
||||
&element,
|
||||
matching_context,
|
||||
context,
|
||||
flags_setter,
|
||||
) {
|
||||
continue;
|
||||
matching_rules
|
||||
.push(rule.to_applicable_declaration_block(cascade_level, cascade_data));
|
||||
}
|
||||
|
||||
if rule.container_condition_id != ContainerConditionId::none() {
|
||||
if !cascade_data.container_condition_matches(rule.container_condition_id, stylist, element) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
matching_rules
|
||||
.push(rule.to_applicable_declaration_block(cascade_level, cascade_data));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,13 +7,13 @@
|
|||
use crate::context::QuirksMode;
|
||||
use crate::custom_properties::CssEnvironment;
|
||||
use crate::font_metrics::FontMetrics;
|
||||
use crate::queries::feature::{AllowsRanges, Evaluator, FeatureFlags, QueryFeatureDescription};
|
||||
use crate::media_queries::media_feature::{AllowsRanges, ParsingRequirements};
|
||||
use crate::media_queries::media_feature::{Evaluator, MediaFeatureDescription};
|
||||
use crate::media_queries::media_feature_expression::RangeOrOperator;
|
||||
use crate::media_queries::MediaType;
|
||||
use crate::properties::ComputedValues;
|
||||
use crate::values::computed::CSSPixelLength;
|
||||
use crate::values::computed::Context;
|
||||
use crate::values::specified::font::FONT_MEDIUM_PX;
|
||||
use crate::values::specified::ViewportVariant;
|
||||
use crate::values::KeyframesName;
|
||||
use app_units::Au;
|
||||
use cssparser::RGBA;
|
||||
|
@ -143,13 +143,8 @@ impl Device {
|
|||
}
|
||||
|
||||
/// Like the above, but records that we've used viewport units.
|
||||
pub fn au_viewport_size_for_viewport_unit_resolution(
|
||||
&self,
|
||||
_: ViewportVariant,
|
||||
) -> UntypedSize2D<Au> {
|
||||
pub fn au_viewport_size_for_viewport_unit_resolution(&self) -> UntypedSize2D<Au> {
|
||||
self.used_viewport_units.store(true, Ordering::Relaxed);
|
||||
// Servo doesn't have dynamic UA interfaces that affect the viewport,
|
||||
// so we can just ignore the ViewportVariant.
|
||||
self.au_viewport_size()
|
||||
}
|
||||
|
||||
|
@ -234,8 +229,16 @@ impl Device {
|
|||
}
|
||||
|
||||
/// https://drafts.csswg.org/mediaqueries-4/#width
|
||||
fn eval_width(context: &Context) -> CSSPixelLength {
|
||||
CSSPixelLength::new(context.device().au_viewport_size().width.to_f32_px())
|
||||
fn eval_width(
|
||||
device: &Device,
|
||||
value: Option<CSSPixelLength>,
|
||||
range_or_operator: Option<RangeOrOperator>,
|
||||
) -> bool {
|
||||
RangeOrOperator::evaluate(
|
||||
range_or_operator,
|
||||
value.map(Au::from),
|
||||
device.au_viewport_size().width,
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
|
||||
|
@ -246,7 +249,7 @@ enum Scan {
|
|||
}
|
||||
|
||||
/// https://drafts.csswg.org/mediaqueries-4/#scan
|
||||
fn eval_scan(_: &Context, _: Option<Scan>) -> bool {
|
||||
fn eval_scan(_: &Device, _: Option<Scan>) -> bool {
|
||||
// Since we doesn't support the 'tv' media type, the 'scan' feature never
|
||||
// matches.
|
||||
false
|
||||
|
@ -254,18 +257,18 @@ fn eval_scan(_: &Context, _: Option<Scan>) -> bool {
|
|||
|
||||
lazy_static! {
|
||||
/// A list with all the media features that Servo supports.
|
||||
pub static ref MEDIA_FEATURES: [QueryFeatureDescription; 2] = [
|
||||
pub static ref MEDIA_FEATURES: [MediaFeatureDescription; 2] = [
|
||||
feature!(
|
||||
atom!("width"),
|
||||
AllowsRanges::Yes,
|
||||
Evaluator::Length(eval_width),
|
||||
FeatureFlags::empty(),
|
||||
ParsingRequirements::empty(),
|
||||
),
|
||||
feature!(
|
||||
atom!("scan"),
|
||||
AllowsRanges::No,
|
||||
keyword_evaluator!(eval_scan, Scan),
|
||||
FeatureFlags::empty(),
|
||||
ParsingRequirements::empty(),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
//! elements can indeed share the same style.
|
||||
|
||||
use crate::bloom::StyleBloom;
|
||||
use crate::context::SharedStyleContext;
|
||||
use crate::context::{SelectorFlagsMap, SharedStyleContext};
|
||||
use crate::dom::TElement;
|
||||
use crate::sharing::{StyleSharingCandidate, StyleSharingTarget};
|
||||
use selectors::NthIndexCache;
|
||||
|
@ -120,6 +120,7 @@ pub fn revalidate<E>(
|
|||
shared_context: &SharedStyleContext,
|
||||
bloom: &StyleBloom<E>,
|
||||
nth_index_cache: &mut NthIndexCache,
|
||||
selector_flags_map: &mut SelectorFlagsMap<E>,
|
||||
) -> bool
|
||||
where
|
||||
E: TElement,
|
||||
|
@ -127,7 +128,7 @@ where
|
|||
let stylist = &shared_context.stylist;
|
||||
|
||||
let for_element =
|
||||
target.revalidation_match_results(stylist, bloom, nth_index_cache);
|
||||
target.revalidation_match_results(stylist, bloom, nth_index_cache, selector_flags_map);
|
||||
|
||||
let for_candidate = candidate.revalidation_match_results(stylist, bloom, nth_index_cache);
|
||||
|
||||
|
|
|
@ -66,8 +66,9 @@
|
|||
|
||||
use crate::applicable_declarations::ApplicableDeclarationBlock;
|
||||
use crate::bloom::StyleBloom;
|
||||
use crate::context::{SharedStyleContext, StyleContext};
|
||||
use crate::context::{SelectorFlagsMap, SharedStyleContext, StyleContext};
|
||||
use crate::dom::{SendElement, TElement};
|
||||
use crate::matching::MatchMethods;
|
||||
use crate::properties::ComputedValues;
|
||||
use crate::rule_tree::StrongRuleNode;
|
||||
use crate::style_resolver::{PrimaryStyle, ResolvedElementStyles};
|
||||
|
@ -75,7 +76,7 @@ use crate::stylist::Stylist;
|
|||
use crate::values::AtomIdent;
|
||||
use atomic_refcell::{AtomicRefCell, AtomicRefMut};
|
||||
use owning_ref::OwningHandle;
|
||||
use selectors::matching::{VisitedHandlingMode, NeedsSelectorFlags};
|
||||
use selectors::matching::{ElementSelectorFlags, VisitedHandlingMode};
|
||||
use selectors::NthIndexCache;
|
||||
use servo_arc::Arc;
|
||||
use smallbitvec::SmallBitVec;
|
||||
|
@ -222,17 +223,18 @@ impl ValidationData {
|
|||
/// Computes the revalidation results if needed, and returns it.
|
||||
/// Inline so we know at compile time what bloom_known_valid is.
|
||||
#[inline]
|
||||
fn revalidation_match_results<E>(
|
||||
fn revalidation_match_results<E, F>(
|
||||
&mut self,
|
||||
element: E,
|
||||
stylist: &Stylist,
|
||||
bloom: &StyleBloom<E>,
|
||||
nth_index_cache: &mut NthIndexCache,
|
||||
bloom_known_valid: bool,
|
||||
needs_selector_flags: NeedsSelectorFlags,
|
||||
flags_setter: &mut F,
|
||||
) -> &SmallBitVec
|
||||
where
|
||||
E: TElement,
|
||||
F: FnMut(&E, ElementSelectorFlags),
|
||||
{
|
||||
self.revalidation_match_results.get_or_insert_with(|| {
|
||||
// The bloom filter may already be set up for our element.
|
||||
|
@ -255,7 +257,7 @@ impl ValidationData {
|
|||
element,
|
||||
bloom_to_use,
|
||||
nth_index_cache,
|
||||
needs_selector_flags,
|
||||
flags_setter,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
@ -325,9 +327,7 @@ impl<E: TElement> StyleSharingCandidate<E> {
|
|||
bloom,
|
||||
nth_index_cache,
|
||||
/* bloom_known_valid = */ false,
|
||||
// The candidate must already have the right bits already, if
|
||||
// needed.
|
||||
NeedsSelectorFlags::No,
|
||||
&mut |_, _| {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -384,6 +384,7 @@ impl<E: TElement> StyleSharingTarget<E> {
|
|||
stylist: &Stylist,
|
||||
bloom: &StyleBloom<E>,
|
||||
nth_index_cache: &mut NthIndexCache,
|
||||
selector_flags_map: &mut SelectorFlagsMap<E>,
|
||||
) -> &SmallBitVec {
|
||||
// It's important to set the selector flags. Otherwise, if we succeed in
|
||||
// sharing the style, we may not set the slow selector flags for the
|
||||
|
@ -400,13 +401,18 @@ impl<E: TElement> StyleSharingTarget<E> {
|
|||
// The style sharing cache will get a hit for the second span. When the
|
||||
// child span is subsequently removed from the DOM, missing selector
|
||||
// flags would cause us to miss the restyle on the second span.
|
||||
let element = self.element;
|
||||
let mut set_selector_flags = |el: &E, flags: ElementSelectorFlags| {
|
||||
element.apply_selector_flags(selector_flags_map, el, flags);
|
||||
};
|
||||
|
||||
self.validation_data.revalidation_match_results(
|
||||
self.element,
|
||||
stylist,
|
||||
bloom,
|
||||
nth_index_cache,
|
||||
/* bloom_known_valid = */ true,
|
||||
NeedsSelectorFlags::Yes,
|
||||
&mut set_selector_flags,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -417,6 +423,7 @@ impl<E: TElement> StyleSharingTarget<E> {
|
|||
) -> Option<ResolvedElementStyles> {
|
||||
let cache = &mut context.thread_local.sharing_cache;
|
||||
let shared_context = &context.shared;
|
||||
let selector_flags_map = &mut context.thread_local.selector_flags;
|
||||
let bloom_filter = &context.thread_local.bloom_filter;
|
||||
let nth_index_cache = &mut context.thread_local.nth_index_cache;
|
||||
|
||||
|
@ -436,6 +443,7 @@ impl<E: TElement> StyleSharingTarget<E> {
|
|||
|
||||
cache.share_style_if_possible(
|
||||
shared_context,
|
||||
selector_flags_map,
|
||||
bloom_filter,
|
||||
nth_index_cache,
|
||||
self,
|
||||
|
@ -623,13 +631,13 @@ impl<E: TElement> StyleSharingCache<E> {
|
|||
//
|
||||
// These are things we don't check in the candidate match because they
|
||||
// are either uncommon or expensive.
|
||||
let ui_style = style.style().get_ui();
|
||||
if ui_style.specifies_transitions() {
|
||||
let box_style = style.style().get_box();
|
||||
if box_style.specifies_transitions() {
|
||||
debug!("Failing to insert to the cache: transitions");
|
||||
return;
|
||||
}
|
||||
|
||||
if ui_style.specifies_animations() {
|
||||
if box_style.specifies_animations() {
|
||||
debug!("Failing to insert to the cache: animations");
|
||||
return;
|
||||
}
|
||||
|
@ -659,6 +667,7 @@ impl<E: TElement> StyleSharingCache<E> {
|
|||
fn share_style_if_possible(
|
||||
&mut self,
|
||||
shared_context: &SharedStyleContext,
|
||||
selector_flags_map: &mut SelectorFlagsMap<E>,
|
||||
bloom_filter: &StyleBloom<E>,
|
||||
nth_index_cache: &mut NthIndexCache,
|
||||
target: &mut StyleSharingTarget<E>,
|
||||
|
@ -691,6 +700,7 @@ impl<E: TElement> StyleSharingCache<E> {
|
|||
&shared_context,
|
||||
bloom_filter,
|
||||
nth_index_cache,
|
||||
selector_flags_map,
|
||||
shared_context,
|
||||
)
|
||||
})
|
||||
|
@ -702,6 +712,7 @@ impl<E: TElement> StyleSharingCache<E> {
|
|||
shared: &SharedStyleContext,
|
||||
bloom: &StyleBloom<E>,
|
||||
nth_index_cache: &mut NthIndexCache,
|
||||
selector_flags_map: &mut SelectorFlagsMap<E>,
|
||||
shared_context: &SharedStyleContext,
|
||||
) -> Option<ResolvedElementStyles> {
|
||||
debug_assert!(!target.is_in_native_anonymous_subtree());
|
||||
|
@ -806,6 +817,7 @@ impl<E: TElement> StyleSharingCache<E> {
|
|||
shared,
|
||||
bloom,
|
||||
nth_index_cache,
|
||||
selector_flags_map,
|
||||
) {
|
||||
trace!("Miss: Revalidation");
|
||||
return None;
|
||||
|
|
|
@ -15,7 +15,7 @@ use crate::rule_tree::StrongRuleNode;
|
|||
use crate::selector_parser::{PseudoElement, SelectorImpl};
|
||||
use crate::stylist::RuleInclusion;
|
||||
use log::Level::Trace;
|
||||
use selectors::matching::{NeedsSelectorFlags, MatchingContext};
|
||||
use selectors::matching::{ElementSelectorFlags, MatchingContext};
|
||||
use selectors::matching::{MatchingMode, VisitedHandlingMode};
|
||||
use servo_arc::Arc;
|
||||
|
||||
|
@ -451,6 +451,7 @@ where
|
|||
);
|
||||
let mut applicable_declarations = ApplicableDeclarationList::new();
|
||||
|
||||
let map = &mut self.context.thread_local.selector_flags;
|
||||
let bloom_filter = self.context.thread_local.bloom_filter.filter();
|
||||
let nth_index_cache = &mut self.context.thread_local.nth_index_cache;
|
||||
let mut matching_context = MatchingContext::new_for_visited(
|
||||
|
@ -459,22 +460,29 @@ where
|
|||
Some(nth_index_cache),
|
||||
visited_handling,
|
||||
self.context.shared.quirks_mode(),
|
||||
NeedsSelectorFlags::Yes,
|
||||
);
|
||||
|
||||
let stylist = &self.context.shared.stylist;
|
||||
let implemented_pseudo = self.element.implemented_pseudo_element();
|
||||
// Compute the primary rule node.
|
||||
stylist.push_applicable_declarations(
|
||||
self.element,
|
||||
implemented_pseudo.as_ref(),
|
||||
self.element.style_attribute(),
|
||||
self.element.smil_override(),
|
||||
self.element.animation_declarations(self.context.shared),
|
||||
self.rule_inclusion,
|
||||
&mut applicable_declarations,
|
||||
&mut matching_context,
|
||||
);
|
||||
{
|
||||
let resolving_element = self.element;
|
||||
let mut set_selector_flags = |element: &E, flags: ElementSelectorFlags| {
|
||||
resolving_element.apply_selector_flags(map, element, flags);
|
||||
};
|
||||
|
||||
// Compute the primary rule node.
|
||||
stylist.push_applicable_declarations(
|
||||
self.element,
|
||||
implemented_pseudo.as_ref(),
|
||||
self.element.style_attribute(),
|
||||
self.element.smil_override(),
|
||||
self.element.animation_declarations(self.context.shared),
|
||||
self.rule_inclusion,
|
||||
&mut applicable_declarations,
|
||||
&mut matching_context,
|
||||
&mut set_selector_flags,
|
||||
);
|
||||
}
|
||||
|
||||
// FIXME(emilio): This is a hack for animations, and should go away.
|
||||
self.element.unset_dirty_style_attribute();
|
||||
|
@ -532,9 +540,14 @@ where
|
|||
Some(nth_index_cache),
|
||||
visited_handling,
|
||||
self.context.shared.quirks_mode(),
|
||||
NeedsSelectorFlags::Yes,
|
||||
);
|
||||
|
||||
let map = &mut self.context.thread_local.selector_flags;
|
||||
let resolving_element = self.element;
|
||||
let mut set_selector_flags = |element: &E, flags: ElementSelectorFlags| {
|
||||
resolving_element.apply_selector_flags(map, element, flags);
|
||||
};
|
||||
|
||||
// NB: We handle animation rules for ::before and ::after when
|
||||
// traversing them.
|
||||
stylist.push_applicable_declarations(
|
||||
|
@ -546,6 +559,7 @@ where
|
|||
self.rule_inclusion,
|
||||
&mut applicable_declarations,
|
||||
&mut matching_context,
|
||||
&mut set_selector_flags,
|
||||
);
|
||||
|
||||
if applicable_declarations.is_empty() {
|
||||
|
|
|
@ -1,281 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
//! A [`@container`][container] rule.
|
||||
//!
|
||||
//! [container]: https://drafts.csswg.org/css-contain-3/#container-rule
|
||||
|
||||
use crate::logical_geometry::{WritingMode, LogicalSize};
|
||||
use crate::dom::TElement;
|
||||
use crate::media_queries::Device;
|
||||
use crate::parser::ParserContext;
|
||||
use crate::queries::{QueryCondition, FeatureType};
|
||||
use crate::queries::feature::{AllowsRanges, Evaluator, FeatureFlags, QueryFeatureDescription};
|
||||
use crate::queries::values::Orientation;
|
||||
use crate::str::CssStringWriter;
|
||||
use crate::shared_lock::{
|
||||
DeepCloneParams, DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard,
|
||||
};
|
||||
use crate::values::specified::ContainerName;
|
||||
use crate::values::computed::{Context, CSSPixelLength, Ratio};
|
||||
use crate::properties::ComputedValues;
|
||||
use crate::stylesheets::CssRules;
|
||||
use app_units::Au;
|
||||
use cssparser::{SourceLocation, Parser};
|
||||
use euclid::default::Size2D;
|
||||
#[cfg(feature = "gecko")]
|
||||
use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
|
||||
use servo_arc::Arc;
|
||||
use std::fmt::{self, Write};
|
||||
use style_traits::{CssWriter, ToCss, ParseError};
|
||||
|
||||
/// A container rule.
|
||||
#[derive(Debug, ToShmem)]
|
||||
pub struct ContainerRule {
|
||||
/// The container query and name.
|
||||
pub condition: Arc<ContainerCondition>,
|
||||
/// The nested rules inside the block.
|
||||
pub rules: Arc<Locked<CssRules>>,
|
||||
/// The source position where this rule was found.
|
||||
pub source_location: SourceLocation,
|
||||
}
|
||||
|
||||
impl ContainerRule {
|
||||
/// Returns the query condition.
|
||||
pub fn query_condition(&self) -> &QueryCondition {
|
||||
&self.condition.condition
|
||||
}
|
||||
|
||||
/// Measure heap usage.
|
||||
#[cfg(feature = "gecko")]
|
||||
pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
|
||||
// Measurement of other fields may be added later.
|
||||
self.rules.unconditional_shallow_size_of(ops)
|
||||
+ self.rules.read_with(guard).size_of(guard, ops)
|
||||
}
|
||||
}
|
||||
|
||||
impl DeepCloneWithLock for ContainerRule {
|
||||
fn deep_clone_with_lock(
|
||||
&self,
|
||||
lock: &SharedRwLock,
|
||||
guard: &SharedRwLockReadGuard,
|
||||
params: &DeepCloneParams,
|
||||
) -> Self {
|
||||
let rules = self.rules.read_with(guard);
|
||||
Self {
|
||||
condition: self.condition.clone(),
|
||||
rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard, params))),
|
||||
source_location: self.source_location.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToCssWithGuard for ContainerRule {
|
||||
fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
|
||||
dest.write_str("@container ")?;
|
||||
{
|
||||
let mut writer = CssWriter::new(dest);
|
||||
if !self.condition.name.is_none() {
|
||||
self.condition.name.to_css(&mut writer)?;
|
||||
writer.write_char(' ')?;
|
||||
}
|
||||
self.condition.condition.to_css(&mut writer)?;
|
||||
}
|
||||
self.rules.read_with(guard).to_css_block(guard, dest)
|
||||
}
|
||||
}
|
||||
|
||||
/// A container condition and filter, combined.
|
||||
#[derive(Debug, ToShmem)]
|
||||
pub struct ContainerCondition {
|
||||
name: ContainerName,
|
||||
condition: QueryCondition,
|
||||
flags: FeatureFlags,
|
||||
}
|
||||
|
||||
impl ContainerCondition {
|
||||
/// Parse a container condition.
|
||||
pub fn parse<'a>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'a, '_>,
|
||||
) -> Result<Self, ParseError<'a>> {
|
||||
use crate::parser::Parse;
|
||||
|
||||
// FIXME: This is a bit ambiguous:
|
||||
// https://github.com/w3c/csswg-drafts/issues/7203
|
||||
let name = input.try_parse(|input| {
|
||||
ContainerName::parse(context, input)
|
||||
}).ok().unwrap_or_else(ContainerName::none);
|
||||
let condition = QueryCondition::parse(context, input, FeatureType::Container)?;
|
||||
let flags = condition.cumulative_flags();
|
||||
Ok(Self { name, condition, flags })
|
||||
}
|
||||
|
||||
fn valid_container_info<E>(&self, potential_container: E) -> Option<(ContainerInfo, Arc<ComputedValues>)>
|
||||
where
|
||||
E: TElement,
|
||||
{
|
||||
use crate::values::computed::ContainerType;
|
||||
|
||||
fn container_type_axes(ty_: ContainerType, wm: WritingMode) -> FeatureFlags {
|
||||
if ty_.intersects(ContainerType::SIZE) {
|
||||
return FeatureFlags::all_container_axes()
|
||||
}
|
||||
if ty_.intersects(ContainerType::INLINE_SIZE) {
|
||||
let physical_axis = if wm.is_vertical() {
|
||||
FeatureFlags::CONTAINER_REQUIRES_HEIGHT_AXIS
|
||||
} else {
|
||||
FeatureFlags::CONTAINER_REQUIRES_WIDTH_AXIS
|
||||
};
|
||||
return FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS | physical_axis
|
||||
}
|
||||
FeatureFlags::empty()
|
||||
}
|
||||
|
||||
let data = match potential_container.borrow_data() {
|
||||
Some(data) => data,
|
||||
None => return None,
|
||||
};
|
||||
let style = data.styles.primary();
|
||||
let wm = style.writing_mode;
|
||||
let box_style = style.get_box();
|
||||
|
||||
// Filter by container-type.
|
||||
let container_type = box_style.clone_container_type();
|
||||
let available_axes = container_type_axes(container_type, wm);
|
||||
if !available_axes.contains(self.flags.container_axes()) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Filter by container-name.
|
||||
let container_name = box_style.clone_container_name();
|
||||
for filter_name in self.name.0.iter() {
|
||||
if !container_name.0.contains(filter_name) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
let size = potential_container.primary_box_size();
|
||||
let style = style.clone();
|
||||
Some((ContainerInfo { size, wm }, style))
|
||||
}
|
||||
|
||||
fn find_container<E>(&self, mut e: E) -> Option<(ContainerInfo, Arc<ComputedValues>)>
|
||||
where
|
||||
E: TElement,
|
||||
{
|
||||
while let Some(element) = e.traversal_parent() {
|
||||
if let Some(info) = self.valid_container_info(element) {
|
||||
return Some(info);
|
||||
}
|
||||
e = element;
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Tries to match a container query condition for a given element.
|
||||
pub(crate) fn matches<E>(&self, device: &Device, element: E) -> bool
|
||||
where
|
||||
E: TElement,
|
||||
{
|
||||
let info = self.find_container(element);
|
||||
Context::for_container_query_evaluation(device, info, |context| {
|
||||
self.condition.matches(context)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Information needed to evaluate an individual container query.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct ContainerInfo {
|
||||
size: Size2D<Au>,
|
||||
wm: WritingMode,
|
||||
}
|
||||
|
||||
fn get_container(context: &Context) -> ContainerInfo {
|
||||
if let Some(ref info) = context.container_info {
|
||||
return info.clone()
|
||||
}
|
||||
ContainerInfo {
|
||||
size: context.device().au_viewport_size(),
|
||||
wm: WritingMode::horizontal_tb(),
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_width(context: &Context) -> CSSPixelLength {
|
||||
let info = get_container(context);
|
||||
CSSPixelLength::new(info.size.width.to_f32_px())
|
||||
}
|
||||
|
||||
fn eval_height(context: &Context) -> CSSPixelLength {
|
||||
let info = get_container(context);
|
||||
CSSPixelLength::new(info.size.height.to_f32_px())
|
||||
}
|
||||
|
||||
fn eval_inline_size(context: &Context) -> CSSPixelLength {
|
||||
let info = get_container(context);
|
||||
CSSPixelLength::new(LogicalSize::from_physical(info.wm, info.size).inline.to_f32_px())
|
||||
}
|
||||
|
||||
fn eval_block_size(context: &Context) -> CSSPixelLength {
|
||||
let info = get_container(context);
|
||||
CSSPixelLength::new(LogicalSize::from_physical(info.wm, info.size).block.to_f32_px())
|
||||
}
|
||||
|
||||
fn eval_aspect_ratio(context: &Context) -> Ratio {
|
||||
let info = get_container(context);
|
||||
Ratio::new(info.size.width.0 as f32, info.size.height.0 as f32)
|
||||
}
|
||||
|
||||
fn eval_orientation(context: &Context, value: Option<Orientation>) -> bool {
|
||||
let info = get_container(context);
|
||||
Orientation::eval(info.size, value)
|
||||
}
|
||||
|
||||
/// https://drafts.csswg.org/css-contain-3/#container-features
|
||||
///
|
||||
/// TODO: Support style queries, perhaps.
|
||||
pub static CONTAINER_FEATURES: [QueryFeatureDescription; 6] = [
|
||||
feature!(
|
||||
atom!("width"),
|
||||
AllowsRanges::Yes,
|
||||
Evaluator::Length(eval_width),
|
||||
FeatureFlags::CONTAINER_REQUIRES_WIDTH_AXIS,
|
||||
),
|
||||
feature!(
|
||||
atom!("height"),
|
||||
AllowsRanges::Yes,
|
||||
Evaluator::Length(eval_height),
|
||||
FeatureFlags::CONTAINER_REQUIRES_HEIGHT_AXIS,
|
||||
),
|
||||
feature!(
|
||||
atom!("inline-size"),
|
||||
AllowsRanges::Yes,
|
||||
Evaluator::Length(eval_inline_size),
|
||||
FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS,
|
||||
),
|
||||
feature!(
|
||||
atom!("block-size"),
|
||||
AllowsRanges::Yes,
|
||||
Evaluator::Length(eval_block_size),
|
||||
FeatureFlags::CONTAINER_REQUIRES_BLOCK_AXIS,
|
||||
),
|
||||
feature!(
|
||||
atom!("aspect-ratio"),
|
||||
AllowsRanges::Yes,
|
||||
Evaluator::NumberRatio(eval_aspect_ratio),
|
||||
// XXX from_bits_truncate is const, but the pipe operator isn't, so this
|
||||
// works around it.
|
||||
FeatureFlags::from_bits_truncate(FeatureFlags::CONTAINER_REQUIRES_BLOCK_AXIS.bits() | FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS.bits()),
|
||||
),
|
||||
feature!(
|
||||
atom!("orientation"),
|
||||
AllowsRanges::No,
|
||||
keyword_evaluator!(eval_orientation, Orientation),
|
||||
FeatureFlags::from_bits_truncate(FeatureFlags::CONTAINER_REQUIRES_BLOCK_AXIS.bits() | FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS.bits()),
|
||||
),
|
||||
];
|
|
@ -2,7 +2,7 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
//! A [`@layer`][layer] rule.
|
||||
//! A [`@layer`][layer] urle.
|
||||
//!
|
||||
//! [layer]: https://drafts.csswg.org/css-cascade-5/#layering
|
||||
|
||||
|
@ -61,6 +61,17 @@ impl LayerOrder {
|
|||
}
|
||||
}
|
||||
|
||||
/// The id of a given layer, a sequentially-increasing identifier.
|
||||
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord)]
|
||||
pub struct LayerId(pub u32);
|
||||
|
||||
impl LayerId {
|
||||
/// The id of the root layer.
|
||||
pub const fn root() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
}
|
||||
|
||||
/// A `<layer-name>`: https://drafts.csswg.org/css-cascade-5/#typedef-layer-name
|
||||
#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToShmem)]
|
||||
pub struct LayerName(pub SmallVec<[AtomIdent; 1]>);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
//! An [`@media`][media] rule.
|
||||
//! An [`@media`][media] urle.
|
||||
//!
|
||||
//! [media]: https://drafts.csswg.org/css-conditional/#at-ruledef-media
|
||||
|
||||
|
@ -18,7 +18,7 @@ use servo_arc::Arc;
|
|||
use std::fmt::{self, Write};
|
||||
use style_traits::{CssWriter, ToCss};
|
||||
|
||||
/// An [`@media`][media] rule.
|
||||
/// An [`@media`][media] urle.
|
||||
///
|
||||
/// [media]: https://drafts.csswg.org/css-conditional/#at-ruledef-media
|
||||
#[derive(Debug, ToShmem)]
|
||||
|
|
|
@ -12,7 +12,6 @@ pub mod font_feature_values_rule;
|
|||
pub mod import_rule;
|
||||
pub mod keyframes_rule;
|
||||
pub mod layer_rule;
|
||||
pub mod container_rule;
|
||||
mod loader;
|
||||
mod media_rule;
|
||||
mod namespace_rule;
|
||||
|
@ -54,7 +53,6 @@ pub use self::import_rule::ImportRule;
|
|||
pub use self::keyframes_rule::KeyframesRule;
|
||||
pub use self::layer_rule::{LayerBlockRule, LayerStatementRule};
|
||||
pub use self::loader::StylesheetLoader;
|
||||
pub use self::container_rule::ContainerRule;
|
||||
pub use self::media_rule::MediaRule;
|
||||
pub use self::namespace_rule::NamespaceRule;
|
||||
pub use self::origin::{Origin, OriginSet, OriginSetIterator, PerOrigin, PerOriginIter};
|
||||
|
@ -255,7 +253,6 @@ pub enum CssRule {
|
|||
Import(Arc<Locked<ImportRule>>),
|
||||
Style(Arc<Locked<StyleRule>>),
|
||||
Media(Arc<Locked<MediaRule>>),
|
||||
Container(Arc<Locked<ContainerRule>>),
|
||||
FontFace(Arc<Locked<FontFaceRule>>),
|
||||
FontFeatureValues(Arc<Locked<FontFeatureValuesRule>>),
|
||||
CounterStyle(Arc<Locked<CounterStyleRule>>),
|
||||
|
@ -290,10 +287,6 @@ impl CssRule {
|
|||
lock.unconditional_shallow_size_of(ops) + lock.read_with(guard).size_of(guard, ops)
|
||||
},
|
||||
|
||||
CssRule::Container(ref lock) => {
|
||||
lock.unconditional_shallow_size_of(ops) + lock.read_with(guard).size_of(guard, ops)
|
||||
},
|
||||
|
||||
CssRule::FontFace(_) => 0,
|
||||
CssRule::FontFeatureValues(_) => 0,
|
||||
CssRule::CounterStyle(_) => 0,
|
||||
|
@ -351,7 +344,6 @@ pub enum CssRuleType {
|
|||
LayerBlock = 16,
|
||||
LayerStatement = 17,
|
||||
ScrollTimeline = 18,
|
||||
Container = 19,
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
|
@ -381,7 +373,16 @@ impl CssRule {
|
|||
CssRule::LayerBlock(_) => CssRuleType::LayerBlock,
|
||||
CssRule::LayerStatement(_) => CssRuleType::LayerStatement,
|
||||
CssRule::ScrollTimeline(_) => CssRuleType::ScrollTimeline,
|
||||
CssRule::Container(_) => CssRuleType::Container,
|
||||
}
|
||||
}
|
||||
|
||||
fn rule_state(&self) -> State {
|
||||
match *self {
|
||||
// CssRule::Charset(..) => State::Start,
|
||||
CssRule::Import(..) => State::Imports,
|
||||
CssRule::Namespace(..) => State::Namespaces,
|
||||
// TODO(emilio): Do we need something for EarlyLayers?
|
||||
_ => State::Body,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -459,12 +460,6 @@ impl DeepCloneWithLock for CssRule {
|
|||
lock.wrap(rule.deep_clone_with_lock(lock, guard, params)),
|
||||
))
|
||||
},
|
||||
CssRule::Container(ref arc) => {
|
||||
let rule = arc.read_with(guard);
|
||||
CssRule::Container(Arc::new(
|
||||
lock.wrap(rule.deep_clone_with_lock(lock, guard, params)),
|
||||
))
|
||||
},
|
||||
CssRule::Media(ref arc) => {
|
||||
let rule = arc.read_with(guard);
|
||||
CssRule::Media(Arc::new(
|
||||
|
@ -550,7 +545,6 @@ impl ToCssWithGuard for CssRule {
|
|||
CssRule::LayerBlock(ref lock) => lock.read_with(guard).to_css(guard, dest),
|
||||
CssRule::LayerStatement(ref lock) => lock.read_with(guard).to_css(guard, dest),
|
||||
CssRule::ScrollTimeline(ref lock) => lock.read_with(guard).to_css(guard, dest),
|
||||
CssRule::Container(ref lock) => lock.read_with(guard).to_css(guard, dest),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -153,17 +153,21 @@ impl CssRulesHelpers for RawOffsetArc<Locked<CssRules>> {
|
|||
}
|
||||
|
||||
// Computes the parser state at the given index
|
||||
let insert_rule_context = InsertRuleContext {
|
||||
rule_list: &rules.0,
|
||||
index,
|
||||
};
|
||||
|
||||
let state = if nested {
|
||||
State::Body
|
||||
} else if index == 0 {
|
||||
State::Start
|
||||
} else {
|
||||
insert_rule_context.max_rule_state_at_index(index - 1)
|
||||
rules
|
||||
.0
|
||||
.get(index - 1)
|
||||
.map(CssRule::rule_state)
|
||||
.unwrap_or(State::Body)
|
||||
};
|
||||
|
||||
let insert_rule_context = InsertRuleContext {
|
||||
rule_list: &rules.0,
|
||||
index,
|
||||
};
|
||||
|
||||
// Steps 3, 4, 5, 6
|
||||
|
|
|
@ -13,7 +13,6 @@ use crate::properties::parse_property_declaration_list;
|
|||
use crate::selector_parser::{SelectorImpl, SelectorParser};
|
||||
use crate::shared_lock::{Locked, SharedRwLock};
|
||||
use crate::str::starts_with_ignore_ascii_case;
|
||||
use crate::stylesheets::container_rule::{ContainerRule, ContainerCondition};
|
||||
use crate::stylesheets::document_rule::DocumentCondition;
|
||||
use crate::stylesheets::font_feature_values_rule::parse_family_name_list;
|
||||
use crate::stylesheets::import_rule::ImportLayer;
|
||||
|
@ -46,36 +45,6 @@ pub struct InsertRuleContext<'a> {
|
|||
pub index: usize,
|
||||
}
|
||||
|
||||
impl<'a> InsertRuleContext<'a> {
|
||||
/// Returns the max rule state allowable for insertion at a given index in
|
||||
/// the rule list.
|
||||
pub fn max_rule_state_at_index(&self, index: usize) -> State {
|
||||
let rule = match self.rule_list.get(index) {
|
||||
Some(rule) => rule,
|
||||
None => return State::Body,
|
||||
};
|
||||
match rule {
|
||||
CssRule::Import(..) => State::Imports,
|
||||
CssRule::Namespace(..) => State::Namespaces,
|
||||
CssRule::LayerStatement(..) => {
|
||||
// If there are @import / @namespace after this layer, then
|
||||
// we're in the early-layers phase, otherwise we're in the body
|
||||
// and everything is fair game.
|
||||
let next_non_layer_statement_rule = self.rule_list[index + 1..]
|
||||
.iter()
|
||||
.find(|r| !matches!(*r, CssRule::LayerStatement(..)));
|
||||
if let Some(non_layer) = next_non_layer_statement_rule {
|
||||
if matches!(*non_layer, CssRule::Import(..) | CssRule::Namespace(..)) {
|
||||
return State::EarlyLayers;
|
||||
}
|
||||
}
|
||||
State::Body
|
||||
}
|
||||
_ => State::Body,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The parser for the top-level rules in a stylesheet.
|
||||
pub struct TopLevelRuleParser<'a> {
|
||||
/// A reference to the lock we need to use to create rules.
|
||||
|
@ -133,8 +102,12 @@ impl<'b> TopLevelRuleParser<'b> {
|
|||
None => return true,
|
||||
};
|
||||
|
||||
let max_rule_state = ctx.max_rule_state_at_index(ctx.index);
|
||||
if new_state > max_rule_state {
|
||||
let next_rule_state = match ctx.rule_list.get(ctx.index) {
|
||||
None => return true,
|
||||
Some(rule) => rule.rule_state(),
|
||||
};
|
||||
|
||||
if new_state > next_rule_state {
|
||||
self.dom_error = Some(RulesMutateError::HierarchyRequest);
|
||||
return false;
|
||||
}
|
||||
|
@ -189,8 +162,6 @@ pub enum AtRulePrelude {
|
|||
CounterStyle(CustomIdent),
|
||||
/// A @media rule prelude, with its media queries.
|
||||
Media(Arc<Locked<MediaList>>),
|
||||
/// A @container rule prelude.
|
||||
Container(Arc<ContainerCondition>),
|
||||
/// An @supports rule, with its conditional
|
||||
Supports(SupportsCondition),
|
||||
/// A @viewport rule prelude.
|
||||
|
@ -291,23 +262,11 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> {
|
|||
self.dom_error = Some(RulesMutateError::HierarchyRequest);
|
||||
return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedCharsetRule))
|
||||
},
|
||||
"layer" => {
|
||||
let state_to_check = if self.state <= State::EarlyLayers {
|
||||
// The real state depends on whether there's a block or not.
|
||||
// We don't know that yet, but the parse_block check deals
|
||||
// with that.
|
||||
State::EarlyLayers
|
||||
} else {
|
||||
State::Body
|
||||
};
|
||||
if !self.check_state(state_to_check) {
|
||||
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
// All other rules have blocks, so we do this check early in
|
||||
// parse_block instead.
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if !self.check_state(State::Body) {
|
||||
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
|
||||
}
|
||||
|
||||
AtRuleParser::parse_prelude(&mut self.nested(), name, input)
|
||||
|
@ -320,9 +279,6 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> {
|
|||
start: &ParserState,
|
||||
input: &mut Parser<'i, 't>,
|
||||
) -> Result<Self::AtRule, ParseError<'i>> {
|
||||
if !self.check_state(State::Body) {
|
||||
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
|
||||
}
|
||||
let rule = AtRuleParser::parse_block(&mut self.nested(), prelude, start, input)?;
|
||||
self.state = State::Body;
|
||||
Ok((start.position(), rule))
|
||||
|
@ -454,16 +410,6 @@ impl<'a, 'b> NestedRuleParser<'a, 'b> {
|
|||
}
|
||||
}
|
||||
|
||||
fn container_queries_enabled() -> bool {
|
||||
#[cfg(feature = "gecko")]
|
||||
return static_prefs::pref!("layout.css.container-queries.enabled");
|
||||
#[cfg(feature = "servo")]
|
||||
return servo_config::prefs::pref_map()
|
||||
.get("layout.container-queries.enabled")
|
||||
.as_bool()
|
||||
.unwrap_or(false);
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> {
|
||||
type Prelude = AtRulePrelude;
|
||||
type AtRule = CssRule;
|
||||
|
@ -487,10 +433,6 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> {
|
|||
"font-face" => {
|
||||
AtRulePrelude::FontFace
|
||||
},
|
||||
"container" if container_queries_enabled() => {
|
||||
let condition = Arc::new(ContainerCondition::parse(self.context, input)?);
|
||||
AtRulePrelude::Container(condition)
|
||||
},
|
||||
"layer" => {
|
||||
let names = input.try_parse(|input| {
|
||||
input.parse_comma_separated(|input| {
|
||||
|
@ -672,15 +614,6 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> {
|
|||
},
|
||||
))))
|
||||
},
|
||||
AtRulePrelude::Container(condition) => {
|
||||
Ok(CssRule::Container(Arc::new(self.shared_lock.wrap(
|
||||
ContainerRule {
|
||||
condition,
|
||||
rules: self.parse_nested_rules(input, CssRuleType::Container),
|
||||
source_location: start.source_location(),
|
||||
},
|
||||
))))
|
||||
},
|
||||
AtRulePrelude::Layer(names) => {
|
||||
let name = match names.len() {
|
||||
0 | 1 => names.into_iter().next(),
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue