diff --git a/components/style/context.rs b/components/style/context.rs index 997a3f37574..3a17f5d5b58 100644 --- a/components/style/context.rs +++ b/components/style/context.rs @@ -191,7 +191,7 @@ pub struct CascadeInputs { impl CascadeInputs { /// Construct inputs from previous cascade results, if any. - pub fn new_from_style(style: &Arc) -> Self { + pub fn new_from_style(style: &ComputedValues) -> Self { CascadeInputs { rules: style.rules.clone(), visited_rules: style.get_visited_style().and_then(|v| v.rules.clone()), diff --git a/components/style/gecko/data.rs b/components/style/gecko/data.rs index f845ec421c2..09e9fc511e1 100644 --- a/components/style/gecko/data.rs +++ b/components/style/gecko/data.rs @@ -204,6 +204,15 @@ impl PerDocumentStyleDataImpl { pub fn clear_stylist(&mut self) { self.stylist.clear(); } + + /// Returns whether visited links are enabled. + fn visited_links_enabled(&self) -> bool { + unsafe { bindings::Gecko_AreVisitedLinksEnabled() } + } + /// Returns whether visited styles are enabled. + pub fn visited_styles_enabled(&self) -> bool { + self.visited_links_enabled() && !self.is_private_browsing_enabled() + } } unsafe impl HasFFI for PerDocumentStyleData { diff --git a/components/style/gecko/generated/bindings.rs b/components/style/gecko/generated/bindings.rs index 5a8de33b06c..235ba7f42f1 100644 --- a/components/style/gecko/generated/bindings.rs +++ b/components/style/gecko/generated/bindings.rs @@ -2817,6 +2817,16 @@ extern "C" { set: RawServoStyleSetBorrowed) -> ServoStyleContextStrong; } +extern "C" { + pub fn Servo_ReparentStyle(style_to_reparent: ServoStyleContextBorrowed, + parent_style: ServoStyleContextBorrowed, + parent_style_ignoring_first_line: + ServoStyleContextBorrowed, + layout_parent_style: ServoStyleContextBorrowed, + element: RawGeckoElementBorrowedOrNull, + set: RawServoStyleSetBorrowed) + -> ServoStyleContextStrong; +} extern "C" { pub fn Servo_TraverseSubtree(root: RawGeckoElementBorrowed, set: RawServoStyleSetBorrowed, diff --git a/components/style/gecko/pseudo_element.rs b/components/style/gecko/pseudo_element.rs index cf37567252c..5b1cbfdf2a5 100644 --- a/components/style/gecko/pseudo_element.rs +++ b/components/style/gecko/pseudo_element.rs @@ -87,6 +87,12 @@ impl PseudoElement { *self == PseudoElement::FirstLetter } + /// Whether this pseudo-element is ::first-line. + #[inline] + pub fn is_first_line(&self) -> bool { + *self == PseudoElement::FirstLine + } + /// Whether this pseudo-element is ::-moz-fieldset-content. #[inline] pub fn is_fieldset_content(&self) -> bool { diff --git a/components/style/matching.rs b/components/style/matching.rs index eeb06259dd4..71ec40b7988 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -827,7 +827,7 @@ pub trait MatchMethods : TElement { return StyleDifference::new(RestyleDamage::empty(), StyleChange::Unchanged) } - if pseudo.map_or(false, |p| p.is_first_letter()) { + if pseudo.map_or(false, |p| p.is_first_letter() || p.is_first_line()) { // No one cares about this pseudo, and we've checked above that // we're not switching from a "cares" to a "doesn't care" state // or vice versa. diff --git a/components/style/properties/gecko.mako.rs b/components/style/properties/gecko.mako.rs index 89a0514351e..08e5c81cf81 100644 --- a/components/style/properties/gecko.mako.rs +++ b/components/style/properties/gecko.mako.rs @@ -41,6 +41,9 @@ use gecko_bindings::bindings::{Gecko_ResetFilters, Gecko_CopyFiltersFrom}; use gecko_bindings::bindings::RawGeckoPresContextBorrowed; use gecko_bindings::structs; use gecko_bindings::structs::nsCSSPropertyID; +use gecko_bindings::structs::mozilla::CSSPseudoElementType; +use gecko_bindings::structs::mozilla::CSSPseudoElementType_InheritingAnonBox; +use gecko_bindings::structs::root::NS_STYLE_CONTEXT_TYPE_SHIFT; use gecko_bindings::sugar::ns_style_coord::{CoordDataValue, CoordData, CoordDataMut}; use gecko::values::convert_nscolor_to_rgba; use gecko::values::convert_rgba_to_nscolor; @@ -133,6 +136,18 @@ impl ComputedValues { let atom = Atom::from(atom); PseudoElement::from_atom(&atom) } + + fn get_pseudo_type(&self) -> CSSPseudoElementType { + let bits = (self.0)._base.mBits; + let our_type = bits >> NS_STYLE_CONTEXT_TYPE_SHIFT; + unsafe { transmute(our_type as u8) } + } + + pub fn is_anon_box(&self) -> bool { + let our_type = self.get_pseudo_type(); + return our_type == CSSPseudoElementType_InheritingAnonBox || + our_type == CSSPseudoElementType::NonInheritingAnonBox; + } } impl Drop for ComputedValues { diff --git a/components/style/servo/selector_parser.rs b/components/style/servo/selector_parser.rs index c88b4faa3ad..95ef6b0169f 100644 --- a/components/style/servo/selector_parser.rs +++ b/components/style/servo/selector_parser.rs @@ -42,6 +42,8 @@ pub enum PseudoElement { 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 @@ -125,6 +127,12 @@ impl PseudoElement { false } + /// Whether the current pseudo element is :first-line + #[inline] + pub fn is_first_line(&self) -> bool { + false + } + /// Whether this pseudo-element is eagerly-cascaded. #[inline] pub fn is_eager(&self) -> bool { diff --git a/components/style/stylist.rs b/components/style/stylist.rs index 6ec47c45675..e70a0e2fc01 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -20,6 +20,7 @@ use properties::{self, CascadeFlags, ComputedValues}; use properties::{AnimationRules, PropertyDeclarationBlock}; #[cfg(feature = "servo")] use properties::INHERIT_ALL; +use properties::IS_LINK; use rule_tree::{CascadeLevel, RuleTree, StyleSource}; use selector_map::{SelectorMap, SelectorMapEntry}; use selector_parser::{SelectorImpl, PseudoElement}; @@ -727,6 +728,47 @@ impl Stylist { return None } + // FIXME(emilio): The lack of layout_parent_style here could be + // worrying, but we're probably dropping the display fixup for + // pseudos other than before and after, so it's probably ok. + // + // (Though the flags don't indicate so!) + Some(self.compute_style_with_inputs(inputs, + Some(pseudo), + guards, + parent_style, + parent_style, + parent_style, + font_metrics, + CascadeFlags::empty())) + } + + /// Computes a style using the given CascadeInputs. This can be used to + /// compute a style any time we know what rules apply and just need to use + /// the given parent styles. + /// + /// parent_style is the style to inherit from for properties affected by + /// first-line ancestors. + /// + /// parent_style_ignoring_first_line is the style to inherit from for + /// properties not affected by first-line ancestors. + /// + /// layout_parent_style is the style used for some property fixups. It's + /// the style of the nearest ancestor with a layout box. + /// + /// is_link should be true if we're computing style for a link; that affects + /// how :visited handling is done. + pub fn compute_style_with_inputs(&self, + inputs: &CascadeInputs, + pseudo: Option<&PseudoElement>, + guards: &StylesheetGuards, + parent_style: &ComputedValues, + parent_style_ignoring_first_line: &ComputedValues, + layout_parent_style: &ComputedValues, + font_metrics: &FontMetricsProvider, + cascade_flags: CascadeFlags) + -> Arc + { // We need to compute visited values if we have visited rules or if our // parent has visited values. let visited_values = if inputs.visited_rules.is_some() || parent_style.get_visited_style().is_some() { @@ -737,28 +779,37 @@ impl Stylist { Some(rules) => rules, None => inputs.rules.as_ref().unwrap(), }; - // We want to use the visited bits (if any) from our parent style as - // our parent. - let inherited_style = - parent_style.get_visited_style().unwrap_or(parent_style); + let inherited_style; + let inherited_style_ignoring_first_line; + let layout_parent_style_for_visited; + if cascade_flags.contains(IS_LINK) { + // We just want to use our parent style as our parent. + inherited_style = parent_style; + inherited_style_ignoring_first_line = parent_style_ignoring_first_line; + layout_parent_style_for_visited = layout_parent_style; + } else { + // We want to use the visited bits (if any) from our parent + // style as our parent. + inherited_style = + parent_style.get_visited_style().unwrap_or(parent_style); + inherited_style_ignoring_first_line = + parent_style_ignoring_first_line.get_visited_style().unwrap_or(parent_style_ignoring_first_line); + layout_parent_style_for_visited = + layout_parent_style.get_visited_style().unwrap_or(layout_parent_style); + } - // FIXME(emilio): The lack of layout_parent_style here could be - // worrying, but we're probably dropping the display fixup for - // pseudos other than before and after, so it's probably ok. - // - // (Though the flags don't indicate so!) let computed = properties::cascade(&self.device, - Some(pseudo), + pseudo, rule_node, guards, Some(inherited_style), - Some(inherited_style), - Some(inherited_style), + Some(inherited_style_ignoring_first_line), + Some(layout_parent_style_for_visited), None, None, font_metrics, - CascadeFlags::empty(), + cascade_flags, self.quirks_mode); Some(computed) @@ -774,18 +825,18 @@ impl Stylist { // difficult to assert that display: contents nodes never arrive here // (tl;dr: It doesn't apply for replaced elements and such, but the // computed value is still "contents"). - Some(properties::cascade(&self.device, - Some(pseudo), - rules, - guards, - Some(parent_style), - Some(parent_style), - Some(parent_style), - visited_values, - None, - font_metrics, - CascadeFlags::empty(), - self.quirks_mode)) + properties::cascade(&self.device, + pseudo, + rules, + guards, + Some(parent_style), + Some(parent_style_ignoring_first_line), + Some(layout_parent_style), + visited_values, + None, + font_metrics, + cascade_flags, + self.quirks_mode) } /// Computes the cascade inputs for a lazily-cascaded pseudo-element. diff --git a/ports/geckolib/glue.rs b/ports/geckolib/glue.rs index a92ea59a2c3..ea4297b97c6 100644 --- a/ports/geckolib/glue.rs +++ b/ports/geckolib/glue.rs @@ -95,10 +95,11 @@ use style::invalidation::element::restyle_hints::{self, RestyleHint}; use style::media_queries::{MediaList, parse_media_query_list}; use style::parallel; use style::parser::{ParserContext, self}; -use style::properties::{ComputedValues, Importance}; -use style::properties::{IS_FIELDSET_CONTENT, LonghandIdSet}; +use style::properties::{CascadeFlags, ComputedValues, Importance}; +use style::properties::{IS_FIELDSET_CONTENT, IS_LINK, IS_VISITED_LINK, LonghandIdSet}; use style::properties::{PropertyDeclaration, PropertyDeclarationBlock, PropertyId, ShorthandId}; use style::properties::{SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP, SourcePropertyDeclaration, StyleBuilder}; +use style::properties::PROHIBIT_DISPLAY_CONTENTS; use style::properties::animated_properties::{Animatable, AnimatableLonghand, AnimationValue}; use style::properties::animated_properties::compare_property_priority; use style::properties::parse_one_declaration_into; @@ -179,13 +180,9 @@ fn create_shared_context<'a>(global_style_data: &GlobalStyleData, traversal_flags: TraversalFlags, snapshot_map: &'a ServoElementSnapshotTable) -> SharedStyleContext<'a> { - let visited_styles_enabled = - unsafe { bindings::Gecko_AreVisitedLinksEnabled() } && - !per_doc_data.is_private_browsing_enabled(); - SharedStyleContext { stylist: &per_doc_data.stylist, - visited_styles_enabled: visited_styles_enabled, + visited_styles_enabled: per_doc_data.visited_styles_enabled(), options: global_style_data.options.clone(), guards: StylesheetGuards::same(guard), timer: Timer::new(), @@ -1661,9 +1658,27 @@ fn get_pseudo_style( }) }, _ => { - debug_assert!(inherited_styles.is_none() || - ptr::eq(inherited_styles.unwrap(), - &**styles.primary())); + // Unfortunately, we can't assert that inherited_styles, if + // present, is pointer-equal to styles.primary(), or even + // equal in any meaningful way. The way it can fail is as + // follows. Say we append an element with a ::before, + // ::after, or ::first-line to a parent with a ::first-line, + // such that the element ends up on the first line of the + // parent (e.g. it's an inline-block in the case it has a + // ::first-line, or any container in the ::before/::after + // cases). Then gecko will update its frame's style to + // inherit from the parent's ::first-line. The next time we + // try to get the ::before/::after/::first-line style for + // the kid, we'll likely pass in the frame's style as + // inherited_styles, but that's not pointer-identical to + // styles.primary(), because it got reparented. + // + // Now in practice this turns out to be OK, because all the + // cases in which there's a mismatch go ahead and reparent + // styles again as needed to make sure the ::first-line + // affects all the things it should affect. But it makes it + // impossible to assert anything about the two styles + // matching here, unfortunately. styles.pseudos.get(&pseudo).cloned() }, } @@ -2903,6 +2918,59 @@ pub extern "C" fn Servo_ResolveStyleLazily(element: RawGeckoElementBorrowed, finish(&styles).into() } +#[no_mangle] +pub extern "C" fn Servo_ReparentStyle(style_to_reparent: ServoStyleContextBorrowed, + parent_style: ServoStyleContextBorrowed, + parent_style_ignoring_first_line: ServoStyleContextBorrowed, + layout_parent_style: ServoStyleContextBorrowed, + element: RawGeckoElementBorrowedOrNull, + raw_data: RawServoStyleSetBorrowed) + -> ServoStyleContextStrong +{ + let global_style_data = &*GLOBAL_STYLE_DATA; + let guard = global_style_data.shared_lock.read(); + let doc_data = PerDocumentStyleData::from_ffi(raw_data).borrow(); + let inputs = CascadeInputs::new_from_style(style_to_reparent); + let metrics = get_metrics_provider_for_product(); + let pseudo = style_to_reparent.pseudo(); + let element = element.map(GeckoElement); + + let mut cascade_flags = CascadeFlags::empty(); + if style_to_reparent.is_anon_box() { + cascade_flags.insert(SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP); + } + if let Some(element) = element { + if element.is_link() { + cascade_flags.insert(IS_LINK); + if element.is_visited_link() && + doc_data.visited_styles_enabled() { + cascade_flags.insert(IS_VISITED_LINK); + } + }; + + if element.is_native_anonymous() { + cascade_flags.insert(PROHIBIT_DISPLAY_CONTENTS); + } + } + if let Some(pseudo) = pseudo.as_ref() { + cascade_flags.insert(PROHIBIT_DISPLAY_CONTENTS); + if pseudo.is_fieldset_content() { + cascade_flags.insert(IS_FIELDSET_CONTENT); + } + } + + doc_data.stylist + .compute_style_with_inputs(&inputs, + pseudo.as_ref(), + &StylesheetGuards::same(&guard), + parent_style, + parent_style_ignoring_first_line, + layout_parent_style, + &metrics, + cascade_flags) + .into() +} + #[cfg(feature = "gecko_debug")] fn simulate_compute_values_failure(property: &PropertyValuePair) -> bool { let p = property.mProperty;