Bug 912900 - Show open tabs in connected device's Firefox in AppManager. r=jryans
☠☠ backed out by 163c21c788e5 ☠ ☠
authorRatnadeep Debnath <rtnpro@gmail.com>
Tue, 11 Mar 2014 17:01:29 -0400
changeset 191348 4f50ba79c6fffdddc9add080f5d4c783d6cf5c12
parent 191347 b1b516d5c5e6c2d884e57107101b1e1f9b29193c
child 191349 7a6b4371379fdd624bb41d6d5fbda1257bafc699
push id474
push userasasaki@mozilla.com
push dateMon, 02 Jun 2014 21:01:02 +0000
treeherdermozilla-release@967f4cf1b31c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjryans
bugs912900
milestone30.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 912900 - Show open tabs in connected device's Firefox in AppManager. r=jryans
browser/devtools/app-manager/content/device.js
browser/devtools/app-manager/content/device.xhtml
browser/devtools/app-manager/device-store.js
browser/locales/en-US/chrome/browser/devtools/app-manager.dtd
browser/themes/shared/devtools/app-manager/device.css
--- a/browser/devtools/app-manager/content/device.js
+++ b/browser/devtools/app-manager/content/device.js
@@ -1,27 +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/. */
 
 const Cu = Components.utils;
 Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource:///modules/devtools/gDevTools.jsm");
+Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
+const {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
 
 const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 const {require} = devtools;
 
 const {ConnectionManager, Connection}
   = require("devtools/client/connection-manager");
 const {getDeviceFront} = require("devtools/server/actors/device");
 const {getTargetForApp, launchApp, closeApp}
   = require("devtools/app-actor-front");
 const DeviceStore = require("devtools/app-manager/device-store");
 const WebappsStore = require("devtools/app-manager/webapps-store");
 const promise = require("sdk/core/promise");
+const DEFAULT_APP_ICON = "chrome://browser/skin/devtools/app-manager/default-app-icon.png";
 
 window.addEventListener("message", function(event) {
   try {
     let message = JSON.parse(event.data);
     if (message.name == "connection") {
       let cid = parseInt(message.cid);
       for (let c of ConnectionManager.connections) {
         if (c.uid == cid) {
@@ -136,30 +138,55 @@ let UI = {
 
     var tab = document.querySelector(".tab." + name);
     var panel = document.querySelector(".tabpanel." + name);
 
     if (tab) tab.classList.add("selected");
     if (panel) panel.classList.add("selected");
   },
 
-  openToolbox: function(manifest) {
+  openToolboxForApp: function(manifest) {
     if (!this.connected) {
       return;
     }
 
     let app = this.store.object.apps.all.filter(a => a.manifestURL == manifest)[0];
     getTargetForApp(this.connection.client,
                     this.listTabsResponse.webappsActor,
                     manifest).then((target) => {
 
       top.UI.openAndShowToolboxForTarget(target, app.name, app.iconURL);
     }, console.error);
   },
 
+  _getTargetForTab: function (form) {
+      let options = {
+        form: form,
+        client: this.connection.client,
+        chrome: false
+      };
+      let deferred = promise.defer();
+      return devtools.TargetFactory.forRemoteTab(options);
+  },
+
+  openToolboxForTab: function (aNode) {
+    let index = Array.prototype.indexOf.apply(
+      aNode.parentNode.parentNode.parentNode.children,
+      [aNode.parentNode.parentNode]);
+    this.connection.client.listTabs(
+      response => {
+        let tab = response.tabs[index];
+        this._getTargetForTab(tab).then(target => {
+          top.UI.openAndShowToolboxForTarget(
+            target, tab.title, DEFAULT_APP_ICON);
+        }, console.error);
+      }
+    );
+  },
+
   startApp: function(manifest) {
     if (!this.connected) {
       return promise.reject();
     }
     return launchApp(this.connection.client,
                      this.listTabsResponse.webappsActor,
                      manifest);
   },
--- a/browser/devtools/app-manager/content/device.xhtml
+++ b/browser/devtools/app-manager/content/device.xhtml
@@ -39,16 +39,17 @@
           <div id="tabs-headers">
             <div onclick="UI.setTab('apps')" class="tab sidebar-item apps" title="&device.installedAppsTooltip;">&device.installedApps;</div>
             <div onclick="UI.setTab('permissions')" class="tab sidebar-item permissions" title="&device.permissionsTooltip;">
               &device.permissions;
               <a target="_blank" href="&device.permissionsHelpLink;">
                 <button class="help">&device.help;</button>
               </a>
             </div>
+            <div onclick="UI.setTab('browser-tabs')" class="tab sidebar-item browser-tabs" title="&device.browserTabsTooltip;">&device.browserTabs;</div>
           </div>
         </div>
       </aside>
       <section id="detail">
         <div id="tabs">
           <div class="tabpanel apps">
             <div class="app-list" template-loop='{"arrayPath":"apps.all","childSelector":"#app-template"}'></div>
           </div>
@@ -63,37 +64,52 @@
               <section template-loop='{"arrayPath":"device.permissions","childSelector":"#permission-template"}'></section>
             </div>
             <div class="permission-table-footer">
               <div class="allow-label" title="&device.allowTooltip;">&device.allow;</div>
               <div class="prompt-label" title="&device.promptTooltip;">&device.prompt;</div>
               <div class="deny-label" title="&device.denyTooltip;">&device.deny;</div>
             </div>
           </div>
+          <div class="tabpanel browser-tabs">
+            <section template-loop='{"arrayPath":"device.tabs","childSelector":"#browser-tab-template"}'></section>
+          </div>
         </div>
       </section>
     </section>
     <iframe id="connection-footer" hidden="true"></iframe>
   </body>
 
   <template id="permission-template">
   <div class="permission">
     <div template='{"type":"textContent","path":"name"}'></div>
     <div template='{"type":"attribute", "name":"permission", "path":"app"}'></div>
     <div template='{"type":"attribute", "name":"permission", "path":"privileged"}'></div>
     <div template='{"type":"attribute", "name":"permission", "path":"certified"}'></div>
   </div>
   </template>
 
+  <template id="browser-tab-template">
+  <div class="browser-tab">
+    <div class="browser-tab-details">
+      <p template='{"type":"textContent","path":"title"}'></p>
+      <p class="browser-tab-url-subheading" template='{"type":"textContent","path":"url"}'></p>
+    </div>
+    <div class="browser-tab-buttons">
+      <button class="button-debug" template='{"type":"attribute","path":"actor","name":"data-actor"}' onclick="UI.openToolboxForTab(this)" style="display: inline-block;" title="&device.debugBrowserTabTooltip;">&device.debugBrowserTab;</button>
+    </div>
+  </div>
+  </template>
+
   <template id="app-template">
   <div class="app" template='{"type":"attribute","path":"running","name":"running"}'>
     <img class="app-icon" template='{"type":"attribute","path":"iconURL","name":"src"}'></img>
     <span class="app-name" template='{"type":"textContent","path":"name"}'></span>
     <div class="app-buttons">
-      <button class="button-debug" template='{"type":"attribute","path":"manifestURL","name":"data-manifest"}' onclick="UI.openToolbox(this.dataset.manifest)" title="&device.debugAppTooltip;">&device.debugApp;</button>
+      <button class="button-debug" template='{"type":"attribute","path":"manifestURL","name":"data-manifest"}' onclick="UI.openToolboxForApp(this.dataset.manifest)" title="&device.debugAppTooltip;">&device.debugApp;</button>
       <button class="button-start" template='{"type":"attribute","path":"manifestURL","name":"data-manifest"}' onclick="UI.startApp(this.dataset.manifest)" title="&device.startAppTooltip;">&device.startApp;</button>
       <button class="button-stop" template='{"type":"attribute","path":"manifestURL","name":"data-manifest"}' onclick="UI.stopApp(this.dataset.manifest)" title="&device.stopAppTooltip;">&device.stopApp;</button>
     </div>
   </div>
   </template>
 
   <script type="application/javascript;version=1.8" src="utils.js"></script>
   <script type="application/javascript;version=1.8" src="template.js"></script>
--- a/browser/devtools/app-manager/device-store.js
+++ b/browser/devtools/app-manager/device-store.js
@@ -26,16 +26,17 @@ module.exports = DeviceStore = function(
   this._resetStore();
 
   this.destroy = this.destroy.bind(this);
   this._onStatusChanged = this._onStatusChanged.bind(this);
 
   this._connection = connection;
   this._connection.once(Connection.Events.DESTROYED, this.destroy);
   this._connection.on(Connection.Events.STATUS_CHANGED, this._onStatusChanged);
+  this._onTabListChanged = this._onTabListChanged.bind(this);
   this._onStatusChanged();
   return this;
 }
 
 DeviceStore.prototype = {
   destroy: function() {
     if (this._connection) {
       // While this.destroy is bound using .once() above, that event may not
@@ -46,29 +47,44 @@ DeviceStore.prototype = {
       _knownDeviceStores.delete(this._connection);
       this._connection = null;
     }
   },
 
   _resetStore: function() {
     this.object.description = {};
     this.object.permissions = [];
+    this.object.tabs = [];
   },
 
   _onStatusChanged: function() {
     if (this._connection.status == Connection.Status.CONNECTED) {
       this._listTabs();
     } else {
       this._resetStore();
     }
   },
 
+  _onTabListChanged: function() {
+    this._listTabs();
+  },
+
   _listTabs: function() {
     this._connection.client.listTabs((resp) => {
+      if (resp.error) {
+        this._connection.disconnect();
+        return;
+      }
       this._deviceFront = getDeviceFront(this._connection.client, resp);
+      // Save remote browser's tabs
+      this.object.tabs = resp.tabs;
+      // Add listener to update remote browser's tabs list in app-manager
+      // when it changes
+      this._connection.client.addListener(
+        'tabListChanged', this._onTabListChanged);
       this._feedStore();
     });
   },
 
   _feedStore: function() {
     this._getDeviceDescription();
     this._getDevicePermissionsTable();
   },
@@ -91,10 +107,10 @@ DeviceStore.prototype = {
           name: name,
           app: permissionsTable[name].app,
           privileged: permissionsTable[name].privileged,
           certified: permissionsTable[name].certified,
         });
       }
       this.object.permissions = permissionsArray;
     });
-  },
+  }
 }
--- a/browser/locales/en-US/chrome/browser/devtools/app-manager.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/app-manager.dtd
@@ -27,16 +27,20 @@
 <!ENTITY device.promptTooltip "This permission requires a user prompt for apps of this type">
 <!ENTITY device.deny "Deny">
 <!ENTITY device.denyTooltip "This permission is denied for apps of this type">
 <!ENTITY device.installedApps "Installed Apps">
 <!ENTITY device.installedAppsTooltip "View a list of apps installed on the device. Some apps, such as certified apps, may be excluded from this view.">
 <!ENTITY device.permissions "Permissions">
 <!ENTITY device.permissionsTooltip "View a table of the permissions accessible to the different types of apps">
 <!ENTITY device.permissionsHelpLink "https://developer.mozilla.org/docs/Web/Apps/App_permissions">
+<!ENTITY device.browserTabs "Browser Tabs">
+<!ENTITY device.browserTabsTooltip "View a list of tabs in the browser of the connected device">
+<!ENTITY device.debugBrowserTab "Debug">
+<!ENTITY device.debugBrowserTabTooltip "Open the Developer Tools connected to this browser tab on the device">
 <!ENTITY device.help "Help">
 
 <!ENTITY connection.connectTooltip "Connect to the device">
 <!ENTITY connection.disconnect "Disconnect">
 <!ENTITY connection.disconnectTooltip "Disconnect from the current device or simulator">
 <!ENTITY connection.notConnected2 "Not Connected.">
 <!ENTITY connection.connectTo "Connect to:">
 <!ENTITY connection.noDeviceFound "No device found. Plug a device">
--- a/browser/themes/shared/devtools/app-manager/device.css
+++ b/browser/themes/shared/devtools/app-manager/device.css
@@ -303,53 +303,57 @@ header {
   background-image: linear-gradient(to bottom, rgba(0,0,0,0), rgba(0,0,0,0.7));
   color: #FFF;
   text-shadow: 0 1px 2px rgba(0,0,0,0.8);
   padding: 10px;
 }
 
 
 
-/*****************      APPS       *****************/
+/*****************      APPS & BROWSER TABS      *****************/
 
 
 
 
-.apps {
+.apps, .browser-tabs {
   display: flex;
   flex-direction: column;
   overflow: auto;
 }
 
-.app {
+.app, .browser-tab {
   display: flex;
   align-items: center;
   order: 1;
 }
 
-.app-name {
+.app-name, .browser-tab-details {
   flex-grow: 1;
   font-weight: bold;
 }
 
-.app {
+.app, .browser-tab {
   padding: 10px 20px;
   border-bottom: 1px solid #CCC;
 }
 
-.app:hover {
+.app:hover, .browser-tab:hover {
   background-color: #EFEFEF;
 }
 
 .app-icon {
   width: 32px;
   height: 32px;
   margin-right: 10px;
 }
 
+.browser-tab-url-subheading {
+  font-size: 10px;
+}
+
 
 
 /*****************      NOT CONNECTED      *****************/
 
 
 
 body:not(.notconnected) > #notConnectedMessage,
 body.notconnected > #content {