blob: fe8e419f698a603b80f8b7a17561a46f48dd8157 [file] [log] [blame]
/*
* Copyright (C) 2006, 2007, 2008, 2009 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "bindings/core/v8/V8AbstractEventListener.h"
#include "bindings/core/v8/V8Binding.h"
#include "bindings/core/v8/V8Event.h"
#include "bindings/core/v8/V8EventListenerHelper.h"
#include "bindings/core/v8/V8EventTarget.h"
#include "bindings/core/v8/V8HiddenValue.h"
#include "core/events/BeforeUnloadEvent.h"
#include "core/events/Event.h"
#include "core/inspector/InstanceCounters.h"
#include "core/workers/WorkerGlobalScope.h"
namespace blink {
V8AbstractEventListener::V8AbstractEventListener(bool isAttribute,
DOMWrapperWorld& world,
v8::Isolate* isolate)
: EventListener(JSEventListenerType),
m_isAttribute(isAttribute),
m_world(world),
m_isolate(isolate),
m_workerGlobalScope(nullptr) {
if (isMainThread())
InstanceCounters::incrementCounter(
InstanceCounters::JSEventListenerCounter);
else
m_workerGlobalScope = toWorkerGlobalScope(currentExecutionContext(isolate));
}
V8AbstractEventListener::~V8AbstractEventListener() {
ASSERT(m_listener.isEmpty());
if (isMainThread())
InstanceCounters::decrementCounter(
InstanceCounters::JSEventListenerCounter);
}
void V8AbstractEventListener::handleEvent(ExecutionContext* executionContext,
Event* event) {
if (!executionContext)
return;
// Don't reenter V8 if execution was terminated in this instance of V8.
if (executionContext->isJSExecutionForbidden())
return;
// A ScriptState used by the event listener needs to be calculated based on
// the ExecutionContext that fired the the event listener and the world
// that installed the event listener.
ASSERT(event);
v8::HandleScope handleScope(toIsolate(executionContext));
v8::Local<v8::Context> v8Context = toV8Context(executionContext, world());
if (v8Context.IsEmpty())
return;
ScriptState* scriptState = ScriptState::from(v8Context);
if (!scriptState->contextIsValid())
return;
handleEvent(scriptState, event);
}
void V8AbstractEventListener::handleEvent(ScriptState* scriptState,
Event* event) {
ScriptState::Scope scope(scriptState);
// Get the V8 wrapper for the event object.
v8::Local<v8::Value> jsEvent =
toV8(event, scriptState->context()->Global(), isolate());
if (jsEvent.IsEmpty())
return;
invokeEventHandler(scriptState, event,
v8::Local<v8::Value>::New(isolate(), jsEvent));
}
void V8AbstractEventListener::setListenerObject(
v8::Local<v8::Object> listener) {
ASSERT(m_listener.isEmpty());
// Balanced in wrapperCleared xor clearListenerObject.
if (m_workerGlobalScope) {
m_workerGlobalScope->registerEventListener(this);
} else {
m_keepAlive = this;
}
m_listener.set(isolate(), listener);
m_listener.setWeak(this, &wrapperCleared);
}
void V8AbstractEventListener::invokeEventHandler(ScriptState* scriptState,
Event* event,
v8::Local<v8::Value> jsEvent) {
if (!event->canBeDispatchedInWorld(world()))
return;
v8::Local<v8::Value> returnValue;
{
// Catch exceptions thrown in the event handler so they do not propagate to javascript code that caused the event to fire.
v8::TryCatch tryCatch(isolate());
tryCatch.SetVerbose(true);
// Save the old 'event' property so we can restore it later.
v8::Local<v8::Value> savedEvent = V8HiddenValue::getHiddenValue(
scriptState, scriptState->context()->Global(),
V8HiddenValue::event(isolate()));
tryCatch.Reset();
// Make the event available in the global object, so LocalDOMWindow can expose it.
V8HiddenValue::setHiddenValue(scriptState, scriptState->context()->Global(),
V8HiddenValue::event(isolate()), jsEvent);
tryCatch.Reset();
returnValue = callListenerFunction(scriptState, jsEvent, event);
if (tryCatch.HasCaught())
event->target()->uncaughtExceptionInEventHandler();
if (!tryCatch.CanContinue()) { // Result of TerminateExecution().
if (scriptState->getExecutionContext()->isWorkerGlobalScope())
toWorkerGlobalScope(scriptState->getExecutionContext())
->scriptController()
->forbidExecution();
return;
}
tryCatch.Reset();
// Restore the old event. This must be done for all exit paths through this method.
if (savedEvent.IsEmpty())
V8HiddenValue::setHiddenValue(
scriptState, scriptState->context()->Global(),
V8HiddenValue::event(isolate()), v8::Undefined(isolate()));
else
V8HiddenValue::setHiddenValue(
scriptState, scriptState->context()->Global(),
V8HiddenValue::event(isolate()), savedEvent);
tryCatch.Reset();
}
if (returnValue.IsEmpty())
return;
if (m_isAttribute && !returnValue->IsNull() && !returnValue->IsUndefined() &&
event->isBeforeUnloadEvent()) {
TOSTRING_VOID(V8StringResource<>, stringReturnValue, returnValue);
toBeforeUnloadEvent(event)->setReturnValue(stringReturnValue);
}
if (m_isAttribute && shouldPreventDefault(returnValue))
event->preventDefault();
}
bool V8AbstractEventListener::shouldPreventDefault(
v8::Local<v8::Value> returnValue) {
// Prevent default action if the return value is false in accord with the spec
// http://www.w3.org/TR/html5/webappapis.html#event-handler-attributes
return returnValue->IsBoolean() && !returnValue.As<v8::Boolean>()->Value();
}
v8::Local<v8::Object> V8AbstractEventListener::getReceiverObject(
ScriptState* scriptState,
Event* event) {
v8::Local<v8::Object> listener = m_listener.newLocal(isolate());
if (!m_listener.isEmpty() && !listener->IsFunction())
return listener;
EventTarget* target = event->currentTarget();
v8::Local<v8::Value> value =
toV8(target, scriptState->context()->Global(), isolate());
if (value.IsEmpty())
return v8::Local<v8::Object>();
return v8::Local<v8::Object>::New(isolate(),
v8::Local<v8::Object>::Cast(value));
}
bool V8AbstractEventListener::belongsToTheCurrentWorld() const {
return ScriptState::hasCurrentScriptState(isolate()) &&
&world() == &DOMWrapperWorld::current(isolate());
}
void V8AbstractEventListener::clearListenerObject() {
if (!hasExistingListenerObject())
return;
m_listener.clear();
if (m_workerGlobalScope) {
m_workerGlobalScope->deregisterEventListener(this);
} else {
m_keepAlive.clear();
}
}
void V8AbstractEventListener::wrapperCleared(
const v8::WeakCallbackInfo<V8AbstractEventListener>& data) {
data.GetParameter()->clearListenerObject();
}
DEFINE_TRACE(V8AbstractEventListener) {
visitor->trace(m_workerGlobalScope);
EventListener::trace(visitor);
}
DEFINE_TRACE_WRAPPERS(V8AbstractEventListener) {
visitor->traceWrappers(&m_listener);
}
} // namespace blink