|
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 "mozilla/NullPtr.h" |
|
16 #include "nsIObserverService.h" |
|
17 #include "nsThreadUtils.h" |
|
18 |
|
19 |
|
20 // Implementation of nsIFinalizationWitnessService |
|
21 |
|
22 namespace mozilla { |
|
23 |
|
24 namespace { |
|
25 |
|
26 /** |
|
27 * An event meant to be dispatched to the main thread upon finalization |
|
28 * of a FinalizationWitness, unless method |forget()| has been called. |
|
29 * |
|
30 * Held as private data by each instance of FinalizationWitness. |
|
31 * Important note: we maintain the invariant that these private data |
|
32 * slots are already addrefed. |
|
33 */ |
|
34 class FinalizationEvent MOZ_FINAL: public nsRunnable |
|
35 { |
|
36 public: |
|
37 FinalizationEvent(const char* aTopic, |
|
38 const jschar* aValue) |
|
39 : mTopic(aTopic) |
|
40 , mValue(aValue) |
|
41 { } |
|
42 |
|
43 NS_METHOD Run() { |
|
44 nsCOMPtr<nsIObserverService> observerService = |
|
45 mozilla::services::GetObserverService(); |
|
46 if (!observerService) { |
|
47 // This is either too early or, more likely, too late for notifications. |
|
48 // Bail out. |
|
49 return NS_ERROR_NOT_AVAILABLE; |
|
50 } |
|
51 (void)observerService-> |
|
52 NotifyObservers(nullptr, mTopic.get(), mValue.get()); |
|
53 return NS_OK; |
|
54 } |
|
55 private: |
|
56 /** |
|
57 * The topic on which to broadcast the notification of finalization. |
|
58 * |
|
59 * Deallocated on the main thread. |
|
60 */ |
|
61 const nsCString mTopic; |
|
62 |
|
63 /** |
|
64 * The result of converting the exception to a string. |
|
65 * |
|
66 * Deallocated on the main thread. |
|
67 */ |
|
68 const nsString mValue; |
|
69 }; |
|
70 |
|
71 enum { |
|
72 WITNESS_SLOT_EVENT, |
|
73 WITNESS_INSTANCES_SLOTS |
|
74 }; |
|
75 |
|
76 /** |
|
77 * Extract the FinalizationEvent from an instance of FinalizationWitness |
|
78 * and clear the slot containing the FinalizationEvent. |
|
79 */ |
|
80 already_AddRefed<FinalizationEvent> |
|
81 ExtractFinalizationEvent(JSObject *objSelf) |
|
82 { |
|
83 JS::Value slotEvent = JS_GetReservedSlot(objSelf, WITNESS_SLOT_EVENT); |
|
84 if (slotEvent.isUndefined()) { |
|
85 // Forget() has been called |
|
86 return nullptr; |
|
87 } |
|
88 |
|
89 JS_SetReservedSlot(objSelf, WITNESS_SLOT_EVENT, JS::UndefinedValue()); |
|
90 |
|
91 return dont_AddRef(static_cast<FinalizationEvent*>(slotEvent.toPrivate())); |
|
92 } |
|
93 |
|
94 /** |
|
95 * Finalizer for instances of FinalizationWitness. |
|
96 * |
|
97 * Unless method Forget() has been called, the finalizer displays an error |
|
98 * message. |
|
99 */ |
|
100 void Finalize(JSFreeOp *fop, JSObject *objSelf) |
|
101 { |
|
102 nsRefPtr<FinalizationEvent> event = ExtractFinalizationEvent(objSelf); |
|
103 if (event == nullptr) { |
|
104 // Forget() has been called |
|
105 return; |
|
106 } |
|
107 |
|
108 // Notify observers. Since we are executed during garbage-collection, |
|
109 // we need to dispatch the notification to the main thread. |
|
110 (void)NS_DispatchToMainThread(event); |
|
111 // We may fail at dispatching to the main thread if we arrive too late |
|
112 // during shutdown. In that case, there is not much we can do. |
|
113 } |
|
114 |
|
115 static const JSClass sWitnessClass = { |
|
116 "FinalizationWitness", |
|
117 JSCLASS_HAS_RESERVED_SLOTS(WITNESS_INSTANCES_SLOTS), |
|
118 JS_PropertyStub /* addProperty */, |
|
119 JS_DeletePropertyStub /* delProperty */, |
|
120 JS_PropertyStub /* getProperty */, |
|
121 JS_StrictPropertyStub /* setProperty */, |
|
122 JS_EnumerateStub /* enumerate */, |
|
123 JS_ResolveStub /* resolve */, |
|
124 JS_ConvertStub /* convert */, |
|
125 Finalize /* finalize */ |
|
126 }; |
|
127 |
|
128 bool IsWitness(JS::Handle<JS::Value> v) |
|
129 { |
|
130 return v.isObject() && JS_GetClass(&v.toObject()) == &sWitnessClass; |
|
131 } |
|
132 |
|
133 |
|
134 /** |
|
135 * JS method |forget()| |
|
136 * |
|
137 * === JS documentation |
|
138 * |
|
139 * Neutralize the witness. Once this method is called, the witness will |
|
140 * never report any error. |
|
141 */ |
|
142 bool ForgetImpl(JSContext* cx, JS::CallArgs args) |
|
143 { |
|
144 if (args.length() != 0) { |
|
145 JS_ReportError(cx, "forget() takes no arguments"); |
|
146 return false; |
|
147 } |
|
148 JS::Rooted<JS::Value> valSelf(cx, args.thisv()); |
|
149 JS::Rooted<JSObject*> objSelf(cx, &valSelf.toObject()); |
|
150 |
|
151 nsRefPtr<FinalizationEvent> event = ExtractFinalizationEvent(objSelf); |
|
152 if (event == nullptr) { |
|
153 JS_ReportError(cx, "forget() called twice"); |
|
154 return false; |
|
155 } |
|
156 |
|
157 args.rval().setUndefined(); |
|
158 return true; |
|
159 } |
|
160 |
|
161 bool Forget(JSContext *cx, unsigned argc, JS::Value *vp) |
|
162 { |
|
163 JS::CallArgs args = CallArgsFromVp(argc, vp); |
|
164 return JS::CallNonGenericMethod<IsWitness, ForgetImpl>(cx, args); |
|
165 } |
|
166 |
|
167 static const JSFunctionSpec sWitnessClassFunctions[] = { |
|
168 JS_FN("forget", Forget, 0, JSPROP_READONLY | JSPROP_PERMANENT), |
|
169 JS_FS_END |
|
170 }; |
|
171 |
|
172 } |
|
173 |
|
174 NS_IMPL_ISUPPORTS(FinalizationWitnessService, nsIFinalizationWitnessService) |
|
175 |
|
176 /** |
|
177 * Create a new Finalization Witness. |
|
178 * |
|
179 * A finalization witness is an object whose sole role is to notify |
|
180 * observers when it is gc-ed. Once the witness is created, call its |
|
181 * method |forget()| to prevent the observers from being notified. |
|
182 * |
|
183 * @param aTopic The notification topic. |
|
184 * @param aValue The notification value. Converted to a string. |
|
185 * |
|
186 * @constructor |
|
187 */ |
|
188 NS_IMETHODIMP |
|
189 FinalizationWitnessService::Make(const char* aTopic, |
|
190 const char16_t* aValue, |
|
191 JSContext* aCx, |
|
192 JS::MutableHandle<JS::Value> aRetval) |
|
193 { |
|
194 JS::Rooted<JSObject*> objResult(aCx, JS_NewObject(aCx, &sWitnessClass, JS::NullPtr(), |
|
195 JS::NullPtr())); |
|
196 if (!objResult) { |
|
197 return NS_ERROR_OUT_OF_MEMORY; |
|
198 } |
|
199 if (!JS_DefineFunctions(aCx, objResult, sWitnessClassFunctions)) { |
|
200 return NS_ERROR_FAILURE; |
|
201 } |
|
202 |
|
203 nsRefPtr<FinalizationEvent> event = new FinalizationEvent(aTopic, aValue); |
|
204 |
|
205 // Transfer ownership of the addrefed |event| to |objResult|. |
|
206 JS_SetReservedSlot(objResult, WITNESS_SLOT_EVENT, |
|
207 JS::PrivateValue(event.forget().take())); |
|
208 |
|
209 aRetval.setObject(*objResult); |
|
210 return NS_OK; |
|
211 } |
|
212 |
|
213 } // namespace mozilla |