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/. */
6 #include "mozilla/DebugOnly.h"
8 #include "nsXBLProtoImpl.h"
9 #include "nsIContent.h"
10 #include "nsIDocument.h"
11 #include "nsContentUtils.h"
12 #include "nsIXPConnect.h"
13 #include "nsIServiceManager.h"
14 #include "nsIDOMNode.h"
15 #include "nsXBLPrototypeBinding.h"
16 #include "nsXBLProtoImplProperty.h"
18 #include "mozilla/AddonPathService.h"
19 #include "mozilla/dom/ScriptSettings.h"
20 #include "mozilla/dom/XULElementBinding.h"
21 #include "xpcpublic.h"
22 #include "js/CharacterEncoding.h"
24 using namespace mozilla
;
25 using js::GetGlobalForObjectCrossCompartment
;
26 using js::AssertSameCompartment
;
29 nsXBLProtoImpl::InstallImplementation(nsXBLPrototypeBinding
* aPrototypeBinding
,
30 nsXBLBinding
* aBinding
)
32 // This function is called to install a concrete implementation on a bound element using
33 // this prototype implementation as a guide. The prototype implementation is compiled lazily,
34 // so for the first bound element that needs a concrete implementation, we also build the
35 // prototype implementation.
36 if (!mMembers
&& !mFields
) // Constructor and destructor also live in mMembers
37 return NS_OK
; // Nothing to do, so let's not waste time.
39 // If the way this gets the script context changes, fix
40 // nsXBLProtoImplAnonymousMethod::Execute
41 nsIDocument
* document
= aBinding
->GetBoundElement()->OwnerDoc();
43 // This sometimes gets called when we have no outer window and if we don't
44 // catch this, we get leaks during crashtests and reftests.
45 if (NS_WARN_IF(!document
->GetWindow())) {
49 // |propertyHolder| (below) can be an existing object, so in theory we might
50 // hit something that could end up running script. We never want that to
51 // happen here, so we use an AutoJSAPI instead of an AutoEntryScript.
53 if (NS_WARN_IF(!jsapi
.Init(document
->GetScopeObject()))) {
56 JSContext
* cx
= jsapi
.cx();
58 // InitTarget objects gives us back the JS object that represents the bound element and the
59 // class object in the bound document that represents the concrete version of this implementation.
60 // This function also has the side effect of building up the prototype implementation if it has
61 // not been built already.
62 JS::Rooted
<JSObject
*> targetClassObject(cx
, nullptr);
63 bool targetObjectIsNew
= false;
64 nsresult rv
= InitTargetObjects(aPrototypeBinding
,
65 aBinding
->GetBoundElement(),
68 NS_ENSURE_SUCCESS(rv
, rv
); // kick out if we were unable to properly intialize our target objects
69 MOZ_ASSERT(targetClassObject
);
71 // If the prototype already existed, we don't need to install anything. return early.
72 if (!targetObjectIsNew
)
75 // We want to define the canonical set of members in a safe place. If we're
76 // using a separate XBL scope, we want to define them there first (so that
77 // they'll be available for Xray lookups, among other things), and then copy
78 // the properties to the content-side prototype as needed. We don't need to
79 // bother about the field accessors here, since we don't use/support those
80 // for in-content bindings.
82 // First, start by entering the compartment of the XBL scope. This may or may
83 // not be the same compartment as globalObject.
84 JSAddonId
* addonId
= MapURIToAddonID(aPrototypeBinding
->BindingURI());
85 JS::Rooted
<JSObject
*> globalObject(cx
,
86 GetGlobalForObjectCrossCompartment(targetClassObject
));
87 JS::Rooted
<JSObject
*> scopeObject(cx
, xpc::GetScopeForXBLExecution(cx
, globalObject
, addonId
));
88 NS_ENSURE_TRUE(scopeObject
, NS_ERROR_OUT_OF_MEMORY
);
89 JSAutoCompartment
ac(cx
, scopeObject
);
91 // Determine the appropriate property holder.
93 // Note: If |targetIsNew| is false, we'll early-return above. However, that only
94 // tells us if the content-side object is new, which may be the case even if
95 // we've already set up the binding on the XBL side. For example, if we apply
96 // a binding #foo to a <span> when we've already applied it to a <div>, we'll
97 // end up with a different content prototype, but we'll already have a property
98 // holder called |foo| in the XBL scope. Check for that to avoid wasteful and
99 // weird property holder duplication.
100 const char* className
= aPrototypeBinding
->ClassName().get();
101 JS::Rooted
<JSObject
*> propertyHolder(cx
);
102 JS::Rooted
<JSPropertyDescriptor
> existingHolder(cx
);
103 if (scopeObject
!= globalObject
&&
104 !JS_GetOwnPropertyDescriptor(cx
, scopeObject
, className
, &existingHolder
)) {
105 return NS_ERROR_FAILURE
;
107 bool propertyHolderIsNew
= !existingHolder
.object() || !existingHolder
.value().isObject();
109 if (!propertyHolderIsNew
) {
110 propertyHolder
= &existingHolder
.value().toObject();
111 } else if (scopeObject
!= globalObject
) {
113 // This is just a property holder, so it doesn't need any special JSClass.
114 propertyHolder
= JS_NewObjectWithGivenProto(cx
, nullptr, JS::NullPtr(), scopeObject
);
115 NS_ENSURE_TRUE(propertyHolder
, NS_ERROR_OUT_OF_MEMORY
);
117 // Define it as a property on the scopeObject, using the same name used on
119 bool ok
= JS_DefineProperty(cx
, scopeObject
, className
, propertyHolder
,
120 JSPROP_PERMANENT
| JSPROP_READONLY
,
121 JS_PropertyStub
, JS_StrictPropertyStub
);
122 NS_ENSURE_TRUE(ok
, NS_ERROR_UNEXPECTED
);
124 propertyHolder
= targetClassObject
;
127 // Walk our member list and install each one in turn on the XBL scope object.
128 if (propertyHolderIsNew
) {
129 for (nsXBLProtoImplMember
* curr
= mMembers
;
131 curr
= curr
->GetNext())
132 curr
->InstallMember(cx
, propertyHolder
);
135 // Now, if we're using a separate XBL scope, enter the compartment of the
136 // bound node and copy exposable properties to the prototype there. This
137 // rewraps them appropriately, which should result in cross-compartment
138 // function wrappers.
139 if (propertyHolder
!= targetClassObject
) {
140 AssertSameCompartment(propertyHolder
, scopeObject
);
141 AssertSameCompartment(targetClassObject
, globalObject
);
142 bool inContentXBLScope
= xpc::IsInContentXBLScope(scopeObject
);
143 for (nsXBLProtoImplMember
* curr
= mMembers
; curr
; curr
= curr
->GetNext()) {
144 if (!inContentXBLScope
|| curr
->ShouldExposeToUntrustedContent()) {
145 JS::Rooted
<jsid
> id(cx
);
146 JS::TwoByteChars
chars(curr
->GetName(), NS_strlen(curr
->GetName()));
147 bool ok
= JS_CharsToId(cx
, chars
, &id
);
148 NS_ENSURE_TRUE(ok
, NS_ERROR_UNEXPECTED
);
151 ok
= JS_HasPropertyById(cx
, propertyHolder
, id
, &found
);
152 NS_ENSURE_TRUE(ok
, NS_ERROR_UNEXPECTED
);
154 // Some members don't install anything in InstallMember (e.g.,
155 // nsXBLProtoImplAnonymousMethod). We need to skip copying in
160 ok
= JS_CopyPropertyFrom(cx
, id
, targetClassObject
, propertyHolder
);
161 NS_ENSURE_TRUE(ok
, NS_ERROR_UNEXPECTED
);
166 // From here on out, work in the scope of the bound element.
167 JSAutoCompartment
ac2(cx
, targetClassObject
);
169 // Install all of our field accessors.
170 for (nsXBLProtoImplField
* curr
= mFields
;
172 curr
= curr
->GetNext())
173 curr
->InstallAccessors(cx
, targetClassObject
);
179 nsXBLProtoImpl::InitTargetObjects(nsXBLPrototypeBinding
* aBinding
,
180 nsIContent
* aBoundElement
,
181 JS::MutableHandle
<JSObject
*> aTargetClassObject
,
186 if (!mPrecompiledMemberHolder
) {
187 rv
= CompilePrototypeMembers(aBinding
); // This is the first time we've ever installed this binding on an element.
188 // We need to go ahead and compile all methods and properties on a class
189 // in our prototype binding.
193 MOZ_ASSERT(mPrecompiledMemberHolder
);
196 nsIDocument
*ownerDoc
= aBoundElement
->OwnerDoc();
197 nsIGlobalObject
*sgo
;
199 if (!(sgo
= ownerDoc
->GetScopeObject())) {
200 return NS_ERROR_UNEXPECTED
;
203 // Because our prototype implementation has a class, we need to build up a corresponding
204 // class for the concrete implementation in the bound document.
206 JS::Rooted
<JSObject
*> global(cx
, sgo
->GetGlobalJSObject());
207 JS::Rooted
<JS::Value
> v(cx
);
209 JSAutoCompartment
ac(cx
, global
);
210 // Make sure the interface object is created before the prototype object
211 // so that XULElement is hidden from content. See bug 909340.
212 bool defineOnGlobal
= dom::XULElementBinding::ConstructorEnabled(cx
, global
);
213 dom::XULElementBinding::GetConstructorObject(cx
, global
, defineOnGlobal
);
215 rv
= nsContentUtils::WrapNative(cx
, aBoundElement
, &v
,
216 /* aAllowWrapping = */ false);
217 NS_ENSURE_SUCCESS(rv
, rv
);
219 JS::Rooted
<JSObject
*> value(cx
, &v
.toObject());
220 JSAutoCompartment
ac2(cx
, value
);
222 // All of the above code was just obtaining the bound element's script object and its immediate
223 // concrete base class. We need to alter the object so that our concrete class is interposed
224 // between the object and its base class. We become the new base class of the object, and the
225 // object's old base class becomes the new class' base class.
226 rv
= aBinding
->InitClass(mClassName
, cx
, value
, aTargetClassObject
, aTargetIsNew
);
231 aBoundElement
->PreserveWrapper(aBoundElement
);
237 nsXBLProtoImpl::CompilePrototypeMembers(nsXBLPrototypeBinding
* aBinding
)
239 // We want to pre-compile our implementation's members against a "prototype context". Then when we actually
240 // bind the prototype to a real xbl instance, we'll clone the pre-compiled JS into the real instance's
242 AutoSafeJSContext cx
;
243 JS::Rooted
<JSObject
*> compilationGlobal(cx
, xpc::CompilationScope());
244 JSAutoCompartment
ac(cx
, compilationGlobal
);
246 mPrecompiledMemberHolder
= JS_NewObjectWithGivenProto(cx
, nullptr, JS::NullPtr(), compilationGlobal
);
247 if (!mPrecompiledMemberHolder
)
248 return NS_ERROR_OUT_OF_MEMORY
;
250 // Now that we have a class object installed, we walk our member list and compile each of our
251 // properties and methods in turn.
252 JS::Rooted
<JSObject
*> rootedHolder(cx
, mPrecompiledMemberHolder
);
253 for (nsXBLProtoImplMember
* curr
= mMembers
;
255 curr
= curr
->GetNext()) {
256 nsresult rv
= curr
->CompileMember(mClassName
, rootedHolder
);
267 nsXBLProtoImpl::LookupMember(JSContext
* aCx
, nsString
& aName
,
268 JS::Handle
<jsid
> aNameAsId
,
269 JS::MutableHandle
<JSPropertyDescriptor
> aDesc
,
270 JS::Handle
<JSObject
*> aClassObject
)
272 for (nsXBLProtoImplMember
* m
= mMembers
; m
; m
= m
->GetNext()) {
273 if (aName
.Equals(m
->GetName())) {
274 return JS_GetPropertyDescriptorById(aCx
, aClassObject
, aNameAsId
, aDesc
);
281 nsXBLProtoImpl::Trace(const TraceCallbacks
& aCallbacks
, void *aClosure
)
283 // If we don't have a class object then we either didn't compile members
284 // or we only have fields, in both cases there are no cycles through our
286 if (!mPrecompiledMemberHolder
) {
290 nsXBLProtoImplMember
*member
;
291 for (member
= mMembers
; member
; member
= member
->GetNext()) {
292 member
->Trace(aCallbacks
, aClosure
);
297 nsXBLProtoImpl::UnlinkJSObjects()
299 if (mPrecompiledMemberHolder
) {
305 nsXBLProtoImpl::FindField(const nsString
& aFieldName
) const
307 for (nsXBLProtoImplField
* f
= mFields
; f
; f
= f
->GetNext()) {
308 if (aFieldName
.Equals(f
->GetName())) {
317 nsXBLProtoImpl::ResolveAllFields(JSContext
*cx
, JS::Handle
<JSObject
*> obj
) const
319 for (nsXBLProtoImplField
* f
= mFields
; f
; f
= f
->GetNext()) {
320 // Using OBJ_LOOKUP_PROPERTY is a pain, since what we have is a
321 // char16_t* for the property name. Let's just use the public API and
323 nsDependentString
name(f
->GetName());
324 JS::Rooted
<JS::Value
> dummy(cx
);
325 if (!::JS_LookupUCProperty(cx
, obj
, name
.get(), name
.Length(), &dummy
)) {
334 nsXBLProtoImpl::UndefineFields(JSContext
*cx
, JS::Handle
<JSObject
*> obj
) const
336 JSAutoRequest
ar(cx
);
337 for (nsXBLProtoImplField
* f
= mFields
; f
; f
= f
->GetNext()) {
338 nsDependentString
name(f
->GetName());
340 const jschar
* s
= name
.get();
342 if (::JS_AlreadyHasOwnUCProperty(cx
, obj
, s
, name
.Length(), &hasProp
) &&
345 ::JS_DeleteUCProperty2(cx
, obj
, s
, name
.Length(), &dummy
);
351 nsXBLProtoImpl::DestroyMembers()
353 MOZ_ASSERT(mPrecompiledMemberHolder
);
357 mConstructor
= nullptr;
358 mDestructor
= nullptr;
362 nsXBLProtoImpl::Read(nsIObjectInputStream
* aStream
,
363 nsXBLPrototypeBinding
* aBinding
)
365 AssertInCompilationScope();
367 // Set up a class object first so that deserialization is possible
368 JS::Rooted
<JSObject
*> global(cx
, JS::CurrentGlobalOrNull(cx
));
369 mPrecompiledMemberHolder
= JS_NewObjectWithGivenProto(cx
, nullptr, JS::NullPtr(), global
);
370 if (!mPrecompiledMemberHolder
)
371 return NS_ERROR_OUT_OF_MEMORY
;
373 nsXBLProtoImplField
* previousField
= nullptr;
374 nsXBLProtoImplMember
* previousMember
= nullptr;
377 XBLBindingSerializeDetails type
;
378 nsresult rv
= aStream
->Read8(&type
);
379 NS_ENSURE_SUCCESS(rv
, rv
);
380 if (type
== XBLBinding_Serialize_NoMoreItems
)
383 switch (type
& XBLBinding_Serialize_Mask
) {
384 case XBLBinding_Serialize_Field
:
386 nsXBLProtoImplField
* field
=
387 new nsXBLProtoImplField(type
& XBLBinding_Serialize_ReadOnly
);
388 rv
= field
->Read(aStream
);
395 previousField
->SetNext(field
);
400 previousField
= field
;
404 case XBLBinding_Serialize_GetterProperty
:
405 case XBLBinding_Serialize_SetterProperty
:
406 case XBLBinding_Serialize_GetterSetterProperty
:
409 nsresult rv
= aStream
->ReadString(name
);
410 NS_ENSURE_SUCCESS(rv
, rv
);
412 nsXBLProtoImplProperty
* prop
=
413 new nsXBLProtoImplProperty(name
.get(), type
& XBLBinding_Serialize_ReadOnly
);
414 rv
= prop
->Read(aStream
, type
& XBLBinding_Serialize_Mask
);
420 previousMember
= AddMember(prop
, previousMember
);
423 case XBLBinding_Serialize_Method
:
426 rv
= aStream
->ReadString(name
);
427 NS_ENSURE_SUCCESS(rv
, rv
);
429 nsXBLProtoImplMethod
* method
= new nsXBLProtoImplMethod(name
.get());
430 rv
= method
->Read(aStream
);
436 previousMember
= AddMember(method
, previousMember
);
439 case XBLBinding_Serialize_Constructor
:
442 rv
= aStream
->ReadString(name
);
443 NS_ENSURE_SUCCESS(rv
, rv
);
445 mConstructor
= new nsXBLProtoImplAnonymousMethod(name
.get());
446 rv
= mConstructor
->Read(aStream
);
449 mConstructor
= nullptr;
453 previousMember
= AddMember(mConstructor
, previousMember
);
456 case XBLBinding_Serialize_Destructor
:
459 rv
= aStream
->ReadString(name
);
460 NS_ENSURE_SUCCESS(rv
, rv
);
462 mDestructor
= new nsXBLProtoImplAnonymousMethod(name
.get());
463 rv
= mDestructor
->Read(aStream
);
466 mDestructor
= nullptr;
470 previousMember
= AddMember(mDestructor
, previousMember
);
474 NS_ERROR("Unexpected binding member type");
483 nsXBLProtoImpl::Write(nsIObjectOutputStream
* aStream
,
484 nsXBLPrototypeBinding
* aBinding
)
488 if (!mPrecompiledMemberHolder
) {
489 rv
= CompilePrototypeMembers(aBinding
);
490 NS_ENSURE_SUCCESS(rv
, rv
);
493 rv
= aStream
->WriteStringZ(mClassName
.get());
494 NS_ENSURE_SUCCESS(rv
, rv
);
496 for (nsXBLProtoImplField
* curr
= mFields
; curr
; curr
= curr
->GetNext()) {
497 rv
= curr
->Write(aStream
);
498 NS_ENSURE_SUCCESS(rv
, rv
);
500 for (nsXBLProtoImplMember
* curr
= mMembers
; curr
; curr
= curr
->GetNext()) {
501 if (curr
== mConstructor
) {
502 rv
= mConstructor
->Write(aStream
, XBLBinding_Serialize_Constructor
);
504 else if (curr
== mDestructor
) {
505 rv
= mDestructor
->Write(aStream
, XBLBinding_Serialize_Destructor
);
508 rv
= curr
->Write(aStream
);
510 NS_ENSURE_SUCCESS(rv
, rv
);
513 return aStream
->Write8(XBLBinding_Serialize_NoMoreItems
);
517 NS_NewXBLProtoImpl(nsXBLPrototypeBinding
* aBinding
,
518 const char16_t
* aClassName
,
519 nsXBLProtoImpl
** aResult
)
521 nsXBLProtoImpl
* impl
= new nsXBLProtoImpl();
523 return NS_ERROR_OUT_OF_MEMORY
;
525 impl
->mClassName
.AssignWithConversion(aClassName
);
527 aBinding
->BindingURI()->GetSpec(impl
->mClassName
);
528 aBinding
->SetImplementation(impl
);