michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* vim: set ts=8 sts=4 et sw=4 tw=99: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* Implement global service to track stack of JSContext. */ michael@0: michael@0: #include "xpcprivate.h" michael@0: #include "XPCWrapper.h" michael@0: #include "nsDOMJSUtils.h" michael@0: #include "nsNullPrincipal.h" michael@0: #include "mozilla/dom/BindingUtils.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace JS; michael@0: using namespace xpc; michael@0: using mozilla::dom::DestroyProtoAndIfaceCache; michael@0: michael@0: /***************************************************************************/ michael@0: michael@0: XPCJSContextStack::~XPCJSContextStack() michael@0: { michael@0: if (mSafeJSContext) { michael@0: mSafeJSContextGlobal = nullptr; michael@0: JS_DestroyContextNoGC(mSafeJSContext); michael@0: mSafeJSContext = nullptr; michael@0: } michael@0: } michael@0: michael@0: JSContext* michael@0: XPCJSContextStack::Pop() michael@0: { michael@0: MOZ_ASSERT(!mStack.IsEmpty()); michael@0: michael@0: uint32_t idx = mStack.Length() - 1; // The thing we're popping michael@0: michael@0: JSContext *cx = mStack[idx].cx; michael@0: michael@0: mStack.RemoveElementAt(idx); michael@0: if (idx == 0) { michael@0: js::Debug_SetActiveJSContext(mRuntime->Runtime(), nullptr); michael@0: return cx; michael@0: } michael@0: michael@0: --idx; // Advance to new top of the stack michael@0: michael@0: XPCJSContextInfo &e = mStack[idx]; michael@0: if (e.cx && e.savedFrameChain) { michael@0: // Pop() can be called outside any request for e.cx. michael@0: JSAutoRequest ar(e.cx); michael@0: JS_RestoreFrameChain(e.cx); michael@0: e.savedFrameChain = false; michael@0: } michael@0: js::Debug_SetActiveJSContext(mRuntime->Runtime(), e.cx); michael@0: return cx; michael@0: } michael@0: michael@0: bool michael@0: XPCJSContextStack::Push(JSContext *cx) michael@0: { michael@0: js::Debug_SetActiveJSContext(mRuntime->Runtime(), cx); michael@0: if (mStack.Length() == 0) { michael@0: mStack.AppendElement(cx); michael@0: return true; michael@0: } michael@0: michael@0: XPCJSContextInfo &e = mStack[mStack.Length() - 1]; michael@0: if (e.cx) { michael@0: // The cx we're pushing is also stack-top. In general we still need to michael@0: // call JS_SaveFrameChain here. But if that would put us in a michael@0: // compartment that's same-origin with the current one, we can skip it. michael@0: nsIScriptSecurityManager* ssm = XPCWrapper::GetSecurityManager(); michael@0: if ((e.cx == cx) && ssm) { michael@0: // DOM JSContexts don't store their default compartment object on michael@0: // the cx, so in those cases we need to fetch it via the scx michael@0: // instead. And in some cases (i.e. the SafeJSContext), we have no michael@0: // default compartment object at all. michael@0: RootedObject defaultScope(cx, GetDefaultScopeFromJSContext(cx)); michael@0: if (defaultScope) { michael@0: nsIPrincipal *currentPrincipal = michael@0: GetCompartmentPrincipal(js::GetContextCompartment(cx)); michael@0: nsIPrincipal *defaultPrincipal = GetObjectPrincipal(defaultScope); michael@0: if (currentPrincipal->Equals(defaultPrincipal)) { michael@0: mStack.AppendElement(cx); michael@0: return true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: { michael@0: // Push() can be called outside any request for e.cx. michael@0: JSAutoRequest ar(e.cx); michael@0: if (!JS_SaveFrameChain(e.cx)) michael@0: return false; michael@0: e.savedFrameChain = true; michael@0: } michael@0: } michael@0: michael@0: mStack.AppendElement(cx); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: XPCJSContextStack::HasJSContext(JSContext *cx) michael@0: { michael@0: for (uint32_t i = 0; i < mStack.Length(); i++) michael@0: if (cx == mStack[i].cx) michael@0: return true; michael@0: return false; michael@0: } michael@0: michael@0: static bool michael@0: SafeGlobalResolve(JSContext *cx, HandleObject obj, HandleId id) michael@0: { michael@0: bool resolved; michael@0: return JS_ResolveStandardClass(cx, obj, id, &resolved); michael@0: } michael@0: michael@0: static void michael@0: SafeFinalize(JSFreeOp *fop, JSObject* obj) michael@0: { michael@0: SandboxPrivate* sop = michael@0: static_cast(xpc_GetJSPrivate(obj)); michael@0: sop->ForgetGlobalObject(); michael@0: NS_IF_RELEASE(sop); michael@0: DestroyProtoAndIfaceCache(obj); michael@0: } michael@0: michael@0: const JSClass xpc::SafeJSContextGlobalClass = { michael@0: "global_for_XPCJSContextStack_SafeJSContext", michael@0: XPCONNECT_GLOBAL_FLAGS, michael@0: JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, michael@0: JS_EnumerateStub, SafeGlobalResolve, JS_ConvertStub, SafeFinalize, michael@0: nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook michael@0: }; michael@0: michael@0: JSContext* michael@0: XPCJSContextStack::GetSafeJSContext() michael@0: { michael@0: MOZ_ASSERT(mSafeJSContext); michael@0: return mSafeJSContext; michael@0: } michael@0: michael@0: JSObject* michael@0: XPCJSContextStack::GetSafeJSContextGlobal() michael@0: { michael@0: MOZ_ASSERT(mSafeJSContextGlobal); michael@0: return mSafeJSContextGlobal; michael@0: } michael@0: michael@0: JSContext* michael@0: XPCJSContextStack::InitSafeJSContext() michael@0: { michael@0: MOZ_ASSERT(!mSafeJSContext); michael@0: michael@0: // Start by getting the principal holder and principal for this michael@0: // context. If we can't manage that, don't bother with the rest. michael@0: nsRefPtr principal = new nsNullPrincipal(); michael@0: nsresult rv = principal->Init(); michael@0: if (NS_FAILED(rv)) michael@0: MOZ_CRASH(); michael@0: michael@0: nsXPConnect* xpc = nsXPConnect::XPConnect(); michael@0: JSRuntime *rt = xpc->GetRuntime()->Runtime(); michael@0: if (!rt) michael@0: MOZ_CRASH(); michael@0: michael@0: mSafeJSContext = JS_NewContext(rt, 8192); michael@0: if (!mSafeJSContext) michael@0: MOZ_CRASH(); michael@0: JSAutoRequest req(mSafeJSContext); michael@0: ContextOptionsRef(mSafeJSContext).setNoDefaultCompartmentObject(true); michael@0: michael@0: JS_SetErrorReporter(mSafeJSContext, xpc::SystemErrorReporter); michael@0: michael@0: JS::CompartmentOptions options; michael@0: options.setZone(JS::SystemZone) michael@0: .setTrace(TraceXPCGlobal); michael@0: mSafeJSContextGlobal = CreateGlobalObject(mSafeJSContext, michael@0: &SafeJSContextGlobalClass, michael@0: principal, options); michael@0: if (!mSafeJSContextGlobal) michael@0: MOZ_CRASH(); michael@0: michael@0: // Note: make sure to set the private before calling michael@0: // InitClasses michael@0: nsRefPtr sp = new SandboxPrivate(principal, mSafeJSContextGlobal); michael@0: JS_SetPrivate(mSafeJSContextGlobal, sp.forget().take()); michael@0: michael@0: // After this point either glob is null and the michael@0: // nsIScriptObjectPrincipal ownership is either handled by the michael@0: // nsCOMPtr or dealt with, or we'll release in the finalize michael@0: // hook. michael@0: if (NS_FAILED(xpc->InitClasses(mSafeJSContext, mSafeJSContextGlobal))) michael@0: MOZ_CRASH(); michael@0: michael@0: JS::RootedObject glob(mSafeJSContext, mSafeJSContextGlobal); michael@0: JS_FireOnNewGlobalObject(mSafeJSContext, glob); michael@0: michael@0: return mSafeJSContext; michael@0: }