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
26WebInspector.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    this._contentRequested = false;
35
36    var inheritedMIMEType = this._sourceMap.originalSourceCode instanceof WebInspector.Resource ? this._sourceMap.originalSourceCode.syntheticMIMEType : null;
37
38    var fileExtension = WebInspector.fileExtensionForURL(url);
39    var fileExtensionMIMEType = WebInspector.mimeTypeForFileExtension(fileExtension, true);
40
41    // FIXME: This is a layering violation. It should use a helper function on the
42    // Resource base-class to set _mimeType and _type.
43    this._mimeType = fileExtensionMIMEType || inheritedMIMEType || "text/javascript";
44    this._type = WebInspector.Resource.Type.fromMIMEType(this._mimeType);
45
46    // Mark the resource as loaded so it does not show a spinner in the sidebar.
47    // We will really load the resource the first time content is requested.
48    this.markAsFinished();
49};
50
51WebInspector.SourceMapResource.prototype = {
52    constructor: WebInspector.SourceMapResource,
53
54    // Public
55
56    get sourceMap()
57    {
58        return this._sourceMap;
59    },
60
61    get sourceMapDisplaySubpath()
62    {
63        var sourceMappingBasePathURLComponents = this._sourceMap.sourceMappingBasePathURLComponents;
64        var resourceURLComponents = this.urlComponents;
65
66        // Different schemes / hosts. Return the host + path of this resource.
67        if (resourceURLComponents.scheme !== sourceMappingBasePathURLComponents.scheme || resourceURLComponents.host !== sourceMappingBasePathURLComponents.host)
68            return resourceURLComponents.host + (resourceURLComponents.port ? (":" + resourceURLComponents.port) : "") + resourceURLComponents.path;
69
70        // Same host, but not a subpath of the base. This implies a ".." in the relative path.
71        if (!resourceURLComponents.path.startsWith(sourceMappingBasePathURLComponents.path))
72            return relativePath(resourceURLComponents.path, sourceMappingBasePathURLComponents.path);
73
74        // Same host. Just a subpath of the base.
75        return resourceURLComponents.path.substring(sourceMappingBasePathURLComponents.path.length, resourceURLComponents.length);
76    },
77
78    canRequestContentFromBackend: function()
79    {
80        return !this._contentRequested;
81    },
82
83    requestContentFromBackend: function(callback)
84    {
85        this._contentRequested = true;
86
87        // Revert the markAsFinished that was done in the constructor.
88        this.revertMarkAsFinished();
89
90        var inlineContent = this._sourceMap.sourceContent(this.url);
91        if (inlineContent) {
92            // Force inline content to be asynchronous to match the expected load pattern.
93            setTimeout(function() {
94                // FIXME: We don't know the MIME-type for inline content. Guess by analyzing the content?
95                sourceMapResourceLoaded.call(this, null, inlineContent, this.mimeType, 200);
96            }.bind(this));
97
98            return true;
99        }
100
101        function sourceMapResourceLoaded(error, body, mimeType, statusCode)
102        {
103            const base64encoded = false;
104
105            if (error || statusCode >= 400) {
106                this.markAsFailed();
107                callback(error, body, base64encoded);
108                return;
109            }
110
111            // FIXME: Add support for picking the best MIME-type. Right now the file extension is the best bet.
112            // The constructor set MIME-type based on the file extension and we ignore mimeType here.
113
114            this.markAsFinished();
115
116            callback(null, body, base64encoded);
117        }
118
119        if (!NetworkAgent.loadResource) {
120            sourceMapResourceLoaded.call(this, "error: no NetworkAgent.loadResource");
121            return false;
122        }
123
124        var frameIdentifier = null;
125        if (this._sourceMap.originalSourceCode instanceof WebInspector.Resource && this._sourceMap.originalSourceCode.parentFrame)
126            frameIdentifier = this._sourceMap.originalSourceCode.parentFrame.id;
127
128        if (!frameIdentifier)
129            frameIdentifier = WebInspector.frameResourceManager.mainFrame.id;
130
131        NetworkAgent.loadResource(frameIdentifier, this.url, sourceMapResourceLoaded.bind(this));
132
133        return true;
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
167WebInspector.SourceMapResource.prototype.__proto__ = WebInspector.Resource.prototype;
168