HTMLAllCollection.cpp (6185B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/dom/HTMLAllCollection.h" 8 9 #include "jsfriendapi.h" 10 #include "mozilla/dom/Element.h" 11 #include "mozilla/dom/HTMLAllCollectionBinding.h" 12 #include "mozilla/dom/Nullable.h" 13 #include "nsContentList.h" 14 #include "nsGenericHTMLElement.h" 15 16 namespace mozilla::dom { 17 18 HTMLAllCollection::HTMLAllCollection(mozilla::dom::Document* aDocument) 19 : mDocument(aDocument) { 20 MOZ_ASSERT(mDocument); 21 } 22 23 HTMLAllCollection::~HTMLAllCollection() = default; 24 25 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(HTMLAllCollection, mDocument, mCollection, 26 mNamedMap) 27 28 NS_IMPL_CYCLE_COLLECTING_ADDREF(HTMLAllCollection) 29 NS_IMPL_CYCLE_COLLECTING_RELEASE(HTMLAllCollection) 30 31 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(HTMLAllCollection) 32 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 33 NS_INTERFACE_MAP_ENTRY(nsISupports) 34 NS_INTERFACE_MAP_END 35 36 nsINode* HTMLAllCollection::GetParentObject() const { return mDocument; } 37 38 uint32_t HTMLAllCollection::Length() { return Collection()->Length(true); } 39 40 Element* HTMLAllCollection::Item(uint32_t aIndex) { 41 nsIContent* item = Collection()->Item(aIndex); 42 return item ? item->AsElement() : nullptr; 43 } 44 45 void HTMLAllCollection::Item(const Optional<nsAString>& aNameOrIndex, 46 Nullable<OwningHTMLCollectionOrElement>& aResult) { 47 if (!aNameOrIndex.WasPassed()) { 48 aResult.SetNull(); 49 return; 50 } 51 52 const nsAString& nameOrIndex = aNameOrIndex.Value(); 53 uint32_t indexVal; 54 if (js::StringIsArrayIndex(nameOrIndex.BeginReading(), nameOrIndex.Length(), 55 &indexVal)) { 56 Element* element = Item(indexVal); 57 if (element) { 58 aResult.SetValue().SetAsElement() = element; 59 } else { 60 aResult.SetNull(); 61 } 62 return; 63 } 64 65 NamedItem(nameOrIndex, aResult); 66 } 67 68 nsContentList* HTMLAllCollection::Collection() { 69 if (!mCollection) { 70 Document* document = mDocument; 71 mCollection = document->GetElementsByTagName(u"*"_ns); 72 MOZ_ASSERT(mCollection); 73 } 74 return mCollection; 75 } 76 77 static bool IsAllNamedElement(nsIContent* aContent) { 78 return aContent->IsAnyOfHTMLElements( 79 nsGkAtoms::a, nsGkAtoms::button, nsGkAtoms::embed, nsGkAtoms::form, 80 nsGkAtoms::iframe, nsGkAtoms::img, nsGkAtoms::input, nsGkAtoms::map, 81 nsGkAtoms::meta, nsGkAtoms::object, nsGkAtoms::select, 82 nsGkAtoms::textarea, nsGkAtoms::frame, nsGkAtoms::frameset); 83 } 84 85 static bool DocAllResultMatch(Element* aElement, int32_t aNamespaceID, 86 nsAtom* aAtom, void* aData) { 87 if (aElement->GetID() == aAtom) { 88 return true; 89 } 90 91 nsGenericHTMLElement* elm = nsGenericHTMLElement::FromNode(aElement); 92 if (!elm) { 93 return false; 94 } 95 96 if (!IsAllNamedElement(elm)) { 97 return false; 98 } 99 100 const nsAttrValue* val = elm->GetParsedAttr(nsGkAtoms::name); 101 return val && val->Type() == nsAttrValue::eAtom && 102 val->GetAtomValue() == aAtom; 103 } 104 105 nsContentList* HTMLAllCollection::GetDocumentAllList(const nsAString& aID) { 106 return mNamedMap 107 .LookupOrInsertWith(aID, 108 [this, &aID] { 109 RefPtr<nsAtom> id = NS_Atomize(aID); 110 return new nsContentList(mDocument, 111 DocAllResultMatch, nullptr, 112 nullptr, true, id); 113 }) 114 .get(); 115 } 116 117 void HTMLAllCollection::NamedGetter( 118 const nsAString& aID, bool& aFound, 119 Nullable<OwningHTMLCollectionOrElement>& aResult) { 120 if (aID.IsEmpty()) { 121 aFound = false; 122 aResult.SetNull(); 123 return; 124 } 125 126 nsContentList* docAllList = GetDocumentAllList(aID); 127 if (!docAllList) { 128 aFound = false; 129 aResult.SetNull(); 130 return; 131 } 132 133 // Check if there are more than 1 entries. Do this by getting the second one 134 // rather than the length since getting the length always requires walking 135 // the entire document. 136 if (docAllList->Item(1, true)) { 137 aFound = true; 138 aResult.SetValue().SetAsHTMLCollection() = docAllList; 139 return; 140 } 141 142 // There's only 0 or 1 items. Return the first one or null. 143 if (nsIContent* node = docAllList->Item(0, true)) { 144 aFound = true; 145 aResult.SetValue().SetAsElement() = node->AsElement(); 146 return; 147 } 148 149 aFound = false; 150 aResult.SetNull(); 151 } 152 153 void HTMLAllCollection::GetSupportedNames(nsTArray<nsString>& aNames) { 154 // XXXbz this is very similar to nsContentList::GetSupportedNames, 155 // but has to check IsAllNamedElement for the name case. 156 AutoTArray<nsAtom*, 8> atoms; 157 for (uint32_t i = 0; i < Length(); ++i) { 158 nsIContent* content = Item(i); 159 if (content->HasID()) { 160 nsAtom* id = content->GetID(); 161 MOZ_ASSERT(id != nsGkAtoms::_empty, "Empty ids don't get atomized"); 162 if (!atoms.Contains(id)) { 163 atoms.AppendElement(id); 164 } 165 } 166 167 nsGenericHTMLElement* el = nsGenericHTMLElement::FromNode(content); 168 if (el) { 169 // Note: nsINode::HasName means the name is exposed on the document, 170 // which is false for options, so we don't check it here. 171 const nsAttrValue* val = el->GetParsedAttr(nsGkAtoms::name); 172 if (val && val->Type() == nsAttrValue::eAtom && 173 IsAllNamedElement(content)) { 174 nsAtom* name = val->GetAtomValue(); 175 MOZ_ASSERT(name != nsGkAtoms::_empty, "Empty names don't get atomized"); 176 if (!atoms.Contains(name)) { 177 atoms.AppendElement(name); 178 } 179 } 180 } 181 } 182 183 uint32_t atomsLen = atoms.Length(); 184 nsString* names = aNames.AppendElements(atomsLen); 185 for (uint32_t i = 0; i < atomsLen; ++i) { 186 atoms[i]->ToString(names[i]); 187 } 188 } 189 190 JSObject* HTMLAllCollection::WrapObject(JSContext* aCx, 191 JS::Handle<JSObject*> aGivenProto) { 192 return HTMLAllCollection_Binding::Wrap(aCx, this, aGivenProto); 193 } 194 195 } // namespace mozilla::dom