8504c8fe735ca34b2ced584bd685641735d78558
[WebKit-https.git] / WebCore / bindings / js / JSEventListener.cpp
1 /*
2  *  Copyright (C) 2001 Peter Kelly (pmk@post.com)
3  *  Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008 Apple Inc. All Rights Reserved.
4  *
5  *  This library is free software; you can redistribute it and/or
6  *  modify it under the terms of the GNU Lesser General Public
7  *  License as published by the Free Software Foundation; either
8  *  version 2 of the License, or (at your option) any later version.
9  *
10  *  This library is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  *  Lesser General Public License for more details.
14  *
15  *  You should have received a copy of the GNU Lesser General Public
16  *  License along with this library; if not, write to the Free Software
17  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18  */
19
20 #include "config.h"
21 #include "JSEventListener.h"
22
23 #include "CString.h"
24 #include "Console.h"
25 #include "DOMWindow.h"
26 #include "Document.h"
27 #include "Event.h"
28 #include "Frame.h"
29 #include "FrameLoader.h"
30 #include "JSDOMWindow.h"
31 #include "JSEvent.h"
32 #include "JSEventTarget.h"
33 #include "JSNode.h"
34 #include "ScriptController.h"
35 #include <runtime/FunctionConstructor.h>
36 #include <runtime/JSLock.h>
37 #include <wtf/RefCountedLeakCounter.h>
38
39 using namespace JSC;
40
41 namespace WebCore {
42
43 ASSERT_CLASS_FITS_IN_CELL(JSAbstractEventListener);
44
45 void JSAbstractEventListener::handleEvent(Event* event, bool isWindowEvent)
46 {
47     JSLock lock(false);
48
49     JSObject* listener = listenerObj();
50     if (!listener)
51         return;
52
53     JSDOMGlobalObject* globalObject = this->globalObject();
54     // Null check as clearGlobalObject() can clear this and we still get called back by
55     // xmlhttprequest objects. See http://bugs.webkit.org/show_bug.cgi?id=13275
56     // FIXME: Is this check still necessary? Requests are supposed to be stopped before clearGlobalObject() is called.
57     if (!globalObject)
58         return;
59
60     ScriptExecutionContext* scriptExecutionContext = globalObject->scriptExecutionContext();
61     if (!scriptExecutionContext)
62         return;
63
64     if (scriptExecutionContext->isDocument()) {
65         JSDOMWindow* window = static_cast<JSDOMWindow*>(globalObject);
66         Frame* frame = window->impl()->frame();
67         if (!frame)
68             return;
69         // The window must still be active in its frame. See <https://bugs.webkit.org/show_bug.cgi?id=21921>.
70         // FIXME: A better fix for this may be to change DOMWindow::frame() to not return a frame the detached window used to be in.
71         if (frame->domWindow() != window->impl())
72             return;
73         // FIXME: Is this check needed for other contexts?
74         ScriptController* script = frame->script();
75         if (!script->isEnabled() || script->isPaused())
76             return;
77     }
78
79     ExecState* exec = globalObject->globalExec();
80
81     JSValuePtr handleEventFunction = listener->get(exec, Identifier(exec, "handleEvent"));
82     CallData callData;
83     CallType callType = handleEventFunction.getCallData(callData);
84     if (callType == CallTypeNone) {
85         handleEventFunction = noValue();
86         callType = listener->getCallData(callData);
87     }
88
89     if (callType != CallTypeNone) {
90         ref();
91
92         ArgList args;
93         args.append(toJS(exec, event));
94
95         Event* savedEvent = globalObject->currentEvent();
96         globalObject->setCurrentEvent(event);
97
98         // If this event handler is the first JavaScript to execute, then the
99         // dynamic global object should be set to the global object of the
100         // window in which the event occurred.
101         JSGlobalData* globalData = globalObject->globalData();
102         DynamicGlobalObjectScope globalObjectScope(exec, globalData->dynamicGlobalObject ? globalData->dynamicGlobalObject : globalObject);
103
104         JSValuePtr retval;
105         if (handleEventFunction) {
106             globalObject->globalData()->timeoutChecker.start();
107             retval = call(exec, handleEventFunction, callType, callData, listener, args);
108         } else {
109             JSValuePtr thisValue;
110             if (isWindowEvent)
111                 thisValue = globalObject->toThisObject(exec);
112             else
113                 thisValue = toJS(exec, event->currentTarget());
114             globalObject->globalData()->timeoutChecker.start();
115             retval = call(exec, listener, callType, callData, thisValue, args);
116         }
117         globalObject->globalData()->timeoutChecker.stop();
118
119         globalObject->setCurrentEvent(savedEvent);
120
121         if (exec->hadException())
122             reportCurrentException(exec);
123         else {
124             if (!retval.isUndefinedOrNull() && event->storesResultAsString())
125                 event->storeResult(retval.toString(exec));
126             if (m_isInline) {
127                 bool retvalbool;
128                 if (retval.getBoolean(retvalbool) && !retvalbool)
129                     event->preventDefault();
130             }
131         }
132
133         if (scriptExecutionContext->isDocument())
134             Document::updateDocumentsRendering();
135         deref();
136     }
137 }
138
139 bool JSAbstractEventListener::isInline() const
140 {
141     return m_isInline;
142 }
143
144 // -------------------------------------------------------------------------
145
146 JSEventListener::JSEventListener(JSObject* listener, JSDOMGlobalObject* globalObject, bool isInline)
147     : JSAbstractEventListener(isInline)
148     , m_listener(listener)
149     , m_globalObject(globalObject)
150 {
151     if (m_listener) {
152         JSDOMWindow::JSListenersMap& listeners = isInline
153             ? globalObject->jsInlineEventListeners() : globalObject->jsEventListeners();
154         listeners.set(m_listener, this);
155     }
156 }
157
158 JSEventListener::~JSEventListener()
159 {
160     if (m_listener && m_globalObject) {
161         JSDOMWindow::JSListenersMap& listeners = isInline()
162             ? m_globalObject->jsInlineEventListeners() : m_globalObject->jsEventListeners();
163         listeners.remove(m_listener);
164     }
165 }
166
167 JSObject* JSEventListener::listenerObj() const
168 {
169     return m_listener;
170 }
171
172 JSDOMGlobalObject* JSEventListener::globalObject() const
173 {
174     return m_globalObject;
175 }
176
177 void JSEventListener::clearGlobalObject()
178 {
179     m_globalObject = 0;
180 }
181
182 void JSEventListener::mark()
183 {
184     if (m_listener && !m_listener->marked())
185         m_listener->mark();
186 }
187
188 #ifndef NDEBUG
189 static WTF::RefCountedLeakCounter eventListenerCounter("EventListener");
190 #endif
191
192 // -------------------------------------------------------------------------
193
194 JSProtectedEventListener::JSProtectedEventListener(JSObject* listener, JSDOMGlobalObject* globalObject, bool isInline)
195     : JSAbstractEventListener(isInline)
196     , m_listener(listener)
197     , m_globalObject(globalObject)
198 {
199     if (m_listener) {
200         JSDOMWindow::ProtectedListenersMap& listeners = isInline
201             ? m_globalObject->jsProtectedInlineEventListeners() : m_globalObject->jsProtectedEventListeners();
202         listeners.set(m_listener, this);
203     }
204 #ifndef NDEBUG
205     eventListenerCounter.increment();
206 #endif
207 }
208
209 JSProtectedEventListener::~JSProtectedEventListener()
210 {
211     if (m_listener && m_globalObject) {
212         JSDOMWindow::ProtectedListenersMap& listeners = isInline()
213             ? m_globalObject->jsProtectedInlineEventListeners() : m_globalObject->jsProtectedEventListeners();
214         listeners.remove(m_listener);
215     }
216 #ifndef NDEBUG
217     eventListenerCounter.decrement();
218 #endif
219 }
220
221 JSObject* JSProtectedEventListener::listenerObj() const
222 {
223     return m_listener;
224 }
225
226 JSDOMGlobalObject* JSProtectedEventListener::globalObject() const
227 {
228     return m_globalObject;
229 }
230
231 void JSProtectedEventListener::clearGlobalObject()
232 {
233     m_globalObject = 0;
234 }
235
236 // -------------------------------------------------------------------------
237
238 JSLazyEventListener::JSLazyEventListener(LazyEventListenerType type, const String& functionName, const String& code, JSDOMGlobalObject* globalObject, Node* node, int lineNumber)
239     : JSProtectedEventListener(0, globalObject, true)
240     , m_functionName(functionName)
241     , m_code(code)
242     , m_parsed(false)
243     , m_lineNumber(lineNumber)
244     , m_originalNode(node)
245     , m_type(type)
246 {
247     // We don't retain the original node because we assume it
248     // will stay alive as long as this handler object is around
249     // and we need to avoid a reference cycle. If JS transfers
250     // this handler to another node, parseCode will be called and
251     // then originalNode is no longer needed.
252
253     // A JSLazyEventListener can be created with a line number of zero when it is created with
254     // a setAttribute call from JavaScript, so make the line number 1 in that case.
255     if (m_lineNumber == 0)
256         m_lineNumber = 1;
257 }
258
259 JSObject* JSLazyEventListener::listenerObj() const
260 {
261     parseCode();
262     return m_listener;
263 }
264
265 // Helper function
266 inline JSValuePtr eventParameterName(JSLazyEventListener::LazyEventListenerType type, ExecState* exec)
267 {
268     switch (type) {
269     case JSLazyEventListener::HTMLLazyEventListener:
270         return jsNontrivialString(exec, "event");
271 #if ENABLE(SVG)
272     case JSLazyEventListener::SVGLazyEventListener:
273         return jsNontrivialString(exec, "evt");
274 #endif
275     default:
276         ASSERT_NOT_REACHED();
277         return jsUndefined();
278     }
279 }
280
281 void JSLazyEventListener::parseCode() const
282 {
283     if (m_parsed)
284         return;
285
286     if (globalObject()->scriptExecutionContext()->isDocument()) {
287         JSDOMWindow* window = static_cast<JSDOMWindow*>(globalObject());
288         Frame* frame = window->impl()->frame();
289         if (!frame)
290             return;
291         // FIXME: Is this check needed for non-Document contexts?
292         ScriptController* script = frame->script();
293         if (!script->isEnabled() || script->isPaused())
294             return;
295     }
296
297     m_parsed = true;
298
299     ExecState* exec = globalObject()->globalExec();
300
301     ArgList args;
302     UString sourceURL(globalObject()->scriptExecutionContext()->url().string());
303     args.append(eventParameterName(m_type, exec));
304     args.append(jsString(exec, m_code));
305
306     // FIXME: Passing the document's URL to construct is not always correct, since this event listener might
307     // have been added with setAttribute from a script, and we should pass String() in that case.
308     m_listener = constructFunction(exec, args, Identifier(exec, m_functionName), sourceURL, m_lineNumber); // FIXME: is globalExec ok?
309
310     JSFunction* listenerAsFunction = static_cast<JSFunction*>(m_listener.get());
311
312     if (exec->hadException()) {
313         exec->clearException();
314
315         // failed to parse, so let's just make this listener a no-op
316         m_listener = 0;
317     } else if (m_originalNode) {
318         // Add the event's home element to the scope
319         // (and the document, and the form - see JSHTMLElement::eventHandlerScope)
320         ScopeChain scope = listenerAsFunction->scope();
321
322         JSValuePtr thisObj = toJS(exec, m_originalNode);
323         if (thisObj.isObject()) {
324             static_cast<JSNode*>(asObject(thisObj))->pushEventHandlerScope(exec, scope);
325             listenerAsFunction->setScope(scope);
326         }
327     }
328
329     // no more need to keep the unparsed code around
330     m_functionName = String();
331     m_code = String();
332
333     if (m_listener) {
334         ASSERT(isInline());
335         JSDOMWindow::ProtectedListenersMap& listeners = globalObject()->jsProtectedInlineEventListeners();
336         listeners.set(m_listener, const_cast<JSLazyEventListener*>(this));
337     }
338 }
339
340 } // namespace WebCore