mirror of
https://github.com/servo/servo.git
synced 2025-06-21 07:38:59 +01:00
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:
commit
ed75bcae75
9 changed files with 204 additions and 37 deletions
|
@ -191,7 +191,7 @@ pub struct CascadeInputs {
|
|||
|
||||
impl CascadeInputs {
|
||||
/// 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 {
|
||||
rules: style.rules.clone(),
|
||||
visited_rules: style.get_visited_style().and_then(|v| v.rules.clone()),
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<ComputedValues>
|
||||
{
|
||||
// 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.
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue