summaryrefslogtreecommitdiffstats
path: root/chromium/third_party/blink/renderer/bindings/core/v8/js_event_handler.cc
blob: 022c4e3cbe83e3637f17bf03d11cac25a4f65aee (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
// 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_event_handler.h"

#include "third_party/blink/renderer/bindings/core/v8/script_value.h"
#include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_string_resource.h"
#include "third_party/blink/renderer/core/dom/events/event_target.h"
#include "third_party/blink/renderer/core/events/before_unload_event.h"
#include "third_party/blink/renderer/core/events/error_event.h"

namespace blink {

v8::Local<v8::Value> JSEventHandler::GetEffectiveFunction(EventTarget& target) {
  v8::Local<v8::Value> v8_listener = GetListenerObject(target);
  if (!v8_listener.IsEmpty() && v8_listener->IsFunction())
    return GetBoundFunction(v8_listener.As<v8::Function>());
  return v8::Undefined(GetIsolate());
}

void JSEventHandler::SetCompiledHandler(
    ScriptState* script_state,
    v8::Local<v8::Function> listener,
    const V8PrivateProperty::Symbol& property) {
  DCHECK(!HasCompiledHandler());
  v8::Context::BackupIncumbentScope backup_incumbent_scope(
      script_state->GetContext());
  event_handler_ = V8EventHandlerNonNull::Create(listener);
  Attach(script_state, listener, property, this);
}

// https://html.spec.whatwg.org/C/webappapis.html#the-event-handler-processing-algorithm
void JSEventHandler::CallListenerFunction(EventTarget& event_target,
                                          Event& event,
                                          v8::Local<v8::Value> js_event) {
  DCHECK(!js_event.IsEmpty());

  // Step 1. Let callback be the result of getting the current value of the
  //         event handler given eventTarget and name.
  // Step 2. If callback is null, then return.
  v8::Local<v8::Value> listener_value =
      GetListenerObject(*event.currentTarget());
  if (listener_value.IsEmpty() || listener_value->IsNull())
    return;
  DCHECK(HasCompiledHandler());

  // Step 3. Let special error event handling be true if event is an ErrorEvent
  // object, event's type is error, and event's currentTarget implements the
  // WindowOrWorkerGlobalScope mixin. Otherwise, let special error event
  // handling be false.
  const bool special_error_event_handling =
      event.IsErrorEvent() && event.type() == EventTypeNames::error &&
      event.currentTarget()->IsWindowOrWorkerGlobalScope();

  // Step 4. Process the Event object event as follows:
  //   If special error event handling is true
  //     Invoke callback with five arguments, the first one having the value of
  //     event's message attribute, the second having the value of event's
  //     filename attribute, the third having the value of event's lineno
  //     attribute, the fourth having the value of event's colno attribute, the
  //     fifth having the value of event's error attribute, and with the
  //     callback this value set to event's currentTarget. Let return value be
  //     the callback's return value.
  //   Otherwise
  //     Invoke callback with one argument, the value of which is the Event
  //     object event, with the callback this value set to event's
  //     currentTarget. Let return value be the callback's return value.
  //   If an exception gets thrown by the callback, end these steps and allow
  //   the exception to propagate. (It will propagate to the DOM event dispatch
  //   logic, which will then report the exception.)
  Vector<ScriptValue> arguments;
  ScriptState* script_state_of_listener =
      event_handler_->CallbackRelevantScriptState();

  if (special_error_event_handling) {
    ErrorEvent* error_event = ToErrorEvent(&event);

    // The error argument should be initialized to null for dedicated workers.
    // https://html.spec.whatwg.org/C/workers.html#runtime-script-errors-2
    ScriptValue error_attribute = error_event->error(script_state_of_listener);
    if (error_attribute.IsEmpty() ||
        error_event->target()->InterfaceName() == EventTargetNames::Worker)
      error_attribute = ScriptValue::CreateNull(script_state_of_listener);

    arguments = {
        ScriptValue::From(script_state_of_listener, error_event->message()),
        ScriptValue::From(script_state_of_listener, error_event->filename()),
        ScriptValue::From(script_state_of_listener, error_event->lineno()),
        ScriptValue::From(script_state_of_listener, error_event->colno()),
        error_attribute};
  } else {
    arguments = {ScriptValue::From(script_state_of_listener, js_event)};
  }

  const bool is_beforeunload_event =
      event.IsBeforeUnloadEvent() &&
      event.type() == EventTypeNames::beforeunload;
  const bool is_print_event =
      // TODO(yukishiino): Should check event.Is{Before,After}PrintEvent.
      event.type() == EventTypeNames::beforeprint ||
      event.type() == EventTypeNames::afterprint;
  if (!event_handler_->IsRunnableOrThrowException(
          (is_beforeunload_event || is_print_event)
              ? V8EventHandlerNonNull::IgnorePause::kIgnore
              : V8EventHandlerNonNull::IgnorePause::kDontIgnore)) {
    return;
  }
  ScriptValue result;
  if (!event_handler_
           ->InvokeWithoutRunnabilityCheck(event.currentTarget(), arguments)
           .To(&result) ||
      GetIsolate()->IsExecutionTerminating())
    return;
  v8::Local<v8::Value> v8_return_value = result.V8Value();

  // There is nothing to do if |v8_return_value| is null or undefined.
  // See Step 5. for more information.
  if (v8_return_value->IsNullOrUndefined())
    return;

  // https://heycam.github.io/webidl/#invoke-a-callback-function
  // step 13: Set completion to the result of converting callResult.[[Value]] to
  //          an IDL value of the same type as the operation's return type.
  //
  // OnBeforeUnloadEventHandler returns DOMString? while OnErrorEventHandler and
  // EventHandler return any, so converting |v8_return_value| to return type is
  // necessary only for OnBeforeUnloadEventHandler.
  String result_for_beforeunload;
  if (IsOnBeforeUnloadEventHandler()) {
    // TODO(yukiy): use |NativeValueTraits|.
    V8StringResource<> native_result(v8_return_value);

    // |native_result.Prepare()| throws exception if it fails to convert
    // |native_result| to String.
    if (!native_result.Prepare())
      return;
    result_for_beforeunload = native_result;
  }

  // Step 5. Process return value as follows:
  //   If event is a BeforeUnloadEvent object and event's type is beforeunload
  //     If return value is not null, then:
  //       1. Set event's canceled flag.
  //       2. If event's returnValue attribute's value is the empty string, then
  //          set event's returnValue attribute's value to return value.
  //   If special error event handling is true
  //     If return value is true, then set event's canceled flag.
  //   Otherwise
  //     If return value is false, then set event's canceled flag.
  //       Note: If we've gotten to this "Otherwise" clause because event's type
  //             is beforeunload but event is not a BeforeUnloadEvent object,
  //             then return value will never be false, since in such cases
  //             return value will have been coerced into either null or a
  //             DOMString.
  if (is_beforeunload_event) {
    if (result_for_beforeunload) {
      event.preventDefault();
      BeforeUnloadEvent* before_unload_event = ToBeforeUnloadEvent(&event);
      if (before_unload_event->returnValue().IsEmpty())
        before_unload_event->setReturnValue(result_for_beforeunload);
    }
  } else if (!IsOnBeforeUnloadEventHandler()) {
    if (special_error_event_handling && v8_return_value->IsBoolean() &&
        v8_return_value.As<v8::Boolean>()->Value())
      event.preventDefault();
    else if (!special_error_event_handling && v8_return_value->IsBoolean() &&
             !v8_return_value.As<v8::Boolean>()->Value())
      event.preventDefault();
  }
}

void JSEventHandler::Trace(blink::Visitor* visitor) {
  visitor->Trace(event_handler_);
  JSBasedEventListener::Trace(visitor);
}

}  // namespace blink