1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "third_party/blink/renderer/bindings/core/v8/js_based_event_listener.h"
6
7 #include "third_party/blink/renderer/bindings/core/v8/binding_security.h"
8 #include "third_party/blink/renderer/bindings/core/v8/source_location.h"
9 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
10 #include "third_party/blink/renderer/core/dom/document.h"
11 #include "third_party/blink/renderer/core/dom/document_parser.h"
12 #include "third_party/blink/renderer/core/dom/events/event.h"
13 #include "third_party/blink/renderer/core/dom/events/event_target.h"
14 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
15 #include "third_party/blink/renderer/platform/instrumentation/instance_counters.h"
16
17 namespace blink {
18
JSBasedEventListener()19 JSBasedEventListener::JSBasedEventListener() {
20 if (IsMainThread()) {
21 InstanceCounters::IncrementCounter(
22 InstanceCounters::kJSEventListenerCounter);
23 }
24 }
25
~JSBasedEventListener()26 JSBasedEventListener::~JSBasedEventListener() {
27 if (IsMainThread()) {
28 InstanceCounters::DecrementCounter(
29 InstanceCounters::kJSEventListenerCounter);
30 }
31 }
32
BelongsToTheCurrentWorld(ExecutionContext * execution_context) const33 bool JSBasedEventListener::BelongsToTheCurrentWorld(
34 ExecutionContext* execution_context) const {
35 v8::Isolate* isolate = GetIsolate();
36 if (!isolate->GetCurrentContext().IsEmpty() &&
37 &GetWorld() == &DOMWrapperWorld::Current(isolate))
38 return true;
39 // If currently parsing, the parser could be accessing this listener
40 // outside of any v8 context; check if it belongs to the main world.
41 if (!isolate->InContext() && execution_context &&
42 IsA<LocalDOMWindow>(execution_context)) {
43 Document* document = To<LocalDOMWindow>(execution_context)->document();
44 if (document->Parser() && document->Parser()->IsParsing())
45 return GetWorld().IsMainWorld();
46 }
47 return false;
48 }
49
50 // Implements step 2. of "inner invoke".
51 // https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke
Invoke(ExecutionContext * execution_context_of_event_target,Event * event)52 void JSBasedEventListener::Invoke(
53 ExecutionContext* execution_context_of_event_target,
54 Event* event) {
55 DCHECK(execution_context_of_event_target);
56 DCHECK(event);
57 DCHECK(event->target());
58 DCHECK(event->currentTarget());
59
60 v8::Isolate* isolate = GetIsolate();
61
62 // Don't reenter V8 if execution was terminated in this instance of V8.
63 // For example, worker can be terminated in event listener, and also window
64 // can be terminated from inspector by the TerminateExecution method.
65 if (isolate->IsExecutionTerminating())
66 return;
67
68 if (!event->CanBeDispatchedInWorld(GetWorld()))
69 return;
70
71 {
72 v8::HandleScope handle_scope(isolate);
73
74 // Calling |GetListenerObject()| here may cause compilation of the
75 // uncompiled script body in eventHandler's value earlier than standard's
76 // order, which says it should be done in step 10. There is no behavioral
77 // difference but the advantage that we can use listener's |ScriptState|
78 // after it get compiled.
79 // https://html.spec.whatwg.org/C/#event-handler-value
80 v8::Local<v8::Value> listener = GetListenerObject(*event->currentTarget());
81
82 if (listener.IsEmpty() || !listener->IsObject())
83 return;
84 }
85
86 ScriptState* script_state_of_listener = GetScriptStateOrReportError("invoke");
87 if (!script_state_of_listener)
88 return; // The error is already reported.
89 if (!script_state_of_listener->ContextIsValid())
90 return; // Silently fail.
91
92 ScriptState::Scope listener_script_state_scope(script_state_of_listener);
93
94 // https://dom.spec.whatwg.org/#firing-events
95 // Step 2. of firing events: Let event be the result of creating an event
96 // given eventConstructor, in the relevant Realm of target.
97 //
98 // |js_event|, a V8 wrapper object for |event|, must be created in the
99 // relevant realm of the event target. The world must match the event
100 // listener's world.
101 v8::Local<v8::Context> v8_context_of_event_target =
102 ToV8Context(execution_context_of_event_target, GetWorld());
103 if (v8_context_of_event_target.IsEmpty())
104 return;
105
106 // Check if the current context, which is set to the listener's relevant
107 // context by creating |listener_script_state_scope|, has access to the
108 // event target's relevant context before creating |js_event|. SecurityError
109 // is thrown if it doesn't have access.
110 if (!BindingSecurity::ShouldAllowAccessToV8Context(
111 script_state_of_listener->GetContext(), v8_context_of_event_target,
112 BindingSecurity::ErrorReportOption::kReport)) {
113 return;
114 }
115
116 v8::Local<v8::Value> js_event =
117 ToV8(event, v8_context_of_event_target->Global(), isolate);
118 if (js_event.IsEmpty())
119 return;
120
121 // Step 6: Let |global| be listener callback’s associated Realm’s global
122 // object.
123 LocalDOMWindow* window =
124 ToLocalDOMWindow(script_state_of_listener->GetContext());
125
126 // Step 7: Let |current_event| be undefined.
127 Event* current_event = nullptr;
128
129 // Step 8: If |global| is a Window object, then:
130 if (window) {
131 // Step 8-1: Set |current_event| to |global|’s current event.
132 current_event = window->CurrentEvent();
133
134 // Step 8-2: If |struct|’s invocation-target-in-shadow-tree is false (i.e.,
135 // event's target is in a V1 shadow tree), then set |global|’s current
136 // event to event.
137 Node* target_node = event->target()->ToNode();
138 if (!(target_node && target_node->IsInV1ShadowTree()))
139 window->SetCurrentEvent(event);
140 }
141
142 {
143 // Catch exceptions thrown in the event listener if any and report them to
144 // DevTools console.
145 v8::TryCatch try_catch(isolate);
146 try_catch.SetVerbose(true);
147
148 // Step 10: Call a listener with event's currentTarget as receiver and event
149 // and handle errors if thrown.
150 InvokeInternal(*event->currentTarget(), *event, js_event);
151
152 if (try_catch.HasCaught()) {
153 // Step 10-2: Set legacyOutputDidListenersThrowFlag if given.
154 event->LegacySetDidListenersThrowFlag();
155 }
156 }
157
158 // Step 12: If |global| is a Window object, then set |global|’s current event
159 // to |current_event|.
160 if (window)
161 window->SetCurrentEvent(current_event);
162 }
163
GetSourceLocation(EventTarget & target)164 std::unique_ptr<SourceLocation> JSBasedEventListener::GetSourceLocation(
165 EventTarget& target) {
166 v8::HandleScope handle_scope(GetIsolate());
167 v8::Local<v8::Value> effective_function = GetEffectiveFunction(target);
168 if (effective_function->IsFunction())
169 return SourceLocation::FromFunction(effective_function.As<v8::Function>());
170 return nullptr;
171 }
172
173 } // namespace blink
174