Bug 1288640 - Make TextComposition not dispatch eCompositionChange events (DOM "text" event) in the default group of web content r=smaug
authorMasayuki Nakano <masayuki@d-toybox.com>
Tue, 27 Nov 2018 13:26:51 +0000
changeset 504703 8be20508c9d5577ec80c7e181fae203a7f736bc1
parent 504702 f58c9289f62e89f87c1899b9b7b89dda191d740a
child 504729 f83dcdf697697c8708e8015cd5c7b0b49d170b7f
child 504730 22794547ae9ad0014c4aec7653d5a66dffdfc505
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1288640
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1288640 - Make TextComposition not dispatch eCompositionChange events (DOM "text" event) in the default group of web content r=smaug The usage of our specific "text" event is enough low (0.0003%). So, let's stop dispatching the event in the default group of web content. Once we release this new behavior, we can get rid of dispatching the event even in chrome. Then, we can optimize the event order for new specs. Differential Revision: https://phabricator.services.mozilla.com/D13034
dom/events/TextComposition.cpp
dom/events/test/mochitest.ini
dom/events/test/test_text_event_in_content.html
editor/libeditor/tests/test_bug1230473.html
editor/libeditor/tests/test_bug697842.html
modules/libpref/init/StaticPrefList.h
widget/tests/test_assign_event_data.html
--- a/dom/events/TextComposition.cpp
+++ b/dom/events/TextComposition.cpp
@@ -12,16 +12,17 @@
 #include "nsIPresShell.h"
 #include "nsPresContext.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/EditorBase.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/IMEStateManager.h"
 #include "mozilla/MiscEvents.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs.h"
 #include "mozilla/TextComposition.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/Unused.h"
 #include "mozilla/dom/TabParent.h"
 
 #ifdef XP_MACOSX
 // Some defiens will be conflict with OSX SDK
 #define TextRange _TextRange
