2010-12-09 Ryosuke Niwa <rniwa@webkit.org>
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 10 Dec 2010 07:48:03 +0000 (07:48 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 10 Dec 2010 07:48:03 +0000 (07:48 +0000)
        Reviewed by Ojan Vafai.

        Make DOM Mutation Events Asynchronous
        https://bugs.webkit.org/show_bug.cgi?id=46936

        Implemented DOM mutations events as scoped events. A scoped event is an event whose
        dispatch is done via ScopedEventQueue. The behavior of the queue is controlled by
        EventQueueScope objects (RAII idiom), which increments and decrements the scoping level
        on its constructor and destructor respectively.

        When the scoping level is 0 (initial level), scoped events are dispatched as soon as
        they are enqueued and act like synchronous events. When the scoping level is greater than 0,
        however, events are queued in ScopedEventQueue and their dispatches are delayed until
        the scoping level goes back to 0 (by the destruction of EventQueueScope).

        DOMSubtreeModified, DOMNodeInserted, DOMNodeRemoved, DOMNodeRemovedFromDocument,
        DOMNodeInsertedIntoDocument, DOMFocusIn, DOMFocusOut, focusin, and focusout are treated as
        scoped events, and a scope object is instantiated in EditCommand::apply to delay dispatches
        of the events until the completion of each call of EditCommand::doApply.

        Test: fast/events/mutation/execCommands.html

        * Android.mk: Added ScopedEventQueue.cpp.
        * CMakeLists.txt: Ditto.
        * WebCore.pro: Ditto.
        * GNUmakefile.am: Added ScopedEventQueue.cpp and ScopedEventQueue.h.
        * WebCore.gypi:  Ditto.
        * WebCore.vcproj/project.vcproj: Ditto.
        * WebCore.xcodeproj/project.pbxproj: Ditto.
        * dom/ContainerNode.cpp:
        (WebCore::dispatchChildInsertionEvents): Calls dispatchScopedEvent.
        (WebCore::dispatchChildRemovalEvents): Ditto.
        * dom/DOMAllInOne.cpp: Added ScopedEventQueue.cpp.
        * dom/Element.cpp:
        (WebCore::Element::dispatchAttrRemovalEvent): Ditto.
        (WebCore::Element::dispatchAttrAdditionEvent): Ditto.
        * dom/Node.cpp:
        (WebCore::Node::dispatchScopedEvent): Added.
        (WebCore::Node::dispatchSubtreeModifiedEvent): Calls dispatchScopedEvent.
        * dom/Node.h:
        * dom/ScopedEventQueue.cpp: Added.
        (WebCore::ScopedEventQueue::initialize): Added.
        (WebCore::ScopedEventQueue::enqueueEvent): Added.
        (WebCore::ScopedEventQueue::dispatchAllEvents): Added.
        (WebCore::ScopedEventQueue::dispatchEvent): Added.
        (WebCore::ScopedEventQueue::instance): Added.
        (WebCore::ScopedEventQueue::incrementScopingLevel): Added.
        (WebCore::ScopedEventQueue::decrementScopingLevel): Added.
        * dom/ScopedEventQueue.h: Added.
        (WebCore::ScopedEventQueue::~ScopedEventQueue): Added.
        (WebCore::ScopedEventQueue::ScopedEventQueue): Added.
        (WebCore::EventQueueScope::EventQueueScope): Added.
        (WebCore::EventQueueScope::~EventQueueScope): Added.
        * editing/EditCommand.cpp:
        (WebCore::EditCommand::apply): Instantiates EventQueueScope.
2010-12-09  Ryosuke Niwa  <rniwa@webkit.org>

        Reviewed by Ojan Vafai.

        Make DOM Mutation Events Asynchronous
        https://bugs.webkit.org/show_bug.cgi?id=46936

        Added a test to ensure no DOM mutation events are fired while execCommand is in progress.

        * fast/events/crash-on-mutate-during-drop.html: DOMNodeInserted is fired after BR is detached
        from the document, and can't be observed. Modify the DOM when text node is inserted.
        * fast/events/scoped: Added.
        * fast/events/scoped/editing-commands-expected.txt: Added.
        * fast/events/scoped/editing-commands.html: Added.

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

20 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/events/crash-on-mutate-during-drop.html
LayoutTests/fast/events/scoped/editing-commands-expected.txt [new file with mode: 0644]
LayoutTests/fast/events/scoped/editing-commands.html [new file with mode: 0644]
WebCore/Android.mk
WebCore/CMakeLists.txt
WebCore/ChangeLog
WebCore/GNUmakefile.am
WebCore/WebCore.gypi
WebCore/WebCore.pro
WebCore/WebCore.vcproj/WebCore.vcproj
WebCore/WebCore.xcodeproj/project.pbxproj
WebCore/dom/ContainerNode.cpp
WebCore/dom/DOMAllInOne.cpp
WebCore/dom/Element.cpp
WebCore/dom/Node.cpp
WebCore/dom/Node.h
WebCore/dom/ScopedEventQueue.cpp [new file with mode: 0644]
WebCore/dom/ScopedEventQueue.h [new file with mode: 0644]
WebCore/editing/EditCommand.cpp

index c03fb52..6a273d5 100644 (file)
@@ -1,3 +1,18 @@
+2010-12-09  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Reviewed by Ojan Vafai.
+
+        Make DOM Mutation Events Asynchronous
+        https://bugs.webkit.org/show_bug.cgi?id=46936
+
+        Added a test to ensure no DOM mutation events are fired while execCommand is in progress.
+
+        * fast/events/crash-on-mutate-during-drop.html: DOMNodeInserted is fired after BR is detached
+        from the document, and can't be observed. Modify the DOM when text node is inserted.
+        * fast/events/scoped: Added.
+        * fast/events/scoped/editing-commands-expected.txt: Added.
+        * fast/events/scoped/editing-commands.html: Added.
+
 2010-12-09  Sheriff Bot  <webkit.review.bot@gmail.com>
 
         Unreviewed, rolling out r73684.
index 908e53e..1b85004 100644 (file)
@@ -2,7 +2,7 @@
 <head>
 <script>
 function foo() {
-    if (event.type == "DOMNodeInserted" && event.target.nodeName == "BR")
+    if (event.type == "DOMNodeInserted" && event.target.nodeType == 3)
         document.body.innerHTML = "PASSED";
 }
 
@@ -12,7 +12,7 @@ function runTest() {
 
     window.layoutTestController.dumpAsText();
 
-    document.body.addEventListener("DOMNodeInserted", function() { foo() }, true);
+    document.addEventListener("DOMNodeInserted", function() { foo() }, true);
 
     // Select the element 'dragSource'.
     var selection = window.getSelection();
diff --git a/LayoutTests/fast/events/scoped/editing-commands-expected.txt b/LayoutTests/fast/events/scoped/editing-commands-expected.txt
new file mode 100644 (file)
index 0000000..8de3126
--- /dev/null
@@ -0,0 +1,40 @@
+This test ensures WebKit does not fire DOM mutation events while execCommand is in progress.
+
+PASS: execCommand('BackColor', false, 'blue')
+PASS: execCommand('CreateLink', false, 'about:blank')
+PASS: execCommand('Delete', false, null)
+PASS: execCommand('FontName', false, 'Arial')
+PASS: execCommand('FontSize', false, '5')
+PASS: execCommand('FontSizeDelta', false, '5')
+PASS: execCommand('ForeColor', false, 'blue')
+PASS: execCommand('FormatBlock', false, 'pre')
+PASS: execCommand('ForwardDelete', false, null)
+PASS: execCommand('HiliteColor', false, 'red')
+PASS: execCommand('Indent', false, null)
+PASS: execCommand('InsertHTML', false, '<i>hello')
+PASS: execCommand('InsertHorizontalRule', false, null)
+PASS: execCommand('InsertImage', false, '../resources/abe.png')
+PASS: execCommand('InsertLineBreak', false, null)
+PASS: execCommand('InsertNewlineInQuotedContent', false, null)
+PASS: execCommand('InsertOrderedList', false, null)
+PASS: execCommand('InsertParagraph', false, null)
+PASS: execCommand('InsertText', false, 'webkit')
+PASS: execCommand('InsertUnorderedList', false, null)
+PASS: execCommand('Italic', false, null)
+PASS: execCommand('JustifyCenter', false, null)
+PASS: execCommand('JustifyFull', false, null)
+PASS: execCommand('JustifyLeft', false, null)
+PASS: execCommand('JustifyNone', false, null)
+PASS: execCommand('JustifyRight', false, null)
+PASS: execCommand('Outdent', false, null)
+PASS: execCommand('RemoveFormat', false, null)
+PASS: execCommand('Strikethrough', false, null)
+PASS: execCommand('Subscript', false, null)
+PASS: execCommand('Superscript', false, null)
+PASS: execCommand('Transpose', false, null)
+PASS: execCommand('Underline', false, null)
+PASS: execCommand('Unlink', false, null)
+
+
+DONE
+
diff --git a/LayoutTests/fast/events/scoped/editing-commands.html b/LayoutTests/fast/events/scoped/editing-commands.html
new file mode 100644 (file)
index 0000000..33b3dae
--- /dev/null
@@ -0,0 +1,129 @@
+<!DOCTYPE html>
+<html>
+<body>
+<p>This test ensures WebKit does not fire DOM mutation events while execCommand is in progress.</p>
+<div id="test" contenteditable></div>
+<pre>
+<script>
+
+if (window.layoutTestController)
+    layoutTestController.dumpAsText();
+
+var commands = [
+    {name: 'BackColor', value: 'blue'},
+    {name: 'CreateLink', value: 'about:blank'},
+    {name: 'Delete', value: null},
+    {name: 'FontName', value: 'Arial'},
+    {name: 'FontSize', value: '5'},
+    {name: 'FontSizeDelta', value: '5'},
+    {name: 'ForeColor', value: 'blue'},
+    {name: 'FormatBlock', value: 'pre'},
+    {name: 'ForwardDelete', value: null},
+    {name: 'HiliteColor', value: 'red'},
+    {name: 'Indent', value: null},
+    {name: 'InsertHTML', value: "<i>hello</i>"},
+    {name: 'InsertHorizontalRule', value: null},
+    {name: 'InsertImage', value: '../resources/abe.png'},
+    {name: 'InsertLineBreak', value: null},
+    {name: 'InsertNewlineInQuotedContent', value: null},
+    {name: 'InsertOrderedList', value: null},
+    {name: 'InsertParagraph', value: null},
+    {name: 'InsertText', value: 'webkit'},
+    {name: 'InsertUnorderedList', value: null},
+    {name: 'Italic', value: null},
+    {name: 'JustifyCenter', value: null},
+    {name: 'JustifyFull', value: null},
+    {name: 'JustifyLeft', value: null},
+    {name: 'JustifyNone', value: null},
+    {name: 'JustifyRight', value: null},
+    {name: 'Outdent', value: null},
+    {name: 'RemoveFormat', value: null},
+    {name: 'Strikethrough', value: null},
+    {name: 'Subscript', value: null},
+    {name: 'Superscript', value: null},
+    {name: 'Transpose', value: null, selector: function (test) { window.getSelection().setPosition(test.firstChild, 1); }},
+    {name: 'Underline', value: null},
+    {name: 'Unlink', value: null},
+];
+
+var events = {
+    'DOMSubtreeModified': false,
+    'DOMNodeInserted': false,
+    'DOMNodeRemoved': false,
+    'DOMNodeRemovedFromDocument': false,
+    'DOMNodeInsertedIntoDocument': true, // this event can never be observed.
+    'DOMFocusIn': false,
+    'DOMFocusOut': false,
+    'focusin': false,
+    'focusout': false,
+};
+
+var log = [];
+var test = document.getElementById('test');
+
+function addEventListeners(node) {
+    for (var e in events) {
+        node.addEventListener(e, function (event) {
+            log.push(test.innerHTML);
+            events[event.type] = true;
+        }, false);
+    }
+}
+
+function isLogConsistent() {
+    for (var i= 1; i < log.length; i++) {
+        if (log[0] != log[i]) {
+            console.log(log);
+            return false;
+        }
+    }
+    return true;
+}
+
+addEventListeners(test);
+
+var initial = 'hello, <input type="text"><blockquote align="right"><u><a href="about:blank">world</a></u></blockquote>';
+for (var i = 0; i < commands.length; i++) {
+    test.innerHTML = initial;
+    if (i)
+        document.write("\n");
+
+    if (test.innerHTML != initial) {
+        document.write("FAIL: initial innerHTML didn't match");
+        continue;
+    }
+
+    if (commands[i].selector)
+        commands[i].selector(test);
+    else {    
+        document.getElementsByTagName('input')[0].focus();
+        window.getSelection().selectAllChildren(test);
+    }
+    addEventListeners(test.childNodes[2]);
+    log = []; // clear log
+    document.execCommand(commands[i].name, false, commands[i].value);
+
+    var quotedValue = commands[i].value ? "'" + commands[i].value.replace('<', '&lt;') + "'" : null;
+    var action = "execCommand('" + commands[i].name + "', false, " + quotedValue + ")";
+
+    if (test.innerHTML == initial || log.length <= 0)
+        document.write('FAIL: ' + action + ' made no change to the DOM.');
+    else if (!isLogConsistent())
+        document.write('FAIL: ' + action + ' dispatched events before finalizing the DOM tree.');
+    else
+        document.write('PASS: ' + action);
+}
+test.style.display = 'none';
+
+document.write('\n');
+for (var e in events) {
+    if (!events[e])
+        document.write('\nWARNING: ' + e + ' was never observed.');
+}
+
+document.write('\n\nDONE');
+
+</script>
+</pre>
+</body>
+</html>
index ce67406..04bea8f 100644 (file)
@@ -165,6 +165,7 @@ LOCAL_SRC_FILES := $(LOCAL_SRC_FILES) \
        dom/QualifiedName.cpp \
        dom/Range.cpp \
        dom/RegisteredEventListener.cpp \
+       dom/ScopedEventQueue.cpp \
        dom/ScriptableDocumentParser.cpp \
        dom/ScriptElement.cpp \
        dom/ScriptExecutionContext.cpp \
index fc8f984..47b83cf 100644 (file)
@@ -881,6 +881,7 @@ SET(WebCore_SOURCES
     dom/QualifiedName.cpp
     dom/Range.cpp
     dom/RegisteredEventListener.cpp
+    dom/ScopedEventQueue.cpp
     dom/ScriptableDocumentParser.cpp
     dom/ScriptElement.cpp
     dom/ScriptExecutionContext.cpp
index 19489a8..bbc485b 100644 (file)
@@ -1,3 +1,61 @@
+2010-12-09  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Reviewed by Ojan Vafai.
+
+        Make DOM Mutation Events Asynchronous
+        https://bugs.webkit.org/show_bug.cgi?id=46936
+
+        Implemented DOM mutations events as scoped events. A scoped event is an event whose
+        dispatch is done via ScopedEventQueue. The behavior of the queue is controlled by
+        EventQueueScope objects (RAII idiom), which increments and decrements the scoping level
+        on its constructor and destructor respectively.
+
+        When the scoping level is 0 (initial level), scoped events are dispatched as soon as
+        they are enqueued and act like synchronous events. When the scoping level is greater than 0,
+        however, events are queued in ScopedEventQueue and their dispatches are delayed until
+        the scoping level goes back to 0 (by the destruction of EventQueueScope).
+
+        DOMSubtreeModified, DOMNodeInserted, DOMNodeRemoved, DOMNodeRemovedFromDocument,
+        DOMNodeInsertedIntoDocument, DOMFocusIn, DOMFocusOut, focusin, and focusout are treated as
+        scoped events, and a scope object is instantiated in EditCommand::apply to delay dispatches
+        of the events until the completion of each call of EditCommand::doApply.
+
+        Test: fast/events/mutation/execCommands.html
+
+        * Android.mk: Added ScopedEventQueue.cpp.
+        * CMakeLists.txt: Ditto.
+        * WebCore.pro: Ditto.
+        * GNUmakefile.am: Added ScopedEventQueue.cpp and ScopedEventQueue.h.
+        * WebCore.gypi:  Ditto.
+        * WebCore.vcproj/project.vcproj: Ditto.
+        * WebCore.xcodeproj/project.pbxproj: Ditto.
+        * dom/ContainerNode.cpp:
+        (WebCore::dispatchChildInsertionEvents): Calls dispatchScopedEvent.
+        (WebCore::dispatchChildRemovalEvents): Ditto.
+        * dom/DOMAllInOne.cpp: Added ScopedEventQueue.cpp.
+        * dom/Element.cpp:
+        (WebCore::Element::dispatchAttrRemovalEvent): Ditto. 
+        (WebCore::Element::dispatchAttrAdditionEvent): Ditto.
+        * dom/Node.cpp:
+        (WebCore::Node::dispatchScopedEvent): Added.
+        (WebCore::Node::dispatchSubtreeModifiedEvent): Calls dispatchScopedEvent.
+        * dom/Node.h:
+        * dom/ScopedEventQueue.cpp: Added.
+        (WebCore::ScopedEventQueue::initialize): Added.
+        (WebCore::ScopedEventQueue::enqueueEvent): Added.
+        (WebCore::ScopedEventQueue::dispatchAllEvents): Added.
+        (WebCore::ScopedEventQueue::dispatchEvent): Added.
+        (WebCore::ScopedEventQueue::instance): Added.
+        (WebCore::ScopedEventQueue::incrementScopingLevel): Added.
+        (WebCore::ScopedEventQueue::decrementScopingLevel): Added.
+        * dom/ScopedEventQueue.h: Added.
+        (WebCore::ScopedEventQueue::~ScopedEventQueue): Added.
+        (WebCore::ScopedEventQueue::ScopedEventQueue): Added.
+        (WebCore::EventQueueScope::EventQueueScope): Added.
+        (WebCore::EventQueueScope::~EventQueueScope): Added.
+        * editing/EditCommand.cpp:
+        (WebCore::EditCommand::apply): Instantiates EventQueueScope.
+
 2010-12-09  Sheriff Bot  <webkit.review.bot@gmail.com>
 
         Unreviewed, rolling out r73684.
index 79754a5..25c24e9 100644 (file)
@@ -1268,6 +1268,8 @@ webcore_sources += \
        WebCore/dom/RawDataDocumentParser.h \
        WebCore/dom/RegisteredEventListener.cpp \
        WebCore/dom/RegisteredEventListener.h \
+       WebCore/dom/ScopedEventQueue.cpp \
+       WebCore/dom/ScopedEventQueue.h \
        WebCore/dom/ScriptableDocumentParser.cpp \
        WebCore/dom/ScriptableDocumentParser.h \
        WebCore/dom/ScriptElement.cpp \
index 48aa06e..327c623 100644 (file)
             'dom/RawDataDocumentParser.h',
             'dom/RegisteredEventListener.cpp',
             'dom/RegisteredEventListener.h',
+            'dom/ScopedEventQueue.cpp',
+            'dom/ScopedEventQueue.h',
             'dom/ScriptableDocumentParser.cpp',
             'dom/ScriptableDocumentParser.h',
             'dom/ScriptElement.cpp',
index 0eaff5a..5f394ed 100644 (file)
@@ -762,6 +762,7 @@ SOURCES += \
     dom/Range.cpp \
     dom/RawDataDocumentParser.h \
     dom/RegisteredEventListener.cpp \
+    dom/ScopedEventQueue.cpp \
     dom/ScriptableDocumentParser.cpp \
     dom/ScriptElement.cpp \
     dom/ScriptExecutionContext.cpp \
index 30051de..4a1e084 100755 (executable)
                                >
                        </File>
                        <File
+                               RelativePath="..\dom\ScopedEventQueue.cpp"
+                               >
+                               <FileConfiguration
+                                       Name="Debug|Win32"
+                                       ExcludedFromBuild="true"
+                                       >
+                                       <Tool
+                                               Name="VCCLCompilerTool"
+                                       />
+                               </FileConfiguration>
+                               <FileConfiguration
+                                       Name="Release|Win32"
+                                       ExcludedFromBuild="true"
+                                       >
+                                       <Tool
+                                               Name="VCCLCompilerTool"
+                                       />
+                               </FileConfiguration>
+                               <FileConfiguration
+                                       Name="Debug_Cairo_CFLite|Win32"
+                                       ExcludedFromBuild="true"
+                                       >
+                                       <Tool
+                                               Name="VCCLCompilerTool"
+                                       />
+                               </FileConfiguration>
+                               <FileConfiguration
+                                       Name="Release_Cairo_CFLite|Win32"
+                                       ExcludedFromBuild="true"
+                                       >
+                                       <Tool
+                                               Name="VCCLCompilerTool"
+                                       />
+                               </FileConfiguration>
+                               <FileConfiguration
+                                       Name="Debug_All|Win32"
+                                       ExcludedFromBuild="true"
+                                       >
+                                       <Tool
+                                               Name="VCCLCompilerTool"
+                                       />
+                               </FileConfiguration>
+                               <FileConfiguration
+                                       Name="Release_LTCG|Win32"
+                                       ExcludedFromBuild="true"
+                                       >
+                                       <Tool
+                                               Name="VCCLCompilerTool"
+                                       />
+                               </FileConfiguration>
+                       </File>
+                       <File
+                               RelativePath="..\dom\ScopedEventQueue.h"
+                               >
+                       </File>
+                       <File
                                RelativePath="..\dom\ScriptableDocumentParser.cpp"
                                >
                                <FileConfiguration
index 7a1b190..f36a2a6 100644 (file)
                9B417065125662B3006B28FC /* ApplyBlockElementCommand.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9B417063125662B3006B28FC /* ApplyBlockElementCommand.cpp */; };
                9BAB6C6C12550631001626D4 /* EditingStyle.h in Headers */ = {isa = PBXBuildFile; fileRef = 9BAB6C6A12550631001626D4 /* EditingStyle.h */; settings = {ATTRIBUTES = (Private, ); }; };
                9BAB6C6D12550631001626D4 /* EditingStyle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9BAB6C6B12550631001626D4 /* EditingStyle.cpp */; };
+               9BD0BF9312A42BF50072FD43 /* ScopedEventQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 9BD0BF9112A42BF50072FD43 /* ScopedEventQueue.h */; };
+               9BD0BF9412A42BF50072FD43 /* ScopedEventQueue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9BD0BF9212A42BF50072FD43 /* ScopedEventQueue.cpp */; };
                9F0D6B2E121BFEBA006C0288 /* InspectorProfilerAgent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9F0D6B2C121BFEBA006C0288 /* InspectorProfilerAgent.cpp */; };
                9F0D6B2F121BFEBA006C0288 /* InspectorProfilerAgent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9F0D6B2D121BFEBA006C0288 /* InspectorProfilerAgent.h */; };
                9F3B947E12241758005304E7 /* ScriptHeapSnapshot.h in Headers */ = {isa = PBXBuildFile; fileRef = 9F3B947D12241758005304E7 /* ScriptHeapSnapshot.h */; };
                9B417063125662B3006B28FC /* ApplyBlockElementCommand.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ApplyBlockElementCommand.cpp; sourceTree = "<group>"; };
                9BAB6C6A12550631001626D4 /* EditingStyle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EditingStyle.h; sourceTree = "<group>"; };
                9BAB6C6B12550631001626D4 /* EditingStyle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EditingStyle.cpp; sourceTree = "<group>"; };
+               9BD0BF9112A42BF50072FD43 /* ScopedEventQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ScopedEventQueue.h; sourceTree = "<group>"; };
+               9BD0BF9212A42BF50072FD43 /* ScopedEventQueue.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ScopedEventQueue.cpp; sourceTree = "<group>"; };
                9F0D6B2C121BFEBA006C0288 /* InspectorProfilerAgent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InspectorProfilerAgent.cpp; sourceTree = "<group>"; };
                9F0D6B2D121BFEBA006C0288 /* InspectorProfilerAgent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InspectorProfilerAgent.h; sourceTree = "<group>"; };
                9F3B947D12241758005304E7 /* ScriptHeapSnapshot.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ScriptHeapSnapshot.h; sourceTree = "<group>"; };
                                A84D827B11D333ED00972990 /* RawDataDocumentParser.h */,
                                85031B350A44EFC700F992E0 /* RegisteredEventListener.cpp */,
                                85031B360A44EFC700F992E0 /* RegisteredEventListener.h */,
