Adopting a child node of a script element can run script
authorbfulgham@apple.com <bfulgham@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 9 Feb 2017 17:59:45 +0000 (17:59 +0000)
committerbfulgham@apple.com <bfulgham@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 9 Feb 2017 17:59:45 +0000 (17:59 +0000)
https://bugs.webkit.org/show_bug.cgi?id=167318

Patch by Ryosuke Niwa <rniwa@webkit.org> on 2017-02-09
Reviewed by Darin Adler.

Source/WebCore:

The bug was caused by ScriptElement::childrenChanged indiscriminately running the script.
Do this only if some node has been inserted as spec'ed:

https://html.spec.whatwg.org/multipage/scripting.html#script-processing-model
"The script element is connected and a node or document fragment is inserted into
the script element, after any script elements inserted at that time."

Split NonContentsChildChanged into NonContentsChildInserted and NonContentsChildRemoved to disambiguate
non-contents child such as text and element being removed or inserted. New behavior matches that of
Gecko and Chrome as well as the latest HTML5 specification.

Also deploy NoEventDispatchAssertion in more places. Unfortunately, this results in some DOM trees
internal to WebKit to be mutated while there is NoEventDispatchAssertion in the stack. Added a new RAII
object "EventAllowedScope" to temporarily disable this assertion within such a tree. CachedSVGFont's
ensureCustomFontData used to completely disable this assertion but we no longer have to do this either.

To clarify the new semantics, renamed isEventDispatchForbidden to isEventAllowedInMainThread with
the negated boolean value, and added a new variant isEventDispatchAllowedInSubtree, which checks
isEventDispatchForbidden() is true or if the node was one of an internal DOM node or its descendent
held by EventAllowedScope.

Inspired by https://chromium.googlesource.com/chromium/src/+/604e798ec6ee30f44d57a5c4a44ce3dab3a871ed

Tests: fast/html/script-must-not-run-when-child-is-adopted.html
       fast/html/script-must-not-run-when-child-is-removed.html

* dom/CharacterData.cpp:
(WebCore::CharacterData::notifyParentAfterChange): Added NoEventDispatchAssertion.
* dom/ContainerNode.cpp:
(WebCore::ContainerNode::insertBefore): Added NoEventDispatchAssertion around TreeScope's adoptIfNeeded
and insertBeforeCommon as done elsewhere.
(WebCore::ContainerNode::appendChildCommon): Added NoEventDispatchAssertion.
(WebCore::ContainerNode::changeForChildInsertion): Use NonContentsChildInserted here.
(WebCore::ContainerNode::notifyChildRemoved): Added NoEventDispatchAssertion.
(WebCore::ContainerNode::replaceChild): Moved adoptIfNeeded into NoEventDispatchAssertion.
(WebCore::ContainerNode::removeChild): Added NoEventDispatchAssertion.
(WebCore::ContainerNode::parserRemoveChild): Added NoEventDispatchAssertion.
(WebCore::ContainerNode::removeChildren): Call childrenChanged in NoEventDispatchAssertion.
(WebCore::ContainerNode::appendChildWithoutPreInsertionValidityCheck): Moved adoptIfNeeded into
NoEventDispatchAssertion.
(WebCore::dispatchChildInsertionEvents): Check the forbidden-ness more precisely.
(WebCore::dispatchChildRemovalEvents): Ditto.
* dom/ContainerNode.h:
(WebCore::ContainerNode::ChildChange::isInsertion): Added.
* dom/ContainerNodeAlgorithms.cpp:
(WebCore::notifyChildNodeInserted): Check the forbidden-ness more precisely. Here, we check against
insertionPoint since EventAllowedScope checks against the root node.
* dom/Document.cpp:
(WebCore::Document::adoptNode): Assert the node to be adopted has not been inserted back, or else
remove() had resulted in an exception before calling TreeScope::adoptIfNeeded.
* dom/Element.cpp:
(WebCore::Element::childrenChanged):
* dom/NoEventDispatchAssertion.h:
(WebCore::NoEventDispatchAssertion::isEventDispatchForbidden): Added a new variant that takes a node.
If this node is a descendent of a node "marked as safe" by EventAllowedScope, then we don't consider
the event dispatch to be forbidden.
(WebCore::NoEventDispatchAssertion::dropTemporarily): Deleted.
(WebCore::NoEventDispatchAssertion::restoreDropped): Deleted.
(WebCore::NoEventDispatchAssertion::EventAllowedScope): Added. A RAII object which marks descendants of
a given node as "safe" for the purpose of checking isEventDispatchForbidden.
(WebCore::NoEventDispatchAssertion::EventAllowedScope::EventAllowedScope): Added. There can be a chain
of EventAllowedScope objects in the stack. s_currentScope points to the most recently instantiated
RAII object, and each instance remembers prior instance.
(WebCore::NoEventDispatchAssertion::EventAllowedScope::~EventAllowedScope): Added.
(WebCore::NoEventDispatchAssertion::EventAllowedScope::isAllowedNode): Added. Returns true if the given
node is a descendent of any node held by instances of EventAllowedScope.
(WebCore::NoEventDispatchAssertion::EventAllowedScope::isAllowedNodeInternal): Added. A helper function
for isAllowedNode.
* dom/Node.cpp:
(WebCore::Node::dispatchSubtreeModifiedEvent): Check the forbidden-ness more precisely.
* dom/ScriptElement.cpp:
(WebCore::ScriptElement::childrenChanged): Only prepare the script if we've inserted nodes.
(WebCore::ScriptElement::executeClassicScript): Assert isEventDispatchForbidden is false since running
arbitrary author scripts can, indeed, result dispatch any events.
* dom/ScriptElement.h:
* html/HTMLElement.cpp:
(WebCore::textToFragment): Made this a static local function and not return an exception since there
is no way appendChild called in this function can throw an exception.
(WebCore::HTMLElement::setInnerText): Create EventAllowedScope for the fragment. It's called called by
HTMLTextAreaElement's childrenChanged to update its UA shadow tree, and it's dispatching as event on
a new fragment can't execute arbitrary scripts since it has never been exposed to author scripts.
Because of the precise-ness of this check, this does not disable the assertion for "this" element.
HTMLTextFormControlElement::setInnerTextValue explicitly creates another EventAllowedScope to mark
the shadow tree into which the fragment is inserted safe.
(WebCore::HTMLElement::setOuterText):
* html/HTMLElement.h:
* html/HTMLScriptElement.cpp:
(WebCore::HTMLScriptElement::childrenChanged):
* html/HTMLTextFormControlElement.cpp:
(WebCore::HTMLTextFormControlElement::setInnerTextValue): See above (setInnerText).
* html/track/VTTCue.cpp:
(WebCore::VTTCue::createCueRenderingTree): Create EventAllowedScope for the cloned fragment here since
the VTT tree is never exposed to author scripts.
(WebCore::VTTCue::updateDisplayTree): Ditto.
* loader/cache/CachedSVGFont.cpp:
(WebCore::CachedSVGFont::ensureCustomFontData): Use EventAllowedScope to disable assertions only on
the new SVG document we just created instead of disabling for all DOM trees.
* svg/SVGScriptElement.cpp:
(WebCore::SVGScriptElement::childrenChanged):