@@ -160,16 +161,21 @@ void
 TextComposition::DispatchEvent(WidgetCompositionEvent* aDispatchEvent,
                                nsEventStatus* aStatus,
                                EventDispatchingCallback* aCallBack,
                                const WidgetCompositionEvent *aOriginalEvent)
 {
   nsPluginInstanceOwner::GeneratePluginEvent(aOriginalEvent,
                                              aDispatchEvent);
 
+  if (aDispatchEvent->mMessage == eCompositionChange &&
+      StaticPrefs::
+        dom_compositionevent_text_dispatch_only_system_group_in_content()) {
+    aDispatchEvent->mFlags.mOnlySystemGroupDispatchInContent = true;
+  }
   EventDispatcher::Dispatch(mNode, mPresContext,
                             aDispatchEvent, nullptr, aStatus, aCallBack);
 
   OnCompositionEventDispatched(aDispatchEvent);
 }
 
 void
 TextComposition::OnCompositionEventDiscarded(
--- a/dom/events/test/mochitest.ini
+++ b/dom/events/test/mochitest.ini
@@ -183,16 +183,17 @@ skip-if = toolkit == 'android' #CRASH_DU
 [test_messageEvent.html]
 [test_messageEvent_init.html]
 [test_moz_mouse_pixel_scroll_event.html]
 [test_offsetxy.html]
 [test_onerror_handler_args.html]
 [test_passive_listeners.html]
 [test_paste_image.html]
 skip-if = headless # Bug 1405869
+[test_text_event_in_content.html]
 [test_wheel_default_action.html]
 skip-if = (verify && debug && (os == 'linux'))
 [test_bug687787.html]
 [test_bug1305458.html]
 [test_bug1298970.html]
 [test_bug1304044.html]
 [test_bug1332699.html]
 [test_bug1339758.html]
new file mode 100644
--- /dev/null
+++ b/dom/events/test/test_text_event_in_content.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<html>
+<head>
+  <title>Not dispatching DOM "text" event on web apps</title>
+  <script src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<input id="input">
+<textarea id="textarea"></textarea>
+<div contenteditable id="editor"><p><br></p></div>
+<script>
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(async function doTests() {
+  await SpecialPowers.pushPrefEnv({set: [
+    ["dom.compositionevent.text.dispatch_only_system_group_in_content", true],
+  ]});
+  for (let editorId of ["input", "textarea", "editor"]) {
+    let editor = document.getElementById(editorId);
+    editor.focus();
+    let fired = false;
+    function onText() {
+      fired = true;
+    }
+    editor.addEventListener("text", onText);
+
+    fired = false;
+    synthesizeCompositionChange({
+      composition: {string: "abc",
+                    clauses: [{length: 3, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
+      caret: {start: 3, length: 0},
+    });
+    ok(!fired, `Starting composition shouldn't fire DOM "text" event in ${editorId}`);
+    fired = false;
+    synthesizeComposition({type: "compositioncommitasis", key: {key: "KEY_Enter"}});
+    ok(!fired, `Committing composition with the latest string shouldn't fire DOM "text" event in ${editorId}`);
+
+    fired = false;
+    synthesizeCompositionChange({
+      composition: {string: "def",
+                    clauses: [{length: 3, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
+      caret: {start: 3, length: 0},
+    });
+    ok(!fired, `Restarting composition shouldn't fire DOM "text" event in ${editorId}`);
+    fired = false;
+    synthesizeComposition({type: "compositioncommit", data: "", key: {key: "KEY_Escape"}});
+    ok(!fired, `Committing composition with empty string shouldn't fire DOM "text" event in ${editorId}`);
+
+    fired = false;
+    synthesizeCompositionChange({
+      composition: {string: "de",
+                    clauses: [{length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
+      caret: {start: 2, length: 0},
+    });
+    ok(!fired, `Restarting composition shouldn't fire DOM "text" event in ${editorId}`);
+    fired = false;
+    synthesizeComposition({type: "compositioncommit", data: "def", key: {key: "KEY_Escape"}});
+    ok(!fired, `Committing composition with new string shouldn't fire DOM "text" event in ${editorId}`);
+
+    fired = false;
+    synthesizeComposition({type: "compositioncommit", data: "ghi"});
+    ok(!fired, `Inserting string shouldn't fire DOM "text" event in ${editorId}`);
+
+    editor.removeEventListener("text", onText);
+  }
+  SimpleTest.finish();
+});
+</script>
+</body>
+</html>
--- a/editor/libeditor/tests/test_bug1230473.html
+++ b/editor/libeditor/tests/test_bug1230473.html
@@ -74,24 +74,26 @@ SimpleTest.waitForFocus(() => {
     synthesizeCompositionChange({ composition: { string: "a", clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }] },
                                   caret: { start: 1, length: 0 }, key: { key: "a" }});
     aEditor.removeEventListener("compositionupdate", committer, true);
     ok(!isComposing(), "composition in " + aEditor.id + " should be committed by compositionupdate event handler");
     is(value(), "a", "composition in " + aEditor.id + " should have \"a\" since IME committed with it");
     clear();
 
     // Committing at first text (eCompositionChange)
-    aEditor.focus();
-    aEditor.addEventListener("text", committer, true);
-    synthesizeCompositionChange({ composition: { string: "a", clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }] },
-                                  caret: { start: 1, length: 0 }, key: { key: "a" }});
-    aEditor.removeEventListener("text", committer, true);
-    ok(!isComposing(), "composition in " + aEditor.id + " should be committed by text event handler");
-    is(value(), "", "composition in " + aEditor.id + " should have inserted any text since it's committed at first text");
-    clear();
+    if (!SpecialPowers.getBoolPref("dom.compositionevent.text.dispatch_only_system_group_in_content")) {
+      aEditor.focus();
+      aEditor.addEventListener("text", committer, true);
+      synthesizeCompositionChange({ composition: { string: "a", clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }] },
+                                    caret: { start: 1, length: 0 }, key: { key: "a" }});
+      aEditor.removeEventListener("text", committer, true);
+      ok(!isComposing(), "composition in " + aEditor.id + " should be committed by text event handler");
+      is(value(), "", "composition in " + aEditor.id + " should have inserted any text since it's committed at first text");
+      clear();
+    }
 
     // Committing at second compositionupdate
     aEditor.focus();
     // FYI: "compositionstart" will be dispatched automatically.
     synthesizeCompositionChange({ composition: { string: "a", clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }] },
                                   caret: { start: 1, length: 0 }, key: { key: "a" }});
     ok(isComposing(), "composition should be in " + aEditor.id + " before dispatching second compositionupdate");
     is(value(), "a", "composition in " + aEditor.id + " should be 'a' before dispatching second compositionupdate");
@@ -99,29 +101,31 @@ SimpleTest.waitForFocus(() => {
     synthesizeCompositionChange({ composition: { string: "ab", clauses: [{length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE }] },
                                   caret: { start: 2, length: 0 }, key: { key: "b" }});
     aEditor.removeEventListener("compositionupdate", committer, true);
     ok(!isComposing(), "composition in " + aEditor.id + " should be committed by compositionupdate event handler");
     is(value(), "ab", "composition in " + aEditor.id + " should have \"ab\" since IME committed with it");
     clear();
 
     // Committing at second text (eCompositionChange)
-    aEditor.focus();
-    // FYI: "compositionstart" will be dispatched automatically.
-    synthesizeCompositionChange({ composition: { string: "a", clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }] },
-                                  caret: { start: 1, length: 0 }, key: { key: "a" }});
-    ok(isComposing(), "composition should be in " + aEditor.id + " before dispatching second text");
-    is(value(), "a", "composition in " + aEditor.id + " should be 'a' before dispatching second text");
-    aEditor.addEventListener("text", committer, true);
-    synthesizeCompositionChange({ composition: { string: "ab", clauses: [{length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE }] },
-                                  caret: { start: 2, length: 0 }, key: { key: "b" }});
-    aEditor.removeEventListener("text", committer, true);
-    ok(!isComposing(), "composition in " + aEditor.id + " should be committed by text event handler");
-    is(value(), "ab", "composition in " + aEditor.id + " should have \"ab\" since IME committed with it");
-    clear();
+    if (!SpecialPowers.getBoolPref("dom.compositionevent.text.dispatch_only_system_group_in_content")) {
+      aEditor.focus();
+      // FYI: "compositionstart" will be dispatched automatically.
+      synthesizeCompositionChange({ composition: { string: "a", clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }] },
+                                    caret: { start: 1, length: 0 }, key: { key: "a" }});
+      ok(isComposing(), "composition should be in " + aEditor.id + " before dispatching second text");
+      is(value(), "a", "composition in " + aEditor.id + " should be 'a' before dispatching second text");
+      aEditor.addEventListener("text", committer, true);
+      synthesizeCompositionChange({ composition: { string: "ab", clauses: [{length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE }] },
+                                    caret: { start: 2, length: 0 }, key: { key: "b" }});
+      aEditor.removeEventListener("text", committer, true);
+      ok(!isComposing(), "composition in " + aEditor.id + " should be committed by text event handler");
+      is(value(), "ab", "composition in " + aEditor.id + " should have \"ab\" since IME committed with it");
+      clear();
+    }
   }
   runTest(document.getElementById("input"));
   runTest(document.getElementById("textarea"));
   runTest(document.getElementById("div"));
   SimpleTest.finish();
 });
 </script>
 </body>