+                               9BD0BF9212A42BF50072FD43 /* ScopedEventQueue.cpp */,
+                               9BD0BF9112A42BF50072FD43 /* ScopedEventQueue.h */,
                                A84D82C011D3474800972990 /* ScriptableDocumentParser.cpp */,
                                A84D82BF11D3474800972990 /* ScriptableDocumentParser.h */,
                                08A484750E5272C500C3FE76 /* ScriptElement.cpp */,
                                5DFE8F570D16477C0076E937 /* ScheduledAction.h in Headers */,
                                1CEFC9B90D78DC8C007D2579 /* SchedulePair.h in Headers */,
                                5162C7F511F77EFB00612EFE /* SchemeRegistry.h in Headers */,
+                               9BD0BF9312A42BF50072FD43 /* ScopedEventQueue.h in Headers */,
                                BCEC01BE0C274DAC009F4EC9 /* Screen.h in Headers */,
                                A84D82C111D3474800972990 /* ScriptableDocumentParser.h in Headers */,
                                F39BE95C12673BF400E0A674 /* ScriptArguments.h in Headers */,
                                1CEFC9BA0D78DC8C007D2579 /* SchedulePair.cpp in Sources */,
                                1CE24F970D7CAF0E007E04C2 /* SchedulePairMac.mm in Sources */,
                                5162C7F411F77EFB00612EFE /* SchemeRegistry.cpp in Sources */,
