| /* |
| * This file is part of the KDE libraries |
| * Copyright (C) 2001 Peter Kelly (pmk@post.com) |
| * Copyright (C) 2003, 2004, 2005, 2006 Apple Computer, Inc. |
| * |
| * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| */ |
| |
| #include "config.h" |
| #include "kjs_events.h" |
| |
| #include "Document.h" |
| #include "EventNames.h" |
| #include "Frame.h" |
| #include "JSEvent.h" |
| #include "JSMutationEvent.h" |
| #include "JSWheelEvent.h" |
| #include "JSMouseEvent.h" |
| #include "JSKeyboardEvent.h" |
| #include "dom2_eventsimpl.h" |
| #include "HTMLNames.h" |
| #include "HTMLImageElement.h" |
| #include "kjs_proxy.h" |
| #include "kjs_window.h" |
| |
| #include "kjs_events.lut.h" |
| |
| using namespace WebCore; |
| using namespace EventNames; |
| using namespace HTMLNames; |
| |
| namespace KJS { |
| |
| JSAbstractEventListener::JSAbstractEventListener(bool _html) |
| : html(_html) |
| { |
| } |
| |
| void JSAbstractEventListener::handleEvent(Event* ele, bool isWindowEvent) |
| { |
| #ifdef KJS_DEBUGGER |
| if (KJSDebugWin::instance() && KJSDebugWin::instance()->inSession()) |
| return; |
| #endif |
| |
| Event *event = ele; |
| |
| JSObject* listener = listenerObj(); |
| if (!listener) |
| return; |
| |
| Window* window = windowObj(); |
| Frame *frame = window->frame(); |
| if (!frame) |
| return; |
| KJSProxy* proxy = frame->jScript(); |
| if (!proxy) |
| return; |
| |
| JSLock lock; |
| |
| ScriptInterpreter* interpreter = proxy->interpreter(); |
| ExecState* exec = interpreter->globalExec(); |
| |
| JSValue* handleEventFuncValue = listener->get(exec, "handleEvent"); |
| JSObject* handleEventFunc = 0; |
| if (handleEventFuncValue->isObject()) { |
| handleEventFunc = static_cast<JSObject*>(handleEventFuncValue); |
| if (!handleEventFunc->implementsCall()) |
| handleEventFunc = 0; |
| } |
| |
| if (handleEventFunc || listener->implementsCall()) { |
| ref(); |
| |
| List args; |
| args.append(toJS(exec, event)); |
| |
| // Set the event we're handling in the Window object |
| window->setCurrentEvent(event); |
| // ... and in the interpreter |
| interpreter->setCurrentEvent(event); |
| |
| JSValue* retval; |
| if (handleEventFunc) |
| retval = handleEventFunc->call(exec, listener, args); |
| else { |
| JSObject* thisObj; |
| if (isWindowEvent) |
| thisObj = window; |
| else |
| thisObj = static_cast<JSObject*>(toJS(exec, event->currentTarget())); |
| retval = listener->call(exec, thisObj, args); |
| } |
| |
| window->setCurrentEvent(0); |
| interpreter->setCurrentEvent(0); |
| |
| if (exec->hadException()) { |
| JSObject* exception = exec->exception()->toObject(exec); |
| String message = exception->get(exec, messagePropertyName)->toString(exec); |
| int lineNumber = exception->get(exec, "line")->toInt32(exec); |
| String sourceURL = exception->get(exec, "sourceURL")->toString(exec); |
| if (Interpreter::shouldPrintExceptions()) |
| printf("(event handler):%s\n", message.deprecatedString().utf8().data()); |
| frame->addMessageToConsole(message, lineNumber, sourceURL); |
| exec->clearException(); |
| } else { |
| if (!retval->isUndefinedOrNull() && event->storesResultAsString()) |
| event->storeResult(retval->toString(exec)); |
| if (html) { |
| bool retvalbool; |
| if (retval->getBoolean(retvalbool) && !retvalbool) |
| event->preventDefault(); |
| } |
| } |
| |
| Document::updateDocumentsRendering(); |
| deref(); |
| } |
| } |
| |
| bool JSAbstractEventListener::isHTMLEventListener() const |
| { |
| return html; |
| } |
| |
| // ------------------------------------------------------------------------- |
| |
| JSUnprotectedEventListener::JSUnprotectedEventListener(JSObject* _listener, Window* _win, bool _html) |
| : JSAbstractEventListener(_html) |
| , listener(_listener) |
| , win(_win) |
| { |
| if (_listener) |
| _win->jsUnprotectedEventListeners.set(_listener, this); |
| } |
| |
| JSUnprotectedEventListener::~JSUnprotectedEventListener() |
| { |
| if (listener && win) |
| win->jsUnprotectedEventListeners.remove(listener); |
| } |
| |
| JSObject* JSUnprotectedEventListener::listenerObj() const |
| { |
| return listener; |
| } |
| |
| Window* JSUnprotectedEventListener::windowObj() const |
| { |
| return win; |
| } |
| |
| void JSUnprotectedEventListener::clearWindowObj() |
| { |
| win = 0; |
| } |
| |
| void JSUnprotectedEventListener::mark() |
| { |
| if (listener && !listener->marked()) |
| listener->mark(); |
| } |
| |
| // ------------------------------------------------------------------------- |
| |
| JSEventListener::JSEventListener(JSObject* _listener, Window* _win, bool _html) |
| : JSAbstractEventListener(_html) |
| , listener(_listener) |
| , win(_win) |
| { |
| if (_listener) |
| _win->jsEventListeners.set(_listener, this); |
| } |
| |
| JSEventListener::~JSEventListener() |
| { |
| if (listener && win) |
| win->jsEventListeners.remove(listener); |
| } |
| |
| JSObject* JSEventListener::listenerObj() const |
| { |
| return listener; |
| } |
| |
| Window* JSEventListener::windowObj() const |
| { |
| return win; |
| } |
| |
| void JSEventListener::clearWindowObj() |
| { |
| win = 0; |
| } |
| |
| // ------------------------------------------------------------------------- |
| |
| JSLazyEventListener::JSLazyEventListener(const String& functionName, const String& code, Window* win, WebCore::Node* node, int lineno) |
| : JSEventListener(0, win, true) |
| , m_functionName(functionName) |
| , code(code) |
| , parsed(false) |
| , lineNumber(lineno) |
| , originalNode(node) |
| { |
| // We don't retain the original node because we assume it |
| // will stay alive as long as this handler object is around |
| // and we need to avoid a reference cycle. If JS transfers |
| // this handler to another node, parseCode will be called and |
| // then originalNode is no longer needed. |
| } |
| |
| JSObject* JSLazyEventListener::listenerObj() const |
| { |
| parseCode(); |
| return listener; |
| } |
| |
| JSValue* JSLazyEventListener::eventParameterName() const |
| { |
| static ProtectedPtr<JSValue> eventString = jsString("event"); |
| return eventString.get(); |
| } |
| |
| void JSLazyEventListener::parseCode() const |
| { |
| if (parsed) |
| return; |
| parsed = true; |
| |
| Frame *frame = windowObj()->frame(); |
| KJSProxy *proxy = 0; |
| if (frame) |
| proxy = frame->jScript(); |
| |
| if (proxy) { |
| ScriptInterpreter* interpreter = proxy->interpreter(); |
| ExecState* exec = interpreter->globalExec(); |
| |
| JSLock lock; |
| JSObject* constr = interpreter->builtinFunction(); |
| List args; |
| |
| UString sourceURL(frame->url().url()); |
| args.append(eventParameterName()); |
| args.append(jsString(code)); |
| listener = constr->construct(exec, args, m_functionName, sourceURL, lineNumber); // ### is globalExec ok ? |
| |
| if (exec->hadException()) { |
| exec->clearException(); |
| |
| // failed to parse, so let's just make this listener a no-op |
| listener = 0; |
| } else if (originalNode) { |
| // Add the event's home element to the scope |
| // (and the document, and the form - see JSHTMLElement::eventHandlerScope) |
| ScopeChain scope = listener->scope(); |
| |
| JSValue* thisObj = toJS(exec, originalNode); |
| if (thisObj->isObject()) { |
| static_cast<DOMEventTargetNode*>(thisObj)->pushEventHandlerScope(exec, scope); |
| listener->setScope(scope); |
| } |
| } |
| } |
| |
| // no more need to keep the unparsed code around |
| m_functionName = String(); |
| code = String(); |
| |
| if (listener) |
| windowObj()->jsEventListeners.set(listener, const_cast<JSLazyEventListener*>(this)); |
| } |
| |
| JSValue* getNodeEventListener(EventTargetNode* n, const AtomicString& eventType) |
| { |
| if (JSAbstractEventListener* listener = static_cast<JSAbstractEventListener*>(n->getHTMLEventListener(eventType))) |
| if (JSValue* obj = listener->listenerObj()) |
| return obj; |
| return jsNull(); |
| } |
| |
| // ------------------------------------------------------------------------- |
| |
| const ClassInfo DOMEvent::info = { "Event", 0, &DOMEventTable, 0 }; |
| /* |
| @begin DOMEventTable 12 |
| type DOMEvent::Type DontDelete|ReadOnly |
| target DOMEvent::Target DontDelete|ReadOnly |
| currentTarget DOMEvent::CurrentTarget DontDelete|ReadOnly |
| srcElement DOMEvent::SrcElement DontDelete|ReadOnly |
| eventPhase DOMEvent::EventPhase DontDelete|ReadOnly |
| bubbles DOMEvent::Bubbles DontDelete|ReadOnly |
| cancelable DOMEvent::Cancelable DontDelete|ReadOnly |
| timeStamp DOMEvent::TimeStamp DontDelete|ReadOnly |
| returnValue DOMEvent::ReturnValue DontDelete |
| cancelBubble DOMEvent::CancelBubble DontDelete |
| dataTransfer DOMEvent::DataTransfer DontDelete|ReadOnly |
| clipboardData DOMEvent::ClipboardData DontDelete|ReadOnly |
| @end |
| @begin DOMEventProtoTable 3 |
| stopPropagation DOMEvent::StopPropagation DontDelete|Function 0 |
| preventDefault DOMEvent::PreventDefault DontDelete|Function 0 |
| initEvent DOMEvent::InitEvent DontDelete|Function 3 |
| @end |
| */ |
| KJS_IMPLEMENT_PROTOFUNC(DOMEventProtoFunc) |
| KJS_IMPLEMENT_PROTOTYPE("DOMEvent", DOMEventProto, DOMEventProtoFunc) |
| |
| DOMEvent::DOMEvent(ExecState *exec, Event *e) |
| : m_impl(e), clipboard(0) |
| { |
| setPrototype(DOMEventProto::self(exec)); |
| } |
| |
| DOMEvent::~DOMEvent() |
| { |
| ScriptInterpreter::forgetDOMObject(m_impl.get()); |
| } |
| |
| // pass marks through to JS objects we hold during garbage collection |
| void DOMEvent::mark() |
| { |
| DOMObject::mark(); |
| if (clipboard && !clipboard->marked()) |
| clipboard->mark(); |
| } |
| |
| bool DOMEvent::getOwnPropertySlot(ExecState *exec, const Identifier& propertyName, PropertySlot& slot) |
| { |
| return getStaticValueSlot<DOMEvent, DOMObject>(exec, &DOMEventTable, this, propertyName, slot); |
| } |
| |
| JSValue *DOMEvent::getValueProperty(ExecState *exec, int token) const |
| { |
| Event &event = *m_impl; |
| switch (token) { |
| case Type: |
| return jsString(event.type()); |
| case Target: |
| case SrcElement: /*MSIE extension - "the object that fired the event"*/ |
| return toJS(exec, event.target()); |
| case CurrentTarget: |
| return toJS(exec, event.currentTarget()); |
| case EventPhase: |
| return jsNumber(event.eventPhase()); |
| case Bubbles: |
| return jsBoolean(event.bubbles()); |
| case CancelBubble: |
| return jsBoolean(event.getCancelBubble()); |
| case ReturnValue: |
| return jsBoolean(!event.defaultPrevented()); |
| case Cancelable: |
| return jsBoolean(event.cancelable()); |
| case TimeStamp: |
| return jsNumber(event.timeStamp()); |
| case ClipboardData: |
| { |
| if (event.isClipboardEvent()) { |
| ClipboardEvent *impl = static_cast<ClipboardEvent *>(&event); |
| if (!clipboard) |
| clipboard = new Clipboard(exec, impl->clipboard()); |
| return clipboard; |
| } else |
| return jsUndefined(); |
| } |
| case DataTransfer: |
| { |
| if (event.isDragEvent()) { |
| MouseEvent *impl = static_cast<MouseEvent *>(&event); |
| if (!clipboard) |
| clipboard = new Clipboard(exec, impl->clipboard()); |
| return clipboard; |
| } else |
| return jsUndefined(); |
| } |
| default: |
| return 0; |
| } |
| } |
| |
| void DOMEvent::put(ExecState *exec, const Identifier &propertyName, |
| JSValue *value, int attr) |
| { |
| lookupPut<DOMEvent, DOMObject>(exec, propertyName, value, attr, &DOMEventTable, this); |
| } |
| |
| void DOMEvent::putValueProperty(ExecState *exec, int token, JSValue *value, int) |
| { |
| Event &event = *m_impl; |
| switch (token) { |
| case ReturnValue: |
| event.setDefaultPrevented(!value->toBoolean(exec)); |
| break; |
| case CancelBubble: |
| event.setCancelBubble(value->toBoolean(exec)); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| JSValue *DOMEventProtoFunc::callAsFunction(ExecState *exec, JSObject * thisObj, const List &args) |
| { |
| if (!thisObj->inherits(&DOMEvent::info)) |
| return throwError(exec, TypeError); |
| Event &event = *static_cast<DOMEvent *>( thisObj )->impl(); |
| switch (id) { |
| case DOMEvent::StopPropagation: |
| event.stopPropagation(); |
| return jsUndefined(); |
| case DOMEvent::PreventDefault: |
| event.preventDefault(); |
| return jsUndefined(); |
| case DOMEvent::InitEvent: |
| event.initEvent(AtomicString(args[0]->toString(exec)), args[1]->toBoolean(exec), args[2]->toBoolean(exec)); |
| return jsUndefined(); |
| }; |
| return jsUndefined(); |
| } |
| |
| JSValue *toJS(ExecState *exec, Event *e) |
| { |
| if (!e) |
| return jsNull(); |
| ScriptInterpreter* interp = static_cast<ScriptInterpreter *>(exec->dynamicInterpreter()); |
| |
| JSLock lock; |
| |
| DOMObject *ret = interp->getDOMObject(e); |
| if (!ret) { |
| if (e->isKeyboardEvent()) |
| ret = new JSKeyboardEvent(exec, static_cast<KeyboardEvent *>(e)); |
| else if (e->isMouseEvent()) |
| ret = new JSMouseEvent(exec, static_cast<MouseEvent *>(e)); |
| else if (e->isWheelEvent()) |
| ret = new JSWheelEvent(exec, static_cast<WheelEvent *>(e)); |
| else if (e->isUIEvent()) |
| ret = new JSUIEvent(exec, static_cast<UIEvent *>(e)); |
| else if (e->isMutationEvent()) |
| ret = new JSMutationEvent(exec, static_cast<MutationEvent *>(e)); |
| else |
| ret = new JSEvent(exec, e); |
| |
| interp->putDOMObject(e, ret); |
| } |
| |
| return ret; |
| } |
| |
| Event *toEvent(JSValue *val) |
| { |
| if (!val || !val->isObject(&DOMEvent::info)) |
| return 0; |
| return static_cast<DOMEvent *>(val)->impl(); |
| } |
| |
| // ------------------------------------------------------------------------- |
| |
| const ClassInfo Clipboard::info = { "Clipboard", 0, &ClipboardTable, 0 }; |
| |
| /* Source for ClipboardTable. Use "make hashtables" to regenerate. |
| @begin ClipboardTable 3 |
| dropEffect Clipboard::DropEffect DontDelete |
| effectAllowed Clipboard::EffectAllowed DontDelete |
| types Clipboard::Types DontDelete|ReadOnly |
| @end |
| @begin ClipboardProtoTable 4 |
| clearData Clipboard::ClearData DontDelete|Function 0 |
| getData Clipboard::GetData DontDelete|Function 1 |
| setData Clipboard::SetData DontDelete|Function 2 |
| setDragImage Clipboard::SetDragImage DontDelete|Function 3 |
| @end |
| */ |
| |
| KJS_DEFINE_PROTOTYPE(ClipboardProto) |
| KJS_IMPLEMENT_PROTOFUNC(ClipboardProtoFunc) |
| KJS_IMPLEMENT_PROTOTYPE("Clipboard", ClipboardProto, ClipboardProtoFunc) |
| |
| Clipboard::Clipboard(ExecState *exec, WebCore::Clipboard *cb) |
| : clipboard(cb) |
| { |
| setPrototype(ClipboardProto::self(exec)); |
| } |
| |
| bool Clipboard::getOwnPropertySlot(ExecState *exec, const Identifier& propertyName, PropertySlot& slot) |
| { |
| return getStaticValueSlot<Clipboard, DOMObject>(exec, &ClipboardTable, this, propertyName, slot); |
| } |
| |
| JSValue *Clipboard::getValueProperty(ExecState *exec, int token) const |
| { |
| switch (token) { |
| case DropEffect: |
| assert(clipboard->isForDragging() || clipboard->dropEffect().isNull()); |
| return jsStringOrUndefined(clipboard->dropEffect()); |
| case EffectAllowed: |
| assert(clipboard->isForDragging() || clipboard->effectAllowed().isNull()); |
| return jsStringOrUndefined(clipboard->effectAllowed()); |
| case Types: |
| { |
| DeprecatedStringList qTypes = clipboard->types(); |
| if (qTypes.isEmpty()) |
| return jsNull(); |
| else { |
| List list; |
| for (DeprecatedStringList::Iterator it = qTypes.begin(); it != qTypes.end(); ++it) { |
| list.append(jsString(UString(*it))); |
| } |
| return exec->lexicalInterpreter()->builtinArray()->construct(exec, list); |
| } |
| } |
| default: |
| return NULL; |
| } |
| } |
| |
| void Clipboard::put(ExecState *exec, const Identifier &propertyName, JSValue *value, int attr) |
| { |
| lookupPut<Clipboard,DOMObject>(exec, propertyName, value, attr, &ClipboardTable, this ); |
| } |
| |
| void Clipboard::putValueProperty(ExecState *exec, int token, JSValue *value, int /*attr*/) |
| { |
| switch (token) { |
| case DropEffect: |
| // can never set this when not for dragging, thus getting always returns NULL string |
| if (clipboard->isForDragging()) |
| clipboard->setDropEffect(value->toString(exec)); |
| break; |
| case EffectAllowed: |
| // can never set this when not for dragging, thus getting always returns NULL string |
| if (clipboard->isForDragging()) |
| clipboard->setEffectAllowed(value->toString(exec)); |
| break; |
| } |
| } |
| |
| JSValue *ClipboardProtoFunc::callAsFunction(ExecState *exec, JSObject *thisObj, const List &args) |
| { |
| if (!thisObj->inherits(&Clipboard::info)) |
| return throwError(exec, TypeError); |
| |
| Clipboard *cb = static_cast<Clipboard *>(thisObj); |
| switch (id) { |
| case Clipboard::ClearData: |
| if (args.size() == 0) { |
| cb->clipboard->clearAllData(); |
| return jsUndefined(); |
| } else if (args.size() == 1) { |
| cb->clipboard->clearData(args[0]->toString(exec)); |
| return jsUndefined(); |
| } else |
| return throwError(exec, SyntaxError, "clearData: Invalid number of arguments"); |
| case Clipboard::GetData: |
| { |
| if (args.size() == 1) { |
| bool success; |
| WebCore::String result = cb->clipboard->getData(args[0]->toString(exec), success); |
| if (success) |
| return jsString(result); |
| else |
| return jsUndefined(); |
| } else |
| return throwError(exec, SyntaxError, "getData: Invalid number of arguments"); |
| } |
| case Clipboard::SetData: |
| if (args.size() == 2) |
| return jsBoolean(cb->clipboard->setData(args[0]->toString(exec), args[1]->toString(exec))); |
| else |
| return throwError(exec, SyntaxError, "setData: Invalid number of arguments"); |
| case Clipboard::SetDragImage: |
| { |
| if (!cb->clipboard->isForDragging()) |
| return jsUndefined(); |
| |
| if (args.size() != 3) |
| return throwError(exec, SyntaxError, "setDragImage: Invalid number of arguments"); |
| |
| int x = (int)args[1]->toNumber(exec); |
| int y = (int)args[2]->toNumber(exec); |
| |
| // See if they passed us a node |
| WebCore::Node *node = toNode(args[0]); |
| if (!node) |
| return throwError(exec, TypeError); |
| |
| if (!node->isElementNode()) |
| return throwError(exec, SyntaxError, "setDragImageFromElement: Invalid first argument"); |
| |
| if (static_cast<Element*>(node)->hasLocalName(imgTag) && |
| !node->inDocument()) |
| cb->clipboard->setDragImage(static_cast<HTMLImageElement*>(node)->cachedImage(), IntPoint(x, y)); |
| else |
| cb->clipboard->setDragImageElement(node, IntPoint(x, y)); |
| |
| return jsUndefined(); |
| } |
| } |
| return jsUndefined(); |
| } |
| |
| } |