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