tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsDOMTokenList.cpp (9809B)


      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 /*
      8 * Implementation of DOMTokenList specified by HTML5.
      9 */
     10 
     11 #include "nsDOMTokenList.h"
     12 
     13 #include "mozilla/ErrorResult.h"
     14 #include "mozilla/dom/DOMTokenListBinding.h"
     15 #include "mozilla/dom/Document.h"
     16 #include "mozilla/dom/Element.h"
     17 #include "nsAttrValue.h"
     18 #include "nsAttrValueInlines.h"
     19 #include "nsError.h"
     20 #include "nsHashKeys.h"
     21 #include "nsTHashMap.h"
     22 
     23 using namespace mozilla;
     24 using namespace mozilla::dom;
     25 
     26 nsDOMTokenList::nsDOMTokenList(
     27    Element* aElement, nsAtom* aAttrAtom,
     28    const DOMTokenListSupportedTokenArray aSupportedTokens)
     29    : mElement(aElement),
     30      mAttrAtom(aAttrAtom),
     31      mSupportedTokens(aSupportedTokens) {
     32  // We don't add a reference to our element. If it goes away,
     33  // we'll be told to drop our reference
     34 }
     35 
     36 nsDOMTokenList::~nsDOMTokenList() = default;
     37 
     38 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsDOMTokenList, mElement)
     39 
     40 NS_INTERFACE_MAP_BEGIN(nsDOMTokenList)
     41  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
     42  NS_INTERFACE_MAP_ENTRY(nsISupports)
     43  NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsDOMTokenList)
     44 NS_INTERFACE_MAP_END
     45 
     46 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMTokenList)
     47 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMTokenList)
     48 
     49 const nsAttrValue* nsDOMTokenList::GetParsedAttr() {
     50  if (!mElement) {
     51    return nullptr;
     52  }
     53  return mElement->GetAttrInfo(kNameSpaceID_None, mAttrAtom).mValue;
     54 }
     55 
     56 static void RemoveDuplicates(const nsAttrValue* aAttr) {
     57  if (!aAttr || aAttr->Type() != nsAttrValue::eAtomArray) {
     58    return;
     59  }
     60  const_cast<nsAttrValue*>(aAttr)->RemoveDuplicatesFromAtomArray();
     61 }
     62 
     63 uint32_t nsDOMTokenList::Length() {
     64  const nsAttrValue* attr = GetParsedAttr();
     65  if (!attr) {
     66    return 0;
     67  }
     68 
     69  RemoveDuplicates(attr);
     70  return attr->GetAtomCount();
     71 }
     72 
     73 void nsDOMTokenList::IndexedGetter(uint32_t aIndex, bool& aFound,
     74                                   nsAString& aResult) {
     75  const nsAttrValue* attr = GetParsedAttr();
     76 
     77  if (!attr || aIndex >= static_cast<uint32_t>(attr->GetAtomCount())) {
     78    aFound = false;
     79    return;
     80  }
     81 
     82  RemoveDuplicates(attr);
     83 
     84  if (attr && aIndex < static_cast<uint32_t>(attr->GetAtomCount())) {
     85    aFound = true;
     86    attr->AtomAt(aIndex)->ToString(aResult);
     87  } else {
     88    aFound = false;
     89  }
     90 }
     91 
     92 void nsDOMTokenList::GetValue(nsAString& aResult) {
     93  if (!mElement) {
     94    aResult.Truncate();
     95    return;
     96  }
     97 
     98  mElement->GetAttr(mAttrAtom, aResult);
     99 }
    100 
    101 void nsDOMTokenList::SetValue(const nsAString& aValue, ErrorResult& rv) {
    102  if (!mElement) {
    103    return;
    104  }
    105 
    106  rv = mElement->SetAttr(kNameSpaceID_None, mAttrAtom, aValue, true);
    107 }
    108 
    109 void nsDOMTokenList::CheckToken(const nsAString& aToken, ErrorResult& aRv) {
    110  if (aToken.IsEmpty()) {
    111    return aRv.ThrowSyntaxError("The empty string is not a valid token.");
    112  }
    113 
    114  nsAString::const_iterator iter, end;
    115  aToken.BeginReading(iter);
    116  aToken.EndReading(end);
    117 
    118  while (iter != end) {
    119    if (nsContentUtils::IsHTMLWhitespace(*iter)) {
    120      return aRv.ThrowInvalidCharacterError(
    121          "The token can not contain whitespace.");
    122    }
    123    ++iter;
    124  }
    125 }
    126 
    127 void nsDOMTokenList::CheckTokens(const nsTArray<nsString>& aTokens,
    128                                 ErrorResult& aRv) {
    129  for (uint32_t i = 0, l = aTokens.Length(); i < l; ++i) {
    130    CheckToken(aTokens[i], aRv);
    131    if (aRv.Failed()) {
    132      return;
    133    }
    134  }
    135 }
    136 
    137 bool nsDOMTokenList::Contains(const nsAString& aToken) {
    138  const nsAttrValue* attr = GetParsedAttr();
    139  return attr && attr->Contains(aToken);
    140 }
    141 
    142 void nsDOMTokenList::AddInternal(const nsAttrValue* aAttr,
    143                                 const nsTArray<nsString>& aTokens) {
    144  if (!mElement) {
    145    return;
    146  }
    147 
    148  nsAutoString resultStr;
    149 
    150  if (aAttr) {
    151    RemoveDuplicates(aAttr);
    152    for (uint32_t i = 0; i < aAttr->GetAtomCount(); i++) {
    153      if (i != 0) {
    154        resultStr.AppendLiteral(" ");
    155      }
    156      resultStr.Append(nsDependentAtomString(aAttr->AtomAt(i)));
    157    }
    158  }
    159 
    160  AutoTArray<nsString, 10> addedClasses;
    161 
    162  for (uint32_t i = 0, l = aTokens.Length(); i < l; ++i) {
    163    const nsString& aToken = aTokens[i];
    164 
    165    if ((aAttr && aAttr->Contains(aToken)) || addedClasses.Contains(aToken)) {
    166      continue;
    167    }
    168 
    169    if (!resultStr.IsEmpty()) {
    170      resultStr.Append(' ');
    171    }
    172    resultStr.Append(aToken);
    173 
    174    addedClasses.AppendElement(aToken);
    175  }
    176 
    177  mElement->SetAttr(kNameSpaceID_None, mAttrAtom, resultStr, true);
    178 }
    179 
    180 void nsDOMTokenList::Add(const nsTArray<nsString>& aTokens,
    181                         ErrorResult& aError) {
    182  CheckTokens(aTokens, aError);
    183  if (aError.Failed()) {
    184    return;
    185  }
    186 
    187  const nsAttrValue* attr = GetParsedAttr();
    188  AddInternal(attr, aTokens);
    189 }
    190 
    191 void nsDOMTokenList::Add(const nsAString& aToken, ErrorResult& aError) {
    192  AutoTArray<nsString, 1> tokens;
    193  tokens.AppendElement(aToken);
    194  Add(tokens, aError);
    195 }
    196 
    197 void nsDOMTokenList::RemoveInternal(const nsAttrValue* aAttr,
    198                                    const nsTArray<nsString>& aTokens) {
    199  MOZ_ASSERT(aAttr, "Need an attribute");
    200 
    201  RemoveDuplicates(aAttr);
    202 
    203  nsAutoString resultStr;
    204  for (uint32_t i = 0; i < aAttr->GetAtomCount(); i++) {
    205    if (aTokens.Contains(nsDependentAtomString(aAttr->AtomAt(i)))) {
    206      continue;
    207    }
    208    if (!resultStr.IsEmpty()) {
    209      resultStr.AppendLiteral(" ");
    210    }
    211    resultStr.Append(nsDependentAtomString(aAttr->AtomAt(i)));
    212  }
    213 
    214  mElement->SetAttr(kNameSpaceID_None, mAttrAtom, resultStr, true);
    215 }
    216 
    217 void nsDOMTokenList::Remove(const nsTArray<nsString>& aTokens,
    218                            ErrorResult& aError) {
    219  CheckTokens(aTokens, aError);
    220  if (aError.Failed()) {
    221    return;
    222  }
    223 
    224  const nsAttrValue* attr = GetParsedAttr();
    225  if (!attr) {
    226    return;
    227  }
    228 
    229  RemoveInternal(attr, aTokens);
    230 }
    231 
    232 void nsDOMTokenList::Remove(const nsAString& aToken, ErrorResult& aError) {
    233  AutoTArray<nsString, 1> tokens;
    234  tokens.AppendElement(aToken);
    235  Remove(tokens, aError);
    236 }
    237 
    238 bool nsDOMTokenList::Toggle(const nsAString& aToken,
    239                            const Optional<bool>& aForce, ErrorResult& aError) {
    240  CheckToken(aToken, aError);
    241  if (aError.Failed()) {
    242    return false;
    243  }
    244 
    245  const nsAttrValue* attr = GetParsedAttr();
    246  const bool forceOn = aForce.WasPassed() && aForce.Value();
    247  const bool forceOff = aForce.WasPassed() && !aForce.Value();
    248 
    249  bool isPresent = attr && attr->Contains(aToken);
    250  AutoTArray<nsString, 1> tokens;
    251  (*tokens.AppendElement()).Rebind(aToken.Data(), aToken.Length());
    252 
    253  if (isPresent) {
    254    if (!forceOn) {
    255      RemoveInternal(attr, tokens);
    256      isPresent = false;
    257    }
    258  } else {
    259    if (!forceOff) {
    260      AddInternal(attr, tokens);
    261      isPresent = true;
    262    }
    263  }
    264 
    265  return isPresent;
    266 }
    267 
    268 bool nsDOMTokenList::Replace(const nsAString& aToken,
    269                             const nsAString& aNewToken, ErrorResult& aError) {
    270  // Doing this here instead of using `CheckToken` because if aToken had invalid
    271  // characters, and aNewToken is empty, the returned error should be a
    272  // SyntaxError, not an InvalidCharacterError.
    273  if (aNewToken.IsEmpty()) {
    274    aError.ThrowSyntaxError("The empty string is not a valid token.");
    275    return false;
    276  }
    277 
    278  CheckToken(aToken, aError);
    279  if (aError.Failed()) {
    280    return false;
    281  }
    282 
    283  CheckToken(aNewToken, aError);
    284  if (aError.Failed()) {
    285    return false;
    286  }
    287 
    288  const nsAttrValue* attr = GetParsedAttr();
    289  if (!attr) {
    290    return false;
    291  }
    292 
    293  return ReplaceInternal(attr, aToken, aNewToken);
    294 }
    295 
    296 bool nsDOMTokenList::ReplaceInternal(const nsAttrValue* aAttr,
    297                                     const nsAString& aToken,
    298                                     const nsAString& aNewToken) {
    299  RemoveDuplicates(aAttr);
    300 
    301  // Trying to do a single pass here leads to really complicated code.  Just do
    302  // the simple thing.
    303  bool haveOld = false;
    304  for (uint32_t i = 0; i < aAttr->GetAtomCount(); ++i) {
    305    if (aAttr->AtomAt(i)->Equals(aToken)) {
    306      haveOld = true;
    307      break;
    308    }
    309  }
    310  if (!haveOld) {
    311    // Make sure to not touch the attribute value in this case.
    312    return false;
    313  }
    314 
    315  bool sawIt = false;
    316  nsAutoString resultStr;
    317  for (uint32_t i = 0; i < aAttr->GetAtomCount(); i++) {
    318    if (aAttr->AtomAt(i)->Equals(aToken) ||
    319        aAttr->AtomAt(i)->Equals(aNewToken)) {
    320      if (sawIt) {
    321        // We keep only the first
    322        continue;
    323      }
    324      sawIt = true;
    325      if (!resultStr.IsEmpty()) {
    326        resultStr.AppendLiteral(" ");
    327      }
    328      resultStr.Append(aNewToken);
    329      continue;
    330    }
    331    if (!resultStr.IsEmpty()) {
    332      resultStr.AppendLiteral(" ");
    333    }
    334    resultStr.Append(nsDependentAtomString(aAttr->AtomAt(i)));
    335  }
    336 
    337  MOZ_ASSERT(sawIt, "How could we not have found our token this time?");
    338  mElement->SetAttr(kNameSpaceID_None, mAttrAtom, resultStr, true);
    339  return true;
    340 }
    341 
    342 bool nsDOMTokenList::Supports(const nsAString& aToken, ErrorResult& aError) {
    343  if (!mSupportedTokens) {
    344    aError.ThrowTypeError<MSG_TOKENLIST_NO_SUPPORTED_TOKENS>(
    345        NS_ConvertUTF16toUTF8(mElement->LocalName()),
    346        NS_ConvertUTF16toUTF8(nsDependentAtomString(mAttrAtom)));
    347    return false;
    348  }
    349 
    350  for (DOMTokenListSupportedToken* supportedToken = mSupportedTokens;
    351       *supportedToken; ++supportedToken) {
    352    if (aToken.LowerCaseEqualsASCII(*supportedToken)) {
    353      return true;
    354    }
    355  }
    356 
    357  return false;
    358 }
    359 
    360 DocGroup* nsDOMTokenList::GetDocGroup() const {
    361  return mElement ? mElement->OwnerDoc()->GetDocGroup() : nullptr;
    362 }
    363 
    364 JSObject* nsDOMTokenList::WrapObject(JSContext* cx,
    365                                     JS::Handle<JSObject*> aGivenProto) {
    366  return DOMTokenList_Binding::Wrap(cx, this, aGivenProto);
    367 }