chrome-devtools-frontend 1.0.941095 → 1.0.943017

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/WATCHLISTS +1 -1
  2. package/config/gni/all_devtools_files.gni +0 -62
  3. package/config/gni/devtools_grd_files.gni +55 -19
  4. package/config/gni/devtools_image_files.gni +2 -3
  5. package/front_end/.eslintrc.js +12 -1
  6. package/front_end/Images/src/feedback_button_icon.svg +3 -0
  7. package/front_end/Images/src/{feedback_thin_16x16_icon.svg → survey_feedback_icon.svg} +1 -1
  8. package/front_end/Tests.js +1 -32
  9. package/front_end/core/common/Color.ts +5 -0
  10. package/front_end/core/i18n/locales/en-US.json +20 -29
  11. package/front_end/core/i18n/locales/en-XL.json +20 -29
  12. package/front_end/core/sdk/CPUProfilerModel.ts +7 -9
  13. package/front_end/core/sdk/ConsoleModel.ts +26 -28
  14. package/front_end/core/sdk/DebuggerModel.ts +4 -14
  15. package/front_end/core/sdk/sdk-meta.ts +17 -3
  16. package/front_end/entrypoints/devtools_app/devtools_app.json +1 -11
  17. package/front_end/entrypoints/inspector/inspector.json +1 -3
  18. package/front_end/entrypoints/js_app/js_app.json +1 -3
  19. package/front_end/entrypoints/main/MainImpl.ts +26 -0
  20. package/front_end/entrypoints/node_app/node_app.json +1 -3
  21. package/front_end/entrypoints/shell/shell.js +0 -11
  22. package/front_end/entrypoints/shell/shell.json +1 -5
  23. package/front_end/entrypoints/worker_app/worker_app.json +1 -7
  24. package/front_end/generated/InspectorBackendCommands.js +19 -0
  25. package/front_end/generated/protocol-mapping.d.ts +31 -1
  26. package/front_end/generated/protocol-proxy-api.d.ts +34 -2
  27. package/front_end/generated/protocol.d.ts +81 -6
  28. package/front_end/global_typings/global_defs.d.ts +5 -0
  29. package/front_end/legacy_test_runner/bindings_test_runner/IsolatedFilesystemTestRunner.js +2 -2
  30. package/front_end/legacy_test_runner/console_test_runner/console_test_runner.js +14 -2
  31. package/front_end/legacy_test_runner/legacy_test_runner.ts +10 -1
  32. package/front_end/legacy_test_runner/test_runner/TestRunner.js +11 -0
  33. package/front_end/models/formatter/SourceFormatter.ts +0 -10
  34. package/front_end/models/workspace/UISourceCode.ts +9 -42
  35. package/front_end/panels/animation/AnimationTimeline.ts +3 -3
  36. package/front_end/panels/application/ApplicationPanelSidebar.ts +3 -3
  37. package/front_end/panels/application/BackForwardCacheStrings.ts +3 -1
  38. package/front_end/panels/application/application-meta.ts +0 -3
  39. package/front_end/panels/application/components/EndpointsGrid.ts +1 -1
  40. package/front_end/panels/application/components/ReportsGrid.ts +1 -1
  41. package/front_end/panels/console/ConsolePinPane.ts +21 -26
  42. package/front_end/panels/coverage/CoverageDecorationManager.ts +4 -5
  43. package/front_end/panels/coverage/CoverageView.ts +2 -105
  44. package/front_end/panels/css_overview/components/CSSOverviewStartView.ts +11 -56
  45. package/front_end/panels/css_overview/components/cssOverviewStartView.css +1 -8
  46. package/front_end/panels/elements/ElementsTreeElement.ts +4 -9
  47. package/front_end/panels/elements/components/StylePropertyEditor.ts +2 -0
  48. package/front_end/panels/elements/components/adornerSettingsPane.css +0 -4
  49. package/front_end/panels/emulation/DeviceModeToolbar.ts +3 -1
  50. package/front_end/panels/emulation/DeviceModeView.ts +2 -1
  51. package/front_end/panels/emulation/InspectedPagePlaceholder.ts +3 -1
  52. package/front_end/panels/emulation/MediaQueryInspector.ts +3 -1
  53. package/front_end/panels/emulation/emulation-meta.ts +2 -4
  54. package/front_end/panels/issues/issues-meta.ts +0 -2
  55. package/front_end/panels/js_profiler/js_profiler-meta.ts +0 -3
  56. package/front_end/panels/layers/module.json +0 -1
  57. package/front_end/panels/lighthouse/LighthousePanel.ts +2 -4
  58. package/front_end/panels/lighthouse/LighthouseReportRenderer.ts +1 -4
  59. package/front_end/panels/lighthouse/lighthouseStartView.css +4 -0
  60. package/front_end/panels/lighthouse/module.json +0 -6
  61. package/front_end/panels/media/media-meta.ts +0 -3
  62. package/front_end/panels/network/ResourceWebSocketFrameView.ts +2 -1
  63. package/front_end/panels/network/network-meta.ts +0 -3
  64. package/front_end/panels/profiler/CPUProfileView.ts +10 -3
  65. package/front_end/panels/profiler/profiler-meta.ts +0 -2
  66. package/front_end/panels/screencast/screencast-meta.ts +0 -3
  67. package/front_end/panels/security/security-meta.ts +0 -3
  68. package/front_end/panels/sources/BreakpointEditDialog.ts +16 -30
  69. package/front_end/panels/sources/CSSPlugin.ts +310 -331
  70. package/front_end/panels/sources/CallStackSidebarPane.ts +28 -34
  71. package/front_end/panels/sources/CoveragePlugin.ts +121 -6
  72. package/front_end/panels/sources/DebuggerPlugin.ts +1166 -1243
  73. package/front_end/panels/sources/EditingLocationHistoryManager.ts +71 -101
  74. package/front_end/panels/sources/GoToLineQuickOpen.ts +4 -3
  75. package/front_end/panels/sources/InplaceFormatterEditorAction.ts +3 -3
  76. package/front_end/panels/sources/JavaScriptCompilerPlugin.ts +26 -23
  77. package/front_end/panels/sources/Plugin.ts +20 -4
  78. package/front_end/panels/sources/ProfilePlugin.ts +185 -0
  79. package/front_end/panels/sources/ScriptFormatterEditorAction.ts +3 -3
  80. package/front_end/panels/sources/ScriptOriginPlugin.ts +0 -10
  81. package/front_end/panels/sources/SnippetsPlugin.ts +1 -10
  82. package/front_end/panels/sources/SourcesPanel.ts +6 -5
  83. package/front_end/panels/sources/SourcesView.ts +10 -8
  84. package/front_end/panels/sources/TabbedEditorContainer.ts +31 -27
  85. package/front_end/panels/sources/UISourceCodeFrame.ts +335 -470
  86. package/front_end/panels/sources/WatchExpressionsSidebarPane.ts +3 -2
  87. package/front_end/panels/sources/sources-legacy.ts +0 -6
  88. package/front_end/panels/sources/sources-meta.ts +1 -4
  89. package/front_end/panels/sources/sources.ts +0 -2
  90. package/front_end/panels/timeline/timeline-meta.ts +0 -5
  91. package/front_end/third_party/codemirror.next/bundle.ts +9 -13
  92. package/front_end/third_party/codemirror.next/chunk/codemirror.js +1 -1
  93. package/front_end/third_party/codemirror.next/chunk/javascript.js +2 -2
  94. package/front_end/third_party/codemirror.next/chunk/markdown.js +2 -6
  95. package/front_end/third_party/codemirror.next/chunk/php.js +2 -6
  96. package/front_end/third_party/codemirror.next/chunk/python.js +1 -1
  97. package/front_end/third_party/codemirror.next/chunk/wast.js +1 -1
  98. package/front_end/third_party/codemirror.next/chunk/xml.js +2 -2
  99. package/front_end/third_party/codemirror.next/codemirror.next.d.ts +279 -198
  100. package/front_end/third_party/codemirror.next/codemirror.next.js +1 -1
  101. package/front_end/third_party/codemirror.next/package.json +13 -11
  102. package/front_end/third_party/lighthouse/lighthouse-dt-bundle.js +1128 -1158
  103. package/front_end/third_party/lighthouse/locales/ar-XB.json +211 -79
  104. package/front_end/third_party/lighthouse/locales/ar.json +213 -81
  105. package/front_end/third_party/lighthouse/locales/bg.json +211 -79
  106. package/front_end/third_party/lighthouse/locales/ca.json +212 -80
  107. package/front_end/third_party/lighthouse/locales/cs.json +211 -79
  108. package/front_end/third_party/lighthouse/locales/da.json +211 -79
  109. package/front_end/third_party/lighthouse/locales/de.json +211 -79
  110. package/front_end/third_party/lighthouse/locales/el.json +213 -81
  111. package/front_end/third_party/lighthouse/locales/en-GB.json +211 -79
  112. package/front_end/third_party/lighthouse/locales/en-US.json +186 -75
  113. package/front_end/third_party/lighthouse/locales/en-XA.json +211 -79
  114. package/front_end/third_party/lighthouse/locales/en-XL.json +186 -75
  115. package/front_end/third_party/lighthouse/locales/es-419.json +211 -79
  116. package/front_end/third_party/lighthouse/locales/es.json +212 -80
  117. package/front_end/third_party/lighthouse/locales/fi.json +211 -79
  118. package/front_end/third_party/lighthouse/locales/fil.json +211 -79
  119. package/front_end/third_party/lighthouse/locales/fr.json +211 -79
  120. package/front_end/third_party/lighthouse/locales/he.json +212 -80
  121. package/front_end/third_party/lighthouse/locales/hi.json +214 -82
  122. package/front_end/third_party/lighthouse/locales/hr.json +211 -79
  123. package/front_end/third_party/lighthouse/locales/hu.json +211 -79
  124. package/front_end/third_party/lighthouse/locales/id.json +211 -79
  125. package/front_end/third_party/lighthouse/locales/it.json +211 -79
  126. package/front_end/third_party/lighthouse/locales/ja.json +211 -79
  127. package/front_end/third_party/lighthouse/locales/ko.json +211 -79
  128. package/front_end/third_party/lighthouse/locales/lt.json +211 -79
  129. package/front_end/third_party/lighthouse/locales/lv.json +214 -82
  130. package/front_end/third_party/lighthouse/locales/nl.json +211 -79
  131. package/front_end/third_party/lighthouse/locales/no.json +211 -79
  132. package/front_end/third_party/lighthouse/locales/pl.json +211 -79
  133. package/front_end/third_party/lighthouse/locales/pt-PT.json +211 -79
  134. package/front_end/third_party/lighthouse/locales/pt.json +211 -79
  135. package/front_end/third_party/lighthouse/locales/ro.json +212 -80
  136. package/front_end/third_party/lighthouse/locales/ru.json +211 -79
  137. package/front_end/third_party/lighthouse/locales/sk.json +211 -79
  138. package/front_end/third_party/lighthouse/locales/sl.json +211 -79
  139. package/front_end/third_party/lighthouse/locales/sr-Latn.json +211 -79
  140. package/front_end/third_party/lighthouse/locales/sr.json +211 -79
  141. package/front_end/third_party/lighthouse/locales/sv.json +211 -79
  142. package/front_end/third_party/lighthouse/locales/ta.json +218 -86
  143. package/front_end/third_party/lighthouse/locales/te.json +251 -119
  144. package/front_end/third_party/lighthouse/locales/th.json +211 -79
  145. package/front_end/third_party/lighthouse/locales/tr.json +211 -79
  146. package/front_end/third_party/lighthouse/locales/uk.json +212 -80
  147. package/front_end/third_party/lighthouse/locales/vi.json +211 -79
  148. package/front_end/third_party/lighthouse/locales/zh-HK.json +211 -79
  149. package/front_end/third_party/lighthouse/locales/zh-TW.json +211 -79
  150. package/front_end/third_party/lighthouse/locales/zh.json +211 -79
  151. package/front_end/third_party/lighthouse/report/bundle.d.ts +72 -34
  152. package/front_end/third_party/lighthouse/report/bundle.js +698 -492
  153. package/front_end/third_party/lighthouse/report-assets/report-generator.js +1 -2
  154. package/front_end/third_party/lighthouse/report-assets/report.js +40 -35
  155. package/front_end/third_party/lighthouse/report-assets/standalone-template.html +2 -4
  156. package/front_end/ui/components/code_highlighter/CodeHighlighter.ts +60 -68
  157. package/front_end/ui/components/data_grid/dataGrid.css +12 -10
  158. package/front_end/ui/components/docs/css_overview/start_view.html +25 -0
  159. package/front_end/ui/components/docs/css_overview/start_view.ts +14 -0
  160. package/front_end/ui/components/docs/icon_button/basic.ts +3 -3
  161. package/front_end/ui/components/docs/panel_feedback/button.html +25 -0
  162. package/front_end/ui/components/docs/panel_feedback/button.ts +18 -0
  163. package/front_end/ui/components/helpers/get-stylesheet.ts +0 -14
  164. package/front_end/ui/components/markdown_view/MarkdownImagesMap.ts +1 -1
  165. package/front_end/ui/components/panel_feedback/FeedbackButton.ts +67 -0
  166. package/front_end/ui/components/panel_feedback/panel_feedback.ts +1 -0
  167. package/front_end/ui/components/survey_link/SurveyLink.ts +1 -1
  168. package/front_end/ui/components/text_editor/TextEditor.ts +79 -36
  169. package/front_end/ui/components/text_editor/config.ts +42 -26
  170. package/front_end/ui/components/text_editor/javascript.ts +2 -3
  171. package/front_end/ui/components/text_editor/position.ts +17 -0
  172. package/front_end/ui/components/text_editor/text_editor.ts +1 -0
  173. package/front_end/ui/components/text_editor/theme.ts +5 -1
  174. package/front_end/ui/legacy/Dialog.ts +3 -1
  175. package/front_end/ui/legacy/DropTarget.ts +2 -1
  176. package/front_end/ui/legacy/EmptyWidget.ts +2 -1
  177. package/front_end/ui/legacy/FilterBar.ts +2 -1
  178. package/front_end/ui/legacy/GlassPane.ts +4 -2
  179. package/front_end/ui/legacy/Infobar.ts +5 -8
  180. package/front_end/ui/legacy/InspectorView.ts +6 -1
  181. package/front_end/ui/legacy/ListWidget.ts +2 -1
  182. package/front_end/ui/legacy/PopoverHelper.ts +2 -1
  183. package/front_end/ui/legacy/ProgressIndicator.ts +2 -1
  184. package/front_end/ui/legacy/RemoteDebuggingTerminatedScreen.ts +2 -1
  185. package/front_end/ui/legacy/ReportView.ts +2 -1
  186. package/front_end/ui/legacy/RootView.ts +2 -1
  187. package/front_end/ui/legacy/SearchableView.ts +2 -1
  188. package/front_end/ui/legacy/ShortcutRegistry.ts +11 -7
  189. package/front_end/ui/legacy/SoftContextMenu.ts +2 -1
  190. package/front_end/ui/legacy/SoftDropDown.ts +4 -2
  191. package/front_end/ui/legacy/SplitWidget.ts +2 -1
  192. package/front_end/ui/legacy/SuggestBox.ts +2 -1
  193. package/front_end/ui/legacy/TabbedPane.ts +2 -1
  194. package/front_end/ui/legacy/TargetCrashedScreen.ts +2 -1
  195. package/front_end/ui/legacy/TextPrompt.ts +2 -1
  196. package/front_end/ui/legacy/Toolbar.ts +3 -2
  197. package/front_end/ui/legacy/Treeoutline.ts +3 -2
  198. package/front_end/ui/legacy/UIUtils.ts +16 -13
  199. package/front_end/ui/legacy/ViewManager.ts +2 -1
  200. package/front_end/ui/legacy/Widget.ts +1 -1
  201. package/front_end/ui/legacy/components/object_ui/object_ui-meta.ts +0 -3
  202. package/front_end/ui/legacy/components/perf_ui/ChartViewport.ts +2 -1
  203. package/front_end/ui/legacy/components/perf_ui/FilmStripView.ts +3 -1
  204. package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +2 -1
  205. package/front_end/ui/legacy/components/perf_ui/LineLevelProfile.ts +35 -131
  206. package/front_end/ui/legacy/components/perf_ui/OverviewGrid.ts +2 -1
  207. package/front_end/ui/legacy/components/perf_ui/TimelineGrid.ts +3 -1
  208. package/front_end/ui/legacy/components/perf_ui/TimelineOverviewPane.ts +2 -1
  209. package/front_end/ui/legacy/components/perf_ui/perf_ui-meta.ts +0 -2
  210. package/front_end/ui/legacy/components/quick_open/filteredListWidget.css +2 -2
  211. package/front_end/ui/legacy/components/source_frame/BinaryResourceViewFactory.ts +3 -6
  212. package/front_end/ui/legacy/components/source_frame/FontView.ts +1 -0
  213. package/front_end/ui/legacy/components/source_frame/ImageView.ts +1 -0
  214. package/front_end/ui/legacy/components/source_frame/JSONView.ts +1 -0
  215. package/front_end/ui/legacy/components/source_frame/ResourceSourceFrame.ts +19 -14
  216. package/front_end/ui/legacy/components/source_frame/SourceFrame.ts +501 -252
  217. package/front_end/ui/legacy/components/source_frame/XMLView.ts +2 -0
  218. package/front_end/ui/legacy/components/source_frame/module.json +0 -3
  219. package/front_end/ui/legacy/components/source_frame/source_frame-legacy.ts +0 -11
  220. package/front_end/ui/legacy/components/source_frame/source_frame.ts +0 -2
  221. package/front_end/ui/legacy/components/text_editor/CodeMirrorTextEditor.ts +2 -0
  222. package/front_end/ui/legacy/components/text_editor/cmdevtools.css +3 -1
  223. package/front_end/ui/legacy/components/text_editor/module.json +0 -3
  224. package/front_end/ui/legacy/components/utils/Linkifier.ts +7 -15
  225. package/front_end/ui/legacy/radioButton.css +1 -13
  226. package/front_end/ui/legacy/textButton.css +5 -4
  227. package/front_end/ui/legacy/themeColors.css +36 -0
  228. package/front_end/ui/legacy/theme_support/theme_support_impl.ts +7 -9
  229. package/front_end/ui/legacy/utils/append-style.ts +9 -4
  230. package/front_end/ui/legacy/utils/create-shadow-root-with-core-styles.ts +2 -2
  231. package/front_end/ui/legacy/utils/inject-core-styles.ts +7 -4
  232. package/package.json +1 -1
  233. package/scripts/build/generate_css_js_files.js +23 -9
  234. package/scripts/build/ninja/generate_css.gni +10 -1
  235. package/scripts/eslint_rules/lib/check_css_import.js +2 -2
  236. package/scripts/eslint_rules/tests/check_css_import_test.js +12 -0
  237. package/front_end/Images/radioDot-dark-theme.png +0 -0
  238. package/front_end/Images/radioDot.png +0 -0
  239. package/front_end/emulated_devices/module.json +0 -6
  240. package/front_end/panels/application/module.json +0 -7
  241. package/front_end/panels/emulation/module.json +0 -11
  242. package/front_end/panels/issues/module.json +0 -6
  243. package/front_end/panels/js_profiler/module.json +0 -5
  244. package/front_end/panels/layer_viewer/module.json +0 -6
  245. package/front_end/panels/media/module.json +0 -6
  246. package/front_end/panels/network/module.json +0 -6
  247. package/front_end/panels/profiler/module.json +0 -6
  248. package/front_end/panels/screencast/module.json +0 -6
  249. package/front_end/panels/security/module.json +0 -5
  250. package/front_end/panels/timeline/module.json +0 -8
  251. package/front_end/third_party/lighthouse/report-assets/report.css +0 -1774
  252. package/front_end/ui/legacy/components/perf_ui/module.json +0 -13
  253. package/front_end/ui/legacy/components/source_frame/SourcesTextEditor.ts +0 -1030
  254. package/front_end/ui/legacy/module.json +0 -41
@@ -34,15 +34,15 @@ import * as i18n from '../../core/i18n/i18n.js';
34
34
  import * as Platform from '../../core/platform/platform.js';
35
35
  import * as Root from '../../core/root/root.js';
36
36
  import * as SDK from '../../core/sdk/sdk.js';
37
+ import * as Protocol from '../../generated/protocol.js';
37
38
  import * as Bindings from '../../models/bindings/bindings.js';
38
39
  import * as TextUtils from '../../models/text_utils/text_utils.js';
39
40
  import * as Workspace from '../../models/workspace/workspace.js';
41
+ import * as CodeMirror from '../../third_party/codemirror.next/codemirror.next.js';
40
42
  import * as ObjectUI from '../../ui/legacy/components/object_ui/object_ui.js';
41
43
  import * as SourceFrame from '../../ui/legacy/components/source_frame/source_frame.js';
42
-
43
- import type * as TextEditor from '../../ui/legacy/components/text_editor/text_editor.js';
44
44
  import * as UI from '../../ui/legacy/legacy.js';
45
- import * as Protocol from '../../generated/protocol.js';
45
+ import type * as TextEditor from '../../ui/components/text_editor/text_editor.js';
46
46
 
47
47
  import {AddSourceMapURLDialog} from './AddSourceMapURLDialog.js';
48
48
  import {BreakpointEditDialog, LogpointPrefix} from './BreakpointEditDialog.js';
@@ -144,154 +144,67 @@ const UIStrings = {
144
144
  };
145
145
  const str_ = i18n.i18n.registerUIStrings('panels/sources/DebuggerPlugin.ts', UIStrings);
