|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include "nsIAtom.h" |
|
7 #include "nsString.h" |
|
8 #include "jsapi.h" |
|
9 #include "nsIContent.h" |
|
10 #include "nsXBLProtoImplProperty.h" |
|
11 #include "nsUnicharUtils.h" |
|
12 #include "nsCxPusher.h" |
|
13 #include "nsReadableUtils.h" |
|
14 #include "nsJSUtils.h" |
|
15 #include "nsXBLPrototypeBinding.h" |
|
16 #include "nsXBLSerialize.h" |
|
17 #include "xpcpublic.h" |
|
18 |
|
19 using namespace mozilla; |
|
20 |
|
21 nsXBLProtoImplProperty::nsXBLProtoImplProperty(const char16_t* aName, |
|
22 const char16_t* aGetter, |
|
23 const char16_t* aSetter, |
|
24 const char16_t* aReadOnly, |
|
25 uint32_t aLineNumber) : |
|
26 nsXBLProtoImplMember(aName), |
|
27 mJSAttributes(JSPROP_ENUMERATE) |
|
28 #ifdef DEBUG |
|
29 , mIsCompiled(false) |
|
30 #endif |
|
31 { |
|
32 MOZ_COUNT_CTOR(nsXBLProtoImplProperty); |
|
33 |
|
34 if (aReadOnly) { |
|
35 nsAutoString readOnly; readOnly.Assign(*aReadOnly); |
|
36 if (readOnly.LowerCaseEqualsLiteral("true")) |
|
37 mJSAttributes |= JSPROP_READONLY; |
|
38 } |
|
39 |
|
40 if (aGetter) { |
|
41 AppendGetterText(nsDependentString(aGetter)); |
|
42 SetGetterLineNumber(aLineNumber); |
|
43 } |
|
44 if (aSetter) { |
|
45 AppendSetterText(nsDependentString(aSetter)); |
|
46 SetSetterLineNumber(aLineNumber); |
|
47 } |
|
48 } |
|
49 |
|
50 nsXBLProtoImplProperty::nsXBLProtoImplProperty(const char16_t* aName, |
|
51 const bool aIsReadOnly) |
|
52 : nsXBLProtoImplMember(aName), |
|
53 mJSAttributes(JSPROP_ENUMERATE) |
|
54 #ifdef DEBUG |
|
55 , mIsCompiled(false) |
|
56 #endif |
|
57 { |
|
58 MOZ_COUNT_CTOR(nsXBLProtoImplProperty); |
|
59 |
|
60 if (aIsReadOnly) |
|
61 mJSAttributes |= JSPROP_READONLY; |
|
62 } |
|
63 |
|
64 nsXBLProtoImplProperty::~nsXBLProtoImplProperty() |
|
65 { |
|
66 MOZ_COUNT_DTOR(nsXBLProtoImplProperty); |
|
67 |
|
68 if (!mGetter.IsCompiled()) { |
|
69 delete mGetter.GetUncompiled(); |
|
70 } |
|
71 |
|
72 if (!mSetter.IsCompiled()) { |
|
73 delete mSetter.GetUncompiled(); |
|
74 } |
|
75 } |
|
76 |
|
77 void nsXBLProtoImplProperty::EnsureUncompiledText(PropertyOp& aPropertyOp) |
|
78 { |
|
79 if (!aPropertyOp.GetUncompiled()) { |
|
80 nsXBLTextWithLineNumber* text = new nsXBLTextWithLineNumber(); |
|
81 aPropertyOp.SetUncompiled(text); |
|
82 } |
|
83 } |
|
84 |
|
85 void |
|
86 nsXBLProtoImplProperty::AppendGetterText(const nsAString& aText) |
|
87 { |
|
88 NS_PRECONDITION(!mIsCompiled, |
|
89 "Must not be compiled when accessing getter text"); |
|
90 EnsureUncompiledText(mGetter); |
|
91 mGetter.GetUncompiled()->AppendText(aText); |
|
92 } |
|
93 |
|
94 void |
|
95 nsXBLProtoImplProperty::AppendSetterText(const nsAString& aText) |
|
96 { |
|
97 NS_PRECONDITION(!mIsCompiled, |
|
98 "Must not be compiled when accessing setter text"); |
|
99 EnsureUncompiledText(mSetter); |
|
100 mSetter.GetUncompiled()->AppendText(aText); |
|
101 } |
|
102 |
|
103 void |
|
104 nsXBLProtoImplProperty::SetGetterLineNumber(uint32_t aLineNumber) |
|
105 { |
|
106 NS_PRECONDITION(!mIsCompiled, |
|
107 "Must not be compiled when accessing getter text"); |
|
108 EnsureUncompiledText(mGetter); |
|
109 mGetter.GetUncompiled()->SetLineNumber(aLineNumber); |
|
110 } |
|
111 |
|
112 void |
|
113 nsXBLProtoImplProperty::SetSetterLineNumber(uint32_t aLineNumber) |
|
114 { |
|
115 NS_PRECONDITION(!mIsCompiled, |
|
116 "Must not be compiled when accessing setter text"); |
|
117 EnsureUncompiledText(mSetter); |
|
118 mSetter.GetUncompiled()->SetLineNumber(aLineNumber); |
|
119 } |
|
120 |
|
121 const char* gPropertyArgs[] = { "val" }; |
|
122 |
|
123 nsresult |
|
124 nsXBLProtoImplProperty::InstallMember(JSContext *aCx, |
|
125 JS::Handle<JSObject*> aTargetClassObject) |
|
126 { |
|
127 NS_PRECONDITION(mIsCompiled, |
|
128 "Should not be installing an uncompiled property"); |
|
129 MOZ_ASSERT(mGetter.IsCompiled() && mSetter.IsCompiled()); |
|
130 MOZ_ASSERT(js::IsObjectInContextCompartment(aTargetClassObject, aCx)); |
|
131 JS::Rooted<JSObject*> globalObject(aCx, JS_GetGlobalForObject(aCx, aTargetClassObject)); |
|
132 MOZ_ASSERT(xpc::IsInXBLScope(globalObject) || |
|
133 globalObject == xpc::GetXBLScope(aCx, globalObject)); |
|
134 |
|
135 JS::Rooted<JSObject*> getter(aCx, mGetter.GetJSFunction()); |
|
136 JS::Rooted<JSObject*> setter(aCx, mSetter.GetJSFunction()); |
|
137 if (getter || setter) { |
|
138 if (getter) { |
|
139 if (!(getter = ::JS_CloneFunctionObject(aCx, getter, globalObject))) |
|
140 return NS_ERROR_OUT_OF_MEMORY; |
|
141 } |
|
142 |
|
143 if (setter) { |
|
144 if (!(setter = ::JS_CloneFunctionObject(aCx, setter, globalObject))) |
|
145 return NS_ERROR_OUT_OF_MEMORY; |
|
146 } |
|
147 |
|
148 nsDependentString name(mName); |
|
149 if (!::JS_DefineUCProperty(aCx, aTargetClassObject, |
|
150 static_cast<const jschar*>(mName), |
|
151 name.Length(), JSVAL_VOID, |
|
152 JS_DATA_TO_FUNC_PTR(JSPropertyOp, getter.get()), |
|
153 JS_DATA_TO_FUNC_PTR(JSStrictPropertyOp, setter.get()), |
|
154 mJSAttributes)) |
|
155 return NS_ERROR_OUT_OF_MEMORY; |
|
156 } |
|
157 return NS_OK; |
|
158 } |
|
159 |
|
160 nsresult |
|
161 nsXBLProtoImplProperty::CompileMember(const nsCString& aClassStr, |
|
162 JS::Handle<JSObject*> aClassObject) |
|
163 { |
|
164 AssertInCompilationScope(); |
|
165 NS_PRECONDITION(!mIsCompiled, |
|
166 "Trying to compile an already-compiled property"); |
|
167 NS_PRECONDITION(aClassObject, |
|
168 "Must have class object to compile"); |
|
169 MOZ_ASSERT(!mGetter.IsCompiled() && !mSetter.IsCompiled()); |
|
170 |
|
171 if (!mName) |
|
172 return NS_ERROR_FAILURE; // Without a valid name, we can't install the member. |
|
173 |
|
174 // We have a property. |
|
175 nsresult rv = NS_OK; |
|
176 |
|
177 nsAutoCString functionUri; |
|
178 if (mGetter.GetUncompiled() || mSetter.GetUncompiled()) { |
|
179 functionUri = aClassStr; |
|
180 int32_t hash = functionUri.RFindChar('#'); |
|
181 if (hash != kNotFound) { |
|
182 functionUri.Truncate(hash); |
|
183 } |
|
184 } |
|
185 |
|
186 bool deletedGetter = false; |
|
187 nsXBLTextWithLineNumber *getterText = mGetter.GetUncompiled(); |
|
188 if (getterText && getterText->GetText()) { |
|
189 nsDependentString getter(getterText->GetText()); |
|
190 if (!getter.IsEmpty()) { |
|
191 AutoJSContext cx; |
|
192 JSAutoCompartment ac(cx, aClassObject); |
|
193 JS::CompileOptions options(cx); |
|
194 options.setFileAndLine(functionUri.get(), getterText->GetLineNumber()) |
|
195 .setVersion(JSVERSION_LATEST); |
|
196 nsCString name = NS_LITERAL_CSTRING("get_") + NS_ConvertUTF16toUTF8(mName); |
|
197 JS::Rooted<JSObject*> getterObject(cx); |
|
198 rv = nsJSUtils::CompileFunction(cx, JS::NullPtr(), options, name, 0, |
|
199 nullptr, getter, getterObject.address()); |
|
200 |
|
201 delete getterText; |
|
202 deletedGetter = true; |
|
203 |
|
204 mGetter.SetJSFunction(getterObject); |
|
205 |
|
206 if (mGetter.GetJSFunction() && NS_SUCCEEDED(rv)) { |
|
207 mJSAttributes |= JSPROP_GETTER | JSPROP_SHARED; |
|
208 } |
|
209 if (NS_FAILED(rv)) { |
|
210 mGetter.SetJSFunction(nullptr); |
|
211 mJSAttributes &= ~JSPROP_GETTER; |
|
212 /*chaining to return failure*/ |
|
213 } |
|
214 } |
|
215 } // if getter is not empty |
|
216 |
|
217 if (!deletedGetter) { // Empty getter |
|
218 delete getterText; |
|
219 mGetter.SetJSFunction(nullptr); |
|
220 } |
|
221 |
|
222 if (NS_FAILED(rv)) { |
|
223 // We failed to compile our getter. So either we've set it to null, or |
|
224 // it's still set to the text object. In either case, it's safe to return |
|
225 // the error here, since then we'll be cleaned up as uncompiled and that |
|
226 // will be ok. Going on and compiling the setter and _then_ returning an |
|
227 // error, on the other hand, will try to clean up a compiled setter as |
|
228 // uncompiled and crash. |
|
229 return rv; |
|
230 } |
|
231 |
|
232 bool deletedSetter = false; |
|
233 nsXBLTextWithLineNumber *setterText = mSetter.GetUncompiled(); |
|
234 if (setterText && setterText->GetText()) { |
|
235 nsDependentString setter(setterText->GetText()); |
|
236 if (!setter.IsEmpty()) { |
|
237 AutoJSContext cx; |
|
238 JSAutoCompartment ac(cx, aClassObject); |
|
239 JS::CompileOptions options(cx); |
|
240 options.setFileAndLine(functionUri.get(), setterText->GetLineNumber()) |
|
241 .setVersion(JSVERSION_LATEST); |
|
242 nsCString name = NS_LITERAL_CSTRING("set_") + NS_ConvertUTF16toUTF8(mName); |
|
243 JS::Rooted<JSObject*> setterObject(cx); |
|
244 rv = nsJSUtils::CompileFunction(cx, JS::NullPtr(), options, name, 1, |
|
245 gPropertyArgs, setter, |
|
246 setterObject.address()); |
|
247 |
|
248 delete setterText; |
|
249 deletedSetter = true; |
|
250 mSetter.SetJSFunction(setterObject); |
|
251 |
|
252 if (mSetter.GetJSFunction() && NS_SUCCEEDED(rv)) { |
|
253 mJSAttributes |= JSPROP_SETTER | JSPROP_SHARED; |
|
254 } |
|
255 if (NS_FAILED(rv)) { |
|
256 mSetter.SetJSFunction(nullptr); |
|
257 mJSAttributes &= ~JSPROP_SETTER; |
|
258 /*chaining to return failure*/ |
|
259 } |
|
260 } |
|
261 } // if setter wasn't empty.... |
|
262 |
|
263 if (!deletedSetter) { // Empty setter |
|
264 delete setterText; |
|
265 mSetter.SetJSFunction(nullptr); |
|
266 } |
|
267 |
|
268 #ifdef DEBUG |
|
269 mIsCompiled = NS_SUCCEEDED(rv); |
|
270 #endif |
|
271 |
|
272 return rv; |
|
273 } |
|
274 |
|
275 void |
|
276 nsXBLProtoImplProperty::Trace(const TraceCallbacks& aCallbacks, void *aClosure) |
|
277 { |
|
278 if (mJSAttributes & JSPROP_GETTER) { |
|
279 aCallbacks.Trace(&mGetter.AsHeapObject(), "mGetter", aClosure); |
|
280 } |
|
281 |
|
282 if (mJSAttributes & JSPROP_SETTER) { |
|
283 aCallbacks.Trace(&mSetter.AsHeapObject(), "mSetter", aClosure); |
|
284 } |
|
285 } |
|
286 |
|
287 nsresult |
|
288 nsXBLProtoImplProperty::Read(nsIObjectInputStream* aStream, |
|
289 XBLBindingSerializeDetails aType) |
|
290 { |
|
291 AssertInCompilationScope(); |
|
292 MOZ_ASSERT(!mIsCompiled); |
|
293 MOZ_ASSERT(!mGetter.GetUncompiled() && !mSetter.GetUncompiled()); |
|
294 |
|
295 AutoJSContext cx; |
|
296 JS::Rooted<JSObject*> getterObject(cx); |
|
297 if (aType == XBLBinding_Serialize_GetterProperty || |
|
298 aType == XBLBinding_Serialize_GetterSetterProperty) { |
|
299 nsresult rv = XBL_DeserializeFunction(aStream, &getterObject); |
|
300 NS_ENSURE_SUCCESS(rv, rv); |
|
301 |
|
302 mJSAttributes |= JSPROP_GETTER | JSPROP_SHARED; |
|
303 } |
|
304 mGetter.SetJSFunction(getterObject); |
|
305 |
|
306 JS::Rooted<JSObject*> setterObject(cx); |
|
307 if (aType == XBLBinding_Serialize_SetterProperty || |
|
308 aType == XBLBinding_Serialize_GetterSetterProperty) { |
|
309 nsresult rv = XBL_DeserializeFunction(aStream, &setterObject); |
|
310 NS_ENSURE_SUCCESS(rv, rv); |
|
311 |
|
312 mJSAttributes |= JSPROP_SETTER | JSPROP_SHARED; |
|
313 } |
|
314 mSetter.SetJSFunction(setterObject); |
|
315 |
|
316 #ifdef DEBUG |
|
317 mIsCompiled = true; |
|
318 #endif |
|
319 |
|
320 return NS_OK; |
|
321 } |
|
322 |
|
323 nsresult |
|
324 nsXBLProtoImplProperty::Write(nsIObjectOutputStream* aStream) |
|
325 { |
|
326 AssertInCompilationScope(); |
|
327 XBLBindingSerializeDetails type; |
|
328 |
|
329 if (mJSAttributes & JSPROP_GETTER) { |
|
330 type = mJSAttributes & JSPROP_SETTER ? |
|
331 XBLBinding_Serialize_GetterSetterProperty : |
|
332 XBLBinding_Serialize_GetterProperty; |
|
333 } |
|
334 else { |
|
335 type = XBLBinding_Serialize_SetterProperty; |
|
336 } |
|
337 |
|
338 if (mJSAttributes & JSPROP_READONLY) { |
|
339 type |= XBLBinding_Serialize_ReadOnly; |
|
340 } |
|
341 |
|
342 nsresult rv = aStream->Write8(type); |
|
343 NS_ENSURE_SUCCESS(rv, rv); |
|
344 rv = aStream->WriteWStringZ(mName); |
|
345 NS_ENSURE_SUCCESS(rv, rv); |
|
346 |
|
347 // The calls to fromMarkedLocation() below are safe because mSetter and |
|
348 // mGetter are traced by the Trace() method above, and because their values |
|
349 // are never changed after they have been set to a compiled function. |
|
350 MOZ_ASSERT_IF(mJSAttributes & (JSPROP_GETTER | JSPROP_SETTER), mIsCompiled); |
|
351 |
|
352 if (mJSAttributes & JSPROP_GETTER) { |
|
353 JS::Handle<JSObject*> function = |
|
354 JS::Handle<JSObject*>::fromMarkedLocation(mGetter.AsHeapObject().address()); |
|
355 rv = XBL_SerializeFunction(aStream, function); |
|
356 NS_ENSURE_SUCCESS(rv, rv); |
|
357 } |
|
358 |
|
359 if (mJSAttributes & JSPROP_SETTER) { |
|
360 JS::Handle<JSObject*> function = |
|
361 JS::Handle<JSObject*>::fromMarkedLocation(mSetter.AsHeapObject().address()); |
|
362 rv = XBL_SerializeFunction(aStream, function); |
|
363 NS_ENSURE_SUCCESS(rv, rv); |
|
364 } |
|
365 |
|
366 return NS_OK; |
|
367 } |