Bug 743311: Implement Debugger.prototype.onNewGlobalObject. r=jorendorff
authorJim Blandy <jimb@mozilla.com>
Sat, 13 Oct 2012 16:04:41 -0700
changeset 118141 e44a8b7579fb531430a0711514f976992e19c38f
parent 118140 10f0632888bb4086cda39330fd574ed2749c02b9
child 118142 801ed9c31fd5ea40a797f4641a80ec3452ce3d38
push id1997
push userakeybl@mozilla.com
push dateMon, 07 Jan 2013 21:25:26 +0000
treeherdermozilla-beta@4baf45cdcf21 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs743311
milestone19.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 743311: Implement Debugger.prototype.onNewGlobalObject. r=jorendorff
js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-01.js
js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-02.js
js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-03.js
js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-04.js
js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-05.js
js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-06.js
js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-07.js
js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-08.js
js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-09.js
js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-10.js
js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-11.js
js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-12.js
js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-13.js
js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-14.js
js/src/jsapi.cpp
js/src/jscntxt.h
js/src/vm/Debugger.cpp
js/src/vm/Debugger.h
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-01.js
@@ -0,0 +1,64 @@
+// Debugger.prototype.onNewGlobalObject surfaces.
+
+load(libdir + 'asserts.js');
+
+var dbg = new Debugger;
+
+function f() { }
+function g() { }
+
+assertEq(Object.getOwnPropertyDescriptor(dbg, 'onNewGlobalObject'), undefined);
+
+var d = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(dbg), 'onNewGlobalObject');
+assertEq(d.enumerable, false);
+assertEq(d.configurable, true);
+assertEq(typeof d.get, "function");
+assertEq(typeof d.set, "function");
+
+assertEq(dbg.onNewGlobalObject, undefined);
+
+assertThrowsInstanceOf(function () { dbg.onNewGlobalObject = ''; }, TypeError);
+assertEq(dbg.onNewGlobalObject, undefined);
+
+assertThrowsInstanceOf(function () { dbg.onNewGlobalObject = false; }, TypeError);
+assertEq(dbg.onNewGlobalObject, undefined);
+
+assertThrowsInstanceOf(function () { dbg.onNewGlobalObject = 0; }, TypeError);
+assertEq(dbg.onNewGlobalObject, undefined);
+
+assertThrowsInstanceOf(function () { dbg.onNewGlobalObject = Math.PI; }, TypeError);
+assertEq(dbg.onNewGlobalObject, undefined);
+
+assertThrowsInstanceOf(function () { dbg.onNewGlobalObject = null; }, TypeError);
+assertEq(dbg.onNewGlobalObject, undefined);
+
+assertThrowsInstanceOf(function () { dbg.onNewGlobalObject = {}; }, TypeError);
+assertEq(dbg.onNewGlobalObject, undefined);
+
+// But any function, even a useless one, is okay. How fair is that?
+dbg.onNewGlobalObject = f;
+assertEq(dbg.onNewGlobalObject, f);
+
+dbg.onNewGlobalObject = undefined;
+assertEq(dbg.onNewGlobalObject, undefined);
+
+var dbg2 = new Debugger;
+assertEq(dbg.onNewGlobalObject, undefined);
+assertEq(dbg2.onNewGlobalObject, undefined);
+
+dbg.onNewGlobalObject = f;
+assertEq(dbg.onNewGlobalObject, f);
+assertEq(dbg2.onNewGlobalObject, undefined);
+
+dbg2.onNewGlobalObject = g;
+assertEq(dbg.onNewGlobalObject, f);
+assertEq(dbg2.onNewGlobalObject, g);
+
+dbg.onNewGlobalObject = undefined;
+assertEq(dbg.onNewGlobalObject, undefined);
+assertEq(dbg2.onNewGlobalObject, g);
+
+// You shouldn't be able to apply the accessor to the prototype.
+assertThrowsInstanceOf(function () {
+                         Debugger.prototype.onNewGlobalObject = function () { };
+                       }, TypeError);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-02.js
@@ -0,0 +1,23 @@
+// onNewGlobalObject handlers fire, until they are removed.
+
+var dbg = new Debugger;
+var log;
+
+log = '';
+newGlobal();
+assertEq(log, '');
+
+dbg.onNewGlobalObject = function (global) {
+  log += 'n';
+  assertEq(global.seen, undefined);
+  global.seen = true;
+};
+
+log = '';
+newGlobal();
+assertEq(log, 'n');
+
+log = '';
+dbg.onNewGlobalObject = undefined;
+newGlobal();
+assertEq(log, '');
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-03.js
@@ -0,0 +1,40 @@
+// onNewGlobalObject handlers on different Debugger instances are independent.
+
+var dbg1 = new Debugger;
+var log1;
+function h1(global) {
+  log1 += 'n';
+  assertEq(global.seen, undefined);
+  global.seen = true;
+}
+
+var dbg2 = new Debugger;
+var log2;
+function h2(global) {
+  log2 += 'n';
+  assertEq(global.seen, undefined);
+  global.seen = true;
+}
+
+log1 = log2 = '';
+newGlobal();
+assertEq(log1, '');
+assertEq(log2, '');
+
+log1 = log2 = '';
+dbg1.onNewGlobalObject = h1;
+newGlobal();
+assertEq(log1, 'n');
+assertEq(log2, '');
+
+log1 = log2 = '';
+dbg2.onNewGlobalObject = h2;
+newGlobal();
+assertEq(log1, 'n');
+assertEq(log2, 'n');
+
+log1 = log2 = '';
+dbg1.onNewGlobalObject = undefined;
+newGlobal();
+assertEq(log1, '');
+assertEq(log2, 'n');
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-04.js
@@ -0,0 +1,24 @@
+// onNewGlobalObject handlers only fire on enabled Debuggers.
+
+var dbg = new Debugger;
+var log;
+
+dbg.onNewGlobalObject = function (global) {
+  log += 'n';
+  assertEq(global.seen, undefined);
+  global.seen = true;
+};
+
+log = '';
+newGlobal();
+assertEq(log, 'n');
+
+log = '';
+dbg.enabled = false;
+newGlobal();
+assertEq(log, '');
+
+log = '';
+dbg.enabled = true;
+newGlobal();
+assertEq(log, 'n');
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-05.js
@@ -0,0 +1,13 @@
+// An onNewGlobalObject handler can disable itself.
+
+var dbg = new Debugger;
+var log;
+
+dbg.onNewGlobalObject = function (global) {
+  log += 'n';
+  dbg.onNewGlobalObject = undefined;
+};
+
+log = '';
+newGlobal();
+assertEq(log, 'n');
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-06.js
@@ -0,0 +1,20 @@
+// One Debugger's onNewGlobalObject handler can disable another Debugger's handler.
+
+var dbg1 = new Debugger;
+var dbg2 = new Debugger;
+var dbg3 = new Debugger;
+var log;
+var hit;
+
+function handler(global) {
+  hit++;
+  log += hit;
+  if (hit == 2)
+    dbg1.onNewGlobalObject = dbg2.onNewGlobalObject = dbg3.onNewGlobalObject = undefined;
+};
+
+log = '';
+hit = 0;
+dbg1.onNewGlobalObject = dbg2.onNewGlobalObject = dbg3.onNewGlobalObject = handler;
+newGlobal();
+assertEq(log, '12');
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-07.js
@@ -0,0 +1,20 @@
+// One Debugger's onNewGlobalObject handler can disable other Debuggers.
+
+var dbg1 = new Debugger;
+var dbg2 = new Debugger;
+var dbg3 = new Debugger;
+var log;
+var hit;
+
+function handler(global) {
+  hit++;
+  log += hit;
+  if (hit == 2)
+    dbg1.enabled = dbg2.enabled = dbg3.enabled = false;
+};
+
+log = '';
+hit = 0;
+dbg1.onNewGlobalObject = dbg2.onNewGlobalObject = dbg3.onNewGlobalObject = handler;
+newGlobal();
+assertEq(log, '12');
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-08.js
@@ -0,0 +1,26 @@
+// Creating a global within an onNewGlobalObject handler causes a recursive handler invocation.
+//
+// This isn't really desirable behavior, as presumably a global created while a
+// handler is running is one the debugger is creating for its own purposes and
+// should not be observed, but if this behavior changes, we sure want to know.
+
+var dbg = new Debugger;
+var log;
+var depth;
+
+dbg.onNewGlobalObject = function (global) {
+  log += '('; depth++;
+
+  assertEq(global.seen, undefined);
+  global.seen = true;
+
+  if (depth < 3)
+    newGlobal();
+
+  log += ')'; depth--;
+};
+
+log = '';
+depth = 0;
+newGlobal();
+assertEq(log, '((()))');
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-09.js
@@ -0,0 +1,27 @@
+// Resumption values from onNewGlobalObject handlers are respected.
+
+load(libdir + 'asserts.js');
+
+var dbg = new Debugger;
+var log;
+
+dbg.onNewGlobalObject = function (g) { log += 'n'; return undefined; };
+log = '';
+assertEq(typeof newGlobal(), "object");
+assertEq(log, 'n');
+
+// For onNewGlobalObject, { return: V } resumption values are treated like
+// 'undefined': the new global is still returned.
+dbg.onNewGlobalObject = function (g) { log += 'n'; return { return: "snoo" }; };
+log = '';
+assertEq(typeof newGlobal(), "object");
+assertEq(log, 'n');
+
+dbg.onNewGlobalObject = function (g) { log += 'n'; return { throw: "snoo" }; };
+log = '';
+assertThrowsValue(function () { newGlobal(); }, "snoo");
+assertEq(log, 'n');
+
+dbg.onNewGlobalObject = function (g) { log += 'n'; return null; };
+log = '';
+assertEq(evaluate('newGlobal();', { catchTermination: true }), "terminated");
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-10.js
@@ -0,0 +1,23 @@
+// An earlier onNewGlobalObject handler returning a 'throw' resumption
+// value causes later handlers not to run.
+
+load(libdir + 'asserts.js');
+
+var dbg1 = new Debugger;
+var dbg2 = new Debugger;
+var dbg3 = new Debugger;
+var log;
+var count;
+
+dbg1.onNewGlobalObject = dbg2.onNewGlobalObject = dbg3.onNewGlobalObject = function (global) {
+  count++;
+  log += count;
+  if (count == 2)
+    return { throw: "snoo" };
+  return undefined;
+};
+
+log = '';
+count = 0;
+assertThrowsValue(function () { newGlobal(); }, "snoo");
+assertEq(log, '12');
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-11.js
@@ -0,0 +1,30 @@
+// Resumption values from uncaughtExceptionHook from onNewGlobalObject handlers are respected.
+
+load(libdir + 'asserts.js');
+
+var dbg = new Debugger;
+var log;
+
+dbg.onNewGlobalObject = function () {
+  log += 'n';
+  throw 'party';
+};
+
+dbg.uncaughtExceptionHook = function (ex) {
+  log += 'u';
+  assertEq(ex, 'party');
+  return { throw: 'fit' };
+};
+
+log = '';
+assertThrowsValue(newGlobal, 'fit');
+assertEq(log, 'nu');
+
+dbg.uncaughtExceptionHook = function (ex) {
+  log += 'u';
+  assertEq(ex, 'party');
+};
+
+log = '';
+assertEq(typeof newGlobal(), 'object');
+assertEq(log, 'nu');
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-12.js
@@ -0,0 +1,29 @@
+// Resumption values from uncaughtExceptionHook from onNewGlobalObject
+// handlers affect the dispatch of the event to other Debugger instances.
+
+load(libdir + 'asserts.js');
+
+var dbg1 = new Debugger;
+var dbg2 = new Debugger;
+var dbg3 = new Debugger;
+var log;
+var count;
+
+dbg1.onNewGlobalObject = dbg2.onNewGlobalObject = dbg3.onNewGlobalObject = function () {
+  log += 'n';
+  throw 'party';
+};
+
+dbg1.uncaughtExceptionHook = dbg2.uncaughtExceptionHook = dbg3.uncaughtExceptionHook =
+function (ex) {
+  log += 'u';
+  assertEq(ex, 'party');
+  if (++count == 2)
+    return { throw: 'fit' };
+  return undefined;
+};
+
+log = '';
+count = 0;
+assertThrowsValue(newGlobal, 'fit');
+assertEq(log, 'nunu');
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-13.js
@@ -0,0 +1,17 @@
+// onNewGlobalObject handlers receive the correct Debugger.Object instances.
+
+var dbg = new Debugger;
+
+var gw = null;
+dbg.onNewGlobalObject = function (global) {
+  assertEq(arguments.length, 1);
+  assertEq(this, dbg);
+  gw = global;
+};
+var g = newGlobal();
+assertEq(typeof gw, 'object');
+assertEq(dbg.addDebuggee(g), gw);
+
+// The Debugger.Objects passed to onNewGlobalObject are the global as
+// viewed from its own compartment.
+assertEq(gw.makeDebuggeeValue(g), gw);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-14.js
@@ -0,0 +1,17 @@
+// Globals passed to onNewGlobalObject handers are ready for use immediately.
+
+var dbg = new Debugger;
+var log = '';
+dbg.onNewGlobalObject = function (global) {
+  log += 'n';
+  var gw = dbg.addDebuggee(global);
+  gw.defineProperty('x', { value: -1 });
+  // Check that the global's magic lazy properties are working.
+  assertEq(gw.evalInGlobalWithBindings('Math.atan2(y,x)', { y: 0 }).return, Math.PI);
+  // Check that the global's prototype is hooked up.
+  assertEq(gw.evalInGlobalWithBindings('x.toString()', { x: gw }).return, "[object global]");
+};
+
+newGlobal();
+
+assertEq(log, 'n');
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -58,16 +58,17 @@
 #include "builtin/MapObject.h"
 #include "builtin/RegExp.h"
 #include "builtin/ParallelArray.h"
 #include "ds/LifoAlloc.h"
 #include "frontend/BytecodeCompiler.h"
 #include "gc/Marking.h"
 #include "gc/Memory.h"
 #include "js/MemoryMetrics.h"
