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