1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
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 #include "nsXULAppAPI.h"
9 #include "jsfriendapi.h"
10 #include "js/Array.h" // JS::NewArrayObject
11 #include "js/CallAndConstruct.h" // JS_CallFunctionValue
12 #include "js/CharacterEncoding.h"
13 #include "js/CompilationAndEvaluation.h" // JS::Evaluate
14 #include "js/ContextOptions.h"
15 #include "js/Printf.h"
16 #include "js/PropertyAndElement.h" // JS_DefineElement, JS_DefineFunctions, JS_DefineProperty
17 #include "js/PropertySpec.h"
18 #include "js/SourceText.h" // JS::SourceText
19 #include "mozilla/ChaosMode.h"
20 #include "mozilla/dom/AutoEntryScript.h"
21 #include "mozilla/dom/ScriptSettings.h"
22 #include "mozilla/IOInterposer.h"
23 #include "mozilla/Preferences.h"
24 #include "mozilla/Utf8.h" // mozilla::Utf8Unit
25 #include "nsServiceManagerUtils.h"
26 #include "nsComponentManagerUtils.h"
27 #include "nsExceptionHandler.h"
28 #include "nsIServiceManager.h"
31 #include "nsIDirectoryService.h"
32 #include "nsDirectoryServiceDefs.h"
33 #include "nsAppDirectoryServiceDefs.h"
35 #include "nsArrayEnumerator.h"
36 #include "nsCOMArray.h"
37 #include "nsDirectoryServiceUtils.h"
39 #include "nsJSPrincipals.h"
40 #include "nsJSUtils.h"
41 #include "xpcpublic.h"
42 #include "xpcprivate.h"
43 #include "BackstagePass.h"
44 #include "nsIScriptSecurityManager.h"
45 #include "nsIPrincipal.h"
46 #include "nsJSUtils.h"
48 #include "nsIXULRuntime.h"
49 #include "nsIAppStartup.h"
50 #include "Components.h"
51 #include "ProfilerControl.h"
54 # include <android/log.h>
55 # include "XREShellData.h"
59 # include "mozilla/mscom/ProcessRuntime.h"
60 # include "mozilla/ScopeExit.h"
61 # include "mozilla/widget/AudioSession.h"
62 # include "mozilla/WinDllServices.h"
63 # include "mozilla/WindowsBCryptInitialization.h"
65 # if defined(MOZ_SANDBOX)
66 # include "XREShellData.h"
67 # include "sandboxBroker.h"
71 #ifdef MOZ_CODE_COVERAGE
72 # include "mozilla/CodeCoverageHandler.h"
75 // all this crap is needed to do the interactive shell stuff
79 # include <io.h> /* for isatty() */
82 # include <unistd.h> /* for isatty() */
86 # include "xpctest_private.h"
89 // Fuzzing support for XPC runtime fuzzing
90 #ifdef FUZZING_INTERFACES
91 # include "xpcrtfuzzing/xpcrtfuzzing.h"
92 # include "XREShellData.h"
93 static bool fuzzDoDebug
= !!getenv("MOZ_FUZZ_DEBUG");
94 static bool fuzzHaveModule
= !!getenv("FUZZER");
95 #endif // FUZZING_INTERFACES
97 using namespace mozilla
;
99 using mozilla::dom::AutoEntryScript
;
100 using mozilla::dom::AutoJSAPI
;
102 class XPCShellDirProvider
: public nsIDirectoryServiceProvider2
{
104 NS_DECL_ISUPPORTS_INHERITED
105 NS_DECL_NSIDIRECTORYSERVICEPROVIDER
106 NS_DECL_NSIDIRECTORYSERVICEPROVIDER2
108 XPCShellDirProvider() = default;
109 ~XPCShellDirProvider() = default;
111 // The platform resource folder
112 void SetGREDirs(nsIFile
* greDir
);
113 void ClearGREDirs() {
115 mGREBinDir
= nullptr;
117 // The application resource folder
118 void SetAppDir(nsIFile
* appFile
);
119 void ClearAppDir() { mAppDir
= nullptr; }
120 // The app executable
121 void SetAppFile(nsIFile
* appFile
);
122 void ClearAppFile() { mAppFile
= nullptr; }
125 nsCOMPtr
<nsIFile
> mGREDir
;
126 nsCOMPtr
<nsIFile
> mGREBinDir
;
127 nsCOMPtr
<nsIFile
> mAppDir
;
128 nsCOMPtr
<nsIFile
> mAppFile
;
132 class MOZ_STACK_CLASS AutoAudioSession
{
134 AutoAudioSession() { widget::StartAudioSession(); }
136 ~AutoAudioSession() { widget::StopAudioSession(); }
140 #define EXITCODE_RUNTIME_ERROR 3
141 #define EXITCODE_FILE_NOT_FOUND 4
143 static FILE* gOutFile
= nullptr;
144 static FILE* gErrFile
= nullptr;
145 static FILE* gInFile
= nullptr;
147 static int gExitCode
= 0;
148 static bool gQuitting
= false;
149 static bool reportWarnings
= true;
150 static bool compileOnly
= false;
152 static JSPrincipals
* gJSPrincipals
= nullptr;
153 static nsAutoString
* gWorkingDirectory
= nullptr;
155 static bool GetLocationProperty(JSContext
* cx
, unsigned argc
, Value
* vp
) {
156 CallArgs args
= CallArgsFromVp(argc
, vp
);
157 if (!args
.thisv().isObject()) {
158 JS_ReportErrorASCII(cx
, "Unexpected this value for GetLocationProperty");
161 #if !defined(XP_WIN) && !defined(XP_UNIX)
162 // XXX: your platform should really implement this
165 JS::AutoFilename filename
;
166 if (JS::DescribeScriptedCaller(cx
, &filename
) && filename
.get()) {
167 NS_ConvertUTF8toUTF16
filenameString(filename
.get());
170 // replace forward slashes with backslashes,
171 // since nsLocalFileWin chokes on them
172 char16_t
* start
= filenameString
.BeginWriting();
173 char16_t
* end
= filenameString
.EndWriting();
175 while (start
!= end
) {
176 if (*start
== L
'/') {
183 nsCOMPtr
<nsIFile
> location
;
185 NS_NewLocalFile(filenameString
, false, getter_AddRefs(location
));
187 if (!location
&& gWorkingDirectory
) {
188 // could be a relative path, try appending it to the cwd
189 // and then normalize
190 nsAutoString
absolutePath(*gWorkingDirectory
);
191 absolutePath
.Append(filenameString
);
193 rv
= NS_NewLocalFile(absolutePath
, false, getter_AddRefs(location
));
198 // don't normalize symlinks, because that's kind of confusing
199 if (NS_SUCCEEDED(location
->IsSymlink(&symlink
)) && !symlink
)
200 location
->Normalize();
201 RootedObject
locationObj(cx
);
202 RootedObject
scope(cx
, JS::CurrentGlobalOrNull(cx
));
203 rv
= nsXPConnect::XPConnect()->WrapNative(
204 cx
, scope
, location
, NS_GET_IID(nsIFile
), locationObj
.address());
205 if (NS_SUCCEEDED(rv
) && locationObj
) {
206 args
.rval().setObject(*locationObj
);
215 static bool GetLine(JSContext
* cx
, char* bufp
, FILE* file
, const char* prompt
) {
216 fputs(prompt
, gOutFile
);
219 char line
[4096] = {'\0'};
221 if (fgets(line
, sizeof line
, file
)) {
225 if (errno
!= EINTR
) {
231 static bool ReadLine(JSContext
* cx
, unsigned argc
, Value
* vp
) {
232 CallArgs args
= CallArgsFromVp(argc
, vp
);
234 // While 4096 might be quite arbitrary, this is something to be fixed in
235 // bug 105707. It is also the same limit as in ProcessFile.
237 RootedString
str(cx
);
239 /* If a prompt was specified, construct the string */
240 if (args
.length() > 0) {
241 str
= JS::ToString(cx
, args
[0]);
246 str
= JS_GetEmptyString(cx
);
249 /* Get a line from the infile */
250 JS::UniqueChars strBytes
= JS_EncodeStringToLatin1(cx
, str
);
251 if (!strBytes
|| !GetLine(cx
, buf
, gInFile
, strBytes
.get())) {
255 /* Strip newline character added by GetLine() */
256 unsigned int buflen
= strlen(buf
);
259 args
.rval().setNull();
262 } else if (buf
[buflen
- 1] == '\n') {
266 /* Turn buf into a JSString */
267 str
= JS_NewStringCopyN(cx
, buf
, buflen
);
272 args
.rval().setString(str
);
276 static bool Print(JSContext
* cx
, unsigned argc
, Value
* vp
) {
277 CallArgs args
= CallArgsFromVp(argc
, vp
);
278 args
.rval().setUndefined();
280 #ifdef FUZZING_INTERFACES
281 if (fuzzHaveModule
&& !fuzzDoDebug
) {
282 // When fuzzing and not debugging, suppress any print() output,
283 // as it slows down fuzzing and makes libFuzzer's output hard
287 #endif // FUZZING_INTERFACES
289 RootedString
str(cx
);
290 nsAutoCString utf8output
;
292 for (unsigned i
= 0; i
< args
.length(); i
++) {
293 str
= ToString(cx
, args
[i
]);
298 JS::UniqueChars utf8str
= JS_EncodeStringToUTF8(cx
, str
);
304 utf8output
.Append(' ');
306 utf8output
.Append(utf8str
.get(), strlen(utf8str
.get()));
308 utf8output
.Append('\n');
309 fputs(utf8output
.get(), gOutFile
);
314 static bool Dump(JSContext
* cx
, unsigned argc
, Value
* vp
) {
315 CallArgs args
= CallArgsFromVp(argc
, vp
);
316 args
.rval().setUndefined();
318 if (!args
.length()) {
322 RootedString
str(cx
, ToString(cx
, args
[0]));
327 JS::UniqueChars utf8str
= JS_EncodeStringToUTF8(cx
, str
);
333 __android_log_print(ANDROID_LOG_INFO
, "Gecko", "%s", utf8str
.get());
336 if (IsDebuggerPresent()) {
338 if (!wstr
.init(cx
, str
)) {
341 OutputDebugStringW(wstr
.get());
344 fputs(utf8str
.get(), gOutFile
);
349 static bool Load(JSContext
* cx
, unsigned argc
, Value
* vp
) {
350 CallArgs args
= CallArgsFromVp(argc
, vp
);
352 JS::RootedObject
thisObject(cx
);
353 if (!args
.computeThis(cx
, &thisObject
)) {
356 if (!JS_IsGlobalObject(thisObject
)) {
357 JS_ReportErrorASCII(cx
, "Trying to load() into a non-global object");
361 RootedString
str(cx
);
362 for (unsigned i
= 0; i
< args
.length(); i
++) {
363 str
= ToString(cx
, args
[i
]);
367 JS::UniqueChars filename
= JS_EncodeStringToUTF8(cx
, str
);
371 JS::CompileOptions
options(cx
);
372 options
.setIsRunOnce(true).setSkipFilenameValidation(true);
373 JS::Rooted
<JSScript
*> script(
374 cx
, JS::CompileUtf8Path(cx
, options
, filename
.get()));
380 if (!JS_ExecuteScript(cx
, script
)) {
385 args
.rval().setUndefined();
389 static bool Quit(JSContext
* cx
, unsigned argc
, Value
* vp
) {
390 CallArgs args
= CallArgsFromVp(argc
, vp
);
393 if (!ToInt32(cx
, args
.get(0), &gExitCode
)) {
402 static bool DumpXPC(JSContext
* cx
, unsigned argc
, Value
* vp
) {
403 JS::CallArgs args
= CallArgsFromVp(argc
, vp
);
406 if (args
.length() > 0) {
407 if (!JS::ToUint16(cx
, args
[0], &depth
)) {
412 nsXPConnect::XPConnect()->DebugDump(int16_t(depth
));
413 args
.rval().setUndefined();
417 static bool GC(JSContext
* cx
, unsigned argc
, Value
* vp
) {
418 CallArgs args
= CallArgsFromVp(argc
, vp
);
422 args
.rval().setUndefined();
427 static bool GCZeal(JSContext
* cx
, unsigned argc
, Value
* vp
) {
428 CallArgs args
= CallArgsFromVp(argc
, vp
);
430 if (!ToUint32(cx
, args
.get(0), &zeal
)) {
434 JS_SetGCZeal(cx
, uint8_t(zeal
), JS_DEFAULT_ZEAL_FREQ
);
435 args
.rval().setUndefined();
440 static bool SendCommand(JSContext
* cx
, unsigned argc
, Value
* vp
) {
441 CallArgs args
= CallArgsFromVp(argc
, vp
);
443 if (args
.length() == 0) {
444 JS_ReportErrorASCII(cx
, "Function takes at least one argument!");
448 RootedString
str(cx
, ToString(cx
, args
[0]));
450 JS_ReportErrorASCII(cx
, "Could not convert argument 1 to string!");
454 if (args
.get(1).isObject() && !JS_ObjectIsFunction(&args
[1].toObject())) {
455 JS_ReportErrorASCII(cx
, "Could not convert argument 2 to function!");
459 if (!XRE_SendTestShellCommand(
460 cx
, str
, args
.length() > 1 ? args
[1].address() : nullptr)) {
461 JS_ReportErrorASCII(cx
, "Couldn't send command!");
465 args
.rval().setUndefined();
469 static bool Options(JSContext
* cx
, unsigned argc
, Value
* vp
) {
470 JS::CallArgs args
= CallArgsFromVp(argc
, vp
);
472 RootedString
str(cx
);
474 if (args
.length() > 0) {
475 str
= ToString(cx
, args
[0]);
480 opt
= JS_EncodeStringToUTF8(cx
, str
);
485 JS_ReportErrorUTF8(cx
, "unknown option name '%s'.", opt
.get());
489 args
.rval().setString(JS_GetEmptyString(cx
));
493 static PersistentRootedValue
* sScriptedInterruptCallback
= nullptr;
495 static bool XPCShellInterruptCallback(JSContext
* cx
) {
496 MOZ_ASSERT(sScriptedInterruptCallback
->initialized());
497 RootedValue
callback(cx
, *sScriptedInterruptCallback
);
499 // If no interrupt callback was set by script, no-op.
500 if (callback
.isUndefined()) {
504 MOZ_ASSERT(js::IsFunctionObject(&callback
.toObject()));
506 JSAutoRealm
ar(cx
, &callback
.toObject());
508 if (!JS_CallFunctionValue(cx
, nullptr, callback
,
509 JS::HandleValueArray::empty(), &rv
) ||
511 NS_WARNING("Scripted interrupt callback failed! Terminating script.");
512 JS_ClearPendingException(cx
);
516 return rv
.toBoolean();
519 static bool SetInterruptCallback(JSContext
* cx
, unsigned argc
, Value
* vp
) {
520 MOZ_ASSERT(sScriptedInterruptCallback
->initialized());
522 // Sanity-check args.
523 JS::CallArgs args
= JS::CallArgsFromVp(argc
, vp
);
524 if (args
.length() != 1) {
525 JS_ReportErrorASCII(cx
, "Wrong number of arguments");
529 // Allow callers to remove the interrupt callback by passing undefined.
530 if (args
[0].isUndefined()) {
531 *sScriptedInterruptCallback
= UndefinedValue();
535 // Otherwise, we should have a function object.
536 if (!args
[0].isObject() || !js::IsFunctionObject(&args
[0].toObject())) {
537 JS_ReportErrorASCII(cx
, "Argument must be a function");
541 *sScriptedInterruptCallback
= args
[0];
546 static bool SimulateNoScriptActivity(JSContext
* cx
, unsigned argc
, Value
* vp
) {
547 // Sanity-check args.
548 JS::CallArgs args
= JS::CallArgsFromVp(argc
, vp
);
549 if (args
.length() != 1 || !args
[0].isInt32() || args
[0].toInt32() < 0) {
550 JS_ReportErrorASCII(cx
, "Expected a positive integer argument");
554 // This mimics mozilla::SpinEventLoopUntil but instead of spinning the
555 // event loop we sleep, to make sure we don't run script.
556 xpc::AutoScriptActivity
asa(false);
557 PR_Sleep(PR_SecondsToInterval(args
[0].toInt32()));
559 args
.rval().setUndefined();
563 static bool RegisterAppManifest(JSContext
* cx
, unsigned argc
, Value
* vp
) {
564 JS::CallArgs args
= JS::CallArgsFromVp(argc
, vp
);
565 if (args
.length() != 1) {
566 JS_ReportErrorASCII(cx
, "Wrong number of arguments");
569 if (!args
[0].isObject()) {
570 JS_ReportErrorASCII(cx
,
571 "Expected object as argument 1 to registerAppManifest");
575 Rooted
<JSObject
*> arg1(cx
, &args
[0].toObject());
576 nsCOMPtr
<nsIFile
> file
;
577 nsresult rv
= nsXPConnect::XPConnect()->WrapJS(cx
, arg1
, NS_GET_IID(nsIFile
),
578 getter_AddRefs(file
));
580 XPCThrower::Throw(rv
, cx
);
583 rv
= XRE_AddManifestLocation(NS_APP_LOCATION
, file
);
585 XPCThrower::Throw(rv
, cx
);
592 static bool ChangeTestShellDir(JSContext
* cx
, unsigned argc
, Value
* vp
) {
593 // This method should only be used by testing/xpcshell/head.js to change to
594 // the correct directory on Android Remote XPCShell tests.
596 // TODO: Bug 1801725 - Find a more ergonomic way to do this than exposing
597 // identical methods in XPCShellEnvironment and XPCShellImpl to chdir on
598 // android for Remote XPCShell tests on Android.
599 CallArgs args
= CallArgsFromVp(argc
, vp
);
601 if (args
.length() != 1) {
602 JS_ReportErrorASCII(cx
, "changeTestShellDir() takes one argument");
606 nsAutoJSCString path
;
607 if (!path
.init(cx
, args
[0])) {
609 cx
, "changeTestShellDir(): could not convert argument 1 to string");
613 if (chdir(path
.get())) {
614 JS_ReportErrorASCII(cx
, "changeTestShellDir(): could not change directory");
623 static bool RegisterXPCTestComponents(JSContext
* cx
, unsigned argc
, Value
* vp
) {
624 JS::CallArgs args
= JS::CallArgsFromVp(argc
, vp
);
625 if (args
.length() != 0) {
626 JS_ReportErrorASCII(cx
, "Wrong number of arguments");
629 nsresult rv
= xpcTestRegisterComponents();
631 XPCThrower::Throw(rv
, cx
);
638 static const JSFunctionSpec glob_functions
[] = {
640 JS_FN("print", Print
, 0,0),
641 JS_FN("readline", ReadLine
, 1,0),
642 JS_FN("load", Load
, 1,0),
643 JS_FN("quit", Quit
, 0,0),
644 JS_FN("dumpXPC", DumpXPC
, 1,0),
645 JS_FN("dump", Dump
, 1,0),
646 JS_FN("gc", GC
, 0,0),
648 JS_FN("gczeal", GCZeal
, 1,0),
650 JS_FN("options", Options
, 0,0),
651 JS_FN("sendCommand", SendCommand
, 1,0),
652 JS_FN("atob", xpc::Atob
, 1,0),
653 JS_FN("btoa", xpc::Btoa
, 1,0),
654 JS_FN("setInterruptCallback", SetInterruptCallback
, 1,0),
655 JS_FN("simulateNoScriptActivity", SimulateNoScriptActivity
, 1,0),
656 JS_FN("registerAppManifest", RegisterAppManifest
, 1, 0),
658 JS_FN("changeTestShellDir", ChangeTestShellDir
, 1,0),
661 JS_FN("registerXPCTestComponents", RegisterXPCTestComponents
, 0, 0),
667 /***************************************************************************/
669 typedef enum JSShellErrNum
{
670 #define MSG_DEF(name, number, count, exception, format) name = number,
671 #include "jsshell.msg"
676 static const JSErrorFormatString jsShell_ErrorFormatString
[JSShellErr_Limit
] = {
677 #define MSG_DEF(name, number, count, exception, format) {#name, format, count},
678 #include "jsshell.msg"
682 static const JSErrorFormatString
* my_GetErrorMessage(
683 void* userRef
, const unsigned errorNumber
) {
684 if (errorNumber
== 0 || errorNumber
>= JSShellErr_Limit
) {
688 return &jsShell_ErrorFormatString
[errorNumber
];
691 static bool ProcessUtf8Line(AutoJSAPI
& jsapi
, const char* buffer
,
693 JSContext
* cx
= jsapi
.cx();
694 JS::CompileOptions
options(cx
);
695 options
.setFileAndLine("typein", startline
)
697 .setSkipFilenameValidation(true);
699 JS::SourceText
<mozilla::Utf8Unit
> srcBuf
;
700 if (!srcBuf
.init(cx
, buffer
, strlen(buffer
), JS::SourceOwnership::Borrowed
)) {
704 JS::RootedScript
script(cx
, JS::Compile(cx
, options
, srcBuf
));
712 JS::RootedValue
result(cx
);
713 if (!JS_ExecuteScript(cx
, script
, &result
)) {
717 if (result
.isUndefined()) {
721 RootedString
str(cx
, JS::ToString(cx
, result
));
726 JS::UniqueChars bytes
= JS_EncodeStringToLatin1(cx
, str
);
731 fprintf(gOutFile
, "%s\n", bytes
.get());
735 static bool ProcessFile(AutoJSAPI
& jsapi
, const char* filename
, FILE* file
,
737 JSContext
* cx
= jsapi
.cx();
738 JS::Rooted
<JSObject
*> global(cx
, JS::CurrentGlobalOrNull(cx
));
743 } else if (!isatty(fileno(file
))) {
745 * It's not interactive - just execute it.
747 * Support the UNIX #! shell hack; gobble the first line if it starts
750 int ch
= fgetc(file
);
752 while ((ch
= fgetc(file
)) != EOF
) {
753 if (ch
== '\n' || ch
== '\r') {
760 JS::UniqueChars filenameUtf8
= JS::EncodeNarrowToUtf8(jsapi
.cx(), filename
);
765 JS::RootedScript
script(cx
);
766 JS::RootedValue
unused(cx
);
767 JS::CompileOptions
options(cx
);
768 options
.setFileAndLine(filenameUtf8
.get(), 1)
770 .setNoScriptRval(true)
771 .setSkipFilenameValidation(true);
772 script
= JS::CompileUtf8File(cx
, options
, file
);
776 return compileOnly
|| JS_ExecuteScript(cx
, script
, &unused
);
779 /* It's an interactive filehandle; drop into read-eval-print loop. */
788 * Accumulate lines until we get a 'compilable unit' - one that either
789 * generates an error (before running out of source) or that compiles
790 * cleanly. This should be whenever we get a complete statement that
791 * coincides with the end of a line.
793 int startline
= lineno
;
795 if (!GetLine(cx
, bufp
, file
, startline
== lineno
? "js> " : "")) {
799 bufp
+= strlen(bufp
);
802 !JS_Utf8BufferIsCompilableUnit(cx
, global
, buffer
, strlen(buffer
)));
804 if (!ProcessUtf8Line(jsapi
, buffer
, startline
)) {
805 jsapi
.ReportException();
807 } while (!hitEOF
&& !gQuitting
);
809 fprintf(gOutFile
, "\n");
813 static bool Process(AutoJSAPI
& jsapi
, const char* filename
, bool forceTTY
) {
816 if (forceTTY
|| !filename
|| strcmp(filename
, "-") == 0) {
819 file
= fopen(filename
, "r");
822 * Use Latin1 variant here because the encoding of the return value
823 * of strerror function can be non-UTF-8.
825 JS_ReportErrorNumberLatin1(jsapi
.cx(), my_GetErrorMessage
, nullptr,
826 JSSMSG_CANT_OPEN
, filename
, strerror(errno
));
827 gExitCode
= EXITCODE_FILE_NOT_FOUND
;
832 bool ok
= ProcessFile(jsapi
, filename
, file
, forceTTY
);
840 fprintf(gErrFile
, "%s\n", JS_GetImplementationVersion());
843 "usage: xpcshell [-g gredir] [-a appdir] [-r manifest]... [-WwxiCmIp] "
844 "[-f scriptfile] [-e script] [scriptfile] [scriptarg...]\n");
848 static bool printUsageAndSetExitCode() {
853 static bool ProcessArgs(AutoJSAPI
& jsapi
, char** argv
, int argc
,
854 XPCShellDirProvider
* aDirProvider
) {
855 JSContext
* cx
= jsapi
.cx();
856 const char rcfilename
[] = "xpcshell.js";
859 JS::Rooted
<JSObject
*> argsObj(cx
);
860 char* filename
= nullptr;
861 bool isInteractive
= true;
862 bool forceTTY
= false;
864 rcfile
= fopen(rcfilename
, "r");
866 printf("[loading '%s'...]\n", rcfilename
);
867 bool ok
= ProcessFile(jsapi
, rcfilename
, rcfile
, false);
874 JS::Rooted
<JSObject
*> global(cx
, JS::CurrentGlobalOrNull(cx
));
877 * Scan past all optional arguments so we can create the arguments object
878 * before processing any -f options, which must interleave properly with
879 * -v and -w options. This requires two passes, and without getopt, we'll
880 * have to keep the option logic here and in the second for loop in sync.
881 * First of all, find out the first argument position which will be passed
882 * as a script file to be executed.
884 for (rootPosition
= 0; rootPosition
< argc
; rootPosition
++) {
885 if (argv
[rootPosition
][0] != '-' || argv
[rootPosition
][1] == '\0') {
891 argv
[rootPosition
][0] != '\0' &&
892 (argv
[rootPosition
][1] == 'v' || argv
[rootPosition
][1] == 'f' ||
893 argv
[rootPosition
][1] == 'e');
894 if (isPairedFlag
&& rootPosition
< argc
- 1) {
895 ++rootPosition
; // Skip over the 'foo' portion of |-v foo|, |-f foo|, or
901 * Create arguments early and define it to root it, so it's safe from any
902 * GC calls nested below, and so it is available to -f <file> arguments.
904 argsObj
= JS::NewArrayObject(cx
, 0);
908 if (!JS_DefineProperty(cx
, global
, "arguments", argsObj
, 0)) {
912 for (int j
= 0, length
= argc
- rootPosition
; j
< length
; j
++) {
913 RootedString
str(cx
, JS_NewStringCopyZ(cx
, argv
[rootPosition
++]));
914 if (!str
|| !JS_DefineElement(cx
, argsObj
, j
, str
, JSPROP_ENUMERATE
)) {
919 for (int i
= 0; i
< argc
; i
++) {
920 if (argv
[i
][0] != '-' || argv
[i
][1] == '\0') {
921 filename
= argv
[i
++];
922 isInteractive
= false;
925 switch (argv
[i
][1]) {
927 reportWarnings
= false;
930 reportWarnings
= true;
935 /* This used to try to turn on the debugger. */
941 return printUsageAndSetExitCode();
943 if (!Process(jsapi
, argv
[i
], false)) {
947 * XXX: js -f foo.js should interpret foo.js and then
948 * drop into interactive mode, but that breaks test
949 * harness. Just execute foo.js for now.
951 isInteractive
= false;
954 isInteractive
= forceTTY
= true;
957 RootedValue
rval(cx
);
960 return printUsageAndSetExitCode();
963 JS::CompileOptions
opts(cx
);
964 opts
.setSkipFilenameValidation(true);
965 opts
.setFileAndLine("-e", 1);
967 JS::SourceText
<mozilla::Utf8Unit
> srcBuf
;
968 if (srcBuf
.init(cx
, argv
[i
], strlen(argv
[i
]),
969 JS::SourceOwnership::Borrowed
)) {
970 JS::Evaluate(cx
, opts
, srcBuf
, &rval
);
973 isInteractive
= false;
978 isInteractive
= false;
981 return printUsageAndSetExitCode();
985 if (filename
|| isInteractive
) {
986 return Process(jsapi
, filename
, forceTTY
);
991 /***************************************************************************/
993 static bool GetCurrentWorkingDirectory(nsAString
& workingDirectory
) {
994 #if !defined(XP_WIN) && !defined(XP_UNIX)
995 // XXX: your platform should really implement this
998 DWORD requiredLength
= GetCurrentDirectoryW(0, nullptr);
999 workingDirectory
.SetLength(requiredLength
);
1000 GetCurrentDirectoryW(workingDirectory
.Length(),
1001 (LPWSTR
)workingDirectory
.BeginWriting());
1002 // we got a trailing null there
1003 workingDirectory
.SetLength(requiredLength
);
1004 workingDirectory
.Replace(workingDirectory
.Length() - 1, 1, L
'\\');
1005 #elif defined(XP_UNIX)
1007 // 1024 is just a guess at a sane starting value
1008 size_t bufsize
= 1024;
1009 char* result
= nullptr;
1010 while (result
== nullptr) {
1011 cwd
.SetLength(bufsize
);
1012 result
= getcwd(cwd
.BeginWriting(), cwd
.Length());
1014 if (errno
!= ERANGE
) {
1017 // need to make the buffer bigger
1021 // size back down to the actual string length
1022 cwd
.SetLength(strlen(result
) + 1);
1023 cwd
.Replace(cwd
.Length() - 1, 1, '/');
1024 CopyUTF8toUTF16(cwd
, workingDirectory
);
1029 static JSSecurityCallbacks shellSecurityCallbacks
;
1031 int XRE_XPCShellMain(int argc
, char** argv
, char** envp
,
1032 const XREShellData
* aShellData
) {
1033 MOZ_ASSERT(aShellData
);
1040 gOutFile
= aShellData
->outFile
;
1041 gErrFile
= aShellData
->errFile
;
1050 mozilla::LogModule::Init(argc
, argv
);
1052 // This guard ensures that all threads that attempt to register themselves
1053 // with the IOInterposer will be properly tracked.
1054 mozilla::AutoIOInterposer ioInterposerGuard
;
1055 ioInterposerGuard
.Init();
1057 XRE_InitCommandLine(argc
, argv
);
1060 profiler_init(&aLocal
);
1062 #ifdef MOZ_ASAN_REPORTER
1063 PR_SetEnv("MOZ_DISABLE_ASAN_REPORTER=1");
1066 if (PR_GetEnv("MOZ_CHAOSMODE")) {
1067 ChaosFeature feature
= ChaosFeature::Any
;
1068 long featureInt
= strtol(PR_GetEnv("MOZ_CHAOSMODE"), nullptr, 16);
1070 // NOTE: MOZ_CHAOSMODE=0 or a non-hex value maps to Any feature.
1071 feature
= static_cast<ChaosFeature
>(featureInt
);
1073 ChaosMode::SetChaosFeature(feature
);
1076 if (ChaosMode::isActive(ChaosFeature::Any
)) {
1078 "*** You are running in chaos test mode. See ChaosMode.h. ***\n");
1082 // Some COM settings are global to the process and must be set before any non-
1083 // trivial COM is run in the application. Since these settings may affect
1084 // stability, we should instantiate COM ASAP so that we can ensure that these
1085 // global settings are configured before anything can interfere.
1086 mscom::ProcessRuntime mscom
;
1089 nsAutoString binDirPath
;
1093 // The provider needs to outlive the call to shutting down XPCOM.
1094 XPCShellDirProvider dirprovider
;
1096 { // Start scoping nsCOMPtrs
1097 nsCOMPtr
<nsIFile
> appFile
;
1098 rv
= XRE_GetBinaryPath(getter_AddRefs(appFile
));
1099 if (NS_FAILED(rv
)) {
1100 printf("Couldn't find application file.\n");
1103 nsCOMPtr
<nsIFile
> appDir
;
1104 rv
= appFile
->GetParent(getter_AddRefs(appDir
));
1105 if (NS_FAILED(rv
)) {
1106 printf("Couldn't get application directory.\n");
1110 #if defined(XP_WIN) && defined(MOZ_SANDBOX)
1111 // We need the binary directory to initialize the windows sandbox.
1112 MOZ_ALWAYS_SUCCEEDS(appDir
->GetPath(binDirPath
));
1115 dirprovider
.SetAppFile(appFile
);
1117 nsCOMPtr
<nsIFile
> greDir
;
1118 if (argc
> 1 && !strcmp(argv
[1], "-g")) {
1123 rv
= XRE_GetFileFromPath(argv
[2], getter_AddRefs(greDir
));
1124 if (NS_FAILED(rv
)) {
1125 printf("Couldn't use given GRE dir.\n");
1129 dirprovider
.SetGREDirs(greDir
);
1135 // On OSX, the GreD needs to point to Contents/Resources in the .app
1136 // bundle. Libraries will be loaded at a relative path to GreD, i.e.
1138 nsCOMPtr
<nsIFile
> tmpDir
;
1139 XRE_GetFileFromPath(argv
[0], getter_AddRefs(greDir
));
1140 greDir
->GetParent(getter_AddRefs(tmpDir
));
1141 tmpDir
->Clone(getter_AddRefs(greDir
));
1142 tmpDir
->SetNativeLeafName("Resources"_ns
);
1143 bool dirExists
= false;
1144 tmpDir
->Exists(&dirExists
);
1146 greDir
= tmpDir
.forget();
1148 dirprovider
.SetGREDirs(greDir
);
1150 nsAutoString workingDir
;
1151 if (!GetCurrentWorkingDirectory(workingDir
)) {
1152 printf("GetCurrentWorkingDirectory failed.\n");
1155 rv
= NS_NewLocalFile(workingDir
, true, getter_AddRefs(greDir
));
1156 if (NS_FAILED(rv
)) {
1157 printf("NS_NewLocalFile failed.\n");
1163 if (argc
> 1 && !strcmp(argv
[1], "-a")) {
1168 nsCOMPtr
<nsIFile
> dir
;
1169 rv
= XRE_GetFileFromPath(argv
[2], getter_AddRefs(dir
));
1170 if (NS_SUCCEEDED(rv
)) {
1172 dirprovider
.SetAppDir(appDir
);
1174 if (NS_FAILED(rv
)) {
1175 printf("Couldn't use given appdir.\n");
1182 while (argc
> 1 && !strcmp(argv
[1], "-r")) {
1187 nsCOMPtr
<nsIFile
> lf
;
1188 rv
= XRE_GetFileFromPath(argv
[2], getter_AddRefs(lf
));
1189 if (NS_FAILED(rv
)) {
1190 printf("Couldn't get manifest file.\n");
1193 XRE_AddManifestLocation(NS_APP_LOCATION
, lf
);
1199 const char* val
= getenv("MOZ_CRASHREPORTER");
1200 if (val
&& *val
&& !CrashReporter::IsDummy()) {
1201 rv
= CrashReporter::SetExceptionHandler(greDir
, true);
1202 if (NS_FAILED(rv
)) {
1203 printf("CrashReporter::SetExceptionHandler failed!\n");
1206 MOZ_ASSERT(CrashReporter::GetEnabled());
1209 if (argc
> 1 && !strcmp(argv
[1], "--greomni")) {
1210 nsCOMPtr
<nsIFile
> greOmni
;
1211 XRE_GetFileFromPath(argv
[2], getter_AddRefs(greOmni
));
1212 XRE_InitOmnijar(greOmni
, greOmni
);
1217 rv
= NS_InitXPCOM(nullptr, appDir
, &dirprovider
);
1218 if (NS_FAILED(rv
)) {
1219 printf("NS_InitXPCOM failed!\n");
1223 // xpc::ErrorReport::LogToConsoleWithStack needs this to print errors
1225 Preferences::SetBool("browser.dom.window.dump.enabled", true);
1226 Preferences::SetBool("devtools.console.stdout.chrome", true);
1232 // Override the default XPConnect interrupt callback. We could store the
1233 // old one and restore it before shutting down, but there's not really a
1234 // reason to bother.
1235 sScriptedInterruptCallback
= new PersistentRootedValue
;
1236 sScriptedInterruptCallback
->init(cx
, UndefinedValue());
1238 JS_AddInterruptCallback(cx
, XPCShellInterruptCallback
);
1243 nsCOMPtr
<nsIPrincipal
> systemprincipal
;
1244 // Fetch the system principal and store it away in a global, to use for
1245 // script compilation in Load() and ProcessFile() (including interactive
1248 nsCOMPtr
<nsIScriptSecurityManager
> securityManager
=
1249 do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID
, &rv
);
1250 if (NS_SUCCEEDED(rv
) && securityManager
) {
1251 rv
= securityManager
->GetSystemPrincipal(
1252 getter_AddRefs(systemprincipal
));
1253 if (NS_FAILED(rv
)) {
1255 "+++ Failed to obtain SystemPrincipal from "
1256 "ScriptSecurityManager service.\n");
1258 // fetch the JS principals and stick in a global
1259 gJSPrincipals
= nsJSPrincipals::get(systemprincipal
);
1260 JS_HoldPrincipals(gJSPrincipals
);
1264 "+++ Failed to get ScriptSecurityManager service, running "
1265 "without principals");
1269 const JSSecurityCallbacks
* scb
= JS_GetSecurityCallbacks(cx
);
1272 "We are assuming that nsScriptSecurityManager::Init() has been run");
1273 shellSecurityCallbacks
= *scb
;
1274 JS_SetSecurityCallbacks(cx
, &shellSecurityCallbacks
);
1276 auto backstagePass
= MakeRefPtr
<BackstagePass
>();
1278 // Make the default XPCShell global use a fresh zone (rather than the
1279 // System Zone) to improve cross-zone test coverage.
1280 JS::RealmOptions options
;
1281 options
.creationOptions().setNewCompartmentAndZone();
1282 xpc::SetPrefableRealmOptions(options
);
1284 // Even if we're building in a configuration where source is
1285 // discarded, there's no reason to do that on XPCShell, and doing so
1286 // might break various automation scripts.
1287 options
.behaviors().setDiscardSource(false);
1289 JS::Rooted
<JSObject
*> glob(cx
);
1290 rv
= xpc::InitClassesWithNewWrappedGlobal(
1291 cx
, static_cast<nsIGlobalObject
*>(backstagePass
), systemprincipal
, 0,
1293 if (NS_FAILED(rv
)) {
1297 // Initialize e10s check on the main thread, if not already done
1298 BrowserTabsRemoteAutostart();
1300 // Plugin may require audio session if installed plugin can initialize
1302 AutoAudioSession audioSession
;
1304 // Ensure that DLL Services are running
1305 RefPtr
<DllServices
> dllSvc(DllServices::Get());
1306 dllSvc
->StartUntrustedModulesProcessor(true);
1307 auto dllServicesDisable
=
1308 MakeScopeExit([&dllSvc
]() { dllSvc
->DisableFull(); });
1310 # if defined(MOZ_SANDBOX)
1311 // Required for sandboxed child processes.
1312 if (aShellData
->sandboxBrokerServices
) {
1313 SandboxBroker::Initialize(aShellData
->sandboxBrokerServices
, binDirPath
);
1314 SandboxBroker::GeckoDependentInitialize();
1317 "Failed to initialize broker services, sandboxed "
1318 "processes will fail to start.");
1320 # endif // defined(MOZ_SANDBOX)
1323 DebugOnly
<bool> result
= WindowsBCryptInitialization();
1326 #endif // defined(XP_WIN)
1328 #ifdef MOZ_CODE_COVERAGE
1329 CodeCoverageHandler::Init();
1337 nsCOMPtr
<nsIAppStartup
> appStartup(components::AppStartup::Service());
1341 appStartup
->DoneStartingUp();
1343 backstagePass
->SetGlobalObject(glob
);
1345 JSAutoRealm
ar(cx
, glob
);
1347 if (!JS_InitReflectParse(cx
, glob
)) {
1351 if (!JS_DefineFunctions(cx
, glob
, glob_functions
)) {
1355 nsAutoString workingDirectory
;
1356 if (GetCurrentWorkingDirectory(workingDirectory
)) {
1357 gWorkingDirectory
= &workingDirectory
;
1360 JS_DefineProperty(cx
, glob
, "__LOCATION__", GetLocationProperty
, nullptr,
1364 #ifdef FUZZING_INTERFACES
1365 if (fuzzHaveModule
) {
1367 // argv[0] was removed previously, but libFuzzer expects it
1371 result
= FuzzXPCRuntimeStart(&jsapi
, &argc
, &argv
,
1372 aShellData
->fuzzerDriver
);
1374 MOZ_CRASH("AFL is unsupported for XPC runtime fuzzing integration");
1378 // We are almost certainly going to run script here, so we need an
1379 // AutoEntryScript. This is Gecko-specific and not in any spec.
1380 AutoEntryScript
aes(backstagePass
, "xpcshell argument processing");
1382 // If an exception is thrown, we'll set our return code
1383 // appropriately, and then let the AutoEntryScript destructor report
1384 // the error to the console.
1385 if (!ProcessArgs(aes
, argv
, argc
, &dirprovider
)) {
1388 } else if (gQuitting
) {
1391 result
= EXITCODE_RUNTIME_ERROR
;
1394 #ifdef FUZZING_INTERFACES
1399 // Signal that we're now shutting down.
1400 nsCOMPtr
<nsIObserver
> obs
= do_QueryInterface(appStartup
);
1402 obs
->Observe(nullptr, "quit-application-forced", nullptr);
1405 JS_DropPrincipals(cx
, gJSPrincipals
);
1406 JS_SetAllNonReservedSlotsToUndefined(glob
);
1407 JS::RootedObject
lexicalEnv(cx
, JS_GlobalLexicalEnvironment(glob
));
1408 JS_SetAllNonReservedSlotsToUndefined(lexicalEnv
);
1413 dirprovider
.ClearGREDirs();
1414 dirprovider
.ClearAppDir();
1415 dirprovider
.ClearAppFile();
1416 } // this scopes the nsCOMPtrs
1418 if (!XRE_ShutdownTestShell()) {
1419 NS_ERROR("problem shutting down testshell");
1422 // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM
1423 rv
= NS_ShutdownXPCOM(nullptr);
1424 MOZ_ASSERT(NS_SUCCEEDED(rv
), "NS_ShutdownXPCOM failed");
1426 // Shut down the crashreporter service to prevent leaking some strings it
1428 if (CrashReporter::GetEnabled()) {
1429 CrashReporter::UnsetExceptionHandler();
1432 // This must precede NS_LogTerm(), otherwise xpcshell return non-zero
1433 // during some tests, which causes failures.
1434 profiler_shutdown();
1438 XRE_DeinitCommandLine();
1443 void XPCShellDirProvider::SetGREDirs(nsIFile
* greDir
) {
1445 mGREDir
->Clone(getter_AddRefs(mGREBinDir
));
1448 nsAutoCString leafName
;
1449 mGREDir
->GetNativeLeafName(leafName
);
1450 if (leafName
.EqualsLiteral("Resources")) {
1451 mGREBinDir
->SetNativeLeafName("MacOS"_ns
);
1456 void XPCShellDirProvider::SetAppFile(nsIFile
* appFile
) { mAppFile
= appFile
; }
1458 void XPCShellDirProvider::SetAppDir(nsIFile
* appDir
) { mAppDir
= appDir
; }
1460 NS_IMETHODIMP_(MozExternalRefCountType
)
1461 XPCShellDirProvider::AddRef() { return 2; }
1463 NS_IMETHODIMP_(MozExternalRefCountType
)
1464 XPCShellDirProvider::Release() { return 1; }
1466 NS_IMPL_QUERY_INTERFACE(XPCShellDirProvider
, nsIDirectoryServiceProvider
,
1467 nsIDirectoryServiceProvider2
)
1470 XPCShellDirProvider::GetFile(const char* prop
, bool* persistent
,
1472 if (mGREDir
&& !strcmp(prop
, NS_GRE_DIR
)) {
1474 return mGREDir
->Clone(result
);
1475 } else if (mGREBinDir
&& !strcmp(prop
, NS_GRE_BIN_DIR
)) {
1477 return mGREBinDir
->Clone(result
);
1478 } else if (mAppFile
&& !strcmp(prop
, XRE_EXECUTABLE_FILE
)) {
1480 return mAppFile
->Clone(result
);
1481 } else if (mGREDir
&& !strcmp(prop
, NS_APP_PREF_DEFAULTS_50_DIR
)) {
1482 nsCOMPtr
<nsIFile
> file
;
1484 if (NS_FAILED(mGREDir
->Clone(getter_AddRefs(file
))) ||
1485 NS_FAILED(file
->AppendNative("defaults"_ns
)) ||
1486 NS_FAILED(file
->AppendNative("pref"_ns
)))
1487 return NS_ERROR_FAILURE
;
1488 file
.forget(result
);
1492 return NS_ERROR_FAILURE
;
1496 XPCShellDirProvider::GetFiles(const char* prop
, nsISimpleEnumerator
** result
) {
1497 if (mGREDir
&& !strcmp(prop
, "ChromeML")) {
1498 nsCOMArray
<nsIFile
> dirs
;
1500 nsCOMPtr
<nsIFile
> file
;
1501 mGREDir
->Clone(getter_AddRefs(file
));
1502 file
->AppendNative("chrome"_ns
);
1503 dirs
.AppendObject(file
);
1506 NS_GetSpecialDirectory(NS_APP_CHROME_DIR
, getter_AddRefs(file
));
1507 if (NS_SUCCEEDED(rv
)) {
1508 dirs
.AppendObject(file
);
1511 return NS_NewArrayEnumerator(result
, dirs
, NS_GET_IID(nsIFile
));
1512 } else if (!strcmp(prop
, NS_APP_PREFS_DEFAULTS_DIR_LIST
)) {
1513 nsCOMArray
<nsIFile
> dirs
;
1514 nsCOMPtr
<nsIFile
> appDir
;
1516 if (mAppDir
&& NS_SUCCEEDED(mAppDir
->Clone(getter_AddRefs(appDir
))) &&
1517 NS_SUCCEEDED(appDir
->AppendNative("defaults"_ns
)) &&
1518 NS_SUCCEEDED(appDir
->AppendNative("preferences"_ns
)) &&
1519 NS_SUCCEEDED(appDir
->Exists(&exists
)) && exists
) {
1520 dirs
.AppendObject(appDir
);
1521 return NS_NewArrayEnumerator(result
, dirs
, NS_GET_IID(nsIFile
));
1523 return NS_ERROR_FAILURE
;
1525 return NS_ERROR_FAILURE
;