Bug 1816170 - Disable perftest-on-autoland cron. r=aglavic
[gecko.git] / dom / html / HTMLTableElement.cpp
blobd6fc1573c277b395411929c98d525271ed9c832d
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/MappedDeclarations.h"
9 #include "nsAttrValueInlines.h"
10 #include "nsHTMLStyleSheet.h"
11 #include "nsMappedAttributes.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 "jsfriendapi.h"
19 NS_IMPL_NS_NEW_HTML_ELEMENT(Table)
21 namespace mozilla::dom {
23 /* ------------------------- TableRowsCollection --------------------------- */
24 /**
25 * This class provides a late-bound collection of rows in a table.
26 * mParent is NOT ref-counted to avoid circular references
28 class TableRowsCollection final : public nsIHTMLCollection,
29 public nsStubMutationObserver,
30 public nsWrapperCache {
31 public:
32 explicit TableRowsCollection(HTMLTableElement* aParent);
34 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
36 NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
37 NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
38 NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
39 NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
41 virtual uint32_t Length() override;
42 virtual Element* GetElementAt(uint32_t aIndex) override;
43 virtual nsINode* GetParentObject() override { return mParent; }
45 virtual Element* GetFirstNamedElement(const nsAString& aName,
46 bool& aFound) override;
47 virtual 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 virtual JSObject* WrapObject(JSContext* aCx,
58 JS::Handle<JSObject*> aGivenProto) override;
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();
72 virtual JSObject* GetWrapperPreserveColorInternal() override {
73 return nsWrapperCache::GetWrapperPreserveColor();
75 virtual void PreserveWrapperInternal(
76 nsISupports* aScriptObjectHolder) override {
77 nsWrapperCache::PreserveWrapper(aScriptObjectHolder);
80 // Ensure that HTMLTableElement is in a valid state. This must be called
81 // before inspecting the mRows object.
82 void EnsureInitialized();
84 // Checks if the passed-in container is interesting for the purposes of
85 // invalidation due to a mutation observer.
86 bool InterestingContainer(nsIContent* aContainer);
88 // Check if the passed-in nsIContent is a <tr> within the section defined by
89 // `aSection`. The root of the table is considered to be part of the `<tbody>`
90 // section.
91 bool IsAppropriateRow(nsAtom* aSection, nsIContent* aContent);
93 // Scan backwards starting from `aCurrent` in the table, looking for the
94 // previous row in the table which is within the section `aSection`.
95 nsIContent* PreviousRow(nsAtom* aSection, nsIContent* aCurrent);
97 // Handle the insertion of the child `aChild` into the container `aContainer`
98 // within the tree. The container must be an `InterestingContainer`. This
99 // method updates the mRows, mBodyStart, and mFootStart member variables.
101 // HandleInsert returns an integer which can be passed to the next call of the
102 // method in a loop inserting children into the same container. This will
103 // optimize subsequent insertions to require less work. This can either be -1,
104 // in which case we don't know where to insert the next row, and When passed
105 // to HandleInsert, it will use `PreviousRow` to locate the index to insert.
106 // Or, it can be an index to insert the next <tr> in the same container at.
107 int32_t HandleInsert(nsIContent* aContainer, nsIContent* aChild,
108 int32_t aIndexGuess = -1);
110 // The HTMLTableElement which this TableRowsCollection tracks the rows for.
111 HTMLTableElement* mParent;
113 // The current state of the TableRowsCollection. mBodyStart and mFootStart are
114 // indices into mRows which represent the location of the first row in the
115 // body or foot section. If there are no rows in a section, the index points
116 // at the location where the first element in that section would be inserted.
117 nsTArray<nsCOMPtr<nsIContent>> mRows;
118 uint32_t mBodyStart;
119 uint32_t mFootStart;
120 bool mInitialized;
123 TableRowsCollection::TableRowsCollection(HTMLTableElement* aParent)
124 : mParent(aParent), mBodyStart(0), mFootStart(0), mInitialized(false) {
125 MOZ_ASSERT(mParent);
128 void TableRowsCollection::EnsureInitialized() {
129 if (mInitialized) {
130 return;
132 mInitialized = true;
134 // Initialize mRows as the TableRowsCollection is created. The mutation
135 // observer should keep it up to date.
137 // It should be extremely unlikely that anyone creates a TableRowsCollection
138 // without calling a method on it, so lazily performing this initialization
139 // seems unnecessary.
140 AutoTArray<nsCOMPtr<nsIContent>, 32> body;
141 AutoTArray<nsCOMPtr<nsIContent>, 32> foot;
142 mRows.Clear();
144 auto addRowChildren = [&](nsTArray<nsCOMPtr<nsIContent>>& aArray,
145 nsIContent* aNode) {
146 for (nsIContent* inner = aNode->nsINode::GetFirstChild(); inner;
147 inner = inner->GetNextSibling()) {
148 if (inner->IsHTMLElement(nsGkAtoms::tr)) {
149 aArray.AppendElement(inner);
154 for (nsIContent* node = mParent->nsINode::GetFirstChild(); node;
155 node = node->GetNextSibling()) {
156 if (node->IsHTMLElement(nsGkAtoms::thead)) {
157 addRowChildren(mRows, node);
158 } else if (node->IsHTMLElement(nsGkAtoms::tbody)) {
159 addRowChildren(body, node);
160 } else if (node->IsHTMLElement(nsGkAtoms::tfoot)) {
161 addRowChildren(foot, node);
162 } else if (node->IsHTMLElement(nsGkAtoms::tr)) {
163 body.AppendElement(node);
167 mBodyStart = mRows.Length();
168 mRows.AppendElements(std::move(body));
169 mFootStart = mRows.Length();
170 mRows.AppendElements(std::move(foot));
172 mParent->AddMutationObserver(this);
175 void TableRowsCollection::CleanUp() {
176 // Unregister ourselves as a mutation observer.
177 if (mInitialized && mParent) {
178 mParent->RemoveMutationObserver(this);
181 // Clean up all of our internal state and make it empty in case someone looks
182 // at us.
183 mRows.Clear();
184 mBodyStart = 0;
185 mFootStart = 0;
187 // We set mInitialized to true in case someone still has a reference to us, as
188 // we don't need to try to initialize first.
189 mInitialized = true;
190 mParent = nullptr;
193 JSObject* TableRowsCollection::WrapObject(JSContext* aCx,
194 JS::Handle<JSObject*> aGivenProto) {
195 return HTMLCollection_Binding::Wrap(aCx, this, aGivenProto);
198 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TableRowsCollection, mRows)
199 NS_IMPL_CYCLE_COLLECTING_ADDREF(TableRowsCollection)
200 NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(TableRowsCollection,
201 LastRelease())
203 NS_INTERFACE_TABLE_HEAD(TableRowsCollection)
204 NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
205 NS_INTERFACE_TABLE(TableRowsCollection, nsIHTMLCollection,
206 nsIMutationObserver)
207 NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(TableRowsCollection)
208 NS_INTERFACE_MAP_END
210 uint32_t TableRowsCollection::Length() {
211 EnsureInitialized();
212 return mRows.Length();
215 Element* TableRowsCollection::GetElementAt(uint32_t aIndex) {
216 EnsureInitialized();
217 if (aIndex < mRows.Length()) {
218 return mRows[aIndex]->AsElement();
220 return nullptr;
223 Element* TableRowsCollection::GetFirstNamedElement(const nsAString& aName,
224 bool& aFound) {
225 EnsureInitialized();
226 aFound = false;
227 RefPtr<nsAtom> nameAtom = NS_Atomize(aName);
228 NS_ENSURE_TRUE(nameAtom, nullptr);
230 for (auto& node : mRows) {
231 if (node->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
232 nameAtom, eCaseMatters) ||
233 node->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::id,
234 nameAtom, eCaseMatters)) {
235 aFound = true;
236 return node->AsElement();
240 return nullptr;
243 void TableRowsCollection::GetSupportedNames(nsTArray<nsString>& aNames) {
244 EnsureInitialized();
245 for (auto& node : mRows) {
246 if (node->HasID()) {
247 nsAtom* idAtom = node->GetID();
248 MOZ_ASSERT(idAtom != nsGkAtoms::_empty, "Empty ids don't get atomized");
249 nsDependentAtomString idStr(idAtom);
250 if (!aNames.Contains(idStr)) {
251 aNames.AppendElement(idStr);
255 nsGenericHTMLElement* el = nsGenericHTMLElement::FromNode(node);
256 if (el) {
257 const nsAttrValue* val = el->GetParsedAttr(nsGkAtoms::name);
258 if (val && val->Type() == nsAttrValue::eAtom) {
259 nsAtom* nameAtom = val->GetAtomValue();
260 MOZ_ASSERT(nameAtom != nsGkAtoms::_empty,
261 "Empty names don't get atomized");
262 nsDependentAtomString nameStr(nameAtom);
263 if (!aNames.Contains(nameStr)) {
264 aNames.AppendElement(nameStr);
271 NS_IMETHODIMP
272 TableRowsCollection::ParentDestroyed() {
273 CleanUp();
274 return NS_OK;
277 bool TableRowsCollection::InterestingContainer(nsIContent* aContainer) {
278 return mParent && aContainer &&
279 (aContainer == mParent ||
280 (aContainer->GetParent() == mParent &&
281 aContainer->IsAnyOfHTMLElements(nsGkAtoms::thead, nsGkAtoms::tbody,
282 nsGkAtoms::tfoot)));
285 bool TableRowsCollection::IsAppropriateRow(nsAtom* aSection,
286 nsIContent* aContent) {
287 if (!aContent->IsHTMLElement(nsGkAtoms::tr)) {
288 return false;
290 // If it's in the root, then we consider it to be in a tbody.
291 nsIContent* parent = aContent->GetParent();
292 if (aSection == nsGkAtoms::tbody && parent == mParent) {
293 return true;
295 return parent->IsHTMLElement(aSection);
298 nsIContent* TableRowsCollection::PreviousRow(nsAtom* aSection,
299 nsIContent* aCurrent) {
300 // Keep going backwards until we've found a `tr` element. We want to always
301 // run at least once, as we don't want to find ourselves.
303 // Each spin of the loop we step backwards one element. If we're at the top of
304 // a section, we step out of it into the root, and if we step onto a section
305 // matching `aSection`, we step into it. We keep spinning the loop until
306 // either we reach the first element in mParent, or find a <tr> in an
307 // appropriate section.
308 nsIContent* prev = aCurrent;
309 do {
310 nsIContent* parent = prev->GetParent();
311 prev = prev->GetPreviousSibling();
313 // Ascend out of any sections we're currently in, if we've run out of
314 // elements.
315 if (!prev && parent != mParent) {
316 prev = parent->GetPreviousSibling();
319 // Descend into a section if we stepped onto one.
320 if (prev && prev->GetParent() == mParent && prev->IsHTMLElement(aSection)) {
321 prev = prev->GetLastChild();
323 } while (prev && !IsAppropriateRow(aSection, prev));
324 return prev;
327 int32_t TableRowsCollection::HandleInsert(nsIContent* aContainer,
328 nsIContent* aChild,
329 int32_t aIndexGuess) {
330 if (!nsContentUtils::IsInSameAnonymousTree(mParent, aChild)) {
331 return aIndexGuess; // Nothing inserted, guess hasn't changed.
334 // If we're adding a section to the root, add each of the rows in that section
335 // individually.
336 if (aContainer == mParent &&
337 aChild->IsAnyOfHTMLElements(nsGkAtoms::thead, nsGkAtoms::tbody,
338 nsGkAtoms::tfoot)) {
339 // If we're entering a tbody, we can persist the index guess we were passed,
340 // as the newly added items are in the same section as us, however, if we're
341 // entering thead or tfoot we will have to re-scan.
342 bool isTBody = aChild->IsHTMLElement(nsGkAtoms::tbody);
343 int32_t indexGuess = isTBody ? aIndexGuess : -1;
345 for (nsIContent* inner = aChild->GetFirstChild(); inner;
346 inner = inner->GetNextSibling()) {
347 indexGuess = HandleInsert(aChild, inner, indexGuess);
350 return isTBody ? indexGuess : -1;
352 if (!aChild->IsHTMLElement(nsGkAtoms::tr)) {
353 return aIndexGuess; // Nothing inserted, guess hasn't changed.
356 // We should have only been passed an insertion from an interesting container,
357 // so we can get the container we're inserting to fairly easily.
358 nsAtom* section = aContainer == mParent ? nsGkAtoms::tbody
359 : aContainer->NodeInfo()->NameAtom();
361 // Determine the default index we would to insert after if we don't find any
362 // previous row, and offset our section boundaries based on the section we're
363 // planning to insert into.
364 size_t index = 0;
365 if (section == nsGkAtoms::thead) {
366 mBodyStart++;
367 mFootStart++;
368 } else if (section == nsGkAtoms::tbody) {
369 index = mBodyStart;
370 mFootStart++;
371 } else if (section == nsGkAtoms::tfoot) {
372 index = mFootStart;
373 } else {
374 MOZ_ASSERT(false, "section should be one of thead, tbody, or tfoot");
377 // If we already have an index guess, we can skip scanning for the previous
378 // row.
379 if (aIndexGuess >= 0) {
380 index = aIndexGuess;
381 } else {
382 // Find the previous row in the section we're inserting into. If we find it,
383 // we can use it to override our insertion index. We don't need to modify
384 // mBodyStart or mFootStart anymore, as they have already been correctly
385 // updated based only on section.
386 nsIContent* insertAfter = PreviousRow(section, aChild);
387 if (insertAfter) {
388 // NOTE: We want to ensure that appending elements is quick, so we search
389 // from the end rather than from the beginning.
390 index = mRows.LastIndexOf(insertAfter) + 1;
391 MOZ_ASSERT(index != nsTArray<nsCOMPtr<nsIContent>>::NoIndex);
395 #ifdef DEBUG
396 // Assert that we're inserting into the correct section.
397 if (section == nsGkAtoms::thead) {
398 MOZ_ASSERT(index < mBodyStart);
399 } else if (section == nsGkAtoms::tbody) {
400 MOZ_ASSERT(index >= mBodyStart);
401 MOZ_ASSERT(index < mFootStart);
402 } else if (section == nsGkAtoms::tfoot) {
403 MOZ_ASSERT(index >= mFootStart);
404 MOZ_ASSERT(index <= mRows.Length());
407 MOZ_ASSERT(mBodyStart <= mFootStart);
408 MOZ_ASSERT(mFootStart <= mRows.Length() + 1);
409 #endif
411 mRows.InsertElementAt(index, aChild);
412 return index + 1;
415 // nsIMutationObserver
417 void TableRowsCollection::ContentAppended(nsIContent* aFirstNewContent) {
418 nsIContent* container = aFirstNewContent->GetParent();
419 if (!nsContentUtils::IsInSameAnonymousTree(mParent, aFirstNewContent) ||
420 !InterestingContainer(container)) {
421 return;
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;
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);
438 void TableRowsCollection::ContentInserted(nsIContent* aChild) {
439 if (!nsContentUtils::IsInSameAnonymousTree(mParent, aChild) ||
440 !InterestingContainer(aChild->GetParent())) {
441 return;
444 HandleInsert(aChild->GetParent(), aChild);
447 void TableRowsCollection::ContentRemoved(nsIContent* aChild,
448 nsIContent* aPreviousSibling) {
449 if (!nsContentUtils::IsInSameAnonymousTree(mParent, aChild) ||
450 !InterestingContainer(aChild->GetParent())) {
451 return;
454 // If the element being removed is a `tr`, we can just remove it from our
455 // list. It shouldn't change the order of anything.
456 if (aChild->IsHTMLElement(nsGkAtoms::tr)) {
457 size_t index = mRows.IndexOf(aChild);
458 if (index != nsTArray<nsCOMPtr<nsIContent>>::NoIndex) {
459 mRows.RemoveElementAt(index);
460 if (mBodyStart > index) {
461 mBodyStart--;
463 if (mFootStart > index) {
464 mFootStart--;
467 return;
470 // If the element being removed is a `thead`, `tbody`, or `tfoot`, we can
471 // remove any `tr`s in our list which have that element as its parent node. In
472 // any other situation, the removal won't affect us, so we can ignore it.
473 if (!aChild->IsAnyOfHTMLElements(nsGkAtoms::thead, nsGkAtoms::tbody,
474 nsGkAtoms::tfoot)) {
475 return;
478 size_t beforeLength = mRows.Length();
479 mRows.RemoveElementsBy(
480 [&](nsIContent* element) { return element->GetParent() == aChild; });
481 size_t removed = beforeLength - mRows.Length();
482 if (aChild->IsHTMLElement(nsGkAtoms::thead)) {
483 // NOTE: Need to move both tbody and tfoot, as we removed from head.
484 mBodyStart -= removed;
485 mFootStart -= removed;
486 } else if (aChild->IsHTMLElement(nsGkAtoms::tbody)) {
487 // NOTE: Need to move tfoot, as we removed from body.
488 mFootStart -= removed;
492 void TableRowsCollection::NodeWillBeDestroyed(nsINode* aNode) {
493 // Set mInitialized to false so CleanUp doesn't try to remove our mutation
494 // observer, as we're going away. CleanUp() will reset mInitialized to true as
495 // it returns.
496 mInitialized = false;
497 CleanUp();
500 /* --------------------------- HTMLTableElement ---------------------------- */
502 HTMLTableElement::HTMLTableElement(
503 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
504 : nsGenericHTMLElement(std::move(aNodeInfo)),
505 mTableInheritedAttributes(nullptr) {
506 SetHasWeirdParserInsertionMode();
509 HTMLTableElement::~HTMLTableElement() {
510 if (mRows) {
511 mRows->ParentDestroyed();
513 ReleaseInheritedAttributes();
516 JSObject* HTMLTableElement::WrapNode(JSContext* aCx,
517 JS::Handle<JSObject*> aGivenProto) {
518 return HTMLTableElement_Binding::Wrap(aCx, this, aGivenProto);
521 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLTableElement)
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();
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
537 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(HTMLTableElement,
538 nsGenericHTMLElement)
540 NS_IMPL_ELEMENT_CLONE(HTMLTableElement)
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.
546 nsIHTMLCollection* HTMLTableElement::Rows() {
547 if (!mRows) {
548 mRows = new TableRowsCollection(this);
551 return mRows;
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);
561 return mTBodies;
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));
572 head = NS_NewHTMLTableSectionElement(nodeInfo.forget());
573 if (!head) {
574 return nullptr;
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;
587 nsINode::InsertBefore(*head, refNode, IgnoreErrors());
589 return head.forget();
592 void HTMLTableElement::DeleteTHead() {
593 RefPtr<HTMLTableSectionElement> tHead = GetTHead();
594 if (tHead) {
595 mozilla::IgnoredErrorResult rv;
596 nsINode::RemoveChild(*tHead, rv);
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));
608 foot = NS_NewHTMLTableSectionElement(nodeInfo.forget());
609 if (!foot) {
610 return nullptr;
612 AppendChildTo(foot, true, IgnoreErrors());
615 return foot.forget();
618 void HTMLTableElement::DeleteTFoot() {
619 RefPtr<HTMLTableSectionElement> tFoot = GetTFoot();
620 if (tFoot) {
621 mozilla::IgnoredErrorResult rv;
622 nsINode::RemoveChild(*tFoot, rv);
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));
634 caption = NS_NewHTMLTableCaptionElement(nodeInfo.forget());
635 if (!caption) {
636 return nullptr;
639 nsCOMPtr<nsINode> firsChild = nsINode::GetFirstChild();
640 nsINode::InsertBefore(*caption, firsChild, IgnoreErrors());
642 return caption.forget();
645 void HTMLTableElement::DeleteCaption() {
646 RefPtr<HTMLTableCaptionElement> caption = GetCaption();
647 if (caption) {
648 mozilla::IgnoredErrorResult rv;
649 nsINode::RemoveChild(*caption, rv);
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);
659 RefPtr<nsGenericHTMLElement> newBody =
660 NS_NewHTMLTableSectionElement(nodeInfo.forget());
661 MOZ_ASSERT(newBody);
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;
672 nsINode::InsertBefore(*newBody, referenceNode, IgnoreErrors());
674 return newBody.forget();
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
687 if (aIndex < -1) {
688 aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
689 return nullptr;
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;
699 // use local variable refIndex so we can remember original aIndex
700 uint32_t refIndex = (uint32_t)aIndex;
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)
708 refIndex = rowCount - 1;
711 RefPtr<Element> refRow = rows->Item(refIndex);
712 nsCOMPtr<nsINode> parent = refRow->GetParentNode();
714 // create the row
715 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
716 nsContentUtils::QNameChanged(mNodeInfo, nsGkAtoms::tr,
717 getter_AddRefs(nodeInfo));
719 newRow = NS_NewHTMLTableRowElement(nodeInfo.forget());
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);
731 if (aError.Failed()) {
732 return nullptr;
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;
747 if (!rowGroup) { // need to create a TBODY
748 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
749 nsContentUtils::QNameChanged(mNodeInfo, nsGkAtoms::tbody,
750 getter_AddRefs(nodeInfo));
752 rowGroup = NS_NewHTMLTableSectionElement(nodeInfo.forget());
753 if (rowGroup) {
754 AppendChildTo(rowGroup, true, aError);
755 if (aError.Failed()) {
756 return nullptr;
761 if (rowGroup) {
762 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
763 nsContentUtils::QNameChanged(mNodeInfo, nsGkAtoms::tr,
764 getter_AddRefs(nodeInfo));
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);
777 return newRow.forget();
780 void HTMLTableElement::DeleteRow(int32_t aIndex, ErrorResult& aError) {
781 if (aIndex < -1) {
782 aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
783 return;
786 nsIHTMLCollection* rows = Rows();
787 uint32_t refIndex;
788 if (aIndex == -1) {
789 refIndex = rows->Length();
790 if (refIndex == 0) {
791 return;
794 --refIndex;
795 } else {
796 refIndex = (uint32_t)aIndex;
799 nsCOMPtr<nsIContent> row = rows->Item(refIndex);
800 if (!row) {
801 aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
802 return;
805 row->RemoveFromParent();
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);
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);
825 if (aAttribute == nsGkAtoms::width) {
826 return aResult.ParseNonzeroHTMLDimension(aValue);
829 if (aAttribute == nsGkAtoms::align) {
830 return ParseTableHAlignValue(aValue, aResult);
832 if (aAttribute == nsGkAtoms::bgcolor ||
833 aAttribute == nsGkAtoms::bordercolor) {
834 return aResult.ParseColor(aValue);
838 return nsGenericHTMLElement::ParseBackgroundAttribute(
839 aNamespaceID, aAttribute, aValue, aResult) ||
840 nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
841 aMaybeScriptedPrincipal, aResult);
844 void HTMLTableElement::MapAttributesIntoRule(
845 const nsMappedAttributes* aAttributes, MappedDeclarations& aDecls) {
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).
856 // cellspacing
857 const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::cellspacing);
858 if (value && value->Type() == nsAttrValue::eInteger &&
859 !aDecls.PropertyIsSet(eCSSProperty_border_spacing)) {
860 aDecls.SetPixelValue(eCSSProperty_border_spacing,
861 float(value->GetIntegerValue()));
863 // align; Check for enumerated type (it may be another type if
864 // illegal)
865 value = aAttributes->GetAttr(nsGkAtoms::align);
866 if (value && value->Type() == nsAttrValue::eEnum) {
867 if (value->GetEnumValue() == uint8_t(StyleTextAlign::Center) ||
868 value->GetEnumValue() == uint8_t(StyleTextAlign::MozCenter)) {
869 aDecls.SetAutoValueIfUnset(eCSSProperty_margin_left);
870 aDecls.SetAutoValueIfUnset(eCSSProperty_margin_right);
874 // bordercolor
875 value = aAttributes->GetAttr(nsGkAtoms::bordercolor);
876 nscolor color;
877 if (value && value->GetColorValue(color)) {
878 aDecls.SetColorValueIfUnset(eCSSProperty_border_top_color, color);
879 aDecls.SetColorValueIfUnset(eCSSProperty_border_left_color, color);
880 aDecls.SetColorValueIfUnset(eCSSProperty_border_bottom_color, color);
881 aDecls.SetColorValueIfUnset(eCSSProperty_border_right_color, color);
884 // border
885 const nsAttrValue* borderValue = aAttributes->GetAttr(nsGkAtoms::border);
886 if (borderValue) {
887 // border = 1 pixel default
888 int32_t borderThickness = 1;
890 if (borderValue->Type() == nsAttrValue::eInteger)
891 borderThickness = borderValue->GetIntegerValue();
893 // by default, set all border sides to the specified width
894 aDecls.SetPixelValueIfUnset(eCSSProperty_border_top_width,
895 (float)borderThickness);
896 aDecls.SetPixelValueIfUnset(eCSSProperty_border_left_width,
897 (float)borderThickness);
898 aDecls.SetPixelValueIfUnset(eCSSProperty_border_bottom_width,
899 (float)borderThickness);
900 aDecls.SetPixelValueIfUnset(eCSSProperty_border_right_width,
901 (float)borderThickness);
904 nsGenericHTMLElement::MapImageSizeAttributesInto(aAttributes, aDecls);
905 nsGenericHTMLElement::MapBackgroundAttributesInto(aAttributes, aDecls);
906 nsGenericHTMLElement::MapCommonAttributesInto(aAttributes, aDecls);
909 NS_IMETHODIMP_(bool)
910 HTMLTableElement::IsAttributeMapped(const nsAtom* aAttribute) const {
911 static const MappedAttributeEntry attributes[] = {
912 {nsGkAtoms::cellpadding}, {nsGkAtoms::cellspacing},
913 {nsGkAtoms::border}, {nsGkAtoms::width},
914 {nsGkAtoms::height},
916 {nsGkAtoms::bordercolor},
918 {nsGkAtoms::align}, {nullptr}};
920 static const MappedAttributeEntry* const map[] = {
921 attributes,
922 sCommonAttributeMap,
923 sBackgroundAttributeMap,
926 return FindAttributeDependence(aAttribute, map);
929 nsMapRuleToAttributesFunc HTMLTableElement::GetAttributeMappingFunction()
930 const {
931 return &MapAttributesIntoRule;
934 static void MapInheritedTableAttributesIntoRule(
935 const nsMappedAttributes* aAttributes, MappedDeclarations& aDecls) {
936 const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::cellpadding);
937 if (value && value->Type() == nsAttrValue::eInteger) {
938 // We have cellpadding. This will override our padding values if we
939 // don't have any set.
940 float pad = float(value->GetIntegerValue());
942 aDecls.SetPixelValueIfUnset(eCSSProperty_padding_top, pad);
943 aDecls.SetPixelValueIfUnset(eCSSProperty_padding_right, pad);
944 aDecls.SetPixelValueIfUnset(eCSSProperty_padding_bottom, pad);
945 aDecls.SetPixelValueIfUnset(eCSSProperty_padding_left, pad);
949 nsMappedAttributes* HTMLTableElement::GetAttributesMappedForCell() {
950 return mTableInheritedAttributes;
953 void HTMLTableElement::BuildInheritedAttributes() {
954 NS_ASSERTION(!mTableInheritedAttributes,
955 "potential leak, plus waste of work");
956 MOZ_ASSERT(NS_IsMainThread());
957 Document* document = GetComposedDoc();
958 nsHTMLStyleSheet* sheet =
959 document ? document->GetAttributeStyleSheet() : nullptr;
960 RefPtr<nsMappedAttributes> newAttrs;
961 if (sheet) {
962 const nsAttrValue* value = mAttrs.GetAttr(nsGkAtoms::cellpadding);
963 if (value) {
964 RefPtr<nsMappedAttributes> modifiableMapped =
965 new nsMappedAttributes(sheet, MapInheritedTableAttributesIntoRule);
967 if (modifiableMapped) {
968 nsAttrValue val(*value);
969 bool oldValueSet;
970 modifiableMapped->SetAndSwapAttr(nsGkAtoms::cellpadding, val,
971 &oldValueSet);
973 newAttrs = sheet->UniqueMappedAttributes(modifiableMapped);
974 NS_ASSERTION(newAttrs, "out of memory, but handling gracefully");
976 if (newAttrs != modifiableMapped) {
977 // Reset the stylesheet of modifiableMapped so that it doesn't
978 // spend time trying to remove itself from the hash. There is no
979 // risk that modifiableMapped is in the hash since we created
980 // it ourselves and it didn't come from the stylesheet (in which
981 // case it would not have been modifiable).
982 modifiableMapped->DropStyleSheetReference();
985 mTableInheritedAttributes = newAttrs;
986 NS_IF_ADDREF(mTableInheritedAttributes);
990 void HTMLTableElement::ReleaseInheritedAttributes() {
991 NS_IF_RELEASE(mTableInheritedAttributes);
994 nsresult HTMLTableElement::BindToTree(BindContext& aContext, nsINode& aParent) {
995 ReleaseInheritedAttributes();
996 nsresult rv = nsGenericHTMLElement::BindToTree(aContext, aParent);
997 NS_ENSURE_SUCCESS(rv, rv);
998 BuildInheritedAttributes();
999 return NS_OK;
1002 void HTMLTableElement::UnbindFromTree(bool aNullParent) {
1003 ReleaseInheritedAttributes();
1004 nsGenericHTMLElement::UnbindFromTree(aNullParent);
1007 nsresult HTMLTableElement::BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName,
1008 const nsAttrValueOrString* aValue,
1009 bool aNotify) {
1010 if (aName == nsGkAtoms::cellpadding && aNameSpaceID == kNameSpaceID_None) {
1011 ReleaseInheritedAttributes();
1013 return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID, aName, aValue,
1014 aNotify);
1017 nsresult HTMLTableElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
1018 const nsAttrValue* aValue,
1019 const nsAttrValue* aOldValue,
1020 nsIPrincipal* aSubjectPrincipal,
1021 bool aNotify) {
1022 if (aName == nsGkAtoms::cellpadding && aNameSpaceID == kNameSpaceID_None) {
1023 BuildInheritedAttributes();
1025 return nsGenericHTMLElement::AfterSetAttr(
1026 aNameSpaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify);
1029 } // namespace mozilla::dom