a6a0f8cf60358cca038654f1324666b7e95f8a41
[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 "Event.h"
24 #include "Frame.h"
25 #include "JSEvent.h"
26 #include "JSEventTarget.h"
27 #include <runtime/JSLock.h>
28 #include <wtf/RefCountedLeakCounter.h>
29
30 using namespace JSC;
31
32 namespace WebCore {
33
34 void JSAbstractEventListener::handleEvent(Event* event, bool isWindowEvent)
35 {
36     JSLock lock(false);
37
38     JSObject* listener = listenerObj();
39     if (!listener)
40         return;
41
42     JSDOMGlobalObject* globalObject = this->globalObject();
43     // Null check as clearGlobalObject() can clear this and we still get called back by
44     // xmlhttprequest objects. See http://bugs.webkit.org/show_bug.cgi?id=13275
45     // FIXME: Is this check still necessary? Requests are supposed to be stopped before clearGlobalObject() is called.
46     if (!globalObject)
47         return;
48
49     ScriptExecutionContext* scriptExecutionContext = globalObject->scriptExecutionContext();
50     if (!scriptExecutionContext)
51         return;
52
53     if (scriptExecutionContext->isDocument()) {
54         JSDOMWindow* window = static_cast<JSDOMWindow*>(globalObject);
55         Frame* frame = window->impl()->frame();
56         if (!frame)
57             return;
58         // The window must still be active in its frame. See <https://bugs.webkit.org/show_bug.cgi?id=21921>.
59         // FIXME: A better fix for this may be to change DOMWindow::frame() to not return a frame the detached window used to be in.
60         if (frame->domWindow() != window->impl())
61             return;
62         // FIXME: Is this check needed for other contexts?
63         ScriptController* script = frame->script();
64         if (!script->isEnabled() || script->isPaused())
65             return;
66     }
67
68     ExecState* exec = globalObject->globalExec();
69
70     JSValuePtr handleEventFunction = listener->get(exec, Identifier(exec, "handleEvent"));
71     CallData callData;
72     CallType callType = handleEventFunction.getCallData(callData);
73     if (callType == CallTypeNone) {
74         handleEventFunction = noValue();
75         callType = listener->getCallData(callData);
76     }
77
78     if (callType != CallTypeNone) {
79         ref();
80
81         ArgList args;
82         args.append(toJS(exec, event));
83
84         Event* savedEvent = globalObject->currentEvent();
85         globalObject->setCurrentEvent(event);
86
87         // If this event handler is the first JavaScript to execute, then the
88         // dynamic global object should be set to the global object of the
89         // window in which the event occurred.
90         JSGlobalData* globalData = globalObject->globalData();
91         DynamicGlobalObjectScope globalObjectScope(exec, globalData->dynamicGlobalObject ? globalData->dynamicGlobalObject : globalObject);
92
93         JSValuePtr retval;
94         if (handleEventFunction) {
95             globalObject->globalData()->timeoutChecker.start();
96             retval = call(exec, handleEventFunction, callType, callData, listener, args);
97         } else {
98             JSValuePtr thisValue;
99             if (isWindowEvent)
100                 thisValue = globalObject->toThisObject(exec);
101             else
102                 thisValue = toJS(exec, event->currentTarget());
103             globalObject->globalData()->timeoutChecker.start();
104             retval = call(exec, listener, callType, callData, thisValue, args);
105         }
106         globalObject->globalData()->timeoutChecker.stop();
107
108         globalObject->setCurrentEvent(savedEvent);
109
110         if (exec->hadException())
111             reportCurrentException(exec);
112         else {
113             if (!retval.isUndefinedOrNull() && event->storesResultAsString())
114                 event->storeResult(retval.toString(exec));
115             if (m_isInline) {
116                 bool retvalbool;
117                 if (retval.getBoolean(retvalbool) && !retvalbool)
118                     event->preventDefault();
119             }
120         }
121
122         if (scriptExecutionContext->isDocument())
123             Document::updateDocumentsRendering();
124         deref();
125     }
126 }
127
128 bool JSAbstractEventListener::isInline() const
129 {
130     return m_isInline;
131 }
132
133 // -------------------------------------------------------------------------
134
135 JSEventListener::JSEventListener(JSObject* listener, JSDOMGlobalObject* globalObject, bool isInline)
136     : JSAbstractEventListener(isInline)
137     , m_listener(listener)
138     , m_globalObject(globalObject)
139 {
140     if (m_listener) {
141         JSDOMWindow::JSListenersMap& listeners = isInline
142             ? globalObject->jsInlineEventListeners() : globalObject->jsEventListeners();
143         listeners.set(m_listener, this);
144     }
145 }
146
147 JSEventListener::~JSEventListener()
148 {
149     if (m_listener && m_globalObject) {
150         JSDOMWindow::JSListenersMap& listeners = isInline()
151             ? m_globalObject->jsInlineEventListeners() : m_globalObject->jsEventListeners();
152         listeners.remove(m_listener);
153     }
154 }
155
156 JSObject* JSEventListener::listenerObj() const
157 {
158     return m_listener;
159 }
160
161 JSDOMGlobalObject* JSEventListener::globalObject() const
162 {
163     return m_globalObject;
164 }
165
166 void JSEventListener::clearGlobalObject()
167 {
168     m_globalObject = 0;
169 }
170
171 void JSEventListener::mark()
172 {
173     if (m_listener && !m_listener->marked())
174         m_listener->mark();
175 }
176
177 #ifndef NDEBUG
178 static WTF::RefCountedLeakCounter eventListenerCounter("EventListener");
179 #endif
180
181 // -------------------------------------------------------------------------
182
183 JSProtectedEventListener::JSProtectedEventListener(JSObject* listener, JSDOMGlobalObject* globalObject, bool isInline)
184     : JSAbstractEventListener(isInline)
185     , m_listener(listener)
186     , m_globalObject(globalObject)
187 {
188     if (m_listener) {
189         JSDOMWindow::ProtectedListenersMap& listeners = isInline
190             ? m_globalObject->jsProtectedInlineEventListeners() : m_globalObject->jsProtectedEventListeners();
191         listeners.set(m_listener, this);
192     }
193 #ifndef NDEBUG
194     eventListenerCounter.increment();
195 #endif
196 }
197
198 JSProtectedEventListener::~JSProtectedEventListener()
199 {
200     if (m_listener && m_globalObject) {
201         JSDOMWindow::ProtectedListenersMap& listeners = isInline()
202             ? m_globalObject->jsProtectedInlineEventListeners() : m_globalObject->jsProtectedEventListeners();
203         listeners.remove(m_listener);
204     }
205 #ifndef NDEBUG
206     eventListenerCounter.decrement();
207 #endif
208 }
209
210 JSObject* JSProtectedEventListener::listenerObj() const
211 {
212     return m_listener;
213 }
214
215 JSDOMGlobalObject* JSProtectedEventListener::globalObject() const
216 {
217     return m_globalObject;
218 }
219
220 void JSProtectedEventListener::clearGlobalObject()
221 {
222     m_globalObject = 0;
223 }
224
225 } // namespace WebCore