+                               9BD0BF9412A42BF50072FD43 /* ScopedEventQueue.cpp in Sources */,
                                BCEC01BD0C274DAC009F4EC9 /* Screen.cpp in Sources */,
                                A84D82C211D3474800972990 /* ScriptableDocumentParser.cpp in Sources */,
                                F39BE95B12673BF400E0A674 /* ScriptArguments.cpp in Sources */,
index e158828..645768d 100644 (file)
@@ -1027,12 +1027,12 @@ static void dispatchChildInsertionEvents(Node* child)
     RefPtr<Document> document = child->document();
 
     if (c->parentNode() && document->hasListenerType(Document::DOMNODEINSERTED_LISTENER))
-        c->dispatchEvent(MutationEvent::create(eventNames().DOMNodeInsertedEvent, true, c->parentNode()));
+        c->dispatchScopedEvent(MutationEvent::create(eventNames().DOMNodeInsertedEvent, true, c->parentNode()));
 
     // dispatch the DOMNodeInsertedIntoDocument event to all descendants
     if (c->inDocument() && document->hasListenerType(Document::DOMNODEINSERTEDINTODOCUMENT_LISTENER)) {
         for (; c; c = c->traverseNextNode(child))
-            c->dispatchEvent(MutationEvent::create(eventNames().DOMNodeInsertedIntoDocumentEvent, false));
+            c->dispatchScopedEvent(MutationEvent::create(eventNames().DOMNodeInsertedIntoDocumentEvent, false));
     }
 }
 
