Bug 912900 - Show open tabs in connected device's Firefox in AppManager. r=jryans
authorRatnadeep Debnath <rtnpro@gmail.com>
Sat, 15 Mar 2014 17:17:45 -0400
changeset 192021 5d7cb798cd156ce2b5221ddbb953700fd199441d
parent 192020 fb515da0c7222fa677f9ada37dd1e76ef54c68ad
child 192022 2a5546bbe6311840cdbf9f1e75f4a3da7214372f
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/devtools/app-manager/test/test_device_store.html
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/devtools/app-manager/test/test_device_store.html
+++ b/browser/devtools/app-manager/test/test_device_store.html
@@ -62,23 +62,23 @@ Bug 901520 - [app manager] data store fo
                 }
                 connection.disconnect();
               }).then(null, (error) => ok(false, "Error:" + error));
             });
           });
         });
 
         connection.once("disconnected", function() {
-          compare(store.object, {description:{},permissions:[]}, "empty store after disconnect")
+          compare(store.object, {description:{},permissions:[],tabs:[]}, "empty store after disconnect")
           connection.destroy();
           DebuggerServer.destroy();
           SimpleTest.finish();
         });
 
-        compare(store.object, {description:{},permissions:[]}, "empty store before disconnect")
+        compare(store.object, {description:{},permissions:[],tabs:[]}, "empty store before disconnect")
 
         connection.connect();
 
       }
 
     </script>
   </body>
 </html>
--- 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 {