Bug 1534328 - Update breakpoint text snippets when files load. r=jlast
authorLogan Smyth <loganfsmyth@gmail.com>
Tue, 26 Mar 2019 19:28:15 +0000
changeset 466180 b7663bdb3f6e5c01f10c4af4d96e471811860917
parent 466179 f2c8f1414bd4fbb23350aef94c7370cdf016c624
child 466181 0bd696ed34eace2844a540509a5022dc57b48508
push id35762
push usercsabou@mozilla.com
push dateWed, 27 Mar 2019 04:44:00 +0000
treeherdermozilla-central@bc572aee49b6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjlast
bugs1534328
milestone68.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 1534328 - Update breakpoint text snippets when files load. r=jlast Differential Revision: https://phabricator.services.mozilla.com/D24827
devtools/client/debugger/new/src/actions/sources/loadSourceText.js
devtools/client/debugger/new/src/actions/sources/tests/loadSource.spec.js
devtools/client/debugger/new/src/actions/tests/helpers/threadClient.js
devtools/client/debugger/new/src/actions/types/BreakpointAction.js
devtools/client/debugger/new/src/reducers/breakpoints.js
--- a/devtools/client/debugger/new/src/actions/sources/loadSourceText.js
+++ b/devtools/client/debugger/new/src/actions/sources/loadSourceText.js
@@ -83,16 +83,21 @@ async function loadSourceTextPromise(
     [PROMISE]: loadSource(getState(), source, { sourceMaps, client })
   });
 
   const newSource = getSource(getState(), source.id);
   if (!newSource) {
     return;
   }
 
+  dispatch({
+    type: "UPDATE_BREAKPOINT_TEXT",
+    source: newSource
+  });
+
   if (!newSource.isWasm && isLoaded(newSource)) {
     parser.setSource(newSource);
     dispatch(setBreakpointPositions(newSource.id));
   }
 
   return newSource;
 }
 
--- a/devtools/client/debugger/new/src/actions/sources/tests/loadSource.spec.js
+++ b/devtools/client/debugger/new/src/actions/sources/tests/loadSource.spec.js
@@ -4,19 +4,25 @@
 
 // @flow
 
 import {
   actions,
   selectors,
   watchForState,
   createStore,
+  makeOriginalSource,
   makeSource
 } from "../../../utils/test-head";
