diff -r 000000000000 -r 6474c204b198 dom/base/nsJSUtils.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dom/base/nsJSUtils.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,338 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 sw=2 et tw=78: */ +/* 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/. */ + +/** + * This is not a generated file. It contains common utility functions + * invoked from the JavaScript code generated from IDL interfaces. + * The goal of the utility functions is to cut down on the size of + * the generated code itself. + */ + +#include "nsJSUtils.h" +#include "jsapi.h" +#include "js/OldDebugAPI.h" +#include "jsfriendapi.h" +#include "nsIScriptContext.h" +#include "nsIScriptGlobalObject.h" +#include "nsIXPConnect.h" +#include "nsCOMPtr.h" +#include "nsIScriptSecurityManager.h" +#include "nsPIDOMWindow.h" +#include "GeckoProfiler.h" +#include "nsDOMJSUtils.h" // for GetScriptContextFromJSContext +#include "nsJSPrincipals.h" +#include "xpcpublic.h" +#include "nsContentUtils.h" +#include "nsGlobalWindow.h" + +bool +nsJSUtils::GetCallingLocation(JSContext* aContext, const char* *aFilename, + uint32_t* aLineno) +{ + JS::AutoFilename filename; + unsigned lineno = 0; + + if (!JS::DescribeScriptedCaller(aContext, &filename, &lineno)) { + return false; + } + + *aFilename = filename.get(); + *aLineno = lineno; + + return true; +} + +nsIScriptGlobalObject * +nsJSUtils::GetStaticScriptGlobal(JSObject* aObj) +{ + if (!aObj) + return nullptr; + return xpc::WindowGlobalOrNull(aObj); +} + +nsIScriptContext * +nsJSUtils::GetStaticScriptContext(JSObject* aObj) +{ + nsIScriptGlobalObject *nativeGlobal = GetStaticScriptGlobal(aObj); + if (!nativeGlobal) + return nullptr; + + return nativeGlobal->GetScriptContext(); +} + +nsIScriptGlobalObject * +nsJSUtils::GetDynamicScriptGlobal(JSContext* aContext) +{ + nsIScriptContext *scriptCX = GetDynamicScriptContext(aContext); + if (!scriptCX) + return nullptr; + return scriptCX->GetGlobalObject(); +} + +nsIScriptContext * +nsJSUtils::GetDynamicScriptContext(JSContext *aContext) +{ + return GetScriptContextFromJSContext(aContext); +} + +uint64_t +nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(JSContext *aContext) +{ + if (!aContext) + return 0; + + uint64_t innerWindowID = 0; + + JSObject *jsGlobal = JS::CurrentGlobalOrNull(aContext); + if (jsGlobal) { + nsIScriptGlobalObject *scriptGlobal = GetStaticScriptGlobal(jsGlobal); + if (scriptGlobal) { + nsCOMPtr win = do_QueryInterface(scriptGlobal); + if (win) + innerWindowID = win->WindowID(); + } + } + + return innerWindowID; +} + +void +nsJSUtils::ReportPendingException(JSContext *aContext) +{ + if (JS_IsExceptionPending(aContext)) { + bool saved = JS_SaveFrameChain(aContext); + { + // JS_SaveFrameChain set the compartment of aContext to null, so we need + // to enter a compartment. The question is, which one? We don't want to + // enter the original compartment of aContext (or the compartment of the + // current exception on aContext, for that matter) because when we + // JS_ReportPendingException the JS engine can try to duck-type the + // exception and produce a JSErrorReport. It will then pass that + // JSErrorReport to the error reporter on aContext, which might expose + // information from it to script via onerror handlers. So it's very + // important that the duck typing happen in the same compartment as the + // onerror handler. In practice, that's the compartment of the window (or + // otherwise default global) of aContext, so use that here. + nsIScriptContext* scx = GetScriptContextFromJSContext(aContext); + JS::Rooted scope(aContext); + scope = scx ? scx->GetWindowProxy() + : js::DefaultObjectForContextOrNull(aContext); + if (!scope) { + // The SafeJSContext has no default object associated with it. + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aContext == nsContentUtils::GetSafeJSContext()); + scope = xpc::GetSafeJSContextGlobal(); + } + JSAutoCompartment ac(aContext, scope); + JS_ReportPendingException(aContext); + } + if (saved) { + JS_RestoreFrameChain(aContext); + } + } +} + +nsresult +nsJSUtils::CompileFunction(JSContext* aCx, + JS::Handle aTarget, + JS::CompileOptions& aOptions, + const nsACString& aName, + uint32_t aArgCount, + const char** aArgArray, + const nsAString& aBody, + JSObject** aFunctionObject) +{ + MOZ_ASSERT(js::GetEnterCompartmentDepth(aCx) > 0); + MOZ_ASSERT_IF(aTarget, js::IsObjectInContextCompartment(aTarget, aCx)); + MOZ_ASSERT_IF(aOptions.versionSet, aOptions.version != JSVERSION_UNKNOWN); + mozilla::DebugOnly ctx = GetScriptContextFromJSContext(aCx); + MOZ_ASSERT_IF(ctx, ctx->IsContextInitialized()); + + // Do the junk Gecko is supposed to do before calling into JSAPI. + if (aTarget) { + JS::ExposeObjectToActiveJS(aTarget); + } + + // Compile. + JSFunction* fun = JS::CompileFunction(aCx, aTarget, aOptions, + PromiseFlatCString(aName).get(), + aArgCount, aArgArray, + PromiseFlatString(aBody).get(), + aBody.Length()); + if (!fun) { + ReportPendingException(aCx); + return NS_ERROR_FAILURE; + } + + *aFunctionObject = JS_GetFunctionObject(fun); + return NS_OK; +} + +nsresult +nsJSUtils::EvaluateString(JSContext* aCx, + const nsAString& aScript, + JS::Handle aScopeObject, + JS::CompileOptions& aCompileOptions, + const EvaluateOptions& aEvaluateOptions, + JS::MutableHandle aRetValue, + void **aOffThreadToken) +{ + const nsPromiseFlatString& flatScript = PromiseFlatString(aScript); + JS::SourceBufferHolder srcBuf(flatScript.get(), aScript.Length(), + JS::SourceBufferHolder::NoOwnership); + return EvaluateString(aCx, srcBuf, aScopeObject, aCompileOptions, + aEvaluateOptions, aRetValue, aOffThreadToken); +} + +nsresult +nsJSUtils::EvaluateString(JSContext* aCx, + JS::SourceBufferHolder& aSrcBuf, + JS::Handle aScopeObject, + JS::CompileOptions& aCompileOptions, + const EvaluateOptions& aEvaluateOptions, + JS::MutableHandle aRetValue, + void **aOffThreadToken) +{ + PROFILER_LABEL("JS", "EvaluateString"); + MOZ_ASSERT_IF(aCompileOptions.versionSet, + aCompileOptions.version != JSVERSION_UNKNOWN); + MOZ_ASSERT_IF(aEvaluateOptions.coerceToString, aEvaluateOptions.needResult); + MOZ_ASSERT_IF(!aEvaluateOptions.reportUncaught, aEvaluateOptions.needResult); + MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext()); + MOZ_ASSERT(aSrcBuf.get()); + + // Unfortunately, the JS engine actually compiles scripts with a return value + // in a different, less efficient way. Furthermore, it can't JIT them in many + // cases. So we need to be explicitly told whether the caller cares about the + // return value. Callers can do this by calling the other overload of + // EvaluateString() which calls this function with aEvaluateOptions.needResult + // set to false. + aRetValue.setUndefined(); + + JS::ExposeObjectToActiveJS(aScopeObject); + nsAutoMicroTask mt; + nsresult rv = NS_OK; + + bool ok = false; + nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); + NS_ENSURE_TRUE(ssm->ScriptAllowed(js::GetGlobalForObjectCrossCompartment(aScopeObject)), NS_OK); + + mozilla::Maybe dontReport; + if (!aEvaluateOptions.reportUncaught) { + // We need to prevent AutoLastFrameCheck from reporting and clearing + // any pending exceptions. + dontReport.construct(aCx); + } + + // Scope the JSAutoCompartment so that we can later wrap the return value + // into the caller's cx. + { + JSAutoCompartment ac(aCx, aScopeObject); + + JS::Rooted rootedScope(aCx, aScopeObject); + if (aOffThreadToken) { + JS::Rooted + script(aCx, JS::FinishOffThreadScript(aCx, JS_GetRuntime(aCx), *aOffThreadToken)); + *aOffThreadToken = nullptr; // Mark the token as having been finished. + if (script) { + if (aEvaluateOptions.needResult) { + ok = JS_ExecuteScript(aCx, rootedScope, script, aRetValue); + } else { + ok = JS_ExecuteScript(aCx, rootedScope, script); + } + } else { + ok = false; + } + } else { + if (aEvaluateOptions.needResult) { + ok = JS::Evaluate(aCx, rootedScope, aCompileOptions, + aSrcBuf, aRetValue); + } else { + ok = JS::Evaluate(aCx, rootedScope, aCompileOptions, + aSrcBuf); + } + } + + if (ok && aEvaluateOptions.coerceToString && !aRetValue.isUndefined()) { + JS::Rooted value(aCx, aRetValue); + JSString* str = JS::ToString(aCx, value); + ok = !!str; + aRetValue.set(ok ? JS::StringValue(str) : JS::UndefinedValue()); + } + } + + if (!ok) { + if (aEvaluateOptions.reportUncaught) { + ReportPendingException(aCx); + if (aEvaluateOptions.needResult) { + aRetValue.setUndefined(); + } + } else { + rv = JS_IsExceptionPending(aCx) ? NS_ERROR_FAILURE + : NS_ERROR_OUT_OF_MEMORY; + JS::Rooted exn(aCx); + JS_GetPendingException(aCx, &exn); + if (aEvaluateOptions.needResult) { + aRetValue.set(exn); + } + JS_ClearPendingException(aCx); + } + } + + // Wrap the return value into whatever compartment aCx was in. + if (aEvaluateOptions.needResult) { + JS::Rooted v(aCx, aRetValue); + if (!JS_WrapValue(aCx, &v)) { + return NS_ERROR_OUT_OF_MEMORY; + } + aRetValue.set(v); + } + return rv; +} + +nsresult +nsJSUtils::EvaluateString(JSContext* aCx, + const nsAString& aScript, + JS::Handle aScopeObject, + JS::CompileOptions& aCompileOptions, + void **aOffThreadToken) +{ + EvaluateOptions options; + options.setNeedResult(false); + JS::RootedValue unused(aCx); + return EvaluateString(aCx, aScript, aScopeObject, aCompileOptions, + options, &unused, aOffThreadToken); +} + +nsresult +nsJSUtils::EvaluateString(JSContext* aCx, + JS::SourceBufferHolder& aSrcBuf, + JS::Handle aScopeObject, + JS::CompileOptions& aCompileOptions, + void **aOffThreadToken) +{ + EvaluateOptions options; + options.setNeedResult(false); + JS::RootedValue unused(aCx); + return EvaluateString(aCx, aSrcBuf, aScopeObject, aCompileOptions, + options, &unused, aOffThreadToken); +} + +// +// nsDOMJSUtils.h +// + +JSObject* GetDefaultScopeFromJSContext(JSContext *cx) +{ + // DOM JSContexts don't store their default compartment object on + // the cx, so in those cases we need to fetch it via the scx + // instead. + nsIScriptContext *scx = GetScriptContextFromJSContext(cx); + if (scx) { + return scx->GetWindowProxy(); + } + return js::DefaultObjectForContextOrNull(cx); +}