1/* 2 * Copyright (C) 2009 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 31WebInspector.SourceFrame = function(parentElement, addBreakpointDelegate, removeBreakpointDelegate) 32{ 33 this._parentElement = parentElement; 34 35 this._textModel = new WebInspector.TextEditorModel(); 36 this._textModel.replaceTabsWithSpaces = true; 37 38 this._messages = []; 39 this._rowMessages = {}; 40 this._messageBubbles = {}; 41 this.breakpoints = []; 42 this._shortcuts = {}; 43 44 this._loaded = false; 45 46 this._addBreakpointDelegate = addBreakpointDelegate; 47 this._removeBreakpointDelegate = removeBreakpointDelegate; 48} 49 50WebInspector.SourceFrame.prototype = { 51 52 set visible(visible) 53 { 54 this._visible = visible; 55 this._createViewerIfNeeded(); 56 }, 57 58 get executionLine() 59 { 60 return this._executionLine; 61 }, 62 63 set executionLine(x) 64 { 65 if (this._executionLine === x) 66 return; 67 68 var previousLine = this._executionLine; 69 this._executionLine = x; 70 71 if (this._textViewer) 72 this._updateExecutionLine(previousLine); 73 }, 74 75 revealLine: function(lineNumber) 76 { 77 if (this._textViewer) 78 this._textViewer.revealLine(lineNumber - 1, 0); 79 else 80 this._lineNumberToReveal = lineNumber; 81 }, 82 83 addBreakpoint: function(breakpoint) 84 { 85 this.breakpoints.push(breakpoint); 86 breakpoint.addEventListener("enabled", this._breakpointChanged, this); 87 breakpoint.addEventListener("disabled", this._breakpointChanged, this); 88 breakpoint.addEventListener("condition-changed", this._breakpointChanged, this); 89 if (this._textViewer) 90 this._addBreakpointToSource(breakpoint); 91 }, 92 93 removeBreakpoint: function(breakpoint) 94 { 95 this.breakpoints.remove(breakpoint); 96 breakpoint.removeEventListener("enabled", null, this); 97 breakpoint.removeEventListener("disabled", null, this); 98 breakpoint.removeEventListener("condition-changed", null, this); 99 if (this._textViewer) 100 this._removeBreakpointFromSource(breakpoint); 101 }, 102 103 addMessage: function(msg) 104 { 105 // Don't add the message if there is no message or valid line or if the msg isn't an error or warning. 106 if (!msg.message || msg.line <= 0 || !msg.isErrorOrWarning()) 107 return; 108 this._messages.push(msg) 109 if (this._textViewer) 110 this._addMessageToSource(msg); 111 }, 112 113 clearMessages: function() 114 { 115 for (var line in this._messageBubbles) { 116 var bubble = this._messageBubbles[line]; 117 bubble.parentNode.removeChild(bubble); 118 } 119 120 this._messages = []; 121 this._rowMessages = {}; 122 this._messageBubbles = {}; 123 if (this._textViewer) 124 this._textViewer.resize(); 125 }, 126 127 sizeToFitContentHeight: function() 128 { 129 if (this._textViewer) 130 this._textViewer.revalidateDecorationsAndPaint(); 131 }, 132 133 setContent: function(mimeType, content, url) 134 { 135 this._loaded = true; 136 this._textModel.setText(null, content); 137 this._mimeType = mimeType; 138 this._url = url; 139 this._createViewerIfNeeded(); 140 }, 141 142 highlightLine: function(line) 143 { 144 if (this._textViewer) 145 this._textViewer.highlightLine(line - 1); 146 else 147 this._lineToHighlight = line; 148 }, 149 150 _createViewerIfNeeded: function() 151 { 152 if (!this._visible || !this._loaded || this._textViewer) 153 return; 154 155 this._textViewer = new WebInspector.TextViewer(this._textModel, WebInspector.platform, this._url); 156 var element = this._textViewer.element; 157 element.addEventListener("keydown", this._keyDown.bind(this), true); 158 element.addEventListener("contextmenu", this._contextMenu.bind(this), true); 159 element.addEventListener("mousedown", this._mouseDown.bind(this), true); 160 this._parentElement.appendChild(element); 161 162 this._needsProgramCounterImage = true; 163 this._needsBreakpointImages = true; 164 165 this._textViewer.beginUpdates(); 166 167 this._textViewer.mimeType = this._mimeType; 168 this._addExistingMessagesToSource(); 169 this._addExistingBreakpointsToSource(); 170 this._updateExecutionLine(); 171 this._textViewer.resize(); 172 173 if (this._lineNumberToReveal) { 174 this.revealLine(this._lineNumberToReveal); 175 delete this._lineNumberToReveal; 176 } 177 178 if (this._pendingMarkRange) { 179 var range = this._pendingMarkRange; 180 this.markAndRevealRange(range); 181 delete this._pendingMarkRange; 182 } 183 184 if (this._lineToHighlight) { 185 this.highlightLine(this._lineToHighlight); 186 delete this._lineToHighlight; 187 } 188 this._textViewer.endUpdates(); 189 }, 190 191 findSearchMatches: function(query) 192 { 193 var ranges = []; 194 195 // First do case-insensitive search. 196 var regex = ""; 197 for (var i = 0; i < query.length; ++i) { 198 var char = query.charAt(i); 199 if (char === "]") 200 char = "\\]"; 201 regex += "[" + char + "]"; 202 } 203 var regexObject = new RegExp(regex, "i"); 204 this._collectRegexMatches(regexObject, ranges); 205 206 // Then try regex search if user knows the / / hint. 207 try { 208 if (/^\/.*\/$/.test(query)) 209 this._collectRegexMatches(new RegExp(query.substring(1, query.length - 1)), ranges); 210 } catch (e) { 211 // Silent catch. 212 } 213 return ranges; 214 }, 215 216 _collectRegexMatches: function(regexObject, ranges) 217 { 218 for (var i = 0; i < this._textModel.linesCount; ++i) { 219 var line = this._textModel.line(i); 220 var offset = 0; 221 do { 222 var match = regexObject.exec(line); 223 if (match) { 224 ranges.push(new WebInspector.TextRange(i, offset + match.index, i, offset + match.index + match[0].length)); 225 offset += match.index + 1; 226 line = line.substring(match.index + 1); 227 } 228 } while (match) 229 } 230 return ranges; 231 }, 232 233 markAndRevealRange: function(range) 234 { 235 if (this._textViewer) 236 this._textViewer.markAndRevealRange(range); 237 else 238 this._pendingMarkRange = range; 239 }, 240 241 clearMarkedRange: function() 242 { 243 if (this._textViewer) { 244 this._textViewer.markAndRevealRange(null); 245 } else 246 delete this._pendingMarkRange; 247 }, 248 249 _incrementMessageRepeatCount: function(msg, repeatDelta) 250 { 251 if (!msg._resourceMessageLineElement) 252 return; 253 254 if (!msg._resourceMessageRepeatCountElement) { 255 var repeatedElement = document.createElement("span"); 256 msg._resourceMessageLineElement.appendChild(repeatedElement); 257 msg._resourceMessageRepeatCountElement = repeatedElement; 258 } 259 260 msg.repeatCount += repeatDelta; 261 msg._resourceMessageRepeatCountElement.textContent = WebInspector.UIString(" (repeated %d times)", msg.repeatCount); 262 }, 263 264 _breakpointChanged: function(event) 265 { 266 var breakpoint = event.target; 267 var lineNumber = breakpoint.line - 1; 268 if (lineNumber >= this._textModel.linesCount) 269 return; 270 271 if (breakpoint.enabled) 272 this._textViewer.removeDecoration(lineNumber, "webkit-breakpoint-disabled"); 273 else 274 this._textViewer.addDecoration(lineNumber, "webkit-breakpoint-disabled"); 275 276 if (breakpoint.condition) 277 this._textViewer.addDecoration(lineNumber, "webkit-breakpoint-conditional"); 278 else 279 this._textViewer.removeDecoration(lineNumber, "webkit-breakpoint-conditional"); 280 }, 281 282 _updateExecutionLine: function(previousLine) 283 { 284 if (previousLine) { 285 if (previousLine - 1 < this._textModel.linesCount) 286 this._textViewer.removeDecoration(previousLine - 1, "webkit-execution-line"); 287 } 288 289 if (!this._executionLine) 290 return; 291 292 this._drawProgramCounterImageIfNeeded(); 293 294 if (this._executionLine < this._textModel.linesCount) 295 this._textViewer.addDecoration(this._executionLine - 1, "webkit-execution-line"); 296 }, 297 298 _addExistingMessagesToSource: function() 299 { 300 var length = this._messages.length; 301 for (var i = 0; i < length; ++i) 302 this._addMessageToSource(this._messages[i]); 303 }, 304 305 _addMessageToSource: function(msg) 306 { 307 if (msg.line >= this._textModel.linesCount) 308 return; 309 310 var messageBubbleElement = this._messageBubbles[msg.line]; 311 if (!messageBubbleElement || messageBubbleElement.nodeType !== Node.ELEMENT_NODE || !messageBubbleElement.hasStyleClass("webkit-html-message-bubble")) { 312 messageBubbleElement = document.createElement("div"); 313 messageBubbleElement.className = "webkit-html-message-bubble"; 314 this._messageBubbles[msg.line] = messageBubbleElement; 315 this._textViewer.addDecoration(msg.line - 1, messageBubbleElement); 316 } 317 318 var rowMessages = this._rowMessages[msg.line]; 319 if (!rowMessages) { 320 rowMessages = []; 321 this._rowMessages[msg.line] = rowMessages; 322 } 323 324 for (var i = 0; i < rowMessages.length; ++i) { 325 if (rowMessages[i].isEqual(msg, true)) { 326 this._incrementMessageRepeatCount(rowMessages[i], msg.repeatDelta); 327 return; 328 } 329 } 330 331 rowMessages.push(msg); 332 333 var imageURL; 334 switch (msg.level) { 335 case WebInspector.ConsoleMessage.MessageLevel.Error: 336 messageBubbleElement.addStyleClass("webkit-html-error-message"); 337 imageURL = "Images/errorIcon.png"; 338 break; 339 case WebInspector.ConsoleMessage.MessageLevel.Warning: 340 messageBubbleElement.addStyleClass("webkit-html-warning-message"); 341 imageURL = "Images/warningIcon.png"; 342 break; 343 } 344 345 var messageLineElement = document.createElement("div"); 346 messageLineElement.className = "webkit-html-message-line"; 347 messageBubbleElement.appendChild(messageLineElement); 348 349 // Create the image element in the Inspector's document so we can use relative image URLs. 350 var image = document.createElement("img"); 351 image.src = imageURL; 352 image.className = "webkit-html-message-icon"; 353 messageLineElement.appendChild(image); 354 messageLineElement.appendChild(document.createTextNode(msg.message)); 355 356 msg._resourceMessageLineElement = messageLineElement; 357 }, 358 359 _addExistingBreakpointsToSource: function() 360 { 361 for (var i = 0; i < this.breakpoints.length; ++i) 362 this._addBreakpointToSource(this.breakpoints[i]); 363 }, 364 365 _addBreakpointToSource: function(breakpoint) 366 { 367 var lineNumber = breakpoint.line - 1; 368 if (lineNumber >= this._textModel.linesCount) 369 return; 370 371 this._textModel.setAttribute(lineNumber, "breakpoint", breakpoint); 372 breakpoint.sourceText = this._textModel.line(breakpoint.line - 1); 373 this._drawBreakpointImagesIfNeeded(); 374 375 this._textViewer.beginUpdates(); 376 this._textViewer.addDecoration(lineNumber, "webkit-breakpoint"); 377 if (!breakpoint.enabled) 378 this._textViewer.addDecoration(lineNumber, "webkit-breakpoint-disabled"); 379 if (breakpoint.condition) 380 this._textViewer.addDecoration(lineNumber, "webkit-breakpoint-conditional"); 381 this._textViewer.endUpdates(); 382 }, 383 384 _removeBreakpointFromSource: function(breakpoint) 385 { 386 var lineNumber = breakpoint.line - 1; 387 this._textViewer.beginUpdates(); 388 this._textModel.removeAttribute(lineNumber, "breakpoint"); 389 this._textViewer.removeDecoration(lineNumber, "webkit-breakpoint"); 390 this._textViewer.removeDecoration(lineNumber, "webkit-breakpoint-disabled"); 391 this._textViewer.removeDecoration(lineNumber, "webkit-breakpoint-conditional"); 392 this._textViewer.endUpdates(); 393 }, 394 395 _contextMenu: function(event) 396 { 397 if (event.target.className !== "webkit-line-number") 398 return; 399 var row = event.target.parentElement; 400 401 var lineNumber = row.lineNumber; 402 var contextMenu = new WebInspector.ContextMenu(); 403 404 var breakpoint = this._textModel.getAttribute(lineNumber, "breakpoint"); 405 if (!breakpoint) { 406 // This row doesn't have a breakpoint: We want to show Add Breakpoint and Add and Edit Breakpoint. 407 contextMenu.appendItem(WebInspector.UIString("Add Breakpoint"), this._addBreakpointDelegate.bind(this, lineNumber + 1)); 408 409 function addConditionalBreakpoint() 410 { 411 this._addBreakpointDelegate(lineNumber + 1); 412 var breakpoint = this._textModel.getAttribute(lineNumber, "breakpoint"); 413 if (breakpoint) 414 this._editBreakpointCondition(breakpoint); 415 } 416 417 contextMenu.appendItem(WebInspector.UIString("Add Conditional Breakpoint..."), addConditionalBreakpoint.bind(this)); 418 } else { 419 // This row has a breakpoint, we want to show edit and remove breakpoint, and either disable or enable. 420 contextMenu.appendItem(WebInspector.UIString("Remove Breakpoint"), WebInspector.panels.scripts.removeBreakpoint.bind(WebInspector.panels.scripts, breakpoint)); 421 contextMenu.appendItem(WebInspector.UIString("Edit Breakpoint..."), this._editBreakpointCondition.bind(this, breakpoint)); 422 if (breakpoint.enabled) 423 contextMenu.appendItem(WebInspector.UIString("Disable Breakpoint"), function() { breakpoint.enabled = false; }); 424 else 425 contextMenu.appendItem(WebInspector.UIString("Enable Breakpoint"), function() { breakpoint.enabled = true; }); 426 } 427 contextMenu.show(event); 428 }, 429 430 _mouseDown: function(event) 431 { 432 if (event.button != 0 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) 433 return; 434 if (event.target.className !== "webkit-line-number") 435 return; 436 var row = event.target.parentElement; 437 438 var lineNumber = row.lineNumber; 439 440 var breakpoint = this._textModel.getAttribute(lineNumber, "breakpoint"); 441 if (breakpoint) 442 this._removeBreakpointDelegate(breakpoint); 443 else if (this._addBreakpointDelegate) 444 this._addBreakpointDelegate(lineNumber + 1); 445 event.preventDefault(); 446 }, 447 448 _editBreakpointCondition: function(breakpoint) 449 { 450 this._showBreakpointConditionPopup(breakpoint.line); 451 452 function committed(element, newText) 453 { 454 breakpoint.condition = newText; 455 dismissed.call(this); 456 } 457 458 function dismissed() 459 { 460 if (this._conditionElement) 461 this._textViewer.removeDecoration(breakpoint.line - 1, this._conditionElement); 462 delete this._conditionEditorElement; 463 delete this._conditionElement; 464 } 465 466 var dismissedHandler = dismissed.bind(this); 467 this._conditionEditorElement.addEventListener("blur", dismissedHandler, false); 468 469 WebInspector.startEditing(this._conditionEditorElement, committed.bind(this), dismissedHandler); 470 this._conditionEditorElement.value = breakpoint.condition; 471 this._conditionEditorElement.select(); 472 }, 473 474 _showBreakpointConditionPopup: function(lineNumber) 475 { 476 this._conditionElement = this._createConditionElement(lineNumber); 477 this._textViewer.addDecoration(lineNumber - 1, this._conditionElement); 478 }, 479 480 _createConditionElement: function(lineNumber) 481 { 482 var conditionElement = document.createElement("div"); 483 conditionElement.className = "source-frame-breakpoint-condition"; 484 485 var labelElement = document.createElement("label"); 486 labelElement.className = "source-frame-breakpoint-message"; 487 labelElement.htmlFor = "source-frame-breakpoint-condition"; 488 labelElement.appendChild(document.createTextNode(WebInspector.UIString("The breakpoint on line %d will stop only if this expression is true:", lineNumber))); 489 conditionElement.appendChild(labelElement); 490 491 var editorElement = document.createElement("input"); 492 editorElement.id = "source-frame-breakpoint-condition"; 493 editorElement.className = "monospace"; 494 editorElement.type = "text" 495 conditionElement.appendChild(editorElement); 496 this._conditionEditorElement = editorElement; 497 498 return conditionElement; 499 }, 500 501 _keyDown: function(event) 502 { 503 var shortcut = WebInspector.KeyboardShortcut.makeKeyFromEvent(event); 504 var handler = this._shortcuts[shortcut]; 505 if (handler) { 506 handler(event); 507 event.preventDefault(); 508 } else 509 WebInspector.documentKeyDown(event); 510 }, 511 512 _evalSelectionInCallFrame: function(event) 513 { 514 if (!WebInspector.panels.scripts || !WebInspector.panels.scripts.paused) 515 return; 516 517 var selection = this.element.contentWindow.getSelection(); 518 if (!selection.rangeCount) 519 return; 520 521 var expression = selection.getRangeAt(0).toString().trimWhitespace(); 522 WebInspector.panels.scripts.evaluateInSelectedCallFrame(expression, false, "console", function(result, exception) { 523 WebInspector.showConsole(); 524 var commandMessage = new WebInspector.ConsoleCommand(expression); 525 WebInspector.console.addMessage(commandMessage); 526 WebInspector.console.addMessage(new WebInspector.ConsoleCommandResult(result, exception, commandMessage)); 527 }); 528 }, 529 530 resize: function() 531 { 532 if (this._textViewer) 533 this._textViewer.resize(); 534 }, 535 536 _drawProgramCounterInContext: function(ctx, glow) 537 { 538 if (glow) 539 ctx.save(); 540 541 ctx.beginPath(); 542 ctx.moveTo(17, 2); 543 ctx.lineTo(19, 2); 544 ctx.lineTo(19, 0); 545 ctx.lineTo(21, 0); 546 ctx.lineTo(26, 5.5); 547 ctx.lineTo(21, 11); 548 ctx.lineTo(19, 11); 549 ctx.lineTo(19, 9); 550 ctx.lineTo(17, 9); 551 ctx.closePath(); 552 ctx.fillStyle = "rgb(142, 5, 4)"; 553 554 if (glow) { 555 ctx.shadowBlur = 4; 556 ctx.shadowColor = "rgb(255, 255, 255)"; 557 ctx.shadowOffsetX = -1; 558 ctx.shadowOffsetY = 0; 559 } 560 561 ctx.fill(); 562 ctx.fill(); // Fill twice to get a good shadow and darker anti-aliased pixels. 563 564 if (glow) 565 ctx.restore(); 566 }, 567 568 _drawProgramCounterImageIfNeeded: function() 569 { 570 if (!this._needsProgramCounterImage) 571 return; 572 573 var ctx = document.getCSSCanvasContext("2d", "program-counter", 26, 11); 574 ctx.clearRect(0, 0, 26, 11); 575 this._drawProgramCounterInContext(ctx, true); 576 577 delete this._needsProgramCounterImage; 578 }, 579 580 _drawBreakpointImagesIfNeeded: function(conditional) 581 { 582 if (!this._needsBreakpointImages) 583 return; 584 585 function drawBreakpoint(ctx, disabled, conditional) 586 { 587 ctx.beginPath(); 588 ctx.moveTo(0, 2); 589 ctx.lineTo(2, 0); 590 ctx.lineTo(21, 0); 591 ctx.lineTo(26, 5.5); 592 ctx.lineTo(21, 11); 593 ctx.lineTo(2, 11); 594 ctx.lineTo(0, 9); 595 ctx.closePath(); 596 ctx.fillStyle = conditional ? "rgb(217, 142, 1)" : "rgb(1, 142, 217)"; 597 ctx.strokeStyle = conditional ? "rgb(205, 103, 0)" : "rgb(0, 103, 205)"; 598 ctx.lineWidth = 3; 599 ctx.fill(); 600 ctx.save(); 601 ctx.clip(); 602 ctx.stroke(); 603 ctx.restore(); 604 605 if (!disabled) 606 return; 607 608 ctx.save(); 609 ctx.globalCompositeOperation = "destination-out"; 610 ctx.fillStyle = "rgba(0, 0, 0, 0.5)"; 611 ctx.fillRect(0, 0, 26, 11); 612 ctx.restore(); 613 } 614 615 616 // Unconditional breakpoints. 617 618 var ctx = document.getCSSCanvasContext("2d", "breakpoint", 26, 11); 619 ctx.clearRect(0, 0, 26, 11); 620 drawBreakpoint(ctx); 621 622 var ctx = document.getCSSCanvasContext("2d", "breakpoint-program-counter", 26, 11); 623 ctx.clearRect(0, 0, 26, 11); 624 drawBreakpoint(ctx); 625 ctx.clearRect(20, 0, 6, 11); 626 this._drawProgramCounterInContext(ctx, true); 627 628 var ctx = document.getCSSCanvasContext("2d", "breakpoint-disabled", 26, 11); 629 ctx.clearRect(0, 0, 26, 11); 630 drawBreakpoint(ctx, true); 631 632 var ctx = document.getCSSCanvasContext("2d", "breakpoint-disabled-program-counter", 26, 11); 633 ctx.clearRect(0, 0, 26, 11); 634 drawBreakpoint(ctx, true); 635 ctx.clearRect(20, 0, 6, 11); 636 this._drawProgramCounterInContext(ctx, true); 637 638 639 // Conditional breakpoints. 640 641 var ctx = document.getCSSCanvasContext("2d", "breakpoint-conditional", 26, 11); 642 ctx.clearRect(0, 0, 26, 11); 643 drawBreakpoint(ctx, false, true); 644 645 var ctx = document.getCSSCanvasContext("2d", "breakpoint-conditional-program-counter", 26, 11); 646 ctx.clearRect(0, 0, 26, 11); 647 drawBreakpoint(ctx, false, true); 648 ctx.clearRect(20, 0, 6, 11); 649 this._drawProgramCounterInContext(ctx, true); 650 651 var ctx = document.getCSSCanvasContext("2d", "breakpoint-disabled-conditional", 26, 11); 652 ctx.clearRect(0, 0, 26, 11); 653 drawBreakpoint(ctx, true, true); 654 655 var ctx = document.getCSSCanvasContext("2d", "breakpoint-disabled-conditional-program-counter", 26, 11); 656 ctx.clearRect(0, 0, 26, 11); 657 drawBreakpoint(ctx, true, true); 658 ctx.clearRect(20, 0, 6, 11); 659 this._drawProgramCounterInContext(ctx, true); 660 661 delete this._needsBreakpointImages; 662 } 663} 664 665WebInspector.SourceFrame.prototype.__proto__ = WebInspector.Object.prototype; 666