Initial port of identity UI to Fennec. b=437955 r=gavin
authorjohnath@gavin-pc-ubuntu
Fri, 20 Jun 2008 17:01:42 -0400
changeset 64723 43f0100812e62fa37a5465223f4ee5901e729c27
parent 64722 8b9cc7fb2c159d8625beef3e30b3015802305807
child 64724 dcd8c729376b53f9c0228e6c69e04dfaec4dc238
push idunknown
push userunknown
push dateunknown
reviewersgavin
bugs437955
Initial port of identity UI to Fennec. b=437955 r=gavin
mobile/chrome/content/browser.js
mobile/chrome/content/browser.xul
mobile/chrome/content/toolbar.xul
mobile/chrome/jar.mn
mobile/chrome/locale/en-US/browser.properties
mobile/chrome/locale/en-US/toolbar.dtd
mobile/chrome/skin/browser.css
--- a/mobile/chrome/content/browser.js
+++ b/mobile/chrome/content/browser.js
@@ -18,16 +18,17 @@
  * Mozilla Corporation.
  * Portions created by the Initial Developer are Copyright (C) 2008
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Brad Lassey <blassey@mozilla.com>
  *   Mark Finkle <mfinkle@mozilla.com>
  *   Aleks Totic <a@totic.org>
+ *   Johnathan Nightingale <johnath@mozilla.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -376,31 +377,72 @@ ProgressController.prototype = {
 
   // This method is called to indicate progress changes for the currently
   // loading page.
   onProgressChange : function(aWebProgress, aRequest, aCurSelf, aMaxSelf, aCurTotal, aMaxTotal) {
   },
 
   // This method is called to indicate a change to the current location.
   onLocationChange : function(aWebProgress, aRequest, aLocation) {
+    
+    this._hostChanged = true;
+    
     if (aWebProgress.DOMWindow == this._browser.contentWindow) {
       if (LocationBar)
         LocationBar.setURI();
       if (HUDBar)
         HUDBar.setURI(aLocation.spec);
     }
   },
 
   // This method is called to indicate a status changes for the currently
   // loading page.  The message is already formatted for display.
   onStatusChange : function(aWebProgress, aRequest, aStatus, aMessage) {
   },
 
+ // Properties used to cache security state used to update the UI
+  _state: null,
+  _host: undefined,
+  _hostChanged: false, // onLocationChange will flip this bit
+
   // This method is called when the security state of the browser changes.
   onSecurityChange : function(aWebProgress, aRequest, aState) {
+
+    // Don't need to do anything if the data we use to update the UI hasn't
+    // changed
+    if (this._state == aState &&
+        !this._hostChanged) {
+      return;
+    }
+    this._state = aState;
+
+    try {
+      this._host = getBrowser().contentWindow.location.host;
+    } catch(ex) {
+      this._host = null;
+    }
+
+    this._hostChanged = false;
+
+    // Don't pass in the actual location object, since it can cause us to 
+    // hold on to the window object too long.  Just pass in the fields we
+    // care about. (bug 424829)
+    var location = getBrowser().contentWindow.location;
+    var locationObj = {};
+    try {
+      locationObj.host = location.host;
+      locationObj.hostname = location.hostname;
+      locationObj.port = location.port;
+    } catch (ex) {
+      // Can sometimes throw if the URL being visited has no host/hostname,
+      // e.g. about:blank. The _state for these pages means we won't need these
+      // properties anyways, though.
+    }
+    getIdentityHandler().checkIdentity(this._state, locationObj);
+    
   },
 
   QueryInterface : function(aIID) {
     if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
         aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
         aIID.equals(Components.interfaces.nsISupports))
       return this;
 
@@ -774,8 +816,304 @@ ZoomController.prototype = {
     this.scale = (this._browser.boxObject.width) / (elRect.width + 2 * margin);
 
     // Now that we are scaled, we need to scroll to the element
     elRect = this.getPagePosition(el);
     winRect = this.getWindowRect();
     this._browser.contentWindow.scrollTo(Math.max(elRect.x - margin, 0), Math.max(0, elRect.y - margin));
   }
 };
+
+/**
+ * Utility class to handle manipulations of the identity indicators in the UI
+ */
+function IdentityHandler() {
+  this._stringBundle = document.getElementById("bundle_browser");
+  this._staticStrings = {};
+  this._staticStrings[this.IDENTITY_MODE_DOMAIN_VERIFIED] = {
+    encryption_label: this._stringBundle.getString("identity.encrypted")  
+  };
+  this._staticStrings[this.IDENTITY_MODE_IDENTIFIED] = {
+    encryption_label: this._stringBundle.getString("identity.encrypted")
+  };
+  this._staticStrings[this.IDENTITY_MODE_UNKNOWN] = {
+    encryption_label: this._stringBundle.getString("identity.unencrypted")  
+  };
+
+  this._cacheElements();
+}
+
+IdentityHandler.prototype = {
+
+  // Mode strings used to control CSS display
+  IDENTITY_MODE_IDENTIFIED       : "verifiedIdentity", // High-quality identity information
+  IDENTITY_MODE_DOMAIN_VERIFIED  : "verifiedDomain",   // Minimal SSL CA-signed domain verification
+  IDENTITY_MODE_UNKNOWN          : "unknownIdentity",  // No trusted identity information
+
+  // Cache the most recent SSLStatus and Location seen in checkIdentity
+  _lastStatus : null,
+  _lastLocation : null,
+
+  /**
+   * Build out a cache of the elements that we need frequently.
+   */
+  _cacheElements : function() {
+    this._identityPopup = document.getElementById("identity-popup");
+    this._identityBox = document.getElementById("identity-box");
+    this._identityPopupContentBox = document.getElementById("identity-popup-content-box");
+    this._identityPopupContentHost = document.getElementById("identity-popup-content-host");
+    this._identityPopupContentOwner = document.getElementById("identity-popup-content-owner");
+    this._identityPopupContentSupp = document.getElementById("identity-popup-content-supplemental");
+    this._identityPopupContentVerif = document.getElementById("identity-popup-content-verifier");
+    this._identityPopupEncLabel = document.getElementById("identity-popup-encryption-label");
+  },
+
+  /**
+   * Handler for mouseclicks on the "More Information" button in the
+   * "identity-popup" panel.
+   */
+  handleMoreInfoClick : function(event) {
+    displaySecurityInfo();
+    event.stopPropagation();
+  },
+  
+  /**
+   * Helper to parse out the important parts of _lastStatus (of the SSL cert in
+   * particular) for use in constructing identity UI strings
+  */
+  getIdentityData : function() {
+    var result = {};
+    var status = this._lastStatus.QueryInterface(Components.interfaces.nsISSLStatus);
+    var cert = status.serverCert;
+    
+    // Human readable name of Subject
+    result.subjectOrg = cert.organization;
+    
+    // SubjectName fields, broken up for individual access
+    if (cert.subjectName) {
+      result.subjectNameFields = {};
+      cert.subjectName.split(",").forEach(function(v) {
+        var field = v.split("=");
+        this[field[0]] = field[1];
+      }, result.subjectNameFields);
+      
+      // Call out city, state, and country specifically
+      result.city = result.subjectNameFields.L;
+      result.state = result.subjectNameFields.ST;
+      result.country = result.subjectNameFields.C;
+    }
+    
+    // Human readable name of Certificate Authority
+    result.caOrg =  cert.issuerOrganization || cert.issuerCommonName;
+    result.cert = cert;
+    
+    return result;
+  },
+  
+  /**
+   * Determine the identity of the page being displayed by examining its SSL cert
+   * (if available) and, if necessary, update the UI to reflect this.  Intended to
+   * be called by onSecurityChange
+   * 
+   * @param PRUint32 state
+   * @param JS Object location that mirrors an nsLocation (i.e. has .host and
+   *                           .hostname and .port)
+   */
+  checkIdentity : function(state, location) {
+    var currentStatus = getBrowser().securityUI
+                                .QueryInterface(Components.interfaces.nsISSLStatusProvider)
+                                .SSLStatus;
+    this._lastStatus = currentStatus;
+    this._lastLocation = location;
+    
+    if (state & Components.interfaces.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL)
+      this.setMode(this.IDENTITY_MODE_IDENTIFIED);
+    else if (state & Components.interfaces.nsIWebProgressListener.STATE_SECURE_HIGH)
+      this.setMode(this.IDENTITY_MODE_DOMAIN_VERIFIED);
+    else
+      this.setMode(this.IDENTITY_MODE_UNKNOWN);
+  },
+  
+  /**
+   * Return the eTLD+1 version of the current hostname
+   */
+  getEffectiveHost : function() {
+    // Cache the eTLDService if this is our first time through
+    if (!this._eTLDService)
+      this._eTLDService = Cc["@mozilla.org/network/effective-tld-service;1"]
+                         .getService(Ci.nsIEffectiveTLDService);
+    try {
+      return this._eTLDService.getBaseDomainFromHost(this._lastLocation.hostname);
+    } catch (e) {
+      // If something goes wrong (e.g. hostname is an IP address) just fail back
+      // to the full domain.
+      return this._lastLocation.hostname;
+    }
+  },
+  
+  /**
+   * Update the UI to reflect the specified mode, which should be one of the
+   * IDENTITY_MODE_* constants.
+   */
+  setMode : function(newMode) {
+    if (!this._identityBox) {
+      // No identity box means the identity box is not visible, in which
+      // case there's nothing to do.
+      return;
+    }
+
+    this._identityBox.className = newMode;
+    this.setIdentityMessages(newMode);
+    
+    // Update the popup too, if it's open
+    if (this._identityPopup.state == "open")
+      this.setPopupMessages(newMode);
+  },
+  
+  /**
+   * Set up the messages for the primary identity UI based on the specified mode,
+   * and the details of the SSL cert, where applicable
+   *
+   * @param newMode The newly set identity mode.  Should be one of the IDENTITY_MODE_* constants.
+   */
+  setIdentityMessages : function(newMode) {
+    if (newMode == this.IDENTITY_MODE_DOMAIN_VERIFIED) {
+      var iData = this.getIdentityData();
+
+      // We need a port number for all lookups.  If one hasn't been specified, use
+      // the https default
+      var lookupHost = this._lastLocation.host;
+      if (lookupHost.indexOf(':') < 0)
+        lookupHost += ":443";
+
+      // Cache the override service the first time we need to check it
+      if (!this._overrideService)
+        this._overrideService = Components.classes["@mozilla.org/security/certoverride;1"]
+                                          .getService(Components.interfaces.nsICertOverrideService);
+
+      // Verifier is either the CA Org, for a normal cert, or a special string
+      // for certs that are trusted because of a security exception.
+      var tooltip = this._stringBundle.getFormattedString("identity.identified.verifier",
+                                                          [iData.caOrg]);
+      
+      // Check whether this site is a security exception. XPConnect does the right
+      // thing here in terms of converting _lastLocation.port from string to int, but
+      // the overrideService doesn't like undefined ports, so make sure we have
+      // something in the default case (bug 432241).
+      if (this._overrideService.hasMatchingOverride(this._lastLocation.hostname, 
+                                                    (this._lastLocation.port || 443),
+                                                    iData.cert, {}, {}))
+        tooltip = this._stringBundle.getString("identity.identified.verified_by_you");
+    }
+    else if (newMode == this.IDENTITY_MODE_IDENTIFIED) {
+      // If it's identified, then we can populate the dialog with credentials
+      iData = this.getIdentityData();  
+      tooltip = this._stringBundle.getFormattedString("identity.identified.verifier",
+                                                      [iData.caOrg]);
+    }
+    else {
+      tooltip = this._stringBundle.getString("identity.unknown.tooltip");
+    }
+    
+    // Push the appropriate strings out to the UI
+    this._identityBox.tooltipText = tooltip;
+  },
+  
+  /**
+   * Set up the title and content messages for the identity message popup,
+   * based on the specified mode, and the details of the SSL cert, where
+   * applicable
+   *
+   * @param newMode The newly set identity mode.  Should be one of the IDENTITY_MODE_* constants.
+   */
+  setPopupMessages : function(newMode) {
+      
+    this._identityPopup.className = newMode;
+    this._identityPopupContentBox.className = newMode;
+    
+    // Set the static strings up front
+    this._identityPopupEncLabel.textContent = this._staticStrings[newMode].encryption_label;
+    
+    // Initialize the optional strings to empty values
+    var supplemental = "";
+    var verifier = "";
+    
+    if (newMode == this.IDENTITY_MODE_DOMAIN_VERIFIED) {
+      var iData = this.getIdentityData();
+      var host = this.getEffectiveHost();
+      var owner = this._stringBundle.getString("identity.ownerUnknown2");
+      verifier = this._identityBox.tooltipText;
+      supplemental = "";
+    }
+    else if (newMode == this.IDENTITY_MODE_IDENTIFIED) {
+      // If it's identified, then we can populate the dialog with credentials
+      iData = this.getIdentityData();
+      host = this.getEffectiveHost();
+      owner = iData.subjectOrg; 
+      verifier = this._identityBox.tooltipText;
+
+      // Build an appropriate supplemental block out of whatever location data we have
+      if (iData.city)
+        supplemental += iData.city + "\n";        
+      if (iData.state && iData.country)
+        supplemental += this._stringBundle.getFormattedString("identity.identified.state_and_country",
+                                                              [iData.state, iData.country]);
+      else if (iData.state) // State only
+        supplemental += iData.state;
+      else if (iData.country) // Country only
+        supplemental += iData.country;
+    }
+    else {
+      // These strings will be hidden in CSS anyhow
+      host = "";
+      owner = "";
+    }
+    
+    // Push the appropriate strings out to the UI
+    this._identityPopupContentHost.textContent = host;
+    this._identityPopupContentOwner.textContent = owner;
+    this._identityPopupContentSupp.textContent = supplemental;
+    this._identityPopupContentVerif.textContent = verifier;
+  },
+
+  hideIdentityPopup : function() {
+    this._identityPopup.hidePopup();
+  },
+
+  /**
+   * Click handler for the identity-box element in primary chrome.  
+   */
+  handleIdentityButtonEvent : function(event) {
+  
+    event.stopPropagation();
+ 
+    if ((event.type == "click" && event.button != 0) ||
+        (event.type == "keypress" && event.charCode != KeyEvent.DOM_VK_SPACE &&
+         event.keyCode != KeyEvent.DOM_VK_RETURN))
+      return; // Left click, space or enter only
+
+    // Make sure that the display:none style we set in xul is removed now that
+    // the popup is actually needed
+    this._identityPopup.hidden = false;
+    
+    // Tell the popup to consume dismiss clicks, to avoid bug 395314
+    this._identityPopup.popupBoxObject
+        .setConsumeRollupEvent(Ci.nsIPopupBoxObject.ROLLUP_CONSUME);
+    
+    // Update the popup strings
+    this.setPopupMessages(this._identityBox.className);
+    
+    // Now open the popup, anchored off the primary chrome element
+    this._identityPopup.openPopup(this._identityBox, 'after_start');
+  }
+};
+
+var gIdentityHandler; 
+
+/**
+ * Returns the singleton instance of the identity handler class.  Should always be
+ * used instead of referencing the global variable directly or creating new instances
+ */
+function getIdentityHandler() {
+  if (!gIdentityHandler)
+    gIdentityHandler = new IdentityHandler();
+  return gIdentityHandler;    
+}
--- a/mobile/chrome/content/browser.xul
+++ b/mobile/chrome/content/browser.xul
@@ -56,16 +56,20 @@
         width="800" height="480"
         onload="Browser.startup();"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
   <script type="application/x-javascript" src="chrome://global/content/inlineSpellCheckUI.js"/>
   <script type="application/x-javascript" src="chrome://browser/content/commandUtil.js"/>
   <script type="application/x-javascript" src="chrome://browser/content/browser.js"/>
 
+  <stringbundleset id="stringbundleset">
+    <stringbundle id="bundle_browser"  src="chrome://browser/locale/browser.properties"/>
+  </stringbundleset>
+
   <commandset id="cmdset_main">
     <command id="cmd_newTab" label="New Tab" oncommand="CommandUpdater.doCommand(this.id);"/>
     <command id="cmd_closeTab" label="Close Tab" oncommand="CommandUpdater.doCommand(this.id);"/>
     <command id="cmd_switchTab" label="Tabs" oncommand="CommandUpdater.doCommand(this.id);"/>
     <command id="cmd_menu" oncommand="CommandUpdater.doCommand(this.id);"/>
     <command id="cmd_fullscreen" oncommand="CommandUpdater.doCommand(this.id);"/>
     <command id="cmd_addons" oncommand="CommandUpdater.doCommand(this.id);"/>
     <command id="cmd_downloads" oncommand="CommandUpdater.doCommand(this.id);"/>
--- a/mobile/chrome/content/toolbar.xul
+++ b/mobile/chrome/content/toolbar.xul
@@ -48,26 +48,57 @@
     <command id="cmd_forward" label="&forward.label;" disabled="true" oncommand="CommandUpdater.doCommand(this.id);"/>
     <command id="cmd_reload" label="&reload.label;"  oncommand="CommandUpdater.doCommand(this.id);"/>
     <command id="cmd_stop" label="&stop.label;"  oncommand="CommandUpdater.doCommand(this.id);"/>
     <command id="cmd_search" label="&search.label;" oncommand="CommandUpdater.doCommand(this.id);"/>
     <command id="cmd_go" label="&go.label;" oncommand="CommandUpdater.doCommand(this.id);"/>
     <command id="cmd_star" label="&star.label;" oncommand="CommandUpdater.doCommand(this.id);"/>
     <command id="cmd_bookmarks" label="&bookmarks.label;" oncommand="CommandUpdater.doCommand(this.id);"/>
   </commandset>
+  
+  <popupset id="mainPopupSet">
+    <!-- Popup for site identity information -->
+    <panel id="identity-popup" position="after_start" hidden="true" noautofocus="true"
+           norestorefocus="true">
+      <hbox id="identity-popup-container" align="top">
+        <image id="identity-popup-icon"/>
+        <vbox id="identity-popup-content-box">
+          <label id="identity-popup-connectedToLabel" value="&identity.connectedTo;"/>
+          <label id="identity-popup-connectedToLabel2"
+                 value="&identity.unverifiedsite2;"/>
+          <description id="identity-popup-content-host"/>
+          <label id="identity-popup-runByLabel" value="&identity.runBy;"/>
+          <description id="identity-popup-content-owner"/>
+          <description id="identity-popup-content-supplemental"/>
+          <description id="identity-popup-content-verifier"/>
+          <hbox id="identity-popup-encryption" flex="1">
+            <vbox>
+              <image id="identity-popup-encryption-icon"/>
+              <spacer flex="1"/>
+            </vbox>
+            <description id="identity-popup-encryption-label" flex="1"/>
+          </hbox>
+        </vbox>
+      </hbox>
+    </panel>
+  </popupset>
 
   <toolbox insertbefore="browser">
     <toolbar id="toolbar_main" mode="icons">
       <toolbarbutton id="tool_back" command="cmd_back"/>
       <toolbarbutton id="tool_forward" command="cmd_forward"/>
       <toolbaritem id="urlbar-container" flex="1">
