Fix some minor problems in the StringImpl header
[WebKit-https.git] / Source / WebCore / html / HTMLOptionElement.cpp
index b03c7f9..9795969 100644 (file)
@@ -3,7 +3,7 @@
  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
  *           (C) 2001 Dirk Mueller (mueller@kde.org)
  *           (C) 2006 Alexey Proskuryakov (ap@nypop.com)
- * Copyright (C) 2004, 2005, 2006, 2010 Apple Inc. All rights reserved.
+ * Copyright (C) 2004-2017 Apple Inc. All rights reserved.
  * Copyright (C) 2010 Google Inc. All rights reserved.
  * Copyright (C) 2011 Motorola Mobility, Inc.  All rights reserved.
  *
 #include "config.h"
 #include "HTMLOptionElement.h"
 
-#include "Attribute.h"
 #include "Document.h"
-#include "ExceptionCode.h"
 #include "HTMLDataListElement.h"
 #include "HTMLNames.h"
+#include "HTMLOptGroupElement.h"
 #include "HTMLParserIdioms.h"
 #include "HTMLSelectElement.h"
 #include "NodeRenderStyle.h"
-#include "NodeRenderingContext.h"
+#include "NodeTraversal.h"
 #include "RenderMenuList.h"
 #include "RenderTheme.h"
 #include "ScriptElement.h"
 #include "StyleResolver.h"
 #include "Text.h"
-#include <wtf/StdLibExtras.h>
-#include <wtf/Vector.h>
-#include <wtf/text/StringBuilder.h>
+#include <wtf/Ref.h>
 
 namespace WebCore {
 
 using namespace HTMLNames;
 
-HTMLOptionElement::HTMLOptionElement(const QualifiedName& tagName, Document* document)
+HTMLOptionElement::HTMLOptionElement(const QualifiedName& tagName, Document& document)
     : HTMLElement(tagName, document)
     , m_disabled(false)
     , m_isSelected(false)
 {
     ASSERT(hasTagName(optionTag));
-    setHasCustomCallbacks();
+    setHasCustomStyleResolveCallbacks();
 }
 
-PassRefPtr<HTMLOptionElement> HTMLOptionElement::create(Document* document)
+Ref<HTMLOptionElement> HTMLOptionElement::create(Document& document)
 {
-    return adoptRef(new HTMLOptionElement(optionTag, document));
+    return adoptRef(*new HTMLOptionElement(optionTag, document));
 }
 
-PassRefPtr<HTMLOptionElement> HTMLOptionElement::create(const QualifiedName& tagName, Document* document)
+Ref<HTMLOptionElement> HTMLOptionElement::create(const QualifiedName& tagName, Document& document)
 {
-    return adoptRef(new HTMLOptionElement(tagName, document));
+    return adoptRef(*new HTMLOptionElement(tagName, document));
 }
 
-PassRefPtr<HTMLOptionElement> HTMLOptionElement::createForJSConstructor(Document* document, const String& data, const String& value,
-        bool defaultSelected, bool selected, ExceptionCode& ec)
+ExceptionOr<Ref<HTMLOptionElement>> HTMLOptionElement::createForJSConstructor(Document& document, const String& text, const String& value, bool defaultSelected, bool selected)
 {
-    RefPtr<HTMLOptionElement> element = adoptRef(new HTMLOptionElement(optionTag, document));
+    auto element = create(document);
 
-    RefPtr<Text> text = Text::create(document, data.isNull() ? "" : data);
-
-    ec = 0;
-    element->appendChild(text.release(), ec);
-    if (ec)
-        return 0;
+    if (!text.isEmpty()) {
+        auto appendResult = element->appendChild(Text::create(document, text));
+        if (appendResult.hasException())
+            return appendResult.releaseException();
+    }
 
     if (!value.isNull())
         element->setValue(value);
     if (defaultSelected)
-        element->setAttribute(selectedAttr, emptyAtom);
+        element->setAttributeWithoutSynchronization(selectedAttr, emptyAtom());
     element->setSelected(selected);
 
-    return element.release();
-}
-
-void HTMLOptionElement::attach()
-{
-    HTMLElement::attach();
-    // If after attaching nothing called styleForRenderer() on this node we
-    // manually cache the value. This happens if our parent doesn't have a
-    // renderer like <optgroup> or if it doesn't allow children like <select>.
-    if (!m_style && parentNode()->renderStyle())
-        updateNonRenderStyle();
-}
-
-void HTMLOptionElement::detach()
-{
-    m_style.clear();
-    HTMLElement::detach();
+    return WTFMove(element);
 }
 
-bool HTMLOptionElement::supportsFocus() const
+bool HTMLOptionElement::isFocusable() const
 {
-    return HTMLElement::supportsFocus();
+    if (!supportsFocus())
+        return false;
+    // Option elements do not have a renderer.
+    auto* style = const_cast<HTMLOptionElement&>(*this).computedStyle();
+    return style && style->display() != NONE;
 }
 
-bool HTMLOptionElement::isFocusable() const
+bool HTMLOptionElement::matchesDefaultPseudoClass() const
 {
-    // Option elements do not have a renderer so we check the renderStyle instead.
-    return supportsFocus() && renderStyle() && renderStyle()->display() != NONE;
+    return hasAttributeWithoutSynchronization(selectedAttr);
 }
 
 String HTMLOptionElement::text() const
 {
-    Document* document = this->document();
-    String text;
-
-    // WinIE does not use the label attribute, so as a quirk, we ignore it.
-    if (!document->inQuirksMode())
-        text = fastGetAttribute(labelAttr);
-
-    // FIXME: The following treats an element with the label attribute set to
-    // the empty string the same as an element with no label attribute at all.
-    // Is that correct? If it is, then should the label function work the same way?
-    if (text.isEmpty())
-        text = collectOptionInnerText();
+    String text = collectOptionInnerText();
 
     // FIXME: Is displayStringModifiedByEncoding helpful here?
     // If it's correct here, then isn't it needed in the value and label functions too?
-    return document->displayStringModifiedByEncoding(text).stripWhiteSpace(isHTMLSpace).simplifyWhiteSpace(isHTMLSpace);
+    return stripLeadingAndTrailingHTMLSpaces(document().displayStringModifiedByEncoding(text)).simplifyWhiteSpace(isHTMLSpace);
 }
 
-void HTMLOptionElement::setText(const String &text, ExceptionCode& ec)
+void HTMLOptionElement::setText(const String &text)
 {
-    RefPtr<Node> protectFromMutationEvents(this);
+    Ref<HTMLOptionElement> protectedThis(*this);
 
     // Changing the text causes a recalc of a select's items, which will reset the selected
     // index to the first item if the select is single selection with a menu list. We attempt to
@@ -148,12 +119,12 @@ void HTMLOptionElement::setText(const String &text, ExceptionCode& ec)
     int oldSelectedIndex = selectIsMenuList ? select->selectedIndex() : -1;
 
     // Handle the common special case where there's exactly 1 child node, and it's a text node.
-    Node* child = firstChild();
-    if (child && child->isTextNode() && !child->nextSibling())
-        toText(child)->setData(text, ec);
+    RefPtr<Node> child = firstChild();
+    if (is<Text>(child) && !child->nextSibling())
+        downcast<Text>(*child).setData(text);
     else {
         removeChildren();
-        appendChild(Text::create(document(), text), ec);
+        appendChild(Text::create(document(), text));
     }
     
     if (selectIsMenuList && select->selectedIndex() != oldSelectedIndex)
@@ -162,7 +133,7 @@ void HTMLOptionElement::setText(const String &text, ExceptionCode& ec)
 
 void HTMLOptionElement::accessKeyAction(bool)
 {
-    HTMLSelectElement* select = ownerSelectElement();
+    RefPtr<HTMLSelectElement> select = ownerSelectElement();
     if (select)
         select->accessKeySetSelectedIndex(index());
 }
@@ -171,18 +142,16 @@ int HTMLOptionElement::index() const
 {
     // It would be faster to cache the index, but harder to get it right in all cases.
 
-    HTMLSelectElement* selectElement = ownerSelectElement();
+    RefPtr<HTMLSelectElement> selectElement = ownerSelectElement();
     if (!selectElement)
         return 0;
 
     int optionIndex = 0;
 
-    const Vector<HTMLElement*>& items = selectElement->listItems();
-    size_t length = items.size();
-    for (size_t i = 0; i < length; ++i) {
-        if (!items[i]->hasTagName(optionTag))
+    for (auto& item : selectElement->listItems()) {
+        if (!is<HTMLOptionElement>(*item))
             continue;
-        if (items[i] == this)
+        if (item == this)
             return optionIndex;
         ++optionIndex;
     }
@@ -190,50 +159,52 @@ int HTMLOptionElement::index() const
     return 0;
 }
 
-void HTMLOptionElement::parseAttribute(const Attribute& attribute)
+void HTMLOptionElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
 {
 #if ENABLE(DATALIST_ELEMENT)
-    if (attribute.name() == valueAttr) {
-        if (HTMLDataListElement* dataList = ownerDataListElement())
+    if (name == valueAttr) {
+        if (RefPtr<HTMLDataListElement> dataList = ownerDataListElement())
             dataList->optionElementChildrenChanged();
     } else
 #endif
-    if (attribute.name() == disabledAttr) {
+    if (name == disabledAttr) {
         bool oldDisabled = m_disabled;
-        m_disabled = !attribute.isNull();
+        m_disabled = !value.isNull();
         if (oldDisabled != m_disabled) {
-            setNeedsStyleRecalc();
-            if (renderer() && renderer()->style()->hasAppearance())
-                renderer()->theme()->stateChanged(renderer(), EnabledState);
+            invalidateStyleForSubtree();
+            if (renderer() && renderer()->style().hasAppearance())
+                renderer()->theme().stateChanged(*renderer(), ControlStates::EnabledState);
         }
-    } else if (attribute.name() == selectedAttr) {
+    } else if (name == selectedAttr) {
+        invalidateStyleForSubtree();
+
         // FIXME: This doesn't match what the HTML specification says.
         // The specification implies that removing the selected attribute or
         // changing the value of a selected attribute that is already present
         // has no effect on whether the element is selected. Further, it seems
         // that we need to do more than just set m_isSelected to select in that
         // case; we'd need to do the other work from the setSelected function.
-        m_isSelected = !attribute.isNull();
+        m_isSelected = !value.isNull();
     } else
-        HTMLElement::parseAttribute(attribute);
+        HTMLElement::parseAttribute(name, value);
 }
 
 String HTMLOptionElement::value() const
 {
-    const AtomicString& value = fastGetAttribute(valueAttr);
+    const AtomicString& value = attributeWithoutSynchronization(valueAttr);
     if (!value.isNull())
         return value;
-    return collectOptionInnerText().stripWhiteSpace(isHTMLSpace).simplifyWhiteSpace(isHTMLSpace);
+    return stripLeadingAndTrailingHTMLSpaces(collectOptionInnerText()).simplifyWhiteSpace(isHTMLSpace);
 }
 
 void HTMLOptionElement::setValue(const String& value)
 {
-    setAttribute(valueAttr, value);
+    setAttributeWithoutSynchronization(valueAttr, value);
 }
 
 bool HTMLOptionElement::selected()
 {
-    if (HTMLSelectElement* select = ownerSelectElement())
+    if (RefPtr<HTMLSelectElement> select = ownerSelectElement())
         select->updateListItemSelectedStates();
     return m_isSelected;
 }
@@ -245,8 +216,8 @@ void HTMLOptionElement::setSelected(bool selected)
 
     setSelectedState(selected);
 
-    if (HTMLSelectElement* select = ownerSelectElement())
-        select->optionSelectionStateChanged(this, selected);
+    if (RefPtr<HTMLSelectElement> select = ownerSelectElement())
+        select->optionSelectionStateChanged(*this, selected);
 }
 
 void HTMLOptionElement::setSelectedState(bool selected)
@@ -255,153 +226,127 @@ void HTMLOptionElement::setSelectedState(bool selected)
         return;
 
     m_isSelected = selected;
-    setNeedsStyleRecalc();
+    invalidateStyleForSubtree();
 
-    if (HTMLSelectElement* select = ownerSelectElement())
+    if (RefPtr<HTMLSelectElement> select = ownerSelectElement())
         select->invalidateSelectedItems();
 }
 
-void HTMLOptionElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
+void HTMLOptionElement::childrenChanged(const ChildChange& change)
 {
 #if ENABLE(DATALIST_ELEMENT)
-    if (HTMLDataListElement* dataList = ownerDataListElement())
+    if (RefPtr<HTMLDataListElement> dataList = ownerDataListElement())
         dataList->optionElementChildrenChanged();
     else
 #endif
-    if (HTMLSelectElement* select = ownerSelectElement())
+    if (RefPtr<HTMLSelectElement> select = ownerSelectElement())
         select->optionElementChildrenChanged();
-    HTMLElement::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
+    HTMLElement::childrenChanged(change);
 }
 
 #if ENABLE(DATALIST_ELEMENT)
 HTMLDataListElement* HTMLOptionElement::ownerDataListElement() const
 {
-    for (ContainerNode* parent = parentNode(); parent ; parent = parent->parentNode()) {
-        if (parent->hasTagName(datalistTag))
-            return static_cast<HTMLDataListElement*>(parent);
+    for (RefPtr<ContainerNode> parent = parentNode(); parent ; parent = parent->parentNode()) {
+        if (is<HTMLDataListElement>(*parent))
+            return downcast<HTMLDataListElement>(parent);
     }
-    return 0;
+    return nullptr;
 }
 #endif
 
 HTMLSelectElement* HTMLOptionElement::ownerSelectElement() const
 {
-    ContainerNode* select = parentNode();
-    while (select && !select->hasTagName(selectTag))
+    RefPtr<ContainerNode> select = parentNode();
+    while (select && !is<HTMLSelectElement>(*select))
         select = select->parentNode();
 
     if (!select)
-        return 0;
+        return nullptr;
 
-    return toHTMLSelectElement(select);
+    return downcast<HTMLSelectElement>(select.get());
 }
 
 String HTMLOptionElement::label() const
 {
-    const AtomicString& label = fastGetAttribute(labelAttr);
+    String label = attributeWithoutSynchronization(labelAttr);
     if (!label.isNull())
-        return label; 
-    return collectOptionInnerText().stripWhiteSpace(isHTMLSpace).simplifyWhiteSpace(isHTMLSpace);
+        return stripLeadingAndTrailingHTMLSpaces(label);
+    return stripLeadingAndTrailingHTMLSpaces(collectOptionInnerText()).simplifyWhiteSpace(isHTMLSpace);
 }
 
-void HTMLOptionElement::setLabel(const String& label)
+// Same as label() but ignores the label content attribute in quirks mode for compatibility with other browsers.
+String HTMLOptionElement::displayLabel() const
 {
-    setAttribute(labelAttr, label);
+    if (document().inQuirksMode())
+        return stripLeadingAndTrailingHTMLSpaces(collectOptionInnerText()).simplifyWhiteSpace(isHTMLSpace);
+    return label();
 }
 
-void HTMLOptionElement::updateNonRenderStyle()
-{
-    m_style = document()->styleResolver()->styleForElement(this);
-}
-
-RenderStyle* HTMLOptionElement::nonRendererStyle() const
-{
-    return m_style.get();
-}
-
-PassRefPtr<RenderStyle> HTMLOptionElement::customStyleForRenderer()
+void HTMLOptionElement::setLabel(const String& label)
 {
-    // styleForRenderer is called whenever a new style should be associated
-    // with an Element so now is a good time to update our cached style.
-    updateNonRenderStyle();
-    return m_style;
+    setAttributeWithoutSynchronization(labelAttr, label);
 }
 
-void HTMLOptionElement::didRecalcStyle(StyleChange)
+void HTMLOptionElement::willResetComputedStyle()
 {
     // FIXME: This is nasty, we ask our owner select to repaint even if the new
     // style is exactly the same.
-    if (HTMLSelectElement* select = ownerSelectElement()) {
-        if (RenderObject* renderer = select->renderer())
+    if (auto select = ownerSelectElement()) {
+        if (auto renderer = select->renderer())
             renderer->repaint();
     }
 }
 
 String HTMLOptionElement::textIndentedToRespectGroupLabel() const
 {
-    ContainerNode* parent = parentNode();
-    if (parent && parent->hasTagName(optgroupTag))
-        return "    " + text();
-    return text();
+    RefPtr<ContainerNode> parent = parentNode();
+    if (is<HTMLOptGroupElement>(parent))
+        return "    " + displayLabel();
+    return displayLabel();
 }
 
-bool HTMLOptionElement::disabled() const
+bool HTMLOptionElement::isDisabledFormControl() const
 {
     if (ownElementDisabled())
         return true;
 
-    if (!parentNode() || !parentNode()->isHTMLElement())
+    if (!is<HTMLOptGroupElement>(parentNode()))
         return false;
 
-    HTMLElement* parentElement = static_cast<HTMLElement*>(parentNode());
-    return parentElement->hasTagName(optgroupTag) && parentElement->disabled();
+    return downcast<HTMLOptGroupElement>(*parentNode()).isDisabledFormControl();
 }
 
-Node::InsertionNotificationRequest HTMLOptionElement::insertedInto(ContainerNode* insertionPoint)
+Node::InsertedIntoAncestorResult HTMLOptionElement::insertedIntoAncestor(InsertionType insertionType, ContainerNode& parentOfInsertedTree)
 {
-    if (HTMLSelectElement* select = ownerSelectElement()) {
+    if (RefPtr<HTMLSelectElement> select = ownerSelectElement()) {
         select->setRecalcListItems();
+        select->updateValidity();
         // Do not call selected() since calling updateListItemSelectedStates()
         // at this time won't do the right thing. (Why, exactly?)
         // FIXME: Might be better to call this unconditionally, always passing m_isSelected,
         // rather than only calling it if we are selected.
         if (m_isSelected)
-            select->optionSelectionStateChanged(this, true);
+            select->optionSelectionStateChanged(*this, true);
         select->scrollToSelection();
     }
 
-    return HTMLElement::insertedInto(insertionPoint);
+    return HTMLElement::insertedIntoAncestor(insertionType, parentOfInsertedTree);
 }
 
 String HTMLOptionElement::collectOptionInnerText() const
 {
     StringBuilder text;
-    for (Node* node = firstChild(); node; ) {
-        if (node->isTextNode())
+    for (RefPtr<Node> node = firstChild(); node; ) {
+        if (is<Text>(*node))
             text.append(node->nodeValue());
         // Text nodes inside script elements are not part of the option text.
-        if (node->isElementNode() && toScriptElement(toElement(node)))
-            node = node->traverseNextSibling(this);
+        if (is<Element>(*node) && isScriptElement(downcast<Element>(*node)))
+            node = NodeTraversal::nextSkippingChildren(*node, this);
         else
-            node = node->traverseNextNode(this);
+            node = NodeTraversal::next(*node, this);
     }
     return text.toString();
 }
 
-#ifndef NDEBUG
-
-HTMLOptionElement* toHTMLOptionElement(Node* node)
-{
-    ASSERT(!node || node->hasTagName(optionTag));
-    return static_cast<HTMLOptionElement*>(node);
-}
-
-const HTMLOptionElement* toHTMLOptionElement(const Node* node)
-{
-    ASSERT(!node || node->hasTagName(optionTag));
-    return static_cast<const HTMLOptionElement*>(node);
-}
-
-#endif
-
 } // namespace