summaryrefslogtreecommitdiffstats
path: root/ipc/testshell/XPCShellEnvironment.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'ipc/testshell/XPCShellEnvironment.cpp')
-rw-r--r--ipc/testshell/XPCShellEnvironment.cpp541
1 files changed, 541 insertions, 0 deletions
diff --git a/ipc/testshell/XPCShellEnvironment.cpp b/ipc/testshell/XPCShellEnvironment.cpp
new file mode 100644
index 000000000..a6979ccae
--- /dev/null
+++ b/ipc/testshell/XPCShellEnvironment.cpp
@@ -0,0 +1,541 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=4 et sw=4 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 <stdlib.h>
+#include <errno.h>
+#ifdef HAVE_IO_H
+#include <io.h> /* for isatty() */
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h> /* for isatty() */
+#endif
+
+#include "base/basictypes.h"
+
+#include "jsapi.h"
+
+#include "xpcpublic.h"
+
+#include "XPCShellEnvironment.h"
+
+#include "mozilla/XPCOM.h"
+
+#include "nsIChannel.h"
+#include "nsIClassInfo.h"
+#include "nsIDirectoryService.h"
+#include "nsIPrincipal.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIURI.h"
+#include "nsIXPConnect.h"
+#include "nsIXPCScriptable.h"
+
+#include "nsJSUtils.h"
+#include "nsJSPrincipals.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+
+#include "BackstagePass.h"
+
+#include "TestShellChild.h"
+#include "TestShellParent.h"
+
+using mozilla::ipc::XPCShellEnvironment;
+using mozilla::ipc::TestShellChild;
+using mozilla::ipc::TestShellParent;
+using mozilla::AutoSafeJSContext;
+using mozilla::dom::AutoJSAPI;
+using mozilla::dom::AutoEntryScript;
+using namespace JS;
+
+namespace {
+
+static const char kDefaultRuntimeScriptFilename[] = "xpcshell.js";
+
+inline XPCShellEnvironment*
+Environment(Handle<JSObject*> global)
+{
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(global)) {
+ return nullptr;
+ }
+ JSContext* cx = jsapi.cx();
+ Rooted<Value> v(cx);
+ if (!JS_GetProperty(cx, global, "__XPCShellEnvironment", &v) ||
+ !v.get().isDouble())
+ {
+ return nullptr;
+ }
+ return static_cast<XPCShellEnvironment*>(v.get().toPrivate());
+}
+
+static bool
+Print(JSContext *cx, unsigned argc, JS::Value *vp)
+{
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+
+ for (unsigned i = 0; i < args.length(); i++) {
+ JSString *str = JS::ToString(cx, args[i]);
+ if (!str)
+ return false;
+ JSAutoByteString bytes(cx, str);
+ if (!bytes)
+ return false;
+ fprintf(stdout, "%s%s", i ? " " : "", bytes.ptr());
+ fflush(stdout);
+ }
+ fputc('\n', stdout);
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+GetLine(char *bufp,
+ FILE *file,
+ const char *prompt)
+{
+ char line[256];
+ fputs(prompt, stdout);
+ fflush(stdout);
+ if (!fgets(line, sizeof line, file))
+ return false;
+ strcpy(bufp, line);
+ return true;
+}
+
+static bool
+Dump(JSContext *cx, unsigned argc, JS::Value *vp)
+{
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+
+ if (!args.length())
+ return true;
+
+ JSString *str = JS::ToString(cx, args[0]);
+ if (!str)
+ return false;
+ JSAutoByteString bytes(cx, str);
+ if (!bytes)
+ return false;
+
+ fputs(bytes.ptr(), stdout);
+ fflush(stdout);
+ return true;
+}
+
+static bool
+Load(JSContext *cx,
+ unsigned argc,
+ JS::Value *vp)
+{
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+
+ JS::Rooted<JSObject*> obj(cx, JS_THIS_OBJECT(cx, vp));
+ if (!obj)
+ return false;
+
+ if (!JS_IsGlobalObject(obj)) {
+ JS_ReportErrorASCII(cx, "Trying to load() into a non-global object");
+ return false;
+ }
+
+ for (unsigned i = 0; i < args.length(); i++) {
+ JS::Rooted<JSString*> str(cx, JS::ToString(cx, args[i]));
+ if (!str)
+ return false;
+ JSAutoByteString filename(cx, str);
+ if (!filename)
+ return false;
+ FILE *file = fopen(filename.ptr(), "r");
+ if (!file) {
+ filename.clear();
+ if (!filename.encodeUtf8(cx, str))
+ return false;
+ JS_ReportErrorUTF8(cx, "cannot open file '%s' for reading", filename.ptr());
+ return false;
+ }
+ JS::CompileOptions options(cx);
+ options.setUTF8(true)
+ .setFileAndLine(filename.ptr(), 1);
+ JS::Rooted<JSScript*> script(cx);
+ bool ok = JS::Compile(cx, options, file, &script);
+ fclose(file);
+ if (!ok)
+ return false;
+
+ if (!JS_ExecuteScript(cx, script)) {
+ return false;
+ }
+ }
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+Version(JSContext *cx,
+ unsigned argc,
+ JS::Value *vp)
+{
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ args.rval().setInt32(JS_GetVersion(cx));
+ if (args.get(0).isInt32())
+ JS_SetVersionForCompartment(js::GetContextCompartment(cx),
+ JSVersion(args[0].toInt32()));
+ return true;
+}
+
+static bool
+Quit(JSContext *cx,
+ unsigned argc,
+ JS::Value *vp)
+{
+ Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
+ XPCShellEnvironment* env = Environment(global);
+ env->SetIsQuitting();
+
+ return false;
+}
+
+static bool
+DumpXPC(JSContext *cx,
+ unsigned argc,
+ JS::Value *vp)
+{
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+
+ uint16_t depth = 2;
+ if (args.length() > 0) {
+ if (!JS::ToUint16(cx, args[0], &depth))
+ return false;
+ }
+
+ nsCOMPtr<nsIXPConnect> xpc = do_GetService(nsIXPConnect::GetCID());
+ if (xpc)
+ xpc->DebugDump(int16_t(depth));
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+GC(JSContext *cx,
+ unsigned argc,
+ JS::Value *vp)
+{
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+
+ JS_GC(cx);
+
+ args.rval().setUndefined();
+ return true;
+}
+
+#ifdef JS_GC_ZEAL
+static bool
+GCZeal(JSContext *cx, unsigned argc, JS::Value *vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ uint32_t zeal;
+ if (!ToUint32(cx, args.get(0), &zeal))
+ return false;
+
+ JS_SetGCZeal(cx, uint8_t(zeal), JS_DEFAULT_ZEAL_FREQ);
+ return true;
+}
+#endif
+
+const JSFunctionSpec gGlobalFunctions[] =
+{
+ JS_FS("print", Print, 0,0),
+ JS_FS("load", Load, 1,0),
+ JS_FS("quit", Quit, 0,0),
+ JS_FS("version", Version, 1,0),
+ JS_FS("dumpXPC", DumpXPC, 1,0),
+ JS_FS("dump", Dump, 1,0),
+ JS_FS("gc", GC, 0,0),
+ #ifdef JS_GC_ZEAL
+ JS_FS("gczeal", GCZeal, 1,0),
+ #endif
+ JS_FS_END
+};
+
+typedef enum JSShellErrNum
+{
+#define MSG_DEF(name, number, count, exception, format) \
+ name = number,
+#include "jsshell.msg"
+#undef MSG_DEF
+ JSShellErr_Limit
+#undef MSGDEF
+} JSShellErrNum;
+
+} /* anonymous namespace */
+
+void
+XPCShellEnvironment::ProcessFile(JSContext *cx,
+ const char *filename,
+ FILE *file,
+ bool forceTTY)
+{
+ XPCShellEnvironment* env = this;
+
+ JS::Rooted<JS::Value> result(cx);
+ int lineno, startline;
+ bool ok, hitEOF;
+ char *bufp, buffer[4096];
+ JSString *str;
+
+ JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
+ MOZ_ASSERT(global);
+
+ if (forceTTY) {
+ file = stdin;
+ }
+ else if (!isatty(fileno(file)))
+ {
+ /*
+ * It's not interactive - just execute it.
+ *
+ * Support the UNIX #! shell hack; gobble the first line if it starts
+ * with '#'. TODO - this isn't quite compatible with sharp variables,
+ * as a legal js program (using sharp variables) might start with '#'.
+ * But that would require multi-character lookahead.
+ */
+ int ch = fgetc(file);
+ if (ch == '#') {
+ while((ch = fgetc(file)) != EOF) {
+ if(ch == '\n' || ch == '\r')
+ break;
+ }
+ }
+ ungetc(ch, file);
+
+ JS::CompileOptions options(cx);
+ options.setUTF8(true)
+ .setFileAndLine(filename, 1);
+ JS::Rooted<JSScript*> script(cx);
+ if (JS::Compile(cx, options, file, &script))
+ (void)JS_ExecuteScript(cx, script, &result);
+
+ return;
+ }
+
+ /* It's an interactive filehandle; drop into read-eval-print loop. */
+ lineno = 1;
+ hitEOF = false;
+ do {
+ bufp = buffer;
+ *bufp = '\0';
+
+ /*
+ * Accumulate lines until we get a 'compilable unit' - one that either
+ * generates an error (before running out of source) or that compiles
+ * cleanly. This should be whenever we get a complete statement that
+ * coincides with the end of a line.
+ */
+ startline = lineno;
+ do {
+ if (!GetLine(bufp, file, startline == lineno ? "js> " : "")) {
+ hitEOF = true;
+ break;
+ }
+ bufp += strlen(bufp);
+ lineno++;
+ } while (!JS_BufferIsCompilableUnit(cx, global, buffer, strlen(buffer)));
+
+ /* Clear any pending exception from previous failed compiles. */
+ JS_ClearPendingException(cx);
+ JS::CompileOptions options(cx);
+ options.setFileAndLine("typein", startline);
+ JS::Rooted<JSScript*> script(cx);
+ if (JS_CompileScript(cx, buffer, strlen(buffer), options, &script)) {
+ JS::WarningReporter older;
+
+ ok = JS_ExecuteScript(cx, script, &result);
+ if (ok && !result.isUndefined()) {
+ /* Suppress warnings from JS::ToString(). */
+ older = JS::SetWarningReporter(cx, nullptr);
+ str = JS::ToString(cx, result);
+ JSAutoByteString bytes;
+ if (str)
+ bytes.encodeLatin1(cx, str);
+ JS::SetWarningReporter(cx, older);
+
+ if (!!bytes)
+ fprintf(stdout, "%s\n", bytes.ptr());
+ else
+ ok = false;
+ }
+ }
+ } while (!hitEOF && !env->IsQuitting());
+
+ fprintf(stdout, "\n");
+}
+
+// static
+XPCShellEnvironment*
+XPCShellEnvironment::CreateEnvironment()
+{
+ XPCShellEnvironment* env = new XPCShellEnvironment();
+ if (env && !env->Init()) {
+ delete env;
+ env = nullptr;
+ }
+ return env;
+}
+
+XPCShellEnvironment::XPCShellEnvironment()
+: mQuitting(false)
+{
+}
+
+XPCShellEnvironment::~XPCShellEnvironment()
+{
+ if (GetGlobalObject()) {
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(GetGlobalObject())) {
+ return;
+ }
+ JSContext* cx = jsapi.cx();
+ Rooted<JSObject*> global(cx, GetGlobalObject());
+
+ {
+ JSAutoCompartment ac(cx, global);
+ JS_SetAllNonReservedSlotsToUndefined(cx, global);
+ }
+ mGlobalHolder.reset();
+
+ JS_GC(cx);
+ }
+}
+
+bool
+XPCShellEnvironment::Init()
+{
+ nsresult rv;
+
+ // unbuffer stdout so that output is in the correct order; note that stderr
+ // is unbuffered by default
+ setbuf(stdout, 0);
+
+ AutoSafeJSContext cx;
+
+ mGlobalHolder.init(cx);
+
+ nsCOMPtr<nsIXPConnect> xpc =
+ do_GetService(nsIXPConnect::GetCID());
+ if (!xpc) {
+ NS_ERROR("failed to get nsXPConnect service!");
+ return false;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsCOMPtr<nsIScriptSecurityManager> securityManager =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && securityManager) {
+ rv = securityManager->GetSystemPrincipal(getter_AddRefs(principal));
+ if (NS_FAILED(rv)) {
+ fprintf(stderr, "+++ Failed to obtain SystemPrincipal from ScriptSecurityManager service.\n");
+ }
+ } else {
+ fprintf(stderr, "+++ Failed to get ScriptSecurityManager service, running without principals");
+ }
+
+ RefPtr<BackstagePass> backstagePass;
+ rv = NS_NewBackstagePass(getter_AddRefs(backstagePass));
+ if (NS_FAILED(rv)) {
+ NS_ERROR("Failed to create backstage pass!");
+ return false;
+ }
+
+ JS::CompartmentOptions options;
+ options.creationOptions().setZone(JS::SystemZone);
+ options.behaviors().setVersion(JSVERSION_LATEST);
+ if (xpc::SharedMemoryEnabled())
+ options.creationOptions().setSharedMemoryAndAtomicsEnabled(true);
+
+ nsCOMPtr<nsIXPConnectJSObjectHolder> holder;
+ rv = xpc->InitClassesWithNewWrappedGlobal(cx,
+ static_cast<nsIGlobalObject *>(backstagePass),
+ principal, 0,
+ options,
+ getter_AddRefs(holder));
+ if (NS_FAILED(rv)) {
+ NS_ERROR("InitClassesWithNewWrappedGlobal failed!");
+ return false;
+ }
+
+ JS::Rooted<JSObject*> globalObj(cx, holder->GetJSObject());
+ if (!globalObj) {
+ NS_ERROR("Failed to get global JSObject!");
+ return false;
+ }
+ JSAutoCompartment ac(cx, globalObj);
+
+ backstagePass->SetGlobalObject(globalObj);
+
+ JS::Rooted<Value> privateVal(cx, PrivateValue(this));
+ if (!JS_DefineProperty(cx, globalObj, "__XPCShellEnvironment",
+ privateVal,
+ JSPROP_READONLY | JSPROP_PERMANENT,
+ JS_STUBGETTER, JS_STUBSETTER) ||
+ !JS_DefineFunctions(cx, globalObj, gGlobalFunctions) ||
+ !JS_DefineProfilingFunctions(cx, globalObj))
+ {
+ NS_ERROR("JS_DefineFunctions failed!");
+ return false;
+ }
+
+ mGlobalHolder = globalObj;
+
+ FILE* runtimeScriptFile = fopen(kDefaultRuntimeScriptFilename, "r");
+ if (runtimeScriptFile) {
+ fprintf(stdout, "[loading '%s'...]\n", kDefaultRuntimeScriptFilename);
+ ProcessFile(cx, kDefaultRuntimeScriptFilename,
+ runtimeScriptFile, false);
+ fclose(runtimeScriptFile);
+ }
+
+ return true;
+}
+
+bool
+XPCShellEnvironment::EvaluateString(const nsString& aString,
+ nsString* aResult)
+{
+ AutoEntryScript aes(GetGlobalObject(),
+ "ipc XPCShellEnvironment::EvaluateString");
+ JSContext* cx = aes.cx();
+
+ JS::CompileOptions options(cx);
+ options.setFileAndLine("typein", 0);
+ JS::Rooted<JSScript*> script(cx);
+ if (!JS_CompileUCScript(cx, aString.get(), aString.Length(), options,
+ &script))
+ {
+ return false;
+ }
+
+ if (aResult) {
+ aResult->Truncate();
+ }
+
+ JS::Rooted<JS::Value> result(cx);
+ bool ok = JS_ExecuteScript(cx, script, &result);
+ if (ok && !result.isUndefined()) {
+ JS::WarningReporter old = JS::SetWarningReporter(cx, nullptr);
+ JSString* str = JS::ToString(cx, result);
+ nsAutoJSString autoStr;
+ if (str)
+ autoStr.init(cx, str);
+ JS::SetWarningReporter(cx, old);
+
+ if (!autoStr.IsEmpty() && aResult) {
+ aResult->Assign(autoStr);
+ }
+ }
+
+ return true;
+}