/src/mozilla-central/toolkit/components/finalizationwitness/FinalizationWitnessService.cpp
Line | Count | Source (jump to first uncovered line) |
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: |
38 | | FinalizationEvent(const char* aTopic, |
39 | | const char16_t* aValue) |
40 | | : Runnable("FinalizationEvent") |
41 | | , mTopic(aTopic) |
42 | | , mValue(aValue) |
43 | 0 | { } |
44 | | |
45 | 0 | NS_IMETHOD Run() override { |
46 | 0 | nsCOMPtr<nsIObserverService> observerService = |
47 | 0 | mozilla::services::GetObserverService(); |
48 | 0 | if (!observerService) { |
49 | 0 | // This is either too early or, more likely, too late for notifications. |
50 | 0 | // Bail out. |
51 | 0 | return NS_ERROR_NOT_AVAILABLE; |
52 | 0 | } |
53 | 0 | (void)observerService-> |
54 | 0 | NotifyObservers(nullptr, mTopic.get(), mValue.get()); |
55 | 0 | return NS_OK; |
56 | 0 | } |
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 | | ExtractFinalizationEvent(JSObject *objSelf) |
84 | 0 | { |
85 | 0 | JS::Value slotEvent = JS_GetReservedSlot(objSelf, WITNESS_SLOT_EVENT); |
86 | 0 | if (slotEvent.isUndefined()) { |
87 | 0 | // Forget() has been called |
88 | 0 | return nullptr; |
89 | 0 | } |
90 | 0 | |
91 | 0 | JS_SetReservedSlot(objSelf, WITNESS_SLOT_EVENT, JS::UndefinedValue()); |
92 | 0 |
|
93 | 0 | return dont_AddRef(static_cast<FinalizationEvent*>(slotEvent.toPrivate())); |
94 | 0 | } |
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 | | void Finalize(JSFreeOp *fop, JSObject *objSelf) |
103 | 0 | { |
104 | 0 | RefPtr<FinalizationEvent> event = ExtractFinalizationEvent(objSelf); |
105 | 0 | if (event == nullptr || gShuttingDown) { |
106 | 0 | // NB: event will be null if Forget() has been called |
107 | 0 | return; |
108 | 0 | } |
109 | 0 | |
110 | 0 | // Notify observers. Since we are executed during garbage-collection, |
111 | 0 | // 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 | 0 | } |
116 | 0 | // We may fail at dispatching to the main thread if we arrive too late |
117 | 0 | // during shutdown. In that case, there is not much we can do. |
118 | 0 | } |
119 | | |
120 | | static const JSClassOps sWitnessClassOps = { |
121 | | nullptr /* addProperty */, |
122 | | nullptr /* delProperty */, |
123 | | nullptr /* enumerate */, |
124 | | nullptr /* newEnumerate */, |
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 | | |
137 | | bool IsWitness(JS::Handle<JS::Value> v) |
138 | 0 | { |
139 | 0 | return v.isObject() && JS_GetClass(&v.toObject()) == &sWitnessClass; |
140 | 0 | } |
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 | | */ |
151 | | bool ForgetImpl(JSContext* cx, const JS::CallArgs& args) |
152 | 0 | { |
153 | 0 | if (args.length() != 0) { |
154 | 0 | JS_ReportErrorASCII(cx, "forget() takes no arguments"); |
155 | 0 | return false; |
156 | 0 | } |
157 | 0 | JS::Rooted<JS::Value> valSelf(cx, args.thisv()); |
158 | 0 | JS::Rooted<JSObject*> objSelf(cx, &valSelf.toObject()); |
159 | 0 |
|
160 | 0 | RefPtr<FinalizationEvent> event = ExtractFinalizationEvent(objSelf); |
161 | 0 | if (event == nullptr) { |
162 | 0 | JS_ReportErrorASCII(cx, "forget() called twice"); |
163 | 0 | return false; |
164 | 0 | } |
165 | 0 | |
166 | 0 | args.rval().setUndefined(); |
167 | 0 | return true; |
168 | 0 | } |
169 | | |
170 | | bool Forget(JSContext *cx, unsigned argc, JS::Value *vp) |
171 | 0 | { |
172 | 0 | JS::CallArgs args = CallArgsFromVp(argc, vp); |
173 | 0 | return JS::CallNonGenericMethod<IsWitness, ForgetImpl>(cx, args); |
174 | 0 | } |
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 | | |
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 | 0 | { |
203 | 0 | // Finalize witnesses are not created when recording or replaying, as |
204 | 0 | // finalizations occur non-deterministically in the recording. |
205 | 0 | if (recordreplay::IsRecordingOrReplaying()) { |
206 | 0 | return NS_ERROR_NOT_AVAILABLE; |
207 | 0 | } |
208 | 0 | |
209 | 0 | JS::Rooted<JSObject*> objResult(aCx, JS_NewObject(aCx, &sWitnessClass)); |
210 | 0 | if (!objResult) { |
211 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
212 | 0 | } |
213 | 0 | if (!JS_DefineFunctions(aCx, objResult, sWitnessClassFunctions)) { |
214 | 0 | return NS_ERROR_FAILURE; |
215 | 0 | } |
216 | 0 | |
217 | 0 | RefPtr<FinalizationEvent> event = new FinalizationEvent(aTopic, aValue); |
218 | 0 |
|
219 | 0 | // Transfer ownership of the addrefed |event| to |objResult|. |
220 | 0 | JS_SetReservedSlot(objResult, WITNESS_SLOT_EVENT, |
221 | 0 | JS::PrivateValue(event.forget().take())); |
222 | 0 |
|
223 | 0 | aRetval.setObject(*objResult); |
224 | 0 | return NS_OK; |
225 | 0 | } |
226 | | |
227 | | NS_IMETHODIMP |
228 | | FinalizationWitnessService::Observe(nsISupports* aSubject, |
229 | | const char* aTopic, |
230 | | const char16_t* aValue) |
231 | 0 | { |
232 | 0 | MOZ_ASSERT(strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0); |
233 | 0 | gShuttingDown = true; |
234 | 0 | nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); |
235 | 0 | if (obs) { |
236 | 0 | obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); |
237 | 0 | } |
238 | 0 |
|
239 | 0 | return NS_OK; |
240 | 0 | } |
241 | | |
242 | | nsresult |
243 | | FinalizationWitnessService::Init() |
244 | 0 | { |
245 | 0 | nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); |
246 | 0 | if (!obs) { |
247 | 0 | return NS_ERROR_FAILURE; |
248 | 0 | } |
249 | 0 | |
250 | 0 | return obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); |
251 | 0 | } |
252 | | |
253 | | } // namespace mozilla |