ChildIterator.cpp (10066B)
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 file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "ChildIterator.h" 8 9 #include "mozilla/dom/Document.h" 10 #include "mozilla/dom/HTMLSlotElement.h" 11 #include "mozilla/dom/ShadowRoot.h" 12 #include "nsCSSAnonBoxes.h" 13 #include "nsContentUtils.h" 14 #include "nsIAnonymousContentCreator.h" 15 #include "nsIFrame.h" 16 #include "nsLayoutUtils.h" 17 18 namespace mozilla::dom { 19 20 FlattenedChildIterator::FlattenedChildIterator(const nsIContent* aParent, 21 bool aStartAtBeginning) 22 : mParent(aParent), mOriginalParent(aParent), mIsFirst(aStartAtBeginning) { 23 if (!mParent->IsElement()) { 24 // TODO(emilio): I think it probably makes sense to only allow constructing 25 // FlattenedChildIterators with Element. 26 return; 27 } 28 29 if (ShadowRoot* shadow = mParent->AsElement()->GetShadowRoot()) { 30 mParent = shadow; 31 mShadowDOMInvolved = true; 32 return; 33 } 34 35 if (const auto* slot = HTMLSlotElement::FromNode(mParent)) { 36 if (!slot->AssignedNodes().IsEmpty()) { 37 mParentAsSlot = slot; 38 if (!aStartAtBeginning) { 39 mIndexInInserted = slot->AssignedNodes().Length(); 40 } 41 mShadowDOMInvolved = true; 42 } 43 } 44 } 45 46 uint32_t FlattenedChildIterator::GetLength(const nsINode* aParent) { 47 if (const auto* element = Element::FromNode(aParent)) { 48 if (const auto* slot = HTMLSlotElement::FromNode(element)) { 49 if (uint32_t len = slot->AssignedNodes().Length()) { 50 return len; 51 } 52 } else if (auto* shadowRoot = element->GetShadowRoot()) { 53 return shadowRoot->GetChildCount(); 54 } 55 } 56 return aParent->GetChildCount(); 57 } 58 59 Maybe<uint32_t> FlattenedChildIterator::GetIndexOf( 60 const nsINode* aParent, const nsINode* aPossibleChild) { 61 if (const auto* element = Element::FromNode(aParent)) { 62 if (const auto* slot = HTMLSlotElement::FromNode(element)) { 63 const Span assigned = slot->AssignedNodes(); 64 if (!assigned.IsEmpty()) { 65 auto index = assigned.IndexOf(aPossibleChild); 66 if (index == assigned.npos) { 67 return Nothing(); 68 } 69 return Some(index); 70 } 71 } else if (auto* shadowRoot = element->GetShadowRoot()) { 72 return shadowRoot->ComputeIndexOf(aPossibleChild); 73 } 74 } 75 return aParent->ComputeIndexOf(aPossibleChild); 76 } 77 78 nsIContent* FlattenedChildIterator::GetNextChild() { 79 // If we're already in the inserted-children array, look there first 80 if (mParentAsSlot) { 81 const Span assignedNodes = mParentAsSlot->AssignedNodes(); 82 if (mIsFirst) { 83 mIsFirst = false; 84 MOZ_ASSERT(mIndexInInserted == 0); 85 mChild = assignedNodes[0]->AsContent(); 86 return mChild; 87 } 88 MOZ_ASSERT(mIndexInInserted <= assignedNodes.Length()); 89 if (mIndexInInserted + 1 >= assignedNodes.Length()) { 90 mIndexInInserted = assignedNodes.Length(); 91 return nullptr; 92 } 93 mChild = assignedNodes[++mIndexInInserted]->AsContent(); 94 return mChild; 95 } 96 97 if (mIsFirst) { // at the beginning of the child list 98 mChild = mParent->GetFirstChild(); 99 mIsFirst = false; 100 } else if (mChild) { // in the middle of the child list 101 mChild = mChild->GetNextSibling(); 102 } 103 104 return mChild; 105 } 106 107 bool FlattenedChildIterator::Seek(const nsIContent* aChildToFind) { 108 if (!mParentAsSlot && aChildToFind->GetParent() == mParent && 109 !aChildToFind->IsRootOfNativeAnonymousSubtree()) { 110 // Fast path: just point ourselves to aChildToFind, which is a 111 // normal DOM child of ours. 112 mChild = const_cast<nsIContent*>(aChildToFind); 113 mIndexInInserted = 0; 114 mIsFirst = false; 115 return true; 116 } 117 118 // Can we add more fast paths here based on whether the parent of aChildToFind 119 // is a This version can take shortcuts that the two-argument version 120 // can't, so can be faster (and in fact cshadow insertion point or content 121 // insertion point? 122 123 // It would be nice to assert that we find aChildToFind, but bz thinks that 124 // we might not find aChildToFind when called from ContentInserted 125 // if first-letter frames are about. 126 while (nsIContent* child = GetNextChild()) { 127 if (child == aChildToFind) { 128 return true; 129 } 130 } 131 132 return false; 133 } 134 135 nsIContent* FlattenedChildIterator::GetPreviousChild() { 136 if (mIsFirst) { // at the beginning of the child list 137 return nullptr; 138 } 139 if (mParentAsSlot) { 140 const Span assignedNodes = mParentAsSlot->AssignedNodes(); 141 MOZ_ASSERT(mIndexInInserted <= assignedNodes.Length()); 142 if (mIndexInInserted == 0) { 143 mIsFirst = true; 144 return nullptr; 145 } 146 mChild = assignedNodes[--mIndexInInserted]->AsContent(); 147 return mChild; 148 } 149 if (mChild) { // in the middle of the child list 150 mChild = mChild->GetPreviousSibling(); 151 } else { // at the end of the child list 152 mChild = mParent->GetLastChild(); 153 } 154 if (!mChild) { 155 mIsFirst = true; 156 } 157 158 return mChild; 159 } 160 161 nsIContent* AllChildrenIterator::Get() const { 162 switch (mPhase) { 163 case Phase::AtBackdropKid: { 164 Element* backdrop = nsLayoutUtils::GetBackdropPseudo(Parent()); 165 MOZ_ASSERT(backdrop, "No content marker frame at AtBackdropKid phase"); 166 return backdrop; 167 } 168 169 case Phase::AtMarkerKid: { 170 Element* marker = nsLayoutUtils::GetMarkerPseudo(Parent()); 171 MOZ_ASSERT(marker, "No content marker frame at AtMarkerKid phase"); 172 return marker; 173 } 174 175 case Phase::AtBeforeKid: { 176 Element* before = nsLayoutUtils::GetBeforePseudo(Parent()); 177 MOZ_ASSERT(before, "No content before frame at AtBeforeKid phase"); 178 return before; 179 } 180 181 case Phase::AtFlatTreeKids: 182 return FlattenedChildIterator::Get(); 183 184 case Phase::AtAnonKids: 185 return mAnonKids[mAnonKidsIdx]; 186 187 case Phase::AtAfterKid: { 188 Element* after = nsLayoutUtils::GetAfterPseudo(Parent()); 189 MOZ_ASSERT(after, "No content after frame at AtAfterKid phase"); 190 return after; 191 } 192 193 default: 194 return nullptr; 195 } 196 } 197 198 bool AllChildrenIterator::Seek(const nsIContent* aChildToFind) { 199 while (mPhase != Phase::AtEnd) { 200 if (mPhase == Phase::AtFlatTreeKids) { 201 if (FlattenedChildIterator::Seek(aChildToFind)) { 202 return true; 203 } 204 mPhase = Phase::AtAnonKids; 205 } 206 if (GetNextChild() == aChildToFind) { 207 return true; 208 } 209 } 210 return false; 211 } 212 213 void AllChildrenIterator::AppendNativeAnonymousChildren() { 214 nsContentUtils::AppendNativeAnonymousChildren(Parent(), mAnonKids, mFlags); 215 } 216 217 nsIContent* AllChildrenIterator::GetNextChild() { 218 switch (mPhase) { 219 case Phase::AtBegin: 220 if (Element* backdropPseudo = 221 nsLayoutUtils::GetBackdropPseudo(Parent())) { 222 mPhase = Phase::AtBackdropKid; 223 return backdropPseudo; 224 } 225 [[fallthrough]]; 226 case Phase::AtBackdropKid: 227 if (Element* markerContent = nsLayoutUtils::GetMarkerPseudo(Parent())) { 228 mPhase = Phase::AtMarkerKid; 229 return markerContent; 230 } 231 [[fallthrough]]; 232 case Phase::AtMarkerKid: 233 if (Element* beforeContent = nsLayoutUtils::GetBeforePseudo(Parent())) { 234 mPhase = Phase::AtBeforeKid; 235 return beforeContent; 236 } 237 [[fallthrough]]; 238 case Phase::AtBeforeKid: 239 [[fallthrough]]; 240 case Phase::AtFlatTreeKids: 241 if (nsIContent* kid = FlattenedChildIterator::GetNextChild()) { 242 mPhase = Phase::AtFlatTreeKids; 243 return kid; 244 } 245 [[fallthrough]]; 246 case Phase::AtAnonKids: 247 if (mAnonKids.IsEmpty()) { 248 MOZ_ASSERT(mAnonKidsIdx == UINT32_MAX); 249 AppendNativeAnonymousChildren(); 250 mAnonKidsIdx = 0; 251 } else if (mAnonKidsIdx == UINT32_MAX) { 252 mAnonKidsIdx = 0; 253 } else { 254 mAnonKidsIdx++; 255 } 256 if (mAnonKidsIdx < mAnonKids.Length()) { 257 mPhase = Phase::AtAnonKids; 258 return mAnonKids[mAnonKidsIdx]; 259 } 260 if (Element* afterContent = nsLayoutUtils::GetAfterPseudo(Parent())) { 261 mPhase = Phase::AtAfterKid; 262 return afterContent; 263 } 264 [[fallthrough]]; 265 case Phase::AtAfterKid: 266 case Phase::AtEnd: 267 break; 268 } 269 270 mPhase = Phase::AtEnd; 271 return nullptr; 272 } 273 274 nsIContent* AllChildrenIterator::GetPreviousChild() { 275 switch (mPhase) { 276 case Phase::AtEnd: 277 if (Element* afterContent = nsLayoutUtils::GetAfterPseudo(Parent())) { 278 mPhase = Phase::AtAfterKid; 279 return afterContent; 280 } 281 [[fallthrough]]; 282 case Phase::AtAfterKid: 283 MOZ_ASSERT(mAnonKidsIdx == mAnonKids.Length()); 284 [[fallthrough]]; 285 case Phase::AtAnonKids: 286 if (mAnonKids.IsEmpty()) { 287 AppendNativeAnonymousChildren(); 288 mAnonKidsIdx = mAnonKids.Length(); 289 } 290 // If 0 then it turns into UINT32_MAX, which indicates the iterator is 291 // before the anonymous children. 292 --mAnonKidsIdx; 293 if (mAnonKidsIdx < mAnonKids.Length()) { 294 mPhase = Phase::AtAnonKids; 295 return mAnonKids[mAnonKidsIdx]; 296 } 297 [[fallthrough]]; 298 case Phase::AtFlatTreeKids: 299 if (nsIContent* kid = FlattenedChildIterator::GetPreviousChild()) { 300 mPhase = Phase::AtFlatTreeKids; 301 return kid; 302 } 303 if (Element* beforeContent = nsLayoutUtils::GetBeforePseudo(Parent())) { 304 mPhase = Phase::AtBeforeKid; 305 return beforeContent; 306 } 307 [[fallthrough]]; 308 case Phase::AtBeforeKid: 309 if (Element* markerContent = nsLayoutUtils::GetMarkerPseudo(Parent())) { 310 mPhase = Phase::AtMarkerKid; 311 return markerContent; 312 } 313 [[fallthrough]]; 314 case Phase::AtMarkerKid: 315 if (Element* backdrop = nsLayoutUtils::GetBackdropPseudo(Parent())) { 316 mPhase = Phase::AtBackdropKid; 317 return backdrop; 318 } 319 [[fallthrough]]; 320 case Phase::AtBackdropKid: 321 case Phase::AtBegin: 322 break; 323 } 324 325 mPhase = Phase::AtBegin; 326 return nullptr; 327 } 328 329 } // namespace mozilla::dom