LEFT | RIGHT |
1 // Copyright (C) 2008 Google Inc. | 1 // Copyright (C) 2008 Google Inc. |
2 // | 2 // |
3 // Licensed under the Apache License, Version 2.0 (the "License"); | 3 // Licensed under the Apache License, Version 2.0 (the "License"); |
4 // you may not use this file except in compliance with the License. | 4 // you may not use this file except in compliance with the License. |
5 // You may obtain a copy of the License at | 5 // You may obtain a copy of the License at |
6 // | 6 // |
7 // http://www.apache.org/licenses/LICENSE-2.0 | 7 // http://www.apache.org/licenses/LICENSE-2.0 |
8 // | 8 // |
9 // Unless required by applicable law or agreed to in writing, software | 9 // Unless required by applicable law or agreed to in writing, software |
10 // distributed under the License is distributed on an "AS IS" BASIS, | 10 // distributed under the License is distributed on an "AS IS" BASIS, |
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 // See the License for the specific language governing permissions and | 12 // See the License for the specific language governing permissions and |
13 // limitations under the License. | 13 // limitations under the License. |
14 | 14 |
15 /** | 15 /** |
16 * @fileoverview | 16 * @fileoverview |
17 * JavaScript support for TemplateCompiler.java. | 17 * JavaScript support for TemplateCompiler.java. |
18 * <p> | 18 * <p> |
19 * This handles the problem of making sure that only the bits of a Gadget's | 19 * This handles the problem of making sure that only the bits of a Gadget's |
20 * static HTML which should be visible to a script are visible, and provides | 20 * static HTML which should be visible to a script are visible, and provides |
21 * mechanisms to reliably find elements using dynamically generated unique IDs | 21 * mechanisms to reliably find elements using dynamically generated unique IDs |
22 * in the face of DOM modifications by untrusted scripts. | 22 * in the face of DOM modifications by untrusted scripts. |
23 * | 23 * |
24 * @author mikesamuel@gmail.com | 24 * @author mikesamuel@gmail.com |
25 */ | 25 */ |
26 function HtmlEmitter(base) { | 26 function HtmlEmitter(base, opt_tameDocument) { |
27 if (!base) { throw new Error(); } | 27 if (!base) { throw new Error(); } |
28 | 28 |
29 /** | 29 /** |
30 * Contiguous pairs of ex-descendants of base, and their ex-parent. | 30 * Contiguous pairs of ex-descendants of base, and their ex-parent. |
31 * The detached elements (even indices) are ordered depth-first. | 31 * The detached elements (even indices) are ordered depth-first. |
32 */ | 32 */ |
33 var detached = null; | 33 var detached = null; |
34 /** Makes sure IDs are accessible within removed detached nodes. */ | 34 /** Makes sure IDs are accessible within removed detached nodes. */ |
35 var idMap = null; | 35 var idMap = null; |
36 | 36 |
37 var arraySplice = Array.prototype.splice; | 37 var arraySplice = Array.prototype.splice; |
38 | 38 |
39 function buildIdMap() { | 39 function buildIdMap() { |
40 idMap = {}; | 40 idMap = {}; |
41 var descs = base.getElementsByTagName('*'); | 41 var descs = base.getElementsByTagName('*'); |
42 for (var i = 0, desc; (desc = descs[i]); ++i) { | 42 for (var i = 0, desc; (desc = descs[i]); ++i) { |
43 if (desc.id) { idMap[desc.id] = desc; } | 43 if (desc.id) { idMap[desc.id] = desc; } |
44 } | 44 } |
45 } | 45 } |
| 46 /** |
| 47 * Returns the element with the given ID under the base node. |
| 48 * @param id an auto-generated ID since we cannot rely on user supplied IDs |
| 49 * to be unique. |
| 50 * @return {Element|null} null if no such element exists. |
| 51 */ |
46 function byId(id) { | 52 function byId(id) { |
47 if (!idMap) { buildIdMap(); } | 53 if (!idMap) { buildIdMap(); } |
48 var node = idMap[id]; | 54 var node = idMap[id]; |
49 if (node) { return node; } | 55 if (node) { return node; } |
50 for (; (node = base.ownerDocument.getElementById(id));) { | 56 for (; (node = base.ownerDocument.getElementById(id));) { |
51 if (base.contains | 57 if (base.contains |
52 ? base.contains(node) | 58 ? base.contains(node) |
53 : (base.compareDocumentPosition(node) & 0x10)) { | 59 : (base.compareDocumentPosition(node) & 0x10)) { |
54 idMap[id] = node; | 60 idMap[id] = node; |
55 return node; | 61 return node; |
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
119 // Then the reattach operation advances the number. In the example above, we | 125 // Then the reattach operation advances the number. In the example above, we |
120 // advance the index from 0 to 3, and then from 3 to 6. | 126 // advance the index from 0 to 3, and then from 3 to 6. |
121 // The finish operation simply reattaches the rest, advancing the counter from | 127 // The finish operation simply reattaches the rest, advancing the counter from |
122 // 6 to the end. | 128 // 6 to the end. |
123 | 129 |
124 // The minimal detached list from the node with DFS index I is the ordered | 130 // The minimal detached list from the node with DFS index I is the ordered |
125 // list such that a (node, parent) pair (N, P) is on the list if | 131 // list such that a (node, parent) pair (N, P) is on the list if |
126 // dfs-index(N) > I and there is no pair (P, GP) on the list. | 132 // dfs-index(N) > I and there is no pair (P, GP) on the list. |
127 | 133 |
128 // To calculate the minimal detached list given a node representing a point in | 134 // To calculate the minimal detached list given a node representing a point in |
129 // that ordering, we rely on the following principle: | 135 // that ordering, we rely on the following observations: |
130 // The minimal detached list after a node, is the concatenation of | 136 // The minimal detached list after a node, is the concatenation of |
131 // (1) that node's children in order | 137 // (1) that node's children in order |
132 // (2) the next sibling of that node and its later siblings, | 138 // (2) the next sibling of that node and its later siblings, |
133 // the next sibling of that node's parent and its later siblings, | 139 // the next sibling of that node's parent and its later siblings, |
134 // the next sibling of that node's grandparent and its later siblings, | 140 // the next sibling of that node's grandparent and its later siblings, |
135 // etc., until base is reached. | 141 // etc., until base is reached. |
136 | 142 |
137 function detachOnto(limit, out) { | 143 function detachOnto(limit, out) { |
138 // Set detached to be the minimal set of nodes that have to be removed | 144 // Set detached to be the minimal set of nodes that have to be removed |
139 // to make sure that limit is the last attached node in DFS order as | 145 // to make sure that limit is the last attached node in DFS order as |
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
202 // detached list, since children are earlier than siblings by DFS order. | 208 // detached list, since children are earlier than siblings by DFS order. |
203 var text = detached[0]; | 209 var text = detached[0]; |
204 // If this is not true, the TemplateCompiler must be generating unwrap calls | 210 // If this is not true, the TemplateCompiler must be generating unwrap calls |
205 // out of order. | 211 // out of order. |
206 // An untrusted script block should not be able to nuke the wrapper before | 212 // An untrusted script block should not be able to nuke the wrapper before |
207 // it's removed so there should be a parentNode. | 213 // it's removed so there should be a parentNode. |
208 wrapper.parentNode.replaceChild(text, wrapper); | 214 wrapper.parentNode.replaceChild(text, wrapper); |
209 detached.splice(0, 2); | 215 detached.splice(0, 2); |
210 } | 216 } |
211 /** | 217 /** |
212 * Reattach any remaining detached bit and free resources. | 218 * Reattach any remaining detached bits, free resources, and fire a document |
| 219 * loaded event. |
213 */ | 220 */ |
214 function finish() { | 221 function finish() { |
215 if (detached) { | 222 if (detached) { |
216 for (var i = 0, n = detached.length; i < n; i += 2) { | 223 for (var i = 0, n = detached.length; i < n; i += 2) { |
217 detached[i + 1].appendChild(detached[i]); | 224 detached[i + 1].appendChild(detached[i]); |
218 } | 225 } |
219 } | 226 } |
220 // Release references so nodes can be garbage collected. | 227 // Release references so nodes can be garbage collected. |
221 idMap = detached = base = null; | 228 idMap = detached = base = null; |
| 229 |
| 230 // Signals the close of the document and fires any window.onload event |
| 231 // handlers. |
| 232 var doc = opt_tameDocument; |
| 233 if (doc) { doc.signalLoaded___(); } |
| 234 return this; |
222 } | 235 } |
223 | 236 |
224 this.byId = byId; | 237 this.byId = byId; |
225 this.attach = attach; | 238 this.attach = attach; |
226 this.unwrap = unwrap; | 239 this.unwrap = unwrap; |
227 this.finish = finish; | 240 this.finish = finish; |
228 this.setAttr = bridal.setAttribute; | 241 this.setAttr = bridal.setAttribute; |
229 } | 242 } |
LEFT | RIGHT |