Commit e12b963e authored by dpapad's avatar dpapad Committed by Commit Bot

WebUI: Migrate certificate viewer to JS modules.

Also change parse_html_subset.js to initialize the
parse-html-subset TrustedTypes policy lazily, only when it is
actually used, to avoid having to allow that policy in the CSP
configuration on the C++ side for cases where parse_html_subset.js
is just a transitive dependency and not actually used (which is
the case for chrome;//view-cert).

Bug: 1149868
Change-Id: I1a8f32f3289f6068c0eafbb80cdc046451fc028e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2544067
Commit-Queue: dpapad <dpapad@chromium.org>
Reviewed-by: default avatarRebekah Potter <rbpotter@chromium.org>
Cr-Commit-Position: refs/heads/master@{#830138}
parent 716bf8c0
...@@ -6,14 +6,16 @@ import("//chrome/common/features.gni") ...@@ -6,14 +6,16 @@ import("//chrome/common/features.gni")
import("//third_party/closure_compiler/compile_js.gni") import("//third_party/closure_compiler/compile_js.gni")
js_type_check("closure_compile") { js_type_check("closure_compile") {
uses_js_modules = true
deps = [ ":certificate_viewer" ] deps = [ ":certificate_viewer" ]
} }
js_library("certificate_viewer") { js_library("certificate_viewer") {
deps = [ deps = [
"//ui/webui/resources/js:util", "//ui/webui/resources/js:cr.m",
"//ui/webui/resources/js/cr:ui", "//ui/webui/resources/js:util.m",
"//ui/webui/resources/js/cr/ui:tabs", "//ui/webui/resources/js/cr:ui.m",
"//ui/webui/resources/js/cr/ui:tree", "//ui/webui/resources/js/cr/ui:tabs.m",
"//ui/webui/resources/js/cr/ui:tree.m",
] ]
} }
...@@ -7,17 +7,7 @@ ...@@ -7,17 +7,7 @@
<link rel="stylesheet" href="chrome://resources/css/tabs.css"> <link rel="stylesheet" href="chrome://resources/css/tabs.css">
<link rel="stylesheet" href="chrome://resources/css/tree.css"> <link rel="stylesheet" href="chrome://resources/css/tree.css">
<link rel="stylesheet" href="certificate_viewer.css"> <link rel="stylesheet" href="certificate_viewer.css">
<script src="chrome://resources/js/load_time_data.js"></script> <script type="module" src="certificate_viewer.js"></script>
<script src="strings.js"></script>
<script src="chrome://resources/js/promise_resolver.js"></script>
<script src="chrome://resources/js/cr.js"></script>
<script src="chrome://resources/js/cr/ui.js"></script>
<script src="chrome://resources/js/cr/ui/focus_outline_manager.js"></script>
<script src="chrome://resources/js/cr/ui/tabs.js"></script>
<script src="chrome://resources/js/cr/ui/tree.js"></script>
<script src="chrome://resources/js/assert.js"></script>
<script src="chrome://resources/js/util.js"></script>
<script src="certificate_viewer.js"></script>
</head> </head>
<body> <body>
<tabbox id="tabbox"> <tabbox id="tabbox">
......
...@@ -2,8 +2,14 @@ ...@@ -2,8 +2,14 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
(function() { import './strings.m.js';
'use strict';
import {assert} from 'chrome://resources/js/assert.m.js';
import {sendWithPromise} from 'chrome://resources/js/cr.m.js';
import {decorate} from 'chrome://resources/js/cr/ui.m.js';
import {TabBox} from 'chrome://resources/js/cr/ui/tabs.m.js';
import {Tree, TreeItem} from 'chrome://resources/js/cr/ui/tree.m.js';
import {$} from 'chrome://resources/js/util.m.js';
/** /**
* @typedef {{ * @typedef {{
...@@ -18,7 +24,7 @@ ...@@ -18,7 +24,7 @@
* substituting in translated strings and requesting certificate details. * substituting in translated strings and requesting certificate details.
*/ */
function initialize() { function initialize() {
cr.ui.decorate('tabbox', cr.ui.TabBox); decorate('tabbox', TabBox);
const args = JSON.parse(chrome.getVariableValue('dialogArguments')); const args = JSON.parse(chrome.getVariableValue('dialogArguments'));
getCertificateInfo(/** @type {!CertificateInfo} */ (args)); getCertificateInfo(/** @type {!CertificateInfo} */ (args));
...@@ -69,13 +75,13 @@ ...@@ -69,13 +75,13 @@
} }
/** /**
* Initialize a cr.ui.Tree object from a given element using the specified * Initialize a Tree object from a given element using the specified
* change handler. * change handler.
* @param {!HTMLElement} tree The HTMLElement to style as a tree. * @param {!HTMLElement} tree The HTMLElement to style as a tree.
* @param {function()} handler Function to call when a node is selected. * @param {function()} handler Function to call when a node is selected.
*/ */
function initializeTree(tree, handler) { function initializeTree(tree, handler) {
cr.ui.decorate(tree, cr.ui.Tree); decorate(tree, Tree);
tree['detail'] = {payload: {}, children: {}}; tree['detail'] = {payload: {}, children: {}};
tree.addEventListener('change', handler); tree.addEventListener('change', handler);
} }
...@@ -97,7 +103,7 @@ ...@@ -97,7 +103,7 @@
/** /**
* Expand all nodes of the given tree object. * Expand all nodes of the given tree object.
* @param {!cr.ui.Tree} tree The tree object to expand all nodes on. * @param {!Tree} tree The tree object to expand all nodes on.
*/ */
function revealTree(tree) { function revealTree(tree) {
tree.expanded = true; tree.expanded = true;
...@@ -122,7 +128,7 @@ ...@@ -122,7 +128,7 @@
* @param {Object} hierarchy A dictionary containing the hierarchy. * @param {Object} hierarchy A dictionary containing the hierarchy.
*/ */
function createCertificateHierarchy(hierarchy) { function createCertificateHierarchy(hierarchy) {
const treeItem = /** @type {!cr.ui.Tree} */ ($('hierarchy')); const treeItem = /** @type {!Tree} */ ($('hierarchy'));
const root = constructTree(hierarchy[0]); const root = constructTree(hierarchy[0]);
treeItem['detail'].children['root'] = root; treeItem['detail'].children['root'] = root;
treeItem.add(root); treeItem.add(root);
...@@ -138,12 +144,12 @@ ...@@ -138,12 +144,12 @@
} }
/** /**
* Constructs a cr.ui.TreeItem corresponding to the passed in tree * Constructs a TreeItem corresponding to the passed in tree
* @param {Object} tree Dictionary describing the tree structure. * @param {Object} tree Dictionary describing the tree structure.
* @return {!cr.ui.TreeItem} Tree node corresponding to the input dictionary. * @return {!TreeItem} Tree node corresponding to the input dictionary.
*/ */
function constructTree(tree) { function constructTree(tree) {
const treeItem = new cr.ui.TreeItem({ const treeItem = new TreeItem({
label: tree.label, label: tree.label,
detail: {payload: tree.payload ? tree.payload : {}, children: {}} detail: {payload: tree.payload ? tree.payload : {}, children: {}}
}); });
...@@ -160,7 +166,7 @@ ...@@ -160,7 +166,7 @@
* Clear any previous certificate fields in the tree. * Clear any previous certificate fields in the tree.
*/ */
function clearCertificateFields() { function clearCertificateFields() {
const treeItem = /** @type {!cr.ui.Tree} */ ($('cert-fields')); const treeItem = /** @type {!Tree} */ ($('cert-fields'));
for (const key in treeItem['detail'].children) { for (const key in treeItem['detail'].children) {
treeItem.remove(treeItem['detail'].children[key]); treeItem.remove(treeItem['detail'].children[key]);
delete treeItem['detail'].children[key]; delete treeItem['detail'].children[key];
...@@ -174,7 +180,7 @@ ...@@ -174,7 +180,7 @@
clearCertificateFields(); clearCertificateFields();
const item = $('hierarchy').selectedItem; const item = $('hierarchy').selectedItem;
if (item && item.detail.payload.index !== undefined) { if (item && item.detail.payload.index !== undefined) {
cr.sendWithPromise('requestCertificateFields', item.detail.payload.index) sendWithPromise('requestCertificateFields', item.detail.payload.index)
.then(onCertificateFields); .then(onCertificateFields);
} }
} }
...@@ -186,7 +192,7 @@ ...@@ -186,7 +192,7 @@
*/ */
function onCertificateFields(certFields) { function onCertificateFields(certFields) {
clearCertificateFields(); clearCertificateFields();
const treeItem = /** @type {!cr.ui.Tree} */ ($('cert-fields')); const treeItem = /** @type {!Tree} */ ($('cert-fields'));
treeItem.add( treeItem.add(
treeItem['detail'].children['root'] = constructTree(certFields[0])); treeItem['detail'].children['root'] = constructTree(certFields[0]));
revealTree(treeItem); revealTree(treeItem);
...@@ -219,4 +225,3 @@ ...@@ -219,4 +225,3 @@
} }
document.addEventListener('DOMContentLoaded', initialize); document.addEventListener('DOMContentLoaded', initialize);
})();
...@@ -50,7 +50,10 @@ content::WebUIDataSource* GetWebUIDataSource(const std::string& host) { ...@@ -50,7 +50,10 @@ content::WebUIDataSource* GetWebUIDataSource(const std::string& host) {
html_source->OverrideContentSecurityPolicy( html_source->OverrideContentSecurityPolicy(
network::mojom::CSPDirectiveName::TrustedTypes, network::mojom::CSPDirectiveName::TrustedTypes,
"trusted-types cr-ui-tree-js-static;"); "trusted-types cr-ui-tree-js-static certificate-test-script;");
html_source->OverrideContentSecurityPolicy(
network::mojom::CSPDirectiveName::ScriptSrc,
"script-src chrome://resources chrome://test 'self';");
html_source->UseStringsJs(); html_source->UseStringsJs();
......
...@@ -18,8 +18,6 @@ CertificateViewerUITest.prototype = { ...@@ -18,8 +18,6 @@ CertificateViewerUITest.prototype = {
extraLibraries: [ extraLibraries: [
'//third_party/mocha/mocha.js', '//third_party/mocha/mocha.js',
'//chrome/test/data/webui/mocha_adapter.js', '//chrome/test/data/webui/mocha_adapter.js',
'test_util.js',
'certificate_viewer_dialog_test.js',
], ],
/** /**
...@@ -37,6 +35,23 @@ CertificateViewerUITest.prototype = { ...@@ -37,6 +35,23 @@ CertificateViewerUITest.prototype = {
}, },
}; };
// Helper for loading the Mocha test file as a JS module. Not using
// test_loader.html, as the test code needs to be loaded in the context of the
// dialog triggered with the ShowCertificateViewer() C++ call above.
function loadTestModule() {
const scriptPolicy =
window.trustedTypes.createPolicy('certificate-test-script', {
createScriptURL: () =>
'chrome://test/certificate_viewer_dialog_test.js',
});
const s = document.createElement('script');
s.type = 'module';
s.src = scriptPolicy.createScriptURL('');
document.body.appendChild(s);
return new Promise(function(resolve, reject) {
s.addEventListener('load', () => resolve());
});
}
// Include the bulk of c++ code. // Include the bulk of c++ code.
// Certificate viewer UI tests are disabled on platforms with native certificate // Certificate viewer UI tests are disabled on platforms with native certificate
...@@ -49,13 +64,19 @@ GEN('CertificateViewerUITest::CertificateViewerUITest() {}'); ...@@ -49,13 +64,19 @@ GEN('CertificateViewerUITest::CertificateViewerUITest() {}');
GEN('CertificateViewerUITest::~CertificateViewerUITest() {}'); GEN('CertificateViewerUITest::~CertificateViewerUITest() {}');
TEST_F('CertificateViewerUITest', 'DialogURL', function() { TEST_F('CertificateViewerUITest', 'DialogURL', function() {
mocha.grep('DialogURL').run(); loadTestModule().then(() => {
mocha.grep('DialogURL').run();
});
}); });
TEST_F('CertificateViewerUITest', 'CommonName', function() { TEST_F('CertificateViewerUITest', 'CommonName', function() {
mocha.grep('CommonName').run(); loadTestModule().then(() => {
mocha.grep('CommonName').run();
});
}); });
TEST_F('CertificateViewerUITest', 'Details', function() { TEST_F('CertificateViewerUITest', 'Details', function() {
mocha.grep('Details').run(); loadTestModule().then(() => {
mocha.grep('Details').run();
});
}); });
...@@ -2,7 +2,8 @@ ...@@ -2,7 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
GEN('#include "content/public/test/browser_test.h"'); import {$} from 'chrome://resources/js/util.m.js';
import {eventToPromise} from 'chrome://test/test_util.m.js';
/** /**
* Find the first tree item (in the certificate fields tree) with a value. * Find the first tree item (in the certificate fields tree) with a value.
...@@ -34,7 +35,6 @@ suite('CertificateViewer', function() { ...@@ -34,7 +35,6 @@ suite('CertificateViewer', function() {
assertEquals('www.google.com', $('issued-cn').textContent); assertEquals('www.google.com', $('issued-cn').textContent);
}); });
test('Details', async function() { test('Details', async function() {
var certHierarchy = $('hierarchy'); var certHierarchy = $('hierarchy');
var certFields = $('cert-fields'); var certFields = $('cert-fields');
...@@ -49,7 +49,7 @@ suite('CertificateViewer', function() { ...@@ -49,7 +49,7 @@ suite('CertificateViewer', function() {
// Wait for the '-for-testing' event to fire only if |certFields| is not // Wait for the '-for-testing' event to fire only if |certFields| is not
// populated yet, otherwise don't wait, the event has already fired. // populated yet, otherwise don't wait, the event has already fired.
const whenLoaded = certFields.childNodes.length === 0 ? const whenLoaded = certFields.childNodes.length === 0 ?
test_util.eventToPromise( eventToPromise(
'certificate-fields-updated-for-testing', document.body) : 'certificate-fields-updated-for-testing', document.body) :
Promise.resolve(); Promise.resolve();
......
...@@ -84,14 +84,10 @@ ...@@ -84,14 +84,10 @@
* This policy maps a given string to a `TrustedHTML` object * This policy maps a given string to a `TrustedHTML` object
* without performing any validation. Callsites must ensure * without performing any validation. Callsites must ensure
* that the resulting object will only be used in inert * that the resulting object will only be used in inert
* documents. * documents. Initialized lazily.
* @type {!TrustedTypePolicy} * @type {!TrustedTypePolicy}
*/ */
let unsanitizedPolicy; let unsanitizedPolicy;
if (window.trustedTypes) {
unsanitizedPolicy = trustedTypes.createPolicy(
'parse-html-subset', {createHTML: untrustedHTML => untrustedHTML});
}
/** /**
* @param {!Array<string>} optTags an Array to merge. * @param {!Array<string>} optTags an Array to merge.
...@@ -154,6 +150,10 @@ ...@@ -154,6 +150,10 @@
r.selectNode(doc.body); r.selectNode(doc.body);
if (window.trustedTypes) { if (window.trustedTypes) {
if (!unsanitizedPolicy) {
unsanitizedPolicy = trustedTypes.createPolicy(
'parse-html-subset', {createHTML: untrustedHTML => untrustedHTML});
}
s = unsanitizedPolicy.createHTML(s); s = unsanitizedPolicy.createHTML(s);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment