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