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