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