m_ancestorDisabledState should never be unknown
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 21 Feb 2014 06:40:05 +0000 (06:40 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 21 Feb 2014 06:40:05 +0000 (06:40 +0000)
https://bugs.webkit.org/show_bug.cgi?id=129084

Reviewed by Benjamin Poulain.

Source/WebCore:

In order to resolve the bug 129035, a text form control elements needs to synchronously change
its inner text element's editability by setting or unsetting contenteditable content attribute.
Before this patch, we could not do this because editability of a text form control dependent on
its disabled-ness which was only computed lazily via updateAncestorDisabledState().

This patch makes HTMLFieldSetElement and HTMLFormControlElement update this state synchronously.
To avoid O(k) DOM traversal, where k is the depth of the tree, in insertedInto and removedFrom of
HTMLFormControlElement on most pages, a new document-level flag, m_disabledFieldsetElementsCount,
has been added to indicate whether the document contains any disabled fieldset or not.

Also renamed the misleadingly named disabledAttributeChanged to disabledStateChanged, and added
new function of the same name (disabledAttributeChanged) to be used by HTMLFieldSetElement
for keeping the document-level flag up-to-date upon disabled attribute changes.

Tests: fast/forms/fieldset/fieldset-disabled-2.html

* dom/Document.cpp:
(WebCore::Document::Document): Initializes newly added m_disabledFieldsetElementsCount.
(WebCore::Document::~Document): Assert that we've done house keeping right.
* dom/Document.h:
(WebCore::Document::hasDisabledFieldsetElement): Added.
(WebCore::Document::addDisabledFieldsetElement): Added.
(WebCore::Document::removeDisabledFieldsetElement): Added.

* html/HTMLFieldSetElement.cpp:
(WebCore::HTMLFieldSetElement::~HTMLFieldSetElement): Removes itself from the owner document.

(WebCore::updateFromControlElementsAncestorDisabledStateUnder): Added. Updates startNode and
its descendants' ancestor disabled flag. We don't update controls under another disabled
fieldset element since disabled-ness of those controls aren't affected by startNode.

(WebCore::HTMLFieldSetElement::disabledAttributeChanged): Call addDisabledFieldsetElement and
removeDisabledFieldsetElement to update the owner document's flag.

(WebCore::HTMLFieldSetElement::disabledStateChanged): Renamed from disabledAttributeChanged.
Enable form control elements under the first legend element and disable or enable other
descendent form controls in accordance with the presence of disabled content attribute.

(WebCore::HTMLFieldSetElement::childrenChanged): Update disabled-ness of form controls under
child legend elements because controls aren't disabled in the first legend element, and adding
or removing child elements may have changed the first legend element.

(WebCore::HTMLFieldSetElement::didMoveToNewDocument): Update the flag on the owner document.
* html/HTMLFieldSetElement.h:

* html/HTMLFormControlElement.cpp:
(WebCore::HTMLFormControlElement::HTMLFormControlElement):
(WebCore::HTMLFormControlElement::computeIsDisabledByFieldsetAncestor): Returns boolean instead of
updating m_ancestorDisabledState internally. Also renamed from updateAncestorDisabledState.

(WebCore::HTMLFormControlElement::setAncestorDisabled): Replaced ancestorDisabledStateWasChanged.
This function updates m_disabledByAncestorFieldset and calls disabledAttributeChanged as needed.

(WebCore::HTMLFormControlElement::disabledAttributeChanged): Added. Calls disabledStateChanged.
(WebCore::HTMLFormControlElement::disabledStateChanged): Renamed from disabledAttributeChanged.

(WebCore::HTMLFormControlElement::insertedInto): Update m_disabledByAncestorFieldset if there is
a possibility (i.e. the document contains any disabled fieldset element) that this form control
is inserted under a disabled fieldset element.

(WebCore::HTMLFormControlElement::removedFrom): If this form control element is not disabled by
a fieldset ancestor, then there is nothing to do. If it is, then check to see if the element is
still disabled now that we've lost some ancestors.

(WebCore::HTMLFormControlElement::isDisabledFormControl): No longer updates m_ancestorDisabledState
lazily since m_disabledByAncestorFieldset is never ambiguous now.

* html/HTMLFormControlElement.h:
(WebCore::HTMLFormControlElement::disabledByAncestorFieldset): Added.

LayoutTests:

Added more test cases.

* fast/forms/fieldset/fieldset-disabled-2-expected.txt:
* fast/forms/fieldset/fieldset-disabled-2.html:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@164475 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/fast/forms/fieldset/fieldset-disabled-2-expected.txt
LayoutTests/fast/forms/fieldset/fieldset-disabled-2.html
Source/WebCore/ChangeLog
Source/WebCore/dom/Document.cpp
Source/WebCore/dom/Document.h
Source/WebCore/html/HTMLFieldSetElement.cpp
Source/WebCore/html/HTMLFieldSetElement.h
Source/WebCore/html/HTMLFormControlElement.cpp
Source/WebCore/html/HTMLFormControlElement.h

index 5d60749b2f6a48df6fbc1cfad803d7861a3b39e9..7da4d219173647c76361b57d143ee003c734ca23 100644 (file)
@@ -1,3 +1,15 @@
+2014-02-19  Ryosuke Niwa  <rniwa@webkit.org>
+
+        m_ancestorDisabledState should never be unknown
+        https://bugs.webkit.org/show_bug.cgi?id=129084
+
+        Reviewed by Benjamin Poulain.
+
+        Added more test cases.
+
+        * fast/forms/fieldset/fieldset-disabled-2-expected.txt:
+        * fast/forms/fieldset/fieldset-disabled-2.html:
+
 2014-02-20  Joseph Pecoraro  <pecoraro@apple.com>
 
         [iOS] Assert / crash trying to draw empty checked input
index b4c780a231f29b6c5f3f5afad06a05d03f7e1e34..32580899a3aa75259deeab48f0fa9d6d8dd5e388 100644 (file)
@@ -15,6 +15,25 @@ PASS isInputDisabledById("inputInsideFirstLegend") is false
 PASS isInputDisabledById("inputInsideSecondLegend") is false
 PASS isInputDisabledById("inputInsideNestedFirstLegend") is false
 PASS isInputDisabledById("inputInsideFirstLegendWithDisabledOuterFieldset") is false
+
+contentDocument = createIframe();
+fieldset = contentDocument.createElement("fieldset"); contentDocument.body.appendChild(fieldset);
+PASS input = contentDocument.createElement("input"); fieldset.appendChild(input); isInputDisabled(input) is false
+PASS fieldset.disabled = true; isInputDisabled(input) is true
+PASS contentDocument.body.appendChild(input); isInputDisabled(input) is false
+PASS document.body.appendChild(input); isInputDisabled(input) is false
+PASS fieldset.appendChild(input); isInputDisabled(input) is true
+fieldset2 = document.createElement("fieldset"); fieldset2.disabled = true
+input2 = document.createElement("input"); fieldset2.appendChild(input2)
+PASS contentDocument.body.appendChild(fieldset2); isInputDisabled(input2) is true
+PASS document.body.appendChild(fieldset); isInputDisabled(input) is true
+
+setDisabledOnAllFieldsets(true)
+PASS isInputDisabledById("inputOutsideLegend") is true
+PASS isInputDisabledById("inputInsideFirstLegend") is false
+PASS isInputDisabledById("inputInsideSecondLegend") is true
+PASS isInputDisabledById("inputInsideNestedFirstLegend") is false
+PASS isInputDisabledById("inputInsideFirstLegendWithDisabledOuterFieldset") is true
 PASS successfullyParsed is true
 
 TEST COMPLETE
index 4ad36ff96dcefc388fa87a684790a5b719b4c20e..0c0c4d60fc74c77a318ebd95a8802beb78b2b52a 100644 (file)
@@ -18,11 +18,14 @@ input:disabled { color: #666; }
 
 description("Additional tests for fieldset element disabling descendent input elements");
 
-function isInputDisabledById(id) {
-    var input = document.getElementById(id);
+function isInputDisabled(input) {
     return getComputedStyle(input).color == 'rgb(102, 102, 102)';
 }
 
+function isInputDisabledById(id) {
+    return isInputDisabled(document.getElementById(id));
+}
+
 shouldBeTrue('isInputDisabledById("inputOutsideLegend")');
 shouldBeFalse('isInputDisabledById("inputInsideFirstLegend")');
 shouldBeTrue('isInputDisabledById("inputInsideSecondLegend")');
@@ -30,7 +33,7 @@ shouldBeFalse('isInputDisabledById("inputInsideNestedFirstLegend")');
 shouldBeTrue('isInputDisabledById("inputInsideFirstLegendWithDisabledOuterFieldset")');
 
 function setDisabledOnAllFieldsets(value) {
-    var fieldsets = document.querySelectorAll('fieldset');
+    var fieldsets = document.querySelectorAll('#container > fieldset, body > fieldset');
     for (var i = 0; i < fieldsets.length; i++)
         fieldsets[i].disabled = value;
 }
@@ -43,6 +46,40 @@ shouldBeFalse('isInputDisabledById("inputInsideSecondLegend")');
 shouldBeFalse('isInputDisabledById("inputInsideNestedFirstLegend")');
 shouldBeFalse('isInputDisabledById("inputInsideFirstLegendWithDisabledOuterFieldset")');
 
+function createIframe() {
+    var iframe = document.createElement("iframe");
+    document.getElementById('container').appendChild(iframe);
+    iframe.contentDocument.head.appendChild(document.querySelector('body style').cloneNode(true));
+    return iframe.contentDocument;
+}
+
+function insertNewFieldsetIntoBody(contentDocument) {
+    fieldset = contentDocument.createElement("fieldset");
+    contentDocument.body.appendChild(fieldset);
+    return fieldset;
+}
+
+debug('');
+evalAndLog('contentDocument = createIframe();');
+evalAndLog('fieldset = contentDocument.createElement("fieldset"); contentDocument.body.appendChild(fieldset);');
+shouldBeFalse('input = contentDocument.createElement("input"); fieldset.appendChild(input); isInputDisabled(input)');
+shouldBeTrue('fieldset.disabled = true; isInputDisabled(input)');
+shouldBeFalse('contentDocument.body.appendChild(input); isInputDisabled(input)');
+shouldBeFalse('document.body.appendChild(input); isInputDisabled(input)');
+shouldBeTrue('fieldset.appendChild(input); isInputDisabled(input)');
+evalAndLog('fieldset2 = document.createElement("fieldset"); fieldset2.disabled = true');
+evalAndLog('input2 = document.createElement("input"); fieldset2.appendChild(input2)');
+shouldBeTrue('contentDocument.body.appendChild(fieldset2); isInputDisabled(input2)');
+shouldBeTrue('document.body.appendChild(fieldset); isInputDisabled(input)');
+
+debug('');
+evalAndLog('setDisabledOnAllFieldsets(true)');
+shouldBeTrue('isInputDisabledById("inputOutsideLegend")');
+shouldBeFalse('isInputDisabledById("inputInsideFirstLegend")');
+shouldBeTrue('isInputDisabledById("inputInsideSecondLegend")');
+shouldBeFalse('isInputDisabledById("inputInsideNestedFirstLegend")');
+shouldBeTrue('isInputDisabledById("inputInsideFirstLegendWithDisabledOuterFieldset")');
+
 document.getElementById('container').style.display = 'none';
 
 </script>
index cb9e1f18aa8086609fc837cb1aa73e3fa1d57230..ab5988ff4d2fcb9ad68157f240e66a93122edd03 100644 (file)
@@ -1,3 +1,80 @@
+2014-02-20  Ryosuke Niwa  <rniwa@webkit.org>
+
+        m_ancestorDisabledState should never be unknown
+        https://bugs.webkit.org/show_bug.cgi?id=129084
+
+        Reviewed by Benjamin Poulain.
+
+        In order to resolve the bug 129035, a text form control elements needs to synchronously change
+        its inner text element's editability by setting or unsetting contenteditable content attribute.
+        Before this patch, we could not do this because editability of a text form control dependent on
+        its disabled-ness which was only computed lazily via updateAncestorDisabledState().
+
+        This patch makes HTMLFieldSetElement and HTMLFormControlElement update this state synchronously.
+        To avoid O(k) DOM traversal, where k is the depth of the tree, in insertedInto and removedFrom of
+        HTMLFormControlElement on most pages, a new document-level flag, m_disabledFieldsetElementsCount,
+        has been added to indicate whether the document contains any disabled fieldset or not.
+
+        Also renamed the misleadingly named disabledAttributeChanged to disabledStateChanged, and added
+        new function of the same name (disabledAttributeChanged) to be used by HTMLFieldSetElement
+        for keeping the document-level flag up-to-date upon disabled attribute changes.
+
+        Tests: fast/forms/fieldset/fieldset-disabled-2.html
+
+        * dom/Document.cpp:
+        (WebCore::Document::Document): Initializes newly added m_disabledFieldsetElementsCount.
+        (WebCore::Document::~Document): Assert that we've done house keeping right.
+        * dom/Document.h:
+        (WebCore::Document::hasDisabledFieldsetElement): Added.
+        (WebCore::Document::addDisabledFieldsetElement): Added.
+        (WebCore::Document::removeDisabledFieldsetElement): Added.
+
+        * html/HTMLFieldSetElement.cpp:
+        (WebCore::HTMLFieldSetElement::~HTMLFieldSetElement): Removes itself from the owner document.
+
+        (WebCore::updateFromControlElementsAncestorDisabledStateUnder): Added. Updates startNode and
+        its descendants' ancestor disabled flag. We don't update controls under another disabled
+        fieldset element since disabled-ness of those controls aren't affected by startNode.
+
+        (WebCore::HTMLFieldSetElement::disabledAttributeChanged): Call addDisabledFieldsetElement and
+        removeDisabledFieldsetElement to update the owner document's flag.
+
+        (WebCore::HTMLFieldSetElement::disabledStateChanged): Renamed from disabledAttributeChanged. 
+        Enable form control elements under the first legend element and disable or enable other
+        descendent form controls in accordance with the presence of disabled content attribute.
+
+        (WebCore::HTMLFieldSetElement::childrenChanged): Update disabled-ness of form controls under
+        child legend elements because controls aren't disabled in the first legend element, and adding
+        or removing child elements may have changed the first legend element.
+
+        (WebCore::HTMLFieldSetElement::didMoveToNewDocument): Update the flag on the owner document.
+        * html/HTMLFieldSetElement.h:
+
+        * html/HTMLFormControlElement.cpp:
+        (WebCore::HTMLFormControlElement::HTMLFormControlElement):
+        (WebCore::HTMLFormControlElement::computeIsDisabledByFieldsetAncestor): Returns boolean instead of
+        updating m_ancestorDisabledState internally. Also renamed from updateAncestorDisabledState.
+
+        (WebCore::HTMLFormControlElement::setAncestorDisabled): Replaced ancestorDisabledStateWasChanged.
+        This function updates m_disabledByAncestorFieldset and calls disabledAttributeChanged as needed.
+
+        (WebCore::HTMLFormControlElement::disabledAttributeChanged): Added. Calls disabledStateChanged.
+        (WebCore::HTMLFormControlElement::disabledStateChanged): Renamed from disabledAttributeChanged.
+
+        (WebCore::HTMLFormControlElement::insertedInto): Update m_disabledByAncestorFieldset if there is
+        a possibility (i.e. the document contains any disabled fieldset element) that this form control
+        is inserted under a disabled fieldset element.
+
+        (WebCore::HTMLFormControlElement::removedFrom): If this form control element is not disabled by
+        a fieldset ancestor, then there is nothing to do. If it is, then check to see if the element is
+        still disabled now that we've lost some ancestors.
+
+        (WebCore::HTMLFormControlElement::isDisabledFormControl): No longer updates m_ancestorDisabledState
+        lazily since m_disabledByAncestorFieldset is never ambiguous now.
+
+        * html/HTMLFormControlElement.h:
+        (WebCore::HTMLFormControlElement::disabledByAncestorFieldset): Added.
+
 2014-02-20  Zalan Bujtas  <zalan@apple.com>
 
         Remove redundant deviceScaleFactor() functions and make callers use Document::deviceScaleFactor() when accessible.
index 563fbb9a6d3aeacf83d594985ad080c98088c6fa..a4e1d75b0fc1eb1b173415fac531a87556f866df 100644 (file)
@@ -502,6 +502,7 @@ Document::Document(Frame* frame, const URL& url, unsigned documentClasses, unsig
     , m_inputCursor(EmptyInputCursor::create())
 #endif
     , m_didAssociateFormControlsTimer(this, &Document::didAssociateFormControlsTimerFired)
+    , m_disabledFieldsetElementsCount(0)
     , m_hasInjectedPlugInsScript(false)
     , m_renderTreeBeingDestroyed(false)
 {
@@ -572,6 +573,7 @@ Document::~Document()
     ASSERT(!m_inPageCache);
     ASSERT(m_ranges.isEmpty());
     ASSERT(!m_parentTreeScope);
+    ASSERT(!m_disabledFieldsetElementsCount);
 
 #if ENABLE(DEVICE_ORIENTATION) && PLATFORM(IOS)
     m_deviceMotionClient->deviceMotionControllerDestroyed();
index 3af3f57feb07110281f467b68ba5242618c6d1ed..01b8c43c4ec050f6f24d686717282c27fc26544a 100644 (file)
@@ -1239,6 +1239,9 @@ public:
 #endif
 
     void didAssociateFormControl(Element*);
+    bool hasDisabledFieldsetElement() const { return m_disabledFieldsetElementsCount; }
+    void addDisabledFieldsetElement() { m_disabledFieldsetElementsCount++; }
+    void removeDisabledFieldsetElement() { ASSERT(m_disabledFieldsetElementsCount); m_disabledFieldsetElementsCount--; }
 
     virtual void addConsoleMessage(MessageSource, MessageLevel, const String& message, unsigned long requestIdentifier = 0) override;
 
@@ -1676,6 +1679,7 @@ private:
 
     Timer<Document> m_didAssociateFormControlsTimer;
     HashSet<RefPtr<Element>> m_associatedFormControls;
+    unsigned m_disabledFieldsetElementsCount;
 
     bool m_hasInjectedPlugInsScript;
     bool m_renderTreeBeingDestroyed;
index 9fa083b897ad9a1aa5bfb5eeb029e52e4bf86a8d..91112933cc10c4bec94489c6e083cf2906710dd6 100644 (file)
@@ -44,30 +44,88 @@ inline HTMLFieldSetElement::HTMLFieldSetElement(const QualifiedName& tagName, Do
     ASSERT(hasTagName(fieldsetTag));
 }
 
+HTMLFieldSetElement::~HTMLFieldSetElement()
+{
+    if (hasAttribute(disabledAttr))
+        document().removeDisabledFieldsetElement();
+}
+
 PassRefPtr<HTMLFieldSetElement> HTMLFieldSetElement::create(const QualifiedName& tagName, Document& document, HTMLFormElement* form)
 {
     return adoptRef(new HTMLFieldSetElement(tagName, document, form));
 }
 
-void HTMLFieldSetElement::invalidateDisabledStateUnder(Element* base)
+static void updateFromControlElementsAncestorDisabledStateUnder(HTMLElement& startNode, bool isDisabled)
 {
-    for (auto& control : descendantsOfType<HTMLFormControlElement>(*base))
-        control.ancestorDisabledStateWasChanged();
+    HTMLFormControlElement* control;
+    if (isHTMLFormControlElement(startNode))
+        control = &toHTMLFormControlElement(startNode);
+    else
+        control = Traversal<HTMLFormControlElement>::firstWithin(&startNode);
+    while (control) {
+        control->setAncestorDisabled(isDisabled);
+        // Don't call setAncestorDisabled(false) on form contorls inside disabled fieldsets.
+        if (isHTMLFieldSetElement(control) && control->hasAttribute(disabledAttr))
+            control = Traversal<HTMLFormControlElement>::nextSkippingChildren(control, &startNode);
+        else
+            control = Traversal<HTMLFormControlElement>::next(control, &startNode);
+    }
 }
 
 void HTMLFieldSetElement::disabledAttributeChanged()
 {
-    // This element must be updated before the style of nodes in its subtree gets recalculated.
+    if (hasAttribute(disabledAttr))
+        document().addDisabledFieldsetElement();
+    else
+        document().removeDisabledFieldsetElement();
+
     HTMLFormControlElement::disabledAttributeChanged();
-    invalidateDisabledStateUnder(this);
+}
+
+void HTMLFieldSetElement::disabledStateChanged()
+{
+    // This element must be updated before the style of nodes in its subtree gets recalculated.
+    HTMLFormControlElement::disabledStateChanged();
+
+    if (disabledByAncestorFieldset())
+        return;
+
+    bool thisFieldsetIsDisabled = hasAttribute(disabledAttr);
+    bool hasSeenFirstLegendElement = false;
+    for (auto& control : childrenOfType<HTMLElement>(*this)) {
+        if (!hasSeenFirstLegendElement && isHTMLLegendElement(control)) {
+            hasSeenFirstLegendElement = true;
+            updateFromControlElementsAncestorDisabledStateUnder(control, false /* isDisabled */);
+            continue;
+        }
+        updateFromControlElementsAncestorDisabledStateUnder(control, thisFieldsetIsDisabled);
+    }
 }
 
 void HTMLFieldSetElement::childrenChanged(const ChildChange& change)
 {
     HTMLFormControlElement::childrenChanged(change);
+    if (!hasAttribute(disabledAttr))
+        return;
+
+    HTMLLegendElement* legend = Traversal<HTMLLegendElement>::firstChild(this);
+    if (!legend)
+        return;
+
+    // We only care about the first legend element (in which form contorls are not disabled by this element) changing here.
+    updateFromControlElementsAncestorDisabledStateUnder(*legend, false /* isDisabled */);
+    while ((legend = Traversal<HTMLLegendElement>::nextSibling(legend)))
+        updateFromControlElementsAncestorDisabledStateUnder(*legend, true);
+}
 
-    for (auto& legend : childrenOfType<HTMLLegendElement>(*this))
-        invalidateDisabledStateUnder(&legend);
+void HTMLFieldSetElement::didMoveToNewDocument(Document* oldDocument)
+{
+    HTMLFormControlElement::didMoveToNewDocument(oldDocument);
+    if (hasAttribute(disabledAttr)) {
+        if (oldDocument)
+            oldDocument->removeDisabledFieldsetElement();
+        document().addDisabledFieldsetElement();
+    }
 }
 
 bool HTMLFieldSetElement::supportsFocus() const
index e2d84cf18588bfaa3841fbd69ab3cb173e268997..7c13ea0d53a3b94fcdd35d2c67813762b5d2dca7 100644 (file)
@@ -42,19 +42,21 @@ public:
     unsigned length() const;
 
 protected:
-    virtual void disabledAttributeChanged() override;
 
 private:
     HTMLFieldSetElement(const QualifiedName&, Document&, HTMLFormElement*);
+    ~HTMLFieldSetElement();
 
     virtual bool isEnumeratable() const override { return true; }
     virtual bool supportsFocus() const override;
     virtual RenderPtr<RenderElement> createElementRenderer(PassRef<RenderStyle>) override;
     virtual const AtomicString& formControlType() const override;
     virtual bool recalcWillValidate() const override { return false; }
+    virtual void disabledAttributeChanged() override;
+    virtual void disabledStateChanged() override;
     virtual void childrenChanged(const ChildChange&) override;
+    virtual void didMoveToNewDocument(Document* oldDocument) override;
 
-    static void invalidateDisabledStateUnder(Element*);
     void refreshElementsIfNeeded() const;
 
     mutable Vector<FormAssociatedElement*> m_associatedElements;
index d7fdb76fd5b521145447e4516a3f9dca6b9df62a..db4be6525c8605f4de1a1bd534ee76871f7ec85c 100644 (file)
@@ -52,7 +52,7 @@ HTMLFormControlElement::HTMLFormControlElement(const QualifiedName& tagName, Doc
     , m_isReadOnly(false)
     , m_isRequired(false)
     , m_valueMatchesRenderer(false)
-    , m_ancestorDisabledState(AncestorDisabledStateUnknown)
+    , m_disabledByAncestorFieldset(false)
     , m_dataListAncestorState(Unknown)
     , m_willValidateInitialized(false)
     , m_willValidate(true)
@@ -99,25 +99,27 @@ bool HTMLFormControlElement::formNoValidate() const
     return fastHasAttribute(formnovalidateAttr);
 }
 
-void HTMLFormControlElement::updateAncestorDisabledState() const
+bool HTMLFormControlElement::computeIsDisabledByFieldsetAncestor() const
 {
     Element* previousAncestor = nullptr;
     for (Element* ancestor = parentElement(); ancestor; ancestor = ancestor->parentElement()) {
         if (isHTMLFieldSetElement(ancestor) && ancestor->hasAttribute(disabledAttr)) {
             HTMLFieldSetElement& fieldSetAncestor = toHTMLFieldSetElement(*ancestor);
             bool isInFirstLegend = previousAncestor && isHTMLLegendElement(previousAncestor) && previousAncestor == fieldSetAncestor.legend();
-            m_ancestorDisabledState = isInFirstLegend ? AncestorDisabledStateEnabled : AncestorDisabledStateDisabled;
-            return;
+            return !isInFirstLegend;
         }
         previousAncestor = ancestor;
     }
-    m_ancestorDisabledState = AncestorDisabledStateEnabled;
+    return false;
 }
 
-void HTMLFormControlElement::ancestorDisabledStateWasChanged()
+void HTMLFormControlElement::setAncestorDisabled(bool isDisabled)
 {
-    m_ancestorDisabledState = AncestorDisabledStateUnknown;
-    disabledAttributeChanged();
+    ASSERT(computeIsDisabledByFieldsetAncestor() == isDisabled);
+    bool oldValue = m_disabledByAncestorFieldset;
+    m_disabledByAncestorFieldset = isDisabled;
+    if (oldValue != m_disabledByAncestorFieldset)
+        disabledStateChanged();
 }
 
 void HTMLFormControlElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
@@ -153,6 +155,11 @@ void HTMLFormControlElement::parseAttribute(const QualifiedName& name, const Ato
 }
 
 void HTMLFormControlElement::disabledAttributeChanged()
+{
+    disabledStateChanged();
+}
+
+void HTMLFormControlElement::disabledStateChanged()
 {
     setNeedsWillValidateCheck();
     didAffectSelector(AffectedSelectorDisabled | AffectedSelectorEnabled);
@@ -231,7 +238,8 @@ void HTMLFormControlElement::didMoveToNewDocument(Document* oldDocument)
 
 Node::InsertionNotificationRequest HTMLFormControlElement::insertedInto(ContainerNode& insertionPoint)
 {
-    m_ancestorDisabledState = AncestorDisabledStateUnknown;
+    if (document().hasDisabledFieldsetElement())
+        setAncestorDisabled(computeIsDisabledByFieldsetAncestor());
     m_dataListAncestorState = Unknown;
     setNeedsWillValidateCheck();
     HTMLElement::insertedInto(insertionPoint);
@@ -242,7 +250,8 @@ Node::InsertionNotificationRequest HTMLFormControlElement::insertedInto(Containe
 void HTMLFormControlElement::removedFrom(ContainerNode& insertionPoint)
 {
     m_validationMessage = nullptr;
-    m_ancestorDisabledState = AncestorDisabledStateUnknown;
+    if (m_disabledByAncestorFieldset)
+        setAncestorDisabled(computeIsDisabledByFieldsetAncestor());
     m_dataListAncestorState = Unknown;
     HTMLElement::removedFrom(insertionPoint);
     FormAssociatedElement::removedFrom(insertionPoint);
@@ -272,14 +281,7 @@ void HTMLFormControlElement::dispatchFormControlInputEvent()
 
 bool HTMLFormControlElement::isDisabledFormControl() const
 {
-    if (m_disabled)
-        return true;
-
-    if (m_ancestorDisabledState == AncestorDisabledStateUnknown)
-        updateAncestorDisabledState();
-    if (m_ancestorDisabledState == AncestorDisabledStateDisabled)
-        return true;
-    return HTMLElement::isDisabledFormControl();
+    return m_disabled || m_disabledByAncestorFieldset;
 }
 
 bool HTMLFormControlElement::isRequired() const
index fba0480e5f0ea0522b12534b866def9b47f6bba4..6958f28b89760efcb2814ba4185df0146d2032f9 100644 (file)
@@ -54,7 +54,7 @@ public:
     void setFormMethod(const String&);
     bool formNoValidate() const;
 
-    void ancestorDisabledStateWasChanged();
+    void setAncestorDisabled(bool isDisabled);
 
     virtual void reset() { }
 
@@ -120,9 +120,12 @@ public:
 protected:
     HTMLFormControlElement(const QualifiedName& tagName, Document&, HTMLFormElement*);
 
+    bool disabledByAncestorFieldset() const { return m_disabledByAncestorFieldset; }
+
     virtual void parseAttribute(const QualifiedName&, const AtomicString&) override;
     virtual void requiredAttributeChanged();
     virtual void disabledAttributeChanged();
+    virtual void disabledStateChanged();
     virtual void didAttachRenderers() override;
     virtual InsertionNotificationRequest insertedInto(ContainerNode&) override;
     virtual void removedFrom(ContainerNode&) override;
@@ -154,7 +157,8 @@ private:
     virtual HTMLFormElement* virtualForm() const override;
     virtual bool isDefaultButtonForForm() const override;
     virtual bool isValidFormControlElement() const override;
-    void updateAncestorDisabledState() const;
+
+    bool computeIsDisabledByFieldsetAncestor() const;
 
     virtual HTMLElement& asHTMLElement() override final { return *this; }
     virtual const HTMLFormControlElement& asHTMLElement() const override final { return *this; }
@@ -165,9 +169,8 @@ private:
     bool m_isReadOnly : 1;
     bool m_isRequired : 1;
     bool m_valueMatchesRenderer : 1;
+    bool m_disabledByAncestorFieldset : 1;
 
-    enum AncestorDisabledState { AncestorDisabledStateUnknown, AncestorDisabledStateEnabled, AncestorDisabledStateDisabled };
-    mutable AncestorDisabledState m_ancestorDisabledState;
     enum DataListAncestorState { Unknown, InsideDataList, NotInsideDataList };
     mutable enum DataListAncestorState m_dataListAncestorState;