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.cpp468
1 files changed, 468 insertions, 0 deletions
diff --git a/ipc/testshell/XPCShellEnvironment.cpp b/ipc/testshell/XPCShellEnvironment.cpp
new file mode 100644
index 0000000000..3d83b36ab4
--- /dev/null
+++ b/ipc/testshell/XPCShellEnvironment.cpp
@@ -0,0 +1,468 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 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/. */
+
+#ifdef HAVE_IO_H
+# include <io.h> /* for isatty() */
+#endif
+#ifdef HAVE_UNISTD_H
+# include <unistd.h> /* for isatty() */
+#endif
+
+#include "jsapi.h"
+#include "js/CharacterEncoding.h"
+#include "js/CompilationAndEvaluation.h" // JS::Compile{,Utf8File}
+#include "js/PropertyAndElement.h" // JS_DefineFunctions, JS_DefineProperty, JS_GetProperty
+#include "js/PropertySpec.h"
+#include "js/RealmOptions.h"
+#include "js/SourceText.h" // JS::Source{Ownership,Text}
+
+#include "xpcpublic.h"
+
+#include "XPCShellEnvironment.h"
+
+#include "mozilla/Utf8.h" // mozilla::Utf8Unit
+#include "mozilla/dom/AutoEntryScript.h"
+#include "mozilla/dom/ScriptSettings.h"
+
+#include "nsIPrincipal.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIXPConnect.h"
+#include "nsServiceManagerUtils.h"
+
+#include "nsJSUtils.h"
+
+#include "BackstagePass.h"
+
+#include "TestShellChild.h"
+
+using mozilla::dom::AutoEntryScript;
+using mozilla::dom::AutoJSAPI;
+using mozilla::ipc::XPCShellEnvironment;
+using namespace JS;
+
+namespace {
+
+static const char kDefaultRuntimeScriptFilename[] = "xpcshell.js";
+
+inline XPCShellEnvironment* Environment(JS::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++) {
+ JS::Rooted<JSString*> str(cx, JS::ToString(cx, args[i]));
+ if (!str) return false;
+ JS::UniqueChars bytes = JS_EncodeStringToUTF8(cx, str);
+ if (!bytes) return false;
+ fprintf(stdout, "%s%s", i ? " " : "", bytes.get());
+ 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;
+
+ JS::Rooted<JSString*> str(cx, JS::ToString(cx, args[0]));
+ if (!str) return false;
+ JS::UniqueChars bytes = JS_EncodeStringToUTF8(cx, str);
+ if (!bytes) return false;
+
+ fputs(bytes.get(), stdout);
+ fflush(stdout);
+ return true;
+}
+
+static bool Load(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+
+ JS::RootedObject thisObject(cx);
+ if (!args.computeThis(cx, &thisObject)) return false;
+ if (!JS_IsGlobalObject(thisObject)) {
+ 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;
+ }
+ JS::UniqueChars filename = JS_EncodeStringToUTF8(cx, str);
+ if (!filename) {
+ return false;
+ }
+
+ JS::CompileOptions options(cx);
+ JS::Rooted<JSScript*> script(
+ cx, JS::CompileUtf8Path(cx, options, filename.get()));
+ if (!script) {
+ return false;
+ }
+
+ if (!JS_ExecuteScript(cx, script)) {
+ return false;
+ }
+ }
+ args.rval().setUndefined();
+ 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 = nsIXPConnect::XPConnect();
+ 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
+
+#ifdef ANDROID
+static bool ChangeTestShellDir(JSContext* cx, unsigned argc, Value* vp) {
+ // This method should only be used by testing/xpcshell/head.js to change to
+ // the correct directory on Android Remote XPCShell tests.
+ //
+ // TODO: Bug 1801725 - Find a more ergonomic way to do this than exposing
+ // identical methods in XPCShellEnvironment and XPCShellImpl to chdir on
+ // android for Remote XPCShell tests on Android.
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 1) {
+ JS_ReportErrorASCII(cx, "changeTestShellDir() takes one argument");
+ return false;
+ }
+
+ nsAutoJSCString path;
+ if (!path.init(cx, args[0])) {
+ JS_ReportErrorASCII(
+ cx, "changeTestShellDir(): could not convert argument 1 to string");
+ return false;
+ }
+
+ if (chdir(path.get())) {
+ JS_ReportErrorASCII(cx, "changeTestShellDir(): could not change directory");
+ return false;
+ }
+
+ return true;
+}
+#endif
+
+const JSFunctionSpec gGlobalFunctions[] = {
+ JS_FN("print", Print, 0, 0),
+ JS_FN("load", Load, 1, 0),
+ JS_FN("quit", Quit, 0, 0),
+ JS_FN("dumpXPC", DumpXPC, 1, 0),
+ JS_FN("dump", Dump, 1, 0),
+ JS_FN("gc", GC, 0, 0),
+#ifdef JS_GC_ZEAL
+ JS_FN("gczeal", GCZeal, 1, 0),
+#endif
+#ifdef ANDROID
+ JS_FN("changeTestShellDir", ChangeTestShellDir, 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 '#'.
+ */
+ 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.setFileAndLine(filename, 1);
+
+ JS::Rooted<JSScript*> script(cx, JS::CompileUtf8File(cx, options, file));
+ if (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_Utf8BufferIsCompilableUnit(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::SourceText<mozilla::Utf8Unit> srcBuf;
+ JS::Rooted<JSScript*> script(cx);
+
+ if (srcBuf.init(cx, buffer, strlen(buffer),
+ JS::SourceOwnership::Borrowed) &&
+ (script = JS::Compile(cx, options, srcBuf))) {
+ ok = JS_ExecuteScript(cx, script, &result);
+ if (ok && !result.isUndefined()) {
+ /* Suppress warnings from JS::ToString(). */
+ JS::AutoSuppressWarningReporter suppressWarnings(cx);
+ str = JS::ToString(cx, result);
+ JS::UniqueChars bytes;
+ if (str) bytes = JS_EncodeStringToLatin1(cx, str);
+
+ if (!!bytes)
+ fprintf(stdout, "%s\n", bytes.get());
+ else
+ ok = false;
+ }
+ }
+ } while (!hitEOF && !env->IsQuitting());
+
+ fprintf(stdout, "\n");
+}
+
+// static
+XPCShellEnvironment* XPCShellEnvironment::CreateEnvironment() {
+ auto* 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;
+ }
+ JS_SetAllNonReservedSlotsToUndefined(mGlobalHolder);
+ mGlobalHolder.reset();
+
+ JS_GC(jsapi.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<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");
+ }
+
+ auto backstagePass = MakeRefPtr<BackstagePass>();
+
+ JS::RealmOptions options;
+ options.creationOptions().setNewCompartmentInSystemZone();
+ xpc::SetPrefableRealmOptions(options);
+
+ JS::Rooted<JSObject*> globalObj(cx);
+ rv = xpc::InitClassesWithNewWrappedGlobal(
+ cx, static_cast<nsIGlobalObject*>(backstagePass), principal, 0, options,
+ &globalObj);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("InitClassesWithNewWrappedGlobal failed!");
+ return false;
+ }
+
+ if (!globalObj) {
+ NS_ERROR("Failed to get global JSObject!");
+ return false;
+ }
+ JSAutoRealm ar(cx, globalObj);
+
+ backstagePass->SetGlobalObject(globalObj);
+
+ JS::Rooted<Value> privateVal(cx, PrivateValue(this));
+ if (!JS_DefineProperty(cx, globalObj, "__XPCShellEnvironment", privateVal,
+ JSPROP_READONLY | JSPROP_PERMANENT) ||
+ !JS_DefineFunctions(cx, globalObj, gGlobalFunctions)) {
+ 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 nsAString& aString,
+ nsString* aResult) {
+ AutoEntryScript aes(GetGlobalObject(),
+ "ipc XPCShellEnvironment::EvaluateString");
+ JSContext* cx = aes.cx();
+
+ JS::CompileOptions options(cx);
+ options.setFileAndLine("typein", 0);
+
+ JS::SourceText<char16_t> srcBuf;
+ if (!srcBuf.init(cx, aString.BeginReading(), aString.Length(),
+ JS::SourceOwnership::Borrowed)) {
+ return false;
+ }
+
+ JS::Rooted<JSScript*> script(cx, JS::Compile(cx, options, srcBuf));
+ if (!script) {
+ return false;
+ }
+
+ if (aResult) {
+ aResult->Truncate();
+ }
+
+ JS::Rooted<JS::Value> result(cx);
+ bool ok = JS_ExecuteScript(cx, script, &result);
+ if (ok && !result.isUndefined()) {
+ /* Suppress warnings from JS::ToString(). */
+ JS::AutoSuppressWarningReporter suppressWarnings(cx);
+ JSString* str = JS::ToString(cx, result);
+ nsAutoJSString autoStr;
+ if (str) autoStr.init(cx, str);
+
+ if (!autoStr.IsEmpty() && aResult) {
+ aResult->Assign(autoStr);
+ }
+ }
+
+ return true;
+}