-        <stack id="urlbar-image-stack">
-          <image id="urlbar-throbber" src="throbber.png"/>
-          <image id="urlbar-favicon" src=""/>
-        </stack>
+        <box id="identity-box"
+             onclick="getIdentityHandler().handleIdentityButtonEvent(event);"
+             onkeypress="getIdentityHandler().handleIdentityButtonEvent(event);">
+          <stack id="urlbar-image-stack">
+            <image id="urlbar-throbber" src="throbber.png"/>
+            <image id="urlbar-favicon" src=""/>
+          </stack>
+        </box>
         <textbox id="urlbar" type="autocomplete" autocompletesearch="history" enablehistory="false" maxrows="6" completeselectedindex="true" flex="1"
                  ontextentered="LocationBar.goToURI();" ontextreverted="LocationBar.revertURI();"/>
         <hbox id="urlbar-icons">
           <toolbarbutton id="tool_search" command="cmd_search"/>
           <toolbarbutton id="tool_go" command="cmd_go"/>
           <toolbarbutton id="tool_reload" command="cmd_reload"/>
           <toolbarbutton id="tool_stop" command="cmd_stop"/>
         </hbox>
--- a/mobile/chrome/jar.mn
+++ b/mobile/chrome/jar.mn
@@ -26,24 +26,27 @@ classic.jar:
 % skin browser classic/1.0 %
   browser.css                          (skin/browser.css)
   hud.css                              (skin/hud.css)
   images/bookmarks.png                 (skin/images/bookmarks.png)
   images/close.png                     (skin/images/close.png)
   images/close-small.png               (skin/images/close-small.png)
   images/default-favicon.png           (skin/images/default-favicon.png)
   images/go-arrow.png                  (skin/images/go-arrow.png)
