nsDOMStringMap.cpp (7759B)
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 #include "nsDOMStringMap.h" 8 9 #include "jsapi.h" 10 #include "mozilla/dom/DOMStringMapBinding.h" 11 #include "nsContentUtils.h" 12 #include "nsError.h" 13 #include "nsGenericHTMLElement.h" 14 15 using namespace mozilla; 16 using namespace mozilla::dom; 17 18 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(nsDOMStringMap) 19 20 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsDOMStringMap) 21 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElement) 22 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 23 24 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDOMStringMap) 25 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER 26 // Check that mElement exists in case the unlink code is run more than once. 27 if (tmp->mElement) { 28 // Call back to element to null out weak reference to this object. 29 tmp->mElement->ClearDataset(); 30 tmp->mElement->RemoveMutationObserver(tmp); 31 tmp->mElement = nullptr; 32 } 33 tmp->mExpandoAndGeneration.OwnerUnlinked(); 34 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 35 36 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMStringMap) 37 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 38 NS_INTERFACE_MAP_ENTRY(nsIMutationObserver) 39 NS_INTERFACE_MAP_ENTRY(nsISupports) 40 NS_INTERFACE_MAP_END 41 42 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMStringMap) 43 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMStringMap) 44 45 nsDOMStringMap::nsDOMStringMap(Element* aElement) 46 : mElement(aElement), mRemovingProp(false) { 47 mElement->AddMutationObserver(this); 48 } 49 50 nsDOMStringMap::~nsDOMStringMap() { 51 // Check if element still exists, may have been unlinked by cycle collector. 52 if (mElement) { 53 // Call back to element to null out weak reference to this object. 54 mElement->ClearDataset(); 55 mElement->RemoveMutationObserver(this); 56 } 57 } 58 59 DocGroup* nsDOMStringMap::GetDocGroup() const { 60 return mElement ? mElement->GetDocGroup() : nullptr; 61 } 62 63 /* virtual */ 64 JSObject* nsDOMStringMap::WrapObject(JSContext* cx, 65 JS::Handle<JSObject*> aGivenProto) { 66 return DOMStringMap_Binding::Wrap(cx, this, aGivenProto); 67 } 68 69 void nsDOMStringMap::NamedGetter(const nsAString& aProp, bool& found, 70 DOMString& aResult) const { 71 nsAutoString attr; 72 73 if (!DataPropToAttr(aProp, attr)) { 74 found = false; 75 return; 76 } 77 78 found = mElement->GetAttr(attr, aResult); 79 } 80 81 void nsDOMStringMap::NamedSetter(const nsAString& aProp, 82 const nsAString& aValue, ErrorResult& rv) { 83 nsAutoString attr; 84 if (!DataPropToAttr(aProp, attr)) { 85 rv.Throw(NS_ERROR_DOM_SYNTAX_ERR); 86 return; 87 } 88 89 nsresult res = nsContentUtils::CheckQName(attr, false); 90 if (NS_FAILED(res)) { 91 rv.Throw(res); 92 return; 93 } 94 95 RefPtr<nsAtom> attrAtom = NS_Atomize(attr); 96 MOZ_ASSERT(attrAtom, "Should be infallible"); 97 98 res = mElement->SetAttr(kNameSpaceID_None, attrAtom, aValue, true); 99 if (NS_FAILED(res)) { 100 rv.Throw(res); 101 } 102 } 103 104 void nsDOMStringMap::NamedDeleter(const nsAString& aProp, bool& found) { 105 // Currently removing property, attribute is already removed. 106 if (mRemovingProp) { 107 found = false; 108 return; 109 } 110 111 nsAutoString attr; 112 if (!DataPropToAttr(aProp, attr)) { 113 found = false; 114 return; 115 } 116 117 RefPtr<nsAtom> attrAtom = NS_Atomize(attr); 118 MOZ_ASSERT(attrAtom, "Should be infallible"); 119 120 found = mElement->HasAttr(attrAtom); 121 122 if (found) { 123 mRemovingProp = true; 124 mElement->UnsetAttr(kNameSpaceID_None, attrAtom, true); 125 mRemovingProp = false; 126 } 127 } 128 129 void nsDOMStringMap::GetSupportedNames(nsTArray<nsString>& aNames) { 130 uint32_t attrCount = mElement->GetAttrCount(); 131 132 // Iterate through all the attributes and add property 133 // names corresponding to data attributes to return array. 134 for (uint32_t i = 0; i < attrCount; ++i) { 135 const nsAttrName* attrName = mElement->GetAttrNameAt(i); 136 // Skip the ones that are not in the null namespace 137 if (attrName->NamespaceID() != kNameSpaceID_None) { 138 continue; 139 } 140 141 nsAutoString prop; 142 if (!AttrToDataProp(nsDependentAtomString(attrName->LocalName()), prop)) { 143 continue; 144 } 145 146 aNames.AppendElement(prop); 147 } 148 } 149 150 /** 151 * Converts a dataset property name to the corresponding data attribute name. 152 * (ex. aBigFish to data-a-big-fish). 153 */ 154 bool nsDOMStringMap::DataPropToAttr(const nsAString& aProp, 155 nsAutoString& aResult) { 156 // aResult is an autostring, so don't worry about setting its capacity: 157 // SetCapacity is slow even when it's a no-op and we already have enough 158 // storage there for most cases, probably. 159 aResult.AppendLiteral("data-"); 160 161 // Iterate property by character to form attribute name. 162 // Return syntax error if there is a sequence of "-" followed by a character 163 // in the range "a" to "z". 164 // Replace capital characters with "-" followed by lower case character. 165 // Otherwise, simply append character to attribute name. 166 const char16_t* start = aProp.BeginReading(); 167 const char16_t* end = aProp.EndReading(); 168 const char16_t* cur = start; 169 for (; cur < end; ++cur) { 170 const char16_t* next = cur + 1; 171 if (char16_t('-') == *cur && next < end && char16_t('a') <= *next && 172 *next <= char16_t('z')) { 173 // Syntax error if character following "-" is in range "a" to "z". 174 return false; 175 } 176 177 if (char16_t('A') <= *cur && *cur <= char16_t('Z')) { 178 // Append the characters in the range [start, cur) 179 aResult.Append(start, cur - start); 180 // Uncamel-case characters in the range of "A" to "Z". 181 aResult.Append(char16_t('-')); 182 aResult.Append(*cur - 'A' + 'a'); 183 start = next; // We've already appended the thing at *cur 184 } 185 } 186 187 aResult.Append(start, cur - start); 188 189 return true; 190 } 191 192 /** 193 * Converts a data attribute name to the corresponding dataset property name. 194 * (ex. data-a-big-fish to aBigFish). 195 */ 196 bool nsDOMStringMap::AttrToDataProp(const nsAString& aAttr, 197 nsAutoString& aResult) { 198 // If the attribute name does not begin with "data-" then it can not be 199 // a data attribute. 200 if (!StringBeginsWith(aAttr, u"data-"_ns)) { 201 return false; 202 } 203 204 // Start reading attribute from first character after "data-". 205 const char16_t* cur = aAttr.BeginReading() + 5; 206 const char16_t* end = aAttr.EndReading(); 207 208 // Don't try to mess with aResult's capacity: the probably-no-op SetCapacity() 209 // call is not that fast. 210 211 // Iterate through attrName by character to form property name. 212 // If there is a sequence of "-" followed by a character in the range "a" to 213 // "z" then replace with upper case letter. 214 // Otherwise append character to property name. 215 for (; cur < end; ++cur) { 216 const char16_t* next = cur + 1; 217 if (char16_t('-') == *cur && next < end && char16_t('a') <= *next && 218 *next <= char16_t('z')) { 219 // Upper case the lower case letters that follow a "-". 220 aResult.Append(*next - 'a' + 'A'); 221 // Consume character to account for "-" character. 222 ++cur; 223 } else { 224 // Simply append character if camel case is not necessary. 225 aResult.Append(*cur); 226 } 227 } 228 229 return true; 230 } 231 232 void nsDOMStringMap::AttributeChanged(Element* aElement, int32_t aNameSpaceID, 233 nsAtom* aAttribute, AttrModType aModType, 234 const nsAttrValue* aOldValue) { 235 if (IsAdditionOrRemoval(aModType) && aNameSpaceID == kNameSpaceID_None && 236 StringBeginsWith(nsDependentAtomString(aAttribute), u"data-"_ns)) { 237 ++mExpandoAndGeneration.generation; 238 } 239 }