Bug 976348 - Change DescribeScriptedCaller to not return a JSScript (r=bholley)
authorLuke Wagner <luke@mozilla.com>
Tue, 25 Feb 2014 09:43:14 -0600
changeset 187758 9dcd0eff1b231ad7441491274ae824ee4f9b0fd3
parent 187757 e4dd4dd96cbe1c8f5952f44cd54227b2bb6e14ad
child 187759 5d9a2c52728afef2770f3a9bec363e88b5100dbc
push id3503
push userraliiev@mozilla.com
push dateMon, 28 Apr 2014 18:51:11 +0000
treeherdermozilla-beta@c95ac01e332e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbholley
bugs976348
milestone30.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 976348 - Change DescribeScriptedCaller to not return a JSScript (r=bholley)
caps/src/nsScriptSecurityManager.cpp
content/base/src/WebSocket.cpp
dom/base/ScriptSettings.cpp
dom/base/nsGlobalWindow.cpp
dom/base/nsJSEnvironment.cpp
dom/base/nsJSUtils.cpp
dom/workers/RuntimeService.cpp
dom/workers/WorkerPrivate.cpp
js/src/jsapi-tests/testDebugger.cpp
js/src/jsapi.cpp
js/src/jsapi.h
js/src/shell/js.cpp
js/xpconnect/loader/mozJSSubScriptLoader.cpp
js/xpconnect/src/Sandbox.cpp
js/xpconnect/src/XPCShellImpl.cpp
js/xpconnect/src/XPCWrappedJSClass.cpp
--- a/caps/src/nsScriptSecurityManager.cpp
+++ b/caps/src/nsScriptSecurityManager.cpp
@@ -390,19 +390,19 @@ nsScriptSecurityManager::ContentSecurity
         return true; // fail open to not break sites.
     }
 
     if (reportViolation) {
         nsAutoString fileName;
         unsigned lineNum = 0;
         NS_NAMED_LITERAL_STRING(scriptSample, "call to eval() or related function blocked by CSP");
 
-        JS::RootedScript script(cx);
-        if (JS_DescribeScriptedCaller(cx, &script, &lineNum)) {
-            if (const char *file = JS_GetScriptFilename(cx, script)) {
+        JS::AutoFilename scriptFilename;
+        if (JS::DescribeScriptedCaller(cx, &scriptFilename, &lineNum)) {
+            if (const char *file = scriptFilename.get()) {
                 CopyUTF8toUTF16(nsDependentCString(file), fileName);
             }
         }
         csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL,
                                  fileName,
                                  scriptSample,
                                  lineNum,
                                  EmptyString(),
--- a/content/base/src/WebSocket.cpp
+++ b/content/base/src/WebSocket.cpp
@@ -662,19 +662,19 @@ WebSocket::Init(JSContext* aCx,
   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
   NS_ENSURE_STATE(os);
   rv = os->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC, true);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = os->AddObserver(this, DOM_WINDOW_FROZEN_TOPIC, true);
   NS_ENSURE_SUCCESS(rv, rv);
 
   unsigned lineno;
-  JS::Rooted<JSScript*> script(aCx);
-  if (JS_DescribeScriptedCaller(aCx, &script, &lineno)) {
-    mScriptFile = JS_GetScriptFilename(aCx, script);
+  JS::AutoFilename file;
+  if (JS::DescribeScriptedCaller(aCx, &file, &lineno)) {
+    mScriptFile = file.get();
     mScriptLine = lineno;
   }
 
   // Get WindowID
   mInnerWindowID = nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(aCx);
 
   // parses the url
   rv = ParseURL(PromiseFlatString(aURL));
--- a/dom/base/ScriptSettings.cpp
+++ b/dom/base/ScriptSettings.cpp
@@ -138,20 +138,17 @@ GetIncumbentGlobal()
     MOZ_ASSERT(ScriptSettingsStack::Ref().EntryGlobal() == nullptr);
     return nullptr;
   }
 
   // See what the JS engine has to say. If we've got a scripted caller
   // override in place, the JS engine will lie to us and pretend that
   // there's nothing on the JS stack, which will cause us to check the
   // incumbent script stack below.
-  JS::RootedScript script(cx);
-  if (JS_DescribeScriptedCaller(cx, &script, nullptr)) {
-    JS::RootedObject global(cx, JS_GetGlobalFromScript(script));
-    MOZ_ASSERT(global);
+  if (JSObject *global = JS::GetScriptedCallerGlobal(cx)) {
     return xpc::GetNativeForGlobal(global);
   }
 
   // Ok, nothing from the JS engine. Let's use whatever's on the
   // explicit stack.
   return ScriptSettingsStack::Ref().IncumbentGlobal();
 }
 
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -10531,19 +10531,19 @@ nsGlobalWindow::ShowSlowScriptDialog()
 
   // Get the nsIPrompt interface from the docshell
   nsCOMPtr<nsIDocShell> ds = GetDocShell();
   NS_ENSURE_TRUE(ds, KillSlowScript);
   nsCOMPtr<nsIPrompt> prompt = do_GetInterface(ds);
   NS_ENSURE_TRUE(prompt, KillSlowScript);
 
   // Check if we should offer the option to debug
-  JS::Rooted<JSScript*> script(cx);
+  JS::AutoFilename filename;
   unsigned lineno;
-  bool hasFrame = JS_DescribeScriptedCaller(cx, &script, &lineno);
+  bool hasFrame = JS::DescribeScriptedCaller(cx, &filename, &lineno);
 
   bool debugPossible = hasFrame && js::CanCallContextDebugHandler(cx);
 #ifdef MOZ_JSDEBUGGER
   // Get the debugger service if necessary.
   if (debugPossible) {
     bool jsds_IsOn = false;
     const char jsdServiceCtrID[] = "@mozilla.org/js/jsd/debugger-service;1";
     nsCOMPtr<jsdIExecutionHook> jsdHook;
@@ -10618,33 +10618,30 @@ nsGlobalWindow::ShowSlowScriptDialog()
   // GetStringFromName can return NS_OK and still give nullptr string
   if (NS_FAILED(rv) || !title || !msg || !stopButton || !waitButton ||
       (!debugButton && debugPossible) || !neverShowDlg) {
     NS_ERROR("Failed to get localized strings.");
     return ContinueSlowScript;
   }
 
   // Append file and line number information, if available
-  if (script) {
-    const char *filename = JS_GetScriptFilename(cx, script);
-    if (filename) {
-      nsXPIDLString scriptLocation;
-      NS_ConvertUTF8toUTF16 filenameUTF16(filename);
-      const char16_t *formatParams[] = { filenameUTF16.get() };
-      rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
-                                                 "KillScriptLocation",
-                                                 formatParams,
-                                                 scriptLocation);
-
-      if (NS_SUCCEEDED(rv) && scriptLocation) {
-        msg.AppendLiteral("\n\n");
-        msg.Append(scriptLocation);
-        msg.Append(':');
-        msg.AppendInt(lineno);
-      }
+  if (filename.get()) {
+    nsXPIDLString scriptLocation;
+    NS_ConvertUTF8toUTF16 filenameUTF16(filename.get());
+    const char16_t *formatParams[] = { filenameUTF16.get() };
+    rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+                                               "KillScriptLocation",
+                                               formatParams,
+                                               scriptLocation);
+
+    if (NS_SUCCEEDED(rv) && scriptLocation) {
+      msg.AppendLiteral("\n\n");
+      msg.Append(scriptLocation);
+      msg.Append(':');
+      msg.AppendInt(lineno);
     }
   }
 
   int32_t buttonPressed = 0; // In case the user exits dialog by clicking X.
   bool neverShowDlgChk = false;
   uint32_t buttonFlags = nsIPrompt::BUTTON_POS_1_DEFAULT +
                          (nsIPrompt::BUTTON_TITLE_IS_STRING *
                           (nsIPrompt::BUTTON_POS_0 + nsIPrompt::BUTTON_POS_1));
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -521,18 +521,17 @@ void
 NS_ScriptErrorReporter(JSContext *cx,
                        const char *message,
                        JSErrorReport *report)
 {
   // We don't want to report exceptions too eagerly, but warnings in the
   // absence of werror are swallowed whole, so report those now.
   if (!JSREPORT_IS_WARNING(report->flags)) {
     nsIXPConnect* xpc = nsContentUtils::XPConnect();
-    JS::Rooted<JSScript*> script(cx);
-    if (JS_DescribeScriptedCaller(cx, &script, nullptr)) {
+    if (JS::DescribeScriptedCaller(cx)) {
       xpc->MarkErrorUnreported(cx);
       return;
     }
 
     if (xpc) {
       nsAXPCNativeCallContext *cc = nullptr;
       xpc->GetCurrentNativeCallContext(&cc);
       if (cc) {
--- a/dom/base/nsJSUtils.cpp
+++ b/dom/base/nsJSUtils.cpp
@@ -27,24 +27,24 @@
 #include "xpcpublic.h"
 #include "nsContentUtils.h"
 #include "nsGlobalWindow.h"
 
 bool
 nsJSUtils::GetCallingLocation(JSContext* aContext, const char* *aFilename,
                               uint32_t* aLineno)
 {
-  JS::Rooted<JSScript*> script(aContext);
+  JS::AutoFilename filename;
   unsigned lineno = 0;
 
-  if (!JS_DescribeScriptedCaller(aContext, &script, &lineno)) {
+  if (!JS::DescribeScriptedCaller(aContext, &filename, &lineno)) {
     return false;
   }
 
-  *aFilename = ::JS_GetScriptFilename(aContext, script);
+  *aFilename = filename.get();
   *aLineno = lineno;
 
   return true;
 }
 
 nsIScriptGlobalObject *
 nsJSUtils::GetStaticScriptGlobal(JSObject* aObj)
 {
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -658,21 +658,19 @@ ContentSecurityPolicyAllows(JSContext* a
 {
   WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx);
   worker->AssertIsOnWorkerThread();
 
   if (worker->GetReportCSPViolations()) {
     nsString fileName;
     uint32_t lineNum = 0;
 
-    JS::Rooted<JSScript*> script(aCx);
-    const char* file;
-    if (JS_DescribeScriptedCaller(aCx, &script, &lineNum) &&
-        (file = JS_GetScriptFilename(aCx, script))) {
-      fileName = NS_ConvertUTF8toUTF16(file);
+    JS::AutoFilename file;
+    if (JS::DescribeScriptedCaller(aCx, &file, &lineNum) && file.get()) {
+      fileName = NS_ConvertUTF8toUTF16(file.get());
     } else {
       JS_ReportPendingException(aCx);
     }
 
     nsRefPtr<LogViolationDetailsRunnable> runnable =
         new LogViolationDetailsRunnable(worker, fileName, lineNum);
 
     if (!runnable->Dispatch(aCx)) {
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -3848,44 +3848,42 @@ WorkerPrivate::GetLoadInfo(JSContext* aC
 
       loadInfo.mXHRParamsAllowed = perm == nsIPermissionManager::ALLOW_ACTION;
     } else {
       // Not a window
       MOZ_ASSERT(isChrome);
 
       // We're being created outside of a window. Need to figure out the script
       // that is creating us in order for us to use relative URIs later on.
-      JS::Rooted<JSScript*> script(aCx);
-      if (JS_DescribeScriptedCaller(aCx, &script, nullptr)) {
-        const char* fileName = JS_GetScriptFilename(aCx, script);
-
+      JS::AutoFilename fileName;
+      if (JS::DescribeScriptedCaller(aCx, &fileName)) {
         // In most cases, fileName is URI. In a few other cases
         // (e.g. xpcshell), fileName is a file path. Ideally, we would
         // prefer testing whether fileName parses as an URI and fallback
         // to file path in case of error, but Windows file paths have
         // the interesting property that they can be parsed as bogus
         // URIs (e.g. C:/Windows/Tmp is interpreted as scheme "C",
         // hostname "Windows", path "Tmp"), which defeats this algorithm.
         // Therefore, we adopt the opposite convention.
         nsCOMPtr<nsIFile> scriptFile =
           do_CreateInstance("@mozilla.org/file/local;1", &rv);
         if (NS_FAILED(rv)) {
           return rv;
         }
 
-        rv = scriptFile->InitWithPath(NS_ConvertUTF8toUTF16(fileName));
+        rv = scriptFile->InitWithPath(NS_ConvertUTF8toUTF16(fileName.get()));
         if (NS_SUCCEEDED(rv)) {
           rv = NS_NewFileURI(getter_AddRefs(loadInfo.mBaseURI),
                              scriptFile);
         }
         if (NS_FAILED(rv)) {
           // As expected, fileName is not a path, so proceed with
           // a uri.
           rv = NS_NewURI(getter_AddRefs(loadInfo.mBaseURI),
-                         fileName);
+                         fileName.get());
         }
         if (NS_FAILED(rv)) {
           return rv;
         }
       }
       loadInfo.mXHRParamsAllowed = true;
     }
 
--- a/js/src/jsapi-tests/testDebugger.cpp
+++ b/js/src/jsapi-tests/testDebugger.cpp
@@ -5,16 +5,18 @@
  * 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/. */
 
 #include "jscntxt.h"
 
 #include "js/OldDebugAPI.h"
 #include "jsapi-tests/tests.h"
 
+using namespace js;
+
 static int callCounts[2] = {0, 0};
 
 static void *
 callCountHook(JSContext *cx, JSAbstractFramePtr frame, bool isConstructing, bool before,
               bool *ok, void *closure)
 {
     callCounts[before]++;
 
@@ -248,23 +250,23 @@ BEGIN_TEST(testDebugger_singleStepThrow)
              "try { f(); }\n"
              "catch (x) { e = x; }\n");
         return true;
     }
 
     static bool
     setStepMode(JSContext *cx, unsigned argc, jsval *vp)
     {
-        JS::RootedScript script(cx);
-        JS_DescribeScriptedCaller(cx, &script, nullptr);
-        JS_ASSERT(script);
+        CallArgs args = CallArgsFromVp(argc, vp);
 
-        if (!JS_SetSingleStepMode(cx, script, true))
+        NonBuiltinScriptFrameIter iter(cx);
+        if (!JS_SetSingleStepMode(cx, iter.script(), true))
             return false;
-        JS_SET_RVAL(cx, vp, JSVAL_VOID);
+
+        args.rval().set(UndefinedValue());
         return true;
     }
 
     static JSTrapStatus
     onStep(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, void *closure)
     {
         return JSTRAP_CONTINUE;
     }
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -6107,39 +6107,78 @@ JS_IsIdentifier(JSContext *cx, HandleStr
     JSLinearString* linearStr = str->ensureLinear(cx);
     if (!linearStr)
         return false;
 
     *isIdentifier = js::frontend::IsIdentifier(linearStr);
     return true;
 }
 
+namespace JS {
+
+void
+AutoFilename::reset(void *newScriptSource)
+{
+    if (newScriptSource)
+        reinterpret_cast<ScriptSource*>(newScriptSource)->incref();
+    if (scriptSource_)
+        reinterpret_cast<ScriptSource*>(scriptSource_)->decref();
+    scriptSource_ = newScriptSource;
+}
+
+const char *
+AutoFilename::get() const
+{
+    JS_ASSERT(scriptSource_);
+    return reinterpret_cast<ScriptSource*>(scriptSource_)->filename();
+}
+
 JS_PUBLIC_API(bool)
-JS_DescribeScriptedCaller(JSContext *cx, MutableHandleScript script, unsigned *lineno)
-{
-    script.set(nullptr);
+DescribeScriptedCaller(JSContext *cx, AutoFilename *filename, unsigned *lineno)
+{
     if (lineno)
         *lineno = 0;
 
     NonBuiltinScriptFrameIter i(cx);
     if (i.done())
         return false;
 
-    // If the caller is hidden, the embedding wants us to return null here so
-    // that it can check its own stack.
+    // If the caller is hidden, the embedding wants us to return false here so
+    // that it can check its own stack (see HideScriptedCaller).
     if (i.activation()->scriptedCallerIsHidden())
         return false;
 
-    script.set(i.script());
+    if (filename)
+        filename->reset(i.script()->scriptSource());
     if (lineno)
         *lineno = js::PCToLineNumber(i.script(), i.pc());
     return true;
 }
 
-namespace JS {
+JS_PUBLIC_API(JSObject *)
+GetScriptedCallerGlobal(JSContext *cx)
+{
+    NonBuiltinScriptFrameIter i(cx);
+    if (i.done())
+        return nullptr;
+
+    // If the caller is hidden, the embedding wants us to return null here so
+    // that it can check its own stack (see HideScriptedCaller).
+    if (i.activation()->scriptedCallerIsHidden())
+        return nullptr;
+
+    GlobalObject *global = i.activation()->compartment()->maybeGlobal();
+
+    // Noone should be running code in the atoms compartment or running code in
+    // a compartment without any live objects, so there should definitely be a
+    // live global.
+    JS_ASSERT(global);
+
+    return global;
+}
 
 JS_PUBLIC_API(void)
 HideScriptedCaller(JSContext *cx)
 {
     MOZ_ASSERT(cx);
 
     // If there's no accessible activation on the stack, we'll return null from
     // JS_DescribeScriptedCaller anyway, so there's no need to annotate
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -4699,27 +4699,51 @@ extern JS_PUBLIC_API(bool)
 JS_CharsToId(JSContext* cx, JS::TwoByteChars chars, JS::MutableHandleId);
 
 /*
  *  Test if the given string is a valid ECMAScript identifier
  */
 extern JS_PUBLIC_API(bool)
 JS_IsIdentifier(JSContext *cx, JS::HandleString str, bool *isIdentifier);
 
+namespace JS {
+
 /*
- * Return the current script and line number of the most currently running
+ * AutoFilename encapsulates a pointer to a C-string and keeps the C-string
+ * alive for as long as the associated AutoFilename object is alive.
+ */
+class MOZ_STACK_CLASS JS_PUBLIC_API(AutoFilename)
+{
+    void *scriptSource_;
+
+    AutoFilename(const AutoFilename &) MOZ_DELETE;
+    void operator=(const AutoFilename &) MOZ_DELETE;
+
+  public:
+    AutoFilename() : scriptSource_(nullptr) {}
+    ~AutoFilename() { reset(nullptr); }
+
+    const char *get() const;
+
+    void reset(void *newScriptSource);
+};
+
+/*
+ * Return the current filename and line number of the most currently running
  * frame. Returns true if a scripted frame was found, false otherwise.
  *
  * If a the embedding has hidden the scripted caller for the topmost activation
  * record, this will also return false.
  */
 extern JS_PUBLIC_API(bool)
-JS_DescribeScriptedCaller(JSContext *cx, JS::MutableHandleScript script, unsigned *lineno);
-
-namespace JS {
+DescribeScriptedCaller(JSContext *cx, AutoFilename *filename = nullptr,
+                       unsigned *lineno = nullptr);
+
+extern JS_PUBLIC_API(JSObject *)
+GetScriptedCallerGlobal(JSContext *cx);
 
 /*
  * Informs the JS engine that the scripted caller should be hidden. This can be
  * used by the embedding to maintain an override of the scripted caller in its
  * calculations, by hiding the scripted caller in the JS engine and pushing data
  * onto a separate stack, which it inspects when JS_DescribeScriptedCaller
  * returns null.
  *
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -572,24 +572,16 @@ Version(JSContext *cx, unsigned argc, js
             return false;
         }
         JS_SetVersionForCompartment(js::GetContextCompartment(cx), JSVersion(v));
         args.rval().setInt32(origVersion);
     }
     return true;
 }
 
-static JSScript *
-GetTopScript(JSContext *cx)
-{
-    RootedScript script(cx);
-    JS_DescribeScriptedCaller(cx, &script, nullptr);
-    return script;
-}
-
 /*
  * Resolve a (possibly) relative filename to an absolute path. If
  * |scriptRelative| is true, then the result will be relative to the directory
  * containing the currently-running script, or the current working directory if
  * the currently-running script is "-e" (namely, you're using it from the
  * command line.) Otherwise, it will be relative to the current working
  * directory.
  */
@@ -612,29 +604,33 @@ ResolvePath(JSContext *cx, HandleString 
     if (strlen(pathname) > 3 && isalpha(pathname[0]) && pathname[1] == ':' && pathname[2] == '\\')
         return filenameStr;
     // "\\..."
     if (strlen(pathname) > 2 && pathname[1] == '\\' && pathname[2] == '\\')
         return filenameStr;
 #endif
 
     /* Get the currently executing script's name. */
-    RootedScript script(cx, GetTopScript(cx));
-    if (!script->filename())
+    JS::AutoFilename scriptFilename;
+    if (!DescribeScriptedCaller(cx, &scriptFilename))
         return nullptr;
-    if (strcmp(script->filename(), "-e") == 0 || strcmp(script->filename(), "typein") == 0)
+
+    if (!scriptFilename.get())
+        return nullptr;
+
+    if (strcmp(scriptFilename.get(), "-e") == 0 || strcmp(scriptFilename.get(), "typein") == 0)
         scriptRelative = false;
 
     static char buffer[PATH_MAX+1];
     if (scriptRelative) {
 #ifdef XP_WIN
         // The docs say it can return EINVAL, but the compiler says it's void
-        _splitpath(script->filename(), nullptr, buffer, nullptr, nullptr);
+        _splitpath(scriptFilename.get(), nullptr, buffer, nullptr, nullptr);
 #else
-        strncpy(buffer, script->filename(), PATH_MAX+1);
+        strncpy(buffer, scriptFilename.get(), PATH_MAX+1);
         if (buffer[PATH_MAX] != '\0')
             return nullptr;
 
         // dirname(buffer) might return buffer, or it might return a
         // statically-allocated string
         memmove(buffer, dirname(buffer), strlen(buffer) + 1);
 #endif
     } else {
@@ -1652,16 +1648,23 @@ SetDebug(JSContext *cx, unsigned argc, j
      */
 
     bool ok = !!JS_SetDebugMode(cx, args[0].toBoolean());
     if (ok)
         args.rval().setBoolean(true);
     return ok;
 }
 
+static JSScript *
+GetTopScript(JSContext *cx)
+{
+    NonBuiltinScriptFrameIter iter(cx);
+    return iter.done() ? nullptr : iter.script();
+}
+
 static bool
 GetScriptAndPCArgs(JSContext *cx, unsigned argc, jsval *argv, MutableHandleScript scriptp,
                    int32_t *ip)
 {
     RootedScript script(cx, GetTopScript(cx));
     *ip = 0;
     if (argc != 0) {
         jsval v = argv[0];
@@ -2690,20 +2693,20 @@ EvalInContext(JSContext *cx, unsigned ar
             return false;
     }
 
     if (srclen == 0) {
         args.rval().setObject(*sobj);
         return true;
     }
 
-    RootedScript script(cx);
+    JS::AutoFilename filename;
     unsigned lineno;
 
-    JS_DescribeScriptedCaller(cx, &script, &lineno);
+    DescribeScriptedCaller(cx, &filename, &lineno);
     {
         Maybe<JSAutoCompartment> ac;
         unsigned flags;
         JSObject *unwrapped = UncheckedUnwrap(sobj, true, &flags);
         if (flags & Wrapper::CROSS_COMPARTMENT) {
             sobj = unwrapped;
             ac.construct(cx, sobj);
         }
@@ -2711,17 +2714,17 @@ EvalInContext(JSContext *cx, unsigned ar
         sobj = GetInnerObject(cx, sobj);
         if (!sobj)
             return false;
         if (!(sobj->getClass()->flags & JSCLASS_IS_GLOBAL)) {
             JS_ReportError(cx, "Invalid scope argument to evalcx");
             return false;
         }
         if (!JS_EvaluateUCScript(cx, sobj, src, srclen,
-                                 script->filename(),
+                                 filename.get(),
                                  lineno,
                                  args.rval())) {
             return false;
         }
     }
 
     if (!cx->compartment()->wrap(cx, args.rval()))
         return false;
@@ -4009,46 +4012,53 @@ DecompileFunction(JSContext *cx, unsigne
 {
     return DecompileFunctionSomehow(cx, argc, vp, JS_DecompileFunction);
 }
 
 static bool
 DecompileThisScript(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-    RootedScript script (cx);
-    if (!JS_DescribeScriptedCaller(cx, &script, nullptr)) {
+
+    NonBuiltinScriptFrameIter iter(cx);
+    if (iter.done()) {
         args.rval().setString(cx->runtime()->emptyString);
         return true;
     }
 
     {
-        JSAutoCompartment ac(cx, script);
+        JSAutoCompartment ac(cx, iter.script());
+
+        RootedScript script(cx, iter.script());
         JSString *result = JS_DecompileScript(cx, script, "test", 0);
         if (!result)
             return false;
+
         args.rval().setString(result);
     }
 
     return JS_WrapValue(cx, args.rval());
 }
 
 static bool
 ThisFilename(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-    RootedScript script (cx);
-    if (!JS_DescribeScriptedCaller(cx, &script, nullptr) || !script->filename()) {
+
+    JS::AutoFilename filename;
+    if (!DescribeScriptedCaller(cx, &filename) || !filename.get()) {
         args.rval().setString(cx->runtime()->emptyString);
         return true;
     }
-    JSString *filename = JS_NewStringCopyZ(cx, script->filename());
-    if (!filename)
-        return false;
-    args.rval().setString(filename);
+
+    JSString *str = JS_NewStringCopyZ(cx, filename.get());
+    if (!str)
+        return false;
+
+    args.rval().setString(str);
     return true;
 }
 
 static bool
 Wrap(JSContext *cx, unsigned argc, jsval *vp)
 {
     jsval v = argc > 0 ? JS_ARGV(cx, vp)[0] : UndefinedValue();
     if (JSVAL_IS_PRIMITIVE(v)) {
--- a/js/xpconnect/loader/mozJSSubScriptLoader.cpp
+++ b/js/xpconnect/loader/mozJSSubScriptLoader.cpp
@@ -264,20 +264,19 @@ mozJSSubScriptLoader::DoLoadSubScriptWit
     JSAutoCompartment ac(cx, targetObj);
 
     /* load up the url.  From here on, failures are reflected as ``custom''
      * js exceptions */
     nsCOMPtr<nsIURI> uri;
     nsAutoCString uriStr;
     nsAutoCString scheme;
 
-    RootedScript script(cx);
-
     // Figure out who's calling us
-    if (!JS_DescribeScriptedCaller(cx, &script, nullptr)) {
+    JS::AutoFilename filename;
+    if (!JS::DescribeScriptedCaller(cx, &filename)) {
         // No scripted frame means we don't know who's calling, bail.
         return NS_ERROR_FAILURE;
     }
 
     // Suppress caching if we're compiling as content.
     StartupCache* cache = (principal == mSystemPrincipal)
                           ? StartupCache::GetSingleton()
                           : nullptr;
@@ -308,31 +307,31 @@ mozJSSubScriptLoader::DoLoadSubScriptWit
         nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(uri);
         nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(innerURI);
         if (!fileURL) {
             return ReportError(cx, LOAD_ERROR_URI_NOT_LOCAL);
         }
 
         // For file URIs prepend the filename with the filename of the
         // calling script, and " -> ". See bug 418356.
-        nsAutoCString tmp(JS_GetScriptFilename(cx, script));
+        nsAutoCString tmp(filename.get());
         tmp.AppendLiteral(" -> ");
         tmp.Append(uriStr);
 
         uriStr = tmp;
     }
 
     bool writeScript = false;
     JSVersion version = JS_GetVersion(cx);
     nsAutoCString cachePath;
     cachePath.AppendPrintf("jssubloader/%d", version);
     PathifyURI(uri, cachePath);
 
     RootedFunction function(cx);
-    script = nullptr;
+    RootedScript script(cx);
     if (cache && !options.ignoreCache)
         rv = ReadCachedScript(cache, cachePath, cx, mSystemPrincipal, &script);
     if (!script) {
         rv = ReadScript(uri, cx, targetObj, options.charset,
                         static_cast<const char*>(uriStr.get()), serv,
                         principal, reusingGlobal, script.address(), function.address());
         writeScript = !!script;
     }
--- a/js/xpconnect/src/Sandbox.cpp
+++ b/js/xpconnect/src/Sandbox.cpp
@@ -349,19 +349,19 @@ ExportFunction(JSContext *cx, unsigned a
     RootedValue options(cx, args.length() > 2 ? args[2] : UndefinedValue());
     return ExportFunction(cx, args[0], args[1], options, args.rval());
 }
 } /* namespace xpc */
 
 static bool
 GetFilenameAndLineNumber(JSContext *cx, nsACString &filename, unsigned &lineno)
 {
-    JS::RootedScript script(cx);
-    if (JS_DescribeScriptedCaller(cx, &script, &lineno)) {
-        if (const char *cfilename = JS_GetScriptFilename(cx, script)) {
+    JS::AutoFilename scriptFilename;
+    if (JS::DescribeScriptedCaller(cx, &scriptFilename, &lineno)) {
+        if (const char *cfilename = scriptFilename.get()) {
             filename.Assign(nsDependentCString(cfilename));
             return true;
         }
     }
     return false;
 }
 
 bool
--- a/js/xpconnect/src/XPCShellImpl.cpp
+++ b/js/xpconnect/src/XPCShellImpl.cpp
@@ -116,49 +116,46 @@ static nsAutoString *gWorkingDirectory =
 
 static bool
 GetLocationProperty(JSContext *cx, HandleObject obj, HandleId id, MutableHandleValue vp)
 {
 #if !defined(XP_WIN) && !defined(XP_UNIX)
     //XXX: your platform should really implement this
     return false;
 #else
-    JS::RootedScript script(cx);
-    JS_DescribeScriptedCaller(cx, &script, nullptr);
-    const char *filename = JS_GetScriptFilename(cx, script);
-
-    if (filename) {
+    JS::AutoFilename filename;
+    if (JS::DescribeScriptedCaller(cx, &filename) && filename.get()) {
         nsresult rv;
         nsCOMPtr<nsIXPConnect> xpc =
             do_GetService(kXPConnectServiceContractID, &rv);
 
 #if defined(XP_WIN)
         // convert from the system codepage to UTF-16
-        int bufferSize = MultiByteToWideChar(CP_ACP, 0, filename,
+        int bufferSize = MultiByteToWideChar(CP_ACP, 0, filename.get(),
                                              -1, nullptr, 0);
         nsAutoString filenameString;
         filenameString.SetLength(bufferSize);
-        MultiByteToWideChar(CP_ACP, 0, filename,
+        MultiByteToWideChar(CP_ACP, 0, filename.get(),
                             -1, (LPWSTR)filenameString.BeginWriting(),
                             filenameString.Length());
         // remove the null terminator
         filenameString.SetLength(bufferSize - 1);
 
         // replace forward slashes with backslashes,
         // since nsLocalFileWin chokes on them
         char16_t* start = filenameString.BeginWriting();
         char16_t* end = filenameString.EndWriting();
 
         while (start != end) {
             if (*start == L'/')
                 *start = L'\\';
             start++;
         }
 #elif defined(XP_UNIX)
-        NS_ConvertUTF8toUTF16 filenameString(filename);
+        NS_ConvertUTF8toUTF16 filenameString(filename.get());
 #endif
 
         nsCOMPtr<nsIFile> location;
         if (NS_SUCCEEDED(rv)) {
             rv = NS_NewLocalFile(filenameString,
                                  false, getter_AddRefs(location));
         }
 
--- a/js/xpconnect/src/XPCWrappedJSClass.cpp
+++ b/js/xpconnect/src/XPCWrappedJSClass.cpp
@@ -840,20 +840,18 @@ nsXPCWrappedJSClass::CheckForException(X
                 // See if an environment variable was set or someone has told us
                 // that a user pref was set indicating that we should report all
                 // exceptions.
                 if (!reportable)
                     reportable = nsXPConnect::ReportAllJSExceptions();
 
                 // Finally, check to see if this is the last JS frame on the
                 // stack. If so then we always want to report it.
-                if (!reportable) {
-                    RootedScript ignored(cx);
-                    reportable = !JS_DescribeScriptedCaller(cx, &ignored, nullptr);
-                }
+                if (!reportable)
+                    reportable = !JS::DescribeScriptedCaller(cx);
 
                 // Ugly special case for GetInterface. It's "special" in the
                 // same way as QueryInterface in that a failure is not
                 // exceptional and shouldn't be reported. We have to do this
                 // check here instead of in xpcwrappedjs (like we do for QI) to
                 // avoid adding extra code to all xpcwrappedjs objects.
                 if (reportable && e_result == NS_ERROR_NO_INTERFACE &&
                     !strcmp(anInterfaceName, "nsIInterfaceRequestor") &&