/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsIAtom.h" #include "nsString.h" #include "jsapi.h" #include "nsIContent.h" #include "nsXBLProtoImplProperty.h" #include "nsUnicharUtils.h" #include "nsReadableUtils.h" #include "nsJSUtils.h" #include "nsXBLPrototypeBinding.h" #include "nsXBLSerialize.h" #include "xpcpublic.h" using namespace mozilla; using namespace mozilla::dom; nsXBLProtoImplProperty::nsXBLProtoImplProperty(const char16_t* aName, const char16_t* aGetter, const char16_t* aSetter, const char16_t* aReadOnly, uint32_t aLineNumber) : nsXBLProtoImplMember(aName), mJSAttributes(JSPROP_ENUMERATE) #ifdef DEBUG , mIsCompiled(false) #endif { MOZ_COUNT_CTOR(nsXBLProtoImplProperty); if (aReadOnly) { nsAutoString readOnly; readOnly.Assign(*aReadOnly); if (readOnly.LowerCaseEqualsLiteral("true")) mJSAttributes |= JSPROP_READONLY; } if (aGetter) { AppendGetterText(nsDependentString(aGetter)); SetGetterLineNumber(aLineNumber); } if (aSetter) { AppendSetterText(nsDependentString(aSetter)); SetSetterLineNumber(aLineNumber); } } nsXBLProtoImplProperty::nsXBLProtoImplProperty(const char16_t* aName, const bool aIsReadOnly) : nsXBLProtoImplMember(aName), mJSAttributes(JSPROP_ENUMERATE) #ifdef DEBUG , mIsCompiled(false) #endif { MOZ_COUNT_CTOR(nsXBLProtoImplProperty); if (aIsReadOnly) mJSAttributes |= JSPROP_READONLY; } nsXBLProtoImplProperty::~nsXBLProtoImplProperty() { MOZ_COUNT_DTOR(nsXBLProtoImplProperty); if (!mGetter.IsCompiled()) { delete mGetter.GetUncompiled(); } if (!mSetter.IsCompiled()) { delete mSetter.GetUncompiled(); } } void nsXBLProtoImplProperty::EnsureUncompiledText(PropertyOp& aPropertyOp) { if (!aPropertyOp.GetUncompiled()) { nsXBLTextWithLineNumber* text = new nsXBLTextWithLineNumber(); aPropertyOp.SetUncompiled(text); } } void nsXBLProtoImplProperty::AppendGetterText(const nsAString& aText) { NS_PRECONDITION(!mIsCompiled, "Must not be compiled when accessing getter text"); EnsureUncompiledText(mGetter); mGetter.GetUncompiled()->AppendText(aText); } void nsXBLProtoImplProperty::AppendSetterText(const nsAString& aText) { NS_PRECONDITION(!mIsCompiled, "Must not be compiled when accessing setter text"); EnsureUncompiledText(mSetter); mSetter.GetUncompiled()->AppendText(aText); } void nsXBLProtoImplProperty::SetGetterLineNumber(uint32_t aLineNumber) { NS_PRECONDITION(!mIsCompiled, "Must not be compiled when accessing getter text"); EnsureUncompiledText(mGetter); mGetter.GetUncompiled()->SetLineNumber(aLineNumber); } void nsXBLProtoImplProperty::SetSetterLineNumber(uint32_t aLineNumber) { NS_PRECONDITION(!mIsCompiled, "Must not be compiled when accessing setter text"); EnsureUncompiledText(mSetter); mSetter.GetUncompiled()->SetLineNumber(aLineNumber); } const char* gPropertyArgs[] = { "val" }; nsresult nsXBLProtoImplProperty::InstallMember(JSContext *aCx, JS::Handle aTargetClassObject) { NS_PRECONDITION(mIsCompiled, "Should not be installing an uncompiled property"); MOZ_ASSERT(mGetter.IsCompiled() && mSetter.IsCompiled()); MOZ_ASSERT(js::IsObjectInContextCompartment(aTargetClassObject, aCx)); #ifdef DEBUG { JS::Rooted globalObject(aCx, JS_GetGlobalForObject(aCx, aTargetClassObject)); MOZ_ASSERT(xpc::IsInContentXBLScope(globalObject) || xpc::IsInAddonScope(globalObject) || globalObject == xpc::GetXBLScope(aCx, globalObject)); MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx) == globalObject); } #endif JS::Rooted getter(aCx, mGetter.GetJSFunction()); JS::Rooted setter(aCx, mSetter.GetJSFunction()); if (getter || setter) { if (getter) { if (!(getter = JS::CloneFunctionObject(aCx, getter))) return NS_ERROR_OUT_OF_MEMORY; } if (setter) { if (!(setter = JS::CloneFunctionObject(aCx, setter))) return NS_ERROR_OUT_OF_MEMORY; } nsDependentString name(mName); if (!::JS_DefineUCProperty(aCx, aTargetClassObject, static_cast(mName), name.Length(), JS::UndefinedHandleValue, mJSAttributes, JS_DATA_TO_FUNC_PTR(JSNative, getter.get()), JS_DATA_TO_FUNC_PTR(JSNative, setter.get()))) return NS_ERROR_OUT_OF_MEMORY; } return NS_OK; } nsresult nsXBLProtoImplProperty::CompileMember(AutoJSAPI& jsapi, const nsString& aClassStr, JS::Handle aClassObject) { AssertInCompilationScope(); NS_PRECONDITION(!mIsCompiled, "Trying to compile an already-compiled property"); NS_PRECONDITION(aClassObject, "Must have class object to compile"); MOZ_ASSERT(!mGetter.IsCompiled() && !mSetter.IsCompiled()); JSContext *cx = jsapi.cx(); if (!mName) return NS_ERROR_FAILURE; // Without a valid name, we can't install the member. // We have a property. nsresult rv = NS_OK; nsAutoCString functionUri; if (mGetter.GetUncompiled() || mSetter.GetUncompiled()) { functionUri = NS_ConvertUTF16toUTF8(aClassStr); int32_t hash = functionUri.RFindChar('#'); if (hash != kNotFound) { functionUri.Truncate(hash); } } bool deletedGetter = false; nsXBLTextWithLineNumber *getterText = mGetter.GetUncompiled(); if (getterText && getterText->GetText()) { nsDependentString getter(getterText->GetText()); if (!getter.IsEmpty()) { JSAutoCompartment ac(cx, aClassObject); JS::CompileOptions options(cx); options.setFileAndLine(functionUri.get(), getterText->GetLineNumber()) .setVersion(JSVERSION_LATEST); nsCString name = NS_LITERAL_CSTRING("get_") + NS_ConvertUTF16toUTF8(mName); JS::Rooted getterObject(cx); JS::AutoObjectVector emptyVector(cx); rv = nsJSUtils::CompileFunction(jsapi, emptyVector, options, name, 0, nullptr, getter, getterObject.address()); delete getterText; deletedGetter = true; mGetter.SetJSFunction(getterObject); if (mGetter.GetJSFunction() && NS_SUCCEEDED(rv)) { mJSAttributes |= JSPROP_GETTER | JSPROP_SHARED; } if (NS_FAILED(rv)) { mGetter.SetJSFunction(nullptr); mJSAttributes &= ~JSPROP_GETTER; /*chaining to return failure*/ } } } // if getter is not empty if (!deletedGetter) { // Empty getter delete getterText; mGetter.SetJSFunction(nullptr); } if (NS_FAILED(rv)) { // We failed to compile our getter. So either we've set it to null, or // it's still set to the text object. In either case, it's safe to return // the error here, since then we'll be cleaned up as uncompiled and that // will be ok. Going on and compiling the setter and _then_ returning an // error, on the other hand, will try to clean up a compiled setter as // uncompiled and crash. return rv; } bool deletedSetter = false; nsXBLTextWithLineNumber *setterText = mSetter.GetUncompiled(); if (setterText && setterText->GetText()) { nsDependentString setter(setterText->GetText()); if (!setter.IsEmpty()) { JSAutoCompartment ac(cx, aClassObject); JS::CompileOptions options(cx); options.setFileAndLine(functionUri.get(), setterText->GetLineNumber()) .setVersion(JSVERSION_LATEST); nsCString name = NS_LITERAL_CSTRING("set_") + NS_ConvertUTF16toUTF8(mName); JS::Rooted setterObject(cx); JS::AutoObjectVector emptyVector(cx); rv = nsJSUtils::CompileFunction(jsapi, emptyVector, options, name, 1, gPropertyArgs, setter, setterObject.address()); delete setterText; deletedSetter = true; mSetter.SetJSFunction(setterObject); if (mSetter.GetJSFunction() && NS_SUCCEEDED(rv)) { mJSAttributes |= JSPROP_SETTER | JSPROP_SHARED; } if (NS_FAILED(rv)) { mSetter.SetJSFunction(nullptr); mJSAttributes &= ~JSPROP_SETTER; /*chaining to return failure*/ } } } // if setter wasn't empty.... if (!deletedSetter) { // Empty setter delete setterText; mSetter.SetJSFunction(nullptr); } #ifdef DEBUG mIsCompiled = NS_SUCCEEDED(rv); #endif return rv; } void nsXBLProtoImplProperty::Trace(const TraceCallbacks& aCallbacks, void *aClosure) { if (mJSAttributes & JSPROP_GETTER) { aCallbacks.Trace(&mGetter.AsHeapObject(), "mGetter", aClosure); } if (mJSAttributes & JSPROP_SETTER) { aCallbacks.Trace(&mSetter.AsHeapObject(), "mSetter", aClosure); } } nsresult nsXBLProtoImplProperty::Read(nsIObjectInputStream* aStream, XBLBindingSerializeDetails aType) { AssertInCompilationScope(); MOZ_ASSERT(!mIsCompiled); MOZ_ASSERT(!mGetter.GetUncompiled() && !mSetter.GetUncompiled()); AutoJSContext cx; JS::Rooted getterObject(cx); if (aType == XBLBinding_Serialize_GetterProperty || aType == XBLBinding_Serialize_GetterSetterProperty) { nsresult rv = XBL_DeserializeFunction(aStream, &getterObject); NS_ENSURE_SUCCESS(rv, rv); mJSAttributes |= JSPROP_GETTER | JSPROP_SHARED; } mGetter.SetJSFunction(getterObject); JS::Rooted setterObject(cx); if (aType == XBLBinding_Serialize_SetterProperty || aType == XBLBinding_Serialize_GetterSetterProperty) { nsresult rv = XBL_DeserializeFunction(aStream, &setterObject); NS_ENSURE_SUCCESS(rv, rv); mJSAttributes |= JSPROP_SETTER | JSPROP_SHARED; } mSetter.SetJSFunction(setterObject); #ifdef DEBUG mIsCompiled = true; #endif return NS_OK; } nsresult nsXBLProtoImplProperty::Write(nsIObjectOutputStream* aStream) { AssertInCompilationScope(); XBLBindingSerializeDetails type; if (mJSAttributes & JSPROP_GETTER) { type = mJSAttributes & JSPROP_SETTER ? XBLBinding_Serialize_GetterSetterProperty : XBLBinding_Serialize_GetterProperty; } else { type = XBLBinding_Serialize_SetterProperty; } if (mJSAttributes & JSPROP_READONLY) { type |= XBLBinding_Serialize_ReadOnly; } nsresult rv = aStream->Write8(type); NS_ENSURE_SUCCESS(rv, rv); rv = aStream->WriteWStringZ(mName); NS_ENSURE_SUCCESS(rv, rv); // The calls to fromMarkedLocation() below are safe because mSetter and // mGetter are traced by the Trace() method above, and because their values // are never changed after they have been set to a compiled function. MOZ_ASSERT_IF(mJSAttributes & (JSPROP_GETTER | JSPROP_SETTER), mIsCompiled); if (mJSAttributes & JSPROP_GETTER) { JS::Handle function = JS::Handle::fromMarkedLocation(mGetter.AsHeapObject().address()); rv = XBL_SerializeFunction(aStream, function); NS_ENSURE_SUCCESS(rv, rv); } if (mJSAttributes & JSPROP_SETTER) { JS::Handle function = JS::Handle::fromMarkedLocation(mSetter.AsHeapObject().address()); rv = XBL_SerializeFunction(aStream, function); NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; }