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 290922 84008247ec173492c40caa6122ca121b4defe4e4
parent 290921 a7031fdd9cb9952d03721942ef8b0639bd144b6d
child 290923 31071bd61e48933cd2b142701e5324d1830463f3
push id74432
push userkwierso@gmail.com
push dateThu, 31 Mar 2016 20:08:46 +0000
treeherdermozilla-inbound@bccb11375f2a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersGijs
bugs1247345
milestone48.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 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;
 }