1 /* 2 * Copyright (C) 1997 Martin Jones (mjones (at) kde.org) 3 * (C) 1997 Torben Weis (weis (at) kde.org) 4 * (C) 1998 Waldo Bastian (bastian (at) kde.org) 5 * (C) 1999 Lars Knoll (knoll (at) kde.org) 6 * (C) 1999 Antti Koivisto (koivisto (at) 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 "core/html/HTMLTableElement.h" 27 28 #include "CSSPropertyNames.h" 29 #include "CSSValueKeywords.h" 30 #include "HTMLNames.h" 31 #include "bindings/v8/ExceptionState.h" 32 #include "bindings/v8/ExceptionStatePlaceholder.h" 33 #include "core/css/CSSImageValue.h" 34 #include "core/css/CSSValuePool.h" 35 #include "core/css/StylePropertySet.h" 36 #include "core/dom/Attribute.h" 37 #include "core/dom/ExceptionCode.h" 38 #include "core/html/HTMLTableCaptionElement.h" 39 #include "core/html/HTMLTableRowElement.h" 40 #include "core/html/HTMLTableRowsCollection.h" 41 #include "core/html/HTMLTableSectionElement.h" 42 #include "core/html/parser/HTMLParserIdioms.h" 43 #include "core/rendering/RenderTable.h" 44 45 namespace WebCore { 46 47 using namespace HTMLNames; 48 49 HTMLTableElement::HTMLTableElement(const QualifiedName& tagName, Document* document) 50 : HTMLElement(tagName, document) 51 , m_borderAttr(false) 52 , m_borderColorAttr(false) 53 , m_frameAttr(false) 54 , m_rulesAttr(UnsetRules) 55 , m_padding(1) 56 { 57 ASSERT(hasTagName(tableTag)); 58 ScriptWrappable::init(this); 59 } 60 61 PassRefPtr<HTMLTableElement> HTMLTableElement::create(Document* document) 62 { 63 return adoptRef(new HTMLTableElement(tableTag, document)); 64 } 65 66 PassRefPtr<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 (child->hasTagName(captionTag)) 75 return static_cast<HTMLTableCaptionElement*>(child); 76 } 77 return 0; 78 } 79 80 void HTMLTableElement::setCaption(PassRefPtr<HTMLTableCaptionElement> newCaption, ExceptionState& es) 81 { 82 deleteCaption(); 83 insertBefore(newCaption, firstChild(), es); 84 } 85 86 HTMLTableSectionElement* HTMLTableElement::tHead() const 87 { 88 for (Node* child = firstChild(); child; child = child->nextSibling()) { 89 if (child->hasTagName(theadTag)) 90 return static_cast<HTMLTableSectionElement*>(child); 91 } 92 return 0; 93 } 94 95 void HTMLTableElement::setTHead(PassRefPtr<HTMLTableSectionElement> newHead, ExceptionState& es) 96 { 97 deleteTHead(); 98 99 Node* child; 100 for (child = firstChild(); child; child = child->nextSibling()) 101 if (child->isElementNode() && !child->hasTagName(captionTag) && !child->hasTagName(colgroupTag)) 102 break; 103 104 insertBefore(newHead, child, es); 105 } 106 107 HTMLTableSectionElement* HTMLTableElement::tFoot() const 108 { 109 for (Node* child = firstChild(); child; child = child->nextSibling()) { 110 if (child->hasTagName(tfootTag)) 111 return static_cast<HTMLTableSectionElement*>(child); 112 } 113 return 0; 114 } 115 116 void HTMLTableElement::setTFoot(PassRefPtr<HTMLTableSectionElement> newFoot, ExceptionState& es) 117 { 118 deleteTFoot(); 119 120 Node* child; 121 for (child = firstChild(); child; child = child->nextSibling()) 122 if (child->isElementNode() && !child->hasTagName(captionTag) && !child->hasTagName(colgroupTag) && !child->hasTagName(theadTag)) 123 break; 124 125 insertBefore(newFoot, child, es); 126 } 127 128 PassRefPtr<HTMLElement> HTMLTableElement::createTHead() 129 { 130 if (HTMLTableSectionElement* existingHead = tHead()) 131 return existingHead; 132 RefPtr<HTMLTableSectionElement> head = HTMLTableSectionElement::create(theadTag, document()); 133 setTHead(head, IGNORE_EXCEPTION); 134 return head.release(); 135 } 136 137 void HTMLTableElement::deleteTHead() 138 { 139 removeChild(tHead(), IGNORE_EXCEPTION); 140 } 141 142 PassRefPtr<HTMLElement> HTMLTableElement::createTFoot() 143 { 144 if (HTMLTableSectionElement* existingFoot = tFoot()) 145 return existingFoot; 146 RefPtr<HTMLTableSectionElement> foot = HTMLTableSectionElement::create(tfootTag, document()); 147 setTFoot(foot, IGNORE_EXCEPTION); 148 return foot.release(); 149 } 150 151 void HTMLTableElement::deleteTFoot() 152 { 153 removeChild(tFoot(), IGNORE_EXCEPTION); 154 } 155 156 PassRefPtr<HTMLElement> HTMLTableElement::createTBody() 157 { 158 RefPtr<HTMLTableSectionElement> body = HTMLTableSectionElement::create(tbodyTag, document()); 159 Node* referenceElement = lastBody() ? lastBody()->nextSibling() : 0; 160 161 insertBefore(body, referenceElement, ASSERT_NO_EXCEPTION); 162 return body.release(); 163 } 164 165 PassRefPtr<HTMLElement> HTMLTableElement::createCaption() 166 { 167 if (HTMLTableCaptionElement* existingCaption = caption()) 168 return existingCaption; 169 RefPtr<HTMLTableCaptionElement> caption = HTMLTableCaptionElement::create(captionTag, document()); 170 setCaption(caption, IGNORE_EXCEPTION); 171 return caption.release(); 172 } 173 174 void HTMLTableElement::deleteCaption() 175 { 176 removeChild(caption(), IGNORE_EXCEPTION); 177 } 178 179 HTMLTableSectionElement* HTMLTableElement::lastBody() const 180 { 181 for (Node* child = lastChild(); child; child = child->previousSibling()) { 182 if (child->hasTagName(tbodyTag)) 183 return static_cast<HTMLTableSectionElement*>(child); 184 } 185 return 0; 186 } 187 188 PassRefPtr<HTMLElement> HTMLTableElement::insertRow(int index, ExceptionState& es) 189 { 190 if (index < -1) { 191 es.throwDOMException(IndexSizeError); 192 return 0; 193 } 194 195 RefPtr<Node> protectFromMutationEvents(this); 196 197 RefPtr<HTMLTableRowElement> lastRow = 0; 198 RefPtr<HTMLTableRowElement> row = 0; 199 if (index == -1) 200 lastRow = HTMLTableRowsCollection::lastRow(this); 201 else { 202 for (int i = 0; i <= index; ++i) { 203 row = HTMLTableRowsCollection::rowAfter(this, lastRow.get()); 204 if (!row) { 205 if (i != index) { 206 es.throwDOMException(IndexSizeError); 207 return 0; 208 } 209 break; 210 } 211 lastRow = row; 212 } 213 } 214 215 RefPtr<ContainerNode> parent; 216 if (lastRow) 217 parent = row ? row->parentNode() : lastRow->parentNode(); 218 else { 219 parent = lastBody(); 220 if (!parent) { 221 RefPtr<HTMLTableSectionElement> newBody = HTMLTableSectionElement::create(tbodyTag, document()); 222 RefPtr<HTMLTableRowElement> newRow = HTMLTableRowElement::create(document()); 223 newBody->appendChild(newRow, es); 224 appendChild(newBody.release(), es, AttachLazily); 225 return newRow.release(); 226 } 227 } 228 229 RefPtr<HTMLTableRowElement> newRow = HTMLTableRowElement::create(document()); 230 parent->insertBefore(newRow, row.get(), es, AttachLazily); 231 return newRow.release(); 232 } 233 234 void HTMLTableElement::deleteRow(int index, ExceptionState& es) 235 { 236 HTMLTableRowElement* row = 0; 237 if (index == -1) 238 row = HTMLTableRowsCollection::lastRow(this); 239 else { 240 for (int i = 0; i <= index; ++i) { 241 row = HTMLTableRowsCollection::rowAfter(this, row); 242 if (!row) 243 break; 244 } 245 } 246 if (!row) { 247 es.throwDOMException(IndexSizeError); 248 return; 249 } 250 row->remove(es); 251 } 252 253 static inline bool isTableCellAncestor(Node* n) 254 { 255 return n->hasTagName(theadTag) || n->hasTagName(tbodyTag) || 256 n->hasTagName(tfootTag) || n->hasTagName(trTag) || 257 n->hasTagName(thTag); 258 } 259 260 static bool setTableCellsChanged(Node* n) 261 { 262 ASSERT(n); 263 bool cellChanged = false; 264 265 if (n->hasTagName(tdTag)) 266 cellChanged = true; 267 else if (isTableCellAncestor(n)) { 268 for (Node* child = n->firstChild(); child; child = child->nextSibling()) 269 cellChanged |= setTableCellsChanged(child); 270 } 271 272 if (cellChanged) 273 n->setNeedsStyleRecalc(); 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 (equalIgnoringCase(value, "above")) 286 borderTop = true; 287 else if (equalIgnoringCase(value, "below")) 288 borderBottom = true; 289 else if (equalIgnoringCase(value, "hsides")) 290 borderTop = borderBottom = true; 291 else if (equalIgnoringCase(value, "vsides")) 292 borderLeft = borderRight = true; 293 else if (equalIgnoringCase(value, "lhs")) 294 borderLeft = true; 295 else if (equalIgnoringCase(value, "rhs")) 296 borderRight = true; 297 else if (equalIgnoringCase(value, "box") || equalIgnoringCase(value, "border")) 298 borderTop = borderBottom = borderLeft = borderRight = true; 299 else if (!equalIgnoringCase(value, "void")) 300 return false; 301 return true; 302 } 303 304 void HTMLTableElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStylePropertySet* 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).string()))); 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 (equalIgnoringCase(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 (equalIgnoringCase(value, "none")) 388 m_rulesAttr = NoneRules; 389 else if (equalIgnoringCase(value, "groups")) 390 m_rulesAttr = GroupsRules; 391 else if (equalIgnoringCase(value, "rows")) 392 m_rulesAttr = RowsRules; 393 else if (equalIgnoringCase(value, "cols")) 394 m_rulesAttr = ColsRules; 395 else if (equalIgnoringCase(value, "all")) 396 m_rulesAttr = AllRules; 397 } else if (name == cellpaddingAttr) { 398 if (!value.isEmpty()) 399 m_padding = 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 = 0; 409 bool cellChanged = false; 410 for (Node* child = firstChild(); child; child = child->nextSibling()) 411 cellChanged |= setTableCellsChanged(child); 412 if (cellChanged) 413 setNeedsStyleRecalc(); 414 } 415 } 416 417 static StylePropertySet* leakBorderStyle(CSSValueID value) 418 { 419 RefPtr<MutableStylePropertySet> style = MutableStylePropertySet::create(); 420 style->setProperty(CSSPropertyBorderTopStyle, value); 421 style->setProperty(CSSPropertyBorderBottomStyle, value); 422 style->setProperty(CSSPropertyBorderLeftStyle, value); 423 style->setProperty(CSSPropertyBorderRightStyle, value); 424 return style.release().leakRef(); 425 } 426 427 const StylePropertySet* HTMLTableElement::additionalPresentationAttributeStyle() 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 StylePropertySet* solidBorderStyle = leakBorderStyle(CSSValueHidden); 437 return solidBorderStyle; 438 } 439 return 0; 440 } 441 442 if (m_borderColorAttr) { 443 static StylePropertySet* solidBorderStyle = leakBorderStyle(CSSValueSolid); 444 return solidBorderStyle; 445 } 446 static StylePropertySet* 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 PassRefPtr<StylePropertySet> HTMLTableElement::createSharedCellStyle() 474 { 475 RefPtr<MutableStylePropertySet> style = MutableStylePropertySet::create(); 476 477 switch (cellBorders()) { 478 case SolidBordersColsOnly: 479 style->setProperty(CSSPropertyBorderLeftWidth, CSSValueThin); 480 style->setProperty(CSSPropertyBorderRightWidth, CSSValueThin); 481 style->setProperty(CSSPropertyBorderLeftStyle, CSSValueSolid); 482 style->setProperty(CSSPropertyBorderRightStyle, CSSValueSolid); 483 style->setProperty(CSSPropertyBorderColor, cssValuePool().createInheritedValue()); 484 break; 485 case SolidBordersRowsOnly: 486 style->setProperty(CSSPropertyBorderTopWidth, CSSValueThin); 487 style->setProperty(CSSPropertyBorderBottomWidth, CSSValueThin); 488 style->setProperty(CSSPropertyBorderTopStyle, CSSValueSolid); 489 style->setProperty(CSSPropertyBorderBottomStyle, CSSValueSolid); 490 style->setProperty(CSSPropertyBorderColor, cssValuePool().createInheritedValue()); 491 break; 492 case SolidBorders: 493 style->setProperty(CSSPropertyBorderWidth, cssValuePool().createValue(1, CSSPrimitiveValue::CSS_PX)); 494 style->setProperty(CSSPropertyBorderStyle, cssValuePool().createIdentifierValue(CSSValueSolid)); 495 style->setProperty(CSSPropertyBorderColor, cssValuePool().createInheritedValue()); 496 break; 497 case InsetBorders: 498 style->setProperty(CSSPropertyBorderWidth, cssValuePool().createValue(1, CSSPrimitiveValue::CSS_PX)); 499 style->setProperty(CSSPropertyBorderStyle, cssValuePool().createIdentifierValue(CSSValueInset)); 500 style->setProperty(CSSPropertyBorderColor, cssValuePool().createInheritedValue()); 501 break; 502 case NoBorders: 503 // If 'rules=none' then allow any borders set at cell level to take effect. 504 break; 505 } 506 507 if (m_padding) 508 style->setProperty(CSSPropertyPadding, cssValuePool().createValue(m_padding, CSSPrimitiveValue::CSS_PX)); 509 510 return style.release(); 511 } 512 513 const StylePropertySet* HTMLTableElement::additionalCellStyle() 514 { 515 if (!m_sharedCellStyle) 516 m_sharedCellStyle = createSharedCellStyle(); 517 return m_sharedCellStyle.get(); 518 } 519 520 static StylePropertySet* leakGroupBorderStyle(int rows) 521 { 522 RefPtr<MutableStylePropertySet> style = MutableStylePropertySet::create(); 523 if (rows) { 524 style->setProperty(CSSPropertyBorderTopWidth, CSSValueThin); 525 style->setProperty(CSSPropertyBorderBottomWidth, CSSValueThin); 526 style->setProperty(CSSPropertyBorderTopStyle, CSSValueSolid); 527 style->setProperty(CSSPropertyBorderBottomStyle, CSSValueSolid); 528 } else { 529 style->setProperty(CSSPropertyBorderLeftWidth, CSSValueThin); 530 style->setProperty(CSSPropertyBorderRightWidth, CSSValueThin); 531 style->setProperty(CSSPropertyBorderLeftStyle, CSSValueSolid); 532 style->setProperty(CSSPropertyBorderRightStyle, CSSValueSolid); 533 } 534 return style.release().leakRef(); 535 } 536 537 const StylePropertySet* HTMLTableElement::additionalGroupStyle(bool rows) 538 { 539 if (m_rulesAttr != GroupsRules) 540 return 0; 541 542 if (rows) { 543 static StylePropertySet* rowBorderStyle = leakGroupBorderStyle(true); 544 return rowBorderStyle; 545 } 546 static StylePropertySet* columnBorderStyle = leakGroupBorderStyle(false); 547 return columnBorderStyle; 548 } 549 550 bool HTMLTableElement::isURLAttribute(const Attribute& attribute) const 551 { 552 return attribute.name() == backgroundAttr || HTMLElement::isURLAttribute(attribute); 553 } 554 555 PassRefPtr<HTMLCollection> HTMLTableElement::rows() 556 { 557 return ensureCachedHTMLCollection(TableRows); 558 } 559 560 PassRefPtr<HTMLCollection> HTMLTableElement::tBodies() 561 { 562 return ensureCachedHTMLCollection(TableTBodies); 563 } 564 565 String HTMLTableElement::rules() const 566 { 567 return getAttribute(rulesAttr); 568 } 569 570 String HTMLTableElement::summary() const 571 { 572 return getAttribute(summaryAttr); 573 } 574 575 void HTMLTableElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const 576 { 577 HTMLElement::addSubresourceAttributeURLs(urls); 578 579 addSubresourceURL(urls, document()->completeURL(getAttribute(backgroundAttr))); 580 } 581 582 } 583