diff options
Diffstat (limited to 'Source/WebCore/rendering/RenderQuote.cpp')
-rw-r--r-- | Source/WebCore/rendering/RenderQuote.cpp | 384 |
1 files changed, 126 insertions, 258 deletions
diff --git a/Source/WebCore/rendering/RenderQuote.cpp b/Source/WebCore/rendering/RenderQuote.cpp index 79da0d20d..e17a044a4 100644 --- a/Source/WebCore/rendering/RenderQuote.cpp +++ b/Source/WebCore/rendering/RenderQuote.cpp @@ -21,321 +21,189 @@ #include "config.h" #include "RenderQuote.h" -#include "Document.h" -#include "Element.h" -#include "HTMLElement.h" -#include "QuotesData.h" -#include "RenderStyle.h" -#include <algorithm> #include <wtf/text/AtomicString.h> -#include <wtf/text/CString.h> -#define UNKNOWN_DEPTH -1 +#define U(x) ((const UChar*)L##x) namespace WebCore { -static inline void adjustDepth(int &depth, QuoteType type) -{ - switch (type) { - case OPEN_QUOTE: - case NO_OPEN_QUOTE: - ++depth; - break; - case CLOSE_QUOTE: - case NO_CLOSE_QUOTE: - if (depth) - --depth; - break; - default: - ASSERT_NOT_REACHED(); - } -} RenderQuote::RenderQuote(Document* node, QuoteType quote) : RenderText(node, StringImpl::empty()) , m_type(quote) - , m_depth(UNKNOWN_DEPTH) + , m_depth(0) , m_next(0) , m_previous(0) + , m_attached(false) { - view()->addRenderQuote(); } RenderQuote::~RenderQuote() { + ASSERT(!m_attached); + ASSERT(!m_next && !m_previous); } void RenderQuote::willBeDestroyed() { - if (view()) - view()->removeRenderQuote(); + detachQuote(); RenderText::willBeDestroyed(); } -const char* RenderQuote::renderName() const -{ - return "RenderQuote"; -} - -// This function places a list of quote renderers starting at "this" in the list of quote renderers already -// in the document's renderer tree. -// The assumptions are made (for performance): -// 1. The list of quotes already in the renderers tree of the document is already in a consistent state -// (All quote renderers are linked and have the correct depth set) -// 2. The quote renderers of the inserted list are in a tree of renderers of their own which has been just -// inserted in the main renderer tree with its root as child of some renderer. -// 3. The quote renderers in the inserted list have depths consistent with their position in the list relative -// to "this", thus if "this" does not need to change its depth upon insertion, the other renderers in the list don't -// need to either. -void RenderQuote::placeQuote() -{ - RenderQuote* head = this; - ASSERT(!head->m_previous); - RenderQuote* tail = 0; - for (RenderObject* predecessor = head->previousInPreOrder(); predecessor; predecessor = predecessor->previousInPreOrder()) { - if (!predecessor->isQuote()) - continue; - head->m_previous = toRenderQuote(predecessor); - if (head->m_previous->m_next) { - // We need to splice the list of quotes headed by head into the document's list of quotes. - tail = head; - while (tail->m_next) - tail = tail->m_next; - tail->m_next = head->m_previous->m_next; - ASSERT(tail->m_next->m_previous == head->m_previous); - tail->m_next->m_previous = tail; - tail = tail->m_next; // This marks the splicing point here there may be a depth discontinuity - } - head->m_previous->m_next = head; - ASSERT(head->m_previous->m_depth != UNKNOWN_DEPTH); - break; - } - int newDepth; - if (!head->m_previous) { - newDepth = 0; - goto skipNewDepthCalc; - } - newDepth = head->m_previous->m_depth; - do { - adjustDepth(newDepth, head->m_previous->m_type); -skipNewDepthCalc: - if (head->m_depth == newDepth) { // All remaining depth should be correct except if splicing was done. - if (!tail) // We've done the post splicing section already or there was no splicing. - break; - head = tail; // Continue after the splicing point - tail = 0; // Mark the possible splicing point discontinuity fixed. - newDepth = head->m_previous->m_depth; - continue; - } - head->m_depth = newDepth; - // FIXME: If the width and height of the quotation characters does not change we may only need to - // Invalidate the renderer's area not a relayout. - head->setNeedsLayoutAndPrefWidthsRecalc(); - head = head->m_next; - if (head == tail) // We are at the splicing point - tail = 0; // Mark the possible depth discontinuity fixed. - } while (head); -} - -#define ARRAY_SIZE(Carray) (sizeof(Carray) / sizeof(*Carray)) -#define LANGUAGE_DATA(name, languageSourceArray) { name, languageSourceArray, ARRAY_SIZE(languageSourceArray) } -#define U(x) ((const UChar*)L##x) - -static const UChar* simpleQuotes[] = {U("\""), U("\""), U("'"), U("'")}; - -static const UChar* englishQuotes[] = {U("\x201C"), U("\x201D"), U("\x2018"), U("\x2019")}; -static const UChar* norwegianQuotes[] = { U("\x00AB"), U("\x00BB"), U("\x2039"), U("\x203A") }; -static const UChar* romanianQuotes[] = { U("\x201E"), U("\x201D")}; -static const UChar* russianQuotes[] = { U("\x00AB"), U("\x00BB"), U("\x201E"), U("\x201C") }; -#undef U - -struct LanguageData { - const char *name; - const UChar* const* const array; - const int arraySize; - bool operator<(const LanguageData& compareTo) const - { - return strcmp(name, compareTo.name); - } -}; - -// Data mast be alphabetically sorted and in all lower case for fast comparison -LanguageData languageData[] = { - LANGUAGE_DATA("en", englishQuotes), - LANGUAGE_DATA("no", norwegianQuotes), - LANGUAGE_DATA("ro", romanianQuotes), - LANGUAGE_DATA("ru", russianQuotes) -}; -#undef LANGUAGE_DATA -const LanguageData* const languageDataEnd = languageData + ARRAY_SIZE(languageData); - -#define defaultLanguageQuotesSource simpleQuotes -#define defaultLanguageQuotesCount ARRAY_SIZE(defaultLanguageQuotesSource) - -static QuotesData* defaultLanguageQuotesValue = 0; -static const QuotesData* defaultLanguageQuotes() -{ - if (!defaultLanguageQuotesValue) { - defaultLanguageQuotesValue = QuotesData::create(defaultLanguageQuotesCount); - if (!defaultLanguageQuotesValue) - return 0; - String* data = defaultLanguageQuotesValue->data(); - for (size_t i = 0; i < defaultLanguageQuotesCount; ++i) - data[i] = defaultLanguageQuotesSource[i]; - } - return defaultLanguageQuotesValue; -} -#undef defaultLanguageQuotesSource -#undef defaultLanguageQuotesCount - -typedef HashMap<RefPtr<AtomicStringImpl>, QuotesData* > QuotesMap; +typedef HashMap<AtomicString, const QuotesData*, CaseFoldingHash> QuotesMap; -static QuotesMap& quotesMap() +static const QuotesMap& quotesDataLanguageMap() { DEFINE_STATIC_LOCAL(QuotesMap, staticQuotesMap, ()); + if (staticQuotesMap.size()) + return staticQuotesMap; + // FIXME: Expand this table to include all the languages in https://bug-3234-attachments.webkit.org/attachment.cgi?id=2135 + staticQuotesMap.set("en", QuotesData::create(U("\x201C"), U("\x201D"), U("\x2018"), U("\x2019")).leakRef()); + staticQuotesMap.set("no", QuotesData::create(U("\x00AB"), U("\x00BB"), U("\x2039"), U("\x203A")).leakRef()); + staticQuotesMap.set("ro", QuotesData::create(U("\x201E"), U("\x201D")).leakRef()); + staticQuotesMap.set("ru", QuotesData::create(U("\x00AB"), U("\x00BB"), U("\x201E"), U("\x201C")).leakRef()); return staticQuotesMap; } -static const QuotesData* quotesForLanguage(AtomicStringImpl* language) +static const QuotesData* basicQuotesData() { - QuotesData* returnValue; - AtomicString lower(language->lower()); - returnValue = quotesMap().get(lower.impl()); - if (returnValue) - return returnValue; - CString s(static_cast<const String&>(lower).ascii()); - LanguageData request = { s.buffer()->data(), 0, 0 }; - const LanguageData* lowerBound = std::lower_bound<const LanguageData*, const LanguageData>(languageData, languageDataEnd, request); - if (lowerBound == languageDataEnd) - return defaultLanguageQuotes(); - if (strncmp(lowerBound->name, request.name, strlen(lowerBound->name))) - return defaultLanguageQuotes(); - returnValue = QuotesData::create(lowerBound->arraySize); - if (!returnValue) - return defaultLanguageQuotes(); - String* data = returnValue->data(); - for (int i = 0; i < lowerBound->arraySize; ++i) - data[i] = lowerBound->array[i]; - quotesMap().set(lower.impl(), returnValue); - return returnValue; -} -#undef ARRAY_SIZE - -static const QuotesData* defaultQuotes(const RenderObject* object) -{ - DEFINE_STATIC_LOCAL(String, langString, ("lang")); - Node* node = object->generatingNode(); - Element* element; - if (!node) { - element = object->document()->body(); - if (!element) - element = object->document()->documentElement(); - } else if (!node->isElementNode()) { - element = node->parentElement(); - if (!element) - return defaultLanguageQuotes(); - } else - element = toElement(node); - const AtomicString* language; - while ((language = &element->getAttribute(langString)) && language->isNull()) { - element = element->parentElement(); - if (!element) - return defaultLanguageQuotes(); - } - return quotesForLanguage(language->impl()); + static const QuotesData* staticBasicQuotes = QuotesData::create(U("\""), U("\""), U("'"), U("'")).leakRef(); + return staticBasicQuotes; } PassRefPtr<StringImpl> RenderQuote::originalText() const { - if (!parent()) - return 0; - ASSERT(m_depth != UNKNOWN_DEPTH); - const QuotesData* quotes = style()->quotes(); - if (!quotes) - quotes = defaultQuotes(this); - if (!quotes->length) - return emptyAtom.impl(); - int index = m_depth * 2; switch (m_type) { case NO_OPEN_QUOTE: case NO_CLOSE_QUOTE: - return emptyString().impl(); + return StringImpl::empty(); case CLOSE_QUOTE: - if (index) - --index; - else - ++index; - break; + // FIXME: When m_depth is 0 we should return empty string. + return quotesData()->getCloseQuote(std::max(m_depth - 1, 0)).impl(); case OPEN_QUOTE: - break; - default: - ASSERT_NOT_REACHED(); - return emptyAtom.impl(); + return quotesData()->getOpenQuote(m_depth).impl(); } - if (index >= quotes->length) - index = (quotes->length-2) | (index & 1); - if (index < 0) - return emptyAtom.impl(); - return quotes->data()[index].impl(); + ASSERT_NOT_REACHED(); + return StringImpl::empty(); } void RenderQuote::computePreferredLogicalWidths(float lead) { + if (!m_attached) + attachQuote(); setTextInternal(originalText()); RenderText::computePreferredLogicalWidths(lead); } -void RenderQuote::rendererSubtreeAttached(RenderObject* renderer) +const QuotesData* RenderQuote::quotesData() const +{ + if (QuotesData* customQuotes = style()->quotes()) + return customQuotes; + + AtomicString language = style()->locale(); + if (language.isNull()) + return basicQuotesData(); + const QuotesData* quotes = quotesDataLanguageMap().get(language); + if (!quotes) + return basicQuotesData(); + return quotes; +} + +void RenderQuote::attachQuote() { - ASSERT(renderer->view()); - if (!renderer->view()->hasRenderQuotes()) + ASSERT(view()); + ASSERT(!m_attached); + ASSERT(!m_next && !m_previous); + + // FIXME: Don't set pref widths dirty during layout. See updateDepth() for + // more detail. + if (!isRooted()) { + setNeedsLayoutAndPrefWidthsRecalc(); return; - for (RenderObject* descendant = renderer; descendant; descendant = descendant->nextInPreOrder(renderer)) - if (descendant->isQuote()) { - toRenderQuote(descendant)->placeQuote(); - break; - } + } + + if (!view()->renderQuoteHead()) { + view()->setRenderQuoteHead(this); + m_attached = true; + return; + } + + for (RenderObject* predecessor = previousInPreOrder(); predecessor; predecessor = predecessor->previousInPreOrder()) { + // Skip unattached predecessors to avoid having stale m_previous pointers + // if the previous node is never attached and is then destroyed. + if (!predecessor->isQuote() || !toRenderQuote(predecessor)->isAttached()) + continue; + m_previous = toRenderQuote(predecessor); + m_next = m_previous->m_next; + m_previous->m_next = this; + if (m_next) + m_next->m_previous = this; + break; + } + + if (!m_previous) { + m_next = view()->renderQuoteHead(); + view()->setRenderQuoteHead(this); + if (m_next) + m_next->m_previous = this; + } + m_attached = true; + + for (RenderQuote* quote = this; quote; quote = quote->m_next) + quote->updateDepth(); + + ASSERT(!m_next || m_next->m_attached); + ASSERT(!m_next || m_next->m_previous == this); + ASSERT(!m_previous || m_previous->m_attached); + ASSERT(!m_previous || m_previous->m_next == this); } -void RenderQuote::rendererRemovedFromTree(RenderObject* renderer) +void RenderQuote::detachQuote() { - ASSERT(renderer->view()); - if (!renderer->view()->hasRenderQuotes()) + ASSERT(!m_next || m_next->m_attached); + ASSERT(!m_previous || m_previous->m_attached); + if (!m_attached) return; - for (RenderObject* descendant = renderer; descendant; descendant = descendant->nextInPreOrder(renderer)) - if (descendant->isQuote()) { - RenderQuote* removedQuote = toRenderQuote(descendant); - RenderQuote* lastQuoteBefore = removedQuote->m_previous; - removedQuote->m_previous = 0; - int depth = removedQuote->m_depth; - for (descendant = descendant->nextInPreOrder(renderer); descendant; descendant = descendant->nextInPreOrder(renderer)) - if (descendant->isQuote()) - removedQuote = toRenderQuote(descendant); - RenderQuote* quoteAfter = removedQuote->m_next; - removedQuote->m_next = 0; - if (lastQuoteBefore) - lastQuoteBefore->m_next = quoteAfter; - if (quoteAfter) { - quoteAfter->m_previous = lastQuoteBefore; - do { - if (depth == quoteAfter->m_depth) - break; - quoteAfter->m_depth = depth; - quoteAfter->setNeedsLayoutAndPrefWidthsRecalc(); - adjustDepth(depth, quoteAfter->m_type); - quoteAfter = quoteAfter->m_next; - } while (quoteAfter); - } - break; - } + if (m_previous) + m_previous->m_next = m_next; + else if (view()) + view()->setRenderQuoteHead(m_next); + if (m_next) + m_next->m_previous = m_previous; + if (!documentBeingDestroyed()) { + for (RenderQuote* quote = m_next; quote; quote = quote->m_next) + quote->updateDepth(); + } + m_attached = false; + m_next = 0; + m_previous = 0; + m_depth = 0; } -void RenderQuote::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +void RenderQuote::updateDepth() { - const QuotesData* newQuotes = style()->quotes(); - const QuotesData* oldQuotes = oldStyle ? oldStyle->quotes() : 0; - if (!QuotesData::equal(newQuotes, oldQuotes)) + ASSERT(m_attached); + int oldDepth = m_depth; + m_depth = 0; + if (m_previous) { + m_depth = m_previous->m_depth; + switch (m_previous->m_type) { + case OPEN_QUOTE: + case NO_OPEN_QUOTE: + m_depth++; + break; + case CLOSE_QUOTE: + case NO_CLOSE_QUOTE: + if (m_depth) + m_depth--; + break; + } + } + // FIXME: Don't call setNeedsLayout or dirty our preferred widths during layout. + // This is likely to fail anyway as one of our ancestor will call setNeedsLayout(false), + // preventing the future layout to occur on |this|. The solution is to move that to a + // pre-layout phase. + if (oldDepth != m_depth) setNeedsLayoutAndPrefWidthsRecalc(); - RenderText::styleDidChange(diff, oldStyle); } } // namespace WebCore |