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