org.openqa.selenium.htmlunit.HtmlUnitDriver Maven / Gradle / Ivy
Show all versions of htmlunit3-driver Show documentation
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you 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 org.openqa.selenium.htmlunit;
import static org.openqa.selenium.remote.CapabilityType.ACCEPT_INSECURE_CERTS;
import static org.openqa.selenium.remote.CapabilityType.PAGE_LOAD_STRATEGY;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Map.Entry;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.net.ssl.SSLHandshakeException;
import org.htmlunit.BrowserVersion;
import org.htmlunit.CookieManager;
import org.htmlunit.Page;
import org.htmlunit.ProxyConfig;
import org.htmlunit.ScriptResult;
import org.htmlunit.SgmlPage;
import org.htmlunit.StringWebResponse;
import org.htmlunit.TopLevelWindow;
import org.htmlunit.UnexpectedPage;
import org.htmlunit.WaitingRefreshHandler;
import org.htmlunit.WebClient;
import org.htmlunit.WebClientOptions;
import org.htmlunit.WebRequest;
import org.htmlunit.WebResponse;
import org.htmlunit.WebWindow;
import org.htmlunit.WebWindowEvent;
import org.htmlunit.WebWindowListener;
import org.htmlunit.corejs.javascript.Context;
import org.htmlunit.corejs.javascript.IdScriptableObject;
import org.htmlunit.corejs.javascript.NativeArray;
import org.htmlunit.corejs.javascript.NativeObject;
import org.htmlunit.corejs.javascript.Scriptable;
import org.htmlunit.corejs.javascript.Undefined;
import org.htmlunit.html.DomElement;
import org.htmlunit.html.DomNode;
import org.htmlunit.html.FrameWindow;
import org.htmlunit.html.HtmlElement;
import org.htmlunit.html.HtmlPage;
import org.htmlunit.javascript.HtmlUnitScriptable;
import org.htmlunit.javascript.host.Element;
import org.htmlunit.javascript.host.Location;
import org.htmlunit.javascript.host.html.DocumentProxy;
import org.htmlunit.javascript.host.html.HTMLCollection;
import org.htmlunit.javascript.host.html.HTMLElement;
import org.htmlunit.platform.AwtClipboardHandler;
import org.htmlunit.util.UrlUtils;
import org.openqa.selenium.By;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.Cookie;
import org.openqa.selenium.HasCapabilities;
import org.openqa.selenium.InvalidCookieDomainException;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.NoSuchSessionException;
import org.openqa.selenium.NoSuchWindowException;
import org.openqa.selenium.Proxy;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.UnableToSetCookieException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.WrapsElement;
import org.openqa.selenium.htmlunit.logging.HtmlUnitLogs;
import org.openqa.selenium.htmlunit.options.HtmlUnitDriverOptions;
import org.openqa.selenium.htmlunit.w3.Action;
import org.openqa.selenium.htmlunit.w3.Algorithms;
import org.openqa.selenium.interactions.Interactive;
import org.openqa.selenium.interactions.Sequence;
import org.openqa.selenium.logging.Logs;
import org.openqa.selenium.remote.Browser;
import org.openqa.selenium.remote.DesiredCapabilities;
/**
* An implementation of {@link WebDriver} that drives
* HtmlUnit, which is a headless
* (GUI-less) browser simulator.
*
* The main supported browsers are Chrome, Edge, Firefox and Internet Explorer.
*
* @author Alexei Barantsev
* @author Ahmed Ashour
* @author Rafael Jimenez
* @author Luke Inman-Semerau
* @author Kay McCormick
* @author Simon Stewart
* @author Javier Neira
* @author Ronald Brill
* @author Rob Winch
* @author Andrei Solntsev
* @author Martin Bartoš
* @author Scott Babcock
*/
public class HtmlUnitDriver implements WebDriver, JavascriptExecutor, HasCapabilities, Interactive {
private static final int sleepTime = 200;
private WebClient webClient_;
private final HtmlUnitAlert alert_;
private HtmlUnitWindow currentWindow_;
private HtmlUnitKeyboard keyboard_;
private HtmlUnitMouse mouse_;
private final TargetLocator targetLocator_;
private AsyncScriptExecutor asyncScriptExecutor_;
private PageLoadStrategy pageLoadStrategy_ = PageLoadStrategy.NORMAL;
private final ElementsMap elementsMap_ = new ElementsMap();
private final Options options_;
private final HtmlUnitElementFinder elementFinder_;
private HtmlUnitInputProcessor inputProcessor_ = new HtmlUnitInputProcessor(this);
/** BROWSER_LANGUAGE_CAPABILITY = "browserLanguage". */
public static final String BROWSER_LANGUAGE_CAPABILITY = "browserLanguage";
/** DOWNLOAD_IMAGES_CAPABILITY = "downloadImages". */
public static final String DOWNLOAD_IMAGES_CAPABILITY = "downloadImages";
/** JAVASCRIPT_ENABLED = "javascriptEnabled". */
public static final String JAVASCRIPT_ENABLED = "javascriptEnabled";
/**
* The Lock for the {@link #mainCondition_}, which waits at the end of
* {@link #runAsync(Runnable)} till either and alert is triggered, or
* {@link Runnable} finishes.
*/
private final Lock conditionLock_ = new ReentrantLock();
private final Condition mainCondition_ = conditionLock_.newCondition();
private boolean runAsyncRunning_;
private RuntimeException exception_;
private final ExecutorService defaultExecutor_;
private Executor executor_;
/**
* Constructs a new instance with JavaScript disabled, and the
* {@link BrowserVersion#getDefault() default} BrowserVersion.
*/
public HtmlUnitDriver() {
this(BrowserVersion.getDefault(), false);
}
/**
* Constructs a new instance with the specified {@link BrowserVersion}.
*
* @param version the browser version to use
*/
public HtmlUnitDriver(final BrowserVersion version) {
this(version, false);
}
/**
* Constructs a new instance, specify JavaScript support and using the
* {@link BrowserVersion#getDefault() default} BrowserVersion.
*
* @param enableJavascript whether to enable JavaScript support or not
*/
public HtmlUnitDriver(final boolean enableJavascript) {
this(BrowserVersion.getDefault(), enableJavascript);
}
/**
* Constructs a new instance with the specified {@link BrowserVersion} and the
* JavaScript support.
*
* @param version the browser version to use
* @param enableJavascript whether to enable JavaScript support or not
*/
public HtmlUnitDriver(final BrowserVersion version, final boolean enableJavascript) {
this(new HtmlUnitDriverOptions(version, enableJavascript));
}
public HtmlUnitDriver(final Capabilities desiredCapabilities, final Capabilities requiredCapabilities) {
this(new DesiredCapabilities(desiredCapabilities, requiredCapabilities));
}
/**
* The browserName is {@link Browser#HTMLUNIT} "htmlunit" and the
* browserVersion denotes the required browser AND its version. For example
* "chrome" for Chrome, "firefox-100" for Firefox 100.
*
* @param capabilities desired capabilities requested for the htmlunit driver
* session
*/
public HtmlUnitDriver(final Capabilities capabilities) {
final HtmlUnitDriverOptions driverOptions = new HtmlUnitDriverOptions(capabilities);
webClient_ = newWebClient(driverOptions.getWebClientVersion());
setAcceptInsecureCerts(Boolean.FALSE != driverOptions.getCapability(ACCEPT_INSECURE_CERTS));
final String pageLoadStrategyString = (String) driverOptions.getCapability(PAGE_LOAD_STRATEGY);
if ("none".equals(pageLoadStrategyString)) {
pageLoadStrategy_ = PageLoadStrategy.NONE;
}
else if ("eager".equals(pageLoadStrategyString)) {
pageLoadStrategy_ = PageLoadStrategy.EAGER;
}
final WebClientOptions clientOptions = webClient_.getOptions();
driverOptions.applyOptions(clientOptions);
setProxySettings(Proxy.extractFrom(driverOptions));
webClient_.setRefreshHandler(new WaitingRefreshHandler());
webClient_.setClipboardHandler(new AwtClipboardHandler());
elementFinder_ = new HtmlUnitElementFinder();
alert_ = new HtmlUnitAlert(this);
alert_.handleBrowserCapabilities(driverOptions);
currentWindow_ = new HtmlUnitWindow(webClient_.getCurrentWindow());
defaultExecutor_ = Executors.newCachedThreadPool();
executor_ = defaultExecutor_;
// Now put us on the home page, like a real browser
get(clientOptions.getHomePage());
options_ = new HtmlUnitWebDriverOptions(this);
targetLocator_ = new HtmlUnitTargetLocator(this);
webClient_.addWebWindowListener(new WebWindowListener() {
@Override
public void webWindowOpened(final WebWindowEvent webWindowEvent) {
if (webWindowEvent.getWebWindow() instanceof TopLevelWindow) {
// use the first top level window we are getting aware of
if (currentWindow_ == null && webClient_.getTopLevelWindows().size() == 1) {
currentWindow_ = new HtmlUnitWindow(webClient_.getTopLevelWindows().get(0));
}
}
}
@Override
public void webWindowContentChanged(final WebWindowEvent event) {
elementsMap_.remove(event.getOldPage());
if (event.getWebWindow() != currentWindow_.getWebWindow()) {
return;
}
// Do we need to pick some new default content?
switchToDefaultContentOfWindow(currentWindow_.getWebWindow());
}
@Override
public void webWindowClosed(final WebWindowEvent event) {
elementsMap_.remove(event.getOldPage());
// the last window is gone
if (getWebClient().getTopLevelWindows().size() == 0) {
currentWindow_ = null;
return;
}
// Check if the event window refers to us or one of our parent windows
// setup the currentWindow appropriately if necessary
WebWindow ourCurrentWindow = currentWindow_.getWebWindow();
final WebWindow ourCurrentTopWindow = currentWindow_.getWebWindow().getTopWindow();
do {
// Instance equality is okay in this case
if (ourCurrentWindow == event.getWebWindow()) {
setCurrentWindow(ourCurrentTopWindow);
return;
}
ourCurrentWindow = ourCurrentWindow.getParentWindow();
}
while (ourCurrentWindow != ourCurrentTopWindow);
}
});
resetKeyboardAndMouseState();
modifyWebClient(webClient_);
}
/**
* @return to process or not to proceed
*/
boolean isProcessAlert() {
if (asyncScriptExecutor_ != null) {
final String text = alert_.getText();
alert_.dismiss();
asyncScriptExecutor_.alertTriggered(text);
return false;
}
conditionLock_.lock();
try {
mainCondition_.signal();
}
finally {
conditionLock_.unlock();
}
return true;
}
protected void runAsync(final Runnable r) {
final boolean loadStrategyWait = pageLoadStrategy_ != PageLoadStrategy.NONE;
if (loadStrategyWait) {
while (runAsyncRunning_) {
try {
Thread.sleep(10);
}
catch (final InterruptedException e) {
throw new RuntimeException(e);
}
}
conditionLock_.lock();
runAsyncRunning_ = true;
}
exception_ = null;
final Runnable wrapped = () -> {
try {
r.run();
}
catch (final RuntimeException e) {
exception_ = e;
}
finally {
conditionLock_.lock();
try {
runAsyncRunning_ = false;
mainCondition_.signal();
}
finally {
conditionLock_.unlock();
}
}
};
executor_.execute(wrapped);
if (loadStrategyWait && this.runAsyncRunning_) {
mainCondition_.awaitUninterruptibly();
conditionLock_.unlock();
}
if (exception_ != null) {
throw exception_;
}
}
public void click(final DomElement element, final boolean directClick) {
runAsync(() -> mouse_.click(element, directClick));
}
public void doubleClick(final DomElement element) {
runAsync(() -> mouse_.doubleClick(element));
}
public void mouseUp(final DomElement element) {
runAsync(() -> mouse_.mouseUp(element));
}
public void mouseMove(final DomElement element) {
runAsync(() -> mouse_.mouseMove(element));
}
public void mouseDown(final DomElement element) {
runAsync(() -> mouse_.mouseDown(element));
}
public void submit(final HtmlUnitWebElement element) {
runAsync(element::submitImpl);
}
public void sendKeys(final HtmlUnitWebElement element, final CharSequence... value) {
runAsync(() -> keyboard_.sendKeys(element, true, value));
}
/**
* Get the simulated {@code BrowserVersion}.
*
* @return the used {@code BrowserVersion}
*/
public BrowserVersion getBrowserVersion() {
return webClient_.getBrowserVersion();
}
/**
* Create the underlying WebClient, but don't set any fields on it.
*
* @param version Which browser to emulate
* @return a new instance of WebClient.
*/
protected WebClient newWebClient(final BrowserVersion version) {
return new WebClient(version);
}
/**
* Child classes can override this method to customize the WebClient that the
* HtmlUnit driver uses.
*
* @param client The client to modify
* @return The modified client
*/
protected WebClient modifyWebClient(final WebClient client) {
// Does nothing here to be overridden.
return client;
}
public HtmlUnitAlert getAlert() {
return alert_;
}
public ElementsMap getElementsMap() {
return elementsMap_;
}
public void setCurrentWindow(final WebWindow window) {
if (currentWindow_.getWebWindow() != window) {
currentWindow_ = new HtmlUnitWindow(window);
}
}
/**
* Set proxy for WebClient using Proxy.
*
* @param proxy The proxy preferences.
*/
public void setProxySettings(final Proxy proxy) {
if (proxy == null || proxy.getProxyType() == Proxy.ProxyType.UNSPECIFIED) {
return;
}
switch (proxy.getProxyType()) {
case MANUAL:
final List noProxyHosts = new ArrayList<>();
final String noProxy = proxy.getNoProxy();
if (noProxy != null && !noProxy.isEmpty()) {
final String[] hosts = noProxy.split(",");
for (final String host : hosts) {
if (host.trim().length() > 0) {
noProxyHosts.add(host.trim());
}
}
}
final String httpProxy = proxy.getHttpProxy();
if (httpProxy != null && !httpProxy.isEmpty()) {
String host = httpProxy;
int port = 0;
final int index = httpProxy.indexOf(":");
if (index != -1) {
host = httpProxy.substring(0, index);
port = Integer.parseInt(httpProxy.substring(index + 1));
}
setHTTPProxy(host, port, noProxyHosts);
}
final String socksProxy = proxy.getSocksProxy();
if (socksProxy != null && !socksProxy.isEmpty()) {
String host = socksProxy;
int port = 0;
final int index = socksProxy.indexOf(":");
if (index != -1) {
host = socksProxy.substring(0, index);
port = Integer.parseInt(socksProxy.substring(index + 1));
}
setSocksProxy(host, port, noProxyHosts);
}
// sslProxy is not supported/implemented
// ftpProxy is not supported/implemented
break;
case PAC:
final String pac = proxy.getProxyAutoconfigUrl();
if (pac != null && !pac.isEmpty()) {
setAutoProxy(pac);
}
break;
default:
break;
}
}
/**
* Sets HTTP proxy for WebClient.
*
* @param host The hostname of HTTP proxy
* @param port The port of HTTP proxy, 0 means HTTP proxy w/o port
*/
public void setProxy(final String host, final int port) {
setHTTPProxy(host, port, null);
}
/**
* Sets HTTP proxy for WebClient with bypass proxy hosts.
*
* @param host The hostname of HTTP proxy
* @param port The port of HTTP proxy, 0 means HTTP proxy w/o port
* @param noProxyHosts The list of hosts which need to bypass HTTP proxy
*/
public void setHTTPProxy(final String host, final int port, final List noProxyHosts) {
final ProxyConfig proxyConfig = new ProxyConfig();
proxyConfig.setProxyHost(host);
proxyConfig.setProxyPort(port);
if (noProxyHosts != null && noProxyHosts.size() > 0) {
for (final String noProxyHost : noProxyHosts) {
proxyConfig.addHostsToProxyBypass(noProxyHost);
}
}
getWebClient().getOptions().setProxyConfig(proxyConfig);
}
/**
* Sets SOCKS proxy for WebClient.
*
* @param host The hostname of SOCKS proxy
* @param port The port of SOCKS proxy, 0 means HTTP proxy w/o port
*/
public void setSocksProxy(final String host, final int port) {
setSocksProxy(host, port, null);
}
/**
* Sets SOCKS proxy for WebClient with bypass proxy hosts.
*
* @param host The hostname of SOCKS proxy
* @param port The port of SOCKS proxy, 0 means HTTP proxy w/o port
* @param noProxyHosts The list of hosts which need to bypass SOCKS proxy
*/
public void setSocksProxy(final String host, final int port, final List noProxyHosts) {
final ProxyConfig proxyConfig = new ProxyConfig();
proxyConfig.setProxyHost(host);
proxyConfig.setProxyPort(port);
proxyConfig.setSocksProxy(true);
if (noProxyHosts != null && noProxyHosts.size() > 0) {
for (final String noProxyHost : noProxyHosts) {
proxyConfig.addHostsToProxyBypass(noProxyHost);
}
}
getWebClient().getOptions().setProxyConfig(proxyConfig);
}
/**
* Sets the {@link Executor} to be used for submitting async tasks to. You have
* to close this manually on {@link #quit()}
*
* @param executor the {@link Executor} to use
*/
public void setExecutor(final Executor executor) {
if (executor == null) {
throw new IllegalArgumentException("executor cannot be null");
}
this.executor_ = executor;
}
/**
* Sets Proxy Autoconfiguration URL for WebClient.
*
* @param autoProxyUrl The Proxy Autoconfiguration URL
*/
public void setAutoProxy(final String autoProxyUrl) {
final ProxyConfig proxyConfig = new ProxyConfig();
proxyConfig.setProxyAutoConfigUrl(autoProxyUrl);
getWebClient().getOptions().setProxyConfig(proxyConfig);
}
@Override
public Capabilities getCapabilities() {
return new HtmlUnitDriverOptions(getBrowserVersion()).importOptions(webClient_.getOptions());
}
@Override
public void get(final String url) {
final URL fullUrl;
try {
// this takes care of data: and about:
fullUrl = UrlUtils.toUrlUnsafe(url);
}
catch (final Exception e) {
throw new WebDriverException(e);
}
runAsync(() -> get(fullUrl));
}
/**
* Allows HtmlUnit's about:blank to be loaded in the constructor, and may be
* useful for other tests?
*
* @param fullUrl The URL to visit
*/
protected void get(final URL fullUrl) {
getAlert().close();
getAlert().setAutoAccept(false);
try {
// we can't use webClient.getPage(url) here because selenium has a different
// idea of the current window and we like to load into to selenium current one
final BrowserVersion browser = getBrowserVersion();
final WebRequest request = new WebRequest(fullUrl, browser.getHtmlAcceptHeader(),
browser.getAcceptEncodingHeader());
request.setCharset(StandardCharsets.UTF_8);
getWebClient().getPage(getCurrentWindow().getWebWindow().getTopWindow(), request);
// A "get" works over the entire page
setCurrentWindow(getCurrentWindow().getWebWindow().getTopWindow());
}
catch (final UnknownHostException e) {
final WebWindow currentTopWebWindow = getCurrentWindow().getWebWindow().getTopWindow();
final UnexpectedPage unexpectedPage = new UnexpectedPage(new StringWebResponse("Unknown host", fullUrl),
currentTopWebWindow);
currentTopWebWindow.setEnclosedPage(unexpectedPage);
}
catch (final ConnectException e) {
// This might be expected
}
catch (final SocketTimeoutException e) {
throw new TimeoutException(e);
}
catch (final NoSuchSessionException e) {
throw e;
}
catch (final NoSuchWindowException e) {
throw e;
}
catch (final SSLHandshakeException e) {
return;
}
catch (final Exception e) {
throw new WebDriverException(e);
}
resetKeyboardAndMouseState();
}
private void resetKeyboardAndMouseState() {
keyboard_ = new HtmlUnitKeyboard(this);
mouse_ = new HtmlUnitMouse(this, keyboard_);
}
@Override
public String getCurrentUrl() {
getWebClient(); // check that session is active
final Page page = getCurrentWindow().getWebWindow().getTopWindow().getEnclosedPage();
if (page == null) {
return null;
}
final URL url = page.getUrl();
if (url == null) {
return null;
}
return url.toString();
}
@Override
public String getTitle() {
alert_.ensureUnlocked();
Page page = getCurrentWindow().lastPage();
if (!(page instanceof HtmlPage)) {
return null; // no page so there is no title
}
if (getCurrentWindow().getWebWindow() instanceof FrameWindow) {
page = getCurrentWindow().getWebWindow().getTopWindow().getEnclosedPage();
}
return ((HtmlPage) page).getTitleText();
}
@Override
public WebElement findElement(final By by) {
alert_.ensureUnlocked();
return implicitlyWaitFor(() -> elementFinder_.findElement(this, by));
}
@Override
public List findElements(final By by) {
final long implicitWait = options_.timeouts().getImplicitWaitTimeout().toMillis();
if (implicitWait < sleepTime) {
return elementFinder_.findElements(this, by);
}
final long end = System.currentTimeMillis() + implicitWait;
List found;
do {
found = elementFinder_.findElements(this, by);
if (!found.isEmpty()) {
return found;
}
sleepQuietly(sleepTime);
}
while (System.currentTimeMillis() < end);
return found;
}
public WebElement findElement(final HtmlUnitWebElement element, final By by) {
alert_.ensureUnlocked();
return implicitlyWaitFor(() -> elementFinder_.findElement(element, by));
}
public List findElements(final HtmlUnitWebElement element, final By by) {
final long implicitWait = options_.timeouts().getImplicitWaitTimeout().toMillis();
if (implicitWait < sleepTime) {
return elementFinder_.findElements(element, by);
}
final long end = System.currentTimeMillis() + implicitWait;
List found;
do {
found = elementFinder_.findElements(element, by);
if (!found.isEmpty()) {
return found;
}
sleepQuietly(sleepTime);
}
while (System.currentTimeMillis() < end);
return found;
}
@Override
public String getPageSource() {
final Page page = getCurrentWindow().lastPage();
if (page == null) {
return null;
}
if (page instanceof SgmlPage) {
return ((SgmlPage) page).asXml();
}
final WebResponse response = page.getWebResponse();
return response.getContentAsString();
}
@Override
public void close() {
getWebClient(); // check that session is active
if (getWebClient().getWebWindows().size() == 1) {
// closing the last window is equivalent to quit
quit();
}
else {
final WebWindow thisWindow = getCurrentWindow().getWebWindow(); // check that the current window is active
if (thisWindow != null) {
alert_.close();
((TopLevelWindow) thisWindow.getTopWindow()).close();
}
if (getWebClient().getWebWindows().size() == 0) {
quit();
}
}
}
@Override
public void quit() {
// closing the web client while some async processes are running
// will produce strange effects; therefore wait until they are done
while (runAsyncRunning_) {
try {
Thread.sleep(10);
}
catch (final InterruptedException e) {
throw new RuntimeException(e);
}
}
conditionLock_.lock();
runAsyncRunning_ = true;
try {
if (webClient_ != null) {
alert_.close();
webClient_.close();
webClient_ = null;
}
defaultExecutor_.shutdown();
}
finally {
runAsyncRunning_ = false;
conditionLock_.unlock();
}
}
@Override
public Set getWindowHandles() {
final Set allHandles = new HashSet<>();
for (final WebWindow window : getWebClient().getTopLevelWindows()) {
allHandles.add(String.valueOf(System.identityHashCode(window)));
}
return allHandles;
}
@Override
public String getWindowHandle() {
final WebWindow topWindow = getCurrentWindow().getWebWindow().getTopWindow();
if (topWindow.isClosed()) {
throw new NoSuchWindowException("Window is closed");
}
return String.valueOf(System.identityHashCode(topWindow));
}
@Override
public Object executeScript(String script, final Object... args) {
final HtmlPage page = getPageToInjectScriptInto();
script = "function() {" + script + "\n};";
ScriptResult result = page.executeJavaScript(script);
final Object function = result.getJavaScriptResult();
final Object[] parameters = convertScriptArgs(page, args);
try {
result = page.executeJavaScriptFunction(function, getCurrentWindow().getWebWindow().getScriptableObject(),
parameters, page.getDocumentElement());
return parseNativeJavascriptResult(result);
}
catch (final Throwable ex) {
throw new WebDriverException(ex);
}
}
@Override
public Object executeAsyncScript(final String script, Object... args) {
final HtmlPage page = getPageToInjectScriptInto();
args = convertScriptArgs(page, args);
asyncScriptExecutor_ = new AsyncScriptExecutor(page, options_.timeouts().getScriptTimeout().toMillis());
try {
final Object result = asyncScriptExecutor_.execute(script, args);
alert_.ensureUnlocked();
return parseNativeJavascriptResult(result);
}
finally {
asyncScriptExecutor_ = null;
}
}
private Object[] convertScriptArgs(final HtmlPage page, final Object[] args) {
final HtmlUnitScriptable scope = page.getEnclosingWindow().getScriptableObject();
if (scope == null) {
return args;
}
final Object[] parameters = new Object[args.length];
Context.enter();
try {
for (int i = 0; i < args.length; i++) {
parameters[i] = parseArgumentIntoJavascriptParameter(scope, args[i]);
}
}
finally {
Context.exit();
}
return parameters;
}
private HtmlPage getPageToInjectScriptInto() {
if (!isJavascriptEnabled()) {
throw new UnsupportedOperationException("Javascript is not enabled for this HtmlUnitDriver instance");
}
final Page lastPage = getCurrentWindow().lastPage();
if (!(lastPage instanceof HtmlPage)) {
throw new UnsupportedOperationException("Cannot execute JS against a plain text page");
}
return (HtmlPage) lastPage;
}
private Object parseArgumentIntoJavascriptParameter(final Scriptable scope, Object arg) {
while (arg instanceof WrapsElement) {
arg = ((WrapsElement) arg).getWrappedElement();
}
if (!(arg instanceof HtmlUnitWebElement
|| arg instanceof HtmlElement
|| arg instanceof Number // special case the underlying type
|| arg instanceof String
|| arg instanceof Boolean
|| arg instanceof Object[]
|| arg instanceof int[]
|| arg instanceof long[]
|| arg instanceof float[]
|| arg instanceof double[]
|| arg instanceof boolean[]
|| arg instanceof Collection> || arg instanceof Map, ?>)) {
throw new IllegalArgumentException(
"Argument must be a string, number, boolean or WebElement: " + arg + " (" + arg.getClass() + ")");
}
if (arg instanceof HtmlUnitWebElement) {
final HtmlUnitWebElement webElement = (HtmlUnitWebElement) arg;
assertElementNotStale(webElement.getElement());
return webElement.getElement().getScriptableObject();
}
else if (arg instanceof HtmlElement) {
final HtmlElement element = (HtmlElement) arg;
assertElementNotStale(element);
return element.getScriptableObject();
}
else if (arg instanceof Collection>) {
final List