michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * Implementation of the |attributes| property of DOM Core's Element object. michael@0: */ michael@0: michael@0: #include "nsDOMAttributeMap.h" michael@0: michael@0: #include "mozilla/MemoryReporting.h" michael@0: #include "mozilla/dom/Attr.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "mozilla/dom/MozNamedAttrMapBinding.h" michael@0: #include "nsAttrName.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsError.h" michael@0: #include "nsIContentInlines.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsNameSpaceManager.h" michael@0: #include "nsNodeInfoManager.h" michael@0: #include "nsUnicharUtils.h" michael@0: #include "nsWrapperCacheInlines.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: nsDOMAttributeMap::nsDOMAttributeMap(Element* aContent) michael@0: : mContent(aContent) michael@0: { michael@0: // We don't add a reference to our content. If it goes away, michael@0: // we'll be told to drop our reference michael@0: SetIsDOMBinding(); michael@0: } michael@0: michael@0: /** michael@0: * Clear map pointer for attributes. michael@0: */ michael@0: PLDHashOperator michael@0: RemoveMapRef(nsAttrHashKey::KeyType aKey, nsRefPtr& aData, michael@0: void* aUserArg) michael@0: { michael@0: aData->SetMap(nullptr); michael@0: michael@0: return PL_DHASH_REMOVE; michael@0: } michael@0: michael@0: nsDOMAttributeMap::~nsDOMAttributeMap() michael@0: { michael@0: mAttributeCache.Enumerate(RemoveMapRef, nullptr); michael@0: } michael@0: michael@0: void michael@0: nsDOMAttributeMap::DropReference() michael@0: { michael@0: mAttributeCache.Enumerate(RemoveMapRef, nullptr); michael@0: mContent = nullptr; michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMAttributeMap) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDOMAttributeMap) michael@0: tmp->DropReference(); michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mContent) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: michael@0: michael@0: PLDHashOperator michael@0: TraverseMapEntry(nsAttrHashKey::KeyType aKey, nsRefPtr& aData, michael@0: void* aUserArg) michael@0: { michael@0: nsCycleCollectionTraversalCallback *cb = michael@0: static_cast(aUserArg); michael@0: michael@0: cb->NoteXPCOMChild(static_cast(aData.get())); michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsDOMAttributeMap) michael@0: tmp->mAttributeCache.Enumerate(TraverseMapEntry, &cb); michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContent) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(nsDOMAttributeMap) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsDOMAttributeMap) michael@0: if (tmp->IsBlack()) { michael@0: if (tmp->mContent) { michael@0: // The map owns the element so we can mark it when the michael@0: // map itself is certainly alive. michael@0: mozilla::dom::FragmentOrElement::MarkNodeChildren(tmp->mContent); michael@0: } michael@0: return true; michael@0: } michael@0: if (tmp->mContent && michael@0: mozilla::dom::FragmentOrElement::CanSkip(tmp->mContent, true)) { michael@0: return true; michael@0: } michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsDOMAttributeMap) michael@0: return tmp->IsBlackAndDoesNotNeedTracing(tmp); michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsDOMAttributeMap) michael@0: return tmp->IsBlack(); michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END michael@0: michael@0: // QueryInterface implementation for nsDOMAttributeMap michael@0: NS_INTERFACE_TABLE_HEAD(nsDOMAttributeMap) michael@0: NS_INTERFACE_TABLE(nsDOMAttributeMap, nsIDOMMozNamedAttrMap) michael@0: NS_INTERFACE_TABLE_TO_MAP_SEGUE michael@0: NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY michael@0: NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsDOMAttributeMap) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMAttributeMap) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMAttributeMap) michael@0: michael@0: PLDHashOperator michael@0: SetOwnerDocumentFunc(nsAttrHashKey::KeyType aKey, michael@0: nsRefPtr& aData, michael@0: void* aUserArg) michael@0: { michael@0: nsresult rv = aData->SetOwnerDocument(static_cast(aUserArg)); michael@0: michael@0: return NS_FAILED(rv) ? PL_DHASH_STOP : PL_DHASH_NEXT; michael@0: } michael@0: michael@0: nsresult michael@0: nsDOMAttributeMap::SetOwnerDocument(nsIDocument* aDocument) michael@0: { michael@0: uint32_t n = mAttributeCache.Enumerate(SetOwnerDocumentFunc, aDocument); michael@0: NS_ENSURE_TRUE(n == mAttributeCache.Count(), NS_ERROR_FAILURE); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsDOMAttributeMap::DropAttribute(int32_t aNamespaceID, nsIAtom* aLocalName) michael@0: { michael@0: nsAttrKey attr(aNamespaceID, aLocalName); michael@0: Attr *node = mAttributeCache.GetWeak(attr); michael@0: if (node) { michael@0: // Break link to map michael@0: node->SetMap(nullptr); michael@0: michael@0: // Remove from cache michael@0: mAttributeCache.Remove(attr); michael@0: } michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsDOMAttributeMap::RemoveAttribute(nsINodeInfo* aNodeInfo) michael@0: { michael@0: NS_ASSERTION(aNodeInfo, "RemoveAttribute() called with aNodeInfo == nullptr!"); michael@0: michael@0: nsAttrKey attr(aNodeInfo->NamespaceID(), aNodeInfo->NameAtom()); michael@0: michael@0: nsRefPtr node; michael@0: if (!mAttributeCache.Get(attr, getter_AddRefs(node))) { michael@0: nsAutoString value; michael@0: // As we are removing the attribute we need to set the current value in michael@0: // the attribute node. michael@0: mContent->GetAttr(aNodeInfo->NamespaceID(), aNodeInfo->NameAtom(), value); michael@0: nsCOMPtr ni = aNodeInfo; michael@0: node = new Attr(nullptr, ni.forget(), value, true); michael@0: } michael@0: else { michael@0: // Break link to map michael@0: node->SetMap(nullptr); michael@0: michael@0: // Remove from cache michael@0: mAttributeCache.Remove(attr); michael@0: } michael@0: michael@0: return node.forget(); michael@0: } michael@0: michael@0: Attr* michael@0: nsDOMAttributeMap::GetAttribute(nsINodeInfo* aNodeInfo, bool aNsAware) michael@0: { michael@0: NS_ASSERTION(aNodeInfo, "GetAttribute() called with aNodeInfo == nullptr!"); michael@0: michael@0: nsAttrKey attr(aNodeInfo->NamespaceID(), aNodeInfo->NameAtom()); michael@0: michael@0: Attr* node = mAttributeCache.GetWeak(attr); michael@0: if (!node) { michael@0: nsCOMPtr ni = aNodeInfo; michael@0: nsRefPtr newAttr = michael@0: new Attr(this, ni.forget(), EmptyString(), aNsAware); michael@0: mAttributeCache.Put(attr, newAttr); michael@0: node = newAttr; michael@0: } michael@0: michael@0: return node; michael@0: } michael@0: michael@0: Attr* michael@0: nsDOMAttributeMap::NamedGetter(const nsAString& aAttrName, bool& aFound) michael@0: { michael@0: aFound = false; michael@0: NS_ENSURE_TRUE(mContent, nullptr); michael@0: michael@0: nsCOMPtr ni = mContent->GetExistingAttrNameFromQName(aAttrName); michael@0: if (!ni) { michael@0: return nullptr; michael@0: } michael@0: michael@0: aFound = true; michael@0: return GetAttribute(ni, false); michael@0: } michael@0: michael@0: bool michael@0: nsDOMAttributeMap::NameIsEnumerable(const nsAString& aName) michael@0: { michael@0: return true; michael@0: } michael@0: michael@0: Attr* michael@0: nsDOMAttributeMap::GetNamedItem(const nsAString& aAttrName) michael@0: { michael@0: bool dummy; michael@0: return NamedGetter(aAttrName, dummy); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMAttributeMap::GetNamedItem(const nsAString& aAttrName, michael@0: nsIDOMAttr** aAttribute) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aAttribute); michael@0: michael@0: NS_IF_ADDREF(*aAttribute = GetNamedItem(aAttrName)); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMAttributeMap::SetNamedItem(nsIDOMAttr* aAttr, nsIDOMAttr** aReturn) michael@0: { michael@0: Attr* attribute = static_cast(aAttr); michael@0: NS_ENSURE_ARG(attribute); michael@0: michael@0: ErrorResult rv; michael@0: *aReturn = SetNamedItem(*attribute, rv).take(); michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMAttributeMap::SetNamedItemNS(nsIDOMAttr* aAttr, nsIDOMAttr** aReturn) michael@0: { michael@0: Attr* attribute = static_cast(aAttr); michael@0: NS_ENSURE_ARG(attribute); michael@0: michael@0: ErrorResult rv; michael@0: *aReturn = SetNamedItemNS(*attribute, rv).take(); michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsDOMAttributeMap::SetNamedItemInternal(Attr& aAttr, michael@0: bool aWithNS, michael@0: ErrorResult& aError) michael@0: { michael@0: NS_ENSURE_TRUE(mContent, nullptr); michael@0: michael@0: // XXX should check same-origin between mContent and aAttr however michael@0: // nsContentUtils::CheckSameOrigin can't deal with attributenodes yet michael@0: michael@0: // Check that attribute is not owned by somebody else michael@0: nsDOMAttributeMap* owner = aAttr.GetMap(); michael@0: if (owner) { michael@0: if (owner != this) { michael@0: aError.Throw(NS_ERROR_DOM_INUSE_ATTRIBUTE_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: // setting a preexisting attribute is a no-op, just return the same michael@0: // node. michael@0: nsRefPtr attribute = &aAttr; michael@0: return attribute.forget(); michael@0: } michael@0: michael@0: nsresult rv; michael@0: if (mContent->OwnerDoc() != aAttr.OwnerDoc()) { michael@0: nsCOMPtr adoptedNode = michael@0: mContent->OwnerDoc()->AdoptNode(aAttr, aError); michael@0: if (aError.Failed()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: NS_ASSERTION(adoptedNode == &aAttr, "Uh, adopt node changed nodes?"); michael@0: } michael@0: michael@0: // Get nodeinfo and preexisting attribute (if it exists) michael@0: nsAutoString name; michael@0: nsCOMPtr ni; michael@0: michael@0: nsRefPtr attr; michael@0: // SetNamedItemNS() michael@0: if (aWithNS) { michael@0: // Return existing attribute, if present michael@0: ni = aAttr.NodeInfo(); michael@0: michael@0: if (mContent->HasAttr(ni->NamespaceID(), ni->NameAtom())) { michael@0: attr = RemoveAttribute(ni); michael@0: } michael@0: } else { // SetNamedItem() michael@0: aAttr.GetName(name); michael@0: michael@0: // get node-info of old attribute michael@0: ni = mContent->GetExistingAttrNameFromQName(name); michael@0: if (ni) { michael@0: attr = RemoveAttribute(ni); michael@0: } michael@0: else { michael@0: if (mContent->IsInHTMLDocument() && michael@0: mContent->IsHTML()) { michael@0: nsContentUtils::ASCIIToLower(name); michael@0: } michael@0: michael@0: rv = mContent->NodeInfo()->NodeInfoManager()-> michael@0: GetNodeInfo(name, nullptr, kNameSpaceID_None, michael@0: nsIDOMNode::ATTRIBUTE_NODE, getter_AddRefs(ni)); michael@0: if (NS_FAILED(rv)) { michael@0: aError.Throw(rv); michael@0: return nullptr; michael@0: } michael@0: // value is already empty michael@0: } michael@0: } michael@0: michael@0: nsAutoString value; michael@0: aAttr.GetValue(value); michael@0: michael@0: // Add the new attribute to the attribute map before updating michael@0: // its value in the element. @see bug 364413. michael@0: nsAttrKey attrkey(ni->NamespaceID(), ni->NameAtom()); michael@0: mAttributeCache.Put(attrkey, &aAttr); michael@0: aAttr.SetMap(this); michael@0: michael@0: rv = mContent->SetAttr(ni->NamespaceID(), ni->NameAtom(), michael@0: ni->GetPrefixAtom(), value, true); michael@0: if (NS_FAILED(rv)) { michael@0: aError.Throw(rv); michael@0: DropAttribute(ni->NamespaceID(), ni->NameAtom()); michael@0: } michael@0: michael@0: return attr.forget(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMAttributeMap::RemoveNamedItem(const nsAString& aName, michael@0: nsIDOMAttr** aReturn) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aReturn); michael@0: michael@0: ErrorResult rv; michael@0: *aReturn = RemoveNamedItem(aName, rv).take(); michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsDOMAttributeMap::RemoveNamedItem(const nsAString& aName, ErrorResult& aError) michael@0: { michael@0: if (!mContent) { michael@0: aError.Throw(NS_ERROR_DOM_NOT_FOUND_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: nsCOMPtr ni = mContent->GetExistingAttrNameFromQName(aName); michael@0: if (!ni) { michael@0: aError.Throw(NS_ERROR_DOM_NOT_FOUND_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: nsRefPtr attribute = GetAttribute(ni, true); michael@0: michael@0: // This removes the attribute node from the attribute map. michael@0: aError = mContent->UnsetAttr(ni->NamespaceID(), ni->NameAtom(), true); michael@0: return attribute.forget(); michael@0: } michael@0: michael@0: michael@0: Attr* michael@0: nsDOMAttributeMap::IndexedGetter(uint32_t aIndex, bool& aFound) michael@0: { michael@0: aFound = false; michael@0: NS_ENSURE_TRUE(mContent, nullptr); michael@0: michael@0: const nsAttrName* name = mContent->GetAttrNameAt(aIndex); michael@0: NS_ENSURE_TRUE(name, nullptr); michael@0: michael@0: aFound = true; michael@0: // Don't use the nodeinfo even if one exists since it can have the wrong michael@0: // owner document. michael@0: nsCOMPtr ni = mContent->NodeInfo()->NodeInfoManager()-> michael@0: GetNodeInfo(name->LocalName(), name->GetPrefix(), name->NamespaceID(), michael@0: nsIDOMNode::ATTRIBUTE_NODE); michael@0: return GetAttribute(ni, true); michael@0: } michael@0: michael@0: Attr* michael@0: nsDOMAttributeMap::Item(uint32_t aIndex) michael@0: { michael@0: bool dummy; michael@0: return IndexedGetter(aIndex, dummy); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMAttributeMap::Item(uint32_t aIndex, nsIDOMAttr** aReturn) michael@0: { michael@0: NS_IF_ADDREF(*aReturn = Item(aIndex)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: uint32_t michael@0: nsDOMAttributeMap::Length() const michael@0: { michael@0: NS_ENSURE_TRUE(mContent, 0); michael@0: michael@0: return mContent->GetAttrCount(); michael@0: } michael@0: michael@0: nsresult michael@0: nsDOMAttributeMap::GetLength(uint32_t *aLength) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aLength); michael@0: *aLength = Length(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMAttributeMap::GetNamedItemNS(const nsAString& aNamespaceURI, michael@0: const nsAString& aLocalName, michael@0: nsIDOMAttr** aReturn) michael@0: { michael@0: NS_IF_ADDREF(*aReturn = GetNamedItemNS(aNamespaceURI, aLocalName)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: Attr* michael@0: nsDOMAttributeMap::GetNamedItemNS(const nsAString& aNamespaceURI, michael@0: const nsAString& aLocalName) michael@0: { michael@0: nsCOMPtr ni = GetAttrNodeInfo(aNamespaceURI, aLocalName); michael@0: if (!ni) { michael@0: return nullptr; michael@0: } michael@0: michael@0: return GetAttribute(ni, true); michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsDOMAttributeMap::GetAttrNodeInfo(const nsAString& aNamespaceURI, michael@0: const nsAString& aLocalName) michael@0: { michael@0: if (!mContent) { michael@0: return nullptr; michael@0: } michael@0: michael@0: int32_t nameSpaceID = kNameSpaceID_None; michael@0: michael@0: if (!aNamespaceURI.IsEmpty()) { michael@0: nameSpaceID = michael@0: nsContentUtils::NameSpaceManager()->GetNameSpaceID(aNamespaceURI); michael@0: michael@0: if (nameSpaceID == kNameSpaceID_Unknown) { michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: uint32_t i, count = mContent->GetAttrCount(); michael@0: for (i = 0; i < count; ++i) { michael@0: const nsAttrName* name = mContent->GetAttrNameAt(i); michael@0: int32_t attrNS = name->NamespaceID(); michael@0: nsIAtom* nameAtom = name->LocalName(); michael@0: michael@0: if (nameSpaceID == attrNS && michael@0: nameAtom->Equals(aLocalName)) { michael@0: nsCOMPtr ni; michael@0: ni = mContent->NodeInfo()->NodeInfoManager()-> michael@0: GetNodeInfo(nameAtom, name->GetPrefix(), nameSpaceID, michael@0: nsIDOMNode::ATTRIBUTE_NODE); michael@0: michael@0: return ni.forget(); michael@0: } michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMAttributeMap::RemoveNamedItemNS(const nsAString& aNamespaceURI, michael@0: const nsAString& aLocalName, michael@0: nsIDOMAttr** aReturn) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aReturn); michael@0: ErrorResult rv; michael@0: *aReturn = RemoveNamedItemNS(aNamespaceURI, aLocalName, rv).take(); michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsDOMAttributeMap::RemoveNamedItemNS(const nsAString& aNamespaceURI, michael@0: const nsAString& aLocalName, michael@0: ErrorResult& aError) michael@0: { michael@0: nsCOMPtr ni = GetAttrNodeInfo(aNamespaceURI, aLocalName); michael@0: if (!ni) { michael@0: aError.Throw(NS_ERROR_DOM_NOT_FOUND_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: nsRefPtr attr = RemoveAttribute(ni); michael@0: nsINodeInfo* attrNi = attr->NodeInfo(); michael@0: mContent->UnsetAttr(attrNi->NamespaceID(), attrNi->NameAtom(), true); michael@0: michael@0: return attr.forget(); michael@0: } michael@0: michael@0: uint32_t michael@0: nsDOMAttributeMap::Count() const michael@0: { michael@0: return mAttributeCache.Count(); michael@0: } michael@0: michael@0: uint32_t michael@0: nsDOMAttributeMap::Enumerate(AttrCache::EnumReadFunction aFunc, michael@0: void *aUserArg) const michael@0: { michael@0: return mAttributeCache.EnumerateRead(aFunc, aUserArg); michael@0: } michael@0: michael@0: size_t michael@0: AttrCacheSizeEnumerator(const nsAttrKey& aKey, michael@0: const nsRefPtr& aValue, michael@0: MallocSizeOf aMallocSizeOf, michael@0: void* aUserArg) michael@0: { michael@0: return aMallocSizeOf(aValue.get()); michael@0: } michael@0: michael@0: size_t michael@0: nsDOMAttributeMap::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: size_t n = aMallocSizeOf(this); michael@0: n += mAttributeCache.SizeOfExcludingThis(AttrCacheSizeEnumerator, michael@0: aMallocSizeOf); michael@0: michael@0: // NB: mContent is non-owning and thus not counted. michael@0: return n; michael@0: } michael@0: michael@0: /* virtual */ JSObject* michael@0: nsDOMAttributeMap::WrapObject(JSContext* aCx) michael@0: { michael@0: return MozNamedAttrMapBinding::Wrap(aCx, this); michael@0: }