+#include "vm/Debugger.h"
 #include "vm/NumericConversions.h"
 #include "vm/StringBuffer.h"
 #include "vm/Xdr.h"
 #include "yarr/BumpPointerAllocator.h"
 
 #include "jsatominlines.h"
 #include "jsinferinlines.h"
 #include "jsinterpinlines.h"
@@ -859,16 +860,17 @@ JSRuntime::JSRuntime()
     ionStackLimit(0),
     ionActivation(NULL),
     ionPcScriptCache(NULL),
     ionReturnOverride_(MagicValue(JS_ARG_POISON))
 {
     /* Initialize infallibly first, so we can goto bad and JS_DestroyRuntime. */
     JS_INIT_CLIST(&contextList);
     JS_INIT_CLIST(&debuggerList);
+    JS_INIT_CLIST(&onNewGlobalObjectWatchers);
 
     PodZero(&debugHooks);
     PodZero(&atomState);
 
 #if JS_STACK_GROWTH_DIRECTION > 0
     nativeStackLimit = UINTPTR_MAX;
 #endif
 }
@@ -3376,18 +3378,23 @@ JS_NewGlobalObject(JSContext *cx, JSClas
     JSCompartment *compartment = NewCompartment(cx, principals);
     if (!compartment)
         return NULL;
 
     AutoHoldCompartment hold(compartment);
 
     JSCompartment *saved = cx->compartment;
     cx->setCompartment(compartment);
-    GlobalObject *global = GlobalObject::create(cx, Valueify(clasp));
+    Rooted<GlobalObject *> global(cx, GlobalObject::create(cx, Valueify(clasp)));
     cx->setCompartment(saved);
+    if (!global)
+        return NULL;
+
+    if (!Debugger::onNewGlobalObject(cx, global))
+        return NULL;
 
     return global;
 }
 
 JS_PUBLIC_API(JSObject *)
 JS_NewObject(JSContext *cx, JSClass *jsclasp, JSObject *protoArg, JSObject *parentArg)
 {
     RootedObject proto(cx, protoArg);
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -809,16 +809,22 @@ struct JSRuntime : js::RuntimeFriendFiel
     JSBool              hadOutOfMemory;
 
     /*
      * Linked list of all js::Debugger objects. This may be accessed by the GC
      * thread, if any, or a thread that is in a request and holds gcLock.
      */
     JSCList             debuggerList;
 
+    /*
+     * Head of circular list of all enabled Debuggers that have
+     * onNewGlobalObject handler methods established.
+     */
+    JSCList             onNewGlobalObjectWatchers;
+
     /* Bookkeeping information for debug scope objects. */
     js::DebugScopes     *debugScopes;
 
     /* Linked list of live array buffers with >1 view */
     JSObject            *liveArrayBuffers;
 
     /* Client opaque pointers */
     void                *data;
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -361,25 +361,32 @@ Debugger::Debugger(JSContext *cx, JSObje
   : object(dbg), uncaughtExceptionHook(NULL), enabled(true),
     frames(cx), scripts(cx), objects(cx), environments(cx)
 {
     assertSameCompartment(cx, dbg);
 
     JSRuntime *rt = cx->runtime;
     JS_APPEND_LINK(&link, &rt->debuggerList);
     JS_INIT_CLIST(&breakpoints);
+    JS_INIT_CLIST(&onNewGlobalObjectWatchersLink);
 }
 
 Debugger::~Debugger()
 {
     JS_ASSERT(debuggees.empty());
 
     /* This always happens in the GC thread, so no locking is required. */
     JS_ASSERT(object->compartment()->rt->isHeapBusy());
     JS_REMOVE_LINK(&link);
+
+    /*
+     * Since the inactive state for this link is a singleton cycle, it's always
+     * safe to apply JS_REMOVE_LINK to it, regardless of whether we're in the list or not.
+     */
+    JS_REMOVE_LINK(&onNewGlobalObjectWatchersLink);
 }
 
 bool
 Debugger::init(JSContext *cx)
 {
     bool ok = debuggees.init() &&
               frames.init() &&
               scripts.init() &&
@@ -1241,16 +1248,89 @@ Debugger::onSingleStep(JSContext *cx, Va
     }
 
     vp->setUndefined();
     if (exceptionPending)
         cx->setPendingException(exception);
     return JSTRAP_CONTINUE;
 }
 
+JSTrapStatus
+Debugger::fireNewGlobalObject(JSContext *cx, Handle<GlobalObject *> global, Value *vp)
+{
+    RootedObject hook(cx, getHook(OnNewGlobalObject));
+    JS_ASSERT(hook);
+    JS_ASSERT(hook->isCallable());
+
+    Maybe<AutoCompartment> ac;
+    ac.construct(cx, object);
+
+    Value argv[1];
+    argv[0].setObject(*global);
+    if (!wrapDebuggeeValue(cx, &argv[0]))
+        return handleUncaughtException(ac, NULL, false);
+
+    Value rv;
+    bool ok = Invoke(cx, ObjectValue(*object), ObjectValue(*hook), 1, argv, &rv);
+    return parseResumptionValue(ac, ok, rv, vp);
+}
+
+bool
+Debugger::slowPathOnNewGlobalObject(JSContext *cx, Handle<GlobalObject *> global)
+{
+    JS_ASSERT(!JS_CLIST_IS_EMPTY(&cx->runtime->onNewGlobalObjectWatchers));
+
+    /*
+     * Make a copy of the runtime's onNewGlobalObjectWatchers before running the
+     * handlers. Since one Debugger's handler can disable another's, the list
+     * can be mutated while we're walking it.
+     */
+    AutoObjectVector watchers(cx);
+    for (JSCList *link = JS_LIST_HEAD(&cx->runtime->onNewGlobalObjectWatchers);
+         link != &cx->runtime->onNewGlobalObjectWatchers;
+         link = JS_NEXT_LINK(link)) {
+        Debugger *dbg = fromOnNewGlobalObjectWatchersLink(link);
+        JS_ASSERT(dbg->observesNewGlobalObject());
+        if (!watchers.append(dbg->object))
+            return false;
+    }
+
+    JSTrapStatus status = JSTRAP_CONTINUE;
+    RootedValue value(cx);
+
+    for (size_t i = 0; i < watchers.length(); i++) {
+        Debugger *dbg = fromJSObject(watchers[i]);
+
+        // One Debugger's onNewGlobalObject handler can disable another's, so we
+        // must test this in the loop.
+        if (dbg->observesNewGlobalObject()) {
+            status = dbg->fireNewGlobalObject(cx, global, &value.get());
+            if (status != JSTRAP_CONTINUE && status != JSTRAP_RETURN)
+                break;
+        }
+    }
+
+    switch (status) {
+      case JSTRAP_CONTINUE:
+      case JSTRAP_RETURN: // Treat return like continue, ignoring the value.
+        return true;
+
+      case JSTRAP_ERROR:
+        JS_ASSERT(!cx->isExceptionPending());
+        return false;
+
+      case JSTRAP_THROW:
+        cx->setPendingException(value);
+        return false;
+
+      default:
+        JS_NOT_REACHED("bad status from Debugger::fireNewGlobalObject");
+    }
+}
+
 
 /*** Debugger JSObjects **************************************************************************/
 
 void
 Debugger::markKeysInCompartment(JSTracer *tracer)
 {
     /*
      * WeakMap::Range is deliberately private, to discourage C++ code from
@@ -1551,16 +1631,33 @@ Debugger::setEnabled(JSContext *cx, unsi
 
     if (enabled != dbg->enabled) {
         for (Breakpoint *bp = dbg->firstBreakpoint(); bp; bp = bp->nextInDebugger()) {
             if (enabled)
                 bp->site->inc(cx->runtime->defaultFreeOp());
             else
                 bp->site->dec(cx->runtime->defaultFreeOp());
         }
+
+        /*
+         * Add or remove ourselves from the runtime's list of Debuggers
+         * that care about new globals.
+         */
+        if (dbg->getHook(OnNewGlobalObject)) {
+            if (enabled) {
+                /* If we were not enabled, the link should be a singleton list. */
+                JS_ASSERT(JS_CLIST_IS_EMPTY(&dbg->onNewGlobalObjectWatchersLink));
+                JS_APPEND_LINK(&dbg->onNewGlobalObjectWatchersLink,
+                               &cx->runtime->onNewGlobalObjectWatchers);
+            } else {
+                /* If we were enabled, the link should be inserted in the list. */
+                JS_ASSERT(!JS_CLIST_IS_EMPTY(&dbg->onNewGlobalObjectWatchersLink));
+                JS_REMOVE_AND_INIT_LINK(&dbg->onNewGlobalObjectWatchersLink);
+            }
+        }
     }
 
     dbg->enabled = enabled;
     args.rval().setUndefined();
     return true;
 }
 
 JSBool
@@ -1634,16 +1731,52 @@ Debugger::getOnEnterFrame(JSContext *cx,
 
 JSBool
 Debugger::setOnEnterFrame(JSContext *cx, unsigned argc, Value *vp)
 {
     return setHookImpl(cx, argc, vp, OnEnterFrame);
 }
 
 JSBool
+Debugger::getOnNewGlobalObject(JSContext *cx, unsigned argc, Value *vp)
+{
+    return getHookImpl(cx, argc, vp, OnNewGlobalObject);
+}
+
+JSBool
+Debugger::setOnNewGlobalObject(JSContext *cx, unsigned argc, Value *vp)
+{
+    THIS_DEBUGGER(cx, argc, vp, "setOnNewGlobalObject", args, dbg);
+    JSObject *oldHook = dbg->getHook(OnNewGlobalObject);
+
+    if (!setHookImpl(cx, argc, vp, OnNewGlobalObject))
+        return false;
+
+    /*
+     * Add or remove ourselves from the runtime's list of Debuggers that
+     * care about new globals.
+     */
+    if (dbg->enabled) {
+        JSObject *newHook = dbg->getHook(OnNewGlobalObject);
+        if (!oldHook && newHook) {
+            /* If we didn't have a hook, the link should be a singleton list. */
+            JS_ASSERT(JS_CLIST_IS_EMPTY(&dbg->onNewGlobalObjectWatchersLink));
+            JS_APPEND_LINK(&dbg->onNewGlobalObjectWatchersLink,
+                           &cx->runtime->onNewGlobalObjectWatchers);
+        } else if (oldHook && !newHook) {
+            /* If we did have a hook, the link should be inserted in the list. */
+            JS_ASSERT(!JS_CLIST_IS_EMPTY(&dbg->onNewGlobalObjectWatchersLink));
+            JS_REMOVE_AND_INIT_LINK(&dbg->onNewGlobalObjectWatchersLink);
+        }
+    }
+
+    return true;
+}
+
+JSBool
 Debugger::getUncaughtExceptionHook(JSContext *cx, unsigned argc, Value *vp)
 {
     THIS_DEBUGGER(cx, argc, vp, "get uncaughtExceptionHook", args, dbg);
     args.rval().setObjectOrNull(dbg->uncaughtExceptionHook);
     return true;
 }
 
 JSBool
@@ -2354,16 +2487,17 @@ Debugger::findScripts(JSContext *cx, uns
 JSPropertySpec Debugger::properties[] = {
     JS_PSGS("enabled", Debugger::getEnabled, Debugger::setEnabled, 0),
     JS_PSGS("onDebuggerStatement", Debugger::getOnDebuggerStatement,
             Debugger::setOnDebuggerStatement, 0),
     JS_PSGS("onExceptionUnwind", Debugger::getOnExceptionUnwind,
             Debugger::setOnExceptionUnwind, 0),
     JS_PSGS("onNewScript", Debugger::getOnNewScript, Debugger::setOnNewScript, 0),
     JS_PSGS("onEnterFrame", Debugger::getOnEnterFrame, Debugger::setOnEnterFrame, 0),
+    JS_PSGS("onNewGlobalObject", Debugger::getOnNewGlobalObject, Debugger::setOnNewGlobalObject, 0),
     JS_PSGS("uncaughtExceptionHook", Debugger::getUncaughtExceptionHook,
             Debugger::setUncaughtExceptionHook, 0),
     JS_PS_END
 };
 
 JSFunctionSpec Debugger::methods[] = {
     JS_FN("addDebuggee", Debugger::addDebuggee, 1, 0),
     JS_FN("removeDebuggee", Debugger::removeDebuggee, 1, 0),
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -29,16 +29,17 @@ class Debugger {
     friend JSBool (::JS_DefineDebuggerObject)(JSContext *cx, JSObject *obj);
 
   public:
     enum Hook {
         OnDebuggerStatement,
         OnExceptionUnwind,
         OnNewScript,
         OnEnterFrame,
+        OnNewGlobalObject,
         HookCount
     };
 
     enum {
         JSSLOT_DEBUG_PROTO_START,
         JSSLOT_DEBUG_FRAME_PROTO = JSSLOT_DEBUG_PROTO_START,
         JSSLOT_DEBUG_ENV_PROTO,
         JSSLOT_DEBUG_OBJECT_PROTO,
@@ -50,17 +51,25 @@ class Debugger {
     };
 
   private:
     JSCList link;                       /* See JSRuntime::debuggerList. */
     HeapPtrObject object;               /* The Debugger object. Strong reference. */
     GlobalObjectSet debuggees;          /* Debuggee globals. Cross-compartment weak references. */
     js::HeapPtrObject uncaughtExceptionHook; /* Strong reference. */
     bool enabled;
-    JSCList breakpoints;                /* cyclic list of all js::Breakpoints in this debugger */
+    JSCList breakpoints;                /* Circular list of all js::Breakpoints in this debugger */
+
+    /*
+     * If this Debugger is enabled, and has a onNewGlobalObject handler, then
+     * this link is inserted into the circular list headed by
+     * JSRuntime::onNewGlobalObjectWatchers. Otherwise, this is set to a
+     * singleton cycle.
+     */
+    JSCList onNewGlobalObjectWatchersLink;
 
     /*
      * Map from stack frames that are currently on the stack to Debugger.Frame
      * instances.
      *
      * The keys are always live stack frames. We drop them from this map as
      * soon as they leave the stack (see slowPathOnLeaveFrame) and in
      * removeDebuggee.
@@ -156,16 +165,18 @@ class Debugger {
     static JSBool getOnDebuggerStatement(JSContext *cx, unsigned argc, Value *vp);
     static JSBool setOnDebuggerStatement(JSContext *cx, unsigned argc, Value *vp);
     static JSBool getOnExceptionUnwind(JSContext *cx, unsigned argc, Value *vp);
     static JSBool setOnExceptionUnwind(JSContext *cx, unsigned argc, Value *vp);
     static JSBool getOnNewScript(JSContext *cx, unsigned argc, Value *vp);
     static JSBool setOnNewScript(JSContext *cx, unsigned argc, Value *vp);
     static JSBool getOnEnterFrame(JSContext *cx, unsigned argc, Value *vp);
     static JSBool setOnEnterFrame(JSContext *cx, unsigned argc, Value *vp);
+    static JSBool getOnNewGlobalObject(JSContext *cx, unsigned argc, Value *vp);
+    static JSBool setOnNewGlobalObject(JSContext *cx, unsigned argc, Value *vp);
     static JSBool getUncaughtExceptionHook(JSContext *cx, unsigned argc, Value *vp);
     static JSBool setUncaughtExceptionHook(JSContext *cx, unsigned argc, Value *vp);
     static JSBool addDebuggee(JSContext *cx, unsigned argc, Value *vp);
     static JSBool removeDebuggee(JSContext *cx, unsigned argc, Value *vp);
     static JSBool hasDebuggee(JSContext *cx, unsigned argc, Value *vp);
     static JSBool getDebuggees(JSContext *cx, unsigned argc, Value *vp);
     static JSBool getNewestFrame(JSContext *cx, unsigned argc, Value *vp);
     static JSBool clearAllBreakpoints(JSContext *cx, unsigned argc, Value *vp);
@@ -177,37 +188,41 @@ class Debugger {
 
     JSObject *getHook(Hook hook) const;
     bool hasAnyLiveHooks() const;
 
     static JSTrapStatus slowPathOnEnterFrame(JSContext *cx, Value *vp);
     static bool slowPathOnLeaveFrame(JSContext *cx, bool ok);
     static void slowPathOnNewScript(JSContext *cx, JSScript *script,
                                     GlobalObject *compileAndGoGlobal);
+    static bool slowPathOnNewGlobalObject(JSContext *cx, Handle<GlobalObject *> global);
     static JSTrapStatus dispatchHook(JSContext *cx, Value *vp, Hook which);
 
     JSTrapStatus fireDebuggerStatement(JSContext *cx, Value *vp);
     JSTrapStatus fireExceptionUnwind(JSContext *cx, Value *vp);
     JSTrapStatus fireEnterFrame(JSContext *cx, Value *vp);
+    JSTrapStatus fireNewGlobalObject(JSContext *cx, Handle<GlobalObject *> global, Value *vp);
 
     /*
      * Allocate and initialize a Debugger.Script instance whose referent is
      * |script|.
      */
     JSObject *newDebuggerScript(JSContext *cx, HandleScript script);
 
     /*
      * Receive a "new script" event from the engine. A new script was compiled
      * or deserialized.
      */
     void fireNewScript(JSContext *cx, HandleScript script);
 
     static inline Debugger *fromLinks(JSCList *links);
     inline Breakpoint *firstBreakpoint() const;
 
+    static inline Debugger *fromOnNewGlobalObjectWatchersLink(JSCList *link);
+
   public:
     Debugger(JSContext *cx, JSObject *dbg);
     ~Debugger();
 
     bool init(JSContext *cx);
     inline const js::HeapPtrObject &toJSObject() const;
     inline js::HeapPtrObject &toJSObjectRef();
     static inline Debugger *fromJSObject(JSObject *obj);
@@ -237,23 +252,25 @@ class Debugger {
                                              GlobalObjectSet::Enum *compartmentEnum);
 
     static inline JSTrapStatus onEnterFrame(JSContext *cx, Value *vp);
     static inline bool onLeaveFrame(JSContext *cx, bool ok);
     static inline JSTrapStatus onDebuggerStatement(JSContext *cx, Value *vp);
     static inline JSTrapStatus onExceptionUnwind(JSContext *cx, Value *vp);
     static inline void onNewScript(JSContext *cx, JSScript *script,
                                    GlobalObject *compileAndGoGlobal);
+    static inline bool onNewGlobalObject(JSContext *cx, Handle<GlobalObject *> global);
     static JSTrapStatus onTrap(JSContext *cx, Value *vp);
     static JSTrapStatus onSingleStep(JSContext *cx, Value *vp);
 
     /************************************* Functions for use by Debugger.cpp. */
 
     inline bool observesEnterFrame() const;
     inline bool observesNewScript() const;
+    inline bool observesNewGlobalObject() const;
     inline bool observesGlobal(GlobalObject *global) const;
     inline bool observesFrame(StackFrame *fp) const;
     bool observesScript(JSScript *script) const;
 
     /*
      * If env is NULL, call vp->setNull() and return true. Otherwise, find or
      * create a Debugger.Environment object for the given Env. On success,
      * store the Environment object in *vp and return true.
@@ -416,28 +433,34 @@ class Breakpoint {
     Breakpoint *nextInSite();
     const HeapPtrObject &getHandler() const { return handler; }
     HeapPtrObject &getHandlerRef() { return handler; }
 };
 
 Debugger *
 Debugger::fromLinks(JSCList *links)
 {
-    unsigned char *p = reinterpret_cast<unsigned char *>(links);
+    char *p = reinterpret_cast<char *>(links);
     return reinterpret_cast<Debugger *>(p - offsetof(Debugger, link));
 }
 
 Breakpoint *
 Debugger::firstBreakpoint() const
 {
     if (JS_CLIST_IS_EMPTY(&breakpoints))
         return NULL;
     return Breakpoint::fromDebuggerLinks(JS_NEXT_LINK(&breakpoints));
 }
 
+Debugger *
+Debugger::fromOnNewGlobalObjectWatchersLink(JSCList *link) {
+    char *p = reinterpret_cast<char *>(link);
+    return reinterpret_cast<Debugger *>(p - offsetof(Debugger, onNewGlobalObjectWatchersLink));
+}
+
 const js::HeapPtrObject &
 Debugger::toJSObject() const
 {
     JS_ASSERT(object);
     return object;
 }
 
 js::HeapPtrObject &
@@ -462,16 +485,22 @@ Debugger::observesEnterFrame() const
 
 bool
 Debugger::observesNewScript() const
 {
     return enabled && getHook(OnNewScript);
 }
 
 bool
+Debugger::observesNewGlobalObject() const
+{
+    return enabled && getHook(OnNewGlobalObject);
+}
+
+bool
 Debugger::observesGlobal(GlobalObject *global) const
 {
     return debuggees.has(global);
 }
 
 bool
 Debugger::observesFrame(StackFrame *fp) const
 {
@@ -517,16 +546,24 @@ void
 Debugger::onNewScript(JSContext *cx, JSScript *script, GlobalObject *compileAndGoGlobal)
 {
     JS_ASSERT_IF(script->compileAndGo, compileAndGoGlobal);
     JS_ASSERT_IF(!script->compileAndGo, !compileAndGoGlobal);
     if (!script->compartment()->getDebuggees().empty())
         slowPathOnNewScript(cx, script, compileAndGoGlobal);
 }
 
+bool
+Debugger::onNewGlobalObject(JSContext *cx, Handle<GlobalObject *> global)
+{
+    if (JS_CLIST_IS_EMPTY(&cx->runtime->onNewGlobalObjectWatchers))
+        return true;
+    return Debugger::slowPathOnNewGlobalObject(cx, global);
+}
+
 extern JSBool
 EvaluateInEnv(JSContext *cx, Handle<Env*> env, HandleValue thisv, StackFrame *fp,
               StableCharPtr chars, unsigned length, const char *filename, unsigned lineno,
               Value *rval);
 
 }
 
 #endif /* Debugger_h__ */