Bug 1247345 - arrange for the searchbox to not scroll in the SyncedTabs sidebar. r=Gijs
authorMark Hammond <mhammond@skippinet.com.au>
Tue, 29 Mar 2016 22:08:13 +1100
changeset 290787 84008247ec173492c40caa6122ca121b4defe4e4
parent 290786 a7031fdd9cb9952d03721942ef8b0639bd144b6d
child 290788 31071bd61e48933cd2b142701e5324d1830463f3
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersGijs
bugs1247345
milestone48.0a1
Bug 1247345 - arrange for the searchbox to not scroll in the SyncedTabs sidebar. r=Gijs
browser/components/syncedtabs/SyncedTabsDeckView.js
browser/components/syncedtabs/TabListView.js
browser/components/syncedtabs/sidebar.js
browser/components/syncedtabs/sidebar.xhtml
browser/components/syncedtabs/test/browser/browser_sidebar_syncedtabslist.js
browser/themes/shared/syncedtabs/sidebar.inc.css
--- a/browser/components/syncedtabs/SyncedTabsDeckView.js
+++ b/browser/components/syncedtabs/SyncedTabsDeckView.js
@@ -79,21 +79,26 @@ SyncedTabsDeckView.prototype = {
   },
 
   destroy() {
     this._tabListComponent.uninit();
     this.container.remove();
   },
 
   update(state) {
+    // Note that we may also want to update elements that are outside of the
+    // deck, so use the document to find the class names rather than our
+    // container.
     for (let panel of state.panels) {
       if (panel.selected) {
-        this.container.getElementsByClassName(panel.id).item(0).classList.add("selected");
+        Array.prototype.map.call(this._doc.getElementsByClassName(panel.id),
+                                 item => item.classList.add("selected"));
       } else {
-        this.container.getElementsByClassName(panel.id).item(0).classList.remove("selected");
+        Array.prototype.map.call(this._doc.getElementsByClassName(panel.id),
+                                 item => item.classList.remove("selected"));
       }
     }
   },
 
   _clearChilden() {
     while (this.container.firstChild) {
       this.container.removeChild(this.container.firstChild);
     }
--- a/browser/components/syncedtabs/TabListView.js
+++ b/browser/components/syncedtabs/TabListView.js
@@ -67,25 +67,23 @@ TabListView.prototype = {
   },
 
   // Create the initial DOM from templates
   _create(state) {
     let wrapper = this._doc.importNode(this._tabsContainerTemplate.content, true).firstElementChild;
     this._clearChilden();
     this.container.appendChild(wrapper);
 
-    this.tabsFilter = this.container.querySelector(".tabsFilter");
-    this.clearFilter = this.container.querySelector(".textbox-search-clear");
-    this.searchBox = this.container.querySelector(".search-box");
+    // The search-box is outside of our container (it's not scrollable)
+    this.tabsFilter = this._doc.querySelector(".tabsFilter");
+    this.clearFilter = this._doc.querySelector(".textbox-search-clear");
+    this.searchBox = this._doc.querySelector(".search-box");
+    this.searchIcon = this._doc.querySelector(".textbox-search-icon");
+
     this.list = this.container.querySelector(".list");
-    this.searchIcon = this.container.querySelector(".textbox-search-icon");
-
-    if (state.filter) {
-      this.tabsFilter.value = state.filter;
-    }
 
     this._createList(state);
     this._updateSearchBox(state);
 
     this._attachListeners();
   },
 
   _createList(state) {
@@ -180,16 +178,17 @@ TabListView.prototype = {
   },
 
   _updateSearchBox(state) {
     if (state.filter) {
       this.searchBox.classList.add("filtered");
     } else {
       this.searchBox.classList.remove("filtered");
     }
+    this.tabsFilter.value = state.filter;
     if (state.inputFocused) {
       this.searchBox.setAttribute("focused", true);
       this.tabsFilter.focus();
     } else {
       this.searchBox.removeAttribute("focused");
     }
   },
 
--- a/browser/components/syncedtabs/sidebar.js
+++ b/browser/components/syncedtabs/sidebar.js
@@ -12,17 +12,17 @@ Cu.import("resource:///modules/syncedtab
 
 XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
                                   "resource://gre/modules/FxAccounts.jsm");
 
 this.syncedTabsDeckComponent = new SyncedTabsDeckComponent({window, SyncedTabs, fxAccounts});
 
 let onLoaded = () => {
   syncedTabsDeckComponent.init();
-  document.body.appendChild(syncedTabsDeckComponent.container);
+  document.getElementById("template-container").appendChild(syncedTabsDeckComponent.container);
 };
 
 let onUnloaded = () => {
   removeEventListener("DOMContentLoaded", onLoaded);
   removeEventListener("unload", onUnloaded);
   syncedTabsDeckComponent.uninit();
 };
 
--- a/browser/components/syncedtabs/sidebar.xhtml
+++ b/browser/components/syncedtabs/sidebar.xhtml
@@ -58,27 +58,16 @@
           <div class="item-icon-container"></div>
           <p class="item-title"></p>
         </div>
       </div>
     </template>
 
     <template id="tabs-container-template">
       <div class="tabs-container">
-        <div class="sidebar-search-container">
-          <div class="search-box compact">
-            <div class="textbox-input-box">
-              <input type="text" class="tabsFilter textbox-input"/>
-              <div class="textbox-search-icons">
-                <a class="textbox-search-clear"></a>
-                <a class="textbox-search-icon"></a>
-              </div>
-            </div>
-          </div>
-        </div>
         <div class="list" role="listbox" tabindex="1"></div>
       </div>
     </template>
 
     <template id="deck-template">
       <div class="deck">
         <div class="tabs-fetching sync-state">
           <p>&syncedTabs.sidebar.fetching.label;</p>
@@ -93,10 +82,29 @@
         </div>
         <div class="tabs-disabled sync-state">
           <p>&syncedTabs.sidebar.tabsnotsyncing.label;</p>
           <p><a href="#" class="sync-prefs text-link">&syncedTabs.sidebar.openprefs.label;</a></p>
         </div>
       </div>
     </template>
 
+    <div class="content-container">
+      <!-- the non-scrollable header -->
+      <div class="content-header">
+        <div class="sidebar-search-container tabs-container sync-state">
+          <div class="search-box compact">
+            <div class="textbox-input-box">
+              <input type="text" class="tabsFilter textbox-input"/>
+              <div class="textbox-search-icons">
+                <a class="textbox-search-clear"></a>
+                <a class="textbox-search-icon"></a>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+      <!-- the scrollable content area where our templates are inserted -->
+      <div id="template-container" class="content-scrollable">
+      </div>
+    </div>
   </body>
 </html>
--- a/browser/components/syncedtabs/test/browser/browser_sidebar_syncedtabslist.js
+++ b/browser/components/syncedtabs/test/browser/browser_sidebar_syncedtabslist.js
@@ -152,17 +152,17 @@ add_task(function* testSyncedTabsSidebar
   sinon.stub(SyncedTabs._internal, "getTabClients", ()=> Promise.resolve(Cu.cloneInto(FIXTURE, {})));
 
   yield syncedTabsDeckComponent.updatePanel();
   // This is a hacky way of waiting for the view to render. The view renders
   // after the following promise (a different instance of which is triggered
   // in updatePanel) resolves, so we wait for it here as well
   yield syncedTabsDeckComponent.tabListComponent._store.getData();
 
-  let filterInput = syncedTabsDeckComponent.container.querySelector(".tabsFilter");
+  let filterInput = syncedTabsDeckComponent._window.document.querySelector(".tabsFilter");
   filterInput.value = "filter text";
   filterInput.blur();
 
   yield syncedTabsDeckComponent.tabListComponent._store.getData("filter text");
 
   let selectedPanel = syncedTabsDeckComponent.container.querySelector(".sync-state.selected");
   Assert.ok(selectedPanel.classList.contains("tabs-container"),
     "tabs panel is selected");
@@ -348,17 +348,17 @@ function checkItem(node, item) {
       "Node should have .client class");
     Assert.equal(node.dataset.id, item.id,
       "Node's ID should match item ID");
   }
 }
 
 function* testContextMenu(syncedTabsDeckComponent, contextSelector, triggerSelector, menuSelectors) {
   let contextMenu = document.querySelector(contextSelector);
-  let triggerElement = syncedTabsDeckComponent.container.querySelector(triggerSelector);
+  let triggerElement = syncedTabsDeckComponent._window.document.querySelector(triggerSelector);
 
   let promisePopupShown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
 
   let chromeWindow = triggerElement.ownerDocument.defaultView.top;
   let rect = triggerElement.getBoundingClientRect();
   let contentRect = chromeWindow.SidebarUI.browser.getBoundingClientRect();
   // The offsets in `rect` are relative to the content window, but
   // `synthesizeMouseAtPoint` calls `nsIDOMWindowUtils.sendMouseEvent`,
--- a/browser/themes/shared/syncedtabs/sidebar.inc.css
+++ b/browser/themes/shared/syncedtabs/sidebar.inc.css
@@ -1,24 +1,44 @@
 % 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/.
 
 /* These styles are intended to mimic XUL trees and the XUL search box. */
 
-:root, body {
-  overflow-x: hidden;
+html {
+  height: 100%;
 }
 
 body {
+  height: 100%;
   margin: 0;
   font: message-box;
   color: #333333;
   -moz-user-select: none;
-  overflow: hidden;
+}
+
+/* The content-container holds the non-scrollable header and the scrollable
+   content area.
+*/
+.content-container {
+  display: flex;
+  flex-flow: column;
+  height: 100%;
+}
+
+/* The content header is not scrollable */
+.content-header {
+  flex: 0 1 auto;
+}
+
+/* The main content area is scrollable and fills the rest of the area */
+.content-scrollable {
+  flex: 1 1 auto;
+  overflow: auto;
 }
 
 .emptyListInfo {
   cursor: default;
   padding: 3em 1em;
   text-align: center;
 }
 
@@ -163,16 +183,20 @@ body {
   border-top: 0px;
 }
 
 .deck .sync-state.selected {
   display: unset;
   opacity: 100;
 }
 
+.sidebar-search-container.tabs-container:not(.selected) {
+  display: none;
+}
+
 .textbox-search-clear:not([disabled]) {
   cursor: default;
 }
 
 .textbox-search-icons .textbox-search-clear,
 .filtered .textbox-search-icons .textbox-search-icon {
   display: none;
 }