LayoutTests:

Added regression tests for adopting or removing a child node of a script element.
The script must not run when nodes are adopted or removed.

* fast/html/script-must-not-run-when-child-is-adopted-expected.txt: Added.
* fast/html/script-must-not-run-when-child-is-adopted.html: Added.
* fast/html/script-must-not-run-when-child-is-removed-expected.txt: Added.
* fast/html/script-must-not-run-when-child-is-removed.html: Added.

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

27 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/html/script-must-not-run-when-child-is-adopted-expected.txt [new file with mode: 0644]
LayoutTests/fast/html/script-must-not-run-when-child-is-adopted.html [new file with mode: 0644]
LayoutTests/fast/html/script-must-not-run-when-child-is-removed-expected.txt [new file with mode: 0644]
LayoutTests/fast/html/script-must-not-run-when-child-is-removed.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/dom/CharacterData.cpp
Source/WebCore/dom/ContainerNode.cpp
Source/WebCore/dom/ContainerNode.h
Source/WebCore/dom/ContainerNodeAlgorithms.cpp
Source/WebCore/dom/Document.cpp
Source/WebCore/dom/Element.cpp
Source/WebCore/dom/EventDispatcher.cpp
Source/WebCore/dom/EventTarget.cpp
Source/WebCore/dom/NoEventDispatchAssertion.h
Source/WebCore/dom/Node.cpp
Source/WebCore/dom/ScriptElement.cpp
Source/WebCore/dom/ScriptElement.h
Source/WebCore/dom/WebKitNamedFlow.cpp
Source/WebCore/html/HTMLElement.cpp
Source/WebCore/html/HTMLElement.h
Source/WebCore/html/HTMLMediaElement.cpp
Source/WebCore/html/HTMLScriptElement.cpp
Source/WebCore/html/HTMLTextFormControlElement.cpp
Source/WebCore/html/track/VTTCue.cpp
Source/WebCore/loader/cache/CachedSVGFont.cpp
Source/WebCore/svg/SVGScriptElement.cpp

index b86e5f9..8820aa0 100644 (file)
@@ -1,3 +1,18 @@
+2017-02-09  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Adopting a child node of a script element can run script
+        https://bugs.webkit.org/show_bug.cgi?id=167318
+
+        Reviewed by Darin Adler.
+
+        Added regression tests for adopting or removing a child node of a script element.
+        The script must not run when nodes are adopted or removed.
+
+        * fast/html/script-must-not-run-when-child-is-adopted-expected.txt: Added.
+        * fast/html/script-must-not-run-when-child-is-adopted.html: Added.
+        * fast/html/script-must-not-run-when-child-is-removed-expected.txt: Added.
+        * fast/html/script-must-not-run-when-child-is-removed.html: Added.
+
 2017-02-09  Eric Carlson  <eric.carlson@apple.com>
 
         [MediaStream] Remove legacy Navigator.webkitGetUserMedia
