Bug 1421663 - Allow changing of custom viewport size in RDM with arrow keys. r=jryans draft
authorabhinav <abhinav.koppula@gmail.com>
Wed, 06 Dec 2017 00:31:57 +0530
changeset 709445 4447d5d9d06a16390ed4f61398f1012a031301b3
parent 708706 4b94da21a9e6171f9911ffad171af23c26e6227b
child 743419 b2458741307b29a883912db48ee4fa930042026d
push id92642
push userbmo:abhinav.koppula@gmail.com
push dateFri, 08 Dec 2017 02:50:36 +0000
reviewersjryans
bugs1421663
milestone59.0a1
Bug 1421663 - Allow changing of custom viewport size in RDM with arrow keys. r=jryans MozReview-Commit-ID: AQwqkt9EPn3
devtools/client/responsive.html/components/ViewportDimension.js
devtools/client/responsive.html/test/browser/browser_device_width.js
devtools/client/responsive.html/utils/key.js
devtools/client/responsive.html/utils/moz.build
--- a/devtools/client/responsive.html/components/ViewportDimension.js
+++ b/devtools/client/responsive.html/components/ViewportDimension.js
@@ -2,20 +2,49 @@
  * 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 { Component } = require("devtools/client/shared/vendor/react");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
+const { isKeyIn } = require("../utils/key");
 
 const Constants = require("../constants");
 const Types = require("../types");
 
+/**
+ * Get the increment/decrement step to use for the provided key event.
+ */
+function getIncrement(event) {
+  const defaultIncrement = 1;
+  const largeIncrement = 100;
+  const mediumIncrement = 10;
+
+  let increment = 0;
+  let key = event.keyCode;
+
+  if (isKeyIn(key, "UP", "PAGE_UP")) {
+    increment = 1 * defaultIncrement;
+  } else if (isKeyIn(key, "DOWN", "PAGE_DOWN")) {
+    increment = -1 * defaultIncrement;
+  }
+
+  if (event.shiftKey) {
+    if (isKeyIn(key, "PAGE_UP", "PAGE_DOWN")) {
+      increment *= largeIncrement;
+    } else {
+      increment *= mediumIncrement;
+    }
+  }
+
+  return increment;
+}
+
 class ViewportDimension extends Component {
   static get propTypes() {
     return {
       viewport: PropTypes.shape(Types.viewport).isRequired,
       onChangeSize: PropTypes.func.isRequired,
       onRemoveDeviceAssociation: PropTypes.func.isRequired,
     };
   }
@@ -30,16 +59,17 @@ class ViewportDimension extends Componen
       isEditing: false,
       isInvalid: false,
     };
 
     this.validateInput = this.validateInput.bind(this);
     this.onInputBlur = this.onInputBlur.bind(this);
     this.onInputChange = this.onInputChange.bind(this);
     this.onInputFocus = this.onInputFocus.bind(this);
+    this.onInputKeyDown = this.onInputKeyDown.bind(this);
     this.onInputKeyUp = this.onInputKeyUp.bind(this);
     this.onInputSubmit = this.onInputSubmit.bind(this);
   }
 
   componentWillReceiveProps(nextProps) {
     let { width, height } = nextProps.viewport;
 
     this.setState({
@@ -70,38 +100,48 @@ class ViewportDimension extends Componen
     }
 
     this.setState({
       isEditing: false,
       inInvalid: false,
     });
   }
 
-  onInputChange({ target }) {
+  onInputChange({ target }, callback) {
     if (target.value.length > 4) {
       return;
     }
 
     if (this.refs.widthInput == target) {
-      this.setState({ width: target.value });
+      this.setState({ width: target.value }, callback);
       this.validateInput(target.value);
     }
 
     if (this.refs.heightInput == target) {
-      this.setState({ height: target.value });
+      this.setState({ height: target.value }, callback);
       this.validateInput(target.value);
     }
   }
 
   onInputFocus() {
     this.setState({
       isEditing: true,
     });
   }
 
+  onInputKeyDown(event) {
+    let { target } = event;
+    let increment = getIncrement(event);
+    if (!increment) {
+      return;
+    }
+    target.value = parseInt(target.value, 10) + increment;
+    this.onInputChange(event, this.onInputSubmit);
+  }
+
   onInputKeyUp({ target, keyCode }) {
     // On Enter, submit the input
     if (keyCode == 13) {
       this.onInputSubmit();
     }
 
     // On Esc, blur the target
     if (keyCode == 27) {
@@ -155,29 +195,31 @@ class ViewportDimension extends Componen
         dom.input({
           ref: "widthInput",
           className: inputClass,
           size: 4,
           value: this.state.width,
           onBlur: this.onInputBlur,
           onChange: this.onInputChange,
           onFocus: this.onInputFocus,
+          onKeyDown: this.onInputKeyDown,
           onKeyUp: this.onInputKeyUp,
         }),
         dom.span({
           className: "viewport-dimension-separator",
         }, "×"),
         dom.input({
           ref: "heightInput",
           className: inputClass,
           size: 4,
           value: this.state.height,
           onBlur: this.onInputBlur,
           onChange: this.onInputChange,
           onFocus: this.onInputFocus,
+          onKeyDown: this.onInputKeyDown,
           onKeyUp: this.onInputKeyUp,
         })
       )
     );
   }
 }
 
 module.exports = ViewportDimension;
