ae12bae6198428c9ff565d7d7eea02c8d5729278
[WebKit-https.git] / Source / WebCore / inspector / front-end / JavaScriptSourceFrame.js
1 /*
2  * Copyright (C) 2011 Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 /**
32  * @constructor
33  * @extends {WebInspector.SourceFrame}
34  * @param {WebInspector.ScriptsPanel} scriptsPanel
35  * @param {WebInspector.JavaScriptSource} javaScriptSource
36  */
37 WebInspector.JavaScriptSourceFrame = function(scriptsPanel, javaScriptSource)
38 {
39     this._scriptsPanel = scriptsPanel;
40     this._breakpointManager = WebInspector.breakpointManager;
41     this._javaScriptSource = javaScriptSource;
42
43     var locations = this._breakpointManager.breakpointLocationsForUISourceCode(this._javaScriptSource);
44     for (var i = 0; i < locations.length; ++i)
45         this._breakpointAdded({data:locations[i]});
46
47     WebInspector.SourceFrame.call(this, javaScriptSource);
48
49     this._popoverHelper = new WebInspector.ObjectPopoverHelper(this.textEditor.element,
50             this._getPopoverAnchor.bind(this), this._resolveObjectForPopover.bind(this), this._onHidePopover.bind(this), true);
51
52     this.textEditor.element.addEventListener("keydown", this._onKeyDown.bind(this), true);
53
54     this.textEditor.addEventListener(WebInspector.TextEditor.Events.GutterClick, this._handleGutterClick.bind(this), this);
55
56     this._breakpointManager.addEventListener(WebInspector.BreakpointManager.Events.BreakpointAdded, this._breakpointAdded, this);
57     this._breakpointManager.addEventListener(WebInspector.BreakpointManager.Events.BreakpointRemoved, this._breakpointRemoved, this);
58
59     this._javaScriptSource.addEventListener(WebInspector.UISourceCode.Events.ContentChanged, this._onContentChanged, this);
60     this._javaScriptSource.addEventListener(WebInspector.UISourceCode.Events.ConsoleMessageAdded, this._consoleMessageAdded, this);
61     this._javaScriptSource.addEventListener(WebInspector.UISourceCode.Events.ConsoleMessageRemoved, this._consoleMessageRemoved, this);
62     this._javaScriptSource.addEventListener(WebInspector.UISourceCode.Events.ConsoleMessagesCleared, this._consoleMessagesCleared, this);
63 }
64
65 WebInspector.JavaScriptSourceFrame.prototype = {
66     // View events
67     wasShown: function()
68     {
69         WebInspector.SourceFrame.prototype.wasShown.call(this);
70     },
71
72     willHide: function()
73     {
74         WebInspector.SourceFrame.prototype.willHide.call(this);
75         this._popoverHelper.hidePopover();
76     },
77
78     /**
79      * @return {boolean}
80      */
81     canEditSource: function()
82     {
83         return this._javaScriptSource.isEditable();
84     },
85
86     /**
87      * @param {string} text 
88      */
89     commitEditing: function(text)
90     {
91         if (!this._javaScriptSource.isDirty())
92             return;
93
94         this._isCommittingEditing = true;
95         this._javaScriptSource.commitWorkingCopy(this._didEditContent.bind(this));
96         delete this._isCommittingEditing;
97     },
98
99     /**
100      * @param {WebInspector.Event} event
101      */
102     _onContentChanged: function(event)
103     {
104         if (this._isCommittingEditing)
105             return;
106         var content = /** @type {string} */ event.data.content;
107
108         if (this._javaScriptSource.togglingFormatter())
109             this.setContent(content, false, this._javaScriptSource.mimeType());
110         else {
111             var breakpointLocations = this._breakpointManager.breakpointLocationsForUISourceCode(this._javaScriptSource);
112             for (var i = 0; i < breakpointLocations.length; ++i)
113                 breakpointLocations[i].breakpoint.remove();
114             this.setContent(content, false, this._javaScriptSource.mimeType());
115         }
116     },
117
118     populateLineGutterContextMenu: function(contextMenu, lineNumber)
119     {
120         contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Continue to here" : "Continue to Here"), this._continueToLine.bind(this, lineNumber));
121
122         var breakpoint = this._breakpointManager.findBreakpoint(this._javaScriptSource, lineNumber);
123         if (!breakpoint) {
124             // This row doesn't have a breakpoint: We want to show Add Breakpoint and Add and Edit Breakpoint.
125             contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add breakpoint" : "Add Breakpoint"), this._setBreakpoint.bind(this, lineNumber, "", true));
126             contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add conditional breakpoint…" : "Add Conditional Breakpoint…"), this._editBreakpointCondition.bind(this, lineNumber));
127         } else {
128             // This row has a breakpoint, we want to show edit and remove breakpoint, and either disable or enable.
129             contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Remove breakpoint" : "Remove Breakpoint"), breakpoint.remove.bind(breakpoint));
130             contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Edit breakpoint…" : "Edit Breakpoint…"), this._editBreakpointCondition.bind(this, lineNumber, breakpoint));
131             if (breakpoint.enabled())
132                 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Disable breakpoint" : "Disable Breakpoint"), breakpoint.setEnabled.bind(breakpoint, false));
133             else
134                 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Enable breakpoint" : "Enable Breakpoint"), breakpoint.setEnabled.bind(breakpoint, true));
135         }
136     },
137
138     populateTextAreaContextMenu: function(contextMenu, lineNumber)
139     {
140         WebInspector.SourceFrame.prototype.populateTextAreaContextMenu.call(this, contextMenu, lineNumber);
141         var selection = window.getSelection();
142         if (selection.type === "Range" && !selection.isCollapsed) {
143             var addToWatchLabel = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add to watch" : "Add to Watch");
144             contextMenu.appendItem(addToWatchLabel, this._scriptsPanel.addToWatch.bind(this._scriptsPanel, selection.toString()));
145             var evaluateLabel = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Evaluate in console" : "Evaluate in Console");
146             contextMenu.appendItem(evaluateLabel, WebInspector.evaluateInConsole.bind(WebInspector, selection.toString()));
147             contextMenu.appendSeparator();
148         }
149         contextMenu.appendApplicableItems(this._javaScriptSource);
150     },
151
152     onTextChanged: function(oldRange, newRange)
153     {
154         WebInspector.SourceFrame.prototype.onTextChanged.call(this, oldRange, newRange);
155         this._removeBreakpointsBeforeEditing();
156         this._javaScriptSource.setWorkingCopy(this._textEditor.text());
157         this._restoreBreakpointsAfterEditing();
158     },
159
160     _didEditContent: function(error)
161     {
162         if (error) {
163             WebInspector.log(error, WebInspector.ConsoleMessage.MessageLevel.Error, true);
164             return;
165         }
166         if (!this._javaScriptSource.supportsEnabledBreakpointsWhileEditing())
167             this._restoreBreakpointsAfterEditing();
168     },
169
170     _removeBreakpointsBeforeEditing: function()
171     {
172         var supportsBreakpointsOnEdit = this._javaScriptSource.supportsEnabledBreakpointsWhileEditing();
173         if (!this._javaScriptSource.isDirty() || supportsBreakpointsOnEdit) {
174             // Disable all breakpoints in the model, store them as muted breakpoints.
175             var breakpointLocations = this._breakpointManager.breakpointLocationsForUISourceCode(this._javaScriptSource);
176             var lineNumbers = {};
177             this._preserveDecorations = true;
178             for (var i = 0; i < breakpointLocations.length; ++i) {
179                 var breakpoint = breakpointLocations[i].breakpoint;
180                 breakpointLocations[i].breakpoint.remove();
181             }
182             delete this._preserveDecorations;
183
184             for (var lineNumber = 0; lineNumber < this._textEditor.linesCount; ++lineNumber) {
185                 var breakpointDecoration = this._textEditor.getAttribute(lineNumber, "breakpoint");
186                 if (breakpointDecoration)
187                     this._addBreakpointDecoration(lineNumber, breakpointDecoration.condition, breakpointDecoration.enabled, !supportsBreakpointsOnEdit);
188             }
189             this.clearExecutionLine();
190         }
191     },
192
193     _restoreBreakpointsAfterEditing: function()
194     {
195         if (!this._javaScriptSource.isDirty() || this._javaScriptSource.supportsEnabledBreakpointsWhileEditing()) {
196             // Restore all muted breakpoints.
197             for (var lineNumber = 0; lineNumber < this._textEditor.linesCount; ++lineNumber) {
198                 var breakpointDecoration = this._textEditor.getAttribute(lineNumber, "breakpoint");
199                 if (breakpointDecoration) {
200                     // Remove fake decoration
201                     this._removeBreakpointDecoration(lineNumber);
202                     // Set new breakpoint
203                     this._setBreakpoint(lineNumber, breakpointDecoration.condition, breakpointDecoration.enabled);
204                 }
205             }
206         }
207     },
208
209     _getPopoverAnchor: function(element, event)
210     {
211         if (!WebInspector.debuggerModel.isPaused())
212             return null;
213         if (window.getSelection().type === "Range")
214             return null;
215         var lineElement = element.enclosingNodeOrSelfWithClass("webkit-line-content");
216         if (!lineElement)
217             return null;
218
219         if (element.hasStyleClass("webkit-javascript-ident"))
220             return element;
221
222         if (element.hasStyleClass("source-frame-token"))
223             return element;
224
225         // We are interested in identifiers and "this" keyword.
226         if (element.hasStyleClass("webkit-javascript-keyword"))
227             return element.textContent === "this" ? element : null;
228
229         if (element !== lineElement || lineElement.childElementCount)
230             return null;
231
232         // Handle non-highlighted case
233         // 1. Collect ranges of identifier suspects
234         var lineContent = lineElement.textContent;
235         var ranges = [];
236         var regex = new RegExp("[a-zA-Z_\$0-9]+", "g");
237         var match;
238         while (regex.lastIndex < lineContent.length && (match = regex.exec(lineContent)))
239             ranges.push({offset: match.index, length: regex.lastIndex - match.index});
240
241         // 2. 'highlight' them with artificial style to detect word boundaries
242         var changes = [];
243         WebInspector.highlightRangesWithStyleClass(lineElement, ranges, "source-frame-token", changes);
244         var lineOffsetLeft = lineElement.totalOffsetLeft();
245         for (var child = lineElement.firstChild; child; child = child.nextSibling) {
246             if (child.nodeType !== Node.ELEMENT_NODE || !child.hasStyleClass("source-frame-token"))
247                 continue;
248             if (event.x > lineOffsetLeft + child.offsetLeft && event.x < lineOffsetLeft + child.offsetLeft + child.offsetWidth) {
249                 var text = child.textContent;
250                 return (text === "this" || !WebInspector.SourceJavaScriptTokenizer.Keywords[text]) ? child : null;
251             }
252         }
253         return null;
254     },
255
256     _resolveObjectForPopover: function(element, showCallback, objectGroupName)
257     {
258         this._highlightElement = this._highlightExpression(element);
259
260         /**
261          * @param {?RuntimeAgent.RemoteObject} result
262          * @param {boolean=} wasThrown
263          */
264         function showObjectPopover(result, wasThrown)
265         {
266             if (!WebInspector.debuggerModel.isPaused()) {
267                 this._popoverHelper.hidePopover();
268                 return;
269             }
270             showCallback(WebInspector.RemoteObject.fromPayload(result), wasThrown, this._highlightElement);
271             // Popover may have been removed by showCallback().
272             if (this._highlightElement)
273                 this._highlightElement.addStyleClass("source-frame-eval-expression");
274         }
275
276         if (!WebInspector.debuggerModel.isPaused()) {
277             this._popoverHelper.hidePopover();
278             return;
279         }
280         var selectedCallFrame = WebInspector.debuggerModel.selectedCallFrame();
281         selectedCallFrame.evaluate(this._highlightElement.textContent, objectGroupName, false, true, false, showObjectPopover.bind(this));
282     },
283
284     _onHidePopover: function()
285     {
286         // Replace higlight element with its contents inplace.
287         var highlightElement = this._highlightElement;
288         if (!highlightElement)
289             return;
290         // FIXME: the text editor should maintain highlight on its own. The check below is a workaround for
291         // the case when highlight element is detached from DOM by the TextEditor when re-building the DOM.
292         var parentElement = highlightElement.parentElement;
293         if (parentElement) {
294             var child = highlightElement.firstChild;
295             while (child) {
296                 var nextSibling = child.nextSibling;
297                 parentElement.insertBefore(child, highlightElement);
298                 child = nextSibling;
299             }
300             parentElement.removeChild(highlightElement);
301         }
302         delete this._highlightElement;
303     },
304
305     _highlightExpression: function(element)
306     {
307         // Collect tokens belonging to evaluated expression.
308         var tokens = [ element ];
309         var token = element.previousSibling;
310         while (token && (token.className === "webkit-javascript-ident" || token.className === "source-frame-token" || token.className === "webkit-javascript-keyword" || token.textContent.trim() === ".")) {
311             tokens.push(token);
312             token = token.previousSibling;
313         }
314         tokens.reverse();
315
316         // Wrap them with highlight element.
317         var parentElement = element.parentElement;
318         var nextElement = element.nextSibling;
319         var container = document.createElement("span");
320         for (var i = 0; i < tokens.length; ++i)
321             container.appendChild(tokens[i]);
322         parentElement.insertBefore(container, nextElement);
323         return container;
324     },
325
326     /**
327      * @param {number} lineNumber
328      * @param {string} condition
329      * @param {boolean} enabled
330      * @param {boolean} mutedWhileEditing
331      */
332     _addBreakpointDecoration: function(lineNumber, condition, enabled, mutedWhileEditing)
333     {
334         var breakpoint = {
335             condition: condition,
336             enabled: enabled
337         };
338         this.textEditor.setAttribute(lineNumber, "breakpoint", breakpoint);
339
340         var disabled = !enabled || (mutedWhileEditing && !this._javaScriptSource.supportsEnabledBreakpointsWhileEditing());
341         this.textEditor.addBreakpoint(lineNumber, disabled, !!condition);
342     },
343
344     _removeBreakpointDecoration: function(lineNumber)
345     {
346         if (this._preserveDecorations)
347             return;
348         this.textEditor.removeAttribute(lineNumber, "breakpoint");
349         this.textEditor.removeBreakpoint(lineNumber);
350     },
351
352     _onKeyDown: function(event)
353     {
354         if (event.keyIdentifier === "U+001B") { // Escape key
355             if (this._popoverHelper.isPopoverVisible()) {
356                 this._popoverHelper.hidePopover();
357                 event.consume();
358             }
359         }
360     },
361
362     /**
363      * @param {number} lineNumber
364      * @param {WebInspector.BreakpointManager.Breakpoint=} breakpoint
365      */
366     _editBreakpointCondition: function(lineNumber, breakpoint)
367     {
368         this._conditionElement = this._createConditionElement(lineNumber);
369         this.textEditor.addDecoration(lineNumber, this._conditionElement);
370
371         function finishEditing(committed, element, newText)
372         {
373             this.textEditor.removeDecoration(lineNumber, this._conditionElement);
374             delete this._conditionEditorElement;
375             delete this._conditionElement;
376             if (breakpoint)
377                 breakpoint.setCondition(newText);
378             else
379                 this._setBreakpoint(lineNumber, newText, true);
380         }
381
382         var config = new WebInspector.EditingConfig(finishEditing.bind(this, true), finishEditing.bind(this, false));
383         WebInspector.startEditing(this._conditionEditorElement, config);
384         this._conditionEditorElement.value = breakpoint ? breakpoint.condition() : "";
385         this._conditionEditorElement.select();
386     },
387
388     _createConditionElement: function(lineNumber)
389     {
390         var conditionElement = document.createElement("div");
391         conditionElement.className = "source-frame-breakpoint-condition";
392
393         var labelElement = document.createElement("label");
394         labelElement.className = "source-frame-breakpoint-message";
395         labelElement.htmlFor = "source-frame-breakpoint-condition";
396         labelElement.appendChild(document.createTextNode(WebInspector.UIString("The breakpoint on line %d will stop only if this expression is true:", lineNumber)));
397         conditionElement.appendChild(labelElement);
398
399         var editorElement = document.createElement("input");
400         editorElement.id = "source-frame-breakpoint-condition";
401         editorElement.className = "monospace";
402         editorElement.type = "text";
403         conditionElement.appendChild(editorElement);
404         this._conditionEditorElement = editorElement;
405
406         return conditionElement;
407     },
408
409     /**
410      * @param {number} lineNumber
411      */
412     setExecutionLine: function(lineNumber)
413     {
414         this._executionLineNumber = lineNumber;
415         if (this.loaded) {
416             this.textEditor.setExecutionLine(lineNumber);
417             this.revealLine(this._executionLineNumber);
418             if (this.canEditSource())
419                 this.setSelection(WebInspector.TextRange.createFromLocation(lineNumber, 0));
420         }
421     },
422
423     clearExecutionLine: function()
424     {
425         if (this.loaded && typeof this._executionLineNumber === "number")
426             this.textEditor.clearExecutionLine();
427         delete this._executionLineNumber;
428     },
429
430     _lineNumberAfterEditing: function(lineNumber, oldRange, newRange)
431     {
432         var shiftOffset = lineNumber <= oldRange.startLine ? 0 : newRange.linesCount - oldRange.linesCount;
433
434         // Special case of editing the line itself. We should decide whether the line number should move below or not.
435         if (lineNumber === oldRange.startLine) {
436             var whiteSpacesRegex = /^[\s\xA0]*$/;
437             for (var i = 0; lineNumber + i <= newRange.endLine; ++i) {
438                 if (!whiteSpacesRegex.test(this.textEditor.line(lineNumber + i))) {
439                     shiftOffset = i;
440                     break;
441                 }
442             }
443         }
444
445         var newLineNumber = Math.max(0, lineNumber + shiftOffset);
446         if (oldRange.startLine < lineNumber && lineNumber < oldRange.endLine)
447             newLineNumber = oldRange.startLine;
448         return newLineNumber;
449     },
450
451     _breakpointAdded: function(event)
452     {
453         var uiLocation = /** @type {WebInspector.UILocation} */ event.data.uiLocation;
454
455         if (uiLocation.uiSourceCode !== this._javaScriptSource)
456             return;
457
458         var breakpoint = /** @type {WebInspector.BreakpointManager.Breakpoint} */ event.data.breakpoint;
459         if (this.loaded)
460             this._addBreakpointDecoration(uiLocation.lineNumber, breakpoint.condition(), breakpoint.enabled(), false);
461     },
462
463     _breakpointRemoved: function(event)
464     {
465         var uiLocation = /** @type {WebInspector.UILocation} */ event.data.uiLocation;
466         if (uiLocation.uiSourceCode !== this._javaScriptSource)
467             return;
468
469         var breakpoint = /** @type {WebInspector.BreakpointManager.Breakpoint} */ event.data.breakpoint;
470         var remainingBreakpoint = this._breakpointManager.findBreakpoint(this._javaScriptSource, uiLocation.lineNumber);
471         if (!remainingBreakpoint && this.loaded)
472             this._removeBreakpointDecoration(uiLocation.lineNumber);
473     },
474
475     _consoleMessageAdded: function(event)
476     {
477         var message = /** @type {WebInspector.PresentationConsoleMessage} */ event.data;
478         if (this.loaded)
479             this.addMessageToSource(message.lineNumber, message.originalMessage);
480     },
481
482     _consoleMessageRemoved: function(event)
483     {
484         var message = /** @type {WebInspector.PresentationConsoleMessage} */ event.data;
485         if (this.loaded)
486             this.removeMessageFromSource(message.lineNumber, message.originalMessage);
487     },
488
489     _consoleMessagesCleared: function(event)
490     {
491         this.clearMessages();
492     },
493
494     onTextEditorContentLoaded: function()
495     {
496         if (typeof this._executionLineNumber === "number")
497             this.setExecutionLine(this._executionLineNumber);
498
499         var breakpointLocations = this._breakpointManager.breakpointLocationsForUISourceCode(this._javaScriptSource);
500         for (var i = 0; i < breakpointLocations.length; ++i) {
501             var breakpoint = breakpointLocations[i].breakpoint;
502             this._addBreakpointDecoration(breakpointLocations[i].uiLocation.lineNumber, breakpoint.condition(), breakpoint.enabled(), false);
503         }
504
505         var messages = this._javaScriptSource.consoleMessages();
506         for (var i = 0; i < messages.length; ++i) {
507             var message = messages[i];
508             this.addMessageToSource(message.lineNumber, message.originalMessage);
509         }
510     },
511
512     /**
513      * @param {Event} event
514      */
515     _handleGutterClick: function(event)
516     {
517         if (this._javaScriptSource.isDirty() && !this._javaScriptSource.supportsEnabledBreakpointsWhileEditing())
518             return;
519
520         var lineNumber = event.data.lineNumber;
521         var eventObject = /** @type {Event} */ event.data.event;
522
523         if (eventObject.button != 0 || eventObject.altKey || eventObject.ctrlKey || eventObject.metaKey)
524             return;
525
526         this._toggleBreakpoint(lineNumber, eventObject.shiftKey);
527         eventObject.consume(true);
528     },
529
530     /**
531      * @param {number} lineNumber
532      * @param {boolean} onlyDisable
533      */
534     _toggleBreakpoint: function(lineNumber, onlyDisable)
535     {
536         var breakpoint = this._breakpointManager.findBreakpoint(this._javaScriptSource, lineNumber);
537         if (breakpoint) {
538             if (onlyDisable)
539                 breakpoint.setEnabled(!breakpoint.enabled());
540             else
541                 breakpoint.remove();
542         } else
543             this._setBreakpoint(lineNumber, "", true);
544     },
545
546     toggleBreakpointOnCurrentLine: function()
547     {
548         var selection = this.textEditor.selection();
549         if (!selection)
550             return;
551         this._toggleBreakpoint(selection.startLine, false);
552     },
553
554     /**
555      * @param {number} lineNumber
556      * @param {string} condition
557      * @param {boolean} enabled
558      */
559     _setBreakpoint: function(lineNumber, condition, enabled)
560     {
561         this._breakpointManager.setBreakpoint(this._javaScriptSource, lineNumber, condition, enabled);
562     },
563
564     /**
565      * @param {number} lineNumber
566      */
567     _continueToLine: function(lineNumber)
568     {
569         var rawLocation = this._javaScriptSource.uiLocationToRawLocation(lineNumber, 0);
570         WebInspector.debuggerModel.continueToLocation(rawLocation);
571     }
572 }
573
574 WebInspector.JavaScriptSourceFrame.prototype.__proto__ = WebInspector.SourceFrame.prototype;