32d28e71294ac74c578bd448b67880a4ebea9607
[WebKit-https.git] / Source / WebCore / inspector / front-end / DebuggerModel.js
1 /*
2  * Copyright (C) 2010 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.DebuggerModel = function()
32 {
33     this._paused = false;
34     this._callFrames = [];
35     this._breakpoints = {};
36     this._sourceIDAndLineToBreakpointId = {};
37     this._scripts = {};
38
39     InspectorBackend.registerDomainDispatcher("Debugger", new WebInspector.DebuggerDispatcher(this));
40 }
41
42 WebInspector.DebuggerModel.Events = {
43     DebuggerPaused: "debugger-paused",
44     DebuggerResumed: "debugger-resumed",
45     ParsedScriptSource: "parsed-script-source",
46     FailedToParseScriptSource: "failed-to-parse-script-source",
47     ScriptSourceChanged: "script-source-changed",
48     BreakpointAdded: "breakpoint-added",
49     BreakpointRemoved: "breakpoint-removed"
50 }
51
52 WebInspector.DebuggerModel.prototype = {
53     continueToLine: function(sourceID, lineNumber)
54     {
55         function didSetBreakpoint(breakpointId, actualLineNumber)
56         {
57             if (!breakpointId)
58                 return;
59             if (this.findBreakpoint(sourceID, actualLineNumber)) {
60                 InspectorBackend.removeBreakpoint(breakpointId);
61                 return;
62             }
63             if ("_continueToLineBreakpointId" in this)
64                 InspectorBackend.removeBreakpoint(this._continueToLineBreakpointId);
65             this._continueToLineBreakpointId = breakpointId;
66         }
67         var breakpoint = { sourceID: sourceID, lineNumber: lineNumber, columnNumber: 1, condition: "", enabled: true };
68         InspectorBackend.setBreakpoint(breakpoint, didSetBreakpoint.bind(this));
69         if (this._paused)
70             InspectorBackend.resume();
71     },
72
73     setBreakpoint: function(sourceID, lineNumber, enabled, condition)
74     {
75         function didSetBreakpoint(breakpointId, actualLineNumber, actualColumnNumber)
76         {
77             if (!breakpointId)
78                 return;
79             breakpoint.originalLineNumber = breakpoint.lineNumber;
80             breakpoint.originalColumnumber = breakpoint.columnNumber;
81             breakpoint.lineNumber = actualLineNumber;
82             breakpoint.columnNumber = actualColumnNumber;
83             this._breakpointSetOnBackend(breakpointId, breakpoint, false);
84         }
85         var breakpoint = { sourceID: sourceID, lineNumber: lineNumber, columnNumber: 1, condition: condition, enabled: enabled };
86         InspectorBackend.setBreakpoint(breakpoint, didSetBreakpoint.bind(this));
87     },
88
89     removeBreakpoint: function(breakpointId)
90     {
91         InspectorBackend.removeBreakpoint(breakpointId);
92         var breakpoint = this._breakpoints[breakpointId];
93         delete this._breakpoints[breakpointId];
94         delete this._sourceIDAndLineToBreakpointId[this._encodeSourceIDAndLine(breakpoint.sourceID, breakpoint.line)];
95         this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.BreakpointRemoved, breakpointId);
96         breakpoint.dispatchEventToListeners("removed");
97     },
98
99     _breakpointSetOnBackend: function(breakpointId, breakpointData, restored)
100     {
101         var sourceIDAndLine = this._encodeSourceIDAndLine(breakpointData.sourceID, breakpointData.lineNumber);
102         if (sourceIDAndLine in this._sourceIDAndLineToBreakpointId) {
103             InspectorBackend.removeBreakpoint(breakpointId);
104             return;
105         }
106
107         var breakpoint = new WebInspector.Breakpoint(this, breakpointId, breakpointData);
108         breakpoint.restored = restored;
109         breakpoint.originalLineNumber = breakpointData.originalLineNumber;
110         this._breakpoints[breakpointId] = breakpoint;
111         this._sourceIDAndLineToBreakpointId[sourceIDAndLine] = breakpointId;
112         this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.BreakpointAdded, breakpoint);
113     },
114
115     breakpointForId: function(breakpointId)
116     {
117         return this._breakpoints[breakpointId];
118     },
119
120     queryBreakpoints: function(filter)
121     {
122         var breakpoints = [];
123         for (var id in this._breakpoints) {
124            var breakpoint = this._breakpoints[id];
125            if (filter(breakpoint))
126                breakpoints.push(breakpoint);
127         }
128         return breakpoints;
129     },
130
131     findBreakpoint: function(sourceID, lineNumber)
132     {
133         var sourceIDAndLine = this._encodeSourceIDAndLine(sourceID, lineNumber);
134         var breakpointId = this._sourceIDAndLineToBreakpointId[sourceIDAndLine];
135         return this._breakpoints[breakpointId];
136     },
137
138     _encodeSourceIDAndLine: function(sourceID, lineNumber)
139     {
140         return sourceID + ":" + lineNumber;
141     },
142
143     reset: function()
144     {
145         this._paused = false;
146         this._callFrames = [];
147         this._breakpoints = {};
148         delete this._oneTimeBreakpoint;
149         this._sourceIDAndLineToBreakpointId = {};
150         this._scripts = {};
151     },
152
153     scriptForSourceID: function(sourceID)
154     {
155         return this._scripts[sourceID];
156     },
157
158     scriptsForURL: function(url)
159     {
160         return this.queryScripts(function(s) { return s.sourceURL === url; });
161     },
162
163     queryScripts: function(filter)
164     {
165         var scripts = [];
166         for (var sourceID in this._scripts) {
167             var script = this._scripts[sourceID];
168             if (filter(script))
169                 scripts.push(script);
170         }
171         return scripts;
172     },
173
174     editScriptSource: function(sourceID, scriptSource)
175     {
176         function didEditScriptSource(success, newBodyOrErrorMessage, callFrames)
177         {
178             if (success) {
179                 if (callFrames && callFrames.length)
180                     this._callFrames = callFrames;
181                 this._updateScriptSource(sourceID, newBodyOrErrorMessage);
182             } else
183                 WebInspector.log(newBodyOrErrorMessage, WebInspector.ConsoleMessage.MessageLevel.Warning);
184         }
185         InspectorBackend.editScriptSource(sourceID, scriptSource, didEditScriptSource.bind(this));
186     },
187
188     _updateScriptSource: function(sourceID, scriptSource)
189     {
190         var script = this._scripts[sourceID];
191         var oldSource = script.source;
192         script.source = scriptSource;
193
194         // Clear and re-create breakpoints according to text diff.
195         var diff = Array.diff(oldSource.split("\n"), script.source.split("\n"));
196         for (var id in this._breakpoints) {
197             var breakpoint = this._breakpoints[id];
198             if (breakpoint.sourceID !== sourceID)
199                 continue;
200             breakpoint.remove();
201             var lineNumber = breakpoint.line - 1;
202             var newLineNumber = diff.left[lineNumber].row;
203             if (newLineNumber === undefined) {
204                 for (var i = lineNumber - 1; i >= 0; --i) {
205                     if (diff.left[i].row === undefined)
206                         continue;
207                     var shiftedLineNumber = diff.left[i].row + lineNumber - i;
208                     if (shiftedLineNumber < diff.right.length) {
209                         var originalLineNumber = diff.right[shiftedLineNumber].row;
210                         if (originalLineNumber === lineNumber || originalLineNumber === undefined)
211                             newLineNumber = shiftedLineNumber;
212                     }
213                     break;
214                 }
215             }
216             if (newLineNumber !== undefined)
217                 this.setBreakpoint(sourceID, newLineNumber + 1, breakpoint.enabled, breakpoint.condition);
218         }
219
220         this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.ScriptSourceChanged, { sourceID: sourceID, oldSource: oldSource });
221     },
222
223     get callFrames()
224     {
225         return this._callFrames;
226     },
227
228     _pausedScript: function(details)
229     {
230         this._paused = true;
231         this._callFrames = details.callFrames;
232         if ("_continueToLineBreakpointId" in this) {
233             InspectorBackend.removeBreakpoint(this._continueToLineBreakpointId);
234             delete this._continueToLineBreakpointId;
235         }
236         this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.DebuggerPaused, details);
237     },
238
239     _resumedScript: function()
240     {
241         this._paused = false;
242         this._callFrames = [];
243         this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.DebuggerResumed);
244     },
245
246     _parsedScriptSource: function(sourceID, sourceURL, lineOffset, columnOffset, length, scriptWorldType)
247     {
248         var script = new WebInspector.Script(sourceID, sourceURL, "", lineOffset, columnOffset, length, undefined, undefined, scriptWorldType);
249         this._scripts[sourceID] = script;
250         this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.ParsedScriptSource, script);
251     },
252
253     _failedToParseScriptSource: function(sourceURL, source, startingLine, errorLine, errorMessage)
254     {
255         var script = new WebInspector.Script(null, sourceURL, source, startingLine, errorLine, errorMessage, undefined);
256         this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.FailedToParseScriptSource, script);
257     }
258 }
259
260 WebInspector.DebuggerModel.prototype.__proto__ = WebInspector.Object.prototype;
261
262 WebInspector.DebuggerEventTypes = {
263     JavaScriptPause: 0,
264     JavaScriptBreakpoint: 1,
265     NativeBreakpoint: 2
266 };
267
268 WebInspector.DebuggerDispatcher = function(debuggerModel)
269 {
270     this._debuggerModel = debuggerModel;
271 }
272
273 WebInspector.DebuggerDispatcher.prototype = {
274     pausedScript: function(details)
275     {
276         this._debuggerModel._pausedScript(details);
277     },
278
279     resumedScript: function()
280     {
281         this._debuggerModel._resumedScript();
282     },
283
284     debuggerWasEnabled: function()
285     {
286         WebInspector.panels.scripts.debuggerWasEnabled();
287     },
288
289     debuggerWasDisabled: function()
290     {
291         WebInspector.panels.scripts.debuggerWasDisabled();
292     },
293
294     parsedScriptSource: function(sourceID, sourceURL, lineOffset, columnOffset, length, scriptWorldType)
295     {
296         this._debuggerModel._parsedScriptSource(sourceID, sourceURL, lineOffset, columnOffset, length, scriptWorldType);
297     },
298
299     failedToParseScriptSource: function(sourceURL, source, startingLine, errorLine, errorMessage)
300     {
301         this._debuggerModel._failedToParseScriptSource(sourceURL, source, startingLine, errorLine, errorMessage);
302     },
303
304     breakpointResolved: function(breakpointId, breakpoint)
305     {
306         this._debuggerModel._breakpointSetOnBackend(breakpointId, breakpoint, true);
307     },
308
309     didCreateWorker: function()
310     {
311         var workersPane = WebInspector.panels.scripts.sidebarPanes.workers;
312         workersPane.addWorker.apply(workersPane, arguments);
313     },
314
315     didDestroyWorker: function()
316     {
317         var workersPane = WebInspector.panels.scripts.sidebarPanes.workers;
318         workersPane.removeWorker.apply(workersPane, arguments);
319     }
320 }