Commit 66094461 authored by Rune Lillesveen's avatar Rune Lillesveen Committed by Commit Bot

Don't re-attach ::first-letter during style recalc.

Update (create or destroy) the FirstLetterPseudoElement and its style
at the point where the block level element's layout sub-tree is up-to-
date. This means we will always attach the layout tree for the
::first-letter element in Element::AttachLayoutTree, but the pseudo
element and its style may be created/updated at style recalc time,
layout tree rebuild time, or layout tree attachment time depending on
when we know what will be the LayoutText from which we will format the
first letter if any.

UpdateFirstLetterPseudoElement is split out from UpdatePseudoElement to
make the code easier to read as the former case has some exceptional
cases.

We no longer use the pseudo style cache for ::first-letter as we will
now compute the style only once per pass with the correct inheritance
parent.

Bug: 847218
Change-Id: I7a1e2a60122891fa38998ff85e566bec0a38b513
Reviewed-on: https://chromium-review.googlesource.com/1155591Reviewed-by: default avatarAnders Ruud <andruud@chromium.org>
Commit-Queue: Rune Lillesveen <futhark@chromium.org>
Cr-Commit-Position: refs/heads/master@{#581320}
parent 616ae14e
<!DOCTYPE html>
<title>CSS Test: Changing ::first-letter color while sibling changes display type.</title>
<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
<link rel="match" href="first-letter-block-to-inline-ref.html">
<link rel="help" href="https://drafts.csswg.org/css-pseudo-4/#first-letter-pseudo">
<style>
div { color: green }
div::first-letter { color: red }
.green::first-letter { color: green }
</style>
<div>This text should be green.<span></span></div>
<script>
document.body.offsetTop;
document.querySelector("span").style.display = "block";
document.querySelector("div").className = "green";
</script>
......@@ -13,7 +13,7 @@ PASS getComputedStyle(r3, '::first-line').backgroundColor is transparent
PASS internals.updateStyleAndReturnAffectedElementCount() is 9
PASS getComputedStyle(r3, '::first-line').backgroundColor is green
PASS getComputedStyle(r4, '::first-letter').backgroundColor is transparent
PASS internals.updateStyleAndReturnAffectedElementCount() is 10
PASS internals.updateStyleAndReturnAffectedElementCount() is 9
PASS getComputedStyle(r4, '::first-letter').backgroundColor is green
PASS successfullyParsed is true
......
......@@ -81,8 +81,7 @@ forceLayout();
t4.className = "a4";
if (window.internals)
shouldBe("internals.updateStyleAndReturnAffectedElementCount()", "10");
shouldBe("internals.updateStyleAndReturnAffectedElementCount()", "9");
document.body.offsetLeft; // workaround for issue 351308
shouldBe("getComputedStyle(r4, '::first-letter').backgroundColor", "green");
</script>
......@@ -985,7 +985,14 @@ class CORE_EXPORT Element : public ContainerNode {
const Node* node_after_change);
void UpdatePseudoElement(PseudoId, StyleRecalcChange);
bool UpdateFirstLetter(Element*);
enum class StyleUpdatePhase {
kRecalc,
kRebuildLayoutTree,
kAttachLayoutTree,
};
void UpdateFirstLetterPseudoElement(StyleUpdatePhase);
inline PseudoElement* CreatePseudoElementIfNeeded(PseudoId);
void AttachPseudoElement(PseudoId, AttachContext&);
......
......@@ -26,6 +26,7 @@
#include "third_party/blink/renderer/core/css/style_change_reason.h"
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/dom/node_computed_style.h"
#include "third_party/blink/renderer/core/layout/generated_children.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/layout/layout_object_inlines.h"
......@@ -271,27 +272,16 @@ void FirstLetterPseudoElement::DetachLayoutTree(const AttachContext& context) {
PseudoElement::DetachLayoutTree(context);
}
ComputedStyle* FirstLetterPseudoElement::StyleForFirstLetter(
LayoutObject* layout_object_container) {
DCHECK(layout_object_container);
LayoutObject* style_container =
ParentOrShadowHostElement()->GetLayoutObject();
DCHECK(style_container);
// We always force the pseudo style to recompute as the first-letter style
// computed by the style container may not have taken the layoutObjects styles
// into account.
style_container->MutableStyle()->RemoveCachedPseudoStyle(
kPseudoIdFirstLetter);
ComputedStyle* pseudo_style = style_container->GetCachedPseudoStyle(
kPseudoIdFirstLetter, layout_object_container->FirstLineStyle());
DCHECK(pseudo_style);
pseudo_style->UpdateIsStackingContext(false /* is_document_element */,
false /* is_in_top_layer */,
false /* is_svg_stacking */);
return pseudo_style;
scoped_refptr<ComputedStyle>
FirstLetterPseudoElement::CustomStyleForLayoutObject() {
LayoutObject* first_letter_text =
FirstLetterPseudoElement::FirstLetterTextLayoutObject(*this);
if (!first_letter_text)
return nullptr;
DCHECK(first_letter_text->Parent());
return ParentOrShadowHostElement()->StyleForPseudoElement(
PseudoStyleRequest(GetPseudoId()),
first_letter_text->Parent()->FirstLineStyle());
}
void FirstLetterPseudoElement::AttachFirstLetterTextLayoutObjects(LayoutText* first_letter_text) {
......@@ -304,12 +294,6 @@ void FirstLetterPseudoElement::AttachFirstLetterTextLayoutObjects(LayoutText* fi
String old_text = first_letter_text->IsTextFragment() ? ToLayoutTextFragment(first_letter_text)->CompleteText() : first_letter_text->OriginalText();
DCHECK(old_text.Impl());
// :first-letter inherits from the parent of the text. It may not be
// this->Parent() when e.g., <div><span>text</span></div>.
ComputedStyle* pseudo_style =
StyleForFirstLetter(first_letter_text->Parent());
GetLayoutObject()->SetStyle(pseudo_style);
// FIXME: This would already have been calculated in firstLetterLayoutObject.
// Can we pass the length through?
unsigned length = FirstLetterPseudoElement::FirstLetterLength(old_text);
......@@ -344,7 +328,7 @@ void FirstLetterPseudoElement::AttachFirstLetterTextLayoutObjects(LayoutText* fi
LayoutTextFragment* letter =
LayoutTextFragment::CreateAnonymous(*this, old_text.Impl(), 0, length);
letter->SetFirstLetterPseudoElement(this);
letter->SetStyle(pseudo_style);
letter->SetStyle(MutableComputedStyle());
GetLayoutObject()->AddChild(letter);
first_letter_text->Destroy();
......@@ -355,30 +339,15 @@ void FirstLetterPseudoElement::DidRecalcStyle(StyleRecalcChange) {
if (!layout_object)
return;
// :first-letter inherits from the parent of the text. It may not be
// this->Parent() when e.g., <div><span>text</span></div>.
DCHECK(remaining_text_layout_object_);
ComputedStyle* pseudo_style =
StyleForFirstLetter(remaining_text_layout_object_->Parent());
DCHECK(pseudo_style);
// TODO(kojii): While setting to GetLayoutObject() looks correct all the time,
// as we do so in AttachFirstLetterTextLayoutObjects(), it is required only
// when inline box has text children, and can break layout tree when changing
// :first-letter to floats. The check in Element::UpdatePseudoElement() does
// not catch all such cases.
if (!pseudo_style->IsDisplayBlockContainer())
layout_object->SetStyle(pseudo_style);
// The layoutObjects inside pseudo elements are anonymous so they don't get
// notified of recalcStyle and must have
// the style propagated downward manually similar to
// LayoutObject::propagateStyleToAnonymousChildren.
// The layout objects inside pseudo elements are anonymous so they don't get
// notified of RecalcStyle and must have the style propagated downward
// manually similar to LayoutObject::PropagateStyleToAnonymousChildren.
for (LayoutObject* child = layout_object->NextInPreOrder(layout_object);
child; child = child->NextInPreOrder(layout_object)) {
// We need to re-calculate the correct style for the first letter element
// and then apply that to the container and the text fragment inside.
if (child->Style()->StyleType() == kPseudoIdFirstLetter) {
child->SetPseudoStyle(pseudo_style);
child->SetPseudoStyle(layout_object->MutableStyle());
continue;
}
......
......@@ -33,7 +33,6 @@
namespace blink {
class Element;
class LayoutObject;
class LayoutText;
class LayoutTextFragment;
......@@ -61,10 +60,10 @@ class CORE_EXPORT FirstLetterPseudoElement final : public PseudoElement {
private:
explicit FirstLetterPseudoElement(Element*);
scoped_refptr<ComputedStyle> CustomStyleForLayoutObject() override;
void DidRecalcStyle(StyleRecalcChange) override;
void AttachFirstLetterTextLayoutObjects(LayoutText* first_letter_text);
ComputedStyle* StyleForFirstLetter(LayoutObject*);
LayoutTextFragment* remaining_text_layout_object_;
DISALLOW_COPY_AND_ASSIGN(FirstLetterPseudoElement);
......
......@@ -131,11 +131,7 @@ bool LayoutTreeBuilderForElement::ShouldCreateLayoutObject() const {
}
ComputedStyle& LayoutTreeBuilderForElement::Style() const {
if (!style_) {
DCHECK(!node_->GetNonAttachedStyle());
DCHECK(node_->IsFirstLetterPseudoElement());
style_ = node_->StyleForLayoutObject();
}
DCHECK(style_);
return *style_;
}
......
......@@ -2220,9 +2220,7 @@ class ComputedStyle : public ComputedStyleBase,
InterpolationQuality GetInterpolationQuality() const;
bool CanGeneratePseudoElement(PseudoId pseudo) const {
// The first letter pseudo element has to look up the tree and see if any
// of the ancestors are first letter.
if (pseudo != kPseudoIdFirstLetter && !HasPseudoStyle(pseudo))
if (!HasPseudoStyle(pseudo))
return false;
if (Display() == EDisplay::kNone)
return false;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment