Bug 1239459 - Toggle touch event simulation r=jryans
☠☠ backed out by c8241f3e1f53 ☠ ☠
authorGabriel Luong <gabriel.luong@gmail.com>
Tue, 10 May 2016 17:09:55 -0700
changeset 365549 027b0144a1a6c2d62dfb1656e41597acfe6a22ef
parent 365548 d8df7fcffeb14796b272feb5cb24b1c15dcf67cb
child 365550 68fddeb3a7dbc1adc7b8b8841523ec48961add04
push id17789
push userbmo:ahunt@mozilla.com
push dateWed, 11 May 2016 03:29:35 +0000
reviewersjryans
bugs1239459
milestone49.0a1
Bug 1239459 - Toggle touch event simulation r=jryans
devtools/client/responsive.html/actions/index.js
devtools/client/responsive.html/actions/moz.build
devtools/client/responsive.html/actions/touch-simulation.js
devtools/client/responsive.html/app.js
devtools/client/responsive.html/components/global-toolbar.js
devtools/client/responsive.html/images/moz.build
devtools/client/responsive.html/images/touch-events.svg
devtools/client/responsive.html/index.css
devtools/client/responsive.html/manager.js
devtools/client/responsive.html/reducers.js
devtools/client/responsive.html/reducers/moz.build
devtools/client/responsive.html/reducers/touch-simulation.js
devtools/client/responsive.html/test/browser/browser.ini
devtools/client/responsive.html/test/browser/browser_touch_simulation.js
devtools/client/responsive.html/test/unit/test_update_touch_simulation_enabled.js
devtools/client/responsive.html/test/unit/xpcshell.ini
devtools/client/responsive.html/types.js
devtools/shared/touch/simulator.js
--- a/devtools/client/responsive.html/actions/index.js
+++ b/devtools/client/responsive.html/actions/index.js
@@ -39,16 +39,19 @@ createEnum([
   "TAKE_SCREENSHOT_END",
 
   // Update the device display state in the device selector.
   "UPDATE_DEVICE_DISPLAYED",
 
   // Update the device modal open state.
   "UPDATE_DEVICE_MODAL_OPEN",
 
+  // Update the touch simulation enabled state.
+  "UPDATE_TOUCH_SIMULATION_ENABLED",
+
 ], module.exports);
 
 /**
  * Create a simple enum-like object with keys mirrored to values from an array.
  * This makes comparison to a specfic value simpler without having to repeat and
  * mis-type the value.
  */
 function createEnum(array, target) {
--- a/devtools/client/responsive.html/actions/moz.build
+++ b/devtools/client/responsive.html/actions/moz.build
@@ -4,10 +4,11 @@
 # 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(
     'devices.js',
     'index.js',
     'location.js',
     'screenshot.js',
+    'touch-simulation.js',
     'viewports.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/actions/touch-simulation.js
@@ -0,0 +1,22 @@
+/* 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/. */
+
+/* eslint-env browser */
+
+"use strict";
+
+const {
+  UPDATE_TOUCH_SIMULATION_ENABLED
+} = require("./index");
+
+module.exports = {
+
+  updateTouchSimulationEnabled(enabled) {
+    return {
+      type: UPDATE_TOUCH_SIMULATION_ENABLED,
+      enabled,
+    };
+  },
+
+};
--- a/devtools/client/responsive.html/app.js
+++ b/devtools/client/responsive.html/app.js
@@ -15,28 +15,30 @@ const {
   updateDeviceModalOpen,
 } = require("./actions/devices");
 const {
   changeDevice,
   resizeViewport,
   rotateViewport
 } = require("./actions/viewports");
 const { takeScreenshot } = require("./actions/screenshot");
+const { updateTouchSimulationEnabled } = require("./actions/touch-simulation");
 const DeviceModal = createFactory(require("./components/device-modal"));
 const GlobalToolbar = createFactory(require("./components/global-toolbar"));
 const Viewports = createFactory(require("./components/viewports"));
 const { updateDeviceList } = require("./devices");
 const Types = require("./types");
 
 let App = createClass({
   propTypes: {
     devices: PropTypes.shape(Types.devices).isRequired,
     location: Types.location.isRequired,
+    screenshot: PropTypes.shape(Types.screenshot).isRequired,
+    touchSimulation: PropTypes.shape(Types.touchSimulation).isRequired,
     viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired,
-    screenshot: PropTypes.shape(Types.screenshot).isRequired,
   },
 
   displayName: "App",
 
   onBrowserMounted() {
     window.postMessage({ type: "browser-mounted" }, "*");
   },
 
@@ -75,45 +77,60 @@ let App = createClass({
   onUpdateDeviceDisplayed(device, deviceType, displayed) {
     this.props.dispatch(updateDeviceDisplayed(device, deviceType, displayed));
   },
 
   onUpdateDeviceModalOpen(isOpen) {
     this.props.dispatch(updateDeviceModalOpen(isOpen));
   },
 
+  onUpdateTouchSimulationEnabled() {
+    let { enabled } = this.props.touchSimulation;
+
+    window.postMessage({
+      type: "update-touch-simulation",
+      enabled,
+    }, "*");
+
+    this.props.dispatch(updateTouchSimulationEnabled(!enabled));
+  },
+
   render() {
     let {
       devices,
       location,
       screenshot,
+      touchSimulation,
       viewports,
     } = this.props;
 
     let {
       onBrowserMounted,
       onChangeViewportDevice,
       onContentResize,
       onDeviceListUpdate,
       onExit,
       onResizeViewport,
       onRotateViewport,
       onScreenshot,
       onUpdateDeviceDisplayed,
       onUpdateDeviceModalOpen,
+      onUpdateTouchSimulationEnabled,
     } = this;
 
     return dom.div(
       {
         id: "app",
       },
       GlobalToolbar({
         screenshot,
+        touchSimulation,
         onExit,
         onScreenshot,
+        onUpdateTouchSimulationEnabled,
       }),
       Viewports({
         devices,
         location,
         screenshot,
         viewports,
         onBrowserMounted,
         onChangeViewportDevice,
--- a/devtools/client/responsive.html/components/global-toolbar.js
+++ b/devtools/client/responsive.html/components/global-toolbar.js
@@ -6,43 +6,57 @@
 
 const { getStr } = require("../utils/l10n");
 const { DOM: dom, createClass, PropTypes, addons } =
   require("devtools/client/shared/vendor/react");
 const Types = require("../types");
 
 module.exports = createClass({
   propTypes: {
+    screenshot: PropTypes.shape(Types.screenshot).isRequired,
+    touchSimulation: PropTypes.shape(Types.touchSimulation).isRequired,
     onExit: PropTypes.func.isRequired,
     onScreenshot: PropTypes.func.isRequired,
-    screenshot: PropTypes.shape(Types.screenshot).isRequired,
+    onUpdateTouchSimulationEnabled: PropTypes.func.isRequired,
   },
 
   displayName: "GlobalToolbar",
 
   mixins: [ addons.PureRenderMixin ],
 
   render() {
     let {
+      screenshot,
+      touchSimulation,
       onExit,
       onScreenshot,
-      screenshot,
+      onUpdateTouchSimulationEnabled
     } = this.props;
 
+    let touchButtonClass = "toolbar-button devtools-button";
+    if (touchSimulation.enabled) {
+      touchButtonClass += " active";
+    }
+
     return dom.header(
       {
         id: "global-toolbar",
         className: "container",
       },
       dom.span(
         {
           className: "title",
         },
         getStr("responsive.title")),
       dom.button({
+        id: "global-touch-simulation-button",
+        className: touchButtonClass,
+        onClick: onUpdateTouchSimulationEnabled,
+      }),
+      dom.button({
         id: "global-screenshot-button",
         className: "toolbar-button devtools-button",
         title: getStr("responsive.screenshot"),
         onClick: onScreenshot,
         disabled: screenshot.isCapturing,
       }),
       dom.button({
         id: "global-exit-button",
--- a/devtools/client/responsive.html/images/moz.build
+++ b/devtools/client/responsive.html/images/moz.build
@@ -5,9 +5,10 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
     'close.svg',
     'grippers.svg',
     'rotate-viewport.svg',
     'screenshot.svg',
     'select-arrow.svg',
+    'touch-events.svg',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/images/touch-events.svg
@@ -0,0 +1,9 @@
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+  <style type="text/css">
+    .st0{fill:#F5F5F5;}
+  </style>
+  <path class="st0" d="M12.5 5.3c-.2 0-.4 0-.6.1-.2-.6-.8-1-1.4-1-.3 0-.5.1-.8.2C9.4 4.2 9 4 8.6 4h-.4V1.5C8.2.7 7.5 0 6.7 0S5.2.7 5.2 1.5v6.6l-.7-.6c-.6-.6-1.6-.6-2.2 0-.5.6-.5 1.4-.1 2.1.3.4.6 1.1 1 1.8C4.2 13.6 5.3 16 7 16h3.9s3.1-1 3.1-4V6.7c.1-.8-.7-1.4-1.5-1.4zm.6 6.7c0 2-2.1 3-2.4 3H7c-1 0-2.1-2.4-2.9-4-.3-.8-.7-1.6-1-2-.2-.3-.2-.5-.1-.7.1-.1.2-.1.3-.1.1 0 .2 0 .3.1l1.5 1.5c.3.2.6.2.7.1.1 0 .4-.2.4-.5V1.5c0-.2.2-.4.5-.4s.5.2.5.4v5.3c0 .3.2.5.5.5s.5-.2.5-.5V5.5c0-.4.2-.5.5-.5.2 0 .5.2.5.4v2c-.1.3.2.6.4.6.3 0 .5-.2.5-.5V5.8c0-.2.2-.4.5-.4s.5.2.5.4v2.3c0 .3.2.5.5.5s.5-.2.5-.5V6.7c0-.2.2-.4.5-.4s.5.2.5.4V12z"/>
+</svg>
--- a/devtools/client/responsive.html/index.css
+++ b/devtools/client/responsive.html/index.css
@@ -95,16 +95,25 @@ html, body {
 }
 
 #global-toolbar .toolbar-button,
 #global-toolbar .toolbar-button::before {
   width: 12px;
   height: 12px;
 }
 
+#global-touch-simulation-button::before {
+  background-image: url("./images/touch-events.svg");
+  margin: -6px 0 0 -6px;
+}
+
+#global-touch-simulation-button.active::before {
+  filter: url("chrome://devtools/skin/images/filters.svg#checked-icon-state");
+}
+
 #global-screenshot-button::before {
   background-image: url("./images/screenshot.svg");
   margin: -6px 0 0 -6px;
 }
 
 #global-exit-button::before {
   background-image: url("./images/close.svg");
   margin: -6px 0 0 -6px;
--- a/devtools/client/responsive.html/manager.js
+++ b/devtools/client/responsive.html/manager.js
@@ -4,16 +4,17 @@
 
 "use strict";
 
 const { Ci, Cr } = require("chrome");
 const promise = require("promise");
 const { Task } = require("resource://gre/modules/Task.jsm");
 const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
 const EventEmitter = require("devtools/shared/event-emitter");
+const { TouchEventSimulator } = require("devtools/shared/touch/simulator");
 const { getOwnerWindow } = require("sdk/tabs/utils");
 const { on, off } = require("sdk/event/core");
 const { startup } = require("sdk/window/helpers");
 const events = require("./events");
 
 const TOOL_URL = "chrome://devtools/content/responsive.html/index.xhtml";
 
 /**
@@ -193,16 +194,21 @@ ResponsiveUI.prototype = {
    * design tool.  It is safe to reference this window directly even with e10s,
    * as the tool UI is always loaded in the parent process.  The web content
    * contained *within* the tool UI on the other hand is loaded in the child
    * process.
    */
   toolWindow: null,
 
   /**
+   * Touch event simulator.
+   */
+  touchEventSimulator: null,
+
+  /**
    * For the moment, we open the tool by:
    * 1. Recording the tab's URL
    * 2. Navigating the tab to the tool
    * 3. Passing along the URL to the tool to open in the viewport
    *
    * This approach is simple, but it also discards the user's state on the page.
    * It's just like opening a fresh tab and pasting the URL.
    *
@@ -215,25 +221,33 @@ ResponsiveUI.prototype = {
     let contentURI = tabBrowser.documentURI.spec;
     tabBrowser.loadURI(TOOL_URL);
     yield tabLoaded(this.tab);
     let toolWindow = this.toolWindow = tabBrowser.contentWindow;
     toolWindow.addEventListener("message", this);
     yield waitForMessage(toolWindow, "init");
     toolWindow.addInitialViewport(contentURI);
     yield waitForMessage(toolWindow, "browser-mounted");
+
+    let browser = toolWindow.document.querySelector("iframe.browser");
+    this.touchEventSimulator = new TouchEventSimulator(browser);
   }),
 
   destroy: Task.async(function* () {
     let tabBrowser = this.tab.linkedBrowser;
     let browserWindow = this.browserWindow;
+
     this.browserWindow = null;
     this.tab = null;
     this.inited = null;
     this.toolWindow = null;
+
+    yield this.touchEventSimulator.stop();
+    this.touchEventSimulator = null;
+
     let loaded = waitForDocLoadComplete(browserWindow.gBrowser);
     tabBrowser.goBack();
     yield loaded;
   }),
 
   handleEvent(event) {
     let { tab, window } = this;
     let toolWindow = tab.linkedBrowser.contentWindow;
@@ -249,19 +263,31 @@ ResponsiveUI.prototype = {
           width,
           height,
         });
         break;
       case "exit":
         toolWindow.removeEventListener(event.type, this);
         ResponsiveUIManager.closeIfNeeded(window, tab);
         break;
+      case "update-touch-simulation":
+        let { enabled } = event.data;
+        this.updateTouchSimulation(enabled);
+        break;
     }
   },
 
+  updateTouchSimulation: Task.async(function* (enabled) {
+    if (enabled) {
+      this.touchEventSimulator.start();
+    } else {
+      this.touchEventSimulator.stop();
+    }
+  }),
+
   getViewportSize() {
     return this.toolWindow.getViewportSize();
   },
 
   setViewportSize: Task.async(function* (width, height) {
     yield this.inited;
     this.toolWindow.setViewportSize(width, height);
   }),
--- a/devtools/client/responsive.html/reducers.js
+++ b/devtools/client/responsive.html/reducers.js
@@ -2,9 +2,10 @@
  * 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";
 
 exports.devices = require("./reducers/devices");
 exports.location = require("./reducers/location");
 exports.screenshot = require("./reducers/screenshot");
+exports.touchSimulation = require("./reducers/touch-simulation");
 exports.viewports = require("./reducers/viewports");
--- a/devtools/client/responsive.html/reducers/moz.build
+++ b/devtools/client/responsive.html/reducers/moz.build
@@ -3,10 +3,11 @@
 # 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(
     'devices.js',
     'location.js',
     'screenshot.js',
+    'touch-simulation.js',
     'viewports.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/reducers/touch-simulation.js
@@ -0,0 +1,29 @@
+/* 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 {
+  UPDATE_TOUCH_SIMULATION_ENABLED,
+} = require("../actions/index");
+
+const INITIAL_TOUCH_SIMULATION = { enabled: false };
+
+let reducers = {
+
+  [UPDATE_TOUCH_SIMULATION_ENABLED](touchSimulation, { enabled }) {
+    return Object.assign({}, touchSimulation, {
+      enabled,
+    });
+  },
+
+};
+
+module.exports = function (touchSimulation = INITIAL_TOUCH_SIMULATION, action) {
+  let reducer = reducers[action.type];
+  if (!reducer) {
+    return touchSimulation;
+  }
+  return reducer(touchSimulation, action);
+};
--- a/devtools/client/responsive.html/test/browser/browser.ini
+++ b/devtools/client/responsive.html/test/browser/browser.ini
@@ -14,9 +14,10 @@ support-files =
 [browser_device_width.js]
 [browser_exit_button.js]
 [browser_menu_item_01.js]
 [browser_menu_item_02.js]
 skip-if = (e10s && debug) # Bug 1267278: browser.xul leaks
 [browser_mouse_resize.js]
 [browser_resize_cmd.js]
 [browser_screenshot_button.js]
+[browser_touch_simulation.js]
 [browser_viewport_basics.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/test/browser/browser_touch_simulation.js
@@ -0,0 +1,25 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test global touch simulation button
+
+const TEST_URL = "data:text/html;charset=utf-8,";
+
+addRDMTask(TEST_URL, function* ({ ui }) {
+  let { store, document } = ui.toolWindow;
+  let touchButton = document.querySelector("#global-touch-simulation-button");
+
+  // Wait until the viewport has been added
+  yield waitUntilState(store, state => state.viewports.length == 1);
+  yield waitForFrameLoad(ui, TEST_URL);
+
+  ok(!touchButton.classList.contains("active"),
+    "Touch simulation is not active by default.");
+
+  touchButton.click();
+
+  ok(touchButton.classList.contains("active"),
+    "Touch simulation is started on click.");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/test/unit/test_update_touch_simulation_enabled.js
@@ -0,0 +1,23 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test updating the touch simulation `enabled` property
+
+const {
+  updateTouchSimulationEnabled,
+} = require("devtools/client/responsive.html/actions/touch-simulation");
+
+add_task(function* () {
+  let store = Store();
+  const { getState, dispatch } = store;
+
+  ok(!getState().touchSimulation.enabled,
+    "Touch simulation is disabled by default.");
+
+  dispatch(updateTouchSimulationEnabled(true));
+
+  ok(getState().touchSimulation.enabled,
+    "Touch simulation is enabled.");
+});
--- a/devtools/client/responsive.html/test/unit/xpcshell.ini
+++ b/devtools/client/responsive.html/test/unit/xpcshell.ini
@@ -7,8 +7,9 @@ firefox-appdir = browser
 [test_add_device.js]
 [test_add_device_type.js]
 [test_add_viewport.js]
 [test_change_location.js]
 [test_change_viewport_device.js]
 [test_resize_viewport.js]
 [test_rotate_viewport.js]
 [test_update_device_displayed.js]
+[test_update_touch_simulation_enabled.js]
--- a/devtools/client/responsive.html/types.js
+++ b/devtools/client/responsive.html/types.js
@@ -81,16 +81,26 @@ exports.location = PropTypes.string;
  */
 exports.screenshot = {
 
   isCapturing: PropTypes.bool.isRequired,
 
 };
 
 /**
+ * Touch simulation.
+ */
+exports.touchSimulation = {
+
+  // Whether or not the touch simulation is enabled
+  enabled: PropTypes.bool.isRequired,
+
+};
+
+/**
  * A single viewport displaying a document.
  */
 exports.viewport = {
 
   // The id of the viewport
   id: PropTypes.number.isRequired,
 
   // The currently selected device applied to the viewport.
--- a/devtools/shared/touch/simulator.js
+++ b/devtools/shared/touch/simulator.js
@@ -1,14 +1,13 @@
 /* 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";
 
-var { Ci } = require("chrome");
 var promise = require("promise");
 var Services = require("Services");
 
 const FRAME_SCRIPT =
   "resource://devtools/shared/touch/simulator-content.js";
 
 var trackedBrowsers = new WeakMap();
 var savedTouchEventsEnabled =
@@ -20,22 +19,17 @@ var savedTouchEventsEnabled =
  */
 function TouchEventSimulator(browser) {
   // Returns an already instantiated simulator for this browser
   let simulator = trackedBrowsers.get(browser);
   if (simulator) {
     return simulator;
   }
 
-  let mm = browser.messageManager;
-  if (!mm) {
-    // Maybe browser is an iframe
-    mm = browser.QueryInterface(Ci.nsIFrameLoaderOwner)
-                .frameLoader.messageManager;
-  }
+  let mm = browser.frameLoader.messageManager;
   mm.loadFrameScript(FRAME_SCRIPT, true);
 
   simulator = {
     enabled: false,
 
     start() {
       if (this.enabled) {
         return promise.resolve({ isReloadNeeded: false });