001: /*
002: * Copyright (c) 2002-2008 Gargoyle Software Inc. All rights reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * 1. Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: * 2. Redistributions in binary form must reproduce the above copyright notice,
010: * this list of conditions and the following disclaimer in the documentation
011: * and/or other materials provided with the distribution.
012: * 3. The end-user documentation included with the redistribution, if any, must
013: * include the following acknowledgment:
014: *
015: * "This product includes software developed by Gargoyle Software Inc.
016: * (http://www.GargoyleSoftware.com/)."
017: *
018: * Alternately, this acknowledgment may appear in the software itself, if
019: * and wherever such third-party acknowledgments normally appear.
020: * 4. The name "Gargoyle Software" must not be used to endorse or promote
021: * products derived from this software without prior written permission.
022: * For written permission, please contact info@GargoyleSoftware.com.
023: * 5. Products derived from this software may not be called "HtmlUnit", nor may
024: * "HtmlUnit" appear in their name, without prior written permission of
025: * Gargoyle Software Inc.
026: *
027: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
028: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
029: * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARGOYLE
030: * SOFTWARE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
031: * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
032: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
033: * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
034: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
035: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
036: * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
037: */
038: package com.gargoylesoftware.htmlunit.html;
039:
040: import java.util.Map;
041:
042: import org.mozilla.javascript.Function;
043:
044: import com.gargoylesoftware.htmlunit.javascript.host.Script;
045:
046: /**
047: * Wrapper for the html element "script".<br>
048: * When a script tag references an external script (with attribute src) it gets executed when the node
049: * is added to the DOM tree. When the script code is nested, it gets executed when the text node
050: * containing the script is added to the HtmlScript.<br>
051: * The ScriptFilter feature of NekoHtml can't be used because it doesn't allow immediate access to the DOM
052: * (i.e. <code>document.write("<span id='mySpan'/>"); document.getElementById("mySpan").tagName;</code>
053: * can't work with a filter).
054: *
055: * @version $Revision: 2163 $
056: * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
057: * @author <a href="mailto:cse@dynabean.de">Christian Sell</a>
058: * @author Marc Guillemot
059: * @author David K. Taylor
060: * @author Ahmed Ashour
061: * @author Daniel Gredler
062: * @see <a href="http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/level-one-html.html#ID-81598695">DOM Level 1</a>
063: * @see <a href="http://www.w3.org/TR/2003/REC-DOM-Level-2-HTML-20030109/html.html#ID-81598695">DOM Level 2</a>
064: */
065: public class HtmlScript extends HtmlElement {
066:
067: private static final long serialVersionUID = 5736570536821513938L;
068:
069: /** The HTML tag represented by this element. */
070: public static final String TAG_NAME = "script";
071:
072: /** Invalid source attribute which should be ignored (used by JS libraries like jQuery). */
073: private static final String SLASH_SLASH_COLON = "//:";
074:
075: /** Not really used? */
076: private static int EventHandlerId_;
077:
078: /**
079: * Create an instance of HtmlScript
080: *
081: * @param page The HtmlPage that contains this element.
082: * @param attributes the initial attributes
083: * @deprecated You should not directly construct HtmlScript.
084: */
085: //TODO: to be removed, deprecated after 1.11
086: public HtmlScript(final HtmlPage page, final Map attributes) {
087: this (null, TAG_NAME, page, attributes);
088: }
089:
090: /**
091: * Create an instance of HtmlScript
092: *
093: * @param namespaceURI the URI that identifies an XML namespace.
094: * @param qualifiedName The qualified name of the element type to instantiate
095: * @param page The HtmlPage that contains this element.
096: * @param attributes the initial attributes
097: */
098: HtmlScript(final String namespaceURI, final String qualifiedName,
099: final HtmlPage page, final Map attributes) {
100: super (namespaceURI, qualifiedName, page, attributes);
101: }
102:
103: /**
104: * Return the value of the attribute "charset". Refer to the
105: * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
106: * documentation for details on the use of this attribute.
107: *
108: * @return The value of the attribute "charset"
109: * or an empty string if that attribute isn't defined.
110: */
111: public final String getCharsetAttribute() {
112: return getAttributeValue("charset");
113: }
114:
115: /**
116: * Return the value of the attribute "type". Refer to the
117: * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
118: * documentation for details on the use of this attribute.
119: *
120: * @return The value of the attribute "type"
121: * or an empty string if that attribute isn't defined.
122: */
123: public final String getTypeAttribute() {
124: return getAttributeValue("type");
125: }
126:
127: /**
128: * Return the value of the attribute "language". Refer to the
129: * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
130: * documentation for details on the use of this attribute.
131: *
132: * @return The value of the attribute "language"
133: * or an empty string if that attribute isn't defined.
134: */
135: public final String getLanguageAttribute() {
136: return getAttributeValue("language");
137: }
138:
139: /**
140: * Return the value of the attribute "src". Refer to the
141: * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
142: * documentation for details on the use of this attribute.
143: *
144: * @return The value of the attribute "src"
145: * or an empty string if that attribute isn't defined.
146: */
147: public final String getSrcAttribute() {
148: return getAttributeValue("src");
149: }
150:
151: /**
152: * Return the value of the attribute "event".
153: * @return The value of the attribute "event"
154: */
155: public final String getEventAttribute() {
156: return getAttributeValue("event");
157: }
158:
159: /**
160: * Return the value of the attribute "for".
161: * @return The value of the attribute "for"
162: */
163: public final String getHtmlForAttribute() {
164: return getAttributeValue("for");
165: }
166:
167: /**
168: * Return the value of the attribute "defer". Refer to the
169: * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
170: * documentation for details on the use of this attribute.
171: *
172: * @return The value of the attribute "defer"
173: * or an empty string if that attribute isn't defined.
174: */
175: public final String getDeferAttribute() {
176: return getAttributeValue("defer");
177: }
178:
179: /**
180: * Executes the content as a script if said content is a text node.
181: * {@inheritDoc}
182: */
183: public DomNode appendDomChild(final DomNode node) {
184: final DomNode response = super .appendDomChild(node);
185: executeInlineScriptIfNeeded(false);
186: return response;
187: }
188:
189: /**
190: * Executes the <tt>onreadystatechange</tt> handler when simulating IE,
191: * as well as executing the script itself, if necessary.
192: * {@inheritDoc}
193: */
194: protected void onAddedToPage() {
195: getLog().debug("Script node added: " + asXml());
196: executeOnReadyStateChangeHandlerIfNecessary();
197: executeScriptIfNeeded(true);
198: super .onAddedToPage();
199: }
200:
201: /**
202: * Executes this script node as inline script if necessary and/or possible.
203: *
204: * @param executeIfDeferred if <tt>false</tt>, and we are emulating IE, and the <tt>defer</tt>
205: * attribute is defined, the script is not executed
206: */
207: private void executeInlineScriptIfNeeded(
208: final boolean executeIfDeferred) {
209: if (!isExecutionNeeded()) {
210: return;
211: }
212:
213: final String defer = getDeferAttribute();
214: final boolean ie = getPage().getWebClient().getBrowserVersion()
215: .isIE();
216: if (!executeIfDeferred && defer != ATTRIBUTE_NOT_DEFINED && ie) {
217: return;
218: }
219:
220: final String src = getSrcAttribute();
221: if (src != HtmlElement.ATTRIBUTE_NOT_DEFINED) {
222: return;
223: }
224:
225: final DomCharacterData textNode = (DomCharacterData) getFirstDomChild();
226: final String forr = getHtmlForAttribute();
227: String event = getEventAttribute();
228:
229: final String scriptCode;
230: if (event != ATTRIBUTE_NOT_DEFINED
231: && forr != ATTRIBUTE_NOT_DEFINED) {
232: // The event name can be like "onload" or "onload()".
233: if (event.endsWith("()")) {
234: event = event.substring(0, event.length() - 2);
235: }
236: final String handler = forr + "." + event;
237: final String functionName = "htmlunit_event_handler_JJLL"
238: + EventHandlerId_;
239: scriptCode = "function " + functionName + "()\n" + "{"
240: + textNode.getData() + "}\n" + handler + "="
241: + functionName + ";";
242: } else {
243: scriptCode = textNode.getData();
244: }
245:
246: final String url = getPage().getWebResponse().getUrl()
247: .toExternalForm();
248: final int line1 = getStartLineNumber();
249: final int line2 = getEndLineNumber();
250: final int col1 = getStartColumnNumber();
251: final int col2 = getEndColumnNumber();
252: final String desc = "script in " + url + " from (" + line1
253: + ", " + col1 + ") to (" + line2 + ", " + col2 + ")";
254: getPage().executeJavaScriptIfPossible(scriptCode, desc, line1);
255: }
256:
257: /**
258: * Executes this script node if necessary and/or possible.
259: *
260: * @param executeIfDeferred if <tt>false</tt>, and we are emulating IE, and the <tt>defer</tt>
261: * attribute is defined, the script is not executed
262: */
263: void executeScriptIfNeeded(final boolean executeIfDeferred) {
264: if (!isExecutionNeeded()) {
265: return;
266: }
267:
268: final String defer = getDeferAttribute();
269: final boolean ie = getPage().getWebClient().getBrowserVersion()
270: .isIE();
271: if (!executeIfDeferred && defer != ATTRIBUTE_NOT_DEFINED && ie) {
272: return;
273: }
274:
275: final String src = getSrcAttribute();
276: if (src.equals(SLASH_SLASH_COLON)) {
277: return;
278: }
279:
280: if (src != ATTRIBUTE_NOT_DEFINED) {
281: getLog().debug("Loading external javascript: " + src);
282: getPage().loadExternalJavaScriptFile(src,
283: getCharsetAttribute());
284: } else if (getFirstDomChild() != null) {
285: executeInlineScriptIfNeeded(executeIfDeferred);
286: }
287: }
288:
289: /**
290: * Indicates if script execution is necessary and/or possible.
291: *
292: * @return <code>true</code> if the script should be executed.
293: */
294: private boolean isExecutionNeeded() {
295: final HtmlPage page = getPage();
296:
297: // If JavaScript is disabled, we don't need to execute.
298: if (!page.getWebClient().isJavaScriptEnabled()) {
299: return false;
300: }
301:
302: // If the script node is nested in an iframe, a noframes, or a noscript node, we don't need to execute.
303: for (DomNode o = this ; o != null; o = o.getParentDomNode()) {
304: if (o instanceof HtmlInlineFrame
305: || o instanceof HtmlNoFrames
306: || o instanceof HtmlNoScript) {
307: return false;
308: }
309: }
310:
311: // If the underlying page no longer owns its window, the client has moved on (possibly
312: // because another script set window.location.href), and we don't need to execute.
313: if (page.getEnclosingWindow() != null
314: && page.getEnclosingWindow().getEnclosedPage() != page) {
315: return false;
316: }
317:
318: // If the script language is not JavaScript, we can't execute.
319: if (!HtmlPage.isJavaScript(getTypeAttribute(),
320: getLanguageAttribute())) {
321: final String t = getTypeAttribute();
322: final String l = getLanguageAttribute();
323: getLog().warn(
324: "Script is not javascript (type: " + t
325: + ", language: " + l
326: + "). Skipping execution.");
327: return false;
328: }
329:
330: // If the script's root ancestor node is not the page, the the script is not a part of the page.
331: // If it isn't yet part of the page, don't execute the script; it's probably just being cloned.
332: DomNode root = this ;
333: while (root.getParentDomNode() != null) {
334: root = root.getParentDomNode();
335: }
336: if (root != getPage()) {
337: return false;
338: }
339:
340: return true;
341: }
342:
343: /**
344: * Sets the <tt>readyState</tt> to {@link DomNode#READY_STATE_COMPLETE} and executes the
345: * <tt>onreadystatechange</tt> handler when simulating IE. Note that script nodes go
346: * straight to the {@link DomNode#READY_STATE_COMPLETE} state, skipping all previous states.
347: */
348: private void executeOnReadyStateChangeHandlerIfNecessary() {
349: if (getPage().getWebClient().getBrowserVersion().isIE()) {
350: setReadyState(READY_STATE_COMPLETE);
351: final Script script = (Script) getScriptObject();
352: final Function handler = script
353: .getOnReadyStateChangeHandler();
354: if (handler != null) {
355: getPage().executeJavaScriptFunctionIfPossible(handler,
356: script, new Object[0], this );
357: }
358: }
359: }
360:
361: /**
362: * @see com.gargoylesoftware.htmlunit.html.HtmlInput#asText()
363: * @return an empty string as the content of script is not visible by itself
364: */
365: public String asText() {
366: return "";
367: }
368:
369: /**
370: * Indicates if a node without children should be written in expanded form as xml
371: * (i.e. with closing tag rather than with "/>")
372: * @return <code>true</code> to make generated xml readable as html
373: */
374: protected boolean isEmptyXmlTagExpanded() {
375: return true;
376: }
377: }
|