diff -r 000000000000 -r dd21522fd290 webengine/osswebengine/WebCore/bindings/js/kjs_window.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/webengine/osswebengine/WebCore/bindings/js/kjs_window.cpp Mon Mar 30 12:54:55 2009 +0300 @@ -0,0 +1,1921 @@ +// -*- c-basic-offset: 4 -*- +/* + * Copyright (C) 2000 Harri Porten (porten@kde.org) + * Copyright (C) 2006 Jon Shier (jshier@iastate.edu) + * Copyright (C) 2003, 2004, 2005, 2006, 2007 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" + +#if PLATFORM(SYMBIAN) +#include "../../bidi.h" +#endif + +#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 "FrameView.h" +#include "GCController.h" +#include "HTMLDocument.h" +#include "JSCSSRule.h" +#include "JSCSSValue.h" +#include "JSDOMExceptionConstructor.h" +#include "JSDOMWindow.h" +#include "JSEvent.h" +#include "JSHTMLCollection.h" +#include "JSHTMLOptionElementConstructor.h" +#include "JSMutationEvent.h" +#include "JSNode.h" +#include "JSNodeFilter.h" +#include "JSRange.h" +#include "JSXMLHttpRequest.h" +#include "Logging.h" +#include "Page.h" +#include "PlatformScreen.h" +#include "PlugInInfoStore.h" +#include "RenderView.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 +#include "symbian/kjs_console.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_dialogArguments(0) + , m_returnValueSlot(0) + { + } + + Window::ListenersMap jsEventListeners; + Window::ListenersMap jsHTMLEventListeners; + Window::UnprotectedListenersMap jsUnprotectedEventListeners; + Window::UnprotectedListenersMap jsUnprotectedHTMLEventListeners; + mutable Location* loc; + WebCore::Event *m_evt; + JSValue* m_dialogArguments; + JSValue** m_returnValueSlot; + typedef HashMap TimeoutsMap; + TimeoutsMap m_timeouts; +}; + +class DOMWindowTimer : public TimerBase { +public: + DOMWindowTimer(int timeoutId, int nestingLevel, Window* o, ScheduledAction* a) + : m_timeoutId(timeoutId), m_nestingLevel(nestingLevel), m_object(o), m_action(a) { } + 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; } + + ScheduledAction* action() const { return m_action; } + ScheduledAction* takeAction() { ScheduledAction* a = m_action; m_action = 0; return a; } + +private: + virtual void fired(); + + int m_timeoutId; + int m_nestingLevel; + Window* m_object; + ScheduledAction* m_action; +}; + +class PausedTimeout { +public: + int timeoutId; + int nestingLevel; + double nextFireInterval; + double repeatInterval; + ScheduledAction *action; +}; + +} // namespace KJS + +#include "kjs_window.lut.h" + +namespace KJS { + +////////////////////// Window Object //////////////////////// + +const ClassInfo Window::info = { "Window", 0, &WindowTable, 0 }; + +/* +@begin WindowTable 119 +# Warning, when adding a function to this object you need to add a case in Window::get +# -- Functions -- + atob Window::AToB DontDelete|Function 1 + btoa Window::BToA DontDelete|Function 1 + scroll Window::Scroll DontDelete|Function 2 + scrollBy Window::ScrollBy DontDelete|Function 2 + scrollTo Window::ScrollTo DontDelete|Function 2 + moveBy Window::MoveBy DontDelete|Function 2 + moveTo Window::MoveTo DontDelete|Function 2 + resizeBy Window::ResizeBy DontDelete|Function 2 + resizeTo Window::ResizeTo DontDelete|Function 2 + open Window::Open DontDelete|Function 3 + setTimeout Window::SetTimeout DontDelete|Function 2 + clearTimeout Window::ClearTimeout DontDelete|Function 1 + setInterval Window::SetInterval DontDelete|Function 2 + clearInterval Window::ClearInterval DontDelete|Function 1 + captureEvents Window::CaptureEvents DontDelete|Function 0 + releaseEvents Window::ReleaseEvents DontDelete|Function 0 + addEventListener Window::AddEventListener DontDelete|Function 3 + removeEventListener Window::RemoveEventListener DontDelete|Function 3 + showModalDialog Window::ShowModalDialog DontDelete|Function 1 +# -- Attributes -- + crypto Window::Crypto DontDelete|ReadOnly + event Window::Event_ DontDelete + location Window::Location_ DontDelete + navigator Window::Navigator_ DontDelete|ReadOnly + clientInformation Window::ClientInformation DontDelete|ReadOnly + console Window::Console_ 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 -- + DOMException Window::DOMException DontDelete + Image Window::Image DontDelete + Option Window::Option DontDelete + XMLHttpRequest Window::XMLHttpRequest DontDelete + XSLTProcessor Window::XSLTProcessor_ DontDelete +@end +*/ + +Window::Window(DOMWindow* window) + : m_impl(window) + , d(new WindowPrivate) +{ +} + +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(); +} + +ScriptInterpreter* Window::interpreter() const +{ + Frame* frame = impl()->frame(); + if (!frame) + return 0; + + return frame->scriptProxy()->interpreter(); +} + +Window *Window::retrieveWindow(Frame *f) +{ + JSObject *o = retrieve(f)->getObject(); + + ASSERT(o || !f->settings() || !f->settings()->isJavaScriptEnabled()); + return static_cast(o); +} + +Window *Window::retrieveActive(ExecState *exec) +{ + JSValue *imp = exec->dynamicInterpreter()->globalObject(); + ASSERT(imp); + return static_cast(imp); +} + +JSValue *Window::retrieve(Frame *p) +{ + ASSERT(p); + if (KJSProxy *proxy = p->scriptProxy()) + return proxy->interpreter()->globalObject(); // the Global object is the "window" + + return jsUndefined(); // This can happen with JS disabled on the domain of that window +} + +Location *Window::location() const +{ + if (!d->loc) + d->loc = new Location(impl()->frame()); + return d->loc; +} + +ConsoleObject *Window::console(ExecState *exec) const +{ + return new ConsoleObject(exec, impl()->frame()->page()->chrome()); +} + +// reference our special objects during garbage collection +void Window::mark() +{ + JSObject::mark(); + if (d->loc && !d->loc->marked()) + d->loc->mark(); +} + +static bool allowPopUp(ExecState *exec, Window *window) +{ + Frame* frame = window->impl()->frame(); + if (!frame) + return false; + if (static_cast(exec->dynamicInterpreter())->wasRunByUserGesture()) + return true; + Settings* settings = frame->settings(); + return settings && settings->JavaScriptCanOpenWindowsAutomatically(); +} + +static HashMap parseModalDialogFeatures(ExecState *exec, JSValue *featuresArg) +{ + HashMap map; + + Vector features = valueToStringWithUndefinedOrNullCheck(exec, featuresArg).split(';'); + Vector::const_iterator end = features.end(); + for (Vector::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 bool boolFeature(const HashMap& features, const char* key, bool defaultValue = false) +{ + HashMap::const_iterator it = features.find(key); + if (it == features.end()) + return defaultValue; + const String& value = it->second; + return value.isNull() || value == "1" || value == "yes" || value == "on"; +} + +static float floatFeature(const HashMap &features, const char *key, float min, float max, float defaultValue) +{ + HashMap::const_iterator it = features.find(key); + if (it == features.end()) + return defaultValue; + // FIXME: Can't distinguish "0q" from string with no digits in it -- both return d == 0 and ok == false. + // Would be good to tell them apart somehow since string with no digits should be default value and + // "0q" should be minimum value. + bool ok; + double d = it->second.toDouble(&ok); + if ((d == 0 && !ok) || isnan(d)) + return defaultValue; + if (d < min || max <= min) + return min; + if (d > max) + return max; + return static_cast(d); +} + +static Frame* createWindow(ExecState* exec, Frame* openerFrame, const String& url, + const String& frameName, const WindowFeatures& windowFeatures) +{ + Frame* activeFrame = Window::retrieveActive(exec)->impl()->frame(); + + ResourceRequest request; + if (activeFrame) + 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 isSafeScript 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; + Frame* newFrame = openerFrame->loader()->createWindow(frameRequest, windowFeatures, created); + if (!newFrame) + return 0; + + newFrame->loader()->setOpener(openerFrame); + newFrame->loader()->setOpenedByDOM(); + + Window* newWindow = Window::retrieveWindow(newFrame); + + if (!url.startsWith("javascript:", false) || newWindow->isSafeScript(exec)) { + String completedURL = url.isEmpty() ? url : activeFrame->document()->completeURL(url); + bool userGesture = static_cast(exec->dynamicInterpreter())->wasRunByUserGesture(); + + if (created) { + newFrame->loader()->changeLocation(KURL(completedURL.deprecatedString()), activeFrame->loader()->outgoingReferrer(), false, userGesture); + if (Document* oldDoc = openerFrame->document()) { + newFrame->document()->setDomainInternal(oldDoc->domain()); + newFrame->document()->setBaseURL(oldDoc->baseURL()); + } + } else if (!url.isEmpty()) + newFrame->loader()->scheduleLocationChange(completedURL, activeFrame->loader()->outgoingReferrer(), false, userGesture); + } + + return newFrame; +} + +static bool canShowModalDialog(const Window *window) +{ + Frame* frame = window->impl()->frame(); + if (!frame) + return false; + + return frame->page()->chrome()->canRunModal(); +} + +static bool canShowModalDialogNow(const Window *window) +{ + Frame* frame = window->impl()->frame(); + if (!frame) + return false; + + return frame->page()->chrome()->canRunModalNow(); +} + +static JSValue* showModalDialog(ExecState* exec, Window* openerWindow, const List& args) +{ + if (!canShowModalDialogNow(openerWindow) || !allowPopUp(exec, openerWindow)) + return jsUndefined(); + + const HashMap features = parseModalDialogFeatures(exec, args[2]); + + bool trusted = false; + + WindowFeatures wargs; + + // 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"); + Frame* frame = openerWindow->impl()->frame(); + if (!frame) + return jsUndefined(); + + FloatRect screenRect = screenAvailableRect(frame->view()); + + wargs.width = floatFeature(features, "dialogwidth", 100, screenRect.width(), 620); // default here came from frame size of dialog in MacIE + wargs.widthSet = true; + wargs.height = floatFeature(features, "dialogheight", 100, screenRect.height(), 450); // default here came from frame size of dialog in MacIE + wargs.heightSet = true; + + wargs.x = floatFeature(features, "dialogleft", screenRect.x(), screenRect.right() - wargs.width, -1); + wargs.xSet = wargs.x > 0; + wargs.y = floatFeature(features, "dialogtop", screenRect.y(), screenRect.bottom() - wargs.height, -1); + wargs.ySet = wargs.y > 0; + + if (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 = boolFeature(features, "resizable"); + wargs.scrollbarsVisible = boolFeature(features, "scroll", true); + wargs.statusBarVisible = boolFeature(features, "status", !trusted); + wargs.menuBarVisible = false; + wargs.toolBarVisible = false; + wargs.locationBarVisible = false; + wargs.fullscreen = false; + + Frame* dialogFrame = createWindow(exec, frame, valueToStringWithUndefinedOrNullCheck(exec, args[0]), "", wargs); + if (!dialogFrame) + return jsUndefined(); + + Window* dialogWindow = Window::retrieveWindow(dialogFrame); + + dialogWindow->putDirect("dialogArguments", args[1]); + + // 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->setDialogArgumentsAndReturnValueSlot(args[1], &returnValue); + dialogFrame->page()->chrome()->runModal(); + dialogWindow->setDialogArgumentsAndReturnValueSlot(0, 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 Console_: + return console(exec); + case Crypto: + if (!isSafeScript(exec)) + return jsUndefined(); + return jsUndefined(); // FIXME: implement this + case DOMException: + if (!isSafeScript(exec)) + return jsUndefined(); + return getDOMExceptionConstructor(exec); + case Event_: + if (!isSafeScript(exec)) + return jsUndefined(); + if (!d->m_evt) + return jsUndefined(); + return toJS(exec, d->m_evt); + case Location_: + return location(); + case Navigator_: + case ClientInformation: { + if (!isSafeScript(exec)) + return jsUndefined(); + // Store the navigator in the object so we get the same one each time. + Navigator *n = new Navigator(exec, 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(this)->putDirect("navigator", n, DontDelete|ReadOnly); + const_cast(this)->putDirect("clientInformation", n, DontDelete|ReadOnly); + return n; + } + case Image: + if (!isSafeScript(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 (!isSafeScript(exec)) + return jsUndefined(); + return new JSHTMLOptionElementConstructor(exec, impl()->frame()->document()); + case XMLHttpRequest: + if (!isSafeScript(exec)) + return jsUndefined(); + return new JSXMLHttpRequestConstructorImp(exec, impl()->frame()->document()); +#if ENABLE(XSLT) + case XSLTProcessor_: + if (!isSafeScript(exec)) + return jsUndefined(); + return new XSLTProcessorConstructorImp(exec); +#else + case XSLTProcessor_: + return jsUndefined(); +#endif + } + + if (!isSafeScript(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(0); + return jsUndefined(); +} + +JSValue* Window::childFrameGetter(ExecState*, JSObject*, const Identifier& propertyName, const PropertySlot& slot) +{ + return retrieve(static_cast(slot.slotBase())->impl()->frame()->tree()->child(AtomicString(propertyName))); +} + +JSValue* Window::indexGetter(ExecState*, JSObject*, const Identifier&, const PropertySlot& slot) +{ + return retrieve(static_cast(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(slot.slotBase()); + Document *doc = thisObj->impl()->frame()->document(); + ASSERT(thisObj->isSafeScript(exec) && doc && doc->isHTMLDocument()); + + String name = propertyName; + RefPtr collection = doc->windowNamedItems(name); + 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) { + switch (entry->value) { + case ShowModalDialog: + if (!canShowModalDialog(this)) + return false; + // fall through + default: + if (isSafeScript(exec)) + slot.setStaticEntry(this, entry, staticFunctionGetter); + else + slot.setUndefined(this); + } + } else + slot.setStaticEntry(this, entry, staticValueGetter); + 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; + } + + // allow shortcuts like 'Image1' instead of document.images.Image1 + Document* doc = impl()->frame()->document(); + if (doc && doc->isHTMLDocument()) { + if (!isSafeScript(exec)) { + slot.setUndefined(this); + return true; + } + + AtomicString atomicPropertyName = propertyName; + if (static_cast(doc)->hasNamedItem(atomicPropertyName) || doc->getElementById(atomicPropertyName)) { + slot.setCustom(this, namedItemGetter); + return true; + } + } + + if (!isSafeScript(exec)) { + slot.setUndefined(this); + return true; + } + + return JSObject::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 (isSafeScript(exec)) + JSObject::put(exec, propertyName, value, attr); + return; + } + if (entry->attr & ReadOnly) + return; + + switch (entry->value) { + case Location_: { + Frame* p = Window::retrieveActive(exec)->impl()->frame(); + if (p) { + if (!p->loader()->shouldAllowNavigation(impl()->frame())) + return; + DeprecatedString dstUrl = p->loader()->completeURL(DeprecatedString(value->toString(exec))).url(); + if (!dstUrl.startsWith("javascript:", false) || isSafeScript(exec)) { + bool userGesture = static_cast(exec->dynamicInterpreter())->wasRunByUserGesture(); + // 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 (isSafeScript(exec)) + setListener(exec, abortEvent,value); + return; + case Onblur: + if (isSafeScript(exec)) + setListener(exec, blurEvent,value); + return; + case Onchange: + if (isSafeScript(exec)) + setListener(exec, changeEvent,value); + return; + case Onclick: + if (isSafeScript(exec)) + setListener(exec,clickEvent,value); + return; + case Ondblclick: + if (isSafeScript(exec)) + setListener(exec, dblclickEvent,value); + return; + case Onerror: + if (isSafeScript(exec)) + setListener(exec, errorEvent, value); + return; + case Onfocus: + if (isSafeScript(exec)) + setListener(exec,focusEvent,value); + return; + case Onkeydown: + if (isSafeScript(exec)) + setListener(exec,keydownEvent,value); + return; + case Onkeypress: + if (isSafeScript(exec)) + setListener(exec,keypressEvent,value); + return; + case Onkeyup: + if (isSafeScript(exec)) + setListener(exec,keyupEvent,value); + return; + case Onload: + if (isSafeScript(exec)) + setListener(exec,loadEvent,value); + return; + case Onmousedown: + if (isSafeScript(exec)) + setListener(exec,mousedownEvent,value); + return; + case Onmousemove: + if (isSafeScript(exec)) + setListener(exec,mousemoveEvent,value); + return; + case Onmouseout: + if (isSafeScript(exec)) + setListener(exec,mouseoutEvent,value); + return; + case Onmouseover: + if (isSafeScript(exec)) + setListener(exec,mouseoverEvent,value); + return; + case Onmouseup: + if (isSafeScript(exec)) + setListener(exec,mouseupEvent,value); + return; + case OnWindowMouseWheel: + if (isSafeScript(exec)) + setListener(exec, mousewheelEvent,value); + return; + case Onreset: + if (isSafeScript(exec)) + setListener(exec,resetEvent,value); + return; + case Onresize: + if (isSafeScript(exec)) + setListener(exec,resizeEvent,value); + return; + case Onscroll: + if (isSafeScript(exec)) + setListener(exec,scrollEvent,value); + return; + case Onsearch: + if (isSafeScript(exec)) + setListener(exec,searchEvent,value); + return; + case Onselect: + if (isSafeScript(exec)) + setListener(exec,selectEvent,value); + return; + case Onsubmit: + if (isSafeScript(exec)) + setListener(exec,submitEvent,value); + return; + case Onbeforeunload: + if (isSafeScript(exec)) + setListener(exec, beforeunloadEvent, value); + return; + case Onunload: + if (isSafeScript(exec)) + setListener(exec, unloadEvent, value); + return; + default: + break; + } + } + if (isSafeScript(exec)) + JSObject::put(exec, propertyName, value, attr); +} + +static bool shouldLoadAsEmptyDocument(const KURL &url) +{ + return url.protocol().lower() == "about" || url.isEmpty(); +} + +bool Window::isSafeScript(const ScriptInterpreter *origin, const ScriptInterpreter *target) +{ + if (origin == target) + return true; + + Frame* originFrame = origin->frame(); + Frame* targetFrame = target->frame(); + + // 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 (!targetFrame->document()) + return true; + + WebCore::Document *originDocument = originFrame->document(); + WebCore::Document *targetDocument = targetFrame->document(); + + if (!targetDocument) { + return false; + } + + WebCore::String targetDomain = targetDocument->domain(); + + // Always allow local pages to execute any JS. + if (targetDomain.isNull()) + return true; + + WebCore::String originDomain = originDocument->domain(); + + // if this document is being initially loaded as empty by its parent + // or opener, allow access from any document in the same domain as + // the parent or opener. + if (shouldLoadAsEmptyDocument(targetFrame->loader()->url())) { + Frame* ancestorFrame = targetFrame->loader()->opener() ? targetFrame->loader()->opener() : targetFrame->tree()->parent(); + while (ancestorFrame && shouldLoadAsEmptyDocument(ancestorFrame->loader()->url())) + ancestorFrame = ancestorFrame->tree()->parent(); + if (ancestorFrame) + originDomain = ancestorFrame->document()->domain(); + } + + if ( targetDomain == originDomain ) + return true; + + if (Interpreter::shouldPrintExceptions()) { + printf("Unsafe JavaScript attempt to access frame with URL %s from frame with URL %s. Domains must match.\n", + targetDocument->URL().latin1(), originDocument->URL().latin1()); + } + String message = String::format("Unsafe JavaScript attempt to access frame with URL %s from frame with URL %s. Domains must match.\n", + targetDocument->URL().latin1(), originDocument->URL().latin1()); + if (Page* page = targetFrame->page()) + page->chrome()->addMessageToConsole(JSMessageSource, ErrorMessageLevel, message, 1, String()); // FIXME: provide a real line number and source URL. + + return false; +} + +bool Window::isSafeScript(ExecState *exec) const +{ + Frame* frame = impl()->frame(); + if (!frame) + return false; + Frame* activeFrame = static_cast(exec->dynamicInterpreter())->frame(); + if (!activeFrame) + return false; + if (activeFrame == frame) + return true; + + WebCore::Document* thisDocument = frame->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 (!thisDocument) + return true; + + WebCore::Document* actDocument = activeFrame->document(); + + const SecurityOrigin& actSecurityOrigin = actDocument->securityOrigin(); + const SecurityOrigin& thisSecurityOrigin = thisDocument->securityOrigin(); + + if (actSecurityOrigin.canAccess(thisSecurityOrigin)) + return true; + + // FIXME: this error message should contain more specifics of why the same origin check has failed. + String message = String::format("Unsafe JavaScript attempt to access frame with URL %s from frame with URL %s. Domains, protocols and ports must match.\n", + thisDocument->URL().utf8().data(), actDocument->URL().utf8().data()); + + if (Interpreter::shouldPrintExceptions()) + printf("%s", message.utf8().data()); + + if (Page* page = frame->page()) + page->chrome()->addMessageToConsole(JSMessageSource, ErrorMessageLevel, message, 1, String()); + + return false; +} + +void Window::setListener(ExecState *exec, const AtomicString &eventType, JSValue *func) +{ + if (!isSafeScript(exec)) + return; + Frame* frame = impl()->frame(); + if (!frame) + return; + Document* doc = frame->document(); + if (!doc) + return; + + doc->setHTMLWindowEventListener(eventType, findOrCreateJSEventListener(func,true)); +} + +JSValue *Window::getListener(ExecState *exec, const AtomicString &eventType) const +{ + if (!isSafeScript(exec)) + return jsUndefined(); + Frame* frame = impl()->frame(); + if (!frame) + return jsUndefined(); + Document* doc = frame->document(); + if (!doc) + return jsUndefined(); + + WebCore::EventListener *listener = doc->getHTMLWindowEventListener(eventType); + if (listener && static_cast(listener)->listenerObj()) + return static_cast(listener)->listenerObj(); + else + return jsNull(); +} + +JSEventListener* Window::findJSEventListener(JSValue* val, bool html) +{ + if (!val->isObject()) + return 0; + JSObject* object = static_cast(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(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(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(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(); + clearProperties(); + clearHelperObjectProperties(); + setPrototype(JSDOMWindowPrototype::self()); // clear the prototype + + // 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()->interpreter()->initGlobalObject(); + + if (d->m_dialogArguments) + putDirect("dialogArguments", d->m_dialogArguments); + + // there's likely to be lots of garbage now + gcController().garbageCollectSoon(); +} + +void Window::setCurrentEvent(Event *evt) +{ + d->m_evt = evt; +} + +static void setWindowFeature(const String& keyString, const String& valueString, WindowFeatures& windowFeatures) +{ + int value; + + if (valueString.length() == 0 || // listing a key with no value is shorthand for key=yes + valueString == "yes") + value = 1; + else + value = valueString.toInt(); + + if (keyString == "left" || keyString == "screenx") { + windowFeatures.xSet = true; + windowFeatures.x = value; + } else if (keyString == "top" || keyString == "screeny") { + windowFeatures.ySet = true; + windowFeatures.y = value; + } else if (keyString == "width" || keyString == "innerwidth") { + windowFeatures.widthSet = true; + windowFeatures.width = value; + } else if (keyString == "height" || keyString == "innerheight") { + windowFeatures.heightSet = true; + windowFeatures.height = value; + } else if (keyString == "menubar") + windowFeatures.menuBarVisible = value; + else if (keyString == "toolbar") + windowFeatures.toolBarVisible = value; + else if (keyString == "location") + windowFeatures.locationBarVisible = value; + else if (keyString == "status") + windowFeatures.statusBarVisible = value; + else if (keyString == "resizable") + windowFeatures.resizable = value; + else if (keyString == "fullscreen") + windowFeatures.fullscreen = value; + else if (keyString == "scrollbars") + windowFeatures.scrollbarsVisible = value; +} + +// Though isspace() considers \t and \v to be whitespace, Win IE doesn't. +static bool isSeparator(::UChar c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '=' || c == ',' || c == '\0'; +} + +static void parseWindowFeatures(const String& features, WindowFeatures& windowFeatures) +{ + /* + The IE rule is: all features except for channelmode and fullscreen default to YES, but + if the user specifies a feature string, all features default to NO. (There is no public + standard that applies to this method.) + + + */ + + windowFeatures.dialog = false; + windowFeatures.fullscreen = false; + + windowFeatures.xSet = false; + windowFeatures.ySet = false; + windowFeatures.widthSet = false; + windowFeatures.heightSet = false; + + if (features.length() == 0) { + windowFeatures.menuBarVisible = true; + windowFeatures.statusBarVisible = true; + windowFeatures.toolBarVisible = true; + windowFeatures.locationBarVisible = true; + windowFeatures.scrollbarsVisible = true; + windowFeatures.resizable = true; + + return; + } + + windowFeatures.menuBarVisible = false; + windowFeatures.statusBarVisible = false; + windowFeatures.toolBarVisible = false; + windowFeatures.locationBarVisible = false; + windowFeatures.scrollbarsVisible = false; + windowFeatures.resizable = false; + + // Tread lightly in this code -- it was specifically designed to mimic Win IE's parsing behavior. + int keyBegin, keyEnd; + int valueBegin, valueEnd; + + int i = 0; + int length = features.length(); + String buffer = features.lower(); + while (i < length) { + // skip to first non-separator, but don't skip past the end of the string + while (isSeparator(buffer[i])) { + if (i >= length) + break; + i++; + } + keyBegin = i; + + // skip to first separator + while (!isSeparator(buffer[i])) + i++; + keyEnd = i; + + // skip to first '=', but don't skip past a ',' or the end of the string + while (buffer[i] != '=') { + if (buffer[i] == ',' || i >= length) + break; + i++; + } + + // skip to first non-separator, but don't skip past a ',' or the end of the string + while (isSeparator(buffer[i])) { + if (buffer[i] == ',' || i >= length) + break; + i++; + } + valueBegin = i; + + // skip to first separator + while (!isSeparator(buffer[i])) + i++; + valueEnd = i; + + ASSERT(i <= length); + + String keyString(buffer.substring(keyBegin, keyEnd - keyBegin)); + String valueString(buffer.substring(valueBegin, valueEnd - valueBegin)); + setWindowFeature(keyString, valueString, windowFeatures); + } +} + +// FIXME instead of the screen should we actually check the workspace? +static void constrainToVisible(const FloatRect& screen, FloatRect& window) +{ + float x = window.x() + screen.x(); + if (x < screen.x() || x >= screen.right()) + x = screen.x(); // only safe choice until size is determined + window.setX(x); + + float y = window.y() + screen.y(); + if (y < screen.y() || y >= screen.bottom()) + y = screen.y(); // only safe choice until size is determined + window.setY(y); + + float height = window.height(); + if (height > screen.height()) + height = screen.height(); + if (height < 100) + height = 100; + window.setHeight(height); + + float width = window.width(); + if (width > screen.width()) + width = screen.width(); + if (width < 100) + width = 100; + window.setWidth(width); +} + +JSValue *WindowFunc::callAsFunction(ExecState *exec, JSObject *thisObj, const List &args) +{ + if (!thisObj->inherits(&Window::info)) + return throwError(exec, TypeError); + Window *window = static_cast(thisObj); + Frame *frame = window->impl()->frame(); + if (!frame) + return jsUndefined(); + + FrameView *widget = frame->view(); + Page* page = frame->page(); + JSValue *v = args[0]; + UString s = v->toString(exec); + String str = s; + String str2; + + switch (id) { + case Window::AToB: + case Window::BToA: { + if (args.size() < 1) + return throwError(exec, SyntaxError, "Not enough arguments"); + if (v->isNull()) + return jsString(); + if (!s.is8Bit()) { + setDOMException(exec, INVALID_CHARACTER_ERR); + return jsUndefined(); + } + + Vector in(s.size()); + for (int i = 0; i < s.size(); ++i) + in[i] = static_cast(s.data()[i].unicode()); + Vector out; + + if (id == Window::AToB) { + if (!base64Decode(in, out)) + return throwError(exec, GeneralError, "Cannot decode base64"); + } else + base64Encode(in, out); + + return jsString(String(out.data(), out.size())); + } + case Window::Open: + { + Frame* activeFrame = Window::retrieveActive(exec)->impl()->frame(); + if (!activeFrame) + return jsUndefined(); + + 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, window) && (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; + if (!activeFrame->loader()->shouldAllowNavigation(frame)) + return jsUndefined(); + topOrParent = true; + } + + if (topOrParent) { + String completedURL; + if (!urlString.isEmpty()) + completedURL = activeFrame->document()->completeURL(urlString); + + const Window* window = Window::retrieveWindow(frame); + if (!completedURL.isEmpty() && (!completedURL.startsWith("javascript:", false) || (window && window->isSafeScript(exec)))) { + bool userGesture = static_cast(exec->dynamicInterpreter())->wasRunByUserGesture(); + 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; + String features = valueToStringWithUndefinedOrNullCheck(exec, args[2]); + parseWindowFeatures(features, windowFeatures); + FloatRect windowRect(windowFeatures.x, windowFeatures.y, windowFeatures.width, windowFeatures.height); + constrainToVisible(screenRect(page->mainFrame()->view()), windowRect); + + windowFeatures.x = windowRect.x(); + windowFeatures.y = windowRect.y(); + windowFeatures.height = windowRect.height(); + windowFeatures.width = windowRect.width(); + + frame = createWindow(exec, frame, urlString, frameName, windowFeatures); + + if (!frame) + return jsUndefined(); + + return Window::retrieve(frame); // global object + } + case Window::ScrollBy: + window->updateLayout(); + if(args.size() >= 2 && widget) + widget->scrollBy(args[0]->toInt32(exec), args[1]->toInt32(exec)); + return jsUndefined(); + case Window::Scroll: + case Window::ScrollTo: + window->updateLayout(); + if (args.size() >= 2 && widget) + widget->setContentsPos(args[0]->toInt32(exec), args[1]->toInt32(exec)); + return jsUndefined(); + case Window::MoveBy: + if (args.size() >= 2 && page) { + FloatRect fr = page->chrome()->windowRect(); + fr.move(args[0]->toFloat(exec), args[1]->toFloat(exec)); + // Security check (the spec talks about UniversalBrowserWrite to disable this check...) + constrainToVisible(screenRect(page->mainFrame()->view()), fr); + page->chrome()->setWindowRect(fr); + } + return jsUndefined(); + case Window::MoveTo: + if (args.size() >= 2 && page) { + FloatRect fr = page->chrome()->windowRect(); + FloatRect sr = screenRect(page->mainFrame()->view()); + fr.setLocation(sr.location()); + fr.move(args[0]->toFloat(exec), args[1]->toFloat(exec)); + // Security check (the spec talks about UniversalBrowserWrite to disable this check...) + constrainToVisible(sr, fr); + page->chrome()->setWindowRect(fr); + } + return jsUndefined(); + case Window::ResizeBy: + if (args.size() >= 2 && page) { + FloatRect r = page->chrome()->windowRect(); + FloatSize dest = r.size() + FloatSize(args[0]->toFloat(exec), args[1]->toFloat(exec)); + FloatRect fr = FloatRect(r.location(), dest); + constrainToVisible(screenRect(page->mainFrame()->view()), fr); + page->chrome()->setWindowRect(fr); + } + return jsUndefined(); + case Window::ResizeTo: + if (args.size() >= 2 && page) { + FloatRect r = page->chrome()->windowRect(); + FloatSize dest = FloatSize(args[0]->toFloat(exec), args[1]->toFloat(exec)); + FloatRect fr = FloatRect(r.location(), dest); + constrainToVisible(screenRect(page->mainFrame()->view()), fr); + page->chrome()->setWindowRect(fr); + } + return jsUndefined(); + case Window::SetTimeout: + if (!window->isSafeScript(exec)) + return jsUndefined(); + if (v->isString()) { + int i = args[1]->toInt32(exec); + int r = (const_cast(window))->installTimeout(s, i, true /*single shot*/); + return jsNumber(r); + } + else if (v->isObject() && static_cast(v)->implementsCall()) { + JSValue *func = args[0]; + int i = args[1]->toInt32(exec); + + // All arguments after the second should go to the function + // FIXME: could be more efficient + List funcArgs = args.copyTail().copyTail(); + + int r = (const_cast(window))->installTimeout(func, funcArgs, i, true /*single shot*/); + return jsNumber(r); + } + else + return jsUndefined(); + case Window::SetInterval: + if (!window->isSafeScript(exec)) + return jsUndefined(); + if (args.size() >= 2 && v->isString()) { + int i = args[1]->toInt32(exec); + int r = (const_cast(window))->installTimeout(s, i, false); + return jsNumber(r); + } + else if (args.size() >= 2 && v->isObject() && static_cast(v)->implementsCall()) { + JSValue *func = args[0]; + int i = args[1]->toInt32(exec); + + // All arguments after the second should go to the function + // FIXME: could be more efficient + List funcArgs = args.copyTail().copyTail(); + + int r = (const_cast(window))->installTimeout(func, funcArgs, i, false); + return jsNumber(r); + } + else + return jsUndefined(); + case Window::ClearTimeout: + case Window::ClearInterval: + if (!window->isSafeScript(exec)) + return jsUndefined(); + (const_cast(window))->clearTimeout(v->toInt32(exec)); + return jsUndefined(); + case Window::CaptureEvents: + case Window::ReleaseEvents: + // If anyone implements these, they need the safe script security check. + if (!window->isSafeScript(exec)) + return jsUndefined(); + // Not implemented. + return jsUndefined(); + case Window::AddEventListener: + if (!window->isSafeScript(exec)) + 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(); + case Window::RemoveEventListener: + if (!window->isSafeScript(exec)) + 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(); + case Window::ShowModalDialog: { + JSValue* result = showModalDialog(exec, window, args); + return result; + } + } + return jsUndefined(); +} + +void Window::updateLayout() const +{ + Frame* frame = impl()->frame(); + if (!frame) + return; + WebCore::Document* docimpl = frame->document(); + if (docimpl) + docimpl->updateLayoutIgnorePendingStylesheets(); +} + +void Window::setDialogArgumentsAndReturnValueSlot(JSValue* dialogArgs, JSValue** returnValueSlot) +{ + d->m_dialogArguments = dialogArgs; + d->m_returnValueSlot = returnValueSlot; +} + +////////////////////// ScheduledAction //////////////////////// + +void ScheduledAction::execute(Window* window) +{ + RefPtr frame = window->impl()->frame(); + if (!frame) + return; + + KJSProxy* scriptProxy = frame->scriptProxy(); + if (!scriptProxy) + return; + + RefPtr interpreter = scriptProxy->interpreter(); + + interpreter->setProcessingTimerCallback(true); + + if (JSValue* func = m_func.get()) { + JSLock lock; + if (func->isObject() && static_cast(func)->implementsCall()) { + ExecState* exec = interpreter->globalExec(); + ASSERT(window == interpreter->globalObject()); + interpreter->startTimeoutCheck(); + static_cast(func)->call(exec, window, m_args); + interpreter->stopTimeoutCheck(); + if (exec->hadException()) { + JSObject* exception = exec->exception()->toObject(exec); + exec->clearException(); + String message = exception->get(exec, exec->propertyNames().message)->toString(exec); + int lineNumber = exception->get(exec, "line")->toInt32(exec); + if (Interpreter::shouldPrintExceptions()) + printf("(timer):%s\n", message.utf8().data()); + if (Page* page = frame->page()) + page->chrome()->addMessageToConsole(JSMessageSource, ErrorMessageLevel, message, lineNumber, String()); + } + } + } else + frame->loader()->executeScript(m_code); + + // Update our document's rendering following the execution of the timeout callback. + // FIXME: Why not use updateDocumentsRendering to update rendering of all documents? + // FIXME: Is this really the right point to do the update? We need a place that works + // for all possible entry points that might possibly execute script, but this seems + // to be a bit too low-level. + if (Document* doc = frame->document()) + doc->updateRendering(); + + interpreter->setProcessingTimerCallback(false); +} + +////////////////////// timeouts //////////////////////// + +void Window::clearAllTimeouts() +{ + deleteAllValues(d->m_timeouts); + d->m_timeouts.clear(); +} + +int Window::installTimeout(ScheduledAction* a, int t, bool singleShot) +{ + +#if PLATFORM(SYMBIAN) + if (d->m_evt && d->m_evt->type() == "mouseover") { + impl()->frame()->page()->chrome()->setElementVisibilityChanged(true); + } +#endif + + int timeoutId = ++lastUsedTimeoutId; + 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 ScheduledAction(handler), t, singleShot); +} + +int Window::installTimeout(JSValue* func, const List& args, int t, bool singleShot) +{ + return installTimeout(new ScheduledAction(func, args), t, singleShot); +} + +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) +{ + WindowPrivate::TimeoutsMap::iterator it = d->m_timeouts.find(timeoutId); + if (it == d->m_timeouts.end()) + return; + DOMWindowTimer* timer = it->second; + d->m_timeouts.remove(it); + delete timer; +} + +void Window::timerFired(DOMWindowTimer* timer) +{ + // Simple case for non-one-shot timers. + if (timer->isActive()) { + int timeoutId = timer->timeoutId(); + + timer->action()->execute(this); + if (d->m_timeouts.contains(timeoutId) && 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. + 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; +} + +////////////////////// Location Object //////////////////////// + +const ClassInfo Location::info = { "Location", 0, &LocationTable, 0 }; +/* +@begin LocationTable 12 + assign Location::Assign DontDelete|Function 1 + hash Location::Hash DontDelete + host Location::Host DontDelete + hostname Location::Hostname DontDelete + href Location::Href DontDelete + pathname Location::Pathname DontDelete + port Location::Port DontDelete + protocol Location::Protocol DontDelete + search Location::Search DontDelete + toString Location::ToString DontEnum|DontDelete|Function 0 + replace Location::Replace DontDelete|Function 1 + reload Location::Reload DontDelete|Function 0 +@end +*/ +KJS_IMPLEMENT_PROTOTYPE_FUNCTION(LocationFunc) +Location::Location(Frame *p) : m_frame(p) +{ +} + +JSValue *Location::getValueProperty(ExecState *exec, int token) const +{ + KURL url = m_frame->loader()->url(); + switch (token) { + case Hash: + return jsString(url.ref().isNull() ? "" : "#" + url.ref()); + case Host: { + // Note: this is the IE spec. The NS spec swaps the two, it says + // "The hostname property is the concatenation of the host and port properties, separated by a colon." + // Bleh. + UString str = url.host(); + if (url.port()) + str += ":" + String::number((int)url.port()); + return jsString(str); + } + case Hostname: + return jsString(url.host()); + case Href: + if (!url.hasPath()) + return jsString(url.prettyURL() + "/"); + return jsString(url.prettyURL()); + case Pathname: + return jsString(url.path().isEmpty() ? "/" : url.path()); + case Port: + return jsString(url.port() ? String::number((int)url.port()) : ""); + case Protocol: + return jsString(url.protocol() + ":"); + case Search: + return jsString(url.query()); + default: + ASSERT(0); + return jsUndefined(); + } +} + +bool Location::getOwnPropertySlot(ExecState *exec, const Identifier& propertyName, PropertySlot& slot) +{ + if (!m_frame) + return false; + + const Window* window = Window::retrieveWindow(m_frame); + + const HashEntry *entry = Lookup::findEntry(&LocationTable, propertyName); + if (!entry || (entry->value != Replace && entry->value != Reload && entry->value != Assign)) + if (!window || !window->isSafeScript(exec)) { + slot.setUndefined(this); + return true; + } + + return getStaticPropertySlot(exec, &LocationTable, this, propertyName, slot); +} + +void Location::put(ExecState *exec, const Identifier &p, JSValue *v, int attr) +{ + if (!m_frame) + return; + + DeprecatedString str = v->toString(exec); + KURL url = m_frame->loader()->url(); + const Window* window = Window::retrieveWindow(m_frame); + bool sameDomainAccess = window && window->isSafeScript(exec); + + const HashEntry *entry = Lookup::findEntry(&LocationTable, p); + + if (entry) { + // cross-domain access to the location is allowed when assigning the whole location, + // but not when assigning the individual pieces, since that might inadvertently + // disclose other parts of the original location. + if (entry->value != Href && !sameDomainAccess) + return; + + switch (entry->value) { + case Href: { + Frame* frame = Window::retrieveActive(exec)->impl()->frame(); + if (!frame) + return; + if (!frame->loader()->shouldAllowNavigation(m_frame)) + return; + url = frame->loader()->completeURL(str).url(); + break; + } + case Hash: { + if (str.startsWith("#")) + str = str.mid(1); + + if (url.ref() == str) + return; + + url.setRef(str); + break; + } + case Host: { + url.setHostAndPort(str); + break; + } + case Hostname: + url.setHost(str); + break; + case Pathname: + url.setPath(str); + break; + case Port: + url.setPort(str.toUInt()); + break; + case Protocol: + url.setProtocol(str); + break; + case Search: + url.setQuery(str); + break; + default: + // Disallow changing other properties in LocationTable. e.g., "window.location.toString = ...". + // + return; + } + } else { + if (sameDomainAccess) + JSObject::put(exec, p, v, attr); + return; + } + + Frame* activeFrame = Window::retrieveActive(exec)->impl()->frame(); + if (!url.url().startsWith("javascript:", false) || sameDomainAccess) { + bool userGesture = static_cast(exec->dynamicInterpreter())->wasRunByUserGesture(); + m_frame->loader()->scheduleLocationChange(url.url(), activeFrame->loader()->outgoingReferrer(), false, userGesture); + } +} + +JSValue *LocationFunc::callAsFunction(ExecState *exec, JSObject *thisObj, const List &args) +{ + if (!thisObj->inherits(&Location::info)) + return throwError(exec, TypeError); + Location *location = static_cast(thisObj); + Frame *frame = location->frame(); + if (frame) { + + Window* window = Window::retrieveWindow(frame); + if (id != Location::Replace && !window->isSafeScript(exec)) + return jsUndefined(); + + switch (id) { + case Location::Replace: + { + Frame* p = Window::retrieveActive(exec)->impl()->frame(); + if (p) { + if (!p->loader()->shouldAllowNavigation(frame)) + return jsUndefined(); + DeprecatedString str = args[0]->toString(exec); + const Window* window = Window::retrieveWindow(frame); + if (!str.startsWith("javascript:", false) || (window && window->isSafeScript(exec))) { + bool userGesture = static_cast(exec->dynamicInterpreter())->wasRunByUserGesture(); + frame->loader()->scheduleLocationChange(p->loader()->completeURL(str).url(), p->loader()->outgoingReferrer(), true, userGesture); + } + } + break; + } + case Location::Reload: + { + const Window* window = Window::retrieveWindow(frame); + if (!frame->loader()->url().url().startsWith("javascript:", false) || (window && window->isSafeScript(exec))) { + bool userGesture = static_cast(exec->dynamicInterpreter())->wasRunByUserGesture(); + frame->loader()->scheduleRefresh(userGesture); + } + break; + } + case Location::Assign: + { + Frame *p = Window::retrieveActive(exec)->impl()->frame(); + if (p) { + if (!p->loader()->shouldAllowNavigation(frame)) + return jsUndefined(); + const Window *window = Window::retrieveWindow(frame); + DeprecatedString dstUrl = p->loader()->completeURL(DeprecatedString(args[0]->toString(exec))).url(); + if (!dstUrl.startsWith("javascript:", false) || (window && window->isSafeScript(exec))) { + bool userGesture = static_cast(exec->dynamicInterpreter())->wasRunByUserGesture(); + // We want a new history item if this JS was called via a user gesture + frame->loader()->scheduleLocationChange(dstUrl, p->loader()->outgoingReferrer(), false, userGesture); + } + } + break; + } + case Location::ToString: + if (!frame || !Window::retrieveWindow(frame)->isSafeScript(exec)) + return jsString(); + + if (!frame->loader()->url().hasPath()) + return jsString(frame->loader()->url().prettyURL() + "/"); + return jsString(frame->loader()->url().prettyURL()); + } + } + return jsUndefined(); +} + +///////////////////////////////////////////////////////////////////////////// + +PausedTimeouts::~PausedTimeouts() +{ + PausedTimeout *array = m_array; + if (!array) + return; + size_t count = m_length; + for (size_t i = 0; i != count; ++i) + delete array[i].action; + delete [] array; +} + +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