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