nsDOMAttributeMap.cpp (13014B)
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 * Implementation of the |attributes| property of DOM Core's Element object. 9 */ 10 11 #include "nsDOMAttributeMap.h" 12 13 #include "mozilla/MemoryReporting.h" 14 #include "mozilla/dom/Attr.h" 15 #include "mozilla/dom/Document.h" 16 #include "mozilla/dom/Element.h" 17 #include "mozilla/dom/NamedNodeMapBinding.h" 18 #include "mozilla/dom/NodeInfoInlines.h" 19 #include "mozilla/dom/TrustedTypeUtils.h" 20 #include "mozilla/dom/TrustedTypesConstants.h" 21 #include "nsAttrName.h" 22 #include "nsContentUtils.h" 23 #include "nsError.h" 24 #include "nsIContentInlines.h" 25 #include "nsNameSpaceManager.h" 26 #include "nsNodeInfoManager.h" 27 #include "nsUnicharUtils.h" 28 #include "nsWrapperCacheInlines.h" 29 30 using namespace mozilla; 31 using namespace mozilla::dom; 32 33 //---------------------------------------------------------------------- 34 35 nsDOMAttributeMap::nsDOMAttributeMap(Element* aContent) : mContent(aContent) { 36 // We don't add a reference to our content. If it goes away, 37 // we'll be told to drop our reference 38 } 39 40 nsDOMAttributeMap::~nsDOMAttributeMap() { DropReference(); } 41 42 void nsDOMAttributeMap::DropReference() { 43 for (auto iter = mAttributeCache.Iter(); !iter.Done(); iter.Next()) { 44 iter.Data()->SetMap(nullptr); 45 iter.Remove(); 46 } 47 mContent = nullptr; 48 } 49 50 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(nsDOMAttributeMap) 51 52 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDOMAttributeMap) 53 tmp->DropReference(); 54 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER 55 NS_IMPL_CYCLE_COLLECTION_UNLINK(mContent) 56 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 57 58 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsDOMAttributeMap) 59 for (const auto& entry : tmp->mAttributeCache) { 60 cb.NoteXPCOMChild(static_cast<nsINode*>(entry.GetWeak())); 61 } 62 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContent) 63 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 64 65 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsDOMAttributeMap) 66 if (tmp->HasKnownLiveWrapper()) { 67 if (tmp->mContent) { 68 // The map owns the element so we can mark it when the 69 // map itself is certainly alive. 70 mozilla::dom::FragmentOrElement::MarkNodeChildren(tmp->mContent); 71 } 72 return true; 73 } 74 if (tmp->mContent && 75 mozilla::dom::FragmentOrElement::CanSkip(tmp->mContent, true)) { 76 return true; 77 } 78 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END 79 80 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsDOMAttributeMap) 81 return tmp->HasKnownLiveWrapperAndDoesNotNeedTracing(tmp); 82 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END 83 84 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsDOMAttributeMap) 85 return tmp->HasKnownLiveWrapper(); 86 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END 87 88 // QueryInterface implementation for nsDOMAttributeMap 89 90 NS_INTERFACE_MAP_BEGIN(nsDOMAttributeMap) 91 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 92 NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsDOMAttributeMap) 93 NS_INTERFACE_MAP_ENTRY(nsISupports) 94 NS_INTERFACE_MAP_END 95 96 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMAttributeMap) 97 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMAttributeMap) 98 99 nsresult nsDOMAttributeMap::SetOwnerDocument(Document* aDocument) { 100 for (const auto& entry : mAttributeCache.Values()) { 101 nsresult rv = entry->SetOwnerDocument(aDocument); 102 NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); 103 } 104 return NS_OK; 105 } 106 107 void nsDOMAttributeMap::DropAttribute(int32_t aNamespaceID, 108 nsAtom* aLocalName) { 109 nsAttrKey attr(aNamespaceID, aLocalName); 110 if (auto entry = mAttributeCache.Lookup(attr)) { 111 entry.Data()->SetMap(nullptr); // break link to map 112 entry.Remove(); 113 } 114 } 115 116 Attr* nsDOMAttributeMap::GetAttribute(mozilla::dom::NodeInfo* aNodeInfo) { 117 NS_ASSERTION(aNodeInfo, "GetAttribute() called with aNodeInfo == nullptr!"); 118 119 nsAttrKey attr(aNodeInfo->NamespaceID(), aNodeInfo->NameAtom()); 120 121 return mAttributeCache.LookupOrInsertWith(attr, [&] { 122 // Newly inserted entry! 123 RefPtr<mozilla::dom::NodeInfo> ni = aNodeInfo; 124 auto* nim = ni->NodeInfoManager(); 125 return new (nim) Attr(this, ni.forget(), u""_ns); 126 }); 127 } 128 129 Attr* nsDOMAttributeMap::NamedGetter(const nsAString& aAttrName, bool& aFound) { 130 aFound = false; 131 NS_ENSURE_TRUE(mContent, nullptr); 132 133 RefPtr<mozilla::dom::NodeInfo> ni = 134 mContent->GetExistingAttrNameFromQName(aAttrName); 135 if (!ni) { 136 return nullptr; 137 } 138 139 aFound = true; 140 return GetAttribute(ni); 141 } 142 143 void nsDOMAttributeMap::GetSupportedNames(nsTArray<nsString>& aNames) { 144 // For HTML elements in HTML documents, only include names that are still the 145 // same after ASCII-lowercasing, since our named getter will end up 146 // ASCII-lowercasing the given string. 147 bool lowercaseNamesOnly = 148 mContent->IsHTMLElement() && mContent->IsInHTMLDocument(); 149 150 const uint32_t count = mContent->GetAttrCount(); 151 bool seenNonAtomName = false; 152 for (uint32_t i = 0; i < count; i++) { 153 const nsAttrName* name = mContent->GetAttrNameAt(i); 154 seenNonAtomName = seenNonAtomName || !name->IsAtom(); 155 nsString qualifiedName; 156 name->GetQualifiedName(qualifiedName); 157 158 if (lowercaseNamesOnly && 159 nsContentUtils::StringContainsASCIIUpper(qualifiedName)) { 160 continue; 161 } 162 163 // Omit duplicates. We only need to do this check if we've seen a non-atom 164 // name, because that's the only way we can have two identical qualified 165 // names. 166 if (seenNonAtomName && aNames.Contains(qualifiedName)) { 167 continue; 168 } 169 170 aNames.AppendElement(qualifiedName); 171 } 172 } 173 174 Attr* nsDOMAttributeMap::GetNamedItem(const nsAString& aAttrName) { 175 bool dummy; 176 return NamedGetter(aAttrName, dummy); 177 } 178 179 already_AddRefed<Attr> nsDOMAttributeMap::SetNamedItemNS( 180 Attr& aAttr, nsIPrincipal* aSubjectPrincipal, ErrorResult& aError) { 181 NS_ENSURE_TRUE(mContent, nullptr); 182 183 nsAutoString value; 184 aAttr.GetValue(value); 185 186 RefPtr<NodeInfo> ni = aAttr.NodeInfo(); 187 188 Maybe<nsAutoString> compliantStringHolder; 189 RefPtr<nsAtom> nameAtom = ni->NameAtom(); 190 nsCOMPtr<Element> element = mContent; 191 const nsAString* compliantString = 192 TrustedTypeUtils::GetTrustedTypesCompliantAttributeValue( 193 *element, nameAtom, ni->NamespaceID(), value, aSubjectPrincipal, 194 compliantStringHolder, aError); 195 if (aError.Failed()) { 196 return nullptr; 197 } 198 199 // Check that attribute is not owned by somebody else 200 nsDOMAttributeMap* owner = aAttr.GetMap(); 201 if (owner) { 202 if (owner != this) { 203 aError.Throw(NS_ERROR_DOM_INUSE_ATTRIBUTE_ERR); 204 return nullptr; 205 } 206 207 // setting a preexisting attribute is a no-op, just return the same 208 // node. 209 RefPtr<Attr> attribute = &aAttr; 210 return attribute.forget(); 211 } 212 213 nsresult rv; 214 if (mContent->OwnerDoc() != aAttr.OwnerDoc()) { 215 DebugOnly<void*> adoptedNode = 216 mContent->OwnerDoc()->AdoptNode(aAttr, aError); 217 if (aError.Failed()) { 218 return nullptr; 219 } 220 221 NS_ASSERTION(adoptedNode == &aAttr, "Uh, adopt node changed nodes?"); 222 } 223 224 // Get nodeinfo and preexisting attribute (if it exists) 225 RefPtr<NodeInfo> oldNi; 226 227 uint32_t i, count = mContent->GetAttrCount(); 228 for (i = 0; i < count; ++i) { 229 const nsAttrName* name = mContent->GetAttrNameAt(i); 230 int32_t attrNS = name->NamespaceID(); 231 nsAtom* nameAtom = name->LocalName(); 232 233 // we're purposefully ignoring the prefix. 234 if (aAttr.NodeInfo()->Equals(nameAtom, attrNS)) { 235 oldNi = mContent->NodeInfo()->NodeInfoManager()->GetNodeInfo( 236 nameAtom, name->GetPrefix(), aAttr.NodeInfo()->NamespaceID(), 237 nsINode::ATTRIBUTE_NODE); 238 break; 239 } 240 } 241 242 RefPtr<Attr> oldAttr; 243 244 if (oldNi) { 245 oldAttr = GetAttribute(oldNi); 246 247 if (oldAttr == &aAttr) { 248 return oldAttr.forget(); 249 } 250 251 if (oldAttr) { 252 // Just remove it from our hashtable. This has no side-effects, so we 253 // don't have to recheck anything after we do it. Then we'll add our new 254 // Attr to the hashtable and do the actual attr set on the element. This 255 // will make the whole thing look like a single attribute mutation (with 256 // the new attr node in place) as opposed to a removal and addition. 257 DropAttribute(oldNi->NamespaceID(), oldNi->NameAtom()); 258 } 259 } 260 261 // Add the new attribute to the attribute map before updating 262 // its value in the element. @see bug 364413. 263 nsAttrKey attrkey(ni->NamespaceID(), ni->NameAtom()); 264 mAttributeCache.InsertOrUpdate(attrkey, RefPtr{&aAttr}); 265 aAttr.SetMap(this); 266 267 rv = mContent->SetAttr(ni->NamespaceID(), ni->NameAtom(), ni->GetPrefixAtom(), 268 *compliantString, true); 269 if (NS_FAILED(rv)) { 270 DropAttribute(ni->NamespaceID(), ni->NameAtom()); 271 aError.Throw(rv); 272 return nullptr; 273 } 274 275 return oldAttr.forget(); 276 } 277 278 already_AddRefed<Attr> nsDOMAttributeMap::RemoveNamedItem(NodeInfo* aNodeInfo, 279 ErrorResult& aError) { 280 RefPtr<Attr> attribute = GetAttribute(aNodeInfo); 281 // This removes the attribute node from the attribute map. 282 aError = mContent->UnsetAttr(aNodeInfo->NamespaceID(), aNodeInfo->NameAtom(), 283 true); 284 return attribute.forget(); 285 } 286 287 already_AddRefed<Attr> nsDOMAttributeMap::RemoveNamedItem( 288 const nsAString& aName, ErrorResult& aError) { 289 if (!mContent) { 290 aError.Throw(NS_ERROR_DOM_NOT_FOUND_ERR); 291 return nullptr; 292 } 293 294 RefPtr<mozilla::dom::NodeInfo> ni = 295 mContent->GetExistingAttrNameFromQName(aName); 296 if (!ni) { 297 aError.Throw(NS_ERROR_DOM_NOT_FOUND_ERR); 298 return nullptr; 299 } 300 301 return RemoveNamedItem(ni, aError); 302 } 303 304 Attr* nsDOMAttributeMap::IndexedGetter(uint32_t aIndex, bool& aFound) { 305 aFound = false; 306 NS_ENSURE_TRUE(mContent, nullptr); 307 308 const nsAttrName* name; 309 if (!mContent->GetAttrNameAt(aIndex, &name)) { 310 return nullptr; 311 } 312 313 aFound = true; 314 // Don't use the nodeinfo even if one exists since it can have the wrong 315 // owner document. 316 RefPtr<mozilla::dom::NodeInfo> ni = 317 mContent->NodeInfo()->NodeInfoManager()->GetNodeInfo( 318 name->LocalName(), name->GetPrefix(), name->NamespaceID(), 319 nsINode::ATTRIBUTE_NODE); 320 return GetAttribute(ni); 321 } 322 323 Attr* nsDOMAttributeMap::Item(uint32_t aIndex) { 324 bool dummy; 325 return IndexedGetter(aIndex, dummy); 326 } 327 328 uint32_t nsDOMAttributeMap::Length() const { 329 NS_ENSURE_TRUE(mContent, 0); 330 331 return mContent->GetAttrCount(); 332 } 333 334 Attr* nsDOMAttributeMap::GetNamedItemNS(const nsAString& aNamespaceURI, 335 const nsAString& aLocalName) { 336 RefPtr<mozilla::dom::NodeInfo> ni = 337 GetAttrNodeInfo(aNamespaceURI, aLocalName); 338 if (!ni) { 339 return nullptr; 340 } 341 342 return GetAttribute(ni); 343 } 344 345 already_AddRefed<mozilla::dom::NodeInfo> nsDOMAttributeMap::GetAttrNodeInfo( 346 const nsAString& aNamespaceURI, const nsAString& aLocalName) { 347 if (!mContent) { 348 return nullptr; 349 } 350 351 int32_t nameSpaceID = kNameSpaceID_None; 352 353 if (!aNamespaceURI.IsEmpty()) { 354 nameSpaceID = nsNameSpaceManager::GetInstance()->GetNameSpaceID( 355 aNamespaceURI, nsContentUtils::IsChromeDoc(mContent->OwnerDoc())); 356 357 if (nameSpaceID == kNameSpaceID_Unknown) { 358 return nullptr; 359 } 360 } 361 362 uint32_t i, count = mContent->GetAttrCount(); 363 for (i = 0; i < count; ++i) { 364 const nsAttrName* name = mContent->GetAttrNameAt(i); 365 int32_t attrNS = name->NamespaceID(); 366 nsAtom* nameAtom = name->LocalName(); 367 368 // we're purposefully ignoring the prefix. 369 if (nameSpaceID == attrNS && nameAtom->Equals(aLocalName)) { 370 RefPtr<mozilla::dom::NodeInfo> ni; 371 ni = mContent->NodeInfo()->NodeInfoManager()->GetNodeInfo( 372 nameAtom, name->GetPrefix(), nameSpaceID, nsINode::ATTRIBUTE_NODE); 373 374 return ni.forget(); 375 } 376 } 377 378 return nullptr; 379 } 380 381 already_AddRefed<Attr> nsDOMAttributeMap::RemoveNamedItemNS( 382 const nsAString& aNamespaceURI, const nsAString& aLocalName, 383 ErrorResult& aError) { 384 RefPtr<mozilla::dom::NodeInfo> ni = 385 GetAttrNodeInfo(aNamespaceURI, aLocalName); 386 if (!ni) { 387 aError.Throw(NS_ERROR_DOM_NOT_FOUND_ERR); 388 return nullptr; 389 } 390 391 return RemoveNamedItem(ni, aError); 392 } 393 394 uint32_t nsDOMAttributeMap::Count() const { return mAttributeCache.Count(); } 395 396 size_t nsDOMAttributeMap::SizeOfIncludingThis( 397 MallocSizeOf aMallocSizeOf) const { 398 size_t n = aMallocSizeOf(this); 399 400 n += mAttributeCache.ShallowSizeOfExcludingThis(aMallocSizeOf); 401 for (const auto& entry : mAttributeCache) { 402 n += aMallocSizeOf(entry.GetWeak()); 403 } 404 405 // NB: mContent is non-owning and thus not counted. 406 return n; 407 } 408 409 /* virtual */ 410 JSObject* nsDOMAttributeMap::WrapObject(JSContext* aCx, 411 JS::Handle<JSObject*> aGivenProto) { 412 return NamedNodeMap_Binding::Wrap(aCx, this, aGivenProto); 413 } 414 415 DocGroup* nsDOMAttributeMap::GetDocGroup() const { 416 return mContent ? mContent->OwnerDoc()->GetDocGroup() : nullptr; 417 }