Title: [201471] trunk
Revision
201471
Author
[email protected]
Date
2016-05-27 15:31:43 -0700 (Fri, 27 May 2016)

Log Message

Crash in TreeScope::focusedElement
https://bugs.webkit.org/show_bug.cgi?id=158108

Reviewed by Enrica Casucci.

Source/WebCore:

The bug was caused by a flawed sequence of steps we took to remove an element. When an element is removed,
willRemoveChild and willRemoveChildren fire blur events on removed focused element and its ancestors and
unload event on any removed iframes. However, it was possible to focus an element on which we had fired blur
during an unload event, leaving m_focusedElement point to an element that's not in the document anymore.

Changing the order doesn't help because that would make it possible to insert the removed iframes back into
the document inside a event listener of the blur event, which was specifically fixed by r127534 four years ago.

Instead, fix the bug by not firing blur and change events on removed nodes. New behavior matches Firefox and HTML5
specification: https://html.spec.whatwg.org/multipage/interaction.html#focus-fixup-rule-one

Test: fast/shadow-dom/shadow-root-active-element-crash.html

* dom/ContainerNode.cpp:
(WebCore::willRemoveChild): Made this function static local since it didn't need to have access to any private
member variables. Call Document::nodeWillBeRemoved after disconnecting iframes since unload event handler could
allocate new Ranges just like mutation events.
(WebCore::willRemoveChildren): Ditto.
(WebCore::ContainerNode::removeChild): Removed the calls to removeFullScreenElementOfSubtree and
removeFocusedNodeOfSubtree as they're now called in Document::nodeWillBeRemoved.
(WebCore::ContainerNode::removeChildren): Ditto.
* dom/ContainerNode.h:
* dom/Document.cpp:
(WebCore::Document::removeFocusedNodeOfSubtree): Don't dispatch blur and change events when a node is removed.
(WebCore::Document::setFocusedElement): Added FocusRemovalEventsMode as the third argument. Avoid dispatching blur
and change events when FocusRemovalEventsMode::Dispatch is set.
(WebCore::Document::nodeChildrenWillBeRemoved): Added calls to removeFullScreenElementOfSubtree and
removeFocusedNodeOfSubtree. Also assert that no events are fired within this function. If we ever fire an event here,
"unloaded" iframes can be inserted back into a document before ContainerNode::removeChild actually removes them.
(WebCore::Document::nodeWillBeRemoved): Ditto.
* dom/Document.h:
* dom/TreeScope.cpp:
(WebCore::TreeScope::focusedElement): Added a release assertion to make sure the focused element is in the document
of the tree scope, and added an explicit type check just in case.

LayoutTests:

Added a regression test for accessing shadowRoot.activeElement after re-focusing an element
inside DOMNodeRemovedFromDocument event and unload events.

This patch also restores the expected result of fast/events/onblur-remove.html to that of when
the test was in r15720 and updated in r19014. The expected result was changed in r85495 as it was
converted to a eventSender test.

* fast/dom/Range/range-created-during-remove-children-expected.txt:
* fast/dom/Range/range-created-during-remove-children.html: Update the test to use unload event
of an iframe since we no longer fire blur event when removing a focused element.
* fast/dom/adopt-node-prevented-expected.txt:
* fast/dom/adopt-node-prevented.html: Ditto.
* fast/dom/remove-body-during-body-replacement2.html: Ditto. Use DOMNodeRemoved instead.
* fast/events/nested-event-remove-node-crash.html: Ditto. Use DOMNodeRemovedFromDocument instead.
* fast/events/onblur-remove-expected.txt:
* fast/events/onblur-remove.html: See above.
* fast/shadow-dom/shadow-root-active-element-crash-expected.txt: Added.
* fast/shadow-dom/shadow-root-active-element-crash.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (201470 => 201471)


