d914e303bd2764bf62b6110d99aa07ce68b1dcc5
[WebKit-https.git] / Source / 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 "BeforeUnloadEvent.h"
24 #include "Event.h"
25 #include "Frame.h"
26 #include "InspectorCounters.h"
27 #include "JSEvent.h"
28 #include "JSEventTarget.h"
29 #include "JSMainThreadExecState.h"
30 #include "JSMainThreadExecStateInstrumentation.h"
31 #include "ScriptController.h"
32 #include "WorkerGlobalScope.h"
33 #include <runtime/ExceptionHelpers.h>
34 #include <runtime/JSLock.h>
35 #include <wtf/Ref.h>
36 #include <wtf/RefCountedLeakCounter.h>
37
38 using namespace JSC;
39
40 namespace WebCore {
41
42 JSEventListener::JSEventListener(JSObject* function, JSObject* wrapper, bool isAttribute, DOMWrapperWorld* isolatedWorld)
43     : EventListener(JSEventListenerType)
44     , m_wrapper(wrapper)
45     , m_isAttribute(isAttribute)
46     , m_isolatedWorld(isolatedWorld)
47 {
48     if (wrapper) {
49         JSC::Heap::writeBarrier(wrapper, function);
50         m_jsFunction = JSC::Weak<JSC::JSObject>(function);
51     } else
52         ASSERT(!function);
53 #if ENABLE(INSPECTOR)
54     ThreadLocalInspectorCounters::current().incrementCounter(ThreadLocalInspectorCounters::JSEventListenerCounter);
55 #endif
56 }
57
58 JSEventListener::~JSEventListener()
59 {
60 #if ENABLE(INSPECTOR)
61     ThreadLocalInspectorCounters::current().decrementCounter(ThreadLocalInspectorCounters::JSEventListenerCounter);
62 #endif
63 }
64
65 JSObject* JSEventListener::initializeJSFunction(ScriptExecutionContext*) const
66 {
67     return 0;
68 }
69
70 void JSEventListener::visitJSFunction(SlotVisitor& visitor)
71 {
72     // If m_wrapper is 0, then m_jsFunction is zombied, and should never be accessed.
73     if (!m_wrapper)
74         return;
75
76     visitor.appendUnbarrieredWeak(&m_jsFunction);
77 }
78
79 void JSEventListener::handleEvent(ScriptExecutionContext* scriptExecutionContext, Event* event)
80 {
81     ASSERT(scriptExecutionContext);
82     if (!scriptExecutionContext || scriptExecutionContext->isJSExecutionForbidden())
83         return;
84
85     JSLockHolder lock(scriptExecutionContext->vm());
86
87     JSObject* jsFunction = this->jsFunction(scriptExecutionContext);
88     if (!jsFunction)
89         return;
90
91     JSDOMGlobalObject* globalObject = toJSDOMGlobalObject(scriptExecutionContext, m_isolatedWorld.get());
92     if (!globalObject)
93         return;
94
95     if (scriptExecutionContext->isDocument()) {
96         JSDOMWindow* window = jsCast<JSDOMWindow*>(globalObject);
97         if (!window->impl()->isCurrentlyDisplayedInFrame())
98             return;
99         // FIXME: Is this check needed for other contexts?
100         ScriptController& script = window->impl()->frame()->script();
101         if (!script.canExecuteScripts(AboutToExecuteScript) || script.isPaused())
102             return;
103     }
104
105     ExecState* exec = globalObject->globalExec();
106     JSValue handleEventFunction = jsFunction;
107
108     CallData callData;
109     CallType callType = getCallData(handleEventFunction, callData);
110     // If jsFunction is not actually a function, see if it implements the EventListener interface and use that
111     if (callType == CallTypeNone) {
112         handleEventFunction = jsFunction->get(exec, Identifier(exec, "handleEvent"));
113         callType = getCallData(handleEventFunction, callData);
114     }
115
116     if (callType != CallTypeNone) {
117         Ref<JSEventListener> protect(*this);
118
119         MarkedArgumentBuffer args;
120         args.append(toJS(exec, globalObject, event));
121
122         Event* savedEvent = globalObject->currentEvent();
123         globalObject->setCurrentEvent(event);
124
125         VM& vm = globalObject->vm();
126         DynamicGlobalObjectScope globalObjectScope(vm, vm.dynamicGlobalObject ? vm.dynamicGlobalObject : globalObject);
127
128         InspectorInstrumentationCookie cookie = JSMainThreadExecState::instrumentFunctionCall(scriptExecutionContext, callType, callData);
129
130         JSValue thisValue = handleEventFunction == jsFunction ? toJS(exec, globalObject, event->currentTarget()) : jsFunction;
131         JSValue retval = scriptExecutionContext->isDocument()
132             ? JSMainThreadExecState::call(exec, handleEventFunction, callType, callData, thisValue, args)
133             : JSC::call(exec, handleEventFunction, callType, callData, thisValue, args);
134
135         InspectorInstrumentation::didCallFunction(cookie);
136
137         globalObject->setCurrentEvent(savedEvent);
138
139 #if ENABLE(WORKERS)
140         if (scriptExecutionContext->isWorkerGlobalScope()) {
141             bool terminatorCausedException = (exec->hadException() && isTerminatedExecutionException(exec->exception()));
142             if (terminatorCausedException || vm.watchdog.didFire())
143                 static_cast<WorkerGlobalScope*>(scriptExecutionContext)->script()->forbidExecution();
144         }
145 #endif
146
147         if (exec->hadException()) {
148             event->target()->uncaughtExceptionInEventHandler();
149             reportCurrentException(exec);
150         } else {
151             if (!retval.isUndefinedOrNull() && event->isBeforeUnloadEvent())
152                 toBeforeUnloadEvent(event)->setReturnValue(retval.toString(exec)->value(exec));
153             if (m_isAttribute) {
154                 if (retval.isFalse())
155                     event->preventDefault();
156             }
157         }
158     }
159 }
160
161 bool JSEventListener::virtualisAttribute() const
162 {
163     return m_isAttribute;
164 }
165
166 bool JSEventListener::operator==(const EventListener& listener)
167 {
168     if (const JSEventListener* jsEventListener = JSEventListener::cast(&listener))
169         return m_jsFunction == jsEventListener->m_jsFunction && m_isAttribute == jsEventListener->m_isAttribute;
170     return false;
171 }
172
173 } // namespace WebCore