michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * Implementation of DOMTokenList specified by HTML5. michael@0: */ michael@0: michael@0: #include "nsDOMTokenList.h" michael@0: michael@0: #include "nsAttrValue.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsError.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "mozilla/dom/DOMTokenListBinding.h" michael@0: #include "mozilla/ErrorResult.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: nsDOMTokenList::nsDOMTokenList(Element* aElement, nsIAtom* aAttrAtom) michael@0: : mElement(aElement), michael@0: mAttrAtom(aAttrAtom) michael@0: { michael@0: // We don't add a reference to our element. If it goes away, michael@0: // we'll be told to drop our reference michael@0: SetIsDOMBinding(); michael@0: } michael@0: michael@0: nsDOMTokenList::~nsDOMTokenList() { } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_1(nsDOMTokenList, mElement) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(nsDOMTokenList) michael@0: NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY michael@0: NS_INTERFACE_MAP_ENTRY(nsISupports) michael@0: NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsDOMTokenList) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMTokenList) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMTokenList) michael@0: michael@0: const nsAttrValue* michael@0: nsDOMTokenList::GetParsedAttr() michael@0: { michael@0: if (!mElement) { michael@0: return nullptr; michael@0: } michael@0: return mElement->GetAttrInfo(kNameSpaceID_None, mAttrAtom).mValue; michael@0: } michael@0: michael@0: uint32_t michael@0: nsDOMTokenList::Length() michael@0: { michael@0: const nsAttrValue* attr = GetParsedAttr(); michael@0: if (!attr) { michael@0: return 0; michael@0: } michael@0: michael@0: return attr->GetAtomCount(); michael@0: } michael@0: michael@0: void michael@0: nsDOMTokenList::IndexedGetter(uint32_t aIndex, bool& aFound, nsAString& aResult) michael@0: { michael@0: const nsAttrValue* attr = GetParsedAttr(); michael@0: michael@0: if (attr && aIndex < static_cast(attr->GetAtomCount())) { michael@0: aFound = true; michael@0: attr->AtomAt(aIndex)->ToString(aResult); michael@0: } else { michael@0: aFound = false; michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsDOMTokenList::CheckToken(const nsAString& aStr) michael@0: { michael@0: if (aStr.IsEmpty()) { michael@0: return NS_ERROR_DOM_SYNTAX_ERR; michael@0: } michael@0: michael@0: nsAString::const_iterator iter, end; michael@0: aStr.BeginReading(iter); michael@0: aStr.EndReading(end); michael@0: michael@0: while (iter != end) { michael@0: if (nsContentUtils::IsHTMLWhitespace(*iter)) michael@0: return NS_ERROR_DOM_INVALID_CHARACTER_ERR; michael@0: ++iter; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsDOMTokenList::CheckTokens(const nsTArray& aTokens) michael@0: { michael@0: for (uint32_t i = 0, l = aTokens.Length(); i < l; ++i) { michael@0: nsresult rv = CheckToken(aTokens[i]); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsDOMTokenList::Contains(const nsAString& aToken, ErrorResult& aError) michael@0: { michael@0: aError = CheckToken(aToken); michael@0: if (aError.Failed()) { michael@0: return false; michael@0: } michael@0: michael@0: const nsAttrValue* attr = GetParsedAttr(); michael@0: return attr && attr->Contains(aToken); michael@0: } michael@0: michael@0: void michael@0: nsDOMTokenList::AddInternal(const nsAttrValue* aAttr, michael@0: const nsTArray& aTokens) michael@0: { michael@0: if (!mElement) { michael@0: return; michael@0: } michael@0: michael@0: nsAutoString resultStr; michael@0: michael@0: if (aAttr) { michael@0: aAttr->ToString(resultStr); michael@0: } michael@0: michael@0: bool oneWasAdded = false; michael@0: nsAutoTArray addedClasses; michael@0: michael@0: for (uint32_t i = 0, l = aTokens.Length(); i < l; ++i) { michael@0: const nsString& aToken = aTokens[i]; michael@0: michael@0: if ((aAttr && aAttr->Contains(aToken)) || michael@0: addedClasses.Contains(aToken)) { michael@0: continue; michael@0: } michael@0: michael@0: if (oneWasAdded || michael@0: (!resultStr.IsEmpty() && michael@0: !nsContentUtils::IsHTMLWhitespace(resultStr.Last()))) { michael@0: resultStr.Append(NS_LITERAL_STRING(" ") + aToken); michael@0: } else { michael@0: resultStr.Append(aToken); michael@0: } michael@0: michael@0: oneWasAdded = true; michael@0: addedClasses.AppendElement(aToken); michael@0: } michael@0: michael@0: mElement->SetAttr(kNameSpaceID_None, mAttrAtom, resultStr, true); michael@0: } michael@0: michael@0: void michael@0: nsDOMTokenList::Add(const nsTArray& aTokens, ErrorResult& aError) michael@0: { michael@0: aError = CheckTokens(aTokens); michael@0: if (aError.Failed()) { michael@0: return; michael@0: } michael@0: michael@0: const nsAttrValue* attr = GetParsedAttr(); michael@0: AddInternal(attr, aTokens); michael@0: } michael@0: michael@0: void michael@0: nsDOMTokenList::Add(const nsAString& aToken, mozilla::ErrorResult& aError) michael@0: { michael@0: nsAutoTArray tokens; michael@0: tokens.AppendElement(aToken); michael@0: Add(tokens, aError); michael@0: } michael@0: michael@0: void michael@0: nsDOMTokenList::RemoveInternal(const nsAttrValue* aAttr, michael@0: const nsTArray& aTokens) michael@0: { michael@0: NS_ABORT_IF_FALSE(aAttr, "Need an attribute"); michael@0: michael@0: nsAutoString input; michael@0: aAttr->ToString(input); michael@0: michael@0: nsAString::const_iterator copyStart, tokenStart, iter, end; michael@0: input.BeginReading(iter); michael@0: input.EndReading(end); michael@0: copyStart = iter; michael@0: michael@0: nsAutoString output; michael@0: bool lastTokenRemoved = false; michael@0: michael@0: while (iter != end) { michael@0: // skip whitespace. michael@0: while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) { michael@0: ++iter; michael@0: } michael@0: michael@0: if (iter == end) { michael@0: // At this point we're sure the last seen token (if any) wasn't to be michael@0: // removed. So the trailing spaces will need to be kept. michael@0: NS_ABORT_IF_FALSE(!lastTokenRemoved, "How did this happen?"); michael@0: michael@0: output.Append(Substring(copyStart, end)); michael@0: break; michael@0: } michael@0: michael@0: tokenStart = iter; michael@0: do { michael@0: ++iter; michael@0: } while (iter != end && !nsContentUtils::IsHTMLWhitespace(*iter)); michael@0: michael@0: if (aTokens.Contains(Substring(tokenStart, iter))) { michael@0: michael@0: // Skip whitespace after the token, it will be collapsed. michael@0: while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) { michael@0: ++iter; michael@0: } michael@0: copyStart = iter; michael@0: lastTokenRemoved = true; michael@0: michael@0: } else { michael@0: michael@0: if (lastTokenRemoved && !output.IsEmpty()) { michael@0: NS_ABORT_IF_FALSE(!nsContentUtils::IsHTMLWhitespace( michael@0: output.Last()), "Invalid last output token"); michael@0: output.Append(char16_t(' ')); michael@0: } michael@0: lastTokenRemoved = false; michael@0: output.Append(Substring(copyStart, iter)); michael@0: copyStart = iter; michael@0: } michael@0: } michael@0: michael@0: mElement->SetAttr(kNameSpaceID_None, mAttrAtom, output, true); michael@0: } michael@0: michael@0: void michael@0: nsDOMTokenList::Remove(const nsTArray& aTokens, ErrorResult& aError) michael@0: { michael@0: aError = CheckTokens(aTokens); michael@0: if (aError.Failed()) { michael@0: return; michael@0: } michael@0: michael@0: const nsAttrValue* attr = GetParsedAttr(); michael@0: if (!attr) { michael@0: return; michael@0: } michael@0: michael@0: RemoveInternal(attr, aTokens); michael@0: } michael@0: michael@0: void michael@0: nsDOMTokenList::Remove(const nsAString& aToken, mozilla::ErrorResult& aError) michael@0: { michael@0: nsAutoTArray tokens; michael@0: tokens.AppendElement(aToken); michael@0: Remove(tokens, aError); michael@0: } michael@0: michael@0: bool michael@0: nsDOMTokenList::Toggle(const nsAString& aToken, michael@0: const Optional& aForce, michael@0: ErrorResult& aError) michael@0: { michael@0: aError = CheckToken(aToken); michael@0: if (aError.Failed()) { michael@0: return false; michael@0: } michael@0: michael@0: const nsAttrValue* attr = GetParsedAttr(); michael@0: const bool forceOn = aForce.WasPassed() && aForce.Value(); michael@0: const bool forceOff = aForce.WasPassed() && !aForce.Value(); michael@0: michael@0: bool isPresent = attr && attr->Contains(aToken); michael@0: nsAutoTArray tokens; michael@0: (*tokens.AppendElement()).Rebind(aToken.Data(), aToken.Length()); michael@0: michael@0: if (isPresent) { michael@0: if (!forceOn) { michael@0: RemoveInternal(attr, tokens); michael@0: isPresent = false; michael@0: } michael@0: } else { michael@0: if (!forceOff) { michael@0: AddInternal(attr, tokens); michael@0: isPresent = true; michael@0: } michael@0: } michael@0: michael@0: return isPresent; michael@0: } michael@0: michael@0: void michael@0: nsDOMTokenList::Stringify(nsAString& aResult) michael@0: { michael@0: if (!mElement) { michael@0: aResult.Truncate(); michael@0: return; michael@0: } michael@0: michael@0: mElement->GetAttr(kNameSpaceID_None, mAttrAtom, aResult); michael@0: } michael@0: michael@0: JSObject* michael@0: nsDOMTokenList::WrapObject(JSContext *cx) michael@0: { michael@0: return DOMTokenListBinding::Wrap(cx, this); michael@0: } michael@0: