Index: Source/devtools/front_end/security/SecurityPanel.js |
diff --git a/Source/devtools/front_end/security/SecurityPanel.js b/Source/devtools/front_end/security/SecurityPanel.js |
index 177a57d1c5e1420b2a55a6b601dc58f8ea2038bc..0009a7be72defaeed6ca13af97cc646858c762ac 100644 |
--- a/Source/devtools/front_end/security/SecurityPanel.js |
+++ b/Source/devtools/front_end/security/SecurityPanel.js |
@@ -7,7 +7,8 @@ |
* @extends {WebInspector.PanelWithSidebar} |
* @implements {WebInspector.TargetManager.Observer} |
*/ |
-WebInspector.SecurityPanel = function() { |
+WebInspector.SecurityPanel = function() |
+{ |
WebInspector.PanelWithSidebar.call(this, "security"); |
this.registerRequiredCSS("security/securityPanel.css"); |
this.registerRequiredCSS("security/lockIcon.css"); |
@@ -15,20 +16,24 @@ WebInspector.SecurityPanel = function() { |
var sidebarTree = new TreeOutlineInShadow(); |
sidebarTree.element.classList.add("sidebar-tree"); |
this.panelSidebarElement().appendChild(sidebarTree.element); |
+ sidebarTree.registerRequiredCSS("security/sidebar.css"); |
sidebarTree.registerRequiredCSS("security/lockIcon.css"); |
this.setDefaultFocusedElement(sidebarTree.element); |
this._sidebarMainViewElement = new WebInspector.SecurityMainViewSidebarTreeElement(this); |
sidebarTree.appendChild(this._sidebarMainViewElement); |
+ // TODO(lgarron): Add a section for the main origin. |
+ this._sidebarOriginSection = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("Origins")); |
+ this._sidebarOriginSection.listItemElement.classList.add("security-sidebar-origins"); |
+ sidebarTree.appendChild(this._sidebarOriginSection); |
+ |
this._mainView = new WebInspector.SecurityMainView(); |
- this.showMainView(); |
/** @type {!Map<string, !{securityState: !SecurityAgent.SecurityState, securityDetails: ?NetworkAgent.SecurityDetails}>} */ |
this._origins = new Map(); |
- WebInspector.targetManager.addEventListener(WebInspector.ResourceTreeModel.EventTypes.InspectedURLChanged, this._clear, this); |
- WebInspector.targetManager.addEventListener(WebInspector.ResourceTreeModel.EventTypes.WillReloadPage, this._clear, this); |
- WebInspector.targetManager.addEventListener(WebInspector.ResourceTreeModel.EventTypes.MainFrameNavigated, this._clear, this); |
+ // TODO(lgarron): Until we can clear the panel properly (https://crbug.com/522762), don't trigger _clear(). |
+ // WebInspector.targetManager.addEventListener(WebInspector.ResourceTreeModel.EventTypes.MainFrameNavigated, this._clear, this); |
dgozman
2015/08/21 19:44:36
Do not commit commented code. Better mention in co
lgarron
2015/08/21 21:50:06
I don't want to uncomment the MainFrameNavigated e
|
WebInspector.targetManager.observeTargets(this); |
} |
@@ -61,6 +66,25 @@ WebInspector.SecurityPanel.prototype = { |
}, |
/** |
+ * @param {!WebInspector.SecurityPanel.Origin} origin |
+ */ |
+ showOrigin: function(origin) |
+ { |
+ var originData = this._origins.get(origin); |
+ if (!originData.originView) |
+ originData.originView = new WebInspector.SecurityOriginView(this, origin, originData.securityState, originData.securityDetails); |
+ |
+ this._setVisibleView(originData.originView); |
+ }, |
+ |
+ wasShown: function() |
+ { |
+ WebInspector.Panel.prototype.wasShown.call(this); |
+ if (!this._visibleView) |
+ this._sidebarMainViewElement.select(); |
+ }, |
+ |
+ /** |
* @param {!WebInspector.VBox} view |
*/ |
_setVisibleView: function(view) |
@@ -88,7 +112,13 @@ WebInspector.SecurityPanel.prototype = { |
if (this._origins.has(origin)) { |
var originData = this._origins.get(origin); |
- originData.securityState = this._securityStateMin(originData.securityState, securityState); |
+ var oldSecurityState = originData.securityState; |
+ originData.securityState = this._securityStateMin(oldSecurityState, securityState); |
+ if (oldSecurityState != originData.securityState) { |
+ originData.sidebarElement.setSecurityState(securityState); |
+ if (originData.originView) |
+ originData.originView.setSecurityState(securityState); |
+ } |
} else { |
// TODO(lgarron): Store a (deduplicated) list of different security details we have seen. |
var originData = {}; |
@@ -97,6 +127,12 @@ WebInspector.SecurityPanel.prototype = { |
originData.securityDetails = data.securityDetails; |
this._origins.set(origin, originData); |
+ |
+ originData.sidebarElement = new WebInspector.SecurityOriginViewSidebarTreeElement(this, origin); |
+ this._sidebarOriginSection.appendChild(originData.sidebarElement) |
dgozman
2015/08/21 19:44:37
nit: semicolon
lgarron
2015/08/21 21:50:06
Done.
|
+ originData.sidebarElement.setSecurityState(securityState); |
+ |
+ // Don't construct the origin view yet (let it happen lazily). |
} |
}, |
@@ -148,6 +184,8 @@ WebInspector.SecurityPanel.prototype = { |
_clear: function() |
{ |
this._updateSecurityState(SecurityAgent.SecurityState.Unknown, []); |
+ this._sidebarMainViewElement.select(); |
+ this._sidebarOriginSection.removeChildren(); |
this._origins.clear(); |
}, |
@@ -164,6 +202,9 @@ WebInspector.SecurityPanel._instance = function() |
return WebInspector.SecurityPanel._instanceObject; |
} |
+/** @typedef {string} */ |
+WebInspector.SecurityPanel.Origin; |
dgozman
2015/08/21 19:44:36
nit: move to the top (before SecurityPanel.prototy
lgarron
2015/08/21 21:50:06
Done.
|
+ |
/** |
* @constructor |
* @extends {WebInspector.SidebarTreeElement} |
@@ -172,8 +213,7 @@ WebInspector.SecurityPanel._instance = function() |
WebInspector.SecurityMainViewSidebarTreeElement = function(panel) |
{ |
this._panel = panel; |
- this.small = true; |
- WebInspector.SidebarTreeElement.call(this, "security-sidebar-tree-item", WebInspector.UIString("Overview")); |
+ WebInspector.SidebarTreeElement.call(this, "security-main-view-sidebar-tree-item", WebInspector.UIString("Overview")); |
this.iconElement.classList.add("lock-icon"); |
} |
@@ -210,6 +250,47 @@ WebInspector.SecurityMainViewSidebarTreeElement.prototype = { |
/** |
* @constructor |
+ * @extends {WebInspector.SidebarTreeElement} |
+ * @param {!WebInspector.SecurityPanel} panel |
+ * @param {!WebInspector.SecurityPanel.Origin} origin |
+ */ |
+WebInspector.SecurityOriginViewSidebarTreeElement = function(panel, origin) |
+{ |
+ this._panel = panel; |
+ this._origin = origin; |
+ this.small = true |
dgozman
2015/08/21 19:44:37
nit: semicolon
lgarron
2015/08/21 21:50:06
Done.
|
+ WebInspector.SidebarTreeElement.call(this, "security-sidebar-tree-item", WebInspector.UIString(origin)); |
dgozman
2015/08/21 19:44:37
Don't use UIString for origin. It's meant for stri
lgarron
2015/08/21 21:50:06
Okay. So on user-originated strings, we don't do a
dgozman
2015/08/24 21:44:54
Using |textContent| is safe, it's not like |innerH
lgarron
2015/08/24 23:42:16
Cool, that matches what I expect. :-)
|
+ this.iconElement.classList.add("security-property"); |
+} |
+ |
+WebInspector.SecurityOriginViewSidebarTreeElement.prototype = { |
+ /** |
+ * @override |
+ * @return {boolean} |
+ */ |
+ onselect: function() |
+ { |
+ this._panel.showOrigin(this._origin); |
+ return true; |
+ }, |
+ |
+ /** |
+ * @param {!SecurityAgent.SecurityState} newSecurityState |
+ */ |
+ setSecurityState: function(newSecurityState) |
+ { |
+ for (var className of this.iconElement.classList) |
dgozman
2015/08/21 19:44:37
nit: use {} for more-than-one-liners
lgarron
2015/08/21 21:50:06
:-D
(Done.)
|
+ if (className.indexOf("security-property-") === 0) |
dgozman
2015/08/21 19:44:37
startsWith
lgarron
2015/08/21 21:50:06
Done.
|
+ this.iconElement.classList.remove(className); |
dgozman
2015/08/21 19:44:37
Removing while iterating could be dangerous. Make
lgarron
2015/08/21 21:50:06
Done.
|
+ |
+ this.iconElement.classList.add("security-property-" + newSecurityState); |
+ }, |
+ |
+ __proto__: WebInspector.SidebarTreeElement.prototype |
+} |
+ |
+/** |
+ * @constructor |
* @implements {WebInspector.PanelFactory} |
*/ |
WebInspector.SecurityPanelFactory = function() |
@@ -257,8 +338,8 @@ WebInspector.SecurityMainView.prototype = { |
var explanationLockIcon = explanationDiv.createChild("div", "lock-icon"); |
explanationLockIcon.classList.add("lock-icon-" + explanation.securityState); |
- explanationDiv.createChild("div", "explanation-title").textContent = explanation.summary; |
- explanationDiv.createChild("div", "explanation-text").textContent = explanation.description; |
+ explanationDiv.createChild("div", "explanation-title").textContent = WebInspector.UIString(explanation.summary); |
+ explanationDiv.createChild("div", "explanation-text").textContent = WebInspector.UIString(explanation.description); |
}, |
/** |
@@ -283,3 +364,120 @@ WebInspector.SecurityMainView.prototype = { |
__proto__: WebInspector.VBox.prototype |
} |
+ |
+/** |
+ * @constructor |
+ * @extends {WebInspector.VBox} |
+ */ |
+WebInspector.SecurityOriginView = function(panel, origin, securityState, securityDetails) |
dgozman
2015/08/21 19:44:37
- JSDoc for params;
- why pass securityState and d
lgarron
2015/08/21 21:50:06
JSDoc: Done.
Why pass? I wanted to keep all the b
|
+{ |
+ this._panel = panel; |
+ WebInspector.VBox.call(this); |
+ this.setMinimumSize(200, 100); |
+ |
+ this.element.classList.add("security-origin-view"); |
+ this.registerRequiredCSS("security/originView.css"); |
+ this.registerRequiredCSS("security/lockIcon.css"); |
+ |
+ var titleSection = this.element.createChild("div", "origin-view-section title-section"); |
+ titleSection.createChild("h1").textContent = WebInspector.UIString("Origin"); |
+ var originDisplay = titleSection.createChild("div", "origin-display"); |
+ this._originLockIcon = originDisplay.createChild("span", "security-property"); |
+ this._originLockIcon.classList.add("security-property-" + securityState); |
+ // TODO(lgarron): Highlight the origin scheme. |
+ originDisplay.createChild("span", "origin").textContent = WebInspector.UIString(origin); |
+ |
+ if (securityDetails && securityDetails.certificateDetails) { |
+ var connectionSection = this.element.createChild("div", "origin-view-section"); |
+ connectionSection.createChild("h2").textContent = "Connection"; |
+ |
+ var table = connectionSection.createChild("table", "details-table"); |
+ this._addTableRow(table, "Protocol", securityDetails.protocol); |
+ this._addTableRow(table, "Cipher Suite", securityDetails.cipher + (securityDetails.mac ? " with " + securityDetails.mac : "")); |
+ this._addTableRow(table, "Key Exchange", securityDetails.keyExchange); |
+ } |
+ |
+ if (securityDetails) { |
+ var certificateSection = this.element.createChild("div", "origin-view-section"); |
+ certificateSection.createChild("h2").textContent = "Certificate"; |
+ |
+ var sanDiv = this._createSanDiv(securityDetails); |
+ var validFromString = new Date(1000 * securityDetails.certificateDetails.validFrom).toUTCString(); |
+ var validUntilString = new Date(1000 * securityDetails.certificateDetails.validTo).toUTCString(); |
+ |
+ var table = certificateSection.createChild("table", "details-table"); |
+ this._addTableRow(table, "Subject", securityDetails.certificateDetails.subject.name); |
+ this._addTableRow(table, "SAN", sanDiv); |
+ this._addTableRow(table, "Valid From", validFromString); |
+ this._addTableRow(table, "Valid Until", validUntilString); |
+ this._addTableRow(table, "Issuer", securityDetails.certificateDetails.issuer); |
+ // TODO(lgarron): Make SCT status available in certificate details and show it here. |
+ |
+ // TODO(lgarron): Implement a link to get certificateDetails |
+ |
+ var noteSection = this.element.createChild("div", "origin-view-section"); |
+ noteSection.createChild("h2").textContent = "Development Note"; |
+ // TODO(lgarron): Fix the issue and then remove this section. See comment in _onResponseReceivedSecurityDetails |
+ noteSection.createChild("div").textContent = WebInspector.UIString("At the moment, this view only shows security details from the first connection made to %s", origin); |
+ } |
+ |
+ if (!securityDetails) { |
+ var notSecureSection = this.element.createChild("div", "origin-view-section"); |
+ notSecureSection.createChild("h2").textContent = "Not Secure"; |
+ notSecureSection.createChild("div").textContent = WebInspector.UIString("Your connection to this origin is not secure."); |
+ } |
+} |
+ |
+WebInspector.SecurityOriginView.prototype = { |
+ /** |
+ * @param {!Element} table |
+ * @param {string} key |
+ * @param {string|!HTMLDivElement} value |
+ */ |
+ _addTableRow: function(table, key, value) |
+ { |
+ var row = table.createChild("tr"); |
+ row.createChild("td").textContent = WebInspector.UIString(key); |
+ |
+ var valueTd = row.createChild("td"); |
+ if (value instanceof HTMLDivElement) { |
+ valueTd.appendChild(value); |
+ } else { |
+ valueTd.textContent = WebInspector.UIString(value); |
+ } |
+ }, |
+ |
+ /** |
+ * @param {!NetworkAgent.SecurityDetails} securityDetails |
+ * *return {!Element} |
+ */ |
+ _createSanDiv: function(securityDetails) |
+ { |
+ // TODO(lgarron): Truncate the display of SAN entries and add a button to toggle the full list. |
+ var sanDiv = createElement("div"); |
+ var sanList = securityDetails.certificateDetails.subject.sanDnsNames.concat(securityDetails.certificateDetails.subject.sanIpAddresses); |
+ if (sanList.length === 0) { |
+ sanDiv.textContent = WebInspector.UIString("(N/A)"); |
+ } else { |
+ for (var sanEntry of sanList) { |
+ var span = sanDiv.createChild("span", "san-entry"); |
+ span.textContent = WebInspector.UIString(sanEntry); |
+ } |
+ } |
+ return sanDiv; |
+ }, |
+ |
+ /** |
+ * @param {!SecurityAgent.SecurityState} newSecurityState |
+ */ |
+ setSecurityState: function(newSecurityState) |
+ { |
+ for (var className of this._originLockIcon.classList) |
+ if (className.indexOf("security-property-") === 0) |
+ this._originLockIcon.classList.remove(className); |
+ |
+ this._originLockIcon.classList.add("security-property-" + newSecurityState); |
+ }, |
+ |
+ __proto__: WebInspector.VBox.prototype |
+} |