Chromium Code Reviews| Index: Source/core/dom/Element.cpp |
| diff --git a/Source/core/dom/Element.cpp b/Source/core/dom/Element.cpp |
| index 7ea8de5065dbd90acc65c9a9d6b55422c47938c5..51ec0b78663642ecd824107ee2a576ef641c0a06 100644 |
| --- a/Source/core/dom/Element.cpp |
| +++ b/Source/core/dom/Element.cpp |
| @@ -103,6 +103,7 @@ |
| #include "core/page/Page.h" |
| #include "core/page/PointerLockController.h" |
| #include "core/rendering/RenderLayer.h" |
| +#include "core/rendering/RenderTextFragment.h" |
| #include "core/rendering/RenderView.h" |
| #include "core/rendering/compositing/RenderLayerCompositor.h" |
| #include "core/svg/SVGDocumentExtensions.h" |
| @@ -116,12 +117,16 @@ |
| #include "wtf/text/CString.h" |
| #include "wtf/text/StringBuilder.h" |
| #include "wtf/text/TextPosition.h" |
| +#include "wtf/unicode/icu/UnicodeIcu.h" |
| namespace blink { |
| using namespace HTMLNames; |
| using namespace XMLNames; |
| +using namespace WTF; |
| +using namespace Unicode; |
| + |
| typedef WillBeHeapVector<RefPtrWillBeMember<Attr> > AttrNodeList; |
| static Attr* findAttrNodeInList(const AttrNodeList& attrNodeList, const QualifiedName& name) |
| @@ -1346,6 +1351,11 @@ void Element::attach(const AttachContext& context) |
| createPseudoElementIfNeeded(AFTER); |
| createPseudoElementIfNeeded(BACKDROP); |
| + // We create the first-letter element after the :before, :after and |
| + // children are attached because the first letter text could come |
| + // from any of them. |
| + createPseudoElementIfNeeded(FIRST_LETTER); |
| + |
| if (hasRareData() && !renderer()) { |
| if (ActiveAnimations* activeAnimations = elementRareData()->activeAnimations()) { |
| activeAnimations->cssAnimations().cancel(); |
| @@ -1505,6 +1515,12 @@ void Element::recalcStyle(StyleRecalcChange change, Text* nextTextSibling) |
| updatePseudoElement(AFTER, change); |
| updatePseudoElement(BACKDROP, change); |
| + // If our children have changed then we need to force the first-letter |
| + // checks as we don't know if they effected the first letter or not. |
| + // This can be seen when a child transitions from floating to |
| + // non-floating we have to take it into account for the first letter. |
| + updatePseudoElement(FIRST_LETTER, childNeedsStyleRecalc() ? Force : change); |
| + |
| clearChildNeedsStyleRecalc(); |
| } |
| @@ -1737,6 +1753,9 @@ void Element::childrenChanged(const ChildrenChange& change) |
| { |
| ContainerNode::childrenChanged(change); |
| + if (change.isChildRemoval() && pseudoElement(FIRST_LETTER)) |
| + clearFirstLetterPseudoElement(); |
|
esprehn
2014/09/30 09:00:30
Destroying it on every child mutation doesn't seem
dsinclair
2014/09/30 21:46:33
Removed. Re-ran the layout tests with this removed
|
| + |
| checkForEmptyStyleChange(); |
| if (!change.byParser && change.isChildElementChange()) |
| checkForSiblingStyleChanges(change.type == ElementRemoved ? SiblingElementRemoved : SiblingElementInserted, change.siblingBeforeChange, change.siblingAfterChange); |
| @@ -2486,7 +2505,29 @@ void Element::updatePseudoElement(PseudoId pseudoId, StyleRecalcChange change) |
| { |
| ASSERT(!needsStyleRecalc()); |
| PseudoElement* element = pseudoElement(pseudoId); |
| - if (element && (change == UpdatePseudoElements || element->shouldCallRecalcStyle(change))) { |
| + |
| + // We have a first-letter pseudoElement, but we no longer should have one. |
| + // This can happen if we change to a float, for example. We need to cleanup the |
| + // first-letter pseudoElement and then fix the text the original remaining |
| + // text renderer. |
| + if (pseudoId == FIRST_LETTER && element && !element->firstLetterTextRenderer()) { |
|
esprehn
2014/09/30 09:00:30
Why can't we handle this in the code below?
dsinclair
2014/09/30 21:46:33
Done.
Moved it into the if (change >= UpdatePseud
|
| + elementRareData()->setPseudoElement(pseudoId, nullptr); |
| + |
| + // For first-letter we need to force the style recalc here. The first letter |
| + // node's renderer needs to be forced to get a new style. This can be seen |
| + // in fast/css/first-letter-nested.html when we change the font size of |
| + // test_span_2. |
|
esprehn
2014/09/30 09:00:30
Why? This comment explains what you're doing but n
dsinclair
2014/09/30 21:46:33
The caller is now doing a Force if the children ha
|
| + } else if (element && (change == UpdatePseudoElements || element->shouldCallRecalcStyle(change) |
| + || element->isFirstLetterPseudoElement())) { |
| + |
| + // If we're updating first letter, and the current first letter renderer |
| + // is not the same as the one we're currently using we need to re-create |
| + // the first letter renderer. |
| + if (pseudoId == FIRST_LETTER && element->firstLetterTextRenderer() != element->remainingTextRenderer()) { |
|
esprehn
2014/09/30 09:00:30
firstLetterTextRenderer() doesn't seem like someth
dsinclair
2014/09/30 21:46:33
Done.
|
| + elementRareData()->setPseudoElement(pseudoId, nullptr); |
| + createPseudoElementIfNeeded(pseudoId); |
| + return; |
| + } |
| // Need to clear the cached style if the PseudoElement wants a recalc so it |
| // computes a new style. |
| @@ -2499,7 +2540,7 @@ void Element::updatePseudoElement(PseudoId pseudoId, StyleRecalcChange change) |
| element->recalcStyle(change == UpdatePseudoElements ? Force : change); |
| // Wait until our parent is not displayed or pseudoElementRendererIsNeeded |
| - // is false, otherwise we could continously create and destroy PseudoElements |
| + // is false, otherwise we could continuously create and destroy PseudoElements |
| // when RenderObject::isChildAllowed on our parent returns false for the |
| // PseudoElement's renderer for each style recalc. |
| if (!renderer() || !pseudoElementRendererIsNeeded(renderer()->getCachedPseudoStyle(pseudoId))) |
| @@ -2542,6 +2583,12 @@ RenderObject* Element::pseudoElementRenderer(PseudoId pseudoId) const |
| return 0; |
| } |
| +void Element::clearFirstLetterPseudoElement() |
| +{ |
| + elementRareData()->setPseudoElement(FIRST_LETTER, nullptr); |
| + setNeedsStyleRecalc(SubtreeStyleChange); |
|
esprehn
2014/09/30 09:00:30
Woah, this is crazy, why does removing it cause a
dsinclair
2014/09/30 21:46:33
Done. This call has been removed.
|
| +} |
| + |
| bool Element::matches(const String& selectors, ExceptionState& exceptionState) |
| { |
| SelectorQuery* selectorQuery = document().selectorQueryCache().add(AtomicString(selectors), document(), exceptionState); |
| @@ -3267,4 +3314,123 @@ v8::Handle<v8::Object> Element::wrapCustomElement(v8::Handle<v8::Object> creatio |
| return V8DOMWrapper::associateObjectWithWrapperNonTemplate(this, wrapperType, wrapper, isolate); |
| } |
| + |
| +// CSS 2.1 http://www.w3.org/TR/CSS21/selector.html#first-letter |
| +// "Punctuation (i.e, characters defined in Unicode [UNICODE] in the "open" (Ps), "close" (Pe), |
| +// "initial" (Pi). "final" (Pf) and "other" (Po) punctuation classes), that precedes or follows the first letter should be included" |
| +static inline bool isPunctuationForFirstLetter(UChar c) |
|
esprehn
2014/09/30 09:00:30
Lets put all this code in it's own class.
dsinclair
2014/09/30 21:46:33
Done.
|
| +{ |
| + CharCategory charCategory = category(c); |
| + return charCategory == Punctuation_Open |
| + || charCategory == Punctuation_Close |
| + || charCategory == Punctuation_InitialQuote |
| + || charCategory == Punctuation_FinalQuote |
| + || charCategory == Punctuation_Other; |
| +} |
| + |
| +static inline bool isSpaceOrNewline(UChar c) |
| +{ |
| + // Use isASCIISpace() for basic Latin-1. |
| + // This will include newlines, which aren't included in Unicode DirWS. |
| + return c <= 0x7F ? WTF::isASCIISpace(c) : WTF::Unicode::direction(c) == WTF::Unicode::WhiteSpaceNeutral; |
| +} |
| + |
| +static inline bool isSpaceForFirstLetter(UChar c) |
| +{ |
| + return isSpaceOrNewline(c) || c == noBreakSpace; |
| +} |
| + |
| +unsigned Element::firstLetterLength(const String& text) const |
|
esprehn
2014/09/30 09:00:30
ditto. Own class, own files.
dsinclair
2014/09/30 21:46:33
Done.
|
| +{ |
| + unsigned length = 0; |
| + unsigned textLength = text.length(); |
| + |
| + // Account for leading spaces first. |
| + while (length < textLength && isSpaceForFirstLetter(text[length])) |
| + length++; |
| + |
| + // Now account for leading punctuation. |
| + while (length < textLength && isPunctuationForFirstLetter(text[length])) |
| + length++; |
| + |
| + // Bail if we didn't find a letter before the end of the text or before a space. |
| + if (isSpaceForFirstLetter(text[length]) || (textLength && length == textLength)) |
| + return 0; |
| + |
| + // Account the next character for first letter. |
| + length++; |
| + |
| + // Keep looking allowed punctuation for the :first-letter. |
| + for (unsigned scanLength = length; scanLength < textLength; ++scanLength) { |
| + UChar c = text[scanLength]; |
| + |
| + if (!isPunctuationForFirstLetter(c)) |
| + break; |
| + |
| + length = scanLength + 1; |
| + } |
| + |
| + // FIXME: If textLength is 0, length may still be 1! |
| + return length; |
| +} |
| + |
| +RenderObject* Element::firstLetterTextRenderer() const |
|
esprehn
2014/09/30 09:00:30
ditto
dsinclair
2014/09/30 21:46:33
Done.
|
| +{ |
| + RenderObject* parentRenderer = renderer(); |
| + if (!parentRenderer |
| + || !parentRenderer->style()->hasPseudoStyle(FIRST_LETTER) |
| + || !parentRenderer->canHaveGeneratedChildren() |
| + || !(parentRenderer->isRenderBlockFlow() || parentRenderer->isRenderButton())) |
| + return nullptr; |
| + |
| + // Drill down into our children and look for our first text child. |
| + RenderObject* firstLetterTextRenderer = parentRenderer->slowFirstChild(); |
| + while (firstLetterTextRenderer) { |
| + |
| + // This can be called when the first letter renderer is already in the tree. We do not |
| + // want to consider that renderer for our text renderer so we go the sibling. |
| + if (firstLetterTextRenderer->style() && firstLetterTextRenderer->style()->styleType() == FIRST_LETTER) { |
| + firstLetterTextRenderer = firstLetterTextRenderer->nextSibling(); |
| + |
| + } else if (firstLetterTextRenderer->isText()) { |
| + // FIXME: If there is leading punctuation in a different RenderText than |
| + // the first letter, we'll not apply the correct style to it. |
| + if (firstLetterLength(toRenderText(firstLetterTextRenderer)->originalText())) |
| + break; |
| + firstLetterTextRenderer = firstLetterTextRenderer->nextSibling(); |
| + |
| + } else if (firstLetterTextRenderer->isListMarker()) { |
| + firstLetterTextRenderer = firstLetterTextRenderer->nextSibling(); |
| + |
| + } else if (firstLetterTextRenderer->isFloatingOrOutOfFlowPositioned()) { |
| + if (firstLetterTextRenderer->style()->styleType() == FIRST_LETTER) { |
| + firstLetterTextRenderer = firstLetterTextRenderer->slowFirstChild(); |
| + break; |
| + } |
| + firstLetterTextRenderer = firstLetterTextRenderer->nextSibling(); |
| + |
| + } else if (firstLetterTextRenderer->isReplaced() || firstLetterTextRenderer->isRenderButton() |
| + || firstLetterTextRenderer->isMenuList()) { |
| + return nullptr; |
| + |
| + } else if (firstLetterTextRenderer->style()->hasPseudoStyle(FIRST_LETTER) |
| + && firstLetterTextRenderer->canHaveGeneratedChildren()) { |
| + // Let the child handle it when it's attached. |
| + return nullptr; |
| + |
| + } else { |
| + firstLetterTextRenderer = firstLetterTextRenderer->slowFirstChild(); |
| + } |
| + } |
| + |
| + // No first letter text to display, we're done. |
| + // FIXME: This black-list of disallowed RenderText subclasses is fragile. |
| + // Should counter be on this list? What about RenderTextFragment? |
| + if (!firstLetterTextRenderer || !firstLetterTextRenderer->isText() |
| + || firstLetterTextRenderer->isBR() || toRenderText(firstLetterTextRenderer)->isWordBreak()) |
| + return nullptr; |
| + |
| + return firstLetterTextRenderer; |
| +} |
| + |
| } // namespace blink |