summaryrefslogtreecommitdiffstats
path: root/ipc/testshell
diff options
context:
space:
mode:
Diffstat (limited to 'ipc/testshell')
-rw-r--r--ipc/testshell/PTestShell.ipdl28
-rw-r--r--ipc/testshell/PTestShellCommand.ipdl23
-rw-r--r--ipc/testshell/TestShellChild.cpp54
-rw-r--r--ipc/testshell/TestShellChild.h40
-rw-r--r--ipc/testshell/TestShellParent.cpp90
-rw-r--r--ipc/testshell/TestShellParent.h69
-rw-r--r--ipc/testshell/XPCShellEnvironment.cpp468
-rw-r--r--ipc/testshell/XPCShellEnvironment.h57
-rw-r--r--ipc/testshell/moz.build37
-rw-r--r--ipc/testshell/tests/test_ipcshell.js34
-rw-r--r--ipc/testshell/tests/test_ipcshell_child.js6
-rw-r--r--ipc/testshell/tests/xpcshell.ini8
12 files changed, 914 insertions, 0 deletions
diff --git a/ipc/testshell/PTestShell.ipdl b/ipc/testshell/PTestShell.ipdl
new file mode 100644
index 0000000000..f952f59584
--- /dev/null
+++ b/ipc/testshell/PTestShell.ipdl
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
+/* 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 protocol PContent;
+include protocol PTestShellCommand;
+
+namespace mozilla {
+namespace ipc {
+
+[ManualDealloc]
+async protocol PTestShell
+{
+ manager PContent;
+
+ manages PTestShellCommand;
+
+child:
+ async __delete__();
+
+ async ExecuteCommand(nsString aCommand);
+
+ async PTestShellCommand(nsString aCommand);
+};
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/testshell/PTestShellCommand.ipdl b/ipc/testshell/PTestShellCommand.ipdl
new file mode 100644
index 0000000000..a99c22630d
--- /dev/null
+++ b/ipc/testshell/PTestShellCommand.ipdl
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
+/* 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 protocol PTestShell;
+
+include "mozilla/ipc/TestShellParent.h";
+
+namespace mozilla {
+namespace ipc {
+
+[ManualDealloc, ChildImpl=virtual, ParentImpl="TestShellCommandParent"]
+protocol PTestShellCommand
+{
+ manager PTestShell;
+
+parent:
+ async __delete__(nsString aResponse);
+};
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/testshell/TestShellChild.cpp b/ipc/testshell/TestShellChild.cpp
new file mode 100644
index 0000000000..6af5630d2d
--- /dev/null
+++ b/ipc/testshell/TestShellChild.cpp
@@ -0,0 +1,54 @@
+/* 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 "TestShellChild.h"
+
+using mozilla::ipc::PTestShellCommandChild;
+using mozilla::ipc::TestShellChild;
+using mozilla::ipc::XPCShellEnvironment;
+
+TestShellChild::TestShellChild()
+ : mXPCShell(XPCShellEnvironment::CreateEnvironment()) {}
+
+mozilla::ipc::IPCResult TestShellChild::RecvExecuteCommand(
+ const nsAString& aCommand) {
+ if (mXPCShell->IsQuitting()) {
+ NS_WARNING("Commands sent after quit command issued!");
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ if (!mXPCShell->EvaluateString(aCommand)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ return IPC_OK();
+}
+
+PTestShellCommandChild* TestShellChild::AllocPTestShellCommandChild(
+ const nsAString& aCommand) {
+ return new PTestShellCommandChild();
+}
+
+bool TestShellChild::DeallocPTestShellCommandChild(
+ PTestShellCommandChild* aCommand) {
+ delete aCommand;
+ return true;
+}
+
+mozilla::ipc::IPCResult TestShellChild::RecvPTestShellCommandConstructor(
+ PTestShellCommandChild* aActor, const nsAString& aCommand) {
+ if (mXPCShell->IsQuitting()) {
+ NS_WARNING("Commands sent after quit command issued!");
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ nsString response;
+ if (!mXPCShell->EvaluateString(aCommand, &response)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ if (!PTestShellCommandChild::Send__delete__(aActor, response)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ return IPC_OK();
+}
diff --git a/ipc/testshell/TestShellChild.h b/ipc/testshell/TestShellChild.h
new file mode 100644
index 0000000000..12f54549d6
--- /dev/null
+++ b/ipc/testshell/TestShellChild.h
@@ -0,0 +1,40 @@
+/* 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/. */
+
+#ifndef ipc_testshell_TestShellChild_h
+#define ipc_testshell_TestShellChild_h 1
+
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "mozilla/ipc/PTestShellChild.h"
+#include "mozilla/ipc/PTestShellCommandChild.h"
+#include "mozilla/ipc/XPCShellEnvironment.h"
+
+namespace mozilla {
+
+namespace ipc {
+
+class XPCShellEnvironment;
+
+class TestShellChild : public PTestShellChild {
+ public:
+ TestShellChild();
+
+ mozilla::ipc::IPCResult RecvExecuteCommand(const nsAString& aCommand);
+
+ PTestShellCommandChild* AllocPTestShellCommandChild(
+ const nsAString& aCommand);
+
+ mozilla::ipc::IPCResult RecvPTestShellCommandConstructor(
+ PTestShellCommandChild* aActor, const nsAString& aCommand) override;
+
+ bool DeallocPTestShellCommandChild(PTestShellCommandChild* aCommand);
+
+ private:
+ UniquePtr<XPCShellEnvironment> mXPCShell;
+};
+
+} /* namespace ipc */
+} /* namespace mozilla */
+
+#endif /* ipc_testshell_TestShellChild_h */
diff --git a/ipc/testshell/TestShellParent.cpp b/ipc/testshell/TestShellParent.cpp
new file mode 100644
index 0000000000..8cd8dad08e
--- /dev/null
+++ b/ipc/testshell/TestShellParent.cpp
@@ -0,0 +1,90 @@
+/* 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 "TestShellParent.h"
+
+/* This must occur *after* TestShellParent.h to avoid typedefs conflicts. */
+#include "jsfriendapi.h"
+#include "js/CallAndConstruct.h" // JS_CallFunctionValue
+
+#include "mozilla/dom/AutoEntryScript.h"
+
+using namespace mozilla;
+using mozilla::ipc::PTestShellCommandParent;
+using mozilla::ipc::TestShellCommandParent;
+using mozilla::ipc::TestShellParent;
+
+void TestShellParent::ActorDestroy(ActorDestroyReason aWhy) {
+ // Implement me! Bug 1005177
+}
+
+PTestShellCommandParent* TestShellParent::AllocPTestShellCommandParent(
+ const nsAString& aCommand) {
+ return new TestShellCommandParent();
+}
+
+bool TestShellParent::DeallocPTestShellCommandParent(
+ PTestShellCommandParent* aActor) {
+ delete aActor;
+ return true;
+}
+
+bool TestShellParent::CommandDone(TestShellCommandParent* command,
+ const nsAString& aResponse) {
+ // XXX what should happen if the callback fails?
+ /*bool ok = */ command->RunCallback(aResponse);
+ command->ReleaseCallback();
+
+ return true;
+}
+
+bool TestShellCommandParent::SetCallback(JSContext* aCx,
+ const JS::Value& aCallback) {
+ if (!mCallback.initialized()) {
+ mCallback.init(aCx, aCallback);
+ return true;
+ }
+
+ mCallback = aCallback;
+
+ return true;
+}
+
+bool TestShellCommandParent::RunCallback(const nsAString& aResponse) {
+ NS_ENSURE_TRUE(mCallback.isObject(), false);
+
+ MOZ_RELEASE_ASSERT(js::IsFunctionObject(&mCallback.toObject()));
+
+ // We're about to run script via JS_CallFunctionValue, so we need an
+ // AutoEntryScript. This is just for testing and not in any spec.
+ dom::AutoEntryScript aes(&mCallback.toObject(), "TestShellCommand");
+ JSContext* cx = aes.cx();
+ JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
+
+ JSString* str =
+ JS_NewUCStringCopyN(cx, aResponse.BeginReading(), aResponse.Length());
+ NS_ENSURE_TRUE(str, false);
+
+ JS::Rooted<JS::Value> strVal(cx, JS::StringValue(str));
+
+ JS::Rooted<JS::Value> rval(cx);
+ JS::Rooted<JS::Value> callback(cx, mCallback);
+ bool ok = JS_CallFunctionValue(cx, global, callback,
+ JS::HandleValueArray(strVal), &rval);
+ NS_ENSURE_TRUE(ok, false);
+
+ return true;
+}
+
+void TestShellCommandParent::ReleaseCallback() { mCallback.reset(); }
+
+bool TestShellCommandParent::ExecuteCallback(const nsAString& aResponse) {
+ return static_cast<TestShellParent*>(Manager())->CommandDone(this, aResponse);
+}
+
+void TestShellCommandParent::ActorDestroy(ActorDestroyReason why) {
+ if (why == AbnormalShutdown) {
+ ExecuteCallback(u""_ns);
+ }
+}
diff --git a/ipc/testshell/TestShellParent.h b/ipc/testshell/TestShellParent.h
new file mode 100644
index 0000000000..ac704f04b2
--- /dev/null
+++ b/ipc/testshell/TestShellParent.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=8 et :
+ */
+/* 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/. */
+
+#ifndef ipc_testshell_TestShellParent_h
+#define ipc_testshell_TestShellParent_h 1
+
+#include "mozilla/ipc/PTestShellParent.h"
+#include "mozilla/ipc/PTestShellCommandParent.h"
+
+#include "js/RootingAPI.h"
+#include "js/Value.h"
+#include "nsString.h"
+
+namespace mozilla {
+
+namespace ipc {
+
+class TestShellCommandParent;
+
+class TestShellParent : public PTestShellParent {
+ friend class PTestShellParent;
+
+ public:
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ PTestShellCommandParent* AllocPTestShellCommandParent(
+ const nsAString& aCommand);
+
+ bool DeallocPTestShellCommandParent(PTestShellCommandParent* aActor);
+
+ bool CommandDone(TestShellCommandParent* aActor, const nsAString& aResponse);
+};
+
+class TestShellCommandParent : public PTestShellCommandParent {
+ friend class PTestShellCommandParent;
+
+ public:
+ TestShellCommandParent() = default;
+
+ bool SetCallback(JSContext* aCx, const JS::Value& aCallback);
+
+ bool RunCallback(const nsAString& aResponse);
+
+ void ReleaseCallback();
+
+ protected:
+ bool ExecuteCallback(const nsAString& aResponse);
+
+ void ActorDestroy(ActorDestroyReason why) override;
+
+ mozilla::ipc::IPCResult Recv__delete__(const nsAString& aResponse) {
+ if (!ExecuteCallback(aResponse)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ return IPC_OK();
+ }
+
+ private:
+ JS::PersistentRooted<JS::Value> mCallback;
+};
+
+} /* namespace ipc */
+} /* namespace mozilla */
+
+#endif /* ipc_testshell_TestShellParent_h */
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;
+}
diff --git a/ipc/testshell/XPCShellEnvironment.h b/ipc/testshell/XPCShellEnvironment.h
new file mode 100644
index 0000000000..b479ec85b6
--- /dev/null
+++ b/ipc/testshell/XPCShellEnvironment.h
@@ -0,0 +1,57 @@
+/* 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/. */
+
+#ifndef _IPC_TESTSHELL_XPCSHELLENVIRONMENT_H_
+#define _IPC_TESTSHELL_XPCSHELLENVIRONMENT_H_
+
+#include "base/basictypes.h"
+
+#include <string>
+#include <stdio.h>
+
+#include "nsCOMPtr.h"
+#include "nsDebug.h"
+#include "nsString.h"
+#include "nsJSPrincipals.h"
+#include "nsContentUtils.h"
+#include "js/RootingAPI.h"
+#include "js/TypeDecls.h"
+
+struct JSPrincipals;
+
+namespace mozilla {
+namespace ipc {
+
+class XPCShellEnvironment {
+ public:
+ static XPCShellEnvironment* CreateEnvironment();
+ ~XPCShellEnvironment();
+
+ void ProcessFile(JSContext* cx, const char* filename, FILE* file,
+ bool forceTTY);
+ bool EvaluateString(const nsAString& aString, nsString* aResult = nullptr);
+
+ JSPrincipals* GetPrincipal() {
+ return nsJSPrincipals::get(nsContentUtils::GetSystemPrincipal());
+ }
+
+ JSObject* GetGlobalObject() { return mGlobalHolder; }
+
+ void SetIsQuitting() { mQuitting = true; }
+ bool IsQuitting() { return mQuitting; }
+
+ protected:
+ XPCShellEnvironment();
+ bool Init();
+
+ private:
+ JS::PersistentRooted<JSObject*> mGlobalHolder;
+
+ bool mQuitting;
+};
+
+} /* namespace ipc */
+} /* namespace mozilla */
+
+#endif /* _IPC_TESTSHELL_XPCSHELLENVIRONMENT_H_ */
diff --git a/ipc/testshell/moz.build b/ipc/testshell/moz.build
new file mode 100644
index 0000000000..56447a7165
--- /dev/null
+++ b/ipc/testshell/moz.build
@@ -0,0 +1,37 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXPORTS.mozilla.ipc += [
+ "TestShellChild.h",
+ "TestShellParent.h",
+ "XPCShellEnvironment.h",
+]
+
+XPCSHELL_TESTS_MANIFESTS += ["tests/xpcshell.ini"]
+
+SOURCES += [
+ "TestShellChild.cpp",
+ "TestShellParent.cpp",
+ "XPCShellEnvironment.cpp",
+]
+
+IPDL_SOURCES = [
+ "PTestShell.ipdl",
+ "PTestShellCommand.ipdl",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+# For xpcshell error messages and nsAutoJSString
+LOCAL_INCLUDES += [
+ "/dom/base",
+ "/js/xpconnect/src",
+]
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "XPConnect")
diff --git a/ipc/testshell/tests/test_ipcshell.js b/ipc/testshell/tests/test_ipcshell.js
new file mode 100644
index 0000000000..3e2e54e670
--- /dev/null
+++ b/ipc/testshell/tests/test_ipcshell.js
@@ -0,0 +1,34 @@
+// eslint-disable-next-line mozilla/use-services
+const runtime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
+
+function callback(result) {
+ Assert.equal(result, Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT);
+ do_test_finished();
+}
+
+function run_test() {
+ do_test_pending();
+
+ Assert.equal(runtime.processType, Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT);
+
+ sendCommand("load('test_ipcshell_child.js');");
+
+ sendCommand("runtime.processType;", callback);
+
+ [
+ ["C", "D"],
+ ["D", "C"],
+ ["\u010C", "D"],
+ ["D", "\u010C"],
+ ].forEach(function (pair) {
+ do_test_pending();
+ var cmp = pair[0].localeCompare(pair[1]);
+ sendCommand(
+ "'" + pair[0] + "'.localeCompare('" + pair[1] + "');",
+ function (result) {
+ Assert.equal(cmp, result);
+ do_test_finished();
+ }
+ );
+ });
+}
diff --git a/ipc/testshell/tests/test_ipcshell_child.js b/ipc/testshell/tests/test_ipcshell_child.js
new file mode 100644
index 0000000000..f2a0e1e245
--- /dev/null
+++ b/ipc/testshell/tests/test_ipcshell_child.js
@@ -0,0 +1,6 @@
+// eslint-disable-next-line mozilla/use-services
+const runtime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
+
+function run_test() {
+ Assert.equal(runtime.processType, Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT);
+}
diff --git a/ipc/testshell/tests/xpcshell.ini b/ipc/testshell/tests/xpcshell.ini
new file mode 100644
index 0000000000..3dfd85b2ea
--- /dev/null
+++ b/ipc/testshell/tests/xpcshell.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+head =
+skip-if = toolkit == 'android'
+
+[test_ipcshell.js]
+# Bug 676963: test fails consistently on Android
+fail-if = os == "android"
+[test_ipcshell_child.js]