[Author: zork]
[google-gears.git] / gears / base / common / js_runner_ff.cc
blob05afe34916874f8c48d9b12f4359f4911b35bbe8
1 // Copyright 2007, Google Inc.
2 //
3 // Redistribution and use in source and binary forms, with or without
4 // modification, are permitted provided that the following conditions are met:
5 //
6 // 1. Redistributions of source code must retain the above copyright notice,
7 // this list of conditions and the following disclaimer.
8 // 2. Redistributions in binary form must reproduce the above copyright notice,
9 // this list of conditions and the following disclaimer in the documentation
10 // and/or other materials provided with the distribution.
11 // 3. Neither the name of Google Inc. nor the names of its contributors may be
12 // used to endorse or promote products derived from this software without
13 // specific prior written permission.
15 // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
16 // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
17 // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
18 // EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
21 // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22 // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23 // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
24 // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 #include <assert.h>
27 #include <map>
28 #include <set>
29 #include <gecko_sdk/include/nspr.h> // for PR_*
30 #include <gecko_sdk/include/nsCOMPtr.h>
31 #include <gecko_internal/jsapi.h>
32 #include <gecko_internal/nsIJSContextStack.h>
33 #include <gecko_internal/nsIPrincipal.h>
34 #include <gecko_internal/nsIScriptContext.h>
35 #include <gecko_internal/nsIScriptGlobalObject.h>
36 #include <gecko_internal/nsIScriptObjectPrincipal.h>
37 #include <gecko_internal/nsITimer.h>
38 #include <gecko_internal/nsITimerInternal.h>
40 #include "gears/base/common/js_runner.h"
42 #include "ff/genfiles/console.h"
43 #include "ff/genfiles/database.h"
44 #include "ff/genfiles/desktop_ff.h"
45 #include "ff/genfiles/httprequest.h"
47 #ifdef OFFICIAL_BUILD
48 // The Image and Blgo APIs have not been finalized for official builds
49 #else
50 #include "ff/genfiles/blob_ff.h"
51 #include "ff/genfiles/image.h"
52 #endif
54 #include "ff/genfiles/localserver.h"
55 #include "ff/genfiles/timer_ff.h"
56 #include "ff/genfiles/workerpool.h"
57 #include "gears/base/common/common.h" // for DISALLOW_EVIL_CONSTRUCTORS
58 #include "gears/base/common/exception_handler_win32.h"
59 #include "gears/base/common/html_event_monitor.h"
60 #include "gears/base/common/js_runner_ff_marshaling.h"
61 #include "gears/base/common/scoped_token.h"
62 #include "gears/base/common/string_utils.h"
63 #include "gears/base/firefox/dom_utils.h"
64 #include "gears/factory/firefox/factory.h"
66 static const int kGarbageCollectionIntervalMsec = 2000;
68 // Internal base class used to share some code between DocumentJsRunner and
69 // JsRunner. Do not override these methods from JsRunner or DocumentJsRunner.
70 // Either share the code here, or move it to those two classes if it's
71 // different.
72 class JsRunnerBase : public JsRunnerInterface {
73 public:
74 JsRunnerBase() : alloc_js_wrapper_(NULL), js_engine_context_(NULL) {}
76 JsContextPtr GetContext() {
77 return js_engine_context_;
80 JsContextWrapperPtr GetContextWrapper() {
81 assert(alloc_js_wrapper_);
82 return alloc_js_wrapper_;
85 JsObject *NewObject(const char16 *optional_global_ctor_name,
86 bool dump_on_error = false) {
87 if (!js_engine_context_) {
88 if (dump_on_error) ExceptionManager::CaptureAndSendMinidump();
89 LOG(("Could not get JavaScript engine context."));
90 return NULL;
93 JSObject *global_object = JS_GetGlobalObject(js_engine_context_);
94 if (!global_object) {
95 if (dump_on_error) ExceptionManager::CaptureAndSendMinidump();
96 LOG(("Could not get global object from script engine."));
97 return NULL;
100 std::string ctor_name_utf8;
101 if (optional_global_ctor_name) {
102 if (!String16ToUTF8(optional_global_ctor_name, &ctor_name_utf8)) {
103 if (dump_on_error) ExceptionManager::CaptureAndSendMinidump();
104 LOG(("Could not convert constructor name."));
105 return NULL;
107 } else {
108 ctor_name_utf8 = "Object";
111 jsval val = INT_TO_JSVAL(0);
112 JSBool result = JS_GetProperty(js_engine_context_, global_object,
113 ctor_name_utf8.c_str(), &val);
114 if (!result) {
115 if (dump_on_error) ExceptionManager::CaptureAndSendMinidump();
116 LOG(("Could not get constructor property from global object."));
117 return NULL;
120 JSFunction *ctor = JS_ValueToFunction(js_engine_context_, val);
121 if (!ctor) {
122 if (dump_on_error) ExceptionManager::CaptureAndSendMinidump();
123 LOG(("Could not convert constructor property to function."));
124 return NULL;
127 // NOTE: We are calling the specified function here as a regular function,
128 // not as a constructor. I could not find a way to call a function as a
129 // constructor using JSAPI other than JS_ConstructObject which takes
130 // arguments I don't know how to provide. Ideally, there would be something
131 // like DISPATCH_CONSTRUCT in IE.
133 // This is OK for the built-in constructors that we want to call (such as
134 // "Error", "Object", etc) because those objects are specified to behave as
135 // constructors even without the 'new' keyword.
137 // For more information, see:
138 // * ECMAScript spec section 15.2.1, 15.3.1, 15.4.1, etc.
139 // * DISPATCH_CONSTRUCT:
140 // http://msdn2.microsoft.com/en-us/library/asd22sd4.aspx
141 result = JS_CallFunction(js_engine_context_, global_object, ctor, 0, NULL,
142 &val);
143 if (!result) {
144 if (dump_on_error) ExceptionManager::CaptureAndSendMinidump();
145 LOG(("Could not call constructor function."));
146 return NULL;
149 if (JSVAL_IS_OBJECT(val)) {
150 scoped_ptr<JsObject> retval(new JsObject);
152 if (!retval->SetObject(val, GetContext())) {
153 if (dump_on_error) ExceptionManager::CaptureAndSendMinidump();
154 LOG(("Could not assign to JsObject."));
155 return NULL;
157 return retval.release();
158 } else {
159 if (dump_on_error) ExceptionManager::CaptureAndSendMinidump();
160 LOG(("Constructor did not return an object"));
161 return NULL;
165 JsArray* NewArray() {
166 JSObject* array_object = JS_NewArrayObject(GetContext(), 0, NULL);
167 if (!array_object)
168 return NULL;
170 scoped_ptr<JsArray> js_array(new JsArray());
171 if (!js_array.get())
172 return NULL;
174 jsval array = OBJECT_TO_JSVAL(array_object);
175 if (!js_array->SetArray(array, GetContext()))
176 return NULL;
178 return js_array.release();
181 virtual bool InvokeCallbackSpecialized(
182 const JsRootedCallback *callback, int argc, jsval *argv,
183 JsRootedToken **optional_alloc_retval) = 0;
185 bool InvokeCallback(const JsRootedCallback *callback,
186 int argc, JsParamToSend *argv,
187 JsRootedToken **optional_alloc_retval) {
188 assert(callback && (!argc || argv));
190 if (JsTokenIsNullOrUndefined(callback->token())) { return false; }
192 // Setup argument array.
193 scoped_array<jsval> js_engine_argv(new jsval[argc]);
194 for (int i = 0; i < argc; ++i)
195 ConvertJsParamToToken(argv[i], callback->context(), &js_engine_argv[i]);
197 // Invoke the method.
198 return InvokeCallbackSpecialized(callback, argc, js_engine_argv.get(),
199 optional_alloc_retval);
202 // Add the provided handler to the notification list for the specified event.
203 virtual bool AddEventHandler(JsEventType event_type,
204 JsEventHandlerInterface *handler) {
205 assert(event_type >= 0 && event_type < MAX_JSEVENTS);
207 event_handlers_[event_type].insert(handler);
208 return true;
211 // Remove the provided handler from the notification list for the specified
212 // event.
213 virtual bool RemoveEventHandler(JsEventType event_type,
214 JsEventHandlerInterface *handler) {
215 assert(event_type >= 0 && event_type < MAX_JSEVENTS);
217 event_handlers_[event_type].erase(handler);
218 return true;
221 #ifdef DEBUG
222 void ForceGC() {
223 if (js_engine_context_) {
224 JS_GC(js_engine_context_);
227 #endif
229 protected:
230 // Alert all monitors that an event has occured.
231 void SendEvent(JsEventType event_type) {
232 assert(event_type >= 0 && event_type < MAX_JSEVENTS);
234 // Make a copy of the list of listeners, in case they change during the
235 // alert phase.
236 std::vector<JsEventHandlerInterface *> monitors;
237 monitors.insert(monitors.end(),
238 event_handlers_[event_type].begin(),
239 event_handlers_[event_type].end());
241 std::vector<JsEventHandlerInterface *>::iterator monitor;
242 for (monitor = monitors.begin();
243 monitor != monitors.end();
244 ++monitor) {
245 // Check that the listener hasn't been removed. This can occur if a
246 // listener removes another listener from the list.
247 if (event_handlers_[event_type].find(*monitor) !=
248 event_handlers_[event_type].end()) {
249 (*monitor)->HandleEvent(event_type);
254 // Not using scoped_ptr even though it is possible because the cleanup code
255 // for JsRunner is tricky and would rather be explicit about the order things
256 // get torn down.
257 JsContextWrapper *alloc_js_wrapper_;
258 JSContext *js_engine_context_;
260 private:
261 std::set<JsEventHandlerInterface *> event_handlers_[MAX_JSEVENTS];
263 DISALLOW_EVIL_CONSTRUCTORS(JsRunnerBase);
267 class JsRunner : public JsRunnerBase {
268 public:
269 JsRunner() : error_handler_(NULL), global_obj_(NULL), js_runtime_(NULL),
270 js_script_(NULL) {
271 // TODO(aa): Consider moving initialization of JsRunners out since there is
272 // no way to detect errors in ctors.
273 if (!InitJavaScriptEngine())
274 return;
276 // This creates a timer to run the garbage collector on a repeating
277 // interval, which is what Firefox does in
278 // source/dom/src/base/nsJSEnvironment.cpp.
279 nsresult result;
280 gc_timer_ = do_CreateInstance("@mozilla.org/timer;1", &result);
282 if (NS_SUCCEEDED(result)) {
283 // Turning off idle causes the callback to be invoked in this thread,
284 // instead of in the Timer idle thread.
285 nsCOMPtr<nsITimerInternal> timer_internal(do_QueryInterface(gc_timer_));
286 timer_internal->SetIdle(false);
288 // Start the timer
289 gc_timer_->InitWithFuncCallback(GarbageCollectionCallback,
290 js_engine_context_,
291 kGarbageCollectionIntervalMsec,
292 nsITimer::TYPE_REPEATING_SLACK);
296 ~JsRunner();
298 bool AddGlobal(const std::string16 &name, IGeneric *object, gIID iface_id);
299 bool Start(const std::string16 &full_script);
300 bool Stop();
301 bool Eval(const std::string16 &full_script);
302 void SetErrorHandler(JsErrorHandlerInterface *handler) {
303 error_handler_ = handler;
305 bool InvokeCallbackSpecialized(const JsRootedCallback *callback,
306 int argc, jsval *argv,
307 JsRootedCallback **optional_alloc_retval);
309 private:
310 bool InitJavaScriptEngine();
311 bool GetProtoFromIID(const nsIID iface_id, JSObject **proto);
313 static void GarbageCollectionCallback(nsITimer *timer, void *context);
314 static void JS_DLL_CALLBACK JsErrorHandler(JSContext *cx, const char *message,
315 JSErrorReport *report);
317 JsErrorHandlerInterface *error_handler_;
318 JSObject *global_obj_;
319 IIDToProtoMap proto_interfaces_;
320 std::vector<IGeneric *> globals_;
321 JSRuntime *js_runtime_;
322 JSScript *js_script_;
323 scoped_ptr<JsRootedToken> js_script_root_;
324 nsCOMPtr<nsITimer> gc_timer_;
326 DISALLOW_EVIL_CONSTRUCTORS(JsRunner);
329 void JsRunner::GarbageCollectionCallback(nsITimer *timer,
330 void *context) {
331 JSContext *cx = reinterpret_cast<JSContext *>(context);
332 JS_GC(cx);
335 void JS_DLL_CALLBACK JsRunner::JsErrorHandler(JSContext *cx,
336 const char *message,
337 JSErrorReport *report) {
338 JsRunner *js_runner = static_cast<JsRunner*>(JS_GetContextPrivate(cx));
339 if (js_runner && js_runner->error_handler_ && report) {
340 JsErrorInfo error_info;
341 error_info.line = report->lineno + 1; // Reported lines start at zero.
343 // The error message can either be in the separate *message param or in
344 // *report->ucmessage. For example, running the following JS in a worker
345 // causes the separate message param to get used:
346 // throw new Error("foo")
347 // Other errors cause the report->ucmessage property to get used.
349 // Mozilla also does this, see:
350 // http://lxr.mozilla.org/mozilla1.8.0/source/dom/src/base/nsJSEnvironment.cpp#163
351 if (report->ucmessage) {
352 error_info.message = reinterpret_cast<const char16 *>(report->ucmessage);
353 } else if (message) {
354 std::string16 message_str;
355 if (UTF8ToString16(message, &message_str)) {
356 error_info.message = message_str;
360 js_runner->error_handler_->HandleError(error_info);
364 JsRunner::~JsRunner() {
365 // Alert modules that the engine is unloading.
366 SendEvent(JSEVENT_UNLOAD);
368 if (gc_timer_) {
369 // Stop garbage collection now.
370 gc_timer_->Cancel();
371 gc_timer_ = NULL;
374 // We need to remove the roots now, because they will be referencing an
375 // invalid context if we wait for the destructor.
376 if (alloc_js_wrapper_)
377 alloc_js_wrapper_->CleanupRoots();
379 std::vector<IGeneric *>::iterator global;
380 for (global = globals_.begin(); global != globals_.end(); ++global) {
381 NS_RELEASE(*global);
384 // Reset the scoped_ptr to unroot the script. This needs to be done before
385 // we destroy the context and runtime, so we can't wait for the destructor.
386 js_script_root_.reset(NULL);
388 if (js_engine_context_) {
389 JS_DestroyContext(js_engine_context_);
391 if (js_runtime_) {
392 JS_DestroyRuntime(js_runtime_);
395 // This has to occur after the context and runtime have been destroyed,
396 // because it maintains data structures that the JS engine requires.
397 // Specifically, any of the JSObjects stored in the JsWrapperData and the
398 // global_boj_ need to exist as long as the object in the JS Engine which
399 // they are linked to.
400 delete alloc_js_wrapper_;
403 bool JsRunner::GetProtoFromIID(const nsIID iface_id, JSObject **proto) {
404 IIDToProtoMap::iterator proto_interface =
405 proto_interfaces_.find(iface_id);
406 if (proto_interface != proto_interfaces_.end()) {
407 *proto = proto_interface->second;
408 return true;
411 proto_interfaces_[iface_id] = NULL;
412 proto_interface = proto_interfaces_.find(iface_id);
414 // passing NULL for class_id and class_name prevents child workers
415 // from using "new CLASSNAME"
416 if (alloc_js_wrapper_->DefineClass(&iface_id,
417 NULL, // class_id
418 NULL, // class_name
419 &(proto_interface->second))) {
420 *proto = proto_interface->second;
421 return true;
422 } else {
423 proto_interfaces_.erase(iface_id);
424 return false;
428 class JS_DestroyContextFunctor {
429 public:
430 inline void operator()(JSContext* x) const {
431 if (x != NULL) { JS_DestroyContext(x); }
434 typedef scoped_token<JSContext*, JS_DestroyContextFunctor> scoped_jscontext_ptr;
436 bool JsRunner::InitJavaScriptEngine() {
437 JSBool js_ok;
438 bool succeeded;
440 // To cleanup after failures we use scoped objects to manage everything that
441 // should be destroyed. On success we take ownership to avoid cleanup.
443 // These structs are static because they must live for duration of JS engine.
444 // SpiderMonkey README also suggests using static for one-off objects.
445 static JSClass global_class = {
446 "Global", 0, // name, flags
447 JS_PropertyStub, JS_PropertyStub, // defineProperty, deleteProperty
448 JS_PropertyStub, JS_PropertyStub, // getProperty, setProperty
449 JS_EnumerateStub, JS_ResolveStub, // enum, resolve
450 JS_ConvertStub, JS_FinalizeStub // convert, finalize
455 // Instantiate a JavaScript engine
458 // Create a new runtime. If we instead use xpc/RuntimeService to get a
459 // runtime, strange things break (like eval).
460 const int kRuntimeMaxBytes = 64 * 1024 * 1024; // mozilla/.../js.c uses 64 MB
461 js_runtime_ = JS_NewRuntime(kRuntimeMaxBytes);
462 if (!js_runtime_) {
463 ExceptionManager::CaptureAndSendMinidump();
464 LOG(("Maximum thread count reached."));
465 return false;
468 const int kContextStackChunkSize = 1024; // Firefox often uses 1024;
469 // also see js/src/readme.html
471 scoped_jscontext_ptr cx(JS_NewContext(js_runtime_, kContextStackChunkSize));
472 if (!cx.get()) { return false; }
473 // VAROBJFIX is recommended in /mozilla/js/src/jsapi.h
474 JS_SetOptions(cx.get(), JS_GetOptions(cx.get()) | JSOPTION_VAROBJFIX);
476 // JS_SetErrorReporter takes a static callback, so we need
477 // JS_SetContextPrivate to later save the error in a per-worker location
478 JS_SetErrorReporter(cx.get(), JsErrorHandler);
479 JS_SetContextPrivate(cx.get(), static_cast<void*>(this));
480 #ifdef DEBUG
481 // must set this here to allow workerPool.forceGC() during child init
482 js_engine_context_ = cx.get();
483 #endif
485 global_obj_ = JS_NewObject(cx.get(), &global_class, 0, 0);
487 if (!global_obj_) { return false; }
488 js_ok = JS_InitStandardClasses(cx.get(), global_obj_);
489 if (!js_ok) { return false; }
490 // Note: an alternative is to lazily define the "standard classes" (which
491 // include things like eval). To do that, change JS_InitStandardClasses
492 // to JS_SetGlobalObject, and add handlers for Enumerate and Resolve in
493 // global_class. See /mozilla/js/src/js.c for sample code.
496 // Define classes in the JSContext
499 // first need to create a JsWrapperManager for this thread
500 scoped_ptr<JsContextWrapper> js_wrapper(new JsContextWrapper(cx.get(),
501 global_obj_));
503 js_engine_context_ = cx.release();
504 alloc_js_wrapper_ = js_wrapper.release();
506 struct {
507 const nsIID iface_id;
508 JSObject *proto_obj; // gets set by code below
509 } classes[] = {
510 // TODO(cprince): Unify the interface lists here and in GearsFactory.
511 // Could share code, or could query GearsFactory.
512 {GEARSFACTORYINTERFACE_IID, NULL},
513 // workerpool
514 {GEARSWORKERPOOLINTERFACE_IID, NULL},
515 // database
516 {GEARSDATABASEINTERFACE_IID, NULL},
517 {GEARSRESULTSETINTERFACE_IID, NULL},
518 // desktop
519 {GEARSDESKTOPINTERFACE_IID, NULL},
520 // localserver
521 {GEARSLOCALSERVERINTERFACE_IID, NULL},
522 {GEARSMANAGEDRESOURCESTOREINTERFACE_IID, NULL},
523 {GEARSRESOURCESTOREINTERFACE_IID, NULL},
524 // GEARSFILESUBMITTERINTERFACE_IID can never be created in a child worker
525 // timer
526 {GEARSTIMERINTERFACE_IID, NULL},
527 // httprequest
528 {GEARSHTTPREQUESTINTERFACE_IID, NULL},
529 #ifdef OFFICIAL_BUILD
530 // The Image and Blog APIs have not been finalized for official builds
531 #else
532 // blob
533 {GEARSBLOBINTERFACE_IID, NULL},
534 // image
535 {GEARSIMAGEINTERFACE_IID, NULL},
536 #endif
537 // console
538 {GEARSCONSOLEINTERFACE_IID, NULL}
540 const int num_classes = sizeof(classes) / sizeof(classes[0]);
542 for (int i = 0; i < num_classes; ++i) {
543 // passing NULL for class_id and class_name prevents child workers
544 // from using "new CLASSNAME"
545 succeeded = GetProtoFromIID(classes[i].iface_id, &classes[i].proto_obj);
546 if (!succeeded) { return false; }
549 #ifdef DEBUG
550 // Do it here to trigger potential GC bugs in our code.
551 JS_GC(js_engine_context_);
552 #endif
554 return true; // succeeded
557 bool JsRunner::AddGlobal(const std::string16 &name,
558 IGeneric *object,
559 gIID iface_id) {
560 JSObject *proto_object;
561 if (!GetProtoFromIID(iface_id, &proto_object)) {
562 return false;
565 if (!alloc_js_wrapper_->DefineGlobal(proto_object, object, name.c_str())) {
566 return false;
569 globals_.push_back(object);
570 NS_ADDREF(globals_.back());
572 return true; // succeeded
575 bool JsRunner::Start(const std::string16 &full_script) {
577 // Add script code to the engine instance
580 uintN line_number_start = 0;
581 js_script_ = JS_CompileUCScript(
582 js_engine_context_, global_obj_,
583 reinterpret_cast<const jschar *>(full_script.c_str()),
584 full_script.length(),
585 "script", line_number_start);
586 if (!js_script_) { return false; }
588 // we must root any script returned by JS_Compile* (see jsapi.h)
589 JSObject *compiled_script_obj = JS_NewScriptObject(js_engine_context_,
590 js_script_);
591 if (!compiled_script_obj) { return false; }
592 js_script_root_.reset(new JsRootedToken(
593 js_engine_context_,
594 OBJECT_TO_JSVAL(compiled_script_obj)));
598 // Start the engine running
601 jsval return_string;
602 JSBool js_ok = JS_ExecuteScript(js_engine_context_, global_obj_,
603 js_script_, &return_string);
604 if (!js_ok) { return false; }
606 return true;
609 bool JsRunner::Stop() {
610 // TODO(zork): Implement
611 return false;
614 bool JsRunner::Eval(const std::string16 &script) {
615 JSObject *object = JS_GetGlobalObject(js_engine_context_);
617 uintN line_number_start = 0;
618 jsval rval;
619 JSBool js_ok = JS_EvaluateUCScript(
620 js_engine_context_,
621 object,
622 reinterpret_cast<const jschar *>(script.c_str()),
623 script.length(),
624 "script", line_number_start,
625 &rval);
626 if (!js_ok) { return false; }
627 return true;
630 bool JsRunner::InvokeCallbackSpecialized(
631 const JsRootedCallback *callback, int argc, jsval *argv,
632 JsRootedToken **optional_alloc_retval) {
633 jsval retval;
634 JSBool result = JS_CallFunctionValue(
635 callback->context(),
636 JS_GetGlobalObject(callback->context()),
637 callback->token(), argc, argv, &retval);
638 if (result == JS_FALSE) { return false; }
640 if (optional_alloc_retval) {
641 // Note: A valid jsval is returned no matter what the javascript function
642 // returns. If the javascript function returns nothing, or explicitly
643 // returns <undefined>, the the jsval will be JSVAL_IS_VOID. If the
644 // javascript function returns <null>, then the jsval will be JSVAL_IS_NULL.
645 // Always returning a JsRootedToken should allow us to coerce these values
646 // to other types correctly in the future.
647 *optional_alloc_retval = new JsRootedToken(js_engine_context_, retval);
650 return true;
653 // Provides the same interface as JsRunner, but for the normal JavaScript engine
654 // that runs in HTML pages.
655 class DocumentJsRunner : public JsRunnerBase {
656 public:
657 DocumentJsRunner(IGeneric *base, JsContextPtr context) {
658 js_engine_context_ = context;
659 alloc_js_wrapper_ = new JsContextWrapper(context,
660 JS_GetGlobalObject(context));
663 ~DocumentJsRunner() {
664 if (alloc_js_wrapper_)
665 delete alloc_js_wrapper_;
668 bool AddGlobal(const std::string16 &name, IGeneric *object, gIID iface_id) {
669 // TODO(zork): Add this functionality to DocumentJsRunner.
670 return false;
672 void SetErrorHandler(JsErrorHandlerInterface *handler) {
673 assert(false); // This should not be called on DocumentJsRunner.
675 bool Start(const std::string16 &full_script) {
676 assert(false); // This should not be called on DocumentJsRunner.
677 return false;
679 bool Stop() {
680 assert(false); // This should not be called on DocumentJsRunner.
681 return false;
683 bool Eval(const std::string16 &full_script);
684 bool InvokeCallbackSpecialized(const JsRootedCallback *callback,
685 int argc, jsval *argv,
686 JsRootedToken **optional_alloc_retval);
687 bool AddEventHandler(JsEventType event_type,
688 JsEventHandlerInterface *handler);
690 private:
691 static void HandleEventUnload(void *user_param); // Callback for 'onunload'
693 scoped_ptr<HtmlEventMonitor> unload_monitor_; // For 'onunload' notifications
694 DISALLOW_EVIL_CONSTRUCTORS(DocumentJsRunner);
698 bool DocumentJsRunner::Eval(const std::string16 &script) {
699 JSObject *object = JS_GetGlobalObject(js_engine_context_);
700 if (!object) { return false; }
702 // To eval the script, we need the JSPrincipals to be acquired through
703 // nsIPrincipal. nsIPrincipal can be queried through the
704 // nsIScriptObjectPrincipal interface on the Script Global Object. In order
705 // to get the Script Global Object, we need to request the private data
706 // associated with the global JSObject on the current context.
707 nsCOMPtr<nsIScriptGlobalObject> sgo;
708 nsISupports *priv = reinterpret_cast<nsISupports *>(JS_GetPrivate(
709 js_engine_context_,
710 object));
711 nsCOMPtr<nsIXPConnectWrappedNative> wrapped_native = do_QueryInterface(priv);
713 if (wrapped_native) {
714 // The global object is a XPConnect wrapped native, the native in
715 // the wrapper might be the nsIScriptGlobalObject.
716 sgo = do_QueryWrappedNative(wrapped_native);
717 } else {
718 sgo = do_QueryInterface(priv);
721 JSPrincipals *jsprin;
722 nsresult nr;
724 nsCOMPtr<nsIScriptObjectPrincipal> obj_prin = do_QueryInterface(sgo, &nr);
725 if (NS_FAILED(nr)) { return false; }
727 nsIPrincipal *principal = obj_prin->GetPrincipal();
728 if (!principal) { return false; }
730 principal->GetJSPrincipals(js_engine_context_, &jsprin);
732 // Set up the JS stack so that our context is on top. This is needed to
733 // play nicely with plugins that access the context stack, such as Firebug.
734 nsCOMPtr<nsIJSContextStack> stack =
735 do_GetService("@mozilla.org/js/xpc/ContextStack;1");
736 if (!stack) { return false; }
738 stack->Push(js_engine_context_);
740 uintN line_number_start = 0;
741 jsval rval;
742 JSBool js_ok = JS_EvaluateUCScriptForPrincipals(
743 js_engine_context_, object, jsprin,
744 reinterpret_cast<const jschar *>(script.c_str()),
745 script.length(), "script", line_number_start, &rval);
747 // Restore the context stack.
748 JSContext *cx;
749 stack->Pop(&cx);
751 // Decrements ref count on jsprin (Was added in GetJSPrincipals()).
752 (void)JSPRINCIPALS_DROP(js_engine_context_, jsprin);
753 if (!js_ok) { return false; }
754 return true;
757 bool DocumentJsRunner::AddEventHandler(JsEventType event_type,
758 JsEventHandlerInterface *handler) {
759 if (event_type == JSEVENT_UNLOAD) {
760 // Monitor 'onunload' to send the unload event when the page goes away.
761 if (unload_monitor_ == NULL) {
762 unload_monitor_.reset(new HtmlEventMonitor(kEventUnload,
763 HandleEventUnload,
764 this));
765 nsCOMPtr<nsIDOMEventTarget> event_source;
766 if (NS_SUCCEEDED(DOMUtils::GetWindowEventTarget(
767 getter_AddRefs(event_source))))
769 unload_monitor_->Start(event_source);
770 } else {
771 return false;
776 return JsRunnerBase::AddEventHandler(event_type, handler);
779 void DocumentJsRunner::HandleEventUnload(void *user_param) {
780 static_cast<DocumentJsRunner*>(user_param)->SendEvent(JSEVENT_UNLOAD);
783 bool DocumentJsRunner::InvokeCallbackSpecialized(
784 const JsRootedCallback *callback,
785 int argc, jsval *argv,
786 JsRootedToken **optional_alloc_retval) {
787 // When invoking a callback on the document context, we must go through
788 // nsIScriptContext->CallEventHandler because it sets up certain state that
789 // the browser error handler expects to find if there is an error. Without
790 // this, crashes happen. For more information, see:
791 // http://code.google.com/p/google-gears/issues/detail?id=32
792 nsCOMPtr<nsIScriptContext> sc;
793 sc = GetScriptContextFromJSContext(callback->context());
794 if (!sc) { return false; }
796 jsval retval;
797 nsresult result = sc->CallEventHandler(
798 JS_GetGlobalObject(callback->context()),
799 JSVAL_TO_OBJECT(callback->token()),
800 argc, argv, &retval);
801 if (NS_FAILED(result)) { return false; }
803 if (optional_alloc_retval) {
804 // See note in JsRunner::InvokeCallbackSpecialized about return values of
805 // javascript functions.
806 *optional_alloc_retval = new JsRootedToken(js_engine_context_, retval);
809 return true;
813 JsRunnerInterface* NewJsRunner() {
814 return static_cast<JsRunnerInterface*>(new JsRunner());
817 JsRunnerInterface* NewDocumentJsRunner(IGeneric *base, JsContextPtr context) {
818 return static_cast<JsRunnerInterface*>(new DocumentJsRunner(base, context));