Bug 1032869 - Part 2: Move debuggee-ness to frames and selectively deoptimize when Debugger needs to observe execution. (r=jimb)
authorShu-yu Guo <shu@rfrn.org>
Thu, 13 Nov 2014 14:39:39 -0800
changeset 215647 b160657339f8e05bca3649d31d52481a25de188c
parent 215646 2dbd7a8b984a6907ea1248c6c8d9d50c74054619
child 215648 bb2f13ba7b1cceeb491e876773196c0aad71ef95
push id51810
push usershu@rfrn.org
push dateThu, 13 Nov 2014 22:39:12 +0000
treeherdermozilla-inbound@d65f01b9e6c6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjimb
bugs1032869
milestone36.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 1032869 - Part 2: Move debuggee-ness to frames and selectively deoptimize when Debugger needs to observe execution. (r=jimb)
js/src/asmjs/AsmJSValidate.cpp
js/src/jit-test/tests/basic/eif-generator.js
js/src/jit-test/tests/basic/testBug552248.js
js/src/jit-test/tests/debug/Debugger-enabled-02.js
js/src/jit-test/tests/debug/breakpoint-13.js
js/src/jit-test/tests/debug/breakpoint-14.js
js/src/jit-test/tests/debug/execution-observability-01.js
js/src/jit-test/tests/jaeger/invokeSessionGuard.js
js/src/jit/BaselineBailouts.cpp
js/src/jit/BaselineCompiler.cpp
js/src/jit/BaselineDebugModeOSR.cpp
js/src/jit/BaselineDebugModeOSR.h
js/src/jit/BaselineFrame-inl.h
js/src/jit/BaselineFrame.cpp
js/src/jit/BaselineFrame.h
js/src/jit/BaselineJIT.cpp
js/src/jit/BaselineJIT.h
js/src/jit/Ion.cpp
js/src/jit/Ion.h
js/src/jit/IonAnalysis.cpp
js/src/jit/IonBuilder.cpp
js/src/jit/IonFrames.cpp
js/src/jit/RematerializedFrame.cpp
js/src/jit/RematerializedFrame.h
js/src/jit/VMFunctions.cpp
js/src/jit/shared/BaselineCompiler-shared.cpp
js/src/jit/shared/BaselineCompiler-shared.h
js/src/js.msg
js/src/jscompartment.cpp
js/src/jscompartment.h
js/src/jsfun.cpp
js/src/jsinfer.cpp
js/src/jsscript.cpp
js/src/jsscript.h
js/src/jsscriptinlines.h
js/src/shell/js.cpp
js/src/vm/Debugger-inl.h
js/src/vm/Debugger.cpp
js/src/vm/Debugger.h
js/src/vm/Interpreter.cpp
js/src/vm/Runtime.cpp
js/src/vm/ScopeObject.cpp
js/src/vm/ScopeObject.h
js/src/vm/Stack-inl.h
js/src/vm/Stack.cpp
js/src/vm/Stack.h
--- a/js/src/asmjs/AsmJSValidate.cpp
+++ b/js/src/asmjs/AsmJSValidate.cpp
@@ -8672,17 +8672,17 @@ EstablishPreconditions(ExclusiveContext 
         return Warn(parser, JSMSG_USE_ASM_TYPE_FAIL, "Disabled by non 4KiB system page size");
 
     if (!parser.options().asmJSOption)
         return Warn(parser, JSMSG_USE_ASM_TYPE_FAIL, "Disabled by javascript.options.asmjs in about:config");
 
     if (!parser.options().compileAndGo)
         return Warn(parser, JSMSG_USE_ASM_TYPE_FAIL, "Temporarily disabled for event-handler and other cloneable scripts");
 
-    if (cx->compartment()->debugMode())
+    if (cx->compartment()->isDebuggee())
         return Warn(parser, JSMSG_USE_ASM_TYPE_FAIL, "Disabled by debugger");
 
     if (parser.pc->isGenerator())
         return Warn(parser, JSMSG_USE_ASM_TYPE_FAIL, "Disabled by generator context");
 
     if (parser.pc->isArrowFunction())
         return Warn(parser, JSMSG_USE_ASM_TYPE_FAIL, "Disabled by arrow function context");
 
@@ -8734,15 +8734,15 @@ js::IsAsmJSCompilationAvailable(JSContex
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // See EstablishPreconditions.
 #ifdef JS_CODEGEN_NONE
     bool available = false;
 #else
     bool available = cx->jitSupportsFloatingPoint() &&
                      cx->gcSystemPageSize() == AsmJSPageSize &&
-                     !cx->compartment()->debugMode() &&
+                     !cx->compartment()->isDebuggee() &&
                      cx->runtime()->options().asmJS();
 #endif
 
     args.rval().set(BooleanValue(available));
     return true;
 }
--- a/js/src/jit-test/tests/basic/eif-generator.js
+++ b/js/src/jit-test/tests/basic/eif-generator.js
@@ -1,10 +1,12 @@
 var global = newGlobal();
 var dbg = new global.Debugger(this);
+// Force dbg to observe all execution.
+dbg.onDebuggerStatement = function () {};
 
 function f() {
     let (x = 1) {
         while (true) {
             yield evalInFrame(0, "x");
             x++;
             let (y = 1) {
                 yield evalInFrame(0, "++y");
--- a/js/src/jit-test/tests/basic/testBug552248.js
+++ b/js/src/jit-test/tests/basic/testBug552248.js
@@ -1,10 +1,12 @@
 var global = newGlobal();
 var dbg = new global.Debugger(this);
+// Force dbg to observe all execution.
+dbg.onDebuggerStatement = function () {};
 
 var a = new Array();
 
 function i(save) {
     var x = 9;
     evalInFrame(0, "a.push(x)", save);
     evalInFrame(1, "a.push(z)", save);
     evalInFrame(2, "a.push(z)", save);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-enabled-02.js
@@ -0,0 +1,17 @@
+// Tests that hooks work if set while the Debugger is disabled.
+
+var g = newGlobal();
+var dbg = new Debugger(g);
+var log = "";
+
+g.eval("" + function f() { return 42; });
+
+dbg.enabled = false;
+dbg.onEnterFrame = function (frame) {
+  log += "1";
+};
+dbg.enabled = true;
+
+g.f();
+
+assertEq(log, "1");
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/breakpoint-13.js
@@ -0,0 +1,13 @@
+// Breakpoints should be hit on scripts gotten not via Debugger.Frame.
+
+var g = newGlobal();
+g.eval("function f(x) { return x + 1; }");
+// Warm up so f gets OSRed into the jits.
+g.eval("for (var i = 0; i < 10000; i++) f(i);");
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+var fw = gw.getOwnPropertyDescriptor("f").value;
+var hits = 0;
+fw.script.setBreakpoint(0, { hit: function(frame) { hits++; } });
+g.eval("f(42);");
+assertEq(hits, 1);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/breakpoint-14.js
@@ -0,0 +1,14 @@
+// Breakpoints should be hit on scripts gotten not via Debugger.Frame.
+
+var g = newGlobal();
+g.eval("function f(x) { return x + 1; }");
+g.eval("function g(x) { f(x); }");
+// Warm up so f gets OSRed into the jits and g inlines f.
+g.eval("for (var i = 0; i < 10000; i++) g(i);");
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+var fw = gw.getOwnPropertyDescriptor("f").value;
+var hits = 0;
+fw.script.setBreakpoint(0, { hit: function(frame) { hits++; } });
+g.eval("g(42);");
+assertEq(hits, 1);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/execution-observability-01.js
@@ -0,0 +1,22 @@
+// For perf reasons we don't recompile all a debuggee global's scripts when
+// Debugger no longer needs to observe all execution for that global. Test that
+// things don't crash if we try to run a script with a BaselineScript that was
+// compiled with debug instrumentation when the global is no longer a debuggee.
+
+var g = newGlobal();
+var dbg = new Debugger(g);
+var counter = 0;
+dbg.onDebuggerStatement = function (frame) {
+  counter++;
+  if (counter == 15)
+    dbg.onDebuggerStatement = undefined;
+};
+
+g.eval("" + function f() {
+  {
+    let inner = 42;
+    debugger;
+    inner++;
+  }
+});
+g.eval("for (var i = 0; i < 20; i++) f()");
--- a/js/src/jit-test/tests/jaeger/invokeSessionGuard.js
+++ b/js/src/jit-test/tests/jaeger/invokeSessionGuard.js
@@ -1,9 +1,11 @@
 var g = newGlobal();
 var dbg = new g.Debugger(this);
+// Force dbg to observe all execution.
+dbg.onDebuggerStatement = function () {};
 
 [1,2,3,4,5,6,7,8].forEach(
     function(x) {
         // evalInFrame means lightweight gets call obj
         assertEq(evalInFrame(0, "x"), x);
     }
 );
--- a/js/src/jit/BaselineBailouts.cpp
+++ b/js/src/jit/BaselineBailouts.cpp
@@ -602,16 +602,22 @@ InitFromBailout(JSContext *cx, HandleScr
     // that will be fixed later.
     if (ionScript->hasSPSInstrumentation()) {
         if (callerPC == nullptr) {
             JitSpew(JitSpew_BaselineBailouts, "      Setting SPS flag on top frame!");
             flags |= BaselineFrame::HAS_PUSHED_SPS_FRAME;
         }
     }
 
+    // If we are bailing to a script whose execution is observed, mark the
+    // baseline frame as a debuggee frame. This is to cover the case where we
+    // don't rematerialize the Ion frame via the Debugger.
+    if (script->isDebuggee())
+        flags |= BaselineFrame::DEBUGGEE;
+
     // Initialize BaselineFrame's scopeChain and argsObj
     JSObject *scopeChain = nullptr;
     Value returnValue;
     ArgumentsObject *argsObj = nullptr;
     BailoutKind bailoutKind = iter.bailoutKind();
     if (bailoutKind == Bailout_ArgumentCheck) {
         // Temporary hack -- skip the (unused) scopeChain, because it could be
         // bogus (we can fail before the scope chain slot is set). Strip the
@@ -838,17 +844,17 @@ InitFromBailout(JSContext *cx, HandleScr
             // If we are in the middle of propagating an exception from Ion by
             // bailing to baseline due to debug mode, we might not have all
             // the stack if we are at the newest frame.
             //
             // For instance, if calling |f()| pushed an Ion frame which threw,
             // the snapshot expects the return value to be pushed, but it's
             // possible nothing was pushed before we threw. Iterators might
             // still be on the stack, so we can't just drop the stack.
-            MOZ_ASSERT(cx->compartment()->debugMode());
+            MOZ_ASSERT(cx->compartment()->isDebuggee());
             if (iter.moreFrames())
                 v = iter.read();
             else
                 v = MagicValue(JS_OPTIMIZED_OUT);
         } else {
             v = iter.read();
         }
         if (!builder.writeValue(v, "StackValue"))
@@ -1589,18 +1595,24 @@ CopyFromRematerializedFrame(JSContext *c
 
     for (size_t i = 0; i < frame->script()->nfixed(); i++)
         *frame->valueSlot(i) = rematFrame->locals()[i];
 
     JitSpew(JitSpew_BaselineBailouts,
             "  Copied from rematerialized frame at (%p,%u)",
             fp, inlineDepth);
 
-    if (cx->compartment()->debugMode())
+    // Propagate the debuggee frame flag. For the case where the Debugger did
+    // not rematerialize an Ion frame, the baseline frame has its debuggee
+    // flag set iff its script is considered a debuggee. See the debuggee case
+    // in InitFromBailout.
+    if (rematFrame->isDebuggee()) {
+        frame->setIsDebuggee();
         return Debugger::handleIonBailout(cx, rematFrame, frame);
+    }
 
     return true;
 }
 
 uint32_t
 jit::FinishBailoutToBaseline(BaselineBailoutInfo *bailoutInfo)
 {
     // The caller pushes R0 and R1 on the stack without rooting them.
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -242,18 +242,18 @@ BaselineCompiler::compile()
     types::FillBytecodeTypeMap(script, bytecodeMap);
 
     // The last entry in the last index found, and is used to avoid binary
     // searches for the sought entry when queries are in linear order.
     bytecodeMap[script->nTypeSets()] = 0;
 
     baselineScript->copyYieldEntries(script, yieldOffsets_);
 
-    if (script->compartment()->debugMode())
-        baselineScript->setDebugMode();
+    if (compileDebugInstrumentation_)
+        baselineScript->setHasDebugInstrumentation();
 
     // Register a native => bytecode mapping entry for this script if needed.
     if (cx->runtime()->jitRuntime()->isNativeToBytecodeMapEnabled(cx->runtime())) {
         JitSpew(JitSpew_Profiling, "Added JitcodeGlobalEntry for baseline script %s:%d (%p)",
                     script->filename(), script->lineno(), baselineScript);
         JitcodeGlobalEntry::BaselineEntry entry;
         entry.init(code->raw(), code->raw() + code->instructionsSize(), script);
 
@@ -552,17 +552,17 @@ BaselineCompiler::emitStackCheck(bool ea
 }
 
 typedef bool (*DebugPrologueFn)(JSContext *, BaselineFrame *, jsbytecode *, bool *);
 static const VMFunction DebugPrologueInfo = FunctionInfo<DebugPrologueFn>(jit::DebugPrologue);
 
 bool
 BaselineCompiler::emitDebugPrologue()
 {
-    if (debugMode_) {
+    if (compileDebugInstrumentation_) {
         // Load pointer to BaselineFrame in R0.
         masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
 
         prepareVMCall();
         pushArg(ImmPtr(pc));
         pushArg(R0.scratchReg());
         if (!callVM(DebugPrologueInfo))
             return false;
@@ -735,17 +735,17 @@ BaselineCompiler::emitArgumentTypeChecks
     }
 
     return true;
 }
 
 bool
 BaselineCompiler::emitDebugTrap()
 {
-    MOZ_ASSERT(debugMode_);
+    MOZ_ASSERT(compileDebugInstrumentation_);
     MOZ_ASSERT(frame.numUnsyncedSlots() == 0);
 
     bool enabled = script->stepModeEnabled() || script->hasBreakpointsAt(pc);
 
     // Emit patchable call to debug trap handler.
     JitCode *handler = cx->runtime()->jitRuntime()->debugTrapHandler(cx);
     mozilla::DebugOnly<CodeOffsetLabel> offset = masm.toggledCall(handler, enabled);
 
@@ -823,17 +823,17 @@ BaselineCompiler::emitBody()
 
         // Fully sync the stack if there are incoming jumps.
         if (info->jumpTarget) {
             frame.syncStack(0);
             frame.setStackDepth(info->stackDepth);
         }
 
         // Always sync in debug mode.
-        if (debugMode_)
+        if (compileDebugInstrumentation_)
             frame.syncStack(0);
 
         // At the beginning of any op, at most the top 2 stack-values are unsynced.
         if (frame.stackDepth() > 2)
             frame.syncStack(2);
 
         frame.assertValidState(*info);
 
@@ -845,17 +845,17 @@ BaselineCompiler::emitBody()
         // PCMappingIndexEntry for more information.
         bool addIndexEntry = (pc == script->code() || lastOpUnreachable || emittedOps > 100);
         if (addIndexEntry)
             emittedOps = 0;
         if (!addPCMappingEntry(addIndexEntry))
             return Method_Error;
 
         // Emit traps for breakpoints and step mode.
-        if (debugMode_ && !emitDebugTrap())
+        if (compileDebugInstrumentation_ && !emitDebugTrap())
             return Method_Error;
 
         switch (op) {
           default:
             JitSpew(JitSpew_BaselineAbort, "Unhandled op: %s", js_CodeName[op]);
             return Method_CantCompile;
 
 #define EMIT_OP(OP)                            \
@@ -2901,17 +2901,17 @@ BaselineCompiler::emit_JSOP_POPBLOCKSCOP
 }
 
 typedef bool (*DebugLeaveBlockFn)(JSContext *, BaselineFrame *, jsbytecode *);
 static const VMFunction DebugLeaveBlockInfo = FunctionInfo<DebugLeaveBlockFn>(jit::DebugLeaveBlock);
 
 bool
 BaselineCompiler::emit_JSOP_DEBUGLEAVEBLOCK()
 {
-    if (!debugMode_)
+    if (!compileDebugInstrumentation_)
         return true;
 
     prepareVMCall();
     masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
     pushArg(ImmPtr(pc));
     pushArg(R0.scratchReg());
 
     return callVM(DebugLeaveBlockInfo);
@@ -2972,17 +2972,17 @@ BaselineCompiler::emit_JSOP_EXCEPTION()
 
 typedef bool (*OnDebuggerStatementFn)(JSContext *, BaselineFrame *, jsbytecode *pc, bool *);
 static const VMFunction OnDebuggerStatementInfo =
     FunctionInfo<OnDebuggerStatementFn>(jit::OnDebuggerStatement);
 
 bool
 BaselineCompiler::emit_JSOP_DEBUGGER()
 {
-    if (!debugMode_)
+    if (!compileDebugInstrumentation_)
         return true;
 
     prepareVMCall();
     pushArg(ImmPtr(pc));
 
     masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
     pushArg(R0.scratchReg());
 
@@ -3002,17 +3002,17 @@ BaselineCompiler::emit_JSOP_DEBUGGER()
 
 typedef bool (*DebugEpilogueFn)(JSContext *, BaselineFrame *, jsbytecode *);
 static const VMFunction DebugEpilogueInfo =
     FunctionInfo<DebugEpilogueFn>(jit::DebugEpilogueOnBaselineReturn);
 
 bool
 BaselineCompiler::emitReturn()
 {
-    if (debugMode_) {
+    if (compileDebugInstrumentation_) {
         // Move return value into the frame's rval slot.
         masm.storeValue(JSReturnOperand, frame.addressOfReturnValue());
         masm.or32(Imm32(BaselineFrame::HAS_RVAL), frame.addressOfFlags());
 
         // Load BaselineFrame pointer in R0.
         frame.syncStack(0);
         masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
 
--- a/js/src/jit/BaselineDebugModeOSR.cpp
+++ b/js/src/jit/BaselineDebugModeOSR.cpp
@@ -102,31 +102,33 @@ struct DebugModeOSREntry
     BaselineDebugModeOSRInfo *takeRecompInfo() {
         MOZ_ASSERT(needsRecompileInfo() && recompInfo);
         BaselineDebugModeOSRInfo *tmp = recompInfo;
         recompInfo = nullptr;
         return tmp;
     }
 
     bool allocateRecompileInfo(JSContext *cx) {
+        MOZ_ASSERT(script);
         MOZ_ASSERT(needsRecompileInfo());
 
         // If we are returning to a frame which needs a continuation fixer,
         // allocate the recompile info up front so that the patching function
         // is infallible.
         jsbytecode *pc = script->offsetToPC(pcOffset);
 
         // XXX: Work around compiler error disallowing using bitfields
         // with the template magic of new_.
         ICEntry::Kind kind = frameKind;
         recompInfo = cx->new_<BaselineDebugModeOSRInfo>(pc, kind);
         return !!recompInfo;
     }
 
     ICFallbackStub *fallbackStub() const {
+        MOZ_ASSERT(script);
         MOZ_ASSERT(oldStub);
         return script->baselineScript()->icEntryFromPCOffset(pcOffset).fallbackStub();
     }
 };
 
 typedef Vector<DebugModeOSREntry> DebugModeOSREntryVector;
 
 class UniqueScriptOSREntryIter
@@ -162,68 +164,75 @@ class UniqueScriptOSREntryIter
             if (unique)
                 break;
         }
         return *this;
     }
 };
 
 static bool
-CollectOnStackScripts(JSContext *cx, const JitActivationIterator &activation,
-                      DebugModeOSREntryVector &entries)
+CollectJitStackScripts(JSContext *cx, const Debugger::ExecutionObservableSet &obs,
+                       const ActivationIterator &activation, DebugModeOSREntryVector &entries)
 {
     ICStub *prevFrameStubPtr = nullptr;
     bool needsRecompileHandler = false;
     for (JitFrameIterator iter(activation); !iter.done(); ++iter) {
         switch (iter.type()) {
           case JitFrame_BaselineJS: {
             JSScript *script = iter.script();
 
+            if (!obs.shouldRecompileOrInvalidate(script)) {
+                prevFrameStubPtr = nullptr;
+                break;
+            }
+
             if (BaselineDebugModeOSRInfo *info = iter.baselineFrame()->getDebugModeOSRInfo()) {
                 // If patching a previously patched yet unpopped frame, we can
                 // use the BaselineDebugModeOSRInfo on the frame directly to
                 // patch. Indeed, we cannot use iter.returnAddressToFp(), as
                 // it points into the debug mode OSR handler and cannot be
                 // used to look up a corresponding ICEntry.
                 //
-                // See cases F and G in PatchBaselineFrameForDebugMode.
+                // See cases F and G in PatchBaselineFramesForDebugMode.
                 if (!entries.append(DebugModeOSREntry(script, info)))
                     return false;
             } else {
                 // Otherwise, use the return address to look up the ICEntry.
                 uint8_t *retAddr = iter.returnAddressToFp();
                 ICEntry &entry = script->baselineScript()->icEntryFromReturnAddress(retAddr);
                 if (!entries.append(DebugModeOSREntry(script, entry)))
                     return false;
             }
 
             if (entries.back().needsRecompileInfo()) {
                 if (!entries.back().allocateRecompileInfo(cx))
                     return false;
 
                 needsRecompileHandler |= true;
             }
-
             entries.back().oldStub = prevFrameStubPtr;
             prevFrameStubPtr = nullptr;
             break;
           }
 
           case JitFrame_BaselineStub:
             prevFrameStubPtr =
                 reinterpret_cast<IonBaselineStubFrameLayout *>(iter.fp())->maybeStubPtr();
             break;
 
           case JitFrame_IonJS: {
-            JSScript *script = iter.script();
-            if (!entries.append(DebugModeOSREntry(script)))
-                return false;
-            for (InlineFrameIterator inlineIter(cx, &iter); inlineIter.more(); ++inlineIter) {
-                if (!entries.append(DebugModeOSREntry(inlineIter.script())))
-                    return false;
+            InlineFrameIterator inlineIter(cx, &iter);
+            while (true) {
+                if (obs.shouldRecompileOrInvalidate(inlineIter.script())) {
+                    if (!entries.append(DebugModeOSREntry(inlineIter.script())))
+                        return false;
+                }
+                if (!inlineIter.more())
+                    break;
+                ++inlineIter;
             }
             break;
           }
 
           default:;
         }
     }
 
@@ -233,16 +242,35 @@ CollectOnStackScripts(JSContext *cx, con
         JitRuntime *rt = cx->runtime()->jitRuntime();
         if (!rt->getBaselineDebugModeOSRHandlerAddress(cx, true))
             return false;
     }
 
     return true;
 }
 
+static bool
+CollectInterpreterStackScripts(JSContext *cx, const Debugger::ExecutionObservableSet &obs,
+                               const ActivationIterator &activation,
+                               DebugModeOSREntryVector &entries)
+{
+    // Collect interpreter frame stacks with IonScript or BaselineScript as
+    // well. These do not need to be patched, but do need to be invalidated
+    // and recompiled.
+    InterpreterActivation *act = activation.activation()->asInterpreter();
+    for (InterpreterFrameIterator iter(act); !iter.done(); ++iter) {
+        JSScript *script = iter.frame()->script();
+        if (obs.shouldRecompileOrInvalidate(script)) {
+            if (!entries.append(DebugModeOSREntry(iter.frame()->script())))
+                return false;
+        }
+    }
+    return true;
+}
+
 static const char *
 ICEntryKindToString(ICEntry::Kind kind)
 {
     switch (kind) {
       case ICEntry::Kind_Op:
         return "IC";
       case ICEntry::Kind_NonOp:
         return "non-op IC";
@@ -273,17 +301,18 @@ static void
 SpewPatchStubFrame(ICStub *oldStub, ICStub *newStub)
 {
     JitSpew(JitSpew_BaselineDebugModeOSR,
             "Patch   stub %p -> %p on BaselineStub frame (%s)",
             oldStub, newStub, ICStub::KindString(newStub->kind()));
 }
 
 static void
-PatchBaselineFramesForDebugMode(JSContext *cx, const JitActivationIterator &activation,
+PatchBaselineFramesForDebugMode(JSContext *cx, const Debugger::ExecutionObservableSet &obs,
+                                const ActivationIterator &activation,
                                 DebugModeOSREntryVector &entries, size_t *start)
 {
     //
     // Recompile Patching Overview
     //
     // When toggling debug mode with live baseline scripts on the stack, we
     // could have entered the VM via the following ways from the baseline
     // script.
@@ -307,36 +336,38 @@ PatchBaselineFramesForDebugMode(JSContex
     //
     // In general, we patch the return address from the VM call to return to a
     // "continuation fixer" to fix up machine state (registers and stack
     // state). Specifics on what need to be done are documented below.
     //
 
     IonCommonFrameLayout *prev = nullptr;
     size_t entryIndex = *start;
-    DebugOnly<bool> expectedDebugMode = cx->compartment()->debugMode();
 
     for (JitFrameIterator iter(activation); !iter.done(); ++iter) {
-        DebugModeOSREntry &entry = entries[entryIndex];
-
         switch (iter.type()) {
           case JitFrame_BaselineJS: {
-            // If the script wasn't recompiled, there's nothing to patch.
+            // If the script wasn't recompiled or is not observed, there's
+            // nothing to patch.
+            if (!obs.shouldRecompileOrInvalidate(iter.script()))
+                break;
+
+            DebugModeOSREntry &entry = entries[entryIndex];
+
             if (!entry.recompiled()) {
                 entryIndex++;
                 break;
             }
 
             JSScript *script = entry.script;
             uint32_t pcOffset = entry.pcOffset;
             jsbytecode *pc = script->offsetToPC(pcOffset);
 
             MOZ_ASSERT(script == iter.script());
             MOZ_ASSERT(pcOffset < script->length());
-            MOZ_ASSERT(script->baselineScript()->debugMode() == expectedDebugMode);
 
             BaselineScript *bl = script->baselineScript();
             ICEntry::Kind kind = entry.frameKind;
 
             if (kind == ICEntry::Kind_Op) {
                 // Case A above.
                 //
                 // Patching this case needs to patch both the stub frame and
@@ -358,22 +389,24 @@ PatchBaselineFramesForDebugMode(JSContex
             // We undo a previous recompile by handling cases B, C, D, and E
             // like normal, except that we retrieved the pc information via
             // the previous OSR debug info stashed on the frame.
             if (BaselineDebugModeOSRInfo *info = iter.baselineFrame()->getDebugModeOSRInfo()) {
                 MOZ_ASSERT(info->pc == pc);
                 MOZ_ASSERT(info->frameKind == kind);
 
                 // Case G, might need to undo B, C, D, or E.
-                MOZ_ASSERT_IF(expectedDebugMode, (kind == ICEntry::Kind_CallVM ||
-                                                  kind == ICEntry::Kind_DebugTrap ||
-                                                  kind == ICEntry::Kind_DebugPrologue ||
-                                                  kind == ICEntry::Kind_DebugEpilogue));
+                MOZ_ASSERT_IF(script->baselineScript()->hasDebugInstrumentation(),
+                              kind == ICEntry::Kind_CallVM ||
+                              kind == ICEntry::Kind_DebugTrap ||
+                              kind == ICEntry::Kind_DebugPrologue ||
+                              kind == ICEntry::Kind_DebugEpilogue);
                 // Case F, should only need to undo case B.
-                MOZ_ASSERT_IF(!expectedDebugMode, kind == ICEntry::Kind_CallVM);
+                MOZ_ASSERT_IF(!script->baselineScript()->hasDebugInstrumentation(),
+                              kind == ICEntry::Kind_CallVM);
 
                 // We will have allocated a new recompile info, so delete the
                 // existing one.
                 iter.baselineFrame()->deleteDebugModeOSRInfo();
             }
 
             // The RecompileInfo must already be allocated so that this
             // function may be infallible.
@@ -436,23 +469,30 @@ PatchBaselineFramesForDebugMode(JSContex
             prev->setReturnAddress(reinterpret_cast<uint8_t *>(handlerAddr));
             iter.baselineFrame()->setDebugModeOSRInfo(recompInfo);
 
             entryIndex++;
             break;
           }
 
           case JitFrame_BaselineStub: {
+            JitFrameIterator prev(iter);
+            ++prev;
+            BaselineFrame *prevFrame = prev.baselineFrame();
+            if (!obs.shouldRecompileOrInvalidate(prevFrame->script()))
+                break;
+
+            DebugModeOSREntry &entry = entries[entryIndex];
+
             // If the script wasn't recompiled, there's nothing to patch.
             if (!entry.recompiled())
                 break;
 
             IonBaselineStubFrameLayout *layout =
                 reinterpret_cast<IonBaselineStubFrameLayout *>(iter.fp());
-            MOZ_ASSERT(entry.script->baselineScript()->debugMode() == expectedDebugMode);
             MOZ_ASSERT(layout->maybeStubPtr() == entry.oldStub);
 
             // Patch baseline stub frames for case A above.
             //
             // We need to patch the stub frame to point to an ICStub belonging
             // to the recompiled baseline script. These stubs are allocated up
             // front in CloneOldBaselineStub. They share the same JitCode as
             // the old baseline script's stubs, so we don't need to patch the
@@ -471,66 +511,84 @@ PatchBaselineFramesForDebugMode(JSContex
                 MOZ_ASSERT(entry.newStub);
                 SpewPatchStubFrame(entry.oldStub, entry.newStub);
                 layout->setStubPtr(entry.newStub);
             }
 
             break;
           }
 
-          case JitFrame_IonJS:
+          case JitFrame_IonJS: {
             // Nothing to patch.
-            entryIndex++;
-            for (InlineFrameIterator inlineIter(cx, &iter); inlineIter.more(); ++inlineIter)
-                entryIndex++;
+            InlineFrameIterator inlineIter(cx, &iter);
+            while (true) {
+                if (obs.shouldRecompileOrInvalidate(inlineIter.script()))
+                    entryIndex++;
+                if (!inlineIter.more())
+                    break;
+                ++inlineIter;
+            }
             break;
+          }
 
           default:;
         }
 
         prev = iter.current();
     }
 
     *start = entryIndex;
 }
 
+static void
+SkipInterpreterFrameEntries(const Debugger::ExecutionObservableSet &obs,
+                            const ActivationIterator &activation,
+                            DebugModeOSREntryVector &entries, size_t *start)
+{
+    size_t entryIndex = *start;
+
+    // Skip interpreter frames, which do not need patching.
+    InterpreterActivation *act = activation.activation()->asInterpreter();
+    for (InterpreterFrameIterator iter(act); !iter.done(); ++iter) {
+        if (obs.shouldRecompileOrInvalidate(iter.frame()->script()))
+            entryIndex++;
+    }
+
+    *start = entryIndex;
+}
+
 static bool
-RecompileBaselineScriptForDebugMode(JSContext *cx, JSScript *script)
+RecompileBaselineScriptForDebugMode(JSContext *cx, JSScript *script,
+                                    Debugger::IsObserving observing)
 {
     BaselineScript *oldBaselineScript = script->baselineScript();
 
     // If a script is on the stack multiple times, it may have already
     // been recompiled.
-    bool expectedDebugMode = cx->compartment()->debugMode();
-    if (oldBaselineScript->debugMode() == expectedDebugMode)
+    if (oldBaselineScript->hasDebugInstrumentation() == observing)
         return true;
 
-    JitSpew(JitSpew_BaselineDebugModeOSR, "Recompiling (%s:%d) for debug mode %s",
-            script->filename(), script->lineno(), expectedDebugMode ? "ON" : "OFF");
-
-    CancelOffThreadIonCompile(cx->compartment(), script);
-
-    if (script->hasIonScript())
-        Invalidate(cx, script, /* resetUses = */ false);
+    JitSpew(JitSpew_BaselineDebugModeOSR, "Recompiling (%s:%d) for %s",
+            script->filename(), script->lineno(), observing ? "DEBUGGING" : "NORMAL EXECUTION");
 
     script->setBaselineScript(cx, nullptr);
 
-    MethodStatus status = BaselineCompile(cx, script);
+    MethodStatus status = BaselineCompile(cx, script, /* forceDebugMode = */ observing);
     if (status != Method_Compiled) {
         // We will only fail to recompile for debug mode due to OOM. Restore
         // the old baseline script in case something doesn't properly
         // propagate OOM.
         MOZ_ASSERT(status == Method_Error);
         script->setBaselineScript(cx, oldBaselineScript);
         return false;
     }
 
     // Don't destroy the old baseline script yet, since if we fail any of the
     // recompiles we need to rollback all the old baseline scripts.
-    MOZ_ASSERT(script->baselineScript()->debugMode() == expectedDebugMode);
+    MOZ_ASSERT(script->baselineScript()->hasDebugInstrumentation() == observing);
     return true;
 }
 
 #define PATCHABLE_ICSTUB_KIND_LIST(_)           \
     _(Call_Scripted)                            \
     _(Call_AnyScripted)                         \
     _(Call_Native)                              \
     _(Call_ClassHook)                           \
@@ -620,16 +678,31 @@ CloneOldBaselineStub(JSContext *cx, Debu
 
     if (!entry.newStub)
         return false;
 
     fallbackStub->addNewStub(entry.newStub);
     return true;
 }
 
+static bool
+InvalidateScriptsInZone(JSContext *cx, Zone *zone, const Vector<DebugModeOSREntry> &entries)
+{
+    types::RecompileInfoVector invalid;
+    for (UniqueScriptOSREntryIter iter(entries); !iter.done(); ++iter) {
+        JSScript *script = iter.entry().script;
+        if (script->hasIonScript() && script->compartment()->zone() == zone) {
+            if (!invalid.append(script->ionScript()->recompileInfo()))
+                return false;
+        }
+    }
+    Invalidate(zone->types, cx->runtime()->defaultFreeOp(), invalid);
+    return true;
+}
+
 static void
 UndoRecompileBaselineScriptsForDebugMode(JSContext *cx,
                                          const DebugModeOSREntryVector &entries)
 {
     // In case of failure, roll back the entire set of active scripts so that
     // we don't have to patch return addresses on the stack.
     for (UniqueScriptOSREntryIter iter(entries); !iter.done(); ++iter) {
         const DebugModeOSREntry &entry = iter.entry();
@@ -638,49 +711,65 @@ UndoRecompileBaselineScriptsForDebugMode
         if (entry.recompiled()) {
             script->setBaselineScript(cx, entry.oldBaselineScript);
             BaselineScript::Destroy(cx->runtime()->defaultFreeOp(), baselineScript);
         }
     }
 }
 
 bool
-jit::RecompileOnStackBaselineScriptsForDebugMode(JSContext *cx, JSCompartment *comp)
+jit::RecompileOnStackBaselineScriptsForDebugMode(JSContext *cx,
+                                                 const Debugger::ExecutionObservableSet &obs,
+                                                 Debugger::IsObserving observing)
 {
-    AutoCompartment ac(cx, comp);
-
     // First recompile the active scripts on the stack and patch the live
     // frames.
     Vector<DebugModeOSREntry> entries(cx);
 
-    for (JitActivationIterator iter(cx->runtime()); !iter.done(); ++iter) {
-        if (iter.activation()->compartment() == comp) {
-            if (!CollectOnStackScripts(cx, iter, entries))
+    for (ActivationIterator iter(cx->runtime()); !iter.done(); ++iter) {
+        if (iter->isJit()) {
+            if (!CollectJitStackScripts(cx, obs, iter, entries))
+                return false;
+        } else if (iter->isInterpreter()) {
+            if (!CollectInterpreterStackScripts(cx, obs, iter, entries))
                 return false;
         }
     }
 
+    if (entries.empty())
+        return true;
+
 #ifdef JSGC_GENERATIONAL
     // Scripts can entrain nursery things. See note in js::ReleaseAllJITCode.
-    if (!entries.empty())
-        cx->runtime()->gc.evictNursery();
+    cx->runtime()->gc.evictNursery();
 #endif
 
-    // When the profiler is enabled, we need to suppress sampling from here until
-    // the end of the function, since the basline jit scripts are in a state of
-    // flux.
-    AutoSuppressProfilerSampling suppressProfilerSampling(cx);
+    // When the profiler is enabled, we need to have suppressed sampling,
+    // since the basline jit scripts are in a state of flux.
+    MOZ_ASSERT(!cx->runtime()->isProfilerSamplingEnabled());
+
+    // Invalidate all scripts we are recompiling.
+    if (Zone *zone = obs.singleZone()) {
+        if (!InvalidateScriptsInZone(cx, zone, entries))
+            return false;
+    } else {
+        typedef Debugger::ExecutionObservableSet::ZoneRange ZoneRange;
+        for (ZoneRange r = obs.zones()->all(); !r.empty(); r.popFront()) {
+            if (!InvalidateScriptsInZone(cx, r.front(), entries))
+                return false;
+        }
+    }
 
     // Try to recompile all the scripts. If we encounter an error, we need to
     // roll back as if none of the compilations happened, so that we don't
     // crash.
     for (size_t i = 0; i < entries.length(); i++) {
         JSScript *script = entries[i].script;
-
-        if (!RecompileBaselineScriptForDebugMode(cx, script) ||
+        AutoCompartment ac(cx, script->compartment());
+        if (!RecompileBaselineScriptForDebugMode(cx, script, observing) ||
             !CloneOldBaselineStub(cx, entries, i))
         {
             UndoRecompileBaselineScriptsForDebugMode(cx, entries);
             return false;
         }
     }
 
     // If all recompiles succeeded, destroy the old baseline scripts and patch
@@ -690,19 +779,21 @@ jit::RecompileOnStackBaselineScriptsForD
 
     for (UniqueScriptOSREntryIter iter(entries); !iter.done(); ++iter) {
         const DebugModeOSREntry &entry = iter.entry();
         if (entry.recompiled())
             BaselineScript::Destroy(cx->runtime()->defaultFreeOp(), entry.oldBaselineScript);
     }
 
     size_t processed = 0;
-    for (JitActivationIterator iter(cx->runtime()); !iter.done(); ++iter) {
-        if (iter.activation()->compartment() == comp)
-            PatchBaselineFramesForDebugMode(cx, iter, entries, &processed);
+    for (ActivationIterator iter(cx->runtime()); !iter.done(); ++iter) {
+        if (iter->isJit())
+            PatchBaselineFramesForDebugMode(cx, obs, iter, entries, &processed);
+        else if (iter->isInterpreter())
+            SkipInterpreterFrameEntries(obs, iter, entries, &processed);
     }
     MOZ_ASSERT(processed == entries.length());
 
     return true;
 }
 
 void
 BaselineDebugModeOSRInfo::popValueInto(PCMappingSlotInfo::SlotLocation loc, Value *vp)
--- a/js/src/jit/BaselineDebugModeOSR.h
+++ b/js/src/jit/BaselineDebugModeOSR.h
@@ -6,16 +6,18 @@
 
 #ifndef jit_BaselineDebugModeOSR_h
 #define jit_BaselineDebugModeOSR_h
 
 #include "jit/BaselineFrame.h"
 #include "jit/BaselineIC.h"
 #include "jit/BaselineJIT.h"
 
+#include "vm/Debugger.h"
+
 namespace js {
 namespace jit {
 
 // Note that this file and the corresponding .cpp implement debug mode
 // on-stack recompilation. This is to be distinguished from ordinary
 // Baseline->Ion OSR, which is used to jump into compiled loops.
 
 //
@@ -89,14 +91,16 @@ struct BaselineDebugModeOSRInfo
         valueR0(UndefinedValue()),
         valueR1(UndefinedValue())
     { }
 
     void popValueInto(PCMappingSlotInfo::SlotLocation loc, Value *vp);
 };
 
 bool
-RecompileOnStackBaselineScriptsForDebugMode(JSContext *cx, JSCompartment *comp);
+RecompileOnStackBaselineScriptsForDebugMode(JSContext *cx,
+                                            const Debugger::ExecutionObservableSet &obs,
+                                            Debugger::IsObserving observing);
 
 } // namespace jit
 } // namespace js
 
 #endif // jit_BaselineDebugModeOSR_h
--- a/js/src/jit/BaselineFrame-inl.h
+++ b/js/src/jit/BaselineFrame-inl.h
@@ -29,17 +29,17 @@ inline void
 BaselineFrame::popOffScopeChain()
 {
     scopeChain_ = &scopeChain_->as<ScopeObject>().enclosingScope();
 }
 
 inline void
 BaselineFrame::popWith(JSContext *cx)
 {
-    if (MOZ_UNLIKELY(cx->compartment()->debugMode()))
+    if (MOZ_UNLIKELY(isDebuggee()))
         DebugScopes::onPopWith(this);
 
     MOZ_ASSERT(scopeChain()->is<DynamicWithObject>());
     popOffScopeChain();
 }
 
 inline bool
 BaselineFrame::pushBlock(JSContext *cx, Handle<StaticBlockObject *> block)
--- a/js/src/jit/BaselineFrame.cpp
+++ b/js/src/jit/BaselineFrame.cpp
@@ -189,27 +189,29 @@ BaselineFrame::initForOsr(InterpreterFra
         BaselineFrame::Size() +
         numStackValues * sizeof(Value);
 
     MOZ_ASSERT(numValueSlots() == numStackValues);
 
     for (uint32_t i = 0; i < numStackValues; i++)
         *valueSlot(i) = fp->slots()[i];
 
-    if (cx->compartment()->debugMode()) {
-        // In debug mode, update any Debugger.Frame objects for the
+    if (fp->isDebuggee()) {
+        // For debuggee frames, update any Debugger.Frame objects for the
         // InterpreterFrame to point to the BaselineFrame.
 
         // The caller pushed a fake return address. ScriptFrameIter, used by the
         // debugger, wants a valid return address, but it's okay to just pick one.
         // In debug mode there's always at least 1 ICEntry (since there are always
         // debug prologue/epilogue calls).
         JitFrameIterator iter(cx);
         MOZ_ASSERT(iter.returnAddress() == nullptr);
         BaselineScript *baseline = fp->script()->baselineScript();
         iter.current()->setReturnAddress(baseline->returnAddressForIC(baseline->icEntry(0)));
 
         if (!Debugger::handleBaselineOsr(cx, fp, this))
             return false;
+
+        setIsDebuggee();
     }
 
     return true;
 }
--- a/js/src/jit/BaselineFrame.h
+++ b/js/src/jit/BaselineFrame.h
@@ -42,18 +42,24 @@ class BaselineFrame
         HAS_CALL_OBJ     = 1 << 2,
 
         // Frame has an arguments object, argsObj_.
         HAS_ARGS_OBJ     = 1 << 4,
 
         // See InterpreterFrame::PREV_UP_TO_DATE.
         PREV_UP_TO_DATE  = 1 << 5,
 
+        // Frame has execution observed by a Debugger.
+        //
+        // See comment above 'debugMode' in jscompartment.h for explanation of
+        // invariants of debuggee compartments, scripts, and frames.
+        DEBUGGEE         = 1 << 6,
+
         // Eval frame, see the "eval frames" comment.
-        EVAL             = 1 << 6,
+        EVAL             = 1 << 7,
 
         // Frame has profiler entry pushed.
         HAS_PUSHED_SPS_FRAME = 1 << 8,
 
         // Frame has over-recursed on an early check.
         OVER_RECURSED    = 1 << 9,
 
         // Frame has a BaselineRecompileInfo stashed in the scratch value
@@ -263,16 +269,27 @@ class BaselineFrame
 
     bool prevUpToDate() const {
         return flags_ & PREV_UP_TO_DATE;
     }
     void setPrevUpToDate() {
         flags_ |= PREV_UP_TO_DATE;
     }
 
+    bool isDebuggee() const {
+        return flags_ & DEBUGGEE;
+    }
+    void setIsDebuggee() {
+        flags_ |= DEBUGGEE;
+    }
+    void unsetIsDebuggee() {
+        MOZ_ASSERT(!script()->isDebuggee());
+        flags_ &= ~DEBUGGEE;
+    }
+
     JSScript *evalScript() const {
         MOZ_ASSERT(isEvalFrame());
         return evalScript_;
     }
 
     bool hasPushedSPSFrame() const {
         return flags_ & HAS_PUSHED_SPS_FRAME;
     }
--- a/js/src/jit/BaselineJIT.cpp
+++ b/js/src/jit/BaselineJIT.cpp
@@ -153,18 +153,20 @@ jit::EnterBaselineAtBranch(JSContext *cx
 
     BaselineScript *baseline = fp->script()->baselineScript();
 
     EnterJitData data(cx);
     data.jitcode = baseline->nativeCodeForPC(fp->script(), pc);
 
     // Skip debug breakpoint/trap handler, the interpreter already handled it
     // for the current op.
-    if (cx->compartment()->debugMode())
+    if (fp->isDebuggee()) {
+        MOZ_ASSERT(baseline->hasDebugInstrumentation());
         data.jitcode += MacroAssembler::ToggledCallSize(data.jitcode);
+    }
 
     data.osrFrame = fp;
     data.osrNumStackValues = fp->script()->nfixed() + cx->interpreterRegs().stackDepth();
 
     RootedValue thisv(cx);
 
     if (fp->isNonEvalFunctionFrame()) {
         data.constructing = fp->isConstructing();
@@ -196,17 +198,17 @@ jit::EnterBaselineAtBranch(JSContext *cx
     if (status != IonExec_Ok)
         return status;
 
     fp->setReturnValue(data.result);
     return IonExec_Ok;
 }
 
 MethodStatus
-jit::BaselineCompile(JSContext *cx, JSScript *script)
+jit::BaselineCompile(JSContext *cx, JSScript *script, bool forceDebugInstrumentation)
 {
     MOZ_ASSERT(!script->hasBaselineScript());
     MOZ_ASSERT(script->canBaselineCompile());
     MOZ_ASSERT(IsBaselineEnabled(cx));
 
     script->ensureNonLazyCanonicalFunction(cx);
 
     LifoAlloc alloc(TempAllocator::PreferredLifoChunkSize);
@@ -214,30 +216,32 @@ jit::BaselineCompile(JSContext *cx, JSSc
     if (!temp)
         return Method_Error;
 
     IonContext ictx(cx, temp);
 
     BaselineCompiler compiler(cx, *temp, script);
     if (!compiler.init())
         return Method_Error;
+    if (forceDebugInstrumentation)
+        compiler.setCompileDebugInstrumentation();
 
     MethodStatus status = compiler.compile();
 
     MOZ_ASSERT_IF(status == Method_Compiled, script->hasBaselineScript());
     MOZ_ASSERT_IF(status != Method_Compiled, !script->hasBaselineScript());
 
     if (status == Method_CantCompile)
         script->setBaselineScript(cx, BASELINE_DISABLED_SCRIPT);
 
     return status;
 }
 
 static MethodStatus
-CanEnterBaselineJIT(JSContext *cx, HandleScript script, bool osr)
+CanEnterBaselineJIT(JSContext *cx, HandleScript script, InterpreterFrame *osrFrame)
 {
     MOZ_ASSERT(jit::IsBaselineEnabled(cx));
 
     // Skip if the script has been disabled.
     if (!script->canBaselineCompile())
         return Method_Skipped;
 
     if (script->length() > BaselineScript::MAX_JSSCRIPT_LENGTH)
@@ -255,17 +259,17 @@ CanEnterBaselineJIT(JSContext *cx, Handl
     // Check script warm-up counter.
     //
     // Also eagerly compile if we are in parallel warmup, the point of which
     // is to gather type information so that the script may be compiled for
     // parallel execution. We want to avoid the situation of OSRing during
     // warm-up and only gathering type information for the loop, and not the
     // rest of the function.
     if (cx->runtime()->forkJoinWarmup > 0) {
-        if (osr)
+        if (osrFrame)
             return Method_Skipped;
     } else if (script->incWarmUpCounter() <= js_JitOptions.baselineWarmUpThreshold) {
         return Method_Skipped;
     }
 
     if (script->isCallsiteClone()) {
         // Ensure the original function is compiled too, so that bailouts from
         // Ion code have a BaselineScript to resume into.
@@ -277,17 +281,20 @@ CanEnterBaselineJIT(JSContext *cx, Handl
 
         if (!original->hasBaselineScript()) {
             MethodStatus status = BaselineCompile(cx, original);
             if (status != Method_Compiled)
                 return status;
         }
     }
 
-    return BaselineCompile(cx, script);
+    // Frames can be marked as debuggee frames independently of its underlying
+    // script being a debuggee script, e.g., when performing
+    // Debugger.Frame.prototype.eval.
+    return BaselineCompile(cx, script, osrFrame && osrFrame->isDebuggee());
 }
 
 MethodStatus
 jit::CanEnterBaselineAtBranch(JSContext *cx, InterpreterFrame *fp, bool newType)
 {
    // If constructing, allocate a new |this| object.
    if (fp->isConstructing() && fp->functionThis().isPrimitive()) {
        RootedObject callee(cx, &fp->callee());
@@ -296,17 +303,17 @@ jit::CanEnterBaselineAtBranch(JSContext 
            return Method_Skipped;
        fp->functionThis().setObject(*obj);
    }
 
    if (!CheckFrame(fp))
        return Method_CantCompile;
 
    RootedScript script(cx, fp->script());
-   return CanEnterBaselineJIT(cx, script, /* osr = */true);
+   return CanEnterBaselineJIT(cx, script, fp);
 }
 
 MethodStatus
 jit::CanEnterBaselineMethod(JSContext *cx, RunState &state)
 {
     if (state.isInvoke()) {
         InvokeState &invoke = *state.asInvoke();
 
@@ -322,17 +329,17 @@ jit::CanEnterBaselineMethod(JSContext *c
         ExecuteType type = state.asExecute()->type();
         if (type == EXECUTE_DEBUG || type == EXECUTE_DEBUG_GLOBAL) {
             JitSpew(JitSpew_BaselineAbort, "debugger frame");
             return Method_CantCompile;
         }
     }
 
     RootedScript script(cx, state.script());
-    return CanEnterBaselineJIT(cx, script, /* osr = */false);
+    return CanEnterBaselineJIT(cx, script, /* osrFrame = */ nullptr);
 };
 
 BaselineScript *
 BaselineScript::New(JSScript *jsscript, uint32_t prologueOffset, uint32_t epilogueOffset,
                     uint32_t spsPushToggleOffset, uint32_t postDebugPrologueOffset,
                     size_t icEntries, size_t pcMappingIndexEntries, size_t pcMappingSize,
                     size_t bytecodeTypeMapEntries, size_t yieldEntries)
 {
@@ -768,17 +775,17 @@ BaselineScript::pcForNativeAddress(JSScr
 }
 
 void
 BaselineScript::toggleDebugTraps(JSScript *script, jsbytecode *pc)
 {
     MOZ_ASSERT(script->baselineScript() == this);
 
     // Only scripts compiled for debug mode have toggled calls.
-    if (!debugMode())
+    if (!hasDebugInstrumentation())
         return;
 
     SrcNoteLineScanner scanner(script->notes(), script->lineno());
 
     for (uint32_t i = 0; i < numPCMappingIndexEntries(); i++) {
         PCMappingIndexEntry &entry = pcMappingIndexEntry(i);
 
         CompactBufferReader reader(pcMappingReader(i));
--- a/js/src/jit/BaselineJIT.h
+++ b/js/src/jit/BaselineJIT.h
@@ -144,19 +144,19 @@ struct BaselineScript
         // Flag set when discarding JIT code, to indicate this script is
         // on the stack and should not be discarded.
         ACTIVE = 1 << 1,
 
         // Flag set when the script contains any writes to its on-stack
         // (rather than call object stored) arguments.
         MODIFIES_ARGUMENTS = 1 << 2,
 
-        // Flag set when compiled for use for debug mode. Handles various
+        // Flag set when compiled for use with Debugger. Handles various
         // Debugger hooks and compiles toggled calls for traps.
-        DEBUG_MODE = 1 << 3,
+        HAS_DEBUG_INSTRUMENTATION = 1 << 3,
 
         // Flag set if this script has ever been Ion compiled, either directly
         // or inlined into another script. This is cleared when the script's
         // type information or caches are cleared.
         ION_COMPILED_OR_INLINED = 1 << 4
     };
 
   private:
@@ -227,21 +227,21 @@ struct BaselineScript
 
     void setModifiesArguments() {
         flags_ |= MODIFIES_ARGUMENTS;
     }
     bool modifiesArguments() {
         return flags_ & MODIFIES_ARGUMENTS;
     }
 
-    void setDebugMode() {
-        flags_ |= DEBUG_MODE;
+    void setHasDebugInstrumentation() {
+        flags_ |= HAS_DEBUG_INSTRUMENTATION;
     }
-    bool debugMode() const {
-        return flags_ & DEBUG_MODE;
+    bool hasDebugInstrumentation() const {
+        return flags_ & HAS_DEBUG_INSTRUMENTATION;
     }
 
     void setIonCompiledOrInlined() {
         flags_ |= ION_COMPILED_OR_INLINED;
     }
     void clearIonCompiledOrInlined() {
         flags_ &= ~ION_COMPILED_OR_INLINED;
     }
@@ -456,14 +456,14 @@ BailoutIonToBaseline(JSContext *cx, JitA
                      bool *poppedLastSPSFrame);
 
 // Mark baseline scripts on the stack as active, so that they are not discarded
 // during GC.
 void
 MarkActiveBaselineScripts(Zone *zone);
 
 MethodStatus
-BaselineCompile(JSContext *cx, JSScript *script);
+BaselineCompile(JSContext *cx, JSScript *script, bool forceDebugInstrumentation = false);
 
 } // namespace jit
 } // namespace js
 
 #endif /* jit_BaselineJIT_h */
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -11,17 +11,16 @@
 
 #include "jscompartment.h"
 #include "jsprf.h"
 
 #include "asmjs/AsmJSModule.h"
 #include "gc/Marking.h"
 #include "jit/AliasAnalysis.h"
 #include "jit/BacktrackingAllocator.h"
-#include "jit/BaselineDebugModeOSR.h"
 #include "jit/BaselineFrame.h"
 #include "jit/BaselineInspector.h"
 #include "jit/BaselineJIT.h"
 #include "jit/CodeGenerator.h"
 #include "jit/EdgeCaseAnalysis.h"
 #include "jit/EffectiveAddressAnalysis.h"
 #include "jit/IonAnalysis.h"
 #include "jit/IonBuilder.h"
@@ -2119,19 +2118,19 @@ Compile(JSContext *cx, HandleScript scri
     MOZ_ASSERT(jit::IsBaselineEnabled(cx));
     MOZ_ASSERT_IF(osrPc != nullptr, LoopEntryCanIonOsr(osrPc));
     MOZ_ASSERT_IF(executionMode == ParallelExecution, !osrFrame && !osrPc);
     MOZ_ASSERT_IF(executionMode == ParallelExecution, !HasIonScript(script, executionMode));
 
     if (!script->hasBaselineScript())
         return Method_Skipped;
 
-    if (cx->compartment()->debugMode()) {
+    if (script->isDebuggee() || (osrFrame && osrFrame->isDebuggee())) {
         JitSpew(JitSpew_IonAbort, "debugging");
-        return Method_CantCompile;
+        return Method_Skipped;
     }
 
     if (!CheckScript(cx, script, bool(osrPc))) {
         JitSpew(JitSpew_IonAbort, "Aborted compilation of %s:%d", script->filename(), script->lineno());
         return Method_CantCompile;
     }
 
     MethodStatus status = CheckScriptSize(cx, script);
@@ -3176,98 +3175,16 @@ jit::TraceIonScripts(JSTracer* trc, JSSc
     if (script->hasParallelIonScript())
         jit::IonScript::Trace(trc, script->parallelIonScript());
 
     if (script->hasBaselineScript())
         jit::BaselineScript::Trace(trc, script->baselineScript());
 }
 
 bool
-jit::RematerializeAllFrames(JSContext *cx, JSCompartment *comp)
-{
-    for (JitActivationIterator iter(comp->runtimeFromMainThread()); !iter.done(); ++iter) {
-        if (iter.activation()->compartment() == comp) {
-            for (JitFrameIterator frameIter(iter); !frameIter.done(); ++frameIter) {
-                if (!frameIter.isIonJS())
-                    continue;
-                if (!iter.activation()->asJit()->getRematerializedFrame(cx, frameIter))
-                    return false;
-            }
-        }
-    }
-    return true;
-}
-
-bool
-jit::UpdateForDebugMode(JSContext *maybecx, JSCompartment *comp,
-                     AutoDebugModeInvalidation &invalidate)
-{
-    MOZ_ASSERT(invalidate.isFor(comp));
-
-    // Schedule invalidation of all optimized JIT code since debug mode
-    // invalidates assumptions.
-    invalidate.scheduleInvalidation(comp->debugMode());
-
-    // Recompile on-stack baseline scripts if we have a cx.
-    if (maybecx) {
-        IonContext ictx(maybecx, nullptr);
-        if (!RecompileOnStackBaselineScriptsForDebugMode(maybecx, comp)) {
-            js_ReportOutOfMemory(maybecx);
-            return false;
-        }
-    }
-
-    return true;
-}
-
-AutoDebugModeInvalidation::~AutoDebugModeInvalidation()
-{
-    MOZ_ASSERT(!!comp_ != !!zone_);
-
-    if (needInvalidation_ == NoNeed)
-        return;
-
-    Zone *zone = zone_ ? zone_ : comp_->zone();
-    JSRuntime *rt = zone->runtimeFromMainThread();
-    FreeOp *fop = rt->defaultFreeOp();
-
-    if (comp_) {
-        StopAllOffThreadCompilations(comp_);
-    } else {
-        for (CompartmentsInZoneIter comp(zone_); !comp.done(); comp.next())
-            StopAllOffThreadCompilations(comp);
-    }
-
-    // Don't discard active baseline scripts. They are recompiled for debug
-    // mode.
-    jit::MarkActiveBaselineScripts(zone);
-
-    for (JitActivationIterator iter(rt); !iter.done(); ++iter) {
-        JSCompartment *comp = iter->compartment();
-        if (comp_ == comp || zone_ == comp->zone()) {
-            IonContext ictx(CompileRuntime::get(rt));
-            JitSpew(JitSpew_IonInvalidate, "Invalidating frames for debug mode toggle");
-            InvalidateActivation(fop, iter, true);
-        }
-    }
-
-    for (gc::ZoneCellIter i(zone, gc::FINALIZE_SCRIPT); !i.done(); i.next()) {
-        JSScript *script = i.get<JSScript>();
-        if (script->compartment() == comp_ || zone_) {
-            FinishInvalidation<SequentialExecution>(fop, script);
-            FinishInvalidation<ParallelExecution>(fop, script);
-            FinishDiscardBaselineScript(fop, script);
-            script->resetWarmUpCounter();
-        } else if (script->hasBaselineScript()) {
-            script->baselineScript()->resetActive();
-        }
-    }
-}
-
-bool
 jit::JitSupportsFloatingPoint()
 {
     return js::jit::MacroAssembler::SupportsFloatingPoint();
 }
 
 bool
 jit::JitSupportsSimd()
 {
--- a/js/src/jit/Ion.h
+++ b/js/src/jit/Ion.h
@@ -196,19 +196,15 @@ NumLocalsAndArgs(JSScript *script)
 void ForbidCompilation(JSContext *cx, JSScript *script);
 void ForbidCompilation(JSContext *cx, JSScript *script, ExecutionMode mode);
 
 void PurgeCaches(JSScript *script);
 size_t SizeOfIonData(JSScript *script, mozilla::MallocSizeOf mallocSizeOf);
 void DestroyIonScripts(FreeOp *fop, JSScript *script);
 void TraceIonScripts(JSTracer* trc, JSScript *script);
 
-bool RematerializeAllFrames(JSContext *cx, JSCompartment *comp);
-bool UpdateForDebugMode(JSContext *maybecx, JSCompartment *comp,
-                        AutoDebugModeInvalidation &invalidate);
-
 bool JitSupportsFloatingPoint();
 bool JitSupportsSimd();
 
 } // namespace jit
 } // namespace js
 
 #endif /* jit_Ion_h */
--- a/js/src/jit/IonAnalysis.cpp
+++ b/js/src/jit/IonAnalysis.cpp
@@ -3091,17 +3091,17 @@ jit::AnalyzeArgumentsUsage(JSContext *cx
     // object can escape through assignments to the function's named arguments,
     // and also simplifies handling of early returns.
     script->setNeedsArgsObj(true);
 
     // Always construct arguments objects when in debug mode and for generator
     // scripts (generators can be suspended when speculation fails).
     //
     // FIXME: Don't build arguments for ES6 generator expressions.
-    if (cx->compartment()->debugMode() || script->isGenerator())
+    if (scriptArg->isDebuggee() || script->isGenerator())
         return true;
 
     // If the script has dynamic name accesses which could reach 'arguments',
     // the parser will already have checked to ensure there are no explicit
     // uses of 'arguments' in the function. If there are such uses, the script
     // will be marked as definitely needing an arguments object.
     //
     // New accesses on 'arguments' can occur through 'eval' or the debugger
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -397,16 +397,19 @@ IonBuilder::canInlineTarget(JSFunction *
         return DontInline(inlineScript, "Heavyweight function");
 
     if (inlineScript->uninlineable())
         return DontInline(inlineScript, "Uninlineable script");
 
     if (inlineScript->needsArgsObj())
         return DontInline(inlineScript, "Script that needs an arguments object");
 
+    if (inlineScript->isDebuggee())
+        return DontInline(inlineScript, "Script is debuggee");
+
     types::TypeObjectKey *targetType = types::TypeObjectKey::get(target);
     if (targetType->unknownProperties())
         return DontInline(inlineScript, "Target type has unknown properties");
 
     return InliningDecision_Inline;
 }
 
 void
--- a/js/src/jit/IonFrames.cpp
+++ b/js/src/jit/IonFrames.cpp
@@ -31,16 +31,17 @@
 #include "vm/Debugger.h"
 #include "vm/ForkJoin.h"
 #include "vm/Interpreter.h"
 #include "vm/TraceLogging.h"
 
 #include "jsscriptinlines.h"
 #include "gc/Nursery-inl.h"
 #include "jit/JitFrameIterator-inl.h"
+#include "vm/Debugger-inl.h"
 #include "vm/Probes-inl.h"
 
 namespace js {
 namespace jit {
 
 // Given a slot index, returns the offset, in bytes, of that slot from an
 // IonJSFrameLayout. Slot distances are uniform across architectures, however,
 // the distance does depend on the size of the frame header.
@@ -415,33 +416,47 @@ CloseLiveIterator(JSContext *cx, const I
 static void
 HandleExceptionIon(JSContext *cx, const InlineFrameIterator &frame, ResumeFromException *rfe,
                    bool *overrecursed)
 {
     RootedScript script(cx, frame.script());
     jsbytecode *pc = frame.pc();
 
     bool bailedOutForDebugMode = false;
-    if (cx->compartment()->debugMode()) {
-        // If we have an exception from within Ion and the debugger is active,
-        // we do the following:
-        //
-        //   1. Bailout to baseline to reconstruct a baseline frame.
-        //   2. Resume immediately into the exception tail afterwards, and
-        //   handle the exception again with the top frame now a baseline
-        //   frame.
-        //
-        // An empty exception info denotes that we're propagating an Ion
-        // exception due to debug mode, which BailoutIonToBaseline needs to
-        // know. This is because we might not be able to fully reconstruct up
-        // to the stack depth at the snapshot, as we could've thrown in the
-        // middle of a call.
-        ExceptionBailoutInfo propagateInfo;
-        uint32_t retval = ExceptionHandlerBailout(cx, frame, rfe, propagateInfo, overrecursed);
-        bailedOutForDebugMode = retval == BAILOUT_RETURN_OK;
+    if (cx->compartment()->isDebuggee()) {
+        // We need to bail when debug mode is active to observe the Debugger's
+        // exception unwinding handler if either a Debugger is observing all
+        // execution in the compartment, or it has observed this frame, i.e.,
+        // by there being a debuggee RematerializedFrame.
+        bool shouldBail = cx->compartment()->debugObservesAllExecution();
+        if (!shouldBail) {
+            JitActivation *act = cx->mainThread().activation()->asJit();
+            RematerializedFrame *rematFrame =
+                act->lookupRematerializedFrame(frame.frame().fp(), frame.frameNo());
+            shouldBail = rematFrame && rematFrame->isDebuggee();
+        }
+
+        if (shouldBail) {
+            // If we have an exception from within Ion and the debugger is active,
+            // we do the following:
+            //
+            //   1. Bailout to baseline to reconstruct a baseline frame.
+            //   2. Resume immediately into the exception tail afterwards, and
+            //      handle the exception again with the top frame now a baseline
+            //      frame.
+            //
+            // An empty exception info denotes that we're propagating an Ion
+            // exception due to debug mode, which BailoutIonToBaseline needs to
+            // know. This is because we might not be able to fully reconstruct up
+            // to the stack depth at the snapshot, as we could've thrown in the
+            // middle of a call.
+            ExceptionBailoutInfo propagateInfo;
+            uint32_t retval = ExceptionHandlerBailout(cx, frame, rfe, propagateInfo, overrecursed);
+            bailedOutForDebugMode = retval == BAILOUT_RETURN_OK;
+        }
     }
 
     if (!script->hasTrynotes())
         return;
 
     JSTryNote *tn = script->trynotes()->vector;
     JSTryNote *tnEnd = tn + script->trynotes()->length;
 
@@ -549,20 +564,20 @@ HandleExceptionBaseline(JSContext *cx, c
     // callback, which cannot easily force a return.
     if (cx->isPropagatingForcedReturn()) {
         cx->clearPropagatingForcedReturn();
         ForcedReturn(cx, frame, pc, rfe, calledDebugEpilogue);
         return;
     }
 
     RootedValue exception(cx);
-    if (cx->isExceptionPending() && cx->compartment()->debugMode() &&
+    BaselineFrame *baselineFrame = frame.baselineFrame();
+    if (cx->isExceptionPending() && baselineFrame->isDebuggee() &&
         cx->getPendingException(&exception) && !exception.isMagic(JS_GENERATOR_CLOSING))
     {
-        BaselineFrame *baselineFrame = frame.baselineFrame();
         switch (Debugger::onExceptionUnwind(cx, baselineFrame)) {
           case JSTRAP_ERROR:
             // Uncatchable exception.
             MOZ_ASSERT(!cx->isExceptionPending());
             break;
 
           case JSTRAP_CONTINUE:
           case JSTRAP_THROW:
@@ -773,17 +788,17 @@ HandleException(ResumeFromException *rfe
             JSScript *script = iter.script();
             probes::ExitScript(cx, script, script->functionNonDelazifying(),
                                iter.baselineFrame()->hasPushedSPSFrame());
             // After this point, any pushed SPS frame would have been popped if it needed
             // to be.  Unset the flag here so that if we call DebugEpilogue below,
             // it doesn't try to pop the SPS frame again.
             iter.baselineFrame()->unsetPushedSPSFrame();
 
-            if (cx->compartment()->debugMode() && !calledDebugEpilogue) {
+            if (iter.baselineFrame()->isDebuggee() && !calledDebugEpilogue) {
                 // If we still need to call the DebugEpilogue, we must
                 // remember the pc we unwound the scope chain to, as it will
                 // be out of sync with the frame's actual pc.
                 if (unwoundScopeToPc)
                     iter.baselineFrame()->setUnwoundScopeOverridePc(unwoundScopeToPc);
 
                 // If DebugEpilogue returns |true|, we have to perform a forced
                 // return, e.g. return frame->returnValue() to the caller.
--- a/js/src/jit/RematerializedFrame.cpp
+++ b/js/src/jit/RematerializedFrame.cpp
@@ -26,16 +26,17 @@ struct CopyValueToRematerializedFrame
     void operator()(const Value &v) {
         *slots++ = v;
     }
 };
 
 RematerializedFrame::RematerializedFrame(ThreadSafeContext *cx, uint8_t *top,
                                          unsigned numActualArgs, InlineFrameIterator &iter)
   : prevUpToDate_(false),
+    isDebuggee_(iter.script()->isDebuggee()),
     top_(top),
     pc_(iter.pc()),
     frameNo_(iter.frameNo()),
     numActualArgs_(numActualArgs),
     script_(iter.script())
 {
     CopyValueToRematerializedFrame op(slots_);
     MaybeReadFallback fallback(MagicValue(JS_OPTIMIZED_OUT));
--- a/js/src/jit/RematerializedFrame.h
+++ b/js/src/jit/RematerializedFrame.h
@@ -4,32 +4,36 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef jit_RematerializedFrame_h
 #define jit_RematerializedFrame_h
 
 #include "jsfun.h"
 
+#include "jit/IonFrames.h"
 #include "jit/JitFrameIterator.h"
 
 #include "vm/Stack.h"
 
 namespace js {
 namespace jit {
 
 //
 // An optimized frame that has been rematerialized with values read out of
 // Snapshots.
 //
 class RematerializedFrame
 {
     // See DebugScopes::updateLiveScopes.
     bool prevUpToDate_;
 
+    // Propagated to the Baseline frame once this is popped.
+    bool isDebuggee_;
+
     // The fp of the top frame associated with this possibly inlined frame.
     uint8_t *top_;
 
     // The bytecode at the time of rematerialization.
     jsbytecode *pc_;
 
     size_t frameNo_;
     unsigned numActualArgs_;
@@ -64,19 +68,34 @@ class RematerializedFrame
 
     bool prevUpToDate() const {
         return prevUpToDate_;
     }
     void setPrevUpToDate() {
         prevUpToDate_ = true;
     }
 
+    bool isDebuggee() const {
+        return isDebuggee_;
+    }
+    void setIsDebuggee() {
+        isDebuggee_ = true;
+    }
+    void unsetIsDebuggee() {
+        MOZ_ASSERT(!script()->isDebuggee());
+        isDebuggee_ = false;
+    }
+
     uint8_t *top() const {
         return top_;
     }
+    JSScript *outerScript() const {
+        IonJSFrameLayout *jsFrame = (IonJSFrameLayout *)top_;
+        return ScriptFromCalleeToken(jsFrame->calleeToken());
+    }
     jsbytecode *pc() const {
         return pc_;
     }
     size_t frameNo() const {
         return frameNo_;
     }
     bool inlined() const {
         return frameNo_ > 0;
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -740,16 +740,21 @@ GetIndexFromString(JSString *str)
         return UINT32_MAX;
 
     return index;
 }
 
 bool
 DebugPrologue(JSContext *cx, BaselineFrame *frame, jsbytecode *pc, bool *mustReturn)
 {
+    // Mark the BaselineFrame as a debuggee frame if necessary. This must be
+    // done dynamically, so we might as well do it here.
+    if (frame->script()->isDebuggee())
+        frame->setIsDebuggee();
+
     *mustReturn = false;
 
     switch (Debugger::onEnterFrame(cx, frame)) {
       case JSTRAP_CONTINUE:
         return true;
 
       case JSTRAP_RETURN:
         // The script is going to return immediately, so we have to call the
@@ -958,17 +963,17 @@ InitRestParameter(JSContext *cx, uint32_
 bool
 HandleDebugTrap(JSContext *cx, BaselineFrame *frame, uint8_t *retAddr, bool *mustReturn)
 {
     *mustReturn = false;
 
     RootedScript script(cx, frame->script());
     jsbytecode *pc = script->baselineScript()->icEntryFromReturnAddress(retAddr).pc(script);
 
-    MOZ_ASSERT(cx->compartment()->debugMode());
+    MOZ_ASSERT(frame->isDebuggee());
     MOZ_ASSERT(script->stepModeEnabled() || script->hasBreakpointsAt(pc));
 
     RootedValue rval(cx);
     JSTrapStatus status = JSTRAP_CONTINUE;
 
     if (script->stepModeEnabled())
         status = Debugger::onSingleStep(cx, &rval);
 
@@ -1001,17 +1006,17 @@ HandleDebugTrap(JSContext *cx, BaselineF
 bool
 OnDebuggerStatement(JSContext *cx, BaselineFrame *frame, jsbytecode *pc, bool *mustReturn)
 {
     *mustReturn = false;
 
     RootedScript script(cx, frame->script());
     RootedValue rval(cx);
 
-    switch (Debugger::onDebuggerStatement(cx, &rval)) {
+    switch (Debugger::onDebuggerStatement(cx, frame, &rval)) {
       case JSTRAP_ERROR:
         return false;
 
       case JSTRAP_CONTINUE:
         return true;
 
       case JSTRAP_RETURN:
         frame->setReturnValue(rval);
@@ -1038,20 +1043,19 @@ PopBlockScope(JSContext *cx, BaselineFra
 {
     frame->popBlock(cx);
     return true;
 }
 
 bool
 DebugLeaveBlock(JSContext *cx, BaselineFrame *frame, jsbytecode *pc)
 {
-    MOZ_ASSERT(frame->script()->baselineScript()->debugMode());
-
-    DebugScopes::onPopBlock(cx, frame, pc);
-
+    MOZ_ASSERT(frame->script()->baselineScript()->hasDebugInstrumentation());
+    if (cx->compartment()->isDebuggee())
+        DebugScopes::onPopBlock(cx, frame, pc);
     return true;
 }
 
 bool
 EnterWith(JSContext *cx, BaselineFrame *frame, HandleValue val, Handle<StaticWithObject *> templ)
 {
     return EnterWithOperation(cx, frame, val, templ);
 }
--- a/js/src/jit/shared/BaselineCompiler-shared.cpp
+++ b/js/src/jit/shared/BaselineCompiler-shared.cpp
@@ -4,26 +4,28 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "jit/shared/BaselineCompiler-shared.h"
 
 #include "jit/BaselineIC.h"
 #include "jit/VMFunctions.h"
 
+#include "jsscriptinlines.h"
+
 using namespace js;
 using namespace js::jit;
 
 BaselineCompilerShared::BaselineCompilerShared(JSContext *cx, TempAllocator &alloc, JSScript *script)
   : cx(cx),
     script(script),
     pc(script->code()),
     ionCompileable_(jit::IsIonEnabled(cx) && CanIonCompileScript(cx, script, false)),
     ionOSRCompileable_(jit::IsIonEnabled(cx) && CanIonCompileScript(cx, script, true)),
-    debugMode_(cx->compartment()->debugMode()),
+    compileDebugInstrumentation_(script->isDebuggee()),
     alloc_(alloc),
     analysis_(alloc, script),
     frame(script, masm),
     stubSpace_(),
     icEntries_(),
     pcMappingEntries_(),
     icLoadLabels_(),
     pushedBeforeCall_(0),
--- a/js/src/jit/shared/BaselineCompiler-shared.h
+++ b/js/src/jit/shared/BaselineCompiler-shared.h
@@ -19,17 +19,17 @@ class BaselineCompilerShared
 {
   protected:
     JSContext *cx;
     JSScript *script;
     jsbytecode *pc;
     MacroAssembler masm;
     bool ionCompileable_;
     bool ionOSRCompileable_;
-    bool debugMode_;
+    bool compileDebugInstrumentation_;
 
     TempAllocator &alloc_;
     BytecodeAnalysis analysis_;
     FrameInfo frame;
 
     FallbackICStubSpace stubSpace_;
     js::Vector<ICEntry, 16, SystemAllocPolicy> icEntries_;
 
@@ -136,14 +136,18 @@ class BaselineCompilerShared
         CHECK_OVER_RECURSED
     };
     bool callVM(const VMFunction &fun, CallVMPhase phase=POST_INITIALIZE);
 
   public:
     BytecodeAnalysis &analysis() {
         return analysis_;
     }
+
+    void setCompileDebugInstrumentation() {
+        compileDebugInstrumentation_ = true;
+    }
 };
 
 } // namespace jit
 } // namespace js
 
 #endif /* jit_shared_BaselineCompiler_shared_h */
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -85,17 +85,17 @@ MSG_DEF(JSMSG_INVALID_DESCRIPTOR,      0
 MSG_DEF(JSMSG_OBJECT_NOT_EXTENSIBLE,   1, JSEXN_TYPEERR, "{0} is not extensible")
 MSG_DEF(JSMSG_CANT_REDEFINE_PROP,      1, JSEXN_TYPEERR, "can't redefine non-configurable property '{0}'")
 MSG_DEF(JSMSG_CANT_APPEND_TO_ARRAY,    0, JSEXN_TYPEERR, "can't add elements past the end of an array if its length property is unwritable")
 MSG_DEF(JSMSG_CANT_REDEFINE_ARRAY_LENGTH, 0, JSEXN_TYPEERR, "can't redefine array length")
 MSG_DEF(JSMSG_CANT_DEFINE_PAST_ARRAY_LENGTH, 0, JSEXN_TYPEERR, "can't define array index property past the end of an array with non-writable length")
 MSG_DEF(JSMSG_BAD_GET_SET_FIELD,       1, JSEXN_TYPEERR, "property descriptor's {0} field is neither undefined nor a function")
 MSG_DEF(JSMSG_THROW_TYPE_ERROR,        0, JSEXN_TYPEERR, "'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them")
 MSG_DEF(JSMSG_NOT_EXPECTED_TYPE,       3, JSEXN_TYPEERR, "{0}: expected {1}, got {2}")
-MSG_DEF(JSMSG_NEED_DEBUG_MODE,         0, JSEXN_ERR, "function can be called only in debug mode")
+MSG_DEF(JSMSG_NEED_DEBUG_MODE,         0, JSEXN_ERR, "function can be called only if a Debugger is observing all execution")
 MSG_DEF(JSMSG_NOT_ITERABLE,            1, JSEXN_TYPEERR, "{0} is not iterable")
 MSG_DEF(JSMSG_ALREADY_HAS_PRAGMA,      2, JSEXN_NONE, "{0} is being assigned a {1}, but already has one")
 MSG_DEF(JSMSG_NEXT_RETURNED_PRIMITIVE, 0, JSEXN_TYPEERR, "iterator.next() returned a non-object value")
 MSG_DEF(JSMSG_WRONG_VALUE,             2, JSEXN_ERR, "expected {0} but found {1}")
 MSG_DEF(JSMSG_SETPROTOTYPEOF_FAIL,     1, JSEXN_TYPEERR, "[[SetPrototypeOf]] failed on {0}")
 MSG_DEF(JSMSG_INVALID_ARG_TYPE,        3, JSEXN_TYPEERR, "Invalid type: {0} can't be a{1} {2}")
 MSG_DEF(JSMSG_TERMINATED,              1, JSEXN_ERR, "Script terminated by timeout at:\n{0}")
 
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -558,17 +558,17 @@ JSCompartment::sweepSavedStacks()
 {
     savedStacks_.sweep(runtimeFromAnyThread());
 }
 
 void
 JSCompartment::sweepGlobalObject(FreeOp *fop)
 {
     if (global_.unbarrieredGet() && IsObjectAboutToBeFinalizedFromAnyThread(global_.unsafeGet())) {
-        if (debugMode())
+        if (isDebuggee())
             Debugger::detachAllDebuggersFromGlobal(fop, global_);
         global_.set(nullptr);
     }
 }
 
 void
 JSCompartment::sweepSelfHostingScriptSource()
 {
@@ -711,27 +711,16 @@ JSCompartment::setObjectMetadataCallback
 {
     // Clear any jitcode in the runtime, which behaves differently depending on
     // whether there is a creation callback.
     ReleaseAllJITCode(runtime_->defaultFreeOp());
 
     objectMetadataCallback = callback;
 }
 
-bool
-JSCompartment::hasScriptsOnStack()
-{
-    for (ActivationIterator iter(runtimeFromMainThread()); !iter.done(); ++iter) {
-        if (iter->compartment() == this)
-            return true;
-    }
-
-    return false;
-}
-
 static bool
 AddInnerLazyFunctionsFromScript(JSScript *script, AutoObjectVector &lazyFunctions)
 {
     if (!script->hasObjects())
         return true;
     ObjectArray *objects = script->objects();
     for (size_t i = script->innerObjectsStart(); i < objects->length; i++) {
         JSObject *obj = objects->vector[i];
@@ -794,68 +783,22 @@ JSCompartment::ensureDelazifyScriptsForD
 {
     MOZ_ASSERT(cx->compartment() == this);
     if ((debugModeBits & DebugNeedDelazification) && !CreateLazyScriptsForCompartment(cx))
         return false;
     debugModeBits &= ~DebugNeedDelazification;
     return true;
 }
 
-bool
-JSCompartment::updateJITForDebugMode(JSContext *maybecx, AutoDebugModeInvalidation &invalidate)
-{
-    // The AutoDebugModeInvalidation argument makes sure we can't forget to
-    // invalidate, but it is also important not to run any scripts in this
-    // compartment until the invalidate is destroyed.  That is the caller's
-    // responsibility.
-    return jit::UpdateForDebugMode(maybecx, this, invalidate);
-}
-
-bool
-JSCompartment::enterDebugMode(JSContext *cx)
-{
-    AutoDebugModeInvalidation invalidate(this);
-    return enterDebugMode(cx, invalidate);
-}
-
-bool
-JSCompartment::enterDebugMode(JSContext *cx, AutoDebugModeInvalidation &invalidate)
+void
+JSCompartment::unsetIsDebuggee()
 {
-    if (!debugMode()) {
-        debugModeBits |= DebugMode;
-        if (!updateJITForDebugMode(cx, invalidate))
-            return false;
-    }
-    return true;
-}
-
-bool
-JSCompartment::leaveDebugMode(JSContext *cx)
-{
-    AutoDebugModeInvalidation invalidate(this);
-    return leaveDebugMode(cx, invalidate);
-}
-
-bool
-JSCompartment::leaveDebugMode(JSContext *cx, AutoDebugModeInvalidation &invalidate)
-{
-    if (debugMode()) {
-        leaveDebugModeUnderGC();
-        if (!updateJITForDebugMode(cx, invalidate))
-            return false;
-    }
-    return true;
-}
-
-void
-JSCompartment::leaveDebugModeUnderGC()
-{
-    if (debugMode()) {
-        debugModeBits &= ~DebugMode;
-        DebugScopes::onCompartmentLeaveDebugMode(this);
+    if (isDebuggee()) {
+        debugModeBits &= ~DebugExecutionMask;
+        DebugScopes::onCompartmentUnsetIsDebuggee(this);
     }
 }
 
 void
 JSCompartment::clearBreakpointsIn(FreeOp *fop, js::Debugger *dbg, HandleObject handler)
 {
     for (gc::ZoneCellIter i(zone(), gc::FINALIZE_SCRIPT); !i.done(); i.next()) {
         JSScript *script = i.get<JSScript>();
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -125,17 +125,16 @@ typedef HashMap<CrossCompartmentKey, Rea
 
 } /* namespace js */
 
 namespace JS {
 struct TypeInferenceSizes;
 }
 
 namespace js {
-class AutoDebugModeInvalidation;
 class DebugScopes;
 class LazyArrayBufferTable;
 class WeakMapBase;
 }
 
 struct JSCompartment
 {
     JS::CompartmentOptions       options_;
@@ -316,19 +315,24 @@ struct JSCompartment
     js::WeakMapBase              *gcWeakMapList;
 
   private:
     /* Whether to preserve JIT code on non-shrinking GCs. */
     bool                         gcPreserveJitCode;
 
     enum {
         DebugMode = 1 << 0,
-        DebugNeedDelazification = 1 << 1
+        DebugObservesAllExecution = 1 << 1,
+        DebugNeedDelazification = 1 << 2
     };
 
+    // DebugObservesAllExecution is a submode of DebugMode, and is only valid
+    // when DebugMode is also set.
+    static const unsigned DebugExecutionMask = DebugMode | DebugObservesAllExecution;
+
     unsigned                     debugModeBits;
 
   public:
     JSCompartment(JS::Zone *zone, const JS::CompartmentOptions &options);
     ~JSCompartment();
 
     bool init(JSContext *cx);
 
@@ -411,42 +415,76 @@ struct JSCompartment
     js::DtoaCache dtoaCache;
 
     /* Random number generator state, used by jsmath.cpp. */
     uint64_t rngState;
 
   private:
     JSCompartment *thisForCtor() { return this; }
 
-    // Only called from {enter,leave}DebugMode.
-    bool updateJITForDebugMode(JSContext *maybecx, js::AutoDebugModeInvalidation &invalidate);
+  public:
+    //
+    // The Debugger observes execution on a frame-by-frame basis. The
+    // invariants of JSCompartment's debug mode bits, JSScript::isDebuggee,
+    // InterpreterFrame::isDebuggee, and Baseline::isDebuggee are enumerated
+    // below.
+    //
+    // 1. When a compartment's isDebuggee() == true, relazification and lazy
+    //    parsing are disabled.
+    //
+    // 2. When a compartment's debugObservesAllExecution() == true, all of the
+    //    compartment's scripts are considered debuggee scripts.
+    //
+    // 3. A script is considered a debuggee script either when, per above, its
+    //    compartment is observing all execution, or if it has breakpoints set.
+    //
+    // 4. A debuggee script always pushes a debuggee frame.
+    //
+    // 5. A debuggee frame calls all slow path Debugger hooks in the
+    //    Interpreter and Baseline. A debuggee frame implies that its script's
+    //    BaselineScript, if extant, has been compiled with debug hook calls.
+    //
+    // 6. A debuggee script or a debuggee frame (i.e., during OSR) ensures
+    //    that the compiled BaselineScript is compiled with debug hook calls
+    //    when attempting to enter Baseline.
+    //
+    // 7. A debuggee script or a debuggee frame (i.e., during OSR) does not
+    //    attempt to enter Ion.
+    //
+    // Note that a debuggee frame may exist without its script being a
+    // debuggee script. e.g., Debugger.Frame.prototype.eval only marks the
+    // frame in which it is evaluating as a debuggee frame.
+    //
 
-  public:
     // True if this compartment's global is a debuggee of some Debugger
     // object.
-    bool debugMode() const {
-        return !!(debugModeBits & DebugMode);
-    }
+    bool isDebuggee() const { return !!(debugModeBits & DebugMode); }
+    void setIsDebuggee() { debugModeBits |= DebugMode; }
+    void unsetIsDebuggee();
 
-    bool enterDebugMode(JSContext *cx);
-    bool enterDebugMode(JSContext *cx, js::AutoDebugModeInvalidation &invalidate);
-    bool leaveDebugMode(JSContext *cx);
-    bool leaveDebugMode(JSContext *cx, js::AutoDebugModeInvalidation &invalidate);
-    void leaveDebugModeUnderGC();
-
-    /* True if any scripts from this compartment are on the JS stack. */
-    bool hasScriptsOnStack();
+    // True if an this compartment's global is a debuggee of some Debugger
+    // object with a live hook that observes all execution; e.g.,
+    // onEnterFrame.
+    bool debugObservesAllExecution() const {
+        return (debugModeBits & DebugExecutionMask) == DebugExecutionMask;
+    }
+    void setDebugObservesAllExecution() {
+        MOZ_ASSERT(isDebuggee());
+        debugModeBits |= DebugObservesAllExecution;
+    }
+    void unsetDebugObservesAllExecution() {
+        MOZ_ASSERT(isDebuggee());
+        debugModeBits &= ~DebugObservesAllExecution;
+    }
 
     /*
      * Schedule the compartment to be delazified. Called from
      * LazyScript::Create.
      */
-    void scheduleDelazificationForDebugMode() {
-        debugModeBits |= DebugNeedDelazification;
-    }
+    void scheduleDelazificationForDebugMode() { debugModeBits |= DebugNeedDelazification; }
 
     /*
      * If we scheduled delazification for turning on debug mode, delazify all
      * scripts.
      */
     bool ensureDelazifyScriptsForDebugMode(JSContext *cx);
 
     void clearBreakpointsIn(js::FreeOp *fop, js::Debugger *dbg, JS::HandleObject handler);
@@ -489,70 +527,16 @@ struct JSCompartment
 };
 
 inline bool
 JSRuntime::isAtomsZone(JS::Zone *zone)
 {
     return zone == atomsCompartment_->zone();
 }
 
-// For use when changing the debug mode flag on one or more compartments.
-// Invalidate and discard JIT code since debug mode breaks JIT assumptions.
-//
-// AutoDebugModeInvalidation has two modes: compartment or zone
-// invalidation. While it is correct to always use compartment invalidation,
-// if you know ahead of time you need to invalidate a whole zone, it is faster
-// to invalidate the zone.
-//
-// Compartment invalidation only invalidates scripts belonging to that
-// compartment.
-//
-// Zone invalidation invalidates all scripts belonging to non-special
-// (i.e. those with principals) compartments of the zone.
-//
-// FIXME: Remove entirely once bug 716647 lands.
-//
-class js::AutoDebugModeInvalidation
-{
-    JSCompartment *comp_;
-    JS::Zone *zone_;
-
-    enum {
-        NoNeed = 0,
-        ToggledOn = 1,
-        ToggledOff = 2
-    } needInvalidation_;
-
-  public:
-    explicit AutoDebugModeInvalidation(JSCompartment *comp)
-      : comp_(comp), zone_(nullptr), needInvalidation_(NoNeed)
-    { }
-
-    explicit AutoDebugModeInvalidation(JS::Zone *zone)
-      : comp_(nullptr), zone_(zone), needInvalidation_(NoNeed)
-    { }
-
-    ~AutoDebugModeInvalidation();
-
-    bool isFor(JSCompartment *comp) {
-        if (comp_)
-            return comp == comp_;
-        return comp->zone() == zone_;
-    }
-
-    void scheduleInvalidation(bool debugMode) {
-        // If we are scheduling invalidation for multiple compartments, they
-        // must all agree on the toggle. This is so we can decide if we need
-        // to invalidate on-stack scripts.
-        MOZ_ASSERT_IF(needInvalidation_ != NoNeed,
-                      needInvalidation_ == (debugMode ? ToggledOn : ToggledOff));
-        needInvalidation_ = debugMode ? ToggledOn : ToggledOff;
-    }
-};
-
 namespace js {
 
 inline js::Handle<js::GlobalObject*>
 ExclusiveContext::global() const
 {
     /*
      * It's safe to use |unsafeGet()| here because any compartment that is
      * on-stack will be marked automatically, so there's no need for a read
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -733,17 +733,17 @@ JSFunction::trace(JSTracer *trc)
             // - they aren't generators
             // - they don't have JIT code attached
             // - they don't have child functions
             // - they have information for un-lazifying them again later
             // This information can either be a LazyScript, or the name of a
             // self-hosted function which can be cloned over again. The latter
             // is stored in the first extended slot.
             if (IS_GC_MARKING_TRACER(trc) && !compartment()->hasBeenEntered() &&
-                !compartment()->debugMode() && !compartment()->isSelfHosting &&
+                !compartment()->isDebuggee() && !compartment()->isSelfHosting &&
                 u.i.s.script_->isRelazifiable() && (!isSelfHostedBuiltin() || isExtended()))
             {
                 relazify(trc);
             } else {
                 MarkScriptUnbarriered(trc, &u.i.s.script_, "script");
             }
         } else if (isInterpretedLazy() && u.i.s.lazy_) {
             MarkLazyScriptUnbarriered(trc, &u.i.s.lazy_, "lazyScript");
@@ -1495,17 +1495,17 @@ JSFunction::createScriptForLazilyInterpr
 }
 
 void
 JSFunction::relazify(JSTracer *trc)
 {
     JSScript *script = nonLazyScript();
     MOZ_ASSERT(script->isRelazifiable());
     MOZ_ASSERT(!compartment()->hasBeenEntered());
-    MOZ_ASSERT(!compartment()->debugMode());
+    MOZ_ASSERT(!compartment()->isDebuggee());
 
     // If the script's canonical function isn't lazy, we have to mark the
     // script. Otherwise, the following scenario would leave it unmarked
     // and cause it to be swept while a function is still expecting it to be
     // valid:
     // 1. an incremental GC slice causes the canonical function to relazify
     // 2. a clone is used and delazifies the canonical function
     // 3. another GC slice causes the clone to relazify
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -1239,16 +1239,24 @@ types::FinishCompilation(JSContext *cx, 
 
     for (size_t i = 0; i < constraints->numFrozenScripts(); i++) {
         const CompilerConstraintList::FrozenScript &entry = constraints->frozenScript(i);
         if (!entry.script->types()) {
             succeeded = false;
             break;
         }
 
+        // It could happen that one of the compiled scripts was made a
+        // debuggee mid-compilation (e.g., via setting a breakpoint). If so,
+        // throw away the compilation.
+        if (entry.script->isDebuggee()) {
+            succeeded = false;
+            break;
+        }
+
         if (!CheckFrozenTypeSet(cx, entry.thisTypes, types::TypeScript::ThisTypes(entry.script)))
             succeeded = false;
         unsigned nargs = entry.script->functionNonDelazifying()
                          ? entry.script->functionNonDelazifying()->nargs()
                          : 0;
         for (size_t i = 0; i < nargs; i++) {
             if (!CheckFrozenTypeSet(cx, &entry.argTypes[i], types::TypeScript::ArgTypes(entry.script, i)))
                 succeeded = false;
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -3310,17 +3310,17 @@ JSScript::setNewStepMode(FreeOp *fop, ui
             fop->free_(releaseDebugScript());
     }
 }
 
 bool
 JSScript::incrementStepModeCount(JSContext *cx)
 {
     assertSameCompartment(cx, this);
-    MOZ_ASSERT(cx->compartment()->debugMode());
+    MOZ_ASSERT(cx->compartment()->isDebuggee());
 
     if (!ensureHasDebugScript(cx))
         return false;
 
     DebugScript *debug = debugScript();
     uint32_t count = debug->stepMode;
     setNewStepMode(cx->runtime()->defaultFreeOp(), count + 1);
     return true;
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -1668,16 +1668,20 @@ class JSScript : public js::gc::TenuredC
     js::DebugScript *debugScript();
     js::DebugScript *releaseDebugScript();
     void destroyDebugScript(js::FreeOp *fop);
 
   public:
     bool hasBreakpointsAt(jsbytecode *pc);
     bool hasAnyBreakpointsOrStepMode() { return hasDebugScript_; }
 
+    // See comment above 'debugMode' in jscompartment.h for explanation of
+    // invariants of debuggee compartments, scripts, and frames.
+    inline bool isDebuggee() const;
+
     js::BreakpointSite *getBreakpointSite(jsbytecode *pc)
     {
         return hasDebugScript_ ? debugScript()->breakpoints[pcToOffset(pc)] : nullptr;
     }
 
     js::BreakpointSite *getOrCreateBreakpointSite(JSContext *cx, jsbytecode *pc);
 
     void destroyBreakpointSite(js::FreeOp *fop, jsbytecode *pc);
--- a/js/src/jsscriptinlines.h
+++ b/js/src/jsscriptinlines.h
@@ -177,9 +177,15 @@ JSScript::setBaselineScript(JSContext *m
 inline bool
 JSScript::ensureHasAnalyzedArgsUsage(JSContext *cx)
 {
     if (analyzedArgsUsage())
         return true;
     return js::jit::AnalyzeArgumentsUsage(cx, this);
 }
 
+inline bool
+JSScript::isDebuggee() const
+{
+    return compartment_->debugObservesAllExecution() || hasDebugScript_;
+}
+
 #endif /* jsscriptinlines_h */
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -2646,17 +2646,17 @@ EvalInFrame(JSContext *cx, unsigned argc
         JS_ReportError(cx, "Invalid arguments to evalInFrame");
         return false;
     }
 
     uint32_t upCount = args[0].toInt32();
     RootedString str(cx, args[1].toString());
     bool saveCurrent = args.get(2).isBoolean() ? args[2].toBoolean() : false;
 
-    if (!cx->compartment()->debugMode()) {
+    if (!cx->compartment()->debugObservesAllExecution()) {
         JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, js_GetErrorMessage,
                                      nullptr, JSMSG_NEED_DEBUG_MODE);
         return false;
     }
 
     /* Debug-mode currently disables Ion compilation. */
     ScriptFrameIter fi(cx);
     for (uint32_t i = 0; i < upCount; ++i, ++fi) {
--- a/js/src/vm/Debugger-inl.h
+++ b/js/src/vm/Debugger-inl.h
@@ -10,24 +10,53 @@
 #include "vm/Debugger.h"
 
 #include "vm/Stack-inl.h"
 
 /* static */ inline bool
 js::Debugger::onLeaveFrame(JSContext *cx, AbstractFramePtr frame, bool ok)
 {
     MOZ_ASSERT_IF(frame.isInterpreterFrame(), frame.asInterpreterFrame() == cx->interpreterFrame());
+    MOZ_ASSERT_IF(frame.script()->isDebuggee(), frame.isDebuggee());
     /* Traps must be cleared from eval frames, see slowPathOnLeaveFrame. */
-    bool evalTraps = frame.isEvalFrame() &&
-                     frame.script()->hasAnyBreakpointsOrStepMode();
-    if (cx->compartment()->debugMode() || evalTraps)
+    mozilla::DebugOnly<bool> evalTraps = frame.isEvalFrame() &&
+                                         frame.script()->hasAnyBreakpointsOrStepMode();
+    MOZ_ASSERT_IF(evalTraps, frame.isDebuggee());
+    if (frame.isDebuggee())
         ok = slowPathOnLeaveFrame(cx, frame, ok);
     return ok;
 }
 
 /* static */ inline js::Debugger *
 js::Debugger::fromJSObject(JSObject *obj)
 {
     MOZ_ASSERT(js::GetObjectClass(obj) == &jsclass);
     return (Debugger *) obj->as<NativeObject>().getPrivate();
 }
 
+/* static */ JSTrapStatus
+js::Debugger::onEnterFrame(JSContext *cx, AbstractFramePtr frame)
+{
+    MOZ_ASSERT_IF(frame.script()->isDebuggee(), frame.isDebuggee());
+    if (!frame.isDebuggee())
+        return JSTRAP_CONTINUE;
+    return slowPathOnEnterFrame(cx, frame);
+}
+
+/* static */ JSTrapStatus
+js::Debugger::onDebuggerStatement(JSContext *cx, AbstractFramePtr frame, MutableHandleValue vp)
+{
+    MOZ_ASSERT_IF(frame.script()->isDebuggee(), frame.isDebuggee());
+    return frame.isDebuggee()
+           ? dispatchHook(cx, vp, OnDebuggerStatement)
+           : JSTRAP_CONTINUE;
+}
+
+/* static */ JSTrapStatus
+js::Debugger::onExceptionUnwind(JSContext *cx, AbstractFramePtr frame)
+{
+    MOZ_ASSERT_IF(frame.script()->isDebuggee(), frame.isDebuggee());
+    if (!frame.isDebuggee())
+        return JSTRAP_CONTINUE;
+    return slowPathOnExceptionUnwind(cx, frame);
+}
+
 #endif /* vm_Debugger_inl_h */
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -6,33 +6,36 @@
 
 #include "vm/Debugger-inl.h"
 
 #include "mozilla/DebugOnly.h"
 
 #include "jscntxt.h"
 #include "jscompartment.h"
 #include "jshashutil.h"
+#include "jsinfer.h"
 #include "jsnum.h"
 #include "jsobj.h"
 #include "jswrapper.h"
 
 #include "frontend/BytecodeCompiler.h"
 #include "gc/Marking.h"
+#include "jit/BaselineDebugModeOSR.h"
 #include "jit/BaselineJIT.h"
 #include "js/Debug.h"
 #include "js/GCAPI.h"
 #include "js/UbiNodeTraverse.h"
 #include "js/Vector.h"
 #include "vm/ArgumentsObject.h"
 #include "vm/DebuggerMemory.h"
 #include "vm/SPSProfiler.h"
 #include "vm/WrapperObject.h"
 
 #include "jsgcinlines.h"
+#include "jsinferinlines.h"
 #include "jsobjinlines.h"
 #include "jsopcodeinlines.h"
 #include "jsscriptinlines.h"
 
 #include "vm/NativeObject-inl.h"
 #include "vm/Stack-inl.h"
 
 using namespace js;
@@ -450,16 +453,19 @@ Debugger::getScriptFrameWithIter(JSConte
         }
 
         frameobj->setReservedSlot(JSSLOT_DEBUGFRAME_OWNER, ObjectValue(*object));
 
         if (!frames.add(p, frame, frameobj)) {
             js_ReportOutOfMemory(cx);
             return false;
         }
+
+        if (!ensureExecutionObservabilityOfFrame(cx, frame))
+            return false;
     }
     vp.setObject(*p->value());
     return true;
 }
 
 JSObject *
 Debugger::getHook(Hook hook) const
 {
@@ -1273,20 +1279,19 @@ Debugger::slowPathOnNewScript(JSContext 
             dbg->fireNewScript(cx, script);
         }
     }
 }
 
 /* static */ JSTrapStatus
 Debugger::onTrap(JSContext *cx, MutableHandleValue vp)
 {
-    MOZ_ASSERT(cx->compartment()->debugMode());
-
     ScriptFrameIter iter(cx);
     RootedScript script(cx, iter.script());
+    MOZ_ASSERT(script->isDebuggee());
     Rooted<GlobalObject*> scriptGlobal(cx, &script->global());
     jsbytecode *pc = iter.pc();
     BreakpointSite *site = script->getBreakpointSite(pc);
     JSOp op = JSOp(*pc);
 
     /* Build list of breakpoint handlers. */
     Vector<Breakpoint *> triggered(cx);
     for (Breakpoint *bp = site->firstBreakpoint(); bp; bp = bp->nextInSite()) {
@@ -1563,16 +1568,409 @@ Debugger::appendAllocationSite(JSContext
 void
 Debugger::emptyAllocationsLog()
 {
     while (!allocationsLog.isEmpty())
         js_delete(allocationsLog.getFirst());
     allocationsLogLength = 0;
 }
 
+
+/*** Debugger code invalidation for observing execution ******************************************/
+
+class MOZ_STACK_CLASS ExecutionObservableCompartments : public Debugger::ExecutionObservableSet
+{
+    HashSet<JSCompartment *> compartments_;
+    HashSet<Zone *> zones_;
+
+  public:
+    explicit ExecutionObservableCompartments(JSContext *cx
+                                             MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+      : compartments_(cx),
+        zones_(cx)
+    {
+        MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+    }
+
+    bool init() { return compartments_.init() && zones_.init(); }
+    bool add(JSCompartment *comp) { return compartments_.put(comp) && zones_.put(comp->zone()); }
+
+    const HashSet<Zone *> *zones() const { return &zones_; }
+    bool shouldRecompileOrInvalidate(JSScript *script) const {
+        return script->hasBaselineScript() && compartments_.has(script->compartment());
+    }
+    bool shouldMarkAsDebuggee(ScriptFrameIter &iter) const {
+        // AbstractFramePtr can't refer to non-remateralized Ion frames, so if
+        // iter refers to one such, we know we don't match.
+        return iter.hasUsableAbstractFramePtr() && compartments_.has(iter.compartment());
+    }
+
+    MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+};
+
+// Given a particular AbstractFramePtr F that has become observable, this
+// represents the stack frames that need to be bailed out or marked as
+// debuggees, and the scripts that need to be recompiled, taking inlining into
+// account.
+class MOZ_STACK_CLASS ExecutionObservableFrame : public Debugger::ExecutionObservableSet
+{
+    AbstractFramePtr frame_;
+
+  public:
+    explicit ExecutionObservableFrame(AbstractFramePtr frame
+                                      MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+      : frame_(frame)
+    {
+        MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+    }
+
+    Zone *singleZone() const {
+        // We never inline across compartments, let alone across zones, so
+        // frames_'s script's zone is the only one of interest.
+        return frame_.script()->compartment()->zone();
+    }
+
+    JSScript *singleScriptForZoneInvalidation() const {
+        MOZ_CRASH("ExecutionObservableFrame shouldn't need zone-wide invalidation.");
+        return nullptr;
+    }
+
+    bool shouldRecompileOrInvalidate(JSScript *script) const {
+        // Normally, *this represents exactly one script: the one frame_ is
+        // running.
+        //
+        // However, debug-mode OSR uses *this for both invalidating Ion frames,
+        // and recompiling the Baseline scripts that those Ion frames will bail
+        // out into. Suppose frame_ is an inline frame, executing a copy of its
+        // JSScript, S_inner, that has been inlined into the IonScript of some
+        // other JSScript, S_outer. We must match S_outer, to decide which Ion
+        // frame to invalidate; and we must match S_inner, to decide which
+        // Baseline script to recompile.
+        //
+        // Note that this does not, by design, invalidate *all* inliners of
+        // frame_.script(), as only frame_ is made observable, not
+        // frame_.script().
+        if (!script->hasBaselineScript())
+            return false;
+
+        if (script == frame_.script())
+            return true;
+
+        return frame_.isRematerializedFrame() &&
+               script == frame_.asRematerializedFrame()->outerScript();
+    }
+
+    bool shouldMarkAsDebuggee(ScriptFrameIter &iter) const {
+        // AbstractFramePtr can't refer to non-remateralized Ion frames, so if
+        // iter refers to one such, we know we don't match.
+        //
+        // We never use this 'has' overload for frame invalidation, only for
+        // frame debuggee marking; so this overload doesn't need a parallel to
+        // the just-so inlining logic above.
+        return iter.hasUsableAbstractFramePtr() && iter.abstractFramePtr() == frame_;
+    }
+
+    MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+};
+
+class MOZ_STACK_CLASS ExecutionObservableScript : public Debugger::ExecutionObservableSet
+{
+    RootedScript script_;
+
+  public:
+    ExecutionObservableScript(JSContext *cx, JSScript *script
+                              MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+      : script_(cx, script)
+    {
+        MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+    }
+
+    Zone *singleZone() const { return script_->compartment()->zone(); }
+    JSScript *singleScriptForZoneInvalidation() const { return script_; }
+    bool shouldRecompileOrInvalidate(JSScript *script) const {
+        return script->hasBaselineScript() && script == script_;
+    }
+    bool shouldMarkAsDebuggee(ScriptFrameIter &iter) const {
+        // AbstractFramePtr can't refer to non-remateralized Ion frames, and
+        // while a non-rematerialized Ion frame may indeed be running script_,
+        // we cannot mark them as debuggees until they bail out.
+        //
+        // Upon bailing out, any newly constructed Baseline frames that came
+        // from Ion frames with scripts that are isDebuggee() is marked as
+        // debuggee. This is correct in that the only other way a frame may be
+        // marked as debuggee is via Debugger.Frame reflection, which would
+        // have rematerialized any Ion frames.
+        return iter.hasUsableAbstractFramePtr() && iter.abstractFramePtr().script() == script_;
+    }
+
+    MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+};
+
+/* static */ bool
+Debugger::updateExecutionObservabilityOfFrames(JSContext *cx, const ExecutionObservableSet &obs,
+                                               IsObserving observing)
+{
+    AutoSuppressProfilerSampling suppressProfilerSampling(cx);
+
+    {
+        jit::IonContext ictx(cx, nullptr);
+        if (!jit::RecompileOnStackBaselineScriptsForDebugMode(cx, obs, observing)) {
+            js_ReportOutOfMemory(cx);
+            return false;
+        }
+    }
+
+    for (ScriptFrameIter iter(cx); !iter.done(); ++iter) {
+        if (obs.shouldMarkAsDebuggee(iter)) {
+            if (observing) {
+                iter.abstractFramePtr().setIsDebuggee();
+            } else {
+#ifdef DEBUG
+                // Debugger.Frame lifetimes are managed by the debug epilogue,
+                // so in general it's unsafe to unmark a frame if it has a
+                // Debugger.Frame associated with it.
+                FrameRange r(iter.abstractFramePtr());
+                MOZ_ASSERT(r.empty());
+#endif
+                iter.abstractFramePtr().unsetIsDebuggee();
+            }
+        }
+    }
+    return true;
+}
+
+static inline void
+MarkBaselineScriptActiveIfObservable(JSScript *script, const Debugger::ExecutionObservableSet &obs)
+{
+    if (obs.shouldRecompileOrInvalidate(script))
+        script->baselineScript()->setActive();
+}
+
+static bool
+AppendAndInvalidateScriptIfObservable(JSContext *cx, Zone *zone, JSScript *script,
+                                      const Debugger::ExecutionObservableSet &obs,
+                                      Vector<JSScript *> &scripts)
+{
+    if (obs.shouldRecompileOrInvalidate(script)) {
+        // Enter the script's compartment as addPendingRecompile attempts to
+        // cancel off-thread compilations, whose books are kept on the
+        // script's compartment.
+        MOZ_ASSERT(script->compartment()->zone() == zone);
+        AutoCompartment ac(cx, script->compartment());
+        zone->types.addPendingRecompile(cx, script);
+        return scripts.append(script);
+    }
+    return true;
+}
+
+static bool
+UpdateExecutionObservabilityOfScriptsInZone(JSContext *cx, Zone *zone,
+                                            const Debugger::ExecutionObservableSet &obs,
+                                            Debugger::IsObserving observing)
+{
+    using namespace js::jit;
+
+#ifdef JSGC_GENERATIONAL
+    // See note in js::ReleaseAllJITCode.
+    cx->runtime()->gc.evictNursery();
+#endif
+
+    AutoSuppressProfilerSampling suppressProfilerSampling(cx);
+
+    JSRuntime *rt = cx->runtime();
+    FreeOp *fop = cx->runtime()->defaultFreeOp();
+
+    // Mark active baseline scripts in the observable set so that they don't
+    // get discarded. They will be recompiled.
+    for (JitActivationIterator actIter(rt); !actIter.done(); ++actIter) {
+        if (actIter->compartment()->zone() != zone)
+            continue;
+
+        for (JitFrameIterator iter(actIter); !iter.done(); ++iter) {
+            switch (iter.type()) {
+              case JitFrame_BaselineJS:
+                MarkBaselineScriptActiveIfObservable(iter.script(), obs);
+                break;
+              case JitFrame_IonJS:
+                MarkBaselineScriptActiveIfObservable(iter.script(), obs);
+                for (InlineFrameIterator inlineIter(rt, &iter); inlineIter.more(); ++inlineIter)
+                    MarkBaselineScriptActiveIfObservable(inlineIter.script(), obs);
+                break;
+              default:;
+            }
+        }
+    }
+
+    Vector<JSScript *> scripts(cx);
+
+    // Iterate through observable scripts, invalidating their Ion scripts and
+    // appending them to a vector for discarding their baseline scripts later.
+    {
+        types::AutoEnterAnalysis enter(fop, zone);
+        if (JSScript *script = obs.singleScriptForZoneInvalidation()) {
+            if (!AppendAndInvalidateScriptIfObservable(cx, zone, script, obs, scripts))
+                return false;
+        } else {
+            for (gc::ZoneCellIter iter(zone, gc::FINALIZE_SCRIPT); !iter.done(); iter.next()) {
+                JSScript *script = iter.get<JSScript>();
+                if (!AppendAndInvalidateScriptIfObservable(cx, zone, script, obs, scripts))
+                    return false;
+            }
+        }
+    }
+
+    // Iterate through the scripts again and finish discarding
+    // BaselineScripts. This must be done as a separate phase as we can only
+    // discard the BaselineScript on scripts that have no IonScript.
+    for (size_t i = 0; i < scripts.length(); i++) {
+        MOZ_ASSERT_IF(scripts[i]->isDebuggee(), observing);
+        FinishDiscardBaselineScript(fop, scripts[i]);
+    }
+
+    return true;
+}
+
+/* static */ bool
+Debugger::updateExecutionObservabilityOfScripts(JSContext *cx, const ExecutionObservableSet &obs,
+                                                IsObserving observing)
+{
+    if (Zone *zone = obs.singleZone())
+        return UpdateExecutionObservabilityOfScriptsInZone(cx, zone, obs, observing);
+
+    typedef ExecutionObservableSet::ZoneRange ZoneRange;
+    for (ZoneRange r = obs.zones()->all(); !r.empty(); r.popFront()) {
+        if (!UpdateExecutionObservabilityOfScriptsInZone(cx, r.front(), obs, observing))
+            return false;
+    }
+
+    return true;
+}
+
+/* static */ bool
+Debugger::updateExecutionObservability(JSContext *cx, ExecutionObservableSet &obs,
+                                       IsObserving observing)
+{
+    if (!obs.singleZone() && obs.zones()->empty())
+        return true;
+
+    // Invalidate scripts first so we can set the needsArgsObj flag on scripts
+    // before patching frames.
+    return updateExecutionObservabilityOfScripts(cx, obs, observing) &&
+           updateExecutionObservabilityOfFrames(cx, obs, observing);
+}
+
+/* static */ bool
+Debugger::ensureExecutionObservabilityOfScript(JSContext *cx, JSScript *script)
+{
+    if (script->isDebuggee())
+        return true;
+    ExecutionObservableScript obs(cx, script);
+    return updateExecutionObservability(cx, obs, Observing);
+}
+
+/* static */ bool
+Debugger::ensureExecutionObservabilityOfFrame(JSContext *cx, AbstractFramePtr frame)
+{
+    MOZ_ASSERT_IF(frame.script()->isDebuggee(), frame.isDebuggee());
+    if (frame.isDebuggee())
+        return true;
+    ExecutionObservableFrame obs(frame);
+    return updateExecutionObservabilityOfFrames(cx, obs, Observing);
+}
+
+/* static */ bool
+Debugger::ensureExecutionObservabilityOfCompartment(JSContext *cx, JSCompartment *comp)
+{
+    if (comp->debugObservesAllExecution())
+        return true;
+    ExecutionObservableCompartments obs(cx);
+    if (!obs.init() || !obs.add(comp))
+        return false;
+    comp->setDebugObservesAllExecution();
+    return updateExecutionObservability(cx, obs, Observing);
+}
+
+static const Debugger::Hook ObserveAllExecutionHooks[] = { Debugger::OnDebuggerStatement,
+                                                           Debugger::OnExceptionUnwind,
+                                                           Debugger::OnEnterFrame,
+                                                           Debugger::HookCount };
+
+/* static */ bool
+Debugger::hookObservesAllExecution(Hook which)
+{
+    for (const Hook *hook = ObserveAllExecutionHooks; *hook != HookCount; hook++) {
+        if (*hook == which)
+            return true;
+    }
+    return false;
+}
+
+bool
+Debugger::hasAnyLiveHooksThatObserveAllExecution() const
+{
+    if (!enabled)
+        return false;
+    return hasAnyHooksThatObserveAllExecution();
+}
+
+bool
+Debugger::hasAnyHooksThatObserveAllExecution() const
+{
+    for (const Hook *hook = ObserveAllExecutionHooks; *hook != HookCount; hook++) {
+        if (getHook(*hook))
+            return true;
+    }
+    return false;
+}
+
+/* static */ bool
+Debugger::anyOtherDebuggerObservingAllExecution(GlobalObject *global) const
+{
+    // If we are toggling from Observing to NotObserving, add the
+    // compartment to the observable set only if none of its other
+    // debuggers are observing all execution.
+    GlobalObject::DebuggerVector *debuggers = global->getDebuggers();
+    for (Debugger **p = debuggers->begin(); p != debuggers->end(); p++) {
+        Debugger *dbg = *p;
+        if (dbg != this && dbg->hasAnyLiveHooksThatObserveAllExecution())
+            return true;
+    }
+    return false;
+}
+
+// Toggle whether this Debugger's debuggees observe all execution. This is
+// called when a hook that observes all execution is set or unset. See
+// hookObservesAllExecution.
+bool
+Debugger::setObservesAllExecution(JSContext *cx, IsObserving observing)
+{
+    ExecutionObservableCompartments obs(cx);
+    if (!obs.init())
+        return false;
+
+    for (GlobalObjectSet::Range r = debuggees.all(); !r.empty(); r.popFront()) {
+        GlobalObject * global = r.front();
+        JSCompartment *comp = global->compartment();
+
+        if (comp->debugObservesAllExecution() == observing)
+            continue;
+
+        if (observing) {
+            if (!obs.add(comp))
+                return false;
+            comp->setDebugObservesAllExecution();
+        } else if (!anyOtherDebuggerObservingAllExecution(global)) {
+            // It's expensive to eagerly invalidate and recompile a
+            // compartment, so don't add the compartment to the set.
+            comp->unsetDebugObservesAllExecution();
+        }
+    }
+
+    return updateExecutionObservability(cx, obs, observing);
+}
 
 
 /*** Debugger JSObjects **************************************************************************/
 
 void
 Debugger::markKeysInCompartment(JSTracer *trc)
 {
     /*
@@ -1639,17 +2037,17 @@ Debugger::markAllIteratively(GCMarker *t
     bool markedAny = false;
 
     /*
      * Find all Debugger objects in danger of GC. This code is a little
      * convoluted since the easiest way to find them is via their debuggees.
      */
     JSRuntime *rt = trc->runtime();
     for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
-        if (c->debugMode()) {
+        if (c->isDebuggee()) {
             GlobalObject *global = c->maybeGlobal();
             if (!IsObjectMarked(&global))
                 continue;
 
             /*
              * Every debuggee has at least one debugger, so in this case
              * getDebuggers can't return nullptr.
              */
@@ -1789,33 +2187,29 @@ Debugger::sweepAll(FreeOp *fop)
 
     for (Debugger *dbg = rt->debuggerList.getFirst(); dbg; dbg = dbg->getNext()) {
         if (IsObjectAboutToBeFinalized(&dbg->object)) {
             /*
              * dbg is being GC'd. Detach it from its debuggees. The debuggee
              * might be GC'd too. Since detaching requires access to both
              * objects, this must be done before finalize time.
              */
-            for (GlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront()) {
-                // We can't recompile on-stack scripts here, and we
-                // can only toggle debug mode to off, so we use an
-                // infallible variant of removeDebuggeeGlobal.
-                dbg->removeDebuggeeGlobalUnderGC(fop, e.front(), &e);
-            }
+            for (GlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront())
+                dbg->removeDebuggeeGlobal(fop, e.front(), &e);
         }
     }
 }
 
 /* static */ void
 Debugger::detachAllDebuggersFromGlobal(FreeOp *fop, GlobalObject *global)
 {
     const GlobalObject::DebuggerVector *debuggers = global->getDebuggers();
     MOZ_ASSERT(!debuggers->empty());
     while (!debuggers->empty())
-        debuggers->back()->removeDebuggeeGlobalUnderGC(fop, global, nullptr);
+        debuggers->back()->removeDebuggeeGlobal(fop, global, nullptr);
 }
 
 /* static */ void
 Debugger::findCompartmentEdges(Zone *zone, js::gc::ComponentFinder<Zone> &finder)
 {
     /*
      * For debugger cross compartment wrappers, add edges in the opposite
      * direction to those already added by JSCompartment::findOutgoingEdges.
@@ -1929,16 +2323,28 @@ Debugger::setEnabled(JSContext *cx, unsi
                 JS_APPEND_LINK(&dbg->onNewGlobalObjectWatchersLink,
                                &cx->runtime()->onNewGlobalObjectWatchers);
             } else {
                 /* If we were enabled, the link should be inserted in the list. */
                 MOZ_ASSERT(!JS_CLIST_IS_EMPTY(&dbg->onNewGlobalObjectWatchersLink));
                 JS_REMOVE_AND_INIT_LINK(&dbg->onNewGlobalObjectWatchersLink);
             }
         }
+
+        // Ensure the compartment is observable if we are re-enabling a
+        // Debugger with hooks that observe all execution.
+        if (enabled) {
+            if (dbg->hasAnyHooksThatObserveAllExecution()) {
+                if (!dbg->setObservesAllExecution(cx, Observing))
+                    return false;
+            }
+        } else {
+            if (!dbg->setObservesAllExecution(cx, NotObserving))
+                return false;
+        }
     }
 
     dbg->enabled = enabled;
     args.rval().setUndefined();
     return true;
 }
 
 /* static */ bool
@@ -1960,16 +2366,23 @@ Debugger::setHookImpl(JSContext *cx, uns
     if (args[0].isObject()) {
         if (!args[0].toObject().isCallable())
             return ReportIsNotFunction(cx, args[0], args.length() - 1);
     } else if (!args[0].isUndefined()) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_CALLABLE_OR_UNDEFINED);
         return false;
     }
     dbg->object->setReservedSlot(JSSLOT_DEBUG_HOOK_START + which, args[0]);
+    if (hookObservesAllExecution(which)) {
+        if (!dbg->setObservesAllExecution(cx, dbg->hasAnyLiveHooksThatObserveAllExecution()
+                                              ? Observing : NotObserving))
+        {
+            return false;
+        }
+    }
     args.rval().setUndefined();
     return true;
 }
 
 /* static */ bool
 Debugger::getOnDebuggerStatement(JSContext *cx, unsigned argc, Value *vp)
 {
     return getHookImpl(cx, argc, vp, OnDebuggerStatement);
@@ -2155,65 +2568,85 @@ Debugger::addDebuggee(JSContext *cx, uns
     return true;
 }
 
 /* static */ bool
 Debugger::addAllGlobalsAsDebuggees(JSContext *cx, unsigned argc, Value *vp)
 {
     THIS_DEBUGGER(cx, argc, vp, "addAllGlobalsAsDebuggees", args, dbg);
     for (ZonesIter zone(cx->runtime(), SkipAtoms); !zone.done(); zone.next()) {
-        // Invalidate a zone at a time to avoid doing a ZoneCellIter
-        // per compartment.
-        AutoDebugModeInvalidation invalidate(zone);
-
         for (CompartmentsInZoneIter c(zone); !c.done(); c.next()) {
             if (c == dbg->object->compartment() || c->options().invisibleToDebugger())
                 continue;
             c->scheduledForDestruction = false;
             GlobalObject *global = c->maybeGlobal();
             if (global) {
                 Rooted<GlobalObject*> rg(cx, global);
-                if (!dbg->addDebuggeeGlobal(cx, rg, invalidate))
+                if (!dbg->addDebuggeeGlobal(cx, rg))
                     return false;
             }
         }
     }
 
     args.rval().setUndefined();
     return true;
 }
 
 /* static */ bool
 Debugger::removeDebuggee(JSContext *cx, unsigned argc, Value *vp)
 {
     THIS_DEBUGGER(cx, argc, vp, "removeDebuggee", args, dbg);
+
     if (!args.requireAtLeast(cx, "Debugger.removeDebuggee", 1))
         return false;
     Rooted<GlobalObject *> global(cx, dbg->unwrapDebuggeeArgument(cx, args[0]));
     if (!global)
         return false;
+
+    ExecutionObservableCompartments obs(cx);
+    if (!obs.init())
+        return false;
+
     if (dbg->debuggees.has(global)) {
-        if (!dbg->removeDebuggeeGlobal(cx, global, nullptr))
+        dbg->removeDebuggeeGlobal(cx->runtime()->defaultFreeOp(), global, nullptr);
+
+        // Only update the compartment if there are no Debuggers left, as it's
+        // expensive to check if no other Debugger has a live script or frame hook
+        // on any of the current on-stack debuggee frames.
+        if (global->getDebuggers()->empty() && !obs.add(global->compartment()))
             return false;
-    }
+        if (!updateExecutionObservability(cx, obs, NotObserving))
+            return false;
+    }
+
     args.rval().setUndefined();
     return true;
 }
 
 /* static */ bool
 Debugger::removeAllDebuggees(JSContext *cx, unsigned argc, Value *vp)
 {
     THIS_DEBUGGER(cx, argc, vp, "removeAllDebuggees", args, dbg);
 
+    ExecutionObservableCompartments obs(cx);
+    if (!obs.init())
+        return false;
+
     for (GlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront()) {
         Rooted<GlobalObject *> global(cx, e.front());
-        if (!dbg->removeDebuggeeGlobal(cx, global, &e))
+        dbg->removeDebuggeeGlobal(cx->runtime()->defaultFreeOp(), global, &e);
+
+        // See note about adding to the observable set in removeDebuggee.
+        if (global->getDebuggers()->empty() && !obs.add(global->compartment()))
             return false;
     }
 
+    if (!updateExecutionObservability(cx, obs, NotObserving))
+        return false;
+
     args.rval().setUndefined();
     return true;
 }
 
 /* static */ bool
 Debugger::hasDebuggee(JSContext *cx, unsigned argc, Value *vp)
 {
     THIS_DEBUGGER(cx, argc, vp, "hasDebuggee", args, dbg);
@@ -2347,26 +2780,16 @@ Debugger::construct(JSContext *cx, unsig
 
     args.rval().setObject(*obj);
     return true;
 }
 
 bool
 Debugger::addDebuggeeGlobal(JSContext *cx, Handle<GlobalObject*> global)
 {
-    AutoSuppressProfilerSampling suppressProfilerSampling(cx);
-    AutoDebugModeInvalidation invalidate(global->compartment());
-    return addDebuggeeGlobal(cx, global, invalidate);
-}
-
-bool
-Debugger::addDebuggeeGlobal(JSContext *cx,
-                            Handle<GlobalObject*> global,
-                            AutoDebugModeInvalidation &invalidate)
-{
     if (debuggees.has(global))
         return true;
 
     // Callers should generally be unable to get a reference to a debugger-
     // invisible global in order to pass it to addDebuggee. But this is possible
     // with certain testing aides we expose in the shell, so just make addDebuggee
     // throw in that case.
     JSCompartment *debuggeeCompartment = global->compartment();
@@ -2391,17 +2814,17 @@ Debugger::addDebuggeeGlobal(JSContext *c
             JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_LOOP);
             return false;
         }
 
         /*
          * Find all compartments containing debuggers debugging c's global
          * object. Add those compartments to visited.
          */
-        if (c->debugMode()) {
+        if (c->isDebuggee()) {
             GlobalObject::DebuggerVector *v = c->maybeGlobal()->getDebuggers();
             for (Debugger **p = v->begin(); p != v->end(); p++) {
                 JSCompartment *next = (*p)->object->compartment();
                 if (Find(visited, next) == visited.end() && !visited.append(next))
                     return false;
             }
         }
     }
@@ -2429,38 +2852,39 @@ Debugger::addDebuggeeGlobal(JSContext *c
     AutoCompartment ac(cx, global);
     GlobalObject::DebuggerVector *v = GlobalObject::getOrCreateDebuggers(cx, global);
     if (!v || !v->append(this)) {
         js_ReportOutOfMemory(cx);
     } else {
         if (!debuggees.put(global)) {
             js_ReportOutOfMemory(cx);
         } else {
-            if (global->getDebuggers()->length() > 1)
+            debuggeeCompartment->setIsDebuggee();
+            if (!hasAnyLiveHooksThatObserveAllExecution())
                 return true;
-            if (debuggeeCompartment->enterDebugMode(cx, invalidate))
+            if (ensureExecutionObservabilityOfCompartment(cx, debuggeeCompartment))
                 return true;
 
             /* Maintain consistency on error. */
             debuggees.remove(global);
         }
+
         MOZ_ASSERT(v->back() == this);
         v->popBack();
     }
 
     /* Don't leave the object metadata hook set if we OOM'd. */
     if (setMetadataCallback)
         debuggeeCompartment->forgetObjectMetadataCallback();
 
     return false;
 }
 
 void
-Debugger::cleanupDebuggeeGlobalBeforeRemoval(FreeOp *fop, GlobalObject *global,
-                                             GlobalObjectSet::Enum *debugEnum)
+Debugger::removeDebuggeeGlobal(FreeOp *fop, GlobalObject *global, GlobalObjectSet::Enum *debugEnum)
 {
     /*
      * The caller might have found global by enumerating this->debuggees; if
      * so, use HashSet::Enum::removeFront rather than HashSet::remove below,
      * to avoid invalidating the live enumerator.
      */
     MOZ_ASSERT(debuggees.has(global));
     MOZ_ASSERT_IF(debugEnum, debugEnum->front() == global);
@@ -2512,53 +2936,24 @@ Debugger::cleanupDebuggeeGlobalBeforeRem
     MOZ_ASSERT_IF(debuggees.empty(), !firstBreakpoint());
 
     /*
      * If we are tracking allocation sites, we need to remove the object
      * metadata callback from this global's compartment.
      */
     if (trackingAllocationSites)
         global->compartment()->forgetObjectMetadataCallback();
-}
-
-bool
-Debugger::removeDebuggeeGlobal(JSContext *cx, Handle<GlobalObject *> global,
-                               GlobalObjectSet::Enum *debugEnum)
-{
-    AutoDebugModeInvalidation invalidate(global->compartment());
-    return removeDebuggeeGlobal(cx, global, invalidate, debugEnum);
-}
-
-bool
-Debugger::removeDebuggeeGlobal(JSContext *cx, Handle<GlobalObject *> global,
-                               AutoDebugModeInvalidation &invalidate,
-                               GlobalObjectSet::Enum *debugEnum)
-{
-    cleanupDebuggeeGlobalBeforeRemoval(cx->runtime()->defaultFreeOp(), global, debugEnum);
-
-    // The debuggee needs to be removed from the compartment last to save a root.
-    if (global->getDebuggers()->empty())
-        return global->compartment()->leaveDebugMode(cx, invalidate);
-
-    return true;
-}
-
-void
-Debugger::removeDebuggeeGlobalUnderGC(FreeOp *fop, GlobalObject *global,
-                                      GlobalObjectSet::Enum *debugEnum)
-{
-    cleanupDebuggeeGlobalBeforeRemoval(fop, global, debugEnum);
 
     /*
      * The debuggee needs to be removed from the compartment last, as this can
      * trigger GCs if the compartment's debug mode is being changed, and the
      * global cannot be rooted on the stack without a cx.
      */
     if (global->getDebuggers()->empty())
-        global->compartment()->leaveDebugModeUnderGC();
+        global->compartment()->unsetIsDebuggee();
 }
 
 static inline ScriptSourceObject *GetSourceReferent(JSObject *obj);
 
 /*
  * A class for parsing 'findScripts' query arguments and searching for
  * scripts that match the criteria they represent.
  */
@@ -4063,20 +4458,27 @@ DebuggerScript_setBreakpoint(JSContext *
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_DEBUGGING);
         return false;
     }
 
     size_t offset;
     if (!ScriptOffset(cx, script, args[0], &offset))
         return false;
 
-    JSObject *handler = NonNullObject(cx, args[1]);
+    RootedObject handler(cx, NonNullObject(cx, args[1]));
     if (!handler)
         return false;
 
+    // Ensure observability *before* setting the breakpoint. If the script's
+    // compartment is not already a debuggee, trying to ensure observability
+    // after setting the breakpoint (and thus marking the script as a
+    // debuggee) will skip actually ensuring observability.
+    if (!dbg->ensureExecutionObservabilityOfScript(cx, script))
+        return false;
+
     jsbytecode *pc = script->offsetToPC(offset);
     BreakpointSite *site = script->getOrCreateBreakpointSite(cx, pc);
     if (!site)
         return false;
     site->inc(cx->runtime()->defaultFreeOp());
     if (cx->runtime()->new_<Breakpoint>(dbg, site, handler)) {
         args.rval().setUndefined();
         return true;
@@ -4649,17 +5051,17 @@ CheckThisFrame(JSContext *cx, const Call
     {                                                                          \
         AbstractFramePtr f = AbstractFramePtr::FromRaw(thisobj->getPrivate()); \
         if (f.isScriptFrameIterData()) {                                       \
             maybeIter.emplace(*(ScriptFrameIter::Data *)(f.raw()));            \
         } else {                                                               \
             maybeIter.emplace(cx, ScriptFrameIter::ALL_CONTEXTS,               \
                               ScriptFrameIter::GO_THROUGH_SAVED);              \
             ScriptFrameIter &iter = *maybeIter;                                \
-            while (iter.isIon() || iter.abstractFramePtr() != f)               \
+            while (!iter.hasUsableAbstractFramePtr() || iter.abstractFramePtr() != f) \
                 ++iter;                                                        \
             AbstractFramePtr data = iter.copyDataAsAbstractFramePtr();         \
             if (!data)                                                         \
                 return false;                                                  \
             thisobj->setPrivate(data.raw());                                   \
         }                                                                      \
     }                                                                          \
     ScriptFrameIter &iter = *maybeIter
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -196,16 +196,37 @@ class Debugger : private mozilla::Linked
         JSSLOT_DEBUG_SOURCE_PROTO,
         JSSLOT_DEBUG_MEMORY_PROTO,
         JSSLOT_DEBUG_PROTO_STOP,
         JSSLOT_DEBUG_HOOK_START = JSSLOT_DEBUG_PROTO_STOP,
         JSSLOT_DEBUG_HOOK_STOP = JSSLOT_DEBUG_HOOK_START + HookCount,
         JSSLOT_DEBUG_MEMORY_INSTANCE = JSSLOT_DEBUG_HOOK_STOP,
         JSSLOT_DEBUG_COUNT
     };
+
+    class ExecutionObservableSet
+    {
+      public:
+        typedef HashSet<Zone *>::Range ZoneRange;
+
+        virtual Zone *singleZone() const { return nullptr; }
+        virtual JSScript *singleScriptForZoneInvalidation() const { return nullptr; }
+        virtual const HashSet<Zone *> *zones() const { return nullptr; }
+
+        virtual bool shouldRecompileOrInvalidate(JSScript *script) const = 0;
+        virtual bool shouldMarkAsDebuggee(ScriptFrameIter &iter) const = 0;
+    };
+
+    // This enum is converted to and compare with bool values; NotObserving
+    // must be 0 and Observing must be 1.
+    enum IsObserving {
+        NotObserving = 0,
+        Observing = 1
+    };
+
   private:
     HeapPtrNativeObject object;         /* The Debugger object. Strong reference. */
     GlobalObjectSet debuggees;          /* Debuggee globals. Cross-compartment weak references. */
     js::HeapPtrObject uncaughtExceptionHook; /* Strong reference. */
     bool enabled;
     JSCList breakpoints;                /* Circular list of all js::Breakpoints in this debugger */
 
     struct AllocationSite : public mozilla::LinkedListElement<AllocationSite>
@@ -269,27 +290,17 @@ class Debugger : private mozilla::Linked
     /* The map from debuggee Envs to Debugger.Environment instances. */
     ObjectWeakMap environments;
 
     class FrameRange;
     class ScriptQuery;
     class ObjectQuery;
 
     bool addDebuggeeGlobal(JSContext *cx, Handle<GlobalObject*> obj);
-    bool addDebuggeeGlobal(JSContext *cx, Handle<GlobalObject*> obj,
-                           AutoDebugModeInvalidation &invalidate);
-    void cleanupDebuggeeGlobalBeforeRemoval(FreeOp *fop, GlobalObject *global,
-                                            GlobalObjectSet::Enum *debugEnum);
-    bool removeDebuggeeGlobal(JSContext *cx, Handle<GlobalObject *> global,
-                              GlobalObjectSet::Enum *debugEnum);
-    bool removeDebuggeeGlobal(JSContext *cx, Handle<GlobalObject *> global,
-                              AutoDebugModeInvalidation &invalidate,
-                              GlobalObjectSet::Enum *debugEnum);
-    void removeDebuggeeGlobalUnderGC(FreeOp *fop, GlobalObject *global,
-                                     GlobalObjectSet::Enum *debugEnum);
+    void removeDebuggeeGlobal(FreeOp *fop, GlobalObject *global, GlobalObjectSet::Enum *debugEnum);
 
     /*
      * Cope with an error or exception in a debugger hook.
      *
      * If callHook is true, then call the uncaughtExceptionHook, if any. If, in
      * addition, vp is given, then parse the value returned by
      * uncaughtExceptionHook as a resumption value.
      *
@@ -372,16 +383,37 @@ class Debugger : private mozilla::Linked
     static bool findScripts(JSContext *cx, unsigned argc, Value *vp);
     static bool findObjects(JSContext *cx, unsigned argc, Value *vp);
     static bool findAllGlobals(JSContext *cx, unsigned argc, Value *vp);
     static bool makeGlobalObjectReference(JSContext *cx, unsigned argc, Value *vp);
     static bool construct(JSContext *cx, unsigned argc, Value *vp);
     static const JSPropertySpec properties[];
     static const JSFunctionSpec methods[];
 
+    static bool updateExecutionObservabilityOfFrames(JSContext *cx, const ExecutionObservableSet &obs,
+                                                     IsObserving observing);
+    static bool updateExecutionObservabilityOfScripts(JSContext *cx, const ExecutionObservableSet &obs,
+                                                      IsObserving observing);
+    static bool updateExecutionObservability(JSContext *cx, ExecutionObservableSet &obs,
+                                             IsObserving observing);
+
+  public:
+    // Public for DebuggerScript_setBreakpoint.
+    static bool ensureExecutionObservabilityOfScript(JSContext *cx, JSScript *script);
+
+  private:
+    static bool ensureExecutionObservabilityOfFrame(JSContext *cx, AbstractFramePtr frame);
+    static bool ensureExecutionObservabilityOfCompartment(JSContext *cx, JSCompartment *comp);
+
+    static bool hookObservesAllExecution(Hook which);
+    bool anyOtherDebuggerObservingAllExecution(GlobalObject *global) const;
+    bool hasAnyLiveHooksThatObserveAllExecution() const;
+    bool hasAnyHooksThatObserveAllExecution() const;
+    bool setObservesAllExecution(JSContext *cx, IsObserving observing);
+
     JSObject *getHook(Hook hook) const;
     bool hasAnyLiveHooks() const;
 
     static JSTrapStatus slowPathOnEnterFrame(JSContext *cx, AbstractFramePtr frame);
     static bool slowPathOnLeaveFrame(JSContext *cx, AbstractFramePtr frame, bool ok);
     static JSTrapStatus slowPathOnExceptionUnwind(JSContext *cx, AbstractFramePtr frame);
     static void slowPathOnNewScript(JSContext *cx, HandleScript script,
                                     GlobalObject *compileAndGoGlobal);
@@ -494,17 +526,18 @@ class Debugger : private mozilla::Linked
      * This function may be called twice for the same outgoing frame; only the
      * first call has any effect. (Permitting double calls simplifies some
      * cases where an onPop handler's resumption value changes a return to a
      * throw, or vice versa: we can redirect to a complete copy of the
      * alternative path, containing its own call to onLeaveFrame.)
      */
     static inline bool onLeaveFrame(JSContext *cx, AbstractFramePtr frame, bool ok);
 
-    static inline JSTrapStatus onDebuggerStatement(JSContext *cx, MutableHandleValue vp);
+    static inline JSTrapStatus onDebuggerStatement(JSContext *cx, AbstractFramePtr frame,
+                                                   MutableHandleValue vp);
 
     /*
      * Announce to the debugger that an exception has been thrown and propagated
      * to |frame|. Call whatever hooks have been registered to observe this and
      * return a JSTrapStatus code indication how execution should proceed:
      *
      * - JSTRAP_CONTINUE: Continue throwing the current exception.
      *
@@ -787,52 +820,28 @@ Debugger::observesNewGlobalObject() cons
 }
 
 bool
 Debugger::observesGlobal(GlobalObject *global) const
 {
     return debuggees.has(global);
 }
 
-/* static */ JSTrapStatus
-Debugger::onEnterFrame(JSContext *cx, AbstractFramePtr frame)
-{
-    if (!cx->compartment()->debugMode())
-        return JSTRAP_CONTINUE;
-    return slowPathOnEnterFrame(cx, frame);
-}
-
-/* static */ JSTrapStatus
-Debugger::onDebuggerStatement(JSContext *cx, MutableHandleValue vp)
-{
-    return cx->compartment()->debugMode()
-           ? dispatchHook(cx, vp, OnDebuggerStatement)
-           : JSTRAP_CONTINUE;
-}
-
-/* static */ JSTrapStatus
-Debugger::onExceptionUnwind(JSContext *cx, AbstractFramePtr frame)
-{
-    if (!cx->compartment()->debugMode())
-        return JSTRAP_CONTINUE;
-    return slowPathOnExceptionUnwind(cx, frame);
-}
-
-/* static */ void
+void
 Debugger::onNewScript(JSContext *cx, HandleScript script, GlobalObject *compileAndGoGlobal)
 {
     MOZ_ASSERT_IF(script->compileAndGo(), compileAndGoGlobal);
     MOZ_ASSERT_IF(script->compileAndGo(), compileAndGoGlobal == &script->uninlinedGlobal());
     // We early return in slowPathOnNewScript for self-hosted scripts, so we can
     // ignore those in our assertion here.
     MOZ_ASSERT_IF(!script->compartment()->options().invisibleToDebugger() &&
                   !script->selfHosted(),
                   script->compartment()->firedOnNewGlobalObject);
     MOZ_ASSERT_IF(!script->compileAndGo(), !compileAndGoGlobal);
-    if (script->compartment()->debugMode())
+    if (script->compartment()->isDebuggee())
         slowPathOnNewScript(cx, script, compileAndGoGlobal);
 }
 
 /* static */ void
 Debugger::onNewGlobalObject(JSContext *cx, Handle<GlobalObject *> global)
 {
     MOZ_ASSERT(!global->compartment()->firedOnNewGlobalObject);
 #ifdef DEBUG
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -862,17 +862,17 @@ js::EnterWithOperation(JSContext *cx, Ab
     return true;
 }
 
 static void
 PopScope(JSContext *cx, ScopeIter &si)
 {
     switch (si.type()) {
       case ScopeIter::Block:
-        if (cx->compartment()->debugMode())
+        if (cx->compartment()->isDebuggee())
             DebugScopes::onPopBlock(cx, si);
         if (si.staticBlock().needsClone())
             si.frame().popBlock(cx);
         break;
       case ScopeIter::With:
         si.frame().popWith(cx);
         break;
       case ScopeIter::Call:
@@ -1525,17 +1525,17 @@ CASE(EnableInterruptsPseudoOpcode)
     }
 
     if (script->hasScriptCounts()) {
         PCCounts counts = script->getPCCounts(REGS.pc);
         counts.get(PCCounts::BASE_INTERP)++;
         moreInterrupts = true;
     }
 
-    if (cx->compartment()->debugMode()) {
+    if (script->isDebuggee()) {
         if (script->stepModeEnabled()) {
             RootedValue rval(cx);
             JSTrapStatus status = JSTRAP_CONTINUE;
             status = Debugger::onSingleStep(cx, &rval);
             switch (status) {
               case JSTRAP_ERROR:
                 goto error;
               case JSTRAP_CONTINUE:
@@ -3291,17 +3291,17 @@ CASE(JSOP_INSTANCEOF)
     REGS.sp--;
     REGS.sp[-1].setBoolean(cond);
 }
 END_CASE(JSOP_INSTANCEOF)
 
 CASE(JSOP_DEBUGGER)
 {
     RootedValue rval(cx);
-    switch (Debugger::onDebuggerStatement(cx, &rval)) {
+    switch (Debugger::onDebuggerStatement(cx, REGS.fp(), &rval)) {
       case JSTRAP_ERROR:
         goto error;
       case JSTRAP_CONTINUE:
         break;
       case JSTRAP_RETURN:
         REGS.fp()->setReturnValue(rval);
         ForcedReturn(cx, REGS);
         goto successful_return_continuation;
@@ -3343,17 +3343,17 @@ END_CASE(JSOP_POPBLOCKSCOPE)
 CASE(JSOP_DEBUGLEAVEBLOCK)
 {
     MOZ_ASSERT(script->getStaticScope(REGS.pc));
     MOZ_ASSERT(script->getStaticScope(REGS.pc)->is<StaticBlockObject>());
 
     // FIXME: This opcode should not be necessary.  The debugger shouldn't need
     // help from bytecode to do its job.  See bug 927782.
 
-    if (MOZ_UNLIKELY(cx->compartment()->debugMode()))
+    if (MOZ_UNLIKELY(cx->compartment()->isDebuggee()))
         DebugScopes::onPopBlock(cx, REGS.fp(), REGS.pc);
 }
 END_CASE(JSOP_DEBUGLEAVEBLOCK)
 
 CASE(JSOP_GENERATOR)
 {
     MOZ_ASSERT(!cx->isExceptionPending());
     MOZ_ASSERT(REGS.stackDepth() == 0);
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -540,17 +540,17 @@ InvokeInterruptCallback(JSContext *cx)
     // callback is disconnected before attempting such re-entry.
     JSInterruptCallback cb = cx->runtime()->interruptCallback;
     if (!cb)
         return true;
 
     if (cb(cx)) {
         // Debugger treats invoking the interrupt callback as a "step", so
         // invoke the onStep handler.
-        if (cx->compartment()->debugMode()) {
+        if (cx->compartment()->isDebuggee()) {
             ScriptFrameIter iter(cx);
             if (iter.script()->stepModeEnabled()) {
                 RootedValue rval(cx);
                 switch (Debugger::onSingleStep(cx, &rval)) {
                   case JSTRAP_ERROR:
                     return false;
                   case JSTRAP_CONTINUE:
                     return true;
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -2047,24 +2047,24 @@ DebugScopes::checkHashTablesAfterMovingG
         CheckGCThingAfterMovingGC(r.front().value().staticScope_.get());
     }
 }
 #endif
 
 /*
  * Unfortunately, GetDebugScopeForFrame needs to work even outside debug mode
  * (in particular, JS_GetFrameScopeChain does not require debug mode). Since
- * DebugScopes::onPop* are only called in debug mode, this means we cannot
- * use any of the maps in DebugScopes. This will produce debug scope chains
- * that do not obey the debugger invariants but that is just fine.
+ * DebugScopes::onPop* are only called in debuggee frames, this means we
+ * cannot use any of the maps in DebugScopes. This will produce debug scope
+ * chains that do not obey the debugger invariants but that is just fine.
  */
 static bool
 CanUseDebugScopeMaps(JSContext *cx)
 {
-    return cx->compartment()->debugMode();
+    return cx->compartment()->isDebuggee();
 }
 
 DebugScopes *
 DebugScopes::ensureCompartmentData(JSContext *cx)
 {
     JSCompartment *c = cx->compartment();
     if (c->debugScopes)
         return c->debugScopes;
@@ -2301,17 +2301,17 @@ DebugScopes::onPopStrictEvalScope(Abstra
      * The stack frame may be observed before the prologue has created the
      * CallObject. See ScopeIter::settle.
      */
     if (frame.hasCallObj())
         scopes->liveScopes.remove(&frame.scopeChain()->as<CallObject>());
 }
 
 void
-DebugScopes::onCompartmentLeaveDebugMode(JSCompartment *c)
+DebugScopes::onCompartmentUnsetIsDebuggee(JSCompartment *c)
 {
     DebugScopes *scopes = c->debugScopes;
     if (scopes) {
         scopes->proxiedScopes.clear();
         scopes->missingScopes.clear();
         scopes->liveScopes.clear();
     }
 }
@@ -2338,31 +2338,34 @@ DebugScopes::updateLiveScopes(JSContext 
 
         AbstractFramePtr frame = i.abstractFramePtr();
         if (frame.scopeChain()->compartment() != cx->compartment())
             continue;
 
         if (frame.isFunctionFrame() && frame.callee()->isGenerator())
             continue;
 
+        if (!frame.isDebuggee())
+            continue;
+
         for (ScopeIter si(frame, i.pc(), cx); !si.done(); ++si) {
             if (si.hasScopeObject()) {
                 MOZ_ASSERT(si.scope().compartment() == cx->compartment());
                 DebugScopes *scopes = ensureCompartmentData(cx);
                 if (!scopes)
                     return false;
                 if (!scopes->liveScopes.put(&si.scope(), ScopeIterVal(si)))
                     return false;
                 liveScopesPostWriteBarrier(cx->runtime(), &scopes->liveScopes, &si.scope());
             }
         }
 
         if (frame.prevUpToDate())
             return true;
-        MOZ_ASSERT(frame.scopeChain()->compartment()->debugMode());
+        MOZ_ASSERT(frame.scopeChain()->compartment()->isDebuggee());
         frame.setPrevUpToDate();
     }
 
     return true;
 }
 
 ScopeIterVal*
 DebugScopes::hasLiveScope(ScopeObject &scope)
@@ -2519,17 +2522,17 @@ GetDebugScope(JSContext *cx, const Scope
     ScopeIter copy(si, cx);
     return GetDebugScopeForScope(cx, scope, ++copy);
 }
 
 JSObject *
 js::GetDebugScopeForFunction(JSContext *cx, HandleFunction fun)
 {
     assertSameCompartment(cx, fun);
-    MOZ_ASSERT(cx->compartment()->debugMode());
+    MOZ_ASSERT(CanUseDebugScopeMaps(cx));
     if (!DebugScopes::updateLiveScopes(cx))
         return nullptr;
     return GetDebugScope(cx, *fun->environment());
 }
 
 JSObject *
 js::GetDebugScopeForFrame(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc)
 {
--- a/js/src/vm/ScopeObject.h
+++ b/js/src/vm/ScopeObject.h
@@ -935,17 +935,17 @@ class DebugScopes
 
     // In debug-mode, these must be called whenever exiting a scope that might
     // have stack-allocated locals.
     static void onPopCall(AbstractFramePtr frame, JSContext *cx);
     static void onPopBlock(JSContext *cx, const ScopeIter &si);
     static void onPopBlock(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc);
     static void onPopWith(AbstractFramePtr frame);
     static void onPopStrictEvalScope(AbstractFramePtr frame);
-    static void onCompartmentLeaveDebugMode(JSCompartment *c);
+    static void onCompartmentUnsetIsDebuggee(JSCompartment *c);
 };
 
 }  /* namespace js */
 
 template<>
 inline bool
 JSObject::is<js::NestedScopeObject>() const
 {
--- a/js/src/vm/Stack-inl.h
+++ b/js/src/vm/Stack-inl.h
@@ -7,23 +7,25 @@
 #ifndef vm_Stack_inl_h
 #define vm_Stack_inl_h
 
 #include "vm/Stack.h"
 
 #include "mozilla/PodOperations.h"
 
 #include "jscntxt.h"
+#include "jsscript.h"
 
 #include "jit/BaselineFrame.h"
 #include "jit/RematerializedFrame.h"
 #include "vm/GeneratorObject.h"
 #include "vm/ScopeObject.h"
 
 #include "jsobjinlines.h"
+#include "jsscriptinlines.h"
 
 #include "jit/BaselineFrame-inl.h"
 
 namespace js {
 
 /*
  * We cache name lookup results only for the global object or for native
  * non-global objects without prototype or with prototype that never mutates,
@@ -84,16 +86,19 @@ InterpreterFrame::initCallFrame(JSContex
     argv_ = argv;
     exec.fun = &callee;
     u.nactual = nactual;
     scopeChain_ = callee.environment();
     prev_ = prev;
     prevpc_ = prevpc;
     prevsp_ = prevsp;
 
+    if (script->isDebuggee())
+        setIsDebuggee();
+
     initLocals();
 }
 
 inline void
 InterpreterFrame::initLocals()
 {
     SetValueRangeToUndefined(slots(), script()->nfixedvars());
 
@@ -215,16 +220,23 @@ InterpreterFrame::callObj() const
     MOZ_ASSERT(fun()->isHeavyweight());
 
     JSObject *pobj = scopeChain();
     while (MOZ_UNLIKELY(!pobj->is<CallObject>()))
         pobj = pobj->enclosingScope();
     return pobj->as<CallObject>();
 }
 
+inline void
+InterpreterFrame::unsetIsDebuggee()
+{
+    MOZ_ASSERT(!script()->isDebuggee());
+    flags_ &= ~DEBUGGEE;
+}
+
 /*****************************************************************************/
 
 inline void
 InterpreterStack::purge(JSRuntime *rt)
 {
     rt->gc.freeUnusedLifoBlocksAfterSweeping(&allocator_);
 }
 
@@ -553,28 +565,61 @@ AbstractFramePtr::isEvalFrame() const
 {
     if (isInterpreterFrame())
         return asInterpreterFrame()->isEvalFrame();
     if (isBaselineFrame())
         return asBaselineFrame()->isEvalFrame();
     MOZ_ASSERT(isRematerializedFrame());
     return false;
 }
+
 inline bool
 AbstractFramePtr::isDebuggerEvalFrame() const
 {
     if (isInterpreterFrame())
         return asInterpreterFrame()->isDebuggerEvalFrame();
     if (isBaselineFrame())
         return asBaselineFrame()->isDebuggerEvalFrame();
     MOZ_ASSERT(isRematerializedFrame());
     return false;
 }
 
 inline bool
+AbstractFramePtr::isDebuggee() const
+{
+    if (isInterpreterFrame())
+        return asInterpreterFrame()->isDebuggee();
+    if (isBaselineFrame())
+        return asBaselineFrame()->isDebuggee();
+    return asRematerializedFrame()->isDebuggee();
+}
+
+inline void
+AbstractFramePtr::setIsDebuggee()
+{
+    if (isInterpreterFrame())
+        asInterpreterFrame()->setIsDebuggee();
+    else if (isBaselineFrame())
+        asBaselineFrame()->setIsDebuggee();
+    else
+        asRematerializedFrame()->setIsDebuggee();
+}
+
+inline void
+AbstractFramePtr::unsetIsDebuggee()
+{
+    if (isInterpreterFrame())
+        asInterpreterFrame()->unsetIsDebuggee();
+    else if (isBaselineFrame())
+        asBaselineFrame()->unsetIsDebuggee();
+    else
+        asRematerializedFrame()->unsetIsDebuggee();
+}
+
+inline bool
 AbstractFramePtr::hasArgs() const {
     return isNonEvalFunctionFrame();
 }
 
 inline JSScript *
 AbstractFramePtr::script() const
 {
     if (isInterpreterFrame())
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -82,16 +82,19 @@ InterpreterFrame::initExecuteFrame(JSCon
     scopeChain_ = &scopeChain;
     prev_ = nullptr;
     prevpc_ = nullptr;
     prevsp_ = nullptr;
 
     MOZ_ASSERT_IF(evalInFramePrev, isDebuggerEvalFrame());
     evalInFramePrev_ = evalInFramePrev;
 
+    if (script->isDebuggee())
+        setIsDebuggee();
+
 #ifdef DEBUG
     Debug_SetValueRangeToCrashOnTouch(&rval_, 1);
 #endif
 }
 
 void
 InterpreterFrame::writeBarrierPost()
 {
@@ -224,17 +227,17 @@ void
 InterpreterFrame::epilogue(JSContext *cx)
 {
     RootedScript script(cx, this->script());
     probes::ExitScript(cx, script, script->functionNonDelazifying(), hasPushedSPSFrame());
 
     if (isEvalFrame()) {
         if (isStrictEvalFrame()) {
             MOZ_ASSERT_IF(hasCallObj(), scopeChain()->as<CallObject>().isForEval());
-            if (MOZ_UNLIKELY(cx->compartment()->debugMode()))
+            if (MOZ_UNLIKELY(cx->compartment()->isDebuggee()))
                 DebugScopes::onPopStrictEvalScope(this);
         } else if (isDirectEvalFrame()) {
             if (isDebuggerEvalFrame())
                 MOZ_ASSERT(!scopeChain()->is<ScopeObject>());
         } else {
             /*
              * Debugger.Object.prototype.evalInGlobal creates indirect eval
              * frames scoped to the given global;
@@ -263,17 +266,17 @@ InterpreterFrame::epilogue(JSContext *cx
 
     if (fun()->isHeavyweight()) {
         MOZ_ASSERT_IF(hasCallObj() && !fun()->isGenerator(),
                       scopeChain()->as<CallObject>().callee().nonLazyScript() == script);
     } else {
         AssertDynamicScopeMatchesStaticScope(cx, script, scopeChain());
     }
 
-    if (MOZ_UNLIKELY(cx->compartment()->debugMode()))
+    if (MOZ_UNLIKELY(cx->compartment()->isDebuggee()))
         DebugScopes::onPopCall(this, cx);
 
     if (isConstructing() && thisValue().isObject() && returnValue().isPrimitive())
         setReturnValue(ObjectValue(constructorThis()));
 }
 
 bool
 InterpreterFrame::pushBlock(JSContext *cx, StaticBlockObject &block)
@@ -295,17 +298,17 @@ InterpreterFrame::popBlock(JSContext *cx
 {
     MOZ_ASSERT(scopeChain_->is<ClonedBlockObject>());
     popOffScopeChain();
 }
 
 void
 InterpreterFrame::popWith(JSContext *cx)
 {
-    if (MOZ_UNLIKELY(cx->compartment()->debugMode()))
+    if (MOZ_UNLIKELY(cx->compartment()->isDebuggee()))
         DebugScopes::onPopWith(this);
 
     MOZ_ASSERT(scopeChain()->is<DynamicWithObject>());
     popOffScopeChain();
 }
 
 void
 InterpreterFrame::mark(JSTracer *trc)
@@ -705,28 +708,27 @@ FrameIter &
 FrameIter::operator++()
 {
     switch (data_.state_) {
       case DONE:
         MOZ_CRASH("Unexpected state");
       case INTERP:
         if (interpFrame()->isDebuggerEvalFrame() && interpFrame()->evalInFramePrev()) {
             AbstractFramePtr eifPrev = interpFrame()->evalInFramePrev();
-            MOZ_ASSERT(!eifPrev.isRematerializedFrame());
 
             // Eval-in-frame can cross contexts and works across saved frame
             // chains.
             ContextOption prevContextOption = data_.contextOption_;
             SavedOption prevSavedOption = data_.savedOption_;
             data_.contextOption_ = ALL_CONTEXTS;
             data_.savedOption_ = GO_THROUGH_SAVED;
 
             popInterpreterFrame();
 
-            while (isIon() || abstractFramePtr() != eifPrev) {
+            while (!hasUsableAbstractFramePtr() || abstractFramePtr() != eifPrev) {
                 if (data_.state_ == JIT)
                     popJitFrame();
                 else
                     popInterpreterFrame();
             }
 
             data_.contextOption_ = prevContextOption;
             data_.savedOption_ = prevSavedOption;
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -213,16 +213,20 @@ class AbstractFramePtr
     inline Value &unaliasedLocal(uint32_t i);
     inline Value &unaliasedFormal(unsigned i, MaybeCheckAliasing checkAliasing = CHECK_ALIASING);
     inline Value &unaliasedActual(unsigned i, MaybeCheckAliasing checkAliasing = CHECK_ALIASING);
     template <class Op> inline void unaliasedForEachActual(JSContext *cx, Op op);
 
     inline bool prevUpToDate() const;
     inline void setPrevUpToDate() const;
 
+    inline bool isDebuggee() const;
+    inline void setIsDebuggee();
+    inline void unsetIsDebuggee();
+
     JSObject *evalPrevScopeChain(JSContext *cx) const;
 
     inline HandleValue returnValue() const;
     inline void setReturnValue(const Value &rval) const;
 
     bool hasPushedSPSFrame() const;
 
     inline void popBlock(JSContext *cx) const;
@@ -291,27 +295,33 @@ class InterpreterFrame
 
         /* Lazy frame initialization */
         HAS_RVAL           =      0x800,  /* frame has rval_ set */
         HAS_SCOPECHAIN     =     0x1000,  /* frame has scopeChain_ set */
 
         /* Debugger state */
         PREV_UP_TO_DATE    =     0x4000,  /* see DebugScopes::updateLiveScopes */
 
+        /*
+         * See comment above 'debugMode' in jscompartment.h for explanation of
+         * invariants of debuggee compartments, scripts, and frames.
+         */
+        DEBUGGEE           =     0x8000,  /* Execution is being observed by Debugger */
+
         /* Used in tracking calls and profiling (see vm/SPSProfiler.cpp) */
-        HAS_PUSHED_SPS_FRAME =   0x8000,  /* SPS was notified of enty */
+        HAS_PUSHED_SPS_FRAME =   0x10000, /* SPS was notified of enty */
 
         /*
          * If set, we entered one of the JITs and ScriptFrameIter should skip
          * this frame.
          */
-        RUNNING_IN_JIT     =    0x10000,
+        RUNNING_IN_JIT     =    0x20000,
 
         /* Miscellaneous state. */
-        USE_NEW_TYPE       =    0x20000   /* Use new type for constructed |this| object. */
+        USE_NEW_TYPE       =    0x40000   /* Use new type for constructed |this| object. */
     };
 
   private:
     mutable uint32_t    flags_;         /* bits described by Flags */
     union {                             /* describes what code is executing in a */
         JSScript        *script;        /*   global frame */
         JSFunction      *fun;           /*   function frame, pre GetScopeChain */
     } exec;
@@ -818,16 +828,26 @@ class InterpreterFrame
     bool prevUpToDate() const {
         return !!(flags_ & PREV_UP_TO_DATE);
     }
 
     void setPrevUpToDate() {
         flags_ |= PREV_UP_TO_DATE;
     }
 
+    bool isDebuggee() const {
+        return !!(flags_ & DEBUGGEE);
+    }
+
+    void setIsDebuggee() {
+        flags_ |= DEBUGGEE;
+    }
+
+    inline void unsetIsDebuggee();
+
   public:
     void mark(JSTracer *trc);
     void markValues(JSTracer *trc, unsigned start, unsigned end);
     void markValues(JSTracer *trc, Value *sp, jsbytecode *pc);
 
     // Entered Baseline/Ion from the interpreter.
     bool runningInJit() const {
         return !!(flags_ & RUNNING_IN_JIT);