diff options
author | arv@chromium.org <arv@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-04-26 22:30:44 +0000 |
---|---|---|
committer | arv@chromium.org <arv@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-04-26 22:30:44 +0000 |
commit | 86f76ebbe5b1da4438e257663daa40091a5883ee (patch) | |
tree | 648b69f05e0a048a6de59e53a00df225d154ee50 /chrome/browser/resources/shared | |
parent | 9a1e6d4cfc5028c9c7c431bbf7ed100b28271957 (diff) | |
download | chromium_src-86f76ebbe5b1da4438e257663daa40091a5883ee.zip chromium_src-86f76ebbe5b1da4438e257663daa40091a5883ee.tar.gz chromium_src-86f76ebbe5b1da4438e257663daa40091a5883ee.tar.bz2 |
Refactor parts of the NTP to split things into more managable chunks.
BUG=None
TEST=Manual
Review URL: http://codereview.chromium.org/1759007
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@45631 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/resources/shared')
4 files changed, 319 insertions, 0 deletions
diff --git a/chrome/browser/resources/shared/js/i18n_template.js b/chrome/browser/resources/shared/js/i18n_template.js new file mode 100644 index 0000000..2645bdb --- /dev/null +++ b/chrome/browser/resources/shared/js/i18n_template.js @@ -0,0 +1,108 @@ +// Copyright (c) 2010 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 This is a simple template engine inspired by JsTemplates + * optimized for i18n. + * + * It currently supports two handlers: + * + * * i18n-content which sets the textContent of the element + * + * <span i18n-content="myContent"></span> + * i18nTemplate.process(element, {'myContent': 'Content'}); + * + * * i18n-values is a list of attribute-value or property-value pairs. + * Properties are prefixed with a '.' and can contain nested properties. + * + * <span i18n-values="title:myTitle;.style.fontSize:fontSize"></span> + * i18nTemplate.process(element, { + * 'myTitle': 'Title', + * 'fontSize': '13px' + * }); + */ + +var i18nTemplate = (function() { + /** + * This provides the handlers for the templating engine. The key is used as + * the attribute name and the value is the function that gets called for every + * single node that has this attribute. + * @type {Object} + */ + var handlers = { + /** + * This handler sets the textContent of the element. + */ + 'i18n-content': function(element, attributeValue, obj) { + element.textContent = obj[attributeValue]; + }, + + /** + * This is used to set HTML attributes and DOM properties,. The syntax is: + * attributename:key; + * .domProperty:key; + * .nested.dom.property:key + */ + 'i18n-values': function(element, attributeValue, obj) { + var parts = attributeValue.replace(/\s/g, '').split(/;/); + for (var j = 0; j < parts.length; j++) { + var a = parts[j].match(/^([^:]+):(.+)$/); + if (a) { + var propName = a[1]; + var propExpr = a[2]; + + // Ignore missing properties + if (propExpr in obj) { + var value = obj[propExpr]; + if (propName.charAt(0) == '.') { + var path = propName.slice(1).split('.'); + var object = element; + while (object && path.length > 1) { + object = object[path.shift()]; + } + if (object) { + object[path] = value; + // In case we set innerHTML (ignoring others) we need to + // recursively check the content + if (path == 'innerHTML') { + process(element, obj); + } + } + } else { + element.setAttribute(propName, value); + } + } else { + console.warn('i18n-values: Missing value for "' + propExpr + '"'); + } + } + } + } + }; + + var attributeNames = []; + for (var key in handlers) { + attributeNames.push(key); + } + var selector = '[' + attributeNames.join('],[') + ']'; + + /** + * Processes a DOM tree with the {@code obj} map. + */ + function process(node, obj) { + var elements = node.querySelectorAll(selector); + for (var element, i = 0; element = elements[i]; i++) { + for (var j = 0; j < attributeNames.length; j++) { + var name = attributeNames[j]; + var att = element.getAttribute(name); + if (att != null) { + handlers[name](element, att, obj); + } + } + } + } + + return { + process: process + }; +})(); diff --git a/chrome/browser/resources/shared/js/local_strings.js b/chrome/browser/resources/shared/js/local_strings.js new file mode 100644 index 0000000..75b9859 --- /dev/null +++ b/chrome/browser/resources/shared/js/local_strings.js @@ -0,0 +1,47 @@ +// Copyright (c) 2010 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. + +/** + * The local strings get injected into the page usig a variable named + * {@code templateData}. This class provides a simpler interface to access those + * strings. + * @constructor + */ +function LocalStrings() { +} + +LocalStrings.prototype = { + /** + * The template data object. + * @type {Object} + */ + templateData: null, + + /** + * Gets a localized string by its id. + * @param {string} s The ID of the string we want. + * @return {string} The localized string. + */ + getString: function(id) { + // TODO(arv): We should not rely on a global variable here. + return (this.templateData || window.templateData)[id] || ''; + }, + + /** + * Returns a formatted localized string where $1 to $9 are replaced by the + * second to the tenth argument. + * @param {string} id The ID of the string we want. + * @param {...string} The extra values to include in the formatted output. + * @return {string} The formatted string. + */ + getStringF: function(id, var_args) { + var s = this.getString(id); + var args = arguments; + return s.replace(/\$[$1-9]/g, function(m) { + if (m == '$$') + return '$'; + return args[m[1]]; + }); + } +}; diff --git a/chrome/browser/resources/shared/js/parse_html_subset.js b/chrome/browser/resources/shared/js/parse_html_subset.js new file mode 100644 index 0000000..eecf6ee --- /dev/null +++ b/chrome/browser/resources/shared/js/parse_html_subset.js @@ -0,0 +1,79 @@ +/** + * Whitelist of tag names allowed in parseHtmlSubset. + * @type {[string]} + */ +var allowedTags = ['A', 'B', 'STRONG']; + +/** + * Parse a very small subset of HTML. + * @param {string} s The string to parse. + * @throws {Error} In case of non supported markup. + * @return {DocumentFragment} A document fragment containing the DOM tree. + */ +var allowedAttributes = { + 'href': function(node, value) { + // Only allow a[href] starting with http:// and https:// + return node.tagName == 'A' && (value.indexOf('http://') == 0 || + value.indexOf('https://') == 0); + }, + 'target': function(node, value) { + // Allow a[target] but reset the value to "". + if (node.tagName != 'A') + return false; + node.setAttribute('target', ''); + return true; + } +} + +/** + * Parse a very small subset of HTML. This ensures that insecure HTML / + * javascript cannot be injected into the new tab page. + * @param {string} s The string to parse. + * @throws {Error} In case of non supported markup. + * @return {DocumentFragment} A document fragment containing the DOM tree. + */ +function parseHtmlSubset(s) { + function walk(n, f) { + f(n); + for (var i = 0; i < n.childNodes.length; i++) { + walk(n.childNodes[i], f); + } + } + + function assertElement(node) { + if (allowedTags.indexOf(node.tagName) == -1) + throw Error(node.tagName + ' is not supported'); + } + + function assertAttribute(attrNode, node) { + var n = attrNode.nodeName; + var v = attrNode.nodeValue; + if (!allowedAttributes.hasOwnProperty(n) || !allowedAttributes[n](node, v)) + throw Error(node.tagName + '[' + n + '="' + v + '"] is not supported'); + } + + var r = document.createRange(); + r.selectNode(document.body); + // This does not execute any scripts. + var df = r.createContextualFragment(s); + walk(df, function(node) { + switch (node.nodeType) { + case Node.ELEMENT_NODE: + assertElement(node); + var attrs = node.attributes; + for (var i = 0; i < attrs.length; i++) { + assertAttribute(attrs[i], node); + } + break; + + case Node.COMMENT_NODE: + case Node.DOCUMENT_FRAGMENT_NODE: + case Node.TEXT_NODE: + break; + + default: + throw Error('Node type ' + node.nodeType + ' is not supported'); + } + }); + return df; +} diff --git a/chrome/browser/resources/shared/js/parse_html_subset_test.html b/chrome/browser/resources/shared/js/parse_html_subset_test.html new file mode 100644 index 0000000..54bb3da --- /dev/null +++ b/chrome/browser/resources/shared/js/parse_html_subset_test.html @@ -0,0 +1,85 @@ +<!DOCTYPE html> +<html> +<head> +<title></title> +<script src="http://closure-library.googlecode.com/svn/trunk/closure/goog/base.js"></script> +<script src="parse_html_subset.js"></script> +<script> + +goog.require('goog.testing.jsunit'); + +</script> + +</head> +<body> +<script> + +function parseAndAssertThrows(s) { + assertThrows(function() { + parseHtmlSubset(s); + }); +} + +function parseAndAssertNotThrows(s) { + assertNotThrows(function() { + parseHtmlSubset(s); + }); +} + +function testText() { + parseAndAssertNotThrows(''); + parseAndAssertNotThrows('abc'); + parseAndAssertNotThrows(' '); +} + +function testSupportedTags() { + parseAndAssertNotThrows('<b>bold</b>'); + parseAndAssertNotThrows('Some <b>bold</b> text'); + parseAndAssertNotThrows('Some <strong>strong</strong> text'); + parseAndAssertNotThrows('<B>bold</B>'); + parseAndAssertNotThrows('Some <B>bold</B> text'); + parseAndAssertNotThrows('Some <STRONG>strong</STRONG> text'); +} + +function testInvaliTags() { + parseAndAssertThrows('<unknown_tag>x</unknown_tag>'); + parseAndAssertThrows('<img>'); + parseAndAssertThrows('<script>alert(1)<' + '/script>'); +} + +function testInvalidAttributes() { + parseAndAssertThrows('<b onclick="alert(1)">x</b>'); + parseAndAssertThrows('<b style="color:red">x</b>'); + parseAndAssertThrows('<b foo>x</b>'); + parseAndAssertThrows('<b foo=bar></b>'); +} + +function testValidAnchors() { + parseAndAssertNotThrows('<a href="http://google.com">Google</a>'); + parseAndAssertNotThrows('<a href="https://google.com">Google</a>'); +} + +function testInvalidAnchorHrefs() { + parseAndAssertThrows('<a href="ftp://google.com">Google</a>'); + parseAndAssertThrows('<a href="http/google.com">Google</a>'); + parseAndAssertThrows('<a href="javascript:alert(1)">Google</a>'); +} + +function testInvalidAnchorAttributes() { + parseAndAssertThrows('<a name=foo>Google</a>'); + parseAndAssertThrows( + '<a onclick="alert(1)" href="http://google.com">Google</a>'); + parseAndAssertThrows('<a foo="bar(1)" href="http://google.com">Google</a>'); +} + +function testAnchorTarget() { + parseAndAssertNotThrows( + '<a href="http://google.com" target="blank_">Google</a>'); + parseAndAssertNotThrows( + '<a href="http://google.com" target="foo">Google</a>'); +} + +</script> + +</body> +</html> |