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