CallbackObject.cpp 12 KB

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