7646219911d419e70e00b1cb9452d536dd6546b9
[WebKit-https.git] / Source / WebCore / inspector / front-end / BreakpointManager.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.Object}
34  * @param {WebInspector.Setting} breakpointStorage
35  * @param {WebInspector.DebuggerModel} debuggerModel
36  */
37 WebInspector.BreakpointManager = function(breakpointStorage, debuggerModel)
38 {
39     this._storage = new WebInspector.BreakpointManager.Storage(this, breakpointStorage);
40     this._debuggerModel = debuggerModel;
41
42     this._breakpoints = [];
43     this._breakpointForDebuggerId = {};
44     this._breakpointsForUISourceCode = new Map();
45     this._sourceFilesWithRestoredBreakpoints = {};
46
47     this._debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.BreakpointResolved, this._breakpointResolved, this);
48     this._debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.GlobalObjectCleared, this._debuggerReset, this);
49 }
50
51 WebInspector.BreakpointManager.Events = {
52     BreakpointAdded: "breakpoint-added",
53     BreakpointRemoved: "breakpoint-removed"
54 }
55
56 WebInspector.BreakpointManager.prototype = {
57     /**
58      * @param {WebInspector.UISourceCode} uiSourceCode
59      */
60     restoreBreakpoints: function(uiSourceCode)
61     {
62         var sourceFileId = uiSourceCode.breakpointStorageId();
63         if (!sourceFileId || this._sourceFilesWithRestoredBreakpoints[sourceFileId])
64             return;
65         this._sourceFilesWithRestoredBreakpoints[sourceFileId] = true;
66
67         // Erase provisional breakpoints prior to restoring them.
68         for (var debuggerId in this._breakpointForDebuggerId) {
69             var breakpoint = this._breakpointForDebuggerId[debuggerId];
70             if (breakpoint._sourceCodeStorageId !== sourceFileId)
71                 continue;
72             this._debuggerModel.removeBreakpoint(debuggerId);
73             delete this._breakpointForDebuggerId[debuggerId];
74         }
75         this._storage._restoreBreakpoints(uiSourceCode);
76     },
77
78     /**
79      * @param {WebInspector.UISourceCode} uiSourceCode
80      * @param {number} lineNumber
81      * @param {string} condition
82      * @param {boolean} enabled
83      * @return {WebInspector.BreakpointManager.Breakpoint}
84      */
85     setBreakpoint: function(uiSourceCode, lineNumber, condition, enabled)
86     {
87         this._debuggerModel.setBreakpointsActive(true);
88         return this._innerSetBreakpoint(uiSourceCode, lineNumber, condition, enabled);
89     },
90
91     /**
92      * @param {WebInspector.UISourceCode} uiSourceCode
93      * @param {number} lineNumber
94      * @param {string} condition
95      * @param {boolean} enabled
96      * @return {WebInspector.BreakpointManager.Breakpoint}
97      */
98     _innerSetBreakpoint: function(uiSourceCode, lineNumber, condition, enabled)
99     {
100         var breakpoint = this.findBreakpoint(uiSourceCode, lineNumber);
101         if (breakpoint) {
102             breakpoint._updateBreakpoint(condition, enabled);
103             return breakpoint;
104         }
105         breakpoint = new WebInspector.BreakpointManager.Breakpoint(this, uiSourceCode, lineNumber, condition, enabled);
106         this._breakpoints.push(breakpoint);
107         return breakpoint;
108     },
109
110     /**
111      * @param {WebInspector.UISourceCode} uiSourceCode
112      * @param {number} lineNumber
113      * @return {?WebInspector.BreakpointManager.Breakpoint}
114      */
115     findBreakpoint: function(uiSourceCode, lineNumber)
116     {
117         var breakpoints = this._breakpointsForUISourceCode.get(uiSourceCode);
118         var lineBreakpoints = breakpoints ? breakpoints[lineNumber] : null;
119         return lineBreakpoints ? lineBreakpoints[0] : null;
120     },
121
122     /**
123      * @param {WebInspector.UISourceCode} uiSourceCode
124      * @return {Array.<Object>}
125      */
126     breakpointLocationsForUISourceCode: function(uiSourceCode)
127     {
128         var result = [];
129         for (var i = 0; i < this._breakpoints.length; ++i) {
130             var breakpoint = this._breakpoints[i];
131             var uiLocations = breakpoint._uiLocations.values();
132             for (var j = 0; j < uiLocations.length; ++j) {
133                 var uiLocation = uiLocations[j];
134                 if (uiLocation.uiSourceCode === uiSourceCode)
135                     result.push({breakpoint: breakpoint, uiLocation: uiLocation});
136             }
137         }
138         return result;
139     },
140
141     removeAllBreakpoints: function()
142     {
143         var breakpoints = this._breakpoints.slice();
144         for (var i = 0; i < breakpoints.length; ++i)
145             breakpoints[i].remove();
146     },
147
148     reset: function()
149     {
150         // Remove all breakpoints from UI and debugger, do not update storage.
151         this._storage._muted = true;
152         this.removeAllBreakpoints();
153         delete this._storage._muted;
154
155         // Remove all provisional breakpoints from the debugger.
156         for (var debuggerId in this._breakpointForDebuggerId)
157             this._debuggerModel.removeBreakpoint(debuggerId);
158         this._breakpointForDebuggerId = {};
159         this._sourceFilesWithRestoredBreakpoints = {};
160     },
161
162     _debuggerReset: function()
163     {
164         var breakpoints = this._breakpoints.slice();
165         for (var i = 0; i < breakpoints.length; ++i) {
166             breakpoints[i]._resetLocations();
167             breakpoints[i]._isProvisional = true;
168         }
169         this._breakpoints = [];
170         this._breakpointsForUISourceCode.clear();
171         this._sourceFilesWithRestoredBreakpoints = {};
172     },
173
174     _breakpointResolved: function(event)
175     {
176         var breakpointId = /** @type {DebuggerAgent.BreakpointId} */ event.data.breakpointId;
177         var location = /** @type {DebuggerAgent.Location} */ event.data.location;
178         var breakpoint = this._breakpointForDebuggerId[breakpointId];
179         if (!breakpoint || breakpoint._isProvisional)
180             return;
181         breakpoint._addResolvedLocation(location);
182     },
183
184     /**
185      * @param {WebInspector.BreakpointManager.Breakpoint} breakpoint
186      * @param {boolean} removeFromStorage
187      */
188     _removeBreakpoint: function(breakpoint, removeFromStorage)
189     {
190         console.assert(!breakpoint._debuggerId)
191         this._breakpoints.remove(breakpoint);
192         if (removeFromStorage)
193             this._storage._removeBreakpoint(breakpoint);
194     },
195
196     /**
197      * @param {WebInspector.BreakpointManager.Breakpoint} breakpoint
198      * @param {WebInspector.UILocation} uiLocation
199      */
200     _uiLocationAdded: function(breakpoint, uiLocation)
201     {
202         var breakpoints = this._breakpointsForUISourceCode.get(uiLocation.uiSourceCode);
203         if (!breakpoints) {
204             breakpoints = {};
205             this._breakpointsForUISourceCode.put(uiLocation.uiSourceCode, breakpoints);
206         }
207
208         var lineBreakpoints = breakpoints[uiLocation.lineNumber];
209         if (!lineBreakpoints) {
210             lineBreakpoints = [];
211             breakpoints[uiLocation.lineNumber] = lineBreakpoints;
212         }
213
214         lineBreakpoints.push(breakpoint);
215         this.dispatchEventToListeners(WebInspector.BreakpointManager.Events.BreakpointAdded, {breakpoint: breakpoint, uiLocation: uiLocation});
216     },
217
218     /**
219      * @param {WebInspector.BreakpointManager.Breakpoint} breakpoint
220      * @param {WebInspector.UILocation} uiLocation
221      */
222     _uiLocationRemoved: function(breakpoint, uiLocation)
223     {
224       var breakpoints = this._breakpointsForUISourceCode.get(uiLocation.uiSourceCode);
225         if (!breakpoints)
226             return;
227
228         var lineBreakpoints = breakpoints[uiLocation.lineNumber];
229         if (!lineBreakpoints)
230             return;
231
232         lineBreakpoints.remove(breakpoint);
233         if (!lineBreakpoints.length)
234             delete breakpoints[uiLocation.lineNumber];
235         this.dispatchEventToListeners(WebInspector.BreakpointManager.Events.BreakpointRemoved, {breakpoint: breakpoint, uiLocation: uiLocation});
236     }
237 }
238
239 WebInspector.BreakpointManager.prototype.__proto__ = WebInspector.Object.prototype;
240
241 /**
242  * @constructor
243  * @param {WebInspector.BreakpointManager} breakpointManager
244  * @param {WebInspector.UISourceCode} uiSourceCode
245  * @param {number} lineNumber
246  * @param {string} condition
247  * @param {boolean} enabled
248  */
249 WebInspector.BreakpointManager.Breakpoint = function(breakpointManager, uiSourceCode, lineNumber, condition, enabled)
250 {
251     this._breakpointManager = breakpointManager;
252     this._primaryUILocation = new WebInspector.UILocation(uiSourceCode, lineNumber, 0);
253     this._sourceCodeStorageId = uiSourceCode.breakpointStorageId();
254     this._liveLocations = [];
255     this._uiLocations = new Map();
256
257     // Force breakpoint update.
258     /** @type {string} */ this._condition;
259     /** @type {boolean} */ this._enabled;
260     this._updateBreakpoint(condition, enabled);
261 }
262
263 WebInspector.BreakpointManager.Breakpoint.prototype = {
264     /**
265      * @return {WebInspector.UILocation}
266      */
267     primaryUILocation: function()
268     {
269         return this._primaryUILocation;
270     },
271
272     /**
273      * @param {DebuggerAgent.Location} location
274      */
275     _addResolvedLocation: function(location)
276     {
277         this._liveLocations.push(this._breakpointManager._debuggerModel.createLiveLocation(location, this._locationUpdated.bind(this, location)));
278     },
279
280     /**
281      * @param {DebuggerAgent.Location} location
282      * @param {WebInspector.UILocation} uiLocation
283      */
284     _locationUpdated: function(location, uiLocation)
285     {
286         var oldUILocation = /** @type {WebInspector.UILocation} */ this._uiLocations.get(location);
287         if (oldUILocation)
288             this._breakpointManager._uiLocationRemoved(this, oldUILocation);
289         this._uiLocations.put(location, uiLocation);
290         this._breakpointManager._uiLocationAdded(this, uiLocation);
291     },
292
293     /**
294      * @return {boolean}
295      */
296     enabled: function()
297     {
298         return this._enabled;
299     },
300
301     /**
302      * @param {boolean} enabled
303      */
304     setEnabled: function(enabled)
305     {
306         this._updateBreakpoint(this._condition, enabled);
307     },
308
309     /**
310      * @return {string}
311      */
312     condition: function()
313     {
314         return this._condition;
315     },
316
317     /**
318      * @param {string} condition
319      */
320     setCondition: function(condition)
321     {
322         this._updateBreakpoint(condition, this._enabled);
323     },
324
325     /**
326      * @param {string} condition
327      * @param {boolean} enabled
328      */
329     _updateBreakpoint: function(condition, enabled)
330     {
331         if (this._enabled === enabled && this._condition === condition)
332             return;
333
334         if (this._enabled)
335             this._removeFromDebugger();
336
337         this._enabled = enabled;
338         this._condition = condition;
339         this._breakpointManager._storage._updateBreakpoint(this);
340
341         if (this._enabled) {
342             this._setInDebugger();
343             return;
344         }
345
346         this._fakeBreakpointAtPrimaryLocation();
347     },
348
349     remove: function()
350     {
351         this._resetLocations();
352         this._removeFromDebugger();
353         this._breakpointManager._removeBreakpoint(this, true);
354     },
355
356     _setInDebugger: function()
357     {
358         var rawLocation = this._primaryUILocation.uiLocationToRawLocation();
359         this._breakpointManager._debuggerModel.setBreakpointByScriptLocation(rawLocation, this._condition, didSetBreakpoint.bind(this));
360         /**
361          * @this {WebInspector.BreakpointManager.Breakpoint}
362          * @param {?DebuggerAgent.BreakpointId} breakpointId
363          * @param {Array.<DebuggerAgent.Location>} locations
364          */
365         function didSetBreakpoint(breakpointId, locations)
366         {
367             if (!breakpointId) {
368                 this._resetLocations();
369                 this._breakpointManager._removeBreakpoint(this, false);
370                 return;
371             }
372
373             this._debuggerId = breakpointId;
374             this._breakpointManager._breakpointForDebuggerId[breakpointId] = this;
375
376             if (!locations.length) {
377                 this._fakeBreakpointAtPrimaryLocation();
378                 return;
379             }
380
381             this._resetLocations();
382             for (var i = 0; i < locations.length; ++i) {
383                 var script = this._breakpointManager._debuggerModel.scriptForId(locations[i].scriptId);
384                 var uiLocation = script.rawLocationToUILocation(locations[i]);
385                 if (this._breakpointManager.findBreakpoint(uiLocation.uiSourceCode, uiLocation.lineNumber)) {
386                     // location clash
387                     this.remove();
388                     return;
389                 }
390             }
391
392             for (var i = 0; i < locations.length; ++i)
393                 this._addResolvedLocation(locations[i]);
394         }
395     },
396
397     _removeFromDebugger: function()
398     {
399         if (this._debuggerId) {
400             this._breakpointManager._debuggerModel.removeBreakpoint(this._debuggerId);
401             delete this._breakpointManager._breakpointForDebuggerId[this._debuggerId];
402             delete this._debuggerId;
403         }
404     },
405
406     _resetLocations: function()
407     {
408         var uiLocations = this._uiLocations.values();
409         for (var i = 0; i < uiLocations.length; ++i)
410             this._breakpointManager._uiLocationRemoved(this, uiLocations[i]);
411
412         for (var i = 0; i < this._liveLocations.length; ++i)
413             this._liveLocations[i].dispose();
414         this._liveLocations = [];
415
416         this._uiLocations = new Map();
417     },
418
419     /**
420      * @return {string}
421      */
422     _breakpointStorageId: function()
423     {
424         return this._primaryUILocation.uiSourceCode.breakpointStorageId() + ":" + this._primaryUILocation.lineNumber;
425     },
426
427     _fakeBreakpointAtPrimaryLocation: function()
428     {
429         this._resetLocations();
430         this._uiLocations.put({}, this._primaryUILocation);
431         this._breakpointManager._uiLocationAdded(this, this._primaryUILocation);
432     }
433 }
434
435 /**
436  * @constructor
437  * @param {WebInspector.BreakpointManager} breakpointManager
438  * @param {WebInspector.Setting} setting
439  */
440 WebInspector.BreakpointManager.Storage = function(breakpointManager, setting)
441 {
442     this._breakpointManager = breakpointManager;
443     this._setting = setting;
444     var breakpoints = this._setting.get();
445     /** @type {Object.<string,WebInspector.BreakpointManager.Storage.Item>} */
446     this._breakpoints = {};
447     for (var i = 0; i < breakpoints.length; ++i) {
448         var breakpoint = /** @type {WebInspector.BreakpointManager.Storage.Item} */ breakpoints[i];
449         this._breakpoints[breakpoint.sourceFileId + ":" + breakpoint.lineNumber] = breakpoint;
450     }
451 }
452
453 WebInspector.BreakpointManager.Storage.prototype = {
454     /**
455      * @param {WebInspector.UISourceCode} uiSourceCode
456      */
457     _restoreBreakpoints: function(uiSourceCode)
458     {
459         this._muted = true;
460         var breakpointStorageId = uiSourceCode.breakpointStorageId();
461         for (var id in this._breakpoints) {
462             var breakpoint = this._breakpoints[id];
463             if (breakpoint.sourceFileId === breakpointStorageId)
464                 this._breakpointManager._innerSetBreakpoint(uiSourceCode, breakpoint.lineNumber, breakpoint.condition, breakpoint.enabled);
465         }
466         delete this._muted;
467     },
468
469     /**
470      * @param {WebInspector.BreakpointManager.Breakpoint} breakpoint
471      */
472     _updateBreakpoint: function(breakpoint)
473     {
474         if (this._muted)
475             return;
476         this._breakpoints[breakpoint._breakpointStorageId()] = new WebInspector.BreakpointManager.Storage.Item(breakpoint);
477         this._save();
478     },
479
480     /**
481      * @param {WebInspector.BreakpointManager.Breakpoint} breakpoint
482      */
483     _removeBreakpoint: function(breakpoint)
484     {
485         if (this._muted)
486             return;
487         delete this._breakpoints[breakpoint._breakpointStorageId()];
488         this._save();
489     },
490
491     _save: function()
492     {
493         var breakpointsArray = [];
494         for (var id in this._breakpoints)
495             breakpointsArray.push(this._breakpoints[id]);
496         this._setting.set(breakpointsArray);
497     }
498 }
499
500 /**
501  * @constructor
502  * @param {WebInspector.BreakpointManager.Breakpoint} breakpoint
503  */
504 WebInspector.BreakpointManager.Storage.Item = function(breakpoint)
505 {
506     var primaryUILocation = breakpoint.primaryUILocation();
507     this.sourceFileId = primaryUILocation.uiSourceCode.breakpointStorageId();
508     this.lineNumber = primaryUILocation.lineNumber;
509     this.condition = breakpoint.condition();
510     this.enabled = breakpoint.enabled();
511 }
512
513 /** @type {WebInspector.BreakpointManager} */
514 WebInspector.breakpointManager = null;