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