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 if (engine != null) {
135 engine.processPostponedActions();
136 }
137 }
138 catch (final RuntimeException e) {
139 throw e;
140 }
141 catch (final Exception e) {
142 throw new RuntimeException(e);
143 }
144 }
145 }
146 }
147
148
149
150
151
152
153
154
155
156
157 public static void executeScriptIfNeeded(final ScriptElement script, final boolean ignoreAttachedToPage,
158 final boolean ignorePageIsAncestor) {
159 if (!isExecutionNeeded(script, ignoreAttachedToPage, ignorePageIsAncestor)) {
160 return;
161 }
162
163 final String src = script.getScriptSource();
164 final DomElement element = (DomElement) script;
165 if (SLASH_SLASH_COLON.equals(src)) {
166 executeEvent(element, Event.TYPE_ERROR);
167 return;
168 }
169
170 final HtmlPage page = (HtmlPage) element.getPage();
171 if (src != ATTRIBUTE_NOT_DEFINED) {
172 if (!src.startsWith(JavaScriptURLConnection.JAVASCRIPT_PREFIX)) {
173
174 if (LOG.isDebugEnabled()) {
175 LOG.debug("Loading external JavaScript: " + src);
176 }
177 try {
178 script.setExecuted(true);
179 Charset charset = EncodingSniffer.toCharset(script.getScriptCharset());
180 if (charset == null) {
181 charset = page.getCharset();
182 }
183
184 final JavaScriptLoadResult result;
185 final Window win = page.getEnclosingWindow().getScriptableObject();
186 final Document doc = win.getDocument();
187 try {
188 doc.setCurrentScript(element.getScriptableObject());
189 result = page.loadExternalJavaScriptFile(src, charset, script.isCrossorigin());
190 }
191 finally {
192 doc.setCurrentScript(null);
193 }
194
195 if (result == JavaScriptLoadResult.SUCCESS) {
196 executeEvent(element, Event.TYPE_LOAD);
197 }
198 else if (result == JavaScriptLoadResult.DOWNLOAD_ERROR) {
199 executeEvent(element, Event.TYPE_ERROR);
200 }
201 else if (result == JavaScriptLoadResult.NO_CONTENT) {
202 executeEvent(element, Event.TYPE_LOAD);
203 }
204 }
205 catch (final FailingHttpStatusCodeException e) {
206 executeEvent(element, Event.TYPE_ERROR);
207 throw e;
208 }
209 }
210 }
211 else if (element.getFirstChild() != null) {
212
213 final Window win = page.getEnclosingWindow().getScriptableObject();
214 final Document doc = win.getDocument();
215 try {
216 doc.setCurrentScript(element.getScriptableObject());
217 executeInlineScript(script);
218 }
219 finally {
220 doc.setCurrentScript(null);
221 }
222 }
223 }
224
225
226
227
228
229
230
231
232
233 private static boolean isExecutionNeeded(final ScriptElement script, final boolean ignoreAttachedToPage,
234 final boolean ignorePageIsAncestor) {
235 if (script.isExecuted() || script.wasCreatedByDomParser()) {
236 return false;
237 }
238
239 final DomElement element = (DomElement) script;
240 if (!ignoreAttachedToPage && !element.isAttachedToPage()) {
241 return false;
242 }
243
244
245 final SgmlPage page = element.getPage();
246 if (!page.getWebClient().isJavaScriptEnabled()) {
247 return false;
248 }
249
250
251 final HtmlPage htmlPage = element.getHtmlPageOrNull();
252 if (htmlPage != null && htmlPage.isParsingHtmlSnippet()) {
253 return false;
254 }
255
256
257 for (DomNode o = element; o != null; o = o.getParentNode()) {
258 if (o instanceof HtmlInlineFrame || o instanceof HtmlNoFrames) {
259 return false;
260 }
261 }
262
263
264
265 if (page.getEnclosingWindow() != null && page.getEnclosingWindow().getEnclosedPage() != page) {
266 return false;
267 }
268
269
270 final String t = element.getAttributeDirect("type");
271 final String l = element.getAttributeDirect("language");
272 if (!isJavaScript(t, l)) {
273
274
275
276 if (LOG.isDebugEnabled()) {
277 LOG.debug("Script is not JavaScript (type: '" + t + "', language: '" + l + "'). Skipping execution.");
278 }
279 return false;
280 }
281
282
283
284 return ignorePageIsAncestor || element.getPage().isAncestorOf(element);
285 }
286
287
288
289
290
291
292
293
294
295
296 public static boolean isJavaScript(String typeAttribute, final String languageAttribute) {
297 typeAttribute = typeAttribute.trim();
298
299 if (!StringUtils.isEmptyOrNull(typeAttribute)) {
300 return MimeType.isJavascriptMimeType(typeAttribute);
301 }
302
303 if (!StringUtils.isEmptyOrNull(languageAttribute)) {
304 return StringUtils.startsWithIgnoreCase(languageAttribute, "javascript");
305 }
306 return true;
307 }
308
309 private static void executeEvent(final DomElement element, final String type) {
310 final EventTarget eventTarget = element.getScriptableObject();
311 final Event event = new Event(element, type);
312
313 event.setParentScope(eventTarget.getParentScope());
314 event.setPrototype(eventTarget.getPrototype(event.getClass()));
315
316 eventTarget.executeEventLocally(event);
317 }
318
319
320
321
322 private static void executeInlineScript(final ScriptElement script) {
323 final DomElement element = (DomElement) script;
324 final String forr = element.getAttributeDirect("for");
325 String event = element.getAttributeDirect("event");
326
327 if (event.endsWith("()")) {
328 event = event.substring(0, event.length() - 2);
329 }
330
331 final String scriptCode = getScriptCode(element);
332 if (forr == ATTRIBUTE_NOT_DEFINED || "onload".equals(event)) {
333 final String url = element.getPage().getUrl().toExternalForm();
334 final int line1 = element.getStartLineNumber();
335 final int line2 = element.getEndLineNumber();
336 final int col1 = element.getStartColumnNumber();
337 final int col2 = element.getEndColumnNumber();
338 final String desc = "script in " + url + " from (" + line1 + ", " + col1
339 + ") to (" + line2 + ", " + col2 + ")";
340
341 script.setExecuted(true);
342 ((HtmlPage) element.getPage()).executeJavaScript(scriptCode, desc, line1);
343 }
344 }
345
346
347
348
349 private static String getScriptCode(final DomElement element) {
350 final StringBuilder scriptCode = new StringBuilder();
351 for (final DomNode node : element.getChildren()) {
352 if (node instanceof DomText domText) {
353 scriptCode.append(domText.getData());
354 }
355 }
356 return scriptCode.toString();
357 }
358 }