+  images/identity.png                  (skin/images/identity.png)
   images/page-starred.png              (skin/images/page-starred.png)
   images/search-glass.png              (skin/images/search-glass.png)
+  images/secure24.png                  (skin/images/secure24.png)
   images/star-page.png                 (skin/images/star-page.png)
   images/starred48.png                 (skin/images/starred48.png)
   images/tap-n-hold.png                (skin/images/tap-n-hold.png)
   images/throbber.png                  (skin/images/throbber.png)
   images/throbber.gif                  (skin/images/throbber.gif)
   images/toolbar.png                   (skin/images/toolbar.png)
   images/mono-toolbar.png              (skin/images/mono-toolbar.png)
 
 @AB_CD@.jar:
 % locale browser @AB_CD@ %
   browser.dtd                          (locale/@AB_CD@/browser.dtd)
+  browser.properties                   (locale/@AB_CD@/browser.properties)
   bookmarks.dtd                        (locale/@AB_CD@/bookmarks.dtd)
   hud.dtd                              (locale/@AB_CD@/hud.dtd)
   toolbar.dtd                          (locale/@AB_CD@/toolbar.dtd)
new file mode 100644
--- /dev/null
+++ b/mobile/chrome/locale/en-US/browser.properties
@@ -0,0 +1,11 @@
+identity.identified.verifier=Verified by: %S
+identity.identified.verified_by_you=You have added a security exception for this site
+identity.identified.state_and_country=%S, %S
+identity.identified.title_with_country=%S (%S)
+
+identity.encrypted=Your connection to this web site is encrypted to prevent eavesdropping.
+identity.unencrypted=Your connection to this web site is not encrypted.
+
+identity.unknown.tooltip=This web site does not supply identity information.
+
+identity.ownerUnknown2=(unknown)
--- a/mobile/chrome/locale/en-US/toolbar.dtd
+++ b/mobile/chrome/locale/en-US/toolbar.dtd
@@ -9,8 +9,23 @@
 <!ENTITY search.label          "Search">
 <!ENTITY search.tooltip        "Search">
 <!ENTITY go.label              "Go">
 <!ENTITY go.tooltip            "Go">
 <!ENTITY star.label            "Star">
 <!ENTITY star.tooltip          "Bookmark this page">
 <!ENTITY bookmarks.label       "Bookmarks">
 <!ENTITY bookmarks.tooltip     "View bookmarks">
