Initial port of identity UI to Fennec. b=437955 r=gavin

This commit is contained in:
johnath@gavin-pc-ubuntu 2008-06-20 17:01:42 -04:00
parent 6309e1149e
commit 616060f3ea
7 changed files with 501 additions and 4 deletions

View File

@ -23,6 +23,7 @@
* 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
@ -381,6 +382,9 @@ ProgressController.prototype = {
// 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();
@ -394,8 +398,46 @@ ProgressController.prototype = {
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) {
@ -779,3 +821,299 @@ ZoomController.prototype = {
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;
}

View File

@ -61,6 +61,10 @@
<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);"/>

View File

@ -53,16 +53,47 @@
<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">

View File

@ -31,8 +31,10 @@ classic.jar:
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)
@ -44,6 +46,7 @@ classic.jar:
@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)

View File

@ -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)

View File

@ -14,3 +14,18 @@
<!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">

View File

@ -265,3 +265,98 @@ toolbarbutton[open="true"] {
-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;
}