2007-10-20 Rodney Dawes <dobey@wayofthemonkey.com>
[WebKit-https.git] / WebCore / bindings / js / kjs_events.cpp
1 /*
2  *  This file is part of the KDE libraries
3  *  Copyright (C) 2001 Peter Kelly (pmk@post.com)
4  *  Copyright (C) 2003, 2004, 2005, 2006 Apple Computer, Inc.
5  *
6  *  This library is free software; you can redistribute it and/or
7  *  modify it under the terms of the GNU Lesser General Public
8  *  License as published by the Free Software Foundation; either
9  *  version 2 of the License, or (at your option) any later version.
10  *
11  *  This library is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  *  Lesser General Public License for more details.
15  *
16  *  You should have received a copy of the GNU Lesser General Public
17  *  License along with this library; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19  */
20
21 #include "config.h"
22 #include "kjs_events.h"
23
24 #include "CString.h"
25 #include "Chrome.h"
26 #include "Clipboard.h"
27 #include "ClipboardEvent.h"
28 #include "DOMWindow.h"
29 #include "Document.h"
30 #include "Event.h"
31 #include "EventNames.h"
32 #include "Frame.h"
33 #include "FrameLoader.h"
34 #include "HTMLImageElement.h"
35 #include "HTMLNames.h"
36 #include "JSEvent.h"
37 #include "JSEventTargetNode.h"
38 #include "KURL.h"
39 #include "Page.h"
40 #include "kjs_proxy.h"
41 #include "kjs_window.h"
42
43 #include "kjs_events.lut.h"
44
45 namespace WebCore {
46
47 using namespace KJS;
48 using namespace EventNames;
49 using namespace HTMLNames;
50
51 JSAbstractEventListener::JSAbstractEventListener(bool html)
52     : m_html(html)
53 {
54 }
55
56 void JSAbstractEventListener::handleEvent(Event* ele, bool isWindowEvent)
57 {
58 #ifdef KJS_DEBUGGER
59     if (KJSDebugWin::instance() && KJSDebugWin::instance()->inSession())
60         return;
61 #endif
62
63     Event* event = ele;
64
65     JSObject* listener = listenerObj();
66     if (!listener)
67         return;
68
69     KJS::Window* window = windowObj();
70     // Null check as clearWindowObj() can clear this and we still get called back by
71     // xmlhttprequest objects. See http://bugs.webkit.org/show_bug.cgi?id=13275
72     if (!window)
73         return;
74     Frame *frame = window->impl()->frame();
75     if (!frame)
76         return;
77     KJSProxy* proxy = frame->scriptProxy();
78     if (!proxy)
79         return;
80
81     JSLock lock;
82
83     ScriptInterpreter* interpreter = proxy->interpreter();
84     ExecState* exec = interpreter->globalExec();
85
86     JSValue* handleEventFuncValue = listener->get(exec, "handleEvent");
87     JSObject* handleEventFunc = 0;
88     if (handleEventFuncValue->isObject()) {
89         handleEventFunc = static_cast<JSObject*>(handleEventFuncValue);
90         if (!handleEventFunc->implementsCall())
91             handleEventFunc = 0;
92     }
93
94     if (handleEventFunc || listener->implementsCall()) {
95         ref();
96
97         List args;
98         args.append(toJS(exec, event));
99
100         // Set the event we're handling in the KJS::Window object
101         window->setCurrentEvent(event);
102         // ... and in the interpreter
103         interpreter->setCurrentEvent(event);
104
105         JSValue* retval;
106         if (handleEventFunc) {
107             interpreter->startTimeoutCheck();
108             retval = handleEventFunc->call(exec, listener, args);
109         } else {
110             JSObject* thisObj;
111             if (isWindowEvent)
112                 thisObj = window;
113             else
114                 thisObj = static_cast<JSObject*>(toJS(exec, event->currentTarget()));
115             interpreter->startTimeoutCheck();
116             retval = listener->call(exec, thisObj, args);
117         }
118         interpreter->stopTimeoutCheck();
119
120         window->setCurrentEvent(0);
121         interpreter->setCurrentEvent(0);
122
123         if (exec->hadException()) {
124             JSObject* exception = exec->exception()->toObject(exec);
125             String message = exception->get(exec, exec->propertyNames().message)->toString(exec);
126             int lineNumber = exception->get(exec, "line")->toInt32(exec);
127             String sourceURL = exception->get(exec, "sourceURL")->toString(exec);
128             if (Interpreter::shouldPrintExceptions())
129                 printf("(event handler):%s\n", message.utf8().data());
130             if (Page* page = frame->page())
131                 page->chrome()->addMessageToConsole(JSMessageSource, ErrorMessageLevel, message, lineNumber, sourceURL);
132             exec->clearException();
133         } else {
134             if (!retval->isUndefinedOrNull() && event->storesResultAsString())
135                 event->storeResult(retval->toString(exec));
136             if (m_html) {
137                 bool retvalbool;
138                 if (retval->getBoolean(retvalbool) && !retvalbool)
139                     event->preventDefault();
140             }
141         }
142
143         Document::updateDocumentsRendering();
144         deref();
145     }
146 }
147
148 bool JSAbstractEventListener::isHTMLEventListener() const
149 {
150     return m_html;
151 }
152
153 // -------------------------------------------------------------------------
154
155 JSUnprotectedEventListener::JSUnprotectedEventListener(JSObject* listener, KJS::Window* win, bool html)
156     : JSAbstractEventListener(html)
157     , m_listener(listener)
158     , m_win(win)
159 {
160     if (m_listener) {
161         KJS::Window::UnprotectedListenersMap& listeners = html
162             ? m_win->jsUnprotectedHTMLEventListeners() : m_win->jsUnprotectedEventListeners();
163         listeners.set(m_listener, this);
164     }
165 }
166
167 JSUnprotectedEventListener::~JSUnprotectedEventListener()
168 {
169     if (m_listener && m_win) {
170         KJS::Window::UnprotectedListenersMap& listeners = isHTMLEventListener()
171             ? m_win->jsUnprotectedHTMLEventListeners() : m_win->jsUnprotectedEventListeners();
172         listeners.remove(m_listener);
173     }
174 }
175
176 JSObject* JSUnprotectedEventListener::listenerObj() const
177 {
178     return m_listener;
179 }
180
181 KJS::Window* JSUnprotectedEventListener::windowObj() const
182 {
183     return m_win;
184 }
185
186 void JSUnprotectedEventListener::clearWindowObj()
187 {
188     m_win = 0;
189 }
190
191 void JSUnprotectedEventListener::mark()
192 {
193     if (m_listener && !m_listener->marked())
194         m_listener->mark();
195 }
196
197 #ifndef NDEBUG
198 #ifndef LOG_CHANNEL_PREFIX
199 #define LOG_CHANNEL_PREFIX Log
200 #endif
201 WTFLogChannel LogWebCoreEventListenerLeaks = { 0x00000000, "", WTFLogChannelOn };
202
203 struct EventListenerCounter {
204     static unsigned count;
205     ~EventListenerCounter()
206     {
207         if (count)
208             LOG(WebCoreEventListenerLeaks, "LEAK: %u EventListeners\n", count);
209     }
210 };
211 unsigned EventListenerCounter::count = 0;
212 static EventListenerCounter eventListenerCounter;
213 #endif
214
215 // -------------------------------------------------------------------------
216
217 JSEventListener::JSEventListener(JSObject* listener, KJS::Window* win, bool html)
218     : JSAbstractEventListener(html)
219     , m_listener(listener)
220     , m_win(win)
221 {
222     if (m_listener) {
223         KJS::Window::ListenersMap& listeners = html
224             ? m_win->jsHTMLEventListeners() : m_win->jsEventListeners();
225         listeners.set(m_listener, this);
226     }
227 #ifndef NDEBUG
228     ++eventListenerCounter.count;
229 #endif
230 }
231
232 JSEventListener::~JSEventListener()
233 {
234     if (m_listener && m_win) {
235         KJS::Window::ListenersMap& listeners = isHTMLEventListener()
236             ? m_win->jsHTMLEventListeners() : m_win->jsEventListeners();
237         listeners.remove(m_listener);
238     }
239 #ifndef NDEBUG
240     --eventListenerCounter.count;
241 #endif
242 }
243
244 JSObject* JSEventListener::listenerObj() const
245 {
246     return m_listener;
247 }
248
249 KJS::Window* JSEventListener::windowObj() const
250 {
251     return m_win;
252 }
253
254 void JSEventListener::clearWindowObj()
255 {
256     m_win = 0;
257 }
258
259 // -------------------------------------------------------------------------
260
261 JSLazyEventListener::JSLazyEventListener(const String& functionName, const String& code, KJS::Window* win, Node* node, int lineNumber)
262     : JSEventListener(0, win, true)
263     , m_functionName(functionName)
264     , m_code(code)
265     , m_parsed(false)
266     , m_lineNumber(lineNumber)
267     , m_originalNode(node)
268 {
269     // We don't retain the original node because we assume it
270     // will stay alive as long as this handler object is around
271     // and we need to avoid a reference cycle. If JS transfers
272     // this handler to another node, parseCode will be called and
273     // then originalNode is no longer needed.
274 }
275
276 JSObject* JSLazyEventListener::listenerObj() const
277 {
278     parseCode();
279     return m_listener;
280 }
281
282 JSValue* JSLazyEventListener::eventParameterName() const
283 {
284     static ProtectedPtr<JSValue> eventString = jsString("event");
285     return eventString.get();
286 }
287
288 void JSLazyEventListener::parseCode() const
289 {
290     if (m_parsed)
291         return;
292     m_parsed = true;
293
294     Frame* frame = windowObj()->impl()->frame();
295     KJSProxy* proxy = 0;
296     if (frame)
297         proxy = frame->scriptProxy();
298
299     if (proxy) {
300         ScriptInterpreter* interpreter = proxy->interpreter();
301         ExecState* exec = interpreter->globalExec();
302
303         JSLock lock;
304         JSObject* constr = interpreter->builtinFunction();
305         List args;
306
307         UString sourceURL(frame->loader()->url().url());
308         args.append(eventParameterName());
309         args.append(jsString(m_code));
310         m_listener = constr->construct(exec, args, m_functionName, sourceURL, m_lineNumber); // FIXME: is globalExec ok ?
311
312         FunctionImp* listenerAsFunction = static_cast<FunctionImp*>(m_listener.get());
313
314         if (exec->hadException()) {
315             exec->clearException();
316
317             // failed to parse, so let's just make this listener a no-op
318             m_listener = 0;
319         } else if (m_originalNode) {
320             // Add the event's home element to the scope
321             // (and the document, and the form - see JSHTMLElement::eventHandlerScope)
322             ScopeChain scope = listenerAsFunction->scope();
323
324             JSValue* thisObj = toJS(exec, m_originalNode);
325             if (thisObj->isObject()) {
326                 static_cast<JSEventTargetNode*>(thisObj)->pushEventHandlerScope(exec, scope);
327                 listenerAsFunction->setScope(scope);
328             }
329         }
330     }
331
332     // no more need to keep the unparsed code around
333     m_functionName = String();
334     m_code = String();
335
336     if (m_listener) {
337         KJS::Window::ListenersMap& listeners = isHTMLEventListener()
338             ? windowObj()->jsHTMLEventListeners() : windowObj()->jsEventListeners();
339         listeners.set(m_listener, const_cast<JSLazyEventListener*>(this));
340     }
341 }
342
343 JSValue* getNodeEventListener(EventTargetNode* n, const AtomicString& eventType)
344 {
345     if (JSAbstractEventListener* listener = static_cast<JSAbstractEventListener*>(n->getHTMLEventListener(eventType))) {
346         if (JSValue* obj = listener->listenerObj())
347             return obj;
348     }
349     return jsNull();
350 }
351
352 // -------------------------------------------------------------------------
353
354 const ClassInfo JSClipboard::info = { "Clipboard", 0, &JSClipboardTable, 0 };
355
356 /* Source for JSClipboardTable. Use "make hashtables" to regenerate.
357 @begin JSClipboardTable 3
358   dropEffect    WebCore::JSClipboard::DropEffect   DontDelete
359   effectAllowed WebCore::JSClipboard::EffectAllowed        DontDelete
360   types         WebCore::JSClipboard::Types        DontDelete|ReadOnly
361 @end
362 @begin JSClipboardPrototypeTable 4
363   clearData     WebCore::JSClipboard::ClearData    DontDelete|Function 0
364   getData       WebCore::JSClipboard::GetData      DontDelete|Function 1
365   setData       WebCore::JSClipboard::SetData      DontDelete|Function 2
366   setDragImage  WebCore::JSClipboard::SetDragImage DontDelete|Function 3
367 @end
368 */
369
370 KJS_DEFINE_PROTOTYPE(JSClipboardPrototype)
371 KJS_IMPLEMENT_PROTOTYPE_FUNCTION(JSClipboardPrototypeFunction)
372 KJS_IMPLEMENT_PROTOTYPE("Clipboard", JSClipboardPrototype, JSClipboardPrototypeFunction)
373
374 JSClipboard::JSClipboard(ExecState* exec, Clipboard* clipboard)
375     : m_impl(clipboard)
376 {
377     setPrototype(JSClipboardPrototype::self(exec));
378 }
379
380 JSClipboard::~JSClipboard()
381 {
382     ScriptInterpreter::forgetDOMObject(m_impl.get());
383 }
384
385 bool JSClipboard::getOwnPropertySlot(ExecState* exec, const Identifier& propertyName, PropertySlot& slot)
386 {
387     return getStaticValueSlot<JSClipboard, DOMObject>(exec, &JSClipboardTable, this, propertyName, slot);
388 }
389
390 JSValue* JSClipboard::getValueProperty(ExecState* exec, int token) const
391 {
392     Clipboard* clipboard = impl();
393     switch (token) {
394         case DropEffect:
395             ASSERT(clipboard->isForDragging() || clipboard->dropEffect().isNull());
396             return jsStringOrUndefined(clipboard->dropEffect());
397         case EffectAllowed:
398             ASSERT(clipboard->isForDragging() || clipboard->effectAllowed().isNull());
399             return jsStringOrUndefined(clipboard->effectAllowed());
400         case Types:
401         {
402             HashSet<String> types = clipboard->types();
403             if (types.isEmpty())
404                 return jsNull();
405             else {
406                 List list;
407                 HashSet<String>::const_iterator end = types.end();
408                 for (HashSet<String>::const_iterator it = types.begin(); it != end; ++it)
409                     list.append(jsString(UString(*it)));
410                 return exec->lexicalInterpreter()->builtinArray()->construct(exec, list);
411             }
412         }
413         default:
414             return 0;
415     }
416 }
417
418 void JSClipboard::put(ExecState* exec, const Identifier& propertyName, JSValue* value, int attr)
419 {
420     lookupPut<JSClipboard, DOMObject>(exec, propertyName, value, attr, &JSClipboardTable, this );
421 }
422
423 void JSClipboard::putValueProperty(ExecState* exec, int token, JSValue* value, int /*attr*/)
424 {
425     Clipboard* clipboard = impl();
426     switch (token) {
427         case DropEffect:
428             // can never set this when not for dragging, thus getting always returns NULL string
429             if (clipboard->isForDragging())
430                 clipboard->setDropEffect(value->toString(exec));
431             break;
432         case EffectAllowed:
433             // can never set this when not for dragging, thus getting always returns NULL string
434             if (clipboard->isForDragging())
435                 clipboard->setEffectAllowed(value->toString(exec));
436             break;
437     }
438 }
439
440 JSValue* JSClipboardPrototypeFunction::callAsFunction(ExecState* exec, JSObject* thisObj, const List& args)
441 {
442     if (!thisObj->inherits(&JSClipboard::info))
443         return throwError(exec, TypeError);
444
445     Clipboard* clipboard = static_cast<JSClipboard*>(thisObj)->impl();
446     switch (id) {
447         case JSClipboard::ClearData:
448             if (args.size() == 0) {
449                 clipboard->clearAllData();
450                 return jsUndefined();
451             } else if (args.size() == 1) {
452                 clipboard->clearData(args[0]->toString(exec));
453                 return jsUndefined();
454             } else
455                 return throwError(exec, SyntaxError, "clearData: Invalid number of arguments");
456         case JSClipboard::GetData:
457         {
458             if (args.size() == 1) {
459                 bool success;
460                 String result = clipboard->getData(args[0]->toString(exec), success);
461                 if (success)
462                     return jsString(result);
463                 return jsUndefined();
464             } else
465                 return throwError(exec, SyntaxError, "getData: Invalid number of arguments");
466         }
467         case JSClipboard::SetData:
468             if (args.size() == 2)
469                 return jsBoolean(clipboard->setData(args[0]->toString(exec), args[1]->toString(exec)));
470             return throwError(exec, SyntaxError, "setData: Invalid number of arguments");
471         case JSClipboard::SetDragImage:
472         {
473             if (!clipboard->isForDragging())
474                 return jsUndefined();
475
476             if (args.size() != 3)
477                 return throwError(exec, SyntaxError, "setDragImage: Invalid number of arguments");
478
479             int x = args[1]->toInt32(exec);
480             int y = args[2]->toInt32(exec);
481
482             // See if they passed us a node
483             Node* node = toNode(args[0]);
484             if (!node)
485                 return throwError(exec, TypeError);
486
487             if (!node->isElementNode())
488                 return throwError(exec, SyntaxError, "setDragImageFromElement: Invalid first argument");
489
490             if (static_cast<Element*>(node)->hasLocalName(imgTag) &&
491                 !node->inDocument())
492                 clipboard->setDragImage(static_cast<HTMLImageElement*>(node)->cachedImage(), IntPoint(x, y));
493             else
494                 clipboard->setDragImageElement(node, IntPoint(x, y));
495
496             return jsUndefined();
497         }
498     }
499     return jsUndefined();
500 }
501
502 JSValue* toJS(ExecState* exec, Clipboard* obj)
503 {
504     return cacheDOMObject<Clipboard, JSClipboard>(exec, obj);
505 }
506
507 Clipboard* toClipboard(JSValue* val)
508 {
509     return val->isObject(&JSClipboard::info) ? static_cast<JSClipboard*>(val)->impl() : 0;
510 }
511
512 } // namespace WebCore