HTMLTableElement.cpp (34830B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/dom/HTMLTableElement.h" 8 9 #include "jsfriendapi.h" 10 #include "mozilla/AttributeStyles.h" 11 #include "mozilla/DeclarationBlock.h" 12 #include "mozilla/MappedDeclarationsBuilder.h" 13 #include "mozilla/dom/Document.h" 14 #include "mozilla/dom/HTMLCollectionBinding.h" 15 #include "mozilla/dom/HTMLTableElementBinding.h" 16 #include "nsAttrValueInlines.h" 17 #include "nsContentUtils.h" 18 #include "nsLayoutUtils.h" 19 #include "nsWrapperCacheInlines.h" 20 21 NS_IMPL_NS_NEW_HTML_ELEMENT(Table) 22 23 namespace mozilla::dom { 24 25 /* ------------------------- TableRowsCollection --------------------------- */ 26 /** 27 * This class provides a late-bound collection of rows in a table. 28 * mParent is NOT ref-counted to avoid circular references 29 */ 30 class TableRowsCollection final : public nsIHTMLCollection, 31 public nsStubMutationObserver, 32 public nsWrapperCache { 33 public: 34 explicit TableRowsCollection(HTMLTableElement* aParent); 35 36 NS_DECL_CYCLE_COLLECTING_ISUPPORTS 37 38 NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED 39 NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED 40 NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED 41 NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED 42 43 uint32_t Length() override; 44 Element* GetElementAt(uint32_t aIndex) override; 45 nsINode* GetParentObject() override { return mParent; } 46 47 Element* GetFirstNamedElement(const nsAString& aName, bool& aFound) override; 48 void GetSupportedNames(nsTArray<nsString>& aNames) override; 49 50 NS_IMETHOD ParentDestroyed(); 51 52 NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS_AMBIGUOUS(TableRowsCollection, 53 nsIHTMLCollection) 54 55 // nsWrapperCache 56 using nsWrapperCache::GetWrapperPreserveColor; 57 using nsWrapperCache::PreserveWrapper; 58 JSObject* WrapObject(JSContext*, JS::Handle<JSObject*> aGivenProto) override; 59 60 protected: 61 // Unregister ourselves as a mutation observer, and clear our internal state. 62 void CleanUp(); 63 void LastRelease() { CleanUp(); } 64 virtual ~TableRowsCollection() { 65 // we do NOT have a ref-counted reference to mParent, so do NOT 66 // release it! this is to avoid circular references. The 67 // instantiator who provided mParent is responsible for managing our 68 // reference for us. 69 CleanUp(); 70 } 71 72 JSObject* GetWrapperPreserveColorInternal() override { 73 return nsWrapperCache::GetWrapperPreserveColor(); 74 } 75 void PreserveWrapperInternal(nsISupports* aScriptObjectHolder) override { 76 nsWrapperCache::PreserveWrapper(aScriptObjectHolder); 77 } 78 79 // Ensure that HTMLTableElement is in a valid state. This must be called 80 // before inspecting the mRows object. 81 void EnsureInitialized(); 82 83 // Checks if the passed-in container is interesting for the purposes of 84 // invalidation due to a mutation observer. 85 bool InterestingContainer(nsIContent* aContainer); 86 87 // Check if the passed-in nsIContent is a <tr> within the section defined by 88 // `aSection`. The root of the table is considered to be part of the `<tbody>` 89 // section. 90 bool IsAppropriateRow(nsAtom* aSection, nsIContent* aContent); 91 92 // Scan backwards starting from `aCurrent` in the table, looking for the 93 // previous row in the table which is within the section `aSection`. 94 nsIContent* PreviousRow(nsAtom* aSection, nsIContent* aCurrent); 95 96 // Handle the insertion of the child `aChild` into the container `aContainer` 97 // within the tree. The container must be an `InterestingContainer`. This 98 // method updates the mRows, mBodyStart, and mFootStart member variables. 99 // 100 // HandleInsert returns an integer which can be passed to the next call of the 101 // method in a loop inserting children into the same container. This will 102 // optimize subsequent insertions to require less work. This can either be -1, 103 // in which case we don't know where to insert the next row, and When passed 104 // to HandleInsert, it will use `PreviousRow` to locate the index to insert. 105 // Or, it can be an index to insert the next <tr> in the same container at. 106 int32_t HandleInsert(nsIContent* aContainer, nsIContent* aChild, 107 int32_t aIndexGuess = -1); 108 109 // The HTMLTableElement which this TableRowsCollection tracks the rows for. 110 HTMLTableElement* mParent; 111 112 // The current state of the TableRowsCollection. mBodyStart and mFootStart are 113 // indices into mRows which represent the location of the first row in the 114 // body or foot section. If there are no rows in a section, the index points 115 // at the location where the first element in that section would be inserted. 116 nsTArray<nsCOMPtr<nsIContent>> mRows; 117 uint32_t mBodyStart; 118 uint32_t mFootStart; 119 bool mInitialized; 120 }; 121 122 TableRowsCollection::TableRowsCollection(HTMLTableElement* aParent) 123 : mParent(aParent), mBodyStart(0), mFootStart(0), mInitialized(false) { 124 MOZ_ASSERT(mParent); 125 } 126 127 void TableRowsCollection::EnsureInitialized() { 128 if (mInitialized) { 129 return; 130 } 131 mInitialized = true; 132 133 // Initialize mRows as the TableRowsCollection is created. The mutation 134 // observer should keep it up to date. 135 // 136 // It should be extremely unlikely that anyone creates a TableRowsCollection 137 // without calling a method on it, so lazily performing this initialization 138 // seems unnecessary. 139 AutoTArray<nsCOMPtr<nsIContent>, 32> body; 140 AutoTArray<nsCOMPtr<nsIContent>, 32> foot; 141 mRows.Clear(); 142 143 auto addRowChildren = [&](nsTArray<nsCOMPtr<nsIContent>>& aArray, 144 nsIContent* aNode) { 145 for (nsIContent* inner = aNode->nsINode::GetFirstChild(); inner; 146 inner = inner->GetNextSibling()) { 147 if (inner->IsHTMLElement(nsGkAtoms::tr)) { 148 aArray.AppendElement(inner); 149 } 150 } 151 }; 152 153 for (nsIContent* node = mParent->nsINode::GetFirstChild(); node; 154 node = node->GetNextSibling()) { 155 if (node->IsHTMLElement(nsGkAtoms::thead)) { 156 addRowChildren(mRows, node); 157 } else if (node->IsHTMLElement(nsGkAtoms::tbody)) { 158 addRowChildren(body, node); 159 } else if (node->IsHTMLElement(nsGkAtoms::tfoot)) { 160 addRowChildren(foot, node); 161 } else if (node->IsHTMLElement(nsGkAtoms::tr)) { 162 body.AppendElement(node); 163 } 164 } 165 166 mBodyStart = mRows.Length(); 167 mRows.AppendElements(std::move(body)); 168 mFootStart = mRows.Length(); 169 mRows.AppendElements(std::move(foot)); 170 171 mParent->AddMutationObserver(this); 172 } 173 174 void TableRowsCollection::CleanUp() { 175 // Unregister ourselves as a mutation observer. 176 if (mInitialized && mParent) { 177 mParent->RemoveMutationObserver(this); 178 } 179 180 // Clean up all of our internal state and make it empty in case someone looks 181 // at us. 182 mRows.Clear(); 183 mBodyStart = 0; 184 mFootStart = 0; 185 186 // We set mInitialized to true in case someone still has a reference to us, as 187 // we don't need to try to initialize first. 188 mInitialized = true; 189 mParent = nullptr; 190 } 191 192 JSObject* TableRowsCollection::WrapObject(JSContext* aCx, 193 JS::Handle<JSObject*> aGivenProto) { 194 return HTMLCollection_Binding::Wrap(aCx, this, aGivenProto); 195 } 196 197 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TableRowsCollection, mRows) 198 NS_IMPL_CYCLE_COLLECTING_ADDREF(TableRowsCollection) 199 NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(TableRowsCollection, 200 LastRelease()) 201 202 NS_INTERFACE_TABLE_HEAD(TableRowsCollection) 203 NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY 204 NS_INTERFACE_TABLE(TableRowsCollection, nsIHTMLCollection, 205 nsIMutationObserver) 206 NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(TableRowsCollection) 207 NS_INTERFACE_MAP_END 208 209 uint32_t TableRowsCollection::Length() { 210 EnsureInitialized(); 211 return mRows.Length(); 212 } 213 214 Element* TableRowsCollection::GetElementAt(uint32_t aIndex) { 215 EnsureInitialized(); 216 if (aIndex < mRows.Length()) { 217 return mRows[aIndex]->AsElement(); 218 } 219 return nullptr; 220 } 221 222 Element* TableRowsCollection::GetFirstNamedElement(const nsAString& aName, 223 bool& aFound) { 224 EnsureInitialized(); 225 aFound = false; 226 RefPtr<nsAtom> nameAtom = NS_Atomize(aName); 227 NS_ENSURE_TRUE(nameAtom, nullptr); 228 229 for (auto& node : mRows) { 230 if (node->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, 231 nameAtom, eCaseMatters) || 232 node->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::id, 233 nameAtom, eCaseMatters)) { 234 aFound = true; 235 return node->AsElement(); 236 } 237 } 238 239 return nullptr; 240 } 241 242 void TableRowsCollection::GetSupportedNames(nsTArray<nsString>& aNames) { 243 EnsureInitialized(); 244 for (auto& node : mRows) { 245 if (node->HasID()) { 246 nsAtom* idAtom = node->GetID(); 247 MOZ_ASSERT(idAtom != nsGkAtoms::_empty, "Empty ids don't get atomized"); 248 nsDependentAtomString idStr(idAtom); 249 if (!aNames.Contains(idStr)) { 250 aNames.AppendElement(idStr); 251 } 252 } 253 254 nsGenericHTMLElement* el = nsGenericHTMLElement::FromNode(node); 255 if (el) { 256 const nsAttrValue* val = el->GetParsedAttr(nsGkAtoms::name); 257 if (val && val->Type() == nsAttrValue::eAtom) { 258 nsAtom* nameAtom = val->GetAtomValue(); 259 MOZ_ASSERT(nameAtom != nsGkAtoms::_empty, 260 "Empty names don't get atomized"); 261 nsDependentAtomString nameStr(nameAtom); 262 if (!aNames.Contains(nameStr)) { 263 aNames.AppendElement(nameStr); 264 } 265 } 266 } 267 } 268 } 269 270 NS_IMETHODIMP 271 TableRowsCollection::ParentDestroyed() { 272 CleanUp(); 273 return NS_OK; 274 } 275 276 bool TableRowsCollection::InterestingContainer(nsIContent* aContainer) { 277 return mParent && aContainer && 278 (aContainer == mParent || 279 (aContainer->GetParent() == mParent && 280 aContainer->IsAnyOfHTMLElements(nsGkAtoms::thead, nsGkAtoms::tbody, 281 nsGkAtoms::tfoot))); 282 } 283 284 bool TableRowsCollection::IsAppropriateRow(nsAtom* aSection, 285 nsIContent* aContent) { 286 if (!aContent->IsHTMLElement(nsGkAtoms::tr)) { 287 return false; 288 } 289 // If it's in the root, then we consider it to be in a tbody. 290 nsIContent* parent = aContent->GetParent(); 291 if (aSection == nsGkAtoms::tbody && parent == mParent) { 292 return true; 293 } 294 return parent->IsHTMLElement(aSection); 295 } 296 297 nsIContent* TableRowsCollection::PreviousRow(nsAtom* aSection, 298 nsIContent* aCurrent) { 299 // Keep going backwards until we've found a `tr` element. We want to always 300 // run at least once, as we don't want to find ourselves. 301 // 302 // Each spin of the loop we step backwards one element. If we're at the top of 303 // a section, we step out of it into the root, and if we step onto a section 304 // matching `aSection`, we step into it. We keep spinning the loop until 305 // either we reach the first element in mParent, or find a <tr> in an 306 // appropriate section. 307 nsIContent* prev = aCurrent; 308 do { 309 nsIContent* parent = prev->GetParent(); 310 prev = prev->GetPreviousSibling(); 311 312 // Ascend out of any sections we're currently in, if we've run out of 313 // elements. 314 if (!prev && parent != mParent) { 315 prev = parent->GetPreviousSibling(); 316 } 317 318 // Descend into a section if we stepped onto one. 319 if (prev && prev->GetParent() == mParent && prev->IsHTMLElement(aSection)) { 320 prev = prev->GetLastChild(); 321 } 322 } while (prev && !IsAppropriateRow(aSection, prev)); 323 return prev; 324 } 325 326 int32_t TableRowsCollection::HandleInsert(nsIContent* aContainer, 327 nsIContent* aChild, 328 int32_t aIndexGuess) { 329 if (!nsContentUtils::IsInSameAnonymousTree(mParent, aChild)) { 330 return aIndexGuess; // Nothing inserted, guess hasn't changed. 331 } 332 333 // If we're adding a section to the root, add each of the rows in that section 334 // individually. 335 if (aContainer == mParent && 336 aChild->IsAnyOfHTMLElements(nsGkAtoms::thead, nsGkAtoms::tbody, 337 nsGkAtoms::tfoot)) { 338 // If we're entering a tbody, we can persist the index guess we were passed, 339 // as the newly added items are in the same section as us, however, if we're 340 // entering thead or tfoot we will have to re-scan. 341 bool isTBody = aChild->IsHTMLElement(nsGkAtoms::tbody); 342 int32_t indexGuess = isTBody ? aIndexGuess : -1; 343 344 for (nsIContent* inner = aChild->GetFirstChild(); inner; 345 inner = inner->GetNextSibling()) { 346 indexGuess = HandleInsert(aChild, inner, indexGuess); 347 } 348 349 return isTBody ? indexGuess : -1; 350 } 351 if (!aChild->IsHTMLElement(nsGkAtoms::tr)) { 352 return aIndexGuess; // Nothing inserted, guess hasn't changed. 353 } 354 355 // We should have only been passed an insertion from an interesting container, 356 // so we can get the container we're inserting to fairly easily. 357 nsAtom* section = aContainer == mParent ? nsGkAtoms::tbody 358 : aContainer->NodeInfo()->NameAtom(); 359 360 // Determine the default index we would to insert after if we don't find any 361 // previous row, and offset our section boundaries based on the section we're 362 // planning to insert into. 363 size_t index = 0; 364 if (section == nsGkAtoms::thead) { 365 mBodyStart++; 366 mFootStart++; 367 } else if (section == nsGkAtoms::tbody) { 368 index = mBodyStart; 369 mFootStart++; 370 } else if (section == nsGkAtoms::tfoot) { 371 index = mFootStart; 372 } else { 373 MOZ_ASSERT(false, "section should be one of thead, tbody, or tfoot"); 374 } 375 376 // If we already have an index guess, we can skip scanning for the previous 377 // row. 378 if (aIndexGuess >= 0) { 379 index = aIndexGuess; 380 } else { 381 // Find the previous row in the section we're inserting into. If we find it, 382 // we can use it to override our insertion index. We don't need to modify 383 // mBodyStart or mFootStart anymore, as they have already been correctly 384 // updated based only on section. 385 nsIContent* insertAfter = PreviousRow(section, aChild); 386 if (insertAfter) { 387 // NOTE: We want to ensure that appending elements is quick, so we search 388 // from the end rather than from the beginning. 389 index = mRows.LastIndexOf(insertAfter) + 1; 390 MOZ_ASSERT(index != nsTArray<nsCOMPtr<nsIContent>>::NoIndex); 391 } 392 } 393 394 #ifdef DEBUG 395 // Assert that we're inserting into the correct section. 396 if (section == nsGkAtoms::thead) { 397 MOZ_ASSERT(index < mBodyStart); 398 } else if (section == nsGkAtoms::tbody) { 399 MOZ_ASSERT(index >= mBodyStart); 400 MOZ_ASSERT(index < mFootStart); 401 } else if (section == nsGkAtoms::tfoot) { 402 MOZ_ASSERT(index >= mFootStart); 403 MOZ_ASSERT(index <= mRows.Length()); 404 } 405 406 MOZ_ASSERT(mBodyStart <= mFootStart); 407 MOZ_ASSERT(mFootStart <= mRows.Length() + 1); 408 #endif 409 410 mRows.InsertElementAt(index, aChild); 411 return index + 1; 412 } 413 414 // nsIMutationObserver 415 416 void TableRowsCollection::ContentAppended(nsIContent* aFirstNewContent, 417 const ContentAppendInfo&) { 418 nsIContent* container = aFirstNewContent->GetParent(); 419 if (!nsContentUtils::IsInSameAnonymousTree(mParent, aFirstNewContent) || 420 !InterestingContainer(container)) { 421 return; 422 } 423 424 // We usually can't guess where we need to start inserting, unless we're 425 // appending into mParent, in which case we can provide the guess that we 426 // should insert at the end of the body, which can help us avoid potentially 427 // expensive work in the common case. 428 int32_t indexGuess = mParent == container ? mFootStart : -1; 429 430 // Insert each of the newly added content one at a time. The indexGuess should 431 // make insertions of a large number of elements cheaper. 432 for (nsIContent* content = aFirstNewContent; content; 433 content = content->GetNextSibling()) { 434 indexGuess = HandleInsert(container, content, indexGuess); 435 } 436 } 437 438 void TableRowsCollection::ContentInserted(nsIContent* aChild, 439 const ContentInsertInfo&) { 440 if (!nsContentUtils::IsInSameAnonymousTree(mParent, aChild) || 441 !InterestingContainer(aChild->GetParent())) { 442 return; 443 } 444 445 HandleInsert(aChild->GetParent(), aChild); 446 } 447 448 void TableRowsCollection::ContentWillBeRemoved(nsIContent* aChild, 449 const ContentRemoveInfo&) { 450 if (!nsContentUtils::IsInSameAnonymousTree(mParent, aChild) || 451 !InterestingContainer(aChild->GetParent())) { 452 return; 453 } 454 455 // If the element being removed is a `tr`, we can just remove it from our 456 // list. It shouldn't change the order of anything. 457 if (aChild->IsHTMLElement(nsGkAtoms::tr)) { 458 size_t index = mRows.IndexOf(aChild); 459 if (index != nsTArray<nsCOMPtr<nsIContent>>::NoIndex) { 460 mRows.RemoveElementAt(index); 461 if (mBodyStart > index) { 462 mBodyStart--; 463 } 464 if (mFootStart > index) { 465 mFootStart--; 466 } 467 } 468 return; 469 } 470 471 // If the element being removed is a `thead`, `tbody`, or `tfoot`, we can 472 // remove any `tr`s in our list which have that element as its parent node. In 473 // any other situation, the removal won't affect us, so we can ignore it. 474 if (!aChild->IsAnyOfHTMLElements(nsGkAtoms::thead, nsGkAtoms::tbody, 475 nsGkAtoms::tfoot)) { 476 return; 477 } 478 479 size_t beforeLength = mRows.Length(); 480 mRows.RemoveElementsBy( 481 [&](nsIContent* element) { return element->GetParent() == aChild; }); 482 size_t removed = beforeLength - mRows.Length(); 483 if (aChild->IsHTMLElement(nsGkAtoms::thead)) { 484 // NOTE: Need to move both tbody and tfoot, as we removed from head. 485 mBodyStart -= removed; 486 mFootStart -= removed; 487 } else if (aChild->IsHTMLElement(nsGkAtoms::tbody)) { 488 // NOTE: Need to move tfoot, as we removed from body. 489 mFootStart -= removed; 490 } 491 } 492 493 void TableRowsCollection::NodeWillBeDestroyed(nsINode* aNode) { 494 // Set mInitialized to false so CleanUp doesn't try to remove our mutation 495 // observer, as we're going away. CleanUp() will reset mInitialized to true as 496 // it returns. 497 mInitialized = false; 498 CleanUp(); 499 } 500 501 /* --------------------------- HTMLTableElement ---------------------------- */ 502 503 HTMLTableElement::HTMLTableElement( 504 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) 505 : nsGenericHTMLElement(std::move(aNodeInfo)) { 506 SetHasWeirdParserInsertionMode(); 507 } 508 509 HTMLTableElement::~HTMLTableElement() { 510 if (mRows) { 511 mRows->ParentDestroyed(); 512 } 513 ReleaseInheritedAttributes(); 514 } 515 516 JSObject* HTMLTableElement::WrapNode(JSContext* aCx, 517 JS::Handle<JSObject*> aGivenProto) { 518 return HTMLTableElement_Binding::Wrap(aCx, this, aGivenProto); 519 } 520 521 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLTableElement) 522 523 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLTableElement, 524 nsGenericHTMLElement) 525 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTBodies) 526 if (tmp->mRows) { 527 tmp->mRows->ParentDestroyed(); 528 } 529 NS_IMPL_CYCLE_COLLECTION_UNLINK(mRows) 530 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 531 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLTableElement, 532 nsGenericHTMLElement) 533 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTBodies) 534 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRows) 535 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 536 537 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(HTMLTableElement, 538 nsGenericHTMLElement) 539 540 NS_IMPL_ELEMENT_CLONE(HTMLTableElement) 541 542 // the DOM spec says border, cellpadding, cellSpacing are all "wstring" 543 // in fact, they are integers or they are meaningless. so we store them 544 // here as ints. 545 546 nsIHTMLCollection* HTMLTableElement::Rows() { 547 if (!mRows) { 548 mRows = new TableRowsCollection(this); 549 } 550 551 return mRows; 552 } 553 554 nsIHTMLCollection* HTMLTableElement::TBodies() { 555 if (!mTBodies) { 556 // Not using NS_GetContentList because this should not be cached 557 mTBodies = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::tbody, 558 nsGkAtoms::tbody, false); 559 } 560 561 return mTBodies; 562 } 563 564 already_AddRefed<nsGenericHTMLElement> HTMLTableElement::CreateTHead() { 565 RefPtr<nsGenericHTMLElement> head = GetTHead(); 566 if (!head) { 567 // Create a new head rowgroup. 568 RefPtr<mozilla::dom::NodeInfo> nodeInfo; 569 nsContentUtils::QNameChanged(mNodeInfo, nsGkAtoms::thead, 570 getter_AddRefs(nodeInfo)); 571 572 head = NS_NewHTMLTableSectionElement(nodeInfo.forget()); 573 if (!head) { 574 return nullptr; 575 } 576 577 nsCOMPtr<nsIContent> refNode = nullptr; 578 for (refNode = nsINode::GetFirstChild(); refNode; 579 refNode = refNode->GetNextSibling()) { 580 if (refNode->IsHTMLElement() && 581 !refNode->IsHTMLElement(nsGkAtoms::caption) && 582 !refNode->IsHTMLElement(nsGkAtoms::colgroup)) { 583 break; 584 } 585 } 586 587 nsINode::InsertBefore(*head, refNode, IgnoreErrors()); 588 } 589 return head.forget(); 590 } 591 592 void HTMLTableElement::DeleteTHead() { 593 RefPtr<HTMLTableSectionElement> tHead = GetTHead(); 594 if (tHead) { 595 mozilla::IgnoredErrorResult rv; 596 nsINode::RemoveChild(*tHead, rv); 597 } 598 } 599 600 already_AddRefed<nsGenericHTMLElement> HTMLTableElement::CreateTFoot() { 601 RefPtr<nsGenericHTMLElement> foot = GetTFoot(); 602 if (!foot) { 603 // create a new foot rowgroup 604 RefPtr<mozilla::dom::NodeInfo> nodeInfo; 605 nsContentUtils::QNameChanged(mNodeInfo, nsGkAtoms::tfoot, 606 getter_AddRefs(nodeInfo)); 607 608 foot = NS_NewHTMLTableSectionElement(nodeInfo.forget()); 609 if (!foot) { 610 return nullptr; 611 } 612 AppendChildTo(foot, true, IgnoreErrors()); 613 } 614 615 return foot.forget(); 616 } 617 618 void HTMLTableElement::DeleteTFoot() { 619 RefPtr<HTMLTableSectionElement> tFoot = GetTFoot(); 620 if (tFoot) { 621 mozilla::IgnoredErrorResult rv; 622 nsINode::RemoveChild(*tFoot, rv); 623 } 624 } 625 626 already_AddRefed<nsGenericHTMLElement> HTMLTableElement::CreateCaption() { 627 RefPtr<nsGenericHTMLElement> caption = GetCaption(); 628 if (!caption) { 629 // Create a new caption. 630 RefPtr<mozilla::dom::NodeInfo> nodeInfo; 631 nsContentUtils::QNameChanged(mNodeInfo, nsGkAtoms::caption, 632 getter_AddRefs(nodeInfo)); 633 634 caption = NS_NewHTMLTableCaptionElement(nodeInfo.forget()); 635 if (!caption) { 636 return nullptr; 637 } 638 639 nsCOMPtr<nsINode> firsChild = nsINode::GetFirstChild(); 640 nsINode::InsertBefore(*caption, firsChild, IgnoreErrors()); 641 } 642 return caption.forget(); 643 } 644 645 void HTMLTableElement::DeleteCaption() { 646 RefPtr<HTMLTableCaptionElement> caption = GetCaption(); 647 if (caption) { 648 mozilla::IgnoredErrorResult rv; 649 nsINode::RemoveChild(*caption, rv); 650 } 651 } 652 653 already_AddRefed<nsGenericHTMLElement> HTMLTableElement::CreateTBody() { 654 RefPtr<mozilla::dom::NodeInfo> nodeInfo = 655 OwnerDoc()->NodeInfoManager()->GetNodeInfo( 656 nsGkAtoms::tbody, nullptr, kNameSpaceID_XHTML, ELEMENT_NODE); 657 MOZ_ASSERT(nodeInfo); 658 659 RefPtr<nsGenericHTMLElement> newBody = 660 NS_NewHTMLTableSectionElement(nodeInfo.forget()); 661 MOZ_ASSERT(newBody); 662 663 nsCOMPtr<nsIContent> referenceNode = nullptr; 664 for (nsIContent* child = nsINode::GetLastChild(); child; 665 child = child->GetPreviousSibling()) { 666 if (child->IsHTMLElement(nsGkAtoms::tbody)) { 667 referenceNode = child->GetNextSibling(); 668 break; 669 } 670 } 671 672 nsINode::InsertBefore(*newBody, referenceNode, IgnoreErrors()); 673 674 return newBody.forget(); 675 } 676 677 already_AddRefed<nsGenericHTMLElement> HTMLTableElement::InsertRow( 678 int32_t aIndex, ErrorResult& aError) { 679 /* get the ref row at aIndex 680 if there is one, 681 get its parent 682 insert the new row just before the ref row 683 else 684 get the first row group 685 insert the new row as its first child 686 */ 687 if (aIndex < -1) { 688 aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); 689 return nullptr; 690 } 691 692 nsIHTMLCollection* rows = Rows(); 693 uint32_t rowCount = rows->Length(); 694 if ((uint32_t)aIndex > rowCount && aIndex != -1) { 695 aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); 696 return nullptr; 697 } 698 699 // use local variable refIndex so we can remember original aIndex 700 uint32_t refIndex = (uint32_t)aIndex; 701 702 RefPtr<nsGenericHTMLElement> newRow; 703 if (rowCount > 0) { 704 if (refIndex == rowCount || aIndex == -1) { 705 // we set refIndex to the last row so we can get the last row's 706 // parent we then do an AppendChild below if (rowCount<aIndex) 707 708 refIndex = rowCount - 1; 709 } 710 711 RefPtr<Element> refRow = rows->Item(refIndex); 712 nsCOMPtr<nsINode> parent = refRow->GetParentNode(); 713 714 // create the row 715 RefPtr<mozilla::dom::NodeInfo> nodeInfo; 716 nsContentUtils::QNameChanged(mNodeInfo, nsGkAtoms::tr, 717 getter_AddRefs(nodeInfo)); 718 719 newRow = NS_NewHTMLTableRowElement(nodeInfo.forget()); 720 721 if (newRow) { 722 // If aIndex is -1 or equal to the number of rows, the new row 723 // is appended. 724 if (aIndex == -1 || uint32_t(aIndex) == rowCount) { 725 parent->AppendChild(*newRow, aError); 726 } else { 727 // insert the new row before the reference row we found above 728 parent->InsertBefore(*newRow, refRow, aError); 729 } 730 731 if (aError.Failed()) { 732 return nullptr; 733 } 734 } 735 } else { 736 // the row count was 0, so 737 // find the last row group and insert there as first child 738 nsCOMPtr<nsIContent> rowGroup; 739 for (nsIContent* child = nsINode::GetLastChild(); child; 740 child = child->GetPreviousSibling()) { 741 if (child->IsHTMLElement(nsGkAtoms::tbody)) { 742 rowGroup = child; 743 break; 744 } 745 } 746 747 if (!rowGroup) { // need to create a TBODY 748 RefPtr<mozilla::dom::NodeInfo> nodeInfo; 749 nsContentUtils::QNameChanged(mNodeInfo, nsGkAtoms::tbody, 750 getter_AddRefs(nodeInfo)); 751 752 rowGroup = NS_NewHTMLTableSectionElement(nodeInfo.forget()); 753 if (rowGroup) { 754 AppendChildTo(rowGroup, true, aError); 755 if (aError.Failed()) { 756 return nullptr; 757 } 758 } 759 } 760 761 if (rowGroup) { 762 RefPtr<mozilla::dom::NodeInfo> nodeInfo; 763 nsContentUtils::QNameChanged(mNodeInfo, nsGkAtoms::tr, 764 getter_AddRefs(nodeInfo)); 765 766 newRow = NS_NewHTMLTableRowElement(nodeInfo.forget()); 767 if (newRow) { 768 HTMLTableSectionElement* section = 769 static_cast<HTMLTableSectionElement*>(rowGroup.get()); 770 nsIHTMLCollection* rows = section->Rows(); 771 nsCOMPtr<nsINode> refNode = rows->Item(0); 772 rowGroup->InsertBefore(*newRow, refNode, aError); 773 } 774 } 775 } 776 777 return newRow.forget(); 778 } 779 780 void HTMLTableElement::DeleteRow(int32_t aIndex, ErrorResult& aError) { 781 if (aIndex < -1) { 782 aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); 783 return; 784 } 785 786 nsIHTMLCollection* rows = Rows(); 787 uint32_t refIndex; 788 if (aIndex == -1) { 789 refIndex = rows->Length(); 790 if (refIndex == 0) { 791 return; 792 } 793 794 --refIndex; 795 } else { 796 refIndex = (uint32_t)aIndex; 797 } 798 799 nsCOMPtr<nsIContent> row = rows->Item(refIndex); 800 if (!row) { 801 aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); 802 return; 803 } 804 805 row->RemoveFromParent(); 806 } 807 808 bool HTMLTableElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, 809 const nsAString& aValue, 810 nsIPrincipal* aMaybeScriptedPrincipal, 811 nsAttrValue& aResult) { 812 /* ignore summary, just a string */ 813 if (aNamespaceID == kNameSpaceID_None) { 814 if (aAttribute == nsGkAtoms::cellspacing || 815 aAttribute == nsGkAtoms::cellpadding || 816 aAttribute == nsGkAtoms::border) { 817 return aResult.ParseNonNegativeIntValue(aValue); 818 } 819 if (aAttribute == nsGkAtoms::height) { 820 // Purposeful spec violation (spec says to use ParseNonzeroHTMLDimension) 821 // to stay compatible with our old behavior and other browsers. See 822 // https://github.com/whatwg/html/issues/4715 823 return aResult.ParseHTMLDimension(aValue); 824 } 825 if (aAttribute == nsGkAtoms::width) { 826 return aResult.ParseNonzeroHTMLDimension(aValue); 827 } 828 829 if (aAttribute == nsGkAtoms::align) { 830 return ParseTableHAlignValue(aValue, aResult); 831 } 832 if (aAttribute == nsGkAtoms::bgcolor || 833 aAttribute == nsGkAtoms::bordercolor) { 834 return aResult.ParseColor(aValue); 835 } 836 } 837 838 return nsGenericHTMLElement::ParseBackgroundAttribute( 839 aNamespaceID, aAttribute, aValue, aResult) || 840 nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue, 841 aMaybeScriptedPrincipal, aResult); 842 } 843 844 void HTMLTableElement::MapAttributesIntoRule( 845 MappedDeclarationsBuilder& aBuilder) { 846 // XXX Bug 211636: This function is used by a single style rule 847 // that's used to match two different type of elements -- tables, and 848 // table cells. (nsHTMLTableCellElement overrides 849 // WalkContentStyleRules so that this happens.) This violates the 850 // nsIStyleRule contract, since it's the same style rule object doing 851 // the mapping in two different ways. It's also incorrect since it's 852 // testing the display type of the ComputedStyle rather than checking 853 // which *element* it's matching (style rules should not stop matching 854 // when the display type is changed). 855 856 // cellspacing 857 const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::cellspacing); 858 if (value && value->Type() == nsAttrValue::eInteger && 859 !aBuilder.PropertyIsSet(eCSSProperty_border_spacing)) { 860 aBuilder.SetPixelValue(eCSSProperty_border_spacing, 861 float(value->GetIntegerValue())); 862 } 863 864 // bordercolor 865 value = aBuilder.GetAttr(nsGkAtoms::bordercolor); 866 nscolor color; 867 if (value && value->GetColorValue(color)) { 868 aBuilder.SetColorValueIfUnset(eCSSProperty_border_top_color, color); 869 aBuilder.SetColorValueIfUnset(eCSSProperty_border_left_color, color); 870 aBuilder.SetColorValueIfUnset(eCSSProperty_border_bottom_color, color); 871 aBuilder.SetColorValueIfUnset(eCSSProperty_border_right_color, color); 872 } 873 874 // border 875 if (const nsAttrValue* borderValue = aBuilder.GetAttr(nsGkAtoms::border)) { 876 // border = 1 pixel default 877 int32_t borderThickness = 1; 878 if (borderValue->Type() == nsAttrValue::eInteger) { 879 borderThickness = borderValue->GetIntegerValue(); 880 } 881 882 // by default, set all border sides to the specified width 883 aBuilder.SetPixelValueIfUnset(eCSSProperty_border_top_width, 884 (float)borderThickness); 885 aBuilder.SetPixelValueIfUnset(eCSSProperty_border_left_width, 886 (float)borderThickness); 887 aBuilder.SetPixelValueIfUnset(eCSSProperty_border_bottom_width, 888 (float)borderThickness); 889 aBuilder.SetPixelValueIfUnset(eCSSProperty_border_right_width, 890 (float)borderThickness); 891 } 892 893 nsGenericHTMLElement::MapTableHAlignAttributeInto(aBuilder); 894 nsGenericHTMLElement::MapImageSizeAttributesInto(aBuilder); 895 nsGenericHTMLElement::MapBackgroundAttributesInto(aBuilder); 896 nsGenericHTMLElement::MapCommonAttributesInto(aBuilder); 897 } 898 899 NS_IMETHODIMP_(bool) 900 HTMLTableElement::IsAttributeMapped(const nsAtom* aAttribute) const { 901 static const MappedAttributeEntry attributes[] = { 902 {nsGkAtoms::cellpadding}, {nsGkAtoms::cellspacing}, 903 {nsGkAtoms::border}, {nsGkAtoms::width}, 904 {nsGkAtoms::height}, 905 906 {nsGkAtoms::bordercolor}, 907 908 {nsGkAtoms::align}, {nullptr}}; 909 910 static const MappedAttributeEntry* const map[] = { 911 attributes, 912 sCommonAttributeMap, 913 sBackgroundAttributeMap, 914 }; 915 916 return FindAttributeDependence(aAttribute, map); 917 } 918 919 nsMapRuleToAttributesFunc HTMLTableElement::GetAttributeMappingFunction() 920 const { 921 return &MapAttributesIntoRule; 922 } 923 924 void HTMLTableElement::BuildInheritedAttributes() { 925 MOZ_ASSERT(!mTableInheritedAttributes, "potential leak, plus waste of work"); 926 MOZ_ASSERT(NS_IsMainThread()); 927 Document* document = GetComposedDoc(); 928 if (!document) { 929 return; 930 } 931 const nsAttrValue* value = GetParsedAttr(nsGkAtoms::cellpadding); 932 if (!value || value->Type() != nsAttrValue::eInteger) { 933 return; 934 } 935 // We have cellpadding. This will override our padding values if we don't 936 // have any set. 937 float pad = float(value->GetIntegerValue()); 938 MappedDeclarationsBuilder builder(*this, *document); 939 builder.SetPixelValue(eCSSProperty_padding_top, pad); 940 builder.SetPixelValue(eCSSProperty_padding_right, pad); 941 builder.SetPixelValue(eCSSProperty_padding_bottom, pad); 942 builder.SetPixelValue(eCSSProperty_padding_left, pad); 943 mTableInheritedAttributes = builder.TakeDeclarationBlock(); 944 } 945 946 void HTMLTableElement::ReleaseInheritedAttributes() { 947 mTableInheritedAttributes = nullptr; 948 } 949 950 nsresult HTMLTableElement::BindToTree(BindContext& aContext, nsINode& aParent) { 951 ReleaseInheritedAttributes(); 952 nsresult rv = nsGenericHTMLElement::BindToTree(aContext, aParent); 953 NS_ENSURE_SUCCESS(rv, rv); 954 BuildInheritedAttributes(); 955 return NS_OK; 956 } 957 958 void HTMLTableElement::UnbindFromTree(UnbindContext& aContext) { 959 ReleaseInheritedAttributes(); 960 nsGenericHTMLElement::UnbindFromTree(aContext); 961 } 962 963 void HTMLTableElement::BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName, 964 const nsAttrValue* aValue, bool aNotify) { 965 if (aName == nsGkAtoms::cellpadding && aNameSpaceID == kNameSpaceID_None) { 966 ReleaseInheritedAttributes(); 967 } 968 return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID, aName, aValue, 969 aNotify); 970 } 971 972 void HTMLTableElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName, 973 const nsAttrValue* aValue, 974 const nsAttrValue* aOldValue, 975 nsIPrincipal* aSubjectPrincipal, 976 bool aNotify) { 977 if (aName == nsGkAtoms::cellpadding && aNameSpaceID == kNameSpaceID_None) { 978 BuildInheritedAttributes(); 979 // This affects our cell styles. 980 // TODO(emilio): Maybe GetAttributeChangeHint should also allow you to 981 // specify a restyle hint and this could move there? 982 nsLayoutUtils::PostRestyleEvent(this, RestyleHint::RestyleSubtree(), 983 nsChangeHint(0)); 984 } 985 return nsGenericHTMLElement::AfterSetAttr( 986 aNameSpaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify); 987 } 988 989 } // namespace mozilla::dom