diff --git a/LayoutTests/fast/html/script-must-not-run-when-child-is-adopted-expected.txt b/LayoutTests/fast/html/script-must-not-run-when-child-is-adopted-expected.txt
new file mode 100644 (file)
index 0000000..b4df41a
--- /dev/null
@@ -0,0 +1,20 @@
+Adopting a script element must not run the script.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+Testing HTMLScriptElement
+PASS didRunScript is false
+PASS div.parentNode is not doc.body
+PASS div.parentNode is null
+PASS div.ownerDocument is document
+
+Testing SVGScriptElement
+PASS didRunScript is false
+PASS div.parentNode is not doc.body
+PASS div.parentNode is null
+PASS div.ownerDocument is document
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/fast/html/script-must-not-run-when-child-is-adopted.html b/LayoutTests/fast/html/script-must-not-run-when-child-is-adopted.html
new file mode 100644 (file)
index 0000000..e148d33
--- /dev/null
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div id="test-container"></div>
+<script src="../../resources/js-test.js"></script>
+<script>
+
+description('Adopting a script element must not run the script.');
+
+let testContainer = document.getElementById('test-container');
+
+var doc;
+var didRunScript;
+var div;
+function runTest(createScriptElement)
+{
+    didRunScript = false;
+    div = document.createElement('div');
+
+    let iframe = document.createElement('iframe');
+    testContainer.appendChild(iframe);
+
+    doc = iframe.contentDocument;
+    let script = createScriptElement(doc);
+    script.type = 'invalid-type';
+    script.textContent = 'parent.didRunScript = true; document.body.appendChild(parent.div)';
+
+    script.appendChild(div);
+    doc.body.appendChild(script);
+
+    script.type = '';
+    document.adoptNode(div);
+    testContainer.innerHTML = '';
+
+    shouldBeFalse('didRunScript');
+    shouldNotBe('div.parentNode', 'doc.body');
+    shouldBe('div.parentNode', 'null');
+    shouldBe('div.ownerDocument', 'document');
+}
+
+debug('Testing HTMLScriptElement');
+runTest((doc) => doc.createElement('script'));
+
+debug('');
+debug('Testing SVGScriptElement');
+runTest((doc) => doc.createElementNS('http://www.w3.org/2000/svg', 'script'));
+
+</script>
+</body>
+</html>
diff --git a/LayoutTests/fast/html/script-must-not-run-when-child-is-removed-expected.txt b/LayoutTests/fast/html/script-must-not-run-when-child-is-removed-expected.txt
new file mode 100644 (file)
index 0000000..ae36b1c
--- /dev/null
@@ -0,0 +1,15 @@
+Adopting a script element must not run the script.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS script = scriptElement("html"); script.removeChild(textChild); didRunScript is false
+PASS script = scriptElement("svg"); script.removeChild(textChild); didRunScript is false
+PASS script = scriptElement("html"); script.removeChild(elementChild); didRunScript is false
+PASS script = scriptElement("svg"); script.removeChild(elementChild); didRunScript is false
+PASS script = scriptElement("html"); script.removeChild(commentChild); didRunScript is false
+PASS script = scriptElement("svg"); script.removeChild(commentChild); didRunScript is false
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/fast/html/script-must-not-run-when-child-is-removed.html b/LayoutTests/fast/html/script-must-not-run-when-child-is-removed.html
new file mode 100644 (file)
index 0000000..5dd33a2
--- /dev/null
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div id="test-container"></div>
+<script src="../../resources/js-test.js"></script>
+<script>
+
+description('Adopting a script element must not run the script.');
+
+let testContainer = document.getElementById('test-container');
+
+var didRunScript;
+var textChild;
+var elementChild;
+var commentChild;
+function scriptElement(type)
+{
+    didRunScript = false;
+
+    let namespaceURI = type == 'html' ? 'http://www.w3.org/1999/xhtml' : 'http://www.w3.org/2000/svg';
+    let script = document.createElementNS(namespaceURI, 'script');
+    script.type = 'invalid-type';
+    script.textContent = 'didRunScript = true';
+
+    document.body.appendChild(script);
+
+    textChild = document.createTextNode('');
+    script.appendChild(textChild);
+
+    elementChild = document.createElement('div');
+    script.appendChild(elementChild);
+
+    commentChild = document.createComment('');
+    script.appendChild(commentChild);
+
+    script.type = "";
+    return script;
+}
+
+shouldBeFalse('script = scriptElement("html"); script.removeChild(textChild); didRunScript');
+shouldBeFalse('script = scriptElement("svg"); script.removeChild(textChild); didRunScript');
+
+shouldBeFalse('script = scriptElement("html"); script.removeChild(elementChild); didRunScript');
+shouldBeFalse('script = scriptElement("svg"); script.removeChild(elementChild); didRunScript');
+
+shouldBeFalse('script = scriptElement("html"); script.removeChild(commentChild); didRunScript');
+shouldBeFalse('script = scriptElement("svg"); script.removeChild(commentChild); didRunScript');
+
+</script>
+</body>
+</html>
index f0ced7e..aad373f 100644 (file)
@@ -1,3 +1,110 @@
+2017-02-09  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Adopting a child node of a script element can run script
+        https://bugs.webkit.org/show_bug.cgi?id=167318
+
+        Reviewed by Darin Adler.
+
+        The bug was caused by ScriptElement::childrenChanged indiscriminately running the script.
+        Do this only if some node has been inserted as spec'ed:
+
+        https://html.spec.whatwg.org/multipage/scripting.html#script-processing-model
+        "The script element is connected and a node or document fragment is inserted into
+        the script element, after any script elements inserted at that time."
+
+        Split NonContentsChildChanged into NonContentsChildInserted and NonContentsChildRemoved to disambiguate
+        non-contents child such as text and element being removed or inserted. New behavior matches that of
+        Gecko and Chrome as well as the latest HTML5 specification.
+
+        Also deploy NoEventDispatchAssertion in more places. Unfortunately, this results in some DOM trees
+        internal to WebKit to be mutated while there is NoEventDispatchAssertion in the stack. Added a new RAII
+        object "EventAllowedScope" to temporarily disable this assertion within such a tree. CachedSVGFont's
+        ensureCustomFontData used to completely disable this assertion but we no longer have to do this either.
+
+        To clarify the new semantics, renamed isEventDispatchForbidden to isEventAllowedInMainThread with
+        the negated boolean value, and added a new variant isEventDispatchAllowedInSubtree, which checks
+        isEventDispatchForbidden() is true or if the node was one of an internal DOM node or its descendent
+        held by EventAllowedScope.
+
+        Inspired by https://chromium.googlesource.com/chromium/src/+/604e798ec6ee30f44d57a5c4a44ce3dab3a871ed
+
+        Tests: fast/html/script-must-not-run-when-child-is-adopted.html
+               fast/html/script-must-not-run-when-child-is-removed.html
+
+        * dom/CharacterData.cpp:
+        (WebCore::CharacterData::notifyParentAfterChange): Added NoEventDispatchAssertion.
+        * dom/ContainerNode.cpp:
+        (WebCore::ContainerNode::insertBefore): Added NoEventDispatchAssertion around TreeScope's adoptIfNeeded
+        and insertBeforeCommon as done elsewhere.
+        (WebCore::ContainerNode::appendChildCommon): Added NoEventDispatchAssertion.
+        (WebCore::ContainerNode::changeForChildInsertion): Use NonContentsChildInserted here.
+        (WebCore::ContainerNode::notifyChildRemoved): Added NoEventDispatchAssertion.
+        (WebCore::ContainerNode::replaceChild): Moved adoptIfNeeded into NoEventDispatchAssertion.
+        (WebCore::ContainerNode::removeChild): Added NoEventDispatchAssertion.
+        (WebCore::ContainerNode::parserRemoveChild): Added NoEventDispatchAssertion.
+        (WebCore::ContainerNode::removeChildren): Call childrenChanged in NoEventDispatchAssertion.
+        (WebCore::ContainerNode::appendChildWithoutPreInsertionValidityCheck): Moved adoptIfNeeded into
+        NoEventDispatchAssertion.
+        (WebCore::dispatchChildInsertionEvents): Check the forbidden-ness more precisely.
+        (WebCore::dispatchChildRemovalEvents): Ditto.
+        * dom/ContainerNode.h:
+        (WebCore::ContainerNode::ChildChange::isInsertion): Added.
+        * dom/ContainerNodeAlgorithms.cpp:
+        (WebCore::notifyChildNodeInserted): Check the forbidden-ness more precisely. Here, we check against
+        insertionPoint since EventAllowedScope checks against the root node.
+        * dom/Document.cpp:
+        (WebCore::Document::adoptNode): Assert the node to be adopted has not been inserted back, or else
+        remove() had resulted in an exception before calling TreeScope::adoptIfNeeded.
+        * dom/Element.cpp:
+        (WebCore::Element::childrenChanged):
+        * dom/NoEventDispatchAssertion.h:
+        (WebCore::NoEventDispatchAssertion::isEventDispatchForbidden): Added a new variant that takes a node.
+        If this node is a descendent of a node "marked as safe" by EventAllowedScope, then we don't consider
+        the event dispatch to be forbidden.
+        (WebCore::NoEventDispatchAssertion::dropTemporarily): Deleted.
+        (WebCore::NoEventDispatchAssertion::restoreDropped): Deleted.
+        (WebCore::NoEventDispatchAssertion::EventAllowedScope): Added. A RAII object which marks descendants of
+        a given node as "safe" for the purpose of checking isEventDispatchForbidden.
+        (WebCore::NoEventDispatchAssertion::EventAllowedScope::EventAllowedScope): Added. There can be a chain
+        of EventAllowedScope objects in the stack. s_currentScope points to the most recently instantiated
+        RAII object, and each instance remembers prior instance. 
+        (WebCore::NoEventDispatchAssertion::EventAllowedScope::~EventAllowedScope): Added.
+        (WebCore::NoEventDispatchAssertion::EventAllowedScope::isAllowedNode): Added. Returns true if the given
+        node is a descendent of any node held by instances of EventAllowedScope.
+        (WebCore::NoEventDispatchAssertion::EventAllowedScope::isAllowedNodeInternal): Added. A helper function
+        for isAllowedNode.
+        * dom/Node.cpp:
+        (WebCore::Node::dispatchSubtreeModifiedEvent): Check the forbidden-ness more precisely.
+        * dom/ScriptElement.cpp:
+        (WebCore::ScriptElement::childrenChanged): Only prepare the script if we've inserted nodes.
+        (WebCore::ScriptElement::executeClassicScript): Assert isEventDispatchForbidden is false since running
+        arbitrary author scripts can, indeed, result dispatch any events.
+        * dom/ScriptElement.h:
+        * html/HTMLElement.cpp:
+        (WebCore::textToFragment): Made this a static local function and not return an exception since there
+        is no way appendChild called in this function can throw an exception.
+        (WebCore::HTMLElement::setInnerText): Create EventAllowedScope for the fragment. It's called called by
+        HTMLTextAreaElement's childrenChanged to update its UA shadow tree, and it's dispatching as event on
+        a new fragment can't execute arbitrary scripts since it has never been exposed to author scripts.
+        Because of the precise-ness of this check, this does not disable the assertion for "this" element.
+        HTMLTextFormControlElement::setInnerTextValue explicitly creates another EventAllowedScope to mark
+        the shadow tree into which the fragment is inserted safe.
+        (WebCore::HTMLElement::setOuterText):
+        * html/HTMLElement.h:
+        * html/HTMLScriptElement.cpp:
+        (WebCore::HTMLScriptElement::childrenChanged):
+        * html/HTMLTextFormControlElement.cpp:
+        (WebCore::HTMLTextFormControlElement::setInnerTextValue): See above (setInnerText).
+        * html/track/VTTCue.cpp:
+        (WebCore::VTTCue::createCueRenderingTree): Create EventAllowedScope for the cloned fragment here since
+        the VTT tree is never exposed to author scripts.
+        (WebCore::VTTCue::updateDisplayTree): Ditto.
+        * loader/cache/CachedSVGFont.cpp:
+        (WebCore::CachedSVGFont::ensureCustomFontData): Use EventAllowedScope to disable assertions only on
+        the new SVG document we just created instead of disabling for all DOM trees.
+        * svg/SVGScriptElement.cpp:
+        (WebCore::SVGScriptElement::childrenChanged):
+
 2017-02-09  Andreas Kling  <akling@apple.com>
 
         Document should always have a Settings.
