dom/xbl/nsXBLProtoImpl.cpp

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:e655abe1f401
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

mercurial