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