2 * Copyright (C) 2001 Peter Kelly (pmk@post.com)
3 * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008 Apple Inc. All Rights Reserved.
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.
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.
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
21 #include "JSEventListener.h"
25 #include "DOMWindow.h"
29 #include "FrameLoader.h"
30 #include "JSDOMWindow.h"
32 #include "JSEventTarget.h"
34 #include "ScriptController.h"
35 #include <runtime/FunctionConstructor.h>
36 #include <runtime/JSLock.h>
37 #include <wtf/RefCountedLeakCounter.h>
43 ASSERT_CLASS_FITS_IN_CELL(JSAbstractEventListener);
45 void JSAbstractEventListener::handleEvent(Event* event, bool isWindowEvent)
49 JSObject* listener = listenerObj();
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.
60 ScriptExecutionContext* scriptExecutionContext = globalObject->scriptExecutionContext();
61 if (!scriptExecutionContext)
64 if (scriptExecutionContext->isDocument()) {
65 JSDOMWindow* window = static_cast<JSDOMWindow*>(globalObject);
66 Frame* frame = window->impl()->frame();
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())
73 // FIXME: Is this check needed for other contexts?
74 ScriptController* script = frame->script();
75 if (!script->isEnabled() || script->isPaused())
79 ExecState* exec = globalObject->globalExec();
81 JSValuePtr handleEventFunction = listener->get(exec, Identifier(exec, "handleEvent"));
83 CallType callType = handleEventFunction.getCallData(callData);
84 if (callType == CallTypeNone) {
85 handleEventFunction = noValue();
86 callType = listener->getCallData(callData);
89 if (callType != CallTypeNone) {
93 args.append(toJS(exec, event));
95 Event* savedEvent = globalObject->currentEvent();
96 globalObject->setCurrentEvent(event);
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);
105 if (handleEventFunction) {
106 globalObject->globalData()->timeoutChecker.start();
107 retval = call(exec, handleEventFunction, callType, callData, listener, args);
109 JSValuePtr thisValue;
111 thisValue = globalObject->toThisObject(exec);
113 thisValue = toJS(exec, event->currentTarget());
114 globalObject->globalData()->timeoutChecker.start();
115 retval = call(exec, listener, callType, callData, thisValue, args);
117 globalObject->globalData()->timeoutChecker.stop();
119 globalObject->setCurrentEvent(savedEvent);
121 if (exec->hadException())
122 reportCurrentException(exec);
124 if (!retval.isUndefinedOrNull() && event->storesResultAsString())
125 event->storeResult(retval.toString(exec));
128 if (retval.getBoolean(retvalbool) && !retvalbool)
129 event->preventDefault();
133 if (scriptExecutionContext->isDocument())
134 Document::updateDocumentsRendering();
139 bool JSAbstractEventListener::isInline() const
144 // -------------------------------------------------------------------------
146 JSEventListener::JSEventListener(JSObject* listener, JSDOMGlobalObject* globalObject, bool isInline)
147 : JSAbstractEventListener(isInline)
148 , m_listener(listener)
149 , m_globalObject(globalObject)
152 JSDOMWindow::JSListenersMap& listeners = isInline
153 ? globalObject->jsInlineEventListeners() : globalObject->jsEventListeners();
154 listeners.set(m_listener, this);
158 JSEventListener::~JSEventListener()
160 if (m_listener && m_globalObject) {
161 JSDOMWindow::JSListenersMap& listeners = isInline()
162 ? m_globalObject->jsInlineEventListeners() : m_globalObject->jsEventListeners();
163 listeners.remove(m_listener);
167 JSObject* JSEventListener::listenerObj() const
172 JSDOMGlobalObject* JSEventListener::globalObject() const
174 return m_globalObject;
177 void JSEventListener::clearGlobalObject()
182 void JSEventListener::mark()
184 if (m_listener && !m_listener->marked())
189 static WTF::RefCountedLeakCounter eventListenerCounter("EventListener");
192 // -------------------------------------------------------------------------
194 JSProtectedEventListener::JSProtectedEventListener(JSObject* listener, JSDOMGlobalObject* globalObject, bool isInline)
195 : JSAbstractEventListener(isInline)
196 , m_listener(listener)
197 , m_globalObject(globalObject)
200 JSDOMWindow::ProtectedListenersMap& listeners = isInline
201 ? m_globalObject->jsProtectedInlineEventListeners() : m_globalObject->jsProtectedEventListeners();
202 listeners.set(m_listener, this);
205 eventListenerCounter.increment();
209 JSProtectedEventListener::~JSProtectedEventListener()
211 if (m_listener && m_globalObject) {
212 JSDOMWindow::ProtectedListenersMap& listeners = isInline()
213 ? m_globalObject->jsProtectedInlineEventListeners() : m_globalObject->jsProtectedEventListeners();
214 listeners.remove(m_listener);
217 eventListenerCounter.decrement();
221 JSObject* JSProtectedEventListener::listenerObj() const
226 JSDOMGlobalObject* JSProtectedEventListener::globalObject() const
228 return m_globalObject;
231 void JSProtectedEventListener::clearGlobalObject()
236 // -------------------------------------------------------------------------
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)
243 , m_lineNumber(lineNumber)
244 , m_originalNode(node)
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.
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)
259 JSObject* JSLazyEventListener::listenerObj() const
266 inline JSValuePtr eventParameterName(JSLazyEventListener::LazyEventListenerType type, ExecState* exec)
269 case JSLazyEventListener::HTMLLazyEventListener:
270 return jsNontrivialString(exec, "event");
272 case JSLazyEventListener::SVGLazyEventListener:
273 return jsNontrivialString(exec, "evt");
276 ASSERT_NOT_REACHED();
277 return jsUndefined();
281 void JSLazyEventListener::parseCode() const
286 if (globalObject()->scriptExecutionContext()->isDocument()) {
287 JSDOMWindow* window = static_cast<JSDOMWindow*>(globalObject());
288 Frame* frame = window->impl()->frame();
291 // FIXME: Is this check needed for non-Document contexts?
292 ScriptController* script = frame->script();
293 if (!script->isEnabled() || script->isPaused())
299 ExecState* exec = globalObject()->globalExec();
302 UString sourceURL(globalObject()->scriptExecutionContext()->url().string());
303 args.append(eventParameterName(m_type, exec));
304 args.append(jsString(exec, m_code));
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?
310 JSFunction* listenerAsFunction = static_cast<JSFunction*>(m_listener.get());
312 if (exec->hadException()) {
313 exec->clearException();
315 // failed to parse, so let's just make this listener a no-op
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();
322 JSValuePtr thisObj = toJS(exec, m_originalNode);
323 if (thisObj.isObject()) {
324 static_cast<JSNode*>(asObject(thisObj))->pushEventHandlerScope(exec, scope);
325 listenerAsFunction->setScope(scope);
329 // no more need to keep the unparsed code around
330 m_functionName = String();
335 JSDOMWindow::ProtectedListenersMap& listeners = globalObject()->jsProtectedInlineEventListeners();
336 listeners.set(m_listener, const_cast<JSLazyEventListener*>(this));
340 } // namespace WebCore