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