diff options
Diffstat (limited to 'devtools/client/debugger/src/actions/breakpoints/syncBreakpoint.js')
-rw-r--r-- | devtools/client/debugger/src/actions/breakpoints/syncBreakpoint.js | 194 |
1 files changed, 194 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..bed0193313 --- /dev/null +++ b/devtools/client/debugger/src/actions/breakpoints/syncBreakpoint.js @@ -0,0 +1,194 @@ +/* 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/>. */ + +// @flow + +import { setBreakpointPositions } from "./breakpointPositions"; +import { setSymbols } from "../sources/symbols"; +import { + assertPendingBreakpoint, + findFunctionByName, + findPosition, + makeBreakpointLocation, +} from "../../utils/breakpoint"; + +import { comparePosition, createLocation } from "../../utils/location"; + +import { originalToGeneratedId, isOriginalId } from "devtools-source-map"; +import { getSource } from "../../selectors"; +import { addBreakpoint, removeBreakpointAtGeneratedLocation } from "."; + +import type { ThunkArgs } from "../types"; +import type { LoadedSymbols } from "../../reducers/types"; + +import type { + SourceLocation, + ASTLocation, + PendingBreakpoint, + SourceId, + Source, + BreakpointPositions, + Context, +} from "../../types"; + +async function findBreakpointPosition( + cx: Context, + { getState, dispatch }: ThunkArgs, + location: SourceLocation +) { + const { sourceId, line } = location; + const positions: BreakpointPositions = await dispatch( + setBreakpointPositions({ cx, sourceId, line }) + ); + + const position = findPosition(positions, location); + return position?.generatedLocation; +} + +async function findNewLocation( + cx: Context, + { name, offset, index }: ASTLocation, + location: SourceLocation, + source: Source, + thunkArgs: ThunkArgs +) { + const symbols: LoadedSymbols = await thunkArgs.dispatch( + setSymbols({ cx, source }) + ); + const func = symbols ? findFunctionByName(symbols, name, index) : null; + + // Fallback onto the location line, if we do not find a function. + let { line } = location; + if (func) { + line = func.location.start.line + offset.line; + } + + return { + line, + column: location.column, + sourceUrl: source.url, + sourceId: source.id, + }; +} + +// 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 syncBreakpoint( + cx: Context, + sourceId: SourceId, + pendingBreakpoint: PendingBreakpoint +) { + return async (thunkArgs: ThunkArgs) => { + const { getState, client, dispatch } = thunkArgs; + assertPendingBreakpoint(pendingBreakpoint); + + const source = getSource(getState(), sourceId); + + const generatedSourceId = isOriginalId(sourceId) + ? originalToGeneratedId(sourceId) + : sourceId; + + const generatedSource = getSource(getState(), generatedSourceId); + + if (!source || !generatedSource) { + return; + } + + const { location, generatedLocation, astLocation } = pendingBreakpoint; + const sourceGeneratedLocation = createLocation({ + ...generatedLocation, + sourceId: generatedSourceId, + }); + + if ( + source == generatedSource && + location.sourceUrl != generatedLocation.sourceUrl + ) { + // 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 breakpointLocation = makeBreakpointLocation( + getState(), + sourceGeneratedLocation + ); + return dispatch( + addBreakpoint( + cx, + sourceGeneratedLocation, + pendingBreakpoint.options, + pendingBreakpoint.disabled, + () => !client.hasBreakpoint(breakpointLocation) + ) + ); + } + + const previousLocation = { ...location, sourceId }; + + const newLocation = await findNewLocation( + cx, + astLocation, + previousLocation, + source, + thunkArgs + ); + + const newGeneratedLocation = await findBreakpointPosition( + cx, + thunkArgs, + newLocation + ); + + 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 (location.sourceUrl != generatedLocation.sourceUrl) { + dispatch( + removeBreakpointAtGeneratedLocation(cx, sourceGeneratedLocation) + ); + } + return; + } + + 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, + newLocation, + pendingBreakpoint.options, + pendingBreakpoint.disabled + ) + ); + }; +} |