com.gargoylesoftware.htmlunit.javascript.host.Window Maven / Gradle / Ivy
/*
* Copyright (c) 2002-2016 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.javascript.host;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_WINDOW_CHANGE_OPENER_ONLY_WINDOW_OBJECT;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_WINDOW_FORMFIELDS_ACCESSIBLE_BY_NAME;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_WINDOW_FRAMES_ACCESSIBLE_BY_ID;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_WINDOW_FRAME_BY_ID_RETURNS_WINDOW;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_WINDOW_SELECTION_NULL_IF_INVISIBLE;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_WINDOW_TOP_WRITABLE;
import static com.gargoylesoftware.htmlunit.javascript.configuration.BrowserName.CHROME;
import static com.gargoylesoftware.htmlunit.javascript.configuration.BrowserName.EDGE;
import static com.gargoylesoftware.htmlunit.javascript.configuration.BrowserName.FF;
import static com.gargoylesoftware.htmlunit.javascript.configuration.BrowserName.IE;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.gargoylesoftware.htmlunit.AlertHandler;
import com.gargoylesoftware.htmlunit.ConfirmHandler;
import com.gargoylesoftware.htmlunit.DialogWindow;
import com.gargoylesoftware.htmlunit.ElementNotFoundException;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.PromptHandler;
import com.gargoylesoftware.htmlunit.ScriptException;
import com.gargoylesoftware.htmlunit.ScriptResult;
import com.gargoylesoftware.htmlunit.SgmlPage;
import com.gargoylesoftware.htmlunit.StatusHandler;
import com.gargoylesoftware.htmlunit.StorageHolder.Type;
import com.gargoylesoftware.htmlunit.TopLevelWindow;
import com.gargoylesoftware.htmlunit.WebAssert;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebWindow;
import com.gargoylesoftware.htmlunit.WebWindowNotFoundException;
import com.gargoylesoftware.htmlunit.html.BaseFrameElement;
import com.gargoylesoftware.htmlunit.html.DomChangeEvent;
import com.gargoylesoftware.htmlunit.html.DomChangeListener;
import com.gargoylesoftware.htmlunit.html.DomElement;
import com.gargoylesoftware.htmlunit.html.DomNode;
import com.gargoylesoftware.htmlunit.html.FrameWindow;
import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
import com.gargoylesoftware.htmlunit.html.HtmlAttributeChangeEvent;
import com.gargoylesoftware.htmlunit.html.HtmlAttributeChangeListener;
import com.gargoylesoftware.htmlunit.html.HtmlButton;
import com.gargoylesoftware.htmlunit.html.HtmlElement;
import com.gargoylesoftware.htmlunit.html.HtmlEmbed;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlFrame;
import com.gargoylesoftware.htmlunit.html.HtmlImage;
import com.gargoylesoftware.htmlunit.html.HtmlInput;
import com.gargoylesoftware.htmlunit.html.HtmlLink;
import com.gargoylesoftware.htmlunit.html.HtmlMap;
import com.gargoylesoftware.htmlunit.html.HtmlObject;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.html.HtmlSelect;
import com.gargoylesoftware.htmlunit.html.HtmlStyle;
import com.gargoylesoftware.htmlunit.html.HtmlTextArea;
import com.gargoylesoftware.htmlunit.javascript.JavaScriptEngine;
import com.gargoylesoftware.htmlunit.javascript.PostponedAction;
import com.gargoylesoftware.htmlunit.javascript.ScriptableWithFallbackGetter;
import com.gargoylesoftware.htmlunit.javascript.SimpleScriptable;
import com.gargoylesoftware.htmlunit.javascript.background.BackgroundJavaScriptFactory;
import com.gargoylesoftware.htmlunit.javascript.background.JavaScriptJob;
import com.gargoylesoftware.htmlunit.javascript.configuration.CanSetReadOnly;
import com.gargoylesoftware.htmlunit.javascript.configuration.CanSetReadOnlyStatus;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxClass;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxConstant;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxConstructor;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxFunction;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxGetter;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxSetter;
import com.gargoylesoftware.htmlunit.javascript.configuration.WebBrowser;
import com.gargoylesoftware.htmlunit.javascript.host.crypto.Crypto;
import com.gargoylesoftware.htmlunit.javascript.host.css.CSS2Properties;
import com.gargoylesoftware.htmlunit.javascript.host.css.CSSStyleDeclaration;
import com.gargoylesoftware.htmlunit.javascript.host.css.CSSStyleSheet;
import com.gargoylesoftware.htmlunit.javascript.host.css.MediaQueryList;
import com.gargoylesoftware.htmlunit.javascript.host.css.StyleMedia;
import com.gargoylesoftware.htmlunit.javascript.host.css.StyleSheetList;
import com.gargoylesoftware.htmlunit.javascript.host.dom.Document;
import com.gargoylesoftware.htmlunit.javascript.host.dom.Node;
import com.gargoylesoftware.htmlunit.javascript.host.dom.Selection;
import com.gargoylesoftware.htmlunit.javascript.host.event.Event;
import com.gargoylesoftware.htmlunit.javascript.host.event.EventTarget;
import com.gargoylesoftware.htmlunit.javascript.host.event.MessageEvent;
import com.gargoylesoftware.htmlunit.javascript.host.html.DataTransfer;
import com.gargoylesoftware.htmlunit.javascript.host.html.DocumentProxy;
import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLBodyElement;
import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLCollection;
import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLDocument;
import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLElement;
import com.gargoylesoftware.htmlunit.javascript.host.performance.Performance;
import com.gargoylesoftware.htmlunit.javascript.host.speech.SpeechSynthesis;
import com.gargoylesoftware.htmlunit.javascript.host.xml.XMLDocument;
import com.gargoylesoftware.htmlunit.svg.SvgPage;
import com.gargoylesoftware.htmlunit.xml.XmlPage;
import net.sourceforge.htmlunit.corejs.javascript.Context;
import net.sourceforge.htmlunit.corejs.javascript.ContextAction;
import net.sourceforge.htmlunit.corejs.javascript.ContextFactory;
import net.sourceforge.htmlunit.corejs.javascript.Function;
import net.sourceforge.htmlunit.corejs.javascript.FunctionObject;
import net.sourceforge.htmlunit.corejs.javascript.Scriptable;
import net.sourceforge.htmlunit.corejs.javascript.ScriptableObject;
import net.sourceforge.htmlunit.corejs.javascript.Undefined;
/**
* A JavaScript object for {@code Window}.
*
* @author Mike Bowler
* @author Chen Jun
* @author David K. Taylor
* @author Christian Sell
* @author Darrell DeBoer
* @author Marc Guillemot
* @author Dierk Koenig
* @author Daniel Gredler
* @author David D. Kilzer
* @author Chris Erskine
* @author Ahmed Ashour
* @author Ronald Brill
* @author Frank Danek
* @see MSDN documentation
*/
@JsxClass
public class Window extends EventTarget implements ScriptableWithFallbackGetter, Function {
private static final Log LOG = LogFactory.getLog(Window.class);
/** To be documented. */
@JsxConstant(@WebBrowser(CHROME))
public static final short TEMPORARY = 0;
/** To be documented. */
@JsxConstant(@WebBrowser(CHROME))
public static final short PERSISTENT = 1;
/**
* The minimum delay that can be used with setInterval() or setTimeout(). Browser minimums are
* usually in the 10ms to 15ms range, but there's really no reason for us to waste that much time.
* http://jsninja.com/Timers#Minimum_Timer_Delay_and_Reliability
*/
private static final int MIN_TIMER_DELAY = 1;
private Document document_;
private DocumentProxy documentProxy_;
private Navigator navigator_;
private WebWindow webWindow_;
private WindowProxy windowProxy_;
private Screen screen_;
private History history_;
private Location location_;
private ScriptableObject console_;
private ApplicationCache applicationCache_;
private Selection selection_;
private Event currentEvent_;
private String status_ = "";
private HTMLCollection frames_; // has to be a member to have equality (==) working
private Map, Scriptable> prototypes_ = new HashMap<>();
private Map prototypesPerJSName_ = new HashMap<>();
private Object controllers_;
private Object opener_;
private Object top_ = NOT_FOUND; // top can be set from JS to any value!
private Crypto crypto_;
/**
* Cache computed styles when possible, because their calculation is very expensive.
* We use a weak hash map because we don't want this cache to be the only reason
* nodes are kept around in the JVM, if all other references to them are gone.
*/
private transient WeakHashMap computedStyles_ = new WeakHashMap<>();
private final Map storages_ = new HashMap<>();
/**
* Creates an instance.
*/
@JsxConstructor({ @WebBrowser(CHROME), @WebBrowser(value = FF, minVersion = 38), @WebBrowser(EDGE) })
public Window() {
}
/**
* Restores the transient {@link #computedStyles_} map during deserialization.
* @param stream the stream to read the object from
* @throws IOException if an IO error occurs
* @throws ClassNotFoundException if a class is not found
*/
private void readObject(final ObjectInputStream stream) throws IOException, ClassNotFoundException {
stream.defaultReadObject();
computedStyles_ = new WeakHashMap<>();
}
/**
* Returns the prototype object corresponding to the specified HtmlUnit class inside the window scope.
* @param jsClass the class whose prototype is to be returned
* @return the prototype object corresponding to the specified class inside the specified scope
*/
@Override
public Scriptable getPrototype(final Class extends SimpleScriptable> jsClass) {
return prototypes_.get(jsClass);
}
/**
* Returns the prototype object corresponding to the specified HtmlUnit class inside the window scope.
* @param className the class name whose prototype is to be returned
* @return the prototype object corresponding to the specified class inside the specified scope
*/
public Scriptable getPrototype(final String className) {
return prototypesPerJSName_.get(className);
}
/**
* Sets the prototypes for HtmlUnit host classes.
* @param map a Map of ({@link Class}, {@link Scriptable})
* @param prototypesPerJSName map of {@link String} and {@link Scriptable}
*/
public void setPrototypes(final Map, Scriptable> map,
final Map prototypesPerJSName) {
prototypes_ = map;
prototypesPerJSName_ = prototypesPerJSName;
}
/**
* The JavaScript function {@code alert()}.
* @param message the message
*/
@JsxFunction
public void alert(final Object message) {
// use Object as parameter and perform String conversion by ourself
// this allows to place breakpoint here and "see" the message object and its properties
final String stringMessage = Context.toString(message);
final AlertHandler handler = getWebWindow().getWebClient().getAlertHandler();
if (handler == null) {
LOG.warn("window.alert(\"" + stringMessage + "\") no alert handler installed");
}
else {
handler.handleAlert(document_.getPage(), stringMessage);
}
}
/**
* Creates a base-64 encoded ASCII string from a string of binary data.
* @param stringToEncode string to encode
* @return the encoded string
*/
@JsxFunction
public String btoa(final String stringToEncode) {
return new String(Base64.encodeBase64(stringToEncode.getBytes()));
}
/**
* Decodes a string of data which has been encoded using base-64 encoding..
* @param encodedData the encoded string
* @return the decoded value
*/
@JsxFunction
public String atob(final String encodedData) {
return new String(Base64.decodeBase64(encodedData.getBytes()));
}
/**
* The JavaScript function "confirm()".
* @param message the message
* @return true if ok was pressed, false if cancel was pressed
*/
@JsxFunction
public boolean confirm(final String message) {
final ConfirmHandler handler = getWebWindow().getWebClient().getConfirmHandler();
if (handler == null) {
LOG.warn("window.confirm(\""
+ message + "\") no confirm handler installed, simulating the OK button");
return true;
}
return handler.handleConfirm(document_.getPage(), message);
}
/**
* The JavaScript function "prompt()".
* @param message the message
* @return true if ok was pressed, false if cancel was pressed
*/
@JsxFunction
public String prompt(final String message) {
final PromptHandler handler = getWebWindow().getWebClient().getPromptHandler();
if (handler == null) {
LOG.warn("window.prompt(\"" + message + "\") no prompt handler installed");
return null;
}
return handler.handlePrompt(document_.getPage(), message);
}
/**
* Returns the JavaScript property {@code document}.
* @return the document
*/
@JsxGetter(propertyName = "document")
public DocumentProxy getDocument_js() {
return documentProxy_;
}
/**
* Returns the window's current document.
* @return the window's current document
*/
public Document getDocument() {
return document_;
}
/**
* Returns the application cache.
* @return the application cache
*/
@JsxGetter
public ApplicationCache getApplicationCache() {
return applicationCache_;
}
/**
* Returns the current event (used by JavaScript only when emulating IE).
* @return the current event, or {@code null} if no event is currently available
*/
@JsxGetter({ @WebBrowser(IE), @WebBrowser(CHROME) })
public Object getEvent() {
return currentEvent_;
}
/**
* Returns the current event (used internally regardless of the emulation mode).
* @return the current event, or {@code null} if no event is currently available
*/
public Event getCurrentEvent() {
return currentEvent_;
}
/**
* Sets the current event.
* @param event the current event
*/
public void setCurrentEvent(final Event event) {
currentEvent_ = event;
}
/**
* Opens a new window.
*
* @param url when a new document is opened, url is a String that specifies a MIME type for the document.
* When a new window is opened, url is a String that specifies the URL to render in the new window
* @param name the name
* @param features the features
* @param replace whether to replace in the history list or no
* @return the newly opened window, or {@code null} if popup windows have been disabled
* @see com.gargoylesoftware.htmlunit.WebClientOptions#isPopupBlockerEnabled()
* @see MSDN documentation
*/
@JsxFunction
public WindowProxy open(final Object url, final Object name, final Object features,
final Object replace) {
String urlString = null;
if (url != Undefined.instance) {
urlString = Context.toString(url);
}
String windowName = "";
if (name != Undefined.instance) {
windowName = Context.toString(name);
}
String featuresString = null;
if (features != Undefined.instance) {
featuresString = Context.toString(features);
}
final WebClient webClient = getWebWindow().getWebClient();
if (webClient.getOptions().isPopupBlockerEnabled()) {
if (LOG.isDebugEnabled()) {
LOG.debug("Ignoring window.open() invocation because popups are blocked.");
}
return null;
}
boolean replaceCurrentEntryInBrowsingHistory = false;
if (replace != Undefined.instance) {
replaceCurrentEntryInBrowsingHistory = Context.toBoolean(replace);
}
if (featuresString != null || replaceCurrentEntryInBrowsingHistory) {
if (LOG.isDebugEnabled()) {
LOG.debug(
"window.open: features and replaceCurrentEntryInBrowsingHistory "
+ "not implemented: url=[" + urlString
+ "] windowName=[" + windowName
+ "] features=[" + featuresString
+ "] replaceCurrentEntry=[" + replaceCurrentEntryInBrowsingHistory
+ "]");
}
}
// if specified name is the name of an existing window, then hold it
if (StringUtils.isEmpty(urlString) && !"".equals(windowName)) {
try {
final WebWindow webWindow = webClient.getWebWindowByName(windowName);
return getProxy(webWindow);
}
catch (final WebWindowNotFoundException e) {
// nothing
}
}
final URL newUrl = makeUrlForOpenWindow(urlString);
final WebWindow newWebWindow = webClient.openWindow(newUrl, windowName, webWindow_);
return getProxy(newWebWindow);
}
private URL makeUrlForOpenWindow(final String urlString) {
if (urlString.isEmpty()) {
return WebClient.URL_ABOUT_BLANK;
}
try {
final Page page = getWebWindow().getEnclosedPage();
if (page != null && page.isHtmlPage()) {
return ((HtmlPage) page).getFullyQualifiedUrl(urlString);
}
return new URL(urlString);
}
catch (final MalformedURLException e) {
LOG.error("Unable to create URL for openWindow: relativeUrl=[" + urlString + "]", e);
return null;
}
}
/**
* Sets a chunk of JavaScript to be invoked at some specified time later.
* The invocation occurs only if the window is opened after the delay
* and does not contain an other page than the one that originated the setTimeout.
*
* @param code specifies the function pointer or string that indicates the code to be executed
* when the specified interval has elapsed
* @param timeout specifies the number of milliseconds
* @param language specifies language
* @return the id of the created timer
*/
@JsxFunction
public int setTimeout(final Object code, int timeout, final Object language) {
if (timeout < MIN_TIMER_DELAY) {
timeout = MIN_TIMER_DELAY;
}
if (code == null) {
throw Context.reportRuntimeError("Function not provided.");
}
final int id;
final WebWindow webWindow = getWebWindow();
final Page page = (Page) getDomNodeOrNull();
if (code instanceof String) {
final String s = (String) code;
final String description = "window.setTimeout(" + s + ", " + timeout + ")";
final JavaScriptJob job = BackgroundJavaScriptFactory.theFactory().
createJavaScriptJob(timeout, null, description, webWindow, s);
id = webWindow.getJobManager().addJob(job, page);
}
else if (code instanceof Function) {
final Function f = (Function) code;
final String functionName;
if (f instanceof FunctionObject) {
functionName = ((FunctionObject) f).getFunctionName();
}
else {
functionName = String.valueOf(f); // can this happen?
}
final String description = "window.setTimeout(" + functionName + ", " + timeout + ")";
final JavaScriptJob job = BackgroundJavaScriptFactory.theFactory().
createJavaScriptJob(timeout, null, description, webWindow, f);
id = webWindow.getJobManager().addJob(job, page);
}
else {
throw Context.reportRuntimeError("Unknown type for function.");
}
return id;
}
/**
* Cancels a time-out previously set with the setTimeout method.
*
* @param timeoutId identifier for the timeout to clear (returned by setTimeout)
*/
@JsxFunction
public void clearTimeout(final int timeoutId) {
if (LOG.isDebugEnabled()) {
LOG.debug("clearTimeout(" + timeoutId + ")");
}
getWebWindow().getJobManager().removeJob(timeoutId);
}
/**
* Returns the JavaScript property {@code navigator}.
* @return the navigator
*/
@JsxGetter
public Navigator getNavigator() {
return navigator_;
}
/**
* Returns the JavaScript property {@code clientInformation}.
* @return the client information
*/
@JsxGetter({ @WebBrowser(IE), @WebBrowser(CHROME) })
public Navigator getClientInformation() {
return navigator_;
}
/**
* Returns the JavaScript property {@code clipboardData}.
* @return the {@link DataTransfer}
*/
@JsxGetter(@WebBrowser(IE))
public DataTransfer getClipboardData() {
final DataTransfer dataTransfer = new DataTransfer();
dataTransfer.setParentScope(this);
dataTransfer.setPrototype(getPrototype(dataTransfer.getClass()));
return dataTransfer;
}
/**
* Returns the window property. This is a synonym for "self".
* @return the window property (a reference to this)
*/
@JsxGetter(propertyName = "window")
public WindowProxy getWindow_js() {
return windowProxy_;
}
/**
* Returns the {@code self} property.
* @return this
*/
@JsxGetter
public WindowProxy getSelf() {
return windowProxy_;
}
/**
* Returns the localStorage property.
* @return the localStorage property
*/
@JsxGetter
public Storage getLocalStorage() {
return getStorage(Type.LOCAL_STORAGE);
}
/**
* Returns the sessionStorage property.
* @return the sessionStorage property
*/
@JsxGetter
public Storage getSessionStorage() {
return getStorage(Type.SESSION_STORAGE);
}
/**
* Gets the storage of the specified type.
* @param storageType the type
* @return the storage
*/
public Storage getStorage(final Type storageType) {
Storage storage = storages_.get(storageType);
if (storage == null) {
final WebWindow webWindow = getWebWindow();
final Map store = webWindow.getWebClient().getStorageHolder().getStore(storageType,
webWindow.getEnclosedPage());
storage = new Storage(this, store);
storages_.put(storageType, storage);
}
return storage;
}
/**
* Returns the location property.
* @return the location property
*/
@JsxGetter
public Location getLocation() {
return location_;
}
/**
* Sets the location property. This will cause a reload of the window.
* @param newLocation the URL of the new content
* @throws IOException when location loading fails
*/
@JsxSetter
public void setLocation(final String newLocation) throws IOException {
location_.setHref(newLocation);
}
/**
* Returns the console property.
* @return the console property
*/
@JsxGetter
public ScriptableObject getConsole() {
return console_;
}
/**
* Sets the console.
* @param console the console
*/
@JsxSetter
public void setConsole(final ScriptableObject console) {
console_ = console;
}
/**
* Prints messages to the console.
* @param message the message to log
*/
@JsxFunction(@WebBrowser(FF))
public void dump(final String message) {
if (console_ instanceof Console) {
Console.log(null, console_, new Object[] {message}, null);
}
}
/**
* Dummy implementation for requestAnimationFrame.
* @param callback the function to call when it's time to update the animation
* @return an identification id
* @see MDN Doc
*/
@JsxFunction({ @WebBrowser(FF), @WebBrowser(value = IE, minVersion = 10), @WebBrowser(CHROME) })
public int requestAnimationFrame(final Object callback) {
// nothing for now
return 1;
}
/**
* Dummy implementation for cancelAnimationFrame.
* @param requestId the ID value returned by the call to window.requestAnimationFrame()
* @see MDN Doc
*/
@JsxFunction({ @WebBrowser(FF), @WebBrowser(value = IE, minVersion = 10), @WebBrowser(CHROME) })
public void cancelAnimationFrame(final Object requestId) {
// nothing for now
}
/**
* Returns the {@code screen} property.
* @return the screen property
*/
@JsxGetter
public Screen getScreen() {
return screen_;
}
/**
* Returns the {@code history} property.
* @return the {@code history} property
*/
@JsxGetter
public History getHistory() {
return history_;
}
/**
* Returns the {@code external} property.
* @return the {@code external} property
*/
@JsxGetter
public External getExternal() {
final External external = new External();
external.setParentScope(this);
external.setPrototype(getPrototype(external.getClass()));
return external;
}
/**
* Initializes this window.
* @param webWindow the web window corresponding to this window
*/
public void initialize(final WebWindow webWindow) {
webWindow_ = webWindow;
webWindow_.setScriptableObject(this);
windowProxy_ = new WindowProxy(webWindow_);
final Page enclosedPage = webWindow.getEnclosedPage();
if (enclosedPage instanceof XmlPage || enclosedPage instanceof SvgPage) {
document_ = new XMLDocument();
}
else {
document_ = new HTMLDocument();
}
document_.setParentScope(this);
document_.setPrototype(getPrototype(document_.getClass()));
document_.setWindow(this);
if (enclosedPage instanceof SgmlPage) {
final SgmlPage page = (SgmlPage) enclosedPage;
document_.setDomNode(page);
final DomHtmlAttributeChangeListenerImpl listener = new DomHtmlAttributeChangeListenerImpl();
page.addDomChangeListener(listener);
if (page.isHtmlPage()) {
((HtmlPage) page).addHtmlAttributeChangeListener(listener);
}
}
documentProxy_ = new DocumentProxy(webWindow_);
navigator_ = new Navigator();
navigator_.setParentScope(this);
navigator_.setPrototype(getPrototype(navigator_.getClass()));
screen_ = new Screen();
screen_.setParentScope(this);
screen_.setPrototype(getPrototype(screen_.getClass()));
history_ = new History();
history_.setParentScope(this);
history_.setPrototype(getPrototype(history_.getClass()));
location_ = new Location();
location_.setParentScope(this);
location_.setPrototype(getPrototype(location_.getClass()));
location_.initialize(this);
console_ = new Console();
((Console) console_).setWebWindow(webWindow_);
console_.setParentScope(this);
((Console) console_).setPrototype(getPrototype(((SimpleScriptable) console_).getClass()));
applicationCache_ = new ApplicationCache();
applicationCache_.setParentScope(this);
applicationCache_.setPrototype(getPrototype(applicationCache_.getClass()));
// like a JS new Object()
final Context ctx = Context.getCurrentContext();
controllers_ = ctx.newObject(this);
if (webWindow_ instanceof TopLevelWindow) {
final WebWindow opener = ((TopLevelWindow) webWindow_).getOpener();
if (opener != null) {
opener_ = opener.getScriptableObject();
}
}
}
/**
* Initialize the object.
* @param enclosedPage the page containing the JavaScript
*/
public void initialize(final Page enclosedPage) {
if (enclosedPage != null && enclosedPage.isHtmlPage()) {
final HtmlPage htmlPage = (HtmlPage) enclosedPage;
// Windows don't have corresponding DomNodes so set the domNode
// variable to be the page. If this isn't set then SimpleScriptable.get()
// won't work properly
setDomNode(htmlPage);
clearEventListenersContainer();
WebAssert.notNull("document_", document_);
document_.setDomNode(htmlPage);
}
}
/**
* Initializes the object. Only called for Windows with no contents.
*/
public void initialize() {
// Empty.
}
/**
* Returns the value of the top property.
* @return the value of "top"
*/
@JsxGetter
public Object getTop() {
if (top_ != NOT_FOUND) {
return top_;
}
final WebWindow top = getWebWindow().getTopWindow();
return getProxy(top);
}
/**
* Sets the value of the top property.
* @param o the new value
*/
@JsxSetter
public void setTop(final Object o) {
if (getBrowserVersion().hasFeature(JS_WINDOW_TOP_WRITABLE)) {
top_ = o;
}
}
/**
* Returns the value of the parent property.
* @return the value of window.parent
*/
@JsxGetter
public WindowProxy getParent() {
final WebWindow parent = getWebWindow().getParentWindow();
return getProxy(parent);
}
/**
* Returns the value of the opener property.
* @return the value of window.opener, {@code null} for a top level window
*/
@JsxGetter
public Object getOpener() {
Object opener = opener_;
if (opener instanceof Window) {
opener = ((Window) opener).windowProxy_;
}
return opener;
}
/**
* Sets the opener property.
* @param newValue the new value
*/
@JsxSetter
public void setOpener(final Object newValue) {
if (getBrowserVersion().hasFeature(JS_WINDOW_CHANGE_OPENER_ONLY_WINDOW_OBJECT)
&& newValue != null && newValue != Context.getUndefinedValue() && !(newValue instanceof Window)) {
throw Context.reportRuntimeError("Can't set opener to something other than a window!");
}
opener_ = newValue;
}
/**
* Returns the (i)frame in which the window is contained.
* @return {@code null} for a top level window
*/
@JsxGetter
public Object getFrameElement() {
final WebWindow window = getWebWindow();
if (window instanceof FrameWindow) {
return ((FrameWindow) window).getFrameElement().getScriptableObject();
}
return null;
}
/**
* Returns the value of the frames property.
* @return the value of the frames property
*/
@JsxGetter(propertyName = "frames")
public WindowProxy getFrames_js() {
return windowProxy_;
}
/**
* Returns the number of frames contained by this window.
* @return the number of frames contained by this window
*/
@JsxGetter
public int getLength() {
return getFrames().getLength();
}
/**
* Returns the live collection of frames contained by this window.
* @return the live collection of frames contained by this window
*/
private HTMLCollection getFrames() {
if (frames_ == null) {
final HtmlPage page = (HtmlPage) getWebWindow().getEnclosedPage();
frames_ = new HTMLCollectionFrames(page);
}
return frames_;
}
/**
* Returns the WebWindow associated with this Window.
* @return the WebWindow
*/
public WebWindow getWebWindow() {
return webWindow_;
}
/**
* Sets the focus to this element.
*/
@JsxFunction
public void focus() {
final WebWindow window = getWebWindow();
window.getWebClient().setCurrentWindow(window);
}
/**
* Removes focus from this element.
*/
@JsxFunction
public void blur() {
if (LOG.isDebugEnabled()) {
LOG.debug("window.blur() not implemented");
}
}
/**
* Closes this window.
*/
@JsxFunction
public void close() {
final WebWindow webWindow = getWebWindow();
if (webWindow instanceof TopLevelWindow) {
((TopLevelWindow) webWindow).close();
}
else {
webWindow.getWebClient().deregisterWebWindow(webWindow);
}
}
/**
* Indicates if this window is closed.
* @return {@code true} if this window is closed
*/
@JsxGetter
@CanSetReadOnly(CanSetReadOnlyStatus.IGNORE)
public boolean getClosed() {
final WebWindow webWindow = getWebWindow();
return !webWindow.getWebClient().containsWebWindow(webWindow);
}
/**
* Does nothing.
* @param x the horizontal position
* @param y the vertical position
*/
@JsxFunction
public void moveTo(final int x, final int y) {
if (LOG.isDebugEnabled()) {
LOG.debug("window.moveTo() not implemented");
}
}
/**
* Does nothing.
* @param x the horizontal position
* @param y the vertical position
*/
@JsxFunction
public void moveBy(final int x, final int y) {
if (LOG.isDebugEnabled()) {
LOG.debug("window.moveBy() not implemented");
}
}
/**
* Loads the new HTML document corresponding to the specified URL.
* @param url the location of the new HTML document to load
* @throws IOException if loading the specified location fails
* @see MSDN Documentation
*/
@JsxFunction(@WebBrowser(IE))
public void navigate(final String url) throws IOException {
getLocation().assign(url);
}
/**
* Does nothing.
* @param width the width offset
* @param height the height offset
*/
@JsxFunction
public void resizeBy(final int width, final int height) {
if (LOG.isDebugEnabled()) {
LOG.debug("window.resizeBy() not implemented");
}
}
/**
* Does nothing.
* @param width the width of the Window in pixel after resize
* @param height the height of the Window in pixel after resize
*/
@JsxFunction
public void resizeTo(final int width, final int height) {
if (LOG.isDebugEnabled()) {
LOG.debug("window.resizeTo() not implemented");
}
}
/**
* Scrolls to the specified location on the page.
* @param x the horizontal position to scroll to
* @param y the vertical position to scroll to
*/
@JsxFunction
public void scroll(final int x, final int y) {
scrollTo(x, y);
}
/**
* Scrolls the window content the specified distance.
* @param x the horizontal distance to scroll by
* @param y the vertical distance to scroll by
*/
@JsxFunction
public void scrollBy(final int x, final int y) {
final HTMLElement body = ((HTMLDocument) document_).getBody();
if (body != null) {
body.setScrollLeft(body.getScrollLeft() + x);
body.setScrollTop(body.getScrollTop() + y);
}
}
/**
* Scrolls the window content down by the specified number of lines.
* @param lines the number of lines to scroll down
*/
@JsxFunction(@WebBrowser(FF))
public void scrollByLines(final int lines) {
final HTMLElement body = ((HTMLDocument) document_).getBody();
if (body != null) {
body.setScrollTop(body.getScrollTop() + (19 * lines));
}
}
/**
* Scrolls the window content down by the specified number of pages.
* @param pages the number of pages to scroll down
*/
@JsxFunction(@WebBrowser(FF))
public void scrollByPages(final int pages) {
final HTMLElement body = ((HTMLDocument) document_).getBody();
if (body != null) {
body.setScrollTop(body.getScrollTop() + (getInnerHeight() * pages));
}
}
/**
* Scrolls to the specified location on the page.
* @param x the horizontal position to scroll to
* @param y the vertical position to scroll to
*/
@JsxFunction
public void scrollTo(final int x, final int y) {
final HTMLElement body = ((HTMLDocument) document_).getBody();
if (body != null) {
body.setScrollLeft(x);
body.setScrollTop(y);
}
}
/**
* Returns the onload property. Note that this is not necessarily a function if something else has been set.
* @return the onload property
*/
@JsxGetter
public Object getOnload() {
final Object onload = getHandlerForJavaScript("load");
if (onload == null) {
// NB: for IE, the onload of window is the one of the body element but not for Mozilla.
final HtmlPage page = (HtmlPage) getWebWindow().getEnclosedPage();
final HtmlElement body = page.getBody();
if (body != null) {
final HTMLBodyElement b = (HTMLBodyElement) body.getScriptableObject();
return b.getEventHandler("onload");
}
return null;
}
return onload;
}
/**
* Sets the value of the onload event handler.
* @param newOnload the new handler
*/
@JsxSetter
public void setOnload(final Object newOnload) {
getEventListenersContainer().setEventHandlerProp("load", newOnload);
}
/**
* Returns the onclick property (caution this is not necessary a function if something else has
* been set).
* @return the onclick property
*/
@JsxGetter
public Object getOnclick() {
return getHandlerForJavaScript("click");
}
/**
* Sets the value of the onclick event handler.
* @param newOnload the new handler
*/
@JsxSetter
public void setOnclick(final Object newOnload) {
setHandlerForJavaScript("click", newOnload);
}
/**
* Returns the ondblclick property (caution this is not necessary a function if something else has
* been set).
* @return the ondblclick property
*/
@JsxGetter
public Object getOndblclick() {
return getHandlerForJavaScript("dblclick");
}
/**
* Sets the value of the ondblclick event handler.
* @param newHandler the new handler
*/
@JsxSetter
public void setOndblclick(final Object newHandler) {
setHandlerForJavaScript("dblclick", newHandler);
}
/**
* Returns the onhashchange property (caution this is not necessary a function if something else has
* been set).
* @return the onhashchange property
*/
@JsxGetter
public Object getOnhashchange() {
return getHandlerForJavaScript(Event.TYPE_HASH_CHANGE);
}
/**
* Sets the value of the onhashchange event handler.
* @param newHandler the new handler
*/
@JsxSetter
public void setOnhashchange(final Object newHandler) {
setHandlerForJavaScript(Event.TYPE_HASH_CHANGE, newHandler);
}
/**
* Returns the value of the window's {@code name} property.
* @return the value of the window's {@code name} property
*/
@JsxGetter
public String getName() {
return getWebWindow().getName();
}
/**
* Sets the value of the window's {@code name} property.
* @param name the value of the window's {@code name} property
*/
@JsxSetter
public void setName(final String name) {
getWebWindow().setName(name);
}
/**
* Returns the value of the window's {@code onbeforeunload} property.
* @return the value of the window's {@code onbeforeunload} property
*/
@JsxGetter
public Object getOnbeforeunload() {
return getHandlerForJavaScript(Event.TYPE_BEFORE_UNLOAD);
}
/**
* Sets the value of the window's {@code onbeforeunload} property.
* @param onbeforeunload the value of the window's {@code onbeforeunload} property
*/
@JsxSetter
public void setOnbeforeunload(final Object onbeforeunload) {
setHandlerForJavaScript(Event.TYPE_BEFORE_UNLOAD, onbeforeunload);
}
/**
* Returns the value of the window's {@code onerror} property.
* @return the value of the window's {@code onerror} property
*/
@JsxGetter
public Object getOnerror() {
return getHandlerForJavaScript(Event.TYPE_ERROR);
}
/**
* Sets the value of the window's {@code onerror} property.
* @param onerror the value of the window's {@code onerror} property
*/
@JsxSetter
public void setOnerror(final Object onerror) {
setHandlerForJavaScript(Event.TYPE_ERROR, onerror);
}
/**
* Returns the value of the window's {@code onmessage} property.
* @return the value of the window's {@code onmessage} property
*/
@JsxGetter
public Object getOnmessage() {
return getHandlerForJavaScript(Event.TYPE_MESSAGE);
}
/**
* Sets the value of the window's {@code onmessage} property.
* @param onmessage the value of the window's {@code onmessage} property
*/
@JsxSetter
public void setOnmessage(final Object onmessage) {
setHandlerForJavaScript(Event.TYPE_MESSAGE, onmessage);
}
/**
* Triggers the onerror handler, if one has been set.
* @param e the error that needs to be reported
*/
public void triggerOnError(final ScriptException e) {
final Object o = getOnerror();
if (o instanceof Function) {
final Function f = (Function) o;
final String msg = e.getMessage();
final String url = e.getPage().getUrl().toExternalForm();
final int line = e.getFailingLineNumber();
final int column = e.getFailingColumnNumber();
final Object[] args = new Object[] {msg, url, Integer.valueOf(line), Integer.valueOf(column), e};
f.call(Context.getCurrentContext(), this, this, args);
}
}
private Object getHandlerForJavaScript(final String eventName) {
return getEventListenersContainer().getEventHandlerProp(eventName);
}
private void setHandlerForJavaScript(final String eventName, final Object handler) {
if (handler == null || handler instanceof Function) {
getEventListenersContainer().setEventHandlerProp(eventName, handler);
}
// Otherwise, fail silently.
}
/**
* {@inheritDoc}
*/
@Override
public Object call(final Context cx, final Scriptable scope, final Scriptable thisObj, final Object[] args) {
throw Context.reportRuntimeError("Window is not a function.");
}
/**
* {@inheritDoc}
*/
@Override
public Scriptable construct(final Context cx, final Scriptable scope, final Object[] args) {
throw Context.reportRuntimeError("Window is not a function.");
}
/**
* {@inheritDoc}
*/
@Override
public Object getWithFallback(final String name) {
Object result = NOT_FOUND;
final DomNode domNode = getDomNodeOrNull();
if (domNode != null) {
// May be attempting to retrieve a frame by name.
final HtmlPage page = (HtmlPage) domNode.getPage();
result = getFrameWindowByName(page, name);
if (result == NOT_FOUND) {
result = getElementsByName(page, name);
if (result == NOT_FOUND) {
// May be attempting to retrieve element by ID (try map-backed operation again instead of XPath).
try {
final HtmlElement htmlElement = page.getHtmlElementById(name);
if (getBrowserVersion().hasFeature(JS_WINDOW_FRAME_BY_ID_RETURNS_WINDOW)
&& htmlElement instanceof HtmlFrame) {
final HtmlFrame frame = (HtmlFrame) htmlElement;
result = getScriptableFor(frame.getEnclosedWindow());
}
else {
result = getScriptableFor(htmlElement);
}
}
catch (final ElementNotFoundException e) {
result = NOT_FOUND;
}
}
}
if (result instanceof Window) {
final WebWindow webWindow = ((Window) result).getWebWindow();
result = getProxy(webWindow);
}
}
return result;
}
/**
* {@inheritDoc}
*/
@Override
public Object get(final int index, final Scriptable start) {
final HTMLCollection frames = getFrames();
if (index >= frames.getLength()) {
return Context.getUndefinedValue();
}
return frames.item(Integer.valueOf(index));
}
private static Object getFrameWindowByName(final HtmlPage page, final String name) {
try {
return page.getFrameByName(name).getScriptableObject();
}
catch (final ElementNotFoundException e) {
return NOT_FOUND;
}
}
private Object getElementsByName(final HtmlPage page, final String name) {
// May be attempting to retrieve element(s) by name. IMPORTANT: We're using map-backed operations
// like getHtmlElementsByName() and getHtmlElementById() as much as possible, so as to avoid XPath
// overhead. We only use an XPath-based operation when we have to (where there is more than one
// matching element). This optimization appears to improve performance in certain situations by ~15%
// vs using XPath-based operations throughout.
final List elements = page.getElementsByName(name);
final boolean includeFormFields = getBrowserVersion().hasFeature(JS_WINDOW_FORMFIELDS_ACCESSIBLE_BY_NAME);
final Filter filter = new Filter(includeFormFields);
final Iterator it = elements.iterator();
while (it.hasNext()) {
if (!filter.matches(it.next())) {
it.remove();
}
}
if (elements.size() == 0) {
return NOT_FOUND;
}
if (elements.size() == 1) {
return getScriptableFor(elements.get(0));
}
// Null must be changed to '' for proper collection initialization.
final String expElementName = "null".equals(name) ? "" : name;
return new HTMLCollection(page, true) {
@Override
protected List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy