| Index: ui/webui/resources/js/i18n_template_no_process.js
|
| diff --git a/ui/webui/resources/js/i18n_template_no_process.js b/ui/webui/resources/js/i18n_template_no_process.js
|
| index 44262810b0328671273ec433f4b6a5b0f2c24496..18f7cb775eac418fa0b1e00b4f4e562520b21471 100644
|
| --- a/ui/webui/resources/js/i18n_template_no_process.js
|
| +++ b/ui/webui/resources/js/i18n_template_no_process.js
|
| @@ -2,6 +2,9 @@
|
| // Use of this source code is governed by a BSD-style license that can be
|
| // found in the LICENSE file.
|
|
|
| +/** @typedef {Document|DocumentFragment|Element} */
|
| +var ProcessingRoot;
|
| +
|
| /**
|
| * @fileoverview This is a simple template engine inspired by JsTemplates
|
| * optimized for i18n.
|
| @@ -38,8 +41,9 @@ var i18nTemplate = (function() {
|
| * @param {HTMLElement} element The node to modify.
|
| * @param {string} key The name of the value in the dictionary.
|
| * @param {LoadTimeData} dictionary The dictionary of strings to draw from.
|
| + * @param {!Array<ProcessingRoot>} visited
|
| */
|
| - 'i18n-content': function(element, key, dictionary) {
|
| + 'i18n-content': function(element, key, dictionary, visited) {
|
| element.textContent = dictionary.getString(key);
|
| },
|
|
|
| @@ -51,8 +55,9 @@ var i18nTemplate = (function() {
|
| * if a pair, represents [content, value]. Otherwise, it should be a
|
| * content string with no value.
|
| * @param {LoadTimeData} dictionary The dictionary of strings to draw from.
|
| + * @param {!Array<ProcessingRoot>} visited
|
| */
|
| - 'i18n-options': function(select, key, dictionary) {
|
| + 'i18n-options': function(select, key, dictionary, visited) {
|
| var options = dictionary.getValue(key);
|
| options.forEach(function(optionData) {
|
| var option = typeof optionData == 'string' ?
|
| @@ -72,8 +77,9 @@ var i18nTemplate = (function() {
|
| * followed by a colon, and the name of the value in the dictionary.
|
| * Multiple attribute/key pairs may be separated by semicolons.
|
| * @param {LoadTimeData} dictionary The dictionary of strings to draw from.
|
| + * @param {!Array<ProcessingRoot>} visited
|
| */
|
| - 'i18n-values': function(element, attributeAndKeys, dictionary) {
|
| + 'i18n-values': function(element, attributeAndKeys, dictionary, visited) {
|
| var parts = attributeAndKeys.replace(/\s/g, '').split(/;/);
|
| parts.forEach(function(part) {
|
| if (!part)
|
| @@ -98,10 +104,14 @@ var i18nTemplate = (function() {
|
| }
|
| if (targetObject) {
|
| targetObject[path] = value;
|
| - // In case we set innerHTML (ignoring others) we need to
|
| - // recursively check the content.
|
| - if (path == 'innerHTML')
|
| - process(element, dictionary);
|
| + // In case we set innerHTML (ignoring others) we need to recursively
|
| + // check the content.
|
| + if (path == 'innerHTML') {
|
| + for (var temp = element.firstElementChild; temp;
|
| + temp = temp.nextElementSibling) {
|
| + processWithoutCycles(temp, dictionary, visited);
|
| + }
|
| + }
|
| }
|
| } else {
|
| element.setAttribute(propName, /** @type {string} */(value));
|
| @@ -111,33 +121,85 @@ var i18nTemplate = (function() {
|
| };
|
|
|
| var attributeNames = Object.keys(handlers);
|
| - // Chrome for iOS must use Apple's UIWebView, which (as of April 2015) does
|
| - // not have native shadow DOM support. If shadow DOM is supported (or
|
| - // polyfilled), search for i18n attributes using the /deep/ selector;
|
| - // otherwise, do not attempt to search within the shadow DOM.
|
| - var selector =
|
| - (window.document.body && window.document.body.createShadowRoot) ?
|
| - 'html /deep/ [' + attributeNames.join('],[') + ']' :
|
| - '[' + attributeNames.join('],[') + ']';
|
| + // Only use /deep/ when shadow DOM is supported. As of April 2015 iOS Chrome
|
| + // doesn't support shadow DOM.
|
| + var prefix = Element.prototype.createShadowRoot ? ':root /deep/ ' : '';
|
| + var selector = prefix + '[' + attributeNames.join('],' + prefix + '[') + ']';
|
|
|
| /**
|
| * Processes a DOM tree with the {@code dictionary} map.
|
| - * @param {Document|Element} root The root of the DOM tree to process.
|
| + * @param {ProcessingRoot} root The root of the DOM tree to process.
|
| * @param {LoadTimeData} dictionary The dictionary to draw from.
|
| */
|
| function process(root, dictionary) {
|
| + processWithoutCycles(root, dictionary, []);
|
| + }
|
| +
|
| + /**
|
| + * Internal process() method that stops cycles while processing.
|
| + * @param {ProcessingRoot} root
|
| + * @param {LoadTimeData} dictionary
|
| + * @param {!Array<ProcessingRoot>} visited Already visited roots.
|
| + */
|
| + function processWithoutCycles(root, dictionary, visited) {
|
| + if (visited.indexOf(root) >= 0) {
|
| + // Found a cycle. Stop it.
|
| + return;
|
| + }
|
| +
|
| + // Mark the node as visited before recursing.
|
| + visited.push(root);
|
| +
|
| + var importLinks = root.querySelectorAll('link[rel=import]');
|
| + for (var i = 0; i < importLinks.length; ++i) {
|
| + var importLink = /** @type {!HTMLLinkElement} */(importLinks[i]);
|
| + if (!importLink.import) {
|
| + // Happens when a <link rel=import> is inside a <template>.
|
| + // TODO(dbeam): should we log an error if we detect that here?
|
| + continue;
|
| + }
|
| + processWithoutCycles(importLink.import, dictionary, visited);
|
| + }
|
| +
|
| + var templates = root.querySelectorAll('template');
|
| + for (var i = 0; i < templates.length; ++i) {
|
| + var template = /** @type {HTMLTemplateElement} */(templates[i]);
|
| + processWithoutCycles(template.content, dictionary, visited);
|
| + }
|
| +
|
| + var firstElement = root instanceof Element ? root : root.querySelector('*');
|
| +
|
| + if (prefix) {
|
| + // Prefixes skip root level elements. This is typically <html> but can
|
| + // differ inside of DocumentFragments (i.e. <template>s). Process them
|
| + // explicitly.
|
| + for (var temp = firstElement; temp; temp = temp.nextElementSibling) {
|
| + processElement(/** @type {Element} */(temp), dictionary, visited);
|
| + }
|
| + }
|
| +
|
| var elements = root.querySelectorAll(selector);
|
| for (var element, i = 0; element = elements[i]; i++) {
|
| - for (var j = 0; j < attributeNames.length; j++) {
|
| - var name = attributeNames[j];
|
| - var attribute = element.getAttribute(name);
|
| - if (attribute != null)
|
| - handlers[name](element, attribute, dictionary);
|
| - }
|
| + processElement(element, dictionary, visited);
|
| + }
|
| +
|
| + if (firstElement)
|
| + firstElement.setAttribute('i18n-processed', '');
|
| + }
|
| +
|
| + /**
|
| + * Run through various [i18n-*] attributes and do activate replacements.
|
| + * @param {Element} element
|
| + * @param {LoadTimeData} dictionary
|
| + * @param {!Array<ProcessingRoot>} visited
|
| + */
|
| + function processElement(element, dictionary, visited) {
|
| + for (var i = 0; i < attributeNames.length; i++) {
|
| + var name = attributeNames[i];
|
| + var attribute = element.getAttribute(name);
|
| + if (attribute != null)
|
| + handlers[name](element, attribute, dictionary, visited);
|
| }
|
| - var doc = root instanceof Document ? root : root.ownerDocument;
|
| - if (doc)
|
| - doc.documentElement.classList.add('i18n-processed');
|
| }
|
|
|
| return {
|
|
|