nsresult nsXBLProtoImpl::InstallImplementation(nsXBLPrototypeBinding* aPrototypeBinding, nsXBLBinding* aBinding) { // This function is called to install a concrete implementation on a bound element using // this prototype implementation as a guide. The prototype implementation is compiled lazily, // so for the first bound element that needs a concrete implementation, we also build the // prototype implementation. if (!mMembers && !mFields) // Constructor and destructor also live in mMembers return NS_OK; // Nothing to do, so let's not waste time. // If the way this gets the script context changes, fix // nsXBLProtoImplAnonymousMethod::Execute nsIDocument* document = aBinding->GetBoundElement()->OwnerDoc(); nsCOMPtr<nsIScriptGlobalObject> global = do_QueryInterface(document->GetScopeObject()); if (!global) return NS_OK; nsCOMPtr<nsIScriptContext> context = global->GetContext(); if (!context) return NS_OK; JSContext* cx = context->GetNativeContext(); AutoCxPusher pusher(cx); // InitTarget objects gives us back the JS object that represents the bound element and the // class object in the bound document that represents the concrete version of this implementation. // This function also has the side effect of building up the prototype implementation if it has // not been built already. JS::Rooted<JSObject*> targetClassObject(cx, nullptr); bool targetObjectIsNew = false; nsresult rv = InitTargetObjects(aPrototypeBinding, aBinding->GetBoundElement(), &targetClassObject, &targetObjectIsNew); NS_ENSURE_SUCCESS(rv, rv); // kick out if we were unable to properly intialize our target objects MOZ_ASSERT(targetClassObject); // Stash a strong reference to the JSClass in the binding. aBinding->SetJSClass(nsXBLJSClass::fromJSClass(JS_GetClass(targetClassObject))); // If the prototype already existed, we don't need to install anything. return early. if (!targetObjectIsNew) return NS_OK; // We want to define the canonical set of members in a safe place. If we're // using a separate XBL scope, we want to define them there first (so that // they'll be available for Xray lookups, among other things), and then copy // the properties to the content-side prototype as needed. We don't need to // bother about the field accessors here, since we don't use/support those // for in-content bindings. // First, start by entering the compartment of the XBL scope. This may or may // not be the same compartment as globalObject. JS::Rooted<JSObject*> globalObject(cx, GetGlobalForObjectCrossCompartment(targetClassObject)); JS::Rooted<JSObject*> scopeObject(cx, xpc::GetXBLScopeOrGlobal(cx, globalObject)); NS_ENSURE_TRUE(scopeObject, NS_ERROR_OUT_OF_MEMORY); JSAutoCompartment ac(cx, scopeObject); // If they're different, create our safe holder object in the XBL scope. JS::Rooted<JSObject*> propertyHolder(cx); if (scopeObject != globalObject) { // This is just a property holder, so it doesn't need any special JSClass. propertyHolder = JS_NewObjectWithGivenProto(cx, nullptr, JS::NullPtr(), scopeObject); NS_ENSURE_TRUE(propertyHolder, NS_ERROR_OUT_OF_MEMORY); // Define it as a property on the scopeObject, using the same name used on // the content side. bool ok = JS_DefineProperty(cx, scopeObject, js::GetObjectClass(targetClassObject)->name, JS::ObjectValue(*propertyHolder), JS_PropertyStub, JS_StrictPropertyStub, JSPROP_PERMANENT | JSPROP_READONLY); NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED); } else { propertyHolder = targetClassObject; } // Walk our member list and install each one in turn on the XBL scope object. for (nsXBLProtoImplMember* curr = mMembers; curr; curr = curr->GetNext()) curr->InstallMember(cx, propertyHolder); // Now, if we're using a separate XBL scope, enter the compartment of the // bound node and copy exposable properties to the prototype there. This // rewraps them appropriately, which should result in cross-compartment // function wrappers. if (propertyHolder != targetClassObject) { AssertSameCompartment(propertyHolder, scopeObject); AssertSameCompartment(targetClassObject, globalObject); for (nsXBLProtoImplMember* curr = mMembers; curr; curr = curr->GetNext()) { if (curr->ShouldExposeToUntrustedContent()) { JS::Rooted<jsid> id(cx); JS::TwoByteChars chars(curr->GetName(), NS_strlen(curr->GetName())); bool ok = JS_CharsToId(cx, chars, &id); NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED); JS_CopyPropertyFrom(cx, id, targetClassObject, propertyHolder); NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED); } } } // From here on out, work in the scope of the bound element. JSAutoCompartment ac2(cx, targetClassObject); // Install all of our field accessors. for (nsXBLProtoImplField* curr = mFields; curr; curr = curr->GetNext()) curr->InstallAccessors(cx, targetClassObject); return NS_OK; }
nsresult nsXBLProtoImpl::InstallImplementation(nsXBLPrototypeBinding* aPrototypeBinding, nsXBLBinding* aBinding) { // This function is called to install a concrete implementation on a bound element using // this prototype implementation as a guide. The prototype implementation is compiled lazily, // so for the first bound element that needs a concrete implementation, we also build the // prototype implementation. if (!mMembers && !mFields) // Constructor and destructor also live in mMembers return NS_OK; // Nothing to do, so let's not waste time. // If the way this gets the script context changes, fix // nsXBLProtoImplAnonymousMethod::Execute nsIDocument* document = aBinding->GetBoundElement()->OwnerDoc(); // This sometimes gets called when we have no outer window and if we don't // catch this, we get leaks during crashtests and reftests. if (NS_WARN_IF(!document->GetWindow())) { return NS_OK; } // |propertyHolder| (below) can be an existing object, so in theory we might // hit something that could end up running script. We never want that to // happen here, so we use an AutoJSAPI instead of an AutoEntryScript. dom::AutoJSAPI jsapi; if (NS_WARN_IF(!jsapi.Init(document->GetScopeObject()))) { return NS_OK; } JSContext* cx = jsapi.cx(); // InitTarget objects gives us back the JS object that represents the bound element and the // class object in the bound document that represents the concrete version of this implementation. // This function also has the side effect of building up the prototype implementation if it has // not been built already. JS::Rooted<JSObject*> targetClassObject(cx, nullptr); bool targetObjectIsNew = false; nsresult rv = InitTargetObjects(aPrototypeBinding, aBinding->GetBoundElement(), &targetClassObject, &targetObjectIsNew); NS_ENSURE_SUCCESS(rv, rv); // kick out if we were unable to properly intialize our target objects MOZ_ASSERT(targetClassObject); // If the prototype already existed, we don't need to install anything. return early. if (!targetObjectIsNew) return NS_OK; // We want to define the canonical set of members in a safe place. If we're // using a separate XBL scope, we want to define them there first (so that // they'll be available for Xray lookups, among other things), and then copy // the properties to the content-side prototype as needed. We don't need to // bother about the field accessors here, since we don't use/support those // for in-content bindings. // First, start by entering the compartment of the XBL scope. This may or may // not be the same compartment as globalObject. JSAddonId* addonId = MapURIToAddonID(aPrototypeBinding->BindingURI()); JS::Rooted<JSObject*> globalObject(cx, GetGlobalForObjectCrossCompartment(targetClassObject)); JS::Rooted<JSObject*> scopeObject(cx, xpc::GetScopeForXBLExecution(cx, globalObject, addonId)); NS_ENSURE_TRUE(scopeObject, NS_ERROR_OUT_OF_MEMORY); JSAutoCompartment ac(cx, scopeObject); // Determine the appropriate property holder. // // Note: If |targetIsNew| is false, we'll early-return above. However, that only // tells us if the content-side object is new, which may be the case even if // we've already set up the binding on the XBL side. For example, if we apply // a binding #foo to a <span> when we've already applied it to a <div>, we'll // end up with a different content prototype, but we'll already have a property // holder called |foo| in the XBL scope. Check for that to avoid wasteful and // weird property holder duplication. const char* className = aPrototypeBinding->ClassName().get(); JS::Rooted<JSObject*> propertyHolder(cx); JS::Rooted<JSPropertyDescriptor> existingHolder(cx); if (scopeObject != globalObject && !JS_GetOwnPropertyDescriptor(cx, scopeObject, className, &existingHolder)) { return NS_ERROR_FAILURE; } bool propertyHolderIsNew = !existingHolder.object() || !existingHolder.value().isObject(); if (!propertyHolderIsNew) { propertyHolder = &existingHolder.value().toObject(); } else if (scopeObject != globalObject) { // This is just a property holder, so it doesn't need any special JSClass. propertyHolder = JS_NewObjectWithGivenProto(cx, nullptr, JS::NullPtr(), scopeObject); NS_ENSURE_TRUE(propertyHolder, NS_ERROR_OUT_OF_MEMORY); // Define it as a property on the scopeObject, using the same name used on // the content side. bool ok = JS_DefineProperty(cx, scopeObject, className, propertyHolder, JSPROP_PERMANENT | JSPROP_READONLY, JS_PropertyStub, JS_StrictPropertyStub); NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED); } else { propertyHolder = targetClassObject; } // Walk our member list and install each one in turn on the XBL scope object. if (propertyHolderIsNew) { for (nsXBLProtoImplMember* curr = mMembers; curr; curr = curr->GetNext()) curr->InstallMember(cx, propertyHolder); } // Now, if we're using a separate XBL scope, enter the compartment of the // bound node and copy exposable properties to the prototype there. This // rewraps them appropriately, which should result in cross-compartment // function wrappers. if (propertyHolder != targetClassObject) { AssertSameCompartment(propertyHolder, scopeObject); AssertSameCompartment(targetClassObject, globalObject); bool inContentXBLScope = xpc::IsInContentXBLScope(scopeObject); for (nsXBLProtoImplMember* curr = mMembers; curr; curr = curr->GetNext()) { if (!inContentXBLScope || curr->ShouldExposeToUntrustedContent()) { JS::Rooted<jsid> id(cx); JS::TwoByteChars chars(curr->GetName(), NS_strlen(curr->GetName())); bool ok = JS_CharsToId(cx, chars, &id); NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED); bool found; ok = JS_HasPropertyById(cx, propertyHolder, id, &found); NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED); if (!found) { // Some members don't install anything in InstallMember (e.g., // nsXBLProtoImplAnonymousMethod). We need to skip copying in // those cases. continue; } ok = JS_CopyPropertyFrom(cx, id, targetClassObject, propertyHolder); NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED); } } } // From here on out, work in the scope of the bound element. JSAutoCompartment ac2(cx, targetClassObject); // Install all of our field accessors. for (nsXBLProtoImplField* curr = mFields; curr; curr = curr->GetNext()) curr->InstallAccessors(cx, targetClassObject); return NS_OK; }