1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/xbl/nsXBLProtoImpl.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,495 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "mozilla/DebugOnly.h" 1.10 + 1.11 +#include "nsXBLProtoImpl.h" 1.12 +#include "nsIContent.h" 1.13 +#include "nsIDocument.h" 1.14 +#include "nsContentUtils.h" 1.15 +#include "nsCxPusher.h" 1.16 +#include "nsIScriptGlobalObject.h" 1.17 +#include "nsIScriptContext.h" 1.18 +#include "nsIXPConnect.h" 1.19 +#include "nsIServiceManager.h" 1.20 +#include "nsIDOMNode.h" 1.21 +#include "nsXBLPrototypeBinding.h" 1.22 +#include "nsXBLProtoImplProperty.h" 1.23 +#include "nsIURI.h" 1.24 +#include "mozilla/dom/XULElementBinding.h" 1.25 +#include "xpcpublic.h" 1.26 +#include "js/CharacterEncoding.h" 1.27 + 1.28 +using namespace mozilla; 1.29 +using js::GetGlobalForObjectCrossCompartment; 1.30 +using js::AssertSameCompartment; 1.31 + 1.32 +nsresult 1.33 +nsXBLProtoImpl::InstallImplementation(nsXBLPrototypeBinding* aPrototypeBinding, 1.34 + nsXBLBinding* aBinding) 1.35 +{ 1.36 + // This function is called to install a concrete implementation on a bound element using 1.37 + // this prototype implementation as a guide. The prototype implementation is compiled lazily, 1.38 + // so for the first bound element that needs a concrete implementation, we also build the 1.39 + // prototype implementation. 1.40 + if (!mMembers && !mFields) // Constructor and destructor also live in mMembers 1.41 + return NS_OK; // Nothing to do, so let's not waste time. 1.42 + 1.43 + // If the way this gets the script context changes, fix 1.44 + // nsXBLProtoImplAnonymousMethod::Execute 1.45 + nsIDocument* document = aBinding->GetBoundElement()->OwnerDoc(); 1.46 + 1.47 + nsCOMPtr<nsIScriptGlobalObject> global = do_QueryInterface(document->GetScopeObject()); 1.48 + if (!global) return NS_OK; 1.49 + 1.50 + nsCOMPtr<nsIScriptContext> context = global->GetContext(); 1.51 + if (!context) return NS_OK; 1.52 + JSContext* cx = context->GetNativeContext(); 1.53 + AutoCxPusher pusher(cx); 1.54 + 1.55 + // InitTarget objects gives us back the JS object that represents the bound element and the 1.56 + // class object in the bound document that represents the concrete version of this implementation. 1.57 + // This function also has the side effect of building up the prototype implementation if it has 1.58 + // not been built already. 1.59 + JS::Rooted<JSObject*> targetClassObject(cx, nullptr); 1.60 + bool targetObjectIsNew = false; 1.61 + nsresult rv = InitTargetObjects(aPrototypeBinding, 1.62 + aBinding->GetBoundElement(), 1.63 + &targetClassObject, 1.64 + &targetObjectIsNew); 1.65 + NS_ENSURE_SUCCESS(rv, rv); // kick out if we were unable to properly intialize our target objects 1.66 + MOZ_ASSERT(targetClassObject); 1.67 + 1.68 + // If the prototype already existed, we don't need to install anything. return early. 1.69 + if (!targetObjectIsNew) 1.70 + return NS_OK; 1.71 + 1.72 + // We want to define the canonical set of members in a safe place. If we're 1.73 + // using a separate XBL scope, we want to define them there first (so that 1.74 + // they'll be available for Xray lookups, among other things), and then copy 1.75 + // the properties to the content-side prototype as needed. We don't need to 1.76 + // bother about the field accessors here, since we don't use/support those 1.77 + // for in-content bindings. 1.78 + 1.79 + // First, start by entering the compartment of the XBL scope. This may or may 1.80 + // not be the same compartment as globalObject. 1.81 + JS::Rooted<JSObject*> globalObject(cx, 1.82 + GetGlobalForObjectCrossCompartment(targetClassObject)); 1.83 + JS::Rooted<JSObject*> scopeObject(cx, xpc::GetXBLScopeOrGlobal(cx, globalObject)); 1.84 + NS_ENSURE_TRUE(scopeObject, NS_ERROR_OUT_OF_MEMORY); 1.85 + JSAutoCompartment ac(cx, scopeObject); 1.86 + 1.87 + // If they're different, create our safe holder object in the XBL scope. 1.88 + JS::Rooted<JSObject*> propertyHolder(cx); 1.89 + if (scopeObject != globalObject) { 1.90 + 1.91 + // This is just a property holder, so it doesn't need any special JSClass. 1.92 + propertyHolder = JS_NewObjectWithGivenProto(cx, nullptr, JS::NullPtr(), scopeObject); 1.93 + NS_ENSURE_TRUE(propertyHolder, NS_ERROR_OUT_OF_MEMORY); 1.94 + 1.95 + // Define it as a property on the scopeObject, using the same name used on 1.96 + // the content side. 1.97 + bool ok = JS_DefineProperty(cx, scopeObject, aPrototypeBinding->ClassName().get(), 1.98 + propertyHolder, JSPROP_PERMANENT | JSPROP_READONLY, 1.99 + JS_PropertyStub, JS_StrictPropertyStub); 1.100 + NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED); 1.101 + } else { 1.102 + propertyHolder = targetClassObject; 1.103 + } 1.104 + 1.105 + // Walk our member list and install each one in turn on the XBL scope object. 1.106 + for (nsXBLProtoImplMember* curr = mMembers; 1.107 + curr; 1.108 + curr = curr->GetNext()) 1.109 + curr->InstallMember(cx, propertyHolder); 1.110 + 1.111 + // Now, if we're using a separate XBL scope, enter the compartment of the 1.112 + // bound node and copy exposable properties to the prototype there. This 1.113 + // rewraps them appropriately, which should result in cross-compartment 1.114 + // function wrappers. 1.115 + if (propertyHolder != targetClassObject) { 1.116 + AssertSameCompartment(propertyHolder, scopeObject); 1.117 + AssertSameCompartment(targetClassObject, globalObject); 1.118 + for (nsXBLProtoImplMember* curr = mMembers; curr; curr = curr->GetNext()) { 1.119 + if (curr->ShouldExposeToUntrustedContent()) { 1.120 + JS::Rooted<jsid> id(cx); 1.121 + JS::TwoByteChars chars(curr->GetName(), NS_strlen(curr->GetName())); 1.122 + bool ok = JS_CharsToId(cx, chars, &id); 1.123 + NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED); 1.124 + JS_CopyPropertyFrom(cx, id, targetClassObject, propertyHolder); 1.125 + NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED); 1.126 + } 1.127 + } 1.128 + } 1.129 + 1.130 + // From here on out, work in the scope of the bound element. 1.131 + JSAutoCompartment ac2(cx, targetClassObject); 1.132 + 1.133 + // Install all of our field accessors. 1.134 + for (nsXBLProtoImplField* curr = mFields; 1.135 + curr; 1.136 + curr = curr->GetNext()) 1.137 + curr->InstallAccessors(cx, targetClassObject); 1.138 + 1.139 + return NS_OK; 1.140 +} 1.141 + 1.142 +nsresult 1.143 +nsXBLProtoImpl::InitTargetObjects(nsXBLPrototypeBinding* aBinding, 1.144 + nsIContent* aBoundElement, 1.145 + JS::MutableHandle<JSObject*> aTargetClassObject, 1.146 + bool* aTargetIsNew) 1.147 +{ 1.148 + nsresult rv = NS_OK; 1.149 + 1.150 + if (!mPrecompiledMemberHolder) { 1.151 + rv = CompilePrototypeMembers(aBinding); // This is the first time we've ever installed this binding on an element. 1.152 + // We need to go ahead and compile all methods and properties on a class 1.153 + // in our prototype binding. 1.154 + if (NS_FAILED(rv)) 1.155 + return rv; 1.156 + 1.157 + MOZ_ASSERT(mPrecompiledMemberHolder); 1.158 + } 1.159 + 1.160 + nsIDocument *ownerDoc = aBoundElement->OwnerDoc(); 1.161 + nsIGlobalObject *sgo; 1.162 + 1.163 + if (!(sgo = ownerDoc->GetScopeObject())) { 1.164 + return NS_ERROR_UNEXPECTED; 1.165 + } 1.166 + 1.167 + // Because our prototype implementation has a class, we need to build up a corresponding 1.168 + // class for the concrete implementation in the bound document. 1.169 + AutoJSContext cx; 1.170 + JS::Rooted<JSObject*> global(cx, sgo->GetGlobalJSObject()); 1.171 + JS::Rooted<JS::Value> v(cx); 1.172 + 1.173 + JSAutoCompartment ac(cx, global); 1.174 + // Make sure the interface object is created before the prototype object 1.175 + // so that XULElement is hidden from content. See bug 909340. 1.176 + bool defineOnGlobal = dom::XULElementBinding::ConstructorEnabled(cx, global); 1.177 + dom::XULElementBinding::GetConstructorObject(cx, global, defineOnGlobal); 1.178 + 1.179 + rv = nsContentUtils::WrapNative(cx, aBoundElement, &v, 1.180 + /* aAllowWrapping = */ false); 1.181 + NS_ENSURE_SUCCESS(rv, rv); 1.182 + 1.183 + JS::Rooted<JSObject*> value(cx, &v.toObject()); 1.184 + JSAutoCompartment ac2(cx, value); 1.185 + 1.186 + // All of the above code was just obtaining the bound element's script object and its immediate 1.187 + // concrete base class. We need to alter the object so that our concrete class is interposed 1.188 + // between the object and its base class. We become the new base class of the object, and the 1.189 + // object's old base class becomes the new class' base class. 1.190 + rv = aBinding->InitClass(mClassName, cx, value, aTargetClassObject, aTargetIsNew); 1.191 + if (NS_FAILED(rv)) { 1.192 + return rv; 1.193 + } 1.194 + 1.195 + aBoundElement->PreserveWrapper(aBoundElement); 1.196 + 1.197 + return rv; 1.198 +} 1.199 + 1.200 +nsresult 1.201 +nsXBLProtoImpl::CompilePrototypeMembers(nsXBLPrototypeBinding* aBinding) 1.202 +{ 1.203 + // We want to pre-compile our implementation's members against a "prototype context". Then when we actually 1.204 + // bind the prototype to a real xbl instance, we'll clone the pre-compiled JS into the real instance's 1.205 + // context. 1.206 + AutoSafeJSContext cx; 1.207 + JS::Rooted<JSObject*> compilationGlobal(cx, xpc::GetCompilationScope()); 1.208 + NS_ENSURE_TRUE(compilationGlobal, NS_ERROR_UNEXPECTED); 1.209 + JSAutoCompartment ac(cx, compilationGlobal); 1.210 + 1.211 + mPrecompiledMemberHolder = JS_NewObjectWithGivenProto(cx, nullptr, JS::NullPtr(), compilationGlobal); 1.212 + if (!mPrecompiledMemberHolder) 1.213 + return NS_ERROR_OUT_OF_MEMORY; 1.214 + 1.215 + // Now that we have a class object installed, we walk our member list and compile each of our 1.216 + // properties and methods in turn. 1.217 + JS::Rooted<JSObject*> rootedHolder(cx, mPrecompiledMemberHolder); 1.218 + for (nsXBLProtoImplMember* curr = mMembers; 1.219 + curr; 1.220 + curr = curr->GetNext()) { 1.221 + nsresult rv = curr->CompileMember(mClassName, rootedHolder); 1.222 + if (NS_FAILED(rv)) { 1.223 + DestroyMembers(); 1.224 + return rv; 1.225 + } 1.226 + } 1.227 + 1.228 + return NS_OK; 1.229 +} 1.230 + 1.231 +bool 1.232 +nsXBLProtoImpl::LookupMember(JSContext* aCx, nsString& aName, 1.233 + JS::Handle<jsid> aNameAsId, 1.234 + JS::MutableHandle<JSPropertyDescriptor> aDesc, 1.235 + JS::Handle<JSObject*> aClassObject) 1.236 +{ 1.237 + for (nsXBLProtoImplMember* m = mMembers; m; m = m->GetNext()) { 1.238 + if (aName.Equals(m->GetName())) { 1.239 + return JS_GetPropertyDescriptorById(aCx, aClassObject, aNameAsId, aDesc); 1.240 + } 1.241 + } 1.242 + return true; 1.243 +} 1.244 + 1.245 +void 1.246 +nsXBLProtoImpl::Trace(const TraceCallbacks& aCallbacks, void *aClosure) 1.247 +{ 1.248 + // If we don't have a class object then we either didn't compile members 1.249 + // or we only have fields, in both cases there are no cycles through our 1.250 + // members. 1.251 + if (!mPrecompiledMemberHolder) { 1.252 + return; 1.253 + } 1.254 + 1.255 + nsXBLProtoImplMember *member; 1.256 + for (member = mMembers; member; member = member->GetNext()) { 1.257 + member->Trace(aCallbacks, aClosure); 1.258 + } 1.259 +} 1.260 + 1.261 +void 1.262 +nsXBLProtoImpl::UnlinkJSObjects() 1.263 +{ 1.264 + if (mPrecompiledMemberHolder) { 1.265 + DestroyMembers(); 1.266 + } 1.267 +} 1.268 + 1.269 +nsXBLProtoImplField* 1.270 +nsXBLProtoImpl::FindField(const nsString& aFieldName) const 1.271 +{ 1.272 + for (nsXBLProtoImplField* f = mFields; f; f = f->GetNext()) { 1.273 + if (aFieldName.Equals(f->GetName())) { 1.274 + return f; 1.275 + } 1.276 + } 1.277 + 1.278 + return nullptr; 1.279 +} 1.280 + 1.281 +bool 1.282 +nsXBLProtoImpl::ResolveAllFields(JSContext *cx, JS::Handle<JSObject*> obj) const 1.283 +{ 1.284 + for (nsXBLProtoImplField* f = mFields; f; f = f->GetNext()) { 1.285 + // Using OBJ_LOOKUP_PROPERTY is a pain, since what we have is a 1.286 + // char16_t* for the property name. Let's just use the public API and 1.287 + // all. 1.288 + nsDependentString name(f->GetName()); 1.289 + JS::Rooted<JS::Value> dummy(cx); 1.290 + if (!::JS_LookupUCProperty(cx, obj, name.get(), name.Length(), &dummy)) { 1.291 + return false; 1.292 + } 1.293 + } 1.294 + 1.295 + return true; 1.296 +} 1.297 + 1.298 +void 1.299 +nsXBLProtoImpl::UndefineFields(JSContext *cx, JS::Handle<JSObject*> obj) const 1.300 +{ 1.301 + JSAutoRequest ar(cx); 1.302 + for (nsXBLProtoImplField* f = mFields; f; f = f->GetNext()) { 1.303 + nsDependentString name(f->GetName()); 1.304 + 1.305 + const jschar* s = name.get(); 1.306 + bool hasProp; 1.307 + if (::JS_AlreadyHasOwnUCProperty(cx, obj, s, name.Length(), &hasProp) && 1.308 + hasProp) { 1.309 + bool dummy; 1.310 + ::JS_DeleteUCProperty2(cx, obj, s, name.Length(), &dummy); 1.311 + } 1.312 + } 1.313 +} 1.314 + 1.315 +void 1.316 +nsXBLProtoImpl::DestroyMembers() 1.317 +{ 1.318 + MOZ_ASSERT(mPrecompiledMemberHolder); 1.319 + 1.320 + delete mMembers; 1.321 + mMembers = nullptr; 1.322 + mConstructor = nullptr; 1.323 + mDestructor = nullptr; 1.324 +} 1.325 + 1.326 +nsresult 1.327 +nsXBLProtoImpl::Read(nsIObjectInputStream* aStream, 1.328 + nsXBLPrototypeBinding* aBinding) 1.329 +{ 1.330 + AssertInCompilationScope(); 1.331 + AutoJSContext cx; 1.332 + // Set up a class object first so that deserialization is possible 1.333 + JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx)); 1.334 + mPrecompiledMemberHolder = JS_NewObjectWithGivenProto(cx, nullptr, JS::NullPtr(), global); 1.335 + if (!mPrecompiledMemberHolder) 1.336 + return NS_ERROR_OUT_OF_MEMORY; 1.337 + 1.338 + nsXBLProtoImplField* previousField = nullptr; 1.339 + nsXBLProtoImplMember* previousMember = nullptr; 1.340 + 1.341 + do { 1.342 + XBLBindingSerializeDetails type; 1.343 + nsresult rv = aStream->Read8(&type); 1.344 + NS_ENSURE_SUCCESS(rv, rv); 1.345 + if (type == XBLBinding_Serialize_NoMoreItems) 1.346 + break; 1.347 + 1.348 + switch (type & XBLBinding_Serialize_Mask) { 1.349 + case XBLBinding_Serialize_Field: 1.350 + { 1.351 + nsXBLProtoImplField* field = 1.352 + new nsXBLProtoImplField(type & XBLBinding_Serialize_ReadOnly); 1.353 + rv = field->Read(aStream); 1.354 + if (NS_FAILED(rv)) { 1.355 + delete field; 1.356 + return rv; 1.357 + } 1.358 + 1.359 + if (previousField) { 1.360 + previousField->SetNext(field); 1.361 + } 1.362 + else { 1.363 + mFields = field; 1.364 + } 1.365 + previousField = field; 1.366 + 1.367 + break; 1.368 + } 1.369 + case XBLBinding_Serialize_GetterProperty: 1.370 + case XBLBinding_Serialize_SetterProperty: 1.371 + case XBLBinding_Serialize_GetterSetterProperty: 1.372 + { 1.373 + nsAutoString name; 1.374 + nsresult rv = aStream->ReadString(name); 1.375 + NS_ENSURE_SUCCESS(rv, rv); 1.376 + 1.377 + nsXBLProtoImplProperty* prop = 1.378 + new nsXBLProtoImplProperty(name.get(), type & XBLBinding_Serialize_ReadOnly); 1.379 + rv = prop->Read(aStream, type & XBLBinding_Serialize_Mask); 1.380 + if (NS_FAILED(rv)) { 1.381 + delete prop; 1.382 + return rv; 1.383 + } 1.384 + 1.385 + previousMember = AddMember(prop, previousMember); 1.386 + break; 1.387 + } 1.388 + case XBLBinding_Serialize_Method: 1.389 + { 1.390 + nsAutoString name; 1.391 + rv = aStream->ReadString(name); 1.392 + NS_ENSURE_SUCCESS(rv, rv); 1.393 + 1.394 + nsXBLProtoImplMethod* method = new nsXBLProtoImplMethod(name.get()); 1.395 + rv = method->Read(aStream); 1.396 + if (NS_FAILED(rv)) { 1.397 + delete method; 1.398 + return rv; 1.399 + } 1.400 + 1.401 + previousMember = AddMember(method, previousMember); 1.402 + break; 1.403 + } 1.404 + case XBLBinding_Serialize_Constructor: 1.405 + { 1.406 + nsAutoString name; 1.407 + rv = aStream->ReadString(name); 1.408 + NS_ENSURE_SUCCESS(rv, rv); 1.409 + 1.410 + mConstructor = new nsXBLProtoImplAnonymousMethod(name.get()); 1.411 + rv = mConstructor->Read(aStream); 1.412 + if (NS_FAILED(rv)) { 1.413 + delete mConstructor; 1.414 + mConstructor = nullptr; 1.415 + return rv; 1.416 + } 1.417 + 1.418 + previousMember = AddMember(mConstructor, previousMember); 1.419 + break; 1.420 + } 1.421 + case XBLBinding_Serialize_Destructor: 1.422 + { 1.423 + nsAutoString name; 1.424 + rv = aStream->ReadString(name); 1.425 + NS_ENSURE_SUCCESS(rv, rv); 1.426 + 1.427 + mDestructor = new nsXBLProtoImplAnonymousMethod(name.get()); 1.428 + rv = mDestructor->Read(aStream); 1.429 + if (NS_FAILED(rv)) { 1.430 + delete mDestructor; 1.431 + mDestructor = nullptr; 1.432 + return rv; 1.433 + } 1.434 + 1.435 + previousMember = AddMember(mDestructor, previousMember); 1.436 + break; 1.437 + } 1.438 + default: 1.439 + NS_ERROR("Unexpected binding member type"); 1.440 + break; 1.441 + } 1.442 + } while (1); 1.443 + 1.444 + return NS_OK; 1.445 +} 1.446 + 1.447 +nsresult 1.448 +nsXBLProtoImpl::Write(nsIObjectOutputStream* aStream, 1.449 + nsXBLPrototypeBinding* aBinding) 1.450 +{ 1.451 + nsresult rv; 1.452 + 1.453 + if (!mPrecompiledMemberHolder) { 1.454 + rv = CompilePrototypeMembers(aBinding); 1.455 + NS_ENSURE_SUCCESS(rv, rv); 1.456 + } 1.457 + 1.458 + rv = aStream->WriteStringZ(mClassName.get()); 1.459 + NS_ENSURE_SUCCESS(rv, rv); 1.460 + 1.461 + for (nsXBLProtoImplField* curr = mFields; curr; curr = curr->GetNext()) { 1.462 + rv = curr->Write(aStream); 1.463 + NS_ENSURE_SUCCESS(rv, rv); 1.464 + } 1.465 + for (nsXBLProtoImplMember* curr = mMembers; curr; curr = curr->GetNext()) { 1.466 + if (curr == mConstructor) { 1.467 + rv = mConstructor->Write(aStream, XBLBinding_Serialize_Constructor); 1.468 + } 1.469 + else if (curr == mDestructor) { 1.470 + rv = mDestructor->Write(aStream, XBLBinding_Serialize_Destructor); 1.471 + } 1.472 + else { 1.473 + rv = curr->Write(aStream); 1.474 + } 1.475 + NS_ENSURE_SUCCESS(rv, rv); 1.476 + } 1.477 + 1.478 + return aStream->Write8(XBLBinding_Serialize_NoMoreItems); 1.479 +} 1.480 + 1.481 +nsresult 1.482 +NS_NewXBLProtoImpl(nsXBLPrototypeBinding* aBinding, 1.483 + const char16_t* aClassName, 1.484 + nsXBLProtoImpl** aResult) 1.485 +{ 1.486 + nsXBLProtoImpl* impl = new nsXBLProtoImpl(); 1.487 + if (!impl) 1.488 + return NS_ERROR_OUT_OF_MEMORY; 1.489 + if (aClassName) 1.490 + impl->mClassName.AssignWithConversion(aClassName); 1.491 + else 1.492 + aBinding->BindingURI()->GetSpec(impl->mClassName); 1.493 + aBinding->SetImplementation(impl); 1.494 + *aResult = impl; 1.495 + 1.496 + return NS_OK; 1.497 +} 1.498 +