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 223084 7f0b79f1904f19d0fd666d7eb0c5f188bee3a5ae
parent 223083 c4d908fa4442a6132527bd49bbc9bc1186c91ee7
child 223085 14d1166ae92bc795a6f4a6aec2a493beeda1f094
push id10756
push userpastithas@mozilla.com
push dateSun, 11 Jan 2015 09:33:32 +0000
treeherderfx-team@4e343b8494f9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvporof
bugs932179
milestone37.0a1
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 {