index 8b63810..e00b20c 100644 (file)
@@ -30,6 +30,7 @@
 #include "MutationEvent.h"
 #include "MutationObserverInterestGroup.h"
 #include "MutationRecord.h"
+#include "NoEventDispatchAssertion.h"
 #include "ProcessingInstruction.h"
 #include "RenderText.h"
 #include "StyleInheritedData.h"
@@ -207,6 +208,8 @@ void CharacterData::setDataAndUpdate(const String& newData, unsigned offsetOfRep
 
 void CharacterData::notifyParentAfterChange(ContainerNode::ChildChangeSource source)
 {
+    NoEventDispatchAssertion assertNoEventDispatch;
+
     document().incDOMTreeVersion();
 
     if (!parentNode())
index a5d79d3..4d5cf1e 100644 (file)
@@ -71,6 +71,7 @@ ChildNodesLazySnapshot* ChildNodesLazySnapshot::latestSnapshot;
 
 #ifndef NDEBUG
 unsigned NoEventDispatchAssertion::s_count = 0;
+NoEventDispatchAssertion::EventAllowedScope* NoEventDispatchAssertion::EventAllowedScope::s_currentScope = nullptr;
 #endif
 
 static ExceptionOr<void> collectChildrenAndRemoveFromOldParent(Node& node, NodeVector& nodes)
@@ -279,9 +280,12 @@ ExceptionOr<void> ContainerNode::insertBefore(Node& newChild, Node* refChild)
         if (child->parentNode())
             break;
 
-        treeScope().adoptIfNeeded(child);
+        {
+            NoEventDispatchAssertion assertNoEventDispatch;
 
-        insertBeforeCommon(next, child);
+            treeScope().adoptIfNeeded(child);
+            insertBeforeCommon(next, child);
+        }
 
         updateTreeAfterInsertion(child);
     }
@@ -317,6 +321,8 @@ void ContainerNode::insertBeforeCommon(Node& nextChild, Node& newChild)
 
 void ContainerNode::appendChildCommon(Node& child)
 {
+    NoEventDispatchAssertion assertNoEventDispatch;
+
     child.setParentNode(this);
 
     if (m_lastChild) {
@@ -334,7 +340,7 @@ inline auto ContainerNode::changeForChildInsertion(Node& child, ChildChangeSourc
         return { AllChildrenReplaced, nullptr, nullptr, source };
 
     return {
-        child.isElementNode() ? ElementInserted : child.isTextNode() ? TextInserted : NonContentsChildChanged,
+        child.isElementNode() ? ElementInserted : child.isTextNode() ? TextInserted : NonContentsChildInserted,
         ElementTraversal::previousSibling(child),
         ElementTraversal::nextSibling(child),
         source
@@ -356,10 +362,11 @@ void ContainerNode::notifyChildInserted(Node& child, const ChildChange& change)
 
 void ContainerNode::notifyChildRemoved(Node& child, Node* previousSibling, Node* nextSibling, ChildChangeSource source)
 {
+    NoEventDispatchAssertion assertNoEventDispatch;
     notifyChildNodeRemoved(*this, child);
 
     ChildChange change;
-    change.type = is<Element>(child) ? ElementRemoved : is<Text>(child) ? TextRemoved : NonContentsChildChanged;
+    change.type = is<Element>(child) ? ElementRemoved : is<Text>(child) ? TextRemoved : NonContentsChildRemoved;
     change.previousSiblingElement = (!previousSibling || is<Element>(*previousSibling)) ? downcast<Element>(previousSibling) : ElementTraversal::previousSibling(*previousSibling);
     change.nextSiblingElement = (!nextSibling || is<Element>(*nextSibling)) ? downcast<Element>(nextSibling) : ElementTraversal::nextSibling(*nextSibling);
     change.source = source;
@@ -450,10 +457,9 @@ ExceptionOr<void> ContainerNode::replaceChild(Node& newChild, Node& oldChild)
         if (child->parentNode())
             break;
 
-        treeScope().adoptIfNeeded(child);
-
         {
             NoEventDispatchAssertion assertNoEventDispatch;
+            treeScope().adoptIfNeeded(child);
             if (refChild)
                 insertBeforeCommon(*refChild, child.get());
             else
@@ -534,6 +540,7 @@ ExceptionOr<void> ContainerNode::removeChild(Node& oldChild)
 
     {
         WidgetHierarchyUpdatesSuspensionScope suspendWidgetHierarchyUpdates;
+        NoEventDispatchAssertion assertNoEventDispatch;
 
         Node* prev = child->previousSibling();
         Node* next = child->nextSibling();
@@ -584,6 +591,8 @@ void ContainerNode::removeBetween(Node* previousChild, Node* nextChild, Node& ol
 
 void ContainerNode::parserRemoveChild(Node& oldChild)
 {
+    NoEventDispatchAssertion assertNoEventDispatch;
+
     ASSERT(oldChild.parentNode() == this);
     ASSERT(!oldChild.isDocumentFragment());
 
@@ -675,12 +684,11 @@ void ContainerNode::removeChildren()
 
     {
         WidgetHierarchyUpdatesSuspensionScope suspendWidgetHierarchyUpdates;
-        {
-            NoEventDispatchAssertion assertNoEventDispatch;
-            while (RefPtr<Node> child = m_firstChild) {
-                removeBetween(0, child->nextSibling(), *child);
-                notifyChildNodeRemoved(*this, *child);
-            }
+        NoEventDispatchAssertion assertNoEventDispatch;
+
+        while (RefPtr<Node> child = m_firstChild) {
+            removeBetween(0, child->nextSibling(), *child);
+            notifyChildNodeRemoved(*this, *child);
         }
 
         ChildChange change = { AllChildrenRemoved, nullptr, nullptr, ChildChangeSourceAPI };
@@ -733,11 +741,10 @@ ExceptionOr<void> ContainerNode::appendChildWithoutPreInsertionValidityCheck(Nod
         if (child->parentNode())
             break;
 
-        treeScope().adoptIfNeeded(child);
-
         // Append child to the end of the list
         {
             NoEventDispatchAssertion assertNoEventDispatch;
+            treeScope().adoptIfNeeded(child);
             appendChildCommon(child);
         }
 
@@ -807,7 +814,7 @@ static void dispatchChildInsertionEvents(Node& child)
     if (child.isInShadowTree())
         return;
 
-    ASSERT_WITH_SECURITY_IMPLICATION(!NoEventDispatchAssertion::isEventDispatchForbidden());
+    ASSERT_WITH_SECURITY_IMPLICATION(NoEventDispatchAssertion::isEventDispatchAllowedInSubtree(child));
 
     RefPtr<Node> c = &child;
     Ref<Document> document(child.document());
@@ -829,7 +836,7 @@ static void dispatchChildRemovalEvents(Node& child)
         return;
     }
 
-    ASSERT_WITH_SECURITY_IMPLICATION(!NoEventDispatchAssertion::isEventDispatchForbidden());
+    ASSERT_WITH_SECURITY_IMPLICATION(NoEventDispatchAssertion::isEventDispatchAllowedInSubtree(child));
 
     willCreatePossiblyOrphanedTreeByRemoval(&child);
     InspectorInstrumentation::willRemoveDOMNode(child.document(), child);
index b0beb6c..8a8e0a2 100644 (file)
@@ -69,13 +69,32 @@ public:
 
     void cloneChildNodes(ContainerNode& clone);
 
-    enum ChildChangeType { ElementInserted, ElementRemoved, TextInserted, TextRemoved, TextChanged, AllChildrenRemoved, NonContentsChildChanged, AllChildrenReplaced };
+    enum ChildChangeType { ElementInserted, ElementRemoved, TextInserted, TextRemoved, TextChanged, AllChildrenRemoved, NonContentsChildRemoved, NonContentsChildInserted, AllChildrenReplaced };
     enum ChildChangeSource { ChildChangeSourceParser, ChildChangeSourceAPI };
     struct ChildChange {
         ChildChangeType type;
         Element* previousSiblingElement;
         Element* nextSiblingElement;
         ChildChangeSource source;
+
+        bool isInsertion() const
+        {
+            switch (type) {
+            case ElementInserted:
+            case TextInserted:
+            case NonContentsChildInserted:
+            case AllChildrenReplaced:
+                return true;
+            case ElementRemoved:
+            case TextRemoved:
+            case TextChanged:
+            case AllChildrenRemoved:
+            case NonContentsChildRemoved:
+                return false;
+            }
+            ASSERT_NOT_REACHED();
+            return false;
+        }
     };
     virtual void childrenChanged(const ChildChange&);
 
index 514f986..1c286c7 100644 (file)
@@ -27,6 +27,7 @@
 #include "ContainerNodeAlgorithms.h"
 
 #include "HTMLFrameOwnerElement.h"
+#include "HTMLTextAreaElement.h"
 #include "InspectorInstrumentation.h"
 #include "NoEventDispatchAssertion.h"
 #include "ShadowRoot.h"
@@ -90,7 +91,7 @@ void notifyNodeInsertedIntoTree(ContainerNode& insertionPoint, ContainerNode& no
 
 void notifyChildNodeInserted(ContainerNode& insertionPoint, Node& node, NodeVector& postInsertionNotificationTargets)
 {
-    ASSERT_WITH_SECURITY_IMPLICATION(!NoEventDispatchAssertion::isEventDispatchForbidden());
+    ASSERT_WITH_SECURITY_IMPLICATION(NoEventDispatchAssertion::isEventDispatchAllowedInSubtree(insertionPoint));
 
     InspectorInstrumentation::didInsertDOMNode(node.document(), node);
 
index 409f82b..8166a1f 100644 (file)
@@ -1041,6 +1041,8 @@ ExceptionOr<Ref<Node>> Document::adoptNode(Node& source)
         auto result = source.remove();
         if (result.hasException())
             return result.releaseException();
+        ASSERT_WITH_SECURITY_IMPLICATION(!source.inDocument());
+        ASSERT_WITH_SECURITY_IMPLICATION(!source.parentNode());
     }
 
     adoptIfNeeded(source);
@@ -4066,7 +4068,7 @@ EventListener* Document::getWindowAttributeEventListener(const AtomicString& eve
 
 void Document::dispatchWindowEvent(Event& event, EventTarget* target)
 {
-    ASSERT_WITH_SECURITY_IMPLICATION(!NoEventDispatchAssertion::isEventDispatchForbidden());
+    ASSERT_WITH_SECURITY_IMPLICATION(NoEventDispatchAssertion::isEventAllowedInMainThread());
     if (!m_domWindow)
         return;
     m_domWindow->dispatchEvent(event, target);
@@ -4074,7 +4076,7 @@ void Document::dispatchWindowEvent(Event& event, EventTarget* target)
 
 void Document::dispatchWindowLoadEvent()
 {
-    ASSERT_WITH_SECURITY_IMPLICATION(!NoEventDispatchAssertion::isEventDispatchForbidden());
+    ASSERT_WITH_SECURITY_IMPLICATION(NoEventDispatchAssertion::isEventAllowedInMainThread());
     if (!m_domWindow)
         return;
     m_domWindow->dispatchLoadEvent();
index d25c776..409d8c0 100644 (file)
@@ -2043,7 +2043,8 @@ void Element::childrenChanged(const ChildChange& change)
         case TextChanged:
             shadowRoot->didChangeDefaultSlot();
             break;
-        case NonContentsChildChanged:
+        case NonContentsChildInserted:
+        case NonContentsChildRemoved:
             break;
         }
     }
@@ -2449,14 +2450,14 @@ void Element::blur()
 
 void Element::dispatchFocusInEvent(const AtomicString& eventType, RefPtr<Element>&& oldFocusedElement)
 {
-    ASSERT_WITH_SECURITY_IMPLICATION(!NoEventDispatchAssertion::isEventDispatchForbidden());
+    ASSERT_WITH_SECURITY_IMPLICATION(NoEventDispatchAssertion::isEventAllowedInMainThread());
     ASSERT(eventType == eventNames().focusinEvent || eventType == eventNames().DOMFocusInEvent);
     dispatchScopedEvent(FocusEvent::create(eventType, true, false, document().defaultView(), 0, WTFMove(oldFocusedElement)));
 }
 
 void Element::dispatchFocusOutEvent(const AtomicString& eventType, RefPtr<Element>&& newFocusedElement)
 {
-    ASSERT_WITH_SECURITY_IMPLICATION(!NoEventDispatchAssertion::isEventDispatchForbidden());
+    ASSERT_WITH_SECURITY_IMPLICATION(NoEventDispatchAssertion::isEventAllowedInMainThread());
     ASSERT(eventType == eventNames().focusoutEvent || eventType == eventNames().DOMFocusOutEvent);
     dispatchScopedEvent(FocusEvent::create(eventType, true, false, document().defaultView(), 0, WTFMove(newFocusedElement)));
 }
index 9969caf..a673f09 100644 (file)
@@ -128,7 +128,7 @@ static bool shouldSuppressEventDispatchInDOM(Node& node, Event& event)
 
 bool EventDispatcher::dispatchEvent(Node& node, Event& event)
 {
-    ASSERT_WITH_SECURITY_IMPLICATION(!NoEventDispatchAssertion::isEventDispatchForbidden());
+    ASSERT_WITH_SECURITY_IMPLICATION(NoEventDispatchAssertion::isEventAllowedInMainThread());
     Ref<Node> protectedNode(node);
     RefPtr<FrameView> view = node.document().view();
     EventPath eventPath(node, event);
@@ -147,7 +147,7 @@ bool EventDispatcher::dispatchEvent(Node& node, Event& event)
     if (!event.target())
         return true;
 
-    ASSERT_WITH_SECURITY_IMPLICATION(!NoEventDispatchAssertion::isEventDispatchForbidden());
+    ASSERT_WITH_SECURITY_IMPLICATION(NoEventDispatchAssertion::isEventAllowedInMainThread());
 
     InputElementClickState clickHandlingState;
     if (is<HTMLInputElement>(node))
index 32c8b73..e27dfdf 100644 (file)
@@ -187,7 +187,7 @@ static const AtomicString& legacyType(const Event& event)
 
 bool EventTarget::fireEventListeners(Event& event)
 {
-    ASSERT_WITH_SECURITY_IMPLICATION(!NoEventDispatchAssertion::isEventDispatchForbidden());
+    ASSERT_WITH_SECURITY_IMPLICATION(NoEventDispatchAssertion::isEventAllowedInMainThread());
     ASSERT(event.isInitialized());
 
     auto* data = eventTargetData();
index 70b2076..a32c1f4 100644 (file)
@@ -23,6 +23,7 @@
 
 #pragma once
 
+#include "ContainerNode.h"
 #include <wtf/MainThread.h>
 
 namespace WebCore {
@@ -53,35 +54,58 @@ public:
 #endif
     }
 
-    static bool isEventDispatchForbidden()
+    static bool isEventAllowedInMainThread()
     {
 #if ASSERT_DISABLED
-        return false;
+        return true;
 #else
-        return isMainThread() && s_count;
+        return !isMainThread() || !s_count;
 #endif
     }
 
-    static unsigned dropTemporarily()
+    static bool isEventDispatchAllowedInSubtree(Node& node)
     {
-#if ASSERT_DISABLED
-        return 0;
-#else
-        unsigned count = s_count;
-        s_count = 0;
-        return count;
-#endif
+        return isEventAllowedInMainThread() || EventAllowedScope::isAllowedNode(node);
     }
 
-    static void restoreDropped(unsigned count)
-    {
-#if ASSERT_DISABLED
-        UNUSED_PARAM(count);
+#if !ASSERT_DISABLED
+    class EventAllowedScope {
+    public:
+        explicit EventAllowedScope(ContainerNode& userAgentContentRoot)
+            : m_eventAllowedTreeRoot(userAgentContentRoot)
+            , m_previousScope(s_currentScope)
+        {
+            s_currentScope = this;
+        }
+
+        ~EventAllowedScope()
+        {
+            s_currentScope = m_previousScope;
+        }
+
+        static bool isAllowedNode(Node& node)
+        {
+            return s_currentScope && s_currentScope->isAllowedNodeInternal(node);
+        }
+
+    private:
+        bool isAllowedNodeInternal(Node& node)
+        {
+            return m_eventAllowedTreeRoot->contains(&node) || (m_previousScope && m_previousScope->isAllowedNodeInternal(node));
+        }
+
+        Ref<ContainerNode> m_eventAllowedTreeRoot;
+
+        EventAllowedScope* m_previousScope;
+        static EventAllowedScope* s_currentScope;
+    };
 #else
-        ASSERT(!s_count);
-        s_count = count;
+    class EventAllowedScope {
+    public:
+        explicit EventAllowedScope(ContainerNode&) { }
+        static bool isAllowedNode(Node&) { return true; }
+    };
 #endif
-    }
 
 #if !ASSERT_DISABLED
 private:
index a6a8efa..7255790 100644 (file)
@@ -2192,7 +2192,7 @@ void Node::dispatchSubtreeModifiedEvent()
     if (isInShadowTree())
         return;
 
-    ASSERT_WITH_SECURITY_IMPLICATION(!NoEventDispatchAssertion::isEventDispatchForbidden());
+    ASSERT_WITH_SECURITY_IMPLICATION(NoEventDispatchAssertion::isEventDispatchAllowedInSubtree(*this));
 
     if (!document().hasListenerType(Document::DOMSUBTREEMODIFIED_LISTENER))
         return;
@@ -2205,7 +2205,7 @@ void Node::dispatchSubtreeModifiedEvent()
 
 bool Node::dispatchDOMActivateEvent(int detail, Event& underlyingEvent)
 {
-    ASSERT_WITH_SECURITY_IMPLICATION(!NoEventDispatchAssertion::isEventDispatchForbidden());
+    ASSERT_WITH_SECURITY_IMPLICATION(NoEventDispatchAssertion::isEventAllowedInMainThread());
     Ref<UIEvent> event = UIEvent::create(eventNames().DOMActivateEvent, true, true, document().defaultView(), detail);
     event->setUnderlyingEvent(&underlyingEvent);
     dispatchScopedEvent(event);
index d018c5d..cf51d40 100644 (file)
@@ -41,6 +41,7 @@
 #include "LoadableClassicScript.h"
 #include "LoadableModuleScript.h"
 #include "MIMETypeRegistry.h"
+#include "NoEventDispatchAssertion.h"
 #include "PendingScript.h"
 #include "SVGScriptElement.h"
 #include "ScriptController.h"
@@ -83,9 +84,9 @@ void ScriptElement::finishedInsertingSubtree()
     prepareScript(); // FIXME: Provide a real starting line number here.
 }
 
-void ScriptElement::childrenChanged()
+void ScriptElement::childrenChanged(const ContainerNode::ChildChange& childChange)
 {
-    if (!m_parserInserted && m_element.isConnected())
+    if (!m_parserInserted && childChange.isInsertion() && m_element.isConnected())
         prepareScript(); // FIXME: Provide a real starting line number here.
 }
 
@@ -365,6 +366,7 @@ bool ScriptElement::requestModuleScript(const TextPosition& scriptStartPosition)
 
 void ScriptElement::executeClassicScript(const ScriptSourceCode& sourceCode)
 {
+    ASSERT_WITH_SECURITY_IMPLICATION(NoEventDispatchAssertion::isEventAllowedInMainThread());
     ASSERT(m_alreadyStarted);
 
     if (sourceCode.isEmpty())
index 632cd04..e5e95fd 100644 (file)
@@ -23,6 +23,7 @@
 
 #include "CachedResourceClient.h"
 #include "CachedResourceHandle.h"
+#include "ContainerNode.h"
 #include "LoadableScript.h"
 #include "LoadableScriptClient.h"
 #include "Timer.h"
@@ -84,7 +85,7 @@ protected:
     // Helper functions used by our parent classes.
     bool shouldCallFinishedInsertingSubtree(ContainerNode&);
     void finishedInsertingSubtree();
-    void childrenChanged();
+    void childrenChanged(const ContainerNode::ChildChange&);
     void handleSourceAttribute(const String& sourceURL);
     void handleAsyncAttribute();
 
index dc94f11..f4cc9f2 100644 (file)
@@ -212,7 +212,7 @@ void WebKitNamedFlow::setRenderer(RenderNamedFlowThread* parentFlowThread)
 
 void WebKitNamedFlow::dispatchRegionOversetChangeEvent()
 {
-    ASSERT_WITH_SECURITY_IMPLICATION(!NoEventDispatchAssertion::isEventDispatchForbidden());
+    ASSERT_WITH_SECURITY_IMPLICATION(NoEventDispatchAssertion::isEventAllowedInMainThread());
     
     // If the flow is in the "NULL" state the event should not be dispatched any more.
     if (flowState() == FlowStateNull)
index 2654fbb..aae8e9a 100644 (file)
@@ -434,12 +434,14 @@ void HTMLElement::parseAttribute(const QualifiedName& name, const AtomicString&
         setAttributeEventListener(eventName, name, value);
 }
 
-ExceptionOr<Ref<DocumentFragment>> HTMLElement::textToFragment(const String& text)
+static Ref<DocumentFragment> textToFragment(Document& document, const String& text)
 {
-    auto fragment = DocumentFragment::create(document());
+    auto fragment = DocumentFragment::create(document);
 
-    for (unsigned start = 0, length = text.length(); start < length; ) {
+    // It's safe to dispatch events on the new fragment since author scripts have no access to it yet.
+    NoEventDispatchAssertion::EventAllowedScope allowedScope(fragment);
 
+    for (unsigned start = 0, length = text.length(); start < length; ) {
         // Find next line break.
         UChar c = 0;
         unsigned i;
@@ -449,17 +451,11 @@ ExceptionOr<Ref<DocumentFragment>> HTMLElement::textToFragment(const String& tex
                 break;
         }
 
-        auto appendResult = fragment->appendChild(Text::create(document(), text.substring(start, i - start)));
-        if (appendResult.hasException())
-            return appendResult.releaseException();
-
+        fragment->appendChild(Text::create(document, text.substring(start, i - start)));
         if (i == length)
             break;
 
-        appendResult = fragment->appendChild(HTMLBRElement::create(document()));
-        if (appendResult.hasException())
-            return appendResult.releaseException();
-
+        fragment->appendChild(HTMLBRElement::create(document));
         // Make sure \r\n doesn't result in two line breaks.
         if (c == '\r' && i + 1 < length && text[i + 1] == '\n')
             ++i;
@@ -467,7 +463,7 @@ ExceptionOr<Ref<DocumentFragment>> HTMLElement::textToFragment(const String& tex
         start = i + 1; // Character after line break.
     }
 
-    return WTFMove(fragment);
+    return fragment;
 }
 
 // Returns the conforming 'dir' value associated with the state the attribute is in (in its canonical case), if any,
@@ -527,11 +523,11 @@ ExceptionOr<void> HTMLElement::setInnerText(const String& text)
     }
 
     // Add text nodes and <br> elements.
-    auto fragment = textToFragment(text);
-    if (fragment.hasException())
-        return fragment.releaseException();
+    auto fragment = textToFragment(document(), text);
     // FIXME: This should use replaceAllChildren() once it accepts DocumentFragments as input.
-    return replaceChildrenWithFragment(*this, fragment.releaseReturnValue());
+    // It's safe to dispatch events on the new fragment since author scripts have no access to it yet.
+    NoEventDispatchAssertion::EventAllowedScope allowedScope(fragment.get());
+    return replaceChildrenWithFragment(*this, WTFMove(fragment));
 }
 
 ExceptionOr<void> HTMLElement::setOuterText(const String& text)
@@ -545,12 +541,9 @@ ExceptionOr<void> HTMLElement::setOuterText(const String& text)
     RefPtr<Node> newChild;
 
     // Convert text to fragment with <br> tags instead of linebreaks if needed.
-    if (text.contains('\r') || text.contains('\n')) {
-        auto result = textToFragment(text);
-        if (result.hasException())
-            return result.releaseException();
-        newChild = result.releaseReturnValue();
-    } else
+    if (text.contains('\r') || text.contains('\n'))
+        newChild = textToFragment(document(), text);
+    else
         newChild = Text::create(document(), text);
 
     if (!parentNode())
index c1f9ecb..3bada1b 100644 (file)
@@ -133,8 +133,6 @@ private:
 
     void mapLanguageAttributeToLocale(const AtomicString&, MutableStyleProperties&);
 
-    ExceptionOr<Ref<DocumentFragment>> textToFragment(const String&);
-
     void dirAttributeChanged(const AtomicString&);
     void adjustDirectionalityIfNeededAfterChildAttributeChanged(Element* child);
     void adjustDirectionalityIfNeededAfterChildrenChanged(Element* beforeChange, ChildChangeType);
index d93b0e0..29363a1 100644 (file)
@@ -1284,7 +1284,7 @@ void HTMLMediaElement::loadInternal()
     // Some of the code paths below this function dispatch the BeforeLoad event. This ASSERT helps
     // us catch those bugs more quickly without needing all the branches to align to actually
     // trigger the event.
-    ASSERT(!NoEventDispatchAssertion::isEventDispatchForbidden());
+    ASSERT(NoEventDispatchAssertion::isEventAllowedInMainThread());
 
     // If we can't start a load right away, start it later.
     if (!m_mediaSession->pageAllowsDataLoading(*this)) {
index 65808a0..a8e14b4 100644 (file)
@@ -55,7 +55,7 @@ bool HTMLScriptElement::isURLAttribute(const Attribute& attribute) const
 void HTMLScriptElement::childrenChanged(const ChildChange& change)
 {
     HTMLElement::childrenChanged(change);
-    ScriptElement::childrenChanged();
+    ScriptElement::childrenChanged(change);
 }
 
 void HTMLScriptElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
index e8cd483..820a87e 100644 (file)
@@ -40,6 +40,7 @@
 #include "HTMLNames.h"
 #include "HTMLParserIdioms.h"
 #include "Logging.h"
+#include "NoEventDispatchAssertion.h"
 #include "NodeTraversal.h"
 #include "Page.h"
 #include "RenderTextControlSingleLine.h"
@@ -557,10 +558,16 @@ void HTMLTextFormControlElement::setInnerTextValue(const String& value)
                 cache->postNotification(this, AXObjectCache::AXValueChanged, TargetObservableParent);
         }
 #endif
-        innerText->setInnerText(value);
 
-        if (value.endsWith('\n') || value.endsWith('\r'))
-            innerText->appendChild(HTMLBRElement::create(document()));
+        {
+            // Events dispatched on the inner text element cannot execute arbitrary author scripts.
+            NoEventDispatchAssertion::EventAllowedScope allowedScope(*userAgentShadowRoot());
+
+            innerText->setInnerText(value);
+
+            if (value.endsWith('\n') || value.endsWith('\r'))
+                innerText->appendChild(HTMLBRElement::create(document()));
+        }
 
 #if HAVE(ACCESSIBILITY) && PLATFORM(COCOA)
         if (textIsChanged && renderer()) {
index 92f615c..e858f39 100644 (file)
@@ -42,6 +42,7 @@
 #include "HTMLDivElement.h"
 #include "HTMLSpanElement.h"
 #include "Logging.h"
+#include "NoEventDispatchAssertion.h"
 #include "NodeTraversal.h"
 #include "RenderVTTCue.h"
 #include "Text.h"
@@ -524,6 +525,10 @@ RefPtr<DocumentFragment> VTTCue::createCueRenderingTree()
         return nullptr;
 
     auto clonedFragment = DocumentFragment::create(ownerDocument());
+
+    // The cloned fragment is never exposed to author scripts so it's safe to dispatch events here.
+    NoEventDispatchAssertion::EventAllowedScope noEventDispatchAssertionDisabledForScope(clonedFragment);
+
     m_webVTTNodeTree->cloneChildNodes(clonedFragment);
     return WTFMove(clonedFragment);
 }
@@ -779,6 +784,9 @@ void VTTCue::updateDisplayTree(const MediaTime& movieTime)
     if (!track()->isRendered())
         return;
 
+    // Mutating the VTT contents is safe because it's never exposed to author scripts.
+    NoEventDispatchAssertion::EventAllowedScope allowedScopeForCueHighlightBox(*m_cueHighlightBox);
+
     // Clear the contents of the set.
     m_cueHighlightBox->removeChildren();
 
@@ -787,6 +795,8 @@ void VTTCue::updateDisplayTree(const MediaTime& movieTime)
     if (!referenceTree)
         return;
 
+    NoEventDispatchAssertion::EventAllowedScope allowedScopeForReferenceTree(*referenceTree);
+
     markFutureAndPastNodes(referenceTree.get(), startMediaTime(), movieTime);
     m_cueHighlightBox->appendChild(*referenceTree);
 }
index 510c0f6..a066412 100644 (file)
@@ -69,17 +69,20 @@ FontPlatformData CachedSVGFont::platformDataFromCustomData(const FontDescription
 bool CachedSVGFont::ensureCustomFontData(const AtomicString& remoteURI)
 {
     if (!m_externalSVGDocument && !errorOccurred() && !isLoading() && m_data) {
-        // We may get here during render tree updates when events are forbidden.
-        // Frameless document can't run scripts or call back to the client so this is safe.
-        auto count = NoEventDispatchAssertion::dropTemporarily();
+        bool sawError = false;
+        {
+            // We may get here during render tree updates when events are forbidden.
+            // Frameless document can't run scripts or call back to the client so this is safe.
+            m_externalSVGDocument = SVGDocument::create(nullptr, URL());
+            auto decoder = TextResourceDecoder::create("application/xml");
 
-        m_externalSVGDocument = SVGDocument::create(nullptr, URL());
-        RefPtr<TextResourceDecoder> decoder = TextResourceDecoder::create("application/xml");
-        m_externalSVGDocument->setContent(decoder->decodeAndFlush(m_data->data(), m_data->size()));
+            NoEventDispatchAssertion::EventAllowedScope allowedScope(*m_externalSVGDocument);
 
-        NoEventDispatchAssertion::restoreDropped(count);
+            m_externalSVGDocument->setContent(decoder->decodeAndFlush(m_data->data(), m_data->size()));
+            sawError = decoder->sawError();
+        }
 
-        if (decoder->sawError())
+        if (sawError)
             m_externalSVGDocument = nullptr;
         if (m_externalSVGDocument)
             maybeInitializeExternalSVGFontElement(remoteURI);
index 414c55c..d6649fa 100644 (file)
@@ -87,7 +87,7 @@ void SVGScriptElement::finishedInsertingSubtree()
 void SVGScriptElement::childrenChanged(const ChildChange& change)
 {
     SVGElement::childrenChanged(change);
-    ScriptElement::childrenChanged();
+    ScriptElement::childrenChanged(change);
 }
 
 bool SVGScriptElement::isURLAttribute(const Attribute& attribute) const