/* * Copyright (c) 2002-2008 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.protocol.javascript.JavaScriptURLConnection.JAVASCRIPT_PREFIX; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.util.EncodingUtil; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.mozilla.javascript.Context; import org.mozilla.javascript.Function; import org.mozilla.javascript.Script; import org.mozilla.javascript.Scriptable; import org.w3c.dom.Attr; import org.w3c.dom.CDATASection; import org.w3c.dom.Comment; import org.w3c.dom.DOMConfiguration; import org.w3c.dom.DOMException; import org.w3c.dom.DOMImplementation; import org.w3c.dom.Document; import org.w3c.dom.DocumentFragment; import org.w3c.dom.DocumentType; import org.w3c.dom.Element; import org.w3c.dom.EntityReference; import org.w3c.dom.NodeList; import org.w3c.dom.ProcessingInstruction; import org.w3c.dom.Text; import com.gargoylesoftware.htmlunit.Cache; import com.gargoylesoftware.htmlunit.ElementNotFoundException; import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException; import com.gargoylesoftware.htmlunit.OnbeforeunloadHandler; import com.gargoylesoftware.htmlunit.Page; import com.gargoylesoftware.htmlunit.ScriptException; import com.gargoylesoftware.htmlunit.ScriptResult; import com.gargoylesoftware.htmlunit.SgmlPage; import com.gargoylesoftware.htmlunit.TextUtil; import com.gargoylesoftware.htmlunit.WebAssert; import com.gargoylesoftware.htmlunit.WebClient; import com.gargoylesoftware.htmlunit.WebRequestSettings; import com.gargoylesoftware.htmlunit.WebResponse; import com.gargoylesoftware.htmlunit.WebWindow; import com.gargoylesoftware.htmlunit.html.HTMLParser.HtmlUnitDOMBuilder; import com.gargoylesoftware.htmlunit.javascript.JavaScriptEngine; import com.gargoylesoftware.htmlunit.javascript.host.Event; import com.gargoylesoftware.htmlunit.javascript.host.Node; import com.gargoylesoftware.htmlunit.javascript.host.Window; /** * A representation of an HTML page returned from a server. *
* This class provides different methods to access the page's content like * {@link #getForms()}, {@link #getAnchors()}, {@link #getElementById(String)}, ... as well as the * very powerful inherited methods {@link #getByXPath(String)} and {@link #getFirstByXPath(String)} * for fine grained user specific access to child nodes. *
** Child elements allowing user interaction provide methods for this purpose like {@link HtmlAnchor#click()}, * {@link HtmlInput#type(String)}, {@link HtmlOption#setSelected(boolean)}, ... *
*
* HtmlPage instances should not be instantiated directly. They will be returned by {@link WebClient#getPage(String)}
* when the content type of the server's response is text/html (or one of its variations).
*
* Example:
*
*
* final HtmlPage page =
* (HtmlPage) webClient.{@link WebClient#getPage(String) getPage}("http://mywebsite/some/page.html");
*
*
* * The rules for determining tab order are as follows: *
* * The following elements support the tabindex attribute: A, AREA, BUTTON, * INPUT, OBJECT, SELECT, and TEXTAREA.
*
* @return all the tabbable elements in proper tab order
*/
public List
*
* Only the following HTML elements may have accesskeys defined: A, AREA,
* BUTTON, INPUT, LABEL, LEGEND, and TEXTAREA.
*
* @param accessKey the key to look for
* @return the HTML element that is assigned to the specified key or null
* if no elements can be found that match the specified key.
*/
public HtmlElement getHtmlElementByAccessKey(final char accessKey) {
final List
*
* The HTML specification seems to indicate that one accesskey cannot be used
* for multiple elements however Internet Explorer does seem to support this.
* It's worth noting that Mozilla does not support multiple elements with one
* access key so you are making your HTML browser specific if you rely on this
* feature.
*
* Only the following HTML elements may have accesskeys defined: A, AREA,
* BUTTON, INPUT, LABEL, LEGEND, and TEXTAREA.
*
* @param accessKey the key to look for
* @return the elements that are assigned to the specified accesskey
*/
public List
* Note: the provided code won't be executed if JavaScript has been disabled on the WebClient
* (see {@link WebClient#isJavaScriptEnabled()}.
* @param sourceCode the JavaScript code to execute
* @return a ScriptResult which will contain both the current page (which may be different than
* the previous page) and a JavaScript result object
*/
public ScriptResult executeJavaScript(final String sourceCode) {
return executeJavaScriptIfPossible(sourceCode, "injected script", 1);
}
/**
* INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.
* Execute the specified JavaScript if a JavaScript engine was successfully
* instantiated. If this JavaScript causes the current page to be reloaded
* (through location="" or form.submit()) then return the new page. Otherwise
* return the current page.
* Please note: Although this method is public, it is not intended for
* general execution of JavaScript. Users of HtmlUnit should interact with the pages
* as a user would by clicking on buttons or links and having the JavaScript event
* handlers execute as needed..
*
* Parses the given string as would it belong to the content being parsed
* at the current parsing position
*
*
*
* Execute a Function in the given context.
*
* @param function the JavaScript Function to call
* @param thisObject the "this" object to be used during invocation
* @param args the arguments to pass into the call
* @param htmlElementScope the HTML element for which this script is being executed
* This element will be the context during the JavaScript execution. If null,
* the context will default to the page.
* @return a ScriptResult which will contain both the current page (which may be different than
* the previous page and a JavaScript result object.
*/
public ScriptResult executeJavaScriptFunctionIfPossible(final Function function, final Scriptable thisObject,
final Object[] args, final DomNode htmlElementScope) {
if (!getWebClient().isJavaScriptEnabled()) {
return new ScriptResult(null, this);
}
final JavaScriptEngine engine = getWebClient().getJavaScriptEngine();
final Object result = engine.callFunction(this, function, thisObject, args, htmlElementScope);
return new ScriptResult(result, getWebClient().getCurrentWindow().getEnclosedPage());
}
/**
* Returns the log object for this element.
* @return the log object for this element
*/
protected Log getJsLog() {
return javascriptLog_;
}
/**
* INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.
*
* @param srcAttribute the source attribute from the script tag
* @param charset the charset attribute from the script tag
*/
void loadExternalJavaScriptFile(final String srcAttribute, final String charset) {
if (getWebClient().isJavaScriptEnabled()) {
final URL scriptURL;
try {
scriptURL = getFullyQualifiedUrl(srcAttribute);
if (scriptURL.getProtocol().equals("javascript")) {
if (mainLog_.isInfoEnabled()) {
mainLog_.info("Ignoring script src [" + srcAttribute + "]");
}
return;
}
}
catch (final MalformedURLException e) {
if (mainLog_.isErrorEnabled()) {
mainLog_.error("Unable to build URL for script src tag [" + srcAttribute + "]");
}
if (getWebClient().isThrowExceptionOnScriptError()) {
throw new ScriptException(this, e);
}
return;
}
final Script script = loadJavaScriptFromUrl(scriptURL, charset);
if (script != null) {
getWebClient().getJavaScriptEngine().execute(this, script);
}
}
}
/**
* INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.
*
* Return true if a script with the specified type and language attributes
* is actually JavaScript.
* According to W3C recommendation
* are content types case insensitive.
* @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 String typeAttribute, final String languageAttribute) {
// Unless otherwise specified, we have to assume that any script is JavaScript
final boolean isJavaScript;
if (languageAttribute != null && languageAttribute.length() != 0) {
isJavaScript = TextUtil.startsWithIgnoreCase(languageAttribute, "javascript");
}
else if (typeAttribute != null && typeAttribute.length() != 0) {
isJavaScript = typeAttribute.equalsIgnoreCase("text/javascript");
}
else {
isJavaScript = true;
}
return isJavaScript;
}
/**
* Loads JavaScript from the specified URL. This method may return null if
* there is a problem loading the code from the specified URL.
*
* @param url the URL of the script
* @param charset the charset to use to read the text
* @return the content of the file
*/
private Script loadJavaScriptFromUrl(final URL url, final String charset) {
String scriptEncoding = charset;
getPageEncoding();
final WebClient client = getWebClient();
final Cache cache = client.getCache();
final WebRequestSettings request = new WebRequestSettings(url);
final Script cachedScript = cache.getCachedScript(request);
if (cachedScript != null) {
return cachedScript;
}
WebResponse response;
try {
response = client.loadWebResponse(request);
}
catch (final IOException e) {
if (mainLog_.isErrorEnabled()) {
mainLog_.error("Error loading JavaScript from [" + url.toExternalForm() + "].", e);
}
return null;
}
client.printContentIfNecessary(response);
client.throwFailingHttpStatusCodeExceptionIfNecessary(response);
final int statusCode = response.getStatusCode();
final boolean successful = (statusCode >= HttpStatus.SC_OK && statusCode < HttpStatus.SC_MULTIPLE_CHOICES);
if (!successful) {
return null;
}
final String contentType = response.getContentType();
if (!contentType.equalsIgnoreCase("text/javascript")
&& !contentType.equalsIgnoreCase("application/x-javascript")) {
if (mainLog_.isWarnEnabled()) {
mainLog_.warn("Expected content type of 'text/javascript' or 'application/x-javascript' for "
+ "remotely loaded JavaScript element at '" + url + "', "
+ "but got '" + contentType + "'.");
}
}
if (StringUtils.isEmpty(scriptEncoding)) {
final String contentCharset = response.getContentCharSet();
if (!contentCharset.equals(TextUtil.DEFAULT_CHARSET)) {
scriptEncoding = contentCharset;
}
else if (!originalCharset_.equals(TextUtil.DEFAULT_CHARSET)) {
scriptEncoding = originalCharset_;
}
else {
scriptEncoding = TextUtil.DEFAULT_CHARSET;
}
}
final byte[] data = response.getResponseBody();
final String scriptCode = EncodingUtil.getString(data, 0, data.length, scriptEncoding);
final JavaScriptEngine javaScriptEngine = client.getJavaScriptEngine();
final Script script = javaScriptEngine.compile(this, scriptCode, url.toExternalForm(), 1);
cache.cacheIfPossible(request, response, script);
return script;
}
/**
* Returns the title of this page or an empty string if the title wasn't specified.
*
* @return the title of this page or an empty string if the title wasn't specified
*/
public String getTitleText() {
final HtmlTitle titleElement = getTitleElement();
if (titleElement != null) {
return titleElement.asText();
}
return "";
}
/**
* Sets the text for the title of this page. If there is not a title element
* on this page, then one has to be generated.
* @param message the new text
*/
public void setTitleText(final String message) {
HtmlTitle titleElement = getTitleElement();
if (titleElement == null) {
if (mainLog_.isDebugEnabled()) {
mainLog_.debug("No title element, creating one");
}
final HtmlHead head = (HtmlHead) getFirstChildElement(getDocumentElement(), HtmlHead.class);
if (head == null) {
// perhaps should we create head too?
throw new IllegalStateException("Headelement was not defined for this page");
}
final Mapnull if no child found
*/
private HtmlElement getFirstChildElement(final HtmlElement startElement, final Class< ? > clazz) {
for (final HtmlElement element : startElement.getChildElements()) {
if (clazz.isInstance(element)) {
return element;
}
}
return null;
}
/**
* Gets the title element for this page. Returns null if one is not found.
*
* @return the title element for this page or null if this is not one
*/
private HtmlTitle getTitleElement() {
final HtmlHead head = (HtmlHead) getFirstChildElement(getDocumentElement(), HtmlHead.class);
if (head != null) {
return (HtmlTitle) getFirstChildElement(head, HtmlTitle.class);
}
return null;
}
/**
* Look for and execute any appropriate event handlers. Look for body
* and frame tags.
* @param eventType either {@link Event#TYPE_LOAD}, {@link Event#TYPE_UNLOAD}, or {@link Event#TYPE_BEFORE_UNLOAD}
* @return true if user accepted onbeforeunload (not relevant to other events)
*/
private boolean executeEventHandlersIfNeeded(final String eventType) {
// If JavaScript isn't enabled, there's nothing for us to do.
if (!getWebClient().isJavaScriptEnabled()) {
return true;
}
// Execute the specified event on the document element.
final WebWindow window = getEnclosingWindow();
final Window jsWindow = (Window) window.getScriptObject();
if (jsWindow != null) {
final HtmlElement element = getDocumentElement();
final Event event = new Event(element, eventType);
element.fireEvent(event);
if (!isOnbeforeunloadAccepted(this, event)) {
return false;
}
}
// If this page was loaded in a frame, execute the version of the event specified on the frame tag.
if (window instanceof FrameWindow) {
final FrameWindow fw = (FrameWindow) window;
final BaseFrame frame = fw.getFrameElement();
final Function frameTagEventHandler = frame.getEventHandler("on" + eventType);
if (frameTagEventHandler != null) {
if (mainLog_.isDebugEnabled()) {
mainLog_.debug("Executing on" + eventType + " handler for " + frame);
}
final Event event = new Event(frame, eventType);
((Node) frame.getScriptObject()).executeEvent(event);
if (!isOnbeforeunloadAccepted((HtmlPage) frame.getPage(), event)) {
return false;
}
}
}
return true;
}
private boolean isOnbeforeunloadAccepted(final HtmlPage page, final Event event) {
if (event.jsxGet_type().equals(Event.TYPE_BEFORE_UNLOAD) && event.jsxGet_returnValue() != null) {
final OnbeforeunloadHandler handler = getWebClient().getOnbeforeunloadHandler();
if (handler == null) {
if (mainLog_.isWarnEnabled()) {
mainLog_.warn("document.onbeforeunload() returned a string in event.returnValue,"
+ " but no onbeforeunload handler installed.");
}
}
else {
final String message = Context.toString(event.jsxGet_returnValue());
return handler.handleEvent(page, message);
}
}
return true;
}
/**
* If a refresh has been specified either through a meta tag or an HTTP
* response header, then perform that refresh.
* @throws IOException if an IO problem occurs
*/
private void executeRefreshIfNeeded() throws IOException {
// If this page is not in a frame then a refresh has already happened,
// most likely through the JavaScript onload handler, so we don't do a
// second refresh.
final WebWindow window = getEnclosingWindow();
if (window == null) {
return;
}
final String refreshString = getRefreshStringOrNull();
if (refreshString == null || refreshString.length() == 0) {
return;
}
final int time;
final URL url;
int index = refreshString.indexOf(";");
final boolean timeOnly = (index == -1);
if (timeOnly) {
// Format:
try {
time = Integer.parseInt(refreshString);
}
catch (final NumberFormatException e) {
if (mainLog_.isErrorEnabled()) {
mainLog_.error("Malformed refresh string (no ';' but not a number): " + refreshString, e);
}
return;
}
url = getWebResponse().getUrl();
}
else {
// Format:
try {
time = Integer.parseInt(refreshString.substring(0, index).trim());
}
catch (final NumberFormatException e) {
if (mainLog_.isErrorEnabled()) {
mainLog_.error("Malformed refresh string (no valid number before ';') " + refreshString, e);
}
return;
}
index = refreshString.toLowerCase().indexOf("url=", index);
if (index == -1) {
if (mainLog_.isErrorEnabled()) {
mainLog_.error("Malformed refresh string (found ';' but no 'url='): " + refreshString);
}
return;
}
final StringBuilder buffer = new StringBuilder(refreshString.substring(index + 4));
if (buffer.toString().trim().length() == 0) {
//content='10; URL=' is treated as content='10'
url = getWebResponse().getUrl();
}
else {
if (buffer.charAt(0) == '"' || buffer.charAt(0) == 0x27) {
buffer.deleteCharAt(0);
}
if (buffer.charAt(buffer.length() - 1) == '"' || buffer.charAt(buffer.length() - 1) == 0x27) {
buffer.deleteCharAt(buffer.length() - 1);
}
final String urlString = buffer.toString();
try {
url = getFullyQualifiedUrl(urlString);
}
catch (final MalformedURLException e) {
if (mainLog_.isErrorEnabled()) {
mainLog_.error("Malformed URL in refresh string: " + refreshString, e);
}
throw e;
}
}
}
getWebClient().getRefreshHandler().handleRefresh(this, url, time);
}
/**
* Returns an auto-refresh string if specified. This will look in both the meta
* tags (taking care of <noscript> if any) and inside the HTTP response headers.
* @return the auto-refresh string
*/
private String getRefreshStringOrNull() {
final boolean javaScriptEnabled = getWebClient().isJavaScriptEnabled();
for (final HtmlMeta meta : getMetaTags("refresh")) {
if ((!javaScriptEnabled || getFirstParent(meta, HtmlNoScript.TAG_NAME) == null)) {
return meta.getContentAttribute();
}
}
return getWebResponse().getResponseHeaderValue("Refresh");
}
/**
* Executes any deferred scripts, if necessary.
*/
private void executeDeferredScriptsIfNeeded() {
if (!getWebClient().isJavaScriptEnabled()) {
return;
}
if (!getWebClient().getBrowserVersion().isIE()) {
return;
}
final HtmlElement doc = getDocumentElement();
final List
*
* @param node the node that has just been added to the document
*/
void notifyNodeAdded(final DomNode node) {
if (node instanceof HtmlElement) {
boolean insideNoScript = false;
if (getWebClient().isJavaScriptEnabled()) {
for (DomNode parent = node.getParentNode(); parent != null; parent = parent.getParentNode()) {
if (parent instanceof HtmlNoScript) {
insideNoScript = true;
break;
}
}
}
if (!insideNoScript) {
addMappedElement((HtmlElement) node, true);
}
}
node.onAddedToPage();
}
/**
* INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.
*
* @param node the node that has just been removed from the tree
*/
void notifyNodeRemoved(final DomNode node) {
if (node instanceof HtmlElement) {
removeMappedElement((HtmlElement) node, true, true);
}
}
/**
* Loads the content of the contained frames. This is done after the page is completely loaded, to allow script
* contained in the frames to reference elements from the page located after the closing </frame> tag.
* @throws FailingHttpStatusCodeException if the server returns a failing status code AND the property
* {@link WebClient#setThrowExceptionOnFailingStatusCode(boolean)} is set to true
*/
void loadFrames() throws FailingHttpStatusCodeException {
for (final FrameWindow w : getFrames()) {
final BaseFrame frame = w.getFrameElement();
// test if the frame should really be loaded:
// if a script has already changed its content, it should be skipped
// use == and not equals(...) to identify initial content (versus URL set to "about:blank")
if (frame.getEnclosedPage().getWebResponse().getUrl() == WebClient.URL_ABOUT_BLANK) {
frame.loadInnerPage();
}
}
}
/**
* {@inheritDoc}
*/
@Override
public String asXml() {
return getDocumentElement().asXml();
}
/**
* Gives a basic representation for debugging purposes.
* @return a basic representation
*/
@Override
public String toString() {
final StringBuilder buffer = new StringBuilder();
buffer.append("HtmlPage(");
buffer.append(getWebResponse().getUrl());
buffer.append(")@");
buffer.append(hashCode());
return buffer.toString();
}
/**
* Moves the focus to the specified component. This will trigger any relevant JavaScript
* event handlers.
*
* @param newElement the element that will receive the focus, use null to remove focus from any element
* @return true if the specified element now has the focus
* @see #getElementWithFocus()
* @see #tabToNextElement()
* @see #tabToPreviousElement()
* @see #pressAccessKey(char)
* @see WebAssert#assertAllTabIndexAttributesSet(HtmlPage)
* @deprecated As of 2.0, please use {@link #setFocusedElement(HtmlElement)} instead.
*/
@Deprecated
public boolean moveFocusToElement(final HtmlElement newElement) {
return setFocusedElement(newElement);
}
/**
* Moves the focus to the specified element. This will trigger any relevant JavaScript
* event handlers.
*
* @param newElement the element that will receive the focus, use null to remove focus from any element
* @return true if the specified element now has the focus
* @see #getFocusedElement()
* @see #tabToNextElement()
* @see #tabToPreviousElement()
* @see #pressAccessKey(char)
* @see WebAssert#assertAllTabIndexAttributesSet(HtmlPage)
*/
public boolean setFocusedElement(final HtmlElement newElement) {
return setFocusedElement(newElement, false);
}
/**
* Moves the focus to the specified element. This will trigger any relevant JavaScript
* event handlers.
*
* @param newElement the element that will receive the focus, use null to remove focus from any element
* @param windowActivated - whether the enclosing window got focus resulting in specified element getting focus
* @return true if the specified element now has the focus
* @see #getFocusedElement()
* @see #tabToNextElement()
* @see #tabToPreviousElement()
* @see #pressAccessKey(char)
* @see WebAssert#assertAllTabIndexAttributesSet(HtmlPage)
*/
public boolean setFocusedElement(final HtmlElement newElement, final boolean windowActivated) {
if (elementWithFocus_ == newElement && (!windowActivated)) {
// nothing to do
return true;
}
else if (newElement != null && newElement.getPage() != this) {
throw new IllegalArgumentException("Can't move focus to an element from an other page");
}
if (!windowActivated) {
if (elementWithFocus_ != null) {
elementWithFocus_.fireEvent(Event.TYPE_FOCUS_OUT);
}
if (newElement != null) {
newElement.fireEvent(Event.TYPE_FOCUS_IN);
}
if (elementWithFocus_ != null) {
if (getWebClient().getBrowserVersion().hasProperty("blurBeforeOnchange")) {
elementWithFocus_.fireEvent(Event.TYPE_BLUR);
elementWithFocus_.removeFocus();
}
else { // IE, FF3
elementWithFocus_.removeFocus();
elementWithFocus_.fireEvent(Event.TYPE_BLUR);
}
}
}
elementWithFocus_ = newElement;
if (newElement != null) {
elementWithFocus_.focus();
newElement.fireEvent(Event.TYPE_FOCUS);
}
// If a page reload happened as a result of the focus change then obviously this
// element will not have the focus because its page has gone away.
return this == getEnclosingWindow().getEnclosedPage();
}
/**
* Returns the element with the focus or null if no element has the focus.
* @return the element with focus or null
* @see #moveFocusToElement(HtmlElement)
* @deprecated As of 2.0, please use {@link #getFocusedElement()} instead.
*/
@Deprecated
public HtmlElement getElementWithFocus() {
return getFocusedElement();
}
/**
* Returns the element with the focus or null if no element has the focus.
* @return the element with focus or null
* @see #setFocusedElement(HtmlElement)
*/
public HtmlElement getFocusedElement() {
return elementWithFocus_;
}
/**
* Gets the meta tag for a given http-equiv value.
* @param httpEquiv the http-equiv value
* @return a list of {@link HtmlMeta}
*/
protected List
*
* @return true if the OnbeforeunloadHandler has accepted to change the page
*/
public boolean isOnbeforeunloadAccepted() {
return executeEventHandlersIfNeeded(Event.TYPE_BEFORE_UNLOAD);
}
/**
* Returns true if an HTML parser is operating on this page, adding content to it.
* @return true if an HTML parser is operating on this page, adding content to it
*/
public boolean isBeingParsed() {
return parserCount_ > 0;
}
/**
* Called by the HTML parser to let the page know that it has started parsing some content for this page.
*/
void registerParsingStart() {
parserCount_++;
}
/**
* Called by the HTML parser to let the page know that it has finished parsing some content for this page.
*/
void registerParsingEnd() {
parserCount_--;
}
/**
* Refreshes the page by sending the same parameters as previously sent to get this page.
* @return the newly loaded page.
* @throws IOException if an IO problem occurs
*/
public Page refresh() throws IOException {
return getWebClient().getPage(getWebResponse().getRequestSettings());
}
/**
* INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.
*