summaryrefslogtreecommitdiffstats
path: root/devtools/client/debugger/content/actions/breakpoints.js
blob: 5c5552d789c3d58ef687f2a3e2da19456b334d0c (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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
/* 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/. */
"use strict";

const constants = require("../constants");
const promise = require("promise");
const { asPaused } = require("../utils");
const { PROMISE } = require("devtools/client/shared/redux/middleware/promise");
const {
  getSource, getBreakpoint, getBreakpoints, makeLocationId
} = require("../queries");
const { Task } = require("devtools/shared/task");

// Because breakpoints are just simple data structures, we still need
// a way to lookup the actual client instance to talk to the server.
// We keep an internal database of clients based off of actor ID.
const BREAKPOINT_CLIENT_STORE = new Map();

function setBreakpointClient(actor, client) {
  BREAKPOINT_CLIENT_STORE.set(actor, client);
}

function getBreakpointClient(actor) {
  return BREAKPOINT_CLIENT_STORE.get(actor);
}

function enableBreakpoint(location) {
  // Enabling is exactly the same as adding. It will use the existing
  // breakpoint that still stored.
  return addBreakpoint(location);
}

function _breakpointExists(state, location) {
  const currentBp = getBreakpoint(state, location);
  return currentBp && !currentBp.disabled;
}

function _getOrCreateBreakpoint(state, location, condition) {
  return getBreakpoint(state, location) || { location, condition };
}

function addBreakpoint(location, condition) {
  return (dispatch, getState) => {
    if (_breakpointExists(getState(), location)) {
      return;
    }

    const bp = _getOrCreateBreakpoint(getState(), location, condition);

    return dispatch({
      type: constants.ADD_BREAKPOINT,
      breakpoint: bp,
      condition: condition,
      [PROMISE]: Task.spawn(function* () {
        const sourceClient = gThreadClient.source(
          getSource(getState(), bp.location.actor)
        );
        const [response, bpClient] = yield sourceClient.setBreakpoint({
          line: bp.location.line,
          column: bp.location.column,
          condition: bp.condition
        });
        const { isPending, actualLocation } = response;

        // Save the client instance
        setBreakpointClient(bpClient.actor, bpClient);

        return {
          text: DebuggerView.editor.getText(
            (actualLocation ? actualLocation.line : bp.location.line) - 1
          ).trim(),

          // If the breakpoint response has an "actualLocation" attached, then
          // the original requested placement for the breakpoint wasn't
          // accepted.
          actualLocation: isPending ? null : actualLocation,
          actor: bpClient.actor
        };
      })
    });
  };
}

function disableBreakpoint(location) {
  return _removeOrDisableBreakpoint(location, true);
}

function removeBreakpoint(location) {
  return _removeOrDisableBreakpoint(location);
}

function _removeOrDisableBreakpoint(location, isDisabled) {
  return (dispatch, getState) => {
    let bp = getBreakpoint(getState(), location);
    if (!bp) {
      throw new Error("attempt to remove breakpoint that does not exist");
    }
    if (bp.loading) {
      // TODO(jwl): make this wait until the breakpoint is saved if it
      // is still loading
      throw new Error("attempt to remove unsaved breakpoint");
    }

    const bpClient = getBreakpointClient(bp.actor);
    const action = {
      type: constants.REMOVE_BREAKPOINT,
      breakpoint: bp,
      disabled: isDisabled
    };

    // If the breakpoint is already disabled, we don't need to remove
    // it from the server. We just need to dispatch an action
    // simulating a successful server request to remove it, and it
    // will be removed completely from the state.
    if (!bp.disabled) {
      return dispatch(Object.assign({}, action, {
        [PROMISE]: bpClient.remove()
      }));
    } else {
      return dispatch(Object.assign({}, action, { status: "done" }));
    }
  };
}

function removeAllBreakpoints() {
  return (dispatch, getState) => {
    const breakpoints = getBreakpoints(getState());
    const activeBreakpoints = breakpoints.filter(bp => !bp.disabled);
    activeBreakpoints.forEach(bp => removeBreakpoint(bp.location));
  };
}

/**
 * Update the condition of a breakpoint.
 *
 * @param object aLocation
 *        @see DebuggerController.Breakpoints.addBreakpoint
 * @param string aClients
 *        The condition to set on the breakpoint
 * @return object
 *         A promise that will be resolved with the breakpoint client
 */
function setBreakpointCondition(location, condition) {
  return (dispatch, getState) => {
    const bp = getBreakpoint(getState(), location);
    if (!bp) {
      throw new Error("Breakpoint does not exist at the specified location");
    }
    if (bp.loading) {
      // TODO(jwl): when this function is called, make sure the action
      // creator waits for the breakpoint to exist
      throw new Error("breakpoint must be saved");
    }

    const bpClient = getBreakpointClient(bp.actor);
    const action = {
      type: constants.SET_BREAKPOINT_CONDITION,
      breakpoint: bp,
      condition: condition
    };

    // If it's not disabled, we need to update the condition on the
    // server. Otherwise, just dispatch a non-remote action that
    // updates the condition locally.
    if (!bp.disabled) {
      return dispatch(Object.assign({}, action, {
        [PROMISE]: Task.spawn(function* () {
          const newClient = yield bpClient.setCondition(gThreadClient, condition);

          // Remove the old instance and save the new one
          setBreakpointClient(bpClient.actor, null);
          setBreakpointClient(newClient.actor, newClient);

          return { actor: newClient.actor };
        })
      }));
    } else {
      return dispatch(action);
    }
  };
}

module.exports = {
  enableBreakpoint,
  addBreakpoint,
  disableBreakpoint,
  removeBreakpoint,
  removeAllBreakpoints,
  setBreakpointCondition
};