|
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 "mozilla/DebugOnly.h" |
|
7 |
|
8 #include "nsXBLProtoImpl.h" |
|
9 #include "nsIContent.h" |
|
10 #include "nsIDocument.h" |
|
11 #include "nsContentUtils.h" |
|
12 #include "nsCxPusher.h" |
|
13 #include "nsIScriptGlobalObject.h" |
|
14 #include "nsIScriptContext.h" |
|
15 #include "nsIXPConnect.h" |
|
16 #include "nsIServiceManager.h" |
|
17 #include "nsIDOMNode.h" |
|
18 #include "nsXBLPrototypeBinding.h" |
|
19 #include "nsXBLProtoImplProperty.h" |
|
20 #include "nsIURI.h" |
|
21 #include "mozilla/dom/XULElementBinding.h" |
|
22 #include "xpcpublic.h" |
|
23 #include "js/CharacterEncoding.h" |
|
24 |
|
25 using namespace mozilla; |
|
26 using js::GetGlobalForObjectCrossCompartment; |
|
27 using js::AssertSameCompartment; |
|
28 |
|
29 nsresult |
|
30 nsXBLProtoImpl::InstallImplementation(nsXBLPrototypeBinding* aPrototypeBinding, |
|
31 nsXBLBinding* aBinding) |
|
32 { |
|
33 // This function is called to install a concrete implementation on a bound element using |
|
34 // this prototype implementation as a guide. The prototype implementation is compiled lazily, |
|
35 // so for the first bound element that needs a concrete implementation, we also build the |
|
36 // prototype implementation. |
|
37 if (!mMembers && !mFields) // Constructor and destructor also live in mMembers |
|
38 return NS_OK; // Nothing to do, so let's not waste time. |
|
39 |
|
40 // If the way this gets the script context changes, fix |
|
41 // nsXBLProtoImplAnonymousMethod::Execute |
|
42 nsIDocument* document = aBinding->GetBoundElement()->OwnerDoc(); |
|
43 |
|
44 nsCOMPtr<nsIScriptGlobalObject> global = do_QueryInterface(document->GetScopeObject()); |
|
45 if (!global) return NS_OK; |
|
46 |
|
47 nsCOMPtr<nsIScriptContext> context = global->GetContext(); |
|
48 if (!context) return NS_OK; |
|
49 JSContext* cx = context->GetNativeContext(); |
|
50 AutoCxPusher pusher(cx); |
|
51 |
|
52 // InitTarget objects gives us back the JS object that represents the bound element and the |
|
53 // class object in the bound document that represents the concrete version of this implementation. |
|
54 // This function also has the side effect of building up the prototype implementation if it has |
|
55 // not been built already. |
|
56 JS::Rooted<JSObject*> targetClassObject(cx, nullptr); |
|
57 bool targetObjectIsNew = false; |
|
58 nsresult rv = InitTargetObjects(aPrototypeBinding, |
|
59 aBinding->GetBoundElement(), |
|
60 &targetClassObject, |
|
61 &targetObjectIsNew); |
|
62 NS_ENSURE_SUCCESS(rv, rv); // kick out if we were unable to properly intialize our target objects |
|
63 MOZ_ASSERT(targetClassObject); |
|
64 |
|
65 // If the prototype already existed, we don't need to install anything. return early. |
|
66 if (!targetObjectIsNew) |
|
67 return NS_OK; |
|
68 |
|
69 // We want to define the canonical set of members in a safe place. If we're |
|
70 // using a separate XBL scope, we want to define them there first (so that |
|
71 // they'll be available for Xray lookups, among other things), and then copy |
|
72 // the properties to the content-side prototype as needed. We don't need to |
|
73 // bother about the field accessors here, since we don't use/support those |
|
74 // for in-content bindings. |
|
75 |
|
76 // First, start by entering the compartment of the XBL scope. This may or may |
|
77 // not be the same compartment as globalObject. |
|
78 JS::Rooted<JSObject*> globalObject(cx, |
|
79 GetGlobalForObjectCrossCompartment(targetClassObject)); |
|
80 JS::Rooted<JSObject*> scopeObject(cx, xpc::GetXBLScopeOrGlobal(cx, globalObject)); |
|
81 NS_ENSURE_TRUE(scopeObject, NS_ERROR_OUT_OF_MEMORY); |
|
82 JSAutoCompartment ac(cx, scopeObject); |
|
83 |
|
84 // If they're different, create our safe holder object in the XBL scope. |
|
85 JS::Rooted<JSObject*> propertyHolder(cx); |
|
86 if (scopeObject != globalObject) { |
|
87 |
|
88 // This is just a property holder, so it doesn't need any special JSClass. |
|
89 propertyHolder = JS_NewObjectWithGivenProto(cx, nullptr, JS::NullPtr(), scopeObject); |
|
90 NS_ENSURE_TRUE(propertyHolder, NS_ERROR_OUT_OF_MEMORY); |
|
91 |
|
92 // Define it as a property on the scopeObject, using the same name used on |
|
93 // the content side. |
|
94 bool ok = JS_DefineProperty(cx, scopeObject, aPrototypeBinding->ClassName().get(), |
|
95 propertyHolder, JSPROP_PERMANENT | JSPROP_READONLY, |
|
96 JS_PropertyStub, JS_StrictPropertyStub); |
|
97 NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED); |
|
98 } else { |
|
99 propertyHolder = targetClassObject; |
|
100 } |
|
101 |
|
102 // Walk our member list and install each one in turn on the XBL scope object. |
|
103 for (nsXBLProtoImplMember* curr = mMembers; |
|
104 curr; |
|
105 curr = curr->GetNext()) |
|
106 curr->InstallMember(cx, propertyHolder); |
|
107 |
|
108 // Now, if we're using a separate XBL scope, enter the compartment of the |
|
109 // bound node and copy exposable properties to the prototype there. This |
|
110 // rewraps them appropriately, which should result in cross-compartment |
|
111 // function wrappers. |
|
112 if (propertyHolder != targetClassObject) { |
|
113 AssertSameCompartment(propertyHolder, scopeObject); |
|
114 AssertSameCompartment(targetClassObject, globalObject); |
|
115 for (nsXBLProtoImplMember* curr = mMembers; curr; curr = curr->GetNext()) { |
|
116 if (curr->ShouldExposeToUntrustedContent()) { |
|
117 JS::Rooted<jsid> id(cx); |
|
118 JS::TwoByteChars chars(curr->GetName(), NS_strlen(curr->GetName())); |
|
119 bool ok = JS_CharsToId(cx, chars, &id); |
|
120 NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED); |
|
121 JS_CopyPropertyFrom(cx, id, targetClassObject, propertyHolder); |
|
122 NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED); |
|
123 } |
|
124 } |
|
125 } |
|
126 |
|
127 // From here on out, work in the scope of the bound element. |
|
128 JSAutoCompartment ac2(cx, targetClassObject); |
|
129 |
|
130 // Install all of our field accessors. |
|
131 for (nsXBLProtoImplField* curr = mFields; |
|
132 curr; |
|
133 curr = curr->GetNext()) |
|
134 curr->InstallAccessors(cx, targetClassObject); |
|
135 |
|
136 return NS_OK; |
|
137 } |
|
138 |
|
139 nsresult |
|
140 nsXBLProtoImpl::InitTargetObjects(nsXBLPrototypeBinding* aBinding, |
|
141 nsIContent* aBoundElement, |
|
142 JS::MutableHandle<JSObject*> aTargetClassObject, |
|
143 bool* aTargetIsNew) |
|
144 { |
|
145 nsresult rv = NS_OK; |
|
146 |
|
147 if (!mPrecompiledMemberHolder) { |
|
148 rv = CompilePrototypeMembers(aBinding); // This is the first time we've ever installed this binding on an element. |
|
149 // We need to go ahead and compile all methods and properties on a class |
|
150 // in our prototype binding. |
|
151 if (NS_FAILED(rv)) |
|
152 return rv; |
|
153 |
|
154 MOZ_ASSERT(mPrecompiledMemberHolder); |
|
155 } |
|
156 |
|
157 nsIDocument *ownerDoc = aBoundElement->OwnerDoc(); |
|
158 nsIGlobalObject *sgo; |
|
159 |
|
160 if (!(sgo = ownerDoc->GetScopeObject())) { |
|
161 return NS_ERROR_UNEXPECTED; |
|
162 } |
|
163 |
|
164 // Because our prototype implementation has a class, we need to build up a corresponding |
|
165 // class for the concrete implementation in the bound document. |
|
166 AutoJSContext cx; |
|
167 JS::Rooted<JSObject*> global(cx, sgo->GetGlobalJSObject()); |
|
168 JS::Rooted<JS::Value> v(cx); |
|
169 |
|
170 JSAutoCompartment ac(cx, global); |
|
171 // Make sure the interface object is created before the prototype object |
|
172 // so that XULElement is hidden from content. See bug 909340. |
|
173 bool defineOnGlobal = dom::XULElementBinding::ConstructorEnabled(cx, global); |
|
174 dom::XULElementBinding::GetConstructorObject(cx, global, defineOnGlobal); |
|
175 |
|
176 rv = nsContentUtils::WrapNative(cx, aBoundElement, &v, |
|
177 /* aAllowWrapping = */ false); |
|
178 NS_ENSURE_SUCCESS(rv, rv); |
|
179 |
|
180 JS::Rooted<JSObject*> value(cx, &v.toObject()); |
|
181 JSAutoCompartment ac2(cx, value); |
|
182 |
|
183 // All of the above code was just obtaining the bound element's script object and its immediate |
|
184 // concrete base class. We need to alter the object so that our concrete class is interposed |
|
185 // between the object and its base class. We become the new base class of the object, and the |
|
186 // object's old base class becomes the new class' base class. |
|
187 rv = aBinding->InitClass(mClassName, cx, value, aTargetClassObject, aTargetIsNew); |
|
188 if (NS_FAILED(rv)) { |
|
189 return rv; |
|
190 } |
|
191 |
|
192 aBoundElement->PreserveWrapper(aBoundElement); |
|
193 |
|
194 return rv; |
|
195 } |
|
196 |
|
197 nsresult |
|
198 nsXBLProtoImpl::CompilePrototypeMembers(nsXBLPrototypeBinding* aBinding) |
|
199 { |
|
200 // We want to pre-compile our implementation's members against a "prototype context". Then when we actually |
|
201 // bind the prototype to a real xbl instance, we'll clone the pre-compiled JS into the real instance's |
|
202 // context. |
|
203 AutoSafeJSContext cx; |
|
204 JS::Rooted<JSObject*> compilationGlobal(cx, xpc::GetCompilationScope()); |
|
205 NS_ENSURE_TRUE(compilationGlobal, NS_ERROR_UNEXPECTED); |
|
206 JSAutoCompartment ac(cx, compilationGlobal); |
|
207 |
|
208 mPrecompiledMemberHolder = JS_NewObjectWithGivenProto(cx, nullptr, JS::NullPtr(), compilationGlobal); |
|
209 if (!mPrecompiledMemberHolder) |
|
210 return NS_ERROR_OUT_OF_MEMORY; |
|
211 |
|
212 // Now that we have a class object installed, we walk our member list and compile each of our |
|
213 // properties and methods in turn. |
|
214 JS::Rooted<JSObject*> rootedHolder(cx, mPrecompiledMemberHolder); |
|
215 for (nsXBLProtoImplMember* curr = mMembers; |
|
216 curr; |
|
217 curr = curr->GetNext()) { |
|
218 nsresult rv = curr->CompileMember(mClassName, rootedHolder); |
|
219 if (NS_FAILED(rv)) { |
|
220 DestroyMembers(); |
|
221 return rv; |
|
222 } |
|
223 } |
|
224 |
|
225 return NS_OK; |
|
226 } |
|
227 |
|
228 bool |
|
229 nsXBLProtoImpl::LookupMember(JSContext* aCx, nsString& aName, |
|
230 JS::Handle<jsid> aNameAsId, |
|
231 JS::MutableHandle<JSPropertyDescriptor> aDesc, |
|
232 JS::Handle<JSObject*> aClassObject) |
|
233 { |
|
234 for (nsXBLProtoImplMember* m = mMembers; m; m = m->GetNext()) { |
|
235 if (aName.Equals(m->GetName())) { |
|
236 return JS_GetPropertyDescriptorById(aCx, aClassObject, aNameAsId, aDesc); |
|
237 } |
|
238 } |
|
239 return true; |
|
240 } |
|
241 |
|
242 void |
|
243 nsXBLProtoImpl::Trace(const TraceCallbacks& aCallbacks, void *aClosure) |
|
244 { |
|
245 // If we don't have a class object then we either didn't compile members |
|
246 // or we only have fields, in both cases there are no cycles through our |
|
247 // members. |
|
248 if (!mPrecompiledMemberHolder) { |
|
249 return; |
|
250 } |
|
251 |
|
252 nsXBLProtoImplMember *member; |
|
253 for (member = mMembers; member; member = member->GetNext()) { |
|
254 member->Trace(aCallbacks, aClosure); |
|
255 } |
|
256 } |
|
257 |
|
258 void |
|
259 nsXBLProtoImpl::UnlinkJSObjects() |
|
260 { |
|
261 if (mPrecompiledMemberHolder) { |
|
262 DestroyMembers(); |
|
263 } |
|
264 } |
|
265 |
|
266 nsXBLProtoImplField* |
|
267 nsXBLProtoImpl::FindField(const nsString& aFieldName) const |
|
268 { |
|
269 for (nsXBLProtoImplField* f = mFields; f; f = f->GetNext()) { |
|
270 if (aFieldName.Equals(f->GetName())) { |
|
271 return f; |
|
272 } |
|
273 } |
|
274 |
|
275 return nullptr; |
|
276 } |
|
277 |
|
278 bool |
|
279 nsXBLProtoImpl::ResolveAllFields(JSContext *cx, JS::Handle<JSObject*> obj) const |
|
280 { |
|
281 for (nsXBLProtoImplField* f = mFields; f; f = f->GetNext()) { |
|
282 // Using OBJ_LOOKUP_PROPERTY is a pain, since what we have is a |
|
283 // char16_t* for the property name. Let's just use the public API and |
|
284 // all. |
|
285 nsDependentString name(f->GetName()); |
|
286 JS::Rooted<JS::Value> dummy(cx); |
|
287 if (!::JS_LookupUCProperty(cx, obj, name.get(), name.Length(), &dummy)) { |
|
288 return false; |
|
289 } |
|
290 } |
|
291 |
|
292 return true; |
|
293 } |
|
294 |
|
295 void |
|
296 nsXBLProtoImpl::UndefineFields(JSContext *cx, JS::Handle<JSObject*> obj) const |
|
297 { |
|
298 JSAutoRequest ar(cx); |
|
299 for (nsXBLProtoImplField* f = mFields; f; f = f->GetNext()) { |
|
300 nsDependentString name(f->GetName()); |
|
301 |
|
302 const jschar* s = name.get(); |
|
303 bool hasProp; |
|
304 if (::JS_AlreadyHasOwnUCProperty(cx, obj, s, name.Length(), &hasProp) && |
|
305 hasProp) { |
|
306 bool dummy; |
|
307 ::JS_DeleteUCProperty2(cx, obj, s, name.Length(), &dummy); |
|
308 } |
|
309 } |
|
310 } |
|
311 |
|
312 void |
|
313 nsXBLProtoImpl::DestroyMembers() |
|
314 { |
|
315 MOZ_ASSERT(mPrecompiledMemberHolder); |
|
316 |
|
317 delete mMembers; |
|
318 mMembers = nullptr; |
|
319 mConstructor = nullptr; |
|
320 mDestructor = nullptr; |
|
321 } |
|
322 |
|
323 nsresult |
|
324 nsXBLProtoImpl::Read(nsIObjectInputStream* aStream, |
|
325 nsXBLPrototypeBinding* aBinding) |
|
326 { |
|
327 AssertInCompilationScope(); |
|
328 AutoJSContext cx; |
|
329 // Set up a class object first so that deserialization is possible |
|
330 JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx)); |
|
331 mPrecompiledMemberHolder = JS_NewObjectWithGivenProto(cx, nullptr, JS::NullPtr(), global); |
|
332 if (!mPrecompiledMemberHolder) |
|
333 return NS_ERROR_OUT_OF_MEMORY; |
|
334 |
|
335 nsXBLProtoImplField* previousField = nullptr; |
|
336 nsXBLProtoImplMember* previousMember = nullptr; |
|
337 |
|
338 do { |
|
339 XBLBindingSerializeDetails type; |
|
340 nsresult rv = aStream->Read8(&type); |
|
341 NS_ENSURE_SUCCESS(rv, rv); |
|
342 if (type == XBLBinding_Serialize_NoMoreItems) |
|
343 break; |
|
344 |
|
345 switch (type & XBLBinding_Serialize_Mask) { |
|
346 case XBLBinding_Serialize_Field: |
|
347 { |
|
348 nsXBLProtoImplField* field = |
|
349 new nsXBLProtoImplField(type & XBLBinding_Serialize_ReadOnly); |
|
350 rv = field->Read(aStream); |
|
351 if (NS_FAILED(rv)) { |
|
352 delete field; |
|
353 return rv; |
|
354 } |
|
355 |
|
356 if (previousField) { |
|
357 previousField->SetNext(field); |
|
358 } |
|
359 else { |
|
360 mFields = field; |
|
361 } |
|
362 previousField = field; |
|
363 |
|
364 break; |
|
365 } |
|
366 case XBLBinding_Serialize_GetterProperty: |
|
367 case XBLBinding_Serialize_SetterProperty: |
|
368 case XBLBinding_Serialize_GetterSetterProperty: |
|
369 { |
|
370 nsAutoString name; |
|
371 nsresult rv = aStream->ReadString(name); |
|
372 NS_ENSURE_SUCCESS(rv, rv); |
|
373 |
|
374 nsXBLProtoImplProperty* prop = |
|
375 new nsXBLProtoImplProperty(name.get(), type & XBLBinding_Serialize_ReadOnly); |
|
376 rv = prop->Read(aStream, type & XBLBinding_Serialize_Mask); |
|
377 if (NS_FAILED(rv)) { |
|
378 delete prop; |
|
379 return rv; |
|
380 } |
|
381 |
|
382 previousMember = AddMember(prop, previousMember); |
|
383 break; |
|
384 } |
|
385 case XBLBinding_Serialize_Method: |
|
386 { |
|
387 nsAutoString name; |
|
388 rv = aStream->ReadString(name); |
|
389 NS_ENSURE_SUCCESS(rv, rv); |
|
390 |
|
391 nsXBLProtoImplMethod* method = new nsXBLProtoImplMethod(name.get()); |
|
392 rv = method->Read(aStream); |
|
393 if (NS_FAILED(rv)) { |
|
394 delete method; |
|
395 return rv; |
|
396 } |
|
397 |
|
398 previousMember = AddMember(method, previousMember); |
|
399 break; |
|
400 } |
|
401 case XBLBinding_Serialize_Constructor: |
|
402 { |
|
403 nsAutoString name; |
|
404 rv = aStream->ReadString(name); |
|
405 NS_ENSURE_SUCCESS(rv, rv); |
|
406 |
|
407 mConstructor = new nsXBLProtoImplAnonymousMethod(name.get()); |
|
408 rv = mConstructor->Read(aStream); |
|
409 if (NS_FAILED(rv)) { |
|
410 delete mConstructor; |
|
411 mConstructor = nullptr; |
|
412 return rv; |
|
413 } |
|
414 |
|
415 previousMember = AddMember(mConstructor, previousMember); |
|
416 break; |
|
417 } |
|
418 case XBLBinding_Serialize_Destructor: |
|
419 { |
|
420 nsAutoString name; |
|
421 rv = aStream->ReadString(name); |
|
422 NS_ENSURE_SUCCESS(rv, rv); |
|
423 |
|
424 mDestructor = new nsXBLProtoImplAnonymousMethod(name.get()); |
|
425 rv = mDestructor->Read(aStream); |
|
426 if (NS_FAILED(rv)) { |
|
427 delete mDestructor; |
|
428 mDestructor = nullptr; |
|
429 return rv; |
|
430 } |
|
431 |
|
432 previousMember = AddMember(mDestructor, previousMember); |
|
433 break; |
|
434 } |
|
435 default: |
|
436 NS_ERROR("Unexpected binding member type"); |
|
437 break; |
|
438 } |
|
439 } while (1); |
|
440 |
|
441 return NS_OK; |
|
442 } |
|
443 |
|
444 nsresult |
|
445 nsXBLProtoImpl::Write(nsIObjectOutputStream* aStream, |
|
446 nsXBLPrototypeBinding* aBinding) |
|
447 { |
|
448 nsresult rv; |
|
449 |
|
450 if (!mPrecompiledMemberHolder) { |
|
451 rv = CompilePrototypeMembers(aBinding); |
|
452 NS_ENSURE_SUCCESS(rv, rv); |
|
453 } |
|
454 |
|
455 rv = aStream->WriteStringZ(mClassName.get()); |
|
456 NS_ENSURE_SUCCESS(rv, rv); |
|
457 |
|
458 for (nsXBLProtoImplField* curr = mFields; curr; curr = curr->GetNext()) { |
|
459 rv = curr->Write(aStream); |
|
460 NS_ENSURE_SUCCESS(rv, rv); |
|
461 } |
|
462 for (nsXBLProtoImplMember* curr = mMembers; curr; curr = curr->GetNext()) { |
|
463 if (curr == mConstructor) { |
|
464 rv = mConstructor->Write(aStream, XBLBinding_Serialize_Constructor); |
|
465 } |
|
466 else if (curr == mDestructor) { |
|
467 rv = mDestructor->Write(aStream, XBLBinding_Serialize_Destructor); |
|
468 } |
|
469 else { |
|
470 rv = curr->Write(aStream); |
|
471 } |
|
472 NS_ENSURE_SUCCESS(rv, rv); |
|
473 } |
|
474 |
|
475 return aStream->Write8(XBLBinding_Serialize_NoMoreItems); |
|
476 } |
|
477 |
|
478 nsresult |
|
479 NS_NewXBLProtoImpl(nsXBLPrototypeBinding* aBinding, |
|
480 const char16_t* aClassName, |
|
481 nsXBLProtoImpl** aResult) |
|
482 { |
|
483 nsXBLProtoImpl* impl = new nsXBLProtoImpl(); |
|
484 if (!impl) |
|
485 return NS_ERROR_OUT_OF_MEMORY; |
|
486 if (aClassName) |
|
487 impl->mClassName.AssignWithConversion(aClassName); |
|
488 else |
|
489 aBinding->BindingURI()->GetSpec(impl->mClassName); |
|
490 aBinding->SetImplementation(impl); |
|
491 *aResult = impl; |
|
492 |
|
493 return NS_OK; |
|
494 } |
|
495 |