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