--- trunk/LayoutTests/ChangeLog	2016-05-27 22:29:02 UTC (rev 201470)
+++ trunk/LayoutTests/ChangeLog	2016-05-27 22:31:43 UTC (rev 201471)
@@ -1,3 +1,29 @@
+2016-05-26  Ryosuke Niwa  <[email protected]>
+
+        Crash in TreeScope::focusedElement
+        https://bugs.webkit.org/show_bug.cgi?id=158108
+
+        Reviewed by Enrica Casucci.
+
+        Added a regression test for accessing shadowRoot.activeElement after re-focusing an element
+        inside DOMNodeRemovedFromDocument event and unload events.
+
+        This patch also restores the expected result of fast/events/onblur-remove.html to that of when
+        the test was in r15720 and updated in r19014. The expected result was changed in r85495 as it was
+        converted to a eventSender test.
+
+        * fast/dom/Range/range-created-during-remove-children-expected.txt:
+        * fast/dom/Range/range-created-during-remove-children.html: Update the test to use unload event
+        of an iframe since we no longer fire blur event when removing a focused element.
+        * fast/dom/adopt-node-prevented-expected.txt:
+        * fast/dom/adopt-node-prevented.html: Ditto.
+        * fast/dom/remove-body-during-body-replacement2.html: Ditto. Use DOMNodeRemoved instead.
+        * fast/events/nested-event-remove-node-crash.html: Ditto. Use DOMNodeRemovedFromDocument instead.
+        * fast/events/onblur-remove-expected.txt:
+        * fast/events/onblur-remove.html: See above.
+        * fast/shadow-dom/shadow-root-active-element-crash-expected.txt: Added.
+        * fast/shadow-dom/shadow-root-active-element-crash.html: Added.
+
 2016-05-27  Brent Fulgham  <[email protected]>
 
         CSP: Fire 'load' events even when blocking loads via 'frame-src'.

Modified: trunk/LayoutTests/fast/dom/Range/range-created-during-remove-children-expected.txt (201470 => 201471)


--- trunk/LayoutTests/fast/dom/Range/range-created-during-remove-children-expected.txt	2016-05-27 22:29:02 UTC (rev 201470)
+++ trunk/LayoutTests/fast/dom/Range/range-created-during-remove-children-expected.txt	2016-05-27 22:31:43 UTC (rev 201471)
@@ -1,7 +1,7 @@
-PASS ranges["blur"].startContainer is sample
-PASS ranges["blur"].endContainer is sample
-PASS ranges["blur"].startOffset is 0
-PASS ranges["blur"].endOffset is 0
+PASS ranges["unload"].startContainer is sample
+PASS ranges["unload"].endContainer is sample
+PASS ranges["unload"].startOffset is 0
+PASS ranges["unload"].endOffset is 0
 PASS ranges["DOMNodeRemovedFromDocument"].startContainer is sample
 PASS ranges["DOMNodeRemovedFromDocument"].endContainer is sample
 PASS ranges["DOMNodeRemovedFromDocument"].startOffset is 0

Modified: trunk/LayoutTests/fast/dom/Range/range-created-during-remove-children.html (201470 => 201471)


--- trunk/LayoutTests/fast/dom/Range/range-created-during-remove-children.html	2016-05-27 22:29:02 UTC (rev 201470)
+++ trunk/LayoutTests/fast/dom/Range/range-created-during-remove-children.html	2016-05-27 22:31:43 UTC (rev 201471)
@@ -1,6 +1,6 @@
 <div id="container">
 <p id="description"></p>
-<div id="sample"><span contenteditable="true">foobar</span></div>
+<div id="sample"><span contenteditable="true">foobar<iframe id="target"></iframe></span></div>
 </div>
 <div id="console"></div>
 <script src=""
@@ -16,16 +16,16 @@
     ranges[event.type].selectNodeContents(sample.firstChild.firstChild);
 }
 
-document.addEventListener('blur', eventHandler, true);
+document.querySelector('iframe').contentWindow.addEventListener('unload', eventHandler, true);
 document.addEventListener('DOMNodeRemovedFromDocument', eventHandler, true);
 
 sample.firstChild.focus();
 sample.innerHTML = '';
 