@@ -1049,12 +1049,12 @@ static void dispatchChildRemovalEvents(Node* child)
 
     // dispatch pre-removal mutation events
     if (c->parentNode() && document->hasListenerType(Document::DOMNODEREMOVED_LISTENER))
-        c->dispatchEvent(MutationEvent::create(eventNames().DOMNodeRemovedEvent, true, c->parentNode()));
+        c->dispatchScopedEvent(MutationEvent::create(eventNames().DOMNodeRemovedEvent, true, c->parentNode()));
 
     // dispatch the DOMNodeRemovedFromDocument event to all descendants
     if (c->inDocument() && document->hasListenerType(Document::DOMNODEREMOVEDFROMDOCUMENT_LISTENER)) {
         for (; c; c = c->traverseNextNode(child))
-            c->dispatchEvent(MutationEvent::create(eventNames().DOMNodeRemovedFromDocumentEvent, false));
+            c->dispatchScopedEvent(MutationEvent::create(eventNames().DOMNodeRemovedFromDocumentEvent, false));
     }
 }
 
index 2f3510d..dc4e39b 100644 (file)
 #include "ProgressEvent.cpp"
 #include "Range.cpp"
 #include "RegisteredEventListener.cpp"
+#include "ScopedEventQueue.cpp"
 #include "ScriptElement.cpp"
 #include "ScriptExecutionContext.cpp"
 #include "ScriptableDocumentParser.cpp"
