diff options
Diffstat (limited to 'servo/components/style/servo')
-rw-r--r-- | servo/components/style/servo/media_queries.rs | 232 | ||||
-rw-r--r-- | servo/components/style/servo/mod.rs | 12 | ||||
-rw-r--r-- | servo/components/style/servo/restyle_damage.rs | 268 | ||||
-rw-r--r-- | servo/components/style/servo/selector_parser.rs | 806 | ||||
-rw-r--r-- | servo/components/style/servo/url.rs | 238 |
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; |