1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is Mozilla Communicator client code.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Netscape Communications Corporation.
19 : * Portions created by the Initial Developer are Copyright (C) 1998
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Allan Beaufour <allan@beaufour.dk>
24 : *
25 : * Alternatively, the contents of this file may be used under the terms of
26 : * either of the GNU General Public License Version 2 or later (the "GPL"),
27 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 : * in which case the provisions of the GPL or the LGPL are applicable instead
29 : * of those above. If you wish to allow use of your version of this file only
30 : * under the terms of either the GPL or the LGPL, and not to allow others to
31 : * use your version of this file under the terms of the MPL, indicate your
32 : * decision by deleting the provisions above and replace them with the notice
33 : * and other provisions required by the GPL or the LGPL. If you do not delete
34 : * the provisions above, a recipient may use your version of this file under
35 : * the terms of any one of the MPL, the GPL or the LGPL.
36 : *
37 : * ***** END LICENSE BLOCK ***** */
38 :
39 : /*
40 : * Implementation of the |attributes| property of DOM Core's nsIDOMNode object.
41 : */
42 :
43 : #include "nsDOMAttributeMap.h"
44 : #include "nsDOMAttribute.h"
45 : #include "nsIDOMDocument.h"
46 : #include "nsGenericElement.h"
47 : #include "nsIContent.h"
48 : #include "nsIDocument.h"
49 : #include "nsINameSpaceManager.h"
50 : #include "nsDOMError.h"
51 : #include "nsContentUtils.h"
52 : #include "nsNodeInfoManager.h"
53 : #include "nsAttrName.h"
54 : #include "nsUnicharUtils.h"
55 :
56 : //----------------------------------------------------------------------
57 :
58 232 : nsDOMAttributeMap::nsDOMAttributeMap(Element* aContent)
59 232 : : mContent(aContent)
60 : {
61 : // We don't add a reference to our content. If it goes away,
62 : // we'll be told to drop our reference
63 232 : }
64 :
65 : bool
66 232 : nsDOMAttributeMap::Init()
67 : {
68 232 : return mAttributeCache.Init();
69 : }
70 :
71 : /**
72 : * Clear map pointer for attributes.
73 : */
74 : PLDHashOperator
75 945 : RemoveMapRef(nsAttrHashKey::KeyType aKey, nsRefPtr<nsDOMAttribute>& aData,
76 : void* aUserArg)
77 : {
78 945 : aData->SetMap(nsnull);
79 :
80 945 : return PL_DHASH_REMOVE;
81 : }
82 :
83 696 : nsDOMAttributeMap::~nsDOMAttributeMap()
84 : {
85 232 : mAttributeCache.Enumerate(RemoveMapRef, nsnull);
86 928 : }
87 :
88 : void
89 464 : nsDOMAttributeMap::DropReference()
90 : {
91 464 : mAttributeCache.Enumerate(RemoveMapRef, nsnull);
92 464 : mContent = nsnull;
93 464 : }
94 :
95 1464 : NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMAttributeMap)
96 :
97 232 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDOMAttributeMap)
98 232 : tmp->DropReference();
99 232 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
100 :
101 :
102 : PLDHashOperator
103 945 : TraverseMapEntry(nsAttrHashKey::KeyType aKey, nsRefPtr<nsDOMAttribute>& aData,
104 : void* aUserArg)
105 : {
106 : nsCycleCollectionTraversalCallback *cb =
107 945 : static_cast<nsCycleCollectionTraversalCallback*>(aUserArg);
108 :
109 945 : cb->NoteXPCOMChild(static_cast<nsINode*>(aData.get()));
110 :
111 945 : return PL_DHASH_NEXT;
112 : }
113 :
114 232 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsDOMAttributeMap)
115 232 : tmp->mAttributeCache.Enumerate(TraverseMapEntry, &cb);
116 232 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
117 :
118 : DOMCI_DATA(NamedNodeMap, nsDOMAttributeMap)
119 :
120 : // QueryInterface implementation for nsDOMAttributeMap
121 14366 : NS_INTERFACE_TABLE_HEAD(nsDOMAttributeMap)
122 : NS_OFFSET_AND_INTERFACE_TABLE_BEGIN(nsDOMAttributeMap)
123 : NS_INTERFACE_TABLE_ENTRY(nsDOMAttributeMap, nsIDOMNamedNodeMap)
124 14366 : NS_OFFSET_AND_INTERFACE_TABLE_END
125 12252 : NS_OFFSET_AND_INTERFACE_TABLE_TO_MAP_SEGUE
126 8222 : NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsDOMAttributeMap)
127 2594 : NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(NamedNodeMap)
128 2362 : NS_INTERFACE_MAP_END
129 :
130 6394 : NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMAttributeMap)
131 6394 : NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMAttributeMap)
132 :
133 : PLDHashOperator
134 0 : SetOwnerDocumentFunc(nsAttrHashKey::KeyType aKey,
135 : nsRefPtr<nsDOMAttribute>& aData,
136 : void* aUserArg)
137 : {
138 0 : nsresult rv = aData->SetOwnerDocument(static_cast<nsIDocument*>(aUserArg));
139 :
140 0 : return NS_FAILED(rv) ? PL_DHASH_STOP : PL_DHASH_NEXT;
141 : }
142 :
143 : nsresult
144 0 : nsDOMAttributeMap::SetOwnerDocument(nsIDocument* aDocument)
145 : {
146 0 : PRUint32 n = mAttributeCache.Enumerate(SetOwnerDocumentFunc, aDocument);
147 0 : NS_ENSURE_TRUE(n == mAttributeCache.Count(), NS_ERROR_FAILURE);
148 :
149 0 : return NS_OK;
150 : }
151 :
152 : void
153 0 : nsDOMAttributeMap::DropAttribute(PRInt32 aNamespaceID, nsIAtom* aLocalName)
154 : {
155 0 : nsAttrKey attr(aNamespaceID, aLocalName);
156 0 : nsDOMAttribute *node = mAttributeCache.GetWeak(attr);
157 0 : if (node) {
158 : // Break link to map
159 0 : node->SetMap(nsnull);
160 :
161 : // Remove from cache
162 0 : mAttributeCache.Remove(attr);
163 : }
164 0 : }
165 :
166 : nsresult
167 0 : nsDOMAttributeMap::RemoveAttribute(nsINodeInfo* aNodeInfo, nsIDOMNode** aReturn)
168 : {
169 0 : NS_ASSERTION(aNodeInfo, "RemoveAttribute() called with aNodeInfo == nsnull!");
170 0 : NS_ASSERTION(aReturn, "RemoveAttribute() called with aReturn == nsnull");
171 :
172 0 : *aReturn = nsnull;
173 :
174 0 : nsAttrKey attr(aNodeInfo->NamespaceID(), aNodeInfo->NameAtom());
175 :
176 0 : nsRefPtr<nsDOMAttribute> node;
177 0 : if (!mAttributeCache.Get(attr, getter_AddRefs(node))) {
178 0 : nsAutoString value;
179 : // As we are removing the attribute we need to set the current value in
180 : // the attribute node.
181 0 : mContent->GetAttr(aNodeInfo->NamespaceID(), aNodeInfo->NameAtom(), value);
182 0 : nsCOMPtr<nsINodeInfo> ni = aNodeInfo;
183 : nsCOMPtr<nsIDOMNode> newAttr =
184 0 : new nsDOMAttribute(nsnull, ni.forget(), value, true);
185 0 : if (!newAttr) {
186 0 : return NS_ERROR_OUT_OF_MEMORY;
187 : }
188 0 : newAttr.swap(*aReturn);
189 : }
190 : else {
191 : // Break link to map
192 0 : node->SetMap(nsnull);
193 :
194 : // Remove from cache
195 0 : mAttributeCache.Remove(attr);
196 :
197 0 : node.forget(aReturn);
198 : }
199 :
200 0 : return NS_OK;
201 : }
202 :
203 : nsDOMAttribute*
204 977 : nsDOMAttributeMap::GetAttribute(nsINodeInfo* aNodeInfo, bool aNsAware)
205 : {
206 977 : NS_ASSERTION(aNodeInfo, "GetAttribute() called with aNodeInfo == nsnull!");
207 :
208 977 : nsAttrKey attr(aNodeInfo->NamespaceID(), aNodeInfo->NameAtom());
209 :
210 977 : nsDOMAttribute* node = mAttributeCache.GetWeak(attr);
211 977 : if (!node) {
212 1886 : nsCOMPtr<nsINodeInfo> ni = aNodeInfo;
213 : nsRefPtr<nsDOMAttribute> newAttr =
214 2829 : new nsDOMAttribute(this, ni.forget(), EmptyString(), aNsAware);
215 943 : if (newAttr && mAttributeCache.Put(attr, newAttr)) {
216 943 : node = newAttr;
217 : }
218 : }
219 :
220 977 : return node;
221 : }
222 :
223 : nsDOMAttribute*
224 0 : nsDOMAttributeMap::GetNamedItem(const nsAString& aAttrName, nsresult *aResult)
225 : {
226 0 : *aResult = NS_OK;
227 :
228 0 : if (mContent) {
229 : nsCOMPtr<nsINodeInfo> ni =
230 0 : mContent->GetExistingAttrNameFromQName(aAttrName);
231 0 : if (ni) {
232 0 : return GetAttribute(ni, false);
233 : }
234 : }
235 :
236 0 : return nsnull;
237 : }
238 :
239 : NS_IMETHODIMP
240 0 : nsDOMAttributeMap::GetNamedItem(const nsAString& aAttrName,
241 : nsIDOMNode** aAttribute)
242 : {
243 0 : NS_ENSURE_ARG_POINTER(aAttribute);
244 :
245 : nsresult rv;
246 0 : NS_IF_ADDREF(*aAttribute = GetNamedItem(aAttrName, &rv));
247 :
248 0 : return rv;
249 : }
250 :
251 : NS_IMETHODIMP
252 2 : nsDOMAttributeMap::SetNamedItem(nsIDOMNode *aNode, nsIDOMNode **aReturn)
253 : {
254 2 : return SetNamedItemInternal(aNode, aReturn, false);
255 : }
256 :
257 : NS_IMETHODIMP
258 0 : nsDOMAttributeMap::SetNamedItemNS(nsIDOMNode *aNode, nsIDOMNode **aReturn)
259 : {
260 0 : return SetNamedItemInternal(aNode, aReturn, true);
261 : }
262 :
263 : nsresult
264 2 : nsDOMAttributeMap::SetNamedItemInternal(nsIDOMNode *aNode,
265 : nsIDOMNode **aReturn,
266 : bool aWithNS)
267 : {
268 2 : NS_ENSURE_ARG_POINTER(aNode);
269 2 : NS_ENSURE_ARG_POINTER(aReturn);
270 :
271 2 : nsresult rv = NS_OK;
272 2 : *aReturn = nsnull;
273 4 : nsCOMPtr<nsIDOMNode> tmpReturn;
274 :
275 2 : if (mContent) {
276 : // XXX should check same-origin between mContent and aNode however
277 : // nsContentUtils::CheckSameOrigin can't deal with attributenodes yet
278 :
279 4 : nsCOMPtr<nsIAttribute> iAttribute(do_QueryInterface(aNode));
280 2 : if (!iAttribute) {
281 0 : return NS_ERROR_DOM_HIERARCHY_REQUEST_ERR;
282 : }
283 :
284 2 : nsDOMAttribute *attribute = static_cast<nsDOMAttribute*>(iAttribute.get());
285 :
286 : // Check that attribute is not owned by somebody else
287 2 : nsDOMAttributeMap* owner = iAttribute->GetMap();
288 2 : if (owner) {
289 0 : if (owner != this) {
290 0 : return NS_ERROR_DOM_INUSE_ATTRIBUTE_ERR;
291 : }
292 :
293 : // setting a preexisting attribute is a no-op, just return the same
294 : // node.
295 0 : NS_ADDREF(*aReturn = aNode);
296 :
297 0 : return NS_OK;
298 : }
299 :
300 2 : if (!mContent->HasSameOwnerDoc(iAttribute)) {
301 : nsCOMPtr<nsIDOMDocument> domDoc =
302 0 : do_QueryInterface(mContent->OwnerDoc(), &rv);
303 0 : NS_ENSURE_SUCCESS(rv, rv);
304 :
305 0 : nsCOMPtr<nsIDOMNode> adoptedNode;
306 0 : rv = domDoc->AdoptNode(aNode, getter_AddRefs(adoptedNode));
307 0 : NS_ENSURE_SUCCESS(rv, rv);
308 :
309 0 : NS_ASSERTION(adoptedNode == aNode, "Uh, adopt node changed nodes?");
310 : }
311 :
312 : // Get nodeinfo and preexisting attribute (if it exists)
313 4 : nsAutoString name;
314 4 : nsCOMPtr<nsINodeInfo> ni;
315 :
316 : // SetNamedItemNS()
317 2 : if (aWithNS) {
318 : // Return existing attribute, if present
319 0 : ni = iAttribute->NodeInfo();
320 :
321 0 : if (mContent->HasAttr(ni->NamespaceID(), ni->NameAtom())) {
322 0 : rv = RemoveAttribute(ni, getter_AddRefs(tmpReturn));
323 0 : NS_ENSURE_SUCCESS(rv, rv);
324 : }
325 : }
326 : else { // SetNamedItem()
327 2 : attribute->GetName(name);
328 :
329 : // get node-info of old attribute
330 2 : ni = mContent->GetExistingAttrNameFromQName(name);
331 2 : if (ni) {
332 0 : rv = RemoveAttribute(ni, getter_AddRefs(tmpReturn));
333 0 : NS_ENSURE_SUCCESS(rv, rv);
334 : }
335 : else {
336 2 : if (mContent->IsInHTMLDocument() &&
337 0 : mContent->IsHTML()) {
338 0 : nsContentUtils::ASCIIToLower(name);
339 : }
340 :
341 : rv = mContent->NodeInfo()->NodeInfoManager()->
342 : GetNodeInfo(name, nsnull, kNameSpaceID_None,
343 2 : nsIDOMNode::ATTRIBUTE_NODE, getter_AddRefs(ni));
344 2 : NS_ENSURE_SUCCESS(rv, rv);
345 : // value is already empty
346 : }
347 : }
348 :
349 4 : nsAutoString value;
350 2 : attribute->GetValue(value);
351 :
352 : // Add the new attribute to the attribute map before updating
353 : // its value in the element. @see bug 364413.
354 2 : nsAttrKey attrkey(ni->NamespaceID(), ni->NameAtom());
355 2 : rv = mAttributeCache.Put(attrkey, attribute);
356 2 : NS_ENSURE_SUCCESS(rv, rv);
357 2 : iAttribute->SetMap(this);
358 :
359 : rv = mContent->SetAttr(ni->NamespaceID(), ni->NameAtom(),
360 2 : ni->GetPrefixAtom(), value, true);
361 2 : if (NS_FAILED(rv)) {
362 0 : DropAttribute(ni->NamespaceID(), ni->NameAtom());
363 : }
364 : }
365 :
366 2 : tmpReturn.swap(*aReturn); // transfers ref.
367 :
368 2 : return rv;
369 : }
370 :
371 : NS_IMETHODIMP
372 0 : nsDOMAttributeMap::RemoveNamedItem(const nsAString& aName,
373 : nsIDOMNode** aReturn)
374 : {
375 0 : NS_ENSURE_ARG_POINTER(aReturn);
376 0 : *aReturn = nsnull;
377 :
378 0 : nsresult rv = NS_OK;
379 :
380 0 : if (mContent) {
381 0 : nsCOMPtr<nsINodeInfo> ni = mContent->GetExistingAttrNameFromQName(aName);
382 0 : if (!ni) {
383 0 : return NS_ERROR_DOM_NOT_FOUND_ERR;
384 : }
385 :
386 0 : NS_ADDREF(*aReturn = GetAttribute(ni, true));
387 :
388 : // This removes the attribute node from the attribute map.
389 0 : rv = mContent->UnsetAttr(ni->NamespaceID(), ni->NameAtom(), true);
390 : }
391 :
392 0 : return rv;
393 : }
394 :
395 :
396 : nsDOMAttribute*
397 943 : nsDOMAttributeMap::GetItemAt(PRUint32 aIndex, nsresult *aResult)
398 : {
399 943 : *aResult = NS_OK;
400 :
401 943 : nsDOMAttribute* node = nsnull;
402 :
403 : const nsAttrName* name;
404 943 : if (mContent && (name = mContent->GetAttrNameAt(aIndex))) {
405 : // Don't use the nodeinfo even if one exists since it can
406 : // have the wrong owner document.
407 1886 : nsCOMPtr<nsINodeInfo> ni;
408 : ni = mContent->NodeInfo()->NodeInfoManager()->
409 : GetNodeInfo(name->LocalName(), name->GetPrefix(), name->NamespaceID(),
410 943 : nsIDOMNode::ATTRIBUTE_NODE);
411 943 : if (ni) {
412 943 : node = GetAttribute(ni, true);
413 : }
414 : else {
415 0 : *aResult = NS_ERROR_OUT_OF_MEMORY;
416 : }
417 : }
418 :
419 943 : return node;
420 : }
421 :
422 : NS_IMETHODIMP
423 943 : nsDOMAttributeMap::Item(PRUint32 aIndex, nsIDOMNode** aReturn)
424 : {
425 : nsresult rv;
426 943 : NS_IF_ADDREF(*aReturn = GetItemAt(aIndex, &rv));
427 943 : return rv;
428 : }
429 :
430 : nsresult
431 1171 : nsDOMAttributeMap::GetLength(PRUint32 *aLength)
432 : {
433 1171 : NS_ENSURE_ARG_POINTER(aLength);
434 :
435 1171 : if (mContent) {
436 1171 : *aLength = mContent->GetAttrCount();
437 : }
438 : else {
439 0 : *aLength = 0;
440 : }
441 :
442 1171 : return NS_OK;
443 : }
444 :
445 : NS_IMETHODIMP
446 34 : nsDOMAttributeMap::GetNamedItemNS(const nsAString& aNamespaceURI,
447 : const nsAString& aLocalName,
448 : nsIDOMNode** aReturn)
449 : {
450 34 : return GetNamedItemNSInternal(aNamespaceURI, aLocalName, aReturn);
451 : }
452 :
453 : nsresult
454 34 : nsDOMAttributeMap::GetNamedItemNSInternal(const nsAString& aNamespaceURI,
455 : const nsAString& aLocalName,
456 : nsIDOMNode** aReturn,
457 : bool aRemove)
458 : {
459 34 : NS_ENSURE_ARG_POINTER(aReturn);
460 34 : *aReturn = nsnull;
461 :
462 34 : if (!mContent) {
463 0 : return NS_OK;
464 : }
465 :
466 34 : PRInt32 nameSpaceID = kNameSpaceID_None;
467 :
468 34 : if (!aNamespaceURI.IsEmpty()) {
469 : nameSpaceID =
470 32 : nsContentUtils::NameSpaceManager()->GetNameSpaceID(aNamespaceURI);
471 :
472 32 : if (nameSpaceID == kNameSpaceID_Unknown) {
473 0 : return NS_OK;
474 : }
475 : }
476 :
477 34 : PRUint32 i, count = mContent->GetAttrCount();
478 212 : for (i = 0; i < count; ++i) {
479 212 : const nsAttrName* name = mContent->GetAttrNameAt(i);
480 212 : PRInt32 attrNS = name->NamespaceID();
481 212 : nsIAtom* nameAtom = name->LocalName();
482 :
483 272 : if (nameSpaceID == attrNS &&
484 60 : nameAtom->Equals(aLocalName)) {
485 68 : nsCOMPtr<nsINodeInfo> ni;
486 : ni = mContent->NodeInfo()->NodeInfoManager()->
487 : GetNodeInfo(nameAtom, name->GetPrefix(), nameSpaceID,
488 34 : nsIDOMNode::ATTRIBUTE_NODE);
489 34 : NS_ENSURE_TRUE(ni, NS_ERROR_OUT_OF_MEMORY);
490 :
491 34 : if (aRemove) {
492 0 : return RemoveAttribute(ni, aReturn);
493 : }
494 :
495 34 : NS_ADDREF(*aReturn = GetAttribute(ni, true));
496 :
497 34 : return NS_OK;
498 : }
499 : }
500 :
501 0 : return NS_OK;
502 : }
503 :
504 : NS_IMETHODIMP
505 0 : nsDOMAttributeMap::RemoveNamedItemNS(const nsAString& aNamespaceURI,
506 : const nsAString& aLocalName,
507 : nsIDOMNode** aReturn)
508 : {
509 0 : NS_ENSURE_ARG_POINTER(aReturn);
510 0 : *aReturn = nsnull;
511 :
512 : nsresult rv = GetNamedItemNSInternal(aNamespaceURI,
513 : aLocalName,
514 : aReturn,
515 0 : true);
516 0 : NS_ENSURE_SUCCESS(rv, rv);
517 :
518 0 : if (!*aReturn) {
519 0 : return NS_ERROR_DOM_NOT_FOUND_ERR;
520 : }
521 :
522 0 : nsCOMPtr<nsIAttribute> attr = do_QueryInterface(*aReturn);
523 0 : NS_ASSERTION(attr, "attribute returned from nsDOMAttributeMap::GetNameItemNS "
524 : "didn't implement nsIAttribute");
525 0 : NS_ENSURE_TRUE(attr, NS_ERROR_UNEXPECTED);
526 :
527 0 : nsINodeInfo *ni = attr->NodeInfo();
528 0 : mContent->UnsetAttr(ni->NamespaceID(), ni->NameAtom(), true);
529 :
530 0 : return NS_OK;
531 : }
532 :
533 : PRUint32
534 0 : nsDOMAttributeMap::Count() const
535 : {
536 0 : return mAttributeCache.Count();
537 : }
538 :
539 : PRUint32
540 0 : nsDOMAttributeMap::Enumerate(AttrCache::EnumReadFunction aFunc,
541 : void *aUserArg) const
542 : {
543 0 : return mAttributeCache.EnumerateRead(aFunc, aUserArg);
544 4392 : }
|