diff options
Diffstat (limited to 'servo/components/style/gecko')
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 + } +} |