WebCore:
[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, 2009 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 JSEventListener::JSEventListener(JSObject* function, JSDOMGlobalObject* globalObject, bool isAttribute)
35     : m_jsFunction(function)
36     , m_globalObject(globalObject)
37     , m_isAttribute(isAttribute)
38 {
39     if (!m_isAttribute && m_jsFunction)
40         globalObject->jsEventListeners().set(m_jsFunction, this);
41 }
42
43 JSEventListener::~JSEventListener()
44 {
45     if (!m_isAttribute && m_jsFunction && m_globalObject)
46         m_globalObject->jsEventListeners().remove(m_jsFunction);
47 }
48
49 JSObject* JSEventListener::jsFunction() const
50 {
51     return m_jsFunction;
52 }
53
54 void JSEventListener::markJSFunction()
55 {
56     if (m_jsFunction && !m_jsFunction->marked())
57         m_jsFunction->mark();
58     if (m_globalObject && !m_globalObject->marked())
59         m_globalObject->mark();
60 }
61
62 void JSEventListener::handleEvent(Event* event, bool isWindowEvent)
63 {
64     JSLock lock(false);
65
66     JSObject* jsFunction = this->jsFunction();
67     if (!jsFunction)
68         return;
69
70     JSDOMGlobalObject* globalObject = m_globalObject;
71     // Null check as clearGlobalObject() can clear this and we still get called back by
72     // xmlhttprequest objects. See http://bugs.webkit.org/show_bug.cgi?id=13275
73     // FIXME: Is this check still necessary? Requests are supposed to be stopped before clearGlobalObject() is called.
74     if (!globalObject)
75         return;
76
77     ScriptExecutionContext* scriptExecutionContext = globalObject->scriptExecutionContext();
78     if (!scriptExecutionContext)
79         return;
80
81     if (scriptExecutionContext->isDocument()) {
82         JSDOMWindow* window = static_cast<JSDOMWindow*>(globalObject);
83         Frame* frame = window->impl()->frame();
84         if (!frame)
85             return;
86         // The window must still be active in its frame. See <https://bugs.webkit.org/show_bug.cgi?id=21921>.
87         // FIXME: A better fix for this may be to change DOMWindow::frame() to not return a frame the detached window used to be in.
88         if (frame->domWindow() != window->impl())
89             return;
90         // FIXME: Is this check needed for other contexts?
91         ScriptController* script = frame->script();
92         if (!script->isEnabled() || script->isPaused())
93             return;
94     }
95
96     ExecState* exec = globalObject->globalExec();
97
98     JSValue handleEventFunction = jsFunction->get(exec, Identifier(exec, "handleEvent"));
99     CallData callData;
100     CallType callType = handleEventFunction.getCallData(callData);
101     if (callType == CallTypeNone) {
102         handleEventFunction = JSValue();
103         callType = jsFunction->getCallData(callData);
104     }
105
106     if (callType != CallTypeNone) {
107         ref();
108
109         MarkedArgumentBuffer args;
110         args.append(toJS(exec, event));
111
112         Event* savedEvent = globalObject->currentEvent();
113         globalObject->setCurrentEvent(event);
114
115         // If this event handler is the first JavaScript to execute, then the
116         // dynamic global object should be set to the global object of the
117         // window in which the event occurred.
118         JSGlobalData* globalData = globalObject->globalData();
119         DynamicGlobalObjectScope globalObjectScope(exec, globalData->dynamicGlobalObject ? globalData->dynamicGlobalObject : globalObject);
120
121         JSValue retval;
122         if (handleEventFunction) {
123             globalObject->globalData()->timeoutChecker.start();
124             retval = call(exec, handleEventFunction, callType, callData, jsFunction, args);
125         } else {
126             JSValue thisValue;
127             if (isWindowEvent)
128                 thisValue = globalObject->toThisObject(exec);
129             else
130                 thisValue = toJS(exec, event->currentTarget());
131             globalObject->globalData()->timeoutChecker.start();
132             retval = call(exec, jsFunction, callType, callData, thisValue, args);
133         }
134         globalObject->globalData()->timeoutChecker.stop();
135
136         globalObject->setCurrentEvent(savedEvent);
137
138         if (exec->hadException())
139             reportCurrentException(exec);
140         else {
141             if (!retval.isUndefinedOrNull() && event->storesResultAsString())
142                 event->storeResult(retval.toString(exec));
143             if (m_isAttribute) {
144                 bool retvalbool;
145                 if (retval.getBoolean(retvalbool) && !retvalbool)
146                     event->preventDefault();
147             }
148         }
149
150         if (scriptExecutionContext->isDocument())
151             Document::updateStyleForAllDocuments();
152         deref();
153     }
154 }
155
156 bool JSEventListener::reportError(const String& message, const String& url, int lineNumber)
157 {
158     JSLock lock(false);
159
160     JSObject* jsFunction = this->jsFunction();
161     if (!jsFunction)
162         return false;
163
164     JSDOMGlobalObject* globalObject = m_globalObject;
165     if (!globalObject)
166         return false;
167
168     ExecState* exec = globalObject->globalExec();
169
170     CallData callData;
171     CallType callType = jsFunction->getCallData(callData);
172
173     if (callType == CallTypeNone)
174         return false;
175
176     MarkedArgumentBuffer args;
177     args.append(jsString(exec, message));
178     args.append(jsString(exec, url));
179     args.append(jsNumber(exec, lineNumber));
180
181     // If this event handler is the first JavaScript to execute, then the
182     // dynamic global object should be set to the global object of the
183     // window in which the event occurred.
184     JSGlobalData* globalData = globalObject->globalData();
185     DynamicGlobalObjectScope globalObjectScope(exec, globalData->dynamicGlobalObject ? globalData->dynamicGlobalObject : globalObject);    
186
187     JSValue thisValue = globalObject->toThisObject(exec);
188
189     globalObject->globalData()->timeoutChecker.start();
190     JSValue returnValue = call(exec, jsFunction, callType, callData, thisValue, args);
191     globalObject->globalData()->timeoutChecker.stop();
192
193     // If an error occurs while handling the script error, it should be bubbled up.
194     if (exec->hadException()) {
195         exec->clearException();
196         return false;
197     }
198     
199     bool bubbleEvent;
200     return returnValue.getBoolean(bubbleEvent) && !bubbleEvent;
201 }
202
203 bool JSEventListener::virtualisAttribute() const
204 {
205     return m_isAttribute;
206 }
207
208 } // namespace WebCore