1/* 2 * Copyright (C) 2013, 2014 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26WebInspector.Breakpoint = function(sourceCodeLocationOrInfo, disabled, condition) 27{ 28 WebInspector.Object.call(this); 29 30 if (sourceCodeLocationOrInfo instanceof WebInspector.SourceCodeLocation) { 31 var sourceCode = sourceCodeLocationOrInfo.sourceCode; 32 var url = sourceCode ? sourceCode.url : null; 33 var scriptIdentifier = sourceCode instanceof WebInspector.Script ? sourceCode.id : null; 34 var location = sourceCodeLocationOrInfo; 35 } else if (sourceCodeLocationOrInfo && typeof sourceCodeLocationOrInfo === "object") { 36 var url = sourceCodeLocationOrInfo.url; 37 var lineNumber = sourceCodeLocationOrInfo.lineNumber || 0; 38 var columnNumber = sourceCodeLocationOrInfo.columnNumber || 0; 39 var location = new WebInspector.SourceCodeLocation(null, lineNumber, columnNumber); 40 var autoContinue = sourceCodeLocationOrInfo.autoContinue || false; 41 var actions = sourceCodeLocationOrInfo.actions || []; 42 for (var i = 0; i < actions.length; ++i) 43 actions[i] = new WebInspector.BreakpointAction(this, actions[i]); 44 disabled = sourceCodeLocationOrInfo.disabled; 45 condition = sourceCodeLocationOrInfo.condition; 46 } else 47 console.error("Unexpected type passed to WebInspector.Breakpoint", sourceCodeLocationOrInfo); 48 49 this._id = null; 50 this._url = url || null; 51 this._scriptIdentifier = scriptIdentifier || null; 52 this._disabled = disabled || false; 53 this._condition = condition || ""; 54 this._autoContinue = autoContinue || false; 55 this._actions = actions || []; 56 this._resolved = false; 57 58 this._sourceCodeLocation = location; 59 this._sourceCodeLocation.addEventListener(WebInspector.SourceCodeLocation.Event.LocationChanged, this._sourceCodeLocationLocationChanged, this); 60 this._sourceCodeLocation.addEventListener(WebInspector.SourceCodeLocation.Event.DisplayLocationChanged, this._sourceCodeLocationDisplayLocationChanged, this); 61}; 62 63WebInspector.Object.addConstructorFunctions(WebInspector.Breakpoint); 64 65WebInspector.Breakpoint.PopoverClassName = "edit-breakpoint-popover-content"; 66WebInspector.Breakpoint.WidePopoverClassName = "wide"; 67WebInspector.Breakpoint.PopoverConditionInputId = "edit-breakpoint-popover-condition"; 68WebInspector.Breakpoint.PopoverOptionsAutoContinueInputId = "edit-breakpoint-popoover-auto-continue"; 69WebInspector.Breakpoint.HiddenStyleClassName = "hidden"; 70 71WebInspector.Breakpoint.DefaultBreakpointActionType = WebInspector.BreakpointAction.Type.Log; 72 73WebInspector.Breakpoint.TypeIdentifier = "breakpoint"; 74WebInspector.Breakpoint.URLCookieKey = "breakpoint-url"; 75WebInspector.Breakpoint.LineNumberCookieKey = "breakpoint-line-number"; 76WebInspector.Breakpoint.ColumnNumberCookieKey = "breakpoint-column-number"; 77 78WebInspector.Breakpoint.Event = { 79 DisabledStateDidChange: "breakpoint-disabled-state-did-change", 80 ResolvedStateDidChange: "breakpoint-resolved-state-did-change", 81 ConditionDidChange: "breakpoint-condition-did-change", 82 ActionsDidChange: "breakpoint-actions-did-change", 83 AutoContinueDidChange: "breakpoint-auto-continue-did-change", 84 LocationDidChange: "breakpoint-location-did-change", 85 DisplayLocationDidChange: "breakpoint-display-location-did-change", 86}; 87 88WebInspector.Breakpoint.prototype = { 89 constructor: WebInspector.Breakpoint, 90 91 // Public 92 93 get id() 94 { 95 return this._id; 96 }, 97 98 set id(id) 99 { 100 this._id = id || null; 101 }, 102 103 get url() 104 { 105 return this._url; 106 }, 107 108 get scriptIdentifier() 109 { 110 return this._scriptIdentifier; 111 }, 112 113 get sourceCodeLocation() 114 { 115 return this._sourceCodeLocation; 116 }, 117 118 get resolved() 119 { 120 return this._resolved && WebInspector.debuggerManager.breakpointsEnabled; 121 }, 122 123 set resolved(resolved) 124 { 125 if (this._resolved === resolved) 126 return; 127 128 this._resolved = resolved || false; 129 130 this.dispatchEventToListeners(WebInspector.Breakpoint.Event.ResolvedStateDidChange); 131 }, 132 133 get disabled() 134 { 135 return this._disabled; 136 }, 137 138 set disabled(disabled) 139 { 140 if (this._disabled === disabled) 141 return; 142 143 this._disabled = disabled || false; 144 145 this.dispatchEventToListeners(WebInspector.Breakpoint.Event.DisabledStateDidChange); 146 }, 147 148 get condition() 149 { 150 return this._condition; 151 }, 152 153 set condition(condition) 154 { 155 if (this._condition === condition) 156 return; 157 158 this._condition = condition; 159 160 this.dispatchEventToListeners(WebInspector.Breakpoint.Event.ConditionDidChange); 161 }, 162 163 get autoContinue() 164 { 165 return this._autoContinue; 166 }, 167 168 set autoContinue(cont) 169 { 170 if (this._autoContinue === cont) 171 return; 172 173 this._autoContinue = cont; 174 175 this.dispatchEventToListeners(WebInspector.Breakpoint.Event.AutoContinueDidChange); 176 }, 177 178 get actions() 179 { 180 return this._actions; 181 }, 182 183 get options() 184 { 185 return { 186 condition: this._condition, 187 actions: this._serializableActions(), 188 autoContinue: this._autoContinue 189 }; 190 }, 191 192 get info() 193 { 194 // The id, scriptIdentifier and resolved state are tied to the current session, so don't include them for serialization. 195 return { 196 url: this._url, 197 lineNumber: this._sourceCodeLocation.lineNumber, 198 columnNumber: this._sourceCodeLocation.columnNumber, 199 disabled: this._disabled, 200 condition: this._condition, 201 actions: this._serializableActions(), 202 autoContinue: this._autoContinue 203 }; 204 }, 205 206 get probeActions() 207 { 208 return this._actions.filter(function(action) { 209 return action.type === WebInspector.BreakpointAction.Type.Probe; 210 }); 211 }, 212 213 cycleToNextMode: function() 214 { 215 if (this.disabled) { 216 // When cycling, clear auto-continue when going from disabled to enabled. 217 this.autoContinue = false; 218 this.disabled = false; 219 return; 220 } 221 222 if (this.autoContinue) { 223 this.disabled = true; 224 return; 225 } 226 227 if (this.actions.length) { 228 this.autoContinue = true; 229 return; 230 } 231 232 this.disabled = true; 233 }, 234 235 appendContextMenuItems: function(contextMenu, breakpointDisplayElement) 236 { 237 console.assert(document.body.contains(breakpointDisplayElement), "breakpoint popover display element must be in the DOM"); 238 239 var boundingClientRect = breakpointDisplayElement.getBoundingClientRect(); 240 241 function editBreakpoint() 242 { 243 this._showEditBreakpointPopover(boundingClientRect); 244 } 245 246 function removeBreakpoint() 247 { 248 WebInspector.debuggerManager.removeBreakpoint(this); 249 } 250 251 function toggleBreakpoint() 252 { 253 this.disabled = !this.disabled; 254 } 255 256 function toggleAutoContinue() 257 { 258 this.autoContinue = !this.autoContinue; 259 } 260 261 function revealOriginalSourceCodeLocation() 262 { 263 WebInspector.resourceSidebarPanel.showOriginalOrFormattedSourceCodeLocation(this._sourceCodeLocation); 264 } 265 266 if (WebInspector.debuggerManager.isBreakpointEditable(this)) 267 contextMenu.appendItem(WebInspector.UIString("Edit Breakpoint…"), editBreakpoint.bind(this)); 268 269 if (this.autoContinue && !this.disabled) { 270 contextMenu.appendItem(WebInspector.UIString("Disable Breakpoint"), toggleBreakpoint.bind(this)); 271 contextMenu.appendItem(WebInspector.UIString("Cancel Automatic Continue"), toggleAutoContinue.bind(this)); 272 } else if (!this.disabled) 273 contextMenu.appendItem(WebInspector.UIString("Disable Breakpoint"), toggleBreakpoint.bind(this)); 274 else 275 contextMenu.appendItem(WebInspector.UIString("Enable Breakpoint"), toggleBreakpoint.bind(this)); 276 277 if (!this.autoContinue && !this.disabled && this.actions.length) 278 contextMenu.appendItem(WebInspector.UIString("Set to Automatically Continue"), toggleAutoContinue.bind(this)); 279 280 if (WebInspector.debuggerManager.isBreakpointRemovable(this)) { 281 contextMenu.appendSeparator(); 282 contextMenu.appendItem(WebInspector.UIString("Delete Breakpoint"), removeBreakpoint.bind(this)); 283 } 284 285 if (this._sourceCodeLocation.hasMappedLocation()) { 286 contextMenu.appendSeparator(); 287 contextMenu.appendItem(WebInspector.UIString("Reveal in Original Resource"), revealOriginalSourceCodeLocation.bind(this)); 288 } 289 }, 290 291 createAction: function(type, precedingAction, data) 292 { 293 var newAction = new WebInspector.BreakpointAction(this, type, data || null); 294 295 if (!precedingAction) 296 this._actions.push(newAction); 297 else { 298 var index = this._actions.indexOf(precedingAction); 299 console.assert(index !== -1); 300 if (index === -1) 301 this._actions.push(newAction); 302 else 303 this._actions.splice(index + 1, 0, newAction); 304 } 305 306 this.dispatchEventToListeners(WebInspector.Breakpoint.Event.ActionsDidChange); 307 308 return newAction; 309 }, 310 311 recreateAction: function(type, actionToReplace) 312 { 313 var newAction = new WebInspector.BreakpointAction(this, type, null); 314 315 var index = this._actions.indexOf(actionToReplace); 316 console.assert(index !== -1); 317 if (index === -1) 318 return null; 319 320 this._actions[index] = newAction; 321 322 this.dispatchEventToListeners(WebInspector.Breakpoint.Event.ActionsDidChange); 323 324 return newAction; 325 }, 326 327 removeAction: function(action) 328 { 329 var index = this._actions.indexOf(action); 330 console.assert(index !== -1); 331 if (index === -1) 332 return; 333 334 this._actions.splice(index, 1); 335 336 if (!this._actions.length) 337 this.autoContinue = false; 338 339 this.dispatchEventToListeners(WebInspector.Breakpoint.Event.ActionsDidChange); 340 }, 341 342 clearActions: function(type) 343 { 344 if (!type) 345 this._actions = []; 346 else 347 this._actions = this._actions.filter(function(action) { action.type != type; }); 348 349 this.dispatchEventToListeners(WebInspector.Breakpoint.Event.ActionsDidChange); 350 }, 351 352 saveIdentityToCookie: function(cookie) 353 { 354 cookie[WebInspector.Breakpoint.URLCookieKey] = this.url; 355 cookie[WebInspector.Breakpoint.LineNumberCookieKey] = this.sourceCodeLocation.lineNumber; 356 cookie[WebInspector.Breakpoint.ColumnNumberCookieKey] = this.sourceCodeLocation.columnNumber; 357 }, 358 359 // Protected (Called by BreakpointAction) 360 361 breakpointActionDidChange: function(action) 362 { 363 var index = this._actions.indexOf(action); 364 console.assert(index !== -1); 365 if (index === -1) 366 return; 367 368 this.dispatchEventToListeners(WebInspector.Breakpoint.Event.ActionsDidChange); 369 }, 370 371 // Private 372 373 _serializableActions: function() 374 { 375 var actions = []; 376 for (var i = 0; i < this._actions.length; ++i) 377 actions.push(this._actions[i].info); 378 return actions; 379 }, 380 381 _popoverToggleEnabledCheckboxChanged: function(event) 382 { 383 this.disabled = !event.target.checked; 384 }, 385 386 _popoverConditionInputChanged: function(event) 387 { 388 this.condition = event.target.value; 389 }, 390 391 _popoverToggleAutoContinueCheckboxChanged: function(event) 392 { 393 this.autoContinue = event.target.checked; 394 }, 395 396 _popoverConditionInputKeyDown: function(event) 397 { 398 if (this._keyboardShortcutEsc.matchesEvent(event) || this._keyboardShortcutEnter.matchesEvent(event)) { 399 this._popover.dismiss(); 400 event.stopPropagation(); 401 event.preventDefault(); 402 } 403 }, 404 405 _editBreakpointPopoverContentElement: function() 406 { 407 var content = this._popoverContentElement = document.createElement("div"); 408 content.className = WebInspector.Breakpoint.PopoverClassName; 409 410 var checkboxElement = document.createElement("input"); 411 checkboxElement.type = "checkbox"; 412 checkboxElement.checked = !this._disabled; 413 checkboxElement.addEventListener("change", this._popoverToggleEnabledCheckboxChanged.bind(this)); 414 415 var checkboxLabel = document.createElement("label"); 416 checkboxLabel.className = "toggle"; 417 checkboxLabel.appendChild(checkboxElement); 418 checkboxLabel.appendChild(document.createTextNode(this._sourceCodeLocation.displayLocationString())); 419 420 var table = document.createElement("table"); 421 422 var conditionRow = table.appendChild(document.createElement("tr")); 423 var conditionHeader = conditionRow.appendChild(document.createElement("th")); 424 var conditionData = conditionRow.appendChild(document.createElement("td")); 425 var conditionLabel = conditionHeader.appendChild(document.createElement("label")); 426 var conditionInput = conditionData.appendChild(document.createElement("input")); 427 conditionInput.id = WebInspector.Breakpoint.PopoverConditionInputId; 428 conditionInput.value = this._condition || ""; 429 conditionInput.spellcheck = false; 430 conditionInput.addEventListener("change", this._popoverConditionInputChanged.bind(this)); 431 conditionInput.addEventListener("keydown", this._popoverConditionInputKeyDown.bind(this)); 432 conditionInput.placeholder = WebInspector.UIString("Conditional expression"); 433 conditionLabel.setAttribute("for", conditionInput.id); 434 conditionLabel.textContent = WebInspector.UIString("Condition"); 435 436 if (DebuggerAgent.setBreakpoint.supports("options")) { 437 var actionRow = table.appendChild(document.createElement("tr")); 438 var actionHeader = actionRow.appendChild(document.createElement("th")); 439 var actionData = this._actionsContainer = actionRow.appendChild(document.createElement("td")); 440 var actionLabel = actionHeader.appendChild(document.createElement("label")); 441 actionLabel.textContent = WebInspector.UIString("Action"); 442 443 if (!this._actions.length) 444 this._popoverActionsCreateAddActionButton(); 445 else { 446 this._popoverContentElement.classList.add(WebInspector.Breakpoint.WidePopoverClassName); 447 for (var i = 0; i < this._actions.length; ++i) { 448 var breakpointActionView = new WebInspector.BreakpointActionView(this._actions[i], this, true); 449 this._popoverActionsInsertBreakpointActionView(breakpointActionView, i); 450 } 451 } 452 453 var optionsRow = this._popoverOptionsRowElement = table.appendChild(document.createElement("tr")); 454 if (!this._actions.length) 455 optionsRow.classList.add(WebInspector.Breakpoint.HiddenStyleClassName); 456 var optionsHeader = optionsRow.appendChild(document.createElement("th")); 457 var optionsData = optionsRow.appendChild(document.createElement("td")); 458 var optionsLabel = optionsHeader.appendChild(document.createElement("label")); 459 var optionsCheckbox = this._popoverOptionsCheckboxElement = optionsData.appendChild(document.createElement("input")); 460 var optionsCheckboxLabel = optionsData.appendChild(document.createElement("label")); 461 optionsCheckbox.id = WebInspector.Breakpoint.PopoverOptionsAutoContinueInputId; 462 optionsCheckbox.type = "checkbox"; 463 optionsCheckbox.checked = this._autoContinue; 464 optionsCheckbox.addEventListener("change", this._popoverToggleAutoContinueCheckboxChanged.bind(this)); 465 optionsLabel.textContent = WebInspector.UIString("Options"); 466 optionsCheckboxLabel.setAttribute("for", optionsCheckbox.id); 467 optionsCheckboxLabel.textContent = WebInspector.UIString("Automatically continue after evaluating"); 468 } 469 470 content.appendChild(checkboxLabel); 471 content.appendChild(table); 472 473 return content; 474 }, 475 476 _popoverActionsCreateAddActionButton: function() 477 { 478 this._popoverContentElement.classList.remove(WebInspector.Breakpoint.WidePopoverClassName); 479 this._actionsContainer.removeChildren(); 480 481 var addActionButton = this._actionsContainer.appendChild(document.createElement("button")); 482 addActionButton.textContent = WebInspector.UIString("Add Action"); 483 addActionButton.addEventListener("click", this._popoverActionsAddActionButtonClicked.bind(this)); 484 }, 485 486 _popoverActionsAddActionButtonClicked: function(event) 487 { 488 this._popoverContentElement.classList.add(WebInspector.Breakpoint.WidePopoverClassName); 489 this._actionsContainer.removeChildren(); 490 491 var newAction = this.createAction(WebInspector.Breakpoint.DefaultBreakpointActionType); 492 var newBreakpointActionView = new WebInspector.BreakpointActionView(newAction, this); 493 this._popoverActionsInsertBreakpointActionView(newBreakpointActionView, -1); 494 this._popoverOptionsRowElement.classList.remove(WebInspector.Breakpoint.HiddenStyleClassName); 495 this._popover.update(); 496 }, 497 498 _popoverActionsInsertBreakpointActionView: function(breakpointActionView, index) 499 { 500 if (index === -1) 501 this._actionsContainer.appendChild(breakpointActionView.element); 502 else { 503 var nextElement = this._actionsContainer.children[index + 1] || null; 504 this._actionsContainer.insertBefore(breakpointActionView.element, nextElement); 505 } 506 }, 507 508 breakpointActionViewAppendActionView: function(breakpointActionView, newAction) 509 { 510 var newBreakpointActionView = new WebInspector.BreakpointActionView(newAction, this); 511 512 var index = 0; 513 var children = this._actionsContainer.children; 514 for (var i = 0; children.length; ++i) { 515 if (children[i] === breakpointActionView.element) { 516 index = i; 517 break; 518 } 519 } 520 521 this._popoverActionsInsertBreakpointActionView(newBreakpointActionView, index); 522 this._popoverOptionsRowElement.classList.remove(WebInspector.Breakpoint.HiddenStyleClassName); 523 524 this._popover.update(); 525 }, 526 527 breakpointActionViewRemoveActionView: function(breakpointActionView) 528 { 529 breakpointActionView.element.remove(); 530 531 if (!this._actionsContainer.children.length) { 532 this._popoverActionsCreateAddActionButton(); 533 this._popoverOptionsRowElement.classList.add(WebInspector.Breakpoint.HiddenStyleClassName); 534 this._popoverOptionsCheckboxElement.checked = false; 535 } 536 537 this._popover.update(); 538 }, 539 540 breakpointActionViewResized: function(breakpointActionView) 541 { 542 this._popover.update(); 543 }, 544 545 willDismissPopover: function(popover) 546 { 547 console.assert(this._popover === popover); 548 delete this._popoverContentElement; 549 delete this._popoverOptionsRowElement; 550 delete this._popoverOptionsCheckboxElement; 551 delete this._actionsContainer; 552 delete this._popover; 553 }, 554 555 _showEditBreakpointPopover: function(boundingClientRect) 556 { 557 var bounds = WebInspector.Rect.rectFromClientRect(boundingClientRect); 558 bounds.origin.x -= 1; // Move the anchor left one pixel so it looks more centered. 559 560 this._popover = this._popover || new WebInspector.Popover(this); 561 this._popover.content = this._editBreakpointPopoverContentElement(); 562 this._popover.present(bounds.pad(2), [WebInspector.RectEdge.MAX_Y]); 563 564 if (!this._keyboardShortcutEsc) { 565 this._keyboardShortcutEsc = new WebInspector.KeyboardShortcut(null, WebInspector.KeyboardShortcut.Key.Escape); 566 this._keyboardShortcutEnter = new WebInspector.KeyboardShortcut(null, WebInspector.KeyboardShortcut.Key.Enter); 567 } 568 569 document.getElementById(WebInspector.Breakpoint.PopoverConditionInputId).select(); 570 }, 571 572 _sourceCodeLocationLocationChanged: function(event) 573 { 574 this.dispatchEventToListeners(WebInspector.Breakpoint.Event.LocationDidChange, event.data); 575 }, 576 577 _sourceCodeLocationDisplayLocationChanged: function(event) 578 { 579 this.dispatchEventToListeners(WebInspector.Breakpoint.Event.DisplayLocationDidChange, event.data); 580 } 581}; 582 583WebInspector.Breakpoint.prototype.__proto__ = WebInspector.Object.prototype; 584