blob: ba104f9a8a577bc528cbe541dc4cfb2423b6f526 [file] [log] [blame]
/*
* Copyright (C) 2000 Harri Porten (porten@kde.org)
* Copyright (C) 2006 Jon Shier (jshier@iastate.edu)
* Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reseved.
* Copyright (C) 2006 Alexey Proskuryakov (ap@webkit.org)
*
* 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 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 Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
#include "config.h"
#include "kjs_window.h"
#include "Base64.h"
#include "CString.h"
#include "Chrome.h"
#include "DOMWindow.h"
#include "Element.h"
#include "EventListener.h"
#include "EventNames.h"
#include "ExceptionCode.h"
#include "FloatRect.h"
#include "Frame.h"
#include "FrameLoadRequest.h"
#include "FrameLoader.h"
#include "FrameTree.h"
#include "GCController.h"
#include "HTMLDocument.h"
#include "JSDOMWindow.h"
#include "JSEvent.h"
#include "JSAudioConstructor.h"
#include "JSHTMLCollection.h"
#include "JSHTMLOptionElementConstructor.h"
#include "JSXMLHttpRequest.h"
#include "JSLocation.h"
#include "Logging.h"
#include "MediaPlayer.h"
#include "Page.h"
#include "PausedTimeouts.h"
#include "PlatformScreen.h"
#include "PluginInfoStore.h"
#include "RenderView.h"
#include "ScheduledAction.h"
#include "SecurityOrigin.h"
#include "Settings.h"
#include "WindowFeatures.h"
#include "htmlediting.h"
#include "kjs_css.h"
#include "kjs_events.h"
#include "kjs_navigator.h"
#include "kjs_proxy.h"
#include <wtf/AlwaysInline.h>
#include <wtf/MathExtras.h>
#if ENABLE(XSLT)
#include "JSXSLTProcessor.h"
#endif
using namespace WebCore;
using namespace EventNames;
namespace KJS {
static int lastUsedTimeoutId;
static int timerNestingLevel = 0;
const int cMaxTimerNestingLevel = 5;
const double cMinimumTimerInterval = 0.010;
struct WindowPrivate {
WindowPrivate()
: loc(0)
, m_evt(0)
, m_returnValueSlot(0)
{
}
Window::ListenersMap jsEventListeners;
Window::ListenersMap jsHTMLEventListeners;
Window::UnprotectedListenersMap jsUnprotectedEventListeners;
Window::UnprotectedListenersMap jsUnprotectedHTMLEventListeners;
mutable WebCore::JSLocation* loc;
WebCore::Event* m_evt;
JSValue** m_returnValueSlot;
typedef HashMap<int, DOMWindowTimer*> TimeoutsMap;
TimeoutsMap m_timeouts;
};
class DOMWindowTimer : public TimerBase {
public:
DOMWindowTimer(int timeoutId, int nestingLevel, Window* object, WebCore::ScheduledAction* action)
: m_timeoutId(timeoutId)
, m_nestingLevel(nestingLevel)
, m_object(object)
, m_action(action)
{
}
virtual ~DOMWindowTimer()
{
JSLock lock;
delete m_action;
}
int timeoutId() const { return m_timeoutId; }
int nestingLevel() const { return m_nestingLevel; }
void setNestingLevel(int n) { m_nestingLevel = n; }
WebCore::ScheduledAction* action() const { return m_action; }
WebCore::ScheduledAction* takeAction() { WebCore::ScheduledAction* a = m_action; m_action = 0; return a; }
private:
virtual void fired();
int m_timeoutId;
int m_nestingLevel;
Window* m_object;
WebCore::ScheduledAction* m_action;
};
} // namespace KJS
#include "kjs_window.lut.h"
namespace KJS {
////////////////////// Window Object ////////////////////////
const ClassInfo Window::info = { "Window", 0, &WindowTable };
/*
@begin WindowTable 118
# Warning, when adding a function to this object you need to add a case in Window::get
# -- Functions --
atob windowProtoFuncAToB DontDelete|Function 1
btoa windowProtoFuncBToA DontDelete|Function 1
open windowProtoFuncOpen DontDelete|Function 3
setTimeout windowProtoFuncSetTimeout DontDelete|Function 2
clearTimeout windowProtoFuncClearTimeout DontDelete|Function 1
setInterval windowProtoFuncSetInterval DontDelete|Function 2
clearInterval windowProtoFuncClearTimeout DontDelete|Function 1
addEventListener windowProtoFuncAddEventListener DontDelete|Function 3
removeEventListener windowProtoFuncRemoveEventListener DontDelete|Function 3
showModalDialog windowProtoFuncShowModalDialog DontDelete|Function 1
# Not implemented
captureEvents windowProtoFuncNotImplemented DontDelete|Function 0
releaseEvents windowProtoFuncNotImplemented DontDelete|Function 0
# -- Attributes --
crypto Window::Crypto DontDelete|ReadOnly
event Window::Event_ DontDelete
location Window::Location_ DontDelete
navigator Window::Navigator_ DontDelete
clientInformation Window::ClientInformation DontDelete
# -- Event Listeners --
onabort Window::Onabort DontDelete
onblur Window::Onblur DontDelete
onchange Window::Onchange DontDelete
onclick Window::Onclick DontDelete
ondblclick Window::Ondblclick DontDelete
onerror Window::Onerror DontDelete
onfocus Window::Onfocus DontDelete
onkeydown Window::Onkeydown DontDelete
onkeypress Window::Onkeypress DontDelete
onkeyup Window::Onkeyup DontDelete
onload Window::Onload DontDelete
onmousedown Window::Onmousedown DontDelete
onmousemove Window::Onmousemove DontDelete
onmouseout Window::Onmouseout DontDelete
onmouseover Window::Onmouseover DontDelete
onmouseup Window::Onmouseup DontDelete
onmousewheel Window::OnWindowMouseWheel DontDelete
onreset Window::Onreset DontDelete
onresize Window::Onresize DontDelete
onscroll Window::Onscroll DontDelete
onsearch Window::Onsearch DontDelete
onselect Window::Onselect DontDelete
onsubmit Window::Onsubmit DontDelete
onunload Window::Onunload DontDelete
onbeforeunload Window::Onbeforeunload DontDelete
# -- Constructors --
Audio Window::Audio DontDelete
Image Window::Image DontDelete
Option Window::Option DontDelete
XMLHttpRequest Window::XMLHttpRequest DontDelete
XSLTProcessor Window::XSLTProcessor_ DontDelete
@end
*/
Window::Window(JSObject* prototype, DOMWindow* window)
: JSGlobalObject(prototype)
, m_impl(window)
, d(new WindowPrivate)
{
// Window destruction is not thread-safe because of
// the non-thread-safe WebCore structures it references.
Collector::collectOnMainThreadOnly(this);
// Time in milliseconds before the script timeout handler kicks in.
setTimeoutTime(10000);
}
Window::~Window()
{
clearAllTimeouts();
// Clear any backpointers to the window
ListenersMap::iterator i2 = d->jsEventListeners.begin();
ListenersMap::iterator e2 = d->jsEventListeners.end();
for (; i2 != e2; ++i2)
i2->second->clearWindowObj();
i2 = d->jsHTMLEventListeners.begin();
e2 = d->jsHTMLEventListeners.end();
for (; i2 != e2; ++i2)
i2->second->clearWindowObj();
UnprotectedListenersMap::iterator i1 = d->jsUnprotectedEventListeners.begin();
UnprotectedListenersMap::iterator e1 = d->jsUnprotectedEventListeners.end();
for (; i1 != e1; ++i1)
i1->second->clearWindowObj();
i1 = d->jsUnprotectedHTMLEventListeners.begin();
e1 = d->jsUnprotectedHTMLEventListeners.end();
for (; i1 != e1; ++i1)
i1->second->clearWindowObj();
}
Window* Window::retrieveWindow(Frame* frame)
{
JSObject* o = retrieve(frame)->getObject();
ASSERT(o || !frame->scriptProxy()->isEnabled());
return static_cast<Window*>(o);
}
Window* Window::retrieveActive(ExecState* exec)
{
JSGlobalObject* globalObject = exec->dynamicGlobalObject();
ASSERT(globalObject);
return static_cast<Window*>(globalObject);
}
JSValue* Window::retrieve(Frame* frame)
{
ASSERT(frame);
if (frame->scriptProxy()->isEnabled())
return frame->scriptProxy()->globalObject(); // the Global object is the "window"
return jsUndefined(); // This can happen with JS disabled on the domain of that window
}
WebCore::JSLocation* Window::location() const
{
if (!d->loc)
d->loc = new JSLocation(0, impl()->frame()); // FIXME: we need to pass a prototype.
return d->loc;
}
void Window::mark()
{
Base::mark();
if (d->loc && !d->loc->marked())
d->loc->mark();
}
static bool allowPopUp(ExecState* exec)
{
Frame* frame = Window::retrieveActive(exec)->impl()->frame();
ASSERT(frame);
if (frame->scriptProxy()->processingUserGesture())
return true;
Settings* settings = frame->settings();
return settings && settings->JavaScriptCanOpenWindowsAutomatically();
}
static HashMap<String, String> parseModalDialogFeatures(const String& featuresArg)
{
HashMap<String, String> map;
Vector<String> features = featuresArg.split(';');
Vector<String>::const_iterator end = features.end();
for (Vector<String>::const_iterator it = features.begin(); it != end; ++it) {
String s = *it;
int pos = s.find('=');
int colonPos = s.find(':');
if (pos >= 0 && colonPos >= 0)
continue; // ignore any strings that have both = and :
if (pos < 0)
pos = colonPos;
if (pos < 0) {
// null string for value means key without value
map.set(s.stripWhiteSpace().lower(), String());
} else {
String key = s.left(pos).stripWhiteSpace().lower();
String val = s.substring(pos + 1).stripWhiteSpace().lower();
int spacePos = val.find(' ');
if (spacePos != -1)
val = val.left(spacePos);
map.set(key, val);
}
}
return map;
}
static Frame* createWindow(ExecState* exec, Frame* openerFrame, const String& url,
const String& frameName, const WindowFeatures& windowFeatures, JSValue* dialogArgs)
{
Frame* activeFrame = Window::retrieveActive(exec)->impl()->frame();
ResourceRequest request;
request.setHTTPReferrer(activeFrame->loader()->outgoingReferrer());
FrameLoadRequest frameRequest(request, frameName);
// FIXME: It's much better for client API if a new window starts with a URL, here where we
// know what URL we are going to open. Unfortunately, this code passes the empty string
// for the URL, but there's a reason for that. Before loading we have to set up the opener,
// openedByDOM, and dialogArguments values. Also, to decide whether to use the URL we currently
// do an allowsAccessFrom call using the window we create, which can't be done before creating it.
// We'd have to resolve all those issues to pass the URL instead of "".
bool created;
// We pass in the opener frame here so it can be used for looking up the frame name, in case the active frame
// is different from the opener frame, and the name references a frame relative to the opener frame, for example
// "_self" or "_parent".
Frame* newFrame = activeFrame->loader()->createWindow(openerFrame->loader(), frameRequest, windowFeatures, created);
if (!newFrame)
return 0;
newFrame->loader()->setOpener(openerFrame);
newFrame->loader()->setOpenedByDOM();
Window* newWindow = Window::retrieveWindow(newFrame);
if (dialogArgs)
newWindow->putDirect("dialogArguments", dialogArgs);
if (!url.startsWith("javascript:", false) || newWindow->allowsAccessFrom(exec)) {
String completedURL = url.isEmpty() ? url : activeFrame->document()->completeURL(url);
bool userGesture = activeFrame->scriptProxy()->processingUserGesture();
if (created) {
newFrame->loader()->changeLocation(KURL(completedURL.deprecatedString()), activeFrame->loader()->outgoingReferrer(), false, userGesture);
if (Document* oldDoc = openerFrame->document())
newFrame->document()->setBaseURL(oldDoc->baseURL());
} else if (!url.isEmpty())
newFrame->loader()->scheduleLocationChange(completedURL, activeFrame->loader()->outgoingReferrer(), false, userGesture);
}
return newFrame;
}
static bool canShowModalDialog(const Frame* frame)
{
if (!frame)
return false;
return frame->page()->chrome()->canRunModal();
}
static bool canShowModalDialogNow(const Frame* frame)
{
if (!frame)
return false;
return frame->page()->chrome()->canRunModalNow();
}
static JSValue* showModalDialog(ExecState* exec, Frame* frame, const String& url, JSValue* dialogArgs, const String& featureArgs)
{
if (!canShowModalDialogNow(frame) || !allowPopUp(exec))
return jsUndefined();
const HashMap<String, String> features = parseModalDialogFeatures(featureArgs);
const bool trusted = false;
// The following features from Microsoft's documentation are not implemented:
// - default font settings
// - width, height, left, and top specified in units other than "px"
// - edge (sunken or raised, default is raised)
// - dialogHide: trusted && boolFeature(features, "dialoghide"), makes dialog hide when you print
// - help: boolFeature(features, "help", true), makes help icon appear in dialog (what does it do on Windows?)
// - unadorned: trusted && boolFeature(features, "unadorned");
if (!frame)
return jsUndefined();
FloatRect screenRect = screenAvailableRect(frame->view());
WindowFeatures wargs;
wargs.width = WindowFeatures::floatFeature(features, "dialogwidth", 100, screenRect.width(), 620); // default here came from frame size of dialog in MacIE
wargs.widthSet = true;
wargs.height = WindowFeatures::floatFeature(features, "dialogheight", 100, screenRect.height(), 450); // default here came from frame size of dialog in MacIE
wargs.heightSet = true;
wargs.x = WindowFeatures::floatFeature(features, "dialogleft", screenRect.x(), screenRect.right() - wargs.width, -1);
wargs.xSet = wargs.x > 0;
wargs.y = WindowFeatures::floatFeature(features, "dialogtop", screenRect.y(), screenRect.bottom() - wargs.height, -1);
wargs.ySet = wargs.y > 0;
if (WindowFeatures::boolFeature(features, "center", true)) {
if (!wargs.xSet) {
wargs.x = screenRect.x() + (screenRect.width() - wargs.width) / 2;
wargs.xSet = true;
}
if (!wargs.ySet) {
wargs.y = screenRect.y() + (screenRect.height() - wargs.height) / 2;
wargs.ySet = true;
}
}
wargs.dialog = true;
wargs.resizable = WindowFeatures::boolFeature(features, "resizable");
wargs.scrollbarsVisible = WindowFeatures::boolFeature(features, "scroll", true);
wargs.statusBarVisible = WindowFeatures::boolFeature(features, "status", !trusted);
wargs.menuBarVisible = false;
wargs.toolBarVisible = false;
wargs.locationBarVisible = false;
wargs.fullscreen = false;
Frame* dialogFrame = createWindow(exec, frame, url, "", wargs, dialogArgs);
if (!dialogFrame)
return jsUndefined();
Window* dialogWindow = Window::retrieveWindow(dialogFrame);
// Get the return value either just before clearing the dialog window's
// properties (in Window::clear), or when on return from runModal.
JSValue* returnValue = 0;
dialogWindow->setReturnValueSlot(&returnValue);
dialogFrame->page()->chrome()->runModal();
dialogWindow->setReturnValueSlot(0);
// If we don't have a return value, get it now.
// Either Window::clear was not called yet, or there was no return value,
// and in that case, there's no harm in trying again (no benefit either).
if (!returnValue)
returnValue = dialogWindow->getDirect("returnValue");
return returnValue ? returnValue : jsUndefined();
}
JSValue *Window::getValueProperty(ExecState *exec, int token) const
{
ASSERT(impl()->frame());
switch (token) {
case Crypto:
return jsUndefined(); // FIXME: implement this
case Event_:
if (!allowsAccessFrom(exec))
return jsUndefined();
if (!d->m_evt)
return jsUndefined();
return toJS(exec, d->m_evt);
case Location_:
return location();
case Navigator_:
case ClientInformation: {
if (!allowsAccessFrom(exec))
return jsUndefined();
// Store the navigator in the object so we get the same one each time.
Navigator* n = new Navigator(exec->lexicalGlobalObject()->objectPrototype(), impl()->frame());
// FIXME: this will make the "navigator" object accessible from windows that fail
// the security check the first time, but not subsequent times, seems weird.
const_cast<Window *>(this)->putDirect("navigator", n, DontDelete);
const_cast<Window *>(this)->putDirect("clientInformation", n, DontDelete);
return n;
}
case Image:
if (!allowsAccessFrom(exec))
return jsUndefined();
// FIXME: this property (and the few below) probably shouldn't create a new object every
// time
return new ImageConstructorImp(exec, impl()->frame()->document());
case Option:
if (!allowsAccessFrom(exec))
return jsUndefined();
return new JSHTMLOptionElementConstructor(exec, impl()->frame()->document());
case XMLHttpRequest:
if (!allowsAccessFrom(exec))
return jsUndefined();
return new JSXMLHttpRequestConstructorImp(exec, impl()->frame()->document());
case Audio:
#if ENABLE(VIDEO)
if (!allowsAccessFrom(exec))
return jsUndefined();
if (!MediaPlayer::isAvailable())
return jsUndefined();
return new JSAudioConstructor(exec, impl()->frame()->document());
#else
return jsUndefined();
#endif
#if ENABLE(XSLT)
case XSLTProcessor_:
if (!allowsAccessFrom(exec))
return jsUndefined();
return new XSLTProcessorConstructorImp(exec);
#else
case XSLTProcessor_:
return jsUndefined();
#endif
}
if (!allowsAccessFrom(exec))
return jsUndefined();
switch (token) {
case Onabort:
return getListener(exec, abortEvent);
case Onblur:
return getListener(exec, blurEvent);
case Onchange:
return getListener(exec, changeEvent);
case Onclick:
return getListener(exec, clickEvent);
case Ondblclick:
return getListener(exec, dblclickEvent);
case Onerror:
return getListener(exec, errorEvent);
case Onfocus:
return getListener(exec, focusEvent);
case Onkeydown:
return getListener(exec, keydownEvent);
case Onkeypress:
return getListener(exec, keypressEvent);
case Onkeyup:
return getListener(exec, keyupEvent);
case Onload:
return getListener(exec, loadEvent);
case Onmousedown:
return getListener(exec, mousedownEvent);
case Onmousemove:
return getListener(exec, mousemoveEvent);
case Onmouseout:
return getListener(exec, mouseoutEvent);
case Onmouseover:
return getListener(exec, mouseoverEvent);
case Onmouseup:
return getListener(exec, mouseupEvent);
case OnWindowMouseWheel:
return getListener(exec, mousewheelEvent);
case Onreset:
return getListener(exec, resetEvent);
case Onresize:
return getListener(exec,resizeEvent);
case Onscroll:
return getListener(exec,scrollEvent);
case Onsearch:
return getListener(exec,searchEvent);
case Onselect:
return getListener(exec,selectEvent);
case Onsubmit:
return getListener(exec,submitEvent);
case Onbeforeunload:
return getListener(exec, beforeunloadEvent);
case Onunload:
return getListener(exec, unloadEvent);
}
ASSERT_NOT_REACHED();
return jsUndefined();
}
JSValue* Window::childFrameGetter(ExecState*, JSObject*, const Identifier& propertyName, const PropertySlot& slot)
{
return retrieve(static_cast<Window*>(slot.slotBase())->impl()->frame()->tree()->child(AtomicString(propertyName)));
}
JSValue* Window::indexGetter(ExecState*, JSObject*, const Identifier&, const PropertySlot& slot)
{
return retrieve(static_cast<Window*>(slot.slotBase())->impl()->frame()->tree()->child(slot.index()));
}
JSValue* Window::namedItemGetter(ExecState* exec, JSObject* originalObject, const Identifier& propertyName, const PropertySlot& slot)
{
Window* thisObj = static_cast<Window*>(slot.slotBase());
Document* doc = thisObj->impl()->frame()->document();
ASSERT(thisObj->allowsAccessFrom(exec));
ASSERT(doc);
ASSERT(doc->isHTMLDocument());
RefPtr<WebCore::HTMLCollection> collection = doc->windowNamedItems(propertyName);
if (collection->length() == 1)
return toJS(exec, collection->firstItem());
return toJS(exec, collection.get());
}
bool Window::getOwnPropertySlot(ExecState* exec, const Identifier& propertyName, PropertySlot& slot)
{
// Check for child frames by name before built-in properties to
// match Mozilla. This does not match IE, but some sites end up
// naming frames things that conflict with window properties that
// are in Moz but not IE. Since we have some of these, we have to do
// it the Moz way.
if (impl()->frame()->tree()->child(propertyName)) {
slot.setCustom(this, childFrameGetter);
return true;
}
const HashEntry* entry = Lookup::findEntry(&WindowTable, propertyName);
if (entry) {
if (entry->attr & Function) {
if (entry->value.functionValue == windowProtoFuncShowModalDialog) {
if (!canShowModalDialog(impl()->frame()))
return false;
}
if (allowsAccessFrom(exec))
slot.setStaticEntry(this, entry, staticFunctionGetter);
else
slot.setUndefined(this);
} else
slot.setStaticEntry(this, entry, staticValueGetter<Window>);
return true;
}
// Do prototype lookup early so that functions and attributes in the prototype can have
// precedence over the index and name getters.
JSValue* proto = prototype();
if (proto->isObject()) {
if (static_cast<JSObject*>(proto)->getOwnPropertySlot(exec, propertyName, slot)) {
if (!allowsAccessFrom(exec))
slot.setUndefined(this);
return true;
}
}
// FIXME: Search the whole frame hierachy somewhere around here.
// We need to test the correct priority order.
// allow window[1] or parent[1] etc. (#56983)
bool ok;
unsigned i = propertyName.toArrayIndex(&ok);
if (ok && i < impl()->frame()->tree()->childCount()) {
slot.setCustomIndex(this, i, indexGetter);
return true;
}
if (!allowsAccessFrom(exec)) {
slot.setUndefined(this);
return true;
}
// Allow shortcuts like 'Image1' instead of document.images.Image1
Document* doc = impl()->frame()->document();
if (doc && doc->isHTMLDocument()) {
AtomicString atomicPropertyName = propertyName;
if (static_cast<HTMLDocument*>(doc)->hasNamedItem(atomicPropertyName) || doc->getElementById(atomicPropertyName)) {
slot.setCustom(this, namedItemGetter);
return true;
}
}
return Base::getOwnPropertySlot(exec, propertyName, slot);
}
void Window::put(ExecState* exec, const Identifier& propertyName, JSValue* value, int attr)
{
const HashEntry* entry = Lookup::findEntry(&WindowTable, propertyName);
if (entry) {
if (entry->attr & Function) {
if (allowsAccessFrom(exec))
Base::put(exec, propertyName, value, attr);
return;
}
if (entry->attr & ReadOnly)
return;
switch (entry->value.intValue) {
case Location_: {
if (Frame* p = Window::retrieveActive(exec)->impl()->frame()) {
// To avoid breaking old widgets, make "var location =" in a top-level frame create
// a property named "location" instead of performing a navigation (<rdar://problem/5688039>).
if (Settings* settings = p->settings()) {
if (settings->usesDashboardBackwardCompatibilityMode() && !p->tree()->parent()) {
if (allowsAccessFrom(exec))
putDirect(propertyName, value, attr);
return;
}
}
if (!p->loader()->shouldAllowNavigation(impl()->frame()))
return;
String dstUrl = p->loader()->completeURL(value->toString(exec)).string();
if (!dstUrl.startsWith("javascript:", false) || allowsAccessFrom(exec)) {
bool userGesture = p->scriptProxy()->processingUserGesture();
// We want a new history item if this JS was called via a user gesture
impl()->frame()->loader()->scheduleLocationChange(dstUrl, p->loader()->outgoingReferrer(), false, userGesture);
}
}
return;
}
case Onabort:
if (allowsAccessFrom(exec))
setListener(exec, abortEvent,value);
return;
case Onblur:
if (allowsAccessFrom(exec))
setListener(exec, blurEvent,value);
return;
case Onchange:
if (allowsAccessFrom(exec))
setListener(exec, changeEvent,value);
return;
case Onclick:
if (allowsAccessFrom(exec))
setListener(exec,clickEvent,value);
return;
case Ondblclick:
if (allowsAccessFrom(exec))
setListener(exec, dblclickEvent,value);
return;
case Onerror:
if (allowsAccessFrom(exec))
setListener(exec, errorEvent, value);
return;
case Onfocus:
if (allowsAccessFrom(exec))
setListener(exec,focusEvent,value);
return;
case Onkeydown:
if (allowsAccessFrom(exec))
setListener(exec,keydownEvent,value);
return;
case Onkeypress:
if (allowsAccessFrom(exec))
setListener(exec,keypressEvent,value);
return;
case Onkeyup:
if (allowsAccessFrom(exec))
setListener(exec,keyupEvent,value);
return;
case Onload:
if (allowsAccessFrom(exec))
setListener(exec,loadEvent,value);
return;
case Onmousedown:
if (allowsAccessFrom(exec))
setListener(exec,mousedownEvent,value);
return;
case Onmousemove:
if (allowsAccessFrom(exec))
setListener(exec,mousemoveEvent,value);
return;
case Onmouseout:
if (allowsAccessFrom(exec))
setListener(exec,mouseoutEvent,value);
return;
case Onmouseover:
if (allowsAccessFrom(exec))
setListener(exec,mouseoverEvent,value);
return;
case Onmouseup:
if (allowsAccessFrom(exec))
setListener(exec,mouseupEvent,value);
return;
case OnWindowMouseWheel:
if (allowsAccessFrom(exec))
setListener(exec, mousewheelEvent,value);
return;
case Onreset:
if (allowsAccessFrom(exec))
setListener(exec,resetEvent,value);
return;
case Onresize:
if (allowsAccessFrom(exec))
setListener(exec,resizeEvent,value);
return;
case Onscroll:
if (allowsAccessFrom(exec))
setListener(exec,scrollEvent,value);
return;
case Onsearch:
if (allowsAccessFrom(exec))
setListener(exec,searchEvent,value);
return;
case Onselect:
if (allowsAccessFrom(exec))
setListener(exec,selectEvent,value);
return;
case Onsubmit:
if (allowsAccessFrom(exec))
setListener(exec,submitEvent,value);
return;
case Onbeforeunload:
if (allowsAccessFrom(exec))
setListener(exec, beforeunloadEvent, value);
return;
case Onunload:
if (allowsAccessFrom(exec))
setListener(exec, unloadEvent, value);
return;
default:
break;
}
}
if (allowsAccessFrom(exec))
Base::put(exec, propertyName, value, attr);
}
bool Window::allowsAccessFrom(const JSGlobalObject* other) const
{
SecurityOrigin::Reason reason;
if (allowsAccessFromPrivate(other, reason))
return true;
printErrorMessage(crossDomainAccessErrorMessage(other, reason));
return false;
}
bool Window::allowsAccessFrom(ExecState* exec) const
{
SecurityOrigin::Reason reason;
if (allowsAccessFromPrivate(exec, reason))
return true;
printErrorMessage(crossDomainAccessErrorMessage(exec->dynamicGlobalObject(), reason));
return false;
}
bool Window::allowsAccessFromNoErrorMessage(ExecState* exec) const
{
SecurityOrigin::Reason reason;
return allowsAccessFromPrivate(exec, reason);
}
bool Window::allowsAccessFrom(ExecState* exec, String& message) const
{
SecurityOrigin::Reason reason;
if (allowsAccessFromPrivate(exec, reason))
return true;
message = crossDomainAccessErrorMessage(exec->dynamicGlobalObject(), reason);
return false;
}
ALWAYS_INLINE bool Window::allowsAccessFromPrivate(const ExecState* exec, SecurityOrigin::Reason& reason) const
{
if (allowsAccessFromPrivate(exec->dynamicGlobalObject(), reason))
return true;
if (reason == SecurityOrigin::DomainSetInDOMMismatch) {
// If the only reason the access failed was a domainSetInDOM bit mismatch, try again against
// lexical global object <rdar://problem/5698200>
if (allowsAccessFromPrivate(exec->lexicalGlobalObject(), reason))
return true;
}
return false;
}
ALWAYS_INLINE bool Window::allowsAccessFromPrivate(const JSGlobalObject* other, SecurityOrigin::Reason& reason) const
{
const Frame* originFrame = static_cast<const Window*>(other)->impl()->frame();
if (!originFrame) {
reason = SecurityOrigin::GenericMismatch;
return false;
}
const Frame* targetFrame = impl()->frame();
if (originFrame == targetFrame)
return true;
if (!targetFrame) {
reason = SecurityOrigin::GenericMismatch;
return false;
}
WebCore::Document* targetDocument = targetFrame->document();
// JS may be attempting to access the "window" object, which should be valid,
// even if the document hasn't been constructed yet. If the document doesn't
// exist yet allow JS to access the window object.
if (!targetDocument)
return true;
WebCore::Document* originDocument = originFrame->document();
const SecurityOrigin* originSecurityOrigin = originDocument->securityOrigin();
const SecurityOrigin* targetSecurityOrigin = targetDocument->securityOrigin();
if (originSecurityOrigin->canAccess(targetSecurityOrigin, reason))
return true;
return false;
}
String Window::crossDomainAccessErrorMessage(const JSGlobalObject* other, SecurityOrigin::Reason) const
{
const Frame* originFrame = static_cast<const Window*>(other)->impl()->frame();
const Frame* targetFrame = impl()->frame();
if (!originFrame || !targetFrame)
return String();
WebCore::Document* targetDocument = targetFrame->document();
WebCore::Document* originDocument = originFrame->document();
if (!originDocument || !targetDocument)
return String();
// FIXME: this error message should contain more specifics of why the same origin check has failed.
return String::format("Unsafe JavaScript attempt to access frame with URL %s from frame with URL %s. Domains, protocols and ports must match.\n",
targetDocument->url().utf8().data(), originDocument->url().utf8().data());
}
void Window::printErrorMessage(const String& message) const
{
if (message.isEmpty())
return;
Frame* frame = impl()->frame();
if (!frame)
return;
if (frame->settings()->privateBrowsingEnabled())
return;
if (Interpreter::shouldPrintExceptions())
printf("%s", message.utf8().data());
if (Page* page = frame->page())
page->chrome()->addMessageToConsole(JSMessageSource, ErrorMessageLevel, message, 1, String()); // FIXME: provide a real line number and source URL.
}
ExecState* Window::globalExec()
{
// We need to make sure that any script execution happening in this
// frame does not destroy it
ASSERT(impl()->frame());
impl()->frame()->keepAlive();
return Base::globalExec();
}
bool Window::shouldInterruptScript() const
{
ASSERT(impl()->frame());
Page* page = impl()->frame()->page();
// See <rdar://problem/5479443>. We don't think that page can ever be NULL
// in this case, but if it is, we've gotten into a state where we may have
// hung the UI, with no way to ask the client whether to cancel execution.
// For now, our solution is just to cancel execution no matter what,
// ensuring that we never hang. We might want to consider other solutions
// if we discover problems with this one.
ASSERT(page);
if (!page)
return true;
return page->chrome()->shouldInterruptJavaScript();
}
void Window::setListener(ExecState* exec, const AtomicString& eventType, JSValue* func)
{
ASSERT(impl()->frame());
Document* doc = impl()->frame()->document();
if (!doc)
return;
doc->setHTMLWindowEventListener(eventType, findOrCreateJSEventListener(func, true));
}
JSValue* Window::getListener(ExecState* exec, const AtomicString& eventType) const
{
ASSERT(impl()->frame());
Document* doc = impl()->frame()->document();
if (!doc)
return jsUndefined();
WebCore::EventListener* listener = doc->getHTMLWindowEventListener(eventType);
if (listener && static_cast<JSEventListener*>(listener)->listenerObj())
return static_cast<JSEventListener*>(listener)->listenerObj();
return jsNull();
}
JSEventListener* Window::findJSEventListener(JSValue* val, bool html)
{
if (!val->isObject())
return 0;
JSObject* object = static_cast<JSObject*>(val);
ListenersMap& listeners = html ? d->jsHTMLEventListeners : d->jsEventListeners;
return listeners.get(object);
}
JSEventListener* Window::findOrCreateJSEventListener(JSValue* val, bool html)
{
JSEventListener* listener = findJSEventListener(val, html);
if (listener)
return listener;
if (!val->isObject())
return 0;
JSObject* object = static_cast<JSObject*>(val);
// Note that the JSEventListener constructor adds it to our jsEventListeners list
return new JSEventListener(object, this, html);
}
JSUnprotectedEventListener* Window::findJSUnprotectedEventListener(JSValue* val, bool html)
{
if (!val->isObject())
return 0;
JSObject* object = static_cast<JSObject*>(val);
UnprotectedListenersMap& listeners = html ? d->jsUnprotectedHTMLEventListeners : d->jsUnprotectedEventListeners;
return listeners.get(object);
}
JSUnprotectedEventListener* Window::findOrCreateJSUnprotectedEventListener(JSValue* val, bool html)
{
JSUnprotectedEventListener* listener = findJSUnprotectedEventListener(val, html);
if (listener)
return listener;
if (!val->isObject())
return 0;
JSObject* object = static_cast<JSObject*>(val);
// The JSUnprotectedEventListener constructor adds it to our jsUnprotectedEventListeners map.
return new JSUnprotectedEventListener(object, this, html);
}
void Window::clearHelperObjectProperties()
{
d->loc = 0;
d->m_evt = 0;
}
void Window::clear()
{
JSLock lock;
if (d->m_returnValueSlot && !*d->m_returnValueSlot)
*d->m_returnValueSlot = getDirect("returnValue");
clearAllTimeouts();
clearHelperObjectProperties();
// Now recreate a working global object for the next URL that will use us; but only if we haven't been
// disconnected yet
if (Frame* frame = impl()->frame())
frame->scriptProxy()->globalObject()->reset(JSDOMWindowPrototype::self());
// there's likely to be lots of garbage now
gcController().garbageCollectSoon();
}
void Window::setCurrentEvent(Event* evt)
{
d->m_evt = evt;
}
Event* Window::currentEvent()
{
return d->m_evt;
}
JSValue* windowProtoFuncAToB(ExecState* exec, JSObject* thisObj, const List& args)
{
if (!thisObj->inherits(&Window::info))
return throwError(exec, TypeError);
if (!static_cast<Window*>(thisObj)->allowsAccessFrom(exec))
return jsUndefined();
if (args.size() < 1)
return throwError(exec, SyntaxError, "Not enough arguments");
JSValue* v = args[0];
if (v->isNull())
return jsString();
UString s = v->toString(exec);
if (!s.is8Bit()) {
setDOMException(exec, INVALID_CHARACTER_ERR);
return jsUndefined();
}
Vector<char> in(s.size());
for (int i = 0; i < s.size(); ++i)
in[i] = static_cast<char>(s.data()[i].unicode());
Vector<char> out;
if (!base64Decode(in, out))
return throwError(exec, GeneralError, "Cannot decode base64");
return jsString(String(out.data(), out.size()));
}
JSValue* windowProtoFuncBToA(ExecState* exec, JSObject* thisObj, const List& args)
{
if (!thisObj->inherits(&Window::info))
return throwError(exec, TypeError);
if (!static_cast<Window*>(thisObj)->allowsAccessFrom(exec))
return jsUndefined();
if (args.size() < 1)
return throwError(exec, SyntaxError, "Not enough arguments");
JSValue* v = args[0];
if (v->isNull())
return jsString();
UString s = v->toString(exec);
if (!s.is8Bit()) {
setDOMException(exec, INVALID_CHARACTER_ERR);
return jsUndefined();
}
Vector<char> in(s.size());
for (int i = 0; i < s.size(); ++i)
in[i] = static_cast<char>(s.data()[i].unicode());
Vector<char> out;
base64Encode(in, out);
return jsString(String(out.data(), out.size()));
}
JSValue* windowProtoFuncOpen(ExecState* exec, JSObject* thisObj, const List& args)
{
if (!thisObj->inherits(&Window::info))
return throwError(exec, TypeError);
Window* window = static_cast<Window*>(thisObj);
if (!window->allowsAccessFrom(exec))
return jsUndefined();
Frame* frame = window->impl()->frame();
if (!frame)
return jsUndefined();
Frame* activeFrame = Window::retrieveActive(exec)->impl()->frame();
if (!activeFrame)
return jsUndefined();
Page* page = frame->page();
String urlString = valueToStringWithUndefinedOrNullCheck(exec, args[0]);
AtomicString frameName = args[1]->isUndefinedOrNull() ? "_blank" : AtomicString(args[1]->toString(exec));
// Because FrameTree::find() returns true for empty strings, we must check for empty framenames.
// Otherwise, illegitimate window.open() calls with no name will pass right through the popup blocker.
if (!allowPopUp(exec) && (frameName.isEmpty() || !frame->tree()->find(frameName)))
return jsUndefined();
// Get the target frame for the special cases of _top and _parent. In those
// cases, we can schedule a location change right now and return early.
bool topOrParent = false;
if (frameName == "_top") {
frame = frame->tree()->top();
topOrParent = true;
} else if (frameName == "_parent") {
if (Frame* parent = frame->tree()->parent())
frame = parent;
topOrParent = true;
}
if (topOrParent) {
if (!activeFrame->loader()->shouldAllowNavigation(frame))
return jsUndefined();
String completedURL;
if (!urlString.isEmpty())
completedURL = activeFrame->document()->completeURL(urlString);
const Window* targetedWindow = Window::retrieveWindow(frame);
if (!completedURL.isEmpty() && (!completedURL.startsWith("javascript:", false) || (targetedWindow && targetedWindow->allowsAccessFrom(exec)))) {
bool userGesture = activeFrame->scriptProxy()->processingUserGesture();
frame->loader()->scheduleLocationChange(completedURL, activeFrame->loader()->outgoingReferrer(), false, userGesture);
}
return Window::retrieve(frame);
}
// In the case of a named frame or a new window, we'll use the createWindow() helper
WindowFeatures windowFeatures(valueToStringWithUndefinedOrNullCheck(exec, args[2]));
FloatRect windowRect(windowFeatures.x, windowFeatures.y, windowFeatures.width, windowFeatures.height);
WebCore::DOMWindow::adjustWindowRect(screenAvailableRect(page->mainFrame()->view()), windowRect, windowRect);
windowFeatures.x = windowRect.x();
windowFeatures.y = windowRect.y();
windowFeatures.height = windowRect.height();
windowFeatures.width = windowRect.width();
frame = createWindow(exec, frame, urlString, frameName, windowFeatures, 0);
if (!frame)
return jsUndefined();
return Window::retrieve(frame); // global object
}
JSValue* windowProtoFuncSetTimeout(ExecState* exec, JSObject* thisObj, const List& args)
{
if (!thisObj->inherits(&Window::info))
return throwError(exec, TypeError);
Window* window = static_cast<Window*>(thisObj);
if (!window->allowsAccessFrom(exec))
return jsUndefined();
JSValue* v = args[0];
if (v->isString())
return jsNumber(window->installTimeout(v->toString(exec), args[1]->toInt32(exec), true /*single shot*/));
if (v->isObject() && static_cast<JSObject*>(v)->implementsCall()) {
List argsTail;
args.getSlice(2, argsTail);
return jsNumber(window->installTimeout(v, argsTail, args[1]->toInt32(exec), true /*single shot*/));
}
return jsUndefined();
}
JSValue* windowProtoFuncClearTimeout(ExecState* exec, JSObject* thisObj, const List& args)
{
// Also the implementation for window.clearInterval()
if (!thisObj->inherits(&Window::info))
return throwError(exec, TypeError);
Window* window = static_cast<Window*>(thisObj);
if (!window->allowsAccessFrom(exec))
return jsUndefined();
window->clearTimeout(args[0]->toInt32(exec));
return jsUndefined();
}
JSValue* windowProtoFuncSetInterval(ExecState* exec, JSObject* thisObj, const List& args)
{
if (!thisObj->inherits(&Window::info))
return throwError(exec, TypeError);
Window* window = static_cast<Window*>(thisObj);
if (!window->allowsAccessFrom(exec))
return jsUndefined();
if (args.size() >= 2) {
JSValue* v = args[0];
int delay = args[1]->toInt32(exec);
if (v->isString())
return jsNumber(window->installTimeout(v->toString(exec), delay, false));
if (v->isObject() && static_cast<JSObject*>(v)->implementsCall()) {
List argsTail;
args.getSlice(2, argsTail);
return jsNumber(window->installTimeout(v, argsTail, delay, false));
}
}
return jsUndefined();
}
JSValue* windowProtoFuncAddEventListener(ExecState* exec, JSObject* thisObj, const List& args)
{
if (!thisObj->inherits(&Window::info))
return throwError(exec, TypeError);
Window* window = static_cast<Window*>(thisObj);
if (!window->allowsAccessFrom(exec))
return jsUndefined();
Frame* frame = window->impl()->frame();
if (!frame)
return jsUndefined();
if (JSEventListener* listener = window->findOrCreateJSEventListener(args[1])) {
if (Document* doc = frame->document())
doc->addWindowEventListener(AtomicString(args[0]->toString(exec)), listener, args[2]->toBoolean(exec));
}
return jsUndefined();
}
JSValue* windowProtoFuncRemoveEventListener(ExecState* exec, JSObject* thisObj, const List& args)
{
if (!thisObj->inherits(&Window::info))
return throwError(exec, TypeError);
Window* window = static_cast<Window*>(thisObj);
if (!window->allowsAccessFrom(exec))
return jsUndefined();
Frame* frame = window->impl()->frame();
if (!frame)
return jsUndefined();
if (JSEventListener* listener = window->findJSEventListener(args[1])) {
if (Document* doc = frame->document())
doc->removeWindowEventListener(AtomicString(args[0]->toString(exec)), listener, args[2]->toBoolean(exec));
}
return jsUndefined();
}
JSValue* windowProtoFuncShowModalDialog(ExecState* exec, JSObject* thisObj, const List& args)
{
if (!thisObj->inherits(&Window::info))
return throwError(exec, TypeError);
Window* window = static_cast<Window*>(thisObj);
Frame* frame = window->impl()->frame();
if (!frame)
return jsUndefined();
return showModalDialog(exec, frame, valueToStringWithUndefinedOrNullCheck(exec, args[0]), args[1], valueToStringWithUndefinedOrNullCheck(exec, args[2]));
}
JSValue* windowProtoFuncNotImplemented(ExecState* exec, JSObject* thisObj, const List& args)
{
if (!thisObj->inherits(&Window::info))
return throwError(exec, TypeError);
return jsUndefined();
}
void Window::setReturnValueSlot(JSValue** slot)
{
d->m_returnValueSlot = slot;
}
////////////////////// timeouts ////////////////////////
void Window::clearAllTimeouts()
{
deleteAllValues(d->m_timeouts);
d->m_timeouts.clear();
}
int Window::installTimeout(WebCore::ScheduledAction* a, int t, bool singleShot)
{
int timeoutId = ++lastUsedTimeoutId;
// avoid wraparound going negative on us
if (timeoutId <= 0)
timeoutId = 1;
int nestLevel = timerNestingLevel + 1;
DOMWindowTimer* timer = new DOMWindowTimer(timeoutId, nestLevel, this, a);
ASSERT(!d->m_timeouts.get(timeoutId));
d->m_timeouts.set(timeoutId, timer);
// Use a minimum interval of 10 ms to match other browsers, but only once we've
// nested enough to notice that we're repeating.
// Faster timers might be "better", but they're incompatible.
double interval = max(0.001, t * 0.001);
if (interval < cMinimumTimerInterval && nestLevel >= cMaxTimerNestingLevel)
interval = cMinimumTimerInterval;
if (singleShot)
timer->startOneShot(interval);
else
timer->startRepeating(interval);
return timeoutId;
}
int Window::installTimeout(const UString& handler, int t, bool singleShot)
{
return installTimeout(new WebCore::ScheduledAction(handler), t, singleShot);
}
int Window::installTimeout(JSValue* func, const List& args, int t, bool singleShot)
{
return installTimeout(new WebCore::ScheduledAction(func, args), t, singleShot);
}
WebCore::PausedTimeouts* Window::pauseTimeouts()
{
size_t count = d->m_timeouts.size();
if (count == 0)
return 0;
PausedTimeout* t = new PausedTimeout [count];
PausedTimeouts* result = new PausedTimeouts(t, count);
WindowPrivate::TimeoutsMap::iterator it = d->m_timeouts.begin();
for (size_t i = 0; i != count; ++i, ++it) {
int timeoutId = it->first;
DOMWindowTimer* timer = it->second;
t[i].timeoutId = timeoutId;
t[i].nestingLevel = timer->nestingLevel();
t[i].nextFireInterval = timer->nextFireInterval();
t[i].repeatInterval = timer->repeatInterval();
t[i].action = timer->takeAction();
}
ASSERT(it == d->m_timeouts.end());
deleteAllValues(d->m_timeouts);
d->m_timeouts.clear();
return result;
}
void Window::resumeTimeouts(PausedTimeouts* timeouts)
{
if (!timeouts)
return;
size_t count = timeouts->numTimeouts();
PausedTimeout* array = timeouts->takeTimeouts();
for (size_t i = 0; i != count; ++i) {
int timeoutId = array[i].timeoutId;
DOMWindowTimer* timer = new DOMWindowTimer(timeoutId, array[i].nestingLevel, this, array[i].action);
d->m_timeouts.set(timeoutId, timer);
timer->start(array[i].nextFireInterval, array[i].repeatInterval);
}
delete [] array;
}
void Window::clearTimeout(int timeoutId, bool delAction)
{
// timeout IDs have to be positive, and 0 and -1 are unsafe to
// even look up since they are the empty and deleted value
// respectively
if (timeoutId <= 0)
return;
delete d->m_timeouts.take(timeoutId);
}
void Window::timerFired(DOMWindowTimer* timer)
{
// Simple case for non-one-shot timers.
if (timer->isActive()) {
int timeoutId = timer->timeoutId();
timer->action()->execute(this);
// The DOMWindowTimer object may have been deleted or replaced during execution,
// so we re-fetch it.
timer = d->m_timeouts.get(timeoutId);
if (!timer)
return;
if (timer->repeatInterval() && timer->repeatInterval() < cMinimumTimerInterval) {
timer->setNestingLevel(timer->nestingLevel() + 1);
if (timer->nestingLevel() >= cMaxTimerNestingLevel)
timer->augmentRepeatInterval(cMinimumTimerInterval - timer->repeatInterval());
}
return;
}
// Delete timer before executing the action for one-shot timers.
WebCore::ScheduledAction* action = timer->takeAction();
d->m_timeouts.remove(timer->timeoutId());
delete timer;
action->execute(this);
JSLock lock;
delete action;
}
void Window::disconnectFrame()
{
clearAllTimeouts();
if (d->loc)
d->loc->m_frame = 0;
}
Window::ListenersMap& Window::jsEventListeners()
{
return d->jsEventListeners;
}
Window::ListenersMap& Window::jsHTMLEventListeners()
{
return d->jsHTMLEventListeners;
}
Window::UnprotectedListenersMap& Window::jsUnprotectedEventListeners()
{
return d->jsUnprotectedEventListeners;
}
Window::UnprotectedListenersMap& Window::jsUnprotectedHTMLEventListeners()
{
return d->jsUnprotectedHTMLEventListeners;
}
/////////////////////////////////////////////////////////////////////////////
void DOMWindowTimer::fired()
{
timerNestingLevel = m_nestingLevel;
m_object->timerFired(this);
timerNestingLevel = 0;
}
} // namespace KJS
using namespace KJS;
namespace WebCore {
JSValue* toJS(ExecState*, DOMWindow* domWindow)
{
if (!domWindow)
return jsNull();
Frame* frame = domWindow->frame();
if (!frame)
return jsNull();
return Window::retrieve(frame);
}
} // namespace WebCore