Web Inspector: Create JavaScriptSources based on network resources.
[WebKit-https.git] / Source / WebCore / inspector / front-end / ResourceScriptMapping.js
1 /*
2  * Copyright (C) 2012 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  * @constructor
33  * @implements {WebInspector.SourceMapping}
34  * @param {WebInspector.Workspace} workspace
35  */
36 WebInspector.ResourceScriptMapping = function(workspace)
37 {
38     this._workspace = workspace;
39     this._workspace.addEventListener(WebInspector.Workspace.Events.ProjectWillReset, this._reset, this);
40     this._workspace.addEventListener(WebInspector.UISourceCodeProvider.Events.UISourceCodeAdded, this._uiSourceCodeAddedToWorkspace, this);
41
42     /** @type {Object.<string, WebInspector.UISourceCode>} */
43     this._uiSourceCodeForScriptId = {};
44     this._scriptIdForUISourceCode = new Map();
45     this._temporaryUISourceCodes = new Map();
46     /** @type {Object.<string, number>} */
47     this._nextDynamicScriptIndexForURL = {};
48 }
49
50 WebInspector.ResourceScriptMapping.prototype = {
51     /**
52      * @param {WebInspector.RawLocation} rawLocation
53      * @return {WebInspector.UILocation}
54      */
55     rawLocationToUILocation: function(rawLocation)
56     {
57         var debuggerModelLocation = /** @type {WebInspector.DebuggerModel.Location} */ rawLocation;
58         var script = WebInspector.debuggerModel.scriptForId(debuggerModelLocation.scriptId);
59         var uiSourceCode = this._uiSourceCodeForScriptId[debuggerModelLocation.scriptId];
60         return new WebInspector.UILocation(uiSourceCode, debuggerModelLocation.lineNumber, debuggerModelLocation.columnNumber || 0);
61     },
62
63     /**
64      * @param {WebInspector.UISourceCode} uiSourceCode
65      * @param {number} lineNumber
66      * @param {number} columnNumber
67      * @return {WebInspector.DebuggerModel.Location}
68      */
69     uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber)
70     {
71         var scriptId = this._scriptIdForUISourceCode.get(uiSourceCode);
72         var script = WebInspector.debuggerModel.scriptForId(scriptId);
73         return WebInspector.debuggerModel.createRawLocation(script, lineNumber, columnNumber);
74     },
75
76     /**
77      * @param {WebInspector.Script} script
78      */
79     addScript: function(script)
80     {
81         console.assert(!this._uiSourceCodeForScriptId[script.scriptId]);
82
83         var isDynamicScript = false;
84         if (!script.isAnonymousScript()) {
85             var uiSourceCode = this._workspace.uiSourceCodeForURL(script.sourceURL);
86             isDynamicScript = !!uiSourceCode && uiSourceCode.contentType() === WebInspector.resourceTypes.Document && !script.isInlineScript();
87             if (uiSourceCode && !isDynamicScript && !this._temporaryUISourceCodes.get(uiSourceCode))
88                 this._bindUISourceCodeToScripts(uiSourceCode, [script]);
89         }
90         if (!this._uiSourceCodeForScriptId[script.scriptId])
91             this._addOrReplaceTemporaryUISourceCode(script, isDynamicScript);
92
93         console.assert(this._uiSourceCodeForScriptId[script.scriptId]);
94     },
95
96     /**
97      * @param {WebInspector.UISourceCode} uiSourceCode
98      * @param {Array.<WebInspector.Script>} scripts
99      */
100     _bindUISourceCodeToScripts: function(uiSourceCode, scripts)
101     {
102         console.assert(scripts.length);
103
104         for (var i = 0; i < scripts.length; ++i) {
105             this._uiSourceCodeForScriptId[scripts[i].scriptId] = uiSourceCode;
106             scripts[i].setSourceMapping(this);
107         }
108         uiSourceCode.isContentScript = scripts[0].isContentScript;
109         uiSourceCode.setSourceMapping(this);
110         this._scriptIdForUISourceCode.put(uiSourceCode, scripts[0].scriptId);
111     },
112
113     /**
114      * @param {string} sourceURL
115      * @param {boolean} isInlineScript
116      * @return {Array.<WebInspector.Script>}
117      */
118     _scriptsForSourceURL: function(sourceURL, isInlineScript)
119     {
120         function filter(script)
121         {
122             return script.sourceURL === sourceURL && script.isInlineScript() === isInlineScript;
123         }
124
125         return Object.values(WebInspector.debuggerModel.scripts).filter(filter);
126     },
127
128     /**
129      * @param {WebInspector.Script} script
130      * @param {boolean} isDynamicScript
131      */
132     _addOrReplaceTemporaryUISourceCode: function(script, isDynamicScript)
133     {
134         var scripts = script.isInlineScript() ? this._scriptsForSourceURL(script.sourceURL, true) : [script];
135
136         var oldUISourceCode;
137         for (var i = 0; i < scripts.length; ++i) {
138             oldUISourceCode = this._uiSourceCodeForScriptId[scripts[i].scriptId];
139             if (oldUISourceCode)
140                 break;
141         }
142         console.assert(!oldUISourceCode || this._temporaryUISourceCodes.get(oldUISourceCode));
143
144         var contentProvider = script.isInlineScript() ? new WebInspector.ConcatenatedScriptsContentProvider(scripts) : script;
145         var url = script.sourceURL;
146         if (isDynamicScript) {
147             var nextIndex = this._nextDynamicScriptIndexForURL[script.sourceURL] || 1;
148             url += " (" + nextIndex + ")";
149             this._nextDynamicScriptIndexForURL[script.sourceURL] = nextIndex + 1;
150         }
151         var uiSourceCode = new WebInspector.JavaScriptSource(url, null, contentProvider, !script.isInlineScript());
152         this._temporaryUISourceCodes.put(uiSourceCode, uiSourceCode);
153         this._bindUISourceCodeToScripts(uiSourceCode, scripts);
154
155         if (!script.sourceURL)
156             return uiSourceCode;
157
158         if (oldUISourceCode)
159             this._uiSourceCodeReplaced(oldUISourceCode, uiSourceCode);
160         else
161             this._workspace.project().addUISourceCode(uiSourceCode);
162         return uiSourceCode;
163     },
164
165     _uiSourceCodeAddedToWorkspace: function(event)
166     {
167         var uiSourceCode = /** @type {WebInspector.UISourceCode} */ event.data;
168         console.assert(!this._scriptIdForUISourceCode.get(uiSourceCode) || this._temporaryUISourceCodes.get(uiSourceCode));
169         if (!uiSourceCode.url || this._temporaryUISourceCodes.get(uiSourceCode))
170             return;
171         this._addUISourceCode(uiSourceCode);
172     },
173
174     /**
175      * @param {WebInspector.UISourceCode} uiSourceCode
176      */
177     _addUISourceCode: function(uiSourceCode)
178     {
179         var isInlineScript;
180         switch (uiSourceCode.contentType()) {
181         case WebInspector.resourceTypes.Document:
182             isInlineScript = true;
183             break;
184         case WebInspector.resourceTypes.Script:
185             isInlineScript = false;
186             break;
187         default:
188             return;
189         }
190
191         var scripts = this._scriptsForSourceURL(uiSourceCode.url, isInlineScript);
192         if (!scripts.length)
193             return;
194
195         var oldUISourceCode = this._uiSourceCodeForScriptId[scripts[0].scriptId];
196         this._bindUISourceCodeToScripts(uiSourceCode, scripts);
197
198         if (oldUISourceCode) {
199             console.assert(this._temporaryUISourceCodes.get(oldUISourceCode));
200             this._uiSourceCodeReplaced(oldUISourceCode, uiSourceCode);
201         }
202
203         console.assert(this._scriptIdForUISourceCode.get(uiSourceCode) && !this._temporaryUISourceCodes.get(uiSourceCode));
204     },
205
206     /**
207      * @param {WebInspector.UISourceCode} oldUISourceCode
208      * @param {WebInspector.UISourceCode} uiSourceCode
209      */
210     _uiSourceCodeReplaced: function(oldUISourceCode, uiSourceCode)
211     {
212         this._temporaryUISourceCodes.remove(oldUISourceCode);
213         this._scriptIdForUISourceCode.remove(oldUISourceCode);
214         this._workspace.project().replaceUISourceCode(oldUISourceCode, uiSourceCode);
215     },
216
217     _reset: function()
218     {
219         this._uiSourceCodeForScriptId = {};
220         this._scriptIdForUISourceCode.clear();
221         this._temporaryUISourceCodes.clear();
222         this._nextDynamicScriptIndexForURL = {};
223     },
224 }