com.gargoylesoftware.htmlunit.html.HtmlScript Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of vaadin-client-compiler-deps Show documentation
Show all versions of vaadin-client-compiler-deps Show documentation
Vaadin is a web application framework for Rich Internet Applications (RIA).
Vaadin enables easy development and maintenance of fast and
secure rich web
applications with a stunning look and feel and a wide browser support.
It features a server-side architecture with the majority of the logic
running
on the server. Ajax technology is used at the browser-side to ensure a
rich
and interactive user experience.
/*
* Copyright (c) 2002-2011 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 java.io.PrintWriter;
import java.util.Map;
import net.sourceforge.htmlunit.corejs.javascript.BaseFunction;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.gargoylesoftware.htmlunit.BrowserVersion;
import com.gargoylesoftware.htmlunit.BrowserVersionFeatures;
import com.gargoylesoftware.htmlunit.ElementNotFoundException;
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.Event;
import com.gargoylesoftware.htmlunit.javascript.host.EventHandler;
import com.gargoylesoftware.htmlunit.javascript.host.Window;
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).
*
* @version $Revision: 6357 $
* @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
* @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 = "//:";
/**
* Creates 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 getAttribute("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 getAttribute("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 getAttribute("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 getAttribute("src");
}
/**
* Returns the value of the attribute "event".
* @return the value of the attribute "event"
*/
public final String getEventAttribute() {
return getAttribute("event");
}
/**
* Returns the value of the attribute "for".
* @return the value of the attribute "for"
*/
public final String getHtmlForAttribute() {
return getAttribute("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 getAttribute("defer");
}
/**
* Returns true if this script is deferred.
* @return 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) {
final String oldValue = getAttributeNS(namespaceURI, qualifiedName);
super.setAttributeNS(namespaceURI, qualifiedName, attributeValue);
if (namespaceURI == null && "src".equals(qualifiedName)) {
final boolean ie =
getPage().getWebClient().getBrowserVersion().hasFeature(BrowserVersionFeatures.GENERATED_5);
if (ie || (oldValue.length() == 0 && getFirstChild() == null)) {
// Always execute if IE;
// if FF, only execute if the "src" attribute
// was undefined and there was no inline code.
executeScriptIfNeeded();
}
}
}
/**
* 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()) {
@Override
public void execute() {
final boolean ie =
getPage().getWebClient().getBrowserVersion().hasFeature(BrowserVersionFeatures.GENERATED_6);
if (ie) {
if (!isDeferred()) {
if (!SLASH_SLASH_COLON.equals(getSrcAttribute())) {
setAndExecuteReadyState(READY_STATE_LOADING);
executeScriptIfNeeded();
setAndExecuteReadyState(READY_STATE_LOADED);
}
else {
setAndExecuteReadyState(READY_STATE_COMPLETE);
executeScriptIfNeeded();
}
}
}
else {
executeScriptIfNeeded();
}
}
};
if (postponed && StringUtils.isBlank(getTextContent())) {
final JavaScriptEngine engine = getPage().getWebClient().getJavaScriptEngine();
engine.addPostponedAction(action);
}
else {
try {
action.execute();
}
catch (final Exception e) {
if (e instanceof RuntimeException) {
throw (RuntimeException) 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 != DomElement.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 boolean ie = getPage().getWebClient().getBrowserVersion().hasFeature(BrowserVersionFeatures.GENERATED_7);
final String scriptCode = getScriptCode();
if (ie && event != ATTRIBUTE_NOT_DEFINED && forr != ATTRIBUTE_NOT_DEFINED) {
if ("window".equals(forr)) {
// everything fine, accepted by IE and FF
final Window window = (Window) getPage().getEnclosingWindow().getScriptObject();
final BaseFunction function = new EventHandler(this, event, scriptCode);
window.jsxFunction_attachEvent(event, function);
}
else {
try {
final HtmlElement elt = ((HtmlPage) getPage()).getHtmlElementById(forr);
elt.setEventHandler(event, scriptCode);
}
catch (final ElementNotFoundException e) {
LOG.warn("
if (browser.hasFeature(BrowserVersionFeatures.HTMLSCRIPT_SRC_JAVASCRIPT)) {
String code = StringUtils.removeStart(src, JavaScriptURLConnection.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 (LOG.isDebugEnabled()) {
LOG.debug("Executing JavaScript: " + code);
}
page.executeJavaScriptIfPossible(code, code, getStartLineNumber());
}
}
}
}
else {
//
if (LOG.isDebugEnabled()) {
LOG.debug("Loading external JavaScript: " + src);
}
try {
final JavaScriptLoadResult result = page.loadExternalJavaScriptFile(src, getCharsetAttribute());
if (result == JavaScriptLoadResult.SUCCESS) {
executeEventIfBrowserHasFeature(Event.TYPE_LOAD,
BrowserVersionFeatures.EVENT_ONLOAD_EXTERNAL_JAVASCRIPT);
}
else if (result == JavaScriptLoadResult.DOWNLOAD_ERROR) {
executeEventIfBrowserHasFeature(Event.TYPE_ERROR,
BrowserVersionFeatures.EVENT_ONERROR_EXTERNAL_JAVASCRIPT);
}
}
catch (final FailingHttpStatusCodeException e) {
executeEventIfBrowserHasFeature(Event.TYPE_ERROR,
BrowserVersionFeatures.EVENT_ONERROR_EXTERNAL_JAVASCRIPT);
throw e;
}
}
}
else if (getFirstChild() != null) {
//
executeInlineScriptIfNeeded();
}
}
private void executeEventIfBrowserHasFeature(final String type, final BrowserVersionFeatures feature) {
if (getPage().getWebClient().getBrowserVersion().hasFeature(feature)) {
final HTMLScriptElement script = (HTMLScriptElement) getScriptObject();
final Event event = new Event(HtmlScript.this, type);
script.executeEvent(event);
}
}
/**
* 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 (!isDirectlyAttachedToPage()) {
return false;
}
// 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) {
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, 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;
}
/**
* 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(final String typeAttribute, final String languageAttribute) {
if (StringUtils.isNotEmpty(typeAttribute)) {
if ("text/javascript".equalsIgnoreCase(typeAttribute)
|| "text/ecmascript".equalsIgnoreCase(typeAttribute)) {
return true;
}
final boolean appJavascriptSupported = getPage().getWebClient().getBrowserVersion()
.hasFeature(BrowserVersionFeatures.HTMLSCRIPT_APPLICATION_JAVASCRIPT);
if (appJavascriptSupported
&& ("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) {
if (getPage().getWebClient().getBrowserVersion()
.hasFeature(BrowserVersionFeatures.EVENT_ONREADY_STATE_CHANGE)) {
setReadyState(state);
final HTMLScriptElement script = (HTMLScriptElement) getScriptObject();
final Event event = new Event(this, Event.TYPE_READY_STATE_CHANGE);
script.executeEvent(event);
}
}
/**
* @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 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("//");
}
}
}