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/. */
8 # include <io.h> /* for isatty() */
11 # include <unistd.h> /* for isatty() */
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
;
48 static const char kDefaultRuntimeScriptFilename
[] = "xpcshell.js";
50 inline XPCShellEnvironment
* Environment(JS::Handle
<JSObject
*> global
) {
52 if (!jsapi
.Init(global
)) {
55 JSContext
* cx
= jsapi
.cx();
57 if (!JS_GetProperty(cx
, global
, "__XPCShellEnvironment", &v
) ||
58 !v
.get().isDouble()) {
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());
76 args
.rval().setUndefined();
80 static bool GetLine(char* bufp
, FILE* file
, const char* prompt
) {
82 fputs(prompt
, stdout
);
84 if (!fgets(line
, sizeof line
, file
)) return false;
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
);
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");
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");
121 filename
= JS_EncodeStringToUTF8(cx
, str
);
122 if (!filename
) return false;
123 JS_ReportErrorUTF8(cx
, "cannot open file '%s' for reading",
128 JS::CompileOptions
options(cx
);
129 options
.setFileAndLine(filename
.get(), 1);
131 JS::Rooted
<JSScript
*> script(cx
, JS::CompileUtf8File(cx
, options
, file
));
133 if (!script
) return false;
135 if (!JS_ExecuteScript(cx
, script
)) {
139 args
.rval().setUndefined();
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();
151 static bool DumpXPC(JSContext
* cx
, unsigned argc
, JS::Value
* vp
) {
152 JS::CallArgs args
= CallArgsFromVp(argc
, vp
);
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();
165 static bool GC(JSContext
* cx
, unsigned argc
, JS::Value
* vp
) {
166 JS::CallArgs args
= JS::CallArgsFromVp(argc
, vp
);
170 args
.rval().setUndefined();
175 static bool GCZeal(JSContext
* cx
, unsigned argc
, JS::Value
* vp
) {
176 CallArgs args
= CallArgsFromVp(argc
, vp
);
179 if (!ToUint32(cx
, args
.get(0), &zeal
)) return false;
181 JS_SetGCZeal(cx
, uint8_t(zeal
), JS_DEFAULT_ZEAL_FREQ
);
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),
193 JS_FN("gczeal", GCZeal
, 1, 0),
197 typedef enum JSShellErrNum
{
198 #define MSG_DEF(name, number, count, exception, format) name = number,
199 #include "jsshell.msg"
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
;
214 char *bufp
, buffer
[4096];
217 JS::Rooted
<JSObject
*> global(cx
, JS::CurrentGlobalOrNull(cx
));
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
);
233 while ((ch
= fgetc(file
)) != EOF
) {
234 if (ch
== '\n' || ch
== '\r') break;
239 JS::CompileOptions
options(cx
);
240 options
.setFileAndLine(filename
, 1);
242 JS::Rooted
<JSScript
*> script(cx
, JS::CompileUtf8File(cx
, options
, file
));
244 (void)JS_ExecuteScript(cx
, script
, &result
);
250 /* It's an interactive filehandle; drop into read-eval-print loop. */
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.
265 if (!GetLine(bufp
, file
, startline
== lineno
? "js> " : "")) {
269 bufp
+= strlen(bufp
);
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
);
295 fprintf(stdout
, "%s\n", bytes
.get());
300 } while (!hitEOF
&& !env
->IsQuitting());
302 fprintf(stdout
, "\n");
306 XPCShellEnvironment
* XPCShellEnvironment::CreateEnvironment() {
307 auto* env
= new XPCShellEnvironment();
308 if (env
&& !env
->Init()) {
315 XPCShellEnvironment::XPCShellEnvironment() : mQuitting(false) {}
317 XPCShellEnvironment::~XPCShellEnvironment() {
318 if (GetGlobalObject()) {
320 if (!jsapi
.Init(GetGlobalObject())) {
323 JS_SetAllNonReservedSlotsToUndefined(mGlobalHolder
);
324 mGlobalHolder
.reset();
330 bool XPCShellEnvironment::Init() {
333 // unbuffer stdout so that output is in the correct order; note that stderr
334 // is unbuffered by default
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
));
348 "+++ Failed to obtain SystemPrincipal from ScriptSecurityManager "
353 "+++ Failed to get ScriptSecurityManager service, running without "
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
,
368 NS_ERROR("InitClassesWithNewWrappedGlobal failed!");
373 NS_ERROR("Failed to get global JSObject!");
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!");
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
);
400 bool XPCShellEnvironment::EvaluateString(const nsAString
& aString
,
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
)) {
415 JS::Rooted
<JSScript
*> script(cx
, JS::Compile(cx
, options
, srcBuf
));
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
);