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

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

There is a newer version: 2.70.0
Show newest version
/*
 * Copyright (c) 2002-2020 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 static com.gargoylesoftware.htmlunit.html.DomElement.ATTRIBUTE_NOT_DEFINED;

import java.nio.charset.Charset;

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.AbstractJavaScriptEngine;
import com.gargoylesoftware.htmlunit.javascript.PostponedAction;
import com.gargoylesoftware.htmlunit.javascript.host.Window;
import com.gargoylesoftware.htmlunit.javascript.host.event.Event;
import com.gargoylesoftware.htmlunit.javascript.host.event.EventHandler;
import com.gargoylesoftware.htmlunit.javascript.host.event.EventTarget;
import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLDocument;
import com.gargoylesoftware.htmlunit.protocol.javascript.JavaScriptURLConnection;
import com.gargoylesoftware.htmlunit.util.EncodingSniffer;
import com.gargoylesoftware.htmlunit.util.MimeType;
import com.gargoylesoftware.htmlunit.xml.XmlPage;

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

/**
 * INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.
* * A helper class to be used by elements which support {@link ScriptElement}. * * @author Ahmed Ashour * @author Ronald Brill * @author Ronny Shapiro */ public final class ScriptElementSupport { private static final Log LOG = LogFactory.getLog(ScriptElementSupport.class); /** Invalid source attribute which should be ignored (used by JS libraries like jQuery). */ private static final String SLASH_SLASH_COLON = "//:"; private ScriptElementSupport() { } /** * Lifecycle method invoked after a node and all its children have been added to a page, during * parsing of the HTML. Intended to be overridden by nodes which need to perform custom logic * after they and all their child nodes have been processed by the HTML parser. This method is * not recursive, and the default implementation is empty, so there is no need to call * super.onAllChildrenAddedToPage() if you implement this method. * @param element the element * @param postponed whether to use {@link com.gargoylesoftware.htmlunit.javascript.PostponedAction} or no */ public static void onAllChildrenAddedToPage(final DomElement element, final boolean postponed) { if (element.getOwnerDocument() instanceof XmlPage) { return; } if (LOG.isDebugEnabled()) { LOG.debug("Script node added: " + element.asXml()); } if (!element.getPage().getWebClient().isJavaScriptEngineEnabled()) { if (LOG.isDebugEnabled()) { LOG.debug("SvgScript found but not executed because javascript engine is disabled"); } return; } final PostponedAction action = new PostponedAction(element.getPage(), "Execution of script " + element) { @Override public void execute() { final HTMLDocument jsDoc = (HTMLDocument) ((Window) element.getPage().getEnclosingWindow().getScriptableObject()).getDocument(); jsDoc.setExecutingDynamicExternalPosponed(element.getStartLineNumber() == -1 && ((ScriptElement) element).getSrcAttribute() != ATTRIBUTE_NOT_DEFINED); try { executeScriptIfNeeded(element); } finally { jsDoc.setExecutingDynamicExternalPosponed(false); } } }; final AbstractJavaScriptEngine engine = element.getPage().getWebClient().getJavaScriptEngine(); if (element.hasAttribute("async") && !engine.isScriptRunning()) { final HtmlPage owningPage = element.getHtmlPageOrNull(); owningPage.addAfterLoadAction(action); } else if (element.hasAttribute("async") || postponed && StringUtils.isBlank(element.getTextContent())) { engine.addPostponedAction(action); } else { try { action.execute(); } catch (final RuntimeException e) { throw e; } catch (final Exception e) { throw new RuntimeException(e); } } } /** * INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.
* * Executes this script node if necessary and/or possible. * @param element the element */ public static void executeScriptIfNeeded(final DomElement element) { if (!isExecutionNeeded(element)) { return; } final HtmlPage page = (HtmlPage) element.getPage(); final String src = ((ScriptElement) element).getSrcAttribute(); if (src.equals(SLASH_SLASH_COLON)) { executeEvent(element, 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 { final ScriptElement scriptElement = (ScriptElement) element; scriptElement.setExecuted(true); Charset charset = EncodingSniffer.toCharset(scriptElement.getCharsetAttribute()); if (charset == null) { charset = page.getCharset(); } final JavaScriptLoadResult result = page.loadExternalJavaScriptFile(src, charset); if (result == JavaScriptLoadResult.SUCCESS || result == JavaScriptLoadResult.NO_CONTENT) { executeEvent(element, Event.TYPE_LOAD); } else if (result == JavaScriptLoadResult.DOWNLOAD_ERROR) { executeEvent(element, Event.TYPE_ERROR); } } catch (final FailingHttpStatusCodeException e) { executeEvent(element, Event.TYPE_ERROR); throw e; } } } else if (element.getFirstChild() != null) { // executeInlineScriptIfNeeded(element); if (element.hasFeature(EVENT_ONLOAD_INTERNAL_JAVASCRIPT)) { executeEvent(element, Event.TYPE_LOAD); } } } /** * Indicates if script execution is necessary and/or possible. * * @param element the element * @return {@code true} if the script should be executed */ private static boolean isExecutionNeeded(final DomElement element) { if (((ScriptElement) element).isExecuted()) { return false; } if (!element.isAttachedToPage()) { return false; } // If JavaScript is disabled, we don't need to execute. final SgmlPage page = element.getPage(); if (!page.getWebClient().isJavaScriptEnabled()) { return false; } // If innerHTML or outerHTML is being parsed final HtmlPage htmlPage = element.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 = element; 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. final String t = element.getAttributeDirect("type"); final String l = element.getAttributeDirect("language"); if (!isJavaScript(element, t, l)) { 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. return element.getPage().isAncestorOf(element); } /** * 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 element the element * @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 */ public static boolean isJavaScript(final DomElement element, String typeAttribute, final String languageAttribute) { final BrowserVersion browserVersion = element.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 (MimeType.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; } private static void executeEvent(final DomElement element, final String type) { final EventTarget eventTarget = element.getScriptableObject(); final Event event = new Event(element, type); eventTarget.executeEventLocally(event); } /** * Executes this script node as inline script if necessary and/or possible. */ private static void executeInlineScriptIfNeeded(final DomElement element) { if (!isExecutionNeeded(element)) { return; } final String src = ((ScriptElement) element).getSrcAttribute(); if (src != ATTRIBUTE_NOT_DEFINED) { return; } final String forr = element.getAttributeDirect("for"); String event = element.getAttributeDirect("event"); // The event name can be like "onload" or "onload()". if (event.endsWith("()")) { event = event.substring(0, event.length() - 2); } final String scriptCode = getScriptCode(element); if (event != ATTRIBUTE_NOT_DEFINED && forr != ATTRIBUTE_NOT_DEFINED) { if (element.hasFeature(JS_SCRIPT_SUPPORTS_FOR_AND_EVENT_WINDOW) && "window".equals(forr)) { final Window window = element.getPage().getEnclosingWindow().getScriptableObject(); final BaseFunction function = new EventHandler(element, event, scriptCode); window.getEventListenersContainer().addEventListener(StringUtils.substring(event, 2), function, false); return; } } if (forr == ATTRIBUTE_NOT_DEFINED || "onload".equals(event)) { final String url = element.getPage().getUrl().toExternalForm(); final int line1 = element.getStartLineNumber(); final int line2 = element.getEndLineNumber(); final int col1 = element.getStartColumnNumber(); final int col2 = element.getEndColumnNumber(); final String desc = "script in " + url + " from (" + line1 + ", " + col1 + ") to (" + line2 + ", " + col2 + ")"; ((ScriptElement) element).setExecuted(true); ((HtmlPage) element.getPage()).executeJavaScript(scriptCode, desc, line1); } } /** * Gets the script held within the script tag. */ private static String getScriptCode(final DomElement element) { final Iterable textNodes = element.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(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy