dom/bindings/CallbackObject.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
     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/dom/BindingUtils.h"
     9 #include "mozilla/dom/DOMError.h"
    10 #include "mozilla/dom/DOMErrorBinding.h"
    11 #include "jsfriendapi.h"
    12 #include "nsIScriptGlobalObject.h"
    13 #include "nsIXPConnect.h"
    14 #include "nsIScriptContext.h"
    15 #include "nsPIDOMWindow.h"
    16 #include "nsJSUtils.h"
    17 #include "nsCxPusher.h"
    18 #include "nsIScriptSecurityManager.h"
    19 #include "xpcprivate.h"
    20 #include "WorkerPrivate.h"
    21 #include "nsGlobalWindow.h"
    22 #include "WorkerScope.h"
    24 namespace mozilla {
    25 namespace dom {
    27 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CallbackObject)
    28   NS_INTERFACE_MAP_ENTRY(mozilla::dom::CallbackObject)
    29   NS_INTERFACE_MAP_ENTRY(nsISupports)
    30 NS_INTERFACE_MAP_END
    32 NS_IMPL_CYCLE_COLLECTING_ADDREF(CallbackObject)
    33 NS_IMPL_CYCLE_COLLECTING_RELEASE(CallbackObject)
    35 NS_IMPL_CYCLE_COLLECTION_CLASS(CallbackObject)
    37 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CallbackObject)
    38   tmp->DropJSObjects();
    39   NS_IMPL_CYCLE_COLLECTION_UNLINK(mIncumbentGlobal)
    40 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
    41 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CallbackObject)
    42   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
    43   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIncumbentGlobal)
    44 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
    45 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CallbackObject)
    46   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCallback)
    47   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mIncumbentJSGlobal)
    48 NS_IMPL_CYCLE_COLLECTION_TRACE_END
    50 CallbackObject::CallSetup::CallSetup(CallbackObject* aCallback,
    51                                      ErrorResult& aRv,
    52                                      ExceptionHandling aExceptionHandling,
    53                                      JSCompartment* aCompartment,
    54                                      bool aIsJSImplementedWebIDL)
    55   : mCx(nullptr)
    56   , mCompartment(aCompartment)
    57   , mErrorResult(aRv)
    58   , mExceptionHandling(aExceptionHandling)
    59   , mIsMainThread(NS_IsMainThread())
    60 {
    61   if (mIsMainThread) {
    62     nsContentUtils::EnterMicroTask();
    63   }
    65   // Compute the caller's subject principal (if necessary) early, before we
    66   // do anything that might perturb the relevant state.
    67   nsIPrincipal* webIDLCallerPrincipal = nullptr;
    68   if (aIsJSImplementedWebIDL) {
    69     webIDLCallerPrincipal = nsContentUtils::GetSubjectPrincipal();
    70   }
    72   // We need to produce a useful JSContext here.  Ideally one that the callback
    73   // is in some sense associated with, so that we can sort of treat it as a
    74   // "script entry point".  Though once we actually have script entry points,
    75   // we'll need to do the script entry point bits once we have an actual
    76   // callable.
    78   // First, find the real underlying callback.
    79   JSObject* realCallback = js::UncheckedUnwrap(aCallback->CallbackPreserveColor());
    80   JSContext* cx = nullptr;
    81   nsIGlobalObject* globalObject = nullptr;
    83   {
    84     JS::AutoAssertNoGC nogc;
    85     if (mIsMainThread) {
    86       // Now get the global and JSContext for this callback.
    87       nsGlobalWindow* win = xpc::WindowGlobalOrNull(realCallback);
    88       if (win) {
    89         // Make sure that if this is a window it's the current inner, since the
    90         // nsIScriptContext and hence JSContext are associated with the outer
    91         // window.  Which means that if someone holds on to a function from a
    92         // now-unloaded document we'd have the new document as the script entry
    93         // point...
    94         MOZ_ASSERT(win->IsInnerWindow());
    95         nsPIDOMWindow* outer = win->GetOuterWindow();
    96         if (!outer || win != outer->GetCurrentInnerWindow()) {
    97           // Just bail out from here
    98           return;
    99         }
   100         cx = win->GetContext() ? win->GetContext()->GetNativeContext()
   101                                // This happens - Removing it causes
   102                                // test_bug293235.xul to go orange.
   103                                : nsContentUtils::GetSafeJSContext();
   104         globalObject = win;
   105       } else {
   106         // No DOM Window. Store the global and use the SafeJSContext.
   107         JSObject* glob = js::GetGlobalForObjectCrossCompartment(realCallback);
   108         globalObject = xpc::GetNativeForGlobal(glob);
   109         MOZ_ASSERT(globalObject);
   110         cx = nsContentUtils::GetSafeJSContext();
   111       }
   112     } else {
   113       cx = workers::GetCurrentThreadJSContext();
   114       globalObject = workers::GetCurrentThreadWorkerPrivate()->GlobalScope();
   115     }
   117     // Bail out if there's no useful global. This seems to happen intermittently
   118     // on gaia-ui tests, probably because nsInProcessTabChildGlobal is returning
   119     // null in some kind of teardown state.
   120     if (!globalObject->GetGlobalJSObject()) {
   121       return;
   122     }
   124     mAutoEntryScript.construct(globalObject, mIsMainThread, cx);
   125     mAutoEntryScript.ref().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         return;
   135       }
   136       mAutoIncumbentScript.construct(incumbent);
   137     }
   139     // Unmark the callable (by invoking Callback() and not the CallbackPreserveColor()
   140     // variant), and stick it in a Rooted before it can go gray again.
   141     // Nothing before us in this function can trigger a CC, so it's safe to wait
   142     // until here it do the unmark. This allows us to order the following two
   143     // operations _after_ the Push() above, which lets us take advantage of the
   144     // JSAutoRequest embedded in the pusher.
   145     //
   146     // We can do this even though we're not in the right compartment yet, because
   147     // Rooted<> does not care about compartments.
   148     mRootedCallable.construct(cx, aCallback->Callback());
   149   }
   151   if (mIsMainThread) {
   152     // Check that it's ok to run this callback at all.
   153     // Make sure to use realCallback to get the global of the callback object,
   154     // not the wrapper.
   155     bool allowed = nsContentUtils::GetSecurityManager()->
   156       ScriptAllowed(js::GetGlobalForObjectCrossCompartment(realCallback));
   158     if (!allowed) {
   159       return;
   160     }
   161   }
   163   // Enter the compartment of our callback, so we can actually work with it.
   164   //
   165   // Note that if the callback is a wrapper, this will not be the same
   166   // compartment that we ended up in with mAutoEntryScript above, because the
   167   // entry point is based off of the unwrapped callback (realCallback).
   168   mAc.construct(cx, mRootedCallable.ref());
   170   // And now we're ready to go.
   171   mCx = cx;
   173   // Make sure the JS engine doesn't report exceptions we want to re-throw
   174   if ((mCompartment && mExceptionHandling == eRethrowContentExceptions) ||
   175       mExceptionHandling == eRethrowExceptions) {
   176     mSavedJSContextOptions = JS::ContextOptionsRef(cx);
   177     JS::ContextOptionsRef(cx).setDontReportUncaught(true);
   178   }
   179 }
   181 bool
   182 CallbackObject::CallSetup::ShouldRethrowException(JS::Handle<JS::Value> aException)
   183 {
   184   if (mExceptionHandling == eRethrowExceptions) {
   185     return true;
   186   }
   188   MOZ_ASSERT(mExceptionHandling == eRethrowContentExceptions);
   190   // For eRethrowContentExceptions we only want to throw an exception if the
   191   // object that was thrown is a DOMError object in the caller compartment
   192   // (which we stored in mCompartment).
   194   if (!aException.isObject()) {
   195     return false;
   196   }
   198   JS::Rooted<JSObject*> obj(mCx, &aException.toObject());
   199   obj = js::UncheckedUnwrap(obj, /* stopAtOuter = */ false);
   200   if (js::GetObjectCompartment(obj) != mCompartment) {
   201     return false;
   202   }
   204   DOMError* domError;
   205   return NS_SUCCEEDED(UNWRAP_OBJECT(DOMError, obj, domError));
   206 }
   208 CallbackObject::CallSetup::~CallSetup()
   209 {
   210   // To get our nesting right we have to destroy our JSAutoCompartment first.
   211   // In particular, we want to do this before we try reporting any exceptions,
   212   // so we end up reporting them while in the compartment of our entry point,
   213   // not whatever cross-compartment wrappper mCallback might be.
   214   // Be careful: the JSAutoCompartment might not have been constructed at all!
   215   mAc.destroyIfConstructed();
   217   // Now, if we have a JSContext, report any pending errors on it, unless we
   218   // were told to re-throw them.
   219   if (mCx) {
   220     bool dealtWithPendingException = false;
   221     if ((mCompartment && mExceptionHandling == eRethrowContentExceptions) ||
   222         mExceptionHandling == eRethrowExceptions) {
   223       // Restore the old context options
   224       JS::ContextOptionsRef(mCx) = mSavedJSContextOptions;
   225       mErrorResult.MightThrowJSException();
   226       if (JS_IsExceptionPending(mCx)) {
   227         JS::Rooted<JS::Value> exn(mCx);
   228         if (JS_GetPendingException(mCx, &exn) &&
   229             ShouldRethrowException(exn)) {
   230           mErrorResult.ThrowJSException(mCx, exn);
   231           JS_ClearPendingException(mCx);
   232           dealtWithPendingException = true;
   233         }
   234       }
   235     }
   237     if (!dealtWithPendingException) {
   238       // Either we're supposed to report our exceptions, or we're supposed to
   239       // re-throw them but we failed to JS_GetPendingException.  Either way,
   240       // just report the pending exception, if any.
   241       //
   242       // We don't use nsJSUtils::ReportPendingException here because all it
   243       // does at this point is JS_SaveFrameChain and enter a compartment around
   244       // a JS_ReportPendingException call.  But our mAutoEntryScript should
   245       // already do a JS_SaveFrameChain and we are already in the compartment
   246       // we want to be in, so all nsJSUtils::ReportPendingException would do is
   247       // screw up our compartment, which is exactly what we do not want.
   248       //
   249       // XXXbz FIXME: bug 979525 means we don't always JS_SaveFrameChain here,
   250       // so we need to go ahead and do that.
   251       JS::Rooted<JSObject*> oldGlobal(mCx, JS::CurrentGlobalOrNull(mCx));
   252       MOZ_ASSERT(oldGlobal, "How can we not have a global here??");
   253       bool saved = JS_SaveFrameChain(mCx);
   254       // Make sure the JSAutoCompartment goes out of scope before the
   255       // JS_RestoreFrameChain call!
   256       {
   257         JSAutoCompartment ac(mCx, oldGlobal);
   258         MOZ_ASSERT(!JS::DescribeScriptedCaller(mCx),
   259                    "Our comment above about JS_SaveFrameChain having been "
   260                    "called is a lie?");
   261         JS_ReportPendingException(mCx);
   262       }
   263       if (saved) {
   264         JS_RestoreFrameChain(mCx);
   265       }
   266     }
   267   }
   269   mAutoIncumbentScript.destroyIfConstructed();
   270   mAutoEntryScript.destroyIfConstructed();
   272   // It is important that this is the last thing we do, after leaving the
   273   // compartment and undoing all our entry/incumbent script changes
   274   if (mIsMainThread) {
   275     nsContentUtils::LeaveMicroTask();
   276   }
   277 }
   279 already_AddRefed<nsISupports>
   280 CallbackObjectHolderBase::ToXPCOMCallback(CallbackObject* aCallback,
   281                                           const nsIID& aIID) const
   282 {
   283   MOZ_ASSERT(NS_IsMainThread());
   284   if (!aCallback) {
   285     return nullptr;
   286   }
   288   AutoSafeJSContext cx;
   290   JS::Rooted<JSObject*> callback(cx, aCallback->Callback());
   292   JSAutoCompartment ac(cx, callback);
   293   nsRefPtr<nsXPCWrappedJS> wrappedJS;
   294   nsresult rv =
   295     nsXPCWrappedJS::GetNewOrUsed(callback, aIID, getter_AddRefs(wrappedJS));
   296   if (NS_FAILED(rv) || !wrappedJS) {
   297     return nullptr;
   298   }
   300   nsCOMPtr<nsISupports> retval;
   301   rv = wrappedJS->QueryInterface(aIID, getter_AddRefs(retval));
   302   if (NS_FAILED(rv)) {
   303     return nullptr;
   304   }
   306   return retval.forget();
   307 }
   309 } // namespace dom
   310 } // namespace mozilla

mercurial