diff options
Diffstat (limited to 'devtools/client/debugger/src/actions/breakpoints/syncBreakpoint.js')
-rw-r--r-- | devtools/client/debugger/src/actions/breakpoints/syncBreakpoint.js | 138 |
1 files changed, 138 insertions, 0 deletions
diff --git a/devtools/client/debugger/src/actions/breakpoints/syncBreakpoint.js b/devtools/client/debugger/src/actions/breakpoints/syncBreakpoint.js new file mode 100644 index 0000000000..b52c0ddfb1 --- /dev/null +++ b/devtools/client/debugger/src/actions/breakpoints/syncBreakpoint.js @@ -0,0 +1,138 @@ +/* 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/>. */ + +import { setBreakpointPositions } from "./breakpointPositions"; +import { + findPosition, + makeBreakpointServerLocation, +} from "../../utils/breakpoint"; + +import { comparePosition, createLocation } from "../../utils/location"; + +import { + originalToGeneratedId, + isOriginalId, +} from "devtools/client/shared/source-map-loader/index"; +import { getSource } from "../../selectors"; +import { addBreakpoint, removeBreakpointAtGeneratedLocation } from "."; + +async function findBreakpointPosition(cx, { getState, dispatch }, location) { + const positions = await dispatch(setBreakpointPositions({ cx, location })); + + const position = findPosition(positions, location); + return position; +} + +// Breakpoint syncing occurs when a source is found that matches either the +// original or generated URL of a pending breakpoint. A new breakpoint is +// constructed that might have a different original and/or generated location, +// if the original source has changed since the pending breakpoint was created. +// There are a couple subtle aspects to syncing: +// +// - We handle both the original and generated source because there is no +// guarantee that seeing the generated source means we will also see the +// original source. When connecting, a breakpoint will be installed in the +// client for the generated location in the pending breakpoint, and we need +// to make sure that either a breakpoint is added to the reducer or that this +// client breakpoint is deleted. +// +// - If we see both the original and generated sources and the source mapping +// has changed, we need to make sure that only a single breakpoint is added +// to the reducer for the new location corresponding to the original location +// in the pending breakpoint. +export function syncPendingBreakpoint(cx, sourceId, pendingBreakpoint) { + return async thunkArgs => { + const { getState, client, dispatch } = thunkArgs; + + const source = getSource(getState(), sourceId); + + const generatedSourceId = isOriginalId(sourceId) + ? originalToGeneratedId(sourceId) + : sourceId; + + const generatedSource = getSource(getState(), generatedSourceId); + + if (!source || !generatedSource) { + return null; + } + + // /!\ Pending breakpoint locations come only with sourceUrl, line and column attributes. + // We have to map it to a specific source object and avoid trying to query its non-existent 'source' attribute. + const { location, generatedLocation } = pendingBreakpoint; + const isPendingBreakpointWithSourceMap = + location.sourceUrl != generatedLocation.sourceUrl; + const sourceGeneratedLocation = createLocation({ + ...generatedLocation, + source: generatedSource, + }); + + if (source == generatedSource && isPendingBreakpointWithSourceMap) { + // We are handling the generated source and the pending breakpoint has a + // source mapping. Supply a cancellation callback that will abort the + // breakpoint if the original source was synced to a different location, + // in which case the client breakpoint has been removed. + const breakpointServerLocation = makeBreakpointServerLocation( + getState(), + sourceGeneratedLocation + ); + return dispatch( + addBreakpoint( + cx, + sourceGeneratedLocation, + pendingBreakpoint.options, + pendingBreakpoint.disabled, + () => !client.hasBreakpoint(breakpointServerLocation) + ) + ); + } + + const originalLocation = createLocation({ + ...location, + source, + }); + + const newPosition = await findBreakpointPosition( + cx, + thunkArgs, + originalLocation + ); + + const newGeneratedLocation = newPosition?.generatedLocation; + if (!newGeneratedLocation) { + // We couldn't find a new mapping for the breakpoint. If there is a source + // mapping, remove any breakpoints for the generated location, as if the + // breakpoint moved. If the old generated location still maps to an + // original location then we don't want to add a breakpoint for it. + if (isPendingBreakpointWithSourceMap) { + dispatch( + removeBreakpointAtGeneratedLocation(cx, sourceGeneratedLocation) + ); + } + return null; + } + + const isSameLocation = comparePosition( + generatedLocation, + newGeneratedLocation + ); + + // If the new generated location has changed from that in the pending + // breakpoint, remove any breakpoint associated with the old generated + // location. + if (!isSameLocation) { + dispatch( + removeBreakpointAtGeneratedLocation(cx, sourceGeneratedLocation) + ); + } + + return dispatch( + addBreakpoint( + cx, + newGeneratedLocation, + pendingBreakpoint.options, + pendingBreakpoint.disabled + ) + ); + }; +} |