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 "mozilla/dom/CallbackObject.h"
8 : #include "mozilla/dom/BindingUtils.h"
9 : #include "jsfriendapi.h"
10 : #include "nsIScriptGlobalObject.h"
11 : #include "nsIXPConnect.h"
12 : #include "nsIScriptContext.h"
13 : #include "nsPIDOMWindow.h"
14 : #include "nsJSUtils.h"
15 : #include "xpcprivate.h"
16 : #include "WorkerPrivate.h"
17 : #include "nsGlobalWindow.h"
18 : #include "WorkerScope.h"
19 : #include "jsapi.h"
20 : #include "nsJSPrincipals.h"
21 :
22 : namespace mozilla {
23 : namespace dom {
24 :
25 4817 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CallbackObject)
26 0 : NS_INTERFACE_MAP_ENTRY(mozilla::dom::CallbackObject)
27 0 : NS_INTERFACE_MAP_ENTRY(nsISupports)
28 0 : NS_INTERFACE_MAP_END
29 :
30 753 : NS_IMPL_CYCLE_COLLECTING_ADDREF(CallbackObject)
31 815 : NS_IMPL_CYCLE_COLLECTING_RELEASE(CallbackObject)
32 :
33 : NS_IMPL_CYCLE_COLLECTION_CLASS(CallbackObject)
34 :
35 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CallbackObject)
36 0 : tmp->ClearJSReferences();
37 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mIncumbentGlobal)
38 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
39 :
40 0 : NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(CallbackObject)
41 0 : JSObject* callback = tmp->CallbackPreserveColor();
42 :
43 0 : if (!aRemovingAllowed) {
44 : // If our callback has been cleared, we can't be part of a garbage cycle.
45 0 : return !callback;
46 : }
47 :
48 : // mCallback is always wrapped for the CallbackObject's incumbent global. In
49 : // the case where the real callback is in a different compartment, we have a
50 : // cross-compartment wrapper, and it will automatically be cut when its
51 : // compartment is nuked. In the case where it is in the same compartment, we
52 : // have a reference to the real function. Since that means there are no
53 : // wrappers to cut, we need to check whether the compartment is still alive,
54 : // and drop the references if it is not.
55 :
56 0 : if (MOZ_UNLIKELY(!callback)) {
57 0 : return true;
58 : }
59 0 : auto pvt = xpc::CompartmentPrivate::Get(callback);
60 0 : if (MOZ_LIKELY(tmp->mIncumbentGlobal && pvt) && MOZ_UNLIKELY(pvt->wasNuked)) {
61 : // It's not safe to release our global reference or drop our JS objects at
62 : // this point, so defer their finalization until CC is finished.
63 0 : AddForDeferredFinalization(new JSObjectsDropper(tmp));
64 0 : DeferredFinalize(tmp->mIncumbentGlobal.forget().take());
65 0 : return true;
66 : }
67 0 : NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
68 :
69 0 : NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(CallbackObject)
70 0 : return !tmp->mCallback;
71 : NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
72 :
73 0 : NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(CallbackObject)
74 0 : return !tmp->mCallback;
75 : NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
76 :
77 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CallbackObject)
78 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIncumbentGlobal)
79 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
80 957 : NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CallbackObject)
81 957 : NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCallback)
82 957 : NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCreationStack)
83 957 : NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mIncumbentJSGlobal)
84 957 : NS_IMPL_CYCLE_COLLECTION_TRACE_END
85 :
86 : void
87 0 : CallbackObject::Trace(JSTracer* aTracer)
88 : {
89 0 : JS::TraceEdge(aTracer, &mCallback, "CallbackObject.mCallback");
90 0 : JS::TraceEdge(aTracer, &mCreationStack, "CallbackObject.mCreationStack");
91 0 : JS::TraceEdge(aTracer, &mIncumbentJSGlobal,
92 0 : "CallbackObject.mIncumbentJSGlobal");
93 0 : }
94 :
95 : void
96 213 : CallbackObject::FinishSlowJSInitIfMoreThanOneOwner(JSContext* aCx)
97 : {
98 213 : MOZ_ASSERT(mRefCnt.get() > 0);
99 213 : if (mRefCnt.get() > 1) {
100 197 : mozilla::HoldJSObjects(this);
101 197 : if (JS::ContextOptionsRef(aCx).asyncStack()) {
102 394 : JS::RootedObject stack(aCx);
103 197 : if (!JS::CaptureCurrentStack(aCx, &stack)) {
104 0 : JS_ClearPendingException(aCx);
105 : }
106 197 : mCreationStack = stack;
107 : }
108 197 : mIncumbentGlobal = GetIncumbentGlobal();
109 197 : if (mIncumbentGlobal) {
110 197 : mIncumbentJSGlobal = mIncumbentGlobal->GetGlobalJSObject();
111 : }
112 : } else {
113 : // We can just forget all our stuff.
114 16 : ClearJSReferences();
115 : }
116 213 : }
117 :
118 294 : CallbackObject::CallSetup::CallSetup(CallbackObject* aCallback,
119 : ErrorResult& aRv,
120 : const char* aExecutionReason,
121 : ExceptionHandling aExceptionHandling,
122 : JSCompartment* aCompartment,
123 294 : bool aIsJSImplementedWebIDL)
124 : : mCx(nullptr)
125 : , mCompartment(aCompartment)
126 : , mErrorResult(aRv)
127 : , mExceptionHandling(aExceptionHandling)
128 294 : , mIsMainThread(NS_IsMainThread())
129 : {
130 294 : if (mIsMainThread) {
131 294 : nsContentUtils::EnterMicroTask();
132 : }
133 :
134 : // Compute the caller's subject principal (if necessary) early, before we
135 : // do anything that might perturb the relevant state.
136 294 : nsIPrincipal* webIDLCallerPrincipal = nullptr;
137 294 : if (aIsJSImplementedWebIDL) {
138 0 : webIDLCallerPrincipal = nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller();
139 : }
140 :
141 294 : JSObject* wrappedCallback = aCallback->CallbackPreserveColor();
142 294 : if (!wrappedCallback) {
143 0 : aRv.ThrowDOMException(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
144 0 : NS_LITERAL_CSTRING("Cannot execute callback from a nuked compartment."));
145 0 : return;
146 : }
147 :
148 : // First, find the real underlying callback.
149 294 : JSObject* realCallback = js::UncheckedUnwrap(wrappedCallback);
150 294 : nsIGlobalObject* globalObject = nullptr;
151 :
152 : JSContext* cx;
153 : {
154 : // Bug 955660: we cannot do "proper" rooting here because we need the
155 : // global to get a context. Everything here is simple getters that cannot
156 : // GC, so just paper over the necessary dataflow inversion.
157 588 : JS::AutoSuppressGCAnalysis nogc;
158 :
159 : // Now get the global for this callback. Note that for the case of
160 : // JS-implemented WebIDL we never have a window here.
161 588 : nsGlobalWindow* win = mIsMainThread && !aIsJSImplementedWebIDL
162 588 : ? xpc::WindowGlobalOrNull(realCallback)
163 294 : : nullptr;
164 294 : if (win) {
165 74 : MOZ_ASSERT(win->IsInnerWindow());
166 : // We don't want to run script in windows that have been navigated away
167 : // from.
168 74 : if (!win->AsInner()->HasActiveDocument()) {
169 0 : aRv.ThrowDOMException(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
170 0 : NS_LITERAL_CSTRING("Refusing to execute function from window "
171 0 : "whose document is no longer active."));
172 0 : return;
173 : }
174 74 : globalObject = win;
175 : } else {
176 : // No DOM Window. Store the global.
177 220 : JSObject* global = js::GetGlobalForObjectCrossCompartment(realCallback);
178 220 : globalObject = xpc::NativeGlobal(global);
179 220 : MOZ_ASSERT(globalObject);
180 : }
181 :
182 : // Bail out if there's no useful global. This seems to happen intermittently
183 : // on gaia-ui tests, probably because nsInProcessTabChildGlobal is returning
184 : // null in some kind of teardown state.
185 294 : if (!globalObject->GetGlobalJSObject()) {
186 0 : aRv.ThrowDOMException(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
187 0 : NS_LITERAL_CSTRING("Refusing to execute function from global which is "
188 0 : "being torn down."));
189 0 : return;
190 : }
191 :
192 294 : mAutoEntryScript.emplace(globalObject, aExecutionReason, mIsMainThread);
193 294 : mAutoEntryScript->SetWebIDLCallerPrincipal(webIDLCallerPrincipal);
194 294 : nsIGlobalObject* incumbent = aCallback->IncumbentGlobalOrNull();
195 294 : if (incumbent) {
196 : // The callback object traces its incumbent JS global, so in general it
197 : // should be alive here. However, it's possible that we could run afoul
198 : // of the same IPC global weirdness described above, wherein the
199 : // nsIGlobalObject has severed its reference to the JS global. Let's just
200 : // be safe here, so that nobody has to waste a day debugging gaia-ui tests.
201 279 : if (!incumbent->GetGlobalJSObject()) {
202 0 : aRv.ThrowDOMException(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
203 0 : NS_LITERAL_CSTRING("Refusing to execute function because our "
204 0 : "incumbent global is being torn down."));
205 0 : return;
206 : }
207 279 : mAutoIncumbentScript.emplace(incumbent);
208 : }
209 :
210 294 : cx = mAutoEntryScript->cx();
211 :
212 : // Unmark the callable (by invoking CallbackOrNull() and not the
213 : // CallbackPreserveColor() variant), and stick it in a Rooted before it can
214 : // go gray again.
215 : // Nothing before us in this function can trigger a CC, so it's safe to wait
216 : // until here it do the unmark. This allows us to construct mRootedCallable
217 : // with the cx from mAutoEntryScript, avoiding the cost of finding another
218 : // JSContext. (Rooted<> does not care about requests or compartments.)
219 294 : mRootedCallable.emplace(cx, aCallback->CallbackOrNull());
220 : }
221 :
222 : // JS-implemented WebIDL is always OK to run, since it runs with Chrome
223 : // privileges anyway.
224 294 : if (mIsMainThread && !aIsJSImplementedWebIDL) {
225 : // Check that it's ok to run this callback at all.
226 : // Make sure to use realCallback to get the global of the callback object,
227 : // not the wrapper.
228 294 : bool allowed = xpc::Scriptability::Get(realCallback).Allowed();
229 :
230 294 : if (!allowed) {
231 0 : aRv.ThrowDOMException(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
232 0 : NS_LITERAL_CSTRING("Refusing to execute function from global in which "
233 0 : "script is disabled."));
234 0 : return;
235 : }
236 : }
237 :
238 294 : mAsyncStack.emplace(cx, aCallback->GetCreationStack());
239 294 : if (*mAsyncStack) {
240 194 : mAsyncStackSetter.emplace(cx, *mAsyncStack, aExecutionReason);
241 : }
242 :
243 : // Enter the compartment of our callback, so we can actually work with it.
244 : //
245 : // Note that if the callback is a wrapper, this will not be the same
246 : // compartment that we ended up in with mAutoEntryScript above, because the
247 : // entry point is based off of the unwrapped callback (realCallback).
248 294 : mAc.emplace(cx, *mRootedCallable);
249 :
250 : // And now we're ready to go.
251 294 : mCx = cx;
252 : }
253 :
254 : bool
255 0 : CallbackObject::CallSetup::ShouldRethrowException(JS::Handle<JS::Value> aException)
256 : {
257 0 : if (mExceptionHandling == eRethrowExceptions) {
258 0 : if (!mCompartment) {
259 : // Caller didn't ask us to filter for only exceptions we subsume.
260 0 : return true;
261 : }
262 :
263 : // On workers, we don't have nsIPrincipals to work with. But we also only
264 : // have one compartment, so check whether mCompartment is the same as the
265 : // current compartment of mCx.
266 0 : if (mCompartment == js::GetContextCompartment(mCx)) {
267 0 : return true;
268 : }
269 :
270 0 : MOZ_ASSERT(NS_IsMainThread());
271 :
272 : // At this point mCx is in the compartment of our unwrapped callback, so
273 : // just check whether the principal of mCompartment subsumes that of the
274 : // current compartment/global of mCx.
275 : nsIPrincipal* callerPrincipal =
276 0 : nsJSPrincipals::get(JS_GetCompartmentPrincipals(mCompartment));
277 0 : nsIPrincipal* calleePrincipal = nsContentUtils::SubjectPrincipal();
278 0 : if (callerPrincipal->SubsumesConsideringDomain(calleePrincipal)) {
279 0 : return true;
280 : }
281 : }
282 :
283 0 : MOZ_ASSERT(mCompartment);
284 :
285 : // Now we only want to throw an exception to the caller if the object that was
286 : // thrown is in the caller compartment (which we stored in mCompartment).
287 :
288 0 : if (!aException.isObject()) {
289 0 : return false;
290 : }
291 :
292 0 : JS::Rooted<JSObject*> obj(mCx, &aException.toObject());
293 0 : obj = js::UncheckedUnwrap(obj, /* stopAtWindowProxy = */ false);
294 0 : return js::GetObjectCompartment(obj) == mCompartment;
295 : }
296 :
297 588 : CallbackObject::CallSetup::~CallSetup()
298 : {
299 : // To get our nesting right we have to destroy our JSAutoCompartment first.
300 : // In particular, we want to do this before we try reporting any exceptions,
301 : // so we end up reporting them while in the compartment of our entry point,
302 : // not whatever cross-compartment wrappper mCallback might be.
303 : // Be careful: the JSAutoCompartment might not have been constructed at all!
304 294 : mAc.reset();
305 :
306 : // Now, if we have a JSContext, report any pending errors on it, unless we
307 : // were told to re-throw them.
308 294 : if (mCx) {
309 294 : bool needToDealWithException = mAutoEntryScript->HasException();
310 588 : if ((mCompartment && mExceptionHandling == eRethrowContentExceptions) ||
311 294 : mExceptionHandling == eRethrowExceptions) {
312 0 : mErrorResult.MightThrowJSException();
313 0 : if (needToDealWithException) {
314 0 : JS::Rooted<JS::Value> exn(mCx);
315 0 : if (mAutoEntryScript->PeekException(&exn) &&
316 0 : ShouldRethrowException(exn)) {
317 0 : mAutoEntryScript->ClearException();
318 0 : MOZ_ASSERT(!mAutoEntryScript->HasException());
319 0 : mErrorResult.ThrowJSException(mCx, exn);
320 0 : needToDealWithException = false;
321 : }
322 : }
323 : }
324 :
325 294 : if (needToDealWithException) {
326 : // Either we're supposed to report our exceptions, or we're supposed to
327 : // re-throw them but we failed to get the exception value. Either way,
328 : // we'll just report the pending exception, if any, once ~mAutoEntryScript
329 : // runs. Note that we've already run ~mAc, effectively, so we don't have
330 : // to worry about ordering here.
331 0 : if (mErrorResult.IsJSContextException()) {
332 : // XXXkhuey bug 1117269. When this is fixed, please consider fixing
333 : // ThrowExceptionValueIfSafe over in Exceptions.cpp in the same way.
334 :
335 : // IsJSContextException shouldn't be true anymore because we will report
336 : // the exception on the JSContext ... so throw something else.
337 0 : mErrorResult.ThrowWithCustomCleanup(NS_ERROR_UNEXPECTED);
338 : }
339 : }
340 : }
341 :
342 294 : mAutoIncumbentScript.reset();
343 294 : mAutoEntryScript.reset();
344 :
345 : // It is important that this is the last thing we do, after leaving the
346 : // compartment and undoing all our entry/incumbent script changes
347 294 : if (mIsMainThread) {
348 294 : nsContentUtils::LeaveMicroTask();
349 : }
350 294 : }
351 :
352 : already_AddRefed<nsISupports>
353 0 : CallbackObjectHolderBase::ToXPCOMCallback(CallbackObject* aCallback,
354 : const nsIID& aIID) const
355 : {
356 0 : MOZ_ASSERT(NS_IsMainThread());
357 0 : if (!aCallback) {
358 0 : return nullptr;
359 : }
360 :
361 : // We don't init the AutoJSAPI with our callback because we don't want it
362 : // reporting errors to its global's onerror handlers.
363 0 : AutoJSAPI jsapi;
364 0 : jsapi.Init();
365 0 : JSContext* cx = jsapi.cx();
366 :
367 0 : JS::Rooted<JSObject*> callback(cx, aCallback->CallbackOrNull());
368 0 : if (!callback) {
369 0 : return nullptr;
370 : }
371 :
372 0 : JSAutoCompartment ac(cx, callback);
373 0 : RefPtr<nsXPCWrappedJS> wrappedJS;
374 : nsresult rv =
375 0 : nsXPCWrappedJS::GetNewOrUsed(callback, aIID, getter_AddRefs(wrappedJS));
376 0 : if (NS_FAILED(rv) || !wrappedJS) {
377 0 : return nullptr;
378 : }
379 :
380 0 : nsCOMPtr<nsISupports> retval;
381 0 : rv = wrappedJS->QueryInterface(aIID, getter_AddRefs(retval));
382 0 : if (NS_FAILED(rv)) {
383 0 : return nullptr;
384 : }
385 :
386 0 : return retval.forget();
387 : }
388 :
389 : } // namespace dom
390 : } // namespace mozilla
|