Bug 828008 - Add an UI to emulate user agent from responsive mode. r=jryans
authorTim Nguyen <ntim.bugs@gmail.com>
Tue, 01 Mar 2016 18:48:05 +0100
changeset 322647 750799a0d1a9e47bfdc7653e7f5b8e87816ae196
parent 322646 978a7d2a4475f3368367c48c83557d4c41a23695
child 322648 664023b706928c2d9a0e778a0865bdf00abffc25
push id5913
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 16:57:49 +0000
treeherdermozilla-beta@dcaf0a6fa115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjryans
bugs828008
milestone47.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 828008 - Add an UI to emulate user agent from responsive mode. r=jryans * * * [mq]: foo MozReview-Commit-ID: I6Z4VclffGM * * * Wait for reloads MozReview-Commit-ID: FEmN7dgIkjq
devtools/client/locales/en-US/responsiveUI.properties
devtools/client/responsivedesign/responsivedesign.jsm
devtools/client/themes/responsivedesign.inc.css
--- a/devtools/client/locales/en-US/responsiveUI.properties
+++ b/devtools/client/locales/en-US/responsiveUI.properties
@@ -13,16 +13,19 @@
 
 
 # LOCALIZATION NOTE  (responsiveUI.rotate2): tooltip of the rotate button.
 responsiveUI.rotate2=Rotate
 
 # LOCALIZATION NOTE  (responsiveUI.screenshot): tooltip of the screenshot button.
 responsiveUI.screenshot=Screenshot
 
+# LOCALIZATION NOTE  (responsiveUI.userAgentPlaceholder): placeholder for the user agent input.
+responsiveUI.userAgentPlaceholder=Custom User Agent
+
 # LOCALIZATION NOTE (responsiveUI.screenshotGeneratedFilename): The auto generated filename.
 # The first argument (%1$S) is the date string in yyyy-mm-dd format and the second
 # argument (%2$S) is the time string in HH.MM.SS format.
 responsiveUI.screenshotGeneratedFilename=Screen Shot %1$S at %2$S
 
 # LOCALIZATION NOTE  (responsiveUI.touch): tooltip of the touch button.
 responsiveUI.touch=Simulate touch events (page reload might be needed)
 