-shouldBe('ranges["blur"].startContainer', 'sample');
-shouldBe('ranges["blur"].endContainer', 'sample');
-shouldBe('ranges["blur"].startOffset', '0');
-shouldBe('ranges["blur"].endOffset', '0');
+shouldBe('ranges["unload"].startContainer', 'sample');
+shouldBe('ranges["unload"].endContainer', 'sample');
+shouldBe('ranges["unload"].startOffset', '0');
+shouldBe('ranges["unload"].endOffset', '0');
 shouldBe('ranges["DOMNodeRemovedFromDocument"].startContainer', 'sample');
 shouldBe('ranges["DOMNodeRemovedFromDocument"].endContainer', 'sample');
 shouldBe('ranges["DOMNodeRemovedFromDocument"].startOffset', '0');

Modified: trunk/LayoutTests/fast/dom/adopt-node-prevented-expected.txt (201470 => 201471)


--- trunk/LayoutTests/fast/dom/adopt-node-prevented-expected.txt	2016-05-27 22:29:02 UTC (rev 201470)
+++ trunk/LayoutTests/fast/dom/adopt-node-prevented-expected.txt	2016-05-27 22:31:43 UTC (rev 201471)
@@ -8,3 +8,4 @@
 TEST COMPLETE
 PASS target.ownerDocument.location is document.location
 
+

Modified: trunk/LayoutTests/fast/dom/adopt-node-prevented.html (201470 => 201471)


--- trunk/LayoutTests/fast/dom/adopt-node-prevented.html	2016-05-27 22:29:02 UTC (rev 201470)
+++ trunk/LayoutTests/fast/dom/adopt-node-prevented.html	2016-05-27 22:31:43 UTC (rev 201471)
@@ -5,14 +5,14 @@
 </head>
 <body>
   <div id="newParent"></div>
-  <a href="" id="target"></a>
+  <iframe href="" id="target"></iframe>
 <script>
 description("Test that adoptNode fails safely if prevented by a DOM mutation.");
 
 function run() {
     newParent = document.getElementById("newParent");
     target = document.getElementById("target");
-    target.addEventListener("blur", function () { newParent.appendChild(target); }, false);
+    target.contentWindow.addEventListener("unload", function () { newParent.appendChild(target); }, false);
     target.focus();
     var anotherDocument = document.implementation.createDocument("", "", null);
 

Modified: trunk/LayoutTests/fast/dom/remove-body-during-body-replacement2.html (201470 => 201471)


--- trunk/LayoutTests/fast/dom/remove-body-during-body-replacement2.html	2016-05-27 22:29:02 UTC (rev 201470)
+++ trunk/LayoutTests/fast/dom/remove-body-during-body-replacement2.html	2016-05-27 22:31:43 UTC (rev 201471)
@@ -17,7 +17,7 @@
     }
 
     setTimeout(function () {
-        document.addEventListener('DOMFocusOut', function () { crash(); }, true);
+        document.addEventListener('DOMNodeRemoved', function () { crash(); }, true);
         document.addEventListener('DOMSubtreeModified', function () { /* noop */ }, false);
         document.designMode = "on";
         document.execCommand("SelectAll");

Modified: trunk/LayoutTests/fast/events/nested-event-remove-node-crash.html (201470 => 201471)


--- trunk/LayoutTests/fast/events/nested-event-remove-node-crash.html	2016-05-27 22:29:02 UTC (rev 201470)
+++ trunk/LayoutTests/fast/events/nested-event-remove-node-crash.html	2016-05-27 22:31:43 UTC (rev 201471)
@@ -34,7 +34,9 @@
     3. the focused node's onblur will fire
     4. the onblur event handler will send off an XHR who's handler will remove the node
 */
-    document.getElementById("theSelect").focus();
+    var select = document.getElementById("theSelect");
+    select.addEventListener('DOMNodeRemovedFromDocument', function () { sendXHR();GC(); });
+    select.focus();
     sendXHR();
     
     if (window.testRunner) {

Modified: trunk/LayoutTests/fast/events/onblur-remove-expected.txt (201470 => 201471)


--- trunk/LayoutTests/fast/events/onblur-remove-expected.txt	2016-05-27 22:29:02 UTC (rev 201470)
+++ trunk/LayoutTests/fast/events/onblur-remove-expected.txt	2016-05-27 22:31:43 UTC (rev 201471)
@@ -1,10 +1,9 @@
-This tests that elements shouldn't emit any onblur events when they are being removed from the document. 
-Note, this test is expected to fail as of 04/25/2011. See bug #59379.
+This tests that elements shouldn't emit any onblur events when they are being removed from the document.
 
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
 
-FAIL Onblur handler called.
+PASS blurEventCount is 0
 
 TEST COMPLETE
 

Modified: trunk/LayoutTests/fast/events/onblur-remove.html (201470 => 201471)


--- trunk/LayoutTests/fast/events/onblur-remove.html	2016-05-27 22:29:02 UTC (rev 201470)
+++ trunk/LayoutTests/fast/events/onblur-remove.html	2016-05-27 22:31:43 UTC (rev 201471)
@@ -5,7 +5,7 @@
         if (window.testRunner)
             testRunner.waitUntilDone();
 
-        var numBlurs = 0;
+        var blurEventCount = 0;
 
         window._onload_ = function() { document.getElementById("input").focus(); }
 
@@ -14,10 +14,7 @@
 
             f.innerHTML = '';
 
-            if (numBlurs)
-                testFailed('Onblur handler called.');
-            else
-                testPassed('Onblur handler not called.');
+            shouldBe("blurEventCount", "0");
 
             debug('<br /><span class="pass">TEST COMPLETE</span>');
             if (window.testRunner)
@@ -28,12 +25,11 @@
 <body>
     <p id="description"></p>
     <form id='f'>
-      <input id="input" _onblur_="numBlurs++" _onfocus_="setTimeout('finish()', 0)">
+      <input id="input" _onblur_="blurEventCount++" _onfocus_="setTimeout('finish()', 0)">
     </form>
     <div id="console"></div>
     <script>
-        description("This tests that elements shouldn't emit any onblur events when they are being removed from the document. <br>" +
-                    "Note, this test is expected to fail as of 04/25/2011. See bug #59379.");
+        description("This tests that elements shouldn't emit any onblur events when they are being removed from the document.");
     </script>
   </body>
 </html>

Added: trunk/LayoutTests/fast/shadow-dom/shadow-root-active-element-crash-expected.txt (0 => 201471)


--- trunk/LayoutTests/fast/shadow-dom/shadow-root-active-element-crash-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/fast/shadow-dom/shadow-root-active-element-crash-expected.txt	2016-05-27 22:31:43 UTC (rev 201471)
@@ -0,0 +1,4 @@
+This tests removing a focused element from a document and calling activeElement on a shadow tree in the same document.
+WebKit should clear the focused element even if the removed element was focused during removal and should not crash or hit an assertion.
+
+PASS - did not crash

Added: trunk/LayoutTests/fast/shadow-dom/shadow-root-active-element-crash.html (0 => 201471)


--- trunk/LayoutTests/fast/shadow-dom/shadow-root-active-element-crash.html	                        (rev 0)
+++ trunk/LayoutTests/fast/shadow-dom/shadow-root-active-element-crash.html	2016-05-27 22:31:43 UTC (rev 201471)
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html>
+<body>
+<p>This tests removing a focused element from a document and calling activeElement on a shadow tree in the same document.<br>
+WebKit should clear the focused element even if the removed element was focused during removal and should not crash or hit an assertion.</p>
+<script>
+
+if (window.testRunner)
+    testRunner.dumpAsText();
+
+var iframe = document.createElement('iframe');
+document.body.appendChild(iframe);
+
+var doc = iframe.contentDocument;
+var host = doc.createElement('div');
+var shadowRoot = host.attachShadow({mode: 'closed'});
+doc.body.appendChild(host);
+
+var input = doc.createElement('input');
+doc.body.appendChild(input);
+input.focus();
+
+input.addEventListener('DOMNodeRemovedFromDocument', function () {
+    input.focus();
+});
+
+// 1. ContainerNode::removeChild
+document.body.appendChild(input);
+shadowRoot.activeElement;
+
+// 2. ContainerNode::removeChildren
+doc.body.appendChild(input);
+input.focus();
+doc.body.innerHTML = '';
+document.body.appendChild(input);
+shadowRoot.activeElement;
+
+
+// 3. ContainerNode::removeChild for disconnecting frames
+var focusableIframe = document.createElement('iframe');
+doc.body.appendChild(host);
+doc.body.appendChild(focusableIframe);
+focusableIframe.focus();
+focusableIframe.contentWindow.addEventListener('unload', function () {
+    focusableIframe.focus();
+});
+document.body.appendChild(focusableIframe);
+shadowRoot.activeElement;
+
+// 4. ContainerNode::removeChildren for disconnecting frames
+focusableIframe = document.createElement('iframe');
+doc.body.appendChild(host);
+doc.body.appendChild(focusableIframe);
+focusableIframe.focus();
+focusableIframe.contentWindow.addEventListener('unload', function () {
+    focusableIframe.focus();
+});
+doc.body.innerHTML = '';
+document.body.appendChild(focusableIframe);
+shadowRoot.activeElement;
+
+document.write('PASS - did not crash');
+
+</script>
+</body>
+</html>

Modified: trunk/Source/WebCore/ChangeLog (201470 => 201471)


--- trunk/Source/WebCore/ChangeLog	2016-05-27 22:29:02 UTC (rev 201470)
+++ trunk/Source/WebCore/ChangeLog	2016-05-27 22:31:43 UTC (rev 201471)
@@ -1,3 +1,45 @@
+2016-05-26  Ryosuke Niwa  <[email protected]>
+
+        Crash in TreeScope::focusedElement
+        https://bugs.webkit.org/show_bug.cgi?id=158108
+
+        Reviewed by Enrica Casucci.
+
+        The bug was caused by a flawed sequence of steps we took to remove an element. When an element is removed,
+        willRemoveChild and willRemoveChildren fire blur events on removed focused element and its ancestors and
+        unload event on any removed iframes. However, it was possible to focus an element on which we had fired blur
+        during an unload event, leaving m_focusedElement point to an element that's not in the document anymore.
+
+        Changing the order doesn't help because that would make it possible to insert the removed iframes back into
+        the document inside a event listener of the blur event, which was specifically fixed by r127534 four years ago.
+
+        Instead, fix the bug by not firing blur and change events on removed nodes. New behavior matches Firefox and HTML5
+        specification: https://html.spec.whatwg.org/multipage/interaction.html#focus-fixup-rule-one
+
+        Test: fast/shadow-dom/shadow-root-active-element-crash.html
+
+        * dom/ContainerNode.cpp:
+        (WebCore::willRemoveChild): Made this function static local since it didn't need to have access to any private
+        member variables. Call Document::nodeWillBeRemoved after disconnecting iframes since unload event handler could
+        allocate new Ranges just like mutation events.
+        (WebCore::willRemoveChildren): Ditto.
+        (WebCore::ContainerNode::removeChild): Removed the calls to removeFullScreenElementOfSubtree and
+        removeFocusedNodeOfSubtree as they're now called in Document::nodeWillBeRemoved.
+        (WebCore::ContainerNode::removeChildren): Ditto.
+        * dom/ContainerNode.h:
+        * dom/Document.cpp:
+        (WebCore::Document::removeFocusedNodeOfSubtree): Don't dispatch blur and change events when a node is removed.
+        (WebCore::Document::setFocusedElement): Added FocusRemovalEventsMode as the third argument. Avoid dispatching blur
+        and change events when FocusRemovalEventsMode::Dispatch is set.
+        (WebCore::Document::nodeChildrenWillBeRemoved): Added calls to removeFullScreenElementOfSubtree and
+        removeFocusedNodeOfSubtree. Also assert that no events are fired within this function. If we ever fire an event here,
+        "unloaded" iframes can be inserted back into a document before ContainerNode::removeChild actually removes them.
+        (WebCore::Document::nodeWillBeRemoved): Ditto.
+        * dom/Document.h:
+        * dom/TreeScope.cpp:
+        (WebCore::TreeScope::focusedElement): Added a release assertion to make sure the focused element is in the document
+        of the tree scope, and added an explicit type check just in case.
+
 2016-05-27  Brent Fulgham  <[email protected]>
 
         CSP: Fire 'load' events even when blocking loads via 'frame-src'.

Modified: trunk/Source/WebCore/dom/ContainerNode.cpp (201470 => 201471)


--- trunk/Source/WebCore/dom/ContainerNode.cpp	2016-05-27 22:29:02 UTC (rev 201470)
+++ trunk/Source/WebCore/dom/ContainerNode.cpp	2016-05-27 22:31:43 UTC (rev 201471)
@@ -456,7 +456,7 @@
     return true;
 }
 
-void ContainerNode::willRemoveChild(Node& child)
+static void willRemoveChild(ContainerNode& container, Node& child)
 {
     ASSERT(child.parentNode());
 
@@ -464,12 +464,16 @@
     child.notifyMutationObserversNodeWillDetach();
     dispatchChildRemovalEvents(child);
 
-    if (child.parentNode() != this)
+    if (child.parentNode() != &container)
         return;
 
-    child.document().nodeWillBeRemoved(child); // e.g. mutation event listener can create a new range.
     if (is<ContainerNode>(child))
         disconnectSubframesIfNeeded(downcast<ContainerNode>(child), RootAndDescendants);
+
+    if (child.parentNode() != &container)
+        return;
+
+    child.document().nodeWillBeRemoved(child); // e.g. mutation event listener can create a new range.
 }
 
 static void willRemoveChildren(ContainerNode& container)
@@ -486,9 +490,10 @@
         dispatchChildRemovalEvents(child.get());
     }
 
+    disconnectSubframesIfNeeded(container, DescendantsOnly);
+
     container.document().nodeChildrenWillBeRemoved(container);
 
-    disconnectSubframesIfNeeded(container, DescendantsOnly);
 }
 
 void ContainerNode::disconnectDescendantFrames()
