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