Bug 1518661 - Part 7: Update debugger server to use new getPossibleBreakpoints APIs. r=jlast
authorLogan Smyth <loganfsmyth@gmail.com>
Wed, 13 Feb 2019 02:31:03 +0000
changeset 458907 266c1eee61a8
parent 458906 62f3c188b868
child 458908 b3e21f09ee45
push id35551
push usershindli@mozilla.com
push dateWed, 13 Feb 2019 21:34:09 +0000
treeherdermozilla-central@08f794a4928e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjlast
bugs1518661
milestone67.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 1518661 - Part 7: Update debugger server to use new getPossibleBreakpoints APIs. r=jlast Making use of the new SpiderMonkey APIs for available breakpoints means that the server needs to think a lot less about where it is pausing and allows us to drop the concept of a pause points from the server entirely. It is now up to SpiderMonkey to decide where it will and will not stop when it is stepping. Differential Revision: https://phabricator.services.mozilla.com/D17665
devtools/client/debugger/new/src/actions/ast/setPausePoints.js
devtools/client/debugger/new/test/mochitest/browser_dbg-breakpoints-actions.js
devtools/client/debugger/new/test/mochitest/browser_dbg-keyboard-shortcuts.js
devtools/client/debugger/new/test/mochitest/browser_dbg-pause-points.js
devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemapped-stepping.js
devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemaps.js
devtools/client/debugger/new/test/mochitest/browser_dbg-stepping.js
devtools/client/webreplay/mochitest/browser_dbg_rr_stepping-04.js
devtools/server/actors/replay/debugger.js
devtools/server/actors/replay/replay.js
devtools/server/actors/source.js
devtools/server/actors/thread.js
devtools/server/actors/utils/TabSources.js
devtools/server/tests/unit/test_breakpoint-10.js
devtools/server/tests/unit/test_breakpoint-11.js
devtools/server/tests/unit/test_breakpoint-13.js
devtools/server/tests/unit/test_breakpoint-14.js
devtools/server/tests/unit/test_setBreakpoint-on-line-with-multiple-offsets.js
devtools/server/tests/unit/test_source-02.js
devtools/server/tests/unit/test_stepping-01.js
devtools/server/tests/unit/test_stepping-06.js
devtools/server/tests/unit/test_stepping-07.js
devtools/server/tests/unit/test_stepping-08.js
devtools/server/tests/unit/test_stepping-09.js
devtools/server/tests/unit/test_stepping-with-pause-points.js
devtools/server/tests/unit/test_stepping-with-skip-breakpoints.js
devtools/server/tests/unit/xpcshell.ini
--- a/devtools/client/debugger/new/src/actions/ast/setPausePoints.js
+++ b/devtools/client/debugger/new/src/actions/ast/setPausePoints.js
@@ -9,29 +9,16 @@ import * as parser from "../../workers/p
 import { isGenerated } from "../../utils/source";
 import { convertToList } from "../../utils/pause/pausePoints";
 import { features } from "../../utils/prefs";
 import { getGeneratedLocation } from "../../utils/source-maps";
 
 import type { SourceId } from "../../types";
 import type { ThunkArgs, Action } from "../types";
 
