Shaka Player Embedded
event_target.cc
Go to the documentation of this file.
1 // Copyright 2016 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
16 
17 #include <vector>
18 
19 #include "src/js/js_error.h"
20 #include "src/memory/heap_tracer.h"
21 
22 namespace shaka {
23 namespace js {
24 namespace events {
25 
26 EventTarget::EventTarget() : is_dispatching_(false) {}
27 // \cond Doxygen_Skip
28 EventTarget::~EventTarget() {}
29 // \endcond Doxygen_Skip
30 
32  BackingObject::Trace(tracer);
33  for (auto& listener : listeners_) {
34  tracer->Trace(&listener.callback_);
35  }
36  for (auto& pair : on_listeners_) {
37  tracer->Trace(pair.second);
38  }
39 }
40 
41 void EventTarget::AddEventListener(const std::string& type, Listener callback) {
42  if (FindListener(callback, type) != listeners_.end())
43  return;
44  listeners_.emplace_back(callback, type);
45 }
46 
48  std::function<void()> callback) {
49  cpp_listeners_.emplace(to_string(type), callback);
50 }
51 
52 void EventTarget::RemoveEventListener(const std::string& type,
53  Listener callback) {
54  auto it = FindListener(callback, type);
55  if (it != listeners_.end()) {
56  if (is_dispatching_) {
57  it->should_remove_ = true;
58  } else {
59  listeners_.erase(it);
60  }
61  }
62 }
63 
64 void EventTarget::UnsetCppEventListener(EventType type) {
65  cpp_listeners_.erase(to_string(type));
66 }
67 
69  RefPtr<Event> event, bool* did_listeners_throw) {
70  if (is_dispatching_) {
72  "Already dispatching events.");
73  }
74 
75  if (did_listeners_throw)
76  *did_listeners_throw = false;
77  is_dispatching_ = true;
78 
79  event->target = this;
80 
81  // Shaka Player does not use capturing or bubbling events, so we only care
82  // about the initial target. Normally we would need to construct a path
83  // going up the DOM.
84  event->event_phase = Event::AT_TARGET;
85  InvokeListeners(event, did_listeners_throw);
86 
87  // Now that we are done firing events, remove the event listeners that have
88  // been marked for removal.
89  for (auto it = listeners_.begin(); it != listeners_.end();) {
90  if (it->should_remove_)
91  it = listeners_.erase(it);
92  else
93  ++it;
94  }
95 
96  is_dispatching_ = false;
97  event->event_phase = Event::NONE;
98  event->current_target = nullptr;
99  return !event->default_prevented;
100 }
101 
102 EventTarget::ListenerInfo::ListenerInfo(Listener listener,
103  const std::string& type)
104  : callback_(listener), type_(type), should_remove_(false) {}
105 
106 EventTarget::ListenerInfo::~ListenerInfo() {}
107 
108 void EventTarget::InvokeListeners(RefPtr<Event> event,
109  bool* did_listeners_throw) {
110  if (event->is_stopped())
111  return;
112 
113  event->current_target = this;
114 
115  // First, evoke the cpp callbacks. They have priority, due to being internal.
116  // It is assumed that they will not change during this process.
117  if (cpp_listeners_.count(event->type) > 0)
118  cpp_listeners_.at(event->type)();
119 
120  // Invoke the on-event listeners second. This is slightly different from
121  // Chrome which will invoke it in the order it was set (i.e. calling
122  // addEventListener then setting onerror will call callbacks in that order).
123  auto on_iter = on_listeners_.find(event->type);
124  if (on_iter != on_listeners_.end()) {
125  // Note that even though it exists in the map does not mean the field is
126  // set.
127  if (on_iter->second->has_value()) {
128  ExceptionOr<void> except =
129  on_iter->second->value().CallWithThis(this, event);
130  if (holds_alternative<JsError>(except)) {
131  OnUncaughtException(get<JsError>(except).error(),
132  /* in_promise */ false);
133  if (did_listeners_throw)
134  *did_listeners_throw = true;
135  }
136  if (event->is_immediate_stopped()) {
137  return;
138  }
139  }
140  }
141 
142  if (listeners_.empty())
143  return;
144 
145  // Store the end of the list. Elements are added to the end of the list so
146  // we will not fire events added after this one. This needs to be inclusive
147  // since the pseudo-value for end() may not remain valid after insertions.
148  auto end = --listeners_.end();
149 
150  // We need to process all items in the range [begin, end]. We cannot use a
151  // for loop because we still need to process the |end| element. So we process
152  // |it|, then increment, then compare the old value to |end|. Note that
153  // since |end| is valid, the last increment is still valid.
154  auto it = listeners_.begin();
155  do {
156  if (it->should_remove_)
157  continue;
158 
159  if (it->type_ == event->type && it->callback_.has_value()) {
160  ExceptionOr<void> except = it->callback_->CallWithThis(this, event);
161  if (holds_alternative<JsError>(except)) {
162  OnUncaughtException(get<JsError>(except).error(),
163  /* in_promise */ false);
164  if (did_listeners_throw)
165  *did_listeners_throw = true;
166  }
167  }
168 
169  if (event->is_immediate_stopped())
170  break;
171  } while ((it++) != end);
172 }
173 
174 std::list<EventTarget::ListenerInfo>::iterator EventTarget::FindListener(
175  const Listener& callback, const std::string& type) {
176  auto it = listeners_.begin();
177  for (; it != listeners_.end(); it++) {
178  if (it->type_ == type && callback == it->callback_) {
179  break;
180  }
181  }
182  return it;
183 }
184 
186  AddMemberFunction("addEventListener", &EventTarget::AddEventListener);
187  AddMemberFunction("removeEventListener", &EventTarget::RemoveEventListener);
188  AddMemberFunction("dispatchEvent", &EventTarget::DispatchEvent);
189 }
190 
191 } // namespace events
192 } // namespace js
193 } // namespace shaka
ExceptionOr< bool > DispatchEventInternal(RefPtr< Event > event, bool *did_listeners_throw)
Definition: event_target.cc:68
void Trace(memory::HeapTracer *tracer) const override
std::string to_string(VideoReadyState state)
Definition: media_player.cc:32
ExceptionCode type
void Trace(const Traceable *ptr)
Definition: heap_tracer.cc:43
void RemoveEventListener(const std::string &type, Listener callback)
Definition: event_target.cc:52
void UnsetCppEventListener(EventType type)
Definition: event_target.cc:64
void Trace(memory::HeapTracer *tracer) const override
Definition: event_target.cc:31
ExceptionOr< bool > DispatchEvent(RefPtr< Event > event)
Definition: event_target.h:86
void AddEventListener(const std::string &type, Listener callback)
Definition: event_target.cc:41
static JsError DOMException(ExceptionCode code)
Definition: js_error.cc:115
void OnUncaughtException(JSValueRef exception, bool in_promise)
Definition: jsc_utils.cc:28
void SetCppEventListener(EventType type, std::function< void()> callback)
Definition: event_target.cc:47