Revert "Backport several style changes from Gecko (5) (#30099)" (#30104)

This reverts commit 8e15389cae.
This commit is contained in:
Oriol Brufau 2023-08-16 08:24:42 +02:00 committed by GitHub
parent 8e15389cae
commit d6ae8dc112
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
152 changed files with 4622 additions and 5862 deletions

17
Cargo.lock generated
View file

@ -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",
]

View file

@ -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

View file

@ -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() {

View file

@ -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" }

View file

@ -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(),
},
},

View file

@ -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

View file

@ -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 {

View file

@ -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;

View file

@ -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,

View file

@ -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,

View file

@ -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);

View file

@ -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

View 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
);

View file

@ -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 {

View file

@ -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,

View file

@ -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);

View file

@ -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,

View file

@ -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

View file

@ -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(),

View file

@ -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" }

View file

@ -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())

View file

@ -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;

View 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
);

View file

@ -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

View file

@ -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 {

View file

@ -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)

View file

@ -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>

View file

@ -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 }

View file

@ -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 {

View file

@ -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;

View file

@ -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()
}

View file

@ -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 )* ]

View file

@ -1 +0,0 @@
disable_all_formatting = true

View file

@ -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;

View file

@ -8,6 +8,3 @@ publish = false
[lib]
path = "lib.rs"
[dependencies]
static_assertions = "1.1"

View file

@ -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!()
)
}
}
};
}

View 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"

View file

@ -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,

View file

@ -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);

View file

@ -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();

View file

@ -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> {

View file

@ -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):

View file

@ -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 {

View file

@ -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

View file

@ -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 {

View file

@ -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;
}
}

View file

@ -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]);

View file

@ -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);

View file

@ -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"
),
];

View file

@ -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() {

View file

@ -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, _, _),
]
}

View file

@ -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);

View file

@ -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);

View file

@ -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();

View file

@ -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 {

View file

@ -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 {

View file

@ -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,

View file

@ -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 {

View file

@ -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

View file

@ -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.
},

View file

@ -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")]

View file

@ -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> {

View file

@ -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);
};
}

View file

@ -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.
///

View file

@ -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)),
}
},
}

View file

@ -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()
}
}

View 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 its
// 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()))
},
})
}
}

View file

@ -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.

View file

@ -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
};

View file

@ -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};

View file

@ -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(),
}
}
}

View file

@ -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 {

View file

@ -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",

View file

@ -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",

View file

@ -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">

View file

@ -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,

View file

@ -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",

View file

@ -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],

View file

@ -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",

View file

@ -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,
)}

View file

@ -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, its 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. Its 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} {

View file

@ -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"

View file

@ -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>

View file

@ -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 its
// 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)?)
},
})
}
}

View file

@ -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};

View file

@ -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,
}
}
}

View file

@ -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,
);
}

View file

@ -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);

View file

@ -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;

View file

@ -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));
}
}
}

View file

@ -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(),
),
];
}

View file

@ -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);

View file

@ -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;

View file

@ -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() {

View file

@ -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()),
),
];

View file

@ -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]>);

View file

@ -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)]

View file

@ -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),
}
}
}

View file

@ -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

View file

@ -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