Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / 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     /** @type {!Object.<string, !WebInspector.UISourceCode>} */
40     this._uiSourceCodeForScriptId = {};
41     /** @type {!Map.<!WebInspector.UISourceCode, !WebInspector.Script>} */
42     this._scriptForUISourceCode = new Map();
43     /** @type {!Object.<string, !WebInspector.UISourceCode>} */
44     this._uiSourceCodeForSnippetId = {};
45     /** @type {!Map.<!WebInspector.UISourceCode, string>} */
46     this._snippetIdForUISourceCode = new Map();
47     
48     this._snippetStorage = new WebInspector.SnippetStorage("script", "Script snippet #");
49     this._lastSnippetEvaluationIndexSetting = WebInspector.settings.createSetting("lastSnippetEvaluationIndex", 0);
50     this._snippetScriptMapping = new WebInspector.SnippetScriptMapping(this);
51     this._projectDelegate = new WebInspector.SnippetsProjectDelegate(this);
52     this._project = this._workspace.addProject(this._projectDelegate);
53     this.reset();
54     WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.GlobalObjectCleared, this._debuggerReset, this);
55 }
56
57 WebInspector.ScriptSnippetModel.prototype = {
58     /**
59      * @return {!WebInspector.SnippetScriptMapping}
60      */
61     get scriptMapping()
62     {
63         return this._snippetScriptMapping;
64     },
65
66     /**
67      * @return {!WebInspector.Project}
68      */
69     project: function()
70     {
71         return this._project;
72     },
73
74     _loadSnippets: function()
75     {
76         var snippets = this._snippetStorage.snippets();
77         for (var i = 0; i < snippets.length; ++i)
78             this._addScriptSnippet(snippets[i]);
79     },
80
81     /**
82      * @param {string} content
83      * @return {string}
84      */
85     createScriptSnippet: function(content)
86     {
87         var snippet = this._snippetStorage.createSnippet();
88         snippet.content = content;
89         return this._addScriptSnippet(snippet);
90     },
91
92     /**
93      * @param {!WebInspector.Snippet} snippet
94      * @return {string}
95      */
96     _addScriptSnippet: function(snippet)
97     {
98         var path = this._projectDelegate.addSnippet(snippet.name, new WebInspector.SnippetContentProvider(snippet));
99         var uiSourceCode = this._workspace.uiSourceCode(this._projectDelegate.id(), path);
100         if (!uiSourceCode) {
101             console.assert(uiSourceCode);
102             return "";
103         }
104         var scriptFile = new WebInspector.SnippetScriptFile(this, uiSourceCode);
105         uiSourceCode.setScriptFile(scriptFile);
106         this._snippetIdForUISourceCode.put(uiSourceCode, snippet.id);
107         uiSourceCode.setSourceMapping(this._snippetScriptMapping);
108         this._uiSourceCodeForSnippetId[snippet.id] = uiSourceCode;
109         return path;
110     },
111
112     /**
113      * @param {string} path
114      */
115     deleteScriptSnippet: function(path)
116     {
117         var uiSourceCode = this._workspace.uiSourceCode(this._projectDelegate.id(), path);
118         if (!uiSourceCode)
119             return;
120         var snippetId = this._snippetIdForUISourceCode.get(uiSourceCode) || "";
121         var snippet = this._snippetStorage.snippetForId(snippetId);
122         this._snippetStorage.deleteSnippet(snippet);
123         this._removeBreakpoints(uiSourceCode);
124         this._releaseSnippetScript(uiSourceCode);
125         delete this._uiSourceCodeForSnippetId[snippet.id];
126         this._snippetIdForUISourceCode.remove(uiSourceCode);
127         this._projectDelegate.removeFile(snippet.name);
128     },
129
130     /**
131      * @param {string} name
132      * @param {string} newName
133      * @param {function(boolean, string=)} callback
134      */
135     renameScriptSnippet: function(name, newName, callback)
136     {
137         newName = newName.trim();
138         if (!newName || newName.indexOf("/") !== -1 || name === newName || this._snippetStorage.snippetForName(newName)) {
139             callback(false);
140             return;
141         }
142         var snippet = this._snippetStorage.snippetForName(name);
143         console.assert(snippet, "Snippet '" + name + "' was not found.");
144         var uiSourceCode = this._uiSourceCodeForSnippetId[snippet.id];
145         console.assert(uiSourceCode, "No uiSourceCode was found for snippet '" + name + "'.");
146
147         var breakpointLocations = this._removeBreakpoints(uiSourceCode);
148         snippet.name = newName;
149         this._restoreBreakpoints(uiSourceCode, breakpointLocations);
150         callback(true, newName);
151     },
152
153     /**
154      * @param {string} name
155      * @param {string} newContent
156      */
157     _setScriptSnippetContent: function(name, newContent)
158     {
159         var snippet = this._snippetStorage.snippetForName(name);
160         snippet.content = newContent;
161     },
162
163     /**
164      * @param {!WebInspector.UISourceCode} uiSourceCode
165      */
166     _scriptSnippetEdited: function(uiSourceCode)
167     {
168         var script = this._scriptForUISourceCode.get(uiSourceCode);
169         if (!script)
170             return;
171
172         var breakpointLocations = this._removeBreakpoints(uiSourceCode);
173         this._releaseSnippetScript(uiSourceCode);
174         this._restoreBreakpoints(uiSourceCode, breakpointLocations);
175         var scriptUISourceCode = script.rawLocationToUILocation(0, 0).uiSourceCode;
176         if (scriptUISourceCode)
177             this._restoreBreakpoints(scriptUISourceCode, breakpointLocations);
178     },
179
180     /**
181      * @param {string} snippetId
182      * @return {number}
183      */
184     _nextEvaluationIndex: function(snippetId)
185     {
186         var evaluationIndex = this._lastSnippetEvaluationIndexSetting.get() + 1;
187         this._lastSnippetEvaluationIndexSetting.set(evaluationIndex);
188         return evaluationIndex;
189     },
190
191     /**
192      * @param {!WebInspector.UISourceCode} uiSourceCode
193      */
194     evaluateScriptSnippet: function(uiSourceCode)
195     {
196         var breakpointLocations = this._removeBreakpoints(uiSourceCode);
197         this._releaseSnippetScript(uiSourceCode);
198         this._restoreBreakpoints(uiSourceCode, breakpointLocations);
199         var snippetId = this._snippetIdForUISourceCode.get(uiSourceCode) || "";
200         var evaluationIndex = this._nextEvaluationIndex(snippetId);
201         uiSourceCode._evaluationIndex = evaluationIndex;
202         var evaluationUrl = this._evaluationSourceURL(uiSourceCode);
203
204         var expression = uiSourceCode.workingCopy();
205         
206         // In order to stop on the breakpoints during the snippet evaluation we need to compile and run it separately.
207         // If separate compilation and execution is not supported by the port we fall back to evaluation in console.
208         // In case we don't need that since debugger is already paused.
209         // We do the same when we are stopped on the call frame  since debugger is already paused and can not stop on breakpoint anymore.
210         if (WebInspector.debuggerModel.selectedCallFrame()) {
211             expression = uiSourceCode.workingCopy() + "\n//# sourceURL=" + evaluationUrl + "\n";
212             WebInspector.evaluateInConsole(expression, true);
213             return;
214         }
215         
216         WebInspector.showConsole();
217         DebuggerAgent.compileScript(expression, evaluationUrl, compileCallback.bind(this));
218
219         /**
220          * @param {?string} error
221          * @param {string=} scriptId
222          * @param {string=} syntaxErrorMessage
223          * @this {WebInspector.ScriptSnippetModel}
224          */
225         function compileCallback(error, scriptId, syntaxErrorMessage)
226         {
227             if (!uiSourceCode || uiSourceCode._evaluationIndex !== evaluationIndex)
228                 return;
229
230             if (error) {
231                 console.error(error);
232                 return;
233             }
234
235             if (!scriptId) {
236                 var consoleMessage = WebInspector.ConsoleMessage.create(
237                         WebInspector.ConsoleMessage.MessageSource.JS,
238                         WebInspector.ConsoleMessage.MessageLevel.Error,
239                         syntaxErrorMessage || "");
240                 WebInspector.console.addMessage(consoleMessage);
241                 return;
242             }
243
244             var breakpointLocations = this._removeBreakpoints(uiSourceCode);
245             this._restoreBreakpoints(uiSourceCode, breakpointLocations);
246
247             this._runScript(scriptId);
248         }
249     },
250
251     /**
252      * @param {!DebuggerAgent.ScriptId} scriptId
253      */
254     _runScript: function(scriptId)
255     {
256         var currentExecutionContext = WebInspector.runtimeModel.currentExecutionContext();
257         DebuggerAgent.runScript(scriptId, currentExecutionContext ? currentExecutionContext.id : undefined, "console", false, runCallback.bind(this));
258
259         /**
260          * @param {?string} error
261          * @param {?RuntimeAgent.RemoteObject} result
262          * @param {boolean=} wasThrown
263          * @this {WebInspector.ScriptSnippetModel}
264          */
265         function runCallback(error, result, wasThrown)
266         {
267             if (error) {
268                 console.error(error);
269                 return;
270             }
271
272             this._printRunScriptResult(result, wasThrown);
273         }
274     },
275
276     /**
277      * @param {?RuntimeAgent.RemoteObject} result
278      * @param {boolean=} wasThrown
279      */
280     _printRunScriptResult: function(result, wasThrown)
281     {
282         var level = (wasThrown ? WebInspector.ConsoleMessage.MessageLevel.Error : WebInspector.ConsoleMessage.MessageLevel.Log);
283         var message = WebInspector.ConsoleMessage.create(WebInspector.ConsoleMessage.MessageSource.JS, level, "", undefined, undefined, undefined, undefined, undefined, [result]);
284         WebInspector.console.addMessage(message)
285     },
286
287     /**
288      * @param {!WebInspector.DebuggerModel.Location} rawLocation
289      * @return {?WebInspector.UILocation}
290      */
291     _rawLocationToUILocation: function(rawLocation)
292     {
293         var uiSourceCode = this._uiSourceCodeForScriptId[rawLocation.scriptId];
294         if (!uiSourceCode)
295             return null;
296         return new WebInspector.UILocation(uiSourceCode, rawLocation.lineNumber, rawLocation.columnNumber || 0);
297     },
298
299     /**
300      * @param {!WebInspector.UISourceCode} uiSourceCode
301      * @param {number} lineNumber
302      * @param {number} columnNumber
303      * @return {?WebInspector.DebuggerModel.Location}
304      */
305     _uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber)
306     {
307         var script = this._scriptForUISourceCode.get(uiSourceCode);
308         if (!script)
309             return null;
310
311         return WebInspector.debuggerModel.createRawLocation(script, lineNumber, columnNumber);
312     },
313
314     /**
315      * @param {!WebInspector.Script} script
316      */
317     _addScript: function(script)
318     {
319         var snippetId = this._snippetIdForSourceURL(script.sourceURL);
320         if (!snippetId)
321             return;
322         var uiSourceCode = this._uiSourceCodeForSnippetId[snippetId];
323
324         if (!uiSourceCode || this._evaluationSourceURL(uiSourceCode) !== script.sourceURL)
325             return;
326
327         console.assert(!this._scriptForUISourceCode.get(uiSourceCode));
328         this._uiSourceCodeForScriptId[script.scriptId] = uiSourceCode;
329         this._scriptForUISourceCode.put(uiSourceCode, script);
330         uiSourceCode.scriptFile().setHasDivergedFromVM(false);
331         script.pushSourceMapping(this._snippetScriptMapping);
332     },
333
334     /**
335      * @param {!WebInspector.UISourceCode} uiSourceCode
336      * @return {!Array.<!Object>}
337      */
338     _removeBreakpoints: function(uiSourceCode)
339     {
340         var breakpointLocations = WebInspector.breakpointManager.breakpointLocationsForUISourceCode(uiSourceCode);
341         for (var i = 0; i < breakpointLocations.length; ++i)
342             breakpointLocations[i].breakpoint.remove();
343         return breakpointLocations;
344     },
345
346     /**
347      * @param {!WebInspector.UISourceCode} uiSourceCode
348      * @param {!Array.<!Object>} breakpointLocations
349      */
350     _restoreBreakpoints: function(uiSourceCode, breakpointLocations)
351     {
352         for (var i = 0; i < breakpointLocations.length; ++i) {
353             var uiLocation = breakpointLocations[i].uiLocation;
354             var breakpoint = breakpointLocations[i].breakpoint;
355             WebInspector.breakpointManager.setBreakpoint(uiSourceCode, uiLocation.lineNumber, uiLocation.columnNumber, breakpoint.condition(), breakpoint.enabled());
356         }
357     },
358
359     /**
360      * @param {!WebInspector.UISourceCode} uiSourceCode
361      */
362     _releaseSnippetScript: function(uiSourceCode)
363     {
364         var script = this._scriptForUISourceCode.get(uiSourceCode);
365         if (!script)
366             return null;
367
368         uiSourceCode.scriptFile().setIsDivergingFromVM(true);
369         uiSourceCode.scriptFile().setHasDivergedFromVM(true);
370         delete this._uiSourceCodeForScriptId[script.scriptId];
371         this._scriptForUISourceCode.remove(uiSourceCode);
372         delete uiSourceCode._evaluationIndex;
373         uiSourceCode.scriptFile().setIsDivergingFromVM(false);
374     },
375
376     _debuggerReset: function()
377     {
378         for (var snippetId in this._uiSourceCodeForSnippetId) {
379             var uiSourceCode = this._uiSourceCodeForSnippetId[snippetId];
380             this._releaseSnippetScript(uiSourceCode);
381         }
382     },
383
384     /**
385      * @param {!WebInspector.UISourceCode} uiSourceCode
386      * @return {string}
387      */
388     _evaluationSourceURL: function(uiSourceCode)
389     {
390         var evaluationSuffix = "_" + uiSourceCode._evaluationIndex;
391         var snippetId = this._snippetIdForUISourceCode.get(uiSourceCode);
392         return WebInspector.Script.snippetSourceURLPrefix + snippetId + evaluationSuffix;
393     },
394
395     /**
396      * @param {string} sourceURL
397      * @return {?string}
398      */
399     _snippetIdForSourceURL: function(sourceURL)
400     {
401         var snippetPrefix = WebInspector.Script.snippetSourceURLPrefix;
402         if (!sourceURL.startsWith(snippetPrefix))
403             return null;
404         var splitURL = sourceURL.substring(snippetPrefix.length).split("_");
405         var snippetId = splitURL[0];
406         return snippetId;
407     },
408
409     reset: function()
410     {
411         /** @type {!Object.<string, !WebInspector.UISourceCode>} */
412         this._uiSourceCodeForScriptId = {};
413         this._scriptForUISourceCode = new Map();
414         /** @type {!Object.<string, !WebInspector.UISourceCode>} */
415         this._uiSourceCodeForSnippetId = {};
416         this._snippetIdForUISourceCode = new Map();
417         this._projectDelegate.reset();
418         this._loadSnippets();
419     },
420
421     __proto__: WebInspector.Object.prototype
422 }
423
424 /**
425  * @constructor
426  * @implements {WebInspector.ScriptFile}
427  * @extends {WebInspector.Object}
428  * @param {!WebInspector.ScriptSnippetModel} scriptSnippetModel
429  * @param {!WebInspector.UISourceCode} uiSourceCode
430  */
431 WebInspector.SnippetScriptFile = function(scriptSnippetModel, uiSourceCode)
432 {
433     WebInspector.ScriptFile.call(this);
434     this._scriptSnippetModel = scriptSnippetModel;
435     this._uiSourceCode = uiSourceCode;
436     this._hasDivergedFromVM = true;
437     this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this);
438 }
439
440 WebInspector.SnippetScriptFile.prototype = {
441     /**
442      * @return {boolean}
443      */
444     hasDivergedFromVM: function()
445     {
446         return this._hasDivergedFromVM;
447     },
448
449     /**
450      * @param {boolean} hasDivergedFromVM
451      */
452     setHasDivergedFromVM: function(hasDivergedFromVM)
453     {
454         this._hasDivergedFromVM = hasDivergedFromVM;
455     },
456
457     /**
458      * @return {boolean}
459      */
460     isDivergingFromVM: function()
461     {
462         return this._isDivergingFromVM;
463     },
464
465     checkMapping: function()
466     {
467     },
468
469     /**
470      * @return {boolean}
471      */
472     isMergingToVM: function()
473     {
474         return false;
475     },
476
477     /**
478      * @param {boolean} isDivergingFromVM
479      */
480     setIsDivergingFromVM: function(isDivergingFromVM)
481     {
482         this._isDivergingFromVM = isDivergingFromVM;
483     },
484
485     _workingCopyChanged: function()
486     {
487         this._scriptSnippetModel._scriptSnippetEdited(this._uiSourceCode);
488     },
489
490     __proto__: WebInspector.Object.prototype
491 }
492
493 /**
494  * @constructor
495  * @implements {WebInspector.ScriptSourceMapping}
496  * @param {!WebInspector.ScriptSnippetModel} scriptSnippetModel
497  */
498 WebInspector.SnippetScriptMapping = function(scriptSnippetModel)
499 {
500     this._scriptSnippetModel = scriptSnippetModel;
501 }
502
503 WebInspector.SnippetScriptMapping.prototype = {
504     /**
505      * @param {!WebInspector.RawLocation} rawLocation
506      * @return {?WebInspector.UILocation}
507      */
508     rawLocationToUILocation: function(rawLocation)
509     {
510         var debuggerModelLocation = /** @type {!WebInspector.DebuggerModel.Location} */(rawLocation);
511         return this._scriptSnippetModel._rawLocationToUILocation(debuggerModelLocation);
512     },
513
514     /**
515      * @param {!WebInspector.UISourceCode} uiSourceCode
516      * @param {number} lineNumber
517      * @param {number} columnNumber
518      * @return {?WebInspector.DebuggerModel.Location}
519      */
520     uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber)
521     {
522         return this._scriptSnippetModel._uiLocationToRawLocation(uiSourceCode, lineNumber, columnNumber);
523     },
524
525     /**
526      * @param {string} sourceURL
527      * @return {?string}
528      */
529     snippetIdForSourceURL: function(sourceURL)
530     {
531         return this._scriptSnippetModel._snippetIdForSourceURL(sourceURL);
532     },
533
534     /**
535      * @param {!WebInspector.Script} script
536      */
537     addScript: function(script)
538     {
539         this._scriptSnippetModel._addScript(script);
540     }
541 }
542
543 /**
544  * @constructor
545  * @implements {WebInspector.ContentProvider}
546  * @param {!WebInspector.Snippet} snippet
547  */
548 WebInspector.SnippetContentProvider = function(snippet)
549 {
550     this._snippet = snippet;
551 }
552
553 WebInspector.SnippetContentProvider.prototype = {
554     /**
555      * @return {string}
556      */
557     contentURL: function()
558     {
559         return "";
560     },
561
562     /**
563      * @return {!WebInspector.ResourceType}
564      */
565     contentType: function()
566     {
567         return WebInspector.resourceTypes.Script;
568     },
569
570     /**
571      * @param {function(?string)} callback
572      */
573     requestContent: function(callback)
574     {
575         callback(this._snippet.content);
576     },
577
578     /**
579      * @param {string} query
580      * @param {boolean} caseSensitive
581      * @param {boolean} isRegex
582      * @param {function(!Array.<!WebInspector.ContentProvider.SearchMatch>)} callback
583      */
584     searchInContent: function(query, caseSensitive, isRegex, callback)
585     {
586         /**
587          * @this {WebInspector.SnippetContentProvider}
588          */
589         function performSearch()
590         {
591             callback(WebInspector.ContentProvider.performSearchInContent(this._snippet.content, query, caseSensitive, isRegex));
592         }
593
594         // searchInContent should call back later.
595         window.setTimeout(performSearch.bind(this), 0);
596     }
597 }
598
599 /**
600  * @constructor
601  * @extends {WebInspector.ContentProviderBasedProjectDelegate}
602  * @param {!WebInspector.ScriptSnippetModel} model
603  */
604 WebInspector.SnippetsProjectDelegate = function(model)
605 {
606     WebInspector.ContentProviderBasedProjectDelegate.call(this, WebInspector.projectTypes.Snippets);
607     this._model = model;
608 }
609
610 WebInspector.SnippetsProjectDelegate.prototype = {
611     /**
612      * @override
613      * @return {string}
614      */
615     id: function()
616     {
617         return WebInspector.projectTypes.Snippets + ":";
618     },
619
620     /**
621      * @param {string} name
622      * @param {!WebInspector.ContentProvider} contentProvider
623      * @return {string}
624      */
625     addSnippet: function(name, contentProvider)
626     {
627         return this.addContentProvider("", name, name, contentProvider, true, false);
628     },
629
630     /**
631      * @return {boolean}
632      */
633     canSetFileContent: function()
634     {
635         return true;
636     },
637
638     /**
639      * @param {string} path
640      * @param {string} newContent
641      * @param {function(?string)} callback
642      */
643     setFileContent: function(path, newContent, callback)
644     {
645         this._model._setScriptSnippetContent(path, newContent);
646         callback("");
647     },
648
649     /**
650      * @return {boolean}
651      */
652     canRename: function()
653     {
654         return true;
655     },
656
657     /**
658      * @param {string} path
659      * @param {string} newName
660      * @param {function(boolean, string=)} callback
661      */
662     performRename: function(path, newName, callback)
663     {
664         this._model.renameScriptSnippet(path, newName, callback);
665     },
666
667     /**
668      * @param {string} path
669      * @param {?string} name
670      * @param {string} content
671      * @param {function(?string)} callback
672      */
673     createFile: function(path, name, content, callback)
674     {
675         var filePath = this._model.createScriptSnippet(content);
676         callback(filePath);
677     },
678
679     /**
680      * @param {string} path
681      */
682     deleteFile: function(path)
683     {
684         this._model.deleteScriptSnippet(path);
685     },
686
687     __proto__: WebInspector.ContentProviderBasedProjectDelegate.prototype
688 }
689
690 /**
691  * @type {!WebInspector.ScriptSnippetModel}
692  */
693 WebInspector.scriptSnippetModel;