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 --------------------------- */
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
{
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
,
55 using nsWrapperCache::GetWrapperPreserveColor
;
56 using nsWrapperCache::PreserveWrapper
;
57 virtual JSObject
* WrapObject(JSContext
* aCx
,
58 JS::Handle
<JSObject
*> aGivenProto
) override
;
61 // Unregister ourselves as a mutation observer, and clear our internal state.
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
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>`
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
;
123 TableRowsCollection::TableRowsCollection(HTMLTableElement
* aParent
)
124 : mParent(aParent
), mBodyStart(0), mFootStart(0), mInitialized(false) {
128 void TableRowsCollection::EnsureInitialized() {
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
;
144 auto addRowChildren
= [&](nsTArray
<nsCOMPtr
<nsIContent
>>& aArray
,
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
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.
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
,
203 NS_INTERFACE_TABLE_HEAD(TableRowsCollection
)
204 NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
205 NS_INTERFACE_TABLE(TableRowsCollection
, nsIHTMLCollection
,
207 NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(TableRowsCollection
)
210 uint32_t TableRowsCollection::Length() {
212 return mRows
.Length();
215 Element
* TableRowsCollection::GetElementAt(uint32_t aIndex
) {
217 if (aIndex
< mRows
.Length()) {
218 return mRows
[aIndex
]->AsElement();
223 Element
* TableRowsCollection::GetFirstNamedElement(const nsAString
& aName
,
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
)) {
236 return node
->AsElement();
243 void TableRowsCollection::GetSupportedNames(nsTArray
<nsString
>& aNames
) {
245 for (auto& node
: mRows
) {
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
);
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
);
272 TableRowsCollection::ParentDestroyed() {
277 bool TableRowsCollection::InterestingContainer(nsIContent
* aContainer
) {
278 return mParent
&& aContainer
&&
279 (aContainer
== mParent
||
280 (aContainer
->GetParent() == mParent
&&
281 aContainer
->IsAnyOfHTMLElements(nsGkAtoms::thead
, nsGkAtoms::tbody
,
285 bool TableRowsCollection::IsAppropriateRow(nsAtom
* aSection
,
286 nsIContent
* aContent
) {
287 if (!aContent
->IsHTMLElement(nsGkAtoms::tr
)) {
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
) {
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
;
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
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
));
327 int32_t TableRowsCollection::HandleInsert(nsIContent
* aContainer
,
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
336 if (aContainer
== mParent
&&
337 aChild
->IsAnyOfHTMLElements(nsGkAtoms::thead
, nsGkAtoms::tbody
,
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.
365 if (section
== nsGkAtoms::thead
) {
368 } else if (section
== nsGkAtoms::tbody
) {
371 } else if (section
== nsGkAtoms::tfoot
) {
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
379 if (aIndexGuess
>= 0) {
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
);
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
);
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);
411 mRows
.InsertElementAt(index
, aChild
);
415 // nsIMutationObserver
417 void TableRowsCollection::ContentAppended(nsIContent
* aFirstNewContent
) {
418 nsIContent
* container
= aFirstNewContent
->GetParent();
419 if (!nsContentUtils::IsInSameAnonymousTree(mParent
, aFirstNewContent
) ||
420 !InterestingContainer(container
)) {
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())) {
444 HandleInsert(aChild
->GetParent(), aChild
);
447 void TableRowsCollection::ContentRemoved(nsIContent
* aChild
,
448 nsIContent
* aPreviousSibling
) {
449 if (!nsContentUtils::IsInSameAnonymousTree(mParent
, aChild
) ||
450 !InterestingContainer(aChild
->GetParent())) {
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
) {
463 if (mFootStart
> index
) {
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
,
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
496 mInitialized
= false;
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() {
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
)
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
546 nsIHTMLCollection
* HTMLTableElement::Rows() {
548 mRows
= new TableRowsCollection(this);
554 nsIHTMLCollection
* HTMLTableElement::TBodies() {
556 // Not using NS_GetContentList because this should not be cached
557 mTBodies
= new nsContentList(this, kNameSpaceID_XHTML
, nsGkAtoms::tbody
,
558 nsGkAtoms::tbody
, false);
564 already_AddRefed
<nsGenericHTMLElement
> HTMLTableElement::CreateTHead() {
565 RefPtr
<nsGenericHTMLElement
> head
= GetTHead();
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());
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
)) {
587 nsINode::InsertBefore(*head
, refNode
, IgnoreErrors());
589 return head
.forget();
592 void HTMLTableElement::DeleteTHead() {
593 RefPtr
<HTMLTableSectionElement
> tHead
= GetTHead();
595 mozilla::IgnoredErrorResult rv
;
596 nsINode::RemoveChild(*tHead
, rv
);
600 already_AddRefed
<nsGenericHTMLElement
> HTMLTableElement::CreateTFoot() {
601 RefPtr
<nsGenericHTMLElement
> foot
= GetTFoot();
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());
612 AppendChildTo(foot
, true, IgnoreErrors());
615 return foot
.forget();
618 void HTMLTableElement::DeleteTFoot() {
619 RefPtr
<HTMLTableSectionElement
> tFoot
= GetTFoot();
621 mozilla::IgnoredErrorResult rv
;
622 nsINode::RemoveChild(*tFoot
, rv
);
626 already_AddRefed
<nsGenericHTMLElement
> HTMLTableElement::CreateCaption() {
627 RefPtr
<nsGenericHTMLElement
> caption
= GetCaption();
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());
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();
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());
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();
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
682 insert the new row just before the ref row
684 get the first row group
685 insert the new row as its first child
688 aError
.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR
);
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
);
699 // use local variable refIndex so we can remember original aIndex
700 uint32_t refIndex
= (uint32_t)aIndex
;
702 RefPtr
<nsGenericHTMLElement
> newRow
;
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();
715 RefPtr
<mozilla::dom::NodeInfo
> nodeInfo
;
716 nsContentUtils::QNameChanged(mNodeInfo
, nsGkAtoms::tr
,
717 getter_AddRefs(nodeInfo
));
719 newRow
= NS_NewHTMLTableRowElement(nodeInfo
.forget());
722 // If aIndex is -1 or equal to the number of rows, the new row
724 if (aIndex
== -1 || uint32_t(aIndex
) == rowCount
) {
725 parent
->AppendChild(*newRow
, aError
);
727 // insert the new row before the reference row we found above
728 parent
->InsertBefore(*newRow
, refRow
, aError
);
731 if (aError
.Failed()) {
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
)) {
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());
754 AppendChildTo(rowGroup
, true, aError
);
755 if (aError
.Failed()) {
762 RefPtr
<mozilla::dom::NodeInfo
> nodeInfo
;
763 nsContentUtils::QNameChanged(mNodeInfo
, nsGkAtoms::tr
,
764 getter_AddRefs(nodeInfo
));
766 newRow
= NS_NewHTMLTableRowElement(nodeInfo
.forget());
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
) {
782 aError
.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR
);
786 nsIHTMLCollection
* rows
= Rows();
789 refIndex
= rows
->Length();
796 refIndex
= (uint32_t)aIndex
;
799 nsCOMPtr
<nsIContent
> row
= rows
->Item(refIndex
);
801 aError
.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR
);
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).
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
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
);
875 value
= aAttributes
->GetAttr(nsGkAtoms::bordercolor
);
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
);
885 const nsAttrValue
* borderValue
= aAttributes
->GetAttr(nsGkAtoms::border
);
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
);
910 HTMLTableElement::IsAttributeMapped(const nsAtom
* aAttribute
) const {
911 static const MappedAttributeEntry attributes
[] = {
912 {nsGkAtoms::cellpadding
}, {nsGkAtoms::cellspacing
},
913 {nsGkAtoms::border
}, {nsGkAtoms::width
},
916 {nsGkAtoms::bordercolor
},
918 {nsGkAtoms::align
}, {nullptr}};
920 static const MappedAttributeEntry
* const map
[] = {
923 sBackgroundAttributeMap
,
926 return FindAttributeDependence(aAttribute
, map
);
929 nsMapRuleToAttributesFunc
HTMLTableElement::GetAttributeMappingFunction()
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
;
962 const nsAttrValue
* value
= mAttrs
.GetAttr(nsGkAtoms::cellpadding
);
964 RefPtr
<nsMappedAttributes
> modifiableMapped
=
965 new nsMappedAttributes(sheet
, MapInheritedTableAttributesIntoRule
);
967 if (modifiableMapped
) {
968 nsAttrValue
val(*value
);
970 modifiableMapped
->SetAndSwapAttr(nsGkAtoms::cellpadding
, val
,
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();
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
,
1010 if (aName
== nsGkAtoms::cellpadding
&& aNameSpaceID
== kNameSpaceID_None
) {
1011 ReleaseInheritedAttributes();
1013 return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID
, aName
, aValue
,
1017 nsresult
HTMLTableElement::AfterSetAttr(int32_t aNameSpaceID
, nsAtom
* aName
,
1018 const nsAttrValue
* aValue
,
1019 const nsAttrValue
* aOldValue
,
1020 nsIPrincipal
* aSubjectPrincipal
,
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