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