summaryrefslogtreecommitdiffstats
path: root/servo/components/style/servo
diff options
context:
space:
mode:
Diffstat (limited to 'servo/components/style/servo')
-rw-r--r--servo/components/style/servo/media_queries.rs232
-rw-r--r--servo/components/style/servo/mod.rs12
-rw-r--r--servo/components/style/servo/restyle_damage.rs268
-rw-r--r--servo/components/style/servo/selector_parser.rs806
-rw-r--r--servo/components/style/servo/url.rs238
5 files changed, 1556 insertions, 0 deletions
diff --git a/servo/components/style/servo/media_queries.rs b/servo/components/style/servo/media_queries.rs
new file mode 100644
index 0000000000..286660b162
--- /dev/null
+++ b/servo/components/style/servo/media_queries.rs
@@ -0,0 +1,232 @@
+/* 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/. */
+
+//! Servo's media-query device and expression representation.
+
+use crate::context::QuirksMode;
+use crate::custom_properties::CssEnvironment;
+use crate::media_queries::media_feature::{AllowsRanges, ParsingRequirements};
+use crate::media_queries::media_feature::{Evaluator, MediaFeatureDescription};
+use crate::media_queries::media_feature_expression::RangeOrOperator;
+use crate::media_queries::MediaType;
+use crate::properties::ComputedValues;
+use crate::values::computed::CSSPixelLength;
+use crate::values::specified::font::FONT_MEDIUM_PX;
+use crate::values::KeyframesName;
+use app_units::Au;
+use euclid::default::Size2D as UntypedSize2D;
+use euclid::{Scale, SideOffsets2D, Size2D};
+use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
+use style_traits::viewport::ViewportConstraints;
+use style_traits::{CSSPixel, DevicePixel};
+
+/// A device is a structure that represents the current media a given document
+/// is displayed in.
+///
+/// This is the struct against which media queries are evaluated.
+#[derive(Debug, MallocSizeOf)]
+pub struct Device {
+ /// The current media type used by de device.
+ media_type: MediaType,
+ /// The current viewport size, in CSS pixels.
+ viewport_size: Size2D<f32, CSSPixel>,
+ /// The current device pixel ratio, from CSS pixels to device pixels.
+ device_pixel_ratio: Scale<f32, CSSPixel, DevicePixel>,
+ /// The current quirks mode.
+ #[ignore_malloc_size_of = "Pure stack type"]
+ quirks_mode: QuirksMode,
+
+ /// 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.
+ #[ignore_malloc_size_of = "Pure stack type"]
+ root_font_size: AtomicU32,
+ /// Whether any styles computed in the document relied on the root font-size
+ /// by using rem units.
+ #[ignore_malloc_size_of = "Pure stack type"]
+ used_root_font_size: AtomicBool,
+ /// Whether any styles computed in the document relied on the viewport size.
+ #[ignore_malloc_size_of = "Pure stack type"]
+ used_viewport_units: AtomicBool,
+ /// The CssEnvironment object responsible of getting CSS environment
+ /// variables.
+ environment: CssEnvironment,
+}
+
+impl Device {
+ /// Trivially construct a new `Device`.
+ pub fn new(
+ media_type: MediaType,
+ quirks_mode: QuirksMode,
+ viewport_size: Size2D<f32, CSSPixel>,
+ device_pixel_ratio: Scale<f32, CSSPixel, DevicePixel>,
+ ) -> Device {
+ Device {
+ media_type,
+ viewport_size,
+ device_pixel_ratio,
+ quirks_mode,
+ // FIXME(bz): Seems dubious?
+ root_font_size: AtomicU32::new(FONT_MEDIUM_PX.to_bits()),
+ used_root_font_size: AtomicBool::new(false),
+ used_viewport_units: AtomicBool::new(false),
+ environment: CssEnvironment,
+ }
+ }
+
+ /// Get the relevant environment to resolve `env()` functions.
+ #[inline]
+ pub fn environment(&self) -> &CssEnvironment {
+ &self.environment
+ }
+
+ /// Return the default computed values for this device.
+ pub fn default_computed_values(&self) -> &ComputedValues {
+ // FIXME(bz): This isn't really right, but it's no more wrong
+ // than what we used to do. See
+ // https://github.com/servo/servo/issues/14773 for fixing it properly.
+ ComputedValues::initial_values()
+ }
+
+ /// Get the font size of the root element (for rem)
+ pub fn root_font_size(&self) -> CSSPixelLength {
+ self.used_root_font_size.store(true, Ordering::Relaxed);
+ CSSPixelLength::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: CSSPixelLength) {
+ self.root_font_size
+ .store(size.px().to_bits(), Ordering::Relaxed)
+ }
+
+ /// Get the quirks mode of the current device.
+ pub fn quirks_mode(&self) -> QuirksMode {
+ self.quirks_mode
+ }
+
+ /// 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: RGBA) {
+ // Servo doesn't implement this quirk (yet)
+ }
+
+ /// Whether a given animation name may be referenced from style.
+ pub fn animation_name_may_be_referenced(&self, _: &KeyframesName) -> bool {
+ // Assume it is, since we don't have any good way to prove it's not.
+ true
+ }
+
+ /// 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 the viewport size of the current device in app units, needed,
+ /// among other things, to resolve viewport units.
+ #[inline]
+ pub fn au_viewport_size(&self) -> UntypedSize2D<Au> {
+ Size2D::new(
+ Au::from_f32_px(self.viewport_size.width),
+ Au::from_f32_px(self.viewport_size.height),
+ )
+ }
+
+ /// Like the above, but records that we've used viewport units.
+ pub fn au_viewport_size_for_viewport_unit_resolution(&self) -> UntypedSize2D<Au> {
+ self.used_viewport_units.store(true, Ordering::Relaxed);
+ self.au_viewport_size()
+ }
+
+ /// Whether viewport units were used since the last device change.
+ pub fn used_viewport_units(&self) -> bool {
+ self.used_viewport_units.load(Ordering::Relaxed)
+ }
+
+ /// Returns the device pixel ratio.
+ pub fn device_pixel_ratio(&self) -> Scale<f32, CSSPixel, DevicePixel> {
+ self.device_pixel_ratio
+ }
+
+ /// Take into account a viewport rule taken from the stylesheets.
+ pub fn account_for_viewport_rule(&mut self, constraints: &ViewportConstraints) {
+ self.viewport_size = constraints.size;
+ }
+
+ /// Return the media type of the current device.
+ pub fn media_type(&self) -> MediaType {
+ self.media_type.clone()
+ }
+
+ /// Returns whether document colors are enabled.
+ pub fn use_document_colors(&self) -> bool {
+ true
+ }
+
+ /// Returns the default background color.
+ pub fn default_background_color(&self) -> RGBA {
+ RGBA::new(255, 255, 255, 255)
+ }
+
+ /// Returns the default color color.
+ pub fn default_color(&self) -> RGBA {
+ RGBA::new(0, 0, 0, 255)
+ }
+
+ /// Returns safe area insets
+ pub fn safe_area_insets(&self) -> SideOffsets2D<f32, CSSPixel> {
+ SideOffsets2D::zero()
+ }
+}
+
+/// https://drafts.csswg.org/mediaqueries-4/#width
+fn eval_width(
+ device: &Device,
+ value: Option<CSSPixelLength>,
+ range_or_operator: Option<RangeOrOperator>,
+) -> bool {
+ RangeOrOperator::evaluate(
+ range_or_operator,
+ value.map(Au::from),
+ device.au_viewport_size().width,
+ )
+}
+
+#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
+#[repr(u8)]
+enum Scan {
+ Progressive,
+ Interlace,
+}
+
+/// https://drafts.csswg.org/mediaqueries-4/#scan
+fn eval_scan(_: &Device, _: Option<Scan>) -> bool {
+ // Since we doesn't support the 'tv' media type, the 'scan' feature never
+ // matches.
+ false
+}
+
+lazy_static! {
+ /// A list with all the media features that Servo supports.
+ pub static ref MEDIA_FEATURES: [MediaFeatureDescription; 2] = [
+ feature!(
+ atom!("width"),
+ AllowsRanges::Yes,
+ Evaluator::Length(eval_width),
+ ParsingRequirements::empty(),
+ ),
+ feature!(
+ atom!("scan"),
+ AllowsRanges::No,
+ keyword_evaluator!(eval_scan, Scan),
+ ParsingRequirements::empty(),
+ ),
+ ];
+}
diff --git a/servo/components/style/servo/mod.rs b/servo/components/style/servo/mod.rs
new file mode 100644
index 0000000000..6502d28727
--- /dev/null
+++ b/servo/components/style/servo/mod.rs
@@ -0,0 +1,12 @@
+/* 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/. */
+
+//! Servo-specific bits of the style system.
+//!
+//! These get compiled out on a Gecko build.
+
+pub mod media_queries;
+pub mod restyle_damage;
+pub mod selector_parser;
+pub mod url;
diff --git a/servo/components/style/servo/restyle_damage.rs b/servo/components/style/servo/restyle_damage.rs
new file mode 100644
index 0000000000..fe17fa6198
--- /dev/null
+++ b/servo/components/style/servo/restyle_damage.rs
@@ -0,0 +1,268 @@
+/* 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/. */
+
+//! The restyle damage is a hint that tells layout which kind of operations may
+//! be needed in presence of incremental style changes.
+
+use crate::computed_values::display::T as Display;
+use crate::matching::{StyleChange, StyleDifference};
+use crate::properties::ComputedValues;
+use std::fmt;
+
+bitflags! {
+ /// Individual layout actions that may be necessary after restyling.
+ pub struct ServoRestyleDamage: u8 {
+ /// Repaint the node itself.
+ ///
+ /// Currently unused; need to decide how this propagates.
+ const REPAINT = 0x01;
+
+ /// The stacking-context-relative position of this node or its
+ /// descendants has changed.
+ ///
+ /// Propagates both up and down the flow tree.
+ const REPOSITION = 0x02;
+
+ /// Recompute the overflow regions (bounding box of object and all descendants).
+ ///
+ /// Propagates down the flow tree because the computation is bottom-up.
+ const STORE_OVERFLOW = 0x04;
+
+ /// Recompute intrinsic inline_sizes (minimum and preferred).
+ ///
+ /// Propagates down the flow tree because the computation is.
+ /// bottom-up.
+ const BUBBLE_ISIZES = 0x08;
+
+ /// Recompute actual inline-sizes and block-sizes, only taking
+ /// out-of-flow children into account.
+ ///
+ /// Propagates up the flow tree because the computation is top-down.
+ const REFLOW_OUT_OF_FLOW = 0x10;
+
+ /// Recompute actual inline_sizes and block_sizes.
+ ///
+ /// Propagates up the flow tree because the computation is top-down.
+ const REFLOW = 0x20;
+
+ /// Re-resolve generated content.
+ ///
+ /// Propagates up the flow tree because the computation is inorder.
+ const RESOLVE_GENERATED_CONTENT = 0x40;
+
+ /// The entire flow needs to be reconstructed.
+ const RECONSTRUCT_FLOW = 0x80;
+ }
+}
+
+malloc_size_of_is_0!(ServoRestyleDamage);
+
+impl ServoRestyleDamage {
+ /// Compute the `StyleDifference` (including the appropriate restyle damage)
+ /// for a given style change between `old` and `new`.
+ pub fn compute_style_difference(old: &ComputedValues, new: &ComputedValues) -> StyleDifference {
+ let damage = compute_damage(old, new);
+ let change = if damage.is_empty() {
+ StyleChange::Unchanged
+ } else {
+ // FIXME(emilio): Differentiate between reset and inherited
+ // properties here, and set `reset_only` appropriately so the
+ // optimization to skip the cascade in those cases applies.
+ StyleChange::Changed { reset_only: false }
+ };
+ StyleDifference { damage, change }
+ }
+
+ /// Returns a bitmask that represents a flow that needs to be rebuilt and
+ /// reflowed.
+ ///
+ /// FIXME(bholley): Do we ever actually need this? Shouldn't
+ /// RECONSTRUCT_FLOW imply everything else?
+ pub fn rebuild_and_reflow() -> ServoRestyleDamage {
+ ServoRestyleDamage::REPAINT |
+ ServoRestyleDamage::REPOSITION |
+ ServoRestyleDamage::STORE_OVERFLOW |
+ ServoRestyleDamage::BUBBLE_ISIZES |
+ ServoRestyleDamage::REFLOW_OUT_OF_FLOW |
+ ServoRestyleDamage::REFLOW |
+ ServoRestyleDamage::RECONSTRUCT_FLOW
+ }
+
+ /// Returns a bitmask indicating that the frame needs to be reconstructed.
+ pub fn reconstruct() -> ServoRestyleDamage {
+ ServoRestyleDamage::RECONSTRUCT_FLOW
+ }
+
+ /// Supposing a flow has the given `position` property and this damage,
+ /// returns the damage that we should add to the *parent* of this flow.
+ pub fn damage_for_parent(self, child_is_absolutely_positioned: bool) -> ServoRestyleDamage {
+ if child_is_absolutely_positioned {
+ self & (ServoRestyleDamage::REPAINT |
+ ServoRestyleDamage::REPOSITION |
+ ServoRestyleDamage::STORE_OVERFLOW |
+ ServoRestyleDamage::REFLOW_OUT_OF_FLOW |
+ ServoRestyleDamage::RESOLVE_GENERATED_CONTENT)
+ } else {
+ self & (ServoRestyleDamage::REPAINT |
+ ServoRestyleDamage::REPOSITION |
+ ServoRestyleDamage::STORE_OVERFLOW |
+ ServoRestyleDamage::REFLOW |
+ ServoRestyleDamage::REFLOW_OUT_OF_FLOW |
+ ServoRestyleDamage::RESOLVE_GENERATED_CONTENT)
+ }
+ }
+
+ /// Supposing the *parent* of a flow with the given `position` property has
+ /// this damage, returns the damage that we should add to this flow.
+ pub fn damage_for_child(
+ self,
+ parent_is_absolutely_positioned: bool,
+ child_is_absolutely_positioned: bool,
+ ) -> ServoRestyleDamage {
+ match (
+ parent_is_absolutely_positioned,
+ child_is_absolutely_positioned,
+ ) {
+ (false, true) => {
+ // Absolute children are out-of-flow and therefore insulated from changes.
+ //
+ // FIXME(pcwalton): Au contraire, if the containing block dimensions change!
+ self & (ServoRestyleDamage::REPAINT | ServoRestyleDamage::REPOSITION)
+ },
+ (true, false) => {
+ // Changing the position of an absolutely-positioned block requires us to reflow
+ // its kids.
+ if self.contains(ServoRestyleDamage::REFLOW_OUT_OF_FLOW) {
+ self | ServoRestyleDamage::REFLOW
+ } else {
+ self
+ }
+ },
+ _ => {
+ // TODO(pcwalton): Take floatedness into account.
+ self & (ServoRestyleDamage::REPAINT |
+ ServoRestyleDamage::REPOSITION |
+ ServoRestyleDamage::REFLOW)
+ },
+ }
+ }
+}
+
+impl Default for ServoRestyleDamage {
+ fn default() -> Self {
+ Self::empty()
+ }
+}
+
+impl fmt::Display for ServoRestyleDamage {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ let mut first_elem = true;
+
+ let to_iter = [
+ (ServoRestyleDamage::REPAINT, "Repaint"),
+ (ServoRestyleDamage::REPOSITION, "Reposition"),
+ (ServoRestyleDamage::STORE_OVERFLOW, "StoreOverflow"),
+ (ServoRestyleDamage::BUBBLE_ISIZES, "BubbleISizes"),
+ (ServoRestyleDamage::REFLOW_OUT_OF_FLOW, "ReflowOutOfFlow"),
+ (ServoRestyleDamage::REFLOW, "Reflow"),
+ (
+ ServoRestyleDamage::RESOLVE_GENERATED_CONTENT,
+ "ResolveGeneratedContent",
+ ),
+ (ServoRestyleDamage::RECONSTRUCT_FLOW, "ReconstructFlow"),
+ ];
+
+ for &(damage, damage_str) in &to_iter {
+ if self.contains(damage) {
+ if !first_elem {
+ write!(f, " | ")?;
+ }
+ write!(f, "{}", damage_str)?;
+ first_elem = false;
+ }
+ }
+
+ if first_elem {
+ write!(f, "NoDamage")?;
+ }
+
+ Ok(())
+ }
+}
+
+fn compute_damage(old: &ComputedValues, new: &ComputedValues) -> ServoRestyleDamage {
+ let mut damage = ServoRestyleDamage::empty();
+
+ // This should check every CSS property, as enumerated in the fields of
+ // https://doc.servo.org/style/properties/struct.ComputedValues.html
+
+ // This uses short-circuiting boolean OR for its side effects and ignores the result.
+ let _ = restyle_damage_rebuild_and_reflow!(
+ old,
+ new,
+ damage,
+ [
+ ServoRestyleDamage::REPAINT,
+ ServoRestyleDamage::REPOSITION,
+ ServoRestyleDamage::STORE_OVERFLOW,
+ ServoRestyleDamage::BUBBLE_ISIZES,
+ ServoRestyleDamage::REFLOW_OUT_OF_FLOW,
+ ServoRestyleDamage::REFLOW,
+ ServoRestyleDamage::RECONSTRUCT_FLOW
+ ]
+ ) || (new.get_box().display == Display::Inline &&
+ restyle_damage_rebuild_and_reflow_inline!(
+ old,
+ new,
+ damage,
+ [
+ ServoRestyleDamage::REPAINT,
+ ServoRestyleDamage::REPOSITION,
+ ServoRestyleDamage::STORE_OVERFLOW,
+ ServoRestyleDamage::BUBBLE_ISIZES,
+ ServoRestyleDamage::REFLOW_OUT_OF_FLOW,
+ ServoRestyleDamage::REFLOW,
+ ServoRestyleDamage::RECONSTRUCT_FLOW
+ ]
+ )) ||
+ restyle_damage_reflow!(
+ old,
+ new,
+ damage,
+ [
+ ServoRestyleDamage::REPAINT,
+ ServoRestyleDamage::REPOSITION,
+ ServoRestyleDamage::STORE_OVERFLOW,
+ ServoRestyleDamage::BUBBLE_ISIZES,
+ ServoRestyleDamage::REFLOW_OUT_OF_FLOW,
+ ServoRestyleDamage::REFLOW
+ ]
+ ) ||
+ restyle_damage_reflow_out_of_flow!(
+ old,
+ new,
+ damage,
+ [
+ ServoRestyleDamage::REPAINT,
+ ServoRestyleDamage::REPOSITION,
+ ServoRestyleDamage::STORE_OVERFLOW,
+ ServoRestyleDamage::REFLOW_OUT_OF_FLOW
+ ]
+ ) ||
+ restyle_damage_repaint!(old, new, damage, [ServoRestyleDamage::REPAINT]);
+
+ // Paint worklets may depend on custom properties,
+ // so if they have changed we should repaint.
+ if !old.custom_properties_equal(new) {
+ damage.insert(ServoRestyleDamage::REPAINT);
+ }
+
+ // If the layer requirements of this flow have changed due to the value
+ // of the transform, then reflow is required to rebuild the layers.
+ if old.transform_requires_layer() != new.transform_requires_layer() {
+ damage.insert(ServoRestyleDamage::rebuild_and_reflow());
+ }
+
+ damage
+}
diff --git a/servo/components/style/servo/selector_parser.rs b/servo/components/style/servo/selector_parser.rs
new file mode 100644
index 0000000000..b20f1754a0
--- /dev/null
+++ b/servo/components/style/servo/selector_parser.rs
@@ -0,0 +1,806 @@
+/* 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/. */
+
+#![deny(missing_docs)]
+
+//! Servo's selector parser.
+
+use crate::attr::{AttrIdentifier, AttrValue};
+use crate::dom::{OpaqueNode, TElement, TNode};
+use crate::invalidation::element::document_state::InvalidationMatchingData;
+use crate::invalidation::element::element_wrapper::ElementSnapshot;
+use crate::properties::longhands::display::computed_value::T as Display;
+use crate::properties::{ComputedValues, PropertyFlags};
+use crate::selector_parser::AttrValue as SelectorAttrValue;
+use crate::selector_parser::{PseudoElementCascadeType, SelectorParser};
+use crate::values::{AtomIdent, AtomString};
+use crate::{Atom, CaseSensitivityExt, LocalName, Namespace, Prefix};
+use cssparser::{serialize_identifier, CowRcStr, Parser as CssParser, SourceLocation, ToCss};
+use dom::{DocumentState, ElementState};
+use fxhash::FxHashMap;
+use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint};
+use selectors::parser::SelectorParseErrorKind;
+use selectors::visitor::SelectorVisitor;
+use std::fmt;
+use std::mem;
+use std::ops::{Deref, DerefMut};
+use style_traits::{ParseError, StyleParseErrorKind};
+
+/// A pseudo-element, both public and private.
+///
+/// NB: If you add to this list, be sure to update `each_simple_pseudo_element` too.
+#[derive(
+ Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, ToShmem,
+)]
+#[allow(missing_docs)]
+#[repr(usize)]
+pub enum PseudoElement {
+ // Eager pseudos. Keep these first so that eager_index() works.
+ After = 0,
+ Before,
+ Selection,
+ // If/when :first-letter is added, update is_first_letter accordingly.
+
+ // If/when :first-line is added, update is_first_line accordingly.
+
+ // If/when ::first-letter, ::first-line, or ::placeholder are added, adjust
+ // our property_restriction implementation to do property filtering for
+ // them. Also, make sure the UA sheet has the !important rules some of the
+ // APPLIES_TO_PLACEHOLDER properties expect!
+
+ // Non-eager pseudos.
+ DetailsSummary,
+ DetailsContent,
+ ServoText,
+ ServoInputText,
+ ServoTableWrapper,
+ ServoAnonymousTableWrapper,
+ ServoAnonymousTable,
+ ServoAnonymousTableRow,
+ ServoAnonymousTableCell,
+ ServoAnonymousBlock,
+ ServoInlineBlockWrapper,
+ ServoInlineAbsolute,
+}
+
+/// The count of all pseudo-elements.
+pub const PSEUDO_COUNT: usize = PseudoElement::ServoInlineAbsolute as usize + 1;
+
+impl ::selectors::parser::PseudoElement for PseudoElement {
+ type Impl = SelectorImpl;
+}
+
+impl ToCss for PseudoElement {
+ fn to_css<W>(&self, dest: &mut W) -> fmt::Result
+ where
+ W: fmt::Write,
+ {
+ use self::PseudoElement::*;
+ dest.write_str(match *self {
+ After => "::after",
+ Before => "::before",
+ Selection => "::selection",
+ DetailsSummary => "::-servo-details-summary",
+ DetailsContent => "::-servo-details-content",
+ ServoText => "::-servo-text",
+ ServoInputText => "::-servo-input-text",
+ ServoTableWrapper => "::-servo-table-wrapper",
+ ServoAnonymousTableWrapper => "::-servo-anonymous-table-wrapper",
+ ServoAnonymousTable => "::-servo-anonymous-table",
+ ServoAnonymousTableRow => "::-servo-anonymous-table-row",
+ ServoAnonymousTableCell => "::-servo-anonymous-table-cell",
+ ServoAnonymousBlock => "::-servo-anonymous-block",
+ ServoInlineBlockWrapper => "::-servo-inline-block-wrapper",
+ ServoInlineAbsolute => "::-servo-inline-absolute",
+ })
+ }
+}
+
+/// The number of eager pseudo-elements. Keep this in sync with cascade_type.
+pub const EAGER_PSEUDO_COUNT: usize = 3;
+
+impl PseudoElement {
+ /// Gets the canonical index of this eagerly-cascaded pseudo-element.
+ #[inline]
+ pub fn eager_index(&self) -> usize {
+ debug_assert!(self.is_eager());
+ self.clone() as usize
+ }
+
+ /// An index for this pseudo-element to be indexed in an enumerated array.
+ #[inline]
+ pub fn index(&self) -> usize {
+ self.clone() as usize
+ }
+
+ /// An array of `None`, one per pseudo-element.
+ pub fn pseudo_none_array<T>() -> [Option<T>; PSEUDO_COUNT] {
+ Default::default()
+ }
+
+ /// Creates a pseudo-element from an eager index.
+ #[inline]
+ pub fn from_eager_index(i: usize) -> Self {
+ assert!(i < EAGER_PSEUDO_COUNT);
+ let result: PseudoElement = unsafe { mem::transmute(i) };
+ debug_assert!(result.is_eager());
+ result
+ }
+
+ /// Whether the current pseudo element is ::before or ::after.
+ #[inline]
+ pub fn is_before_or_after(&self) -> bool {
+ self.is_before() || self.is_after()
+ }
+
+ /// Whether this is an unknown ::-webkit- pseudo-element.
+ #[inline]
+ pub fn is_unknown_webkit_pseudo_element(&self) -> bool {
+ false
+ }
+
+ /// Whether this pseudo-element is the ::marker pseudo.
+ #[inline]
+ pub fn is_marker(&self) -> bool {
+ false
+ }
+
+ /// Whether this pseudo-element is the ::selection pseudo.
+ #[inline]
+ pub fn is_selection(&self) -> bool {
+ *self == PseudoElement::Selection
+ }
+
+ /// 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 the current pseudo element is :first-letter
+ #[inline]
+ pub fn is_first_letter(&self) -> bool {
+ false
+ }
+
+ /// Whether the current pseudo element is :first-line
+ #[inline]
+ pub fn is_first_line(&self) -> bool {
+ false
+ }
+
+ /// Whether this pseudo-element is the ::-moz-color-swatch pseudo.
+ #[inline]
+ pub fn is_color_swatch(&self) -> bool {
+ false
+ }
+
+ /// Whether this pseudo-element is eagerly-cascaded.
+ #[inline]
+ pub fn is_eager(&self) -> bool {
+ self.cascade_type() == PseudoElementCascadeType::Eager
+ }
+
+ /// Whether this pseudo-element is lazily-cascaded.
+ #[inline]
+ pub fn is_lazy(&self) -> bool {
+ self.cascade_type() == PseudoElementCascadeType::Lazy
+ }
+
+ /// Whether this pseudo-element is for an anonymous box.
+ pub fn is_anon_box(&self) -> bool {
+ self.is_precomputed()
+ }
+
+ /// Whether this pseudo-element skips flex/grid container display-based
+ /// fixup.
+ #[inline]
+ pub fn skip_item_display_fixup(&self) -> bool {
+ !self.is_before_or_after()
+ }
+
+ /// Whether this pseudo-element is precomputed.
+ #[inline]
+ pub fn is_precomputed(&self) -> bool {
+ self.cascade_type() == PseudoElementCascadeType::Precomputed
+ }
+
+ /// Returns which kind of cascade type has this pseudo.
+ ///
+ /// For more info on cascade types, see docs/components/style.md
+ ///
+ /// Note: Keep this in sync with EAGER_PSEUDO_COUNT.
+ #[inline]
+ pub fn cascade_type(&self) -> PseudoElementCascadeType {
+ match *self {
+ PseudoElement::After | PseudoElement::Before | PseudoElement::Selection => {
+ PseudoElementCascadeType::Eager
+ },
+ PseudoElement::DetailsSummary => PseudoElementCascadeType::Lazy,
+ PseudoElement::DetailsContent |
+ PseudoElement::ServoText |
+ PseudoElement::ServoInputText |
+ PseudoElement::ServoTableWrapper |
+ PseudoElement::ServoAnonymousTableWrapper |
+ PseudoElement::ServoAnonymousTable |
+ PseudoElement::ServoAnonymousTableRow |
+ PseudoElement::ServoAnonymousTableCell |
+ PseudoElement::ServoAnonymousBlock |
+ PseudoElement::ServoInlineBlockWrapper |
+ PseudoElement::ServoInlineAbsolute => PseudoElementCascadeType::Precomputed,
+ }
+ }
+
+ /// Covert non-canonical pseudo-element to canonical one, and keep a
+ /// canonical one as it is.
+ pub fn canonical(&self) -> PseudoElement {
+ self.clone()
+ }
+
+ /// Stub, only Gecko needs this
+ pub fn pseudo_info(&self) {
+ ()
+ }
+
+ /// Property flag that properties must have to apply to this pseudo-element.
+ #[inline]
+ pub fn property_restriction(&self) -> Option<PropertyFlags> {
+ None
+ }
+
+ /// Whether this pseudo-element should actually exist if it has
+ /// the given styles.
+ pub fn should_exist(&self, style: &ComputedValues) -> bool {
+ let display = style.get_box().clone_display();
+ if display == Display::None {
+ return false;
+ }
+ if self.is_before_or_after() && style.ineffective_content_property() {
+ return false;
+ }
+
+ true
+ }
+}
+
+/// The type used for storing `:lang` arguments.
+pub type Lang = Box<str>;
+
+/// A non tree-structural pseudo-class.
+/// See https://drafts.csswg.org/selectors-4/#structural-pseudos
+#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToShmem)]
+#[allow(missing_docs)]
+pub enum NonTSPseudoClass {
+ Active,
+ AnyLink,
+ Checked,
+ Defined,
+ Disabled,
+ Enabled,
+ Focus,
+ Fullscreen,
+ Hover,
+ Indeterminate,
+ Lang(Lang),
+ Link,
+ PlaceholderShown,
+ ReadWrite,
+ ReadOnly,
+ ServoNonZeroBorder,
+ Target,
+ Visited,
+}
+
+impl ::selectors::parser::NonTSPseudoClass for NonTSPseudoClass {
+ type Impl = SelectorImpl;
+
+ #[inline]
+ fn is_active_or_hover(&self) -> bool {
+ matches!(*self, NonTSPseudoClass::Active | NonTSPseudoClass::Hover)
+ }
+
+ #[inline]
+ fn is_user_action_state(&self) -> bool {
+ matches!(
+ *self,
+ NonTSPseudoClass::Active | NonTSPseudoClass::Hover | NonTSPseudoClass::Focus
+ )
+ }
+
+ fn visit<V>(&self, _: &mut V) -> bool
+ where
+ V: SelectorVisitor<Impl = Self::Impl>,
+ {
+ true
+ }
+}
+
+impl ToCss for NonTSPseudoClass {
+ fn to_css<W>(&self, dest: &mut W) -> fmt::Result
+ where
+ W: fmt::Write,
+ {
+ use self::NonTSPseudoClass::*;
+ if let Lang(ref lang) = *self {
+ dest.write_str(":lang(")?;
+ serialize_identifier(lang, dest)?;
+ return dest.write_char(')');
+ }
+
+ dest.write_str(match *self {
+ Active => ":active",
+ AnyLink => ":any-link",
+ Checked => ":checked",
+ Defined => ":defined",
+ Disabled => ":disabled",
+ Enabled => ":enabled",
+ Focus => ":focus",
+ Fullscreen => ":fullscreen",
+ Hover => ":hover",
+ Indeterminate => ":indeterminate",
+ Link => ":link",
+ PlaceholderShown => ":placeholder-shown",
+ ReadWrite => ":read-write",
+ ReadOnly => ":read-only",
+ ServoNonZeroBorder => ":-servo-nonzero-border",
+ Target => ":target",
+ Visited => ":visited",
+ Lang(_) => unreachable!(),
+ })
+ }
+}
+
+impl NonTSPseudoClass {
+ /// Gets a given state flag for this pseudo-class. This is used to do
+ /// selector matching, and it's set from the DOM.
+ pub fn state_flag(&self) -> ElementState {
+ use self::NonTSPseudoClass::*;
+ match *self {
+ Active => ElementState::IN_ACTIVE_STATE,
+ Focus => ElementState::IN_FOCUS_STATE,
+ Fullscreen => ElementState::IN_FULLSCREEN_STATE,
+ Hover => ElementState::IN_HOVER_STATE,
+ Defined => ElementState::IN_DEFINED_STATE,
+ Enabled => ElementState::IN_ENABLED_STATE,
+ Disabled => ElementState::IN_DISABLED_STATE,
+ Checked => ElementState::IN_CHECKED_STATE,
+ Indeterminate => ElementState::IN_INDETERMINATE_STATE,
+ ReadOnly | ReadWrite => ElementState::IN_READWRITE_STATE,
+ PlaceholderShown => ElementState::IN_PLACEHOLDER_SHOWN_STATE,
+ Target => ElementState::IN_TARGET_STATE,
+
+ AnyLink | Lang(_) | Link | Visited | ServoNonZeroBorder => ElementState::empty(),
+ }
+ }
+
+ /// Get the document state flag associated with a pseudo-class, if any.
+ pub fn document_state_flag(&self) -> DocumentState {
+ 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()
+ }
+}
+
+/// The abstract struct we implement the selector parser implementation on top
+/// of.
+#[derive(Clone, Debug, PartialEq)]
+#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
+pub struct SelectorImpl;
+
+impl ::selectors::SelectorImpl for SelectorImpl {
+ type PseudoElement = PseudoElement;
+ type NonTSPseudoClass = NonTSPseudoClass;
+
+ type ExtraMatchingData = InvalidationMatchingData;
+ type AttrValue = String;
+ type Identifier = Atom;
+ type ClassName = Atom;
+ type PartName = Atom;
+ type LocalName = LocalName;
+ type NamespacePrefix = Prefix;
+ type NamespaceUrl = Namespace;
+ type BorrowedLocalName = LocalName;
+ type BorrowedNamespaceUrl = Namespace;
+}
+
+impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> {
+ type Impl = SelectorImpl;
+ type Error = StyleParseErrorKind<'i>;
+
+ fn parse_non_ts_pseudo_class(
+ &self,
+ location: SourceLocation,
+ name: CowRcStr<'i>,
+ ) -> Result<NonTSPseudoClass, ParseError<'i>> {
+ use self::NonTSPseudoClass::*;
+ let pseudo_class = match_ignore_ascii_case! { &name,
+ "active" => Active,
+ "any-link" => AnyLink,
+ "checked" => Checked,
+ "defined" => Defined,
+ "disabled" => Disabled,
+ "enabled" => Enabled,
+ "focus" => Focus,
+ "fullscreen" => Fullscreen,
+ "hover" => Hover,
+ "indeterminate" => Indeterminate,
+ "-moz-inert" => MozInert,
+ "link" => Link,
+ "placeholder-shown" => PlaceholderShown,
+ "read-write" => ReadWrite,
+ "read-only" => ReadOnly,
+ "target" => Target,
+ "visited" => Visited,
+ "-servo-nonzero-border" => {
+ if !self.in_user_agent_stylesheet() {
+ return Err(location.new_custom_error(
+ SelectorParseErrorKind::UnexpectedIdent("-servo-nonzero-border".into())
+ ))
+ }
+ ServoNonZeroBorder
+ },
+ _ => return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))),
+ };
+
+ Ok(pseudo_class)
+ }
+
+ fn parse_non_ts_functional_pseudo_class<'t>(
+ &self,
+ name: CowRcStr<'i>,
+ parser: &mut CssParser<'i, 't>,
+ ) -> Result<NonTSPseudoClass, ParseError<'i>> {
+ use self::NonTSPseudoClass::*;
+ let pseudo_class = match_ignore_ascii_case! { &name,
+ "lang" => {
+ Lang(parser.expect_ident_or_string()?.as_ref().into())
+ },
+ _ => return Err(parser.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))),
+ };
+
+ Ok(pseudo_class)
+ }
+
+ fn parse_pseudo_element(
+ &self,
+ location: SourceLocation,
+ name: CowRcStr<'i>,
+ ) -> Result<PseudoElement, ParseError<'i>> {
+ use self::PseudoElement::*;
+ let pseudo_element = match_ignore_ascii_case! { &name,
+ "before" => Before,
+ "after" => After,
+ "selection" => Selection,
+ "-servo-details-summary" => {
+ if !self.in_user_agent_stylesheet() {
+ return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
+ }
+ DetailsSummary
+ },
+ "-servo-details-content" => {
+ if !self.in_user_agent_stylesheet() {
+ return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
+ }
+ DetailsContent
+ },
+ "-servo-text" => {
+ if !self.in_user_agent_stylesheet() {
+ return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
+ }
+ ServoText
+ },
+ "-servo-input-text" => {
+ if !self.in_user_agent_stylesheet() {
+ return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
+ }
+ ServoInputText
+ },
+ "-servo-table-wrapper" => {
+ if !self.in_user_agent_stylesheet() {
+ return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
+ }
+ ServoTableWrapper
+ },
+ "-servo-anonymous-table-wrapper" => {
+ if !self.in_user_agent_stylesheet() {
+ return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
+ }
+ ServoAnonymousTableWrapper
+ },
+ "-servo-anonymous-table" => {
+ if !self.in_user_agent_stylesheet() {
+ return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
+ }
+ ServoAnonymousTable
+ },
+ "-servo-anonymous-table-row" => {
+ if !self.in_user_agent_stylesheet() {
+ return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
+ }
+ ServoAnonymousTableRow
+ },
+ "-servo-anonymous-table-cell" => {
+ if !self.in_user_agent_stylesheet() {
+ return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
+ }
+ ServoAnonymousTableCell
+ },
+ "-servo-anonymous-block" => {
+ if !self.in_user_agent_stylesheet() {
+ return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
+ }
+ ServoAnonymousBlock
+ },
+ "-servo-inline-block-wrapper" => {
+ if !self.in_user_agent_stylesheet() {
+ return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
+ }
+ ServoInlineBlockWrapper
+ },
+ "-servo-inline-absolute" => {
+ if !self.in_user_agent_stylesheet() {
+ return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
+ }
+ ServoInlineAbsolute
+ },
+ _ => return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
+
+ };
+
+ Ok(pseudo_element)
+ }
+
+ fn default_namespace(&self) -> Option<Namespace> {
+ self.namespaces.default.as_ref().map(|ns| ns.clone())
+ }
+
+ fn namespace_for_prefix(&self, prefix: &Prefix) -> 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 i in 0..EAGER_PSEUDO_COUNT {
+ fun(PseudoElement::from_eager_index(i));
+ }
+ }
+}
+
+/// A map from elements to snapshots for the Servo style back-end.
+#[derive(Debug)]
+pub struct SnapshotMap(FxHashMap<OpaqueNode, ServoElementSnapshot>);
+
+impl SnapshotMap {
+ /// Create a new empty `SnapshotMap`.
+ pub fn new() -> Self {
+ SnapshotMap(FxHashMap::default())
+ }
+
+ /// Get a snapshot given an element.
+ pub fn get<T: TElement>(&self, el: &T) -> Option<&ServoElementSnapshot> {
+ self.0.get(&el.as_node().opaque())
+ }
+}
+
+impl Deref for SnapshotMap {
+ type Target = FxHashMap<OpaqueNode, ServoElementSnapshot>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl DerefMut for SnapshotMap {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.0
+ }
+}
+
+/// Servo's version of an element snapshot.
+#[derive(Debug, Default, MallocSizeOf)]
+pub struct ServoElementSnapshot {
+ /// The stored state of the element.
+ pub state: Option<ElementState>,
+ /// The set of stored attributes and its values.
+ pub attrs: Option<Vec<(AttrIdentifier, AttrValue)>>,
+ /// The set of changed attributes and its values.
+ pub changed_attrs: Vec<LocalName>,
+ /// Whether the class attribute changed or not.
+ pub class_changed: bool,
+ /// Whether the id attribute changed or not.
+ pub id_changed: bool,
+ /// Whether other attributes other than id or class changed or not.
+ pub other_attributes_changed: bool,
+}
+
+impl ServoElementSnapshot {
+ /// Create an empty element snapshot.
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// Returns whether the id attribute changed or not.
+ pub fn id_changed(&self) -> bool {
+ self.id_changed
+ }
+
+ /// Returns whether the class attribute changed or not.
+ pub fn class_changed(&self) -> bool {
+ self.class_changed
+ }
+
+ /// Returns whether other attributes other than id or class changed or not.
+ pub fn other_attr_changed(&self) -> bool {
+ self.other_attributes_changed
+ }
+
+ fn get_attr(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue> {
+ self.attrs
+ .as_ref()
+ .unwrap()
+ .iter()
+ .find(|&&(ref ident, _)| ident.local_name == *name && ident.namespace == *namespace)
+ .map(|&(_, ref v)| v)
+ }
+
+ /// Executes the callback once for each attribute that changed.
+ #[inline]
+ pub fn each_attr_changed<F>(&self, mut callback: F)
+ where
+ F: FnMut(&LocalName),
+ {
+ for name in &self.changed_attrs {
+ callback(name)
+ }
+ }
+
+ fn any_attr_ignore_ns<F>(&self, name: &LocalName, mut f: F) -> bool
+ where
+ F: FnMut(&AttrValue) -> bool,
+ {
+ self.attrs
+ .as_ref()
+ .unwrap()
+ .iter()
+ .any(|&(ref ident, ref v)| ident.local_name == *name && f(v))
+ }
+}
+
+impl ElementSnapshot for ServoElementSnapshot {
+ fn state(&self) -> Option<ElementState> {
+ self.state.clone()
+ }
+
+ fn has_attrs(&self) -> bool {
+ self.attrs.is_some()
+ }
+
+ fn id_attr(&self) -> Option<&Atom> {
+ self.get_attr(&ns!(), &local_name!("id"))
+ .map(|v| v.as_atom())
+ }
+
+ fn is_part(&self, _name: &AtomIdent) -> bool {
+ false
+ }
+
+ fn imported_part(&self, _: &AtomIdent) -> Option<AtomIdent> {
+ None
+ }
+
+ fn has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool {
+ self.get_attr(&ns!(), &local_name!("class"))
+ .map_or(false, |v| {
+ v.as_tokens()
+ .iter()
+ .any(|atom| case_sensitivity.eq_atom(atom, name))
+ })
+ }
+
+ fn each_class<F>(&self, mut callback: F)
+ where
+ F: FnMut(&AtomIdent),
+ {
+ if let Some(v) = self.get_attr(&ns!(), &local_name!("class")) {
+ for class in v.as_tokens() {
+ callback(AtomIdent::cast(class));
+ }
+ }
+ }
+
+ fn lang_attr(&self) -> Option<SelectorAttrValue> {
+ self.get_attr(&ns!(xml), &local_name!("lang"))
+ .or_else(|| self.get_attr(&ns!(), &local_name!("lang")))
+ .map(|v| SelectorAttrValue::from(v as &str))
+ }
+}
+
+impl ServoElementSnapshot {
+ /// selectors::Element::attr_matches
+ pub fn attr_matches(
+ &self,
+ ns: &NamespaceConstraint<&Namespace>,
+ local_name: &LocalName,
+ operation: &AttrSelectorOperation<&AtomString>,
+ ) -> bool {
+ match *ns {
+ NamespaceConstraint::Specific(ref ns) => self
+ .get_attr(ns, local_name)
+ .map_or(false, |value| value.eval_selector(operation)),
+ NamespaceConstraint::Any => {
+ self.any_attr_ignore_ns(local_name, |value| value.eval_selector(operation))
+ },
+ }
+ }
+}
+
+/// Returns whether the language is matched, as defined by
+/// [RFC 4647](https://tools.ietf.org/html/rfc4647#section-3.3.2).
+pub fn extended_filtering(tag: &str, range: &str) -> bool {
+ range.split(',').any(|lang_range| {
+ // step 1
+ let mut range_subtags = lang_range.split('\x2d');
+ let mut tag_subtags = tag.split('\x2d');
+
+ // step 2
+ // Note: [Level-4 spec](https://drafts.csswg.org/selectors/#lang-pseudo) check for wild card
+ if let (Some(range_subtag), Some(tag_subtag)) = (range_subtags.next(), tag_subtags.next()) {
+ if !(range_subtag.eq_ignore_ascii_case(tag_subtag) ||
+ range_subtag.eq_ignore_ascii_case("*"))
+ {
+ return false;
+ }
+ }
+
+ let mut current_tag_subtag = tag_subtags.next();
+
+ // step 3
+ for range_subtag in range_subtags {
+ // step 3a
+ if range_subtag == "*" {
+ continue;
+ }
+ match current_tag_subtag.clone() {
+ Some(tag_subtag) => {
+ // step 3c
+ if range_subtag.eq_ignore_ascii_case(tag_subtag) {
+ current_tag_subtag = tag_subtags.next();
+ continue;
+ }
+ // step 3d
+ if tag_subtag.len() == 1 {
+ return false;
+ }
+ // else step 3e - continue with loop
+ current_tag_subtag = tag_subtags.next();
+ if current_tag_subtag.is_none() {
+ return false;
+ }
+ },
+ // step 3b
+ None => {
+ return false;
+ },
+ }
+ }
+ // step 4
+ true
+ })
+}
diff --git a/servo/components/style/servo/url.rs b/servo/components/style/servo/url.rs
new file mode 100644
index 0000000000..2186be7aab
--- /dev/null
+++ b/servo/components/style/servo/url.rs
@@ -0,0 +1,238 @@
+/* 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::parser::{Parse, ParserContext};
+use crate::stylesheets::CorsMode;
+use crate::values::computed::{Context, ToComputedValue};
+use cssparser::Parser;
+use servo_arc::Arc;
+use servo_url::ServoUrl;
+use std::fmt::{self, Write};
+use style_traits::{CssWriter, ParseError, ToCss};
+
+/// A CSS url() value for servo.
+///
+/// Servo eagerly resolves SpecifiedUrls, which it can then take advantage of
+/// when computing values. In contrast, Gecko uses a different URL backend, so
+/// eagerly resolving with rust-url would be duplicated work.
+///
+/// However, this approach is still not necessarily optimal: See
+/// <https://bugzilla.mozilla.org/show_bug.cgi?id=1347435#c6>
+///
+/// TODO(emilio): This should be shrunk by making CssUrl a wrapper type of an
+/// arc, and keep the serialization in that Arc. See gecko/url.rs for example.
+#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize, SpecifiedValueInfo, ToShmem)]
+pub struct CssUrl {
+ /// The original URI. This might be optional since we may insert computed
+ /// values of images into the cascade directly, and we don't bother to
+ /// convert their serialization.
+ ///
+ /// Refcounted since cloning this should be cheap and data: uris can be
+ /// really large.
+ #[ignore_malloc_size_of = "Arc"]
+ original: Option<Arc<String>>,
+
+ /// The resolved value for the url, if valid.
+ resolved: Option<ServoUrl>,
+}
+
+impl CssUrl {
+ /// Try to parse a URL from a string value that is a valid CSS token for a
+ /// URL.
+ ///
+ /// FIXME(emilio): Should honor CorsMode.
+ pub fn parse_from_string(url: String, context: &ParserContext, _: CorsMode) -> Self {
+ let serialization = Arc::new(url);
+ let resolved = context.url_data.join(&serialization).ok();
+ CssUrl {
+ original: Some(serialization),
+ resolved: resolved,
+ }
+ }
+
+ /// Returns true if the URL is definitely invalid. For Servo URLs, we can
+ /// use its |resolved| status.
+ pub fn is_invalid(&self) -> bool {
+ self.resolved.is_none()
+ }
+
+ /// Returns true if this URL looks like a fragment.
+ /// See https://drafts.csswg.org/css-values/#local-urls
+ ///
+ /// Since Servo currently stores resolved URLs, this is hard to implement. We
+ /// either need to change servo to lazily resolve (like Gecko), or note this
+ /// information in the tokenizer.
+ pub fn is_fragment(&self) -> bool {
+ error!("Can't determine whether the url is a fragment.");
+ false
+ }
+
+ /// Returns the resolved url if it was valid.
+ pub fn url(&self) -> Option<&ServoUrl> {
+ self.resolved.as_ref()
+ }
+
+ /// Return the resolved url as string, or the empty string if it's invalid.
+ ///
+ /// TODO(emilio): Should we return the original one if needed?
+ pub fn as_str(&self) -> &str {
+ match self.resolved {
+ Some(ref url) => url.as_str(),
+ None => "",
+ }
+ }
+
+ /// Creates an already specified url value from an already resolved URL
+ /// for insertion in the cascade.
+ pub fn for_cascade(url: ServoUrl) -> Self {
+ CssUrl {
+ original: None,
+ resolved: Some(url),
+ }
+ }
+
+ /// Gets a new url from a string for unit tests.
+ pub fn new_for_testing(url: &str) -> Self {
+ CssUrl {
+ original: Some(Arc::new(url.into())),
+ resolved: ServoUrl::parse(url).ok(),
+ }
+ }
+
+ /// Parses a URL request and records that the corresponding request needs to
+ /// be CORS-enabled.
+ ///
+ /// This is only for shape images and masks in Gecko, thus unimplemented for
+ /// now so somebody notices when trying to do so.
+ pub 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,
+ ))
+ }
+}
+
+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 PartialEq for CssUrl {
+ fn eq(&self, other: &Self) -> bool {
+ // TODO(emilio): maybe we care about equality of the specified values if
+ // present? Seems not.
+ self.resolved == other.resolved
+ }
+}
+
+impl Eq for CssUrl {}
+
+impl ToCss for CssUrl {
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ let string = match self.original {
+ Some(ref original) => &**original,
+ None => match self.resolved {
+ Some(ref url) => url.as_str(),
+ // This can only happen if the url wasn't specified by the
+ // user *and* it's an invalid url that has been transformed
+ // back to specified value via the "uncompute" functionality.
+ None => "about:invalid",
+ },
+ };
+
+ dest.write_str("url(")?;
+ string.to_css(dest)?;
+ dest.write_char(')')
+ }
+}
+
+/// A specified url() value for servo.
+pub type SpecifiedUrl = CssUrl;
+
+impl ToComputedValue for SpecifiedUrl {
+ type ComputedValue = ComputedUrl;
+
+ // If we can't resolve the URL from the specified one, we fall back to the original
+ // but still return it as a ComputedUrl::Invalid
+ fn to_computed_value(&self, _: &Context) -> Self::ComputedValue {
+ match self.resolved {
+ Some(ref url) => ComputedUrl::Valid(url.clone()),
+ None => match self.original {
+ Some(ref url) => ComputedUrl::Invalid(url.clone()),
+ None => {
+ unreachable!("Found specified url with neither resolved or original URI!");
+ },
+ },
+ }
+ }
+
+ fn from_computed_value(computed: &ComputedUrl) -> Self {
+ match *computed {
+ ComputedUrl::Valid(ref url) => SpecifiedUrl {
+ original: None,
+ resolved: Some(url.clone()),
+ },
+ ComputedUrl::Invalid(ref url) => SpecifiedUrl {
+ original: Some(url.clone()),
+ resolved: None,
+ },
+ }
+ }
+}
+
+/// A specified image url() value for servo.
+pub type SpecifiedImageUrl = CssUrl;
+
+/// The computed value of a CSS `url()`, resolved relative to the stylesheet URL.
+#[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
+pub enum ComputedUrl {
+ /// The `url()` was invalid or it wasn't specified by the user.
+ Invalid(#[ignore_malloc_size_of = "Arc"] Arc<String>),
+ /// The resolved `url()` relative to the stylesheet URL.
+ Valid(ServoUrl),
+}
+
+impl ComputedUrl {
+ /// Returns the resolved url if it was valid.
+ pub fn url(&self) -> Option<&ServoUrl> {
+ match *self {
+ ComputedUrl::Valid(ref url) => Some(url),
+ _ => None,
+ }
+ }
+}
+
+impl ToCss for ComputedUrl {
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ let string = match *self {
+ ComputedUrl::Valid(ref url) => url.as_str(),
+ ComputedUrl::Invalid(ref invalid_string) => invalid_string,
+ };
+
+ dest.write_str("url(")?;
+ string.to_css(dest)?;
+ dest.write_char(')')
+ }
+}
+
+/// The computed value of a CSS `url()` for image.
+pub type ComputedImageUrl = ComputedUrl;