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