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