Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* vim: set ts=8 sts=4 et sw=4 tw=80:
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include <stdlib.h>
7 #include <errno.h>
8 #ifdef HAVE_IO_H
9 #include <io.h> /* for isatty() */
10 #endif
11 #ifdef HAVE_UNISTD_H
12 #include <unistd.h> /* for isatty() */
13 #endif
15 #include "base/basictypes.h"
17 #include "jsapi.h"
18 #include "js/OldDebugAPI.h"
20 #include "xpcpublic.h"
22 #include "XPCShellEnvironment.h"
24 #include "mozilla/XPCOM.h"
26 #include "nsIChannel.h"
27 #include "nsIClassInfo.h"
28 #include "nsIDirectoryService.h"
29 #include "nsIJSRuntimeService.h"
30 #include "nsIPrincipal.h"
31 #include "nsIScriptSecurityManager.h"
32 #include "nsIURI.h"
33 #include "nsIXPConnect.h"
34 #include "nsIXPCScriptable.h"
36 #include "nsCxPusher.h"
37 #include "nsJSUtils.h"
38 #include "nsJSPrincipals.h"
39 #include "nsThreadUtils.h"
40 #include "nsXULAppAPI.h"
42 #include "BackstagePass.h"
44 #include "TestShellChild.h"
45 #include "TestShellParent.h"
47 using mozilla::ipc::XPCShellEnvironment;
48 using mozilla::ipc::TestShellChild;
49 using mozilla::ipc::TestShellParent;
50 using mozilla::AutoSafeJSContext;
51 using namespace JS;
53 namespace {
55 static const char kDefaultRuntimeScriptFilename[] = "xpcshell.js";
57 class XPCShellDirProvider : public nsIDirectoryServiceProvider
58 {
59 public:
60 NS_DECL_ISUPPORTS
61 NS_DECL_NSIDIRECTORYSERVICEPROVIDER
63 XPCShellDirProvider() { }
64 ~XPCShellDirProvider() { }
66 bool SetGREDir(const char *dir);
67 void ClearGREDir() { mGREDir = nullptr; }
69 private:
70 nsCOMPtr<nsIFile> mGREDir;
71 };
73 inline XPCShellEnvironment*
74 Environment(Handle<JSObject*> global)
75 {
76 AutoSafeJSContext cx;
77 JSAutoCompartment ac(cx, global);
78 Rooted<Value> v(cx);
79 if (!JS_GetProperty(cx, global, "__XPCShellEnvironment", &v) ||
80 !v.get().isDouble())
81 {
82 return nullptr;
83 }
84 return static_cast<XPCShellEnvironment*>(v.get().toPrivate());
85 }
87 static bool
88 Print(JSContext *cx, unsigned argc, JS::Value *vp)
89 {
90 JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
92 for (unsigned i = 0; i < args.length(); i++) {
93 JSString *str = JS::ToString(cx, args[i]);
94 if (!str)
95 return false;
96 JSAutoByteString bytes(cx, str);
97 if (!bytes)
98 return false;
99 fprintf(stdout, "%s%s", i ? " " : "", bytes.ptr());
100 fflush(stdout);
101 }
102 fputc('\n', stdout);
103 args.rval().setUndefined();
104 return true;
105 }
107 static bool
108 GetLine(char *bufp,
109 FILE *file,
110 const char *prompt)
111 {
112 char line[256];
113 fputs(prompt, stdout);
114 fflush(stdout);
115 if (!fgets(line, sizeof line, file))
116 return false;
117 strcpy(bufp, line);
118 return true;
119 }
121 static bool
122 Dump(JSContext *cx, unsigned argc, JS::Value *vp)
123 {
124 JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
126 if (!args.length())
127 return true;
129 JSString *str = JS::ToString(cx, args[0]);
130 if (!str)
131 return false;
132 JSAutoByteString bytes(cx, str);
133 if (!bytes)
134 return false;
136 fputs(bytes.ptr(), stdout);
137 fflush(stdout);
138 return true;
139 }
141 static bool
142 Load(JSContext *cx,
143 unsigned argc,
144 JS::Value *vp)
145 {
146 JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
148 JS::Rooted<JSObject*> obj(cx, JS_THIS_OBJECT(cx, vp));
149 if (!obj)
150 return false;
152 for (unsigned i = 0; i < args.length(); i++) {
153 JS::Rooted<JSString*> str(cx, JS::ToString(cx, args[i]));
154 if (!str)
155 return false;
156 JSAutoByteString filename(cx, str);
157 if (!filename)
158 return false;
159 FILE *file = fopen(filename.ptr(), "r");
160 if (!file) {
161 JS_ReportError(cx, "cannot open file '%s' for reading", filename.ptr());
162 return false;
163 }
164 Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
165 JS::CompileOptions options(cx);
166 options.setUTF8(true)
167 .setFileAndLine(filename.ptr(), 1);
168 JS::Rooted<JSScript*> script(cx, JS::Compile(cx, obj, options, file));
169 fclose(file);
170 if (!script)
171 return false;
173 if (!JS_ExecuteScript(cx, obj, script)) {
174 return false;
175 }
176 }
177 args.rval().setUndefined();
178 return true;
179 }
181 static bool
182 Version(JSContext *cx,
183 unsigned argc,
184 JS::Value *vp)
185 {
186 JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
187 args.rval().setInt32(JS_GetVersion(cx));
188 if (args.get(0).isInt32())
189 JS_SetVersionForCompartment(js::GetContextCompartment(cx),
190 JSVersion(args[0].toInt32()));
191 return true;
192 }
194 static bool
195 BuildDate(JSContext *cx, unsigned argc, JS::Value *vp)
196 {
197 JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
198 fprintf(stdout, "built on %s at %s\n", __DATE__, __TIME__);
199 args.rval().setUndefined();
200 return true;
201 }
203 static bool
204 Quit(JSContext *cx,
205 unsigned argc,
206 JS::Value *vp)
207 {
208 Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
209 XPCShellEnvironment* env = Environment(global);
210 env->SetIsQuitting();
212 return false;
213 }
215 static bool
216 DumpXPC(JSContext *cx,
217 unsigned argc,
218 JS::Value *vp)
219 {
220 JS::CallArgs args = CallArgsFromVp(argc, vp);
222 uint16_t depth = 2;
223 if (args.length() > 0) {
224 if (!JS::ToUint16(cx, args[0], &depth))
225 return false;
226 }
228 nsCOMPtr<nsIXPConnect> xpc = do_GetService(nsIXPConnect::GetCID());
229 if (xpc)
230 xpc->DebugDump(int16_t(depth));
231 args.rval().setUndefined();
232 return true;
233 }
235 static bool
236 GC(JSContext *cx,
237 unsigned argc,
238 JS::Value *vp)
239 {
240 JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
242 JSRuntime *rt = JS_GetRuntime(cx);
243 JS_GC(rt);
244 #ifdef JS_GCMETER
245 js_DumpGCStats(rt, stdout);
246 #endif
247 args.rval().setUndefined();
248 return true;
249 }
251 #ifdef JS_GC_ZEAL
252 static bool
253 GCZeal(JSContext *cx, unsigned argc, JS::Value *vp)
254 {
255 CallArgs args = CallArgsFromVp(argc, vp);
257 uint32_t zeal;
258 if (!ToUint32(cx, args.get(0), &zeal))
259 return false;
261 JS_SetGCZeal(cx, uint8_t(zeal), JS_DEFAULT_ZEAL_FREQ);
262 return true;
263 }
264 #endif
266 const JSFunctionSpec gGlobalFunctions[] =
267 {
268 JS_FS("print", Print, 0,0),
269 JS_FS("load", Load, 1,0),
270 JS_FS("quit", Quit, 0,0),
271 JS_FS("version", Version, 1,0),
272 JS_FS("build", BuildDate, 0,0),
273 JS_FS("dumpXPC", DumpXPC, 1,0),
274 JS_FS("dump", Dump, 1,0),
275 JS_FS("gc", GC, 0,0),
276 #ifdef JS_GC_ZEAL
277 JS_FS("gczeal", GCZeal, 1,0),
278 #endif
279 JS_FS_END
280 };
282 typedef enum JSShellErrNum
283 {
284 #define MSG_DEF(name, number, count, exception, format) \
285 name = number,
286 #include "jsshell.msg"
287 #undef MSG_DEF
288 JSShellErr_Limit
289 #undef MSGDEF
290 } JSShellErrNum;
292 } /* anonymous namespace */
294 void
295 XPCShellEnvironment::ProcessFile(JSContext *cx,
296 JS::Handle<JSObject*> obj,
297 const char *filename,
298 FILE *file,
299 bool forceTTY)
300 {
301 XPCShellEnvironment* env = this;
303 JS::Rooted<JS::Value> result(cx);
304 int lineno, startline;
305 bool ok, hitEOF;
306 char *bufp, buffer[4096];
307 JSString *str;
309 if (forceTTY) {
310 file = stdin;
311 }
312 else if (!isatty(fileno(file)))
313 {
314 /*
315 * It's not interactive - just execute it.
316 *
317 * Support the UNIX #! shell hack; gobble the first line if it starts
318 * with '#'. TODO - this isn't quite compatible with sharp variables,
319 * as a legal js program (using sharp variables) might start with '#'.
320 * But that would require multi-character lookahead.
321 */
322 int ch = fgetc(file);
323 if (ch == '#') {
324 while((ch = fgetc(file)) != EOF) {
325 if(ch == '\n' || ch == '\r')
326 break;
327 }
328 }
329 ungetc(ch, file);
331 JSAutoRequest ar(cx);
332 JSAutoCompartment ac(cx, obj);
334 JS::CompileOptions options(cx);
335 options.setUTF8(true)
336 .setFileAndLine(filename, 1);
337 JS::Rooted<JSScript*> script(cx, JS::Compile(cx, obj, options, file));
338 if (script)
339 (void)JS_ExecuteScript(cx, obj, script, &result);
341 return;
342 }
344 /* It's an interactive filehandle; drop into read-eval-print loop. */
345 lineno = 1;
346 hitEOF = false;
347 do {
348 bufp = buffer;
349 *bufp = '\0';
351 JSAutoRequest ar(cx);
352 JSAutoCompartment ac(cx, obj);
354 /*
355 * Accumulate lines until we get a 'compilable unit' - one that either
356 * generates an error (before running out of source) or that compiles
357 * cleanly. This should be whenever we get a complete statement that
358 * coincides with the end of a line.
359 */
360 startline = lineno;
361 do {
362 if (!GetLine(bufp, file, startline == lineno ? "js> " : "")) {
363 hitEOF = true;
364 break;
365 }
366 bufp += strlen(bufp);
367 lineno++;
368 } while (!JS_BufferIsCompilableUnit(cx, obj, buffer, strlen(buffer)));
370 /* Clear any pending exception from previous failed compiles. */
371 JS_ClearPendingException(cx);
372 JS::CompileOptions options(cx);
373 options.setFileAndLine("typein", startline);
374 JS::Rooted<JSScript*> script(cx,
375 JS_CompileScript(cx, obj, buffer, strlen(buffer), options));
376 if (script) {
377 JSErrorReporter older;
379 ok = JS_ExecuteScript(cx, obj, script, &result);
380 if (ok && result != JSVAL_VOID) {
381 /* Suppress error reports from JS::ToString(). */
382 older = JS_SetErrorReporter(cx, nullptr);
383 str = JS::ToString(cx, result);
384 JSAutoByteString bytes;
385 if (str)
386 bytes.encodeLatin1(cx, str);
387 JS_SetErrorReporter(cx, older);
389 if (!!bytes)
390 fprintf(stdout, "%s\n", bytes.ptr());
391 else
392 ok = false;
393 }
394 }
395 } while (!hitEOF && !env->IsQuitting());
397 fprintf(stdout, "\n");
398 }
400 NS_IMETHODIMP_(MozExternalRefCountType)
401 XPCShellDirProvider::AddRef()
402 {
403 return 2;
404 }
406 NS_IMETHODIMP_(MozExternalRefCountType)
407 XPCShellDirProvider::Release()
408 {
409 return 1;
410 }
412 NS_IMPL_QUERY_INTERFACE(XPCShellDirProvider, nsIDirectoryServiceProvider)
414 bool
415 XPCShellDirProvider::SetGREDir(const char *dir)
416 {
417 nsresult rv = XRE_GetFileFromPath(dir, getter_AddRefs(mGREDir));
418 return NS_SUCCEEDED(rv);
419 }
421 NS_IMETHODIMP
422 XPCShellDirProvider::GetFile(const char *prop,
423 bool *persistent,
424 nsIFile* *result)
425 {
426 if (mGREDir && !strcmp(prop, NS_GRE_DIR)) {
427 *persistent = true;
428 NS_ADDREF(*result = mGREDir);
429 return NS_OK;
430 }
432 return NS_ERROR_FAILURE;
433 }
435 // static
436 XPCShellEnvironment*
437 XPCShellEnvironment::CreateEnvironment()
438 {
439 XPCShellEnvironment* env = new XPCShellEnvironment();
440 if (env && !env->Init()) {
441 delete env;
442 env = nullptr;
443 }
444 return env;
445 }
447 XPCShellEnvironment::XPCShellEnvironment()
448 : mQuitting(false)
449 {
450 }
452 XPCShellEnvironment::~XPCShellEnvironment()
453 {
455 AutoSafeJSContext cx;
456 Rooted<JSObject*> global(cx, GetGlobalObject());
457 if (global) {
458 {
459 JSAutoCompartment ac(cx, global);
460 JS_SetAllNonReservedSlotsToUndefined(cx, global);
461 }
462 mGlobalHolder.Release();
464 JSRuntime *rt = JS_GetRuntime(cx);
465 JS_GC(rt);
466 }
467 }
469 bool
470 XPCShellEnvironment::Init()
471 {
472 nsresult rv;
474 // unbuffer stdout so that output is in the correct order; note that stderr
475 // is unbuffered by default
476 setbuf(stdout, 0);
478 nsCOMPtr<nsIJSRuntimeService> rtsvc =
479 do_GetService("@mozilla.org/js/xpc/RuntimeService;1");
480 if (!rtsvc) {
481 NS_ERROR("failed to get nsJSRuntimeService!");
482 return false;
483 }
485 JSRuntime *rt;
486 if (NS_FAILED(rtsvc->GetRuntime(&rt)) || !rt) {
487 NS_ERROR("failed to get JSRuntime from nsJSRuntimeService!");
488 return false;
489 }
491 if (!mGlobalHolder.Hold(rt)) {
492 NS_ERROR("Can't protect global object!");
493 return false;
494 }
496 AutoSafeJSContext cx;
498 JS_SetContextPrivate(cx, this);
500 nsCOMPtr<nsIXPConnect> xpc =
501 do_GetService(nsIXPConnect::GetCID());
502 if (!xpc) {
503 NS_ERROR("failed to get nsXPConnect service!");
504 return false;
505 }
507 nsCOMPtr<nsIPrincipal> principal;
508 nsCOMPtr<nsIScriptSecurityManager> securityManager =
509 do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
510 if (NS_SUCCEEDED(rv) && securityManager) {
511 rv = securityManager->GetSystemPrincipal(getter_AddRefs(principal));
512 if (NS_FAILED(rv)) {
513 fprintf(stderr, "+++ Failed to obtain SystemPrincipal from ScriptSecurityManager service.\n");
514 }
515 } else {
516 fprintf(stderr, "+++ Failed to get ScriptSecurityManager service, running without principals");
517 }
519 nsRefPtr<BackstagePass> backstagePass;
520 rv = NS_NewBackstagePass(getter_AddRefs(backstagePass));
521 if (NS_FAILED(rv)) {
522 NS_ERROR("Failed to create backstage pass!");
523 return false;
524 }
526 JS::CompartmentOptions options;
527 options.setZone(JS::SystemZone)
528 .setVersion(JSVERSION_LATEST);
529 nsCOMPtr<nsIXPConnectJSObjectHolder> holder;
530 rv = xpc->InitClassesWithNewWrappedGlobal(cx,
531 static_cast<nsIGlobalObject *>(backstagePass),
532 principal, 0,
533 options,
534 getter_AddRefs(holder));
535 if (NS_FAILED(rv)) {
536 NS_ERROR("InitClassesWithNewWrappedGlobal failed!");
537 return false;
538 }
540 JS::Rooted<JSObject*> globalObj(cx, holder->GetJSObject());
541 if (!globalObj) {
542 NS_ERROR("Failed to get global JSObject!");
543 return false;
544 }
545 JSAutoCompartment ac(cx, globalObj);
547 backstagePass->SetGlobalObject(globalObj);
549 JS::Rooted<Value> privateVal(cx, PrivateValue(this));
550 if (!JS_DefineProperty(cx, globalObj, "__XPCShellEnvironment",
551 privateVal, JSPROP_READONLY | JSPROP_PERMANENT,
552 JS_PropertyStub, JS_StrictPropertyStub) ||
553 !JS_DefineFunctions(cx, globalObj, gGlobalFunctions) ||
554 !JS_DefineProfilingFunctions(cx, globalObj))
555 {
556 NS_ERROR("JS_DefineFunctions failed!");
557 return false;
558 }
560 mGlobalHolder = globalObj;
562 FILE* runtimeScriptFile = fopen(kDefaultRuntimeScriptFilename, "r");
563 if (runtimeScriptFile) {
564 fprintf(stdout, "[loading '%s'...]\n", kDefaultRuntimeScriptFilename);
565 ProcessFile(cx, globalObj, kDefaultRuntimeScriptFilename,
566 runtimeScriptFile, false);
567 fclose(runtimeScriptFile);
568 }
570 return true;
571 }
573 bool
574 XPCShellEnvironment::EvaluateString(const nsString& aString,
575 nsString* aResult)
576 {
577 AutoSafeJSContext cx;
578 JS::Rooted<JSObject*> global(cx, GetGlobalObject());
579 JSAutoCompartment ac(cx, global);
581 JS::CompileOptions options(cx);
582 options.setFileAndLine("typein", 0);
583 JS::Rooted<JSScript*> script(cx, JS_CompileUCScript(cx, global, aString.get(),
584 aString.Length(), options));
585 if (!script) {
586 return false;
587 }
589 if (aResult) {
590 aResult->Truncate();
591 }
593 JS::Rooted<JS::Value> result(cx);
594 bool ok = JS_ExecuteScript(cx, global, script, &result);
595 if (ok && result != JSVAL_VOID) {
596 JSErrorReporter old = JS_SetErrorReporter(cx, nullptr);
597 JSString* str = JS::ToString(cx, result);
598 nsDependentJSString depStr;
599 if (str)
600 depStr.init(cx, str);
601 JS_SetErrorReporter(cx, old);
603 if (!depStr.IsEmpty() && aResult) {
604 aResult->Assign(depStr);
605 }
606 }
608 return true;
609 }