Bug 666809 - Support SecurityUI in e10s mode. r=felipe f=gavin

This commit is contained in:
Tom Schuster 2013-07-09 22:45:07 -04:00
parent 9123c9870a
commit 8db45abadc
7 changed files with 141 additions and 67 deletions

View File

@ -3668,10 +3668,6 @@ var XULBrowserWindow = {
init: function () {
this.throbberElement = document.getElementById("navigator-throbber");
// Bug 666809 - SecurityUI support for e10s
if (gMultiProcessBrowser)
return;
// Initialize the security button's state and tooltip text. Remember to reset
// _hostChanged, otherwise onSecurityChange will short circuit.
var securityUI = gBrowser.securityUI;
@ -4077,26 +4073,11 @@ var XULBrowserWindow = {
gURLBar.removeAttribute("level");
}
if (gMultiProcessBrowser)
return;
// 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 = gBrowser.contentWindow.location;
var locationObj = {};
let uri = gBrowser.currentURI;
try {
// about:blank can be used by webpages so pretend it is http
locationObj.protocol = location == "about:blank" ? "http:" : location.protocol;
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.
}
gIdentityHandler.checkIdentity(this._state, locationObj);
uri = Services.uriFixup.createExposableURI(uri);
} catch (e) {}
gIdentityHandler.checkIdentity(this._state, uri);
},
// simulate all change notifications after switching tabs
@ -6395,7 +6376,7 @@ var gIdentityHandler = {
// Cache the most recent SSLStatus and Location seen in checkIdentity
_lastStatus : null,
_lastLocation : null,
_lastUri : null,
_mode : "unknownIdentity",
// smart getters
@ -6553,19 +6534,29 @@ var gIdentityHandler = {
* be called by onSecurityChange
*
* @param PRUint32 state
* @param JS Object location that mirrors an nsLocation (i.e. has .host and
* .hostname and .port)
* @param nsIURI uri The address for which the UI should be updated.
*/
checkIdentity : function(state, location) {
checkIdentity : function(state, uri) {
var currentStatus = gBrowser.securityUI
.QueryInterface(Components.interfaces.nsISSLStatusProvider)
.SSLStatus;
this._lastStatus = currentStatus;
this._lastLocation = location;
this._lastUri = uri;
let nsIWebProgressListener = Ci.nsIWebProgressListener;
if (location.protocol == "chrome:" || location.protocol == "about:") {
// For some URIs like data: we can't get a host and so can't do
// anything useful here. Chrome URIs however get special treatment.
let unknown = false;
try {
uri.host;
} catch (e) { unknown = true; }
if ((uri.scheme == "chrome" || uri.scheme == "about") &&
uri.spec !== "about:blank") {
this.setMode(this.IDENTITY_MODE_CHROMEUI);
} else if (unknown) {
this.setMode(this.IDENTITY_MODE_UNKNOWN);
} else if (state & nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL) {
this.setMode(this.IDENTITY_MODE_IDENTIFIED);
} else if (state & nsIWebProgressListener.STATE_IS_SECURE) {
@ -6636,12 +6627,12 @@ var gIdentityHandler = {
.getService(Ci.nsIIDNService);
try {
let baseDomain =
Services.eTLD.getBaseDomainFromHost(this._lastLocation.hostname);
Services.eTLD.getBaseDomainFromHost(this._lastUri.host);
return this._IDNService.convertToDisplayIDN(baseDomain, {});
} catch (e) {
// If something goes wrong (e.g. hostname is an IP address) just fail back
// If something goes wrong (e.g. host is an IP address) just fail back
// to the full domain.
return this._lastLocation.hostname;
return this._lastUri.host;
}
},
@ -6688,19 +6679,17 @@ var gIdentityHandler = {
tooltip = gNavigatorBundle.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).
// .hostname can return an empty string in some exceptional cases -
// hasMatchingOverride does not handle that, so avoid calling it.
// Updating the tooltip value in those cases isn't critical.
// FIXME: Fixing bug 646690 would probably makes this check unnecessary
if (this._lastLocation.hostname &&
this._overrideService.hasMatchingOverride(this._lastLocation.hostname,
(this._lastLocation.port || 443),
iData.cert, {}, {}))
// This can't throw, because URI's with a host that throw don't end up in this case.
let host = this._lastUri.host;
let port = 443;
try {
if (this._lastUri.port > 0)
port = this._lastUri.port;
} catch (e) {}
if (this._overrideService.hasMatchingOverride(host, port, iData.cert, {}, {}))
tooltip = gNavigatorBundle.getString("identity.identified.verified_by_you");
break; }
case this.IDENTITY_MODE_IDENTIFIED: {
// If it's identified, then we can populate the dialog with credentials

View File

@ -16,54 +16,45 @@ var tests = [
{
name: "normal domain",
location: "http://test1.example.org/",
host: "test1.example.org",
effectiveHost: "example.org"
},
{
name: "view-source",
location: "view-source:http://example.com/",
// TODO: these should not be blank, bug 646690
host: "",
effectiveHost: ""
effectiveHost: null
},
{
name: "normal HTTPS",
location: "https://example.com/",
host: "example.com",
effectiveHost: "example.com",
isHTTPS: true
},
{
name: "IDN subdomain",
location: "http://sub1." + idnDomain + "/",
host: "sub1." + idnDomain,
effectiveHost: idnDomain
},
{
name: "subdomain with port",
location: "http://sub1.test1.example.org:8000/",
host: "sub1.test1.example.org:8000",
effectiveHost: "example.org"
},
{
name: "subdomain HTTPS",
location: "https://test1.example.com",
host: "test1.example.com",
location: "https://test1.example.com/",
effectiveHost: "example.com",
isHTTPS: true
},
{
name: "view-source HTTPS",
location: "view-source:https://example.com/",
// TODO: these should not be blank, bug 646690
host: "",
effectiveHost: "",
effectiveHost: null,
isHTTPS: true
},
{
name: "IP address",
location: "http://127.0.0.1:8888/",
host: "127.0.0.1:8888",
effectiveHost: "127.0.0.1"
},
]
@ -112,8 +103,12 @@ function nextTest() {
function checkResult() {
// Sanity check other values, and the value of gIdentityHandler.getEffectiveHost()
is(gIdentityHandler._lastLocation.host, gCurrentTest.host, "host matches for test " + gTestDesc);
is(gIdentityHandler.getEffectiveHost(), gCurrentTest.effectiveHost, "effectiveHost matches for test " + gTestDesc);
is(gIdentityHandler._lastUri.spec, gCurrentTest.location, "location matches for test " + gTestDesc);
// getEffectiveHost can't be called for all modes
if (gCurrentTest.effectiveHost === null)
is(gIdentityHandler._mode == gIdentityHandler.IDENTITY_MODE_UNKNOWN || gIdentityHandler._mode == gIdentityHandler.IDENTITY_MODE_CHROMEUI, true, "mode matched");
else
is(gIdentityHandler.getEffectiveHost(), gCurrentTest.effectiveHost, "effectiveHost matches for test " + gTestDesc);
executeSoon(nextTest);
}

View File

@ -65,6 +65,7 @@ let WebProgressListener = {
onSecurityChange: function onSecurityChange(aWebProgress, aRequest, aState) {
let json = this._setupJSON(aWebProgress, aRequest);
json.state = aState;
json.status = SecurityUI.getSSLStatusAsString();
sendAsyncMessage("Content:SecurityChange", json);
},
@ -149,6 +150,22 @@ let WebNavigation = {
WebNavigation.init();
let SecurityUI = {
getSSLStatusAsString: function() {
let status = docShell.securityUI.QueryInterface(Ci.nsISSLStatusProvider).SSLStatus;
if (status) {
let helper = Cc["@mozilla.org/network/serialization-helper;1"]
.getService(Ci.nsISerializationHelper);
status.QueryInterface(Ci.nsISerializable);
return helper.serializeToString(status);
}
return null;
}
};
addEventListener("DOMTitleChanged", function (aEvent) {
let document = content.document;
switch (aEvent.type) {

View File

@ -12,8 +12,24 @@
<implementation type="application/javascript" implements="nsIAccessibleProvider, nsIObserver, nsIDOMEventListener, nsIMessageListener, nsIMessageListener">
<field name="_securityUI">null</field>
<property name="securityUI"
onget="return null;"/>
readonly="true">
<getter><![CDATA[
if (!this._securityUI) {
let jsm = "resource://gre/modules/RemoteSecurityUI.jsm";
let RemoteSecurityUI = Components.utils.import(jsm, {}).RemoteSecurityUI;
this._securityUI = new RemoteSecurityUI();
}
// We want to double-wrap the JS implemented interface, so that QI and instanceof works.
var ptr = Cc["@mozilla.org/supports-interface-pointer;1"].
createInstance(Ci.nsISupportsInterfacePointer);
ptr.data = this._securityUI;
return ptr.data.QueryInterface(Ci.nsISecureBrowserUI);
]]></getter>
</property>
<property name="webNavigation"
onget="return this._remoteWebNavigation;"
@ -22,16 +38,16 @@
<field name="_remoteWebProgress">null</field>
<property name="webProgress" readonly="true">
<getter>
<![CDATA[
<getter>
<![CDATA[
if (!this._remoteWebProgress) {
let RemoteWebProgress = Components.utils.import("resource://gre/modules/RemoteWebProgress.jsm",
{}).RemoteWebProgress;
let jsm = "resource://gre/modules/RemoteWebProgress.jsm";
let RemoteWebProgress = Components.utils.import(jsm, {}).RemoteWebProgress;
this._remoteWebProgress = new RemoteWebProgress(this);
}
return this._remoteWebProgress;
]]>
</getter>
]]>
</getter>
</property>
<field name="_contentTitle">null</field>

View File

@ -0,0 +1,48 @@
// -*- Mode: javascript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
// 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/.
this.EXPORTED_SYMBOLS = ["RemoteSecurityUI"];
const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
function RemoteSecurityUI()
{
this._state = 0;
this._SSLStatus = null;
}
RemoteSecurityUI.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsISSLStatusProvider, Ci.nsISecureBrowserUI]),
// nsISecureBrowserUI
get state() { return this._state; },
get tooltipText() { return ""; },
// nsISSLStatusProvider
get SSLStatus() { return this._SSLStatus; },
_update: function (state, status) {
let deserialized = null;
if (status) {
let helper = Cc["@mozilla.org/network/serialization-helper;1"]
.getService(Components.interfaces.nsISerializationHelper);
deserialized = helper.deserializeObject(status)
deserialized.QueryInterface(Ci.nsISSLStatus);
}
// We must check the Extended Validation (EV) state here, on the chrome
// process, because NSS is needed for that determination.
if (deserialized && deserialized.isExtendedValidation)
state |= Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL;
this._state = state;
this._SSLStatus = deserialized;
}
};

View File

@ -106,8 +106,16 @@ RemoteWebProgress.prototype = {
break;
case "Content:SecurityChange":
// Invoking this getter triggers the generation of the underlying object,
// which we need to access with ._securityUI, because .securityUI returns
// a wrapper that makes _update inaccessible.
void this._browser.securityUI;
this._browser._securityUI._update(aMessage.json.state, aMessage.json.status);
// The state passed might not be correct due to checks performed
// on the chrome side. _update fixes that.
for each (let p in this._progressListeners) {
p.onSecurityChange(this, req, aMessage.json.state);
p.onSecurityChange(this, req, this._browser.securityUI.state);
}
break;

View File

@ -20,6 +20,7 @@ EXTRA_JS_MODULES += [
'PrivateBrowsingUtils.jsm',
'Promise.jsm',
'PropertyListUtils.jsm',
'RemoteSecurityUI.jsm',
'RemoteWebProgress.jsm',
'Sqlite.jsm',
'Task.jsm',