summaryrefslogtreecommitdiffstats
path: root/devtools/client/debugger/src/reducers/pending-breakpoints.js
blob: a0d51e5693559ba0383d30df5f45e1e671432b60 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/* This Source Code Form is subject to the terms of the Mozilla Public
 * 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/>. */

/**
 * Pending breakpoints reducer.
 *
 * Pending breakpoints are a more lightweight version compared to regular breakpoints objects.
 * They are meant to be persisted across Firefox restarts and stored into async-storage.
 * This reducer data is saved into asyncStore from bootstrap.js and restored from main.js.
 *
 * The main difference with pending breakpoints is that we only save breakpoints
 * against source with an URL as only them can be restored. (source IDs are different across reloads).
 * The second difference is that we don't store the whole source object but only the source URL.
 */

import { isPrettyURL } from "../utils/source";
import assert from "../utils/assert";

function update(state = {}, action) {
  switch (action.type) {
    case "SET_BREAKPOINT":
      if (action.status === "start") {
        return setBreakpoint(state, action.breakpoint);
      }
      return state;

    case "REMOVE_BREAKPOINT":
      if (action.status === "start") {
        return removeBreakpoint(state, action.breakpoint);
      }
      return state;

    case "REMOVE_PENDING_BREAKPOINT":
      return removePendingBreakpoint(state, action.pendingBreakpoint);

    case "CLEAR_BREAKPOINTS": {
      return {};
    }
  }

  return state;
}

function shouldBreakpointBePersisted(breakpoint) {
  // We only save breakpoint for source with URL.
  // Source without URL can only be identified via their source actor ID
  // which isn't persisted across reloads.
  return !breakpoint.options.hidden && breakpoint.location.source.url;
}

function setBreakpoint(state, breakpoint) {
  if (!shouldBreakpointBePersisted(breakpoint)) {
    return state;
  }

  const id = makeIdFromBreakpoint(breakpoint);
  const pendingBreakpoint = createPendingBreakpoint(breakpoint);

  return { ...state, [id]: pendingBreakpoint };
}

function removeBreakpoint(state, breakpoint) {
  if (!shouldBreakpointBePersisted(breakpoint)) {
    return state;
  }

  const id = makeIdFromBreakpoint(breakpoint);
  state = { ...state };

  delete state[id];
  return state;
}

function removePendingBreakpoint(state, pendingBreakpoint) {
  const id = makeIdFromPendingBreakpoint(pendingBreakpoint);
  state = { ...state };

  delete state[id];
  return state;
}

/**
 * Return a unique identifier for a given breakpoint,
 * using its original location, or for pretty-printed sources,
 * its generated location.
 *
 * @param {Object} breakpoint
 */
function makeIdFromBreakpoint(breakpoint) {
  const location = isPrettyURL(breakpoint.location.source.url)
    ? breakpoint.generatedLocation
    : breakpoint.location;

  const { source, line, column } = location;
  const sourceUrlString = source.url || "";
  const columnString = column || "";

  return `${sourceUrlString}:${line}:${columnString}`;
}

function makeIdFromPendingBreakpoint(pendingBreakpoint) {
  const { sourceUrl, line, column } = pendingBreakpoint.location;
  const sourceUrlString = sourceUrl || "";
  const columnString = column || "";

  return `${sourceUrlString}:${line}:${columnString}`;
}

/**
 * Convert typical debugger frontend location (created via location.js:createLocation)
 * to a more lightweight flavor of it which will be stored in async storage.
 */
function createPendingLocation(location) {
  assert(location.hasOwnProperty("line"), "location must have a line");
  assert(location.hasOwnProperty("column"), "location must have a column");

  const { source, line, column } = location;
  assert(source.url !== undefined, "pending location must have a source url");
  return { sourceUrl: source.url, line, column };
}

/**
 * Create a new pending breakpoint, which is a more lightweight version of the regular breakpoint object.
 */
function createPendingBreakpoint(bp) {
  return {
    options: bp.options,
    disabled: bp.disabled,
    location: createPendingLocation(bp.location),
    generatedLocation: createPendingLocation(bp.generatedLocation),
  };
}

export default update;