-import { sourceThreadClient } from "../../tests/helpers/threadClient.js";
+import {
+  createSource,
+  sourceThreadClient
+} from "../../tests/helpers/threadClient.js";
+import { addBreakpoint } from "../../breakpoints/addBreakpoint";
+import { getBreakpointsList } from "../../../selectors";
 
 describe("loadSourceText", () => {
   it("should load source text", async () => {
     const store = createStore(sourceThreadClient);
     const { dispatch, getState } = store;
 
     const foo1Source = makeSource("foo1");
     await dispatch(actions.newSource(foo1Source));
@@ -34,16 +40,74 @@ describe("loadSourceText", () => {
     const foo2Source = selectors.getSource(getState(), "foo2");
 
     if (!foo2Source || typeof foo2Source.text != "string") {
       throw new Error("bad fooSource");
     }
     expect(foo2Source.text.indexOf("return foo2")).not.toBe(-1);
   });
 
+  it("should update breakpoint text when a source loads", async () => {
+    const fooOrigSource = makeOriginalSource("fooGen");
+    const fooGenSource = makeSource("fooGen");
+
+    const fooOrigContent = createSource("fooOrig", "var fooOrig = 42;");
+    const fooGenContent = createSource("fooGen", "var fooGen = 42;");
+
+    const store = createStore(
+      {
+        ...sourceThreadClient,
+        sourceContents: async () => fooGenContent,
+        getBreakpointPositions: async () => ({ "1": [0] })
+      },
+      {},
+      {
+        getGeneratedRangesForOriginal: async () => [
+          { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } }
+        ],
+        getOriginalLocations: async items =>
+          items.map(item => ({
+            ...item,
+            sourceId: fooOrigSource.id
+          })),
+        getOriginalSourceText: async s => ({
+          text: fooOrigContent.source,
+          contentType: fooOrigContent.contentType
+        })
+      }
+    );
+    const { dispatch, getState } = store;
+
+    await dispatch(actions.newSource(fooOrigSource));
+    await dispatch(actions.newSource(fooGenSource));
+
+    const breakpoint = await dispatch(
+      addBreakpoint({
+        sourceId: fooOrigSource.id,
+        line: 1,
+        column: 0
+      })
+    );
+
+    expect(breakpoint.text).toBe("");
+    expect(breakpoint.originalText).toBe("");
+
+    await dispatch(actions.loadSourceText(fooOrigSource));
+
+    const breakpoint1 = getBreakpointsList(getState())[0];
+    expect(breakpoint1.text).toBe("");
+    expect(breakpoint1.originalText).toBe("var fooOrig = 42;");
+
+    await dispatch(actions.loadSourceText(fooGenSource));
+
+    const breakpoint2 = getBreakpointsList(getState())[0];
+    expect(breakpoint2.text).toBe("var fooGen = 42;");
+    expect(breakpoint2.originalText).toBe("var fooOrig = 42;");
+  });
+
   it("loads two sources w/ one request", async () => {
     let resolve;
     let count = 0;
     const { dispatch, getState } = createStore({
       sourceContents: () =>
         new Promise(r => {
           count++;
           resolve = r;
--- a/devtools/client/debugger/new/src/actions/tests/helpers/threadClient.js
+++ b/devtools/client/debugger/new/src/actions/tests/helpers/threadClient.js
@@ -5,20 +5,20 @@
 // @flow
 
 import type {
   SourceActor,
   SourceActorLocation,
   BreakpointOptions
 } from "../../../types";
 
-function createSource(name) {
+export function createSource(name: string, code?: string) {
   name = name.replace(/\..*$/, "");
   return {
-    source: `function ${name}() {\n  return ${name} \n}`,
+    source: code || `function ${name}() {\n  return ${name} \n}`,
     contentType: "text/javascript"
   };
 }
 
 const sources = [
   "a",
   "b",
   "foo",
@@ -69,13 +69,14 @@ export const sourceThreadClient = {
     return new Promise((resolve, reject) => {
       if (sources.includes(source)) {
         resolve(createSource(source));
       }
 
       reject(`unknown source: ${source}`);
     });
   },
+  setBreakpoint: async () => {},
   threadClient: async () => {},
   getFrameScopes: async () => {},
   evaluateExpressions: async () => {},
   getBreakpointPositions: async () => ({})
 };
--- a/devtools/client/debugger/new/src/actions/types/BreakpointAction.js
+++ b/devtools/client/debugger/new/src/actions/types/BreakpointAction.js
@@ -92,9 +92,13 @@ export type BreakpointAction =
   | {|
       +type: "REMAP_BREAKPOINTS",
       +breakpoints: Breakpoint[]
     |}
   | {|
       type: "ADD_BREAKPOINT_POSITIONS",
       positions: BreakpointPositions,
       source: Source
+    |}
+  | {|
+      +type: "UPDATE_BREAKPOINT_TEXT",
+      +source: Source
     |};
--- a/devtools/client/debugger/new/src/reducers/breakpoints.js
+++ b/devtools/client/debugger/new/src/reducers/breakpoints.js
@@ -9,25 +9,27 @@
  * @module reducers/breakpoints
  */
 
 import { isGeneratedId } from "devtools-source-map";
 import { isEqual } from "lodash";
 
 import { makeBreakpointId, findPosition } from "../utils/breakpoint";
 import { findEmptyLines } from "../utils/empty-lines";
+import { getTextAtPosition } from "../utils/source";
 
 // eslint-disable-next-line max-len
 import { getBreakpointsList as getBreakpointsListSelector } from "../selectors/breakpoints";
 
 import type {
   XHRBreakpoint,
   Breakpoint,
   BreakpointId,
   MappedLocation,
+  Source,
   SourceLocation,
   BreakpointPositions
 } from "../types";
 import type { Action, DonePromiseAction } from "../actions/types";
 
 export type BreakpointsMap = { [BreakpointId]: Breakpoint };
 export type XHRBreakpointsList = $ReadOnlyArray<XHRBreakpoint>;
 export type BreakpointPositionsMap = { [string]: BreakpointPositions };
@@ -52,16 +54,20 @@ export function initialBreakpointsState(
   };
 }
 
 function update(
   state: BreakpointsState = initialBreakpointsState(),
   action: Action
 ): BreakpointsState {
   switch (action.type) {
+    case "UPDATE_BREAKPOINT_TEXT": {
+      return updateBreakpointText(state, action.source);
+    }
+
     case "ADD_BREAKPOINT": {
       return addBreakpoint(state, action);
     }
 
     case "SYNC_BREAKPOINT": {
       return syncBreakpoint(state, action);
     }
 
@@ -133,16 +139,63 @@ function update(
         }
       };
     }
   }
 
   return state;
 }
 
+function updateBreakpointText(
+  state: BreakpointsState,
+  source: Source
+): BreakpointsState {
+  const updates = [];
+  for (const id of Object.keys(state.breakpoints)) {
+    const breakpoint = state.breakpoints[id];
+    const { location, generatedLocation } = breakpoint;
+    let { text, originalText } = breakpoint;
+    let needsUpdate = false;
+
+    if (location.sourceId === source.id) {
+      const result = getTextAtPosition(source, location);
+      if (result !== originalText) {
+        originalText = result;
+        needsUpdate = true;
+      }
+    }
+    if (generatedLocation.sourceId === source.id) {
+      const result = getTextAtPosition(source, generatedLocation);
+      if (result !== text) {
+        text = result;
+        needsUpdate = true;
+      }
+    }
+
+    if (needsUpdate) {
+      updates.push({ id, text, originalText });
+    }
+  }
+
+  if (updates.length > 0) {
+    const { ...breakpoints } = state.breakpoints;
+
+    for (const { id, text, originalText } of updates) {
+      breakpoints[id] = { ...breakpoints[id], text, originalText };
+    }
+
+    state = {
+      ...state,
+      breakpoints
+    };
+  }
+
+  return state;
+}
+
 function addXHRBreakpoint(state, action) {
   const { xhrBreakpoints } = state;
   const { breakpoint } = action;
   const { path, method } = breakpoint;
 
   const existingBreakpointIndex = state.xhrBreakpoints.findIndex(
     bp => bp.path === path && bp.method === method
   );