1 /*
2 * Copyright (c) 2002-2025 Gargoyle Software Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 * https://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 package org.htmlunit.html;
16
17 import java.io.PrintWriter;
18 import java.io.StringWriter;
19 import java.util.Map;
20
21 import org.htmlunit.SgmlPage;
22 import org.htmlunit.javascript.AbstractJavaScriptEngine;
23 import org.htmlunit.javascript.PostponedAction;
24 import org.htmlunit.javascript.host.dom.Document;
25
26 /**
27 * Wrapper for the HTML element "script".<br>
28 * When a script tag references an external script (with attribute src) it gets executed when the node
29 * is added to the DOM tree. When the script code is nested, it gets executed when the text node
30 * containing the script is added to the HtmlScript.<br>
31 * The ScriptFilter feature of NekoHtml can't be used because it doesn't allow immediate access to the DOM
32 * (i.e. <code>document.write("<span id='mySpan'/>"); document.getElementById("mySpan").tagName;</code>
33 * can't work with a filter).
34 *
35 * @author Mike Bowler
36 * @author Christian Sell
37 * @author Marc Guillemot
38 * @author David K. Taylor
39 * @author Ahmed Ashour
40 * @author Daniel Gredler
41 * @author Dmitri Zoubkov
42 * @author Sudhan Moghe
43 * @author Ronald Brill
44 * @author Daniel Wagner-Hall
45 * @author Frank Danek
46 * @see <a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-one-html.html#ID-81598695">DOM Level 1</a>
47 * @see <a href="http://www.w3.org/TR/2003/REC-DOM-Level-2-HTML-20030109/html.html#ID-81598695">DOM Level 2</a>
48 */
49 public class HtmlScript extends HtmlElement implements ScriptElement {
50
51 /** The HTML tag represented by this element. */
52 public static final String TAG_NAME = "script";
53
54 private boolean executed_;
55 private boolean createdByDomParser_;
56
57 /**
58 * Creates an instance of HtmlScript
59 *
60 * @param qualifiedName the qualified name of the element type to instantiate
61 * @param page the HtmlPage that contains this element
62 * @param attributes the initial attributes
63 */
64 HtmlScript(final String qualifiedName, final SgmlPage page,
65 final Map<String, DomAttr> attributes) {
66 super(qualifiedName, page, attributes);
67 }
68
69 /**
70 * Returns the value of the attribute {@code charset}. Refer to the
71 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
72 * documentation for details on the use of this attribute.
73 *
74 * @return the value of the attribute {@code charset}
75 * or an empty string if that attribute isn't defined.
76 */
77 public final String getCharsetAttribute() {
78 return getAttributeDirect("charset");
79 }
80
81 /**
82 * {@inheritDoc}
83 */
84 @Override
85 public final String getScriptCharset() {
86 return getAttributeDirect("charset");
87 }
88
89 /**
90 * Returns the value of the attribute {@code type}. Refer to the
91 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
92 * documentation for details on the use of this attribute.
93 *
94 * @return the value of the attribute {@code type}
95 * or an empty string if that attribute isn't defined.
96 */
97 public final String getTypeAttribute() {
98 return getAttributeDirect(TYPE_ATTRIBUTE);
99 }
100
101 /**
102 * Returns the value of the attribute {@code language}. Refer to the
103 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
104 * documentation for details on the use of this attribute.
105 *
106 * @return the value of the attribute {@code language}
107 * or an empty string if that attribute isn't defined.
108 */
109 public final String getLanguageAttribute() {
110 return getAttributeDirect("language");
111 }
112
113 /**
114 * Returns the value of the attribute {@code src}. Refer to the
115 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
116 * documentation for details on the use of this attribute.
117 *
118 * @return the value of the attribute {@code src}
119 * or an empty string if that attribute isn't defined.
120 */
121 public final String getSrcAttribute() {
122 return getSrcAttributeNormalized();
123 }
124
125 /**
126 * {@inheritDoc}
127 */
128 @Override
129 public final String getScriptSource() {
130 return getSrcAttributeNormalized();
131 }
132
133 /**
134 * Returns the value of the attribute {@code event}.
135 * @return the value of the attribute {@code event}
136 */
137 public final String getEventAttribute() {
138 return getAttributeDirect("event");
139 }
140
141 /**
142 * Returns the value of the attribute {@code for}.
143 * @return the value of the attribute {@code for}
144 */
145 public final String getHtmlForAttribute() {
146 return getAttributeDirect("for");
147 }
148
149 /**
150 * Returns the value of the attribute {@code defer}. Refer to the
151 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
152 * documentation for details on the use of this attribute.
153 *
154 * @return the value of the attribute {@code defer}
155 * or an empty string if that attribute isn't defined.
156 */
157 public final String getDeferAttribute() {
158 return getAttributeDirect("defer");
159 }
160
161 /**
162 * {@inheritDoc}
163 */
164 @Override
165 public boolean isDeferred() {
166 return getDeferAttribute() != ATTRIBUTE_NOT_DEFINED;
167 }
168
169 /**
170 * {@inheritDoc}
171 */
172 @Override
173 public boolean mayBeDisplayed() {
174 return false;
175 }
176
177 /**
178 * If setting the <code>src</code> attribute, this method executes the new JavaScript if necessary
179 * (behavior varies by browser version). {@inheritDoc}
180 */
181 @Override
182 protected void setAttributeNS(final String namespaceURI, final String qualifiedName, final String attributeValue,
183 final boolean notifyAttributeChangeListeners, final boolean notifyMutationObservers) {
184 final String qualifiedNameLC = org.htmlunit.util.StringUtils.toRootLowerCase(qualifiedName);
185 // special additional processing for the 'src'
186 if (namespaceURI != null || !SRC_ATTRIBUTE.equals(qualifiedNameLC)) {
187 super.setAttributeNS(namespaceURI, qualifiedNameLC, attributeValue, notifyAttributeChangeListeners,
188 notifyMutationObservers);
189 return;
190 }
191
192 // namespaceURI is always null here - we can call getAttribute directly
193 final String oldValue = getAttribute(qualifiedNameLC);
194 super.setAttributeNS(null, qualifiedNameLC, attributeValue, notifyAttributeChangeListeners,
195 notifyMutationObservers);
196
197 if (isAttachedToPage() && oldValue.isEmpty() && getFirstChild() == null) {
198 final PostponedAction action = new PostponedAction(getPage(), "HtmlScript.setAttributeNS") {
199 @Override
200 public void execute() {
201 ScriptElementSupport.executeScriptIfNeeded(HtmlScript.this, false, false);
202 }
203 };
204 final AbstractJavaScriptEngine<?> engine = getPage().getWebClient().getJavaScriptEngine();
205 engine.addPostponedAction(action);
206 }
207 }
208
209 /**
210 * Executes the <code>onreadystatechange</code> handler, as well as executing
211 * the script itself, if necessary.
212 * {@inheritDoc}
213 */
214 @Override
215 public void onAllChildrenAddedToPage(final boolean postponed) {
216 ScriptElementSupport.onAllChildrenAddedToPage(this, postponed);
217 }
218
219 /**
220 * Gets the script held within the script tag.
221 */
222 private String getScriptCode() {
223 final Iterable<DomNode> textNodes = getChildren();
224 final StringBuilder scriptCode = new StringBuilder();
225 for (final DomNode node : textNodes) {
226 if (node instanceof DomText) {
227 final DomText domText = (DomText) node;
228 scriptCode.append(domText.getData());
229 }
230 }
231 return scriptCode.toString();
232 }
233
234 /**
235 * Indicates if a node without children should be written in expanded form as XML
236 * (i.e. with closing tag rather than with "/>")
237 * @return {@code true} to make generated XML readable as HTML
238 */
239 @Override
240 protected boolean isEmptyXmlTagExpanded() {
241 return true;
242 }
243
244 /**
245 * {@inheritDoc}
246 */
247 @Override
248 protected boolean printChildrenAsXml(final String indent, final boolean tagBefore, final PrintWriter printWriter) {
249 final DomCharacterData textNode = (DomCharacterData) getFirstChild();
250 if (textNode == null) {
251 return tagBefore;
252 }
253
254 final String data = textNode.getData();
255 printWriter.print("\r\n");
256 if (data.contains("//<![CDATA[")) {
257 printWriter.print(data);
258 }
259 else {
260 printWriter.print("//<![CDATA[");
261 printWriter.print("\r\n");
262 printWriter.print(data);
263 printWriter.print("\r\n");
264 printWriter.print("//]]>");
265 }
266 return true;
267 }
268
269 /**
270 * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br>
271 *
272 * Resets the executed flag.
273 * @see HtmlScript#processImportNode(Document)
274 */
275 public void resetExecuted() {
276 executed_ = false;
277 }
278
279 @Override
280 public void processImportNode(final Document doc) {
281 super.processImportNode(doc);
282
283 executed_ = true;
284 }
285
286 /**
287 * Returns a string representation of this object.
288 * @return a string representation of this object
289 */
290 @Override
291 public String toString() {
292 final StringWriter writer = new StringWriter();
293 final PrintWriter printWriter = new PrintWriter(writer);
294
295 printWriter.print(getClass().getSimpleName());
296 printWriter.print("[<");
297 printOpeningTagContentAsXml(printWriter);
298 printWriter.print(">");
299 printWriter.print(getScriptCode());
300 printWriter.print("]");
301 printWriter.flush();
302 return writer.toString();
303 }
304
305 /**
306 * {@inheritDoc}
307 */
308 @Override
309 public DisplayStyle getDefaultStyleDisplay() {
310 return DisplayStyle.NONE;
311 }
312
313 /**
314 * {@inheritDoc}
315 */
316 @Override
317 public void markAsCreatedByDomParser() {
318 createdByDomParser_ = true;
319 }
320
321 /**
322 * {@inheritDoc}
323 */
324 @Override
325 public boolean wasCreatedByDomParser() {
326 return createdByDomParser_;
327 }
328
329 /**
330 * {@inheritDoc}
331 */
332 @Override
333 public boolean isExecuted() {
334 return executed_;
335 }
336
337 /**
338 * {@inheritDoc}
339 */
340 @Override
341 public void setExecuted(final boolean executed) {
342 executed_ = executed;
343 }
344 }