Home | History | Annotate | Download | only in front-end
      1 /*
      2  * Copyright (C) 2011 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 WebInspector.ScriptFormatter = function()
     32 {
     33     this._worker = new Worker("ScriptFormatterWorker.js");
     34     this._worker.onmessage = this._handleMessage.bind(this);
     35     this._worker.onerror = this._handleError.bind(this);
     36     this._tasks = [];
     37 }
     38 
     39 WebInspector.ScriptFormatter.locationToPosition = function(lineEndings, location)
     40 {
     41     var position = location.lineNumber ? lineEndings[location.lineNumber - 1] + 1 : 0;
     42     return position + location.columnNumber;
     43 }
     44 
     45 WebInspector.ScriptFormatter.lineToPosition = function(lineEndings, lineNumber)
     46 {
     47     return this.locationToPosition(lineEndings, { lineNumber: lineNumber, columnNumber: 0 });
     48 }
     49 
     50 WebInspector.ScriptFormatter.positionToLocation = function(lineEndings, position)
     51 {
     52     var location = {};
     53     location.lineNumber = lineEndings.upperBound(position - 1);
     54     if (!location.lineNumber)
     55         location.columnNumber = position;
     56     else
     57         location.columnNumber = position - lineEndings[location.lineNumber - 1] - 1;
     58     return location;
     59 }
     60 
     61 WebInspector.ScriptFormatter.findScriptRanges = function(lineEndings, scripts)
     62 {
     63     var scriptRanges = [];
     64     for (var i = 0; i < scripts.length; ++i) {
     65         var start = { lineNumber: scripts[i].lineOffset, columnNumber: scripts[i].columnOffset };
     66         start.position = WebInspector.ScriptFormatter.locationToPosition(lineEndings, start);
     67         var endPosition = start.position + scripts[i].length;
     68         var end = WebInspector.ScriptFormatter.positionToLocation(lineEndings, endPosition);
     69         end.position = endPosition;
     70         scriptRanges.push({ start: start, end: end, sourceID: scripts[i].sourceID });
     71     }
     72     scriptRanges.sort(function(x, y) { return x.start.position - y.start.position; });
     73     return scriptRanges;
     74 }
     75 
     76 WebInspector.ScriptFormatter.prototype = {
     77     formatContent: function(text, scripts, callback)
     78     {
     79         var scriptRanges = WebInspector.ScriptFormatter.findScriptRanges(text.lineEndings(), scripts);
     80         var chunks = this._splitContentIntoChunks(text, scriptRanges);
     81 
     82         function didFormatChunks()
     83         {
     84             var result = this._buildContentFromChunks(chunks);
     85             callback(result.text, result.mapping);
     86         }
     87         this._formatChunks(chunks, 0, didFormatChunks.bind(this));
     88     },
     89 
     90     _splitContentIntoChunks: function(text, scriptRanges)
     91     {
     92         var chunks = [];
     93         function addChunk(start, end, isScript)
     94         {
     95             var chunk = {};
     96             chunk.start = start;
     97             chunk.end = end;
     98             chunk.isScript = isScript;
     99             chunk.text = text.substring(start, end);
    100             chunks.push(chunk);
    101         }
    102         var currentPosition = 0;
    103         for (var i = 0; i < scriptRanges.length; ++i) {
    104             var start = scriptRanges[i].start.position;
    105             var end = scriptRanges[i].end.position;
    106             if (currentPosition < start)
    107                 addChunk(currentPosition, start, false);
    108             addChunk(start, end, true);
    109             currentPosition = end;
    110         }
    111         if (currentPosition < text.length)
    112             addChunk(currentPosition, text.length, false);
    113         return chunks;
    114     },
    115 
    116     _formatChunks: function(chunks, index, callback)
    117     {
    118         while(true) {
    119             if (index === chunks.length) {
    120                 callback();
    121                 return;
    122             }
    123             var chunk = chunks[index++];
    124             if (chunk.isScript)
    125                 break;
    126         }
    127 
    128         function didFormat(formattedSource, mapping)
    129         {
    130             chunk.text = formattedSource;
    131             chunk.mapping = mapping;
    132             this._formatChunks(chunks, index, callback);
    133         }
    134         this._formatScript(chunk.text, didFormat.bind(this));
    135     },
    136 
    137     _buildContentFromChunks: function(chunks)
    138     {
    139         var text = "";
    140         var mapping = { original: [], formatted: [] };
    141         for (var i = 0; i < chunks.length; ++i) {
    142             var chunk = chunks[i];
    143             mapping.original.push(chunk.start);
    144             mapping.formatted.push(text.length);
    145             if (chunk.isScript) {
    146                 if (text)
    147                     text += "\n";
    148                 for (var j = 0; j < chunk.mapping.original.length; ++j) {
    149                     mapping.original.push(chunk.mapping.original[j] + chunk.start);
    150                     mapping.formatted.push(chunk.mapping.formatted[j] + text.length);
    151                 }
    152                 text += chunk.text;
    153             } else {
    154                 if (text)
    155                     text += "\n";
    156                 text += chunk.text;
    157             }
    158             mapping.original.push(chunk.end);
    159             mapping.formatted.push(text.length);
    160         }
    161         return { text: text, mapping: mapping };
    162     },
    163 
    164     _formatScript: function(source, callback)
    165     {
    166         this._tasks.push({ source: source, callback: callback });
    167         this._worker.postMessage(source);
    168     },
    169 
    170     _handleMessage: function(event)
    171     {
    172         var task = this._tasks.shift();
    173         task.callback(event.data.formattedSource, event.data.mapping);
    174     },
    175 
    176     _handleError: function(event)
    177     {
    178         console.warn("Error in script formatter worker:", event);
    179         event.preventDefault()
    180         var task = this._tasks.shift();
    181         task.callback(task.source, { original: [], formatted: [] });
    182     }
    183 }
    184