/*
* GNU LESSER GENERAL PUBLIC LICENSE Copyright (C) 2006 The Lobo Project
*
* This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
* Contact info: xamjadmin@users.sourceforge.net
*/
/*
* Created on Nov 12, 2005
*/
package org.cobra_grendel.html.js;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.Timer;
import org.cobra_grendel.html.HtmlRendererContext;
import org.cobra_grendel.html.UserAgentContext;
import org.cobra_grendel.html.domimpl.HTMLDocumentImpl;
import org.cobra_grendel.html.domimpl.HTMLIFrameElementImpl;
import org.cobra_grendel.html.domimpl.HTMLImageElementImpl;
import org.cobra_grendel.html.domimpl.HTMLOptionElementImpl;
import org.cobra_grendel.html.domimpl.HTMLScriptElementImpl;
import org.cobra_grendel.html.domimpl.HTMLSelectElementImpl;
import org.cobra_grendel.js.AbstractScriptableDelegate;
import org.cobra_grendel.js.JavaClassWrapper;
import org.cobra_grendel.js.JavaClassWrapperFactory;
import org.cobra_grendel.js.JavaInstantiator;
import org.cobra_grendel.js.JavaObjectWrapper;
import org.cobra_grendel.js.JavaScript;
import org.cobra_grendel.util.ID;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.w3c.dom.Document;
import org.w3c.dom.html2.HTMLCollection;
public class Window extends AbstractScriptableDelegate
{
/**
*
*/
private static final long serialVersionUID = 1L;
private static final Map CONTEXT_WINDOWS = new WeakHashMap();
private static final Logger logger = Logger.getLogger(Window.class.getName());
// private static final JavaClassWrapper IMAGE_WRAPPER =
// JavaClassWrapperFactory.getInstance().getClassWrapper(Image.class);
private static final JavaClassWrapper XMLHTTPREQUEST_WRAPPER = JavaClassWrapperFactory.getInstance().getClassWrapper(XMLHttpRequest.class);
public static Window getWindow(final HtmlRendererContext rcontext)
{
if (rcontext == null)
{
return null;
}
synchronized (CONTEXT_WINDOWS)
{
Reference wref = (Reference) CONTEXT_WINDOWS.get(rcontext);
if (wref != null)
{
Window window = (Window) wref.get();
if (window != null)
{
return window;
}
}
Window window = new Window(rcontext, rcontext.getUserAgentContext());
CONTEXT_WINDOWS.put(rcontext, new WeakReference(window));
return window;
}
}
private volatile HTMLDocumentImpl document;
private Location location;
private Navigator navigator;
private Function onunload;
private final HtmlRendererContext rcontext;
private Screen screen;
private Map taskMap;
private final UserAgentContext uaContext;
private ScriptableObject windowScope;
public Window(final HtmlRendererContext rcontext, final UserAgentContext uaContext)
{
super(-1);
// TODO: Probably need to create a new Window instance
// for every document. Sharing of Window state between
// different documents is not correct.
this.rcontext = rcontext;
this.uaContext = uaContext;
}
/**
* Disabled to prevent problems in scanner
*/
public void alert(final String message)
{
System.out.println("ALERT: " + message);
/*
* if(this.rcontext != null) { this.rcontext.alert(message); }
*/
}
/**
* Disabled to prevent problems in scanner
*/
public void back()
{
/*
* HtmlRendererContext rcontext = this.rcontext; if(rcontext != null) { rcontext.back(); }
*/}
public void blur()
{
HtmlRendererContext rcontext = this.rcontext;
if (rcontext != null)
{
rcontext.blur();
}
}
// private Timer getTask(Long timeoutID) {
// synchronized(this) {
// Map taskMap = this.taskMap;
// if(taskMap != null) {
// return (Timer) taskMap.get(timeoutID);
// }
// }
// return null;
// }
public void clearTimeout(final int timeoutID)
{
Integer key = new Integer(timeoutID);
forgetTask(key, true);
}
public void close()
{
HtmlRendererContext rcontext = this.rcontext;
if (rcontext != null)
{
rcontext.close();
}
}
/**
* Disabled to prevent problems in scanner
*
* @param message
* @return Always returns true
*/
public boolean confirm(final String message)
{
/*
* HtmlRendererContext rcontext = this.rcontext; if(rcontext != null) { return rcontext.confirm(message); } else { return false; }
*/
return true;
}
private final void defineElementClass(final Scriptable scope, final String jsClassName, final String elementName, final Class javaClass)
{
JavaInstantiator ji = new JavaInstantiator()
{
/**
*
*/
private static final long serialVersionUID = 1L;
@Override
public Object newInstance()
{
Document document = Window.this.document;
if (document == null)
{
throw new IllegalStateException("Document not set in current context.");
}
return document.createElement(elementName);
}
};
JavaClassWrapper classWrapper = JavaClassWrapperFactory.getInstance().getClassWrapper(javaClass);
Function constructorFunction = JavaObjectWrapper.getConstructor(jsClassName, classWrapper, scope, ji);
ScriptableObject.defineProperty(scope, jsClassName, constructorFunction, ScriptableObject.READONLY);
}
public Object eval(final String javascript)
{
HTMLDocumentImpl document = this.document;
if (document == null)
{
throw new IllegalStateException("Cannot evaluate if document is not set.");
}
Context ctx = Executor.createContext(document.getDocumentURL(), uaContext);
try
{
Scriptable scope = getWindowScope();
if (scope == null)
{
throw new IllegalStateException("Scriptable (scope) instance was expected to be keyed as UserData to document using " + Executor.SCOPE_KEY);
}
String scriptURI = "window.eval";
if (logger.isLoggable(Level.INFO))
{
logger.info("eval(): javascript follows...\r\n" + javascript);
}
return ctx.evaluateString(scope, javascript, scriptURI, 1, null);
}
finally
{
Context.exit();
}
}
public void focus()
{
HtmlRendererContext rcontext = this.rcontext;
if (rcontext != null)
{
rcontext.focus();
}
}
private void forgetAllTasks()
{
synchronized (this)
{
Map taskMap = this.taskMap;
if (taskMap != null)
{
Iterator i = taskMap.values().iterator();
while (i.hasNext())
{
Timer timer = (Timer) i.next();
timer.stop();
}
this.taskMap = null;
}
}
}
private void forgetTask(final Integer timeoutID, final boolean cancel)
{
synchronized (this)
{
Map taskMap = this.taskMap;
if (taskMap != null)
{
Timer timer = (Timer) taskMap.remove(timeoutID);
if (timer != null && cancel)
{
timer.stop();
}
}
}
}
public String getDefaultStatus()
{
HtmlRendererContext rcontext = this.rcontext;
if (rcontext != null)
{
return rcontext.getDefaultStatus();
}
else
{
return null;
}
}
public Document getDocument()
{
return document;
}
public HTMLCollection getFrames()
{
Document doc = document;
if (doc instanceof HTMLDocumentImpl)
{
return ((HTMLDocumentImpl) doc).getFrames();
}
return null;
}
public HtmlRendererContext getHtmlRendererContext()
{
return rcontext;
}
/**
* Gets the number of frames.
*/
public int getLength()
{
HTMLCollection frames = getFrames();
return frames == null ? 0 : frames.getLength();
}
public Location getLocation()
{
synchronized (this)
{
Location location = this.location;
if (location == null)
{
location = new Location(this, -1);
this.location = location;
}
return location;
}
}
public String getName()
{
HtmlRendererContext rcontext = this.rcontext;
if (rcontext != null)
{
return rcontext.getName();
}
else
{
return null;
}
}
public Navigator getNavigator()
{
synchronized (this)
{
Navigator nav = navigator;
if (nav == null)
{
nav = new Navigator(uaContext);
navigator = nav;
}
return nav;
}
}
public Function getOnload()
{
Document doc = document;
if (doc instanceof HTMLDocumentImpl)
{
return ((HTMLDocumentImpl) doc).getOnloadHandler();
}
else
{
return null;
}
}
public Function getOnunload()
{
return onunload;
}
public Window getOpener()
{
HtmlRendererContext rcontext = this.rcontext;
if (rcontext != null)
{
return Window.getWindow(rcontext.getOpener());
}
else
{
return null;
}
}
public Window getParent()
{
HtmlRendererContext rcontext = this.rcontext;
if (rcontext != null)
{
return Window.getWindow(rcontext.getParent());
}
else
{
return null;
}
}
public Screen getScreen()
{
synchronized (this)
{
Screen nav = screen;
if (nav == null)
{
nav = new Screen();
screen = nav;
}
return nav;
}
}
public Window getSelf()
{
return this;
}
public String getStatus()
{
HtmlRendererContext rcontext = this.rcontext;
if (rcontext != null)
{
return rcontext.getStatus();
}
else
{
return null;
}
}
public Window getTop()
{
HtmlRendererContext rcontext = this.rcontext;
if (rcontext != null)
{
return Window.getWindow(rcontext.getTop());
}
else
{
return null;
}
}
public Window getWindow()
{
return this;
}
public Scriptable getWindowScope()
{
synchronized (this)
{
ScriptableObject windowScope = this.windowScope;
if (windowScope != null)
{
return windowScope;
}
// Context.enter() OK in this particular case.
Context ctx = Context.enter();
try
{
// Window scope needs to be top-most scope.
windowScope = (ScriptableObject) JavaScript.getInstance().getJavascriptObject(this, null);
ctx.initStandardObjects(windowScope);
// Special Javascript class: XMLHttpRequest
final Scriptable ws = windowScope;
JavaInstantiator xi = new JavaInstantiator()
{
/**
*
*/
private static final long serialVersionUID = 1L;
@Override
public Object newInstance()
{
HTMLDocumentImpl doc = document;
if (doc == null)
{
throw new IllegalStateException("Cannot perform operation when document is unset.");
}
return new XMLHttpRequest(uaContext, doc.getDocumentURL(), ws, doc.getTransactionId());
}
};
Function xmlHttpRequestC = JavaObjectWrapper.getConstructor("XMLHttpRequest", XMLHTTPREQUEST_WRAPPER, windowScope, xi);
ScriptableObject.defineProperty(windowScope, "XMLHttpRequest", xmlHttpRequestC, ScriptableObject.READONLY);
// HTML element classes
defineElementClass(windowScope, "Image", "img", HTMLImageElementImpl.class);
defineElementClass(windowScope, "Script", "script", HTMLScriptElementImpl.class);
defineElementClass(windowScope, "IFrame", "iframe", HTMLIFrameElementImpl.class);
defineElementClass(windowScope, "Option", "option", HTMLOptionElementImpl.class);
defineElementClass(windowScope, "Select", "select", HTMLSelectElementImpl.class);
this.windowScope = windowScope;
return windowScope;
}
finally
{
Context.exit();
}
}
}
public boolean isClosed()
{
HtmlRendererContext rcontext = this.rcontext;
if (rcontext != null)
{
return rcontext.isClosed();
}
else
{
return false;
}
}
/**
* For now, disabled to prevent problems with scanner. May be reenabled if possible.
*
* @param url
* @param windowName
* @return Always returns null
*/
public Window open(final String url, final String windowName)
{
// return this.open(url, windowName, "", false);
return null;
}
/**
* For now, disabled to prevent problems with scanner. May be reenabled if possible.
*
* @param url
* @param windowName
* @param windowFeatures
* @return Always returns null
*/
public Window open(final String url, final String windowName, final String windowFeatures)
{
// return this.open(url, windowName, windowFeatures, false);
return null;
}
/**
* For now, disabled to prevent problems with scanner. May be reenabled if possible.
*
* @param relativeUrl
* @param windowName
* @param windowFeatures
* @param replace
* @return Always returns null
*/
public Window open(final String relativeUrl, final String windowName, final String windowFeatures, final boolean replace)
{
/*
* HtmlRendererContext rcontext = this.rcontext; if(rcontext != null) { java.net.URL url; Object document = this.document; if(document instanceof HTMLDocumentImpl) { url = ((HTMLDocumentImpl)
* document).getFullURL(relativeUrl); } else { try { url = new java.net.URL(relativeUrl); } catch(java.net.MalformedURLException mfu) { throw new IllegalArgumentException("Malformed URI: " +
* relativeUrl); } } HtmlRendererContext newContext = rcontext.open(url, windowName, windowFeatures, replace); return getWindow(newContext); } else { return null; }
*/
return null;
}
/**
* Disabled to prevent problems with the scanner.
*
* @param message
* @return Always returns an empty string
*/
public String prompt(final String message)
{
return "";
// return this.prompt(message, "");
}
/**
* Disabled to prevent problems with the scanner.
*
* @param message
* @param inputDefault
* @return Always returns the default
*/
public String prompt(final String message, final int inputDefault)
{
// return this.prompt(message, String.valueOf(inputDefault));
return String.valueOf(inputDefault);
}
/**
* Disabled to prevent problems with the scanner.
*
* @param message
* @param inputDefault
* @returnAlways returns the default
*/
public String prompt(final String message, final String inputDefault)
{
return inputDefault;
/*
* HtmlRendererContext rcontext = this.rcontext; if(rcontext != null) { return rcontext.prompt(message, inputDefault); } else { return null; }
*/
}
private void putTask(final Integer timeoutID, final Timer timer)
{
synchronized (this)
{
Map taskMap = this.taskMap;
if (taskMap == null)
{
taskMap = new HashMap();
this.taskMap = taskMap;
}
taskMap.put(timeoutID, timer);
}
}
public void scroll(final int x, final int y)
{
HtmlRendererContext rcontext = this.rcontext;
if (rcontext != null)
{
rcontext.scroll(x, y);
}
}
public void setDocument(final HTMLDocumentImpl document)
{
Document prevDocument = this.document;
if (prevDocument != document)
{
forgetAllTasks();
Function onunload = this.onunload;
if (onunload != null)
{
HTMLDocumentImpl oldDoc = this.document;
Executor.executeFunction(getWindowScope(), onunload, oldDoc.getDocumentURL(), uaContext);
this.onunload = null;
}
this.document = document;
}
}
/**
* Not in Cobra, but easy to implement. The interval is always zero, and it only gets execute once.
*
* @param function
* @param millis
* @return
*/
public int setInterval(final Function function, final double millis)
{
return setTimeout(function, millis);
}
/**
* Not in Cobra, but easy to implement. The interval is always zero, and it only gets execute once.
*
* @param expr
* @param millis
* @return
*/
public int setInterval(final String expr, final double millis)
{
return setTimeout(expr, millis);
}
/**
* Disabled to prevent problems with scanner. Will be reenabled in the future.
*
* @param location
*/
public void setLocation(final String location)
{
// this.getLocation().setHref(location);
}
public void setOnload(final Function onload)
{
// Note that body.onload overrides
// window.onload.
Document doc = document;
if (doc instanceof HTMLDocumentImpl)
{
((HTMLDocumentImpl) doc).setOnloadHandler(onload);
}
}
public void setOnunload(final Function onunload)
{
this.onunload = onunload;
}
public void setOpener(final Window opener)
{
HtmlRendererContext rcontext = this.rcontext;
if (rcontext != null)
{
if (opener == null)
{
rcontext.setOpener(null);
}
else
{
rcontext.setOpener(opener.rcontext);
}
}
}
public void setStatus(final String message)
{
HtmlRendererContext rcontext = this.rcontext;
if (rcontext != null)
{
rcontext.setStatus(message);
}
}
/**
* Delay is always set to zero to prevent problems in the scanner
*
* @param function
* @param millis
* @return
*/
public int setTimeout(final Function function, double millis)
{
millis = 0;
final int timeID = ID.generateInt();
final Integer timeIDInt = new Integer(timeID);
ActionListener task = new ActionListener()
{
@Override
public void actionPerformed(final ActionEvent e)
{
// This executes in the GUI thread and that's good.
Window.this.forgetTask(timeIDInt, false);
HTMLDocumentImpl doc = document;
if (doc == null)
{
throw new IllegalStateException("Cannot perform operation when document is unset.");
}
Executor.executeFunction(getWindowScope(), function, doc.getDocumentURL(), uaContext);
}
};
if (millis > Integer.MAX_VALUE || millis < 0)
{
throw new IllegalArgumentException("Timeout value " + millis + " is not supported.");
}
Timer timer = new Timer((int) millis, task);
timer.setRepeats(false);
timer.start();
putTask(timeIDInt, timer);
return timeID;
}
/**
* Delay is always set to zero to prevent problems in the scanner
*
* @param expr
* @param millis
* @return
*/
public int setTimeout(final String expr, double millis)
{
millis = 0;
final int timeID = ID.generateInt();
final Integer timeIDInt = new Integer(timeID);
ActionListener task = new ActionListener()
{
@Override
public void actionPerformed(final ActionEvent e)
{
// This executes in the GUI thread and that's good.
Window.this.forgetTask(timeIDInt, false);
Window.this.eval(expr);
}
};
if (millis > Integer.MAX_VALUE || millis < 0)
{
throw new IllegalArgumentException("Timeout value " + millis + " is not supported.");
}
Timer timer = new Timer((int) millis, task);
timer.setRepeats(false);
timer.start();
putTask(timeIDInt, timer);
return timeID;
}
/**
* Used for testing for XSS using a function name that won't be blocked by stupid filters
*
* @param token
*/
public void testXSS(final String token)
{
document.setXssToken(token);
}
}