Bug 932179 - Part 2: UI to present security info in NetMonitor. r=vporof
authorSami Jaktholm <sjakthol@outlook.com>
Sat, 10 Jan 2015 22:44:00 +0200
changeset 249085 7f0b79f1904f19d0fd666d7eb0c5f188bee3a5ae
parent 249084 c4d908fa4442a6132527bd49bbc9bc1186c91ee7
child 249086 14d1166ae92bc795a6f4a6aec2a493beeda1f094
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvporof
bugs932179
milestone37.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 932179 - Part 2: UI to present security info in NetMonitor. r=vporof
browser/devtools/netmonitor/netmonitor-controller.js
browser/devtools/netmonitor/netmonitor-view.js
browser/devtools/netmonitor/netmonitor.xul
browser/devtools/netmonitor/test/browser.ini
browser/devtools/netmonitor/test/browser_net_html-preview.js
browser/devtools/netmonitor/test/browser_net_security-details.js
browser/devtools/netmonitor/test/browser_net_security-error.js
browser/devtools/netmonitor/test/browser_net_security-icon-click.js
browser/devtools/netmonitor/test/browser_net_security-redirect.js
browser/devtools/netmonitor/test/browser_net_security-state.js
browser/devtools/netmonitor/test/browser_net_security-tab-deselect.js
browser/devtools/netmonitor/test/browser_net_security-tab-visibility.js
browser/devtools/netmonitor/test/head.js
browser/devtools/netmonitor/test/sjs_https-redirect-test-server.sjs
browser/locales/en-US/chrome/browser/devtools/netmonitor.dtd
browser/locales/en-US/chrome/browser/devtools/netmonitor.properties
browser/themes/linux/devtools/netmonitor.css
browser/themes/shared/devtools/netmonitor.inc.css
--- a/browser/devtools/netmonitor/netmonitor-controller.js
+++ b/browser/devtools/netmonitor/netmonitor-controller.js
@@ -3,16 +3,17 @@
 /* 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 { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 const NET_STRINGS_URI = "chrome://browser/locale/devtools/netmonitor.properties";
+const PKI_STRINGS_URI = "chrome://pippki/locale/pippki.properties";
 const LISTENERS = [ "NetworkActivity" ];
 const NET_PREFS = { "NetworkMonitor.saveRequestAndResponseBodies": true };
 
 // The panel's window global is an EventEmitter firing the following events:
 const EVENTS = {
   // When the monitored target begins and finishes navigating.
   TARGET_WILL_NAVIGATE: "NetMonitor:TargetWillNavigate",
   TARGET_DID_NAVIGATE: "NetMonitor:TargetNavigate",
@@ -29,16 +30,20 @@ const EVENTS = {
   // When request cookies begin and finish receiving.
   UPDATING_REQUEST_COOKIES: "NetMonitor:NetworkEventUpdating:RequestCookies",
   RECEIVED_REQUEST_COOKIES: "NetMonitor:NetworkEventUpdated:RequestCookies",
 
   // When request post data begins and finishes receiving.
   UPDATING_REQUEST_POST_DATA: "NetMonitor:NetworkEventUpdating:RequestPostData",
   RECEIVED_REQUEST_POST_DATA: "NetMonitor:NetworkEventUpdated:RequestPostData",
 
+  // When security information begins and finishes receiving.
+  UPDATING_SECURITY_INFO: "NetMonitor::NetworkEventUpdating:SecurityInfo",
+  RECEIVED_SECURITY_INFO: "NetMonitor::NetworkEventUpdated:SecurityInfo",
+
   // When response headers begin and finish receiving.
   UPDATING_RESPONSE_HEADERS: "NetMonitor:NetworkEventUpdating:ResponseHeaders",
   RECEIVED_RESPONSE_HEADERS: "NetMonitor:NetworkEventUpdated:ResponseHeaders",
 
   // When response cookies begin and finish receiving.
   UPDATING_RESPONSE_COOKIES: "NetMonitor:NetworkEventUpdating:ResponseCookies",
   RECEIVED_RESPONSE_COOKIES: "NetMonitor:NetworkEventUpdated:ResponseCookies",
 
@@ -131,16 +136,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   "resource://gre/modules/PluralForm.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "DevToolsUtils",
   "resource://gre/modules/devtools/DevToolsUtils.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper",
   "@mozilla.org/widget/clipboardhelper;1", "nsIClipboardHelper");
 
+XPCOMUtils.defineLazyServiceGetter(this, "DOMParser",
+  "@mozilla.org/xmlextras/domparser;1", "nsIDOMParser");
+
 Object.defineProperty(this, "NetworkHelper", {
   get: function() {
     return require("devtools/toolkit/webconsole/network-helper");
   },
   configurable: true,
   enumerable: true
 });
 
@@ -565,16 +573,23 @@ NetworkEventsHandler.prototype = {
       case "requestCookies":
         this.webConsoleClient.getRequestCookies(actor, this._onRequestCookies);
         window.emit(EVENTS.UPDATING_REQUEST_COOKIES, actor);
         break;
       case "requestPostData":
         this.webConsoleClient.getRequestPostData(actor, this._onRequestPostData);
         window.emit(EVENTS.UPDATING_REQUEST_POST_DATA, actor);
         break;
+      case "securityInfo":
+        NetMonitorView.RequestsMenu.updateRequest(aPacket.from, {
+          securityState: aPacket.state,
+        });
+        this.webConsoleClient.getSecurityInfo(actor, this._onSecurityInfo);
+        window.emit(EVENTS.UPDATING_SECURITY_INFO, actor);
+        break;
       case "responseHeaders":
         this.webConsoleClient.getResponseHeaders(actor, this._onResponseHeaders);
         window.emit(EVENTS.UPDATING_RESPONSE_HEADERS, actor);
         break;
       case "responseCookies":
         this.webConsoleClient.getResponseCookies(actor, this._onResponseCookies);
         window.emit(EVENTS.UPDATING_RESPONSE_COOKIES, actor);
         break;
@@ -640,16 +655,30 @@ NetworkEventsHandler.prototype = {
   _onRequestPostData: function(aResponse) {
     NetMonitorView.RequestsMenu.updateRequest(aResponse.from, {
       requestPostData: aResponse
     });
     window.emit(EVENTS.RECEIVED_REQUEST_POST_DATA, aResponse.from);
   },
 
   /**
+   * Handles additional information received for a "securityInfo" packet.
+   *
+   * @param object aResponse
+   *        The message received from the server.
+   */
+   _onSecurityInfo: function(aResponse) {
+     NetMonitorView.RequestsMenu.updateRequest(aResponse.from, {
+       securityInfo: aResponse.securityInfo
+     });
+
+     window.emit(EVENTS.RECEIVED_SECURITY_INFO, aResponse.from);
+   },
+
+  /**
    * Handles additional information received for a "responseHeaders" packet.
    *
    * @param object aResponse
    *        The message received from the server.
    */
   _onResponseHeaders: function(aResponse) {
     NetMonitorView.RequestsMenu.updateRequest(aResponse.from, {
       responseHeaders: aResponse
@@ -733,16 +762,17 @@ NetworkEventsHandler.prototype = {
     return deferred.promise;
   }
 };
 
 /**
  * Localization convenience methods.
  */
 let L10N = new ViewHelpers.L10N(NET_STRINGS_URI);
+let PKI_L10N = new ViewHelpers.L10N(PKI_STRINGS_URI);
 
 /**
  * Shortcuts for accessing various network monitor preferences.
  */
 let Prefs = new ViewHelpers.Prefs("devtools.netmonitor", {
   networkDetailsWidth: ["Int", "panes-network-details-width"],
   networkDetailsHeight: ["Int", "panes-network-details-height"],
   statistics: ["Bool", "statistics"],
--- a/browser/devtools/netmonitor/netmonitor-view.js
+++ b/browser/devtools/netmonitor/netmonitor-view.js
@@ -331,16 +331,17 @@ function RequestsMenuView() {
   this._flushRequests = this._flushRequests.bind(this);
   this._onHover = this._onHover.bind(this);
   this._onSelect = this._onSelect.bind(this);
   this._onSwap = this._onSwap.bind(this);
   this._onResize = this._onResize.bind(this);
   this._byFile = this._byFile.bind(this);
   this._byDomain = this._byDomain.bind(this);
   this._byType = this._byType.bind(this);
+  this._onSecurityIconClick = this._onSecurityIconClick.bind(this);
 }
 
 RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
   /**
    * Initialization function, called when the network monitor is started.
    */
   initialize: function() {
     dumpn("Initializing the RequestsMenuView");
@@ -1051,16 +1052,27 @@ RequestsMenuView.prototype = Heritage.ex
   refreshTooltip: function(aItem) {
     let tooltip = aItem.attachment.tooltip;
     tooltip.hide();
     tooltip.startTogglingOnHover(aItem.target, this._onHover);
     tooltip.defaultPosition = REQUESTS_TOOLTIP_POSITION;
   },
 
   /**
+   * Attaches security icon click listener for the given request menu item.
+   *
+   * @param object item
+   *        The network request item to attach the listener to.
+   */
+  attachSecurityIconClickListener: function ({ target }) {
+    let icon = $(".requests-security-state-icon", target);
+    icon.addEventListener("click", this._onSecurityIconClick);
+  },
+
+  /**
    * Schedules adding additional information to a network request.
    *
    * @param string aId
    *        An identifier coming from the network monitor controller.
    * @param object aData
    *        An object containing several { key: value } tuples of network info.
    *        Supported keys are "httpVersion", "status", "statusText" etc.
    */
@@ -1128,16 +1140,23 @@ RequestsMenuView.prototype = Heritage.ex
               // The `getString` promise is async, so we need to refresh the
               // information displayed in the network details pane again here.
               refreshNetworkDetailsPaneIfNecessary(currentItem);
             });
 
             requestItem.attachment.requestPostData = value;
             requestItem.attachment.requestHeadersFromUploadStream = currentStore;
             break;
+          case "securityState":
+            requestItem.attachment.securityState = value;
+            this.updateMenuView(requestItem, key, value);
+            break;
+          case "securityInfo":
+            requestItem.attachment.securityInfo = value;
+            break;
           case "responseHeaders":
             requestItem.attachment.responseHeaders = value;
             break;
           case "responseCookies":
             requestItem.attachment.responseCookies = value;
             break;
           case "httpVersion":
             requestItem.attachment.httpVersion = value;
@@ -1281,16 +1300,25 @@ RequestsMenuView.prototype = Heritage.ex
         file.setAttribute("value", nameWithQuery);
         file.setAttribute("tooltiptext", nameWithQuery);
 
         let domain = $(".requests-menu-domain", target);
         domain.setAttribute("value", hostPort);
         domain.setAttribute("tooltiptext", hostPort);
         break;
       }
+      case "securityState": {
+        let tooltip = L10N.getStr("netmonitor.security.state." + aValue);
+        let icon = $(".requests-security-state-icon", target);
+        icon.classList.add("security-state-" + aValue);
+        icon.setAttribute("tooltiptext", tooltip);
+
+        this.attachSecurityIconClickListener(aItem);
+        break;
+      }
       case "status": {
         let node = $(".requests-menu-status", target);
         let codeNode = $(".requests-menu-status-code", target);
         codeNode.setAttribute("value", aValue);
         node.setAttribute("code", aValue);
         break;
       }
       case "statusText": {
@@ -1570,16 +1598,21 @@ RequestsMenuView.prototype = Heritage.ex
    * The swap listener for this container.
    * Called when two items switch places, when the contents are sorted.
    */
   _onSwap: function({ detail: [firstItem, secondItem] }) {
     // Sorting will create new anchor nodes for all the swapped request items
     // in this container, so it's necessary to refresh the Tooltip instances.
     this.refreshTooltip(firstItem);
     this.refreshTooltip(secondItem);
+
+    // Reattach click listener to the security icons
+    this.attachSecurityIconClickListener(firstItem);
+    this.attachSecurityIconClickListener(secondItem);
+
   },
 
   /**
    * The predicate used when deciding whether a popup should be shown
    * over a request item or not.
    *
    * @param nsIDOMNode aTarget
    *        The element node currently being hovered.
@@ -1605,16 +1638,28 @@ RequestsMenuView.prototype = Heritage.ex
         let src = "data:" + mimeType + ";" + encoding + "," + aString;
         aTooltip.setImageContent(src, { maxDim: REQUESTS_TOOLTIP_IMAGE_MAX_DIM });
         return anchor;
       });
     }
   },
 
   /**
+   * A handler that opens the security tab in the details view if secure or
+   * broken security indicator is clicked.
+   */
+  _onSecurityIconClick: function(e) {
+    let state = this.selectedItem.attachment.securityState;
+    if (state === "broken" || state === "secure") {
+      // Choose the security tab.
+      NetMonitorView.NetworkDetails.widget.selectedIndex = 5;
+    }
+  },
+
+  /**
    * The resize listener for this container's window.
    */
   _onResize: function(e) {
     // Allow requests to settle down first.
     setNamedTimeout(
       "resize-events", RESIZE_REFRESH_RATE, () => this._flushWaterfallViews(true));
   },
 
@@ -2043,19 +2088,30 @@ NetworkDetailsView.prototype = {
     $("#response-content-image-box").hidden = true;
 
     let isHtml = RequestsMenuView.prototype.isHtml({ attachment: aData });
 
     // Show the "Preview" tabpanel only for plain HTML responses.
     $("#preview-tab").hidden = !isHtml;
     $("#preview-tabpanel").hidden = !isHtml;
 
+    // Show the "Security" tab only for requests that
+    //   1) are https (state != insecure)
+    //   2) come from a target that provides security information.
+    let hasSecurityInfo = aData.securityState &&
+                          aData.securityState !== "insecure";
+
+    $("#security-tab").hidden = !hasSecurityInfo;
+
     // Switch to the "Headers" tabpanel if the "Preview" previously selected
-    // and this is not an HTML response.
-    if (!isHtml && this.widget.selectedIndex == 5) {
+    // and this is not an HTML response or "Security" was selected but this
+    // request has no security information.
+
+    if (!isHtml && this.widget.selectedPanel === $("#preview-tabpanel") ||
+        !hasSecurityInfo && this.widget.selectedPanel === $("#security-tabpanel")) {
       this.widget.selectedIndex = 0;
     }
 
     this._headers.empty();
     this._cookies.empty();
     this._params.empty();
     this._json.empty();
 
@@ -2112,17 +2168,20 @@ NetworkDetailsView.prototype = {
             src.requestPostData);
           break;
         case 3: // "Response"
           yield view._setResponseBody(src.url, src.responseContent);
           break;
         case 4: // "Timings"
           yield view._setTimingsInformation(src.eventTimings);
           break;
-        case 5: // "Preview"
+        case 5: // "Security"
+          yield view._setSecurityInfo(src.securityInfo, src.url);
+          break;
+        case 6: // "Preview"
           yield view._setHtmlPreview(src.responseContent);
           break;
       }
       viewState.updating[tab] = false;
     }).then(() => {
       if (tab == this.widget.selectedIndex) {
         if (viewState.dirty[tab]) {
           // The request information was updated while the task was running.
@@ -2617,16 +2676,108 @@ NetworkDetailsView.prototype = {
     // Always disable JS when previewing HTML responses.
     let iframe = $("#response-preview");
     iframe.contentDocument.docShell.allowJavascript = false;
     iframe.contentDocument.documentElement.innerHTML = responseBody;
 
     window.emit(EVENTS.RESPONSE_HTML_PREVIEW_DISPLAYED);
   }),
 
+  /**
+   * 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 label text to specified value.
+     *
+     * @param string selector
+     *        A selector for the label.
+     * @param string value
+     *        The value label should have. If this evaluates to false a
+     *        placeholder string <Not Available> is used instead.
+     */
+    function setLabel(selector, value) {
+      let label = $(selector);
+      if (!value) {
+        label.value = L10N.getStr("netmonitor.security.notAvailable");
+        label.setAttribute("tooltiptext", label.value);
+      } else {
+        label.value = value;
+        label.setAttribute("tooltiptext", value);
+      }
+    }
+
+    let errorbox = $("#security-error");
+    let infobox = $("#security-information");
+
+    if (securityInfo.state === "secure") {
+      infobox.hidden = false;
+      errorbox.hidden = true;
+
+      let enabledLabel = L10N.getStr("netmonitor.security.enabled");
+      let disabledLabel = L10N.getStr("netmonitor.security.disabled");
+
+      // Connection parameters
+      setLabel("#security-protocol-version-value", securityInfo.protocolVersion);
+      setLabel("#security-ciphersuite-value", securityInfo.cipherSuite);
+
+      // Host header
+      let domain = NetMonitorView.RequestsMenu._getUriHostPort(url);
+      let hostHeader = L10N.getFormatStr("netmonitor.security.hostHeader", domain);
+      setLabel("#security-info-host-header", hostHeader);
+
+      // Parameters related to the domain
+      setLabel("#security-http-strict-transport-security-value",
+                securityInfo.hsts ? enabledLabel : disabledLabel);
+
+      setLabel("#security-public-key-pinning-value",
+                securityInfo.hpkp ? enabledLabel : disabledLabel);
+
+      // Certificate parameters
+      let cert = securityInfo.cert;
+      setLabel("#security-cert-subject-cn", cert.subject.commonName);
+      setLabel("#security-cert-subject-o", cert.subject.organization);
+      setLabel("#security-cert-subject-ou", cert.subject.organizationalUnit);
+
+      setLabel("#security-cert-issuer-cn", cert.issuer.commonName);
+      setLabel("#security-cert-issuer-o", cert.issuer.organization);
+      setLabel("#security-cert-issuer-ou", cert.issuer.organizationalUnit);
+
+      setLabel("#security-cert-validity-begins", cert.validity.start);
+      setLabel("#security-cert-validity-expires", cert.validity.end);
+
+      setLabel("#security-cert-sha1-fingerprint", cert.fingerprint.sha1);
+      setLabel("#security-cert-sha256-fingerprint", cert.fingerprint.sha256);
+    } else {
+      infobox.hidden = true;
+      errorbox.hidden = false;
+
+      // Strip any HTML from the message.
+      let plain = DOMParser.parseFromString(securityInfo.errorMessage, "text/html");
+      $("#security-error-message").textContent = plain.body.textContent;
+    }
+  }),
+
   _dataSrc: null,
   _headers: null,
   _cookies: null,
   _params: null,
   _json: null,
   _paramsQueryString: "",
   _paramsFormData: "",
   _paramsPostPayload: "",
--- a/browser/devtools/netmonitor/netmonitor.xul
+++ b/browser/devtools/netmonitor/netmonitor.xul
@@ -6,16 +6,18 @@
 <?xml-stylesheet href="chrome://browser/content/devtools/widgets.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/content/devtools/netmonitor.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/devtools/common.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/devtools/widgets.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/devtools/netmonitor.css" type="text/css"?>
 <!DOCTYPE window [
   <!ENTITY % netmonitorDTD SYSTEM "chrome://browser/locale/devtools/netmonitor.dtd">
   %netmonitorDTD;
+  <!ENTITY % certManagerDTD SYSTEM "chrome://pippki/locale/certManager.dtd">
+  %certManagerDTD;
 ]>
 
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         xmlns:html="http://www.w3.org/1999/xhtml">
 
   <script type="application/javascript;version=1.8"
           src="chrome://browser/content/devtools/theme-switching.js"/>
   <script type="text/javascript" src="netmonitor-controller.js"/>
@@ -77,20 +79,20 @@
                 <button id="requests-menu-file-button"
                         class="requests-menu-header-button requests-menu-file"
                         data-key="file"
                         label="&netmonitorUI.toolbar.file;"
                         flex="1">
                 </button>
               </hbox>
               <hbox id="requests-menu-domain-header-box"
-                    class="requests-menu-header requests-menu-domain"
+                    class="requests-menu-header requests-menu-security-and-domain"
                     align="center">
                 <button id="requests-menu-domain-button"
-                        class="requests-menu-header-button requests-menu-domain"
+                        class="requests-menu-header-button requests-menu-security-and-domain"
                         data-key="domain"
                         label="&netmonitorUI.toolbar.domain;"
                         flex="1">
                 </button>
               </hbox>
               <hbox id="requests-menu-type-header-box"
                     class="requests-menu-header requests-menu-type"
                     align="center">
@@ -165,18 +167,23 @@
               </hbox>
               <hbox class="requests-menu-subitem requests-menu-icon-and-file"
                     align="center">
                 <image class="requests-menu-icon" hidden="true"/>
                 <label class="plain requests-menu-file"
                        crop="end"
                        flex="1"/>
               </hbox>
-              <label class="plain requests-menu-subitem requests-menu-domain"
-                     crop="end"/>
+              <hbox class="requests-menu-subitem requests-menu-security-and-domain"
+                    align="center">
+                <image class="requests-security-state-icon" />
+                <label class="plain requests-menu-domain"
+                       crop="end"
+                       flex="1"/>
+              </hbox>
               <label class="plain requests-menu-subitem requests-menu-type"
                      crop="end"/>
               <label class="plain requests-menu-subitem requests-menu-size"
                      crop="end"/>
               <hbox class="requests-menu-subitem requests-menu-waterfall"
                     align="center"
                     flex="1">
                 <hbox class="requests-menu-timings"
@@ -261,16 +268,18 @@
               <tab id="cookies-tab"
                    label="&netmonitorUI.tab.cookies;"/>
               <tab id="params-tab"
                    label="&netmonitorUI.tab.params;"/>
               <tab id="response-tab"
                    label="&netmonitorUI.tab.response;"/>
               <tab id="timings-tab"
                    label="&netmonitorUI.tab.timings;"/>
+              <tab id="security-tab"
+                   label="&netmonitorUI.tab.security;"/>
               <tab id="preview-tab"
                    label="&netmonitorUI.tab.preview;"/>
             </tabs>
             <tabpanels flex="1">
               <tabpanel id="headers-tabpanel"
                         class="tabpanel-content">
                 <vbox flex="1">
                   <hbox id="headers-summary-url"
@@ -455,16 +464,204 @@
                         align="center">
                     <label class="plain tabpanel-summary-label"
                            value="&netmonitorUI.timings.receive;"/>
                     <hbox class="requests-menu-timings-box receive"/>
                     <label class="plain requests-menu-timings-total"/>
                   </hbox>
                 </vbox>
               </tabpanel>
+              <tabpanel id="security-tabpanel"
+                        class="tabpanel-content">
+                  <vbox id="security-error"
+                        class="tabpanel-summary-container"
+                        flex="1">
+                    <label class="plain tabpanel-summary-label"
+                           value="&netmonitorUI.security.error;"/>
+                    <description id="security-error-message" flex="1"/>
+                  </vbox>
+                  <vbox id="security-information"
+                        flex="1">
+                    <vbox id="security-info-connection"
+                          class="tabpanel-summary-container">
+                      <label class="plain tabpanel-summary-label"
+                             value="&netmonitorUI.security.connection;"/>
+                      <vbox class="security-info-section">
+                        <hbox id="security-protocol-version"
+                              class="tabpanel-summary-container"
+                              align="center">
+                          <label class="plain tabpanel-summary-label"
+                                 value="&netmonitorUI.security.protocolVersion;"/>
+                          <label id="security-protocol-version-value"
+                                 class="plain tabpanel-summary-value devtools-monospace"
+                                 crop="end"
+                                 flex="1"/>
+                        </hbox>
+                        <hbox id="security-ciphersuite"
+                              class="tabpanel-summary-container"
+                              align="center">
+                          <label class="plain tabpanel-summary-label"
+                                 value="&netmonitorUI.security.cipherSuite;"/>
+                          <label id="security-ciphersuite-value"
+                                 class="plain tabpanel-summary-value devtools-monospace"
+                                 crop="end"
+                                 flex="1"/>
+                        </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="center">
+                          <label class="plain tabpanel-summary-label"
+                                 value="&netmonitorUI.security.hsts;"/>
+                          <label id="security-http-strict-transport-security-value"
+                                 class="plain tabpanel-summary-value devtools-monospace"
+                                 crop="end"
+                                 flex="1"/>
+                        </hbox>
+                        <hbox id="security-public-key-pinning"
+                              class="tabpanel-summary-container"
+                              align="center">
+                          <label class="plain tabpanel-summary-label"
+                                 value="&netmonitorUI.security.hpkp;"/>
+                          <label id="security-public-key-pinning-value"
+                                 class="plain tabpanel-summary-value devtools-monospace"
+                                 crop="end"
+                                 flex="1"/>
+                        </hbox>
+                      </vbox>
+                    </vbox>
+                    <vbox id="security-info-certificate"
+                          class="tabpanel-summary-container">
+                        <label class="plain tabpanel-summary-label"
+                               value="&netmonitorUI.security.certificate;"/>
+                      <vbox class="security-info-section">
+                        <vbox class="tabpanel-summary-container">
+                          <label class="plain tabpanel-summary-label"
+                                 value="&certmgr.subjectinfo.label;" flex="1"/>
+                        </vbox>
+                        <vbox class="security-info-section">
+                          <hbox class="tabpanel-summary-container"
+                                align="center">
+                            <label class="plain tabpanel-summary-label"
+                                   value="&certmgr.certdetail.cn;:"/>
+                            <label id="security-cert-subject-cn"
+                                   class="plain tabpanel-summary-value devtools-monospace"
+                                   crop="end"
+                                   flex="1"/>
+                          </hbox>
+                          <hbox class="tabpanel-summary-container"
+                                align="center">
+                            <label class="plain tabpanel-summary-label"
+                                   value="&certmgr.certdetail.o;:"/>
+                            <label id="security-cert-subject-o"
+                                   class="plain tabpanel-summary-value devtools-monospace"
+                                   crop="end"
+                                   flex="1"/>
+                          </hbox>
+                          <hbox class="tabpanel-summary-container"
+                                align="center">
+                            <label class="plain tabpanel-summary-label"
+                                   value="&certmgr.certdetail.ou;:"/>
+                            <label id="security-cert-subject-ou"
+                                   class="plain tabpanel-summary-value devtools-monospace"
+                                   crop="end"
+                                   flex="1"/>
+                          </hbox>
+                        </vbox>
+                        <vbox class="tabpanel-summary-container">
+                          <label class="plain tabpanel-summary-label"
+                                 value="&certmgr.issuerinfo.label;" flex="1"/>
+                        </vbox>
+                        <vbox class="security-info-section">
+                          <hbox class="tabpanel-summary-container"
+                                align="center">
+                            <label class="plain tabpanel-summary-label"
+                                   value="&certmgr.certdetail.cn;:"/>
+                            <label id="security-cert-issuer-cn"
+                                   class="plain tabpanel-summary-value devtools-monospace"
+                                   crop="end"
+                                   flex="1"/>
+                          </hbox>
+                          <hbox class="tabpanel-summary-container"
+                                align="center">
+                            <label class="plain tabpanel-summary-label"
+                                   value="&certmgr.certdetail.o;:"/>
+                            <label id="security-cert-issuer-o"
+                                   class="plain tabpanel-summary-value devtools-monospace"
+                                   crop="end"
+                                   flex="1"/>
+                          </hbox>
+                          <hbox class="tabpanel-summary-container"
+                                align="center">
+                            <label class="plain tabpanel-summary-label"
+                                   value="&certmgr.certdetail.ou;:"/>
+                            <label id="security-cert-issuer-ou"
+                                   class="plain tabpanel-summary-value devtools-monospace"
+                                   crop="end"
+                                   flex="1"/>
+                          </hbox>
+                        </vbox>
+                        <vbox class="tabpanel-summary-container">
+                          <label class="plain tabpanel-summary-label"
+                                 value="&certmgr.periodofvalidity.label;" flex="1"/>
+                        </vbox>
+                        <vbox class="security-info-section">
+                          <hbox class="tabpanel-summary-container"
+                                align="center">
+                            <label class="plain tabpanel-summary-label"
+                                   value="&certmgr.begins;:"/>
+                            <label id="security-cert-validity-begins"
+                                   class="plain tabpanel-summary-value devtools-monospace"
+                                   crop="end"
+                                   flex="1"/>
+                          </hbox>
+                          <hbox class="tabpanel-summary-container"
+                                align="center">
+                            <label class="plain tabpanel-summary-label"
+                                   value="&certmgr.expires;:"/>
+                            <label id="security-cert-validity-expires"
+                                   class="plain tabpanel-summary-value devtools-monospace"
+                                   crop="end"
+                                   flex="1"/>
+                          </hbox>
+                        </vbox>
+                        <vbox class="tabpanel-summary-container">
+                          <label class="plain tabpanel-summary-label"
+                                 value="&certmgr.fingerprints.label;" flex="1"/>
+                        </vbox>
+                        <vbox class="security-info-section">
+                          <hbox class="tabpanel-summary-container"
+                                align="center">
+                            <label class="plain tabpanel-summary-label"
+                                   value="&certmgr.certdetail.sha256fingerprint;:"/>
+                            <label id="security-cert-sha256-fingerprint"
+                                   class="plain tabpanel-summary-value devtools-monospace"
+                                   crop="end"
+                                   flex="1"/>
+                          </hbox>
+                          <hbox class="tabpanel-summary-container"
+                                align="center">
+                            <label class="plain tabpanel-summary-label"
+                                   value="&certmgr.certdetail.sha1fingerprint;:"/>
+                            <label id="security-cert-sha1-fingerprint"
+                                   class="plain tabpanel-summary-value devtools-monospace"
+                                   crop="end"
+                                   flex="1"/>
+                          </hbox>
+                        </vbox>
+                      </vbox>
+                    </vbox>
+                  </vbox>
+              </tabpanel>
               <tabpanel id="preview-tabpanel"
                         class="tabpanel-content">
                 <html:iframe id="response-preview"
                              frameborder="0"
                              sandbox=""/>
               </tabpanel>
             </tabpanels>
           </tabbox>
--- a/browser/devtools/netmonitor/test/browser.ini
+++ b/browser/devtools/netmonitor/test/browser.ini
@@ -21,16 +21,17 @@ support-files =
   html_post-raw-with-headers-test-page.html
   html_simple-test-page.html
   html_sorting-test-page.html
   html_statistics-test-page.html
   html_status-codes-test-page.html
   html_copy-as-curl.html
   html_curl-utils.html
   sjs_content-type-test-server.sjs
+  sjs_https-redirect-test-server.sjs
   sjs_simple-test-server.sjs
   sjs_sorting-test-server.sjs
   sjs_status-codes-test-server.sjs
   test-image.png
 
 [browser_net_aaa_leaktest.js]
 [browser_net_accessibility-01.js]
 [browser_net_accessibility-02.js]
@@ -78,16 +79,23 @@ skip-if = e10s # Bug 1091603
 [browser_net_post-data-03.js]
 [browser_net_prefs-and-l10n.js]
 [browser_net_prefs-reload.js]
 [browser_net_raw_headers.js]
 [browser_net_reload-button.js]
 [browser_net_req-resp-bodies.js]
 [browser_net_resend.js]
 skip-if = e10s # Bug 1091612
+[browser_net_security-details.js]
+[browser_net_security-error.js]
+[browser_net_security-icon-click.js]
+[browser_net_security-redirect.js]
+[browser_net_security-state.js]
+[browser_net_security-tab-deselect.js]
+[browser_net_security-tab-visibility.js]
 [browser_net_simple-init.js]
 [browser_net_simple-request-data.js]
 [browser_net_simple-request-details.js]
 [browser_net_simple-request.js]
 [browser_net_sort-01.js]
 [browser_net_sort-02.js]
 [browser_net_sort-03.js]
 [browser_net_statistics-01.js]
--- a/browser/devtools/netmonitor/test/browser_net_html-preview.js
+++ b/browser/devtools/netmonitor/test/browser_net_html-preview.js
@@ -21,20 +21,20 @@ function test() {
       is($("#event-details-pane").selectedIndex, 0,
         "The first tab in the details pane should be selected.");
       is($("#preview-tab").hidden, true,
         "The preview tab should be hidden for non html responses.");
       is($("#preview-tabpanel").hidden, true,
         "The preview tabpanel should be hidden for non html responses.");
 
       RequestsMenu.selectedIndex = 4;
-      NetMonitorView.toggleDetailsPane({ visible: true, animated: false }, 5);
+      NetMonitorView.toggleDetailsPane({ visible: true, animated: false }, 6);
 
-      is($("#event-details-pane").selectedIndex, 5,
-        "The fifth tab in the details pane should be selected.");
+      is($("#event-details-pane").selectedIndex, 6,
+        "The sixth tab in the details pane should be selected.");
       is($("#preview-tab").hidden, false,
         "The preview tab should be visible now.");
       is($("#preview-tabpanel").hidden, false,
         "The preview tabpanel should be visible now.");
 
       waitFor(aMonitor.panelWin, EVENTS.RESPONSE_HTML_PREVIEW_DISPLAYED).then(() => {
         let iframe = $("#response-preview");
         ok(iframe,
new file mode 100644
--- /dev/null
+++ b/browser/devtools/netmonitor/test/browser_net_security-details.js
@@ -0,0 +1,90 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/**
+ * Test that Security details tab contains the expected data.
+ */
+
+add_task(function* () {
+  let [tab, debuggee, monitor] = yield initNetMonitor(CUSTOM_GET_URL);
+  let { $, EVENTS, NetMonitorView } = monitor.panelWin;
+  let { RequestsMenu, NetworkDetails } = NetMonitorView;
+  RequestsMenu.lazyUpdate = false;
+
+  info("Performing a secure request.");
+  debuggee.performRequests(1, "https://example.com");
+
+  yield waitForNetworkEvents(monitor, 1);
+
+  info("Selecting the request.");
+  RequestsMenu.selectedIndex = 0;
+
+  info("Waiting for details pane to be updated.");
+  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 infobox = $("#security-information");
+
+  is(errorbox.hidden, true, "Error box is hidden.");
+  is(infobox.hidden, false, "Information box visible.");
+
+  // Connection
+  checkLabel("#security-protocol-version-value", "TLSv1");
+  checkLabel("#security-ciphersuite-value", "TLS_RSA_WITH_AES_128_CBC_SHA");
+
+  // Host
+  checkLabel("#security-info-host-header", "Host example.com:");
+  checkLabel("#security-http-strict-transport-security-value", "Disabled");
+  checkLabel("#security-public-key-pinning-value", "Disabled");
+
+  // Cert
+  checkLabel("#security-cert-subject-cn", "example.com");
+  checkLabel("#security-cert-subject-o", "<Not Available>");
+  checkLabel("#security-cert-subject-ou", "<Not Available>");
+
+  checkLabel("#security-cert-issuer-cn", "Temporary Certificate Authority");
+  checkLabel("#security-cert-issuer-o", "Mozilla Testing");
+  checkLabel("#security-cert-issuer-ou", "<Not Available>");
+
+  // 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);
+
+    ok(element, "Selector matched an element.");
+    is(element.value, expected, "Label has the expected value.");
+  }
+
+  /**
+   * 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.");
+  }
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/netmonitor/test/browser_net_security-error.js
@@ -0,0 +1,67 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/**
+ * Test that Security details tab shows an error message with broken connections.
+ */
+
+add_task(function* () {
+  let [tab, debuggee, monitor] = yield initNetMonitor(CUSTOM_GET_URL);
+  let { $, EVENTS, NetMonitorView } = monitor.panelWin;
+  let { RequestsMenu, NetworkDetails } = NetMonitorView;
+  RequestsMenu.lazyUpdate = false;
+
+  info("Requesting a resource that has a certificate problem.");
+  debuggee.performRequests(1, "https://nocert.example.com");
+
+  yield waitForSecurityBrokenNetworkEvent();
+
+  info("Selecting the request.");
+  RequestsMenu.selectedIndex = 0;
+
+  info("Waiting for details pane to be updated.");
+  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");
+
+  is(errorbox.hidden, false, "Error box is visble.");
+  is(infobox.hidden, true, "Information box is hidden.");
+
+  isnot(errormsg.textContent, "", "Error message is not empty.");
+
+  yield teardown(monitor);
+
+  /**
+   * Returns a promise that's resolved once a request with security issues is
+   * completed.
+   */
+  function waitForSecurityBrokenNetworkEvent() {
+    let awaitedEvents = [
+      "UPDATING_REQUEST_HEADERS",
+      "RECEIVED_REQUEST_HEADERS",
+      "UPDATING_REQUEST_COOKIES",
+      "RECEIVED_REQUEST_COOKIES",
+      "STARTED_RECEIVING_RESPONSE",
+      "UPDATING_RESPONSE_CONTENT",
+      "RECEIVED_RESPONSE_CONTENT",
+      "UPDATING_EVENT_TIMINGS",
+      "RECEIVED_EVENT_TIMINGS",
+    ];
+
+    let promises = awaitedEvents.map((event) => {
+      return monitor.panelWin.once(EVENTS[event]);
+    });
+
+    return Promise.all(promises);
+  }
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/netmonitor/test/browser_net_security-icon-click.js
@@ -0,0 +1,53 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/**
+ * Test that clicking on the security indicator opens the security details tab.
+ */
+
+add_task(function* () {
+  let [tab, debuggee, monitor] = yield initNetMonitor(CUSTOM_GET_URL);
+  let { $, EVENTS, NetMonitorView } = monitor.panelWin;
+  let { RequestsMenu, NetworkDetails } = NetMonitorView;
+  RequestsMenu.lazyUpdate = false;
+
+  info("Requesting a resource over HTTPS.");
+  debuggee.performRequests(1, "https://example.com/request_2");
+  yield waitForNetworkEvents(monitor, 1);
+
+  debuggee.performRequests(1, "https://example.com/request_1");
+  yield waitForNetworkEvents(monitor, 1);
+
+  is(RequestsMenu.itemCount, 2, "Two events event logged.");
+
+  yield clickAndTestSecurityIcon();
+
+  info("Selecting headers panel again.");
+  NetworkDetails.widget.selectedIndex = 0;
+  yield monitor.panelWin.once(EVENTS.TAB_UPDATED);
+
+  info("Sorting the items by filename.");
+  EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-file-button"));
+
+  info("Testing that security icon can be clicked after the items were sorted.");
+  yield clickAndTestSecurityIcon();
+
+  yield teardown(monitor);
+
+  function* clickAndTestSecurityIcon() {
+    let item = RequestsMenu.items[0];
+    let icon = $(".requests-security-state-icon", item.target);
+
+    info("Clicking security icon of the first request and waiting for the " +
+         "panel to update.");
+
+    icon.click();
+    yield monitor.panelWin.once(EVENTS.TAB_UPDATED);
+
+    is(NetworkDetails.widget.selectedPanel, $("#security-tabpanel"),
+      "Security tab is selected.");
+  }
+
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/netmonitor/test/browser_net_security-redirect.js
@@ -0,0 +1,35 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/**
+ * Test a http -> https redirect shows secure icon only for redirected https
+ * request.
+ */
+
+add_task(function* () {
+  let [tab, debuggee, monitor] = yield initNetMonitor(CUSTOM_GET_URL);
+  let { $, NetMonitorView } = monitor.panelWin;
+  let { RequestsMenu } = NetMonitorView;
+  RequestsMenu.lazyUpdate = false;
+
+  debuggee.performRequests(1, HTTPS_REDIRECT_SJS);
+  yield waitForNetworkEvents(monitor, 2);
+
+  is(RequestsMenu.itemCount, 2, "There were two requests due to redirect.");
+
+  let initial = RequestsMenu.items[0];
+  let redirect = RequestsMenu.items[1];
+
+  let initialSecurityIcon = $(".requests-security-state-icon", initial.target);
+  let redirectSecurityIcon = $(".requests-security-state-icon", redirect.target);
+
+  ok(initialSecurityIcon.classList.contains("security-state-insecure"),
+     "Initial request was marked insecure.");
+
+  ok(redirectSecurityIcon.classList.contains("security-state-secure"),
+     "Redirected request was marked secure.");
+
+  yield teardown(monitor);
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/netmonitor/test/browser_net_security-state.js
@@ -0,0 +1,99 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/**
+ * Test that correct security state indicator appears depending on the security
+ * state.
+ */
+
+add_task(function* () {
+  const EXPECTED_SECURITY_STATES = {
+    "test1.example.com": "security-state-insecure",
+    "example.com": "security-state-secure",
+    "nocert.example.com": "security-state-broken",
+  };
+
+  let [tab, debuggee, monitor] = yield initNetMonitor(CUSTOM_GET_URL);
+  let { $, EVENTS, NetMonitorView } = monitor.panelWin;
+  let { RequestsMenu } = NetMonitorView;
+  RequestsMenu.lazyUpdate = false;
+
+  yield performRequests();
+
+  for (let item of RequestsMenu.items) {
+    let domain = $(".requests-menu-domain", item.target).value;
+
+    info("Found a request to " + domain);
+    ok(domain in EXPECTED_SECURITY_STATES, "Domain " + domain + " was expected.");
+
+    let classes = $(".requests-security-state-icon", item.target).classList;
+    let expectedClass = EXPECTED_SECURITY_STATES[domain];
+
+    info("Classes of security state icon are: " + classes);
+    info("Security state icon is expected to contain class: " + expectedClass);
+    ok(classes.contains(expectedClass), "Icon contained the correct class name.");
+  }
+
+  yield teardown(monitor);
+
+  /**
+   * A helper that performs requests to
+   *  - https://nocert.example.com (broken)
+   *  - https://example.com (secure)
+   *  - http://test1.example.com (insecure)
+   * and waits until NetworkMonitor has handled all packets sent by the server.
+   */
+  function* performRequests() {
+    // waitForNetworkEvents does not work for requests with security errors as
+    // those only emit 9/13 events of a successful request.
+    let done = waitForSecurityBrokenNetworkEvent();
+
+    info("Requesting a resource that has a certificate problem.");
+    debuggee.performRequests(1, "https://nocert.example.com");
+
+    // Wait for the request to complete before firing another request. Otherwise
+    // the request with security issues interfere with waitForNetworkEvents.
+    info("Waiting for request to complete.");
+    yield done;
+
+    // Next perform a request over HTTP. If done the other way around the latter
+    // occasionally hangs waiting for event timings that don't seem to appear...
+    done = waitForNetworkEvents(monitor, 1);
+    info("Requesting a resource over HTTP.");
+    debuggee.performRequests(1, "http://test1.example.com");
+    yield done;
+
+    done = waitForNetworkEvents(monitor, 1);
+    info("Requesting a resource over HTTPS.");
+    debuggee.performRequests(1, "https://example.com");
+    yield done;
+
+    is(RequestsMenu.itemCount, 3, "Three events logged.");
+  }
+
+  /**
+   * Returns a promise that's resolved once a request with security issues is
+   * completed.
+   */
+  function waitForSecurityBrokenNetworkEvent() {
+    let awaitedEvents = [
+      "UPDATING_REQUEST_HEADERS",
+      "RECEIVED_REQUEST_HEADERS",
+      "UPDATING_REQUEST_COOKIES",
+      "RECEIVED_REQUEST_COOKIES",
+      "STARTED_RECEIVING_RESPONSE",
+      "UPDATING_RESPONSE_CONTENT",
+      "RECEIVED_RESPONSE_CONTENT",
+      "UPDATING_EVENT_TIMINGS",
+      "RECEIVED_EVENT_TIMINGS",
+    ];
+
+    let promises = awaitedEvents.map((event) => {
+      return monitor.panelWin.once(EVENTS[event]);
+    });
+
+    return Promise.all(promises);
+  }
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/netmonitor/test/browser_net_security-tab-deselect.js
@@ -0,0 +1,38 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/**
+ * Test that security details tab is no longer selected if an insecure request
+ * is selected.
+ */
+
+add_task(function* () {
+  let [tab, debuggee, monitor] = yield initNetMonitor(CUSTOM_GET_URL);
+  let { $, EVENTS, NetMonitorView } = monitor.panelWin;
+  let { RequestsMenu, NetworkDetails } = NetMonitorView;
+  RequestsMenu.lazyUpdate = false;
+
+  info("Performing requests.");
+  debuggee.performRequests(1, "https://example.com");
+  debuggee.performRequests(1, "http://example.com");
+  yield waitForNetworkEvents(monitor, 2);
+
+  info("Selecting secure request.");
+  RequestsMenu.selectedIndex = 0;
+
+  info("Selecting security tab.");
+  NetworkDetails.widget.selectedIndex = 5;
+
+  info("Selecting insecure request.");
+  RequestsMenu.selectedIndex = 1;
+
+  info("Waiting for security tab to be updated.");
+  yield monitor.panelWin.once(EVENTS.NETWORKDETAILSVIEW_POPULATED);
+
+  is(NetworkDetails.widget.selectedIndex, 0,
+    "Selected tab was reset when selected security tab was hidden.");
+
+  yield teardown(monitor);
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/netmonitor/test/browser_net_security-tab-visibility.js
@@ -0,0 +1,111 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/**
+ * Test that security details tab is visible only when it should.
+ */
+
+add_task(function* () {
+  const TEST_DATA = [
+    {
+      desc: "http request",
+      uri: "http://example.com",
+      visibleOnNewEvent: false,
+      visibleOnSecurityInfo: false,
+      visibleOnceComplete: false,
+    }, {
+      desc: "working https request",
+      uri: "https://example.com",
+      visibleOnNewEvent: false,
+      visibleOnSecurityInfo: true,
+      visibleOnceComplete: true,
+    }, {
+      desc: "broken https request",
+      uri: "https://nocert.example.com",
+      isBroken: true,
+      visibleOnNewEvent: false,
+      visibleOnSecurityInfo: true,
+      visibleOnceComplete: true,
+    }
+  ];
+
+  let [tab, debuggee, monitor] = yield initNetMonitor(CUSTOM_GET_URL);
+  let { $, EVENTS, NetMonitorView } = monitor.panelWin;
+  let { RequestsMenu } = NetMonitorView;
+  RequestsMenu.lazyUpdate = false;
+
+  for (let testcase of TEST_DATA) {
+    info("Testing Security tab visibility for " + testcase.desc);
+    let onNewItem = monitor.panelWin.once(EVENTS.NETWORK_EVENT);
+    let onSecurityInfo = monitor.panelWin.once(EVENTS.RECEIVED_SECURITY_INFO);
+    let onComplete = testcase.isBroken ?
+                       waitForSecurityBrokenNetworkEvent() :
+                       waitForNetworkEvents(monitor, 1);
+
+    let tab = $("#security-tab");
+
+    info("Performing a request to " + testcase.uri);
+    debuggee.performRequests(1, testcase.uri);
+
+    info("Waiting for new network event.");
+    yield onNewItem;
+
+    info("Selecting the request.");
+    RequestsMenu.selectedIndex = 0;
+
+    is(RequestsMenu.selectedItem.attachment.securityState, undefined,
+       "Security state has not yet arrived.");
+    is(tab.hidden, !testcase.visibleOnNewEvent,
+       "Security tab 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.attachment.securityState,
+       "Security state arrived.");
+    is(tab.hidden, !testcase.visibleOnSecurityInfo,
+       "Security tab is " +
+        (testcase.visibleOnSecurityInfo ? "visible" : "hidden") +
+       " after security information arrived.");
+
+    info("Waiting for request to complete.");
+    yield onComplete;
+    is(tab.hidden, !testcase.visibleOnceComplete,
+       "Security tab is " +
+        (testcase.visibleOnceComplete ? "visible" : "hidden") +
+       " after request has been completed.");
+
+    info("Clearing requests.");
+    RequestsMenu.clear();
+  }
+
+  yield teardown(monitor);
+
+  /**
+   * Returns a promise that's resolved once a request with security issues is
+   * completed.
+   */
+  function waitForSecurityBrokenNetworkEvent() {
+    let awaitedEvents = [
+      "UPDATING_REQUEST_HEADERS",
+      "RECEIVED_REQUEST_HEADERS",
+      "UPDATING_REQUEST_COOKIES",
+      "RECEIVED_REQUEST_COOKIES",
+      "STARTED_RECEIVING_RESPONSE",
+      "UPDATING_RESPONSE_CONTENT",
+      "RECEIVED_RESPONSE_CONTENT",
+      "UPDATING_EVENT_TIMINGS",
+      "RECEIVED_EVENT_TIMINGS",
+    ];
+
+    let promises = awaitedEvents.map((event) => {
+      return monitor.panelWin.once(EVENTS[event]);
+    });
+
+    return Promise.all(promises);
+  }
+});
--- a/browser/devtools/netmonitor/test/head.js
+++ b/browser/devtools/netmonitor/test/head.js
@@ -38,16 +38,17 @@ const SINGLE_GET_URL = EXAMPLE_URL + "ht
 const STATISTICS_URL = EXAMPLE_URL + "html_statistics-test-page.html";
 const CURL_URL = EXAMPLE_URL + "html_copy-as-curl.html";
 const CURL_UTILS_URL = EXAMPLE_URL + "html_curl-utils.html";
 
 const SIMPLE_SJS = EXAMPLE_URL + "sjs_simple-test-server.sjs";
 const CONTENT_TYPE_SJS = EXAMPLE_URL + "sjs_content-type-test-server.sjs";
 const STATUS_CODES_SJS = EXAMPLE_URL + "sjs_status-codes-test-server.sjs";
 const SORTING_SJS = EXAMPLE_URL + "sjs_sorting-test-server.sjs";
+const HTTPS_REDIRECT_SJS = EXAMPLE_URL + "sjs_https-redirect-test-server.sjs";
 
 const TEST_IMAGE = EXAMPLE_URL + "test-image.png";
 const TEST_IMAGE_DATA_URI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==";
 
 gDevTools.testing = true;
 SimpleTest.registerCleanupFunction(() => {
   gDevTools.testing = false;
 });
new file mode 100644
--- /dev/null
+++ b/browser/devtools/netmonitor/test/sjs_https-redirect-test-server.sjs
@@ -0,0 +1,18 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function handleRequest(request, response) {
+  response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
+  response.setHeader("Pragma", "no-cache");
+  response.setHeader("Expires", "0");
+
+
+  if (request.scheme === "http") {
+    response.setStatusLine(request.httpVersion, 302, "Found");
+    response.setHeader("Location", "https://" + request.host + request.path);
+  } else {
+    response.setStatusLine(request.httpVersion, 200, "OK");
+    response.write("Page was accessed over HTTPS!");
+  }
+
+}
--- a/browser/locales/en-US/chrome/browser/devtools/netmonitor.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/netmonitor.dtd
@@ -69,16 +69,20 @@
 <!-- LOCALIZATION NOTE (debuggerUI.tab.timings): This is the label displayed
   -  in the network details pane identifying the timings tab. -->
 <!ENTITY netmonitorUI.tab.timings         "Timings">
 
 <!-- LOCALIZATION NOTE (debuggerUI.tab.preview): This is the label displayed
   -  in the network details pane identifying the preview tab. -->
 <!ENTITY netmonitorUI.tab.preview         "Preview">
 
+<!-- LOCALIZATION NOTE (netmonitorUI.tab.security): This is the label displayed
+  -  in the network details pane identifying the security tab. -->
+<!ENTITY netmonitorUI.tab.security        "Security">
+
 <!-- LOCALIZATION NOTE (debuggerUI.footer.filterAll): This is the label displayed
   -  in the network details footer for the "All" filtering button. -->
 <!ENTITY netmonitorUI.footer.filterAll    "All">
 
 <!-- LOCALIZATION NOTE (debuggerUI.footer.filterHTML): This is the label displayed
   -  in the network details footer for the "HTML" filtering button. -->
 <!ENTITY netmonitorUI.footer.filterHTML   "HTML">
 
@@ -187,16 +191,45 @@
   -  in a "wait" state. -->
 <!ENTITY netmonitorUI.timings.wait        "Waiting:">
 
 <!-- LOCALIZATION NOTE (debuggerUI.timings.receive): This is the label displayed
   -  in the network details timings tab identifying the amount of time spent
   -  in a "receive" state. -->
 <!ENTITY netmonitorUI.timings.receive     "Receiving:">
 
+<!-- LOCALIZATION NOTE (netmonitorUI.security.error): This is the label displayed
+  -  in the security tab if a security error prevented the connection. -->
+<!ENTITY netmonitorUI.security.error      "An error occured:">
+
+<!-- LOCALIZATION NOTE (netmonitorUI.security.protocolVersion): This is the label displayed
+  -  in the security tab describing TLS/SSL protocol version. -->
+<!ENTITY netmonitorUI.security.protocolVersion "Protocol version:">
+
+<!-- LOCALIZATION NOTE (netmonitorUI.security.cipherSuite): This is the label displayed
+  -  in the security tab describing the cipher suite used to secure this connection. -->
+<!ENTITY netmonitorUI.security.cipherSuite "Cipher suite:">
+
+<!-- LOCALIZATION NOTE (netmonitorUI.security.hsts): This is the label displayed
+  -  in the security tab describing the usage of HTTP Strict Transport Security. -->
+<!ENTITY netmonitorUI.security.hsts "HTTP Strict Transport Security:">
+
+<!-- LOCALIZATION NOTE (netmonitorUI.security.hpkp): This is the label displayed
+  -  in the security tab describing the usage of Public Key Pinning. -->
+<!ENTITY netmonitorUI.security.hpkp "Public Key Pinning:">
+
+<!-- LOCALIZATION NOTE (netmonitorUI.security.connection): This is the label displayed
+  -  in the security tab describing the section containing information related to
+  -  the secure connection. -->
+<!ENTITY netmonitorUI.security.connection "Connection:">
+
+<!-- LOCALIZATION NOTE (netmonitorUI.security.certificate): This is the label displayed
+  -  in the security tab describing the server certificate section. -->
+<!ENTITY netmonitorUI.security.certificate "Certificate:">
+
 <!-- LOCALIZATION NOTE (netmonitorUI.context.perfTools): This is the label displayed
   -  on the context menu that shows the performance analysis tools -->
 <!ENTITY netmonitorUI.context.perfTools   "Start Performance Analysis…">
 
 <!-- LOCALIZATION NOTE (netmonitorUI.context.perfTools.accesskey): This is the access key
   -  for the performance analysis menu item displayed in the context menu for a request -->
 <!ENTITY netmonitorUI.context.perfTools.accesskey "S">
 
--- a/browser/locales/en-US/chrome/browser/devtools/netmonitor.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/netmonitor.properties
@@ -24,16 +24,56 @@ netmonitor.panelLabel=Network Panel
 netmonitor.commandkey=Q
 netmonitor.accesskey=N
 
 # LOCALIZATION NOTE (netmonitor.tooltip):
 # This string is displayed in the tooltip of the tab when the Network Monitor is
 # displayed inside the developer tools window.
 netmonitor.tooltip=Network Monitor
 
+# LOCALIZATION NOTE (netmonitor.security.state.secure)
+# This string is used as an tooltip for request that was performed over secure
+# channel i.e. the connection was encrypted.
+netmonitor.security.state.secure=The connection used to fetch this resource was secure.
+
+# LOCALIZATION NOTE (netmonitor.security.state.insecure)
+# This string is used as an tooltip for request that was performed over insecure
+# channel i.e. the connection was not encrypted.
+netmonitor.security.state.insecure=The connection used to fetch this resource was not encrypted.
+
+# LOCALIZATION NOTE (netmonitor.security.state.broken)
+# This string is used as an tooltip for request that failed due to security
+# issues.
+netmonitor.security.state.broken=A security error prevented the resource from being loaded.
+
+# LOCALIZATION NOTE (netmonitor.security.enabled):
+# This string is used to indicate that a specific security feature is used by
+# a connection in the security details tab.
+# For example: "HTTP Strict Transport Security: Enabled"
+netmonitor.security.enabled=Enabled
+
+# LOCALIZATION NOTE (netmonitor.security.disabled):
+# This string is used to indicate that a specific security feature is not used by
+# a connection in the security details tab.
+# For example: "HTTP Strict Transport Security: Disabled"
+netmonitor.security.disabled=Disabled
+
+# LOCALIZATION NOTE (netmonitor.security.hostHeader):
+# This string is used as a header for section containing security information
+# related to the remote host. %S is replaced with the domain name of the remote
+# host. For example: Host example.com
+netmonitor.security.hostHeader=Host %S:
+
+# LOCALIZATION NOTE (netmonitor.security.notAvailable):
+# This string is used to indicate that a certain piece of information is not
+# available to be displayd. For example a certificate that has no organization
+# defined:
+#   Organization: <Not Available>
+netmonitor.security.notAvailable=<Not Available>
+
 # LOCALIZATION NOTE (collapseDetailsPane): This is the tooltip for the button
 # that collapses the network details pane in the UI.
 collapseDetailsPane=Hide request details
 
 # LOCALIZATION NOTE (expandDetailsPane): This is the tooltip for the button
 # that expands the network details pane in the UI.
 expandDetailsPane=Show request details
 
--- a/browser/themes/linux/devtools/netmonitor.css
+++ b/browser/themes/linux/devtools/netmonitor.css
@@ -11,17 +11,17 @@
 #toggle-raw-headers {
   padding: 4px;
 }
 
 .requests-menu-status-and-method {
   width: 9em;
 }
 
-.requests-menu-domain {
+.requests-menu-security-and-domain {
   width: 16vw;
 }
 
 .requests-menu-size {
   width: 6em;
 }
 
 /* Responsive sidebar */
--- a/browser/themes/shared/devtools/netmonitor.inc.css
+++ b/browser/themes/shared/devtools/netmonitor.inc.css
@@ -146,21 +146,48 @@
 .theme-light .requests-menu-icon {
   outline: 1px solid @table_itemLightStartBorder@;
 }
 
 .requests-menu-file {
   text-align: start;
 }
 
-.requests-menu-domain {
+.requests-menu-security-and-domain {
   width: 14vw;
   min-width: 10em;
 }
 
+.requests-security-state-icon {
+  -moz-margin-end: 4px;
+  -moz-image-region:rect(0px, 16px, 16px, 0px);
+}
+
+.requests-security-state-icon:hover {
+  -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+
+.requests-security-state-icon:active {
+  -moz-image-region: rect(0px, 48px, 16px, 32px);
+}
+
+.security-state-insecure {
+  list-style-image: url(chrome://browser/skin/identity-icons-generic.png);
+}
+
+.security-state-secure {
+  cursor: pointer;
+  list-style-image: url(chrome://browser/skin/identity-icons-https.png);
+}
+
+.security-state-broken {
+  cursor: pointer;
+  list-style-image: url(chrome://browser/skin/identity-icons-https-mixed-active.png);
+}
+
 .requests-menu-type {
   text-align: center;
   width: 4em;
 }
 
 .requests-menu-size {
   text-align: center;
   width: 8em;
@@ -528,16 +555,29 @@ label.requests-menu-status-code {
   border: none;
   min-width: 1px;
 }
 
 #timings-tabpanel .requests-menu-timings-total {
   transition: transform 0.2s ease-out;
 }
 
+/* Security tabpanel */
+.security-info-section {
+  -moz-padding-start: 1em;
+}
+
+#security-tabpanel {
+  overflow: auto;
+}
+
+#security-error-message {
+  white-space: pre-wrap;
+}
+
 /* Custom request form */
 
 #custom-pane {
   padding: 0.6em 0.5em;
 }
 
 .custom-header {
   font-size: 1.1em;
@@ -773,17 +813,17 @@ label.requests-menu-status-code {
   .requests-menu-status-and-method {
     width: 16vw;
   }
 
   .requests-menu-icon-and-file {
     width: 30vw;
   }
 
-  .requests-menu-domain {
+  .requests-menu-security-and-domain {
     width: 30vw;
   }
 
   .requests-menu-type {
     width: 8vw;
   }
 
   .requests-menu-size {