diff options
Diffstat (limited to 'ipc/testshell')
-rw-r--r-- | ipc/testshell/PTestShell.ipdl | 27 | ||||
-rw-r--r-- | ipc/testshell/PTestShellCommand.ipdl | 23 | ||||
-rw-r--r-- | ipc/testshell/TestShellChild.cpp | 54 | ||||
-rw-r--r-- | ipc/testshell/TestShellChild.h | 44 | ||||
-rw-r--r-- | ipc/testshell/TestShellParent.cpp | 90 | ||||
-rw-r--r-- | ipc/testshell/TestShellParent.h | 74 | ||||
-rw-r--r-- | ipc/testshell/XPCShellEnvironment.cpp | 468 | ||||
-rw-r--r-- | ipc/testshell/XPCShellEnvironment.h | 57 | ||||
-rw-r--r-- | ipc/testshell/moz.build | 37 | ||||
-rw-r--r-- | ipc/testshell/tests/test_ipcshell.js | 34 | ||||
-rw-r--r-- | ipc/testshell/tests/test_ipcshell_child.js | 6 | ||||
-rw-r--r-- | ipc/testshell/tests/xpcshell.toml | 9 |
12 files changed, 923 insertions, 0 deletions
diff --git a/ipc/testshell/PTestShell.ipdl b/ipc/testshell/PTestShell.ipdl new file mode 100644 index 0000000000..db4d7eedcb --- /dev/null +++ b/ipc/testshell/PTestShell.ipdl @@ -0,0 +1,27 @@ +/* -*- 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 { + +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..77f2a19832 --- /dev/null +++ b/ipc/testshell/TestShellChild.h @@ -0,0 +1,44 @@ +/* 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: + NS_INLINE_DECL_REFCOUNTING(TestShellChild, override) + + 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: + ~TestShellChild() = default; + + 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..de4f73a94b --- /dev/null +++ b/ipc/testshell/TestShellParent.h @@ -0,0 +1,74 @@ +/* -*- 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: + NS_INLINE_DECL_REFCOUNTING(TestShellParent, override) + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + PTestShellCommandParent* AllocPTestShellCommandParent( + const nsAString& aCommand); + + bool DeallocPTestShellCommandParent(PTestShellCommandParent* aActor); + + bool CommandDone(TestShellCommandParent* aActor, const nsAString& aResponse); + + private: + ~TestShellParent() = default; +}; + +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..c30175c0c0 --- /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.toml"] + +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.toml b/ipc/testshell/tests/xpcshell.toml new file mode 100644 index 0000000000..9e3b377e24 --- /dev/null +++ b/ipc/testshell/tests/xpcshell.toml @@ -0,0 +1,9 @@ +[DEFAULT] +head = "" +skip-if = ["os == 'android'"] + +["test_ipcshell.js"] +# Bug 676963: test fails consistently on Android +fail-if = ["os == 'android'"] + +["test_ipcshell_child.js"] |