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