1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/xbl/nsXBLProtoImplMethod.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,375 @@ 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 "nsIAtom.h" 1.10 +#include "nsString.h" 1.11 +#include "jsapi.h" 1.12 +#include "nsIContent.h" 1.13 +#include "nsIDocument.h" 1.14 +#include "nsIScriptGlobalObject.h" 1.15 +#include "nsUnicharUtils.h" 1.16 +#include "nsReadableUtils.h" 1.17 +#include "nsXBLProtoImplMethod.h" 1.18 +#include "nsIScriptContext.h" 1.19 +#include "nsJSUtils.h" 1.20 +#include "nsContentUtils.h" 1.21 +#include "nsCxPusher.h" 1.22 +#include "nsIScriptSecurityManager.h" 1.23 +#include "nsIXPConnect.h" 1.24 +#include "xpcpublic.h" 1.25 +#include "nsXBLPrototypeBinding.h" 1.26 + 1.27 +using namespace mozilla; 1.28 + 1.29 +nsXBLProtoImplMethod::nsXBLProtoImplMethod(const char16_t* aName) : 1.30 + nsXBLProtoImplMember(aName), 1.31 + mMethod() 1.32 +{ 1.33 + MOZ_COUNT_CTOR(nsXBLProtoImplMethod); 1.34 +} 1.35 + 1.36 +nsXBLProtoImplMethod::~nsXBLProtoImplMethod() 1.37 +{ 1.38 + MOZ_COUNT_DTOR(nsXBLProtoImplMethod); 1.39 + 1.40 + if (!IsCompiled()) { 1.41 + delete GetUncompiledMethod(); 1.42 + } 1.43 +} 1.44 + 1.45 +void 1.46 +nsXBLProtoImplMethod::AppendBodyText(const nsAString& aText) 1.47 +{ 1.48 + NS_PRECONDITION(!IsCompiled(), 1.49 + "Must not be compiled when accessing uncompiled method"); 1.50 + 1.51 + nsXBLUncompiledMethod* uncompiledMethod = GetUncompiledMethod(); 1.52 + if (!uncompiledMethod) { 1.53 + uncompiledMethod = new nsXBLUncompiledMethod(); 1.54 + if (!uncompiledMethod) 1.55 + return; 1.56 + SetUncompiledMethod(uncompiledMethod); 1.57 + } 1.58 + 1.59 + uncompiledMethod->AppendBodyText(aText); 1.60 +} 1.61 + 1.62 +void 1.63 +nsXBLProtoImplMethod::AddParameter(const nsAString& aText) 1.64 +{ 1.65 + NS_PRECONDITION(!IsCompiled(), 1.66 + "Must not be compiled when accessing uncompiled method"); 1.67 + 1.68 + if (aText.IsEmpty()) { 1.69 + NS_WARNING("Empty name attribute in xbl:parameter!"); 1.70 + return; 1.71 + } 1.72 + 1.73 + nsXBLUncompiledMethod* uncompiledMethod = GetUncompiledMethod(); 1.74 + if (!uncompiledMethod) { 1.75 + uncompiledMethod = new nsXBLUncompiledMethod(); 1.76 + if (!uncompiledMethod) 1.77 + return; 1.78 + SetUncompiledMethod(uncompiledMethod); 1.79 + } 1.80 + 1.81 + uncompiledMethod->AddParameter(aText); 1.82 +} 1.83 + 1.84 +void 1.85 +nsXBLProtoImplMethod::SetLineNumber(uint32_t aLineNumber) 1.86 +{ 1.87 + NS_PRECONDITION(!IsCompiled(), 1.88 + "Must not be compiled when accessing uncompiled method"); 1.89 + 1.90 + nsXBLUncompiledMethod* uncompiledMethod = GetUncompiledMethod(); 1.91 + if (!uncompiledMethod) { 1.92 + uncompiledMethod = new nsXBLUncompiledMethod(); 1.93 + if (!uncompiledMethod) 1.94 + return; 1.95 + SetUncompiledMethod(uncompiledMethod); 1.96 + } 1.97 + 1.98 + uncompiledMethod->SetLineNumber(aLineNumber); 1.99 +} 1.100 + 1.101 +nsresult 1.102 +nsXBLProtoImplMethod::InstallMember(JSContext* aCx, 1.103 + JS::Handle<JSObject*> aTargetClassObject) 1.104 +{ 1.105 + NS_PRECONDITION(IsCompiled(), 1.106 + "Should not be installing an uncompiled method"); 1.107 + MOZ_ASSERT(js::IsObjectInContextCompartment(aTargetClassObject, aCx)); 1.108 + 1.109 + JS::Rooted<JSObject*> globalObject(aCx, JS_GetGlobalForObject(aCx, aTargetClassObject)); 1.110 + MOZ_ASSERT(xpc::IsInXBLScope(globalObject) || 1.111 + globalObject == xpc::GetXBLScope(aCx, globalObject)); 1.112 + 1.113 + JS::Rooted<JSObject*> jsMethodObject(aCx, GetCompiledMethod()); 1.114 + if (jsMethodObject) { 1.115 + nsDependentString name(mName); 1.116 + 1.117 + JS::Rooted<JSObject*> method(aCx, JS_CloneFunctionObject(aCx, jsMethodObject, globalObject)); 1.118 + NS_ENSURE_TRUE(method, NS_ERROR_OUT_OF_MEMORY); 1.119 + 1.120 + JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*method)); 1.121 + if (!::JS_DefineUCProperty(aCx, aTargetClassObject, 1.122 + static_cast<const jschar*>(mName), 1.123 + name.Length(), value, 1.124 + nullptr, nullptr, JSPROP_ENUMERATE)) { 1.125 + return NS_ERROR_OUT_OF_MEMORY; 1.126 + } 1.127 + } 1.128 + return NS_OK; 1.129 +} 1.130 + 1.131 +nsresult 1.132 +nsXBLProtoImplMethod::CompileMember(const nsCString& aClassStr, 1.133 + JS::Handle<JSObject*> aClassObject) 1.134 +{ 1.135 + AssertInCompilationScope(); 1.136 + NS_PRECONDITION(!IsCompiled(), 1.137 + "Trying to compile an already-compiled method"); 1.138 + NS_PRECONDITION(aClassObject, 1.139 + "Must have class object to compile"); 1.140 + 1.141 + nsXBLUncompiledMethod* uncompiledMethod = GetUncompiledMethod(); 1.142 + 1.143 + // No parameters or body was supplied, so don't install method. 1.144 + if (!uncompiledMethod) { 1.145 + // Early return after which we consider ourselves compiled. 1.146 + SetCompiledMethod(nullptr); 1.147 + 1.148 + return NS_OK; 1.149 + } 1.150 + 1.151 + // Don't install method if no name was supplied. 1.152 + if (!mName) { 1.153 + delete uncompiledMethod; 1.154 + 1.155 + // Early return after which we consider ourselves compiled. 1.156 + SetCompiledMethod(nullptr); 1.157 + 1.158 + return NS_OK; 1.159 + } 1.160 + 1.161 + // We have a method. 1.162 + // Allocate an array for our arguments. 1.163 + int32_t paramCount = uncompiledMethod->GetParameterCount(); 1.164 + char** args = nullptr; 1.165 + if (paramCount > 0) { 1.166 + args = new char*[paramCount]; 1.167 + if (!args) 1.168 + return NS_ERROR_OUT_OF_MEMORY; 1.169 + 1.170 + // Add our parameters to our args array. 1.171 + int32_t argPos = 0; 1.172 + for (nsXBLParameter* curr = uncompiledMethod->mParameters; 1.173 + curr; 1.174 + curr = curr->mNext) { 1.175 + args[argPos] = curr->mName; 1.176 + argPos++; 1.177 + } 1.178 + } 1.179 + 1.180 + // Get the body 1.181 + nsDependentString body; 1.182 + char16_t *bodyText = uncompiledMethod->mBodyText.GetText(); 1.183 + if (bodyText) 1.184 + body.Rebind(bodyText); 1.185 + 1.186 + // Now that we have a body and args, compile the function 1.187 + // and then define it. 1.188 + NS_ConvertUTF16toUTF8 cname(mName); 1.189 + nsAutoCString functionUri(aClassStr); 1.190 + int32_t hash = functionUri.RFindChar('#'); 1.191 + if (hash != kNotFound) { 1.192 + functionUri.Truncate(hash); 1.193 + } 1.194 + 1.195 + AutoJSContext cx; 1.196 + JSAutoCompartment ac(cx, aClassObject); 1.197 + JS::CompileOptions options(cx); 1.198 + options.setFileAndLine(functionUri.get(), 1.199 + uncompiledMethod->mBodyText.GetLineNumber()) 1.200 + .setVersion(JSVERSION_LATEST); 1.201 + JS::Rooted<JSObject*> methodObject(cx); 1.202 + nsresult rv = nsJSUtils::CompileFunction(cx, JS::NullPtr(), options, cname, 1.203 + paramCount, 1.204 + const_cast<const char**>(args), 1.205 + body, methodObject.address()); 1.206 + 1.207 + // Destroy our uncompiled method and delete our arg list. 1.208 + delete uncompiledMethod; 1.209 + delete [] args; 1.210 + if (NS_FAILED(rv)) { 1.211 + SetUncompiledMethod(nullptr); 1.212 + return rv; 1.213 + } 1.214 + 1.215 + SetCompiledMethod(methodObject); 1.216 + 1.217 + return NS_OK; 1.218 +} 1.219 + 1.220 +void 1.221 +nsXBLProtoImplMethod::Trace(const TraceCallbacks& aCallbacks, void *aClosure) 1.222 +{ 1.223 + if (IsCompiled() && GetCompiledMethodPreserveColor()) { 1.224 + aCallbacks.Trace(&mMethod.AsHeapObject(), "mMethod", aClosure); 1.225 + } 1.226 +} 1.227 + 1.228 +nsresult 1.229 +nsXBLProtoImplMethod::Read(nsIObjectInputStream* aStream) 1.230 +{ 1.231 + AssertInCompilationScope(); 1.232 + MOZ_ASSERT(!IsCompiled() && !GetUncompiledMethod()); 1.233 + 1.234 + AutoJSContext cx; 1.235 + JS::Rooted<JSObject*> methodObject(cx); 1.236 + nsresult rv = XBL_DeserializeFunction(aStream, &methodObject); 1.237 + if (NS_FAILED(rv)) { 1.238 + SetUncompiledMethod(nullptr); 1.239 + return rv; 1.240 + } 1.241 + 1.242 + SetCompiledMethod(methodObject); 1.243 + 1.244 + return NS_OK; 1.245 +} 1.246 + 1.247 +nsresult 1.248 +nsXBLProtoImplMethod::Write(nsIObjectOutputStream* aStream) 1.249 +{ 1.250 + AssertInCompilationScope(); 1.251 + MOZ_ASSERT(IsCompiled()); 1.252 + if (GetCompiledMethodPreserveColor()) { 1.253 + nsresult rv = aStream->Write8(XBLBinding_Serialize_Method); 1.254 + NS_ENSURE_SUCCESS(rv, rv); 1.255 + 1.256 + rv = aStream->WriteWStringZ(mName); 1.257 + NS_ENSURE_SUCCESS(rv, rv); 1.258 + 1.259 + // Calling fromMarkedLocation() is safe because mMethod is traced by the 1.260 + // Trace() method above, and because its value is never changed after it has 1.261 + // been set to a compiled method. 1.262 + JS::Handle<JSObject*> method = 1.263 + JS::Handle<JSObject*>::fromMarkedLocation(mMethod.AsHeapObject().address()); 1.264 + return XBL_SerializeFunction(aStream, method); 1.265 + } 1.266 + 1.267 + return NS_OK; 1.268 +} 1.269 + 1.270 +nsresult 1.271 +nsXBLProtoImplAnonymousMethod::Execute(nsIContent* aBoundElement) 1.272 +{ 1.273 + NS_PRECONDITION(IsCompiled(), "Can't execute uncompiled method"); 1.274 + 1.275 + if (!GetCompiledMethod()) { 1.276 + // Nothing to do here 1.277 + return NS_OK; 1.278 + } 1.279 + 1.280 + // Get the script context the same way 1.281 + // nsXBLProtoImpl::InstallImplementation does. 1.282 + nsIDocument* document = aBoundElement->OwnerDoc(); 1.283 + 1.284 + nsCOMPtr<nsIScriptGlobalObject> global = 1.285 + do_QueryInterface(document->GetWindow()); 1.286 + if (!global) { 1.287 + return NS_OK; 1.288 + } 1.289 + 1.290 + nsCOMPtr<nsIScriptContext> context = global->GetContext(); 1.291 + if (!context) { 1.292 + return NS_OK; 1.293 + } 1.294 + 1.295 + nsAutoMicroTask mt; 1.296 + 1.297 + AutoPushJSContext cx(context->GetNativeContext()); 1.298 + 1.299 + JS::Rooted<JSObject*> globalObject(cx, global->GetGlobalJSObject()); 1.300 + 1.301 + JS::Rooted<JS::Value> v(cx); 1.302 + nsresult rv = nsContentUtils::WrapNative(cx, aBoundElement, &v); 1.303 + NS_ENSURE_SUCCESS(rv, rv); 1.304 + 1.305 + // Use nsCxPusher to make sure we call ScriptEvaluated when we're done. 1.306 + // 1.307 + // Make sure to do this before entering the compartment, since pushing Push() 1.308 + // may call JS_SaveFrameChain(), which puts us back in an unentered state. 1.309 + nsCxPusher pusher; 1.310 + if (!pusher.Push(aBoundElement)) 1.311 + return NS_ERROR_UNEXPECTED; 1.312 + MOZ_ASSERT(cx == nsContentUtils::GetCurrentJSContext()); 1.313 + 1.314 + JS::Rooted<JSObject*> thisObject(cx, &v.toObject()); 1.315 + JS::Rooted<JSObject*> scopeObject(cx, xpc::GetXBLScopeOrGlobal(cx, globalObject)); 1.316 + NS_ENSURE_TRUE(scopeObject, NS_ERROR_OUT_OF_MEMORY); 1.317 + 1.318 + JSAutoCompartment ac(cx, scopeObject); 1.319 + if (!JS_WrapObject(cx, &thisObject)) 1.320 + return NS_ERROR_OUT_OF_MEMORY; 1.321 + 1.322 + // Clone the function object, using thisObject as the parent so "this" is in 1.323 + // the scope chain of the resulting function (for backwards compat to the 1.324 + // days when this was an event handler). 1.325 + JS::Rooted<JSObject*> jsMethodObject(cx, GetCompiledMethod()); 1.326 + JS::Rooted<JSObject*> method(cx, ::JS_CloneFunctionObject(cx, jsMethodObject, thisObject)); 1.327 + if (!method) 1.328 + return NS_ERROR_OUT_OF_MEMORY; 1.329 + 1.330 + // Now call the method 1.331 + 1.332 + // Check whether script is enabled. 1.333 + bool scriptAllowed = nsContentUtils::GetSecurityManager()-> 1.334 + ScriptAllowed(js::GetGlobalForObjectCrossCompartment(method)); 1.335 + 1.336 + bool ok = true; 1.337 + if (scriptAllowed) { 1.338 + JS::Rooted<JS::Value> retval(cx); 1.339 + JS::Rooted<JS::Value> methodVal(cx, JS::ObjectValue(*method)); 1.340 + ok = ::JS::Call(cx, thisObject, methodVal, JS::HandleValueArray::empty(), &retval); 1.341 + } 1.342 + 1.343 + if (!ok) { 1.344 + // If a constructor or destructor threw an exception, it doesn't stop 1.345 + // anything else. We just report it. Note that we need to set aside the 1.346 + // frame chain here, since the constructor invocation is not related to 1.347 + // whatever is on the stack right now, really. 1.348 + nsJSUtils::ReportPendingException(cx); 1.349 + return NS_ERROR_FAILURE; 1.350 + } 1.351 + 1.352 + return NS_OK; 1.353 +} 1.354 + 1.355 +nsresult 1.356 +nsXBLProtoImplAnonymousMethod::Write(nsIObjectOutputStream* aStream, 1.357 + XBLBindingSerializeDetails aType) 1.358 +{ 1.359 + AssertInCompilationScope(); 1.360 + MOZ_ASSERT(IsCompiled()); 1.361 + if (GetCompiledMethodPreserveColor()) { 1.362 + nsresult rv = aStream->Write8(aType); 1.363 + NS_ENSURE_SUCCESS(rv, rv); 1.364 + 1.365 + rv = aStream->WriteWStringZ(mName); 1.366 + NS_ENSURE_SUCCESS(rv, rv); 1.367 + 1.368 + // Calling fromMarkedLocation() is safe because mMethod is traced by the 1.369 + // Trace() method above, and because its value is never changed after it has 1.370 + // been set to a compiled method. 1.371 + JS::Handle<JSObject*> method = 1.372 + JS::Handle<JSObject*>::fromMarkedLocation(mMethod.AsHeapObject().address()); 1.373 + rv = XBL_SerializeFunction(aStream, method); 1.374 + NS_ENSURE_SUCCESS(rv, rv); 1.375 + } 1.376 + 1.377 + return NS_OK; 1.378 +}