View Javadoc
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("&lt;span id='mySpan'/&gt;"); document.getElementById("mySpan").tagName;</code>
33   * can't work with a filter).
34   *
35   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
36   * @author <a href="mailto:cse@dynabean.de">Christian Sell</a>
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 "/&gt;")
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 void printChildrenAsXml(final String indent, final PrintWriter printWriter) {
249         final DomCharacterData textNode = (DomCharacterData) getFirstChild();
250         if (textNode == null) {
251             return;
252         }
253 
254         final String data = textNode.getData();
255         if (data.contains("//<![CDATA[")) {
256             printWriter.print(data);
257             printWriter.print("\r\n");
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             printWriter.print("\r\n");
266         }
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 }