onbeforeunload event return value coercion is not per-spec
[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 "ContentSecurityPolicy.h"
25 #include "Event.h"
26 #include "Frame.h"
27 #include "HTMLElement.h"
28 #include "JSDOMConvert.h"
29 #include "JSDocument.h"
30 #include "JSEvent.h"
31 #include "JSEventTarget.h"
32 #include "JSMainThreadExecState.h"
33 #include "JSMainThreadExecStateInstrumentation.h"
34 #include "ScriptController.h"
35 #include "WorkerGlobalScope.h"
36 #include <runtime/ExceptionHelpers.h>
37 #include <runtime/JSLock.h>
38 #include <runtime/VMEntryScope.h>
39 #include <runtime/Watchdog.h>
40 #include <wtf/Ref.h>
41
42 using namespace JSC;
43
44 namespace WebCore {
45
46 JSEventListener::JSEventListener(JSObject* function, JSObject* wrapper, bool isAttribute, DOMWrapperWorld& isolatedWorld)
47     : EventListener(JSEventListenerType)
48     , m_wrapper(wrapper)
49     , m_isAttribute(isAttribute)
50     , m_isolatedWorld(&isolatedWorld)
51 {
52     if (wrapper) {
53         JSC::Heap::heap(wrapper)->writeBarrier(wrapper, function);
54         m_jsFunction = JSC::Weak<JSC::JSObject>(function);
55     } else
56         ASSERT(!function);
57 }
58
59 JSEventListener::~JSEventListener()
60 {
61 }
62
63 JSObject* JSEventListener::initializeJSFunction(ScriptExecutionContext*) const
64 {
65     return 0;
66 }
67
68 void JSEventListener::visitJSFunction(SlotVisitor& visitor)
69 {
70     // If m_wrapper is 0, then m_jsFunction is zombied, and should never be accessed.
71     if (!m_wrapper)
72         return;
73
74     visitor.append(m_jsFunction);
75 }
76
77 static void handleBeforeUnloadEventReturnValue(BeforeUnloadEvent& event, const String& returnValue)
78 {
79     if (returnValue.isNull())
80         return;
81
82     event.preventDefault();
83     if (event.returnValue().isEmpty())
84         event.setReturnValue(returnValue);
85 }
86
87 void JSEventListener::handleEvent(ScriptExecutionContext* scriptExecutionContext, Event* event)
88 {
89     ASSERT(scriptExecutionContext);
90     if (!scriptExecutionContext || scriptExecutionContext->isJSExecutionForbidden())
91         return;
92
93     VM& vm = scriptExecutionContext->vm();
94     JSLockHolder lock(vm);
95     auto scope = DECLARE_CATCH_SCOPE(vm);
96     // See https://dom.spec.whatwg.org/#dispatching-events spec on calling handleEvent.
97     // "If this throws an exception, report the exception." It should not propagate the
98     // exception.
99
100     JSObject* jsFunction = this->jsFunction(scriptExecutionContext);
101     if (!jsFunction)
102         return;
103
104     JSDOMGlobalObject* globalObject = toJSDOMGlobalObject(scriptExecutionContext, *m_isolatedWorld);
105     if (!globalObject)
106         return;
107
108     if (scriptExecutionContext->isDocument()) {
109         JSDOMWindow* window = jsCast<JSDOMWindow*>(globalObject);
110         if (!window->wrapped().isCurrentlyDisplayedInFrame())
111             return;
112         if (wasCreatedFromMarkup() && !scriptExecutionContext->contentSecurityPolicy()->allowInlineEventHandlers(sourceURL(), sourcePosition().m_line))
113             return;
114         // FIXME: Is this check needed for other contexts?
115         ScriptController& script = window->wrapped().frame()->script();
116         if (!script.canExecuteScripts(AboutToExecuteScript) || script.isPaused())
117             return;
118     }
119
120     ExecState* exec = globalObject->globalExec();
121     JSValue handleEventFunction = jsFunction;
122
123     CallData callData;
124     CallType callType = getCallData(handleEventFunction, callData);
125     // If jsFunction is not actually a function, see if it implements the EventListener interface and use that
126     if (callType == CallType::None) {
127         handleEventFunction = jsFunction->get(exec, Identifier::fromString(exec, "handleEvent"));
128         if (UNLIKELY(scope.exception())) {
129             auto* exception = scope.exception();
130             scope.clearException();
131
132             event->target()->uncaughtExceptionInEventHandler();
133             reportException(exec, exception);
134             return;
135         }
136         callType = getCallData(handleEventFunction, callData);
137     }
138
139     if (callType != CallType::None) {
140         Ref<JSEventListener> protectedThis(*this);
141
142         MarkedArgumentBuffer args;
143         args.append(toJS(exec, globalObject, event));
144
145         Event* savedEvent = globalObject->currentEvent();
146         globalObject->setCurrentEvent(event);
147
148         VMEntryScope entryScope(vm, vm.entryScope ? vm.entryScope->globalObject() : globalObject);
149
150         InspectorInstrumentationCookie cookie = JSMainThreadExecState::instrumentFunctionCall(scriptExecutionContext, callType, callData);
151
152         JSValue thisValue = handleEventFunction == jsFunction ? toJS(exec, globalObject, event->currentTarget()) : jsFunction;
153         NakedPtr<JSC::Exception> exception;
154         JSValue retval = scriptExecutionContext->isDocument()
155             ? JSMainThreadExecState::profiledCall(exec, JSC::ProfilingReason::Other, handleEventFunction, callType, callData, thisValue, args, exception)
156             : JSC::profiledCall(exec, JSC::ProfilingReason::Other, handleEventFunction, callType, callData, thisValue, args, exception);
157
158         InspectorInstrumentation::didCallFunction(cookie, scriptExecutionContext);
159
160         globalObject->setCurrentEvent(savedEvent);
161
162         if (is<WorkerGlobalScope>(*scriptExecutionContext)) {
163             auto scriptController = downcast<WorkerGlobalScope>(*scriptExecutionContext).script();
164             bool terminatorCausedException = (scope.exception() && isTerminatedExecutionException(vm, scope.exception()));
165             if (terminatorCausedException || scriptController->isTerminatingExecution())
166                 scriptController->forbidExecution();
167         }
168
169         if (exception) {
170             event->target()->uncaughtExceptionInEventHandler();
171             reportException(exec, exception);
172         } else {
173             if (is<BeforeUnloadEvent>(*event))
174                 handleBeforeUnloadEventReturnValue(downcast<BeforeUnloadEvent>(*event), convert<IDLNullable<IDLDOMString>>(*exec, retval, StringConversionConfiguration::Normal));
175
176             if (m_isAttribute) {
177                 if (retval.isFalse())
178                     event->preventDefault();
179             }
180         }
181     }
182 }
183
184 bool JSEventListener::virtualisAttribute() const
185 {
186     return m_isAttribute;
187 }
188
189 bool JSEventListener::operator==(const EventListener& listener) const
190 {
191     if (const JSEventListener* jsEventListener = JSEventListener::cast(&listener))
192         return m_jsFunction == jsEventListener->m_jsFunction && m_isAttribute == jsEventListener->m_isAttribute;
193     return false;
194 }
195
196 static inline JSC::JSValue eventHandlerAttribute(EventListener* abstractListener, ScriptExecutionContext& context)
197 {
198     if (!abstractListener)
199         return jsNull();
200
201     auto* listener = JSEventListener::cast(abstractListener);
202     if (!listener)
203         return jsNull();
204
205     auto* function = listener->jsFunction(&context);
206     if (!function)
207         return jsNull();
208
209     return function;
210 }
211
212 static inline RefPtr<JSEventListener> createEventListenerForEventHandlerAttribute(JSC::ExecState& state, JSC::JSValue listener, JSC::JSObject& wrapper)
213 {
214     if (!listener.isObject())
215         return nullptr;
216     return JSEventListener::create(asObject(listener), &wrapper, true, currentWorld(&state));
217 }
218
219 JSC::JSValue eventHandlerAttribute(EventTarget& target, const AtomicString& eventType)
220 {
221     return eventHandlerAttribute(target.attributeEventListener(eventType), *target.scriptExecutionContext());
222 }
223
224 void setEventHandlerAttribute(JSC::ExecState& state, JSC::JSObject& wrapper, EventTarget& target, const AtomicString& eventType, JSC::JSValue value)
225 {
226     target.setAttributeEventListener(eventType, createEventListenerForEventHandlerAttribute(state, value, wrapper));
227 }
228
229 JSC::JSValue windowEventHandlerAttribute(HTMLElement& element, const AtomicString& eventType)
230 {
231     auto& document = element.document();
232     return eventHandlerAttribute(document.getWindowAttributeEventListener(eventType), document);
233 }
234
235 void setWindowEventHandlerAttribute(JSC::ExecState& state, JSC::JSObject& wrapper, HTMLElement& element, const AtomicString& eventType, JSC::JSValue value)
236 {
237     ASSERT(wrapper.globalObject());
238     element.document().setWindowAttributeEventListener(eventType, createEventListenerForEventHandlerAttribute(state, value, *wrapper.globalObject()));
239 }
240
241 JSC::JSValue windowEventHandlerAttribute(DOMWindow& window, const AtomicString& eventType)
242 {
243     return eventHandlerAttribute(window, eventType);
244 }
245
246 void setWindowEventHandlerAttribute(JSC::ExecState& state, JSC::JSObject& wrapper, DOMWindow& window, const AtomicString& eventType, JSC::JSValue value)
247 {
248     setEventHandlerAttribute(state, wrapper, window, eventType, value);
249 }
250
251 JSC::JSValue documentEventHandlerAttribute(HTMLElement& element, const AtomicString& eventType)
252 {
253     auto& document = element.document();
254     return eventHandlerAttribute(document.attributeEventListener(eventType), document);
255 }
256
257 void setDocumentEventHandlerAttribute(JSC::ExecState& state, JSC::JSObject& wrapper, HTMLElement& element, const AtomicString& eventType, JSC::JSValue value)
258 {
259     ASSERT(wrapper.globalObject());
260     auto& document = element.document();
261     auto* documentWrapper = JSC::jsCast<JSDocument*>(toJS(&state, JSC::jsCast<JSDOMGlobalObject*>(wrapper.globalObject()), document));
262     ASSERT(documentWrapper);
263     document.setAttributeEventListener(eventType, createEventListenerForEventHandlerAttribute(state, value, *documentWrapper));
264 }
265
266 JSC::JSValue documentEventHandlerAttribute(Document& document, const AtomicString& eventType)
267 {
268     return eventHandlerAttribute(document, eventType);
269 }
270
271 void setDocumentEventHandlerAttribute(JSC::ExecState& state, JSC::JSObject& wrapper, Document& document, const AtomicString& eventType, JSC::JSValue value)
272 {
273     setEventHandlerAttribute(state, wrapper, document, eventType, value);
274 }
275
276 } // namespace WebCore