1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 #include "FinalizationWitnessService.h"
6
7 #include "nsString.h"
8 #include "jsapi.h"
9 #include "js/CallNonGenericMethod.h"
10 #include "mozJSComponentLoader.h"
11 #include "nsZipArchive.h"
12
13 #include "mozilla/Scoped.h"
14 #include "mozilla/Services.h"
15 #include "nsIObserverService.h"
16 #include "nsThreadUtils.h"
17
18
19 // Implementation of nsIFinalizationWitnessService
20
21 static bool gShuttingDown = false;
22
23 namespace mozilla {
24
25 namespace {
26
27 /**
28 * An event meant to be dispatched to the main thread upon finalization
29 * of a FinalizationWitness, unless method |forget()| has been called.
30 *
31 * Held as private data by each instance of FinalizationWitness.
32 * Important note: we maintain the invariant that these private data
33 * slots are already addrefed.
34 */
35 class FinalizationEvent final: public Runnable
36 {
37 public:
FinalizationEvent(const char * aTopic,const char16_t * aValue)38 FinalizationEvent(const char* aTopic,
39 const char16_t* aValue)
40 : mTopic(aTopic)
41 , mValue(aValue)
42 { }
43
Run()44 NS_IMETHOD Run() override {
45 nsCOMPtr<nsIObserverService> observerService =
46 mozilla::services::GetObserverService();
47 if (!observerService) {
48 // This is either too early or, more likely, too late for notifications.
49 // Bail out.
50 return NS_ERROR_NOT_AVAILABLE;
51 }
52 (void)observerService->
53 NotifyObservers(nullptr, mTopic.get(), mValue.get());
54 return NS_OK;
55 }
56 private:
57 /**
58 * The topic on which to broadcast the notification of finalization.
59 *
60 * Deallocated on the main thread.
61 */
62 const nsCString mTopic;
63
64 /**
65 * The result of converting the exception to a string.
66 *
67 * Deallocated on the main thread.
68 */
69 const nsString mValue;
70 };
71
72 enum {
73 WITNESS_SLOT_EVENT,
74 WITNESS_INSTANCES_SLOTS
75 };
76
77 /**
78 * Extract the FinalizationEvent from an instance of FinalizationWitness
79 * and clear the slot containing the FinalizationEvent.
80 */
81 already_AddRefed<FinalizationEvent>
ExtractFinalizationEvent(JSObject * objSelf)82 ExtractFinalizationEvent(JSObject *objSelf)
83 {
84 JS::Value slotEvent = JS_GetReservedSlot(objSelf, WITNESS_SLOT_EVENT);
85 if (slotEvent.isUndefined()) {
86 // Forget() has been called
87 return nullptr;
88 }
89
90 JS_SetReservedSlot(objSelf, WITNESS_SLOT_EVENT, JS::UndefinedValue());
91
92 return dont_AddRef(static_cast<FinalizationEvent*>(slotEvent.toPrivate()));
93 }
94
95 /**
96 * Finalizer for instances of FinalizationWitness.
97 *
98 * Unless method Forget() has been called, the finalizer displays an error
99 * message.
100 */
Finalize(JSFreeOp * fop,JSObject * objSelf)101 void Finalize(JSFreeOp *fop, JSObject *objSelf)
102 {
103 RefPtr<FinalizationEvent> event = ExtractFinalizationEvent(objSelf);
104 if (event == nullptr || gShuttingDown) {
105 // NB: event will be null if Forget() has been called
106 return;
107 }
108
109 // Notify observers. Since we are executed during garbage-collection,
110 // we need to dispatch the notification to the main thread.
111 nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
112 if (mainThread) {
113 mainThread->Dispatch(event.forget(), NS_DISPATCH_NORMAL);
114 }
115 // We may fail at dispatching to the main thread if we arrive too late
116 // during shutdown. In that case, there is not much we can do.
117 }
118
119 static const JSClassOps sWitnessClassOps = {
120 nullptr /* addProperty */,
121 nullptr /* delProperty */,
122 nullptr /* getProperty */,
123 nullptr /* setProperty */,
124 nullptr /* enumerate */,
125 nullptr /* resolve */,
126 nullptr /* mayResolve */,
127 Finalize /* finalize */
128 };
129
130 static const JSClass sWitnessClass = {
131 "FinalizationWitness",
132 JSCLASS_HAS_RESERVED_SLOTS(WITNESS_INSTANCES_SLOTS) |
133 JSCLASS_FOREGROUND_FINALIZE,
134 &sWitnessClassOps
135 };
136
IsWitness(JS::Handle<JS::Value> v)137 bool IsWitness(JS::Handle<JS::Value> v)
138 {
139 return v.isObject() && JS_GetClass(&v.toObject()) == &sWitnessClass;
140 }
141
142
143 /**
144 * JS method |forget()|
145 *
146 * === JS documentation
147 *
148 * Neutralize the witness. Once this method is called, the witness will
149 * never report any error.
150 */
ForgetImpl(JSContext * cx,const JS::CallArgs & args)151 bool ForgetImpl(JSContext* cx, const JS::CallArgs& args)
152 {
153 if (args.length() != 0) {
154 JS_ReportErrorASCII(cx, "forget() takes no arguments");
155 return false;
156 }
157 JS::Rooted<JS::Value> valSelf(cx, args.thisv());
158 JS::Rooted<JSObject*> objSelf(cx, &valSelf.toObject());
159
160 RefPtr<FinalizationEvent> event = ExtractFinalizationEvent(objSelf);
161 if (event == nullptr) {
162 JS_ReportErrorASCII(cx, "forget() called twice");
163 return false;
164 }
165
166 args.rval().setUndefined();
167 return true;
168 }
169
Forget(JSContext * cx,unsigned argc,JS::Value * vp)170 bool Forget(JSContext *cx, unsigned argc, JS::Value *vp)
171 {
172 JS::CallArgs args = CallArgsFromVp(argc, vp);
173 return JS::CallNonGenericMethod<IsWitness, ForgetImpl>(cx, args);
174 }
175
176 static const JSFunctionSpec sWitnessClassFunctions[] = {
177 JS_FN("forget", Forget, 0, JSPROP_READONLY | JSPROP_PERMANENT),
178 JS_FS_END
179 };
180
181 } // namespace
182
NS_IMPL_ISUPPORTS(FinalizationWitnessService,nsIFinalizationWitnessService,nsIObserver)183 NS_IMPL_ISUPPORTS(FinalizationWitnessService, nsIFinalizationWitnessService, nsIObserver)
184
185 /**
186 * Create a new Finalization Witness.
187 *
188 * A finalization witness is an object whose sole role is to notify
189 * observers when it is gc-ed. Once the witness is created, call its
190 * method |forget()| to prevent the observers from being notified.
191 *
192 * @param aTopic The notification topic.
193 * @param aValue The notification value. Converted to a string.
194 *
195 * @constructor
196 */
197 NS_IMETHODIMP
198 FinalizationWitnessService::Make(const char* aTopic,
199 const char16_t* aValue,
200 JSContext* aCx,
201 JS::MutableHandle<JS::Value> aRetval)
202 {
203 JS::Rooted<JSObject*> objResult(aCx, JS_NewObject(aCx, &sWitnessClass));
204 if (!objResult) {
205 return NS_ERROR_OUT_OF_MEMORY;
206 }
207 if (!JS_DefineFunctions(aCx, objResult, sWitnessClassFunctions)) {
208 return NS_ERROR_FAILURE;
209 }
210
211 RefPtr<FinalizationEvent> event = new FinalizationEvent(aTopic, aValue);
212
213 // Transfer ownership of the addrefed |event| to |objResult|.
214 JS_SetReservedSlot(objResult, WITNESS_SLOT_EVENT,
215 JS::PrivateValue(event.forget().take()));
216
217 aRetval.setObject(*objResult);
218 return NS_OK;
219 }
220
221 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aValue)222 FinalizationWitnessService::Observe(nsISupports* aSubject,
223 const char* aTopic,
224 const char16_t* aValue)
225 {
226 MOZ_ASSERT(strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0);
227 gShuttingDown = true;
228 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
229 if (obs) {
230 obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
231 }
232
233 return NS_OK;
234 }
235
236 nsresult
Init()237 FinalizationWitnessService::Init()
238 {
239 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
240 if (!obs) {
241 return NS_ERROR_FAILURE;
242 }
243
244 return obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
245 }
246
247 } // namespace mozilla
248