--- a/devtools/client/responsivedesign/responsivedesign.jsm
+++ b/devtools/client/responsivedesign/responsivedesign.jsm
@@ -2,28 +2,32 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* 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/. */
 
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
-var { loader, require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+var {loader, require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
 var Telemetry = require("devtools/client/shared/telemetry");
-var { showDoorhanger } = require("devtools/client/shared/doorhanger");
-var { TouchEventSimulator } = require("devtools/shared/touch/simulator");
-var { Task } = require("resource://gre/modules/Task.jsm");
+var {showDoorhanger} = require("devtools/client/shared/doorhanger");
+var {TouchEventSimulator} = require("devtools/shared/touch/simulator");
+var {Task} = require("resource://gre/modules/Task.jsm");
 var promise = require("promise");
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
 var Services = require("Services");
 var EventEmitter = require("devtools/shared/event-emitter");
-var { ViewHelpers } = require("devtools/client/shared/widgets/ViewHelpers.jsm");
+var {ViewHelpers} = require("devtools/client/shared/widgets/ViewHelpers.jsm");
 loader.lazyImporter(this, "SystemAppProxy",
                     "resource://gre/modules/SystemAppProxy.jsm");
+loader.lazyRequireGetter(this, "DebuggerClient",
+                         "devtools/shared/client/main", true);
+loader.lazyRequireGetter(this, "DebuggerServer",
+                         "devtools/server/main", true);
 
 this.EXPORTED_SYMBOLS = ["ResponsiveUIManager"];
 
 const MIN_WIDTH = 50;
 const MIN_HEIGHT = 50;
 
 const MAX_WIDTH = 10000;
 const MAX_HEIGHT = 10000;
@@ -170,16 +174,17 @@ function ResponsiveUI(aWindow, aTab)
   this.bound_removePreset = this.removePreset.bind(this);
   this.bound_rotate = this.rotate.bind(this);
   this.bound_screenshot = () => this.screenshot();
   this.bound_touch = this.toggleTouch.bind(this);
   this.bound_close = this.close.bind(this);
   this.bound_startResizing = this.startResizing.bind(this);
   this.bound_stopResizing = this.stopResizing.bind(this);
   this.bound_onDrag = this.onDrag.bind(this);
+  this.bound_changeUA = this.changeUA.bind(this);
   this.bound_onContentResize = this.onContentResize.bind(this);
 
   this.mm.addMessageListener("ResponsiveMode:OnContentResize",
                              this.bound_onContentResize);
 
   ActiveTabs.set(this.tab, this);
 
   this.inited = this.init();
@@ -196,17 +201,16 @@ ResponsiveUI.prototype = {
       this.stack.removeAttribute("notransition");
     } else if (!aValue) {
       this.stack.setAttribute("notransition", "true");
     }
   },
 
   init: Task.async(function*() {
     debug("INIT BEGINS");
-
     let ready = this.waitForMessage("ResponsiveMode:ChildScriptReady");
     this.mm.loadFrameScript("resource://devtools/client/responsivedesign/responsivedesign-child.js", true);
     yield ready;
 
     let requiresFloatingScrollbars =
       !this.mainWindow.matchMedia("(-moz-overlay-scrollbars)").matches;
     let started = this.waitForMessage("ResponsiveMode:Start:Done");
     debug("SEND START");
@@ -236,29 +240,47 @@ ResponsiveUI.prototype = {
         this.rotate();
       }
     } catch(e) {}
 
     // Touch events support
     this.touchEnableBefore = false;
     this.touchEventSimulator = new TouchEventSimulator(this.browser);
 
+    yield this.connectToServer();
+    this.userAgentInput.hidden = false;
+
     // Hook to display promotional Developer Edition doorhanger.
     // Only displayed once.
     showDoorhanger({
       window: this.mainWindow,
       type: "deveditionpromo",
       anchor: this.chromeDoc.querySelector("#content")
     });
 
     // Notify that responsive mode is on.
     this._telemetry.toolOpened("responsive");
     ResponsiveUIManager.emit("on", { tab: this.tab });
   }),
 
+  connectToServer: Task.async(function*() {
+    if (!DebuggerServer.initialized) {
+      DebuggerServer.init();
+      DebuggerServer.addBrowserActors();
+    }
+    this.client = new DebuggerClient(DebuggerServer.connectPipe());
+    yield this.client.connect();
+    let {tab} = yield this.client.getTab();
+    let [response, tabClient] = yield this.client.attachTab(tab.actor);
+    this.tabClient = tabClient;
+    if (!tabClient) {
+      Cu.reportError("Responsive Mode: failed to attach tab");
+    }
+  }),
+
   loadPresets: function() {
     // Try to load presets from prefs
     let presets = defaultPresets;
     if (Services.prefs.prefHasUserValue("devtools.responsiveUI.presets")) {
       try {
         presets = JSON.parse(Services.prefs.getCharPref("devtools.responsiveUI.presets"));
       } catch(e) {
         // User pref is malformated.
@@ -354,17 +376,24 @@ ResponsiveUI.prototype = {
     // Unset the responsive mode.
     this.container.removeAttribute("responsivemode");
     this.stack.removeAttribute("responsivemode");
 
     ActiveTabs.delete(this.tab);
     if (this.touchEventSimulator) {
       this.touchEventSimulator.stop();
     }
+
+    yield new Promise((resolve, reject) => {
+      this.client.close(resolve);
+      this.client = this.tabClient = null;
+    });
+
     this._telemetry.toolClosed("responsive");
+
     let stopped = this.waitForMessage("ResponsiveMode:Stop:Done");
     this.tab.linkedBrowser.messageManager.sendAsyncMessage("ResponsiveMode:Stop");
     yield stopped;
 
     this.inited = null;
     ResponsiveUIManager.emit("off", { tab: this.tab });
   }),
 
@@ -505,16 +534,24 @@ ResponsiveUI.prototype = {
     this.touchbutton.setAttribute("tabindex", "0");
     this.touchbutton.setAttribute("tooltiptext", this.strings.GetStringFromName("responsiveUI.touch"));
     this.touchbutton.className = "devtools-responsiveui-toolbarbutton devtools-responsiveui-touch";
     this.touchbutton.addEventListener("command", this.bound_touch, true);
     this.toolbar.appendChild(this.touchbutton);
 
     this.toolbar.appendChild(this.screenshotbutton);
 
+    this.userAgentInput = this.chromeDoc.createElement("textbox");
+    this.userAgentInput.className = "devtools-responsiveui-textinput";
+    this.userAgentInput.setAttribute("placeholder",
+      this.strings.GetStringFromName("responsiveUI.userAgentPlaceholder"));
+    this.userAgentInput.addEventListener("blur", this.bound_changeUA, true);
+    this.userAgentInput.hidden = true;
+    this.toolbar.appendChild(this.userAgentInput);
+
     // Resizers
     let resizerTooltip = this.strings.GetStringFromName("responsiveUI.resizerTooltip");
     this.resizer = this.chromeDoc.createElement("box");
     this.resizer.className = "devtools-responsiveui-resizehandle";
     this.resizer.setAttribute("right", "0");
     this.resizer.setAttribute("bottom", "0");
     this.resizer.setAttribute("tooltiptext", resizerTooltip);
     this.resizer.onmousedown = this.bound_startResizing;
@@ -900,16 +937,49 @@ ResponsiveUI.prototype = {
            "responsive-ui-need-reload",
            null,
            nbox.PRIORITY_INFO_LOW,
            buttons);
        }
      }
    }),
 
