Bug 1259819 - HTML Sidebar component; r=pbro
☠☠ backed out by b6e311c419b5 ☠ ☠
authorJan Odvarko <odvarko@gmail.com>
Thu, 14 Jul 2016 13:26:02 +0200
changeset 330059 453c308dcab1e3236fde0c4ee2e6d0d3d2d60dae
parent 330058 a8ab215f15d3a875f964663a6011b5a6cba67919
child 330060 935085e3a4f0cad3beb1ddfe793135c8ab93f635
push id9858
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 14:37:10 +0000
treeherdermozilla-aurora@203106ef6cb6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspbro
bugs1259819
milestone50.0a1
Bug 1259819 - HTML Sidebar component; r=pbro MozReview-Commit-ID: ABPOx2gRZpb
devtools/client/inspector/components/inspector-tab-panel.css
devtools/client/inspector/components/inspector-tab-panel.js
devtools/client/inspector/components/moz.build
devtools/client/inspector/computed/computed.js
devtools/client/inspector/inspector-panel.js
devtools/client/inspector/inspector.css
devtools/client/inspector/inspector.xul
devtools/client/inspector/moz.build
devtools/client/inspector/test/browser_inspector_pane-toggle-02.js
devtools/client/inspector/test/browser_inspector_pane-toggle-03.js
devtools/client/inspector/test/browser_inspector_pane-toggle-04.js
devtools/client/inspector/test/browser_inspector_sidebarstate.js
devtools/client/inspector/toolsidebar.js
devtools/client/jsonview/css/main.css
devtools/client/jsonview/json-viewer.js
devtools/client/locales/en-US/font-inspector.dtd
devtools/client/locales/en-US/inspector.properties
devtools/client/locales/en-US/layoutview.dtd
devtools/client/locales/en-US/styleinspector.dtd
devtools/client/shared/components/tabs/moz.build
devtools/client/shared/components/tabs/tabbar.css
devtools/client/shared/components/tabs/tabbar.js
devtools/client/shared/components/tabs/tabs.css
devtools/client/shared/components/tabs/tabs.js
devtools/client/themes/animationinspector.css
devtools/client/themes/computed.css
devtools/client/themes/fonts.css
devtools/client/themes/layout.css
devtools/client/themes/rules.css
devtools/client/themes/toolbars.css
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/components/inspector-tab-panel.css
@@ -0,0 +1,15 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+.devtools-inspector-tab-frame {
+  border: none;
+  height: 100%;
+  width: 100%;
+}
+
+.devtools-inspector-tab-panel {
+  width: 100%;
+  height: 100%;
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/components/inspector-tab-panel.js
@@ -0,0 +1,55 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+"use strict";
+
+const { DOM, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
+
+// Shortcuts
+const { div } = DOM;
+
+/**
+ * Side panel for the Inspector panel.
+ * This side panel is using an existing DOM node as a content.
+ */
+var InspectorTabPanel = createClass({
+  displayName: "InspectorTabPanel",
+
+  propTypes: {
+    onMount: PropTypes.func,
+  },
+
+  componentDidMount: function () {
+    let doc = this.refs.content.ownerDocument;
+    let panel = doc.getElementById("sidebar-panel-" + this.props.id);
+
+    // Append existing DOM node into panel's content.
+    this.refs.content.appendChild(panel);
+
+    if (this.props.onMount) {
+      this.props.onMount(this.refs.content, this.props);
+    }
+  },
+
+  componentWillUnmount: function () {
+    let doc = this.refs.content.ownerDocument;
+    let panels = doc.getElementById("tabpanels");
+
+    // Move panel's content node back into list of tab panels.
+    panels.appendChild(this.refs.content.firstChild);
+  },
+
+  render: function () {
+    return (
+      div({
+        ref: "content",
+        className: "devtools-inspector-tab-panel",
+      })
+    );
+  }
+});
+
+module.exports = InspectorTabPanel;
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/components/moz.build
@@ -0,0 +1,10 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+DevToolsModules(
+    'inspector-tab-panel.css',
+    'inspector-tab-panel.js',
+)
--- a/devtools/client/inspector/computed/computed.js
+++ b/devtools/client/inspector/computed/computed.js
@@ -183,17 +183,17 @@ function CssComputedView(inspector, docu
   this.styleDocument.addEventListener("mousedown", this.focusWindow);
   this.element.addEventListener("click", this._onClick);
   this.element.addEventListener("copy", this._onCopy);
   this.element.addEventListener("contextmenu", this._onContextMenu);
   this.searchField.addEventListener("input", this._onFilterStyles);
   this.searchField.addEventListener("contextmenu",
                                     this._onFilterTextboxContextMenu);
   this.searchClearButton.addEventListener("click", this._onClearSearch);
-  this.includeBrowserStylesCheckbox.addEventListener("command",
+  this.includeBrowserStylesCheckbox.addEventListener("input",
     this._onIncludeBrowserStyles);
 
   this.searchClearButton.hidden = true;
 
   // No results text.
   this.noResults = this.styleDocument.getElementById("computedview-no-results");
 
   // Refresh panel when color unit changed.
@@ -768,18 +768,18 @@ CssComputedView.prototype = {
     this.styleDocument.removeEventListener("mousedown", this.focusWindow);
     this.element.removeEventListener("click", this._onClick);
     this.element.removeEventListener("copy", this._onCopy);
     this.element.removeEventListener("contextmenu", this._onContextMenu);
     this.searchField.removeEventListener("input", this._onFilterStyles);
     this.searchField.removeEventListener("contextmenu",
                                          this._onFilterTextboxContextMenu);
     this.searchClearButton.removeEventListener("click", this._onClearSearch);
-    this.includeBrowserStylesCheckbox.removeEventListener("command",
-      this.includeBrowserStylesChanged);
+    this.includeBrowserStylesCheckbox.removeEventListener("input",
+      this._onIncludeBrowserStyles);
 
     // Nodes used in templating
     this.root = null;
     this.element = null;
     this.panel = null;
     this.searchField = null;
     this.searchClearButton = null;
     this.includeBrowserStylesCheckbox = null;
--- a/devtools/client/inspector/inspector-panel.js
+++ b/devtools/client/inspector/inspector-panel.js
@@ -29,17 +29,17 @@ loader.lazyRequireGetter(this, "CSS", "C
 loader.lazyRequireGetter(this, "CommandUtils", "devtools/client/shared/developer-toolbar", true);
 loader.lazyRequireGetter(this, "ComputedViewTool", "devtools/client/inspector/computed/computed", true);
 loader.lazyRequireGetter(this, "FontInspector", "devtools/client/inspector/fonts/fonts", true);
 loader.lazyRequireGetter(this, "HTMLBreadcrumbs", "devtools/client/inspector/breadcrumbs", true);
 loader.lazyRequireGetter(this, "InspectorSearch", "devtools/client/inspector/inspector-search", true);
 loader.lazyRequireGetter(this, "LayoutView", "devtools/client/inspector/layout/layout", true);
 loader.lazyRequireGetter(this, "MarkupView", "devtools/client/inspector/markup/markup", true);
 loader.lazyRequireGetter(this, "RuleViewTool", "devtools/client/inspector/rules/rules", true);
-loader.lazyRequireGetter(this, "ToolSidebar", "devtools/client/framework/sidebar", true);
+loader.lazyRequireGetter(this, "ToolSidebar", "devtools/client/inspector/toolsidebar", true);
 loader.lazyRequireGetter(this, "ViewHelpers", "devtools/client/shared/widgets/view-helpers", true);
 
 loader.lazyGetter(this, "strings", () => {
   return Services.strings.createBundle("chrome://devtools/locale/inspector.properties");
 });
 loader.lazyGetter(this, "toolboxStrings", () => {
   return Services.strings.createBundle("chrome://devtools/locale/toolbox.properties");
 });
@@ -407,42 +407,100 @@ InspectorPanel.prototype = {
 
     let defaultTab = Services.prefs.getCharPref("devtools.inspector.activeSidebar");
 
     if (!Services.prefs.getBoolPref("devtools.fontinspector.enabled") &&
        defaultTab == "fontinspector") {
       defaultTab = "ruleview";
     }
 
+    // Append all side panels
+    this.sidebar.addExistingTab(
+      "ruleview",
+      strings.GetStringFromName("inspector.sidebar.ruleViewTitle"),
+      defaultTab == "ruleview");
+
+    this.sidebar.addExistingTab(
+      "computedview",
+      strings.GetStringFromName("inspector.sidebar.computedViewTitle"),
+      defaultTab == "computedview");
+
+    this.sidebar.addExistingTab(
+      "layoutview",
+      strings.GetStringFromName("inspector.sidebar.layoutViewTitle"),
+      defaultTab == "layoutview");
+
     this._setDefaultSidebar = (event, toolId) => {
       Services.prefs.setCharPref("devtools.inspector.activeSidebar", toolId);
     };
 
     this.sidebar.on("select", this._setDefaultSidebar);
 
     this.ruleview = new RuleViewTool(this, this.panelWin);
     this.computedview = new ComputedViewTool(this, this.panelWin);
     this.layoutview = new LayoutView(this, this.panelWin);
 
     if (this.target.form.animationsActor) {
-      this.sidebar.addTab("animationinspector",
-                          "chrome://devtools/content/animationinspector/animation-inspector.xhtml",
-                          {selected: defaultTab == "animationinspector",
-                           insertBefore: "fontinspector"});
+      this.sidebar.addFrameTab(
+        "animationinspector",
+        strings.GetStringFromName("inspector.sidebar.animationInspectorTitle"),
+        "chrome://devtools/content/animationinspector/animation-inspector.xhtml",
+        defaultTab == "animationinspector");
     }
 
     if (Services.prefs.getBoolPref("devtools.fontinspector.enabled") &&
         this.canGetUsedFontFaces) {
+      this.sidebar.addExistingTab(
+        "fontinspector",
+        strings.GetStringFromName("inspector.sidebar.fontInspectorTitle"),
+        defaultTab == "fontinspector");
+
       this.fontInspector = new FontInspector(this, this.panelWin);
       this.sidebar.toggleTab(true, "fontinspector");
     }
 
+    this.setupSidebarToggle();
+    this.setupSidebarWidth();
+
     this.sidebar.show(defaultTab);
+  },
+
+  /**
+   * Sidebar width is currently driven by vbox.inspector-sidebar-container
+   * element, which is located at the left side of the side bar splitter.
+   * It's width is changed by the splitter and stored into preferences.
+   * As soon as bug 1260552 is fixed and new HTML based splitter in place
+   * the width can be driven by div.inspector-sidebar element. This element
+   * represents the ToolSidebar and so, the entire logic related to width
+   * persistence can be done inside the ToolSidebar.
+   */
+  setupSidebarWidth: function () {
+    let sidePaneContainer = this.panelDoc.querySelector(
+      "#inspector-sidebar-container");
 
-    this.setupSidebarToggle();
+    this.sidebar.on("show", () => {
+      try {
+        sidePaneContainer.width = Services.prefs.getIntPref(
+          "devtools.toolsidebar-width.inspector");
+      } catch (e) {
+        // The default width is the min-width set in CSS
+        // for #inspector-sidebar-container
+        sidePaneContainer.width = 450;
+      }
+    });
+
+    this.sidebar.on("hide", () => {
+      Services.prefs.setIntPref("devtools.toolsidebar-width.inspector",
+        sidePaneContainer.width);
+    });
+
+    this.sidebar.on("destroy", () => {
+      Services.prefs.setIntPref("devtools.toolsidebar-width.inspector",
+        sidePaneContainer.width);
+    });
   },
 
   /**
    * Add the expand/collapse behavior for the sidebar panel.
    */
   setupSidebarToggle: function () {
     let SidebarToggle = this.React.createFactory(this.browserRequire(
       "devtools/client/shared/components/sidebar-toggle"));
@@ -1165,41 +1223,62 @@ InspectorPanel.prototype = {
     }
   },
 
   /**
    * When the pane toggle button is clicked, toggle the pane, change the button
    * state and tooltip.
    */
   onPaneToggleButtonActivated: function (e) {
-    let sidePane = this.panelDoc.querySelector("#inspector-sidebar");
+    let sidePaneContainer = this.panelDoc.querySelector("#inspector-sidebar-container");
     let isVisible = !this._sidebarToggle.state.collapsed;
+    let sidePane = this.panelDoc.querySelector(
+      "#inspector-sidebar .devtools-sidebar-tabs");
 
     // Make sure the sidebar has width and height attributes before collapsing
     // because ViewHelpers needs it.
     if (isVisible) {
-      let rect = sidePane.getBoundingClientRect();
-      if (!sidePane.hasAttribute("width")) {
-        sidePane.setAttribute("width", rect.width);
+      let rect = sidePaneContainer.getBoundingClientRect();
+      if (!sidePaneContainer.hasAttribute("width")) {
+        sidePaneContainer.setAttribute("width", rect.width);
+
+        sidePane.style.width = rect.width + "px";
       }
       // always refresh the height attribute before collapsing, it could have
       // been modified by resizing the container.
-      sidePane.setAttribute("height", rect.height);
+      sidePaneContainer.setAttribute("height", rect.height);
+      sidePane.style.height = rect.height + "px";
     }
 
+    let onAnimationDone = () => {
+      if (isVisible) {
+        this._sidebarToggle.setState({collapsed: true});
+      } else {
+        this._sidebarToggle.setState({collapsed: false});
+      }
+
+      // If the panel is hiding, hide the content after
+      // the animation is done so, the content is visible
+      // during the animation.
+      if (isVisible) {
+        this.sidebar.toggle();
+      }
+    };
+
     ViewHelpers.togglePane({
       visible: !isVisible,
       animated: true,
-      delayed: true
-    }, sidePane);
+      delayed: true,
+      callback: onAnimationDone
+    }, sidePaneContainer);
 
-    if (isVisible) {
-      this._sidebarToggle.setState({collapsed: true});
-    } else {
-      this._sidebarToggle.setState({collapsed: false});
+    // If the panel is showing, show the content intermediately so,
+    // it's visible during the animation.
+    if (!isVisible) {
+      this.sidebar.toggle();
     }
   },
 
   /**
    * Create a new node as the last child of the current selection, expand the
    * parent and select the new node.
    */
   addNode: Task.async(function* () {
--- a/devtools/client/inspector/inspector.css
+++ b/devtools/client/inspector/inspector.css
@@ -1,19 +1,30 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* 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/. */
 
-#inspector-sidebar {
-  min-width: 250px;
+/* Set the minimun width for the side bar so, all tabs are
+  properly visible. The value can be decreased when bug 1281789
+  is fixed and the all-tabs-menu is available again. */
+#inspector-sidebar-container {
+  overflow: hidden;
+  min-width: 450px;
+  position: relative;
 }
 
+#inspector-sidebar {
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  right: 0;
+}
+
+/* Override `-moz-user-focus:ignore;` from toolkit/content/minimal-xul.css */
 .inspector-tabpanel > * {
-  /*
-   * Override `-moz-user-focus:ignore;` from toolkit/content/minimal-xul.css
-   */
   -moz-user-focus: normal;
 }
 
 #inspector-sidebar-toggle-box {
   line-height: initial;
 }
--- a/devtools/client/inspector/inspector.xul
+++ b/devtools/client/inspector/inspector.xul
@@ -6,17 +6,22 @@
 <?xml-stylesheet href="chrome://devtools/content/shared/widgets/widgets.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/content/inspector/inspector.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/widgets.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/inspector.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/rules.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/computed.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/fonts.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/layout.css" type="text/css"?>
+<?xml-stylesheet href="chrome://devtools/skin/animationinspector.css" type="text/css"?>
 <?xml-stylesheet href="resource://devtools/client/shared/components/sidebar-toggle.css" type="text/css"?>
+<?xml-stylesheet href="resource://devtools/client/shared/components/tabs/tabs.css" type="text/css"?>
+<?xml-stylesheet href="resource://devtools/client/shared/components/tabs/tabbar.css" type="text/css"?>
+<?xml-stylesheet href="resource://devtools/client/inspector/components/side-panel.css" type="text/css"?>
+<?xml-stylesheet href="resource://devtools/client/inspector/components/inspector-tab-panel.css" type="text/css"?>
 
 <!DOCTYPE window [
   <!ENTITY % inspectorDTD SYSTEM "chrome://devtools/locale/inspector.dtd"> %inspectorDTD;
   <!ENTITY % styleinspectorDTD SYSTEM "chrome://devtools/locale/styleinspector.dtd"> %styleinspectorDTD;
   <!ENTITY % fontinspectorDTD SYSTEM "chrome://devtools/locale/font-inspector.dtd"> %fontinspectorDTD;
   <!ENTITY % layoutviewDTD SYSTEM "chrome://devtools/locale/layoutview.dtd"> %layoutviewDTD;
 ]>
 
@@ -45,161 +50,159 @@
       </html:div>
       <vbox flex="1" id="markup-box">
       </vbox>
       <html:div id="inspector-breadcrumbs-toolbar" class="devtools-toolbar">
         <html:div id="inspector-breadcrumbs" class="breadcrumbs-widget-container"/>
       </html:div>
     </vbox>
     <splitter class="devtools-side-splitter"/>
-    <tabbox id="inspector-sidebar" handleCtrlTab="false" class="devtools-sidebar-tabs" hidden="true">
-      <tabs>
-        <tab id="sidebar-tab-ruleview"
-             label="&ruleViewTitle;"
-             crop="end"/>
-        <tab id="sidebar-tab-computedview"
-             label="&computedViewTitle;"
-             crop="end"/>
-        <tab id="sidebar-tab-layoutview"
-             label="&layoutViewTitle;"
-             crop="end"/>
-        <tab id="sidebar-tab-fontinspector"
-             label="&fontInspectorTitle;"
-             crop="end"
-             hidden="true"/>
-      </tabs>
-      <tabpanels flex="1">
-        <tabpanel id="sidebar-panel-ruleview" class="devtools-monospace theme-sidebar inspector-tabpanel">
-          <html:div id="ruleview-toolbar-container" class="devtools-toolbar">
-            <html:div id="ruleview-toolbar">
-              <html:div class="devtools-searchbox">
-                <html:input id="ruleview-searchbox"
-                            class="devtools-filterinput devtools-rule-searchbox"
-                            type="search"
-                            placeholder="&filterStylesPlaceholder;"/>
-                <html:button id="ruleview-searchinput-clear" class="devtools-searchinput-clear"></html:button>
-              </html:div>
-              <html:div id="ruleview-command-toolbar">
-                <html:button id="ruleview-add-rule-button" title="&addRuleButtonTooltip;" class="devtools-button"></html:button>
-                <html:button id="pseudo-class-panel-toggle" title="&togglePseudoClassPanel;" class="devtools-button"></html:button>
-              </html:div>
-            </html:div>
-            <html:div id="pseudo-class-panel" hidden="true">
-              <html:label><html:input id="pseudo-hover-toggle" type="checkbox" value=":hover" tabindex="-1" />:hover</html:label>
-              <html:label><html:input id="pseudo-active-toggle" type="checkbox" value=":active" tabindex="-1" />:active</html:label>
-              <html:label><html:input id="pseudo-focus-toggle" type="checkbox" value=":focus" tabindex="-1" />:focus</html:label>
-          </html:div>
-          </html:div>
+    <vbox id="inspector-sidebar-container">
+      <!-- Specify the XHTML namespace explicitely
+        otherwise the layout is broken. -->
+      <div xmlns="http://www.w3.org/1999/xhtml"
+           id="inspector-sidebar"
+           hidden="true" />
+    </vbox>
 
-          <html:div id="ruleview-container" class="ruleview">
-          </html:div>
-        </tabpanel>
-
-        <tabpanel id="sidebar-panel-computedview" class="devtools-monospace theme-sidebar inspector-tabpanel">
-          <html:div class="devtools-toolbar">
+    <!-- Sidebar panel definitions -->
+    <html:div xmlns="http://www.w3.org/1999/xhtml" id="tabpanels" style="visibility:collapse">
+      <html:div id="sidebar-panel-ruleview" class="devtools-monospace theme-sidebar inspector-tabpanel">
+        <html:div id="ruleview-toolbar-container" class="devtools-toolbar">
+          <html:div id="ruleview-toolbar">
             <html:div class="devtools-searchbox">
-              <html:input id="computedview-searchbox"
+              <html:input id="ruleview-searchbox"
                           class="devtools-filterinput devtools-rule-searchbox"
                           type="search"
                           placeholder="&filterStylesPlaceholder;"/>
-              <html:button id="computedview-searchinput-clear" class="devtools-searchinput-clear"></html:button>
+              <html:button id="ruleview-searchinput-clear" class="devtools-searchinput-clear"></html:button>
+            </html:div>
+            <html:div id="ruleview-command-toolbar">
+              <html:button id="ruleview-add-rule-button" title="&addRuleButtonTooltip;" class="devtools-button"></html:button>
+              <html:button id="pseudo-class-panel-toggle" title="&togglePseudoClassPanel;" class="devtools-button"></html:button>
             </html:div>
-            <checkbox id="browser-style-checkbox"
-                      class="includebrowserstyles"
-                      checked="false"
-                      label="&browserStylesLabel;"/>
           </html:div>
+          <html:div id="pseudo-class-panel" hidden="true">
+            <html:label><html:input id="pseudo-hover-toggle" type="checkbox" value=":hover" tabindex="-1" />:hover</html:label>
+            <html:label><html:input id="pseudo-active-toggle" type="checkbox" value=":active" tabindex="-1" />:active</html:label>
+            <html:label><html:input id="pseudo-focus-toggle" type="checkbox" value=":focus" tabindex="-1" />:focus</html:label>
+        </html:div>
+        </html:div>
 
-          <html:div id="propertyContainer">
-          </html:div>
-
-          <html:div id="computedview-no-results" hidden="">
-            &noPropertiesFound;
-          </html:div>
-        </tabpanel>
+        <html:div id="ruleview-container" class="ruleview">
+        </html:div>
+      </html:div>
 
-        <tabpanel id="sidebar-panel-layoutview" class="devtools-monospace theme-sidebar inspector-tabpanel">
-          <html:div id="layout-wrapper">
-            <html:div id="layout-container">
-              <html:p id="layout-header">
-                <html:span id="layout-element-size"></html:span>
-                <html:section id="layout-position-group">
-                  <html:button class="devtools-button" id="layout-geometry-editor" title="&geometry.button.tooltip;"></html:button>
-                  <html:span id="layout-element-position"></html:span>
-                </html:section>
-              </html:p>
+      <html:div id="sidebar-panel-computedview" class="devtools-monospace theme-sidebar inspector-tabpanel">
+        <html:div class="devtools-toolbar">
+          <html:div class="devtools-searchbox">
+            <html:input id="computedview-searchbox"
+                        class="devtools-filterinput devtools-rule-searchbox"
+                        type="search"
+                        placeholder="&filterStylesPlaceholder;"/>
+            <html:button id="computedview-searchinput-clear" class="devtools-searchinput-clear"></html:button>
+          </html:div>
+          <html:label id="browser-style-checkbox-label" for="browser-style-checkbox">
+            <html:input id="browser-style-checkbox"
+                        type="checkbox"
+                        class="includebrowserstyles"
+                        label="&browserStylesLabel;"/>&browserStylesLabel;</html:label>
+        </html:div>
+
+        <html:div id="propertyContainer">
+        </html:div>
 
-              <html:div id="layout-main">
-                <html:span class="layout-legend" data-box="margin" title="&margin.tooltip;">&margin.tooltip;</html:span>
-                <html:div id="layout-margins" data-box="margin" title="&margin.tooltip;">
-                  <html:span class="layout-legend" data-box="border" title="&border.tooltip;">&border.tooltip;</html:span>
-                  <html:div id="layout-borders" data-box="border" title="&border.tooltip;">
-                    <html:span class="layout-legend" data-box="padding" title="&padding.tooltip;">&padding.tooltip;</html:span>
-                    <html:div id="layout-padding" data-box="padding" title="&padding.tooltip;">
-                      <html:div id="layout-content" data-box="content" title="&content.tooltip;">
-                      </html:div>
+        <html:div id="computedview-no-results" hidden="">
+          &noPropertiesFound;
+        </html:div>
+      </html:div>
+
+      <html:div id="sidebar-panel-layoutview" class="devtools-monospace theme-sidebar inspector-tabpanel">
+        <html:div id="layout-wrapper">
+          <html:div id="layout-container">
+            <html:p id="layout-header">
+              <html:span id="layout-element-size"></html:span>
+              <html:section id="layout-position-group">
+                <html:button class="devtools-button" id="layout-geometry-editor" title="&geometry.button.tooltip;"></html:button>
+                <html:span id="layout-element-position"></html:span>
+              </html:section>
+            </html:p>
+
+            <html:div id="layout-main">
+              <html:span class="layout-legend" data-box="margin" title="&margin.tooltip;">&margin.tooltip;</html:span>
+              <html:div id="layout-margins" data-box="margin" title="&margin.tooltip;">
+                <html:span class="layout-legend" data-box="border" title="&border.tooltip;">&border.tooltip;</html:span>
+                <html:div id="layout-borders" data-box="border" title="&border.tooltip;">
+                  <html:span class="layout-legend" data-box="padding" title="&padding.tooltip;">&padding.tooltip;</html:span>
+                  <html:div id="layout-padding" data-box="padding" title="&padding.tooltip;">
+                    <html:div id="layout-content" data-box="content" title="&content.tooltip;">
                     </html:div>
                   </html:div>
                 </html:div>
-
-                <html:p class="layout-border layout-top"><html:span data-box="border" class="layout-editable" title="border-top"></html:span></html:p>
-                <html:p class="layout-border layout-right"><html:span data-box="border" class="layout-editable" title="border-right"></html:span></html:p>
-                <html:p class="layout-border layout-bottom"><html:span data-box="border" class="layout-editable" title="border-bottom"></html:span></html:p>
-                <html:p class="layout-border layout-left"><html:span data-box="border" class="layout-editable" title="border-left"></html:span></html:p>
-
-                <html:p class="layout-margin layout-top"><html:span data-box="margin" class="layout-editable" title="margin-top"></html:span></html:p>
-                <html:p class="layout-margin layout-right"><html:span data-box="margin" class="layout-editable" title="margin-right"></html:span></html:p>
-                <html:p class="layout-margin layout-bottom"><html:span data-box="margin" class="layout-editable" title="margin-bottom"></html:span></html:p>
-                <html:p class="layout-margin layout-left"><html:span data-box="margin" class="layout-editable" title="margin-left"></html:span></html:p>
-
-                <html:p class="layout-padding layout-top"><html:span data-box="padding" class="layout-editable" title="padding-top"></html:span></html:p>
-                <html:p class="layout-padding layout-right"><html:span data-box="padding" class="layout-editable" title="padding-right"></html:span></html:p>
-                <html:p class="layout-padding layout-bottom"><html:span data-box="padding" class="layout-editable" title="padding-bottom"></html:span></html:p>
-                <html:p class="layout-padding layout-left"><html:span data-box="padding" class="layout-editable" title="padding-left"></html:span></html:p>
-
-                <html:p class="layout-size"><html:span data-box="content" title="&content.tooltip;"></html:span></html:p>
               </html:div>
 
-              <html:div style="display: none">
-                <html:p id="layout-dummy"></html:p>
-              </html:div>
+              <html:p class="layout-border layout-top"><html:span data-box="border" class="layout-editable" title="border-top"></html:span></html:p>
+              <html:p class="layout-border layout-right"><html:span data-box="border" class="layout-editable" title="border-right"></html:span></html:p>
+              <html:p class="layout-border layout-bottom"><html:span data-box="border" class="layout-editable" title="border-bottom"></html:span></html:p>
+              <html:p class="layout-border layout-left"><html:span data-box="border" class="layout-editable" title="border-left"></html:span></html:p>
+
+              <html:p class="layout-margin layout-top"><html:span data-box="margin" class="layout-editable" title="margin-top"></html:span></html:p>
+              <html:p class="layout-margin layout-right"><html:span data-box="margin" class="layout-editable" title="margin-right"></html:span></html:p>
+              <html:p class="layout-margin layout-bottom"><html:span data-box="margin" class="layout-editable" title="margin-bottom"></html:span></html:p>
+              <html:p class="layout-margin layout-left"><html:span data-box="margin" class="layout-editable" title="margin-left"></html:span></html:p>
+
+              <html:p class="layout-padding layout-top"><html:span data-box="padding" class="layout-editable" title="padding-top"></html:span></html:p>
+              <html:p class="layout-padding layout-right"><html:span data-box="padding" class="layout-editable" title="padding-right"></html:span></html:p>
+              <html:p class="layout-padding layout-bottom"><html:span data-box="padding" class="layout-editable" title="padding-bottom"></html:span></html:p>
+              <html:p class="layout-padding layout-left"><html:span data-box="padding" class="layout-editable" title="padding-left"></html:span></html:p>
+
+              <html:p class="layout-size"><html:span data-box="content" title="&content.tooltip;"></html:span></html:p>
+            </html:div>
+
+            <html:div style="display: none">
+              <html:p id="layout-dummy"></html:p>
             </html:div>
           </html:div>
-        </tabpanel>
+        </html:div>
+      </html:div>
 
-        <tabpanel id="sidebar-panel-fontinspector" class="devtools-monospace theme-sidebar inspector-tabpanel">
-          <html:div class="devtools-toolbar">
-            <html:div class="devtools-searchbox">
-              <html:input id="font-preview-text-input"
-                          class="devtools-textinput"
-                          type="search"
-                          placeholder="&previewHint;"/>
-            </html:div>
+      <html:div id="sidebar-panel-fontinspector" class="devtools-monospace theme-sidebar inspector-tabpanel">
+        <html:div class="devtools-toolbar">
+          <html:div class="devtools-searchbox">
+            <html:input id="font-preview-text-input"
+                        class="devtools-textinput"
+                        type="search"
+                        placeholder="&previewHint;"/>
           </html:div>
+        </html:div>
 
-          <html:div id="font-container">
-            <html:ul id="all-fonts"></html:ul>
-            <html:button id="font-showall">&showAllFonts;</html:button>
-          </html:div>
+        <html:div id="font-container">
+          <html:ul id="all-fonts"></html:ul>
+          <html:button id="font-showall">&showAllFonts;</html:button>
+        </html:div>
 
-          <html:div id="font-template">
-            <html:section class="font">
-              <html:div class="font-preview-container">
-                <html:img class="font-preview"></html:img>
-              </html:div>
-              <html:div class="font-info">
-                <html:h1 class="font-name"></html:h1>
-                <html:span class="font-is-local">&system;</html:span>
-                <html:span class="font-is-remote">&remote;</html:span>
-                <html:p class="font-format-url">
-                  <html:input readonly="readonly" class="font-url"></html:input>
-                  <html:span class="font-format"></html:span>
-                </html:p>
-                <html:p class="font-css">&usedAs; "<html:span class="font-css-name"></html:span>"</html:p>
-                <html:pre class="font-css-code"></html:pre>
-              </html:div>
-            </html:section>
-          </html:div>
-        </tabpanel>
-      </tabpanels>
-    </tabbox>
+        <html:div id="font-template">
+          <html:section class="font">
+            <html:div class="font-preview-container">
+              <html:img class="font-preview"></html:img>
+            </html:div>
+            <html:div class="font-info">
+              <html:h1 class="font-name"></html:h1>
+              <html:span class="font-is-local">&system;</html:span>
+              <html:span class="font-is-remote">&remote;</html:span>
+              <html:p class="font-format-url">
+                <html:input readonly="readonly" class="font-url"></html:input>
+                <html:span class="font-format"></html:span>
+              </html:p>
+              <html:p class="font-css">&usedAs; "<html:span class="font-css-name"></html:span>"</html:p>
+              <html:pre class="font-css-code"></html:pre>
+            </html:div>
+          </html:section>
+        </html:div>
+      </html:div>
+
+      <html:div id="sidebar-panel-animationinspector" class="devtools-monospace theme-sidebar inspector-tabpanel">
+        <html:iframe class="devtools-inspector-tab-frame" />
+      </html:div>
+    </html:div>
+
   </box>
 </window>
--- a/devtools/client/inspector/moz.build
+++ b/devtools/client/inspector/moz.build
@@ -1,21 +1,23 @@
 # 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/.
 
 DIRS += [
+    'components',
     'computed',
     'fonts',
     'layout',
     'markup',
     'rules',
     'shared'
 ]
 
 DevToolsModules(
     'breadcrumbs.js',
     'inspector-commands.js',
     'inspector-panel.js',
-    'inspector-search.js'
+    'inspector-search.js',
+    'toolsidebar.js',
 )
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
--- a/devtools/client/inspector/test/browser_inspector_pane-toggle-02.js
+++ b/devtools/client/inspector/test/browser_inspector_pane-toggle-02.js
@@ -6,17 +6,17 @@
 // Test that the inspector toggled panel is visible by default, is hidden after
 // clicking on the toggle button and remains expanded/collapsed when switching
 // hosts.
 
 add_task(function* () {
   info("Open the inspector in a side toolbox host");
   let {toolbox, inspector} = yield openInspectorForURL("about:blank", "side");
 
-  let panel = inspector.panelDoc.querySelector("#inspector-sidebar");
+  let panel = inspector.panelDoc.querySelector("#inspector-sidebar-container");
   let button = inspector.panelDoc.querySelector(".sidebar-toggle");
   ok(!panel.classList.contains("pane-collapsed"), "The panel is in expanded state");
 
   info("Listen to the end of the animation on the sidebar panel");
   let onTransitionEnd = once(panel, "transitionend");
 
   info("Click on the toggle button");
   EventUtils.synthesizeMouseAtCenter(button, {},
--- a/devtools/client/inspector/test/browser_inspector_pane-toggle-03.js
+++ b/devtools/client/inspector/test/browser_inspector_pane-toggle-03.js
@@ -5,17 +5,17 @@
 
 // Test that the toggle button can collapse and expand the inspector side/bottom
 // panel, and that the appropriate attributes are updated in the process.
 
 add_task(function* () {
   let {inspector} = yield openInspectorForURL("about:blank");
 
   let button = inspector.panelDoc.querySelector(".sidebar-toggle");
-  let panel = inspector.panelDoc.querySelector("#inspector-sidebar");
+  let panel = inspector.panelDoc.querySelector("#inspector-sidebar-container");
 
   ok(!button.classList.contains("pane-collapsed"), "The button is in expanded state");
 
   info("Listen to the end of the animation on the sidebar panel");
   let onTransitionEnd = once(panel, "transitionend");
 
   info("Click on the toggle button");
   EventUtils.synthesizeMouseAtCenter(button, {},
--- a/devtools/client/inspector/test/browser_inspector_pane-toggle-04.js
+++ b/devtools/client/inspector/test/browser_inspector_pane-toggle-04.js
@@ -15,17 +15,17 @@ add_task(function* () {
     let options = {"set": [
       ["devtools.toolsidebar-width.inspector", 200]
     ]};
     SpecialPowers.pushPrefEnv(options, resolve);
   });
 
   let { inspector, toolbox } = yield openInspectorForURL("about:blank");
   let button = inspector.panelDoc.querySelector(".sidebar-toggle");
-  let panel = inspector.panelDoc.querySelector("#inspector-sidebar");
+  let panel = inspector.panelDoc.querySelector("#inspector-sidebar-container");
 
   info("Changing toolbox host to a window.");
   yield toolbox.switchHost(Toolbox.HostType.WINDOW);
 
   let hostWindow = toolbox._host._window;
   let originalWidth = hostWindow.outerWidth;
   let originalHeight = hostWindow.outerHeight;
 
--- a/devtools/client/inspector/test/browser_inspector_sidebarstate.js
+++ b/devtools/client/inspector/test/browser_inspector_sidebarstate.js
@@ -13,16 +13,20 @@ add_task(function* () {
   inspector.sidebar.select("ruleview");
 
   is(inspector.sidebar.getCurrentTabID(), "ruleview",
      "Rule View is selected by default");
 
   info("Selecting computed view.");
   inspector.sidebar.select("computedview");
 
+  // Finish initialization of the computed panel before
+  // destroying the toolbox.
+  yield waitForTick();
+
   info("Closing inspector.");
   yield toolbox.destroy();
 
   info("Re-opening inspector.");
   inspector = (yield openInspector()).inspector;
 
   if (!inspector.sidebar.getCurrentTabID()) {
     info("Default sidebar still to be selected, adding select listener.");
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/toolsidebar.js
@@ -0,0 +1,320 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+"use strict";
+
+var Services = require("Services");
+var EventEmitter = require("devtools/shared/event-emitter");
+var Telemetry = require("devtools/client/shared/telemetry");
+var { Task } = require("devtools/shared/task");
+var { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
+
+/**
+ * This object represents replacement for ToolSidebar
+ * implemented in devtools/client/framework/sidebar.js module
+ *
+ * This new component is part of devtools.html aimed at
+ * removing XUL and use HTML for entire DevTools UI.
+ * There are currently two implementation of the side bar since
+ * the `sidebar.js` module (mentioned above) is still used by
+ * other panels.
+ * As soon as all panels are using this HTML based
+ * implementation it can be removed.
+ */
+function ToolSidebar(tabbox, panel, uid, options = {}) {
+  EventEmitter.decorate(this);
+
+  this._tabbox = tabbox;
+  this._uid = uid;
+  this._panelDoc = this._tabbox.ownerDocument;
+  this._toolPanel = panel;
+  this._options = options;
+
+  if (!options.disableTelemetry) {
+    this._telemetry = new Telemetry();
+  }
+
+  this._tabs = [];
+
+  if (this._options.hideTabstripe) {
+    this._tabbox.setAttribute("hidetabs", "true");
+  }
+
+  this.render();
+
+  this._toolPanel.emit("sidebar-created", this);
+}
+
+exports.ToolSidebar = ToolSidebar;
+
+ToolSidebar.prototype = {
+  TABPANEL_ID_PREFIX: "sidebar-panel-",
+
+  // React
+
+  get React() {
+    return this._toolPanel.React;
+  },
+
+  get ReactDOM() {
+    return this._toolPanel.ReactDOM;
+  },
+
+  get browserRequire() {
+    return this._toolPanel.browserRequire;
+  },
+
+  get InspectorTabPanel() {
+    if (!this._InspectorTabPanel) {
+      this._InspectorTabPanel =
+        this.React.createFactory(this.browserRequire(
+        "devtools/client/inspector/components/inspector-tab-panel"));
+    }
+    return this._InspectorTabPanel;
+  },
+
+  // Rendering
+
+  render: function () {
+    let Tabbar = this.React.createFactory(this.browserRequire(
+      "devtools/client/shared/components/tabs/tabbar"));
+
+    let sidebar = Tabbar({
+      onSelect: this.handleSelectionChange.bind(this),
+    });
+
+    this._tabbar = this.ReactDOM.render(sidebar, this._tabbox);
+  },
+
+  addExistingTab: function (id, title, selected) {
+    this._tabbar.addTab(id, title, selected, this.InspectorTabPanel);
+
+    this.emit("new-tab-registered", id);
+  },
+
+  /**
+   * Register a tab. A tab is a document.
+   * The document must have a title, which will be used as the name of the tab.
+   *
+   * @param {string} tab uniq id
+   * @param {string} url
+   */
+  addFrameTab: function (id, title, url, selected) {
+    let panel = this.InspectorTabPanel({
+      id: id,
+      key: id,
+      title: title,
+      url: url,
+      onMount: this.onSidePanelMounted.bind(this),
+    });
+
+    this._tabbar.addTab(id, title, selected, panel);
+
+    this.emit("new-tab-registered", id);
+  },
+
+  onSidePanelMounted: function (content, props) {
+    let iframe = content.querySelector("iframe");
+    if (!iframe || iframe.getAttribute("src")) {
+      return;
+    }
+
+    let onIFrameLoaded = (event) => {
+      iframe.removeEventListener("load", onIFrameLoaded, true);
+
+      let doc = event.target;
+      let win = doc.defaultView;
+      if ("setPanel" in win) {
+        win.setPanel(this._toolPanel, iframe);
+      }
+      this.emit(props.id + "-ready");
+    };
+
+    iframe.addEventListener("load", onIFrameLoaded, true);
+    iframe.setAttribute("src", props.url);
+  },
+
+  /**
+   * Remove an existing tab.
+   * @param {String} tabId The ID of the tab that was used to register it, or
+   * the tab id attribute value if the tab existed before the sidebar
+   * got created.
+   * @param {String} tabPanelId Optional. If provided, this ID will be used
+   * instead of the tabId to retrieve and remove the corresponding <tabpanel>
+   */
+  removeTab: Task.async(function* (tabId, tabPanelId) {
+    this._tabbar.removeTab(tabId);
+
+    let win = this.getWindowForTab(tabId);
+    if (win && ("destroy" in win)) {
+      yield win.destroy();
+    }
+
+    this.emit("tab-unregistered", tabId);
+  }),
+
+  /**
+   * Show or hide a specific tab.
+   * @param {Boolean} isVisible True to show the tab/tabpanel, False to hide it.
+   * @param {String} id The ID of the tab to be hidden.
+   */
+  toggleTab: function (isVisible, id) {
+    this._tabbar.toggleTab(id, isVisible);
+  },
+
+  /**
+   * Select a specific tab.
+   */
+  select: function (id) {
+    this._tabbar.select(id);
+  },
+
+  /**
+   * Return the id of the selected tab.
+   */
+  getCurrentTabID: function () {
+    return this._currentTool;
+  },
+
+  /**
+   * Returns the requested tab panel based on the id.
+   * @param {String} id
+   * @return {DOMNode}
+   */
+  getTabPanel: function (id) {
+    // Search with and without the ID prefix as there might have been existing
+    // tabpanels by the time the sidebar got created
+    return this._panelDoc.querySelector("#" +
+      this.TABPANEL_ID_PREFIX + id + ", #" + id);
+  },
+
+  /**
+   * Event handler.
+   */
+  handleSelectionChange: function (id) {
+    if (this._destroyed) {
+      return;
+    }
+
+    let previousTool = this._currentTool;
+    if (previousTool) {
+      if (this._telemetry) {
+        this._telemetry.toolClosed(previousTool);
+      }
+      this.emit(previousTool + "-unselected");
+    }
+
+    this._currentTool = id;
+
+    if (this._telemetry) {
+      this._telemetry.toolOpened(this._currentTool);
+    }
+
+    this.emit(this._currentTool + "-selected");
+    this.emit("select", this._currentTool);
+  },
+
+  /**
+   * Show the sidebar.
+   *
+   * @param  {String} id
+   *         The sidebar tab id to select.
+   */
+  show: function (id) {
+    this._tabbox.removeAttribute("hidden");
+
+    // If an id is given, select the corresponding sidebar tab and record the
+    // tool opened.
+    if (id) {
+      this._currentTool = id;
+
+      if (this._telemetry) {
+        this._telemetry.toolOpened(this._currentTool);
+      }
+    }
+
+    this.emit("show");
+  },
+
+  /**
+   * Show the sidebar.
+   */
+  hide: function () {
+    this._tabbox.setAttribute("hidden", "true");
+
+    this.emit("hide");
+  },
+
+  /**
+   * Return the window containing the tab content.
+   */
+  getWindowForTab: function (id) {
+    // Get the tabpanel and make sure it contains an iframe
+    let panel = this.getTabPanel(id);
+    if (!panel || !panel.firstElementChild || !panel.firstElementChild.contentWindow) {
+      return null;
+    }
+
+    return panel.firstElementChild.contentWindow;
+  },
+
+  /**
+   * Clean-up.
+   */
+  destroy: Task.async(function* () {
+    if (this._destroyed) {
+      return;
+    }
+    this._destroyed = true;
+
+    this.emit("destroy");
+
+    // Note that we check for the existence of this._tabbox.tabpanels at each
+    // step as the container window may have been closed by the time one of the
+    // panel's destroy promise resolves.
+    let tabpanels = [...this._tabbox.querySelectorAll(".tab-panel-box")];
+    for (let panel of tabpanels) {
+      let iframe = panel.querySelector("iframe");
+      if (!iframe) {
+        continue;
+      }
+      let win = iframe.contentWindow;
+      if (win && ("destroy" in win)) {
+        yield win.destroy();
+      }
+      panel.remove();
+    }
+
+    if (this._currentTool && this._telemetry) {
+      this._telemetry.toolClosed(this._currentTool);
+    }
+
+    this._toolPanel.emit("sidebar-destroyed", this);
+
+    this._tabs = null;
+    this._tabbox = null;
+    this._panelDoc = null;
+    this._toolPanel = null;
+  })
+};
+
+XPCOMUtils.defineLazyGetter(this, "l10n", function () {
+  let bundle = Services.strings.createBundle(
+    "chrome://devtools/locale/toolbox.properties");
+
+  let l10n = function (name, ...args) {
+    try {
+      if (args.length == 0) {
+        return bundle.GetStringFromName(name);
+      }
+      return bundle.formatStringFromName(name, args, args.length);
+    } catch (err) {
+      console.error(err);
+    }
+    return null;
+  };
+  return l10n;
+});
--- a/devtools/client/jsonview/css/main.css
+++ b/devtools/client/jsonview/css/main.css
@@ -37,15 +37,21 @@
 
 /******************************************************************************/
 /* Theme Firebug */
 
 .theme-firebug .panelContent {
   height: calc(100% - 30px);
 }
 
+/* JSON View is using bigger font-size for the main tabs so,
+  let's overwrite the default value. */
+.theme-firebug .tabs .tabs-navigation {
+  font-size: 14px;
+}
+
 /******************************************************************************/
 /* Theme Light & Theme Dark*/
 
 .theme-dark .panelContent,
 .theme-light .panelContent {
   height: calc(100% - 27px);
 }
--- a/devtools/client/jsonview/json-viewer.js
+++ b/devtools/client/jsonview/json-viewer.js
@@ -24,17 +24,17 @@ define(function (require, exports, modul
   }
 
   // Application state object.
   let input = {
     jsonText: json.textContent,
     jsonPretty: null,
     json: jsonData,
     headers: JSON.parse(headers.textContent),
-    tabActive: 1,
+    tabActive: 0,
     prettified: false
   };
 
   json.remove();
   headers.remove();
 
   /**
    * Application actions/commands. This list implements all commands
--- a/devtools/client/locales/en-US/font-inspector.dtd
+++ b/devtools/client/locales/en-US/font-inspector.dtd
@@ -1,16 +1,15 @@
 <!-- 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/. -->
 
 <!-- LOCALIZATION NOTE : FILE This file contains the Font Inspector strings.
   - The Font Inspector is the panel accessible in the Inspector sidebar. -->
 
-<!ENTITY fontInspectorTitle "Fonts">
 <!ENTITY showAllFonts "See all the fonts used in the page">
 <!ENTITY usedAs "Used as: ">
 <!ENTITY system "system">
 <!ENTITY remote "remote">
 
 <!-- LOCALIZATION NOTE (previewHint): This is the label shown as the
      placeholder in font inspector preview text box. -->
 <!ENTITY previewHint "Preview Text">
--- a/devtools/client/locales/en-US/inspector.properties
+++ b/devtools/client/locales/en-US/inspector.properties
@@ -327,8 +327,35 @@ markupView.hide.key=h
 # LOCALIZATION NOTE (markupView.edit.key):
 # Key shortcut used to hide the selected node in the markup view.
 markupView.edit.key=F2
 
 # LOCALIZATION NOTE (markupView.scrollInto.key):
 # Key shortcut used to scroll the webpage in order to ensure the selected node
 # is visible
 markupView.scrollInto.key=s
+
+# LOCALIZATION NOTE (inspector.sidebar.fontInspectorTitle):
+# This is the title shown in a tab in the side panel of the Inspector panel
+# that corresponds to the tool displaying the list of fonts used in the page.
+inspector.sidebar.fontInspectorTitle=Fonts
+
+# LOCALIZATION NOTE (inspector.sidebar.ruleViewTitle):
+# This is the title shown in a tab in the side panel of the Inspector panel
+# that corresponds to the tool displaying the list of CSS rules used
+# in the page.
+inspector.sidebar.ruleViewTitle=Rules
+
+# LOCALIZATION NOTE (inspector.sidebar.computedViewTitle):
+# This is the title shown in a tab in the side panel of the Inspector panel
+# that corresponds to the tool displaying the list of computed CSS values
+# used in the page.
+inspector.sidebar.computedViewTitle=Computed
+
+# LOCALIZATION NOTE (inspector.sidebar.layoutViewTitle):
+# This is the title shown in a tab in the side panel of the Inspector panel
+# that corresponds to the tool displaying box model of the selected element.
+inspector.sidebar.layoutViewTitle=Box Model
+
+# LOCALIZATION NOTE (inspector.sidebar.animationInspectorTitle):
+# This is the title shown in a tab in the side panel of the Inspector panel
+# that corresponds to the tool displaying animations defined in the page.
+inspector.sidebar.animationInspectorTitle=Animations
--- a/devtools/client/locales/en-US/layoutview.dtd
+++ b/devtools/client/locales/en-US/layoutview.dtd
@@ -11,17 +11,16 @@
   - You want to make that choice consistent across the developer tools.
   - A good criteria is the language in which you'd find the best
   - documentation on web development on the web. -->
 
 <!-- LOCALIZATION NOTE (*.tooltip): These tooltips are not regular tooltips.
   -  The text appears on the bottom right corner of the layout view when
   -  the corresponding box is hovered. -->
 
-<!ENTITY layoutViewTitle          "Box Model">
 <!ENTITY margin.tooltip           "margin">
 <!ENTITY border.tooltip           "border">
 <!ENTITY padding.tooltip          "padding">
 <!ENTITY content.tooltip          "content">
 
 <!-- LOCALIZATION NOTE: This label is displayed as a tooltip that appears when
   -  hovering over the button that allows users to edit the position of an
   -  element in the page. -->
--- a/devtools/client/locales/en-US/styleinspector.dtd
+++ b/devtools/client/locales/en-US/styleinspector.dtd
@@ -31,12 +31,8 @@
   -  shown when hovering over the `Toggle Pseudo Class Panel` button in the
   -  rule view toolbar. -->
 <!ENTITY togglePseudoClassPanel  "Toggle pseudo-classes">
 
 <!-- LOCALIZATION NOTE (noPropertiesFound): In the case where there are no CSS
   -  properties to display e.g. due to search criteria this message is
   -  displayed. -->
 <!ENTITY noPropertiesFound     "No CSS properties found.">
-
-<!-- FIXME: notes -->
-<!ENTITY computedViewTitle     "Computed">
-<!ENTITY ruleViewTitle         "Rules">
--- a/devtools/client/shared/components/tabs/moz.build
+++ b/devtools/client/shared/components/tabs/moz.build
@@ -1,10 +1,12 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 DevToolsModules(
+    'tabbar.css',
+    'tabbar.js',
     'tabs.css',
     'tabs.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/tabs/tabbar.css
@@ -0,0 +1,54 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+.tabs .tabs-navigation {
+  line-height: 15px;
+}
+
+.tabs .tabs-navigation {
+  height: 23px;
+}
+
+.tabs .tabs-menu-item:first-child {
+  border-inline-start-width: 0;
+}
+
+.tabs .tabs-navigation .tabs-menu-item:focus {
+  outline: var(--theme-focus-outline);
+  outline-offset: -2px;
+}
+
+.tabs .tabs-menu-item.is-active {
+  height: 23px;
+}
+
+/* Firebug theme is using slightly different height. */
+.theme-firebug .tabs .tabs-navigation {
+  height: 24px;
+}
+
+.tabs .tabs-menu-item a {
+  cursor: default;
+}
+
+/* The tab takes entire horizontal space and individual tabs
+  should stretch accordingly. Use flexbox for the behavior. */
+.tabs .tabs-navigation .tabs-menu {
+  display: flex;
+}
+
+.tabs .tabs-navigation .tabs-menu-item {
+  flex-grow: 1;
+}
+
+.tabs .tabs-navigation .tabs-menu-item a {
+  text-align: center;
+}
+
+/* Firebug theme doesn't stretch the tabs. */
+.theme-firebug .tabs .tabs-navigation .tabs-menu {
+  display: block;
+}
+
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/tabs/tabbar.js
@@ -0,0 +1,160 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+"use strict";
+
+const { DOM, createClass, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
+const Tabs = createFactory(require("devtools/client/shared/components/tabs/tabs").Tabs);
+
+// Shortcuts
+const { div } = DOM;
+
+/**
+ * Renders Tabbar component.
+ */
+let Tabbar = createClass({
+  displayName: "Tabbar",
+
+  propTypes: {
+    onSelect: PropTypes.func,
+  },
+
+  getInitialState: function () {
+    return {
+      tabs: [],
+      activeTab: 0
+    };
+  },
+
+  // Public API
+
+  addTab: function (id, title, selected = false, panel, url) {
+    let tabs = this.state.tabs.slice();
+    tabs.push({id, title, panel, url});
+
+    let newState = Object.assign({}, this.state, {
+      tabs: tabs,
+    });
+
+    if (selected) {
+      newState.activeTab = tabs.length - 1;
+    }
+
+    this.setState(newState);
+  },
+
+  toggleTab: function (tabId, isVisible) {
+    let index = this.getTabIndex(tabId);
+    if (index < 0) {
+      return;
+    }
+
+    let tabs = this.state.tabs.slice();
+    tabs[index] = Object.assign({}, tabs[index], {
+      isVisible: isVisible
+    });
+
+    this.setState(Object.assign({}, this.state, {
+      tabs: tabs,
+    }));
+  },
+
+  removeTab: function (tabId) {
+    let index = this.getTabIndex(tabId);
+    if (index < 0) {
+      return;
+    }
+
+    let tabs = this.state.tabs.slice();
+    tabs.splice(index, 1);
+
+    this.setState(Object.assign({}, this.state, {
+      tabs: tabs,
+    }));
+  },
+
+  select: function (tabId) {
+    let index = this.getTabIndex(tabId);
+    if (index < 0) {
+      return;
+    }
+
+    let newState = Object.assign({}, this.state, {
+      activeTab: index,
+    });
+
+    this.setState(newState, () => {
+      if (this.props.onSelect) {
+        this.props.onSelect(tabId);
+      }
+    });
+  },
+
+  // Helpers
+
+  getTabIndex: function (tabId) {
+    let tabIndex = -1;
+    this.state.tabs.forEach((tab, index) => {
+      if (tab.id == tabId) {
+        tabIndex = index;
+      }
+    });
+    return tabIndex;
+  },
+
+  getTabId: function (index) {
+    return this.state.tabs[index].id;
+  },
+
+  getCurrentTabId: function () {
+    return this.state.tabs[this.state.activeTab].id;
+  },
+
+  // Event Handlers
+
+  onTabChanged: function (index) {
+    this.setState({
+      activeTab: index
+    });
+
+    if (this.props.onSelect) {
+      this.props.onSelect(this.state.tabs[index].id);
+    }
+  },
+
+  // Rendering
+
+  renderTab: function (tab) {
+    if (typeof tab.panel === "function") {
+      return tab.panel({
+        key: tab.id,
+        title: tab.title,
+        id: tab.id,
+        url: tab.url,
+      });
+    }
+
+    return tab.panel;
+  },
+
+  render: function () {
+    let tabs = this.state.tabs.map(tab => {
+      return this.renderTab(tab);
+    });
+
+    return (
+      div({className: "devtools-sidebar-tabs"},
+        Tabs({
+          tabActive: this.state.activeTab,
+          onAfterChange: this.onTabChanged},
+          tabs
+        )
+      )
+    );
+  },
+});
+
+module.exports = Tabbar;
--- a/devtools/client/shared/components/tabs/tabs.css
+++ b/devtools/client/shared/components/tabs/tabs.css
@@ -1,124 +1,65 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* 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/. */
 
-/******************************************************************************/
 /* Tabs General Styles */
 
 .tabs {
   height: 100%;
 }
 
-.tabs .tabs-navigation {
-  font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
-  font-size: 14px;
-  border-bottom-color: rgb(170, 188, 207);
-}
-
 .tabs .tabs-menu {
   display: table;
   list-style: none;
   padding: 0;
   margin: 0;
 }
 
 .tabs .tabs-menu-item {
   float: left;
 }
 
 .tabs .tabs-menu-item a {
   display: block;
   color: #A9A9A9;
   padding: 4px 8px;
-}
-
-.tabs .tabs-menu-item a {
   border: 1px solid transparent;
   text-decoration: none;
+  white-space: nowrap;
 }
 
-.tabs .tab-panel {
-  background-color: white;
+/* Make sure panel content takes entire vertical space.
+  (minus the height of the tab bar) */
+.tabs .panels {
+  height: calc(100% - 24px);
 }
 
-.tabs .tab-panel > DIV,
-.tabs .tab-panel > DIV > DIV {
+.tabs .tab-panel-box,
+.tabs .tab-panel {
   height: 100%;
 }
 
-/******************************************************************************/
-/* Firebug Theme */
-
-.theme-firebug .tabs {
-  background-color: rgb(219, 234, 249);
-  background-image: linear-gradient(rgba(253, 253, 253, 0.2), rgba(253, 253, 253, 0));
-}
-
-.theme-firebug .tabs .tabs-navigation {
-  padding-top: 3px;
-  padding-left: 3px;
-  height: 27px;
-  border-bottom: 1px solid rgb(170, 188, 207);
-}
-
-.theme-firebug .tabs .tab-panel {
-  height: calc(100% - 31px); /* minus the height of the tab bar */
-}
-
-.theme-firebug .tabs .tabs-menu-item {
-  position: relative;
-}
-
-.theme-firebug .tabs .tabs-menu-item a {
-  padding: 5px 8px 4px 8px;;
-  font-weight: bold;
-  color: #565656;
-  border-radius: 4px 4px 0 0;
-}
-
-.theme-firebug .tabs .tabs-menu-item.is-active a {
-  background-color: rgb(247, 251, 254);
-  border: 1px solid rgb(170, 188, 207);
-  border-bottom-color: transparent;
-}
-
-.theme-firebug .tabs .tabs-menu-item:hover a {
-  border: 1px solid #C8C8C8;
-  border-bottom: 1px solid transparent;
-  background-color: transparent !important;
-}
-
-.theme-firebug .tabs .tabs-menu-item.is-active:hover a {
-  border: 1px solid rgb(170, 188, 207) !important;
-  background-color: rgb(247, 251, 254) !important;
-  border-bottom-color: transparent !important;
-}
-
-/******************************************************************************/
 /* Light Theme */
 
 .theme-dark .tabs,
 .theme-light .tabs {
   background: var(--theme-tab-toolbar-background);
 }
 
 .theme-dark .tabs .tabs-navigation,
 .theme-light .tabs .tabs-navigation {
   border-bottom: 1px solid var(--theme-splitter-color);
-  box-shadow: 0 -2px 0 rgba(170, 170, 170,.1) inset;
-  height: 24px;
   font-size: 12px;
 }
 
-.theme-dark .tabs .tab-panel,
-.theme-light .tabs .tab-panel {
-  height: calc(100% - 24px); /* minus the height of the tab bar */
+.theme-firebug .tabs .tabs-navigation {
+  font-size: 11px;
 }
 
 .theme-dark .tabs .tabs-menu-item,
 .theme-light .tabs .tabs-menu-item {
   margin: 0;
   padding: 0;
   border-style: solid;
   border-width: 0;
@@ -130,19 +71,17 @@
 .theme-light .tabs .tabs-menu-item a {
   color: var(--theme-content-color1);
 }
 
 .theme-dark .tabs .tabs-menu-item a:hover,
 .theme-dark .tabs .tabs-menu-item a,
 .theme-light .tabs .tabs-menu-item a:hover,
 .theme-light .tabs .tabs-menu-item a {
-  border: none !important;
-  background-color: transparent !important;
-  padding: 5px 15px;
+  padding: 3px 15px;
 }
 
 .theme-dark .tabs .tabs-menu-item:hover,
 .theme-light .tabs .tabs-menu-item:hover {
   background-color: var(--toolbar-tab-hover);
 }
 
 .theme-dark .tabs .tabs-menu-item.is-active,
@@ -154,51 +93,72 @@
 
 .theme-dark .tabs .tabs-menu-item.is-active a,
 .theme-dark .tabs .tabs-menu-item.is-active:hover a,
 .theme-light .tabs .tabs-menu-item.is-active a,
 .theme-light .tabs .tabs-menu-item.is-active:hover a {
   color: var(--theme-selection-color);
 }
 
-.theme-dark .tabs .tabs-menu-item:active:hover,
-.theme-light .tabs .tabs-menu-item:active:hover {
-  background-color: var(--toolbar-tab-hover-active);
-}
-
-.theme-dark .tabs .tabs-menu-item.is-active,
-.theme-light .tabs .tabs-menu-item.is-active {
-  box-shadow: 0 2px 0 #d7f1ff inset,
-              0 8px 3px -5px #2b82bf inset,
-              0 -2px 0 rgba(0,0,0,.06) inset;
-}
-
-/******************************************************************************/
 /* Dark Theme */
 
-.theme-dark .tabs .tabs-navigation {
-  box-shadow: 0px -2px 0px rgba(0, 0, 0, 0.1) inset;
-}
-
 .theme-dark .tabs .tabs-menu-item a {
   color: #CED3D9;
 }
 
-.theme-dark .tabs .tabs-menu-item a:hover,
-.theme-dark .tabs .tabs-menu-item a {
-  border: none !important;
-  background-color: transparent !important;
-  padding: 5px 15px;
-}
-
 .theme-dark .tabs .tabs-menu-item:active:hover {
   background-color: hsla(206, 37%, 4%, .4); /* --toolbar-tab-hover-active */
 }
 
-.theme-dark .tabs .tabs-menu-item.is-active {
-  box-shadow: 0px 2px 0px #D7F1FF inset,
-   0px 8px 3px -5px #2B82BF inset,
-   0px -2px 0px rgba(0, 0, 0, 0.2) inset;
+/* Firebug Theme */
+
+.theme-firebug .tabs {
+  background-color: rgb(219, 234, 249);
+  background-image: linear-gradient(rgba(253, 253, 253, 0.2), rgba(253, 253, 253, 0));
+}
+
+.theme-firebug .tabs .tabs-navigation {
+  padding-top: 3px;
+  padding-left: 3px;
+  height: 27px;
+  border-bottom: 1px solid rgb(170, 188, 207);
+}
+
+.theme-firebug .tabs .tabs-menu-item.is-active,
+.theme-firebug .tabs .tabs-menu-item.is-active:hover {
+  background-color: transparent;
+}
+
+.theme-firebug .tabs .tabs-menu-item {
+  position: relative;
+  border-inline-start-width: 0;
 }
 
-.theme-dark .tabs .tab-panel {
-  background-color: var(--theme-body-background);
+.theme-firebug .tabs .tabs-menu-item a {
+  font-family: var(--proportional-font-family);
+  font-weight: bold;
+  color: var(--theme-body-color);
+  border-radius: 4px 4px 0 0;
+}
+
+.theme-firebug .tabs .tabs-menu-item:hover a {
+  border: 1px solid #C8C8C8;
+  border-bottom: 1px solid transparent;
+  background-color: transparent;
 }
+
+.theme-firebug .tabs .tabs-menu-item.is-active a,
+.theme-firebug .tabs .tabs-menu-item.is-active:hover a {
+  background-color: rgb(247, 251, 254);
+  border: 1px solid rgb(170, 188, 207);
+  border-bottom-color: transparent;
+  color: var(--theme-body-color);
+}
+
+.theme-firebug .tabs .tabs-menu-item a:hover,
+.theme-firebug .tabs .tabs-menu-item a {
+  border: 1px solid transparent;
+}
+
+.theme-firebug .tabs .tabs-menu-item a:hover,
+.theme-firebug .tabs .tabs-menu-item a {
+  padding: 4px 8px 4px 8px;
+}
--- a/devtools/client/shared/components/tabs/tabs.js
+++ b/devtools/client/shared/components/tabs/tabs.js
@@ -3,36 +3,37 @@
 /* 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/. */
 
 "use strict";
 
 define(function (require, exports, module) {
   const React = require("devtools/client/shared/vendor/react");
-  const DOM = React.DOM;
+  const { DOM } = React;
+  const { findDOMNode } = require("devtools/client/shared/vendor/react-dom");
 
   /**
    * Renders simple 'tab' widget.
    *
    * Based on ReactSimpleTabs component
    * https://github.com/pedronauck/react-simpletabs
    *
    * Component markup (+CSS) example:
    *
    * <div class='tabs'>
    *  <nav class='tabs-navigation'>
    *    <ul class='tabs-menu'>
    *      <li class='tabs-menu-item is-active'>Tab #1</li>
    *      <li class='tabs-menu-item'>Tab #2</li>
    *    </ul>
    *  </nav>
-   *  <article class='tab-panel'>
+   *  <div class='tab-panel'>
    *    The content of active panel here
-   *  </article>
+   *  </div>
    * <div>
    */
   let Tabs = React.createClass({
     displayName: "Tabs",
 
     propTypes: {
       className: React.PropTypes.oneOfType([
         React.PropTypes.array,
@@ -46,124 +47,236 @@ define(function (require, exports, modul
       children: React.PropTypes.oneOfType([
         React.PropTypes.array,
         React.PropTypes.element
       ]).isRequired
     },
 
     getDefaultProps: function () {
       return {
-        tabActive: 1
+        tabActive: 0
       };
     },
 
     getInitialState: function () {
       return {
-        tabActive: this.props.tabActive
+        tabActive: this.props.tabActive,
+
+        // This array is used to store an information whether a tab
+        // at specific index has already been created (e.g. selected
+        // at least once).
+        // If yes, it's rendered even if not currently selected.
+        // This is because in some cases we don't want to re-create
+        // tab content when it's being unselected/selected.
+        // E.g. in case of an iframe being used as a tab-content
+        // we want the iframe to stay in the DOM.
+        created: [],
       };
     },
 
     componentDidMount: function () {
+      let node = findDOMNode(this);
+      node.addEventListener("keydown", this.onKeyDown, false);
+
       let index = this.state.tabActive;
       if (this.props.onMount) {
         this.props.onMount(index);
       }
     },
 
     componentWillReceiveProps: function (newProps) {
       if (newProps.tabActive) {
-        this.setState({tabActive: newProps.tabActive});
+        let created = [...this.state.created];
+        created[newProps.tabActive] = true;
+
+        this.setState(Object.assign({}, this.state, {
+          tabActive: newProps.tabActive,
+          created: created,
+        }));
       }
     },
 
-    setActive: function (index, e) {
+    componentWillUnmount: function () {
+      let node = findDOMNode(this);
+      node.removeEventListener("keydown", this.onKeyDown, false);
+    },
+
+    // DOM Events
+
+    onKeyDown: function (event) {
+      // Bail out if the focus isn't on a tab.
+      if (!event.target.closest(".tabs-menu-item")) {
+        return;
+      }
+
+      let tabActive = this.state.tabActive;
+      let tabCount = this.props.children.length;
+
+      switch (event.code) {
+        case "ArrowRight":
+          tabActive = Math.min(tabCount - 1, tabActive + 1);
+          break;
+        case "ArrowLeft":
+          tabActive = Math.max(0, tabActive - 1);
+          break;
+      }
+
+      if (this.state.tabActive != tabActive) {
+        this.setActive(tabActive);
+      }
+    },
+
+    onClickTab: function (index, event) {
+      this.setActive(index);
+      event.preventDefault();
+    },
+
+    // API
+
+    setActive: function (index) {
       let onAfterChange = this.props.onAfterChange;
       let onBeforeChange = this.props.onBeforeChange;
 
       if (onBeforeChange) {
         let cancel = onBeforeChange(index);
         if (cancel) {
           return;
         }
       }
 
-      let newState = {
-        tabActive: index
-      };
+      let created = [...this.state.created];
+      created[index] = true;
+
+      let newState = Object.assign({}, this.state, {
+        tabActive: index,
+        created: created
+      });
 
       this.setState(newState, () => {
+        // Properly set focus on selected tab.
+        let node = findDOMNode(this);
+        let selectedTab = node.querySelector(".is-active > a");
+        if (selectedTab) {
+          selectedTab.focus();
+        }
+
         if (onAfterChange) {
           onAfterChange(index);
         }
       });
-
-      e.preventDefault();
     },
 
-    getMenuItems: function () {
+    // Rendering
+
+    renderMenuItems: function () {
       if (!this.props.children) {
-        throw new Error("Tabs must contain at least one Panel");
+        throw new Error("There must be at least one Tab");
       }
 
       if (!Array.isArray(this.props.children)) {
         this.props.children = [this.props.children];
       }
 
-      let menuItems = this.props.children
-        .map(function (panel) {
-          return typeof panel === "function" ? panel() : panel;
-        }).filter(function (panel) {
-          return panel;
-        }).map(function (panel, index) {
-          let ref = ("tab-menu-" + (index + 1));
-          let title = panel.props.title;
-          let tabClassName = panel.props.className;
+      let tabs = this.props.children
+        .map(tab => {
+          return typeof tab === "function" ? tab() : tab;
+        }).filter(tab => {
+          return tab;
+        }).map((tab, index) => {
+          let ref = ("tab-menu-" + index);
+          let title = tab.props.title;
+          let tabClassName = tab.props.className;
 
           let classes = [
             "tabs-menu-item",
             tabClassName,
-            this.state.tabActive === (index + 1) && "is-active"
+            this.state.tabActive === index ? "is-active" : ""
           ].join(" ");
 
+          // Set tabindex to -1 (except the selected tab) so, it's focusable,
+          // but not reachable via sequential tab-key navigation.
+          // Changing selected tab (and so, moving focus) is done through
+          // left and right arrow keys.
+          // See also `onKeyDown()` event handler.
           return (
-            DOM.li({ref: ref, key: index, className: classes},
-              DOM.a({href: "#", onClick: this.setActive.bind(this, index + 1)},
+            DOM.li({
+              ref: ref,
+              key: index,
+              className: classes},
+              DOM.a({
+                href: "#",
+                tabIndex: this.state.tabActive === index ? 0 : -1,
+                onClick: this.onClickTab.bind(this, index)},
                 title
               )
             )
           );
-        }.bind(this));
+        });
 
       return (
         DOM.nav({className: "tabs-navigation"},
           DOM.ul({className: "tabs-menu"},
-            menuItems
+            tabs
           )
         )
       );
     },
 
-    getSelectedPanel: function () {
-      let index = this.state.tabActive - 1;
-      let panel = this.props.children[index];
+    renderPanels: function () {
+      if (!this.props.children) {
+        throw new Error("There must be at least one Tab");
+      }
+
+      if (!Array.isArray(this.props.children)) {
+        this.props.children = [this.props.children];
+      }
+
+      let selectedIndex = this.state.tabActive;
+
+      let panels = this.props.children
+        .map(tab => {
+          return typeof tab === "function" ? tab() : tab;
+        }).filter(tab => {
+          return tab;
+        }).map((tab, index) => {
+          let selected = selectedIndex == index;
+
+          // Use 'visibility:hidden' + 'width/height:0' for hiding
+          // content of non-selected tab. It's faster (not sure why)
+          // than display:none and visibility:collapse.
+          let style = {
+            visibility: selected ? "visible" : "hidden",
+            height: selected ? "100%" : "0",
+            width: selected ? "100%" : "0",
+          };
+
+          return (
+            DOM.div({
+              key: index,
+              style: style,
+              className: "tab-panel-box"},
+              (selected || this.state.created[index]) ? tab : null
+            )
+          );
+        });
 
       return (
-        DOM.article({ref: "tab-panel", className: "tab-panel"},
-          panel
+        DOM.div({className: "panels"},
+          panels
         )
       );
     },
 
     render: function () {
       let classNames = ["tabs", this.props.className].join(" ");
 
       return (
         DOM.div({className: classNames},
-          this.getMenuItems(),
-          this.getSelectedPanel()
+          this.renderMenuItems(),
+          this.renderPanels()
         )
       );
     },
   });
 
   /**
    * Renders simple tab 'panel'.
    */
@@ -174,17 +287,17 @@ define(function (require, exports, modul
       title: React.PropTypes.string.isRequired,
       children: React.PropTypes.oneOfType([
         React.PropTypes.array,
         React.PropTypes.element
       ]).isRequired
     },
 
     render: function () {
-      return DOM.div({},
+      return DOM.div({className: "tab-panel"},
         this.props.children
       );
     }
   });
 
   // Exports from this module
   exports.TabPanel = Panel;
   exports.Tabs = Tabs;
--- a/devtools/client/themes/animationinspector.css
+++ b/devtools/client/themes/animationinspector.css
@@ -100,16 +100,21 @@ body {
 }
 
 [timeline] #timeline-toolbar {
   display: flex;
 }
 
 /* The main animations container */
 
+#sidebar-panel-animationinspector {
+  height: 100%;
+  width: 100%;
+}
+
 #players {
   height: calc(100% - var(--toolbar-height));
   overflow-x: hidden;
   overflow-y: auto;
 }
 
 [empty] #players {
   display: none;
--- a/devtools/client/themes/computed.css
+++ b/devtools/client/themes/computed.css
@@ -3,35 +3,44 @@
  * 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/. */
 
 #sidebar-panel-computedview {
   margin: 0;
   display : flex;
   flex-direction: column;
   width: 100%;
-  /* Bug 1243598 - Reduce the container height by the tab height to make room
-     for the tabs above. */
-  height: calc(100% - 24px);
-  position: absolute;
+  height: 100%;
 }
 
 #sidebar-panel-computedview > .devtools-toolbar {
   display: flex;
 }
 
 #browser-style-checkbox {
   /* Bug 1200073 - extra space before the browser styles checkbox so
-     they aren't squished together in a small window. */
+     they aren't squished together in a small window. Put also
+     an extra space after. */
   margin-inline-start: 5px;
+  margin-inline-end: 5px;
+}
+
+#browser-style-checkbox-label {
+  margin-right: 5px;
+
+  /* Vertically center the 'Browser styles' checkbox in the
+     Computed panel with its label. */
+  display: flex;
+  align-items: center;
 }
 
 #propertyContainer {
   -moz-user-select: text;
-  overflow: auto;
+  overflow-y: auto;
+  overflow-x: hidden;
   flex: auto;
 }
 
 .row-striped {
   background: var(--theme-body-background);
 }
 
 .property-view-hidden,
--- a/devtools/client/themes/fonts.css
+++ b/devtools/client/themes/fonts.css
@@ -3,20 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #sidebar-panel-fontinspector {
   margin: 0;
   display: flex;
   flex-direction: column;
   padding-bottom: 20px;
   width: 100%;
-  /* Bug 1243598 - Reduce the container height by the tab height to make room
-     for the tabs above. */
-  height: calc(100% - 24px);
-  position: absolute;
+  height: 100%;
 }
 
 #sidebar-panel-fontinspector > .devtools-toolbar {
   display: flex;
 }
 
 #font-container {
   overflow: auto;
--- a/devtools/client/themes/layout.css
+++ b/devtools/client/themes/layout.css
@@ -1,15 +1,16 @@
 /* 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/ */
 
 #sidebar-panel-layoutview {
   display: block;
   overflow: auto;
+  height: 100%;
 }
 
 #layout-wrapper {
   /* The sidebar-panel is not focusable, this wrapper will catch click events in
      all the empty area around the layout-container */
   height: 100%;
 }
 
--- a/devtools/client/themes/rules.css
+++ b/devtools/client/themes/rules.css
@@ -27,20 +27,17 @@
   font-size: 11px;
 }
 
 #sidebar-panel-ruleview {
   margin: 0;
   display: flex;
   flex-direction: column;
   width: 100%;
-  /* Bug 1243598 - Reduce the container height by the tab height to make room
-     for the tabs above. */
-  height: calc(100% - 24px);
-  position: absolute;
+  height: 100%;
 }
 
 /* Rule View Toolbar */
 
 #ruleview-toolbar-container {
   display: flex;
   flex-direction: column;
   height: auto;
@@ -78,16 +75,17 @@
 }
 
 /* Rule View Container */
 
 #ruleview-container {
   -moz-user-select: text;
   overflow: auto;
   flex: auto;
+  height: 100%;
 }
 
 #ruleview-container.non-interactive {
   pointer-events: none;
   visibility: collapse;
   transition: visibility 0.25s;
 }
 
--- a/devtools/client/themes/toolbars.css
+++ b/devtools/client/themes/toolbars.css
@@ -498,16 +498,17 @@
 .devtools-filterinput > .textbox-input-box > .textbox-search-icons > .textbox-search-clear:hover {
   -moz-image-region: rect(0, 32px, 16px, 16px);
 }
 
 /* In-tools sidebar */
 .devtools-sidebar-tabs {
   -moz-appearance: none;
   margin: 0;
+  height: 100%;
 }
 
 .devtools-sidebar-tabs > tabpanels {
   -moz-appearance: none;
   background: transparent;
   padding: 0;
   border: 0;
 }