LCOV - code coverage report
Current view: top level - dom/bindings - CallbackObject.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 72 173 41.6 %
Date: 2017-07-14 16:53:18 Functions: 8 16 50.0 %
Legend: Lines: hit not hit

          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

Generated by: LCOV version 1.13