Left: | ||
Right: |
OLD | NEW |
---|---|
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 * TODO: automated browser tests. detach/reattach hits some browser quirks. | |
25 * http://code.google.com/p/google-caja/issues/detail?id=1060 | |
26 * | |
24 * @author mikesamuel@gmail.com | 27 * @author mikesamuel@gmail.com |
25 */ | 28 */ |
26 function HtmlEmitter(base, opt_tameDocument) { | 29 function HtmlEmitter(base, opt_tameDocument) { |
27 if (!base) { throw new Error(); } | 30 if (!base) { throw new Error(); } |
28 | 31 |
29 /** | 32 /** |
30 * Contiguous pairs of ex-descendants of base, and their ex-parent. | 33 * Contiguous pairs of ex-descendants of base, and their ex-parent. |
31 * The detached elements (even indices) are ordered depth-first. | 34 * The detached elements (even indices) are ordered depth-first. |
32 */ | 35 */ |
33 var detached = null; | 36 var detached = null; |
(...skipping 139 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
173 // nodes with the nodes detached from limit. | 176 // nodes with the nodes detached from limit. |
174 var newDetached = [0, 0]; | 177 var newDetached = [0, 0]; |
175 // Since limit has no parent, detachOnto will bottom out at its sibling. | 178 // Since limit has no parent, detachOnto will bottom out at its sibling. |
176 detachOnto(limit, newDetached); | 179 detachOnto(limit, newDetached); |
177 // Find the node containing limit that appears on detached. | 180 // Find the node containing limit that appears on detached. |
178 for (var limitAnc = limit, parent; (parent = limitAnc.parentNode);) { | 181 for (var limitAnc = limit, parent; (parent = limitAnc.parentNode);) { |
179 limitAnc = parent; | 182 limitAnc = parent; |
180 } | 183 } |
181 // Reattach up to and including limit ancestor. | 184 // Reattach up to and including limit ancestor. |
182 var nConsumed = 0; | 185 var nConsumed = 0; |
183 while (true) { | 186 while (nConsumed < detached.length) { |
184 var toReattach = detached[nConsumed]; | 187 // in IE, some types of nodes can't be standalone, and detaching |
185 (detached[nConsumed + 1] /* the parent */).appendChild(toReattach); | 188 // one will create new parentNodes for them. so at this point, |
189 // limitAnc might be an ancestor of the node on detached. | |
190 var reattach = detached[nConsumed]; | |
191 var reattAnc = reattach; | |
192 for (; reattAnc.parentNode; reattAnc = reattAnc.parentNode) {} | |
193 (detached[nConsumed + 1] /* the parent */).appendChild(reattach); | |
186 nConsumed += 2; | 194 nConsumed += 2; |
187 if (toReattach === limitAnc) { break; } | 195 if (reattAnc === limitAnc) { break; } |
188 } | 196 } |
189 // Replace the reattached bits with the ones detached from limit. | 197 // Replace the reattached bits with the ones detached from limit. |
190 newDetached[1] = nConsumed; // splice's second arg is the number removed | 198 newDetached[1] = nConsumed; // splice's second arg is the number removed |
191 arraySplice.apply(detached, newDetached); | 199 arraySplice.apply(detached, newDetached); |
192 } else { | 200 } else { |
193 // The first time attach is called, the limit is actually part of the DOM. | 201 // The first time attach is called, the limit is actually part of the DOM. |
194 // There's no point removing anything when all scripts are deferred. | 202 // There's no point removing anything when all scripts are deferred. |
195 detached = []; | 203 detached = []; |
196 detachOnto(limit, detached); | 204 detachOnto(limit, detached); |
197 } | 205 } |
198 return limit; | 206 return limit; |
199 } | 207 } |
200 /** | 208 /** |
201 * Removes a wrapper from a textNode | 209 * Removes a TextNode wrapper. |
202 * When a text node immediately precedes a script block, the limit will be | 210 * When the last node before a script block is a TextNode, the |
203 * a text node. Text nodes can't be addressed by ID, so the TemplateCompiler | 211 * TemplateCompiler labels that TextNode by wrapping it in a span, and |
204 * wraps them in a <span> which must be removed to be semantics preserving. | 212 * adds a call to unwrap() to tell us when to remove the span. |
205 */ | 213 */ |
206 function unwrap(wrapper) { | 214 function unwrap(wrapper) { |
207 // Text nodes must have exactly one child, so it must be first on the | 215 // At this point, the wrapper's TextNode child has been removed and |
208 // detached list, since children are earlier than siblings by DFS order. | 216 // placed at the front of the detached nodes list. There should never |
209 var text = detached[0]; | 217 // be more than one, but sometimes there's zero, because some browsers |
210 // If this is not true, the TemplateCompiler must be generating unwrap calls | 218 // (such as IE) do not create a TextNode when it's only whitespace. |
211 // out of order. | 219 if (detached[1] === wrapper) { |
212 // An untrusted script block should not be able to nuke the wrapper before | 220 wrapper.parentNode.replaceChild(detached[0], wrapper); |
213 // it's removed so there should be a parentNode. | 221 detached.splice(0, 2); |
214 wrapper.parentNode.replaceChild(text, wrapper); | 222 } |
215 detached.splice(0, 2); | |
216 } | 223 } |
217 /** | 224 /** |
218 * Reattach any remaining detached bits, free resources, and fire a document | 225 * Reattach any remaining detached bits, free resources, and fire a document |
219 * loaded event. | 226 * loaded event. |
220 */ | 227 */ |
221 function finish() { | 228 function finish() { |
222 if (detached) { | 229 if (detached) { |
223 for (var i = 0, n = detached.length; i < n; i += 2) { | 230 for (var i = 0, n = detached.length; i < n; i += 2) { |
224 detached[i + 1].appendChild(detached[i]); | 231 detached[i + 1].appendChild(detached[i]); |
225 } | 232 } |
226 } | 233 } |
227 // Release references so nodes can be garbage collected. | 234 // Release references so nodes can be garbage collected. |
228 idMap = detached = base = null; | 235 idMap = detached = base = null; |
229 return this; | 236 return this; |
230 } | 237 } |
231 | 238 |
232 function signalLoaded() { | 239 function signalLoaded() { |
233 // Signals the close of the document and fires any window.onload event | 240 // Signals the close of the document and fires any window.onload event |
234 // handlers. | 241 // handlers. |
235 var doc = opt_tameDocument; | 242 var doc = opt_tameDocument; |
236 if (doc) { doc.signalLoaded___(); } | 243 if (doc) { doc.signalLoaded___(); } |
237 return this; | 244 return this; |
238 } | 245 } |
239 | 246 |
247 /** Set an attribute on an element */ | |
248 function setAttr(el, attrName, value) { | |
249 bridal.setAttribute(el, attrName, value); | |
250 | |
251 // onevent attributes are extra special | |
252 var tagAttr = el.tagName.toLowerCase() + ':' + attrName; | |
MikeSamuel
2009/07/20 19:04:51
Can we use the lower casing function from domita h
| |
253 if (html4.ATTRIBS[tagAttr] === html4.atype.SCRIPT | |
254 || html4.ATTRIBS['*:' + attrName] === html4.atype.SCRIPT) { | |
MikeSamuel
2009/07/20 19:04:51
The second clause only applies if html4.ATTRIBS[ta
| |
255 // IE<8 requires us to set onevent attributes this way. | |
256 el[attrName] = new Function('event', value); | |
257 } | |
258 } | |
259 | |
240 this.byId = byId; | 260 this.byId = byId; |
241 this.attach = attach; | 261 this.attach = attach; |
242 this.unwrap = unwrap; | 262 this.unwrap = unwrap; |
243 this.finish = finish; | 263 this.finish = finish; |
244 this.signalLoaded = signalLoaded; | 264 this.signalLoaded = signalLoaded; |
245 this.setAttr = bridal.setAttribute; | 265 this.setAttr = setAttr; |
246 } | 266 } |
OLD | NEW |