936 lines
34 KiB
JavaScript
936 lines
34 KiB
JavaScript
/*
|
|
* 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(contentProvider, url, isScript)
|
|
{
|
|
WebInspector.View.call(this);
|
|
|
|
this.element.addStyleClass("script-view");
|
|
|
|
this._contentProvider = contentProvider;
|
|
this._url = url;
|
|
this._isScript = isScript;
|
|
|
|
this._textModel = new WebInspector.TextEditorModel();
|
|
this._textModel.replaceTabsWithSpaces = true;
|
|
|
|
this._currentSearchResultIndex = -1;
|
|
this._searchResults = [];
|
|
|
|
this._messages = [];
|
|
this._rowMessages = {};
|
|
this._messageBubbles = {};
|
|
|
|
this._popoverObjectGroup = "popover";
|
|
}
|
|
|
|
WebInspector.SourceFrame.prototype = {
|
|
|
|
show: function(parentElement)
|
|
{
|
|
WebInspector.View.prototype.show.call(this, parentElement);
|
|
|
|
if (!this._contentRequested) {
|
|
this._contentRequested = true;
|
|
this._contentProvider.requestContent(this._createTextViewer.bind(this));
|
|
}
|
|
|
|
if (this._textViewer) {
|
|
if (this._scrollTop)
|
|
this._textViewer.scrollTop = this._scrollTop;
|
|
if (this._scrollLeft)
|
|
this._textViewer.scrollLeft = this._scrollLeft;
|
|
this._textViewer.resize();
|
|
}
|
|
},
|
|
|
|
hide: function()
|
|
{
|
|
if (this._textViewer) {
|
|
this._scrollTop = this._textViewer.scrollTop;
|
|
this._scrollLeft = this._textViewer.scrollLeft;
|
|
this._textViewer.freeCachedElements();
|
|
}
|
|
|
|
WebInspector.View.prototype.hide.call(this);
|
|
|
|
this._hidePopup();
|
|
this._clearLineHighlight();
|
|
},
|
|
|
|
hasContent: function()
|
|
{
|
|
return true;
|
|
},
|
|
|
|
markDiff: function(diffData)
|
|
{
|
|
if (this._diffLines && this._textViewer)
|
|
this._removeDiffDecorations();
|
|
|
|
this._diffLines = diffData;
|
|
if (this._textViewer)
|
|
this._updateDiffDecorations();
|
|
},
|
|
|
|
revealLine: function(lineNumber)
|
|
{
|
|
if (this._textViewer)
|
|
this._textViewer.revealLine(lineNumber - 1, 0);
|
|
else
|
|
this._lineNumberToReveal = lineNumber;
|
|
},
|
|
|
|
addMessage: function(msg)
|
|
{
|
|
// Don't add the message if there is no message or valid line or if the msg isn't an error or warning.
|
|
if (!msg.message || msg.line <= 0 || !msg.isErrorOrWarning())
|
|
return;
|
|
this._messages.push(msg);
|
|
if (this._textViewer)
|
|
this._addMessageToSource(msg);
|
|
},
|
|
|
|
clearMessages: function()
|
|
{
|
|
for (var line in this._messageBubbles) {
|
|
var bubble = this._messageBubbles[line];
|
|
bubble.parentNode.removeChild(bubble);
|
|
}
|
|
|
|
this._messages = [];
|
|
this._rowMessages = {};
|
|
this._messageBubbles = {};
|
|
if (this._textViewer)
|
|
this._textViewer.resize();
|
|
},
|
|
|
|
sizeToFitContentHeight: function()
|
|
{
|
|
if (this._textViewer)
|
|
this._textViewer.revalidateDecorationsAndPaint();
|
|
},
|
|
|
|
get textModel()
|
|
{
|
|
return this._textModel;
|
|
},
|
|
|
|
get scrollTop()
|
|
{
|
|
return this._textViewer ? this._textViewer.scrollTop : this._scrollTop;
|
|
},
|
|
|
|
set scrollTop(scrollTop)
|
|
{
|
|
this._scrollTop = scrollTop;
|
|
if (this._textViewer)
|
|
this._textViewer.scrollTop = scrollTop;
|
|
},
|
|
|
|
highlightLine: function(line)
|
|
{
|
|
if (this._textViewer)
|
|
this._textViewer.highlightLine(line - 1);
|
|
else
|
|
this._lineToHighlight = line;
|
|
},
|
|
|
|
_clearLineHighlight: function()
|
|
{
|
|
if (this._textViewer)
|
|
this._textViewer.clearLineHighlight();
|
|
else
|
|
delete this._lineToHighlight;
|
|
},
|
|
|
|
_createTextViewer: function(mimeType, content)
|
|
{
|
|
this._content = content;
|
|
this._textModel.setText(null, content.text);
|
|
|
|
this._textViewer = new WebInspector.TextViewer(this._textModel, WebInspector.platform, this._url);
|
|
var element = this._textViewer.element;
|
|
element.addEventListener("contextmenu", this._contextMenu.bind(this), true);
|
|
element.addEventListener("mousedown", this._mouseDown.bind(this), true);
|
|
element.addEventListener("mousemove", this._mouseMove.bind(this), true);
|
|
element.addEventListener("scroll", this._scroll.bind(this), true);
|
|
element.addEventListener("dblclick", this._doubleClick.bind(this), true);
|
|
this.element.appendChild(element);
|
|
|
|
this._textViewer.beginUpdates();
|
|
|
|
this._textViewer.mimeType = mimeType;
|
|
this._setTextViewerDecorations();
|
|
|
|
if (this._lineNumberToReveal) {
|
|
this.revealLine(this._lineNumberToReveal);
|
|
delete this._lineNumberToReveal;
|
|
}
|
|
|
|
if (this._lineToHighlight) {
|
|
this.highlightLine(this._lineToHighlight);
|
|
delete this._lineToHighlight;
|
|
}
|
|
|
|
if (this._delayedFindSearchMatches) {
|
|
this._delayedFindSearchMatches();
|
|
delete this._delayedFindSearchMatches;
|
|
}
|
|
|
|
this._textViewer.endUpdates();
|
|
|
|
WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.BreakpointAdded, this._breakpointAdded, this);
|
|
WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.BreakpointRemoved, this._breakpointRemoved, this);
|
|
WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.BreakpointResolved, this._breakpointResolved, this);
|
|
},
|
|
|
|
_setTextViewerDecorations: function()
|
|
{
|
|
this._rowMessages = {};
|
|
this._messageBubbles = {};
|
|
|
|
this._textViewer.beginUpdates();
|
|
|
|
this._addExistingMessagesToSource();
|
|
this._updateDiffDecorations();
|
|
|
|
if (this._executionLocation)
|
|
this._setExecutionLocation();
|
|
|
|
this._breakpointIdToTextViewerLineNumber = {};
|
|
this._textViewerLineNumberToBreakpointId = {};
|
|
var breakpoints = WebInspector.debuggerModel.breakpoints;
|
|
for (var id in breakpoints)
|
|
this._breakpointAdded({ data: breakpoints[id] });
|
|
|
|
this._textViewer.resize();
|
|
|
|
this._textViewer.endUpdates();
|
|
},
|
|
|
|
_shouldDisplayBreakpoint: function(breakpoint)
|
|
{
|
|
if (this._url)
|
|
return this._url === breakpoint.url;
|
|
var scripts = this._content.scriptRanges;
|
|
for (var i = 0; i < scripts.length; ++i) {
|
|
if (breakpoint.sourceID === scripts[i].sourceID)
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
|
|
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 = [];
|
|
|
|
// First do case-insensitive search.
|
|
var regexObject = createSearchRegex(query);
|
|
this._collectRegexMatches(regexObject, this._searchResults);
|
|
|
|
// Then try regex search if user knows the / / hint.
|
|
try {
|
|
if (/^\/.*\/$/.test(query))
|
|
this._collectRegexMatches(new RegExp(query.substring(1, query.length - 1)), this._searchResults);
|
|
} catch (e) {
|
|
// Silent catch.
|
|
}
|
|
|
|
callback(this, this._searchResults.length);
|
|
}
|
|
|
|
if (this._textViewer)
|
|
doFindSearchMatches.call(this, query);
|
|
else
|
|
this._delayedFindSearchMatches = doFindSearchMatches.bind(this, query);
|
|
|
|
},
|
|
|
|
searchCanceled: function()
|
|
{
|
|
delete this._delayedFindSearchMatches;
|
|
if (!this._textViewer)
|
|
return;
|
|
|
|
this._currentSearchResultIndex = -1;
|
|
this._searchResults = [];
|
|
this._textViewer.markAndRevealRange(null);
|
|
},
|
|
|
|
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._textViewer || !this._searchResults.length)
|
|
return;
|
|
this._currentSearchResultIndex = (index + this._searchResults.length) % this._searchResults.length;
|
|
this._textViewer.markAndRevealRange(this._searchResults[this._currentSearchResultIndex]);
|
|
},
|
|
|
|
_collectRegexMatches: function(regexObject, 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) {
|
|
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)
|
|
}
|
|
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);
|
|
},
|
|
|
|
setExecutionLocation: function(lineNumber, columnNumber)
|
|
{
|
|
this._executionLocation = { lineNumber: lineNumber, columnNumber: columnNumber };
|
|
if (this._textViewer)
|
|
this._setExecutionLocation();
|
|
},
|
|
|
|
clearExecutionLocation: function()
|
|
{
|
|
if (this._textViewer) {
|
|
var textViewerLineNumber = this._content.actualLocationToSourceFrameLineNumber(this._executionLocation.lineNumber, this._executionLocation.columnNumber);
|
|
this._textViewer.removeDecoration(textViewerLineNumber, "webkit-execution-line");
|
|
}
|
|
delete this._executionLocation;
|
|
},
|
|
|
|
_setExecutionLocation: function()
|
|
{
|
|
var textViewerLineNumber = this._content.actualLocationToSourceFrameLineNumber(this._executionLocation.lineNumber, this._executionLocation.columnNumber);
|
|
this._textViewer.addDecoration(textViewerLineNumber, "webkit-execution-line");
|
|
},
|
|
|
|
_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]);
|
|
},
|
|
|
|
_addMessageToSource: function(msg)
|
|
{
|
|
if (msg.line > this._textModel.linesCount)
|
|
return;
|
|
|
|
var messageBubbleElement = this._messageBubbles[msg.line];
|
|
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[msg.line] = messageBubbleElement;
|
|
this._textViewer.addDecoration(msg.line - 1, messageBubbleElement);
|
|
}
|
|
|
|
var rowMessages = this._rowMessages[msg.line];
|
|
if (!rowMessages) {
|
|
rowMessages = [];
|
|
this._rowMessages[msg.line] = 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;
|
|
},
|
|
|
|
_breakpointAdded: function(event)
|
|
{
|
|
var breakpoint = event.data;
|
|
|
|
if (!this._shouldDisplayBreakpoint(breakpoint))
|
|
return;
|
|
|
|
var resolved = breakpoint.locations.length;
|
|
var location = resolved ? breakpoint.locations[0] : breakpoint;
|
|
|
|
var textViewerLineNumber = this._content.actualLocationToSourceFrameLineNumber(location.lineNumber, location.columnNumber);
|
|
if (textViewerLineNumber >= this._textModel.linesCount)
|
|
return;
|
|
|
|
var existingBreakpointId = this._textViewerLineNumberToBreakpointId[textViewerLineNumber];
|
|
if (existingBreakpointId) {
|
|
WebInspector.debuggerModel.removeBreakpoint(breakpoint.id);
|
|
return;
|
|
}
|
|
|
|
this._breakpointIdToTextViewerLineNumber[breakpoint.id] = textViewerLineNumber;
|
|
this._textViewerLineNumberToBreakpointId[textViewerLineNumber] = breakpoint.id;
|
|
this._setBreakpointDecoration(textViewerLineNumber, resolved, breakpoint.enabled, !!breakpoint.condition);
|
|
},
|
|
|
|
_breakpointRemoved: function(event)
|
|
{
|
|
var breakpointId = event.data;
|
|
|
|
var textViewerLineNumber = this._breakpointIdToTextViewerLineNumber[breakpointId];
|
|
if (textViewerLineNumber === undefined)
|
|
return;
|
|
|
|
delete this._breakpointIdToTextViewerLineNumber[breakpointId];
|
|
delete this._textViewerLineNumberToBreakpointId[textViewerLineNumber];
|
|
this._removeBreakpointDecoration(textViewerLineNumber);
|
|
},
|
|
|
|
_breakpointResolved: function(event)
|
|
{
|
|
var breakpoint = event.data;
|
|
this._breakpointRemoved({ data: breakpoint.id });
|
|
this._breakpointAdded({ data: breakpoint });
|
|
},
|
|
|
|
_setBreakpointDecoration: function(lineNumber, resolved, enabled, hasCondition)
|
|
{
|
|
this._textViewer.beginUpdates();
|
|
this._textViewer.addDecoration(lineNumber, "webkit-breakpoint");
|
|
if (!enabled)
|
|
this._textViewer.addDecoration(lineNumber, "webkit-breakpoint-disabled");
|
|
if (hasCondition)
|
|
this._textViewer.addDecoration(lineNumber, "webkit-breakpoint-conditional");
|
|
this._textViewer.endUpdates();
|
|
},
|
|
|
|
_removeBreakpointDecoration: function(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();
|
|
},
|
|
|
|
_contextMenu: function(event)
|
|
{
|
|
if (!WebInspector.panels.scripts)
|
|
return;
|
|
|
|
var target = event.target.enclosingNodeOrSelfWithClass("webkit-line-number");
|
|
if (!target)
|
|
return;
|
|
var textViewerLineNumber = target.lineNumber;
|
|
|
|
var contextMenu = new WebInspector.ContextMenu();
|
|
|
|
contextMenu.appendItem(WebInspector.UIString("Continue to Here"), this._continueToLine.bind(this, textViewerLineNumber));
|
|
|
|
var breakpoint = this._findBreakpoint(textViewerLineNumber);
|
|
if (!breakpoint) {
|
|
// This row doesn't have a breakpoint: We want to show Add Breakpoint and Add and Edit Breakpoint.
|
|
contextMenu.appendItem(WebInspector.UIString("Add Breakpoint"), this._setBreakpoint.bind(this, textViewerLineNumber, "", true));
|
|
|
|
function addConditionalBreakpoint()
|
|
{
|
|
this._setBreakpointDecoration(textViewerLineNumber, true, true, true);
|
|
function didEditBreakpointCondition(committed, condition)
|
|
{
|
|
this._removeBreakpointDecoration(textViewerLineNumber);
|
|
if (committed)
|
|
this._setBreakpoint(textViewerLineNumber, condition, true);
|
|
}
|
|
this._editBreakpointCondition(textViewerLineNumber, "", didEditBreakpointCondition.bind(this));
|
|
}
|
|
contextMenu.appendItem(WebInspector.UIString("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.
|
|
function removeBreakpoint()
|
|
{
|
|
WebInspector.debuggerModel.removeBreakpoint(breakpoint.id);
|
|
}
|
|
contextMenu.appendItem(WebInspector.UIString("Remove Breakpoint"), removeBreakpoint);
|
|
function editBreakpointCondition()
|
|
{
|
|
function didEditBreakpointCondition(committed, condition)
|
|
{
|
|
if (committed)
|
|
WebInspector.debuggerModel.updateBreakpoint(breakpoint.id, condition, breakpoint.enabled);
|
|
}
|
|
this._editBreakpointCondition(textViewerLineNumber, breakpoint.condition, didEditBreakpointCondition.bind(this));
|
|
}
|
|
contextMenu.appendItem(WebInspector.UIString("Edit Breakpoint…"), editBreakpointCondition.bind(this));
|
|
function setBreakpointEnabled(enabled)
|
|
{
|
|
WebInspector.debuggerModel.updateBreakpoint(breakpoint.id, breakpoint.condition, enabled);
|
|
}
|
|
if (breakpoint.enabled)
|
|
contextMenu.appendItem(WebInspector.UIString("Disable Breakpoint"), setBreakpointEnabled.bind(this, false));
|
|
else
|
|
contextMenu.appendItem(WebInspector.UIString("Enable Breakpoint"), setBreakpointEnabled.bind(this, true));
|
|
}
|
|
contextMenu.show(event);
|
|
},
|
|
|
|
_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 textViewerLineNumber = target.lineNumber;
|
|
|
|
var breakpoint = this._findBreakpoint(textViewerLineNumber);
|
|
if (breakpoint) {
|
|
if (event.shiftKey)
|
|
WebInspector.debuggerModel.updateBreakpoint(breakpoint.id, breakpoint.condition, !breakpoint.enabled);
|
|
else
|
|
WebInspector.debuggerModel.removeBreakpoint(breakpoint.id);
|
|
} else
|
|
this._setBreakpoint(textViewerLineNumber, "", 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 (!WebInspector.panels.scripts || !WebInspector.panels.scripts.paused)
|
|
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;
|
|
|
|
var 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;
|
|
InspectorBackend.releaseWrapperObjectGroup(0, this._popoverObjectGroup);
|
|
},
|
|
|
|
_mouseHover: function(element)
|
|
{
|
|
delete this._hoverTimer;
|
|
|
|
if (!WebInspector.panels.scripts || !WebInspector.panels.scripts.paused)
|
|
return;
|
|
|
|
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)
|
|
{
|
|
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)
|
|
{
|
|
if (!WebInspector.panels.scripts.paused)
|
|
return;
|
|
|
|
var popupContentElement = null;
|
|
if (result.type !== "object" && result.type !== "node" && result.type !== "array") {
|
|
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, "", null, false);
|
|
section.expanded = true;
|
|
section.element.addStyleClass("source-frame-popover-tree");
|
|
section.headerElement.addStyleClass("hidden");
|
|
popupContentElement.appendChild(section.element);
|
|
|
|
this._popup = new WebInspector.Popover(popupContentElement);
|
|
var popupWidth = 300;
|
|
var 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);
|
|
}
|
|
|
|
function evaluateCallback(result)
|
|
{
|
|
if (result.isError())
|
|
return;
|
|
if (!WebInspector.panels.scripts.paused)
|
|
return;
|
|
showObjectPopup.call(this, result);
|
|
}
|
|
WebInspector.panels.scripts.evaluateInSelectedCallFrame(element.textContent, false, this._popoverObjectGroup, false, evaluateCallback.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;
|
|
},
|
|
|
|
_evalSelectionInCallFrame: function(event)
|
|
{
|
|
if (!WebInspector.panels.scripts || !WebInspector.panels.scripts.paused)
|
|
return;
|
|
|
|
var selection = this.element.contentWindow.getSelection();
|
|
if (!selection.rangeCount)
|
|
return;
|
|
|
|
var expression = selection.getRangeAt(0).toString().trim();
|
|
WebInspector.panels.scripts.evaluateInSelectedCallFrame(expression, false, "console", function(result) {
|
|
WebInspector.showConsole();
|
|
var commandMessage = new WebInspector.ConsoleCommand(expression);
|
|
WebInspector.console.addMessage(commandMessage);
|
|
WebInspector.console.addMessage(new WebInspector.ConsoleCommandResult(result, commandMessage));
|
|
});
|
|
},
|
|
|
|
resize: function()
|
|
{
|
|
if (this._textViewer)
|
|
this._textViewer.resize();
|
|
},
|
|
|
|
formatSource: function()
|
|
{
|
|
if (!this._content)
|
|
return;
|
|
|
|
function didFormat(formattedContent)
|
|
{
|
|
this._content = formattedContent;
|
|
this._textModel.setText(null, formattedContent.text);
|
|
this._setTextViewerDecorations();
|
|
}
|
|
var formatter = new WebInspector.ScriptFormatter();
|
|
formatter.formatContent(this._content, didFormat.bind(this))
|
|
},
|
|
|
|
_continueToLine: function(lineNumber)
|
|
{
|
|
var location = this._content.sourceFrameLineNumberToActualLocation(lineNumber);
|
|
if (location.sourceID)
|
|
WebInspector.debuggerModel.continueToLocation(location.sourceID, location.lineNumber, location.columnNumber);
|
|
},
|
|
|
|
_doubleClick: function(event)
|
|
{
|
|
if (!Preferences.canEditScriptSource || !this._isScript)
|
|
return;
|
|
|
|
var lineRow = event.target.enclosingNodeOrSelfWithClass("webkit-line-content");
|
|
if (!lineRow)
|
|
return; // Do not trigger editing from line numbers.
|
|
|
|
var lineNumber = lineRow.lineNumber;
|
|
var location = this._content.sourceFrameLineNumberToActualLocation(lineNumber);
|
|
if (!location.sourceID)
|
|
return;
|
|
|
|
function didEditLine(newContent)
|
|
{
|
|
var lines = [];
|
|
var oldLines = this._content.text.split('\n');
|
|
for (var i = 0; i < oldLines.length; ++i) {
|
|
if (i === lineNumber)
|
|
lines.push(newContent);
|
|
else
|
|
lines.push(oldLines[i]);
|
|
}
|
|
WebInspector.debuggerModel.editScriptSource(location.sourceID, lines.join("\n"));
|
|
}
|
|
this._textViewer.editLine(lineRow, didEditLine.bind(this));
|
|
},
|
|
|
|
_setBreakpoint: function(lineNumber, condition, enabled)
|
|
{
|
|
var location = this._content.sourceFrameLineNumberToActualLocation(lineNumber);
|
|
if (this._url)
|
|
WebInspector.debuggerModel.setBreakpoint(this._url, location.lineNumber, location.columnNumber, condition, enabled);
|
|
else if (location.sourceID)
|
|
WebInspector.debuggerModel.setBreakpointBySourceId(location.sourceID, location.lineNumber, location.columnNumber, condition, enabled);
|
|
else
|
|
return;
|
|
|
|
if (!WebInspector.panels.scripts.breakpointsActivated)
|
|
WebInspector.panels.scripts.toggleBreakpointsClicked();
|
|
},
|
|
|
|
_findBreakpoint: function(textViewerLineNumber)
|
|
{
|
|
var breakpointId = this._textViewerLineNumberToBreakpointId[textViewerLineNumber];
|
|
return WebInspector.debuggerModel.breakpointForId(breakpointId);
|
|
}
|
|
}
|
|
|
|
WebInspector.SourceFrame.prototype.__proto__ = WebInspector.View.prototype;
|
|
|
|
|
|
WebInspector.SourceFrameContentProvider = function()
|
|
{
|
|
}
|
|
|
|
WebInspector.SourceFrameContentProvider.prototype = {
|
|
requestContent: function(callback)
|
|
{
|
|
// Should be implemented by subclasses.
|
|
}
|
|
}
|