Bug 1266420 - Implement SidebarToggle component; r=bgrins
authorJan Odvarko <odvarko@gmail.com>
Mon, 04 Jul 2016 16:47:48 +0200
changeset 303691 e27437d7fe35881b13a2f493427cb0e3ba6aeee0
parent 303690 162b6fca7d868e8165d0f6ef80e02b5dd73161cb
child 303692 d28a755c4014f6148f9fc7d168536c4800190c7f
push id79141
push usercbook@mozilla.com
push dateTue, 05 Jul 2016 14:07:42 +0000
treeherdermozilla-inbound@f08c54971dd1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbgrins
bugs1266420
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 1266420 - Implement SidebarToggle component; r=bgrins
devtools/client/debugger/debugger-view.js
devtools/client/debugger/test/mochitest/browser_dbg_instruments-pane-collapse.js
devtools/client/inspector/inspector-panel.js
devtools/client/inspector/inspector.css
devtools/client/inspector/inspector.xul
devtools/client/inspector/test/browser_inspector_pane-toggle-01.js
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/netmonitor/netmonitor-view.js
devtools/client/netmonitor/netmonitor.css
devtools/client/netmonitor/test/browser_net_clear.js
devtools/client/netmonitor/test/browser_net_pane-collapse.js
devtools/client/netmonitor/test/browser_net_pane-toggle.js
devtools/client/projecteditor/lib/projecteditor.js
devtools/client/shared/components/moz.build
devtools/client/shared/components/sidebar-toggle.css
devtools/client/shared/components/sidebar-toggle.js
devtools/client/shared/components/test/mochitest/chrome.ini
devtools/client/shared/components/test/mochitest/test_sidebar_toggle.html
devtools/client/shared/widgets/view-helpers.js
devtools/client/themes/debugger.css
devtools/client/themes/inspector.css
devtools/client/themes/netmonitor.css
devtools/client/themes/webaudioeditor.css
devtools/client/themes/widgets.css
devtools/client/webaudioeditor/views/utils.js
--- a/devtools/client/debugger/debugger-view.js
+++ b/devtools/client/debugger/debugger-view.js
@@ -654,17 +654,17 @@ var DebuggerView = {
     }));
   },
 
   /**
    * Gets the visibility state of the instruments pane.
    * @return boolean
    */
   get instrumentsPaneHidden() {
-    return this._instrumentsPane.hasAttribute("pane-collapsed");
+    return this._instrumentsPane.classList.contains("pane-collapsed");
   },
 
   /**
    * Gets the currently selected tab in the instruments pane.
    * @return string
    */
   get instrumentsPaneTab() {
     return this._instrumentsPane.selectedTab.id;
@@ -684,20 +684,20 @@ var DebuggerView = {
    */
   toggleInstrumentsPane: function (aFlags, aTabIndex) {
     let pane = this._instrumentsPane;
     let button = this._instrumentsPaneToggleButton;
 
     ViewHelpers.togglePane(aFlags, pane);
 
     if (aFlags.visible) {
-      button.removeAttribute("pane-collapsed");
+      button.classList.remove("pane-collapsed");
       button.setAttribute("tooltiptext", this._collapsePaneString);
     } else {
-      button.setAttribute("pane-collapsed", "");
+      button.classList.add("pane-collapsed");
       button.setAttribute("tooltiptext", this._expandPaneString);
     }
 
     if (aTabIndex !== undefined) {
       pane.selectedIndex = aTabIndex;
     }
   },
 
--- a/devtools/client/debugger/test/mochitest/browser_dbg_instruments-pane-collapse.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_instruments-pane-collapse.js
@@ -36,18 +36,18 @@ function test() {
 }
 
 function testPanesState() {
   let instrumentsPane =
     gDebugger.document.getElementById("instruments-pane");
   let instrumentsPaneToggleButton =
     gDebugger.document.getElementById("instruments-pane-toggle");
 
-  ok(instrumentsPane.hasAttribute("pane-collapsed") &&
-     instrumentsPaneToggleButton.hasAttribute("pane-collapsed"),
+  ok(instrumentsPane.classList.contains("pane-collapsed") &&
+     instrumentsPaneToggleButton.classList.contains("pane-collapsed"),
     "The debugger view instruments pane should initially be hidden.");
   is(gPrefs.panesVisibleOnStartup, false,
     "The debugger view instruments pane should initially be preffed as hidden.");
   isnot(gOptions._showPanesOnStartupItem.getAttribute("checked"), "true",
     "The options menu item should not be checked.");
 }
 
 function testInstrumentsPaneCollapse() {
@@ -60,18 +60,18 @@ function testInstrumentsPaneCollapse() {
   is(width, gPrefs.instrumentsWidth,
     "The instruments pane has an incorrect width.");
   is(instrumentsPane.style.marginLeft, "0px",
     "The instruments pane has an incorrect left margin.");
   is(instrumentsPane.style.marginRight, "0px",
     "The instruments pane has an incorrect right margin.");
   ok(!instrumentsPane.hasAttribute("animated"),
     "The instruments pane has an incorrect animated attribute.");
-  ok(!instrumentsPane.hasAttribute("pane-collapsed") &&
-     !instrumentsPaneToggleButton.hasAttribute("pane-collapsed"),
+  ok(!instrumentsPane.classList.contains("pane-collapsed") &&
+     !instrumentsPaneToggleButton.classList.contains("pane-collapsed"),
     "The instruments pane should at this point be visible.");
 
   gDebugger.DebuggerView.toggleInstrumentsPane({ visible: false, animated: true });
 
   is(gPrefs.panesVisibleOnStartup, false,
     "The debugger view panes should still initially be preffed as hidden.");
   isnot(gOptions._showPanesOnStartupItem.getAttribute("checked"), "true",
     "The options menu item should still not be checked.");
@@ -80,18 +80,18 @@ function testInstrumentsPaneCollapse() {
   is(width, gPrefs.instrumentsWidth,
     "The instruments pane has an incorrect width after collapsing.");
   is(instrumentsPane.style.marginLeft, margin,
     "The instruments pane has an incorrect left margin after collapsing.");
   is(instrumentsPane.style.marginRight, margin,
     "The instruments pane has an incorrect right margin after collapsing.");
   ok(instrumentsPane.hasAttribute("animated"),
     "The instruments pane has an incorrect attribute after an animated collapsing.");
-  ok(instrumentsPane.hasAttribute("pane-collapsed") &&
-     instrumentsPaneToggleButton.hasAttribute("pane-collapsed"),
+  ok(instrumentsPane.classList.contains("pane-collapsed") &&
+     instrumentsPaneToggleButton.classList.contains("pane-collapsed"),
     "The instruments pane should not be visible after collapsing.");
 
   gDebugger.DebuggerView.toggleInstrumentsPane({ visible: true, animated: false });
 
   is(gPrefs.panesVisibleOnStartup, false,
     "The debugger view panes should still initially be preffed as hidden.");
   isnot(gOptions._showPanesOnStartupItem.getAttribute("checked"), "true",
     "The options menu item should still not be checked.");
@@ -99,54 +99,54 @@ function testInstrumentsPaneCollapse() {
   is(width, gPrefs.instrumentsWidth,
     "The instruments pane has an incorrect width after uncollapsing.");
   is(instrumentsPane.style.marginLeft, "0px",
     "The instruments pane has an incorrect left margin after uncollapsing.");
   is(instrumentsPane.style.marginRight, "0px",
     "The instruments pane has an incorrect right margin after uncollapsing.");
   ok(!instrumentsPane.hasAttribute("animated"),
     "The instruments pane has an incorrect attribute after an unanimated uncollapsing.");
-  ok(!instrumentsPane.hasAttribute("pane-collapsed") &&
-     !instrumentsPaneToggleButton.hasAttribute("pane-collapsed"),
+  ok(!instrumentsPane.classList.contains("pane-collapsed") &&
+     !instrumentsPaneToggleButton.classList.contains("pane-collapsed"),
     "The instruments pane should be visible again after uncollapsing.");
 }
 
 function testPanesStartupPref() {
   let instrumentsPane =
     gDebugger.document.getElementById("instruments-pane");
   let instrumentsPaneToggleButton =
     gDebugger.document.getElementById("instruments-pane-toggle");
 
   is(gPrefs.panesVisibleOnStartup, false,
     "The debugger view panes should still initially be preffed as hidden.");
 
-  ok(!instrumentsPane.hasAttribute("pane-collapsed") &&
-     !instrumentsPaneToggleButton.hasAttribute("pane-collapsed"),
+  ok(!instrumentsPane.classList.contains("pane-collapsed") &&
+     !instrumentsPaneToggleButton.classList.contains("pane-collapsed"),
     "The debugger instruments pane should at this point be visible.");
   is(gPrefs.panesVisibleOnStartup, false,
     "The debugger view panes should initially be preffed as hidden.");
   isnot(gOptions._showPanesOnStartupItem.getAttribute("checked"), "true",
     "The options menu item should still not be checked.");
 
   gOptions._showPanesOnStartupItem.setAttribute("checked", "true");
   gOptions._toggleShowPanesOnStartup();
 
-  ok(!instrumentsPane.hasAttribute("pane-collapsed") &&
-     !instrumentsPaneToggleButton.hasAttribute("pane-collapsed"),
+  ok(!instrumentsPane.classList.contains("pane-collapsed") &&
+     !instrumentsPaneToggleButton.classList.contains("pane-collapsed"),
     "The debugger instruments pane should at this point be visible.");
   is(gPrefs.panesVisibleOnStartup, true,
     "The debugger view panes should now be preffed as visible.");
   is(gOptions._showPanesOnStartupItem.getAttribute("checked"), "true",
     "The options menu item should now be checked.");
 
   gOptions._showPanesOnStartupItem.setAttribute("checked", "false");
   gOptions._toggleShowPanesOnStartup();
 
-  ok(!instrumentsPane.hasAttribute("pane-collapsed") &&
-     !instrumentsPaneToggleButton.hasAttribute("pane-collapsed"),
+  ok(!instrumentsPane.classList.contains("pane-collapsed") &&
+     !instrumentsPaneToggleButton.classList.contains("pane-collapsed"),
     "The debugger instruments pane should at this point be visible.");
   is(gPrefs.panesVisibleOnStartup, false,
     "The debugger view panes should now be preffed as hidden.");
   isnot(gOptions._showPanesOnStartupItem.getAttribute("checked"), "true",
     "The options menu item should now be unchecked.");
 }
 
 registerCleanupFunction(function () {
--- a/devtools/client/inspector/inspector-panel.js
+++ b/devtools/client/inspector/inspector-panel.js
@@ -378,16 +378,28 @@ InspectorPanel.prototype = {
       } else {
         str = strings.GetStringFromName("inspector.searchResultsNone");
       }
     }
 
     this.searchResultsLabel.textContent = str;
   },
 
+  get React() {
+    return this._toolbox.React;
+  },
+
+  get ReactDOM() {
+    return this._toolbox.ReactDOM;
+  },
+
+  get browserRequire() {
+    return this._toolbox.browserRequire;
+  },
+
   /**
    * Build the sidebar.
    */
   setupSidebar: function () {
     let tabbox = this.panelDoc.querySelector("#inspector-sidebar");
     this.sidebar = new ToolSidebar(tabbox, this, "inspector", {
       showAllTabsMenu: true
     });
@@ -426,21 +438,28 @@ InspectorPanel.prototype = {
 
     this.setupSidebarToggle();
   },
 
   /**
    * Add the expand/collapse behavior for the sidebar panel.
    */
   setupSidebarToggle: function () {
-    this._paneToggleButton = this.panelDoc.getElementById("inspector-pane-toggle");
-    this._paneToggleButton.setAttribute("tooltiptext",
-      strings.GetStringFromName("inspector.collapsePane"));
-    this._paneToggleButton.addEventListener("mousedown",
-      this.onPaneToggleButtonClicked);
+    let SidebarToggle = this.React.createFactory(this.browserRequire(
+      "devtools/client/shared/components/sidebar-toggle"));
+
+    let sidebarToggle = SidebarToggle({
+      onClick: this.onPaneToggleButtonClicked,
+      collapsed: false,
+      expandPaneTitle: strings.GetStringFromName("inspector.expandPane"),
+      collapsePaneTitle: strings.GetStringFromName("inspector.collapsePane"),
+    });
+
+    let parentBox = this.panelDoc.getElementById("inspector-sidebar-toggle-box");
+    this._sidebarToggle = this.ReactDOM.render(sidebarToggle, parentBox);
   },
 
   /**
    * Reset the inspector on new root mutation.
    */
   onNewRoot: function () {
     this._defaultNode = null;
     this.selection.setNodeFront(null);
@@ -684,19 +703,16 @@ InspectorPanel.prototype = {
     });
 
     this.sidebar.off("select", this._setDefaultSidebar);
     let sidebarDestroyer = this.sidebar.destroy();
     this.sidebar = null;
 
     this.addNodeButton.removeEventListener("click", this.addNode);
     this.breadcrumbs.destroy();
-    this._paneToggleButton.removeEventListener("mousedown",
-      this.onPaneToggleButtonClicked);
-    this._paneToggleButton = null;
     this.selection.off("new-node-front", this.onNewSelection);
     this.selection.off("before-new-node", this.onBeforeNewSelection);
     this.selection.off("before-new-node-front", this.onBeforeNewSelection);
     this.selection.off("detached-front", this.onDetached);
     let markupDestroyer = this._destroyMarkup();
     this.panelWin.inspector = null;
     this.target = null;
     this.panelDoc = null;
@@ -1138,18 +1154,17 @@ InspectorPanel.prototype = {
   },
 
   /**
    * When the pane toggle button is clicked, toggle the pane, change the button
    * state and tooltip.
    */
   onPaneToggleButtonClicked: function (e) {
     let sidePane = this.panelDoc.querySelector("#inspector-sidebar");
-    let button = this._paneToggleButton;
-    let isVisible = !button.hasAttribute("pane-collapsed");
+    let isVisible = !this._sidebarToggle.state.collapsed;
 
     // 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);
       }
@@ -1160,21 +1175,19 @@ InspectorPanel.prototype = {
 
     ViewHelpers.togglePane({
       visible: !isVisible,
       animated: true,
       delayed: true
     }, sidePane);
 
     if (isVisible) {
-      button.setAttribute("pane-collapsed", "");
-      button.setAttribute("tooltiptext", strings.GetStringFromName("inspector.expandPane"));
+      this._sidebarToggle.setState({collapsed: true});
     } else {
-      button.removeAttribute("pane-collapsed");
-      button.setAttribute("tooltiptext", strings.GetStringFromName("inspector.collapsePane"));
+      this._sidebarToggle.setState({collapsed: false});
     }
   },
 
   /**
    * 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
@@ -27,9 +27,13 @@
   max-width: 150px;
 }
 
 .inspector-tabpanel > * {
   /*
    * Override `-moz-user-focus:ignore;` from toolkit/content/minimal-xul.css
    */
   -moz-user-focus: normal;
-}
\ No newline at end of file
+}
+
+#inspector-sidebar-toggle-box {
+  line-height: initial;
+}
--- a/devtools/client/inspector/inspector.xul
+++ b/devtools/client/inspector/inspector.xul
@@ -6,16 +6,17 @@
 <?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="resource://devtools/client/shared/components/sidebar-toggle.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;
 ]>
 
@@ -34,19 +35,18 @@
           class="devtools-button" />
         <html:div class="devtools-toolbar-spacer" />
         <html:span id="inspector-searchlabel" />
         <textbox id="inspector-searchbox"
           type="search"
           timeout="50"
           class="devtools-searchinput"
           placeholder="&inspectorSearchHTML.label3;"/>
-        <html:button id="inspector-pane-toggle"
-          class="devtools-button"
-          tabindex="0" />
+        <div xmlns="http://www.w3.org/1999/xhtml"
+             id="inspector-sidebar-toggle-box" />
       </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"/>
--- a/devtools/client/inspector/test/browser_inspector_pane-toggle-01.js
+++ b/devtools/client/inspector/test/browser_inspector_pane-toggle-01.js
@@ -5,23 +5,23 @@
 
 // Test that the inspector panel has a sidebar pane toggle button, and that
 // this button is visible both in BOTTOM and SIDE hosts.
 
 add_task(function* () {
   info("Open the inspector in a bottom toolbox host");
   let {toolbox, inspector} = yield openInspectorForURL("about:blank", "bottom");
 
-  let button = inspector.panelDoc.getElementById("inspector-pane-toggle");
+  let button = inspector.panelDoc.querySelector(".sidebar-toggle");
   ok(button, "The toggle button exists in the DOM");
-  is(button.parentNode.id, "inspector-toolbar",
-     "The toggle button is in the toolbar");
-  ok(button.getAttribute("tooltiptext"), "The tool tip has initial state");
-  ok(!button.hasAttribute("pane-collapsed"), "The button is in expanded state");
+  is(button.parentNode.id, "inspector-sidebar-toggle-box",
+     "The toggle button has the right parent");
+  ok(button.getAttribute("title"), "The tool tip has initial state");
+  ok(!button.classList.contains("pane-collapsed"), "The button is in expanded state");
   ok(!!button.getClientRects().length, "The button is visible");
 
   info("Switch the host to side type");
   yield toolbox.switchHost("side");
 
   ok(!!button.getClientRects().length, "The button is still visible");
-  ok(!button.hasAttribute("pane-collapsed"),
+  ok(!button.classList.contains("pane-collapsed"),
      "The button is still in expanded state");
 });
--- a/devtools/client/inspector/test/browser_inspector_pane-toggle-02.js
+++ b/devtools/client/inspector/test/browser_inspector_pane-toggle-02.js
@@ -7,36 +7,36 @@
 // 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 button = inspector.panelDoc.getElementById("inspector-pane-toggle");
-  ok(!panel.hasAttribute("pane-collapsed"), "The panel is in expanded state");
+  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, {type: "mousedown"},
+  EventUtils.synthesizeMouseAtCenter(button, {},
     inspector.panelDoc.defaultView);
 
   yield onTransitionEnd;
-  ok(panel.hasAttribute("pane-collapsed"), "The panel is in collapsed state");
+  ok(panel.classList.contains("pane-collapsed"), "The panel is in collapsed state");
   ok(!panel.hasAttribute("animated"),
     "The collapsed panel will not perform unwanted animations");
 
   info("Switch the host to bottom type");
   yield toolbox.switchHost("bottom");
-  ok(panel.hasAttribute("pane-collapsed"), "The panel is in collapsed state");
+  ok(panel.classList.contains("pane-collapsed"), "The panel is in collapsed state");
 
   info("Click on the toggle button to expand the panel again");
 
   onTransitionEnd = once(panel, "transitionend");
-  EventUtils.synthesizeMouseAtCenter(button, {type: "mousedown"},
+  EventUtils.synthesizeMouseAtCenter(button, {},
     inspector.panelDoc.defaultView);
   yield onTransitionEnd;
 
-  ok(!panel.hasAttribute("pane-collapsed"), "The panel is in expanded state");
+  ok(!panel.classList.contains("pane-collapsed"), "The panel is in expanded state");
 });
--- a/devtools/client/inspector/test/browser_inspector_pane-toggle-03.js
+++ b/devtools/client/inspector/test/browser_inspector_pane-toggle-03.js
@@ -4,35 +4,35 @@
 "use strict";
 
 // 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.getElementById("inspector-pane-toggle");
+  let button = inspector.panelDoc.querySelector(".sidebar-toggle");
   let panel = inspector.panelDoc.querySelector("#inspector-sidebar");
 
-  ok(!button.hasAttribute("pane-collapsed"), "The button is in expanded state");
+  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, {type: "mousedown"},
+  EventUtils.synthesizeMouseAtCenter(button, {},
     inspector.panelDoc.defaultView);
 
   yield onTransitionEnd;
-  ok(button.hasAttribute("pane-collapsed"), "The button is in collapsed state");
-  ok(panel.hasAttribute("pane-collapsed"), "The panel is in collapsed state");
+  ok(button.classList.contains("pane-collapsed"), "The button is in collapsed state");
+  ok(panel.classList.contains("pane-collapsed"), "The panel is in collapsed state");
 
   info("Listen again to the end of the animation on the sidebar panel");
   onTransitionEnd = once(panel, "transitionend");
 
   info("Click on the toggle button again");
-  EventUtils.synthesizeMouseAtCenter(button, {type: "mousedown"},
+  EventUtils.synthesizeMouseAtCenter(button, {},
     inspector.panelDoc.defaultView);
 
   yield onTransitionEnd;
-  ok(!button.hasAttribute("pane-collapsed"), "The button is in expanded state");
-  ok(!panel.hasAttribute("pane-collapsed"), "The panel is in expanded state");
+  ok(!button.classList.contains("pane-collapsed"), "The button is in expanded state");
+  ok(!panel.classList.contains("pane-collapsed"), "The panel is in expanded state");
 });
--- a/devtools/client/inspector/test/browser_inspector_pane-toggle-04.js
+++ b/devtools/client/inspector/test/browser_inspector_pane-toggle-04.js
@@ -14,47 +14,47 @@ add_task(function* () {
   yield new Promise(resolve => {
     let options = {"set": [
       ["devtools.toolsidebar-width.inspector", 200]
     ]};
     SpecialPowers.pushPrefEnv(options, resolve);
   });
 
   let { inspector, toolbox } = yield openInspectorForURL("about:blank");
-  let button = inspector.panelDoc.getElementById("inspector-pane-toggle");
+  let button = inspector.panelDoc.querySelector(".sidebar-toggle");
   let panel = inspector.panelDoc.querySelector("#inspector-sidebar");
 
   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;
 
   info("Resizing window to switch to the horizontal layout.");
   hostWindow.resizeTo(800, 300);
 
   // Check the sidebar is expanded when the test starts.
-  ok(!panel.hasAttribute("pane-collapsed"), "The panel is in expanded state");
+  ok(!panel.classList.contains("pane-collapsed"), "The panel is in expanded state");
 
   info("Collapse the inspector sidebar.");
   let onTransitionEnd = once(panel, "transitionend");
-  EventUtils.synthesizeMouseAtCenter(button, {type: "mousedown"},
+  EventUtils.synthesizeMouseAtCenter(button, {},
     inspector.panelDoc.defaultView);
   yield onTransitionEnd;
 
-  ok(panel.hasAttribute("pane-collapsed"), "The panel is in collapsed state");
+  ok(panel.classList.contains("pane-collapsed"), "The panel is in collapsed state");
   let currentPanelHeight = panel.getBoundingClientRect().height;
   let currentPanelMarginBottom = panel.style.marginBottom;
 
   info("Resizing window to switch to the vertical layout.");
   hostWindow.resizeTo(300, 800);
 
   // Check the panel is collapsed, and still has the same dimensions.
-  ok(panel.hasAttribute("pane-collapsed"), "The panel is still collapsed");
+  ok(panel.classList.contains("pane-collapsed"), "The panel is still collapsed");
   is(panel.getBoundingClientRect().height, currentPanelHeight,
     "The panel height has not been modified when changing the layout.");
   is(panel.style.marginBottom, currentPanelMarginBottom,
     "The panel margin-bottom has not been modified when changing the layout.");
 
   info("Restoring window original size.");
   hostWindow.resizeTo(originalWidth, originalHeight);
 });
--- a/devtools/client/netmonitor/netmonitor-view.js
+++ b/devtools/client/netmonitor/netmonitor-view.js
@@ -245,17 +245,17 @@ var NetMonitorView = {
     }
   }),
 
   /**
    * Gets the visibility state of the network details pane.
    * @return boolean
    */
   get detailsPaneHidden() {
-    return this._detailsPane.hasAttribute("pane-collapsed");
+    return this._detailsPane.classList.contains("pane-collapsed");
   },
 
   /**
    * Sets the network details pane hidden or visible.
    *
    * @param object flags
    *        An object containing some of the following properties:
    *        - visible: true if the pane should be shown, false to hide
@@ -267,22 +267,22 @@ var NetMonitorView = {
    */
   toggleDetailsPane: function (flags, tabIndex) {
     let pane = this._detailsPane;
     let button = this._detailsPaneToggleButton;
 
     ViewHelpers.togglePane(flags, pane);
 
     if (flags.visible) {
-      this._body.removeAttribute("pane-collapsed");
-      button.removeAttribute("pane-collapsed");
+      this._body.classList.remove("pane-collapsed");
+      button.classList.remove("pane-collapsed");
       button.setAttribute("tooltiptext", this._collapsePaneString);
     } else {
-      this._body.setAttribute("pane-collapsed", "");
-      button.setAttribute("pane-collapsed", "");
+      this._body.classList.add("pane-collapsed");
+      button.classList.add("pane-collapsed");
       button.setAttribute("tooltiptext", this._expandPaneString);
     }
 
     if (tabIndex !== undefined) {
       $("#event-details-pane").selectedIndex = tabIndex;
     }
   },
 
--- a/devtools/client/netmonitor/netmonitor.css
+++ b/devtools/client/netmonitor/netmonitor.css
@@ -31,14 +31,14 @@
   /* workaround for textbox not supporting the @crop attribute */
   text-overflow: ellipsis;
 }
 
 /* Responsive sidebar */
 @media (max-width: 700px) {
   #toolbar-spacer,
   #details-pane-toggle,
-  #details-pane[pane-collapsed],
+  #details-pane.pane-collapsed,
   .requests-menu-waterfall,
   #requests-menu-network-summary-button > .toolbarbutton-text {
     display: none;
   }
 }
--- a/devtools/client/netmonitor/test/browser_net_clear.js
+++ b/devtools/client/netmonitor/test/browser_net_clear.js
@@ -29,25 +29,25 @@ function test() {
       assertNoRequestState(RequestsMenu, detailsPaneToggleButton);
 
       // Load a second request and make sure they still show up
       aMonitor.panelWin.once(aMonitor.panelWin.EVENTS.NETWORK_EVENT, () => {
         assertSingleRequestState(RequestsMenu, detailsPaneToggleButton);
 
         // Make sure we can now open the details pane
         NetMonitorView.toggleDetailsPane({ visible: true, animated: false });
-        ok(!detailsPane.hasAttribute("pane-collapsed") &&
-          !detailsPaneToggleButton.hasAttribute("pane-collapsed"),
+        ok(!detailsPane.classList.contains("pane-collapsed") &&
+          !detailsPaneToggleButton.classList.contains("pane-collapsed"),
           "The details pane should be visible after clicking the toggle button.");
 
         // Click clear and make sure the details pane closes
         EventUtils.sendMouseEvent({ type: "click" }, clearButton);
         assertNoRequestState(RequestsMenu, detailsPaneToggleButton);
-        ok(detailsPane.hasAttribute("pane-collapsed") &&
-          detailsPaneToggleButton.hasAttribute("pane-collapsed"),
+        ok(detailsPane.classList.contains("pane-collapsed") &&
+          detailsPaneToggleButton.classList.contains("pane-collapsed"),
           "The details pane should not be visible clicking 'clear'.");
 
         teardown(aMonitor).then(finish);
       });
 
       aDebuggee.location.reload();
     });
 
--- a/devtools/client/netmonitor/test/browser_net_pane-collapse.js
+++ b/devtools/client/netmonitor/test/browser_net_pane-collapse.js
@@ -8,59 +8,59 @@
 function test() {
   initNetMonitor(SIMPLE_URL).then(([aTab, aDebuggee, aMonitor]) => {
     info("Starting test... ");
 
     let { document, Prefs, NetMonitorView } = aMonitor.panelWin;
     let detailsPane = document.getElementById("details-pane");
     let detailsPaneToggleButton = document.getElementById("details-pane-toggle");
 
-    ok(detailsPane.hasAttribute("pane-collapsed") &&
-       detailsPaneToggleButton.hasAttribute("pane-collapsed"),
+    ok(detailsPane.classList.contains("pane-collapsed") &&
+       detailsPaneToggleButton.classList.contains("pane-collapsed"),
       "The details pane should initially be hidden.");
 
     NetMonitorView.toggleDetailsPane({ visible: true, animated: false });
 
     let width = ~~(detailsPane.getAttribute("width"));
     is(width, Prefs.networkDetailsWidth,
       "The details pane has an incorrect width.");
     is(detailsPane.style.marginLeft, "0px",
       "The details pane has an incorrect left margin.");
     is(detailsPane.style.marginRight, "0px",
       "The details pane has an incorrect right margin.");
     ok(!detailsPane.hasAttribute("animated"),
       "The details pane has an incorrect animated attribute.");
-    ok(!detailsPane.hasAttribute("pane-collapsed") &&
-       !detailsPaneToggleButton.hasAttribute("pane-collapsed"),
+    ok(!detailsPane.classList.contains("pane-collapsed") &&
+       !detailsPaneToggleButton.classList.contains("pane-collapsed"),
       "The details pane should at this point be visible.");
 
     NetMonitorView.toggleDetailsPane({ visible: false, animated: true });
 
     let margin = -(width + 1) + "px";
     is(width, Prefs.networkDetailsWidth,
       "The details pane has an incorrect width after collapsing.");
     is(detailsPane.style.marginLeft, margin,
       "The details pane has an incorrect left margin after collapsing.");
     is(detailsPane.style.marginRight, margin,
       "The details pane has an incorrect right margin after collapsing.");
     ok(detailsPane.hasAttribute("animated"),
       "The details pane has an incorrect attribute after an animated collapsing.");
-    ok(detailsPane.hasAttribute("pane-collapsed") &&
-       detailsPaneToggleButton.hasAttribute("pane-collapsed"),
+    ok(detailsPane.classList.contains("pane-collapsed") &&
+       detailsPaneToggleButton.classList.contains("pane-collapsed"),
       "The details pane should not be visible after collapsing.");
 
     NetMonitorView.toggleDetailsPane({ visible: true, animated: false });
 
     is(width, Prefs.networkDetailsWidth,
       "The details pane has an incorrect width after uncollapsing.");
     is(detailsPane.style.marginLeft, "0px",
       "The details pane has an incorrect left margin after uncollapsing.");
     is(detailsPane.style.marginRight, "0px",
       "The details pane has an incorrect right margin after uncollapsing.");
     ok(!detailsPane.hasAttribute("animated"),
       "The details pane has an incorrect attribute after an unanimated uncollapsing.");
-    ok(!detailsPane.hasAttribute("pane-collapsed") &&
-       !detailsPaneToggleButton.hasAttribute("pane-collapsed"),
+    ok(!detailsPane.classList.contains("pane-collapsed") &&
+       !detailsPaneToggleButton.classList.contains("pane-collapsed"),
       "The details pane should be visible again after uncollapsing.");
 
     teardown(aMonitor).then(finish);
   });
 }
--- a/devtools/client/netmonitor/test/browser_net_pane-toggle.js
+++ b/devtools/client/netmonitor/test/browser_net_pane-toggle.js
@@ -13,62 +13,62 @@ function test() {
     let { RequestsMenu } = NetMonitorView;
 
     RequestsMenu.lazyUpdate = false;
 
     is(document.querySelector("#details-pane-toggle")
       .hasAttribute("disabled"), true,
       "The pane toggle button should be disabled when the frontend is opened.");
     is(document.querySelector("#details-pane-toggle")
-      .hasAttribute("pane-collapsed"), true,
+      .classList.contains("pane-collapsed"), true,
       "The pane toggle button should indicate that the details pane is " +
       "collapsed when the frontend is opened.");
     is(NetMonitorView.detailsPaneHidden, true,
       "The details pane should be hidden when the frontend is opened.");
     is(RequestsMenu.selectedItem, null,
       "There should be no selected item in the requests menu.");
 
     aMonitor.panelWin.once(aMonitor.panelWin.EVENTS.NETWORK_EVENT, () => {
       is(document.querySelector("#details-pane-toggle")
         .hasAttribute("disabled"), false,
         "The pane toggle button should be enabled after the first request.");
       is(document.querySelector("#details-pane-toggle")
-        .hasAttribute("pane-collapsed"), true,
+        .classList.contains("pane-collapsed"), true,
         "The pane toggle button should still indicate that the details pane is " +
         "collapsed after the first request.");
       is(NetMonitorView.detailsPaneHidden, true,
         "The details pane should still be hidden after the first request.");
       is(RequestsMenu.selectedItem, null,
         "There should still be no selected item in the requests menu.");
 
       EventUtils.sendMouseEvent({ type: "mousedown" },
         document.getElementById("details-pane-toggle"));
 
       is(document.querySelector("#details-pane-toggle")
         .hasAttribute("disabled"), false,
         "The pane toggle button should still be enabled after being pressed.");
       is(document.querySelector("#details-pane-toggle")
-        .hasAttribute("pane-collapsed"), false,
+        .classList.contains("pane-collapsed"), false,
         "The pane toggle button should now indicate that the details pane is " +
         "not collapsed anymore after being pressed.");
       is(NetMonitorView.detailsPaneHidden, false,
         "The details pane should not be hidden after toggle button was pressed.");
       isnot(RequestsMenu.selectedItem, null,
         "There should be a selected item in the requests menu.");
       is(RequestsMenu.selectedIndex, 0,
         "The first item should be selected in the requests menu.");
 
       EventUtils.sendMouseEvent({ type: "mousedown" },
         document.getElementById("details-pane-toggle"));
 
       is(document.querySelector("#details-pane-toggle")
         .hasAttribute("disabled"), false,
         "The pane toggle button should still be enabled after being pressed again.");
       is(document.querySelector("#details-pane-toggle")
-        .hasAttribute("pane-collapsed"), true,
+        .classList.contains("pane-collapsed"), true,
         "The pane toggle button should now indicate that the details pane is " +
         "collapsed after being pressed again.");
       is(NetMonitorView.detailsPaneHidden, true,
         "The details pane should now be hidden after the toggle button was pressed again.");
       is(RequestsMenu.selectedItem, null,
         "There should now be no selected item in the requests menu.");
 
       teardown(aMonitor).then(finish);
--- a/devtools/client/projecteditor/lib/projecteditor.js
+++ b/devtools/client/projecteditor/lib/projecteditor.js
@@ -701,17 +701,17 @@ var ProjectEditor = Class({
 
     // If no plugin wants to handle it, just use a string from the resource.
     if (!renderedByPlugin) {
       elt.textContent = resource.displayName;
     }
   },
 
   get sourcesVisible() {
-    return this.sourceToggle.hasAttribute("pane-collapsed");
+    return this.sourceToggle.classList.contains("pane-collapsed");
   },
 
   get currentShell() {
     return this.shells.currentShell;
   },
 
   get currentEditor() {
     return this.shells.currentEditor;
--- a/devtools/client/shared/components/moz.build
+++ b/devtools/client/shared/components/moz.build
@@ -10,14 +10,16 @@ DIRS += [
     'tree'
 ]
 
 DevToolsModules(
     'frame.js',
     'h-split-box.js',
     'notification-box.css',
     'notification-box.js',
+    'sidebar-toggle.css',
+    'sidebar-toggle.js',
     'stack-trace.js',
     'tree.js',
 )
 
 MOCHITEST_CHROME_MANIFESTS += ['test/mochitest/chrome.ini']
 BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/sidebar-toggle.css
@@ -0,0 +1,24 @@
+/* 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/. */
+
+.sidebar-toggle {
+  display: block;
+}
+
+.sidebar-toggle::before {
+  background-image: var(--theme-pane-collapse-image);
+}
+
+.sidebar-toggle.pane-collapsed::before {
+  background-image: var(--theme-pane-expand-image);
+}
+
+/* Rotate button icon 90deg if the toolbox container is
+  in vertical mode (sidebar displayed under the main panel) */
+@media (max-width: 700px) {
+  .sidebar-toggle::before {
+    transform: rotate(90deg);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/sidebar-toggle.js
@@ -0,0 +1,66 @@
+/* -*- 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 { button } = DOM;
+
+/**
+ * Sidebar toggle button. This button is used to exapand
+ * and collapse Sidebar.
+ */
+var SidebarToggle = createClass({
+  displayName: "SidebarToggle",
+
+  propTypes: {
+    // Set to true if collapsed.
+    collapsed: PropTypes.bool.isRequired,
+    // Tooltip text used when the button indicates expanded state.
+    collapsePaneTitle: PropTypes.string.isRequired,
+    // Tooltip text used when the button indicates collapsed state.
+    expandPaneTitle: PropTypes.string.isRequired,
+    // Click callback
+    onClick: PropTypes.func.isRequired,
+  },
+
+  getInitialState: function () {
+    return {
+      collapsed: this.props.collapsed,
+    };
+  },
+
+  // Events
+
+  onClick: function (event) {
+    this.props.onClick(event);
+  },
+
+  // Rendering
+
+  render: function () {
+    let title = this.state.collapsed ?
+      this.props.expandPaneTitle :
+      this.props.collapsePaneTitle;
+
+    let classNames = ["devtools-button", "sidebar-toggle"];
+    if (this.state.collapsed) {
+      classNames.push("pane-collapsed");
+    }
+
+    return (
+      button({
+        className: classNames.join(" "),
+        title: title,
+        onClick: this.onClick
+      })
+    );
+  }
+});
+
+module.exports = SidebarToggle;
--- a/devtools/client/shared/components/test/mochitest/chrome.ini
+++ b/devtools/client/shared/components/test/mochitest/chrome.ini
@@ -1,28 +1,29 @@
 [DEFAULT]
 support-files =
   head.js
 
+[test_frame_01.html]
 [test_HSplitBox_01.html]
 [test_notification_box_01.html]
 [test_notification_box_02.html]
 [test_notification_box_03.html]
 [test_reps_attribute.html]
 [test_reps_date-time.html]
 [test_reps_function.html]
 [test_reps_grip.html]
 [test_reps_null.html]
 [test_reps_number.html]
 [test_reps_object-with-text.html]
 [test_reps_object-with-url.html]
 [test_reps_stylesheet.html]
 [test_reps_undefined.html]
 [test_reps_window.html]
-[test_frame_01.html]
+[test_sidebar_toggle.html]
 [test_tree_01.html]
 [test_tree_02.html]
 [test_tree_03.html]
 [test_tree_04.html]
 [test_tree_05.html]
 [test_tree_06.html]
 [test_tree_07.html]
 [test_tree_08.html]
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/test/mochitest/test_sidebar_toggle.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test sidebar toggle button
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Sidebar toggle button test</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
+window.onload = Task.async(function* () {
+  let SidebarToggle = browserRequire("devtools/client/shared/components/sidebar-toggle.js");
+
+  try {
+    yield test();
+  } catch(e) {
+    ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
+  } finally {
+    SimpleTest.finish();
+  }
+
+  function test() {
+    const output1 = shallowRenderComponent(SidebarToggle, {
+      collapsed: false,
+      collapsePaneTitle: "Expand",
+      expandPaneTitle: "Collapse"
+    });
+
+    is(output1.type, "button", "Output is a button element");
+    is(output1.props.title, "Expand", "Proper title is set");
+    is(output1.props.className.indexOf("pane-collapsed"), -1,
+      "Proper class name is set");
+
+    const output2 = shallowRenderComponent(SidebarToggle, {
+      collapsed: true,
+      collapsePaneTitle: "Expand",
+      expandPaneTitle: "Collapse"
+    });
+
+    is(output2.props.title, "Collapse", "Proper title is set");
+    ok(output2.props.className.indexOf("pane-collapsed") >= 0,
+      "Proper class name is set");
+  }
+});
+</script>
+</pre>
+</body>
+</html>
--- a/devtools/client/shared/widgets/view-helpers.js
+++ b/devtools/client/shared/widgets/view-helpers.js
@@ -239,17 +239,17 @@ const ViewHelpers = exports.ViewHelpers 
 
     // Hiding is always handled via margins, not the hidden attribute.
     pane.removeAttribute("hidden");
 
     // Add a class to the pane to handle min-widths, margins and animations.
     pane.classList.add("generic-toggled-pane");
 
     // Avoid useless toggles.
-    if (flags.visible == !pane.hasAttribute("pane-collapsed")) {
+    if (flags.visible == !pane.classList.contains("pane-collapsed")) {
       if (flags.callback) {
         flags.callback();
       }
       return;
     }
 
     // The "animated" attributes enables animated toggles (slide in-out).
     if (flags.animated) {
@@ -262,24 +262,24 @@ const ViewHelpers = exports.ViewHelpers 
     let doToggle = () => {
       // Negative margins are applied to "right" and "left" to support RTL and
       // LTR directions, as well as to "bottom" to support vertical layouts.
       // Unnecessary negative margins are forced to 0 via CSS in widgets.css.
       if (flags.visible) {
         pane.style.marginLeft = "0";
         pane.style.marginRight = "0";
         pane.style.marginBottom = "0";
-        pane.removeAttribute("pane-collapsed");
+        pane.classList.remove("pane-collapsed");
       } else {
         let width = Math.floor(pane.getAttribute("width")) + 1;
         let height = Math.floor(pane.getAttribute("height")) + 1;
         pane.style.marginLeft = -width + "px";
         pane.style.marginRight = -width + "px";
         pane.style.marginBottom = -height + "px";
-        pane.setAttribute("pane-collapsed", "");
+        pane.classList.add("pane-collapsed");
       }
 
       // Wait for the animation to end before calling afterToggle()
       if (flags.animated) {
         pane.addEventListener("transitionend", function onEvent() {
           pane.removeEventListener("transitionend", onEvent, false);
           // Prevent unwanted transitions: if the panel is hidden and the layout
           // changes margins will be updated and the panel will pop out.
--- a/devtools/client/themes/debugger.css
+++ b/devtools/client/themes/debugger.css
@@ -638,17 +638,17 @@
 .theme-firebug #step-out {
   list-style-image: url(images/firebug/debugger-step-out.svg);
 }
 
 #instruments-pane-toggle {
   list-style-image: var(--theme-pane-collapse-image);
 }
 
-#instruments-pane-toggle[pane-collapsed] {
+#instruments-pane-toggle.pane-collapsed {
   list-style-image: var(--theme-pane-expand-image);
 }
 
 /* Horizontal vs. vertical layout */
 
 #vertical-layout-panes-container {
   min-height: 35vh;
   max-height: 80vh;
--- a/devtools/client/themes/inspector.css
+++ b/devtools/client/themes/inspector.css
@@ -73,34 +73,16 @@
 }
 
 #inspector-breadcrumbs .breadcrumbs-widget-item {
   white-space: nowrap;
   flex-shrink: 0;
   font: message-box;
 }
 
-/* Expand/collapse panel toolbar button */
-
-#inspector-pane-toggle::before {
-  background-image: var(--theme-pane-collapse-image);
-}
-
-#inspector-pane-toggle[pane-collapsed]::before {
-  background-image: var(--theme-pane-expand-image);
-}
-
-/* Rotate button icon 90deg if the toolbox container is
-  in vertical mode (sidebar displayed under the main panel) */
-@media (max-width: 700px) {
-  #inspector-pane-toggle::before {
-    transform: rotate(90deg);
-  }
-}
-
 /* Add element toolbar button */
 
 #inspector-element-add-button::before {
   background-image: url("chrome://devtools/skin/images/add.svg");
 }
 
 /* "no results" warning message displayed in the ruleview and in the computed view */
 
--- a/devtools/client/themes/netmonitor.css
+++ b/devtools/client/themes/netmonitor.css
@@ -505,17 +505,17 @@
 }
 
 /* Network request details */
 
 #details-pane-toggle {
   list-style-image: var(--theme-pane-collapse-image);
 }
 
-#details-pane-toggle[pane-collapsed] {
+#details-pane-toggle.pane-collapsed {
   list-style-image: var(--theme-pane-expand-image);
 }
 
 /* Network request details tabpanels */
 
 .tabpanel-content {
   background-color: var(--theme-sidebar-background);
 }
--- a/devtools/client/themes/webaudioeditor.css
+++ b/devtools/client/themes/webaudioeditor.css
@@ -144,17 +144,17 @@ text {
   list-style-image: var(--theme-pane-collapse-image);
 }
 
 #inspector-pane-toggle > .toolbarbutton-icon {
   width: 16px;
   height: 16px;
 }
 
-#inspector-pane-toggle[pane-collapsed] {
+#inspector-pane-toggle.pane-collapsed {
   list-style-image: var(--theme-pane-expand-image);
 }
 
 /**
  * Automation Styles
  */
 
 #automation-param-toolbar .automation-param-button[selected] {
--- a/devtools/client/themes/widgets.css
+++ b/devtools/client/themes/widgets.css
@@ -92,17 +92,17 @@
     border-inline-start-width: 0;
     margin-inline-end: 0;
     margin-inline-start: 0;
 
     /* In some edge case the cursor is not changed to n-resize */
     cursor: n-resize;
   }
 
-  .devtools-responsive-container > .devtools-sidebar-tabs:not([pane-collapsed]) {
+  .devtools-responsive-container > .devtools-sidebar-tabs:not(.pane-collapsed) {
     /* When the panel is collapsed min/max height should not be applied because
        collapsing relies on negative margins, which implies constant height. */
     min-height: 35vh;
     max-height: 75vh;
   }
 
   .devtools-responsive-container .generic-toggled-pane {
     /* To hide generic-toggled-pane, negative margins are applied dynamically.
--- a/devtools/client/webaudioeditor/views/utils.js
+++ b/devtools/client/webaudioeditor/views/utils.js
@@ -60,17 +60,17 @@ var ToggleMixin = {
     this._viewController({ visible: false, delayed: false, animated: false });
   },
 
   /**
    * Returns a boolean indicating whether or not the view.
    * is currently being shown.
    */
   isVisible: function () {
-    return !this.el.hasAttribute("pane-collapsed");
+    return !this.el.classList.contains("pane-collapsed");
   },
 
   /**
    * Toggles the visibility of the view.
    *
    * @param object visible
    *        - visible: boolean indicating whether the panel should be shown or not
    *        - animated: boolean indiciating whether the pane should be animated
@@ -83,21 +83,21 @@ var ToggleMixin = {
       animated: animated != null ? animated : !!this._animated,
       delayed: delayed != null ? delayed : !!this._delayed,
       callback: () => window.emit(this._toggleEvent, visible)
     };
 
     ViewHelpers.togglePane(flags, this.el);
 
     if (flags.visible) {
-      this.button.removeAttribute("pane-collapsed");
+      this.button.classList.remove("pane-collapsed");
       this.button.setAttribute("tooltiptext", this._collapseString);
     }
     else {
-      this.button.setAttribute("pane-collapsed", "");
+      this.button.classList.add("pane-collapsed");
       this.button.setAttribute("tooltiptext", this._expandString);
     }
   },
 
   _onToggle: function () {
     this._viewController({ visible: !this.isVisible() });
   }
 };