Bug 1309188 - Implement Security Panel r?Honza draft
authorRicky Chien <rchien@mozilla.com>
Thu, 24 Nov 2016 21:50:49 +0800
changeset 449845 b102d6ae228d52b28f23781760a5003a50357f92
parent 449758 b1ab720c6d3e412ede797b08dfe63dca170f6ee0
child 539612 9e336369df090020cdf1082567e2c822beac64c4
push id38699
push userbmo:rchien@mozilla.com
push dateThu, 15 Dec 2016 09:08:40 +0000
reviewersHonza
bugs1309188
milestone53.0a1
Bug 1309188 - Implement Security Panel r?Honza MozReview-Commit-ID: LgYB97ByYdR
devtools/client/netmonitor/details-view.js
devtools/client/netmonitor/netmonitor.xul
devtools/client/netmonitor/shared/components/moz.build
devtools/client/netmonitor/shared/components/security-panel.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-warnings.js
devtools/client/shared/components/tree/tree-view.js
devtools/client/themes/netmonitor.css
--- a/devtools/client/netmonitor/details-view.js
+++ b/devtools/client/netmonitor/details-view.js
@@ -19,22 +19,22 @@ const { EVENTS } = require("./events");
 const { L10N } = require("./l10n");
 const { Filters } = require("./filter-predicates");
 const {
   decodeUnicodeUrl,
   formDataURI,
   getFormDataSections,
   getUrlBaseName,
   getUrlQuery,
-  getUrlHost,
   parseQueryString,
 } = require("./request-utils");
 const { createFactory } = require("devtools/client/shared/vendor/react");
 const ReactDOM = require("devtools/client/shared/vendor/react-dom");
 const Provider = createFactory(require("devtools/client/shared/vendor/react-redux").Provider);
+const SecurityPanel = createFactory(require("./shared/components/security-panel"));
 const TimingsPanel = createFactory(require("./shared/components/timings-panel"));
 
 // 100 KB in bytes
 const SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE = 102400;
 const HEADERS_SIZE_DECIMALS = 3;
 const CONTENT_MIME_TYPE_MAPPINGS = {
   "/ecmascript": Editor.modes.js,
   "/javascript": Editor.modes.js,
@@ -88,16 +88,23 @@ DetailsView.prototype = {
   },
 
   /**
    * Initialization function, called when the network monitor is started.
    */
   initialize: function (store) {
     dumpn("Initializing the DetailsView");
 
+    this._securityPanelNode = $("#react-security-tabpanel-hook");
+
+    ReactDOM.render(Provider(
+      { store },
+      SecurityPanel()
+    ), this._securityPanelNode);
+
     this._timingsPanelNode = $("#react-timings-tabpanel-hook");
 
     ReactDOM.render(Provider(
       { store },
       TimingsPanel()
     ), this._timingsPanelNode);
 
     this.widget = $("#event-details-pane");
@@ -140,16 +147,17 @@ DetailsView.prototype = {
     $("tabpanels", this.widget).addEventListener("select", this._onTabSelect);
   },
 
   /**
    * Destruction function, called when the network monitor is closed.
    */
   destroy: function () {
     dumpn("Destroying the DetailsView");
+    ReactDOM.unmountComponentAtNode(this._securityPanelNode);
     ReactDOM.unmountComponentAtNode(this._timingsPanelNode);
     this.sidebar.destroy();
     $("tabpanels", this.widget).removeEventListener("select",
       this._onTabSelect);
   },
 
   /**
    * Populates this view with the specified data.
@@ -250,20 +258,16 @@ DetailsView.prototype = {
             src.requestHeaders,
             src.requestHeadersFromUploadStream,
             src.requestPostData);
           break;
         // "Response"
         case 3:
           yield view._setResponseBody(src.url, src.responseContent);
           break;
-        // "Security"
-        case 5:
-          yield view._setSecurityInfo(src.securityInfo, src.url);
-          break;
         // "Preview"
         case 6:
           yield view._setHtmlPreview(src.responseContent);
           break;
       }
       viewState.updating[tab] = false;
     }).then(() => {
       if (tab == this.widget.selectedIndex) {
@@ -712,122 +716,16 @@ DetailsView.prototype = {
     let iframe = $("#response-preview");
     iframe.contentDocument.docShell.allowJavascript = false;
     iframe.contentDocument.documentElement.innerHTML = responseBody;
 
     window.emit(EVENTS.RESPONSE_HTML_PREVIEW_DISPLAYED);
     return undefined;
   }),
 
-  /**
-   * Sets the security information shown in this view.
-   *
-   * @param object securityInfo
-   *        The data received from server
-   * @param string url
-   *        The URL of this request
-   * @return object
-   *        A promise that is resolved when the security info is rendered.
-   */
-  _setSecurityInfo: Task.async(function* (securityInfo, url) {
-    if (!securityInfo) {
-      // We don't have security info. This could mean one of two things:
-      // 1) This connection is not secure and this tab is not visible and thus
-      //    we shouldn't be here.
-      // 2) We have already received securityState and the tab is visible BUT
-      //    the rest of the information is still on its way. Once it arrives
-      //    this method is called again.
-      return;
-    }
-
-    /**
-     * A helper that sets value and tooltiptext attributes of an element to
-     * specified value.
-     *
-     * @param string selector
-     *        A selector for the element.
-     * @param string value
-     *        The value to set. If this evaluates to false a placeholder string
-     *        <Not Available> is used instead.
-     */
-    function setValue(selector, value) {
-      let label = $(selector);
-      if (!value) {
-        label.setAttribute("value", L10N.getStr(
-          "netmonitor.security.notAvailable"));
-        label.setAttribute("tooltiptext", label.getAttribute("value"));
-      } else {
-        label.setAttribute("value", value);
-        label.setAttribute("tooltiptext", value);
-      }
-    }
-
-    let errorbox = $("#security-error");
-    let infobox = $("#security-information");
-
-    if (securityInfo.state === "secure" || securityInfo.state === "weak") {
-      infobox.hidden = false;
-      errorbox.hidden = true;
-
-      // Warning icons
-      let cipher = $("#security-warning-cipher");
-
-      if (securityInfo.state === "weak") {
-        cipher.hidden = securityInfo.weaknessReasons.indexOf("cipher") === -1;
-      } else {
-        cipher.hidden = true;
-      }
-
-      let enabledLabel = L10N.getStr("netmonitor.security.enabled");
-      let disabledLabel = L10N.getStr("netmonitor.security.disabled");
-
-      // Connection parameters
-      setValue("#security-protocol-version-value",
-        securityInfo.protocolVersion);
-      setValue("#security-ciphersuite-value", securityInfo.cipherSuite);
-
-      // Host header
-      let domain = getUrlHost(url);
-      let hostHeader = L10N.getFormatStr("netmonitor.security.hostHeader",
-        domain);
-      setValue("#security-info-host-header", hostHeader);
-
-      // Parameters related to the domain
-      setValue("#security-http-strict-transport-security-value",
-                securityInfo.hsts ? enabledLabel : disabledLabel);
-
-      setValue("#security-public-key-pinning-value",
-                securityInfo.hpkp ? enabledLabel : disabledLabel);
-
-      // Certificate parameters
-      let cert = securityInfo.cert;
-      setValue("#security-cert-subject-cn", cert.subject.commonName);
-      setValue("#security-cert-subject-o", cert.subject.organization);
-      setValue("#security-cert-subject-ou", cert.subject.organizationalUnit);
-
-      setValue("#security-cert-issuer-cn", cert.issuer.commonName);
-      setValue("#security-cert-issuer-o", cert.issuer.organization);
-      setValue("#security-cert-issuer-ou", cert.issuer.organizationalUnit);
-
-      setValue("#security-cert-validity-begins", cert.validity.start);
-      setValue("#security-cert-validity-expires", cert.validity.end);
-
-      setValue("#security-cert-sha1-fingerprint", cert.fingerprint.sha1);
-      setValue("#security-cert-sha256-fingerprint", cert.fingerprint.sha256);
-    } else {
-      infobox.hidden = true;
-      errorbox.hidden = false;
-
-      // Strip any HTML from the message.
-      let plain = new DOMParser().parseFromString(securityInfo.errorMessage,
-        "text/html");
-      setValue("#security-error-message", plain.body.textContent);
-    }
-  }),
-
   _dataSrc: null,
   _headers: null,
   _cookies: null,
   _params: null,
   _json: null,
   _paramsQueryString: "",
   _paramsFormData: "",
   _paramsPostPayload: "",
--- a/devtools/client/netmonitor/netmonitor.xul
+++ b/devtools/client/netmonitor/netmonitor.xul
@@ -262,214 +262,18 @@
               </tabpanel>
               <tabpanel id="timings-tabpanel"
                         class="tabpanel-content">
                 <html:div xmlns="http://www.w3.org/1999/xhtml"
                           id="react-timings-tabpanel-hook"/>
               </tabpanel>
               <tabpanel id="security-tabpanel"
                         class="tabpanel-content">
-                  <vbox id="security-error"
-                        class="tabpanel-summary-container"
-                        flex="1">
-                    <label class="plain tabpanel-summary-label"
-                           data-localization="content=netmonitor.security.error"/>
-                    <hbox class="security-info-section"
-                          flex="1">
-                      <textbox id="security-error-message"
-                               class="plain"
-                               flex="1"
-                               multiline="true"
-                               readonly="true"/>
-                    </hbox>
-                  </vbox>
-                  <vbox id="security-information"
-                        flex="1">
-                    <vbox id="security-info-connection"
-                          class="tabpanel-summary-container">
-                      <label class="plain tabpanel-summary-label"
-                             data-localization="content=netmonitor.security.connection"/>
-                      <vbox class="security-info-section">
-                        <hbox id="security-protocol-version"
-                              class="tabpanel-summary-container"
-                              align="baseline">
-                          <label class="plain tabpanel-summary-label"
-                                 data-localization="content=netmonitor.security.protocolVersion"/>
-                          <textbox id="security-protocol-version-value"
-                                   class="plain tabpanel-summary-value devtools-monospace cropped-textbox"
-                                   flex="1"
-                                   readonly="true"/>
-                        </hbox>
-                        <hbox id="security-ciphersuite"
-                              class="tabpanel-summary-container"
-                              align="baseline">
-                          <label class="plain tabpanel-summary-label"
-                                 data-localization="content=netmonitor.security.cipherSuite"/>
-                          <textbox id="security-ciphersuite-value"
-                                   class="plain tabpanel-summary-value devtools-monospace cropped-textbox"
-                                   flex="1"
-                                   readonly="true"/>
-                          <image class="security-warning-icon"
-                                 id="security-warning-cipher"
-                                 data-localization="tooltiptext=netmonitor.security.warning.cipher" />
-                        </hbox>
-                      </vbox>
-                    </vbox>
-                    <vbox id="security-info-domain"
-                          class="tabpanel-summary-container">
-                      <label class="plain tabpanel-summary-label"
-                             id="security-info-host-header"/>
-                      <vbox class="security-info-section">
-                        <hbox id="security-http-strict-transport-security"
-                              class="tabpanel-summary-container"
-                              align="baseline">
-                          <label class="plain tabpanel-summary-label"
-                                 data-localization="content=netmonitor.security.hsts"/>
-                          <textbox id="security-http-strict-transport-security-value"
-                                   class="plain tabpanel-summary-value devtools-monospace cropped-textbox"
-                                   flex="1"
-                                   readonly="true"/>
-                        </hbox>
-                        <hbox id="security-public-key-pinning"
-                              class="tabpanel-summary-container"
-                              align="baseline">
-                          <label class="plain tabpanel-summary-label"
-                                 data-localization="content=netmonitor.security.hpkp"/>
-                          <textbox id="security-public-key-pinning-value"
-                                   class="plain tabpanel-summary-value devtools-monospace cropped-textbox"
-                                   flex="1"
-                                   readonly="true"/>
-                        </hbox>
-                      </vbox>
-                    </vbox>
-                    <vbox id="security-info-certificate"
-                          class="tabpanel-summary-container">
-                        <label class="plain tabpanel-summary-label"
-                               data-localization="content=netmonitor.security.certificate"/>
-                      <vbox class="security-info-section">
-                        <vbox class="tabpanel-summary-container">
-                          <label class="plain tabpanel-summary-label"
-                                 data-localization="content=certmgr.subjectinfo.label" flex="1"/>
-                        </vbox>
-                        <vbox class="security-info-section">
-                          <hbox class="tabpanel-summary-container"
-                                align="baseline">
-                            <label class="plain tabpanel-summary-label"
-                                   data-localization="content=certmgr.certdetail.cn"/>
-                            <textbox id="security-cert-subject-cn"
-                                     class="plain tabpanel-summary-value devtools-monospace cropped-textbox"
-                                     flex="1"
-                                     readonly="true"/>
-                          </hbox>
-                          <hbox class="tabpanel-summary-container"
-                                align="baseline">
-                            <label class="plain tabpanel-summary-label"
-                                   data-localization="content=certmgr.certdetail.o"/>
-                            <textbox id="security-cert-subject-o"
-                                     class="plain tabpanel-summary-value devtools-monospace cropped-textbox"
-                                     flex="1"
-                                     readonly="true"/>
-                          </hbox>
-                          <hbox class="tabpanel-summary-container"
-                                align="baseline">
-                            <label class="plain tabpanel-summary-label"
-                                   data-localization="content=certmgr.certdetail.ou"/>
-                            <textbox id="security-cert-subject-ou"
-                                     class="plain tabpanel-summary-value devtools-monospace cropped-textbox"
-                                     flex="1"
-                                     readonly="true"/>
-                          </hbox>
-                        </vbox>
-                        <vbox class="tabpanel-summary-container">
-                          <label class="plain tabpanel-summary-label"
-                                 data-localization="content=certmgr.issuerinfo.label"
-                                 flex="1"/>
-                        </vbox>
-                        <vbox class="security-info-section">
-                          <hbox class="tabpanel-summary-container"
-                                align="baseline">
-                            <label class="plain tabpanel-summary-label"
-                                   data-localization="content=certmgr.certdetail.cn"/>
-                            <textbox id="security-cert-issuer-cn"
-                                     class="plain tabpanel-summary-value devtools-monospace cropped-textbox"
-                                     flex="1"
-                                     readonly="true"/>
-                          </hbox>
-                          <hbox class="tabpanel-summary-container"
-                                align="baseline">
-                            <label class="plain tabpanel-summary-label"
-                                   data-localization="content=certmgr.certdetail.o"/>
-                            <textbox id="security-cert-issuer-o"
-                                     class="plain tabpanel-summary-value devtools-monospace cropped-textbox"
-                                     flex="1"
-                                     readonly="true"/>
-                          </hbox>
-                          <hbox class="tabpanel-summary-container"
-                                align="baseline">
-                            <label class="plain tabpanel-summary-label"
-                                   data-localization="content=certmgr.certdetail.ou"/>
-                            <textbox id="security-cert-issuer-ou"
-                                     class="plain tabpanel-summary-value devtools-monospace cropped-textbox"
-                                     flex="1"
-                                     readonly="true"/>
-                          </hbox>
-                        </vbox>
-                        <vbox class="tabpanel-summary-container">
-                          <label class="plain tabpanel-summary-label"
-                                 data-localization="content=certmgr.periodofvalidity.label"
-                                 flex="1"/>
-                        </vbox>
-                        <vbox class="security-info-section">
-                          <hbox class="tabpanel-summary-container"
-                                align="baseline">
-                            <label class="plain tabpanel-summary-label"
-                                   data-localization="content=certmgr.begins"/>
-                            <textbox id="security-cert-validity-begins"
-                                     class="plain tabpanel-summary-value devtools-monospace cropped-textbox"
-                                     flex="1"
-                                     readonly="true"/>
-                          </hbox>
-                          <hbox class="tabpanel-summary-container"
-                                align="baseline">
-                            <label class="plain tabpanel-summary-label"
-                                   data-localization="content=certmgr.expires"/>
-                            <textbox id="security-cert-validity-expires"
-                                     class="plain tabpanel-summary-value devtools-monospace cropped-textbox"
-                                     flex="1"
-                                     readonly="true"/>
-                          </hbox>
-                        </vbox>
-                        <vbox class="tabpanel-summary-container">
-                          <label class="plain tabpanel-summary-label"
-                                 data-localization="content=certmgr.fingerprints.label"
-                                 flex="1"/>
-                        </vbox>
-                        <vbox class="security-info-section">
-                          <hbox class="tabpanel-summary-container"
-                                align="baseline">
-                            <label class="plain tabpanel-summary-label"
-                                   data-localization="content=certmgr.certdetail.sha256fingerprint"/>
-                            <textbox id="security-cert-sha256-fingerprint"
-                                     class="plain tabpanel-summary-value devtools-monospace cropped-textbox"
-                                     flex="1"
-                                     readonly="true"/>
-                          </hbox>
-                          <hbox class="tabpanel-summary-container"
-                                align="baseline">
-                            <label class="plain tabpanel-summary-label"
-                                   data-localization="content=certmgr.certdetail.sha1fingerprint"/>
-                            <textbox id="security-cert-sha1-fingerprint"
-                                     class="plain tabpanel-summary-value devtools-monospace cropped-textbox"
-                                     flex="1"
-                                     readonly="true"/>
-                          </hbox>
-                        </vbox>
-                      </vbox>
-                    </vbox>
-                  </vbox>
+                <html:div xmlns="http://www.w3.org/1999/xhtml"
+                          id="react-security-tabpanel-hook"/>
               </tabpanel>
               <tabpanel id="preview-tabpanel"
                         class="tabpanel-content">
                 <html:iframe id="response-preview"
                              frameborder="0"
                              sandbox=""/>
               </tabpanel>
             </tabpanels>
--- a/devtools/client/netmonitor/shared/components/moz.build
+++ b/devtools/client/netmonitor/shared/components/moz.build
@@ -1,7 +1,8 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
+    'security-panel.js',
     'timings-panel.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/shared/components/security-panel.js
@@ -0,0 +1,176 @@
+/* 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, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+const TreeView = createFactory(require("devtools/client/shared/components/tree/tree-view"));
+const { L10N } = require("../../l10n");
+const { getUrlHost } = require("../../request-utils");
+const { getSelectedRequest } = require("../../selectors/index");
+
+const { div, input } = DOM;
+
+/*
+ * Security panel component
+ * If the site is being served over HTTPS, you get an extra tab labeled "Security".
+ * This contains details about the secure connection used including the protocol,
+ * the cipher suite, and certificate details
+ */
+function SecurityPanel({
+  securityInfo,
+  url,
+}) {
+  if (!securityInfo || !url) {
+    return div();
+  }
+
+  const notAvailable = L10N.getStr("netmonitor.security.notAvailable");
+  let object;
+
+  if ((securityInfo.state === "secure" || securityInfo.state === "weak")) {
+    const { subject, issuer, validity, fingerprint } = securityInfo.cert;
+    const enabledLabel = L10N.getStr("netmonitor.security.enabled");
+    const disabledLabel = L10N.getStr("netmonitor.security.disabled");
+
+    object = {
+      [L10N.getStr("netmonitor.security.connection")]: {
+        [L10N.getStr("netmonitor.security.protocolVersion")]:
+          securityInfo.protocolVersion || notAvailable,
+        [L10N.getStr("netmonitor.security.cipherSuite")]:
+          securityInfo.cipherSuite || notAvailable,
+      },
+      [L10N.getFormatStr("netmonitor.security.hostHeader", getUrlHost(url))]: {
+        [L10N.getStr("netmonitor.security.hsts")]:
+          securityInfo.hsts ? enabledLabel : disabledLabel,
+        [L10N.getStr("netmonitor.security.hpkp")]:
+          securityInfo.hpkp ? enabledLabel : disabledLabel,
+      },
+      [L10N.getStr("netmonitor.security.certificate")]: {
+        [L10N.getStr("certmgr.subjectinfo.label")]: {
+          [L10N.getStr("certmgr.certdetail.cn")]:
+            subject.commonName || notAvailable,
+          [L10N.getStr("certmgr.certdetail.o")]:
+            subject.organization || notAvailable,
+          [L10N.getStr("certmgr.certdetail.ou")]:
+            subject.organizationUnit || notAvailable,
+        },
+        [L10N.getStr("certmgr.issuerinfo.label")]: {
+          [L10N.getStr("certmgr.certdetail.cn")]:
+            issuer.commonName || notAvailable,
+          [L10N.getStr("certmgr.certdetail.o")]:
+            issuer.organization || notAvailable,
+          [L10N.getStr("certmgr.certdetail.ou")]:
+            issuer.organizationUnit || notAvailable,
+        },
+        [L10N.getStr("certmgr.periodofvalidity.label")]: {
+          [L10N.getStr("certmgr.begins")]:
+            validity.start || notAvailable,
+          [L10N.getStr("certmgr.expires")]:
+            validity.end || notAvailable,
+        },
+        [L10N.getStr("certmgr.fingerprints.label")]: {
+          [L10N.getStr("certmgr.certdetail.sha256fingerprint")]:
+            fingerprint.sha256 || notAvailable,
+          [L10N.getStr("certmgr.certdetail.sha1fingerprint")]:
+            fingerprint.sha1 || notAvailable,
+        },
+      },
+    };
+  } else {
+    object = {
+      [L10N.getStr("netmonitor.security.error")]:
+        new DOMParser().parseFromString(securityInfo.errorMessage, "text/html")
+          .body.textContent || notAvailable
+    };
+  }
+
+  return div({ id: "security-information" },
+    TreeView({
+      object,
+      columns: [{
+        id: "value",
+        width: "100%",
+      }],
+      renderValue: renderValue.bind(null, securityInfo.weaknessReasons),
+      expandedNodes: getExpandedNodes(object),
+      expandableStrings: false,
+    })
+  );
+}
+
+SecurityPanel.displayName = "SecurityPanel";
+
+SecurityPanel.propTypes = {
+  securityInfo: PropTypes.object.isRequired,
+  url: PropTypes.string.isRequired,
+};
+
+function renderValue(weaknessReasons = [], props) {
+  const { member, value } = props;
+
+  // Hide object summary
+  if (typeof member.value === "object") {
+    return null;
+  }
+
+  return div({ className: "security-info-value" },
+    member.name === L10N.getStr("netmonitor.security.error") ?
+      // Display multiline text for security error
+      value
+      :
+      // Display one line selectable text for security details
+      input({
+        className: "textbox-input",
+        readonly: "true",
+        value,
+      })
+    ,
+    weaknessReasons.indexOf("cipher") !== -1 &&
+    member.name === L10N.getStr("netmonitor.security.cipherSuite") ?
+      // Display an extra warning icon after the cipher suite
+      div({
+        id: "security-warning-cipher",
+        className: "security-warning-icon",
+        title: L10N.getStr("netmonitor.security.warning.cipher"),
+      })
+      :
+      null
+  );
+}
+
+function getExpandedNodes(object, path = "", level = 0) {
+  if (typeof object !== "object") {
+    return null;
+  }
+
+  let expandedNodes = new Set();
+  for (let prop in object) {
+    let nodePath = path + "/" + prop;
+    expandedNodes.add(nodePath);
+
+    let nodes = getExpandedNodes(object[prop], nodePath, level + 1);
+    if (nodes) {
+      expandedNodes = new Set([...expandedNodes, ...nodes]);
+    }
+  }
+  return expandedNodes;
+}
+
+module.exports = connect(
+  (state) => {
+    const selectedRequest = getSelectedRequest(state);
+
+    if (selectedRequest) {
+      const { securityInfo, url} = selectedRequest;
+      return {
+        securityInfo,
+        url,
+      };
+    }
+
+    return {};
+  }
+)(SecurityPanel);
--- a/devtools/client/netmonitor/test/browser_net_security-details.js
+++ b/devtools/client/netmonitor/test/browser_net_security-details.js
@@ -4,17 +4,17 @@
 "use strict";
 
 /**
  * Test that Security details tab contains the expected data.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
-  let { $, EVENTS, NetMonitorView } = monitor.panelWin;
+  let { $, $all, EVENTS, NetMonitorView } = monitor.panelWin;
   let { RequestsMenu, NetworkDetails } = NetMonitorView;
   RequestsMenu.lazyUpdate = false;
 
   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);
@@ -31,72 +31,55 @@ add_task(function* () {
   NetworkDetails.widget.selectedIndex = 5;
 
   info("Waiting for security tab to be updated.");
   yield monitor.panelWin.once(EVENTS.TAB_UPDATED);
 
   let errorbox = $("#security-error");
   let infobox = $("#security-information");
 
-  is(errorbox.hidden, true, "Error box is hidden.");
-  is(infobox.hidden, false, "Information box visible.");
+  is(errorbox, null, "Error box is hidden.");
+  ok(infobox, "Information box visible.");
 
+  let textboxes = $all(".textbox-input");
   // Connection
 
   // The protocol will be TLS but the exact version depends on which protocol
   // the test server example.com supports.
-  let protocol = $("#security-protocol-version-value").value;
+  let protocol = textboxes[0].value;
   ok(protocol.startsWith("TLS"), "The protocol " + protocol + " seems valid.");
 
   // The cipher suite used by the test server example.com might change at any
   // moment but all of them should start with "TLS_".
   // http://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml
-  let suite = $("#security-ciphersuite-value").value;
+  let suite = textboxes[1].value;
   ok(suite.startsWith("TLS_"), "The suite " + suite + " seems valid.");
 
   // Host
-  checkLabel("#security-info-host-header", "Host example.com:");
-  checkLabel("#security-http-strict-transport-security-value", "Disabled");
-  checkLabel("#security-public-key-pinning-value", "Disabled");
+  let hostLabel = $all(".treeLabel.objectLabel")[1];
+  is(hostLabel.textContent, "Host example.com:", "Label has the expected value.");
+  is(textboxes[2].value, "Disabled", "Label has the expected value.");
+  is(textboxes[3].value, "Disabled", "Label has the expected value.");
 
   // Cert
-  checkLabel("#security-cert-subject-cn", "example.com");
-  checkLabel("#security-cert-subject-o", "<Not Available>");
-  checkLabel("#security-cert-subject-ou", "<Not Available>");
+  is(textboxes[4].value, "example.com", "Label has the expected value.");
+  is(textboxes[5].value, "<Not Available>", "Label has the expected value.");
+  is(textboxes[6].value, "<Not Available>", "Label has the expected value.");
 
-  checkLabel("#security-cert-issuer-cn", "Temporary Certificate Authority");
-  checkLabel("#security-cert-issuer-o", "Mozilla Testing");
-  checkLabel("#security-cert-issuer-ou", "<Not Available>");
+  is(textboxes[7].value, "Temporary Certificate Authority", "Label has the expected value.");
+  is(textboxes[8].value, "Mozilla Testing", "Label has the expected value.");
+  is(textboxes[9].value, "Profile Guided Optimization", "Label has the expected value.");
 
   // Locale sensitive and varies between timezones. Cant't compare equality or
   // the test fails depending on which part of the world the test is executed.
-  checkLabelNotEmpty("#security-cert-validity-begins");
-  checkLabelNotEmpty("#security-cert-validity-expires");
 
-  checkLabelNotEmpty("#security-cert-sha1-fingerprint");
-  checkLabelNotEmpty("#security-cert-sha256-fingerprint");
-  yield teardown(monitor);
-
-  /**
-   * A helper that compares value attribute of a label with given selector to the
-   * expected value.
-   */
-  function checkLabel(selector, expected) {
-    info("Checking label " + selector);
-
-    let element = $(selector);
+  // cert validity begins
+  isnot(textboxes[10].value, "", "Label was not empty.");
+  // cert validity expires
+  isnot(textboxes[11].value, "", "Label was not empty.");
 
-    ok(element, "Selector matched an element.");
-    is(element.value, expected, "Label has the expected value.");
-  }
+  // cert sha1 fingerprint
+  isnot(textboxes[12].value, "", "Label was not empty.");
+  // cert sha256 fingerprint
+  isnot(textboxes[13].value, "", "Label was not empty.");
 
-  /**
-   * A helper that checks the label with given selector is not an empty string.
-   */
-  function checkLabelNotEmpty(selector) {
-    info("Checking that label " + selector + " is non-empty.");
-
-    let element = $(selector);
-
-    ok(element, "Selector matched an element.");
-    isnot(element.value, "", "Label was not empty.");
-  }
+  yield teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/browser_net_security-error.js
+++ b/devtools/client/netmonitor/test/browser_net_security-error.js
@@ -28,24 +28,19 @@ add_task(function* () {
   yield monitor.panelWin.once(EVENTS.TAB_UPDATED);
 
   info("Selecting security tab.");
   NetworkDetails.widget.selectedIndex = 5;
 
   info("Waiting for security tab to be updated.");
   yield monitor.panelWin.once(EVENTS.TAB_UPDATED);
 
-  let errorbox = $("#security-error");
-  let errormsg = $("#security-error-message");
-  let infobox = $("#security-information");
+  let errormsg = $(".security-info-value");
 
-  is(errorbox.hidden, false, "Error box is visble.");
-  is(infobox.hidden, true, "Information box is hidden.");
-
-  isnot(errormsg.value, "", "Error message is not empty.");
+  isnot(errormsg.textContent, "", "Error message is not empty.");
 
   return teardown(monitor);
 
   /**
    * Returns a promise that's resolved once a request with security issues is
    * completed.
    */
   function waitForSecurityBrokenNetworkEvent() {
--- a/devtools/client/netmonitor/test/browser_net_security-warnings.js
+++ b/devtools/client/netmonitor/test/browser_net_security-warnings.js
@@ -6,17 +6,17 @@
 /**
  * Test that warning indicators are shown when appropriate.
  */
 
 const TEST_CASES = [
   {
     desc: "no warnings",
     uri: "https://example.com" + CORS_SJS_PATH,
-    warnCipher: false,
+    warnCipher: null,
   },
 ];
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
   let { $, EVENTS, NetMonitorView } = monitor.panelWin;
   let { RequestsMenu, NetworkDetails } = NetMonitorView;
   RequestsMenu.lazyUpdate = false;
@@ -42,15 +42,15 @@ add_task(function* () {
     if (NetworkDetails.widget.selectedIndex !== 5) {
       info("Selecting security tab.");
       NetworkDetails.widget.selectedIndex = 5;
 
       info("Waiting for details pane to be updated.");
       yield monitor.panelWin.once(EVENTS.TAB_UPDATED);
     }
 
-    is(cipher.hidden, !test.warnCipher, "Cipher suite warning is hidden.");
+    is(cipher, test.warnCipher, "Cipher suite warning is hidden.");
 
     RequestsMenu.clear();
   }
 
   return teardown(monitor);
 });
--- a/devtools/client/shared/components/tree/tree-view.js
+++ b/devtools/client/shared/components/tree/tree-view.js
@@ -92,30 +92,33 @@ define(function (require, exports, modul
       // Set of expanded nodes
       expandedNodes: PropTypes.object,
       // Custom filtering callback
       onFilter: PropTypes.func,
       // Custom sorting callback
       onSort: PropTypes.func,
       // A header is displayed if set to true
       header: PropTypes.bool,
+      // Long string is expandable by a toggle button
+      expandableStrings: PropTypes.bool,
       // Array of columns
       columns: PropTypes.arrayOf(PropTypes.shape({
         id: PropTypes.string.isRequired,
         title: PropTypes.string,
         width: PropTypes.string
       }))
     },
 
     getDefaultProps: function () {
       return {
         object: null,
         renderRow: null,
         provider: ObjectProvider,
         expandedNodes: new Set(),
+        expandableStrings: true,
         columns: []
       };
     },
 
     getInitialState: function () {
       return {
         expandedNodes: this.props.expandedNodes,
         columns: ensureDefaultColumn(this.props.columns)
@@ -174,17 +177,17 @@ define(function (require, exports, modul
     getMembers: function (parent, level, path) {
       // Strings don't have children. Note that 'long' strings are using
       // the expander icon (+/-) to display the entire original value,
       // but there are no child items.
       if (typeof parent == "string") {
         return [];
       }
 
-      let provider = this.props.provider;
+      let { expandableStrings, provider } = this.props;
       let children = provider.getChildren(parent) || [];
 
       // If the return value is non-array, the children
       // are being loaded asynchronously.
       if (!Array.isArray(children)) {
         return children;
       }
 
@@ -196,17 +199,17 @@ define(function (require, exports, modul
         let type = provider.getType(child);
         let hasChildren = provider.hasChildren(child);
 
         // Value with no column specified is used for optimization.
         // The row is re-rendered only if this value changes.
         // Value for actual column is get when a cell is rendered.
         let value = provider.getValue(child);
 
-        if (isLongString(value)) {
+        if (expandableStrings && isLongString(value)) {
           hasChildren = true;
         }
 
         // Return value is a 'member' object containing meta-data about
         // tree node. It describes node label, value, type, etc.
         return {
           // An object associated with this node.
           object: child,
--- a/devtools/client/themes/netmonitor.css
+++ b/devtools/client/themes/netmonitor.css
@@ -1,13 +1,14 @@
-/* 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/. */
 
+@import "resource://devtools/client/shared/components/tree/tree-view.css";
+
 #toolbar-labels {
   overflow: hidden;
   display: flex;
   flex: auto;
 }
 
 .devtools-toolbar-container {
   display: flex;
@@ -781,16 +782,17 @@
 }
 
 .theme-dark #security-error-message {
   color: var(--theme-selection-color);
 }
 
 #security-tabpanel {
   overflow: auto;
+  -moz-user-select: text;
 }
 
 .security-warning-icon {
   background-image: url(images/alerticon-warning.png);
   background-size: 13px 12px;
   margin-inline-start: 5px;
   vertical-align: top;
   width: 13px;
@@ -1079,17 +1081,66 @@
 
 /* Responsive sidebar */
 @media (max-width: 700px) {
   :root[platform="linux"] .requests-menu-header-button {
     font-size: 85%;
   }
 }
 
+/* Overwrite tree-view cell colon and use l10n string instead */
+.treeTable .treeLabelCell::after {
+  content: "";
+}
+
+/* Layout additional warning icon in tree value cell  */
+.security-info-value {
+  display: flex;
+}
+
+.security-info-value .textbox-input {
+  text-overflow: ellipsis;
+  border: none;
+  background: none;
+  color: inherit;
+  width: 100%;
+  margin-inline-end: 2px;
+}
+
+.security-info-value .textbox-input:focus {
+  outline: 0;
+  box-shadow: var(--theme-focus-box-shadow-textbox);
+}
+
+.treeTable .treeLabel {
+  font-weight: 600;
+}
+
+/* Customize default tree table style to align with devtools theme  */
+.theme-light .treeTable .treeLabel,
+.theme-light .treeTable .treeRow.hasChildren > .treeLabelCell > .treeLabel:hover {
+  color: var(--theme-highlight-red);
+}
+
+.theme-dark .treeTable .treeLabel,
+.theme-dark .treeTable .treeRow.hasChildren > .treeLabelCell > .treeLabel:hover {
+  color: var(--theme-highlight-purple);
+}
+
+.theme-firebug .treeTable .treeLabel {
+  color: var(--theme-body-color);
+}
+
+.treeTable .treeRow.hasChildren > .treeLabelCell > .treeLabel:hover {
+  cursor: default;
+  text-decoration: none;
+}
+
 /*
  * FIXME: normal html block element cannot fill outer XUL element
  * This workaround should be removed after sidebar is migrated to react
  */
+#react-security-tabpanel-hook,
 #react-timings-tabpanel-hook {
   display: -moz-box;
   -moz-box-orient: vertical;
   -moz-box-flex: 1;
 }