146
146
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
147
- // eslint-disable-next-line no-unused-vars
148
- class DecoratorWidget extends HTMLDivElement {
149
- // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
150
- // eslint-disable-next-line @typescript-eslint/naming-convention
151
- nameToToken!: Map<string, HTMLElement>;
152
- constructor() {
153
- super();
154
- }
155
- }
147
+
148
+ // Note: Line numbers are passed around as zero-based numbers (though
149
+ // CodeMirror numbers them from 1).
150
+
151
+ // Don't scan for possible breakpoints on a line beyond this position;
152
+ const MAX_POSSIBLE_BREAKPOINT_LINE = 2500;
153
+
154
+ type BreakpointDescription = {
155
+ position: number,
156
+ breakpoint: Bindings.BreakpointManager.Breakpoint,
157
+ };
156
158
 
157
159
  export class DebuggerPlugin extends Plugin {
158
- private textEditor: SourceFrame.SourcesTextEditor.SourcesTextEditor;
159
- private uiSourceCode: Workspace.UISourceCode.UISourceCode;
160
- private readonly transformer: SourceFrame.SourceFrame.Transformer;
161
- private executionLocation: Workspace.UISourceCode.UILocation|null;
162
- private controlDown: boolean;
163
- private asyncStepInHoveredLine: number|null;
164
- private asyncStepInHovered: boolean;
165
- private clearValueWidgetsTimer: number|null;
166
- private sourceMapInfobar: UI.Infobar.Infobar|null;
167
- private controlTimeout: number|null;
160
+ private editor: TextEditor.TextEditor.TextEditor|undefined = undefined;
161
+ private executionLocation: Workspace.UISourceCode.UILocation|null = null;
162
+ private controlDown: boolean = false;
163
+ private sourceMapInfobar: UI.Infobar.Infobar|null = null;
164
+ private controlTimeout: number|undefined = undefined;
168
165
  private readonly scriptsPanel: SourcesPanel;
169
166
  private readonly breakpointManager: Bindings.BreakpointManager.BreakpointManager;
170
167
  private readonly popoverHelper: UI.PopoverHelper.PopoverHelper;
171
- private readonly boundPopoverHelperHide: () => void;
172
- private readonly boundKeyDown: (arg0: Event) => void;
173
- private readonly boundKeyUp: (arg0: Event) => void;
174
- private readonly boundMouseMove: (arg0: Event) => void;
175
- private readonly boundMouseDown: (arg0: Event) => void;
176
- private readonly boundBlur: (arg0: Event) => void;
177
- private readonly boundWheel: (arg0: Event) => void;
178
- private readonly boundGutterClick:
179
- (arg0: Common.EventTarget.EventTargetEvent<SourceFrame.SourcesTextEditor.GutterClickEventData>) => void;
180
- private readonly breakpointDecorations: Set<BreakpointDecoration>;
181
- private readonly decorationByBreakpoint:
182
- Map<Bindings.BreakpointManager.Breakpoint, Map<string, BreakpointDecoration>>;
183
- private readonly possibleBreakpointsRequested: Set<number>;
184
168
  private scriptFileForDebuggerModel:
185
169
  Map<SDK.DebuggerModel.DebuggerModel, Bindings.ResourceScriptMapping.ResourceScriptFile>;
186
- private readonly valueWidgets: Map<number, DecoratorWidget>;
170
+ // The current set of breakpoints for this file. The locations in
171
+ // here are only in sync with editor positions when `this.muted` is
172
+ // false.
173
+ private breakpoints: BreakpointDescription[] = [];
187
174
  // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
188
175
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
189
- private continueToLocationDecorations: Map<any, Function>|null;
176
+ private continueToLocations: {from: number, to: number, async: boolean, click: () => void}[]|null = null;
190
177
  private readonly liveLocationPool: Bindings.LiveLocation.LiveLocationPool;
191
178
  private muted: boolean;
192
- private mutedFromStart: boolean;
179
+ private initializedMuted: boolean;
193
180
  private ignoreListInfobar: UI.Infobar.Infobar|null;
194
- private hasLineWithoutMapping: boolean;
195
181
  private prettyPrintInfobar!: UI.Infobar.Infobar|null;
196
182
  private scheduledBreakpointDecorationUpdates?: Set<BreakpointDecoration>|null;
183
+ private refreshBreakpointsTimeout: undefined|number = undefined;
197
184
 
198
185
  constructor(
199
- textEditor: SourceFrame.SourcesTextEditor.SourcesTextEditor, uiSourceCode: Workspace.UISourceCode.UISourceCode,
200
- transformer: SourceFrame.SourceFrame.Transformer) {
201
- super();
202
- this.textEditor = textEditor;
203
- this.uiSourceCode = uiSourceCode;
204
- this.transformer = transformer;
205
-
206
- this.executionLocation = null;
207
- this.controlDown = false;
208
- this.asyncStepInHoveredLine = 0;
209
- this.asyncStepInHovered = false;
210
- this.clearValueWidgetsTimer = null;
211
- this.sourceMapInfobar = null;
212
- this.controlTimeout = null;
186
+ uiSourceCode: Workspace.UISourceCode.UISourceCode,
187
+ private readonly transformer: SourceFrame.SourceFrame.Transformer) {
188
+ super(uiSourceCode);
213
189
 
214
190
  this.scriptsPanel = SourcesPanel.instance();
215
191
  this.breakpointManager = Bindings.BreakpointManager.BreakpointManager.instance();
216
- if (uiSourceCode.project().type() === Workspace.Workspace.projectTypes.Debugger) {
217
- this.textEditor.element.classList.add('source-frame-debugger-script');
218
- }
219
192
 
220
193
  this.popoverHelper =
221
194
  new UI.PopoverHelper.PopoverHelper(this.scriptsPanel.element, this.getPopoverRequest.bind(this));
222
195
  this.popoverHelper.setDisableOnClick(true);
223
196
  this.popoverHelper.setTimeout(250, 250);
224
197
  this.popoverHelper.setHasPadding(true);
225
- this.boundPopoverHelperHide = this.popoverHelper.hidePopover.bind(this.popoverHelper);
226
- this.scriptsPanel.element.addEventListener('scroll', this.boundPopoverHelperHide, true);
227
-
228
- const shortcutHandlers = {
229
- 'debugger.toggle-breakpoint': async(): Promise<boolean> => {
230
- const selection = this.textEditor.selection();
231
- if (!selection) {
232
- return false;
233
- }
234
- await this.toggleBreakpoint(selection.startLine, false);
235
- return true;
236
- },
237
- 'debugger.toggle-breakpoint-enabled': async(): Promise<boolean> => {
238
- const selection = this.textEditor.selection();
239
- if (!selection) {
240
- return false;
241
- }
242
- await this.toggleBreakpoint(selection.startLine, true);
243
- return true;
244
- },
245
- 'debugger.breakpoint-input-window': async(): Promise<boolean> => {
246
- const selection = this.textEditor.selection();
247
- if (!selection) {
248
- return false;
249
- }
250
- const breakpoints = this.lineBreakpointDecorations(selection.startLine)
251
- .map(decoration => decoration.breakpoint)
252
- .filter(breakpoint => Boolean(breakpoint));
253
- let breakpoint: (Bindings.BreakpointManager.Breakpoint|null)|null = null;
254
- if (breakpoints.length) {
255
- breakpoint = breakpoints[0];
256
- }
257
- const isLogpoint = breakpoint ? breakpoint.condition().includes(LogpointPrefix) : false;
258
- this.editBreakpointCondition(selection.startLine, breakpoint, null, isLogpoint);
259
- return true;
260
- },
261
- };
262
- UI.ShortcutRegistry.ShortcutRegistry.instance().addShortcutListener(this.textEditor.element, shortcutHandlers);
263
- this.boundKeyDown = (this.onKeyDown.bind(this) as (arg0: Event) => void);
264
- this.textEditor.element.addEventListener('keydown', this.boundKeyDown, true);
265
- this.boundKeyUp = (this.onKeyUp.bind(this) as (arg0: Event) => void);
266
- this.textEditor.element.addEventListener('keyup', this.boundKeyUp, true);
267
- this.boundMouseMove = (this.onMouseMove.bind(this) as (arg0: Event) => void);
268
- this.textEditor.element.addEventListener('mousemove', this.boundMouseMove, false);
269
- this.boundMouseDown = (this.onMouseDown.bind(this) as (arg0: Event) => void);
270
- this.textEditor.element.addEventListener('mousedown', this.boundMouseDown, true);
271
- this.boundBlur = (this.onBlur.bind(this) as (arg0: Event) => void);
272
- this.textEditor.element.addEventListener('focusout', this.boundBlur, false);
273
- this.boundWheel = (this.onWheel.bind(this) as (arg0: Event) => void);
274
- this.textEditor.element.addEventListener('wheel', this.boundWheel, true);
275
- this.boundGutterClick =
276
- (e: Common.EventTarget.EventTargetEvent<SourceFrame.SourcesTextEditor.GutterClickEventData>): void => {
277
- this.handleGutterClick(e);
278
- };
279
-
280
- this.textEditor.addEventListener(SourceFrame.SourcesTextEditor.Events.GutterClick, this.boundGutterClick, this);
281
198
 
282
199
  this.breakpointManager.addEventListener(
283
- Bindings.BreakpointManager.Events.BreakpointAdded, this.breakpointAdded, this);
200
+ Bindings.BreakpointManager.Events.BreakpointAdded, this.breakpointChange, this);
284
201
  this.breakpointManager.addEventListener(
285
- Bindings.BreakpointManager.Events.BreakpointRemoved, this.breakpointRemoved, this);
202
+ Bindings.BreakpointManager.Events.BreakpointRemoved, this.breakpointChange, this);
286
203
 
287
204
  this.uiSourceCode.addEventListener(Workspace.UISourceCode.Events.WorkingCopyChanged, this.workingCopyChanged, this);
288
205
  this.uiSourceCode.addEventListener(
289
206
  Workspace.UISourceCode.Events.WorkingCopyCommitted, this.workingCopyCommitted, this);
290
207
 
291
- this.breakpointDecorations = new Set();
292
- this.decorationByBreakpoint = new Map();
293
- this.possibleBreakpointsRequested = new Set();
294
-
295
208
  this.scriptFileForDebuggerModel = new Map();
296
209
 
297
210
  Common.Settings.Settings.instance()
@@ -301,23 +214,13 @@ export class DebuggerPlugin extends Plugin {
301
214
  .moduleSetting('skipContentScripts')
302
215
  .addChangeListener(this.showIgnoreListInfobarIfNeeded, this);
303
216
 
304
- this.valueWidgets = new Map();
305
- this.continueToLocationDecorations = null;
306
-
307
217
  UI.Context.Context.instance().addFlavorChangeListener(SDK.DebuggerModel.CallFrame, this.callFrameChanged, this);
308
218
  this.liveLocationPool = new Bindings.LiveLocation.LiveLocationPool();
309
- this.callFrameChanged();
310
219
 
311
220
  this.updateScriptFiles();
312
221
 
313
- if (this.uiSourceCode.isDirty()) {
314
- this.muted = true;
315
- this.mutedFromStart = true;
316
- } else {
317
- this.muted = false;
318
- this.mutedFromStart = false;
319
- this.initializeBreakpoints();
320
- }
222
+ this.muted = this.uiSourceCode.isDirty();
223
+ this.initializedMuted = this.muted;
321
224
 
322
225
  this.ignoreListInfobar = null;
323
226
  this.showIgnoreListInfobarIfNeeded();
@@ -326,14 +229,98 @@ export class DebuggerPlugin extends Plugin {
326
229
  scriptFile.checkMapping();
327
230
  }
328
231
 
329
- this.hasLineWithoutMapping = false;
330
- this.updateLinesWithoutMappingHighlight();
331
232
  if (!Root.Runtime.experiments.isEnabled('sourcesPrettyPrint')) {
332
233
  this.prettyPrintInfobar = null;
333
234
  this.detectMinified();
334
235
  }
335
236
  }
336
237
 
238
+ editorExtension(): CodeMirror.Extension {
239
+ const handlers = this.shortcutHandlers();
240
+
241
+ return [
242
+ CodeMirror.EditorView.updateListener.of(update => this.onEditorUpdate(update)),
243
+ CodeMirror.EditorView.domEventHandlers({
244
+ keydown: (event): boolean => {
245
+ if (this.onKeyDown(event)) {
246
+ return true;
247
+ }
248
+ handlers(event);
249
+ return event.defaultPrevented;
250
+ },
251
+ keyup: event => this.onKeyUp(event),
252
+ mousemove: event => this.onMouseMove(event),
253
+ mousedown: event => this.onMouseDown(event),
254
+ focusout: event => this.onBlur(event),
255
+ wheel: event => this.onWheel(event),
256
+ }),
257
+ CodeMirror.lineNumbers({
258
+ domEventHandlers: {
259
+ mousedown: (view, block, event) =>
260
+ this.handleGutterClick(view.state.doc.lineAt(block.from), event as MouseEvent),
261
+ },
262
+ }),
263
+ infobarState,
264
+ breakpointMarkers,
265
+ CodeMirror.Prec.highest(executionLine.field),
266
+ CodeMirror.Prec.lowest(continueToMarkers.field),
267
+ markIfContinueTo,
268
+ valueDecorations.field,
269
+ CodeMirror.Prec.lowest(evalExpression.field),
270
+ theme,
271
+ this.uiSourceCode.project().type() === Workspace.Workspace.projectTypes.Debugger ?
272
+ CodeMirror.EditorView.editorAttributes.of({class: 'source-frame-debugger-script'}) :
273
+ [],
274
+ ];
275
+ }
276
+
277
+ private shortcutHandlers(): (event: KeyboardEvent) => void {
278
+ const selectionLine = (editor: TextEditor.TextEditor.TextEditor): CodeMirror.Line => {
279
+ return editor.state.doc.lineAt(editor.state.selection.main.head);
280
+ };
281
+
282
+ return UI.ShortcutRegistry.ShortcutRegistry.instance().getShortcutListener({
283
+ 'debugger.toggle-breakpoint': async(): Promise<boolean> => {
284
+ if (this.muted || !this.editor) {
285
+ return false;
286
+ }
287
+ await this.toggleBreakpoint(selectionLine(this.editor), false);
288
+ return true;
289
+ },
290
+ 'debugger.toggle-breakpoint-enabled': async(): Promise<boolean> => {
291
+ if (this.muted || !this.editor) {
292
+ return false;
293
+ }
294
+ await this.toggleBreakpoint(selectionLine(this.editor), true);
295
+ return true;
296
+ },
297
+ 'debugger.breakpoint-input-window': async(): Promise<boolean> => {
298
+ if (this.muted || !this.editor) {
299
+ return false;
300
+ }
301
+ const line = selectionLine(this.editor);
302
+ const breakpoint =
303
+ this.breakpoints.find(b => b.position >= line.from && b.position <= line.to)?.breakpoint || null;
304
+ const isLogpoint = breakpoint ? breakpoint.condition().includes(LogpointPrefix) : false;
305
+ this.editBreakpointCondition(line, breakpoint, null, isLogpoint);
306
+ return true;
307
+ },
308
+ });
309
+ }
310
+
311
+ editorInitialized(editor: TextEditor.TextEditor.TextEditor): void {
312
+ this.editor = editor;
313
+ computeNonBreakableLines(editor.state, this.uiSourceCode).then(linePositions => {
314
+ if (linePositions.length) {
315
+ editor.dispatch({effects: SourceFrame.SourceFrame.addNonBreakableLines.of(linePositions)});
316
+ }
317
+ }, console.error);
318
+ if (!this.muted) {
319
+ this.refreshBreakpoints();
320
+ }
321
+ this.callFrameChanged();
322
+ }
323
+
337
324
  static accepts(uiSourceCode: Workspace.UISourceCode.UISourceCode): boolean {
338
325
  return uiSourceCode.contentType().hasScripts();
339
326
  }
@@ -372,6 +359,7 @@ export class DebuggerPlugin extends Plugin {
372
359
  },
373
360
  ]);
374
361
  this.ignoreListInfobar = infobar;
362
+ infobar.setCloseCallback(() => this.removeInfobar(this.ignoreListInfobar));
375
363
 
376
364
  infobar.createDetailsRowMessage(i18nString(UIStrings.theDebuggerWillSkipStepping));
377
365
 
@@ -380,7 +368,19 @@ export class DebuggerPlugin extends Plugin {
380
368
  if (scriptFile && scriptFile.hasSourceMapURL()) {
381
369
  infobar.createDetailsRowMessage(i18nString(UIStrings.sourceMapFoundButIgnoredForFile));
382
370
  }
383
- this.textEditor.attachInfobar(this.ignoreListInfobar);
371
+ this.attachInfobar(this.ignoreListInfobar);
372
+ }
373
+
374
+ attachInfobar(bar: UI.Infobar.Infobar): void {
375
+ if (this.editor) {
376
+ this.editor.dispatch({effects: addInfobar.of(bar)});
377
+ }
378
+ }
379
+
380
+ removeInfobar(bar: UI.Infobar.Infobar|null): void {
381
+ if (this.editor && bar) {
382
+ this.editor.dispatch({effects: removeInfobar.of(bar)});
383
+ }
384
384
  }
385
385
 
386
386
  private hideIgnoreListInfobar(): void {
@@ -391,73 +391,64 @@ export class DebuggerPlugin extends Plugin {
391
391
  this.ignoreListInfobar = null;
392
392
  }
393
393
 
394
- wasShown(): void {
395
- if (this.executionLocation) {
396
- // We need SourcesTextEditor to be initialized prior to this call. @see crbug.com/499889
397
- queueMicrotask(() => {
398
- this.generateValuesInSource();
399
- });
400
- }
401
- }
402
-
403
394
  willHide(): void {
404
395
  this.popoverHelper.hidePopover();
405
396
  }
406
397
 
407
- async populateLineGutterContextMenu(contextMenu: UI.ContextMenu.ContextMenu, editorLineNumber: number):
408
- Promise<void> {
398
+ populateLineGutterContextMenu(contextMenu: UI.ContextMenu.ContextMenu, editorLineNumber: number): void {
409
399
  const uiLocation = new Workspace.UISourceCode.UILocation(this.uiSourceCode, editorLineNumber, 0);
410
400
  this.scriptsPanel.appendUILocationItems(contextMenu, uiLocation);
411
- const breakpoints =
412
- (this.lineBreakpointDecorations(editorLineNumber)
413
- .map(decoration => decoration.breakpoint)
414
- .filter(breakpoint => Boolean(breakpoint)) as Bindings.BreakpointManager.Breakpoint[]);
401
+ if (this.muted || !this.editor) {
402
+ return;
403
+ }
404
+ const line = this.editor.state.doc.line(editorLineNumber + 1);
405
+ const breakpoints = this.lineBreakpoints(line);
415
406
  const supportsConditionalBreakpoints =
416
407
  Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance().supportsConditionalBreakpoints(
417
408
  this.uiSourceCode);
418
409
  if (!breakpoints.length) {
419
- if (!this.textEditor.hasLineClass(editorLineNumber, 'cm-non-breakable-line')) {
410
+ if (this.editor && SourceFrame.SourceFrame.isBreakableLine(this.editor.state, line)) {
420
411
  contextMenu.debugSection().appendItem(
421
- i18nString(UIStrings.addBreakpoint), this.createNewBreakpoint.bind(this, editorLineNumber, '', true));
412
+ i18nString(UIStrings.addBreakpoint), this.createNewBreakpoint.bind(this, line, '', true));
422
413
  if (supportsConditionalBreakpoints) {
423
414
  contextMenu.debugSection().appendItem(
424
415
  i18nString(UIStrings.addConditionalBreakpoint),
425
- this.editBreakpointCondition.bind(this, editorLineNumber, null, null, false /* preferLogpoint */));
416
+ this.editBreakpointCondition.bind(this, line, null, null, false /* preferLogpoint */));
426
417
  contextMenu.debugSection().appendItem(
427
418
  i18nString(UIStrings.addLogpoint),
428
- this.editBreakpointCondition.bind(this, editorLineNumber, null, null, true /* preferLogpoint */));
419
+ this.editBreakpointCondition.bind(this, line, null, null, true /* preferLogpoint */));
429
420
  contextMenu.debugSection().appendItem(
430
- i18nString(UIStrings.neverPauseHere),
431
- this.createNewBreakpoint.bind(this, editorLineNumber, 'false', true));
421
+ i18nString(UIStrings.neverPauseHere), this.createNewBreakpoint.bind(this, line, 'false', true));
432
422
  }
433
423
  }
434
424
  } else {
435
425
  const removeTitle = i18nString(UIStrings.removeBreakpoint, {n: breakpoints.length});
436
- contextMenu.debugSection().appendItem(removeTitle, () => breakpoints.map(breakpoint => breakpoint.remove(false)));
426
+ contextMenu.debugSection().appendItem(
427
+ removeTitle, () => breakpoints.forEach(breakpoint => breakpoint.remove(false)));
437
428
  if (breakpoints.length === 1 && supportsConditionalBreakpoints) {
438
429
  // Editing breakpoints only make sense for conditional breakpoints
439
430
  // and logpoints and both are currently only available for JavaScript
440
431
  // debugging.
441
432
  contextMenu.debugSection().appendItem(
442
433
  i18nString(UIStrings.editBreakpoint),
443
- this.editBreakpointCondition.bind(
444
- this, editorLineNumber, breakpoints[0], null, false /* preferLogpoint */));
434
+ this.editBreakpointCondition.bind(this, line, breakpoints[0], null, false /* preferLogpoint */));
445
435
  }
446
436
  const hasEnabled = breakpoints.some(breakpoint => breakpoint.enabled());
447
437
  if (hasEnabled) {
448
438
  const title = i18nString(UIStrings.disableBreakpoint, {n: breakpoints.length});
449
- contextMenu.debugSection().appendItem(title, () => breakpoints.map(breakpoint => breakpoint.setEnabled(false)));
439
+ contextMenu.debugSection().appendItem(
440
+ title, () => breakpoints.forEach(breakpoint => breakpoint.setEnabled(false)));
450
441
  }
