Bug 1333364 - Introduce id props to tab r?honza draft
authorRicky Chien <rchien@mozilla.com>
Wed, 25 Jan 2017 00:25:11 +0800
changeset 467034 e6823d84f347588e90e32e0d23f4a5ca3f85e2b6
parent 466497 52a34f9a6cf112377299ab32132384e2dc1f543b
child 467043 b14e7595fc5f416264bb974f507df92d7903b30c
push id43094
push userbmo:rchien@mozilla.com
push dateFri, 27 Jan 2017 02:40:06 +0000
reviewershonza
bugs1333364
milestone54.0a1
Bug 1333364 - Introduce id props to tab r?honza MozReview-Commit-ID: 1nbK6mlRSIr
devtools/client/netmonitor/actions/ui.js
devtools/client/netmonitor/components/request-list-content.js
devtools/client/netmonitor/events.js
devtools/client/netmonitor/reducers/ui.js
devtools/client/netmonitor/shared/components/details-panel.js
devtools/client/netmonitor/test/browser.ini
devtools/client/netmonitor/test/browser_net_brotli.js
devtools/client/netmonitor/test/browser_net_complex-params.js
devtools/client/netmonitor/test/browser_net_content-type.js
devtools/client/netmonitor/test/browser_net_cyrillic-01.js
devtools/client/netmonitor/test/browser_net_cyrillic-02.js
devtools/client/netmonitor/test/browser_net_details-no-duplicated-content.js
devtools/client/netmonitor/test/browser_net_html-preview.js
devtools/client/netmonitor/test/browser_net_json-long.js
devtools/client/netmonitor/test/browser_net_json-malformed.js
devtools/client/netmonitor/test/browser_net_json_custom_mime.js
devtools/client/netmonitor/test/browser_net_json_text_mime.js
devtools/client/netmonitor/test/browser_net_jsonp.js
devtools/client/netmonitor/test/browser_net_large-response.js
devtools/client/netmonitor/test/browser_net_post-data-01.js
devtools/client/netmonitor/test/browser_net_post-data-02.js
devtools/client/netmonitor/test/browser_net_post-data-03.js
devtools/client/netmonitor/test/browser_net_post-data-04.js
devtools/client/netmonitor/test/browser_net_security-details.js
devtools/client/netmonitor/test/browser_net_security-error.js
devtools/client/netmonitor/test/browser_net_security-icon-click.js
devtools/client/netmonitor/test/browser_net_security-tab-deselect.js
devtools/client/netmonitor/test/browser_net_security-tab-visibility.js
devtools/client/netmonitor/test/browser_net_security-warnings.js
devtools/client/netmonitor/test/browser_net_status-codes.js
devtools/client/netmonitor/test/browser_net_streaming-response.js
devtools/client/shared/components/tabs/tabbar.js
devtools/client/shared/components/tabs/tabs.js
devtools/client/shared/components/test/mochitest/test_tabs_accessibility.html
--- a/devtools/client/netmonitor/actions/ui.js
+++ b/devtools/client/netmonitor/actions/ui.js
@@ -43,22 +43,22 @@ function resizeWaterfall(width) {
     type: WATERFALL_RESIZE,
     width
   };
 }
 
 /**
  * Change the selected tab for details panel.
  *
- * @param {number} index - tab index to be selected
+ * @param {string} id - tab id to be selected
  */
-function selectDetailsPanelTab(index) {
+function selectDetailsPanelTab(id) {
   return {
     type: SELECT_DETAILS_PANEL_TAB,
-    index,
+    id,
   };
 }
 
 /**
  * Toggle sidebar open state.
  */
 function toggleSidebar() {
   return (dispatch, getState) => dispatch(openSidebar(!getState().ui.sidebarOpen));
--- a/devtools/client/netmonitor/components/request-list-content.js
+++ b/devtools/client/netmonitor/components/request-list-content.js
@@ -259,13 +259,13 @@ module.exports = connect(
     /**
      * A handler that opens the security tab in the details view if secure or
      * broken security indicator is clicked.
      */
     onSecurityIconClick: (e, item) => {
       const { securityState } = item;
       // Choose the security tab.
       if (securityState && securityState !== "insecure") {
-        dispatch(Actions.selectDetailsPanelTab(5));
+        dispatch(Actions.selectDetailsPanelTab("security"));
       }
     },
   })
 )(RequestListContent);