+
+<!ENTITY identity.unverifiedsite2 "This web site does not supply identity information.">
+<!ENTITY identity.connectedTo "You are connected to">
+<!-- Localization note (identity.runBy) : This string appears between a
+domain name (above) and an organization name (below). E.g.
+
+example.com
+which is run by
+Example Enterprises, Inc.
+
+The layout of the identity dialog prevents combining this into a single string with
+substitution variables.  If it is difficult to translate the sense of the string
+with that structure, consider a translation which ignores the preceding domain and
+just addresses the organization to follow, e.g. "This site is run by " -->
+<!ENTITY identity.runBy "which is run by">
--- a/mobile/chrome/skin/browser.css
+++ b/mobile/chrome/skin/browser.css
@@ -260,8 +260,103 @@ toolbarbutton[open="true"] {
   height: 32px;
   list-style-image: url(chrome://browser/skin/images/close-small.png);
 }
 /*
 .deckpage-close:hover {
   -moz-image-region: rect(0px, 32px, 16px, 16px);
 }
 */
+
+/* Popup Icons */
+#identity-popup-icon {
+  height: 64px;
+  width: 64px;
+  padding: 0;
+  list-style-image: url("chrome://browser/skin/images/identity.png");
+  -moz-image-region: rect(0px, 64px, 64px, 0px);
+}
+
+#identity-popup.verifiedDomain > #identity-popup-container > #identity-popup-icon {
+  -moz-image-region: rect(64px, 64px, 128px, 0px);
+}
+
+#identity-popup.verifiedIdentity > #identity-popup-container > #identity-popup-icon {
+  -moz-image-region: rect(128px, 64px, 192px, 0px);
+}
+
+/* Popup Body Text */
+#identity-popup-content-box.unknownIdentity > #identity-popup-connectedToLabel ,
+#identity-popup-content-box.unknownIdentity > #identity-popup-runByLabel ,
+#identity-popup-content-box.unknownIdentity > #identity-popup-content-host ,
+#identity-popup-content-box.unknownIdentity > #identity-popup-content-owner ,
+#identity-popup-content-box.verifiedIdentity > #identity-popup-connectedToLabel2 ,
+#identity-popup-content-box.verifiedDomain > #identity-popup-connectedToLabel2 {
+  display: none;
+}
+
+#identity-popup-content-box > description,
+#identity-popup-encryption-label {
+  white-space: pre-wrap;
+  -moz-padding-start: 15px;
+  margin: 2px 0 4px;
+}
+
+#identity-popup-content-box > label {
+  white-space: pre-wrap;
+  -moz-padding-start: 15px;
+  margin: 0;
+}
+
+#identity-popup-content-host ,
+#identity-popup-content-box.verifiedIdentity > #identity-popup-content-owner {
+  font-size: 1.2em;
+}
+
+#identity-popup-content-host {
+  margin-top: 3px;
+  margin-bottom: 5px;
+  font-weight: bold;
+  max-width: 500px;
+}
+
+#identity-popup-content-owner {
+  margin-top: 4px;
+  margin-bottom: 0 !important;
+  font-weight: bold;
+  max-width: 500px;
+}
+
+.verifiedDomain > #identity-popup-content-owner {
+  font-weight: normal;
+}
+
+#identity-popup-content-verifier {
+  margin: 4px 0 2px;
+}
+
+#identity-popup-content-box.verifiedIdentity > #identity-popup-encryption ,
+#identity-popup-content-box.verifiedDomain > #identity-popup-encryption {
+  margin-top: 10px;
+  margin-left: -24px;
+}
+
+#identity-popup-content-box.verifiedIdentity > #identity-popup-encryption > vbox > #identity-popup-encryption-icon ,
+#identity-popup-content-box.verifiedDomain > #identity-popup-encryption > vbox > #identity-popup-encryption-icon {
+  list-style-image: url("chrome://browser/skin/images/secure24.png");
+}
+
+#identity-popup-more-info-button {
+  margin-top: 6px;
+  -moz-margin-end: 1px;
+}
+
+/* Popup Bounding Box */
+#identity-popup {
+  -moz-appearance: menupopup;
+  color: MenuText;
+}
+
+#identity-popup-container {
+  background-image: none;
+  min-width: 280px;
+  padding: 10px;
+}