| /* |
| * Copyright (C) 2009 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| WebInspector.SourceFrame = function(delegate, url) |
| { |
| WebInspector.View.call(this); |
| this.element.addStyleClass("script-view"); |
| |
| this._delegate = delegate; |
| this._url = url; |
| |
| this._textModel = new WebInspector.TextEditorModel(); |
| this._textModel.replaceTabsWithSpaces = true; |
| |
| var textViewerDelegate = new WebInspector.TextViewerDelegateForSourceFrame(this); |
| this._textViewer = new WebInspector.TextViewer(this._textModel, WebInspector.platform, this._url, textViewerDelegate); |
| this.element.appendChild(this._textViewer.element); |
| |
| this._currentSearchResultIndex = -1; |
| this._searchResults = []; |
| |
| this._messages = []; |
| this._rowMessages = {}; |
| this._messageBubbles = {}; |
| |
| this._breakpoints = {}; |
| } |
| |
| WebInspector.SourceFrame.Events = { |
| Loaded: "loaded" |
| } |
| |
| WebInspector.SourceFrame.createSearchRegex = function(query) |
| { |
| var regex; |
| |
| // First try creating regex if user knows the / / hint. |
| try { |
| if (/^\/.*\/$/.test(query)) |
| regex = new RegExp(query.substring(1, query.length - 1)); |
| } catch (e) { |
| // Silent catch. |
| } |
| |
| // Otherwise just do case-insensitive search. |
| if (!regex) |
| regex = createSearchRegex(query); |
| |
| return regex; |
| } |
| |
| |
| WebInspector.SourceFrame.prototype = { |
| show: function(parentElement) |
| { |
| WebInspector.View.prototype.show.call(this, parentElement); |
| |
| this._ensureContentLoaded(); |
| |
| if (this.loaded) { |
| if (this._scrollTop) |
| this._textViewer.scrollTop = this._scrollTop; |
| if (this._scrollLeft) |
| this._textViewer.scrollLeft = this._scrollLeft; |
| } |
| // Resize after setting the initial scroll positions to avoid unnecessary rendering work. |
| this._textViewer.resize(); |
| }, |
| |
| hide: function() |
| { |
| WebInspector.View.prototype.hide.call(this); |
| |
| if (this.loaded) { |
| this._scrollTop = this._textViewer.scrollTop; |
| this._scrollLeft = this._textViewer.scrollLeft; |
| this._textViewer.freeCachedElements(); |
| } |
| |
| this._textViewer.hide(); |
| this._hidePopup(); |
| this._clearLineHighlight(); |
| }, |
| |
| get loaded() |
| { |
| return !!this._content; |
| }, |
| |
| hasContent: function() |
| { |
| return true; |
| }, |
| |
| _ensureContentLoaded: function() |
| { |
| if (!this._contentRequested) { |
| this._contentRequested = true; |
| this.requestContent(this._initializeTextViewer.bind(this)); |
| } |
| }, |
| |
| requestContent: function(callback) |
| { |
| this._delegate.requestContent(callback); |
| }, |
| |
| markDiff: function(diffData) |
| { |
| if (this._diffLines && this.loaded) |
| this._removeDiffDecorations(); |
| |
| this._diffLines = diffData; |
| if (this.loaded) |
| this._updateDiffDecorations(); |
| }, |
| |
| addMessage: function(msg) |
| { |
| this._messages.push(msg); |
| if (this.loaded) |
| this.addMessageToSource(msg.line - 1, msg); |
| }, |
| |
| clearMessages: function() |
| { |
| for (var line in this._messageBubbles) { |
| var bubble = this._messageBubbles[line]; |
| bubble.parentNode.removeChild(bubble); |
| } |
| |
| this._messages = []; |
| this._rowMessages = {}; |
| this._messageBubbles = {}; |
| |
| this._textViewer.resize(); |
| }, |
| |
| get textModel() |
| { |
| return this._textModel; |
| }, |
| |
| get scrollTop() |
| { |
| return this.loaded ? this._textViewer.scrollTop : this._scrollTop; |
| }, |
| |
| set scrollTop(scrollTop) |
| { |
| this._scrollTop = scrollTop; |
| if (this.loaded) |
| this._textViewer.scrollTop = scrollTop; |
| }, |
| |
| highlightLine: function(line) |
| { |
| if (this.loaded) |
| this._textViewer.highlightLine(line); |
| else |
| this._lineToHighlight = line; |
| }, |
| |
| _clearLineHighlight: function() |
| { |
| if (this.loaded) |
| this._textViewer.clearLineHighlight(); |
| else |
| delete this._lineToHighlight; |
| }, |
| |
| _saveViewerState: function() |
| { |
| this._viewerState = { |
| textModelContent: this._textModel.text, |
| executionLineNumber: this._executionLineNumber, |
| messages: this._messages, |
| diffLines: this._diffLines, |
| breakpoints: this._breakpoints |
| }; |
| }, |
| |
| _restoreViewerState: function() |
| { |
| if (!this._viewerState) |
| return; |
| this._textModel.setText(null, this._viewerState.textModelContent); |
| |
| this._messages = this._viewerState.messages; |
| this._diffLines = this._viewerState.diffLines; |
| this._setTextViewerDecorations(); |
| |
| if (typeof this._viewerState.executionLineNumber === "number") { |
| this.clearExecutionLine(); |
| this.setExecutionLine(this._viewerState.executionLineNumber); |
| } |
| |
| var oldBreakpoints = this._breakpoints; |
| this._breakpoints = {}; |
| for (var lineNumber in oldBreakpoints) |
| this.removeBreakpoint(Number(lineNumber)); |
| |
| var newBreakpoints = this._viewerState.breakpoints; |
| for (var lineNumber in newBreakpoints) { |
| lineNumber = Number(lineNumber); |
| var breakpoint = newBreakpoints[lineNumber]; |
| this.addBreakpoint(lineNumber, breakpoint.resolved, breakpoint.conditional, breakpoint.enabled); |
| } |
| |
| delete this._viewerState; |
| }, |
| |
| beforeTextChanged: function() |
| { |
| if (!this._viewerState) { |
| this._saveViewerState(); |
| this._delegate.setScriptSourceIsBeingEdited(true); |
| } |
| |
| WebInspector.searchController.cancelSearch(); |
| this.clearMessages(); |
| }, |
| |
| afterTextChanged: function(oldRange, newRange) |
| { |
| if (!oldRange || !newRange) |
| return; |
| |
| // Adjust execution line number. |
| if (typeof this._executionLineNumber === "number") { |
| var newExecutionLineNumber = this._lineNumberAfterEditing(this._executionLineNumber, oldRange, newRange); |
| this.clearExecutionLine(); |
| this.setExecutionLine(newExecutionLineNumber, true); |
| } |
| |
| // Adjust breakpoints. |
| var oldBreakpoints = this._breakpoints; |
| this._breakpoints = {}; |
| for (var lineNumber in oldBreakpoints) { |
| lineNumber = Number(lineNumber); |
| var breakpoint = oldBreakpoints[lineNumber]; |
| var newLineNumber = this._lineNumberAfterEditing(lineNumber, oldRange, newRange); |
| if (lineNumber === newLineNumber) |
| this._breakpoints[lineNumber] = breakpoint; |
| else { |
| this.removeBreakpoint(lineNumber); |
| this.addBreakpoint(newLineNumber, breakpoint.resolved, breakpoint.conditional, breakpoint.enabled); |
| } |
| } |
| }, |
| |
| _lineNumberAfterEditing: function(lineNumber, oldRange, newRange) |
| { |
| var shiftOffset = lineNumber <= oldRange.startLine ? 0 : newRange.linesCount - oldRange.linesCount; |
| |
| // Special case of editing the line itself. We should decide whether the line number should move below or not. |
| if (lineNumber === oldRange.startLine) { |
| var whiteSpacesRegex = /^[\s\xA0]*$/; |
| for (var i = 0; lineNumber + i <= newRange.endLine; ++i) { |
| if (!whiteSpacesRegex.test(this._textModel.line(lineNumber + i))) { |
| shiftOffset = i; |
| break; |
| } |
| } |
| } |
| |
| var newLineNumber = Math.max(0, lineNumber + shiftOffset); |
| if (oldRange.startLine < lineNumber && lineNumber < oldRange.endLine) |
| newLineNumber = oldRange.startLine; |
| return newLineNumber; |
| }, |
| |
| _initializeTextViewer: function(mimeType, content) |
| { |
| this._textViewer.mimeType = mimeType; |
| |
| this._content = content; |
| this._textModel.setText(null, content); |
| |
| var element = this._textViewer.element; |
| if (this._delegate.debuggingSupported()) { |
| element.addEventListener("mousedown", this._mouseDown.bind(this), true); |
| element.addEventListener("mousemove", this._mouseMove.bind(this), true); |
| element.addEventListener("scroll", this._scroll.bind(this), true); |
| } |
| |
| this._textViewer.beginUpdates(); |
| |
| this._setTextViewerDecorations(); |
| |
| if (typeof this._executionLineNumber === "number") |
| this.setExecutionLine(this._executionLineNumber); |
| |
| if (this._lineToHighlight) { |
| this.highlightLine(this._lineToHighlight); |
| delete this._lineToHighlight; |
| } |
| |
| if (this._delayedFindSearchMatches) { |
| this._delayedFindSearchMatches(); |
| delete this._delayedFindSearchMatches; |
| } |
| |
| this.dispatchEventToListeners(WebInspector.SourceFrame.Events.Loaded); |
| |
| this._textViewer.endUpdates(); |
| }, |
| |
| _setTextViewerDecorations: function() |
| { |
| this._rowMessages = {}; |
| this._messageBubbles = {}; |
| |
| this._textViewer.beginUpdates(); |
| |
| this._addExistingMessagesToSource(); |
| this._updateDiffDecorations(); |
| |
| this._textViewer.resize(); |
| |
| this._textViewer.endUpdates(); |
| }, |
| |
| performSearch: function(query, callback) |
| { |
| // Call searchCanceled since it will reset everything we need before doing a new search. |
| this.searchCanceled(); |
| |
| function doFindSearchMatches(query) |
| { |
| this._currentSearchResultIndex = -1; |
| this._searchResults = []; |
| |
| var regex = WebInspector.SourceFrame.createSearchRegex(query); |
| this._searchResults = this._collectRegexMatches(regex); |
| |
| callback(this, this._searchResults.length); |
| } |
| |
| if (this.loaded) |
| doFindSearchMatches.call(this, query); |
| else |
| this._delayedFindSearchMatches = doFindSearchMatches.bind(this, query); |
| |
| this._ensureContentLoaded(); |
| }, |
| |
| searchCanceled: function() |
| { |
| delete this._delayedFindSearchMatches; |
| if (!this.loaded) |
| return; |
| |
| this._currentSearchResultIndex = -1; |
| this._searchResults = []; |
| this._textViewer.markAndRevealRange(null); |
| }, |
| |
| hasSearchResults: function() |
| { |
| return this._searchResults.length > 0; |
| }, |
| |
| jumpToFirstSearchResult: function() |
| { |
| this.jumpToSearchResult(0); |
| }, |
| |
| jumpToLastSearchResult: function() |
| { |
| this.jumpToSearchResult(this._searchResults.length - 1); |
| }, |
| |
| jumpToNextSearchResult: function() |
| { |
| this.jumpToSearchResult(this._currentSearchResultIndex + 1); |
| }, |
| |
| jumpToPreviousSearchResult: function() |
| { |
| this.jumpToSearchResult(this._currentSearchResultIndex - 1); |
| }, |
| |
| showingFirstSearchResult: function() |
| { |
| return this._searchResults.length && this._currentSearchResultIndex === 0; |
| }, |
| |
| showingLastSearchResult: function() |
| { |
| return this._searchResults.length && this._currentSearchResultIndex === (this._searchResults.length - 1); |
| }, |
| |
| jumpToSearchResult: function(index) |
| { |
| if (!this.loaded || !this._searchResults.length) |
| return; |
| this._currentSearchResultIndex = (index + this._searchResults.length) % this._searchResults.length; |
| this._textViewer.markAndRevealRange(this._searchResults[this._currentSearchResultIndex]); |
| }, |
| |
| _collectRegexMatches: function(regexObject) |
| { |
| var ranges = []; |
| for (var i = 0; i < this._textModel.linesCount; ++i) { |
| var line = this._textModel.line(i); |
| var offset = 0; |
| do { |
| var match = regexObject.exec(line); |
| if (match) { |
| if (match[0].length) |
| ranges.push(new WebInspector.TextRange(i, offset + match.index, i, offset + match.index + match[0].length)); |
| offset += match.index + 1; |
| line = line.substring(match.index + 1); |
| } |
| } while (match && line); |
| } |
| return ranges; |
| }, |
| |
| _incrementMessageRepeatCount: function(msg, repeatDelta) |
| { |
| if (!msg._resourceMessageLineElement) |
| return; |
| |
| if (!msg._resourceMessageRepeatCountElement) { |
| var repeatedElement = document.createElement("span"); |
| msg._resourceMessageLineElement.appendChild(repeatedElement); |
| msg._resourceMessageRepeatCountElement = repeatedElement; |
| } |
| |
| msg.repeatCount += repeatDelta; |
| msg._resourceMessageRepeatCountElement.textContent = WebInspector.UIString(" (repeated %d times)", msg.repeatCount); |
| }, |
| |
| setExecutionLine: function(lineNumber, skipRevealLine) |
| { |
| this._executionLineNumber = lineNumber; |
| if (this.loaded) { |
| this._textViewer.addDecoration(lineNumber, "webkit-execution-line"); |
| if (!skipRevealLine) |
| this._textViewer.revealLine(lineNumber); |
| } |
| }, |
| |
| clearExecutionLine: function() |
| { |
| if (this.loaded) |
| this._textViewer.removeDecoration(this._executionLineNumber, "webkit-execution-line"); |
| delete this._executionLineNumber; |
| }, |
| |
| _updateDiffDecorations: function() |
| { |
| if (!this._diffLines) |
| return; |
| |
| function addDecorations(textViewer, lines, className) |
| { |
| for (var i = 0; i < lines.length; ++i) |
| textViewer.addDecoration(lines[i], className); |
| } |
| addDecorations(this._textViewer, this._diffLines.added, "webkit-added-line"); |
| addDecorations(this._textViewer, this._diffLines.removed, "webkit-removed-line"); |
| addDecorations(this._textViewer, this._diffLines.changed, "webkit-changed-line"); |
| }, |
| |
| _removeDiffDecorations: function() |
| { |
| function removeDecorations(textViewer, lines, className) |
| { |
| for (var i = 0; i < lines.length; ++i) |
| textViewer.removeDecoration(lines[i], className); |
| } |
| removeDecorations(this._textViewer, this._diffLines.added, "webkit-added-line"); |
| removeDecorations(this._textViewer, this._diffLines.removed, "webkit-removed-line"); |
| removeDecorations(this._textViewer, this._diffLines.changed, "webkit-changed-line"); |
| }, |
| |
| _addExistingMessagesToSource: function() |
| { |
| var length = this._messages.length; |
| for (var i = 0; i < length; ++i) |
| this.addMessageToSource(this._messages[i].line - 1, this._messages[i]); |
| }, |
| |
| addMessageToSource: function(lineNumber, msg) |
| { |
| if (lineNumber >= this._textModel.linesCount) |
| lineNumber = this._textModel.linesCount - 1; |
| if (lineNumber < 0) |
| lineNumber = 0; |
| |
| var messageBubbleElement = this._messageBubbles[lineNumber]; |
| if (!messageBubbleElement || messageBubbleElement.nodeType !== Node.ELEMENT_NODE || !messageBubbleElement.hasStyleClass("webkit-html-message-bubble")) { |
| messageBubbleElement = document.createElement("div"); |
| messageBubbleElement.className = "webkit-html-message-bubble"; |
| this._messageBubbles[lineNumber] = messageBubbleElement; |
| this._textViewer.addDecoration(lineNumber, messageBubbleElement); |
| } |
| |
| var rowMessages = this._rowMessages[lineNumber]; |
| if (!rowMessages) { |
| rowMessages = []; |
| this._rowMessages[lineNumber] = rowMessages; |
| } |
| |
| for (var i = 0; i < rowMessages.length; ++i) { |
| if (rowMessages[i].isEqual(msg)) { |
| this._incrementMessageRepeatCount(rowMessages[i], msg.repeatDelta); |
| return; |
| } |
| } |
| |
| rowMessages.push(msg); |
| |
| var imageURL; |
| switch (msg.level) { |
| case WebInspector.ConsoleMessage.MessageLevel.Error: |
| messageBubbleElement.addStyleClass("webkit-html-error-message"); |
| imageURL = "Images/errorIcon.png"; |
| break; |
| case WebInspector.ConsoleMessage.MessageLevel.Warning: |
| messageBubbleElement.addStyleClass("webkit-html-warning-message"); |
| imageURL = "Images/warningIcon.png"; |
| break; |
| } |
| |
| var messageLineElement = document.createElement("div"); |
| messageLineElement.className = "webkit-html-message-line"; |
| messageBubbleElement.appendChild(messageLineElement); |
| |
| // Create the image element in the Inspector's document so we can use relative image URLs. |
| var image = document.createElement("img"); |
| image.src = imageURL; |
| image.className = "webkit-html-message-icon"; |
| messageLineElement.appendChild(image); |
| messageLineElement.appendChild(document.createTextNode(msg.message)); |
| |
| msg._resourceMessageLineElement = messageLineElement; |
| }, |
| |
| addBreakpoint: function(lineNumber, resolved, conditional, enabled) |
| { |
| this._breakpoints[lineNumber] = { |
| resolved: resolved, |
| conditional: conditional, |
| enabled: enabled |
| }; |
| this._textViewer.beginUpdates(); |
| this._textViewer.addDecoration(lineNumber, "webkit-breakpoint"); |
| if (!enabled) |
| this._textViewer.addDecoration(lineNumber, "webkit-breakpoint-disabled"); |
| if (conditional) |
| this._textViewer.addDecoration(lineNumber, "webkit-breakpoint-conditional"); |
| this._textViewer.endUpdates(); |
| }, |
| |
| removeBreakpoint: function(lineNumber) |
| { |
| delete this._breakpoints[lineNumber]; |
| this._textViewer.beginUpdates(); |
| this._textViewer.removeDecoration(lineNumber, "webkit-breakpoint"); |
| this._textViewer.removeDecoration(lineNumber, "webkit-breakpoint-disabled"); |
| this._textViewer.removeDecoration(lineNumber, "webkit-breakpoint-conditional"); |
| this._textViewer.endUpdates(); |
| }, |
| |
| populateLineGutterContextMenu: function(lineNumber, contextMenu) |
| { |
| contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Continue to here" : "Continue to Here"), this._delegate.continueToLine.bind(this._delegate, lineNumber)); |
| |
| var breakpoint = this._delegate.findBreakpoint(lineNumber); |
| if (!breakpoint) { |
| // This row doesn't have a breakpoint: We want to show Add Breakpoint and Add and Edit Breakpoint. |
| contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add breakpoint" : "Add Breakpoint"), this._delegate.setBreakpoint.bind(this._delegate, lineNumber, "", true)); |
| |
| function addConditionalBreakpoint() |
| { |
| this.addBreakpoint(lineNumber, true, true, true); |
| function didEditBreakpointCondition(committed, condition) |
| { |
| this.removeBreakpoint(lineNumber); |
| if (committed) |
| this._delegate.setBreakpoint(lineNumber, condition, true); |
| } |
| this._editBreakpointCondition(lineNumber, "", didEditBreakpointCondition.bind(this)); |
| } |
| contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add conditional breakpoint…" : "Add Conditional Breakpoint…"), addConditionalBreakpoint.bind(this)); |
| } else { |
| // This row has a breakpoint, we want to show edit and remove breakpoint, and either disable or enable. |
| contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Remove breakpoint" : "Remove Breakpoint"), this._delegate.removeBreakpoint.bind(this._delegate, lineNumber)); |
| function editBreakpointCondition() |
| { |
| function didEditBreakpointCondition(committed, condition) |
| { |
| if (committed) |
| this._delegate.updateBreakpoint(lineNumber, condition, breakpoint.enabled); |
| } |
| this._editBreakpointCondition(lineNumber, breakpoint.condition, didEditBreakpointCondition.bind(this)); |
| } |
| contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Edit breakpoint…" : "Edit Breakpoint…"), editBreakpointCondition.bind(this)); |
| function setBreakpointEnabled(enabled) |
| { |
| this._delegate.updateBreakpoint(lineNumber, breakpoint.condition, enabled); |
| } |
| if (breakpoint.enabled) |
| contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Disable breakpoint" : "Disable Breakpoint"), setBreakpointEnabled.bind(this, false)); |
| else |
| contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Enable breakpoint" : "Enable Breakpoint"), setBreakpointEnabled.bind(this, true)); |
| } |
| }, |
| |
| populateTextAreaContextMenu: function(contextMenu) |
| { |
| }, |
| |
| suggestedFileName: function() |
| { |
| return this._delegate.suggestedFileName(); |
| }, |
| |
| _scroll: function(event) |
| { |
| this._hidePopup(); |
| }, |
| |
| _mouseDown: function(event) |
| { |
| this._resetHoverTimer(); |
| this._hidePopup(); |
| if (event.button != 0 || event.altKey || event.ctrlKey || event.metaKey) |
| return; |
| var target = event.target.enclosingNodeOrSelfWithClass("webkit-line-number"); |
| if (!target) |
| return; |
| var lineNumber = target.lineNumber; |
| |
| var breakpoint = this._delegate.findBreakpoint(lineNumber); |
| if (breakpoint) { |
| if (event.shiftKey) |
| this._delegate.updateBreakpoint(lineNumber, breakpoint.condition, !breakpoint.enabled); |
| else |
| this._delegate.removeBreakpoint(lineNumber); |
| } else |
| this._delegate.setBreakpoint(lineNumber, "", true); |
| event.preventDefault(); |
| }, |
| |
| _mouseMove: function(event) |
| { |
| // Pretend that nothing has happened. |
| if (this._hoverElement === event.target || event.target.hasStyleClass("source-frame-eval-expression")) |
| return; |
| |
| this._resetHoverTimer(); |
| // User has 500ms to reach the popup. |
| if (this._popup) { |
| var self = this; |
| function doHide() |
| { |
| self._hidePopup(); |
| delete self._hidePopupTimer; |
| } |
| if (!("_hidePopupTimer" in this)) |
| this._hidePopupTimer = setTimeout(doHide, 500); |
| } |
| |
| this._hoverElement = event.target; |
| |
| // Now that cleanup routines are set up above, leave this in case we are not on a break. |
| if (!this._delegate.debuggerPaused()) |
| return; |
| |
| // We are interested in identifiers and "this" keyword. |
| if (this._hoverElement.hasStyleClass("webkit-javascript-keyword")) { |
| if (this._hoverElement.textContent !== "this") |
| return; |
| } else if (!this._hoverElement.hasStyleClass("webkit-javascript-ident")) |
| return; |
| |
| const toolTipDelay = this._popup ? 600 : 1000; |
| this._hoverTimer = setTimeout(this._mouseHover.bind(this, this._hoverElement), toolTipDelay); |
| }, |
| |
| _resetHoverTimer: function() |
| { |
| if (this._hoverTimer) { |
| clearTimeout(this._hoverTimer); |
| delete this._hoverTimer; |
| } |
| }, |
| |
| _hidePopup: function() |
| { |
| if (!this._popup) |
| return; |
| |
| // Replace higlight element with its contents inplace. |
| var parentElement = this._popup.highlightElement.parentElement; |
| var child = this._popup.highlightElement.firstChild; |
| while (child) { |
| var nextSibling = child.nextSibling; |
| parentElement.insertBefore(child, this._popup.highlightElement); |
| child = nextSibling; |
| } |
| parentElement.removeChild(this._popup.highlightElement); |
| |
| this._popup.hide(); |
| delete this._popup; |
| this._delegate.releaseEvaluationResult(); |
| }, |
| |
| _mouseHover: function(element) |
| { |
| delete this._hoverTimer; |
| |
| var lineRow = element.enclosingNodeOrSelfWithClass("webkit-line-content"); |
| if (!lineRow) |
| return; |
| |
| // Collect tokens belonging to evaluated exression. |
| var tokens = [ element ]; |
| var token = element.previousSibling; |
| while (token && (token.className === "webkit-javascript-ident" || token.className === "webkit-javascript-keyword" || token.textContent.trim() === ".")) { |
| tokens.push(token); |
| token = token.previousSibling; |
| } |
| tokens.reverse(); |
| |
| // Wrap them with highlight element. |
| var parentElement = element.parentElement; |
| var nextElement = element.nextSibling; |
| var container = document.createElement("span"); |
| for (var i = 0; i < tokens.length; ++i) |
| container.appendChild(tokens[i]); |
| parentElement.insertBefore(container, nextElement); |
| this._showPopup(container); |
| }, |
| |
| _showPopup: function(element) |
| { |
| if (!this._delegate.debuggerPaused()) |
| return; |
| |
| function killHidePopupTimer() |
| { |
| if (this._hidePopupTimer) { |
| clearTimeout(this._hidePopupTimer); |
| delete this._hidePopupTimer; |
| |
| // We know that we reached the popup, but we might have moved over other elements. |
| // Discard pending command. |
| this._resetHoverTimer(); |
| } |
| } |
| |
| function showObjectPopup(result, wasThrown) |
| { |
| if (wasThrown || !this._delegate.debuggerPaused()) |
| return; |
| |
| var popupContentElement = null; |
| if (result.type !== "object") { |
| popupContentElement = document.createElement("span"); |
| popupContentElement.className = "monospace console-formatted-" + result.type; |
| popupContentElement.style.whiteSpace = "pre"; |
| popupContentElement.textContent = result.description; |
| if (result.type === "string") |
| popupContentElement.textContent = "\"" + popupContentElement.textContent + "\""; |
| this._popup = new WebInspector.Popover(popupContentElement); |
| this._popup.show(element); |
| } else { |
| var popupContentElement = document.createElement("div"); |
| |
| var titleElement = document.createElement("div"); |
| titleElement.className = "source-frame-popover-title monospace"; |
| titleElement.textContent = result.description; |
| popupContentElement.appendChild(titleElement); |
| |
| var section = new WebInspector.ObjectPropertiesSection(result); |
| section.expanded = true; |
| section.element.addStyleClass("source-frame-popover-tree"); |
| section.headerElement.addStyleClass("hidden"); |
| popupContentElement.appendChild(section.element); |
| |
| this._popup = new WebInspector.Popover(popupContentElement); |
| const popupWidth = 300; |
| const popupHeight = 250; |
| this._popup.show(element, popupWidth, popupHeight); |
| } |
| this._popup.highlightElement = element; |
| this._popup.highlightElement.addStyleClass("source-frame-eval-expression"); |
| popupContentElement.addEventListener("mousemove", killHidePopupTimer.bind(this), true); |
| } |
| |
| this._delegate.evaluateInSelectedCallFrame(element.textContent, showObjectPopup.bind(this)); |
| }, |
| |
| _editBreakpointCondition: function(lineNumber, condition, callback) |
| { |
| this._conditionElement = this._createConditionElement(lineNumber); |
| this._textViewer.addDecoration(lineNumber, this._conditionElement); |
| |
| function finishEditing(committed, element, newText) |
| { |
| this._textViewer.removeDecoration(lineNumber, this._conditionElement); |
| delete this._conditionEditorElement; |
| delete this._conditionElement; |
| callback(committed, newText); |
| } |
| |
| WebInspector.startEditing(this._conditionEditorElement, { |
| context: null, |
| commitHandler: finishEditing.bind(this, true), |
| cancelHandler: finishEditing.bind(this, false) |
| }); |
| this._conditionEditorElement.value = condition; |
| this._conditionEditorElement.select(); |
| }, |
| |
| _createConditionElement: function(lineNumber) |
| { |
| var conditionElement = document.createElement("div"); |
| conditionElement.className = "source-frame-breakpoint-condition"; |
| |
| var labelElement = document.createElement("label"); |
| labelElement.className = "source-frame-breakpoint-message"; |
| labelElement.htmlFor = "source-frame-breakpoint-condition"; |
| labelElement.appendChild(document.createTextNode(WebInspector.UIString("The breakpoint on line %d will stop only if this expression is true:", lineNumber))); |
| conditionElement.appendChild(labelElement); |
| |
| var editorElement = document.createElement("input"); |
| editorElement.id = "source-frame-breakpoint-condition"; |
| editorElement.className = "monospace"; |
| editorElement.type = "text"; |
| conditionElement.appendChild(editorElement); |
| this._conditionEditorElement = editorElement; |
| |
| return conditionElement; |
| }, |
| |
| resize: function() |
| { |
| this._textViewer.resize(); |
| }, |
| |
| doubleClick: function(lineNumber) |
| { |
| if (!this._delegate.canEditScriptSource()) |
| return; |
| |
| if (this._commitEditingInProgress) |
| return; |
| |
| this._setReadOnly(false); |
| }, |
| |
| commitEditing: function() |
| { |
| if (!this._viewerState) { |
| // No editing was actually done. |
| this._setReadOnly(true); |
| return; |
| } |
| |
| function didEditContent(error) |
| { |
| this._commitEditingInProgress = false; |
| this._textViewer.readOnly = false; |
| |
| if (error) { |
| if (error.message) { |
| WebInspector.log(error.message, WebInspector.ConsoleMessage.MessageLevel.Error); |
| WebInspector.showConsole(); |
| } |
| return; |
| } |
| |
| var newBreakpoints = {}; |
| for (var lineNumber in this._breakpoints) { |
| newBreakpoints[lineNumber] = this._breakpoints[lineNumber]; |
| this.removeBreakpoint(Number(lineNumber)); |
| } |
| |
| for (var lineNumber in this._viewerState.breakpoints) |
| this._delegate.removeBreakpoint(Number(lineNumber)); |
| |
| for (var lineNumber in newBreakpoints) { |
| var breakpoint = newBreakpoints[lineNumber]; |
| this._delegate.setBreakpoint(Number(lineNumber), breakpoint.condition, breakpoint.enabled); |
| } |
| |
| delete this._viewerState; |
| this._delegate.setScriptSourceIsBeingEdited(false); |
| } |
| this._commitEditingInProgress = true; |
| this._textViewer.readOnly = true; |
| this.editContent(this._textModel.text, didEditContent.bind(this)); |
| }, |
| |
| editContent: function(newContent, callback) |
| { |
| this._delegate.setScriptSource(newContent, callback); |
| }, |
| |
| cancelEditing: function() |
| { |
| this._restoreViewerState(); |
| this._setReadOnly(true); |
| }, |
| |
| _setReadOnly: function(readOnly) |
| { |
| this._textViewer.readOnly = readOnly; |
| WebInspector.markBeingEdited(this._textViewer.element, !readOnly); |
| if (readOnly) |
| this._delegate.setScriptSourceIsBeingEdited(false); |
| } |
| } |
| |
| WebInspector.SourceFrame.prototype.__proto__ = WebInspector.View.prototype; |
| |
| |
| WebInspector.TextViewerDelegateForSourceFrame = function(sourceFrame) |
| { |
| this._sourceFrame = sourceFrame; |
| } |
| |
| WebInspector.TextViewerDelegateForSourceFrame.prototype = { |
| doubleClick: function(lineNumber) |
| { |
| this._sourceFrame.doubleClick(lineNumber); |
| }, |
| |
| beforeTextChanged: function() |
| { |
| this._sourceFrame.beforeTextChanged(); |
| }, |
| |
| afterTextChanged: function(oldRange, newRange) |
| { |
| this._sourceFrame.afterTextChanged(oldRange, newRange); |
| }, |
| |
| commitEditing: function() |
| { |
| this._sourceFrame.commitEditing(); |
| }, |
| |
| cancelEditing: function() |
| { |
| this._sourceFrame.cancelEditing(); |
| }, |
| |
| populateLineGutterContextMenu: function(lineNumber, contextMenu) |
| { |
| this._sourceFrame.populateLineGutterContextMenu(lineNumber, contextMenu); |
| }, |
| |
| populateTextAreaContextMenu: function(contextMenu) |
| { |
| this._sourceFrame.populateTextAreaContextMenu(contextMenu); |
| }, |
| |
| suggestedFileName: function() |
| { |
| return this._sourceFrame.suggestedFileName(); |
| } |
| }; |
| |
| WebInspector.TextViewerDelegateForSourceFrame.prototype.__proto__ = WebInspector.TextViewerDelegate.prototype; |
| |
| |
| WebInspector.SourceFrameDelegate = function() |
| { |
| } |
| |
| WebInspector.SourceFrameDelegate.prototype = { |
| requestContent: function(callback) |
| { |
| // Should be implemented by subclasses. |
| }, |
| |
| debuggingSupported: function() |
| { |
| return false; |
| }, |
| |
| setBreakpoint: function(lineNumber, condition, enabled) |
| { |
| // Should be implemented by subclasses. |
| }, |
| |
| removeBreakpoint: function(lineNumber) |
| { |
| // Should be implemented by subclasses. |
| }, |
| |
| updateBreakpoint: function(lineNumber, condition, enabled) |
| { |
| // Should be implemented by subclasses. |
| }, |
| |
| findBreakpoint: function(lineNumber) |
| { |
| // Should be implemented by subclasses. |
| }, |
| |
| continueToLine: function(lineNumber) |
| { |
| // Should be implemented by subclasses. |
| }, |
| |
| canEditScriptSource: function() |
| { |
| return false; |
| }, |
| |
| setScriptSource: function(text, callback) |
| { |
| // Should be implemented by subclasses. |
| }, |
| |
| setScriptSourceIsBeingEdited: function(inEditMode) |
| { |
| // Should be implemented by subclasses. |
| }, |
| |
| debuggerPaused: function() |
| { |
| // Should be implemented by subclasses. |
| }, |
| |
| evaluateInSelectedCallFrame: function(string) |
| { |
| // Should be implemented by subclasses. |
| }, |
| |
| releaseEvaluationResult: function() |
| { |
| // Should be implemented by subclasses. |
| }, |
| |
| suggestedFileName: function() |
| { |
| // Should be implemented by subclasses. |
| } |
| } |