| OLD | NEW |
| 1 // Copyright (c) 2015 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 /** | 4 /** |
| 5 * @implements {UI.ContextFlavorListener} | 5 * @implements {UI.ContextFlavorListener} |
| 6 * @unrestricted | 6 * @unrestricted |
| 7 */ | 7 */ |
| 8 Sources.JavaScriptBreakpointsSidebarPane = class extends UI.VBox { | 8 Sources.JavaScriptBreakpointsSidebarPane = class extends UI.ThrottledWidget { |
| 9 constructor() { | 9 constructor() { |
| 10 super(); | 10 super(true); |
| 11 this.registerRequiredCSS('components/breakpointsList.css'); | 11 this.registerRequiredCSS('components/breakpointsList.css'); |
| 12 | 12 |
| 13 this._breakpointManager = Bindings.breakpointManager; | 13 this._breakpointManager = Bindings.breakpointManager; |
| 14 | 14 this._breakpointManager.addEventListener( |
| 15 this._listElement = createElementWithClass('ol', 'breakpoint-list'); | 15 Bindings.BreakpointManager.Events.BreakpointAdded, this.update, this); |
| 16 | 16 this._breakpointManager.addEventListener( |
| 17 this.emptyElement = this.element.createChild('div', 'gray-info-message'); | 17 Bindings.BreakpointManager.Events.BreakpointRemoved, this.update, this); |
| 18 this.emptyElement.textContent = Common.UIString('No Breakpoints'); | 18 this._breakpointManager.addEventListener( |
| 19 | 19 Bindings.BreakpointManager.Events.BreakpointsActiveStateChanged, this.up
date, this); |
| 20 this._items = new Map(); | 20 |
| 21 | 21 /** @type {?Element} */ |
| 22 this._listElement = null; |
| 23 this.update(); |
| 24 } |
| 25 |
| 26 /** |
| 27 * @override |
| 28 * @return {!Promise<?>} |
| 29 */ |
| 30 doUpdate() { |
| 22 var breakpointLocations = this._breakpointManager.allBreakpointLocations(); | 31 var breakpointLocations = this._breakpointManager.allBreakpointLocations(); |
| 23 for (var i = 0; i < breakpointLocations.length; ++i) | 32 if (!breakpointLocations.length) { |
| 24 this._addBreakpoint(breakpointLocations[i].breakpoint, breakpointLocations
[i].uiLocation); | 33 this._listElement = null; |
| 25 | 34 this.contentElement.removeChildren(); |
| 26 this._breakpointManager.addEventListener( | 35 var emptyElement = this.contentElement.createChild('div', 'gray-info-messa
ge'); |
| 27 Bindings.BreakpointManager.Events.BreakpointAdded, this._breakpointAdded
, this); | 36 emptyElement.textContent = Common.UIString('No Breakpoints'); |
| 28 this._breakpointManager.addEventListener( | 37 this.contentElement.appendChild(emptyElement); |
| 29 Bindings.BreakpointManager.Events.BreakpointRemoved, this._breakpointRem
oved, this); | 38 this._didUpdateForTest(); |
| 30 | 39 return Promise.resolve(); |
| 31 this.emptyElement.addEventListener('contextmenu', this._emptyElementContextM
enu.bind(this), true); | 40 } |
| 32 this._breakpointManager.addEventListener( | 41 |
| 33 Bindings.BreakpointManager.Events.BreakpointsActiveStateChanged, this._b
reakpointsActiveStateChanged, this); | 42 if (!this._listElement) { |
| 34 this._breakpointsActiveStateChanged(); | 43 this.contentElement.removeChildren(); |
| 35 this._update(); | 44 this._listElement = this.contentElement.createChild('div'); |
| 36 } | 45 this.contentElement.appendChild(this._listElement); |
| 37 | 46 } |
| 38 _emptyElementContextMenu(event) { | 47 |
| 39 var contextMenu = new UI.ContextMenu(event); | 48 breakpointLocations.sort((item1, item2) => item1.uiLocation.compareTo(item2.
uiLocation)); |
| 40 this._appendBreakpointActiveItem(contextMenu); | 49 |
| 41 contextMenu.show(); | 50 /** @type {!Multimap<string, !{breakpoint: !Bindings.BreakpointManager.Break
point, uiLocation: !Workspace.UILocation}>} */ |
| 42 } | 51 var locationForEntry = new Multimap(); |
| 43 | 52 for (var breakpointLocation of breakpointLocations) { |
| 44 /** | 53 var uiLocation = breakpointLocation.uiLocation; |
| 45 * @param {!UI.ContextMenu} contextMenu | 54 var entryDescriptor = uiLocation.uiSourceCode.url() + ':' + uiLocation.lin
eNumber; |
| 46 */ | 55 locationForEntry.set(entryDescriptor, breakpointLocation); |
| 47 _appendBreakpointActiveItem(contextMenu) { | 56 } |
| 48 var breakpointActive = this._breakpointManager.breakpointsActive(); | 57 |
| 49 var breakpointActiveTitle = breakpointActive ? Common.UIString.capitalize('D
eactivate ^breakpoints') : | 58 var details = UI.context.flavor(SDK.DebuggerPausedDetails); |
| 50 Common.UIString.capitalize('A
ctivate ^breakpoints'); | 59 var selectedUILocation = details && details.callFrames.length ? |
| 51 contextMenu.appendItem( | 60 Bindings.debuggerWorkspaceBinding.rawLocationToUILocation(details.callFr
ames[0].location()) : |
| 52 breakpointActiveTitle, | 61 null; |
| 53 this._breakpointManager.setBreakpointsActive.bind(this._breakpointManage
r, !breakpointActive)); | 62 |
| 54 } | 63 var shouldShowView = false; |
| 55 | 64 var entry = this._listElement.firstChild; |
| 56 /** | 65 var promises = []; |
| 57 * @param {!Common.Event} event | 66 for (var descriptor of locationForEntry.keysArray()) { |
| 58 */ | 67 if (!entry) { |
| 59 _breakpointAdded(event) { | 68 entry = this._listElement.createChild('div', 'breakpoint-entry'); |
| 60 this._breakpointRemoved(event); | 69 entry.addEventListener('contextmenu', this._breakpointContextMenu.bind(t
his), true); |
| 61 | 70 entry.addEventListener('click', this._revealLocation.bind(this), false); |
| 62 var breakpoint = /** @type {!Bindings.BreakpointManager.Breakpoint} */ (even
t.data.breakpoint); | 71 var checkboxLabel = createCheckboxLabel(''); |
| 63 var uiLocation = /** @type {!Workspace.UILocation} */ (event.data.uiLocation
); | 72 checkboxLabel.addEventListener('click', this._breakpointCheckboxClicked.
bind(this), false); |
| 64 this._addBreakpoint(breakpoint, uiLocation); | 73 entry.appendChild(checkboxLabel); |
| 65 } | 74 entry[Sources.JavaScriptBreakpointsSidebarPane._checkboxLabelSymbol] = c
heckboxLabel; |
| 66 | 75 var snippetElement = entry.createChild('div', 'source-text monospace'); |
| 67 /** | 76 entry[Sources.JavaScriptBreakpointsSidebarPane._snippetElementSymbol] =
snippetElement; |
| 68 * @param {!Bindings.BreakpointManager.Breakpoint} breakpoint | 77 } |
| 78 |
| 79 var locations = Array.from(locationForEntry.get(descriptor)); |
| 80 var uiLocation = locations[0].uiLocation; |
| 81 var isSelected = !!selectedUILocation && locations.some(location => locati
on.uiLocation.id() === selectedUILocation.id()); |
| 82 var hasEnabled = locations.some(location => location.breakpoint.enabled())
; |
| 83 var hasDisabled = locations.some(location => !location.breakpoint.enabled(
)); |
| 84 promises.push(this._resetEntry(/** @type {!Element}*/(entry), uiLocation,
isSelected, hasEnabled, hasDisabled)); |
| 85 |
| 86 if (isSelected) |
| 87 shouldShowView = true; |
| 88 entry = entry.nextSibling; |
| 89 } |
| 90 while (entry) { |
| 91 var next = entry.nextSibling; |
| 92 entry.remove(); |
| 93 entry = next; |
| 94 } |
| 95 if (shouldShowView) |
| 96 UI.viewManager.showView('sources.jsBreakpoints'); |
| 97 this._listElement.classList.toggle('breakpoints-list-deactivated', !this._br
eakpointManager.breakpointsActive()); |
| 98 Promise.all(promises).then(() => this._didUpdateForTest()); |
| 99 return Promise.resolve(); |
| 100 } |
| 101 |
| 102 /** |
| 103 * @param {!Element} element |
| 69 * @param {!Workspace.UILocation} uiLocation | 104 * @param {!Workspace.UILocation} uiLocation |
| 70 */ | 105 * @param {boolean} isSelected |
| 71 _addBreakpoint(breakpoint, uiLocation) { | 106 * @param {boolean} hasEnabled |
| 72 var element = createElementWithClass('li', 'cursor-pointer'); | 107 * @param {boolean} hasDisabled |
| 73 element.addEventListener('contextmenu', this._breakpointContextMenu.bind(thi
s, breakpoint), true); | 108 * @return {!Promise<undefined>} |
| 74 element.addEventListener('click', this._breakpointClicked.bind(this, uiLocat
ion), false); | 109 */ |
| 75 | 110 _resetEntry(element, uiLocation, isSelected, hasEnabled, hasDisabled) { |
| 76 var checkboxLabel = createCheckboxLabel(uiLocation.linkText(), breakpoint.en
abled()); | 111 element[Sources.JavaScriptBreakpointsSidebarPane._locationSymbol] = uiLocati
on; |
| 77 element.appendChild(checkboxLabel); | 112 element.classList.toggle('breakpoint-hit', isSelected); |
| 78 checkboxLabel.addEventListener('click', this._breakpointCheckboxClicked.bind
(this, breakpoint), false); | 113 |
| 79 | 114 var checkboxLabel = element[Sources.JavaScriptBreakpointsSidebarPane._checkb
oxLabelSymbol]; |
| 80 var snippetElement = element.createChild('div', 'source-text monospace'); | 115 checkboxLabel.textElement.textContent = uiLocation.linkText(); |
| 116 checkboxLabel.checkboxElement.checked = hasEnabled; |
| 117 checkboxLabel.checkboxElement.indeterminate = hasEnabled && hasDisabled; |
| 118 |
| 119 var snippetElement = element[Sources.JavaScriptBreakpointsSidebarPane._snipp
etElementSymbol]; |
| 120 return uiLocation.uiSourceCode.requestContent().then(fillSnippetElement.bind
(null, snippetElement)); |
| 81 | 121 |
| 82 /** | 122 /** |
| 123 * @param {!Element} snippetElement |
| 83 * @param {?string} content | 124 * @param {?string} content |
| 84 * @this {Sources.JavaScriptBreakpointsSidebarPane} | |
| 85 */ | 125 */ |
| 86 function didRequestContent(content) { | 126 function fillSnippetElement(snippetElement, content) { |
| 87 var lineNumber = uiLocation.lineNumber; | 127 var lineNumber = uiLocation.lineNumber; |
| 88 var columnNumber = uiLocation.columnNumber; | |
| 89 var text = new Common.Text(content || ''); | 128 var text = new Common.Text(content || ''); |
| 90 if (lineNumber < text.lineCount()) { | 129 if (lineNumber < text.lineCount()) { |
| 91 var lineText = text.lineAt(lineNumber); | 130 var lineText = text.lineAt(lineNumber); |
| 92 var maxSnippetLength = 200; | 131 var maxSnippetLength = 200; |
| 93 var snippetStartIndex = columnNumber > 100 ? columnNumber : 0; | 132 snippetElement.textContent = lineText.trimEnd(maxSnippetLength); |
| 94 snippetElement.textContent = lineText.substr(snippetStartIndex).trimEnd(
maxSnippetLength); | |
| 95 } | 133 } |
| 96 this.didReceiveBreakpointLineForTest(uiLocation.uiSourceCode, lineNumber,
columnNumber); | 134 } |
| 97 } | 135 } |
| 98 | 136 |
| 99 uiLocation.uiSourceCode.requestContent().then(didRequestContent.bind(this)); | 137 /** |
| 100 | 138 * @param {!Event} event |
| 101 element._data = uiLocation; | 139 * @return {?Workspace.UILocation} |
| 102 var currentElement = this._listElement.firstChild; | 140 */ |
| 103 while (currentElement) { | 141 _uiLocationFromEvent(event) { |
| 104 if (currentElement._data && this._compareBreakpoints(currentElement._data,
element._data) > 0) | 142 var node = event.target.enclosingNodeOrSelfWithClass('breakpoint-entry'); |
| 105 break; | 143 if (!node) |
| 106 currentElement = currentElement.nextSibling; | 144 return null; |
| 107 } | 145 return node[Sources.JavaScriptBreakpointsSidebarPane._locationSymbol] || nul
l; |
| 108 this._addListElement(element, currentElement); | 146 } |
| 109 | 147 |
| 110 var breakpointItem = {element: element, checkbox: checkboxLabel.checkboxElem
ent}; | 148 /** |
| 111 this._items.set(breakpoint, breakpointItem); | 149 * @param {!Event} event |
| 112 } | 150 */ |
| 113 | 151 _breakpointCheckboxClicked(event) { |
| 114 /** | 152 var uiLocation = this._uiLocationFromEvent(event); |
| 115 * @param {!Workspace.UISourceCode} uiSourceCode | 153 if (!uiLocation) |
| 116 * @param {number} lineNumber | |
| 117 * @param {number} columnNumber | |
| 118 */ | |
| 119 didReceiveBreakpointLineForTest(uiSourceCode, lineNumber, columnNumber) { | |
| 120 } | |
| 121 | |
| 122 /** | |
| 123 * @param {!Common.Event} event | |
| 124 */ | |
| 125 _breakpointRemoved(event) { | |
| 126 var breakpoint = /** @type {!Bindings.BreakpointManager.Breakpoint} */ (even
t.data.breakpoint); | |
| 127 var breakpointItem = this._items.get(breakpoint); | |
| 128 if (!breakpointItem) | |
| 129 return; | 154 return; |
| 130 this._items.remove(breakpoint); | 155 |
| 131 this._removeListElement(breakpointItem.element); | 156 var breakpoints = this._breakpointManager.findBreakpoints(uiLocation.uiSourc
eCode, uiLocation.lineNumber); |
| 157 var newState = event.target.checkboxElement.checked; |
| 158 for (var breakpoint of breakpoints) |
| 159 breakpoint.setEnabled(newState); |
| 160 event.consume(); |
| 161 } |
| 162 |
| 163 /** |
| 164 * @param {!Event} event |
| 165 */ |
| 166 _revealLocation(event) { |
| 167 var uiLocation = this._uiLocationFromEvent(event); |
| 168 if (uiLocation) |
| 169 Common.Revealer.reveal(uiLocation); |
| 170 } |
| 171 |
| 172 /** |
| 173 * @param {!Event} event |
| 174 */ |
| 175 _breakpointContextMenu(event) { |
| 176 var uiLocation = this._uiLocationFromEvent(event); |
| 177 if (!uiLocation) |
| 178 return; |
| 179 |
| 180 var breakpoints = this._breakpointManager.findBreakpoints(uiLocation.uiSourc
eCode, uiLocation.lineNumber); |
| 181 |
| 182 var contextMenu = new UI.ContextMenu(event); |
| 183 var removeEntryTitle = breakpoints.length > 1 ? Common.UIString('Remove all
breakpoints in line') |
| 184 : Common.UIString('Remove brea
kpoint'); |
| 185 contextMenu.appendItem(removeEntryTitle, () => breakpoints.map(breakpoint =>
breakpoint.remove())); |
| 186 |
| 187 contextMenu.appendSeparator(); |
| 188 var breakpointActive = this._breakpointManager.breakpointsActive(); |
| 189 var breakpointActiveTitle = breakpointActive ? Common.UIString('Deactivate b
reakpoints') : |
| 190 Common.UIString('Activate bre
akpoints'); |
| 191 contextMenu.appendItem( |
| 192 breakpointActiveTitle, |
| 193 this._breakpointManager.setBreakpointsActive.bind(this._breakpointManage
r, !breakpointActive)); |
| 194 |
| 195 contextMenu.appendSeparator(); |
| 196 if (breakpoints.some(breakpoint => !breakpoint.enabled())) { |
| 197 var enableTitle = Common.UIString('Enable all breakpoints'); |
| 198 contextMenu.appendItem( |
| 199 enableTitle, this._breakpointManager.toggleAllBreakpoints.bind(this._b
reakpointManager, true)); |
| 200 } |
| 201 if (breakpoints.some(breakpoint => breakpoint.enabled())) { |
| 202 var disableTitle = Common.UIString('Disable all breakpoints'); |
| 203 contextMenu.appendItem( |
| 204 disableTitle, this._breakpointManager.toggleAllBreakpoints.bind(this._
breakpointManager, false)); |
| 205 } |
| 206 var removeAllTitle = Common.UIString('Remove all breakpoints'); |
| 207 contextMenu.appendItem( |
| 208 removeAllTitle, this._breakpointManager.removeAllBreakpoints.bind(this._
breakpointManager)); |
| 209 contextMenu.show(); |
| 132 } | 210 } |
| 133 | 211 |
| 134 /** | 212 /** |
| 135 * @override | 213 * @override |
| 136 * @param {?Object} object | 214 * @param {?Object} object |
| 137 */ | 215 */ |
| 138 flavorChanged(object) { | 216 flavorChanged(object) { |
| 139 this._update(); | 217 this.update(); |
| 140 } | 218 } |
| 141 | 219 |
| 142 _update() { | 220 _didUpdateForTest() { |
| 143 var details = UI.context.flavor(SDK.DebuggerPausedDetails); | |
| 144 var uiLocation = details && details.callFrames.length ? | |
| 145 Bindings.debuggerWorkspaceBinding.rawLocationToUILocation(details.callFr
ames[0].location()) : | |
| 146 null; | |
| 147 var breakpoint = uiLocation ? | |
| 148 this._breakpointManager.findBreakpoint( | |
| 149 uiLocation.uiSourceCode, uiLocation.lineNumber, uiLocation.columnNum
ber) : | |
| 150 null; | |
| 151 var breakpointItem = this._items.get(breakpoint); | |
| 152 if (!breakpointItem) { | |
| 153 if (this._highlightedBreakpointItem) { | |
| 154 this._highlightedBreakpointItem.element.classList.remove('breakpoint-hit
'); | |
| 155 delete this._highlightedBreakpointItem; | |
| 156 } | |
| 157 return; | |
| 158 } | |
| 159 | |
| 160 breakpointItem.element.classList.add('breakpoint-hit'); | |
| 161 this._highlightedBreakpointItem = breakpointItem; | |
| 162 UI.viewManager.showView('sources.jsBreakpoints'); | |
| 163 } | |
| 164 | |
| 165 _breakpointsActiveStateChanged() { | |
| 166 this._listElement.classList.toggle('breakpoints-list-deactivated', !this._br
eakpointManager.breakpointsActive()); | |
| 167 } | |
| 168 | |
| 169 /** | |
| 170 * @param {!Workspace.UILocation} uiLocation | |
| 171 */ | |
| 172 _breakpointClicked(uiLocation) { | |
| 173 Common.Revealer.reveal(uiLocation); | |
| 174 } | |
| 175 | |
| 176 /** | |
| 177 * @param {!Bindings.BreakpointManager.Breakpoint} breakpoint | |
| 178 * @param {!Event} event | |
| 179 */ | |
| 180 _breakpointCheckboxClicked(breakpoint, event) { | |
| 181 // Breakpoint element has it's own click handler. | |
| 182 event.consume(); | |
| 183 breakpoint.setEnabled(event.target.checkboxElement.checked); | |
| 184 } | |
| 185 | |
| 186 /** | |
| 187 * @param {!Bindings.BreakpointManager.Breakpoint} breakpoint | |
| 188 * @param {!Event} event | |
| 189 */ | |
| 190 _breakpointContextMenu(breakpoint, event) { | |
| 191 var breakpoints = this._items.valuesArray(); | |
| 192 var contextMenu = new UI.ContextMenu(event); | |
| 193 contextMenu.appendItem(Common.UIString.capitalize('Remove ^breakpoint'), bre
akpoint.remove.bind(breakpoint)); | |
| 194 if (breakpoints.length > 1) { | |
| 195 var removeAllTitle = Common.UIString.capitalize('Remove ^all ^breakpoints'
); | |
| 196 contextMenu.appendItem( | |
| 197 removeAllTitle, this._breakpointManager.removeAllBreakpoints.bind(this
._breakpointManager)); | |
| 198 } | |
| 199 | |
| 200 contextMenu.appendSeparator(); | |
| 201 this._appendBreakpointActiveItem(contextMenu); | |
| 202 | |
| 203 function enabledBreakpointCount(breakpoints) { | |
| 204 var count = 0; | |
| 205 for (var i = 0; i < breakpoints.length; ++i) { | |
| 206 if (breakpoints[i].checkbox.checked) | |
| 207 count++; | |
| 208 } | |
| 209 return count; | |
| 210 } | |
| 211 if (breakpoints.length > 1) { | |
| 212 var enableBreakpointCount = enabledBreakpointCount(breakpoints); | |
| 213 var enableTitle = Common.UIString.capitalize('Enable ^all ^breakpoints'); | |
| 214 var disableTitle = Common.UIString.capitalize('Disable ^all ^breakpoints')
; | |
| 215 | |
| 216 contextMenu.appendSeparator(); | |
| 217 | |
| 218 contextMenu.appendItem( | |
| 219 enableTitle, this._breakpointManager.toggleAllBreakpoints.bind(this._b
reakpointManager, true), | |
| 220 !(enableBreakpointCount !== breakpoints.length)); | |
| 221 contextMenu.appendItem( | |
| 222 disableTitle, this._breakpointManager.toggleAllBreakpoints.bind(this._
breakpointManager, false), | |
| 223 !(enableBreakpointCount > 1)); | |
| 224 } | |
| 225 | |
| 226 contextMenu.show(); | |
| 227 } | |
| 228 | |
| 229 _addListElement(element, beforeElement) { | |
| 230 if (beforeElement) | |
| 231 this._listElement.insertBefore(element, beforeElement); | |
| 232 else { | |
| 233 if (!this._listElement.firstChild) { | |
| 234 this.element.removeChild(this.emptyElement); | |
| 235 this.element.appendChild(this._listElement); | |
| 236 } | |
| 237 this._listElement.appendChild(element); | |
| 238 } | |
| 239 } | |
| 240 | |
| 241 _removeListElement(element) { | |
| 242 this._listElement.removeChild(element); | |
| 243 if (!this._listElement.firstChild) { | |
| 244 this.element.removeChild(this._listElement); | |
| 245 this.element.appendChild(this.emptyElement); | |
| 246 } | |
| 247 } | |
| 248 | |
| 249 _compare(x, y) { | |
| 250 if (x !== y) | |
| 251 return x < y ? -1 : 1; | |
| 252 return 0; | |
| 253 } | |
| 254 | |
| 255 _compareBreakpoints(b1, b2) { | |
| 256 return this._compare(b1.uiSourceCode.url(), b2.uiSourceCode.url()) || this._
compare(b1.lineNumber, b2.lineNumber); | |
| 257 } | |
| 258 | |
| 259 reset() { | |
| 260 this._listElement.removeChildren(); | |
| 261 if (this._listElement.parentElement) { | |
| 262 this.element.removeChild(this._listElement); | |
| 263 this.element.appendChild(this.emptyElement); | |
| 264 } | |
| 265 this._items.clear(); | |
| 266 } | 221 } |
| 267 }; | 222 }; |
| 223 |
| 224 Sources.JavaScriptBreakpointsSidebarPane._locationSymbol = Symbol('location'); |
| 225 Sources.JavaScriptBreakpointsSidebarPane._checkboxLabelSymbol = Symbol('checkbox
-label'); |
| 226 Sources.JavaScriptBreakpointsSidebarPane._snippetElementSymbol = Symbol('snippet
-element'); |
| OLD | NEW |