Rename AtomicString to AtomString
[WebKit.git] / Source / WebCore / html / DOMTokenList.cpp
index acb4567..7af20a3 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2010 Google Inc. All rights reserved.
- * Copyright (C) 2015 Apple 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
 #include "config.h"
 #include "DOMTokenList.h"
 
-#include "ExceptionCode.h"
 #include "HTMLParserIdioms.h"
 #include "SpaceSplitString.h"
 #include <wtf/HashSet.h>
-#include <wtf/text/AtomicStringHash.h>
+#include <wtf/SetForScope.h>
+#include <wtf/text/AtomStringHash.h>
 #include <wtf/text/StringBuilder.h>
 
 namespace WebCore {
 
-bool DOMTokenList::validateToken(const String& token, ExceptionCode& ec)
+DOMTokenList::DOMTokenList(Element& element, const QualifiedName& attributeName, IsSupportedTokenFunction&& isSupportedToken)
+    : m_element(element)
+    , m_attributeName(attributeName)
+    , m_isSupportedToken(WTFMove(isSupportedToken))
 {
-    if (token.isEmpty()) {
-        ec = SYNTAX_ERR;
-        return false;
-    }
+}
 
-    unsigned length = token.length();
-    for (unsigned i = 0; i < length; ++i) {
-        if (isHTMLSpace(token[i])) {
-            ec = INVALID_CHARACTER_ERR;
-            return false;
-        }
-    }
+static inline bool tokenContainsHTMLSpace(const String& token)
+{
+    return token.find(isHTMLSpace) != notFound;
+}
 
-    return true;
+ExceptionOr<void> DOMTokenList::validateToken(const String& token)
+{
+    if (token.isEmpty())
+        return Exception { SyntaxError };
+
+    if (tokenContainsHTMLSpace(token))
+        return Exception { InvalidCharacterError };
+
+    return { };
 }
 
-bool DOMTokenList::validateTokens(const String* tokens, size_t length, ExceptionCode& ec)
+ExceptionOr<void> DOMTokenList::validateTokens(const String* tokens, size_t length)
 {
     for (size_t i = 0; i < length; ++i) {
-        if (!validateToken(tokens[i], ec))
-            return false;
+        auto result = validateToken(tokens[i]);
+        if (result.hasException())
+            return result;
     }
-    return true;
+    return { };
 }
 
-bool DOMTokenList::contains(const AtomicString& token, ExceptionCode& ec) const
+bool DOMTokenList::contains(const AtomString& token) const
 {
-    if (!validateToken(token, ec))
-        return false;
-
-    return m_tokens.contains(token);
+    return tokens().contains(token);
 }
 
-inline void DOMTokenList::addInternal(const String* tokens, size_t length, ExceptionCode& ec)
+inline ExceptionOr<void> DOMTokenList::addInternal(const String* newTokens, size_t length)
 {
     // This is usually called with a single token.
-    Vector<AtomicString, 1> uniqueTokens;
-    uniqueTokens.reserveInitialCapacity(length);
+    Vector<AtomString, 1> uniqueNewTokens;
+    uniqueNewTokens.reserveInitialCapacity(length);
+
+    auto& tokens = this->tokens();
 
     for (size_t i = 0; i < length; ++i) {
-        if (!validateToken(tokens[i], ec))
-            return;
-        if (!m_tokens.contains(tokens[i]) && !uniqueTokens.contains(tokens[i]))
-            uniqueTokens.uncheckedAppend(tokens[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 (!uniqueTokens.isEmpty())
-        m_tokens.appendVector(uniqueTokens);
+    if (!uniqueNewTokens.isEmpty())
+        tokens.appendVector(uniqueNewTokens);
+
+    updateAssociatedAttributeFromTokens();
 
-    updateAfterTokenChange();
+    return { };
 }
 
-void DOMTokenList::add(const Vector<String>& tokens, ExceptionCode& ec)
+ExceptionOr<void> DOMTokenList::add(const Vector<String>& tokens)
 {
-    addInternal(tokens.data(), tokens.size(), ec);
+    return addInternal(tokens.data(), tokens.size());
 }
 
-void DOMTokenList::add(const WTF::AtomicString& token, ExceptionCode& ec)
+ExceptionOr<void> DOMTokenList::add(const AtomString& token)
 {
-    addInternal(&token.string(), 1, ec);
+    return addInternal(&token.string(), 1);
 }
 
-inline void DOMTokenList::removeInternal(const String* tokens, size_t length, ExceptionCode& ec)
+inline ExceptionOr<void> DOMTokenList::removeInternal(const String* tokensToRemove, size_t length)
 {
-    if (!validateTokens(tokens, length, ec))
-        return;
+    auto result = validateTokens(tokensToRemove, length);
+    if (result.hasException())
+        return result;
 
+    auto& tokens = this->tokens();
     for (size_t i = 0; i < length; ++i)
-        m_tokens.removeFirst(tokens[i]);
+        tokens.removeFirst(tokensToRemove[i]);
+
+    updateAssociatedAttributeFromTokens();
 
-    updateAfterTokenChange();
+    return { };
 }
 
-void DOMTokenList::remove(const Vector<String>& tokens, ExceptionCode& ec)
+ExceptionOr<void> DOMTokenList::remove(const Vector<String>& tokens)
 {
-    removeInternal(tokens.data(), tokens.size(), ec);
+    return removeInternal(tokens.data(), tokens.size());
 }
 
-void DOMTokenList::remove(const WTF::AtomicString& token, ExceptionCode& ec)
+ExceptionOr<void> DOMTokenList::remove(const AtomString& token)
 {
-    removeInternal(&token.string(), 1, ec);
+    return removeInternal(&token.string(), 1);
 }
 
-bool DOMTokenList::toggle(const AtomicString& token, Optional<bool> force, ExceptionCode& ec)
+ExceptionOr<bool> DOMTokenList::toggle(const AtomString& token, Optional<bool> force)
 {
-    if (!validateToken(token, ec))
-        return false;
+    auto result = validateToken(token);
+    if (result.hasException())
+        return result.releaseException();
+
+    auto& tokens = this->tokens();
 
-    if (m_tokens.contains(token)) {
+    if (tokens.contains(token)) {
         if (!force.valueOr(false)) {
-            m_tokens.removeFirst(token);
-            updateAfterTokenChange();
+            tokens.removeFirst(token);
+            updateAssociatedAttributeFromTokens();
             return false;
         }
         return true;
@@ -137,33 +152,80 @@ bool DOMTokenList::toggle(const AtomicString& token, Optional<bool> force, Excep
     if (force && !force.value())
         return false;
 
-    m_tokens.append(token);
-    updateAfterTokenChange();
+    tokens.append(token);
+    updateAssociatedAttributeFromTokens();
     return true;
 }
 
-const AtomicString& DOMTokenList::value() const
+static inline void replaceInOrderedSet(Vector<AtomString>& tokens, size_t tokenIndex, const AtomString& newToken)
 {
-    if (m_cachedValue.isNull()) {
-        // https://dom.spec.whatwg.org/#concept-ordered-set-serializer
-        StringBuilder builder;
-        for (auto& token : m_tokens) {
-            if (!builder.isEmpty())
-                builder.append(' ');
-            builder.append(token);
-        }
-        m_cachedValue = builder.toAtomicString();
-        ASSERT(!m_cachedValue.isNull());
+    ASSERT(tokenIndex != notFound);
+    ASSERT(tokenIndex < tokens.size());
+
+    auto newTokenIndex = tokens.find(newToken);
+    if (newTokenIndex == notFound) {
+        tokens[tokenIndex] = newToken;
+        return;
     }
-    return m_cachedValue;
+
+    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 String& value)
+{
+    m_element.setAttribute(m_attributeName, value);
 }
 
-void DOMTokenList::setValueInternal(const WTF::String& value)
+void DOMTokenList::updateTokensFromAttributeValue(const String& value)
 {
     // Clear tokens but not capacity.
     m_tokens.shrink(0);
 
-    HashSet<AtomicString> addedTokens;
+    HashSet<AtomString> addedTokens;
     // https://dom.spec.whatwg.org/#ordered%20sets
     for (unsigned start = 0; ; ) {
         while (start < value.length() && isHTMLSpace(value[start]))
@@ -174,7 +236,7 @@ void DOMTokenList::setValueInternal(const WTF::String& value)
         while (end < value.length() && !isHTMLSpace(value[end]))
             ++end;
 
-        AtomicString token = value.substring(start, end - start);
+        AtomString token = value.substring(start, end - start);
         if (!addedTokens.contains(token)) {
             m_tokens.append(token);
             addedTokens.add(token);
@@ -184,7 +246,45 @@ void DOMTokenList::setValueInternal(const WTF::String& value)
     }
 
     m_tokens.shrinkToFit();
-    m_cachedValue = nullAtom;
+    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<bool> 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