--- a/devtools/client/responsive.html/test/browser/browser_device_width.js
+++ b/devtools/client/responsive.html/test/browser/browser_device_width.js
@@ -13,28 +13,68 @@ addRDMTask(TEST_URL, function* ({ ui, ma
   yield doInitialChecks(ui);
 
   info("Changing the RDM size");
   yield setViewportSize(ui, manager, 90, 500);
 
   info("Checking for screen props");
   yield checkScreenProps(ui);
 
+  info("Changing the RDM size using input keys");
+  yield setViewportSizeWithInputKeys(ui);
+
   info("Setting docShell.deviceSizeIsPageSize to false");
   yield ContentTask.spawn(ui.getViewportBrowser(), {}, function* () {
     let docShell = content.QueryInterface(Ci.nsIInterfaceRequestor)
                           .getInterface(Ci.nsIWebNavigation)
                           .QueryInterface(Ci.nsIDocShell);
     docShell.deviceSizeIsPageSize = false;
   });
 
   info("Checking for screen props once again.");
   yield checkScreenProps2(ui);
 });
 
+function* setViewportSizeWithInputKeys(ui) {
+  let width = 320, height = 500;
+  let resized = waitForViewportResizeTo(ui, width, height);
+  ui.setViewportSize({ width, height });
+  yield resized;
+
+  let dimensions = ui.toolWindow.document.querySelectorAll(".viewport-dimension-input");
+
+  // Increase width value to 420 by using the Up arrow key
+  resized = waitForViewportResizeTo(ui, 420, height);
+  dimensions[0].focus();
+  for (let i = 1; i <= 100; i++) {
+    EventUtils.synthesizeKey("KEY_ArrowUp", { code: "ArrowUp" });
+  }
+  yield resized;
+
+  // Resetting width value back to 320 using `Shift + Down` arrow
+  resized = waitForViewportResizeTo(ui, width, height);
+  dimensions[0].focus();
+  for (let i = 1; i <= 10; i++) {
+    EventUtils.synthesizeKey("KEY_ArrowDown", { shiftKey: true, code: "ArrowDown" });
+  }
+  yield resized;
+
+  // Increase height value to 600 by using `PageUp + Shift` key
+  resized = waitForViewportResizeTo(ui, width, 600);
+  dimensions[1].focus();
+  EventUtils.synthesizeKey("VK_PAGE_UP", { shiftKey: true });
+  yield resized;
+
+  // Resetting height value back to 500 by using `PageDown + Shift` key
+  resized = waitForViewportResizeTo(ui, width, height);
+  dimensions[1].focus();
+  EventUtils.synthesizeKey("VK_PAGE_DOWN", { shiftKey: true });
+  yield resized;
+}
+
 function* doInitialChecks(ui) {
   let { innerWidth, matchesMedia } = yield grabContentInfo(ui);
   is(innerWidth, 110, "initial width should be 110px");
   ok(!matchesMedia, "media query shouldn't match.");
 }
 
 function* checkScreenProps(ui) {
   let { matchesMedia, screen } = yield grabContentInfo(ui);
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/utils/key.js
@@ -0,0 +1,25 @@
+/* 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 { KeyCodes } = require("devtools/client/shared/keycodes");
+
+/**
+ * Helper to check if the provided key matches one of the expected keys.
+ * Keys will be prefixed with DOM_VK_ and should match a key in KeyCodes.
+ *
+ * @param {String} key
+ *        the key to check (can be a keyCode).
+ * @param {...String} keys
+ *        list of possible keys allowed.
+ * @return {Boolean} true if the key matches one of the keys.
+ */
+function isKeyIn(key, ...keys) {
+  return keys.some(expectedKey => {
+    return key === KeyCodes["DOM_VK_" + expectedKey];
+  });
+}
+
+exports.isKeyIn = isKeyIn;
--- a/devtools/client/responsive.html/utils/moz.build
+++ b/devtools/client/responsive.html/utils/moz.build
@@ -2,12 +2,13 @@
 # vim: set filetype=python:
 # 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/.
 
 DevToolsModules(
     'css.js',
     'e10s.js',
+    'key.js',
     'l10n.js',
     'message.js',
     'window.js',
 )