@@ -514,12 +519,6 @@
 
     Ref<Node> child(oldChild);
 
-    document().removeFocusedNodeOfSubtree(child.ptr());
-
-#if ENABLE(FULLSCREEN_API)
-    document().removeFullScreenElementOfSubtree(&child.get());
-#endif
-
     // Events fired when blurring currently focused node might have moved this
     // child into a different parent.
     if (child->parentNode() != this) {
@@ -527,7 +526,7 @@
         return false;
     }
 
-    willRemoveChild(child);
+    willRemoveChild(*this, child);
 
     // Mutation events might have moved this child into a different parent.
     if (child->parentNode() != this) {
@@ -611,13 +610,6 @@
     // The container node can be removed from event handlers.
     Ref<ContainerNode> protectedThis(*this);
 
-    // exclude this node when looking for removed focusedNode since only children will be removed
-    document().removeFocusedNodeOfSubtree(this, true);
-
-#if ENABLE(FULLSCREEN_API)
-    document().removeFullScreenElementOfSubtree(this, true);
-#endif
-
     // Do any prep work needed before actually starting to detach
     // and remove... e.g. stop loading frames, fire unload events.
     willRemoveChildren(*this);

Modified: trunk/Source/WebCore/dom/ContainerNode.h (201470 => 201471)


--- trunk/Source/WebCore/dom/ContainerNode.h	2016-05-27 22:29:02 UTC (rev 201470)
+++ trunk/Source/WebCore/dom/ContainerNode.h	2016-05-27 22:31:43 UTC (rev 201471)
@@ -130,8 +130,6 @@
 
     bool isContainerNode() const = delete;
 
-    void willRemoveChild(Node& child);
-
     Node* m_firstChild { nullptr };
     Node* m_lastChild { nullptr };
 };

