michael@0: /* vim: set ts=8 sts=4 et sw=4 tw=80: 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: #include michael@0: #include michael@0: #ifdef HAVE_IO_H michael@0: #include /* for isatty() */ michael@0: #endif michael@0: #ifdef HAVE_UNISTD_H michael@0: #include /* for isatty() */ michael@0: #endif michael@0: michael@0: #include "base/basictypes.h" michael@0: michael@0: #include "jsapi.h" michael@0: #include "js/OldDebugAPI.h" michael@0: michael@0: #include "xpcpublic.h" michael@0: michael@0: #include "XPCShellEnvironment.h" michael@0: michael@0: #include "mozilla/XPCOM.h" michael@0: michael@0: #include "nsIChannel.h" michael@0: #include "nsIClassInfo.h" michael@0: #include "nsIDirectoryService.h" michael@0: #include "nsIJSRuntimeService.h" michael@0: #include "nsIPrincipal.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "nsIURI.h" michael@0: #include "nsIXPConnect.h" michael@0: #include "nsIXPCScriptable.h" michael@0: michael@0: #include "nsCxPusher.h" michael@0: #include "nsJSUtils.h" michael@0: #include "nsJSPrincipals.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsXULAppAPI.h" michael@0: michael@0: #include "BackstagePass.h" michael@0: michael@0: #include "TestShellChild.h" michael@0: #include "TestShellParent.h" michael@0: michael@0: using mozilla::ipc::XPCShellEnvironment; michael@0: using mozilla::ipc::TestShellChild; michael@0: using mozilla::ipc::TestShellParent; michael@0: using mozilla::AutoSafeJSContext; michael@0: using namespace JS; michael@0: michael@0: namespace { michael@0: michael@0: static const char kDefaultRuntimeScriptFilename[] = "xpcshell.js"; michael@0: michael@0: class XPCShellDirProvider : public nsIDirectoryServiceProvider michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSIDIRECTORYSERVICEPROVIDER michael@0: michael@0: XPCShellDirProvider() { } michael@0: ~XPCShellDirProvider() { } michael@0: michael@0: bool SetGREDir(const char *dir); michael@0: void ClearGREDir() { mGREDir = nullptr; } michael@0: michael@0: private: michael@0: nsCOMPtr mGREDir; michael@0: }; michael@0: michael@0: inline XPCShellEnvironment* michael@0: Environment(Handle global) michael@0: { michael@0: AutoSafeJSContext cx; michael@0: JSAutoCompartment ac(cx, global); michael@0: Rooted v(cx); michael@0: if (!JS_GetProperty(cx, global, "__XPCShellEnvironment", &v) || michael@0: !v.get().isDouble()) michael@0: { michael@0: return nullptr; michael@0: } michael@0: return static_cast(v.get().toPrivate()); michael@0: } michael@0: michael@0: static bool michael@0: Print(JSContext *cx, unsigned argc, JS::Value *vp) michael@0: { michael@0: JS::CallArgs args = JS::CallArgsFromVp(argc, vp); michael@0: michael@0: for (unsigned i = 0; i < args.length(); i++) { michael@0: JSString *str = JS::ToString(cx, args[i]); michael@0: if (!str) michael@0: return false; michael@0: JSAutoByteString bytes(cx, str); michael@0: if (!bytes) michael@0: return false; michael@0: fprintf(stdout, "%s%s", i ? " " : "", bytes.ptr()); michael@0: fflush(stdout); michael@0: } michael@0: fputc('\n', stdout); michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: GetLine(char *bufp, michael@0: FILE *file, michael@0: const char *prompt) michael@0: { michael@0: char line[256]; michael@0: fputs(prompt, stdout); michael@0: fflush(stdout); michael@0: if (!fgets(line, sizeof line, file)) michael@0: return false; michael@0: strcpy(bufp, line); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: Dump(JSContext *cx, unsigned argc, JS::Value *vp) michael@0: { michael@0: JS::CallArgs args = JS::CallArgsFromVp(argc, vp); michael@0: michael@0: if (!args.length()) michael@0: return true; michael@0: michael@0: JSString *str = JS::ToString(cx, args[0]); michael@0: if (!str) michael@0: return false; michael@0: JSAutoByteString bytes(cx, str); michael@0: if (!bytes) michael@0: return false; michael@0: michael@0: fputs(bytes.ptr(), stdout); michael@0: fflush(stdout); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: Load(JSContext *cx, michael@0: unsigned argc, michael@0: JS::Value *vp) michael@0: { michael@0: JS::CallArgs args = JS::CallArgsFromVp(argc, vp); michael@0: michael@0: JS::Rooted obj(cx, JS_THIS_OBJECT(cx, vp)); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: for (unsigned i = 0; i < args.length(); i++) { michael@0: JS::Rooted str(cx, JS::ToString(cx, args[i])); michael@0: if (!str) michael@0: return false; michael@0: JSAutoByteString filename(cx, str); michael@0: if (!filename) michael@0: return false; michael@0: FILE *file = fopen(filename.ptr(), "r"); michael@0: if (!file) { michael@0: JS_ReportError(cx, "cannot open file '%s' for reading", filename.ptr()); michael@0: return false; michael@0: } michael@0: Rooted global(cx, JS::CurrentGlobalOrNull(cx)); michael@0: JS::CompileOptions options(cx); michael@0: options.setUTF8(true) michael@0: .setFileAndLine(filename.ptr(), 1); michael@0: JS::Rooted script(cx, JS::Compile(cx, obj, options, file)); michael@0: fclose(file); michael@0: if (!script) michael@0: return false; michael@0: michael@0: if (!JS_ExecuteScript(cx, obj, script)) { michael@0: return false; michael@0: } michael@0: } michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: Version(JSContext *cx, michael@0: unsigned argc, michael@0: JS::Value *vp) michael@0: { michael@0: JS::CallArgs args = JS::CallArgsFromVp(argc, vp); michael@0: args.rval().setInt32(JS_GetVersion(cx)); michael@0: if (args.get(0).isInt32()) michael@0: JS_SetVersionForCompartment(js::GetContextCompartment(cx), michael@0: JSVersion(args[0].toInt32())); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: BuildDate(JSContext *cx, unsigned argc, JS::Value *vp) michael@0: { michael@0: JS::CallArgs args = JS::CallArgsFromVp(argc, vp); michael@0: fprintf(stdout, "built on %s at %s\n", __DATE__, __TIME__); michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: Quit(JSContext *cx, michael@0: unsigned argc, michael@0: JS::Value *vp) michael@0: { michael@0: Rooted global(cx, JS::CurrentGlobalOrNull(cx)); michael@0: XPCShellEnvironment* env = Environment(global); michael@0: env->SetIsQuitting(); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: static bool michael@0: DumpXPC(JSContext *cx, michael@0: unsigned argc, michael@0: JS::Value *vp) michael@0: { michael@0: JS::CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: uint16_t depth = 2; michael@0: if (args.length() > 0) { michael@0: if (!JS::ToUint16(cx, args[0], &depth)) michael@0: return false; michael@0: } michael@0: michael@0: nsCOMPtr xpc = do_GetService(nsIXPConnect::GetCID()); michael@0: if (xpc) michael@0: xpc->DebugDump(int16_t(depth)); michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: GC(JSContext *cx, michael@0: unsigned argc, michael@0: JS::Value *vp) michael@0: { michael@0: JS::CallArgs args = JS::CallArgsFromVp(argc, vp); michael@0: michael@0: JSRuntime *rt = JS_GetRuntime(cx); michael@0: JS_GC(rt); michael@0: #ifdef JS_GCMETER michael@0: js_DumpGCStats(rt, stdout); michael@0: #endif michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: #ifdef JS_GC_ZEAL michael@0: static bool michael@0: GCZeal(JSContext *cx, unsigned argc, JS::Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: uint32_t zeal; michael@0: if (!ToUint32(cx, args.get(0), &zeal)) michael@0: return false; michael@0: michael@0: JS_SetGCZeal(cx, uint8_t(zeal), JS_DEFAULT_ZEAL_FREQ); michael@0: return true; michael@0: } michael@0: #endif michael@0: michael@0: const JSFunctionSpec gGlobalFunctions[] = michael@0: { michael@0: JS_FS("print", Print, 0,0), michael@0: JS_FS("load", Load, 1,0), michael@0: JS_FS("quit", Quit, 0,0), michael@0: JS_FS("version", Version, 1,0), michael@0: JS_FS("build", BuildDate, 0,0), michael@0: JS_FS("dumpXPC", DumpXPC, 1,0), michael@0: JS_FS("dump", Dump, 1,0), michael@0: JS_FS("gc", GC, 0,0), michael@0: #ifdef JS_GC_ZEAL michael@0: JS_FS("gczeal", GCZeal, 1,0), michael@0: #endif michael@0: JS_FS_END michael@0: }; michael@0: michael@0: typedef enum JSShellErrNum michael@0: { michael@0: #define MSG_DEF(name, number, count, exception, format) \ michael@0: name = number, michael@0: #include "jsshell.msg" michael@0: #undef MSG_DEF michael@0: JSShellErr_Limit michael@0: #undef MSGDEF michael@0: } JSShellErrNum; michael@0: michael@0: } /* anonymous namespace */ michael@0: michael@0: void michael@0: XPCShellEnvironment::ProcessFile(JSContext *cx, michael@0: JS::Handle obj, michael@0: const char *filename, michael@0: FILE *file, michael@0: bool forceTTY) michael@0: { michael@0: XPCShellEnvironment* env = this; michael@0: michael@0: JS::Rooted result(cx); michael@0: int lineno, startline; michael@0: bool ok, hitEOF; michael@0: char *bufp, buffer[4096]; michael@0: JSString *str; michael@0: michael@0: if (forceTTY) { michael@0: file = stdin; michael@0: } michael@0: else if (!isatty(fileno(file))) michael@0: { michael@0: /* michael@0: * It's not interactive - just execute it. michael@0: * michael@0: * Support the UNIX #! shell hack; gobble the first line if it starts michael@0: * with '#'. TODO - this isn't quite compatible with sharp variables, michael@0: * as a legal js program (using sharp variables) might start with '#'. michael@0: * But that would require multi-character lookahead. michael@0: */ michael@0: int ch = fgetc(file); michael@0: if (ch == '#') { michael@0: while((ch = fgetc(file)) != EOF) { michael@0: if(ch == '\n' || ch == '\r') michael@0: break; michael@0: } michael@0: } michael@0: ungetc(ch, file); michael@0: michael@0: JSAutoRequest ar(cx); michael@0: JSAutoCompartment ac(cx, obj); michael@0: michael@0: JS::CompileOptions options(cx); michael@0: options.setUTF8(true) michael@0: .setFileAndLine(filename, 1); michael@0: JS::Rooted script(cx, JS::Compile(cx, obj, options, file)); michael@0: if (script) michael@0: (void)JS_ExecuteScript(cx, obj, script, &result); michael@0: michael@0: return; michael@0: } michael@0: michael@0: /* It's an interactive filehandle; drop into read-eval-print loop. */ michael@0: lineno = 1; michael@0: hitEOF = false; michael@0: do { michael@0: bufp = buffer; michael@0: *bufp = '\0'; michael@0: michael@0: JSAutoRequest ar(cx); michael@0: JSAutoCompartment ac(cx, obj); michael@0: michael@0: /* michael@0: * Accumulate lines until we get a 'compilable unit' - one that either michael@0: * generates an error (before running out of source) or that compiles michael@0: * cleanly. This should be whenever we get a complete statement that michael@0: * coincides with the end of a line. michael@0: */ michael@0: startline = lineno; michael@0: do { michael@0: if (!GetLine(bufp, file, startline == lineno ? "js> " : "")) { michael@0: hitEOF = true; michael@0: break; michael@0: } michael@0: bufp += strlen(bufp); michael@0: lineno++; michael@0: } while (!JS_BufferIsCompilableUnit(cx, obj, buffer, strlen(buffer))); michael@0: michael@0: /* Clear any pending exception from previous failed compiles. */ michael@0: JS_ClearPendingException(cx); michael@0: JS::CompileOptions options(cx); michael@0: options.setFileAndLine("typein", startline); michael@0: JS::Rooted script(cx, michael@0: JS_CompileScript(cx, obj, buffer, strlen(buffer), options)); michael@0: if (script) { michael@0: JSErrorReporter older; michael@0: michael@0: ok = JS_ExecuteScript(cx, obj, script, &result); michael@0: if (ok && result != JSVAL_VOID) { michael@0: /* Suppress error reports from JS::ToString(). */ michael@0: older = JS_SetErrorReporter(cx, nullptr); michael@0: str = JS::ToString(cx, result); michael@0: JSAutoByteString bytes; michael@0: if (str) michael@0: bytes.encodeLatin1(cx, str); michael@0: JS_SetErrorReporter(cx, older); michael@0: michael@0: if (!!bytes) michael@0: fprintf(stdout, "%s\n", bytes.ptr()); michael@0: else michael@0: ok = false; michael@0: } michael@0: } michael@0: } while (!hitEOF && !env->IsQuitting()); michael@0: michael@0: fprintf(stdout, "\n"); michael@0: } michael@0: michael@0: NS_IMETHODIMP_(MozExternalRefCountType) michael@0: XPCShellDirProvider::AddRef() michael@0: { michael@0: return 2; michael@0: } michael@0: michael@0: NS_IMETHODIMP_(MozExternalRefCountType) michael@0: XPCShellDirProvider::Release() michael@0: { michael@0: return 1; michael@0: } michael@0: michael@0: NS_IMPL_QUERY_INTERFACE(XPCShellDirProvider, nsIDirectoryServiceProvider) michael@0: michael@0: bool michael@0: XPCShellDirProvider::SetGREDir(const char *dir) michael@0: { michael@0: nsresult rv = XRE_GetFileFromPath(dir, getter_AddRefs(mGREDir)); michael@0: return NS_SUCCEEDED(rv); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: XPCShellDirProvider::GetFile(const char *prop, michael@0: bool *persistent, michael@0: nsIFile* *result) michael@0: { michael@0: if (mGREDir && !strcmp(prop, NS_GRE_DIR)) { michael@0: *persistent = true; michael@0: NS_ADDREF(*result = mGREDir); michael@0: return NS_OK; michael@0: } michael@0: michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // static michael@0: XPCShellEnvironment* michael@0: XPCShellEnvironment::CreateEnvironment() michael@0: { michael@0: XPCShellEnvironment* env = new XPCShellEnvironment(); michael@0: if (env && !env->Init()) { michael@0: delete env; michael@0: env = nullptr; michael@0: } michael@0: return env; michael@0: } michael@0: michael@0: XPCShellEnvironment::XPCShellEnvironment() michael@0: : mQuitting(false) michael@0: { michael@0: } michael@0: michael@0: XPCShellEnvironment::~XPCShellEnvironment() michael@0: { michael@0: michael@0: AutoSafeJSContext cx; michael@0: Rooted global(cx, GetGlobalObject()); michael@0: if (global) { michael@0: { michael@0: JSAutoCompartment ac(cx, global); michael@0: JS_SetAllNonReservedSlotsToUndefined(cx, global); michael@0: } michael@0: mGlobalHolder.Release(); michael@0: michael@0: JSRuntime *rt = JS_GetRuntime(cx); michael@0: JS_GC(rt); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: XPCShellEnvironment::Init() michael@0: { michael@0: nsresult rv; michael@0: michael@0: // unbuffer stdout so that output is in the correct order; note that stderr michael@0: // is unbuffered by default michael@0: setbuf(stdout, 0); michael@0: michael@0: nsCOMPtr rtsvc = michael@0: do_GetService("@mozilla.org/js/xpc/RuntimeService;1"); michael@0: if (!rtsvc) { michael@0: NS_ERROR("failed to get nsJSRuntimeService!"); michael@0: return false; michael@0: } michael@0: michael@0: JSRuntime *rt; michael@0: if (NS_FAILED(rtsvc->GetRuntime(&rt)) || !rt) { michael@0: NS_ERROR("failed to get JSRuntime from nsJSRuntimeService!"); michael@0: return false; michael@0: } michael@0: michael@0: if (!mGlobalHolder.Hold(rt)) { michael@0: NS_ERROR("Can't protect global object!"); michael@0: return false; michael@0: } michael@0: michael@0: AutoSafeJSContext cx; michael@0: michael@0: JS_SetContextPrivate(cx, this); michael@0: michael@0: nsCOMPtr xpc = michael@0: do_GetService(nsIXPConnect::GetCID()); michael@0: if (!xpc) { michael@0: NS_ERROR("failed to get nsXPConnect service!"); michael@0: return false; michael@0: } michael@0: michael@0: nsCOMPtr principal; michael@0: nsCOMPtr securityManager = michael@0: do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv); michael@0: if (NS_SUCCEEDED(rv) && securityManager) { michael@0: rv = securityManager->GetSystemPrincipal(getter_AddRefs(principal)); michael@0: if (NS_FAILED(rv)) { michael@0: fprintf(stderr, "+++ Failed to obtain SystemPrincipal from ScriptSecurityManager service.\n"); michael@0: } michael@0: } else { michael@0: fprintf(stderr, "+++ Failed to get ScriptSecurityManager service, running without principals"); michael@0: } michael@0: michael@0: nsRefPtr backstagePass; michael@0: rv = NS_NewBackstagePass(getter_AddRefs(backstagePass)); michael@0: if (NS_FAILED(rv)) { michael@0: NS_ERROR("Failed to create backstage pass!"); michael@0: return false; michael@0: } michael@0: michael@0: JS::CompartmentOptions options; michael@0: options.setZone(JS::SystemZone) michael@0: .setVersion(JSVERSION_LATEST); michael@0: nsCOMPtr holder; michael@0: rv = xpc->InitClassesWithNewWrappedGlobal(cx, michael@0: static_cast(backstagePass), michael@0: principal, 0, michael@0: options, michael@0: getter_AddRefs(holder)); michael@0: if (NS_FAILED(rv)) { michael@0: NS_ERROR("InitClassesWithNewWrappedGlobal failed!"); michael@0: return false; michael@0: } michael@0: michael@0: JS::Rooted globalObj(cx, holder->GetJSObject()); michael@0: if (!globalObj) { michael@0: NS_ERROR("Failed to get global JSObject!"); michael@0: return false; michael@0: } michael@0: JSAutoCompartment ac(cx, globalObj); michael@0: michael@0: backstagePass->SetGlobalObject(globalObj); michael@0: michael@0: JS::Rooted privateVal(cx, PrivateValue(this)); michael@0: if (!JS_DefineProperty(cx, globalObj, "__XPCShellEnvironment", michael@0: privateVal, JSPROP_READONLY | JSPROP_PERMANENT, michael@0: JS_PropertyStub, JS_StrictPropertyStub) || michael@0: !JS_DefineFunctions(cx, globalObj, gGlobalFunctions) || michael@0: !JS_DefineProfilingFunctions(cx, globalObj)) michael@0: { michael@0: NS_ERROR("JS_DefineFunctions failed!"); michael@0: return false; michael@0: } michael@0: michael@0: mGlobalHolder = globalObj; michael@0: michael@0: FILE* runtimeScriptFile = fopen(kDefaultRuntimeScriptFilename, "r"); michael@0: if (runtimeScriptFile) { michael@0: fprintf(stdout, "[loading '%s'...]\n", kDefaultRuntimeScriptFilename); michael@0: ProcessFile(cx, globalObj, kDefaultRuntimeScriptFilename, michael@0: runtimeScriptFile, false); michael@0: fclose(runtimeScriptFile); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: XPCShellEnvironment::EvaluateString(const nsString& aString, michael@0: nsString* aResult) michael@0: { michael@0: AutoSafeJSContext cx; michael@0: JS::Rooted global(cx, GetGlobalObject()); michael@0: JSAutoCompartment ac(cx, global); michael@0: michael@0: JS::CompileOptions options(cx); michael@0: options.setFileAndLine("typein", 0); michael@0: JS::Rooted script(cx, JS_CompileUCScript(cx, global, aString.get(), michael@0: aString.Length(), options)); michael@0: if (!script) { michael@0: return false; michael@0: } michael@0: michael@0: if (aResult) { michael@0: aResult->Truncate(); michael@0: } michael@0: michael@0: JS::Rooted result(cx); michael@0: bool ok = JS_ExecuteScript(cx, global, script, &result); michael@0: if (ok && result != JSVAL_VOID) { michael@0: JSErrorReporter old = JS_SetErrorReporter(cx, nullptr); michael@0: JSString* str = JS::ToString(cx, result); michael@0: nsDependentJSString depStr; michael@0: if (str) michael@0: depStr.init(cx, str); michael@0: JS_SetErrorReporter(cx, old); michael@0: michael@0: if (!depStr.IsEmpty() && aResult) { michael@0: aResult->Assign(depStr); michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: }