michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* vim: set ts=8 sts=4 et sw=4 tw=99: */ 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 "nsXULAppAPI.h" michael@0: #include "jsapi.h" michael@0: #include "jsfriendapi.h" michael@0: #include "jsprf.h" michael@0: #include "js/OldDebugAPI.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: #include "nsIXPConnect.h" michael@0: #include "nsIJSNativeInitializer.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsIFile.h" michael@0: #include "nsString.h" michael@0: #include "nsIDirectoryService.h" michael@0: #include "nsDirectoryServiceDefs.h" michael@0: #include "nsAppDirectoryServiceDefs.h" michael@0: #include "nscore.h" michael@0: #include "nsArrayEnumerator.h" michael@0: #include "nsCOMArray.h" michael@0: #include "nsDirectoryServiceUtils.h" michael@0: #include "nsIJSRuntimeService.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsJSPrincipals.h" michael@0: #include "xpcpublic.h" michael@0: #include "BackstagePass.h" michael@0: #include "nsCxPusher.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "nsIPrincipal.h" michael@0: michael@0: #ifdef ANDROID michael@0: #include michael@0: #endif michael@0: michael@0: #ifdef XP_WIN michael@0: #include michael@0: #endif michael@0: michael@0: // all this crap is needed to do the interactive shell stuff 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: #ifdef MOZ_CRASHREPORTER michael@0: #include "nsExceptionHandler.h" michael@0: #include "nsICrashReporter.h" michael@0: #endif michael@0: michael@0: using namespace mozilla; michael@0: using namespace JS; michael@0: michael@0: class XPCShellDirProvider : public nsIDirectoryServiceProvider2 michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS_INHERITED michael@0: NS_DECL_NSIDIRECTORYSERVICEPROVIDER michael@0: NS_DECL_NSIDIRECTORYSERVICEPROVIDER2 michael@0: michael@0: XPCShellDirProvider() { } michael@0: ~XPCShellDirProvider() { } michael@0: michael@0: // The platform resource folder michael@0: void SetGREDir(nsIFile *greDir); michael@0: void ClearGREDir() { mGREDir = nullptr; } michael@0: // The application resource folder michael@0: void SetAppDir(nsIFile *appFile); michael@0: void ClearAppDir() { mAppDir = nullptr; } michael@0: // The app executable michael@0: void SetAppFile(nsIFile *appFile); michael@0: void ClearAppFile() { mAppFile = nullptr; } michael@0: // An additional custom plugin dir if specified michael@0: void SetPluginDir(nsIFile* pluginDir); michael@0: void ClearPluginDir() { mPluginDir = nullptr; } michael@0: michael@0: private: michael@0: nsCOMPtr mGREDir; michael@0: nsCOMPtr mAppDir; michael@0: nsCOMPtr mPluginDir; michael@0: nsCOMPtr mAppFile; michael@0: }; michael@0: michael@0: #ifdef JS_THREADSAFE michael@0: #define DoBeginRequest(cx) JS_BeginRequest((cx)) michael@0: #define DoEndRequest(cx) JS_EndRequest((cx)) michael@0: #else michael@0: #define DoBeginRequest(cx) ((void)0) michael@0: #define DoEndRequest(cx) ((void)0) michael@0: #endif michael@0: michael@0: static const char kXPConnectServiceContractID[] = "@mozilla.org/js/xpc/XPConnect;1"; michael@0: michael@0: #define EXITCODE_RUNTIME_ERROR 3 michael@0: #define EXITCODE_FILE_NOT_FOUND 4 michael@0: michael@0: static FILE *gOutFile = nullptr; michael@0: static FILE *gErrFile = nullptr; michael@0: static FILE *gInFile = nullptr; michael@0: michael@0: static int gExitCode = 0; michael@0: static bool gIgnoreReportedErrors = false; michael@0: static bool gQuitting = false; michael@0: static bool reportWarnings = true; michael@0: static bool compileOnly = false; michael@0: michael@0: static JSPrincipals *gJSPrincipals = nullptr; michael@0: static nsAutoString *gWorkingDirectory = nullptr; michael@0: michael@0: static bool michael@0: GetLocationProperty(JSContext *cx, HandleObject obj, HandleId id, MutableHandleValue vp) michael@0: { michael@0: #if !defined(XP_WIN) && !defined(XP_UNIX) michael@0: //XXX: your platform should really implement this michael@0: return false; michael@0: #else michael@0: JS::AutoFilename filename; michael@0: if (JS::DescribeScriptedCaller(cx, &filename) && filename.get()) { michael@0: nsresult rv; michael@0: nsCOMPtr xpc = michael@0: do_GetService(kXPConnectServiceContractID, &rv); michael@0: michael@0: #if defined(XP_WIN) michael@0: // convert from the system codepage to UTF-16 michael@0: int bufferSize = MultiByteToWideChar(CP_ACP, 0, filename.get(), michael@0: -1, nullptr, 0); michael@0: nsAutoString filenameString; michael@0: filenameString.SetLength(bufferSize); michael@0: MultiByteToWideChar(CP_ACP, 0, filename.get(), michael@0: -1, (LPWSTR)filenameString.BeginWriting(), michael@0: filenameString.Length()); michael@0: // remove the null terminator michael@0: filenameString.SetLength(bufferSize - 1); michael@0: michael@0: // replace forward slashes with backslashes, michael@0: // since nsLocalFileWin chokes on them michael@0: char16_t* start = filenameString.BeginWriting(); michael@0: char16_t* end = filenameString.EndWriting(); michael@0: michael@0: while (start != end) { michael@0: if (*start == L'/') michael@0: *start = L'\\'; michael@0: start++; michael@0: } michael@0: #elif defined(XP_UNIX) michael@0: NS_ConvertUTF8toUTF16 filenameString(filename.get()); michael@0: #endif michael@0: michael@0: nsCOMPtr location; michael@0: if (NS_SUCCEEDED(rv)) { michael@0: rv = NS_NewLocalFile(filenameString, michael@0: false, getter_AddRefs(location)); michael@0: } michael@0: michael@0: if (!location && gWorkingDirectory) { michael@0: // could be a relative path, try appending it to the cwd michael@0: // and then normalize michael@0: nsAutoString absolutePath(*gWorkingDirectory); michael@0: absolutePath.Append(filenameString); michael@0: michael@0: rv = NS_NewLocalFile(absolutePath, michael@0: false, getter_AddRefs(location)); michael@0: } michael@0: michael@0: if (location) { michael@0: nsCOMPtr locationHolder; michael@0: michael@0: bool symlink; michael@0: // don't normalize symlinks, because that's kind of confusing michael@0: if (NS_SUCCEEDED(location->IsSymlink(&symlink)) && michael@0: !symlink) michael@0: location->Normalize(); michael@0: rv = xpc->WrapNative(cx, obj, location, michael@0: NS_GET_IID(nsIFile), michael@0: getter_AddRefs(locationHolder)); michael@0: michael@0: if (NS_SUCCEEDED(rv) && michael@0: locationHolder->GetJSObject()) { michael@0: vp.set(OBJECT_TO_JSVAL(locationHolder->GetJSObject())); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: #endif michael@0: } michael@0: michael@0: static bool michael@0: GetLine(JSContext *cx, char *bufp, FILE *file, const char *prompt) { michael@0: { michael@0: char line[256] = { '\0' }; michael@0: fputs(prompt, gOutFile); michael@0: fflush(gOutFile); michael@0: if ((!fgets(line, sizeof line, file) && errno != EINTR) || feof(file)) michael@0: return false; michael@0: strcpy(bufp, line); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: ReadLine(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: // While 4096 might be quite arbitrary, this is something to be fixed in michael@0: // bug 105707. It is also the same limit as in ProcessFile. michael@0: char buf[4096]; michael@0: RootedString str(cx); michael@0: michael@0: /* If a prompt was specified, construct the string */ michael@0: if (args.length() > 0) { michael@0: str = JS::ToString(cx, args[0]); michael@0: if (!str) michael@0: return false; michael@0: } else { michael@0: str = JS_GetEmptyString(JS_GetRuntime(cx)); michael@0: } michael@0: michael@0: /* Get a line from the infile */ michael@0: JSAutoByteString strBytes(cx, str); michael@0: if (!strBytes || !GetLine(cx, buf, gInFile, strBytes.ptr())) michael@0: return false; michael@0: michael@0: /* Strip newline character added by GetLine() */ michael@0: unsigned int buflen = strlen(buf); michael@0: if (buflen == 0) { michael@0: if (feof(gInFile)) { michael@0: args.rval().setNull(); michael@0: return true; michael@0: } michael@0: } else if (buf[buflen - 1] == '\n') { michael@0: --buflen; michael@0: } michael@0: michael@0: /* Turn buf into a JSString */ michael@0: str = JS_NewStringCopyN(cx, buf, buflen); michael@0: if (!str) michael@0: return false; michael@0: michael@0: args.rval().setString(str); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: Print(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: args.rval().setUndefined(); michael@0: michael@0: RootedString str(cx); michael@0: nsAutoCString utf8str; michael@0: size_t length; michael@0: const jschar *chars; michael@0: michael@0: for (unsigned i = 0; i < args.length(); i++) { michael@0: str = ToString(cx, args[i]); michael@0: if (!str) michael@0: return false; michael@0: chars = JS_GetStringCharsAndLength(cx, str, &length); michael@0: if (!chars) michael@0: return false; michael@0: michael@0: if (i) michael@0: utf8str.Append(' '); michael@0: AppendUTF16toUTF8(Substring(reinterpret_cast(chars), michael@0: length), michael@0: utf8str); michael@0: } michael@0: utf8str.Append('\n'); michael@0: fputs(utf8str.get(), gOutFile); michael@0: fflush(gOutFile); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: Dump(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: args.rval().setUndefined(); michael@0: michael@0: if (!args.length()) michael@0: return true; michael@0: michael@0: RootedString str(cx, ToString(cx, args[0])); michael@0: if (!str) michael@0: return false; michael@0: michael@0: size_t length; michael@0: const jschar *chars = JS_GetStringCharsAndLength(cx, str, &length); michael@0: if (!chars) michael@0: return false; michael@0: michael@0: NS_ConvertUTF16toUTF8 utf8str(reinterpret_cast(chars), michael@0: length); michael@0: #ifdef ANDROID michael@0: __android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", utf8str.get()); michael@0: #endif michael@0: #ifdef XP_WIN michael@0: if (IsDebuggerPresent()) { michael@0: OutputDebugStringW(reinterpret_cast(chars)); michael@0: } michael@0: #endif michael@0: fputs(utf8str.get(), gOutFile); michael@0: fflush(gOutFile); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: Load(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = 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: RootedString str(cx); michael@0: for (unsigned i = 0; i < args.length(); i++) { michael@0: str = 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", michael@0: filename.ptr()); michael@0: return false; michael@0: } 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 (!compileOnly && !JS_ExecuteScript(cx, obj, script)) michael@0: return false; michael@0: } michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: Version(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = 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, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: fprintf(gOutFile, "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, unsigned argc, jsval *vp) michael@0: { michael@0: gExitCode = 0; michael@0: JS_ConvertArguments(cx, JS::CallArgsFromVp(argc, vp),"/ i", &gExitCode); michael@0: michael@0: gQuitting = true; michael@0: // exit(0); michael@0: return false; michael@0: } michael@0: michael@0: // Provide script a way to disable the xpcshell error reporter, preventing michael@0: // reported errors from being logged to the console and also from affecting the michael@0: // exit code returned by the xpcshell binary. michael@0: static bool michael@0: IgnoreReportedErrors(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (args.length() != 1 || !args[0].isBoolean()) { michael@0: JS_ReportError(cx, "Bad arguments"); michael@0: return false; michael@0: } michael@0: gIgnoreReportedErrors = args[0].toBoolean(); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DumpXPC(JSContext *cx, unsigned argc, jsval *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, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); 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, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); 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: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: #endif michael@0: michael@0: static bool michael@0: SendCommand(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: if (args.length() == 0) { michael@0: JS_ReportError(cx, "Function takes at least one argument!"); michael@0: return false; michael@0: } michael@0: michael@0: JSString* str = ToString(cx, args[0]); michael@0: if (!str) { michael@0: JS_ReportError(cx, "Could not convert argument 1 to string!"); michael@0: return false; michael@0: } michael@0: michael@0: if (args.length() > 1 && JS_TypeOfValue(cx, args[1]) != JSTYPE_FUNCTION) { michael@0: JS_ReportError(cx, "Could not convert argument 2 to function!"); michael@0: return false; michael@0: } michael@0: michael@0: if (!XRE_SendTestShellCommand(cx, str, args.length() > 1 ? args[1].address() : nullptr)) { michael@0: JS_ReportError(cx, "Couldn't send command!"); 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: Options(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: JS::CallArgs args = CallArgsFromVp(argc, vp); michael@0: ContextOptions oldOptions = ContextOptionsRef(cx); michael@0: michael@0: for (unsigned i = 0; i < args.length(); ++i) { michael@0: JSString *str = ToString(cx, args[i]); michael@0: if (!str) michael@0: return false; michael@0: michael@0: JSAutoByteString opt(cx, str); michael@0: if (!opt) michael@0: return false; michael@0: michael@0: if (strcmp(opt.ptr(), "strict") == 0) michael@0: ContextOptionsRef(cx).toggleExtraWarnings(); michael@0: else if (strcmp(opt.ptr(), "werror") == 0) michael@0: ContextOptionsRef(cx).toggleWerror(); michael@0: else if (strcmp(opt.ptr(), "strict_mode") == 0) michael@0: ContextOptionsRef(cx).toggleStrictMode(); michael@0: else { michael@0: JS_ReportError(cx, "unknown option name '%s'. The valid names are " michael@0: "strict, werror, and strict_mode.", opt.ptr()); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: char *names = nullptr; michael@0: if (oldOptions.extraWarnings()) { michael@0: names = JS_sprintf_append(names, "%s", "strict"); michael@0: if (!names) { michael@0: JS_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: } michael@0: if (oldOptions.werror()) { michael@0: names = JS_sprintf_append(names, "%s%s", names ? "," : "", "werror"); michael@0: if (!names) { michael@0: JS_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: } michael@0: if (names && oldOptions.strictMode()) { michael@0: names = JS_sprintf_append(names, "%s%s", names ? "," : "", "strict_mode"); michael@0: if (!names) { michael@0: JS_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: JSString *str = JS_NewStringCopyZ(cx, names); michael@0: free(names); michael@0: if (!str) michael@0: return false; michael@0: michael@0: args.rval().setString(str); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: Parent(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (args.length() != 1) { michael@0: JS_ReportError(cx, "Wrong number of arguments"); michael@0: return false; michael@0: } michael@0: michael@0: Value v = args[0]; michael@0: if (JSVAL_IS_PRIMITIVE(v)) { michael@0: JS_ReportError(cx, "Only objects have parents!"); michael@0: return false; michael@0: } michael@0: michael@0: args.rval().setObjectOrNull(JS_GetParent(&v.toObject())); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: Atob(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (!args.length()) michael@0: return true; michael@0: michael@0: return xpc::Base64Decode(cx, args[0], args.rval()); michael@0: } michael@0: michael@0: static bool michael@0: Btoa(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (!args.length()) michael@0: return true; michael@0: michael@0: return xpc::Base64Encode(cx, args[0], args.rval()); michael@0: } michael@0: michael@0: static bool michael@0: Blob(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: JS::CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: nsCOMPtr native = michael@0: do_CreateInstance("@mozilla.org/dom/multipart-blob;1"); michael@0: if (!native) { michael@0: JS_ReportError(cx, "Could not create native object!"); michael@0: return false; michael@0: } michael@0: michael@0: nsCOMPtr initializer = do_QueryInterface(native); michael@0: MOZ_ASSERT(initializer); michael@0: michael@0: nsresult rv = initializer->Initialize(nullptr, cx, nullptr, args); michael@0: if (NS_FAILED(rv)) { michael@0: JS_ReportError(cx, "Could not initialize native object!"); michael@0: return false; michael@0: } michael@0: michael@0: nsCOMPtr xpc = do_GetService(kXPConnectServiceContractID, &rv); michael@0: if (NS_FAILED(rv)) { michael@0: JS_ReportError(cx, "Could not get XPConnent service!"); michael@0: return false; michael@0: } michael@0: michael@0: JSObject *global = JS::CurrentGlobalOrNull(cx); michael@0: rv = xpc->WrapNativeToJSVal(cx, global, native, nullptr, michael@0: &NS_GET_IID(nsISupports), true, michael@0: args.rval()); michael@0: if (NS_FAILED(rv)) { michael@0: JS_ReportError(cx, "Could not wrap native object!"); michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: File(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: JS::CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: nsCOMPtr native = michael@0: do_CreateInstance("@mozilla.org/dom/multipart-file;1"); michael@0: if (!native) { michael@0: JS_ReportError(cx, "Could not create native object!"); michael@0: return false; michael@0: } michael@0: michael@0: nsCOMPtr initializer = do_QueryInterface(native); michael@0: MOZ_ASSERT(initializer); michael@0: michael@0: nsresult rv = initializer->Initialize(nullptr, cx, nullptr, args); michael@0: if (NS_FAILED(rv)) { michael@0: JS_ReportError(cx, "Could not initialize native object!"); michael@0: return false; michael@0: } michael@0: michael@0: nsCOMPtr xpc = do_GetService(kXPConnectServiceContractID, &rv); michael@0: if (NS_FAILED(rv)) { michael@0: JS_ReportError(cx, "Could not get XPConnent service!"); michael@0: return false; michael@0: } michael@0: michael@0: JSObject *global = JS::CurrentGlobalOrNull(cx); michael@0: rv = xpc->WrapNativeToJSVal(cx, global, native, nullptr, michael@0: &NS_GET_IID(nsISupports), true, michael@0: args.rval()); michael@0: if (NS_FAILED(rv)) { michael@0: JS_ReportError(cx, "Could not wrap native object!"); michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static Maybe sScriptedInterruptCallback; michael@0: michael@0: static bool michael@0: XPCShellInterruptCallback(JSContext *cx) michael@0: { michael@0: MOZ_ASSERT(!sScriptedInterruptCallback.empty()); michael@0: RootedValue callback(cx, sScriptedInterruptCallback.ref()); michael@0: michael@0: // If no interrupt callback was set by script, no-op. michael@0: if (callback.isUndefined()) michael@0: return true; michael@0: michael@0: JSAutoCompartment ac(cx, &callback.toObject()); michael@0: RootedValue rv(cx); michael@0: if (!JS_CallFunctionValue(cx, JS::NullPtr(), callback, JS::HandleValueArray::empty(), &rv) || michael@0: !rv.isBoolean()) michael@0: { michael@0: NS_WARNING("Scripted interrupt callback failed! Terminating script."); michael@0: JS_ClearPendingException(cx); michael@0: return false; michael@0: } michael@0: michael@0: return rv.toBoolean(); michael@0: } michael@0: michael@0: static bool michael@0: SetInterruptCallback(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: MOZ_ASSERT(!sScriptedInterruptCallback.empty()); michael@0: michael@0: // Sanity-check args. michael@0: JS::CallArgs args = JS::CallArgsFromVp(argc, vp); michael@0: if (args.length() != 1) { michael@0: JS_ReportError(cx, "Wrong number of arguments"); michael@0: return false; michael@0: } michael@0: michael@0: // Allow callers to remove the interrupt callback by passing undefined. michael@0: if (args[0].isUndefined()) { michael@0: sScriptedInterruptCallback.ref() = UndefinedValue(); michael@0: return true; michael@0: } michael@0: michael@0: // Otherwise, we should have a callable object. michael@0: if (!args[0].isObject() || !JS_ObjectIsCallable(cx, &args[0].toObject())) { michael@0: JS_ReportError(cx, "Argument must be callable"); michael@0: return false; michael@0: } michael@0: michael@0: sScriptedInterruptCallback.ref() = args[0]; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: SimulateActivityCallback(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: // Sanity-check args. michael@0: JS::CallArgs args = JS::CallArgsFromVp(argc, vp); michael@0: if (args.length() != 1 || !args[0].isBoolean()) { michael@0: JS_ReportError(cx, "Wrong number of arguments"); michael@0: return false; michael@0: } michael@0: xpc::SimulateActivityCallback(args[0].toBoolean()); michael@0: return true; michael@0: } michael@0: michael@0: static const JSFunctionSpec glob_functions[] = { michael@0: JS_FS("print", Print, 0,0), michael@0: JS_FS("readline", ReadLine, 1,0), michael@0: JS_FS("load", Load, 1,0), michael@0: JS_FS("quit", Quit, 0,0), michael@0: JS_FS("ignoreReportedErrors", IgnoreReportedErrors, 1,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("options", Options, 0,0), michael@0: JS_FN("parent", Parent, 1,0), michael@0: JS_FS("sendCommand", SendCommand, 1,0), michael@0: JS_FS("atob", Atob, 1,0), michael@0: JS_FS("btoa", Btoa, 1,0), michael@0: JS_FS("Blob", Blob, 2,JSFUN_CONSTRUCTOR), michael@0: JS_FS("File", File, 2,JSFUN_CONSTRUCTOR), michael@0: JS_FS("setInterruptCallback", SetInterruptCallback, 1,0), michael@0: JS_FS("simulateActivityCallback", SimulateActivityCallback, 1,0), michael@0: JS_FS_END michael@0: }; michael@0: michael@0: static bool michael@0: env_setProperty(JSContext *cx, HandleObject obj, HandleId id, bool strict, MutableHandleValue vp) michael@0: { michael@0: /* XXX porting may be easy, but these don't seem to supply setenv by default */ michael@0: #if !defined SOLARIS michael@0: JSString *valstr; michael@0: JS::Rooted idstr(cx); michael@0: int rv; michael@0: michael@0: RootedValue idval(cx); michael@0: if (!JS_IdToValue(cx, id, &idval)) michael@0: return false; michael@0: michael@0: idstr = ToString(cx, idval); michael@0: valstr = ToString(cx, vp); michael@0: if (!idstr || !valstr) michael@0: return false; michael@0: JSAutoByteString name(cx, idstr); michael@0: if (!name) michael@0: return false; michael@0: JSAutoByteString value(cx, valstr); michael@0: if (!value) michael@0: return false; michael@0: #if defined XP_WIN || defined HPUX || defined OSF1 || defined SCO michael@0: { michael@0: char *waste = JS_smprintf("%s=%s", name.ptr(), value.ptr()); michael@0: if (!waste) { michael@0: JS_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: rv = putenv(waste); michael@0: #ifdef XP_WIN michael@0: /* michael@0: * HPUX9 at least still has the bad old non-copying putenv. michael@0: * michael@0: * Per mail from , OSF1 also has a putenv michael@0: * that will crash if you pass it an auto char array (so it must place michael@0: * its argument directly in the char *environ[] array). michael@0: */ michael@0: free(waste); michael@0: #endif michael@0: } michael@0: #else michael@0: rv = setenv(name.ptr(), value.ptr(), 1); michael@0: #endif michael@0: if (rv < 0) { michael@0: JS_ReportError(cx, "can't set envariable %s to %s", name.ptr(), value.ptr()); michael@0: return false; michael@0: } michael@0: vp.set(STRING_TO_JSVAL(valstr)); michael@0: #endif /* !defined SOLARIS */ michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: env_enumerate(JSContext *cx, HandleObject obj) michael@0: { michael@0: static bool reflected; michael@0: char **evp, *name, *value; michael@0: RootedString valstr(cx); michael@0: bool ok; michael@0: michael@0: if (reflected) michael@0: return true; michael@0: michael@0: for (evp = (char **)JS_GetPrivate(obj); (name = *evp) != nullptr; evp++) { michael@0: value = strchr(name, '='); michael@0: if (!value) michael@0: continue; michael@0: *value++ = '\0'; michael@0: valstr = JS_NewStringCopyZ(cx, value); michael@0: ok = valstr ? JS_DefineProperty(cx, obj, name, valstr, JSPROP_ENUMERATE) : false; michael@0: value[-1] = '='; michael@0: if (!ok) michael@0: return false; michael@0: } michael@0: michael@0: reflected = true; michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: env_resolve(JSContext *cx, HandleObject obj, HandleId id, michael@0: JS::MutableHandleObject objp) michael@0: { michael@0: JSString *idstr, *valstr; michael@0: michael@0: RootedValue idval(cx); michael@0: if (!JS_IdToValue(cx, id, &idval)) michael@0: return false; michael@0: michael@0: idstr = ToString(cx, idval); michael@0: if (!idstr) michael@0: return false; michael@0: JSAutoByteString name(cx, idstr); michael@0: if (!name) michael@0: return false; michael@0: const char *value = getenv(name.ptr()); michael@0: if (value) { michael@0: valstr = JS_NewStringCopyZ(cx, value); michael@0: if (!valstr) michael@0: return false; michael@0: if (!JS_DefinePropertyById(cx, obj, id, STRING_TO_JSVAL(valstr), michael@0: nullptr, nullptr, JSPROP_ENUMERATE)) { michael@0: return false; michael@0: } michael@0: objp.set(obj); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static const JSClass env_class = { michael@0: "environment", JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE, michael@0: JS_PropertyStub, JS_DeletePropertyStub, michael@0: JS_PropertyStub, env_setProperty, michael@0: env_enumerate, (JSResolveOp) env_resolve, michael@0: JS_ConvertStub, nullptr michael@0: }; michael@0: michael@0: /***************************************************************************/ michael@0: michael@0: typedef enum JSShellErrNum { 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: } JSShellErrNum; michael@0: michael@0: static const JSErrorFormatString jsShell_ErrorFormatString[JSShellErr_Limit] = { michael@0: #define MSG_DEF(name, number, count, exception, format) \ michael@0: { format, count } , michael@0: #include "jsshell.msg" michael@0: #undef MSG_DEF michael@0: }; michael@0: michael@0: static const JSErrorFormatString * michael@0: my_GetErrorMessage(void *userRef, const char *locale, const unsigned errorNumber) michael@0: { michael@0: if (errorNumber == 0 || errorNumber >= JSShellErr_Limit) michael@0: return nullptr; michael@0: michael@0: return &jsShell_ErrorFormatString[errorNumber]; michael@0: } michael@0: michael@0: static void michael@0: ProcessFile(JSContext *cx, JS::Handle obj, const char *filename, FILE *file, michael@0: bool forceTTY) michael@0: { michael@0: JS::RootedScript script(cx); michael@0: JS::RootedValue 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: } 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: DoBeginRequest(cx); michael@0: michael@0: JS::CompileOptions options(cx); michael@0: options.setUTF8(true) michael@0: .setFileAndLine(filename, 1); michael@0: script = JS::Compile(cx, obj, options, file); michael@0: if (script && !compileOnly) michael@0: (void)JS_ExecuteScript(cx, obj, script, &result); michael@0: DoEndRequest(cx); 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: /* 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(cx, 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: DoBeginRequest(cx); 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: script = JS_CompileScript(cx, obj, buffer, strlen(buffer), options); michael@0: if (script) { michael@0: JSErrorReporter older; michael@0: michael@0: if (!compileOnly) { 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 = ToString(cx, result); michael@0: JS_SetErrorReporter(cx, older); michael@0: JSAutoByteString bytes; michael@0: if (str && bytes.encodeLatin1(cx, str)) michael@0: fprintf(gOutFile, "%s\n", bytes.ptr()); michael@0: else michael@0: ok = false; michael@0: } michael@0: } michael@0: } michael@0: DoEndRequest(cx); michael@0: } while (!hitEOF && !gQuitting); michael@0: michael@0: fprintf(gOutFile, "\n"); michael@0: } michael@0: michael@0: static void michael@0: Process(JSContext *cx, JS::Handle obj, const char *filename, bool forceTTY) michael@0: { michael@0: FILE *file; michael@0: michael@0: if (forceTTY || !filename || strcmp(filename, "-") == 0) { michael@0: file = stdin; michael@0: } else { michael@0: file = fopen(filename, "r"); michael@0: if (!file) { michael@0: JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, michael@0: JSSMSG_CANT_OPEN, michael@0: filename, strerror(errno)); michael@0: gExitCode = EXITCODE_FILE_NOT_FOUND; michael@0: return; michael@0: } michael@0: } michael@0: michael@0: ProcessFile(cx, obj, filename, file, forceTTY); michael@0: if (file != stdin) michael@0: fclose(file); michael@0: } michael@0: michael@0: static int michael@0: usage(void) michael@0: { michael@0: fprintf(gErrFile, "%s\n", JS_GetImplementationVersion()); michael@0: fprintf(gErrFile, "usage: xpcshell [-g gredir] [-a appdir] [-r manifest]... [-PsSwWCijmIn] [-v version] [-f scriptfile] [-e script] [scriptfile] [scriptarg...]\n"); michael@0: return 2; michael@0: } michael@0: michael@0: static void michael@0: ProcessArgsForCompartment(JSContext *cx, char **argv, int argc) michael@0: { michael@0: for (int i = 0; i < argc; i++) { michael@0: if (argv[i][0] != '-' || argv[i][1] == '\0') michael@0: break; michael@0: michael@0: switch (argv[i][1]) { michael@0: case 'v': michael@0: case 'f': michael@0: case 'e': michael@0: if (++i == argc) michael@0: return; michael@0: break; michael@0: case 'S': michael@0: ContextOptionsRef(cx).toggleWerror(); michael@0: case 's': michael@0: ContextOptionsRef(cx).toggleExtraWarnings(); michael@0: break; michael@0: case 'I': michael@0: RuntimeOptionsRef(cx).toggleIon() michael@0: .toggleAsmJS(); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: static int michael@0: ProcessArgs(JSContext *cx, JS::Handle obj, char **argv, int argc, XPCShellDirProvider* aDirProvider) michael@0: { michael@0: const char rcfilename[] = "xpcshell.js"; michael@0: FILE *rcfile; michael@0: int i; michael@0: JS::Rooted argsObj(cx); michael@0: char *filename = nullptr; michael@0: bool isInteractive = true; michael@0: bool forceTTY = false; michael@0: michael@0: rcfile = fopen(rcfilename, "r"); michael@0: if (rcfile) { michael@0: printf("[loading '%s'...]\n", rcfilename); michael@0: ProcessFile(cx, obj, rcfilename, rcfile, false); michael@0: fclose(rcfile); michael@0: } michael@0: michael@0: /* michael@0: * Scan past all optional arguments so we can create the arguments object michael@0: * before processing any -f options, which must interleave properly with michael@0: * -v and -w options. This requires two passes, and without getopt, we'll michael@0: * have to keep the option logic here and in the second for loop in sync. michael@0: */ michael@0: for (i = 0; i < argc; i++) { michael@0: if (argv[i][0] != '-' || argv[i][1] == '\0') { michael@0: ++i; michael@0: break; michael@0: } michael@0: switch (argv[i][1]) { michael@0: case 'v': michael@0: case 'f': michael@0: case 'e': michael@0: ++i; michael@0: break; michael@0: default:; michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Create arguments early and define it to root it, so it's safe from any michael@0: * GC calls nested below, and so it is available to -f arguments. michael@0: */ michael@0: argsObj = JS_NewArrayObject(cx, 0); michael@0: if (!argsObj) michael@0: return 1; michael@0: if (!JS_DefineProperty(cx, obj, "arguments", argsObj, 0)) michael@0: return 1; michael@0: michael@0: for (size_t j = 0, length = argc - i; j < length; j++) { michael@0: JSString *str = JS_NewStringCopyZ(cx, argv[i++]); michael@0: if (!str) michael@0: return 1; michael@0: if (!JS_DefineElement(cx, argsObj, j, STRING_TO_JSVAL(str), michael@0: nullptr, nullptr, JSPROP_ENUMERATE)) { michael@0: return 1; michael@0: } michael@0: } michael@0: michael@0: for (i = 0; i < argc; i++) { michael@0: if (argv[i][0] != '-' || argv[i][1] == '\0') { michael@0: filename = argv[i++]; michael@0: isInteractive = false; michael@0: break; michael@0: } michael@0: switch (argv[i][1]) { michael@0: case 'v': michael@0: if (++i == argc) { michael@0: return usage(); michael@0: } michael@0: JS_SetVersionForCompartment(js::GetContextCompartment(cx), michael@0: JSVersion(atoi(argv[i]))); michael@0: break; michael@0: case 'W': michael@0: reportWarnings = false; michael@0: break; michael@0: case 'w': michael@0: reportWarnings = true; michael@0: break; michael@0: case 'x': michael@0: break; michael@0: case 'd': michael@0: xpc_ActivateDebugMode(); michael@0: break; michael@0: case 'f': michael@0: if (++i == argc) { michael@0: return usage(); michael@0: } michael@0: Process(cx, obj, argv[i], false); michael@0: /* michael@0: * XXX: js -f foo.js should interpret foo.js and then michael@0: * drop into interactive mode, but that breaks test michael@0: * harness. Just execute foo.js for now. michael@0: */ michael@0: isInteractive = false; michael@0: break; michael@0: case 'i': michael@0: isInteractive = forceTTY = true; michael@0: break; michael@0: case 'e': michael@0: { michael@0: RootedValue rval(cx); michael@0: michael@0: if (++i == argc) { michael@0: return usage(); michael@0: } michael@0: michael@0: JS_EvaluateScript(cx, obj, argv[i], strlen(argv[i]), "-e", 1, &rval); michael@0: michael@0: isInteractive = false; michael@0: break; michael@0: } michael@0: case 'C': michael@0: compileOnly = true; michael@0: isInteractive = false; michael@0: break; michael@0: case 'S': michael@0: case 's': michael@0: case 'm': michael@0: case 'I': michael@0: // These options are processed in ProcessArgsForCompartment. michael@0: break; michael@0: case 'p': michael@0: { michael@0: // plugins path michael@0: char *pluginPath = argv[++i]; michael@0: nsCOMPtr pluginsDir; michael@0: if (NS_FAILED(XRE_GetFileFromPath(pluginPath, getter_AddRefs(pluginsDir)))) { michael@0: fprintf(gErrFile, "Couldn't use given plugins dir.\n"); michael@0: return usage(); michael@0: } michael@0: aDirProvider->SetPluginDir(pluginsDir); michael@0: break; michael@0: } michael@0: default: michael@0: return usage(); michael@0: } michael@0: } michael@0: michael@0: if (filename || isInteractive) michael@0: Process(cx, obj, filename, forceTTY); michael@0: michael@0: return gExitCode; michael@0: } michael@0: michael@0: /***************************************************************************/ michael@0: michael@0: // #define TEST_InitClassesWithNewWrappedGlobal michael@0: michael@0: #ifdef TEST_InitClassesWithNewWrappedGlobal michael@0: // XXX hacky test code... michael@0: #include "xpctest.h" michael@0: michael@0: class TestGlobal : public nsIXPCTestNoisy, public nsIXPCScriptable michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSIXPCTESTNOISY michael@0: NS_DECL_NSIXPCSCRIPTABLE michael@0: michael@0: TestGlobal(){} michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(TestGlobal, nsIXPCTestNoisy, nsIXPCScriptable) michael@0: michael@0: // The nsIXPCScriptable map declaration that will generate stubs for us... michael@0: #define XPC_MAP_CLASSNAME TestGlobal michael@0: #define XPC_MAP_QUOTED_CLASSNAME "TestGlobal" michael@0: #define XPC_MAP_FLAGS nsIXPCScriptable::USE_JSSTUB_FOR_ADDPROPERTY |\ michael@0: nsIXPCScriptable::USE_JSSTUB_FOR_DELPROPERTY |\ michael@0: nsIXPCScriptable::USE_JSSTUB_FOR_SETPROPERTY michael@0: #include "xpc_map_end.h" /* This will #undef the above */ michael@0: michael@0: NS_IMETHODIMP TestGlobal::Squawk() {return NS_OK;} michael@0: michael@0: #endif michael@0: michael@0: // uncomment to install the test 'this' translator michael@0: // #define TEST_TranslateThis michael@0: michael@0: #ifdef TEST_TranslateThis michael@0: michael@0: #include "xpctest.h" michael@0: michael@0: class nsXPCFunctionThisTranslator : public nsIXPCFunctionThisTranslator michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSIXPCFUNCTIONTHISTRANSLATOR michael@0: michael@0: nsXPCFunctionThisTranslator(); michael@0: virtual ~nsXPCFunctionThisTranslator(); michael@0: /* additional members */ michael@0: }; michael@0: michael@0: /* Implementation file */ michael@0: NS_IMPL_ISUPPORTS(nsXPCFunctionThisTranslator, nsIXPCFunctionThisTranslator) michael@0: michael@0: nsXPCFunctionThisTranslator::nsXPCFunctionThisTranslator() michael@0: { michael@0: } michael@0: michael@0: nsXPCFunctionThisTranslator::~nsXPCFunctionThisTranslator() michael@0: { michael@0: } michael@0: michael@0: /* nsISupports TranslateThis (in nsISupports aInitialThis); */ michael@0: NS_IMETHODIMP michael@0: nsXPCFunctionThisTranslator::TranslateThis(nsISupports *aInitialThis, michael@0: nsISupports **_retval) michael@0: { michael@0: nsCOMPtr temp = aInitialThis; michael@0: temp.forget(_retval); michael@0: return NS_OK; michael@0: } michael@0: michael@0: #endif michael@0: michael@0: static void michael@0: XPCShellErrorReporter(JSContext *cx, const char *message, JSErrorReport *rep) michael@0: { michael@0: if (gIgnoreReportedErrors) michael@0: return; michael@0: michael@0: if (!JSREPORT_IS_WARNING(rep->flags)) michael@0: gExitCode = EXITCODE_RUNTIME_ERROR; michael@0: michael@0: // Delegate to the system error reporter for heavy lifting. michael@0: xpc::SystemErrorReporter(cx, message, rep); michael@0: } michael@0: michael@0: static bool michael@0: ContextCallback(JSContext *cx, unsigned contextOp) michael@0: { michael@0: if (contextOp == JSCONTEXT_NEW) michael@0: JS_SetErrorReporter(cx, XPCShellErrorReporter); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: GetCurrentWorkingDirectory(nsAString& workingDirectory) michael@0: { michael@0: #if !defined(XP_WIN) && !defined(XP_UNIX) michael@0: //XXX: your platform should really implement this michael@0: return false; michael@0: #elif XP_WIN michael@0: DWORD requiredLength = GetCurrentDirectoryW(0, nullptr); michael@0: workingDirectory.SetLength(requiredLength); michael@0: GetCurrentDirectoryW(workingDirectory.Length(), michael@0: (LPWSTR)workingDirectory.BeginWriting()); michael@0: // we got a trailing null there michael@0: workingDirectory.SetLength(requiredLength); michael@0: workingDirectory.Replace(workingDirectory.Length() - 1, 1, L'\\'); michael@0: #elif defined(XP_UNIX) michael@0: nsAutoCString cwd; michael@0: // 1024 is just a guess at a sane starting value michael@0: size_t bufsize = 1024; michael@0: char* result = nullptr; michael@0: while (result == nullptr) { michael@0: cwd.SetLength(bufsize); michael@0: result = getcwd(cwd.BeginWriting(), cwd.Length()); michael@0: if (!result) { michael@0: if (errno != ERANGE) michael@0: return false; michael@0: // need to make the buffer bigger michael@0: bufsize *= 2; michael@0: } michael@0: } michael@0: // size back down to the actual string length michael@0: cwd.SetLength(strlen(result) + 1); michael@0: cwd.Replace(cwd.Length() - 1, 1, '/'); michael@0: workingDirectory = NS_ConvertUTF8toUTF16(cwd); michael@0: #endif michael@0: return true; michael@0: } michael@0: michael@0: static JSSecurityCallbacks shellSecurityCallbacks; michael@0: michael@0: int michael@0: XRE_XPCShellMain(int argc, char **argv, char **envp) michael@0: { michael@0: JSRuntime *rt; michael@0: JSContext *cx; michael@0: int result; michael@0: nsresult rv; michael@0: michael@0: gErrFile = stderr; michael@0: gOutFile = stdout; michael@0: gInFile = stdin; michael@0: michael@0: NS_LogInit(); michael@0: michael@0: nsCOMPtr appFile; michael@0: rv = XRE_GetBinaryPath(argv[0], getter_AddRefs(appFile)); michael@0: if (NS_FAILED(rv)) { michael@0: printf("Couldn't find application file.\n"); michael@0: return 1; michael@0: } michael@0: nsCOMPtr appDir; michael@0: rv = appFile->GetParent(getter_AddRefs(appDir)); michael@0: if (NS_FAILED(rv)) { michael@0: printf("Couldn't get application directory.\n"); michael@0: return 1; michael@0: } michael@0: michael@0: XPCShellDirProvider dirprovider; michael@0: michael@0: dirprovider.SetAppFile(appFile); michael@0: michael@0: nsCOMPtr greDir; michael@0: if (argc > 1 && !strcmp(argv[1], "-g")) { michael@0: if (argc < 3) michael@0: return usage(); michael@0: michael@0: rv = XRE_GetFileFromPath(argv[2], getter_AddRefs(greDir)); michael@0: if (NS_FAILED(rv)) { michael@0: printf("Couldn't use given GRE dir.\n"); michael@0: return 1; michael@0: } michael@0: michael@0: dirprovider.SetGREDir(greDir); michael@0: michael@0: argc -= 2; michael@0: argv += 2; michael@0: } else { michael@0: nsAutoString workingDir; michael@0: if (!GetCurrentWorkingDirectory(workingDir)) { michael@0: printf("GetCurrentWorkingDirectory failed.\n"); michael@0: return 1; michael@0: } michael@0: rv = NS_NewLocalFile(workingDir, true, getter_AddRefs(greDir)); michael@0: if (NS_FAILED(rv)) { michael@0: printf("NS_NewLocalFile failed.\n"); michael@0: return 1; michael@0: } michael@0: } michael@0: michael@0: if (argc > 1 && !strcmp(argv[1], "-a")) { michael@0: if (argc < 3) michael@0: return usage(); michael@0: michael@0: nsCOMPtr dir; michael@0: rv = XRE_GetFileFromPath(argv[2], getter_AddRefs(dir)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: appDir = do_QueryInterface(dir, &rv); michael@0: dirprovider.SetAppDir(appDir); michael@0: } michael@0: if (NS_FAILED(rv)) { michael@0: printf("Couldn't use given appdir.\n"); michael@0: return 1; michael@0: } michael@0: argc -= 2; michael@0: argv += 2; michael@0: } michael@0: michael@0: while (argc > 1 && !strcmp(argv[1], "-r")) { michael@0: if (argc < 3) michael@0: return usage(); michael@0: michael@0: nsCOMPtr lf; michael@0: rv = XRE_GetFileFromPath(argv[2], getter_AddRefs(lf)); michael@0: if (NS_FAILED(rv)) { michael@0: printf("Couldn't get manifest file.\n"); michael@0: return 1; michael@0: } michael@0: XRE_AddManifestLocation(NS_COMPONENT_LOCATION, lf); michael@0: michael@0: argc -= 2; michael@0: argv += 2; michael@0: } michael@0: michael@0: #ifdef MOZ_CRASHREPORTER michael@0: const char *val = getenv("MOZ_CRASHREPORTER"); michael@0: if (val && *val) { michael@0: rv = CrashReporter::SetExceptionHandler(greDir, true); michael@0: if (NS_FAILED(rv)) { michael@0: printf("CrashReporter::SetExceptionHandler failed!\n"); michael@0: return 1; michael@0: } michael@0: MOZ_ASSERT(CrashReporter::GetEnabled()); michael@0: } michael@0: #endif michael@0: michael@0: { michael@0: if (argc > 1 && !strcmp(argv[1], "--greomni")) { michael@0: nsCOMPtr greOmni; michael@0: nsCOMPtr appOmni; michael@0: XRE_GetFileFromPath(argv[2], getter_AddRefs(greOmni)); michael@0: if (argc > 3 && !strcmp(argv[3], "--appomni")) { michael@0: XRE_GetFileFromPath(argv[4], getter_AddRefs(appOmni)); michael@0: argc-=2; michael@0: argv+=2; michael@0: } else { michael@0: appOmni = greOmni; michael@0: } michael@0: michael@0: XRE_InitOmnijar(greOmni, appOmni); michael@0: argc-=2; michael@0: argv+=2; michael@0: } michael@0: michael@0: nsCOMPtr servMan; michael@0: rv = NS_InitXPCOM2(getter_AddRefs(servMan), appDir, &dirprovider); michael@0: if (NS_FAILED(rv)) { michael@0: printf("NS_InitXPCOM2 failed!\n"); michael@0: return 1; michael@0: } michael@0: michael@0: nsCOMPtr rtsvc = do_GetService("@mozilla.org/js/xpc/RuntimeService;1"); michael@0: // get the JSRuntime from the runtime svc michael@0: if (!rtsvc) { michael@0: printf("failed to get nsJSRuntimeService!\n"); michael@0: return 1; michael@0: } michael@0: michael@0: if (NS_FAILED(rtsvc->GetRuntime(&rt)) || !rt) { michael@0: printf("failed to get JSRuntime from nsJSRuntimeService!\n"); michael@0: return 1; michael@0: } michael@0: michael@0: rtsvc->RegisterContextCallback(ContextCallback); michael@0: michael@0: // Override the default XPConnect interrupt callback. We could store the michael@0: // old one and restore it before shutting down, but there's not really a michael@0: // reason to bother. michael@0: sScriptedInterruptCallback.construct(rt, UndefinedValue()); michael@0: JS_SetInterruptCallback(rt, XPCShellInterruptCallback); michael@0: michael@0: cx = JS_NewContext(rt, 8192); michael@0: if (!cx) { michael@0: printf("JS_NewContext failed!\n"); michael@0: return 1; michael@0: } michael@0: michael@0: argc--; michael@0: argv++; michael@0: ProcessArgsForCompartment(cx, argv, argc); michael@0: michael@0: nsCOMPtr xpc = do_GetService(nsIXPConnect::GetCID()); michael@0: if (!xpc) { michael@0: printf("failed to get nsXPConnect service!\n"); michael@0: return 1; michael@0: } michael@0: michael@0: nsCOMPtr systemprincipal; michael@0: // Fetch the system principal and store it away in a global, to use for michael@0: // script compilation in Load() and ProcessFile() (including interactive michael@0: // eval loop) michael@0: { michael@0: 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(systemprincipal)); michael@0: if (NS_FAILED(rv)) { michael@0: fprintf(gErrFile, "+++ Failed to obtain SystemPrincipal from ScriptSecurityManager service.\n"); michael@0: } else { michael@0: // fetch the JS principals and stick in a global michael@0: gJSPrincipals = nsJSPrincipals::get(systemprincipal); michael@0: JS_HoldPrincipals(gJSPrincipals); michael@0: } michael@0: } else { michael@0: fprintf(gErrFile, "+++ Failed to get ScriptSecurityManager service, running without principals"); michael@0: } michael@0: } michael@0: michael@0: const JSSecurityCallbacks *scb = JS_GetSecurityCallbacks(rt); michael@0: MOZ_ASSERT(scb, "We are assuming that nsScriptSecurityManager::Init() has been run"); michael@0: shellSecurityCallbacks = *scb; michael@0: JS_SetSecurityCallbacks(rt, &shellSecurityCallbacks); michael@0: michael@0: #ifdef TEST_TranslateThis michael@0: nsCOMPtr michael@0: translator(new nsXPCFunctionThisTranslator); michael@0: xpc->SetFunctionThisTranslator(NS_GET_IID(nsITestXPCFunctionCallback), translator); michael@0: #endif michael@0: michael@0: nsCxPusher pusher; michael@0: pusher.Push(cx); michael@0: michael@0: nsRefPtr backstagePass; michael@0: rv = NS_NewBackstagePass(getter_AddRefs(backstagePass)); michael@0: if (NS_FAILED(rv)) { michael@0: fprintf(gErrFile, "+++ Failed to create BackstagePass: %8x\n", michael@0: static_cast(rv)); michael@0: return 1; michael@0: } michael@0: michael@0: // Make the default XPCShell global use a fresh zone (rather than the michael@0: // System Zone) to improve cross-zone test coverage. michael@0: JS::CompartmentOptions options; michael@0: options.setZone(JS::FreshZone) michael@0: .setVersion(JSVERSION_LATEST); michael@0: nsCOMPtr holder; michael@0: rv = xpc->InitClassesWithNewWrappedGlobal(cx, michael@0: static_cast(backstagePass), michael@0: systemprincipal, michael@0: 0, michael@0: options, michael@0: getter_AddRefs(holder)); michael@0: if (NS_FAILED(rv)) michael@0: return 1; michael@0: michael@0: { michael@0: JS::Rooted glob(cx, holder->GetJSObject()); michael@0: if (!glob) { michael@0: return 1; michael@0: } michael@0: michael@0: // Even if we're building in a configuration where source is michael@0: // discarded, there's no reason to do that on XPCShell, and doing so michael@0: // might break various automation scripts. michael@0: JS::CompartmentOptionsRef(glob).setDiscardSource(false); michael@0: michael@0: backstagePass->SetGlobalObject(glob); michael@0: michael@0: JSAutoCompartment ac(cx, glob); michael@0: michael@0: if (!JS_InitReflect(cx, glob)) { michael@0: JS_EndRequest(cx); michael@0: return 1; michael@0: } michael@0: michael@0: if (!JS_DefineFunctions(cx, glob, glob_functions) || michael@0: !JS_DefineProfilingFunctions(cx, glob)) { michael@0: JS_EndRequest(cx); michael@0: return 1; michael@0: } michael@0: michael@0: JS::Rooted envobj(cx); michael@0: envobj = JS_DefineObject(cx, glob, "environment", &env_class, nullptr, 0); michael@0: if (!envobj) { michael@0: JS_EndRequest(cx); michael@0: return 1; michael@0: } michael@0: michael@0: JS_SetPrivate(envobj, envp); michael@0: michael@0: nsAutoString workingDirectory; michael@0: if (GetCurrentWorkingDirectory(workingDirectory)) michael@0: gWorkingDirectory = &workingDirectory; michael@0: michael@0: JS_DefineProperty(cx, glob, "__LOCATION__", JS::UndefinedHandleValue, 0, michael@0: GetLocationProperty, nullptr); michael@0: michael@0: result = ProcessArgs(cx, glob, argv, argc, &dirprovider); michael@0: michael@0: JS_DropPrincipals(rt, gJSPrincipals); michael@0: JS_SetAllNonReservedSlotsToUndefined(cx, glob); michael@0: JS_GC(rt); michael@0: } michael@0: pusher.Pop(); michael@0: JS_GC(rt); michael@0: JS_DestroyContext(cx); michael@0: } // this scopes the nsCOMPtrs michael@0: michael@0: if (!XRE_ShutdownTestShell()) michael@0: NS_ERROR("problem shutting down testshell"); michael@0: michael@0: // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM michael@0: rv = NS_ShutdownXPCOM( nullptr ); michael@0: MOZ_ASSERT(NS_SUCCEEDED(rv), "NS_ShutdownXPCOM failed"); michael@0: michael@0: sScriptedInterruptCallback.destroyIfConstructed(); michael@0: michael@0: #ifdef TEST_CALL_ON_WRAPPED_JS_AFTER_SHUTDOWN michael@0: // test of late call and release (see above) michael@0: JSContext* bogusCX; michael@0: bogus->Peek(&bogusCX); michael@0: bogus = nullptr; michael@0: #endif michael@0: michael@0: appDir = nullptr; michael@0: appFile = nullptr; michael@0: dirprovider.ClearGREDir(); michael@0: dirprovider.ClearAppDir(); michael@0: dirprovider.ClearPluginDir(); michael@0: dirprovider.ClearAppFile(); michael@0: michael@0: #ifdef MOZ_CRASHREPORTER michael@0: // Shut down the crashreporter service to prevent leaking some strings it holds. michael@0: if (CrashReporter::GetEnabled()) michael@0: CrashReporter::UnsetExceptionHandler(); michael@0: #endif michael@0: michael@0: NS_LogTerm(); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: void michael@0: XPCShellDirProvider::SetGREDir(nsIFile* greDir) michael@0: { michael@0: mGREDir = greDir; michael@0: } michael@0: michael@0: void michael@0: XPCShellDirProvider::SetAppFile(nsIFile* appFile) michael@0: { michael@0: mAppFile = appFile; michael@0: } michael@0: michael@0: void michael@0: XPCShellDirProvider::SetAppDir(nsIFile* appDir) michael@0: { michael@0: mAppDir = appDir; michael@0: } michael@0: michael@0: void michael@0: XPCShellDirProvider::SetPluginDir(nsIFile* pluginDir) michael@0: { michael@0: mPluginDir = pluginDir; 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, michael@0: nsIDirectoryServiceProvider, michael@0: nsIDirectoryServiceProvider2) michael@0: michael@0: NS_IMETHODIMP michael@0: XPCShellDirProvider::GetFile(const char *prop, bool *persistent, michael@0: nsIFile* *result) michael@0: { michael@0: if (mGREDir && !strcmp(prop, NS_GRE_DIR)) { michael@0: *persistent = true; michael@0: return mGREDir->Clone(result); michael@0: } else if (mAppFile && !strcmp(prop, XRE_EXECUTABLE_FILE)) { michael@0: *persistent = true; michael@0: return mAppFile->Clone(result); michael@0: } else if (mGREDir && !strcmp(prop, NS_APP_PREF_DEFAULTS_50_DIR)) { michael@0: nsCOMPtr file; michael@0: *persistent = true; michael@0: if (NS_FAILED(mGREDir->Clone(getter_AddRefs(file))) || michael@0: NS_FAILED(file->AppendNative(NS_LITERAL_CSTRING("defaults"))) || michael@0: NS_FAILED(file->AppendNative(NS_LITERAL_CSTRING("pref")))) michael@0: return NS_ERROR_FAILURE; michael@0: file.forget(result); michael@0: return NS_OK; michael@0: } michael@0: michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: XPCShellDirProvider::GetFiles(const char *prop, nsISimpleEnumerator* *result) michael@0: { michael@0: if (mGREDir && !strcmp(prop, "ChromeML")) { michael@0: nsCOMArray dirs; michael@0: michael@0: nsCOMPtr file; michael@0: mGREDir->Clone(getter_AddRefs(file)); michael@0: file->AppendNative(NS_LITERAL_CSTRING("chrome")); michael@0: dirs.AppendObject(file); michael@0: michael@0: nsresult rv = NS_GetSpecialDirectory(NS_APP_CHROME_DIR, michael@0: getter_AddRefs(file)); michael@0: if (NS_SUCCEEDED(rv)) michael@0: dirs.AppendObject(file); michael@0: michael@0: return NS_NewArrayEnumerator(result, dirs); michael@0: } else if (!strcmp(prop, NS_APP_PREFS_DEFAULTS_DIR_LIST)) { michael@0: nsCOMArray dirs; michael@0: nsCOMPtr appDir; michael@0: bool exists; michael@0: if (mAppDir && michael@0: NS_SUCCEEDED(mAppDir->Clone(getter_AddRefs(appDir))) && michael@0: NS_SUCCEEDED(appDir->AppendNative(NS_LITERAL_CSTRING("defaults"))) && michael@0: NS_SUCCEEDED(appDir->AppendNative(NS_LITERAL_CSTRING("preferences"))) && michael@0: NS_SUCCEEDED(appDir->Exists(&exists)) && exists) { michael@0: dirs.AppendObject(appDir); michael@0: return NS_NewArrayEnumerator(result, dirs); michael@0: } michael@0: return NS_ERROR_FAILURE; michael@0: } else if (!strcmp(prop, NS_APP_PLUGINS_DIR_LIST)) { michael@0: nsCOMArray dirs; michael@0: // Add the test plugin location passed in by the caller or through michael@0: // runxpcshelltests. michael@0: if (mPluginDir) { michael@0: dirs.AppendObject(mPluginDir); michael@0: // If there was no path specified, default to the one set up by automation michael@0: } else { michael@0: nsCOMPtr file; michael@0: bool exists; michael@0: // We have to add this path, buildbot copies the test plugin directory michael@0: // to (app)/bin when unpacking test zips. michael@0: if (mGREDir) { michael@0: mGREDir->Clone(getter_AddRefs(file)); michael@0: if (NS_SUCCEEDED(mGREDir->Clone(getter_AddRefs(file)))) { michael@0: file->AppendNative(NS_LITERAL_CSTRING("plugins")); michael@0: if (NS_SUCCEEDED(file->Exists(&exists)) && exists) { michael@0: dirs.AppendObject(file); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: return NS_NewArrayEnumerator(result, dirs); michael@0: } michael@0: return NS_ERROR_FAILURE; michael@0: }