• 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 * @param {string} parentPath
34 * @param {string} name
35 * @param {string} originURL
36 * @param {string} url
37 * @param {!WebInspector.ResourceType} contentType
38 * @param {boolean} isEditable
39 * @param {boolean=} isContentScript
40 */
41WebInspector.FileDescriptor = function(parentPath, name, originURL, url, contentType, isEditable, isContentScript)
42{
43    this.parentPath = parentPath;
44    this.name = name;
45    this.originURL = originURL;
46    this.url = url;
47    this.contentType = contentType;
48    this.isEditable = isEditable;
49    this.isContentScript = isContentScript || false;
50}
51
52/**
53 * @interface
54 * @extends {WebInspector.EventTarget}
55 */
56WebInspector.ProjectDelegate = function() { }
57
58WebInspector.ProjectDelegate.Events = {
59    FileAdded: "FileAdded",
60    FileRemoved: "FileRemoved",
61    Reset: "Reset",
62}
63
64WebInspector.ProjectDelegate.prototype = {
65    /**
66     * @return {string}
67     */
68    id: function() { },
69
70    /**
71     * @return {string}
72     */
73    type: function() { },
74
75    /**
76     * @return {string}
77     */
78    displayName: function() { },
79
80    /**
81     * @param {string} path
82     * @param {function(?Date, ?number)} callback
83     */
84    requestMetadata: function(path, callback) { },
85
86    /**
87     * @param {string} path
88     * @param {function(?string)} callback
89     */
90    requestFileContent: function(path, callback) { },
91
92    /**
93     * @return {boolean}
94     */
95    canSetFileContent: function() { },
96
97    /**
98     * @param {string} path
99     * @param {string} newContent
100     * @param {function(?string)} callback
101     */
102    setFileContent: function(path, newContent, callback) { },
103
104    /**
105     * @return {boolean}
106     */
107    canRename: function() { },
108
109    /**
110     * @param {string} path
111     * @param {string} newName
112     * @param {function(boolean, string=, string=, string=, !WebInspector.ResourceType=)} callback
113     */
114    rename: function(path, newName, callback) { },
115
116    /**
117     * @param {string} path
118     */
119    refresh: function(path) { },
120
121    /**
122     * @param {string} path
123     */
124    excludeFolder: function(path) { },
125
126    /**
127     * @param {string} path
128     * @param {?string} name
129     * @param {string} content
130     * @param {function(?string)} callback
131     */
132    createFile: function(path, name, content, callback) { },
133
134    /**
135     * @param {string} path
136     */
137    deleteFile: function(path) { },
138
139    remove: function() { },
140
141    /**
142     * @param {string} path
143     * @param {string} query
144     * @param {boolean} caseSensitive
145     * @param {boolean} isRegex
146     * @param {function(!Array.<!WebInspector.ContentProvider.SearchMatch>)} callback
147     */
148    searchInFileContent: function(path, query, caseSensitive, isRegex, callback) { },
149
150    /**
151     * @param {!Array.<string>} queries
152     * @param {!Array.<string>} fileQueries
153     * @param {boolean} caseSensitive
154     * @param {boolean} isRegex
155     * @param {!WebInspector.Progress} progress
156     * @param {function(!Array.<string>)} callback
157     */
158    findFilesMatchingSearchRequest: function(queries, fileQueries, caseSensitive, isRegex, progress, callback) { },
159
160    /**
161     * @param {!WebInspector.Progress} progress
162     * @param {function()} callback
163     */
164    indexContent: function(progress, callback) { }
165}
166
167/**
168 * @param {!WebInspector.Workspace} workspace
169 * @param {!WebInspector.ProjectDelegate} projectDelegate
170 * @constructor
171 */
172WebInspector.Project = function(workspace, projectDelegate)
173{
174    /** @type {!Object.<string, {uiSourceCode: !WebInspector.UISourceCode, index: number}>} */
175    this._uiSourceCodesMap = {};
176    /** @type {!Array.<!WebInspector.UISourceCode>} */
177    this._uiSourceCodesList = [];
178    this._workspace = workspace;
179    this._projectDelegate = projectDelegate;
180    this._displayName = this._projectDelegate.displayName();
181    this._projectDelegate.addEventListener(WebInspector.ProjectDelegate.Events.FileAdded, this._fileAdded, this);
182    this._projectDelegate.addEventListener(WebInspector.ProjectDelegate.Events.FileRemoved, this._fileRemoved, this);
183    this._projectDelegate.addEventListener(WebInspector.ProjectDelegate.Events.Reset, this._reset, this);
184}
185
186WebInspector.Project.prototype = {
187    /**
188     * @return {string}
189     */
190    id: function()
191    {
192        return this._projectDelegate.id();
193    },
194
195    /**
196     * @return {string}
197     */
198    type: function()
199    {
200        return this._projectDelegate.type();
201    },
202
203    /**
204     * @return {string}
205     */
206    displayName: function()
207    {
208        return this._displayName;
209    },
210
211    /**
212     * @return {boolean}
213     */
214    isServiceProject: function()
215    {
216        return this._projectDelegate.type() === WebInspector.projectTypes.Debugger || this._projectDelegate.type() === WebInspector.projectTypes.LiveEdit;
217    },
218
219    _fileAdded: function(event)
220    {
221        var fileDescriptor = /** @type {!WebInspector.FileDescriptor} */ (event.data);
222        var path = fileDescriptor.parentPath ? fileDescriptor.parentPath + "/" + fileDescriptor.name : fileDescriptor.name;
223        var uiSourceCode = this.uiSourceCode(path);
224        if (uiSourceCode)
225            return;
226
227        uiSourceCode = new WebInspector.UISourceCode(this, fileDescriptor.parentPath, fileDescriptor.name, fileDescriptor.originURL, fileDescriptor.url, fileDescriptor.contentType, fileDescriptor.isEditable);
228        uiSourceCode.isContentScript = fileDescriptor.isContentScript;
229
230        this._uiSourceCodesMap[path] = {uiSourceCode: uiSourceCode, index: this._uiSourceCodesList.length};
231        this._uiSourceCodesList.push(uiSourceCode);
232        this._workspace.dispatchEventToListeners(WebInspector.Workspace.Events.UISourceCodeAdded, uiSourceCode);
233    },
234
235    _fileRemoved: function(event)
236    {
237        var path = /** @type {string} */ (event.data);
238        this._removeFile(path);
239    },
240
241    /**
242     * @param {string} path
243     */
244    _removeFile: function(path)
245    {
246        var uiSourceCode = this.uiSourceCode(path);
247        if (!uiSourceCode)
248            return;
249
250        var entry = this._uiSourceCodesMap[path];
251        var movedUISourceCode = this._uiSourceCodesList[this._uiSourceCodesList.length - 1];
252        this._uiSourceCodesList[entry.index] = movedUISourceCode;
253        var movedEntry = this._uiSourceCodesMap[movedUISourceCode.path()];
254        movedEntry.index = entry.index;
255        this._uiSourceCodesList.splice(this._uiSourceCodesList.length - 1, 1);
256        delete this._uiSourceCodesMap[path];
257        this._workspace.dispatchEventToListeners(WebInspector.Workspace.Events.UISourceCodeRemoved, entry.uiSourceCode);
258    },
259
260    _reset: function()
261    {
262        this._workspace.dispatchEventToListeners(WebInspector.Workspace.Events.ProjectWillReset, this);
263        this._uiSourceCodesMap = {};
264        this._uiSourceCodesList = [];
265    },
266
267    /**
268     * @param {string} path
269     * @return {?WebInspector.UISourceCode}
270     */
271    uiSourceCode: function(path)
272    {
273        var entry = this._uiSourceCodesMap[path];
274        return entry ? entry.uiSourceCode : null;
275    },
276
277    /**
278     * @param {string} originURL
279     * @return {?WebInspector.UISourceCode}
280     */
281    uiSourceCodeForOriginURL: function(originURL)
282    {
283        for (var i = 0; i < this._uiSourceCodesList.length; ++i) {
284            var uiSourceCode = this._uiSourceCodesList[i];
285            if (uiSourceCode.originURL() === originURL)
286                return uiSourceCode;
287        }
288        return null;
289    },
290
291    /**
292     * @return {!Array.<!WebInspector.UISourceCode>}
293     */
294    uiSourceCodes: function()
295    {
296        return this._uiSourceCodesList;
297    },
298
299    /**
300     * @param {!WebInspector.UISourceCode} uiSourceCode
301     * @param {function(?Date, ?number)} callback
302     */
303    requestMetadata: function(uiSourceCode, callback)
304    {
305        this._projectDelegate.requestMetadata(uiSourceCode.path(), callback);
306    },
307
308    /**
309     * @param {!WebInspector.UISourceCode} uiSourceCode
310     * @param {function(?string)} callback
311     */
312    requestFileContent: function(uiSourceCode, callback)
313    {
314        this._projectDelegate.requestFileContent(uiSourceCode.path(), callback);
315    },
316
317    /**
318     * @return {boolean}
319     */
320    canSetFileContent: function()
321    {
322        return this._projectDelegate.canSetFileContent();
323    },
324
325    /**
326     * @param {!WebInspector.UISourceCode} uiSourceCode
327     * @param {string} newContent
328     * @param {function(?string)} callback
329     */
330    setFileContent: function(uiSourceCode, newContent, callback)
331    {
332        this._projectDelegate.setFileContent(uiSourceCode.path(), newContent, onSetContent.bind(this));
333
334        /**
335         * @param {?string} content
336         * @this {WebInspector.Project}
337         */
338        function onSetContent(content)
339        {
340            this._workspace.dispatchEventToListeners(WebInspector.Workspace.Events.UISourceCodeContentCommitted, { uiSourceCode: uiSourceCode, content: newContent });
341            callback(content);
342        }
343    },
344
345    /**
346     * @return {boolean}
347     */
348    canRename: function()
349    {
350        return this._projectDelegate.canRename();
351    },
352
353    /**
354     * @param {!WebInspector.UISourceCode} uiSourceCode
355     * @param {string} newName
356     * @param {function(boolean, string=, string=, string=, !WebInspector.ResourceType=)} callback
357     */
358    rename: function(uiSourceCode, newName, callback)
359    {
360        if (newName === uiSourceCode.name()) {
361            callback(true, uiSourceCode.name(), uiSourceCode.url, uiSourceCode.originURL(), uiSourceCode.contentType());
362            return;
363        }
364
365        this._projectDelegate.rename(uiSourceCode.path(), newName, innerCallback.bind(this));
366
367        /**
368         * @param {boolean} success
369         * @param {string=} newName
370         * @param {string=} newURL
371         * @param {string=} newOriginURL
372         * @param {!WebInspector.ResourceType=} newContentType
373         * @this {WebInspector.Project}
374         */
375        function innerCallback(success, newName, newURL, newOriginURL, newContentType)
376        {
377            if (!success || !newName) {
378                callback(false);
379                return;
380            }
381            var oldPath = uiSourceCode.path();
382            var newPath = uiSourceCode.parentPath() ? uiSourceCode.parentPath() + "/" + newName : newName;
383            this._uiSourceCodesMap[newPath] = this._uiSourceCodesMap[oldPath];
384            delete this._uiSourceCodesMap[oldPath];
385            callback(true, newName, newURL, newOriginURL, newContentType);
386        }
387    },
388
389    /**
390     * @param {string} path
391     */
392    refresh: function(path)
393    {
394        this._projectDelegate.refresh(path);
395    },
396
397    /**
398     * @param {string} path
399     */
400    excludeFolder: function(path)
401    {
402        this._projectDelegate.excludeFolder(path);
403        var uiSourceCodes = this._uiSourceCodesList.slice();
404        for (var i = 0; i < uiSourceCodes.length; ++i) {
405            var uiSourceCode = uiSourceCodes[i];
406            if (uiSourceCode.path().startsWith(path.substr(1)))
407                this._removeFile(uiSourceCode.path());
408        }
409    },
410
411    /**
412     * @param {string} path
413     * @param {?string} name
414     * @param {string} content
415     * @param {function(?string)} callback
416     */
417    createFile: function(path, name, content, callback)
418    {
419        this._projectDelegate.createFile(path, name, content, innerCallback);
420
421        function innerCallback(filePath)
422        {
423            callback(filePath);
424        }
425    },
426
427    /**
428     * @param {string} path
429     */
430    deleteFile: function(path)
431    {
432        this._projectDelegate.deleteFile(path);
433    },
434
435    remove: function()
436    {
437        this._projectDelegate.remove();
438    },
439
440    /**
441     * @param {!WebInspector.UISourceCode} uiSourceCode
442     * @param {string} query
443     * @param {boolean} caseSensitive
444     * @param {boolean} isRegex
445     * @param {function(!Array.<!WebInspector.ContentProvider.SearchMatch>)} callback
446     */
447    searchInFileContent: function(uiSourceCode, query, caseSensitive, isRegex, callback)
448    {
449        this._projectDelegate.searchInFileContent(uiSourceCode.path(), query, caseSensitive, isRegex, callback);
450    },
451
452    /**
453     * @param {!Array.<string>} queries
454     * @param {!Array.<string>} fileQueries
455     * @param {boolean} caseSensitive
456     * @param {boolean} isRegex
457     * @param {!WebInspector.Progress} progress
458     * @param {function(!Array.<string>)} callback
459     */
460    findFilesMatchingSearchRequest: function(queries, fileQueries, caseSensitive, isRegex, progress, callback)
461    {
462        this._projectDelegate.findFilesMatchingSearchRequest(queries, fileQueries, caseSensitive, isRegex, progress, callback);
463    },
464
465    /**
466     * @param {!WebInspector.Progress} progress
467     * @param {function()} callback
468     */
469    indexContent: function(progress, callback)
470    {
471        this._projectDelegate.indexContent(progress, callback);
472    },
473
474    dispose: function()
475    {
476        this._projectDelegate.reset();
477    }
478}
479
480WebInspector.projectTypes = {
481    Debugger: "debugger",
482    LiveEdit: "liveedit",
483    Network: "network",
484    Snippets: "snippets",
485    FileSystem: "filesystem"
486}
487
488/**
489 * @constructor
490 * @extends {WebInspector.Object}
491 * @param {!WebInspector.FileSystemMapping} fileSystemMapping
492 */
493WebInspector.Workspace = function(fileSystemMapping)
494{
495    this._fileSystemMapping = fileSystemMapping;
496    /** @type {!Object.<string, !WebInspector.Project>} */
497    this._projects = {};
498}
499
500WebInspector.Workspace.Events = {
501    UISourceCodeAdded: "UISourceCodeAdded",
502    UISourceCodeRemoved: "UISourceCodeRemoved",
503    UISourceCodeContentCommitted: "UISourceCodeContentCommitted",
504    ProjectWillReset: "ProjectWillReset"
505}
506
507WebInspector.Workspace.prototype = {
508    /**
509     * @return {!Array.<!WebInspector.UISourceCode>}
510     */
511    unsavedSourceCodes: function()
512    {
513        function filterUnsaved(sourceCode)
514        {
515            return sourceCode.isDirty();
516        }
517        return this.uiSourceCodes().filter(filterUnsaved);
518    },
519
520    /**
521     * @param {string} projectId
522     * @param {string} path
523     * @return {?WebInspector.UISourceCode}
524     */
525    uiSourceCode: function(projectId, path)
526    {
527        var project = this._projects[projectId];
528        return project ? project.uiSourceCode(path) : null;
529    },
530
531    /**
532     * @param {string} originURL
533     * @return {?WebInspector.UISourceCode}
534     */
535    uiSourceCodeForOriginURL: function(originURL)
536    {
537        var networkProjects = this.projectsForType(WebInspector.projectTypes.Network)
538        for (var i = 0; i < networkProjects.length; ++i) {
539            var project = networkProjects[i];
540            var uiSourceCode = project.uiSourceCodeForOriginURL(originURL);
541            if (uiSourceCode)
542                return uiSourceCode;
543        }
544        return null;
545    },
546
547    /**
548     * @param {string} type
549     * @return {!Array.<!WebInspector.UISourceCode>}
550     */
551    uiSourceCodesForProjectType: function(type)
552    {
553        var result = [];
554        for (var projectName in this._projects) {
555            var project = this._projects[projectName];
556            if (project.type() === type)
557                result = result.concat(project.uiSourceCodes());
558        }
559        return result;
560    },
561
562    /**
563     * @param {!WebInspector.ProjectDelegate} projectDelegate
564     * @return {!WebInspector.Project}
565     */
566    addProject: function(projectDelegate)
567    {
568        var projectId = projectDelegate.id();
569        this._projects[projectId] = new WebInspector.Project(this, projectDelegate);
570        return this._projects[projectId];
571    },
572
573    /**
574     * @param {string} projectId
575     */
576    removeProject: function(projectId)
577    {
578        var project = this._projects[projectId];
579        if (!project)
580            return;
581        project.dispose();
582        delete this._projects[projectId];
583    },
584
585    /**
586     * @param {string} projectId
587     * @return {!WebInspector.Project}
588     */
589    project: function(projectId)
590    {
591        return this._projects[projectId];
592    },
593
594    /**
595     * @return {!Array.<!WebInspector.Project>}
596     */
597    projects: function()
598    {
599        return Object.values(this._projects);
600    },
601
602    /**
603     * @param {string} type
604     * @return {!Array.<!WebInspector.Project>}
605     */
606    projectsForType: function(type)
607    {
608        function filterByType(project)
609        {
610            return project.type() === type;
611        }
612        return this.projects().filter(filterByType);
613    },
614
615    /**
616     * @return {!Array.<!WebInspector.UISourceCode>}
617     */
618    uiSourceCodes: function()
619    {
620        var result = [];
621        for (var projectId in this._projects) {
622            var project = this._projects[projectId];
623            result = result.concat(project.uiSourceCodes());
624        }
625        return result;
626    },
627
628    /**
629     * @param {string} url
630     * @return {boolean}
631     */
632    hasMappingForURL: function(url)
633    {
634        if (!InspectorFrontendHost.supportsFileSystems())
635            return false;
636        return this._fileSystemMapping.hasMappingForURL(url);
637    },
638
639    /**
640     * @param {string} url
641     * @return {?WebInspector.UISourceCode}
642     */
643    _networkUISourceCodeForURL: function(url)
644    {
645        var splitURL = WebInspector.ParsedURL.splitURL(url);
646        var projectId = WebInspector.SimpleProjectDelegate.projectId(splitURL[0], WebInspector.projectTypes.Network);
647        var project = this.project(projectId);
648        return project ? project.uiSourceCode(splitURL.slice(1).join("/")) : null;
649    },
650
651    /**
652     * @param {string} url
653     * @return {?WebInspector.UISourceCode}
654     */
655    uiSourceCodeForURL: function(url)
656    {
657        if (!InspectorFrontendHost.supportsFileSystems())
658            return this._networkUISourceCodeForURL(url);
659        var file = this._fileSystemMapping.fileForURL(url);
660        if (!file)
661            return this._networkUISourceCodeForURL(url);
662
663        var projectId = WebInspector.FileSystemProjectDelegate.projectId(file.fileSystemPath);
664        var project = this.project(projectId);
665        return project ? project.uiSourceCode(file.filePath) : null;
666    },
667
668    /**
669     * @param {string} fileSystemPath
670     * @param {string} filePath
671     * @return {string}
672     */
673    urlForPath: function(fileSystemPath, filePath)
674    {
675        return this._fileSystemMapping.urlForPath(fileSystemPath, filePath);
676    },
677
678    /**
679     * @param {!WebInspector.UISourceCode} networkUISourceCode
680     * @param {!WebInspector.UISourceCode} uiSourceCode
681     * @param {!WebInspector.FileSystemWorkspaceProvider} fileSystemWorkspaceProvider
682     */
683    addMapping: function(networkUISourceCode, uiSourceCode, fileSystemWorkspaceProvider)
684    {
685        var url = networkUISourceCode.url;
686        var path = uiSourceCode.path();
687        var fileSystemPath = fileSystemWorkspaceProvider.fileSystemPath(uiSourceCode);
688        this._fileSystemMapping.addMappingForResource(url, fileSystemPath, path);
689        WebInspector.suggestReload();
690    },
691
692    /**
693     * @param {!WebInspector.UISourceCode} uiSourceCode
694     */
695    removeMapping: function(uiSourceCode)
696    {
697        this._fileSystemMapping.removeMappingForURL(uiSourceCode.url);
698        WebInspector.suggestReload();
699    },
700
701    __proto__: WebInspector.Object.prototype
702}
703
704/**
705 * @type {!WebInspector.Workspace}
706 */
707WebInspector.workspace;
708