commit 2a3733e687c669cf9e1555a3ed2f98ee81930af9
parent 3dd95597854d8077174b6a03ba5f021dade6979d
Author: Emilio Cobos Álvarez <emilio@crisal.io>
Date: Mon, 22 Dec 2025 21:01:29 +0000
Bug 2006613 - Optimize front-removal of various tree-ordered-arrays. r=smaug
The previous approach wasn't quite sound, because it makes the frame
constructor receive the WillBeRemoved after the flat tree has already
changed.
Instead, what do you think about something like this? This creates a
very simple wrapper over an array, but special-cases removals from the
front, and uses it for TreeOrderedArray and also AssignedNodes...
It could be polished (in particular there are a few places where an
Span::IndexOf operator could be beneficial and so).
Differential Revision: https://phabricator.services.mozilla.com/D277301
Diffstat:
29 files changed, 407 insertions(+), 335 deletions(-)
diff --git a/dom/base/ChildIterator.cpp b/dom/base/ChildIterator.cpp
@@ -60,10 +60,13 @@ Maybe<uint32_t> FlattenedChildIterator::GetIndexOf(
const nsINode* aParent, const nsINode* aPossibleChild) {
if (const auto* element = Element::FromNode(aParent)) {
if (const auto* slot = HTMLSlotElement::FromNode(element)) {
- const auto& assignedNodes = slot->AssignedNodes();
- if (!assignedNodes.IsEmpty()) {
- auto index = assignedNodes.IndexOf(aPossibleChild);
- return index == assignedNodes.NoIndex ? Nothing() : Some(index);
+ const Span assigned = slot->AssignedNodes();
+ if (!assigned.IsEmpty()) {
+ auto index = assigned.IndexOf(aPossibleChild);
+ if (index == assigned.npos) {
+ return Nothing();
+ }
+ return Some(index);
}
} else if (auto* shadowRoot = element->GetShadowRoot()) {
return shadowRoot->ComputeIndexOf(aPossibleChild);
@@ -75,8 +78,7 @@ Maybe<uint32_t> FlattenedChildIterator::GetIndexOf(
nsIContent* FlattenedChildIterator::GetNextChild() {
// If we're already in the inserted-children array, look there first
if (mParentAsSlot) {
- const nsTArray<RefPtr<nsINode>>& assignedNodes =
- mParentAsSlot->AssignedNodes();
+ const Span assignedNodes = mParentAsSlot->AssignedNodes();
if (mIsFirst) {
mIsFirst = false;
MOZ_ASSERT(mIndexInInserted == 0);
@@ -135,8 +137,7 @@ nsIContent* FlattenedChildIterator::GetPreviousChild() {
return nullptr;
}
if (mParentAsSlot) {
- const nsTArray<RefPtr<nsINode>>& assignedNodes =
- mParentAsSlot->AssignedNodes();
+ const Span assignedNodes = mParentAsSlot->AssignedNodes();
MOZ_ASSERT(mIndexInInserted <= assignedNodes.Length());
if (mIndexInInserted == 0) {
mIsFirst = true;
diff --git a/dom/base/ContentIterator.cpp b/dom/base/ContentIterator.cpp
@@ -617,13 +617,11 @@ nsIContent* ContentIteratorBase<NodeType>::GetDeepLastChild(
nsIContent* node = aRoot;
while (HTMLSlotElement* slot = HTMLSlotElement::FromNode(node)) {
+ auto assigned = slot->AssignedNodes();
// The deep last child of a slot should be the last slotted element of it
- if (!slot->AssignedNodes().IsEmpty()) {
- if (nsIContent* content =
- nsIContent::FromNode(slot->AssignedNodes().LastElement())) {
- node = content;
- continue;
- }
+ if (!assigned.IsEmpty()) {
+ node = assigned[assigned.Length() - 1]->AsContent();
+ continue;
}
break;
}
@@ -671,13 +669,10 @@ nsIContent* ContentIteratorBase<NodeType>::GetNextSibling(
}
// Next sibling of a slotted node should be the next slotted node
- auto currentIndex = slot->AssignedNodes().IndexOf(aNode);
- if (currentIndex < slot->AssignedNodes().Length() - 1) {
- nsINode* nextSlottedNode =
- slot->AssignedNodes().ElementAt(currentIndex + 1);
- if (nextSlottedNode->IsContent()) {
- return nextSlottedNode->AsContent();
- }
+ auto assigned = slot->AssignedNodes();
+ auto cur = assigned.IndexOf(aNode);
+ if (cur != assigned.npos && cur + 1 < assigned.Length()) {
+ return assigned[cur + 1]->AsContent();
}
// Move on to assigned slot's next sibling
aNode = slot;
@@ -739,13 +734,10 @@ nsIContent* ContentIteratorBase<NodeType>::GetPrevSibling(
break;
}
// prev sibling of a slotted node should be the prev slotted node
- auto currentIndex = slot->AssignedNodes().IndexOf(aNode);
- if (currentIndex > 0) {
- nsINode* prevSlottedNode =
- slot->AssignedNodes().ElementAt(currentIndex - 1);
- if (prevSlottedNode->IsContent()) {
- return prevSlottedNode->AsContent();
- }
+ auto assigned = slot->AssignedNodes();
+ auto cur = assigned.IndexOf(aNode);
+ if (cur != assigned.npos && cur != 0) {
+ return assigned[cur - 1]->AsContent();
}
aNode = slot;
}
diff --git a/dom/base/DirectionalityUtils.cpp b/dom/base/DirectionalityUtils.cpp
@@ -268,10 +268,10 @@ static Directionality ComputeAutoDirectionality(Element* aElement,
* https://html.spec.whatwg.org/#auto-directionality step 2
*/
Directionality ComputeAutoDirectionFromAssignedNodes(
- HTMLSlotElement* aSlot, const nsTArray<RefPtr<nsINode>>& assignedNodes,
+ HTMLSlotElement* aSlot, Span<const RefPtr<nsINode>> aAssignedNodes,
bool aNotify) {
// Step 2.1. For each node child of element's assigned nodes:
- for (const RefPtr<nsINode>& assignedNode : assignedNodes) {
+ for (const RefPtr<nsINode>& assignedNode : aAssignedNodes) {
// Step 2.1.1. Let childDirection be null.
Directionality childDirection = Directionality::Unset;
@@ -316,7 +316,7 @@ static Directionality ComputeAutoDirectionality(Element* aElement,
// Step 2. If element is a slot element whose root is a shadow root and
// element's assigned nodes are not empty:
if (auto* slot = HTMLSlotElement::FromNode(aElement)) {
- const nsTArray<RefPtr<nsINode>>& assignedNodes = slot->AssignedNodes();
+ const Span assignedNodes = slot->AssignedNodes();
if (!assignedNodes.IsEmpty()) {
MOZ_ASSERT(slot->IsInShadowTree());
return ComputeAutoDirectionFromAssignedNodes(slot, assignedNodes,
diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp
@@ -574,7 +574,7 @@ void IdentifierMapEntry::Traverse(
}
bool IdentifierMapEntry::IsEmpty() {
- return mIdContentList->IsEmpty() && !mNameContentList &&
+ return mIdContentList.IsEmpty() && !mNameContentList &&
!mDocumentNameContentList && !mChangeCallbacks && !mImageElement;
}
@@ -623,11 +623,11 @@ void IdentifierMapEntry::FireChangeCallbacks(Element* aOldElement,
void IdentifierMapEntry::AddIdElement(Element* aElement) {
MOZ_ASSERT(aElement, "Must have element");
- MOZ_ASSERT(!mIdContentList->Contains(nullptr), "Why is null in our list?");
+ MOZ_ASSERT(!mIdContentList.Contains(nullptr), "Why is null in our list?");
size_t index = mIdContentList.Insert(*aElement);
if (index == 0) {
- Element* oldElement = mIdContentList->SafeElementAt(1);
+ Element* oldElement = mIdContentList.SafeElementAt(1, nullptr);
FireChangeCallbacks(oldElement, aElement);
}
}
@@ -642,15 +642,16 @@ void IdentifierMapEntry::RemoveIdElement(Element* aElement) {
// Only assert this in HTML documents for now as XUL does all sorts of weird
// crap.
NS_ASSERTION(!aElement->OwnerDoc()->IsHTMLDocument() ||
- mIdContentList->Contains(aElement),
+ mIdContentList.Contains(aElement),
"Removing id entry that doesn't exist");
// XXXbz should this ever Compact() I guess when all the content is gone
// we'll just get cleaned up in the natural order of things...
- Element* currentElement = mIdContentList->SafeElementAt(0);
+ Element* currentElement = mIdContentList.SafeElementAt(0, nullptr);
mIdContentList.RemoveElement(*aElement);
if (currentElement == aElement) {
- FireChangeCallbacks(currentElement, mIdContentList->SafeElementAt(0));
+ FireChangeCallbacks(currentElement,
+ mIdContentList.SafeElementAt(0, nullptr));
}
}
@@ -664,7 +665,7 @@ void IdentifierMapEntry::SetImageElement(Element* aElement) {
}
void IdentifierMapEntry::ClearAndNotify() {
- Element* currentElement = mIdContentList->SafeElementAt(0);
+ Element* currentElement = mIdContentList.SafeElementAt(0, nullptr);
mIdContentList.Clear();
if (currentElement) {
FireChangeCallbacks(currentElement, nullptr);
@@ -11708,8 +11709,7 @@ void Document::RemoveColorSchemeMeta(HTMLMetaElement& aMeta) {
void Document::RecomputeColorScheme() {
auto oldColorScheme = mColorSchemeBits;
mColorSchemeBits = 0;
- const nsTArray<HTMLMetaElement*>& elements = mColorSchemeMetaTags;
- for (const HTMLMetaElement* el : elements) {
+ for (const HTMLMetaElement* el : mColorSchemeMetaTags.AsSpan()) {
nsAutoString content;
if (!el->GetAttr(nsGkAtoms::content, content)) {
continue;
diff --git a/dom/base/DocumentOrShadowRoot.h b/dom/base/DocumentOrShadowRoot.h
@@ -102,10 +102,12 @@ class DocumentOrShadowRoot {
*
* This is useful for stuff like QuerySelector optimization and such.
*/
- const nsTArray<Element*>* GetAllElementsForId(
+ Span<Element* const> GetAllElementsForId(
const IdentifierMapEntry::DependentAtomOrString& aElementId) const {
- IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aElementId);
- return entry ? &entry->GetIdElements() : nullptr;
+ if (IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aElementId)) {
+ return entry->GetIdElements();
+ }
+ return {};
}
already_AddRefed<nsContentList> GetElementsByTagName(
diff --git a/dom/base/FastFrontRemovableArray.h b/dom/base/FastFrontRemovableArray.h
@@ -0,0 +1,143 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_FastFrontRemovableArray_h
+#define mozilla_dom_FastFrontRemovableArray_h
+
+// An nsTArray of pointers where removing from the front is amortized constant
+// time.
+
+#include "mozilla/Span.h"
+#include "nsTArray.h"
+
+namespace mozilla::dom {
+
+template <typename T, size_t InlineCapacity = 0>
+class FastFrontRemovableArray {
+ using InternalList = AutoTArray<T, InlineCapacity>;
+
+ public:
+ static const size_t NoIndex = InternalList::NoIndex;
+
+ operator Span<const T>() const { return AsSpan(); }
+ operator Span<T>() { return AsSpan(); }
+
+ Span<const T> AsSpan() const {
+ return Span<const T>(mList).From(mIndexOfFirstElement);
+ }
+ Span<T> AsSpan() { return Span<T>(mList).From(mIndexOfFirstElement); }
+
+ size_t Length() const { return mList.Length() - mIndexOfFirstElement; }
+
+ bool IsEmpty() const { return Length() == 0; }
+
+ void RemoveElementAt(size_t aIndex) {
+ if (aIndex == 0) {
+ mList[mIndexOfFirstElement++] = nullptr;
+ if (mIndexOfFirstElement > std::max(size_t(4), mList.Length() / 4)) {
+ // Compact the list if it gets too big. This shifts all the elements,
+ // which is expensive, so only do it if we have more than 4 elements
+ // wasted at the front, and more than a quarter of the list is wasted
+ // space in the front.
+ mList.RemoveElementsAt(0, mIndexOfFirstElement);
+ mIndexOfFirstElement = 0;
+ }
+ return;
+ }
+ mList.RemoveElementAt(aIndex + mIndexOfFirstElement);
+ if (IsEmpty()) {
+ Clear();
+ }
+ }
+
+ template <typename U>
+ void InsertElementAt(size_t aIndex, U* aElem) {
+ if (mIndexOfFirstElement && aIndex == 0) {
+ mList[--mIndexOfFirstElement] = aElem;
+ return;
+ }
+ mList.InsertElementAt(aIndex + mIndexOfFirstElement, aElem);
+ }
+
+ T& operator[](size_t aIndex) { return mList[aIndex + mIndexOfFirstElement]; }
+
+ const T& operator[](size_t aIndex) const {
+ return mList[aIndex + mIndexOfFirstElement];
+ }
+ T& ElementAt(size_t aIndex) { return mList[aIndex + mIndexOfFirstElement]; }
+ const T& ElementAt(size_t aIndex) const {
+ return mList[aIndex + mIndexOfFirstElement];
+ }
+
+ T& SafeElementAt(size_t aIndex, T& aDef) {
+ return mList.SafeElementAt(aIndex + mIndexOfFirstElement, aDef);
+ }
+
+ const T& SafeElementAt(size_t aIndex, const T& aDef) const {
+ return mList.SafeElementAt(aIndex + mIndexOfFirstElement, aDef);
+ }
+
+ const T& FirstElement() const { return ElementAt(0); }
+ const T& LastElement() const { return mList.LastElement(); }
+ T& FirstElement() { return ElementAt(0); }
+ T& LastElement() { return mList.LastElement(); }
+
+ template <typename U>
+ void AppendElement(U* aElem) {
+ mList.AppendElement(aElem);
+ }
+
+ template <typename Item>
+ bool RemoveElement(const Item& aItem) {
+ auto i = IndexOf(aItem);
+ if (i == NoIndex) {
+ return false;
+ }
+ RemoveElementAt(i);
+ return true;
+ }
+
+ template <typename Item>
+ bool Contains(const Item& aItem) const {
+ return IndexOf(aItem) != NoIndex;
+ }
+
+ void Clear() {
+ mList.Clear();
+ mIndexOfFirstElement = 0;
+ }
+
+ template <typename Item>
+ size_t IndexOf(const Item& aItem) const {
+ auto index = mList.IndexOf(aItem, mIndexOfFirstElement);
+ if (index == NoIndex || mIndexOfFirstElement == 0) {
+ return index;
+ }
+ return index - mIndexOfFirstElement;
+ }
+
+ private:
+ AutoTArray<T, InlineCapacity> mList;
+ size_t mIndexOfFirstElement = 0;
+};
+
+template <typename T, size_t InlineCap>
+inline void ImplCycleCollectionUnlink(
+ FastFrontRemovableArray<T, InlineCap>& aField) {
+ aField.Clear();
+}
+
+template <typename T, size_t InlineCap, typename Callback>
+inline void ImplCycleCollectionIndexedContainer(
+ FastFrontRemovableArray<T, InlineCap>& aField, Callback&& aCallback) {
+ for (auto& value : aField.AsSpan()) {
+ aCallback(value);
+ }
+}
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/base/IdentifierMapEntry.h b/dom/base/IdentifierMapEntry.h
@@ -127,12 +127,15 @@ class IdentifierMapEntry : public PLDHashEntryHdr {
* Returns the element if we know the element associated with this
* id. Otherwise returns null.
*/
- Element* GetIdElement() const { return mIdContentList->SafeElementAt(0); }
+ Element* GetIdElement() const {
+ auto span = mIdContentList.AsSpan();
+ return span.IsEmpty() ? nullptr : span[0];
+ }
/**
* Returns the list of all elements associated with this id.
*/
- const nsTArray<Element*>& GetIdElements() const { return mIdContentList; }
+ Span<Element* const> GetIdElements() const { return mIdContentList.AsSpan(); }
/**
* If this entry has a non-null image element set (using SetImageElement),
diff --git a/dom/base/RadioGroupContainer.cpp b/dom/base/RadioGroupContainer.cpp
@@ -33,7 +33,7 @@ RadioGroupContainer::RadioGroupContainer() = default;
RadioGroupContainer::~RadioGroupContainer() {
for (const auto& group : mRadioGroups) {
- for (const auto& button : group.GetData()->mRadioButtons.AsList()) {
+ for (const auto& button : group.GetData()->mRadioButtons.AsSpan()) {
// When the radio group container is being cycle-collected, any remaining
// connected buttons will also be in the process of being cycle-collected.
// Here, we unset the button's reference to the container so that when it
@@ -53,11 +53,10 @@ void RadioGroupContainer::Traverse(RadioGroupContainer* tmp,
cb, "mRadioGroups entry->mSelectedRadioButton");
cb.NoteXPCOMChild(ToSupports(radioGroup->mSelectedRadioButton));
- uint32_t i, count = radioGroup->mRadioButtons->Length();
- for (i = 0; i < count; ++i) {
+ for (auto& button : radioGroup->mRadioButtons.AsSpan()) {
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
cb, "mRadioGroups entry->mRadioButtons[i]");
- cb.NoteXPCOMChild(ToSupports(radioGroup->mRadioButtons->ElementAt(i)));
+ cb.NoteXPCOMChild(ToSupports(button));
}
}
}
@@ -96,12 +95,12 @@ nsresult RadioGroupContainer::GetNextRadioButton(
return NS_ERROR_FAILURE;
}
}
- int32_t index = radioGroup->mRadioButtons->IndexOf(currentRadio);
+ int32_t index = radioGroup->mRadioButtons.IndexOf(currentRadio);
if (index < 0) {
return NS_ERROR_FAILURE;
}
- int32_t numRadios = static_cast<int32_t>(radioGroup->mRadioButtons->Length());
+ int32_t numRadios = static_cast<int32_t>(radioGroup->mRadioButtons.Length());
RefPtr<HTMLInputElement> radio;
do {
if (aPrevious) {
@@ -111,7 +110,7 @@ nsresult RadioGroupContainer::GetNextRadioButton(
} else if (++index >= numRadios) {
index = 0;
}
- radio = radioGroup->mRadioButtons->ElementAt(index);
+ radio = radioGroup->mRadioButtons.ElementAt(index);
} while ((radio->Disabled() || !radio->GetPrimaryFrame() ||
!radio->GetPrimaryFrame()->IsVisibleConsideringAncestors()) &&
radio != currentRadio);
@@ -123,7 +122,7 @@ nsresult RadioGroupContainer::GetNextRadioButton(
HTMLInputElement* RadioGroupContainer::GetFirstRadioButton(
const nsAString& aName) {
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
- for (HTMLInputElement* radio : radioGroup->mRadioButtons.AsList()) {
+ for (HTMLInputElement* radio : radioGroup->mRadioButtons.AsSpan()) {
if (!radio->Disabled() && radio->GetPrimaryFrame() &&
radio->GetPrimaryFrame()->IsVisibleConsideringAncestors()) {
return radio;
@@ -146,7 +145,7 @@ void RadioGroupContainer::RemoveFromRadioGroup(const nsAString& aName,
HTMLInputElement* aRadio) {
nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
MOZ_ASSERT(
- radioGroup->mRadioButtons->Contains(aRadio),
+ radioGroup->mRadioButtons.Contains(aRadio),
"Attempting to remove radio button from group it is not a part of!");
radioGroup->mRadioButtons.RemoveElement(*aRadio);
@@ -200,9 +199,9 @@ nsRadioGroupStruct* RadioGroupContainer::GetOrCreateRadioGroup(
return mRadioGroups.GetOrInsertNew(aName);
}
-const nsTArray<RefPtr<HTMLInputElement>>&
-RadioGroupContainer::GetButtonsInGroup(nsRadioGroupStruct* aGroup) const {
- return aGroup->mRadioButtons.AsList();
+Span<const RefPtr<HTMLInputElement>> RadioGroupContainer::GetButtonsInGroup(
+ nsRadioGroupStruct* aGroup) const {
+ return aGroup->mRadioButtons.AsSpan();
}
} // namespace mozilla::dom
diff --git a/dom/base/RadioGroupContainer.h b/dom/base/RadioGroupContainer.h
@@ -55,7 +55,7 @@ class RadioGroupContainer final {
nsRadioGroupStruct* GetOrCreateRadioGroup(const nsAString& aName);
private:
- const nsTArray<RefPtr<HTMLInputElement>>& GetButtonsInGroup(
+ Span<const RefPtr<HTMLInputElement>> GetButtonsInGroup(
nsRadioGroupStruct* aGroup) const;
nsClassHashtable<nsStringHashKey, nsRadioGroupStruct> mRadioGroups;
diff --git a/dom/base/RangeBoundary.h b/dom/base/RangeBoundary.h
@@ -449,12 +449,10 @@ class RangeBoundaryBase {
if (mTreeKind == TreeKind::Flat) {
if (const auto* slot = dom::HTMLSlotElement::FromNode(mParent)) {
- const auto& assignedNodes = slot->AssignedNodes();
- const auto index = assignedNodes.IndexOf(aCurrentNode);
- if (index != assignedNodes.NoIndex &&
- index + 1 < assignedNodes.Length()) {
- if (RawRefType* nextSibling =
- RawRefType::FromNode(assignedNodes.ElementAt(index + 1))) {
+ const Span assigned = slot->AssignedNodes();
+ const auto index = assigned.IndexOf(aCurrentNode);
+ if (index != assigned.npos && index + 1 < assigned.Length()) {
+ if (auto* nextSibling = RawRefType::FromNode(assigned[index + 1])) {
return nextSibling;
}
return nullptr;
@@ -468,9 +466,9 @@ class RangeBoundaryBase {
MOZ_ASSERT(aNode);
if (mTreeKind == TreeKind::Flat) {
if (const auto* slot = dom::HTMLSlotElement::FromNode(aNode)) {
- const auto& assignedNodes = slot->AssignedNodes();
- if (!assignedNodes.IsEmpty()) {
- if (RawRefType* child = RawRefType::FromNode(assignedNodes[0])) {
+ const Span assigned = slot->AssignedNodes();
+ if (!assigned.IsEmpty()) {
+ if (RawRefType* child = RawRefType::FromNode(assigned[0])) {
return child;
}
return nullptr;
@@ -506,9 +504,9 @@ class RangeBoundaryBase {
MOZ_ASSERT(aNode);
if (mTreeKind == TreeKind::Flat) {
if (const auto* slot = dom::HTMLSlotElement::FromNode(aNode)) {
- const auto& assignedNodes = slot->AssignedNodes();
- if (!assignedNodes.IsEmpty()) {
- return assignedNodes.Length();
+ const Span assigned = slot->AssignedNodes();
+ if (!assigned.IsEmpty()) {
+ return assigned.Length();
}
}
@@ -531,9 +529,9 @@ class RangeBoundaryBase {
MOZ_ASSERT(aParent);
if (mTreeKind == TreeKind::Flat) {
if (const auto* slot = dom::HTMLSlotElement::FromNode(aParent)) {
- const auto& assignedNodes = slot->AssignedNodes();
- if (!assignedNodes.IsEmpty()) {
- return RawRefType::FromNode(assignedNodes.LastElement());
+ const Span assigned = slot->AssignedNodes();
+ if (!assigned.IsEmpty()) {
+ return RawRefType::FromNode(assigned[assigned.Length() - 1]);
}
}
if (const auto* shadowRoot = aParent->GetShadowRoot()) {
diff --git a/dom/base/RangeUtils.cpp b/dom/base/RangeUtils.cpp
@@ -240,8 +240,7 @@ nsresult RangeUtils::CompareNodeToRangeBoundaries(
parent = aNode;
nodeStart = 0;
nodeEnd = aNode->GetChildCount();
- } else if (const HTMLSlotElement* slotAsParent =
- HTMLSlotElement::FromNode(parent);
+ } else if (const auto* slotAsParent = HTMLSlotElement::FromNode(parent);
slotAsParent && aKind == TreeKind::Flat) {
// aNode is a slotted content, use the index in the assigned nodes
// to represent this node.
diff --git a/dom/base/ShadowRoot.cpp b/dom/base/ShadowRoot.cpp
@@ -259,18 +259,17 @@ void ShadowRoot::AddSlot(HTMLSlotElement* aSlot) {
InvalidateStyleAndLayoutOnSubtree(aSlot);
- HTMLSlotElement* oldSlot = currentSlots->SafeElementAt(1);
+ HTMLSlotElement* oldSlot = currentSlots.SafeElementAt(1, nullptr);
if (SlotAssignment() == SlotAssignmentMode::Named) {
if (oldSlot) {
MOZ_DIAGNOSTIC_ASSERT(oldSlot != aSlot);
// Move assigned nodes from old slot to new slot.
InvalidateStyleAndLayoutOnSubtree(oldSlot);
- const nsTArray<RefPtr<nsINode>>& assignedNodes = oldSlot->AssignedNodes();
bool doEnqueueSlotChange = false;
- while (assignedNodes.Length() > 0) {
- nsINode* assignedNode = assignedNodes[0];
-
+ auto assignedNodes =
+ ToTArray<AutoTArray<nsINode*, 8>>(oldSlot->AssignedNodes());
+ for (nsINode* assignedNode : assignedNodes) {
oldSlot->RemoveAssignedNode(*assignedNode->AsContent());
aSlot->AppendAssignedNode(*assignedNode->AsContent());
doEnqueueSlotChange = true;
@@ -327,11 +326,11 @@ void ShadowRoot::RemoveSlot(HTMLSlotElement* aSlot) {
MOZ_ASSERT(mSlotMap.Get(name));
SlotArray& currentSlots = *mSlotMap.Get(name);
- MOZ_DIAGNOSTIC_ASSERT(currentSlots->Contains(aSlot),
+ MOZ_DIAGNOSTIC_ASSERT(currentSlots.Contains(aSlot),
"Slot to de-register wasn't found?");
- if (currentSlots->Length() == 1) {
+ if (currentSlots.Length() == 1) {
MOZ_ASSERT_IF(SlotAssignment() == SlotAssignmentMode::Named,
- currentSlots->ElementAt(0) == aSlot);
+ currentSlots.ElementAt(0) == aSlot);
InvalidateStyleAndLayoutOnSubtree(aSlot);
@@ -351,7 +350,7 @@ void ShadowRoot::RemoveSlot(HTMLSlotElement* aSlot) {
}
}
- const bool wasFirstSlot = currentSlots->ElementAt(0) == aSlot;
+ const bool wasFirstSlot = currentSlots.ElementAt(0) == aSlot;
currentSlots.RemoveElement(*aSlot);
if (!wasFirstSlot || SlotAssignment() == SlotAssignmentMode::Manual) {
return;
@@ -360,16 +359,15 @@ void ShadowRoot::RemoveSlot(HTMLSlotElement* aSlot) {
// Move assigned nodes from removed slot to the next slot in
// tree order with the same name.
InvalidateStyleAndLayoutOnSubtree(aSlot);
- HTMLSlotElement* replacementSlot = currentSlots->ElementAt(0);
- const nsTArray<RefPtr<nsINode>>& assignedNodes = aSlot->AssignedNodes();
+ HTMLSlotElement* replacementSlot = currentSlots.ElementAt(0);
+ auto assignedNodes =
+ ToTArray<AutoTArray<nsINode*, 8>>(aSlot->AssignedNodes());
if (assignedNodes.IsEmpty()) {
return;
}
InvalidateStyleAndLayoutOnSubtree(replacementSlot);
- while (!assignedNodes.IsEmpty()) {
- nsINode* assignedNode = assignedNodes[0];
-
+ for (auto* assignedNode : assignedNodes) {
aSlot->RemoveAssignedNode(*assignedNode->AsContent());
replacementSlot->AppendAssignedNode(*assignedNode->AsContent());
}
@@ -659,7 +657,7 @@ ShadowRoot::SlotInsertionPoint ShadowRoot::SlotInsertionPointFor(
if (!slots) {
return {};
}
- slot = (*slots)->ElementAt(0);
+ slot = (*slots).ElementAt(0);
}
MOZ_ASSERT(slot);
@@ -691,7 +689,7 @@ ShadowRoot::SlotInsertionPoint ShadowRoot::SlotInsertionPointFor(
return {slot, Some(index)};
}
} else {
- const nsTArray<RefPtr<nsINode>>& assignedNodes = slot->AssignedNodes();
+ const Span assignedNodes = slot->AssignedNodes();
nsIContent* currentContent = GetHost()->GetFirstChild();
for (uint32_t i = 0; i < assignedNodes.Length(); i++) {
// Seek through the host's explicit children until the
@@ -763,11 +761,13 @@ void ShadowRoot::MaybeReassignMainSummary(SummaryChangeReason aReason) {
if (aReason == SummaryChangeReason::Insertion) {
// We've inserted a summary element, may need to remove the existing one.
SlotArray* array = mSlotMap.Get(u"internal-main-summary"_ns);
- MOZ_RELEASE_ASSERT(array && (*array)->Length() == 1);
- HTMLSlotElement* slot = (*array)->ElementAt(0);
- auto* summary = HTMLSummaryElement::FromNodeOrNull(
- slot->AssignedNodes().SafeElementAt(0));
- if (summary) {
+ MOZ_RELEASE_ASSERT(array && (*array).Length() == 1);
+ HTMLSlotElement* slot = (*array).ElementAt(0);
+ auto assigned = slot->AssignedNodes();
+ if (assigned.IsEmpty()) {
+ return;
+ }
+ if (auto* summary = HTMLSummaryElement::FromNode(assigned[0])) {
MaybeReassignContent(*summary);
}
} else if (MOZ_LIKELY(GetHost())) {
diff --git a/dom/base/ShadowRoot.h b/dom/base/ShadowRoot.h
@@ -192,7 +192,7 @@ class ShadowRoot final : public DocumentFragment, public DocumentOrShadowRoot {
bool HasSlots() const { return !mSlotMap.IsEmpty(); };
HTMLSlotElement* GetDefaultSlot() const {
SlotArray* list = mSlotMap.Get(u""_ns);
- return list ? (*list)->ElementAt(0) : nullptr;
+ return list ? (*list).AsSpan()[0] : nullptr;
}
void PartAdded(const Element&);
diff --git a/dom/base/TreeOrderedArray.h b/dom/base/TreeOrderedArray.h
@@ -7,7 +7,7 @@
#ifndef mozilla_dom_TreeOrderedArray_h
#define mozilla_dom_TreeOrderedArray_h
-#include "nsTArray.h"
+#include "FastFrontRemovableArray.h"
class nsINode;
template <typename T>
@@ -17,7 +17,9 @@ namespace mozilla::dom {
// A sorted tree-ordered list of pointers (either raw or RefPtr) to nodes.
template <typename NodePointer>
-class TreeOrderedArray {
+class TreeOrderedArray : public FastFrontRemovableArray<NodePointer, 1> {
+ using Base = FastFrontRemovableArray<NodePointer, 1>;
+
template <typename T>
struct RawTypeExtractor {};
@@ -34,10 +36,6 @@ class TreeOrderedArray {
using Node = typename RawTypeExtractor<NodePointer>::type;
public:
- operator const nsTArray<NodePointer>&() const { return mList; }
- const nsTArray<NodePointer>& AsList() const { return mList; }
- const nsTArray<NodePointer>* operator->() const { return &mList; }
-
// Inserts a node into the list, and returns the new index in the array.
//
// All the nodes in the list should be in the same subtree, and debug builds
@@ -49,14 +47,7 @@ class TreeOrderedArray {
// You can provide a potential common ancestor to speed up comparisons, see
// nsContentUtils::CompareTreePosition. That's only a hint.
inline size_t Insert(Node&, nsINode* aCommonAncestor = nullptr);
-
- bool RemoveElement(Node& aNode) { return mList.RemoveElement(&aNode); }
- void RemoveElementAt(size_t aIndex) { mList.RemoveElementAt(aIndex); }
-
- void Clear() { mList.Clear(); }
-
- private:
- AutoTArray<NodePointer, 1> mList;
+ bool RemoveElement(Node& aNode) { return Base::RemoveElement(&aNode); }
};
} // namespace mozilla::dom
diff --git a/dom/base/TreeOrderedArrayInlines.h b/dom/base/TreeOrderedArrayInlines.h
@@ -19,8 +19,10 @@ template <typename Node>
size_t TreeOrderedArray<Node>::Insert(Node& aNode, nsINode* aCommonAncestor) {
static_assert(std::is_base_of_v<nsINode, Node>, "Should be a node");
- if (mList.IsEmpty()) {
- mList.AppendElement(&aNode);
+ auto span = Base::AsSpan();
+ auto len = span.Length();
+ if (!len) {
+ Base::AppendElement(&aNode);
return 0;
}
@@ -39,16 +41,15 @@ size_t TreeOrderedArray<Node>::Insert(Node& aNode, nsINode* aCommonAncestor) {
};
PositionComparator cmp{aNode, aCommonAncestor};
- if (cmp(mList.LastElement()) > 0) {
+ if (cmp(span[len - 1]) > 0) {
// Appending is a really common case, optimize for it.
- auto index = mList.Length();
- mList.AppendElement(&aNode);
- return index;
+ Base::AppendElement(&aNode);
+ return len;
}
size_t idx;
- BinarySearchIf(mList, 0, mList.Length(), cmp, &idx);
- mList.InsertElementAt(idx, &aNode);
+ BinarySearchIf(span, 0, len, cmp, &idx);
+ Base::InsertElementAt(idx, &aNode);
return idx;
}
diff --git a/dom/base/moz.build b/dom/base/moz.build
@@ -190,6 +190,7 @@ EXPORTS.mozilla.dom += [
"EventSource.h",
"EventSourceEventService.h",
"External.h",
+ "FastFrontRemovableArray.h",
"FilteredNodeIterator.h",
"FormData.h",
"FragmentDirective.h",
diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp
@@ -951,20 +951,18 @@ class MOZ_STACK_CLASS CommonAncestors final {
return;
}
- Maybe<uint32_t> childIndex;
+ bool found = false;
if constexpr (aKind == TreeKind::Flat) {
if (auto* slot = HTMLSlotElement::FromNode(mClosestCommonAncestor)) {
- auto index = slot->AssignedNodes().IndexOf(child);
- if (index != nsTArray<RefPtr<nsINode>>::NoIndex) {
- childIndex = Some(index);
- }
+ auto span = slot->AssignedNodes();
+ found = span.IndexOf(child) != span.npos;
}
}
- if (childIndex.isNothing()) {
- childIndex = mClosestCommonAncestor->ComputeIndexOf(child);
+ if (!found) {
+ found = mClosestCommonAncestor->ComputeIndexOf(child).isSome();
}
- if (MOZ_LIKELY(childIndex.isSome())) {
+ if (MOZ_LIKELY(found)) {
return;
}
const Maybe<size_t> index =
@@ -3373,11 +3371,27 @@ Maybe<int32_t> nsContentUtils::CompareChildNodes(
const auto* slot = aChild1->AsContent()->GetAssignedSlot();
MOZ_ASSERT(slot);
- auto child1Index = slot->AssignedNodes().IndexOf(aChild1);
- auto child2Index = slot->AssignedNodes().IndexOf(aChild2);
+ constexpr auto NoIndex = size_t(-1);
+ auto child1Index = NoIndex;
+ auto child2Index = NoIndex;
+ size_t index = 0;
+ for (nsINode* node : slot->AssignedNodes()) {
+ if (node == aChild1) {
+ child1Index = index;
+ if (child2Index != NoIndex) {
+ break;
+ }
+ } else if (node == aChild2) {
+ child2Index = index;
+ if (child1Index != NoIndex) {
+ break;
+ }
+ }
+ index++;
+ }
- MOZ_ASSERT(child1Index != nsTArray<RefPtr<nsINode>>::NoIndex);
- MOZ_ASSERT(child2Index != nsTArray<RefPtr<nsINode>>::NoIndex);
+ MOZ_ASSERT(child1Index != NoIndex);
+ MOZ_ASSERT(child2Index != NoIndex);
return Some(child1Index < child2Index ? -1 : 1);
}
@@ -3554,15 +3568,12 @@ Maybe<int32_t> nsContentUtils::CompareChildOffsetAndChildNode(
return Some(!aOffset1 ? 0 : 1);
}
-#ifdef DEBUG
- if (!isFlatAndSlotted) {
- MOZ_ASSERT(parentNode->GetLastChild());
- }
-#endif
+ MOZ_ASSERT_IF(!isFlatAndSlotted, parentNode->GetLastChild());
const nsIContent& lastChild = [parentNode]() -> const nsIContent& {
if constexpr (aKind == TreeKind::Flat) {
if (const HTMLSlotElement* slot = HTMLSlotElement::FromNode(parentNode)) {
- return *slot->AssignedNodes().LastElement()->AsContent();
+ auto assigned = slot->AssignedNodes();
+ return *assigned[assigned.Length() - 1]->AsContent();
}
}
diff --git a/dom/base/nsINode.cpp b/dom/base/nsINode.cpp
@@ -3385,16 +3385,11 @@ inline static Element* FindMatchingElementWithId(
aRoot.IsInUncomposedDoc() || aRoot.IsInShadowTree(),
"Don't call me if the root is not in the document or in a shadow tree");
- const nsTArray<Element*>* elements =
- aContainingDocOrShadowRoot.GetAllElementsForId(aId);
- if (!elements) {
- // Nothing to do; we're done
- return nullptr;
- }
+ Span elements = aContainingDocOrShadowRoot.GetAllElementsForId(aId);
// XXXbz: Should we fall back to the tree walk if |elements| is long,
// for some value of "long"?
- for (Element* element : *elements) {
+ for (Element* element : elements) {
if (MOZ_UNLIKELY(element == &aRoot)) {
continue;
}
diff --git a/dom/html/HTMLFormControlsCollection.cpp b/dom/html/HTMLFormControlsCollection.cpp
@@ -85,15 +85,15 @@ void HTMLFormControlsCollection::DropFormReference() {
}
void HTMLFormControlsCollection::Clear() {
- // Null out childrens' pointer to me. No refcounting here
- for (nsGenericHTMLFormElement* element : Reversed(mElements.AsList())) {
+ // Null out childrens' pointer to me. No refcounting here.
+ for (nsGenericHTMLFormElement* element : mElements.AsSpan()) {
nsCOMPtr<nsIFormControl> formControl = nsIFormControl::FromNode(element);
MOZ_ASSERT(formControl);
formControl->ClearForm(false, false);
}
mElements.Clear();
- for (nsGenericHTMLFormElement* element : Reversed(mNotInElements.AsList())) {
+ for (nsGenericHTMLFormElement* element : mNotInElements.AsSpan()) {
nsCOMPtr<nsIFormControl> formControl = nsIFormControl::FromNode(element);
MOZ_ASSERT(formControl);
formControl->ClearForm(false, false);
@@ -131,7 +131,7 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(HTMLFormControlsCollection)
// nsIHTMLCollection interfac
-uint32_t HTMLFormControlsCollection::Length() { return mElements->Length(); }
+uint32_t HTMLFormControlsCollection::Length() { return mElements.Length(); }
nsISupports* HTMLFormControlsCollection::NamedItemInternal(
const nsAString& aName) {
@@ -154,7 +154,7 @@ nsresult HTMLFormControlsCollection::IndexOfContent(nsIContent* aContent,
// Note -- not a DOM method; callers should handle flushing themselves
NS_ENSURE_ARG_POINTER(aIndex);
- *aIndex = mElements->IndexOf(aContent);
+ *aIndex = mElements.IndexOf(aContent);
return NS_OK;
}
@@ -171,17 +171,14 @@ nsresult HTMLFormControlsCollection::RemoveElementFromTable(
nsresult HTMLFormControlsCollection::GetSortedControls(
nsTArray<RefPtr<nsGenericHTMLFormElement>>& aControls) const {
-#ifdef DEBUG
- HTMLFormElement::AssertDocumentOrder(mElements, mForm);
- HTMLFormElement::AssertDocumentOrder(mNotInElements, mForm);
-#endif
-
aControls.Clear();
// Merge the elements list and the not in elements list. Both lists are
// already sorted.
- uint32_t elementsLen = mElements->Length();
- uint32_t notInElementsLen = mNotInElements->Length();
+ auto elements = mElements.AsSpan();
+ auto notInElements = mNotInElements.AsSpan();
+ uint32_t elementsLen = elements.Length();
+ uint32_t notInElementsLen = notInElements.Length();
aControls.SetCapacity(elementsLen + notInElementsLen);
uint32_t elementsIdx = 0;
@@ -196,8 +193,7 @@ nsresult HTMLFormControlsCollection::GetSortedControls(
// Append the remaining mNotInElements elements
// XXX(Bug 1631371) Check if this should use a fallible operation as it
// pretended earlier.
- aControls.AppendElements(mNotInElements->Elements() + notInElementsIdx,
- notInElementsLen - notInElementsIdx);
+ aControls.AppendElements(notInElements.From(notInElementsIdx));
break;
}
// Check whether we're done with mNotInElements
@@ -207,25 +203,22 @@ nsresult HTMLFormControlsCollection::GetSortedControls(
// Append the remaining mElements elements
// XXX(Bug 1631371) Check if this should use a fallible operation as it
// pretended earlier.
- aControls.AppendElements(mElements->Elements() + elementsIdx,
- elementsLen - elementsIdx);
+ aControls.AppendElements(elements.From(elementsIdx));
break;
}
// Both lists have elements left.
- NS_ASSERTION(mElements->ElementAt(elementsIdx) &&
- mNotInElements->ElementAt(notInElementsIdx),
+ NS_ASSERTION(elements[elementsIdx] && notInElements[notInElementsIdx],
"Should have remaining elements");
// Determine which of the two elements should be ordered
// first and add it to the end of the list.
nsGenericHTMLFormElement* elementToAdd;
if (nsContentUtils::CompareTreePosition<TreeKind::DOM>(
- mElements->ElementAt(elementsIdx),
- mNotInElements->ElementAt(notInElementsIdx), mForm,
+ elements[elementsIdx], notInElements[notInElementsIdx], mForm,
&indexCache) < 0) {
- elementToAdd = mElements->ElementAt(elementsIdx);
+ elementToAdd = elements[elementsIdx];
++elementsIdx;
} else {
- elementToAdd = mNotInElements->ElementAt(notInElementsIdx);
+ elementToAdd = notInElements[notInElementsIdx];
++notInElementsIdx;
}
// Add the first element to the list.
@@ -236,15 +229,11 @@ nsresult HTMLFormControlsCollection::GetSortedControls(
NS_ASSERTION(aControls.Length() == elementsLen + notInElementsLen,
"Not all form controls were added to the sorted list");
-#ifdef DEBUG
- HTMLFormElement::AssertDocumentOrder(aControls, mForm);
-#endif
-
return NS_OK;
}
Element* HTMLFormControlsCollection::GetElementAt(uint32_t aIndex) {
- return mElements->SafeElementAt(aIndex, nullptr);
+ return mElements.SafeElementAt(aIndex, nullptr);
}
/* virtual */
diff --git a/dom/html/HTMLFormElement.cpp b/dom/html/HTMLFormElement.cpp
@@ -262,10 +262,10 @@ void HTMLFormElement::MaybeSubmit(Element* aSubmitter) {
// FIXME: Should be specified, see:
// https://github.com/whatwg/html/issues/10066
{
- for (nsGenericHTMLFormElement* el : mControls->mElements.AsList()) {
+ for (nsGenericHTMLFormElement* el : mControls->mElements.AsSpan()) {
el->SetUserInteracted(true);
}
- for (nsGenericHTMLFormElement* el : mControls->mNotInElements.AsList()) {
+ for (nsGenericHTMLFormElement* el : mControls->mNotInElements.AsSpan()) {
el->SetUserInteracted(true);
}
}
@@ -397,15 +397,14 @@ nsresult HTMLFormElement::BindToTree(BindContext& aContext, nsINode& aParent) {
}
template <typename T>
-static void MarkOrphans(const nsTArray<T*>& aArray) {
- uint32_t length = aArray.Length();
- for (uint32_t i = 0; i < length; ++i) {
- aArray[i]->SetFlags(MAYBE_ORPHAN_FORM_ELEMENT);
+static void MarkOrphans(Span<T*> aArray) {
+ for (auto* element : aArray) {
+ element->SetFlags(MAYBE_ORPHAN_FORM_ELEMENT);
}
}
static void CollectOrphans(nsINode* aRemovalRoot,
- const nsTArray<nsGenericHTMLFormElement*>& aArray
+ TreeOrderedArray<nsGenericHTMLFormElement*>& aArray
#ifdef DEBUG
,
HTMLFormElement* aThisForm
@@ -450,7 +449,7 @@ static void CollectOrphans(nsINode* aRemovalRoot,
}
static void CollectOrphans(nsINode* aRemovalRoot,
- const nsTArray<HTMLImageElement*>& aArray
+ const TreeOrderedArray<HTMLImageElement*>& aArray
#ifdef DEBUG
,
HTMLFormElement* aThisForm
@@ -496,9 +495,9 @@ void HTMLFormElement::UnbindFromTree(UnbindContext& aContext) {
RefPtr<Document> oldDocument = GetUncomposedDoc();
// Mark all of our controls as maybe being orphans
- MarkOrphans(mControls->mElements.AsList());
- MarkOrphans(mControls->mNotInElements.AsList());
- MarkOrphans(mImageElements.AsList());
+ MarkOrphans(mControls->mElements.AsSpan());
+ MarkOrphans(mControls->mNotInElements.AsSpan());
+ MarkOrphans(mImageElements.AsSpan());
nsGenericHTMLElement::UnbindFromTree(aContext);
@@ -637,8 +636,7 @@ nsresult HTMLFormElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
nsresult HTMLFormElement::DoReset() {
// Make sure the presentation is up-to-date
- Document* doc = GetComposedDoc();
- if (doc) {
+ if (Document* doc = GetComposedDoc()) {
doc->FlushPendingNotifications(FlushType::ContentAndNotify);
}
@@ -646,8 +644,11 @@ nsresult HTMLFormElement::DoReset() {
uint32_t numElements = mControls->Length();
for (uint32_t elementX = 0; elementX < numElements; ++elementX) {
// Hold strong ref in case the reset does something weird
- nsCOMPtr<nsIFormControl> controlNode = nsIFormControl::FromNodeOrNull(
- mControls->mElements->SafeElementAt(elementX, nullptr));
+ if (elementX >= mControls->mElements.Length()) {
+ continue;
+ }
+ nsCOMPtr<nsIFormControl> controlNode =
+ nsIFormControl::FromNode(mControls->mElements[elementX]);
if (controlNode) {
controlNode->Reset();
}
@@ -1115,65 +1116,11 @@ NotNull<const Encoding*> HTMLFormElement::GetSubmitEncoding() {
}
Element* HTMLFormElement::IndexedGetter(uint32_t aIndex, bool& aFound) {
- Element* element = mControls->mElements->SafeElementAt(aIndex, nullptr);
+ Element* element = mControls->mElements.SafeElementAt(aIndex, nullptr);
aFound = element != nullptr;
return element;
}
-#ifdef DEBUG
-/**
- * Checks that all form elements are in document order. Asserts if any pair of
- * consecutive elements are not in increasing document order.
- *
- * @param aControls List of form controls to check.
- * @param aForm Parent form of the controls.
- */
-/* static */
-void HTMLFormElement::AssertDocumentOrder(
- const nsTArray<nsGenericHTMLFormElement*>& aControls, nsIContent* aForm) {
- // TODO: remove the if directive with bug 598468.
- // This is done to prevent asserts in some edge cases.
-# if 0
- // Only iterate if aControls is not empty, since otherwise
- // |aControls.Length() - 1| will be a very large unsigned number... not what
- // we want here.
- if (!aControls.IsEmpty()) {
- for (uint32_t i = 0; i < aControls.Length() - 1; ++i) {
- NS_ASSERTION(
- CompareFormControlPosition(aControls[i], aControls[i + 1], aForm) < 0,
- "Form controls not ordered correctly");
- }
- }
-# endif
-}
-
-/**
- * Copy of the above function, but with RefPtrs.
- *
- * @param aControls List of form controls to check.
- * @param aForm Parent form of the controls.
- */
-/* static */
-void HTMLFormElement::AssertDocumentOrder(
- const nsTArray<RefPtr<nsGenericHTMLFormElement>>& aControls,
- nsIContent* aForm) {
- // TODO: remove the if directive with bug 598468.
- // This is done to prevent asserts in some edge cases.
-# if 0
- // Only iterate if aControls is not empty, since otherwise
- // |aControls.Length() - 1| will be a very large unsigned number... not what
- // we want here.
- if (!aControls.IsEmpty()) {
- for (uint32_t i = 0; i < aControls.Length() - 1; ++i) {
- NS_ASSERTION(
- CompareFormControlPosition(aControls[i], aControls[i + 1], aForm) < 0,
- "Form controls not ordered correctly");
- }
- }
-# endif
-}
-#endif
-
nsresult HTMLFormElement::AddElement(nsGenericHTMLFormElement* aChild,
bool aUpdateValidity, bool aNotify) {
// If an element has a @form, we can assume it *might* be able to not have
@@ -1189,11 +1136,7 @@ nsresult HTMLFormElement::AddElement(nsGenericHTMLFormElement* aChild,
childInElements ? mControls->mElements : mControls->mNotInElements;
const size_t insertedIndex = controlList.Insert(*aChild, this);
- const bool lastElement = controlList->Length() == insertedIndex + 1;
-
-#ifdef DEBUG
- AssertDocumentOrder(controlList, this);
-#endif
+ const bool lastElement = controlList.Length() == insertedIndex + 1;
auto type = fc->ControlType();
@@ -1297,8 +1240,8 @@ nsresult HTMLFormElement::RemoveElement(nsGenericHTMLFormElement* aChild,
// Find the index of the child. This will be used later if necessary
// to find the default submit.
- size_t index = controls->IndexOf(aChild);
- NS_ENSURE_STATE(index != controls.AsList().NoIndex);
+ size_t index = controls.IndexOf(aChild);
+ NS_ENSURE_STATE(index != controls.NoIndex);
controls.RemoveElementAt(index);
@@ -1309,13 +1252,13 @@ nsresult HTMLFormElement::RemoveElement(nsGenericHTMLFormElement* aChild,
*firstSubmitSlot = nullptr;
// We are removing the first submit in this list, find the new first submit
- uint32_t length = controls->Length();
+ uint32_t length = controls.Length();
for (uint32_t i = index; i < length; ++i) {
const auto* currentControl =
- nsIFormControl::FromNode(controls->ElementAt(i));
+ nsIFormControl::FromNode(controls.ElementAt(i));
MOZ_ASSERT(currentControl);
if (currentControl->IsSubmitControl()) {
- *firstSubmitSlot = controls->ElementAt(i);
+ *firstSubmitSlot = controls.ElementAt(i);
break;
}
}
@@ -1659,13 +1602,14 @@ nsGenericHTMLFormElement* HTMLFormElement::GetDefaultSubmitElement() const {
bool HTMLFormElement::ImplicitSubmissionIsDisabled() const {
// Input text controls are always in the elements list.
uint32_t numDisablingControlsFound = 0;
- uint32_t length = mControls->mElements->Length();
- for (uint32_t i = 0; i < length && numDisablingControlsFound < 2; ++i) {
- const auto* fc =
- nsIFormControl::FromNode(mControls->mElements->ElementAt(i));
+ for (auto* element : mControls->mElements.AsSpan()) {
+ const auto* fc = nsIFormControl::FromNode(element);
MOZ_ASSERT(fc);
if (fc->IsSingleLineTextControl(false)) {
numDisablingControlsFound++;
+ if (numDisablingControlsFound > 1) {
+ break;
+ }
}
}
return numDisablingControlsFound != 1;
@@ -1675,7 +1619,7 @@ bool HTMLFormElement::IsLastActiveElement(
const nsGenericHTMLFormElement* aElement) const {
MOZ_ASSERT(aElement, "Unexpected call");
- for (auto* element : Reversed(mControls->mElements.AsList())) {
+ for (auto* element : Reversed(mControls->mElements.AsSpan())) {
const auto* fc = nsIFormControl::FromNode(element);
MOZ_ASSERT(fc);
// XXX How about date/time control?
@@ -1800,7 +1744,7 @@ int32_t HTMLFormElement::IndexOfContent(nsIContent* aContent) {
}
void HTMLFormElement::Clear() {
- for (HTMLImageElement* image : Reversed(mImageElements.AsList())) {
+ for (HTMLImageElement* image : Reversed(mImageElements.AsSpan())) {
image->ClearForm(false);
}
mImageElements.Clear();
diff --git a/dom/html/HTMLFormElement.h b/dom/html/HTMLFormElement.h
@@ -335,14 +335,6 @@ class HTMLFormElement final : public nsGenericHTMLElement {
void GetSupportedNames(nsTArray<nsString>& aRetval);
-#ifdef DEBUG
- static void AssertDocumentOrder(
- const nsTArray<nsGenericHTMLFormElement*>& aControls, nsIContent* aForm);
- static void AssertDocumentOrder(
- const nsTArray<RefPtr<nsGenericHTMLFormElement>>& aControls,
- nsIContent* aForm);
-#endif
-
JS::ExpandoAndGeneration mExpandoAndGeneration;
protected:
diff --git a/dom/html/HTMLSlotElement.cpp b/dom/html/HTMLSlotElement.cpp
@@ -113,7 +113,7 @@ static void FlattenAssignedNodes(HTMLSlotElement* aSlot,
return;
}
- const nsTArray<RefPtr<nsINode>>& assignedNodes = aSlot->AssignedNodes();
+ const Span<const RefPtr<nsINode>> assignedNodes = aSlot->AssignedNodes();
// If assignedNodes is empty, use children of slot as fallback content.
if (assignedNodes.IsEmpty()) {
@@ -148,7 +148,7 @@ void HTMLSlotElement::AssignedNodes(const AssignedNodesOptions& aOptions,
return FlattenAssignedNodes(this, aNodes);
}
- aNodes = mAssignedNodes.Clone();
+ aNodes.AppendElements(mAssignedNodes.AsSpan());
}
void HTMLSlotElement::AssignedElements(const AssignedNodesOptions& aOptions,
@@ -162,10 +162,6 @@ void HTMLSlotElement::AssignedElements(const AssignedNodesOptions& aOptions,
}
}
-const nsTArray<RefPtr<nsINode>>& HTMLSlotElement::AssignedNodes() const {
- return mAssignedNodes;
-}
-
const nsTArray<nsINode*>& HTMLSlotElement::ManuallyAssignedNodes() const {
return mManuallyAssignedNodes;
}
@@ -319,7 +315,7 @@ void HTMLSlotElement::AppendAssignedNode(nsIContent& aNode) {
void HTMLSlotElement::RecalculateHasSlottedState() {
bool hasSlotted = false;
// Find the first node that makes this a slotted element.
- for (const RefPtr<nsINode>& assignedNode : mAssignedNodes) {
+ for (const RefPtr<nsINode>& assignedNode : mAssignedNodes.AsSpan()) {
if (auto* slot = HTMLSlotElement::FromNode(assignedNode)) {
if (slot->IsInShadowTree() &&
!slot->State().HasState(ElementState::HAS_SLOTTED)) {
@@ -352,7 +348,7 @@ void HTMLSlotElement::RemoveAssignedNode(nsIContent& aNode) {
}
void HTMLSlotElement::ClearAssignedNodes() {
- for (RefPtr<nsINode>& node : mAssignedNodes) {
+ for (RefPtr<nsINode>& node : mAssignedNodes.AsSpan()) {
MOZ_ASSERT(!node->AsContent()->GetAssignedSlot() ||
node->AsContent()->GetAssignedSlot() == this,
"How exactly?");
diff --git a/dom/html/HTMLSlotElement.h b/dom/html/HTMLSlotElement.h
@@ -7,6 +7,7 @@
#ifndef mozilla_dom_HTMLSlotElement_h
#define mozilla_dom_HTMLSlotElement_h
+#include "mozilla/dom/FastFrontRemovableArray.h"
#include "nsGenericHTMLElement.h"
#include "nsTArray.h"
@@ -52,7 +53,7 @@ class HTMLSlotElement final : public nsGenericHTMLElement {
void Assign(const Sequence<OwningElementOrText>& aNodes);
// Helper methods
- const nsTArray<RefPtr<nsINode>>& AssignedNodes() const;
+ Span<const RefPtr<nsINode>> AssignedNodes() const { return mAssignedNodes; }
const nsTArray<nsINode*>& ManuallyAssignedNodes() const;
void InsertAssignedNode(uint32_t aIndex, nsIContent&);
void AppendAssignedNode(nsIContent&);
@@ -75,7 +76,7 @@ class HTMLSlotElement final : public nsGenericHTMLElement {
virtual ~HTMLSlotElement();
JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) final;
- nsTArray<RefPtr<nsINode>> mAssignedNodes;
+ FastFrontRemovableArray<RefPtr<nsINode>> mAssignedNodes;
nsTArray<nsINode*> mManuallyAssignedNodes;
// Whether we're in the signal slot list of our unit of related similar-origin
diff --git a/dom/serializers/nsDocumentEncoder.cpp b/dom/serializers/nsDocumentEncoder.cpp
@@ -987,8 +987,11 @@ nsresult nsDocumentEncoder::NodeSerializer::SerializeToStringRecursive(
++counter;
if (allowCrossShadowBoundary) {
if (const auto* slot = HTMLSlotElement::FromNode(node)) {
- auto* next = slot->AssignedNodes().SafeElementAt(counter);
- return next;
+ auto assigned = slot->AssignedNodes();
+ if (size_t(counter) < assigned.Length()) {
+ return assigned[counter];
+ }
+ return nullptr;
}
}
@@ -1260,8 +1263,11 @@ nsresult nsDocumentEncoder::RangeSerializer::SerializeChildrenOfContent(
uint32_t aCurrentIndex) -> nsIContent* {
if (mAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes) {
if (const auto* slot = HTMLSlotElement::FromNode(&aContent)) {
- auto* next = slot->AssignedNodes().SafeElementAt(++aCurrentIndex);
- return nsIContent::FromNodeOrNull(next);
+ auto assigned = slot->AssignedNodes();
+ if (++aCurrentIndex < assigned.Length()) {
+ return nsIContent::FromNode(assigned[aCurrentIndex]);
+ }
+ return nullptr;
}
}
diff --git a/dom/xul/XULPersist.cpp b/dom/xul/XULPersist.cpp
@@ -171,13 +171,13 @@ nsresult XULPersist::ApplyPersistentAttributes() {
// We want to hold strong refs to the elements while applying
// persistent attributes, just in case.
- const nsTArray<Element*>* allElements = mDocument->GetAllElementsForId(id);
- if (!allElements) {
+ const Span allElements = mDocument->GetAllElementsForId(id);
+ if (allElements.IsEmpty()) {
continue;
}
elements.Clear();
- elements.SetCapacity(allElements->Length());
- for (Element* element : *allElements) {
+ elements.SetCapacity(allElements.Length());
+ for (Element* element : allElements) {
elements.AppendObject(element);
}
diff --git a/layout/style/GeckoBindings.cpp b/layout/style/GeckoBindings.cpp
@@ -160,10 +160,12 @@ void Gecko_DestroyAnonymousContentList(nsTArray<nsIContent*>* aAnonContent) {
delete aAnonContent;
}
-const nsTArray<RefPtr<nsINode>>* Gecko_GetAssignedNodes(
- const Element* aElement) {
+RustSpan<const nsINode* const> Gecko_GetAssignedNodes(const Element* aElement) {
MOZ_ASSERT(HTMLSlotElement::FromNode(aElement));
- return &static_cast<const HTMLSlotElement*>(aElement)->AssignedNodes();
+ Span<const RefPtr<nsINode>> span =
+ static_cast<const HTMLSlotElement*>(aElement)->AssignedNodes();
+ return {reinterpret_cast<const nsINode* const*>(span.Elements()),
+ span.Length()};
}
void Gecko_GetQueryContainerSize(const Element* aElement, nscoord* aOutWidth,
@@ -1605,18 +1607,20 @@ void Gecko_ContentList_AppendAll(nsSimpleContentList* aList,
}
}
-const nsTArray<Element*>* Gecko_Document_GetElementsWithId(const Document* aDoc,
- nsAtom* aId) {
+RustSpan<const Element* const> Gecko_Document_GetElementsWithId(
+ const Document* aDoc, nsAtom* aId) {
MOZ_ASSERT(aDoc);
MOZ_ASSERT(aId);
- return aDoc->GetAllElementsForId(aId);
+ auto span = aDoc->GetAllElementsForId(aId);
+ return {span.Elements(), span.Length()};
}
-const nsTArray<Element*>* Gecko_ShadowRoot_GetElementsWithId(
+RustSpan<const Element* const> Gecko_ShadowRoot_GetElementsWithId(
const ShadowRoot* aShadowRoot, nsAtom* aId) {
MOZ_ASSERT(aShadowRoot);
MOZ_ASSERT(aId);
- return aShadowRoot->GetAllElementsForId(aId);
+ auto span = aShadowRoot->GetAllElementsForId(aId);
+ return {span.Elements(), span.Length()};
}
static StyleComputedMozPrefFeatureValue GetPrefValue(const nsCString& aPref) {
diff --git a/layout/style/GeckoBindings.h b/layout/style/GeckoBindings.h
@@ -55,6 +55,15 @@ const bool GECKO_IS_NIGHTLY = true;
const bool GECKO_IS_NIGHTLY = false;
#endif
+template <typename T>
+struct RustSpan {
+ T* begin;
+ size_t length;
+};
+
+template struct RustSpan<const mozilla::dom::Element* const>;
+template struct RustSpan<const nsINode* const>;
+
#define NS_DECL_THREADSAFE_FFI_REFCOUNTING(class_, name_) \
void Gecko_AddRef##name_##ArbitraryThread(class_* aPtr); \
void Gecko_Release##name_##ArbitraryThread(class_* aPtr);
@@ -83,7 +92,7 @@ const nsINode* Gecko_GetFlattenedTreeParentNode(const nsINode*);
void Gecko_GetAnonymousContentForElement(const mozilla::dom::Element*,
nsTArray<nsIContent*>*);
-const nsTArray<RefPtr<nsINode>>* Gecko_GetAssignedNodes(
+RustSpan<const nsINode* const> Gecko_GetAssignedNodes(
const mozilla::dom::Element*);
void Gecko_GetQueryContainerSize(const mozilla::dom::Element*,
@@ -547,10 +556,10 @@ void Gecko_ContentList_AppendAll(nsSimpleContentList* aContentList,
// FIXME(emilio): These two below should be a single function that takes a
// `const DocumentOrShadowRoot*`, but that doesn't make MSVC builds happy for a
// reason I haven't really dug into.
-const nsTArray<mozilla::dom::Element*>* Gecko_Document_GetElementsWithId(
+RustSpan<const mozilla::dom::Element* const> Gecko_Document_GetElementsWithId(
const mozilla::dom::Document*, nsAtom* aId);
-const nsTArray<mozilla::dom::Element*>* Gecko_ShadowRoot_GetElementsWithId(
+RustSpan<const mozilla::dom::Element* const> Gecko_ShadowRoot_GetElementsWithId(
const mozilla::dom::ShadowRoot*, nsAtom* aId);
bool Gecko_EvalMozPrefFeature(nsAtom*,
diff --git a/mfbt/Span.h b/mfbt/Span.h
@@ -382,6 +382,7 @@ class MOZ_GSL_POINTER Span {
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
constexpr static const index_type extent = Extent;
+ constexpr static const index_type npos = index_type(-1);
// [Span.cons], Span constructors, copy, assignment, and destructor
// "Dependent" is needed to make "std::enable_if_t<(Dependent ||
@@ -831,6 +832,18 @@ class MOZ_GSL_POINTER Span {
return {Elements(), Length()};
}
+ // Returns the index of the given element in the span, or `npos` otherwise.
+ template <typename Item>
+ index_type IndexOf(const Item& aItem) const {
+ auto begin = this->begin();
+ auto end = this->end();
+ auto it = std::find(begin, end, aItem);
+ if (it == end) {
+ return npos;
+ }
+ return index_type(it - begin);
+ }
+
private:
// this implementation detail class lets us take advantage of the
// empty base class optimization to pay for only storage of a single
diff --git a/servo/components/style/gecko/wrapper.rs b/servo/components/style/gecko/wrapper.rs
@@ -93,17 +93,14 @@ use std::sync::atomic::{AtomicU32, Ordering};
#[inline]
fn elements_with_id<'a, 'le>(
- array: *const structs::nsTArray<*mut RawGeckoElement>,
+ array: structs::RustSpan<*const RawGeckoElement>,
) -> &'a [GeckoElement<'le>] {
unsafe {
- if array.is_null() {
- return &[];
- }
-
- let elements: &[*mut RawGeckoElement] = &**array;
+ let elements: &[*const RawGeckoElement] =
+ std::slice::from_raw_parts(array.begin, array.length);
// NOTE(emilio): We rely on the in-memory representation of
- // GeckoElement<'ld> and *mut RawGeckoElement being the same.
+ // GeckoElement<'ld> and *const RawGeckoElement being the same.
#[allow(dead_code)]
unsafe fn static_assert() {
mem::transmute::<*mut RawGeckoElement, GeckoElement<'static>>(0xbadc0de as *mut _);
@@ -289,14 +286,14 @@ impl<'ln> GeckoNode<'ln> {
}
fn flags_atomic_for(flags: &Cell<u32>) -> &AtomicU32 {
- const_assert!(std::mem::size_of::<Cell<u32>>() == std::mem::size_of::<AtomicU32>());
- const_assert!(std::mem::align_of::<Cell<u32>>() == std::mem::align_of::<AtomicU32>());
+ const_assert!(mem::size_of::<Cell<u32>>() == mem::size_of::<AtomicU32>());
+ const_assert!(mem::align_of::<Cell<u32>>() == mem::align_of::<AtomicU32>());
// Rust doesn't provide standalone atomic functions like GCC/clang do
// (via the atomic intrinsics) or via std::atomic_ref, but it guarantees
// that the memory representation of u32 and AtomicU32 matches:
// https://doc.rust-lang.org/std/sync/atomic/struct.AtomicU32.html
- unsafe { std::mem::transmute::<&Cell<u32>, &AtomicU32>(flags) }
+ unsafe { mem::transmute::<&Cell<u32>, &AtomicU32>(flags) }
}
#[inline]
@@ -592,7 +589,7 @@ pub enum GeckoChildrenIterator<'a> {
/// replaces it with the next sibling when requested.
Current(Option<GeckoNode<'a>>),
/// A Gecko-implemented iterator we need to drop appropriately.
- GeckoIterator(std::mem::ManuallyDrop<structs::StyleChildrenIterator>),
+ GeckoIterator(mem::ManuallyDrop<structs::StyleChildrenIterator>),
}
impl<'a> Drop for GeckoChildrenIterator<'a> {
@@ -1071,10 +1068,10 @@ impl<'le> TElement for GeckoElement<'le> {
|| self.may_have_anonymous_children()
{
unsafe {
- let mut iter = std::mem::MaybeUninit::<structs::StyleChildrenIterator>::uninit();
+ let mut iter = mem::MaybeUninit::<structs::StyleChildrenIterator>::uninit();
bindings::Gecko_ConstructStyleChildrenIterator(self.0, iter.as_mut_ptr());
return LayoutIterator(GeckoChildrenIterator::GeckoIterator(
- std::mem::ManuallyDrop::new(iter.assume_init()),
+ mem::ManuallyDrop::new(iter.assume_init()),
));
}
}
@@ -1143,38 +1140,23 @@ impl<'le> TElement for GeckoElement<'le> {
if !self.is_html_slot_element() || !self.as_node().is_in_shadow_tree() {
return &[];
}
-
- let slot: &structs::HTMLSlotElement = unsafe { mem::transmute(self.0) };
-
if cfg!(debug_assertions) {
+ let slot: &structs::HTMLSlotElement = unsafe { mem::transmute(self.0) };
let base: &RawGeckoElement = &slot._base._base._base;
assert_eq!(base as *const _, self.0 as *const _, "Bad cast");
}
-
- // FIXME(emilio): Workaround a bindgen bug on Android that causes
- // mAssignedNodes to be at the wrong offset. See bug 1466406.
- //
- // Bug 1466580 tracks running the Android layout tests on automation.
- //
- // The actual bindgen bug still needs reduction.
- let assigned_nodes: &[structs::RefPtr<structs::nsINode>] = if !cfg!(target_os = "android") {
- debug_assert_eq!(
- unsafe { bindings::Gecko_GetAssignedNodes(self.0) },
- &slot.mAssignedNodes as *const _,
- );
-
- &*slot.mAssignedNodes
- } else {
- unsafe { &**bindings::Gecko_GetAssignedNodes(self.0) }
- };
-
- debug_assert_eq!(
- mem::size_of::<structs::RefPtr<structs::nsINode>>(),
- mem::size_of::<Self::ConcreteNode>(),
- "Bad cast!"
- );
-
- unsafe { mem::transmute(assigned_nodes) }
+ unsafe {
+ let nodes = bindings::Gecko_GetAssignedNodes(self.0);
+ let nodes: &[*const RawGeckoNode] =
+ std::slice::from_raw_parts(nodes.begin, nodes.length);
+ // NOTE(emilio): We rely on the in-memory representation of
+ // GeckoNode<'ld> and *const RawGeckoNode being the same.
+ #[allow(dead_code)]
+ unsafe fn static_assert() {
+ mem::transmute::<*mut RawGeckoNode, GeckoNode<'static>>(0xbadc0de as *mut _);
+ }
+ mem::transmute(nodes)
+ }
}
#[inline]