Left: | ||
Right: |
LEFT | RIGHT |
---|---|
1 // Copyright (C) 2009 Google Inc. | 1 // Copyright (C) 2009 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, |
(...skipping 22 matching lines...) Expand all Loading... | |
33 import java.util.Map; | 33 import java.util.Map; |
34 import java.util.regex.Pattern; | 34 import java.util.regex.Pattern; |
35 | 35 |
36 import org.w3c.dom.Attr; | 36 import org.w3c.dom.Attr; |
37 import org.w3c.dom.Document; | 37 import org.w3c.dom.Document; |
38 import org.w3c.dom.DocumentFragment; | 38 import org.w3c.dom.DocumentFragment; |
39 import org.w3c.dom.Element; | 39 import org.w3c.dom.Element; |
40 import org.w3c.dom.Node; | 40 import org.w3c.dom.Node; |
41 import org.w3c.dom.Text; | 41 import org.w3c.dom.Text; |
42 | 42 |
43 /** | |
44 * Produces safe static HTML from a DOM tree that has been compiled by the | |
45 * {@link TemplateCompiler}. | |
46 * This class emits two parse trees: safe HTML that is safe stand-alone, and | |
47 * a number of blocks of Valija/Cajita which will add dynamic attributes to the | |
48 * static HTML interspersed with extracted scripts. | |
49 * | |
50 * <h3>Glossary</h3> | |
51 * <dl> | |
52 * <dt>Safe HTML</dt> | |
53 * <dd>HTML without event handlers or script, or names that can only be | |
54 * resolved on the browser. This will include styles and classNames, and | |
55 * when {@link PluginMeta#getIdClass()} is specified, it will also include | |
56 * style-sheets and most IDs which is enough for a fully styled static view. | |
57 * </dd> | |
58 * <dt>Inline Script</dt> | |
59 * <dd>A script block as extracted by {@link ExtractedHtmlContent} that | |
60 * needs to run in the context of the portion of the static HTML that | |
61 * precedes it. Scripts need to happen in the right context when they | |
62 * generate new content as in | |
63 * <xmp><ul><li>Item 1</li><script>emitItem2()</script><li>Item 3</ul></xmp> | |
64 * </dd> | |
65 * <dt>Skeleton</dt> | |
66 * <dd>A distilled version of the safe HTML that includes only elements, | |
67 * text, and document fragments.</dd> | |
68 * <dt>Bones</dt> | |
Jasvir
2009/05/27 21:21:16
A bone may reference a node in the skeleton which
ihab.awad
2009/05/29 05:11:05
See below.
| |
69 * <dd>References to the nodes in the skeleton in DF order with inline | |
70 * scripts.</dd> | |
Jasvir
2009/05/27 21:21:16
Clarification: A script in a bone is to be run imm
ihab.awad
2009/05/29 05:11:05
I think we've determined that the scripts are actu
| |
71 * <dt>Static Attribute</dt> | |
72 * <dd>Attributes that can be rewritten server-side and included in the | |
73 * safe HTML.</dd> | |
74 * <dt>Dynamic Attribute</dt> | |
75 * <dd>Attributes that cannot be rewritten server-side or cannot be included | |
76 * in the safe HTML, and so which need to be attached by javascript.</dd> | |
77 * <dt>Auto-generated ID</dt> | |
78 * <dd>An auto-generated ID attached statically to a node in the Safe HTML | |
79 * so that javascript can find the node later and attach dynamic attributes. | |
80 * <dt>HTML Emitter</dt> | |
81 * <dd>A class that helps attach dynamic attributes and which provides | |
82 * an {@code attach} that is used to make sure that inline scripts only see | |
83 * the relevant bit of the DOM.</dd> | |
84 * </dl> | |
85 * | |
86 * @author mikesamuel@gmail.com | |
87 */ | |
43 final class SafeHtmlMaker { | 88 final class SafeHtmlMaker { |
44 private static final Name ID = Name.html("id"); | 89 private static final Name ID = Name.html("id"); |
45 | 90 |
46 private final PluginMeta meta; | 91 private final PluginMeta meta; |
47 private final MessageContext mc; | 92 private final MessageContext mc; |
48 private final Document doc; | 93 private final Document doc; |
49 private final List<Block> js = new ArrayList<Block>(); | 94 private final List<Block> js = new ArrayList<Block>(); |
50 private final Map<Node, ParseTreeNode> scriptsPerNode; | 95 private final Map<Node, ParseTreeNode> scriptsPerNode; |
51 private Block currentBlock = null; | 96 private Block currentBlock = null; |
52 /** True iff the current block is in a {@link TranslatedCode} section. */ | 97 /** True iff the current block is in a {@link TranslatedCode} section. */ |
53 private boolean currentBlockStyle; | 98 private boolean currentBlockStyle; |
54 /** True iff js contains the definitions required by HtmlEmitter calls. */ | 99 /** True iff JS contains the definitions required by HtmlEmitter calls. */ |
55 boolean started = false; | 100 boolean started = false; |
56 /** True iff js contains a HtmlEmitter.finish() call to release resources. */ | 101 /** True iff JS contains a HtmlEmitter.finish() call to release resources. */ |
57 boolean finished = false; | 102 boolean finished = false; |
58 | 103 |
104 /** | |
105 * @param doc the owner document for the safe HTML. | |
106 */ | |
59 SafeHtmlMaker(PluginMeta meta, MessageContext mc, Document doc, | 107 SafeHtmlMaker(PluginMeta meta, MessageContext mc, Document doc, |
60 Map<Node, ParseTreeNode> scriptsPerNode) { | 108 Map<Node, ParseTreeNode> scriptsPerNode) { |
61 this.meta = meta; | 109 this.meta = meta; |
62 this.mc = mc; | 110 this.mc = mc; |
63 this.doc = doc; | 111 this.doc = doc; |
64 this.scriptsPerNode = scriptsPerNode; | 112 this.scriptsPerNode = scriptsPerNode; |
65 } | 113 } |
66 | 114 |
67 Pair<Node, List<Block>> make(List<Node> roots, List<Statement> handlers) { | 115 Pair<Node, List<Block>> make(List<Node> roots, List<Statement> handlers) { |
68 js.clear(); | 116 js.clear(); |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
113 final Block script; | 161 final Block script; |
114 ScriptBone(Block script) { | 162 ScriptBone(Block script) { |
115 this.script = script; | 163 this.script = script; |
116 } | 164 } |
117 @Override | 165 @Override |
118 public String toString() { | 166 public String toString() { |
119 return "(" + getClass().getSimpleName() + ")"; | 167 return "(" + getClass().getSimpleName() + ")"; |
120 } | 168 } |
121 } | 169 } |
122 | 170 |
171 /** | |
172 * Produces a skeletal static HTML tree containing only Elements, Text nodes | |
173 * and DocumentFragments, and a set of "bones" including the elements and | |
174 * extracted script elements in depth-first order. | |
175 * | |
176 * <xmp> <ul> <li>Hello <script>foo()</script> Bar</li> </ul> </xmp> | |
177 * results in | |
178 * {@code ((Node UL) (Node LI) (Text "Hello ") (Script foo()) (Text " Bar"))}. | |
179 */ | |
123 private Node makeSkeleton(Node n, List<DomBone> bones) { | 180 private Node makeSkeleton(Node n, List<DomBone> bones) { |
124 if (!scriptsPerNode.containsKey(n)) { return null; } | 181 if (!scriptsPerNode.containsKey(n)) { return null; } |
125 Node safe; | 182 Node safe; |
126 switch (n.getNodeType()) { | 183 switch (n.getNodeType()) { |
127 case Node.ELEMENT_NODE: | 184 case Node.ELEMENT_NODE: |
128 Element el = (Element) n; | 185 Element el = (Element) n; |
129 Block script = ExtractedHtmlContent.extractedScriptFor(el); | 186 Block script = ExtractedHtmlContent.extractedScriptFor(el); |
130 if (script != null) { | 187 if (script != null) { |
131 bones.add(new ScriptBone(script)); | 188 bones.add(new ScriptBone(script)); |
132 return null; | 189 return null; |
(...skipping 19 matching lines...) Expand all Loading... | |
152 for (Node child : Nodes.childrenOf(n)) { | 209 for (Node child : Nodes.childrenOf(n)) { |
153 Node safeChild = makeSkeleton(child, bones); | 210 Node safeChild = makeSkeleton(child, bones); |
154 if (safeChild != null) { safe.appendChild(safeChild); } | 211 if (safeChild != null) { safe.appendChild(safeChild); } |
155 } | 212 } |
156 break; | 213 break; |
157 default: return null; | 214 default: return null; |
158 } | 215 } |
159 return safe; | 216 return safe; |
160 } | 217 } |
161 | 218 |
219 /** | |
220 * Walks the {@link #makeSkeleton skeleton}, adds static attributes, and | |
221 * auto-generated IDs to the skeleton, and generates Javascript that adds | |
222 * dynamic attributes to the static HTML and that executes inline scripts. | |
223 */ | |
162 private void fleshOutSkeleton(List<DomBone> bones) { | 224 private void fleshOutSkeleton(List<DomBone> bones) { |
163 int n = bones.size(); | 225 int n = bones.size(); |
164 // The index of the first script bone not followed by any non-script bones. | 226 // The index of the first script bone not followed by any non-script bones. |
165 int firstDeferredScriptIndex = n; | 227 int firstDeferredScriptIndex = n; |
166 while (firstDeferredScriptIndex > 0 | 228 while (firstDeferredScriptIndex > 0 |
167 && bones.get(firstDeferredScriptIndex - 1) instanceof ScriptBone) { | 229 && bones.get(firstDeferredScriptIndex - 1) instanceof ScriptBone) { |
168 --firstDeferredScriptIndex; | 230 --firstDeferredScriptIndex; |
169 } | 231 } |
170 for (int i = 0; i < n; ++i) { | 232 for (int i = 0; i < n; ++i) { |
171 DomBone bone = bones.get(i); | 233 DomBone bone = bones.get(i); |
(...skipping 28 matching lines...) Expand all Loading... | |
200 | 262 |
201 /** Define bits needed by the emitter calls and the attribute fixup. */ | 263 /** Define bits needed by the emitter calls and the attribute fixup. */ |
202 private void finish() { | 264 private void finish() { |
203 if (started && !finished) { | 265 if (started && !finished) { |
204 // Release resources held by the emitter. | 266 // Release resources held by the emitter. |
205 emitStatement(quasiStmt("el___ = emitter___./*@synthetic*/finish();")); | 267 emitStatement(quasiStmt("el___ = emitter___./*@synthetic*/finish();")); |
206 finished = true; | 268 finished = true; |
207 } | 269 } |
208 } | 270 } |
209 | 271 |
272 /** Emit an inlined script. */ | |
210 private void fleshOutScriptBlock(Block script) { | 273 private void fleshOutScriptBlock(Block script) { |
211 FilePosition unk = FilePosition.UNKNOWN; | 274 FilePosition unk = FilePosition.UNKNOWN; |
212 | 275 |
213 String sourcePath = mc.abbreviate(script.getFilePosition().source()); | 276 String sourcePath = mc.abbreviate(script.getFilePosition().source()); |
214 finishBlock(); | 277 finishBlock(); |
215 emitStatement(quasiStmt( | 278 emitStatement(quasiStmt( |
216 "" | 279 "" |
217 + "try {" | 280 + "try {" |
218 + " @scriptBody;" | 281 + " @scriptBody;" |
219 + "} catch (ex___) {" | 282 + "} catch (ex___) {" |
220 + " ___./*@synthetic*/ getNewModuleHandler()" | 283 + " ___./*@synthetic*/ getNewModuleHandler()" |
221 // getNewModuleHandler is appropriate here since there can't be multiple | 284 // getNewModuleHandler is appropriate here since there can't be multiple |
222 // module handlers in play while loadModule is being called, and all | 285 // module handlers in play while loadModule is being called, and all |
223 // these exception handlers are only reachable while control is in | 286 // these exception handlers are only reachable while control is in |
224 // loadModule. | 287 // loadModule. |
225 + " ./*@synthetic*/ handleUncaughtException(" | 288 + " ./*@synthetic*/ handleUncaughtException(" |
226 + " ex___, onerror, @sourceFile, @line);" | 289 + " ex___, onerror, @sourceFile, @line);" |
227 + "}", | 290 + "}", |
228 "scriptBody", /*new UncajoledModule*/(script), | 291 "scriptBody", /*new UncajoledModule*/(script), |
229 "sourceFile", StringLiteral.valueOf(unk, sourcePath), | 292 "sourceFile", StringLiteral.valueOf(unk, sourcePath), |
230 "line", StringLiteral.valueOf( | 293 "line", StringLiteral.valueOf( |
231 unk, String.valueOf(script.getFilePosition().startLineNo())) | 294 unk, String.valueOf(script.getFilePosition().startLineNo())) |
232 ), false); | 295 ), false); |
233 } | 296 } |
234 | 297 |
298 /** | |
299 * Emit a text block. | |
300 * @param safe a text block that is safe, e.g. not in a context where it would | |
301 * cause code to execute. | |
302 * @param splitDom true if this text node is immediately followed by an inline | |
303 * script block. | |
304 */ | |
235 private void fleshOutText(Text safe, boolean splitDom) { | 305 private void fleshOutText(Text safe, boolean splitDom) { |
236 if (splitDom) { | 306 if (splitDom) { |
237 String dynId = meta.generateUniqueName(ID.getCanonicalForm()); | 307 String dynId = meta.generateUniqueName(ID.getCanonicalForm()); |
238 emitStatement(quasiStmt( | 308 emitStatement(quasiStmt( |
239 "" | 309 "" |
240 + "emitter___./*@synthetic*/unwrap(" | 310 + "emitter___./*@synthetic*/unwrap(" |
241 + " emitter___./*@synthetic*/attach(@id));", | 311 + " emitter___./*@synthetic*/attach(@id));", |
242 "id", StringLiteral.valueOf(FilePosition.UNKNOWN, dynId))); | 312 "id", StringLiteral.valueOf(FilePosition.UNKNOWN, dynId))); |
243 | 313 |
244 Element wrapper = doc.createElement("span"); | 314 Element wrapper = doc.createElement("span"); |
245 wrapper.setAttribute(ID.getCanonicalForm(), dynId); | 315 wrapper.setAttribute(ID.getCanonicalForm(), dynId); |
246 Nodes.setFilePositionFor(wrapper, Nodes.getFilePositionFor(safe)); | 316 Nodes.setFilePositionFor(wrapper, Nodes.getFilePositionFor(safe)); |
247 safe.getParentNode().replaceChild(wrapper, safe); | 317 safe.getParentNode().replaceChild(wrapper, safe); |
248 wrapper.appendChild(safe); | 318 wrapper.appendChild(safe); |
249 } | 319 } |
250 } | 320 } |
251 | 321 |
322 /** | |
323 * Attaches attributes to the safe DOM node corresponding to those on el. | |
324 * | |
325 * @param el an element in the input DOM. | |
326 * @param safe the element in the safe HTML Dom corresponding to el. | |
327 * @param splitDom true if this text node is immediately followed by an inline | |
328 * script block. | |
329 */ | |
252 private void fleshOutElement(Element el, Element safe, boolean splitDom) { | 330 private void fleshOutElement(Element el, Element safe, boolean splitDom) { |
253 FilePosition pos = Nodes.getFilePositionFor(el); | 331 FilePosition pos = Nodes.getFilePositionFor(el); |
254 | 332 |
255 // An ID we attach to a node so that we can retrieve it to add dynamic | 333 // An ID we attach to a node so that we can retrieve it to add dynamic |
256 // attributes later. | 334 // attributes later. |
257 String dynId = null; | 335 String dynId = null; |
258 if (splitDom) { | 336 if (splitDom) { |
259 dynId = makeDynamicId(null, pos); | 337 dynId = makeDynamicId(null, pos); |
338 // Emit first since this makes sure the node is in the DOM. | |
260 emitStatement(quasiStmt( | 339 emitStatement(quasiStmt( |
261 "emitter___./*@synthetic*/attach(@id);", | 340 "emitter___./*@synthetic*/attach(@id);", |
262 "id", StringLiteral.valueOf(FilePosition.UNKNOWN, dynId))); | 341 "id", StringLiteral.valueOf(FilePosition.UNKNOWN, dynId))); |
263 } | 342 } |
264 Nodes.setFilePositionFor(safe, pos); | 343 Nodes.setFilePositionFor(safe, pos); |
265 | 344 |
266 Attr id = null; | 345 Attr id = null; |
267 for (Attr a : Nodes.attributesOf(el)) { | 346 for (Attr a : Nodes.attributesOf(el)) { |
268 if (!scriptsPerNode.containsKey(a)) { continue; } | 347 if (!scriptsPerNode.containsKey(a)) { continue; } |
269 Name attrName = Name.html(a.getName()); | 348 Name attrName = Name.html(a.getName()); |
(...skipping 127 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
397 } else { | 476 } else { |
398 f.appendChild(one); | 477 f.appendChild(one); |
399 } | 478 } |
400 } | 479 } |
401 Nodes.setFilePositionFor(f, pos); | 480 Nodes.setFilePositionFor(f, pos); |
402 return f; | 481 return f; |
403 } | 482 } |
404 | 483 |
405 private void finishBlock() { currentBlock = null; } | 484 private void finishBlock() { currentBlock = null; } |
406 } | 485 } |
LEFT | RIGHT |