• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 */
36WebInspector.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
57WebInspector.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, 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 */
431WebInspector.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
440WebInspector.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 */
498WebInspector.SnippetScriptMapping = function(scriptSnippetModel)
499{
500    this._scriptSnippetModel = scriptSnippetModel;
501}
502
503WebInspector.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 */
548WebInspector.SnippetContentProvider = function(snippet)
549{
550    this._snippet = snippet;
551}
552
553WebInspector.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    __proto__: WebInspector.ContentProvider.prototype
599}
600
601/**
602 * @constructor
603 * @extends {WebInspector.ContentProviderBasedProjectDelegate}
604 * @param {!WebInspector.ScriptSnippetModel} model
605 */
606WebInspector.SnippetsProjectDelegate = function(model)
607{
608    WebInspector.ContentProviderBasedProjectDelegate.call(this, WebInspector.projectTypes.Snippets);
609    this._model = model;
610}
611
612WebInspector.SnippetsProjectDelegate.prototype = {
613    /**
614     * @override
615     * @return {string}
616     */
617    id: function()
618    {
619        return WebInspector.projectTypes.Snippets + ":";
620    },
621
622    /**
623     * @param {string} name
624     * @param {!WebInspector.ContentProvider} contentProvider
625     * @return {string}
626     */
627    addSnippet: function(name, contentProvider)
628    {
629        return this.addContentProvider("", name, name, contentProvider, true, false);
630    },
631
632    /**
633     * @return {boolean}
634     */
635    canSetFileContent: function()
636    {
637        return true;
638    },
639
640    /**
641     * @param {string} path
642     * @param {string} newContent
643     * @param {function(?string)} callback
644     */
645    setFileContent: function(path, newContent, callback)
646    {
647        this._model._setScriptSnippetContent(path, newContent);
648        callback("");
649    },
650
651    /**
652     * @return {boolean}
653     */
654    canRename: function()
655    {
656        return true;
657    },
658
659    /**
660     * @param {string} path
661     * @param {string} newName
662     * @param {function(boolean, string=)} callback
663     */
664    performRename: function(path, newName, callback)
665    {
666        this._model.renameScriptSnippet(path, newName, callback);
667    },
668
669    /**
670     * @param {string} path
671     * @param {?string} name
672     * @param {string} content
673     * @param {function(?string)} callback
674     */
675    createFile: function(path, name, content, callback)
676    {
677        var filePath = this._model.createScriptSnippet(content);
678        callback(filePath);
679    },
680
681    /**
682     * @param {string} path
683     */
684    deleteFile: function(path)
685    {
686        this._model.deleteScriptSnippet(path);
687    },
688
689    __proto__: WebInspector.ContentProviderBasedProjectDelegate.prototype
690}
691
692/**
693 * @type {!WebInspector.ScriptSnippetModel}
694 */
695WebInspector.scriptSnippetModel;
696