location.js (5083B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ 4 5 import { getSelectedLocation } from "./selected-location"; 6 import { getSource } from "../selectors/index"; 7 8 /** 9 * Note that arguments can be created via `createLocation`. 10 * But they can also be created via `createPendingLocation` in reducer/pending-breakpoints.js. 11 * Both will have similar line and column attributes. 12 */ 13 export function comparePosition(a, b) { 14 return a && b && a.line == b.line && a.column == b.column; 15 } 16 17 export function createLocation({ 18 source, 19 sourceActor = null, 20 21 // Line 0 represents no specific line chosen for action 22 line = 0, 23 column, 24 }) { 25 return { 26 source, 27 sourceActor, 28 sourceActorId: sourceActor?.id, 29 30 // # Quick overview of 1-based versus 0-based lines and columns # 31 // 32 // Everything assumes a 1-based line, but columns can be 0 or 1 based. 33 // Note that while lines are 1-based some RDP packet may refer to line 0 which should be considered as "no precise location". 34 // 35 // Columns are 0-based in: 36 // - overall all debugger frontend 37 // - anything around source maps (SourceMapLoader, SourceMapURLService, SourceMap library) 38 // - most RDP packets, especially around the thread actor: 39 // - breakpoints 40 // - breakpoint positions 41 // - pause location 42 // - paused frames 43 // 44 // Columns are 1-based in: 45 // - the UI displayed to the user (console messages, frames, stacktraces,...) 46 // - asserted locations in tests (to match displayed numbers) 47 // - Spidermonkey: 48 // This data is mostly coming from and driven by 49 // JSScript::lineno and JSScript::column 50 // https://searchfox.org/mozilla-central/rev/4c065f1df299065c305fb48b36cdae571a43d97c/js/src/vm/JSScript.h#1567-1570 51 // - some RDP packets outside of the thread actor: 52 // - CONSOLE_MESSAGE, CSS_MESSAGE, PAGE_ERROR resources for lineNumber, columnNumber and stacktrace attributes 53 // - Error objects's Object Actor's grip's "preview" attribute will expose its stacktraces with 1-based columns 54 // - SmartTrace is dealing with these RDP packets and consumes 1-based columns, 55 // but has to map to 0-based columns as it depends on debugger frontend Frames components. 56 // 57 // The RDP server, especially in the thread actor ecosystem has to map from spidermonkey 1-based to historical 0-based columns. 58 line, 59 column, 60 }; 61 } 62 63 /** 64 * Convert location objects created via `createLocation` into 65 * the format used by the Source Map Loader/Worker. 66 * It only needs sourceId, line and column attributes. 67 */ 68 export function debuggerToSourceMapLocation(location) { 69 return { 70 sourceId: location.source.id, 71 // In case of errors loading the source, we might not have a precise location. 72 // Defaults to first line and column. 73 line: location.line || 1, 74 column: location.column || 0, 75 }; 76 } 77 78 /** 79 * Pending location only need these three attributes, 80 * and especially doesn't need the large source and sourceActor objects of the regular location objects. 81 * 82 * @param {object} location 83 */ 84 export function createPendingSelectedLocation(location) { 85 return { 86 url: location.source.url, 87 88 line: location.line, 89 column: location.column, 90 }; 91 } 92 93 export function sortSelectedLocations(locations, selectedSource) { 94 return Array.from(locations).sort((locationA, locationB) => { 95 const aSelected = getSelectedLocation(locationA, selectedSource); 96 const bSelected = getSelectedLocation(locationB, selectedSource); 97 98 // Order the locations by line number… 99 if (aSelected.line < bSelected.line) { 100 return -1; 101 } 102 103 if (aSelected.line > bSelected.line) { 104 return 1; 105 } 106 107 // … and if we have the same line, we want to return location with undefined columns 108 // first, and then order them by column 109 if (aSelected.column == bSelected.column) { 110 return 0; 111 } 112 113 if (aSelected.column === undefined) { 114 return -1; 115 } 116 117 if (bSelected.column === undefined) { 118 return 1; 119 } 120 121 return aSelected.column < bSelected.column ? -1 : 1; 122 }); 123 } 124 125 /** 126 * Source map Loader/Worker and debugger frontend don't use the same objects for locations. 127 * Worker uses 'sourceId' attributes whereas the frontend has 'source' attribute. 128 */ 129 export function sourceMapToDebuggerLocation(state, location) { 130 // From MapScopes modules, we might re-process the exact same location objects 131 // for which we would already have computed the source object, 132 // and which would lack sourceId attribute. 133 if (location.source) { 134 return location; 135 } 136 137 // SourceMapLoader doesn't known about debugger's source objects 138 // so that we have to fetch it from here 139 const source = getSource(state, location.sourceId); 140 if (!source) { 141 throw new Error(`Could not find source-map source ${location.sourceId}`); 142 } 143 144 return createLocation({ 145 ...location, 146 source, 147 }); 148 }