Bug 1259819 - HTML Sidebar component. r=pbro
authorJan Odvarko <odvarko@gmail.com>
Fri, 15 Jul 2016 09:59:21 +0200
changeset 305218 f249501590e6c8d2b872e4b36018fc5214421c24
parent 305217 e0e3108d38d80bae2aec9355fbca139c03726e6f
child 305219 9125083b8dfeb57a6ac6b16e26a020987e3e7295
push id79518
push usercbook@mozilla.com
push dateSun, 17 Jul 2016 08:09:59 +0000
treeherdermozilla-inbound@711963e8daa3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspbro
bugs1259819
milestone50.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 1259819 - HTML Sidebar component. r=pbro
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_pane-toggle-05.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,42 +1223,49 @@ 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});
+      }
+    };
+
     ViewHelpers.togglePane({
       visible: !isVisible,
       animated: true,
-      delayed: true
-    }, sidePane);
-
-    if (isVisible) {
-      this._sidebarToggle.setState({collapsed: true});
-    } else {
-      this._sidebarToggle.setState({collapsed: false});
-    }
+      delayed: true,
+      callback: onAnimationDone
+    }, sidePaneContainer);
   },
 
   /**
    * Create a new node as the last child of the current selection, expand the
    * parent and select the new node.
    */
   addNode: Task.async(function* () {
     if (!this.canAddHTMLChild()) {
--- 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 minimum 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 explicitly
+        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_pane-toggle-05.js
+++ b/devtools/client/inspector/test/browser_inspector_pane-toggle-05.js
@@ -5,17 +5,17 @@
 
 /**
 * Test the keyboard navigation for the pane toggle using
 * space and enter
 */
 
 add_task(function* () {
   let {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");
 
   yield togglePane(button, "Press on the toggle button", panel, "VK_RETURN");
   ok(panel.classList.contains("pane-collapsed"), "The panel is in collapsed state");
 
   yield togglePane(button, "Press on the toggle button to expand the panel again",
--- 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
@@ -501,16 +501,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;
 }