index 30d239b..8e633aa 100644 (file)
@@ -1218,7 +1218,7 @@ void Element::dispatchAttrRemovalEvent(Attribute*)
     if (!document()->hasListenerType(Document::DOMATTRMODIFIED_LISTENER))
         return;
     ExceptionCode ec = 0;
-    dispatchEvent(MutationEvent::create(DOMAttrModifiedEvent, true, attr, attr->value(),
+    dispatchScopedEvent(MutationEvent::create(DOMAttrModifiedEvent, true, attr, attr->value(),
         attr->value(), document()->attrName(attr->id()), MutationEvent::REMOVAL), ec);
 #endif
 }
@@ -1231,7 +1231,7 @@ void Element::dispatchAttrAdditionEvent(Attribute*)
     if (!document()->hasListenerType(Document::DOMATTRMODIFIED_LISTENER))
         return;
     ExceptionCode ec = 0;
-    dispatchEvent(MutationEvent::create(DOMAttrModifiedEvent, true, attr, attr->value(),
+    dispatchScopedEvent(MutationEvent::create(DOMAttrModifiedEvent, true, attr, attr->value(),
         attr->value(), document()->attrName(attr->id()), MutationEvent::ADDITION), ec);
 #endif
 }
index 564be32..81a642b 100644 (file)
@@ -70,6 +70,7 @@
 #include "ProgressEvent.h"
 #include "RegisteredEventListener.h"
 #include "RenderBox.h"
+#include "ScopedEventQueue.h"
 #include "ScriptController.h"
 #include "SelectorNodeList.h"
 #include "StaticNodeList.h"
@@ -2579,6 +2580,14 @@ bool Node::dispatchEvent(PassRefPtr<Event> prpEvent)
     return dispatchGenericEvent(event.release());
 }
 
+void Node::dispatchScopedEvent(PassRefPtr<Event> event)
+{
+    // We need to set the target here because it can go away by the time we actually fire the event.
+    event->setTarget(eventTargetRespectingSVGTargetRules(this));
+
+    ScopedEventQueue::instance()->enqueueEvent(event);
+}
+
 static const EventContext* topEventContext(const Vector<EventContext>& ancestors)
 {
     return ancestors.isEmpty() ? 0 : &ancestors.last();
@@ -2693,7 +2702,7 @@ void Node::dispatchSubtreeModifiedEvent()
     if (!document()->hasListenerType(Document::DOMSUBTREEMODIFIED_LISTENER))
         return;
 
-    dispatchEvent(MutationEvent::create(eventNames().DOMSubtreeModifiedEvent, true));
+    dispatchScopedEvent(MutationEvent::create(eventNames().DOMSubtreeModifiedEvent, true));
 }
 
 void Node::dispatchUIEvent(const AtomicString& eventType, int detail, PassRefPtr<Event> underlyingEvent)
@@ -2703,10 +2712,10 @@ void Node::dispatchUIEvent(const AtomicString& eventType, int detail, PassRefPtr
            eventType == eventNames().DOMFocusInEvent || eventType == eventNames().DOMFocusOutEvent || eventType == eventNames().DOMActivateEvent);
     
     bool cancelable = eventType == eventNames().DOMActivateEvent;
-    
+
     RefPtr<UIEvent> event = UIEvent::create(eventType, true, cancelable, document()->defaultView(), detail);
     event->setUnderlyingEvent(underlyingEvent);
-    dispatchEvent(event.release());
+    dispatchScopedEvent(event.release());
 }
 
 bool Node::dispatchKeyEvent(const PlatformKeyboardEvent& key)
