Bug 656329 - Use a Honeycomb-style action bar on tablets (preffed off by default) [r=mfinkle]
authorMatt Brubeck <mbrubeck@mozilla.com>
Fri, 05 Aug 2011 16:01:33 -0700
changeset 73930 3e764c25c144f1984f22b979afbad6f0cfebe215
parent 73929 66ccc3cf04bc29c2b6f7681ee6ac61c1d95fb56d
child 73931 56b60d34085734d343994cbec17ac5156ab3351d
push id1014
push usermbrubeck@mozilla.com
push dateFri, 05 Aug 2011 23:04:34 +0000
treeherdermozilla-inbound@3e764c25c144 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmfinkle
bugs656329
milestone8.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 656329 - Use a Honeycomb-style action bar on tablets (preffed off by default) [r=mfinkle]
mobile/app/mobile.js
mobile/chrome/content/BookmarkPopup.js
mobile/chrome/content/TabsPopup.js
mobile/chrome/content/browser-scripts.js
mobile/chrome/content/browser-ui.js
mobile/chrome/content/browser.js
mobile/chrome/content/browser.xul
mobile/chrome/content/tabs.xml
mobile/chrome/jar.mn
mobile/locales/en-US/chrome/browser.dtd
mobile/themes/core/browser.css
mobile/themes/core/gingerbread/browser.css
mobile/themes/core/images/tabs-hdpi.png
mobile/themes/core/jar.mn
--- a/mobile/app/mobile.js
+++ b/mobile/app/mobile.js
@@ -389,16 +389,18 @@ pref("javascript.options.mem.high_water_
 pref("javascript.options.gc_on_memory_pressure", false);
 
 pref("dom.max_chrome_script_run_time", 0); // disable slow script dialog for chrome
 pref("dom.max_script_run_time", 20);
 
 // JS error console
 pref("devtools.errorconsole.enabled", false);
 
+pref("browser.ui.layout.tablet", 0); // on: 1, off: 0, auto: -1
+
 // kinetic tweakables
 pref("browser.ui.kinetic.updateInterval", 16);
 pref("browser.ui.kinetic.exponentialC", 1400);
 pref("browser.ui.kinetic.polynomialC", 100);
 pref("browser.ui.kinetic.swipeLength", 160);
 
 // zooming
 pref("browser.ui.zoom.pageFitGranularity", 9); // don't zoom to fit by less than 1/9 (11%)
--- a/mobile/chrome/content/BookmarkPopup.js
+++ b/mobile/chrome/content/BookmarkPopup.js
@@ -1,37 +1,46 @@
 var BookmarkPopup = {
   get box() {
     delete this.box;
     this.box = document.getElementById("bookmark-popup");
 
-    let [tabsSidebar, controlsSidebar] = [Elements.tabs.getBoundingClientRect(), Elements.controls.getBoundingClientRect()];
-    this.box.setAttribute(tabsSidebar.left < controlsSidebar.left ? "right" : "left", controlsSidebar.width - this.box.offset);
-    this.box.top = BrowserUI.starButton.getBoundingClientRect().top - this.box.offset;
-
     // Hide the popup if there is any new page loading
     let self = this;
     messageManager.addMessageListener("pagehide", function(aMessage) {
       self.hide();
     });
-
     return this.box;
   },
 
   hide : function hide() {
     this.box.hidden = true;
     BrowserUI.popPopup(this);
   },
 
   show : function show() {
-    this.box.hidden = false;
-    this.box.anchorTo(BrowserUI.starButton);
+    // Set the box position.
+    let button = document.getElementById("tool-star");
+    if (getComputedStyle(button).visibility == "visible") {
+      let [tabsSidebar, controlsSidebar] = [Elements.tabs.getBoundingClientRect(), Elements.controls.getBoundingClientRect()];
+      this.box.setAttribute(tabsSidebar.left < controlsSidebar.left ? "right" : "left", controlsSidebar.width - this.box.offset);
+      this.box.top = button.getBoundingClientRect().top - this.box.offset;
+    } else {
+      button = document.getElementById("tool-star2");
+      this.box.top = button.getBoundingClientRect().bottom - this.box.offset;
 
-    // include starButton here, so that click-to-dismiss works as expected
-    BrowserUI.pushPopup(this, [this.box, BrowserUI.starButton]);
+      let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry);
+      this.box.setAttribute(chromeReg.isLocaleRTL("global") ? "left" : "right", this.box.offset);
+    }
+
+    this.box.hidden = false;
+    this.box.anchorTo(button);
+
+    // include the star button here, so that click-to-dismiss works as expected
+    BrowserUI.pushPopup(this, [this.box, button]);
   },
 
   toggle : function toggle() {
     if (this.box.hidden)
       this.show();
     else
       this.hide();
   }
new file mode 100644
--- /dev/null
+++ b/mobile/chrome/content/TabsPopup.js
@@ -0,0 +1,72 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Mobile Browser.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Matt Brubeck <mbrubeck@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+var TabsPopup = {
+  get box() {
+    delete this.box;
+    return this.box = document.getElementById("tabs-sidebar");
+  },
+
+  get list() {
+    delete this.list;
+    return this.list = document.getElementById("tabs");
+  },
+
+  get button() {
+    delete this.button;
+    return this.button = document.getElementById("tool-tabs");
+  },
+
+  hide: function hide() {
+    this.box.removeAttribute("open");
+    BrowserUI.popPopup(this);
+  },
+
+  show: function show() {
+    // Set the box position.
+    this.box.setAttribute("open", "true");
+    this.list.resize();
+    BrowserUI.pushPopup(this, [this.box, this.button]);
+  },
+
+  toggle: function toggle() {
+    if (this.box.hasAttribute("open"))
+      this.hide();
+    else
+      this.show();
+  }
+};
--- a/mobile/chrome/content/browser-scripts.js
+++ b/mobile/chrome/content/browser-scripts.js
@@ -103,16 +103,17 @@ XPCOMUtils.defineLazyGetter(this, "Commo
   ["MenuListHelperUI", "chrome://browser/content/MenuListHelperUI.js"],
   ["OfflineApps", "chrome://browser/content/OfflineApps.js"],
   ["IndexedDB", "chrome://browser/content/IndexedDB.js"],
   ["PreferencesView", "chrome://browser/content/preferences.js"],
   ["Sanitizer", "chrome://browser/content/sanitize.js"],
   ["SelectHelperUI", "chrome://browser/content/SelectHelperUI.js"],
   ["ContentPopupHelper", "chrome://browser/content/ContentPopupHelper.js"],
   ["SharingUI", "chrome://browser/content/SharingUI.js"],
+  ["TabsPopup", "chrome://browser/content/TabsPopup.js"],
 #ifdef MOZ_SERVICES_SYNC
   ["WeaveGlue", "chrome://browser/content/sync.js"],
 #endif
   ["SSLExceptions", "chrome://browser/content/exceptions.js"]
 ].forEach(function (aScript) {
   let [name, script] = aScript;
   XPCOMUtils.defineLazyGetter(window, name, function() {
     let sandbox = {};
--- a/mobile/chrome/content/browser-ui.js
+++ b/mobile/chrome/content/browser-ui.js
@@ -431,21 +431,16 @@ var BrowserUI = {
     return this._toolbarH;
   },
 
   get sidebarW() {
     delete this._sidebarW;
     return this._sidebarW = Elements.controls.getBoundingClientRect().width;
   },
 
-  get starButton() {
-    delete this.starButton;
-    return this.starButton = document.getElementById("tool-star");
-  },
-
   sizeControls: function(windowW, windowH) {
     // tabs
     document.getElementById("tabs").resize();
 
     // awesomebar and related panels
     let popup = document.getElementById("awesome-panels");
     popup.top = this.toolbarH;
     popup.height = windowH - this.toolbarH;
@@ -538,16 +533,17 @@ var BrowserUI = {
                                            .ensureContentProcess();
       }
 
 #ifdef MOZ_SERVICES_SYNC
       // Init the sync system
       WeaveGlue.init();
 #endif
 
+      Services.prefs.addObserver("browser.ui.layout.tablet", BrowserUI, false);
       Services.obs.addObserver(BrowserSearch, "browser-search-engine-modified", false);
       messageManager.addMessageListener("Browser:MozApplicationManifest", OfflineApps);
 
       // Init helpers
       BadgeHandlers.register(BrowserUI._edit.popup);
       FormHelperUI.init();
       FindHelperUI.init();
       PageActions.init();
@@ -596,21 +592,35 @@ var BrowserUI = {
             Services.console.logStringMessage("[timing] " + name + ": " + (startup[name] - startup.process) + "ms");
         }
       }, 3000);
 #endif
   },
 
   uninit: function() {
     Services.obs.removeObserver(BrowserSearch, "browser-search-engine-modified");
+    Services.prefs.removeObserver("browser.ui.layout.tablet", BrowserUI);
     messageManager.removeMessageListener("Browser:MozApplicationManifest", OfflineApps);
     ExtensionsView.uninit();
     ConsoleView.uninit();
   },
 
+  observe: function observe(aSubject, aTopic, aData) {
+    if (aTopic == "nsPref:changed" && aData == "browser.ui.layout.tablet")
+      this.updateTabletLayout();
+  },
+
+  updateTabletLayout: function updateTabletLayout() {
+    let tabletPref = Services.prefs.getIntPref("browser.ui.layout.tablet");
+    if (tabletPref == 1 || (tabletPref == -1 && Util.isTablet()))
+      Elements.urlbarState.setAttribute("tablet", "true");
+    else
+      Elements.urlbarState.removeAttribute("tablet");
+  },
+
   update: function(aState) {
     let browser = Browser.selectedBrowser;
 
     switch (aState) {
       case TOOLBARSTATE_LOADED:
         this._updateToolbar();
 
         this._updateIcon(browser.mIconURL);
@@ -748,26 +758,33 @@ var BrowserUI = {
   updateUIFocus: function _updateUIFocus() {
     if (Elements.contentShowing.getAttribute("disabled") == "true")
       Browser.selectedBrowser.messageManager.sendAsyncMessage("Browser:Blur", { });
   },
 
   updateStar: function() {
     let uri = getBrowser().currentURI;
     if (uri.spec == "about:blank") {
-      this.starButton.removeAttribute("starred");
+      this._setStar(false);
       return;
     }
 
-    PlacesUtils.asyncGetBookmarkIds(uri, function (aItemIds) {
-      if (aItemIds.length)
-        this.starButton.setAttribute("starred", "true");
+    PlacesUtils.asyncGetBookmarkIds(uri, function(aItemIds) {
+      this._setStar(aItemIds.length > 0)
+    }, this);
+  },
+
+  _setStar: function _setStar(aIsStarred) {
+    let buttons = document.getElementsByClassName("tool-star");
+    for (let i = 0; i < buttons.length; i++) {
+      if (aIsStarred)
+        buttons[i].setAttribute("starred", "true");
       else
-        this.starButton.removeAttribute("starred");
-    }, this);
+        buttons[i].removeAttribute("starred");
+    }
   },
 
   newTab: function newTab(aURI, aOwner) {
     aURI = aURI || "about:blank";
     let tab = Browser.addTab(aURI, true, aOwner);
 
     this.hidePanel();
 
@@ -1134,16 +1151,17 @@ var BrowserUI = {
       case "cmd_star":
       case "cmd_opensearch":
       case "cmd_bookmarks":
       case "cmd_history":
       case "cmd_remoteTabs":
       case "cmd_quit":
       case "cmd_close":
       case "cmd_menu":
+      case "cmd_showTabs":
       case "cmd_newTab":
       case "cmd_closeTab":
       case "cmd_undoCloseTab":
       case "cmd_actions":
       case "cmd_panel":
       case "cmd_sanitize":
       case "cmd_zoomin":
       case "cmd_zoomout":
@@ -1202,18 +1220,17 @@ var BrowserUI = {
         this.goToURI();
         break;
       case "cmd_openLocation":
         this.showAutoComplete();
         break;
       case "cmd_star":
       {
         BookmarkPopup.toggle();
-        if (!this.starButton.hasAttribute("starred"))
-          this.starButton.setAttribute("starred", "true");
+        this._setStar(true);
 
         let bookmarkURI = browser.currentURI;
         PlacesUtils.asyncGetBookmarkIds(bookmarkURI, function (aItemIds) {
           if (!aItemIds.length) {
             let bookmarkTitle = browser.contentTitle || bookmarkURI.spec;
             try {
               let bookmarkService = PlacesUtils.bookmarks;
               let bookmarkId = bookmarkService.insertBookmark(BookmarkList.panel.mobileRoot, bookmarkURI,
@@ -1271,16 +1288,19 @@ var BrowserUI = {
         this._closeOrQuit();
         break;
       case "cmd_close":
         this._closeOrQuit();
         break;
       case "cmd_menu":
         AppMenu.toggle();
         break;
+      case "cmd_showTabs":
+        TabsPopup.toggle();
+        break;
       case "cmd_newTab":
         this.newTab();
         break;
       case "cmd_closeTab":
         this.closeTab();
         break;
       case "cmd_undoCloseTab":
         this.undoCloseTab();
--- a/mobile/chrome/content/browser.js
+++ b/mobile/chrome/content/browser.js
@@ -254,16 +254,18 @@ var Browser = {
       let h = window.innerHeight;
 
       // Don't bother doing unuseful work during intermediate resized during
       // startup if the goal is to be fullscreen
       let fullscreen = (document.documentElement.getAttribute("sizemode") == "fullscreen");
       if (fullscreen && w != screen.width)
         return;
 
+      BrowserUI.updateTabletLayout();
+
       let toolbarHeight = Math.round(document.getElementById("toolbar-main").getBoundingClientRect().height);
 
       Browser.styles["window-width"].width = w + "px";
       Browser.styles["window-height"].height = h + "px";
       Browser.styles["toolbar-height"].height = toolbarHeight + "px";
 
       // Tell the UI to resize the browser controls
       BrowserUI.sizeControls(w, h);
@@ -1244,49 +1246,55 @@ Browser.MainDragger.prototype = {
     return { x: true, y: true };
   },
 
   dragStart: function dragStart(clientX, clientY, target, scroller) {
     let browser = getBrowser();
     let bcr = browser.getBoundingClientRect();
     this._contentView = browser.getViewAt(clientX - bcr.left, clientY - bcr.top);
     this._stopAtSidebar = 0;
+    this._panToolbars = !Elements.urlbarState.getAttribute("tablet");
     if (this._sidebarTimeout) {
       clearTimeout(this._sidebarTimeout);
       this._sidebarTimeout = null;
     }
   },
 
   dragStop: function dragStop(dx, dy, scroller) {
     if (this._contentView && this._contentView._updateCacheViewport)
       this._contentView._updateCacheViewport();
     this._contentView = null;
     this.dragMove(Browser.snapSidebars(), 0, scroller);
     Browser.tryUnfloatToolbar();
   },
 
   dragMove: function dragMove(dx, dy, scroller, aIsKinetic) {
     let doffset = new Point(dx, dy);
-
-    // If the sidebars are showing, we pan them out of the way before panning the content.
-    // The panning distance that should be used for the sidebars in is stored in sidebarOffset,
-    // and subtracted from doffset.
-    let sidebarOffset = this._getSidebarOffset(doffset);
-
-    // If we started with one sidebar open, stop when we get to the other.
-    if (sidebarOffset.x != 0)
-      this._blockSidebars(sidebarOffset);
+    let sidebarOffset = null;
+
+    if (this._panToolbars) {
+      // If the sidebars are showing, we pan them out of the way before panning the content.
+      // The panning distance that should be used for the sidebars in is stored in sidebarOffset,
+      // and subtracted from doffset.
+      sidebarOffset = this._getSidebarOffset(doffset);
+
+      // If we started with one sidebar open, stop when we get to the other.
+      if (sidebarOffset.x != 0)
+        this._blockSidebars(sidebarOffset);
+    }
 
     if (!this.contentMouseCapture)
       this._panContent(doffset);
 
-    if (aIsKinetic && doffset.x != 0)
-      return false;
-
-    this._panChrome(doffset, sidebarOffset);
+    if (this._panToolbars) {
+      if (aIsKinetic && doffset.x != 0)
+        return false;
+
+      this._panChrome(doffset, sidebarOffset);
+    }
 
     this._updateScrollbars();
 
     return !doffset.equals(dx, dy);
   },
 
   _blockSidebars: function md_blockSidebars(aSidebarOffset) {
     // only call this code once
@@ -1853,17 +1861,18 @@ const ContentTouchHandler = {
     let browser = getBrowser();
     let bcr = browser.getBoundingClientRect();
     let rect = new Rect(0, 0, window.innerWidth, window.innerHeight);
     rect.restrictTo(Rect.fromRect(bcr));
 
     // Check if the user touched near to one of the edges of the browser area
     // or if the urlbar is showing
     this.canCancelPan = (aX >= rect.left + kSafetyX) && (aX <= rect.right - kSafetyX) &&
-                        (aY >= rect.top  + kSafetyY) && bcr.top == 0;
+                        (aY >= rect.top  + kSafetyY) &&
+                        (bcr.top == 0 || Elements.urlbarState.getAttribute("tablet"));
   },
 
   tapDown: function tapDown(aX, aY) {
     let browser = getBrowser();
     browser.focus();
 
     // if the page might capture touch events, we give it the option
     this.updateCanCancel(aX, aY);
@@ -3058,17 +3067,22 @@ function rendererFactory(aBrowser, aCanv
  * window but floats over it.
  */
 var ViewableAreaObserver = {
   get width() {
     return this._width || window.innerWidth;
   },
 
   get height() {
-    return (this._height || window.innerHeight);
+    let height = (this._height || window.innerHeight);
+    if (Elements.urlbarState.getAttribute("tablet")) {
+      let toolbarHeight = Math.round(document.getElementById("toolbar-main").getBoundingClientRect().height);
+      height -= toolbarHeight;
+    }
+    return height;
   },
 
   _isKeyboardOpened: true,
   get isKeyboardOpened() {
     return this._isKeyboardOpened;
   },
 
   set isKeyboardOpened(aValue) {
--- a/mobile/chrome/content/browser.xul
+++ b/mobile/chrome/content/browser.xul
@@ -97,16 +97,17 @@
     <command id="cmd_forward" label="&forward.label;" disabled="true" oncommand="CommandUpdater.doCommand(this.id);"/>
     <command id="cmd_reload" label="&reload.label;" oncommand="CommandUpdater.doCommand(this.id);"/>
     <command id="cmd_forceReload" oncommand="CommandUpdater.doCommand(this.id);"/>
     <command id="cmd_stop" label="&stop.label;" oncommand="CommandUpdater.doCommand(this.id);"/>
     <command id="cmd_go" label="&go.label;" oncommand="CommandUpdater.doCommand(this.id);"/>
     <command id="cmd_openLocation" oncommand="CommandUpdater.doCommand(this.id);"/>
 
     <!-- tabs -->
+    <command id="cmd_showTabs" label="&showTabs.label;" oncommand="CommandUpdater.doCommand(this.id);"/>
     <command id="cmd_newTab" label="&newtab.label;" oncommand="CommandUpdater.doCommand(this.id);"/>
     <command id="cmd_closeTab" label="&closetab.label;" oncommand="CommandUpdater.doCommand(this.id);"/>
 #ifdef MOZ_SERVICES_SYNC
     <command id="cmd_remoteTabs" oncommand="CommandUpdater.doCommand(this.id);"/>
 #endif
     <command id="cmd_undoCloseTab" oncommand="CommandUpdater.doCommand(this.id);"/>
 
     <!-- bookmarking -->
@@ -190,17 +191,17 @@
 
     <!-- tabs -->
     <key id="key_newTab" key="t" modifiers="accel" command="cmd_newTab"/>
     <key id="key_closeTab" key="w" modifiers="accel" command="cmd_closeTab"/>
     <key id="key_undoCloseTab" key="t" modifiers="accel,shift" command="cmd_undoCloseTab"/>
   </keyset>
 
   <stack flex="1" id="stack">
-    <scrollbox id="controls-scrollbox" style="overflow: hidden; -moz-box-orient: horizontal; position: relative;" flex="1">
+    <scrollbox id="controls-scrollbox" style="overflow: hidden; -moz-box-orient: horizontal; position: relative;" flex="1" observes="bcast_urlbarState">
       <vbox id="tabs-sidebar" class="sidebar" observes="bcast_uidiscovery">
         <spacer class="toolbar-height"/>
         <!-- Left toolbar -->
         <vbox id="tabs-container" class="panel-dark" flex="1">
           <vbox id="tabs" flex="1"
                 onselect="BrowserUI.selectTab(this);"
                 onreloadtab="BrowserUI.undoCloseTab()"
                 onclosetab="BrowserUI.closeTab(this)"
@@ -213,17 +214,19 @@
 
       <!-- Page Area -->
       <stack class="window-width window-height">
         <scrollbox id="page-scrollbox" class="window-width window-height">
           <vbox>
             <!-- Main Toolbar -->
             <box id="toolbar-container" class="panel-dark toolbar-height">
               <box id="toolbar-moveable-container" observes="bcast_uidiscovery">
-                <toolbar id="toolbar-main" class="panel-dark window-width">
+                <toolbar id="toolbar-main" class="panel-dark window-width" observes="bcast_urlbarState">
+                  <toolbarbutton id="tool-back2" class="tool-back button-actionbar" command="cmd_back"/>
+                  <toolbarbutton id="tool-forward2" class="tool-forward button-actionbar" command="cmd_forward"/>
 #ifdef MOZ_PLATFORM_MAEMO
 #if MOZ_PLATFORM_MAEMO != 6
                   <toolbarbutton id="tool-app-switch" oncommand="BrowserUI.switchTask();"/>
 #endif
 #endif
                   <hbox id="urlbar-container" flex="1" observes="bcast_urlbarState">
                     <box id="identity-box" class="urlbar-cap-button"
                          onclick="getIdentityHandler().handleIdentityButtonEvent(event);"
@@ -252,25 +255,27 @@
                              ontextentered="BrowserUI.goToURI();"
                              clickSelectsAll="true"/>
                     <hbox id="urlbar-icons" class="urlbar-cap-button" observes="bcast_urlbarState">
                       <toolbarbutton id="tool-reload" oncommand="CommandUpdater.doCommand(event.shiftKey ? 'cmd_forceReload' : 'cmd_reload');"/>
                       <toolbarbutton id="tool-stop" command="cmd_stop"/>
                       <toolbarbutton id="tool-search" command="cmd_opensearch"/>
                     </hbox>
                   </hbox>
+                  <toolbarbutton id="tool-star2" class="tool-star button-actionbar" command="cmd_star"/>
+                  <toolbarbutton id="tool-tabs" class="button-actionbar" command="cmd_showTabs"/>
 #ifndef ANDROID
                   <toolbarbutton id="tool-app-close" class="urlbar-button" command="cmd_close"/>
 #endif
                 </toolbar>
               </box>
             </box>
 
             <!-- Content viewport -->
-            <vbox id="content-viewport" class="window-width window-height">
+            <vbox id="content-viewport" class="viewable-width viewable-height">
               <!-- Content viewport -->
               <stack>
                 <deck id="browsers" flex="1" observes="bcast_uidiscovery"/>
                 <!-- vertical scrollbar -->
                 <box id="vertical-scroller" class="scroller" orient="vertical" end="2" top="0"/>
               </stack>
             </vbox>
           </vbox>
@@ -289,19 +294,19 @@
       <vbox id="controls-sidebar" class="sidebar" observes="bcast_uidiscovery">
         <!-- Because of the stack + fixed position of the urlbar when it is in
              locked mode the event on the top-right part of the urlbar are
              swallow by this spacer, but not with the mousethrough attribute
         -->
         <spacer class="toolbar-height" mousethrough="always"/>
 
         <vbox id="browser-controls" style="overflow: -moz-hidden-unscrollable;" class="panel-dark" flex="1">
-          <toolbarbutton id="tool-star" class="button-control" command="cmd_star"/>
-          <toolbarbutton id="tool-back" class="button-control" command="cmd_back"/>
-          <toolbarbutton id="tool-forward" class="button-control" command="cmd_forward"/>
+          <toolbarbutton id="tool-star" class="tool-star button-control" command="cmd_star"/>
+          <toolbarbutton id="tool-back" class="tool-back button-control" command="cmd_back"/>
+          <toolbarbutton id="tool-forward" class="tool-forward button-control" command="cmd_forward"/>
           <toolbarspring/>
           <toolbarbutton id="tool-panel-open" class="button-control" command="cmd_panel"/>
         </vbox>
       </vbox>
     </scrollbox>
 
     <!-- Form Helper suggestions helper popup -->
     <arrowbox id="form-helper-suggestions-container" flex="1" hidden="true" offset="0" top="0" left="0">
--- a/mobile/chrome/content/tabs.xml
+++ b/mobile/chrome/content/tabs.xml
@@ -207,20 +207,23 @@
 
           this.removeTab(this._closedTab);
         ]]></body>
       </method>
 
       <method name="resize">
         <body>
           <![CDATA[
-            let container = this.parentNode.getBoundingClientRect();
-            let element = this._scrollbox.getBoundingClientRect();
-            let undo = this._tabsUndo.getBoundingClientRect();
-            let lastChild = this.parentNode.lastChild.getBoundingClientRect();
+            let container = Rect.fromRect(this.parentNode.getBoundingClientRect());
+            if (container.height == 0)
+              return; // Don't try to resize while collapsed.
+
+            let element = Rect.fromRect(this._scrollbox.getBoundingClientRect());
+            let undo = Rect.fromRect(this._tabsUndo.getBoundingClientRect());
+            let lastChild = Rect.fromRect(this.parentNode.lastChild.getBoundingClientRect());
 
             let height = window.innerHeight - element.top - (lastChild.top - element.bottom) - lastChild.height;
             this.children.style.height = height + "px";
             this._scrollbox.style.height = height + "px";
 
             this._updateWidth();
           ]]>
         </body>
--- a/mobile/chrome/jar.mn
+++ b/mobile/chrome/jar.mn
@@ -25,16 +25,17 @@ chrome.jar:
   content/BookmarkPopup.js             (content/BookmarkPopup.js)
   content/ContentPopupHelper.js        (content/ContentPopupHelper.js)
 * content/ContextCommands.js           (content/ContextCommands.js)
   content/IndexedDB.js                 (content/IndexedDB.js)
   content/MenuListHelperUI.js          (content/MenuListHelperUI.js)
   content/OfflineApps.js               (content/OfflineApps.js)
   content/SelectHelperUI.js            (content/SelectHelperUI.js)
   content/SharingUI.js                 (content/SharingUI.js)
+  content/TabsPopup.js                 (content/TabsPopup.js)
 * content/content.js                   (content/content.js)
   content/commandUtil.js               (content/commandUtil.js)
 * content/bindings.xml                 (content/bindings.xml)
   content/tabs.xml                     (content/tabs.xml)
   content/bindings/checkbox.xml        (content/bindings/checkbox.xml)
   content/bindings/browser.xml         (content/bindings/browser.xml)
   content/bindings/browser.js          (content/bindings/browser.js)
   content/notification.xml             (content/notification.xml)
--- a/mobile/locales/en-US/chrome/browser.dtd
+++ b/mobile/locales/en-US/chrome/browser.dtd
@@ -2,16 +2,17 @@
 
 <!ENTITY back.label            "Back">
 <!ENTITY forward.label         "Forward">
 <!ENTITY reload.label          "Reload">
 <!ENTITY stop.label            "Stop">
 <!ENTITY go.label              "Go">
 <!ENTITY star.label            "Star">
 
+<!ENTITY showTabs.label        "Show Tabs">
 <!ENTITY newtab.label          "New Tab">
 <!ENTITY closetab.label        "Close Tab">
 
 <!ENTITY cut.label             "Cut">
 <!ENTITY copy.label            "Copy">
 <!ENTITY copyAll.label         "Copy All">
 <!ENTITY copylink.label        "Copy Link Location">
 <!ENTITY paste.label           "Paste">
--- a/mobile/themes/core/browser.css
+++ b/mobile/themes/core/browser.css
@@ -318,55 +318,59 @@ toolbarbutton.urlbar-button {
 #browser-controls {
   -moz-box-align: start;
   padding: 0;
   -moz-border-start: @border_width_large@ solid #262629;
   min-width: @sidebar_width_minimum@ !important;
   background: #5e6166;
 }
 
-toolbarbutton.button-control {
+.button-control {
   padding: 0 !important;
   min-width: @sidebar_width_minimum@ !important;
   border-top: @border_width_tiny@ solid rgba(255,255,255,0.2) !important;
   border-bottom: @border_width_tiny@ solid rgba(0,0,0,0.2) !important;
   background-color: transparent !important;
   -moz-box-align: center;
   -moz-box-pack: center;
   height: @sidebar_button_height@;
 }
 
-toolbarbutton.button-control:last-child {
+.button-control:last-child {
   border-bottom: 0 solid rgba(0,0,0,0.2) !important;
 }
 
-toolbarbutton.button-control[disabled="true"] {
+.button-control[disabled="true"] {
   opacity: 0.5;
 }
 
-toolbarbutton.button-control:not([disabled="true"]):hover:active {
+.button-control:not([disabled="true"]):hover:active {
   background-image: url("chrome://browser/skin/images/sidebarbutton-active-hdpi.png");
   border-top: none;
 }
 
-#tool-star {
+#tool-tabs {
+  list-style-image: url("chrome://browser/skin/images/tabs-hdpi.png");
+}
+
+.tool-star {
   list-style-image: url("chrome://browser/skin/images/bookmark-default-hdpi.png");
 }
 
-#tool-star[starred="true"] {
+.tool-star[starred="true"] {
   list-style-image: url("chrome://browser/skin/images/bookmark-starred-hdpi.png");
 }
 
-#tool-back,
-#tool-forward:-moz-locale-dir(rtl) {
+.tool-back,
+.tool-forward:-moz-locale-dir(rtl) {
   list-style-image: url("chrome://browser/skin/images/back-default-hdpi.png");
 }
 
-#tool-back:-moz-locale-dir(rtl),
-#tool-forward {
+.tool-back:-moz-locale-dir(rtl),
+.tool-forward {
   list-style-image: url("chrome://browser/skin/images/forward-default-hdpi.png");
 }
 
 #tool-panel-open {
   list-style-image: url("chrome://browser/skin/images/settings-default-hdpi.png");
 }
 
 %ifndef ANDROID
@@ -1546,16 +1550,59 @@ setting {
   from { -moz-transform: translateX(0); }
   10% { -moz-transform: translateX(-moz-calc(-121px - @border_width_large@ - 2*@padding_normal@)); }
   45% { -moz-transform: translateX(-moz-calc(-121px - @border_width_large@ - 2*@padding_normal@)); }
   55% { -moz-transform: translateX(@sidebar_width_minimum@); }
   90% { -moz-transform: translateX(@sidebar_width_minimum@); }
   to { -moz-transform: translateX(0); }
 }
 
+/* Tablet mode */
+
+.button-actionbar {
+  visibility: collapse;
+}
+
+.button-actionbar[disabled="true"] {
+  opacity: 0.5;
+}
+
+.button-actionbar:hover:active {
+  background-color: #8db8d8;
+}
+
+#toolbar-main[tablet="true"] > .button-actionbar {
+  visibility: visible;
+}
+#toolbar-main[tablet="true"][mode="edit"] > .button-actionbar {
+  visibility: collapse;
+}
+
+#controls-scrollbox[tablet="true"] > #controls-sidebar {
+  visibility: collapse;
+}
+
+#controls-scrollbox[tablet="true"] > #tabs-sidebar {
+  border: none;
+  position: fixed;
+  top: -moz-calc(@touch_button_xlarge@ + @margin_normal@);
+  visibility: collapse;
+}
+#controls-scrollbox[tablet="true"] > #tabs-sidebar:-moz-locale-dir(ltr) {
+  right: 0;
+}
+#controls-scrollbox[tablet="true"] > #tabs-sidebar:-moz-locale-dir(rtl) {
+  left: 0;
+}
+#controls-scrollbox[tablet="true"] > #tabs-sidebar[open] {
+  visibility: visible;
+}
+
+/* Text selection handles */
+
 #selectionhandle-start,
 #selectionhandle-end {
   min-width: 35px !important;
   width: 35px !important;
   padding: 0 !important;
   margin: 0 !important;
 }
 
--- a/mobile/themes/core/gingerbread/browser.css
+++ b/mobile/themes/core/gingerbread/browser.css
@@ -286,55 +286,59 @@ toolbarbutton.urlbar-button {
 #browser-controls {
   -moz-box-align: start;
   padding: 0;
   -moz-border-start: @border_width_large@ solid #262629;
   min-width: @sidebar_width_minimum@ !important;
   background: @color_background_default@;
 }
 
-toolbarbutton.button-control {
+.button-control {
   padding: 0 !important;
   min-width: @sidebar_width_minimum@ !important;
   border-top: @border_width_tiny@ solid @color_divider_border@ !important;
   border-bottom: @border_width_tiny@ solid @color_divider_border@ !important;
   background-color: transparent !important;
   -moz-box-align: center;
   -moz-box-pack: center;
   height: @sidebar_button_height@;
 }
 
-toolbarbutton.button-control:last-child {
+.button-control:last-child {
   border-bottom: 0 solid @color_divider_border@ !important;
 }
 
-toolbarbutton.button-control[disabled="true"] {
+.button-control[disabled="true"] {
   opacity: 0.5;
 }
 
-toolbarbutton.button-control:not([disabled="true"]):hover:active {
+.button-control:not([disabled="true"]):hover:active {
   background-color: @color_background_active@ !important;
   border-top: none;
 }
 
-#tool-star {
+#tool-tabs {
+  list-style-image: url("chrome://browser/skin/images/tabs-hdpi.png");
+}
+
+.tool-star {
   list-style-image: url("chrome://browser/skin/images/bookmark-default-hdpi.png");
 }
 
 #tool-star[starred="true"] {
   list-style-image: url("chrome://browser/skin/images/bookmark-starred-hdpi.png");
 }
 
-#tool-back,
-#tool-forward:-moz-locale-dir(rtl) {
+.tool-back,
+.tool-forward:-moz-locale-dir(rtl) {
   list-style-image: url("chrome://browser/skin/images/back-default-hdpi.png");
 }
 
-#tool-back:-moz-locale-dir(rtl),
-#tool-forward {
+.tool-back:-moz-locale-dir(rtl),
+.tool-forward {
   list-style-image: url("chrome://browser/skin/images/forward-default-hdpi.png");
 }
 
 #tool-panel-open {
   list-style-image: url("chrome://browser/skin/images/settings-default-hdpi.png");
 }
 
 %ifndef ANDROID
@@ -1525,24 +1529,66 @@ setting {
   from { -moz-transform: translateX(0); }
   10% { -moz-transform: translateX(-moz-calc(-121px - @border_width_large@ - 2*@padding_normal@)); }
   45% { -moz-transform: translateX(-moz-calc(-121px - @border_width_large@ - 2*@padding_normal@)); }
   55% { -moz-transform: translateX(@sidebar_width_minimum@); }
   90% { -moz-transform: translateX(@sidebar_width_minimum@); }
   to { -moz-transform: translateX(0); }
 }
 
+/* Tablet mode */
+
+.button-actionbar {
+  visibility: collapse;
+}
+
+.button-actionbar[disabled="true"] {
+  opacity: 0.5;
+}
+
+.button-actionbar:hover:active {
+  background-color: #8db8d8;
+}
+
+#toolbar-main[tablet="true"] > .button-actionbar {
+  visibility: visible;
+}
+#toolbar-main[tablet="true"][mode="edit"] > .button-actionbar {
+  visibility: collapse;
+}
+
+#controls-scrollbox[tablet="true"] > #controls-sidebar {
+  visibility: collapse;
+}
+
+#controls-scrollbox[tablet="true"] > #tabs-sidebar {
+  border: none;
+  position: fixed;
+  top: -moz-calc(@touch_button_xlarge@ + @margin_normal@);
+  visibility: collapse;
+}
+#controls-scrollbox[tablet="true"] > #tabs-sidebar:-moz-locale-dir(ltr) {
+  right: 0;
+}
+#controls-scrollbox[tablet="true"] > #tabs-sidebar:-moz-locale-dir(rtl) {
+  left: 0;
+}
+#controls-scrollbox[tablet="true"] > #tabs-sidebar[open] {
+  visibility: visible;
+}
+
+/* Text selection handles */
+
 #selectionhandle-start,
 #selectionhandle-end {
   min-width: 35px !important;
   width: 35px !important;
   padding: 0 !important;
   margin: 0 !important;
 }
 
 #selectionhandle-start {
   list-style-image: url("chrome://browser/skin/images/handle-start.png");
 }
 
 #selectionhandle-end {
   list-style-image: url("chrome://browser/skin/images/handle-end.png");
 }
-
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ba656e12dc26bb8f03654c72050ee99cc3be32f9
GIT binary patch
literal 307
zc$@(<0nGl1P)<h;3K|Lk000e1NJLTq001fg001fo1^@s6#ly*400001b5ch_0Itp)
z=>Px#?MXyIR9M69mO%=GKnz6_nG+OxjTX8pg`Q#NrghmlU0hgUbW<}XC87B+h%WT|
z&HE03GmQX1!s{hz5I;v!3IL^Ul-d&_8NY}q{>Fos*$ksvqIpEu$~lvjHds-=g-HX7
z-Zn_P;x^C*0aF;|sFVVT$X?r);k3an>MgXvp-)dlBCFNqdONWTpL@Mm&F5dto?RFP
z0JuM%=!N=0Em6(A!6E8@*#=4DK^l95We%$KKhw7eMMgp(QH)K=-x^I^`m82ey-+XG
z20?uo)u!G;8}uexy-?3;gP=Z)TANV~pVEnHw80xZX#zRD;IJg;Mmhih002ovPDHLk
FV1n4geog=Y
--- a/mobile/themes/core/jar.mn
+++ b/mobile/themes/core/jar.mn
@@ -70,16 +70,17 @@ chrome.jar:
   skin/images/stop-hdpi.png                 (images/stop-hdpi.png)
   skin/images/reload-hdpi.png               (images/reload-hdpi.png)
   skin/images/alert-addons-30.png           (images/alert-addons-30.png)
   skin/images/alert-downloads-30.png        (images/alert-downloads-30.png)
   skin/images/addons-default-hdpi.png         (images/addons-default-hdpi.png)
   skin/images/back-default-hdpi.png           (images/back-default-hdpi.png)
   skin/images/allpages-48.png               (images/allpages-48.png)
   skin/images/history-48.png                (images/history-48.png)
+  skin/images/tabs-hdpi.png                 (images/tabs-hdpi.png)
   skin/images/bookmark-default-hdpi.png     (images/bookmark-default-hdpi.png)
   skin/images/bookmarks-48.png              (images/bookmarks-48.png)
   skin/images/bookmark-starred-hdpi.png     (images/bookmark-starred-hdpi.png)
   skin/images/panelrow-active-hdpi.png      (images/panelrow-active-hdpi.png)
   skin/images/panelrow-default-hdpi.png     (images/panelrow-default-hdpi.png)
   skin/images/panelrow-selected-hdpi.png    (images/panelrow-selected-hdpi.png)
   skin/images/forward-default-hdpi.png        (images/forward-default-hdpi.png)
   skin/images/downloads-default-hdpi.png      (images/downloads-default-hdpi.png)
@@ -245,16 +246,17 @@ chrome.jar:
   skin/gingerbread/images/autocomplete-search-hdpi.png  (gingerbread/images/autocomplete-search-hdpi.png)
   skin/gingerbread/images/play-hdpi.png                 (gingerbread/images/play-hdpi.png)
   skin/gingerbread/images/pause-hdpi.png                (gingerbread/images/pause-hdpi.png)
   skin/gingerbread/images/mute-hdpi.png                 (gingerbread/images/mute-hdpi.png)
   skin/gingerbread/images/unmute-hdpi.png               (gingerbread/images/unmute-hdpi.png)
   skin/gingerbread/images/scrubber-hdpi.png             (gingerbread/images/scrubber-hdpi.png)
   skin/gingerbread/images/handle-start.png              (gingerbread/images/handle-start.png)
   skin/gingerbread/images/handle-end.png                (gingerbread/images/handle-end.png)
+  skin/gingerbread/images/tabs-hdpi.png                 (images/tabs-hdpi.png)
   skin/gingerbread/images/errorpage-warning.png         (images/errorpage-warning.png)
   skin/gingerbread/images/errorpage-larry-white.png     (images/errorpage-larry-white.png)
   skin/gingerbread/images/errorpage-larry-black.png     (images/errorpage-larry-black.png)
 
 chrome.jar:
 % skin browser classic/1.0 %skin/honeycomb/ os=Android osversion>=3.0
 % skin browser honeycomb/1.0 %skin/honeycomb/
   skin/honeycomb/aboutPage.css                        (aboutPage.css)
@@ -371,11 +373,12 @@ chrome.jar:
   skin/honeycomb/images/autocomplete-search-hdpi.png  (honeycomb/images/autocomplete-search-hdpi.png)
   skin/honeycomb/images/play-hdpi.png                 (honeycomb/images/play-hdpi.png)
   skin/honeycomb/images/pause-hdpi.png                (honeycomb/images/pause-hdpi.png)
   skin/honeycomb/images/mute-hdpi.png                 (honeycomb/images/mute-hdpi.png)
   skin/honeycomb/images/unmute-hdpi.png               (honeycomb/images/unmute-hdpi.png)
   skin/honeycomb/images/scrubber-hdpi.png             (honeycomb/images/scrubber-hdpi.png)
   skin/honeycomb/images/handle-start.png              (images/handle-start.png)
   skin/honeycomb/images/handle-end.png                (images/handle-end.png)
+  skin/honeycomb/images/tabs-hdpi.png                 (images/tabs-hdpi.png)
   skin/honeycomb/images/errorpage-warning.png         (images/errorpage-warning.png)
   skin/honeycomb/images/errorpage-larry-white.png     (images/errorpage-larry-white.png)
   skin/honeycomb/images/errorpage-larry-black.png     (images/errorpage-larry-black.png)