Bug 1395168 - Add tests for key synthesis; r=esawin
authorJim Chen <nchen@mozilla.com>
Thu, 31 Aug 2017 13:42:51 -0400
changeset 378147 b0066ef926e105ad6985fc53df554bdccc476552
parent 378146 d1768de3e9f2ed9539b681bb31a491c426205ee5
child 378148 0f25af758ee45b615328e46ae385d6e940e20e71
push id50200
push userarchaeopteryx@coole-files.de
push dateFri, 01 Sep 2017 08:44:00 +0000
treeherderautoland@10bb5830a5b2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersesawin
bugs1395168, 1387889
milestone57.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 1395168 - Add tests for key synthesis; r=esawin Add tests for synthesizing keys, including test for dummy keys and test for wrong metastate for synthesized non-English keys (i.e. bug 1387889). MozReview-Commit-ID: SvddU2BHle
mobile/android/tests/browser/robocop/robocop_input.html
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testInputConnection.java
--- a/mobile/android/tests/browser/robocop/robocop_input.html
+++ b/mobile/android/tests/browser/robocop/robocop_input.html
@@ -21,16 +21,21 @@
       let designMode = document.getElementById("design-mode");
       try {
         designMode.contentDocument.designMode = "on";
       } catch (e) {
         // Setting designMode above sometimes fails, so try again later.
         setTimeout(function() { designMode.contentDocument.designMode = "on" }, 0);
       }
 
+      // Add key listeners to trigger dummy key synthesis.
+      input.addEventListener("keydown", function() {});
+      textArea.addEventListener("keydown", function() {});
+      contentEditable.addEventListener("keydown", function() {});
+
       // An input that resets the editor on every input by resetting the value property.
       let resetting_input = document.getElementById("resetting-input");
       resetting_input.addEventListener("input", function() {
         this.value = this.value;
       });
 
       // An input that hides on input.
       let hiding_input = document.getElementById("hiding-input");
@@ -59,16 +64,37 @@
             events_log += "|";
             break;
           default:
             events_log += "?";
             break;
         }
       }
 
+      let key_log;
+      function get_key_metastate(event) {
+        return (event.ctrlKey ? "C" : "c") +
+               (event.altKey ? "A" : "a") +
+               (event.shiftKey ? "S" : "s") +
+               (event.metaKey ? "M" : "m");
+      }
+
+      function log_key(event) {
+        switch (event.type) {
+          case "keydown":
+          case "keypress":
+          case "keyup":
+            key_log += `${event.type}:${event.key},${get_key_metastate(event)};`;
+            break;
+          default:
+            key_log += "unknown;";
+            break;
+        }
+      }
+
       function get_event_target() {
         var editor = getEditor();
         var parent = SpecialPowers.unwrap(editor.rootElement.parentElement);
         if (parent instanceof HTMLInputElement || parent instanceof HTMLTextAreaElement) {
           // "selectionchange" is only dispatched to the element itself,
           // so use the element as the target instead of using the document.
           return parent;
         }
@@ -177,16 +203,35 @@
 
           let target = get_event_target();
           target.removeEventListener("compositionstart", log_event);
           target.removeEventListener("compositionupdate", log_event);
           target.removeEventListener("compositionend", log_event);
           target.removeEventListener("selectionchange", log_event);
         },
 