+  waitForReload() {
+    let navigatedDeferred = promise.defer();
+    let onNavigated = (_, { state }) => {
+      if (state != "stop") {
+        return;
+      }
+      this.client.removeListener("tabNavigated", onNavigated);
+      navigatedDeferred.resolve();
+    };
+    this.client.addListener("tabNavigated", onNavigated);
+    return navigatedDeferred.promise;
+  },
+
+  /**
+   * Change the user agent string
+   */
+  changeUA: Task.async(function*() {
+    let value = this.userAgentInput.value;
+    if (value) {
+      this.userAgentInput.setAttribute("attention", "true");
+    } else {
+      this.userAgentInput.removeAttribute("attention");
+    }
+
+    // Changing the UA triggers an automatic reload.  Ensure we wait for this to
+    // complete before emitting the changed event, so that tests wait for the
+    // reload.
+    let reloaded = this.waitForReload();
+    yield this.tabClient.reconfigure({customUserAgent: value});
+    yield reloaded;
+    ResponsiveUIManager.emit("userAgentChanged", { tab: this.tab });
+  }),
+
   /**
    * Get the current width and height.
    */
   getSize() {
     let width = Number(this.stack.style.minWidth.replace("px", ""));
     let height = Number(this.stack.style.minHeight.replace("px", ""));
     return {
       width,
--- a/devtools/client/themes/responsivedesign.inc.css
+++ b/devtools/client/themes/responsivedesign.inc.css
@@ -23,16 +23,32 @@
    */
   color: hsl(210,30%,85%);
   margin: 10px 0;
   padding: 0;
   box-shadow: none;
   border-bottom-width: 0;
 }
 
+.devtools-responsiveui-textinput {
+  -moz-appearance: none;
+  background: #333;
+  color: #fff;
+  border: 1px solid #111;
+  border-radius: 2px;
+  padding: 5px;
+  width: 200px;
+  margin: 0;
+}
+
+.devtools-responsiveui-textinput[attention] {
+  border-color: #38ace6;
+  background: rgba(56,172,230,0.4);
+}
+
 .devtools-responsiveui-menulist,
 .devtools-responsiveui-toolbarbutton {
   -moz-appearance: none;
   -moz-box-align: center;
   min-width: 32px;
   min-height: 22px;
   text-shadow: 0 -1px 0 hsla(210,8%,5%,.45);
   border: 1px solid hsla(210,8%,5%,.45);