--- a/devtools/client/netmonitor/events.js
+++ b/devtools/client/netmonitor/events.js
@@ -54,25 +54,19 @@ const EVENTS = {
 
   // When the request post params are displayed in the UI.
   REQUEST_POST_PARAMS_DISPLAYED: "NetMonitor:RequestPostParamsAvailable",
 
   // When the image response thumbnail is displayed in the UI.
   RESPONSE_IMAGE_THUMBNAIL_DISPLAYED:
     "NetMonitor:ResponseImageThumbnailAvailable",
 
-  // When a tab is selected in the NetworkDetailsView and subsequently rendered.
-  TAB_UPDATED: "NetMonitor:TabUpdated",
-
   // Fired when Sidebar has finished being populated.
   SIDEBAR_POPULATED: "NetMonitor:SidebarPopulated",
 
-  // Fired when NetworkDetailsView has finished being populated.
-  NETWORKDETAILSVIEW_POPULATED: "NetMonitor:NetworkDetailsViewPopulated",
-
   // Fired when CustomRequestView has finished being populated.
   CUSTOMREQUESTVIEW_POPULATED: "NetMonitor:CustomRequestViewPopulated",
 
   // Fired when charts have been displayed in the PerformanceStatisticsView.
   PLACEHOLDER_CHARTS_DISPLAYED: "NetMonitor:PlaceholderChartsDisplayed",
   PRIMED_CACHE_CHART_DISPLAYED: "NetMonitor:PrimedChartsDisplayed",
   EMPTY_CACHE_CHART_DISPLAYED: "NetMonitor:EmptyChartsDisplayed",
 
--- a/devtools/client/netmonitor/reducers/ui.js
+++ b/devtools/client/netmonitor/reducers/ui.js
@@ -8,17 +8,17 @@ const I = require("devtools/client/share
 const {
   OPEN_SIDEBAR,
   OPEN_STATISTICS,
   SELECT_DETAILS_PANEL_TAB,
   WATERFALL_RESIZE,
 } = require("../constants");
 
 const UI = I.Record({
-  detailsPanelSelectedTab: 0,
+  detailsPanelSelectedTab: "headers",
   sidebarOpen: false,
   statisticsOpen: false,
   waterfallWidth: null,
 });
 
 // Safe bounds for waterfall width (px)
 const REQUESTS_WATERFALL_SAFE_BOUNDS = 90;
 
@@ -30,17 +30,17 @@ function openSidebar(state, action) {
   return state.set("sidebarOpen", action.open);
 }
 
 function openStatistics(state, action) {
   return state.set("statisticsOpen", action.open);
 }
 
 function setDetailsPanelTab(state, action) {
-  return state.set("detailsPanelSelectedTab", action.index);
+  return state.set("detailsPanelSelectedTab", action.id);
 }
 
 function ui(state = new UI(), action) {
   switch (action.type) {
     case OPEN_SIDEBAR:
       return openSidebar(state, action);
     case OPEN_STATISTICS:
       return openStatistics(state, action);
--- a/devtools/client/netmonitor/shared/components/details-panel.js
+++ b/devtools/client/netmonitor/shared/components/details-panel.js
@@ -33,31 +33,31 @@ const TIMINGS_TITLE = L10N.getStr("netmo
 const SECURITY_TITLE = L10N.getStr("netmonitor.tab.security");
 const PREVIEW_TITLE = L10N.getStr("netmonitor.tab.preview");
 
 /*
  * Details panel component
  * Display the network request details
  */
 function DetailsPanel({
+  activeTabId,
   cloneSelectedRequest,
   request,
-  setTabIndex,
-  tabIndex,
+  selectTab,
   toolbox,
 }) {
   if (!request) {
     return null;
   }
 
   return (
     Tabbar({
-      onSelect: setTabIndex,
+      activeTabId,
+      onSelect: selectTab,
       showAllTabsMenu: true,
-      tabActive: tabIndex,
       toolbox,
     },
       TabPanel({
         id: "headers",
         title: HEADERS_TITLE,
       },
         HeadersPanel({ request, cloneSelectedRequest }),
       ),
@@ -104,22 +104,22 @@ function DetailsPanel({
 }
 
 DetailsPanel.displayName = "DetailsPanel";
 
 DetailsPanel.propTypes = {
   cloneSelectedRequest: PropTypes.func.isRequired,
   request: PropTypes.object,
   setTabIndex: PropTypes.func.isRequired,
-  tabIndex: PropTypes.number.isRequired,
+  selectedTab: PropTypes.number.isRequired,
   toolbox: PropTypes.object.isRequired,
 };
 
 module.exports = connect(
   (state) => ({
+    activeTabId: state.ui.detailsPanelSelectedTab,
     request: getSelectedRequest(state),
-    tabIndex: state.ui.detailsPanelSelectedTab,
   }),
   (dispatch) => ({
     cloneSelectedRequest: () => dispatch(Actions.cloneSelectedRequest()),
-    setTabIndex: (index) => dispatch(Actions.selectDetailsPanelTab(index)),
+    selectTab: (tabId) => dispatch(Actions.selectDetailsPanelTab(tabId)),
   }),
 )(DetailsPanel);
--- a/devtools/client/netmonitor/test/browser.ini
+++ b/devtools/client/netmonitor/test/browser.ini
@@ -89,18 +89,16 @@ skip-if = (os == 'linux' && bits == 32 &
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_net_copy_as_curl.js]
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_net_cors_requests.js]
 [browser_net_cyrillic-01.js]
 [browser_net_cyrillic-02.js]
-[browser_net_details-no-duplicated-content.js]
-skip-if = true # Test broken in React version, is too low-level
 [browser_net_frame.js]
 skip-if = (os == 'linux' && debug && bits == 32) # Bug 1321434
 [browser_net_filter-01.js]
 skip-if = (os == 'linux' && debug && bits == 32) # Bug 1303439
 [browser_net_filter-02.js]
 [browser_net_filter-03.js]
 [browser_net_filter-04.js]
 [browser_net_footer-summary.js]
--- a/devtools/client/netmonitor/test/browser_net_brotli.js
+++ b/devtools/client/netmonitor/test/browser_net_brotli.js
@@ -33,20 +33,20 @@ add_task(function* () {
       statusText: "Connected",
       type: "plain",
       fullMimeType: "text/plain",
       transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 10),
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 64),
       time: true
     });
 
-  wait = waitForDOM(document, "#panel-3 .editor-mount iframe");
+  wait = waitForDOM(document, "#response-panel .editor-mount iframe");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
-  document.querySelector("#tab-3 a").click();
+  document.querySelector("#response-tab").click();
   let [editorFrame] = yield wait;
 
   yield once(editorFrame, "DOMContentLoaded");
   yield waitForDOM(editorFrame.contentDocument, ".CodeMirror-code");
   yield testResponse("br");
 
   yield teardown(monitor);
 
--- a/devtools/client/netmonitor/test/browser_net_complex-params.js
+++ b/devtools/client/netmonitor/test/browser_net_complex-params.js
@@ -20,62 +20,62 @@ add_task(function* () {
   RequestsMenu.lazyUpdate = false;
 
   let wait = waitForNetworkEvents(monitor, 1, 6);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
-  wait = waitForDOM(document, "#panel-2 .tree-section", 2);
+  wait = waitForDOM(document, "#params-panel .tree-section", 2);
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
-  document.querySelector("#tab-2 a").click();
+  document.querySelector("#params-tab").click();
   yield wait;
   testParamsTab1("a", '""', '{ "foo": "bar" }', '""');
 
-  wait = waitForDOM(document, "#panel-2 .tree-section", 2);
+  wait = waitForDOM(document, "#params-panel .tree-section", 2);
   RequestsMenu.selectedIndex = 1;
   yield wait;
   testParamsTab1("a", '"b"', '{ "foo": "bar" }', '""');
 
-  wait = waitForDOM(document, "#panel-2 .tree-section", 2);
+  wait = waitForDOM(document, "#params-panel .tree-section", 2);
   RequestsMenu.selectedIndex = 2;
   yield wait;
   testParamsTab1("a", '"b"', "foo", '"bar"');
 
-  wait = waitForDOM(document, "#panel-2 tr:not(.tree-section).treeRow", 2);
+  wait = waitForDOM(document, "#params-panel tr:not(.tree-section).treeRow", 2);
   RequestsMenu.selectedIndex = 3;
   yield wait;
   testParamsTab2("a", '""', '{ "foo": "bar" }', "js");
 
-  wait = waitForDOM(document, "#panel-2 tr:not(.tree-section).treeRow", 2);
+  wait = waitForDOM(document, "#params-panel tr:not(.tree-section).treeRow", 2);
   RequestsMenu.selectedIndex = 4;
   yield wait;
   testParamsTab2("a", '"b"', '{ "foo": "bar" }', "js");
 
   // Wait for all tree sections and editor updated by react
-  let waitSections = waitForDOM(document, "#panel-2 .tree-section", 2);
-  let waitEditor = waitForDOM(document, "#panel-2 .editor-mount iframe");
+  let waitSections = waitForDOM(document, "#params-panel .tree-section", 2);
+  let waitEditor = waitForDOM(document, "#params-panel .editor-mount iframe");
   RequestsMenu.selectedIndex = 5;
   let [, editorFrames] = yield Promise.all([waitSections, waitEditor]);
   yield once(editorFrames[0], "DOMContentLoaded");
   yield waitForDOM(editorFrames[0].contentDocument, ".CodeMirror-code");
   testParamsTab2("a", '"b"', "?foo=bar", "text");
 
-  wait = waitForDOM(document, "#panel-2 .empty-notice");
+  wait = waitForDOM(document, "#params-panel .empty-notice");
   RequestsMenu.selectedIndex = 6;
   yield wait;
   testParamsTab3();
 
   yield teardown(monitor);
 
   function testParamsTab1(queryStringParamName, queryStringParamValue,
                           formDataParamName, formDataParamValue) {
-    let tabpanel = document.querySelector("#panel-2");
+    let tabpanel = document.querySelector("#params-panel");
 
     is(tabpanel.querySelectorAll(".tree-section").length, 2,
       "The number of param tree sections displayed in this tabpanel is incorrect.");
     is(tabpanel.querySelectorAll("tr:not(.tree-section).treeRow").length, 2,
       "The number of param rows displayed in this tabpanel is incorrect.");
     is(tabpanel.querySelectorAll(".empty-notice").length, 0,
       "The empty notice should not be displayed in this tabpanel.");
 
@@ -106,17 +106,17 @@ add_task(function* () {
       "The first form data param name was incorrect.");
     is(values[1].textContent, formDataParamValue,
       "The first form data param value was incorrect.");
   }
 
   function testParamsTab2(queryStringParamName, queryStringParamValue,
                           requestPayload, editorMode) {
     let isJSON = editorMode === "js";
-    let tabpanel = document.querySelector("#panel-2");
+    let tabpanel = document.querySelector("#params-panel");
 
     is(tabpanel.querySelectorAll(".tree-section").length, 2,
       "The number of param tree sections displayed in this tabpanel is incorrect.");
     is(tabpanel.querySelectorAll("tr:not(.tree-section).treeRow").length, isJSON ? 2 : 1,
       "The number of param rows displayed in this tabpanel is incorrect.");
     is(tabpanel.querySelectorAll(".empty-notice").length, 0,
       "The empty notice should not be displayed in this tabpanel.");
 
@@ -159,17 +159,17 @@ add_task(function* () {
     } else {
       let editor = editorFrames[0].contentDocument.querySelector(".CodeMirror-code");
       ok(editor.textContent.includes(requestPayload),
         "The text shown in the source editor is incorrect.");
     }
   }
 
   function testParamsTab3() {
-    let tabpanel = document.querySelector("#panel-2");
+    let tabpanel = document.querySelector("#params-panel");
 
     is(tabpanel.querySelectorAll(".tree-section").length, 0,
       "The number of param tree sections displayed in this tabpanel is incorrect.");
     is(tabpanel.querySelectorAll("tr:not(.tree-section).treeRow").length, 0,
       "The number of param rows displayed in this tabpanel is incorrect.");
     is(tabpanel.querySelectorAll(".empty-notice").length, 1,
       "The empty notice should be displayed in this tabpanel.");
 
--- a/devtools/client/netmonitor/test/browser_net_content-type.js
+++ b/devtools/client/netmonitor/test/browser_net_content-type.js
@@ -85,24 +85,16 @@ add_task(function* () {
       statusText: "OK",
       type: "plain",
       fullMimeType: "text/plain",
       transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 73),
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 10.73),
       time: true
     });
 
-  wait = waitForDOM(document, "#panel-3");
-  EventUtils.sendMouseEvent({ type: "mousedown" },
-    document.getElementById("details-pane-toggle"));
-  document.querySelector("#tab-3 a").click();
-  yield wait;
-
-  RequestsMenu.selectedIndex = -1;
-
   yield selectIndexAndWaitForEditor(0);
   yield testResponseTab("xml");
 
   yield selectIndexAndWaitForEditor(1);
   yield testResponseTab("css");
 
   yield selectIndexAndWaitForEditor(2);
   yield testResponseTab("js");
@@ -117,17 +109,17 @@ add_task(function* () {
   yield testResponseTab("png");
 
   yield selectIndexAndWaitForEditor(6);
   yield testResponseTab("gzip");
 
   yield teardown(monitor);
 
   function* testResponseTab(type) {
-    let tabpanel = document.querySelector("#panel-3");
+    let tabpanel = document.querySelector("#response-panel");
 
     function checkVisibility(box) {
       is(tabpanel.querySelector(".response-error-header") === null,
         true,
         "The response error header doesn't display");
       let jsonView = tabpanel.querySelector(".tree-section .treeLabel") || {};
       is(jsonView.textContent !== L10N.getStr("jsonScopeName"),
         box != "json",
@@ -227,36 +219,37 @@ add_task(function* () {
         is(text, new Array(1000).join("Hello gzip!"),
           "The text shown in the source editor is incorrect for the gzip request.");
         break;
       }
     }
   }
 
   function* selectIndexAndWaitForEditor(index) {
-    let editor = document.querySelector("#panel-3 .editor-mount iframe");
+    let editor = document.querySelector("#response-panel .editor-mount iframe");
     if (!editor) {
-      let waitDOM = waitForDOM(document, ".editor-mount iframe");
+      let waitDOM = waitForDOM(document, "#response-panel .editor-mount iframe");
       RequestsMenu.selectedIndex = index;
+      document.querySelector("#response-tab").click();
       [editor] = yield waitDOM;
       yield once(editor, "DOMContentLoaded");
     } else {
       RequestsMenu.selectedIndex = index;
     }
 
     yield waitForDOM(editor.contentDocument, ".CodeMirror-code");
   }
 
   function* selectIndexAndWaitForJSONView(index) {
-    let tabpanel = document.querySelector("#panel-3");
+    let tabpanel = document.querySelector("#response-panel");
     let waitDOM = waitForDOM(tabpanel, ".treeTable");
     RequestsMenu.selectedIndex = index;
     yield waitDOM;
   }
 
   function* selectIndexAndWaitForImageView(index) {
-    let tabpanel = document.querySelector("#panel-3");
+    let tabpanel = document.querySelector("#response-panel");
     let waitDOM = waitForDOM(tabpanel, ".response-image");
     RequestsMenu.selectedIndex = index;
     let [imageNode] = yield waitDOM;
     yield once(imageNode, "load");
   }
 });
--- a/devtools/client/netmonitor/test/browser_net_cyrillic-01.js
+++ b/devtools/client/netmonitor/test/browser_net_cyrillic-01.js
@@ -23,20 +23,20 @@ add_task(function* () {
   yield wait;
 
   verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(0),
     "GET", CONTENT_TYPE_SJS + "?fmt=txt", {
       status: 200,
       statusText: "DA DA DA"
     });
 
-  wait = waitForDOM(document, "#panel-3 .editor-mount iframe");
+  wait = waitForDOM(document, "#response-panel .editor-mount iframe");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
-  document.querySelector("#tab-3 a").click();
+  document.querySelector("#response-tab").click();
   let [editor] = yield wait;
   yield once(editor, "DOMContentLoaded");
   yield waitForDOM(editor.contentDocument, ".CodeMirror-code");
   let text = editor.contentDocument
           .querySelector(".CodeMirror-line").textContent;
 
   ok(text.includes("\u0411\u0440\u0430\u0442\u0430\u043d"),
     "The text shown in the source editor is correct.");
--- a/devtools/client/netmonitor/test/browser_net_cyrillic-02.js
+++ b/devtools/client/netmonitor/test/browser_net_cyrillic-02.js
@@ -22,20 +22,20 @@ add_task(function* () {
   yield wait;
 
   verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(0),
     "GET", CYRILLIC_URL, {
       status: 200,
       statusText: "OK"
     });
 
-  wait = waitForDOM(document, "#panel-3 .editor-mount iframe");
+  wait = waitForDOM(document, "#response-panel .editor-mount iframe");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
-  document.querySelector("#tab-3 a").click();
+  document.querySelector("#response-tab").click();
   let [editor] = yield wait;
   yield once(editor, "DOMContentLoaded");
   yield waitForDOM(editor.contentDocument, ".CodeMirror-code");
   let text = editor.contentDocument
           .querySelector(".CodeMirror-code").textContent;
 
   ok(text.includes("\u0411\u0440\u0430\u0442\u0430\u043d"),
     "The text shown in the source editor is correct.");
deleted file mode 100644
--- a/devtools/client/netmonitor/test/browser_net_details-no-duplicated-content.js
+++ /dev/null
@@ -1,171 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-// A test to ensure that the content in details pane is not duplicated.
-
-add_task(function* () {
-  let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
-  let panel = monitor.panelWin;
-  let { NetMonitorView, EVENTS } = panel;
-  let { RequestsMenu, NetworkDetails } = NetMonitorView;
-
-  const COOKIE_UNIQUE_PATH = "/do-not-use-in-other-tests-using-cookies";
-
-  let TEST_CASES = [
-    {
-      desc: "Test headers tab",
-      pageURI: CUSTOM_GET_URL,
-      requestURI: null,
-      isPost: false,
-      tabIndex: 0,
-      variablesView: NetworkDetails._headers,
-      expectedScopeLength: 2,
-    },
-    {
-      desc: "Test cookies tab",
-      pageURI: CUSTOM_GET_URL,
-      requestURI: COOKIE_UNIQUE_PATH,
-      isPost: false,
-      tabIndex: 1,
-      variablesView: NetworkDetails._cookies,
-      expectedScopeLength: 1,
-    },
-    {
-      desc: "Test params tab",
-      pageURI: POST_RAW_URL,
-      requestURI: null,
-      isPost: true,
-      tabIndex: 2,
-      variablesView: NetworkDetails._params,
-      expectedScopeLength: 1,
-    },
-  ];
-
-  info("Adding a cookie for the \"Cookie\" tab test");
-  yield setDocCookie("a=b; path=" + COOKIE_UNIQUE_PATH);
-
-  info("Running tests");
-  for (let spec of TEST_CASES) {
-    yield runTestCase(spec);
-  }
-
-  // Remove the cookie. If an error occurs the path of the cookie ensures it
-  // doesn't mess with the other tests.
-  info("Removing the added cookie.");
-  yield setDocCookie(
-    "a=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=" + COOKIE_UNIQUE_PATH);
-
-  yield teardown(monitor);
-
-  /**
-   * Set a content document cookie
-   */
-  function setDocCookie(cookie) {
-    return ContentTask.spawn(tab.linkedBrowser, cookie, function* (cookieArg) {
-      content.document.cookie = cookieArg;
-    });
-  }
-
-  /**
-   * A helper that handles the execution of each case.
-   */
-  function* runTestCase(spec) {
-    info("Running case: " + spec.desc);
-    let wait = waitForNetworkEvents(monitor, 1);
-    tab.linkedBrowser.loadURI(spec.pageURI);
-    yield wait;
-
-    RequestsMenu.clear();
-    yield waitForFinalDetailTabUpdate(spec.tabIndex, spec.isPost, spec.requestURI);
-
-    is(spec.variablesView._store.length, spec.expectedScopeLength,
-       "View contains " + spec.expectedScopeLength + " scope headers");
-  }
-
-  /**
-   * A helper that prepares the variables view for the actual testing. It
-   * - selects the correct tab
-   * - performs the specified request to specified URI
-   * - opens the details view
-   * - waits for the final update to happen
-   */
-  function* waitForFinalDetailTabUpdate(tabIndex, isPost, uri) {
-    let onNetworkEvent = panel.once(EVENTS.NETWORK_EVENT);
-    let onDetailsPopulated = panel.once(EVENTS.NETWORKDETAILSVIEW_POPULATED);
-    let onRequestFinished = isPost ?
-      waitForNetworkEvents(monitor, 0, 1) :
-      waitForNetworkEvents(monitor, 1);
-
-    info("Performing a request");
-    yield ContentTask.spawn(tab.linkedBrowser, uri, function* (url) {
-      content.wrappedJSObject.performRequests(1, url);
-    });
-
-    info("Waiting for NETWORK_EVENT");
-    yield onNetworkEvent;
-
-    if (!RequestsMenu.getItemAtIndex(0)) {
-      info("Waiting for the request to be added to the view");
-      yield monitor.panelWin.once(EVENTS.REQUEST_ADDED);
-    }
-
-    ok(true, "Received NETWORK_EVENT. Selecting the item.");
-    let item = RequestsMenu.getItemAtIndex(0);
-    RequestsMenu.selectedItem = item;
-
-    info("Item selected. Waiting for NETWORKDETAILSVIEW_POPULATED");
-    yield onDetailsPopulated;
-
-    info("Received populated event. Selecting tab at index " + tabIndex);
-    NetworkDetails.widget.selectedIndex = tabIndex;
-
-    info("Waiting for request to finish.");
-    yield onRequestFinished;
-
-    ok(true, "Request finished.");
-
-    /**
-     * Because this test uses lazy updates there's four scenarios to consider:
-     * #1: Everything is updated and test is ready to continue.
-     * #2: There's updates that are waiting to be flushed.
-     * #3: Updates are flushed but the tab update is still running.
-     * #4: There's pending updates and a tab update is still running.
-     *
-     * For case #1 there's not going to be a TAB_UPDATED event so don't wait for
-     * it (bug 1106181).
-     *
-     * For cases #2 and #3 it's enough to wait for one TAB_UPDATED event as for
-     * - case #2 the next flush will perform the final update and single
-     *   TAB_UPDATED event is emitted.
-     * - case #3 the running update is the final update that'll emit one
-     *   TAB_UPDATED event.
-     *
-     * For case #4 we must wait for the updates to be flushed before we can
-     * start waiting for TAB_UPDATED event or we'll continue the test right
-     * after the pending update finishes.
-     */
-    let hasQueuedUpdates = RequestsMenu._updateQueue.length !== 0;
-    let hasRunningTabUpdate = NetworkDetails._viewState.updating[tabIndex];
-
-    if (hasQueuedUpdates || hasRunningTabUpdate) {
-      info("There's pending updates - waiting for them to finish.");
-      info("  hasQueuedUpdates: " + hasQueuedUpdates);
-      info("  hasRunningTabUpdate: " + hasRunningTabUpdate);
-
-      if (hasQueuedUpdates && hasRunningTabUpdate) {
-        info("Waiting for updates to be flushed.");
-        // _flushRequests calls .populate which emits the following event
-        yield panel.once(EVENTS.NETWORKDETAILSVIEW_POPULATED);
-
-        info("Requests flushed.");
-      }
-
-      info("Waiting for final tab update.");
-      yield waitFor(panel, EVENTS.TAB_UPDATED);
-    }
-
-    info("All updates completed.");
-  }
-});
--- a/devtools/client/netmonitor/test/browser_net_html-preview.js
+++ b/devtools/client/netmonitor/test/browser_net_html-preview.js
@@ -20,49 +20,49 @@ add_task(function* () {
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
 
-  ok(document.querySelector("#tab-0.is-active"),
+  ok(document.querySelector("#headers-tab[aria-selected=true]"),
     "The headers tab in the details panel should be selected.");
-  ok(!document.querySelector("#tab-5"),
+  ok(!document.querySelector("#preview-tab"),
     "The preview tab should be hidden for non html responses.");
-  ok(!document.querySelector("#panel-5"),
+  ok(!document.querySelector("#preview-panel"),
     "The preview panel is hidden for non html responses.");
 
-  wait = waitForDOM(document, "#tab-5");
+  wait = waitForDOM(document, ".tabs");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll(".request-list-item")[4]);
   yield wait;
 
-  document.querySelector("#tab-5 a").click();
+  document.querySelector("#preview-tab").click();
 
-  ok(document.querySelector("#tab-5.is-active"),
+  ok(document.querySelector("#preview-tab[aria-selected=true]"),
     "The preview tab in the details panel should be selected.");
-  ok(document.querySelector("#panel-5"),
+  ok(document.querySelector("#preview-panel"),
     "The preview panel should be visible now.");
 
-  let iframe = document.querySelector("#panel-5 iframe");
+  let iframe = document.querySelector("#preview-panel iframe");
   yield once(iframe, "DOMContentLoaded");
 
   ok(iframe,
     "There should be a response preview iframe available.");
   ok(iframe.contentDocument,
     "The iframe's content document should be available.");
   is(iframe.contentDocument.querySelector("blink").textContent, "Not Found",
     "The iframe's content document should be loaded and correct.");
 
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll(".request-list-item")[5]);
 
-  ok(document.querySelector("#tab-0.is-active"),
+  ok(document.querySelector("#headers-tab[aria-selected=true]"),
     "The headers tab in the details panel should be selected again.");
-  ok(!document.querySelector("#tab-5"),
+  ok(!document.querySelector("#preview-tab"),
     "The preview tab should be hidden again for non html responses.");
-  ok(!document.querySelector("#panel-5"),
+  ok(!document.querySelector("#preview-panel"),
     "The preview panel is hidden again for non html responses.");
 
   yield teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/browser_net_json-long.js
+++ b/devtools/client/netmonitor/test/browser_net_json-long.js
@@ -34,28 +34,28 @@ add_task(function* () {
       statusText: "OK",
       type: "json",
       fullMimeType: "text/json; charset=utf-8",
       size: L10N.getFormatStr("networkMenu.sizeKB",
         L10N.numberWithDecimals(85975 / 1024, 2)),
       time: true
     });
 
-  wait = waitForDOM(document, "#panel-3");
+  wait = waitForDOM(document, "#response-panel");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
-  document.querySelector("#tab-3 a").click();
+  document.querySelector("#response-tab").click();
   yield wait;
 
   testResponseTab();
 
   yield teardown(monitor);
 
   function testResponseTab() {
-    let tabpanel = document.querySelector("#panel-3");
+    let tabpanel = document.querySelector("#response-panel");
 
     is(tabpanel.querySelector(".response-error-header") === null, true,
       "The response error header doesn't have the intended visibility.");
     let jsonView = tabpanel.querySelector(".tree-section .treeLabel") || {};
     is(jsonView.textContent === L10N.getStr("jsonScopeName"), true,
       "The response json view has the intended visibility.");
     is(tabpanel.querySelector(".editor-mount iframe") === null, true,
       "The response editor doesn't have the intended visibility.");
--- a/devtools/client/netmonitor/test/browser_net_json-malformed.js
+++ b/devtools/client/netmonitor/test/browser_net_json-malformed.js
@@ -26,25 +26,25 @@ add_task(function* () {
   verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(0),
     "GET", CONTENT_TYPE_SJS + "?fmt=json-malformed", {
       status: 200,
       statusText: "OK",
       type: "json",
       fullMimeType: "text/json; charset=utf-8"
     });
 
-  wait = waitForDOM(document, "#panel-3 .editor-mount iframe");
+  wait = waitForDOM(document, "#response-panel .editor-mount iframe");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
-  document.querySelector("#tab-3 a").click();
+  document.querySelector("#response-tab").click();
   let [editor] = yield wait;
   yield once(editor, "DOMContentLoaded");
   yield waitForDOM(editor.contentDocument, ".CodeMirror-code");
 
-  let tabpanel = document.querySelector("#panel-3");
+  let tabpanel = document.querySelector("#response-panel");
   is(tabpanel.querySelector(".response-error-header") === null, false,
     "The response error header doesn't have the intended visibility.");
   is(tabpanel.querySelector(".response-error-header").textContent,
     "SyntaxError: JSON.parse: unexpected non-whitespace character after JSON data" +
       " at line 1 column 40 of the JSON data",
     "The response error header doesn't have the intended text content.");
   is(tabpanel.querySelector(".response-error-header").getAttribute("title"),
     "SyntaxError: JSON.parse: unexpected non-whitespace character after JSON data" +
--- a/devtools/client/netmonitor/test/browser_net_json_custom_mime.js
+++ b/devtools/client/netmonitor/test/browser_net_json_custom_mime.js
@@ -29,28 +29,28 @@ add_task(function* () {
       status: 200,
       statusText: "OK",
       type: "x-bigcorp-json",
       fullMimeType: "text/x-bigcorp-json; charset=utf-8",
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 41),
       time: true
     });
 
-  wait = waitForDOM(document, "#panel-3");
+  wait = waitForDOM(document, "#response-panel");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
-  document.querySelector("#tab-3 a").click();
+  document.querySelector("#response-tab").click();
   yield wait;
 
   testResponseTab();
 
   yield teardown(monitor);
 
   function testResponseTab() {
-    let tabpanel = document.querySelector("#panel-3");
+    let tabpanel = document.querySelector("#response-panel");
 
     is(tabpanel.querySelector(".response-error-header") === null, true,
       "The response error header doesn't have the intended visibility.");
     let jsonView = tabpanel.querySelector(".tree-section .treeLabel") || {};
     is(jsonView.textContent === L10N.getStr("jsonScopeName"), true,
       "The response json view has the intended visibility.");
     is(tabpanel.querySelector(".editor-mount iframe") === null, true,
       "The response editor doesn't have the intended visibility.");
--- a/devtools/client/netmonitor/test/browser_net_json_text_mime.js
+++ b/devtools/client/netmonitor/test/browser_net_json_text_mime.js
@@ -29,28 +29,28 @@ add_task(function* () {
       status: 200,
       statusText: "OK",
       type: "plain",
       fullMimeType: "text/plain; charset=utf-8",
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 41),
       time: true
     });
 
-  wait = waitForDOM(document, "#panel-3");
+  wait = waitForDOM(document, "#response-panel");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
-  document.querySelector("#tab-3 a").click();
+  document.querySelector("#response-tab").click();
   yield wait;
 
   testResponseTab();
 
   yield teardown(monitor);
 
   function testResponseTab() {
-    let tabpanel = document.querySelector("#panel-3");
+    let tabpanel = document.querySelector("#response-panel");
 
     is(tabpanel.querySelector(".response-error-header") === null, true,
       "The response error header doesn't have the intended visibility.");
     let jsonView = tabpanel.querySelector(".tree-section .treeLabel") || {};
     is(jsonView.textContent === L10N.getStr("jsonScopeName"), true,
       "The response json view has the intended visibility.");
     is(tabpanel.querySelector(".editor-mount iframe") === null, true,
       "The response editor doesn't have the intended visibility.");
--- a/devtools/client/netmonitor/test/browser_net_jsonp.js
+++ b/devtools/client/netmonitor/test/browser_net_jsonp.js
@@ -38,34 +38,34 @@ add_task(function* () {
       status: 200,
       statusText: "OK",
       type: "json",
       fullMimeType: "text/json; charset=utf-8",
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 54),
       time: true
     });
 
-  wait = waitForDOM(document, "#panel-3");
+  wait = waitForDOM(document, "#response-panel");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
-  document.querySelector("#tab-3 a").click();
+  document.querySelector("#response-tab").click();
   yield wait;
 
   testResponseTab("$_0123Fun", "\"Hello JSONP!\"");
 
-  wait = waitForDOM(document, "#panel-3 .tree-section");
+  wait = waitForDOM(document, "#response-panel .tree-section");
   RequestsMenu.selectedIndex = 1;
   yield wait;
 
   testResponseTab("$_4567Sad", "\"Hello weird JSONP!\"");
 
   yield teardown(monitor);
 
   function testResponseTab(func, greeting) {
-    let tabpanel = document.querySelector("#panel-3");
+    let tabpanel = document.querySelector("#response-panel");
 
     is(tabpanel.querySelector(".response-error-header") === null, true,
       "The response error header doesn't have the intended visibility.");
     is(tabpanel.querySelector(".tree-section .treeLabel").textContent,
       L10N.getFormatStr("jsonpScopeName", func),
       "The response json view has the intened visibility and correct title.");
     is(tabpanel.querySelector(".editor-mount iframe") === null, true,
       "The response editor doesn't have the intended visibility.");
--- a/devtools/client/netmonitor/test/browser_net_large-response.js
+++ b/devtools/client/netmonitor/test/browser_net_large-response.js
@@ -29,20 +29,20 @@ add_task(function* () {
   yield wait;
 
   verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(0),
     "GET", CONTENT_TYPE_SJS + "?fmt=html-long", {
       status: 200,
       statusText: "OK"
     });
 
-  let waitDOM = waitForDOM(document, "#panel-3 .editor-mount iframe");
+  let waitDOM = waitForDOM(document, "#response-panel .editor-mount iframe");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
-  document.querySelector("#tab-3 a").click();
+  document.querySelector("#response-tab").click();
   let [editor] = yield waitDOM;
   yield once(editor, "DOMContentLoaded");
   yield waitForDOM(editor.contentDocument, ".CodeMirror-code");
 
   let text = editor.contentDocument
         .querySelector(".CodeMirror-line").textContent;
 
   ok(text.match(/^<p>/), "The text shown in the source editor is incorrect.");
--- a/devtools/client/netmonitor/test/browser_net_post-data-01.js
+++ b/devtools/client/netmonitor/test/browser_net_post-data-01.js
@@ -42,36 +42,36 @@ add_task(function* () {
       statusText: "Och Aye",
       type: "plain",
       fullMimeType: "text/plain; charset=utf-8",
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 12),
       time: true
     });
 
   // Wait for all tree sections updated by react
-  wait = waitForDOM(document, "#panel-2 .tree-section", 2);
+  wait = waitForDOM(document, "#params-panel .tree-section", 2);
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
-  document.querySelector("#tab-2 a").click();
+  document.querySelector("#params-tab").click();
   yield wait;
   yield testParamsTab("urlencoded");
 
   // Wait for all tree sections and editor updated by react
-  let waitForSections = waitForDOM(document, "#panel-2 .tree-section", 2);
-  let waitForEditor = waitForDOM(document, "#panel-2 .editor-mount iframe");
+  let waitForSections = waitForDOM(document, "#params-panel .tree-section", 2);
+  let waitForEditor = waitForDOM(document, "#params-panel .editor-mount iframe");
   RequestsMenu.selectedIndex = 1;
   let [, editorFrames] = yield Promise.all([waitForSections, waitForEditor]);
   yield once(editorFrames[0], "DOMContentLoaded");
   yield waitForDOM(editorFrames[0].contentDocument, ".CodeMirror-code");
   yield testParamsTab("multipart");
 
   return teardown(monitor);
 
   function* testParamsTab(type) {
-    let tabpanel = document.querySelector("#panel-2");
+    let tabpanel = document.querySelector("#params-panel");
 
     function checkVisibility(box) {
       is(!tabpanel.querySelector(".treeTable"), !box.includes("params"),
         "The request params doesn't have the indended visibility.");
       is(tabpanel.querySelector(".editor-mount") === null,
         !box.includes("editor"),
         "The request post data doesn't have the indended visibility.");
     }
--- a/devtools/client/netmonitor/test/browser_net_post-data-02.js
+++ b/devtools/client/netmonitor/test/browser_net_post-data-02.js
@@ -21,23 +21,23 @@ add_task(function* () {
 
   let wait = waitForNetworkEvents(monitor, 0, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   // Wait for all tree view updated by react
-  wait = waitForDOM(document, "#panel-2 .tree-section");
+  wait = waitForDOM(document, "#params-panel .tree-section");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
-  document.querySelector("#tab-2 a").click();
+  document.querySelector("#params-tab").click();
   yield wait;
 
-  let tabpanel = document.querySelector("#panel-2");
+  let tabpanel = document.querySelector("#params-panel");
 
   ok(tabpanel.querySelector(".treeTable"),
     "The request params doesn't have the indended visibility.");
   ok(tabpanel.querySelector(".editor-mount") === null,
     "The request post data doesn't have the indended visibility.");
 
   is(tabpanel.querySelectorAll(".tree-section").length, 1,
     "There should be 1 tree sections displayed in this tabpanel.");
--- a/devtools/client/netmonitor/test/browser_net_post-data-03.js
+++ b/devtools/client/netmonitor/test/browser_net_post-data-03.js
@@ -21,23 +21,23 @@ add_task(function* () {
 
   let wait = waitForNetworkEvents(monitor, 0, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   // Wait for all tree view updated by react
-  wait = waitForDOM(document, "#panel-0");
+  wait = waitForDOM(document, "#headers-panel");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
-  document.querySelector("#tab-0 a").click();
+  document.querySelector("#headers-tab").click();
   yield wait;
 
-  let tabpanel = document.querySelector("#panel-0");
+  let tabpanel = document.querySelector("#headers-panel");
 
   is(tabpanel.querySelectorAll(".tree-section .treeLabel").length, 3,
     "There should be 3 header sections displayed in this tabpanel.");
 
   is(tabpanel.querySelectorAll(".tree-section .treeLabel")[2].textContent,
     L10N.getStr("requestHeadersFromUpload") + " (" +
     L10N.getFormatStr("networkMenu.sizeB", 74) + ")",
     "The request headers from upload section doesn't have the correct title.");
@@ -52,21 +52,21 @@ add_task(function* () {
   is(values[values.length - 2].textContent, "\"application/x-www-form-urlencoded\"",
     "The first request header value was incorrect.");
   is(labels[labels.length - 1].textContent, "custom-header",
     "The second request header name was incorrect.");
   is(values[values.length - 1].textContent, "\"hello world!\"",
     "The second request header value was incorrect.");
 
   // Wait for all tree sections updated by react
-  wait = waitForDOM(document, "#panel-2 .tree-section");
-  document.querySelector("#tab-2 a").click();
+  wait = waitForDOM(document, "#params-panel .tree-section");
+  document.querySelector("#params-tab").click();
   yield wait;
 
-  tabpanel = document.querySelector("#panel-2");
+  tabpanel = document.querySelector("#params-panel");
 
   ok(tabpanel.querySelector(".treeTable"),
     "The params tree view should be displayed.");
   ok(tabpanel.querySelector(".editor-mount") === null,
     "The post data shouldn't be displayed.");
 
   is(tabpanel.querySelector(".tree-section .treeLabel").textContent,
     L10N.getStr("paramsFormData"),
--- a/devtools/client/netmonitor/test/browser_net_post-data-04.js
+++ b/devtools/client/netmonitor/test/browser_net_post-data-04.js
@@ -21,23 +21,23 @@ add_task(function* () {
 
   let wait = waitForNetworkEvents(monitor, 0, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   // Wait for all tree view updated by react
-  wait = waitForDOM(document, "#panel-2 .tree-section");
+  wait = waitForDOM(document, "#params-panel .tree-section");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
-  document.querySelector("#tab-2 a").click();
+  document.querySelector("#params-tab").click();
   yield wait;
 
-  let tabpanel = document.querySelector("#panel-2");
+  let tabpanel = document.querySelector("#params-panel");
 
   ok(tabpanel.querySelector(".treeTable"),
     "The request params doesn't have the indended visibility.");
   ok(tabpanel.querySelector(".editor-mount") === null,
     "The request post data doesn't have the indended visibility.");
 
   is(tabpanel.querySelectorAll(".tree-section").length, 1,
     "There should be 1 tree sections displayed in this tabpanel.");
--- a/devtools/client/netmonitor/test/browser_net_security-details.js
+++ b/devtools/client/netmonitor/test/browser_net_security-details.js
@@ -17,23 +17,23 @@ add_task(function* () {
   info("Performing a secure request.");
   const REQUESTS_URL = "https://example.com" + CORS_SJS_PATH;
   let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, REQUESTS_URL, function* (url) {
     content.wrappedJSObject.performRequests(1, url);
   });
   yield wait;
 
-  wait = waitForDOM(document, "#panel-5");
+  wait = waitForDOM(document, "#security-panel");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelector("#details-pane-toggle"));
-  document.querySelector("#tab-5 a").click();
+  document.querySelector("#security-tab").click();
   yield wait;
 
-  let tabpanel = document.querySelector("#panel-5");
+  let tabpanel = document.querySelector("#security-panel");
   let textboxes = tabpanel.querySelectorAll(".textbox-input");
 
   // Connection
   // The protocol will be TLS but the exact version depends on which protocol
   // the test server example.com supports.
   let protocol = textboxes[0].value;
   ok(protocol.startsWith("TLS"), "The protocol " + protocol + " seems valid.");
 
--- a/devtools/client/netmonitor/test/browser_net_security-error.js
+++ b/devtools/client/netmonitor/test/browser_net_security-error.js
@@ -16,20 +16,20 @@ add_task(function* () {
   info("Requesting a resource that has a certificate problem.");
 
   let wait = waitForSecurityBrokenNetworkEvent();
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests(1, "https://nocert.example.com");
   });
   yield wait;
 
-  wait = waitForDOM(document, "#panel-5");
+  wait = waitForDOM(document, "#security-panel");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelector("#details-pane-toggle"));
-  document.querySelector("#tab-5 a").click();
+  document.querySelector("#security-tab").click();
   yield wait;
 
   let errormsg = document.querySelector(".security-info-value");
   isnot(errormsg.textContent, "", "Error message is not empty.");
 
   return teardown(monitor);
 
   /**
--- a/devtools/client/netmonitor/test/browser_net_security-icon-click.js
+++ b/devtools/client/netmonitor/test/browser_net_security-icon-click.js
@@ -19,17 +19,17 @@ add_task(function* () {
   yield performRequestAndWait("https://example.com" + CORS_SJS_PATH + "?request_1");
 
   is(RequestsMenu.itemCount, 2, "Two events event logged.");
 
   yield clickAndTestSecurityIcon();
 
   info("Selecting headers panel again.");
   EventUtils.sendMouseEvent({ type: "mousedown" },
-    document.querySelector("#tab-0 a"));
+    document.querySelector("#headers-tab"));
 
   info("Sorting the items by filename.");
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector("#requests-menu-file-button"));
 
   info("Testing that security icon can be clicked after the items were sorted.");
   yield clickAndTestSecurityIcon();
 
@@ -42,15 +42,16 @@ add_task(function* () {
     });
     return wait;
   }
 
   function* clickAndTestSecurityIcon() {
     let item = RequestsMenu.getItemAtIndex(0);
     let icon = document.querySelector(".requests-security-state-icon");
 
-    let wait = waitForDOM(document, "#panel-5");
+    let wait = waitForDOM(document, "#security-panel");
     info("Clicking security icon of the first request and waiting for panel update.");
     EventUtils.synthesizeMouseAtCenter(icon, {}, monitor.panelWin);
     yield wait;
-    ok(document.querySelector("#tab-5.is-active"), "Security tab is selected.");
+
+    ok(document.querySelector("#security-tab[aria-selected=true]"), "Security tab is selected.");
   }
 });
--- a/devtools/client/netmonitor/test/browser_net_security-tab-deselect.js
+++ b/devtools/client/netmonitor/test/browser_net_security-tab-deselect.js
@@ -31,19 +31,19 @@ add_task(function* () {
   info("Selecting secure request.");
   wait = waitForDOM(document, ".tabs");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll(".request-list-item")[0]);
   yield wait;
 
   info("Selecting security tab.");
   EventUtils.sendMouseEvent({ type: "mousedown" },
-    document.querySelector("#tab-5 a"));
+    document.querySelector("#security-tab"));
 
   info("Selecting insecure request.");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll(".request-list-item")[1]);
 
-  ok(document.querySelector("#tab-0.is-active"),
+  ok(document.querySelector("#headers-tab[aria-selected=true]"),
     "Selected tab was reset when selected security tab was hidden.");
 
   return teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/browser_net_security-tab-visibility.js
+++ b/devtools/client/netmonitor/test/browser_net_security-tab-visibility.js
@@ -53,42 +53,42 @@ add_task(function* () {
     info("Waiting for new network event.");
     yield onNewItem;
 
     info("Selecting the request.");
     RequestsMenu.selectedIndex = 0;
 
     is(RequestsMenu.selectedItem.securityState, undefined,
        "Security state has not yet arrived.");
-    is(!!document.querySelector("#tab-5"), testcase.visibleOnNewEvent,
+    is(!!document.querySelector("#security-tab"), testcase.visibleOnNewEvent,
       "Security tab is " + (testcase.visibleOnNewEvent ? "visible" : "hidden") +
       " after new request was added to the menu.");
-    is(!!document.querySelector("#panel-5"), testcase.visibleOnNewEvent,
+    is(!!document.querySelector("#security-panel"), testcase.visibleOnNewEvent,
       "Security panel is " + (testcase.visibleOnNewEvent ? "visible" : "hidden") +
       " after new request was added to the menu.");
 
     info("Waiting for security information to arrive.");
     yield onSecurityInfo;
 
     ok(RequestsMenu.selectedItem.securityState,
        "Security state arrived.");
-    is(!!document.querySelector("#tab-5"), testcase.visibleOnSecurityInfo,
+    is(!!document.querySelector("#security-tab"), testcase.visibleOnSecurityInfo,
        "Security tab is " + (testcase.visibleOnSecurityInfo ? "visible" : "hidden") +
        " after security information arrived.");
-    is(!!document.querySelector("#panel-5"), testcase.visibleOnSecurityInfo,
+    is(!!document.querySelector("#security-panel"), testcase.visibleOnSecurityInfo,
       "Security panel is " + (testcase.visibleOnSecurityInfo? "visible" : "hidden") +
       " after security information arrived.");
 
     info("Waiting for request to complete.");
     yield onComplete;
 
-    is(!!document.querySelector("#tab-5"), testcase.visibleOnceComplete,
+    is(!!document.querySelector("#security-tab"), testcase.visibleOnceComplete,
        "Security tab is " + (testcase.visibleOnceComplete ? "visible" : "hidden") +
        " after request has been completed.");
-    is(!!document.querySelector("#panel-5"), testcase.visibleOnceComplete,
+    is(!!document.querySelector("#security-panel"), testcase.visibleOnceComplete,
       "Security panel is " + (testcase.visibleOnceComplete? "visible" : "hidden") +
       " after request has been completed.");
 
     info("Clearing requests.");
     RequestsMenu.clear();
   }
 
   return teardown(monitor);
--- a/devtools/client/netmonitor/test/browser_net_security-warnings.js
+++ b/devtools/client/netmonitor/test/browser_net_security-warnings.js
@@ -33,20 +33,20 @@ add_task(function* () {
     yield wait;
 
     info("Selecting the request.");
     wait = waitForDOM(document, ".tabs");
     EventUtils.sendMouseEvent({ type: "mousedown" },
       document.querySelectorAll(".request-list-item")[0]);
     yield wait;
 
-    if (!document.querySelector("#tab-5.is-active")) {
+    if (!document.querySelector("#security-tab[aria-selected=true]")) {
       info("Selecting security tab.");
-      wait = waitForDOM(document, "#panel-5 .properties-view");
-      document.querySelector("#tab-5 a").click();
+      wait = waitForDOM(document, "#security-panel .properties-view");
+      document.querySelector("#security-tab").click();
       yield wait;
     }
 
     is(document.querySelector("#security-warning-cipher"),
       test.warnCipher,
       "Cipher suite warning is hidden.");
 
     RequestsMenu.clear();
--- a/devtools/client/netmonitor/test/browser_net_status-codes.js
+++ b/devtools/client/netmonitor/test/browser_net_status-codes.js
@@ -140,44 +140,44 @@ add_task(function* () {
       counter++;
     }
   }
 
   /**
    * A function that tests "Headers" panel contains correct information.
    */
   function* testHeaders(data, index) {
-    let wait = waitForDOM(document, "#panel-0");
+    let wait = waitForDOM(document, "#headers-panel");
     EventUtils.sendMouseEvent({ type: "mousedown" },
       document.querySelectorAll(".request-list-item")[index]);
     yield wait;
 
-    let panel = document.querySelector("#panel-0");
+    let panel = document.querySelector("#headers-panel");
     let summaryValues = panel.querySelectorAll(".tabpanel-summary-value.textbox-input");
     let { method, uri, details: { status, statusText } } = data;
 
     is(summaryValues[0].value, uri, "The url summary value is incorrect.");
     is(summaryValues[1].value, method, "The method summary value is incorrect.");
     is(panel.querySelector(".requests-menu-status-icon").dataset.code, status,
       "The status summary code is incorrect.");
     is(summaryValues[3].value, status + " " + statusText,
       "The status summary value is incorrect.");
   }
 
   /**
    * A function that tests "Params" panel contains correct information.
    */
   function* testParams(data, index) {
-    let wait = waitForDOM(document, "#panel-2 .properties-view");
+    let wait = waitForDOM(document, "#params-panel .properties-view");
     EventUtils.sendMouseEvent({ type: "mousedown" },
       document.querySelectorAll(".request-list-item")[index]);
-    document.querySelector("#tab-2 a").click();
+    document.querySelector("#params-tab").click();
     yield wait;
 
-    let panel = document.querySelector("#panel-2");
+    let panel = document.querySelector("#params-panel");
     let statusParamValue = data.uri.split("=").pop();
     let statusParamShownValue = "\"" + statusParamValue + "\"";
     let treeSections = panel.querySelectorAll(".tree-section");
     debugger
 
     is(treeSections.length, 1,
       "There should be 1 param section displayed in this panel.");
     is(panel.querySelectorAll("tr:not(.tree-section).treeRow").length, 1,
--- a/devtools/client/netmonitor/test/browser_net_streaming-response.js
+++ b/devtools/client/netmonitor/test/browser_net_streaming-response.js
@@ -35,49 +35,50 @@ add_task(function* () {
   REQUESTS.forEach(([ fmt ], i) => {
     verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(i),
       "GET", CONTENT_TYPE_SJS + "?fmt=" + fmt, {
         status: 200,
         statusText: "OK"
       });
   });
 
-  wait = waitForDOM(document, "#panel-3");
+  wait = waitForDOM(document, "#response-panel");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
-  document.querySelector("#tab-3 a").click();
+  document.querySelector("#response-tab").click();
   yield wait;
 
   RequestsMenu.selectedIndex = -1;
 
   yield selectIndexAndWaitForEditor(0);
   // the hls-m3u8 part
   testEditorContent(REQUESTS[0]);
 
   yield selectIndexAndWaitForEditor(1);
   // the mpeg-dash part
   testEditorContent(REQUESTS[1]);
 
   return teardown(monitor);
 
   function* selectIndexAndWaitForEditor(index) {
-    let editor = document.querySelector("#panel-3 .editor-mount iframe");
+    let editor = document.querySelector("#response-panel .editor-mount iframe");
     if (!editor) {
-      let waitDOM = waitForDOM(document, "#panel-3 .editor-mount iframe");
+      let waitDOM = waitForDOM(document, "#response-panel .editor-mount iframe");
       RequestsMenu.selectedIndex = index;
+      document.querySelector("#response-tab").click();
       [editor] = yield waitDOM;
       yield once(editor, "DOMContentLoaded");
     } else {
       RequestsMenu.selectedIndex = index;
     }
 
     yield waitForDOM(editor.contentDocument, ".CodeMirror-code");
   }
 
   function testEditorContent([ fmt, textRe ]) {
-    let editor = document.querySelector("#panel-3 .editor-mount iframe");
+    let editor = document.querySelector("#response-panel .editor-mount iframe");
     let text = editor.contentDocument
           .querySelector(".CodeMirror-line").textContent;
 
     ok(text.match(textRe),
       "The text shown in the source editor for " + fmt + " is correct.");
   }
 });
--- a/devtools/client/shared/components/tabs/tabbar.js
+++ b/devtools/client/shared/components/tabs/tabbar.js
@@ -20,49 +20,57 @@ const { div } = DOM;
  */
 let Tabbar = createClass({
   displayName: "Tabbar",
 
   propTypes: {
     children: PropTypes.object,
     onSelect: PropTypes.func,
     showAllTabsMenu: PropTypes.bool,
-    tabActive: PropTypes.number,
+    activeTabId: PropTypes.string,
     toolbox: PropTypes.object,
   },
 
   getDefaultProps: function () {
     return {
       showAllTabsMenu: false,
-      tabActive: 0,
     };
   },
 
   getInitialState: function () {
-    let { children } = this.props;
+    let { activeTabId, children = [] } = this.props;
+    let tabs = this.createTabs(children);
+    let activeTab = tabs.findIndex((tab, index) => tab.id === activeTabId);
+
     return {
-      tabs: children ? this.createTabs(children) : [],
-      activeTab: 0
+      activeTab: activeTab === -1 ? 0 : activeTab,
+      tabs,
     };
   },
 
   componentWillReceiveProps: function (nextProps) {
-    let { children } = nextProps;
+    let { activeTabId, children = [] } = nextProps;
+    let tabs = this.createTabs(children);
+    let activeTab = tabs.findIndex((tab, index) => tab.id === activeTabId);
 
-    if (children && children !== this.props.children) {
-      this.setState({ tabs: this.createTabs(children) });
+    if (activeTab !== this.state.activeTab ||
+        (children !== this.props.children)) {
+      this.setState({
+        activeTab: activeTab === -1 ? 0 : activeTab,
+        tabs,
+      });
     }
   },
 
   createTabs: function (children) {
     return children
       .filter((panel) => panel)
       .map((panel, index) =>
         Object.assign({}, children[index], {
-          id: index,
+          id: panel.props.id || index,
           panel,
           title: panel.props.title,
         })
       );
   },
 
   // Public API
 
@@ -132,17 +140,17 @@ let Tabbar = createClass({
     });
   },
 
   // Helpers
 
   getTabIndex: function (tabId) {
     let tabIndex = -1;
     this.state.tabs.forEach((tab, index) => {
-      if (tab.id == tabId) {
+      if (tab.id === tabId) {
         tabIndex = index;
       }
     });
     return tabIndex;
   },
 
   getTabId: function (index) {
     return this.state.tabs[index].id;
@@ -209,17 +217,17 @@ let Tabbar = createClass({
   render: function () {
     let tabs = this.state.tabs.map((tab) => this.renderTab(tab));
 
     return (
       div({className: "devtools-sidebar-tabs"},
         Tabs({
           onAllTabsMenuClick: this.onAllTabsMenuClick,
           showAllTabsMenu: this.props.showAllTabsMenu,
-          tabActive: this.props.tabActive || this.state.activeTab,
+          tabActive: this.state.activeTab,
           onAfterChange: this.onTabChanged,
         },
           tabs
         )
       )
     );
   },
 });
--- a/devtools/client/shared/components/tabs/tabs.js
+++ b/devtools/client/shared/components/tabs/tabs.js
@@ -100,18 +100,19 @@ define(function (require, exports, modul
     componentWillReceiveProps: function (nextProps) {
       let { children, tabActive } = nextProps;
 
       // Check type of 'tabActive' props to see if it's valid
       // (it's 0-based index).
       if (typeof tabActive === "number") {
         let panels = children.filter((panel) => panel);
 
-        // Reset to index 0 if index larger than number of panels
-        tabActive = tabActive < panels.length ? tabActive : 0;
+        // Reset to index 0 if index overflows the range of panel array
+        tabActive = (tabActive < panels.length && tabActive >= 0) ?
+          tabActive : 0;
 
         let created = [...this.state.created];
         created[tabActive] = true;
 
         this.setState({
           created,
           tabActive,
         });
@@ -219,48 +220,47 @@ define(function (require, exports, modul
         throw new Error("There must be at least one Tab");
       }
 
       if (!Array.isArray(this.props.children)) {
         this.props.children = [this.props.children];
       }
 
       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);
+        .map((tab) => typeof tab === "function" ? tab() : tab)
+        .filter((tab) => tab)
+        .map((tab, index) => {
+          let id = tab.props.id;
+          let ref = "tab-menu-" + index;
           let title = tab.props.title;
           let tabClassName = tab.props.className;
           let isTabSelected = this.state.tabActive === index;
 
-          let classes = [
+          let className = [
             "tabs-menu-item",
             tabClassName,
             isTabSelected ? "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,
+              className,
               key: index,
-              id: "tab-" + index,
-              className: classes,
+              ref,
               role: "presentation",
             },
               DOM.a({
-                tabIndex: this.state.tabActive === index ? 0 : -1,
-                "aria-controls": "panel-" + index,
+                id: id ? id + "-tab" : "tab-" + index,
+                tabIndex: isTabSelected ? 0 : -1,
+                "aria-controls": id ? id + "-panel" : "panel-" + index,
                 "aria-selected": isTabSelected,
                 role: "tab",
                 onClick: this.onClickTab.bind(this, index),
               },
                 title
               )
             )
           );
@@ -292,60 +292,57 @@ define(function (require, exports, modul
 
       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;
+        .map((tab) => typeof tab === "function" ? tab() : tab)
+        .filter((tab) => tab)
+        .map((tab, index) => {
+          let selected = selectedIndex === index;
+          let id = tab.props.id;
 
           // 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({
+              id: id ? id + "-panel" : "panel-" + index,
               key: index,
-              id: "panel-" + index,
               style: style,
               className: "tab-panel-box",
               role: "tabpanel",
-              "aria-labelledby": "tab-" + index,
+              "aria-labelledby": id ? id + "-tab" : "tab-" + index,
             },
               (selected || this.state.created[index]) ? tab : null
             )
           );
         });
 
       return (
         DOM.div({className: "panels"},
           panels
         )
       );
     },
 
     render: function () {
-      let classNames = ["tabs", this.props.className].join(" ");
-
       return (
-        DOM.div({className: classNames},
+        DOM.div({ className: ["tabs", this.props.className].join(" ") },
           this.renderMenuItems(),
-          this.renderPanels()
+          this.renderPanels(),
         )
       );
     },
   });
 
   /**
    * Renders simple tab 'panel'.
    */
--- a/devtools/client/shared/components/test/mochitest/test_tabs_accessibility.html
+++ b/devtools/client/shared/components/test/mochitest/test_tabs_accessibility.html
@@ -35,38 +35,38 @@ window.onload = Task.async(function* () 
     yield addTabWithPanel(0);
     yield addTabWithPanel(1);
 
     const tabAnchors = tabbarEl.querySelectorAll("li.tabs-menu-item a");
 
     is(tabAnchors[0].parentElement.getAttribute("role"), "presentation", "li role is set correctly");
     is(tabAnchors[0].getAttribute("role"), "tab", "Anchor role is set correctly");
     is(tabAnchors[0].getAttribute("aria-selected"), "true", "Anchor aria-selected is set correctly by default");
-    is(tabAnchors[0].getAttribute("aria-controls"), "panel-0", "Anchor aria-controls is set correctly");
+    is(tabAnchors[0].getAttribute("aria-controls"), "sidebar-0-panel", "Anchor aria-controls is set correctly");
     is(tabAnchors[1].parentElement.getAttribute("role"), "presentation", "li role is set correctly");
     is(tabAnchors[1].getAttribute("role"), "tab", "Anchor role is set correctly");
     is(tabAnchors[1].getAttribute("aria-selected"), "false", "Anchor aria-selected is set correctly by default");
-    is(tabAnchors[1].getAttribute("aria-controls"), "panel-1", "Anchor aria-controls is set correctly");
+    is(tabAnchors[1].getAttribute("aria-controls"), "sidebar-1-panel", "Anchor aria-controls is set correctly");
 
     yield setState(tabbarReact, Object.assign({}, tabbarReact.state, {
       activeTab: 1
     }));
 
     is(tabAnchors[0].getAttribute("aria-selected"), "false", "Anchor aria-selected is reset correctly");
     is(tabAnchors[1].getAttribute("aria-selected"), "true", "Anchor aria-selected is reset correctly");
 
     function addTabWithPanel(tabId) {
       // Setup for InspectorTabPanel
       let panel = document.createElement("div");
-      panel.id = `sidebar-panel-${tabId}`;
+      panel.id = `sidebar-${tabId}`;
       document.body.appendChild(panel);
 
       return setState(tabbarReact, Object.assign({}, tabbarReact.state, {
         tabs: tabbarReact.state.tabs.concat({
-          id: `sidebar-panel-${tabId}`,
+          id: `sidebar-${tabId}`,
           title: `tab-${tabId}`,
           panel: InspectorTabPanel
         }),
       }));
     }
   } catch(e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {