Unreviewed, rolling out r143100.
[WebKit-https.git] / Source / WebCore / inspector / front-end / ScriptSnippetModel.js
1 /*
2  * Copyright (C) 2012 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.Workspace} workspace
35  */
36 WebInspector.ScriptSnippetModel = function(workspace)
37 {
38     this._workspace = workspace;
39     /** {Object.<string, WebInspector.UISourceCode>} */
40     this._uiSourceCodeForScriptId = {};
41     this._scriptForUISourceCode = new Map();
42     /** {Object.<string, WebInspector.UISourceCode>} */
43     this._uiSourceCodeForSnippetId = {};
44     this._snippetIdForUISourceCode = new Map();
45     
46     this._snippetStorage = new WebInspector.SnippetStorage("script", "Script snippet #");
47     this._lastSnippetEvaluationIndexSetting = WebInspector.settings.createSetting("lastSnippetEvaluationIndex", 0);
48     this._snippetScriptMapping = new WebInspector.SnippetScriptMapping(this);
49     this._workspaceProvider = new WebInspector.SimpleWorkspaceProvider(this._workspace, WebInspector.projectTypes.Snippets);
50     this.reset();
51     WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.GlobalObjectCleared, this._debuggerReset, this);
52 }
53
54 WebInspector.ScriptSnippetModel.prototype = {
55     /**
56      * @return {WebInspector.SnippetScriptMapping}
57      */
58     get scriptMapping()
59     {
60         return this._snippetScriptMapping;
61     },
62
63     _loadSnippets: function()
64     {
65         var snippets = this._snippetStorage.snippets();
66         for (var i = 0; i < snippets.length; ++i)
67             this._addScriptSnippet(snippets[i]);
68     },
69
70     /**
71      * @return {WebInspector.UISourceCode}
72      */
73     createScriptSnippet: function()
74     {
75         var snippet = this._snippetStorage.createSnippet();
76         return this._addScriptSnippet(snippet);
77     },
78
79     /**
80      * @param {WebInspector.Snippet} snippet
81      * @return {WebInspector.UISourceCode}
82      */
83     _addScriptSnippet: function(snippet)
84     {
85         var uiSourceCode = this._workspaceProvider.addFileForURL(snippet.name, new WebInspector.SnippetContentProvider(snippet), true);
86         var scriptFile = new WebInspector.SnippetScriptFile(this, uiSourceCode);
87         uiSourceCode.setScriptFile(scriptFile);
88         this._snippetIdForUISourceCode.put(uiSourceCode, snippet.id);
89         uiSourceCode.setSourceMapping(this._snippetScriptMapping);
90         this._uiSourceCodeForSnippetId[snippet.id] = uiSourceCode;
91         return uiSourceCode;
92     },
93
94     /**
95      * @param {WebInspector.UISourceCode} uiSourceCode
96      */
97     deleteScriptSnippet: function(uiSourceCode)
98     {
99         var snippetId = this._snippetIdForUISourceCode.get(uiSourceCode);
100         var snippet = this._snippetStorage.snippetForId(snippetId);
101         this._snippetStorage.deleteSnippet(snippet);
102         this._removeBreakpoints(uiSourceCode);
103         this._releaseSnippetScript(uiSourceCode);
104         delete this._uiSourceCodeForSnippetId[snippet.id];
105         this._snippetIdForUISourceCode.remove(uiSourceCode);
106         this._workspaceProvider.removeFile(snippet.name);
107     },
108
109     /**
110      * @param {WebInspector.UISourceCode} uiSourceCode
111      * @param {string} newName
112      */
113     renameScriptSnippet: function(uiSourceCode, newName)
114     {
115         var breakpointLocations = this._removeBreakpoints(uiSourceCode);
116         var snippetId = this._snippetIdForUISourceCode.get(uiSourceCode);
117         var snippet = this._snippetStorage.snippetForId(snippetId);
118         if (!snippet || !newName || snippet.name === newName)
119             return;
120         snippet.name = newName;
121         this._restoreBreakpoints(uiSourceCode, breakpointLocations);
122         uiSourceCode.urlChanged(snippet.name);
123     },
124
125     /**
126      * @param {WebInspector.UISourceCode} uiSourceCode
127      * @param {string} newContent
128      */
129     _setScriptSnippetContent: function(uiSourceCode, newContent)
130     {
131         var snippetId = this._snippetIdForUISourceCode.get(uiSourceCode);
132         var snippet = this._snippetStorage.snippetForId(snippetId);
133         snippet.content = newContent;
134     },
135
136     /**
137      * @param {WebInspector.UISourceCode} uiSourceCode
138      */
139     _scriptSnippetEdited: function(uiSourceCode)
140     {
141         var script = this._scriptForUISourceCode.get(uiSourceCode);
142         if (!script)
143             return;
144
145         var breakpointLocations = this._removeBreakpoints(uiSourceCode);
146         var scriptUISourceCode = this._releaseSnippetScript(uiSourceCode);
147         this._restoreBreakpoints(uiSourceCode, breakpointLocations);
148         if (scriptUISourceCode)
149             this._restoreBreakpoints(scriptUISourceCode, breakpointLocations);
150     },
151
152     /**
153      * @param {string} snippetId
154      * @return {number}
155      */
156     _nextEvaluationIndex: function(snippetId)
157     {
158         var evaluationIndex = this._lastSnippetEvaluationIndexSetting.get() + 1;
159         this._lastSnippetEvaluationIndexSetting.set(evaluationIndex);
160         return evaluationIndex;
161     },
162
163     /**
164      * @param {WebInspector.UISourceCode} uiSourceCode
165      */
166     evaluateScriptSnippet: function(uiSourceCode)
167     {
168         var breakpointLocations = this._removeBreakpoints(uiSourceCode);
169         this._releaseSnippetScript(uiSourceCode);
170         this._restoreBreakpoints(uiSourceCode, breakpointLocations);
171         var snippetId = this._snippetIdForUISourceCode.get(uiSourceCode);
172         var evaluationIndex = this._nextEvaluationIndex(snippetId);
173         uiSourceCode._evaluationIndex = evaluationIndex;
174         var evaluationUrl = this._evaluationSourceURL(uiSourceCode);
175
176         var expression = uiSourceCode.workingCopy();
177         
178         // In order to stop on the breakpoints during the snippet evaluation we need to compile and run it separately.
179         // If separate compilation and execution is not supported by the port we fall back to evaluation in console.
180         // In case we don't need that since debugger is already paused.
181         // We do the same when we are stopped on the call frame  since debugger is already paused and can not stop on breakpoint anymore.
182         if (WebInspector.debuggerModel.selectedCallFrame() || !Capabilities.separateScriptCompilationAndExecutionEnabled) {
183             expression = uiSourceCode.workingCopy() + "\n//@ sourceURL=" + evaluationUrl + "\n";
184             WebInspector.evaluateInConsole(expression, true);
185             return;
186         }
187         
188         WebInspector.showConsole();
189         DebuggerAgent.compileScript(expression, evaluationUrl, compileCallback.bind(this));
190
191         /**
192          * @param {?string} error
193          * @param {string=} scriptId
194          * @param {string=} syntaxErrorMessage
195          */
196         function compileCallback(error, scriptId, syntaxErrorMessage)
197         {
198             if (!uiSourceCode || uiSourceCode._evaluationIndex !== evaluationIndex)
199                 return;
200
201             if (error) {
202                 console.error(error);
203                 return;
204             }
205
206             if (!scriptId) {
207                 var consoleMessage = WebInspector.ConsoleMessage.create(
208                         WebInspector.ConsoleMessage.MessageSource.JS,
209                         WebInspector.ConsoleMessage.MessageLevel.Error,
210                         syntaxErrorMessage || "");
211                 WebInspector.console.addMessage(consoleMessage);
212                 return;
213             }
214
215             var breakpointLocations = this._removeBreakpoints(uiSourceCode);
216             this._restoreBreakpoints(uiSourceCode, breakpointLocations);
217
218             this._runScript(scriptId);
219         }
220     },
221
222     /**
223      * @param {DebuggerAgent.ScriptId} scriptId
224      */
225     _runScript: function(scriptId)
226     {
227         var currentExecutionContext = WebInspector.runtimeModel.currentExecutionContext();
228         DebuggerAgent.runScript(scriptId, currentExecutionContext ? currentExecutionContext.id : undefined, "console", false, runCallback.bind(this));
229
230         /**
231          * @param {?string} error
232          * @param {?RuntimeAgent.RemoteObject} result
233          * @param {boolean=} wasThrown
234          */
235         function runCallback(error, result, wasThrown)
236         {
237             if (error) {
238                 console.error(error);
239                 return;
240             }
241
242             this._printRunScriptResult(result, wasThrown);
243         }
244     },
245
246     /**
247      * @param {?RuntimeAgent.RemoteObject} result
248      * @param {boolean=} wasThrown
249      */
250     _printRunScriptResult: function(result, wasThrown)
251     {
252         var level = (wasThrown ? WebInspector.ConsoleMessage.MessageLevel.Error : WebInspector.ConsoleMessage.MessageLevel.Log);
253         var message = WebInspector.ConsoleMessage.create(WebInspector.ConsoleMessage.MessageSource.JS, level, "", undefined, undefined, undefined, undefined, [result]);
254         WebInspector.console.addMessage(message)
255     },
256
257     /**
258      * @param {WebInspector.DebuggerModel.Location} rawLocation
259      * @return {WebInspector.UILocation}
260      */
261     _rawLocationToUILocation: function(rawLocation)
262     {
263         var uiSourceCode = this._uiSourceCodeForScriptId[rawLocation.scriptId];
264         return new WebInspector.UILocation(uiSourceCode, rawLocation.lineNumber, rawLocation.columnNumber || 0);
265     },
266
267     /**
268      * @param {WebInspector.UISourceCode} uiSourceCode
269      * @param {number} lineNumber
270      * @param {number} columnNumber
271      * @return {WebInspector.DebuggerModel.Location}
272      */
273     _uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber)
274     {
275         var script = this._scriptForUISourceCode.get(uiSourceCode);
276         if (!script)
277             return null;
278
279         return WebInspector.debuggerModel.createRawLocation(script, lineNumber, columnNumber);
280     },
281
282     /**
283      * @param {WebInspector.Script} script
284      */
285     _addScript: function(script)
286     {
287         var snippetId = this._snippetIdForSourceURL(script.sourceURL);
288         if (!snippetId)
289             return;
290         var uiSourceCode = this._uiSourceCodeForSnippetId[snippetId];
291
292         if (!uiSourceCode || this._evaluationSourceURL(uiSourceCode) !== script.sourceURL)
293             return;
294
295         console.assert(!this._scriptForUISourceCode.get(uiSourceCode));
296         this._uiSourceCodeForScriptId[script.scriptId] = uiSourceCode;
297         this._scriptForUISourceCode.put(uiSourceCode, script);
298         uiSourceCode.scriptFile().setHasDivergedFromVM(false);
299         script.pushSourceMapping(this._snippetScriptMapping);
300     },
301
302     /**
303      * @param {WebInspector.UISourceCode} uiSourceCode
304      * @return {Array.<Object>}
305      */
306     _removeBreakpoints: function(uiSourceCode)
307     {
308         var breakpointLocations = WebInspector.breakpointManager.breakpointLocationsForUISourceCode(uiSourceCode);
309         for (var i = 0; i < breakpointLocations.length; ++i)
310             breakpointLocations[i].breakpoint.remove();
311         return breakpointLocations;
312     },
313
314     /**
315      * @param {WebInspector.UISourceCode} uiSourceCode
316      * @param {Array.<Object>} breakpointLocations
317      */
318     _restoreBreakpoints: function(uiSourceCode, breakpointLocations)
319     {
320         for (var i = 0; i < breakpointLocations.length; ++i) {
321             var uiLocation = breakpointLocations[i].uiLocation;
322             var breakpoint = breakpointLocations[i].breakpoint;
323             WebInspector.breakpointManager.setBreakpoint(uiSourceCode, uiLocation.lineNumber, breakpoint.condition(), breakpoint.enabled());
324         }
325     },
326
327     /**
328      * @param {WebInspector.UISourceCode} uiSourceCode
329      * @return {WebInspector.UISourceCode}
330      */
331     _releaseSnippetScript: function(uiSourceCode)
332     {
333         var script = this._scriptForUISourceCode.get(uiSourceCode);
334         if (!script)
335             return null;
336
337         uiSourceCode.scriptFile().setIsDivergingFromVM(true);
338         uiSourceCode.scriptFile().setHasDivergedFromVM(true);
339         delete this._uiSourceCodeForScriptId[script.scriptId];
340         this._scriptForUISourceCode.remove(uiSourceCode);
341         delete uiSourceCode._evaluationIndex;
342         script.popSourceMapping(this._snippetScriptMapping);
343         uiSourceCode.scriptFile().setIsDivergingFromVM(false);
344         return script.rawLocationToUILocation(0, 0).uiSourceCode;
345     },
346
347     _debuggerReset: function()
348     {
349         for (var snippetId in this._uiSourceCodeForSnippetId) {
350             var uiSourceCode = this._uiSourceCodeForSnippetId[snippetId];
351             this._releaseSnippetScript(uiSourceCode);
352         }
353     },
354
355     /**
356      * @param {WebInspector.UISourceCode} uiSourceCode
357      * @return {string}
358      */
359     _evaluationSourceURL: function(uiSourceCode)
360     {
361         var evaluationSuffix = "_" + uiSourceCode._evaluationIndex;
362         var snippetId = this._snippetIdForUISourceCode.get(uiSourceCode);
363         return WebInspector.Script.snippetSourceURLPrefix + snippetId + evaluationSuffix;
364     },
365
366     /**
367      * @param {string} sourceURL
368      * @return {string|null}
369      */
370     _snippetIdForSourceURL: function(sourceURL)
371     {
372         var snippetPrefix = WebInspector.Script.snippetSourceURLPrefix;
373         if (!sourceURL.startsWith(snippetPrefix))
374             return null;
375         var splittedURL = sourceURL.substring(snippetPrefix.length).split("_");
376         var snippetId = splittedURL[0];
377         return snippetId;
378     },
379
380     reset: function()
381     {
382         /** @type {!Object.<string, WebInspector.UISourceCode>} */
383         this._uiSourceCodeForScriptId = {};
384         this._scriptForUISourceCode = new Map();
385         /** @type {!Object.<string, WebInspector.UISourceCode>} */
386         this._uiSourceCodeForSnippetId = {};
387         this._snippetIdForUISourceCode = new Map();
388         this._workspaceProvider.reset();
389         this._loadSnippets();
390     },
391
392     __proto__: WebInspector.Object.prototype
393 }
394
395 /**
396  * @constructor
397  * @implements {WebInspector.ScriptFile}
398  * @extends {WebInspector.Object}
399  * @param {WebInspector.ScriptSnippetModel} scriptSnippetModel
400  * @param {WebInspector.UISourceCode} uiSourceCode
401  */
402 WebInspector.SnippetScriptFile = function(scriptSnippetModel, uiSourceCode)
403 {
404     WebInspector.ScriptFile.call(this);
405     this._scriptSnippetModel = scriptSnippetModel;
406     this._uiSourceCode = uiSourceCode;
407     this._hasDivergedFromVM = true;
408     this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this);
409     this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this);
410 }
411
412 WebInspector.SnippetScriptFile.prototype = {
413     /**
414      * @return {boolean}
415      */
416     hasDivergedFromVM: function()
417     {
418         return this._hasDivergedFromVM;
419     },
420
421     /**
422      * @param {boolean} hasDivergedFromVM
423      */
424     setHasDivergedFromVM: function(hasDivergedFromVM)
425     {
426         this._hasDivergedFromVM = hasDivergedFromVM;
427     },
428
429     /**
430      * @return {boolean}
431      */
432     isDivergingFromVM: function()
433     {
434         return this._isDivergingFromVM;
435     },
436
437     /**
438      * @param {boolean} isDivergingFromVM
439      */
440     setIsDivergingFromVM: function(isDivergingFromVM)
441     {
442         this._isDivergingFromVM = isDivergingFromVM;
443     },
444
445     _workingCopyCommitted: function()
446     {
447         this._scriptSnippetModel._setScriptSnippetContent(this._uiSourceCode, this._uiSourceCode.workingCopy());
448     },
449
450     _workingCopyChanged: function()
451     {
452         this._scriptSnippetModel._scriptSnippetEdited(this._uiSourceCode);
453     },
454
455     __proto__: WebInspector.Object.prototype
456 }
457
458 /**
459  * @constructor
460  * @implements {WebInspector.ScriptSourceMapping}
461  * @param {WebInspector.ScriptSnippetModel} scriptSnippetModel
462  */
463 WebInspector.SnippetScriptMapping = function(scriptSnippetModel)
464 {
465     this._scriptSnippetModel = scriptSnippetModel;
466 }
467
468 WebInspector.SnippetScriptMapping.prototype = {
469     /**
470      * @param {WebInspector.RawLocation} rawLocation
471      * @return {WebInspector.UILocation}
472      */
473     rawLocationToUILocation: function(rawLocation)
474     {
475         var debuggerModelLocation = /** @type {WebInspector.DebuggerModel.Location} */(rawLocation);
476         return this._scriptSnippetModel._rawLocationToUILocation(debuggerModelLocation);
477     },
478
479     /**
480      * @param {WebInspector.UISourceCode} uiSourceCode
481      * @param {number} lineNumber
482      * @param {number} columnNumber
483      * @return {WebInspector.DebuggerModel.Location}
484      */
485     uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber)
486     {
487         return this._scriptSnippetModel._uiLocationToRawLocation(uiSourceCode, lineNumber, columnNumber);
488     },
489
490     /**
491      * @param {string} sourceURL
492      * @return {string|null}
493      */
494     snippetIdForSourceURL: function(sourceURL)
495     {
496         return this._scriptSnippetModel._snippetIdForSourceURL(sourceURL);
497     },
498
499     /**
500      * @param {WebInspector.Script} script
501      */
502     addScript: function(script)
503     {
504         this._scriptSnippetModel._addScript(script);
505     }
506 }
507
508 /**
509  * @constructor
510  * @extends {WebInspector.StaticContentProvider}
511  * @param {WebInspector.Snippet} snippet
512  */
513 WebInspector.SnippetContentProvider = function(snippet)
514 {
515     WebInspector.StaticContentProvider.call(this, WebInspector.resourceTypes.Script, snippet.content);
516 }
517
518 WebInspector.SnippetContentProvider.prototype = {
519     __proto__: WebInspector.StaticContentProvider.prototype
520 }
521
522 /**
523  * @type {?WebInspector.ScriptSnippetModel}
524  */
525 WebInspector.scriptSnippetModel = null;