/* * Copyright (c) 2002-2008 Gargoyle Software Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.gargoylesoftware.htmlunit.html; import static com.gargoylesoftware.htmlunit.protocol.javascript.JavaScriptURLConnection.JAVASCRIPT_PREFIX; import java.io.PrintWriter; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.mozilla.javascript.Function; import com.gargoylesoftware.htmlunit.BrowserVersion; import com.gargoylesoftware.htmlunit.SgmlPage; import com.gargoylesoftware.htmlunit.javascript.host.HTMLScriptElement; import com.gargoylesoftware.htmlunit.xml.XmlPage; /** * Wrapper for the HTML element "script".
* When a script tag references an external script (with attribute src) it gets executed when the node * is added to the DOM tree. When the script code is nested, it gets executed when the text node * containing the script is added to the HtmlScript.
* The ScriptFilter feature of NekoHtml can't be used because it doesn't allow immediate access to the DOM * (i.e. document.write("<span id='mySpan'/>"); document.getElementById("mySpan").tagName; * can't work with a filter). * * @version $Revision$ * @author Mike Bowler * @author Christian Sell * @author Marc Guillemot * @author David K. Taylor * @author Ahmed Ashour * @author Daniel Gredler * @author Dmitri Zoubkov * @author Sudhan Moghe * @see DOM Level 1 * @see DOM Level 2 */ public class HtmlScript extends HtmlElement { private static final long serialVersionUID = 5736570536821513938L; /** The HTML tag represented by this element. */ public static final String TAG_NAME = "script"; /** Invalid source attribute which should be ignored (used by JS libraries like jQuery). */ private static final String SLASH_SLASH_COLON = "//:"; /** Not really used? */ private static int EventHandlerId_; private final transient Log mainLog_ = LogFactory.getLog(getClass()); /** * Create an instance of HtmlScript * * @param namespaceURI the URI that identifies an XML namespace * @param qualifiedName the qualified name of the element type to instantiate * @param page the HtmlPage that contains this element * @param attributes the initial attributes */ HtmlScript(final String namespaceURI, final String qualifiedName, final SgmlPage page, final Map attributes) { super(namespaceURI, qualifiedName, page, attributes); } /** * Returns the value of the attribute "charset". Refer to the * HTML 4.01 * documentation for details on the use of this attribute. * * @return the value of the attribute "charset" * or an empty string if that attribute isn't defined. */ public final String getCharsetAttribute() { return getAttributeValue("charset"); } /** * Returns the value of the attribute "type". Refer to the * HTML 4.01 * documentation for details on the use of this attribute. * * @return the value of the attribute "type" * or an empty string if that attribute isn't defined. */ public final String getTypeAttribute() { return getAttributeValue("type"); } /** * Returns the value of the attribute "language". Refer to the * HTML 4.01 * documentation for details on the use of this attribute. * * @return the value of the attribute "language" * or an empty string if that attribute isn't defined. */ public final String getLanguageAttribute() { return getAttributeValue("language"); } /** * Returns the value of the attribute "src". Refer to the * HTML 4.01 * documentation for details on the use of this attribute. * * @return the value of the attribute "src" * or an empty string if that attribute isn't defined. */ public final String getSrcAttribute() { return getAttributeValue("src"); } /** * Returns the value of the attribute "event". * @return the value of the attribute "event" */ public final String getEventAttribute() { return getAttributeValue("event"); } /** * Returns the value of the attribute "for". * @return the value of the attribute "for" */ public final String getHtmlForAttribute() { return getAttributeValue("for"); } /** * Returns the value of the attribute "defer". Refer to the * HTML 4.01 * documentation for details on the use of this attribute. * * @return the value of the attribute "defer" * or an empty string if that attribute isn't defined. */ public final String getDeferAttribute() { return getAttributeValue("defer"); } /** * Returns true if this script is deferred. * @return true if this script is deferred */ protected boolean isDeferred() { return getDeferAttribute() != ATTRIBUTE_NOT_DEFINED; } /** * If setting the src attribute, this method executes the new JavaScript if necessary * (behavior varies by browser version). {@inheritDoc} */ @Override protected void setAttributeValue(final String namespaceURI, final String qualifiedName, final String attributeValue, final boolean cloning) { boolean execute = false; if (namespaceURI == null && "src".equals(qualifiedName) && !cloning) { final boolean ie = getPage().getWebClient().getBrowserVersion().isIE(); if (ie || (getAttribute("src").length() == 0 && getFirstChild() == null)) { // Always execute if IE; if FF, only execute if the "src" attribute // was undefined and there was no inline code. execute = true; } } super.setAttributeValue(namespaceURI, qualifiedName, attributeValue, cloning); if (execute) { executeScriptIfNeeded(true); } } /** * Executes the onreadystatechange handler when simulating IE, as well as executing * the script itself, if necessary. {@inheritDoc} */ @Override protected void onAllChildrenAddedToPage() { if (getOwnerDocument() instanceof XmlPage) { return; } if (mainLog_.isDebugEnabled()) { mainLog_.debug("Script node added: " + asXml()); } final boolean ie = getPage().getWebClient().getBrowserVersion().isIE(); final boolean pageFinishedLoading = (getPage().getReadyState() == READY_STATE_COMPLETE); if (!ie || pageFinishedLoading || !isDeferred()) { setReadyStateComplete(); executeScriptIfNeeded(true); } super.onAllChildrenAddedToPage(); } /** * Executes this script node as inline script if necessary and/or possible. * * @param executeIfDeferred if false, and we are emulating IE, and the defer * attribute is defined, the script is not executed */ private void executeInlineScriptIfNeeded(final boolean executeIfDeferred) { if (!isExecutionNeeded()) { return; } final boolean ie = getPage().getWebClient().getBrowserVersion().isIE(); if (!executeIfDeferred && isDeferred() && ie) { return; } final String src = getSrcAttribute(); if (src != HtmlElement.ATTRIBUTE_NOT_DEFINED) { return; } final DomCharacterData textNode = (DomCharacterData) getFirstChild(); final String forr = getHtmlForAttribute(); String event = getEventAttribute(); final String scriptCode; if (event != ATTRIBUTE_NOT_DEFINED && forr != ATTRIBUTE_NOT_DEFINED) { // The event name can be like "onload" or "onload()". if (event.endsWith("()")) { event = event.substring(0, event.length() - 2); } final String handler = forr + "." + event; final String functionName = "htmlunit_event_handler_JJLL" + EventHandlerId_; scriptCode = "function " + functionName + "()\n" + "{" + textNode.getData() + "}\n" + handler + "=" + functionName + ";"; } else { scriptCode = textNode.getData(); } final String url = getPage().getWebResponse().getUrl().toExternalForm(); final int line1 = getStartLineNumber(); final int line2 = getEndLineNumber(); final int col1 = getStartColumnNumber(); final int col2 = getEndColumnNumber(); final String desc = "script in " + url + " from (" + line1 + ", " + col1 + ") to (" + line2 + ", " + col2 + ")"; ((HtmlPage) getPage()).executeJavaScriptIfPossible(scriptCode, desc, line1); } /** * Executes this script node if necessary and/or possible. * * @param executeIfDeferred if false, and we are emulating IE, and the defer * attribute is defined, the script is not executed */ void executeScriptIfNeeded(final boolean executeIfDeferred) { if (!isExecutionNeeded()) { return; } final BrowserVersion browser = getPage().getWebClient().getBrowserVersion(); final boolean ie = browser.isIE(); if (!executeIfDeferred && isDeferred() && ie) { return; } final String src = getSrcAttribute(); if (src.equals(SLASH_SLASH_COLON)) { return; } if (src != ATTRIBUTE_NOT_DEFINED) { if (src.startsWith(JAVASCRIPT_PREFIX)) { // if (!ie || browser.getBrowserVersionNumeric() != 7) { String code = StringUtils.removeStart(src, JAVASCRIPT_PREFIX).trim(); final int len = code.length(); if (len > 2) { if ((code.charAt(0) == '\'' && code.charAt(len - 1) == '\'') || (code.charAt(0) == '"' && code.charAt(len - 1) == '"')) { code = code.substring(1, len - 1); if (mainLog_.isDebugEnabled()) { mainLog_.debug("Executing JavaScript: " + code); } ((HtmlPage) getPage()).executeJavaScriptIfPossible(code, code, getStartLineNumber()); } } } } else { // if (mainLog_.isDebugEnabled()) { mainLog_.debug("Loading external JavaScript: " + src); } ((HtmlPage) getPage()).loadExternalJavaScriptFile(src, getCharsetAttribute()); } } else if (getFirstChild() != null) { // executeInlineScriptIfNeeded(executeIfDeferred); } } /** * Indicates if script execution is necessary and/or possible. * * @return true if the script should be executed */ private boolean isExecutionNeeded() { final SgmlPage page = getPage(); // If JavaScript is disabled, we don't need to execute. if (!page.getWebClient().isJavaScriptEnabled()) { return false; } //If innerHTML or outerHTML is being parsed if (page instanceof HtmlPage && ((HtmlPage) page).isParsingHtmlSnippet()) { return false; } // If the script node is nested in an iframe, a noframes, or a noscript node, we don't need to execute. for (DomNode o = this; o != null; o = o.getParentNode()) { if (o instanceof HtmlInlineFrame || o instanceof HtmlNoFrames || o instanceof HtmlNoScript) { return false; } } // If the underlying page no longer owns its window, the client has moved on (possibly // because another script set window.location.href), and we don't need to execute. if (page.getEnclosingWindow() != null && page.getEnclosingWindow().getEnclosedPage() != page) { return false; } // If the script language is not JavaScript, we can't execute. if (!HtmlPage.isJavaScript(getTypeAttribute(), getLanguageAttribute())) { final String t = getTypeAttribute(); final String l = getLanguageAttribute(); if (mainLog_.isWarnEnabled()) { mainLog_.warn("Script is not JavaScript (type: " + t + ", language: " + l + "). Skipping execution."); } return false; } // If the script's root ancestor node is not the page, the the script is not a part of the page. // If it isn't yet part of the page, don't execute the script; it's probably just being cloned. DomNode root = this; while (root.getParentNode() != null) { root = root.getParentNode(); } if (root != getPage()) { return false; } return true; } /** * Sets the readyState to {@link DomNode#READY_STATE_COMPLETE} and executes the * onreadystatechange handler when simulating IE. Note that script nodes go * straight to the {@link DomNode#READY_STATE_COMPLETE} state, skipping all previous states. */ protected void setReadyStateComplete() { final boolean ie = getPage().getWebClient().getBrowserVersion().isIE(); if (!ie) { return; } setReadyState(READY_STATE_COMPLETE); final HTMLScriptElement script = (HTMLScriptElement) getScriptObject(); final Function handler = script.getOnReadyStateChangeHandler(); if (handler != null) { ((HtmlPage) getPage()).executeJavaScriptFunctionIfPossible(handler, script, new Object[0], this); } } /** * @see com.gargoylesoftware.htmlunit.html.HtmlInput#asText() * @return an empty string as the content of script is not visible by itself */ @Override public String asText() { return ""; } /** * Indicates if a node without children should be written in expanded form as XML * (i.e. with closing tag rather than with "/>") * @return true to make generated XML readable as HTML */ @Override protected boolean isEmptyXmlTagExpanded() { return true; } /** * {@inheritDoc} */ @Override protected void printChildrenAsXml(final String indent, final PrintWriter printWriter) { final DomCharacterData textNode = (DomCharacterData) getFirstChild(); if (textNode != null) { printWriter.println("//"); } } }