<commit_before>/*
Copyright 2007-2009 WebDriver committers
Copyright 2007-2009 Google Inc.
Portions copyright 2011 Software Freedom Conservancy

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 org.openqa.selenium.htmlunit;

import static org.openqa.selenium.remote.CapabilityType.SUPPORTS_FINDING_BY_CSS;

import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableSet;

import org.openqa.selenium.Alert;
import org.openqa.selenium.By;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.Cookie;
import org.openqa.selenium.HasCapabilities;
import org.openqa.selenium.HasInputDevices;
import org.openqa.selenium.InvalidCookieDomainException;
import org.openqa.selenium.InvalidSelectorException;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.Keyboard;
import org.openqa.selenium.Mouse;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.NoSuchFrameException;
import org.openqa.selenium.NoSuchWindowException;
import org.openqa.selenium.Platform;
import org.openqa.selenium.Proxy;
import org.openqa.selenium.SearchContext;
import org.openqa.selenium.UnableToSetCookieException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.browserlaunchers.Proxies;
import org.openqa.selenium.internal.FindsByCssSelector;
import org.openqa.selenium.internal.FindsById;
import org.openqa.selenium.internal.FindsByLinkText;
import org.openqa.selenium.internal.FindsByName;
import org.openqa.selenium.internal.FindsByTagName;
import org.openqa.selenium.internal.FindsByXPath;
import org.openqa.selenium.internal.WrapsElement;
import org.openqa.selenium.logging.Logs;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;

import com.gargoylesoftware.htmlunit.BrowserVersion;
import com.gargoylesoftware.htmlunit.CookieManager;
import com.gargoylesoftware.htmlunit.ElementNotFoundException;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.ProxyConfig;
import com.gargoylesoftware.htmlunit.ScriptResult;
import com.gargoylesoftware.htmlunit.SgmlPage;
import com.gargoylesoftware.htmlunit.TopLevelWindow;
import com.gargoylesoftware.htmlunit.WaitingRefreshHandler;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebResponse;
import com.gargoylesoftware.htmlunit.WebWindow;
import com.gargoylesoftware.htmlunit.WebWindowEvent;
import com.gargoylesoftware.htmlunit.WebWindowListener;
import com.gargoylesoftware.htmlunit.WebWindowNotFoundException;
import com.gargoylesoftware.htmlunit.html.BaseFrame;
import com.gargoylesoftware.htmlunit.html.DomNode;
import com.gargoylesoftware.htmlunit.html.DomNodeList;
import com.gargoylesoftware.htmlunit.html.FrameWindow;
import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
import com.gargoylesoftware.htmlunit.html.HtmlElement;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLCollection;
import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLElement;
import net.sourceforge.htmlunit.corejs.javascript.Context;
import net.sourceforge.htmlunit.corejs.javascript.ContextAction;
import net.sourceforge.htmlunit.corejs.javascript.Function;
import net.sourceforge.htmlunit.corejs.javascript.NativeArray;
import net.sourceforge.htmlunit.corejs.javascript.NativeObject;
import net.sourceforge.htmlunit.corejs.javascript.Scriptable;
import net.sourceforge.htmlunit.corejs.javascript.ScriptableObject;
import net.sourceforge.htmlunit.corejs.javascript.Undefined;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.io.IOException;
import java.net.ConnectException;
import java.net.URL;
import java.net.UnknownHostException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

public class HtmlUnitDriver implements WebDriver, JavascriptExecutor,
    FindsById, FindsByLinkText, FindsByXPath, FindsByName, FindsByCssSelector,
    FindsByTagName, HasCapabilities, HasInputDevices {

  private WebClient webClient;
  private WebWindow currentWindow;

  private boolean enableJavascript;
  private ProxyConfig proxyConfig;
  private long implicitWait = 0;
  private long scriptTimeout = 0;
  private HtmlUnitKeyboard keyboard;
  private HtmlUnitMouse mouse;
  private boolean gotPage;

  public static final String INVALIDXPATHERROR = "The xpath expression '%s' cannot be evaluated";
  public static final String INVALIDSELECTIONERROR =
      "The xpath expression '%s' selected an object of type '%s' instead of a WebElement";

  public HtmlUnitDriver(BrowserVersion version) {
    webClient = createWebClient(version);
    currentWindow = webClient.getCurrentWindow();

    webClient.addWebWindowListener(new WebWindowListener() {
      public void webWindowOpened(WebWindowEvent webWindowEvent) {
        // Ignore
      }

      public void webWindowContentChanged(WebWindowEvent event) {
        if (event.getWebWindow() != currentWindow) {
          return;
        }

        // Do we need to pick some new default content?
        switchToDefaultContentOfWindow(currentWindow);
      }

      public void webWindowClosed(WebWindowEvent event) {
        // Check if the event window refers to us or one of our parent windows
        // setup the currentWindow appropriately if necessary
        WebWindow curr = currentWindow;
        do {
          // Instance equality is okay in this case
          if (curr == event.getWebWindow()) {
            currentWindow = currentWindow.getTopWindow();
            return;
          }
          curr = curr.getParentWindow();
        } while (curr != currentWindow.getTopWindow());
      }
    });

    // Now put us on the home page, like a real browser
    get(webClient.getHomePage());
    gotPage = false;
    resetKeyboardAndMouseState();
  }

  public HtmlUnitDriver() {
    this(false);
  }

  public HtmlUnitDriver(boolean enableJavascript) {
    this(BrowserVersion.getDefault());
    setJavascriptEnabled(enableJavascript);
  }

  /**
   * Note: There are two configuration modes for the HtmlUnitDriver using this constructor. The
   * first is where the browserName is "firefox", "internet explorer" and browserVersion denotes the
   * desired version. The second one is where the browserName is "htmlunit" and the browserVersion
   * denotes the required browser AND its version. In this mode the browserVersion could either be
   * "firefox" for Firefox or "internet explorer-7" for IE 7. The Remote WebDriver uses the second
   * mode - the first mode is deprecated and should not be used.
   */
  public HtmlUnitDriver(Capabilities capabilities) {
    this(determineBrowserVersion(capabilities));

    setJavascriptEnabled(capabilities.isJavascriptEnabled());

    if (capabilities.getCapability(CapabilityType.PROXY) != null) {
      Proxy proxy = Proxies.extractProxy(capabilities);
      String fullProxy = proxy.getHttpProxy();
      if (fullProxy != null) {
        int index = fullProxy.indexOf(":");
        if (index != -1) {
          String host = fullProxy.substring(0, index);
          int port = Integer.parseInt(fullProxy.substring(index + 1));
          setProxy(host, port);
        } else {
          setProxy(fullProxy, 0);
        }
      }
    }
  }

  // Package visibility for testing
  static BrowserVersion determineBrowserVersion(Capabilities capabilities) {
    String browserName = null;
    String browserVersion = null;

    String rawVersion = capabilities.getVersion();
    String[] splitVersion = rawVersion == null ? new String[0] : rawVersion.split("-");
    if (splitVersion.length > 1) {
      browserVersion = splitVersion[1];
      browserName = splitVersion[0];
    } else {
      browserName = capabilities.getVersion();
      browserVersion = "";
    }

    // This is for backwards compatibility - in case there are users who are trying to
    // configure the HtmlUnitDriver by using the c'tor with capabilities.
    if (!"htmlunit".equals(capabilities.getBrowserName())) {
      browserName = capabilities.getBrowserName();
      browserVersion = capabilities.getVersion();
    }

    if ("firefox".equals(browserName)) {
      return BrowserVersion.FIREFOX_3;
    }

    if ("internet explorer".equals(browserName)) {
      // Try and convert the version
      try {
        int version = Integer.parseInt(browserVersion);
        switch (version) {
          case 6:
            return BrowserVersion.INTERNET_EXPLORER_6;
          case 7:
            return BrowserVersion.INTERNET_EXPLORER_7;
          case 8:
            return BrowserVersion.INTERNET_EXPLORER_8;
          default:
            return BrowserVersion.INTERNET_EXPLORER_8;
        }
      } catch (NumberFormatException e) {
        return BrowserVersion.INTERNET_EXPLORER_8;
      }
    }

    return BrowserVersion.FIREFOX_3_6;
  }

  private WebClient createWebClient(BrowserVersion version) {
    WebClient client = newWebClient(version);
    client.setHomePage(WebClient.URL_ABOUT_BLANK.toString());
    client.setThrowExceptionOnFailingStatusCode(false);
    client.setPrintContentOnFailingStatusCode(false);
    client.setJavaScriptEnabled(enableJavascript);
    client.setRedirectEnabled(true);
    client.setRefreshHandler(new WaitingRefreshHandler());

    try {
      client.setUseInsecureSSL(true);
    } catch (GeneralSecurityException e) {
      throw new WebDriverException(e);
    }

    // Ensure that we've set the proxy if necessary
    if (proxyConfig != null) {
      client.setProxyConfig(proxyConfig);
    }

    return modifyWebClient(client);
  }

  /**
   * 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(BrowserVersion version) {
    return new WebClient(version);
  }

  /**
   * Child classes can override this method to customise the webclient that the HtmlUnit driver
   * uses.
   * 
   * @param client The client to modify
   * @return The modified client
   */
  protected WebClient modifyWebClient(WebClient client) {
    // Does nothing here to be overridden.
    return client;
  }

  public void setProxy(String host, int port) {
    proxyConfig = new ProxyConfig(host, port);
    webClient.setProxyConfig(proxyConfig);
  }

  public void setAutoProxy(String autoProxyUrl) {
    proxyConfig = new ProxyConfig();
    proxyConfig.setProxyAutoConfigUrl(autoProxyUrl);
    webClient.setProxyConfig(proxyConfig);
  }

  public Capabilities getCapabilities() {
    DesiredCapabilities capabilities = DesiredCapabilities.htmlUnit();

    capabilities.setPlatform(Platform.getCurrent());
    capabilities.setJavascriptEnabled(isJavascriptEnabled());
    capabilities.setCapability(SUPPORTS_FINDING_BY_CSS, true);

    return capabilities;
  }

  public void get(String url) {
    // Prevent the malformed url exception.
    if (WebClient.URL_ABOUT_BLANK.toString().equals(url)) {
      get(WebClient.URL_ABOUT_BLANK);
      return;
    }

    URL fullUrl;
    try {
      fullUrl = new URL(url);
    } catch (Exception e) {
      throw new WebDriverException(e);
    }

    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(URL fullUrl) {
    try {
      // A "get" works over the entire page
      currentWindow = currentWindow.getTopWindow();
      webClient.getPage(fullUrl);
    } catch (UnknownHostException e) {
      // This should be fine
    } catch (ConnectException e) {
      // This might be expected
    } catch (Exception e) {
      throw new WebDriverException(e);
    }

    gotPage = true;
    pickWindow();
    resetKeyboardAndMouseState();
  }

  private void resetKeyboardAndMouseState() {
    keyboard = new HtmlUnitKeyboard(this);
    mouse = new HtmlUnitMouse(this, keyboard);
  }

  protected void pickWindow() {
    // TODO(simon): HtmlUnit tries to track the current window as the frontmost. We don't
    if (currentWindow == null) {
      currentWindow = webClient.getCurrentWindow();
    }
  }

  public String getCurrentUrl() {
    // TODO(simon): Blech. I can see this being baaad
    URL url = getRawUrl();
    if (url == null) {
      return null;
    }

    return url.toString();
  }

  public String getTitle() {
    Page page = lastPage();
    if (page == null || !(page instanceof HtmlPage)) {
      return null; // no page so there is no title
    }
    if (currentWindow instanceof FrameWindow) {
      page = ((FrameWindow) currentWindow).getTopWindow().getEnclosedPage();
    }

    return ((HtmlPage) page).getTitleText();
  }

  public WebElement findElement(By by) {
    return findElement(by, this);
  }

  public List<WebElement> findElements(By by) {
    return findElements(by, this);
  }

  public String getPageSource() {
    Page page = lastPage();
    if (page == null) {
      return null;
    }

    if (page instanceof SgmlPage) {
      return ((SgmlPage) page).asXml();
    }
    WebResponse response = page.getWebResponse();
    return response.getContentAsString();
  }

  public void close() {
    if (currentWindow != null) {
      ((TopLevelWindow) currentWindow.getTopWindow()).close();
    }
    if (webClient.getWebWindows().size() == 0) {
      quit();
    }
  }

  public void quit() {
    if (webClient != null) {
      webClient.closeAllWindows();
      webClient = null;
    }
    currentWindow = null;
  }

  public Set<String> getWindowHandles() {
    final Set<String> allHandles = new HashSet<String>();
    for (final WebWindow window : webClient.getTopLevelWindows()) {
      allHandles.add(String.valueOf(System.identityHashCode(window)));
    }

    return allHandles;
  }

  public String getWindowHandle() {
    WebWindow topWindow = currentWindow.getTopWindow();
    if (topWindow.isClosed()) {
      throw new NoSuchWindowException("Window is closed");
    }
    return String.valueOf(System.identityHashCode(topWindow));
  }

  public Object executeScript(String script, final Object... args) {
    HtmlPage page = getPageToInjectScriptInto();

    script = "function() {" + script + "\n};";
    ScriptResult result = page.executeJavaScript(script);
    Function func = (Function) result.getJavaScriptResult();

    final Scriptable scope = (Scriptable) page.getEnclosingWindow().getScriptObject();

    final Object[] parameters = new Object[args.length];
    final ContextAction action = new ContextAction() {
      public Object run(final Context context) {
        for (int i = 0; i < args.length; i++) {
          parameters[i] = parseArgumentIntoJavsacriptParameter(context, scope, args[i]);
        }
        return null;
      }
    };
    webClient.getJavaScriptEngine().getContextFactory().call(action);

    result = page.executeJavaScriptFunctionIfPossible(
        func,
        (ScriptableObject) currentWindow.getScriptObject(),
        parameters,
        page.getDocumentElement());

    return parseNativeJavascriptResult(result);
  }

  public Object executeAsyncScript(String script, Object... args) {
    HtmlPage page = getPageToInjectScriptInto();

    Object result = new AsyncScriptExecutor(page, scriptTimeout)
        .execute(script, args);

    return parseNativeJavascriptResult(result);
  }

  private HtmlPage getPageToInjectScriptInto() {
    if (!isJavascriptEnabled()) {
      throw new UnsupportedOperationException(
          "Javascript is not enabled for this HtmlUnitDriver instance");
    }

    final Page lastPage = lastPage();
    if (!(lastPage instanceof HtmlPage)) {
      throw new UnsupportedOperationException("Cannot execute JS against a plain text page");
    } else if (!gotPage) {
      // just to make ExecutingJavascriptTest.testShouldThrowExceptionIfExecutingOnNoPage happy
      // but does this limitation make sense?
      throw new WebDriverException("Can't execute JavaScript before a page has been loaded!");
    }

    return (HtmlPage) lastPage;
  }

  private Object parseArgumentIntoJavsacriptParameter(
      Context context, Scriptable scope, Object arg) {
    if (arg instanceof WrapsElement) {
      arg = ((WrapsElement) arg).getWrappedElement();
    }<commit_msg>SimonStewart: Apparently people write code that doesn't completely unwrap elements. Big sigh.<commit_after>/*
Copyright 2007-2009 WebDriver committers
Copyright 2007-2009 Google Inc.
Portions copyright 2011 Software Freedom Conservancy

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 org.openqa.selenium.htmlunit;

import static org.openqa.selenium.remote.CapabilityType.SUPPORTS_FINDING_BY_CSS;

import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableSet;

import org.openqa.selenium.Alert;
import org.openqa.selenium.By;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.Cookie;
import org.openqa.selenium.HasCapabilities;
import org.openqa.selenium.HasInputDevices;
import org.openqa.selenium.InvalidCookieDomainException;
import org.openqa.selenium.InvalidSelectorException;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.Keyboard;
import org.openqa.selenium.Mouse;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.NoSuchFrameException;
import org.openqa.selenium.NoSuchWindowException;
import org.openqa.selenium.Platform;
import org.openqa.selenium.Proxy;
import org.openqa.selenium.SearchContext;
import org.openqa.selenium.UnableToSetCookieException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.browserlaunchers.Proxies;
import org.openqa.selenium.internal.FindsByCssSelector;
import org.openqa.selenium.internal.FindsById;
import org.openqa.selenium.internal.FindsByLinkText;
import org.openqa.selenium.internal.FindsByName;
import org.openqa.selenium.internal.FindsByTagName;
import org.openqa.selenium.internal.FindsByXPath;
import org.openqa.selenium.internal.WrapsElement;
import org.openqa.selenium.logging.Logs;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;

import com.gargoylesoftware.htmlunit.BrowserVersion;
import com.gargoylesoftware.htmlunit.CookieManager;
import com.gargoylesoftware.htmlunit.ElementNotFoundException;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.ProxyConfig;
import com.gargoylesoftware.htmlunit.ScriptResult;
import com.gargoylesoftware.htmlunit.SgmlPage;
import com.gargoylesoftware.htmlunit.TopLevelWindow;
import com.gargoylesoftware.htmlunit.WaitingRefreshHandler;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebResponse;
import com.gargoylesoftware.htmlunit.WebWindow;
import com.gargoylesoftware.htmlunit.WebWindowEvent;
import com.gargoylesoftware.htmlunit.WebWindowListener;
import com.gargoylesoftware.htmlunit.WebWindowNotFoundException;
import com.gargoylesoftware.htmlunit.html.BaseFrame;
import com.gargoylesoftware.htmlunit.html.DomNode;
import com.gargoylesoftware.htmlunit.html.DomNodeList;
import com.gargoylesoftware.htmlunit.html.FrameWindow;
import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
import com.gargoylesoftware.htmlunit.html.HtmlElement;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLCollection;
import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLElement;
import net.sourceforge.htmlunit.corejs.javascript.Context;
import net.sourceforge.htmlunit.corejs.javascript.ContextAction;
import net.sourceforge.htmlunit.corejs.javascript.Function;
import net.sourceforge.htmlunit.corejs.javascript.NativeArray;
import net.sourceforge.htmlunit.corejs.javascript.NativeObject;
import net.sourceforge.htmlunit.corejs.javascript.Scriptable;
import net.sourceforge.htmlunit.corejs.javascript.ScriptableObject;
import net.sourceforge.htmlunit.corejs.javascript.Undefined;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.io.IOException;
import java.net.ConnectException;
import java.net.URL;
import java.net.UnknownHostException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

public class HtmlUnitDriver implements WebDriver, JavascriptExecutor,
    FindsById, FindsByLinkText, FindsByXPath, FindsByName, FindsByCssSelector,
    FindsByTagName, HasCapabilities, HasInputDevices {

  private WebClient webClient;
  private WebWindow currentWindow;

  private boolean enableJavascript;
  private ProxyConfig proxyConfig;
  private long implicitWait = 0;
  private long scriptTimeout = 0;
  private HtmlUnitKeyboard keyboard;
  private HtmlUnitMouse mouse;
  private boolean gotPage;

  public static final String INVALIDXPATHERROR = "The xpath expression '%s' cannot be evaluated";
  public static final String INVALIDSELECTIONERROR =
      "The xpath expression '%s' selected an object of type '%s' instead of a WebElement";

  public HtmlUnitDriver(BrowserVersion version) {
    webClient = createWebClient(version);
    currentWindow = webClient.getCurrentWindow();

    webClient.addWebWindowListener(new WebWindowListener() {
      public void webWindowOpened(WebWindowEvent webWindowEvent) {
        // Ignore
      }

      public void webWindowContentChanged(WebWindowEvent event) {
        if (event.getWebWindow() != currentWindow) {
          return;
        }

        // Do we need to pick some new default content?
        switchToDefaultContentOfWindow(currentWindow);
      }

      public void webWindowClosed(WebWindowEvent event) {
        // Check if the event window refers to us or one of our parent windows
        // setup the currentWindow appropriately if necessary
        WebWindow curr = currentWindow;
        do {
          // Instance equality is okay in this case
          if (curr == event.getWebWindow()) {
            currentWindow = currentWindow.getTopWindow();
            return;
          }
          curr = curr.getParentWindow();
        } while (curr != currentWindow.getTopWindow());
      }
    });

    // Now put us on the home page, like a real browser
    get(webClient.getHomePage());
    gotPage = false;
    resetKeyboardAndMouseState();
  }

  public HtmlUnitDriver() {
    this(false);
  }

  public HtmlUnitDriver(boolean enableJavascript) {
    this(BrowserVersion.getDefault());
    setJavascriptEnabled(enableJavascript);
  }

  /**
   * Note: There are two configuration modes for the HtmlUnitDriver using this constructor. The
   * first is where the browserName is "firefox", "internet explorer" and browserVersion denotes the
   * desired version. The second one is where the browserName is "htmlunit" and the browserVersion
   * denotes the required browser AND its version. In this mode the browserVersion could either be
   * "firefox" for Firefox or "internet explorer-7" for IE 7. The Remote WebDriver uses the second
   * mode - the first mode is deprecated and should not be used.
   */
  public HtmlUnitDriver(Capabilities capabilities) {
    this(determineBrowserVersion(capabilities));

    setJavascriptEnabled(capabilities.isJavascriptEnabled());

    if (capabilities.getCapability(CapabilityType.PROXY) != null) {
      Proxy proxy = Proxies.extractProxy(capabilities);
      String fullProxy = proxy.getHttpProxy();
      if (fullProxy != null) {
        int index = fullProxy.indexOf(":");
        if (index != -1) {
          String host = fullProxy.substring(0, index);
          int port = Integer.parseInt(fullProxy.substring(index + 1));
          setProxy(host, port);
        } else {
          setProxy(fullProxy, 0);
        }
      }
    }
  }

  // Package visibility for testing
  static BrowserVersion determineBrowserVersion(Capabilities capabilities) {
    String browserName = null;
    String browserVersion = null;

    String rawVersion = capabilities.getVersion();
    String[] splitVersion = rawVersion == null ? new String[0] : rawVersion.split("-");
    if (splitVersion.length > 1) {
      browserVersion = splitVersion[1];
      browserName = splitVersion[0];
    } else {
      browserName = capabilities.getVersion();
      browserVersion = "";
    }

    // This is for backwards compatibility - in case there are users who are trying to
    // configure the HtmlUnitDriver by using the c'tor with capabilities.
    if (!"htmlunit".equals(capabilities.getBrowserName())) {
      browserName = capabilities.getBrowserName();
      browserVersion = capabilities.getVersion();
    }

    if ("firefox".equals(browserName)) {
      return BrowserVersion.FIREFOX_3;
    }

    if ("internet explorer".equals(browserName)) {
      // Try and convert the version
      try {
        int version = Integer.parseInt(browserVersion);
        switch (version) {
          case 6:
            return BrowserVersion.INTERNET_EXPLORER_6;
          case 7:
            return BrowserVersion.INTERNET_EXPLORER_7;
          case 8:
            return BrowserVersion.INTERNET_EXPLORER_8;
          default:
            return BrowserVersion.INTERNET_EXPLORER_8;
        }
      } catch (NumberFormatException e) {
        return BrowserVersion.INTERNET_EXPLORER_8;
      }
    }

    return BrowserVersion.FIREFOX_3_6;
  }

  private WebClient createWebClient(BrowserVersion version) {
    WebClient client = newWebClient(version);
    client.setHomePage(WebClient.URL_ABOUT_BLANK.toString());
    client.setThrowExceptionOnFailingStatusCode(false);
    client.setPrintContentOnFailingStatusCode(false);
    client.setJavaScriptEnabled(enableJavascript);
    client.setRedirectEnabled(true);
    client.setRefreshHandler(new WaitingRefreshHandler());

    try {
      client.setUseInsecureSSL(true);
    } catch (GeneralSecurityException e) {
      throw new WebDriverException(e);
    }

    // Ensure that we've set the proxy if necessary
    if (proxyConfig != null) {
      client.setProxyConfig(proxyConfig);
    }

    return modifyWebClient(client);
  }

  /**
   * 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(BrowserVersion version) {
    return new WebClient(version);
  }

  /**
   * Child classes can override this method to customise the webclient that the HtmlUnit driver
   * uses.
   * 
   * @param client The client to modify
   * @return The modified client
   */
  protected WebClient modifyWebClient(WebClient client) {
    // Does nothing here to be overridden.
    return client;
  }

  public void setProxy(String host, int port) {
    proxyConfig = new ProxyConfig(host, port);
    webClient.setProxyConfig(proxyConfig);
  }

  public void setAutoProxy(String autoProxyUrl) {
    proxyConfig = new ProxyConfig();
    proxyConfig.setProxyAutoConfigUrl(autoProxyUrl);
    webClient.setProxyConfig(proxyConfig);
  }

  public Capabilities getCapabilities() {
    DesiredCapabilities capabilities = DesiredCapabilities.htmlUnit();

    capabilities.setPlatform(Platform.getCurrent());
    capabilities.setJavascriptEnabled(isJavascriptEnabled());
    capabilities.setCapability(SUPPORTS_FINDING_BY_CSS, true);

    return capabilities;
  }

  public void get(String url) {
    // Prevent the malformed url exception.
    if (WebClient.URL_ABOUT_BLANK.toString().equals(url)) {
      get(WebClient.URL_ABOUT_BLANK);
      return;
    }

    URL fullUrl;
    try {
      fullUrl = new URL(url);
    } catch (Exception e) {
      throw new WebDriverException(e);
    }

    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(URL fullUrl) {
    try {
      // A "get" works over the entire page
      currentWindow = currentWindow.getTopWindow();
      webClient.getPage(fullUrl);
    } catch (UnknownHostException e) {
      // This should be fine
    } catch (ConnectException e) {
      // This might be expected
    } catch (Exception e) {
      throw new WebDriverException(e);
    }

    gotPage = true;
    pickWindow();
    resetKeyboardAndMouseState();
  }

  private void resetKeyboardAndMouseState() {
    keyboard = new HtmlUnitKeyboard(this);
    mouse = new HtmlUnitMouse(this, keyboard);
  }

  protected void pickWindow() {
    // TODO(simon): HtmlUnit tries to track the current window as the frontmost. We don't
    if (currentWindow == null) {
      currentWindow = webClient.getCurrentWindow();
    }
  }

  public String getCurrentUrl() {
    // TODO(simon): Blech. I can see this being baaad
    URL url = getRawUrl();
    if (url == null) {
      return null;
    }

    return url.toString();
  }

  public String getTitle() {
    Page page = lastPage();
    if (page == null || !(page instanceof HtmlPage)) {
      return null; // no page so there is no title
    }
    if (currentWindow instanceof FrameWindow) {
      page = ((FrameWindow) currentWindow).getTopWindow().getEnclosedPage();
    }

    return ((HtmlPage) page).getTitleText();
  }

  public WebElement findElement(By by) {
    return findElement(by, this);
  }

  public List<WebElement> findElements(By by) {
    return findElements(by, this);
  }

  public String getPageSource() {
    Page page = lastPage();
    if (page == null) {
      return null;
    }

    if (page instanceof SgmlPage) {
      return ((SgmlPage) page).asXml();
    }
    WebResponse response = page.getWebResponse();
    return response.getContentAsString();
  }

  public void close() {
    if (currentWindow != null) {
      ((TopLevelWindow) currentWindow.getTopWindow()).close();
    }
    if (webClient.getWebWindows().size() == 0) {
      quit();
    }
  }

  public void quit() {
    if (webClient != null) {
      webClient.closeAllWindows();
      webClient = null;
    }
    currentWindow = null;
  }

  public Set<String> getWindowHandles() {
    final Set<String> allHandles = new HashSet<String>();
    for (final WebWindow window : webClient.getTopLevelWindows()) {
      allHandles.add(String.valueOf(System.identityHashCode(window)));
    }

    return allHandles;
  }

  public String getWindowHandle() {
    WebWindow topWindow = currentWindow.getTopWindow();
    if (topWindow.isClosed()) {
      throw new NoSuchWindowException("Window is closed");
    }
    return String.valueOf(System.identityHashCode(topWindow));
  }

  public Object executeScript(String script, final Object... args) {
    HtmlPage page = getPageToInjectScriptInto();

    script = "function() {" + script + "\n};";
    ScriptResult result = page.executeJavaScript(script);
    Function func = (Function) result.getJavaScriptResult();

    final Scriptable scope = (Scriptable) page.getEnclosingWindow().getScriptObject();

    final Object[] parameters = new Object[args.length];
    final ContextAction action = new ContextAction() {
      public Object run(final Context context) {
        for (int i = 0; i < args.length; i++) {
          parameters[i] = parseArgumentIntoJavsacriptParameter(context, scope, args[i]);
        }
        return null;
      }
    };
    webClient.getJavaScriptEngine().getContextFactory().call(action);

    result = page.executeJavaScriptFunctionIfPossible(
        func,
        (ScriptableObject) currentWindow.getScriptObject(),
        parameters,
        page.getDocumentElement());

    return parseNativeJavascriptResult(result);
  }

  public Object executeAsyncScript(String script, Object... args) {
    HtmlPage page = getPageToInjectScriptInto();

    Object result = new AsyncScriptExecutor(page, scriptTimeout)
        .execute(script, args);

    return parseNativeJavascriptResult(result);
  }

  private HtmlPage getPageToInjectScriptInto() {
    if (!isJavascriptEnabled()) {
      throw new UnsupportedOperationException(
          "Javascript is not enabled for this HtmlUnitDriver instance");
    }

    final Page lastPage = lastPage();
    if (!(lastPage instanceof HtmlPage)) {
      throw new UnsupportedOperationException("Cannot execute JS against a plain text page");
    } else if (!gotPage) {
      // just to make ExecutingJavascriptTest.testShouldThrowExceptionIfExecutingOnNoPage happy
      // but does this limitation make sense?
      throw new WebDriverException("Can't execute JavaScript before a page has been loaded!");
    }

    return (HtmlPage) lastPage;
  }

  private Object parseArgumentIntoJavsacriptParameter(
      Context context, Scriptable scope, Object arg) {
    while (arg instanceof WrapsElement) {
      arg = ((WrapsElement) arg).getWrappedElement();
    }
{'fraction_non_alphanumeric': 0.09165865454651399, 'fraction_numerical': 0.003030038166826909, 'mean_word_length': 3.7685468185607114, 'pattern_counts': {'":': 2, '<': 13, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4440973', 'n_tokens_mistral': 10038, 'n_tokens_neox': 9708, 'n_words': 2445}