|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include "nsIAtom.h" |
|
7 #include "nsString.h" |
|
8 #include "jsapi.h" |
|
9 #include "nsIContent.h" |
|
10 #include "nsIDocument.h" |
|
11 #include "nsIScriptGlobalObject.h" |
|
12 #include "nsUnicharUtils.h" |
|
13 #include "nsReadableUtils.h" |
|
14 #include "nsXBLProtoImplMethod.h" |
|
15 #include "nsIScriptContext.h" |
|
16 #include "nsJSUtils.h" |
|
17 #include "nsContentUtils.h" |
|
18 #include "nsCxPusher.h" |
|
19 #include "nsIScriptSecurityManager.h" |
|
20 #include "nsIXPConnect.h" |
|
21 #include "xpcpublic.h" |
|
22 #include "nsXBLPrototypeBinding.h" |
|
23 |
|
24 using namespace mozilla; |
|
25 |
|
26 nsXBLProtoImplMethod::nsXBLProtoImplMethod(const char16_t* aName) : |
|
27 nsXBLProtoImplMember(aName), |
|
28 mMethod() |
|
29 { |
|
30 MOZ_COUNT_CTOR(nsXBLProtoImplMethod); |
|
31 } |
|
32 |
|
33 nsXBLProtoImplMethod::~nsXBLProtoImplMethod() |
|
34 { |
|
35 MOZ_COUNT_DTOR(nsXBLProtoImplMethod); |
|
36 |
|
37 if (!IsCompiled()) { |
|
38 delete GetUncompiledMethod(); |
|
39 } |
|
40 } |
|
41 |
|
42 void |
|
43 nsXBLProtoImplMethod::AppendBodyText(const nsAString& aText) |
|
44 { |
|
45 NS_PRECONDITION(!IsCompiled(), |
|
46 "Must not be compiled when accessing uncompiled method"); |
|
47 |
|
48 nsXBLUncompiledMethod* uncompiledMethod = GetUncompiledMethod(); |
|
49 if (!uncompiledMethod) { |
|
50 uncompiledMethod = new nsXBLUncompiledMethod(); |
|
51 if (!uncompiledMethod) |
|
52 return; |
|
53 SetUncompiledMethod(uncompiledMethod); |
|
54 } |
|
55 |
|
56 uncompiledMethod->AppendBodyText(aText); |
|
57 } |
|
58 |
|
59 void |
|
60 nsXBLProtoImplMethod::AddParameter(const nsAString& aText) |
|
61 { |
|
62 NS_PRECONDITION(!IsCompiled(), |
|
63 "Must not be compiled when accessing uncompiled method"); |
|
64 |
|
65 if (aText.IsEmpty()) { |
|
66 NS_WARNING("Empty name attribute in xbl:parameter!"); |
|
67 return; |
|
68 } |
|
69 |
|
70 nsXBLUncompiledMethod* uncompiledMethod = GetUncompiledMethod(); |
|
71 if (!uncompiledMethod) { |
|
72 uncompiledMethod = new nsXBLUncompiledMethod(); |
|
73 if (!uncompiledMethod) |
|
74 return; |
|
75 SetUncompiledMethod(uncompiledMethod); |
|
76 } |
|
77 |
|
78 uncompiledMethod->AddParameter(aText); |
|
79 } |
|
80 |
|
81 void |
|
82 nsXBLProtoImplMethod::SetLineNumber(uint32_t aLineNumber) |
|
83 { |
|
84 NS_PRECONDITION(!IsCompiled(), |
|
85 "Must not be compiled when accessing uncompiled method"); |
|
86 |
|
87 nsXBLUncompiledMethod* uncompiledMethod = GetUncompiledMethod(); |
|
88 if (!uncompiledMethod) { |
|
89 uncompiledMethod = new nsXBLUncompiledMethod(); |
|
90 if (!uncompiledMethod) |
|
91 return; |
|
92 SetUncompiledMethod(uncompiledMethod); |
|
93 } |
|
94 |
|
95 uncompiledMethod->SetLineNumber(aLineNumber); |
|
96 } |
|
97 |
|
98 nsresult |
|
99 nsXBLProtoImplMethod::InstallMember(JSContext* aCx, |
|
100 JS::Handle<JSObject*> aTargetClassObject) |
|
101 { |
|
102 NS_PRECONDITION(IsCompiled(), |
|
103 "Should not be installing an uncompiled method"); |
|
104 MOZ_ASSERT(js::IsObjectInContextCompartment(aTargetClassObject, aCx)); |
|
105 |
|
106 JS::Rooted<JSObject*> globalObject(aCx, JS_GetGlobalForObject(aCx, aTargetClassObject)); |
|
107 MOZ_ASSERT(xpc::IsInXBLScope(globalObject) || |
|
108 globalObject == xpc::GetXBLScope(aCx, globalObject)); |
|
109 |
|
110 JS::Rooted<JSObject*> jsMethodObject(aCx, GetCompiledMethod()); |
|
111 if (jsMethodObject) { |
|
112 nsDependentString name(mName); |
|
113 |
|
114 JS::Rooted<JSObject*> method(aCx, JS_CloneFunctionObject(aCx, jsMethodObject, globalObject)); |
|
115 NS_ENSURE_TRUE(method, NS_ERROR_OUT_OF_MEMORY); |
|
116 |
|
117 JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*method)); |
|
118 if (!::JS_DefineUCProperty(aCx, aTargetClassObject, |
|
119 static_cast<const jschar*>(mName), |
|
120 name.Length(), value, |
|
121 nullptr, nullptr, JSPROP_ENUMERATE)) { |
|
122 return NS_ERROR_OUT_OF_MEMORY; |
|
123 } |
|
124 } |
|
125 return NS_OK; |
|
126 } |
|
127 |
|
128 nsresult |
|
129 nsXBLProtoImplMethod::CompileMember(const nsCString& aClassStr, |
|
130 JS::Handle<JSObject*> aClassObject) |
|
131 { |
|
132 AssertInCompilationScope(); |
|
133 NS_PRECONDITION(!IsCompiled(), |
|
134 "Trying to compile an already-compiled method"); |
|
135 NS_PRECONDITION(aClassObject, |
|
136 "Must have class object to compile"); |
|
137 |
|
138 nsXBLUncompiledMethod* uncompiledMethod = GetUncompiledMethod(); |
|
139 |
|
140 // No parameters or body was supplied, so don't install method. |
|
141 if (!uncompiledMethod) { |
|
142 // Early return after which we consider ourselves compiled. |
|
143 SetCompiledMethod(nullptr); |
|
144 |
|
145 return NS_OK; |
|
146 } |
|
147 |
|
148 // Don't install method if no name was supplied. |
|
149 if (!mName) { |
|
150 delete uncompiledMethod; |
|
151 |
|
152 // Early return after which we consider ourselves compiled. |
|
153 SetCompiledMethod(nullptr); |
|
154 |
|
155 return NS_OK; |
|
156 } |
|
157 |
|
158 // We have a method. |
|
159 // Allocate an array for our arguments. |
|
160 int32_t paramCount = uncompiledMethod->GetParameterCount(); |
|
161 char** args = nullptr; |
|
162 if (paramCount > 0) { |
|
163 args = new char*[paramCount]; |
|
164 if (!args) |
|
165 return NS_ERROR_OUT_OF_MEMORY; |
|
166 |
|
167 // Add our parameters to our args array. |
|
168 int32_t argPos = 0; |
|
169 for (nsXBLParameter* curr = uncompiledMethod->mParameters; |
|
170 curr; |
|
171 curr = curr->mNext) { |
|
172 args[argPos] = curr->mName; |
|
173 argPos++; |
|
174 } |
|
175 } |
|
176 |
|
177 // Get the body |
|
178 nsDependentString body; |
|
179 char16_t *bodyText = uncompiledMethod->mBodyText.GetText(); |
|
180 if (bodyText) |
|
181 body.Rebind(bodyText); |
|
182 |
|
183 // Now that we have a body and args, compile the function |
|
184 // and then define it. |
|
185 NS_ConvertUTF16toUTF8 cname(mName); |
|
186 nsAutoCString functionUri(aClassStr); |
|
187 int32_t hash = functionUri.RFindChar('#'); |
|
188 if (hash != kNotFound) { |
|
189 functionUri.Truncate(hash); |
|
190 } |
|
191 |
|
192 AutoJSContext cx; |
|
193 JSAutoCompartment ac(cx, aClassObject); |
|
194 JS::CompileOptions options(cx); |
|
195 options.setFileAndLine(functionUri.get(), |
|
196 uncompiledMethod->mBodyText.GetLineNumber()) |
|
197 .setVersion(JSVERSION_LATEST); |
|
198 JS::Rooted<JSObject*> methodObject(cx); |
|
199 nsresult rv = nsJSUtils::CompileFunction(cx, JS::NullPtr(), options, cname, |
|
200 paramCount, |
|
201 const_cast<const char**>(args), |
|
202 body, methodObject.address()); |
|
203 |
|
204 // Destroy our uncompiled method and delete our arg list. |
|
205 delete uncompiledMethod; |
|
206 delete [] args; |
|
207 if (NS_FAILED(rv)) { |
|
208 SetUncompiledMethod(nullptr); |
|
209 return rv; |
|
210 } |
|
211 |
|
212 SetCompiledMethod(methodObject); |
|
213 |
|
214 return NS_OK; |
|
215 } |
|
216 |
|
217 void |
|
218 nsXBLProtoImplMethod::Trace(const TraceCallbacks& aCallbacks, void *aClosure) |
|
219 { |
|
220 if (IsCompiled() && GetCompiledMethodPreserveColor()) { |
|
221 aCallbacks.Trace(&mMethod.AsHeapObject(), "mMethod", aClosure); |
|
222 } |
|
223 } |
|
224 |
|
225 nsresult |
|
226 nsXBLProtoImplMethod::Read(nsIObjectInputStream* aStream) |
|
227 { |
|
228 AssertInCompilationScope(); |
|
229 MOZ_ASSERT(!IsCompiled() && !GetUncompiledMethod()); |
|
230 |
|
231 AutoJSContext cx; |
|
232 JS::Rooted<JSObject*> methodObject(cx); |
|
233 nsresult rv = XBL_DeserializeFunction(aStream, &methodObject); |
|
234 if (NS_FAILED(rv)) { |
|
235 SetUncompiledMethod(nullptr); |
|
236 return rv; |
|
237 } |
|
238 |
|
239 SetCompiledMethod(methodObject); |
|
240 |
|
241 return NS_OK; |
|
242 } |
|
243 |
|
244 nsresult |
|
245 nsXBLProtoImplMethod::Write(nsIObjectOutputStream* aStream) |
|
246 { |
|
247 AssertInCompilationScope(); |
|
248 MOZ_ASSERT(IsCompiled()); |
|
249 if (GetCompiledMethodPreserveColor()) { |
|
250 nsresult rv = aStream->Write8(XBLBinding_Serialize_Method); |
|
251 NS_ENSURE_SUCCESS(rv, rv); |
|
252 |
|
253 rv = aStream->WriteWStringZ(mName); |
|
254 NS_ENSURE_SUCCESS(rv, rv); |
|
255 |
|
256 // Calling fromMarkedLocation() is safe because mMethod is traced by the |
|
257 // Trace() method above, and because its value is never changed after it has |
|
258 // been set to a compiled method. |
|
259 JS::Handle<JSObject*> method = |
|
260 JS::Handle<JSObject*>::fromMarkedLocation(mMethod.AsHeapObject().address()); |
|
261 return XBL_SerializeFunction(aStream, method); |
|
262 } |
|
263 |
|
264 return NS_OK; |
|
265 } |
|
266 |
|
267 nsresult |
|
268 nsXBLProtoImplAnonymousMethod::Execute(nsIContent* aBoundElement) |
|
269 { |
|
270 NS_PRECONDITION(IsCompiled(), "Can't execute uncompiled method"); |
|
271 |
|
272 if (!GetCompiledMethod()) { |
|
273 // Nothing to do here |
|
274 return NS_OK; |
|
275 } |
|
276 |
|
277 // Get the script context the same way |
|
278 // nsXBLProtoImpl::InstallImplementation does. |
|
279 nsIDocument* document = aBoundElement->OwnerDoc(); |
|
280 |
|
281 nsCOMPtr<nsIScriptGlobalObject> global = |
|
282 do_QueryInterface(document->GetWindow()); |
|
283 if (!global) { |
|
284 return NS_OK; |
|
285 } |
|
286 |
|
287 nsCOMPtr<nsIScriptContext> context = global->GetContext(); |
|
288 if (!context) { |
|
289 return NS_OK; |
|
290 } |
|
291 |
|
292 nsAutoMicroTask mt; |
|
293 |
|
294 AutoPushJSContext cx(context->GetNativeContext()); |
|
295 |
|
296 JS::Rooted<JSObject*> globalObject(cx, global->GetGlobalJSObject()); |
|
297 |
|
298 JS::Rooted<JS::Value> v(cx); |
|
299 nsresult rv = nsContentUtils::WrapNative(cx, aBoundElement, &v); |
|
300 NS_ENSURE_SUCCESS(rv, rv); |
|
301 |
|
302 // Use nsCxPusher to make sure we call ScriptEvaluated when we're done. |
|
303 // |
|
304 // Make sure to do this before entering the compartment, since pushing Push() |
|
305 // may call JS_SaveFrameChain(), which puts us back in an unentered state. |
|
306 nsCxPusher pusher; |
|
307 if (!pusher.Push(aBoundElement)) |
|
308 return NS_ERROR_UNEXPECTED; |
|
309 MOZ_ASSERT(cx == nsContentUtils::GetCurrentJSContext()); |
|
310 |
|
311 JS::Rooted<JSObject*> thisObject(cx, &v.toObject()); |
|
312 JS::Rooted<JSObject*> scopeObject(cx, xpc::GetXBLScopeOrGlobal(cx, globalObject)); |
|
313 NS_ENSURE_TRUE(scopeObject, NS_ERROR_OUT_OF_MEMORY); |
|
314 |
|
315 JSAutoCompartment ac(cx, scopeObject); |
|
316 if (!JS_WrapObject(cx, &thisObject)) |
|
317 return NS_ERROR_OUT_OF_MEMORY; |
|
318 |
|
319 // Clone the function object, using thisObject as the parent so "this" is in |
|
320 // the scope chain of the resulting function (for backwards compat to the |
|
321 // days when this was an event handler). |
|
322 JS::Rooted<JSObject*> jsMethodObject(cx, GetCompiledMethod()); |
|
323 JS::Rooted<JSObject*> method(cx, ::JS_CloneFunctionObject(cx, jsMethodObject, thisObject)); |
|
324 if (!method) |
|
325 return NS_ERROR_OUT_OF_MEMORY; |
|
326 |
|
327 // Now call the method |
|
328 |
|
329 // Check whether script is enabled. |
|
330 bool scriptAllowed = nsContentUtils::GetSecurityManager()-> |
|
331 ScriptAllowed(js::GetGlobalForObjectCrossCompartment(method)); |
|
332 |
|
333 bool ok = true; |
|
334 if (scriptAllowed) { |
|
335 JS::Rooted<JS::Value> retval(cx); |
|
336 JS::Rooted<JS::Value> methodVal(cx, JS::ObjectValue(*method)); |
|
337 ok = ::JS::Call(cx, thisObject, methodVal, JS::HandleValueArray::empty(), &retval); |
|
338 } |
|
339 |
|
340 if (!ok) { |
|
341 // If a constructor or destructor threw an exception, it doesn't stop |
|
342 // anything else. We just report it. Note that we need to set aside the |
|
343 // frame chain here, since the constructor invocation is not related to |
|
344 // whatever is on the stack right now, really. |
|
345 nsJSUtils::ReportPendingException(cx); |
|
346 return NS_ERROR_FAILURE; |
|
347 } |
|
348 |
|
349 return NS_OK; |
|
350 } |
|
351 |
|
352 nsresult |
|
353 nsXBLProtoImplAnonymousMethod::Write(nsIObjectOutputStream* aStream, |
|
354 XBLBindingSerializeDetails aType) |
|
355 { |
|
356 AssertInCompilationScope(); |
|
357 MOZ_ASSERT(IsCompiled()); |
|
358 if (GetCompiledMethodPreserveColor()) { |
|
359 nsresult rv = aStream->Write8(aType); |
|
360 NS_ENSURE_SUCCESS(rv, rv); |
|
361 |
|
362 rv = aStream->WriteWStringZ(mName); |
|
363 NS_ENSURE_SUCCESS(rv, rv); |
|
364 |
|
365 // Calling fromMarkedLocation() is safe because mMethod is traced by the |
|
366 // Trace() method above, and because its value is never changed after it has |
|
367 // been set to a compiled method. |
|
368 JS::Handle<JSObject*> method = |
|
369 JS::Handle<JSObject*>::fromMarkedLocation(mMethod.AsHeapObject().address()); |
|
370 rv = XBL_SerializeFunction(aStream, method); |
|
371 NS_ENSURE_SUCCESS(rv, rv); |
|
372 } |
|
373 |
|
374 return NS_OK; |
|
375 } |