Home | History | Annotate | Download | only in front_end
      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  * Implements Source Map V3 model. See http://code.google.com/p/closure-compiler/wiki/SourceMaps
     33  * for format description.
     34  * @constructor
     35  * @param {string} sourceMappingURL
     36  * @param {SourceMapV3} payload
     37  */
     38 WebInspector.SourceMap = function(sourceMappingURL, payload)
     39 {
     40     if (!WebInspector.SourceMap.prototype._base64Map) {
     41         const base64Digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
     42         WebInspector.SourceMap.prototype._base64Map = {};
     43         for (var i = 0; i < base64Digits.length; ++i)
     44             WebInspector.SourceMap.prototype._base64Map[base64Digits.charAt(i)] = i;
     45     }
     46 
     47     this._sourceMappingURL = sourceMappingURL;
     48     this._reverseMappingsBySourceURL = {};
     49     this._mappings = [];
     50     this._sources = {};
     51     this._sourceContentByURL = {};
     52     this._parseMappingPayload(payload);
     53 }
     54 
     55 WebInspector.SourceMap._sourceMapRequestHeaderName = "X-Source-Map-Request-From";
     56 WebInspector.SourceMap._sourceMapRequestHeaderValue = "inspector";
     57 
     58 WebInspector.SourceMap.hasSourceMapRequestHeader = function(request)
     59 {
     60     return request && request.requestHeaderValue(WebInspector.SourceMap._sourceMapRequestHeaderName) === WebInspector.SourceMap._sourceMapRequestHeaderValue;
     61 }
     62 
     63 /**
     64  * @param {string} sourceMapURL
     65  * @param {string} compiledURL
     66  * @param {function(WebInspector.SourceMap)} callback
     67  */
     68 WebInspector.SourceMap.load = function(sourceMapURL, compiledURL, callback)
     69 {
     70     var headers = {};
     71     headers[WebInspector.SourceMap._sourceMapRequestHeaderName] = WebInspector.SourceMap._sourceMapRequestHeaderValue;
     72     NetworkAgent.loadResourceForFrontend(WebInspector.resourceTreeModel.mainFrame.id, sourceMapURL, headers, contentLoaded.bind(this));
     73 
     74     /**
     75      * @param {?Protocol.Error} error
     76      * @param {number} statusCode
     77      * @param {NetworkAgent.Headers} headers
     78      * @param {string} content
     79      */
     80     function contentLoaded(error, statusCode, headers, content)
     81     {
     82         if (error || !content || statusCode >= 400) {
     83             callback(null);
     84             return;
     85         }
     86 
     87         if (content.slice(0, 3) === ")]}")
     88             content = content.substring(content.indexOf('\n'));
     89         try {
     90             var payload = /** @type {SourceMapV3} */ (JSON.parse(content));
     91             var baseURL = sourceMapURL.startsWith("data:") ? compiledURL : sourceMapURL;
     92             callback(new WebInspector.SourceMap(baseURL, payload));
     93         } catch(e) {
     94             console.error(e.message);
     95             callback(null);
     96         }
     97     }
     98 }
     99 
    100 WebInspector.SourceMap.prototype = {
    101     /**
    102      * @return {Array.<string>}
    103      */
    104     sources: function()
    105     {
    106         return Object.keys(this._sources);
    107     },
    108 
    109     /**
    110      * @param {string} sourceURL
    111      * @return {string|undefined}
    112      */
    113     sourceContent: function(sourceURL)
    114     {
    115         return this._sourceContentByURL[sourceURL];
    116     },
    117 
    118     /**
    119      * @param {string} sourceURL
    120      * @param {WebInspector.ResourceType} contentType
    121      * @return {WebInspector.ContentProvider}
    122      */
    123     sourceContentProvider: function(sourceURL, contentType)
    124     {
    125         var lastIndexOfDot = sourceURL.lastIndexOf(".");
    126         var extension = lastIndexOfDot !== -1 ? sourceURL.substr(lastIndexOfDot + 1) : "";
    127         var mimeType = WebInspector.ResourceType.mimeTypesForExtensions[extension.toLowerCase()];
    128         var sourceContent = this.sourceContent(sourceURL);
    129         if (sourceContent)
    130             return new WebInspector.StaticContentProvider(contentType, sourceContent, mimeType);
    131         return new WebInspector.CompilerSourceMappingContentProvider(sourceURL, contentType, mimeType);
    132     },
    133 
    134     /**
    135      * @param {SourceMapV3} mappingPayload
    136      */
    137     _parseMappingPayload: function(mappingPayload)
    138     {
    139         if (mappingPayload.sections)
    140             this._parseSections(mappingPayload.sections);
    141         else
    142             this._parseMap(mappingPayload, 0, 0);
    143     },
    144 
    145     /**
    146      * @param {Array.<SourceMapV3.Section>} sections
    147      */
    148     _parseSections: function(sections)
    149     {
    150         for (var i = 0; i < sections.length; ++i) {
    151             var section = sections[i];
    152             this._parseMap(section.map, section.offset.line, section.offset.column);
    153         }
    154     },
    155 
    156     /**
    157      * @param {number} lineNumber in compiled resource
    158      * @param {number} columnNumber in compiled resource
    159      * @return {?Array}
    160      */
    161     findEntry: function(lineNumber, columnNumber)
    162     {
    163         var first = 0;
    164         var count = this._mappings.length;
    165         while (count > 1) {
    166           var step = count >> 1;
    167           var middle = first + step;
    168           var mapping = this._mappings[middle];
    169           if (lineNumber < mapping[0] || (lineNumber === mapping[0] && columnNumber < mapping[1]))
    170               count = step;
    171           else {
    172               first = middle;
    173               count -= step;
    174           }
    175         }
    176         var entry = this._mappings[first];
    177         if (!first && entry && (lineNumber < entry[0] || (lineNumber === entry[0] && columnNumber < entry[1])))
    178             return null;
    179         return entry;
    180     },
    181 
    182     /**
    183      * @param {string} sourceURL of the originating resource
    184      * @param {number} lineNumber in the originating resource
    185      * @return {Array}
    186      */
    187     findEntryReversed: function(sourceURL, lineNumber)
    188     {
    189         var mappings = this._reverseMappingsBySourceURL[sourceURL];
    190         for ( ; lineNumber < mappings.length; ++lineNumber) {
    191             var mapping = mappings[lineNumber];
    192             if (mapping)
    193                 return mapping;
    194         }
    195         return this._mappings[0];
    196     },
    197 
    198     /**
    199      * @override
    200      */
    201     _parseMap: function(map, lineNumber, columnNumber)
    202     {
    203         var sourceIndex = 0;
    204         var sourceLineNumber = 0;
    205         var sourceColumnNumber = 0;
    206         var nameIndex = 0;
    207 
    208         var sources = [];
    209         var originalToCanonicalURLMap = {};
    210         for (var i = 0; i < map.sources.length; ++i) {
    211             var originalSourceURL = map.sources[i];
    212             var sourceRoot = map.sourceRoot || "";
    213             if (sourceRoot && !sourceRoot.endsWith("/"))
    214                 sourceRoot += "/";
    215             var href = sourceRoot + originalSourceURL;
    216             var url = WebInspector.ParsedURL.completeURL(this._sourceMappingURL, href) || href;
    217             originalToCanonicalURLMap[originalSourceURL] = url;
    218             sources.push(url);
    219             this._sources[url] = true;
    220 
    221             if (map.sourcesContent && map.sourcesContent[i])
    222                 this._sourceContentByURL[url] = map.sourcesContent[i];
    223         }
    224 
    225         var stringCharIterator = new WebInspector.SourceMap.StringCharIterator(map.mappings);
    226         var sourceURL = sources[sourceIndex];
    227 
    228         while (true) {
    229             if (stringCharIterator.peek() === ",")
    230                 stringCharIterator.next();
    231             else {
    232                 while (stringCharIterator.peek() === ";") {
    233                     lineNumber += 1;
    234                     columnNumber = 0;
    235                     stringCharIterator.next();
    236                 }
    237                 if (!stringCharIterator.hasNext())
    238                     break;
    239             }
    240 
    241             columnNumber += this._decodeVLQ(stringCharIterator);
    242             if (this._isSeparator(stringCharIterator.peek())) {
    243                 this._mappings.push([lineNumber, columnNumber]);
    244                 continue;
    245             }
    246 
    247             var sourceIndexDelta = this._decodeVLQ(stringCharIterator);
    248             if (sourceIndexDelta) {
    249                 sourceIndex += sourceIndexDelta;
    250                 sourceURL = sources[sourceIndex];
    251             }
    252             sourceLineNumber += this._decodeVLQ(stringCharIterator);
    253             sourceColumnNumber += this._decodeVLQ(stringCharIterator);
    254             if (!this._isSeparator(stringCharIterator.peek()))
    255                 nameIndex += this._decodeVLQ(stringCharIterator);
    256 
    257             this._mappings.push([lineNumber, columnNumber, sourceURL, sourceLineNumber, sourceColumnNumber]);
    258         }
    259 
    260         for (var i = 0; i < this._mappings.length; ++i) {
    261             var mapping = this._mappings[i];
    262             var url = mapping[2];
    263             if (!url)
    264                 continue;
    265             if (!this._reverseMappingsBySourceURL[url])
    266                 this._reverseMappingsBySourceURL[url] = [];
    267             var reverseMappings = this._reverseMappingsBySourceURL[url];
    268             var sourceLine = mapping[3];
    269             if (!reverseMappings[sourceLine])
    270                 reverseMappings[sourceLine] = [mapping[0], mapping[1]];
    271         }
    272     },
    273 
    274     /**
    275      * @param {string} char
    276      * @return {boolean}
    277      */
    278     _isSeparator: function(char)
    279     {
    280         return char === "," || char === ";";
    281     },
    282 
    283     /**
    284      * @param {WebInspector.SourceMap.StringCharIterator} stringCharIterator
    285      * @return {number}
    286      */
    287     _decodeVLQ: function(stringCharIterator)
    288     {
    289         // Read unsigned value.
    290         var result = 0;
    291         var shift = 0;
    292         do {
    293             var digit = this._base64Map[stringCharIterator.next()];
    294             result += (digit & this._VLQ_BASE_MASK) << shift;
    295             shift += this._VLQ_BASE_SHIFT;
    296         } while (digit & this._VLQ_CONTINUATION_MASK);
    297 
    298         // Fix the sign.
    299         var negative = result & 1;
    300         result >>= 1;
    301         return negative ? -result : result;
    302     },
    303 
    304     _VLQ_BASE_SHIFT: 5,
    305     _VLQ_BASE_MASK: (1 << 5) - 1,
    306     _VLQ_CONTINUATION_MASK: 1 << 5
    307 }
    308 
    309 /**
    310  * @constructor
    311  * @param {string} string
    312  */
    313 WebInspector.SourceMap.StringCharIterator = function(string)
    314 {
    315     this._string = string;
    316     this._position = 0;
    317 }
    318 
    319 WebInspector.SourceMap.StringCharIterator.prototype = {
    320     /**
    321      * @return {string}
    322      */
    323     next: function()
    324     {
    325         return this._string.charAt(this._position++);
    326     },
    327 
    328     /**
    329      * @return {string}
    330      */
    331     peek: function()
    332     {
    333         return this._string.charAt(this._position);
    334     },
    335 
    336     /**
    337      * @return {boolean}
    338      */
    339     hasNext: function()
    340     {
    341         return this._position < this._string.length;
    342     }
    343 }
    344