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/. */
7 #include "mozilla/dom/CallbackObject.h"
8 #include "mozilla/CycleCollectedJSContext.h"
9 #include "mozilla/dom/BindingUtils.h"
10 #include "jsfriendapi.h"
11 #include "nsIScriptGlobalObject.h"
12 #include "nsIScriptContext.h"
13 #include "nsPIDOMWindow.h"
14 #include "nsJSUtils.h"
15 #include "xpcprivate.h"
16 #include "WorkerPrivate.h"
17 #include "nsContentUtils.h"
18 #include "nsGlobalWindowInner.h"
19 #include "WorkerScope.h"
21 #include "js/ContextOptions.h"
22 #include "nsJSPrincipals.h"
24 namespace mozilla::dom
{
26 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CallbackObject
)
27 NS_INTERFACE_MAP_ENTRY(mozilla::dom::CallbackObject
)
28 NS_INTERFACE_MAP_ENTRY(nsISupports
)
31 NS_IMPL_CYCLE_COLLECTING_ADDREF(CallbackObject
)
32 NS_IMPL_CYCLE_COLLECTING_RELEASE(CallbackObject
)
34 NS_IMPL_CYCLE_COLLECTION_CLASS(CallbackObject
)
36 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CallbackObject
)
37 tmp
->ClearJSReferences();
38 NS_IMPL_CYCLE_COLLECTION_UNLINK(mIncumbentGlobal
)
39 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
41 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(CallbackObject
)
42 JSObject
* callback
= tmp
->CallbackPreserveColor();
44 if (!aRemovingAllowed
) {
45 // If our callback has been cleared, we can't be part of a garbage cycle.
49 // mCallback is always wrapped for the CallbackObject's incumbent global. In
50 // the case where the real callback is in a different compartment, we have a
51 // cross-compartment wrapper, and it will automatically be cut when its
52 // compartment is nuked. In the case where it is in the same compartment, we
53 // have a reference to the real function. Since that means there are no
54 // wrappers to cut, we need to check whether the compartment is still alive,
55 // and drop the references if it is not.
57 if (MOZ_UNLIKELY(!callback
)) {
60 if (MOZ_LIKELY(tmp
->mIncumbentGlobal
) &&
61 MOZ_UNLIKELY(js::NukedObjectRealm(tmp
->CallbackGlobalPreserveColor()))) {
62 // It's not safe to release our global reference or drop our JS objects at
63 // this point, so defer their finalization until CC is finished.
64 AddForDeferredFinalization(new JSObjectsDropper(tmp
));
65 DeferredFinalize(tmp
->mIncumbentGlobal
.forget().take());
68 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
70 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(CallbackObject
)
71 return !tmp
->mCallback
;
72 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
74 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(CallbackObject
)
75 return !tmp
->mCallback
;
76 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
78 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CallbackObject
)
79 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIncumbentGlobal
)
80 // If a new member is added here, don't forget to update IsBlackForCC.
81 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
82 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CallbackObject
)
83 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCallback
)
84 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCallbackGlobal
)
85 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCreationStack
)
86 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mIncumbentJSGlobal
)
87 // If a new member is added here, don't forget to update IsBlackForCC.
88 NS_IMPL_CYCLE_COLLECTION_TRACE_END
90 void CallbackObject::Trace(JSTracer
* aTracer
) {
91 JS::TraceEdge(aTracer
, &mCallback
, "CallbackObject.mCallback");
92 JS::TraceEdge(aTracer
, &mCallbackGlobal
, "CallbackObject.mCallbackGlobal");
93 JS::TraceEdge(aTracer
, &mCreationStack
, "CallbackObject.mCreationStack");
94 JS::TraceEdge(aTracer
, &mIncumbentJSGlobal
,
95 "CallbackObject.mIncumbentJSGlobal");
98 void CallbackObject::FinishSlowJSInitIfMoreThanOneOwner(JSContext
* aCx
) {
99 MOZ_ASSERT(mRefCnt
.get() > 0);
100 if (mRefCnt
.get() > 1) {
101 mozilla::HoldJSObjects(this);
102 if (JS::IsAsyncStackCaptureEnabledForRealm(aCx
)) {
103 JS::Rooted
<JSObject
*> stack(aCx
);
104 if (!JS::CaptureCurrentStack(aCx
, &stack
)) {
105 JS_ClearPendingException(aCx
);
107 mCreationStack
= stack
;
109 mIncumbentGlobal
= GetIncumbentGlobal();
110 if (mIncumbentGlobal
) {
111 // We don't want to expose to JS here (change the color). If someone ever
112 // reads mIncumbentJSGlobal, that will expose. If not, no need to expose
114 mIncumbentJSGlobal
= mIncumbentGlobal
->GetGlobalJSObjectPreserveColor();
117 // We can just forget all our stuff.
122 JSObject
* CallbackObject::Callback(JSContext
* aCx
) {
123 JSObject
* callback
= CallbackOrNull();
125 callback
= JS_NewDeadWrapper(aCx
);
128 MOZ_DIAGNOSTIC_ASSERT(callback
);
132 void CallbackObject::GetDescription(nsACString
& aOutString
) {
133 JSObject
* wrappedCallback
= CallbackOrNull();
134 if (!wrappedCallback
) {
135 aOutString
.Append("<callback from a nuked compartment>");
139 JS::Rooted
<JSObject
*> unwrappedCallback(
140 RootingCx(), js::CheckedUnwrapStatic(wrappedCallback
));
141 if (!unwrappedCallback
) {
142 aOutString
.Append("<not a function>");
148 JSContext
* cx
= jsapi
.cx();
150 JS::Rooted
<JSObject
*> rootedCallback(cx
, unwrappedCallback
);
151 JSAutoRealm
ar(cx
, rootedCallback
);
153 JS::Rooted
<JSFunction
*> rootedFunction(cx
,
154 JS_GetObjectFunction(rootedCallback
));
155 if (!rootedFunction
) {
156 aOutString
.Append("<not a function>");
160 JS::Rooted
<JSString
*> displayId(
161 cx
, JS_GetMaybePartialFunctionDisplayId(rootedFunction
));
163 nsAutoJSString funcNameStr
;
164 if (funcNameStr
.init(cx
, displayId
)) {
165 if (funcNameStr
.IsEmpty()) {
166 aOutString
.Append("<empty name>");
168 AppendUTF16toUTF8(funcNameStr
, aOutString
);
171 aOutString
.Append("<function name string failed to materialize>");
172 jsapi
.ClearException();
175 aOutString
.Append("<anonymous>");
178 JS::Rooted
<JSScript
*> rootedScript(cx
,
179 JS_GetFunctionScript(cx
, rootedFunction
));
184 aOutString
.Append(" (");
185 aOutString
.Append(JS_GetScriptFilename(rootedScript
));
186 aOutString
.Append(":");
187 aOutString
.AppendInt(JS_GetScriptBaseLineNumber(cx
, rootedScript
));
188 aOutString
.Append(")");
191 CallbackObject::CallSetup::CallSetup(CallbackObject
* aCallback
,
193 const char* aExecutionReason
,
194 ExceptionHandling aExceptionHandling
,
196 bool aIsJSImplementedWebIDL
)
200 mExceptionHandling(aExceptionHandling
),
201 mIsMainThread(NS_IsMainThread()) {
202 MOZ_ASSERT_IF(aExceptionHandling
== eReportExceptions
||
203 aExceptionHandling
== eRethrowExceptions
,
206 CycleCollectedJSContext
* ccjs
= CycleCollectedJSContext::Get();
208 ccjs
->EnterMicroTask();
211 // Compute the caller's subject principal (if necessary) early, before we
212 // do anything that might perturb the relevant state.
213 nsIPrincipal
* webIDLCallerPrincipal
= nullptr;
214 if (aIsJSImplementedWebIDL
) {
215 webIDLCallerPrincipal
=
216 nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller();
219 JSObject
* wrappedCallback
= aCallback
->CallbackPreserveColor();
220 if (!wrappedCallback
) {
221 aRv
.ThrowNotSupportedError(
222 "Cannot execute callback from a nuked compartment.");
226 nsIGlobalObject
* globalObject
= nullptr;
229 // First, find the real underlying callback.
230 JS::Rooted
<JSObject
*> realCallback(ccjs
->RootingCx(),
231 js::UncheckedUnwrap(wrappedCallback
));
233 // Get the global for this callback. Note that for the case of
234 // JS-implemented WebIDL we never have a window here.
235 nsGlobalWindowInner
* win
= mIsMainThread
&& !aIsJSImplementedWebIDL
236 ? xpc::WindowGlobalOrNull(realCallback
)
239 // We don't want to run script in windows that have been navigated away
241 if (!win
->HasActiveDocument()) {
242 aRv
.ThrowNotSupportedError(
243 "Refusing to execute function from window whose document is no "
249 // No DOM Window. Store the global.
250 globalObject
= xpc::NativeGlobal(realCallback
);
251 MOZ_ASSERT(globalObject
);
254 // Make sure to use realCallback to get the global of the callback
255 // object, not the wrapper.
256 if (globalObject
->IsScriptForbidden(realCallback
, aIsJSImplementedWebIDL
)) {
257 aRv
.ThrowNotSupportedError(
258 "Refusing to execute function from global in which script is "
264 // Bail out if there's no useful global.
265 if (!globalObject
->HasJSGlobal()) {
266 aRv
.ThrowNotSupportedError(
267 "Refusing to execute function from global which is being torn down.");
271 AutoAllowLegacyScriptExecution exemption
;
272 mAutoEntryScript
.emplace(globalObject
, aExecutionReason
, mIsMainThread
);
273 mAutoEntryScript
->SetWebIDLCallerPrincipal(webIDLCallerPrincipal
);
274 nsIGlobalObject
* incumbent
= aCallback
->IncumbentGlobalOrNull();
276 // The callback object traces its incumbent JS global, so in general it
277 // should be alive here. However, it's possible that we could run afoul
278 // of the same IPC global weirdness described above, wherein the
279 // nsIGlobalObject has severed its reference to the JS global. Let's just
280 // be safe here, so that nobody has to waste a day debugging gaia-ui tests.
281 if (!incumbent
->HasJSGlobal()) {
282 aRv
.ThrowNotSupportedError(
283 "Refusing to execute function because our incumbent global is being "
287 mAutoIncumbentScript
.emplace(incumbent
);
290 JSContext
* cx
= mAutoEntryScript
->cx();
292 // Unmark the callable (by invoking CallbackOrNull() and not the
293 // CallbackPreserveColor() variant), and stick it in a Rooted before it can
295 // Nothing before us in this function can trigger a CC, so it's safe to wait
296 // until here it do the unmark. This allows us to construct mRootedCallable
297 // with the cx from mAutoEntryScript, avoiding the cost of finding another
298 // JSContext. (Rooted<> does not care about requests or compartments.)
299 mRootedCallable
.emplace(cx
, aCallback
->CallbackOrNull());
300 mRootedCallableGlobal
.emplace(cx
, aCallback
->CallbackGlobalOrNull());
302 mAsyncStack
.emplace(cx
, aCallback
->GetCreationStack());
304 mAsyncStackSetter
.emplace(cx
, *mAsyncStack
, aExecutionReason
);
307 // Enter the realm of our callback, so we can actually work with it.
309 // Note that if the callback is a wrapper, this will not be the same
310 // realm that we ended up in with mAutoEntryScript above, because the
311 // entry point is based off of the unwrapped callback (realCallback).
312 mAr
.emplace(cx
, *mRootedCallableGlobal
);
314 // And now we're ready to go.
317 // We don't really have a good error message prefix to use for the
318 // BindingCallContext.
319 mCallContext
.emplace(cx
, nullptr);
322 bool CallbackObject::CallSetup::ShouldRethrowException(
323 JS::Handle
<JS::Value
> aException
) {
324 if (mExceptionHandling
== eRethrowExceptions
) {
331 // Now we only want to throw an exception to the caller if the object that was
332 // thrown is in the caller realm (which we stored in mRealm).
334 if (!aException
.isObject()) {
338 JS::Rooted
<JSObject
*> obj(mCx
, &aException
.toObject());
339 obj
= js::UncheckedUnwrap(obj
, /* stopAtWindowProxy = */ false);
340 return js::GetNonCCWObjectRealm(obj
) == mRealm
;
343 CallbackObject::CallSetup::~CallSetup() {
344 // To get our nesting right we have to destroy our JSAutoRealm first.
345 // In particular, we want to do this before we try reporting any exceptions,
346 // so we end up reporting them while in the realm of our entry point,
347 // not whatever cross-compartment wrappper mCallback might be.
348 // Be careful: the JSAutoRealm might not have been constructed at all!
351 // Now, if we have a JSContext, report any pending errors on it, unless we
352 // were told to re-throw them.
354 bool needToDealWithException
= mAutoEntryScript
->HasException();
355 if ((mRealm
&& mExceptionHandling
== eRethrowContentExceptions
) ||
356 mExceptionHandling
== eRethrowExceptions
) {
357 mErrorResult
.MightThrowJSException();
358 if (needToDealWithException
) {
359 JS::Rooted
<JS::Value
> exn(mCx
);
360 if (mAutoEntryScript
->PeekException(&exn
) &&
361 ShouldRethrowException(exn
)) {
362 mAutoEntryScript
->ClearException();
363 MOZ_ASSERT(!mAutoEntryScript
->HasException());
364 mErrorResult
.ThrowJSException(mCx
, exn
);
365 needToDealWithException
= false;
370 if (needToDealWithException
) {
371 // Either we're supposed to report our exceptions, or we're supposed to
372 // re-throw them but we failed to get the exception value. Either way,
373 // we'll just report the pending exception, if any, once ~mAutoEntryScript
374 // runs. Note that we've already run ~mAr, effectively, so we don't have
375 // to worry about ordering here.
376 if (mErrorResult
.IsJSContextException()) {
377 // XXXkhuey bug 1117269. When this is fixed, please consider fixing
378 // ThrowExceptionValueIfSafe over in Exceptions.cpp in the same way.
380 // IsJSContextException shouldn't be true anymore because we will report
381 // the exception on the JSContext ... so throw something else.
382 mErrorResult
.Throw(NS_ERROR_UNEXPECTED
);
387 mAutoIncumbentScript
.reset();
388 mAutoEntryScript
.reset();
390 // It is important that this is the last thing we do, after leaving the
391 // realm and undoing all our entry/incumbent script changes
392 CycleCollectedJSContext
* ccjs
= CycleCollectedJSContext::Get();
394 ccjs
->LeaveMicroTask();
398 already_AddRefed
<nsISupports
> CallbackObjectHolderBase::ToXPCOMCallback(
399 CallbackObject
* aCallback
, const nsIID
& aIID
) const {
400 MOZ_ASSERT(NS_IsMainThread());
405 // We don't init the AutoJSAPI with our callback because we don't want it
406 // reporting errors to its global's onerror handlers.
409 JSContext
* cx
= jsapi
.cx();
411 JS::Rooted
<JSObject
*> callback(cx
, aCallback
->CallbackOrNull());
416 JSAutoRealm
ar(cx
, aCallback
->CallbackGlobalOrNull());
418 RefPtr
<nsXPCWrappedJS
> wrappedJS
;
419 nsresult rv
= nsXPCWrappedJS::GetNewOrUsed(cx
, callback
, aIID
,
420 getter_AddRefs(wrappedJS
));
421 if (NS_FAILED(rv
) || !wrappedJS
) {
425 nsCOMPtr
<nsISupports
> retval
;
426 rv
= wrappedJS
->QueryInterface(aIID
, getter_AddRefs(retval
));
431 return retval
.forget();
434 } // namespace mozilla::dom