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 31 WebInspector.SourceFrame = function(delegate, url) 32 { 33 WebInspector.TextViewerDelegate.call(this); 34 35 this._delegate = delegate; 36 this._url = url; 37 38 this._textModel = new WebInspector.TextEditorModel(); 39 this._textModel.replaceTabsWithSpaces = true; 40 41 this._textViewer = new WebInspector.TextViewer(this._textModel, WebInspector.platform, this._url, this); 42 this._textViewer.element.addStyleClass("script-view"); 43 this._visible = false; 44 45 this._currentSearchResultIndex = -1; 46 this._searchResults = []; 47 48 this._messages = []; 49 this._rowMessages = {}; 50 this._messageBubbles = {}; 51 52 this._breakpoints = {}; 53 } 54 55 WebInspector.SourceFrame.Events = { 56 Loaded: "loaded" 57 } 58 59 WebInspector.SourceFrame.prototype = { 60 get visible() 61 { 62 return this._textViewer.visible; 63 }, 64 65 set visible(x) 66 { 67 this._textViewer.visible = x; 68 }, 69 70 show: function(parentElement) 71 { 72 this._ensureContentLoaded(); 73 74 this._textViewer.show(parentElement); 75 this._textViewer.resize(); 76 77 if (this.loaded) { 78 if (this._scrollTop) 79 this._textViewer.scrollTop = this._scrollTop; 80 if (this._scrollLeft) 81 this._textViewer.scrollLeft = this._scrollLeft; 82 } 83 }, 84 85 hide: function() 86 { 87 if (this.loaded) { 88 this._scrollTop = this._textViewer.scrollTop; 89 this._scrollLeft = this._textViewer.scrollLeft; 90 this._textViewer.freeCachedElements(); 91 } 92 93 this._textViewer.hide(); 94 this._hidePopup(); 95 this._clearLineHighlight(); 96 }, 97 98 detach: function() 99 { 100 this._textViewer.detach(); 101 }, 102 103 get element() 104 { 105 return this._textViewer.element; 106 }, 107 108 get loaded() 109 { 110 return !!this._content; 111 }, 112 113 hasContent: function() 114 { 115 return true; 116 }, 117 118 _ensureContentLoaded: function() 119 { 120 if (!this._contentRequested) { 121 this._contentRequested = true; 122 this.requestContent(this._initializeTextViewer.bind(this)); 123 } 124 }, 125 126 requestContent: function(callback) 127 { 128 this._delegate.requestContent(callback); 129 }, 130 131 markDiff: function(diffData) 132 { 133 if (this._diffLines && this.loaded) 134 this._removeDiffDecorations(); 135 136 this._diffLines = diffData; 137 if (this.loaded) 138 this._updateDiffDecorations(); 139 }, 140 141 addMessage: function(msg) 142 { 143 this._messages.push(msg); 144 if (this.loaded) 145 this.addMessageToSource(msg.line - 1, msg); 146 }, 147 148 clearMessages: function() 149 { 150 for (var line in this._messageBubbles) { 151 var bubble = this._messageBubbles[line]; 152 bubble.parentNode.removeChild(bubble); 153 } 154 155 this._messages = []; 156 this._rowMessages = {}; 157 this._messageBubbles = {}; 158 159 this._textViewer.resize(); 160 }, 161 162 get textModel() 163 { 164 return this._textModel; 165 }, 166 167 get scrollTop() 168 { 169 return this.loaded ? this._textViewer.scrollTop : this._scrollTop; 170 }, 171 172 set scrollTop(scrollTop) 173 { 174 this._scrollTop = scrollTop; 175 if (this.loaded) 176 this._textViewer.scrollTop = scrollTop; 177 }, 178 179 highlightLine: function(line) 180 { 181 if (this.loaded) 182 this._textViewer.highlightLine(line); 183 else 184 this._lineToHighlight = line; 185 }, 186 187 _clearLineHighlight: function() 188 { 189 if (this.loaded) 190 this._textViewer.clearLineHighlight(); 191 else 192 delete this._lineToHighlight; 193 }, 194 195 _saveViewerState: function() 196 { 197 this._viewerState = { 198 textModelContent: this._textModel.text, 199 executionLineNumber: this._executionLineNumber, 200 messages: this._messages, 201 diffLines: this._diffLines, 202 breakpoints: this._breakpoints 203 }; 204 }, 205 206 _restoreViewerState: function() 207 { 208 if (!this._viewerState) 209 return; 210 this._textModel.setText(null, this._viewerState.textModelContent); 211 212 this._messages = this._viewerState.messages; 213 this._diffLines = this._viewerState.diffLines; 214 this._setTextViewerDecorations(); 215 216 if (typeof this._viewerState.executionLineNumber === "number") { 217 this.clearExecutionLine(); 218 this.setExecutionLine(this._viewerState.executionLineNumber); 219 } 220 221 var oldBreakpoints = this._breakpoints; 222 this._breakpoints = {}; 223 for (var lineNumber in oldBreakpoints) 224 this.removeBreakpoint(Number(lineNumber)); 225 226 var newBreakpoints = this._viewerState.breakpoints; 227 for (var lineNumber in newBreakpoints) { 228 lineNumber = Number(lineNumber); 229 var breakpoint = newBreakpoints[lineNumber]; 230 this.addBreakpoint(lineNumber, breakpoint.resolved, breakpoint.conditional, breakpoint.enabled); 231 } 232 233 delete this._viewerState; 234 }, 235 236 isContentEditable: function() 237 { 238 return this._delegate.canEditScriptSource(); 239 }, 240 241 readOnlyStateChanged: function(readOnly) 242 { 243 WebInspector.markBeingEdited(this._textViewer.element, !readOnly); 244 }, 245 246 startEditing: function() 247 { 248 if (!this._viewerState) { 249 this._saveViewerState(); 250 this._delegate.setScriptSourceIsBeingEdited(true); 251 } 252 253 WebInspector.searchController.cancelSearch(); 254 this.clearMessages(); 255 }, 256 257 endEditing: function(oldRange, newRange) 258 { 259 if (!oldRange || !newRange) 260 return; 261 262 // Adjust execution line number. 263 if (typeof this._executionLineNumber === "number") { 264 var newExecutionLineNumber = this._lineNumberAfterEditing(this._executionLineNumber, oldRange, newRange); 265 this.clearExecutionLine(); 266 this.setExecutionLine(newExecutionLineNumber, true); 267 } 268 269 // Adjust breakpoints. 270 var oldBreakpoints = this._breakpoints; 271 this._breakpoints = {}; 272 for (var lineNumber in oldBreakpoints) { 273 lineNumber = Number(lineNumber); 274 var breakpoint = oldBreakpoints[lineNumber]; 275 var newLineNumber = this._lineNumberAfterEditing(lineNumber, oldRange, newRange); 276 if (lineNumber === newLineNumber) 277 this._breakpoints[lineNumber] = breakpoint; 278 else { 279 this.removeBreakpoint(lineNumber); 280 this.addBreakpoint(newLineNumber, breakpoint.resolved, breakpoint.conditional, breakpoint.enabled); 281 } 282 } 283 }, 284 285 _lineNumberAfterEditing: function(lineNumber, oldRange, newRange) 286 { 287 var shiftOffset = lineNumber <= oldRange.startLine ? 0 : newRange.linesCount - oldRange.linesCount; 288 289 // Special case of editing the line itself. We should decide whether the line number should move below or not. 290 if (lineNumber === oldRange.startLine) { 291 var whiteSpacesRegex = /^[\s\xA0]*$/; 292 for (var i = 0; lineNumber + i <= newRange.endLine; ++i) { 293 if (!whiteSpacesRegex.test(this._textModel.line(lineNumber + i))) { 294 shiftOffset = i; 295 break; 296 } 297 } 298 } 299 300 var newLineNumber = Math.max(0, lineNumber + shiftOffset); 301 if (oldRange.startLine < lineNumber && lineNumber < oldRange.endLine) 302 newLineNumber = oldRange.startLine; 303 return newLineNumber; 304 }, 305 306 _initializeTextViewer: function(mimeType, content) 307 { 308 this._textViewer.mimeType = mimeType; 309 310 this._content = content; 311 this._textModel.setText(null, content); 312 313 var element = this._textViewer.element; 314 if (this._delegate.debuggingSupported()) { 315 element.addEventListener("contextmenu", this._contextMenu.bind(this), true); 316 element.addEventListener("mousedown", this._mouseDown.bind(this), true); 317 element.addEventListener("mousemove", this._mouseMove.bind(this), true); 318 element.addEventListener("scroll", this._scroll.bind(this), true); 319 } 320 321 this._textViewer.beginUpdates(); 322 323 this._setTextViewerDecorations(); 324 325 if (typeof this._executionLineNumber === "number") 326 this.setExecutionLine(this._executionLineNumber); 327 328 if (this._lineToHighlight) { 329 this.highlightLine(this._lineToHighlight); 330 delete this._lineToHighlight; 331 } 332 333 if (this._delayedFindSearchMatches) { 334 this._delayedFindSearchMatches(); 335 delete this._delayedFindSearchMatches; 336 } 337 338 this.dispatchEventToListeners(WebInspector.SourceFrame.Events.Loaded); 339 340 this._textViewer.endUpdates(); 341 342 if (this._parentElement) 343 this.show(this._parentElement) 344 }, 345 346 _setTextViewerDecorations: function() 347 { 348 this._rowMessages = {}; 349 this._messageBubbles = {}; 350 351 this._textViewer.beginUpdates(); 352 353 this._addExistingMessagesToSource(); 354 this._updateDiffDecorations(); 355 356 this._textViewer.resize(); 357 358 this._textViewer.endUpdates(); 359 }, 360 361 performSearch: function(query, callback) 362 { 363 // Call searchCanceled since it will reset everything we need before doing a new search. 364 this.searchCanceled(); 365 366 function doFindSearchMatches(query) 367 { 368 this._currentSearchResultIndex = -1; 369 this._searchResults = []; 370 371 // First do case-insensitive search. 372 var regexObject = createSearchRegex(query); 373 this._collectRegexMatches(regexObject, this._searchResults); 374 375 // Then try regex search if user knows the / / hint. 376 try { 377 if (/^\/.*\/$/.test(query)) 378 this._collectRegexMatches(new RegExp(query.substring(1, query.length - 1)), this._searchResults); 379 } catch (e) { 380 // Silent catch. 381 } 382 383 callback(this, this._searchResults.length); 384 } 385 386 if (this.loaded) 387 doFindSearchMatches.call(this, query); 388 else 389 this._delayedFindSearchMatches = doFindSearchMatches.bind(this, query); 390 391 this._ensureContentLoaded(); 392 }, 393 394 searchCanceled: function() 395 { 396 delete this._delayedFindSearchMatches; 397 if (!this.loaded) 398 return; 399 400 this._currentSearchResultIndex = -1; 401 this._searchResults = []; 402 this._textViewer.markAndRevealRange(null); 403 }, 404 405 jumpToFirstSearchResult: function() 406 { 407 this._jumpToSearchResult(0); 408 }, 409 410 jumpToLastSearchResult: function() 411 { 412 this._jumpToSearchResult(this._searchResults.length - 1); 413 }, 414 415 jumpToNextSearchResult: function() 416 { 417 this._jumpToSearchResult(this._currentSearchResultIndex + 1); 418 }, 419 420 jumpToPreviousSearchResult: function() 421 { 422 this._jumpToSearchResult(this._currentSearchResultIndex - 1); 423 }, 424 425 showingFirstSearchResult: function() 426 { 427 return this._searchResults.length && this._currentSearchResultIndex === 0; 428 }, 429 430 showingLastSearchResult: function() 431 { 432 return this._searchResults.length && this._currentSearchResultIndex === (this._searchResults.length - 1); 433 }, 434 435 _jumpToSearchResult: function(index) 436 { 437 if (!this.loaded || !this._searchResults.length) 438 return; 439 this._currentSearchResultIndex = (index + this._searchResults.length) % this._searchResults.length; 440 this._textViewer.markAndRevealRange(this._searchResults[this._currentSearchResultIndex]); 441 }, 442 443 _collectRegexMatches: function(regexObject, ranges) 444 { 445 for (var i = 0; i < this._textModel.linesCount; ++i) { 446 var line = this._textModel.line(i); 447 var offset = 0; 448 do { 449 var match = regexObject.exec(line); 450 if (match) { 451 ranges.push(new WebInspector.TextRange(i, offset + match.index, i, offset + match.index + match[0].length)); 452 offset += match.index + 1; 453 line = line.substring(match.index + 1); 454 } 455 } while (match) 456 } 457 return ranges; 458 }, 459 460 _incrementMessageRepeatCount: function(msg, repeatDelta) 461 { 462 if (!msg._resourceMessageLineElement) 463 return; 464 465 if (!msg._resourceMessageRepeatCountElement) { 466 var repeatedElement = document.createElement("span"); 467 msg._resourceMessageLineElement.appendChild(repeatedElement); 468 msg._resourceMessageRepeatCountElement = repeatedElement; 469 } 470 471 msg.repeatCount += repeatDelta; 472 msg._resourceMessageRepeatCountElement.textContent = WebInspector.UIString(" (repeated %d times)", msg.repeatCount); 473 }, 474 475 setExecutionLine: function(lineNumber, skipRevealLine) 476 { 477 this._executionLineNumber = lineNumber; 478 if (this.loaded) { 479 this._textViewer.addDecoration(lineNumber, "webkit-execution-line"); 480 if (!skipRevealLine) 481 this._textViewer.revealLine(lineNumber); 482 } 483 }, 484 485 clearExecutionLine: function() 486 { 487 if (this.loaded) 488 this._textViewer.removeDecoration(this._executionLineNumber, "webkit-execution-line"); 489 delete this._executionLineNumber; 490 }, 491 492 _updateDiffDecorations: function() 493 { 494 if (!this._diffLines) 495 return; 496 497 function addDecorations(textViewer, lines, className) 498 { 499 for (var i = 0; i < lines.length; ++i) 500 textViewer.addDecoration(lines[i], className); 501 } 502 addDecorations(this._textViewer, this._diffLines.added, "webkit-added-line"); 503 addDecorations(this._textViewer, this._diffLines.removed, "webkit-removed-line"); 504 addDecorations(this._textViewer, this._diffLines.changed, "webkit-changed-line"); 505 }, 506 507 _removeDiffDecorations: function() 508 { 509 function removeDecorations(textViewer, lines, className) 510 { 511 for (var i = 0; i < lines.length; ++i) 512 textViewer.removeDecoration(lines[i], className); 513 } 514 removeDecorations(this._textViewer, this._diffLines.added, "webkit-added-line"); 515 removeDecorations(this._textViewer, this._diffLines.removed, "webkit-removed-line"); 516 removeDecorations(this._textViewer, this._diffLines.changed, "webkit-changed-line"); 517 }, 518 519 _addExistingMessagesToSource: function() 520 { 521 var length = this._messages.length; 522 for (var i = 0; i < length; ++i) 523 this.addMessageToSource(this._messages[i].line - 1, this._messages[i]); 524 }, 525 526 addMessageToSource: function(lineNumber, msg) 527 { 528 if (lineNumber >= this._textModel.linesCount) 529 lineNumber = this._textModel.linesCount - 1; 530 if (lineNumber < 0) 531 lineNumber = 0; 532 533 var messageBubbleElement = this._messageBubbles[lineNumber]; 534 if (!messageBubbleElement || messageBubbleElement.nodeType !== Node.ELEMENT_NODE || !messageBubbleElement.hasStyleClass("webkit-html-message-bubble")) { 535 messageBubbleElement = document.createElement("div"); 536 messageBubbleElement.className = "webkit-html-message-bubble"; 537 this._messageBubbles[lineNumber] = messageBubbleElement; 538 this._textViewer.addDecoration(lineNumber, messageBubbleElement); 539 } 540 541 var rowMessages = this._rowMessages[lineNumber]; 542 if (!rowMessages) { 543 rowMessages = []; 544 this._rowMessages[lineNumber] = rowMessages; 545 } 546 547 for (var i = 0; i < rowMessages.length; ++i) { 548 if (rowMessages[i].isEqual(msg)) { 549 this._incrementMessageRepeatCount(rowMessages[i], msg.repeatDelta); 550 return; 551 } 552 } 553 554 rowMessages.push(msg); 555 556 var imageURL; 557 switch (msg.level) { 558 case WebInspector.ConsoleMessage.MessageLevel.Error: 559 messageBubbleElement.addStyleClass("webkit-html-error-message"); 560 imageURL = "Images/errorIcon.png"; 561 break; 562 case WebInspector.ConsoleMessage.MessageLevel.Warning: 563 messageBubbleElement.addStyleClass("webkit-html-warning-message"); 564 imageURL = "Images/warningIcon.png"; 565 break; 566 } 567 568 var messageLineElement = document.createElement("div"); 569 messageLineElement.className = "webkit-html-message-line"; 570 messageBubbleElement.appendChild(messageLineElement); 571 572 // Create the image element in the Inspector's document so we can use relative image URLs. 573 var image = document.createElement("img"); 574 image.src = imageURL; 575 image.className = "webkit-html-message-icon"; 576 messageLineElement.appendChild(image); 577 messageLineElement.appendChild(document.createTextNode(msg.message)); 578 579 msg._resourceMessageLineElement = messageLineElement; 580 }, 581 582 addBreakpoint: function(lineNumber, resolved, conditional, enabled) 583 { 584 this._breakpoints[lineNumber] = { 585 resolved: resolved, 586 conditional: conditional, 587 enabled: enabled 588 }; 589 this._textViewer.beginUpdates(); 590 this._textViewer.addDecoration(lineNumber, "webkit-breakpoint"); 591 if (!enabled) 592 this._textViewer.addDecoration(lineNumber, "webkit-breakpoint-disabled"); 593 if (conditional) 594 this._textViewer.addDecoration(lineNumber, "webkit-breakpoint-conditional"); 595 this._textViewer.endUpdates(); 596 }, 597 598 removeBreakpoint: function(lineNumber) 599 { 600 delete this._breakpoints[lineNumber]; 601 this._textViewer.beginUpdates(); 602 this._textViewer.removeDecoration(lineNumber, "webkit-breakpoint"); 603 this._textViewer.removeDecoration(lineNumber, "webkit-breakpoint-disabled"); 604 this._textViewer.removeDecoration(lineNumber, "webkit-breakpoint-conditional"); 605 this._textViewer.endUpdates(); 606 }, 607 608 _contextMenu: function(event) 609 { 610 var contextMenu = new WebInspector.ContextMenu(); 611 var target = event.target.enclosingNodeOrSelfWithClass("webkit-line-number"); 612 if (target) 613 this._populateLineGutterContextMenu(target.lineNumber, contextMenu); 614 else 615 this._populateTextAreaContextMenu(contextMenu); 616 contextMenu.show(event); 617 }, 618 619 _populateLineGutterContextMenu: function(lineNumber, contextMenu) 620 { 621 contextMenu.appendItem(WebInspector.UIString("Continue to Here"), this._delegate.continueToLine.bind(this._delegate, lineNumber)); 622 623 var breakpoint = this._delegate.findBreakpoint(lineNumber); 624 if (!breakpoint) { 625 // This row doesn't have a breakpoint: We want to show Add Breakpoint and Add and Edit Breakpoint. 626 contextMenu.appendItem(WebInspector.UIString("Add Breakpoint"), this._delegate.setBreakpoint.bind(this._delegate, lineNumber, "", true)); 627 628 function addConditionalBreakpoint() 629 { 630 this.addBreakpoint(lineNumber, true, true, true); 631 function didEditBreakpointCondition(committed, condition) 632 { 633 this.removeBreakpoint(lineNumber); 634 if (committed) 635 this._delegate.setBreakpoint(lineNumber, condition, true); 636 } 637 this._editBreakpointCondition(lineNumber, "", didEditBreakpointCondition.bind(this)); 638 } 639 contextMenu.appendItem(WebInspector.UIString("Add Conditional Breakpoint"), addConditionalBreakpoint.bind(this)); 640 } else { 641 // This row has a breakpoint, we want to show edit and remove breakpoint, and either disable or enable. 642 contextMenu.appendItem(WebInspector.UIString("Remove Breakpoint"), this._delegate.removeBreakpoint.bind(this._delegate, lineNumber)); 643 function editBreakpointCondition() 644 { 645 function didEditBreakpointCondition(committed, condition) 646 { 647 if (committed) 648 this._delegate.updateBreakpoint(lineNumber, condition, breakpoint.enabled); 649 } 650 this._editBreakpointCondition(lineNumber, breakpoint.condition, didEditBreakpointCondition.bind(this)); 651 } 652 contextMenu.appendItem(WebInspector.UIString("Edit Breakpoint"), editBreakpointCondition.bind(this)); 653 function setBreakpointEnabled(enabled) 654 { 655 this._delegate.updateBreakpoint(lineNumber, breakpoint.condition, enabled); 656 } 657 if (breakpoint.enabled) 658 contextMenu.appendItem(WebInspector.UIString("Disable Breakpoint"), setBreakpointEnabled.bind(this, false)); 659 else 660 contextMenu.appendItem(WebInspector.UIString("Enable Breakpoint"), setBreakpointEnabled.bind(this, true)); 661 } 662 }, 663 664 _populateTextAreaContextMenu: function(contextMenu) 665 { 666 contextMenu.appendCheckboxItem(WebInspector.UIString("De-obfuscate Source"), this._delegate.toggleFormatSourceFiles.bind(this._delegate), this._delegate.formatSourceFilesToggled()); 667 }, 668 669 _scroll: function(event) 670 { 671 this._hidePopup(); 672 }, 673 674 _mouseDown: function(event) 675 { 676 this._resetHoverTimer(); 677 this._hidePopup(); 678 if (event.button != 0 || event.altKey || event.ctrlKey || event.metaKey) 679 return; 680 var target = event.target.enclosingNodeOrSelfWithClass("webkit-line-number"); 681 if (!target) 682 return; 683 var lineNumber = target.lineNumber; 684 685 var breakpoint = this._delegate.findBreakpoint(lineNumber); 686 if (breakpoint) { 687 if (event.shiftKey) 688 this._delegate.updateBreakpoint(lineNumber, breakpoint.condition, !breakpoint.enabled); 689 else 690 this._delegate.removeBreakpoint(lineNumber); 691 } else 692 this._delegate.setBreakpoint(lineNumber, "", true); 693 event.preventDefault(); 694 }, 695 696 _mouseMove: function(event) 697 { 698 // Pretend that nothing has happened. 699 if (this._hoverElement === event.target || event.target.hasStyleClass("source-frame-eval-expression")) 700 return; 701 702 this._resetHoverTimer(); 703 // User has 500ms to reach the popup. 704 if (this._popup) { 705 var self = this; 706 function doHide() 707 { 708 self._hidePopup(); 709 delete self._hidePopupTimer; 710 } 711 if (!("_hidePopupTimer" in this)) 712 this._hidePopupTimer = setTimeout(doHide, 500); 713 } 714 715 this._hoverElement = event.target; 716 717 // Now that cleanup routines are set up above, leave this in case we are not on a break. 718 if (!this._delegate.debuggerPaused()) 719 return; 720 721 // We are interested in identifiers and "this" keyword. 722 if (this._hoverElement.hasStyleClass("webkit-javascript-keyword")) { 723 if (this._hoverElement.textContent !== "this") 724 return; 725 } else if (!this._hoverElement.hasStyleClass("webkit-javascript-ident")) 726 return; 727 728 const toolTipDelay = this._popup ? 600 : 1000; 729 this._hoverTimer = setTimeout(this._mouseHover.bind(this, this._hoverElement), toolTipDelay); 730 }, 731 732 _resetHoverTimer: function() 733 { 734 if (this._hoverTimer) { 735 clearTimeout(this._hoverTimer); 736 delete this._hoverTimer; 737 } 738 }, 739 740 _hidePopup: function() 741 { 742 if (!this._popup) 743 return; 744 745 // Replace higlight element with its contents inplace. 746 var parentElement = this._popup.highlightElement.parentElement; 747 var child = this._popup.highlightElement.firstChild; 748 while (child) { 749 var nextSibling = child.nextSibling; 750 parentElement.insertBefore(child, this._popup.highlightElement); 751 child = nextSibling; 752 } 753 parentElement.removeChild(this._popup.highlightElement); 754 755 this._popup.hide(); 756 delete this._popup; 757 this._delegate.releaseEvaluationResult(); 758 }, 759 760 _mouseHover: function(element) 761 { 762 delete this._hoverTimer; 763 764 var lineRow = element.enclosingNodeOrSelfWithClass("webkit-line-content"); 765 if (!lineRow) 766 return; 767 768 // Collect tokens belonging to evaluated exression. 769 var tokens = [ element ]; 770 var token = element.previousSibling; 771 while (token && (token.className === "webkit-javascript-ident" || token.className === "webkit-javascript-keyword" || token.textContent.trim() === ".")) { 772 tokens.push(token); 773 token = token.previousSibling; 774 } 775 tokens.reverse(); 776 777 // Wrap them with highlight element. 778 var parentElement = element.parentElement; 779 var nextElement = element.nextSibling; 780 var container = document.createElement("span"); 781 for (var i = 0; i < tokens.length; ++i) 782 container.appendChild(tokens[i]); 783 parentElement.insertBefore(container, nextElement); 784 this._showPopup(container); 785 }, 786 787 _showPopup: function(element) 788 { 789 if (!this._delegate.debuggerPaused()) 790 return; 791 792 function killHidePopupTimer() 793 { 794 if (this._hidePopupTimer) { 795 clearTimeout(this._hidePopupTimer); 796 delete this._hidePopupTimer; 797 798 // We know that we reached the popup, but we might have moved over other elements. 799 // Discard pending command. 800 this._resetHoverTimer(); 801 } 802 } 803 804 function showObjectPopup(result) 805 { 806 if (result.isError() || !this._delegate.debuggerPaused()) 807 return; 808 809 var popupContentElement = null; 810 if (result.type !== "object" && result.type !== "node" && result.type !== "array") { 811 popupContentElement = document.createElement("span"); 812 popupContentElement.className = "monospace console-formatted-" + result.type; 813 popupContentElement.style.whiteSpace = "pre"; 814 popupContentElement.textContent = result.description; 815 if (result.type === "string") 816 popupContentElement.textContent = "\"" + popupContentElement.textContent + "\""; 817 this._popup = new WebInspector.Popover(popupContentElement); 818 this._popup.show(element); 819 } else { 820 var popupContentElement = document.createElement("div"); 821 822 var titleElement = document.createElement("div"); 823 titleElement.className = "source-frame-popover-title monospace"; 824 titleElement.textContent = result.description; 825 popupContentElement.appendChild(titleElement); 826 827 var section = new WebInspector.ObjectPropertiesSection(result, "", null, false); 828 section.expanded = true; 829 section.element.addStyleClass("source-frame-popover-tree"); 830 section.headerElement.addStyleClass("hidden"); 831 popupContentElement.appendChild(section.element); 832 833 this._popup = new WebInspector.Popover(popupContentElement); 834 const popupWidth = 300; 835 const popupHeight = 250; 836 this._popup.show(element, popupWidth, popupHeight); 837 } 838 this._popup.highlightElement = element; 839 this._popup.highlightElement.addStyleClass("source-frame-eval-expression"); 840 popupContentElement.addEventListener("mousemove", killHidePopupTimer.bind(this), true); 841 } 842 843 this._delegate.evaluateInSelectedCallFrame(element.textContent, showObjectPopup.bind(this)); 844 }, 845 846 _editBreakpointCondition: function(lineNumber, condition, callback) 847 { 848 this._conditionElement = this._createConditionElement(lineNumber); 849 this._textViewer.addDecoration(lineNumber, this._conditionElement); 850 851 function finishEditing(committed, element, newText) 852 { 853 this._textViewer.removeDecoration(lineNumber, this._conditionElement); 854 delete this._conditionEditorElement; 855 delete this._conditionElement; 856 callback(committed, newText); 857 } 858 859 WebInspector.startEditing(this._conditionEditorElement, { 860 context: null, 861 commitHandler: finishEditing.bind(this, true), 862 cancelHandler: finishEditing.bind(this, false) 863 }); 864 this._conditionEditorElement.value = condition; 865 this._conditionEditorElement.select(); 866 }, 867 868 _createConditionElement: function(lineNumber) 869 { 870 var conditionElement = document.createElement("div"); 871 conditionElement.className = "source-frame-breakpoint-condition"; 872 873 var labelElement = document.createElement("label"); 874 labelElement.className = "source-frame-breakpoint-message"; 875 labelElement.htmlFor = "source-frame-breakpoint-condition"; 876 labelElement.appendChild(document.createTextNode(WebInspector.UIString("The breakpoint on line %d will stop only if this expression is true:", lineNumber))); 877 conditionElement.appendChild(labelElement); 878 879 var editorElement = document.createElement("input"); 880 editorElement.id = "source-frame-breakpoint-condition"; 881 editorElement.className = "monospace"; 882 editorElement.type = "text"; 883 conditionElement.appendChild(editorElement); 884 this._conditionEditorElement = editorElement; 885 886 return conditionElement; 887 }, 888 889 resize: function() 890 { 891 this._textViewer.resize(); 892 }, 893 894 commitEditing: function(callback) 895 { 896 if (!this._viewerState) { 897 // No editing was actually done. 898 this._delegate.setScriptSourceIsBeingEdited(false); 899 callback(); 900 return; 901 } 902 903 function didEditContent(error) 904 { 905 if (error) { 906 if (error.data && error.data[0]) { 907 WebInspector.log(error.data[0], WebInspector.ConsoleMessage.MessageLevel.Error); 908 WebInspector.showConsole(); 909 } 910 callback(error); 911 return; 912 } 913 914 var newBreakpoints = {}; 915 for (var lineNumber in this._breakpoints) { 916 newBreakpoints[lineNumber] = this._breakpoints[lineNumber]; 917 this.removeBreakpoint(Number(lineNumber)); 918 } 919 920 for (var lineNumber in this._viewerState.breakpoints) 921 this._delegate.removeBreakpoint(Number(lineNumber)); 922 923 for (var lineNumber in newBreakpoints) { 924 var breakpoint = newBreakpoints[lineNumber]; 925 this._delegate.setBreakpoint(Number(lineNumber), breakpoint.condition, breakpoint.enabled); 926 } 927 928 delete this._viewerState; 929 this._delegate.setScriptSourceIsBeingEdited(false); 930 931 callback(); 932 } 933 this.editContent(this._textModel.text, didEditContent.bind(this)); 934 }, 935 936 editContent: function(newContent, callback) 937 { 938 this._delegate.editScriptSource(newContent, callback); 939 }, 940 941 cancelEditing: function() 942 { 943 this._restoreViewerState(); 944 this._delegate.setScriptSourceIsBeingEdited(false); 945 } 946 } 947 948 WebInspector.SourceFrame.prototype.__proto__ = WebInspector.TextViewerDelegate.prototype; 949 950 951 WebInspector.SourceFrameDelegate = function() 952 { 953 } 954 955 WebInspector.SourceFrameDelegate.prototype = { 956 requestContent: function(callback) 957 { 958 // Should be implemented by subclasses. 959 }, 960 961 debuggingSupported: function() 962 { 963 return false; 964 }, 965 966 setBreakpoint: function(lineNumber, condition, enabled) 967 { 968 // Should be implemented by subclasses. 969 }, 970 971 removeBreakpoint: function(lineNumber) 972 { 973 // Should be implemented by subclasses. 974 }, 975 976 updateBreakpoint: function(lineNumber, condition, enabled) 977 { 978 // Should be implemented by subclasses. 979 }, 980 981 findBreakpoint: function(lineNumber) 982 { 983 // Should be implemented by subclasses. 984 }, 985 986 continueToLine: function(lineNumber) 987 { 988 // Should be implemented by subclasses. 989 }, 990 991 canEditScriptSource: function() 992 { 993 return false; 994 }, 995 996 editScriptSource: function(text, callback) 997 { 998 // Should be implemented by subclasses. 999 }, 1000 1001 setScriptSourceIsBeingEdited: function(inEditMode) 1002 { 1003 // Should be implemented by subclasses. 1004 }, 1005 1006 debuggerPaused: function() 1007 { 1008 // Should be implemented by subclasses. 1009 }, 1010 1011 evaluateInSelectedCallFrame: function(string) 1012 { 1013 // Should be implemented by subclasses. 1014 }, 1015 1016 releaseEvaluationResult: function() 1017 { 1018 // Should be implemented by subclasses. 1019 }, 1020 1021 toggleFormatSourceFiles: function() 1022 { 1023 // Should be implemented by subclasses. 1024 }, 1025 1026 formatSourceFilesToggled: function() 1027 { 1028 // Should be implemented by subclasses. 1029 } 1030 } 1031