1 /* 2 * Copyright (C) 2011 Google 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 are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 /** 32 * @constructor 33 * @extends {WebInspector.Object} 34 * @implements {WebInspector.TargetManager.Observer} 35 * @param {!WebInspector.Setting} breakpointStorage 36 * @param {!WebInspector.Workspace} workspace 37 * @param {!WebInspector.TargetManager} targetManager 38 * @param {!WebInspector.DebuggerWorkspaceBinding} debuggerWorkspaceBinding 39 */ 40 WebInspector.BreakpointManager = function(breakpointStorage, workspace, targetManager, debuggerWorkspaceBinding) 41 { 42 this._storage = new WebInspector.BreakpointManager.Storage(this, breakpointStorage); 43 this._workspace = workspace; 44 this._targetManager = targetManager; 45 this._debuggerWorkspaceBinding = debuggerWorkspaceBinding; 46 47 this._breakpointsActive = true; 48 this._breakpointsForUISourceCode = new Map(); 49 this._breakpointsForPrimaryUISourceCode = new Map(); 50 /** @type {!StringMultimap.<!WebInspector.BreakpointManager.Breakpoint>} */ 51 this._provisionalBreakpoints = new StringMultimap(); 52 53 this._workspace.addEventListener(WebInspector.Workspace.Events.ProjectRemoved, this._projectRemoved, this); 54 this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeAdded, this._uiSourceCodeAdded, this); 55 this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeRemoved, this._uiSourceCodeRemoved, this); 56 } 57 58 WebInspector.BreakpointManager.Events = { 59 BreakpointAdded: "breakpoint-added", 60 BreakpointRemoved: "breakpoint-removed", 61 BreakpointsActiveStateChanged: "BreakpointsActiveStateChanged" 62 } 63 64 WebInspector.BreakpointManager._sourceFileId = function(uiSourceCode) 65 { 66 if (!uiSourceCode.url) 67 return ""; 68 return uiSourceCode.uri(); 69 } 70 71 /** 72 * @param {string} sourceFileId 73 * @param {number} lineNumber 74 * @param {number} columnNumber 75 * @return {string} 76 */ 77 WebInspector.BreakpointManager._breakpointStorageId = function(sourceFileId, lineNumber, columnNumber) 78 { 79 if (!sourceFileId) 80 return ""; 81 return sourceFileId + ":" + lineNumber + ":" + columnNumber; 82 } 83 84 WebInspector.BreakpointManager.prototype = { 85 /** 86 * @param {!WebInspector.Target} target 87 */ 88 targetAdded: function(target) { 89 if (!this._breakpointsActive) 90 target.debuggerAgent().setBreakpointsActive(this._breakpointsActive); 91 }, 92 93 /** 94 * @param {!WebInspector.Target} target 95 */ 96 targetRemoved: function(target) { }, 97 98 /** 99 * @param {string} sourceFileId 100 * @return {!StringMap.<!WebInspector.BreakpointManager.Breakpoint>} 101 */ 102 _provisionalBreakpointsForSourceFileId: function(sourceFileId) 103 { 104 var result = new StringMap(); 105 var breakpoints = this._provisionalBreakpoints.get(sourceFileId).values(); 106 for (var i = 0; i < breakpoints.length; ++i) 107 result.set(breakpoints[i]._breakpointStorageId(), breakpoints[i]); 108 return result; 109 }, 110 111 removeProvisionalBreakpointsForTest: function() 112 { 113 var breakpoints = this._provisionalBreakpoints.values(); 114 for (var i = 0; i < breakpoints.length; ++i) 115 breakpoints[i].remove(); 116 this._provisionalBreakpoints.clear(); 117 }, 118 119 /** 120 * @param {!WebInspector.UISourceCode} uiSourceCode 121 */ 122 _restoreBreakpoints: function(uiSourceCode) 123 { 124 var sourceFileId = WebInspector.BreakpointManager._sourceFileId(uiSourceCode); 125 if (!sourceFileId) 126 return; 127 128 this._storage.mute(); 129 var breakpointItems = this._storage.breakpointItems(uiSourceCode); 130 var provisionalBreakpoints = this._provisionalBreakpointsForSourceFileId(sourceFileId); 131 for (var i = 0; i < breakpointItems.length; ++i) { 132 var breakpointItem = breakpointItems[i]; 133 var itemStorageId = WebInspector.BreakpointManager._breakpointStorageId(breakpointItem.sourceFileId, breakpointItem.lineNumber, breakpointItem.columnNumber); 134 var provisionalBreakpoint = provisionalBreakpoints.get(itemStorageId); 135 if (provisionalBreakpoint) { 136 if (!this._breakpointsForPrimaryUISourceCode.get(uiSourceCode)) 137 this._breakpointsForPrimaryUISourceCode.set(uiSourceCode, []); 138 this._breakpointsForPrimaryUISourceCode.get(uiSourceCode).push(provisionalBreakpoint); 139 provisionalBreakpoint._updateBreakpoint(); 140 } else { 141 this._innerSetBreakpoint(uiSourceCode, breakpointItem.lineNumber, breakpointItem.columnNumber, breakpointItem.condition, breakpointItem.enabled); 142 } 143 } 144 this._provisionalBreakpoints.removeAll(sourceFileId); 145 this._storage.unmute(); 146 }, 147 148 /** 149 * @param {!WebInspector.Event} event 150 */ 151 _uiSourceCodeAdded: function(event) 152 { 153 var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data); 154 this._restoreBreakpoints(uiSourceCode); 155 if (uiSourceCode.contentType() === WebInspector.resourceTypes.Script || uiSourceCode.contentType() === WebInspector.resourceTypes.Document) 156 uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.SourceMappingChanged, this._uiSourceCodeMappingChanged, this); 157 }, 158 159 /** 160 * @param {!WebInspector.Event} event 161 */ 162 _uiSourceCodeRemoved: function(event) 163 { 164 var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data); 165 this._removeUISourceCode(uiSourceCode); 166 }, 167 168 /** 169 * @param {!WebInspector.Event} event 170 */ 171 _uiSourceCodeMappingChanged: function(event) 172 { 173 var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.target); 174 var isIdentity = /** @type {boolean} */ (event.data.isIdentity); 175 var target = /** @type {!WebInspector.Target} */ (event.data.target); 176 if (isIdentity) 177 return; 178 var breakpoints = this._breakpointsForPrimaryUISourceCode.get(uiSourceCode) || []; 179 for (var i = 0; i < breakpoints.length; ++i) 180 breakpoints[i]._updateInDebuggerForTarget(target); 181 }, 182 183 /** 184 * @param {!WebInspector.UISourceCode} uiSourceCode 185 */ 186 _removeUISourceCode: function(uiSourceCode) 187 { 188 var breakpoints = this._breakpointsForPrimaryUISourceCode.get(uiSourceCode) || []; 189 var sourceFileId = WebInspector.BreakpointManager._sourceFileId(uiSourceCode); 190 for (var i = 0; i < breakpoints.length; ++i) { 191 breakpoints[i]._resetLocations(); 192 if (breakpoints[i].enabled()) 193 this._provisionalBreakpoints.set(sourceFileId, breakpoints[i]); 194 } 195 uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.SourceMappingChanged, this._uiSourceCodeMappingChanged, this); 196 this._breakpointsForPrimaryUISourceCode.remove(uiSourceCode); 197 }, 198 199 /** 200 * @param {!WebInspector.UISourceCode} uiSourceCode 201 * @param {number} lineNumber 202 * @param {number} columnNumber 203 * @param {string} condition 204 * @param {boolean} enabled 205 * @return {!WebInspector.BreakpointManager.Breakpoint} 206 */ 207 setBreakpoint: function(uiSourceCode, lineNumber, columnNumber, condition, enabled) 208 { 209 this.setBreakpointsActive(true); 210 return this._innerSetBreakpoint(uiSourceCode, lineNumber, columnNumber, condition, enabled); 211 }, 212 213 /** 214 * @param {!WebInspector.UISourceCode} uiSourceCode 215 * @param {number} lineNumber 216 * @param {number} columnNumber 217 * @param {string} condition 218 * @param {boolean} enabled 219 * @return {!WebInspector.BreakpointManager.Breakpoint} 220 */ 221 _innerSetBreakpoint: function(uiSourceCode, lineNumber, columnNumber, condition, enabled) 222 { 223 var breakpoint = this.findBreakpoint(uiSourceCode, lineNumber, columnNumber); 224 if (breakpoint) { 225 breakpoint._updateState(condition, enabled); 226 return breakpoint; 227 } 228 var projectId = uiSourceCode.project().id(); 229 var path = uiSourceCode.path(); 230 var sourceFileId = WebInspector.BreakpointManager._sourceFileId(uiSourceCode); 231 breakpoint = new WebInspector.BreakpointManager.Breakpoint(this, projectId, path, sourceFileId, lineNumber, columnNumber, condition, enabled); 232 if (!this._breakpointsForPrimaryUISourceCode.get(uiSourceCode)) 233 this._breakpointsForPrimaryUISourceCode.set(uiSourceCode, []); 234 this._breakpointsForPrimaryUISourceCode.get(uiSourceCode).push(breakpoint); 235 return breakpoint; 236 }, 237 238 /** 239 * @param {!WebInspector.UISourceCode} uiSourceCode 240 * @param {number} lineNumber 241 * @param {number} columnNumber 242 * @return {?WebInspector.BreakpointManager.Breakpoint} 243 */ 244 findBreakpoint: function(uiSourceCode, lineNumber, columnNumber) 245 { 246 var breakpoints = this._breakpointsForUISourceCode.get(uiSourceCode); 247 var lineBreakpoints = breakpoints ? breakpoints.get(String(lineNumber)) : null; 248 var columnBreakpoints = lineBreakpoints ? lineBreakpoints.get(String(columnNumber)) : null; 249 return columnBreakpoints ? columnBreakpoints[0] : null; 250 }, 251 252 /** 253 * @param {!WebInspector.UISourceCode} uiSourceCode 254 * @param {number} lineNumber 255 * @return {?WebInspector.BreakpointManager.Breakpoint} 256 */ 257 findBreakpointOnLine: function(uiSourceCode, lineNumber) 258 { 259 var breakpoints = this._breakpointsForUISourceCode.get(uiSourceCode); 260 var lineBreakpoints = breakpoints ? breakpoints.get(String(lineNumber)) : null; 261 return lineBreakpoints ? lineBreakpoints.values()[0][0] : null; 262 }, 263 264 /** 265 * @param {!WebInspector.UISourceCode} uiSourceCode 266 * @return {!Array.<!WebInspector.BreakpointManager.Breakpoint>} 267 */ 268 breakpointsForUISourceCode: function(uiSourceCode) 269 { 270 var result = []; 271 var uiSourceCodeBreakpoints = this._breakpointsForUISourceCode.get(uiSourceCode); 272 var breakpoints = uiSourceCodeBreakpoints ? uiSourceCodeBreakpoints.values() : []; 273 for (var i = 0; i < breakpoints.length; ++i) { 274 var lineBreakpoints = breakpoints[i]; 275 var columnBreakpointArrays = lineBreakpoints ? lineBreakpoints.values() : []; 276 result = result.concat.apply(result, columnBreakpointArrays); 277 } 278 return result; 279 }, 280 281 /** 282 * @return {!Array.<!WebInspector.BreakpointManager.Breakpoint>} 283 */ 284 allBreakpoints: function() 285 { 286 var result = []; 287 var uiSourceCodes = this._breakpointsForUISourceCode.keys(); 288 for (var i = 0; i < uiSourceCodes.length; ++i) 289 result = result.concat(this.breakpointsForUISourceCode(uiSourceCodes[i])); 290 return result; 291 }, 292 293 /** 294 * @param {!WebInspector.UISourceCode} uiSourceCode 295 * @return {!Array.<!{breakpoint: !WebInspector.BreakpointManager.Breakpoint, uiLocation: !WebInspector.UILocation}>} 296 */ 297 breakpointLocationsForUISourceCode: function(uiSourceCode) 298 { 299 var uiSourceCodeBreakpoints = this._breakpointsForUISourceCode.get(uiSourceCode); 300 var lineNumbers = uiSourceCodeBreakpoints ? uiSourceCodeBreakpoints.keys() : []; 301 var result = []; 302 for (var i = 0; i < lineNumbers.length; ++i) { 303 var lineBreakpoints = uiSourceCodeBreakpoints.get(lineNumbers[i]); 304 var columnNumbers = lineBreakpoints.keys(); 305 for (var j = 0; j < columnNumbers.length; ++j) { 306 var columnBreakpoints = lineBreakpoints.get(columnNumbers[j]); 307 var lineNumber = parseInt(lineNumbers[i], 10); 308 var columnNumber = parseInt(columnNumbers[j], 10); 309 for (var k = 0; k < columnBreakpoints.length; ++k) { 310 var breakpoint = columnBreakpoints[k]; 311 var uiLocation = uiSourceCode.uiLocation(lineNumber, columnNumber); 312 result.push({breakpoint: breakpoint, uiLocation: uiLocation}); 313 } 314 } 315 } 316 return result; 317 }, 318 319 /** 320 * @return {!Array.<!{breakpoint: !WebInspector.BreakpointManager.Breakpoint, uiLocation: !WebInspector.UILocation}>} 321 */ 322 allBreakpointLocations: function() 323 { 324 var result = []; 325 var uiSourceCodes = this._breakpointsForUISourceCode.keys(); 326 for (var i = 0; i < uiSourceCodes.length; ++i) 327 result = result.concat(this.breakpointLocationsForUISourceCode(uiSourceCodes[i])); 328 return result; 329 }, 330 331 /** 332 * @param {boolean} toggleState 333 */ 334 toggleAllBreakpoints: function(toggleState) 335 { 336 var breakpoints = this.allBreakpoints(); 337 for (var i = 0; i < breakpoints.length; ++i) 338 breakpoints[i].setEnabled(toggleState); 339 }, 340 341 removeAllBreakpoints: function() 342 { 343 var breakpoints = this.allBreakpoints(); 344 for (var i = 0; i < breakpoints.length; ++i) 345 breakpoints[i].remove(); 346 }, 347 348 _projectRemoved: function(event) 349 { 350 var project = /** @type {!WebInspector.Project} */ (event.data); 351 var uiSourceCodes = project.uiSourceCodes(); 352 for (var i = 0; i < uiSourceCodes.length; ++i) 353 this._removeUISourceCode(uiSourceCodes[i]); 354 }, 355 356 /** 357 * @param {!WebInspector.BreakpointManager.Breakpoint} breakpoint 358 * @param {boolean} removeFromStorage 359 */ 360 _removeBreakpoint: function(breakpoint, removeFromStorage) 361 { 362 var uiSourceCode = breakpoint.uiSourceCode(); 363 var breakpoints = uiSourceCode ? this._breakpointsForPrimaryUISourceCode.get(uiSourceCode) || [] : []; 364 breakpoints.remove(breakpoint); 365 if (removeFromStorage) 366 this._storage._removeBreakpoint(breakpoint); 367 this._provisionalBreakpoints.remove(breakpoint._sourceFileId, breakpoint); 368 }, 369 370 /** 371 * @param {!WebInspector.BreakpointManager.Breakpoint} breakpoint 372 * @param {!WebInspector.UILocation} uiLocation 373 */ 374 _uiLocationAdded: function(breakpoint, uiLocation) 375 { 376 var breakpoints = this._breakpointsForUISourceCode.get(uiLocation.uiSourceCode); 377 if (!breakpoints) { 378 breakpoints = new StringMap(); 379 this._breakpointsForUISourceCode.set(uiLocation.uiSourceCode, breakpoints); 380 } 381 var lineBreakpoints = breakpoints.get(String(uiLocation.lineNumber)); 382 if (!lineBreakpoints) { 383 lineBreakpoints = new StringMap(); 384 breakpoints.set(String(uiLocation.lineNumber), lineBreakpoints); 385 } 386 var columnBreakpoints = lineBreakpoints.get(String(uiLocation.columnNumber)); 387 if (!columnBreakpoints) { 388 columnBreakpoints = []; 389 lineBreakpoints.set(String(uiLocation.columnNumber), columnBreakpoints); 390 } 391 columnBreakpoints.push(breakpoint); 392 this.dispatchEventToListeners(WebInspector.BreakpointManager.Events.BreakpointAdded, {breakpoint: breakpoint, uiLocation: uiLocation}); 393 }, 394 395 /** 396 * @param {!WebInspector.BreakpointManager.Breakpoint} breakpoint 397 * @param {!WebInspector.UILocation} uiLocation 398 */ 399 _uiLocationRemoved: function(breakpoint, uiLocation) 400 { 401 var breakpoints = this._breakpointsForUISourceCode.get(uiLocation.uiSourceCode); 402 if (!breakpoints) 403 return; 404 405 var lineBreakpoints = breakpoints.get(String(uiLocation.lineNumber)); 406 if (!lineBreakpoints) 407 return; 408 var columnBreakpoints = lineBreakpoints.get(String(uiLocation.columnNumber)); 409 if (!columnBreakpoints) 410 return; 411 columnBreakpoints.remove(breakpoint); 412 if (!columnBreakpoints.length) 413 lineBreakpoints.remove(String(uiLocation.columnNumber)); 414 if (!lineBreakpoints.size) 415 breakpoints.remove(String(uiLocation.lineNumber)); 416 if (!breakpoints.size) 417 this._breakpointsForUISourceCode.remove(uiLocation.uiSourceCode); 418 this.dispatchEventToListeners(WebInspector.BreakpointManager.Events.BreakpointRemoved, {breakpoint: breakpoint, uiLocation: uiLocation}); 419 }, 420 421 /** 422 * @param {boolean} active 423 */ 424 setBreakpointsActive: function(active) 425 { 426 if (this._breakpointsActive === active) 427 return; 428 429 this._breakpointsActive = active; 430 var targets = WebInspector.targetManager.targets(); 431 for (var i = 0; i < targets.length; ++i) 432 targets[i].debuggerAgent().setBreakpointsActive(active); 433 434 this.dispatchEventToListeners(WebInspector.BreakpointManager.Events.BreakpointsActiveStateChanged, active); 435 }, 436 437 /** 438 * @return {boolean} 439 */ 440 breakpointsActive: function() 441 { 442 return this._breakpointsActive; 443 }, 444 445 __proto__: WebInspector.Object.prototype 446 } 447 448 /** 449 * @constructor 450 * @implements {WebInspector.TargetManager.Observer} 451 * @param {!WebInspector.BreakpointManager} breakpointManager 452 * @param {string} projectId 453 * @param {string} path 454 * @param {string} sourceFileId 455 * @param {number} lineNumber 456 * @param {number} columnNumber 457 * @param {string} condition 458 * @param {boolean} enabled 459 */ 460 WebInspector.BreakpointManager.Breakpoint = function(breakpointManager, projectId, path, sourceFileId, lineNumber, columnNumber, condition, enabled) 461 { 462 this._breakpointManager = breakpointManager; 463 this._projectId = projectId; 464 this._path = path; 465 this._lineNumber = lineNumber; 466 this._columnNumber = columnNumber; 467 this._sourceFileId = sourceFileId; 468 469 /** @type {!Object.<string, number>} */ 470 this._numberOfDebuggerLocationForUILocation = {}; 471 472 // Force breakpoint update. 473 /** @type {string} */ this._condition; 474 /** @type {boolean} */ this._enabled; 475 /** @type {boolean} */ this._isRemoved; 476 /** @type {!WebInspector.UILocation|undefined} */ this._fakePrimaryLocation; 477 478 this._currentState = null; 479 /** @type {!Map.<!WebInspector.Target, !WebInspector.BreakpointManager.TargetBreakpoint>}*/ 480 this._targetBreakpoints = new Map(); 481 this._updateState(condition, enabled); 482 this._breakpointManager._targetManager.observeTargets(this); 483 } 484 485 WebInspector.BreakpointManager.Breakpoint.prototype = { 486 /** 487 * @param {!WebInspector.Target} target 488 */ 489 targetAdded: function(target) 490 { 491 this._targetBreakpoints.set(target, new WebInspector.BreakpointManager.TargetBreakpoint(target, this, this._breakpointManager._debuggerWorkspaceBinding)); 492 }, 493 494 /** 495 * @param {!WebInspector.Target} target 496 */ 497 targetRemoved: function(target) 498 { 499 var targetBreakpoint = this._targetBreakpoints.remove(target); 500 targetBreakpoint._cleanUpAfterDebuggerIsGone(); 501 targetBreakpoint._removeEventListeners(); 502 }, 503 504 /** 505 * @return {string} 506 */ 507 projectId: function() 508 { 509 return this._projectId; 510 }, 511 512 /** 513 * @return {string} 514 */ 515 path: function() 516 { 517 return this._path; 518 }, 519 520 /** 521 * @return {number} 522 */ 523 lineNumber: function() 524 { 525 return this._lineNumber; 526 }, 527 528 /** 529 * @return {number} 530 */ 531 columnNumber: function() 532 { 533 return this._columnNumber; 534 }, 535 536 /** 537 * @return {?WebInspector.UISourceCode} 538 */ 539 uiSourceCode: function() 540 { 541 return this._breakpointManager._workspace.uiSourceCode(this._projectId, this._path); 542 }, 543 544 /** 545 * @param {?WebInspector.UILocation} oldUILocation 546 * @param {!WebInspector.UILocation} newUILocation 547 */ 548 _replaceUILocation: function(oldUILocation, newUILocation) 549 { 550 if (this._isRemoved) 551 return; 552 553 this._removeUILocation(oldUILocation, true); 554 this._removeFakeBreakpointAtPrimaryLocation(); 555 556 if (!this._numberOfDebuggerLocationForUILocation[newUILocation.id()]) 557 this._numberOfDebuggerLocationForUILocation[newUILocation.id()] = 0; 558 559 if (++this._numberOfDebuggerLocationForUILocation[newUILocation.id()] === 1) 560 this._breakpointManager._uiLocationAdded(this, newUILocation); 561 }, 562 563 /** 564 * @param {?WebInspector.UILocation} uiLocation 565 * @param {boolean=} muteCreationFakeBreakpoint 566 */ 567 _removeUILocation: function(uiLocation, muteCreationFakeBreakpoint) 568 { 569 if (!uiLocation || --this._numberOfDebuggerLocationForUILocation[uiLocation.id()] !== 0) 570 return; 571 572 delete this._numberOfDebuggerLocationForUILocation[uiLocation.id()]; 573 this._breakpointManager._uiLocationRemoved(this, uiLocation); 574 if (!muteCreationFakeBreakpoint) 575 this._fakeBreakpointAtPrimaryLocation(); 576 }, 577 578 /** 579 * @return {boolean} 580 */ 581 enabled: function() 582 { 583 return this._enabled; 584 }, 585 586 /** 587 * @param {boolean} enabled 588 */ 589 setEnabled: function(enabled) 590 { 591 this._updateState(this._condition, enabled); 592 }, 593 594 /** 595 * @return {string} 596 */ 597 condition: function() 598 { 599 return this._condition; 600 }, 601 602 /** 603 * @param {string} condition 604 */ 605 setCondition: function(condition) 606 { 607 this._updateState(condition, this._enabled); 608 }, 609 610 /** 611 * @param {string} condition 612 * @param {boolean} enabled 613 */ 614 _updateState: function(condition, enabled) 615 { 616 if (this._enabled === enabled && this._condition === condition) 617 return; 618 this._enabled = enabled; 619 this._condition = condition; 620 this._breakpointManager._storage._updateBreakpoint(this); 621 this._updateBreakpoint(); 622 }, 623 624 _updateBreakpoint: function() 625 { 626 this._removeFakeBreakpointAtPrimaryLocation(); 627 this._fakeBreakpointAtPrimaryLocation(); 628 var targetBreakpoints = this._targetBreakpoints.values(); 629 for (var i = 0; i < targetBreakpoints.length; ++i) 630 targetBreakpoints[i]._scheduleUpdateInDebugger(); 631 }, 632 633 /** 634 * @param {boolean=} keepInStorage 635 */ 636 remove: function(keepInStorage) 637 { 638 639 this._isRemoved = true; 640 var removeFromStorage = !keepInStorage; 641 this._removeFakeBreakpointAtPrimaryLocation(); 642 var targetBreakpoints = this._targetBreakpoints.values(); 643 for (var i = 0; i < targetBreakpoints.length; ++i) { 644 targetBreakpoints[i]._scheduleUpdateInDebugger(); 645 targetBreakpoints[i]._removeEventListeners(); 646 } 647 648 this._breakpointManager._removeBreakpoint(this, removeFromStorage); 649 this._breakpointManager._targetManager.unobserveTargets(this); 650 }, 651 652 /** 653 * @param {!WebInspector.Target} target 654 */ 655 _updateInDebuggerForTarget: function(target) 656 { 657 this._targetBreakpoints.get(target)._scheduleUpdateInDebugger(); 658 }, 659 660 /** 661 * @return {string} 662 */ 663 _breakpointStorageId: function() 664 { 665 return WebInspector.BreakpointManager._breakpointStorageId(this._sourceFileId, this._lineNumber, this._columnNumber); 666 }, 667 668 _fakeBreakpointAtPrimaryLocation: function() 669 { 670 if (this._isRemoved || !Object.isEmpty(this._numberOfDebuggerLocationForUILocation) || this._fakePrimaryLocation) 671 return; 672 673 var uiSourceCode = this._breakpointManager._workspace.uiSourceCode(this._projectId, this._path); 674 if (!uiSourceCode) 675 return; 676 677 this._fakePrimaryLocation = uiSourceCode.uiLocation(this._lineNumber, this._columnNumber); 678 this._breakpointManager._uiLocationAdded(this, this._fakePrimaryLocation); 679 }, 680 681 _removeFakeBreakpointAtPrimaryLocation: function() 682 { 683 if (this._fakePrimaryLocation) { 684 this._breakpointManager._uiLocationRemoved(this, this._fakePrimaryLocation); 685 delete this._fakePrimaryLocation; 686 } 687 }, 688 689 _resetLocations: function() 690 { 691 this._removeFakeBreakpointAtPrimaryLocation(); 692 var targetBreakpoints = this._targetBreakpoints.values(); 693 for (var i = 0; i < targetBreakpoints.length; ++i) 694 targetBreakpoints[i]._resetLocations(); 695 } 696 } 697 698 /** 699 * @constructor 700 * @extends {WebInspector.SDKObject} 701 * @param {!WebInspector.Target} target 702 * @param {!WebInspector.BreakpointManager.Breakpoint} breakpoint 703 * @param {!WebInspector.DebuggerWorkspaceBinding} debuggerWorkspaceBinding 704 */ 705 WebInspector.BreakpointManager.TargetBreakpoint = function(target, breakpoint, debuggerWorkspaceBinding) 706 { 707 WebInspector.SDKObject.call(this, target); 708 this._breakpoint = breakpoint; 709 this._debuggerWorkspaceBinding = debuggerWorkspaceBinding; 710 711 /** @type {!Array.<!WebInspector.DebuggerWorkspaceBinding.Location>} */ 712 this._liveLocations = []; 713 714 /** @type {!Object.<string, !WebInspector.UILocation>} */ 715 this._uiLocations = {}; 716 target.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.DebuggerWasDisabled, this._cleanUpAfterDebuggerIsGone, this); 717 target.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.DebuggerWasEnabled, this._scheduleUpdateInDebugger, this); 718 this._hasPendingUpdate = false; 719 this._isUpdating = false; 720 this._cancelCallback = false; 721 this._currentState = null; 722 if (target.debuggerModel.debuggerEnabled()) 723 this._scheduleUpdateInDebugger(); 724 } 725 726 WebInspector.BreakpointManager.TargetBreakpoint.prototype = { 727 728 /** 729 * @return {!WebInspector.DebuggerModel} 730 */ 731 _debuggerModel: function() 732 { 733 return this.target().debuggerModel; 734 }, 735 736 _resetLocations: function() 737 { 738 var uiLocations = Object.values(this._uiLocations); 739 for (var i = 0; i < uiLocations.length; ++i) 740 this._breakpoint._removeUILocation(uiLocations[i]); 741 742 this._uiLocations = {}; 743 744 for (var i = 0; i < this._liveLocations.length; ++i) 745 this._liveLocations[i].dispose(); 746 this._liveLocations = []; 747 }, 748 749 _scheduleUpdateInDebugger: function() 750 { 751 if (this._isUpdating) { 752 this._hasPendingUpdate = true; 753 return; 754 } 755 756 this._isUpdating = true; 757 this._updateInDebugger(this._didUpdateInDebugger.bind(this)); 758 }, 759 760 _didUpdateInDebugger: function() 761 { 762 this._isUpdating = false; 763 if (this._hasPendingUpdate) { 764 this._hasPendingUpdate = false; 765 this._scheduleUpdateInDebugger(); 766 } 767 }, 768 769 /** 770 * @return {boolean} 771 */ 772 _scriptDiverged: function() 773 { 774 var uiSourceCode = this._breakpoint.uiSourceCode(); 775 if (!uiSourceCode) 776 return false; 777 var scriptFile = this._debuggerWorkspaceBinding.scriptFile(uiSourceCode, this.target()); 778 return !!scriptFile && scriptFile.hasDivergedFromVM(); 779 780 }, 781 782 /** 783 * @param {function()} callback 784 */ 785 _updateInDebugger: function(callback) 786 { 787 if (this.target().isDetached()) { 788 this._cleanUpAfterDebuggerIsGone(); 789 callback(); 790 return; 791 } 792 793 var uiSourceCode = this._breakpoint.uiSourceCode(); 794 var lineNumber = this._breakpoint._lineNumber; 795 var columnNumber = this._breakpoint._columnNumber; 796 var condition = this._breakpoint.condition(); 797 798 var debuggerLocation = uiSourceCode ? this._debuggerWorkspaceBinding.uiLocationToRawLocation(this.target(), uiSourceCode, lineNumber, columnNumber) : null; 799 var newState; 800 if (this._breakpoint._isRemoved || !this._breakpoint.enabled() || this._scriptDiverged()) 801 newState = null; 802 else if (debuggerLocation) { 803 var script = debuggerLocation.script(); 804 if (script.sourceURL) 805 newState = new WebInspector.BreakpointManager.Breakpoint.State(script.sourceURL, null, debuggerLocation.lineNumber, debuggerLocation.columnNumber, condition); 806 else 807 newState = new WebInspector.BreakpointManager.Breakpoint.State(null, debuggerLocation.scriptId, debuggerLocation.lineNumber, debuggerLocation.columnNumber, condition) 808 } else if (this._breakpoint._currentState && this._breakpoint._currentState.url) { 809 var position = this._breakpoint._currentState; 810 newState = new WebInspector.BreakpointManager.Breakpoint.State(position.url, null, position.lineNumber, position.columnNumber, condition); 811 } else if (uiSourceCode && uiSourceCode.url) 812 newState = new WebInspector.BreakpointManager.Breakpoint.State(uiSourceCode.url, null, lineNumber, columnNumber, condition); 813 814 if (this._debuggerId && WebInspector.BreakpointManager.Breakpoint.State.equals(newState, this._currentState)) { 815 callback(); 816 return; 817 } 818 819 this._breakpoint._currentState = newState; 820 821 if (this._debuggerId) { 822 this._resetLocations(); 823 this._debuggerModel().removeBreakpoint(this._debuggerId, this._didRemoveFromDebugger.bind(this, callback)); 824 this._scheduleUpdateInDebugger(); 825 this._currentState = null; 826 return; 827 } 828 829 if (!newState) { 830 callback(); 831 return; 832 } 833 834 var updateCallback = this._didSetBreakpointInDebugger.bind(this, callback); 835 if (newState.url) 836 this._debuggerModel().setBreakpointByURL(newState.url, newState.lineNumber, newState.columnNumber, this._breakpoint.condition(), updateCallback); 837 else if (newState.scriptId) 838 this._debuggerModel().setBreakpointBySourceId(/** @type {!WebInspector.DebuggerModel.Location} */ (debuggerLocation), condition, updateCallback); 839 840 this._currentState = newState; 841 }, 842 843 /** 844 * @param {function()} callback 845 * @param {?DebuggerAgent.BreakpointId} breakpointId 846 * @param {!Array.<!WebInspector.DebuggerModel.Location>} locations 847 */ 848 _didSetBreakpointInDebugger: function(callback, breakpointId, locations) 849 { 850 if (this._cancelCallback) { 851 this._cancelCallback = false; 852 callback(); 853 return; 854 } 855 856 if (!breakpointId) { 857 this._breakpoint.remove(true); 858 callback(); 859 return; 860 } 861 862 this._debuggerId = breakpointId; 863 this.target().debuggerModel.addBreakpointListener(this._debuggerId, this._breakpointResolved, this); 864 for (var i = 0; i < locations.length; ++i) { 865 if (!this._addResolvedLocation(locations[i])) 866 break; 867 } 868 callback(); 869 }, 870 871 /** 872 * @param {function()} callback 873 */ 874 _didRemoveFromDebugger: function(callback) 875 { 876 if (this._cancelCallback) { 877 this._cancelCallback = false; 878 callback(); 879 return; 880 } 881 882 this._resetLocations(); 883 this.target().debuggerModel.removeBreakpointListener(this._debuggerId, this._breakpointResolved, this); 884 delete this._debuggerId; 885 callback(); 886 }, 887 888 /** 889 * @param {!WebInspector.Event} event 890 */ 891 _breakpointResolved: function(event) 892 { 893 this._addResolvedLocation(/** @type {!WebInspector.DebuggerModel.Location}*/ (event.data)); 894 }, 895 896 /** 897 * @param {!WebInspector.DebuggerModel.Location} location 898 * @param {!WebInspector.UILocation} uiLocation 899 */ 900 _locationUpdated: function(location, uiLocation) 901 { 902 var oldUILocation = this._uiLocations[location.id()] || null; 903 this._uiLocations[location.id()] = uiLocation; 904 this._breakpoint._replaceUILocation(oldUILocation, uiLocation); 905 }, 906 907 /** 908 * @param {!WebInspector.DebuggerModel.Location} location 909 * @return {boolean} 910 */ 911 _addResolvedLocation: function(location) 912 { 913 var uiLocation = this._debuggerWorkspaceBinding.rawLocationToUILocation(location); 914 var breakpoint = this._breakpoint._breakpointManager.findBreakpoint(uiLocation.uiSourceCode, uiLocation.lineNumber, uiLocation.columnNumber); 915 if (breakpoint && breakpoint !== this._breakpoint) { 916 // location clash 917 this._breakpoint.remove(); 918 return false; 919 } 920 this._liveLocations.push(this._debuggerWorkspaceBinding.createLiveLocation(location, this._locationUpdated.bind(this, location))); 921 return true; 922 }, 923 924 _cleanUpAfterDebuggerIsGone: function() 925 { 926 if (this._isUpdating) 927 this._cancelCallback = true; 928 929 this._resetLocations(); 930 this._currentState = null; 931 if (this._debuggerId) 932 this._didRemoveFromDebugger(function() {}); 933 }, 934 935 _removeEventListeners: function() 936 { 937 this.target().debuggerModel.removeEventListener(WebInspector.DebuggerModel.Events.DebuggerWasDisabled, this._cleanUpAfterDebuggerIsGone, this); 938 this.target().debuggerModel.removeEventListener(WebInspector.DebuggerModel.Events.DebuggerWasEnabled, this._scheduleUpdateInDebugger, this); 939 }, 940 941 __proto__: WebInspector.SDKObject.prototype 942 } 943 944 /** 945 * @constructor 946 * @param {?string} url 947 * @param {?string} scriptId 948 * @param {number} lineNumber 949 * @param {number} columnNumber 950 * @param {string} condition 951 */ 952 WebInspector.BreakpointManager.Breakpoint.State = function(url, scriptId, lineNumber, columnNumber, condition) 953 { 954 this.url = url; 955 this.scriptId = scriptId; 956 this.lineNumber = lineNumber; 957 this.columnNumber = columnNumber; 958 this.condition = condition; 959 } 960 961 /** 962 * @param {?WebInspector.BreakpointManager.Breakpoint.State|undefined} stateA 963 * @param {?WebInspector.BreakpointManager.Breakpoint.State|undefined} stateB 964 * @return {boolean} 965 */ 966 WebInspector.BreakpointManager.Breakpoint.State.equals = function(stateA, stateB) 967 { 968 if (!stateA || !stateB) 969 return false; 970 971 if (stateA.scriptId || stateB.scriptId) 972 return false; 973 974 return stateA.url === stateB.url && stateA.lineNumber === stateB.lineNumber && stateA.columnNumber === stateB.columnNumber && stateA.condition === stateB.condition; 975 } 976 977 /** 978 * @constructor 979 * @param {!WebInspector.BreakpointManager} breakpointManager 980 * @param {!WebInspector.Setting} setting 981 */ 982 WebInspector.BreakpointManager.Storage = function(breakpointManager, setting) 983 { 984 this._breakpointManager = breakpointManager; 985 this._setting = setting; 986 var breakpoints = this._setting.get(); 987 /** @type {!Object.<string, !WebInspector.BreakpointManager.Storage.Item>} */ 988 this._breakpoints = {}; 989 for (var i = 0; i < breakpoints.length; ++i) { 990 var breakpoint = /** @type {!WebInspector.BreakpointManager.Storage.Item} */ (breakpoints[i]); 991 breakpoint.columnNumber = breakpoint.columnNumber || 0; 992 this._breakpoints[breakpoint.sourceFileId + ":" + breakpoint.lineNumber + ":" + breakpoint.columnNumber] = breakpoint; 993 } 994 } 995 996 WebInspector.BreakpointManager.Storage.prototype = { 997 mute: function() 998 { 999 this._muted = true; 1000 }, 1001 1002 unmute: function() 1003 { 1004 delete this._muted; 1005 }, 1006 1007 /** 1008 * @param {!WebInspector.UISourceCode} uiSourceCode 1009 * @return {!Array.<!WebInspector.BreakpointManager.Storage.Item>} 1010 */ 1011 breakpointItems: function(uiSourceCode) 1012 { 1013 var result = []; 1014 var sourceFileId = WebInspector.BreakpointManager._sourceFileId(uiSourceCode); 1015 for (var id in this._breakpoints) { 1016 var breakpoint = this._breakpoints[id]; 1017 if (breakpoint.sourceFileId === sourceFileId) 1018 result.push(breakpoint); 1019 } 1020 return result; 1021 }, 1022 1023 /** 1024 * @param {!WebInspector.BreakpointManager.Breakpoint} breakpoint 1025 */ 1026 _updateBreakpoint: function(breakpoint) 1027 { 1028 if (this._muted || !breakpoint._breakpointStorageId()) 1029 return; 1030 this._breakpoints[breakpoint._breakpointStorageId()] = new WebInspector.BreakpointManager.Storage.Item(breakpoint); 1031 this._save(); 1032 }, 1033 1034 /** 1035 * @param {!WebInspector.BreakpointManager.Breakpoint} breakpoint 1036 */ 1037 _removeBreakpoint: function(breakpoint) 1038 { 1039 if (this._muted) 1040 return; 1041 delete this._breakpoints[breakpoint._breakpointStorageId()]; 1042 this._save(); 1043 }, 1044 1045 _save: function() 1046 { 1047 var breakpointsArray = []; 1048 for (var id in this._breakpoints) 1049 breakpointsArray.push(this._breakpoints[id]); 1050 this._setting.set(breakpointsArray); 1051 } 1052 } 1053 1054 /** 1055 * @constructor 1056 * @param {!WebInspector.BreakpointManager.Breakpoint} breakpoint 1057 */ 1058 WebInspector.BreakpointManager.Storage.Item = function(breakpoint) 1059 { 1060 this.sourceFileId = breakpoint._sourceFileId; 1061 this.lineNumber = breakpoint.lineNumber(); 1062 this.columnNumber = breakpoint.columnNumber(); 1063 this.condition = breakpoint.condition(); 1064 this.enabled = breakpoint.enabled(); 1065 } 1066 1067 /** @type {!WebInspector.BreakpointManager} */ 1068 WebInspector.breakpointManager; 1069