1/* 2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org) 3 * (C) 1999 Antti Koivisto (koivisto@kde.org) 4 * (C) 2001 Peter Kelly (pmk@post.com) 5 * (C) 2001 Dirk Mueller (mueller@kde.org) 6 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. 7 * (C) 2007 Eric Seidel (eric@webkit.org) 8 * 9 * This library is free software; you can redistribute it and/or 10 * modify it under the terms of the GNU Library General Public 11 * License as published by the Free Software Foundation; either 12 * version 2 of the License, or (at your option) any later version. 13 * 14 * This library is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 * Library General Public License for more details. 18 * 19 * You should have received a copy of the GNU Library General Public License 20 * along with this library; see the file COPYING.LIB. If not, write to 21 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 22 * Boston, MA 02110-1301, USA. 23 */ 24 25#include "config.h" 26#include "NamedNodeMap.h" 27 28#include "Attr.h" 29#include "Document.h" 30#include "Element.h" 31#include "ExceptionCode.h" 32#include "HTMLNames.h" 33 34namespace WebCore { 35 36using namespace HTMLNames; 37 38static inline bool shouldIgnoreAttributeCase(const Element* e) 39{ 40 return e && e->document()->isHTMLDocument() && e->isHTMLElement(); 41} 42 43inline void NamedNodeMap::detachAttributesFromElement() 44{ 45 size_t size = m_attributes.size(); 46 for (size_t i = 0; i < size; i++) { 47 if (Attr* attr = m_attributes[i]->attr()) 48 attr->m_element = 0; 49 } 50} 51 52NamedNodeMap::~NamedNodeMap() 53{ 54 detachAttributesFromElement(); 55} 56 57PassRefPtr<Node> NamedNodeMap::getNamedItem(const String& name) const 58{ 59 Attribute* a = getAttributeItem(name, shouldIgnoreAttributeCase(m_element)); 60 if (!a) 61 return 0; 62 63 return a->createAttrIfNeeded(m_element); 64} 65 66PassRefPtr<Node> NamedNodeMap::getNamedItemNS(const String& namespaceURI, const String& localName) const 67{ 68 return getNamedItem(QualifiedName(nullAtom, localName, namespaceURI)); 69} 70 71PassRefPtr<Node> NamedNodeMap::removeNamedItem(const String& name, ExceptionCode& ec) 72{ 73 Attribute* a = getAttributeItem(name, shouldIgnoreAttributeCase(m_element)); 74 if (!a) { 75 ec = NOT_FOUND_ERR; 76 return 0; 77 } 78 79 return removeNamedItem(a->name(), ec); 80} 81 82PassRefPtr<Node> NamedNodeMap::removeNamedItemNS(const String& namespaceURI, const String& localName, ExceptionCode& ec) 83{ 84 return removeNamedItem(QualifiedName(nullAtom, localName, namespaceURI), ec); 85} 86 87PassRefPtr<Node> NamedNodeMap::getNamedItem(const QualifiedName& name) const 88{ 89 Attribute* a = getAttributeItem(name); 90 if (!a) 91 return 0; 92 93 return a->createAttrIfNeeded(m_element); 94} 95 96PassRefPtr<Node> NamedNodeMap::setNamedItem(Node* arg, ExceptionCode& ec) 97{ 98 if (!m_element || !arg) { 99 ec = NOT_FOUND_ERR; 100 return 0; 101 } 102 103 // Not mentioned in spec: throw a HIERARCHY_REQUEST_ERROR if the user passes in a non-attribute node 104 if (!arg->isAttributeNode()) { 105 ec = HIERARCHY_REQUEST_ERR; 106 return 0; 107 } 108 Attr *attr = static_cast<Attr*>(arg); 109 110 Attribute* a = attr->attr(); 111 Attribute* old = getAttributeItem(a->name()); 112 if (old == a) 113 return RefPtr<Node>(arg); // we know about it already 114 115 // INUSE_ATTRIBUTE_ERR: Raised if arg is an Attr that is already an attribute of another Element object. 116 // The DOM user must explicitly clone Attr nodes to re-use them in other elements. 117 if (attr->ownerElement()) { 118 ec = INUSE_ATTRIBUTE_ERR; 119 return 0; 120 } 121 122 if (attr->isId()) 123 m_element->updateId(old ? old->value() : nullAtom, a->value()); 124 125 // ### slightly inefficient - resizes attribute array twice. 126 RefPtr<Node> r; 127 if (old) { 128 r = old->createAttrIfNeeded(m_element); 129 removeAttribute(a->name()); 130 } 131 132 addAttribute(a); 133 return r.release(); 134} 135 136PassRefPtr<Node> NamedNodeMap::setNamedItemNS(Node* node, ExceptionCode& ec) 137{ 138 return setNamedItem(node, ec); 139} 140 141// The DOM2 spec doesn't say that removeAttribute[NS] throws NOT_FOUND_ERR 142// if the attribute is not found, but at this level we have to throw NOT_FOUND_ERR 143// because of removeNamedItem, removeNamedItemNS, and removeAttributeNode. 144PassRefPtr<Node> NamedNodeMap::removeNamedItem(const QualifiedName& name, ExceptionCode& ec) 145{ 146 Attribute* a = getAttributeItem(name); 147 if (!a) { 148 ec = NOT_FOUND_ERR; 149 return 0; 150 } 151 152 RefPtr<Attr> r = a->createAttrIfNeeded(m_element); 153 154 if (r->isId()) 155 m_element->updateId(a->value(), nullAtom); 156 157 removeAttribute(name); 158 return r.release(); 159} 160 161PassRefPtr<Node> NamedNodeMap::item(unsigned index) const 162{ 163 if (index >= length()) 164 return 0; 165 166 return m_attributes[index]->createAttrIfNeeded(m_element); 167} 168 169void NamedNodeMap::copyAttributesToVector(Vector<RefPtr<Attribute> >& copy) 170{ 171 copy = m_attributes; 172} 173 174Attribute* NamedNodeMap::getAttributeItemSlowCase(const String& name, bool shouldIgnoreAttributeCase) const 175{ 176 unsigned len = length(); 177 178 // Continue to checking case-insensitively and/or full namespaced names if necessary: 179 for (unsigned i = 0; i < len; ++i) { 180 const QualifiedName& attrName = m_attributes[i]->name(); 181 if (!attrName.hasPrefix()) { 182 if (shouldIgnoreAttributeCase && equalIgnoringCase(name, attrName.localName())) 183 return m_attributes[i].get(); 184 } else { 185 // FIXME: Would be faster to do this comparison without calling toString, which 186 // generates a temporary string by concatenation. But this branch is only reached 187 // if the attribute name has a prefix, which is rare in HTML. 188 if (equalPossiblyIgnoringCase(name, attrName.toString(), shouldIgnoreAttributeCase)) 189 return m_attributes[i].get(); 190 } 191 } 192 return 0; 193} 194 195void NamedNodeMap::clearAttributes() 196{ 197 m_classNames.clear(); 198 m_mappedAttributeCount = 0; 199 200 detachAttributesFromElement(); 201 m_attributes.clear(); 202} 203 204void NamedNodeMap::detachFromElement() 205{ 206 // This can't happen if the holder of the map is JavaScript, because we mark the 207 // element if the map is alive. So it has no impact on web page behavior. Because 208 // of that, we can simply clear all the attributes to avoid accessing stale 209 // pointers to do things like create Attr objects. 210 m_element = 0; 211 clearAttributes(); 212} 213 214void NamedNodeMap::setAttributes(const NamedNodeMap& other) 215{ 216 // clone all attributes in the other map, but attach to our element 217 if (!m_element) 218 return; 219 220 // If assigning the map changes the id attribute, we need to call 221 // updateId. 222 Attribute* oldId = getAttributeItem(m_element->document()->idAttributeName()); 223 Attribute* newId = other.getAttributeItem(m_element->document()->idAttributeName()); 224 225 if (oldId || newId) 226 m_element->updateId(oldId ? oldId->value() : nullAtom, newId ? newId->value() : nullAtom); 227 228 clearAttributes(); 229 unsigned newLength = other.length(); 230 m_attributes.resize(newLength); 231 for (unsigned i = 0; i < newLength; i++) 232 m_attributes[i] = other.m_attributes[i]->clone(); 233 234 // FIXME: This is wasteful. The class list could be preserved on a copy, and we 235 // wouldn't have to waste time reparsing the attribute. 236 // The derived class, HTMLNamedNodeMap, which manages a parsed class list for the CLASS attribute, 237 // will update its member variable when parse attribute is called. 238 for (unsigned i = 0; i < newLength; i++) 239 m_element->attributeChanged(m_attributes[i].get(), true); 240} 241 242void NamedNodeMap::addAttribute(PassRefPtr<Attribute> prpAttribute) 243{ 244 RefPtr<Attribute> attribute = prpAttribute; 245 246 // Add the attribute to the list 247 m_attributes.append(attribute); 248 249 if (Attr* attr = attribute->attr()) 250 attr->m_element = m_element; 251 252 // Notify the element that the attribute has been added, and dispatch appropriate mutation events 253 // Note that element may be null here if we are called from insertAttribute() during parsing 254 if (m_element) { 255 m_element->attributeChanged(attribute.get()); 256 // Because of our updateStyleAttribute() style modification events are never sent at the right time, so don't bother sending them. 257 if (attribute->name() != styleAttr) { 258 m_element->dispatchAttrAdditionEvent(attribute.get()); 259 m_element->dispatchSubtreeModifiedEvent(); 260 } 261 } 262} 263 264void NamedNodeMap::removeAttribute(const QualifiedName& name) 265{ 266 unsigned len = length(); 267 unsigned index = len; 268 for (unsigned i = 0; i < len; ++i) { 269 if (m_attributes[i]->name().matches(name)) { 270 index = i; 271 break; 272 } 273 } 274 275 if (index >= len) 276 return; 277 278 // Remove the attribute from the list 279 RefPtr<Attribute> attr = m_attributes[index].get(); 280 if (Attr* a = m_attributes[index]->attr()) 281 a->m_element = 0; 282 283 m_attributes.remove(index); 284 285 // Notify the element that the attribute has been removed 286 // dispatch appropriate mutation events 287 if (m_element && !attr->m_value.isNull()) { 288 AtomicString value = attr->m_value; 289 attr->m_value = nullAtom; 290 m_element->attributeChanged(attr.get()); 291 attr->m_value = value; 292 } 293 if (m_element) { 294 m_element->dispatchAttrRemovalEvent(attr.get()); 295 m_element->dispatchSubtreeModifiedEvent(); 296 } 297} 298 299void NamedNodeMap::setClass(const String& classStr) 300{ 301 if (!element()->hasClass()) { 302 m_classNames.clear(); 303 return; 304 } 305 306 m_classNames.set(classStr, element()->document()->inQuirksMode()); 307} 308 309int NamedNodeMap::declCount() const 310{ 311 int result = 0; 312 for (unsigned i = 0; i < length(); i++) { 313 Attribute* attr = attributeItem(i); 314 if (attr->decl()) { 315 ASSERT(attr->isMappedAttribute()); 316 result++; 317 } 318 } 319 return result; 320} 321 322bool NamedNodeMap::mapsEquivalent(const NamedNodeMap* otherMap) const 323{ 324 if (!otherMap) 325 return false; 326 327 unsigned len = length(); 328 if (len != otherMap->length()) 329 return false; 330 331 for (unsigned i = 0; i < len; i++) { 332 Attribute* attr = attributeItem(i); 333 Attribute* otherAttr = otherMap->getAttributeItem(attr->name()); 334 if (!otherAttr || attr->value() != otherAttr->value()) 335 return false; 336 } 337 338 return true; 339} 340 341bool NamedNodeMap::mappedMapsEquivalent(const NamedNodeMap* otherMap) const 342{ 343 // The # of decls must match. 344 if (declCount() != otherMap->declCount()) 345 return false; 346 347 // The values for each decl must match. 348 for (unsigned i = 0; i < length(); i++) { 349 Attribute* attr = attributeItem(i); 350 if (attr->decl()) { 351 ASSERT(attr->isMappedAttribute()); 352 353 Attribute* otherAttr = otherMap->getAttributeItem(attr->name()); 354 if (!otherAttr || !otherAttr->decl() || attr->value() != otherAttr->value()) 355 return false; 356 if (!attr->decl()->propertiesEqual(otherAttr->decl())) 357 return false; 358 } 359 } 360 return true; 361} 362 363} // namespace WebCore 364