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