| /* |
| * Copyright (C) 2010 Google Inc. All rights reserved. |
| * Copyright (C) 2015, 2016 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON |
| * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "DOMTokenList.h" |
| |
| #include "HTMLParserIdioms.h" |
| #include "SpaceSplitString.h" |
| #include <wtf/HashSet.h> |
| #include <wtf/SetForScope.h> |
| #include <wtf/text/AtomStringHash.h> |
| #include <wtf/text/StringBuilder.h> |
| |
| namespace WebCore { |
| |
| DOMTokenList::DOMTokenList(Element& element, const QualifiedName& attributeName, IsSupportedTokenFunction&& isSupportedToken) |
| : m_element(element) |
| , m_attributeName(attributeName) |
| , m_isSupportedToken(WTFMove(isSupportedToken)) |
| { |
| } |
| |
| static inline bool tokenContainsHTMLSpace(StringView token) |
| { |
| return token.find(isHTMLSpace<UChar>) != notFound; |
| } |
| |
| ExceptionOr<void> DOMTokenList::validateToken(StringView token) |
| { |
| if (token.isEmpty()) |
| return Exception { SyntaxError }; |
| |
| if (tokenContainsHTMLSpace(token)) |
| return Exception { InvalidCharacterError }; |
| |
| return { }; |
| } |
| |
| ExceptionOr<void> DOMTokenList::validateTokens(const AtomString* tokens, size_t length) |
| { |
| for (size_t i = 0; i < length; ++i) { |
| auto result = validateToken(tokens[i]); |
| if (result.hasException()) |
| return result; |
| } |
| return { }; |
| } |
| |
| bool DOMTokenList::contains(const AtomString& token) const |
| { |
| return tokens().contains(token); |
| } |
| |
| inline ExceptionOr<void> DOMTokenList::addInternal(const AtomString* newTokens, size_t length) |
| { |
| // This is usually called with a single token. |
| Vector<AtomString, 1> uniqueNewTokens; |
| uniqueNewTokens.reserveInitialCapacity(length); |
| |
| auto& tokens = this->tokens(); |
| |
| for (size_t i = 0; i < length; ++i) { |
| auto result = validateToken(newTokens[i]); |
| if (result.hasException()) |
| return result; |
| if (!tokens.contains(newTokens[i]) && !uniqueNewTokens.contains(newTokens[i])) |
| uniqueNewTokens.uncheckedAppend(newTokens[i]); |
| } |
| |
| if (!uniqueNewTokens.isEmpty()) |
| tokens.appendVector(uniqueNewTokens); |
| |
| updateAssociatedAttributeFromTokens(); |
| |
| return { }; |
| } |
| |
| ExceptionOr<void> DOMTokenList::add(const FixedVector<AtomString>& tokens) |
| { |
| return addInternal(tokens.data(), tokens.size()); |
| } |
| |
| ExceptionOr<void> DOMTokenList::add(const AtomString& token) |
| { |
| return addInternal(&token, 1); |
| } |
| |
| inline ExceptionOr<void> DOMTokenList::removeInternal(const AtomString* tokensToRemove, size_t length) |
| { |
| auto result = validateTokens(tokensToRemove, length); |
| if (result.hasException()) |
| return result; |
| |
| auto& tokens = this->tokens(); |
| for (size_t i = 0; i < length; ++i) |
| tokens.removeFirst(tokensToRemove[i]); |
| |
| updateAssociatedAttributeFromTokens(); |
| |
| return { }; |
| } |
| |
| ExceptionOr<void> DOMTokenList::remove(const FixedVector<AtomString>& tokens) |
| { |
| return removeInternal(tokens.data(), tokens.size()); |
| } |
| |
| ExceptionOr<void> DOMTokenList::remove(const AtomString& token) |
| { |
| return removeInternal(&token, 1); |
| } |
| |
| ExceptionOr<bool> DOMTokenList::toggle(const AtomString& token, std::optional<bool> force) |
| { |
| auto result = validateToken(token); |
| if (result.hasException()) |
| return result.releaseException(); |
| |
| auto& tokens = this->tokens(); |
| |
| if (tokens.contains(token)) { |
| if (!force.value_or(false)) { |
| tokens.removeFirst(token); |
| updateAssociatedAttributeFromTokens(); |
| return false; |
| } |
| return true; |
| } |
| |
| if (force && !force.value()) |
| return false; |
| |
| tokens.append(token); |
| updateAssociatedAttributeFromTokens(); |
| return true; |
| } |
| |
| static inline void replaceInOrderedSet(Vector<AtomString>& tokens, size_t tokenIndex, const AtomString& newToken) |
| { |
| ASSERT(tokenIndex != notFound); |
| ASSERT(tokenIndex < tokens.size()); |
| |
| auto newTokenIndex = tokens.find(newToken); |
| if (newTokenIndex == notFound) { |
| tokens[tokenIndex] = newToken; |
| return; |
| } |
| |
| if (newTokenIndex == tokenIndex) |
| return; |
| |
| if (newTokenIndex > tokenIndex) { |
| tokens[tokenIndex] = newToken; |
| tokens.remove(newTokenIndex); |
| } else |
| tokens.remove(tokenIndex); |
| } |
| |
| // https://dom.spec.whatwg.org/#dom-domtokenlist-replace |
| ExceptionOr<bool> DOMTokenList::replace(const AtomString& token, const AtomString& newToken) |
| { |
| if (token.isEmpty() || newToken.isEmpty()) |
| return Exception { SyntaxError }; |
| |
| if (tokenContainsHTMLSpace(token) || tokenContainsHTMLSpace(newToken)) |
| return Exception { InvalidCharacterError }; |
| |
| auto& tokens = this->tokens(); |
| |
| auto tokenIndex = tokens.find(token); |
| if (tokenIndex == notFound) |
| return false; |
| |
| replaceInOrderedSet(tokens, tokenIndex, newToken); |
| ASSERT(token == newToken || tokens.find(token) == notFound); |
| |
| updateAssociatedAttributeFromTokens(); |
| |
| return true; |
| } |
| |
| // https://dom.spec.whatwg.org/#concept-domtokenlist-validation |
| ExceptionOr<bool> DOMTokenList::supports(StringView token) |
| { |
| if (!m_isSupportedToken) |
| return Exception { TypeError }; |
| return m_isSupportedToken(m_element.document(), token); |
| } |
| |
| // https://dom.spec.whatwg.org/#dom-domtokenlist-value |
| const AtomString& DOMTokenList::value() const |
| { |
| return m_element.getAttribute(m_attributeName); |
| } |
| |
| void DOMTokenList::setValue(const AtomString& value) |
| { |
| m_element.setAttribute(m_attributeName, value); |
| } |
| |
| void DOMTokenList::updateTokensFromAttributeValue(StringView value) |
| { |
| // Clear tokens but not capacity. |
| m_tokens.shrink(0); |
| |
| HashSet<AtomString> addedTokens; |
| // https://dom.spec.whatwg.org/#ordered%20sets |
| for (unsigned start = 0; ; ) { |
| while (start < value.length() && isHTMLSpace(value[start])) |
| ++start; |
| if (start >= value.length()) |
| break; |
| unsigned end = start + 1; |
| while (end < value.length() && !isHTMLSpace(value[end])) |
| ++end; |
| |
| auto tokenView = value.substring(start, end - start); |
| if (!addedTokens.contains<StringViewHashTranslator>(tokenView)) { |
| auto token = tokenView.toAtomString(); |
| m_tokens.append(token); |
| addedTokens.add(WTFMove(token)); |
| } |
| |
| start = end + 1; |
| } |
| |
| m_tokens.shrinkToFit(); |
| m_tokensNeedUpdating = false; |
| } |
| |
| void DOMTokenList::associatedAttributeValueChanged(const AtomString&) |
| { |
| // Do not reset the DOMTokenList value if the attribute value was changed by us. |
| if (m_inUpdateAssociatedAttributeFromTokens) |
| return; |
| |
| m_tokensNeedUpdating = true; |
| } |
| |
| // https://dom.spec.whatwg.org/#concept-dtl-update |
| void DOMTokenList::updateAssociatedAttributeFromTokens() |
| { |
| ASSERT(!m_tokensNeedUpdating); |
| |
| if (m_tokens.isEmpty() && !m_element.hasAttribute(m_attributeName)) |
| return; |
| |
| // https://dom.spec.whatwg.org/#concept-ordered-set-serializer |
| StringBuilder builder; |
| for (auto& token : tokens()) { |
| if (!builder.isEmpty()) |
| builder.append(' '); |
| builder.append(token); |
| } |
| AtomString serializedValue = builder.toAtomString(); |
| |
| SetForScope inAttributeUpdate(m_inUpdateAssociatedAttributeFromTokens, true); |
| m_element.setAttribute(m_attributeName, serializedValue); |
| } |
| |
| Vector<AtomString>& DOMTokenList::tokens() |
| { |
| if (m_tokensNeedUpdating) |
| updateTokensFromAttributeValue(m_element.getAttribute(m_attributeName)); |
| ASSERT(!m_tokensNeedUpdating); |
| return m_tokens; |
| } |
| |
| } // namespace WebCore |