1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "DebuggerNotificationObserver.h"
8
9 #include "DebuggerNotification.h"
10 #include "nsIGlobalObject.h"
11 #include "WrapperFactory.h"
12
13 namespace mozilla::dom {
14
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DebuggerNotificationObserver,mOwnerGlobal,mEventListenerCallbacks)15 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DebuggerNotificationObserver,
16 mOwnerGlobal, mEventListenerCallbacks)
17
18 NS_IMPL_CYCLE_COLLECTING_ADDREF(DebuggerNotificationObserver)
19 NS_IMPL_CYCLE_COLLECTING_RELEASE(DebuggerNotificationObserver)
20
21 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DebuggerNotificationObserver)
22 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
23 NS_INTERFACE_MAP_END
24
25 /* static */ already_AddRefed<DebuggerNotificationObserver>
26 DebuggerNotificationObserver::Constructor(GlobalObject& aGlobal,
27 ErrorResult& aRv) {
28 nsCOMPtr<nsIGlobalObject> globalInterface(
29 do_QueryInterface(aGlobal.GetAsSupports()));
30 if (NS_WARN_IF(!globalInterface)) {
31 aRv.Throw(NS_ERROR_FAILURE);
32 return nullptr;
33 }
34
35 RefPtr<DebuggerNotificationObserver> observer(
36 new DebuggerNotificationObserver(globalInterface));
37 return observer.forget();
38 }
39
DebuggerNotificationObserver(nsIGlobalObject * aOwnerGlobal)40 DebuggerNotificationObserver::DebuggerNotificationObserver(
41 nsIGlobalObject* aOwnerGlobal)
42 : mEventListenerCallbacks(), mOwnerGlobal(aOwnerGlobal) {}
43
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)44 JSObject* DebuggerNotificationObserver::WrapObject(
45 JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
46 return DebuggerNotificationObserver_Binding::Wrap(aCx, this, aGivenProto);
47 }
48
GetManager(JSContext * aCx,JS::Handle<JSObject * > aDebuggeeGlobal)49 static already_AddRefed<DebuggerNotificationManager> GetManager(
50 JSContext* aCx, JS::Handle<JSObject*> aDebuggeeGlobal) {
51 // The debuggee global here is likely a debugger-compartment cross-compartment
52 // wrapper for the debuggee global object, so we need to unwrap it to get
53 // the real debuggee-compartment global object.
54 JS::Rooted<JSObject*> debuggeeGlobalRooted(
55 aCx, js::UncheckedUnwrap(aDebuggeeGlobal, false));
56
57 if (!debuggeeGlobalRooted) {
58 return nullptr;
59 }
60
61 nsCOMPtr<nsIGlobalObject> debuggeeGlobalObject(
62 xpc::NativeGlobal(debuggeeGlobalRooted));
63 if (!debuggeeGlobalObject) {
64 return nullptr;
65 }
66
67 RefPtr<DebuggerNotificationManager> manager(
68 debuggeeGlobalObject->GetOrCreateDebuggerNotificationManager());
69 return manager.forget();
70 }
71
Connect(JSContext * aCx,JS::Handle<JSObject * > aDebuggeeGlobal,ErrorResult & aRv)72 bool DebuggerNotificationObserver::Connect(
73 JSContext* aCx, JS::Handle<JSObject*> aDebuggeeGlobal, ErrorResult& aRv) {
74 RefPtr<DebuggerNotificationManager> manager(GetManager(aCx, aDebuggeeGlobal));
75
76 if (!manager) {
77 aRv.Throw(NS_ERROR_FAILURE);
78 return false;
79 }
80
81 return manager->Attach(this);
82 }
83
Disconnect(JSContext * aCx,JS::Handle<JSObject * > aDebuggeeGlobal,ErrorResult & aRv)84 bool DebuggerNotificationObserver::Disconnect(
85 JSContext* aCx, JS::Handle<JSObject*> aDebuggeeGlobal, ErrorResult& aRv) {
86 RefPtr<DebuggerNotificationManager> manager(GetManager(aCx, aDebuggeeGlobal));
87
88 if (!manager) {
89 aRv.Throw(NS_ERROR_FAILURE);
90 return false;
91 }
92
93 return manager->Detach(this);
94 }
95
AddListener(DebuggerNotificationCallback & aHandlerFn)96 bool DebuggerNotificationObserver::AddListener(
97 DebuggerNotificationCallback& aHandlerFn) {
98 const auto [begin, end] = mEventListenerCallbacks.NonObservingRange();
99 if (std::any_of(begin, end,
100 [&](const RefPtr<DebuggerNotificationCallback>& callback) {
101 return *callback == aHandlerFn;
102 })) {
103 return false;
104 }
105
106 RefPtr<DebuggerNotificationCallback> handlerFn(&aHandlerFn);
107 mEventListenerCallbacks.AppendElement(handlerFn);
108 return true;
109 }
110
RemoveListener(DebuggerNotificationCallback & aHandlerFn)111 bool DebuggerNotificationObserver::RemoveListener(
112 DebuggerNotificationCallback& aHandlerFn) {
113 for (nsTObserverArray<RefPtr<DebuggerNotificationCallback>>::ForwardIterator
114 iter(mEventListenerCallbacks);
115 iter.HasMore();) {
116 if (*iter.GetNext().get() == aHandlerFn) {
117 iter.Remove();
118 return true;
119 }
120 }
121
122 return false;
123 }
124
HasListeners()125 bool DebuggerNotificationObserver::HasListeners() {
126 return !mEventListenerCallbacks.IsEmpty();
127 }
128
NotifyListeners(DebuggerNotification * aNotification)129 void DebuggerNotificationObserver::NotifyListeners(
130 DebuggerNotification* aNotification) {
131 if (!HasListeners()) {
132 return;
133 }
134
135 // Since we want the notification objects to live in the same compartment
136 // as the observer, we create a new instance of the notification before
137 // an observer dispatches the event listeners.
138 RefPtr<DebuggerNotification> debuggerNotification(
139 aNotification->CloneInto(mOwnerGlobal));
140
141 for (RefPtr<DebuggerNotificationCallback> callback :
142 mEventListenerCallbacks.ForwardRange()) {
143 callback->Call(*debuggerNotification);
144 }
145 }
146
147 } // namespace mozilla::dom
148