ffffacaee93e83ec2559ffac4a8186d134c48f04
[WebKit-https.git] / Source / WebCore / html / HTMLTableElement.cpp
1 /*
2  * Copyright (C) 1997 Martin Jones (mjones@kde.org)
3  *           (C) 1997 Torben Weis (weis@kde.org)
4  *           (C) 1998 Waldo Bastian (bastian@kde.org)
5  *           (C) 1999 Lars Knoll (knoll@kde.org)
6  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
7  * Copyright (C) 2003, 2004, 2005, 2006, 2008, 2010, 2011 Apple Inc. All rights reserved.
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Library General Public
11  * License as published by the Free Software Foundation; either
12  * version 2 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Library General Public License for more details.
18  *
19  * You should have received a copy of the GNU Library General Public License
20  * along with this library; see the file COPYING.LIB.  If not, write to
21  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22  * Boston, MA 02110-1301, USA.
23  */
24
25 #include "config.h"
26 #include "HTMLTableElement.h"
27
28 #include "CSSImageValue.h"
29 #include "CSSPropertyNames.h"
30 #include "CSSValueKeywords.h"
31 #include "CSSValuePool.h"
32 #include "ElementChildIterator.h"
33 #include "GenericCachedHTMLCollection.h"
34 #include "HTMLNames.h"
35 #include "HTMLParserIdioms.h"
36 #include "HTMLTableCaptionElement.h"
37 #include "HTMLTableRowElement.h"
38 #include "HTMLTableRowsCollection.h"
39 #include "HTMLTableSectionElement.h"
40 #include "NodeRareData.h"
41 #include "RenderTable.h"
42 #include "StyleProperties.h"
43 #include <wtf/Ref.h>
44
45 namespace WebCore {
46
47 using namespace HTMLNames;
48
49 HTMLTableElement::HTMLTableElement(const QualifiedName& tagName, Document& document)
50     : HTMLElement(tagName, document)
51 {
52     ASSERT(hasTagName(tableTag));
53 }
54
55 Ref<HTMLTableElement> HTMLTableElement::create(Document& document)
56 {
57     return adoptRef(*new HTMLTableElement(tableTag, document));
58 }
59
60 Ref<HTMLTableElement> HTMLTableElement::create(const QualifiedName& tagName, Document& document)
61 {
62     return adoptRef(*new HTMLTableElement(tagName, document));
63 }
64
65 HTMLTableCaptionElement* HTMLTableElement::caption() const
66 {
67     return childrenOfType<HTMLTableCaptionElement>(const_cast<HTMLTableElement&>(*this)).first();
68 }
69
70 ExceptionOr<void> HTMLTableElement::setCaption(RefPtr<HTMLTableCaptionElement>&& newCaption)
71 {
72     deleteCaption();
73     if (!newCaption)
74         return { };
75     return insertBefore(*newCaption, firstChild());
76 }
77
78 HTMLTableSectionElement* HTMLTableElement::tHead() const
79 {
80     for (RefPtr<Node> child = firstChild(); child; child = child->nextSibling()) {
81         if (child->hasTagName(theadTag))
82             return downcast<HTMLTableSectionElement>(child.get());
83     }
84     return nullptr;
85 }
86
87 ExceptionOr<void> HTMLTableElement::setTHead(RefPtr<HTMLTableSectionElement>&& newHead)
88 {
89     if (UNLIKELY(newHead && !newHead->hasTagName(theadTag)))
90         return Exception { HierarchyRequestError };
91
92     deleteTHead();
93     if (!newHead)
94         return { };
95
96     RefPtr<Node> child;
97     for (child = firstChild(); child; child = child->nextSibling()) {
98         if (child->isElementNode() && !child->hasTagName(captionTag) && !child->hasTagName(colgroupTag))
99             break;
100     }
101
102     return insertBefore(*newHead, child.get());
103 }
104
105 HTMLTableSectionElement* HTMLTableElement::tFoot() const
106 {
107     for (RefPtr<Node> child = firstChild(); child; child = child->nextSibling()) {
108         if (child->hasTagName(tfootTag))
109             return downcast<HTMLTableSectionElement>(child.get());
110     }
111     return nullptr;
112 }
113
114 ExceptionOr<void> HTMLTableElement::setTFoot(RefPtr<HTMLTableSectionElement>&& newFoot)
115 {
116     if (UNLIKELY(newFoot && !newFoot->hasTagName(tfootTag)))
117         return Exception { HierarchyRequestError };
118     deleteTFoot();
119     if (!newFoot)
120         return { };
121     return appendChild(*newFoot);
122 }
123
124 Ref<HTMLTableSectionElement> HTMLTableElement::createTHead()
125 {
126     if (auto* existingHead = tHead())
127         return *existingHead;
128     auto head = HTMLTableSectionElement::create(theadTag, document());
129     setTHead(head.copyRef());
130     return head;
131 }
132
133 void HTMLTableElement::deleteTHead()
134 {
135     if (auto* head = tHead())
136         removeChild(*head);
137 }
138
139 Ref<HTMLTableSectionElement> HTMLTableElement::createTFoot()
140 {
141     if (auto* existingFoot = tFoot())
142         return *existingFoot;
143     auto foot = HTMLTableSectionElement::create(tfootTag, document());
144     setTFoot(foot.copyRef());
145     return foot;
146 }
147
148 void HTMLTableElement::deleteTFoot()
149 {
150     if (auto* foot = tFoot())
151         removeChild(*foot);
152 }
153
154 Ref<HTMLTableSectionElement> HTMLTableElement::createTBody()
155 {
156     auto body = HTMLTableSectionElement::create(tbodyTag, document());
157     RefPtr<Node> referenceElement = lastBody() ? lastBody()->nextSibling() : nullptr;
158     insertBefore(body, referenceElement.get());
159     return body;
160 }
161
162 Ref<HTMLTableCaptionElement> HTMLTableElement::createCaption()
163 {
164     if (auto* existingCaption = caption())
165         return *existingCaption;
166     auto caption = HTMLTableCaptionElement::create(captionTag, document());
167     setCaption(caption.copyRef());
168     return caption;
169 }
170
171 void HTMLTableElement::deleteCaption()
172 {
173     if (auto* caption = this->caption())
174         removeChild(*caption);
175 }
176
177 HTMLTableSectionElement* HTMLTableElement::lastBody() const
178 {
179     for (RefPtr<Node> child = lastChild(); child; child = child->previousSibling()) {
180         if (child->hasTagName(tbodyTag))
181             return downcast<HTMLTableSectionElement>(child.get());
182     }
183     return nullptr;
184 }
185
186 ExceptionOr<Ref<HTMLElement>> HTMLTableElement::insertRow(int index)
187 {
188     if (index < -1)
189         return Exception { IndexSizeError };
190
191     Ref<HTMLTableElement> protectedThis(*this);
192
193     RefPtr<HTMLTableRowElement> lastRow;
194     RefPtr<HTMLTableRowElement> row;
195     if (index == -1)
196         lastRow = HTMLTableRowsCollection::lastRow(*this);
197     else {
198         for (int i = 0; i <= index; ++i) {
199             row = HTMLTableRowsCollection::rowAfter(*this, lastRow.get());
200             if (!row) {
201                 if (i != index)
202                     return Exception { IndexSizeError };
203                 break;
204             }
205             lastRow = row;
206         }
207     }
208
209     RefPtr<ContainerNode> parent;
210     if (lastRow)
211         parent = row ? row->parentNode() : lastRow->parentNode();
212     else {
213         parent = lastBody();
214         if (!parent) {
215             auto newBody = HTMLTableSectionElement::create(tbodyTag, document());
216             auto newRow = HTMLTableRowElement::create(document());
217             newBody->appendChild(newRow);
218             // FIXME: Why ignore the exception if the first appendChild failed?
219             auto result = appendChild(newBody);
220             if (result.hasException())
221                 return result.releaseException();
222             return Ref<HTMLElement> { WTFMove(newRow) };
223         }
224     }
225
226     auto newRow = HTMLTableRowElement::create(document());
227     auto result = parent->insertBefore(newRow, row.get());
228     if (result.hasException())
229         return result.releaseException();
230     return Ref<HTMLElement> { WTFMove(newRow) };
231 }
232
233 ExceptionOr<void> HTMLTableElement::deleteRow(int index)
234 {
235     RefPtr<HTMLTableRowElement> row;
236     if (index == -1) {
237         row = HTMLTableRowsCollection::lastRow(*this);
238         if (!row)
239             return { };
240     } else {
241         for (int i = 0; i <= index; ++i) {
242             row = HTMLTableRowsCollection::rowAfter(*this, row.get());
243             if (!row)
244                 break;
245         }
246         if (!row)
247             return Exception { IndexSizeError };
248     }
249     return row->remove();
250 }
251
252 static inline bool isTableCellAncestor(const Element& element)
253 {
254     return element.hasTagName(theadTag)
255         || element.hasTagName(tbodyTag)
256         || element.hasTagName(tfootTag)
257         || element.hasTagName(trTag)
258         || element.hasTagName(thTag);
259 }
260
261 static bool setTableCellsChanged(Element& element)
262 {
263     bool cellChanged = false;
264
265     if (element.hasTagName(tdTag))
266         cellChanged = true;
267     else if (isTableCellAncestor(element)) {
268         for (auto& child : childrenOfType<Element>(element))
269             cellChanged |= setTableCellsChanged(child);
270     }
271
272     if (cellChanged)
273         element.invalidateStyleForSubtree();
274
275     return cellChanged;
276 }
277
278 static bool getBordersFromFrameAttributeValue(const AtomicString& value, bool& borderTop, bool& borderRight, bool& borderBottom, bool& borderLeft)
279 {
280     borderTop = false;
281     borderRight = false;
282     borderBottom = false;
283     borderLeft = false;
284
285     if (equalLettersIgnoringASCIICase(value, "above"))
286         borderTop = true;
287     else if (equalLettersIgnoringASCIICase(value, "below"))
288         borderBottom = true;
289     else if (equalLettersIgnoringASCIICase(value, "hsides"))
290         borderTop = borderBottom = true;
291     else if (equalLettersIgnoringASCIICase(value, "vsides"))
292         borderLeft = borderRight = true;
293     else if (equalLettersIgnoringASCIICase(value, "lhs"))
294         borderLeft = true;
295     else if (equalLettersIgnoringASCIICase(value, "rhs"))
296         borderRight = true;
297     else if (equalLettersIgnoringASCIICase(value, "box") || equalLettersIgnoringASCIICase(value, "border"))
298         borderTop = borderBottom = borderLeft = borderRight = true;
299     else if (!equalLettersIgnoringASCIICase(value, "void"))
300         return false;
301     return true;
302 }
303
304 void HTMLTableElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStyleProperties& style)
305 {
306     if (name == widthAttr)
307         addHTMLLengthToStyle(style, CSSPropertyWidth, value);
308     else if (name == heightAttr)
309         addHTMLLengthToStyle(style, CSSPropertyHeight, value);
310     else if (name == borderAttr) 
311         addPropertyToPresentationAttributeStyle(style, CSSPropertyBorderWidth, parseBorderWidthAttribute(value), CSSPrimitiveValue::CSS_PX);
312     else if (name == bordercolorAttr) {
313         if (!value.isEmpty())
314             addHTMLColorToStyle(style, CSSPropertyBorderColor, value);
315     } else if (name == bgcolorAttr)
316         addHTMLColorToStyle(style, CSSPropertyBackgroundColor, value);
317     else if (name == backgroundAttr) {
318         String url = stripLeadingAndTrailingHTMLSpaces(value);
319         if (!url.isEmpty())
320             style.setProperty(CSSProperty(CSSPropertyBackgroundImage, CSSImageValue::create(document().completeURL(url))));
321     } else if (name == valignAttr) {
322         if (!value.isEmpty())
323             addPropertyToPresentationAttributeStyle(style, CSSPropertyVerticalAlign, value);
324     } else if (name == cellspacingAttr) {
325         if (!value.isEmpty())
326             addHTMLLengthToStyle(style, CSSPropertyBorderSpacing, value);
327     } else if (name == vspaceAttr) {
328         addHTMLLengthToStyle(style, CSSPropertyMarginTop, value);
329         addHTMLLengthToStyle(style, CSSPropertyMarginBottom, value);
330     } else if (name == hspaceAttr) {
331         addHTMLLengthToStyle(style, CSSPropertyMarginLeft, value);
332         addHTMLLengthToStyle(style, CSSPropertyMarginRight, value);
333     } else if (name == alignAttr) {
334         if (!value.isEmpty()) {
335             if (equalLettersIgnoringASCIICase(value, "center")) {
336                 addPropertyToPresentationAttributeStyle(style, CSSPropertyWebkitMarginStart, CSSValueAuto);
337                 addPropertyToPresentationAttributeStyle(style, CSSPropertyWebkitMarginEnd, CSSValueAuto);
338             } else
339                 addPropertyToPresentationAttributeStyle(style, CSSPropertyFloat, value);
340         }
341     } else if (name == rulesAttr) {
342         // The presence of a valid rules attribute causes border collapsing to be enabled.
343         if (m_rulesAttr != UnsetRules)
344             addPropertyToPresentationAttributeStyle(style, CSSPropertyBorderCollapse, CSSValueCollapse);
345     } else if (name == frameAttr) {
346         bool borderTop;
347         bool borderRight;
348         bool borderBottom;
349         bool borderLeft;
350         if (getBordersFromFrameAttributeValue(value, borderTop, borderRight, borderBottom, borderLeft)) {
351             addPropertyToPresentationAttributeStyle(style, CSSPropertyBorderWidth, CSSValueThin);
352             addPropertyToPresentationAttributeStyle(style, CSSPropertyBorderTopStyle, borderTop ? CSSValueSolid : CSSValueHidden);
353             addPropertyToPresentationAttributeStyle(style, CSSPropertyBorderBottomStyle, borderBottom ? CSSValueSolid : CSSValueHidden);
354             addPropertyToPresentationAttributeStyle(style, CSSPropertyBorderLeftStyle, borderLeft ? CSSValueSolid : CSSValueHidden);
355             addPropertyToPresentationAttributeStyle(style, CSSPropertyBorderRightStyle, borderRight ? CSSValueSolid : CSSValueHidden);
356         }
357     } else
358         HTMLElement::collectStyleForPresentationAttribute(name, value, style);
359 }
360
361 bool HTMLTableElement::isPresentationAttribute(const QualifiedName& name) const
362 {
363     if (name == widthAttr || name == heightAttr || name == bgcolorAttr || name == backgroundAttr || name == valignAttr || name == vspaceAttr || name == hspaceAttr || name == alignAttr || name == cellspacingAttr || name == borderAttr || name == bordercolorAttr || name == frameAttr || name == rulesAttr)
364         return true;
365     return HTMLElement::isPresentationAttribute(name);
366 }
367
368 void HTMLTableElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
369 {
370     CellBorders bordersBefore = cellBorders();
371     unsigned short oldPadding = m_padding;
372
373     if (name == borderAttr)  {
374         // FIXME: This attribute is a mess.
375         m_borderAttr = parseBorderWidthAttribute(value);
376     } else if (name == bordercolorAttr) {
377         m_borderColorAttr = !value.isEmpty();
378     } else if (name == frameAttr) {
379         // FIXME: This attribute is a mess.
380         bool borderTop;
381         bool borderRight;
382         bool borderBottom;
383         bool borderLeft;
384         m_frameAttr = getBordersFromFrameAttributeValue(value, borderTop, borderRight, borderBottom, borderLeft);
385     } else if (name == rulesAttr) {
386         m_rulesAttr = UnsetRules;
387         if (equalLettersIgnoringASCIICase(value, "none"))
388             m_rulesAttr = NoneRules;
389         else if (equalLettersIgnoringASCIICase(value, "groups"))
390             m_rulesAttr = GroupsRules;
391         else if (equalLettersIgnoringASCIICase(value, "rows"))
392             m_rulesAttr = RowsRules;
393         else if (equalLettersIgnoringASCIICase(value, "cols"))
394             m_rulesAttr = ColsRules;
395         else if (equalLettersIgnoringASCIICase(value, "all"))
396             m_rulesAttr = AllRules;
397     } else if (name == cellpaddingAttr) {
398         if (!value.isEmpty())
399             m_padding = std::max(0, value.toInt());
400         else
401             m_padding = 1;
402     } else if (name == colsAttr) {
403         // ###
404     } else
405         HTMLElement::parseAttribute(name, value);
406
407     if (bordersBefore != cellBorders() || oldPadding != m_padding) {
408         m_sharedCellStyle = nullptr;
409         bool cellChanged = false;
410         for (auto& child : childrenOfType<Element>(*this))
411             cellChanged |= setTableCellsChanged(child);
412         if (cellChanged)
413             invalidateStyleForSubtree();
414     }
415 }
416
417 static StyleProperties* leakBorderStyle(CSSValueID value)
418 {
419     auto style = MutableStyleProperties::create();
420     style->setProperty(CSSPropertyBorderTopStyle, value);
421     style->setProperty(CSSPropertyBorderBottomStyle, value);
422     style->setProperty(CSSPropertyBorderLeftStyle, value);
423     style->setProperty(CSSPropertyBorderRightStyle, value);
424     return &style.leakRef();
425 }
426
427 const StyleProperties* HTMLTableElement::additionalPresentationAttributeStyle() const
428 {
429     if (m_frameAttr)
430         return 0;
431     
432     if (!m_borderAttr && !m_borderColorAttr) {
433         // Setting the border to 'hidden' allows it to win over any border
434         // set on the table's cells during border-conflict resolution.
435         if (m_rulesAttr != UnsetRules) {
436             static StyleProperties* solidBorderStyle = leakBorderStyle(CSSValueHidden);
437             return solidBorderStyle;
438         }
439         return 0;
440     }
441
442     if (m_borderColorAttr) {
443         static StyleProperties* solidBorderStyle = leakBorderStyle(CSSValueSolid);
444         return solidBorderStyle;
445     }
446     static StyleProperties* outsetBorderStyle = leakBorderStyle(CSSValueOutset);
447     return outsetBorderStyle;
448 }
449
450 HTMLTableElement::CellBorders HTMLTableElement::cellBorders() const
451 {
452     switch (m_rulesAttr) {
453         case NoneRules:
454         case GroupsRules:
455             return NoBorders;
456         case AllRules:
457             return SolidBorders;
458         case ColsRules:
459             return SolidBordersColsOnly;
460         case RowsRules:
461             return SolidBordersRowsOnly;
462         case UnsetRules:
463             if (!m_borderAttr)
464                 return NoBorders;
465             if (m_borderColorAttr)
466                 return SolidBorders;
467             return InsetBorders;
468     }
469     ASSERT_NOT_REACHED();
470     return NoBorders;
471 }
472
473 RefPtr<StyleProperties> HTMLTableElement::createSharedCellStyle()
474 {
475     RefPtr<MutableStyleProperties> style = MutableStyleProperties::create();
476
477     auto& cssValuePool = CSSValuePool::singleton();
478     switch (cellBorders()) {
479     case SolidBordersColsOnly:
480         style->setProperty(CSSPropertyBorderLeftWidth, CSSValueThin);
481         style->setProperty(CSSPropertyBorderRightWidth, CSSValueThin);
482         style->setProperty(CSSPropertyBorderLeftStyle, CSSValueSolid);
483         style->setProperty(CSSPropertyBorderRightStyle, CSSValueSolid);
484         style->setProperty(CSSPropertyBorderColor, cssValuePool.createInheritedValue());
485         break;
486     case SolidBordersRowsOnly:
487         style->setProperty(CSSPropertyBorderTopWidth, CSSValueThin);
488         style->setProperty(CSSPropertyBorderBottomWidth, CSSValueThin);
489         style->setProperty(CSSPropertyBorderTopStyle, CSSValueSolid);
490         style->setProperty(CSSPropertyBorderBottomStyle, CSSValueSolid);
491         style->setProperty(CSSPropertyBorderColor, cssValuePool.createInheritedValue());
492         break;
493     case SolidBorders:
494         style->setProperty(CSSPropertyBorderWidth, cssValuePool.createValue(1, CSSPrimitiveValue::CSS_PX));
495         style->setProperty(CSSPropertyBorderStyle, cssValuePool.createIdentifierValue(CSSValueSolid));
496         style->setProperty(CSSPropertyBorderColor, cssValuePool.createInheritedValue());
497         break;
498     case InsetBorders:
499         style->setProperty(CSSPropertyBorderWidth, cssValuePool.createValue(1, CSSPrimitiveValue::CSS_PX));
500         style->setProperty(CSSPropertyBorderStyle, cssValuePool.createIdentifierValue(CSSValueInset));
501         style->setProperty(CSSPropertyBorderColor, cssValuePool.createInheritedValue());
502         break;
503     case NoBorders:
504         // If 'rules=none' then allow any borders set at cell level to take effect. 
505         break;
506     }
507
508     if (m_padding)
509         style->setProperty(CSSPropertyPadding, cssValuePool.createValue(m_padding, CSSPrimitiveValue::CSS_PX));
510
511     return style;
512 }
513
514 const StyleProperties* HTMLTableElement::additionalCellStyle()
515 {
516     if (!m_sharedCellStyle)
517         m_sharedCellStyle = createSharedCellStyle();
518     return m_sharedCellStyle.get();
519 }
520
521 static StyleProperties* leakGroupBorderStyle(int rows)
522 {
523     auto style = MutableStyleProperties::create();
524     if (rows) {
525         style->setProperty(CSSPropertyBorderTopWidth, CSSValueThin);
526         style->setProperty(CSSPropertyBorderBottomWidth, CSSValueThin);
527         style->setProperty(CSSPropertyBorderTopStyle, CSSValueSolid);
528         style->setProperty(CSSPropertyBorderBottomStyle, CSSValueSolid);
529     } else {
530         style->setProperty(CSSPropertyBorderLeftWidth, CSSValueThin);
531         style->setProperty(CSSPropertyBorderRightWidth, CSSValueThin);
532         style->setProperty(CSSPropertyBorderLeftStyle, CSSValueSolid);
533         style->setProperty(CSSPropertyBorderRightStyle, CSSValueSolid);
534     }
535     return &style.leakRef();
536 }
537
538 const StyleProperties* HTMLTableElement::additionalGroupStyle(bool rows)
539 {
540     if (m_rulesAttr != GroupsRules)
541         return 0;
542
543     if (rows) {
544         static StyleProperties* rowBorderStyle = leakGroupBorderStyle(true);
545         return rowBorderStyle;
546     }
547     static StyleProperties* columnBorderStyle = leakGroupBorderStyle(false);
548     return columnBorderStyle;
549 }
550
551 bool HTMLTableElement::isURLAttribute(const Attribute& attribute) const
552 {
553     return attribute.name() == backgroundAttr || HTMLElement::isURLAttribute(attribute);
554 }
555
556 Ref<HTMLCollection> HTMLTableElement::rows()
557 {
558     return ensureRareData().ensureNodeLists().addCachedCollection<HTMLTableRowsCollection>(*this, TableRows);
559 }
560
561 Ref<HTMLCollection> HTMLTableElement::tBodies()
562 {
563     return ensureRareData().ensureNodeLists().addCachedCollection<GenericCachedHTMLCollection<CollectionTypeTraits<TableTBodies>::traversalType>>(*this, TableTBodies);
564 }
565
566 const AtomicString& HTMLTableElement::rules() const
567 {
568     return attributeWithoutSynchronization(rulesAttr);
569 }
570
571 const AtomicString& HTMLTableElement::summary() const
572 {
573     return attributeWithoutSynchronization(summaryAttr);
574 }
575
576 void HTMLTableElement::addSubresourceAttributeURLs(ListHashSet<URL>& urls) const
577 {
578     HTMLElement::addSubresourceAttributeURLs(urls);
579
580     addSubresourceURL(urls, document().completeURL(attributeWithoutSynchronization(backgroundAttr)));
581 }
582
583 }