Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(218)

Side by Side Diff: third_party/WebKit/Source/devtools/front_end/security/SecurityPanel.js

Issue 2296953004: Send certificates to devtools when it's open instead of using certId (Closed)
Patch Set: self review Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2015 The Chromium Authors. All rights reserved. 1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 /** 5 /**
6 * @constructor 6 * @constructor
7 * @extends {WebInspector.PanelWithSidebar} 7 * @extends {WebInspector.PanelWithSidebar}
8 * @implements {WebInspector.TargetManager.Observer} 8 * @implements {WebInspector.TargetManager.Observer}
9 */ 9 */
10 WebInspector.SecurityPanel = function() 10 WebInspector.SecurityPanel = function()
(...skipping 19 matching lines...) Expand all
30 WebInspector.targetManager.observeTargets(this, WebInspector.Target.Capabili ty.Network); 30 WebInspector.targetManager.observeTargets(this, WebInspector.Target.Capabili ty.Network);
31 } 31 }
32 32
33 /** @typedef {string} */ 33 /** @typedef {string} */
34 WebInspector.SecurityPanel.Origin; 34 WebInspector.SecurityPanel.Origin;
35 35
36 /** 36 /**
37 * @typedef {Object} 37 * @typedef {Object}
38 * @property {!SecurityAgent.SecurityState} securityState - Current security sta te of the origin. 38 * @property {!SecurityAgent.SecurityState} securityState - Current security sta te of the origin.
39 * @property {?NetworkAgent.SecurityDetails} securityDetails - Security details of the origin, if available. 39 * @property {?NetworkAgent.SecurityDetails} securityDetails - Security details of the origin, if available.
40 * @property {?Promise<!NetworkAgent.CertificateDetails>} certificateDetailsProm ise - Certificate details of the origin. Only available if securityDetails are a vailable. 40 * @property {?Promise<>} certificateDetailsPromise - Certificate details of the origin.
41 * @property {?WebInspector.SecurityOriginView} originView - Current SecurityOri ginView corresponding to origin. 41 * @property {?WebInspector.SecurityOriginView} originView - Current SecurityOri ginView corresponding to origin.
42 */ 42 */
43 WebInspector.SecurityPanel.OriginState; 43 WebInspector.SecurityPanel.OriginState;
44 44
45 WebInspector.SecurityPanel.prototype = { 45 WebInspector.SecurityPanel.prototype = {
46 46
47 /** 47 /**
48 * @param {!SecurityAgent.SecurityState} securityState 48 * @param {!SecurityAgent.SecurityState} securityState
49 */ 49 */
50 setRanInsecureContentStyle: function(securityState) 50 setRanInsecureContentStyle: function(securityState)
(...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after
165 originState.originView.setSecurityState(securityState); 165 originState.originView.setSecurityState(securityState);
166 } 166 }
167 } else { 167 } else {
168 // TODO(lgarron): Store a (deduplicated) list of different security details we have seen. https://crbug.com/503170 168 // TODO(lgarron): Store a (deduplicated) list of different security details we have seen. https://crbug.com/503170
169 var originState = {}; 169 var originState = {};
170 originState.securityState = securityState; 170 originState.securityState = securityState;
171 171
172 var securityDetails = request.securityDetails(); 172 var securityDetails = request.securityDetails();
173 if (securityDetails) { 173 if (securityDetails) {
174 originState.securityDetails = securityDetails; 174 originState.securityDetails = securityDetails;
175 originState.certificateDetailsPromise = request.networkManager() .certificateDetailsPromise(securityDetails.certificateId);
176 } 175 }
177 176
178 this._origins.set(origin, originState); 177 this._origins.set(origin, originState);
179 178
180 this._sidebarTree.addOrigin(origin, securityState); 179 this._sidebarTree.addOrigin(origin, securityState);
181 180
182 // Don't construct the origin view yet (let it happen lazily). 181 // Don't construct the origin view yet (let it happen lazily).
183 } 182 }
184 }, 183 },
185 184
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
221 /** 220 /**
222 * @param {!WebInspector.NetworkLogView.MixedContentFilterValues} filterKey 221 * @param {!WebInspector.NetworkLogView.MixedContentFilterValues} filterKey
223 * @return {number} 222 * @return {number}
224 */ 223 */
225 filterRequestCount: function(filterKey) 224 filterRequestCount: function(filterKey)
226 { 225 {
227 return this._filterRequestCounts.get(filterKey) || 0; 226 return this._filterRequestCounts.get(filterKey) || 0;
228 }, 227 },
229 228
230 /** 229 /**
230 * @param {!SecurityAgent.CertificateId} certificateId
231 */
232 showCertificateViewer: function(certificateId)
233 {
234 var securityModel = WebInspector.SecurityModel.fromTarget(this._target);
235 securityModel.showCertificateViewer(certificateId);
236 },
237
238 /**
231 * @param {!SecurityAgent.SecurityState} stateA 239 * @param {!SecurityAgent.SecurityState} stateA
232 * @param {!SecurityAgent.SecurityState} stateB 240 * @param {!SecurityAgent.SecurityState} stateB
233 * @return {!SecurityAgent.SecurityState} 241 * @return {!SecurityAgent.SecurityState}
234 */ 242 */
235 _securityStateMin: function(stateA, stateB) 243 _securityStateMin: function(stateA, stateB)
236 { 244 {
237 return WebInspector.SecurityModel.SecurityStateComparator(stateA, stateB ) < 0 ? stateA : stateB; 245 return WebInspector.SecurityModel.SecurityStateComparator(stateA, stateB ) < 0 ? stateA : stateB;
238 }, 246 },
239 247
240 /** 248 /**
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after
316 * @return {!WebInspector.SecurityPanel} 324 * @return {!WebInspector.SecurityPanel}
317 */ 325 */
318 WebInspector.SecurityPanel._instance = function() 326 WebInspector.SecurityPanel._instance = function()
319 { 327 {
320 return /** @type {!WebInspector.SecurityPanel} */ (self.runtime.sharedInstan ce(WebInspector.SecurityPanel)); 328 return /** @type {!WebInspector.SecurityPanel} */ (self.runtime.sharedInstan ce(WebInspector.SecurityPanel));
321 } 329 }
322 330
323 /** 331 /**
324 * @param {string} text 332 * @param {string} text
325 * @param {!SecurityAgent.CertificateId} certificateId 333 * @param {!SecurityAgent.CertificateId} certificateId
334 * @param {!WebInspector.SecurityPanel} panel
326 * @return {!Element} 335 * @return {!Element}
327 */ 336 */
328 WebInspector.SecurityPanel.createCertificateViewerButton = function(text, certif icateId) 337 WebInspector.SecurityPanel.createCertificateViewerButton = function(text, certif icateId, panel)
329 { 338 {
330 /** 339 /**
331 * @param {!Event} e 340 * @param {!Event} e
332 */ 341 */
333 function showCertificateViewer(e) 342 function showCertificateViewer(e)
334 { 343 {
335 e.consume(); 344 e.consume();
336 WebInspector.multitargetNetworkManager.showCertificateViewer(/** @type { number} */ (certificateId)); 345 panel.showCertificateViewer(certificateId);
337 } 346 }
338 347
339 return createTextButton(text, showCertificateViewer, "security-certificate-b utton"); 348 return createTextButton(text, showCertificateViewer, "security-certificate-b utton");
349 }
350
351 /**
352 * @param {string} text
353 * @param {string} origin
354 * @return {!Element}
355 */
356 WebInspector.SecurityPanel.createCertificateViewerButton2 = function(text, origi n)
357 {
358 /**
359 * @param {!Event} e
360 */
361 function showCertificateViewer(e)
362 {
363 function certificateCallback(names)
364 {
365 InspectorFrontendHost.showCertificateViewer(names);
366 }
367
368 e.consume();
369 WebInspector.multitargetNetworkManager.getCertificate(origin, certificat eCallback);
370 }
371
372 return createTextButton(text, showCertificateViewer, "security-certificate-b utton");
340 } 373 }
341 374
342 /** 375 /**
343 * @constructor 376 * @constructor
344 * @extends {TreeOutlineInShadow} 377 * @extends {TreeOutlineInShadow}
345 * @param {!WebInspector.SecurityPanelSidebarTreeElement} mainViewElement 378 * @param {!WebInspector.SecurityPanelSidebarTreeElement} mainViewElement
346 * @param {function(!WebInspector.SecurityPanel.Origin)} showOriginInPanel 379 * @param {function(!WebInspector.SecurityPanel.Origin)} showOriginInPanel
347 */ 380 */
348 WebInspector.SecurityPanelSidebarTree = function(mainViewElement, showOriginInPa nel) 381 WebInspector.SecurityPanelSidebarTree = function(mainViewElement, showOriginInPa nel)
349 { 382 {
(...skipping 242 matching lines...) Expand 10 before | Expand all | Expand 10 after
592 { 625 {
593 var explanationSection = parent.createChild("div", "security-explanation "); 626 var explanationSection = parent.createChild("div", "security-explanation ");
594 explanationSection.classList.add("security-explanation-" + explanation.s ecurityState); 627 explanationSection.classList.add("security-explanation-" + explanation.s ecurityState);
595 628
596 explanationSection.createChild("div", "security-property").classList.add ("security-property-" + explanation.securityState); 629 explanationSection.createChild("div", "security-property").classList.add ("security-property-" + explanation.securityState);
597 var text = explanationSection.createChild("div", "security-explanation-t ext"); 630 var text = explanationSection.createChild("div", "security-explanation-t ext");
598 text.createChild("div", "security-explanation-title").textContent = expl anation.summary; 631 text.createChild("div", "security-explanation-title").textContent = expl anation.summary;
599 text.createChild("div").textContent = explanation.description; 632 text.createChild("div").textContent = explanation.description;
600 633
601 if (explanation.certificateId) { 634 if (explanation.certificateId) {
602 text.appendChild(WebInspector.SecurityPanel.createCertificateViewerB utton(WebInspector.UIString("View certificate"), explanation.certificateId)); 635 text.appendChild(WebInspector.SecurityPanel.createCertificateViewerB utton(WebInspector.UIString("View certificate"), explanation.certificateId, this ._panel));
603 } 636 }
604 637
605 return text; 638 return text;
606 }, 639 },
607 640
608 /** 641 /**
609 * @param {!SecurityAgent.SecurityState} newSecurityState 642 * @param {!SecurityAgent.SecurityState} newSecurityState
610 * @param {!Array<!SecurityAgent.SecurityStateExplanation>} explanations 643 * @param {!Array<!SecurityAgent.SecurityStateExplanation>} explanations
611 * @param {?SecurityAgent.InsecureContentStatus} insecureContentStatus 644 * @param {?SecurityAgent.InsecureContentStatus} insecureContentStatus
612 * @param {boolean} schemeIsCryptographic 645 * @param {boolean} schemeIsCryptographic
(...skipping 220 matching lines...) Expand 10 before | Expand all | Expand 10 after
833 // Create the certificate section outside the callback, so that it appea rs in the right place. 866 // Create the certificate section outside the callback, so that it appea rs in the right place.
834 var certificateSection = this.element.createChild("div", "origin-view-se ction"); 867 var certificateSection = this.element.createChild("div", "origin-view-se ction");
835 certificateSection.createChild("div", "origin-view-section-title").textC ontent = WebInspector.UIString("Certificate"); 868 certificateSection.createChild("div", "origin-view-section-title").textC ontent = WebInspector.UIString("Certificate");
836 869
837 if (originState.securityDetails.signedCertificateTimestampList.length) { 870 if (originState.securityDetails.signedCertificateTimestampList.length) {
838 // Create the Certificate Transparency section outside the callback, so that it appears in the right place. 871 // Create the Certificate Transparency section outside the callback, so that it appears in the right place.
839 var sctSection = this.element.createChild("div", "origin-view-sectio n"); 872 var sctSection = this.element.createChild("div", "origin-view-sectio n");
840 sctSection.createChild("div", "origin-view-section-title").textConte nt = WebInspector.UIString("Certificate Transparency"); 873 sctSection.createChild("div", "origin-view-section-title").textConte nt = WebInspector.UIString("Certificate Transparency");
841 } 874 }
842 875
843 /** 876 var sanDiv = this._createSanDiv(originState.securityDetails.sanList);
844 * @this {WebInspector.SecurityOriginView} 877 var validFromString = new Date(1000 * originState.securityDetails.validF rom).toUTCString();
845 * @param {?NetworkAgent.CertificateDetails} certificateDetails 878 var validUntilString = new Date(1000 * originState.securityDetails.valid To).toUTCString();
846 */ 879
847 function displayCertificateDetails(certificateDetails) 880 table = new WebInspector.SecurityDetailsTable();
881 certificateSection.appendChild(table.element());
882 table.addRow(WebInspector.UIString("Subject"), originState.securityDetai ls.subjectName);
883 table.addRow(WebInspector.UIString("SAN"), sanDiv);
884 table.addRow(WebInspector.UIString("Valid From"), validFromString);
885 table.addRow(WebInspector.UIString("Valid Until"), validUntilString);
886 table.addRow(WebInspector.UIString("Issuer"), originState.securityDetail s.issuer);
887 table.addRow("", WebInspector.SecurityPanel.createCertificateViewerButto n2(WebInspector.UIString("Open full certificate details"), origin));
888
889 if (!originState.securityDetails.signedCertificateTimestampList.length)
890 return;
891
892 // Show summary of SCT(s) of Certificate Transparency.
893 var sctSummaryTable = new WebInspector.SecurityDetailsTable();
894 sctSummaryTable.element().classList.add("sct-summary");
895 sctSection.appendChild(sctSummaryTable.element());
896 for (var i = 0; i < originState.securityDetails.signedCertificateTimesta mpList.length; i++)
848 { 897 {
849 var sanDiv = this._createSanDiv(certificateDetails.subject); 898 var sct = originState.securityDetails.signedCertificateTimestampList [i];
850 var validFromString = new Date(1000 * certificateDetails.validFrom). toUTCString(); 899 sctSummaryTable.addRow(WebInspector.UIString("SCT"), sct.logDescript ion + " (" + sct.origin + ", " + sct.status + ")");
851 var validUntilString = new Date(1000 * certificateDetails.validTo).t oUTCString();
852
853 var table = new WebInspector.SecurityDetailsTable();
854 certificateSection.appendChild(table.element());
855 table.addRow(WebInspector.UIString("Subject"), certificateDetails.su bject.name);
856 table.addRow(WebInspector.UIString("SAN"), sanDiv);
857 table.addRow(WebInspector.UIString("Valid From"), validFromString);
858 table.addRow(WebInspector.UIString("Valid Until"), validUntilString) ;
859 table.addRow(WebInspector.UIString("Issuer"), certificateDetails.iss uer);
860 table.addRow("", WebInspector.SecurityPanel.createCertificateViewerB utton(WebInspector.UIString("Open full certificate details"), originState.securi tyDetails.certificateId));
861
862 if (!originState.securityDetails.signedCertificateTimestampList.leng th)
863 return;
864
865 // Show summary of SCT(s) of Certificate Transparency.
866 var sctSummaryTable = new WebInspector.SecurityDetailsTable();
867 sctSummaryTable.element().classList.add("sct-summary");
868 sctSection.appendChild(sctSummaryTable.element());
869 for (var i = 0; i < originState.securityDetails.signedCertificateTim estampList.length; i++)
870 {
871 var sct = originState.securityDetails.signedCertificateTimestamp List[i];
872 sctSummaryTable.addRow(WebInspector.UIString("SCT"), sct.logDesc ription + " (" + sct.origin + ", " + sct.status + ")");
873 }
874
875 // Show detailed SCT(s) of Certificate Transparency.
876 var sctTableWrapper = sctSection.createChild("div", "sct-details");
877 sctTableWrapper.classList.add("hidden");
878 for (var i = 0; i < originState.securityDetails.signedCertificateTim estampList.length; i++)
879 {
880 var sctTable = new WebInspector.SecurityDetailsTable();
881 sctTableWrapper.appendChild(sctTable.element());
882 var sct = originState.securityDetails.signedCertificateTimestamp List[i];
883 sctTable.addRow(WebInspector.UIString("Log Name"), sct.logDescri ption);
884 sctTable.addRow(WebInspector.UIString("Log ID"), sct.logId.repla ce(/(.{2})/g,"$1 "));
885 sctTable.addRow(WebInspector.UIString("Validation Status"), sct. status);
886 sctTable.addRow(WebInspector.UIString("Source"), sct.origin);
887 sctTable.addRow(WebInspector.UIString("Issued At"), new Date(sct .timestamp).toUTCString());
888 sctTable.addRow(WebInspector.UIString("Hash Algorithm"), sct.has hAlgorithm);
889 sctTable.addRow(WebInspector.UIString("Signature Algorithm"), sc t.signatureAlgorithm);
890 sctTable.addRow(WebInspector.UIString("Signature Data"), sct.sig natureData.replace(/(.{2})/g,"$1 "));
891 }
892
893 // Add link to toggle between displaying of the summary of the SCT(s ) and the detailed SCT(s).
894 var toggleSctsDetailsLink = sctSection.createChild("div", "link");
895 toggleSctsDetailsLink.classList.add("sct-toggle");
896 toggleSctsDetailsLink.textContent = WebInspector.UIString("Show full details");
897 function toggleSctDetailsDisplay()
898 {
899 var isDetailsShown = !sctTableWrapper.classList.contains("hidden ");
900 if (isDetailsShown)
901 toggleSctsDetailsLink.textContent = WebInspector.UIString("S how full details");
902 else
903 toggleSctsDetailsLink.textContent = WebInspector.UIString("H ide full details");
904 sctSummaryTable.element().classList.toggle("hidden");
905 sctTableWrapper.classList.toggle("hidden");
906 }
907 toggleSctsDetailsLink.addEventListener("click", toggleSctDetailsDisp lay, false);
908 } 900 }
909 901
910 function displayCertificateDetailsUnavailable() 902 // Show detailed SCT(s) of Certificate Transparency.
903 var sctTableWrapper = sctSection.createChild("div", "sct-details");
904 sctTableWrapper.classList.add("hidden");
905 for (var i = 0; i < originState.securityDetails.signedCertificateTimesta mpList.length; i++)
911 { 906 {
912 certificateSection.createChild("div").textContent = WebInspector.UIS tring("Certificate details unavailable."); 907 var sctTable = new WebInspector.SecurityDetailsTable();
908 sctTableWrapper.appendChild(sctTable.element());
909 var sct = originState.securityDetails.signedCertificateTimestampList [i];
910 sctTable.addRow(WebInspector.UIString("Log Name"), sct.logDescriptio n);
911 sctTable.addRow(WebInspector.UIString("Log ID"), sct.logId.replace(/ (.{2})/g,"$1 "));
912 sctTable.addRow(WebInspector.UIString("Validation Status"), sct.stat us);
913 sctTable.addRow(WebInspector.UIString("Source"), sct.origin);
914 sctTable.addRow(WebInspector.UIString("Issued At"), new Date(sct.tim estamp).toUTCString());
915 sctTable.addRow(WebInspector.UIString("Hash Algorithm"), sct.hashAlg orithm);
916 sctTable.addRow(WebInspector.UIString("Signature Algorithm"), sct.si gnatureAlgorithm);
917 sctTable.addRow(WebInspector.UIString("Signature Data"), sct.signatu reData.replace(/(.{2})/g,"$1 "));
913 } 918 }
914 919
915 originState.certificateDetailsPromise.then(displayCertificateDetails.bin d(this), displayCertificateDetailsUnavailable); 920 // Add link to toggle between displaying of the summary of the SCT(s) an d the detailed SCT(s).
921 var toggleSctsDetailsLink = sctSection.createChild("div", "link");
922 toggleSctsDetailsLink.classList.add("sct-toggle");
923 toggleSctsDetailsLink.textContent = WebInspector.UIString("Show full det ails");
924 function toggleSctDetailsDisplay()
925 {
926 var isDetailsShown = !sctTableWrapper.classList.contains("hidden");
927 if (isDetailsShown)
928 toggleSctsDetailsLink.textContent = WebInspector.UIString("Show full details");
929 else
930 toggleSctsDetailsLink.textContent = WebInspector.UIString("Hide full details");
931 sctSummaryTable.element().classList.toggle("hidden");
932 sctTableWrapper.classList.toggle("hidden");
933 }
934 toggleSctsDetailsLink.addEventListener("click", toggleSctDetailsDisplay, false);
916 935
917 var noteSection = this.element.createChild("div", "origin-view-section") ; 936 var noteSection = this.element.createChild("div", "origin-view-section") ;
918 // TODO(lgarron): Fix the issue and then remove this section. See commen t in SecurityPanel._processRequest(). 937 // TODO(lgarron): Fix the issue and then remove this section. See commen t in SecurityPanel._processRequest().
919 noteSection.createChild("div").textContent = WebInspector.UIString("The security details above are from the first inspected response."); 938 noteSection.createChild("div").textContent = WebInspector.UIString("The security details above are from the first inspected response.");
920 } else if (originState.securityState !== SecurityAgent.SecurityState.Unknown ) { 939 } else if (originState.securityState !== SecurityAgent.SecurityState.Unknown ) {
921 var notSecureSection = this.element.createChild("div", "origin-view-sect ion"); 940 var notSecureSection = this.element.createChild("div", "origin-view-sect ion");
922 notSecureSection.createChild("div", "origin-view-section-title").textCon tent = WebInspector.UIString("Not Secure"); 941 notSecureSection.createChild("div", "origin-view-section-title").textCon tent = WebInspector.UIString("Not Secure");
923 notSecureSection.createChild("div").textContent = WebInspector.UIString( "Your connection to this origin is not secure."); 942 notSecureSection.createChild("div").textContent = WebInspector.UIString( "Your connection to this origin is not secure.");
924 } else { 943 } else {
925 var noInfoSection = this.element.createChild("div", "origin-view-section "); 944 var noInfoSection = this.element.createChild("div", "origin-view-section ");
926 noInfoSection.createChild("div", "origin-view-section-title").textConten t = WebInspector.UIString("No Security Information"); 945 noInfoSection.createChild("div", "origin-view-section-title").textConten t = WebInspector.UIString("No Security Information");
927 noInfoSection.createChild("div").textContent = WebInspector.UIString("No security details are available for this origin."); 946 noInfoSection.createChild("div").textContent = WebInspector.UIString("No security details are available for this origin.");
928 } 947 }
929 } 948 }
930 949
931 WebInspector.SecurityOriginView.prototype = { 950 WebInspector.SecurityOriginView.prototype = {
932 951
933 /** 952 /**
934 * @param {!NetworkAgent.CertificateSubject} certificateSubject 953 * @param {!Array<string>} sanList
935 * *return {!Element} 954 * *return {!Element}
936 */ 955 */
937 _createSanDiv: function(certificateSubject) 956 _createSanDiv: function(sanList)
938 { 957 {
939 var sanDiv = createElement("div"); 958 var sanDiv = createElement("div");
940 var sanList = certificateSubject.sanDnsNames.concat(certificateSubject.s anIpAddresses);
941 if (sanList.length === 0) { 959 if (sanList.length === 0) {
942 sanDiv.textContent = WebInspector.UIString("(N/A)"); 960 sanDiv.textContent = WebInspector.UIString("(N/A)");
943 sanDiv.classList.add("empty-san"); 961 sanDiv.classList.add("empty-san");
944 } else { 962 } else {
945 var truncatedNumToShow = 2; 963 var truncatedNumToShow = 2;
946 var listIsTruncated = sanList.length > truncatedNumToShow; 964 var listIsTruncated = sanList.length > truncatedNumToShow;
947 for (var i = 0; i < sanList.length; i++) { 965 for (var i = 0; i < sanList.length; i++) {
948 var span = sanDiv.createChild("span", "san-entry"); 966 var span = sanDiv.createChild("span", "san-entry");
949 span.textContent = sanList[i]; 967 span.textContent = sanList[i];
950 if (listIsTruncated && i >= truncatedNumToShow) 968 if (listIsTruncated && i >= truncatedNumToShow)
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after
1016 row.createChild("div").textContent = key; 1034 row.createChild("div").textContent = key;
1017 1035
1018 var valueDiv = row.createChild("div"); 1036 var valueDiv = row.createChild("div");
1019 if (typeof value === "string") { 1037 if (typeof value === "string") {
1020 valueDiv.textContent = value; 1038 valueDiv.textContent = value;
1021 } else { 1039 } else {
1022 valueDiv.appendChild(value); 1040 valueDiv.appendChild(value);
1023 } 1041 }
1024 } 1042 }
1025 } 1043 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698