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