tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

HTMLTableElement.cpp (34830B)


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