Changeset 226095 in webkit


Ignore:
Timestamp:
Dec 18, 2017 8:32:43 PM (6 years ago)
Author:
rniwa@webkit.org
Message:

Assertion hit in DocumentOrderedMap::get while removing a form element
https://bugs.webkit.org/show_bug.cgi?id=137959
<rdar://problem/27702012>

Reviewed by Brent Fulgham.

Source/WebCore:

The assertion failure was caused by FormAssociatedElement::findAssociatedForm calling TreeScope::getElementById
for a form associated element inside FormAttributeTargetObserver::idTargetChanged during the removal of
the owner form element, or the first non-form element with the matching ID. If there are other elements with
the same ID in the removed tree at that moment, MapEntry's count for the ID can be higher than it needs to be
since Element::removedFromAncestor has not been called on those elements yet.

Fixed the bug by checking this condition explicitly. This patch introduces ContainerChildRemovalScope which
keeps track of the container node from which a subtree was removed as well as the root of the removed subtree.
DocumentOrderedMap::get then checks whether the matching element can be found in this removed subtree, and its
isConnected() still returns true (the evidence that Element::removedFromAncestor has not been called) when
count > 0 and there was no matching element in the tree scope.

In the long term, we should refactor the way FormAssociatedElement and HTMLFormElement refers to each other
and avoid calling DocumentOrderedMap::get before finish calling removedFromAncestor on the removed subtree.

Tests: fast/forms/update-form-owner-in-moved-subtree-assertion-failure-5.html

fast/forms/update-form-owner-in-moved-subtree-assertion-failure-6.html

  • dom/ContainerNodeAlgorithms.cpp:

(WebCore::notifyChildNodeRemoved):

  • dom/ContainerNodeAlgorithms.h:

(WebCore::ContainerChildRemovalScope): Added.
(WebCore::ContainerChildRemovalScope::ContainerChildRemovalScope):
(WebCore::ContainerChildRemovalScope::~ContainerChildRemovalScope):
(WebCore::ContainerChildRemovalScope::parentOfRemovedTree):
(WebCore::ContainerChildRemovalScope::removedChild):
(WebCore::ContainerChildRemovalScope::currentScope):

  • dom/DocumentOrderedMap.cpp:

(WebCore::DocumentOrderedMap::get const): Added a special early exit when this function is called during
a node removal.

LayoutTests:

Added regression tests for removing a subtree with a form associated element, its owner form element
and another element with the same ID.

  • fast/forms/update-form-owner-in-moved-subtree-assertion-failure-5-expected.txt: Added.
  • fast/forms/update-form-owner-in-moved-subtree-assertion-failure-5.html: Added.
  • fast/forms/update-form-owner-in-moved-subtree-assertion-failure-6-expected.txt: Added.
  • fast/forms/update-form-owner-in-moved-subtree-assertion-failure-6.html: Added.
Location:
trunk
Files:
4 added
5 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r226090 r226095  
     12017-12-18  Ryosuke Niwa  <rniwa@webkit.org>
     2
     3        Assertion hit in DocumentOrderedMap::get while removing a form element
     4        https://bugs.webkit.org/show_bug.cgi?id=137959
     5        <rdar://problem/27702012>
     6
     7        Reviewed by Brent Fulgham.
     8
     9        Added regression tests for removing a subtree with a form associated element, its owner form element
     10        and another element with the same ID.
     11
     12        * fast/forms/update-form-owner-in-moved-subtree-assertion-failure-5-expected.txt: Added.
     13        * fast/forms/update-form-owner-in-moved-subtree-assertion-failure-5.html: Added.
     14        * fast/forms/update-form-owner-in-moved-subtree-assertion-failure-6-expected.txt: Added.
     15        * fast/forms/update-form-owner-in-moved-subtree-assertion-failure-6.html: Added.
     16
    1172017-12-18  Youenn Fablet  <youenn@apple.com>
    218
  • trunk/Source/WebCore/ChangeLog

    r226094 r226095  
     12017-12-18  Ryosuke Niwa  <rniwa@webkit.org>
     2
     3        Assertion hit in DocumentOrderedMap::get while removing a form element
     4        https://bugs.webkit.org/show_bug.cgi?id=137959
     5        <rdar://problem/27702012>
     6
     7        Reviewed by Brent Fulgham.
     8
     9        The assertion failure was caused by FormAssociatedElement::findAssociatedForm calling TreeScope::getElementById
     10        for a form associated element inside FormAttributeTargetObserver::idTargetChanged during the removal of
     11        the owner form element, or the first non-form element with the matching ID. If there are other elements with
     12        the same ID in the removed tree at that moment, MapEntry's count for the ID can be higher than it needs to be
     13        since Element::removedFromAncestor has not been called on those elements yet.
     14
     15        Fixed the bug by checking this condition explicitly. This patch introduces ContainerChildRemovalScope which
     16        keeps track of the container node from which a subtree was removed as well as the root of the removed subtree.
     17        DocumentOrderedMap::get then checks whether the matching element can be found in this removed subtree, and its
     18        isConnected() still returns true (the evidence that Element::removedFromAncestor has not been called) when
     19        count > 0 and there was no matching element in the tree scope.
     20
     21        In the long term, we should refactor the way FormAssociatedElement and HTMLFormElement refers to each other
     22        and avoid calling DocumentOrderedMap::get before finish calling removedFromAncestor on the removed subtree.
     23
     24        Tests: fast/forms/update-form-owner-in-moved-subtree-assertion-failure-5.html
     25               fast/forms/update-form-owner-in-moved-subtree-assertion-failure-6.html
     26
     27        * dom/ContainerNodeAlgorithms.cpp:
     28        (WebCore::notifyChildNodeRemoved):
     29        * dom/ContainerNodeAlgorithms.h:
     30        (WebCore::ContainerChildRemovalScope): Added.
     31        (WebCore::ContainerChildRemovalScope::ContainerChildRemovalScope):
     32        (WebCore::ContainerChildRemovalScope::~ContainerChildRemovalScope):
     33        (WebCore::ContainerChildRemovalScope::parentOfRemovedTree):
     34        (WebCore::ContainerChildRemovalScope::removedChild):
     35        (WebCore::ContainerChildRemovalScope::currentScope):
     36        * dom/DocumentOrderedMap.cpp:
     37        (WebCore::DocumentOrderedMap::get const): Added a special early exit when this function is called during
     38        a node removal.
     39
    1402017-12-18  Timothy Hatcher  <timothy@hatcher.name>
    241
  • trunk/Source/WebCore/dom/ContainerNodeAlgorithms.cpp

    r224356 r226095  
    3636namespace WebCore {
    3737
     38#if !ASSERT_DISABLED
     39ContainerChildRemovalScope* ContainerChildRemovalScope::s_scope = nullptr;
     40#endif
     41
    3842enum class TreeScopeChange { Changed, DidNotChange };
    3943
     
    150154    // Assert that the caller of this function has an instance of NoEventDispatchAssertion.
    151155    ASSERT(!isMainThread() || !NoEventDispatchAssertion::InMainThread::isEventAllowed());
     156    ContainerChildRemovalScope removalScope(oldParentOfRemovedTree, child);
    152157
    153158    // Tree scope has changed if the container node from which "node" is removed is in a document or a shadow root.
  • trunk/Source/WebCore/dom/ContainerNodeAlgorithms.h

    r223802 r226095  
    2727namespace WebCore {
    2828
     29// FIXME: Delete this class after fixing FormAssociatedElement to avoid calling getElementById during a tree removal.
     30#if !ASSERT_DISABLED
     31class ContainerChildRemovalScope {
     32public:
     33    ContainerChildRemovalScope(ContainerNode& parentOfRemovedTree, Node& child)
     34        : m_parentOfRemovedTree(parentOfRemovedTree)
     35        , m_removedChild(child)
     36        , m_previousScope(s_scope)
     37    {
     38        s_scope = this;
     39    }
     40
     41    ~ContainerChildRemovalScope()
     42    {
     43        s_scope = m_previousScope;
     44    }
     45
     46    ContainerNode& parentOfRemovedTree() { return m_parentOfRemovedTree; }
     47    Node& removedChild() { return m_removedChild; }
     48
     49    static ContainerChildRemovalScope* currentScope() { return s_scope; }
     50
     51private:
     52    ContainerNode& m_parentOfRemovedTree;
     53    Node& m_removedChild;
     54    ContainerChildRemovalScope* m_previousScope;
     55    static ContainerChildRemovalScope* s_scope;
     56};
     57#else
     58class ContainerChildRemovalScope {
     59public:
     60    ContainerChildRemovalScope(ContainerNode&, Node&) { }
     61};
     62#endif
     63
    2964NodeVector notifyChildNodeInserted(ContainerNode& parentOfInsertedTree, Node&);
    3065void notifyChildNodeRemoved(ContainerNode& oldParentOfRemovedTree, Node&);
  • trunk/Source/WebCore/dom/DocumentOrderedMap.cpp

    r223886 r226095  
    3232#include "DocumentOrderedMap.h"
    3333
     34#include "ContainerNodeAlgorithms.h"
    3435#include "ElementIterator.h"
    3536#include "HTMLImageElement.h"
     
    121122        return &element;
    122123    }
     124
     125#if !ASSERT_DISABLED
     126    // FormAssociatedElement may call getElementById to find its owner form in the middle of a tree removal.
     127    if (auto* currentScope = ContainerChildRemovalScope::currentScope()) {
     128        ASSERT(&scope.rootNode() == &currentScope->parentOfRemovedTree().rootNode());
     129        Node& removedTree = currentScope->removedChild();
     130        ASSERT(is<ContainerNode>(removedTree));
     131        for (auto& element : descendantsOfType<Element>(downcast<ContainerNode>(removedTree))) {
     132            if (!keyMatches(key, element))
     133                continue;
     134            bool removedFromAncestorHasNotBeenCalledYet = element.isConnected();
     135            ASSERT(removedFromAncestorHasNotBeenCalledYet);
     136            return nullptr;
     137        }
     138    }
    123139    ASSERT_NOT_REACHED();
     140#endif
     141
    124142    return nullptr;
    125143}
Note: See TracChangeset for help on using the changeset viewer.