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 |