summaryrefslogtreecommitdiffstats
path: root/servo/components/style/gecko
diff options
context:
space:
mode:
Diffstat (limited to 'servo/components/style/gecko')
-rw-r--r--servo/components/style/gecko/arc_types.rs171
-rw-r--r--servo/components/style/gecko/conversions.rs59
-rw-r--r--servo/components/style/gecko/data.rs198
-rw-r--r--servo/components/style/gecko/media_features.rs1003
-rw-r--r--servo/components/style/gecko/media_queries.rs593
-rw-r--r--servo/components/style/gecko/mod.rs23
-rw-r--r--servo/components/style/gecko/non_ts_pseudo_class_list.rs106
-rw-r--r--servo/components/style/gecko/pseudo_element.rs233
-rw-r--r--servo/components/style/gecko/pseudo_element_definition.mako.rs278
-rwxr-xr-xservo/components/style/gecko/regen_atoms.py218
-rw-r--r--servo/components/style/gecko/restyle_damage.rs121
-rw-r--r--servo/components/style/gecko/selector_parser.rs519
-rw-r--r--servo/components/style/gecko/snapshot.rs174
-rw-r--r--servo/components/style/gecko/snapshot_helpers.rs316
-rw-r--r--servo/components/style/gecko/traversal.rs53
-rw-r--r--servo/components/style/gecko/url.rs384
-rw-r--r--servo/components/style/gecko/values.rs77
-rw-r--r--servo/components/style/gecko/wrapper.rs2211
18 files changed, 6737 insertions, 0 deletions
diff --git a/servo/components/style/gecko/arc_types.rs b/servo/components/style/gecko/arc_types.rs
new file mode 100644
index 0000000000..24bf22d69a
--- /dev/null
+++ b/servo/components/style/gecko/arc_types.rs
@@ -0,0 +1,171 @@
+/* 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/. */
+
+//! This file lists all arc FFI types and defines corresponding addref and release functions. This
+//! list loosely corresponds to ServoLockedArcTypeList.h file in Gecko.
+
+#![allow(non_snake_case, missing_docs)]
+
+use crate::gecko::url::CssUrlData;
+use crate::media_queries::MediaList;
+use crate::properties::animated_properties::AnimationValue;
+use crate::properties::{ComputedValues, PropertyDeclarationBlock};
+use crate::shared_lock::Locked;
+use crate::stylesheets::keyframes_rule::Keyframe;
+use crate::stylesheets::{
+ ContainerRule, CounterStyleRule, CssRules, DocumentRule, FontFaceRule, FontFeatureValuesRule,
+ FontPaletteValuesRule, ImportRule, KeyframesRule, LayerBlockRule, LayerStatementRule,
+ MediaRule, NamespaceRule, PageRule, PropertyRule, StyleRule, StylesheetContents, SupportsRule,
+};
+use servo_arc::Arc;
+
+macro_rules! impl_simple_arc_ffi {
+ ($ty:ty, $addref:ident, $release:ident) => {
+ #[no_mangle]
+ pub unsafe extern "C" fn $addref(obj: *const $ty) {
+ std::mem::forget(Arc::from_raw_addrefed(obj));
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn $release(obj: *const $ty) {
+ let _ = Arc::from_raw(obj);
+ }
+ };
+}
+
+macro_rules! impl_locked_arc_ffi {
+ ($servo_type:ty, $alias:ident, $addref:ident, $release:ident) => {
+ /// A simple alias for a locked type.
+ pub type $alias = Locked<$servo_type>;
+ impl_simple_arc_ffi!($alias, $addref, $release);
+ };
+}
+
+impl_locked_arc_ffi!(
+ CssRules,
+ LockedCssRules,
+ Servo_CssRules_AddRef,
+ Servo_CssRules_Release
+);
+impl_locked_arc_ffi!(
+ PropertyDeclarationBlock,
+ LockedDeclarationBlock,
+ Servo_DeclarationBlock_AddRef,
+ Servo_DeclarationBlock_Release
+);
+impl_locked_arc_ffi!(
+ StyleRule,
+ LockedStyleRule,
+ Servo_StyleRule_AddRef,
+ Servo_StyleRule_Release
+);
+impl_locked_arc_ffi!(
+ ImportRule,
+ LockedImportRule,
+ Servo_ImportRule_AddRef,
+ Servo_ImportRule_Release
+);
+impl_locked_arc_ffi!(
+ Keyframe,
+ LockedKeyframe,
+ Servo_Keyframe_AddRef,
+ Servo_Keyframe_Release
+);
+impl_locked_arc_ffi!(
+ KeyframesRule,
+ LockedKeyframesRule,
+ Servo_KeyframesRule_AddRef,
+ Servo_KeyframesRule_Release
+);
+impl_simple_arc_ffi!(
+ LayerBlockRule,
+ Servo_LayerBlockRule_AddRef,
+ Servo_LayerBlockRule_Release
+);
+impl_simple_arc_ffi!(
+ LayerStatementRule,
+ Servo_LayerStatementRule_AddRef,
+ Servo_LayerStatementRule_Release
+);
+impl_locked_arc_ffi!(
+ MediaList,
+ LockedMediaList,
+ Servo_MediaList_AddRef,
+ Servo_MediaList_Release
+);
+impl_simple_arc_ffi!(MediaRule, Servo_MediaRule_AddRef, Servo_MediaRule_Release);
+impl_simple_arc_ffi!(
+ NamespaceRule,
+ Servo_NamespaceRule_AddRef,
+ Servo_NamespaceRule_Release
+);
+impl_locked_arc_ffi!(
+ PageRule,
+ LockedPageRule,
+ Servo_PageRule_AddRef,
+ Servo_PageRule_Release
+);
+impl_simple_arc_ffi!(
+ PropertyRule,
+ Servo_PropertyRule_AddRef,
+ Servo_PropertyRule_Release
+);
+impl_simple_arc_ffi!(
+ SupportsRule,
+ Servo_SupportsRule_AddRef,
+ Servo_SupportsRule_Release
+);
+impl_simple_arc_ffi!(
+ ContainerRule,
+ Servo_ContainerRule_AddRef,
+ Servo_ContainerRule_Release
+);
+impl_simple_arc_ffi!(
+ DocumentRule,
+ Servo_DocumentRule_AddRef,
+ Servo_DocumentRule_Release
+);
+impl_simple_arc_ffi!(
+ FontFeatureValuesRule,
+ Servo_FontFeatureValuesRule_AddRef,
+ Servo_FontFeatureValuesRule_Release
+);
+impl_simple_arc_ffi!(
+ FontPaletteValuesRule,
+ Servo_FontPaletteValuesRule_AddRef,
+ Servo_FontPaletteValuesRule_Release
+);
+impl_locked_arc_ffi!(
+ FontFaceRule,
+ LockedFontFaceRule,
+ Servo_FontFaceRule_AddRef,
+ Servo_FontFaceRule_Release
+);
+impl_locked_arc_ffi!(
+ CounterStyleRule,
+ LockedCounterStyleRule,
+ Servo_CounterStyleRule_AddRef,
+ Servo_CounterStyleRule_Release
+);
+
+impl_simple_arc_ffi!(
+ StylesheetContents,
+ Servo_StyleSheetContents_AddRef,
+ Servo_StyleSheetContents_Release
+);
+impl_simple_arc_ffi!(
+ CssUrlData,
+ Servo_CssUrlData_AddRef,
+ Servo_CssUrlData_Release
+);
+impl_simple_arc_ffi!(
+ ComputedValues,
+ Servo_ComputedStyle_AddRef,
+ Servo_ComputedStyle_Release
+);
+impl_simple_arc_ffi!(
+ AnimationValue,
+ Servo_AnimationValue_AddRef,
+ Servo_AnimationValue_Release
+);
diff --git a/servo/components/style/gecko/conversions.rs b/servo/components/style/gecko/conversions.rs
new file mode 100644
index 0000000000..ea3700a323
--- /dev/null
+++ b/servo/components/style/gecko/conversions.rs
@@ -0,0 +1,59 @@
+/* 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/. */
+
+//! This module contains conversion helpers between Servo and Gecko types
+//! Ideally, it would be in geckolib itself, but coherence
+//! forces us to keep the traits and implementations here
+//!
+//! FIXME(emilio): This file should generally just die.
+
+#![allow(unsafe_code)]
+
+use crate::gecko_bindings::structs::{nsresult, Matrix4x4Components};
+use crate::stylesheets::RulesMutateError;
+use crate::values::computed::transform::Matrix3D;
+
+impl From<RulesMutateError> for nsresult {
+ fn from(other: RulesMutateError) -> Self {
+ match other {
+ RulesMutateError::Syntax => nsresult::NS_ERROR_DOM_SYNTAX_ERR,
+ RulesMutateError::IndexSize => nsresult::NS_ERROR_DOM_INDEX_SIZE_ERR,
+ RulesMutateError::HierarchyRequest => nsresult::NS_ERROR_DOM_HIERARCHY_REQUEST_ERR,
+ RulesMutateError::InvalidState => nsresult::NS_ERROR_DOM_INVALID_STATE_ERR,
+ }
+ }
+}
+
+impl<'a> From<&'a Matrix4x4Components> for Matrix3D {
+ fn from(m: &'a Matrix4x4Components) -> Matrix3D {
+ Matrix3D {
+ m11: m[0],
+ m12: m[1],
+ m13: m[2],
+ m14: m[3],
+ m21: m[4],
+ m22: m[5],
+ m23: m[6],
+ m24: m[7],
+ m31: m[8],
+ m32: m[9],
+ m33: m[10],
+ m34: m[11],
+ m41: m[12],
+ m42: m[13],
+ m43: m[14],
+ m44: m[15],
+ }
+ }
+}
+
+impl From<Matrix3D> for Matrix4x4Components {
+ fn from(matrix: Matrix3D) -> Self {
+ [
+ matrix.m11, matrix.m12, matrix.m13, matrix.m14, matrix.m21, matrix.m22, matrix.m23,
+ matrix.m24, matrix.m31, matrix.m32, matrix.m33, matrix.m34, matrix.m41, matrix.m42,
+ matrix.m43, matrix.m44,
+ ]
+ }
+}
diff --git a/servo/components/style/gecko/data.rs b/servo/components/style/gecko/data.rs
new file mode 100644
index 0000000000..c4a5554c5e
--- /dev/null
+++ b/servo/components/style/gecko/data.rs
@@ -0,0 +1,198 @@
+/* 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/. */
+
+//! Data needed to style a Gecko document.
+
+use crate::dom::TElement;
+use crate::gecko_bindings::bindings;
+use crate::gecko_bindings::structs::{
+ self, ServoStyleSetSizes, StyleSheet as DomStyleSheet, StyleSheetInfo,
+};
+use crate::invalidation::media_queries::{MediaListKey, ToMediaListKey};
+use crate::media_queries::{Device, MediaList};
+use crate::properties::ComputedValues;
+use crate::selector_parser::SnapshotMap;
+use crate::shared_lock::{SharedRwLockReadGuard, StylesheetGuards};
+use crate::stylesheets::{StylesheetContents, StylesheetInDocument};
+use crate::stylist::Stylist;
+use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
+use malloc_size_of::MallocSizeOfOps;
+use servo_arc::Arc;
+use std::fmt;
+
+/// Little wrapper to a Gecko style sheet.
+#[derive(Eq, PartialEq)]
+pub struct GeckoStyleSheet(*const DomStyleSheet);
+
+// NOTE(emilio): These are kind of a lie. We allow to make these Send + Sync so that other data
+// structures can also be Send and Sync, but Gecko's stylesheets are main-thread-reference-counted.
+//
+// We assert that we reference-count in the right thread (in the Addref/Release implementations).
+// Sending these to a different thread can't really happen (it could theoretically really happen if
+// we allowed @import rules inside a nested style rule, but that can't happen per spec and would be
+// a parser bug, caught by the asserts).
+unsafe impl Send for GeckoStyleSheet {}
+unsafe impl Sync for GeckoStyleSheet {}
+
+impl fmt::Debug for GeckoStyleSheet {
+ fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ let contents = self.contents();
+ formatter
+ .debug_struct("GeckoStyleSheet")
+ .field("origin", &contents.origin)
+ .field("url_data", &*contents.url_data.read())
+ .finish()
+ }
+}
+
+impl ToMediaListKey for crate::gecko::data::GeckoStyleSheet {
+ fn to_media_list_key(&self) -> MediaListKey {
+ use std::mem;
+ unsafe { MediaListKey::from_raw(mem::transmute(self.0)) }
+ }
+}
+
+impl GeckoStyleSheet {
+ /// Create a `GeckoStyleSheet` from a raw `DomStyleSheet` pointer.
+ #[inline]
+ pub unsafe fn new(s: *const DomStyleSheet) -> Self {
+ debug_assert!(!s.is_null());
+ bindings::Gecko_StyleSheet_AddRef(s);
+ Self::from_addrefed(s)
+ }
+
+ /// Create a `GeckoStyleSheet` from a raw `DomStyleSheet` pointer that
+ /// already holds a strong reference.
+ #[inline]
+ pub unsafe fn from_addrefed(s: *const DomStyleSheet) -> Self {
+ assert!(!s.is_null());
+ GeckoStyleSheet(s)
+ }
+
+ /// HACK(emilio): This is so that we can avoid crashing release due to
+ /// bug 1719963 and can hopefully get a useful report from fuzzers.
+ #[inline]
+ pub fn hack_is_null(&self) -> bool {
+ self.0.is_null()
+ }
+
+ /// Get the raw `StyleSheet` that we're wrapping.
+ pub fn raw(&self) -> &DomStyleSheet {
+ unsafe { &*self.0 }
+ }
+
+ fn inner(&self) -> &StyleSheetInfo {
+ unsafe { &*(self.raw().mInner as *const StyleSheetInfo) }
+ }
+}
+
+impl Drop for GeckoStyleSheet {
+ fn drop(&mut self) {
+ unsafe { bindings::Gecko_StyleSheet_Release(self.0) };
+ }
+}
+
+impl Clone for GeckoStyleSheet {
+ fn clone(&self) -> Self {
+ unsafe { bindings::Gecko_StyleSheet_AddRef(self.0) };
+ GeckoStyleSheet(self.0)
+ }
+}
+
+impl StylesheetInDocument for GeckoStyleSheet {
+ fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> {
+ use crate::gecko_bindings::structs::mozilla::dom::MediaList as DomMediaList;
+ unsafe {
+ let dom_media_list = self.raw().mMedia.mRawPtr as *const DomMediaList;
+ if dom_media_list.is_null() {
+ return None;
+ }
+ let list = &*(*dom_media_list).mRawList.mRawPtr;
+ Some(list.read_with(guard))
+ }
+ }
+
+ // All the stylesheets Servo knows about are enabled, because that state is
+ // handled externally by Gecko.
+ #[inline]
+ fn enabled(&self) -> bool {
+ true
+ }
+
+ #[inline]
+ fn contents(&self) -> &StylesheetContents {
+ debug_assert!(!self.inner().mContents.mRawPtr.is_null());
+ unsafe { &*self.inner().mContents.mRawPtr }
+ }
+}
+
+/// The container for data that a Servo-backed Gecko document needs to style
+/// itself.
+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
+/// and unexpected races while trying to mutate it.
+pub struct PerDocumentStyleData(AtomicRefCell<PerDocumentStyleDataImpl>);
+
+impl PerDocumentStyleData {
+ /// Create a `PerDocumentStyleData`.
+ pub fn new(document: *const structs::Document) -> Self {
+ let device = Device::new(document);
+ let quirks_mode = device.document().mCompatMode;
+
+ PerDocumentStyleData(AtomicRefCell::new(PerDocumentStyleDataImpl {
+ stylist: Stylist::new(device, quirks_mode.into()),
+ undisplayed_style_cache: Default::default(),
+ undisplayed_style_cache_generation: 0,
+ }))
+ }
+
+ /// Get an immutable reference to this style data.
+ pub fn borrow(&self) -> AtomicRef<PerDocumentStyleDataImpl> {
+ self.0.borrow()
+ }
+
+ /// Get an mutable reference to this style data.
+ pub fn borrow_mut(&self) -> AtomicRefMut<PerDocumentStyleDataImpl> {
+ self.0.borrow_mut()
+ }
+}
+
+impl PerDocumentStyleDataImpl {
+ /// Recreate the style data if the stylesheets have changed.
+ pub fn flush_stylesheets<E>(
+ &mut self,
+ guard: &SharedRwLockReadGuard,
+ document_element: Option<E>,
+ snapshots: Option<&SnapshotMap>,
+ ) -> bool
+ where
+ E: TElement,
+ {
+ self.stylist
+ .flush(&StylesheetGuards::same(guard), document_element, snapshots)
+ }
+
+ /// Get the default computed values for this document.
+ pub fn default_computed_values(&self) -> &Arc<ComputedValues> {
+ self.stylist.device().default_computed_values_arc()
+ }
+
+ /// Measure heap usage.
+ pub fn add_size_of(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes) {
+ self.stylist.add_size_of(ops, sizes);
+ }
+}
+
+/// The gecko-specific AuthorStyles instantiation.
+pub type AuthorStyles = crate::author_styles::AuthorStyles<GeckoStyleSheet>;
diff --git a/servo/components/style/gecko/media_features.rs b/servo/components/style/gecko/media_features.rs
new file mode 100644
index 0000000000..c9ad30b28b
--- /dev/null
+++ b/servo/components/style/gecko/media_features.rs
@@ -0,0 +1,1003 @@
+/* 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/. */
+
+//! Gecko's media feature list and evaluator.
+
+use crate::gecko_bindings::bindings;
+use crate::gecko_bindings::structs;
+use crate::gecko_bindings::structs::ScreenColorGamut;
+use crate::media_queries::{Device, MediaType};
+use crate::queries::condition::KleeneValue;
+use crate::queries::feature::{AllowsRanges, Evaluator, FeatureFlags, QueryFeatureDescription};
+use crate::queries::values::Orientation;
+use crate::values::computed::{CSSPixelLength, Context, Ratio, Resolution};
+use crate::values::AtomString;
+use app_units::Au;
+use euclid::default::Size2D;
+
+fn device_size(device: &Device) -> Size2D<Au> {
+ let mut width = 0;
+ let mut height = 0;
+ unsafe {
+ bindings::Gecko_MediaFeatures_GetDeviceSize(device.document(), &mut width, &mut height);
+ }
+ Size2D::new(Au(width), Au(height))
+}
+
+/// https://drafts.csswg.org/mediaqueries-4/#width
+fn eval_width(context: &Context) -> CSSPixelLength {
+ CSSPixelLength::new(context.device().au_viewport_size().width.to_f32_px())
+}
+
+/// 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())
+}
+
+/// https://drafts.csswg.org/mediaqueries-4/#height
+fn eval_height(context: &Context) -> CSSPixelLength {
+ CSSPixelLength::new(context.device().au_viewport_size().height.to_f32_px())
+}
+
+/// 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_aspect_ratio_for<F>(context: &Context, get_size: F) -> Ratio
+where
+ F: FnOnce(&Device) -> Size2D<Au>,
+{
+ let size = get_size(context.device());
+ Ratio::new(size.width.0 as f32, size.height.0 as f32)
+}
+
+/// https://drafts.csswg.org/mediaqueries-4/#aspect-ratio
+fn eval_aspect_ratio(context: &Context) -> Ratio {
+ eval_aspect_ratio_for(context, 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)
+}
+
+/// https://compat.spec.whatwg.org/#css-media-queries-webkit-device-pixel-ratio
+fn eval_device_pixel_ratio(context: &Context) -> f32 {
+ eval_resolution(context).dppx()
+}
+
+/// https://drafts.csswg.org/mediaqueries-4/#orientation
+fn eval_orientation(context: &Context, value: Option<Orientation>) -> bool {
+ Orientation::eval(context.device().au_viewport_size(), value)
+}
+
+/// 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)
+}
+
+/// Values for the display-mode media feature.
+#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)]
+#[repr(u8)]
+#[allow(missing_docs)]
+pub enum DisplayMode {
+ Browser = 0,
+ MinimalUi,
+ Standalone,
+ Fullscreen,
+}
+
+/// https://w3c.github.io/manifest/#the-display-mode-media-feature
+fn eval_display_mode(context: &Context, query_value: Option<DisplayMode>) -> bool {
+ match query_value {
+ Some(v) => {
+ v == unsafe {
+ bindings::Gecko_MediaFeatures_GetDisplayMode(context.device().document())
+ }
+ },
+ None => true,
+ }
+}
+
+/// https://drafts.csswg.org/mediaqueries-4/#grid
+fn eval_grid(_: &Context) -> bool {
+ // Gecko doesn't support grid devices (e.g., ttys), so the 'grid' feature
+ // is always 0.
+ false
+}
+
+/// https://compat.spec.whatwg.org/#css-media-queries-webkit-transform-3d
+fn eval_transform_3d(_: &Context) -> bool {
+ true
+}
+
+#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
+#[repr(u8)]
+enum Scan {
+ Progressive,
+ Interlace,
+}
+
+/// https://drafts.csswg.org/mediaqueries-4/#scan
+fn eval_scan(_: &Context, _: 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) -> i32 {
+ unsafe { bindings::Gecko_MediaFeatures_GetColorDepth(context.device().document()) }
+}
+
+/// https://drafts.csswg.org/mediaqueries-4/#color-index
+fn eval_color_index(_: &Context) -> i32 {
+ // We should return zero if the device does not use a color lookup table.
+ 0
+}
+
+/// https://drafts.csswg.org/mediaqueries-4/#monochrome
+fn eval_monochrome(context: &Context) -> i32 {
+ // For color devices we should return 0.
+ unsafe { bindings::Gecko_MediaFeatures_GetMonochromeBitsPerPixel(context.device().document()) }
+}
+
+/// Values for the color-gamut media feature.
+/// This implements PartialOrd so that lower values will correctly match
+/// higher capabilities.
+#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, PartialOrd, ToCss)]
+#[repr(u8)]
+enum ColorGamut {
+ /// The sRGB gamut.
+ Srgb,
+ /// The gamut specified by the Display P3 Color Space.
+ P3,
+ /// The gamut specified by the ITU-R Recommendation BT.2020 Color Space.
+ Rec2020,
+}
+
+/// https://drafts.csswg.org/mediaqueries-4/#color-gamut
+fn eval_color_gamut(context: &Context, query_value: Option<ColorGamut>) -> bool {
+ let query_value = match query_value {
+ Some(v) => v,
+ None => return false,
+ };
+ let color_gamut =
+ unsafe { bindings::Gecko_MediaFeatures_ColorGamut(context.device().document()) };
+ // Match if our color gamut is at least as wide as the query value
+ query_value <=
+ match color_gamut {
+ // EndGuard_ is not a valid color gamut, so the default color-gamut is used.
+ ScreenColorGamut::Srgb | ScreenColorGamut::EndGuard_ => ColorGamut::Srgb,
+ ScreenColorGamut::P3 => ColorGamut::P3,
+ ScreenColorGamut::Rec2020 => ColorGamut::Rec2020,
+ }
+}
+
+/// 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)
+}
+
+#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
+#[repr(u8)]
+enum PrefersReducedMotion {
+ NoPreference,
+ Reduce,
+}
+
+#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
+#[repr(u8)]
+enum PrefersReducedTransparency {
+ NoPreference,
+ Reduce,
+}
+
+/// Values for the prefers-color-scheme media feature.
+#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)]
+#[repr(u8)]
+#[allow(missing_docs)]
+pub enum PrefersColorScheme {
+ Light,
+ 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 {
+ let prefers_reduced =
+ unsafe { bindings::Gecko_MediaFeatures_PrefersReducedMotion(context.device().document()) };
+ let query_value = match query_value {
+ Some(v) => v,
+ None => return prefers_reduced,
+ };
+
+ match query_value {
+ PrefersReducedMotion::NoPreference => !prefers_reduced,
+ PrefersReducedMotion::Reduce => prefers_reduced,
+ }
+}
+
+/// https://drafts.csswg.org/mediaqueries-5/#prefers-reduced-transparency
+fn eval_prefers_reduced_transparency(
+ context: &Context,
+ query_value: Option<PrefersReducedTransparency>,
+) -> bool {
+ let prefers_reduced = unsafe {
+ bindings::Gecko_MediaFeatures_PrefersReducedTransparency(context.device().document())
+ };
+ let query_value = match query_value {
+ Some(v) => v,
+ None => return prefers_reduced,
+ };
+
+ match query_value {
+ PrefersReducedTransparency::NoPreference => !prefers_reduced,
+ PrefersReducedTransparency::Reduce => prefers_reduced,
+ }
+}
+
+/// Possible values for prefers-contrast media query.
+/// https://drafts.csswg.org/mediaqueries-5/#prefers-contrast
+#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)]
+#[repr(u8)]
+pub enum PrefersContrast {
+ /// More contrast is preferred.
+ 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 {
+ let prefers_contrast =
+ unsafe { bindings::Gecko_MediaFeatures_PrefersContrast(context.device().document()) };
+ match query_value {
+ Some(v) => v == prefers_contrast,
+ None => prefers_contrast != PrefersContrast::NoPreference,
+ }
+}
+
+/// Possible values for the forced-colors media query.
+/// https://drafts.csswg.org/mediaqueries-5/#forced-colors
+#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)]
+#[repr(u8)]
+pub enum ForcedColors {
+ /// Page colors are not being forced.
+ None,
+ /// Page colors are being forced.
+ Active,
+}
+
+/// 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();
+ match query_value {
+ Some(query_value) => forced == (query_value == ForcedColors::Active),
+ None => forced,
+ }
+}
+
+/// Possible values for the inverted-colors media query.
+/// https://drafts.csswg.org/mediaqueries-5/#inverted
+#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
+#[repr(u8)]
+enum InvertedColors {
+ /// Colors are displayed normally.
+ None,
+ /// All pixels within the displayed area have been inverted.
+ Inverted,
+}
+
+/// https://drafts.csswg.org/mediaqueries-5/#inverted
+fn eval_inverted_colors(context: &Context, query_value: Option<InvertedColors>) -> bool {
+ let inverted_colors =
+ unsafe { bindings::Gecko_MediaFeatures_InvertedColors(context.device().document()) };
+ let query_value = match query_value {
+ Some(v) => v,
+ None => return inverted_colors,
+ };
+
+ match query_value {
+ InvertedColors::None => !inverted_colors,
+ InvertedColors::Inverted => inverted_colors,
+ }
+}
+
+#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
+#[repr(u8)]
+enum OverflowBlock {
+ None,
+ Scroll,
+ Paged,
+}
+
+/// https://drafts.csswg.org/mediaqueries-4/#mf-overflow-block
+fn eval_overflow_block(context: &Context, 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
+ // updating in the future (e.g., ebook readers built with Stylo, a
+ // 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 query_value = match query_value {
+ Some(v) => v,
+ None => return true,
+ };
+
+ match query_value {
+ OverflowBlock::None => false,
+ OverflowBlock::Scroll => scrolling,
+ OverflowBlock::Paged => !scrolling,
+ }
+}
+
+#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
+#[repr(u8)]
+enum OverflowInline {
+ None,
+ Scroll,
+}
+
+/// https://drafts.csswg.org/mediaqueries-4/#mf-overflow-inline
+fn eval_overflow_inline(context: &Context, query_value: Option<OverflowInline>) -> bool {
+ // See the note in eval_overflow_block.
+ let scrolling = context.device().media_type() != MediaType::print();
+ let query_value = match query_value {
+ Some(v) => v,
+ None => return scrolling,
+ };
+
+ match query_value {
+ OverflowInline::None => !scrolling,
+ OverflowInline::Scroll => scrolling,
+ }
+}
+
+#[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,
+ use_content: bool,
+ query_value: Option<PrefersColorScheme>,
+) -> bool {
+ let prefers_color_scheme = unsafe {
+ bindings::Gecko_MediaFeatures_PrefersColorScheme(context.device().document(), use_content)
+ };
+ match query_value {
+ Some(v) => prefers_color_scheme == v,
+ None => true,
+ }
+}
+
+/// 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_content_prefers_color_scheme(
+ context: &Context,
+ 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,
+ }
+}
+
+bitflags! {
+ /// https://drafts.csswg.org/mediaqueries-4/#mf-interaction
+ struct PointerCapabilities: u8 {
+ const COARSE = structs::PointerCapabilities_Coarse;
+ const FINE = structs::PointerCapabilities_Fine;
+ const HOVER = structs::PointerCapabilities_Hover;
+ }
+}
+
+fn primary_pointer_capabilities(context: &Context) -> PointerCapabilities {
+ PointerCapabilities::from_bits_truncate(unsafe {
+ bindings::Gecko_MediaFeatures_PrimaryPointerCapabilities(context.device().document())
+ })
+}
+
+fn all_pointer_capabilities(context: &Context) -> PointerCapabilities {
+ PointerCapabilities::from_bits_truncate(unsafe {
+ bindings::Gecko_MediaFeatures_AllPointerCapabilities(context.device().document())
+ })
+}
+
+#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
+#[repr(u8)]
+enum Pointer {
+ None,
+ Coarse,
+ Fine,
+}
+
+fn eval_pointer_capabilities(
+ query_value: Option<Pointer>,
+ pointer_capabilities: PointerCapabilities,
+) -> bool {
+ let query_value = match query_value {
+ Some(v) => v,
+ None => return !pointer_capabilities.is_empty(),
+ };
+
+ match query_value {
+ Pointer::None => pointer_capabilities.is_empty(),
+ Pointer::Coarse => pointer_capabilities.intersects(PointerCapabilities::COARSE),
+ Pointer::Fine => pointer_capabilities.intersects(PointerCapabilities::FINE),
+ }
+}
+
+/// 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))
+}
+
+/// 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))
+}
+
+#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
+#[repr(u8)]
+enum Hover {
+ None,
+ Hover,
+}
+
+fn eval_hover_capabilities(
+ query_value: Option<Hover>,
+ pointer_capabilities: PointerCapabilities,
+) -> bool {
+ let can_hover = pointer_capabilities.intersects(PointerCapabilities::HOVER);
+ let query_value = match query_value {
+ Some(v) => v,
+ None => return can_hover,
+ };
+
+ match query_value {
+ Hover::None => !can_hover,
+ Hover::Hover => can_hover,
+ }
+}
+
+/// 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))
+}
+
+/// 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_moz_is_glyph(context: &Context) -> bool {
+ context.device().document().mIsSVGGlyphsDocument()
+}
+
+fn eval_moz_print_preview(context: &Context) -> bool {
+ let is_print_preview = context.device().is_print_preview();
+ if is_print_preview {
+ debug_assert_eq!(context.device().media_type(), MediaType::print());
+ }
+ is_print_preview
+}
+
+fn eval_moz_is_resource_document(context: &Context) -> bool {
+ unsafe { bindings::Gecko_MediaFeatures_IsResourceDocument(context.device().document()) }
+}
+
+/// Allows front-end CSS to discern platform via media queries.
+#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
+#[repr(u8)]
+pub enum Platform {
+ /// Matches any Android version.
+ Android,
+ /// For our purposes here, "linux" is just "gtk" (so unix-but-not-mac).
+ /// There's no need for our front-end code to differentiate between those
+ /// platforms and they already use the "linux" string elsewhere (e.g.,
+ /// toolkit/themes/linux).
+ Linux,
+ /// Matches any macOS version.
+ Macos,
+ /// Matches any Windows version.
+ Windows,
+}
+
+fn eval_moz_platform(_: &Context, query_value: Option<Platform>) -> bool {
+ let query_value = match query_value {
+ Some(v) => v,
+ None => return false,
+ };
+
+ unsafe { bindings::Gecko_MediaFeatures_MatchesPlatform(query_value) }
+}
+
+/// Allows front-end CSS to discern gtk theme via media queries.
+#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)]
+#[repr(u8)]
+pub enum GtkThemeFamily {
+ /// Unknown theme family.
+ Unknown = 0,
+ /// Adwaita, the default GTK theme.
+ Adwaita,
+ /// Breeze, the default KDE theme.
+ Breeze,
+ /// Yaru, the default Ubuntu theme.
+ Yaru,
+}
+
+fn eval_gtk_theme_family(_: &Context, query_value: Option<GtkThemeFamily>) -> bool {
+ let family = unsafe { bindings::Gecko_MediaFeatures_GtkThemeFamily() };
+ match query_value {
+ Some(v) => v == family,
+ None => return family != GtkThemeFamily::Unknown,
+ }
+}
+
+/// Values for the scripting media feature.
+/// https://drafts.csswg.org/mediaqueries-5/#scripting
+#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)]
+#[repr(u8)]
+pub enum Scripting {
+ /// Scripting is not supported or not enabled
+ None,
+ /// Scripting is supported and enabled, but only for initial page load
+ /// We will never match this value as it is intended for non-browser user agents,
+ /// but it is part of the spec so we should still parse it.
+ /// See: https://github.com/w3c/csswg-drafts/issues/8621
+ InitialOnly,
+ /// Scripting is supported and enabled
+ Enabled,
+}
+
+/// https://drafts.csswg.org/mediaqueries-5/#scripting
+fn eval_scripting(context: &Context, query_value: Option<Scripting>) -> bool {
+ let scripting = unsafe { bindings::Gecko_MediaFeatures_Scripting(context.device().document()) };
+ match query_value {
+ Some(v) => v == scripting,
+ None => scripting != Scripting::None,
+ }
+}
+
+fn eval_moz_overlay_scrollbars(context: &Context) -> bool {
+ unsafe { bindings::Gecko_MediaFeatures_UseOverlayScrollbars(context.device().document()) }
+}
+
+fn eval_moz_bool_pref(_: &Context, pref: Option<&AtomString>) -> KleeneValue {
+ let Some(pref) = pref else {
+ return KleeneValue::False;
+ };
+ KleeneValue::from(unsafe { bindings::Gecko_ComputeBoolPrefMediaQuery(pref.as_ptr()) })
+}
+
+fn get_lnf_int(int_id: i32) -> i32 {
+ unsafe { bindings::Gecko_GetLookAndFeelInt(int_id) }
+}
+
+fn get_lnf_int_as_bool(int_id: i32) -> bool {
+ get_lnf_int(int_id) != 0
+}
+
+fn get_scrollbar_start_backward(int_id: i32) -> bool {
+ (get_lnf_int(int_id) & bindings::LookAndFeel_eScrollArrow_StartBackward as i32) != 0
+}
+
+fn get_scrollbar_start_forward(int_id: i32) -> bool {
+ (get_lnf_int(int_id) & bindings::LookAndFeel_eScrollArrow_StartForward as i32) != 0
+}
+
+fn get_scrollbar_end_backward(int_id: i32) -> bool {
+ (get_lnf_int(int_id) & bindings::LookAndFeel_eScrollArrow_EndBackward as i32) != 0
+}
+
+fn get_scrollbar_end_forward(int_id: i32) -> bool {
+ (get_lnf_int(int_id) & bindings::LookAndFeel_eScrollArrow_EndForward as i32) != 0
+}
+
+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)
+ }
+
+ feature!(
+ $feature_name,
+ AllowsRanges::No,
+ Evaluator::BoolInteger(__eval),
+ FeatureFlags::CHROME_AND_UA_ONLY,
+ )
+ }};
+ ($feature_name:expr, $int_id:ident) => {{
+ lnf_int_feature!($feature_name, $int_id, get_lnf_int_as_bool)
+ }};
+}
+
+/// Adding new media features requires (1) adding the new feature to this
+/// array, with appropriate entries (and potentially any new code needed
+/// 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] = [
+ feature!(
+ atom!("width"),
+ AllowsRanges::Yes,
+ Evaluator::Length(eval_width),
+ FeatureFlags::VIEWPORT_DEPENDENT,
+ ),
+ feature!(
+ atom!("height"),
+ AllowsRanges::Yes,
+ Evaluator::Length(eval_height),
+ FeatureFlags::VIEWPORT_DEPENDENT,
+ ),
+ feature!(
+ atom!("aspect-ratio"),
+ AllowsRanges::Yes,
+ Evaluator::NumberRatio(eval_aspect_ratio),
+ FeatureFlags::VIEWPORT_DEPENDENT,
+ ),
+ feature!(
+ atom!("orientation"),
+ AllowsRanges::No,
+ keyword_evaluator!(eval_orientation, Orientation),
+ FeatureFlags::VIEWPORT_DEPENDENT,
+ ),
+ feature!(
+ atom!("device-width"),
+ AllowsRanges::Yes,
+ Evaluator::Length(eval_device_width),
+ FeatureFlags::empty(),
+ ),
+ feature!(
+ atom!("device-height"),
+ AllowsRanges::Yes,
+ Evaluator::Length(eval_device_height),
+ FeatureFlags::empty(),
+ ),
+ feature!(
+ atom!("device-aspect-ratio"),
+ AllowsRanges::Yes,
+ Evaluator::NumberRatio(eval_device_aspect_ratio),
+ FeatureFlags::empty(),
+ ),
+ feature!(
+ atom!("-moz-device-orientation"),
+ AllowsRanges::No,
+ keyword_evaluator!(eval_device_orientation, Orientation),
+ FeatureFlags::empty(),
+ ),
+ // Webkit extensions that we support for de-facto web compatibility.
+ // -webkit-{min|max}-device-pixel-ratio (controlled with its own pref):
+ feature!(
+ atom!("device-pixel-ratio"),
+ AllowsRanges::Yes,
+ Evaluator::Float(eval_device_pixel_ratio),
+ FeatureFlags::WEBKIT_PREFIX,
+ ),
+ // -webkit-transform-3d.
+ feature!(
+ atom!("transform-3d"),
+ AllowsRanges::No,
+ Evaluator::BoolInteger(eval_transform_3d),
+ FeatureFlags::WEBKIT_PREFIX,
+ ),
+ feature!(
+ atom!("-moz-device-pixel-ratio"),
+ AllowsRanges::Yes,
+ Evaluator::Float(eval_device_pixel_ratio),
+ FeatureFlags::empty(),
+ ),
+ feature!(
+ atom!("resolution"),
+ AllowsRanges::Yes,
+ Evaluator::Resolution(eval_resolution),
+ FeatureFlags::empty(),
+ ),
+ feature!(
+ atom!("display-mode"),
+ AllowsRanges::No,
+ keyword_evaluator!(eval_display_mode, DisplayMode),
+ FeatureFlags::empty(),
+ ),
+ feature!(
+ atom!("grid"),
+ AllowsRanges::No,
+ Evaluator::BoolInteger(eval_grid),
+ FeatureFlags::empty(),
+ ),
+ feature!(
+ atom!("scan"),
+ AllowsRanges::No,
+ keyword_evaluator!(eval_scan, Scan),
+ FeatureFlags::empty(),
+ ),
+ feature!(
+ atom!("color"),
+ AllowsRanges::Yes,
+ Evaluator::Integer(eval_color),
+ FeatureFlags::empty(),
+ ),
+ feature!(
+ atom!("color-index"),
+ AllowsRanges::Yes,
+ Evaluator::Integer(eval_color_index),
+ FeatureFlags::empty(),
+ ),
+ feature!(
+ atom!("monochrome"),
+ AllowsRanges::Yes,
+ Evaluator::Integer(eval_monochrome),
+ FeatureFlags::empty(),
+ ),
+ feature!(
+ atom!("color-gamut"),
+ AllowsRanges::No,
+ keyword_evaluator!(eval_color_gamut, ColorGamut),
+ FeatureFlags::empty(),
+ ),
+ feature!(
+ atom!("prefers-reduced-motion"),
+ AllowsRanges::No,
+ keyword_evaluator!(eval_prefers_reduced_motion, PrefersReducedMotion),
+ FeatureFlags::empty(),
+ ),
+ feature!(
+ atom!("prefers-reduced-transparency"),
+ AllowsRanges::No,
+ keyword_evaluator!(
+ eval_prefers_reduced_transparency,
+ PrefersReducedTransparency
+ ),
+ FeatureFlags::empty(),
+ ),
+ feature!(
+ atom!("prefers-contrast"),
+ AllowsRanges::No,
+ keyword_evaluator!(eval_prefers_contrast, PrefersContrast),
+ // Note: by default this is only enabled in browser chrome and
+ // ua. It can be enabled on the web via the
+ // layout.css.prefers-contrast.enabled preference. See
+ // disabed_by_pref in media_feature_expression.rs for how that
+ // is done.
+ FeatureFlags::empty(),
+ ),
+ feature!(
+ atom!("forced-colors"),
+ AllowsRanges::No,
+ keyword_evaluator!(eval_forced_colors, ForcedColors),
+ FeatureFlags::empty(),
+ ),
+ feature!(
+ atom!("inverted-colors"),
+ AllowsRanges::No,
+ keyword_evaluator!(eval_inverted_colors, InvertedColors),
+ FeatureFlags::empty(),
+ ),
+ feature!(
+ atom!("overflow-block"),
+ AllowsRanges::No,
+ keyword_evaluator!(eval_overflow_block, OverflowBlock),
+ FeatureFlags::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(),
+ ),
+ 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(),
+ ),
+ feature!(
+ atom!("scripting"),
+ AllowsRanges::No,
+ keyword_evaluator!(eval_scripting, Scripting),
+ FeatureFlags::empty(),
+ ),
+ // Evaluates to the preferred color scheme for content. Only useful in
+ // chrome context, where the chrome color-scheme and the content
+ // color-scheme might differ.
+ feature!(
+ atom!("-moz-content-prefers-color-scheme"),
+ AllowsRanges::No,
+ keyword_evaluator!(eval_content_prefers_color_scheme, PrefersColorScheme),
+ FeatureFlags::CHROME_AND_UA_ONLY,
+ ),
+ feature!(
+ atom!("pointer"),
+ AllowsRanges::No,
+ keyword_evaluator!(eval_pointer, Pointer),
+ FeatureFlags::empty(),
+ ),
+ feature!(
+ atom!("any-pointer"),
+ AllowsRanges::No,
+ keyword_evaluator!(eval_any_pointer, Pointer),
+ FeatureFlags::empty(),
+ ),
+ feature!(
+ atom!("hover"),
+ AllowsRanges::No,
+ keyword_evaluator!(eval_hover, Hover),
+ FeatureFlags::empty(),
+ ),
+ feature!(
+ atom!("any-hover"),
+ AllowsRanges::No,
+ keyword_evaluator!(eval_any_hover, Hover),
+ FeatureFlags::empty(),
+ ),
+ // Internal -moz-is-glyph media feature: applies only inside SVG glyphs.
+ // Internal because it is really only useful in the user agent anyway
+ // and therefore not worth standardizing.
+ feature!(
+ atom!("-moz-is-glyph"),
+ AllowsRanges::No,
+ Evaluator::BoolInteger(eval_moz_is_glyph),
+ FeatureFlags::CHROME_AND_UA_ONLY,
+ ),
+ feature!(
+ atom!("-moz-is-resource-document"),
+ AllowsRanges::No,
+ Evaluator::BoolInteger(eval_moz_is_resource_document),
+ FeatureFlags::CHROME_AND_UA_ONLY,
+ ),
+ feature!(
+ atom!("-moz-platform"),
+ AllowsRanges::No,
+ keyword_evaluator!(eval_moz_platform, Platform),
+ FeatureFlags::CHROME_AND_UA_ONLY,
+ ),
+ feature!(
+ atom!("-moz-gtk-theme-family"),
+ AllowsRanges::No,
+ keyword_evaluator!(eval_gtk_theme_family, GtkThemeFamily),
+ FeatureFlags::CHROME_AND_UA_ONLY,
+ ),
+ feature!(
+ atom!("-moz-print-preview"),
+ AllowsRanges::No,
+ Evaluator::BoolInteger(eval_moz_print_preview),
+ FeatureFlags::CHROME_AND_UA_ONLY,
+ ),
+ feature!(
+ atom!("-moz-overlay-scrollbars"),
+ AllowsRanges::No,
+ Evaluator::BoolInteger(eval_moz_overlay_scrollbars),
+ FeatureFlags::CHROME_AND_UA_ONLY,
+ ),
+ feature!(
+ atom!("-moz-bool-pref"),
+ AllowsRanges::No,
+ Evaluator::String(eval_moz_bool_pref),
+ FeatureFlags::CHROME_AND_UA_ONLY,
+ ),
+ lnf_int_feature!(
+ atom!("-moz-scrollbar-start-backward"),
+ ScrollArrowStyle,
+ get_scrollbar_start_backward
+ ),
+ lnf_int_feature!(
+ atom!("-moz-scrollbar-start-forward"),
+ ScrollArrowStyle,
+ get_scrollbar_start_forward
+ ),
+ lnf_int_feature!(
+ atom!("-moz-scrollbar-end-backward"),
+ ScrollArrowStyle,
+ get_scrollbar_end_backward
+ ),
+ lnf_int_feature!(
+ atom!("-moz-scrollbar-end-forward"),
+ ScrollArrowStyle,
+ get_scrollbar_end_forward
+ ),
+ lnf_int_feature!(atom!("-moz-menubar-drag"), MenuBarDrag),
+ lnf_int_feature!(atom!("-moz-mac-big-sur-theme"), MacBigSurTheme),
+ lnf_int_feature!(atom!("-moz-mac-rtl"), MacRTL),
+ lnf_int_feature!(
+ atom!("-moz-windows-accent-color-in-titlebar"),
+ WindowsAccentColorInTitlebar
+ ),
+ lnf_int_feature!(atom!("-moz-swipe-animation-enabled"), SwipeAnimationEnabled),
+ lnf_int_feature!(atom!("-moz-gtk-csd-available"), GTKCSDAvailable),
+ lnf_int_feature!(atom!("-moz-gtk-csd-minimize-button"), GTKCSDMinimizeButton),
+ lnf_int_feature!(atom!("-moz-gtk-csd-maximize-button"), GTKCSDMaximizeButton),
+ lnf_int_feature!(atom!("-moz-gtk-csd-close-button"), GTKCSDCloseButton),
+ lnf_int_feature!(
+ atom!("-moz-gtk-csd-reversed-placement"),
+ GTKCSDReversedPlacement
+ ),
+ lnf_int_feature!(atom!("-moz-system-dark-theme"), SystemUsesDarkTheme),
+ lnf_int_feature!(atom!("-moz-panel-animations"), PanelAnimations),
+];
diff --git a/servo/components/style/gecko/media_queries.rs b/servo/components/style/gecko/media_queries.rs
new file mode 100644
index 0000000000..ef156ab380
--- /dev/null
+++ b/servo/components/style/gecko/media_queries.rs
@@ -0,0 +1,593 @@
+/* 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/. */
+
+//! Gecko's media-query device and expression representation.
+
+use crate::color::AbsoluteColor;
+use crate::context::QuirksMode;
+use crate::custom_properties::CssEnvironment;
+use crate::font_metrics::FontMetrics;
+use crate::gecko::values::{convert_absolute_color_to_nscolor, convert_nscolor_to_absolute_color};
+use crate::gecko_bindings::bindings;
+use crate::gecko_bindings::structs;
+use crate::logical_geometry::WritingMode;
+use crate::media_queries::MediaType;
+use crate::properties::ComputedValues;
+use crate::string_cache::Atom;
+use crate::values::computed::font::GenericFontFamily;
+use crate::values::computed::{ColorScheme, Length, NonNegativeLength};
+use crate::values::specified::color::SystemColor;
+use crate::values::specified::font::{FONT_MEDIUM_LINE_HEIGHT_PX, FONT_MEDIUM_PX};
+use crate::values::specified::ViewportVariant;
+use crate::values::{CustomIdent, KeyframesName};
+use app_units::{Au, AU_PER_PX};
+use euclid::default::Size2D;
+use euclid::{Scale, SideOffsets2D};
+use servo_arc::Arc;
+use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering};
+use std::{cmp, fmt};
+use style_traits::{CSSPixel, DevicePixel};
+
+/// The `Device` in Gecko wraps a pres context, has a default values computed,
+/// and contains all the viewport rule state.
+pub struct Device {
+ /// NB: The document owns the styleset, who owns the stylist, and thus the
+ /// `Device`, so having a raw document pointer here is fine.
+ document: *const structs::Document,
+ default_values: Arc<ComputedValues>,
+ /// The font size of the root element.
+ ///
+ /// This is set when computing the style of the root element, and used for
+ /// rem units in other elements.
+ ///
+ /// When computing the style of the root element, there can't be any other
+ /// style being computed at the same time, given we need the style of the
+ /// parent to compute everything else. So it is correct to just use a
+ /// relaxed atomic here.
+ root_font_size: AtomicU32,
+ /// Line height of the root element, used for rlh units in other elements.
+ root_line_height: AtomicU32,
+ /// The body text color, stored as an `nscolor`, used for the "tables
+ /// inherit from body" quirk.
+ ///
+ /// <https://quirks.spec.whatwg.org/#the-tables-inherit-color-from-body-quirk>
+ body_text_color: AtomicUsize,
+ /// Whether any styles computed in the document relied on the root font-size
+ /// by using rem units.
+ used_root_font_size: AtomicBool,
+ /// Whether any styles computed in the document relied on the root line-height
+ /// by using rlh units.
+ used_root_line_height: AtomicBool,
+ /// Whether any styles computed in the document relied on font metrics.
+ used_font_metrics: AtomicBool,
+ /// 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,
+}
+
+impl fmt::Debug for Device {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use nsstring::nsCString;
+
+ let mut doc_uri = nsCString::new();
+ unsafe {
+ bindings::Gecko_nsIURI_Debug((*self.document()).mDocumentURI.raw(), &mut doc_uri)
+ };
+
+ f.debug_struct("Device")
+ .field("document_url", &doc_uri)
+ .finish()
+ }
+}
+
+unsafe impl Sync for Device {}
+unsafe impl Send for Device {}
+
+impl Device {
+ /// Trivially constructs a new `Device`.
+ pub fn new(document: *const structs::Document) -> Self {
+ assert!(!document.is_null());
+ let doc = unsafe { &*document };
+ let prefs = unsafe { &*bindings::Gecko_GetPrefSheetPrefs(doc) };
+ Device {
+ document,
+ default_values: ComputedValues::default_values(doc),
+ root_font_size: AtomicU32::new(FONT_MEDIUM_PX.to_bits()),
+ root_line_height: AtomicU32::new(FONT_MEDIUM_LINE_HEIGHT_PX.to_bits()),
+ // This gets updated when we see the <body>, so it doesn't really
+ // matter which color-scheme we look at here.
+ body_text_color: AtomicUsize::new(prefs.mLightColors.mDefault as usize),
+ used_root_font_size: AtomicBool::new(false),
+ used_root_line_height: AtomicBool::new(false),
+ used_font_metrics: AtomicBool::new(false),
+ used_viewport_size: AtomicBool::new(false),
+ used_dynamic_viewport_size: AtomicBool::new(false),
+ environment: CssEnvironment,
+ }
+ }
+
+ /// Get the relevant environment to resolve `env()` functions.
+ #[inline]
+ pub fn environment(&self) -> &CssEnvironment {
+ &self.environment
+ }
+
+ /// Returns the computed line-height for the font in a given computed values instance.
+ ///
+ /// If you pass down an element, then the used line-height is returned.
+ pub fn calc_line_height(
+ &self,
+ font: &crate::properties::style_structs::Font,
+ writing_mode: WritingMode,
+ element: Option<super::wrapper::GeckoElement>,
+ ) -> NonNegativeLength {
+ let pres_context = self.pres_context();
+ let line_height = font.clone_line_height();
+ let au = Au(unsafe {
+ bindings::Gecko_CalcLineHeight(
+ &line_height,
+ pres_context.map_or(std::ptr::null(), |pc| pc),
+ writing_mode.is_text_vertical(),
+ &**font,
+ element.map_or(std::ptr::null(), |e| e.0),
+ )
+ });
+ NonNegativeLength::new(au.to_f32_px())
+ }
+
+ /// Whether any animation name may be referenced from the style of any
+ /// element.
+ pub fn animation_name_may_be_referenced(&self, name: &KeyframesName) -> bool {
+ let pc = match self.pres_context() {
+ Some(pc) => pc,
+ None => return false,
+ };
+
+ unsafe {
+ bindings::Gecko_AnimationNameMayBeReferencedFromStyle(pc, name.as_atom().as_ptr())
+ }
+ }
+
+ /// Returns the default computed values as a reference, in order to match
+ /// Servo.
+ pub fn default_computed_values(&self) -> &ComputedValues {
+ &self.default_values
+ }
+
+ /// Returns the default computed values as an `Arc`.
+ pub fn default_computed_values_arc(&self) -> &Arc<ComputedValues> {
+ &self.default_values
+ }
+
+ /// Get the font size of the root element (for rem)
+ pub fn root_font_size(&self) -> Length {
+ self.used_root_font_size.store(true, Ordering::Relaxed);
+ Length::new(f32::from_bits(self.root_font_size.load(Ordering::Relaxed)))
+ }
+
+ /// Set the font size of the root element (for rem)
+ pub fn set_root_font_size(&self, size: Length) {
+ self.root_font_size
+ .store(size.px().to_bits(), Ordering::Relaxed)
+ }
+
+ /// Get the line height of the root element (for rlh)
+ pub fn root_line_height(&self) -> Length {
+ self.used_root_line_height.store(true, Ordering::Relaxed);
+ Length::new(f32::from_bits(
+ self.root_line_height.load(Ordering::Relaxed),
+ ))
+ }
+
+ /// Set the line height of the root element (for rlh)
+ pub fn set_root_line_height(&self, size: Length) {
+ self.root_line_height
+ .store(size.px().to_bits(), Ordering::Relaxed);
+ }
+
+ /// The quirks mode of the document.
+ pub fn quirks_mode(&self) -> QuirksMode {
+ self.document().mCompatMode.into()
+ }
+
+ /// Sets the body text color for the "inherit color from body" quirk.
+ ///
+ /// <https://quirks.spec.whatwg.org/#the-tables-inherit-color-from-body-quirk>
+ pub fn set_body_text_color(&self, color: AbsoluteColor) {
+ self.body_text_color.store(
+ convert_absolute_color_to_nscolor(&color) as usize,
+ Ordering::Relaxed,
+ )
+ }
+
+ /// Gets the base size given a generic font family and a language.
+ pub fn base_size_for_generic(&self, language: &Atom, generic: GenericFontFamily) -> Length {
+ unsafe { bindings::Gecko_GetBaseSize(self.document(), language.as_ptr(), generic) }
+ }
+
+ /// Gets the size of the scrollbar in CSS pixels.
+ pub fn scrollbar_inline_size(&self) -> Length {
+ let pc = match self.pres_context() {
+ Some(pc) => pc,
+ // XXX: we could have a more reasonable default perhaps.
+ None => return Length::new(0.0),
+ };
+ Length::new(unsafe { bindings::Gecko_GetScrollbarInlineSize(pc) })
+ }
+
+ /// Queries font metrics
+ pub fn query_font_metrics(
+ &self,
+ vertical: bool,
+ font: &crate::properties::style_structs::Font,
+ base_size: Length,
+ in_media_query: bool,
+ retrieve_math_scales: bool,
+ ) -> FontMetrics {
+ self.used_font_metrics.store(true, Ordering::Relaxed);
+ let pc = match self.pres_context() {
+ Some(pc) => pc,
+ None => return Default::default(),
+ };
+ let gecko_metrics = unsafe {
+ bindings::Gecko_GetFontMetrics(
+ pc,
+ vertical,
+ &**font,
+ base_size,
+ // we don't use the user font set in a media query
+ !in_media_query,
+ retrieve_math_scales,
+ )
+ };
+ FontMetrics {
+ x_height: Some(gecko_metrics.mXSize),
+ zero_advance_measure: if gecko_metrics.mChSize.px() >= 0. {
+ Some(gecko_metrics.mChSize)
+ } else {
+ None
+ },
+ cap_height: if gecko_metrics.mCapHeight.px() >= 0. {
+ Some(gecko_metrics.mCapHeight)
+ } else {
+ None
+ },
+ ic_width: if gecko_metrics.mIcWidth.px() >= 0. {
+ Some(gecko_metrics.mIcWidth)
+ } else {
+ None
+ },
+ ascent: gecko_metrics.mAscent,
+ script_percent_scale_down: if gecko_metrics.mScriptPercentScaleDown > 0. {
+ Some(gecko_metrics.mScriptPercentScaleDown)
+ } else {
+ None
+ },
+ script_script_percent_scale_down: if gecko_metrics.mScriptScriptPercentScaleDown > 0. {
+ Some(gecko_metrics.mScriptScriptPercentScaleDown)
+ } else {
+ None
+ },
+ }
+ }
+
+ /// Returns the body text color.
+ pub fn body_text_color(&self) -> AbsoluteColor {
+ convert_nscolor_to_absolute_color(self.body_text_color.load(Ordering::Relaxed) as u32)
+ }
+
+ /// Gets the document pointer.
+ #[inline]
+ pub fn document(&self) -> &structs::Document {
+ unsafe { &*self.document }
+ }
+
+ /// Gets the pres context associated with this document.
+ #[inline]
+ pub fn pres_context(&self) -> Option<&structs::nsPresContext> {
+ unsafe {
+ self.document()
+ .mPresShell
+ .as_ref()?
+ .mPresContext
+ .mRawPtr
+ .as_ref()
+ }
+ }
+
+ /// Gets the preference stylesheet prefs for our document.
+ #[inline]
+ pub fn pref_sheet_prefs(&self) -> &structs::PreferenceSheet_Prefs {
+ unsafe { &*bindings::Gecko_GetPrefSheetPrefs(self.document()) }
+ }
+
+ /// Recreates the default computed values.
+ pub fn reset_computed_values(&mut self) {
+ self.default_values = ComputedValues::default_values(self.document());
+ }
+
+ /// Rebuild all the cached data.
+ pub fn rebuild_cached_data(&mut self) {
+ self.reset_computed_values();
+ self.used_root_font_size.store(false, Ordering::Relaxed);
+ self.used_root_line_height.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.
+ pub fn used_root_font_size(&self) -> bool {
+ self.used_root_font_size.load(Ordering::Relaxed)
+ }
+
+ /// Returns whether we ever looked up the root line-height of the device.
+ pub fn used_root_line_height(&self) -> bool {
+ self.used_root_line_height.load(Ordering::Relaxed)
+ }
+
+ /// Recreates all the temporary state that the `Device` stores.
+ ///
+ /// This includes the viewport override from `@viewport` rules, and also the
+ /// default computed values.
+ pub fn reset(&mut self) {
+ self.reset_computed_values();
+ }
+
+ /// Returns whether this document is in print preview.
+ pub fn is_print_preview(&self) -> bool {
+ let pc = match self.pres_context() {
+ Some(pc) => pc,
+ None => return false,
+ };
+ pc.mType == structs::nsPresContext_nsPresContextType_eContext_PrintPreview
+ }
+
+ /// Returns the current media type of the device.
+ pub fn media_type(&self) -> MediaType {
+ let pc = match self.pres_context() {
+ Some(pc) => pc,
+ None => return MediaType::screen(),
+ };
+
+ // Gecko allows emulating random media with mMediaEmulationData.mMedium.
+ let medium_to_use = if !pc.mMediaEmulationData.mMedium.mRawPtr.is_null() {
+ pc.mMediaEmulationData.mMedium.mRawPtr
+ } else {
+ pc.mMedium as *const structs::nsAtom as *mut _
+ };
+
+ MediaType(CustomIdent(unsafe { Atom::from_raw(medium_to_use) }))
+ }
+
+ // It may make sense to account for @page rule margins here somehow, however
+ // it's not clear how that'd work, see:
+ // https://github.com/w3c/csswg-drafts/issues/5437
+ fn page_size_minus_default_margin(&self, pc: &structs::nsPresContext) -> Size2D<Au> {
+ debug_assert!(pc.mIsRootPaginatedDocument() != 0);
+ let area = &pc.mPageSize;
+ let margin = &pc.mDefaultPageMargin;
+ let width = area.width - margin.left - margin.right;
+ let height = area.height - margin.top - margin.bottom;
+ Size2D::new(Au(cmp::max(width, 0)), Au(cmp::max(height, 0)))
+ }
+
+ /// Returns the current viewport size in app units.
+ pub fn au_viewport_size(&self) -> Size2D<Au> {
+ let pc = match self.pres_context() {
+ Some(pc) => pc,
+ None => return Size2D::new(Au(0), Au(0)),
+ };
+
+ if pc.mIsRootPaginatedDocument() != 0 {
+ return self.page_size_minus_default_margin(pc);
+ }
+
+ let area = &pc.mVisibleArea;
+ Size2D::new(Au(area.width), Au(area.height))
+ }
+
+ /// 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> {
+ self.used_viewport_size.store(true, Ordering::Relaxed);
+ let pc = match self.pres_context() {
+ Some(pc) => pc,
+ None => return Size2D::new(Au(0), Au(0)),
+ };
+
+ if pc.mIsRootPaginatedDocument() != 0 {
+ 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.mDynamicToolbarMaxHeight - pc.mDynamicToolbarHeight) as i32 *
+ pc.mCurAppUnitsPerDevPixel),
+ )
+ },
+ }
+ }
+
+ /// Returns whether we ever looked up the viewport size of the Device.
+ pub fn used_viewport_size(&self) -> bool {
+ 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 number of app units per device pixel we're using currently.
+ pub fn app_units_per_device_pixel(&self) -> i32 {
+ match self.pres_context() {
+ Some(pc) => pc.mCurAppUnitsPerDevPixel,
+ None => AU_PER_PX,
+ }
+ }
+
+ /// Returns the device pixel ratio.
+ pub fn device_pixel_ratio(&self) -> Scale<f32, CSSPixel, DevicePixel> {
+ let pc = match self.pres_context() {
+ Some(pc) => pc,
+ None => return Scale::new(1.),
+ };
+
+ if pc.mMediaEmulationData.mDPPX > 0.0 {
+ return Scale::new(pc.mMediaEmulationData.mDPPX);
+ }
+
+ let au_per_dpx = pc.mCurAppUnitsPerDevPixel as f32;
+ let au_per_px = AU_PER_PX as f32;
+ Scale::new(au_per_px / au_per_dpx)
+ }
+
+ /// Returns whether document colors are enabled.
+ #[inline]
+ pub fn use_document_colors(&self) -> bool {
+ let doc = self.document();
+ if doc.mIsBeingUsedAsImage() {
+ return true;
+ }
+ self.pref_sheet_prefs().mUseDocumentColors
+ }
+
+ /// Computes a system color and returns it as an nscolor.
+ pub(crate) fn system_nscolor(
+ &self,
+ system_color: SystemColor,
+ color_scheme: &ColorScheme,
+ ) -> u32 {
+ unsafe { bindings::Gecko_ComputeSystemColor(system_color, self.document(), color_scheme) }
+ }
+
+ /// Returns whether the used color-scheme for `color-scheme` should be dark.
+ pub(crate) fn is_dark_color_scheme(&self, color_scheme: &ColorScheme) -> bool {
+ unsafe { bindings::Gecko_IsDarkColorScheme(self.document(), color_scheme) }
+ }
+
+ /// Returns the default background color.
+ ///
+ /// This is only for forced-colors/high-contrast, so looking at light colors
+ /// is ok.
+ pub fn default_background_color(&self) -> AbsoluteColor {
+ let normal = ColorScheme::normal();
+ convert_nscolor_to_absolute_color(self.system_nscolor(SystemColor::Canvas, &normal))
+ }
+
+ /// Returns the default foreground color.
+ ///
+ /// See above for looking at light colors only.
+ pub fn default_color(&self) -> AbsoluteColor {
+ let normal = ColorScheme::normal();
+ convert_nscolor_to_absolute_color(self.system_nscolor(SystemColor::Canvastext, &normal))
+ }
+
+ /// Returns the current effective text zoom.
+ #[inline]
+ fn text_zoom(&self) -> f32 {
+ let pc = match self.pres_context() {
+ Some(pc) => pc,
+ None => return 1.,
+ };
+ pc.mTextZoom
+ }
+
+ /// Applies text zoom to a font-size or line-height value (see nsStyleFont::ZoomText).
+ #[inline]
+ pub fn zoom_text(&self, size: Length) -> Length {
+ size.scale_by(self.text_zoom())
+ }
+
+ /// Un-apply text zoom.
+ #[inline]
+ pub fn unzoom_text(&self, size: Length) -> Length {
+ size.scale_by(1. / self.text_zoom())
+ }
+
+ /// Returns safe area insets
+ pub fn safe_area_insets(&self) -> SideOffsets2D<f32, CSSPixel> {
+ let pc = match self.pres_context() {
+ Some(pc) => pc,
+ None => return SideOffsets2D::zero(),
+ };
+ let mut top = 0.0;
+ let mut right = 0.0;
+ let mut bottom = 0.0;
+ let mut left = 0.0;
+ unsafe {
+ bindings::Gecko_GetSafeAreaInsets(pc, &mut top, &mut right, &mut bottom, &mut left)
+ };
+ SideOffsets2D::new(top, right, bottom, left)
+ }
+
+ /// Returns true if the given MIME type is supported
+ pub fn is_supported_mime_type(&self, mime_type: &str) -> bool {
+ unsafe {
+ bindings::Gecko_IsSupportedImageMimeType(mime_type.as_ptr(), mime_type.len() as u32)
+ }
+ }
+
+ /// Return whether the document is a chrome document.
+ ///
+ /// This check is consistent with how we enable chrome rules for chrome:// and resource://
+ /// stylesheets (and thus chrome:// documents).
+ #[inline]
+ pub fn chrome_rules_enabled_for_document(&self) -> bool {
+ self.document().mChromeRulesEnabled()
+ }
+}
diff --git a/servo/components/style/gecko/mod.rs b/servo/components/style/gecko/mod.rs
new file mode 100644
index 0000000000..c32ded14f3
--- /dev/null
+++ b/servo/components/style/gecko/mod.rs
@@ -0,0 +1,23 @@
+/* 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/. */
+
+//! Gecko-specific style-system bits.
+
+#[macro_use]
+mod non_ts_pseudo_class_list;
+
+pub mod arc_types;
+pub mod conversions;
+pub mod data;
+pub mod media_features;
+pub mod media_queries;
+pub mod pseudo_element;
+pub mod restyle_damage;
+pub mod selector_parser;
+pub mod snapshot;
+pub mod snapshot_helpers;
+pub mod traversal;
+pub mod url;
+pub mod values;
+pub mod wrapper;
diff --git a/servo/components/style/gecko/non_ts_pseudo_class_list.rs b/servo/components/style/gecko/non_ts_pseudo_class_list.rs
new file mode 100644
index 0000000000..cc7495dd9c
--- /dev/null
+++ b/servo/components/style/gecko/non_ts_pseudo_class_list.rs
@@ -0,0 +1,106 @@
+/* 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/. */
+
+/*
+ * This file contains a helper macro includes all supported non-tree-structural
+ * pseudo-classes.
+ *
+ * FIXME: Find a way to autogenerate this file.
+ *
+ * Expected usage is as follows:
+ * ```
+ * macro_rules! pseudo_class_macro{
+ * ([$(($css:expr, $name:ident, $gecko_type:tt, $state:tt, $flags:tt),)*]) => {
+ * // do stuff
+ * }
+ * }
+ * apply_non_ts_list!(pseudo_class_macro)
+ * ```
+ *
+ * $gecko_type can be either "_" or an ident in Gecko's CSSPseudoClassType.
+ * $state can be either "_" or an expression of type ElementState. If present,
+ * the semantics are that the pseudo-class matches if any of the bits in
+ * $state are set on the element.
+ * $flags can be either "_" or an expression of type NonTSPseudoClassFlag,
+ * see selector_parser.rs for more details.
+ */
+
+macro_rules! apply_non_ts_list {
+ ($apply_macro:ident) => {
+ $apply_macro! {
+ [
+ ("-moz-table-border-nonzero", MozTableBorderNonzero, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
+ ("-moz-select-list-box", MozSelectListBox, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
+ ("link", Link, UNVISITED, _),
+ ("any-link", AnyLink, VISITED_OR_UNVISITED, _),
+ ("visited", Visited, VISITED, _),
+ ("active", Active, ACTIVE, _),
+ ("autofill", Autofill, AUTOFILL, _),
+ ("checked", Checked, CHECKED, _),
+ ("defined", Defined, DEFINED, _),
+ ("disabled", Disabled, DISABLED, _),
+ ("enabled", Enabled, ENABLED, _),
+ ("focus", Focus, FOCUS, _),
+ ("focus-within", FocusWithin, FOCUS_WITHIN, _),
+ ("focus-visible", FocusVisible, FOCUSRING, _),
+ ("hover", Hover, HOVER, _),
+ ("-moz-drag-over", MozDragOver, DRAGOVER, _),
+ ("target", Target, URLTARGET, _),
+ ("indeterminate", Indeterminate, INDETERMINATE, _),
+ ("-moz-inert", MozInert, INERT, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
+ ("-moz-devtools-highlighted", MozDevtoolsHighlighted, DEVTOOLS_HIGHLIGHTED, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
+ ("-moz-styleeditor-transitioning", MozStyleeditorTransitioning, STYLEEDITOR_TRANSITIONING, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
+ ("fullscreen", Fullscreen, FULLSCREEN, _),
+ ("modal", Modal, MODAL, _),
+ ("-moz-topmost-modal", MozTopmostModal, TOPMOST_MODAL, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
+ ("-moz-broken", MozBroken, BROKEN, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME),
+ ("-moz-has-dir-attr", MozHasDirAttr, HAS_DIR_ATTR, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
+ ("-moz-dir-attr-ltr", MozDirAttrLTR, HAS_DIR_ATTR_LTR, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
+ ("-moz-dir-attr-rtl", MozDirAttrRTL, HAS_DIR_ATTR_RTL, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
+ ("-moz-dir-attr-like-auto", MozDirAttrLikeAuto, HAS_DIR_ATTR_LIKE_AUTO, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
+
+ ("-moz-autofill-preview", MozAutofillPreview, AUTOFILL_PREVIEW, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME),
+ ("-moz-value-empty", MozValueEmpty, VALUE_EMPTY, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
+ ("-moz-revealed", MozRevealed, REVEALED, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
+
+ ("-moz-math-increment-script-level", MozMathIncrementScriptLevel, INCREMENT_SCRIPT_LEVEL, _),
+
+ ("required", Required, REQUIRED, _),
+ ("popover-open", PopoverOpen, POPOVER_OPEN, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME),
+ ("optional", Optional, OPTIONAL_, _),
+ ("valid", Valid, VALID, _),
+ ("invalid", Invalid, INVALID, _),
+ ("in-range", InRange, INRANGE, _),
+ ("out-of-range", OutOfRange, OUTOFRANGE, _),
+ ("default", Default, DEFAULT, _),
+ ("placeholder-shown", PlaceholderShown, PLACEHOLDER_SHOWN, _),
+ ("read-only", ReadOnly, READONLY, _),
+ ("read-write", ReadWrite, READWRITE, _),
+ ("user-valid", UserValid, USER_VALID, _),
+ ("user-invalid", UserInvalid, USER_INVALID, _),
+ ("-moz-meter-optimum", MozMeterOptimum, OPTIMUM, _),
+ ("-moz-meter-sub-optimum", MozMeterSubOptimum, SUB_OPTIMUM, _),
+ ("-moz-meter-sub-sub-optimum", MozMeterSubSubOptimum, SUB_SUB_OPTIMUM, _),
+
+ ("-moz-first-node", MozFirstNode, _, _),
+ ("-moz-last-node", MozLastNode, _, _),
+ ("-moz-only-whitespace", MozOnlyWhitespace, _, _),
+ ("-moz-native-anonymous", MozNativeAnonymous, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
+ ("-moz-placeholder", MozPlaceholder, _, _),
+
+ // NOTE(emilio): Pseudo-classes below only depend on document state, and thus
+ // conceptually they should probably be media queries instead.
+ //
+ // However that has a set of trade-offs that might not be worth making. In
+ // particular, such media queries would prevent documents that match them from
+ // sharing user-agent stylesheets with documents that don't. Also, changes between
+ // media query results are more expensive than document state changes. So for now
+ // making them pseudo-classes is probably the right trade-off.
+ ("-moz-is-html", MozIsHTML, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
+ ("-moz-lwtheme", MozLWTheme, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME),
+ ("-moz-window-inactive", MozWindowInactive, _, _),
+ ]
+ }
+ }
+}
diff --git a/servo/components/style/gecko/pseudo_element.rs b/servo/components/style/gecko/pseudo_element.rs
new file mode 100644
index 0000000000..3bcd873455
--- /dev/null
+++ b/servo/components/style/gecko/pseudo_element.rs
@@ -0,0 +1,233 @@
+/* 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/. */
+
+//! Gecko's definition of a pseudo-element.
+//!
+//! Note that a few autogenerated bits of this live in
+//! `pseudo_element_definition.mako.rs`. If you touch that file, you probably
+//! need to update the checked-in files for Servo.
+
+use crate::gecko_bindings::structs::{self, PseudoStyleType};
+use crate::properties::longhands::display::computed_value::T as Display;
+use crate::properties::{ComputedValues, PropertyFlags};
+use crate::selector_parser::{PseudoElementCascadeType, SelectorImpl};
+use crate::str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase};
+use crate::string_cache::Atom;
+use crate::values::serialize_atom_identifier;
+use crate::values::AtomIdent;
+use cssparser::ToCss;
+use static_prefs::pref;
+use std::fmt;
+
+include!(concat!(
+ env!("OUT_DIR"),
+ "/gecko/pseudo_element_definition.rs"
+));
+
+impl ::selectors::parser::PseudoElement for PseudoElement {
+ type Impl = SelectorImpl;
+
+ // ::slotted() should support all tree-abiding pseudo-elements, see
+ // https://drafts.csswg.org/css-scoping/#slotted-pseudo
+ // https://drafts.csswg.org/css-pseudo-4/#treelike
+ #[inline]
+ fn valid_after_slotted(&self) -> bool {
+ matches!(
+ *self,
+ Self::Before |
+ Self::After |
+ Self::Marker |
+ Self::Placeholder |
+ Self::FileSelectorButton
+ )
+ }
+
+ #[inline]
+ fn accepts_state_pseudo_classes(&self) -> bool {
+ self.supports_user_action_state()
+ }
+}
+
+impl PseudoElement {
+ /// Returns the kind of cascade type that a given pseudo is going to use.
+ ///
+ /// In Gecko we only compute ::before and ::after eagerly. We save the rules
+ /// for anonymous boxes separately, so we resolve them as precomputed
+ /// pseudos.
+ ///
+ /// We resolve the others lazily, see `Servo_ResolvePseudoStyle`.
+ pub fn cascade_type(&self) -> PseudoElementCascadeType {
+ if self.is_eager() {
+ debug_assert!(!self.is_anon_box());
+ return PseudoElementCascadeType::Eager;
+ }
+
+ if self.is_precomputed() {
+ return PseudoElementCascadeType::Precomputed;
+ }
+
+ PseudoElementCascadeType::Lazy
+ }
+
+ /// Gets the canonical index of this eagerly-cascaded pseudo-element.
+ #[inline]
+ pub fn eager_index(&self) -> usize {
+ EAGER_PSEUDOS
+ .iter()
+ .position(|p| p == self)
+ .expect("Not an eager pseudo")
+ }
+
+ /// Creates a pseudo-element from an eager index.
+ #[inline]
+ pub fn from_eager_index(i: usize) -> Self {
+ EAGER_PSEUDOS[i].clone()
+ }
+
+ /// Whether animations for the current pseudo element are stored in the
+ /// parent element.
+ #[inline]
+ pub fn animations_stored_in_parent(&self) -> bool {
+ matches!(*self, Self::Before | Self::After | Self::Marker)
+ }
+
+ /// Whether the current pseudo element is ::before or ::after.
+ #[inline]
+ pub fn is_before_or_after(&self) -> bool {
+ matches!(*self, Self::Before | Self::After)
+ }
+
+ /// Whether this pseudo-element is the ::before pseudo.
+ #[inline]
+ pub fn is_before(&self) -> bool {
+ *self == PseudoElement::Before
+ }
+
+ /// Whether this pseudo-element is the ::after pseudo.
+ #[inline]
+ pub fn is_after(&self) -> bool {
+ *self == PseudoElement::After
+ }
+
+ /// Whether this pseudo-element is the ::marker pseudo.
+ #[inline]
+ pub fn is_marker(&self) -> bool {
+ *self == PseudoElement::Marker
+ }
+
+ /// Whether this pseudo-element is the ::selection pseudo.
+ #[inline]
+ pub fn is_selection(&self) -> bool {
+ *self == PseudoElement::Selection
+ }
+
+ /// Whether this pseudo-element is ::first-letter.
+ #[inline]
+ pub fn is_first_letter(&self) -> bool {
+ *self == PseudoElement::FirstLetter
+ }
+
+ /// Whether this pseudo-element is ::first-line.
+ #[inline]
+ pub fn is_first_line(&self) -> bool {
+ *self == PseudoElement::FirstLine
+ }
+
+ /// Whether this pseudo-element is the ::-moz-color-swatch pseudo.
+ #[inline]
+ pub fn is_color_swatch(&self) -> bool {
+ *self == PseudoElement::MozColorSwatch
+ }
+
+ /// Whether this pseudo-element is lazily-cascaded.
+ #[inline]
+ pub fn is_lazy(&self) -> bool {
+ !self.is_eager() && !self.is_precomputed()
+ }
+
+ /// The identifier of the highlight this pseudo-element represents.
+ pub fn highlight_name(&self) -> Option<&AtomIdent> {
+ match *self {
+ Self::Highlight(ref name) => Some(name),
+ _ => None,
+ }
+ }
+
+ /// Whether this pseudo-element is the ::highlight pseudo.
+ pub fn is_highlight(&self) -> bool {
+ matches!(*self, Self::Highlight(_))
+ }
+
+ /// Whether this pseudo-element supports user action selectors.
+ pub fn supports_user_action_state(&self) -> bool {
+ (self.flags() & structs::CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE) != 0
+ }
+
+ /// Whether this pseudo-element is enabled for all content.
+ pub fn enabled_in_content(&self) -> bool {
+ match *self {
+ Self::Highlight(..) => pref!("dom.customHighlightAPI.enabled"),
+ Self::SliderFill | Self::SliderTrack | Self::SliderThumb => {
+ pref!("layout.css.modern-range-pseudos.enabled")
+ },
+ // If it's not explicitly enabled in UA sheets or chrome, then we're enabled for
+ // content.
+ _ => (self.flags() & structs::CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS_AND_CHROME) == 0,
+ }
+ }
+
+ /// Whether this pseudo is enabled explicitly in UA sheets.
+ pub fn enabled_in_ua_sheets(&self) -> bool {
+ (self.flags() & structs::CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS) != 0
+ }
+
+ /// Whether this pseudo is enabled explicitly in chrome sheets.
+ pub fn enabled_in_chrome(&self) -> bool {
+ (self.flags() & structs::CSS_PSEUDO_ELEMENT_ENABLED_IN_CHROME) != 0
+ }
+
+ /// Whether this pseudo-element skips flex/grid container display-based
+ /// fixup.
+ #[inline]
+ pub fn skip_item_display_fixup(&self) -> bool {
+ (self.flags() & structs::CSS_PSEUDO_ELEMENT_IS_FLEX_OR_GRID_ITEM) == 0
+ }
+
+ /// Whether this pseudo-element is precomputed.
+ #[inline]
+ pub fn is_precomputed(&self) -> bool {
+ self.is_anon_box() && !self.is_tree_pseudo_element()
+ }
+
+ /// Property flag that properties must have to apply to this pseudo-element.
+ #[inline]
+ pub fn property_restriction(&self) -> Option<PropertyFlags> {
+ Some(match *self {
+ PseudoElement::FirstLetter => PropertyFlags::APPLIES_TO_FIRST_LETTER,
+ PseudoElement::FirstLine => PropertyFlags::APPLIES_TO_FIRST_LINE,
+ PseudoElement::Placeholder => PropertyFlags::APPLIES_TO_PLACEHOLDER,
+ PseudoElement::Cue => PropertyFlags::APPLIES_TO_CUE,
+ PseudoElement::Marker if static_prefs::pref!("layout.css.marker.restricted") => {
+ PropertyFlags::APPLIES_TO_MARKER
+ },
+ _ => return None,
+ })
+ }
+
+ /// Whether this pseudo-element should actually exist if it has
+ /// the given styles.
+ pub fn should_exist(&self, style: &ComputedValues) -> bool {
+ debug_assert!(self.is_eager());
+
+ if style.get_box().clone_display() == Display::None {
+ return false;
+ }
+
+ if self.is_before_or_after() && style.ineffective_content_property() {
+ return false;
+ }
+
+ true
+ }
+}
diff --git a/servo/components/style/gecko/pseudo_element_definition.mako.rs b/servo/components/style/gecko/pseudo_element_definition.mako.rs
new file mode 100644
index 0000000000..48f0618502
--- /dev/null
+++ b/servo/components/style/gecko/pseudo_element_definition.mako.rs
@@ -0,0 +1,278 @@
+/* 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/. */
+
+/// Gecko's pseudo-element definition.
+///
+/// We intentionally double-box legacy ::-moz-tree pseudo-elements to keep the
+/// size of PseudoElement (and thus selector components) small.
+#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToShmem)]
+pub enum PseudoElement {
+ % for pseudo in PSEUDOS:
+ /// ${pseudo.value}
+ % if pseudo.is_tree_pseudo_element():
+ ${pseudo.capitalized_pseudo()}(thin_vec::ThinVec<Atom>),
+ % elif pseudo.pseudo_ident == "highlight":
+ ${pseudo.capitalized_pseudo()}(AtomIdent),
+ % else:
+ ${pseudo.capitalized_pseudo()},
+ % endif
+ % endfor
+ /// ::-webkit-* that we don't recognize
+ /// https://github.com/whatwg/compat/issues/103
+ UnknownWebkit(Atom),
+}
+
+/// Important: If you change this, you should also update Gecko's
+/// nsCSSPseudoElements::IsEagerlyCascadedInServo.
+<% EAGER_PSEUDOS = ["Before", "After", "FirstLine", "FirstLetter"] %>
+<% TREE_PSEUDOS = [pseudo for pseudo in PSEUDOS if pseudo.is_tree_pseudo_element()] %>
+<% SIMPLE_PSEUDOS = [pseudo for pseudo in PSEUDOS if pseudo.is_simple_pseudo_element()] %>
+
+/// The number of eager pseudo-elements.
+pub const EAGER_PSEUDO_COUNT: usize = ${len(EAGER_PSEUDOS)};
+
+/// The number of non-functional pseudo-elements.
+pub const SIMPLE_PSEUDO_COUNT: usize = ${len(SIMPLE_PSEUDOS)};
+
+/// The number of tree pseudo-elements.
+pub const TREE_PSEUDO_COUNT: usize = ${len(TREE_PSEUDOS)};
+
+/// The number of all pseudo-elements.
+pub const PSEUDO_COUNT: usize = ${len(PSEUDOS)};
+
+/// The list of eager pseudos.
+pub const EAGER_PSEUDOS: [PseudoElement; EAGER_PSEUDO_COUNT] = [
+ % for eager_pseudo_name in EAGER_PSEUDOS:
+ PseudoElement::${eager_pseudo_name},
+ % endfor
+];
+
+<%def name="pseudo_element_variant(pseudo, tree_arg='..')">\
+PseudoElement::${pseudo.capitalized_pseudo()}${"({})".format(tree_arg) if not pseudo.is_simple_pseudo_element() else ""}\
+</%def>
+
+impl PseudoElement {
+ /// Returns an index of the pseudo-element.
+ #[inline]
+ pub fn index(&self) -> usize {
+ match *self {
+ % for i, pseudo in enumerate(PSEUDOS):
+ ${pseudo_element_variant(pseudo)} => ${i},
+ % endfor
+ PseudoElement::UnknownWebkit(..) => unreachable!(),
+ }
+ }
+
+ /// Returns an array of `None` values.
+ ///
+ /// FIXME(emilio): Integer generics can't come soon enough.
+ pub fn pseudo_none_array<T>() -> [Option<T>; PSEUDO_COUNT] {
+ [
+ ${",\n ".join(["None" for pseudo in PSEUDOS])}
+ ]
+ }
+
+ /// Whether this pseudo-element is an anonymous box.
+ #[inline]
+ pub fn is_anon_box(&self) -> bool {
+ match *self {
+ % for pseudo in PSEUDOS:
+ % if pseudo.is_anon_box():
+ ${pseudo_element_variant(pseudo)} => true,
+ % endif
+ % endfor
+ _ => false,
+ }
+ }
+
+ /// Whether this pseudo-element is eagerly-cascaded.
+ #[inline]
+ pub fn is_eager(&self) -> bool {
+ matches!(*self,
+ ${" | ".join(map(lambda name: "PseudoElement::{}".format(name), EAGER_PSEUDOS))})
+ }
+
+ /// Whether this pseudo-element is tree pseudo-element.
+ #[inline]
+ pub fn is_tree_pseudo_element(&self) -> bool {
+ match *self {
+ % for pseudo in TREE_PSEUDOS:
+ ${pseudo_element_variant(pseudo)} => true,
+ % endfor
+ _ => false,
+ }
+ }
+
+ /// Whether this pseudo-element is an unknown Webkit-prefixed pseudo-element.
+ #[inline]
+ pub fn is_unknown_webkit_pseudo_element(&self) -> bool {
+ matches!(*self, PseudoElement::UnknownWebkit(..))
+ }
+
+ /// Gets the flags associated to this pseudo-element, or 0 if it's an
+ /// anonymous box.
+ pub fn flags(&self) -> u32 {
+ match *self {
+ % for pseudo in PSEUDOS:
+ ${pseudo_element_variant(pseudo)} =>
+ % if pseudo.is_tree_pseudo_element():
+ structs::CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS_AND_CHROME,
+ % elif pseudo.is_anon_box():
+ structs::CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS,
+ % else:
+ structs::SERVO_CSS_PSEUDO_ELEMENT_FLAGS_${pseudo.pseudo_ident},
+ % endif
+ % endfor
+ PseudoElement::UnknownWebkit(..) => 0,
+ }
+ }
+
+ /// Construct a pseudo-element from a `PseudoStyleType`.
+ #[inline]
+ pub fn from_pseudo_type(type_: PseudoStyleType, functional_pseudo_parameter: Option<AtomIdent>) -> Option<Self> {
+ match type_ {
+ % for pseudo in PSEUDOS:
+ % if pseudo.is_simple_pseudo_element():
+ PseudoStyleType::${pseudo.pseudo_ident} => {
+ debug_assert!(functional_pseudo_parameter.is_none());
+ Some(${pseudo_element_variant(pseudo)})
+ },
+ % endif
+ % endfor
+ PseudoStyleType::highlight => {
+ match functional_pseudo_parameter {
+ Some(p) => Some(PseudoElement::Highlight(p)),
+ None => None
+ }
+ }
+ _ => None,
+ }
+ }
+
+ /// Construct a `PseudoStyleType` from a pseudo-element
+ #[inline]
+ pub fn pseudo_type(&self) -> PseudoStyleType {
+ match *self {
+ % for pseudo in PSEUDOS:
+ % if pseudo.is_tree_pseudo_element():
+ PseudoElement::${pseudo.capitalized_pseudo()}(..) => PseudoStyleType::XULTree,
+ % elif pseudo.pseudo_ident == "highlight":
+ PseudoElement::${pseudo.capitalized_pseudo()}(..) => PseudoStyleType::${pseudo.pseudo_ident},
+ % else:
+ PseudoElement::${pseudo.capitalized_pseudo()} => PseudoStyleType::${pseudo.pseudo_ident},
+ % endif
+ % endfor
+ PseudoElement::UnknownWebkit(..) => unreachable!(),
+ }
+ }
+
+ /// Get the argument list of a tree pseudo-element.
+ #[inline]
+ pub fn tree_pseudo_args(&self) -> Option<<&[Atom]> {
+ match *self {
+ % for pseudo in TREE_PSEUDOS:
+ PseudoElement::${pseudo.capitalized_pseudo()}(ref args) => Some(args),
+ % endfor
+ _ => None,
+ }
+ }
+
+ /// Construct a tree pseudo-element from atom and args.
+ #[inline]
+ pub fn from_tree_pseudo_atom(atom: &Atom, args: Box<[Atom]>) -> Option<Self> {
+ % for pseudo in PSEUDOS:
+ % if pseudo.is_tree_pseudo_element():
+ if atom == &atom!("${pseudo.value}") {
+ return Some(PseudoElement::${pseudo.capitalized_pseudo()}(args.into()));
+ }
+ % endif
+ % endfor
+ None
+ }
+
+ /// Constructs a pseudo-element from a string of text.
+ ///
+ /// Returns `None` if the pseudo-element is not recognised.
+ #[inline]
+ pub fn from_slice(name: &str, allow_unkown_webkit: bool) -> Option<Self> {
+ // We don't need to support tree pseudos because functional
+ // pseudo-elements needs arguments, and thus should be created
+ // via other methods.
+ ascii_case_insensitive_phf_map! {
+ pseudo -> PseudoElement = {
+ % for pseudo in SIMPLE_PSEUDOS:
+ "${pseudo.value[1:]}" => ${pseudo_element_variant(pseudo)},
+ % endfor
+ // Alias some legacy prefixed pseudos to their standardized name at parse time:
+ "-moz-selection" => PseudoElement::Selection,
+ "-moz-placeholder" => PseudoElement::Placeholder,
+ "-moz-list-bullet" => PseudoElement::Marker,
+ "-moz-list-number" => PseudoElement::Marker,
+ }
+ }
+ if let Some(p) = pseudo::get(name) {
+ return Some(p.clone());
+ }
+ if starts_with_ignore_ascii_case(name, "-moz-tree-") {
+ return PseudoElement::tree_pseudo_element(name, Default::default())
+ }
+ const WEBKIT_PREFIX: &str = "-webkit-";
+ if allow_unkown_webkit && starts_with_ignore_ascii_case(name, WEBKIT_PREFIX) {
+ let part = string_as_ascii_lowercase(&name[WEBKIT_PREFIX.len()..]);
+ return Some(PseudoElement::UnknownWebkit(part.into()));
+ }
+ None
+ }
+
+ /// Constructs a tree pseudo-element from the given name and arguments.
+ /// "name" must start with "-moz-tree-".
+ ///
+ /// Returns `None` if the pseudo-element is not recognized.
+ #[inline]
+ pub fn tree_pseudo_element(name: &str, args: thin_vec::ThinVec<Atom>) -> Option<Self> {
+ debug_assert!(starts_with_ignore_ascii_case(name, "-moz-tree-"));
+ let tree_part = &name[10..];
+ % for pseudo in TREE_PSEUDOS:
+ if tree_part.eq_ignore_ascii_case("${pseudo.value[11:]}") {
+ return Some(${pseudo_element_variant(pseudo, "args")});
+ }
+ % endfor
+ None
+ }
+}
+
+impl ToCss for PseudoElement {
+ fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+ dest.write_char(':')?;
+ match *self {
+ % for pseudo in (p for p in PSEUDOS if p.pseudo_ident != "highlight"):
+ ${pseudo_element_variant(pseudo)} => dest.write_str("${pseudo.value}")?,
+ % endfor
+ PseudoElement::Highlight(ref name) => {
+ dest.write_str(":highlight(")?;
+ serialize_atom_identifier(name, dest)?;
+ dest.write_char(')')?;
+ }
+ PseudoElement::UnknownWebkit(ref atom) => {
+ dest.write_str(":-webkit-")?;
+ serialize_atom_identifier(atom, dest)?;
+ }
+ }
+ if let Some(args) = self.tree_pseudo_args() {
+ if !args.is_empty() {
+ dest.write_char('(')?;
+ let mut iter = args.iter();
+ if let Some(first) = iter.next() {
+ serialize_atom_identifier(&first, dest)?;
+ for item in iter {
+ dest.write_str(", ")?;
+ serialize_atom_identifier(item, dest)?;
+ }
+ }
+ dest.write_char(')')?;
+ }
+ }
+ Ok(())
+ }
+}
diff --git a/servo/components/style/gecko/regen_atoms.py b/servo/components/style/gecko/regen_atoms.py
new file mode 100755
index 0000000000..61f2fc4c63
--- /dev/null
+++ b/servo/components/style/gecko/regen_atoms.py
@@ -0,0 +1,218 @@
+#!/usr/bin/env python
+
+# 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/.
+
+import re
+import os
+import sys
+
+from io import BytesIO
+
+GECKO_DIR = os.path.dirname(__file__.replace("\\", "/"))
+sys.path.insert(0, os.path.join(os.path.dirname(GECKO_DIR), "properties"))
+
+import build
+
+
+# Matches lines like `GK_ATOM(foo, "foo", 0x12345678, true, nsStaticAtom, PseudoElementAtom)`.
+PATTERN = re.compile(
+ '^GK_ATOM\(([^,]*),[^"]*"([^"]*)",\s*(0x[0-9a-f]+),\s*[^,]*,\s*([^,]*),\s*([^)]*)\)',
+ re.MULTILINE,
+)
+FILE = "include/nsGkAtomList.h"
+
+
+def map_atom(ident):
+ if ident in {
+ "box",
+ "loop",
+ "match",
+ "mod",
+ "ref",
+ "self",
+ "type",
+ "use",
+ "where",
+ "in",
+ }:
+ return ident + "_"
+ return ident
+
+
+class Atom:
+ def __init__(self, ident, value, hash, ty, atom_type):
+ self.ident = "nsGkAtoms_{}".format(ident)
+ self.original_ident = ident
+ self.value = value
+ self.hash = hash
+ # The Gecko type: "nsStaticAtom", "nsCSSPseudoElementStaticAtom", or
+ # "nsAnonBoxPseudoStaticAtom".
+ self.ty = ty
+ # The type of atom: "Atom", "PseudoElement", "NonInheritingAnonBox",
+ # or "InheritingAnonBox".
+ self.atom_type = atom_type
+
+ if (
+ self.is_pseudo_element()
+ or self.is_anon_box()
+ or self.is_tree_pseudo_element()
+ ):
+ self.pseudo_ident = (ident.split("_", 1))[1]
+
+ if self.is_anon_box():
+ assert self.is_inheriting_anon_box() or self.is_non_inheriting_anon_box()
+
+ def type(self):
+ return self.ty
+
+ def capitalized_pseudo(self):
+ return self.pseudo_ident[0].upper() + self.pseudo_ident[1:]
+
+ def is_pseudo_element(self):
+ return self.atom_type == "PseudoElementAtom"
+
+ def is_anon_box(self):
+ if self.is_tree_pseudo_element():
+ return False
+ return self.is_non_inheriting_anon_box() or self.is_inheriting_anon_box()
+
+ def is_non_inheriting_anon_box(self):
+ assert not self.is_tree_pseudo_element()
+ return self.atom_type == "NonInheritingAnonBoxAtom"
+
+ def is_inheriting_anon_box(self):
+ if self.is_tree_pseudo_element():
+ return False
+ return self.atom_type == "InheritingAnonBoxAtom"
+
+ def is_tree_pseudo_element(self):
+ return self.value.startswith(":-moz-tree-")
+
+ def is_simple_pseudo_element(self) -> bool:
+ return not (self.is_tree_pseudo_element() or self.pseudo_ident == "highlight")
+
+
+def collect_atoms(objdir):
+ atoms = []
+ path = os.path.abspath(os.path.join(objdir, FILE))
+ print("cargo:rerun-if-changed={}".format(path))
+ with open(path) as f:
+ content = f.read()
+ for result in PATTERN.finditer(content):
+ atoms.append(
+ Atom(
+ result.group(1),
+ result.group(2),
+ result.group(3),
+ result.group(4),
+ result.group(5),
+ )
+ )
+ return atoms
+
+
+class FileAvoidWrite(BytesIO):
+ """File-like object that buffers output and only writes if content changed."""
+
+ def __init__(self, filename):
+ BytesIO.__init__(self)
+ self.name = filename
+
+ def write(self, buf):
+ if isinstance(buf, str):
+ buf = buf.encode("utf-8")
+ BytesIO.write(self, buf)
+
+ def close(self):
+ buf = self.getvalue()
+ BytesIO.close(self)
+ try:
+ with open(self.name, "rb") as f:
+ old_content = f.read()
+ if old_content == buf:
+ print("{} is not changed, skip".format(self.name))
+ return
+ except IOError:
+ pass
+ with open(self.name, "wb") as f:
+ f.write(buf)
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, type, value, traceback):
+ if not self.closed:
+ self.close()
+
+
+PRELUDE = """
+/* 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/. */
+
+// Autogenerated file created by components/style/gecko/regen_atoms.py.
+// DO NOT EDIT DIRECTLY
+"""[
+ 1:
+]
+
+RULE_TEMPLATE = """
+ ("{atom}") => {{{{
+ #[allow(unsafe_code)] #[allow(unused_unsafe)]
+ unsafe {{ $crate::string_cache::Atom::from_index_unchecked({index}) }}
+ }}}};
+"""[
+ 1:
+]
+
+MACRO_TEMPLATE = """
+/// Returns a static atom by passing the literal string it represents.
+#[macro_export]
+macro_rules! atom {{
+{body}\
+}}
+"""
+
+
+def write_atom_macro(atoms, file_name):
+ with FileAvoidWrite(file_name) as f:
+ f.write(PRELUDE)
+ macro_rules = [
+ RULE_TEMPLATE.format(atom=atom.value, name=atom.ident, index=i)
+ for (i, atom) in enumerate(atoms)
+ ]
+ f.write(MACRO_TEMPLATE.format(body="".join(macro_rules)))
+
+
+def write_pseudo_elements(atoms, target_filename):
+ pseudos = []
+ for atom in atoms:
+ if (
+ atom.type() == "nsCSSPseudoElementStaticAtom"
+ or atom.type() == "nsCSSAnonBoxPseudoStaticAtom"
+ ):
+ pseudos.append(atom)
+
+ pseudo_definition_template = os.path.join(
+ GECKO_DIR, "pseudo_element_definition.mako.rs"
+ )
+ print("cargo:rerun-if-changed={}".format(pseudo_definition_template))
+ contents = build.render(pseudo_definition_template, PSEUDOS=pseudos)
+
+ with FileAvoidWrite(target_filename) as f:
+ f.write(contents)
+
+
+def generate_atoms(dist, out):
+ atoms = collect_atoms(dist)
+ write_atom_macro(atoms, os.path.join(out, "atom_macro.rs"))
+ write_pseudo_elements(atoms, os.path.join(out, "pseudo_element_definition.rs"))
+
+
+if __name__ == "__main__":
+ if len(sys.argv) != 3:
+ print("Usage: {} dist out".format(sys.argv[0]))
+ exit(2)
+ generate_atoms(sys.argv[1], sys.argv[2])
diff --git a/servo/components/style/gecko/restyle_damage.rs b/servo/components/style/gecko/restyle_damage.rs
new file mode 100644
index 0000000000..4749daea18
--- /dev/null
+++ b/servo/components/style/gecko/restyle_damage.rs
@@ -0,0 +1,121 @@
+/* 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/. */
+
+//! Gecko's restyle damage computation (aka change hints, aka `nsChangeHint`).
+
+use crate::gecko_bindings::bindings;
+use crate::gecko_bindings::structs;
+use crate::gecko_bindings::structs::nsChangeHint;
+use crate::matching::{StyleChange, StyleDifference};
+use crate::properties::ComputedValues;
+use std::ops::{BitAnd, BitOr, BitOrAssign, Not};
+
+/// The representation of Gecko's restyle damage is just a wrapper over
+/// `nsChangeHint`.
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub struct GeckoRestyleDamage(nsChangeHint);
+
+impl GeckoRestyleDamage {
+ /// Trivially construct a new `GeckoRestyleDamage`.
+ #[inline]
+ pub fn new(raw: nsChangeHint) -> Self {
+ GeckoRestyleDamage(raw)
+ }
+
+ /// Get the inner change hint for this damage.
+ #[inline]
+ pub fn as_change_hint(&self) -> nsChangeHint {
+ self.0
+ }
+
+ /// Get an empty change hint, that is (`nsChangeHint(0)`).
+ #[inline]
+ pub fn empty() -> Self {
+ GeckoRestyleDamage(nsChangeHint(0))
+ }
+
+ /// Returns whether this restyle damage represents the empty damage.
+ #[inline]
+ pub fn is_empty(&self) -> bool {
+ self.0 == nsChangeHint(0)
+ }
+
+ /// Computes the `StyleDifference` (including the appropriate change hint)
+ /// given an old and a new style.
+ pub fn compute_style_difference(
+ old_style: &ComputedValues,
+ new_style: &ComputedValues,
+ ) -> StyleDifference {
+ let mut any_style_changed = false;
+ let mut reset_only = false;
+ let hint = unsafe {
+ bindings::Gecko_CalcStyleDifference(
+ old_style.as_gecko_computed_style(),
+ new_style.as_gecko_computed_style(),
+ &mut any_style_changed,
+ &mut reset_only,
+ )
+ };
+ if reset_only && !old_style.custom_properties_equal(new_style) {
+ // The Gecko_CalcStyleDifference call only checks the non-custom
+ // property structs, so we check the custom properties here. Since
+ // they generate no damage themselves, we can skip this check if we
+ // already know we had some inherited (regular) property
+ // differences.
+ any_style_changed = true;
+ reset_only = false;
+ }
+ let change = if any_style_changed {
+ StyleChange::Changed { reset_only }
+ } else {
+ StyleChange::Unchanged
+ };
+ let damage = GeckoRestyleDamage(nsChangeHint(hint));
+ StyleDifference { damage, change }
+ }
+
+ /// Returns true if this restyle damage contains all the damage of |other|.
+ pub fn contains(self, other: Self) -> bool {
+ self & other == other
+ }
+
+ /// Gets restyle damage to reconstruct the entire frame, subsuming all
+ /// other damage.
+ pub fn reconstruct() -> Self {
+ GeckoRestyleDamage(structs::nsChangeHint::nsChangeHint_ReconstructFrame)
+ }
+}
+
+impl Default for GeckoRestyleDamage {
+ fn default() -> Self {
+ Self::empty()
+ }
+}
+
+impl BitOr for GeckoRestyleDamage {
+ type Output = Self;
+ fn bitor(self, other: Self) -> Self {
+ GeckoRestyleDamage(self.0 | other.0)
+ }
+}
+
+impl BitOrAssign for GeckoRestyleDamage {
+ fn bitor_assign(&mut self, other: Self) {
+ *self = *self | other;
+ }
+}
+
+impl BitAnd for GeckoRestyleDamage {
+ type Output = Self;
+ fn bitand(self, other: Self) -> Self {
+ GeckoRestyleDamage(nsChangeHint((self.0).0 & (other.0).0))
+ }
+}
+
+impl Not for GeckoRestyleDamage {
+ type Output = Self;
+ fn not(self) -> Self {
+ GeckoRestyleDamage(nsChangeHint(!(self.0).0))
+ }
+}
diff --git a/servo/components/style/gecko/selector_parser.rs b/servo/components/style/gecko/selector_parser.rs
new file mode 100644
index 0000000000..203e6a3609
--- /dev/null
+++ b/servo/components/style/gecko/selector_parser.rs
@@ -0,0 +1,519 @@
+/* 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/. */
+
+//! Gecko-specific bits for selector-parsing.
+
+use crate::computed_value_flags::ComputedValueFlags;
+use crate::invalidation::element::document_state::InvalidationMatchingData;
+use crate::properties::ComputedValues;
+use crate::selector_parser::{Direction, HorizontalDirection, SelectorParser};
+use crate::str::starts_with_ignore_ascii_case;
+use crate::string_cache::{Atom, Namespace, WeakAtom, WeakNamespace};
+use crate::values::{AtomIdent, AtomString};
+use cssparser::{BasicParseError, BasicParseErrorKind, Parser};
+use cssparser::{CowRcStr, SourceLocation, ToCss, Token};
+use dom::{DocumentState, ElementState};
+use selectors::parser::SelectorParseErrorKind;
+use std::fmt;
+use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss as ToCss_};
+use thin_vec::ThinVec;
+
+pub use crate::gecko::pseudo_element::{
+ PseudoElement, EAGER_PSEUDOS, EAGER_PSEUDO_COUNT, PSEUDO_COUNT,
+};
+pub use crate::gecko::snapshot::SnapshotMap;
+
+bitflags! {
+ // See NonTSPseudoClass::is_enabled_in()
+ #[derive(Copy, Clone)]
+ struct NonTSPseudoClassFlag: u8 {
+ const PSEUDO_CLASS_ENABLED_IN_UA_SHEETS = 1 << 0;
+ const PSEUDO_CLASS_ENABLED_IN_CHROME = 1 << 1;
+ const PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME =
+ NonTSPseudoClassFlag::PSEUDO_CLASS_ENABLED_IN_UA_SHEETS.bits() |
+ NonTSPseudoClassFlag::PSEUDO_CLASS_ENABLED_IN_CHROME.bits();
+ }
+}
+
+/// The type used to store the language argument to the `:lang` pseudo-class.
+#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToCss, ToShmem)]
+#[css(comma)]
+pub struct Lang(#[css(iterable)] pub ThinVec<AtomIdent>);
+
+/// The type used to store the state argument to the `:state` pseudo-class.
+#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToCss, ToShmem)]
+pub struct CustomState(pub AtomIdent);
+
+macro_rules! pseudo_class_name {
+ ([$(($css:expr, $name:ident, $state:tt, $flags:tt),)*]) => {
+ /// Our representation of a non tree-structural pseudo-class.
+ #[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
+ pub enum NonTSPseudoClass {
+ $(
+ #[doc = $css]
+ $name,
+ )*
+ /// The `:lang` pseudo-class.
+ Lang(Lang),
+ /// The `:dir` pseudo-class.
+ Dir(Direction),
+ /// The :state` pseudo-class.
+ CustomState(CustomState),
+ /// The non-standard `:-moz-locale-dir` pseudo-class.
+ MozLocaleDir(Direction),
+ }
+ }
+}
+apply_non_ts_list!(pseudo_class_name);
+
+impl ToCss for NonTSPseudoClass {
+ fn to_css<W>(&self, dest: &mut W) -> fmt::Result
+ where
+ W: fmt::Write,
+ {
+ macro_rules! pseudo_class_serialize {
+ ([$(($css:expr, $name:ident, $state:tt, $flags:tt),)*]) => {
+ match *self {
+ $(NonTSPseudoClass::$name => concat!(":", $css),)*
+ NonTSPseudoClass::Lang(ref lang) => {
+ dest.write_str(":lang(")?;
+ lang.to_css(&mut CssWriter::new(dest))?;
+ return dest.write_char(')');
+ },
+ NonTSPseudoClass::CustomState(ref state) => {
+ dest.write_str(":state(")?;
+ state.to_css(&mut CssWriter::new(dest))?;
+ return dest.write_char(')');
+ },
+ NonTSPseudoClass::MozLocaleDir(ref dir) => {
+ dest.write_str(":-moz-locale-dir(")?;
+ dir.to_css(&mut CssWriter::new(dest))?;
+ return dest.write_char(')')
+ },
+ NonTSPseudoClass::Dir(ref dir) => {
+ dest.write_str(":dir(")?;
+ dir.to_css(&mut CssWriter::new(dest))?;
+ return dest.write_char(')')
+ },
+ }
+ }
+ }
+ let ser = apply_non_ts_list!(pseudo_class_serialize);
+ dest.write_str(ser)
+ }
+}
+
+impl NonTSPseudoClass {
+ /// Parses the name and returns a non-ts-pseudo-class if succeeds.
+ /// None otherwise. It doesn't check whether the pseudo-class is enabled
+ /// in a particular state.
+ pub fn parse_non_functional(name: &str) -> Option<Self> {
+ macro_rules! pseudo_class_parse {
+ ([$(($css:expr, $name:ident, $state:tt, $flags:tt),)*]) => {
+ match_ignore_ascii_case! { &name,
+ $($css => Some(NonTSPseudoClass::$name),)*
+ "-moz-full-screen" => Some(NonTSPseudoClass::Fullscreen),
+ "-moz-read-only" => Some(NonTSPseudoClass::ReadOnly),
+ "-moz-read-write" => Some(NonTSPseudoClass::ReadWrite),
+ "-moz-focusring" => Some(NonTSPseudoClass::FocusVisible),
+ "-moz-ui-valid" => Some(NonTSPseudoClass::UserValid),
+ "-moz-ui-invalid" => Some(NonTSPseudoClass::UserInvalid),
+ "-webkit-autofill" => Some(NonTSPseudoClass::Autofill),
+ _ => None,
+ }
+ }
+ }
+ apply_non_ts_list!(pseudo_class_parse)
+ }
+
+ /// Returns true if this pseudo-class has any of the given flags set.
+ fn has_any_flag(&self, flags: NonTSPseudoClassFlag) -> bool {
+ macro_rules! check_flag {
+ (_) => {
+ false
+ };
+ ($flags:ident) => {
+ NonTSPseudoClassFlag::$flags.intersects(flags)
+ };
+ }
+ macro_rules! pseudo_class_check_is_enabled_in {
+ ([$(($css:expr, $name:ident, $state:tt, $flags:tt),)*]) => {
+ match *self {
+ $(NonTSPseudoClass::$name => check_flag!($flags),)*
+ NonTSPseudoClass::MozLocaleDir(_) => check_flag!(PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME),
+ NonTSPseudoClass::CustomState(_) |
+ NonTSPseudoClass::Lang(_) |
+ NonTSPseudoClass::Dir(_) => false,
+ }
+ }
+ }
+ apply_non_ts_list!(pseudo_class_check_is_enabled_in)
+ }
+
+ /// Returns whether the pseudo-class is enabled in content sheets.
+ #[inline]
+ fn is_enabled_in_content(&self) -> bool {
+ if matches!(*self, Self::PopoverOpen) {
+ return static_prefs::pref!("dom.element.popover.enabled");
+ }
+ if matches!(*self, Self::CustomState(_)) {
+ return static_prefs::pref!("dom.element.customstateset.enabled");
+ }
+ !self.has_any_flag(NonTSPseudoClassFlag::PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME)
+ }
+
+ /// Get the state flag associated with a pseudo-class, if any.
+ pub fn state_flag(&self) -> ElementState {
+ macro_rules! flag {
+ (_) => {
+ ElementState::empty()
+ };
+ ($state:ident) => {
+ ElementState::$state
+ };
+ }
+ macro_rules! pseudo_class_state {
+ ([$(($css:expr, $name:ident, $state:tt, $flags:tt),)*]) => {
+ match *self {
+ $(NonTSPseudoClass::$name => flag!($state),)*
+ NonTSPseudoClass::Dir(ref dir) => dir.element_state(),
+ NonTSPseudoClass::MozLocaleDir(..) |
+ NonTSPseudoClass::CustomState(..) |
+ NonTSPseudoClass::Lang(..) => ElementState::empty(),
+ }
+ }
+ }
+ apply_non_ts_list!(pseudo_class_state)
+ }
+
+ /// Get the document state flag associated with a pseudo-class, if any.
+ pub fn document_state_flag(&self) -> DocumentState {
+ match *self {
+ NonTSPseudoClass::MozLocaleDir(ref dir) => match dir.as_horizontal_direction() {
+ Some(HorizontalDirection::Ltr) => DocumentState::LTR_LOCALE,
+ Some(HorizontalDirection::Rtl) => DocumentState::RTL_LOCALE,
+ None => DocumentState::empty(),
+ },
+ NonTSPseudoClass::MozWindowInactive => DocumentState::WINDOW_INACTIVE,
+ NonTSPseudoClass::MozLWTheme => DocumentState::LWTHEME,
+ _ => DocumentState::empty(),
+ }
+ }
+
+ /// Returns true if the given pseudoclass should trigger style sharing cache
+ /// revalidation.
+ pub fn needs_cache_revalidation(&self) -> bool {
+ self.state_flag().is_empty() &&
+ !matches!(
+ *self,
+ // :dir() depends on state only, but may have an empty state_flag for invalid
+ // arguments.
+ NonTSPseudoClass::Dir(_) |
+ // We prevent style sharing for NAC.
+ NonTSPseudoClass::MozNativeAnonymous |
+ // :-moz-placeholder is parsed but never matches.
+ NonTSPseudoClass::MozPlaceholder |
+ // :-moz-is-html, :-moz-lwtheme, :-moz-locale-dir and :-moz-window-inactive
+ // depend only on the state of the document, which is invariant across all
+ // elements involved in a given style cache.
+ NonTSPseudoClass::MozIsHTML |
+ NonTSPseudoClass::MozLWTheme |
+ NonTSPseudoClass::MozLocaleDir(_) |
+ NonTSPseudoClass::MozWindowInactive
+ )
+ }
+}
+
+impl ::selectors::parser::NonTSPseudoClass for NonTSPseudoClass {
+ type Impl = SelectorImpl;
+
+ #[inline]
+ fn is_active_or_hover(&self) -> bool {
+ matches!(*self, NonTSPseudoClass::Active | NonTSPseudoClass::Hover)
+ }
+
+ /// We intentionally skip the link-related ones.
+ #[inline]
+ fn is_user_action_state(&self) -> bool {
+ matches!(
+ *self,
+ NonTSPseudoClass::Hover | NonTSPseudoClass::Active | NonTSPseudoClass::Focus
+ )
+ }
+}
+
+/// The dummy struct we use to implement our selector parsing.
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct SelectorImpl;
+
+/// A set of extra data to carry along with the matching context, either for
+/// selector-matching or invalidation.
+#[derive(Default)]
+pub struct ExtraMatchingData<'a> {
+ /// The invalidation data to invalidate doc-state pseudo-classes correctly.
+ pub invalidation_data: InvalidationMatchingData,
+
+ /// The invalidation bits from matching container queries. These are here
+ /// just for convenience mostly.
+ pub cascade_input_flags: ComputedValueFlags,
+
+ /// The style of the originating element in order to evaluate @container
+ /// size queries affecting pseudo-elements.
+ pub originating_element_style: Option<&'a ComputedValues>,
+}
+
+impl ::selectors::SelectorImpl for SelectorImpl {
+ type ExtraMatchingData<'a> = ExtraMatchingData<'a>;
+ type AttrValue = AtomString;
+ type Identifier = AtomIdent;
+ type LocalName = AtomIdent;
+ type NamespacePrefix = AtomIdent;
+ type NamespaceUrl = Namespace;
+ type BorrowedNamespaceUrl = WeakNamespace;
+ type BorrowedLocalName = WeakAtom;
+
+ type PseudoElement = PseudoElement;
+ type NonTSPseudoClass = NonTSPseudoClass;
+
+ fn should_collect_attr_hash(name: &AtomIdent) -> bool {
+ !crate::bloom::is_attr_name_excluded_from_filter(name)
+ }
+}
+
+impl<'a> SelectorParser<'a> {
+ fn is_pseudo_class_enabled(&self, pseudo_class: &NonTSPseudoClass) -> bool {
+ if pseudo_class.is_enabled_in_content() {
+ return true;
+ }
+
+ if self.in_user_agent_stylesheet() &&
+ pseudo_class.has_any_flag(NonTSPseudoClassFlag::PSEUDO_CLASS_ENABLED_IN_UA_SHEETS)
+ {
+ return true;
+ }
+
+ if self.chrome_rules_enabled() &&
+ pseudo_class.has_any_flag(NonTSPseudoClassFlag::PSEUDO_CLASS_ENABLED_IN_CHROME)
+ {
+ return true;
+ }
+
+ if matches!(*pseudo_class, NonTSPseudoClass::MozBroken) {
+ return static_prefs::pref!("layout.css.moz-broken.content.enabled");
+ }
+
+ return false;
+ }
+
+ fn is_pseudo_element_enabled(&self, pseudo_element: &PseudoElement) -> bool {
+ if pseudo_element.enabled_in_content() {
+ return true;
+ }
+
+ if self.in_user_agent_stylesheet() && pseudo_element.enabled_in_ua_sheets() {
+ return true;
+ }
+
+ if self.chrome_rules_enabled() && pseudo_element.enabled_in_chrome() {
+ return true;
+ }
+
+ return false;
+ }
+}
+
+impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> {
+ type Impl = SelectorImpl;
+ type Error = StyleParseErrorKind<'i>;
+
+ #[inline]
+ fn parse_parent_selector(&self) -> bool {
+ true
+ }
+
+ #[inline]
+ fn parse_slotted(&self) -> bool {
+ true
+ }
+
+ #[inline]
+ fn parse_host(&self) -> bool {
+ true
+ }
+
+ #[inline]
+ fn parse_nth_child_of(&self) -> bool {
+ true
+ }
+
+ #[inline]
+ fn parse_is_and_where(&self) -> bool {
+ true
+ }
+
+ #[inline]
+ fn parse_has(&self) -> bool {
+ static_prefs::pref!("layout.css.has-selector.enabled")
+ }
+
+ #[inline]
+ fn parse_part(&self) -> bool {
+ true
+ }
+
+ #[inline]
+ fn is_is_alias(&self, function: &str) -> bool {
+ function.eq_ignore_ascii_case("-moz-any")
+ }
+
+ #[inline]
+ fn allow_forgiving_selectors(&self) -> bool {
+ !self.for_supports_rule
+ }
+
+ fn parse_non_ts_pseudo_class(
+ &self,
+ location: SourceLocation,
+ name: CowRcStr<'i>,
+ ) -> Result<NonTSPseudoClass, ParseError<'i>> {
+ if let Some(pseudo_class) = NonTSPseudoClass::parse_non_functional(&name) {
+ if self.is_pseudo_class_enabled(&pseudo_class) {
+ return Ok(pseudo_class);
+ }
+ }
+ Err(
+ location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement(
+ name,
+ )),
+ )
+ }
+
+ fn parse_non_ts_functional_pseudo_class<'t>(
+ &self,
+ name: CowRcStr<'i>,
+ parser: &mut Parser<'i, 't>,
+ after_part: bool,
+ ) -> Result<NonTSPseudoClass, ParseError<'i>> {
+ let pseudo_class = match_ignore_ascii_case! { &name,
+ "lang" if !after_part => {
+ let result = parser.parse_comma_separated(|input| {
+ Ok(AtomIdent::from(input.expect_ident_or_string()?.as_ref()))
+ })?;
+ if result.is_empty() {
+ return Err(parser.new_custom_error(StyleParseErrorKind::UnspecifiedError));
+ }
+ NonTSPseudoClass::Lang(Lang(result.into()))
+ },
+ "state" => {
+ let result = AtomIdent::from(parser.expect_ident()?.as_ref());
+ NonTSPseudoClass::CustomState(CustomState(result))
+ },
+ "-moz-locale-dir" if !after_part => {
+ NonTSPseudoClass::MozLocaleDir(Direction::parse(parser)?)
+ },
+ "dir" if !after_part => {
+ NonTSPseudoClass::Dir(Direction::parse(parser)?)
+ },
+ _ => return Err(parser.new_custom_error(
+ SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name.clone())
+ ))
+ };
+ if self.is_pseudo_class_enabled(&pseudo_class) {
+ Ok(pseudo_class)
+ } else {
+ Err(
+ parser.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement(
+ name,
+ )),
+ )
+ }
+ }
+
+ fn parse_pseudo_element(
+ &self,
+ location: SourceLocation,
+ name: CowRcStr<'i>,
+ ) -> Result<PseudoElement, ParseError<'i>> {
+ let allow_unkown_webkit = !self.for_supports_rule;
+ if let Some(pseudo) = PseudoElement::from_slice(&name, allow_unkown_webkit) {
+ if self.is_pseudo_element_enabled(&pseudo) {
+ return Ok(pseudo);
+ }
+ }
+
+ Err(
+ location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement(
+ name,
+ )),
+ )
+ }
+
+ fn parse_functional_pseudo_element<'t>(
+ &self,
+ name: CowRcStr<'i>,
+ parser: &mut Parser<'i, 't>,
+ ) -> Result<PseudoElement, ParseError<'i>> {
+ if starts_with_ignore_ascii_case(&name, "-moz-tree-") {
+ // Tree pseudo-elements can have zero or more arguments, separated
+ // by either comma or space.
+ let mut args = ThinVec::new();
+ loop {
+ let location = parser.current_source_location();
+ match parser.next() {
+ Ok(&Token::Ident(ref ident)) => args.push(Atom::from(ident.as_ref())),
+ Ok(&Token::Comma) => {},
+ Ok(t) => return Err(location.new_unexpected_token_error(t.clone())),
+ Err(BasicParseError {
+ kind: BasicParseErrorKind::EndOfInput,
+ ..
+ }) => break,
+ _ => unreachable!("Parser::next() shouldn't return any other error"),
+ }
+ }
+ if let Some(pseudo) = PseudoElement::tree_pseudo_element(&name, args) {
+ if self.is_pseudo_element_enabled(&pseudo) {
+ return Ok(pseudo);
+ }
+ }
+ } else if name.eq_ignore_ascii_case("highlight") {
+ let pseudo = PseudoElement::Highlight(AtomIdent::from(parser.expect_ident()?.as_ref()));
+ if self.is_pseudo_element_enabled(&pseudo) {
+ return Ok(pseudo);
+ }
+ }
+ Err(
+ parser.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement(
+ name,
+ )),
+ )
+ }
+
+ fn default_namespace(&self) -> Option<Namespace> {
+ self.namespaces.default.clone()
+ }
+
+ fn namespace_for_prefix(&self, prefix: &AtomIdent) -> Option<Namespace> {
+ self.namespaces.prefixes.get(prefix).cloned()
+ }
+}
+
+impl SelectorImpl {
+ /// A helper to traverse each eagerly cascaded pseudo-element, executing
+ /// `fun` on it.
+ #[inline]
+ pub fn each_eagerly_cascaded_pseudo_element<F>(mut fun: F)
+ where
+ F: FnMut(PseudoElement),
+ {
+ for pseudo in &EAGER_PSEUDOS {
+ fun(pseudo.clone())
+ }
+ }
+}
+
+// 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);
diff --git a/servo/components/style/gecko/snapshot.rs b/servo/components/style/gecko/snapshot.rs
new file mode 100644
index 0000000000..2ff04406ac
--- /dev/null
+++ b/servo/components/style/gecko/snapshot.rs
@@ -0,0 +1,174 @@
+/* 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 gecko snapshot, that stores the element attributes and state before they
+//! change in order to properly calculate restyle hints.
+
+use crate::dom::TElement;
+use crate::gecko::snapshot_helpers;
+use crate::gecko::wrapper::GeckoElement;
+use crate::gecko_bindings::bindings;
+use crate::gecko_bindings::structs::ServoElementSnapshot;
+use crate::gecko_bindings::structs::ServoElementSnapshotFlags as Flags;
+use crate::gecko_bindings::structs::ServoElementSnapshotTable;
+use crate::invalidation::element::element_wrapper::ElementSnapshot;
+use crate::selector_parser::AttrValue;
+use crate::string_cache::{Atom, Namespace};
+use crate::values::{AtomIdent, AtomString};
+use crate::LocalName;
+use crate::WeakAtom;
+use dom::ElementState;
+use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint};
+
+/// A snapshot of a Gecko element.
+pub type GeckoElementSnapshot = ServoElementSnapshot;
+
+/// A map from elements to snapshots for Gecko's style back-end.
+pub type SnapshotMap = ServoElementSnapshotTable;
+
+impl SnapshotMap {
+ /// Gets the snapshot for this element, if any.
+ ///
+ /// FIXME(emilio): The transmute() business we do here is kind of nasty, but
+ /// it's a consequence of the map being a OpaqueNode -> Snapshot table in
+ /// Servo and an Element -> Snapshot table in Gecko.
+ ///
+ /// We should be able to make this a more type-safe with type annotations by
+ /// making SnapshotMap a trait and moving the implementations outside, but
+ /// that's a pain because it implies parameterizing SharedStyleContext.
+ pub fn get<E: TElement>(&self, element: &E) -> Option<&GeckoElementSnapshot> {
+ debug_assert!(element.has_snapshot());
+
+ unsafe {
+ let element = ::std::mem::transmute::<&E, &GeckoElement>(element);
+ bindings::Gecko_GetElementSnapshot(self, element.0).as_ref()
+ }
+ }
+}
+
+impl GeckoElementSnapshot {
+ #[inline]
+ fn has_any(&self, flags: Flags) -> bool {
+ (self.mContains as u8 & flags as u8) != 0
+ }
+
+ /// Returns true if the snapshot has stored state for pseudo-classes
+ /// that depend on things other than `ElementState`.
+ #[inline]
+ pub fn has_other_pseudo_class_state(&self) -> bool {
+ self.has_any(Flags::OtherPseudoClassState)
+ }
+
+ /// Returns true if the snapshot recorded an id change.
+ #[inline]
+ pub fn id_changed(&self) -> bool {
+ self.mIdAttributeChanged()
+ }
+
+ /// Returns true if the snapshot recorded a class attribute change.
+ #[inline]
+ pub fn class_changed(&self) -> bool {
+ self.mClassAttributeChanged()
+ }
+
+ /// Executes the callback once for each attribute that changed.
+ #[inline]
+ pub fn each_attr_changed<F>(&self, mut callback: F)
+ where
+ F: FnMut(&AtomIdent),
+ {
+ for attr in self.mChangedAttrNames.iter() {
+ unsafe { AtomIdent::with(attr.mRawPtr, &mut callback) }
+ }
+ }
+
+ /// selectors::Element::attr_matches
+ pub fn attr_matches(
+ &self,
+ ns: &NamespaceConstraint<&Namespace>,
+ local_name: &LocalName,
+ operation: &AttrSelectorOperation<&AttrValue>,
+ ) -> bool {
+ snapshot_helpers::attr_matches(&self.mAttrs, ns, local_name, operation)
+ }
+}
+
+impl ElementSnapshot for GeckoElementSnapshot {
+ fn debug_list_attributes(&self) -> String {
+ use nsstring::nsCString;
+ let mut string = nsCString::new();
+ unsafe {
+ bindings::Gecko_Snapshot_DebugListAttributes(self, &mut string);
+ }
+ String::from_utf8_lossy(&*string).into_owned()
+ }
+
+ fn state(&self) -> Option<ElementState> {
+ if self.has_any(Flags::State) {
+ Some(ElementState::from_bits_retain(self.mState))
+ } else {
+ None
+ }
+ }
+
+ #[inline]
+ fn has_attrs(&self) -> bool {
+ self.has_any(Flags::Attributes)
+ }
+
+ #[inline]
+ fn id_attr(&self) -> Option<&WeakAtom> {
+ if !self.has_any(Flags::Id) {
+ return None;
+ }
+
+ snapshot_helpers::get_id(&*self.mAttrs)
+ }
+
+ #[inline]
+ fn is_part(&self, name: &AtomIdent) -> bool {
+ let attr = match snapshot_helpers::find_attr(&*self.mAttrs, &atom!("part")) {
+ Some(attr) => attr,
+ None => return false,
+ };
+
+ snapshot_helpers::has_class_or_part(name, CaseSensitivity::CaseSensitive, attr)
+ }
+
+ #[inline]
+ fn imported_part(&self, name: &AtomIdent) -> Option<AtomIdent> {
+ snapshot_helpers::imported_part(&*self.mAttrs, name)
+ }
+
+ #[inline]
+ fn has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool {
+ if !self.has_any(Flags::MaybeClass) {
+ return false;
+ }
+
+ snapshot_helpers::has_class_or_part(name, case_sensitivity, &self.mClass)
+ }
+
+ #[inline]
+ fn each_class<F>(&self, callback: F)
+ where
+ F: FnMut(&AtomIdent),
+ {
+ if !self.has_any(Flags::MaybeClass) {
+ return;
+ }
+
+ snapshot_helpers::each_class_or_part(&self.mClass, callback)
+ }
+
+ #[inline]
+ fn lang_attr(&self) -> Option<AtomString> {
+ let ptr = unsafe { bindings::Gecko_SnapshotLangValue(self) };
+ if ptr.is_null() {
+ None
+ } else {
+ Some(AtomString(unsafe { Atom::from_addrefed(ptr) }))
+ }
+ }
+}
diff --git a/servo/components/style/gecko/snapshot_helpers.rs b/servo/components/style/gecko/snapshot_helpers.rs
new file mode 100644
index 0000000000..ab2d08eaf8
--- /dev/null
+++ b/servo/components/style/gecko/snapshot_helpers.rs
@@ -0,0 +1,316 @@
+/* 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/. */
+
+//! Element an snapshot common logic.
+
+use crate::dom::TElement;
+use crate::gecko::wrapper::namespace_id_to_atom;
+use crate::gecko_bindings::bindings;
+use crate::gecko_bindings::structs::{self, nsAtom};
+use crate::invalidation::element::element_wrapper::ElementSnapshot;
+use crate::selector_parser::{AttrValue, SnapshotMap};
+use crate::string_cache::WeakAtom;
+use crate::values::AtomIdent;
+use crate::{Atom, CaseSensitivityExt, LocalName, Namespace};
+use selectors::attr::{
+ AttrSelectorOperation, AttrSelectorOperator, CaseSensitivity, NamespaceConstraint,
+};
+use smallvec::SmallVec;
+
+/// A function that, given an element of type `T`, allows you to get a single
+/// class or a class list.
+enum Class<'a> {
+ None,
+ One(*const nsAtom),
+ More(&'a [structs::RefPtr<nsAtom>]),
+}
+
+#[inline(always)]
+fn base_type(attr: &structs::nsAttrValue) -> structs::nsAttrValue_ValueBaseType {
+ (attr.mBits & structs::NS_ATTRVALUE_BASETYPE_MASK) as structs::nsAttrValue_ValueBaseType
+}
+
+#[inline(always)]
+unsafe fn ptr<T>(attr: &structs::nsAttrValue) -> *const T {
+ (attr.mBits & !structs::NS_ATTRVALUE_BASETYPE_MASK) as *const T
+}
+
+#[inline(always)]
+unsafe fn get_class_or_part_from_attr(attr: &structs::nsAttrValue) -> Class {
+ debug_assert!(bindings::Gecko_AssertClassAttrValueIsSane(attr));
+ let base_type = base_type(attr);
+ if base_type == structs::nsAttrValue_ValueBaseType_eAtomBase {
+ return Class::One(ptr::<nsAtom>(attr));
+ }
+ if base_type == structs::nsAttrValue_ValueBaseType_eOtherBase {
+ let container = ptr::<structs::MiscContainer>(attr);
+ debug_assert_eq!(
+ (*container).mType,
+ structs::nsAttrValue_ValueType_eAtomArray
+ );
+ // NOTE: Bindgen doesn't deal with AutoTArray, so cast it below.
+ let attr_array: *const _ = *(*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>>;
+ return Class::More(&**array);
+ }
+ debug_assert_eq!(base_type, structs::nsAttrValue_ValueBaseType_eStringBase);
+ Class::None
+}
+
+#[inline(always)]
+unsafe fn get_id_from_attr(attr: &structs::nsAttrValue) -> &WeakAtom {
+ debug_assert_eq!(
+ base_type(attr),
+ structs::nsAttrValue_ValueBaseType_eAtomBase
+ );
+ WeakAtom::new(ptr::<nsAtom>(attr))
+}
+
+impl structs::nsAttrName {
+ #[inline]
+ fn is_nodeinfo(&self) -> bool {
+ self.mBits & 1 != 0
+ }
+
+ #[inline]
+ unsafe fn as_nodeinfo(&self) -> &structs::NodeInfo {
+ debug_assert!(self.is_nodeinfo());
+ &*((self.mBits & !1) as *const structs::NodeInfo)
+ }
+
+ #[inline]
+ fn namespace_id(&self) -> i32 {
+ if !self.is_nodeinfo() {
+ return structs::kNameSpaceID_None;
+ }
+ unsafe { self.as_nodeinfo() }.mInner.mNamespaceID
+ }
+
+ /// Returns the attribute name as an atom pointer.
+ #[inline]
+ pub fn name(&self) -> *const nsAtom {
+ if self.is_nodeinfo() {
+ unsafe { self.as_nodeinfo() }.mInner.mName
+ } else {
+ self.mBits as *const nsAtom
+ }
+ }
+}
+
+/// Find an attribute value with a given name and no namespace.
+#[inline(always)]
+pub fn find_attr<'a>(
+ attrs: &'a [structs::AttrArray_InternalAttr],
+ name: &Atom,
+) -> Option<&'a structs::nsAttrValue> {
+ attrs
+ .iter()
+ .find(|attr| attr.mName.mBits == name.as_ptr() as usize)
+ .map(|attr| &attr.mValue)
+}
+
+/// Finds the id attribute from a list of attributes.
+#[inline(always)]
+pub fn get_id(attrs: &[structs::AttrArray_InternalAttr]) -> Option<&WeakAtom> {
+ Some(unsafe { get_id_from_attr(find_attr(attrs, &atom!("id"))?) })
+}
+
+#[inline(always)]
+pub(super) fn each_exported_part(
+ attrs: &[structs::AttrArray_InternalAttr],
+ name: &AtomIdent,
+ mut callback: impl FnMut(&AtomIdent),
+) {
+ let attr = match find_attr(attrs, &atom!("exportparts")) {
+ Some(attr) => attr,
+ None => return,
+ };
+ let mut length = 0;
+ let atoms = unsafe { bindings::Gecko_Element_ExportedParts(attr, name.as_ptr(), &mut length) };
+ if atoms.is_null() {
+ return;
+ }
+
+ unsafe {
+ for atom in std::slice::from_raw_parts(atoms, length) {
+ AtomIdent::with(*atom, &mut callback)
+ }
+ }
+}
+
+#[inline(always)]
+pub(super) fn imported_part(
+ attrs: &[structs::AttrArray_InternalAttr],
+ name: &AtomIdent,
+) -> Option<AtomIdent> {
+ let attr = find_attr(attrs, &atom!("exportparts"))?;
+ let atom = unsafe { bindings::Gecko_Element_ImportedPart(attr, name.as_ptr()) };
+ if atom.is_null() {
+ return None;
+ }
+ Some(AtomIdent(unsafe { Atom::from_raw(atom) }))
+}
+
+/// Given a class or part name, a case sensitivity, and an array of attributes,
+/// returns whether the attribute has that name.
+#[inline(always)]
+pub fn has_class_or_part(
+ name: &AtomIdent,
+ case_sensitivity: CaseSensitivity,
+ attr: &structs::nsAttrValue,
+) -> bool {
+ match unsafe { get_class_or_part_from_attr(attr) } {
+ Class::None => false,
+ Class::One(atom) => unsafe { case_sensitivity.eq_atom(name, WeakAtom::new(atom)) },
+ Class::More(atoms) => match case_sensitivity {
+ CaseSensitivity::CaseSensitive => {
+ let name_ptr = name.as_ptr();
+ atoms.iter().any(|atom| atom.mRawPtr == name_ptr)
+ },
+ CaseSensitivity::AsciiCaseInsensitive => unsafe {
+ atoms
+ .iter()
+ .any(|atom| WeakAtom::new(atom.mRawPtr).eq_ignore_ascii_case(name))
+ },
+ },
+ }
+}
+
+/// Given an item, a callback, and a getter, execute `callback` for each class
+/// or part name this `item` has.
+#[inline(always)]
+pub fn each_class_or_part<F>(attr: &structs::nsAttrValue, mut callback: F)
+where
+ F: FnMut(&AtomIdent),
+{
+ unsafe {
+ match get_class_or_part_from_attr(attr) {
+ Class::None => {},
+ Class::One(atom) => AtomIdent::with(atom, callback),
+ Class::More(atoms) => {
+ for atom in atoms {
+ AtomIdent::with(atom.mRawPtr, &mut callback)
+ }
+ },
+ }
+ }
+}
+
+/// Returns a list of classes that were either added to or removed from the
+/// element since the snapshot.
+pub fn classes_changed<E: TElement>(element: &E, snapshots: &SnapshotMap) -> SmallVec<[Atom; 8]> {
+ debug_assert!(element.has_snapshot(), "Why bothering?");
+ let snapshot = snapshots.get(element).expect("has_snapshot lied");
+ if !snapshot.class_changed() {
+ return SmallVec::new();
+ }
+
+ let mut classes_changed = SmallVec::<[Atom; 8]>::new();
+ snapshot.each_class(|c| {
+ if !element.has_class(c, CaseSensitivity::CaseSensitive) {
+ classes_changed.push(c.0.clone());
+ }
+ });
+ element.each_class(|c| {
+ if !snapshot.has_class(c, CaseSensitivity::CaseSensitive) {
+ classes_changed.push(c.0.clone());
+ }
+ });
+
+ classes_changed
+}
+
+/// Returns whether a given attribute selector matches given the internal attrs.
+#[inline(always)]
+pub(crate) fn attr_matches(
+ attrs: &[structs::AttrArray_InternalAttr],
+ ns: &NamespaceConstraint<&Namespace>,
+ local_name: &LocalName,
+ operation: &AttrSelectorOperation<&AttrValue>,
+) -> bool {
+ let name_ptr = local_name.as_ptr();
+ for attr in attrs {
+ if attr.mName.name() != name_ptr {
+ continue;
+ }
+
+ if attr_matches_checked_name(attr, ns, operation) {
+ return true;
+ }
+
+ // The name matched but the value or namespace didn't. The only reason to check the other
+ // attributes now would be to find one with the same name but a different namespace.
+ if *ns != NamespaceConstraint::Any {
+ // We don't want to look for other namespaces, so we're done.
+ return false;
+ }
+ }
+ false
+}
+
+/// Returns whether a given attribute selector matches given a single attribute,
+/// for the case where the caller has already found an attribute with the right name.
+fn attr_matches_checked_name(
+ attr: &structs::AttrArray_InternalAttr,
+ ns: &NamespaceConstraint<&Namespace>,
+ operation: &AttrSelectorOperation<&AttrValue>,
+) -> bool {
+ let ns_matches = match *ns {
+ NamespaceConstraint::Any => true,
+ NamespaceConstraint::Specific(ns) => {
+ if *ns == ns!() {
+ !attr.mName.is_nodeinfo()
+ } else {
+ ns.as_ptr() == unsafe { namespace_id_to_atom(attr.mName.namespace_id()) }
+ }
+ },
+ };
+
+ if !ns_matches {
+ return false;
+ }
+
+ let (operator, case_sensitivity, value) = match *operation {
+ AttrSelectorOperation::Exists => return true,
+ AttrSelectorOperation::WithValue {
+ operator,
+ case_sensitivity,
+ value,
+ } => (operator, case_sensitivity, value),
+ };
+ let ignore_case = match case_sensitivity {
+ CaseSensitivity::CaseSensitive => false,
+ CaseSensitivity::AsciiCaseInsensitive => true,
+ };
+ let value = value.as_ptr();
+ unsafe {
+ match operator {
+ AttrSelectorOperator::Equal => {
+ bindings::Gecko_AttrEquals(&attr.mValue, value, ignore_case)
+ },
+ AttrSelectorOperator::Includes => {
+ bindings::Gecko_AttrIncludes(&attr.mValue, value, ignore_case)
+ },
+ AttrSelectorOperator::DashMatch => {
+ bindings::Gecko_AttrDashEquals(&attr.mValue, value, ignore_case)
+ },
+ AttrSelectorOperator::Prefix => {
+ bindings::Gecko_AttrHasPrefix(&attr.mValue, value, ignore_case)
+ },
+ AttrSelectorOperator::Suffix => {
+ bindings::Gecko_AttrHasSuffix(&attr.mValue, value, ignore_case)
+ },
+ AttrSelectorOperator::Substring => {
+ bindings::Gecko_AttrHasSubstring(&attr.mValue, value, ignore_case)
+ },
+ }
+ }
+}
diff --git a/servo/components/style/gecko/traversal.rs b/servo/components/style/gecko/traversal.rs
new file mode 100644
index 0000000000..71d1a2f949
--- /dev/null
+++ b/servo/components/style/gecko/traversal.rs
@@ -0,0 +1,53 @@
+/* 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/. */
+
+//! Gecko-specific bits for the styling DOM traversal.
+
+use crate::context::{SharedStyleContext, StyleContext};
+use crate::dom::{TElement, TNode};
+use crate::gecko::wrapper::{GeckoElement, GeckoNode};
+use crate::traversal::{recalc_style_at, DomTraversal, PerLevelTraversalData};
+
+/// This is the simple struct that Gecko uses to encapsulate a DOM traversal for
+/// styling.
+pub struct RecalcStyleOnly<'a> {
+ shared: SharedStyleContext<'a>,
+}
+
+impl<'a> RecalcStyleOnly<'a> {
+ /// Create a `RecalcStyleOnly` traversal from a `SharedStyleContext`.
+ pub fn new(shared: SharedStyleContext<'a>) -> Self {
+ RecalcStyleOnly { shared: shared }
+ }
+}
+
+impl<'recalc, 'le> DomTraversal<GeckoElement<'le>> for RecalcStyleOnly<'recalc> {
+ fn process_preorder<F>(
+ &self,
+ traversal_data: &PerLevelTraversalData,
+ context: &mut StyleContext<GeckoElement<'le>>,
+ node: GeckoNode<'le>,
+ note_child: F,
+ ) where
+ F: FnMut(GeckoNode<'le>),
+ {
+ if let Some(el) = node.as_element() {
+ let mut data = unsafe { el.ensure_data() };
+ recalc_style_at(self, traversal_data, context, el, &mut data, note_child);
+ }
+ }
+
+ fn process_postorder(&self, _: &mut StyleContext<GeckoElement<'le>>, _: GeckoNode<'le>) {
+ unreachable!();
+ }
+
+ /// We don't use the post-order traversal for anything.
+ fn needs_postorder_traversal() -> bool {
+ false
+ }
+
+ fn shared_context(&self) -> &SharedStyleContext {
+ &self.shared
+ }
+}
diff --git a/servo/components/style/gecko/url.rs b/servo/components/style/gecko/url.rs
new file mode 100644
index 0000000000..7fe32acc20
--- /dev/null
+++ b/servo/components/style/gecko/url.rs
@@ -0,0 +1,384 @@
+/* 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 handling for the specified value CSS url() values.
+
+use crate::gecko_bindings::bindings;
+use crate::gecko_bindings::structs;
+use crate::parser::{Parse, ParserContext};
+use crate::stylesheets::{CorsMode, UrlExtraData};
+use crate::values::computed::{Context, ToComputedValue};
+use cssparser::Parser;
+use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
+use nsstring::nsCString;
+use servo_arc::Arc;
+use std::collections::HashMap;
+use std::fmt::{self, Write};
+use std::mem::ManuallyDrop;
+use std::sync::RwLock;
+use style_traits::{CssWriter, ParseError, ToCss};
+use to_shmem::{self, SharedMemoryBuilder, ToShmem};
+
+/// A CSS url() value for gecko.
+#[derive(Clone, Debug, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
+#[css(function = "url")]
+#[repr(C)]
+pub struct CssUrl(pub Arc<CssUrlData>);
+
+/// Data shared between CssUrls.
+///
+/// cbindgen:derive-eq=false
+/// cbindgen:derive-neq=false
+#[derive(Debug, SpecifiedValueInfo, ToCss, ToShmem)]
+#[repr(C)]
+pub struct CssUrlData {
+ /// The URL in unresolved string form.
+ serialization: crate::OwnedStr,
+
+ /// The URL extra data.
+ #[css(skip)]
+ pub extra_data: UrlExtraData,
+
+ /// The CORS mode that will be used for the load.
+ #[css(skip)]
+ cors_mode: CorsMode,
+
+ /// Data to trigger a load from Gecko. This is mutable in C++.
+ ///
+ /// TODO(emilio): Maybe we can eagerly resolve URLs and make this immutable?
+ #[css(skip)]
+ load_data: LoadDataSource,
+}
+
+impl PartialEq for CssUrlData {
+ fn eq(&self, other: &Self) -> bool {
+ self.serialization == other.serialization &&
+ self.extra_data == other.extra_data &&
+ self.cors_mode == other.cors_mode
+ }
+}
+
+impl CssUrl {
+ fn parse_with_cors_mode<'i, 't>(
+ context: &ParserContext,
+ input: &mut Parser<'i, 't>,
+ cors_mode: CorsMode,
+ ) -> Result<Self, ParseError<'i>> {
+ let url = input.expect_url()?;
+ Ok(Self::parse_from_string(
+ url.as_ref().to_owned(),
+ context,
+ cors_mode,
+ ))
+ }
+
+ /// Parse a URL from a string value that is a valid CSS token for a URL.
+ pub fn parse_from_string(url: String, context: &ParserContext, cors_mode: CorsMode) -> Self {
+ CssUrl(Arc::new(CssUrlData {
+ serialization: url.into(),
+ extra_data: context.url_data.clone(),
+ cors_mode,
+ load_data: LoadDataSource::Owned(LoadData::default()),
+ }))
+ }
+
+ /// Returns true if the URL is definitely invalid. We don't eagerly resolve
+ /// URLs in gecko, so we just return false here.
+ /// use its |resolved| status.
+ pub fn is_invalid(&self) -> bool {
+ false
+ }
+
+ /// Returns true if this URL looks like a fragment.
+ /// See https://drafts.csswg.org/css-values/#local-urls
+ #[inline]
+ pub fn is_fragment(&self) -> bool {
+ self.0.is_fragment()
+ }
+
+ /// Return the unresolved url as string, or the empty string if it's
+ /// invalid.
+ #[inline]
+ pub fn as_str(&self) -> &str {
+ self.0.as_str()
+ }
+}
+
+impl CssUrlData {
+ /// Returns true if this URL looks like a fragment.
+ /// See https://drafts.csswg.org/css-values/#local-urls
+ pub fn is_fragment(&self) -> bool {
+ self.as_str()
+ .as_bytes()
+ .iter()
+ .next()
+ .map_or(false, |b| *b == b'#')
+ }
+
+ /// Return the unresolved url as string, or the empty string if it's
+ /// invalid.
+ pub fn as_str(&self) -> &str {
+ &*self.serialization
+ }
+}
+
+impl Parse for CssUrl {
+ fn parse<'i, 't>(
+ context: &ParserContext,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<Self, ParseError<'i>> {
+ Self::parse_with_cors_mode(context, input, CorsMode::None)
+ }
+}
+
+impl Eq for CssUrl {}
+
+impl MallocSizeOf for CssUrl {
+ fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
+ // XXX: measure `serialization` once bug 1397971 lands
+
+ // We ignore `extra_data`, because RefPtr is tricky, and there aren't
+ // many of them in practise (sharing is common).
+
+ 0
+ }
+}
+
+/// A key type for LOAD_DATA_TABLE.
+#[derive(Eq, Hash, PartialEq)]
+struct LoadDataKey(*const LoadDataSource);
+
+unsafe impl Sync for LoadDataKey {}
+unsafe impl Send for LoadDataKey {}
+
+bitflags! {
+ /// Various bits of mutable state that are kept for image loads.
+ #[derive(Debug)]
+ #[repr(C)]
+ pub struct LoadDataFlags: u8 {
+ /// Whether we tried to resolve the uri at least once.
+ const TRIED_TO_RESOLVE_URI = 1 << 0;
+ /// Whether we tried to resolve the image at least once.
+ const TRIED_TO_RESOLVE_IMAGE = 1 << 1;
+ }
+}
+
+/// This is usable and movable from multiple threads just fine, as long as it's
+/// not cloned (it is not clonable), and the methods that mutate it run only on
+/// the main thread (when all the other threads we care about are paused).
+unsafe impl Sync for LoadData {}
+unsafe impl Send for LoadData {}
+
+/// The load data for a given URL. This is mutable from C++, and shouldn't be
+/// accessed from rust for anything.
+#[repr(C)]
+#[derive(Debug)]
+pub struct LoadData {
+ /// A strong reference to the imgRequestProxy, if any, that should be
+ /// released on drop.
+ ///
+ /// These are raw pointers because they are not safe to reference-count off
+ /// the main thread.
+ resolved_image: *mut structs::imgRequestProxy,
+ /// A strong reference to the resolved URI of this image.
+ resolved_uri: *mut structs::nsIURI,
+ /// A few flags that are set when resolving the image or such.
+ flags: LoadDataFlags,
+}
+
+impl Drop for LoadData {
+ fn drop(&mut self) {
+ unsafe { bindings::Gecko_LoadData_Drop(self) }
+ }
+}
+
+impl Default for LoadData {
+ fn default() -> Self {
+ Self {
+ resolved_image: std::ptr::null_mut(),
+ resolved_uri: std::ptr::null_mut(),
+ flags: LoadDataFlags::empty(),
+ }
+ }
+}
+
+/// The data for a load, or a lazy-loaded, static member that will be stored in
+/// LOAD_DATA_TABLE, keyed by the memory location of this object, which is
+/// always in the heap because it's inside the CssUrlData object.
+///
+/// This type is meant not to be used from C++ so we don't derive helper
+/// methods.
+///
+/// cbindgen:derive-helper-methods=false
+#[derive(Debug)]
+#[repr(u8, C)]
+pub enum LoadDataSource {
+ /// An owned copy of the load data.
+ Owned(LoadData),
+ /// A lazily-resolved copy of it.
+ Lazy,
+}
+
+impl LoadDataSource {
+ /// Gets the load data associated with the source.
+ ///
+ /// This relies on the source on being in a stable location if lazy.
+ #[inline]
+ pub unsafe fn get(&self) -> *const LoadData {
+ match *self {
+ LoadDataSource::Owned(ref d) => return d,
+ LoadDataSource::Lazy => {},
+ }
+
+ let key = LoadDataKey(self);
+
+ {
+ let guard = LOAD_DATA_TABLE.read().unwrap();
+ if let Some(r) = guard.get(&key) {
+ return &**r;
+ }
+ }
+ let mut guard = LOAD_DATA_TABLE.write().unwrap();
+ let r = guard.entry(key).or_insert_with(Default::default);
+ &**r
+ }
+}
+
+impl ToShmem for LoadDataSource {
+ fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> {
+ Ok(ManuallyDrop::new(match self {
+ LoadDataSource::Owned(..) => LoadDataSource::Lazy,
+ LoadDataSource::Lazy => LoadDataSource::Lazy,
+ }))
+ }
+}
+
+/// A specified non-image `url()` value.
+pub type SpecifiedUrl = CssUrl;
+
+/// Clears LOAD_DATA_TABLE. Entries in this table, which are for specified URL
+/// values that come from shared memory style sheets, would otherwise persist
+/// until the end of the process and be reported as leaks.
+pub fn shutdown() {
+ LOAD_DATA_TABLE.write().unwrap().clear();
+}
+
+impl ToComputedValue for SpecifiedUrl {
+ type ComputedValue = ComputedUrl;
+
+ #[inline]
+ fn to_computed_value(&self, _: &Context) -> Self::ComputedValue {
+ ComputedUrl(self.clone())
+ }
+
+ #[inline]
+ fn from_computed_value(computed: &Self::ComputedValue) -> Self {
+ computed.0.clone()
+ }
+}
+
+/// A specified image `url()` value.
+#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
+pub struct SpecifiedImageUrl(pub SpecifiedUrl);
+
+impl SpecifiedImageUrl {
+ /// Parse a URL from a string value that is a valid CSS token for a URL.
+ pub fn parse_from_string(url: String, context: &ParserContext, cors_mode: CorsMode) -> Self {
+ SpecifiedImageUrl(SpecifiedUrl::parse_from_string(url, context, cors_mode))
+ }
+
+ /// Provides an alternate method for parsing that associates the URL
+ /// with anonymous CORS headers.
+ pub fn parse_with_cors_mode<'i, 't>(
+ context: &ParserContext,
+ input: &mut Parser<'i, 't>,
+ cors_mode: CorsMode,
+ ) -> Result<Self, ParseError<'i>> {
+ Ok(SpecifiedImageUrl(SpecifiedUrl::parse_with_cors_mode(
+ context, input, cors_mode,
+ )?))
+ }
+}
+
+impl Parse for SpecifiedImageUrl {
+ fn parse<'i, 't>(
+ context: &ParserContext,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<Self, ParseError<'i>> {
+ SpecifiedUrl::parse(context, input).map(SpecifiedImageUrl)
+ }
+}
+
+impl ToComputedValue for SpecifiedImageUrl {
+ type ComputedValue = ComputedImageUrl;
+
+ #[inline]
+ fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
+ ComputedImageUrl(self.0.to_computed_value(context))
+ }
+
+ #[inline]
+ fn from_computed_value(computed: &Self::ComputedValue) -> Self {
+ SpecifiedImageUrl(ToComputedValue::from_computed_value(&computed.0))
+ }
+}
+
+/// The computed value of a CSS non-image `url()`.
+///
+/// The only difference between specified and computed URLs is the
+/// serialization.
+#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq)]
+#[repr(C)]
+pub struct ComputedUrl(pub SpecifiedUrl);
+
+impl ComputedUrl {
+ fn serialize_with<W>(
+ &self,
+ function: unsafe extern "C" fn(*const Self, *mut nsCString),
+ dest: &mut CssWriter<W>,
+ ) -> fmt::Result
+ where
+ W: Write,
+ {
+ dest.write_str("url(")?;
+ unsafe {
+ let mut string = nsCString::new();
+ function(self, &mut string);
+ string.as_str_unchecked().to_css(dest)?;
+ }
+ dest.write_char(')')
+ }
+}
+
+impl ToCss for ComputedUrl {
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ self.serialize_with(bindings::Gecko_GetComputedURLSpec, dest)
+ }
+}
+
+/// The computed value of a CSS image `url()`.
+#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq)]
+#[repr(transparent)]
+pub struct ComputedImageUrl(pub ComputedUrl);
+
+impl ToCss for ComputedImageUrl {
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ self.0
+ .serialize_with(bindings::Gecko_GetComputedImageURLSpec, dest)
+ }
+}
+
+lazy_static! {
+ /// A table mapping CssUrlData objects to their lazily created LoadData
+ /// objects.
+ static ref LOAD_DATA_TABLE: RwLock<HashMap<LoadDataKey, Box<LoadData>>> = {
+ Default::default()
+ };
+}
diff --git a/servo/components/style/gecko/values.rs b/servo/components/style/gecko/values.rs
new file mode 100644
index 0000000000..d04c73c70f
--- /dev/null
+++ b/servo/components/style/gecko/values.rs
@@ -0,0 +1,77 @@
+/* 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/. */
+
+#![allow(unsafe_code)]
+
+//! Different kind of helpers to interact with Gecko values.
+
+use crate::color::{AbsoluteColor, ColorSpace};
+use crate::counter_style::{Symbol, Symbols};
+use crate::gecko_bindings::bindings;
+use crate::gecko_bindings::structs::CounterStylePtr;
+use crate::values::generics::CounterStyle;
+use crate::values::Either;
+use crate::Atom;
+
+/// Convert a color value to `nscolor`.
+pub fn convert_absolute_color_to_nscolor(color: &AbsoluteColor) -> u32 {
+ let srgb = color.to_color_space(ColorSpace::Srgb);
+ u32::from_le_bytes([
+ (srgb.components.0 * 255.0).round() as u8,
+ (srgb.components.1 * 255.0).round() as u8,
+ (srgb.components.2 * 255.0).round() as u8,
+ (srgb.alpha * 255.0).round() as u8,
+ ])
+}
+
+/// Convert a given `nscolor` to a Servo AbsoluteColor value.
+pub fn convert_nscolor_to_absolute_color(color: u32) -> AbsoluteColor {
+ let [r, g, b, a] = color.to_le_bytes();
+ AbsoluteColor::srgb_legacy(r, g, b, a as f32 / 255.0)
+}
+
+#[test]
+fn convert_ns_color_to_absolute_color_should_be_in_legacy_syntax() {
+ use crate::color::ColorFlags;
+
+ let result = convert_nscolor_to_absolute_color(0x336699CC);
+ assert!(result.flags.contains(ColorFlags::IS_LEGACY_SRGB));
+
+ assert!(result.is_legacy_syntax());
+}
+
+impl CounterStyle {
+ /// Convert this counter style to a Gecko CounterStylePtr.
+ #[inline]
+ pub fn to_gecko_value(&self, gecko_value: &mut CounterStylePtr) {
+ unsafe { bindings::Gecko_CounterStyle_ToPtr(self, gecko_value) }
+ }
+
+ /// Convert Gecko CounterStylePtr to CounterStyle or String.
+ pub fn from_gecko_value(gecko_value: &CounterStylePtr) -> Either<Self, String> {
+ use crate::values::CustomIdent;
+
+ let name = unsafe { bindings::Gecko_CounterStyle_GetName(gecko_value) };
+ if !name.is_null() {
+ let name = unsafe { Atom::from_raw(name) };
+ debug_assert_ne!(name, atom!("none"));
+ Either::First(CounterStyle::Name(CustomIdent(name)))
+ } else {
+ let anonymous =
+ unsafe { bindings::Gecko_CounterStyle_GetAnonymous(gecko_value).as_ref() }.unwrap();
+ let symbols = &anonymous.mSymbols;
+ if anonymous.mSingleString {
+ debug_assert_eq!(symbols.len(), 1);
+ Either::Second(symbols[0].to_string())
+ } else {
+ let symbol_type = anonymous.mSymbolsType;
+ let symbols = symbols
+ .iter()
+ .map(|gecko_symbol| Symbol::String(gecko_symbol.to_string().into()))
+ .collect();
+ Either::First(CounterStyle::Symbols(symbol_type, Symbols(symbols)))
+ }
+ }
+ }
+}
diff --git a/servo/components/style/gecko/wrapper.rs b/servo/components/style/gecko/wrapper.rs
new file mode 100644
index 0000000000..61352ef9c0
--- /dev/null
+++ b/servo/components/style/gecko/wrapper.rs
@@ -0,0 +1,2211 @@
+/* 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/. */
+
+#![allow(unsafe_code)]
+
+//! Wrapper definitions on top of Gecko types in order to be used in the style
+//! system.
+//!
+//! This really follows the Servo pattern in
+//! `components/script/layout_wrapper.rs`.
+//!
+//! This theoretically should live in its own crate, but now it lives in the
+//! style system it's kind of pointless in the Stylo case, and only Servo forces
+//! the separation between the style system implementation and everything else.
+
+use crate::applicable_declarations::ApplicableDeclarationBlock;
+use crate::bloom::each_relevant_element_hash;
+use crate::context::{PostAnimationTasks, QuirksMode, SharedStyleContext, UpdateAnimationsTasks};
+use crate::data::ElementData;
+use crate::dom::{LayoutIterator, NodeInfo, OpaqueNode, TDocument, TElement, TNode, TShadowRoot};
+use crate::gecko::selector_parser::{CustomState, NonTSPseudoClass, PseudoElement, SelectorImpl};
+use crate::gecko::snapshot_helpers;
+use crate::gecko_bindings::bindings;
+use crate::gecko_bindings::bindings::Gecko_ElementHasAnimations;
+use crate::gecko_bindings::bindings::Gecko_ElementHasCSSAnimations;
+use crate::gecko_bindings::bindings::Gecko_ElementHasCSSTransitions;
+use crate::gecko_bindings::bindings::Gecko_ElementState;
+use crate::gecko_bindings::bindings::Gecko_GetActiveLinkAttrDeclarationBlock;
+use crate::gecko_bindings::bindings::Gecko_GetAnimationEffectCount;
+use crate::gecko_bindings::bindings::Gecko_GetAnimationRule;
+use crate::gecko_bindings::bindings::Gecko_GetExtraContentStyleDeclarations;
+use crate::gecko_bindings::bindings::Gecko_GetHTMLPresentationAttrDeclarationBlock;
+use crate::gecko_bindings::bindings::Gecko_GetStyleAttrDeclarationBlock;
+use crate::gecko_bindings::bindings::Gecko_GetUnvisitedLinkAttrDeclarationBlock;
+use crate::gecko_bindings::bindings::Gecko_GetVisitedLinkAttrDeclarationBlock;
+use crate::gecko_bindings::bindings::Gecko_IsSignificantChild;
+use crate::gecko_bindings::bindings::Gecko_MatchLang;
+use crate::gecko_bindings::bindings::Gecko_UnsetDirtyStyleAttr;
+use crate::gecko_bindings::bindings::Gecko_UpdateAnimations;
+use crate::gecko_bindings::structs;
+use crate::gecko_bindings::structs::nsChangeHint;
+use crate::gecko_bindings::structs::EffectCompositor_CascadeLevel as CascadeLevel;
+use crate::gecko_bindings::structs::ELEMENT_HANDLED_SNAPSHOT;
+use crate::gecko_bindings::structs::ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO;
+use crate::gecko_bindings::structs::ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO;
+use crate::gecko_bindings::structs::ELEMENT_HAS_SNAPSHOT;
+use crate::gecko_bindings::structs::NODE_DESCENDANTS_NEED_FRAMES;
+use crate::gecko_bindings::structs::NODE_NEEDS_FRAME;
+use crate::gecko_bindings::structs::{nsAtom, nsIContent, nsINode_BooleanFlag};
+use crate::gecko_bindings::structs::{nsINode as RawGeckoNode, Element as RawGeckoElement};
+use crate::global_style_data::GLOBAL_STYLE_DATA;
+use crate::invalidation::element::restyle_hints::RestyleHint;
+use crate::media_queries::Device;
+use crate::properties::{
+ animated_properties::{AnimationValue, AnimationValueMap},
+ ComputedValues, Importance, OwnedPropertyDeclarationId, PropertyDeclaration,
+ PropertyDeclarationBlock, PropertyDeclarationId, PropertyDeclarationIdSet,
+};
+use crate::rule_tree::CascadeLevel as ServoCascadeLevel;
+use crate::selector_parser::{AttrValue, Lang};
+use crate::shared_lock::{Locked, SharedRwLock};
+use crate::string_cache::{Atom, Namespace, WeakAtom, WeakNamespace};
+use crate::stylist::CascadeData;
+use crate::values::computed::Display;
+use crate::values::{AtomIdent, AtomString};
+use crate::CaseSensitivityExt;
+use crate::LocalName;
+use app_units::Au;
+use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
+use dom::{DocumentState, ElementState};
+use euclid::default::Size2D;
+use fxhash::FxHashMap;
+use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint};
+use selectors::bloom::{BloomFilter, BLOOM_HASH_MASK};
+use selectors::matching::VisitedHandlingMode;
+use selectors::matching::{ElementSelectorFlags, MatchingContext};
+use selectors::sink::Push;
+use selectors::{Element, OpaqueElement};
+use servo_arc::{Arc, ArcBorrow};
+use std::cell::Cell;
+use std::fmt;
+use std::hash::{Hash, Hasher};
+use std::mem;
+use std::ptr;
+use std::sync::atomic::{AtomicU32, Ordering};
+
+#[inline]
+fn elements_with_id<'a, 'le>(
+ array: *const structs::nsTArray<*mut RawGeckoElement>,
+) -> &'a [GeckoElement<'le>] {
+ unsafe {
+ if array.is_null() {
+ return &[];
+ }
+
+ let elements: &[*mut RawGeckoElement] = &**array;
+
+ // NOTE(emilio): We rely on the in-memory representation of
+ // GeckoElement<'ld> and *mut RawGeckoElement being the same.
+ #[allow(dead_code)]
+ unsafe fn static_assert() {
+ mem::transmute::<*mut RawGeckoElement, GeckoElement<'static>>(0xbadc0de as *mut _);
+ }
+
+ mem::transmute(elements)
+ }
+}
+
+/// A simple wrapper over `Document`.
+#[derive(Clone, Copy)]
+pub struct GeckoDocument<'ld>(pub &'ld structs::Document);
+
+impl<'ld> TDocument for GeckoDocument<'ld> {
+ type ConcreteNode = GeckoNode<'ld>;
+
+ #[inline]
+ fn as_node(&self) -> Self::ConcreteNode {
+ GeckoNode(&self.0._base)
+ }
+
+ #[inline]
+ fn is_html_document(&self) -> bool {
+ self.0.mType == structs::Document_Type::eHTML
+ }
+
+ #[inline]
+ fn quirks_mode(&self) -> QuirksMode {
+ self.0.mCompatMode.into()
+ }
+
+ #[inline]
+ fn elements_with_id<'a>(&self, id: &AtomIdent) -> Result<&'a [GeckoElement<'ld>], ()>
+ where
+ Self: 'a,
+ {
+ Ok(elements_with_id(unsafe {
+ bindings::Gecko_Document_GetElementsWithId(self.0, id.as_ptr())
+ }))
+ }
+
+ fn shared_lock(&self) -> &SharedRwLock {
+ &GLOBAL_STYLE_DATA.shared_lock
+ }
+}
+
+/// A simple wrapper over `ShadowRoot`.
+#[derive(Clone, Copy)]
+pub struct GeckoShadowRoot<'lr>(pub &'lr structs::ShadowRoot);
+
+impl<'ln> fmt::Debug for GeckoShadowRoot<'ln> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ // TODO(emilio): Maybe print the host or something?
+ write!(f, "<shadow-root> ({:#x})", self.as_node().opaque().0)
+ }
+}
+
+impl<'lr> PartialEq for GeckoShadowRoot<'lr> {
+ #[inline]
+ fn eq(&self, other: &Self) -> bool {
+ self.0 as *const _ == other.0 as *const _
+ }
+}
+
+impl<'lr> TShadowRoot for GeckoShadowRoot<'lr> {
+ type ConcreteNode = GeckoNode<'lr>;
+
+ #[inline]
+ fn as_node(&self) -> Self::ConcreteNode {
+ GeckoNode(&self.0._base._base._base._base)
+ }
+
+ #[inline]
+ fn host(&self) -> GeckoElement<'lr> {
+ GeckoElement(unsafe { &*self.0._base.mHost.mRawPtr })
+ }
+
+ #[inline]
+ fn style_data<'a>(&self) -> Option<&'a CascadeData>
+ where
+ Self: 'a,
+ {
+ let author_styles = unsafe { self.0.mServoStyles.mPtr.as_ref()? };
+ Some(&author_styles.data)
+ }
+
+ #[inline]
+ fn elements_with_id<'a>(&self, id: &AtomIdent) -> Result<&'a [GeckoElement<'lr>], ()>
+ where
+ Self: 'a,
+ {
+ Ok(elements_with_id(unsafe {
+ bindings::Gecko_ShadowRoot_GetElementsWithId(self.0, id.as_ptr())
+ }))
+ }
+
+ #[inline]
+ fn parts<'a>(&self) -> &[<Self::ConcreteNode as TNode>::ConcreteElement]
+ where
+ Self: 'a,
+ {
+ let slice: &[*const RawGeckoElement] = &*self.0.mParts;
+
+ #[allow(dead_code)]
+ unsafe fn static_assert() {
+ mem::transmute::<*const RawGeckoElement, GeckoElement<'static>>(0xbadc0de as *const _);
+ }
+
+ unsafe { mem::transmute(slice) }
+ }
+}
+
+/// A simple wrapper over a non-null Gecko node (`nsINode`) pointer.
+///
+/// Important: We don't currently refcount the DOM, because the wrapper lifetime
+/// magic guarantees that our LayoutFoo references won't outlive the root, and
+/// we don't mutate any of the references on the Gecko side during restyle.
+///
+/// We could implement refcounting if need be (at a potentially non-trivial
+/// performance cost) by implementing Drop and making LayoutFoo non-Copy.
+#[derive(Clone, Copy)]
+pub struct GeckoNode<'ln>(pub &'ln RawGeckoNode);
+
+impl<'ln> PartialEq for GeckoNode<'ln> {
+ #[inline]
+ fn eq(&self, other: &Self) -> bool {
+ self.0 as *const _ == other.0 as *const _
+ }
+}
+
+impl<'ln> fmt::Debug for GeckoNode<'ln> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ if let Some(el) = self.as_element() {
+ return el.fmt(f);
+ }
+
+ if self.is_text_node() {
+ return write!(f, "<text node> ({:#x})", self.opaque().0);
+ }
+
+ if self.is_document() {
+ return write!(f, "<document> ({:#x})", self.opaque().0);
+ }
+
+ if let Some(sr) = self.as_shadow_root() {
+ return sr.fmt(f);
+ }
+
+ write!(f, "<non-text node> ({:#x})", self.opaque().0)
+ }
+}
+
+impl<'ln> GeckoNode<'ln> {
+ #[inline]
+ fn is_document(&self) -> bool {
+ // This is a DOM constant that isn't going to change.
+ const DOCUMENT_NODE: u16 = 9;
+ self.node_info().mInner.mNodeType == DOCUMENT_NODE
+ }
+
+ #[inline]
+ fn is_shadow_root(&self) -> bool {
+ self.is_in_shadow_tree() && self.parent_node().is_none()
+ }
+
+ #[inline]
+ fn from_content(content: &'ln nsIContent) -> Self {
+ GeckoNode(&content._base)
+ }
+
+ #[inline]
+ fn set_flags(&self, flags: u32) {
+ self.flags_atomic().fetch_or(flags, Ordering::Relaxed);
+ }
+
+ fn flags_atomic_for(flags: &Cell<u32>) -> &AtomicU32 {
+ const_assert!(std::mem::size_of::<Cell<u32>>() == std::mem::size_of::<AtomicU32>());
+ const_assert!(std::mem::align_of::<Cell<u32>>() == std::mem::align_of::<AtomicU32>());
+
+ // Rust doesn't provide standalone atomic functions like GCC/clang do
+ // (via the atomic intrinsics) or via std::atomic_ref, but it guarantees
+ // that the memory representation of u32 and AtomicU32 matches:
+ // https://doc.rust-lang.org/std/sync/atomic/struct.AtomicU32.html
+ unsafe { std::mem::transmute::<&Cell<u32>, &AtomicU32>(flags) }
+ }
+
+ #[inline]
+ fn flags_atomic(&self) -> &AtomicU32 {
+ Self::flags_atomic_for(&self.0._base._base_1.mFlags)
+ }
+
+ #[inline]
+ fn flags(&self) -> u32 {
+ self.flags_atomic().load(Ordering::Relaxed)
+ }
+
+ #[inline]
+ fn selector_flags_atomic(&self) -> &AtomicU32 {
+ Self::flags_atomic_for(&self.0.mSelectorFlags)
+ }
+
+ #[inline]
+ fn selector_flags(&self) -> u32 {
+ self.selector_flags_atomic().load(Ordering::Relaxed)
+ }
+
+ #[inline]
+ fn set_selector_flags(&self, flags: u32) {
+ self.selector_flags_atomic()
+ .fetch_or(flags, Ordering::Relaxed);
+ }
+
+ #[inline]
+ fn node_info(&self) -> &structs::NodeInfo {
+ debug_assert!(!self.0.mNodeInfo.mRawPtr.is_null());
+ unsafe { &*self.0.mNodeInfo.mRawPtr }
+ }
+
+ // These live in different locations depending on processor architecture.
+ #[cfg(target_pointer_width = "64")]
+ #[inline]
+ fn bool_flags(&self) -> u32 {
+ (self.0)._base._base_1.mBoolFlags
+ }
+
+ #[cfg(target_pointer_width = "32")]
+ #[inline]
+ fn bool_flags(&self) -> u32 {
+ (self.0).mBoolFlags
+ }
+
+ #[inline]
+ fn get_bool_flag(&self, flag: nsINode_BooleanFlag) -> bool {
+ self.bool_flags() & (1u32 << flag as u32) != 0
+ }
+
+ /// This logic is duplicate in Gecko's nsINode::IsInShadowTree().
+ #[inline]
+ fn is_in_shadow_tree(&self) -> bool {
+ use crate::gecko_bindings::structs::NODE_IS_IN_SHADOW_TREE;
+ self.flags() & NODE_IS_IN_SHADOW_TREE != 0
+ }
+
+ /// Returns true if we know for sure that `flattened_tree_parent` and `parent_node` return the
+ /// same thing.
+ ///
+ /// TODO(emilio): Measure and consider not doing this fast-path, it's only a function call and
+ /// from profiles it seems that keeping this fast path makes the compiler not inline
+ /// `flattened_tree_parent` as a whole, so we're not gaining much either.
+ #[inline]
+ fn flattened_tree_parent_is_parent(&self) -> bool {
+ use crate::gecko_bindings::structs::*;
+ let flags = self.flags();
+
+ let parent = match self.parent_node() {
+ Some(p) => p,
+ None => return true,
+ };
+
+ if parent.is_shadow_root() {
+ return false;
+ }
+
+ if let Some(parent) = parent.as_element() {
+ if flags & NODE_IS_NATIVE_ANONYMOUS_ROOT != 0 && parent.is_root() {
+ return false;
+ }
+ if parent.shadow_root().is_some() || parent.is_html_slot_element() {
+ return false;
+ }
+ }
+
+ true
+ }
+
+ #[inline]
+ fn flattened_tree_parent(&self) -> Option<Self> {
+ if self.flattened_tree_parent_is_parent() {
+ debug_assert_eq!(
+ unsafe {
+ bindings::Gecko_GetFlattenedTreeParentNode(self.0)
+ .as_ref()
+ .map(GeckoNode)
+ },
+ self.parent_node(),
+ "Fast path stopped holding!"
+ );
+ return self.parent_node();
+ }
+
+ // NOTE(emilio): If this call is too expensive, we could manually inline more aggressively.
+ unsafe {
+ bindings::Gecko_GetFlattenedTreeParentNode(self.0)
+ .as_ref()
+ .map(GeckoNode)
+ }
+ }
+
+ #[inline]
+ fn contains_non_whitespace_content(&self) -> bool {
+ unsafe { Gecko_IsSignificantChild(self.0, false) }
+ }
+
+ /// Returns the previous sibling of this node that is an element.
+ #[inline]
+ pub fn prev_sibling_element(&self) -> Option<GeckoElement> {
+ let mut prev = self.prev_sibling();
+ while let Some(p) = prev {
+ if let Some(e) = p.as_element() {
+ return Some(e);
+ }
+ prev = p.prev_sibling();
+ }
+ None
+ }
+
+ /// Returns the next sibling of this node that is an element.
+ #[inline]
+ pub fn next_sibling_element(&self) -> Option<GeckoElement> {
+ let mut next = self.next_sibling();
+ while let Some(n) = next {
+ if let Some(e) = n.as_element() {
+ return Some(e);
+ }
+ next = n.next_sibling();
+ }
+ None
+ }
+
+ /// Returns last child sibling of this node that is an element.
+ #[inline]
+ pub fn last_child_element(&self) -> Option<GeckoElement<'ln>> {
+ let last = match self.last_child() {
+ Some(n) => n,
+ None => return None,
+ };
+ if let Some(e) = last.as_element() {
+ return Some(e);
+ }
+ None
+ }
+}
+
+impl<'ln> NodeInfo for GeckoNode<'ln> {
+ #[inline]
+ fn is_element(&self) -> bool {
+ self.get_bool_flag(nsINode_BooleanFlag::NodeIsElement)
+ }
+
+ fn is_text_node(&self) -> bool {
+ // This is a DOM constant that isn't going to change.
+ const TEXT_NODE: u16 = 3;
+ self.node_info().mInner.mNodeType == TEXT_NODE
+ }
+}
+
+impl<'ln> TNode for GeckoNode<'ln> {
+ type ConcreteDocument = GeckoDocument<'ln>;
+ type ConcreteShadowRoot = GeckoShadowRoot<'ln>;
+ type ConcreteElement = GeckoElement<'ln>;
+
+ #[inline]
+ fn parent_node(&self) -> Option<Self> {
+ unsafe { self.0.mParent.as_ref().map(GeckoNode) }
+ }
+
+ #[inline]
+ fn first_child(&self) -> Option<Self> {
+ unsafe {
+ self.0
+ .mFirstChild
+ .raw()
+ .as_ref()
+ .map(GeckoNode::from_content)
+ }
+ }
+
+ #[inline]
+ fn last_child(&self) -> Option<Self> {
+ unsafe { bindings::Gecko_GetLastChild(self.0).as_ref().map(GeckoNode) }
+ }
+
+ #[inline]
+ fn prev_sibling(&self) -> Option<Self> {
+ unsafe {
+ let prev_or_last = GeckoNode::from_content(self.0.mPreviousOrLastSibling.as_ref()?);
+ if prev_or_last.0.mNextSibling.raw().is_null() {
+ return None;
+ }
+ Some(prev_or_last)
+ }
+ }
+
+ #[inline]
+ fn next_sibling(&self) -> Option<Self> {
+ unsafe {
+ self.0
+ .mNextSibling
+ .raw()
+ .as_ref()
+ .map(GeckoNode::from_content)
+ }
+ }
+
+ #[inline]
+ fn owner_doc(&self) -> Self::ConcreteDocument {
+ debug_assert!(!self.node_info().mDocument.is_null());
+ GeckoDocument(unsafe { &*self.node_info().mDocument })
+ }
+
+ #[inline]
+ fn is_in_document(&self) -> bool {
+ self.get_bool_flag(nsINode_BooleanFlag::IsInDocument)
+ }
+
+ fn traversal_parent(&self) -> Option<GeckoElement<'ln>> {
+ self.flattened_tree_parent().and_then(|n| n.as_element())
+ }
+
+ #[inline]
+ fn opaque(&self) -> OpaqueNode {
+ let ptr: usize = self.0 as *const _ as usize;
+ OpaqueNode(ptr)
+ }
+
+ fn debug_id(self) -> usize {
+ unimplemented!()
+ }
+
+ #[inline]
+ fn as_element(&self) -> Option<GeckoElement<'ln>> {
+ if !self.is_element() {
+ return None;
+ }
+
+ Some(GeckoElement(unsafe {
+ &*(self.0 as *const _ as *const RawGeckoElement)
+ }))
+ }
+
+ #[inline]
+ fn as_document(&self) -> Option<Self::ConcreteDocument> {
+ if !self.is_document() {
+ return None;
+ }
+
+ debug_assert_eq!(self.owner_doc().as_node(), *self, "How?");
+ Some(self.owner_doc())
+ }
+
+ #[inline]
+ fn as_shadow_root(&self) -> Option<Self::ConcreteShadowRoot> {
+ if !self.is_shadow_root() {
+ return None;
+ }
+
+ Some(GeckoShadowRoot(unsafe {
+ &*(self.0 as *const _ as *const structs::ShadowRoot)
+ }))
+ }
+}
+
+/// A wrapper on top of two kind of iterators, depending on the parent being
+/// iterated.
+///
+/// We generally iterate children by traversing the light-tree siblings of the
+/// first child like Servo does.
+///
+/// However, for nodes with anonymous children, we use a custom (heavier-weight)
+/// Gecko-implemented iterator.
+///
+/// FIXME(emilio): If we take into account shadow DOM, we're going to need the
+/// flat tree pretty much always. We can try to optimize the case where there's
+/// no shadow root sibling, probably.
+pub enum GeckoChildrenIterator<'a> {
+ /// A simple iterator that tracks the current node being iterated and
+ /// replaces it with the next sibling when requested.
+ Current(Option<GeckoNode<'a>>),
+ /// A Gecko-implemented iterator we need to drop appropriately.
+ GeckoIterator(structs::StyleChildrenIterator),
+}
+
+impl<'a> Drop for GeckoChildrenIterator<'a> {
+ fn drop(&mut self) {
+ if let GeckoChildrenIterator::GeckoIterator(ref mut it) = *self {
+ unsafe {
+ bindings::Gecko_DestroyStyleChildrenIterator(it);
+ }
+ }
+ }
+}
+
+impl<'a> Iterator for GeckoChildrenIterator<'a> {
+ type Item = GeckoNode<'a>;
+ fn next(&mut self) -> Option<GeckoNode<'a>> {
+ match *self {
+ GeckoChildrenIterator::Current(curr) => {
+ let next = curr.and_then(|node| node.next_sibling());
+ *self = GeckoChildrenIterator::Current(next);
+ curr
+ },
+ GeckoChildrenIterator::GeckoIterator(ref mut it) => unsafe {
+ // We do this unsafe lengthening of the lifetime here because
+ // structs::StyleChildrenIterator is actually StyleChildrenIterator<'a>,
+ // however we can't express this easily with bindgen, and it would
+ // introduce functions with two input lifetimes into bindgen,
+ // which would be out of scope for elision.
+ bindings::Gecko_GetNextStyleChild(&mut *(it as *mut _))
+ .as_ref()
+ .map(GeckoNode)
+ },
+ }
+ }
+}
+
+/// A simple wrapper over a non-null Gecko `Element` pointer.
+#[derive(Clone, Copy)]
+pub struct GeckoElement<'le>(pub &'le RawGeckoElement);
+
+impl<'le> fmt::Debug for GeckoElement<'le> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use nsstring::nsCString;
+
+ write!(f, "<{}", self.local_name())?;
+
+ let mut attrs = nsCString::new();
+ unsafe {
+ bindings::Gecko_Element_DebugListAttributes(self.0, &mut attrs);
+ }
+ write!(f, "{}", attrs)?;
+ write!(f, "> ({:#x})", self.as_node().opaque().0)
+ }
+}
+
+impl<'le> GeckoElement<'le> {
+ /// Gets the raw `ElementData` refcell for the element.
+ #[inline(always)]
+ pub fn get_data(&self) -> Option<&AtomicRefCell<ElementData>> {
+ unsafe { self.0.mServoData.get().as_ref() }
+ }
+
+ /// Returns whether any animation applies to this element.
+ #[inline]
+ pub fn has_any_animation(&self) -> bool {
+ self.may_have_animations() && unsafe { Gecko_ElementHasAnimations(self.0) }
+ }
+
+ #[inline(always)]
+ fn attrs(&self) -> &[structs::AttrArray_InternalAttr] {
+ unsafe {
+ match self.0.mAttrs.mImpl.mPtr.as_ref() {
+ Some(attrs) => attrs.mBuffer.as_slice(attrs.mAttrCount as usize),
+ None => return &[],
+ }
+ }
+ }
+
+ #[inline(always)]
+ fn get_part_attr(&self) -> Option<&structs::nsAttrValue> {
+ if !self.has_part_attr() {
+ return None;
+ }
+ snapshot_helpers::find_attr(self.attrs(), &atom!("part"))
+ }
+
+ #[inline(always)]
+ fn get_class_attr(&self) -> Option<&structs::nsAttrValue> {
+ if !self.may_have_class() {
+ return None;
+ }
+
+ if self.is_svg_element() {
+ let svg_class = unsafe { bindings::Gecko_GetSVGAnimatedClass(self.0).as_ref() };
+ if let Some(c) = svg_class {
+ return Some(c);
+ }
+ }
+
+ snapshot_helpers::find_attr(self.attrs(), &atom!("class"))
+ }
+
+ #[inline]
+ fn may_have_anonymous_children(&self) -> bool {
+ self.as_node()
+ .get_bool_flag(nsINode_BooleanFlag::ElementMayHaveAnonymousChildren)
+ }
+
+ #[inline]
+ fn flags(&self) -> u32 {
+ self.as_node().flags()
+ }
+
+ #[inline]
+ fn set_flags(&self, flags: u32) {
+ self.as_node().set_flags(flags);
+ }
+
+ #[inline]
+ unsafe fn unset_flags(&self, flags: u32) {
+ self.as_node()
+ .flags_atomic()
+ .fetch_and(!flags, Ordering::Relaxed);
+ }
+
+ /// Returns true if this element has descendants for lazy frame construction.
+ #[inline]
+ pub fn descendants_need_frames(&self) -> bool {
+ self.flags() & NODE_DESCENDANTS_NEED_FRAMES != 0
+ }
+
+ /// Returns true if this element needs lazy frame construction.
+ #[inline]
+ pub fn needs_frame(&self) -> bool {
+ self.flags() & NODE_NEEDS_FRAME != 0
+ }
+
+ /// Returns a reference to the DOM slots for this Element, if they exist.
+ #[inline]
+ fn dom_slots(&self) -> Option<&structs::FragmentOrElement_nsDOMSlots> {
+ let slots = self.as_node().0.mSlots as *const structs::FragmentOrElement_nsDOMSlots;
+ unsafe { slots.as_ref() }
+ }
+
+ /// Returns a reference to the extended DOM slots for this Element.
+ #[inline]
+ fn extended_slots(&self) -> Option<&structs::FragmentOrElement_nsExtendedDOMSlots> {
+ self.dom_slots().and_then(|s| unsafe {
+ // For the bit usage, see nsContentSlots::GetExtendedSlots.
+ let e_slots = s._base.mExtendedSlots &
+ !structs::nsIContent_nsContentSlots_sNonOwningExtendedSlotsFlag;
+ (e_slots as *const structs::FragmentOrElement_nsExtendedDOMSlots).as_ref()
+ })
+ }
+
+ #[inline]
+ fn namespace_id(&self) -> i32 {
+ self.as_node().node_info().mInner.mNamespaceID
+ }
+
+ #[inline]
+ fn has_id(&self) -> bool {
+ self.as_node()
+ .get_bool_flag(nsINode_BooleanFlag::ElementHasID)
+ }
+
+ #[inline]
+ fn state_internal(&self) -> u64 {
+ if !self
+ .as_node()
+ .get_bool_flag(nsINode_BooleanFlag::ElementHasLockedStyleStates)
+ {
+ return self.0.mState.bits;
+ }
+ unsafe { Gecko_ElementState(self.0) }
+ }
+
+ #[inline]
+ fn document_state(&self) -> DocumentState {
+ DocumentState::from_bits_retain(self.as_node().owner_doc().0.mState.bits)
+ }
+
+ #[inline]
+ fn may_have_class(&self) -> bool {
+ self.as_node()
+ .get_bool_flag(nsINode_BooleanFlag::ElementMayHaveClass)
+ }
+
+ #[inline]
+ fn has_properties(&self) -> bool {
+ use crate::gecko_bindings::structs::NODE_HAS_PROPERTIES;
+
+ self.flags() & NODE_HAS_PROPERTIES != 0
+ }
+
+ #[inline]
+ fn before_or_after_pseudo(&self, is_before: bool) -> Option<Self> {
+ if !self.has_properties() {
+ return None;
+ }
+
+ unsafe {
+ bindings::Gecko_GetBeforeOrAfterPseudo(self.0, is_before)
+ .as_ref()
+ .map(GeckoElement)
+ }
+ }
+
+ #[inline]
+ fn may_have_style_attribute(&self) -> bool {
+ self.as_node()
+ .get_bool_flag(nsINode_BooleanFlag::ElementMayHaveStyle)
+ }
+
+ /// Only safe to call on the main thread, with exclusive access to the
+ /// element and its ancestors.
+ ///
+ /// This function is also called after display property changed for SMIL
+ /// animation.
+ ///
+ /// Also this function schedules style flush.
+ pub unsafe fn note_explicit_hints(&self, restyle_hint: RestyleHint, change_hint: nsChangeHint) {
+ use crate::gecko::restyle_damage::GeckoRestyleDamage;
+
+ let damage = GeckoRestyleDamage::new(change_hint);
+ debug!(
+ "note_explicit_hints: {:?}, restyle_hint={:?}, change_hint={:?}",
+ self, restyle_hint, change_hint
+ );
+
+ debug_assert!(
+ !(restyle_hint.has_animation_hint() && restyle_hint.has_non_animation_hint()),
+ "Animation restyle hints should not appear with non-animation restyle hints"
+ );
+
+ let mut data = match self.mutate_data() {
+ Some(d) => d,
+ None => {
+ debug!("(Element not styled, discarding hints)");
+ return;
+ },
+ };
+
+ debug_assert!(data.has_styles(), "how?");
+
+ // Propagate the bit up the chain.
+ if restyle_hint.has_animation_hint() {
+ bindings::Gecko_NoteAnimationOnlyDirtyElement(self.0);
+ } else {
+ bindings::Gecko_NoteDirtyElement(self.0);
+ }
+
+ data.hint.insert(restyle_hint);
+ data.damage |= damage;
+ }
+
+ /// This logic is duplicated in Gecko's nsIContent::IsRootOfNativeAnonymousSubtree.
+ #[inline]
+ fn is_root_of_native_anonymous_subtree(&self) -> bool {
+ use crate::gecko_bindings::structs::NODE_IS_NATIVE_ANONYMOUS_ROOT;
+ return self.flags() & NODE_IS_NATIVE_ANONYMOUS_ROOT != 0;
+ }
+
+ fn css_transitions_info(&self) -> FxHashMap<OwnedPropertyDeclarationId, Arc<AnimationValue>> {
+ use crate::gecko_bindings::bindings::Gecko_ElementTransitions_EndValueAt;
+ use crate::gecko_bindings::bindings::Gecko_ElementTransitions_Length;
+
+ let collection_length = unsafe { Gecko_ElementTransitions_Length(self.0) } as usize;
+ let mut map = FxHashMap::with_capacity_and_hasher(collection_length, Default::default());
+
+ for i in 0..collection_length {
+ let end_value =
+ unsafe { Arc::from_raw_addrefed(Gecko_ElementTransitions_EndValueAt(self.0, i)) };
+ let property = end_value.id();
+ debug_assert!(!property.is_logical());
+ map.insert(property.to_owned(), end_value);
+ }
+ map
+ }
+
+ fn needs_transitions_update_per_property(
+ &self,
+ property_declaration_id: PropertyDeclarationId,
+ combined_duration_seconds: f32,
+ before_change_style: &ComputedValues,
+ after_change_style: &ComputedValues,
+ existing_transitions: &FxHashMap<OwnedPropertyDeclarationId, Arc<AnimationValue>>,
+ ) -> bool {
+ use crate::values::animated::{Animate, Procedure};
+ debug_assert!(!property_declaration_id.is_logical());
+
+ // If there is an existing transition, update only if the end value
+ // differs.
+ //
+ // If the end value has not changed, we should leave the currently
+ // running transition as-is since we don't want to interrupt its timing
+ // function.
+ if let Some(ref existing) = existing_transitions.get(&property_declaration_id.to_owned()) {
+ let after_value =
+ AnimationValue::from_computed_values(property_declaration_id, after_change_style)
+ .unwrap();
+
+ return ***existing != after_value;
+ }
+
+ let from =
+ AnimationValue::from_computed_values(property_declaration_id, before_change_style);
+ let to = AnimationValue::from_computed_values(property_declaration_id, after_change_style);
+
+ debug_assert_eq!(to.is_some(), from.is_some());
+
+ combined_duration_seconds > 0.0f32 &&
+ from != to &&
+ from.unwrap()
+ .animate(
+ to.as_ref().unwrap(),
+ Procedure::Interpolate { progress: 0.5 },
+ )
+ .is_ok()
+ }
+
+ /// Get slow selector flags required for nth-of invalidation.
+ pub fn slow_selector_flags(&self) -> ElementSelectorFlags {
+ slow_selector_flags_from_node_selector_flags(self.as_node().selector_flags())
+ }
+}
+
+/// Convert slow selector flags from the raw `NodeSelectorFlags`.
+pub fn slow_selector_flags_from_node_selector_flags(flags: u32) -> ElementSelectorFlags {
+ use crate::gecko_bindings::structs::NodeSelectorFlags;
+ let mut result = ElementSelectorFlags::empty();
+ if flags & NodeSelectorFlags::HasSlowSelector.0 != 0 {
+ result.insert(ElementSelectorFlags::HAS_SLOW_SELECTOR);
+ }
+ if flags & NodeSelectorFlags::HasSlowSelectorLaterSiblings.0 != 0 {
+ result.insert(ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS);
+ }
+ result
+}
+
+/// Converts flags from the layout used by rust-selectors to the layout used
+/// by Gecko. We could align these and then do this without conditionals, but
+/// it's probably not worth the trouble.
+fn selector_flags_to_node_flags(flags: ElementSelectorFlags) -> u32 {
+ use crate::gecko_bindings::structs::NodeSelectorFlags;
+ let mut gecko_flags = 0u32;
+ if flags.contains(ElementSelectorFlags::HAS_SLOW_SELECTOR) {
+ gecko_flags |= NodeSelectorFlags::HasSlowSelector.0;
+ }
+ if flags.contains(ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS) {
+ gecko_flags |= NodeSelectorFlags::HasSlowSelectorLaterSiblings.0;
+ }
+ if flags.contains(ElementSelectorFlags::HAS_SLOW_SELECTOR_NTH) {
+ gecko_flags |= NodeSelectorFlags::HasSlowSelectorNth.0;
+ }
+ if flags.contains(ElementSelectorFlags::HAS_SLOW_SELECTOR_NTH_OF) {
+ gecko_flags |= NodeSelectorFlags::HasSlowSelectorNthOf.0;
+ }
+ if flags.contains(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR) {
+ gecko_flags |= NodeSelectorFlags::HasEdgeChildSelector.0;
+ }
+ if flags.contains(ElementSelectorFlags::HAS_EMPTY_SELECTOR) {
+ gecko_flags |= NodeSelectorFlags::HasEmptySelector.0;
+ }
+ if flags.contains(ElementSelectorFlags::ANCHORS_RELATIVE_SELECTOR) {
+ gecko_flags |= NodeSelectorFlags::RelativeSelectorAnchor.0;
+ }
+ if flags.contains(ElementSelectorFlags::ANCHORS_RELATIVE_SELECTOR_NON_SUBJECT) {
+ gecko_flags |= NodeSelectorFlags::RelativeSelectorAnchorNonSubject.0;
+ }
+ if flags.contains(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR) {
+ gecko_flags |= NodeSelectorFlags::RelativeSelectorSearchDirectionAncestor.0;
+ }
+ if flags.contains(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_SIBLING) {
+ gecko_flags |= NodeSelectorFlags::RelativeSelectorSearchDirectionSibling.0;
+ }
+
+ gecko_flags
+}
+
+fn get_animation_rule(
+ element: &GeckoElement,
+ cascade_level: CascadeLevel,
+) -> Option<Arc<Locked<PropertyDeclarationBlock>>> {
+ // There's a very rough correlation between the number of effects
+ // (animations) on an element and the number of properties it is likely to
+ // animate, so we use that as an initial guess for the size of the
+ // AnimationValueMap in order to reduce the number of re-allocations needed.
+ let effect_count = unsafe { Gecko_GetAnimationEffectCount(element.0) };
+ // Also, we should try to reuse the PDB, to avoid creating extra rule nodes.
+ let mut animation_values = AnimationValueMap::with_capacity_and_hasher(
+ effect_count.min(crate::properties::property_counts::ANIMATABLE),
+ Default::default(),
+ );
+ if unsafe { Gecko_GetAnimationRule(element.0, cascade_level, &mut animation_values) } {
+ let shared_lock = &GLOBAL_STYLE_DATA.shared_lock;
+ Some(Arc::new(shared_lock.wrap(
+ PropertyDeclarationBlock::from_animation_value_map(&animation_values),
+ )))
+ } else {
+ None
+ }
+}
+
+/// Turns a gecko namespace id into an atom. Might panic if you pass any random thing that isn't a
+/// namespace id.
+#[inline(always)]
+pub unsafe fn namespace_id_to_atom(id: i32) -> *mut nsAtom {
+ unsafe {
+ let namespace_manager = structs::nsNameSpaceManager_sInstance.mRawPtr;
+ (*namespace_manager).mURIArray[id as usize].mRawPtr
+ }
+}
+
+impl<'le> TElement for GeckoElement<'le> {
+ type ConcreteNode = GeckoNode<'le>;
+ type TraversalChildrenIterator = GeckoChildrenIterator<'le>;
+
+ fn inheritance_parent(&self) -> Option<Self> {
+ if self.is_pseudo_element() {
+ return self.pseudo_element_originating_element();
+ }
+
+ self.as_node()
+ .flattened_tree_parent()
+ .and_then(|n| n.as_element())
+ }
+
+ fn traversal_children(&self) -> LayoutIterator<GeckoChildrenIterator<'le>> {
+ // This condition is similar to the check that
+ // StyleChildrenIterator::IsNeeded does, except that it might return
+ // true if we used to (but no longer) have anonymous content from
+ // ::before/::after, or nsIAnonymousContentCreators.
+ if self.is_html_slot_element() ||
+ self.shadow_root().is_some() ||
+ self.may_have_anonymous_children()
+ {
+ unsafe {
+ let mut iter: structs::StyleChildrenIterator = ::std::mem::zeroed();
+ bindings::Gecko_ConstructStyleChildrenIterator(self.0, &mut iter);
+ return LayoutIterator(GeckoChildrenIterator::GeckoIterator(iter));
+ }
+ }
+
+ LayoutIterator(GeckoChildrenIterator::Current(self.as_node().first_child()))
+ }
+
+ fn before_pseudo_element(&self) -> Option<Self> {
+ self.before_or_after_pseudo(/* is_before = */ true)
+ }
+
+ fn after_pseudo_element(&self) -> Option<Self> {
+ self.before_or_after_pseudo(/* is_before = */ false)
+ }
+
+ fn marker_pseudo_element(&self) -> Option<Self> {
+ if !self.has_properties() {
+ return None;
+ }
+
+ unsafe {
+ bindings::Gecko_GetMarkerPseudo(self.0)
+ .as_ref()
+ .map(GeckoElement)
+ }
+ }
+
+ #[inline]
+ fn is_html_element(&self) -> bool {
+ self.namespace_id() == structs::kNameSpaceID_XHTML as i32
+ }
+
+ #[inline]
+ fn is_mathml_element(&self) -> bool {
+ self.namespace_id() == structs::kNameSpaceID_MathML as i32
+ }
+
+ #[inline]
+ fn is_svg_element(&self) -> bool {
+ self.namespace_id() == structs::kNameSpaceID_SVG as i32
+ }
+
+ #[inline]
+ fn is_xul_element(&self) -> bool {
+ self.namespace_id() == structs::root::kNameSpaceID_XUL as i32
+ }
+
+ #[inline]
+ fn local_name(&self) -> &WeakAtom {
+ unsafe { WeakAtom::new(self.as_node().node_info().mInner.mName) }
+ }
+
+ #[inline]
+ fn namespace(&self) -> &WeakNamespace {
+ unsafe { WeakNamespace::new(namespace_id_to_atom(self.namespace_id())) }
+ }
+
+ #[inline]
+ fn query_container_size(&self, display: &Display) -> Size2D<Option<Au>> {
+ // If an element gets 'display: contents' and its nsIFrame has not been removed yet,
+ // Gecko_GetQueryContainerSize will not notice that it can't have size containment.
+ // Other cases like 'display: inline' will be handled once the new nsIFrame is created.
+ if display.is_contents() {
+ return Size2D::new(None, None);
+ }
+
+ let mut width = -1;
+ let mut height = -1;
+ unsafe {
+ bindings::Gecko_GetQueryContainerSize(self.0, &mut width, &mut height);
+ }
+ Size2D::new(
+ if width >= 0 { Some(Au(width)) } else { None },
+ if height >= 0 { Some(Au(height)) } else { None },
+ )
+ }
+
+ /// Return the list of slotted nodes of this node.
+ #[inline]
+ fn slotted_nodes(&self) -> &[Self::ConcreteNode] {
+ if !self.is_html_slot_element() || !self.as_node().is_in_shadow_tree() {
+ return &[];
+ }
+
+ let slot: &structs::HTMLSlotElement = unsafe { mem::transmute(self.0) };
+
+ if cfg!(debug_assertions) {
+ let base: &RawGeckoElement = &slot._base._base._base;
+ assert_eq!(base as *const _, self.0 as *const _, "Bad cast");
+ }
+
+ // FIXME(emilio): Workaround a bindgen bug on Android that causes
+ // mAssignedNodes to be at the wrong offset. See bug 1466406.
+ //
+ // Bug 1466580 tracks running the Android layout tests on automation.
+ //
+ // The actual bindgen bug still needs reduction.
+ let assigned_nodes: &[structs::RefPtr<structs::nsINode>] = if !cfg!(target_os = "android") {
+ debug_assert_eq!(
+ unsafe { bindings::Gecko_GetAssignedNodes(self.0) },
+ &slot.mAssignedNodes as *const _,
+ );
+
+ &*slot.mAssignedNodes
+ } else {
+ unsafe { &**bindings::Gecko_GetAssignedNodes(self.0) }
+ };
+
+ debug_assert_eq!(
+ mem::size_of::<structs::RefPtr<structs::nsINode>>(),
+ mem::size_of::<Self::ConcreteNode>(),
+ "Bad cast!"
+ );
+
+ unsafe { mem::transmute(assigned_nodes) }
+ }
+
+ #[inline]
+ fn shadow_root(&self) -> Option<GeckoShadowRoot<'le>> {
+ let slots = self.extended_slots()?;
+ unsafe { slots.mShadowRoot.mRawPtr.as_ref().map(GeckoShadowRoot) }
+ }
+
+ #[inline]
+ fn containing_shadow(&self) -> Option<GeckoShadowRoot<'le>> {
+ let slots = self.extended_slots()?;
+ unsafe {
+ slots
+ ._base
+ .mContainingShadow
+ .mRawPtr
+ .as_ref()
+ .map(GeckoShadowRoot)
+ }
+ }
+
+ fn each_anonymous_content_child<F>(&self, mut f: F)
+ where
+ F: FnMut(Self),
+ {
+ if !self.may_have_anonymous_children() {
+ return;
+ }
+
+ let array: *mut structs::nsTArray<*mut nsIContent> =
+ unsafe { bindings::Gecko_GetAnonymousContentForElement(self.0) };
+
+ if array.is_null() {
+ return;
+ }
+
+ for content in unsafe { &**array } {
+ let node = GeckoNode::from_content(unsafe { &**content });
+ let element = match node.as_element() {
+ Some(e) => e,
+ None => continue,
+ };
+
+ f(element);
+ }
+
+ unsafe { bindings::Gecko_DestroyAnonymousContentList(array) };
+ }
+
+ #[inline]
+ fn as_node(&self) -> Self::ConcreteNode {
+ unsafe { GeckoNode(&*(self.0 as *const _ as *const RawGeckoNode)) }
+ }
+
+ fn owner_doc_matches_for_testing(&self, device: &Device) -> bool {
+ self.as_node().owner_doc().0 as *const structs::Document == device.document() as *const _
+ }
+
+ fn style_attribute(&self) -> Option<ArcBorrow<Locked<PropertyDeclarationBlock>>> {
+ if !self.may_have_style_attribute() {
+ return None;
+ }
+
+ unsafe {
+ let declarations = Gecko_GetStyleAttrDeclarationBlock(self.0).as_ref()?;
+ Some(ArcBorrow::from_ref(declarations))
+ }
+ }
+
+ fn unset_dirty_style_attribute(&self) {
+ if !self.may_have_style_attribute() {
+ return;
+ }
+
+ unsafe { Gecko_UnsetDirtyStyleAttr(self.0) };
+ }
+
+ fn smil_override(&self) -> Option<ArcBorrow<Locked<PropertyDeclarationBlock>>> {
+ unsafe {
+ let slots = self.extended_slots()?;
+
+ let declaration: &structs::DeclarationBlock =
+ slots.mSMILOverrideStyleDeclaration.mRawPtr.as_ref()?;
+
+ let raw: &structs::StyleLockedDeclarationBlock = declaration.mRaw.mRawPtr.as_ref()?;
+ Some(ArcBorrow::from_ref(raw))
+ }
+ }
+
+ fn animation_rule(
+ &self,
+ _: &SharedStyleContext,
+ ) -> Option<Arc<Locked<PropertyDeclarationBlock>>> {
+ get_animation_rule(self, CascadeLevel::Animations)
+ }
+
+ fn transition_rule(
+ &self,
+ _: &SharedStyleContext,
+ ) -> Option<Arc<Locked<PropertyDeclarationBlock>>> {
+ get_animation_rule(self, CascadeLevel::Transitions)
+ }
+
+ #[inline]
+ fn state(&self) -> ElementState {
+ ElementState::from_bits_retain(self.state_internal())
+ }
+
+ #[inline]
+ fn has_custom_state(&self, state: &CustomState) -> bool {
+ if !self.is_html_element() {
+ return false;
+ }
+ let check_state_ptr: *const nsAtom = state.0.as_ptr();
+ self.extended_slots().map_or(false, |slot| {
+ (&slot.mCustomStates).iter().any(|setstate| {
+ let setstate_ptr: *const nsAtom = setstate.mRawPtr;
+ setstate_ptr == check_state_ptr
+ })
+ })
+ }
+
+ #[inline]
+ fn has_part_attr(&self) -> bool {
+ self.as_node()
+ .get_bool_flag(nsINode_BooleanFlag::ElementHasPart)
+ }
+
+ #[inline]
+ fn exports_any_part(&self) -> bool {
+ snapshot_helpers::find_attr(self.attrs(), &atom!("exportparts")).is_some()
+ }
+
+ // FIXME(emilio): we should probably just return a reference to the Atom.
+ #[inline]
+ fn id(&self) -> Option<&WeakAtom> {
+ if !self.has_id() {
+ return None;
+ }
+ snapshot_helpers::get_id(self.attrs())
+ }
+
+ fn each_attr_name<F>(&self, mut callback: F)
+ where
+ F: FnMut(&AtomIdent),
+ {
+ for attr in self.attrs() {
+ unsafe { AtomIdent::with(attr.mName.name(), |a| callback(a)) }
+ }
+ }
+
+ fn each_class<F>(&self, callback: F)
+ where
+ F: FnMut(&AtomIdent),
+ {
+ let attr = match self.get_class_attr() {
+ Some(c) => c,
+ None => return,
+ };
+
+ snapshot_helpers::each_class_or_part(attr, callback)
+ }
+
+ #[inline]
+ fn each_exported_part<F>(&self, name: &AtomIdent, callback: F)
+ where
+ F: FnMut(&AtomIdent),
+ {
+ snapshot_helpers::each_exported_part(self.attrs(), name, callback)
+ }
+
+ fn each_part<F>(&self, callback: F)
+ where
+ F: FnMut(&AtomIdent),
+ {
+ let attr = match self.get_part_attr() {
+ Some(c) => c,
+ None => return,
+ };
+
+ snapshot_helpers::each_class_or_part(attr, callback)
+ }
+
+ #[inline]
+ fn has_snapshot(&self) -> bool {
+ self.flags() & ELEMENT_HAS_SNAPSHOT != 0
+ }
+
+ #[inline]
+ fn handled_snapshot(&self) -> bool {
+ self.flags() & ELEMENT_HANDLED_SNAPSHOT != 0
+ }
+
+ unsafe fn set_handled_snapshot(&self) {
+ debug_assert!(self.has_data());
+ self.set_flags(ELEMENT_HANDLED_SNAPSHOT)
+ }
+
+ #[inline]
+ fn has_dirty_descendants(&self) -> bool {
+ self.flags() & ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO != 0
+ }
+
+ unsafe fn set_dirty_descendants(&self) {
+ debug_assert!(self.has_data());
+ debug!("Setting dirty descendants: {:?}", self);
+ self.set_flags(ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO)
+ }
+
+ unsafe fn unset_dirty_descendants(&self) {
+ self.unset_flags(ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO)
+ }
+
+ #[inline]
+ fn has_animation_only_dirty_descendants(&self) -> bool {
+ self.flags() & ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO != 0
+ }
+
+ unsafe fn set_animation_only_dirty_descendants(&self) {
+ self.set_flags(ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO)
+ }
+
+ unsafe fn unset_animation_only_dirty_descendants(&self) {
+ self.unset_flags(ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO)
+ }
+
+ unsafe fn clear_descendant_bits(&self) {
+ self.unset_flags(
+ ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO |
+ ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO |
+ NODE_DESCENDANTS_NEED_FRAMES,
+ )
+ }
+
+ fn is_visited_link(&self) -> bool {
+ self.state().intersects(ElementState::VISITED)
+ }
+
+ /// We want to match rules from the same tree in all cases, except for native anonymous content
+ /// that _isn't_ part directly of a UA widget (e.g., such generated by form controls, or
+ /// pseudo-elements).
+ #[inline]
+ fn matches_user_and_content_rules(&self) -> bool {
+ use crate::gecko_bindings::structs::{
+ NODE_HAS_BEEN_IN_UA_WIDGET, NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE,
+ };
+ let flags = self.flags();
+ (flags & NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE) == 0 ||
+ (flags & NODE_HAS_BEEN_IN_UA_WIDGET) != 0
+ }
+
+ #[inline]
+ fn implemented_pseudo_element(&self) -> Option<PseudoElement> {
+ if self.matches_user_and_content_rules() {
+ return None;
+ }
+
+ if !self.has_properties() {
+ return None;
+ }
+
+ PseudoElement::from_pseudo_type(
+ unsafe { bindings::Gecko_GetImplementedPseudo(self.0) },
+ None,
+ )
+ }
+
+ #[inline]
+ fn store_children_to_process(&self, _: isize) {
+ // This is only used for bottom-up traversal, and is thus a no-op for Gecko.
+ }
+
+ fn did_process_child(&self) -> isize {
+ panic!("Atomic child count not implemented in Gecko");
+ }
+
+ unsafe fn ensure_data(&self) -> AtomicRefMut<ElementData> {
+ if !self.has_data() {
+ debug!("Creating ElementData for {:?}", self);
+ let ptr = Box::into_raw(Box::new(AtomicRefCell::new(ElementData::default())));
+ self.0.mServoData.set(ptr);
+ }
+ self.mutate_data().unwrap()
+ }
+
+ unsafe fn clear_data(&self) {
+ let ptr = self.0.mServoData.get();
+ self.unset_flags(
+ ELEMENT_HAS_SNAPSHOT |
+ ELEMENT_HANDLED_SNAPSHOT |
+ structs::Element_kAllServoDescendantBits |
+ NODE_NEEDS_FRAME,
+ );
+ if !ptr.is_null() {
+ debug!("Dropping ElementData for {:?}", self);
+ let data = Box::from_raw(self.0.mServoData.get());
+ self.0.mServoData.set(ptr::null_mut());
+
+ // Perform a mutable borrow of the data in debug builds. This
+ // serves as an assertion that there are no outstanding borrows
+ // when we destroy the data.
+ debug_assert!({
+ let _ = data.borrow_mut();
+ true
+ });
+ }
+ }
+
+ #[inline]
+ fn skip_item_display_fixup(&self) -> bool {
+ debug_assert!(
+ !self.is_pseudo_element(),
+ "Just don't call me if I'm a pseudo, you should know the answer already"
+ );
+ self.is_root_of_native_anonymous_subtree()
+ }
+
+ #[inline]
+ fn may_have_animations(&self) -> bool {
+ if let Some(pseudo) = self.implemented_pseudo_element() {
+ if pseudo.animations_stored_in_parent() {
+ // FIXME(emilio): When would the parent of a ::before / ::after
+ // pseudo-element be null?
+ return self.parent_element().map_or(false, |p| {
+ p.as_node()
+ .get_bool_flag(nsINode_BooleanFlag::ElementHasAnimations)
+ });
+ }
+ }
+ self.as_node()
+ .get_bool_flag(nsINode_BooleanFlag::ElementHasAnimations)
+ }
+
+ /// Process various tasks that are a result of animation-only restyle.
+ fn process_post_animation(&self, tasks: PostAnimationTasks) {
+ debug_assert!(!tasks.is_empty(), "Should be involved a task");
+
+ // If display style was changed from none to other, we need to resolve
+ // the descendants in the display:none subtree. Instead of resolving
+ // those styles in animation-only restyle, we defer it to a subsequent
+ // normal restyle.
+ if tasks.intersects(PostAnimationTasks::DISPLAY_CHANGED_FROM_NONE_FOR_SMIL) {
+ debug_assert!(
+ self.implemented_pseudo_element()
+ .map_or(true, |p| !p.is_before_or_after()),
+ "display property animation shouldn't run on pseudo elements \
+ since it's only for SMIL"
+ );
+ unsafe {
+ self.note_explicit_hints(
+ RestyleHint::restyle_subtree(),
+ nsChangeHint::nsChangeHint_Empty,
+ );
+ }
+ }
+ }
+
+ /// Update various animation-related state on a given (pseudo-)element as
+ /// results of normal restyle.
+ fn update_animations(
+ &self,
+ before_change_style: Option<Arc<ComputedValues>>,
+ tasks: UpdateAnimationsTasks,
+ ) {
+ // We have to update animations even if the element has no computed
+ // style since it means the element is in a display:none subtree, we
+ // should destroy all CSS animations in display:none subtree.
+ let computed_data = self.borrow_data();
+ let computed_values = computed_data.as_ref().map(|d| d.styles.primary());
+ let before_change_values = before_change_style
+ .as_ref()
+ .map_or(ptr::null(), |x| x.as_gecko_computed_style());
+ let computed_values_opt = computed_values
+ .as_ref()
+ .map_or(ptr::null(), |x| x.as_gecko_computed_style());
+ unsafe {
+ Gecko_UpdateAnimations(
+ self.0,
+ before_change_values,
+ computed_values_opt,
+ tasks.bits(),
+ );
+ }
+ }
+
+ #[inline]
+ fn has_animations(&self, _: &SharedStyleContext) -> bool {
+ self.has_any_animation()
+ }
+
+ fn has_css_animations(&self, _: &SharedStyleContext, _: Option<PseudoElement>) -> bool {
+ self.may_have_animations() && unsafe { Gecko_ElementHasCSSAnimations(self.0) }
+ }
+
+ fn has_css_transitions(&self, _: &SharedStyleContext, _: Option<PseudoElement>) -> bool {
+ self.may_have_animations() && unsafe { Gecko_ElementHasCSSTransitions(self.0) }
+ }
+
+ // Detect if there are any changes that require us to update transitions.
+ //
+ // This is used as a more thoroughgoing check than the cheaper
+ // might_need_transitions_update check.
+ //
+ // The following logic shadows the logic used on the Gecko side
+ // (nsTransitionManager::DoUpdateTransitions) where we actually perform the
+ // update.
+ //
+ // https://drafts.csswg.org/css-transitions/#starting
+ fn needs_transitions_update(
+ &self,
+ before_change_style: &ComputedValues,
+ after_change_style: &ComputedValues,
+ ) -> bool {
+ let after_change_ui_style = after_change_style.get_ui();
+ let existing_transitions = self.css_transitions_info();
+
+ if after_change_style.get_box().clone_display().is_none() {
+ // We need to cancel existing transitions.
+ return !existing_transitions.is_empty();
+ }
+
+ let mut transitions_to_keep = PropertyDeclarationIdSet::default();
+ for transition_property in after_change_style.transition_properties() {
+ let physical_longhand = PropertyDeclarationId::Longhand(
+ transition_property
+ .longhand_id
+ .to_physical(after_change_style.writing_mode),
+ );
+ 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)
+ .seconds(),
+ before_change_style,
+ after_change_style,
+ &existing_transitions,
+ ) {
+ return true;
+ }
+ }
+
+ // Check if we have to cancel the running transition because this is not
+ // a matching transition-property value.
+ existing_transitions
+ .keys()
+ .any(|property| !transitions_to_keep.contains(property.as_borrowed()))
+ }
+
+ /// Whether there is an ElementData container.
+ #[inline]
+ fn has_data(&self) -> bool {
+ self.get_data().is_some()
+ }
+
+ /// Immutably borrows the ElementData.
+ fn borrow_data(&self) -> Option<AtomicRef<ElementData>> {
+ self.get_data().map(|x| x.borrow())
+ }
+
+ /// Mutably borrows the ElementData.
+ fn mutate_data(&self) -> Option<AtomicRefMut<ElementData>> {
+ self.get_data().map(|x| x.borrow_mut())
+ }
+
+ #[inline]
+ fn lang_attr(&self) -> Option<AttrValue> {
+ let ptr = unsafe { bindings::Gecko_LangValue(self.0) };
+ if ptr.is_null() {
+ None
+ } else {
+ Some(AtomString(unsafe { Atom::from_addrefed(ptr) }))
+ }
+ }
+
+ fn match_element_lang(&self, override_lang: Option<Option<AttrValue>>, value: &Lang) -> bool {
+ // Gecko supports :lang() from CSS Selectors 4, which accepts a list
+ // of language tags, and does BCP47-style range matching.
+ let override_lang_ptr = match override_lang {
+ Some(Some(ref atom)) => atom.as_ptr(),
+ _ => ptr::null_mut(),
+ };
+ value.0.iter().any(|lang| unsafe {
+ Gecko_MatchLang(
+ self.0,
+ override_lang_ptr,
+ override_lang.is_some(),
+ lang.as_slice().as_ptr(),
+ )
+ })
+ }
+
+ fn is_html_document_body_element(&self) -> bool {
+ if self.local_name() != &**local_name!("body") {
+ return false;
+ }
+
+ if !self.is_html_element() {
+ return false;
+ }
+
+ unsafe { bindings::Gecko_IsDocumentBody(self.0) }
+ }
+
+ fn synthesize_presentational_hints_for_legacy_attributes<V>(
+ &self,
+ visited_handling: VisitedHandlingMode,
+ hints: &mut V,
+ ) where
+ V: Push<ApplicableDeclarationBlock>,
+ {
+ use crate::properties::longhands::_x_lang::SpecifiedValue as SpecifiedLang;
+ use crate::properties::longhands::color::SpecifiedValue as SpecifiedColor;
+ use crate::stylesheets::layer_rule::LayerOrder;
+ use crate::values::specified::{color::Color, font::XTextScale};
+ lazy_static! {
+ static ref TABLE_COLOR_RULE: ApplicableDeclarationBlock = {
+ let global_style_data = &*GLOBAL_STYLE_DATA;
+ let pdb = PropertyDeclarationBlock::with_one(
+ PropertyDeclaration::Color(SpecifiedColor(Color::InheritFromBodyQuirk.into())),
+ Importance::Normal,
+ );
+ let arc = Arc::new_leaked(global_style_data.shared_lock.wrap(pdb));
+ ApplicableDeclarationBlock::from_declarations(
+ arc,
+ ServoCascadeLevel::PresHints,
+ LayerOrder::root(),
+ )
+ };
+ static ref MATHML_LANG_RULE: ApplicableDeclarationBlock = {
+ let global_style_data = &*GLOBAL_STYLE_DATA;
+ let pdb = PropertyDeclarationBlock::with_one(
+ PropertyDeclaration::XLang(SpecifiedLang(atom!("x-math"))),
+ Importance::Normal,
+ );
+ let arc = Arc::new_leaked(global_style_data.shared_lock.wrap(pdb));
+ ApplicableDeclarationBlock::from_declarations(
+ arc,
+ ServoCascadeLevel::PresHints,
+ LayerOrder::root(),
+ )
+ };
+ static ref SVG_TEXT_DISABLE_SCALE_RULE: ApplicableDeclarationBlock = {
+ let global_style_data = &*GLOBAL_STYLE_DATA;
+ let pdb = PropertyDeclarationBlock::with_one(
+ PropertyDeclaration::XTextScale(XTextScale::None),
+ Importance::Normal,
+ );
+ let arc = Arc::new_leaked(global_style_data.shared_lock.wrap(pdb));
+ ApplicableDeclarationBlock::from_declarations(
+ arc,
+ ServoCascadeLevel::PresHints,
+ LayerOrder::root(),
+ )
+ };
+ };
+
+ 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() &&
+ self.as_node().owner_doc().quirks_mode() == QuirksMode::Quirks
+ {
+ hints.push(TABLE_COLOR_RULE.clone());
+ }
+ }
+ if ns == structs::kNameSpaceID_SVG as i32 {
+ if self.local_name().as_ptr() == atom!("text").as_ptr() {
+ hints.push(SVG_TEXT_DISABLE_SCALE_RULE.clone());
+ }
+ }
+ let declarations =
+ unsafe { Gecko_GetHTMLPresentationAttrDeclarationBlock(self.0).as_ref() };
+ if let Some(decl) = declarations {
+ hints.push(ApplicableDeclarationBlock::from_declarations(
+ unsafe { Arc::from_raw_addrefed(decl) },
+ ServoCascadeLevel::PresHints,
+ LayerOrder::root(),
+ ));
+ }
+ let declarations = unsafe { Gecko_GetExtraContentStyleDeclarations(self.0).as_ref() };
+ if let Some(decl) = declarations {
+ hints.push(ApplicableDeclarationBlock::from_declarations(
+ unsafe { Arc::from_raw_addrefed(decl) },
+ ServoCascadeLevel::PresHints,
+ LayerOrder::root(),
+ ));
+ }
+
+ // Support for link, vlink, and alink presentation hints on <body>
+ if self.is_link() {
+ // Unvisited vs. visited styles are computed up-front based on the
+ // visited mode (not the element's actual state).
+ let declarations = match visited_handling {
+ VisitedHandlingMode::AllLinksVisitedAndUnvisited => {
+ unreachable!(
+ "We should never try to selector match with \
+ AllLinksVisitedAndUnvisited"
+ );
+ },
+ VisitedHandlingMode::AllLinksUnvisited => unsafe {
+ Gecko_GetUnvisitedLinkAttrDeclarationBlock(self.0).as_ref()
+ },
+ VisitedHandlingMode::RelevantLinkVisited => unsafe {
+ Gecko_GetVisitedLinkAttrDeclarationBlock(self.0).as_ref()
+ },
+ };
+ if let Some(decl) = declarations {
+ hints.push(ApplicableDeclarationBlock::from_declarations(
+ unsafe { Arc::from_raw_addrefed(decl) },
+ ServoCascadeLevel::PresHints,
+ LayerOrder::root(),
+ ));
+ }
+
+ let active = self
+ .state()
+ .intersects(NonTSPseudoClass::Active.state_flag());
+ if active {
+ let declarations =
+ unsafe { Gecko_GetActiveLinkAttrDeclarationBlock(self.0).as_ref() };
+ if let Some(decl) = declarations {
+ hints.push(ApplicableDeclarationBlock::from_declarations(
+ unsafe { Arc::from_raw_addrefed(decl) },
+ ServoCascadeLevel::PresHints,
+ LayerOrder::root(),
+ ));
+ }
+ }
+ }
+
+ // xml:lang has precedence over lang, which can be
+ // set by Gecko_GetHTMLPresentationAttrDeclarationBlock
+ //
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#language
+ let ptr = unsafe { bindings::Gecko_GetXMLLangValue(self.0) };
+ if !ptr.is_null() {
+ let global_style_data = &*GLOBAL_STYLE_DATA;
+
+ let pdb = PropertyDeclarationBlock::with_one(
+ PropertyDeclaration::XLang(SpecifiedLang(unsafe { Atom::from_addrefed(ptr) })),
+ Importance::Normal,
+ );
+ let arc = Arc::new(global_style_data.shared_lock.wrap(pdb));
+ hints.push(ApplicableDeclarationBlock::from_declarations(
+ arc,
+ ServoCascadeLevel::PresHints,
+ LayerOrder::root(),
+ ))
+ }
+ // MathML's default lang has precedence over both `lang` and `xml:lang`
+ if ns == structs::kNameSpaceID_MathML as i32 {
+ if self.local_name().as_ptr() == atom!("math").as_ptr() {
+ hints.push(MATHML_LANG_RULE.clone());
+ }
+ }
+ }
+
+ fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool {
+ let node_flags = selector_flags_to_node_flags(flags);
+ self.as_node().selector_flags() & node_flags == node_flags
+ }
+
+ fn relative_selector_search_direction(&self) -> Option<ElementSelectorFlags> {
+ use crate::gecko_bindings::structs::NodeSelectorFlags;
+ let flags = self.as_node().selector_flags();
+ if (flags & NodeSelectorFlags::RelativeSelectorSearchDirectionAncestorSibling.0) != 0 {
+ Some(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR_SIBLING)
+ } else if (flags & NodeSelectorFlags::RelativeSelectorSearchDirectionAncestor.0) != 0 {
+ Some(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR)
+ } else if (flags & NodeSelectorFlags::RelativeSelectorSearchDirectionSibling.0) != 0 {
+ Some(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_SIBLING)
+ } else {
+ None
+ }
+ }
+}
+
+impl<'le> PartialEq for GeckoElement<'le> {
+ #[inline]
+ fn eq(&self, other: &Self) -> bool {
+ self.0 as *const _ == other.0 as *const _
+ }
+}
+
+impl<'le> Eq for GeckoElement<'le> {}
+
+impl<'le> Hash for GeckoElement<'le> {
+ #[inline]
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ (self.0 as *const RawGeckoElement).hash(state);
+ }
+}
+
+impl<'le> ::selectors::Element for GeckoElement<'le> {
+ type Impl = SelectorImpl;
+
+ #[inline]
+ fn opaque(&self) -> OpaqueElement {
+ OpaqueElement::new(self.0)
+ }
+
+ #[inline]
+ fn parent_element(&self) -> Option<Self> {
+ let parent_node = self.as_node().parent_node();
+ parent_node.and_then(|n| n.as_element())
+ }
+
+ #[inline]
+ fn parent_node_is_shadow_root(&self) -> bool {
+ self.as_node()
+ .parent_node()
+ .map_or(false, |p| p.is_shadow_root())
+ }
+
+ #[inline]
+ fn containing_shadow_host(&self) -> Option<Self> {
+ let shadow = self.containing_shadow()?;
+ Some(shadow.host())
+ }
+
+ #[inline]
+ fn is_pseudo_element(&self) -> bool {
+ self.implemented_pseudo_element().is_some()
+ }
+
+ #[inline]
+ fn pseudo_element_originating_element(&self) -> Option<Self> {
+ debug_assert!(self.is_pseudo_element());
+ debug_assert!(!self.matches_user_and_content_rules());
+ let mut current = *self;
+ loop {
+ if current.is_root_of_native_anonymous_subtree() {
+ return current.traversal_parent();
+ }
+
+ current = current.traversal_parent()?;
+ }
+ }
+
+ #[inline]
+ fn assigned_slot(&self) -> Option<Self> {
+ let slot = self.extended_slots()?._base.mAssignedSlot.mRawPtr;
+
+ unsafe { Some(GeckoElement(&slot.as_ref()?._base._base._base)) }
+ }
+
+ #[inline]
+ fn prev_sibling_element(&self) -> Option<Self> {
+ let mut sibling = self.as_node().prev_sibling();
+ while let Some(sibling_node) = sibling {
+ if let Some(el) = sibling_node.as_element() {
+ return Some(el);
+ }
+ sibling = sibling_node.prev_sibling();
+ }
+ None
+ }
+
+ #[inline]
+ fn next_sibling_element(&self) -> Option<Self> {
+ let mut sibling = self.as_node().next_sibling();
+ while let Some(sibling_node) = sibling {
+ if let Some(el) = sibling_node.as_element() {
+ return Some(el);
+ }
+ sibling = sibling_node.next_sibling();
+ }
+ None
+ }
+
+ #[inline]
+ fn first_element_child(&self) -> Option<Self> {
+ let mut child = self.as_node().first_child();
+ while let Some(child_node) = child {
+ if let Some(el) = child_node.as_element() {
+ return Some(el);
+ }
+ child = child_node.next_sibling();
+ }
+ None
+ }
+
+ 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.as_node()
+ .set_selector_flags(selector_flags_to_node_flags(flags))
+ }
+
+ // Handle flags that apply to the parent.
+ let parent_flags = flags.for_parent();
+ if !parent_flags.is_empty() {
+ if let Some(p) = self.as_node().parent_node() {
+ if p.is_element() || p.is_shadow_root() {
+ p.set_selector_flags(selector_flags_to_node_flags(parent_flags));
+ }
+ }
+ }
+ }
+
+ fn has_attr_in_no_namespace(&self, local_name: &LocalName) -> bool {
+ for attr in self.attrs() {
+ if attr.mName.mBits == local_name.as_ptr() as usize {
+ return true;
+ }
+ }
+ false
+ }
+
+ fn attr_matches(
+ &self,
+ ns: &NamespaceConstraint<&Namespace>,
+ local_name: &LocalName,
+ operation: &AttrSelectorOperation<&AttrValue>,
+ ) -> bool {
+ snapshot_helpers::attr_matches(self.attrs(), ns, local_name, operation)
+ }
+
+ #[inline]
+ fn is_root(&self) -> bool {
+ if self
+ .as_node()
+ .get_bool_flag(nsINode_BooleanFlag::ParentIsContent)
+ {
+ return false;
+ }
+
+ if !self.as_node().is_in_document() {
+ return false;
+ }
+
+ debug_assert!(self
+ .as_node()
+ .parent_node()
+ .map_or(false, |p| p.is_document()));
+ // XXX this should always return true at this point, shouldn't it?
+ unsafe { bindings::Gecko_IsRootElement(self.0) }
+ }
+
+ fn is_empty(&self) -> bool {
+ !self
+ .as_node()
+ .dom_children()
+ .any(|child| unsafe { Gecko_IsSignificantChild(child.0, true) })
+ }
+
+ #[inline]
+ fn has_local_name(&self, name: &WeakAtom) -> bool {
+ self.local_name() == name
+ }
+
+ #[inline]
+ fn has_namespace(&self, ns: &WeakNamespace) -> bool {
+ self.namespace() == ns
+ }
+
+ #[inline]
+ fn is_same_type(&self, other: &Self) -> bool {
+ self.local_name() == other.local_name() && self.namespace() == other.namespace()
+ }
+
+ fn match_non_ts_pseudo_class(
+ &self,
+ pseudo_class: &NonTSPseudoClass,
+ context: &mut MatchingContext<Self::Impl>,
+ ) -> bool {
+ use selectors::matching::*;
+ match *pseudo_class {
+ NonTSPseudoClass::Autofill |
+ NonTSPseudoClass::Defined |
+ NonTSPseudoClass::Focus |
+ NonTSPseudoClass::Enabled |
+ NonTSPseudoClass::Disabled |
+ NonTSPseudoClass::Checked |
+ NonTSPseudoClass::Fullscreen |
+ NonTSPseudoClass::Indeterminate |
+ NonTSPseudoClass::MozInert |
+ NonTSPseudoClass::PopoverOpen |
+ NonTSPseudoClass::PlaceholderShown |
+ NonTSPseudoClass::Target |
+ NonTSPseudoClass::Valid |
+ NonTSPseudoClass::Invalid |
+ NonTSPseudoClass::MozBroken |
+ NonTSPseudoClass::Required |
+ NonTSPseudoClass::Optional |
+ NonTSPseudoClass::ReadOnly |
+ NonTSPseudoClass::ReadWrite |
+ NonTSPseudoClass::FocusWithin |
+ NonTSPseudoClass::FocusVisible |
+ NonTSPseudoClass::MozDragOver |
+ NonTSPseudoClass::MozDevtoolsHighlighted |
+ NonTSPseudoClass::MozStyleeditorTransitioning |
+ NonTSPseudoClass::MozMathIncrementScriptLevel |
+ NonTSPseudoClass::InRange |
+ NonTSPseudoClass::OutOfRange |
+ NonTSPseudoClass::Default |
+ NonTSPseudoClass::UserValid |
+ NonTSPseudoClass::UserInvalid |
+ NonTSPseudoClass::MozMeterOptimum |
+ NonTSPseudoClass::MozMeterSubOptimum |
+ NonTSPseudoClass::MozMeterSubSubOptimum |
+ NonTSPseudoClass::MozHasDirAttr |
+ NonTSPseudoClass::MozDirAttrLTR |
+ NonTSPseudoClass::MozDirAttrRTL |
+ NonTSPseudoClass::MozDirAttrLikeAuto |
+ NonTSPseudoClass::Modal |
+ NonTSPseudoClass::MozTopmostModal |
+ NonTSPseudoClass::Active |
+ NonTSPseudoClass::Hover |
+ NonTSPseudoClass::MozAutofillPreview |
+ NonTSPseudoClass::MozRevealed |
+ NonTSPseudoClass::MozValueEmpty => self.state().intersects(pseudo_class.state_flag()),
+ // TODO: This applying only to HTML elements is weird.
+ NonTSPseudoClass::Dir(ref dir) => {
+ self.is_html_element() && self.state().intersects(dir.element_state())
+ },
+ NonTSPseudoClass::AnyLink => self.is_link(),
+ NonTSPseudoClass::Link => {
+ self.is_link() && context.visited_handling().matches_unvisited()
+ },
+ NonTSPseudoClass::CustomState(ref state) => self.has_custom_state(state),
+ NonTSPseudoClass::Visited => {
+ self.is_link() && context.visited_handling().matches_visited()
+ },
+ NonTSPseudoClass::MozFirstNode => {
+ if context.needs_selector_flags() {
+ self.apply_selector_flags(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR);
+ }
+ let mut elem = self.as_node();
+ while let Some(prev) = elem.prev_sibling() {
+ if prev.contains_non_whitespace_content() {
+ return false;
+ }
+ elem = prev;
+ }
+ true
+ },
+ NonTSPseudoClass::MozLastNode => {
+ if context.needs_selector_flags() {
+ self.apply_selector_flags(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR);
+ }
+ let mut elem = self.as_node();
+ while let Some(next) = elem.next_sibling() {
+ if next.contains_non_whitespace_content() {
+ return false;
+ }
+ elem = next;
+ }
+ true
+ },
+ NonTSPseudoClass::MozOnlyWhitespace => {
+ if context.needs_selector_flags() {
+ self.apply_selector_flags(ElementSelectorFlags::HAS_EMPTY_SELECTOR);
+ }
+ if self
+ .as_node()
+ .dom_children()
+ .any(|c| c.contains_non_whitespace_content())
+ {
+ return false;
+ }
+ true
+ },
+ NonTSPseudoClass::MozNativeAnonymous => !self.matches_user_and_content_rules(),
+ NonTSPseudoClass::MozTableBorderNonzero => unsafe {
+ bindings::Gecko_IsTableBorderNonzero(self.0)
+ },
+ NonTSPseudoClass::MozSelectListBox => unsafe {
+ bindings::Gecko_IsSelectListBox(self.0)
+ },
+ NonTSPseudoClass::MozIsHTML => self.as_node().owner_doc().is_html_document(),
+ NonTSPseudoClass::MozLWTheme |
+ NonTSPseudoClass::MozLocaleDir(..) |
+ NonTSPseudoClass::MozWindowInactive => {
+ let state_bit = pseudo_class.document_state_flag();
+ if state_bit.is_empty() {
+ debug_assert!(
+ matches!(pseudo_class, NonTSPseudoClass::MozLocaleDir(..)),
+ "Only moz-locale-dir should ever return an empty state"
+ );
+ return false;
+ }
+ if context
+ .extra_data
+ .invalidation_data
+ .document_state
+ .intersects(state_bit)
+ {
+ return !context.in_negation();
+ }
+ self.document_state().contains(state_bit)
+ },
+ NonTSPseudoClass::MozPlaceholder => false,
+ NonTSPseudoClass::Lang(ref lang_arg) => self.match_element_lang(None, lang_arg),
+ }
+ }
+
+ fn match_pseudo_element(
+ &self,
+ pseudo_element: &PseudoElement,
+ _context: &mut MatchingContext<Self::Impl>,
+ ) -> bool {
+ // TODO(emilio): I believe we could assert we are a pseudo-element and
+ // match the proper pseudo-element, given how we rulehash the stuff
+ // based on the pseudo.
+ match self.implemented_pseudo_element() {
+ Some(ref pseudo) => *pseudo == *pseudo_element,
+ None => false,
+ }
+ }
+
+ #[inline]
+ fn is_link(&self) -> bool {
+ self.state().intersects(ElementState::VISITED_OR_UNVISITED)
+ }
+
+ #[inline]
+ fn has_id(&self, id: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool {
+ if !self.has_id() {
+ return false;
+ }
+
+ let element_id = match snapshot_helpers::get_id(self.attrs()) {
+ Some(id) => id,
+ None => return false,
+ };
+
+ case_sensitivity.eq_atom(element_id, id)
+ }
+
+ #[inline]
+ fn is_part(&self, name: &AtomIdent) -> bool {
+ let attr = match self.get_part_attr() {
+ Some(c) => c,
+ None => return false,
+ };
+
+ snapshot_helpers::has_class_or_part(name, CaseSensitivity::CaseSensitive, attr)
+ }
+
+ #[inline]
+ fn imported_part(&self, name: &AtomIdent) -> Option<AtomIdent> {
+ snapshot_helpers::imported_part(self.attrs(), name)
+ }
+
+ #[inline(always)]
+ fn has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool {
+ let attr = match self.get_class_attr() {
+ Some(c) => c,
+ None => return false,
+ };
+
+ snapshot_helpers::has_class_or_part(name, case_sensitivity, attr)
+ }
+
+ #[inline]
+ fn is_html_element_in_html_document(&self) -> bool {
+ self.is_html_element() && self.as_node().owner_doc().is_html_document()
+ }
+
+ #[inline]
+ fn is_html_slot_element(&self) -> bool {
+ self.is_html_element() && self.local_name().as_ptr() == local_name!("slot").as_ptr()
+ }
+
+ #[inline]
+ fn ignores_nth_child_selectors(&self) -> bool {
+ self.is_root_of_native_anonymous_subtree()
+ }
+
+ fn add_element_unique_hashes(&self, filter: &mut BloomFilter) -> bool {
+ each_relevant_element_hash(*self, |hash| filter.insert_hash(hash & BLOOM_HASH_MASK));
+ true
+ }
+}