Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / Source / devtools / front_end / bindings / FileSystemWorkspaceBinding.js
blobbf40f7f6d11a8ba60f1ec05865d4806bf11054a1
1 /*
2  * Copyright (C) 2013 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  */
31 /**
32  * @constructor
33  * @param {!WebInspector.IsolatedFileSystemManager} isolatedFileSystemManager
34  * @param {!WebInspector.Workspace} workspace
35  * @param {!WebInspector.NetworkMapping} networkMapping
36  */
37 WebInspector.FileSystemWorkspaceBinding = function(isolatedFileSystemManager, workspace, networkMapping)
39     this._isolatedFileSystemManager = isolatedFileSystemManager;
40     this._workspace = workspace;
41     // FIXME: This dependency should be removed from here once we do not need URL to create a UISourceCode.
42     this._networkMapping = networkMapping;
43     this._isolatedFileSystemManager.addEventListener(WebInspector.IsolatedFileSystemManager.Events.FileSystemAdded, this._fileSystemAdded, this);
44     this._isolatedFileSystemManager.addEventListener(WebInspector.IsolatedFileSystemManager.Events.FileSystemRemoved, this._fileSystemRemoved, this);
45     /** @type {!Map.<string, !WebInspector.FileSystemWorkspaceBinding.FileSystem>} */
46     this._boundFileSystems = new Map();
48     /** @type {!Object.<number, function(!Array.<string>)>} */
49     this._callbacks = {};
50     /** @type {!Object.<number, !WebInspector.Progress>} */
51     this._progresses = {};
53     InspectorFrontendHost.events.addEventListener(InspectorFrontendHostAPI.Events.IndexingTotalWorkCalculated, this._onIndexingTotalWorkCalculated, this);
54     InspectorFrontendHost.events.addEventListener(InspectorFrontendHostAPI.Events.IndexingWorked, this._onIndexingWorked, this);
55     InspectorFrontendHost.events.addEventListener(InspectorFrontendHostAPI.Events.IndexingDone, this._onIndexingDone, this);
56     InspectorFrontendHost.events.addEventListener(InspectorFrontendHostAPI.Events.SearchCompleted, this._onSearchCompleted, this);
59 WebInspector.FileSystemWorkspaceBinding._styleSheetExtensions = ["css", "scss", "sass", "less"].keySet();
60 WebInspector.FileSystemWorkspaceBinding._documentExtensions = ["htm", "html", "asp", "aspx", "phtml", "jsp"].keySet();
62 WebInspector.FileSystemWorkspaceBinding._lastRequestId = 0;
64 /**
65  * @param {string} fileSystemPath
66  * @return {string}
67  */
68 WebInspector.FileSystemWorkspaceBinding.projectId = function(fileSystemPath)
70     return "filesystem:" + fileSystemPath;
73 WebInspector.FileSystemWorkspaceBinding.prototype = {
74     /**
75      * @param {!WebInspector.Event} event
76      */
77     _fileSystemAdded: function(event)
78     {
79         var fileSystem = /** @type {!WebInspector.IsolatedFileSystem} */ (event.data);
80         var boundFileSystem = new WebInspector.FileSystemWorkspaceBinding.FileSystem(this, fileSystem, this._workspace, this._networkMapping);
81         this._boundFileSystems.set(fileSystem.normalizedPath(), boundFileSystem);
82     },
84     /**
85      * @param {!WebInspector.Event} event
86      */
87     _fileSystemRemoved: function(event)
88     {
89         var fileSystem = /** @type {!WebInspector.IsolatedFileSystem} */ (event.data);
90         var boundFileSystem = this._boundFileSystems.get(fileSystem.normalizedPath());
91         boundFileSystem.dispose();
92         this._boundFileSystems.remove(fileSystem.normalizedPath());
93     },
95     /**
96      * @param {string} projectId
97      * @return {string}
98      */
99     fileSystemPath: function(projectId)
100     {
101         var fileSystemPath = projectId.substr("filesystem:".length);
102         var normalizedPath = WebInspector.IsolatedFileSystem.normalizePath(fileSystemPath);
103         return projectId.substr("filesystem:".length);
104     },
106     /**
107      * @return {number}
108      */
109     _nextId: function()
110     {
111         return ++WebInspector.FileSystemWorkspaceBinding._lastRequestId;
112     },
114     /**
115      * @param {function(!Array.<string>)} callback
116      * @return {number}
117      */
118     registerCallback: function(callback)
119     {
120         var requestId = this._nextId();
121         this._callbacks[requestId] = callback;
122         return requestId;
123     },
125     /**
126      * @param {!WebInspector.Progress} progress
127      * @return {number}
128      */
129     registerProgress: function(progress)
130     {
131         var requestId = this._nextId();
132         this._progresses[requestId] = progress;
133         return requestId;
134     },
136     /**
137      * @param {!WebInspector.Event} event
138      */
139     _onIndexingTotalWorkCalculated: function(event)
140     {
141         var requestId = /** @type {number} */ (event.data["requestId"]);
142         var totalWork = /** @type {number} */ (event.data["totalWork"]);
144         var progress = this._progresses[requestId];
145         if (!progress)
146             return;
147         progress.setTotalWork(totalWork);
148     },
150     /**
151      * @param {!WebInspector.Event} event
152      */
153     _onIndexingWorked: function(event)
154     {
155         var requestId = /** @type {number} */ (event.data["requestId"]);
156         var worked = /** @type {number} */ (event.data["worked"]);
158         var progress = this._progresses[requestId];
159         if (!progress)
160             return;
161         progress.worked(worked);
162         if (progress.isCanceled()) {
163             InspectorFrontendHost.stopIndexing(requestId);
164             this._onIndexingDone(event);
165         }
166     },
168     /**
169      * @param {!WebInspector.Event} event
170      */
171     _onIndexingDone: function(event)
172     {
173         var requestId = /** @type {number} */ (event.data["requestId"]);
175         var progress = this._progresses[requestId];
176         if (!progress)
177             return;
178         progress.done();
179         delete this._progresses[requestId];
180     },
182     /**
183      * @param {!WebInspector.Event} event
184      */
185     _onSearchCompleted: function(event)
186     {
187         var requestId = /** @type {number} */ (event.data["requestId"]);
188         var files = /** @type {!Array.<string>} */ (event.data["files"]);
190         var callback = this._callbacks[requestId];
191         if (!callback)
192             return;
193         callback.call(null, files);
194         delete this._callbacks[requestId];
195     },
199  * @constructor
200  * @extends {WebInspector.Object}
201  * @implements {WebInspector.ProjectDelegate}
202  * @param {!WebInspector.FileSystemWorkspaceBinding} fileSystemWorkspaceBinding
203  * @param {!WebInspector.IsolatedFileSystem} isolatedFileSystem
204  * @param {!WebInspector.Workspace} workspace
205  * @param {!WebInspector.NetworkMapping} networkMapping
206  */
207 WebInspector.FileSystemWorkspaceBinding.FileSystem = function(fileSystemWorkspaceBinding, isolatedFileSystem, workspace, networkMapping)
209     WebInspector.Object.call(this);
210     this._fileSystemWorkspaceBinding = fileSystemWorkspaceBinding;
211     this._fileSystem = isolatedFileSystem;
212     this._fileSystemBaseURL = "file://" + this._fileSystem.normalizedPath() + "/";
213     this._fileSystemProjectURL = "filesystem:" + this._fileSystem.normalizedPath();
214     this._workspace = workspace;
215     // FIXME: This dependency should be removed from here once we do not need URL to create a UISourceCode.
216     this._networkMapping = networkMapping;
218     this._projectId = WebInspector.FileSystemWorkspaceBinding.projectId(this._fileSystem.path());
219     console.assert(!this._workspace.project(this._projectId));
220     this._workspace.addProject(this._projectId, this);
221     this.populate();
224 WebInspector.FileSystemWorkspaceBinding.FileSystem.prototype = {
225     /**
226      * @override
227      * @return {string}
228      */
229     type: function()
230     {
231         return WebInspector.projectTypes.FileSystem;
232     },
234     /**
235      * @return {string}
236      */
237     fileSystemPath: function()
238     {
239         return this._fileSystem.path();
240     },
242     /**
243      * @override
244      * @return {string}
245      */
246     displayName: function()
247     {
248         var normalizedPath = this._fileSystem.normalizedPath();
249         return normalizedPath.substr(normalizedPath.lastIndexOf("/") + 1);
250     },
252     /**
253      * @override
254      * @return {string}
255      */
256     url: function()
257     {
258         return this._fileSystemProjectURL;
259     },
261     /**
262      * @param {string} path
263      * @return {string}
264      */
265     _filePathForPath: function(path)
266     {
267         return "/" + path;
268     },
270     /**
271      * @override
272      * @param {string} path
273      * @param {function(?string)} callback
274      */
275     requestFileContent: function(path, callback)
276     {
277         var filePath = this._filePathForPath(path);
278         this._fileSystem.requestFileContent(filePath, callback);
279     },
281     /**
282      * @override
283      * @param {string} path
284      * @param {function(?Date, ?number)} callback
285      */
286     requestMetadata: function(path, callback)
287     {
288         var filePath = this._filePathForPath(path);
289         this._fileSystem.requestMetadata(filePath, callback);
290     },
292     /**
293      * @override
294      * @return {boolean}
295      */
296     canSetFileContent: function()
297     {
298         return true;
299     },
301     /**
302      * @override
303      * @param {string} path
304      * @param {string} newContent
305      * @param {function(?string)} callback
306      */
307     setFileContent: function(path, newContent, callback)
308     {
309         var filePath = this._filePathForPath(path);
310         this._fileSystem.setFileContent(filePath, newContent, callback.bind(this, ""));
311     },
313     /**
314      * @override
315      * @return {boolean}
316      */
317     canRename: function()
318     {
319         return true;
320     },
322     /**
323      * @override
324      * @param {string} path
325      * @param {string} newName
326      * @param {function(boolean, string=, string=, string=, !WebInspector.ResourceType=)} callback
327      */
328     rename: function(path, newName, callback)
329     {
330         var filePath = this._filePathForPath(path);
331         this._fileSystem.renameFile(filePath, newName, innerCallback.bind(this));
333         /**
334          * @param {boolean} success
335          * @param {string=} newName
336          * @this {WebInspector.FileSystemWorkspaceBinding.FileSystem}
337          */
338         function innerCallback(success, newName)
339         {
340             if (!success) {
341                 callback(false, newName);
342                 return;
343             }
344             var validNewName = /** @type {string} */ (newName);
345             console.assert(validNewName);
346             var slash = filePath.lastIndexOf("/");
347             var parentPath = filePath.substring(0, slash);
348             filePath = parentPath + "/" + validNewName;
349             filePath = filePath.substr(1);
350             var newURL = this._networkMapping.urlForPath(this._fileSystem.path(), filePath);
351             var extension = this._extensionForPath(validNewName);
352             var newOriginURL = this._fileSystemBaseURL + filePath;
353             var newContentType = this._contentTypeForExtension(extension);
354             callback(true, validNewName, newURL, newOriginURL, newContentType);
355         }
356     },
358     /**
359      * @override
360      * @param {string} path
361      * @param {string} query
362      * @param {boolean} caseSensitive
363      * @param {boolean} isRegex
364      * @param {function(!Array.<!WebInspector.ContentProvider.SearchMatch>)} callback
365      */
366     searchInFileContent: function(path, query, caseSensitive, isRegex, callback)
367     {
368         var filePath = this._filePathForPath(path);
369         this._fileSystem.requestFileContent(filePath, contentCallback);
371         /**
372          * @param {?string} content
373          */
374         function contentCallback(content)
375         {
376             var result = [];
377             if (content !== null)
378                 result = WebInspector.ContentProvider.performSearchInContent(content, query, caseSensitive, isRegex);
379             callback(result);
380         }
381     },
383     /**
384      * @override
385      * @param {!WebInspector.ProjectSearchConfig} searchConfig
386      * @param {!Array.<string>} filesMathingFileQuery
387      * @param {!WebInspector.Progress} progress
388      * @param {function(!Array.<string>)} callback
389      */
390     findFilesMatchingSearchRequest: function(searchConfig, filesMathingFileQuery, progress, callback)
391     {
392         var result = filesMathingFileQuery;
393         var queriesToRun = searchConfig.queries().slice();
394         if (!queriesToRun.length)
395             queriesToRun.push("");
396         progress.setTotalWork(queriesToRun.length);
397         searchNextQuery.call(this);
399         /**
400          * @this {WebInspector.FileSystemWorkspaceBinding.FileSystem}
401          */
402         function searchNextQuery()
403         {
404             if (!queriesToRun.length) {
405                 progress.done();
406                 callback(result);
407                 return;
408             }
409             var query = queriesToRun.shift();
410             this._searchInPath(searchConfig.isRegex() ? "" : query, progress, innerCallback.bind(this));
411         }
413         /**
414          * @param {!Array.<string>} files
415          * @this {WebInspector.FileSystemWorkspaceBinding.FileSystem}
416          */
417         function innerCallback(files)
418         {
419             files = files.sort();
420             progress.worked(1);
421             result = result.intersectOrdered(files, String.naturalOrderComparator);
422             searchNextQuery.call(this);
423         }
424     },
426     /**
427      * @param {string} query
428      * @param {!WebInspector.Progress} progress
429      * @param {function(!Array.<string>)} callback
430      */
431     _searchInPath: function(query, progress, callback)
432     {
433         var requestId = this._fileSystemWorkspaceBinding.registerCallback(innerCallback.bind(this));
434         InspectorFrontendHost.searchInPath(requestId, this._fileSystem.path(), query);
436         /**
437          * @param {!Array.<string>} files
438          * @this {WebInspector.FileSystemWorkspaceBinding.FileSystem}
439          */
440         function innerCallback(files)
441         {
442             /**
443              * @param {string} fullPath
444              * @this {WebInspector.FileSystemWorkspaceBinding.FileSystem}
445              */
446             function trimAndNormalizeFileSystemPath(fullPath)
447             {
448                 var trimmedPath = fullPath.substr(this._fileSystem.path().length + 1);
449                 if (WebInspector.isWin())
450                     trimmedPath = trimmedPath.replace(/\\/g, "/");
451                 return trimmedPath;
452             }
454             files = files.map(trimAndNormalizeFileSystemPath.bind(this));
455             progress.worked(1);
456             callback(files);
457         }
458     },
460     /**
461      * @override
462      * @param {!WebInspector.Progress} progress
463      */
464     indexContent: function(progress)
465     {
466         progress.setTotalWork(1);
467         var requestId = this._fileSystemWorkspaceBinding.registerProgress(progress);
468         InspectorFrontendHost.indexPath(requestId, this._fileSystem.path());
469     },
471     /**
472      * @param {string} path
473      * @return {string}
474      */
475     _extensionForPath: function(path)
476     {
477         var extensionIndex = path.lastIndexOf(".");
478         if (extensionIndex === -1)
479             return "";
480         return path.substring(extensionIndex + 1).toLowerCase();
481     },
483     /**
484      * @param {string} extension
485      * @return {!WebInspector.ResourceType}
486      */
487     _contentTypeForExtension: function(extension)
488     {
489         if (WebInspector.FileSystemWorkspaceBinding._styleSheetExtensions[extension])
490             return WebInspector.resourceTypes.Stylesheet;
491         if (WebInspector.FileSystemWorkspaceBinding._documentExtensions[extension])
492             return WebInspector.resourceTypes.Document;
493         return WebInspector.resourceTypes.Script;
494     },
496     populate: function()
497     {
498         this._fileSystem.requestFilesRecursive("", this._addFile.bind(this));
499     },
501     /**
502      * @override
503      * @param {string} path
504      * @param {function()=} callback
505      */
506     refresh: function(path, callback)
507     {
508         this._fileSystem.requestFilesRecursive(path, this._addFile.bind(this), callback);
509     },
511     /**
512      * @override
513      * @param {string} path
514      */
515     excludeFolder: function(path)
516     {
517         this._fileSystemWorkspaceBinding._isolatedFileSystemManager.excludedFolderManager().addExcludedFolder(this._fileSystem.path(), path);
518     },
520     /**
521      * @override
522      * @param {string} path
523      * @param {?string} name
524      * @param {string} content
525      * @param {function(?string)} callback
526      */
527     createFile: function(path, name, content, callback)
528     {
529         this._fileSystem.createFile(path, name, innerCallback.bind(this));
530         var createFilePath;
532         /**
533          * @param {?string} filePath
534          * @this {WebInspector.FileSystemWorkspaceBinding.FileSystem}
535          */
536         function innerCallback(filePath)
537         {
538             if (!filePath) {
539                 callback(null);
540                 return;
541             }
542             createFilePath = filePath;
543             if (!content) {
544                 contentSet.call(this);
545                 return;
546             }
547             this._fileSystem.setFileContent(filePath, content, contentSet.bind(this));
548         }
550         /**
551          * @this {WebInspector.FileSystemWorkspaceBinding.FileSystem}
552          */
553         function contentSet()
554         {
555             this._addFile(createFilePath);
556             callback(createFilePath);
557         }
558     },
560     /**
561      * @override
562      * @param {string} path
563      */
564     deleteFile: function(path)
565     {
566         this._fileSystem.deleteFile(path);
567         this._removeFile(path);
568     },
570     /**
571      * @override
572      */
573     remove: function()
574     {
575         this._fileSystemWorkspaceBinding._isolatedFileSystemManager.removeFileSystem(this._fileSystem.path());
576     },
578     /**
579      * @param {string} filePath
580      */
581     _addFile: function(filePath)
582     {
583         if (!filePath)
584             console.assert(false);
586         var slash = filePath.lastIndexOf("/");
587         var parentPath = filePath.substring(0, slash);
588         var name = filePath.substring(slash + 1);
590         var url = this._networkMapping.urlForPath(this._fileSystem.path(), filePath);
591         var extension = this._extensionForPath(name);
592         var contentType = this._contentTypeForExtension(extension);
594         var fileDescriptor = new WebInspector.FileDescriptor(parentPath, name, this._fileSystemBaseURL + filePath, url, contentType);
595         this.dispatchEventToListeners(WebInspector.ProjectDelegate.Events.FileAdded, fileDescriptor);
596     },
598     /**
599      * @param {string} path
600      */
601     _removeFile: function(path)
602     {
603         this.dispatchEventToListeners(WebInspector.ProjectDelegate.Events.FileRemoved, path);
604     },
606     dispose: function()
607     {
608         this._workspace.removeProject(this._projectId);
609     },
611     __proto__: WebInspector.Object.prototype
615  * @type {!WebInspector.FileSystemWorkspaceBinding}
616  */
617 WebInspector.fileSystemWorkspaceBinding;