+        start_key_log: function() {
+          // Reset the log
+          key_log = "";
+
+          let target = get_event_target();
+          target.addEventListener("keydown", log_key);
+          target.addEventListener("keypress", log_key);
+          target.addEventListener("keyup", log_key);
+        },
+
+        end_key_log: function() {
+          java.asyncCall("setKeyLog", key_log);
+
+          let target = get_event_target();
+          target.removeEventListener("keydown", log_key);
+          target.removeEventListener("keypress", log_key);
+          target.removeEventListener("keyup", log_key);
+        },
+
         test_bug1123514: function() {
           document.activeElement.addEventListener("input", function() {
             // Only works on input and textarea.
             if (this.value === "b") {
               this.value = "abc";
             }
           }, {once: true});
         },
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testInputConnection.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testInputConnection.java
@@ -21,25 +21,29 @@ import android.view.inputmethod.InputCon
 /**
  * Tests the proper operation of GeckoInputConnection
  */
 public class testInputConnection extends JavascriptBridgeTest {
 
     private static final String INITIAL_TEXT = "foo";
 
     private String mEventsLog;
+    private String mKeyLog;
 
     public void testInputConnection() throws InterruptedException {
         GeckoHelper.blockForReady();
 
         // Spatial navigation interferes with design-mode key event tests.
         mActions.setPref("snav.enabled", false, /* flush */ false);
         // Enable "selectionchange" events for input/textarea.
         mActions.setPref("dom.select_events.enabled", true, /* flush */ false);
         mActions.setPref("dom.select_events.textcontrols.enabled", true, /* flush */ false);
+        // Enable dummy key synthesis.
+        mActions.setPref("intl.ime.hack.on_ime_unaware_apps.fire_key_events_for_composition",
+                         true, /* flush */ false);
 
         final String url = mStringHelper.ROBOCOP_INPUT_URL;
         NavigationHelper.enterAndLoadUrl(url);
         mToolbar.assertTitle(url);
 
         // First run tests inside the normal input field.
         getJS().syncCall("focus_input", INITIAL_TEXT);
         mGeckoView.mTextInput
@@ -82,16 +86,24 @@ public class testInputConnection extends
     public void setEventsLog(final String log) {
         mEventsLog = log;
     }
 
     public String getEventsLog() {
         return mEventsLog;
     }
 
+    public void setKeyLog(final String log) {
+        mKeyLog = log;
+    }
+
+    public String getKeyLog() {
+        return mKeyLog;
+    }
+
     private class BasicInputConnectionTest extends InputConnectionTest {
         private final String mType;
 
         BasicInputConnectionTest(final String type) {
             mType = type;
         }
 
         @Override
@@ -183,16 +195,44 @@ public class testInputConnection extends
 
             ic.sendKeyEvent(tKey);
             ic.sendKeyEvent(KeyEvent.changeAction(tKey, KeyEvent.ACTION_UP));
             assertTextAndSelectionAt("Can type using event", ic, "frabat", 6);
 
             ic.deleteSurroundingText(6, 0);
             assertTextAndSelectionAt("Can clear text", ic, "", 0);
 
+            // Test key synthesis.
+            getJS().syncCall("start_key_log");
+            ic.setComposingText("f", 1); // Synthesizes dummy key.
+            assertTextAndSelectionAt("Can compose F key", ic, "f", 1);
+            ic.finishComposingText(); // Does not synthesize key.
+            assertTextAndSelectionAt("Can finish F key", ic, "f", 1);
+            ic.commitText("o", 1); // Synthesizes O key.
+            assertTextAndSelectionAt("Can commit O key", ic, "fo", 2);
+            ic.commitText("of", 1); // Synthesizes dummy key.
+            assertTextAndSelectionAt("Can commit non-key string", ic, "foof", 4);
+
+            getJS().syncCall("end_key_log");
+            if (mType.equals("designMode")) {
+                // designMode doesn't support dummy key synthesis.
+                fAssertEquals("Can synthesize keys",
+                              "keydown:o,casm;keypress:o,casm;keyup:o,casm;", // O key
+                              getKeyLog());
+            } else {
+                fAssertEquals("Can synthesize keys",
+                              "keydown:Unidentified,casm;keyup:Unidentified,casm;" + // Dummy
+                              "keydown:o,casm;keypress:o,casm;keyup:o,casm;" +       // O key
+                              "keydown:Unidentified,casm;keyup:Unidentified,casm;",  // Dummy
+                              getKeyLog());
+            }
+
+            ic.deleteSurroundingText(4, 0);
+            assertTextAndSelectionAt("Can clear text", ic, "", 0);
+
             // Bug 1133802, duplication when setting the same composing text more than once.
             ic.setComposingText("foo", 1);
             assertTextAndSelectionAt("Can set the composing text", ic, "foo", 3);
             ic.setComposingText("foo", 1);
             assertTextAndSelectionAt("Can set the same composing text", ic, "foo", 3);
             ic.setComposingText("bar", 1);
             assertTextAndSelectionAt("Can set different composing text", ic, "bar", 3);
             ic.setComposingText("bar", 1);
@@ -320,16 +360,30 @@ public class testInputConnection extends
             getJS().syncCall("end_events_log");
             // compositionstart > compositionchange > selectionchange x2
             fAssertEquals("Can update composition caret", "<=||", getEventsLog());
 
             ic.finishComposingText();
             ic.deleteSurroundingText(0, 3);
             assertTextAndSelectionAt("Can clear text", ic, "", 0);
 
+            // Bug 1387889 - Latin sharp S (U+00DF) triggers Alt+S shortcut
+            getJS().syncCall("start_key_log");
+            ic.commitText("\u00df", 1); // Synthesizes "Latin sharp S" key without modifiers.
+            assertTextAndSelectionAt("Can commit Latin sharp S key", ic, "\u00df", 1);
+
+            getJS().syncCall("end_key_log");
+            fAssertEquals("Can synthesize sharp S key",
+                          "keydown:\u00df,casm;keypress:\u00df,casm;keyup:\u00df,casm;",
+                          getKeyLog());
+
+            ic.finishComposingText();
+            ic.deleteSurroundingText(1, 0);
+            assertTextAndSelectionAt("Can clear text", ic, "", 0);
+
             // Make sure we don't leave behind stale events for the following test.
             processGeckoEvents();
             processInputConnectionEvents();
         }
     }
 
     /**
      * ResettingInputConnectionTest performs tests on the resetting input in