-function compressPausePoints(pausePoints) {
-  const compressed = {};
-  for (const line in pausePoints) {
-    compressed[line] = {};
-    for (const col in pausePoints[line]) {
-      const { types } = pausePoints[line][col];
-      compressed[line][col] = (types.break ? 1 : 0) | (types.step ? 2 : 0);
-    }
-  }
-
-  return compressed;
-}
-
 async function mapLocations(pausePoints, state, source, sourceMaps) {
   const pausePointList = convertToList(pausePoints);
   const sourceId = source.id;
 
   return Promise.all(
     pausePointList.map(async ({ types, location }) => {
       const generatedLocation = await getGeneratedLocation(
         state,
@@ -53,27 +40,18 @@ export function setPausePoints(sourceId:
     if (!features.pausePoints || !source || !source.text) {
       return;
     }
 
     if (source.isWasm) {
       return;
     }
 
-    let pausePoints = await parser.getPausePoints(sourceId);
-
-    if (isGenerated(source)) {
-      const compressed = compressPausePoints(pausePoints);
-      for (const sourceActor of getSourceActors(getState(), sourceId)) {
-        await client.setPausePoints(sourceActor, compressed);
-      }
-    }
-
-    pausePoints = await mapLocations(
-      pausePoints,
+    const pausePoints = await mapLocations(
+      await parser.getPausePoints(sourceId),
       getState(),
       source,
       sourceMaps
     );
 
     dispatch(
       ({
         type: "SET_PAUSE_POINTS",
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg-breakpoints-actions.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-breakpoints-actions.js
@@ -23,61 +23,60 @@ add_task(async function() {
 });
 
 // Tests "disable others", "enable others" and "remove others" context actions
 add_task(async function() {
   const dbg = await initDebugger("doc-scripts.html");
   await selectSource(dbg, "simple1");
   await waitForSelectedSource(dbg, "simple1");
 
-  await addBreakpoint(dbg, "simple1", 1);
   await addBreakpoint(dbg, "simple1", 4);
   await addBreakpoint(dbg, "simple1", 5);
   await addBreakpoint(dbg, "simple1", 6);
 
   openFirstBreakpointContextMenu(dbg);
   // select "Disable Others"
   // FIXME bug 1524374 this waitForDispatch call only sees one dispatch for
   // DISABLE_BREAKPOINT even though three are triggered, due to the order in
   // which promises get resolved. The problem seems to indicate a coverage gap
   // in waitUntilService(). Workaround this by only waiting for one dispatch,
   // though this is fragile and could break again in the future.
-  let dispatched = waitForDispatch(dbg, "DISABLE_BREAKPOINT", /*3*/ 1);
+  let dispatched = waitForDispatch(dbg, "DISABLE_BREAKPOINT", /*2*/ 1);
   selectContextMenuItem(dbg, selectors.breakpointContextMenu.disableOthers);
   await waitForState(dbg, state =>
     dbg.selectors.getBreakpointsList(state)
-      .every(bp => (bp.location.line !== 1) === bp.disabled)
+      .every(bp => (bp.location.line !== 4) === bp.disabled)
   );
   await dispatched;
-  ok("breakpoint at 1 is the only enabled breakpoint");
+  ok("breakpoint at 4 is the only enabled breakpoint");
 
   openFirstBreakpointContextMenu(dbg);
   // select "Disable All"
   dispatched = waitForDispatch(dbg, "DISABLE_ALL_BREAKPOINTS");
   selectContextMenuItem(dbg, selectors.breakpointContextMenu.disableAll);
   await waitForState(dbg, state =>
     dbg.selectors.getBreakpointsList(state).every(bp => bp.disabled)
   );
   await dispatched;
   ok("all breakpoints are disabled");
 
   openFirstBreakpointContextMenu(dbg);
   // select "Enable Others"
-  dispatched = waitForDispatch(dbg, "ENABLE_BREAKPOINT", 3);
+  dispatched = waitForDispatch(dbg, "ENABLE_BREAKPOINT", 2);
   selectContextMenuItem(dbg, selectors.breakpointContextMenu.enableOthers);
   await waitForState(dbg, state =>
     dbg.selectors.getBreakpointsList(state)
-      .every(bp => (bp.location.line === 1) === bp.disabled)
+      .every(bp => (bp.location.line === 4) === bp.disabled)
   );
   await dispatched;
   ok("all breakpoints except line 1 are enabled");
 
   openFirstBreakpointContextMenu(dbg);
   // select "Remove Others"
-  dispatched = waitForDispatch(dbg, "REMOVE_BREAKPOINT", 3);
+  dispatched = waitForDispatch(dbg, "REMOVE_BREAKPOINT", 2);
   selectContextMenuItem(dbg, selectors.breakpointContextMenu.removeOthers);
   await waitForState(dbg, state =>
     dbg.selectors.getBreakpointsList(state).length === 1 &&
-    dbg.selectors.getBreakpointsList(state)[0].location.line === 1
+    dbg.selectors.getBreakpointsList(state)[0].location.line === 4
   );
   await dispatched;
-  ok("remaining breakpoint should be on line 1");
+  ok("remaining breakpoint should be on line 4");
 });
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg-keyboard-shortcuts.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-keyboard-shortcuts.js
@@ -31,16 +31,19 @@ add_task(async function() {
   await reload(dbg);
   await waitForPaused(dbg);
   await waitForLoadedSource(dbg, "doc-debugger-statements.html");
   assertPausedLocation(dbg, "doc-debugger-statements");
 
   await pressResume(dbg);
   assertPausedLocation(dbg);
 
+  await pressStepOver(dbg);
+  assertPausedLocation(dbg);
+
   await pressStepIn(dbg);
   assertPausedLocation(dbg);
 
   await pressStepOut(dbg);
   assertPausedLocation(dbg);
 
   await pressStepOver(dbg);
   assertPausedLocation(dbg);
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg-pause-points.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-pause-points.js
@@ -28,50 +28,35 @@ async function testCase(dbg, { name, ste
 }
 
 add_task(async function test() {
   const dbg = await initDebugger("doc-pause-points.html", "pause-points.js");
 
   await selectSource(dbg, "pause-points.js")
   await testCase(dbg, {
     name: "statements",
-    steps: [
-      [9, 2],
-      [10, 4],
-      [10, 13],
-      [11, 2],
-      [11, 10],
-      [11, 21],
-      [11, 29],
-      [12, 2],
-      [12, 12],
-      [13, 0]
-    ]
+    steps: [[9,2], [10,4], [10,13], [11,2], [11,21], [12,2], [12,12], [13,0]]
   });
 
   await testCase(dbg, {
     name: "expressions",
-    steps: [[40,2], [41,2], [41,8], [42,12], [43,0]]
+    steps: [[40,2], [41,2], [42,12], [43,0]]
   });
 
   await testCase(dbg, {
     name: "sequences",
-    steps: [[23,2], [25,12], [31,4], [34,2], [37,0]]
+    steps: [[23,2], [25,12], [29,12], [34,2], [37,0]]
   });
 
   await testCase(dbg, {
     name: "flow",
     steps: [
       [16, 2],
       [17, 12],
-      [17, 20],
       [18, 10],
-      [19, 2],
       [19, 8],
       [19, 17],
-      [19, 25],
       [19, 8],
       [19, 17],
-      [19, 25],
       [19, 8]
     ]
   });
 });
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemapped-stepping.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemapped-stepping.js
@@ -52,16 +52,17 @@ async function runSteps(dbg, source, ste
 function testStepOverForOf(dbg) {
   return breakpointSteps(
     dbg,
     "webpack3-babel6",
     "step-over-for-of",
     { line: 4, column: 2 },
     [
       ["stepOver", { line: 6, column: 20 }],
+      ["stepOver", { line: 6, column: 2 }],
       ["stepOver", { line: 7, column: 4 }],
       ["stepOver", { line: 6, column: 2 }],
       ["stepOver", { line: 7, column: 4 }],
       ["stepOver", { line: 6, column: 2 }],
       ["stepOver", { line: 10, column: 2 }]
     ]
   );
 }
@@ -71,20 +72,20 @@ function testStepOverForOf(dbg) {
 function testStepOverForOfArray(dbg) {
   return breakpointSteps(
     dbg,
     "webpack3-babel6",
     "step-over-for-of-array",
     { line: 3, column: 2 },
     [
       ["stepOver", { line: 5, column: 2 }],
-      ["stepOver", { line: 5, column: 7 }],
+      ["stepOver", { line: 5, column: 13 }],
       ["stepOver", { line: 6, column: 4 }],
       ["stepOver", { line: 5, column: 2 }],
-      ["stepOver", { line: 5, column: 7 }],
+      ["stepOver", { line: 5, column: 13 }],
       ["stepOver", { line: 6, column: 4 }],
       ["stepOver", { line: 5, column: 2 }],
       ["stepOver", { line: 9, column: 2 }]
     ]
   );
 }
 
 // The closure means it isn't actually possible to step into the for body,
@@ -109,19 +110,19 @@ function testStepOveForOfClosure(dbg) {
 function testStepOverForOfArrayClosure(dbg) {
   return breakpointSteps(
     dbg,
     "webpack3-babel6",
     "step-over-for-of-array-closure",
     { line: 3, column: 2 },
     [
       ["stepOver", { line: 5, column: 2 }],
-      ["stepOver", { line: 5, column: 7 }],
+      ["stepOver", { line: 5, column: 13 }],
       ["stepOver", { line: 5, column: 2 }],
-      ["stepOver", { line: 5, column: 7 }],
+      ["stepOver", { line: 5, column: 13 }],
       ["stepOver", { line: 5, column: 2 }],
       ["stepOver", { line: 9, column: 2 }]
     ]
   );
 }
 
 function testStepOverFunctionParams(dbg) {
   return breakpointSteps(
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemaps.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemaps.js
@@ -77,17 +77,16 @@ add_task(async function() {
   is(getBreakpointCount(getState()), 1, "One breakpoint exists");
   assertBreakpointExists(dbg, entrySrc, 15);
 
   invokeInTab("keepMeAlive");
   await waitForPaused(dbg);
   assertPausedLocation(dbg);
 
   await stepIn(dbg);
-  await stepIn(dbg);
   assertPausedLocation(dbg);
 
   await dbg.actions.jumpToMappedSelectedLocation();
   await stepOver(dbg);
   assertPausedLocation(dbg);
   assertDebugLine(dbg, 71);
 
   await dbg.actions.jumpToMappedSelectedLocation();
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg-stepping.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-stepping.js
@@ -19,11 +19,11 @@ add_task(async function test() {
   await stepIn(dbg);
   await stepIn(dbg);
   await stepIn(dbg);
   await stepIn(dbg);
   await stepIn(dbg);
   await stepIn(dbg);
   await stepIn(dbg);
 
-  assertDebugLine(dbg, 42267);
+  assertDebugLine(dbg, 42271);
   assertPausedLocation(dbg);
 });
--- a/devtools/client/webreplay/mochitest/browser_dbg_rr_stepping-04.js
+++ b/devtools/client/webreplay/mochitest/browser_dbg_rr_stepping-04.js
@@ -23,22 +23,20 @@ add_task(async function() {
   await reverseStepOverToLine(client, 20);
   await reverseStepOverToLine(client, 12);
 
   // After reverse-stepping out of the topmost frame we should rewind to the
   // last breakpoint hit.
   await reverseStepOverToLine(client, 21);
   await checkEvaluateInTopFrame(client, "number", 9);
 
-  await stepOverToLine(client, 21);
   await stepOverToLine(client, 22);
   await stepOverToLine(client, 23);
   await stepOverToLine(client, 13);
   await stepOverToLine(client, 17);
-  await stepOverToLine(client, 17);
   await stepOverToLine(client, 18);
 
   // After forward-stepping out of the topmost frame we should run forward to
   // the next breakpoint hit.
   await stepOverToLine(client, 21);
   await checkEvaluateInTopFrame(client, "number", 10);
 
   await client.removeBreakpoint(bp);
--- a/devtools/server/actors/replay/debugger.js
+++ b/devtools/server/actors/replay/debugger.js
@@ -732,16 +732,23 @@ ReplayDebuggerScript.prototype = {
     return this._dbg._sendRequest({ type, id: this._data.id, value });
   },
 
   getLineOffsets(line) { return this._forward("getLineOffsets", line); },
   getOffsetLocation(pc) { return this._forward("getOffsetLocation", pc); },
   getSuccessorOffsets(pc) { return this._forward("getSuccessorOffsets", pc); },
   getPredecessorOffsets(pc) { return this._forward("getPredecessorOffsets", pc); },
   getAllColumnOffsets() { return this._forward("getAllColumnOffsets"); },
+  getOffsetMetadata(pc) { return this._forward("getOffsetMetadata", pc); },
+  getPossibleBreakpoints(query) {
+    return this._forward("getPossibleBreakpoints", query);
+  },
+  getPossibleBreakpointOffsets(query) {
+    return this._forward("getPossibleBreakpointOffsets", query);
+  },
 
   setBreakpoint(offset, handler) {
     this._dbg._setBreakpoint(() => { handler.hit(this._dbg.getNewestFrame()); },
                              { kind: "Break", script: this._data.id, offset },
                              handler);
   },
 
   clearBreakpoint(handler) {
--- a/devtools/server/actors/replay/replay.js
+++ b/devtools/server/actors/replay/replay.js
@@ -778,16 +778,19 @@ const gRequestHandlers = {
     };
   },
 
   getLineOffsets: forwardToScript("getLineOffsets"),
   getOffsetLocation: forwardToScript("getOffsetLocation"),
   getSuccessorOffsets: forwardToScript("getSuccessorOffsets"),
   getPredecessorOffsets: forwardToScript("getPredecessorOffsets"),
   getAllColumnOffsets: forwardToScript("getAllColumnOffsets"),
+  getOffsetMetadata: forwardToScript("getOffsetMetadata"),
+  getPossibleBreakpoints: forwardToScript("getPossibleBreakpoints"),
+  getPossibleBreakpointOffsets: forwardToScript("getPossibleBreakpointOffsets"),
 
   frameEvaluate(request) {
     if (!RecordReplayControl.maybeDivergeFromRecording()) {
       return { throw: "Recording divergence in frameEvaluate" };
     }
 
     const frame = scriptFrameForIndex(request.index);
     const rv = frame.eval(request.text, request.options);
--- a/devtools/server/actors/source.js
+++ b/devtools/server/actors/source.js
@@ -263,17 +263,17 @@ const SourceActor = ActorClassWithSpec(s
 
   /**
    * Get all executable lines from the current source
    * @return Array - Executable lines of the current script
    */
   getExecutableLines: async function() {
     const offsetsLines = new Set();
     for (const s of this._findDebuggeeScripts()) {
-      for (const offset of s.getAllColumnOffsets()) {
+      for (const offset of s.getPossibleBreakpoints()) {
         offsetsLines.add(offset.lineNumber);
       }
     }
 
     const lines = [...offsetsLines];
     lines.sort((a, b) => {
       return a - b;
     });
@@ -302,17 +302,17 @@ const SourceActor = ActorClassWithSpec(s
       }
       scripts = Array.from(found);
     } else {
       scripts = this._findDebuggeeScripts();
     }
 
     const positions = [];
     for (const script of scripts) {
-      const offsets = script.getAllColumnOffsets();
+      const offsets = script.getPossibleBreakpoints();
       for (const { lineNumber, columnNumber } of offsets) {
         if (
           lineNumber < startLine ||
           (lineNumber === startLine && columnNumber < startColumn) ||
           lineNumber > endLine ||
           (lineNumber === endLine && columnNumber > endColumn)
         ) {
           continue;
@@ -325,23 +325,17 @@ const SourceActor = ActorClassWithSpec(s
       }
     }
 
     return positions
       // Sort the items by location.
       .sort((a, b) => {
         const lineDiff = a.line - b.line;
         return lineDiff === 0 ? a.column - b.column : lineDiff;
-      })
-      // Filter out duplicate locations since they are useless in this context.
-      .filter((item, i, arr) => (
-        i === 0 ||
-        item.line !== arr[i - 1].line ||
-        item.column !== arr[i - 1].column
-      ));
+      });
   },
 
   getBreakpointPositionsCompressed(query) {
     const items = this.getBreakpointPositions(query);
     const compressed = {};
     for (const { line, column } of items) {
       if (!compressed[line]) {
         compressed[line] = [];
@@ -444,32 +438,32 @@ const SourceActor = ActorClassWithSpec(s
     // Find all scripts that match the given source actor and line
     // number.
     let scripts = this._findDebuggeeScripts({ line });
     scripts = scripts.filter((script) => !actor.hasScript(script));
 
     // Find all entry points that correspond to the given location.
     const entryPoints = [];
     if (column === undefined) {
-      // This is a line breakpoint, so we are interested in all offsets
-      // that correspond to the given line number.
+      // This is a line breakpoint, so we add a breakpoint on the first
+      // breakpoint on the line.
       for (const script of scripts) {
-        const offsets = script.getLineOffsets(line);
+        const offsets = script.getPossibleBreakpointOffsets({ line });
         if (offsets.length > 0) {
-          entryPoints.push({ script, offsets });
+          entryPoints.push({ script, offsets: [offsets[0]] });
+          break;
         }
       }
     } else {
       // Compute columnToOffsetMaps for each script so that we can
       // find matching entrypoints for the column breakpoint.
       const columnToOffsetMaps = scripts.map(script =>
         [
           script,
-          script.getAllColumnOffsets()
-            .filter(({ lineNumber }) => lineNumber === line),
+          script.getPossibleBreakpoints({ line }),
         ]
       );
 
       // This is a column breakpoint, so we are interested in all column
       // offsets that correspond to the given line *and* column number.
       for (const [script, columnToOffsetMap] of columnToOffsetMaps) {
         for (const { columnNumber, offset } of columnToOffsetMap) {
           if (columnNumber >= column && columnNumber <= column + 1) {
--- a/devtools/server/actors/thread.js
+++ b/devtools/server/actors/thread.js
@@ -602,61 +602,47 @@ const ThreadActor = ActorClassWithSpec(t
 
     return result;
   },
 
   // Return whether reaching a script offset should be considered a distinct
   // "step" from another location.
   _intraFrameLocationIsStepTarget: function(startLocation, script, offset) {
     // Only allow stepping stops at entry points for the line.
-    if (!script.getOffsetLocation(offset).isEntryPoint) {
+    if (!script.getOffsetMetadata(offset).isBreakpoint) {
       return false;
     }
 
-    // Cases when we have executed enough within a frame to consider a "step"
-    // to have occured:
-    //
-    // 1. We change URLs (can happen without changing frames thanks to
-    //    source mapping).
-    // 2. The source has pause points and we change locations.
-    // 3. The source does not have pause points and we change lines.
-
     const generatedLocation = this.sources.getScriptOffsetLocation(script, offset);
 
-    // Case 1.
     if (startLocation.generatedUrl !== generatedLocation.generatedUrl) {
       return true;
     }
 
-    const pausePoints = generatedLocation.generatedSourceActor.pausePoints;
+    // TODO(logan): When we remove points points, this can be removed too as
+    // we assert that we're at a different frame offset from the last time
+    // we paused.
     const lineChanged = startLocation.generatedLine !== generatedLocation.generatedLine;
     const columnChanged =
       startLocation.generatedColumn !== generatedLocation.generatedColumn;
-
-    if (!pausePoints) {
-      // Case 3.
-      return lineChanged;
-    }
-
-    // Case 2.
     if (!lineChanged && !columnChanged) {
       return false;
     }
 
     // When pause points are specified for the source,
     // we should pause when we are at a stepOver pause point
-    const pausePoint = findPausePointForLocation(pausePoints, generatedLocation);
+    const pausePoints = generatedLocation.generatedSourceActor.pausePoints;
+    const pausePoint = pausePoints &&
+      findPausePointForLocation(pausePoints, generatedLocation);
 
     if (pausePoint) {
       return pausePoint.step;
     }
 
-    // NOTE: if we do not find a pause point we want to
-    // fall back on the old behavior (Case 3)
-    return lineChanged;
+    return script.getOffsetMetadata(offset).isStepStart;
   },
 
   _makeOnStep: function({ thread, pauseAndRespond, startFrame,
                           startLocation, steppingType, completion, rewinding }) {
     // Breaking in place: we should always pause.
     if (steppingType === "break") {
       return () => pauseAndRespond(this);
     }
--- a/devtools/server/actors/utils/TabSources.js
+++ b/devtools/server/actors/utils/TabSources.js
@@ -268,17 +268,17 @@ TabSources.prototype = {
    * @param Debugger.Script script
    *        The script associated with the offset.
    * @param Number offset
    *        Offset within the script of the location.
    * @returns Object
    *          Returns an object of the form { source, line, column }
    */
   getScriptOffsetLocation: function(script, offset) {
-    const {lineNumber, columnNumber} = script.getOffsetLocation(offset);
+    const {lineNumber, columnNumber} = script.getOffsetMetadata(offset);
     return new GeneratedLocation(
       this.createSourceActor(script.source),
       lineNumber,
       columnNumber
     );
   },
 
   /**
--- a/devtools/server/tests/unit/test_breakpoint-10.js
+++ b/devtools/server/tests/unit/test_breakpoint-10.js
@@ -11,36 +11,50 @@
 
 add_task(threadClientTest(({ threadClient, debuggee }) => {
   return new Promise(resolve => {
     threadClient.addOneTimeListener("paused", async function(event, packet) {
       const source = await getSourceById(
         threadClient,
         packet.frame.where.actor
       );
-      const location = { sourceUrl: source.url, line: debuggee.line0 + 3 };
+      const location = {
+        sourceUrl: source.url,
+        line: debuggee.line0 + 3,
+        column: 5,
+      };
 
       threadClient.setBreakpoint(location, {});
 
       threadClient.addOneTimeListener("paused", function(event, packet) {
         // Check the return value.
         Assert.equal(packet.type, "paused");
         Assert.equal(packet.why.type, "breakpoint");
         // Check that the breakpoint worked.
         Assert.equal(debuggee.i, 0);
 
+        // Remove the breakpoint.
+        threadClient.removeBreakpoint(location);
+
+        const location2 = {
+          sourceUrl: source.url,
+          line: debuggee.line0 + 3,
+          column: 12,
+        };
+        threadClient.setBreakpoint(location2, {});
+
         threadClient.addOneTimeListener("paused", function(event, packet) {
           // Check the return value.
           Assert.equal(packet.type, "paused");
           Assert.equal(packet.why.type, "breakpoint");
           // Check that the breakpoint worked.
           Assert.equal(debuggee.i, 1);
 
           // Remove the breakpoint.
-          threadClient.removeBreakpoint(location);
+          threadClient.removeBreakpoint(location2);
 
           threadClient.resume(resolve);
         });
 
         // Continue until the breakpoint is hit again.
         threadClient.resume();
       });
 
--- a/devtools/server/tests/unit/test_breakpoint-11.js
+++ b/devtools/server/tests/unit/test_breakpoint-11.js
@@ -11,37 +11,52 @@
 
 add_task(threadClientTest(({ threadClient, debuggee }) => {
   return new Promise(resolve => {
     threadClient.addOneTimeListener("paused", async function(event, packet) {
       const source = await getSourceById(
         threadClient,
         packet.frame.where.actor
       );
-      const location = { sourceUrl: source.url, line: debuggee.line0 + 2 };
+      const location = {
+        sourceUrl: source.url,
+        line: debuggee.line0 + 2,
+        column: 8,
+      };
 
       threadClient.setBreakpoint(location, {});
 
       threadClient.addOneTimeListener("paused", function(event, packet) {
         // Check the return value.
         Assert.equal(packet.type, "paused");
         Assert.equal(packet.why.type, "breakpoint");
         // Check that the breakpoint worked.
         Assert.equal(debuggee.a, undefined);
 
+        // Remove the breakpoint.
+        threadClient.removeBreakpoint(location);
+
+        const location2 = {
+          sourceUrl: source.url,
+          line: debuggee.line0 + 2,
+          column: 32,
+        };
+
+        threadClient.setBreakpoint(location2, {});
+
         threadClient.addOneTimeListener("paused", function(event, packet) {
           // Check the return value.
           Assert.equal(packet.type, "paused");
           Assert.equal(packet.why.type, "breakpoint");
           // Check that the breakpoint worked.
           Assert.equal(debuggee.a.b, 1);
           Assert.equal(debuggee.res, undefined);
 
           // Remove the breakpoint.
-          threadClient.removeBreakpoint(location);
+          threadClient.removeBreakpoint(location2);
 
           threadClient.resume(resolve);
         });
 
         // Continue until the breakpoint is hit again.
         threadClient.resume();
       });
 
--- a/devtools/server/tests/unit/test_breakpoint-13.js
+++ b/devtools/server/tests/unit/test_breakpoint-13.js
@@ -28,22 +28,16 @@ add_task(threadClientTest(({ threadClien
         },
         function(packet) {
           // Entered the foo function call frame.
           Assert.equal(packet.frame.where.line, location.line);
           Assert.notEqual(packet.why.type, "breakpoint");
           Assert.equal(packet.why.type, "resumeLimit");
         },
         function(packet) {
-          // At the end of the foo function call frame.
-          Assert.equal(packet.frame.where.line, debuggee.line0 + 3);
-          Assert.notEqual(packet.why.type, "breakpoint");
-          Assert.equal(packet.why.type, "resumeLimit");
-        },
-        function(packet) {
           // Check that the breakpoint wasn't the reason for this pause, but
           // that the frame is about to be popped while stepping.
           Assert.equal(packet.frame.where.line, debuggee.line0 + 3);
           Assert.notEqual(packet.why.type, "breakpoint");
           Assert.equal(packet.why.type, "resumeLimit");
           Assert.equal(packet.why.frameFinished.return.type, "undefined");
         },
         function(packet) {
--- a/devtools/server/tests/unit/test_breakpoint-14.js
+++ b/devtools/server/tests/unit/test_breakpoint-14.js
@@ -27,21 +27,16 @@ add_task(threadClientTest(({ threadClien
         },
         function(packet) {
           // Reached the breakpoint.
           Assert.equal(packet.frame.where.line, location.line);
           Assert.equal(packet.why.type, "breakpoint");
           Assert.notEqual(packet.why.type, "resumeLimit");
         },
         function(packet) {
-          // Stepped to the closing brace of the function.
-          Assert.equal(packet.frame.where.line, debuggee.line0 + 3);
-          Assert.equal(packet.why.type, "resumeLimit");
-        },
-        function(packet) {
           // The frame is about to be popped while stepping.
           Assert.equal(packet.frame.where.line, debuggee.line0 + 3);
           Assert.notEqual(packet.why.type, "breakpoint");
           Assert.equal(packet.why.type, "resumeLimit");
           Assert.equal(packet.why.frameFinished.return.type, "undefined");
         },
         function(packet) {
           // Check that the debugger statement wasn't the reason for this pause.
--- a/devtools/server/tests/unit/test_setBreakpoint-on-line-with-multiple-offsets.js
+++ b/devtools/server/tests/unit/test_setBreakpoint-on-line-with-multiple-offsets.js
@@ -20,25 +20,28 @@ add_task(threadClientTest(async ({ threa
   Assert.equal(why.actors.length, 1);
   let frame = packet.frame;
   let where = frame.where;
   Assert.equal(where.actor, source.actor);
   Assert.equal(where.line, location.line);
   let variables = frame.environment.bindings.variables;
   Assert.equal(variables.i.value.type, "undefined");
 
+  const location2 = { sourceUrl: sourceClient.url, line: 7 };
+  setBreakpoint(threadClient, location2);
+
   packet = await executeOnNextTickAndWaitForPause(
     () => resume(threadClient),
     client
   );
   Assert.equal(packet.type, "paused");
   why = packet.why;
   Assert.equal(why.type, "breakpoint");
   Assert.equal(why.actors.length, 1);
   frame = packet.frame;
   where = frame.where;
   Assert.equal(where.actor, source.actor);
-  Assert.equal(where.line, location.line);
+  Assert.equal(where.line, location2.line);
   variables = frame.environment.bindings.variables;
-  Assert.equal(variables.i.value, 0);
+  Assert.equal(variables.i.value, 1);
 
   await resume(threadClient);
 }, { doNotRunWorker: true }));
--- a/devtools/server/tests/unit/test_source-02.js
+++ b/devtools/server/tests/unit/test_source-02.js
@@ -32,17 +32,22 @@ function run_test() {
                              gThreadClient = threadClient;
                              test_source();
                            });
   });
   do_test_pending();
 }
 
 const SOURCE_URL = "http://example.com/foobar.js";
-const SOURCE_CONTENT = "stopMe()";
+const SOURCE_CONTENT = `
+  stopMe();
+  for(var i = 0; i < 2; i++) {
+    debugger;
+  }
+`;
 
 function test_source() {
   DebuggerServer.LONG_STRING_LENGTH = 200;
 
   gThreadClient.addOneTimeListener("paused", function(event, packet) {
     gThreadClient.getSources(async function(response) {
       Assert.ok(!!response);
       Assert.ok(!!response.sources);
@@ -55,31 +60,46 @@ function test_source() {
 
       const sourceClient = gThreadClient.source(source);
       response = await sourceClient.getBreakpointPositions();
       Assert.ok(!!response);
       Assert.ok(!!response.positions);
       Assert.deepEqual(
         response.positions,
         [{
-          line: 1,
-          column: 0,
+          line: 2,
+          column: 2,
+        }, {
+          line: 3,
+          column: 14,
+        }, {
+          line: 3,
+          column: 17,
         }, {
-          line: 1,
-          column: 8,
+          line: 3,
+          column: 24,
+        }, {
+          line: 4,
+          column: 4,
+        }, {
+          line: 6,
+          column: 0,
         }]
       );
 
       response = await sourceClient.getBreakpointPositionsCompressed();
       Assert.ok(!!response);
       Assert.ok(!!response.positions);
       Assert.deepEqual(
         response.positions,
         {
-          1: [0, 8],
+          2: [2],
+          3: [14, 17, 24],
+          4: [4],
+          6: [0],
         }
       );
 
       await gThreadClient.resume();
       finishClient(gClient);
     });
   });
 
--- a/devtools/server/tests/unit/test_stepping-01.js
+++ b/devtools/server/tests/unit/test_stepping-01.js
@@ -56,17 +56,17 @@ async function stepOutOfA(dbg, func, exp
 
   deepEqual(getPauseLocation(packet), expectedLocation, `step out location in ${func}`);
 
   await resume(dbg.threadClient);
 }
 
 async function stepOverInA(dbg, func, expectedLocation) {
   await invokeAndPause(dbg, `${func}()`);
-  await steps(dbg, [stepOver, stepIn, stepOver]);
+  await steps(dbg, [stepOver, stepIn]);
 
   let packet = await step(dbg, stepOver);
   dump(`>> stepOverInA hi\n`);
   equal(getPauseReturn(packet).ownPropertyLength, 1, "a() is returning obj");
 
   packet = await step(dbg, stepOver);
   deepEqual(getPauseLocation(packet), expectedLocation, `step out location in ${func}`);
 
@@ -77,15 +77,15 @@ async function testStep(dbg, func, expec
   await stepOverInA(dbg, func, expectedLocation);
   await stepOutOfA(dbg, func, expectedLocation);
 }
 
 function run_test() {
   return (async function() {
     const dbg = await setupTestFromUrl("stepping.js");
 
-    await testStep(dbg, "arithmetic", {line: 16, column: 8});
-    await testStep(dbg, "composition", {line: 21, column: 2});
-    await testStep(dbg, "chaining", {line: 26, column: 6});
+    await testStep(dbg, "arithmetic", {line: 17, column: 0});
+    await testStep(dbg, "composition", {line: 22, column: 0});
+    await testStep(dbg, "chaining", {line: 27, column: 0});
 
     await testFinish(dbg);
   })();
 }
--- a/devtools/server/tests/unit/test_stepping-06.js
+++ b/devtools/server/tests/unit/test_stepping-06.js
@@ -44,17 +44,17 @@ async function testFinish({threadClient,
   do_test_finished();
 }
 
 async function testRet(dbg) {
   let packet;
 
   info(`1. Test returning from doRet via stepping over`);
   await invokeAndPause(dbg, `doRet()`);
-  await steps(dbg, [stepOver, stepIn, stepOver]);
+  await steps(dbg, [stepOver, stepIn]);
   packet = await step(dbg, stepOver);
 
   deepEqual(
     getPauseLocation(packet),
     {line: 6, column: 0},
     `completion location in doRet`
   );
   deepEqual(
@@ -84,17 +84,17 @@ async function testRet(dbg) {
   await resume(dbg.threadClient);
 }
 
 async function testThrow(dbg) {
   let packet;
 
   info(`3. Test leaving from doThrow via stepping over`);
   await invokeAndPause(dbg, `doThrow()`);
-  await steps(dbg, [stepOver, stepOver, stepIn]);
+  await steps(dbg, [stepOver, stepIn]);
   packet = await step(dbg, stepOver);
 
   deepEqual(
     getPauseLocation(packet),
     {line: 9, column: 8},
     `completion location in doThrow`
   );
 
@@ -108,34 +108,29 @@ async function testThrow(dbg) {
     "yo",
     `completion value preview`
   );
 
   await resume(dbg.threadClient);
 
   info(`4. Test leaving from doThrow via stepping out`);
   await invokeAndPause(dbg, `doThrow()`);
-  await steps(dbg, [stepOver, stepOver, stepIn]);
+  await steps(dbg, [stepOver, stepIn]);
 
   packet = await step(dbg, stepOut);
   deepEqual(
     getPauseLocation(packet),
-    {line: 22, column: 14},
-    `completion location in doThrow`
+    {line: 24, column: 0},
+    `stepOut location in doThrow`
   );
 
   deepEqual(
-    getFrameFinished(packet).throw.class,
-    "Error",
-    `completion completion value class`
-  );
-  deepEqual(
-    getFrameFinished(packet).throw.preview.message,
-    "yo",
-    `completion completion value preview`
+    getFrameFinished(packet),
+    {return: {type: "undefined"}},
+    `completion type`
   );
   await resume(dbg.threadClient);
 }
 
 function run_test() {
   return (async function() {
     const dbg = await setupTestFromUrl("completions.js");
 
--- a/devtools/server/tests/unit/test_stepping-07.js
+++ b/devtools/server/tests/unit/test_stepping-07.js
@@ -24,27 +24,24 @@ add_task(threadClientTest(async ({ threa
   // step at the end of this function to get the frameFinished.
   // See bug 923975.
   //
   // ok(step2.why.frameFinished, "This should be the implicit function return");
 
   dumpn("Continuing and waiting for second debugger statement");
   const dbgStmt2 = await resumeAndWaitForPause(client, threadClient);
   equal(dbgStmt2.frame.where.line, 12,
-        "Should be at debugger statement on line 3");
+        "Should be at debugger statement on line 12");
 
   dumpn("Testing stepping with explicit return");
   const step3 = await stepOver(client, threadClient);
   equal(step3.frame.where.line, 13, "Should step to line 13");
   const step4 = await stepOver(client, threadClient);
   equal(step4.frame.where.line, 15, "Should step out of the function from line 15");
-  // This step is a bit funny, see bug 1013219 for details.
-  const step5 = await stepOver(client, threadClient);
-  equal(step5.frame.where.line, 15, "Should step out of the function from line 15");
-  ok(step5.why.frameFinished, "This should be the explicit function return");
+  ok(step4.why.frameFinished, "This should be the explicit function return");
 }));
 
 function evaluateTestCode(debuggee) {
   /* eslint-disable */
   Cu.evalInSandbox(
     `                                   //  1
     function implicitReturn() {         //  2
       debugger;                         //  3
--- a/devtools/server/tests/unit/test_stepping-08.js
+++ b/devtools/server/tests/unit/test_stepping-08.js
@@ -16,23 +16,27 @@ add_task(threadClientTest(async ({ threa
   dumpn("Setting breakpoint in innerFunction");
   const source = await getSourceById(
     threadClient,
     dbgStmt.frame.where.actor
   );
   await threadClient.setBreakpoint({ sourceUrl: source.url, line: 7 }, {});
 
   dumpn("Step in to innerFunction");
-  const step1 = await stepIn(client, threadClient);
-  equal(step1.frame.where.line, 7);
+  const step1 = await stepOver(client, threadClient);
+  equal(step1.frame.where.line, 3);
+
+  dumpn("Step in to innerFunction");
+  const step2 = await stepIn(client, threadClient);
+  equal(step2.frame.where.line, 7);
 
   dumpn("Step out of innerFunction");
-  const step2 = await stepOut(client, threadClient);
+  const step3 = await stepOut(client, threadClient);
   // The bug was that we'd stop again at the breakpoint on line 7.
-  equal(step2.frame.where.line, 4);
+  equal(step3.frame.where.line, 4);
 }));
 
 function evaluateTestCode(debuggee) {
   /* eslint-disable */
   Cu.evalInSandbox(
     `                                   //  1
     function outerFunction() {          //  2
       debugger; innerFunction();        //  3
--- a/devtools/server/tests/unit/test_stepping-09.js
+++ b/devtools/server/tests/unit/test_stepping-09.js
@@ -13,17 +13,17 @@ add_task(threadClientTest(async ({ threa
   const dbgStmt = await executeOnNextTickAndWaitForPause(
     () => evaluateTestCode(debuggee), client);
   equal(dbgStmt.frame.where.line, 2, "Should be at debugger statement on line 2");
 
   dumpn("Step out of inner and into outer");
   const step2 = await stepOut(client, threadClient);
   // The bug was that we'd step right past the end of the function and never pause.
   equal(step2.frame.where.line, 2);
-  equal(step2.why.frameFinished.return, 42);
+  deepEqual(step2.why.frameFinished.return, { type: "undefined"});
 }));
 
 function evaluateTestCode(debuggee) {
   // By placing the inner and outer on the same line, this triggers the server's
   // logic to skip steps for these functions, meaning that onPop is the only
   // thing that will cause it to pop.
   Cu.evalInSandbox(
     `
deleted file mode 100644
--- a/devtools/server/tests/unit/test_stepping-with-pause-points.js
+++ /dev/null
@@ -1,76 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-/* eslint-disable no-shadow, max-nested-callbacks */
-
-"use strict";
-
-/**
- * Check basic step-over functionality with pause points
- * for the first statement and end of the last statement.
- */
-
-add_task(threadClientTest(async ({ threadClient, debuggee, client }) => {
-  dumpn("Evaluating test code and waiting for first debugger statement");
-  const dbgStmt = await executeOnNextTickAndWaitForPause(
-    () => evaluateTestCode(debuggee), client);
-  equal(dbgStmt.frame.where.line, 2, "Should be at debugger statement on line 2");
-  equal(debuggee.a, undefined);
-  equal(debuggee.b, undefined);
-
-  const source = await getSource(threadClient, "test_stepping-01-test-code.js");
-
-  // Add pause points for the first and end of the last statement.
-  // Note: we intentionally ignore the second statement.
-  source.setPausePoints([{
-    location: {line: 3, column: 8},
-    types: {breakpoint: true, stepOver: true},
-  },
-  {
-    location: {line: 4, column: 14},
-    types: {breakpoint: true, stepOver: true},
-  }]);
-
-  dumpn("Step Over to line 3");
-  const step1 = await stepOver(client, threadClient);
-  equal(step1.type, "paused");
-  equal(step1.why.type, "resumeLimit");
-  equal(step1.frame.where.line, 3);
-  equal(step1.frame.where.column, 0);
-
-  equal(debuggee.a, undefined);
-  equal(debuggee.b, undefined);
-
-  dumpn("Step Over to line 4");
-  const step2 = await stepOver(client, threadClient);
-  equal(step2.type, "paused");
-  equal(step2.why.type, "resumeLimit");
-  equal(step2.frame.where.line, 4);
-  equal(step2.frame.where.column, 0);
-
-  equal(debuggee.a, 1);
-  equal(debuggee.b, undefined);
-
-  dumpn("Step Over to the end of line 4");
-  const step3 = await stepOver(client, threadClient);
-  equal(step3.type, "paused");
-  equal(step3.why.type, "resumeLimit");
-  equal(step3.frame.where.line, 4);
-  equal(step3.frame.where.column, 14);
-  equal(debuggee.a, 1);
-  equal(debuggee.b, 2);
-}));
-
-function evaluateTestCode(debuggee) {
-  /* eslint-disable */
-  Cu.evalInSandbox(
-    `                                   // 1
-    debugger;                           // 2
-    var a = 1;                          // 3
-    var b = 2;`,                        // 4
-    debuggee,
-    "1.8",
-    "test_stepping-01-test-code.js",
-    1
-  );
-  /* eslint-disable */
-}
--- a/devtools/server/tests/unit/test_stepping-with-skip-breakpoints.js
+++ b/devtools/server/tests/unit/test_stepping-with-skip-breakpoints.js
@@ -30,27 +30,27 @@ add_task(threadClientTest(async ({ threa
     types: {breakpoint: true, stepOver: true},
   }]);
 
   dumpn("Step Over to line 3");
   const step1 = await stepOver(client, threadClient);
   equal(step1.type, "paused");
   equal(step1.why.type, "resumeLimit");
   equal(step1.frame.where.line, 3);
-  equal(step1.frame.where.column, 0);
+  equal(step1.frame.where.column, 12);
 
   equal(debuggee.a, undefined);
   equal(debuggee.b, undefined);
 
   dumpn("Step Over to line 4");
   const step2 = await stepOver(client, threadClient);
   equal(step2.type, "paused");
   equal(step2.why.type, "resumeLimit");
   equal(step2.frame.where.line, 4);
-  equal(step2.frame.where.column, 0);
+  equal(step2.frame.where.column, 12);
 
   equal(debuggee.a, 1);
   equal(debuggee.b, undefined);
 
   dumpn("Step Over to the end of line 4");
   const step3 = await stepOver(client, threadClient);
   equal(step3.type, "paused");
   equal(step3.why.type, "resumeLimit");
--- a/devtools/server/tests/unit/xpcshell.ini
+++ b/devtools/server/tests/unit/xpcshell.ini
@@ -187,17 +187,16 @@ skip-if = true # breakpoint sliding is n
 [test_stepping-02.js]
 [test_stepping-03.js]
 [test_stepping-04.js]
 [test_stepping-05.js]
 [test_stepping-06.js]
 [test_stepping-07.js]
 [test_stepping-08.js]
 [test_stepping-09.js]
-[test_stepping-with-pause-points.js]
 [test_stepping-with-skip-breakpoints.js]
 [test_framebindings-01.js]
 [test_framebindings-02.js]
 [test_framebindings-03.js]
 [test_framebindings-04.js]
 [test_framebindings-05.js]
 [test_framebindings-06.js]
 [test_framebindings-07.js]