Web Inspector: Debugger: modernize serialization of breakpoints and the maps that...
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Models / Breakpoint.js
1 /*
2  * Copyright (C) 2013, 2014 Apple 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
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 WI.Breakpoint = class Breakpoint extends WI.Object
27 {
28     constructor(sourceCodeLocation, {contentIdentifier, disabled, condition, ignoreCount, autoContinue} = {})
29     {
30         console.assert(sourceCodeLocation instanceof WI.SourceCodeLocation);
31         console.assert(!contentIdentifier || typeof contentIdentifier === "string");
32         console.assert(!disabled || typeof disabled === "boolean");
33         console.assert(!condition || typeof condition === "string");
34         console.assert(!ignoreCount || !isNaN(ignoreCount));
35         console.assert(!autoContinue || typeof autoContinue === "boolean");
36
37         super();
38
39         this._id = null;
40         this._sourceCodeLocation = sourceCodeLocation;
41
42         let sourceCode = this._sourceCodeLocation.sourceCode;
43         if (sourceCode) {
44             this._contentIdentifier = sourceCode.contentIdentifier;
45             console.assert(!contentIdentifier || contentIdentifier === this._contentIdentifier, "The content identifier from the source code should match the given value.");
46         } else
47             this._contentIdentifier = contentIdentifier || null;
48         console.assert(this._contentIdentifier || this._isSpecial(), "There should always be a content identifier for a breakpoint.");
49
50         this._scriptIdentifier = sourceCode instanceof WI.Script ? sourceCode.id : null;
51         this._target = sourceCode instanceof WI.Script ? sourceCode.target : null;
52         this._disabled = disabled || false;
53         this._condition = condition || "";
54         this._ignoreCount = ignoreCount || 0;
55         this._autoContinue = autoContinue || false;
56         this._actions = [];
57         this._resolved = false;
58
59         this._sourceCodeLocation.addEventListener(WI.SourceCodeLocation.Event.LocationChanged, this._sourceCodeLocationLocationChanged, this);
60         this._sourceCodeLocation.addEventListener(WI.SourceCodeLocation.Event.DisplayLocationChanged, this._sourceCodeLocationDisplayLocationChanged, this);
61     }
62
63     // Import / Export
64
65     static fromJSON(json)
66     {
67         const sourceCode = null;
68         let breakpoint = new Breakpoint(new WI.SourceCodeLocation(sourceCode, json.lineNumber || 0, json.columnNumber || 0), {
69             // The 'url' fallback is for transitioning from older frontends and should be removed.
70             contentIdentifier: json.contentIdentifier || json.url,
71             disabled: json.disabled,
72             condition: json.condition,
73             ignoreCount: json.ignoreCount,
74             autoContinue: json.autoContinue,
75         });
76         breakpoint._actions = json.actions.map((actionJSON) => WI.BreakpointAction.fromJSON(actionJSON, breakpoint));
77         return breakpoint;
78     }
79
80     toJSON(key)
81     {
82         // The id, scriptIdentifier, target, and resolved state are tied to the current session, so don't include them for serialization.
83         let json = {
84             contentIdentifier: this._contentIdentifier,
85             lineNumber: this._sourceCodeLocation.lineNumber,
86             columnNumber: this._sourceCodeLocation.columnNumber,
87             disabled: this._disabled,
88             condition: this._condition,
89             ignoreCount: this._ignoreCount,
90             actions: this._actions.map((action) => action.toJSON()),
91             autoContinue: this._autoContinue,
92         };
93         if (key === WI.ObjectStore.toJSONSymbol)
94             json[WI.objectStores.breakpoints.keyPath] = this._contentIdentifier + ":" + this._sourceCodeLocation.lineNumber + ":" + this._sourceCodeLocation.columnNumber;
95         return json;
96     }
97
98     // Public
99
100     get sourceCodeLocation() { return this._sourceCodeLocation; }
101     get contentIdentifier() { return this._contentIdentifier; }
102     get scriptIdentifier() { return this._scriptIdentifier; }
103     get target() { return this._target; }
104
105     get identifier()
106     {
107         return this._id;
108     }
109
110     set identifier(id)
111     {
112         this._id = id || null;
113     }
114
115     get resolved()
116     {
117         return this._resolved;
118     }
119
120     set resolved(resolved)
121     {
122         if (this._resolved === resolved)
123             return;
124
125         console.assert(!resolved || this._sourceCodeLocation.sourceCode || this._isSpecial(), "Breakpoints must have a SourceCode to be resolved.", this);
126
127         this._resolved = resolved || false;
128
129         this.dispatchEventToListeners(WI.Breakpoint.Event.ResolvedStateDidChange);
130     }
131
132     get disabled()
133     {
134         return this._disabled;
135     }
136
137     set disabled(disabled)
138     {
139         if (this._disabled === disabled)
140             return;
141
142         this._disabled = disabled || false;
143
144         this.dispatchEventToListeners(WI.Breakpoint.Event.DisabledStateDidChange);
145     }
146
147     get condition()
148     {
149         return this._condition;
150     }
151
152     set condition(condition)
153     {
154         if (this._condition === condition)
155             return;
156
157         this._condition = condition;
158
159         this.dispatchEventToListeners(WI.Breakpoint.Event.ConditionDidChange);
160     }
161
162     get ignoreCount()
163     {
164         return this._ignoreCount;
165     }
166
167     set ignoreCount(ignoreCount)
168     {
169         console.assert(ignoreCount >= 0, "Ignore count cannot be negative.");
170         if (ignoreCount < 0)
171             return;
172
173         if (this._ignoreCount === ignoreCount)
174             return;
175
176         this._ignoreCount = ignoreCount;
177
178         this.dispatchEventToListeners(WI.Breakpoint.Event.IgnoreCountDidChange);
179     }
180
181     get autoContinue()
182     {
183         return this._autoContinue;
184     }
185
186     set autoContinue(cont)
187     {
188         if (this._autoContinue === cont)
189             return;
190
191         this._autoContinue = cont;
192
193         this.dispatchEventToListeners(WI.Breakpoint.Event.AutoContinueDidChange);
194     }
195
196     get actions()
197     {
198         return this._actions;
199     }
200
201     get probeActions()
202     {
203         return this._actions.filter(function(action) {
204             return action.type === WI.BreakpointAction.Type.Probe;
205         });
206     }
207
208     cycleToNextMode()
209     {
210         if (this.disabled) {
211             // When cycling, clear auto-continue when going from disabled to enabled.
212             this.autoContinue = false;
213             this.disabled = false;
214             return;
215         }
216
217         if (this.autoContinue) {
218             this.disabled = true;
219             return;
220         }
221
222         if (this.actions.length) {
223             this.autoContinue = true;
224             return;
225         }
226
227         this.disabled = true;
228     }
229
230     createAction(type, precedingAction, data)
231     {
232         var newAction = new WI.BreakpointAction(this, type, data || null);
233
234         if (!precedingAction)
235             this._actions.push(newAction);
236         else {
237             var index = this._actions.indexOf(precedingAction);
238             console.assert(index !== -1);
239             if (index === -1)
240                 this._actions.push(newAction);
241             else
242                 this._actions.splice(index + 1, 0, newAction);
243         }
244
245         this.dispatchEventToListeners(WI.Breakpoint.Event.ActionsDidChange);
246
247         return newAction;
248     }
249
250     recreateAction(type, actionToReplace)
251     {
252         let index = this._actions.indexOf(actionToReplace);
253         console.assert(index !== -1);
254         if (index === -1)
255             return null;
256
257         const data = null;
258         let action = new WI.BreakpointAction(this, type, data);
259         this._actions[index] = action;
260
261         this.dispatchEventToListeners(WI.Breakpoint.Event.ActionsDidChange);
262
263         return action;
264     }
265
266     removeAction(action)
267     {
268         var index = this._actions.indexOf(action);
269         console.assert(index !== -1);
270         if (index === -1)
271             return;
272
273         this._actions.splice(index, 1);
274
275         if (!this._actions.length)
276             this.autoContinue = false;
277
278         this.dispatchEventToListeners(WI.Breakpoint.Event.ActionsDidChange);
279     }
280
281     clearActions(type)
282     {
283         if (!type)
284             this._actions = [];
285         else
286             this._actions = this._actions.filter(function(action) { return action.type !== type; });
287
288         this.dispatchEventToListeners(WI.Breakpoint.Event.ActionsDidChange);
289     }
290
291     saveIdentityToCookie(cookie)
292     {
293         cookie["breakpoint-content-identifier"] = this._contentIdentifier;
294         cookie["breakpoint-line-number"] = this._sourceCodeLocation.lineNumber;
295         cookie["breakpoint-column-number"] = this._sourceCodeLocation.columnNumber;
296     }
297
298     // Protected (Called by BreakpointAction)
299
300     breakpointActionDidChange(action)
301     {
302         var index = this._actions.indexOf(action);
303         console.assert(index !== -1);
304         if (index === -1)
305             return;
306
307         this.dispatchEventToListeners(WI.Breakpoint.Event.ActionsDidChange);
308     }
309
310     // Private
311
312     _isSpecial()
313     {
314         return this._sourceCodeLocation.isEqual(new WI.SourceCodeLocation(null, Infinity, Infinity));
315     }
316
317     _sourceCodeLocationLocationChanged(event)
318     {
319         this.dispatchEventToListeners(WI.Breakpoint.Event.LocationDidChange, event.data);
320     }
321
322     _sourceCodeLocationDisplayLocationChanged(event)
323     {
324         this.dispatchEventToListeners(WI.Breakpoint.Event.DisplayLocationDidChange, event.data);
325     }
326 };
327
328 WI.Breakpoint.TypeIdentifier = "breakpoint";
329
330 WI.Breakpoint.Event = {
331     DisabledStateDidChange: "breakpoint-disabled-state-did-change",
332     ResolvedStateDidChange: "breakpoint-resolved-state-did-change",
333     ConditionDidChange: "breakpoint-condition-did-change",
334     IgnoreCountDidChange: "breakpoint-ignore-count-did-change",
335     ActionsDidChange: "breakpoint-actions-did-change",
336     AutoContinueDidChange: "breakpoint-auto-continue-did-change",
337     LocationDidChange: "breakpoint-location-did-change",
338     DisplayLocationDidChange: "breakpoint-display-location-did-change",
339 };