9d2ce6b49b2611eec34ba6b562f4dfc7e02e22c1
[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, 2013 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 "HTMLElement.h"
27 #include "JSDocument.h"
28 #include "JSEvent.h"
29 #include "JSEventTarget.h"
30 #include "JSMainThreadExecState.h"
31 #include "JSMainThreadExecStateInstrumentation.h"
32 #include "MicroTask.h"
33 #include "ScriptController.h"
34 #include "WorkerGlobalScope.h"
35 #include <runtime/ExceptionHelpers.h>
36 #include <runtime/JSLock.h>
37 #include <runtime/VMEntryScope.h>
38 #include <wtf/Ref.h>
39 #include <wtf/RefCountedLeakCounter.h>
40
41 using namespace JSC;
42
43 namespace WebCore {
44
45 JSEventListener::JSEventListener(JSObject* function, JSObject* wrapper, bool isAttribute, DOMWrapperWorld& isolatedWorld)
46     : EventListener(JSEventListenerType)
47     , m_wrapper(wrapper)
48     , m_isAttribute(isAttribute)
49     , m_isolatedWorld(&isolatedWorld)
50 {
51     if (wrapper) {
52         JSC::Heap::heap(wrapper)->writeBarrier(wrapper, function);
53         m_jsFunction = JSC::Weak<JSC::JSObject>(function);
54     } else
55         ASSERT(!function);
56 }
57
58 JSEventListener::~JSEventListener()
59 {
60 }
61
62 JSObject* JSEventListener::initializeJSFunction(ScriptExecutionContext*) const
63 {
64     return 0;
65 }
66
67 void JSEventListener::visitJSFunction(SlotVisitor& visitor)
68 {
69     // If m_wrapper is 0, then m_jsFunction is zombied, and should never be accessed.
70     if (!m_wrapper)
71         return;
72
73     visitor.appendUnbarrieredWeak(&m_jsFunction);
74 }
75
76 void JSEventListener::handleEvent(ScriptExecutionContext* scriptExecutionContext, Event* event)
77 {
78     ASSERT(scriptExecutionContext);
79     if (!scriptExecutionContext || scriptExecutionContext->isJSExecutionForbidden())
80         return;
81
82     JSLockHolder lock(scriptExecutionContext->vm());
83
84     JSObject* jsFunction = this->jsFunction(scriptExecutionContext);
85     if (!jsFunction)
86         return;
87
88     JSDOMGlobalObject* globalObject = toJSDOMGlobalObject(scriptExecutionContext, *m_isolatedWorld);
89     if (!globalObject)
90         return;
91
92     if (scriptExecutionContext->isDocument()) {
93         JSDOMWindow* window = jsCast<JSDOMWindow*>(globalObject);
94         if (!window->impl().isCurrentlyDisplayedInFrame())
95             return;
96         // FIXME: Is this check needed for other contexts?
97         ScriptController& script = window->impl().frame()->script();
98         if (!script.canExecuteScripts(AboutToExecuteScript) || script.isPaused())
99             return;
100     }
101
102     ExecState* exec = globalObject->globalExec();
103     JSValue handleEventFunction = jsFunction;
104
105     CallData callData;
106     CallType callType = getCallData(handleEventFunction, callData);
107     // If jsFunction is not actually a function, see if it implements the EventListener interface and use that
108     if (callType == CallTypeNone) {
109         handleEventFunction = jsFunction->get(exec, Identifier::fromString(exec, "handleEvent"));
110         callType = getCallData(handleEventFunction, callData);
111     }
112
113     if (callType != CallTypeNone) {
114         Ref<JSEventListener> protect(*this);
115
116         MarkedArgumentBuffer args;
117         args.append(toJS(exec, globalObject, event));
118
119         Event* savedEvent = globalObject->currentEvent();
120         globalObject->setCurrentEvent(event);
121
122         VM& vm = globalObject->vm();
123         VMEntryScope entryScope(vm, vm.entryScope ? vm.entryScope->globalObject() : globalObject);
124
125         InspectorInstrumentationCookie cookie = JSMainThreadExecState::instrumentFunctionCall(scriptExecutionContext, callType, callData);
126
127         JSValue thisValue = handleEventFunction == jsFunction ? toJS(exec, globalObject, event->currentTarget()) : jsFunction;
128         JSValue exception;
129         JSValue retval = scriptExecutionContext->isDocument()
130             ? JSMainThreadExecState::call(exec, handleEventFunction, callType, callData, thisValue, args, &exception)
131             : JSC::call(exec, handleEventFunction, callType, callData, thisValue, args, &exception);
132
133         InspectorInstrumentation::didCallFunction(cookie, scriptExecutionContext);
134
135         globalObject->setCurrentEvent(savedEvent);
136
137         if (is<WorkerGlobalScope>(*scriptExecutionContext)) {
138             bool terminatorCausedException = (exec->hadException() && isTerminatedExecutionException(exec->exception()));
139             if (terminatorCausedException || (vm.watchdog && vm.watchdog->didFire()))
140                 downcast<WorkerGlobalScope>(*scriptExecutionContext).script()->forbidExecution();
141         }
142
143         if (exception) {
144             event->target()->uncaughtExceptionInEventHandler();
145             reportException(exec, exception);
146         } else {
147             if (!retval.isUndefinedOrNull() && is<BeforeUnloadEvent>(*event))
148                 downcast<BeforeUnloadEvent>(*event).setReturnValue(retval.toString(exec)->value(exec));
149             if (m_isAttribute) {
150                 if (retval.isFalse())
151                     event->preventDefault();
152             }
153         }
154     }
155     if (scriptExecutionContext->isDocument())
156         MicroTaskQueue::singleton().runMicroTasks();
157 }
158
159 bool JSEventListener::virtualisAttribute() const
160 {
161     return m_isAttribute;
162 }
163
164 bool JSEventListener::operator==(const EventListener& listener)
165 {
166     if (const JSEventListener* jsEventListener = JSEventListener::cast(&listener))
167         return m_jsFunction == jsEventListener->m_jsFunction && m_isAttribute == jsEventListener->m_isAttribute;
168     return false;
169 }
170
171 Ref<JSEventListener> createJSEventListenerForAdd(JSC::ExecState& state, JSC::JSObject& listener, JSC::JSObject& wrapper)
172 {
173     // FIXME: This abstraction is no longer needed. It was part of support for SVGElementInstance.
174     // We should remove it and simplify the bindings generation scripts.
175     return JSEventListener::create(&listener, &wrapper, false, currentWorld(&state));
176 }
177
178 static inline JSC::JSValue eventHandlerAttribute(EventListener* abstractListener, ScriptExecutionContext& context)
179 {
180     if (!abstractListener)
181         return jsNull();
182
183     auto* listener = JSEventListener::cast(abstractListener);
184     if (!listener)
185         return jsNull();
186
187     auto* function = listener->jsFunction(&context);
188     if (!function)
189         return jsNull();
190
191     return function;
192 }
193
194 static inline RefPtr<JSEventListener> createEventListenerForEventHandlerAttribute(JSC::ExecState& state, JSC::JSValue listener, JSC::JSObject& wrapper)
195 {
196     if (!listener.isObject())
197         return nullptr;
198     return JSEventListener::create(asObject(listener), &wrapper, true, currentWorld(&state));
199 }
200
201 JSC::JSValue eventHandlerAttribute(EventTarget& target, const AtomicString& eventType)
202 {
203     return eventHandlerAttribute(target.getAttributeEventListener(eventType), *target.scriptExecutionContext());
204 }
205
206 void setEventHandlerAttribute(JSC::ExecState& state, JSC::JSObject& wrapper, EventTarget& target, const AtomicString& eventType, JSC::JSValue value)
207 {
208     target.setAttributeEventListener(eventType, createEventListenerForEventHandlerAttribute(state, value, wrapper));
209 }
210
211 JSC::JSValue windowEventHandlerAttribute(HTMLElement& element, const AtomicString& eventType)
212 {
213     auto& document = element.document();
214     return eventHandlerAttribute(document.getWindowAttributeEventListener(eventType), document);
215 }
216
217 void setWindowEventHandlerAttribute(JSC::ExecState& state, JSC::JSObject& wrapper, HTMLElement& element, const AtomicString& eventType, JSC::JSValue value)
218 {
219     ASSERT(wrapper.globalObject());
220     element.document().setWindowAttributeEventListener(eventType, createEventListenerForEventHandlerAttribute(state, value, *wrapper.globalObject()));
221 }
222
223 JSC::JSValue documentEventHandlerAttribute(HTMLElement& element, const AtomicString& eventType)
224 {
225     auto& document = element.document();
226     return eventHandlerAttribute(document.getAttributeEventListener(eventType), document);
227 }
228
229 void setDocumentEventHandlerAttribute(JSC::ExecState& state, JSC::JSObject& wrapper, HTMLElement& element, const AtomicString& eventType, JSC::JSValue value)
230 {
231     ASSERT(wrapper.globalObject());
232     auto& document = element.document();
233     auto* documentWrapper = jsDocumentCast(toJS(&state, JSC::jsCast<JSDOMGlobalObject*>(wrapper.globalObject()), document));
234     ASSERT(documentWrapper);
235     document.setAttributeEventListener(eventType, createEventListenerForEventHandlerAttribute(state, value, *documentWrapper));
236 }
237
238 } // namespace WebCore