Home | History | Annotate | Download | only in tools
      1 // Copyright 2013 the V8 project authors. All rights reserved.
      2 // Redistribution and use in source and binary forms, with or without
      3 // modification, are permitted provided that the following conditions are
      4 // met:
      5 //
      6 //     * Redistributions of source code must retain the above copyright
      7 //       notice, this list of conditions and the following disclaimer.
      8 //     * Redistributions in binary form must reproduce the above
      9 //       copyright notice, this list of conditions and the following
     10 //       disclaimer in the documentation and/or other materials provided
     11 //       with the distribution.
     12 //     * Neither the name of Google Inc. nor the names of its
     13 //       contributors may be used to endorse or promote products derived
     14 //       from this software without specific prior written permission.
     15 //
     16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     27 
     28 // This is a copy from blink dev tools, see:
     29 // http://src.chromium.org/viewvc/blink/trunk/Source/devtools/front_end/SourceMap.js
     30 // revision: 153407
     31 
     32 // Added to make the file work without dev tools
     33 WebInspector = {};
     34 WebInspector.ParsedURL = {};
     35 WebInspector.ParsedURL.completeURL = function(){};
     36 // start of original file content
     37 
     38 /*
     39  * Copyright (C) 2012 Google Inc. All rights reserved.
     40  *
     41  * Redistribution and use in source and binary forms, with or without
     42  * modification, are permitted provided that the following conditions are
     43  * met:
     44  *
     45  *     * Redistributions of source code must retain the above copyright
     46  * notice, this list of conditions and the following disclaimer.
     47  *     * Redistributions in binary form must reproduce the above
     48  * copyright notice, this list of conditions and the following disclaimer
     49  * in the documentation and/or other materials provided with the
     50  * distribution.
     51  *     * Neither the name of Google Inc. nor the names of its
     52  * contributors may be used to endorse or promote products derived from
     53  * this software without specific prior written permission.
     54  *
     55  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     56  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     57  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     58  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     59  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     60  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     61  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     62  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     63  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     64  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     65  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     66  */
     67 
     68 /**
     69  * Implements Source Map V3 model. See http://code.google.com/p/closure-compiler/wiki/SourceMaps
     70  * for format description.
     71  * @constructor
     72  * @param {string} sourceMappingURL
     73  * @param {SourceMapV3} payload
     74  */
     75 WebInspector.SourceMap = function(sourceMappingURL, payload)
     76 {
     77     if (!WebInspector.SourceMap.prototype._base64Map) {
     78         const base64Digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
     79         WebInspector.SourceMap.prototype._base64Map = {};
     80         for (var i = 0; i < base64Digits.length; ++i)
     81             WebInspector.SourceMap.prototype._base64Map[base64Digits.charAt(i)] = i;
     82     }
     83 
     84     this._sourceMappingURL = sourceMappingURL;
     85     this._reverseMappingsBySourceURL = {};
     86     this._mappings = [];
     87     this._sources = {};
     88     this._sourceContentByURL = {};
     89     this._parseMappingPayload(payload);
     90 }
     91 
     92 /**
     93  * @param {string} sourceMapURL
     94  * @param {string} compiledURL
     95  * @param {function(WebInspector.SourceMap)} callback
     96  */
     97 WebInspector.SourceMap.load = function(sourceMapURL, compiledURL, callback)
     98 {
     99     NetworkAgent.loadResourceForFrontend(WebInspector.resourceTreeModel.mainFrame.id, sourceMapURL, undefined, contentLoaded.bind(this));
    100 
    101     /**
    102      * @param {?Protocol.Error} error
    103      * @param {number} statusCode
    104      * @param {NetworkAgent.Headers} headers
    105      * @param {string} content
    106      */
    107     function contentLoaded(error, statusCode, headers, content)
    108     {
    109         if (error || !content || statusCode >= 400) {
    110             console.error("Could not load content for " + sourceMapURL + " : " + (error || ("HTTP status code: " + statusCode)));
    111             callback(null);
    112             return;
    113         }
    114 
    115         if (content.slice(0, 3) === ")]}")
    116             content = content.substring(content.indexOf('\n'));
    117         try {
    118             var payload = /** @type {SourceMapV3} */ (JSON.parse(content));
    119             var baseURL = sourceMapURL.startsWith("data:") ? compiledURL : sourceMapURL;
    120             callback(new WebInspector.SourceMap(baseURL, payload));
    121         } catch(e) {
    122             console.error(e.message);
    123             callback(null);
    124         }
    125     }
    126 }
    127 
    128 WebInspector.SourceMap.prototype = {
    129     /**
    130      * @return {Array.<string>}
    131      */
    132     sources: function()
    133     {
    134         return Object.keys(this._sources);
    135     },
    136 
    137     /**
    138      * @param {string} sourceURL
    139      * @return {string|undefined}
    140      */
    141     sourceContent: function(sourceURL)
    142     {
    143         return this._sourceContentByURL[sourceURL];
    144     },
    145 
    146     /**
    147      * @param {string} sourceURL
    148      * @param {WebInspector.ResourceType} contentType
    149      * @return {WebInspector.ContentProvider}
    150      */
    151     sourceContentProvider: function(sourceURL, contentType)
    152     {
    153         var lastIndexOfDot = sourceURL.lastIndexOf(".");
    154         var extension = lastIndexOfDot !== -1 ? sourceURL.substr(lastIndexOfDot + 1) : "";
    155         var mimeType = WebInspector.ResourceType.mimeTypesForExtensions[extension.toLowerCase()];
    156         var sourceContent = this.sourceContent(sourceURL);
    157         if (sourceContent)
    158             return new WebInspector.StaticContentProvider(contentType, sourceContent, mimeType);
    159         return new WebInspector.CompilerSourceMappingContentProvider(sourceURL, contentType, mimeType);
    160     },
    161 
    162     /**
    163      * @param {SourceMapV3} mappingPayload
    164      */
    165     _parseMappingPayload: function(mappingPayload)
    166     {
    167         if (mappingPayload.sections)
    168             this._parseSections(mappingPayload.sections);
    169         else
    170             this._parseMap(mappingPayload, 0, 0);
    171     },
    172 
    173     /**
    174      * @param {Array.<SourceMapV3.Section>} sections
    175      */
    176     _parseSections: function(sections)
    177     {
    178         for (var i = 0; i < sections.length; ++i) {
    179             var section = sections[i];
    180             this._parseMap(section.map, section.offset.line, section.offset.column);
    181         }
    182     },
    183 
    184     /**
    185      * @param {number} lineNumber in compiled resource
    186      * @param {number} columnNumber in compiled resource
    187      * @return {?Array}
    188      */
    189     findEntry: function(lineNumber, columnNumber)
    190     {
    191         var first = 0;
    192         var count = this._mappings.length;
    193         while (count > 1) {
    194           var step = count >> 1;
    195           var middle = first + step;
    196           var mapping = this._mappings[middle];
    197           if (lineNumber < mapping[0] || (lineNumber === mapping[0] && columnNumber < mapping[1]))
    198               count = step;
    199           else {
    200               first = middle;
    201               count -= step;
    202           }
    203         }
    204         var entry = this._mappings[first];
    205         if (!first && entry && (lineNumber < entry[0] || (lineNumber === entry[0] && columnNumber < entry[1])))
    206             return null;
    207         return entry;
    208     },
    209 
    210     /**
    211      * @param {string} sourceURL of the originating resource
    212      * @param {number} lineNumber in the originating resource
    213      * @return {Array}
    214      */
    215     findEntryReversed: function(sourceURL, lineNumber)
    216     {
    217         var mappings = this._reverseMappingsBySourceURL[sourceURL];
    218         for ( ; lineNumber < mappings.length; ++lineNumber) {
    219             var mapping = mappings[lineNumber];
    220             if (mapping)
    221                 return mapping;
    222         }
    223         return this._mappings[0];
    224     },
    225 
    226     /**
    227      * @override
    228      */
    229     _parseMap: function(map, lineNumber, columnNumber)
    230     {
    231         var sourceIndex = 0;
    232         var sourceLineNumber = 0;
    233         var sourceColumnNumber = 0;
    234         var nameIndex = 0;
    235 
    236         var sources = [];
    237         var originalToCanonicalURLMap = {};
    238         for (var i = 0; i < map.sources.length; ++i) {
    239             var originalSourceURL = map.sources[i];
    240             var sourceRoot = map.sourceRoot || "";
    241             if (sourceRoot && !sourceRoot.endsWith("/"))
    242                 sourceRoot += "/";
    243             var href = sourceRoot + originalSourceURL;
    244             var url = WebInspector.ParsedURL.completeURL(this._sourceMappingURL, href) || href;
    245             originalToCanonicalURLMap[originalSourceURL] = url;
    246             sources.push(url);
    247             this._sources[url] = true;
    248 
    249             if (map.sourcesContent && map.sourcesContent[i])
    250                 this._sourceContentByURL[url] = map.sourcesContent[i];
    251         }
    252 
    253         var stringCharIterator = new WebInspector.SourceMap.StringCharIterator(map.mappings);
    254         var sourceURL = sources[sourceIndex];
    255 
    256         while (true) {
    257             if (stringCharIterator.peek() === ",")
    258                 stringCharIterator.next();
    259             else {
    260                 while (stringCharIterator.peek() === ";") {
    261                     lineNumber += 1;
    262                     columnNumber = 0;
    263                     stringCharIterator.next();
    264                 }
    265                 if (!stringCharIterator.hasNext())
    266                     break;
    267             }
    268 
    269             columnNumber += this._decodeVLQ(stringCharIterator);
    270             if (this._isSeparator(stringCharIterator.peek())) {
    271                 this._mappings.push([lineNumber, columnNumber]);
    272                 continue;
    273             }
    274 
    275             var sourceIndexDelta = this._decodeVLQ(stringCharIterator);
    276             if (sourceIndexDelta) {
    277                 sourceIndex += sourceIndexDelta;
    278                 sourceURL = sources[sourceIndex];
    279             }
    280             sourceLineNumber += this._decodeVLQ(stringCharIterator);
    281             sourceColumnNumber += this._decodeVLQ(stringCharIterator);
    282             if (!this._isSeparator(stringCharIterator.peek()))
    283                 nameIndex += this._decodeVLQ(stringCharIterator);
    284 
    285             this._mappings.push([lineNumber, columnNumber, sourceURL, sourceLineNumber, sourceColumnNumber]);
    286         }
    287 
    288         for (var i = 0; i < this._mappings.length; ++i) {
    289             var mapping = this._mappings[i];
    290             var url = mapping[2];
    291             if (!url)
    292                 continue;
    293             if (!this._reverseMappingsBySourceURL[url])
    294                 this._reverseMappingsBySourceURL[url] = [];
    295             var reverseMappings = this._reverseMappingsBySourceURL[url];
    296             var sourceLine = mapping[3];
    297             if (!reverseMappings[sourceLine])
    298                 reverseMappings[sourceLine] = [mapping[0], mapping[1]];
    299         }
    300     },
    301 
    302     /**
    303      * @param {string} char
    304      * @return {boolean}
    305      */
    306     _isSeparator: function(char)
    307     {
    308         return char === "," || char === ";";
    309     },
    310 
    311     /**
    312      * @param {WebInspector.SourceMap.StringCharIterator} stringCharIterator
    313      * @return {number}
    314      */
    315     _decodeVLQ: function(stringCharIterator)
    316     {
    317         // Read unsigned value.
    318         var result = 0;
    319         var shift = 0;
    320         do {
    321             var digit = this._base64Map[stringCharIterator.next()];
    322             result += (digit & this._VLQ_BASE_MASK) << shift;
    323             shift += this._VLQ_BASE_SHIFT;
    324         } while (digit & this._VLQ_CONTINUATION_MASK);
    325 
    326         // Fix the sign.
    327         var negative = result & 1;
    328         result >>= 1;
    329         return negative ? -result : result;
    330     },
    331 
    332     _VLQ_BASE_SHIFT: 5,
    333     _VLQ_BASE_MASK: (1 << 5) - 1,
    334     _VLQ_CONTINUATION_MASK: 1 << 5
    335 }
    336 
    337 /**
    338  * @constructor
    339  * @param {string} string
    340  */
    341 WebInspector.SourceMap.StringCharIterator = function(string)
    342 {
    343     this._string = string;
    344     this._position = 0;
    345 }
    346 
    347 WebInspector.SourceMap.StringCharIterator.prototype = {
    348     /**
    349      * @return {string}
    350      */
    351     next: function()
    352     {
    353         return this._string.charAt(this._position++);
    354     },
    355 
    356     /**
    357      * @return {string}
    358      */
    359     peek: function()
    360     {
    361         return this._string.charAt(this._position);
    362     },
    363 
    364     /**
    365      * @return {boolean}
    366      */
    367     hasNext: function()
    368     {
    369         return this._position < this._string.length;
    370     }
    371 }
    372