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

Side by Side Diff: ui/webui/resources/js/i18n_template_no_process.js

Issue 1229573003: Teach i18nTemplate.process() to handle <link rel=import> and <template> (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: videoplayer test fixes Created 5 years, 5 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 (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 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 /** @typedef {Document|DocumentFragment|Element} */
6 var ProcessingRoot;
7
5 /** 8 /**
6 * @fileoverview This is a simple template engine inspired by JsTemplates 9 * @fileoverview This is a simple template engine inspired by JsTemplates
7 * optimized for i18n. 10 * optimized for i18n.
8 * 11 *
9 * It currently supports three handlers: 12 * It currently supports three handlers:
10 * 13 *
11 * * i18n-content which sets the textContent of the element. 14 * * i18n-content which sets the textContent of the element.
12 * 15 *
13 * <span i18n-content="myContent"></span> 16 * <span i18n-content="myContent"></span>
14 * 17 *
(...skipping 16 matching lines...) Expand all
31 * the attribute name and the value is the function that gets called for every 34 * the attribute name and the value is the function that gets called for every
32 * single node that has this attribute. 35 * single node that has this attribute.
33 * @type {!Object} 36 * @type {!Object}
34 */ 37 */
35 var handlers = { 38 var handlers = {
36 /** 39 /**
37 * This handler sets the textContent of the element. 40 * This handler sets the textContent of the element.
38 * @param {HTMLElement} element The node to modify. 41 * @param {HTMLElement} element The node to modify.
39 * @param {string} key The name of the value in the dictionary. 42 * @param {string} key The name of the value in the dictionary.
40 * @param {LoadTimeData} dictionary The dictionary of strings to draw from. 43 * @param {LoadTimeData} dictionary The dictionary of strings to draw from.
44 * @param {!Array<ProcessingRoot>} visited
41 */ 45 */
42 'i18n-content': function(element, key, dictionary) { 46 'i18n-content': function(element, key, dictionary, visited) {
43 element.textContent = dictionary.getString(key); 47 element.textContent = dictionary.getString(key);
44 }, 48 },
45 49
46 /** 50 /**
47 * This handler adds options to a <select> element. 51 * This handler adds options to a <select> element.
48 * @param {HTMLElement} select The node to modify. 52 * @param {HTMLElement} select The node to modify.
49 * @param {string} key The name of the value in the dictionary. It should 53 * @param {string} key The name of the value in the dictionary. It should
50 * identify an array of values to initialize an <option>. Each value, 54 * identify an array of values to initialize an <option>. Each value,
51 * if a pair, represents [content, value]. Otherwise, it should be a 55 * if a pair, represents [content, value]. Otherwise, it should be a
52 * content string with no value. 56 * content string with no value.
53 * @param {LoadTimeData} dictionary The dictionary of strings to draw from. 57 * @param {LoadTimeData} dictionary The dictionary of strings to draw from.
58 * @param {!Array<ProcessingRoot>} visited
54 */ 59 */
55 'i18n-options': function(select, key, dictionary) { 60 'i18n-options': function(select, key, dictionary, visited) {
56 var options = dictionary.getValue(key); 61 var options = dictionary.getValue(key);
57 options.forEach(function(optionData) { 62 options.forEach(function(optionData) {
58 var option = typeof optionData == 'string' ? 63 var option = typeof optionData == 'string' ?
59 new Option(optionData) : 64 new Option(optionData) :
60 new Option(optionData[1], optionData[0]); 65 new Option(optionData[1], optionData[0]);
61 select.appendChild(option); 66 select.appendChild(option);
62 }); 67 });
63 }, 68 },
64 69
65 /** 70 /**
66 * This is used to set HTML attributes and DOM properties. The syntax is: 71 * This is used to set HTML attributes and DOM properties. The syntax is:
67 * attributename:key; 72 * attributename:key;
68 * .domProperty:key; 73 * .domProperty:key;
69 * .nested.dom.property:key 74 * .nested.dom.property:key
70 * @param {HTMLElement} element The node to modify. 75 * @param {HTMLElement} element The node to modify.
71 * @param {string} attributeAndKeys The path of the attribute to modify 76 * @param {string} attributeAndKeys The path of the attribute to modify
72 * followed by a colon, and the name of the value in the dictionary. 77 * followed by a colon, and the name of the value in the dictionary.
73 * Multiple attribute/key pairs may be separated by semicolons. 78 * Multiple attribute/key pairs may be separated by semicolons.
74 * @param {LoadTimeData} dictionary The dictionary of strings to draw from. 79 * @param {LoadTimeData} dictionary The dictionary of strings to draw from.
80 * @param {!Array<ProcessingRoot>} visited
75 */ 81 */
76 'i18n-values': function(element, attributeAndKeys, dictionary) { 82 'i18n-values': function(element, attributeAndKeys, dictionary, visited) {
77 var parts = attributeAndKeys.replace(/\s/g, '').split(/;/); 83 var parts = attributeAndKeys.replace(/\s/g, '').split(/;/);
78 parts.forEach(function(part) { 84 parts.forEach(function(part) {
79 if (!part) 85 if (!part)
80 return; 86 return;
81 87
82 var attributeAndKeyPair = part.match(/^([^:]+):(.+)$/); 88 var attributeAndKeyPair = part.match(/^([^:]+):(.+)$/);
83 if (!attributeAndKeyPair) 89 if (!attributeAndKeyPair)
84 throw new Error('malformed i18n-values: ' + attributeAndKeys); 90 throw new Error('malformed i18n-values: ' + attributeAndKeys);
85 91
86 var propName = attributeAndKeyPair[1]; 92 var propName = attributeAndKeyPair[1];
87 var propExpr = attributeAndKeyPair[2]; 93 var propExpr = attributeAndKeyPair[2];
88 94
89 var value = dictionary.getValue(propExpr); 95 var value = dictionary.getValue(propExpr);
90 96
91 // Allow a property of the form '.foo.bar' to assign a value into 97 // Allow a property of the form '.foo.bar' to assign a value into
92 // element.foo.bar. 98 // element.foo.bar.
93 if (propName[0] == '.') { 99 if (propName[0] == '.') {
94 var path = propName.slice(1).split('.'); 100 var path = propName.slice(1).split('.');
95 var targetObject = element; 101 var targetObject = element;
96 while (targetObject && path.length > 1) { 102 while (targetObject && path.length > 1) {
97 targetObject = targetObject[path.shift()]; 103 targetObject = targetObject[path.shift()];
98 } 104 }
99 if (targetObject) { 105 if (targetObject) {
100 targetObject[path] = value; 106 targetObject[path] = value;
101 // In case we set innerHTML (ignoring others) we need to 107 // In case we set innerHTML (ignoring others) we need to recursively
102 // recursively check the content. 108 // check the content.
103 if (path == 'innerHTML') 109 if (path == 'innerHTML') {
104 process(element, dictionary); 110 for (var temp = element.firstElementChild; temp;
111 temp = temp.nextElementSibling) {
112 processWithoutCycles(temp, dictionary, visited);
113 }
114 }
105 } 115 }
106 } else { 116 } else {
107 element.setAttribute(propName, /** @type {string} */(value)); 117 element.setAttribute(propName, /** @type {string} */(value));
108 } 118 }
109 }); 119 });
110 } 120 }
111 }; 121 };
112 122
113 var attributeNames = Object.keys(handlers); 123 var attributeNames = Object.keys(handlers);
114 // Chrome for iOS must use Apple's UIWebView, which (as of April 2015) does 124 // Only use /deep/ when shadow DOM is supported. As of April 2015 iOS Chrome
115 // not have native shadow DOM support. If shadow DOM is supported (or 125 // doesn't support shadow DOM.
116 // polyfilled), search for i18n attributes using the /deep/ selector; 126 var prefix = Element.prototype.createShadowRoot ? ':root /deep/ ' : '';
117 // otherwise, do not attempt to search within the shadow DOM. 127 var selector = prefix + '[' + attributeNames.join('],' + prefix + '[') + ']';
118 var selector =
119 (window.document.body && window.document.body.createShadowRoot) ?
120 'html /deep/ [' + attributeNames.join('],[') + ']' :
121 '[' + attributeNames.join('],[') + ']';
122 128
123 /** 129 /**
124 * Processes a DOM tree with the {@code dictionary} map. 130 * Processes a DOM tree with the {@code dictionary} map.
125 * @param {Document|Element} root The root of the DOM tree to process. 131 * @param {ProcessingRoot} root The root of the DOM tree to process.
126 * @param {LoadTimeData} dictionary The dictionary to draw from. 132 * @param {LoadTimeData} dictionary The dictionary to draw from.
127 */ 133 */
128 function process(root, dictionary) { 134 function process(root, dictionary) {
135 processWithoutCycles(root, dictionary, []);
136 }
137
138 /**
139 * Internal process() method that stops cycles while processing.
140 * @param {ProcessingRoot} root
141 * @param {LoadTimeData} dictionary
142 * @param {!Array<ProcessingRoot>} visited Already visited roots.
143 */
144 function processWithoutCycles(root, dictionary, visited) {
145 if (visited.indexOf(root) >= 0) {
146 // Found a cycle. Stop it.
147 return;
148 }
149
150 // Mark the node as visited before recursing.
151 visited.push(root);
152
153 var importLinks = root.querySelectorAll('link[rel=import]');
154 for (var i = 0; i < importLinks.length; ++i) {
155 var importLink = /** @type {!HTMLLinkElement} */(importLinks[i]);
156 if (!importLink.import) {
157 // Happens when a <link rel=import> is inside a <template>.
158 // TODO(dbeam): should we log an error if we detect that here?
159 continue;
160 }
161 processWithoutCycles(importLink.import, dictionary, visited);
162 }
163
164 var templates = root.querySelectorAll('template');
165 for (var i = 0; i < templates.length; ++i) {
166 var template = /** @type {HTMLTemplateElement} */(templates[i]);
167 processWithoutCycles(template.content, dictionary, visited);
168 }
169
170 var firstElement = root instanceof Element ? root : root.querySelector('*');
171
172 if (prefix) {
173 // Prefixes skip root level elements. This is typically <html> but can
174 // differ inside of DocumentFragments (i.e. <template>s). Process them
175 // explicitly.
176 for (var temp = firstElement; temp; temp = temp.nextElementSibling) {
177 processElement(/** @type {Element} */(temp), dictionary, visited);
178 }
179 }
180
129 var elements = root.querySelectorAll(selector); 181 var elements = root.querySelectorAll(selector);
130 for (var element, i = 0; element = elements[i]; i++) { 182 for (var element, i = 0; element = elements[i]; i++) {
131 for (var j = 0; j < attributeNames.length; j++) { 183 processElement(element, dictionary, visited);
132 var name = attributeNames[j];
133 var attribute = element.getAttribute(name);
134 if (attribute != null)
135 handlers[name](element, attribute, dictionary);
136 }
137 } 184 }
138 var doc = root instanceof Document ? root : root.ownerDocument; 185
139 if (doc) 186 if (firstElement)
140 doc.documentElement.classList.add('i18n-processed'); 187 firstElement.setAttribute('i18n-processed', '');
188 }
189
190 /**
191 * Run through various [i18n-*] attributes and do activate replacements.
192 * @param {Element} element
193 * @param {LoadTimeData} dictionary
194 * @param {!Array<ProcessingRoot>} visited
195 */
196 function processElement(element, dictionary, visited) {
197 for (var i = 0; i < attributeNames.length; i++) {
198 var name = attributeNames[i];
199 var attribute = element.getAttribute(name);
200 if (attribute != null)
201 handlers[name](element, attribute, dictionary, visited);
202 }
141 } 203 }
142 204
143 return { 205 return {
144 process: process 206 process: process
145 }; 207 };
146 }()); 208 }());
OLDNEW
« no previous file with comments | « ui/webui/resources/js/compiled_resources2.gyp ('k') | ui/webui/resources/js/webui_resource_test.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698