1/* 2 * Copyright (C) 2011 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/** 33 * @constructor 34 * @extends {WebInspector.Object} 35 * @implements {WebInspector.ContentProvider} 36 * @param {!WebInspector.Project} project 37 * @param {string} parentPath 38 * @param {string} name 39 * @param {string} originURL 40 * @param {string} url 41 * @param {!WebInspector.ResourceType} contentType 42 */ 43WebInspector.UISourceCode = function(project, parentPath, name, originURL, url, contentType) 44{ 45 this._project = project; 46 this._parentPath = parentPath; 47 this._name = name; 48 this._originURL = originURL; 49 this._url = url; 50 this._contentType = contentType; 51 /** @type {!Array.<function(?string)>} */ 52 this._requestContentCallbacks = []; 53 /** @type {!Array.<!WebInspector.PresentationConsoleMessage>} */ 54 this._consoleMessages = []; 55 56 /** 57 * @type {!Map.<!WebInspector.Target, !WebInspector.SourceMapping>} 58 */ 59 this._sourceMappingForTarget = new Map(); 60 61 /** 62 * @type {!Map.<!WebInspector.Target, !WebInspector.ScriptFile>} 63 */ 64 this._scriptFileForTarget = new Map(); 65 66 /** @type {!Array.<!WebInspector.Revision>} */ 67 this.history = []; 68 if (!this._project.isServiceProject() && this._url) 69 this._restoreRevisionHistory(); 70} 71 72WebInspector.UISourceCode.Events = { 73 WorkingCopyChanged: "WorkingCopyChanged", 74 WorkingCopyCommitted: "WorkingCopyCommitted", 75 TitleChanged: "TitleChanged", 76 SavedStateUpdated: "SavedStateUpdated", 77 ConsoleMessageAdded: "ConsoleMessageAdded", 78 ConsoleMessageRemoved: "ConsoleMessageRemoved", 79 ConsoleMessagesCleared: "ConsoleMessagesCleared", 80 SourceMappingChanged: "SourceMappingChanged", 81} 82 83WebInspector.UISourceCode.prototype = { 84 /** 85 * @return {string} 86 */ 87 get url() 88 { 89 return this._url; 90 }, 91 92 /** 93 * @return {string} 94 */ 95 name: function() 96 { 97 return this._name; 98 }, 99 100 /** 101 * @return {string} 102 */ 103 parentPath: function() 104 { 105 return this._parentPath; 106 }, 107 108 /** 109 * @return {string} 110 */ 111 path: function() 112 { 113 return this._parentPath ? this._parentPath + "/" + this._name : this._name; 114 }, 115 116 /** 117 * @return {string} 118 */ 119 fullDisplayName: function() 120 { 121 return this._project.displayName() + "/" + (this._parentPath ? this._parentPath + "/" : "") + this.displayName(true); 122 }, 123 124 /** 125 * @param {boolean=} skipTrim 126 * @return {string} 127 */ 128 displayName: function(skipTrim) 129 { 130 var displayName = this.name() || WebInspector.UIString("(index)"); 131 return skipTrim ? displayName : displayName.trimEnd(100); 132 }, 133 134 /** 135 * @return {string} 136 */ 137 uri: function() 138 { 139 var path = this.path(); 140 if (!this._project.id()) 141 return path; 142 if (!path) 143 return this._project.id(); 144 return this._project.id() + "/" + path; 145 }, 146 147 /** 148 * @return {string} 149 */ 150 originURL: function() 151 { 152 return this._originURL; 153 }, 154 155 /** 156 * @return {boolean} 157 */ 158 canRename: function() 159 { 160 return this._project.canRename(); 161 }, 162 163 /** 164 * @param {string} newName 165 * @param {function(boolean)} callback 166 */ 167 rename: function(newName, callback) 168 { 169 this._project.rename(this, newName, innerCallback.bind(this)); 170 171 /** 172 * @param {boolean} success 173 * @param {string=} newName 174 * @param {string=} newURL 175 * @param {string=} newOriginURL 176 * @param {!WebInspector.ResourceType=} newContentType 177 * @this {WebInspector.UISourceCode} 178 */ 179 function innerCallback(success, newName, newURL, newOriginURL, newContentType) 180 { 181 if (success) 182 this._updateName(/** @type {string} */ (newName), /** @type {string} */ (newURL), /** @type {string} */ (newOriginURL), /** @type {!WebInspector.ResourceType} */ (newContentType)); 183 callback(success); 184 } 185 }, 186 187 remove: function() 188 { 189 this._project.deleteFile(this.path()); 190 }, 191 192 /** 193 * @param {string} name 194 * @param {string} url 195 * @param {string} originURL 196 * @param {!WebInspector.ResourceType=} contentType 197 */ 198 _updateName: function(name, url, originURL, contentType) 199 { 200 var oldURI = this.uri(); 201 this._name = name; 202 if (url) 203 this._url = url; 204 if (originURL) 205 this._originURL = originURL; 206 if (contentType) 207 this._contentType = contentType; 208 this.dispatchEventToListeners(WebInspector.UISourceCode.Events.TitleChanged, oldURI); 209 }, 210 211 /** 212 * @return {string} 213 */ 214 contentURL: function() 215 { 216 return this.originURL(); 217 }, 218 219 /** 220 * @return {!WebInspector.ResourceType} 221 */ 222 contentType: function() 223 { 224 return this._contentType; 225 }, 226 227 /** 228 * @param {!WebInspector.Target} target 229 * @return {?WebInspector.ScriptFile} 230 */ 231 scriptFileForTarget: function(target) 232 { 233 return this._scriptFileForTarget.get(target) || null; 234 }, 235 236 /** 237 * @param {!WebInspector.Target} target 238 * @param {?WebInspector.ScriptFile} scriptFile 239 */ 240 setScriptFileForTarget: function(target, scriptFile) 241 { 242 if (scriptFile) 243 this._scriptFileForTarget.put(target, scriptFile); 244 else 245 this._scriptFileForTarget.remove(target); 246 }, 247 248 /** 249 * @return {!WebInspector.Project} 250 */ 251 project: function() 252 { 253 return this._project; 254 }, 255 256 /** 257 * @param {function(?Date, ?number)} callback 258 */ 259 requestMetadata: function(callback) 260 { 261 this._project.requestMetadata(this, callback); 262 }, 263 264 /** 265 * @param {function(?string)} callback 266 */ 267 requestContent: function(callback) 268 { 269 if (this._content || this._contentLoaded) { 270 callback(this._content); 271 return; 272 } 273 this._requestContentCallbacks.push(callback); 274 if (this._requestContentCallbacks.length === 1) 275 this._project.requestFileContent(this, this._fireContentAvailable.bind(this)); 276 }, 277 278 /** 279 * @param {function()} callback 280 */ 281 _pushCheckContentUpdatedCallback: function(callback) 282 { 283 if (!this._checkContentUpdatedCallbacks) 284 this._checkContentUpdatedCallbacks = []; 285 this._checkContentUpdatedCallbacks.push(callback); 286 }, 287 288 _terminateContentCheck: function() 289 { 290 delete this._checkingContent; 291 if (this._checkContentUpdatedCallbacks) { 292 this._checkContentUpdatedCallbacks.forEach(function(callback) { callback(); }); 293 delete this._checkContentUpdatedCallbacks; 294 } 295 }, 296 297 /** 298 * @param {function()=} callback 299 */ 300 checkContentUpdated: function(callback) 301 { 302 callback = callback || function() {}; 303 if (!this._project.canSetFileContent()) { 304 callback(); 305 return; 306 } 307 this._pushCheckContentUpdatedCallback(callback); 308 309 if (this._checkingContent) { 310 return; 311 } 312 this._checkingContent = true; 313 this._project.requestFileContent(this, contentLoaded.bind(this)); 314 315 /** 316 * @param {?string} updatedContent 317 * @this {WebInspector.UISourceCode} 318 */ 319 function contentLoaded(updatedContent) 320 { 321 if (updatedContent === null) { 322 var workingCopy = this.workingCopy(); 323 this._commitContent("", false); 324 this.setWorkingCopy(workingCopy); 325 this._terminateContentCheck(); 326 return; 327 } 328 if (typeof this._lastAcceptedContent === "string" && this._lastAcceptedContent === updatedContent) { 329 this._terminateContentCheck(); 330 return; 331 } 332 if (this._content === updatedContent) { 333 delete this._lastAcceptedContent; 334 this._terminateContentCheck(); 335 return; 336 } 337 338 if (!this.isDirty()) { 339 this._commitContent(updatedContent, false); 340 this._terminateContentCheck(); 341 return; 342 } 343 344 var shouldUpdate = window.confirm(WebInspector.UIString("This file was changed externally. Would you like to reload it?")); 345 if (shouldUpdate) 346 this._commitContent(updatedContent, false); 347 else 348 this._lastAcceptedContent = updatedContent; 349 this._terminateContentCheck(); 350 } 351 }, 352 353 /** 354 * @param {function(?string)} callback 355 */ 356 requestOriginalContent: function(callback) 357 { 358 this._project.requestFileContent(this, callback); 359 }, 360 361 /** 362 * @param {string} content 363 * @param {boolean} shouldSetContentInProject 364 */ 365 _commitContent: function(content, shouldSetContentInProject) 366 { 367 delete this._lastAcceptedContent; 368 this._content = content; 369 this._contentLoaded = true; 370 371 var lastRevision = this.history.length ? this.history[this.history.length - 1] : null; 372 if (!lastRevision || lastRevision._content !== this._content) { 373 var revision = new WebInspector.Revision(this, this._content, new Date()); 374 this.history.push(revision); 375 revision._persist(); 376 } 377 378 this._innerResetWorkingCopy(); 379 this._hasCommittedChanges = true; 380 this.dispatchEventToListeners(WebInspector.UISourceCode.Events.WorkingCopyCommitted); 381 if (this._url && WebInspector.fileManager.isURLSaved(this._url)) 382 this._saveURLWithFileManager(false, this._content); 383 if (shouldSetContentInProject) 384 this._project.setFileContent(this, this._content, function() { }); 385 }, 386 387 /** 388 * @param {boolean} forceSaveAs 389 * @param {?string} content 390 */ 391 _saveURLWithFileManager: function(forceSaveAs, content) 392 { 393 WebInspector.fileManager.save(this._url, /** @type {string} */ (content), forceSaveAs, callback.bind(this)); 394 WebInspector.fileManager.close(this._url); 395 396 /** 397 * @param {boolean} accepted 398 * @this {WebInspector.UISourceCode} 399 */ 400 function callback(accepted) 401 { 402 if (!accepted) 403 return; 404 this._savedWithFileManager = true; 405 this.dispatchEventToListeners(WebInspector.UISourceCode.Events.SavedStateUpdated); 406 } 407 }, 408 409 /** 410 * @param {boolean} forceSaveAs 411 */ 412 saveToFileSystem: function(forceSaveAs) 413 { 414 if (this.isDirty()) { 415 this._saveURLWithFileManager(forceSaveAs, this.workingCopy()); 416 this.commitWorkingCopy(function() { }); 417 return; 418 } 419 this.requestContent(this._saveURLWithFileManager.bind(this, forceSaveAs)); 420 }, 421 422 /** 423 * @return {boolean} 424 */ 425 hasUnsavedCommittedChanges: function() 426 { 427 if (this._savedWithFileManager || this.project().canSetFileContent() || this._project.isServiceProject()) 428 return false; 429 if (this._project.workspace().hasResourceContentTrackingExtensions()) 430 return false; 431 return !!this._hasCommittedChanges; 432 }, 433 434 /** 435 * @param {string} content 436 */ 437 addRevision: function(content) 438 { 439 this._commitContent(content, true); 440 }, 441 442 _restoreRevisionHistory: function() 443 { 444 if (!window.localStorage) 445 return; 446 447 var registry = WebInspector.Revision._revisionHistoryRegistry(); 448 var historyItems = registry[this.url]; 449 if (!historyItems) 450 return; 451 452 function filterOutStale(historyItem) 453 { 454 // FIXME: Main frame might not have been loaded yet when uiSourceCodes for snippets are created. 455 if (!WebInspector.resourceTreeModel || !WebInspector.resourceTreeModel.mainFrame) 456 return false; 457 return historyItem.loaderId === WebInspector.resourceTreeModel.mainFrame.loaderId; 458 } 459 460 historyItems = historyItems.filter(filterOutStale); 461 if (!historyItems.length) 462 return; 463 464 for (var i = 0; i < historyItems.length; ++i) { 465 var content = window.localStorage[historyItems[i].key]; 466 var timestamp = new Date(historyItems[i].timestamp); 467 var revision = new WebInspector.Revision(this, content, timestamp); 468 this.history.push(revision); 469 } 470 this._content = this.history[this.history.length - 1].content; 471 this._hasCommittedChanges = true; 472 this._contentLoaded = true; 473 }, 474 475 _clearRevisionHistory: function() 476 { 477 if (!window.localStorage) 478 return; 479 480 var registry = WebInspector.Revision._revisionHistoryRegistry(); 481 var historyItems = registry[this.url]; 482 for (var i = 0; historyItems && i < historyItems.length; ++i) 483 delete window.localStorage[historyItems[i].key]; 484 delete registry[this.url]; 485 window.localStorage["revision-history"] = JSON.stringify(registry); 486 }, 487 488 revertToOriginal: function() 489 { 490 /** 491 * @this {WebInspector.UISourceCode} 492 * @param {?string} content 493 */ 494 function callback(content) 495 { 496 if (typeof content !== "string") 497 return; 498 499 this.addRevision(content); 500 } 501 502 this.requestOriginalContent(callback.bind(this)); 503 504 WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, { 505 action: WebInspector.UserMetrics.UserActionNames.ApplyOriginalContent, 506 url: this.url 507 }); 508 }, 509 510 /** 511 * @param {function(!WebInspector.UISourceCode)} callback 512 */ 513 revertAndClearHistory: function(callback) 514 { 515 /** 516 * @this {WebInspector.UISourceCode} 517 * @param {?string} content 518 */ 519 function revert(content) 520 { 521 if (typeof content !== "string") 522 return; 523 524 this.addRevision(content); 525 this._clearRevisionHistory(); 526 this.history = []; 527 callback(this); 528 } 529 530 this.requestOriginalContent(revert.bind(this)); 531 532 WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, { 533 action: WebInspector.UserMetrics.UserActionNames.RevertRevision, 534 url: this.url 535 }); 536 }, 537 538 /** 539 * @return {string} 540 */ 541 workingCopy: function() 542 { 543 if (this._workingCopyGetter) { 544 this._workingCopy = this._workingCopyGetter(); 545 delete this._workingCopyGetter; 546 } 547 if (this.isDirty()) 548 return this._workingCopy; 549 return this._content; 550 }, 551 552 resetWorkingCopy: function() 553 { 554 this._innerResetWorkingCopy(); 555 this.dispatchEventToListeners(WebInspector.UISourceCode.Events.WorkingCopyChanged); 556 }, 557 558 _innerResetWorkingCopy: function() 559 { 560 delete this._workingCopy; 561 delete this._workingCopyGetter; 562 }, 563 564 /** 565 * @param {string} newWorkingCopy 566 */ 567 setWorkingCopy: function(newWorkingCopy) 568 { 569 this._workingCopy = newWorkingCopy; 570 delete this._workingCopyGetter; 571 this.dispatchEventToListeners(WebInspector.UISourceCode.Events.WorkingCopyChanged); 572 }, 573 574 setWorkingCopyGetter: function(workingCopyGetter) 575 { 576 this._workingCopyGetter = workingCopyGetter; 577 this.dispatchEventToListeners(WebInspector.UISourceCode.Events.WorkingCopyChanged); 578 }, 579 580 removeWorkingCopyGetter: function() 581 { 582 if (!this._workingCopyGetter) 583 return; 584 this._workingCopy = this._workingCopyGetter(); 585 delete this._workingCopyGetter; 586 }, 587 588 /** 589 * @param {function(?string)} callback 590 */ 591 commitWorkingCopy: function(callback) 592 { 593 if (!this.isDirty()) { 594 callback(null); 595 return; 596 } 597 598 this._commitContent(this.workingCopy(), true); 599 callback(null); 600 601 WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, { 602 action: WebInspector.UserMetrics.UserActionNames.FileSaved, 603 url: this.url 604 }); 605 }, 606 607 /** 608 * @return {boolean} 609 */ 610 isDirty: function() 611 { 612 return typeof this._workingCopy !== "undefined" || typeof this._workingCopyGetter !== "undefined"; 613 }, 614 615 /** 616 * @return {string} 617 */ 618 highlighterType: function() 619 { 620 var lastIndexOfDot = this._name.lastIndexOf("."); 621 var extension = lastIndexOfDot !== -1 ? this._name.substr(lastIndexOfDot + 1) : ""; 622 var indexOfQuestionMark = extension.indexOf("?"); 623 if (indexOfQuestionMark !== -1) 624 extension = extension.substr(0, indexOfQuestionMark); 625 var mimeType = WebInspector.ResourceType.mimeTypesForExtensions[extension.toLowerCase()]; 626 return mimeType || this.contentType().canonicalMimeType(); 627 }, 628 629 /** 630 * @return {?string} 631 */ 632 content: function() 633 { 634 return this._content; 635 }, 636 637 /** 638 * @param {string} query 639 * @param {boolean} caseSensitive 640 * @param {boolean} isRegex 641 * @param {function(!Array.<!WebInspector.ContentProvider.SearchMatch>)} callback 642 */ 643 searchInContent: function(query, caseSensitive, isRegex, callback) 644 { 645 var content = this.content(); 646 if (content) { 647 var provider = new WebInspector.StaticContentProvider(this.contentType(), content); 648 provider.searchInContent(query, caseSensitive, isRegex, callback); 649 return; 650 } 651 652 this._project.searchInFileContent(this, query, caseSensitive, isRegex, callback); 653 }, 654 655 /** 656 * @param {?string} content 657 */ 658 _fireContentAvailable: function(content) 659 { 660 this._contentLoaded = true; 661 this._content = content; 662 663 var callbacks = this._requestContentCallbacks.slice(); 664 this._requestContentCallbacks = []; 665 for (var i = 0; i < callbacks.length; ++i) 666 callbacks[i](content); 667 }, 668 669 /** 670 * @return {boolean} 671 */ 672 contentLoaded: function() 673 { 674 return this._contentLoaded; 675 }, 676 677 /** 678 * @param {!WebInspector.Target} target 679 * @param {number} lineNumber 680 * @param {number} columnNumber 681 * @return {?WebInspector.RawLocation} 682 */ 683 uiLocationToRawLocation: function(target, lineNumber, columnNumber) 684 { 685 var sourceMapping = this._sourceMappingForTarget.get(target); 686 if (!sourceMapping) 687 return null; 688 return sourceMapping.uiLocationToRawLocation(this, lineNumber, columnNumber); 689 }, 690 691 /** 692 * @param {number} lineNumber 693 * @param {number} columnNumber 694 * @return {!Array.<!WebInspector.RawLocation>} 695 */ 696 uiLocationToRawLocations: function(lineNumber, columnNumber) 697 { 698 var result = []; 699 var sourceMappings = this._sourceMappingForTarget.values(); 700 for (var i = 0; i < sourceMappings.length; ++i) { 701 var rawLocation = sourceMappings[i].uiLocationToRawLocation(this, lineNumber, columnNumber); 702 if (rawLocation) 703 result.push(rawLocation); 704 } 705 return result; 706 }, 707 708 /** 709 * @return {!Array.<!WebInspector.PresentationConsoleMessage>} 710 */ 711 consoleMessages: function() 712 { 713 return this._consoleMessages; 714 }, 715 716 /** 717 * @param {!WebInspector.PresentationConsoleMessage} message 718 */ 719 consoleMessageAdded: function(message) 720 { 721 this._consoleMessages.push(message); 722 this.dispatchEventToListeners(WebInspector.UISourceCode.Events.ConsoleMessageAdded, message); 723 }, 724 725 /** 726 * @param {!WebInspector.PresentationConsoleMessage} message 727 */ 728 consoleMessageRemoved: function(message) 729 { 730 this._consoleMessages.remove(message); 731 this.dispatchEventToListeners(WebInspector.UISourceCode.Events.ConsoleMessageRemoved, message); 732 }, 733 734 consoleMessagesCleared: function() 735 { 736 this._consoleMessages = []; 737 this.dispatchEventToListeners(WebInspector.UISourceCode.Events.ConsoleMessagesCleared); 738 }, 739 740 /** 741 * @return {boolean} 742 */ 743 hasSourceMapping: function() 744 { 745 return !!this._sourceMappingForTarget.size(); 746 }, 747 748 /** 749 * @param {!WebInspector.Target} target 750 * @param {?WebInspector.SourceMapping} sourceMapping 751 */ 752 setSourceMappingForTarget: function(target, sourceMapping) 753 { 754 if (this._sourceMappingForTarget.get(target) === sourceMapping) 755 return; 756 757 if (sourceMapping) 758 this._sourceMappingForTarget.put(target, sourceMapping); 759 else 760 this._sourceMappingForTarget.remove(target); 761 762 this.dispatchEventToListeners(WebInspector.UISourceCode.Events.SourceMappingChanged, {target: target, isIdentity: sourceMapping ? sourceMapping.isIdentity() : false}); 763 }, 764 765 /** 766 * @param {number} lineNumber 767 * @param {number=} columnNumber 768 * @return {!WebInspector.UILocation} 769 */ 770 uiLocation: function(lineNumber, columnNumber) 771 { 772 if (typeof columnNumber === "undefined") 773 columnNumber = 0; 774 return new WebInspector.UILocation(this, lineNumber, columnNumber); 775 }, 776 777 __proto__: WebInspector.Object.prototype 778} 779 780/** 781 * @constructor 782 * @param {!WebInspector.UISourceCode} uiSourceCode 783 * @param {number} lineNumber 784 * @param {number} columnNumber 785 */ 786WebInspector.UILocation = function(uiSourceCode, lineNumber, columnNumber) 787{ 788 this.uiSourceCode = uiSourceCode; 789 this.lineNumber = lineNumber; 790 this.columnNumber = columnNumber; 791} 792 793WebInspector.UILocation.prototype = { 794 /** 795 * @param {!WebInspector.Target} target 796 * @return {?WebInspector.RawLocation} 797 */ 798 uiLocationToRawLocation: function(target) 799 { 800 return this.uiSourceCode.uiLocationToRawLocation(target, this.lineNumber, this.columnNumber); 801 }, 802 803 /** 804 * @return {!Array.<!WebInspector.RawLocation>} 805 */ 806 uiLocationToRawLocations: function() 807 { 808 return this.uiSourceCode.uiLocationToRawLocations(this.lineNumber, this.columnNumber); 809 }, 810 811 /** 812 * @return {string} 813 */ 814 linkText: function() 815 { 816 var linkText = this.uiSourceCode.displayName(); 817 if (typeof this.lineNumber === "number") 818 linkText += ":" + (this.lineNumber + 1); 819 return linkText; 820 }, 821 822 /** 823 * @return {string} 824 */ 825 id: function() 826 { 827 return this.uiSourceCode.uri() + ":" + this.lineNumber + ":" + this.columnNumber; 828 }, 829} 830 831/** 832 * @interface 833 */ 834WebInspector.RawLocation = function() 835{ 836} 837 838WebInspector.RawLocation.prototype = { 839 /** 840 * @return {?WebInspector.UILocation} 841 */ 842 toUILocation: function() { } 843} 844 845/** 846 * @constructor 847 * @param {!WebInspector.RawLocation} rawLocation 848 * @param {function(!WebInspector.UILocation):(boolean|undefined)} updateDelegate 849 */ 850WebInspector.LiveLocation = function(rawLocation, updateDelegate) 851{ 852 this._rawLocation = rawLocation; 853 this._updateDelegate = updateDelegate; 854} 855 856WebInspector.LiveLocation.prototype = { 857 update: function() 858 { 859 var uiLocation = this.uiLocation(); 860 if (!uiLocation) 861 return; 862 if (this._updateDelegate(uiLocation)) 863 this.dispose(); 864 }, 865 866 /** 867 * @return {!WebInspector.RawLocation} 868 */ 869 rawLocation: function() 870 { 871 return this._rawLocation; 872 }, 873 874 /** 875 * @return {!WebInspector.UILocation} 876 */ 877 uiLocation: function() 878 { 879 throw "Not implemented"; 880 }, 881 882 dispose: function() 883 { 884 // Overridden by subclasses. 885 } 886} 887 888/** 889 * @constructor 890 * @implements {WebInspector.ContentProvider} 891 * @param {!WebInspector.UISourceCode} uiSourceCode 892 * @param {?string|undefined} content 893 * @param {!Date} timestamp 894 */ 895WebInspector.Revision = function(uiSourceCode, content, timestamp) 896{ 897 this._uiSourceCode = uiSourceCode; 898 this._content = content; 899 this._timestamp = timestamp; 900} 901 902WebInspector.Revision._revisionHistoryRegistry = function() 903{ 904 if (!WebInspector.Revision._revisionHistoryRegistryObject) { 905 if (window.localStorage) { 906 var revisionHistory = window.localStorage["revision-history"]; 907 try { 908 WebInspector.Revision._revisionHistoryRegistryObject = revisionHistory ? JSON.parse(revisionHistory) : {}; 909 } catch (e) { 910 WebInspector.Revision._revisionHistoryRegistryObject = {}; 911 } 912 } else 913 WebInspector.Revision._revisionHistoryRegistryObject = {}; 914 } 915 return WebInspector.Revision._revisionHistoryRegistryObject; 916} 917 918WebInspector.Revision.filterOutStaleRevisions = function() 919{ 920 if (!window.localStorage) 921 return; 922 923 var registry = WebInspector.Revision._revisionHistoryRegistry(); 924 var filteredRegistry = {}; 925 for (var url in registry) { 926 var historyItems = registry[url]; 927 var filteredHistoryItems = []; 928 for (var i = 0; historyItems && i < historyItems.length; ++i) { 929 var historyItem = historyItems[i]; 930 if (historyItem.loaderId === WebInspector.resourceTreeModel.mainFrame.loaderId) { 931 filteredHistoryItems.push(historyItem); 932 filteredRegistry[url] = filteredHistoryItems; 933 } else 934 delete window.localStorage[historyItem.key]; 935 } 936 } 937 WebInspector.Revision._revisionHistoryRegistryObject = filteredRegistry; 938 939 function persist() 940 { 941 window.localStorage["revision-history"] = JSON.stringify(filteredRegistry); 942 } 943 944 // Schedule async storage. 945 setTimeout(persist, 0); 946} 947 948WebInspector.Revision.prototype = { 949 /** 950 * @return {!WebInspector.UISourceCode} 951 */ 952 get uiSourceCode() 953 { 954 return this._uiSourceCode; 955 }, 956 957 /** 958 * @return {!Date} 959 */ 960 get timestamp() 961 { 962 return this._timestamp; 963 }, 964 965 /** 966 * @return {?string} 967 */ 968 get content() 969 { 970 return this._content || null; 971 }, 972 973 revertToThis: function() 974 { 975 /** 976 * @param {string} content 977 * @this {WebInspector.Revision} 978 */ 979 function revert(content) 980 { 981 if (this._uiSourceCode._content !== content) 982 this._uiSourceCode.addRevision(content); 983 } 984 this.requestContent(revert.bind(this)); 985 }, 986 987 /** 988 * @return {string} 989 */ 990 contentURL: function() 991 { 992 return this._uiSourceCode.originURL(); 993 }, 994 995 /** 996 * @return {!WebInspector.ResourceType} 997 */ 998 contentType: function() 999 { 1000 return this._uiSourceCode.contentType(); 1001 }, 1002 1003 /** 1004 * @param {function(string)} callback 1005 */ 1006 requestContent: function(callback) 1007 { 1008 callback(this._content || ""); 1009 }, 1010 1011 /** 1012 * @param {string} query 1013 * @param {boolean} caseSensitive 1014 * @param {boolean} isRegex 1015 * @param {function(!Array.<!WebInspector.ContentProvider.SearchMatch>)} callback 1016 */ 1017 searchInContent: function(query, caseSensitive, isRegex, callback) 1018 { 1019 callback([]); 1020 }, 1021 1022 _persist: function() 1023 { 1024 if (this._uiSourceCode.project().type() === WebInspector.projectTypes.FileSystem) 1025 return; 1026 1027 if (!window.localStorage) 1028 return; 1029 1030 var url = this.contentURL(); 1031 if (!url || url.startsWith("inspector://")) 1032 return; 1033 1034 var loaderId = WebInspector.resourceTreeModel.mainFrame.loaderId; 1035 var timestamp = this.timestamp.getTime(); 1036 var key = "revision-history|" + url + "|" + loaderId + "|" + timestamp; 1037 1038 var registry = WebInspector.Revision._revisionHistoryRegistry(); 1039 1040 var historyItems = registry[url]; 1041 if (!historyItems) { 1042 historyItems = []; 1043 registry[url] = historyItems; 1044 } 1045 historyItems.push({url: url, loaderId: loaderId, timestamp: timestamp, key: key}); 1046 1047 /** 1048 * @this {WebInspector.Revision} 1049 */ 1050 function persist() 1051 { 1052 window.localStorage[key] = this._content; 1053 window.localStorage["revision-history"] = JSON.stringify(registry); 1054 } 1055 1056 // Schedule async storage. 1057 setTimeout(persist.bind(this), 0); 1058 } 1059} 1060