3756a4bfc5e5f97f4b7933462346ae110e3d1c22
[WebKit-https.git] / Source / WebCore / inspector / front-end / RawSourceCode.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 // RawSourceCode represents JavaScript resource or HTML resource with inlined scripts
32 // as it came from network.
33
34 /**
35  * @constructor
36  * @extends {WebInspector.Object}
37  * @param {string} id
38  * @param {WebInspector.Script} script
39  * @param {WebInspector.Resource} resource
40  * @param {WebInspector.ScriptFormatter} formatter
41  * @param {boolean} formatted
42  */
43 WebInspector.RawSourceCode = function(id, script, resource, formatter, formatted)
44 {
45     this.id = id;
46     this.url = script.sourceURL;
47     this.isContentScript = script.isContentScript;
48     this.sourceMapURL = script.sourceMapURL;
49     this._scripts = [script];
50     this._formatter = formatter;
51     this._formatted = formatted;
52     this._resource = resource;
53     this.messages = [];
54
55     this._useTemporaryContent = this._resource && !this._resource.finished;
56     this._hasNewScripts = true;
57     if (!this._useTemporaryContent)
58         this._updateSourceMapping();
59     else if (this._resource)
60         this._resource.addEventListener("finished", this._resourceFinished.bind(this));
61 }
62
63 WebInspector.RawSourceCode.Events = {
64     SourceMappingUpdated: "source-mapping-updated"
65 }
66
67 WebInspector.RawSourceCode.prototype = {
68     /**
69      * @param {WebInspector.Script} script
70      */
71     addScript: function(script)
72     {
73         this._scripts.push(script);
74         this._hasNewScripts = true;
75     },
76
77     /**
78      * @return {WebInspector.RawSourceCode.SourceMapping}
79      */
80     get sourceMapping()
81     {
82         return this._sourceMapping;
83     },
84
85     /**
86      * @param {boolean} formatted
87      */
88     setFormatted: function(formatted)
89     {
90         if (this._formatted === formatted)
91             return;
92         this._formatted = formatted;
93         this._updateSourceMapping();
94     },
95
96     /**
97      * @param {WebInspector.CompilerSourceMapping} compilerSourceMapping
98      */
99     setCompilerSourceMapping: function(compilerSourceMapping)
100     {
101         if (compilerSourceMapping)
102             this._useTemporaryContent = false;
103         this._compilerSourceMapping = compilerSourceMapping;
104         this._updateSourceMapping();
105     },
106
107     _resourceFinished: function()
108     {
109         if (this._compilerSourceMapping)
110             return;
111         this._useTemporaryContent = false;
112         this._updateSourceMapping();
113     },
114
115     /**
116      * @param {number} lineNumber
117      * @param {number} columnNumber
118      * @return {WebInspector.Script}
119      */
120     _scriptForRawLocation: function(lineNumber, columnNumber)
121     {
122         var closestScript = this._scripts[0];
123         for (var i = 1; i < this._scripts.length; ++i) {
124             var script = this._scripts[i];
125             if (script.lineOffset > lineNumber || (script.lineOffset === lineNumber && script.columnOffset > columnNumber))
126                 continue;
127             if (script.lineOffset > closestScript.lineOffset ||
128                 (script.lineOffset === closestScript.lineOffset && script.columnOffset > closestScript.columnOffset))
129                 closestScript = script;
130         }
131         return closestScript;
132     },
133
134     /**
135      * @param {WebInspector.Script} script
136      */
137     forceUpdateSourceMapping: function(script)
138     {
139         if (!this._useTemporaryContent || !this._hasNewScripts)
140             return;
141         this._hasNewScripts = false;
142         this._updateSourceMapping();
143     },
144
145     _updateSourceMapping: function()
146     {
147         if (this._updatingSourceMapping) {
148             this._updateNeeded = true;
149             return;
150         }
151         this._updatingSourceMapping = true;
152         this._updateNeeded = false;
153
154         this._createSourceMapping(didCreateSourceMapping.bind(this));
155
156         /**
157          * @this {WebInspector.RawSourceCode}
158          * @param {WebInspector.RawSourceCode.SourceMapping} sourceMapping
159          */
160         function didCreateSourceMapping(sourceMapping)
161         {
162             this._updatingSourceMapping = false;
163             if (!sourceMapping)
164                 return;
165             if (!this._updateNeeded)
166                 this._saveSourceMapping(sourceMapping);
167             else
168                 this._updateSourceMapping();
169         }
170     },
171
172     _createContentProvider: function()
173     {
174         if (this._resource && this._resource.finished)
175             return new WebInspector.ResourceContentProvider(this._resource);
176         if (this._scripts.length === 1 && !this._scripts[0].lineOffset && !this._scripts[0].columnOffset)
177             return new WebInspector.ScriptContentProvider(this._scripts[0]);
178         return new WebInspector.ConcatenatedScriptsContentProvider(this._scripts);
179     },
180
181     /**
182      * @param {function(WebInspector.RawSourceCode.SourceMapping)} callback
183      */
184     _createSourceMapping: function(callback)
185     {
186         if (this._compilerSourceMapping) {
187             var success = this._compilerSourceMapping.load();
188             if (!success) {
189                 delete this._compilerSourceMapping;
190                 callback(null);
191                 return;
192             }
193             var uiSourceCodeList = [];
194             var sourceURLs = this._compilerSourceMapping.sources();
195             for (var i = 0; i < sourceURLs.length; ++i) {
196                 var sourceURL = sourceURLs[i];
197                 var contentProvider = new WebInspector.CompilerSourceMappingContentProvider(sourceURL, this._compilerSourceMapping);
198                 var uiSourceCode = new WebInspector.UISourceCode(sourceURL, sourceURL, this.isContentScript, this, contentProvider);
199                 uiSourceCodeList.push(uiSourceCode);
200             }
201             var sourceMapping = new WebInspector.RawSourceCode.CompilerSourceMapping(this, uiSourceCodeList, this._compilerSourceMapping);
202             callback(sourceMapping);
203             return;
204         }
205
206         var originalContentProvider = this._createContentProvider();
207         if (!this._formatted) {
208             var uiSourceCode = new WebInspector.UISourceCode(this.url, this.url, this.isContentScript, this, originalContentProvider);
209             var sourceMapping = new WebInspector.RawSourceCode.PlainSourceMapping(this, uiSourceCode);
210             callback(sourceMapping);
211             return;
212         }
213
214         /**
215          * @this {WebInspector.RawSourceCode}
216          * @param {string} mimeType
217          * @param {string} content
218          */
219         function didRequestContent(mimeType, content)
220         {
221             /**
222              * @this {WebInspector.RawSourceCode}
223              * @param {string} formattedContent
224              * @param {WebInspector.FormattedSourceMapping} mapping
225              */
226             function didFormatContent(formattedContent, mapping)
227             {
228                 var contentProvider = new WebInspector.StaticContentProvider(mimeType, formattedContent)
229                 var uiSourceCode = new WebInspector.UISourceCode("deobfuscated:" + this.url, this.url, this.isContentScript, this, contentProvider);
230                 var sourceMapping = new WebInspector.RawSourceCode.FormattedSourceMapping(this, uiSourceCode, mapping);
231                 callback(sourceMapping);
232             }
233             this._formatter.formatContent(mimeType, content, didFormatContent.bind(this));
234         }
235         originalContentProvider.requestContent(didRequestContent.bind(this));
236     },
237
238     /**
239      * @param {WebInspector.RawSourceCode.SourceMapping} sourceMapping
240      */
241     _saveSourceMapping: function(sourceMapping)
242     {
243         var oldSourceMapping;
244         if (this._sourceMapping)
245             oldSourceMapping = this._sourceMapping;
246         this._sourceMapping = sourceMapping;
247         this.dispatchEventToListeners(WebInspector.RawSourceCode.Events.SourceMappingUpdated, { oldSourceMapping: oldSourceMapping });
248     }
249 }
250
251 WebInspector.RawSourceCode.prototype.__proto__ = WebInspector.Object.prototype;
252
253 /**
254  * @interface
255  */
256 WebInspector.RawSourceCode.SourceMapping = function()
257 {
258 }
259
260 WebInspector.RawSourceCode.SourceMapping.prototype = {
261     /**
262      * @param {DebuggerAgent.Location} rawLocation
263      * @return {WebInspector.UILocation}
264      */
265     rawLocationToUILocation: function(rawLocation) { },
266
267     /**
268      * @param {WebInspector.UISourceCode} uiSourceCode
269      * @param {number} lineNumber
270      * @param {number} columnNumber
271      * @return {DebuggerAgent.Location}
272      */
273     uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber) { }
274 }
275
276 /**
277  * @constructor
278  * @implements {WebInspector.RawSourceCode.SourceMapping}
279  * @param {WebInspector.RawSourceCode} rawSourceCode
280  * @param {WebInspector.UISourceCode} uiSourceCode
281  */
282 WebInspector.RawSourceCode.PlainSourceMapping = function(rawSourceCode, uiSourceCode)
283 {
284     this._rawSourceCode = rawSourceCode;
285     this._uiSourceCodeList = [uiSourceCode];
286 }
287
288 WebInspector.RawSourceCode.PlainSourceMapping.prototype = {
289     /**
290      * @param {DebuggerAgent.Location} rawLocation
291      * @return {WebInspector.UILocation}
292      */
293     rawLocationToUILocation: function(rawLocation)
294     {
295         return new WebInspector.UILocation(this._uiSourceCodeList[0], rawLocation.lineNumber, rawLocation.columnNumber || 0);
296     },
297
298     /**
299      * @param {WebInspector.UISourceCode} uiSourceCode
300      * @param {number} lineNumber
301      * @param {number} columnNumber
302      * @return {DebuggerAgent.Location}
303      */
304     uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber)
305     {
306         console.assert(uiSourceCode === this._uiSourceCodeList[0]);
307         var rawLocation = { lineNumber: lineNumber, columnNumber: columnNumber };
308         rawLocation.scriptId = this._rawSourceCode._scriptForRawLocation(rawLocation.lineNumber, rawLocation.columnNumber).scriptId;
309         return /** @type {DebuggerAgent.Location} */ rawLocation;
310     },
311
312     /**
313      * @return {Array.<WebInspector.UISourceCode>}
314      */
315     uiSourceCodeList: function()
316     {
317         return this._uiSourceCodeList;
318     }
319 }
320
321 /**
322  * @constructor
323  * @implements {WebInspector.RawSourceCode.SourceMapping}
324  * @param {WebInspector.RawSourceCode} rawSourceCode
325  * @param {WebInspector.UISourceCode} uiSourceCode
326  * @param {WebInspector.FormattedSourceMapping} mapping
327  */
328 WebInspector.RawSourceCode.FormattedSourceMapping = function(rawSourceCode, uiSourceCode, mapping)
329 {
330     this._rawSourceCode = rawSourceCode;
331     this._uiSourceCodeList = [uiSourceCode];
332     this._mapping = mapping;
333 }
334
335 WebInspector.RawSourceCode.FormattedSourceMapping.prototype = {
336     /**
337      * @param {DebuggerAgent.Location} rawLocation
338      */
339     rawLocationToUILocation: function(rawLocation)
340     {
341         var location = this._mapping.originalToFormatted(rawLocation);
342         return new WebInspector.UILocation(this._uiSourceCodeList[0], location.lineNumber, location.columnNumber || 0);
343     },
344
345     /**
346      * @param {WebInspector.UISourceCode} uiSourceCode
347      * @param {number} lineNumber
348      * @param {number} columnNumber
349      * @return {DebuggerAgent.Location}
350      */
351     uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber)
352     {
353         console.assert(uiSourceCode === this._uiSourceCodeList[0]);
354         var rawLocation = this._mapping.formattedToOriginal({ lineNumber: lineNumber, columnNumber: columnNumber });
355         rawLocation.scriptId = this._rawSourceCode._scriptForRawLocation(rawLocation.lineNumber, rawLocation.columnNumber).scriptId;
356         return rawLocation;
357     },
358
359     /**
360      * @return {Array.<WebInspector.UISourceCode>}
361      */
362     uiSourceCodeList: function()
363     {
364         return this._uiSourceCodeList;
365     }
366 }
367
368 /**
369  * @constructor
370  * @implements {WebInspector.RawSourceCode.SourceMapping}
371  * @param {WebInspector.RawSourceCode} rawSourceCode
372  * @param {Array.<WebInspector.UISourceCode>} uiSourceCodeList
373  * @param {WebInspector.CompilerSourceMapping} mapping
374  */
375 WebInspector.RawSourceCode.CompilerSourceMapping = function(rawSourceCode, uiSourceCodeList, mapping)
376 {
377     this._rawSourceCode = rawSourceCode;
378     this._uiSourceCodeList = uiSourceCodeList;
379     this._mapping = mapping;
380     this._uiSourceCodeByURL = {};
381     for (var i = 0; i < uiSourceCodeList.length; ++i)
382         this._uiSourceCodeByURL[uiSourceCodeList[i].url] = uiSourceCodeList[i];
383 }
384
385 WebInspector.RawSourceCode.CompilerSourceMapping.prototype = {
386     /**
387      * @param {DebuggerAgent.Location} rawLocation
388      */
389     rawLocationToUILocation: function(rawLocation)
390     {
391         var location = this._mapping.compiledLocationToSourceLocation(rawLocation.lineNumber, rawLocation.columnNumber || 0);
392         var uiSourceCode = this._uiSourceCodeByURL[location.sourceURL];
393         return new WebInspector.UILocation(uiSourceCode, location.lineNumber, location.columnNumber);
394     },
395
396     /**
397      * @param {WebInspector.UISourceCode} uiSourceCode
398      * @param {number} lineNumber
399      * @param {number} columnNumber
400      * @return {DebuggerAgent.Location}
401      */
402     uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber)
403     {
404         var rawLocation = this._mapping.sourceLocationToCompiledLocation(uiSourceCode.url, lineNumber);
405         rawLocation.scriptId = this._rawSourceCode._scriptForRawLocation(rawLocation.lineNumber, rawLocation.columnNumber).scriptId;
406         return /** @type {DebuggerAgent.Location} */ rawLocation;
407     },
408
409     /**
410      * @return {Array.<WebInspector.UISourceCode>}
411      */
412     uiSourceCodeList: function()
413     {
414         return this._uiSourceCodeList;
415     }
416 }
417
418 /**
419  * @constructor
420  * @param {WebInspector.UISourceCode} uiSourceCode
421  * @param {number} lineNumber
422  * @param {number} columnNumber
423  */
424 WebInspector.UILocation = function(uiSourceCode, lineNumber, columnNumber)
425 {
426     this.uiSourceCode = uiSourceCode;
427     this.lineNumber = lineNumber;
428     this.columnNumber = columnNumber;
429 }