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