blob: 210723f15956d42d954bcb0b1f8c8ad5c579ea2c [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/bindings/core/v8/js_based_event_listener.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/document_parser.h"
#include "third_party/blink/renderer/core/dom/events/event.h"
#include "third_party/blink/renderer/core/dom/events/event_target.h"
#include "third_party/blink/renderer/platform/instance_counters.h"
namespace blink {
JSBasedEventListener::JSBasedEventListener(ListenerType listener_type)
: EventListener(listener_type) {
DCHECK(IsJSBased());
if (IsMainThread()) {
InstanceCounters::IncrementCounter(
InstanceCounters::kJSEventListenerCounter);
}
}
JSBasedEventListener::~JSBasedEventListener() {
if (IsMainThread()) {
InstanceCounters::DecrementCounter(
InstanceCounters::kJSEventListenerCounter);
}
}
bool JSBasedEventListener::BelongsToTheCurrentWorld(
ExecutionContext* execution_context) const {
v8::Isolate* isolate = GetIsolate();
if (!isolate->GetCurrentContext().IsEmpty() &&
&GetWorld() == &DOMWrapperWorld::Current(isolate))
return true;
// If currently parsing, the parser could be accessing this listener
// outside of any v8 context; check if it belongs to the main world.
if (!isolate->InContext() && execution_context &&
execution_context->IsDocument()) {
Document* document = To<Document>(execution_context);
if (document->Parser() && document->Parser()->IsParsing())
return GetWorld().IsMainWorld();
}
return false;
}
// Implements step 2. of "inner invoke".
// https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke
void JSBasedEventListener::handleEvent(
ExecutionContext* execution_context_of_event_target,
Event* event) {
DCHECK(execution_context_of_event_target);
DCHECK(event);
v8::Isolate* isolate = GetIsolate();
// Don't reenter V8 if execution was terminated in this instance of V8.
// For example, worker can be terminated in event listener, and also window
// can be terminated from inspector by the TerminateExecution method.
if (isolate->IsExecutionTerminating())
return;
if (!event->CanBeDispatchedInWorld(GetWorld()))
return;
{
v8::HandleScope scope(isolate);
// Calling |GetListenerObject()| here may cause compilation of the
// uncompiled script body in eventHandler's value earlier than standard's
// order, which says it should be done in step 10. There is no behavioral
// difference but the advantage that we can use listener's |ScriptState|
// after it get compiled.
// https://html.spec.whatwg.org/multipage/webappapis.html#event-handler-value
v8::Local<v8::Value> listener = GetListenerObject(*event->target());
if (listener.IsEmpty() || !listener->IsObject())
return;
}
ScriptState* script_state_of_listener = GetScriptState();
DCHECK(script_state_of_listener);
if (!script_state_of_listener->ContextIsValid())
return;
ScriptState::Scope scope(script_state_of_listener);
// https://dom.spec.whatwg.org/#firing-events
// Step 2. of firing events: Let event be the result of creating an event
// given eventConstructor, in the relevant Realm of target.
//
// |js_event|, a V8 wrapper object for |event|, must be created in the
// relevant realm of the event target. The world must match the event
// listener's world.
v8::Local<v8::Context> v8_context =
ToV8Context(execution_context_of_event_target, GetWorld());
if (v8_context.IsEmpty())
return;
v8::Local<v8::Value> js_event = ToV8(event, v8_context->Global(), isolate);
if (js_event.IsEmpty())
return;
// Step 6: Let |global| be listener callback’s associated Realm’s global
// object.
v8::Local<v8::Object> global =
script_state_of_listener->GetContext()->Global();
// Step 8: If global is a Window object, then:
// Set currentEvent to global’s current event.
// If tuple’s item-in-shadow-tree is false, then set global’s current event to
// event.
V8PrivateProperty::Symbol event_symbol =
V8PrivateProperty::GetGlobalEvent(isolate);
ExecutionContext* execution_context_of_listener =
ExecutionContext::From(script_state_of_listener);
v8::Local<v8::Value> current_event;
if (execution_context_of_listener->IsDocument()) {
current_event = event_symbol.GetOrUndefined(global).ToLocalChecked();
// Expose the event object as |window.event|, except when the event's target
// is in a V1 shadow tree.
Node* target_node = event->target()->ToNode();
if (!(target_node && target_node->IsInV1ShadowTree()))
event_symbol.Set(global, js_event);
}
{
// Catch exceptions thrown in the event listener if any and report them to
// DevTools console.
v8::TryCatch try_catch(isolate);
try_catch.SetVerbose(true);
// Step 10: Call a listener with event's currentTarget as receiver and event
// and handle errors if thrown.
CallListenerFunction(*event->currentTarget(), *event, js_event);
if (try_catch.HasCaught()) {
// Step 10-2: Set legacyOutputDidListenersThrowFlag if given.
event->LegacySetDidListenersThrowFlag();
}
// |event_symbol.Set(global, current_event)| cannot and does not have to be
// performed when the isolate is terminating.
if (isolate->IsExecutionTerminating())
return;
}
// Step 12: If |global| is a Window object, then set |global|’s current event
// to |current_event|.
if (execution_context_of_listener->IsDocument())
event_symbol.Set(global, current_event);
}
} // namespace blink