nsDOMStringMap.cpp 7.8 KB

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