Web Inspector: Add ability to distinguish if a Script was parsed as a module
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Models / Script.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.Script = class Script extends WebInspector.SourceCode
27 {
28     constructor(target, id, range, url, sourceType, injected, sourceURL, sourceMapURL)
29     {
30         super();
31
32         console.assert(id);
33         console.assert(target instanceof WebInspector.Target);
34         console.assert(range instanceof WebInspector.TextRange);
35
36         this._target = target;
37         this._id = id || null;
38         this._range = range || null;
39         this._url = url || null;
40         this._sourceType = sourceType || WebInspector.Script.SourceType.Program;
41         this._sourceURL = sourceURL || null;
42         this._sourceMappingURL = sourceMapURL || null;
43         this._injected = injected || false;
44         this._dynamicallyAddedScriptElement = false;
45         this._scriptSyntaxTree = null;
46
47         this._resource = this._resolveResource();
48
49         // If this Script was a dynamically added <script> to a Document,
50         // do not associate with the Document resource, instead associate
51         // with the frame as a dynamic script.
52         if (this._resource && this._resource.type === WebInspector.Resource.Type.Document && !this._range.startLine && !this._range.startColumn) {
53             console.assert(this._resource.isMainResource());
54             let documentResource = this._resource;
55             this._resource = null;
56             this._dynamicallyAddedScriptElement = true;
57             documentResource.parentFrame.addExtraScript(this);
58             this._dynamicallyAddedScriptElementNumber = documentResource.parentFrame.extraScriptCollection.items.size;
59         } else if (this._resource)
60             this._resource.associateWithScript(this);
61
62         if (isWebInspectorConsoleEvaluationScript(this._sourceURL)) {
63             // Assign a unique number to the script object so it will stay the same.
64             this._uniqueDisplayNameNumber = this.constructor._nextUniqueConsoleDisplayNameNumber++;
65         }
66
67         if (this._sourceMappingURL)
68             WebInspector.sourceMapManager.downloadSourceMap(this._sourceMappingURL, this._url, this);
69     }
70
71     // Static
72
73     static resetUniqueDisplayNameNumbers()
74     {
75         WebInspector.Script._nextUniqueDisplayNameNumber = 1;
76         WebInspector.Script._nextUniqueConsoleDisplayNameNumber = 1;
77     }
78
79     // Public
80
81     get target() { return this._target; }
82     get id() { return this._id; }
83     get range() { return this._range; }
84     get url() { return this._url; }
85     get sourceType() { return this._sourceType; }
86     get sourceURL() { return this._sourceURL; }
87     get sourceMappingURL() { return this._sourceMappingURL; }
88     get injected() { return this._injected; }
89
90     get contentIdentifier()
91     {
92         if (this._url)
93             return this._url;
94
95         if (!this._sourceURL)
96             return null;
97
98         // Since reused content identifiers can cause breakpoints
99         // to show up in completely unrelated files, sourceURLs should
100         // be unique where possible. The checks below exclude cases
101         // where sourceURLs are intentionally reused and we would never
102         // expect a breakpoint to be persisted across sessions.
103         if (isWebInspectorConsoleEvaluationScript(this._sourceURL))
104             return null;
105
106         if (isWebInspectorInternalScript(this._sourceURL))
107             return null;
108
109         return this._sourceURL;
110     }
111
112     get urlComponents()
113     {
114         if (!this._urlComponents)
115             this._urlComponents = parseURL(this._url);
116         return this._urlComponents;
117     }
118
119     get mimeType()
120     {
121         return this._resource.mimeType;
122     }
123
124     get displayName()
125     {
126         if (this._url && !this._dynamicallyAddedScriptElement)
127             return WebInspector.displayNameForURL(this._url, this.urlComponents);
128
129         if (isWebInspectorConsoleEvaluationScript(this._sourceURL)) {
130             console.assert(this._uniqueDisplayNameNumber);
131             return WebInspector.UIString("Console Evaluation %d").format(this._uniqueDisplayNameNumber);
132         }
133
134         if (this._sourceURL) {
135             if (!this._sourceURLComponents)
136                 this._sourceURLComponents = parseURL(this._sourceURL);
137             return WebInspector.displayNameForURL(this._sourceURL, this._sourceURLComponents);
138         }
139
140         if (this._dynamicallyAddedScriptElement)
141             return WebInspector.UIString("Script Element %d").format(this._dynamicallyAddedScriptElementNumber);
142
143         // Assign a unique number to the script object so it will stay the same.
144         if (!this._uniqueDisplayNameNumber)
145             this._uniqueDisplayNameNumber = this.constructor._nextUniqueDisplayNameNumber++;
146
147         return WebInspector.UIString("Anonymous Script %d").format(this._uniqueDisplayNameNumber);
148     }
149
150     get displayURL()
151     {
152         const isMultiLine = true;
153         const dataURIMaxSize = 64;
154
155         if (this._url)
156             return WebInspector.truncateURL(this._url, isMultiLine, dataURIMaxSize);
157         if (this._sourceURL)
158             return WebInspector.truncateURL(this._sourceURL, isMultiLine, dataURIMaxSize);
159         return null;
160     }
161
162     get dynamicallyAddedScriptElement()
163     {
164         return this._dynamicallyAddedScriptElement;
165     }
166
167     get anonymous()
168     {
169         return !this._resource && !this._url && !this._sourceURL;
170     }
171
172     get resource()
173     {
174         return this._resource;
175     }
176
177     get scriptSyntaxTree()
178     {
179         return this._scriptSyntaxTree;
180     }
181
182     isMainResource()
183     {
184         return this._target.mainResource === this;
185     }
186
187     requestContentFromBackend()
188     {
189         if (!this._id) {
190             // There is no identifier to request content with. Return false to cause the
191             // pending callbacks to get null content.
192             return Promise.reject(new Error("There is no identifier to request content with."));
193         }
194
195         return this._target.DebuggerAgent.getScriptSource(this._id);
196     }
197
198     saveIdentityToCookie(cookie)
199     {
200         cookie[WebInspector.Script.URLCookieKey] = this.url;
201         cookie[WebInspector.Script.DisplayNameCookieKey] = this.displayName;
202     }
203
204     requestScriptSyntaxTree(callback)
205     {
206         if (this._scriptSyntaxTree) {
207             setTimeout(() => { callback(this._scriptSyntaxTree); }, 0);
208             return;
209         }
210
211         var makeSyntaxTreeAndCallCallback = (content) => {
212             this._makeSyntaxTree(content);
213             callback(this._scriptSyntaxTree);
214         };
215
216         var content = this.content;
217         if (!content && this._resource && this._resource.type === WebInspector.Resource.Type.Script && this._resource.finished)
218             content = this._resource.content;
219         if (content) {
220             setTimeout(makeSyntaxTreeAndCallCallback, 0, content);
221             return;
222         }
223
224         this.requestContent().then(function(parameters) {
225             makeSyntaxTreeAndCallCallback(parameters.sourceCode.content);
226         }).catch(function(error) {
227             makeSyntaxTreeAndCallCallback(null);
228         });
229     }
230
231     // Private
232
233     _resolveResource()
234     {
235         // FIXME: We should be able to associate a Script with a Resource through identifiers,
236         // we shouldn't need to lookup by URL, which is not safe with frames, where there might
237         // be multiple resources with the same URL.
238         // <rdar://problem/13373951> Scripts should be able to associate directly with a Resource
239
240         // No URL, no resource.
241         if (!this._url)
242             return null;
243
244         let resolver = WebInspector.frameResourceManager;
245         if (this._target !== WebInspector.mainTarget)
246             resolver = this._target.resourceCollection;
247
248         try {
249             // Try with the Script's full URL.
250             let resource = resolver.resourceForURL(this._url);
251             if (resource)
252                 return resource;
253
254             // Try with the Script's full decoded URL.
255             let decodedURL = decodeURI(this._url);
256             if (decodedURL !== this._url) {
257                 resource = resolver.resourceForURL(decodedURL);
258                 if (resource)
259                     return resource;
260             }
261
262             // Next try removing any fragment in the original URL.
263             let urlWithoutFragment = removeURLFragment(this._url);
264             if (urlWithoutFragment !== this._url) {
265                 resource = resolver.resourceForURL(urlWithoutFragment);
266                 if (resource)
267                     return resource;
268             }
269
270             // Finally try removing any fragment in the decoded URL.
271             let decodedURLWithoutFragment = removeURLFragment(decodedURL);
272             if (decodedURLWithoutFragment !== decodedURL) {
273                 resource = resolver.resourceForURL(decodedURLWithoutFragment);
274                 if (resource)
275                     return resource;
276             }
277         } catch (e) {
278             // Ignore possible URIErrors.
279         }
280
281         return null;
282     }
283
284     _makeSyntaxTree(sourceText)
285     {
286         if (this._scriptSyntaxTree || !sourceText)
287             return;
288
289         this._scriptSyntaxTree = new WebInspector.ScriptSyntaxTree(sourceText, this);
290     }
291 };
292
293 WebInspector.Script.SourceType = {
294     Program: "script-source-type-program",
295     Module: "script-source-type-module",
296 };
297
298 WebInspector.Script.TypeIdentifier = "script";
299 WebInspector.Script.URLCookieKey = "script-url";
300 WebInspector.Script.DisplayNameCookieKey = "script-display-name";
301
302 WebInspector.Script._nextUniqueDisplayNameNumber = 1;
303 WebInspector.Script._nextUniqueConsoleDisplayNameNumber = 1;