All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.gargoylesoftware.htmlunit.html.HtmlScript Maven / Gradle / Ivy

/*
 * Copyright (c) 2002-2016 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.BrowserVersionFeatures.EVENT_ONLOAD_INTERNAL_JAVASCRIPT;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.HTMLSCRIPT_TRIM_TYPE;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_SCRIPT_SUPPORTS_FOR_AND_EVENT_WINDOW;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Map;

import net.sourceforge.htmlunit.corejs.javascript.BaseFunction;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.gargoylesoftware.htmlunit.BrowserVersion;
import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
import com.gargoylesoftware.htmlunit.SgmlPage;
import com.gargoylesoftware.htmlunit.html.HtmlPage.JavaScriptLoadResult;
import com.gargoylesoftware.htmlunit.javascript.JavaScriptEngine;
import com.gargoylesoftware.htmlunit.javascript.PostponedAction;
import com.gargoylesoftware.htmlunit.javascript.host.Window;
import com.gargoylesoftware.htmlunit.javascript.host.dom.Document;
import com.gargoylesoftware.htmlunit.javascript.host.event.Event;
import com.gargoylesoftware.htmlunit.javascript.host.event.EventHandler;
import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLDocument;
import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLScriptElement;
import com.gargoylesoftware.htmlunit.protocol.javascript.JavaScriptURLConnection;
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). * * @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 * @author Ronald Brill * @author Daniel Wagner-Hall * @author Frank Danek * @see DOM Level 1 * @see DOM Level 2 */ public class HtmlScript extends HtmlElement { private static final Log LOG = LogFactory.getLog(HtmlScript.class); /** 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 = "//:"; private boolean executed_; /** * Creates an instance of HtmlScript * * @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 qualifiedName, final SgmlPage page, final Map attributes) { super(qualifiedName, page, attributes); } /** * Returns the value of the attribute {@code charset}. Refer to the * HTML 4.01 * documentation for details on the use of this attribute. * * @return the value of the attribute {@code charset} * or an empty string if that attribute isn't defined. */ public final String getCharsetAttribute() { return getAttribute("charset"); } /** * Returns the value of the attribute {@code type}. Refer to the * HTML 4.01 * documentation for details on the use of this attribute. * * @return the value of the attribute {@code type} * or an empty string if that attribute isn't defined. */ public final String getTypeAttribute() { return getAttribute("type"); } /** * Returns the value of the attribute {@code language}. Refer to the * HTML 4.01 * documentation for details on the use of this attribute. * * @return the value of the attribute {@code language} * or an empty string if that attribute isn't defined. */ public final String getLanguageAttribute() { return getAttribute("language"); } /** * Returns the value of the attribute {@code src}. Refer to the * HTML 4.01 * documentation for details on the use of this attribute. * * @return the value of the attribute {@code src} * or an empty string if that attribute isn't defined. */ public final String getSrcAttribute() { return getSrcAttributeNormalized(); } /** * Returns the value of the attribute {@code event}. * @return the value of the attribute {@code event} */ public final String getEventAttribute() { return getAttribute("event"); } /** * Returns the value of the attribute {@code for}. * @return the value of the attribute {@code for} */ public final String getHtmlForAttribute() { return getAttribute("for"); } /** * Returns the value of the attribute {@code defer}. Refer to the * HTML 4.01 * documentation for details on the use of this attribute. * * @return the value of the attribute {@code defer} * or an empty string if that attribute isn't defined. */ public final String getDeferAttribute() { return getAttribute("defer"); } /** * Returns {@code true} if this script is deferred. * @return {@code true} if this script is deferred */ protected boolean isDeferred() { return getDeferAttribute() != ATTRIBUTE_NOT_DEFINED; } /** * {@inheritDoc} */ @Override public boolean mayBeDisplayed() { return false; } /** * If setting the src attribute, this method executes the new JavaScript if necessary * (behavior varies by browser version). {@inheritDoc} */ @Override public void setAttributeNS(final String namespaceURI, final String qualifiedName, final String attributeValue) { // special additional processing for the 'src' if (namespaceURI != null || !"src".equals(qualifiedName)) { super.setAttributeNS(namespaceURI, qualifiedName, attributeValue); return; } final String oldValue = getAttributeNS(namespaceURI, qualifiedName); super.setAttributeNS(namespaceURI, qualifiedName, attributeValue); if (isAttachedToPage()) { // if FF, only execute if the "src" attribute // was undefined and there was no inline code. if (oldValue.isEmpty() && getFirstChild() == null) { final PostponedAction action = new PostponedAction(getPage()) { @Override public void execute() { executeScriptIfNeeded(); } }; final JavaScriptEngine engine = getPage().getWebClient().getJavaScriptEngine(); engine.addPostponedAction(action); } } } /** * Executes the onreadystatechange handler when simulating IE, as well as executing * the script itself, if necessary. {@inheritDoc} */ @Override protected void onAllChildrenAddedToPage(final boolean postponed) { if (getOwnerDocument() instanceof XmlPage) { return; } if (LOG.isDebugEnabled()) { LOG.debug("Script node added: " + asXml()); } final PostponedAction action = new PostponedAction(getPage(), "Execution of script " + this) { @Override public void execute() { final HTMLDocument jsDoc = (HTMLDocument) ((Window) getPage().getEnclosingWindow().getScriptableObject()).getDocument(); jsDoc.setExecutingDynamicExternalPosponed(getStartLineNumber() == -1 && getSrcAttribute() != ATTRIBUTE_NOT_DEFINED); try { executeScriptIfNeeded(); } finally { jsDoc.setExecutingDynamicExternalPosponed(false); } } }; final JavaScriptEngine engine = getPage().getWebClient().getJavaScriptEngine(); if (hasAttribute("async") && !engine.isScriptRunning()) { final HtmlPage owningPage = getHtmlPageOrNull(); owningPage.addAfterLoadAction(action); } else if (hasAttribute("async") || postponed && StringUtils.isBlank(getTextContent())) { engine.addPostponedAction(action); } else { try { action.execute(); } catch (final RuntimeException e) { throw e; } catch (final Exception e) { throw new RuntimeException(e); } } } /** * Executes this script node as inline script if necessary and/or possible. */ private void executeInlineScriptIfNeeded() { if (!isExecutionNeeded()) { return; } final String src = getSrcAttribute(); if (src != ATTRIBUTE_NOT_DEFINED) { return; } final String forr = getHtmlForAttribute(); String event = getEventAttribute(); // The event name can be like "onload" or "onload()". if (event.endsWith("()")) { event = event.substring(0, event.length() - 2); } final String scriptCode = getScriptCode(); if (event != ATTRIBUTE_NOT_DEFINED && forr != ATTRIBUTE_NOT_DEFINED) { if (hasFeature(JS_SCRIPT_SUPPORTS_FOR_AND_EVENT_WINDOW) && "window".equals(forr)) { final Window window = (Window) getPage().getEnclosingWindow().getScriptableObject(); final BaseFunction function = new EventHandler(this, event, scriptCode); window.getEventListenersContainer().addEventListener(StringUtils.substring(event, 2), function, false); return; } } if (forr == ATTRIBUTE_NOT_DEFINED || "onload".equals(event)) { final String url = getPage().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 + ")"; executed_ = true; ((HtmlPage) getPage()).executeJavaScriptIfPossible(scriptCode, desc, line1); } } /** * Gets the script held within the script tag. */ private String getScriptCode() { final Iterable textNodes = getChildren(); final StringBuilder scriptCode = new StringBuilder(); for (final DomNode node : textNodes) { if (node instanceof DomText) { final DomText domText = (DomText) node; scriptCode.append(domText.getData()); } } return scriptCode.toString(); } /** * INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.
* * Executes this script node if necessary and/or possible. */ public void executeScriptIfNeeded() { if (!isExecutionNeeded()) { return; } final HtmlPage page = (HtmlPage) getPage(); final String src = getSrcAttribute(); if (src.equals(SLASH_SLASH_COLON)) { executeEvent(Event.TYPE_ERROR); return; } if (src != ATTRIBUTE_NOT_DEFINED) { if (!src.startsWith(JavaScriptURLConnection.JAVASCRIPT_PREFIX)) { // if (LOG.isDebugEnabled()) { LOG.debug("Loading external JavaScript: " + src); } try { executed_ = true; final JavaScriptLoadResult result = page.loadExternalJavaScriptFile(src, getCharsetAttribute()); if (result == JavaScriptLoadResult.SUCCESS) { executeEvent(Event.TYPE_LOAD); } else if (result == JavaScriptLoadResult.DOWNLOAD_ERROR) { executeEvent(Event.TYPE_ERROR); } } catch (final FailingHttpStatusCodeException e) { executeEvent(Event.TYPE_ERROR); throw e; } } } else if (getFirstChild() != null) { // executeInlineScriptIfNeeded(); if (hasFeature(EVENT_ONLOAD_INTERNAL_JAVASCRIPT)) { executeEvent(Event.TYPE_LOAD); } } } private void executeEvent(final String type) { final HTMLScriptElement script = (HTMLScriptElement) getScriptableObject(); final Event event = new Event(HtmlScript.this, type); script.executeEventLocally(event); } /** * Indicates if script execution is necessary and/or possible. * * @return {@code true} if the script should be executed */ private boolean isExecutionNeeded() { if (executed_) { return false; } if (!isAttachedToPage()) { return false; } // If JavaScript is disabled, we don't need to execute. final SgmlPage page = getPage(); if (!page.getWebClient().getOptions().isJavaScriptEnabled()) { return false; } // If innerHTML or outerHTML is being parsed final HtmlPage htmlPage = getHtmlPageOrNull(); if (htmlPage != null && htmlPage.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) { 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 (!isJavaScript(getTypeAttribute(), getLanguageAttribute())) { final String t = getTypeAttribute(); final String l = getLanguageAttribute(); LOG.warn("Script is not JavaScript (type: " + t + ", language: " + l + "). Skipping execution."); return false; } // If the script's root ancestor node is not the page, then 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. if (!getPage().isAncestorOf(this)) { return false; } return true; } /** * Returns true if a script with the specified type and language attributes is actually JavaScript. * According to W3C recommendation * are content types case insensitive. * IE supports only a limited number of values for the type attribute. For testing you can * use http://www.robinlionheart.com/stds/html4/scripts. * @param typeAttribute the type attribute specified in the script tag * @param languageAttribute the language attribute specified in the script tag * @return true if the script is JavaScript */ boolean isJavaScript(String typeAttribute, final String languageAttribute) { final BrowserVersion browserVersion = getPage().getWebClient().getBrowserVersion(); if (browserVersion.hasFeature(HTMLSCRIPT_TRIM_TYPE)) { typeAttribute = typeAttribute.trim(); } if (StringUtils.isNotEmpty(typeAttribute)) { if ("text/javascript".equalsIgnoreCase(typeAttribute) || "text/ecmascript".equalsIgnoreCase(typeAttribute)) { return true; } if ("application/javascript".equalsIgnoreCase(typeAttribute) || "application/ecmascript".equalsIgnoreCase(typeAttribute) || "application/x-javascript".equalsIgnoreCase(typeAttribute)) { return true; } return false; } if (StringUtils.isNotEmpty(languageAttribute)) { return StringUtils.startsWithIgnoreCase(languageAttribute, "javascript"); } return true; } /** * Sets the readyState to the specified state and executes the * onreadystatechange handler when simulating IE. * @param state this script ready state */ protected void setAndExecuteReadyState(final String state) { } /** * @see com.gargoylesoftware.htmlunit.html.HtmlInput#asText() * @return an empty string as the content of script is not visible by itself */ // we need to preserve this method as it is there since many versions with the above documentation. @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 {@code 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) { return; } final String data = textNode.getData(); if (data.contains("//"); printWriter.print("\r\n"); } } /** * INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.
* * Resets the executed flag. * @see HtmlScript#processImportNode(Document) */ public void resetExecuted() { executed_ = false; } @Override public void processImportNode(final Document doc) { super.processImportNode(doc); executed_ = true; } /** * Returns a string representation of this object. * @return a string representation of this object */ @Override public String toString() { final StringWriter writer = new StringWriter(); final PrintWriter printWriter = new PrintWriter(writer); printWriter.print(getClass().getSimpleName()); printWriter.print("[<"); printOpeningTagContentAsXml(printWriter); printWriter.print(">"); printWriter.print(getScriptCode()); printWriter.print("]"); printWriter.flush(); return writer.toString(); } /** * {@inheritDoc} */ @Override public DisplayStyle getDefaultStyleDisplay() { return DisplayStyle.NONE; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy