1 /* 2 * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org) 3 * (C) 1999 Antti Koivisto (koivisto (at) kde.org) 4 * (C) 2001 Peter Kelly (pmk (at) post.com) 5 * (C) 2001 Dirk Mueller (mueller (at) kde.org) 6 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. 7 * (C) 2007 Eric Seidel (eric (at) 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 34 namespace WebCore { 35 36 using namespace HTMLNames; 37 38 static inline bool shouldIgnoreAttributeCase(const Element* e) 39 { 40 return e && e->document()->isHTMLDocument() && e->isHTMLElement(); 41 } 42 43 inline 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 52 NamedNodeMap::~NamedNodeMap() 53 { 54 detachAttributesFromElement(); 55 } 56 57 PassRefPtr<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 66 PassRefPtr<Node> NamedNodeMap::getNamedItemNS(const String& namespaceURI, const String& localName) const 67 { 68 return getNamedItem(QualifiedName(nullAtom, localName, namespaceURI)); 69 } 70 71 PassRefPtr<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 82 PassRefPtr<Node> NamedNodeMap::removeNamedItemNS(const String& namespaceURI, const String& localName, ExceptionCode& ec) 83 { 84 return removeNamedItem(QualifiedName(nullAtom, localName, namespaceURI), ec); 85 } 86 87 PassRefPtr<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 96 PassRefPtr<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 136 PassRefPtr<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. 144 PassRefPtr<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 161 PassRefPtr<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 169 void NamedNodeMap::copyAttributesToVector(Vector<RefPtr<Attribute> >& copy) 170 { 171 copy = m_attributes; 172 } 173 174 Attribute* 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 195 void NamedNodeMap::clearAttributes() 196 { 197 m_classNames.clear(); 198 m_mappedAttributeCount = 0; 199 200 detachAttributesFromElement(); 201 m_attributes.clear(); 202 } 203 204 void 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 214 void 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 242 void 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 264 void 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 299 void 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 309 int 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 322 bool 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 341 bool 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