Web Inspector: Add infrastructure for eslint based static analyzer
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Models / SourceCode.js
1 /*
2  * Copyright (C) 2013 Apple 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
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 WebInspector.SourceCode = function()
27 {
28     WebInspector.Object.call(this);
29
30     this._pendingContentRequestCallbacks = [];
31
32     this._originalRevision = new WebInspector.SourceCodeRevision(this, null, false);
33     this._currentRevision = this._originalRevision;
34
35     this._sourceMaps = null;
36     this._formatterSourceMap = null;
37 };
38
39 WebInspector.Object.addConstructorFunctions(WebInspector.SourceCode);
40
41 WebInspector.SourceCode.Event = {
42     ContentDidChange: "source-code-content-did-change",
43     SourceMapAdded: "source-code-source-map-added",
44     FormatterDidChange: "source-code-formatter-did-change"
45 };
46
47 WebInspector.SourceCode.prototype = {
48     constructor: WebInspector.SourceCode,
49
50     // Public
51
52     get displayName()
53     {
54         // Implemented by subclasses.
55         console.error("Needs to be implemented by a subclass.");
56         return "";
57     },
58
59     get originalRevision()
60     {
61         return this._originalRevision;
62     },
63
64     get currentRevision()
65     {
66         return this._currentRevision;
67     },
68
69     set currentRevision(revision)
70     {
71         console.assert(revision instanceof WebInspector.SourceCodeRevision);
72         if (!(revision instanceof WebInspector.SourceCodeRevision))
73             return;
74
75         console.assert(revision.sourceCode === this);
76         if (revision.sourceCode !== this)
77             return;
78
79         this._currentRevision = revision;
80
81         this.dispatchEventToListeners(WebInspector.SourceCode.Event.ContentDidChange);
82     },
83
84     get content()
85     {
86         return this._currentRevision.content;
87     },
88
89     get contentIsBase64Encoded()
90     {
91         return this._currentRevision.contentIsBase64Encoded;
92     },
93
94     get sourceMaps()
95     {
96         return this._sourceMaps || [];
97     },
98
99     addSourceMap: function(sourceMap)
100     {
101         console.assert(sourceMap instanceof WebInspector.SourceMap);
102
103         if (!this._sourceMaps)
104             this._sourceMaps = [];
105
106         this._sourceMaps.push(sourceMap);
107
108         this.dispatchEventToListeners(WebInspector.SourceCode.Event.SourceMapAdded);
109     },
110
111     get formatterSourceMap()
112     {
113         return this._formatterSourceMap;
114     },
115
116     set formatterSourceMap(formatterSourceMap)
117     {
118         console.assert(this._formatterSourceMap === null || formatterSourceMap === null);
119         console.assert(formatterSourceMap === null || formatterSourceMap instanceof WebInspector.FormatterSourceMap);
120
121         this._formatterSourceMap = formatterSourceMap;
122
123         this.dispatchEventToListeners(WebInspector.SourceCode.Event.FormatterDidChange);
124     },
125
126     requestContent: function(callback)
127     {
128         console.assert(typeof callback === "function");
129         if (typeof callback !== "function")
130             return;
131
132         this._pendingContentRequestCallbacks.push(callback);
133
134         if (this._contentReceived) {
135             // Call _servicePendingContentRequests on a timeout to force callbacks to be asynchronous.
136             if (!this._servicePendingContentRequestsTimeoutIdentifier)
137                 this._servicePendingContentRequestsTimeoutIdentifier = setTimeout(this.servicePendingContentRequests.bind(this), 0);
138         } else if (this.canRequestContentFromBackend())
139             this.requestContentFromBackendIfNeeded();
140     },
141
142     createSourceCodeLocation: function(lineNumber, columnNumber)
143     {
144         return new WebInspector.SourceCodeLocation(this, lineNumber, columnNumber);
145     },
146
147     createLazySourceCodeLocation: function(lineNumber, columnNumber)
148     {
149         return new WebInspector.LazySourceCodeLocation(this, lineNumber, columnNumber);
150     },
151
152     createSourceCodeTextRange: function(textRange)
153     {
154         return new WebInspector.SourceCodeTextRange(this, textRange);
155     },
156
157     // Protected
158
159     revisionContentDidChange: function(revision)
160     {
161         if (this._ignoreRevisionContentDidChangeEvent)
162             return;
163
164         if (revision !== this._currentRevision)
165             return;
166
167         this.handleCurrentRevisionContentChange();
168
169         this.dispatchEventToListeners(WebInspector.SourceCode.Event.ContentDidChange);
170     },
171
172     handleCurrentRevisionContentChange: function()
173     {
174         // Implemented by subclasses if needed.
175     },
176
177     get revisionForRequestedContent()
178     {
179         // Implemented by subclasses if needed.
180         return this._originalRevision;
181     },
182
183     markContentAsStale: function()
184     {
185         this._contentReceived = false;
186     },
187
188     canRequestContentFromBackend: function()
189     {
190         // Implemented by subclasses.
191         console.error("Needs to be implemented by a subclass.");
192         return false;
193     },
194
195     requestContentFromBackend: function(callback)
196     {
197         // Implemented by subclasses.
198         console.error("Needs to be implemented by a subclass.");
199     },
200
201     requestContentFromBackendIfNeeded: function()
202     {
203         console.assert(this.canRequestContentFromBackend());
204         if (!this.canRequestContentFromBackend())
205             return;
206
207         if (!this._pendingContentRequestCallbacks.length)
208             return;
209
210         if (this._contentRequestResponsePending)
211             return;
212
213         this._contentRequestResponsePending = true;
214
215         if (this.requestContentFromBackend(this._processContent.bind(this)))
216             return;
217
218         // Since requestContentFromBackend returned false, just call _processContent,
219         // which will cause the pending callbacks to get null content.
220         this._processContent();
221     },
222
223     servicePendingContentRequests: function(force)
224     {
225         if (this._servicePendingContentRequestsTimeoutIdentifier) {
226             clearTimeout(this._servicePendingContentRequestsTimeoutIdentifier);
227             delete this._servicePendingContentRequestsTimeoutIdentifier;
228         }
229
230         // Force the content requests to be sent. To do this correctly we also force
231         // _contentReceived to be true so future calls to requestContent go through.
232         if (force)
233             this._contentReceived = true;
234
235         console.assert(this._contentReceived);
236         if (!this._contentReceived)
237             return;
238
239         // Move the callbacks into a local and clear _pendingContentRequestCallbacks so
240         // callbacks that might call requestContent again will not modify the array.
241         var callbacks = this._pendingContentRequestCallbacks;
242         this._pendingContentRequestCallbacks = [];
243
244         for (var i = 0; i < callbacks.length; ++i)
245             callbacks[i](this, this.content, this.contentIsBase64Encoded);
246     },
247
248     // Private
249
250     _processContent: function(error, content, base64Encoded)
251     {
252         if (error)
253             console.error(error);
254
255         this._contentRequestResponsePending = false;
256         this._contentReceived = true;
257
258         var revision = this.revisionForRequestedContent;
259
260         this._ignoreRevisionContentDidChangeEvent = true;
261         revision.content = content || null;
262         revision.contentIsBase64Encoded = base64Encoded || false;
263         delete this._ignoreRevisionContentDidChangeEvent;
264
265         this.servicePendingContentRequests();
266     }
267 };
268
269 WebInspector.SourceCode.prototype.__proto__ = WebInspector.Object.prototype;