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