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 "nsIDocument.h" michael@0: #include "nsIScriptGlobalObject.h" michael@0: #include "nsUnicharUtils.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsXBLProtoImplMethod.h" michael@0: #include "nsIScriptContext.h" michael@0: #include "nsJSUtils.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsCxPusher.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "nsIXPConnect.h" michael@0: #include "xpcpublic.h" michael@0: #include "nsXBLPrototypeBinding.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: nsXBLProtoImplMethod::nsXBLProtoImplMethod(const char16_t* aName) : michael@0: nsXBLProtoImplMember(aName), michael@0: mMethod() michael@0: { michael@0: MOZ_COUNT_CTOR(nsXBLProtoImplMethod); michael@0: } michael@0: michael@0: nsXBLProtoImplMethod::~nsXBLProtoImplMethod() michael@0: { michael@0: MOZ_COUNT_DTOR(nsXBLProtoImplMethod); michael@0: michael@0: if (!IsCompiled()) { michael@0: delete GetUncompiledMethod(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsXBLProtoImplMethod::AppendBodyText(const nsAString& aText) michael@0: { michael@0: NS_PRECONDITION(!IsCompiled(), michael@0: "Must not be compiled when accessing uncompiled method"); michael@0: michael@0: nsXBLUncompiledMethod* uncompiledMethod = GetUncompiledMethod(); michael@0: if (!uncompiledMethod) { michael@0: uncompiledMethod = new nsXBLUncompiledMethod(); michael@0: if (!uncompiledMethod) michael@0: return; michael@0: SetUncompiledMethod(uncompiledMethod); michael@0: } michael@0: michael@0: uncompiledMethod->AppendBodyText(aText); michael@0: } michael@0: michael@0: void michael@0: nsXBLProtoImplMethod::AddParameter(const nsAString& aText) michael@0: { michael@0: NS_PRECONDITION(!IsCompiled(), michael@0: "Must not be compiled when accessing uncompiled method"); michael@0: michael@0: if (aText.IsEmpty()) { michael@0: NS_WARNING("Empty name attribute in xbl:parameter!"); michael@0: return; michael@0: } michael@0: michael@0: nsXBLUncompiledMethod* uncompiledMethod = GetUncompiledMethod(); michael@0: if (!uncompiledMethod) { michael@0: uncompiledMethod = new nsXBLUncompiledMethod(); michael@0: if (!uncompiledMethod) michael@0: return; michael@0: SetUncompiledMethod(uncompiledMethod); michael@0: } michael@0: michael@0: uncompiledMethod->AddParameter(aText); michael@0: } michael@0: michael@0: void michael@0: nsXBLProtoImplMethod::SetLineNumber(uint32_t aLineNumber) michael@0: { michael@0: NS_PRECONDITION(!IsCompiled(), michael@0: "Must not be compiled when accessing uncompiled method"); michael@0: michael@0: nsXBLUncompiledMethod* uncompiledMethod = GetUncompiledMethod(); michael@0: if (!uncompiledMethod) { michael@0: uncompiledMethod = new nsXBLUncompiledMethod(); michael@0: if (!uncompiledMethod) michael@0: return; michael@0: SetUncompiledMethod(uncompiledMethod); michael@0: } michael@0: michael@0: uncompiledMethod->SetLineNumber(aLineNumber); michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLProtoImplMethod::InstallMember(JSContext* aCx, michael@0: JS::Handle aTargetClassObject) michael@0: { michael@0: NS_PRECONDITION(IsCompiled(), michael@0: "Should not be installing an uncompiled method"); michael@0: MOZ_ASSERT(js::IsObjectInContextCompartment(aTargetClassObject, aCx)); michael@0: 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 jsMethodObject(aCx, GetCompiledMethod()); michael@0: if (jsMethodObject) { michael@0: nsDependentString name(mName); michael@0: michael@0: JS::Rooted method(aCx, JS_CloneFunctionObject(aCx, jsMethodObject, globalObject)); michael@0: NS_ENSURE_TRUE(method, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: JS::Rooted value(aCx, JS::ObjectValue(*method)); michael@0: if (!::JS_DefineUCProperty(aCx, aTargetClassObject, michael@0: static_cast(mName), michael@0: name.Length(), value, michael@0: nullptr, nullptr, JSPROP_ENUMERATE)) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLProtoImplMethod::CompileMember(const nsCString& aClassStr, michael@0: JS::Handle aClassObject) michael@0: { michael@0: AssertInCompilationScope(); michael@0: NS_PRECONDITION(!IsCompiled(), michael@0: "Trying to compile an already-compiled method"); michael@0: NS_PRECONDITION(aClassObject, michael@0: "Must have class object to compile"); michael@0: michael@0: nsXBLUncompiledMethod* uncompiledMethod = GetUncompiledMethod(); michael@0: michael@0: // No parameters or body was supplied, so don't install method. michael@0: if (!uncompiledMethod) { michael@0: // Early return after which we consider ourselves compiled. michael@0: SetCompiledMethod(nullptr); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Don't install method if no name was supplied. michael@0: if (!mName) { michael@0: delete uncompiledMethod; michael@0: michael@0: // Early return after which we consider ourselves compiled. michael@0: SetCompiledMethod(nullptr); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // We have a method. michael@0: // Allocate an array for our arguments. michael@0: int32_t paramCount = uncompiledMethod->GetParameterCount(); michael@0: char** args = nullptr; michael@0: if (paramCount > 0) { michael@0: args = new char*[paramCount]; michael@0: if (!args) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: // Add our parameters to our args array. michael@0: int32_t argPos = 0; michael@0: for (nsXBLParameter* curr = uncompiledMethod->mParameters; michael@0: curr; michael@0: curr = curr->mNext) { michael@0: args[argPos] = curr->mName; michael@0: argPos++; michael@0: } michael@0: } michael@0: michael@0: // Get the body michael@0: nsDependentString body; michael@0: char16_t *bodyText = uncompiledMethod->mBodyText.GetText(); michael@0: if (bodyText) michael@0: body.Rebind(bodyText); michael@0: michael@0: // Now that we have a body and args, compile the function michael@0: // and then define it. michael@0: NS_ConvertUTF16toUTF8 cname(mName); michael@0: nsAutoCString 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: AutoJSContext cx; michael@0: JSAutoCompartment ac(cx, aClassObject); michael@0: JS::CompileOptions options(cx); michael@0: options.setFileAndLine(functionUri.get(), michael@0: uncompiledMethod->mBodyText.GetLineNumber()) michael@0: .setVersion(JSVERSION_LATEST); michael@0: JS::Rooted methodObject(cx); michael@0: nsresult rv = nsJSUtils::CompileFunction(cx, JS::NullPtr(), options, cname, michael@0: paramCount, michael@0: const_cast(args), michael@0: body, methodObject.address()); michael@0: michael@0: // Destroy our uncompiled method and delete our arg list. michael@0: delete uncompiledMethod; michael@0: delete [] args; michael@0: if (NS_FAILED(rv)) { michael@0: SetUncompiledMethod(nullptr); michael@0: return rv; michael@0: } michael@0: michael@0: SetCompiledMethod(methodObject); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsXBLProtoImplMethod::Trace(const TraceCallbacks& aCallbacks, void *aClosure) michael@0: { michael@0: if (IsCompiled() && GetCompiledMethodPreserveColor()) { michael@0: aCallbacks.Trace(&mMethod.AsHeapObject(), "mMethod", aClosure); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLProtoImplMethod::Read(nsIObjectInputStream* aStream) michael@0: { michael@0: AssertInCompilationScope(); michael@0: MOZ_ASSERT(!IsCompiled() && !GetUncompiledMethod()); michael@0: michael@0: AutoJSContext cx; michael@0: JS::Rooted methodObject(cx); michael@0: nsresult rv = XBL_DeserializeFunction(aStream, &methodObject); michael@0: if (NS_FAILED(rv)) { michael@0: SetUncompiledMethod(nullptr); michael@0: return rv; michael@0: } michael@0: michael@0: SetCompiledMethod(methodObject); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLProtoImplMethod::Write(nsIObjectOutputStream* aStream) michael@0: { michael@0: AssertInCompilationScope(); michael@0: MOZ_ASSERT(IsCompiled()); michael@0: if (GetCompiledMethodPreserveColor()) { michael@0: nsresult rv = aStream->Write8(XBLBinding_Serialize_Method); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aStream->WriteWStringZ(mName); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Calling fromMarkedLocation() is safe because mMethod is traced by the michael@0: // Trace() method above, and because its value is never changed after it has michael@0: // been set to a compiled method. michael@0: JS::Handle method = michael@0: JS::Handle::fromMarkedLocation(mMethod.AsHeapObject().address()); michael@0: return XBL_SerializeFunction(aStream, method); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLProtoImplAnonymousMethod::Execute(nsIContent* aBoundElement) michael@0: { michael@0: NS_PRECONDITION(IsCompiled(), "Can't execute uncompiled method"); michael@0: michael@0: if (!GetCompiledMethod()) { michael@0: // Nothing to do here michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Get the script context the same way michael@0: // nsXBLProtoImpl::InstallImplementation does. michael@0: nsIDocument* document = aBoundElement->OwnerDoc(); michael@0: michael@0: nsCOMPtr global = michael@0: do_QueryInterface(document->GetWindow()); michael@0: if (!global) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr context = global->GetContext(); michael@0: if (!context) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsAutoMicroTask mt; michael@0: michael@0: AutoPushJSContext cx(context->GetNativeContext()); michael@0: michael@0: JS::Rooted globalObject(cx, global->GetGlobalJSObject()); michael@0: michael@0: JS::Rooted v(cx); michael@0: nsresult rv = nsContentUtils::WrapNative(cx, aBoundElement, &v); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Use nsCxPusher to make sure we call ScriptEvaluated when we're done. michael@0: // michael@0: // Make sure to do this before entering the compartment, since pushing Push() michael@0: // may call JS_SaveFrameChain(), which puts us back in an unentered state. michael@0: nsCxPusher pusher; michael@0: if (!pusher.Push(aBoundElement)) michael@0: return NS_ERROR_UNEXPECTED; michael@0: MOZ_ASSERT(cx == nsContentUtils::GetCurrentJSContext()); michael@0: michael@0: JS::Rooted thisObject(cx, &v.toObject()); michael@0: JS::Rooted scopeObject(cx, xpc::GetXBLScopeOrGlobal(cx, globalObject)); michael@0: NS_ENSURE_TRUE(scopeObject, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: JSAutoCompartment ac(cx, scopeObject); michael@0: if (!JS_WrapObject(cx, &thisObject)) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: // Clone the function object, using thisObject as the parent so "this" is in michael@0: // the scope chain of the resulting function (for backwards compat to the michael@0: // days when this was an event handler). michael@0: JS::Rooted jsMethodObject(cx, GetCompiledMethod()); michael@0: JS::Rooted method(cx, ::JS_CloneFunctionObject(cx, jsMethodObject, thisObject)); michael@0: if (!method) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: // Now call the method michael@0: michael@0: // Check whether script is enabled. michael@0: bool scriptAllowed = nsContentUtils::GetSecurityManager()-> michael@0: ScriptAllowed(js::GetGlobalForObjectCrossCompartment(method)); michael@0: michael@0: bool ok = true; michael@0: if (scriptAllowed) { michael@0: JS::Rooted retval(cx); michael@0: JS::Rooted methodVal(cx, JS::ObjectValue(*method)); michael@0: ok = ::JS::Call(cx, thisObject, methodVal, JS::HandleValueArray::empty(), &retval); michael@0: } michael@0: michael@0: if (!ok) { michael@0: // If a constructor or destructor threw an exception, it doesn't stop michael@0: // anything else. We just report it. Note that we need to set aside the michael@0: // frame chain here, since the constructor invocation is not related to michael@0: // whatever is on the stack right now, really. michael@0: nsJSUtils::ReportPendingException(cx); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLProtoImplAnonymousMethod::Write(nsIObjectOutputStream* aStream, michael@0: XBLBindingSerializeDetails aType) michael@0: { michael@0: AssertInCompilationScope(); michael@0: MOZ_ASSERT(IsCompiled()); michael@0: if (GetCompiledMethodPreserveColor()) { michael@0: nsresult rv = aStream->Write8(aType); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aStream->WriteWStringZ(mName); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Calling fromMarkedLocation() is safe because mMethod is traced by the michael@0: // Trace() method above, and because its value is never changed after it has michael@0: // been set to a compiled method. michael@0: JS::Handle method = michael@0: JS::Handle::fromMarkedLocation(mMethod.AsHeapObject().address()); michael@0: rv = XBL_SerializeFunction(aStream, method); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: }