nsContentList.cpp (37341B)
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 /* 8 * nsBaseContentList is a basic list of content nodes; nsContentList 9 * is a commonly used NodeList implementation (used for 10 * getElementsByTagName, some properties on HTMLDocument/Document, etc). 11 */ 12 13 #include "nsContentList.h" 14 15 #include <algorithm> 16 17 #include "PLDHashTable.h" 18 #include "jsfriendapi.h" 19 #include "mozilla/ContentIterator.h" 20 #include "mozilla/MruCache.h" 21 #include "mozilla/StaticPtr.h" 22 #include "mozilla/dom/Document.h" 23 #include "mozilla/dom/Element.h" 24 #include "mozilla/dom/HTMLCollectionBinding.h" 25 #include "mozilla/dom/NodeInfoInlines.h" 26 #include "mozilla/dom/NodeListBinding.h" 27 #include "nsCCUncollectableMarker.h" 28 #include "nsContentUtils.h" 29 #include "nsGenericHTMLElement.h" 30 #include "nsGkAtoms.h" 31 #include "nsIContent.h" 32 #include "nsTHashtable.h" 33 #include "nsWrapperCacheInlines.h" 34 35 #ifdef DEBUG_CONTENT_LIST 36 # define ASSERT_IN_SYNC AssertInSync() 37 #else 38 # define ASSERT_IN_SYNC PR_BEGIN_MACRO PR_END_MACRO 39 #endif 40 41 using namespace mozilla; 42 using namespace mozilla::dom; 43 44 nsBaseContentList::~nsBaseContentList() = default; 45 46 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(nsBaseContentList) 47 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsBaseContentList) 48 NS_IMPL_CYCLE_COLLECTION_UNLINK(mElements) 49 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER 50 tmp->RemoveFromCaches(); 51 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 52 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsBaseContentList) 53 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElements) 54 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 55 56 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsBaseContentList) 57 if (nsCCUncollectableMarker::sGeneration && tmp->HasKnownLiveWrapper()) { 58 for (uint32_t i = 0; i < tmp->mElements.Length(); ++i) { 59 nsIContent* c = tmp->mElements[i]; 60 if (c->IsPurple()) { 61 c->RemovePurple(); 62 } 63 Element::MarkNodeChildren(c); 64 } 65 return true; 66 } 67 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END 68 69 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsBaseContentList) 70 return nsCCUncollectableMarker::sGeneration && tmp->HasKnownLiveWrapper(); 71 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END 72 73 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsBaseContentList) 74 return nsCCUncollectableMarker::sGeneration && tmp->HasKnownLiveWrapper(); 75 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END 76 77 // QueryInterface implementation for nsBaseContentList 78 NS_INTERFACE_TABLE_HEAD(nsBaseContentList) 79 NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY 80 NS_INTERFACE_TABLE(nsBaseContentList, nsINodeList) 81 NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsBaseContentList) 82 NS_INTERFACE_MAP_END 83 84 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsBaseContentList) 85 NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(nsBaseContentList, 86 LastRelease()) 87 88 nsIContent* nsBaseContentList::Item(uint32_t aIndex) { 89 return mElements.SafeElementAt(aIndex); 90 } 91 92 int32_t nsBaseContentList::IndexOf(nsIContent* aContent, bool aDoFlush) { 93 return mElements.IndexOf(aContent); 94 } 95 96 int32_t nsBaseContentList::IndexOf(nsIContent* aContent) { 97 return IndexOf(aContent, true); 98 } 99 100 size_t nsBaseContentList::SizeOfIncludingThis( 101 MallocSizeOf aMallocSizeOf) const { 102 size_t n = aMallocSizeOf(this); 103 n += mElements.ShallowSizeOfExcludingThis(aMallocSizeOf); 104 return n; 105 } 106 107 NS_IMPL_CYCLE_COLLECTION_INHERITED(nsSimpleContentList, nsBaseContentList, 108 mRoot) 109 110 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsSimpleContentList) 111 NS_INTERFACE_MAP_END_INHERITING(nsBaseContentList) 112 113 NS_IMPL_ADDREF_INHERITED(nsSimpleContentList, nsBaseContentList) 114 NS_IMPL_RELEASE_INHERITED(nsSimpleContentList, nsBaseContentList) 115 116 JSObject* nsSimpleContentList::WrapObject(JSContext* cx, 117 JS::Handle<JSObject*> aGivenProto) { 118 return NodeList_Binding::Wrap(cx, this, aGivenProto); 119 } 120 121 NS_IMPL_CYCLE_COLLECTION_INHERITED(nsEmptyContentList, nsBaseContentList, mRoot) 122 123 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsEmptyContentList) 124 NS_INTERFACE_MAP_ENTRY(nsIHTMLCollection) 125 NS_INTERFACE_MAP_END_INHERITING(nsBaseContentList) 126 127 NS_IMPL_ADDREF_INHERITED(nsEmptyContentList, nsBaseContentList) 128 NS_IMPL_RELEASE_INHERITED(nsEmptyContentList, nsBaseContentList) 129 130 JSObject* nsEmptyContentList::WrapObject(JSContext* cx, 131 JS::Handle<JSObject*> aGivenProto) { 132 return HTMLCollection_Binding::Wrap(cx, this, aGivenProto); 133 } 134 135 mozilla::dom::Element* nsEmptyContentList::GetElementAt(uint32_t index) { 136 return nullptr; 137 } 138 139 mozilla::dom::Element* nsEmptyContentList::GetFirstNamedElement( 140 const nsAString& aName, bool& aFound) { 141 aFound = false; 142 return nullptr; 143 } 144 145 void nsEmptyContentList::GetSupportedNames(nsTArray<nsString>& aNames) {} 146 147 nsIContent* nsEmptyContentList::Item(uint32_t aIndex) { return nullptr; } 148 149 struct ContentListCache 150 : public MruCache<nsContentListKey, nsContentList*, ContentListCache> { 151 static HashNumber Hash(const nsContentListKey& aKey) { 152 return aKey.GetHash(); 153 } 154 static bool Match(const nsContentListKey& aKey, const nsContentList* aVal) { 155 return aVal->MatchesKey(aKey); 156 } 157 }; 158 159 static ContentListCache sRecentlyUsedContentLists; 160 161 class nsContentList::HashEntry : public PLDHashEntryHdr { 162 public: 163 using KeyType = const nsContentListKey*; 164 using KeyTypePointer = KeyType; 165 166 // Note that this is creating a blank entry, so you'll have to manually 167 // initialize it after it has been inserted into the hash table. 168 explicit HashEntry(KeyTypePointer aKey) : mContentList(nullptr) {} 169 170 HashEntry(HashEntry&& aEnt) : mContentList(std::move(aEnt.mContentList)) {} 171 172 ~HashEntry() { 173 if (mContentList) { 174 MOZ_RELEASE_ASSERT(mContentList->mInHashtable); 175 mContentList->mInHashtable = false; 176 } 177 } 178 179 bool KeyEquals(KeyTypePointer aKey) const { 180 return mContentList->MatchesKey(*aKey); 181 } 182 183 static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; } 184 185 static PLDHashNumber HashKey(KeyTypePointer aKey) { return aKey->GetHash(); } 186 187 nsContentList* GetContentList() const { return mContentList; } 188 void SetContentList(nsContentList* aContentList) { 189 MOZ_RELEASE_ASSERT(!mContentList); 190 MOZ_ASSERT(aContentList); 191 MOZ_RELEASE_ASSERT(!aContentList->mInHashtable); 192 mContentList = aContentList; 193 mContentList->mInHashtable = true; 194 } 195 196 enum { ALLOW_MEMMOVE = true }; 197 198 private: 199 nsContentList* MOZ_UNSAFE_REF( 200 "This entry will be removed in nsContentList::RemoveFromHashtable " 201 "before mContentList is destroyed") mContentList; 202 }; 203 204 // Hashtable for storing nsContentLists 205 static StaticAutoPtr<nsTHashtable<nsContentList::HashEntry>> 206 gContentListHashTable; 207 208 already_AddRefed<nsContentList> NS_GetContentList(nsINode* aRootNode, 209 int32_t aMatchNameSpaceId, 210 const nsAString& aTagname) { 211 NS_ASSERTION(aRootNode, "content list has to have a root"); 212 213 RefPtr<nsContentList> list; 214 nsContentListKey hashKey(aRootNode, aMatchNameSpaceId, aTagname, 215 aRootNode->OwnerDoc()->IsHTMLDocument()); 216 auto p = sRecentlyUsedContentLists.Lookup(hashKey); 217 if (p) { 218 list = p.Data(); 219 return list.forget(); 220 } 221 222 // Initialize the hashtable if needed. 223 if (!gContentListHashTable) { 224 gContentListHashTable = new nsTHashtable<nsContentList::HashEntry>(); 225 } 226 227 // First we look in our hashtable. Then we create a content list if needed 228 auto entry = gContentListHashTable->PutEntry(&hashKey, fallible); 229 if (entry) { 230 list = entry->GetContentList(); 231 } 232 233 if (!list) { 234 // We need to create a ContentList and add it to our new entry, if 235 // we have an entry 236 RefPtr<nsAtom> xmlAtom = NS_Atomize(aTagname); 237 RefPtr<nsAtom> htmlAtom; 238 if (aMatchNameSpaceId == kNameSpaceID_Unknown) { 239 nsAutoString lowercaseName; 240 nsContentUtils::ASCIIToLower(aTagname, lowercaseName); 241 htmlAtom = NS_Atomize(lowercaseName); 242 } else { 243 htmlAtom = xmlAtom; 244 } 245 list = new nsContentList(aRootNode, aMatchNameSpaceId, htmlAtom, xmlAtom); 246 if (entry) { 247 entry->SetContentList(list); 248 } 249 } 250 251 p.Set(list); 252 return list.forget(); 253 } 254 255 #ifdef DEBUG 256 const nsCacheableFuncStringContentList::ContentListType 257 nsCachableElementsByNameNodeList::sType = 258 nsCacheableFuncStringContentList::eNodeList; 259 const nsCacheableFuncStringContentList::ContentListType 260 nsCacheableFuncStringHTMLCollection::sType = 261 nsCacheableFuncStringContentList::eHTMLCollection; 262 #endif 263 264 class nsCacheableFuncStringContentList::HashEntry : public PLDHashEntryHdr { 265 public: 266 using KeyType = const nsFuncStringCacheKey*; 267 using KeyTypePointer = KeyType; 268 269 // Note that this is creating a blank entry, so you'll have to manually 270 // initialize it after it has been inserted into the hash table. 271 explicit HashEntry(KeyTypePointer aKey) : mContentList(nullptr) {} 272 273 HashEntry(HashEntry&& aEnt) : mContentList(std::move(aEnt.mContentList)) {} 274 275 ~HashEntry() { 276 if (mContentList) { 277 MOZ_RELEASE_ASSERT(mContentList->mInHashtable); 278 mContentList->mInHashtable = false; 279 } 280 } 281 282 bool KeyEquals(KeyTypePointer aKey) const { 283 return mContentList->Equals(aKey); 284 } 285 286 static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; } 287 288 static PLDHashNumber HashKey(KeyTypePointer aKey) { return aKey->GetHash(); } 289 290 nsCacheableFuncStringContentList* GetContentList() const { 291 return mContentList; 292 } 293 void SetContentList(nsCacheableFuncStringContentList* aContentList) { 294 MOZ_RELEASE_ASSERT(!mContentList); 295 MOZ_ASSERT(aContentList); 296 MOZ_RELEASE_ASSERT(!aContentList->mInHashtable); 297 mContentList = aContentList; 298 mContentList->mInHashtable = true; 299 } 300 301 enum { ALLOW_MEMMOVE = true }; 302 303 private: 304 nsCacheableFuncStringContentList* MOZ_UNSAFE_REF( 305 "This entry will be removed in " 306 "nsCacheableFuncStringContentList::RemoveFromFuncStringHashtable " 307 "before mContentList is destroyed") mContentList; 308 }; 309 310 // Hashtable for storing nsCacheableFuncStringContentList 311 static StaticAutoPtr<nsTHashtable<nsCacheableFuncStringContentList::HashEntry>> 312 gFuncStringContentListHashTable; 313 314 template <class ListType> 315 already_AddRefed<nsContentList> GetFuncStringContentList( 316 nsINode* aRootNode, nsContentListMatchFunc aFunc, 317 nsContentListDestroyFunc aDestroyFunc, 318 nsFuncStringContentListDataAllocator aDataAllocator, 319 const nsAString& aString) { 320 NS_ASSERTION(aRootNode, "content list has to have a root"); 321 322 RefPtr<nsCacheableFuncStringContentList> list; 323 324 // Initialize the hashtable if needed. 325 if (!gFuncStringContentListHashTable) { 326 gFuncStringContentListHashTable = 327 new nsTHashtable<nsCacheableFuncStringContentList::HashEntry>(); 328 } 329 330 nsCacheableFuncStringContentList::HashEntry* entry = nullptr; 331 // First we look in our hashtable. Then we create a content list if needed 332 if (gFuncStringContentListHashTable) { 333 nsFuncStringCacheKey hashKey(aRootNode, aFunc, aString); 334 335 entry = gFuncStringContentListHashTable->PutEntry(&hashKey, fallible); 336 if (entry) { 337 list = entry->GetContentList(); 338 #ifdef DEBUG 339 MOZ_ASSERT_IF(list, list->mType == ListType::sType); 340 #endif 341 } 342 } 343 344 if (!list) { 345 // We need to create a ContentList and add it to our new entry, if 346 // we have an entry 347 list = 348 new ListType(aRootNode, aFunc, aDestroyFunc, aDataAllocator, aString); 349 if (entry) { 350 entry->SetContentList(list); 351 } 352 } 353 354 // Don't cache these lists globally 355 356 return list.forget(); 357 } 358 359 // Explicit instantiations to avoid link errors 360 template already_AddRefed<nsContentList> 361 GetFuncStringContentList<nsCachableElementsByNameNodeList>( 362 nsINode* aRootNode, nsContentListMatchFunc aFunc, 363 nsContentListDestroyFunc aDestroyFunc, 364 nsFuncStringContentListDataAllocator aDataAllocator, 365 const nsAString& aString); 366 template already_AddRefed<nsContentList> 367 GetFuncStringContentList<nsCacheableFuncStringHTMLCollection>( 368 nsINode* aRootNode, nsContentListMatchFunc aFunc, 369 nsContentListDestroyFunc aDestroyFunc, 370 nsFuncStringContentListDataAllocator aDataAllocator, 371 const nsAString& aString); 372 373 //----------------------------------------------------- 374 // nsContentList implementation 375 376 nsContentList::nsContentList(nsINode* aRootNode, int32_t aMatchNameSpaceId, 377 nsAtom* aHTMLMatchAtom, nsAtom* aXMLMatchAtom, 378 bool aDeep, bool aLiveList) 379 : nsBaseContentList(), 380 mRootNode(aRootNode), 381 mMatchNameSpaceId(aMatchNameSpaceId), 382 mHTMLMatchAtom(aHTMLMatchAtom), 383 mXMLMatchAtom(aXMLMatchAtom), 384 mState(State::Dirty), 385 mDeep(aDeep), 386 mFuncMayDependOnAttr(false), 387 mIsHTMLDocument(aRootNode->OwnerDoc()->IsHTMLDocument()), 388 mNamedItemsCacheValid(false), 389 mIsLiveList(aLiveList), 390 mInHashtable(false) { 391 NS_ASSERTION(mRootNode, "Must have root"); 392 if (nsGkAtoms::_asterisk == mHTMLMatchAtom) { 393 NS_ASSERTION(mXMLMatchAtom == nsGkAtoms::_asterisk, 394 "HTML atom and XML atom are not both asterisk?"); 395 mMatchAll = true; 396 } else { 397 mMatchAll = false; 398 } 399 // This is aLiveList instead of mIsLiveList to avoid Valgrind errors. 400 if (aLiveList) { 401 SetEnabledCallbacks(nsIMutationObserver::kNodeWillBeDestroyed); 402 mRootNode->AddMutationObserver(this); 403 } 404 405 // We only need to flush if we're in an non-HTML document, since the 406 // HTML5 parser doesn't need flushing. Further, if we're not in a 407 // document at all right now (in the GetUncomposedDoc() sense), we're 408 // not parser-created and don't need to be flushing stuff under us 409 // to get our kids right. 410 Document* doc = mRootNode->GetUncomposedDoc(); 411 mFlushesNeeded = doc && !doc->IsHTMLDocument(); 412 } 413 414 nsContentList::nsContentList(nsINode* aRootNode, nsContentListMatchFunc aFunc, 415 nsContentListDestroyFunc aDestroyFunc, void* aData, 416 bool aDeep, nsAtom* aMatchAtom, 417 int32_t aMatchNameSpaceId, 418 bool aFuncMayDependOnAttr, bool aLiveList) 419 : nsBaseContentList(), 420 mRootNode(aRootNode), 421 mMatchNameSpaceId(aMatchNameSpaceId), 422 mHTMLMatchAtom(aMatchAtom), 423 mXMLMatchAtom(aMatchAtom), 424 mFunc(aFunc), 425 mDestroyFunc(aDestroyFunc), 426 mData(aData), 427 mState(State::Dirty), 428 mMatchAll(false), 429 mDeep(aDeep), 430 mFuncMayDependOnAttr(aFuncMayDependOnAttr), 431 mIsHTMLDocument(false), 432 mNamedItemsCacheValid(false), 433 mIsLiveList(aLiveList), 434 mInHashtable(false) { 435 NS_ASSERTION(mRootNode, "Must have root"); 436 // This is aLiveList instead of mIsLiveList to avoid Valgrind errors. 437 if (aLiveList) { 438 SetEnabledCallbacks(nsIMutationObserver::kNodeWillBeDestroyed); 439 mRootNode->AddMutationObserver(this); 440 } 441 442 // We only need to flush if we're in an non-HTML document, since the 443 // HTML5 parser doesn't need flushing. Further, if we're not in a 444 // document at all right now (in the GetUncomposedDoc() sense), we're 445 // not parser-created and don't need to be flushing stuff under us 446 // to get our kids right. 447 Document* doc = mRootNode->GetUncomposedDoc(); 448 mFlushesNeeded = doc && !doc->IsHTMLDocument(); 449 } 450 451 nsContentList::~nsContentList() { 452 RemoveFromHashtable(); 453 if (mIsLiveList && mRootNode) { 454 mRootNode->RemoveMutationObserver(this); 455 } 456 457 if (mDestroyFunc) { 458 // Clean up mData 459 (*mDestroyFunc)(mData); 460 } 461 } 462 463 JSObject* nsContentList::WrapObject(JSContext* cx, 464 JS::Handle<JSObject*> aGivenProto) { 465 return HTMLCollection_Binding::Wrap(cx, this, aGivenProto); 466 } 467 468 NS_IMPL_ISUPPORTS_INHERITED(nsContentList, nsBaseContentList, nsIHTMLCollection, 469 nsIMutationObserver) 470 471 uint32_t nsContentList::Length(bool aDoFlush) { 472 BringSelfUpToDate(aDoFlush); 473 474 return mElements.Length(); 475 } 476 477 nsIContent* nsContentList::Item(uint32_t aIndex, bool aDoFlush) { 478 if (mRootNode && aDoFlush && mFlushesNeeded) { 479 // XXX sXBL/XBL2 issue 480 Document* doc = mRootNode->GetUncomposedDoc(); 481 if (doc) { 482 // Flush pending content changes Bug 4891. 483 doc->FlushPendingNotifications(FlushType::ContentAndNotify); 484 } 485 } 486 487 if (mState != State::UpToDate) { 488 PopulateSelf(std::min(aIndex, UINT32_MAX - 1) + 1); 489 } 490 491 ASSERT_IN_SYNC; 492 NS_ASSERTION(!mRootNode || mState != State::Dirty, 493 "PopulateSelf left the list in a dirty (useless) state!"); 494 495 return mElements.SafeElementAt(aIndex); 496 } 497 498 inline void nsContentList::InsertElementInNamedItemsCache( 499 nsIContent& aContent) { 500 const bool hasName = aContent.HasName(); 501 const bool hasId = aContent.HasID(); 502 if (!hasName && !hasId) { 503 return; 504 } 505 506 Element* el = aContent.AsElement(); 507 MOZ_ASSERT_IF(hasName, el->IsHTMLElement()); 508 509 uint32_t i = 0; 510 while (BorrowedAttrInfo info = el->GetAttrInfoAt(i++)) { 511 const bool valid = (info.mName->Equals(nsGkAtoms::name) && hasName) || 512 (info.mName->Equals(nsGkAtoms::id) && hasId); 513 if (!valid) { 514 continue; 515 } 516 517 if (!mNamedItemsCache) { 518 mNamedItemsCache = MakeUnique<NamedItemsCache>(); 519 } 520 521 nsAtom* name = info.mValue->GetAtomValue(); 522 // NOTE: LookupOrInsert makes sure we keep the first element we find for a 523 // given name. 524 mNamedItemsCache->LookupOrInsert(name, el); 525 } 526 } 527 528 inline void nsContentList::InvalidateNamedItemsCacheForAttributeChange( 529 int32_t aNamespaceID, nsAtom* aAttribute) { 530 if (!mNamedItemsCacheValid) { 531 return; 532 } 533 if ((aAttribute == nsGkAtoms::id || aAttribute == nsGkAtoms::name) && 534 aNamespaceID == kNameSpaceID_None) { 535 InvalidateNamedItemsCache(); 536 } 537 } 538 539 inline void nsContentList::InvalidateNamedItemsCacheForInsertion( 540 Element& aElement) { 541 if (!mNamedItemsCacheValid) { 542 return; 543 } 544 545 InsertElementInNamedItemsCache(aElement); 546 } 547 548 inline void nsContentList::InvalidateNamedItemsCacheForDeletion( 549 Element& aElement) { 550 if (!mNamedItemsCacheValid) { 551 return; 552 } 553 if (aElement.HasName() || aElement.HasID()) { 554 InvalidateNamedItemsCache(); 555 } 556 } 557 558 void nsContentList::EnsureNamedItemsCacheValid(bool aDoFlush) { 559 BringSelfUpToDate(aDoFlush); 560 561 if (mNamedItemsCacheValid) { 562 return; 563 } 564 565 MOZ_ASSERT(!mNamedItemsCache); 566 567 // https://dom.spec.whatwg.org/#dom-htmlcollection-nameditem-key 568 // XXX: Blink/WebKit don't follow the spec here, and searches first-by-id, 569 // then by name. 570 for (const nsCOMPtr<nsIContent>& content : mElements) { 571 InsertElementInNamedItemsCache(*content); 572 } 573 574 mNamedItemsCacheValid = true; 575 } 576 577 Element* nsContentList::NamedItem(const nsAString& aName, bool aDoFlush) { 578 if (aName.IsEmpty()) { 579 return nullptr; 580 } 581 582 EnsureNamedItemsCacheValid(aDoFlush); 583 584 if (!mNamedItemsCache) { 585 return nullptr; 586 } 587 588 // Typically IDs and names are atomized 589 RefPtr<nsAtom> name = NS_Atomize(aName); 590 NS_ENSURE_TRUE(name, nullptr); 591 592 return mNamedItemsCache->Get(name); 593 } 594 595 void nsContentList::GetSupportedNames(nsTArray<nsString>& aNames) { 596 BringSelfUpToDate(true); 597 598 AutoTArray<nsAtom*, 8> atoms; 599 for (uint32_t i = 0; i < mElements.Length(); ++i) { 600 nsIContent* content = mElements.ElementAt(i); 601 if (content->HasID()) { 602 nsAtom* id = content->GetID(); 603 MOZ_ASSERT(id != nsGkAtoms::_empty, "Empty ids don't get atomized"); 604 if (!atoms.Contains(id)) { 605 atoms.AppendElement(id); 606 } 607 } 608 609 nsGenericHTMLElement* el = nsGenericHTMLElement::FromNode(content); 610 if (el) { 611 // XXXbz should we be checking for particular tags here? How 612 // stable is this part of the spec? 613 // Note: nsINode::HasName means the name is exposed on the document, 614 // which is false for options, so we don't check it here. 615 const nsAttrValue* val = el->GetParsedAttr(nsGkAtoms::name); 616 if (val && val->Type() == nsAttrValue::eAtom) { 617 nsAtom* name = val->GetAtomValue(); 618 MOZ_ASSERT(name != nsGkAtoms::_empty, "Empty names don't get atomized"); 619 if (!atoms.Contains(name)) { 620 atoms.AppendElement(name); 621 } 622 } 623 } 624 } 625 626 uint32_t atomsLen = atoms.Length(); 627 nsString* names = aNames.AppendElements(atomsLen); 628 for (uint32_t i = 0; i < atomsLen; ++i) { 629 atoms[i]->ToString(names[i]); 630 } 631 } 632 633 int32_t nsContentList::IndexOf(nsIContent* aContent, bool aDoFlush) { 634 BringSelfUpToDate(aDoFlush); 635 636 return mElements.IndexOf(aContent); 637 } 638 639 int32_t nsContentList::IndexOf(nsIContent* aContent) { 640 return IndexOf(aContent, true); 641 } 642 643 void nsContentList::NodeWillBeDestroyed(nsINode* aNode) { 644 // We shouldn't do anything useful from now on 645 646 RemoveFromCaches(); 647 mRootNode = nullptr; 648 649 // We will get no more updates, so we can never know we're up to 650 // date 651 SetDirty(); 652 } 653 654 void nsContentList::LastRelease() { 655 RemoveFromCaches(); 656 if (mIsLiveList && mRootNode) { 657 mRootNode->RemoveMutationObserver(this); 658 mRootNode = nullptr; 659 } 660 SetDirty(); 661 } 662 663 Element* nsContentList::GetElementAt(uint32_t aIndex) { 664 return static_cast<Element*>(Item(aIndex, true)); 665 } 666 667 nsIContent* nsContentList::Item(uint32_t aIndex) { 668 return GetElementAt(aIndex); 669 } 670 671 void nsContentList::AttributeChanged(Element* aElement, int32_t aNameSpaceID, 672 nsAtom* aAttribute, AttrModType, 673 const nsAttrValue* aOldValue) { 674 MOZ_ASSERT(aElement, "Must have a content node to work with"); 675 676 if (mState == State::Dirty || 677 !MayContainRelevantNodes(aElement->GetParentNode()) || 678 !nsContentUtils::IsInSameAnonymousTree(mRootNode, aElement)) { 679 // Either we're already dirty or aElement will never match us. 680 return; 681 } 682 683 InvalidateNamedItemsCacheForAttributeChange(aNameSpaceID, aAttribute); 684 685 if (!mFunc || !mFuncMayDependOnAttr) { 686 // aElement might be relevant but the attribute change doesn't affect 687 // whether we match it. 688 return; 689 } 690 691 if (Match(aElement)) { 692 if (mElements.IndexOf(aElement) == mElements.NoIndex) { 693 // We match aElement now, and it's not in our list already. Just dirty 694 // ourselves; this is simpler than trying to figure out where to insert 695 // aElement. 696 SetDirty(); 697 } 698 } else { 699 // We no longer match aElement. Remove it from our list. If it's 700 // already not there, this is a no-op (though a potentially 701 // expensive one). Either way, no change of mState is required 702 // here. 703 if (mElements.RemoveElement(aElement)) { 704 InvalidateNamedItemsCacheForDeletion(*aElement); 705 } 706 } 707 } 708 709 void nsContentList::ContentAppended(nsIContent* aFirstNewContent, 710 const ContentAppendInfo&) { 711 nsIContent* container = aFirstNewContent->GetParent(); 712 MOZ_ASSERT(container, "Can't get at the new content if no container!"); 713 714 /* 715 * If the state is State::Dirty then we have no useful information in our list 716 * and we want to put off doing work as much as possible. 717 * 718 * Also, if container is anonymous from our point of view, we know that we 719 * can't possibly be matching any of the kids. 720 * 721 * Optimize out also the common case when just one new node is appended and 722 * it doesn't match us. 723 */ 724 if (mState == State::Dirty || 725 !nsContentUtils::IsInSameAnonymousTree(mRootNode, container) || 726 !MayContainRelevantNodes(container) || 727 (!aFirstNewContent->HasChildren() && 728 !aFirstNewContent->GetNextSibling() && !MatchSelf(aFirstNewContent))) { 729 MaybeMarkDirty(); 730 return; 731 } 732 733 /* 734 * We want to handle the case of ContentAppended by sometimes 735 * appending the content to our list, not just setting state to 736 * State::Dirty, since most of our ContentAppended notifications 737 * should come during pageload and be at the end of the document. 738 * Do a bit of work to see whether we could just append to what we 739 * already have. 740 */ 741 742 uint32_t ourCount = mElements.Length(); 743 const bool appendingToList = [&] { 744 if (ourCount == 0) { 745 return true; 746 } 747 if (mRootNode == container) { 748 return true; 749 } 750 return nsContentUtils::PositionIsBefore(mElements.LastElement(), 751 aFirstNewContent); 752 }(); 753 754 if (!appendingToList) { 755 // The new stuff is somewhere in the middle of our list; check 756 // whether we need to invalidate 757 for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) { 758 if (MatchSelf(cur)) { 759 // Uh-oh. We're gonna have to add elements into the middle 760 // of our list. That's not worth the effort. 761 SetDirty(); 762 break; 763 } 764 } 765 766 ASSERT_IN_SYNC; 767 return; 768 } 769 770 /* 771 * At this point we know we could append. If we're not up to 772 * date, however, that would be a bad idea -- it could miss some 773 * content that we never picked up due to being lazy. Further, we 774 * may never get asked for this content... so don't grab it yet. 775 */ 776 if (mState == State::Lazy) { 777 return; 778 } 779 780 /* 781 * We're up to date. That means someone's actively using us; we 782 * may as well grab this content.... 783 */ 784 if (mDeep) { 785 for (nsIContent* cur = aFirstNewContent; cur; 786 cur = cur->GetNextNode(container)) { 787 if (cur->IsElement() && Match(cur->AsElement())) { 788 mElements.AppendElement(cur); 789 InvalidateNamedItemsCacheForInsertion(*cur->AsElement()); 790 } 791 } 792 } else { 793 for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) { 794 if (cur->IsElement() && Match(cur->AsElement())) { 795 mElements.AppendElement(cur); 796 InvalidateNamedItemsCacheForInsertion(*cur->AsElement()); 797 } 798 } 799 } 800 801 ASSERT_IN_SYNC; 802 } 803 804 void nsContentList::ContentInserted(nsIContent* aChild, 805 const ContentInsertInfo&) { 806 // Note that aChild->GetParentNode() can be null here if we are inserting into 807 // the document itself; any attempted optimizations to this method should deal 808 // with that. 809 if (mState != State::Dirty && 810 MayContainRelevantNodes(aChild->GetParentNode()) && 811 nsContentUtils::IsInSameAnonymousTree(mRootNode, aChild) && 812 MatchSelf(aChild)) { 813 SetDirty(); 814 } 815 816 ASSERT_IN_SYNC; 817 } 818 819 void nsContentList::ContentWillBeRemoved(nsIContent* aChild, 820 const ContentRemoveInfo&) { 821 if (mState != State::Dirty && 822 MayContainRelevantNodes(aChild->GetParentNode()) && 823 nsContentUtils::IsInSameAnonymousTree(mRootNode, aChild) && 824 MatchSelf(aChild)) { 825 SetDirty(); 826 } 827 828 ASSERT_IN_SYNC; 829 } 830 831 bool nsContentList::Match(Element* aElement) { 832 if (mFunc) { 833 return (*mFunc)(aElement, mMatchNameSpaceId, mXMLMatchAtom, mData); 834 } 835 836 if (!mXMLMatchAtom) return false; 837 838 NodeInfo* ni = aElement->NodeInfo(); 839 840 bool unknown = mMatchNameSpaceId == kNameSpaceID_Unknown; 841 bool wildcard = mMatchNameSpaceId == kNameSpaceID_Wildcard; 842 bool toReturn = mMatchAll; 843 if (!unknown && !wildcard) toReturn &= ni->NamespaceEquals(mMatchNameSpaceId); 844 845 if (toReturn) return toReturn; 846 847 bool matchHTML = 848 mIsHTMLDocument && aElement->GetNameSpaceID() == kNameSpaceID_XHTML; 849 850 if (unknown) { 851 return matchHTML ? ni->QualifiedNameEquals(mHTMLMatchAtom) 852 : ni->QualifiedNameEquals(mXMLMatchAtom); 853 } 854 855 if (wildcard) { 856 return matchHTML ? ni->Equals(mHTMLMatchAtom) : ni->Equals(mXMLMatchAtom); 857 } 858 859 return matchHTML ? ni->Equals(mHTMLMatchAtom, mMatchNameSpaceId) 860 : ni->Equals(mXMLMatchAtom, mMatchNameSpaceId); 861 } 862 863 bool nsContentList::MatchSelf(nsIContent* aContent) { 864 MOZ_ASSERT(aContent, "Can't match null stuff, you know"); 865 MOZ_ASSERT(mDeep || aContent->GetParentNode() == mRootNode, 866 "MatchSelf called on a node that we can't possibly match"); 867 868 if (!aContent->IsElement()) { 869 return false; 870 } 871 872 if (Match(aContent->AsElement())) return true; 873 874 if (!mDeep) return false; 875 876 for (nsIContent* cur = aContent->GetFirstChild(); cur; 877 cur = cur->GetNextNode(aContent)) { 878 if (cur->IsElement() && Match(cur->AsElement())) { 879 return true; 880 } 881 } 882 883 return false; 884 } 885 886 void nsContentList::PopulateSelf(uint32_t aNeededLength, 887 uint32_t aExpectedElementsIfDirty) { 888 if (!mRootNode) { 889 return; 890 } 891 892 ASSERT_IN_SYNC; 893 894 uint32_t count = mElements.Length(); 895 NS_ASSERTION(mState != State::Dirty || count == aExpectedElementsIfDirty, 896 "Reset() not called when setting state to State::Dirty?"); 897 898 if (count >= aNeededLength) // We're all set 899 return; 900 901 uint32_t elementsToAppend = aNeededLength - count; 902 #ifdef DEBUG 903 uint32_t invariant = elementsToAppend + mElements.Length(); 904 #endif 905 906 if (mDeep) { 907 // If we already have nodes start searching at the last one, otherwise 908 // start searching at the root. 909 nsINode* cur = count ? mElements[count - 1].get() : mRootNode; 910 do { 911 cur = cur->GetNextNode(mRootNode); 912 if (!cur) { 913 break; 914 } 915 if (cur->IsElement() && Match(cur->AsElement())) { 916 // Append AsElement() to get nsIContent instead of nsINode 917 mElements.AppendElement(cur->AsElement()); 918 --elementsToAppend; 919 } 920 } while (elementsToAppend); 921 } else { 922 nsIContent* cur = count ? mElements[count - 1]->GetNextSibling() 923 : mRootNode->GetFirstChild(); 924 for (; cur && elementsToAppend; cur = cur->GetNextSibling()) { 925 if (cur->IsElement() && Match(cur->AsElement())) { 926 mElements.AppendElement(cur); 927 --elementsToAppend; 928 } 929 } 930 } 931 932 NS_ASSERTION(elementsToAppend + mElements.Length() == invariant, 933 "Something is awry!"); 934 935 if (elementsToAppend != 0) { 936 mState = State::UpToDate; 937 } else { 938 mState = State::Lazy; 939 } 940 941 SetEnabledCallbacks(nsIMutationObserver::kAll); 942 943 ASSERT_IN_SYNC; 944 } 945 946 void nsContentList::RemoveFromHashtable() { 947 if (mFunc) { 948 // nsCacheableFuncStringContentList can be in a hash table without being 949 // in gContentListHashTable, but it will have been removed from the hash 950 // table in its dtor before it runs the nsContentList dtor. 951 MOZ_RELEASE_ASSERT(!mInHashtable); 952 953 // This can't be in gContentListHashTable. 954 return; 955 } 956 957 nsDependentAtomString str(mXMLMatchAtom); 958 nsContentListKey key(mRootNode, mMatchNameSpaceId, str, mIsHTMLDocument); 959 sRecentlyUsedContentLists.Remove(key); 960 961 if (gContentListHashTable) { 962 gContentListHashTable->RemoveEntry(&key); 963 964 if (gContentListHashTable->Count() == 0) { 965 gContentListHashTable = nullptr; 966 } 967 } 968 969 MOZ_RELEASE_ASSERT(!mInHashtable); 970 } 971 972 void nsContentList::BringSelfUpToDate(bool aDoFlush) { 973 if (mFlushesNeeded && mRootNode && aDoFlush) { 974 // XXX sXBL/XBL2 issue 975 if (Document* doc = mRootNode->GetUncomposedDoc()) { 976 // Flush pending content changes Bug 4891. 977 doc->FlushPendingNotifications(FlushType::ContentAndNotify); 978 } 979 } 980 981 if (mState != State::UpToDate) { 982 PopulateSelf(uint32_t(-1)); 983 } 984 985 mMissedUpdates = 0; 986 987 ASSERT_IN_SYNC; 988 NS_ASSERTION(!mRootNode || mState == State::UpToDate, 989 "PopulateSelf dod not bring content list up to date!"); 990 } 991 992 nsCacheableFuncStringContentList::~nsCacheableFuncStringContentList() { 993 RemoveFromFuncStringHashtable(); 994 } 995 996 void nsCacheableFuncStringContentList::RemoveFromFuncStringHashtable() { 997 if (!gFuncStringContentListHashTable) { 998 MOZ_RELEASE_ASSERT(!mInHashtable); 999 return; 1000 } 1001 1002 nsFuncStringCacheKey key(mRootNode, mFunc, mString); 1003 gFuncStringContentListHashTable->RemoveEntry(&key); 1004 1005 if (gFuncStringContentListHashTable->Count() == 0) { 1006 gFuncStringContentListHashTable = nullptr; 1007 } 1008 1009 MOZ_RELEASE_ASSERT(!mInHashtable); 1010 } 1011 1012 #ifdef DEBUG_CONTENT_LIST 1013 void nsContentList::AssertInSync() { 1014 if (mState == State::Dirty) { 1015 return; 1016 } 1017 1018 if (!mRootNode) { 1019 NS_ASSERTION(mElements.Length() == 0 && mState == State::Dirty, 1020 "Empty iterator isn't quite empty?"); 1021 return; 1022 } 1023 1024 // XXX This code will need to change if nsContentLists can ever match 1025 // elements that are outside of the document element. 1026 nsIContent* root = mRootNode->IsDocument() 1027 ? mRootNode->AsDocument()->GetRootElement() 1028 : mRootNode->AsContent(); 1029 1030 PreContentIterator preOrderIter; 1031 if (mDeep) { 1032 preOrderIter.Init(root); 1033 preOrderIter.First(); 1034 } 1035 1036 uint32_t cnt = 0, index = 0; 1037 while (true) { 1038 if (cnt == mElements.Length() && mState == State::Lazy) { 1039 break; 1040 } 1041 1042 nsIContent* cur = 1043 mDeep ? preOrderIter.GetCurrentNode() : mRootNode->GetChildAt(index++); 1044 if (!cur) { 1045 break; 1046 } 1047 1048 if (cur->IsElement() && Match(cur->AsElement())) { 1049 NS_ASSERTION(cnt < mElements.Length() && mElements[cnt] == cur, 1050 "Elements is out of sync"); 1051 ++cnt; 1052 } 1053 1054 if (mDeep) { 1055 preOrderIter.Next(); 1056 } 1057 } 1058 1059 NS_ASSERTION(cnt == mElements.Length(), "Too few elements"); 1060 } 1061 #endif 1062 1063 //----------------------------------------------------- 1064 // nsCachableElementsByNameNodeList 1065 1066 JSObject* nsCachableElementsByNameNodeList::WrapObject( 1067 JSContext* cx, JS::Handle<JSObject*> aGivenProto) { 1068 return NodeList_Binding::Wrap(cx, this, aGivenProto); 1069 } 1070 1071 void nsCachableElementsByNameNodeList::AttributeChanged( 1072 Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute, 1073 AttrModType aModType, const nsAttrValue* aOldValue) { 1074 // No need to rebuild the list if the changed attribute is not the name 1075 // attribute. 1076 if (aAttribute != nsGkAtoms::name) { 1077 InvalidateNamedItemsCacheForAttributeChange(aNameSpaceID, aAttribute); 1078 return; 1079 } 1080 1081 nsCacheableFuncStringContentList::AttributeChanged( 1082 aElement, aNameSpaceID, aAttribute, aModType, aOldValue); 1083 } 1084 1085 //----------------------------------------------------- 1086 // nsCacheableFuncStringHTMLCollection 1087 1088 JSObject* nsCacheableFuncStringHTMLCollection::WrapObject( 1089 JSContext* cx, JS::Handle<JSObject*> aGivenProto) { 1090 return HTMLCollection_Binding::Wrap(cx, this, aGivenProto); 1091 } 1092 1093 //----------------------------------------------------- 1094 // nsLabelsNodeList 1095 1096 JSObject* nsLabelsNodeList::WrapObject(JSContext* cx, 1097 JS::Handle<JSObject*> aGivenProto) { 1098 return NodeList_Binding::Wrap(cx, this, aGivenProto); 1099 } 1100 1101 void nsLabelsNodeList::AttributeChanged(Element* aElement, int32_t aNameSpaceID, 1102 nsAtom* aAttribute, AttrModType, 1103 const nsAttrValue* aOldValue) { 1104 MOZ_ASSERT(aElement, "Must have a content node to work with"); 1105 if (mState == State::Dirty || 1106 !nsContentUtils::IsInSameAnonymousTree(mRootNode, aElement)) { 1107 return; 1108 } 1109 1110 InvalidateNamedItemsCacheForAttributeChange(aNameSpaceID, aAttribute); 1111 1112 // We need to handle input type changes to or from "hidden". 1113 if (aElement->IsHTMLElement(nsGkAtoms::input) && 1114 aAttribute == nsGkAtoms::type && aNameSpaceID == kNameSpaceID_None) { 1115 SetDirty(); 1116 return; 1117 } 1118 } 1119 1120 void nsLabelsNodeList::ContentAppended(nsIContent* aFirstNewContent, 1121 const ContentAppendInfo&) { 1122 nsIContent* container = aFirstNewContent->GetParent(); 1123 // If a labelable element is moved to outside or inside of 1124 // nested associated labels, we're gonna have to modify 1125 // the content list. 1126 if (mState != State::Dirty && 1127 nsContentUtils::IsInSameAnonymousTree(mRootNode, container)) { 1128 SetDirty(); 1129 return; 1130 } 1131 } 1132 1133 void nsLabelsNodeList::ContentInserted(nsIContent* aChild, 1134 const ContentInsertInfo&) { 1135 // If a labelable element is moved to outside or inside of 1136 // nested associated labels, we're gonna have to modify 1137 // the content list. 1138 if (mState != State::Dirty && 1139 nsContentUtils::IsInSameAnonymousTree(mRootNode, aChild)) { 1140 SetDirty(); 1141 return; 1142 } 1143 } 1144 1145 void nsLabelsNodeList::ContentWillBeRemoved(nsIContent* aChild, 1146 const ContentRemoveInfo&) { 1147 // If a labelable element is removed, we're gonna have to clean 1148 // the content list. 1149 if (mState != State::Dirty && 1150 nsContentUtils::IsInSameAnonymousTree(mRootNode, aChild)) { 1151 SetDirty(); 1152 return; 1153 } 1154 } 1155 1156 void nsLabelsNodeList::MaybeResetRoot(nsINode* aRootNode) { 1157 MOZ_ASSERT(aRootNode, "Must have root"); 1158 if (mRootNode == aRootNode) { 1159 return; 1160 } 1161 1162 MOZ_ASSERT(mIsLiveList, "nsLabelsNodeList is always a live list"); 1163 if (mRootNode) { 1164 mRootNode->RemoveMutationObserver(this); 1165 } 1166 mRootNode = aRootNode; 1167 mRootNode->AddMutationObserver(this); 1168 SetDirty(); 1169 } 1170 1171 void nsLabelsNodeList::PopulateSelf(uint32_t aNeededLength, 1172 uint32_t aExpectedElementsIfDirty) { 1173 if (!mRootNode) { 1174 return; 1175 } 1176 1177 // Start searching at the root. 1178 nsINode* cur = mRootNode; 1179 if (mElements.IsEmpty() && cur->IsElement() && Match(cur->AsElement())) { 1180 mElements.AppendElement(cur->AsElement()); 1181 ++aExpectedElementsIfDirty; 1182 } 1183 1184 nsContentList::PopulateSelf(aNeededLength, aExpectedElementsIfDirty); 1185 }