2011-02-01 Pavel Podivilov <podivilov@chromium.org>
[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._scripts = {};
37
38     InspectorBackend.registerDomainDispatcher("Debugger", new WebInspector.DebuggerDispatcher(this));
39 }
40
41 WebInspector.DebuggerModel.Events = {
42     DebuggerPaused: "debugger-paused",
43     DebuggerResumed: "debugger-resumed",
44     ParsedScriptSource: "parsed-script-source",
45     FailedToParseScriptSource: "failed-to-parse-script-source",
46     ScriptSourceChanged: "script-source-changed",
47     BreakpointAdded: "breakpoint-added",
48     BreakpointRemoved: "breakpoint-removed",
49     BreakpointResolved: "breakpoint-resolved"
50 }
51
52 WebInspector.DebuggerModel.prototype = {
53     enableDebugger: function()
54     {
55         InspectorBackend.enableDebugger();
56         if (this._breakpointsPushedToBackend)
57             return;
58         var breakpoints = WebInspector.settings.breakpoints;
59         for (var i = 0; i < breakpoints.length; ++i) {
60             var breakpoint = breakpoints[i];
61             if (typeof breakpoint.url !== "string" || typeof breakpoint.lineNumber !== "number" || typeof breakpoint.columnNumber !== "number" ||
62                 typeof breakpoint.condition !== "string" || typeof breakpoint.enabled !== "boolean")
63                 continue;
64             this.setBreakpoint(breakpoint.url, breakpoint.lineNumber, breakpoint.columnNumber, breakpoint.condition, breakpoint.enabled);
65         }
66         this._breakpointsPushedToBackend = true;
67     },
68
69     disableDebugger: function()
70     {
71         InspectorBackend.disableDebugger();
72     },
73
74     continueToLine: function(sourceID, lineNumber)
75     {
76         InspectorBackend.continueToLocation(sourceID, lineNumber, 0);
77     },
78
79     setBreakpoint: function(url, lineNumber, columnNumber, condition, enabled)
80     {
81         function didSetBreakpoint(breakpointsPushedToBackend, breakpointId, locations)
82         {
83             if (!breakpointId)
84                 return;
85             var breakpoint = new WebInspector.Breakpoint(breakpointId, url, "", lineNumber, columnNumber, condition, enabled);
86             breakpoint.locations = locations;
87             this._breakpoints[breakpointId] = breakpoint;
88             if (breakpointsPushedToBackend)
89                 this._saveBreakpoints();
90             this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.BreakpointAdded, breakpoint);
91         }
92         InspectorBackend.setJavaScriptBreakpoint(url, lineNumber, columnNumber, condition, enabled, didSetBreakpoint.bind(this, this._breakpointsPushedToBackend));
93     },
94
95     setBreakpointBySourceId: function(sourceID, lineNumber, columnNumber, condition, enabled)
96     {
97         function didSetBreakpoint(breakpointId, actualLineNumber, actualColumnNumber)
98         {
99             if (!breakpointId)
100                 return;
101             var breakpoint = new WebInspector.Breakpoint(breakpointId, "", sourceID, lineNumber, columnNumber, condition, enabled);
102             breakpoint.addLocation(sourceID, actualLineNumber, actualColumnNumber);
103             this._breakpoints[breakpointId] = breakpoint;
104             this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.BreakpointAdded, breakpoint);
105         }
106         InspectorBackend.setJavaScriptBreakpointBySourceId(sourceID, lineNumber, columnNumber, condition, enabled, didSetBreakpoint.bind(this));
107     },
108
109     removeBreakpoint: function(breakpointId)
110     {
111         InspectorBackend.removeJavaScriptBreakpoint(breakpointId);
112         var breakpoint = this._breakpoints[breakpointId];
113         delete this._breakpoints[breakpointId];
114         this._saveBreakpoints();
115         this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.BreakpointRemoved, breakpointId);
116     },
117
118     updateBreakpoint: function(breakpointId, condition, enabled)
119     {
120         var breakpoint = this._breakpoints[breakpointId];
121         this.removeBreakpoint(breakpointId);
122         if (breakpoint.url)
123             this.setBreakpoint(breakpoint.url, breakpoint.lineNumber, breakpoint.columnNumber, condition, enabled);
124         else
125             this.setBreakpointBySourceId(breakpoint.sourceID, breakpoint.lineNumber, breakpoint.columnNumber, condition, enabled);
126     },
127
128     _breakpointResolved: function(breakpointId, sourceID, lineNumber, columnNumber)
129     {
130         var breakpoint = this._breakpoints[breakpointId];
131         if (!breakpoint)
132             return;
133         breakpoint.addLocation(sourceID, lineNumber, columnNumber);
134         this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.BreakpointResolved, breakpoint);
135     },
136
137     _saveBreakpoints: function()
138     {
139         var serializedBreakpoints = [];
140         for (var id in this._breakpoints) {
141             var breakpoint = this._breakpoints[id];
142             if (!breakpoint.url)
143                 continue;
144             var serializedBreakpoint = {};
145             serializedBreakpoint.url = breakpoint.url;
146             serializedBreakpoint.lineNumber = breakpoint.lineNumber;
147             serializedBreakpoint.columnNumber = breakpoint.columnNumber;
148             serializedBreakpoint.condition = breakpoint.condition;
149             serializedBreakpoint.enabled = breakpoint.enabled;
150             serializedBreakpoints.push(serializedBreakpoint);
151         }
152         WebInspector.settings.breakpoints = serializedBreakpoints;
153     },
154
155     get breakpoints()
156     {
157         return this._breakpoints;
158     },
159
160     breakpointForId: function(breakpointId)
161     {
162         return this._breakpoints[breakpointId];
163     },
164
165     queryBreakpoints: function(filter)
166     {
167         var breakpoints = [];
168         for (var id in this._breakpoints) {
169            var breakpoint = this._breakpoints[id];
170            if (filter(breakpoint))
171                breakpoints.push(breakpoint);
172         }
173         return breakpoints;
174     },
175
176     findBreakpoint: function(sourceID, lineNumber)
177     {
178         for (var id in this._breakpoints) {
179             var locations = this._breakpoints[id].locations;
180             for (var i = 0; i < locations.length; ++i) {
181                 if (locations[i].sourceID == sourceID && locations[i].lineNumber + 1 === lineNumber)
182                     return this._breakpoints[id];
183             }
184         }
185     },
186
187     reset: function()
188     {
189         this._paused = false;
190         this._callFrames = [];
191         for (var id in this._breakpoints) {
192             var breakpoint = this._breakpoints[id];
193             if (!breakpoint.url)
194                 this.removeBreakpoint(id);
195             else
196                 breakpoint.locations = [];
197         }
198         this._scripts = {};
199     },
200
201     scriptForSourceID: function(sourceID)
202     {
203         return this._scripts[sourceID];
204     },
205
206     scriptsForURL: function(url)
207     {
208         return this.queryScripts(function(s) { return s.sourceURL === url; });
209     },
210
211     queryScripts: function(filter)
212     {
213         var scripts = [];
214         for (var sourceID in this._scripts) {
215             var script = this._scripts[sourceID];
216             if (filter(script))
217                 scripts.push(script);
218         }
219         return scripts;
220     },
221
222     editScriptSource: function(sourceID, scriptSource)
223     {
224         function didEditScriptSource(success, newBodyOrErrorMessage, callFrames)
225         {
226             if (success) {
227                 if (callFrames && callFrames.length)
228                     this._callFrames = callFrames;
229                 this._updateScriptSource(sourceID, newBodyOrErrorMessage);
230             } else
231                 WebInspector.log(newBodyOrErrorMessage, WebInspector.ConsoleMessage.MessageLevel.Warning);
232         }
233         InspectorBackend.editScriptSource(sourceID, scriptSource, didEditScriptSource.bind(this));
234     },
235
236     _updateScriptSource: function(sourceID, scriptSource)
237     {
238         var script = this._scripts[sourceID];
239         var oldSource = script.source;
240         script.source = scriptSource;
241
242         // Clear and re-create breakpoints according to text diff.
243         var diff = Array.diff(oldSource.split("\n"), script.source.split("\n"));
244         for (var id in this._breakpoints) {
245             var breakpoint = this._breakpoints[id];
246             if (breakpoint.url) {
247                 if (breakpoint.url !== script.sourceURL)
248                     continue;
249             } else {
250                 if (breakpoint.sourceID !== sourceID)
251                     continue;
252             }
253             this.removeBreakpoint(breakpoint.id);
254             var lineNumber = breakpoint.lineNumber;
255             var newLineNumber = diff.left[lineNumber].row;
256             if (newLineNumber === undefined) {
257                 for (var i = lineNumber - 1; i >= 0; --i) {
258                     if (diff.left[i].row === undefined)
259                         continue;
260                     var shiftedLineNumber = diff.left[i].row + lineNumber - i;
261                     if (shiftedLineNumber < diff.right.length) {
262                         var originalLineNumber = diff.right[shiftedLineNumber].row;
263                         if (originalLineNumber === lineNumber || originalLineNumber === undefined)
264                             newLineNumber = shiftedLineNumber;
265                     }
266                     break;
267                 }
268             }
269             if (newLineNumber === undefined)
270                 continue;
271             if (breakpoint.url)
272                 this.setBreakpoint(breakpoint.url, newLineNumber, breakpoint.columnNumber, breakpoint.condition, breakpoint.enabled);
273             else
274                 this.setBreakpointBySourceId(sourceID, newLineNumber, breakpoint.columnNumber, breakpoint.condition, breakpoint.enabled);
275         }
276
277         this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.ScriptSourceChanged, { sourceID: sourceID, oldSource: oldSource });
278     },
279
280     get callFrames()
281     {
282         return this._callFrames;
283     },
284
285     _pausedScript: function(details)
286     {
287         this._paused = true;
288         this._callFrames = details.callFrames;
289         this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.DebuggerPaused, details);
290     },
291
292     _resumedScript: function()
293     {
294         this._paused = false;
295         this._callFrames = [];
296         this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.DebuggerResumed);
297     },
298
299     _parsedScriptSource: function(sourceID, sourceURL, lineOffset, columnOffset, length, scriptWorldType)
300     {
301         var script = new WebInspector.Script(sourceID, sourceURL, "", lineOffset, columnOffset, length, undefined, undefined, scriptWorldType);
302         this._scripts[sourceID] = script;
303         this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.ParsedScriptSource, script);
304     },
305
306     _failedToParseScriptSource: function(sourceURL, source, startingLine, errorLine, errorMessage)
307     {
308         var script = new WebInspector.Script(null, sourceURL, source, startingLine, errorLine, errorMessage, undefined);
309         this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.FailedToParseScriptSource, script);
310     }
311 }
312
313 WebInspector.DebuggerModel.prototype.__proto__ = WebInspector.Object.prototype;
314
315 WebInspector.DebuggerEventTypes = {
316     JavaScriptPause: 0,
317     JavaScriptBreakpoint: 1,
318     NativeBreakpoint: 2
319 };
320
321 WebInspector.DebuggerDispatcher = function(debuggerModel)
322 {
323     this._debuggerModel = debuggerModel;
324 }
325
326 WebInspector.DebuggerDispatcher.prototype = {
327     pausedScript: function(details)
328     {
329         this._debuggerModel._pausedScript(details);
330     },
331
332     resumedScript: function()
333     {
334         this._debuggerModel._resumedScript();
335     },
336
337     debuggerWasEnabled: function()
338     {
339         WebInspector.panels.scripts.debuggerWasEnabled();
340     },
341
342     debuggerWasDisabled: function()
343     {
344         WebInspector.panels.scripts.debuggerWasDisabled();
345     },
346
347     parsedScriptSource: function(sourceID, sourceURL, lineOffset, columnOffset, length, scriptWorldType)
348     {
349         this._debuggerModel._parsedScriptSource(sourceID, sourceURL, lineOffset, columnOffset, length, scriptWorldType);
350     },
351
352     failedToParseScriptSource: function(sourceURL, source, startingLine, errorLine, errorMessage)
353     {
354         this._debuggerModel._failedToParseScriptSource(sourceURL, source, startingLine, errorLine, errorMessage);
355     },
356
357     breakpointResolved: function(breakpointId, sourceID, lineNumber, columnNumber)
358     {
359         this._debuggerModel._breakpointResolved(breakpointId, sourceID, lineNumber, columnNumber);
360     },
361
362     didCreateWorker: function()
363     {
364         var workersPane = WebInspector.panels.scripts.sidebarPanes.workers;
365         workersPane.addWorker.apply(workersPane, arguments);
366     },
367
368     didDestroyWorker: function()
369     {
370         var workersPane = WebInspector.panels.scripts.sidebarPanes.workers;
371         workersPane.removeWorker.apply(workersPane, arguments);
372     }
373 }