Home | History | Annotate | Download | only in front_end
      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  */
     41 WebInspector.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  */
     56 WebInspector.ProjectDelegate = function() { }
     57 
     58 WebInspector.ProjectDelegate.Events = {
     59     FileAdded: "FileAdded",
     60     FileRemoved: "FileRemoved",
     61     Reset: "Reset",
     62 }
     63 
     64 WebInspector.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  */
    172 WebInspector.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 
    186 WebInspector.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 
    480 WebInspector.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  */
    493 WebInspector.Workspace = function(fileSystemMapping)
    494 {
    495     this._fileSystemMapping = fileSystemMapping;
    496     /** @type {!Object.<string, !WebInspector.Project>} */
    497     this._projects = {};
    498 }
    499 
    500 WebInspector.Workspace.Events = {
    501     UISourceCodeAdded: "UISourceCodeAdded",
    502     UISourceCodeRemoved: "UISourceCodeRemoved",
    503     UISourceCodeContentCommitted: "UISourceCodeContentCommitted",
    504     ProjectWillReset: "ProjectWillReset"
    505 }
    506 
    507 WebInspector.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  */
    707 WebInspector.workspace;
    708