451
442
  const hasDisabled = breakpoints.some(breakpoint => !breakpoint.enabled());
452
443
  if (hasDisabled) {
453
444
  const title = i18nString(UIStrings.enableBreakpoint, {n: breakpoints.length});
454
- contextMenu.debugSection().appendItem(title, () => breakpoints.map(breakpoint => breakpoint.setEnabled(true)));
445
+ contextMenu.debugSection().appendItem(
446
+ title, () => breakpoints.forEach(breakpoint => breakpoint.setEnabled(true)));
455
447
  }
456
448
  }
457
449
  }
458
450
 
459
- populateTextAreaContextMenu(
460
- contextMenu: UI.ContextMenu.ContextMenu, editorLineNumber: number, editorColumnNumber: number): Promise<void> {
451
+ populateTextAreaContextMenu(contextMenu: UI.ContextMenu.ContextMenu): void {
461
452
  function addSourceMapURL(scriptFile: Bindings.ResourceScriptMapping.ResourceScriptFile): void {
462
453
  const dialog = new AddSourceMapURLDialog(addSourceMapURLDialogCallback.bind(null, scriptFile));
463
454
  dialog.show();
@@ -471,95 +462,66 @@ export class DebuggerPlugin extends Plugin {
471
462
  scriptFile.addSourceMapURL(url);
472
463
  }
473
464
 
474
- function populateSourceMapMembers(this: DebuggerPlugin): void {
475
- if (this.uiSourceCode.project().type() === Workspace.Workspace.projectTypes.Network &&
476
- Common.Settings.Settings.instance().moduleSetting('jsSourceMapsEnabled').get() &&
477
- !Bindings.IgnoreListManager.IgnoreListManager.instance().isIgnoreListedUISourceCode(this.uiSourceCode)) {
478
- if (this.scriptFileForDebuggerModel.size) {
479
- const scriptFile = this.scriptFileForDebuggerModel.values().next().value;
480
- const addSourceMapURLLabel = i18nString(UIStrings.addSourceMap);
481
- contextMenu.debugSection().appendItem(addSourceMapURLLabel, addSourceMapURL.bind(null, scriptFile));
482
- }
465
+ if (this.uiSourceCode.project().type() === Workspace.Workspace.projectTypes.Network &&
466
+ Common.Settings.Settings.instance().moduleSetting('jsSourceMapsEnabled').get() &&
467
+ !Bindings.IgnoreListManager.IgnoreListManager.instance().isIgnoreListedUISourceCode(this.uiSourceCode)) {
468
+ if (this.scriptFileForDebuggerModel.size) {
469
+ const scriptFile = this.scriptFileForDebuggerModel.values().next().value;
470
+ const addSourceMapURLLabel = i18nString(UIStrings.addSourceMap);
471
+ contextMenu.debugSection().appendItem(addSourceMapURLLabel, addSourceMapURL.bind(null, scriptFile));
483
472
  }
484
473
  }
485
-
486
- return super.populateTextAreaContextMenu(contextMenu, editorLineNumber, editorColumnNumber)
487
- .then(populateSourceMapMembers.bind(this));
488
474
  }
489
475
 
490
476
  private workingCopyChanged(): void {
491
- if (this.scriptFileForDebuggerModel.size) {
492
- return;
493
- }
494
-
495
- if (this.uiSourceCode.isDirty()) {
496
- this.muteBreakpointsWhileEditing();
497
- } else {
498
- this.restoreBreakpointsAfterEditing();
477
+ if (!this.scriptFileForDebuggerModel.size) {
478
+ this.setMuted(this.uiSourceCode.isDirty());
499
479
  }
500
480
  }
501
481
 
502
482
  private workingCopyCommitted(): void {
503
483
  this.scriptsPanel.updateLastModificationTime();
504
484
  if (!this.scriptFileForDebuggerModel.size) {
505
- this.restoreBreakpointsAfterEditing();
485
+ this.setMuted(false);
506
486
  }
507
487
  }
508
488
 
509
489
  private didMergeToVM(): void {
510
- this.restoreBreakpointsIfConsistentScripts();
490
+ if (this.consistentScripts()) {
491
+ this.setMuted(false);
492
+ }
511
493
  }
512
494
 
513
495
  private didDivergeFromVM(): void {
514
- this.muteBreakpointsWhileEditing();
496
+ this.setMuted(true);
515
497
  }
516
498
 
517
- private muteBreakpointsWhileEditing(): void {
518
- if (this.muted) {
499
+ private setMuted(value: boolean): void {
500
+ if (this.initializedMuted) {
519
501
  return;
520
502
  }
521
- for (const decoration of this.breakpointDecorations) {
522
- this.updateBreakpointDecoration(decoration);
503
+ if (value !== this.muted) {
504
+ this.muted = value;
505
+ if (!value) {
506
+ this.restoreBreakpointsAfterEditing();
507
+ } else if (this.editor) {
508
+ this.editor.dispatch({effects: muteBreakpoints.of(null)});
509
+ }
523
510
  }
524
- this.muted = true;
525
511
  }
526
512
 
527
- private async restoreBreakpointsIfConsistentScripts(): Promise<void> {
513
+ private consistentScripts(): boolean {
528
514
  for (const scriptFile of this.scriptFileForDebuggerModel.values()) {
529
515
  if (scriptFile.hasDivergedFromVM() || scriptFile.isMergingToVM()) {
530
- return;
531
- }
532
- }
533
-
534
- await this.restoreBreakpointsAfterEditing();
535
- }
536
-
537
- private async restoreBreakpointsAfterEditing(): Promise<void> {
538
- this.muted = false;
539
- if (this.mutedFromStart) {
540
- this.mutedFromStart = false;
541
- this.initializeBreakpoints();
542
- return;
543
- }
544
- const decorations = Array.from(this.breakpointDecorations);
545
- this.breakpointDecorations.clear();
546
- this.textEditor.operation(() => decorations.map(decoration => decoration.hide()));
547
- for (const decoration of decorations) {
548
- if (!decoration.breakpoint) {
549
- continue;
550
- }
551
- const enabled = decoration.enabled;
552
- decoration.breakpoint.remove(false);
553
- const location = decoration.handle.resolve();
554
- if (location) {
555
- await this.setBreakpoint(location.lineNumber, location.columnNumber, decoration.condition, enabled);
516
+ return false;
556
517
  }
557
518
  }
519
+ return true;
558
520
  }
559
521
 
560
522
  private isIdentifier(tokenType: string): boolean {
561
- return tokenType.startsWith('js-variable') || tokenType.startsWith('js-property') || tokenType === 'js-def' ||
562
- tokenType === 'variable';
523
+ return tokenType === 'VariableName' || tokenType === 'VariableDefinition' || tokenType === 'PropertyName' ||
524
+ tokenType === 'PropertyDefinition';
563
525
  }
564
526
 
565
527
  private getPopoverRequest(event: MouseEvent): UI.PopoverHelper.PopoverRequest|null {
@@ -568,161 +530,98 @@ export class DebuggerPlugin extends Plugin {
568
530
  }
569
531
  const target = UI.Context.Context.instance().flavor(SDK.Target.Target);
570
532
  const debuggerModel = target ? target.model(SDK.DebuggerModel.DebuggerModel) : null;
571
- if (!debuggerModel || !debuggerModel.isPaused()) {
572
- return null;
573
- }
574
-
575
- const textPosition = this.textEditor.coordinatesToCursorPosition(event.x, event.y);
576
- if (!textPosition) {
533
+ const {editor} = this;
534
+ if (!debuggerModel || !debuggerModel.isPaused() || !editor) {
577
535
  return null;
578
536
  }
579
537
 
580
- const mouseLine = textPosition.startLine;
581
- const mouseColumn = textPosition.startColumn;
582
- const textSelection = this.textEditor.selection().normalize();
583
- let editorLineNumber = -1;
584
- let startHighlight = -1;
585
- let endHighlight = -1;
586
-
587
538
  const selectedCallFrame =
588
539
  (UI.Context.Context.instance().flavor(SDK.DebuggerModel.CallFrame) as SDK.DebuggerModel.CallFrame);
589
540
  if (!selectedCallFrame) {
590
541
  return null;
591
542
  }
592
543
 
593
- if (textSelection && !textSelection.isEmpty()) {
594
- if (textSelection.startLine !== textSelection.endLine || textSelection.startLine !== mouseLine ||
595
- mouseColumn < textSelection.startColumn || mouseColumn > textSelection.endColumn) {
544
+ let textPosition = editor.editor.posAtCoords(event);
545
+ if (!textPosition) {
546
+ return null;
547
+ }
548
+ const positionCoords = editor.editor.coordsAtPos(textPosition);
549
+ if (!positionCoords || event.clientY < positionCoords.top || event.clientY > positionCoords.bottom ||
550
+ event.clientX < positionCoords.left - 30 || event.clientX > positionCoords.right + 30) {
551
+ return null;
552
+ }
553
+ if (event.clientX < positionCoords.left && textPosition > editor.state.doc.lineAt(textPosition).from) {
554
+ textPosition -= 1;
555
+ }
556
+
557
+ const textSelection = editor.state.selection.main;
558
+ let highlightRange: {from: number, to: number};
559
+
560
+ if (!textSelection.empty) {
561
+ if (textPosition < textSelection.from || textPosition > textSelection.to) {
596
562
  return null;
597
563
  }
598
- editorLineNumber = textSelection.startLine;
599
- startHighlight = textSelection.startColumn;
600
- endHighlight = textSelection.endColumn - 1;
564
+ highlightRange = textSelection;
601
565
  } else if (this.uiSourceCode.mimeType() === 'application/wasm') {
602
- const token = this.textEditor.tokenAtTextPosition(textPosition.startLine, textPosition.startColumn);
603
- if (!token || token.type !== 'variable-2') {
566
+ const node = CodeMirror.syntaxTree(editor.state).resolveInner(textPosition, 1);
567
+ if (node.name !== 'Identifier') {
604
568
  return null;
605
569
  }
606
- editorLineNumber = textPosition.startLine;
607
- startHighlight = token.startColumn;
608
- endHighlight = token.endColumn - 1;
609
-
610
570
  // For $label identifiers we can't show a meaningful preview (https://crbug.com/1155548),
611
571
  // so we suppress them for now. Label identifiers can only appear as operands to control
612
- // instructions[1], so we just check the first token on the line and filter them out.
572
+ // instructions[1].
613
573
  //
614
574
  // [1]: https://webassembly.github.io/spec/core/text/instructions.html#control-instructions
615
- for (let firstColumn = 0; firstColumn < startHighlight; ++firstColumn) {
616
- const firstToken = this.textEditor.tokenAtTextPosition(editorLineNumber, firstColumn);
617
- if (firstToken && firstToken.type === 'keyword') {
618
- const line = this.textEditor.line(editorLineNumber);
619
- switch (line.substring(firstToken.startColumn, firstToken.endColumn)) {
620
- case 'block':
621
- case 'loop':
622
- case 'if':
623
- case 'else':
624
- case 'end':
625
- case 'br':
626
- case 'br_if':
627
- case 'br_table':
628
- return null;
629
- default:
630
- break;
575
+ const controlInstructions = ['block', 'loop', 'if', 'else', 'end', 'br', 'br_if', 'br_table'];
576
+ for (let parent: CodeMirror.SyntaxNode|null = node.parent; parent; parent = parent.parent) {
577
+ if (parent.name === 'App') {
578
+ const firstChild = parent.firstChild;
579
+ const opName = firstChild?.name === 'Keyword' && editor.state.sliceDoc(firstChild.from, firstChild.to);
580
+ if (opName && controlInstructions.includes(opName)) {
581
+ return null;
631
582
  }
632
- break;
633
583
  }
634
584
  }
635
- } else {
636
- let token = this.textEditor.tokenAtTextPosition(textPosition.startLine, textPosition.startColumn);
637
- if (!token) {
585
+ highlightRange = node;
586
+ } else if (/^text\/(javascript|typescript|jsx)/.test(this.uiSourceCode.mimeType())) {
587
+ let node: CodeMirror.SyntaxNode|null = CodeMirror.syntaxTree(editor.state).resolveInner(textPosition, 1);
588
+ // Only do something if the cursor is over a leaf node.
589
+ if (node.firstChild) {
638
590
  return null;
639
591
  }
640
- editorLineNumber = textPosition.startLine;
641
- const line = this.textEditor.line(editorLineNumber);
642
- let tokenContent = line.substring(token.startColumn, token.endColumn);
643
-
644
- // When the user hovers an opening bracket, we look for the closing bracket
645
- // and kick off the matching from that below.
646
- if (tokenContent === '[') {
647
- const closingColumn = line.indexOf(']', token.startColumn);
648
- if (closingColumn < 0) {
649
- return null;
650
- }
651
- token = this.textEditor.tokenAtTextPosition(editorLineNumber, closingColumn);
652
- if (!token) {
653
- return null;
654
- }
655
- tokenContent = line.substring(token.startColumn, token.endColumn);
656
- }
657
- startHighlight = token.startColumn;
658
- endHighlight = token.endColumn - 1;
659
-
660
- // Consume multiple `[index][0]...[f(1)]` at the end of the expression.
661
- while (tokenContent === ']') {
662
- startHighlight = line.lastIndexOf('[', startHighlight) - 1;
663
- if (startHighlight < 0) {
664
- return null;
665
- }
666
- token = this.textEditor.tokenAtTextPosition(editorLineNumber, startHighlight);
667
- if (!token) {
668
- return null;
669
- }
670
- tokenContent = line.substring(token.startColumn, token.endColumn);
671
- startHighlight = token.startColumn;
592
+ while (node && node.name !== 'VariableDefinition' && node.name !== 'VariableName' &&
593
+ node.name !== 'MemberExpression') {
594
+ node = node.parent;
672
595
  }
673
-
674
- if (!token.type) {
596
+ if (!node) {
675
597
  return null;
676
598
  }
677
- const isIdentifier = this.isIdentifier(token.type);
678
- if (!isIdentifier && (token.type !== 'js-keyword' || tokenContent !== 'this')) {
599
+ highlightRange = node;
600
+ } else {
601
+ // In other languages, just assume a token consisting entirely
602
+ // of identifier-like characters is an identifier.
603
+ const node: CodeMirror.SyntaxNode = CodeMirror.syntaxTree(editor.state).resolveInner(textPosition, 1);
604
+ if (node.to - node.from > 50 || /[^\w_\-$]/.test(editor.state.sliceDoc(node.from, node.to))) {
679
605
  return null;
680
606
  }
607
+ highlightRange = node;
608
+ }
681
609
 
682
- while (startHighlight > 1 && line.charAt(startHighlight - 1) === '.') {
683
- // Consume multiple `[index][0]...[f(1)]` preceeding a dot.
684
- while (line.charAt(startHighlight - 2) === ']') {
685
- startHighlight = line.lastIndexOf('[', startHighlight - 2) - 1;
686
- if (startHighlight < 0) {
687
- return null;
688
- }
689
- }
690
- const tokenBefore = this.textEditor.tokenAtTextPosition(editorLineNumber, startHighlight - 2);
691
- if (!tokenBefore || !tokenBefore.type) {
692
- return null;
693
- }
694
- if (tokenBefore.type === 'js-meta') {
695
- break;
696
- }
697
- if (tokenBefore.type === 'js-string-2') {
698
- // If we hit a template literal, find the opening ` in this line.
699
- // TODO(bmeurer): We should eventually replace this tokenization
700
- // approach with a proper soluation based on parsing, maybe reusing
701
- // the Parser and AST inside V8 for this (or potentially relying on
702
- // acorn to do the job).
703
- if (tokenBefore.endColumn < 2) {
704
- return null;
705
- }
706
- startHighlight = line.lastIndexOf('`', tokenBefore.endColumn - 2);
707
- if (startHighlight < 0) {
708
- return null;
709
- }
710
- break;
711
- }
712
- startHighlight = tokenBefore.startColumn;
713
- }
610
+ const highlightLine = editor.state.doc.lineAt(highlightRange.from);
611
+ if (highlightRange.to > highlightLine.to) {
612
+ return null;
714
613
  }
715
614
 
716
- const leftCorner =
717
- (this.textEditor.cursorPositionToCoordinates(editorLineNumber, startHighlight) as
718
- TextEditor.CodeMirrorTextEditor.Coordinates);
719
- const rightCorner =
720
- (this.textEditor.cursorPositionToCoordinates(editorLineNumber, endHighlight) as
721
- TextEditor.CodeMirrorTextEditor.Coordinates);
722
- const box = new AnchorBox(leftCorner.x, leftCorner.y, rightCorner.x - leftCorner.x, leftCorner.height);
615
+ const leftCorner = editor.editor.coordsAtPos(highlightRange.from);
616
+ const rightCorner = editor.editor.coordsAtPos(highlightRange.to);
617
+ if (!leftCorner || !rightCorner) {
618
+ return null;
619
+ }
620
+ const box = new AnchorBox(
621
+ leftCorner.left, leftCorner.top - 2, rightCorner.right - leftCorner.left, rightCorner.bottom - leftCorner.top);
622
+ const evaluationText = editor.state.sliceDoc(highlightRange.from, highlightRange.to);
723
623
 
724
624
  let objectPopoverHelper: ObjectUI.ObjectPopoverHelper.ObjectPopoverHelper|null = null;
725
- let highlightDescriptor: CodeMirror.TextMarker|null = null;
726
625
 
727
626
  async function evaluate(uiSourceCode: Workspace.UISourceCode.UISourceCode, evaluationText: string): Promise<{
728
627
  object: SDK.RemoteObject.RemoteObject,
@@ -731,7 +630,8 @@ export class DebuggerPlugin extends Plugin {
731
630
  error: string,
732
631
  }|null> {
733
632
  const resolvedText = await resolveExpression(
734
- selectedCallFrame, evaluationText, uiSourceCode, editorLineNumber, startHighlight, endHighlight);
633
+ selectedCallFrame, evaluationText, uiSourceCode, highlightLine.number - 1,
634
+ highlightRange.from - highlightLine.from, highlightRange.to - highlightLine.from);
735
635
  return await selectedCallFrame.evaluate({
736
636
  expression: resolvedText || evaluationText,
737
637
  objectGroup: 'popover',
@@ -750,9 +650,7 @@ export class DebuggerPlugin extends Plugin {
750
650
  return {
751
651
  box,
752
652
  show: async(popover: UI.GlassPane.GlassPane): Promise<boolean> => {
753
- const evaluationText = this.textEditor.line(editorLineNumber).substring(startHighlight, endHighlight + 1);
754
653
  const result = await evaluate(this.uiSourceCode, evaluationText);
755
-
756
654
  if (!result || 'error' in result || !result.object ||
757
655
  (result.object.type === 'object' && result.object.subtype === 'error')) {
758
656
  return false;
@@ -767,9 +665,8 @@ export class DebuggerPlugin extends Plugin {
767
665
  }
768
666
  return false;
769
667
  }
770
- const highlightRange =
771
- new TextUtils.TextRange.TextRange(editorLineNumber, startHighlight, editorLineNumber, endHighlight);
772
- highlightDescriptor = this.textEditor.highlightRange(highlightRange, 'source-frame-eval-expression');
668
+ const decoration = CodeMirror.Decoration.set(evalExpressionMark.range(highlightRange.from, highlightRange.to));
669
+ editor.dispatch({effects: evalExpression.update.of(decoration)});
773
670
  return true;
774
671
  },
775
672
  hide: (): void => {
@@ -777,379 +674,389 @@ export class DebuggerPlugin extends Plugin {
777
674
  objectPopoverHelper.dispose();
778
675
  }
779
676
  debuggerModel.runtimeModel().releaseObjectGroup('popover');
780
- if (highlightDescriptor) {
781
- this.textEditor.removeHighlight(highlightDescriptor);
782
- }
677
+ editor.dispatch({effects: evalExpression.update.of(CodeMirror.Decoration.none)});
783
678
  },
784
679
  };
785
680
  }
786
681
 
682
+ private onEditorUpdate(update: CodeMirror.ViewUpdate): void {
683
+ if (!update.changes.empty) {
684
+ for (const breakpointDesc of this.breakpoints) {
685
+ breakpointDesc.position = update.changes.mapPos(breakpointDesc.position);
686
+ }
687
+ }
688
+ }
689
+
787
690
  private onWheel(event: WheelEvent): void {
788
691
  if (this.executionLocation && UI.KeyboardShortcut.KeyboardShortcut.eventHasCtrlEquivalentKey(event)) {
789
692
  event.preventDefault();
790
693
  }
791
694
  }
792
695
 
793
- private onKeyDown(event: KeyboardEvent): void {
794
- if (!event.ctrlKey || (!event.metaKey && Host.Platform.isMac())) {
795
- this.clearControlDown();
696
+ private onKeyDown(event: KeyboardEvent): boolean {
697
+ const ctrlDown = UI.KeyboardShortcut.KeyboardShortcut.eventHasCtrlEquivalentKey(event);
698
+ if (!ctrlDown) {
699
+ this.setControlDown(false);
796
700
  }
797
-
798
701
  if (event.key === Platform.KeyboardUtilities.ESCAPE_KEY) {
799
702
  if (this.popoverHelper.isPopoverVisible()) {
800
703
  this.popoverHelper.hidePopover();
801
704
  event.consume();
802
705
  }
803
- return;
706
+ return true;
804
707
  }
805
-
806
- if (UI.KeyboardShortcut.KeyboardShortcut.eventHasCtrlEquivalentKey(event) && this.executionLocation) {
807
- this.controlDown = true;
808
- if (event.key === (Host.Platform.isMac() ? 'Meta' : 'Control')) {
809
- this.controlTimeout = window.setTimeout(() => {
810
- if (this.executionLocation && this.controlDown) {
811
- this.showContinueToLocations();
812
- }
813
- }, 150);
814
- }
708
+ if (ctrlDown && this.executionLocation) {
709
+ this.setControlDown(true);
815
710
  }
711
+ return false;
816
712
  }
817
713
 
818
714
  private onMouseMove(event: MouseEvent): void {
819
715
  if (this.executionLocation && this.controlDown &&
820
716
  UI.KeyboardShortcut.KeyboardShortcut.eventHasCtrlEquivalentKey(event)) {
821
- if (!this.continueToLocationDecorations) {
717
+ if (!this.continueToLocations) {
822
718
  this.showContinueToLocations();
823
719
  }
824
720
  }
825
- if (this.continueToLocationDecorations) {
826
- const target = (event.target as Element);
827
- const textPosition = this.textEditor.coordinatesToCursorPosition(event.x, event.y);
828
- const hovering = Boolean(target.enclosingNodeOrSelfWithClass('source-frame-async-step-in'));
829
- this.setAsyncStepInHoveredLine(textPosition ? textPosition.startLine : null, hovering);
830
- }
831
- }
832
-
833
- private setAsyncStepInHoveredLine(editorLineNumber: number|null, hovered: boolean): void {
834
- if (this.asyncStepInHoveredLine === editorLineNumber && this.asyncStepInHovered === hovered) {
835
- return;
836
- }
837
- if (this.asyncStepInHovered && this.asyncStepInHoveredLine) {
838
- this.textEditor.toggleLineClass(this.asyncStepInHoveredLine, 'source-frame-async-step-in-hovered', false);
839
- }
840
- this.asyncStepInHoveredLine = editorLineNumber;
841
- this.asyncStepInHovered = hovered;
842
- if (this.asyncStepInHovered && this.asyncStepInHoveredLine) {
843
- this.textEditor.toggleLineClass(this.asyncStepInHoveredLine, 'source-frame-async-step-in-hovered', true);
844
- }
845
721
  }
846
722
 
847
723
  private onMouseDown(event: MouseEvent): void {
848
724
  if (!this.executionLocation || !UI.KeyboardShortcut.KeyboardShortcut.eventHasCtrlEquivalentKey(event)) {
849
725
  return;
850
726
  }
851
- if (!this.continueToLocationDecorations) {
727
+ if (!this.continueToLocations || !this.editor) {
852
728
  return;
853
729
  }
854
730
  event.consume();
855
- const textPosition = this.textEditor.coordinatesToCursorPosition(event.x, event.y);
856
- if (!textPosition) {
731
+ const textPosition = this.editor.editor.posAtCoords(event);
732
+ if (textPosition === null) {
857
733
  return;
858
734
  }
859
- for (const decoration of this.continueToLocationDecorations.keys()) {
860
- const range = decoration.find();
861
- if (!range) {
862
- continue;
863
- }
864
- if (range.from.line !== textPosition.startLine || range.to.line !== textPosition.startLine) {
865
- continue;
866
- }
867
- if (range.from.ch <= textPosition.startColumn && textPosition.startColumn <= range.to.ch) {
868
- const callback = this.continueToLocationDecorations.get(decoration);
869
- if (!callback) {
870
- throw new Error('Expected a function');
871
- }
872
- callback();
735
+ for (const {from, to, click} of this.continueToLocations) {
736
+ if (from <= textPosition && to >= textPosition) {
737
+ click();
873
738
  break;
874
739
  }
875
740
  }
876
741
  }
877
742
 
878
743
  private onBlur(_event: Event): void {
879
- this.clearControlDown();
744
+ this.setControlDown(false);
880
745
  }
881
746
 
882
747
  private onKeyUp(_event: KeyboardEvent): void {
883
- this.clearControlDown();
748
+ this.setControlDown(false);
884
749
  }
885
750
 
886
- private clearControlDown(): void {
887
- this.controlDown = false;
888
- this.clearContinueToLocations();
889
- if (this.controlTimeout) {
751
+ private setControlDown(state: boolean): void {
752
+ if (state !== this.controlDown) {
753
+ this.controlDown = state;
890
754
  clearTimeout(this.controlTimeout);
755
+ this.controlTimeout = undefined;
756
+ if (state && this.executionLocation) {
757
+ this.controlTimeout = window.setTimeout(() => {
758
+ if (this.executionLocation && this.controlDown) {
759
+ this.showContinueToLocations();
760
+ }
761
+ }, 150);
762
+ } else {
763
+ this.clearContinueToLocations();
764
+ }
891
765
  }
892
766
  }
893
767
 
894
- private async editBreakpointCondition(
895
- editorLineNumber: number, breakpoint: Bindings.BreakpointManager.Breakpoint|null, location: {
768
+ private editBreakpointCondition(
769
+ line: CodeMirror.Line, breakpoint: Bindings.BreakpointManager.Breakpoint|null, location: {
896
770
  lineNumber: number,
897
771
  columnNumber: number,
898
772
  }|null,
899
- preferLogpoint?: boolean): Promise<void> {
773
+ preferLogpoint?: boolean): void {
774
+ const editor = this.editor as TextEditor.TextEditor.TextEditor;
900
775
  const oldCondition = breakpoint ? breakpoint.condition() : '';
901
776
  const decorationElement = document.createElement('div');
902
- const dialog =
903
- await BreakpointEditDialog.create(editorLineNumber, oldCondition, Boolean(preferLogpoint), async result => {
904
- dialog.detach();
905
- this.textEditor.removeDecoration(decorationElement, editorLineNumber);
906
- if (!result.committed) {
907
- return;
908
- }
909
- if (breakpoint) {
910
- breakpoint.setCondition(result.condition);
911
- } else if (location) {
912
- await this.setBreakpoint(location.lineNumber, location.columnNumber, result.condition, true);
913
- } else {
914
- await this.createNewBreakpoint(editorLineNumber, result.condition, true);
915
- }
916
- });
917
- this.textEditor.addDecoration(decorationElement, editorLineNumber);
777
+ const compartment = new CodeMirror.Compartment();
778
+ const dialog = new BreakpointEditDialog(line.number - 1, oldCondition, Boolean(preferLogpoint), async result => {
779
+ dialog.detach();
780
+ editor.dispatch({effects: compartment.reconfigure([])});
781
+ if (!result.committed) {
782
+ return;
783
+ }
784
+ if (breakpoint) {
785
+ breakpoint.setCondition(result.condition);
786
+ } else if (location) {
787
+ await this.setBreakpoint(location.lineNumber, location.columnNumber, result.condition, true);
788
+ } else {
789
+ await this.createNewBreakpoint(line, result.condition, true);
790
+ }
791
+ });
792
+ editor.dispatch({
793
+ effects: CodeMirror.StateEffect.appendConfig.of(compartment.of(CodeMirror.EditorView.decorations.of(
794
+ CodeMirror.Decoration.set([CodeMirror.Decoration
795
+ .widget({
796
+ block: true, widget: new class extends CodeMirror.WidgetType {
797
+ toDOM(): HTMLElement {
798
+ return decorationElement;
799
+ }
800
+ }(),
801
+ side: 1,
802
+ })
803
+ .range(line.from)])))),
804
+ });
918
805
  dialog.markAsExternallyManaged();
919
806
  dialog.show(decorationElement);
920
807
  dialog.focusEditor();
921
808
  }
922
809
 
923
- private async executionLineChanged(liveLocation: Bindings.LiveLocation.LiveLocation): Promise<void> {
924
- this.clearExecutionLine();
925
- const uiLocation = await liveLocation.uiLocation();
926
- if (!uiLocation || uiLocation.uiSourceCode.url() !== this.uiSourceCode.url()) {
927
- this.executionLocation = null;
928
- return;
810
+ private computeExecutionDecorations(editorState: CodeMirror.EditorState, lineNumber: number, columnNumber: number):
811
+ CodeMirror.DecorationSet {
812
+ const {doc} = editorState;
813
+ if (lineNumber >= doc.lines) {
814
+ return CodeMirror.Decoration.none;
929
815
  }
930
-
931
- this.executionLocation = uiLocation;
932
- const editorLocation = this.transformer.uiLocationToEditorLocation(uiLocation.lineNumber, uiLocation.columnNumber);
933
- this.textEditor.setExecutionLocation(editorLocation.lineNumber, editorLocation.columnNumber);
934
- if (this.textEditor.isShowing()) {
935
- // We need SourcesTextEditor to be initialized prior to this call. @see crbug.com/506566
936
- queueMicrotask(() => {
937
- if (this.controlDown) {
938
- this.showContinueToLocations();
939
- } else {
940
- this.generateValuesInSource();
941
- }
942
- });
816
+ const line = doc.line(lineNumber + 1);
817
+ const decorations: CodeMirror.Range<CodeMirror.Decoration>[] = [executionLineDeco.range(line.from)];
818
+ const position = Math.min(line.to, line.from + columnNumber);
819
+ let syntaxNode = CodeMirror.syntaxTree(editorState).resolveInner(position, 1);
820
+ if (syntaxNode.to === syntaxNode.from - 1 && /[(.]/.test(doc.sliceString(syntaxNode.from, syntaxNode.to))) {
821
+ syntaxNode = syntaxNode.resolve(syntaxNode.to, 1);
943
822
  }
823
+ const tokenEnd = Math.min(line.to, syntaxNode.to);
824
+ if (tokenEnd > position) {
825
+ decorations.push(executionTokenDeco.range(position, tokenEnd));
826
+ }
827
+
828
+ return CodeMirror.Decoration.set(decorations);
944
829
  }
945
830
 
946
- private generateValuesInSource(): void {
947
- if (!Common.Settings.Settings.instance().moduleSetting('inlineVariableValues').get()) {
948
- return;
949
- }
950
- const executionContext = UI.Context.Context.instance().flavor(SDK.RuntimeModel.ExecutionContext);
951
- if (!executionContext) {
952
- return;
953
- }
954
- const callFrame = UI.Context.Context.instance().flavor(SDK.DebuggerModel.CallFrame);
955
- if (!callFrame) {
831
+ // Show widgets with variable's values after lines that mention the
832
+ // variables, if the debugger is paused in this file.
833
+ private async updateValueDecorations(): Promise<void> {
834
+ if (!this.editor) {
956
835
  return;
957
836
  }
958
-
959
- const localScope = callFrame.localScope();
960
- const functionLocation = callFrame.functionLocation();
961
- if (localScope && functionLocation) {
962
- resolveScopeInObject(localScope)
963
- .getAllProperties(false, false)
964
- .then(this.prepareScopeVariables.bind(this, callFrame));
837
+ const decorations = this.executionLocation ? await this.computeValueDecorations() : null;
838
+ if (decorations || this.editor.state.field(valueDecorations.field).size) {
839
+ this.editor.dispatch({effects: valueDecorations.update.of(decorations || CodeMirror.Decoration.none)});
965
840
  }
966
841
  }
967
842
 
968
- private showContinueToLocations(): void {
969
- this.popoverHelper.hidePopover();
843
+ private async computeValueDecorations(): Promise<CodeMirror.DecorationSet|null> {
844
+ if (!this.editor) {
845
+ return null;
846
+ }
847
+ if (!Common.Settings.Settings.instance().moduleSetting('inlineVariableValues').get()) {
848
+ return null;
849
+ }
970
850
  const executionContext = UI.Context.Context.instance().flavor(SDK.RuntimeModel.ExecutionContext);
971
851
  if (!executionContext) {
972
- return;
852
+ return null;
973
853
  }
974
854
  const callFrame = UI.Context.Context.instance().flavor(SDK.DebuggerModel.CallFrame);
975
855
  if (!callFrame) {
976
- return;
856
+ return null;
977
857
  }
978
- const start = callFrame.functionLocation() || callFrame.location();
979
- const debuggerModel = callFrame.debuggerModel;
980
- debuggerModel.getPossibleBreakpoints(start, null, true)
981
- .then(locations => this.textEditor.operation(renderLocations.bind(this, locations)));
982
-
983
- function renderLocations(this: DebuggerPlugin, locations: SDK.DebuggerModel.BreakLocation[]): void {
984
- this.clearContinueToLocationsNoRestore();
985
- this.textEditor.hideExecutionLineBackground();
986
- this.continueToLocationDecorations = new Map();
987
- locations = locations.reverse();
988
- let previousCallLine = -1;
989
- for (const location of locations) {
990
- const editorLocation = this.transformer.uiLocationToEditorLocation(location.lineNumber, location.columnNumber);
991
- const tokenThatIsPossiblyNull =
992
- this.textEditor.tokenAtTextPosition(editorLocation.lineNumber, editorLocation.columnNumber);
993
- if (!tokenThatIsPossiblyNull) {
994
- continue;
995
- }
996
- let token: TextEditor.CodeMirrorTextEditor.Token =
997
- (tokenThatIsPossiblyNull as TextEditor.CodeMirrorTextEditor.Token);
998
-
999
- const line = this.textEditor.line(editorLocation.lineNumber);
1000
- let tokenContent = line.substring(token.startColumn, token.endColumn);
1001
- if (!token.type && tokenContent === '.') {
1002
- const nextToken = this.textEditor.tokenAtTextPosition(editorLocation.lineNumber, token.endColumn + 1);
1003
- if (!nextToken) {
1004
- throw new Error('nextToken should not be null.');
1005
- }
1006
- token = nextToken;
1007
- tokenContent = line.substring(token.startColumn, token.endColumn);
1008
- }
1009
- if (!token.type) {
1010
- continue;
1011
- }
1012
- const validKeyword = token.type === 'js-keyword' &&
1013
- (tokenContent === 'this' || tokenContent === 'return' || tokenContent === 'new' ||
1014
- tokenContent === 'continue' || tokenContent === 'break');
1015
- if (!validKeyword && !this.isIdentifier(token.type)) {
1016
- continue;
1017
- }
1018
- if (previousCallLine === editorLocation.lineNumber &&
1019
- location.type !== Protocol.Debugger.BreakLocationType.Call) {
1020
- continue;
1021
- }
1022
-
1023
- let highlightRange: TextUtils.TextRange.TextRange = new TextUtils.TextRange.TextRange(
1024
- editorLocation.lineNumber, token.startColumn, editorLocation.lineNumber, token.endColumn - 1);
1025
- let decoration = this.textEditor.highlightRange(highlightRange, 'source-frame-continue-to-location');
1026
- this.continueToLocationDecorations.set(decoration, location.continueToLocation.bind(location));
1027
- if (location.type === Protocol.Debugger.BreakLocationType.Call) {
1028
- previousCallLine = editorLocation.lineNumber;
1029
- }
1030
-
1031
- let isAsyncCall: boolean = (line[token.startColumn - 1] === '.' && tokenContent === 'then') ||
1032
- tokenContent === 'setTimeout' || tokenContent === 'setInterval' || tokenContent === 'postMessage';
1033
- if (tokenContent === 'new') {
1034
- const nextToken = this.textEditor.tokenAtTextPosition(editorLocation.lineNumber, token.endColumn + 1);
1035
- if (!nextToken) {
1036
- throw new Error('nextToken should not be null.');
1037
- }
1038
- token = nextToken;
1039
- tokenContent = line.substring(token.startColumn, token.endColumn);
1040
- isAsyncCall = tokenContent === 'Worker';
1041
- }
1042
- const isCurrentPosition = this.executionLocation && location.lineNumber === this.executionLocation.lineNumber &&
1043
- location.columnNumber === this.executionLocation.columnNumber;
1044
- if (location.type === Protocol.Debugger.BreakLocationType.Call && isAsyncCall) {
1045
- const asyncStepInRange =
1046
- this.findAsyncStepInRange(this.textEditor, editorLocation.lineNumber, line, token.endColumn);
1047
- if (asyncStepInRange) {
1048
- highlightRange = new TextUtils.TextRange.TextRange(
1049
- editorLocation.lineNumber, asyncStepInRange.from, editorLocation.lineNumber, asyncStepInRange.to - 1);
1050
- decoration = this.textEditor.highlightRange(highlightRange, 'source-frame-async-step-in');
1051
- this.continueToLocationDecorations.set(
1052
- decoration, this.asyncStepIn.bind(this, location, Boolean(isCurrentPosition)));
1053
- }
1054
- }
1055
- }
1056
858
 
1057
- this.continueToLocationRenderedForTest();
859
+ const localScope = callFrame.localScope();
860
+ if (!localScope || !callFrame.functionLocation()) {
861
+ return null;
1058
862
  }
1059
- }
1060
-
1061
- private continueToLocationRenderedForTest(): void {
1062
- }
1063
863
 
1064
- private findAsyncStepInRange(
1065
- textEditor: SourceFrame.SourcesTextEditor.SourcesTextEditor, editorLineNumber: number, line: string,
1066
- column: number): {
1067
- from: number,
1068
- to: number,
1069
- }|null {
1070
- let token: (TextEditor.CodeMirrorTextEditor.Token|null)|null = null;
1071
- let tokenText;
1072
- let from: number = column;
1073
- let to: number = line.length;
864
+ const {properties} = await resolveScopeInObject(localScope).getAllProperties(false, false);
865
+ if (!properties || !properties.length || properties.length > 500) {
866
+ return null;
867
+ }
1074
868
 
1075
- let position = line.indexOf('(', column);
1076
- const argumentsStart = position;
1077
- if (position === -1) {
869
+ const functionUILocationPromise =
870
+ Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance().rawLocationToUILocation(
871
+ (callFrame.functionLocation() as SDK.DebuggerModel.Location));
872
+ const executionUILocationPromise =
873
+ Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance().rawLocationToUILocation(
874
+ callFrame.location());
875
+ const [functionUILocation, executionUILocation] =
876
+ await Promise.all([functionUILocationPromise, executionUILocationPromise]);
877
+ if (!functionUILocation || !executionUILocation ||
878
+ functionUILocation.uiSourceCode.url() !== this.uiSourceCode.url() ||
879
+ executionUILocation.uiSourceCode.url() !== this.uiSourceCode.url()) {
1078
880
  return null;
1079
881
  }
1080
- position++;
1081
882
 
1082
- skipWhitespace();
1083
- if (position >= line.length) {
883
+ const functionLocation =
884
+ this.transformer.uiLocationToEditorLocation(functionUILocation.lineNumber, functionUILocation.columnNumber);
885
+ const executionLocation =
886
+ this.transformer.uiLocationToEditorLocation(executionUILocation.lineNumber, executionUILocation.columnNumber);
887
+ if (functionLocation.lineNumber >= executionLocation.lineNumber ||
888
+ executionLocation.lineNumber - functionLocation.lineNumber > 500 || functionLocation.lineNumber < 0 ||
889
+ executionLocation.lineNumber >= this.editor.state.doc.lines) {
1084
890
  return null;
1085
891
  }
1086
892
 
1087
- token = nextToken();
1088
- if (!token) {
893
+ const variableMap = new Map<string, SDK.RemoteObject.RemoteObject>(
894
+ properties.map(p => [p.name, p.value] as [string, SDK.RemoteObject.RemoteObject]));
895
+
896
+ const variablesByLine =
897
+ this.getVariablesByLine(this.editor.state, variableMap, functionLocation, executionLocation);
898
+ if (!variablesByLine) {
1089
899
  return null;
1090
900
  }
1091
- from = token.startColumn;
1092
901
 
1093
- if (token.type === 'js-keyword' && tokenText === 'async') {
1094
- skipWhitespace();
1095
- if (position >= line.length) {
1096
- return {from: from, to: to};
902
+ const decorations: CodeMirror.Range<CodeMirror.Decoration>[] = [];
903
+
904
+ for (const [line, names] of variablesByLine) {
905
+ const prevLine = variablesByLine.get(line - 1);
906
+ let newNames = prevLine ? Array.from(names).filter(n => !prevLine.has(n)) : Array.from(names);
907
+ if (!newNames.length) {
908
+ continue;
1097
909
  }
1098
- token = nextToken();
1099
- if (!token) {
1100
- return {from: from, to: to};
910
+ if (newNames.length > 10) {
911
+ newNames = newNames.slice(0, 10);
1101
912
  }
913
+ const pairs = newNames.map(name => [name, variableMap.get(name)] as [string, SDK.RemoteObject.RemoteObject]);
914
+ decorations.push(CodeMirror.Decoration.widget({widget: new ValueDecoration(pairs), side: 1})
915
+ .range(this.editor.state.doc.line(line + 1).to));
1102
916
  }
917
+ return CodeMirror.Decoration.set(decorations, true);
918
+ }
1103
919
 
1104
- if (token.type === 'js-keyword' && tokenText === 'function') {
1105
- return {from: from, to: to};
1106
- }
920
+ getVariablesByLine(
921
+ editorState: CodeMirror.EditorState, variableMap: Map<string, unknown>,
922
+ fromLoc: {lineNumber: number, columnNumber: number},
923
+ toLoc: {lineNumber: number, columnNumber: number}): Map<number, Set<string>>|null {
924
+ const fromLine = editorState.doc.line(fromLoc.lineNumber + 1);
925
+ const fromPos = Math.min(fromLine.to, fromLine.from + fromLoc.columnNumber);
926
+ const toPos = editorState.doc.line(toLoc.lineNumber + 1).from;
1107
927
 
1108
- if (token.type === 'js-string') {
1109
- return {from: argumentsStart, to: to};
928
+ const tree = CodeMirror.ensureSyntaxTree(editorState, toPos, 100);
929
+ if (!tree) {
930
+ return null;
1110
931
  }
1111
932
 
1112
- if (token.type && this.isIdentifier(token.type)) {
1113
- return {from: from, to: to};
1114
- }
933
+ const namesPerLine = new Map<number, Set<string>>();
934
+ let curLine = fromLine;
935
+ tree.iterate({
936
+ from: fromPos,
937
+ to: toPos,
938
+ enter(type, from, to): void {
939
+ const varName = type.name === 'VariableName' && editorState.sliceDoc(from, to);
940
+ if (varName && variableMap.has(varName)) {
941
+ if (from > curLine.to) {
942
+ curLine = editorState.doc.lineAt(from);
943
+ }
944
+ let names = namesPerLine.get(curLine.number - 1);
945
+ if (!names) {
946
+ names = new Set();
947
+ namesPerLine.set(curLine.number - 1, names);
948
+ }
949
+ names.add(varName);
950
+ }
951
+ },
952
+ });
953
+ return namesPerLine;
954
+ }
1115
955
 
1116
- if (tokenText !== '(') {
1117
- return null;
956
+ private async showContinueToLocations(): Promise<void> {
957
+ this.popoverHelper.hidePopover();
958
+ const executionContext = UI.Context.Context.instance().flavor(SDK.RuntimeModel.ExecutionContext);
959
+ if (!executionContext || !this.editor) {
960
+ return;
1118
961
  }
1119
- const closeParen = line.indexOf(')', position);
1120
- if (closeParen === -1 || line.substring(position, closeParen).indexOf('(') !== -1) {
1121
- return {from: from, to: to};
962
+ const callFrame = UI.Context.Context.instance().flavor(SDK.DebuggerModel.CallFrame);
963
+ if (!callFrame) {
964
+ return;
1122
965
  }
1123
- return {from: from, to: closeParen + 1};
966
+ const start = callFrame.functionLocation() || callFrame.location();
967
+ const debuggerModel = callFrame.debuggerModel;
968
+
969
+ const {state} = this.editor;
970
+ const locations = await debuggerModel.getPossibleBreakpoints(start, null, true);
1124
971
 
1125
- function nextToken(): TextEditor.CodeMirrorTextEditor.Token|null {
1126
- token = textEditor.tokenAtTextPosition(editorLineNumber, position);
1127
- if (token) {
1128
- position = token.endColumn;
1129
- to = token.endColumn;
1130
- tokenText = line.substring(token.startColumn, token.endColumn);
972
+ this.continueToLocations = [];
973
+ let previousCallLine = -1;
974
+ for (const location of locations.reverse()) {
975
+ const editorLocation = this.transformer.uiLocationToEditorLocation(location.lineNumber, location.columnNumber);
976
+ if (previousCallLine === editorLocation.lineNumber &&
977
+ location.type !== Protocol.Debugger.BreakLocationType.Call ||
978
+ editorLocation.lineNumber >= state.doc.lines) {
979
+ continue;
980
+ }
981
+ const line = state.doc.line(editorLocation.lineNumber + 1);
982
+ const position = Math.min(line.to, line.from + editorLocation.columnNumber);
983
+ let syntaxNode = CodeMirror.syntaxTree(state).resolveInner(position, 1);
984
+ if (syntaxNode.firstChild || syntaxNode.from < line.from ||
985
+ syntaxNode.to > line.to) { // Only use leaf nodes within the line
986
+ continue;
987
+ }
988
+ if (syntaxNode.name === '.') {
989
+ const nextNode = syntaxNode.resolve(syntaxNode.to, 1);
990
+ if (nextNode.firstChild || nextNode.from < line.from || nextNode.to > line.to) {
991
+ continue;
992
+ }
993
+ syntaxNode = nextNode;
994
+ }
995
+ const syntaxType = syntaxNode.name;
996
+ const validKeyword = syntaxType === 'this' || syntaxType === 'return' || syntaxType === 'new' ||
997
+ syntaxType === 'break' || syntaxType === 'continue';
998
+ if (!validKeyword && !this.isIdentifier(syntaxType)) {
999
+ continue;
1131
1000
  }
1132
1001
 
1133
- return token;
1134
- }
1002
+ this.continueToLocations.push(
1003
+ {from: syntaxNode.from, to: syntaxNode.to, async: false, click: () => location.continueToLocation()});
1004
+ if (location.type === Protocol.Debugger.BreakLocationType.Call) {
1005
+ previousCallLine = editorLocation.lineNumber;
1006
+ }
1135
1007
 
1136
- function skipWhitespace(): void {
1137
- while (position < line.length) {
1138
- if (line[position] === ' ') {
1139
- position++;
1140
- continue;
1008
+ const identifierName =
1009
+ validKeyword ? '' : line.text.slice(syntaxNode.from - line.from, syntaxNode.to - line.from);
1010
+ let asyncCall: CodeMirror.SyntaxNode|null = null;
1011
+ if (identifierName === 'then' && syntaxNode.parent?.name === 'MemberExpression') {
1012
+ asyncCall = syntaxNode.parent.parent;
1013
+ } else if (
1014
+ identifierName === 'setTimeout' || identifierName === 'setInterval' || identifierName === 'postMessage') {
1015
+ asyncCall = syntaxNode.parent;
1016
+ }
1017
+ if (syntaxType === 'new') {
1018
+ const callee = syntaxNode.parent?.getChild('Expression');
1019
+ if (callee && callee.name === 'VariableName' && state.sliceDoc(callee.from, callee.to) === 'Worker') {
1020
+ asyncCall = syntaxNode.parent;
1141
1021
  }
1142
- const token = textEditor.tokenAtTextPosition(editorLineNumber, position);
1143
- if (!token) {
1144
- throw new Error('expected token to not be null');
1022
+ }
1023
+ if (asyncCall && (asyncCall.name === 'CallExpression' || asyncCall.name === 'NewExpression') &&
1024
+ location.type === Protocol.Debugger.BreakLocationType.Call) {
1025
+ const firstArg = asyncCall.getChild('ArgList')?.firstChild?.nextSibling;
1026
+ let highlightNode;
1027
+ if (firstArg?.name === 'VariableName') {
1028
+ highlightNode = firstArg;
1029
+ } else if (firstArg?.name === 'ArrowFunction' || firstArg?.name === 'FunctionExpression') {
1030
+ highlightNode = firstArg.firstChild;
1031
+ if (highlightNode?.name === 'async') {
1032
+ highlightNode = highlightNode.nextSibling;
1033
+ }
1145
1034
  }
1146
- if (token.type === 'js-comment') {
1147
- position = token.endColumn;
1148
- continue;
1035
+ if (highlightNode) {
1036
+ const isCurrentPosition = this.executionLocation &&
1037
+ location.lineNumber === this.executionLocation.lineNumber &&
1038
+ location.columnNumber === this.executionLocation.columnNumber;
1039
+ this.continueToLocations.push({
1040
+ from: highlightNode.from,
1041
+ to: highlightNode.to,
1042
+ async: true,
1043
+ click: () => this.asyncStepIn(location, Boolean(isCurrentPosition)),
1044
+ });
1149
1045
  }
1150
- break;
1151
1046
  }
1152
1047
  }
1048
+ const decorations = CodeMirror.Decoration.set(
1049
+ this.continueToLocations.map(loc => {
1050
+ return (loc.async ? asyncContinueToMark : continueToMark).range(loc.from, loc.to);
1051
+ }),
1052
+ true);
1053
+ this.editor.dispatch({effects: continueToMarkers.update.of(decorations)});
1054
+ }
1055
+
1056
+ private clearContinueToLocations(): void {
1057
+ if (this.editor && this.editor.state.field(continueToMarkers.field).size) {
1058
+ this.editor.dispatch({effects: continueToMarkers.update.of(CodeMirror.Decoration.none)});
1059
+ }
1153
1060
  }
1154
1061
 
1155
1062
  private asyncStepIn(location: SDK.DebuggerModel.BreakLocation, isCurrentPosition: boolean): void {
@@ -1164,573 +1071,213 @@ export class DebuggerPlugin extends Plugin {
1164
1071
  }
1165
1072
  }
1166
1073
 
1167
- private async prepareScopeVariables(
1168
- callFrame: SDK.DebuggerModel.CallFrame, allProperties: SDK.RemoteObject.GetPropertiesResult): Promise<void> {
1169
- const properties = allProperties.properties;
1170
- this.clearValueWidgets();
1171
- if (!properties || !properties.length || properties.length > 500 || !this.textEditor.isShowing()) {
1172
- return;
1074
+ private fetchBreakpoints(): {
1075
+ position: number,
1076
+ breakpoint: Bindings.BreakpointManager.Breakpoint,
1077
+ }[] {
1078
+ if (!this.editor) {
1079
+ return [];
1173
1080
  }
1081
+ const {editor} = this;
1082
+ const breakpointLocations = this.breakpointManager.breakpointLocationsForUISourceCode(this.uiSourceCode);
1083
+ return breakpointLocations.map(({uiLocation, breakpoint}) => {
1084
+ const editorLocation =
1085
+ this.transformer.uiLocationToEditorLocation(uiLocation.lineNumber, uiLocation.columnNumber);
1086
+ return {
1087
+ position: editor.toOffset(editorLocation),
1088
+ breakpoint,
1089
+ };
1090
+ });
1091
+ }
1174
1092
 
1175
- const functionUILocationPromise =
1176
- Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance().rawLocationToUILocation(
1177
- (callFrame.functionLocation() as SDK.DebuggerModel.Location));
1178
- const executionUILocationPromise =
1179
- Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance().rawLocationToUILocation(
1180
- callFrame.location());
1181
- const [functionUILocation, executionUILocation] =
1182
- await Promise.all([functionUILocationPromise, executionUILocationPromise]);
1183
- if (!functionUILocation || !executionUILocation ||
1184
- functionUILocation.uiSourceCode.url() !== this.uiSourceCode.url() ||
1185
- executionUILocation.uiSourceCode.url() !== this.uiSourceCode.url()) {
1186
- return;
1187
- }
1093
+ private lineBreakpoints(line: CodeMirror.Line): readonly Bindings.BreakpointManager.Breakpoint[] {
1094
+ return this.breakpoints.filter(b => b.position >= line.from && b.position <= line.to).map(b => b.breakpoint);
1095
+ }
1188
1096
 
1189
- const functionEditorLocation =
1190
- this.transformer.uiLocationToEditorLocation(functionUILocation.lineNumber, functionUILocation.columnNumber);
1191
- const executionEditorLocation =
1192
- this.transformer.uiLocationToEditorLocation(executionUILocation.lineNumber, executionUILocation.columnNumber);
1193
- const fromLine = functionEditorLocation.lineNumber;
1194
- const fromColumn = functionEditorLocation.columnNumber;
1195
- const toLine = executionEditorLocation.lineNumber;
1196
- if (fromLine >= toLine || toLine - fromLine > 500 || fromLine < 0 || toLine >= this.textEditor.linesCount) {
1197
- return;
1198
- }
1097
+ private async computeBreakpointDecoration(state: CodeMirror.EditorState, breakpoints: BreakpointDescription[]):
1098
+ Promise<BreakpointDecoration> {
1099
+ const decorations: CodeMirror.Range<CodeMirror.Decoration>[] = [];
1100
+ const gutterMarkers: CodeMirror.Range<CodeMirror.GutterMarker>[] = [];
1101
+ const breakpointsByLine = new Map<number, Bindings.BreakpointManager.Breakpoint[]>();
1102
+ const inlineMarkersByLine =
1103
+ new Map<number, {breakpoint: Bindings.BreakpointManager.Breakpoint | null, column: number}[]>();
1104
+ const possibleBreakpointRequests: Promise<void>[] = [];
1105
+ const inlineMarkerPositions = new Set<number>();
1199
1106
 
1200
- const valuesMap = new Map<string, SDK.RemoteObject.RemoteObject|null|undefined>();
1201
- for (const property of properties) {
1202
- valuesMap.set(property.name, property.value);
1203
- }
1107
+ const addInlineMarker =
1108
+ (linePos: number, columnNumber: number, breakpoint: Bindings.BreakpointManager.Breakpoint|null): void => {
1109
+ let inlineMarkers = inlineMarkersByLine.get(linePos);
1110
+ if (!inlineMarkers) {
1111
+ inlineMarkers = [];
1112
+ inlineMarkersByLine.set(linePos, inlineMarkers);
1113
+ }
1114
+ inlineMarkers.push({breakpoint, column: columnNumber});
1115
+ };
1204
1116
 
1205
- const namesPerLine = new Map<number, Set<string>>();
1206
- let skipObjectProperty = false;
1207
- const tokenizer = TextUtils.CodeMirrorUtils.TokenizerFactory.instance().createTokenizer('text/javascript');
1208
- tokenizer(this.textEditor.line(fromLine).substring(fromColumn), processToken.bind(this, fromLine));
1209
- for (let i = fromLine + 1; i < toLine; ++i) {
1210
- tokenizer(this.textEditor.line(i), processToken.bind(this, i));
1211
- }
1212
-
1213
- function processToken(
1214
- this: DebuggerPlugin, editorLineNumber: number, tokenValue: string, tokenType: string|null, _column: number,
1215
- _newColumn: number): void {
1216
- if (!skipObjectProperty && tokenType && this.isIdentifier(tokenType) && valuesMap.get(tokenValue)) {
1217
- let names = namesPerLine.get(editorLineNumber);
1218
- if (!names) {
1219
- names = new Set();
1220
- namesPerLine.set(editorLineNumber, names);
1221
- }
1222
- names.add(tokenValue);
1117
+ for (const {position, breakpoint} of breakpoints) {
1118
+ const line = state.doc.lineAt(position);
1119
+ let forThisLine = breakpointsByLine.get(line.from);
1120
+ if (!forThisLine) {
1121
+ forThisLine = [];
1122
+ breakpointsByLine.set(line.from, forThisLine);
1123
+ }
1124
+ if (breakpoint.enabled() && forThisLine.every(b => !b.enabled())) {
1125
+ // Start a request for possible breakpoint positions on this line
1126
+ const start = this.transformer.editorLocationToUILocation(line.number - 1, 0);
1127
+ const end = this.transformer.editorLocationToUILocation(
1128
+ line.number - 1, Math.min(line.length, MAX_POSSIBLE_BREAKPOINT_LINE));
1129
+ const range = new TextUtils.TextRange.TextRange(
1130
+ start.lineNumber, start.columnNumber || 0, end.lineNumber, end.columnNumber || 0);
1131
+ possibleBreakpointRequests.push(this.breakpointManager.possibleBreakpoints(this.uiSourceCode, range)
1132
+ .then(locations => addPossibleBreakpoints(line, locations)));
1133
+ }
1134
+ forThisLine.push(breakpoint);
1135
+ if (breakpoint.enabled()) {
1136
+ inlineMarkerPositions.add(position);
1137
+ addInlineMarker(line.from, position - line.from, breakpoint);
1223
1138
  }
1224
- skipObjectProperty = tokenValue === '.';
1225
1139
  }
1226
- // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
1227
- // @ts-expect-error
1228
- this.textEditor.operation(this.renderDecorations.bind(this, valuesMap, namesPerLine, fromLine, toLine));
1229
- }
1230
1140
 
1231
- private renderDecorations(
1232
- valuesMap: Map<string, SDK.RemoteObject.RemoteObject>, namesPerLine: Map<number, Set<string>>, fromLine: number,
1233
- toLine: number): void {
1234
- const formatter = new ObjectUI.RemoteObjectPreviewFormatter.RemoteObjectPreviewFormatter();
1235
- for (let i = fromLine; i < toLine; ++i) {
1236
- const names = namesPerLine.get(i);
1237
- const oldWidget = this.valueWidgets.get(i);
1238
- if (!names) {
1239
- if (oldWidget) {
1240
- this.valueWidgets.delete(i);
1241
- this.textEditor.removeDecoration(oldWidget, i);
1242
- }
1243
- continue;
1141
+ for (const [lineStart, lineBreakpoints] of breakpointsByLine) {
1142
+ const main = lineBreakpoints.sort(mostSpecificBreakpoint)[0];
1143
+ let gutterClass = 'cm-breakpoint';
1144
+ if (!main.enabled()) {
1145
+ gutterClass += ' cm-breakpoint-disabled';
1244
1146
  }
1245
-
1246
- const widget = (document.createElement('div') as DecoratorWidget);
1247
- widget.classList.add('text-editor-value-decoration');
1248
- const base = this.textEditor.cursorPositionToCoordinates(i, 0);
1249
- if (!base) {
1250
- throw new Error('base is expected to not be null');
1147
+ if (!main.bound()) {
1148
+ gutterClass += ' cm-breakpoint-unbound';
1251
1149
  }
1252
- const offset = this.textEditor.cursorPositionToCoordinates(i, this.textEditor.line(i).length);
1253
- if (!offset) {
1254
- throw new Error('offset is expected to not be null');
1150
+ if (main.condition().includes(LogpointPrefix)) {
1151
+ gutterClass += ' cm-breakpoint-logpoint';
1152
+ } else if (main.condition()) {
1153
+ gutterClass += ' cm-breakpoint-conditional';
1255
1154
  }
1256
- const codeMirrorLinesLeftPadding = 4;
1257
- const left = offset.x - base.x + codeMirrorLinesLeftPadding;
1258
- widget.style.left = left + 'px';
1259
- widget.nameToToken = new Map();
1260
-
1261
- let renderedNameCount = 0;
1262
- for (const name of names) {
1263
- if (renderedNameCount > 10) {
1264
- break;
1265
- }
1266
- const names = namesPerLine.get(i - 1);
1267
- if (names && names.has(name)) {
1155
+ gutterMarkers.push((new BreakpointGutterMarker(gutterClass)).range(lineStart));
1156
+ }
1157
+
1158
+ const addPossibleBreakpoints = (line: CodeMirror.Line, locations: Workspace.UISourceCode.UILocation[]): void => {
1159
+ for (const location of locations) {
1160
+ const editorLocation = this.transformer.uiLocationToEditorLocation(location.lineNumber, location.columnNumber);
1161
+ if (editorLocation.lineNumber !== line.number - 1) {
1268
1162
  continue;
1269
- } // Only render name once in the given continuous block.
1270
- if (renderedNameCount) {
1271
- UI.UIUtils.createTextChild(widget, ', ');
1272
1163
  }
1273
- const nameValuePair = (widget.createChild('span') as HTMLElement);
1274
- widget.nameToToken.set(name, nameValuePair);
1275
- UI.UIUtils.createTextChild(nameValuePair, name + ' = ');
1276
- const value = valuesMap.get(name);
1277
- if (!value) {
1278
- throw new Error('value is expected to be null');
1164
+ const position = Math.min(line.to, line.from + editorLocation.columnNumber);
1165
+ if (!inlineMarkerPositions.has(position)) {
1166
+ addInlineMarker(line.from, editorLocation.columnNumber, null);
1279
1167
  }
1280
- const propertyCount = value.preview ? value.preview.properties.length : 0;
1281
- const entryCount = value.preview && value.preview.entries ? value.preview.entries.length : 0;
1282
- if (value.preview && propertyCount + entryCount < 10) {
1283
- formatter.appendObjectPreview(nameValuePair, value.preview, false /* isEntry */);
1284
- } else {
1285
- const propertyValue = ObjectUI.ObjectPropertiesSection.ObjectPropertiesSection.createPropertyValue(
1286
- value, /* wasThrown */ false, /* showPreview */ false);
1287
- nameValuePair.appendChild(propertyValue.element);
1288
- }
1289
- ++renderedNameCount;
1290
1168
  }
1169
+ };
1291
1170
 
1292
- let widgetChanged = true;
1293
- if (oldWidget) {
1294
- widgetChanged = false;
1295
- for (const name of widget.nameToToken.keys()) {
1296
- const oldTextElement = oldWidget.nameToToken.get(name);
1297
- const newTextElement = widget.nameToToken.get(name);
1298
- const oldText = oldTextElement ? oldTextElement.textContent : '';
1299
- const newText = newTextElement ? newTextElement.textContent : '';
1300
- if (newText !== oldText) {
1301
- widgetChanged = true;
1302
- UI.UIUtils.runCSSAnimationOnce(
1303
- (widget.nameToToken.get(name) as Element), 'source-frame-value-update-highlight');
1304
- }
1305
- }
1306
- if (widgetChanged) {
1307
- this.valueWidgets.delete(i);
1308
- this.textEditor.removeDecoration(oldWidget, i);
1171
+ await Promise.all(possibleBreakpointRequests);
1172
+ for (const [linePos, inlineMarkers] of inlineMarkersByLine) {
1173
+ if (inlineMarkers.length > 1) {
1174
+ for (const {column, breakpoint} of inlineMarkers) {
1175
+ const marker = new BreakpointInlineMarker(breakpoint, this);
1176
+ decorations.push(CodeMirror.Decoration.widget({widget: marker, side: -1}).range(linePos + column));
1309
1177
  }
1310
1178
  }
1311
- if (widgetChanged) {
1312
- this.valueWidgets.set(i, widget);
1313
- this.textEditor.addDecoration(widget, i);
1314
- }
1315
- }
1316
- }
1317
-
1318
- private clearExecutionLine(): void {
1319
- this.textEditor.operation(() => {
1320
- if (this.executionLocation) {
1321
- this.textEditor.clearExecutionLine();
1322
- }
1323
- this.executionLocation = null;
1324
- if (this.clearValueWidgetsTimer) {
1325
- clearTimeout(this.clearValueWidgetsTimer);
1326
- this.clearValueWidgetsTimer = null;
1327
- }
1328
- this.clearValueWidgetsTimer = window.setTimeout(this.clearValueWidgets.bind(this), 1000);
1329
- this.clearContinueToLocationsNoRestore();
1330
- });
1331
- }
1332
-
1333
- private clearValueWidgets(): void {
1334
- if (this.clearValueWidgetsTimer) {
1335
- clearTimeout(this.clearValueWidgetsTimer);
1336
1179
  }
1337
- this.clearValueWidgetsTimer = null;
1338
- this.textEditor.operation(() => {
1339
- for (const line of this.valueWidgets.keys()) {
1340
- const valueWidget = this.valueWidgets.get(line);
1341
- if (valueWidget) {
1342
- this.textEditor.removeDecoration(valueWidget, line);
1343
- }
1344
- }
1345
- this.valueWidgets.clear();
1346
- });
1347
- }
1348
1180
 
1349
- private clearContinueToLocationsNoRestore(): void {
1350
- const continueToLocationDecorations = this.continueToLocationDecorations;
1351
- if (!continueToLocationDecorations) {
1352
- return;
1353
- }
1354
- this.textEditor.operation(() => {
1355
- for (const decoration of continueToLocationDecorations.keys()) {
1356
- this.textEditor.removeHighlight(decoration);
1357
- }
1358
- this.continueToLocationDecorations = null;
1359
- this.setAsyncStepInHoveredLine(null, false);
1360
- });
1181
+ return {content: CodeMirror.Decoration.set(decorations, true), gutter: CodeMirror.RangeSet.of(gutterMarkers, true)};
1361
1182
  }
1362
1183
 
1363
- private clearContinueToLocations(): void {
1364
- if (!this.continueToLocationDecorations) {
1365
- return;
1184
+ // If, after editing, the editor is synced again (either by going
1185
+ // back to the original document or by saving), we replace any
1186
+ // breakpoints the breakpoint manager might have (which point into
1187
+ // the old file) with the breakpoints we have, which had their
1188
+ // positions tracked through the changes.
1189
+ private restoreBreakpointsAfterEditing(): void {
1190
+ const {breakpoints} = this;
1191
+ const editor = this.editor as TextEditor.TextEditor.TextEditor;
1192
+ this.breakpoints = [];
1193
+ for (const {breakpoint, position} of breakpoints) {
1194
+ const condition = breakpoint.condition(), enabled = breakpoint.enabled();
1195
+ breakpoint.remove(false);
1196
+ const editorLocation = editor.toLineColumn(position);
1197
+ const uiLocation =
1198
+ this.transformer.editorLocationToUILocation(editorLocation.lineNumber, editorLocation.columnNumber);
1199
+ this.setBreakpoint(uiLocation.lineNumber, uiLocation.columnNumber, condition, enabled);
1366
1200
  }
1367
- this.textEditor.operation(() => {
1368
- this.textEditor.showExecutionLineBackground();
1369
- this.generateValuesInSource();
1370
- this.clearContinueToLocationsNoRestore();
1371
- });
1372
- }
1373
-
1374
- private lineBreakpointDecorations(lineNumber: number): BreakpointDecoration[] {
1375
- return Array.from(this.breakpointDecorations)
1376
- .filter(decoration => (decoration.handle.resolve() || {}).lineNumber === lineNumber);
1377
1201
  }
1378
1202
 
1379
- private breakpointDecoration(editorLineNumber: number, editorColumnNumber: number): BreakpointDecoration|null {
1380
- for (const decoration of this.breakpointDecorations) {
1381
- const location = decoration.handle.resolve();
1382
- if (!location) {
1383
- continue;
1384
- }
1385
- if (location.lineNumber === editorLineNumber && location.columnNumber === editorColumnNumber) {
1386
- return decoration;
1203
+ private async refreshBreakpoints(): Promise<void> {
1204
+ if (this.editor) {
1205
+ this.breakpoints = this.fetchBreakpoints();
1206
+ const forBreakpoints = this.breakpoints;
1207
+ const decorations = await this.computeBreakpointDecoration(this.editor.state, forBreakpoints);
1208
+ if (this.breakpoints === forBreakpoints &&
1209
+ (decorations.gutter.size || this.editor.state.field(breakpointMarkers).gutter.size)) {
1210
+ this.editor.dispatch({effects: setBreakpointDeco.of(decorations)});
1387
1211
  }
1388
1212
  }
1389
- return null;
1390
1213
  }
1391
1214
 
1392
- private updateBreakpointDecoration(decoration: BreakpointDecoration): void {
1393
- if (!this.scheduledBreakpointDecorationUpdates) {
1394
- this.scheduledBreakpointDecorationUpdates = new Set();
1395
- queueMicrotask(() => {
1396
- this.textEditor.operation(update.bind(this));
1397
- });
1398
- }
1399
- this.scheduledBreakpointDecorationUpdates.add(decoration);
1400
-
1401
- function update(this: DebuggerPlugin): void {
1402
- if (!this.scheduledBreakpointDecorationUpdates) {
1403
- return;
1404
- }
1405
- const editorLineNumbers = new Set<number>();
1406
- for (const decoration of this.scheduledBreakpointDecorationUpdates) {
1407
- const location = decoration.handle.resolve();
1408
- if (!location) {
1409
- continue;
1410
- }
1411
- editorLineNumbers.add(location.lineNumber);
1412
- }
1413
- this.scheduledBreakpointDecorationUpdates = null;
1414
- let waitingForInlineDecorations = false;
1415
- for (const lineNumber of editorLineNumbers) {
1416
- const decorations = this.lineBreakpointDecorations(lineNumber);
1417
- updateGutter.call(this, lineNumber, decorations);
1418
- if (this.possibleBreakpointsRequested.has(lineNumber)) {
1419
- waitingForInlineDecorations = true;
1420
- continue;
1421
- }
1422
- updateInlineDecorations.call(this, lineNumber, decorations);
1423
- }
1424
- if (!waitingForInlineDecorations) {
1425
- this.breakpointDecorationsUpdatedForTest();
1426
- }
1427
- }
1428
-
1429
- function updateGutter(this: DebuggerPlugin, editorLineNumber: number, decorations: BreakpointDecoration[]): void {
1430
- this.textEditor.toggleLineClass(editorLineNumber, 'cm-breakpoint', false);
1431
- this.textEditor.toggleLineClass(editorLineNumber, 'cm-breakpoint-disabled', false);
1432
- this.textEditor.toggleLineClass(editorLineNumber, 'cm-breakpoint-unbound', false);
1433
- this.textEditor.toggleLineClass(editorLineNumber, 'cm-breakpoint-conditional', false);
1434
- this.textEditor.toggleLineClass(editorLineNumber, 'cm-breakpoint-logpoint', false);
1435
-
1436
- if (decorations.length) {
1437
- decorations.sort(BreakpointDecoration.mostSpecificFirst);
1438
- const isDisabled = !decorations[0].enabled || this.muted;
1439
- const isLogpoint = decorations[0].condition.includes(LogpointPrefix);
1440
- const isUnbound = !decorations[0].bound;
1441
- const isConditionalBreakpoint = Boolean(decorations[0].condition) && !isLogpoint;
1442
-
1443
- this.textEditor.toggleLineClass(editorLineNumber, 'cm-breakpoint', true);
1444
- this.textEditor.toggleLineClass(editorLineNumber, 'cm-breakpoint-disabled', isDisabled);
1445
- this.textEditor.toggleLineClass(editorLineNumber, 'cm-breakpoint-unbound', isUnbound && !isDisabled);
1446
- this.textEditor.toggleLineClass(editorLineNumber, 'cm-breakpoint-logpoint', isLogpoint);
1447
- this.textEditor.toggleLineClass(editorLineNumber, 'cm-breakpoint-conditional', isConditionalBreakpoint);
1448
- }
1215
+ private breakpointChange(event: Common.EventTarget.EventTargetEvent<Bindings.BreakpointManager.BreakpointLocation>):
1216
+ void {
1217
+ const {uiLocation} = event.data;
1218
+ if (uiLocation.uiSourceCode !== this.uiSourceCode || this.muted) {
1219
+ return;
1449
1220
  }
1450
-
1451
- function updateInlineDecorations(
1452
- this: DebuggerPlugin, editorLineNumber: number, decorations: BreakpointDecoration[]): void {
1453
- const actualBookmarks = new Set<TextEditor.CodeMirrorTextEditor.TextEditorBookMark>(
1454
- // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
1455
- // @ts-expect-error
1456
- decorations.map(decoration => decoration.bookmark).filter(bookmark => Boolean(bookmark)));
1457
- const lineEnd = this.textEditor.line(editorLineNumber).length;
1458
- const bookmarks = this.textEditor.bookmarks(
1459
- new TextUtils.TextRange.TextRange(editorLineNumber, 0, editorLineNumber, lineEnd),
1460
- BreakpointDecoration.bookmarkSymbol);
1461
- for (const bookmark of bookmarks) {
1462
- if (!actualBookmarks.has(bookmark)) {
1463
- bookmark.clear();
1464
- }
1465
- }
1466
- if (!decorations.length) {
1221
+ for (const scriptFile of this.scriptFileForDebuggerModel.values()) {
1222
+ if (scriptFile.isDivergingFromVM() || scriptFile.isMergingToVM()) {
1467
1223
  return;
1468
1224
  }
1469
- if (decorations.length > 1) {
1470
- for (const decoration of decorations) {
1471
- decoration.update();
1472
- if (!this.muted) {
1473
- decoration.show();
1474
- } else {
1475
- decoration.hide();
1476
- }
1477
- }
1478
- } else {
1479
- decorations[0].update();
1480
- decorations[0].hide();
1481
- }
1482
1225
  }
1226
+ // These tend to arrive in bursts, so debounce them
1227
+ window.clearTimeout(this.refreshBreakpointsTimeout);
1228
+ this.refreshBreakpointsTimeout = window.setTimeout(() => this.refreshBreakpoints(), 50);
1483
1229
  }
1484
1230
 
1485
- private breakpointDecorationsUpdatedForTest(): void {
1486
- }
1487
-
1488
- private async inlineBreakpointClick(decoration: BreakpointDecoration, event: MouseEvent): Promise<void> {
1231
+ onInlineBreakpointMarkerClick(event: MouseEvent, breakpoint: Bindings.BreakpointManager.Breakpoint|null): void {
1489
1232
  event.consume(true);
1490
- if (decoration.breakpoint) {
1233
+ if (breakpoint) {
1491
1234
  if (event.shiftKey) {
1492
- decoration.breakpoint.setEnabled(!decoration.breakpoint.enabled());
1235
+ breakpoint.setEnabled(!breakpoint.enabled());
1493
1236
  } else {
1494
- decoration.breakpoint.remove(false);
1495
- }
1496
- } else {
1497
- const editorLocation = decoration.handle.resolve();
1498
- if (!editorLocation) {
1499
- return;
1237
+ breakpoint.remove(false);
1500
1238
  }
1501
- const location =
1502
- this.transformer.editorLocationToUILocation(editorLocation.lineNumber, editorLocation.columnNumber);
1503
- await this.setBreakpoint(location.lineNumber, location.columnNumber, decoration.condition, true);
1239
+ } else if (this.editor) {
1240
+ const editorLocation = this.editor.editor.posAtDOM(event.target as unknown as HTMLElement);
1241
+ const line = this.editor.state.doc.lineAt(editorLocation);
1242
+ const uiLocation = this.transformer.editorLocationToUILocation(line.number - 1, editorLocation - line.from);
1243
+ this.setBreakpoint(uiLocation.lineNumber, uiLocation.columnNumber, '', true);
1504
1244
  }
1505
1245
  }
1506
1246
 
1507
- private inlineBreakpointContextMenu(decoration: BreakpointDecoration, event: Event): void {
1247
+ onInlineBreakpointMarkerContextMenu(event: MouseEvent, breakpoint: Bindings.BreakpointManager.Breakpoint|null): void {
1508
1248
  event.consume(true);
1509
- const editorLocation = decoration.handle.resolve();
1510
- if (!editorLocation) {
1511
- return;
1512
- }
1513
- if (this.textEditor.hasLineClass(editorLocation.lineNumber, 'cm-non-breakable-line')) {
1514
- return;
1515
- }
1516
- if (!Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance().supportsConditionalBreakpoints(
1249
+ // If there's events coming from the editor, there must be an editor.
1250
+ const editor = this.editor as TextEditor.TextEditor.TextEditor;
1251
+ const position = editor.editor.posAtDOM(event.target as unknown as HTMLElement);
1252
+ const line = editor.state.doc.lineAt(position);
1253
+ if (!SourceFrame.SourceFrame.isBreakableLine(editor.state, line) ||
1254
+ // Editing breakpoints only make sense for conditional breakpoints
1255
+ // and logpoints.
1256
+ !Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance().supportsConditionalBreakpoints(
1517
1257
  this.uiSourceCode)) {
1518
- // Editing breakpoints only make sense for conditional breakpoints
1519
- // and logpoints.
1520
1258
  return;
1521
1259
  }
1522
- const location =
1523
- this.transformer.editorLocationToUILocation(editorLocation.lineNumber, editorLocation.columnNumber);
1524
1260
  const contextMenu = new UI.ContextMenu.ContextMenu(event);
1525
- if (decoration.breakpoint) {
1261
+ if (breakpoint) {
1526
1262
  contextMenu.debugSection().appendItem(
1527
1263
  i18nString(UIStrings.editBreakpoint),
1528
- this.editBreakpointCondition.bind(
1529
- this, editorLocation.lineNumber, decoration.breakpoint, null, false /* preferLogpoint */));
1264
+ this.editBreakpointCondition.bind(this, line, breakpoint, null, false /* preferLogpoint */));
1530
1265
  } else {
1266
+ const uiLocation = this.transformer.editorLocationToUILocation(line.number - 1, position - line.from);
1531
1267
  contextMenu.debugSection().appendItem(
1532
1268
  i18nString(UIStrings.addConditionalBreakpoint),
1533
- this.editBreakpointCondition.bind(
1534
- this, editorLocation.lineNumber, null, editorLocation, false /* preferLogpoint */));
1269
+ this.editBreakpointCondition.bind(this, line, null, uiLocation, false /* preferLogpoint */));
1535
1270
  contextMenu.debugSection().appendItem(
1536
1271
  i18nString(UIStrings.addLogpoint),
1537
- this.editBreakpointCondition.bind(
1538
- this, editorLocation.lineNumber, null, editorLocation, true /* preferLogpoint */));
1272
+ this.editBreakpointCondition.bind(this, line, null, uiLocation, true /* preferLogpoint */));
1273
+
1539
1274
  contextMenu.debugSection().appendItem(
1540
1275
  i18nString(UIStrings.neverPauseHere),
1541
- this.setBreakpoint.bind(this, location.lineNumber, location.columnNumber, 'false', true));
1276
+ this.setBreakpoint.bind(this, uiLocation.lineNumber, uiLocation.columnNumber, 'false', true));
1542
1277
  }
1543
1278
  contextMenu.show();
1544
1279
  }
1545
1280
 
1546
- private shouldIgnoreExternalBreakpointEvents(
1547
- event: Common.EventTarget.EventTargetEvent<Bindings.BreakpointManager.BreakpointLocation>): boolean {
1548
- const {uiLocation} = event.data;
1549
- if (uiLocation.uiSourceCode !== this.uiSourceCode) {
1550
- return true;
1551
- }
1552
- if (this.muted) {
1553
- return true;
1554
- }
1555
- for (const scriptFile of this.scriptFileForDebuggerModel.values()) {
1556
- if (scriptFile.isDivergingFromVM() || scriptFile.isMergingToVM()) {
1557
- return true;
1558
- }
1559
- }
1560
- return false;
1561
- }
1562
-
1563
- private breakpointAdded(event: Common.EventTarget.EventTargetEvent<Bindings.BreakpointManager.BreakpointLocation>):
1564
- void {
1565
- if (this.shouldIgnoreExternalBreakpointEvents(event)) {
1566
- return;
1567
- }
1568
- const {breakpoint, uiLocation} = event.data;
1569
- this.addBreakpoint(uiLocation, breakpoint);
1570
- }
1571
-
1572
- private addBreakpoint(
1573
- uiLocation: Workspace.UISourceCode.UILocation, breakpoint: Bindings.BreakpointManager.Breakpoint): void {
1574
- const editorLocation = this.transformer.uiLocationToEditorLocation(uiLocation.lineNumber, uiLocation.columnNumber);
1575
- const lineDecorations = this.lineBreakpointDecorations(uiLocation.lineNumber);
1576
- let decoration = this.breakpointDecoration(editorLocation.lineNumber, editorLocation.columnNumber);
1577
- if (decoration) {
1578
- decoration.breakpoint = breakpoint;
1579
- decoration.condition = breakpoint.condition();
1580
- decoration.enabled = breakpoint.enabled();
1581
- } else {
1582
- const handle = this.textEditor.textEditorPositionHandle(editorLocation.lineNumber, editorLocation.columnNumber);
1583
- decoration = new BreakpointDecoration(
1584
- this.textEditor, handle, breakpoint.condition(), breakpoint.enabled(),
1585
- breakpoint.bound() || !breakpoint.hasBoundScript(), breakpoint);
1586
- decoration.element.addEventListener('click', this.inlineBreakpointClick.bind(this, decoration), true);
1587
- decoration.element.addEventListener('contextmenu', this.inlineBreakpointContextMenu.bind(this, decoration), true);
1588
- this.breakpointDecorations.add(decoration);
1589
- }
1590
-
1591
- let uiLocationsForBreakpoint = this.decorationByBreakpoint.get(breakpoint);
1592
- if (!uiLocationsForBreakpoint) {
1593
- uiLocationsForBreakpoint = new Map();
1594
- this.decorationByBreakpoint.set(breakpoint, uiLocationsForBreakpoint);
1595
- }
1596
- uiLocationsForBreakpoint.set(uiLocation.id(), decoration);
1597
-
1598
- this.updateBreakpointDecoration(decoration);
1599
- if (breakpoint.enabled() && !lineDecorations.length) {
1600
- this.possibleBreakpointsRequested.add(editorLocation.lineNumber);
1601
- const start = this.transformer.editorLocationToUILocation(editorLocation.lineNumber, 0);
1602
- const end = this.transformer.editorLocationToUILocation(editorLocation.lineNumber + 1, 0);
1603
- this.breakpointManager
1604
- .possibleBreakpoints(
1605
- this.uiSourceCode,
1606
- new TextUtils.TextRange.TextRange(
1607
- start.lineNumber, start.columnNumber || 0, end.lineNumber, end.columnNumber || 0))
1608
- .then(addInlineDecorations.bind(this, editorLocation.lineNumber));
1609
- }
1610
-
1611
- function addInlineDecorations(
1612
- this: DebuggerPlugin, editorLineNumber: number, possibleLocations: Workspace.UISourceCode.UILocation[]): void {
1613
- this.possibleBreakpointsRequested.delete(editorLineNumber);
1614
- const decorations = this.lineBreakpointDecorations(editorLineNumber);
1615
- for (const decoration of decorations) {
1616
- this.updateBreakpointDecoration(decoration);
1617
- }
1618
- if (!decorations.some(decoration => Boolean(decoration.breakpoint))) {
1619
- return;
1620
- }
1621
- const columns = new Set<number>();
1622
- for (const decoration of decorations) {
1623
- const editorLocation = decoration.handle.resolve();
1624
- if (!editorLocation) {
1625
- continue;
1626
- }
1627
- columns.add(editorLocation.columnNumber);
1628
- }
1629
- // Only consider the first 100 inline breakpoints, as DevTools might appear to hang while CodeMirror is updating
1630
- // the inline breakpoints. See crbug.com/1060105.
1631
- for (const location of possibleLocations.slice(0, 100)) {
1632
- const editorLocation = this.transformer.uiLocationToEditorLocation(location.lineNumber, location.columnNumber);
1633
- if (editorLocation.lineNumber !== editorLineNumber) {
1634
- continue;
1635
- }
1636
- if (columns.has(editorLocation.columnNumber)) {
1637
- continue;
1638
- }
1639
- const handle = this.textEditor.textEditorPositionHandle(editorLocation.lineNumber, editorLocation.columnNumber);
1640
- const decoration = new BreakpointDecoration(
1641
- this.textEditor, handle, '', /** enabled */ false, /** bound */ false, /** breakpoint */ null);
1642
- decoration.element.addEventListener('click', this.inlineBreakpointClick.bind(this, decoration), true);
1643
- decoration.element.addEventListener(
1644
- 'contextmenu', this.inlineBreakpointContextMenu.bind(this, decoration), true);
1645
- this.breakpointDecorations.add(decoration);
1646
- this.updateBreakpointDecoration(decoration);
1647
- }
1648
- }
1649
- }
1650
-
1651
- private breakpointRemoved(event: Common.EventTarget.EventTargetEvent<Bindings.BreakpointManager.BreakpointLocation>):
1652
- void {
1653
- if (this.shouldIgnoreExternalBreakpointEvents(event)) {
1654
- return;
1655
- }
1656
- const {breakpoint, uiLocation} = event.data;
1657
-
1658
- const uiLocationsForBreakpoint = this.decorationByBreakpoint.get(breakpoint);
1659
- if (!uiLocationsForBreakpoint) {
1660
- return;
1661
- }
1662
-
1663
- const decoration = uiLocationsForBreakpoint.get(uiLocation.id());
1664
- uiLocationsForBreakpoint.delete(uiLocation.id());
1665
-
1666
- if (uiLocationsForBreakpoint.size === 0) {
1667
- this.decorationByBreakpoint.delete(breakpoint);
1668
- }
1669
-
1670
- if (!decoration) {
1671
- return;
1672
- }
1673
-
1674
- const editorLocation = this.transformer.uiLocationToEditorLocation(uiLocation.lineNumber, uiLocation.columnNumber);
1675
- decoration.breakpoint = null;
1676
- decoration.enabled = false;
1677
-
1678
- const lineDecorations = this.lineBreakpointDecorations(editorLocation.lineNumber);
1679
- if (!lineDecorations.some(decoration => Boolean(decoration.breakpoint))) {
1680
- for (const lineDecoration of lineDecorations) {
1681
- this.breakpointDecorations.delete(lineDecoration);
1682
- this.updateBreakpointDecoration(lineDecoration);
1683
- }
1684
- } else {
1685
- this.updateBreakpointDecoration(decoration);
1686
- }
1687
- }
1688
-
1689
- private initializeBreakpoints(): void {
1690
- const breakpointLocations = this.breakpointManager.breakpointLocationsForUISourceCode(this.uiSourceCode);
1691
- for (const breakpointLocation of breakpointLocations) {
1692
- this.addBreakpoint(breakpointLocation.uiLocation, breakpointLocation.breakpoint);
1693
- }
1694
- }
1695
-
1696
- private updateLinesWithoutMappingHighlight(): void {
1697
- if (Bindings.CompilerScriptMapping.CompilerScriptMapping.uiSourceCodeOrigin(this.uiSourceCode).length) {
1698
- const linesCount = this.textEditor.linesCount;
1699
- for (let i = 0; i < linesCount; ++i) {
1700
- const lineHasMapping =
1701
- Bindings.CompilerScriptMapping.CompilerScriptMapping.uiLineHasMapping(this.uiSourceCode, i);
1702
- if (!lineHasMapping) {
1703
- this.hasLineWithoutMapping = true;
1704
- }
1705
- if (this.hasLineWithoutMapping) {
1706
- this.textEditor.toggleLineClass(i, 'cm-non-breakable-line', !lineHasMapping);
1707
- }
1708
- }
1709
- return;
1710
- }
1711
-
1712
- const {pluginManager} = Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance();
1713
- if (pluginManager) {
1714
- pluginManager.getMappedLines(this.uiSourceCode)
1715
- .then(mappedLines => {
1716
- if (mappedLines === undefined) {
1717
- return;
1718
- }
1719
- const linesCount = this.textEditor.linesCount;
1720
- for (let i = 0; i < linesCount; ++i) {
1721
- const lineHasMapping = mappedLines.has(i);
1722
- if (!lineHasMapping) {
1723
- this.hasLineWithoutMapping = true;
1724
- }
1725
- if (this.hasLineWithoutMapping) {
1726
- this.textEditor.toggleLineClass(i, 'cm-non-breakable-line', !lineHasMapping);
1727
- }
1728
- }
1729
- })
1730
- .catch(console.error);
1731
- }
1732
- }
1733
-
1734
1281
  private updateScriptFiles(): void {
1735
1282
  for (const debuggerModel of SDK.TargetManager.TargetManager.instance().models(SDK.DebuggerModel.DebuggerModel)) {
1736
1283
  const scriptFile = Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance().scriptFile(
@@ -1751,8 +1298,8 @@ export class DebuggerPlugin extends Plugin {
1751
1298
  Bindings.ResourceScriptMapping.ResourceScriptFile.Events.DidMergeToVM, this.didMergeToVM, this);
1752
1299
  oldScriptFile.removeEventListener(
1753
1300
  Bindings.ResourceScriptMapping.ResourceScriptFile.Events.DidDivergeFromVM, this.didDivergeFromVM, this);
1754
- if (this.muted && !this.uiSourceCode.isDirty()) {
1755
- this.restoreBreakpointsIfConsistentScripts();
1301
+ if (this.muted && !this.uiSourceCode.isDirty() && this.consistentScripts) {
1302
+ this.setMuted(false);
1756
1303
  }
1757
1304
  }
1758
1305
  if (!newScriptFile) {
@@ -1784,9 +1331,10 @@ export class DebuggerPlugin extends Plugin {
1784
1331
  PH1: String(UI.ShortcutRegistry.ShortcutRegistry.instance().shortcutTitleForAction('quickOpen.show')),
1785
1332
  }));
1786
1333
  this.sourceMapInfobar.setCloseCallback(() => {
1334
+ this.removeInfobar(this.sourceMapInfobar);
1787
1335
  this.sourceMapInfobar = null;
1788
1336
  });
1789
- this.textEditor.attachInfobar(this.sourceMapInfobar);
1337
+ this.attachInfobar(this.sourceMapInfobar);
1790
1338
  }
1791
1339
 
1792
1340
  private async detectMinified(): Promise<void> {
@@ -1817,12 +1365,13 @@ export class DebuggerPlugin extends Plugin {
1817
1365
  }
1818
1366
 
1819
1367
  this.prettyPrintInfobar.setCloseCallback(() => {
1368
+ this.removeInfobar(this.prettyPrintInfobar);
1820
1369
  this.prettyPrintInfobar = null;
1821
1370
  });
1822
1371
  const toolbar = new UI.Toolbar.Toolbar('');
1823
1372
  const button = new UI.Toolbar.ToolbarButton('', 'largeicon-pretty-print');
1824
1373
  toolbar.appendToolbarItem(button);
1825
- toolbar.element.style.display = 'inline-block';
1374
+ toolbar.element.style.display = 'inline';
1826
1375
  toolbar.element.style.verticalAlign = 'middle';
1827
1376
  toolbar.element.style.marginBottom = '3px';
1828
1377
  toolbar.element.style.pointerEvents = 'none';
@@ -1831,38 +1380,27 @@ export class DebuggerPlugin extends Plugin {
1831
1380
  element.appendChild(
1832
1381
  i18n.i18n.getFormatLocalizedString(str_, UIStrings.prettyprintingWillFormatThisFile, {PH1: toolbar.element}));
1833
1382
  UI.ARIAUtils.markAsAlert(element);
1834
- this.textEditor.attachInfobar(this.prettyPrintInfobar);
1383
+ this.attachInfobar(this.prettyPrintInfobar);
1835
1384
  }
1836
1385
 
1837
- private async handleGutterClick(
1838
- event: Common.EventTarget.EventTargetEvent<SourceFrame.SourcesTextEditor.GutterClickEventData>): Promise<void> {
1839
- if (this.muted) {
1840
- return;
1841
- }
1842
-
1843
- const {gutterType, lineNumber: editorLineNumber, event: eventObject} = event.data;
1844
- if (gutterType !== SourceFrame.SourcesTextEditor.lineNumbersGutterType) {
1845
- return;
1386
+ private handleGutterClick(line: CodeMirror.Line, event: MouseEvent): boolean {
1387
+ if (this.muted || event.button !== 0 || event.altKey || event.ctrlKey || event.metaKey) {
1388
+ return false;
1846
1389
  }
1390
+ this.toggleBreakpoint(line, event.shiftKey);
1391
+ return true;
1392
+ }
1847
1393
 
1848
- if (eventObject.button !== 0 || eventObject.altKey || eventObject.ctrlKey || eventObject.metaKey) {
1394
+ private async toggleBreakpoint(line: CodeMirror.Line, onlyDisable: boolean): Promise<void> {
1395
+ if (this.muted) {
1849
1396
  return;
1850
1397
  }
1851
-
1852
- await this.toggleBreakpoint(editorLineNumber, eventObject.shiftKey);
1853
- eventObject.consume(true);
1854
- }
1855
-
1856
- private async toggleBreakpoint(editorLineNumber: number, onlyDisable: boolean): Promise<void> {
1857
- const decorations = this.lineBreakpointDecorations(editorLineNumber);
1858
- if (!decorations.length) {
1859
- await this.createNewBreakpoint(editorLineNumber, '', true);
1398
+ const breakpoints = this.lineBreakpoints(line);
1399
+ if (!breakpoints.length) {
1400
+ await this.createNewBreakpoint(line, '', true);
1860
1401
  return;
1861
1402
  }
1862
- const hasDisabled = this.textEditor.hasLineClass(editorLineNumber, 'cm-breakpoint-disabled');
1863
- const breakpoints =
1864
- (decorations.map(decoration => decoration.breakpoint).filter(breakpoint => Boolean(breakpoint)) as
1865
- Bindings.BreakpointManager.Breakpoint[]);
1403
+ const hasDisabled = breakpoints.some(b => !b.enabled());
1866
1404
  for (const breakpoint of breakpoints) {
1867
1405
  if (onlyDisable) {
1868
1406
  breakpoint.setEnabled(hasDisabled);
@@ -1872,12 +1410,12 @@ export class DebuggerPlugin extends Plugin {
1872
1410
  }
1873
1411
  }
1874
1412
 
1875
- private async createNewBreakpoint(editorLineNumber: number, condition: string, enabled: boolean): Promise<void> {
1876
- if (this.textEditor.hasLineClass(editorLineNumber, 'cm-non-breakable-line')) {
1413
+ private async createNewBreakpoint(line: CodeMirror.Line, condition: string, enabled: boolean): Promise<void> {
1414
+ if (!this.editor || !SourceFrame.SourceFrame.isBreakableLine(this.editor.state, line)) {
1877
1415
  return;
1878
1416
  }
1879
1417
  Host.userMetrics.actionTaken(Host.UserMetrics.Action.ScriptsBreakpointSet);
1880
- const origin = this.transformer.editorLocationToUILocation(editorLineNumber);
1418
+ const origin = this.transformer.editorLocationToUILocation(line.number - 1);
1881
1419
  await this.setBreakpoint(origin.lineNumber, origin.columnNumber, condition, enabled);
1882
1420
  }
1883
1421
 
@@ -1896,25 +1434,48 @@ export class DebuggerPlugin extends Plugin {
1896
1434
  this.liveLocationPool.disposeAll();
1897
1435
  const callFrame = UI.Context.Context.instance().flavor(SDK.DebuggerModel.CallFrame);
1898
1436
  if (!callFrame) {
1899
- this.clearExecutionLine();
1900
- return;
1437
+ this.setExecutionLocation(null);
1438
+ } else {
1439
+ await Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance().createCallFrameLiveLocation(
1440
+ callFrame.location(), async(liveLocation: Bindings.LiveLocation.LiveLocation): Promise<void> => {
1441
+ const uiLocation = await liveLocation.uiLocation();
1442
+ if (uiLocation && uiLocation.uiSourceCode.url() === this.uiSourceCode.url()) {
1443
+ this.setExecutionLocation(uiLocation);
1444
+ } else {
1445
+ this.setExecutionLocation(null);
1446
+ }
1447
+ }, this.liveLocationPool);
1901
1448
  }
1902
- await Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance().createCallFrameLiveLocation(
1903
- callFrame.location(), this.executionLineChanged.bind(this), this.liveLocationPool);
1904
1449
  }
1905
1450
 
1906
- dispose(): void {
1907
- for (const decoration of this.breakpointDecorations) {
1908
- decoration.dispose();
1451
+ private setExecutionLocation(executionLocation: Workspace.UISourceCode.UILocation|null): void {
1452
+ if (this.executionLocation === executionLocation || !this.editor) {
1453
+ return;
1909
1454
  }
1910
- this.breakpointDecorations.clear();
1911
- if (this.scheduledBreakpointDecorationUpdates) {
1912
- for (const decoration of this.scheduledBreakpointDecorationUpdates) {
1913
- decoration.dispose();
1455
+ this.executionLocation = executionLocation;
1456
+
1457
+ if (executionLocation) {
1458
+ const editorLocation =
1459
+ this.transformer.uiLocationToEditorLocation(executionLocation.lineNumber, executionLocation.columnNumber);
1460
+ const decorations =
1461
+ this.computeExecutionDecorations(this.editor.state, editorLocation.lineNumber, editorLocation.columnNumber);
1462
+ this.editor.dispatch({effects: executionLine.update.of(decorations)});
1463
+ this.updateValueDecorations();
1464
+ if (this.controlDown) {
1465
+ this.showContinueToLocations();
1914
1466
  }
1915
- this.scheduledBreakpointDecorationUpdates.clear();
1467
+ } else {
1468
+ this.editor.dispatch({
1469
+ effects: [
1470
+ executionLine.update.of(CodeMirror.Decoration.none),
1471
+ continueToMarkers.update.of(CodeMirror.Decoration.none),
1472
+ valueDecorations.update.of(CodeMirror.Decoration.none),
1473
+ ],
1474
+ });
1916
1475
  }
1476
+ }
1917
1477
 
1478
+ dispose(): void {
1918
1479
  this.hideIgnoreListInfobar();
1919
1480
  if (this.sourceMapInfobar) {
1920
1481
  this.sourceMapInfobar.dispose();
@@ -1922,7 +1483,6 @@ export class DebuggerPlugin extends Plugin {
1922
1483
  if (this.prettyPrintInfobar) {
1923
1484
  this.prettyPrintInfobar.dispose();
1924
1485
  }
1925
- this.scriptsPanel.element.removeEventListener('scroll', this.boundPopoverHelperHide, true);
1926
1486
  for (const script of this.scriptFileForDebuggerModel.values()) {
1927
1487
  script.removeEventListener(
1928
1488
  Bindings.ResourceScriptMapping.ResourceScriptFile.Events.DidMergeToVM, this.didMergeToVM, this);
@@ -1931,21 +1491,13 @@ export class DebuggerPlugin extends Plugin {
1931
1491
  }
1932
1492
  this.scriptFileForDebuggerModel.clear();
1933
1493
 
1934
- this.textEditor.element.removeEventListener('keydown', this.boundKeyDown, true);
1935
- this.textEditor.element.removeEventListener('keyup', this.boundKeyUp, true);
1936
- this.textEditor.element.removeEventListener('mousemove', this.boundMouseMove, false);
1937
- this.textEditor.element.removeEventListener('mousedown', this.boundMouseDown, true);
1938
- this.textEditor.element.removeEventListener('focusout', this.boundBlur, false);
1939
- this.textEditor.element.removeEventListener('wheel', this.boundWheel, true);
1940
-
1941
- this.textEditor.removeEventListener(SourceFrame.SourcesTextEditor.Events.GutterClick, this.boundGutterClick, this);
1942
1494
  this.popoverHelper.hidePopover();
1943
1495
  this.popoverHelper.dispose();
1944
1496
 
1945
1497
  this.breakpointManager.removeEventListener(
1946
- Bindings.BreakpointManager.Events.BreakpointAdded, this.breakpointAdded, this);
1498
+ Bindings.BreakpointManager.Events.BreakpointAdded, this.breakpointChange, this);
1947
1499
  this.breakpointManager.removeEventListener(
1948
- Bindings.BreakpointManager.Events.BreakpointRemoved, this.breakpointRemoved, this);
1500
+ Bindings.BreakpointManager.Events.BreakpointRemoved, this.breakpointChange, this);
1949
1501
  this.uiSourceCode.removeEventListener(
1950
1502
  Workspace.UISourceCode.Events.WorkingCopyChanged, this.workingCopyChanged, this);
1951
1503
  this.uiSourceCode.removeEventListener(
@@ -1959,98 +1511,469 @@ export class DebuggerPlugin extends Plugin {
1959
1511
  .removeChangeListener(this.showIgnoreListInfobarIfNeeded, this);
1960
1512
  super.dispose();
1961
1513
 
1962
- this.clearExecutionLine();
1963
1514
  UI.Context.Context.instance().removeFlavorChangeListener(SDK.DebuggerModel.CallFrame, this.callFrameChanged, this);
1964
1515
  this.liveLocationPool.disposeAll();
1965
1516
  }
1966
1517
  }
1967
1518
 
1968
- export class BreakpointDecoration {
1969
- private textEditor: SourceFrame.SourcesTextEditor.SourcesTextEditor;
1970
- handle: TextEditor.CodeMirrorTextEditor.TextEditorPositionHandle;
1971
- condition: string;
1972
- enabled: boolean;
1973
- bound: boolean;
1974
- breakpoint: Bindings.BreakpointManager.Breakpoint|null;
1975
- element: HTMLSpanElement;
1976
- bookmark: TextEditor.CodeMirrorTextEditor.TextEditorBookMark|null;
1977
- constructor(
1978
- textEditor: SourceFrame.SourcesTextEditor.SourcesTextEditor,
1979
- handle: TextEditor.CodeMirrorTextEditor.TextEditorPositionHandle, condition: string, enabled: boolean,
1980
- bound: boolean, breakpoint: Bindings.BreakpointManager.Breakpoint|null) {
1981
- this.textEditor = textEditor;
1982
- this.handle = handle;
1983
- this.condition = condition;
1984
- this.enabled = enabled;
1985
- this.bound = bound;
1986
- this.breakpoint = breakpoint;
1987
- this.element = document.createElement('span');
1988
- this.element.classList.toggle('cm-inline-breakpoint', true);
1989
-
1990
- this.bookmark = null;
1519
+ // Infobar panel state
1520
+
1521
+ const addInfobar = CodeMirror.StateEffect.define<UI.Infobar.Infobar>();
1522
+ const removeInfobar = CodeMirror.StateEffect.define<UI.Infobar.Infobar>();
1523
+
1524
+ const infobarState = CodeMirror.StateField.define<UI.Infobar.Infobar[]>({
1525
+ create(): UI.Infobar.Infobar[] {
1526
+ return [];
1527
+ },
1528
+ update(current, tr): UI.Infobar.Infobar[] {
1529
+ for (const effect of tr.effects) {
1530
+ if (effect.is(addInfobar)) {
1531
+ current = current.concat(effect.value);
1532
+ } else if (effect.is(removeInfobar)) {
1533
+ current = current.filter(b => b !== effect.value);
1534
+ }
1535
+ }
1536
+ return current;
1537
+ },
1538
+ provide: (field): CodeMirror.Extension => CodeMirror.showPanel.computeN(
1539
+ [field],
1540
+ (state): (() => CodeMirror.Panel)[] =>
1541
+ state.field(field).map((bar): (() => CodeMirror.Panel) => (): CodeMirror.Panel => ({dom: bar.element}))),
1542
+ });
1543
+
1544
+ // Enumerate non-breakable lines (lines without a known corresponding
1545
+ // position in the UISource).
1546
+ async function computeNonBreakableLines(
1547
+ state: CodeMirror.EditorState, sourceCode: Workspace.UISourceCode.UISourceCode): Promise<readonly number[]> {
1548
+ const linePositions = [];
1549
+ if (Bindings.CompilerScriptMapping.CompilerScriptMapping.uiSourceCodeOrigin(sourceCode).length) {
1550
+ for (let i = 0; i < state.doc.lines; i++) {
1551
+ const lineHasMapping = Bindings.CompilerScriptMapping.CompilerScriptMapping.uiLineHasMapping(sourceCode, i);
1552
+ if (!lineHasMapping) {
1553
+ linePositions.push(state.doc.line(i + 1).from);
1554
+ }
1555
+ }
1556
+ } else {
1557
+ const {pluginManager} = Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance();
1558
+ if (!pluginManager) {
1559
+ return [];
1560
+ }
1561
+ const mappedLines = await pluginManager.getMappedLines(sourceCode);
1562
+ if (!mappedLines) {
1563
+ return [];
1564
+ }
1565
+ for (let i = 0; i < state.doc.lines; i++) {
1566
+ if (!mappedLines.has(i)) {
1567
+ linePositions.push(state.doc.line(i + 1).from);
1568
+ }
1569
+ }
1991
1570
  }
1571
+ return linePositions;
1572
+ }
1992
1573
 
1993
- static mostSpecificFirst(decoration1: BreakpointDecoration, decoration2: BreakpointDecoration): number {
1994
- if (decoration1.enabled !== decoration2.enabled) {
1995
- return decoration1.enabled ? -1 : 1;
1574
+ // Breakpoint markers
1575
+
1576
+ type BreakpointDecoration = {
1577
+ content: CodeMirror.DecorationSet,
1578
+ gutter: CodeMirror.RangeSet<CodeMirror.GutterMarker>,
1579
+ };
1580
+
1581
+ const setBreakpointDeco = CodeMirror.StateEffect.define<BreakpointDecoration>();
1582
+ const muteBreakpoints = CodeMirror.StateEffect.define<null>();
1583
+
1584
+ function muteGutterMarkers(markers: CodeMirror.RangeSet<CodeMirror.GutterMarker>, doc: CodeMirror.Text):
1585
+ CodeMirror.RangeSet<CodeMirror.GutterMarker> {
1586
+ const newMarkers: CodeMirror.Range<CodeMirror.GutterMarker>[] = [];
1587
+ markers.between(0, doc.length, (from, _to, marker) => {
1588
+ let className: string = marker.elementClass;
1589
+ if (!/cm-breakpoint-disabled/.test(className)) {
1590
+ className += ' cm-breakpoint-disabled';
1996
1591
  }
1997
- if (decoration1.bound !== decoration2.bound) {
1998
- return decoration1.bound ? -1 : 1;
1592
+ newMarkers.push(new BreakpointGutterMarker(className).range(from));
1593
+ });
1594
+ return CodeMirror.RangeSet.of(newMarkers, false);
1595
+ }
1596
+
1597
+ // Holds the inline breakpoint marker decorations and the gutter
1598
+ // markers for lines with breakpoints. When the set of active markers
1599
+ // changes in non-muted state (the editor content matches the original
1600
+ // file), it is recomputed and updated with `setBreakpointDeco`. When
1601
+ // the editor content goes out of sync with the original file, the
1602
+ // `muteBreakpoints` effect hides the inline markers and makes sure
1603
+ // all gutter markers are displayed as disabled.
1604
+ const breakpointMarkers = CodeMirror.StateField.define<BreakpointDecoration>({
1605
+ create(): BreakpointDecoration {
1606
+ return {content: CodeMirror.RangeSet.empty, gutter: CodeMirror.RangeSet.empty};
1607
+ },
1608
+ update(deco, tr): BreakpointDecoration {
1609
+ if (!tr.changes.empty) {
1610
+ deco = {content: deco.content.map(tr.changes), gutter: deco.gutter.map(tr.changes)};
1611
+ }
1612
+ for (const effect of tr.effects) {
1613
+ if (effect.is(setBreakpointDeco)) {
1614
+ deco = effect.value;
1615
+ } else if (effect.is(muteBreakpoints)) {
1616
+ deco = {content: CodeMirror.RangeSet.empty, gutter: muteGutterMarkers(deco.gutter, tr.state.doc)};
1617
+ }
1618
+ }
1619
+ return deco;
1620
+ },
1621
+ provide: field =>
1622
+ [CodeMirror.EditorView.decorations.from(field, deco => deco.content),
1623
+ CodeMirror.lineNumberMarkers.from(field, deco => deco.gutter)],
1624
+ });
1625
+
1626
+ class BreakpointInlineMarker extends CodeMirror.WidgetType {
1627
+ class: string;
1628
+
1629
+ constructor(readonly breakpoint: Bindings.BreakpointManager.Breakpoint|null, readonly parent: DebuggerPlugin) {
1630
+ super();
1631
+ // Eagerly compute DOM class so that the widget is recreated when it changes.
1632
+ this.class = 'cm-inlineBreakpoint';
1633
+ const condition = breakpoint ? breakpoint.condition() : '';
1634
+ if (condition.includes(LogpointPrefix)) {
1635
+ this.class += ' cm-inlineBreakpoint-logpoint';
1636
+ } else if (condition) {
1637
+ this.class += ' cm-inlineBreakpoint-conditional';
1999
1638
  }
2000
- if (Boolean(decoration1.condition) !== Boolean(decoration2.condition)) {
2001
- return Boolean(decoration1.condition) ? -1 : 1;
1639
+ if (!breakpoint?.enabled()) {
1640
+ this.class += ' cm-inlineBreakpoint-disabled';
2002
1641
  }
2003
- return 0;
2004
1642
  }
2005
1643
 
2006
- update(): void {
2007
- const isLogpoint = Boolean(this.condition) && this.condition.includes(LogpointPrefix);
2008
- const isConditionalBreakpoint = Boolean(this.condition) && !isLogpoint;
2009
- this.element.classList.toggle('cm-inline-logpoint', isLogpoint);
2010
- this.element.classList.toggle('cm-inline-breakpoint-conditional', isConditionalBreakpoint);
2011
- this.element.classList.toggle('cm-inline-disabled', !this.enabled);
1644
+ eq(other: BreakpointInlineMarker): boolean {
1645
+ return other.class === this.class && other.breakpoint === this.breakpoint;
2012
1646
  }
2013
1647
 
2014
- show(): void {
2015
- if (this.bookmark) {
2016
- return;
2017
- }
2018
- const editorLocation = this.handle.resolve();
2019
- if (!editorLocation) {
2020
- return;
2021
- }
2022
- this.bookmark = this.textEditor.addBookmark(
2023
- editorLocation.lineNumber, editorLocation.columnNumber, this.element, BreakpointDecoration.bookmarkSymbol);
2024
- // @ts-ignore Only used for layout tests
2025
- this.bookmark[BreakpointDecoration.elementSymbolForTest] = this.element;
1648
+ toDOM(): HTMLElement {
1649
+ const span = document.createElement('span');
1650
+ span.className = this.class;
1651
+ span.addEventListener('click', (event: MouseEvent) => {
1652
+ this.parent.onInlineBreakpointMarkerClick(event, this.breakpoint);
1653
+ event.consume();
1654
+ });
1655
+ span.addEventListener('contextmenu', (event: MouseEvent) => {
1656
+ this.parent.onInlineBreakpointMarkerContextMenu(event, this.breakpoint);
1657
+ event.consume();
1658
+ });
1659
+ return span;
2026
1660
  }
2027
1661
 
2028
- hide(): void {
2029
- if (!this.bookmark) {
2030
- return;
2031
- }
2032
- this.bookmark.clear();
2033
- this.bookmark = null;
1662
+ ignoreEvent(): boolean {
1663
+ return true;
2034
1664
  }
1665
+ }
2035
1666
 
2036
- dispose(): void {
2037
- const location = this.handle.resolve();
2038
- if (location) {
2039
- this.textEditor.toggleLineClass(location.lineNumber, 'cm-breakpoint', false);
2040
- this.textEditor.toggleLineClass(location.lineNumber, 'cm-breakpoint-disabled', false);
2041
- this.textEditor.toggleLineClass(location.lineNumber, 'cm-breakpoint-unbound', false);
2042
- this.textEditor.toggleLineClass(location.lineNumber, 'cm-breakpoint-conditional', false);
2043
- this.textEditor.toggleLineClass(location.lineNumber, 'cm-breakpoint-logpoint', false);
1667
+ class BreakpointGutterMarker extends CodeMirror.GutterMarker {
1668
+ constructor(readonly elementClass: string) {
1669
+ super();
1670
+ }
1671
+
1672
+ eq(other: BreakpointGutterMarker): boolean {
1673
+ return other.elementClass === this.elementClass;
1674
+ }
1675
+ }
1676
+
1677
+ function mostSpecificBreakpoint(
1678
+ a: Bindings.BreakpointManager.Breakpoint, b: Bindings.BreakpointManager.Breakpoint): number {
1679
+ if (a.enabled() !== b.enabled()) {
1680
+ return a.enabled() ? -1 : 1;
1681
+ }
1682
+ if (a.bound() !== b.bound()) {
1683
+ return a.bound() ? -1 : 1;
1684
+ }
1685
+ if (Boolean(a.condition()) !== Boolean(b.condition())) {
1686
+ return Boolean(a.condition()) ? -1 : 1;
1687
+ }
1688
+ return 0;
1689
+ }
1690
+
1691
+ // Generic helper for creating pairs of editor state fields and
1692
+ // effects to model imperatively updated decorations.
1693
+
1694
+ function defineStatefulDecoration(): {
1695
+ update: CodeMirror.StateEffectType<CodeMirror.DecorationSet>,
1696
+ field: CodeMirror.StateField<CodeMirror.DecorationSet>,
1697
+ } {
1698
+ const update = CodeMirror.StateEffect.define<CodeMirror.DecorationSet>();
1699
+ const field = CodeMirror.StateField.define<CodeMirror.DecorationSet>({
1700
+ create(): CodeMirror.DecorationSet {
1701
+ return CodeMirror.Decoration.none;
1702
+ },
1703
+ update(deco, tr): CodeMirror.DecorationSet {
1704
+ return tr.effects.reduce((deco, effect) => effect.is(update) ? effect.value : deco, deco.map(tr.changes));
1705
+ },
1706
+ provide: field => CodeMirror.EditorView.decorations.from(field),
1707
+ });
1708
+ return {update, field};
1709
+ }
1710
+
1711
+ // Execution line highlight
1712
+
1713
+ const executionLineDeco = CodeMirror.Decoration.line({attributes: {class: 'cm-executionLine'}});
1714
+ const executionTokenDeco = CodeMirror.Decoration.mark({attributes: {class: 'cm-executionToken'}});
1715
+ const executionLine = defineStatefulDecoration();
1716
+
1717
+ // Continue-to markers
1718
+
1719
+ const continueToMark = CodeMirror.Decoration.mark({class: 'cm-continueToLocation'});
1720
+ const asyncContinueToMark = CodeMirror.Decoration.mark({class: 'cm-continueToLocation cm-continueToLocation-async'});
1721
+
1722
+ const continueToMarkers = defineStatefulDecoration();
1723
+
1724
+ const noMarkers = {}, hasContinueMarkers = {
1725
+ class: 'cm-hasContinueMarkers',
1726
+ };
1727
+
1728
+ // Add a class to the content element when there are active
1729
+ // continue-to markers. This hides the background on the current
1730
+ // execution line.
1731
+ const markIfContinueTo =
1732
+ CodeMirror.EditorView.contentAttributes.compute([continueToMarkers.field], (state): Record<string, string> => {
1733
+ return state.field(continueToMarkers.field).size ? hasContinueMarkers : noMarkers;
1734
+ });
1735
+
1736
+ // Variable value decorations
1737
+
1738
+ class ValueDecoration extends CodeMirror.WidgetType {
1739
+ constructor(readonly pairs: [string, SDK.RemoteObject.RemoteObject][]) {
1740
+ super();
1741
+ }
1742
+
1743
+ eq(other: ValueDecoration): boolean {
1744
+ return this.pairs.length === other.pairs.length &&
1745
+ this.pairs.every((p, i) => p[0] === other.pairs[i][0] && p[1] === other.pairs[i][1]);
1746
+ }
1747
+
1748
+ toDOM(): HTMLElement {
1749
+ const formatter = new ObjectUI.RemoteObjectPreviewFormatter.RemoteObjectPreviewFormatter();
1750
+ const widget = document.createElement('div');
1751
+ widget.classList.add('cm-variableValues');
1752
+ let first = true;
1753
+ for (const [name, value] of this.pairs) {
1754
+ if (first) {
1755
+ first = false;
1756
+ } else {
1757
+ UI.UIUtils.createTextChild(widget, ', ');
1758
+ }
1759
+ const nameValuePair = (widget.createChild('span') as HTMLElement);
1760
+ UI.UIUtils.createTextChild(nameValuePair, name + ' = ');
1761
+ const propertyCount = value.preview ? value.preview.properties.length : 0;
1762
+ const entryCount = value.preview && value.preview.entries ? value.preview.entries.length : 0;
1763
+ if (value.preview && propertyCount + entryCount < 10) {
1764
+ formatter.appendObjectPreview(nameValuePair, value.preview, false /* isEntry */);
1765
+ } else {
1766
+ const propertyValue = ObjectUI.ObjectPropertiesSection.ObjectPropertiesSection.createPropertyValue(
1767
+ value, /* wasThrown */ false, /* showPreview */ false);
1768
+ nameValuePair.appendChild(propertyValue.element);
1769
+ }
2044
1770
  }
2045
- this.hide();
1771
+ return widget;
2046
1772
  }
1773
+ }
2047
1774
 
2048
- // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
2049
- // eslint-disable-next-line @typescript-eslint/naming-convention
2050
- static readonly bookmarkSymbol = Symbol('bookmark');
2051
- // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
2052
- // eslint-disable-next-line @typescript-eslint/naming-convention
2053
- private static readonly elementSymbolForTest = Symbol('element');
1775
+ const valueDecorations = defineStatefulDecoration();
1776
+
1777
+ // Evaluated expression mark for pop-over
1778
+
1779
+ const evalExpressionMark = CodeMirror.Decoration.mark({class: 'cm-evaluatedExpression'});
1780
+
1781
+ const evalExpression = defineStatefulDecoration();
1782
+
1783
+ // Styling for plugin-local elements
1784
+
1785
+ const theme = CodeMirror.EditorView.baseTheme({
1786
+ '.cm-lineNumbers .cm-gutterElement': {
1787
+ '&:hover, &.cm-breakpoint': {
1788
+ borderStyle: 'solid',
1789
+ borderWidth: '1px 4px 1px 1px',
1790
+ marginRight: '-4px',
1791
+ paddingLeft: '8px',
1792
+ // Make sure text doesn't move down due to the border above it.
1793
+ lineHeight: 'calc(1.2em - 2px)',
1794
+ position: 'relative',
1795
+ },
1796
+ '&:hover': {
1797
+ WebkitBorderImage: lineNumberArrow('#ebeced', '#ebeced'),
1798
+ },
1799
+ '&.cm-breakpoint': {
1800
+ color: '#fff',
1801
+ WebkitBorderImage: lineNumberArrow('#4285f4', '#1a73e8'),
1802
+ },
1803
+ '&.cm-breakpoint-conditional': {
1804
+ WebkitBorderImage: lineNumberArrow('#f29900', '#e37400'),
1805
+ '&::before': {
1806
+ content: '"?"',
1807
+ position: 'absolute',
1808
+ top: 0,
1809
+ left: '1px',
1810
+ },
1811
+ },
1812
+ '&.cm-breakpoint-logpoint': {
1813
+ WebkitBorderImage: lineNumberArrow('#f439a0', '#d01884'),
1814
+ '&::before': {
1815
+ content: '"‥"',
1816
+ position: 'absolute',
1817
+ top: '-3px',
1818
+ left: '1px',
1819
+ },
1820
+ },
1821
+ },
1822
+ '&dark .cm-lineNumbers .cm-gutterElement': {
1823
+ '&:hover': {
1824
+ WebkitBorderImage: lineNumberArrow('#3c4043', '#3c4043'),
1825
+ },
1826
+ '&.cm-breakpoint': {
1827
+ WebkitBorderImage: lineNumberArrow('#5186EC', '#1a73e8'),
1828
+ },
1829
+ '&.cm-breakpoint-conditional': {
1830
+ WebkitBorderImage: lineNumberArrow('#e9a33a', '#e37400'),
1831
+ },
1832
+ '&.cm-breakpoint-logpoint': {
1833
+ WebkitBorderImage: lineNumberArrow('#E54D9B', '#d01884'),
1834
+ },
1835
+ },
1836
+ ':host-context(.breakpoints-deactivated) & .cm-lineNumbers .cm-gutterElement.cm-breakpoint, .cm-lineNumbers .cm-gutterElement.cm-breakpoint-disabled':
1837
+ {
1838
+ color: '#1a73e8',
1839
+ WebkitBorderImage: lineNumberArrow('#d9e7fd', '#1a73e8'),
1840
+ '&.cm-breakpoint-conditional': {
1841
+ color: '#e37400',
1842
+ WebkitBorderImage: lineNumberArrow('#fcebcc', '#e37400'),
1843
+ },
1844
+ '&.cm-breakpoint-logpoint': {
1845
+ color: '#d01884',
1846
+ WebkitBorderImage: lineNumberArrow('#fdd7ec', '#f439a0'),
1847
+ },
1848
+ },
1849
+ ':host-context(.breakpoints-deactivated) &dark .cm-lineNumbers .cm-gutterElement.cm-breakpoint, &dark .cm-lineNumbers .cm-gutterElement.cm-breakpoint-disabled':
1850
+ {
1851
+ WebkitBorderImage: lineNumberArrow('#2a384e', '#1a73e8'),
1852
+ '&.cm-breakpoint-conditional': {
1853
+ WebkitBorderImage: lineNumberArrow('#4d3c1d', '#e37400'),
1854
+ },
1855
+ '&.cm-breakpoint-logpoint': {
1856
+ WebkitBorderImage: lineNumberArrow('#4e283d', '#f439a0'),
1857
+ },
1858
+ },
1859
+
1860
+ '.cm-inlineBreakpoint': {
1861
+ cursor: 'pointer',
1862
+ position: 'relative',
1863
+ top: '1px',
1864
+ content: inlineBreakpointArrow('#4285F4', '#1A73E8'),
1865
+ height: '10px',
1866
+ '&.cm-inlineBreakpoint-conditional': {
1867
+ content: inlineConditionalBreakpointArrow('#F29900', '#E37400'),
1868
+ },
1869
+ '&.cm-inlineBreakpoint-logpoint': {
1870
+ content: inlineLogpointArrow('#F439A0', '#D01884'),
1871
+ },
1872
+ },
1873
+ '&dark .cm-inlineBreakpoint': {
1874
+ content: inlineBreakpointArrow('#5186EC', '#1A73E8'),
1875
+ '&.cm-inlineBreakpoint-conditional': {
1876
+ content: inlineConditionalBreakpointArrow('#e9a33a', '#E37400'),
1877
+ },
1878
+ '&.cm-inlineBreakpoint-logpoint': {
1879
+ content: inlineLogpointArrow('#E54D9B', '#D01884'),
1880
+ },
1881
+ },
1882
+ ':host-context(.breakpoints-deactivated) & .cm-inlineBreakpoint, .cm-inlineBreakpoint-disabled': {
1883
+ content: inlineBreakpointArrow('#4285F4', '#1A73E8', '0.2'),
1884
+ '&.cm-inlineBreakpoint-conditional': {
1885
+ content: inlineConditionalBreakpointArrow('#F9AB00', '#E37400', '0.2'),
1886
+ },
1887
+ '&.cm-inlineBreakpoint-logpoint': {
1888
+ content: inlineLogpointArrow('#F439A0', '#D01884', '0.2'),
1889
+ },
1890
+ },
1891
+
1892
+ '.cm-executionLine': {
1893
+ backgroundColor: 'var(--color-execution-line-background)',
1894
+ outline: '1px solid var(--color-execution-line-outline)',
1895
+ '.cm-hasContinueMarkers &': {
1896
+ backgroundColor: 'transparent',
1897
+ },
1898
+ '&.cm-highlightedLine': {
1899
+ animation: 'cm-fading-highlight-execution 2s 0s',
1900
+ },
1901
+ },
1902
+ '.cm-executionToken': {
1903
+ backgroundColor: 'var(--color-execution-token-background)',
1904
+ },
1905
+ '@keyframes cm-fading-highlight-execution': {
1906
+ from: {
1907
+ backgroundColor: 'var(--color-highlighted-line)',
1908
+ },
1909
+ to: {
1910
+ backgroundColor: 'var(--color-execution-line-background)',
1911
+ },
1912
+ },
1913
+
1914
+ '.cm-continueToLocation': {
1915
+ cursor: 'pointer',
1916
+ backgroundColor: 'var(--color-continue-to-location)',
1917
+ '&:hover': {
1918
+ backgroundColor: 'var(--color-continue-to-location-hover)',
1919
+ border: '1px solid var(--color-continue-to-location-hover-border)',
1920
+ margin: '0 -1px',
1921
+ },
1922
+ '&.cm-continueToLocation-async': {
1923
+ backgroundColor: 'var(--color-continue-to-location-async)',
1924
+ '&:hover': {
1925
+ backgroundColor: 'var(--color-continue-to-location-async-hover)',
1926
+ border: '1px solid var(--color-continue-to-location-async-hover-border)',
1927
+ margin: '0 -1px',
1928
+ },
1929
+ },
1930
+ },
1931
+
1932
+ '.cm-evaluatedExpression': {
1933
+ backgroundColor: 'var(--color-evaluated-expression)',
1934
+ border: '1px solid var(--color-evaluated-expression-border)',
1935
+ margin: '0 -1px',
1936
+ },
1937
+
1938
+ '.cm-variableValues': {
1939
+ display: 'inline',
1940
+ whiteSpace: 'nowrap',
1941
+ overflow: 'hidden',
1942
+ textOverflow: 'ellipsis',
1943
+ maxWidth: '1000px',
1944
+ opacity: '80%',
1945
+ backgroundColor: 'var(--color-variable-values)',
1946
+ marginLeft: '10px',
1947
+ padding: '0 5px',
1948
+ userSelect: 'text',
1949
+ '.cm-executionLine &': {
1950
+ backgroundColor: 'transparent',
1951
+ opacity: '50%',
1952
+ },
1953
+ },
1954
+ });
1955
+
1956
+ function lineNumberArrow(color: string, outline: string): string {
1957
+ return `url('data:image/svg+xml,<svg height="11" width="26" xmlns="http://www.w3.org/2000/svg"><path d="M22.8.5l2.7 5-2.7 5H.5V.5z" fill="${
1958
+ encodeURIComponent(color)}" stroke="${encodeURIComponent(outline)}"/></svg>') 1 3 1 1`;
2054
1959
  }
2055
1960
 
2056
- export const continueToLocationDecorationSymbol = Symbol('bookmark');
1961
+ function inlineBreakpointArrow(color: string, outline: string, opacity: string = '1'): string {
1962
+ return `url('data:image/svg+xml,<svg width="11" height="12" viewBox="0 0 11 12" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M0.5 0.5H5.80139C6.29382 0.5 6.7549 0.741701 7.03503 1.14669L10.392 6L7.03503 10.8533C6.7549 11.2583 6.29382 11.5 5.80139 11.5H0.5V0.5Z" fill="${
1963
+ encodeURIComponent(
1964
+ color)}" stroke="${encodeURIComponent(outline)}" fill-opacity="${encodeURIComponent(opacity)}"/></svg>')`;
1965
+ }
1966
+
1967
+ function inlineConditionalBreakpointArrow(color: string, outline: string, opacity: string = '1'): string {
1968
+ return `url('data:image/svg+xml,<svg width="11" height="12" viewBox="0 0 11 12" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M0.5 0.5H5.80139C6.29382 0.5 6.75489 0.741701 7.03503 1.14669L10.392 6L7.03503 10.8533C6.75489 11.2583 6.29382 11.5 5.80138 11.5H0.5V0.5Z" fill="${
1969
+ encodeURIComponent(color)}" fill-opacity="${encodeURIComponent(opacity)}" stroke="${
1970
+ encodeURIComponent(
1971
+ outline)}"/><path d="M3.51074 7.75635H4.68408V9H3.51074V7.75635ZM4.68408 7.23779H3.51074V6.56104C3.51074 6.271 3.55615 6.02344 3.64697 5.81836C3.73779 5.61328 3.90039 5.39648 4.13477 5.16797L4.53027 4.77686C4.71484 4.59814 4.83936 4.4502 4.90381 4.33301C4.97119 4.21582 5.00488 4.09424 5.00488 3.96826C5.00488 3.77197 4.9375 3.62402 4.80273 3.52441C4.66797 3.4248 4.46582 3.375 4.19629 3.375C3.9502 3.375 3.69238 3.42773 3.42285 3.5332C3.15625 3.63574 2.88232 3.78955 2.60107 3.99463V2.81689C2.88818 2.65283 3.17822 2.52979 3.47119 2.44775C3.76709 2.36279 4.06299 2.32031 4.35889 2.32031C4.95068 2.32031 5.41504 2.45801 5.75195 2.7334C6.08887 3.00879 6.25732 3.38818 6.25732 3.87158C6.25732 4.09424 6.20752 4.30225 6.10791 4.49561C6.0083 4.68604 5.8208 4.91602 5.54541 5.18555L5.15869 5.56348C4.95947 5.75684 4.83203 5.91504 4.77637 6.03809C4.7207 6.16113 4.69287 6.31201 4.69287 6.49072C4.69287 6.51709 4.69141 6.54785 4.68848 6.58301C4.68848 6.61816 4.68701 6.65625 4.68408 6.69727V7.23779Z" fill="white"/></svg>')`;
1972
+ }
1973
+
1974
+ function inlineLogpointArrow(color: string, outline: string, opacity: string = '1'): string {
1975
+ return `url('data:image/svg+xml,<svg width="11" height="12" viewBox="0 0 11 12" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M0.5 0.5H5.80139C6.29382 0.5 6.7549 0.741701 7.03503 1.14669L10.392 6L7.03503 10.8533C6.7549 11.2583 6.29382 11.5 5.80139 11.5H0.5V0.5Z" fill="${
1976
+ encodeURIComponent(color)}" stroke="${encodeURIComponent(outline)}" fill-opacity="${
1977
+ encodeURIComponent(
1978
+ opacity)}"/><circle cx="3" cy="6" r="1" fill="white"/><circle cx="7" cy="6" r="1" fill="white"/></svg>')`;
1979
+ }