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 */ 34WebInspector.WorkspaceController = function(workspace) 35{ 36 this._workspace = workspace; 37 WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.InspectedURLChanged, this._inspectedURLChanged, this); 38} 39 40WebInspector.WorkspaceController.prototype = { 41 /** 42 * @param {WebInspector.Event} event 43 */ 44 _inspectedURLChanged: function(event) 45 { 46 WebInspector.Revision.filterOutStaleRevisions(); 47 } 48} 49 50/** 51 * @constructor 52 * @param {Array.<string>} path 53 * @param {string} originURL 54 * @param {string} url 55 * @param {WebInspector.ResourceType} contentType 56 * @param {boolean} isEditable 57 * @param {boolean=} isContentScript 58 */ 59WebInspector.FileDescriptor = function(path, originURL, url, contentType, isEditable, isContentScript) 60{ 61 this.path = path; 62 this.originURL = originURL; 63 this.url = url; 64 this.contentType = contentType; 65 this.isEditable = isEditable; 66 this.isContentScript = isContentScript || false; 67} 68 69/** 70 * @interface 71 * @extends {WebInspector.EventTarget} 72 */ 73WebInspector.ProjectDelegate = function() { } 74 75WebInspector.ProjectDelegate.Events = { 76 FileAdded: "FileAdded", 77 FileRemoved: "FileRemoved", 78 Reset: "Reset", 79} 80 81WebInspector.ProjectDelegate.prototype = { 82 /** 83 * @return {string} 84 */ 85 id: function() { }, 86 87 /** 88 * @return {string} 89 */ 90 type: function() { }, 91 92 /** 93 * @return {string} 94 */ 95 displayName: function() { }, 96 97 /** 98 * @param {Array.<string>} path 99 * @param {function(?string,boolean,string)} callback 100 */ 101 requestFileContent: function(path, callback) { }, 102 103 /** 104 * @return {boolean} 105 */ 106 canSetFileContent: function() { }, 107 108 /** 109 * @param {Array.<string>} path 110 * @param {string} newContent 111 * @param {function(?string)} callback 112 */ 113 setFileContent: function(path, newContent, callback) { }, 114 115 /** 116 * @param {Array.<string>} path 117 * @param {string} query 118 * @param {boolean} caseSensitive 119 * @param {boolean} isRegex 120 * @param {function(Array.<WebInspector.ContentProvider.SearchMatch>)} callback 121 */ 122 searchInFileContent: function(path, query, caseSensitive, isRegex, callback) { } 123} 124 125/** 126 * @type {?WebInspector.WorkspaceController} 127 */ 128WebInspector.workspaceController = null; 129 130/** 131 * @param {WebInspector.Workspace} workspace 132 * @param {WebInspector.ProjectDelegate} projectDelegate 133 * @constructor 134 */ 135WebInspector.Project = function(workspace, projectDelegate) 136{ 137 /** @type {Object.<string, WebInspector.UISourceCode>} */ 138 this._uiSourceCodes = {}; 139 this._workspace = workspace; 140 this._projectDelegate = projectDelegate; 141 this._projectDelegate.addEventListener(WebInspector.ProjectDelegate.Events.FileAdded, this._fileAdded, this); 142 this._projectDelegate.addEventListener(WebInspector.ProjectDelegate.Events.FileRemoved, this._fileRemoved, this); 143 this._projectDelegate.addEventListener(WebInspector.ProjectDelegate.Events.Reset, this._reset, this); 144} 145 146WebInspector.Project.prototype = { 147 /** 148 * @return {string} 149 */ 150 id: function() 151 { 152 return this._projectDelegate.id(); 153 }, 154 155 /** 156 * @return {string} 157 */ 158 type: function() 159 { 160 return this._projectDelegate.type(); 161 }, 162 163 /** 164 * @return {string} 165 */ 166 displayName: function() 167 { 168 return this._projectDelegate.displayName(); 169 }, 170 171 /** 172 * @return {boolean} 173 */ 174 isServiceProject: function() 175 { 176 return this._projectDelegate.type() === WebInspector.projectTypes.Debugger || this._projectDelegate.type() === WebInspector.projectTypes.LiveEdit; 177 }, 178 179 _fileAdded: function(event) 180 { 181 var fileDescriptor = /** @type {WebInspector.FileDescriptor} */ (event.data); 182 var uiSourceCode = this.uiSourceCode(fileDescriptor.path); 183 if (uiSourceCode) { 184 // FIXME: Implement 185 return; 186 } 187 188 uiSourceCode = new WebInspector.UISourceCode(this, fileDescriptor.path, fileDescriptor.originURL, fileDescriptor.url, fileDescriptor.contentType, fileDescriptor.isEditable); 189 uiSourceCode.isContentScript = fileDescriptor.isContentScript; 190 this._uiSourceCodes[uiSourceCode.path().join("/")] = uiSourceCode; 191 this._workspace.dispatchEventToListeners(WebInspector.UISourceCodeProvider.Events.UISourceCodeAdded, uiSourceCode); 192 }, 193 194 _fileRemoved: function(event) 195 { 196 var path = /** @type {Array.<string>} */ (event.data); 197 var uiSourceCode = this.uiSourceCode(path); 198 if (!uiSourceCode) 199 return; 200 delete this._uiSourceCodes[uiSourceCode.path().join("/")]; 201 this._workspace.dispatchEventToListeners(WebInspector.UISourceCodeProvider.Events.UISourceCodeRemoved, uiSourceCode); 202 }, 203 204 _reset: function() 205 { 206 this._workspace.dispatchEventToListeners(WebInspector.Workspace.Events.ProjectWillReset, this); 207 this._uiSourceCodes = {}; 208 }, 209 210 /** 211 * @param {Array.<string>} path 212 * @return {?WebInspector.UISourceCode} 213 */ 214 uiSourceCode: function(path) 215 { 216 return this._uiSourceCodes[path.join("/")] || null; 217 }, 218 219 /** 220 * @param {string} originURL 221 * @return {?WebInspector.UISourceCode} 222 */ 223 uiSourceCodeForOriginURL: function(originURL) 224 { 225 for (var path in this._uiSourceCodes) { 226 var uiSourceCode = this._uiSourceCodes[path]; 227 if (uiSourceCode.originURL() === originURL) 228 return uiSourceCode; 229 } 230 return null; 231 }, 232 233 /** 234 * @return {Array.<WebInspector.UISourceCode>} 235 */ 236 uiSourceCodes: function() 237 { 238 return Object.values(this._uiSourceCodes); 239 }, 240 241 /** 242 * @param {WebInspector.UISourceCode} uiSourceCode 243 * @param {function(?string,boolean,string)} callback 244 */ 245 requestFileContent: function(uiSourceCode, callback) 246 { 247 this._projectDelegate.requestFileContent(uiSourceCode.path(), callback); 248 }, 249 250 /** 251 * @return {boolean} 252 */ 253 canSetFileContent: function() 254 { 255 return this._projectDelegate.canSetFileContent(); 256 }, 257 258 /** 259 * @param {WebInspector.UISourceCode} uiSourceCode 260 * @param {string} newContent 261 * @param {function(?string)} callback 262 */ 263 setFileContent: function(uiSourceCode, newContent, callback) 264 { 265 this._projectDelegate.setFileContent(uiSourceCode.path(), newContent, callback); 266 this._workspace.dispatchEventToListeners(WebInspector.Workspace.Events.UISourceCodeContentCommitted, { uiSourceCode: uiSourceCode, content: newContent }); 267 }, 268 269 /** 270 * @param {WebInspector.UISourceCode} uiSourceCode 271 * @param {string} query 272 * @param {boolean} caseSensitive 273 * @param {boolean} isRegex 274 * @param {function(Array.<WebInspector.ContentProvider.SearchMatch>)} callback 275 */ 276 searchInFileContent: function(uiSourceCode, query, caseSensitive, isRegex, callback) 277 { 278 this._projectDelegate.searchInFileContent(uiSourceCode.path(), query, caseSensitive, isRegex, callback); 279 }, 280 281 dispose: function() 282 { 283 this._projectDelegate.reset(); 284 } 285} 286 287WebInspector.projectTypes = { 288 Debugger: "debugger", 289 LiveEdit: "liveedit", 290 Network: "network", 291 Snippets: "snippets", 292 FileSystem: "filesystem" 293} 294 295/** 296 * @constructor 297 * @implements {WebInspector.UISourceCodeProvider} 298 * @extends {WebInspector.Object} 299 * @param {WebInspector.FileMapping} fileMapping 300 * @param {WebInspector.FileSystemMapping} fileSystemMapping 301 */ 302WebInspector.Workspace = function(fileMapping, fileSystemMapping) 303{ 304 this._fileMapping = fileMapping; 305 this._fileSystemMapping = fileSystemMapping; 306 /** @type {!Object.<string, WebInspector.Project>} */ 307 this._projects = {}; 308} 309 310WebInspector.Workspace.Events = { 311 UISourceCodeContentCommitted: "UISourceCodeContentCommitted", 312 ProjectWillReset: "ProjectWillReset" 313} 314 315WebInspector.Workspace.prototype = { 316 /** 317 * @param {string} projectId 318 * @param {Array.<string>} path 319 * @return {?WebInspector.UISourceCode} 320 */ 321 uiSourceCode: function(projectId, path) 322 { 323 var project = this._projects[projectId]; 324 return project ? project.uiSourceCode(path) : null; 325 }, 326 327 /** 328 * @param {string} originURL 329 * @return {?WebInspector.UISourceCode} 330 */ 331 uiSourceCodeForOriginURL: function(originURL) 332 { 333 var networkProjects = this.projectsForType(WebInspector.projectTypes.Network) 334 for (var i = 0; i < networkProjects.length; ++i) { 335 var project = networkProjects[i]; 336 var uiSourceCode = project.uiSourceCodeForOriginURL(originURL); 337 if (uiSourceCode) 338 return uiSourceCode; 339 } 340 return null; 341 }, 342 343 /** 344 * @param {string} type 345 * @return {Array.<WebInspector.UISourceCode>} 346 */ 347 uiSourceCodesForProjectType: function(type) 348 { 349 var result = []; 350 for (var projectName in this._projects) { 351 var project = this._projects[projectName]; 352 if (project.type() === type) 353 result = result.concat(project.uiSourceCodes()); 354 } 355 return result; 356 }, 357 358 /** 359 * @param {WebInspector.ProjectDelegate} projectDelegate 360 * @return {WebInspector.Project} 361 */ 362 addProject: function(projectDelegate) 363 { 364 var projectId = projectDelegate.id(); 365 this._projects[projectId] = new WebInspector.Project(this, projectDelegate); 366 return this._projects[projectId]; 367 }, 368 369 /** 370 * @param {string} projectId 371 */ 372 removeProject: function(projectId) 373 { 374 var project = this._projects[projectId]; 375 if (!project) 376 return; 377 project.dispose(); 378 delete this._projects[projectId]; 379 }, 380 381 /** 382 * @param {string} projectId 383 * @return {WebInspector.Project} 384 */ 385 project: function(projectId) 386 { 387 return this._projects[projectId]; 388 }, 389 390 /** 391 * @return {Array.<WebInspector.Project>} 392 */ 393 projects: function() 394 { 395 return Object.values(this._projects); 396 }, 397 398 /** 399 * @param {string} type 400 * @return {Array.<WebInspector.Project>} 401 */ 402 projectsForType: function(type) 403 { 404 function filterByType(project) 405 { 406 return project.type() === type; 407 } 408 return this.projects().filter(filterByType); 409 }, 410 411 /** 412 * @return {Array.<WebInspector.UISourceCode>} 413 */ 414 uiSourceCodes: function() 415 { 416 var result = []; 417 for (var projectId in this._projects) { 418 var project = this._projects[projectId]; 419 result = result.concat(project.uiSourceCodes()); 420 } 421 return result; 422 }, 423 424 /** 425 * @param {string} url 426 * @return {boolean} 427 */ 428 hasMappingForURL: function(url) 429 { 430 var entry = this._fileMapping.mappingEntryForURL(url); 431 if (!entry) 432 return false; 433 return !!this._fileSystemPathForEntry(entry); 434 }, 435 436 /** 437 * @param {WebInspector.FileMapping.Entry} entry 438 * @return {?string} 439 */ 440 _fileSystemPathForEntry: function(entry) 441 { 442 return this._fileSystemMapping.fileSystemPathForPrefix(entry.pathPrefix); 443 }, 444 /** 445 * @param {string} url 446 * @return {WebInspector.UISourceCode} 447 */ 448 uiSourceCodeForURL: function(url) 449 { 450 var entry = this._fileMapping.mappingEntryForURL(url); 451 var fileSystemPath = entry ? this._fileSystemPathForEntry(entry) : null; 452 if (!fileSystemPath) { 453 var splittedURL = WebInspector.ParsedURL.splitURL(url); 454 var projectId = WebInspector.SimpleProjectDelegate.projectId(splittedURL[0], WebInspector.projectTypes.Network); 455 var path = WebInspector.SimpleWorkspaceProvider.pathForSplittedURL(splittedURL); 456 var project = this.project(projectId); 457 return project ? project.uiSourceCode(path) : null; 458 } 459 460 var projectId = WebInspector.FileSystemProjectDelegate.projectId(fileSystemPath); 461 var pathPrefix = entry.pathPrefix.substr(fileSystemPath.length + 1); 462 var path = pathPrefix + url.substr(entry.urlPrefix.length); 463 var project = this.project(projectId); 464 return project ? project.uiSourceCode(path.split("/")) : null; 465 }, 466 467 /** 468 * @param {string} path 469 * @return {string} 470 */ 471 urlForPath: function(path) 472 { 473 var entry = this._fileMapping.mappingEntryForPath(path); 474 if (!entry) 475 return ""; 476 return entry.urlPrefix + path.substring(entry.pathPrefix.length); 477 }, 478 479 /** 480 * @param {WebInspector.UISourceCode} networkUISourceCode 481 * @param {WebInspector.UISourceCode} uiSourceCode 482 * @param {WebInspector.FileSystemWorkspaceProvider} fileSystemWorkspaceProvider 483 */ 484 addMapping: function(networkUISourceCode, uiSourceCode, fileSystemWorkspaceProvider) 485 { 486 var url = networkUISourceCode.url; 487 var path = uiSourceCode.path(); 488 var suffix = ""; 489 for (var i = path.length - 1; i >= 0; --i) { 490 var nextSuffix = "/" + path[i] + suffix; 491 if (!url.endsWith(nextSuffix)) 492 break; 493 suffix = nextSuffix; 494 } 495 var fileSystemPath = fileSystemWorkspaceProvider.fileSystemPath(uiSourceCode); 496 var filePath = "/" + path.join("/"); 497 var pathPrefix = fileSystemPath + filePath.substr(0, filePath.length - suffix.length) + "/"; 498 var urlPrefix = url.substr(0, url.length - suffix.length) + "/"; 499 500 var entries = this._fileMapping.mappingEntries(); 501 var entry = new WebInspector.FileMapping.Entry(urlPrefix, pathPrefix); 502 entries.push(entry); 503 this._fileMapping.setMappingEntries(entries); 504 WebInspector.suggestReload(); 505 }, 506 507 /** 508 * @param {WebInspector.UISourceCode} uiSourceCode 509 */ 510 removeMapping: function(uiSourceCode) 511 { 512 var entry = this._fileMapping.mappingEntryForURL(uiSourceCode.url); 513 var entries = this._fileMapping.mappingEntries(); 514 entries.remove(entry); 515 this._fileMapping.setMappingEntries(entries); 516 WebInspector.suggestReload(); 517 }, 518 519 __proto__: WebInspector.Object.prototype 520} 521 522/** 523 * @type {?WebInspector.Workspace} 524 */ 525WebInspector.workspace = null; 526