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