Bug 1414322 - Refactor sendKeysToElement methods. r=ato, a=test-only
authorHenrik Skupin <mail@hskupin.info>
Thu, 09 Nov 2017 20:39:51 +0100
changeset 445163 13dcd46fb2867118359c0dadcf23530139749aa2
parent 445162 9a6aa369b0d6b532a6ad64ca7d80f6566da0e8bd
child 445164 e171f519ec08f9d8063f39331a694b27ff38d58d
push id1618
push userCallek@gmail.com
push dateThu, 11 Jan 2018 17:45:48 +0000
treeherdermozilla-release@882ca853e05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersato, test-only
bugs1414322
milestone58.0
Bug 1414322 - Refactor sendKeysToElement methods. r=ato, a=test-only Each call to sendKeysToElement should go through the interaction module, and never by directly calling event.sendKeysToElement. This will make sure that keyboard interactability checks will always be performed, even for chrome scope like alerts or modal dialogs. MozReview-Commit-ID: GoDKjMsNZsq
testing/marionette/driver.js
testing/marionette/element.js
testing/marionette/event.js
testing/marionette/interaction.js
testing/marionette/listener.js
--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -24,31 +24,29 @@ Cu.import("chrome://marionette/content/c
 Cu.import("chrome://marionette/content/cert.js");
 Cu.import("chrome://marionette/content/cookie.js");
 const {
   ChromeWebElement,
   element,
   WebElement,
 } = Cu.import("chrome://marionette/content/element.js", {});
 const {
-  ElementNotInteractableError,
   InsecureCertificateError,
   InvalidArgumentError,
   InvalidCookieDomainError,
   InvalidSelectorError,
   NoAlertOpenError,
   NoSuchFrameError,
   NoSuchWindowError,
   SessionNotCreatedError,
   UnknownError,
   UnsupportedOperationError,
   WebDriverError,
 } = Cu.import("chrome://marionette/content/error.js", {});
 Cu.import("chrome://marionette/content/evaluate.js");
-Cu.import("chrome://marionette/content/event.js");
 Cu.import("chrome://marionette/content/interaction.js");
 Cu.import("chrome://marionette/content/l10n.js");
 Cu.import("chrome://marionette/content/legacyaction.js");
 Cu.import("chrome://marionette/content/modal.js");
 Cu.import("chrome://marionette/content/proxy.js");
 Cu.import("chrome://marionette/content/reftest.js");
 Cu.import("chrome://marionette/content/session.js");
 const {
@@ -2613,18 +2611,17 @@ GeckoDriver.prototype.sendKeysToElement 
 
   let id = assert.string(cmd.parameters.id);
   let text = assert.string(cmd.parameters.text);
   let webEl = WebElement.fromUUID(id, this.context);
 
   switch (this.context) {
     case Context.Chrome:
       let el = this.curBrowser.seenEls.get(webEl);
-      await interaction.sendKeysToElement(
-          el, text, true, this.a11yChecks);
+      await interaction.sendKeysToElement(el, text, this.a11yChecks);
       break;
 
     case Context.Content:
       await this.listener.sendKeysToElement(webEl, text);
       break;
   }
 };
 
@@ -3270,32 +3267,24 @@ GeckoDriver.prototype.getTextFromDialog 
  * @throws {ElementNotInteractableError}
  *     If the current user prompt is an alert or confirm.
  * @throws {NoSuchAlertError}
  *     If there is no current user prompt.
  * @throws {UnsupportedOperationError}
  *     If the current user prompt is something other than an alert,
  *     confirm, or a prompt.
  */
-GeckoDriver.prototype.sendKeysToDialog = function(cmd) {
-  let win = assert.window(this.getCurrentWindow());
+GeckoDriver.prototype.sendKeysToDialog = async function(cmd) {
+  assert.window(this.getCurrentWindow());
   this._checkIfAlertIsPresent();
 
   // see toolkit/components/prompts/content/commonDialog.js
-  let {loginContainer, loginTextbox} = this.dialog.ui;
-  if (loginContainer.hidden) {
-    throw new ElementNotInteractableError(
-        "This prompt does not accept text input");
-  }
-
-  event.sendKeysToElement(
-      cmd.parameters.text,
-      loginTextbox,
-      {ignoreVisibility: true},
-      this.dialog.window ? this.dialog.window : win);
+  let {loginTextbox} = this.dialog.ui;
+  await interaction.sendKeysToElement(
+      loginTextbox, cmd.parameters.text, this.a11yChecks);
 };
 
 GeckoDriver.prototype._checkIfAlertIsPresent = function() {
   if (!this.dialog || !this.dialog.ui) {
     throw new NoAlertOpenError("No modal dialog is currently open");
   }
 };
 
--- a/testing/marionette/element.js
+++ b/testing/marionette/element.js
@@ -927,18 +927,17 @@ element.isInView = function(el) {
  *     the target's bounding box.
  *
  * @return {boolean}
  *     True if visible, false otherwise.
  */
 element.isVisible = function(el, x = undefined, y = undefined) {
   let win = el.ownerGlobal;
 
-  // Bug 1094246: webdriver's isShown doesn't work with content xul
-  if (!element.isXULElement(el) && !atom.isElementDisplayed(el, win)) {
+  if (!atom.isElementDisplayed(el, win)) {
     return false;
   }
 
   if (el.tagName.toLowerCase() == "body") {
     return true;
   }
 
   if (!element.inViewport(el, x, y)) {
--- a/testing/marionette/event.js
+++ b/testing/marionette/event.js
@@ -6,18 +6,16 @@
 this.event = {};
 
 "use strict";
 /* global content, is */
 
 const {interfaces: Ci, utils: Cu, classes: Cc} = Components;
 
 Cu.import("chrome://marionette/content/element.js");
-const {ElementNotInteractableError} =
-    Cu.import("chrome://marionette/content/error.js", {});
 
 this.EXPORTED_SYMBOLS = ["event"];
 
 // TODO(ato): Document!
 let seenEvent = false;
 
 function getDOMWindowUtils(win) {
   if (!win) {
@@ -1316,58 +1314,30 @@ event.sendSingleKey = function(keyToSend
     modifiers[modName] = !modifiers[modName];
   } else if (modifiers.shiftKey && keyName != "Shift") {
     keyName = keyName.toUpperCase();
   }
   event.synthesizeKey(keyName, modifiers, window);
 };
 
 /**
- * Focus element and, if a textual input field and no previous selection
- * state exists, move the caret to the end of the input field.
- *
- * @param {Element} element
- *     Element to focus.
- */
-function focusElement(element) {
-  let t = element.type;
-  if (t && (t == "text" || t == "textarea")) {
-    if (element.selectionEnd == 0) {
-      let len = element.value.length;
-      element.setSelectionRange(len, len);
-    }
-  }
-  element.focus();
-}
-
-/**
  * @param {string} keyString
  * @param {Element} element
- * @param {Object.<string, boolean>=} opts
  * @param {Window=} window
  */
-event.sendKeysToElement = function(
-    keyString, el, opts = {}, window = undefined) {
-
-  if (opts.ignoreVisibility || element.isVisible(el)) {
-    focusElement(el);
+event.sendKeysToElement = function(keyString, el, window = undefined) {
+  // make Object.<modifier, false> map
+  let modifiers = Object.create(event.Modifiers);
+  for (let modifier in event.Modifiers) {
+    modifiers[modifier] = false;
+  }
 
-    // make Object.<modifier, false> map
-    let modifiers = Object.create(event.Modifiers);
-    for (let modifier in event.Modifiers) {
-      modifiers[modifier] = false;
-    }
-
-    for (let i = 0; i < keyString.length; i++) {
-      let c = keyString.charAt(i);
-      event.sendSingleKey(c, modifiers, window);
-    }
-
-  } else {
-    throw new ElementNotInteractableError("Element is not visible");
+  for (let i = 0; i < keyString.length; i++) {
+    let c = keyString.charAt(i);
+    event.sendSingleKey(c, modifiers, window);
   }
 };
 
 event.sendEvent = function(eventType, el, modifiers = {}, opts = {}) {
   opts.canBubble = opts.canBubble || true;
 
   let doc = el.ownerDocument || el.document;
   let ev = doc.createEvent("Event");
--- a/testing/marionette/interaction.js
+++ b/testing/marionette/interaction.js
@@ -1,16 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {utils: Cu} = Components;
 
+Cu.import("resource://gre/modules/Preferences.jsm");
+
 Cu.import("chrome://marionette/content/accessibility.js");
 Cu.import("chrome://marionette/content/atom.js");
 const {
   ElementClickInterceptedError,
   ElementNotInteractableError,
   InvalidArgumentError,
   InvalidElementStateError,
 } = Cu.import("chrome://marionette/content/error.js", {});
@@ -316,16 +318,34 @@ interaction.flushEventLoop = async funct
     }
 
     win.addEventListener("beforeunload", handleEvent);
     win.requestAnimationFrame(handleEvent);
   });
 };
 
 /**
+ * Focus element and, if a textual input field and no previous selection
+ * state exists, move the caret to the end of the input field.
+ *
+ * @param {Element} element
+ *     Element to focus.
+ */
+interaction.focusElement = function(el) {
+  let t = el.type;
+  if (t && (t == "text" || t == "textarea")) {
+    if (el.selectionEnd == 0) {
+      let len = el.value.length;
+      el.setSelectionRange(len, len);
+    }
+  }
+  el.focus();
+};
+
+/**
  * Appends <var>path</var> to an <tt>&lt;input type=file&gt;</tt>'s
  * file list.
  *
  * @param {HTMLInputElement} el
  *     An <tt>&lt;input type=file&gt;</tt> element.
  * @param {string} path
  *     Full path to file.
  *
@@ -386,28 +406,45 @@ interaction.setFormControlValue = functi
 
 /**
  * Send keys to element.
  *
  * @param {DOMElement|XULElement} el
  *     Element to send key events to.
  * @param {Array.<string>} value
  *     Sequence of keystrokes to send to the element.
- * @param {boolean} ignoreVisibility
- *     Flag to enable or disable element visibility tests.
  * @param {boolean=} [strict=false] strict
  *     Enforce strict accessibility tests.
  */
 interaction.sendKeysToElement = async function(
-    el, value, ignoreVisibility, strict = false) {
-  let win = getWindow(el);
-  let a11y = accessibility.get(strict);
-  let acc = await a11y.getAccessible(el, true);
-  a11y.assertActionable(acc, el);
-  event.sendKeysToElement(value, el, {ignoreVisibility: false}, win);
+    el, value, strict = false) {
+  const a11y = accessibility.get(strict);
+  const win = getWindow(el);
+
+  if (el.type == "file") {
+    await interaction.uploadFile(el, value);
+  } else if ((el.type == "date" || el.type == "time") &&
+      Preferences.get("dom.forms.datetime")) {
+    interaction.setFormControlValue(el, value);
+  } else {
+    let visibilityCheckEl  = el;
+    if (el.localName == "option") {
+      visibilityCheckEl = element.getContainer(el);
+    }
+
+    if (!element.isVisible(visibilityCheckEl)) {
+      throw new ElementNotInteractableError("Element is not visible");
+    }
+
+    let acc = await a11y.getAccessible(el, true);
+    a11y.assertActionable(acc, el);
+
+    interaction.focusElement(el);
+    event.sendKeysToElement(value, el, win);
+  }
 };
 
 /**
  * Determine the element displayedness of an element.
  *
  * @param {DOMElement|XULElement} el
  *     Element to determine displayedness of.
  * @param {boolean=} [strict=false] strict
--- a/testing/marionette/listener.js
+++ b/testing/marionette/listener.js
@@ -9,17 +9,16 @@
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 const winUtil = content.QueryInterface(Ci.nsIInterfaceRequestor)
     .getInterface(Ci.nsIDOMWindowUtils);
 
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/Log.jsm");
-Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 Cu.import("chrome://marionette/content/accessibility.js");
 Cu.import("chrome://marionette/content/action.js");
 Cu.import("chrome://marionette/content/atom.js");
 Cu.import("chrome://marionette/content/capture.js");
 const {
   element,
@@ -1401,25 +1400,20 @@ function isElementEnabled(el) {
  * and Radio Button states, or option elements.
  */
 function isElementSelected(el) {
   return interaction.isElementSelected(
       el, capabilities.get("moz:accessibilityChecks"));
 }
 
 async function sendKeysToElement(el, val) {
-  if (el.type == "file") {
-    await interaction.uploadFile(el, val);
-  } else if ((el.type == "date" || el.type == "time") &&
-      Preferences.get("dom.forms.datetime")) {
-    interaction.setFormControlValue(el, val);
-  } else {
-    await interaction.sendKeysToElement(
-        el, val, false, capabilities.get("moz:accessibilityChecks"));
-  }
+  await interaction.sendKeysToElement(
+      el, val,
+      capabilities.get("moz:accessibilityChecks"),
+  );
 }
 
 /** Clear the text of an element. */
 function clearElement(el) {
   try {
     if (el.type == "file") {
       el.value = null;
     } else {