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