--- a/editor/libeditor/tests/test_bug697842.html
+++ b/editor/libeditor/tests/test_bug697842.html
@@ -48,17 +48,16 @@ function runTests() {
       }
       aEvent.stopPropagation();
       aEvent.preventDefault();
     }
 
     editor.addEventListener("compositionstart", handler, true);
     editor.addEventListener("compositionend", handler, true);
     editor.addEventListener("compositionupdate", handler, true);
-    editor.addEventListener("text", handler, true);
 
     // input first character
     composingString = "\u306B";
     synthesizeCompositionChange(
       { "composition":
         { "string": composingString,
           "clauses":
           [
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -174,16 +174,24 @@ VARCACHE_PREF(
 #endif
 VARCACHE_PREF(
   "dom.animations-api.timelines.enabled",
    dom_animations_api_timelines_enabled,
   bool, PREF_VALUE
 )
 #undef PREF_VALUE
 
+// Whehter Mozilla specific "text" event should be dispatched only in the
+// system group or not in content.
+VARCACHE_PREF(
+  "dom.compositionevent.text.dispatch_only_system_group_in_content",
+   dom_compositionevent_text_dispatch_only_system_group_in_content,
+   bool, true
+)
+
 // How long a content process can take before closing its IPC channel
 // after shutdown is initiated.  If the process exceeds the timeout,
 // we fear the worst and kill it.
 #if !defined(DEBUG) && !defined(MOZ_ASAN) && !defined(MOZ_VALGRIND) && \
     !defined(MOZ_TSAN)
 # define PREF_VALUE 5
 #else
 # define PREF_VALUE 0
--- a/widget/tests/test_assign_event_data.html
+++ b/widget/tests/test_assign_event_data.html
@@ -332,17 +332,17 @@ const kTests = [
   { description: "WidgetTextEvent (text)",
     targetID: "input-text", eventType: "text",
     dispatchEvent: function () {
       document.getElementById(this.targetID).value = "";
       document.getElementById(this.targetID).focus();
       synthesizeComposition({ type: "compositioncommit", data: "\u306D", key: { key: "," } });
     },
     canRun: function () {
-      return true;
+      return !SpecialPowers.getBoolPref("dom.compositionevent.text.dispatch_only_system_group_in_content");
     },
     todoMismatch: [ ],
   },
   { description: "WidgetCompositionEvent (compositionupdate)",
     targetID: "input-text", eventType: "compositionupdate",
     dispatchEvent: function () {
       document.getElementById(this.targetID).value = "";
       document.getElementById(this.targetID).focus();