diff options
Diffstat (limited to 'dom/base/nsDOMTokenList.cpp')
-rw-r--r-- | dom/base/nsDOMTokenList.cpp | 374 |
1 files changed, 374 insertions, 0 deletions
diff --git a/dom/base/nsDOMTokenList.cpp b/dom/base/nsDOMTokenList.cpp new file mode 100644 index 000000000..39ff60e12 --- /dev/null +++ b/dom/base/nsDOMTokenList.cpp @@ -0,0 +1,374 @@ +/* -*- 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 "nsError.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/DOMTokenListBinding.h" +#include "mozilla/ErrorResult.h" + +using namespace mozilla; +using namespace mozilla::dom; + +nsDOMTokenList::nsDOMTokenList(Element* aElement, nsIAtom* 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() { } + +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; +} + +uint32_t +nsDOMTokenList::Length() +{ + const nsAttrValue* attr = GetParsedAttr(); + if (!attr) { + return 0; + } + + 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 = true; + attr->AtomAt(aIndex)->ToString(aResult); + } else { + aFound = false; + } +} + +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) { + aAttr->ToString(resultStr); + } + + bool oneWasAdded = false; + 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 (oneWasAdded || + (!resultStr.IsEmpty() && + !nsContentUtils::IsHTMLWhitespace(resultStr.Last()))) { + resultStr.Append(' '); + resultStr.Append(aToken); + } else { + resultStr.Append(aToken); + } + + oneWasAdded = true; + 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"); + + nsAutoString input; + aAttr->ToString(input); + + WhitespaceTokenizer tokenizer(input); + nsAutoString output; + + while (tokenizer.hasMoreTokens()) { + auto& currentToken = tokenizer.nextToken(); + if (!aTokens.Contains(currentToken)) { + if (!output.IsEmpty()) { + output.Append(char16_t(' ')); + } + output.Append(currentToken); + } + } + + mElement->SetAttr(kNameSpaceID_None, mAttrAtom, output, 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; +} + +void +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; + } + + aError = CheckToken(aToken); + if (aError.Failed()) { + return; + } + + aError = CheckToken(aNewToken); + if (aError.Failed()) { + return; + } + + const nsAttrValue* attr = GetParsedAttr(); + if (!attr) { + return; + } + + ReplaceInternal(attr, aToken, aNewToken); +} + +void +nsDOMTokenList::ReplaceInternal(const nsAttrValue* aAttr, + const nsAString& aToken, + const nsAString& aNewToken) +{ + nsAutoString attribute; + aAttr->ToString(attribute); + + nsAutoString result; + WhitespaceTokenizer tokenizer(attribute); + + bool sawIt = false; + while (tokenizer.hasMoreTokens()) { + auto currentToken = tokenizer.nextToken(); + if (currentToken.Equals(aToken) || currentToken.Equals(aNewToken)) { + if (!sawIt) { + sawIt = true; + if (!result.IsEmpty()) { + result.Append(char16_t(' ')); + } + result.Append(aNewToken); + } + } else { + if (!result.IsEmpty()) { + result.Append(char16_t(' ')); + } + result.Append(currentToken); + } + } + + if (sawIt) { + mElement->SetAttr(kNameSpaceID_None, mAttrAtom, result, true); + } +} + +bool +nsDOMTokenList::Supports(const nsAString& aToken, + ErrorResult& aError) +{ + if (!mSupportedTokens) { + aError.ThrowTypeError<MSG_TOKENLIST_NO_SUPPORTED_TOKENS>( + mElement->LocalName(), + nsDependentAtomString(mAttrAtom)); + return false; + } + + for (DOMTokenListSupportedToken* supportedToken = mSupportedTokens; + *supportedToken; + ++supportedToken) { + if (aToken.LowerCaseEqualsASCII(*supportedToken)) { + return true; + } + } + + return false; +} + +void +nsDOMTokenList::Stringify(nsAString& aResult) +{ + if (!mElement) { + aResult.Truncate(); + return; + } + + mElement->GetAttr(kNameSpaceID_None, mAttrAtom, aResult); +} + +JSObject* +nsDOMTokenList::WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto) +{ + return DOMTokenListBinding::Wrap(cx, this, aGivenProto); +} + |