Bug 1782289 [wpt PR 35277] - Move mojojs support out of fedcm-helper.js, a=testonly
[gecko.git] / ipc / testshell / XPCShellEnvironment.cpp
blob59543f20b56efaf0e44e2675708fff9bfd327561
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #ifdef HAVE_IO_H
8 # include <io.h> /* for isatty() */
9 #endif
10 #ifdef HAVE_UNISTD_H
11 # include <unistd.h> /* for isatty() */
12 #endif
14 #include "jsapi.h"
15 #include "js/CharacterEncoding.h"
16 #include "js/CompilationAndEvaluation.h" // JS::Compile{,Utf8File}
17 #include "js/PropertyAndElement.h" // JS_DefineFunctions, JS_DefineProperty, JS_GetProperty
18 #include "js/PropertySpec.h"
19 #include "js/RealmOptions.h"
20 #include "js/SourceText.h" // JS::Source{Ownership,Text}
22 #include "xpcpublic.h"
24 #include "XPCShellEnvironment.h"
26 #include "mozilla/Utf8.h" // mozilla::Utf8Unit
27 #include "mozilla/dom/AutoEntryScript.h"
28 #include "mozilla/dom/ScriptSettings.h"
30 #include "nsIPrincipal.h"
31 #include "nsIScriptSecurityManager.h"
32 #include "nsIXPConnect.h"
33 #include "nsServiceManagerUtils.h"
35 #include "nsJSUtils.h"
37 #include "BackstagePass.h"
39 #include "TestShellChild.h"
41 using mozilla::dom::AutoEntryScript;
42 using mozilla::dom::AutoJSAPI;
43 using mozilla::ipc::XPCShellEnvironment;
44 using namespace JS;
46 namespace {
48 static const char kDefaultRuntimeScriptFilename[] = "xpcshell.js";
50 inline XPCShellEnvironment* Environment(JS::Handle<JSObject*> global) {
51 AutoJSAPI jsapi;
52 if (!jsapi.Init(global)) {
53 return nullptr;
55 JSContext* cx = jsapi.cx();
56 Rooted<Value> v(cx);
57 if (!JS_GetProperty(cx, global, "__XPCShellEnvironment", &v) ||
58 !v.get().isDouble()) {
59 return nullptr;
61 return static_cast<XPCShellEnvironment*>(v.get().toPrivate());
64 static bool Print(JSContext* cx, unsigned argc, JS::Value* vp) {
65 JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
67 for (unsigned i = 0; i < args.length(); i++) {
68 JSString* str = JS::ToString(cx, args[i]);
69 if (!str) return false;
70 JS::UniqueChars bytes = JS_EncodeStringToLatin1(cx, str);
71 if (!bytes) return false;
72 fprintf(stdout, "%s%s", i ? " " : "", bytes.get());
73 fflush(stdout);
75 fputc('\n', stdout);
76 args.rval().setUndefined();
77 return true;
80 static bool GetLine(char* bufp, FILE* file, const char* prompt) {
81 char line[256];
82 fputs(prompt, stdout);
83 fflush(stdout);
84 if (!fgets(line, sizeof line, file)) return false;
85 strcpy(bufp, line);
86 return true;
89 static bool Dump(JSContext* cx, unsigned argc, JS::Value* vp) {
90 JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
92 if (!args.length()) return true;
94 JSString* str = JS::ToString(cx, args[0]);
95 if (!str) return false;
96 JS::UniqueChars bytes = JS_EncodeStringToLatin1(cx, str);
97 if (!bytes) return false;
99 fputs(bytes.get(), stdout);
100 fflush(stdout);
101 return true;
104 static bool Load(JSContext* cx, unsigned argc, JS::Value* vp) {
105 JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
107 JS::RootedObject thisObject(cx);
108 if (!args.computeThis(cx, &thisObject)) return false;
109 if (!JS_IsGlobalObject(thisObject)) {
110 JS_ReportErrorASCII(cx, "Trying to load() into a non-global object");
111 return false;
114 for (unsigned i = 0; i < args.length(); i++) {
115 JS::Rooted<JSString*> str(cx, JS::ToString(cx, args[i]));
116 if (!str) return false;
117 JS::UniqueChars filename = JS_EncodeStringToLatin1(cx, str);
118 if (!filename) return false;
119 FILE* file = fopen(filename.get(), "r");
120 if (!file) {
121 filename = JS_EncodeStringToUTF8(cx, str);
122 if (!filename) return false;
123 JS_ReportErrorUTF8(cx, "cannot open file '%s' for reading",
124 filename.get());
125 return false;
128 JS::CompileOptions options(cx);
129 options.setFileAndLine(filename.get(), 1);
131 JS::Rooted<JSScript*> script(cx, JS::CompileUtf8File(cx, options, file));
132 fclose(file);
133 if (!script) return false;
135 if (!JS_ExecuteScript(cx, script)) {
136 return false;
139 args.rval().setUndefined();
140 return true;
143 static bool Quit(JSContext* cx, unsigned argc, JS::Value* vp) {
144 Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
145 XPCShellEnvironment* env = Environment(global);
146 env->SetIsQuitting();
148 return false;
151 static bool DumpXPC(JSContext* cx, unsigned argc, JS::Value* vp) {
152 JS::CallArgs args = CallArgsFromVp(argc, vp);
154 uint16_t depth = 2;
155 if (args.length() > 0) {
156 if (!JS::ToUint16(cx, args[0], &depth)) return false;
159 nsCOMPtr<nsIXPConnect> xpc = nsIXPConnect::XPConnect();
160 if (xpc) xpc->DebugDump(int16_t(depth));
161 args.rval().setUndefined();
162 return true;
165 static bool GC(JSContext* cx, unsigned argc, JS::Value* vp) {
166 JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
168 JS_GC(cx);
170 args.rval().setUndefined();
171 return true;
174 #ifdef JS_GC_ZEAL
175 static bool GCZeal(JSContext* cx, unsigned argc, JS::Value* vp) {
176 CallArgs args = CallArgsFromVp(argc, vp);
178 uint32_t zeal;
179 if (!ToUint32(cx, args.get(0), &zeal)) return false;
181 JS_SetGCZeal(cx, uint8_t(zeal), JS_DEFAULT_ZEAL_FREQ);
182 return true;
184 #endif
186 const JSFunctionSpec gGlobalFunctions[] = {JS_FN("print", Print, 0, 0),
187 JS_FN("load", Load, 1, 0),
188 JS_FN("quit", Quit, 0, 0),
189 JS_FN("dumpXPC", DumpXPC, 1, 0),
190 JS_FN("dump", Dump, 1, 0),
191 JS_FN("gc", GC, 0, 0),
192 #ifdef JS_GC_ZEAL
193 JS_FN("gczeal", GCZeal, 1, 0),
194 #endif
195 JS_FS_END};
197 typedef enum JSShellErrNum {
198 #define MSG_DEF(name, number, count, exception, format) name = number,
199 #include "jsshell.msg"
200 #undef MSG_DEF
201 JSShellErr_Limit
202 #undef MSGDEF
203 } JSShellErrNum;
205 } /* anonymous namespace */
207 void XPCShellEnvironment::ProcessFile(JSContext* cx, const char* filename,
208 FILE* file, bool forceTTY) {
209 XPCShellEnvironment* env = this;
211 JS::Rooted<JS::Value> result(cx);
212 int lineno, startline;
213 bool ok, hitEOF;
214 char *bufp, buffer[4096];
215 JSString* str;
217 JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
218 MOZ_ASSERT(global);
220 if (forceTTY) {
221 file = stdin;
222 } else if (!isatty(fileno(file))) {
224 * It's not interactive - just execute it.
226 * Support the UNIX #! shell hack; gobble the first line if it starts
227 * with '#'. TODO - this isn't quite compatible with sharp variables,
228 * as a legal js program (using sharp variables) might start with '#'.
229 * But that would require multi-character lookahead.
231 int ch = fgetc(file);
232 if (ch == '#') {
233 while ((ch = fgetc(file)) != EOF) {
234 if (ch == '\n' || ch == '\r') break;
237 ungetc(ch, file);
239 JS::CompileOptions options(cx);
240 options.setFileAndLine(filename, 1);
242 JS::Rooted<JSScript*> script(cx, JS::CompileUtf8File(cx, options, file));
243 if (script) {
244 (void)JS_ExecuteScript(cx, script, &result);
247 return;
250 /* It's an interactive filehandle; drop into read-eval-print loop. */
251 lineno = 1;
252 hitEOF = false;
253 do {
254 bufp = buffer;
255 *bufp = '\0';
258 * Accumulate lines until we get a 'compilable unit' - one that either
259 * generates an error (before running out of source) or that compiles
260 * cleanly. This should be whenever we get a complete statement that
261 * coincides with the end of a line.
263 startline = lineno;
264 do {
265 if (!GetLine(bufp, file, startline == lineno ? "js> " : "")) {
266 hitEOF = true;
267 break;
269 bufp += strlen(bufp);
270 lineno++;
271 } while (
272 !JS_Utf8BufferIsCompilableUnit(cx, global, buffer, strlen(buffer)));
274 /* Clear any pending exception from previous failed compiles. */
275 JS_ClearPendingException(cx);
277 JS::CompileOptions options(cx);
278 options.setFileAndLine("typein", startline);
280 JS::SourceText<mozilla::Utf8Unit> srcBuf;
281 JS::Rooted<JSScript*> script(cx);
283 if (srcBuf.init(cx, buffer, strlen(buffer),
284 JS::SourceOwnership::Borrowed) &&
285 (script = JS::Compile(cx, options, srcBuf))) {
286 ok = JS_ExecuteScript(cx, script, &result);
287 if (ok && !result.isUndefined()) {
288 /* Suppress warnings from JS::ToString(). */
289 JS::AutoSuppressWarningReporter suppressWarnings(cx);
290 str = JS::ToString(cx, result);
291 JS::UniqueChars bytes;
292 if (str) bytes = JS_EncodeStringToLatin1(cx, str);
294 if (!!bytes)
295 fprintf(stdout, "%s\n", bytes.get());
296 else
297 ok = false;
300 } while (!hitEOF && !env->IsQuitting());
302 fprintf(stdout, "\n");
305 // static
306 XPCShellEnvironment* XPCShellEnvironment::CreateEnvironment() {
307 auto* env = new XPCShellEnvironment();
308 if (env && !env->Init()) {
309 delete env;
310 env = nullptr;
312 return env;
315 XPCShellEnvironment::XPCShellEnvironment() : mQuitting(false) {}
317 XPCShellEnvironment::~XPCShellEnvironment() {
318 if (GetGlobalObject()) {
319 AutoJSAPI jsapi;
320 if (!jsapi.Init(GetGlobalObject())) {
321 return;
323 JS_SetAllNonReservedSlotsToUndefined(mGlobalHolder);
324 mGlobalHolder.reset();
326 JS_GC(jsapi.cx());
330 bool XPCShellEnvironment::Init() {
331 nsresult rv;
333 // unbuffer stdout so that output is in the correct order; note that stderr
334 // is unbuffered by default
335 setbuf(stdout, 0);
337 AutoSafeJSContext cx;
339 mGlobalHolder.init(cx);
341 nsCOMPtr<nsIPrincipal> principal;
342 nsCOMPtr<nsIScriptSecurityManager> securityManager =
343 do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
344 if (NS_SUCCEEDED(rv) && securityManager) {
345 rv = securityManager->GetSystemPrincipal(getter_AddRefs(principal));
346 if (NS_FAILED(rv)) {
347 fprintf(stderr,
348 "+++ Failed to obtain SystemPrincipal from ScriptSecurityManager "
349 "service.\n");
351 } else {
352 fprintf(stderr,
353 "+++ Failed to get ScriptSecurityManager service, running without "
354 "principals");
357 auto backstagePass = MakeRefPtr<BackstagePass>();
359 JS::RealmOptions options;
360 options.creationOptions().setNewCompartmentInSystemZone();
361 xpc::SetPrefableRealmOptions(options);
363 JS::Rooted<JSObject*> globalObj(cx);
364 rv = xpc::InitClassesWithNewWrappedGlobal(
365 cx, static_cast<nsIGlobalObject*>(backstagePass), principal, 0, options,
366 &globalObj);
367 if (NS_FAILED(rv)) {
368 NS_ERROR("InitClassesWithNewWrappedGlobal failed!");
369 return false;
372 if (!globalObj) {
373 NS_ERROR("Failed to get global JSObject!");
374 return false;
376 JSAutoRealm ar(cx, globalObj);
378 backstagePass->SetGlobalObject(globalObj);
380 JS::Rooted<Value> privateVal(cx, PrivateValue(this));
381 if (!JS_DefineProperty(cx, globalObj, "__XPCShellEnvironment", privateVal,
382 JSPROP_READONLY | JSPROP_PERMANENT) ||
383 !JS_DefineFunctions(cx, globalObj, gGlobalFunctions)) {
384 NS_ERROR("JS_DefineFunctions failed!");
385 return false;
388 mGlobalHolder = globalObj;
390 FILE* runtimeScriptFile = fopen(kDefaultRuntimeScriptFilename, "r");
391 if (runtimeScriptFile) {
392 fprintf(stdout, "[loading '%s'...]\n", kDefaultRuntimeScriptFilename);
393 ProcessFile(cx, kDefaultRuntimeScriptFilename, runtimeScriptFile, false);
394 fclose(runtimeScriptFile);
397 return true;
400 bool XPCShellEnvironment::EvaluateString(const nsAString& aString,
401 nsString* aResult) {
402 AutoEntryScript aes(GetGlobalObject(),
403 "ipc XPCShellEnvironment::EvaluateString");
404 JSContext* cx = aes.cx();
406 JS::CompileOptions options(cx);
407 options.setFileAndLine("typein", 0);
409 JS::SourceText<char16_t> srcBuf;
410 if (!srcBuf.init(cx, aString.BeginReading(), aString.Length(),
411 JS::SourceOwnership::Borrowed)) {
412 return false;
415 JS::Rooted<JSScript*> script(cx, JS::Compile(cx, options, srcBuf));
416 if (!script) {
417 return false;
420 if (aResult) {
421 aResult->Truncate();
424 JS::Rooted<JS::Value> result(cx);
425 bool ok = JS_ExecuteScript(cx, script, &result);
426 if (ok && !result.isUndefined()) {
427 /* Suppress warnings from JS::ToString(). */
428 JS::AutoSuppressWarningReporter suppressWarnings(cx);
429 JSString* str = JS::ToString(cx, result);
430 nsAutoJSString autoStr;
431 if (str) autoStr.init(cx, str);
433 if (!autoStr.IsEmpty() && aResult) {
434 aResult->Assign(autoStr);
438 return true;