123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337 |
- /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
- /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
- #include "mozilla/dom/CallbackObject.h"
- #include "mozilla/CycleCollectedJSContext.h"
- #include "mozilla/dom/BindingUtils.h"
- #include "jsfriendapi.h"
- #include "nsIScriptGlobalObject.h"
- #include "nsIXPConnect.h"
- #include "nsIScriptContext.h"
- #include "nsPIDOMWindow.h"
- #include "nsJSUtils.h"
- #include "xpcprivate.h"
- #include "WorkerPrivate.h"
- #include "nsGlobalWindow.h"
- #include "WorkerScope.h"
- #include "jsapi.h"
- #include "nsJSPrincipals.h"
- namespace mozilla {
- namespace dom {
- NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CallbackObject)
- NS_INTERFACE_MAP_ENTRY(mozilla::dom::CallbackObject)
- NS_INTERFACE_MAP_ENTRY(nsISupports)
- NS_INTERFACE_MAP_END
- NS_IMPL_CYCLE_COLLECTING_ADDREF(CallbackObject)
- NS_IMPL_CYCLE_COLLECTING_RELEASE(CallbackObject)
- NS_IMPL_CYCLE_COLLECTION_CLASS(CallbackObject)
- NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CallbackObject)
- tmp->DropJSObjects();
- NS_IMPL_CYCLE_COLLECTION_UNLINK(mIncumbentGlobal)
- NS_IMPL_CYCLE_COLLECTION_UNLINK_END
- NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CallbackObject)
- NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIncumbentGlobal)
- NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
- NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CallbackObject)
- NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCallback)
- NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCreationStack)
- NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mIncumbentJSGlobal)
- NS_IMPL_CYCLE_COLLECTION_TRACE_END
- void
- CallbackObject::Trace(JSTracer* aTracer)
- {
- JS::TraceEdge(aTracer, &mCallback, "CallbackObject.mCallback");
- JS::TraceEdge(aTracer, &mCreationStack, "CallbackObject.mCreationStack");
- JS::TraceEdge(aTracer, &mIncumbentJSGlobal,
- "CallbackObject.mIncumbentJSGlobal");
- }
- void
- CallbackObject::HoldJSObjectsIfMoreThanOneOwner()
- {
- MOZ_ASSERT(mRefCnt.get() > 0);
- if (mRefCnt.get() > 1) {
- mozilla::HoldJSObjects(this);
- } else {
- // We can just forget all our stuff.
- ClearJSReferences();
- }
- }
- CallbackObject::CallSetup::CallSetup(CallbackObject* aCallback,
- ErrorResult& aRv,
- const char* aExecutionReason,
- ExceptionHandling aExceptionHandling,
- JSCompartment* aCompartment,
- bool aIsJSImplementedWebIDL)
- : mCx(nullptr)
- , mCompartment(aCompartment)
- , mErrorResult(aRv)
- , mExceptionHandling(aExceptionHandling)
- , mIsMainThread(NS_IsMainThread())
- {
- if (mIsMainThread) {
- CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
- if (ccjs) {
- ccjs->EnterMicroTask();
- }
- }
- // Compute the caller's subject principal (if necessary) early, before we
- // do anything that might perturb the relevant state.
- nsIPrincipal* webIDLCallerPrincipal = nullptr;
- if (aIsJSImplementedWebIDL) {
- webIDLCallerPrincipal = nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller();
- }
- // First, find the real underlying callback.
- JSObject* realCallback = js::UncheckedUnwrap(aCallback->CallbackPreserveColor());
- nsIGlobalObject* globalObject = nullptr;
- JSContext* cx;
- {
- // Bug 955660: we cannot do "proper" rooting here because we need the
- // global to get a context. Everything here is simple getters that cannot
- // GC, so just paper over the necessary dataflow inversion.
- JS::AutoSuppressGCAnalysis nogc;
- // Now get the global for this callback. Note that for the case of
- // JS-implemented WebIDL we never have a window here.
- nsGlobalWindow* win = mIsMainThread && !aIsJSImplementedWebIDL
- ? xpc::WindowGlobalOrNull(realCallback)
- : nullptr;
- if (win) {
- MOZ_ASSERT(win->IsInnerWindow());
- // We don't want to run script in windows that have been navigated away
- // from.
- if (!win->AsInner()->HasActiveDocument()) {
- aRv.ThrowDOMException(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
- NS_LITERAL_CSTRING("Refusing to execute function from window "
- "whose document is no longer active."));
- return;
- }
- globalObject = win;
- } else {
- // No DOM Window. Store the global.
- JSObject* global = js::GetGlobalForObjectCrossCompartment(realCallback);
- globalObject = xpc::NativeGlobal(global);
- MOZ_ASSERT(globalObject);
- }
- // Bail out if there's no useful global. This seems to happen intermittently
- // on gaia-ui tests, probably because nsInProcessTabChildGlobal is returning
- // null in some kind of teardown state.
- if (!globalObject->GetGlobalJSObject()) {
- aRv.ThrowDOMException(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
- NS_LITERAL_CSTRING("Refusing to execute function from global which is "
- "being torn down."));
- return;
- }
- mAutoEntryScript.emplace(globalObject, aExecutionReason, mIsMainThread);
- mAutoEntryScript->SetWebIDLCallerPrincipal(webIDLCallerPrincipal);
- nsIGlobalObject* incumbent = aCallback->IncumbentGlobalOrNull();
- if (incumbent) {
- // The callback object traces its incumbent JS global, so in general it
- // should be alive here. However, it's possible that we could run afoul
- // of the same IPC global weirdness described above, wherein the
- // nsIGlobalObject has severed its reference to the JS global. Let's just
- // be safe here, so that nobody has to waste a day debugging gaia-ui tests.
- if (!incumbent->GetGlobalJSObject()) {
- aRv.ThrowDOMException(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
- NS_LITERAL_CSTRING("Refusing to execute function because our "
- "incumbent global is being torn down."));
- return;
- }
- mAutoIncumbentScript.emplace(incumbent);
- }
- cx = mAutoEntryScript->cx();
- // Unmark the callable (by invoking Callback() and not the CallbackPreserveColor()
- // variant), and stick it in a Rooted before it can go gray again.
- // Nothing before us in this function can trigger a CC, so it's safe to wait
- // until here it do the unmark. This allows us to construct mRootedCallable
- // with the cx from mAutoEntryScript, avoiding the cost of finding another
- // JSContext. (Rooted<> does not care about requests or compartments.)
- mRootedCallable.emplace(cx, aCallback->Callback());
- }
- // JS-implemented WebIDL is always OK to run, since it runs with Chrome
- // privileges anyway.
- if (mIsMainThread && !aIsJSImplementedWebIDL) {
- // Check that it's ok to run this callback at all.
- // Make sure to use realCallback to get the global of the callback object,
- // not the wrapper.
- bool allowed = xpc::Scriptability::Get(realCallback).Allowed();
- if (!allowed) {
- aRv.ThrowDOMException(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
- NS_LITERAL_CSTRING("Refusing to execute function from global in which "
- "script is disabled."));
- return;
- }
- }
- mAsyncStack.emplace(cx, aCallback->GetCreationStack());
- if (*mAsyncStack) {
- mAsyncStackSetter.emplace(cx, *mAsyncStack, aExecutionReason);
- }
- // Enter the compartment of our callback, so we can actually work with it.
- //
- // Note that if the callback is a wrapper, this will not be the same
- // compartment that we ended up in with mAutoEntryScript above, because the
- // entry point is based off of the unwrapped callback (realCallback).
- mAc.emplace(cx, *mRootedCallable);
- // And now we're ready to go.
- mCx = cx;
- }
- bool
- CallbackObject::CallSetup::ShouldRethrowException(JS::Handle<JS::Value> aException)
- {
- if (mExceptionHandling == eRethrowExceptions) {
- if (!mCompartment) {
- // Caller didn't ask us to filter for only exceptions we subsume.
- return true;
- }
- // On workers, we don't have nsIPrincipals to work with. But we also only
- // have one compartment, so check whether mCompartment is the same as the
- // current compartment of mCx.
- if (mCompartment == js::GetContextCompartment(mCx)) {
- return true;
- }
- MOZ_ASSERT(NS_IsMainThread());
- // At this point mCx is in the compartment of our unwrapped callback, so
- // just check whether the principal of mCompartment subsumes that of the
- // current compartment/global of mCx.
- nsIPrincipal* callerPrincipal =
- nsJSPrincipals::get(JS_GetCompartmentPrincipals(mCompartment));
- nsIPrincipal* calleePrincipal = nsContentUtils::SubjectPrincipal();
- if (callerPrincipal->SubsumesConsideringDomain(calleePrincipal)) {
- return true;
- }
- }
- MOZ_ASSERT(mCompartment);
- // Now we only want to throw an exception to the caller if the object that was
- // thrown is in the caller compartment (which we stored in mCompartment).
- if (!aException.isObject()) {
- return false;
- }
- JS::Rooted<JSObject*> obj(mCx, &aException.toObject());
- obj = js::UncheckedUnwrap(obj, /* stopAtWindowProxy = */ false);
- return js::GetObjectCompartment(obj) == mCompartment;
- }
- CallbackObject::CallSetup::~CallSetup()
- {
- // To get our nesting right we have to destroy our JSAutoCompartment first.
- // In particular, we want to do this before we try reporting any exceptions,
- // so we end up reporting them while in the compartment of our entry point,
- // not whatever cross-compartment wrappper mCallback might be.
- // Be careful: the JSAutoCompartment might not have been constructed at all!
- mAc.reset();
- // Now, if we have a JSContext, report any pending errors on it, unless we
- // were told to re-throw them.
- if (mCx) {
- bool needToDealWithException = mAutoEntryScript->HasException();
- if ((mCompartment && mExceptionHandling == eRethrowContentExceptions) ||
- mExceptionHandling == eRethrowExceptions) {
- mErrorResult.MightThrowJSException();
- if (needToDealWithException) {
- JS::Rooted<JS::Value> exn(mCx);
- if (mAutoEntryScript->PeekException(&exn) &&
- ShouldRethrowException(exn)) {
- mAutoEntryScript->ClearException();
- MOZ_ASSERT(!mAutoEntryScript->HasException());
- mErrorResult.ThrowJSException(mCx, exn);
- needToDealWithException = false;
- }
- }
- }
- if (needToDealWithException) {
- // Either we're supposed to report our exceptions, or we're supposed to
- // re-throw them but we failed to get the exception value. Either way,
- // we'll just report the pending exception, if any, once ~mAutoEntryScript
- // runs. Note that we've already run ~mAc, effectively, so we don't have
- // to worry about ordering here.
- if (mErrorResult.IsJSContextException()) {
- // XXXkhuey bug 1117269. When this is fixed, please consider fixing
- // ThrowExceptionValueIfSafe over in Exceptions.cpp in the same way.
- // IsJSContextException shouldn't be true anymore because we will report
- // the exception on the JSContext ... so throw something else.
- mErrorResult.Throw(NS_ERROR_UNEXPECTED);
- }
- }
- }
- mAutoIncumbentScript.reset();
- mAutoEntryScript.reset();
- // It is important that this is the last thing we do, after leaving the
- // compartment and undoing all our entry/incumbent script changes
- if (mIsMainThread) {
- CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
- if (ccjs) {
- ccjs->LeaveMicroTask();
- }
- }
- }
- already_AddRefed<nsISupports>
- CallbackObjectHolderBase::ToXPCOMCallback(CallbackObject* aCallback,
- const nsIID& aIID) const
- {
- MOZ_ASSERT(NS_IsMainThread());
- if (!aCallback) {
- return nullptr;
- }
- // We don't init the AutoJSAPI with our callback because we don't want it
- // reporting errors to its global's onerror handlers.
- AutoJSAPI jsapi;
- jsapi.Init();
- JSContext* cx = jsapi.cx();
- JS::Rooted<JSObject*> callback(cx, aCallback->Callback());
- JSAutoCompartment ac(cx, callback);
- RefPtr<nsXPCWrappedJS> wrappedJS;
- nsresult rv =
- nsXPCWrappedJS::GetNewOrUsed(callback, aIID, getter_AddRefs(wrappedJS));
- if (NS_FAILED(rv) || !wrappedJS) {
- return nullptr;
- }
- nsCOMPtr<nsISupports> retval;
- rv = wrappedJS->QueryInterface(aIID, getter_AddRefs(retval));
- if (NS_FAILED(rv)) {
- return nullptr;
- }
- return retval.forget();
- }
- } // namespace dom
- } // namespace mozilla
|