/* 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 };