no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / js / xpconnect / src / XPCShellImpl.cpp
blobb36ba56aed1ddb85d16c72b28395d27a581b0f8a
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"
8 #include "jsapi.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"
29 #include "nsIFile.h"
30 #include "nsString.h"
31 #include "nsIDirectoryService.h"
32 #include "nsDirectoryServiceDefs.h"
33 #include "nsAppDirectoryServiceDefs.h"
34 #include "nscore.h"
35 #include "nsArrayEnumerator.h"
36 #include "nsCOMArray.h"
37 #include "nsDirectoryServiceUtils.h"
38 #include "nsCOMPtr.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"
53 #ifdef ANDROID
54 # include <android/log.h>
55 # include "XREShellData.h"
56 #endif
58 #ifdef XP_WIN
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"
64 # include <windows.h>
65 # if defined(MOZ_SANDBOX)
66 # include "XREShellData.h"
67 # include "sandboxBroker.h"
68 # endif
69 #endif
71 #ifdef MOZ_CODE_COVERAGE
72 # include "mozilla/CodeCoverageHandler.h"
73 #endif
75 // all this crap is needed to do the interactive shell stuff
76 #include <stdlib.h>
77 #include <errno.h>
78 #ifdef HAVE_IO_H
79 # include <io.h> /* for isatty() */
80 #endif
81 #ifdef HAVE_UNISTD_H
82 # include <unistd.h> /* for isatty() */
83 #endif
85 #ifdef ENABLE_TESTS
86 # include "xpctest_private.h"
87 #endif
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;
98 using namespace JS;
99 using mozilla::dom::AutoEntryScript;
100 using mozilla::dom::AutoJSAPI;
102 class XPCShellDirProvider : public nsIDirectoryServiceProvider2 {
103 public:
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() {
114 mGREDir = nullptr;
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; }
124 private:
125 nsCOMPtr<nsIFile> mGREDir;
126 nsCOMPtr<nsIFile> mGREBinDir;
127 nsCOMPtr<nsIFile> mAppDir;
128 nsCOMPtr<nsIFile> mAppFile;
131 #ifdef XP_WIN
132 class MOZ_STACK_CLASS AutoAudioSession {
133 public:
134 AutoAudioSession() { widget::StartAudioSession(); }
136 ~AutoAudioSession() { widget::StopAudioSession(); }
138 #endif
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");
159 return false;
161 #if !defined(XP_WIN) && !defined(XP_UNIX)
162 // XXX: your platform should really implement this
163 return false;
164 #else
165 JS::AutoFilename filename;
166 if (JS::DescribeScriptedCaller(cx, &filename) && filename.get()) {
167 NS_ConvertUTF8toUTF16 filenameString(filename.get());
169 # if defined(XP_WIN)
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'/') {
177 *start = L'\\';
179 start++;
181 # endif
183 nsCOMPtr<nsIFile> location;
184 nsresult rv =
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));
196 if (location) {
197 bool symlink;
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);
211 return true;
212 #endif
215 static bool GetLine(JSContext* cx, char* bufp, FILE* file, const char* prompt) {
216 fputs(prompt, gOutFile);
217 fflush(gOutFile);
219 char line[4096] = {'\0'};
220 while (true) {
221 if (fgets(line, sizeof line, file)) {
222 strcpy(bufp, line);
223 return true;
225 if (errno != EINTR) {
226 return false;
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.
236 char buf[4096];
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]);
242 if (!str) {
243 return false;
245 } else {
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())) {
252 return false;
255 /* Strip newline character added by GetLine() */
256 unsigned int buflen = strlen(buf);
257 if (buflen == 0) {
258 if (feof(gInFile)) {
259 args.rval().setNull();
260 return true;
262 } else if (buf[buflen - 1] == '\n') {
263 --buflen;
266 /* Turn buf into a JSString */
267 str = JS_NewStringCopyN(cx, buf, buflen);
268 if (!str) {
269 return false;
272 args.rval().setString(str);
273 return true;
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
284 // to read.
285 return true;
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]);
294 if (!str) {
295 return false;
298 JS::UniqueChars utf8str = JS_EncodeStringToUTF8(cx, str);
299 if (!utf8str) {
300 return false;
303 if (i) {
304 utf8output.Append(' ');
306 utf8output.Append(utf8str.get(), strlen(utf8str.get()));
308 utf8output.Append('\n');
309 fputs(utf8output.get(), gOutFile);
310 fflush(gOutFile);
311 return true;
314 static bool Dump(JSContext* cx, unsigned argc, Value* vp) {
315 CallArgs args = CallArgsFromVp(argc, vp);
316 args.rval().setUndefined();
318 if (!args.length()) {
319 return true;
322 RootedString str(cx, ToString(cx, args[0]));
323 if (!str) {
324 return false;
327 JS::UniqueChars utf8str = JS_EncodeStringToUTF8(cx, str);
328 if (!utf8str) {
329 return false;
332 #ifdef ANDROID
333 __android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", utf8str.get());
334 #endif
335 #ifdef XP_WIN
336 if (IsDebuggerPresent()) {
337 nsAutoJSString wstr;
338 if (!wstr.init(cx, str)) {
339 return false;
341 OutputDebugStringW(wstr.get());
343 #endif
344 fputs(utf8str.get(), gOutFile);
345 fflush(gOutFile);
346 return true;
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)) {
354 return false;
356 if (!JS_IsGlobalObject(thisObject)) {
357 JS_ReportErrorASCII(cx, "Trying to load() into a non-global object");
358 return false;
361 RootedString str(cx);
362 for (unsigned i = 0; i < args.length(); i++) {
363 str = ToString(cx, args[i]);
364 if (!str) {
365 return false;
367 JS::UniqueChars filename = JS_EncodeStringToUTF8(cx, str);
368 if (!filename) {
369 return false;
371 JS::CompileOptions options(cx);
372 options.setIsRunOnce(true).setSkipFilenameValidation(true);
373 JS::Rooted<JSScript*> script(
374 cx, JS::CompileUtf8Path(cx, options, filename.get()));
375 if (!script) {
376 return false;
379 if (!compileOnly) {
380 if (!JS_ExecuteScript(cx, script)) {
381 return false;
385 args.rval().setUndefined();
386 return true;
389 static bool Quit(JSContext* cx, unsigned argc, Value* vp) {
390 CallArgs args = CallArgsFromVp(argc, vp);
392 gExitCode = 0;
393 if (!ToInt32(cx, args.get(0), &gExitCode)) {
394 return false;
397 gQuitting = true;
398 // exit(0);
399 return false;
402 static bool DumpXPC(JSContext* cx, unsigned argc, Value* vp) {
403 JS::CallArgs args = CallArgsFromVp(argc, vp);
405 uint16_t depth = 2;
406 if (args.length() > 0) {
407 if (!JS::ToUint16(cx, args[0], &depth)) {
408 return false;
412 nsXPConnect::XPConnect()->DebugDump(int16_t(depth));
413 args.rval().setUndefined();
414 return true;
417 static bool GC(JSContext* cx, unsigned argc, Value* vp) {
418 CallArgs args = CallArgsFromVp(argc, vp);
420 JS_GC(cx);
422 args.rval().setUndefined();
423 return true;
426 #ifdef JS_GC_ZEAL
427 static bool GCZeal(JSContext* cx, unsigned argc, Value* vp) {
428 CallArgs args = CallArgsFromVp(argc, vp);
429 uint32_t zeal;
430 if (!ToUint32(cx, args.get(0), &zeal)) {
431 return false;
434 JS_SetGCZeal(cx, uint8_t(zeal), JS_DEFAULT_ZEAL_FREQ);
435 args.rval().setUndefined();
436 return true;
438 #endif
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!");
445 return false;
448 RootedString str(cx, ToString(cx, args[0]));
449 if (!str) {
450 JS_ReportErrorASCII(cx, "Could not convert argument 1 to string!");
451 return false;
454 if (args.get(1).isObject() && !JS_ObjectIsFunction(&args[1].toObject())) {
455 JS_ReportErrorASCII(cx, "Could not convert argument 2 to function!");
456 return false;
459 if (!XRE_SendTestShellCommand(
460 cx, str, args.length() > 1 ? args[1].address() : nullptr)) {
461 JS_ReportErrorASCII(cx, "Couldn't send command!");
462 return false;
465 args.rval().setUndefined();
466 return true;
469 static bool Options(JSContext* cx, unsigned argc, Value* vp) {
470 JS::CallArgs args = CallArgsFromVp(argc, vp);
472 RootedString str(cx);
473 JS::UniqueChars opt;
474 if (args.length() > 0) {
475 str = ToString(cx, args[0]);
476 if (!str) {
477 return false;
480 opt = JS_EncodeStringToUTF8(cx, str);
481 if (!opt) {
482 return false;
485 JS_ReportErrorUTF8(cx, "unknown option name '%s'.", opt.get());
486 return false;
489 args.rval().setString(JS_GetEmptyString(cx));
490 return true;
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()) {
501 return true;
504 MOZ_ASSERT(js::IsFunctionObject(&callback.toObject()));
506 JSAutoRealm ar(cx, &callback.toObject());
507 RootedValue rv(cx);
508 if (!JS_CallFunctionValue(cx, nullptr, callback,
509 JS::HandleValueArray::empty(), &rv) ||
510 !rv.isBoolean()) {
511 NS_WARNING("Scripted interrupt callback failed! Terminating script.");
512 JS_ClearPendingException(cx);
513 return false;
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");
526 return false;
529 // Allow callers to remove the interrupt callback by passing undefined.
530 if (args[0].isUndefined()) {
531 *sScriptedInterruptCallback = UndefinedValue();
532 return true;
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");
538 return false;
541 *sScriptedInterruptCallback = args[0];
543 return true;
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");
551 return false;
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();
560 return true;
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");
567 return false;
569 if (!args[0].isObject()) {
570 JS_ReportErrorASCII(cx,
571 "Expected object as argument 1 to registerAppManifest");
572 return false;
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));
579 if (NS_FAILED(rv)) {
580 XPCThrower::Throw(rv, cx);
581 return false;
583 rv = XRE_AddManifestLocation(NS_APP_LOCATION, file);
584 if (NS_FAILED(rv)) {
585 XPCThrower::Throw(rv, cx);
586 return false;
588 return true;
591 #ifdef ANDROID
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");
603 return false;
606 nsAutoJSCString path;
607 if (!path.init(cx, args[0])) {
608 JS_ReportErrorASCII(
609 cx, "changeTestShellDir(): could not convert argument 1 to string");
610 return false;
613 if (chdir(path.get())) {
614 JS_ReportErrorASCII(cx, "changeTestShellDir(): could not change directory");
615 return false;
618 return true;
620 #endif
622 #ifdef ENABLE_TESTS
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");
627 return false;
629 nsresult rv = xpcTestRegisterComponents();
630 if (NS_FAILED(rv)) {
631 XPCThrower::Throw(rv, cx);
632 return false;
634 return true;
636 #endif
638 static const JSFunctionSpec glob_functions[] = {
639 // clang-format off
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),
647 #ifdef JS_GC_ZEAL
648 JS_FN("gczeal", GCZeal, 1,0),
649 #endif
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),
657 #ifdef ANDROID
658 JS_FN("changeTestShellDir", ChangeTestShellDir, 1,0),
659 #endif
660 #ifdef ENABLE_TESTS
661 JS_FN("registerXPCTestComponents", RegisterXPCTestComponents, 0, 0),
662 #endif
663 JS_FS_END
664 // clang-format on
667 /***************************************************************************/
669 typedef enum JSShellErrNum {
670 #define MSG_DEF(name, number, count, exception, format) name = number,
671 #include "jsshell.msg"
672 #undef MSG_DEF
673 JSShellErr_Limit
674 } JSShellErrNum;
676 static const JSErrorFormatString jsShell_ErrorFormatString[JSShellErr_Limit] = {
677 #define MSG_DEF(name, number, count, exception, format) {#name, format, count},
678 #include "jsshell.msg"
679 #undef MSG_DEF
682 static const JSErrorFormatString* my_GetErrorMessage(
683 void* userRef, const unsigned errorNumber) {
684 if (errorNumber == 0 || errorNumber >= JSShellErr_Limit) {
685 return nullptr;
688 return &jsShell_ErrorFormatString[errorNumber];
691 static bool ProcessUtf8Line(AutoJSAPI& jsapi, const char* buffer,
692 int startline) {
693 JSContext* cx = jsapi.cx();
694 JS::CompileOptions options(cx);
695 options.setFileAndLine("typein", startline)
696 .setIsRunOnce(true)
697 .setSkipFilenameValidation(true);
699 JS::SourceText<mozilla::Utf8Unit> srcBuf;
700 if (!srcBuf.init(cx, buffer, strlen(buffer), JS::SourceOwnership::Borrowed)) {
701 return false;
704 JS::RootedScript script(cx, JS::Compile(cx, options, srcBuf));
705 if (!script) {
706 return false;
708 if (compileOnly) {
709 return true;
712 JS::RootedValue result(cx);
713 if (!JS_ExecuteScript(cx, script, &result)) {
714 return false;
717 if (result.isUndefined()) {
718 return true;
721 RootedString str(cx, JS::ToString(cx, result));
722 if (!str) {
723 return false;
726 JS::UniqueChars bytes = JS_EncodeStringToLatin1(cx, str);
727 if (!bytes) {
728 return false;
731 fprintf(gOutFile, "%s\n", bytes.get());
732 return true;
735 static bool ProcessFile(AutoJSAPI& jsapi, const char* filename, FILE* file,
736 bool forceTTY) {
737 JSContext* cx = jsapi.cx();
738 JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
739 MOZ_ASSERT(global);
741 if (forceTTY) {
742 file = stdin;
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
748 * with '#'.
750 int ch = fgetc(file);
751 if (ch == '#') {
752 while ((ch = fgetc(file)) != EOF) {
753 if (ch == '\n' || ch == '\r') {
754 break;
758 ungetc(ch, file);
760 JS::UniqueChars filenameUtf8 = JS::EncodeNarrowToUtf8(jsapi.cx(), filename);
761 if (!filenameUtf8) {
762 return false;
765 JS::RootedScript script(cx);
766 JS::RootedValue unused(cx);
767 JS::CompileOptions options(cx);
768 options.setFileAndLine(filenameUtf8.get(), 1)
769 .setIsRunOnce(true)
770 .setNoScriptRval(true)
771 .setSkipFilenameValidation(true);
772 script = JS::CompileUtf8File(cx, options, file);
773 if (!script) {
774 return false;
776 return compileOnly || JS_ExecuteScript(cx, script, &unused);
779 /* It's an interactive filehandle; drop into read-eval-print loop. */
780 int lineno = 1;
781 bool hitEOF = false;
782 do {
783 char buffer[4096];
784 char* bufp = buffer;
785 *bufp = '\0';
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;
794 do {
795 if (!GetLine(cx, bufp, file, startline == lineno ? "js> " : "")) {
796 hitEOF = true;
797 break;
799 bufp += strlen(bufp);
800 lineno++;
801 } while (
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");
810 return true;
813 static bool Process(AutoJSAPI& jsapi, const char* filename, bool forceTTY) {
814 FILE* file;
816 if (forceTTY || !filename || strcmp(filename, "-") == 0) {
817 file = stdin;
818 } else {
819 file = fopen(filename, "r");
820 if (!file) {
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;
828 return false;
832 bool ok = ProcessFile(jsapi, filename, file, forceTTY);
833 if (file != stdin) {
834 fclose(file);
836 return ok;
839 static int usage() {
840 fprintf(gErrFile, "%s\n", JS_GetImplementationVersion());
841 fprintf(
842 gErrFile,
843 "usage: xpcshell [-g gredir] [-a appdir] [-r manifest]... [-WwxiCmIp] "
844 "[-f scriptfile] [-e script] [scriptfile] [scriptarg...]\n");
845 return 2;
848 static bool printUsageAndSetExitCode() {
849 gExitCode = usage();
850 return false;
853 static bool ProcessArgs(AutoJSAPI& jsapi, char** argv, int argc,
854 XPCShellDirProvider* aDirProvider) {
855 JSContext* cx = jsapi.cx();
856 const char rcfilename[] = "xpcshell.js";
857 FILE* rcfile;
858 int rootPosition;
859 JS::Rooted<JSObject*> argsObj(cx);
860 char* filename = nullptr;
861 bool isInteractive = true;
862 bool forceTTY = false;
864 rcfile = fopen(rcfilename, "r");
865 if (rcfile) {
866 printf("[loading '%s'...]\n", rcfilename);
867 bool ok = ProcessFile(jsapi, rcfilename, rcfile, false);
868 fclose(rcfile);
869 if (!ok) {
870 return 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') {
886 ++rootPosition;
887 break;
890 bool isPairedFlag =
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
896 // |-e foo|.
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);
905 if (!argsObj) {
906 return 1;
908 if (!JS_DefineProperty(cx, global, "arguments", argsObj, 0)) {
909 return 1;
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)) {
915 return 1;
919 for (int i = 0; i < argc; i++) {
920 if (argv[i][0] != '-' || argv[i][1] == '\0') {
921 filename = argv[i++];
922 isInteractive = false;
923 break;
925 switch (argv[i][1]) {
926 case 'W':
927 reportWarnings = false;
928 break;
929 case 'w':
930 reportWarnings = true;
931 break;
932 case 'x':
933 break;
934 case 'd':
935 /* This used to try to turn on the debugger. */
936 break;
937 case 'm':
938 break;
939 case 'f':
940 if (++i == argc) {
941 return printUsageAndSetExitCode();
943 if (!Process(jsapi, argv[i], false)) {
944 return 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;
952 break;
953 case 'i':
954 isInteractive = forceTTY = true;
955 break;
956 case 'e': {
957 RootedValue rval(cx);
959 if (++i == argc) {
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;
974 break;
976 case 'C':
977 compileOnly = true;
978 isInteractive = false;
979 break;
980 default:
981 return printUsageAndSetExitCode();
985 if (filename || isInteractive) {
986 return Process(jsapi, filename, forceTTY);
988 return true;
991 /***************************************************************************/
993 static bool GetCurrentWorkingDirectory(nsAString& workingDirectory) {
994 #if !defined(XP_WIN) && !defined(XP_UNIX)
995 // XXX: your platform should really implement this
996 return false;
997 #elif XP_WIN
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)
1006 nsAutoCString cwd;
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());
1013 if (!result) {
1014 if (errno != ERANGE) {
1015 return false;
1017 // need to make the buffer bigger
1018 bufsize *= 2;
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);
1025 #endif
1026 return true;
1029 static JSSecurityCallbacks shellSecurityCallbacks;
1031 int XRE_XPCShellMain(int argc, char** argv, char** envp,
1032 const XREShellData* aShellData) {
1033 MOZ_ASSERT(aShellData);
1035 JSContext* cx;
1036 int result = 0;
1037 nsresult rv;
1039 #ifdef ANDROID
1040 gOutFile = aShellData->outFile;
1041 gErrFile = aShellData->errFile;
1042 #else
1043 gOutFile = stdout;
1044 gErrFile = stderr;
1045 #endif
1046 gInFile = stdin;
1048 NS_LogInit();
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);
1059 char aLocal;
1060 profiler_init(&aLocal);
1062 #ifdef MOZ_ASAN_REPORTER
1063 PR_SetEnv("MOZ_DISABLE_ASAN_REPORTER=1");
1064 #endif
1066 if (PR_GetEnv("MOZ_CHAOSMODE")) {
1067 ChaosFeature feature = ChaosFeature::Any;
1068 long featureInt = strtol(PR_GetEnv("MOZ_CHAOSMODE"), nullptr, 16);
1069 if (featureInt) {
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)) {
1077 printf_stderr(
1078 "*** You are running in chaos test mode. See ChaosMode.h. ***\n");
1081 #ifdef XP_WIN
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;
1088 # ifdef MOZ_SANDBOX
1089 nsAutoString binDirPath;
1090 # endif
1091 #endif
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");
1101 return 1;
1103 nsCOMPtr<nsIFile> appDir;
1104 rv = appFile->GetParent(getter_AddRefs(appDir));
1105 if (NS_FAILED(rv)) {
1106 printf("Couldn't get application directory.\n");
1107 return 1;
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));
1113 #endif
1115 dirprovider.SetAppFile(appFile);
1117 nsCOMPtr<nsIFile> greDir;
1118 if (argc > 1 && !strcmp(argv[1], "-g")) {
1119 if (argc < 3) {
1120 return usage();
1123 rv = XRE_GetFileFromPath(argv[2], getter_AddRefs(greDir));
1124 if (NS_FAILED(rv)) {
1125 printf("Couldn't use given GRE dir.\n");
1126 return 1;
1129 dirprovider.SetGREDirs(greDir);
1131 argc -= 2;
1132 argv += 2;
1133 } else {
1134 #ifdef XP_MACOSX
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.
1137 // ../MacOS.
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);
1145 if (dirExists) {
1146 greDir = tmpDir.forget();
1148 dirprovider.SetGREDirs(greDir);
1149 #else
1150 nsAutoString workingDir;
1151 if (!GetCurrentWorkingDirectory(workingDir)) {
1152 printf("GetCurrentWorkingDirectory failed.\n");
1153 return 1;
1155 rv = NS_NewLocalFile(workingDir, true, getter_AddRefs(greDir));
1156 if (NS_FAILED(rv)) {
1157 printf("NS_NewLocalFile failed.\n");
1158 return 1;
1160 #endif
1163 if (argc > 1 && !strcmp(argv[1], "-a")) {
1164 if (argc < 3) {
1165 return usage();
1168 nsCOMPtr<nsIFile> dir;
1169 rv = XRE_GetFileFromPath(argv[2], getter_AddRefs(dir));
1170 if (NS_SUCCEEDED(rv)) {
1171 appDir = dir;
1172 dirprovider.SetAppDir(appDir);
1174 if (NS_FAILED(rv)) {
1175 printf("Couldn't use given appdir.\n");
1176 return 1;
1178 argc -= 2;
1179 argv += 2;
1182 while (argc > 1 && !strcmp(argv[1], "-r")) {
1183 if (argc < 3) {
1184 return usage();
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");
1191 return 1;
1193 XRE_AddManifestLocation(NS_APP_LOCATION, lf);
1195 argc -= 2;
1196 argv += 2;
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");
1204 return 1;
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);
1213 argc -= 2;
1214 argv += 2;
1217 rv = NS_InitXPCOM(nullptr, appDir, &dirprovider);
1218 if (NS_FAILED(rv)) {
1219 printf("NS_InitXPCOM failed!\n");
1220 return 1;
1223 // xpc::ErrorReport::LogToConsoleWithStack needs this to print errors
1224 // to stderr.
1225 Preferences::SetBool("browser.dom.window.dump.enabled", true);
1226 Preferences::SetBool("devtools.console.stdout.chrome", true);
1228 AutoJSAPI jsapi;
1229 jsapi.Init();
1230 cx = jsapi.cx();
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);
1240 argc--;
1241 argv++;
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
1246 // eval loop)
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)) {
1254 fprintf(gErrFile,
1255 "+++ Failed to obtain SystemPrincipal from "
1256 "ScriptSecurityManager service.\n");
1257 } else {
1258 // fetch the JS principals and stick in a global
1259 gJSPrincipals = nsJSPrincipals::get(systemprincipal);
1260 JS_HoldPrincipals(gJSPrincipals);
1262 } else {
1263 fprintf(gErrFile,
1264 "+++ Failed to get ScriptSecurityManager service, running "
1265 "without principals");
1269 const JSSecurityCallbacks* scb = JS_GetSecurityCallbacks(cx);
1270 MOZ_ASSERT(
1271 scb,
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,
1292 options, &glob);
1293 if (NS_FAILED(rv)) {
1294 return 1;
1297 // Initialize e10s check on the main thread, if not already done
1298 BrowserTabsRemoteAutostart();
1299 #if defined(XP_WIN)
1300 // Plugin may require audio session if installed plugin can initialize
1301 // asynchronized.
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();
1315 } else {
1316 NS_WARNING(
1317 "Failed to initialize broker services, sandboxed "
1318 "processes will fail to start.");
1320 # endif // defined(MOZ_SANDBOX)
1323 DebugOnly<bool> result = WindowsBCryptInitialization();
1324 MOZ_ASSERT(result);
1326 #endif // defined(XP_WIN)
1328 #ifdef MOZ_CODE_COVERAGE
1329 CodeCoverageHandler::Init();
1330 #endif
1333 if (!glob) {
1334 return 1;
1337 nsCOMPtr<nsIAppStartup> appStartup(components::AppStartup::Service());
1338 if (!appStartup) {
1339 return 1;
1341 appStartup->DoneStartingUp();
1343 backstagePass->SetGlobalObject(glob);
1345 JSAutoRealm ar(cx, glob);
1347 if (!JS_InitReflectParse(cx, glob)) {
1348 return 1;
1351 if (!JS_DefineFunctions(cx, glob, glob_functions)) {
1352 return 1;
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) {
1366 # ifdef LIBFUZZER
1367 // argv[0] was removed previously, but libFuzzer expects it
1368 argc++;
1369 argv--;
1371 result = FuzzXPCRuntimeStart(&jsapi, &argc, &argv,
1372 aShellData->fuzzerDriver);
1373 # elif AFLFUZZ
1374 MOZ_CRASH("AFL is unsupported for XPC runtime fuzzing integration");
1375 # endif
1376 } else {
1377 #endif
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)) {
1386 if (gExitCode) {
1387 result = gExitCode;
1388 } else if (gQuitting) {
1389 result = 0;
1390 } else {
1391 result = EXITCODE_RUNTIME_ERROR;
1394 #ifdef FUZZING_INTERFACES
1396 #endif
1399 // Signal that we're now shutting down.
1400 nsCOMPtr<nsIObserver> obs = do_QueryInterface(appStartup);
1401 if (obs) {
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);
1409 JS_GC(cx);
1411 JS_GC(cx);
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
1427 // holds.
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();
1436 NS_LogTerm();
1438 XRE_DeinitCommandLine();
1440 return result;
1443 void XPCShellDirProvider::SetGREDirs(nsIFile* greDir) {
1444 mGREDir = greDir;
1445 mGREDir->Clone(getter_AddRefs(mGREBinDir));
1447 #ifdef XP_MACOSX
1448 nsAutoCString leafName;
1449 mGREDir->GetNativeLeafName(leafName);
1450 if (leafName.EqualsLiteral("Resources")) {
1451 mGREBinDir->SetNativeLeafName("MacOS"_ns);
1453 #endif
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)
1469 NS_IMETHODIMP
1470 XPCShellDirProvider::GetFile(const char* prop, bool* persistent,
1471 nsIFile** result) {
1472 if (mGREDir && !strcmp(prop, NS_GRE_DIR)) {
1473 *persistent = true;
1474 return mGREDir->Clone(result);
1475 } else if (mGREBinDir && !strcmp(prop, NS_GRE_BIN_DIR)) {
1476 *persistent = true;
1477 return mGREBinDir->Clone(result);
1478 } else if (mAppFile && !strcmp(prop, XRE_EXECUTABLE_FILE)) {
1479 *persistent = true;
1480 return mAppFile->Clone(result);
1481 } else if (mGREDir && !strcmp(prop, NS_APP_PREF_DEFAULTS_50_DIR)) {
1482 nsCOMPtr<nsIFile> file;
1483 *persistent = true;
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);
1489 return NS_OK;
1492 return NS_ERROR_FAILURE;
1495 NS_IMETHODIMP
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);
1505 nsresult rv =
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;
1515 bool exists;
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;