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