diff --git a/dom/chrome-webidl/ChromeUtils.webidl b/dom/chrome-webidl/ChromeUtils.webidl index 72b848fd01e0..c0a4d900d48a 100644 --- a/dom/chrome-webidl/ChromeUtils.webidl +++ b/dom/chrome-webidl/ChromeUtils.webidl @@ -966,6 +966,11 @@ dictionary CompileScriptOptionsDictionary { */ DOMString charset = "utf-8"; + /** + * The filename to associate with the script. Defaults to the source's URL. + */ + DOMString filename; + /** * If true, certain parts of the script may be parsed lazily, the first time * they are used, rather than eagerly parsed at load time. diff --git a/dom/chrome-webidl/PrecompiledScript.webidl b/dom/chrome-webidl/PrecompiledScript.webidl index 4a71f67fc87e..1f8826a3e470 100644 --- a/dom/chrome-webidl/PrecompiledScript.webidl +++ b/dom/chrome-webidl/PrecompiledScript.webidl @@ -29,7 +29,8 @@ interface PrecompiledScript { any executeInGlobal(object global, optional ExecuteInGlobalOptions options = {}); /** - * The URL that the script was loaded from. + * The URL that the script was loaded from. Could also be an arbitrary other + * value as specified by the "filename" option to ChromeUtils.compileScript. */ [Pure] readonly attribute DOMString url; diff --git a/js/xpconnect/loader/ChromeScriptLoader.cpp b/js/xpconnect/loader/ChromeScriptLoader.cpp index d67c41d014a5..81ba2c38145f 100644 --- a/js/xpconnect/loader/ChromeScriptLoader.cpp +++ b/js/xpconnect/loader/ChromeScriptLoader.cpp @@ -292,7 +292,14 @@ nsresult AsyncScriptCompiler::Start( mCharset = aOptions.mCharset; CompileOptions options(aCx); - options.setFile(mURL.get()).setNoScriptRval(!aOptions.mHasReturnValue); + nsAutoCString filename; + if (aOptions.mFilename.WasPassed()) { + filename = NS_ConvertUTF16toUTF8(aOptions.mFilename.Value()); + options.setFile(filename.get()); + } else { + options.setFile(mURL.get()); + } + options.setNoScriptRval(!aOptions.mHasReturnValue); if (!aOptions.mLazilyParse) { options.setForceFullParse(); @@ -418,7 +425,8 @@ void AsyncScriptCompiler::Reject(JSContext* aCx, const char* aMsg) { nsAutoString msg; msg.AppendASCII(aMsg); msg.AppendLiteral(": "); - AppendUTF8toUTF16(mURL, msg); + nsDependentCString filename(mOptions.filename().c_str()); + AppendUTF8toUTF16(filename, msg); RootedValue exn(aCx); if (xpc::NonVoidStringToJsval(aCx, msg, &exn)) { @@ -501,7 +509,7 @@ PrecompiledScript::PrecompiledScript(nsISupports* aParent, JS::ReadOnlyCompileOptions& aOptions) : mParent(aParent), mStencil(aStencil), - mURL(aOptions.filename().c_str()), + mPublicURL(aOptions.filename().c_str()), mHasReturnValue(!aOptions.noScriptRval) { MOZ_ASSERT(aParent); MOZ_ASSERT(aStencil); @@ -551,7 +559,9 @@ void PrecompiledScript::ExecuteInGlobal(JSContext* aCx, HandleObject aGlobal, JS_WrapValue(aCx, aRval); } -void PrecompiledScript::GetUrl(nsAString& aUrl) { CopyUTF8toUTF16(mURL, aUrl); } +void PrecompiledScript::GetUrl(nsAString& aUrl) { + CopyUTF8toUTF16(mPublicURL, aUrl); +} bool PrecompiledScript::HasReturnValue() { return mHasReturnValue; } diff --git a/js/xpconnect/loader/PrecompiledScript.h b/js/xpconnect/loader/PrecompiledScript.h index 2b49c05373e6..7c0c258a6991 100644 --- a/js/xpconnect/loader/PrecompiledScript.h +++ b/js/xpconnect/loader/PrecompiledScript.h @@ -53,7 +53,7 @@ class PrecompiledScript : public nsISupports, public nsWrapperCache { nsCOMPtr mParent; RefPtr mStencil; - nsCString mURL; + nsCString mPublicURL; const bool mHasReturnValue; }; diff --git a/js/xpconnect/tests/unit/test_compileScript.js b/js/xpconnect/tests/unit/test_compileScript.js index 1baf7ab56e5e..91d1e194e7ac 100644 --- a/js/xpconnect/tests/unit/test_compileScript.js +++ b/js/xpconnect/tests/unit/test_compileScript.js @@ -73,6 +73,48 @@ add_task(async function test_syntaxError() { SyntaxError); }); +add_task(async function test_Error_filename() { + // This function will be serialized as a data:-URL and called. + function getMyError() { + let err = new Error(); + return { + fileName: err.fileName, + stackFirstLine: err.stack.split("\n")[0], + }; + } + const scriptUrl = `data:,(${encodeURIComponent(getMyError)})()`; + const dummyFilename = "dummy filename"; + let script1 = await ChromeUtils.compileScript(scriptUrl, { hasReturnValue: true }); + let script2 = await ChromeUtils.compileScript(scriptUrl, { hasReturnValue: true, filename: dummyFilename }); + + equal(script1.url, scriptUrl, "Script URL is correct"); + equal(script2.url, "dummy filename", "Script URL overridden"); + + let sandbox = Cu.Sandbox("http://example.com"); + let err1 = script1.executeInGlobal(sandbox); + equal(err1.fileName, scriptUrl, "fileName is original script URL"); + equal(err1.stackFirstLine, `getMyError@${scriptUrl}:2:15`, "Stack has original URL"); + + let err2 = script2.executeInGlobal(sandbox); + equal(err2.fileName, dummyFilename, "fileName is overridden filename"); + equal(err2.stackFirstLine, `getMyError@${dummyFilename}:2:15`, "Stack has overridden URL"); +}); + +add_task(async function test_invalid_url() { + // In this test we want a URL that doesn't resolve to a valid file. + // Moreover, the name is chosen such that it does not trigger the + // CheckForBrokenChromeURL check. + await Assert.rejects( + ChromeUtils.compileScript("resource:///invalid.ftl"), + /^Unable to load script: resource:\/\/\/invalid\.ftl$/ + ); + + await Assert.rejects( + ChromeUtils.compileScript("resource:///invalid.ftl", { filename: "bye bye" }), + /^Unable to load script: bye bye$/ + ); +}); + /** * Assert that executeInGlobal throws a special exception when the content script throws. * And the content script exception is notified to the console.