blob: 5931ae9643630e756e5f797a42e687803b4a1e44 [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/binding_security.h"
#include "third_party/blink/renderer/bindings/core/v8/source_location.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/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/platform/instrumentation/instance_counters.h"
namespace blink {
JSBasedEventListener::JSBasedEventListener() {
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 &&
IsA<LocalDOMWindow>(execution_context)) {
Document* document = To<LocalDOMWindow>(execution_context)->document();
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::Invoke(
ExecutionContext* execution_context_of_event_target,
Event* event) {
DCHECK(execution_context_of_event_target);
DCHECK(event);
DCHECK(event->target());
DCHECK(event->currentTarget());
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 handle_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/C/#event-handler-value
v8::Local<v8::Value> listener = GetListenerObject(*event->currentTarget());
if (listener.IsEmpty() || !listener->IsObject())
return;
}
ScriptState* script_state_of_listener = GetScriptStateOrReportError("invoke");
if (!script_state_of_listener)
return; // The error is already reported.
if (!script_state_of_listener->ContextIsValid())
return; // Silently fail.
ScriptState::Scope listener_script_state_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_of_event_target =
ToV8Context(execution_context_of_event_target, GetWorld());
if (v8_context_of_event_target.IsEmpty())
return;
// Check if the current context, which is set to the listener's relevant
// context by creating |listener_script_state_scope|, has access to the
// event target's relevant context before creating |js_event|. SecurityError
// is thrown if it doesn't have access.
if (!BindingSecurity::ShouldAllowAccessToV8Context(
script_state_of_listener->GetContext(), v8_context_of_event_target,
BindingSecurity::ErrorReportOption::kReport)) {
return;
}
v8::Local<v8::Value> js_event =
ToV8(event, v8_context_of_event_target->Global(), isolate);
if (js_event.IsEmpty())
return;
// Step 6: Let |global| be listener callback’s associated Realm’s global
// object.
LocalDOMWindow* window =
ToLocalDOMWindow(script_state_of_listener->GetContext());
// Step 7: Let |current_event| be undefined.
Event* current_event = nullptr;
// Step 8: If |global| is a Window object, then:
if (window) {
// Step 8-1: Set |current_event| to |global|’s current event.
current_event = window->CurrentEvent();
// Step 8-2: If |struct|’s invocation-target-in-shadow-tree is false (i.e.,
// event's target is in a shadow tree), then set |global|’s current
// event to event.
Node* target_node = event->target()->ToNode();
if (!(target_node && target_node->IsInShadowTree()))
window->SetCurrentEvent(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.
InvokeInternal(*event->currentTarget(), *event, js_event);
if (try_catch.HasCaught()) {
// Step 10-2: Set legacyOutputDidListenersThrowFlag if given.
event->LegacySetDidListenersThrowFlag();
}
}
// Step 12: If |global| is a Window object, then set |global|’s current event
// to |current_event|.
if (window)
window->SetCurrentEvent(current_event);
}
std::unique_ptr<SourceLocation> JSBasedEventListener::GetSourceLocation(
EventTarget& target) {
v8::HandleScope handle_scope(GetIsolate());
v8::Local<v8::Value> effective_function = GetEffectiveFunction(target);
if (effective_function->IsFunction())
return SourceLocation::FromFunction(effective_function.As<v8::Function>());
return nullptr;
}
} // namespace blink