49e0955a2779f5503eb75a41d1a3d4647604d614
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / sources / ScriptFormatterEditorAction.js
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 /**
6  * @constructor
7  * @implements {WebInspector.DebuggerSourceMapping}
8  * @param {!WebInspector.Target} target
9  * @param {!WebInspector.ScriptFormatterEditorAction} editorAction
10  */
11 WebInspector.FormatterScriptMapping = function(target, editorAction)
12 {
13     this._target = target;
14     this._editorAction = editorAction;
15 }
16
17 WebInspector.FormatterScriptMapping.prototype = {
18     /**
19      * @param {!WebInspector.DebuggerModel.Location} rawLocation
20      * @return {?WebInspector.UILocation}
21      */
22     rawLocationToUILocation: function(rawLocation)
23     {
24         var debuggerModelLocation = /** @type {!WebInspector.DebuggerModel.Location} */ (rawLocation);
25         var script = debuggerModelLocation.script();
26         var uiSourceCode = this._editorAction._uiSourceCodes.get(script);
27         if (!uiSourceCode)
28             return null;
29
30         var formatData = this._editorAction._formatData.get(uiSourceCode);
31         if (!formatData)
32             return null;
33         var mapping = formatData.mapping;
34         var lineNumber = debuggerModelLocation.lineNumber;
35         var columnNumber = debuggerModelLocation.columnNumber || 0;
36         var formattedLocation = mapping.originalToFormatted(lineNumber, columnNumber);
37         return uiSourceCode.uiLocation(formattedLocation[0], formattedLocation[1]);
38     },
39
40     /**
41      * @param {!WebInspector.UISourceCode} uiSourceCode
42      * @param {number} lineNumber
43      * @param {number} columnNumber
44      * @return {?WebInspector.DebuggerModel.Location}
45      */
46     uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber)
47     {
48         var formatData = this._editorAction._formatData.get(uiSourceCode);
49         if (!formatData)
50             return null;
51         var originalLocation = formatData.mapping.formattedToOriginal(lineNumber, columnNumber);
52         for (var i = 0; i < formatData.scripts.length; ++i) {
53             if (formatData.scripts[i].target() === this._target)
54                 return this._target.debuggerModel.createRawLocation(formatData.scripts[i], originalLocation[0], originalLocation[1])
55         }
56         return null;
57     },
58
59     /**
60      * @return {boolean}
61      */
62     isIdentity: function()
63     {
64         return false;
65     },
66
67     /**
68      * @param {!WebInspector.UISourceCode} uiSourceCode
69      * @param {number} lineNumber
70      * @return {boolean}
71      */
72     uiLineHasMapping: function(uiSourceCode, lineNumber)
73     {
74         return true;
75     }
76
77 }
78
79 /**
80  * @constructor
81  * @param {string} projectId
82  * @param {string} path
83  * @param {!WebInspector.FormatterSourceMapping} mapping
84  * @param {!Array.<!WebInspector.Script>} scripts
85  */
86 WebInspector.FormatterScriptMapping.FormatData = function(projectId, path, mapping, scripts)
87 {
88     this.projectId = projectId;
89     this.path = path;
90     this.mapping = mapping;
91     this.scripts = scripts;
92 }
93
94 /**
95  * @constructor
96  * @param {!WebInspector.Workspace} workspace
97  * @param {string} id
98  * @extends {WebInspector.ContentProviderBasedProjectDelegate}
99  */
100 WebInspector.FormatterProjectDelegate = function(workspace, id)
101 {
102     WebInspector.ContentProviderBasedProjectDelegate.call(this, workspace, id, WebInspector.projectTypes.Formatter);
103 }
104
105 WebInspector.FormatterProjectDelegate.prototype = {
106     /**
107      * @return {string}
108      */
109     displayName: function()
110     {
111         return "formatter";
112     },
113
114     /**
115      * @param {string} name
116      * @param {string} sourceURL
117      * @param {!WebInspector.ResourceType} contentType
118      * @param {string} content
119      * @return {string}
120      */
121     _addFormatted: function(name, sourceURL, contentType, content)
122     {
123         var contentProvider = new WebInspector.StaticContentProvider(contentType, content);
124         return this.addContentProvider(sourceURL, name + ":formatted", "deobfuscated:" + sourceURL, contentProvider);
125     },
126
127     /**
128      * @param {string} path
129      */
130     _removeFormatted: function(path)
131     {
132         this.removeFile(path);
133     },
134
135     __proto__: WebInspector.ContentProviderBasedProjectDelegate.prototype
136 }
137
138 /**
139  * @constructor
140  * @implements {WebInspector.SourcesView.EditorAction}
141  * @implements {WebInspector.TargetManager.Observer}
142  */
143 WebInspector.ScriptFormatterEditorAction = function()
144 {
145     this._projectId = "formatter:";
146     this._projectDelegate = new WebInspector.FormatterProjectDelegate(WebInspector.workspace, this._projectId);
147
148     /** @type {!Map.<!WebInspector.Script, !WebInspector.UISourceCode>} */
149     this._uiSourceCodes = new Map();
150     /** @type {!StringMap.<string>} */
151     this._formattedPaths = new StringMap();
152     /** @type {!Map.<!WebInspector.UISourceCode, !WebInspector.FormatterScriptMapping.FormatData>} */
153     this._formatData = new Map();
154
155     /** @type {!StringSet} */
156     this._pathsToFormatOnLoad = new StringSet();
157
158     /** @type {!Map.<!WebInspector.Target, !WebInspector.FormatterScriptMapping>} */
159     this._scriptMappingByTarget = new Map();
160     this._workspace = WebInspector.workspace;
161     WebInspector.targetManager.observeTargets(this);
162 }
163
164 WebInspector.ScriptFormatterEditorAction.prototype = {
165     /**
166      * @param {!WebInspector.Target} target
167      */
168     targetAdded: function(target)
169     {
170         this._scriptMappingByTarget.set(target, new WebInspector.FormatterScriptMapping(target, this));
171         target.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.GlobalObjectCleared, this._debuggerReset, this);
172     },
173
174     /**
175      * @param {!WebInspector.Target} target
176      */
177     targetRemoved: function(target)
178     {
179         this._scriptMappingByTarget.remove(target);
180         this._cleanForTarget(target);
181         target.debuggerModel.removeEventListener(WebInspector.DebuggerModel.Events.GlobalObjectCleared, this._debuggerReset, this);
182     },
183
184     /**
185      * @param {!WebInspector.Event} event
186      */
187     _editorSelected: function(event)
188     {
189         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data);
190         this._updateButton(uiSourceCode);
191
192         var path = uiSourceCode.project().id() + ":" + uiSourceCode.path();
193         if (this._isFormatableScript(uiSourceCode) && uiSourceCode.url && this._pathsToFormatOnLoad.contains(path) && !this._formattedPaths.get(path))
194             this._formatUISourceCodeScript(uiSourceCode);
195     },
196
197     /**
198      * @param {!WebInspector.Event} event
199      */
200     _editorClosed: function(event)
201     {
202         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data.uiSourceCode);
203         var wasSelected = /** @type {boolean} */ (event.data.wasSelected);
204
205         if (wasSelected)
206             this._updateButton(null);
207         this._discardFormattedUISourceCodeScript(uiSourceCode);
208     },
209
210     /**
211      * @param {?WebInspector.UISourceCode} uiSourceCode
212      */
213     _updateButton: function(uiSourceCode)
214     {
215         this._button.element.classList.toggle("hidden", !this._isFormatableScript(uiSourceCode));
216     },
217
218     /**
219      * @param {!WebInspector.SourcesView} sourcesView
220      * @return {!Element}
221      */
222     button: function(sourcesView)
223     {
224         if (this._button)
225             return this._button.element;
226
227         this._sourcesView = sourcesView;
228         this._sourcesView.addEventListener(WebInspector.SourcesView.Events.EditorSelected, this._editorSelected.bind(this));
229         this._sourcesView.addEventListener(WebInspector.SourcesView.Events.EditorClosed, this._editorClosed.bind(this));
230
231         this._button = new WebInspector.StatusBarButton(WebInspector.UIString("Pretty print"), "sources-toggle-pretty-print-status-bar-item");
232         this._button.toggled = false;
233         this._button.addEventListener("click", this._toggleFormatScriptSource, this);
234         this._updateButton(null);
235
236         return this._button.element;
237     },
238
239     /**
240      * @param {?WebInspector.UISourceCode} uiSourceCode
241      * @return {boolean}
242      */
243     _isFormatableScript: function(uiSourceCode)
244     {
245         if (!uiSourceCode)
246             return false;
247         var supportedProjectTypes = [WebInspector.projectTypes.Network, WebInspector.projectTypes.Debugger, WebInspector.projectTypes.ContentScripts];
248         if (supportedProjectTypes.indexOf(uiSourceCode.project().type()) === -1)
249             return false;
250         var contentType = uiSourceCode.contentType();
251         return contentType === WebInspector.resourceTypes.Script || contentType === WebInspector.resourceTypes.Document;
252     },
253
254     _toggleFormatScriptSource: function()
255     {
256         var uiSourceCode = this._sourcesView.currentUISourceCode();
257         if (!this._isFormatableScript(uiSourceCode))
258             return;
259         this._formatUISourceCodeScript(uiSourceCode);
260
261         WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, {
262             action: WebInspector.UserMetrics.UserActionNames.TogglePrettyPrint,
263             enabled: true,
264             url: uiSourceCode.originURL()
265         });
266     },
267
268     /**
269      * @param {!WebInspector.UISourceCode} uiSourceCode
270      * @param {!WebInspector.UISourceCode} formattedUISourceCode
271      * @param {!WebInspector.FormatterSourceMapping} mapping
272      * @private
273      */
274     _showIfNeeded: function(uiSourceCode, formattedUISourceCode, mapping)
275     {
276         if (uiSourceCode !== this._sourcesView.currentUISourceCode())
277             return;
278         var sourceFrame = this._sourcesView.viewForFile(uiSourceCode);
279         var start = [0, 0];
280         if (sourceFrame) {
281             var selection = sourceFrame.selection();
282             start = mapping.originalToFormatted(selection.startLine, selection.startColumn);
283         }
284         this._sourcesView.showSourceLocation(formattedUISourceCode, start[0], start[1]);
285         this._updateButton(formattedUISourceCode);
286     },
287
288     /**
289      * @param {!WebInspector.UISourceCode} formattedUISourceCode
290      */
291     _discardFormattedUISourceCodeScript: function(formattedUISourceCode)
292     {
293         var formatData = this._formatData.get(formattedUISourceCode);
294         if (!formatData)
295             return;
296
297         this._formatData.remove(formattedUISourceCode);
298         var path = formatData.projectId + ":" + formatData.path;
299         this._formattedPaths.remove(path);
300         this._pathsToFormatOnLoad.remove(path);
301         for (var i = 0; i < formatData.scripts.length; ++i) {
302             this._uiSourceCodes.remove(formatData.scripts[i]);
303             WebInspector.debuggerWorkspaceBinding.popSourceMapping(formatData.scripts[i]);
304         }
305         this._projectDelegate._removeFormatted(formattedUISourceCode.path());
306     },
307
308     /**
309      * @param {!WebInspector.Target} target
310      */
311     _cleanForTarget: function(target)
312     {
313         var uiSourceCodes = this._formatData.keys();
314         for (var i = 0; i < uiSourceCodes.length; ++i) {
315             WebInspector.debuggerWorkspaceBinding.setSourceMapping(target, uiSourceCodes[i], null);
316             var formatData = this._formatData.get(uiSourceCodes[i]);
317             var scripts = [];
318             for (var j = 0; j < formatData.scripts.length; ++j) {
319                 if (formatData.scripts[j].target() === target)
320                     this._uiSourceCodes.remove(formatData.scripts[j]);
321                 else
322                     scripts.push(formatData.scripts[j]);
323             }
324
325             if (scripts.length)
326                 formatData.scripts = scripts;
327             else {
328                 this._formattedPaths.remove(formatData.projectId + ":" + formatData.path);
329                 this._formatData.remove(uiSourceCodes[i]);
330                 this._projectDelegate._removeFormatted(uiSourceCodes[i].path());
331             }
332         }
333     },
334
335     /**
336      * @param {!WebInspector.Event} event
337      */
338     _debuggerReset: function(event)
339     {
340         var debuggerModel = /** @type {!WebInspector.DebuggerModel} */ (event.target);
341         this._cleanForTarget(debuggerModel.target());
342     },
343
344     /**
345      * @param {!WebInspector.UISourceCode} uiSourceCode
346      * @return {!Array.<!WebInspector.Script>}
347      */
348     _scriptsForUISourceCode: function(uiSourceCode)
349     {
350         /**
351          * @param {!WebInspector.Script} script
352          * @return {boolean}
353          */
354         function isInlineScript(script)
355         {
356             return script.isInlineScript() && !script.hasSourceURL;
357         }
358
359         if (uiSourceCode.contentType() === WebInspector.resourceTypes.Document) {
360             var scripts = [];
361             var targets = WebInspector.targetManager.targets();
362             for (var i = 0; i < targets.length; ++i)
363                 scripts.pushAll(targets[i].debuggerModel.scriptsForSourceURL(uiSourceCode.url));
364             return scripts.filter(isInlineScript);
365         }
366         if (uiSourceCode.contentType() === WebInspector.resourceTypes.Script) {
367             var rawLocations = WebInspector.debuggerWorkspaceBinding.uiLocationToRawLocations(uiSourceCode, 0, 0);
368             return rawLocations.map(function(rawLocation) { return rawLocation.script()});
369         }
370         return [];
371     },
372
373     /**
374      * @param {!WebInspector.UISourceCode} uiSourceCode
375      */
376     _formatUISourceCodeScript: function(uiSourceCode)
377     {
378         var formattedPath = this._formattedPaths.get(uiSourceCode.project().id() + ":" + uiSourceCode.path());
379         if (formattedPath) {
380             var uiSourceCodePath = formattedPath;
381             var formattedUISourceCode = this._workspace.uiSourceCode(this._projectId, uiSourceCodePath);
382             var formatData = formattedUISourceCode ? this._formatData.get(formattedUISourceCode) : null;
383             if (formatData)
384                  this._showIfNeeded(uiSourceCode, /** @type {!WebInspector.UISourceCode} */ (formattedUISourceCode), formatData.mapping);
385             return;
386         }
387
388         uiSourceCode.requestContent(contentLoaded.bind(this));
389
390         /**
391          * @this {WebInspector.ScriptFormatterEditorAction}
392          * @param {?string} content
393          */
394         function contentLoaded(content)
395         {
396             var formatter = WebInspector.Formatter.createFormatter(uiSourceCode.contentType());
397             formatter.formatContent(uiSourceCode.highlighterType(), content || "", innerCallback.bind(this));
398         }
399
400         /**
401          * @this {WebInspector.ScriptFormatterEditorAction}
402          * @param {string} formattedContent
403          * @param {!WebInspector.FormatterSourceMapping} formatterMapping
404          */
405         function innerCallback(formattedContent, formatterMapping)
406         {
407             var scripts = this._scriptsForUISourceCode(uiSourceCode);
408             var name;
409             if (uiSourceCode.contentType() === WebInspector.resourceTypes.Document)
410                 name = uiSourceCode.displayName();
411             else
412                 name = uiSourceCode.name() || (scripts.length ? scripts[0].scriptId : "");
413
414             formattedPath = this._projectDelegate._addFormatted(name, uiSourceCode.url, uiSourceCode.contentType(), formattedContent);
415             var formattedUISourceCode = /** @type {!WebInspector.UISourceCode} */ (this._workspace.uiSourceCode(this._projectId, formattedPath));
416             var formatData = new WebInspector.FormatterScriptMapping.FormatData(uiSourceCode.project().id(), uiSourceCode.path(), formatterMapping, scripts);
417             this._formatData.set(formattedUISourceCode, formatData);
418             var path = uiSourceCode.project().id() + ":" + uiSourceCode.path();
419             this._formattedPaths.set(path, formattedPath);
420             this._pathsToFormatOnLoad.add(path);
421             for (var i = 0; i < scripts.length; ++i) {
422                 this._uiSourceCodes.set(scripts[i], formattedUISourceCode);
423                 var scriptMapping = /** @type {!WebInspector.FormatterScriptMapping} */(this._scriptMappingByTarget.get(scripts[i].target()));
424                 WebInspector.debuggerWorkspaceBinding.pushSourceMapping(scripts[i], scriptMapping);
425             }
426
427             var targets = WebInspector.targetManager.targets();
428             for (var i = 0; i < targets.length; ++i) {
429                 var scriptMapping = /** @type {!WebInspector.FormatterScriptMapping} */(this._scriptMappingByTarget.get(targets[i]));
430                 WebInspector.debuggerWorkspaceBinding.setSourceMapping(targets[i], formattedUISourceCode, scriptMapping);
431             }
432             this._showIfNeeded(uiSourceCode, formattedUISourceCode, formatterMapping);
433         }
434     }
435 }