1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit.html;
16
17 import static org.htmlunit.html.DomElement.ATTRIBUTE_NOT_DEFINED;
18
19 import java.nio.charset.Charset;
20
21 import org.apache.commons.lang3.StringUtils;
22 import org.apache.commons.logging.Log;
23 import org.apache.commons.logging.LogFactory;
24 import org.htmlunit.FailingHttpStatusCodeException;
25 import org.htmlunit.SgmlPage;
26 import org.htmlunit.WebClient;
27 import org.htmlunit.WebWindow;
28 import org.htmlunit.html.HtmlPage.JavaScriptLoadResult;
29 import org.htmlunit.javascript.AbstractJavaScriptEngine;
30 import org.htmlunit.javascript.PostponedAction;
31 import org.htmlunit.javascript.host.Window;
32 import org.htmlunit.javascript.host.dom.Document;
33 import org.htmlunit.javascript.host.event.Event;
34 import org.htmlunit.javascript.host.event.EventTarget;
35 import org.htmlunit.javascript.host.html.HTMLDocument;
36 import org.htmlunit.protocol.javascript.JavaScriptURLConnection;
37 import org.htmlunit.util.EncodingSniffer;
38 import org.htmlunit.util.MimeType;
39 import org.htmlunit.xml.XmlPage;
40
41
42
43
44
45
46
47
48
49
50
51 public final class ScriptElementSupport {
52
53 private static final Log LOG = LogFactory.getLog(ScriptElementSupport.class);
54
55
56 private static final String SLASH_SLASH_COLON = "//:";
57
58 private ScriptElementSupport() {
59
60 }
61
62
63
64
65
66
67
68 public static void onAllChildrenAddedToPage(final ScriptElement script, final boolean postponed) {
69 final DomElement element = (DomElement) script;
70 if (element.getOwnerDocument() instanceof XmlPage) {
71 return;
72 }
73 if (LOG.isDebugEnabled()) {
74 LOG.debug("Script node added: " + element.asXml());
75 }
76
77 final SgmlPage page = element.getPage();
78 final WebClient webClient = page.getWebClient();
79 if (!webClient.isJavaScriptEngineEnabled()) {
80 LOG.debug("Script found but not executed because javascript engine is disabled");
81 return;
82 }
83
84 final String srcAttrib = script.getScriptSource();
85 final boolean hasNoSrcAttrib = ATTRIBUTE_NOT_DEFINED == srcAttrib;
86 if (!hasNoSrcAttrib && script.isDeferred()) {
87 return;
88 }
89
90 final WebWindow webWindow = page.getEnclosingWindow();
91 if (webWindow != null) {
92 final StringBuilder description = new StringBuilder()
93 .append("Execution of ")
94 .append(hasNoSrcAttrib ? "inline " : "external ")
95 .append(element.getClass().getSimpleName());
96 if (!hasNoSrcAttrib) {
97 description.append(" (").append(srcAttrib).append(')');
98 }
99
100 final PostponedAction action = new PostponedAction(page, description.toString()) {
101 @Override
102 public void execute() {
103
104 HTMLDocument jsDoc = null;
105 final Window window = webWindow.getScriptableObject();
106 if (window != null) {
107 jsDoc = (HTMLDocument) window.getDocument();
108 jsDoc.setExecutingDynamicExternalPosponed(element.getStartLineNumber() == -1
109 && !hasNoSrcAttrib);
110 }
111 try {
112 executeScriptIfNeeded(script, false, false);
113 }
114 finally {
115 if (jsDoc != null) {
116 jsDoc.setExecutingDynamicExternalPosponed(false);
117 }
118 }
119 }
120 };
121
122 final AbstractJavaScriptEngine<?> engine = webClient.getJavaScriptEngine();
123 if (element.hasAttribute("async") && !hasNoSrcAttrib) {
124 engine.addPostponedAction(action);
125 }
126 else if (postponed && !hasNoSrcAttrib) {
127 engine.addPostponedAction(action);
128 }
129 else {
130 try {
131 action.execute();
132 engine.processPostponedActions();
133 }
134 catch (final RuntimeException e) {
135 throw e;
136 }
137 catch (final Exception e) {
138 throw new RuntimeException(e);
139 }
140 }
141 }
142 }
143
144
145
146
147
148
149
150
151
152
153 public static void executeScriptIfNeeded(final ScriptElement script, final boolean ignoreAttachedToPage,
154 final boolean ignorePageIsAncestor) {
155 if (!isExecutionNeeded(script, ignoreAttachedToPage, ignorePageIsAncestor)) {
156 return;
157 }
158
159 final String src = script.getScriptSource();
160 final DomElement element = (DomElement) script;
161 if (SLASH_SLASH_COLON.equals(src)) {
162 executeEvent(element, Event.TYPE_ERROR);
163 return;
164 }
165
166 final HtmlPage page = (HtmlPage) element.getPage();
167 if (src != ATTRIBUTE_NOT_DEFINED) {
168 if (!src.startsWith(JavaScriptURLConnection.JAVASCRIPT_PREFIX)) {
169
170 if (LOG.isDebugEnabled()) {
171 LOG.debug("Loading external JavaScript: " + src);
172 }
173 try {
174 script.setExecuted(true);
175 Charset charset = EncodingSniffer.toCharset(script.getScriptCharset());
176 if (charset == null) {
177 charset = page.getCharset();
178 }
179
180 final JavaScriptLoadResult result;
181 final Window win = page.getEnclosingWindow().getScriptableObject();
182 final Document doc = win.getDocument();
183 try {
184 doc.setCurrentScript(element.getScriptableObject());
185 result = page.loadExternalJavaScriptFile(src, charset);
186 }
187 finally {
188 doc.setCurrentScript(null);
189 }
190
191 if (result == JavaScriptLoadResult.SUCCESS) {
192 executeEvent(element, Event.TYPE_LOAD);
193 }
194 else if (result == JavaScriptLoadResult.DOWNLOAD_ERROR) {
195 executeEvent(element, Event.TYPE_ERROR);
196 }
197 else if (result == JavaScriptLoadResult.NO_CONTENT) {
198 executeEvent(element, Event.TYPE_LOAD);
199 }
200 }
201 catch (final FailingHttpStatusCodeException e) {
202 executeEvent(element, Event.TYPE_ERROR);
203 throw e;
204 }
205 }
206 }
207 else if (element.getFirstChild() != null) {
208
209 final Window win = page.getEnclosingWindow().getScriptableObject();
210 final Document doc = win.getDocument();
211 try {
212 doc.setCurrentScript(element.getScriptableObject());
213 executeInlineScriptIfNeeded(script);
214 }
215 finally {
216 doc.setCurrentScript(null);
217 }
218 }
219 }
220
221
222
223
224
225
226
227
228
229 private static boolean isExecutionNeeded(final ScriptElement script, final boolean ignoreAttachedToPage,
230 final boolean ignorePageIsAncestor) {
231 if (script.isExecuted() || script.wasCreatedByDomParser()) {
232 return false;
233 }
234
235 final DomElement element = (DomElement) script;
236 if (!ignoreAttachedToPage && !element.isAttachedToPage()) {
237 return false;
238 }
239
240
241 final SgmlPage page = element.getPage();
242 if (!page.getWebClient().isJavaScriptEnabled()) {
243 return false;
244 }
245
246
247 final HtmlPage htmlPage = element.getHtmlPageOrNull();
248 if (htmlPage != null && htmlPage.isParsingHtmlSnippet()) {
249 return false;
250 }
251
252
253 for (DomNode o = element; o != null; o = o.getParentNode()) {
254 if (o instanceof HtmlInlineFrame || o instanceof HtmlNoFrames) {
255 return false;
256 }
257 }
258
259
260
261 if (page.getEnclosingWindow() != null && page.getEnclosingWindow().getEnclosedPage() != page) {
262 return false;
263 }
264
265
266 final String t = element.getAttributeDirect("type");
267 final String l = element.getAttributeDirect("language");
268 if (!isJavaScript(t, l)) {
269
270
271
272 if (LOG.isDebugEnabled()) {
273 LOG.debug("Script is not JavaScript (type: '" + t + "', language: '" + l + "'). Skipping execution.");
274 }
275 return false;
276 }
277
278
279
280 return ignorePageIsAncestor || element.getPage().isAncestorOf(element);
281 }
282
283
284
285
286
287
288
289
290
291
292 public static boolean isJavaScript(String typeAttribute, final String languageAttribute) {
293 typeAttribute = typeAttribute.trim();
294
295 if (StringUtils.isNotEmpty(typeAttribute)) {
296 return MimeType.isJavascriptMimeType(typeAttribute);
297 }
298
299 if (StringUtils.isNotEmpty(languageAttribute)) {
300 return StringUtils.startsWithIgnoreCase(languageAttribute, "javascript");
301 }
302 return true;
303 }
304
305 private static void executeEvent(final DomElement element, final String type) {
306 final EventTarget eventTarget = element.getScriptableObject();
307 final Event event = new Event(element, type);
308
309 event.setParentScope(eventTarget);
310 event.setPrototype(eventTarget.getPrototype(event.getClass()));
311
312 eventTarget.executeEventLocally(event);
313 }
314
315
316
317
318 private static void executeInlineScriptIfNeeded(final ScriptElement script) {
319 if (!isExecutionNeeded(script, false, false)) {
320 return;
321 }
322
323 final String src = script.getScriptSource();
324 if (src != ATTRIBUTE_NOT_DEFINED) {
325 return;
326 }
327
328 final DomElement element = (DomElement) script;
329 final String forr = element.getAttributeDirect("for");
330 String event = element.getAttributeDirect("event");
331
332 if (event.endsWith("()")) {
333 event = event.substring(0, event.length() - 2);
334 }
335
336 final String scriptCode = getScriptCode(element);
337 if (forr == ATTRIBUTE_NOT_DEFINED || "onload".equals(event)) {
338 final String url = element.getPage().getUrl().toExternalForm();
339 final int line1 = element.getStartLineNumber();
340 final int line2 = element.getEndLineNumber();
341 final int col1 = element.getStartColumnNumber();
342 final int col2 = element.getEndColumnNumber();
343 final String desc = "script in " + url + " from (" + line1 + ", " + col1
344 + ") to (" + line2 + ", " + col2 + ")";
345
346 script.setExecuted(true);
347 ((HtmlPage) element.getPage()).executeJavaScript(scriptCode, desc, line1);
348 }
349 }
350
351
352
353
354 private static String getScriptCode(final DomElement element) {
355 final Iterable<DomNode> textNodes = element.getChildren();
356 final StringBuilder scriptCode = new StringBuilder();
357 for (final DomNode node : textNodes) {
358 if (node instanceof DomText) {
359 final DomText domText = (DomText) node;
360 scriptCode.append(domText.getData());
361 }
362 }
363 return scriptCode.toString();
364 }
365
366 }