Modified: trunk/Source/WebCore/dom/Document.cpp (201470 => 201471)


--- trunk/Source/WebCore/dom/Document.cpp	2016-05-27 22:29:02 UTC (rev 201470)
+++ trunk/Source/WebCore/dom/Document.cpp	2016-05-27 22:31:43 UTC (rev 201471)
@@ -3700,7 +3700,7 @@
         nodeInSubtree = (focusedElement == node) || focusedElement->isDescendantOf(node);
     
     if (nodeInSubtree)
-        setFocusedElement(nullptr);
+        setFocusedElement(nullptr, FocusDirectionNone, FocusRemovalEventsMode::DoNotDispatch);
 }
 
 void Document::hoveredElementDidDetach(Element* element)
@@ -3738,7 +3738,7 @@
 }
 #endif
 
-bool Document::setFocusedElement(Element* element, FocusDirection direction)
+bool Document::setFocusedElement(Element* element, FocusDirection direction, FocusRemovalEventsMode eventsMode)
 {
     RefPtr<Element> newFocusedElement = element;
     // Make sure newFocusedElement is actually in this document
@@ -3761,33 +3761,36 @@
 
         oldFocusedElement->setFocus(false);
 
-        // Dispatch a change event for form control elements that have been edited.
-        if (is<HTMLFormControlElement>(*oldFocusedElement)) {
-            HTMLFormControlElement& formControlElement = downcast<HTMLFormControlElement>(*oldFocusedElement);
-            if (formControlElement.wasChangedSinceLastFormControlChangeEvent())
-                formControlElement.dispatchFormControlChangeEvent();
-        }
+        if (eventsMode == FocusRemovalEventsMode::Dispatch) {
+            // Dispatch a change event for form control elements that have been edited.
+            if (is<HTMLFormControlElement>(*oldFocusedElement)) {
+                HTMLFormControlElement& formControlElement = downcast<HTMLFormControlElement>(*oldFocusedElement);
+                if (formControlElement.wasChangedSinceLastFormControlChangeEvent())
+                    formControlElement.dispatchFormControlChangeEvent();
+            }
 
-        // Dispatch the blur event and let the node do any other blur related activities (important for text fields)
-        oldFocusedElement->dispatchBlurEvent(newFocusedElement.copyRef());
+            // Dispatch the blur event and let the node do any other blur related activities (important for text fields)
+            oldFocusedElement->dispatchBlurEvent(newFocusedElement.copyRef());
 
-        if (m_focusedElement) {
-            // handler shifted focus
-            focusChangeBlocked = true;
-            newFocusedElement = nullptr;
-        }
-        
-        oldFocusedElement->dispatchFocusOutEvent(eventNames().focusoutEvent, newFocusedElement.copyRef()); // DOM level 3 name for the bubbling blur event.
-        // FIXME: We should remove firing DOMFocusOutEvent event when we are sure no content depends
-        // on it, probably when <rdar://problem/8503958> is resolved.
-        oldFocusedElement->dispatchFocusOutEvent(eventNames().DOMFocusOutEvent, newFocusedElement.copyRef()); // DOM level 2 name for compatibility.
+            if (m_focusedElement) {
+                // handler shifted focus
+                focusChangeBlocked = true;
+                newFocusedElement = nullptr;
+            }
 
-        if (m_focusedElement) {
-            // handler shifted focus
-            focusChangeBlocked = true;
-            newFocusedElement = nullptr;
-        }
-            
+            oldFocusedElement->dispatchFocusOutEvent(eventNames().focusoutEvent, newFocusedElement.copyRef()); // DOM level 3 name for the bubbling blur event.
+            // FIXME: We should remove firing DOMFocusOutEvent event when we are sure no content depends
+            // on it, probably when <rdar://problem/8503958> is resolved.
+            oldFocusedElement->dispatchFocusOutEvent(eventNames().DOMFocusOutEvent, newFocusedElement.copyRef()); // DOM level 2 name for compatibility.
+
+            if (m_focusedElement) {
+                // handler shifted focus
+                focusChangeBlocked = true;
+                newFocusedElement = nullptr;
+            }
+        } else
+            ASSERT(!m_focusedElement);
+
         if (oldFocusedElement->isRootEditableElement())
             frame()->editor().didEndEditing();
 
@@ -3967,6 +3970,14 @@
 
 void Document::nodeChildrenWillBeRemoved(ContainerNode& container)
 {
+    NoEventDispatchAssertion assertNoEventDispatch;
+
+    removeFocusedNodeOfSubtree(&container, true /* amongChildrenOnly */);
+
+#if ENABLE(FULLSCREEN_API)
+    removeFullScreenElementOfSubtree(&container, true /* amongChildrenOnly */);
+#endif
+
     for (auto* range : m_ranges)
         range->nodeChildrenWillBeRemoved(container);
 
@@ -3991,6 +4002,14 @@
 
 void Document::nodeWillBeRemoved(Node& n)
 {
+    NoEventDispatchAssertion assertNoEventDispatch;
+
+    removeFocusedNodeOfSubtree(&n);
+
+#if ENABLE(FULLSCREEN_API)
+    removeFullScreenElementOfSubtree(&n);
+#endif
+
     for (auto* it : m_nodeIterators)
         it->nodeWillBeRemoved(n);
 

Modified: trunk/Source/WebCore/dom/Document.h (201470 => 201471)


--- trunk/Source/WebCore/dom/Document.h	2016-05-27 22:29:02 UTC (rev 201470)
+++ trunk/Source/WebCore/dom/Document.h	2016-05-27 22:31:43 UTC (rev 201471)
@@ -722,7 +722,9 @@
     String selectedStylesheetSet() const;
     void setSelectedStylesheetSet(const String&);
 
-    WEBCORE_EXPORT bool setFocusedElement(Element*, FocusDirection = FocusDirectionNone);
+    enum class FocusRemovalEventsMode { Dispatch, DoNotDispatch };
+    WEBCORE_EXPORT bool setFocusedElement(Element*, FocusDirection = FocusDirectionNone,
+        FocusRemovalEventsMode = FocusRemovalEventsMode::Dispatch);
     Element* focusedElement() const { return m_focusedElement.get(); }
     UserActionElementSet& userActionElements()  { return m_userActionElements; }
     const UserActionElementSet& userActionElements() const { return m_userActionElements; }

Modified: trunk/Source/WebCore/dom/TreeScope.cpp (201470 => 201471)


--- trunk/Source/WebCore/dom/TreeScope.cpp	2016-05-27 22:29:02 UTC (rev 201470)
+++ trunk/Source/WebCore/dom/TreeScope.cpp	2016-05-27 22:31:43 UTC (rev 201471)
@@ -311,8 +311,13 @@
     if (!element)
         return nullptr;
     TreeScope* treeScope = &element->treeScope();
+    RELEASE_ASSERT(&document == &treeScope->documentScope());
     while (treeScope != this && treeScope != &document) {
-        element = downcast<ShadowRoot>(treeScope->rootNode()).host();
+        auto& rootNode = treeScope->rootNode();
+        if (is<ShadowRoot>(rootNode))
+            element = downcast<ShadowRoot>(rootNode).host();
+        else
+            return nullptr;
         treeScope = &element->treeScope();
     }
     if (this != treeScope)
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to