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