1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/bindings/CallbackObject.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,310 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim:set ts=2 sw=2 sts=2 et cindent: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "mozilla/dom/CallbackObject.h" 1.11 +#include "mozilla/dom/BindingUtils.h" 1.12 +#include "mozilla/dom/DOMError.h" 1.13 +#include "mozilla/dom/DOMErrorBinding.h" 1.14 +#include "jsfriendapi.h" 1.15 +#include "nsIScriptGlobalObject.h" 1.16 +#include "nsIXPConnect.h" 1.17 +#include "nsIScriptContext.h" 1.18 +#include "nsPIDOMWindow.h" 1.19 +#include "nsJSUtils.h" 1.20 +#include "nsCxPusher.h" 1.21 +#include "nsIScriptSecurityManager.h" 1.22 +#include "xpcprivate.h" 1.23 +#include "WorkerPrivate.h" 1.24 +#include "nsGlobalWindow.h" 1.25 +#include "WorkerScope.h" 1.26 + 1.27 +namespace mozilla { 1.28 +namespace dom { 1.29 + 1.30 +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CallbackObject) 1.31 + NS_INTERFACE_MAP_ENTRY(mozilla::dom::CallbackObject) 1.32 + NS_INTERFACE_MAP_ENTRY(nsISupports) 1.33 +NS_INTERFACE_MAP_END 1.34 + 1.35 +NS_IMPL_CYCLE_COLLECTING_ADDREF(CallbackObject) 1.36 +NS_IMPL_CYCLE_COLLECTING_RELEASE(CallbackObject) 1.37 + 1.38 +NS_IMPL_CYCLE_COLLECTION_CLASS(CallbackObject) 1.39 + 1.40 +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CallbackObject) 1.41 + tmp->DropJSObjects(); 1.42 + NS_IMPL_CYCLE_COLLECTION_UNLINK(mIncumbentGlobal) 1.43 +NS_IMPL_CYCLE_COLLECTION_UNLINK_END 1.44 +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CallbackObject) 1.45 + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS 1.46 + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIncumbentGlobal) 1.47 +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 1.48 +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CallbackObject) 1.49 + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCallback) 1.50 + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mIncumbentJSGlobal) 1.51 +NS_IMPL_CYCLE_COLLECTION_TRACE_END 1.52 + 1.53 +CallbackObject::CallSetup::CallSetup(CallbackObject* aCallback, 1.54 + ErrorResult& aRv, 1.55 + ExceptionHandling aExceptionHandling, 1.56 + JSCompartment* aCompartment, 1.57 + bool aIsJSImplementedWebIDL) 1.58 + : mCx(nullptr) 1.59 + , mCompartment(aCompartment) 1.60 + , mErrorResult(aRv) 1.61 + , mExceptionHandling(aExceptionHandling) 1.62 + , mIsMainThread(NS_IsMainThread()) 1.63 +{ 1.64 + if (mIsMainThread) { 1.65 + nsContentUtils::EnterMicroTask(); 1.66 + } 1.67 + 1.68 + // Compute the caller's subject principal (if necessary) early, before we 1.69 + // do anything that might perturb the relevant state. 1.70 + nsIPrincipal* webIDLCallerPrincipal = nullptr; 1.71 + if (aIsJSImplementedWebIDL) { 1.72 + webIDLCallerPrincipal = nsContentUtils::GetSubjectPrincipal(); 1.73 + } 1.74 + 1.75 + // We need to produce a useful JSContext here. Ideally one that the callback 1.76 + // is in some sense associated with, so that we can sort of treat it as a 1.77 + // "script entry point". Though once we actually have script entry points, 1.78 + // we'll need to do the script entry point bits once we have an actual 1.79 + // callable. 1.80 + 1.81 + // First, find the real underlying callback. 1.82 + JSObject* realCallback = js::UncheckedUnwrap(aCallback->CallbackPreserveColor()); 1.83 + JSContext* cx = nullptr; 1.84 + nsIGlobalObject* globalObject = nullptr; 1.85 + 1.86 + { 1.87 + JS::AutoAssertNoGC nogc; 1.88 + if (mIsMainThread) { 1.89 + // Now get the global and JSContext for this callback. 1.90 + nsGlobalWindow* win = xpc::WindowGlobalOrNull(realCallback); 1.91 + if (win) { 1.92 + // Make sure that if this is a window it's the current inner, since the 1.93 + // nsIScriptContext and hence JSContext are associated with the outer 1.94 + // window. Which means that if someone holds on to a function from a 1.95 + // now-unloaded document we'd have the new document as the script entry 1.96 + // point... 1.97 + MOZ_ASSERT(win->IsInnerWindow()); 1.98 + nsPIDOMWindow* outer = win->GetOuterWindow(); 1.99 + if (!outer || win != outer->GetCurrentInnerWindow()) { 1.100 + // Just bail out from here 1.101 + return; 1.102 + } 1.103 + cx = win->GetContext() ? win->GetContext()->GetNativeContext() 1.104 + // This happens - Removing it causes 1.105 + // test_bug293235.xul to go orange. 1.106 + : nsContentUtils::GetSafeJSContext(); 1.107 + globalObject = win; 1.108 + } else { 1.109 + // No DOM Window. Store the global and use the SafeJSContext. 1.110 + JSObject* glob = js::GetGlobalForObjectCrossCompartment(realCallback); 1.111 + globalObject = xpc::GetNativeForGlobal(glob); 1.112 + MOZ_ASSERT(globalObject); 1.113 + cx = nsContentUtils::GetSafeJSContext(); 1.114 + } 1.115 + } else { 1.116 + cx = workers::GetCurrentThreadJSContext(); 1.117 + globalObject = workers::GetCurrentThreadWorkerPrivate()->GlobalScope(); 1.118 + } 1.119 + 1.120 + // Bail out if there's no useful global. This seems to happen intermittently 1.121 + // on gaia-ui tests, probably because nsInProcessTabChildGlobal is returning 1.122 + // null in some kind of teardown state. 1.123 + if (!globalObject->GetGlobalJSObject()) { 1.124 + return; 1.125 + } 1.126 + 1.127 + mAutoEntryScript.construct(globalObject, mIsMainThread, cx); 1.128 + mAutoEntryScript.ref().SetWebIDLCallerPrincipal(webIDLCallerPrincipal); 1.129 + nsIGlobalObject* incumbent = aCallback->IncumbentGlobalOrNull(); 1.130 + if (incumbent) { 1.131 + // The callback object traces its incumbent JS global, so in general it 1.132 + // should be alive here. However, it's possible that we could run afoul 1.133 + // of the same IPC global weirdness described above, wherein the 1.134 + // nsIGlobalObject has severed its reference to the JS global. Let's just 1.135 + // be safe here, so that nobody has to waste a day debugging gaia-ui tests. 1.136 + if (!incumbent->GetGlobalJSObject()) { 1.137 + return; 1.138 + } 1.139 + mAutoIncumbentScript.construct(incumbent); 1.140 + } 1.141 + 1.142 + // Unmark the callable (by invoking Callback() and not the CallbackPreserveColor() 1.143 + // variant), and stick it in a Rooted before it can go gray again. 1.144 + // Nothing before us in this function can trigger a CC, so it's safe to wait 1.145 + // until here it do the unmark. This allows us to order the following two 1.146 + // operations _after_ the Push() above, which lets us take advantage of the 1.147 + // JSAutoRequest embedded in the pusher. 1.148 + // 1.149 + // We can do this even though we're not in the right compartment yet, because 1.150 + // Rooted<> does not care about compartments. 1.151 + mRootedCallable.construct(cx, aCallback->Callback()); 1.152 + } 1.153 + 1.154 + if (mIsMainThread) { 1.155 + // Check that it's ok to run this callback at all. 1.156 + // Make sure to use realCallback to get the global of the callback object, 1.157 + // not the wrapper. 1.158 + bool allowed = nsContentUtils::GetSecurityManager()-> 1.159 + ScriptAllowed(js::GetGlobalForObjectCrossCompartment(realCallback)); 1.160 + 1.161 + if (!allowed) { 1.162 + return; 1.163 + } 1.164 + } 1.165 + 1.166 + // Enter the compartment of our callback, so we can actually work with it. 1.167 + // 1.168 + // Note that if the callback is a wrapper, this will not be the same 1.169 + // compartment that we ended up in with mAutoEntryScript above, because the 1.170 + // entry point is based off of the unwrapped callback (realCallback). 1.171 + mAc.construct(cx, mRootedCallable.ref()); 1.172 + 1.173 + // And now we're ready to go. 1.174 + mCx = cx; 1.175 + 1.176 + // Make sure the JS engine doesn't report exceptions we want to re-throw 1.177 + if ((mCompartment && mExceptionHandling == eRethrowContentExceptions) || 1.178 + mExceptionHandling == eRethrowExceptions) { 1.179 + mSavedJSContextOptions = JS::ContextOptionsRef(cx); 1.180 + JS::ContextOptionsRef(cx).setDontReportUncaught(true); 1.181 + } 1.182 +} 1.183 + 1.184 +bool 1.185 +CallbackObject::CallSetup::ShouldRethrowException(JS::Handle<JS::Value> aException) 1.186 +{ 1.187 + if (mExceptionHandling == eRethrowExceptions) { 1.188 + return true; 1.189 + } 1.190 + 1.191 + MOZ_ASSERT(mExceptionHandling == eRethrowContentExceptions); 1.192 + 1.193 + // For eRethrowContentExceptions we only want to throw an exception if the 1.194 + // object that was thrown is a DOMError object in the caller compartment 1.195 + // (which we stored in mCompartment). 1.196 + 1.197 + if (!aException.isObject()) { 1.198 + return false; 1.199 + } 1.200 + 1.201 + JS::Rooted<JSObject*> obj(mCx, &aException.toObject()); 1.202 + obj = js::UncheckedUnwrap(obj, /* stopAtOuter = */ false); 1.203 + if (js::GetObjectCompartment(obj) != mCompartment) { 1.204 + return false; 1.205 + } 1.206 + 1.207 + DOMError* domError; 1.208 + return NS_SUCCEEDED(UNWRAP_OBJECT(DOMError, obj, domError)); 1.209 +} 1.210 + 1.211 +CallbackObject::CallSetup::~CallSetup() 1.212 +{ 1.213 + // To get our nesting right we have to destroy our JSAutoCompartment first. 1.214 + // In particular, we want to do this before we try reporting any exceptions, 1.215 + // so we end up reporting them while in the compartment of our entry point, 1.216 + // not whatever cross-compartment wrappper mCallback might be. 1.217 + // Be careful: the JSAutoCompartment might not have been constructed at all! 1.218 + mAc.destroyIfConstructed(); 1.219 + 1.220 + // Now, if we have a JSContext, report any pending errors on it, unless we 1.221 + // were told to re-throw them. 1.222 + if (mCx) { 1.223 + bool dealtWithPendingException = false; 1.224 + if ((mCompartment && mExceptionHandling == eRethrowContentExceptions) || 1.225 + mExceptionHandling == eRethrowExceptions) { 1.226 + // Restore the old context options 1.227 + JS::ContextOptionsRef(mCx) = mSavedJSContextOptions; 1.228 + mErrorResult.MightThrowJSException(); 1.229 + if (JS_IsExceptionPending(mCx)) { 1.230 + JS::Rooted<JS::Value> exn(mCx); 1.231 + if (JS_GetPendingException(mCx, &exn) && 1.232 + ShouldRethrowException(exn)) { 1.233 + mErrorResult.ThrowJSException(mCx, exn); 1.234 + JS_ClearPendingException(mCx); 1.235 + dealtWithPendingException = true; 1.236 + } 1.237 + } 1.238 + } 1.239 + 1.240 + if (!dealtWithPendingException) { 1.241 + // Either we're supposed to report our exceptions, or we're supposed to 1.242 + // re-throw them but we failed to JS_GetPendingException. Either way, 1.243 + // just report the pending exception, if any. 1.244 + // 1.245 + // We don't use nsJSUtils::ReportPendingException here because all it 1.246 + // does at this point is JS_SaveFrameChain and enter a compartment around 1.247 + // a JS_ReportPendingException call. But our mAutoEntryScript should 1.248 + // already do a JS_SaveFrameChain and we are already in the compartment 1.249 + // we want to be in, so all nsJSUtils::ReportPendingException would do is 1.250 + // screw up our compartment, which is exactly what we do not want. 1.251 + // 1.252 + // XXXbz FIXME: bug 979525 means we don't always JS_SaveFrameChain here, 1.253 + // so we need to go ahead and do that. 1.254 + JS::Rooted<JSObject*> oldGlobal(mCx, JS::CurrentGlobalOrNull(mCx)); 1.255 + MOZ_ASSERT(oldGlobal, "How can we not have a global here??"); 1.256 + bool saved = JS_SaveFrameChain(mCx); 1.257 + // Make sure the JSAutoCompartment goes out of scope before the 1.258 + // JS_RestoreFrameChain call! 1.259 + { 1.260 + JSAutoCompartment ac(mCx, oldGlobal); 1.261 + MOZ_ASSERT(!JS::DescribeScriptedCaller(mCx), 1.262 + "Our comment above about JS_SaveFrameChain having been " 1.263 + "called is a lie?"); 1.264 + JS_ReportPendingException(mCx); 1.265 + } 1.266 + if (saved) { 1.267 + JS_RestoreFrameChain(mCx); 1.268 + } 1.269 + } 1.270 + } 1.271 + 1.272 + mAutoIncumbentScript.destroyIfConstructed(); 1.273 + mAutoEntryScript.destroyIfConstructed(); 1.274 + 1.275 + // It is important that this is the last thing we do, after leaving the 1.276 + // compartment and undoing all our entry/incumbent script changes 1.277 + if (mIsMainThread) { 1.278 + nsContentUtils::LeaveMicroTask(); 1.279 + } 1.280 +} 1.281 + 1.282 +already_AddRefed<nsISupports> 1.283 +CallbackObjectHolderBase::ToXPCOMCallback(CallbackObject* aCallback, 1.284 + const nsIID& aIID) const 1.285 +{ 1.286 + MOZ_ASSERT(NS_IsMainThread()); 1.287 + if (!aCallback) { 1.288 + return nullptr; 1.289 + } 1.290 + 1.291 + AutoSafeJSContext cx; 1.292 + 1.293 + JS::Rooted<JSObject*> callback(cx, aCallback->Callback()); 1.294 + 1.295 + JSAutoCompartment ac(cx, callback); 1.296 + nsRefPtr<nsXPCWrappedJS> wrappedJS; 1.297 + nsresult rv = 1.298 + nsXPCWrappedJS::GetNewOrUsed(callback, aIID, getter_AddRefs(wrappedJS)); 1.299 + if (NS_FAILED(rv) || !wrappedJS) { 1.300 + return nullptr; 1.301 + } 1.302 + 1.303 + nsCOMPtr<nsISupports> retval; 1.304 + rv = wrappedJS->QueryInterface(aIID, getter_AddRefs(retval)); 1.305 + if (NS_FAILED(rv)) { 1.306 + return nullptr; 1.307 + } 1.308 + 1.309 + return retval.forget(); 1.310 +} 1.311 + 1.312 +} // namespace dom 1.313 +} // namespace mozilla