View Javadoc
1   /*
2    * Copyright (c) 2002-2026 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("&lt;span id='mySpan'/&gt;"); 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 isCrossorigin() {
174         return hasAttribute("crossorigin");
175     }
176 
177     /**
178      * {@inheritDoc}
179      */
180     @Override
181     public boolean mayBeDisplayed() {
182         return false;
183     }
184 
185     /**
186      * If setting the <code>src</code> attribute, this method executes the new JavaScript if necessary
187      * (behavior varies by browser version). {@inheritDoc}
188      */
189     @Override
190     protected void setAttributeNS(final String namespaceURI, final String qualifiedName, final String attributeValue,
191             final boolean notifyAttributeChangeListeners, final boolean notifyMutationObservers) {
192         final String qualifiedNameLC = org.htmlunit.util.StringUtils.toRootLowerCase(qualifiedName);
193         // special additional processing for the 'src'
194         if (namespaceURI != null || !SRC_ATTRIBUTE.equals(qualifiedNameLC)) {
195             super.setAttributeNS(namespaceURI, qualifiedNameLC, attributeValue, notifyAttributeChangeListeners,
196                     notifyMutationObservers);
197             return;
198         }
199 
200         // namespaceURI is always null here - we can call getAttribute directly
201         final String oldValue = getAttribute(qualifiedNameLC);
202         super.setAttributeNS(null, qualifiedNameLC, attributeValue, notifyAttributeChangeListeners,
203                 notifyMutationObservers);
204 
205         if (isAttachedToPage() && oldValue.isEmpty() && getFirstChild() == null) {
206             final PostponedAction action = new PostponedAction(getPage(), "HtmlScript.setAttributeNS") {
207                 @Override
208                 public void execute() {
209                     ScriptElementSupport.executeScriptIfNeeded(HtmlScript.this, false, false);
210                 }
211             };
212             final AbstractJavaScriptEngine<?> engine = getPage().getWebClient().getJavaScriptEngine();
213             engine.addPostponedAction(action);
214         }
215     }
216 
217     /**
218      * Executes the <code>onreadystatechange</code> handler, as well as executing
219      * the script itself, if necessary.
220      * {@inheritDoc}
221      */
222     @Override
223     public void onAllChildrenAddedToPage(final boolean postponed) {
224         ScriptElementSupport.onAllChildrenAddedToPage(this, postponed);
225     }
226 
227     /**
228      * Gets the script held within the script tag.
229      */
230     private String getScriptCode() {
231         final Iterable<DomNode> textNodes = getChildren();
232         final StringBuilder scriptCode = new StringBuilder();
233         for (final DomNode node : textNodes) {
234             if (node instanceof DomText domText) {
235                 scriptCode.append(domText.getData());
236             }
237         }
238         return scriptCode.toString();
239     }
240 
241     /**
242      * Indicates if a node without children should be written in expanded form as XML
243      * (i.e. with closing tag rather than with "/&gt;")
244      * @return {@code true} to make generated XML readable as HTML
245      */
246     @Override
247     protected boolean isEmptyXmlTagExpanded() {
248         return true;
249     }
250 
251     /**
252      * {@inheritDoc}
253      */
254     @Override
255     protected boolean printChildrenAsXml(final String indent, final boolean tagBefore, final PrintWriter printWriter) {
256         final DomCharacterData textNode = (DomCharacterData) getFirstChild();
257         if (textNode == null) {
258             return tagBefore;
259         }
260 
261         final String data = textNode.getData();
262         printWriter.print("\r\n");
263         if (data.contains("//<![CDATA[")) {
264             printWriter.print(data);
265         }
266         else {
267             printWriter.print("//<![CDATA[");
268             printWriter.print("\r\n");
269             printWriter.print(data);
270             printWriter.print("\r\n");
271             printWriter.print("//]]>");
272         }
273         return true;
274     }
275 
276     /**
277      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br>
278      *
279      * Resets the executed flag.
280      * @see HtmlScript#processImportNode(Document)
281      */
282     public void resetExecuted() {
283         executed_ = false;
284     }
285 
286     @Override
287     public void processImportNode(final Document doc) {
288         super.processImportNode(doc);
289 
290         executed_ = true;
291     }
292 
293     /**
294      * Returns a string representation of this object.
295      * @return a string representation of this object
296      */
297     @Override
298     public String toString() {
299         final StringWriter writer = new StringWriter();
300         final PrintWriter printWriter = new PrintWriter(writer);
301 
302         printWriter.print(getClass().getSimpleName());
303         printWriter.print("[<");
304         printOpeningTagContentAsXml(printWriter);
305         printWriter.print(">");
306         printWriter.print(getScriptCode());
307         printWriter.print("]");
308         printWriter.flush();
309         return writer.toString();
310     }
311 
312     /**
313      * {@inheritDoc}
314      */
315     @Override
316     public DisplayStyle getDefaultStyleDisplay() {
317         return DisplayStyle.NONE;
318     }
319 
320     /**
321      * {@inheritDoc}
322      */
323     @Override
324     public void markAsCreatedByDomParser() {
325         createdByDomParser_ = true;
326     }
327 
328     /**
329      * {@inheritDoc}
330      */
331     @Override
332     public boolean wasCreatedByDomParser() {
333         return createdByDomParser_;
334     }
335 
336     /**
337      * {@inheritDoc}
338      */
339     @Override
340     public boolean isExecuted() {
341         return executed_;
342     }
343 
344     /**
345      * {@inheritDoc}
346      */
347     @Override
348     public void setExecuted(final boolean executed) {
349         executed_ = executed;
350     }
351 }