summaryrefslogtreecommitdiff
path: root/chromium/ui/webui/resources/js
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/ui/webui/resources/js')
-rw-r--r--chromium/ui/webui/resources/js/BUILD.gn5
-rw-r--r--chromium/ui/webui/resources/js/cr/ui/command.js2
-rw-r--r--chromium/ui/webui/resources/js/load_time_data.js6
-rw-r--r--chromium/ui/webui/resources/js/parse_html_subset.js155
-rw-r--r--chromium/ui/webui/resources/js/plural_string_proxy.js33
5 files changed, 159 insertions, 42 deletions
diff --git a/chromium/ui/webui/resources/js/BUILD.gn b/chromium/ui/webui/resources/js/BUILD.gn
index 42bcefc2b27..2eb7ada9411 100644
--- a/chromium/ui/webui/resources/js/BUILD.gn
+++ b/chromium/ui/webui/resources/js/BUILD.gn
@@ -167,6 +167,7 @@ js_type_check("js_resources_modules") {
":list_property_update_behavior.m",
":load_time_data.m",
":parse_html_subset.m",
+ ":plural_string_proxy",
":promise_resolver.m",
":search_highlight_utils.m",
":util.m",
@@ -244,6 +245,10 @@ js_library("parse_html_subset.m") {
extra_deps = [ ":modulize" ]
}
+js_library("plural_string_proxy") {
+ deps = [ ":cr.m" ]
+}
+
js_library("promise_resolver.m") {
sources = [ "$root_gen_dir/ui/webui/resources/js/promise_resolver.m.js" ]
deps = [ ":assert.m" ]
diff --git a/chromium/ui/webui/resources/js/cr/ui/command.js b/chromium/ui/webui/resources/js/cr/ui/command.js
index 4f63fbcfab2..4d467c0d66f 100644
--- a/chromium/ui/webui/resources/js/cr/ui/command.js
+++ b/chromium/ui/webui/resources/js/cr/ui/command.js
@@ -193,7 +193,7 @@ cr.define('cr.ui', function() {
const target = e.target;
// Ignore focus on a menu button or command item.
- if (target.menu || target.command) {
+ if (target.menu || target.command || (target instanceof cr.ui.MenuItem)) {
return;
}
diff --git a/chromium/ui/webui/resources/js/load_time_data.js b/chromium/ui/webui/resources/js/load_time_data.js
index 94545cafa27..a4e3054b620 100644
--- a/chromium/ui/webui/resources/js/load_time_data.js
+++ b/chromium/ui/webui/resources/js/load_time_data.js
@@ -19,9 +19,9 @@
/**
* @typedef {{
- * substitutions: (Array<string>|undefined),
- * attrs: (Object<function(Node, string):boolean>|undefined),
- * tags: (Array<string>|undefined),
+ * substitutions: (!Array<string>|undefined),
+ * attrs: (!Array<string>|undefined),
+ * tags: (!Array<string>|undefined),
* }}
*/
/* #export */ let SanitizeInnerHtmlOpts;
diff --git a/chromium/ui/webui/resources/js/parse_html_subset.js b/chromium/ui/webui/resources/js/parse_html_subset.js
index c5869640195..a1432098eb9 100644
--- a/chromium/ui/webui/resources/js/parse_html_subset.js
+++ b/chromium/ui/webui/resources/js/parse_html_subset.js
@@ -3,11 +3,11 @@
// found in the LICENSE file.
/**
- * Parses a very small subset of HTML. This ensures that insecure HTML /
- * javascript cannot be injected into the new tab page.
+ * Parses a very small subset of HTML. This ensures that insecure HTML /
+ * javascript cannot be injected into WebUI.
* @param {string} s The string to parse.
- * @param {Array<string>=} opt_extraTags Optional extra allowed tags.
- * @param {Object<function(Node, string):boolean>=} opt_extraAttrs
+ * @param {!Array<string>=} opt_extraTags Optional extra allowed tags.
+ * @param {!Array<string>=} opt_extraAttrs
* Optional extra allowed attributes (all tags are run through these).
* @throws {Error} In case of non supported markup.
* @return {DocumentFragment} A document fragment containing the DOM tree.
@@ -15,38 +15,114 @@
/* #export */ const parseHtmlSubset = (function() {
'use strict';
- const allowedAttributes = {
- 'href'(node, value) {
- // Only allow a[href] starting with chrome:// and https://
- return node.tagName === 'A' &&
- (value.startsWith('chrome://') || value.startsWith('https://'));
- },
- 'target'(node, value) {
- // Only allow a[target='_blank'].
- // TODO(dbeam): are there valid use cases for target !== '_blank'?
- return node.tagName === 'A' && value === '_blank';
- },
- };
+ /** @typedef {function(!Node, string):boolean} */
+ let AllowFunction;
+
+ /** @type {!AllowFunction} */
+ const allowAttribute = (node, value) => true;
/**
- * Whitelist of tag names allowed in parseHtmlSubset.
- * @type {!Array<string>}
+ * Allow-list of attributes in parseHtmlSubset.
+ * @type {!Map<string, !AllowFunction>}
* @const
*/
- const allowedTags = ['A', 'B', 'SPAN', 'STRONG'];
-
- /** @param {...Object} var_args Objects to merge. */
- function merge(var_args) {
- const clone = {};
- for (let i = 0; i < arguments.length; ++i) {
- if (typeof arguments[i] === 'object') {
- for (const key in arguments[i]) {
- if (arguments[i].hasOwnProperty(key)) {
- clone[key] = arguments[i][key];
- }
- }
+ const allowedAttributes = new Map([
+ [
+ 'href',
+ (node, value) => {
+ // Only allow a[href] starting with chrome:// and https://
+ return node.tagName === 'A' &&
+ (value.startsWith('chrome://') || value.startsWith('https://'));
}
- }
+ ],
+ [
+ 'target',
+ (node, value) => {
+ // Only allow a[target='_blank'].
+ // TODO(dbeam): are there valid use cases for target !== '_blank'?
+ return node.tagName === 'A' && value === '_blank';
+ }
+ ],
+ ]);
+
+ /**
+ * Allow-list of optional attributes in parseHtmlSubset.
+ * @type {!Map<string, !AllowFunction>}
+ * @const
+ */
+ const allowedOptionalAttributes = new Map([
+ ['class', allowAttribute],
+ ['id', allowAttribute],
+ ['is', (node, value) => value === 'action-link' || value === ''],
+ ['role', (node, value) => value === 'link'],
+ [
+ 'src',
+ (node, value) => {
+ // Only allow img[src] starting with chrome://
+ return node.tagName === 'IMG' && value.startsWith('chrome://');
+ }
+ ],
+ ['tabindex', allowAttribute],
+ ]);
+
+ /**
+ * Allow-list of tag names in parseHtmlSubset.
+ * @type {!Set<string>}
+ * @const
+ */
+ const allowedTags =
+ new Set(['A', 'B', 'BR', 'DIV', 'P', 'PRE', 'SPAN', 'STRONG']);
+
+ /**
+ * Allow-list of optional tag names in parseHtmlSubset.
+ * @type {!Set<string>}
+ * @const
+ */
+ const allowedOptionalTags = new Set(['IMG']);
+
+ /**
+ * This is used to create TrustedHTML.
+ * @type {TrustedTypePolicy|undefined}
+ */
+ let untrustedHTMLPolicy;
+ if (window.trustedTypes) {
+ untrustedHTMLPolicy = trustedTypes.createPolicy('parse-html-subset', {
+ createHTML: untrustedHTML => {
+ // This is safe because the untrusted HTML will be sanitized
+ // later in this function. We are adding this so that
+ // the sanitization will not cause a Trusted Types violation.
+ return untrustedHTML;
+ },
+ });
+ }
+
+ /**
+ * @param {!Array<string>} optTags an Array to merge.
+ * @return {!Set<string>} Set of allowed tags.
+ */
+ function mergeTags(optTags) {
+ const clone = new Set(allowedTags);
+ optTags.forEach(str => {
+ const tag = str.toUpperCase();
+ if (allowedOptionalTags.has(tag)) {
+ clone.add(tag);
+ }
+ });
+ return clone;
+ }
+
+ /**
+ * @param {!Array<string>} optAttrs an Array to merge.
+ * @return {!Map<string, !AllowFunction>} Map of allowed
+ * attributes.
+ */
+ function mergeAttrs(optAttrs) {
+ const clone = new Map([...allowedAttributes]);
+ optAttrs.forEach(key => {
+ if (allowedOptionalAttributes.has(key)) {
+ clone.set(key, allowedOptionalAttributes.get(key));
+ }
+ });
return clone;
}
@@ -58,7 +134,7 @@
}
function assertElement(tags, node) {
- if (tags.indexOf(node.tagName) === -1) {
+ if (!tags.has(node.tagName)) {
throw Error(node.tagName + ' is not supported');
}
}
@@ -66,21 +142,24 @@
function assertAttribute(attrs, attrNode, node) {
const n = attrNode.nodeName;
const v = attrNode.nodeValue;
- if (!attrs.hasOwnProperty(n) || !attrs[n](node, v)) {
+ if (!attrs.has(n) || !attrs.get(n)(node, v)) {
throw Error(node.tagName + '[' + n + '="' + v + '"] is not supported');
}
}
return function(s, opt_extraTags, opt_extraAttrs) {
- const extraTags = (opt_extraTags || []).map(function(str) {
- return str.toUpperCase();
- });
- const tags = allowedTags.concat(extraTags);
- const attrs = merge(allowedAttributes, opt_extraAttrs || {});
+ const tags = opt_extraTags ? mergeTags(opt_extraTags) : allowedTags;
+ const attrs =
+ opt_extraAttrs ? mergeAttrs(opt_extraAttrs) : allowedAttributes;
const doc = document.implementation.createHTMLDocument('');
const r = doc.createRange();
r.selectNode(doc.body);
+
+ if (window.trustedTypes) {
+ s = untrustedHTMLPolicy.createHTML(s);
+ }
+
// This does not execute any scripts because the document has no view.
const df = r.createContextualFragment(s);
walk(df, function(node) {
diff --git a/chromium/ui/webui/resources/js/plural_string_proxy.js b/chromium/ui/webui/resources/js/plural_string_proxy.js
new file mode 100644
index 00000000000..5d38a70d862
--- /dev/null
+++ b/chromium/ui/webui/resources/js/plural_string_proxy.js
@@ -0,0 +1,33 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview A helper object used to get a pluralized string.
+ */
+
+// clang-format off
+import {addSingletonGetter, sendWithPromise} from './cr.m.js';
+// clang-format on
+
+/** @interface */
+export class PluralStringProxy {
+ /**
+ * Obtains a pluralized string for |messageName| with |itemCount| items.
+ * @param {!string} messageName The name of the message.
+ * @param {!number} itemCount The number of items.
+ * @return {!Promise<string>} Promise resolved with the appropriate plural
+ * string for |messageName| with |itemCount| items.
+ */
+ getPluralString(messageName, itemCount) {}
+}
+
+/** @implements {PluralStringProxy} */
+export class PluralStringProxyImpl {
+ /** @override */
+ getPluralString(messageName, itemCount) {
+ return sendWithPromise('getPluralString', messageName, itemCount);
+ }
+}
+
+addSingletonGetter(PluralStringProxyImpl);