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/. */
6
7 #include "nsXULAppAPI.h"
8 #include "jsapi.h"
9 #include "jsfriendapi.h"
10 #include "js/Array.h" // JS::NewArrayObject
11 #include "js/CharacterEncoding.h"
12 #include "js/CompilationAndEvaluation.h" // JS::Evaluate
13 #include "js/ContextOptions.h"
14 #include "js/Printf.h"
15 #include "js/PropertySpec.h"
16 #include "js/SourceText.h" // JS::SourceText
17 #include "mozilla/ChaosMode.h"
18 #include "mozilla/dom/AutoEntryScript.h"
19 #include "mozilla/dom/ScriptSettings.h"
20 #include "mozilla/IOInterposer.h"
21 #include "mozilla/Preferences.h"
22 #include "mozilla/Utf8.h" // mozilla::Utf8Unit
23 #include "nsServiceManagerUtils.h"
24 #include "nsComponentManagerUtils.h"
25 #include "nsExceptionHandler.h"
26 #include "nsIServiceManager.h"
27 #include "nsIFile.h"
28 #include "nsString.h"
29 #include "nsIDirectoryService.h"
30 #include "nsDirectoryServiceDefs.h"
31 #include "nsAppDirectoryServiceDefs.h"
32 #include "nscore.h"
33 #include "nsArrayEnumerator.h"
34 #include "nsCOMArray.h"
35 #include "nsDirectoryServiceUtils.h"
36 #include "nsCOMPtr.h"
37 #include "nsJSPrincipals.h"
38 #include "xpcpublic.h"
39 #include "xpcprivate.h"
40 #include "BackstagePass.h"
41 #include "nsIScriptSecurityManager.h"
42 #include "nsIPrincipal.h"
43 #include "nsJSUtils.h"
44
45 #include "nsIXULRuntime.h"
46 #include "nsIAppStartup.h"
47 #include "GeckoProfiler.h"
48 #include "Components.h"
49
50 #ifdef ANDROID
51 # include <android/log.h>
52 # include "XREShellData.h"
53 #endif
54
55 #ifdef XP_WIN
56 # include "mozilla/mscom/ProcessRuntime.h"
57 # include "mozilla/ScopeExit.h"
58 # include "mozilla/widget/AudioSession.h"
59 # include "mozilla/WinDllServices.h"
60 # include <windows.h>
61 # if defined(MOZ_SANDBOX)
62 # include "XREShellData.h"
63 # include "sandboxBroker.h"
64 # endif
65 #endif
66
67 #ifdef MOZ_CODE_COVERAGE
68 # include "mozilla/CodeCoverageHandler.h"
69 #endif
70
71 // all this crap is needed to do the interactive shell stuff
72 #include <stdlib.h>
73 #include <errno.h>
74 #ifdef HAVE_IO_H
75 # include <io.h> /* for isatty() */
76 #endif
77 #ifdef HAVE_UNISTD_H
78 # include <unistd.h> /* for isatty() */
79 #endif
80
81 #ifdef ENABLE_TESTS
82 # include "xpctest_private.h"
83 #endif
84
85 // Fuzzing support for XPC runtime fuzzing
86 #ifdef FUZZING_INTERFACES
87 # include "xpcrtfuzzing/xpcrtfuzzing.h"
88 # include "XREShellData.h"
89 static bool fuzzDoDebug = !!getenv("MOZ_FUZZ_DEBUG");
90 static bool fuzzHaveModule = !!getenv("FUZZER");
91 #endif // FUZZING_INTERFACES
92
93 using namespace mozilla;
94 using namespace JS;
95 using mozilla::dom::AutoEntryScript;
96 using mozilla::dom::AutoJSAPI;
97
98 class XPCShellDirProvider : public nsIDirectoryServiceProvider2 {
99 public:
100 NS_DECL_ISUPPORTS_INHERITED
101 NS_DECL_NSIDIRECTORYSERVICEPROVIDER
102 NS_DECL_NSIDIRECTORYSERVICEPROVIDER2
103
104 XPCShellDirProvider() = default;
105 ~XPCShellDirProvider() = default;
106
107 // The platform resource folder
108 void SetGREDirs(nsIFile* greDir);
ClearGREDirs()109 void ClearGREDirs() {
110 mGREDir = nullptr;
111 mGREBinDir = nullptr;
112 }
113 // The application resource folder
114 void SetAppDir(nsIFile* appFile);
ClearAppDir()115 void ClearAppDir() { mAppDir = nullptr; }
116 // The app executable
117 void SetAppFile(nsIFile* appFile);
ClearAppFile()118 void ClearAppFile() { mAppFile = nullptr; }
119 // An additional custom plugin dir if specified
120 void SetPluginDir(nsIFile* pluginDir);
ClearPluginDir()121 void ClearPluginDir() { mPluginDir = nullptr; }
122
123 private:
124 nsCOMPtr<nsIFile> mGREDir;
125 nsCOMPtr<nsIFile> mGREBinDir;
126 nsCOMPtr<nsIFile> mAppDir;
127 nsCOMPtr<nsIFile> mPluginDir;
128 nsCOMPtr<nsIFile> mAppFile;
129 };
130
131 #ifdef XP_WIN
132 class MOZ_STACK_CLASS AutoAudioSession {
133 public:
AutoAudioSession()134 AutoAudioSession() { widget::StartAudioSession(); }
135
~AutoAudioSession()136 ~AutoAudioSession() { widget::StopAudioSession(); }
137 };
138 #endif
139
140 #define EXITCODE_RUNTIME_ERROR 3
141 #define EXITCODE_FILE_NOT_FOUND 4
142
143 static FILE* gOutFile = nullptr;
144 static FILE* gErrFile = nullptr;
145 static FILE* gInFile = nullptr;
146
147 static int gExitCode = 0;
148 static bool gQuitting = false;
149 static bool reportWarnings = true;
150 static bool compileOnly = false;
151
152 static JSPrincipals* gJSPrincipals = nullptr;
153 static nsAutoString* gWorkingDirectory = nullptr;
154
GetLocationProperty(JSContext * cx,unsigned argc,Value * vp)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;
160 }
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 # if defined(XP_WIN)
168 // convert from the system codepage to UTF-16
169 int bufferSize =
170 MultiByteToWideChar(CP_ACP, 0, filename.get(), -1, nullptr, 0);
171 nsAutoString filenameString;
172 filenameString.SetLength(bufferSize);
173 MultiByteToWideChar(CP_ACP, 0, filename.get(), -1,
174 (LPWSTR)filenameString.BeginWriting(),
175 filenameString.Length());
176 // remove the null terminator
177 filenameString.SetLength(bufferSize - 1);
178
179 // replace forward slashes with backslashes,
180 // since nsLocalFileWin chokes on them
181 char16_t* start = filenameString.BeginWriting();
182 char16_t* end = filenameString.EndWriting();
183
184 while (start != end) {
185 if (*start == L'/') {
186 *start = L'\\';
187 }
188 start++;
189 }
190 # elif defined(XP_UNIX)
191 NS_ConvertUTF8toUTF16 filenameString(filename.get());
192 # endif
193
194 nsCOMPtr<nsIFile> location;
195 nsresult rv =
196 NS_NewLocalFile(filenameString, false, getter_AddRefs(location));
197
198 if (!location && gWorkingDirectory) {
199 // could be a relative path, try appending it to the cwd
200 // and then normalize
201 nsAutoString absolutePath(*gWorkingDirectory);
202 absolutePath.Append(filenameString);
203
204 rv = NS_NewLocalFile(absolutePath, false, getter_AddRefs(location));
205 }
206
207 if (location) {
208 bool symlink;
209 // don't normalize symlinks, because that's kind of confusing
210 if (NS_SUCCEEDED(location->IsSymlink(&symlink)) && !symlink)
211 location->Normalize();
212 RootedObject locationObj(cx);
213 RootedObject scope(cx, JS::CurrentGlobalOrNull(cx));
214 rv = nsXPConnect::XPConnect()->WrapNative(
215 cx, scope, location, NS_GET_IID(nsIFile), locationObj.address());
216 if (NS_SUCCEEDED(rv) && locationObj) {
217 args.rval().setObject(*locationObj);
218 }
219 }
220 }
221
222 return true;
223 #endif
224 }
225
GetLine(JSContext * cx,char * bufp,FILE * file,const char * prompt)226 static bool GetLine(JSContext* cx, char* bufp, FILE* file, const char* prompt) {
227 fputs(prompt, gOutFile);
228 fflush(gOutFile);
229
230 char line[4096] = {'\0'};
231 while (true) {
232 if (fgets(line, sizeof line, file)) {
233 strcpy(bufp, line);
234 return true;
235 }
236 if (errno != EINTR) {
237 return false;
238 }
239 }
240 }
241
ReadLine(JSContext * cx,unsigned argc,Value * vp)242 static bool ReadLine(JSContext* cx, unsigned argc, Value* vp) {
243 CallArgs args = CallArgsFromVp(argc, vp);
244
245 // While 4096 might be quite arbitrary, this is something to be fixed in
246 // bug 105707. It is also the same limit as in ProcessFile.
247 char buf[4096];
248 RootedString str(cx);
249
250 /* If a prompt was specified, construct the string */
251 if (args.length() > 0) {
252 str = JS::ToString(cx, args[0]);
253 if (!str) {
254 return false;
255 }
256 } else {
257 str = JS_GetEmptyString(cx);
258 }
259
260 /* Get a line from the infile */
261 JS::UniqueChars strBytes = JS_EncodeStringToLatin1(cx, str);
262 if (!strBytes || !GetLine(cx, buf, gInFile, strBytes.get())) {
263 return false;
264 }
265
266 /* Strip newline character added by GetLine() */
267 unsigned int buflen = strlen(buf);
268 if (buflen == 0) {
269 if (feof(gInFile)) {
270 args.rval().setNull();
271 return true;
272 }
273 } else if (buf[buflen - 1] == '\n') {
274 --buflen;
275 }
276
277 /* Turn buf into a JSString */
278 str = JS_NewStringCopyN(cx, buf, buflen);
279 if (!str) {
280 return false;
281 }
282
283 args.rval().setString(str);
284 return true;
285 }
286
Print(JSContext * cx,unsigned argc,Value * vp)287 static bool Print(JSContext* cx, unsigned argc, Value* vp) {
288 CallArgs args = CallArgsFromVp(argc, vp);
289 args.rval().setUndefined();
290
291 #ifdef FUZZING_INTERFACES
292 if (fuzzHaveModule && !fuzzDoDebug) {
293 // When fuzzing and not debugging, suppress any print() output,
294 // as it slows down fuzzing and makes libFuzzer's output hard
295 // to read.
296 return true;
297 }
298 #endif // FUZZING_INTERFACES
299
300 RootedString str(cx);
301 nsAutoCString utf8output;
302
303 for (unsigned i = 0; i < args.length(); i++) {
304 str = ToString(cx, args[i]);
305 if (!str) {
306 return false;
307 }
308
309 JS::UniqueChars utf8str = JS_EncodeStringToUTF8(cx, str);
310 if (!utf8str) {
311 return false;
312 }
313
314 if (i) {
315 utf8output.Append(' ');
316 }
317 utf8output.Append(utf8str.get(), strlen(utf8str.get()));
318 }
319 utf8output.Append('\n');
320 fputs(utf8output.get(), gOutFile);
321 fflush(gOutFile);
322 return true;
323 }
324
Dump(JSContext * cx,unsigned argc,Value * vp)325 static bool Dump(JSContext* cx, unsigned argc, Value* vp) {
326 CallArgs args = CallArgsFromVp(argc, vp);
327 args.rval().setUndefined();
328
329 if (!args.length()) {
330 return true;
331 }
332
333 RootedString str(cx, ToString(cx, args[0]));
334 if (!str) {
335 return false;
336 }
337
338 JS::UniqueChars utf8str = JS_EncodeStringToUTF8(cx, str);
339 if (!utf8str) {
340 return false;
341 }
342
343 #ifdef ANDROID
344 __android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", utf8str.get());
345 #endif
346 #ifdef XP_WIN
347 if (IsDebuggerPresent()) {
348 nsAutoJSString wstr;
349 if (!wstr.init(cx, str)) {
350 return false;
351 }
352 OutputDebugStringW(wstr.get());
353 }
354 #endif
355 fputs(utf8str.get(), gOutFile);
356 fflush(gOutFile);
357 return true;
358 }
359
Load(JSContext * cx,unsigned argc,Value * vp)360 static bool Load(JSContext* cx, unsigned argc, Value* vp) {
361 CallArgs args = CallArgsFromVp(argc, vp);
362
363 JS::RootedObject thisObject(cx);
364 if (!args.computeThis(cx, &thisObject)) {
365 return false;
366 }
367 if (!JS_IsGlobalObject(thisObject)) {
368 JS_ReportErrorASCII(cx, "Trying to load() into a non-global object");
369 return false;
370 }
371
372 RootedString str(cx);
373 for (unsigned i = 0; i < args.length(); i++) {
374 str = ToString(cx, args[i]);
375 if (!str) {
376 return false;
377 }
378 JS::UniqueChars filename = JS_EncodeStringToLatin1(cx, str);
379 if (!filename) {
380 return false;
381 }
382 FILE* file = fopen(filename.get(), "r");
383 if (!file) {
384 filename = JS_EncodeStringToUTF8(cx, str);
385 if (!filename) {
386 return false;
387 }
388 JS_ReportErrorUTF8(cx, "cannot open file '%s' for reading",
389 filename.get());
390 return false;
391 }
392 JS::CompileOptions options(cx);
393 options.setFileAndLine(filename.get(), 1)
394 .setIsRunOnce(true)
395 .setSkipFilenameValidation(true);
396 JS::Rooted<JSScript*> script(cx);
397 JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
398 script = JS::CompileUtf8File(cx, options, file);
399 fclose(file);
400 if (!script) {
401 return false;
402 }
403
404 if (!compileOnly) {
405 if (!JS_ExecuteScript(cx, script)) {
406 return false;
407 }
408 }
409 }
410 args.rval().setUndefined();
411 return true;
412 }
413
Quit(JSContext * cx,unsigned argc,Value * vp)414 static bool Quit(JSContext* cx, unsigned argc, Value* vp) {
415 CallArgs args = CallArgsFromVp(argc, vp);
416
417 gExitCode = 0;
418 if (!ToInt32(cx, args.get(0), &gExitCode)) {
419 return false;
420 }
421
422 gQuitting = true;
423 // exit(0);
424 return false;
425 }
426
DumpXPC(JSContext * cx,unsigned argc,Value * vp)427 static bool DumpXPC(JSContext* cx, unsigned argc, Value* vp) {
428 JS::CallArgs args = CallArgsFromVp(argc, vp);
429
430 uint16_t depth = 2;
431 if (args.length() > 0) {
432 if (!JS::ToUint16(cx, args[0], &depth)) {
433 return false;
434 }
435 }
436
437 nsXPConnect::XPConnect()->DebugDump(int16_t(depth));
438 args.rval().setUndefined();
439 return true;
440 }
441
GC(JSContext * cx,unsigned argc,Value * vp)442 static bool GC(JSContext* cx, unsigned argc, Value* vp) {
443 CallArgs args = CallArgsFromVp(argc, vp);
444
445 JS_GC(cx);
446
447 args.rval().setUndefined();
448 return true;
449 }
450
451 #ifdef JS_GC_ZEAL
GCZeal(JSContext * cx,unsigned argc,Value * vp)452 static bool GCZeal(JSContext* cx, unsigned argc, Value* vp) {
453 CallArgs args = CallArgsFromVp(argc, vp);
454 uint32_t zeal;
455 if (!ToUint32(cx, args.get(0), &zeal)) {
456 return false;
457 }
458
459 JS_SetGCZeal(cx, uint8_t(zeal), JS_DEFAULT_ZEAL_FREQ);
460 args.rval().setUndefined();
461 return true;
462 }
463 #endif
464
SendCommand(JSContext * cx,unsigned argc,Value * vp)465 static bool SendCommand(JSContext* cx, unsigned argc, Value* vp) {
466 CallArgs args = CallArgsFromVp(argc, vp);
467
468 if (args.length() == 0) {
469 JS_ReportErrorASCII(cx, "Function takes at least one argument!");
470 return false;
471 }
472
473 RootedString str(cx, ToString(cx, args[0]));
474 if (!str) {
475 JS_ReportErrorASCII(cx, "Could not convert argument 1 to string!");
476 return false;
477 }
478
479 if (args.get(1).isObject() && !JS_ObjectIsFunction(&args[1].toObject())) {
480 JS_ReportErrorASCII(cx, "Could not convert argument 2 to function!");
481 return false;
482 }
483
484 if (!XRE_SendTestShellCommand(
485 cx, str, args.length() > 1 ? args[1].address() : nullptr)) {
486 JS_ReportErrorASCII(cx, "Couldn't send command!");
487 return false;
488 }
489
490 args.rval().setUndefined();
491 return true;
492 }
493
Options(JSContext * cx,unsigned argc,Value * vp)494 static bool Options(JSContext* cx, unsigned argc, Value* vp) {
495 JS::CallArgs args = CallArgsFromVp(argc, vp);
496 ContextOptions oldContextOptions = ContextOptionsRef(cx);
497
498 RootedString str(cx);
499 JS::UniqueChars opt;
500 for (unsigned i = 0; i < args.length(); ++i) {
501 str = ToString(cx, args[i]);
502 if (!str) {
503 return false;
504 }
505
506 opt = JS_EncodeStringToUTF8(cx, str);
507 if (!opt) {
508 return false;
509 }
510
511 if (strcmp(opt.get(), "strict_mode") == 0) {
512 ContextOptionsRef(cx).toggleStrictMode();
513 } else {
514 JS_ReportErrorUTF8(cx,
515 "unknown option name '%s'. The valid name is "
516 "strict_mode.",
517 opt.get());
518 return false;
519 }
520 }
521
522 UniqueChars names;
523 if (names && oldContextOptions.strictMode()) {
524 names = JS_sprintf_append(std::move(names), "%s%s", names ? "," : "",
525 "strict_mode");
526 if (!names) {
527 JS_ReportOutOfMemory(cx);
528 return false;
529 }
530 }
531
532 str = JS_NewStringCopyZ(cx, names.get());
533 if (!str) {
534 return false;
535 }
536
537 args.rval().setString(str);
538 return true;
539 }
540
541 static PersistentRootedValue* sScriptedInterruptCallback = nullptr;
542
XPCShellInterruptCallback(JSContext * cx)543 static bool XPCShellInterruptCallback(JSContext* cx) {
544 MOZ_ASSERT(sScriptedInterruptCallback->initialized());
545 RootedValue callback(cx, *sScriptedInterruptCallback);
546
547 // If no interrupt callback was set by script, no-op.
548 if (callback.isUndefined()) {
549 return true;
550 }
551
552 MOZ_ASSERT(js::IsFunctionObject(&callback.toObject()));
553
554 JSAutoRealm ar(cx, &callback.toObject());
555 RootedValue rv(cx);
556 if (!JS_CallFunctionValue(cx, nullptr, callback,
557 JS::HandleValueArray::empty(), &rv) ||
558 !rv.isBoolean()) {
559 NS_WARNING("Scripted interrupt callback failed! Terminating script.");
560 JS_ClearPendingException(cx);
561 return false;
562 }
563
564 return rv.toBoolean();
565 }
566
SetInterruptCallback(JSContext * cx,unsigned argc,Value * vp)567 static bool SetInterruptCallback(JSContext* cx, unsigned argc, Value* vp) {
568 MOZ_ASSERT(sScriptedInterruptCallback->initialized());
569
570 // Sanity-check args.
571 JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
572 if (args.length() != 1) {
573 JS_ReportErrorASCII(cx, "Wrong number of arguments");
574 return false;
575 }
576
577 // Allow callers to remove the interrupt callback by passing undefined.
578 if (args[0].isUndefined()) {
579 *sScriptedInterruptCallback = UndefinedValue();
580 return true;
581 }
582
583 // Otherwise, we should have a function object.
584 if (!args[0].isObject() || !js::IsFunctionObject(&args[0].toObject())) {
585 JS_ReportErrorASCII(cx, "Argument must be a function");
586 return false;
587 }
588
589 *sScriptedInterruptCallback = args[0];
590
591 return true;
592 }
593
SimulateNoScriptActivity(JSContext * cx,unsigned argc,Value * vp)594 static bool SimulateNoScriptActivity(JSContext* cx, unsigned argc, Value* vp) {
595 // Sanity-check args.
596 JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
597 if (args.length() != 1 || !args[0].isInt32() || args[0].toInt32() < 0) {
598 JS_ReportErrorASCII(cx, "Expected a positive integer argument");
599 return false;
600 }
601
602 // This mimics mozilla::SpinEventLoopUntil but instead of spinning the
603 // event loop we sleep, to make sure we don't run script.
604 xpc::AutoScriptActivity asa(false);
605 PR_Sleep(PR_SecondsToInterval(args[0].toInt32()));
606
607 args.rval().setUndefined();
608 return true;
609 }
610
RegisterAppManifest(JSContext * cx,unsigned argc,Value * vp)611 static bool RegisterAppManifest(JSContext* cx, unsigned argc, Value* vp) {
612 JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
613 if (args.length() != 1) {
614 JS_ReportErrorASCII(cx, "Wrong number of arguments");
615 return false;
616 }
617 if (!args[0].isObject()) {
618 JS_ReportErrorASCII(cx,
619 "Expected object as argument 1 to registerAppManifest");
620 return false;
621 }
622
623 Rooted<JSObject*> arg1(cx, &args[0].toObject());
624 nsCOMPtr<nsIFile> file;
625 nsresult rv = nsXPConnect::XPConnect()->WrapJS(cx, arg1, NS_GET_IID(nsIFile),
626 getter_AddRefs(file));
627 if (NS_FAILED(rv)) {
628 XPCThrower::Throw(rv, cx);
629 return false;
630 }
631 rv = XRE_AddManifestLocation(NS_APP_LOCATION, file);
632 if (NS_FAILED(rv)) {
633 XPCThrower::Throw(rv, cx);
634 return false;
635 }
636 return true;
637 }
638
639 #ifdef ENABLE_TESTS
RegisterXPCTestComponents(JSContext * cx,unsigned argc,Value * vp)640 static bool RegisterXPCTestComponents(JSContext* cx, unsigned argc, Value* vp) {
641 JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
642 if (args.length() != 0) {
643 JS_ReportErrorASCII(cx, "Wrong number of arguments");
644 return false;
645 }
646 nsresult rv = XRE_AddStaticComponent(&kXPCTestModule);
647 if (NS_FAILED(rv)) {
648 XPCThrower::Throw(rv, cx);
649 return false;
650 }
651 return true;
652 }
653 #endif
654
655 static const JSFunctionSpec glob_functions[] = {
656 // clang-format off
657 JS_FN("print", Print, 0,0),
658 JS_FN("readline", ReadLine, 1,0),
659 JS_FN("load", Load, 1,0),
660 JS_FN("quit", Quit, 0,0),
661 JS_FN("dumpXPC", DumpXPC, 1,0),
662 JS_FN("dump", Dump, 1,0),
663 JS_FN("gc", GC, 0,0),
664 #ifdef JS_GC_ZEAL
665 JS_FN("gczeal", GCZeal, 1,0),
666 #endif
667 JS_FN("options", Options, 0,0),
668 JS_FN("sendCommand", SendCommand, 1,0),
669 JS_FN("atob", xpc::Atob, 1,0),
670 JS_FN("btoa", xpc::Btoa, 1,0),
671 JS_FN("setInterruptCallback", SetInterruptCallback, 1,0),
672 JS_FN("simulateNoScriptActivity", SimulateNoScriptActivity, 1,0),
673 JS_FN("registerAppManifest", RegisterAppManifest, 1, 0),
674 #ifdef ENABLE_TESTS
675 JS_FN("registerXPCTestComponents", RegisterXPCTestComponents, 0, 0),
676 #endif
677 JS_FS_END
678 // clang-format on
679 };
680
681 /***************************************************************************/
682
683 typedef enum JSShellErrNum {
684 #define MSG_DEF(name, number, count, exception, format) name = number,
685 #include "jsshell.msg"
686 #undef MSG_DEF
687 JSShellErr_Limit
688 } JSShellErrNum;
689
690 static const JSErrorFormatString jsShell_ErrorFormatString[JSShellErr_Limit] = {
691 #define MSG_DEF(name, number, count, exception, format) {#name, format, count},
692 #include "jsshell.msg"
693 #undef MSG_DEF
694 };
695
my_GetErrorMessage(void * userRef,const unsigned errorNumber)696 static const JSErrorFormatString* my_GetErrorMessage(
697 void* userRef, const unsigned errorNumber) {
698 if (errorNumber == 0 || errorNumber >= JSShellErr_Limit) {
699 return nullptr;
700 }
701
702 return &jsShell_ErrorFormatString[errorNumber];
703 }
704
ProcessUtf8Line(AutoJSAPI & jsapi,const char * buffer,int startline)705 static bool ProcessUtf8Line(AutoJSAPI& jsapi, const char* buffer,
706 int startline) {
707 JSContext* cx = jsapi.cx();
708 JS::CompileOptions options(cx);
709 options.setFileAndLine("typein", startline)
710 .setIsRunOnce(true)
711 .setSkipFilenameValidation(true);
712
713 JS::SourceText<mozilla::Utf8Unit> srcBuf;
714 if (!srcBuf.init(cx, buffer, strlen(buffer), JS::SourceOwnership::Borrowed)) {
715 return false;
716 }
717
718 JS::RootedScript script(cx, JS::Compile(cx, options, srcBuf));
719 if (!script) {
720 return false;
721 }
722 if (compileOnly) {
723 return true;
724 }
725
726 JS::RootedValue result(cx);
727 if (!JS_ExecuteScript(cx, script, &result)) {
728 return false;
729 }
730
731 if (result.isUndefined()) {
732 return true;
733 }
734
735 RootedString str(cx, JS::ToString(cx, result));
736 if (!str) {
737 return false;
738 }
739
740 JS::UniqueChars bytes = JS_EncodeStringToLatin1(cx, str);
741 if (!bytes) {
742 return false;
743 }
744
745 fprintf(gOutFile, "%s\n", bytes.get());
746 return true;
747 }
748
ProcessFile(AutoJSAPI & jsapi,const char * filename,FILE * file,bool forceTTY)749 static bool ProcessFile(AutoJSAPI& jsapi, const char* filename, FILE* file,
750 bool forceTTY) {
751 JSContext* cx = jsapi.cx();
752 JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
753 MOZ_ASSERT(global);
754
755 if (forceTTY) {
756 file = stdin;
757 } else if (!isatty(fileno(file))) {
758 /*
759 * It's not interactive - just execute it.
760 *
761 * Support the UNIX #! shell hack; gobble the first line if it starts
762 * with '#'. TODO - this isn't quite compatible with sharp variables,
763 * as a legal js program (using sharp variables) might start with '#'.
764 * But that would require multi-character lookahead.
765 */
766 int ch = fgetc(file);
767 if (ch == '#') {
768 while ((ch = fgetc(file)) != EOF) {
769 if (ch == '\n' || ch == '\r') {
770 break;
771 }
772 }
773 }
774 ungetc(ch, file);
775
776 JS::RootedScript script(cx);
777 JS::RootedValue unused(cx);
778 JS::CompileOptions options(cx);
779 options.setFileAndLine(filename, 1)
780 .setIsRunOnce(true)
781 .setNoScriptRval(true)
782 .setSkipFilenameValidation(true);
783 script = JS::CompileUtf8File(cx, options, file);
784 if (!script) {
785 return false;
786 }
787 return compileOnly || JS_ExecuteScript(cx, script, &unused);
788 }
789
790 /* It's an interactive filehandle; drop into read-eval-print loop. */
791 int lineno = 1;
792 bool hitEOF = false;
793 do {
794 char buffer[4096];
795 char* bufp = buffer;
796 *bufp = '\0';
797
798 /*
799 * Accumulate lines until we get a 'compilable unit' - one that either
800 * generates an error (before running out of source) or that compiles
801 * cleanly. This should be whenever we get a complete statement that
802 * coincides with the end of a line.
803 */
804 int startline = lineno;
805 do {
806 if (!GetLine(cx, bufp, file, startline == lineno ? "js> " : "")) {
807 hitEOF = true;
808 break;
809 }
810 bufp += strlen(bufp);
811 lineno++;
812 } while (
813 !JS_Utf8BufferIsCompilableUnit(cx, global, buffer, strlen(buffer)));
814
815 if (!ProcessUtf8Line(jsapi, buffer, startline)) {
816 jsapi.ReportException();
817 }
818 } while (!hitEOF && !gQuitting);
819
820 fprintf(gOutFile, "\n");
821 return true;
822 }
823
Process(AutoJSAPI & jsapi,const char * filename,bool forceTTY)824 static bool Process(AutoJSAPI& jsapi, const char* filename, bool forceTTY) {
825 FILE* file;
826
827 if (forceTTY || !filename || strcmp(filename, "-") == 0) {
828 file = stdin;
829 } else {
830 file = fopen(filename, "r");
831 if (!file) {
832 /*
833 * Use Latin1 variant here because the encoding of the return value
834 * of strerror function can be non-UTF-8.
835 */
836 JS_ReportErrorNumberLatin1(jsapi.cx(), my_GetErrorMessage, nullptr,
837 JSSMSG_CANT_OPEN, filename, strerror(errno));
838 gExitCode = EXITCODE_FILE_NOT_FOUND;
839 return false;
840 }
841 }
842
843 bool ok = ProcessFile(jsapi, filename, file, forceTTY);
844 if (file != stdin) {
845 fclose(file);
846 }
847 return ok;
848 }
849
usage()850 static int usage() {
851 fprintf(gErrFile, "%s\n", JS_GetImplementationVersion());
852 fprintf(
853 gErrFile,
854 "usage: xpcshell [-g gredir] [-a appdir] [-r manifest]... [-WwxiCmIp] "
855 "[-f scriptfile] [-e script] [scriptfile] [scriptarg...]\n");
856 return 2;
857 }
858
printUsageAndSetExitCode()859 static bool printUsageAndSetExitCode() {
860 gExitCode = usage();
861 return false;
862 }
863
ProcessArgs(AutoJSAPI & jsapi,char ** argv,int argc,XPCShellDirProvider * aDirProvider)864 static bool ProcessArgs(AutoJSAPI& jsapi, char** argv, int argc,
865 XPCShellDirProvider* aDirProvider) {
866 JSContext* cx = jsapi.cx();
867 const char rcfilename[] = "xpcshell.js";
868 FILE* rcfile;
869 int rootPosition;
870 JS::Rooted<JSObject*> argsObj(cx);
871 char* filename = nullptr;
872 bool isInteractive = true;
873 bool forceTTY = false;
874
875 rcfile = fopen(rcfilename, "r");
876 if (rcfile) {
877 printf("[loading '%s'...]\n", rcfilename);
878 bool ok = ProcessFile(jsapi, rcfilename, rcfile, false);
879 fclose(rcfile);
880 if (!ok) {
881 return false;
882 }
883 }
884
885 JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
886
887 /*
888 * Scan past all optional arguments so we can create the arguments object
889 * before processing any -f options, which must interleave properly with
890 * -v and -w options. This requires two passes, and without getopt, we'll
891 * have to keep the option logic here and in the second for loop in sync.
892 * First of all, find out the first argument position which will be passed
893 * as a script file to be executed.
894 */
895 for (rootPosition = 0; rootPosition < argc; rootPosition++) {
896 if (argv[rootPosition][0] != '-' || argv[rootPosition][1] == '\0') {
897 ++rootPosition;
898 break;
899 }
900
901 bool isPairedFlag =
902 argv[rootPosition][0] != '\0' &&
903 (argv[rootPosition][1] == 'v' || argv[rootPosition][1] == 'f' ||
904 argv[rootPosition][1] == 'e');
905 if (isPairedFlag && rootPosition < argc - 1) {
906 ++rootPosition; // Skip over the 'foo' portion of |-v foo|, |-f foo|, or
907 // |-e foo|.
908 }
909 }
910
911 /*
912 * Create arguments early and define it to root it, so it's safe from any
913 * GC calls nested below, and so it is available to -f <file> arguments.
914 */
915 argsObj = JS::NewArrayObject(cx, 0);
916 if (!argsObj) {
917 return 1;
918 }
919 if (!JS_DefineProperty(cx, global, "arguments", argsObj, 0)) {
920 return 1;
921 }
922
923 for (int j = 0, length = argc - rootPosition; j < length; j++) {
924 RootedString str(cx, JS_NewStringCopyZ(cx, argv[rootPosition++]));
925 if (!str || !JS_DefineElement(cx, argsObj, j, str, JSPROP_ENUMERATE)) {
926 return 1;
927 }
928 }
929
930 for (int i = 0; i < argc; i++) {
931 if (argv[i][0] != '-' || argv[i][1] == '\0') {
932 filename = argv[i++];
933 isInteractive = false;
934 break;
935 }
936 switch (argv[i][1]) {
937 case 'W':
938 reportWarnings = false;
939 break;
940 case 'w':
941 reportWarnings = true;
942 break;
943 case 'x':
944 break;
945 case 'd':
946 /* This used to try to turn on the debugger. */
947 break;
948 case 'm':
949 break;
950 case 'f':
951 if (++i == argc) {
952 return printUsageAndSetExitCode();
953 }
954 if (!Process(jsapi, argv[i], false)) {
955 return false;
956 }
957 /*
958 * XXX: js -f foo.js should interpret foo.js and then
959 * drop into interactive mode, but that breaks test
960 * harness. Just execute foo.js for now.
961 */
962 isInteractive = false;
963 break;
964 case 'i':
965 isInteractive = forceTTY = true;
966 break;
967 case 'e': {
968 RootedValue rval(cx);
969
970 if (++i == argc) {
971 return printUsageAndSetExitCode();
972 }
973
974 JS::CompileOptions opts(cx);
975 opts.setSkipFilenameValidation(true);
976 opts.setFileAndLine("-e", 1);
977
978 JS::SourceText<mozilla::Utf8Unit> srcBuf;
979 if (srcBuf.init(cx, argv[i], strlen(argv[i]),
980 JS::SourceOwnership::Borrowed)) {
981 JS::Evaluate(cx, opts, srcBuf, &rval);
982 }
983
984 isInteractive = false;
985 break;
986 }
987 case 'C':
988 compileOnly = true;
989 isInteractive = false;
990 break;
991 case 'p': {
992 // plugins path
993 char* pluginPath = argv[++i];
994 nsCOMPtr<nsIFile> pluginsDir;
995 if (NS_FAILED(
996 XRE_GetFileFromPath(pluginPath, getter_AddRefs(pluginsDir)))) {
997 fprintf(gErrFile, "Couldn't use given plugins dir.\n");
998 return printUsageAndSetExitCode();
999 }
1000 aDirProvider->SetPluginDir(pluginsDir);
1001 break;
1002 }
1003 default:
1004 return printUsageAndSetExitCode();
1005 }
1006 }
1007
1008 if (filename || isInteractive) {
1009 return Process(jsapi, filename, forceTTY);
1010 }
1011 return true;
1012 }
1013
1014 /***************************************************************************/
1015
GetCurrentWorkingDirectory(nsAString & workingDirectory)1016 static bool GetCurrentWorkingDirectory(nsAString& workingDirectory) {
1017 #if !defined(XP_WIN) && !defined(XP_UNIX)
1018 // XXX: your platform should really implement this
1019 return false;
1020 #elif XP_WIN
1021 DWORD requiredLength = GetCurrentDirectoryW(0, nullptr);
1022 workingDirectory.SetLength(requiredLength);
1023 GetCurrentDirectoryW(workingDirectory.Length(),
1024 (LPWSTR)workingDirectory.BeginWriting());
1025 // we got a trailing null there
1026 workingDirectory.SetLength(requiredLength);
1027 workingDirectory.Replace(workingDirectory.Length() - 1, 1, L'\\');
1028 #elif defined(XP_UNIX)
1029 nsAutoCString cwd;
1030 // 1024 is just a guess at a sane starting value
1031 size_t bufsize = 1024;
1032 char* result = nullptr;
1033 while (result == nullptr) {
1034 cwd.SetLength(bufsize);
1035 result = getcwd(cwd.BeginWriting(), cwd.Length());
1036 if (!result) {
1037 if (errno != ERANGE) {
1038 return false;
1039 }
1040 // need to make the buffer bigger
1041 bufsize *= 2;
1042 }
1043 }
1044 // size back down to the actual string length
1045 cwd.SetLength(strlen(result) + 1);
1046 cwd.Replace(cwd.Length() - 1, 1, '/');
1047 CopyUTF8toUTF16(cwd, workingDirectory);
1048 #endif
1049 return true;
1050 }
1051
1052 static JSSecurityCallbacks shellSecurityCallbacks;
1053
XRE_XPCShellMain(int argc,char ** argv,char ** envp,const XREShellData * aShellData)1054 int XRE_XPCShellMain(int argc, char** argv, char** envp,
1055 const XREShellData* aShellData) {
1056 MOZ_ASSERT(aShellData);
1057
1058 JSContext* cx;
1059 int result = 0;
1060 nsresult rv;
1061
1062 #ifdef ANDROID
1063 gOutFile = aShellData->outFile;
1064 gErrFile = aShellData->errFile;
1065 #else
1066 gOutFile = stdout;
1067 gErrFile = stderr;
1068 #endif
1069 gInFile = stdin;
1070
1071 NS_LogInit();
1072
1073 mozilla::LogModule::Init(argc, argv);
1074
1075 // This guard ensures that all threads that attempt to register themselves
1076 // with the IOInterposer will be properly tracked.
1077 mozilla::IOInterposerInit ioInterposerGuard;
1078
1079 #ifdef MOZ_GECKO_PROFILER
1080 char aLocal;
1081 profiler_init(&aLocal);
1082 #endif
1083
1084 #ifdef MOZ_ASAN_REPORTER
1085 PR_SetEnv("MOZ_DISABLE_ASAN_REPORTER=1");
1086 #endif
1087
1088 if (PR_GetEnv("MOZ_CHAOSMODE")) {
1089 ChaosFeature feature = ChaosFeature::Any;
1090 long featureInt = strtol(PR_GetEnv("MOZ_CHAOSMODE"), nullptr, 16);
1091 if (featureInt) {
1092 // NOTE: MOZ_CHAOSMODE=0 or a non-hex value maps to Any feature.
1093 feature = static_cast<ChaosFeature>(featureInt);
1094 }
1095 ChaosMode::SetChaosFeature(feature);
1096 }
1097
1098 if (ChaosMode::isActive(ChaosFeature::Any)) {
1099 printf_stderr(
1100 "*** You are running in chaos test mode. See ChaosMode.h. ***\n");
1101 }
1102
1103 #ifdef XP_WIN
1104 // Some COM settings are global to the process and must be set before any non-
1105 // trivial COM is run in the application. Since these settings may affect
1106 // stability, we should instantiate COM ASAP so that we can ensure that these
1107 // global settings are configured before anything can interfere.
1108 mscom::ProcessRuntime mscom;
1109 #endif
1110
1111 // The provider needs to outlive the call to shutting down XPCOM.
1112 XPCShellDirProvider dirprovider;
1113
1114 { // Start scoping nsCOMPtrs
1115 nsCOMPtr<nsIFile> appFile;
1116 rv = XRE_GetBinaryPath(getter_AddRefs(appFile));
1117 if (NS_FAILED(rv)) {
1118 printf("Couldn't find application file.\n");
1119 return 1;
1120 }
1121 nsCOMPtr<nsIFile> appDir;
1122 rv = appFile->GetParent(getter_AddRefs(appDir));
1123 if (NS_FAILED(rv)) {
1124 printf("Couldn't get application directory.\n");
1125 return 1;
1126 }
1127
1128 dirprovider.SetAppFile(appFile);
1129
1130 nsCOMPtr<nsIFile> greDir;
1131 if (argc > 1 && !strcmp(argv[1], "-g")) {
1132 if (argc < 3) {
1133 return usage();
1134 }
1135
1136 rv = XRE_GetFileFromPath(argv[2], getter_AddRefs(greDir));
1137 if (NS_FAILED(rv)) {
1138 printf("Couldn't use given GRE dir.\n");
1139 return 1;
1140 }
1141
1142 dirprovider.SetGREDirs(greDir);
1143
1144 argc -= 2;
1145 argv += 2;
1146 } else {
1147 #ifdef XP_MACOSX
1148 // On OSX, the GreD needs to point to Contents/Resources in the .app
1149 // bundle. Libraries will be loaded at a relative path to GreD, i.e.
1150 // ../MacOS.
1151 nsCOMPtr<nsIFile> tmpDir;
1152 XRE_GetFileFromPath(argv[0], getter_AddRefs(greDir));
1153 greDir->GetParent(getter_AddRefs(tmpDir));
1154 tmpDir->Clone(getter_AddRefs(greDir));
1155 tmpDir->SetNativeLeafName("Resources"_ns);
1156 bool dirExists = false;
1157 tmpDir->Exists(&dirExists);
1158 if (dirExists) {
1159 greDir = tmpDir.forget();
1160 }
1161 dirprovider.SetGREDirs(greDir);
1162 #else
1163 nsAutoString workingDir;
1164 if (!GetCurrentWorkingDirectory(workingDir)) {
1165 printf("GetCurrentWorkingDirectory failed.\n");
1166 return 1;
1167 }
1168 rv = NS_NewLocalFile(workingDir, true, getter_AddRefs(greDir));
1169 if (NS_FAILED(rv)) {
1170 printf("NS_NewLocalFile failed.\n");
1171 return 1;
1172 }
1173 #endif
1174 }
1175
1176 if (argc > 1 && !strcmp(argv[1], "-a")) {
1177 if (argc < 3) {
1178 return usage();
1179 }
1180
1181 nsCOMPtr<nsIFile> dir;
1182 rv = XRE_GetFileFromPath(argv[2], getter_AddRefs(dir));
1183 if (NS_SUCCEEDED(rv)) {
1184 appDir = dir;
1185 dirprovider.SetAppDir(appDir);
1186 }
1187 if (NS_FAILED(rv)) {
1188 printf("Couldn't use given appdir.\n");
1189 return 1;
1190 }
1191 argc -= 2;
1192 argv += 2;
1193 }
1194
1195 while (argc > 1 && !strcmp(argv[1], "-r")) {
1196 if (argc < 3) {
1197 return usage();
1198 }
1199
1200 nsCOMPtr<nsIFile> lf;
1201 rv = XRE_GetFileFromPath(argv[2], getter_AddRefs(lf));
1202 if (NS_FAILED(rv)) {
1203 printf("Couldn't get manifest file.\n");
1204 return 1;
1205 }
1206 XRE_AddManifestLocation(NS_APP_LOCATION, lf);
1207
1208 argc -= 2;
1209 argv += 2;
1210 }
1211
1212 const char* val = getenv("MOZ_CRASHREPORTER");
1213 if (val && *val && !CrashReporter::IsDummy()) {
1214 rv = CrashReporter::SetExceptionHandler(greDir, true);
1215 if (NS_FAILED(rv)) {
1216 printf("CrashReporter::SetExceptionHandler failed!\n");
1217 return 1;
1218 }
1219 MOZ_ASSERT(CrashReporter::GetEnabled());
1220 }
1221
1222 if (argc > 1 && !strcmp(argv[1], "--greomni")) {
1223 nsCOMPtr<nsIFile> greOmni;
1224 XRE_GetFileFromPath(argv[2], getter_AddRefs(greOmni));
1225 XRE_InitOmnijar(greOmni, greOmni);
1226 argc -= 2;
1227 argv += 2;
1228 }
1229
1230 rv = NS_InitXPCOM(nullptr, appDir, &dirprovider);
1231 if (NS_FAILED(rv)) {
1232 printf("NS_InitXPCOM failed!\n");
1233 return 1;
1234 }
1235
1236 // xpc::ErrorReport::LogToConsoleWithStack needs this to print errors
1237 // to stderr.
1238 Preferences::SetBool("browser.dom.window.dump.enabled", true);
1239 Preferences::SetBool("devtools.console.stdout.chrome", true);
1240
1241 AutoJSAPI jsapi;
1242 jsapi.Init();
1243 cx = jsapi.cx();
1244
1245 // Override the default XPConnect interrupt callback. We could store the
1246 // old one and restore it before shutting down, but there's not really a
1247 // reason to bother.
1248 sScriptedInterruptCallback = new PersistentRootedValue;
1249 sScriptedInterruptCallback->init(cx, UndefinedValue());
1250
1251 JS_AddInterruptCallback(cx, XPCShellInterruptCallback);
1252
1253 argc--;
1254 argv++;
1255
1256 nsCOMPtr<nsIPrincipal> systemprincipal;
1257 // Fetch the system principal and store it away in a global, to use for
1258 // script compilation in Load() and ProcessFile() (including interactive
1259 // eval loop)
1260 {
1261 nsCOMPtr<nsIScriptSecurityManager> securityManager =
1262 do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
1263 if (NS_SUCCEEDED(rv) && securityManager) {
1264 rv = securityManager->GetSystemPrincipal(
1265 getter_AddRefs(systemprincipal));
1266 if (NS_FAILED(rv)) {
1267 fprintf(gErrFile,
1268 "+++ Failed to obtain SystemPrincipal from "
1269 "ScriptSecurityManager service.\n");
1270 } else {
1271 // fetch the JS principals and stick in a global
1272 gJSPrincipals = nsJSPrincipals::get(systemprincipal);
1273 JS_HoldPrincipals(gJSPrincipals);
1274 }
1275 } else {
1276 fprintf(gErrFile,
1277 "+++ Failed to get ScriptSecurityManager service, running "
1278 "without principals");
1279 }
1280 }
1281
1282 const JSSecurityCallbacks* scb = JS_GetSecurityCallbacks(cx);
1283 MOZ_ASSERT(
1284 scb,
1285 "We are assuming that nsScriptSecurityManager::Init() has been run");
1286 shellSecurityCallbacks = *scb;
1287 JS_SetSecurityCallbacks(cx, &shellSecurityCallbacks);
1288
1289 auto backstagePass = MakeRefPtr<BackstagePass>();
1290
1291 // Make the default XPCShell global use a fresh zone (rather than the
1292 // System Zone) to improve cross-zone test coverage.
1293 JS::RealmOptions options;
1294 options.creationOptions().setNewCompartmentAndZone();
1295 xpc::SetPrefableRealmOptions(options);
1296
1297 // Even if we're building in a configuration where source is
1298 // discarded, there's no reason to do that on XPCShell, and doing so
1299 // might break various automation scripts.
1300 options.behaviors().setDiscardSource(false);
1301
1302 JS::Rooted<JSObject*> glob(cx);
1303 rv = xpc::InitClassesWithNewWrappedGlobal(
1304 cx, static_cast<nsIGlobalObject*>(backstagePass), systemprincipal, 0,
1305 options, &glob);
1306 if (NS_FAILED(rv)) {
1307 return 1;
1308 }
1309
1310 // Initialize e10s check on the main thread, if not already done
1311 BrowserTabsRemoteAutostart();
1312 #ifdef XP_WIN
1313 // Plugin may require audio session if installed plugin can initialize
1314 // asynchronized.
1315 AutoAudioSession audioSession;
1316
1317 // Ensure that DLL Services are running
1318 RefPtr<DllServices> dllSvc(DllServices::Get());
1319 dllSvc->StartUntrustedModulesProcessor();
1320 auto dllServicesDisable =
1321 MakeScopeExit([&dllSvc]() { dllSvc->DisableFull(); });
1322
1323 # if defined(MOZ_SANDBOX)
1324 // Required for sandboxed child processes.
1325 if (aShellData->sandboxBrokerServices) {
1326 SandboxBroker::Initialize(aShellData->sandboxBrokerServices);
1327 SandboxBroker::GeckoDependentInitialize();
1328 } else {
1329 NS_WARNING(
1330 "Failed to initialize broker services, sandboxed "
1331 "processes will fail to start.");
1332 }
1333 # endif
1334 #endif
1335
1336 #ifdef MOZ_CODE_COVERAGE
1337 CodeCoverageHandler::Init();
1338 #endif
1339
1340 {
1341 if (!glob) {
1342 return 1;
1343 }
1344
1345 nsCOMPtr<nsIAppStartup> appStartup(components::AppStartup::Service());
1346 if (!appStartup) {
1347 return 1;
1348 }
1349 appStartup->DoneStartingUp();
1350
1351 backstagePass->SetGlobalObject(glob);
1352
1353 JSAutoRealm ar(cx, glob);
1354
1355 if (!JS_InitReflectParse(cx, glob)) {
1356 return 1;
1357 }
1358
1359 if (!JS_DefineFunctions(cx, glob, glob_functions)) {
1360 return 1;
1361 }
1362
1363 nsAutoString workingDirectory;
1364 if (GetCurrentWorkingDirectory(workingDirectory)) {
1365 gWorkingDirectory = &workingDirectory;
1366 }
1367
1368 JS_DefineProperty(cx, glob, "__LOCATION__", GetLocationProperty, nullptr,
1369 0);
1370
1371 {
1372 #ifdef FUZZING_INTERFACES
1373 if (fuzzHaveModule) {
1374 # ifdef LIBFUZZER
1375 // argv[0] was removed previously, but libFuzzer expects it
1376 argc++;
1377 argv--;
1378
1379 result = FuzzXPCRuntimeStart(&jsapi, &argc, &argv,
1380 aShellData->fuzzerDriver);
1381 # elif __AFL_COMPILER
1382 MOZ_CRASH("AFL is unsupported for XPC runtime fuzzing integration");
1383 # endif
1384 } else {
1385 #endif
1386 // We are almost certainly going to run script here, so we need an
1387 // AutoEntryScript. This is Gecko-specific and not in any spec.
1388 AutoEntryScript aes(backstagePass, "xpcshell argument processing");
1389
1390 // If an exception is thrown, we'll set our return code
1391 // appropriately, and then let the AutoEntryScript destructor report
1392 // the error to the console.
1393 if (!ProcessArgs(aes, argv, argc, &dirprovider)) {
1394 if (gExitCode) {
1395 result = gExitCode;
1396 } else if (gQuitting) {
1397 result = 0;
1398 } else {
1399 result = EXITCODE_RUNTIME_ERROR;
1400 }
1401 }
1402 #ifdef FUZZING_INTERFACES
1403 }
1404 #endif
1405 }
1406
1407 // Signal that we're now shutting down.
1408 nsCOMPtr<nsIObserver> obs = do_QueryInterface(appStartup);
1409 if (obs) {
1410 obs->Observe(nullptr, "quit-application-forced", nullptr);
1411 }
1412
1413 JS_DropPrincipals(cx, gJSPrincipals);
1414 JS_SetAllNonReservedSlotsToUndefined(glob);
1415 JS::RootedObject lexicalEnv(cx, JS_GlobalLexicalEnvironment(glob));
1416 JS_SetAllNonReservedSlotsToUndefined(lexicalEnv);
1417 JS_GC(cx);
1418 }
1419 JS_GC(cx);
1420
1421 dirprovider.ClearGREDirs();
1422 dirprovider.ClearAppDir();
1423 dirprovider.ClearPluginDir();
1424 dirprovider.ClearAppFile();
1425 } // this scopes the nsCOMPtrs
1426
1427 if (!XRE_ShutdownTestShell()) {
1428 NS_ERROR("problem shutting down testshell");
1429 }
1430
1431 // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM
1432 rv = NS_ShutdownXPCOM(nullptr);
1433 MOZ_ASSERT(NS_SUCCEEDED(rv), "NS_ShutdownXPCOM failed");
1434
1435 // Shut down the crashreporter service to prevent leaking some strings it
1436 // holds.
1437 if (CrashReporter::GetEnabled()) {
1438 CrashReporter::UnsetExceptionHandler();
1439 }
1440
1441 #ifdef MOZ_GECKO_PROFILER
1442 // This must precede NS_LogTerm(), otherwise xpcshell return non-zero
1443 // during some tests, which causes failures.
1444 profiler_shutdown();
1445 #endif
1446
1447 NS_LogTerm();
1448
1449 return result;
1450 }
1451
SetGREDirs(nsIFile * greDir)1452 void XPCShellDirProvider::SetGREDirs(nsIFile* greDir) {
1453 mGREDir = greDir;
1454 mGREDir->Clone(getter_AddRefs(mGREBinDir));
1455
1456 #ifdef XP_MACOSX
1457 nsAutoCString leafName;
1458 mGREDir->GetNativeLeafName(leafName);
1459 if (leafName.EqualsLiteral("Resources")) {
1460 mGREBinDir->SetNativeLeafName("MacOS"_ns);
1461 }
1462 #endif
1463 }
1464
SetAppFile(nsIFile * appFile)1465 void XPCShellDirProvider::SetAppFile(nsIFile* appFile) { mAppFile = appFile; }
1466
SetAppDir(nsIFile * appDir)1467 void XPCShellDirProvider::SetAppDir(nsIFile* appDir) { mAppDir = appDir; }
1468
SetPluginDir(nsIFile * pluginDir)1469 void XPCShellDirProvider::SetPluginDir(nsIFile* pluginDir) {
1470 mPluginDir = pluginDir;
1471 }
1472
NS_IMETHODIMP_(MozExternalRefCountType)1473 NS_IMETHODIMP_(MozExternalRefCountType)
1474 XPCShellDirProvider::AddRef() { return 2; }
1475
NS_IMETHODIMP_(MozExternalRefCountType)1476 NS_IMETHODIMP_(MozExternalRefCountType)
1477 XPCShellDirProvider::Release() { return 1; }
1478
NS_IMPL_QUERY_INTERFACE(XPCShellDirProvider,nsIDirectoryServiceProvider,nsIDirectoryServiceProvider2)1479 NS_IMPL_QUERY_INTERFACE(XPCShellDirProvider, nsIDirectoryServiceProvider,
1480 nsIDirectoryServiceProvider2)
1481
1482 NS_IMETHODIMP
1483 XPCShellDirProvider::GetFile(const char* prop, bool* persistent,
1484 nsIFile** result) {
1485 if (mGREDir && !strcmp(prop, NS_GRE_DIR)) {
1486 *persistent = true;
1487 return mGREDir->Clone(result);
1488 } else if (mGREBinDir && !strcmp(prop, NS_GRE_BIN_DIR)) {
1489 *persistent = true;
1490 return mGREBinDir->Clone(result);
1491 } else if (mAppFile && !strcmp(prop, XRE_EXECUTABLE_FILE)) {
1492 *persistent = true;
1493 return mAppFile->Clone(result);
1494 } else if (mGREDir && !strcmp(prop, NS_APP_PREF_DEFAULTS_50_DIR)) {
1495 nsCOMPtr<nsIFile> file;
1496 *persistent = true;
1497 if (NS_FAILED(mGREDir->Clone(getter_AddRefs(file))) ||
1498 NS_FAILED(file->AppendNative("defaults"_ns)) ||
1499 NS_FAILED(file->AppendNative("pref"_ns)))
1500 return NS_ERROR_FAILURE;
1501 file.forget(result);
1502 return NS_OK;
1503 }
1504
1505 return NS_ERROR_FAILURE;
1506 }
1507
1508 NS_IMETHODIMP
GetFiles(const char * prop,nsISimpleEnumerator ** result)1509 XPCShellDirProvider::GetFiles(const char* prop, nsISimpleEnumerator** result) {
1510 if (mGREDir && !strcmp(prop, "ChromeML")) {
1511 nsCOMArray<nsIFile> dirs;
1512
1513 nsCOMPtr<nsIFile> file;
1514 mGREDir->Clone(getter_AddRefs(file));
1515 file->AppendNative("chrome"_ns);
1516 dirs.AppendObject(file);
1517
1518 nsresult rv =
1519 NS_GetSpecialDirectory(NS_APP_CHROME_DIR, getter_AddRefs(file));
1520 if (NS_SUCCEEDED(rv)) {
1521 dirs.AppendObject(file);
1522 }
1523
1524 return NS_NewArrayEnumerator(result, dirs, NS_GET_IID(nsIFile));
1525 } else if (!strcmp(prop, NS_APP_PREFS_DEFAULTS_DIR_LIST)) {
1526 nsCOMArray<nsIFile> dirs;
1527 nsCOMPtr<nsIFile> appDir;
1528 bool exists;
1529 if (mAppDir && NS_SUCCEEDED(mAppDir->Clone(getter_AddRefs(appDir))) &&
1530 NS_SUCCEEDED(appDir->AppendNative("defaults"_ns)) &&
1531 NS_SUCCEEDED(appDir->AppendNative("preferences"_ns)) &&
1532 NS_SUCCEEDED(appDir->Exists(&exists)) && exists) {
1533 dirs.AppendObject(appDir);
1534 return NS_NewArrayEnumerator(result, dirs, NS_GET_IID(nsIFile));
1535 }
1536 return NS_ERROR_FAILURE;
1537 } else if (!strcmp(prop, NS_APP_PLUGINS_DIR_LIST)) {
1538 nsCOMArray<nsIFile> dirs;
1539 // Add the test plugin location passed in by the caller or through
1540 // runxpcshelltests.
1541 if (mPluginDir) {
1542 dirs.AppendObject(mPluginDir);
1543 // If there was no path specified, default to the one set up by automation
1544 } else {
1545 nsCOMPtr<nsIFile> file;
1546 bool exists;
1547 // We have to add this path, buildbot copies the test plugin directory
1548 // to (app)/bin when unpacking test zips.
1549 if (mGREDir) {
1550 mGREDir->Clone(getter_AddRefs(file));
1551 if (NS_SUCCEEDED(mGREDir->Clone(getter_AddRefs(file)))) {
1552 file->AppendNative("plugins"_ns);
1553 if (NS_SUCCEEDED(file->Exists(&exists)) && exists) {
1554 dirs.AppendObject(file);
1555 }
1556 }
1557 }
1558 }
1559 return NS_NewArrayEnumerator(result, dirs, NS_GET_IID(nsIFile));
1560 }
1561 return NS_ERROR_FAILURE;
1562 }
1563