dom/base/nsJSUtils.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* vim: set ts=2 sw=2 et tw=78: */
     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 /**
     8  * This is not a generated file. It contains common utility functions
     9  * invoked from the JavaScript code generated from IDL interfaces.
    10  * The goal of the utility functions is to cut down on the size of
    11  * the generated code itself.
    12  */
    14 #include "nsJSUtils.h"
    15 #include "jsapi.h"
    16 #include "js/OldDebugAPI.h"
    17 #include "jsfriendapi.h"
    18 #include "nsIScriptContext.h"
    19 #include "nsIScriptGlobalObject.h"
    20 #include "nsIXPConnect.h"
    21 #include "nsCOMPtr.h"
    22 #include "nsIScriptSecurityManager.h"
    23 #include "nsPIDOMWindow.h"
    24 #include "GeckoProfiler.h"
    25 #include "nsDOMJSUtils.h" // for GetScriptContextFromJSContext
    26 #include "nsJSPrincipals.h"
    27 #include "xpcpublic.h"
    28 #include "nsContentUtils.h"
    29 #include "nsGlobalWindow.h"
    31 bool
    32 nsJSUtils::GetCallingLocation(JSContext* aContext, const char* *aFilename,
    33                               uint32_t* aLineno)
    34 {
    35   JS::AutoFilename filename;
    36   unsigned lineno = 0;
    38   if (!JS::DescribeScriptedCaller(aContext, &filename, &lineno)) {
    39     return false;
    40   }
    42   *aFilename = filename.get();
    43   *aLineno = lineno;
    45   return true;
    46 }
    48 nsIScriptGlobalObject *
    49 nsJSUtils::GetStaticScriptGlobal(JSObject* aObj)
    50 {
    51   if (!aObj)
    52     return nullptr;
    53   return xpc::WindowGlobalOrNull(aObj);
    54 }
    56 nsIScriptContext *
    57 nsJSUtils::GetStaticScriptContext(JSObject* aObj)
    58 {
    59   nsIScriptGlobalObject *nativeGlobal = GetStaticScriptGlobal(aObj);
    60   if (!nativeGlobal)
    61     return nullptr;
    63   return nativeGlobal->GetScriptContext();
    64 }
    66 nsIScriptGlobalObject *
    67 nsJSUtils::GetDynamicScriptGlobal(JSContext* aContext)
    68 {
    69   nsIScriptContext *scriptCX = GetDynamicScriptContext(aContext);
    70   if (!scriptCX)
    71     return nullptr;
    72   return scriptCX->GetGlobalObject();
    73 }
    75 nsIScriptContext *
    76 nsJSUtils::GetDynamicScriptContext(JSContext *aContext)
    77 {
    78   return GetScriptContextFromJSContext(aContext);
    79 }
    81 uint64_t
    82 nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(JSContext *aContext)
    83 {
    84   if (!aContext)
    85     return 0;
    87   uint64_t innerWindowID = 0;
    89   JSObject *jsGlobal = JS::CurrentGlobalOrNull(aContext);
    90   if (jsGlobal) {
    91     nsIScriptGlobalObject *scriptGlobal = GetStaticScriptGlobal(jsGlobal);
    92     if (scriptGlobal) {
    93       nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(scriptGlobal);
    94       if (win)
    95         innerWindowID = win->WindowID();
    96     }
    97   }
    99   return innerWindowID;
   100 }
   102 void
   103 nsJSUtils::ReportPendingException(JSContext *aContext)
   104 {
   105   if (JS_IsExceptionPending(aContext)) {
   106     bool saved = JS_SaveFrameChain(aContext);
   107     {
   108       // JS_SaveFrameChain set the compartment of aContext to null, so we need
   109       // to enter a compartment.  The question is, which one? We don't want to
   110       // enter the original compartment of aContext (or the compartment of the
   111       // current exception on aContext, for that matter) because when we
   112       // JS_ReportPendingException the JS engine can try to duck-type the
   113       // exception and produce a JSErrorReport.  It will then pass that
   114       // JSErrorReport to the error reporter on aContext, which might expose
   115       // information from it to script via onerror handlers.  So it's very
   116       // important that the duck typing happen in the same compartment as the
   117       // onerror handler.  In practice, that's the compartment of the window (or
   118       // otherwise default global) of aContext, so use that here.
   119       nsIScriptContext* scx = GetScriptContextFromJSContext(aContext);
   120       JS::Rooted<JSObject*> scope(aContext);
   121       scope = scx ? scx->GetWindowProxy()
   122                   : js::DefaultObjectForContextOrNull(aContext);
   123       if (!scope) {
   124         // The SafeJSContext has no default object associated with it.
   125         MOZ_ASSERT(NS_IsMainThread());
   126         MOZ_ASSERT(aContext == nsContentUtils::GetSafeJSContext());
   127         scope = xpc::GetSafeJSContextGlobal();
   128       }
   129       JSAutoCompartment ac(aContext, scope);
   130       JS_ReportPendingException(aContext);
   131     }
   132     if (saved) {
   133       JS_RestoreFrameChain(aContext);
   134     }
   135   }
   136 }
   138 nsresult
   139 nsJSUtils::CompileFunction(JSContext* aCx,
   140                            JS::Handle<JSObject*> aTarget,
   141                            JS::CompileOptions& aOptions,
   142                            const nsACString& aName,
   143                            uint32_t aArgCount,
   144                            const char** aArgArray,
   145                            const nsAString& aBody,
   146                            JSObject** aFunctionObject)
   147 {
   148   MOZ_ASSERT(js::GetEnterCompartmentDepth(aCx) > 0);
   149   MOZ_ASSERT_IF(aTarget, js::IsObjectInContextCompartment(aTarget, aCx));
   150   MOZ_ASSERT_IF(aOptions.versionSet, aOptions.version != JSVERSION_UNKNOWN);
   151   mozilla::DebugOnly<nsIScriptContext*> ctx = GetScriptContextFromJSContext(aCx);
   152   MOZ_ASSERT_IF(ctx, ctx->IsContextInitialized());
   154   // Do the junk Gecko is supposed to do before calling into JSAPI.
   155   if (aTarget) {
   156     JS::ExposeObjectToActiveJS(aTarget);
   157   }
   159   // Compile.
   160   JSFunction* fun = JS::CompileFunction(aCx, aTarget, aOptions,
   161                                         PromiseFlatCString(aName).get(),
   162                                         aArgCount, aArgArray,
   163                                         PromiseFlatString(aBody).get(),
   164                                         aBody.Length());
   165   if (!fun) {
   166     ReportPendingException(aCx);
   167     return NS_ERROR_FAILURE;
   168   }
   170   *aFunctionObject = JS_GetFunctionObject(fun);
   171   return NS_OK;
   172 }
   174 nsresult
   175 nsJSUtils::EvaluateString(JSContext* aCx,
   176                           const nsAString& aScript,
   177                           JS::Handle<JSObject*> aScopeObject,
   178                           JS::CompileOptions& aCompileOptions,
   179                           const EvaluateOptions& aEvaluateOptions,
   180                           JS::MutableHandle<JS::Value> aRetValue,
   181                           void **aOffThreadToken)
   182 {
   183   const nsPromiseFlatString& flatScript = PromiseFlatString(aScript);
   184   JS::SourceBufferHolder srcBuf(flatScript.get(), aScript.Length(),
   185                                 JS::SourceBufferHolder::NoOwnership);
   186   return EvaluateString(aCx, srcBuf, aScopeObject, aCompileOptions,
   187                         aEvaluateOptions, aRetValue, aOffThreadToken);
   188 }
   190 nsresult
   191 nsJSUtils::EvaluateString(JSContext* aCx,
   192                           JS::SourceBufferHolder& aSrcBuf,
   193                           JS::Handle<JSObject*> aScopeObject,
   194                           JS::CompileOptions& aCompileOptions,
   195                           const EvaluateOptions& aEvaluateOptions,
   196                           JS::MutableHandle<JS::Value> aRetValue,
   197                           void **aOffThreadToken)
   198 {
   199   PROFILER_LABEL("JS", "EvaluateString");
   200   MOZ_ASSERT_IF(aCompileOptions.versionSet,
   201                 aCompileOptions.version != JSVERSION_UNKNOWN);
   202   MOZ_ASSERT_IF(aEvaluateOptions.coerceToString, aEvaluateOptions.needResult);
   203   MOZ_ASSERT_IF(!aEvaluateOptions.reportUncaught, aEvaluateOptions.needResult);
   204   MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext());
   205   MOZ_ASSERT(aSrcBuf.get());
   207   // Unfortunately, the JS engine actually compiles scripts with a return value
   208   // in a different, less efficient way.  Furthermore, it can't JIT them in many
   209   // cases.  So we need to be explicitly told whether the caller cares about the
   210   // return value.  Callers can do this by calling the other overload of
   211   // EvaluateString() which calls this function with aEvaluateOptions.needResult
   212   // set to false.
   213   aRetValue.setUndefined();
   215   JS::ExposeObjectToActiveJS(aScopeObject);
   216   nsAutoMicroTask mt;
   217   nsresult rv = NS_OK;
   219   bool ok = false;
   220   nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
   221   NS_ENSURE_TRUE(ssm->ScriptAllowed(js::GetGlobalForObjectCrossCompartment(aScopeObject)), NS_OK);
   223   mozilla::Maybe<AutoDontReportUncaught> dontReport;
   224   if (!aEvaluateOptions.reportUncaught) {
   225     // We need to prevent AutoLastFrameCheck from reporting and clearing
   226     // any pending exceptions.
   227     dontReport.construct(aCx);
   228   }
   230   // Scope the JSAutoCompartment so that we can later wrap the return value
   231   // into the caller's cx.
   232   {
   233     JSAutoCompartment ac(aCx, aScopeObject);
   235     JS::Rooted<JSObject*> rootedScope(aCx, aScopeObject);
   236     if (aOffThreadToken) {
   237       JS::Rooted<JSScript*>
   238         script(aCx, JS::FinishOffThreadScript(aCx, JS_GetRuntime(aCx), *aOffThreadToken));
   239       *aOffThreadToken = nullptr; // Mark the token as having been finished.
   240       if (script) {
   241         if (aEvaluateOptions.needResult) {
   242           ok = JS_ExecuteScript(aCx, rootedScope, script, aRetValue);
   243         } else {
   244           ok = JS_ExecuteScript(aCx, rootedScope, script);
   245         }
   246       } else {
   247         ok = false;
   248       }
   249     } else {
   250       if (aEvaluateOptions.needResult) {
   251         ok = JS::Evaluate(aCx, rootedScope, aCompileOptions,
   252                           aSrcBuf, aRetValue);
   253       } else {
   254         ok = JS::Evaluate(aCx, rootedScope, aCompileOptions,
   255                           aSrcBuf);
   256       }
   257     }
   259     if (ok && aEvaluateOptions.coerceToString && !aRetValue.isUndefined()) {
   260       JS::Rooted<JS::Value> value(aCx, aRetValue);
   261       JSString* str = JS::ToString(aCx, value);
   262       ok = !!str;
   263       aRetValue.set(ok ? JS::StringValue(str) : JS::UndefinedValue());
   264     }
   265   }
   267   if (!ok) {
   268     if (aEvaluateOptions.reportUncaught) {
   269       ReportPendingException(aCx);
   270       if (aEvaluateOptions.needResult) {
   271         aRetValue.setUndefined();
   272       }
   273     } else {
   274       rv = JS_IsExceptionPending(aCx) ? NS_ERROR_FAILURE
   275                                       : NS_ERROR_OUT_OF_MEMORY;
   276       JS::Rooted<JS::Value> exn(aCx);
   277       JS_GetPendingException(aCx, &exn);
   278       if (aEvaluateOptions.needResult) {
   279         aRetValue.set(exn);
   280       }
   281       JS_ClearPendingException(aCx);
   282     }
   283   }
   285   // Wrap the return value into whatever compartment aCx was in.
   286   if (aEvaluateOptions.needResult) {
   287     JS::Rooted<JS::Value> v(aCx, aRetValue);
   288     if (!JS_WrapValue(aCx, &v)) {
   289       return NS_ERROR_OUT_OF_MEMORY;
   290     }
   291     aRetValue.set(v);
   292   }
   293   return rv;
   294 }
   296 nsresult
   297 nsJSUtils::EvaluateString(JSContext* aCx,
   298                           const nsAString& aScript,
   299                           JS::Handle<JSObject*> aScopeObject,
   300                           JS::CompileOptions& aCompileOptions,
   301                           void **aOffThreadToken)
   302 {
   303   EvaluateOptions options;
   304   options.setNeedResult(false);
   305   JS::RootedValue unused(aCx);
   306   return EvaluateString(aCx, aScript, aScopeObject, aCompileOptions,
   307                         options, &unused, aOffThreadToken);
   308 }
   310 nsresult
   311 nsJSUtils::EvaluateString(JSContext* aCx,
   312                           JS::SourceBufferHolder& aSrcBuf,
   313                           JS::Handle<JSObject*> aScopeObject,
   314                           JS::CompileOptions& aCompileOptions,
   315                           void **aOffThreadToken)
   316 {
   317   EvaluateOptions options;
   318   options.setNeedResult(false);
   319   JS::RootedValue unused(aCx);
   320   return EvaluateString(aCx, aSrcBuf, aScopeObject, aCompileOptions,
   321                         options, &unused, aOffThreadToken);
   322 }
   324 //
   325 // nsDOMJSUtils.h
   326 //
   328 JSObject* GetDefaultScopeFromJSContext(JSContext *cx)
   329 {
   330   // DOM JSContexts don't store their default compartment object on
   331   // the cx, so in those cases we need to fetch it via the scx
   332   // instead.
   333   nsIScriptContext *scx = GetScriptContextFromJSContext(cx);
   334   if (scx) {
   335     return scx->GetWindowProxy();
   336   }
   337   return js::DefaultObjectForContextOrNull(cx);
   338 }

mercurial