Bug 1885602 - Part 5: Implement navigating to the SUMO help topic from the menu heade...
[gecko.git] / dom / bindings / CallbackObject.cpp
blob484bba06c0bbcc5fd5834eb813822a5bcb530f00
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"
20 #include "jsapi.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)
29 NS_INTERFACE_MAP_END
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.
46 return !callback;
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)) {
58 return true;
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());
66 return true;
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
113 // here.
114 mIncumbentJSGlobal = mIncumbentGlobal->GetGlobalJSObjectPreserveColor();
116 } else {
117 // We can just forget all our stuff.
118 ClearJSReferences();
122 JSObject* CallbackObject::Callback(JSContext* aCx) {
123 JSObject* callback = CallbackOrNull();
124 if (!callback) {
125 callback = JS_NewDeadWrapper(aCx);
128 MOZ_DIAGNOSTIC_ASSERT(callback);
129 return callback;
132 void CallbackObject::GetDescription(nsACString& aOutString) {
133 JSObject* wrappedCallback = CallbackOrNull();
134 if (!wrappedCallback) {
135 aOutString.Append("<callback from a nuked compartment>");
136 return;
139 JS::Rooted<JSObject*> unwrappedCallback(
140 RootingCx(), js::CheckedUnwrapStatic(wrappedCallback));
141 if (!unwrappedCallback) {
142 aOutString.Append("<not a function>");
143 return;
146 AutoJSAPI jsapi;
147 jsapi.Init();
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>");
157 return;
160 JS::Rooted<JSString*> displayId(
161 cx, JS_GetMaybePartialFunctionDisplayId(rootedFunction));
162 if (displayId) {
163 nsAutoJSString funcNameStr;
164 if (funcNameStr.init(cx, displayId)) {
165 if (funcNameStr.IsEmpty()) {
166 aOutString.Append("<empty name>");
167 } else {
168 AppendUTF16toUTF8(funcNameStr, aOutString);
170 } else {
171 aOutString.Append("<function name string failed to materialize>");
172 jsapi.ClearException();
174 } else {
175 aOutString.Append("<anonymous>");
178 JS::Rooted<JSScript*> rootedScript(cx,
179 JS_GetFunctionScript(cx, rootedFunction));
180 if (!rootedScript) {
181 return;
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,
192 ErrorResult& aRv,
193 const char* aExecutionReason,
194 ExceptionHandling aExceptionHandling,
195 JS::Realm* aRealm,
196 bool aIsJSImplementedWebIDL)
197 : mCx(nullptr),
198 mRealm(aRealm),
199 mErrorResult(aRv),
200 mExceptionHandling(aExceptionHandling),
201 mIsMainThread(NS_IsMainThread()) {
202 MOZ_ASSERT_IF(aExceptionHandling == eReportExceptions ||
203 aExceptionHandling == eRethrowExceptions,
204 !aRealm);
206 CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
207 if (ccjs) {
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.");
223 return;
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)
237 : nullptr;
238 if (win) {
239 // We don't want to run script in windows that have been navigated away
240 // from.
241 if (!win->HasActiveDocument()) {
242 aRv.ThrowNotSupportedError(
243 "Refusing to execute function from window whose document is no "
244 "longer active.");
245 return;
247 globalObject = win;
248 } else {
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 "
259 "disabled.");
260 return;
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.");
268 return;
271 AutoAllowLegacyScriptExecution exemption;
272 mAutoEntryScript.emplace(globalObject, aExecutionReason, mIsMainThread);
273 mAutoEntryScript->SetWebIDLCallerPrincipal(webIDLCallerPrincipal);
274 nsIGlobalObject* incumbent = aCallback->IncumbentGlobalOrNull();
275 if (incumbent) {
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 "
284 "torn down.");
285 return;
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
294 // go gray again.
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());
303 if (*mAsyncStack) {
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.
315 mCx = cx;
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) {
325 MOZ_ASSERT(!mRealm);
326 return true;
329 MOZ_ASSERT(mRealm);
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()) {
335 return false;
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!
349 mAr.reset();
351 // Now, if we have a JSContext, report any pending errors on it, unless we
352 // were told to re-throw them.
353 if (mCx) {
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();
393 if (ccjs) {
394 ccjs->LeaveMicroTask();
398 already_AddRefed<nsISupports> CallbackObjectHolderBase::ToXPCOMCallback(
399 CallbackObject* aCallback, const nsIID& aIID) const {
400 MOZ_ASSERT(NS_IsMainThread());
401 if (!aCallback) {
402 return nullptr;
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.
407 AutoJSAPI jsapi;
408 jsapi.Init();
409 JSContext* cx = jsapi.cx();
411 JS::Rooted<JSObject*> callback(cx, aCallback->CallbackOrNull());
412 if (!callback) {
413 return nullptr;
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) {
422 return nullptr;
425 nsCOMPtr<nsISupports> retval;
426 rv = wrappedJS->QueryInterface(aIID, getter_AddRefs(retval));
427 if (NS_FAILED(rv)) {
428 return nullptr;
431 return retval.forget();
434 } // namespace mozilla::dom