Web Inspector: SourceCode.requestContent should return a promise
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Models / SourceMapResource.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.SourceMapResource = function(url, sourceMap)
27 {
28     WebInspector.Resource.call(this, url, null);
29
30     console.assert(url);
31     console.assert(sourceMap);
32
33     this._sourceMap = sourceMap;
34
35     var inheritedMIMEType = this._sourceMap.originalSourceCode instanceof WebInspector.Resource ? this._sourceMap.originalSourceCode.syntheticMIMEType : null;
36
37     var fileExtension = WebInspector.fileExtensionForURL(url);
38     var fileExtensionMIMEType = WebInspector.mimeTypeForFileExtension(fileExtension, true);
39
40     // FIXME: This is a layering violation. It should use a helper function on the
41     // Resource base-class to set _mimeType and _type.
42     this._mimeType = fileExtensionMIMEType || inheritedMIMEType || "text/javascript";
43     this._type = WebInspector.Resource.typeFromMIMEType(this._mimeType);
44
45     // Mark the resource as loaded so it does not show a spinner in the sidebar.
46     // We will really load the resource the first time content is requested.
47     this.markAsFinished();
48 };
49
50 WebInspector.SourceMapResource.prototype = {
51     constructor: WebInspector.SourceMapResource,
52
53     // Public
54
55     get sourceMap()
56     {
57         return this._sourceMap;
58     },
59
60     get sourceMapDisplaySubpath()
61     {
62         var sourceMappingBasePathURLComponents = this._sourceMap.sourceMappingBasePathURLComponents;
63         var resourceURLComponents = this.urlComponents;
64
65         // Different schemes / hosts. Return the host + path of this resource.
66         if (resourceURLComponents.scheme !== sourceMappingBasePathURLComponents.scheme || resourceURLComponents.host !== sourceMappingBasePathURLComponents.host)
67             return resourceURLComponents.host + (resourceURLComponents.port ? (":" + resourceURLComponents.port) : "") + resourceURLComponents.path;
68
69         // Same host, but not a subpath of the base. This implies a ".." in the relative path.
70         if (!resourceURLComponents.path.startsWith(sourceMappingBasePathURLComponents.path))
71             return relativePath(resourceURLComponents.path, sourceMappingBasePathURLComponents.path);
72
73         // Same host. Just a subpath of the base.
74         return resourceURLComponents.path.substring(sourceMappingBasePathURLComponents.path.length, resourceURLComponents.length);
75     },
76
77     requestContentFromBackend: function(callback)
78     {
79         // Revert the markAsFinished that was done in the constructor.
80         this.revertMarkAsFinished();
81
82         var inlineContent = this._sourceMap.sourceContent(this.url);
83         if (inlineContent) {
84             // Force inline content to be asynchronous to match the expected load pattern.
85             // FIXME: We don't know the MIME-type for inline content. Guess by analyzing the content?
86             // Returns a promise.
87             return sourceMapResourceLoaded.call(this, null, inlineContent, this.mimeType, 200);
88         }
89
90         function sourceMapResourceLoadError(error, body, mimeType, statusCode)
91         {
92             this.markAsFailed();
93             return Promise.resolve({
94                 error: error,
95                 content: body.content,
96                 mimeType: mimeType,
97                 statusCode: statusCode
98             });
99         }
100
101         function sourceMapResourceLoaded(body, mimeType, statusCode)
102         {
103             const base64encoded = false;
104
105             if (statusCode >= 400)
106                 return sourceMapResourceLoadError(error, body, mimeType, statusCode);
107
108             // FIXME: Add support for picking the best MIME-type. Right now the file extension is the best bet.
109             // The constructor set MIME-type based on the file extension and we ignore mimeType here.
110
111             this.markAsFinished();
112
113             return Promise.resolve({
114                 content: body.content,
115                 mimeType: mimeType,
116                 base64encoded: base64encoded,
117                 statusCode: statusCode
118             });
119         }
120
121         if (!NetworkAgent.loadResource) {
122             sourceMapResourceLoaded.call(this, "error: no NetworkAgent.loadResource");
123             return Promise.reject(new Error("No NetworkAgent.loadResource"));
124         }
125
126         var frameIdentifier = null;
127         if (this._sourceMap.originalSourceCode instanceof WebInspector.Resource && this._sourceMap.originalSourceCode.parentFrame)
128             frameIdentifier = this._sourceMap.originalSourceCode.parentFrame.id;
129
130         if (!frameIdentifier)
131             frameIdentifier = WebInspector.frameResourceManager.mainFrame.id;
132
133         return NetworkAgent.loadResource.promise(frameIdentifier, this.url).then(sourceMapResourceLoaded.bind(this)).catch(sourceMapResourceLoadError.bind(this));
134     },
135
136     createSourceCodeLocation: function(lineNumber, columnNumber)
137     {
138         // SourceCodeLocations are always constructed with raw resources and raw locations. Lookup the raw location.
139         var entry = this._sourceMap.findEntryReversed(this.url, lineNumber);
140         var rawLineNumber = entry[0];
141         var rawColumnNumber = entry[1];
142
143         // If the raw location is an inline script we need to include that offset.
144         var originalSourceCode = this._sourceMap.originalSourceCode;
145         if (originalSourceCode instanceof WebInspector.Script) {
146             if (rawLineNumber === 0)
147                 rawColumnNumber += originalSourceCode.range.startColumn;
148             rawLineNumber += originalSourceCode.range.startLine;
149         }
150
151         // Create the SourceCodeLocation and since we already know the the mapped location set it directly.
152         var location = originalSourceCode.createSourceCodeLocation(rawLineNumber, rawColumnNumber);
153         location._setMappedLocation(this, lineNumber, columnNumber);
154         return location;
155     },
156
157     createSourceCodeTextRange: function(textRange)
158     {
159         // SourceCodeTextRanges are always constructed with raw resources and raw locations.
160         // However, we can provide the most accurate mapped locations in construction.
161         var startSourceCodeLocation = this.createSourceCodeLocation(textRange.startLine, textRange.startColumn);
162         var endSourceCodeLocation = this.createSourceCodeLocation(textRange.endLine, textRange.endColumn);
163         return new WebInspector.SourceCodeTextRange(this._sourceMap.originalSourceCode, startSourceCodeLocation, endSourceCodeLocation);
164     }
165 };
166
167 WebInspector.SourceMapResource.prototype.__proto__ = WebInspector.Resource.prototype;