diff options
Diffstat (limited to '')
-rw-r--r-- | dom/base/nsDOMTokenList.cpp | 395 |
1 files changed, 395 insertions, 0 deletions
diff --git a/dom/base/nsDOMTokenList.cpp b/dom/base/nsDOMTokenList.cpp new file mode 100644 index 0000000000..eed8fda232 --- /dev/null +++ b/dom/base/nsDOMTokenList.cpp @@ -0,0 +1,395 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Implementation of DOMTokenList specified by HTML5. + */ + +#include "nsDOMTokenList.h" +#include "nsAttrValue.h" +#include "nsAttrValueInlines.h" +#include "nsDataHashtable.h" +#include "nsError.h" +#include "nsHashKeys.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/DOMTokenListBinding.h" +#include "mozilla/BloomFilter.h" +#include "mozilla/ErrorResult.h" + +using namespace mozilla; +using namespace mozilla::dom; + +nsDOMTokenList::nsDOMTokenList( + Element* aElement, nsAtom* aAttrAtom, + const DOMTokenListSupportedTokenArray aSupportedTokens) + : mElement(aElement), + mAttrAtom(aAttrAtom), + mSupportedTokens(aSupportedTokens) { + // We don't add a reference to our element. If it goes away, + // we'll be told to drop our reference +} + +nsDOMTokenList::~nsDOMTokenList() = default; + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsDOMTokenList, mElement) + +NS_INTERFACE_MAP_BEGIN(nsDOMTokenList) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsDOMTokenList) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMTokenList) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMTokenList) + +const nsAttrValue* nsDOMTokenList::GetParsedAttr() { + if (!mElement) { + return nullptr; + } + return mElement->GetAttrInfo(kNameSpaceID_None, mAttrAtom).mValue; +} + +static void RemoveDuplicatesInternal(AtomArray* aArray, uint32_t aStart) { + nsDataHashtable<nsPtrHashKey<nsAtom>, bool> tokens; + + for (uint32_t i = 0; i < aArray->Length(); i++) { + nsAtom* atom = aArray->ElementAt(i); + // No need to check the hashtable below aStart + if (i >= aStart && tokens.Get(atom)) { + aArray->RemoveElementAt(i); + i--; + } else { + tokens.Put(atom, true); + } + } +} + +void nsDOMTokenList::RemoveDuplicates(const nsAttrValue* aAttr) { + if (!aAttr || aAttr->Type() != nsAttrValue::eAtomArray) { + return; + } + + BloomFilter<8, nsAtom> filter; + AtomArray* array = aAttr->GetAtomArrayValue(); + for (uint32_t i = 0; i < array->Length(); i++) { + nsAtom* atom = array->ElementAt(i); + if (filter.mightContain(atom)) { + // Start again, with a hashtable + RemoveDuplicatesInternal(array, i); + return; + } else { + filter.add(atom); + } + } +} + +uint32_t nsDOMTokenList::Length() { + const nsAttrValue* attr = GetParsedAttr(); + if (!attr) { + return 0; + } + + RemoveDuplicates(attr); + return attr->GetAtomCount(); +} + +void nsDOMTokenList::IndexedGetter(uint32_t aIndex, bool& aFound, + nsAString& aResult) { + const nsAttrValue* attr = GetParsedAttr(); + + if (!attr || aIndex >= static_cast<uint32_t>(attr->GetAtomCount())) { + aFound = false; + return; + } + + RemoveDuplicates(attr); + + if (attr && aIndex < static_cast<uint32_t>(attr->GetAtomCount())) { + aFound = true; + attr->AtomAt(aIndex)->ToString(aResult); + } else { + aFound = false; + } +} + +void nsDOMTokenList::GetValue(nsAString& aResult) { + if (!mElement) { + aResult.Truncate(); + return; + } + + mElement->GetAttr(kNameSpaceID_None, mAttrAtom, aResult); +} + +void nsDOMTokenList::SetValue(const nsAString& aValue, ErrorResult& rv) { + if (!mElement) { + return; + } + + rv = mElement->SetAttr(kNameSpaceID_None, mAttrAtom, aValue, true); +} + +nsresult nsDOMTokenList::CheckToken(const nsAString& aStr) { + if (aStr.IsEmpty()) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + + nsAString::const_iterator iter, end; + aStr.BeginReading(iter); + aStr.EndReading(end); + + while (iter != end) { + if (nsContentUtils::IsHTMLWhitespace(*iter)) + return NS_ERROR_DOM_INVALID_CHARACTER_ERR; + ++iter; + } + + return NS_OK; +} + +nsresult nsDOMTokenList::CheckTokens(const nsTArray<nsString>& aTokens) { + for (uint32_t i = 0, l = aTokens.Length(); i < l; ++i) { + nsresult rv = CheckToken(aTokens[i]); + if (NS_FAILED(rv)) { + return rv; + } + } + + return NS_OK; +} + +bool nsDOMTokenList::Contains(const nsAString& aToken) { + const nsAttrValue* attr = GetParsedAttr(); + return attr && attr->Contains(aToken); +} + +void nsDOMTokenList::AddInternal(const nsAttrValue* aAttr, + const nsTArray<nsString>& aTokens) { + if (!mElement) { + return; + } + + nsAutoString resultStr; + + if (aAttr) { + RemoveDuplicates(aAttr); + for (uint32_t i = 0; i < aAttr->GetAtomCount(); i++) { + if (i != 0) { + resultStr.AppendLiteral(" "); + } + resultStr.Append(nsDependentAtomString(aAttr->AtomAt(i))); + } + } + + AutoTArray<nsString, 10> addedClasses; + + for (uint32_t i = 0, l = aTokens.Length(); i < l; ++i) { + const nsString& aToken = aTokens[i]; + + if ((aAttr && aAttr->Contains(aToken)) || addedClasses.Contains(aToken)) { + continue; + } + + if (!resultStr.IsEmpty()) { + resultStr.Append(' '); + } + resultStr.Append(aToken); + + addedClasses.AppendElement(aToken); + } + + mElement->SetAttr(kNameSpaceID_None, mAttrAtom, resultStr, true); +} + +void nsDOMTokenList::Add(const nsTArray<nsString>& aTokens, + ErrorResult& aError) { + aError = CheckTokens(aTokens); + if (aError.Failed()) { + return; + } + + const nsAttrValue* attr = GetParsedAttr(); + AddInternal(attr, aTokens); +} + +void nsDOMTokenList::Add(const nsAString& aToken, ErrorResult& aError) { + AutoTArray<nsString, 1> tokens; + tokens.AppendElement(aToken); + Add(tokens, aError); +} + +void nsDOMTokenList::RemoveInternal(const nsAttrValue* aAttr, + const nsTArray<nsString>& aTokens) { + MOZ_ASSERT(aAttr, "Need an attribute"); + + RemoveDuplicates(aAttr); + + nsAutoString resultStr; + for (uint32_t i = 0; i < aAttr->GetAtomCount(); i++) { + if (aTokens.Contains(nsDependentAtomString(aAttr->AtomAt(i)))) { + continue; + } + if (!resultStr.IsEmpty()) { + resultStr.AppendLiteral(" "); + } + resultStr.Append(nsDependentAtomString(aAttr->AtomAt(i))); + } + + mElement->SetAttr(kNameSpaceID_None, mAttrAtom, resultStr, true); +} + +void nsDOMTokenList::Remove(const nsTArray<nsString>& aTokens, + ErrorResult& aError) { + aError = CheckTokens(aTokens); + if (aError.Failed()) { + return; + } + + const nsAttrValue* attr = GetParsedAttr(); + if (!attr) { + return; + } + + RemoveInternal(attr, aTokens); +} + +void nsDOMTokenList::Remove(const nsAString& aToken, ErrorResult& aError) { + AutoTArray<nsString, 1> tokens; + tokens.AppendElement(aToken); + Remove(tokens, aError); +} + +bool nsDOMTokenList::Toggle(const nsAString& aToken, + const Optional<bool>& aForce, ErrorResult& aError) { + aError = CheckToken(aToken); + if (aError.Failed()) { + return false; + } + + const nsAttrValue* attr = GetParsedAttr(); + const bool forceOn = aForce.WasPassed() && aForce.Value(); + const bool forceOff = aForce.WasPassed() && !aForce.Value(); + + bool isPresent = attr && attr->Contains(aToken); + AutoTArray<nsString, 1> tokens; + (*tokens.AppendElement()).Rebind(aToken.Data(), aToken.Length()); + + if (isPresent) { + if (!forceOn) { + RemoveInternal(attr, tokens); + isPresent = false; + } + } else { + if (!forceOff) { + AddInternal(attr, tokens); + isPresent = true; + } + } + + return isPresent; +} + +bool nsDOMTokenList::Replace(const nsAString& aToken, + const nsAString& aNewToken, ErrorResult& aError) { + // Doing this here instead of using `CheckToken` because if aToken had invalid + // characters, and aNewToken is empty, the returned error should be a + // SyntaxError, not an InvalidCharacterError. + if (aNewToken.IsEmpty()) { + aError.Throw(NS_ERROR_DOM_SYNTAX_ERR); + return false; + } + + aError = CheckToken(aToken); + if (aError.Failed()) { + return false; + } + + aError = CheckToken(aNewToken); + if (aError.Failed()) { + return false; + } + + const nsAttrValue* attr = GetParsedAttr(); + if (!attr) { + return false; + } + + return ReplaceInternal(attr, aToken, aNewToken); +} + +bool nsDOMTokenList::ReplaceInternal(const nsAttrValue* aAttr, + const nsAString& aToken, + const nsAString& aNewToken) { + RemoveDuplicates(aAttr); + + // Trying to do a single pass here leads to really complicated code. Just do + // the simple thing. + bool haveOld = false; + for (uint32_t i = 0; i < aAttr->GetAtomCount(); ++i) { + if (aAttr->AtomAt(i)->Equals(aToken)) { + haveOld = true; + break; + } + } + if (!haveOld) { + // Make sure to not touch the attribute value in this case. + return false; + } + + bool sawIt = false; + nsAutoString resultStr; + for (uint32_t i = 0; i < aAttr->GetAtomCount(); i++) { + if (aAttr->AtomAt(i)->Equals(aToken) || + aAttr->AtomAt(i)->Equals(aNewToken)) { + if (sawIt) { + // We keep only the first + continue; + } + sawIt = true; + if (!resultStr.IsEmpty()) { + resultStr.AppendLiteral(" "); + } + resultStr.Append(aNewToken); + continue; + } + if (!resultStr.IsEmpty()) { + resultStr.AppendLiteral(" "); + } + resultStr.Append(nsDependentAtomString(aAttr->AtomAt(i))); + } + + MOZ_ASSERT(sawIt, "How could we not have found our token this time?"); + mElement->SetAttr(kNameSpaceID_None, mAttrAtom, resultStr, true); + return true; +} + +bool nsDOMTokenList::Supports(const nsAString& aToken, ErrorResult& aError) { + if (!mSupportedTokens) { + aError.ThrowTypeError<MSG_TOKENLIST_NO_SUPPORTED_TOKENS>( + NS_ConvertUTF16toUTF8(mElement->LocalName()), + NS_ConvertUTF16toUTF8(nsDependentAtomString(mAttrAtom))); + return false; + } + + for (DOMTokenListSupportedToken* supportedToken = mSupportedTokens; + *supportedToken; ++supportedToken) { + if (aToken.LowerCaseEqualsASCII(*supportedToken)) { + return true; + } + } + + return false; +} + +DocGroup* nsDOMTokenList::GetDocGroup() const { + return mElement ? mElement->OwnerDoc()->GetDocGroup() : nullptr; +} + +JSObject* nsDOMTokenList::WrapObject(JSContext* cx, + JS::Handle<JSObject*> aGivenProto) { + return DOMTokenList_Binding::Wrap(cx, this, aGivenProto); +} |