Introduce RenderTreeBuilder
[WebKit-https.git] / Source / WebCore / rendering / RenderQuote.cpp
1 /*
2  * Copyright (C) 2011 Nokia Inc.  All rights reserved.
3  * Copyright (C) 2012 Google Inc. All rights reserved.
4  * Copyright (C) 2013, 2017 Apple Inc. All rights reserved.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public License
17  * along with this library; see the file COPYING.LIB.  If not, write to
18  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  *
21  */
22
23 #include "config.h"
24 #include "RenderQuote.h"
25
26 #include "QuotesData.h"
27 #include "RenderTextFragment.h"
28 #include "RenderTreeBuilder.h"
29 #include "RenderView.h"
30 #include <wtf/IsoMallocInlines.h>
31 #include <wtf/unicode/CharacterNames.h>
32
33 namespace WebCore {
34 using namespace WTF::Unicode;
35
36 WTF_MAKE_ISO_ALLOCATED_IMPL(RenderQuote);
37
38 RenderQuote::RenderQuote(Document& document, RenderStyle&& style, QuoteType quote)
39     : RenderInline(document, WTFMove(style))
40     , m_type(quote)
41     , m_text(emptyString())
42 {
43 }
44
45 RenderQuote::~RenderQuote()
46 {
47     // Do not add any code here. Add it to willBeDestroyed() instead.
48 }
49
50 void RenderQuote::insertedIntoTree()
51 {
52     RenderInline::insertedIntoTree();
53     view().setHasQuotesNeedingUpdate(true);
54 }
55
56 void RenderQuote::willBeRemovedFromTree()
57 {
58     view().setHasQuotesNeedingUpdate(true);
59     RenderInline::willBeRemovedFromTree();
60 }
61
62 void RenderQuote::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
63 {
64     RenderInline::styleDidChange(diff, oldStyle);
65     if (diff >= StyleDifferenceLayout) {
66         m_needsTextUpdate = true;
67         view().setHasQuotesNeedingUpdate(true);
68     }
69 }
70
71 const unsigned maxDistinctQuoteCharacters = 16;
72
73 #if !ASSERT_DISABLED
74
75 static void checkNumberOfDistinctQuoteCharacters(UChar character)
76 {
77     ASSERT(character);
78     static UChar distinctQuoteCharacters[maxDistinctQuoteCharacters];
79     for (unsigned i = 0; i < maxDistinctQuoteCharacters; ++i) {
80         if (distinctQuoteCharacters[i] == character)
81             return;
82         if (!distinctQuoteCharacters[i]) {
83             distinctQuoteCharacters[i] = character;
84             return;
85         }
86     }
87     ASSERT_NOT_REACHED();
88 }
89
90 #endif
91
92 struct QuotesForLanguage {
93     const char* language;
94     UChar open1;
95     UChar close1;
96     UChar open2;
97     UChar close2;
98 };
99
100 static int quoteTableLanguageComparisonFunction(const void* a, const void* b)
101 {
102     return strcmp(static_cast<const QuotesForLanguage*>(a)->language,
103         static_cast<const QuotesForLanguage*>(b)->language);
104 }
105
106 static const QuotesForLanguage* quotesForLanguage(const String& language)
107 {
108     // Table of quotes from http://www.whatwg.org/specs/web-apps/current-work/multipage/rendering.html#quotes
109     static const QuotesForLanguage quoteTable[] = {
110         { "af",         0x201c, 0x201d, 0x2018, 0x2019 },
111         { "agq",        0x201e, 0x201d, 0x201a, 0x2019 },
112         { "ak",         0x201c, 0x201d, 0x2018, 0x2019 },
113         { "am",         0x00ab, 0x00bb, 0x2039, 0x203a },
114         { "ar",         0x201d, 0x201c, 0x2019, 0x2018 },
115         { "asa",        0x201c, 0x201d, 0x2018, 0x2019 },
116         { "az-cyrl",    0x00ab, 0x00bb, 0x2039, 0x203a },
117         { "bas",        0x00ab, 0x00bb, 0x201e, 0x201c },
118         { "bem",        0x201c, 0x201d, 0x2018, 0x2019 },
119         { "bez",        0x201c, 0x201d, 0x2018, 0x2019 },
120         { "bg",         0x201e, 0x201c, 0x201a, 0x2018 },
121         { "bm",         0x00ab, 0x00bb, 0x201c, 0x201d },
122         { "bn",         0x201c, 0x201d, 0x2018, 0x2019 },
123         { "br",         0x00ab, 0x00bb, 0x2039, 0x203a },
124         { "brx",        0x201c, 0x201d, 0x2018, 0x2019 },
125         { "bs-cyrl",    0x201e, 0x201c, 0x201a, 0x2018 },
126         { "ca",         0x201c, 0x201d, 0x00ab, 0x00bb },
127         { "cgg",        0x201c, 0x201d, 0x2018, 0x2019 },
128         { "chr",        0x201c, 0x201d, 0x2018, 0x2019 },
129         { "cs",         0x201e, 0x201c, 0x201a, 0x2018 },
130         { "da",         0x201c, 0x201d, 0x2018, 0x2019 },
131         { "dav",        0x201c, 0x201d, 0x2018, 0x2019 },
132         { "de",         0x201e, 0x201c, 0x201a, 0x2018 },
133         { "de-ch",      0x00ab, 0x00bb, 0x2039, 0x203a },
134         { "dje",        0x201c, 0x201d, 0x2018, 0x2019 },
135         { "dua",        0x00ab, 0x00bb, 0x2018, 0x2019 },
136         { "dyo",        0x00ab, 0x00bb, 0x201c, 0x201d },
137         { "dz",         0x201c, 0x201d, 0x2018, 0x2019 },
138         { "ebu",        0x201c, 0x201d, 0x2018, 0x2019 },
139         { "ee",         0x201c, 0x201d, 0x2018, 0x2019 },
140         { "el",         0x00ab, 0x00bb, 0x201c, 0x201d },
141         { "en",         0x201c, 0x201d, 0x2018, 0x2019 },
142         { "en-gb",      0x201c, 0x201d, 0x2018, 0x2019 },
143         { "es",         0x201c, 0x201d, 0x00ab, 0x00bb },
144         { "et",         0x201e, 0x201c, 0x201a, 0x2018 },
145         { "eu",         0x201c, 0x201d, 0x00ab, 0x00bb },
146         { "ewo",        0x00ab, 0x00bb, 0x201c, 0x201d },
147         { "fa",         0x00ab, 0x00bb, 0x2039, 0x203a },
148         { "ff",         0x201e, 0x201d, 0x201a, 0x2019 },
149         { "fi",         0x201d, 0x201d, 0x2019, 0x2019 },
150         { "fr",         0x00ab, 0x00bb, 0x00ab, 0x00bb },
151         { "fr-ca",      0x00ab, 0x00bb, 0x2039, 0x203a },
152         { "fr-ch",      0x00ab, 0x00bb, 0x2039, 0x203a },
153         { "gsw",        0x00ab, 0x00bb, 0x2039, 0x203a },
154         { "gu",         0x201c, 0x201d, 0x2018, 0x2019 },
155         { "guz",        0x201c, 0x201d, 0x2018, 0x2019 },
156         { "ha",         0x201c, 0x201d, 0x2018, 0x2019 },
157         { "he",         0x0022, 0x0022, 0x0027, 0x0027 },
158         { "hi",         0x201c, 0x201d, 0x2018, 0x2019 },
159         { "hr",         0x201e, 0x201c, 0x201a, 0x2018 },
160         { "hu",         0x201e, 0x201d, 0x00bb, 0x00ab },
161         { "id",         0x201c, 0x201d, 0x2018, 0x2019 },
162         { "ig",         0x201c, 0x201d, 0x2018, 0x2019 },
163         { "it",         0x00ab, 0x00bb, 0x201c, 0x201d },
164         { "ja",         0x300c, 0x300d, 0x300e, 0x300f },
165         { "jgo",        0x00ab, 0x00bb, 0x2039, 0x203a },
166         { "jmc",        0x201c, 0x201d, 0x2018, 0x2019 },
167         { "kab",        0x00ab, 0x00bb, 0x201c, 0x201d },
168         { "kam",        0x201c, 0x201d, 0x2018, 0x2019 },
169         { "kde",        0x201c, 0x201d, 0x2018, 0x2019 },
170         { "kea",        0x201c, 0x201d, 0x2018, 0x2019 },
171         { "khq",        0x201c, 0x201d, 0x2018, 0x2019 },
172         { "ki",         0x201c, 0x201d, 0x2018, 0x2019 },
173         { "kkj",        0x00ab, 0x00bb, 0x2039, 0x203a },
174         { "kln",        0x201c, 0x201d, 0x2018, 0x2019 },
175         { "km",         0x201c, 0x201d, 0x2018, 0x2019 },
176         { "kn",         0x201c, 0x201d, 0x2018, 0x2019 },
177         { "ko",         0x201c, 0x201d, 0x2018, 0x2019 },
178         { "ksb",        0x201c, 0x201d, 0x2018, 0x2019 },
179         { "ksf",        0x00ab, 0x00bb, 0x2018, 0x2019 },
180         { "lag",        0x201d, 0x201d, 0x2019, 0x2019 },
181         { "lg",         0x201c, 0x201d, 0x2018, 0x2019 },
182         { "ln",         0x201c, 0x201d, 0x2018, 0x2019 },
183         { "lo",         0x201c, 0x201d, 0x2018, 0x2019 },
184         { "lt",         0x201e, 0x201c, 0x201e, 0x201c },
185         { "lu",         0x201c, 0x201d, 0x2018, 0x2019 },
186         { "luo",        0x201c, 0x201d, 0x2018, 0x2019 },
187         { "luy",        0x201e, 0x201c, 0x201a, 0x2018 },
188         { "lv",         0x201c, 0x201d, 0x2018, 0x2019 },
189         { "mas",        0x201c, 0x201d, 0x2018, 0x2019 },
190         { "mer",        0x201c, 0x201d, 0x2018, 0x2019 },
191         { "mfe",        0x201c, 0x201d, 0x2018, 0x2019 },
192         { "mg",         0x00ab, 0x00bb, 0x201c, 0x201d },
193         { "mgo",        0x201c, 0x201d, 0x2018, 0x2019 },
194         { "mk",         0x201e, 0x201c, 0x201a, 0x2018 },
195         { "ml",         0x201c, 0x201d, 0x2018, 0x2019 },
196         { "mr",         0x201c, 0x201d, 0x2018, 0x2019 },
197         { "ms",         0x201c, 0x201d, 0x2018, 0x2019 },
198         { "mua",        0x00ab, 0x00bb, 0x201c, 0x201d },
199         { "my",         0x201c, 0x201d, 0x2018, 0x2019 },
200         { "naq",        0x201c, 0x201d, 0x2018, 0x2019 },
201         { "nb",         0x00ab, 0x00bb, 0x2018, 0x2019 },
202         { "nd",         0x201c, 0x201d, 0x2018, 0x2019 },
203         { "nl",         0x201c, 0x201d, 0x2018, 0x2019 },
204         { "nmg",        0x201e, 0x201d, 0x00ab, 0x00bb },
205         { "nn",         0x00ab, 0x00bb, 0x2018, 0x2019 },
206         { "nnh",        0x00ab, 0x00bb, 0x201c, 0x201d },
207         { "nus",        0x201c, 0x201d, 0x2018, 0x2019 },
208         { "nyn",        0x201c, 0x201d, 0x2018, 0x2019 },
209         { "pl",         0x201e, 0x201d, 0x00ab, 0x00bb },
210         { "pt",         0x201c, 0x201d, 0x2018, 0x2019 },
211         { "pt-pt",      0x00ab, 0x00bb, 0x201c, 0x201d },
212         { "rn",         0x201d, 0x201d, 0x2019, 0x2019 },
213         { "ro",         0x201e, 0x201d, 0x00ab, 0x00bb },
214         { "rof",        0x201c, 0x201d, 0x2018, 0x2019 },
215         { "ru",         0x00ab, 0x00bb, 0x201e, 0x201c },
216         { "rw",         0x00ab, 0x00bb, 0x2018, 0x2019 },
217         { "rwk",        0x201c, 0x201d, 0x2018, 0x2019 },
218         { "saq",        0x201c, 0x201d, 0x2018, 0x2019 },
219         { "sbp",        0x201c, 0x201d, 0x2018, 0x2019 },
220         { "seh",        0x201c, 0x201d, 0x2018, 0x2019 },
221         { "ses",        0x201c, 0x201d, 0x2018, 0x2019 },
222         { "sg",         0x00ab, 0x00bb, 0x201c, 0x201d },
223         { "shi",        0x00ab, 0x00bb, 0x201e, 0x201d },
224         { "shi-tfng",   0x00ab, 0x00bb, 0x201e, 0x201d },
225         { "si",         0x201c, 0x201d, 0x2018, 0x2019 },
226         { "sk",         0x201e, 0x201c, 0x201a, 0x2018 },
227         { "sl",         0x201e, 0x201c, 0x201a, 0x2018 },
228         { "sn",         0x201d, 0x201d, 0x2019, 0x2019 },
229         { "so",         0x201c, 0x201d, 0x2018, 0x2019 },
230         { "sq",         0x201e, 0x201c, 0x201a, 0x2018 },
231         { "sr",         0x201e, 0x201c, 0x201a, 0x2018 },
232         { "sr-latn",    0x201e, 0x201c, 0x201a, 0x2018 },
233         { "sv",         0x201d, 0x201d, 0x2019, 0x2019 },
234         { "sw",         0x201c, 0x201d, 0x2018, 0x2019 },
235         { "swc",        0x201c, 0x201d, 0x2018, 0x2019 },
236         { "ta",         0x201c, 0x201d, 0x2018, 0x2019 },
237         { "te",         0x201c, 0x201d, 0x2018, 0x2019 },
238         { "teo",        0x201c, 0x201d, 0x2018, 0x2019 },
239         { "th",         0x201c, 0x201d, 0x2018, 0x2019 },
240         { "ti-er",      0x2018, 0x2019, 0x201c, 0x201d },
241         { "to",         0x201c, 0x201d, 0x2018, 0x2019 },
242         { "tr",         0x201c, 0x201d, 0x2018, 0x2019 },
243         { "twq",        0x201c, 0x201d, 0x2018, 0x2019 },
244         { "tzm",        0x201c, 0x201d, 0x2018, 0x2019 },
245         { "uk",         0x00ab, 0x00bb, 0x201e, 0x201c },
246         { "ur",         0x201d, 0x201c, 0x2019, 0x2018 },
247         { "vai",        0x201c, 0x201d, 0x2018, 0x2019 },
248         { "vai-latn",   0x201c, 0x201d, 0x2018, 0x2019 },
249         { "vi",         0x201c, 0x201d, 0x2018, 0x2019 },
250         { "vun",        0x201c, 0x201d, 0x2018, 0x2019 },
251         { "xh",         0x2018, 0x2019, 0x201c, 0x201d },
252         { "xog",        0x201c, 0x201d, 0x2018, 0x2019 },
253         { "yav",        0x00ab, 0x00bb, 0x00ab, 0x00bb },
254         { "yo",         0x201c, 0x201d, 0x2018, 0x2019 },
255         { "zh",         0x201c, 0x201d, 0x2018, 0x2019 },
256         { "zh-hant",    0x300c, 0x300d, 0x300e, 0x300f },
257         { "zu",         0x201c, 0x201d, 0x2018, 0x2019 },
258     };
259
260     const unsigned maxLanguageLength = 8;
261
262 #if !ASSERT_DISABLED
263     // One time check that the table meets the constraints that the code below relies on.
264
265     static bool didOneTimeCheck = false;
266     if (!didOneTimeCheck) {
267         didOneTimeCheck = true;
268
269         checkNumberOfDistinctQuoteCharacters(quotationMark);
270         checkNumberOfDistinctQuoteCharacters(apostrophe);
271
272         for (unsigned i = 0; i < WTF_ARRAY_LENGTH(quoteTable); ++i) {
273             ASSERT(strlen(quoteTable[i].language) <= maxLanguageLength);
274
275             if (i)
276                 ASSERT(strcmp(quoteTable[i - 1].language, quoteTable[i].language) < 0);
277
278             for (unsigned j = 0; UChar character = quoteTable[i].language[j]; ++j)
279                 ASSERT(isASCIILower(character) || character == '-');
280
281             checkNumberOfDistinctQuoteCharacters(quoteTable[i].open1);
282             checkNumberOfDistinctQuoteCharacters(quoteTable[i].close1);
283             checkNumberOfDistinctQuoteCharacters(quoteTable[i].open2);
284             checkNumberOfDistinctQuoteCharacters(quoteTable[i].close2);
285         }
286     }
287 #endif
288
289     unsigned length = language.length();
290     if (!length || length > maxLanguageLength)
291         return 0;
292
293     char languageKeyBuffer[maxLanguageLength + 1];
294     for (unsigned i = 0; i < length; ++i) {
295         UChar character = toASCIILower(language[i]);
296         if (!(isASCIILower(character) || character == '-'))
297             return 0;
298         languageKeyBuffer[i] = static_cast<char>(character);
299     }
300     languageKeyBuffer[length] = 0;
301
302     QuotesForLanguage languageKey = { languageKeyBuffer, 0, 0, 0, 0 };
303
304     return static_cast<const QuotesForLanguage*>(bsearch(&languageKey,
305         quoteTable, WTF_ARRAY_LENGTH(quoteTable), sizeof(quoteTable[0]), quoteTableLanguageComparisonFunction));
306 }
307
308 static StringImpl* stringForQuoteCharacter(UChar character)
309 {
310     // Use linear search because there is a small number of distinct characters, thus binary search is unneeded.
311     ASSERT(character);
312     struct StringForCharacter {
313         UChar character;
314         StringImpl* string;
315     };
316     static StringForCharacter strings[maxDistinctQuoteCharacters];
317     for (unsigned i = 0; i < maxDistinctQuoteCharacters; ++i) {
318         if (strings[i].character == character)
319             return strings[i].string;
320         if (!strings[i].character) {
321             strings[i].character = character;
322             strings[i].string = &StringImpl::create8BitIfPossible(&character, 1).leakRef();
323             return strings[i].string;
324         }
325     }
326     ASSERT_NOT_REACHED();
327     return StringImpl::empty();
328 }
329
330 static inline StringImpl* quotationMarkString()
331 {
332     static StringImpl* quotationMarkString = stringForQuoteCharacter(quotationMark);
333     return quotationMarkString;
334 }
335
336 static inline StringImpl* apostropheString()
337 {
338     static StringImpl* apostropheString = stringForQuoteCharacter(apostrophe);
339     return apostropheString;
340 }
341
342 static RenderTextFragment* quoteTextRenderer(RenderObject* lastChild)
343 {
344     if (!lastChild)
345         return nullptr;
346
347     if (!is<RenderTextFragment>(lastChild))
348         return nullptr;
349
350     return downcast<RenderTextFragment>(lastChild);
351 }
352
353 void RenderQuote::updateTextRenderer(RenderTreeBuilder& builder)
354 {
355     ASSERT_WITH_SECURITY_IMPLICATION(document().inRenderTreeUpdate());
356     String text = computeText();
357     if (m_text == text)
358         return;
359     m_text = text;
360     if (auto* renderText = quoteTextRenderer(lastChild())) {
361         renderText->setContentString(m_text);
362         renderText->dirtyLineBoxes(false);
363         return;
364     }
365     builder.insertChild(*this, createRenderer<RenderTextFragment>(document(), m_text));
366 }
367
368 String RenderQuote::computeText() const
369 {
370     if (m_depth < 0)
371         return emptyString();
372     bool isOpenQuote = false;
373     switch (m_type) {
374     case NO_OPEN_QUOTE:
375     case NO_CLOSE_QUOTE:
376         return emptyString();
377     case OPEN_QUOTE:
378         isOpenQuote = true;
379         FALLTHROUGH;
380     case CLOSE_QUOTE:
381         if (const QuotesData* quotes = style().quotes())
382             return isOpenQuote ? quotes->openQuote(m_depth).impl() : quotes->closeQuote(m_depth).impl();
383         if (const QuotesForLanguage* quotes = quotesForLanguage(style().locale()))
384             return stringForQuoteCharacter(isOpenQuote ? (m_depth ? quotes->open2 : quotes->open1) : (m_depth ? quotes->close2 : quotes->close1));
385         // FIXME: Should the default be the quotes for "en" rather than straight quotes?
386         return m_depth ? apostropheString() : quotationMarkString();
387     }
388     ASSERT_NOT_REACHED();
389     return emptyString();
390 }
391
392 bool RenderQuote::isOpen() const
393 {
394     switch (m_type) {
395     case OPEN_QUOTE:
396     case NO_OPEN_QUOTE:
397         return true;
398     case CLOSE_QUOTE:
399     case NO_CLOSE_QUOTE:
400         return false;
401     }
402     ASSERT_NOT_REACHED();
403     return false;
404 }
405
406 void RenderQuote::updateRenderer(RenderTreeBuilder& builder, RenderQuote* previousQuote)
407 {
408     ASSERT_WITH_SECURITY_IMPLICATION(document().inRenderTreeUpdate());
409     int depth = -1;
410     if (previousQuote) {
411         depth = previousQuote->m_depth;
412         if (previousQuote->isOpen())
413             ++depth;
414     }
415
416     if (!isOpen())
417         --depth;
418     else if (depth < 0)
419         depth = 0;
420
421     if (m_depth == depth && !m_needsTextUpdate)
422         return;
423
424     m_depth = depth;
425     m_needsTextUpdate = false;
426     updateTextRenderer(builder);
427 }
428
429 } // namespace WebCore