Auto merge of #17912 - bzbarsky:stylo-first-line, r=emilio

Implement ::first-line support in stylo

<!-- Please describe your changes on the following line: -->

Fixes Gecko bug 1324619.

---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: -->
- [X] `./mach build -d` does not report any errors
- [X] `./mach test-tidy` does not report any errors
- [X] These changes fix https://bugzilla.mozilla.org/show_bug.cgi?id=1324619

<!-- Either: -->
- [ ] There are tests for these changes OR
- [X] These changes do not require tests because there are Gecko tests

<!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.-->

<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/17912)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2017-07-28 22:51:20 -05:00 committed by GitHub
commit ed75bcae75
9 changed files with 204 additions and 37 deletions

View file

@ -191,7 +191,7 @@ pub struct CascadeInputs {
impl CascadeInputs { impl CascadeInputs {
/// Construct inputs from previous cascade results, if any. /// Construct inputs from previous cascade results, if any.
pub fn new_from_style(style: &Arc<ComputedValues>) -> Self { pub fn new_from_style(style: &ComputedValues) -> Self {
CascadeInputs { CascadeInputs {
rules: style.rules.clone(), rules: style.rules.clone(),
visited_rules: style.get_visited_style().and_then(|v| v.rules.clone()), visited_rules: style.get_visited_style().and_then(|v| v.rules.clone()),

View file

@ -204,6 +204,15 @@ impl PerDocumentStyleDataImpl {
pub fn clear_stylist(&mut self) { pub fn clear_stylist(&mut self) {
self.stylist.clear(); 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 { unsafe impl HasFFI for PerDocumentStyleData {

View file

@ -2817,6 +2817,16 @@ extern "C" {
set: RawServoStyleSetBorrowed) set: RawServoStyleSetBorrowed)
-> ServoStyleContextStrong; -> 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" { extern "C" {
pub fn Servo_TraverseSubtree(root: RawGeckoElementBorrowed, pub fn Servo_TraverseSubtree(root: RawGeckoElementBorrowed,
set: RawServoStyleSetBorrowed, set: RawServoStyleSetBorrowed,

View file

@ -87,6 +87,12 @@ impl PseudoElement {
*self == PseudoElement::FirstLetter *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. /// Whether this pseudo-element is ::-moz-fieldset-content.
#[inline] #[inline]
pub fn is_fieldset_content(&self) -> bool { pub fn is_fieldset_content(&self) -> bool {

View file

@ -827,7 +827,7 @@ pub trait MatchMethods : TElement {
return StyleDifference::new(RestyleDamage::empty(), StyleChange::Unchanged) 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 // 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 // we're not switching from a "cares" to a "doesn't care" state
// or vice versa. // or vice versa.

View file

@ -41,6 +41,9 @@ use gecko_bindings::bindings::{Gecko_ResetFilters, Gecko_CopyFiltersFrom};
use gecko_bindings::bindings::RawGeckoPresContextBorrowed; use gecko_bindings::bindings::RawGeckoPresContextBorrowed;
use gecko_bindings::structs; use gecko_bindings::structs;
use gecko_bindings::structs::nsCSSPropertyID; 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_bindings::sugar::ns_style_coord::{CoordDataValue, CoordData, CoordDataMut};
use gecko::values::convert_nscolor_to_rgba; use gecko::values::convert_nscolor_to_rgba;
use gecko::values::convert_rgba_to_nscolor; use gecko::values::convert_rgba_to_nscolor;
@ -133,6 +136,18 @@ impl ComputedValues {
let atom = Atom::from(atom); let atom = Atom::from(atom);
PseudoElement::from_atom(&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 { impl Drop for ComputedValues {

View file

@ -42,6 +42,8 @@ pub enum PseudoElement {
Selection, Selection,
// If/when :first-letter is added, update is_first_letter accordingly. // 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 // If/when ::first-letter, ::first-line, or ::placeholder are added, adjust
// our property_restriction implementation to do property filtering for // our property_restriction implementation to do property filtering for
// them. Also, make sure the UA sheet has the !important rules some of the // them. Also, make sure the UA sheet has the !important rules some of the
@ -125,6 +127,12 @@ impl PseudoElement {
false 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. /// Whether this pseudo-element is eagerly-cascaded.
#[inline] #[inline]
pub fn is_eager(&self) -> bool { pub fn is_eager(&self) -> bool {

View file

@ -20,6 +20,7 @@ use properties::{self, CascadeFlags, ComputedValues};
use properties::{AnimationRules, PropertyDeclarationBlock}; use properties::{AnimationRules, PropertyDeclarationBlock};
#[cfg(feature = "servo")] #[cfg(feature = "servo")]
use properties::INHERIT_ALL; use properties::INHERIT_ALL;
use properties::IS_LINK;
use rule_tree::{CascadeLevel, RuleTree, StyleSource}; use rule_tree::{CascadeLevel, RuleTree, StyleSource};
use selector_map::{SelectorMap, SelectorMapEntry}; use selector_map::{SelectorMap, SelectorMapEntry};
use selector_parser::{SelectorImpl, PseudoElement}; use selector_parser::{SelectorImpl, PseudoElement};
@ -727,6 +728,47 @@ impl Stylist {
return None 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<ComputedValues>
{
// We need to compute visited values if we have visited rules or if our // We need to compute visited values if we have visited rules or if our
// parent has visited values. // parent has visited values.
let visited_values = if inputs.visited_rules.is_some() || parent_style.get_visited_style().is_some() { 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, Some(rules) => rules,
None => inputs.rules.as_ref().unwrap(), None => inputs.rules.as_ref().unwrap(),
}; };
// We want to use the visited bits (if any) from our parent style as let inherited_style;
// our parent. let inherited_style_ignoring_first_line;
let inherited_style = let layout_parent_style_for_visited;
parent_style.get_visited_style().unwrap_or(parent_style); 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 = let computed =
properties::cascade(&self.device, properties::cascade(&self.device,
Some(pseudo), pseudo,
rule_node, rule_node,
guards, guards,
Some(inherited_style), Some(inherited_style),
Some(inherited_style), Some(inherited_style_ignoring_first_line),
Some(inherited_style), Some(layout_parent_style_for_visited),
None, None,
None, None,
font_metrics, font_metrics,
CascadeFlags::empty(), cascade_flags,
self.quirks_mode); self.quirks_mode);
Some(computed) Some(computed)
@ -774,18 +825,18 @@ impl Stylist {
// difficult to assert that display: contents nodes never arrive here // difficult to assert that display: contents nodes never arrive here
// (tl;dr: It doesn't apply for replaced elements and such, but the // (tl;dr: It doesn't apply for replaced elements and such, but the
// computed value is still "contents"). // computed value is still "contents").
Some(properties::cascade(&self.device, properties::cascade(&self.device,
Some(pseudo), pseudo,
rules, rules,
guards, guards,
Some(parent_style), Some(parent_style),
Some(parent_style), Some(parent_style_ignoring_first_line),
Some(parent_style), Some(layout_parent_style),
visited_values, visited_values,
None, None,
font_metrics, font_metrics,
CascadeFlags::empty(), cascade_flags,
self.quirks_mode)) self.quirks_mode)
} }
/// Computes the cascade inputs for a lazily-cascaded pseudo-element. /// Computes the cascade inputs for a lazily-cascaded pseudo-element.

View file

@ -95,10 +95,11 @@ use style::invalidation::element::restyle_hints::{self, RestyleHint};
use style::media_queries::{MediaList, parse_media_query_list}; use style::media_queries::{MediaList, parse_media_query_list};
use style::parallel; use style::parallel;
use style::parser::{ParserContext, self}; use style::parser::{ParserContext, self};
use style::properties::{ComputedValues, Importance}; use style::properties::{CascadeFlags, ComputedValues, Importance};
use style::properties::{IS_FIELDSET_CONTENT, LonghandIdSet}; use style::properties::{IS_FIELDSET_CONTENT, IS_LINK, IS_VISITED_LINK, LonghandIdSet};
use style::properties::{PropertyDeclaration, PropertyDeclarationBlock, PropertyId, ShorthandId}; use style::properties::{PropertyDeclaration, PropertyDeclarationBlock, PropertyId, ShorthandId};
use style::properties::{SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP, SourcePropertyDeclaration, StyleBuilder}; 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::{Animatable, AnimatableLonghand, AnimationValue};
use style::properties::animated_properties::compare_property_priority; use style::properties::animated_properties::compare_property_priority;
use style::properties::parse_one_declaration_into; use style::properties::parse_one_declaration_into;
@ -179,13 +180,9 @@ fn create_shared_context<'a>(global_style_data: &GlobalStyleData,
traversal_flags: TraversalFlags, traversal_flags: TraversalFlags,
snapshot_map: &'a ServoElementSnapshotTable) snapshot_map: &'a ServoElementSnapshotTable)
-> SharedStyleContext<'a> { -> SharedStyleContext<'a> {
let visited_styles_enabled =
unsafe { bindings::Gecko_AreVisitedLinksEnabled() } &&
!per_doc_data.is_private_browsing_enabled();
SharedStyleContext { SharedStyleContext {
stylist: &per_doc_data.stylist, 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(), options: global_style_data.options.clone(),
guards: StylesheetGuards::same(guard), guards: StylesheetGuards::same(guard),
timer: Timer::new(), timer: Timer::new(),
@ -1661,9 +1658,27 @@ fn get_pseudo_style(
}) })
}, },
_ => { _ => {
debug_assert!(inherited_styles.is_none() || // Unfortunately, we can't assert that inherited_styles, if
ptr::eq(inherited_styles.unwrap(), // present, is pointer-equal to styles.primary(), or even
&**styles.primary())); // 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() styles.pseudos.get(&pseudo).cloned()
}, },
} }
@ -2903,6 +2918,59 @@ pub extern "C" fn Servo_ResolveStyleLazily(element: RawGeckoElementBorrowed,
finish(&styles).into() 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")] #[cfg(feature = "gecko_debug")]
fn simulate_compute_values_failure(property: &PropertyValuePair) -> bool { fn simulate_compute_values_failure(property: &PropertyValuePair) -> bool {
let p = property.mProperty; let p = property.mProperty;