index 1c5c5c4..0881f08 100644 (file)
@@ -520,7 +520,8 @@ public:
     virtual void postDispatchEventHandler(Event*, void* /*dataFromPreDispatch*/) { }
 
     using EventTarget::dispatchEvent;
-    virtual bool dispatchEvent(PassRefPtr<Event>);
+    bool dispatchEvent(PassRefPtr<Event>);
+    void dispatchScopedEvent(PassRefPtr<Event>);
 
     bool dispatchGenericEvent(PassRefPtr<Event>);
     virtual void handleLocalEvents(Event*);
diff --git a/WebCore/dom/ScopedEventQueue.cpp b/WebCore/dom/ScopedEventQueue.cpp
new file mode 100644 (file)
index 0000000..680d82d
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "ScopedEventQueue.h"
+
+#include "Event.h"
+#include "EventTarget.h"
+
+namespace WebCore {
+
+ScopedEventQueue* ScopedEventQueue::s_instance = 0;
+
+ScopedEventQueue::ScopedEventQueue()
+    : m_scopingLevel(0)
+{
+}
+
+ScopedEventQueue::~ScopedEventQueue()
+{
+    ASSERT(!m_scopingLevel);
+    ASSERT(!m_queuedEvents.size());
+}
+
+void ScopedEventQueue::initialize()
+{
+    ASSERT(!s_instance);
+    OwnPtr<ScopedEventQueue> instance = adoptPtr(new ScopedEventQueue);
+    s_instance = instance.leakPtr();
+}
+
+void ScopedEventQueue::enqueueEvent(PassRefPtr<Event> event)
+{
+    if (m_scopingLevel)
+        m_queuedEvents.append(event);
+    else
+        dispatchEvent(event);
+}
+
+void ScopedEventQueue::dispatchAllEvents()
+{
+    Vector<RefPtr<Event> > queuedEvents;
+    queuedEvents.swap(m_queuedEvents);
+
+    for (size_t i = 0; i < queuedEvents.size(); i++)
+        dispatchEvent(queuedEvents[i].release());
+}
+
+void ScopedEventQueue::dispatchEvent(PassRefPtr<Event> event) const
+{
+    RefPtr<EventTarget> eventTarget = event->target();
+    eventTarget->dispatchEvent(event);
+}
+
+ScopedEventQueue* ScopedEventQueue::instance()
+{
+    if (!s_instance)
+        initialize();
+
+    return s_instance;
+}
+
+void ScopedEventQueue::incrementScopingLevel()
+{
+    m_scopingLevel++;
+}
+
+void ScopedEventQueue::decrementScopingLevel()
+{
+    ASSERT(m_scopingLevel);
+    m_scopingLevel--;
+    if (!m_scopingLevel)
+        dispatchAllEvents();
+}
+
+}
diff --git a/WebCore/dom/ScopedEventQueue.h b/WebCore/dom/ScopedEventQueue.h
new file mode 100644 (file)
index 0000000..52415d4
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef ScopedEventQueue_h
+#define ScopedEventQueue_h
+
+#include <wtf/Noncopyable.h>
+#include <wtf/PassOwnPtr.h>
+#include <wtf/PassRefPtr.h>
+#include <wtf/Vector.h>
+
+namespace WebCore {
+
+class Event;
+
+class ScopedEventQueue {
+    WTF_MAKE_NONCOPYABLE(ScopedEventQueue);
+
+public:
+    ~ScopedEventQueue();
+
+    void enqueueEvent(PassRefPtr<Event>);
+    void dispatchAllEvents();
+    static ScopedEventQueue* instance();
+
+    void incrementScopingLevel();
+    void decrementScopingLevel();
+
+private:
+    ScopedEventQueue();
+    static void initialize();
+    void dispatchEvent(PassRefPtr<Event>) const;
+
+    Vector<RefPtr<Event> > m_queuedEvents;
+    unsigned m_scopingLevel;
+
+    static ScopedEventQueue* s_instance;
+};
+
+class EventQueueScope {
+    WTF_MAKE_NONCOPYABLE(EventQueueScope);
+
+public:
+    EventQueueScope() { ScopedEventQueue::instance()->incrementScopingLevel(); }
+    ~EventQueueScope() { ScopedEventQueue::instance()->decrementScopingLevel(); }
+};
+
+}
+
+#endif // ScopedEventQueue_h
index 5f0cfd4..1b4451d 100644 (file)
@@ -33,6 +33,7 @@
 #include "Element.h"
 #include "EventNames.h"
 #include "Frame.h"
+#include "ScopedEventQueue.h"
 #include "SelectionController.h"
 #include "VisiblePosition.h"
 #include "htmlediting.h"
@@ -84,10 +85,13 @@ void EditCommand::apply()
     if (isTopLevelCommand())
         updateLayout();
 
-    DeleteButtonController* deleteButtonController = frame->editor()->deleteButtonController();
-    deleteButtonController->disable();
-    doApply();
-    deleteButtonController->enable();
+    {
+        EventQueueScope scope;
+        DeleteButtonController* deleteButtonController = frame->editor()->deleteButtonController();
+        deleteButtonController->disable();
+        doApply();
+        deleteButtonController->enable();
+    }
 
     if (isTopLevelCommand()) {
         // Only need to call appliedEditing for top-level commands, and TypingCommands do it on their