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 */ 30importScripts("utilities.js"); 31importScripts("cm/headlesscodemirror.js"); 32importScripts("cm/css.js"); 33importScripts("cm/javascript.js"); 34importScripts("cm/xml.js"); 35importScripts("cm/htmlmixed.js"); 36WebInspector = {}; 37FormatterWorker = {}; 38importScripts("CodeMirrorUtils.js"); 39 40var onmessage = function(event) { 41 if (!event.data.method) 42 return; 43 44 FormatterWorker[event.data.method](event.data.params); 45}; 46 47/** 48 * @param {!Object} params 49 */ 50FormatterWorker.format = function(params) 51{ 52 // Default to a 4-space indent. 53 var indentString = params.indentString || " "; 54 var result = {}; 55 56 if (params.mimeType === "text/html") { 57 var formatter = new FormatterWorker.HTMLFormatter(indentString); 58 result = formatter.format(params.content); 59 } else if (params.mimeType === "text/css") { 60 result.mapping = { original: [0], formatted: [0] }; 61 result.content = FormatterWorker._formatCSS(params.content, result.mapping, 0, 0, indentString); 62 } else { 63 result.mapping = { original: [0], formatted: [0] }; 64 result.content = FormatterWorker._formatScript(params.content, result.mapping, 0, 0, indentString); 65 } 66 postMessage(result); 67} 68 69/** 70 * @param {number} totalLength 71 * @param {number} chunkSize 72 */ 73FormatterWorker._chunkCount = function(totalLength, chunkSize) 74{ 75 if (totalLength <= chunkSize) 76 return 1; 77 78 var remainder = totalLength % chunkSize; 79 var partialLength = totalLength - remainder; 80 return (partialLength / chunkSize) + (remainder ? 1 : 0); 81} 82 83/** 84 * @param {!Object} params 85 */ 86FormatterWorker.outline = function(params) 87{ 88 const chunkSize = 100000; // characters per data chunk 89 const totalLength = params.content.length; 90 const lines = params.content.split("\n"); 91 const chunkCount = FormatterWorker._chunkCount(totalLength, chunkSize); 92 var outlineChunk = []; 93 var previousIdentifier = null; 94 var previousToken = null; 95 var previousTokenType = null; 96 var currentChunk = 1; 97 var processedChunkCharacters = 0; 98 var addedFunction = false; 99 var isReadingArguments = false; 100 var argumentsText = ""; 101 var currentFunction = null; 102 var tokenizer = WebInspector.CodeMirrorUtils.createTokenizer("text/javascript"); 103 for (var i = 0; i < lines.length; ++i) { 104 var line = lines[i]; 105 tokenizer(line, processToken); 106 } 107 108 /** 109 * @param {string} tokenValue 110 * @param {string} tokenType 111 * @param {number} column 112 * @param {number} newColumn 113 */ 114 function processToken(tokenValue, tokenType, column, newColumn) 115 { 116 tokenType = tokenType ? WebInspector.CodeMirrorUtils.convertTokenType(tokenType) : null; 117 if (tokenType === "javascript-ident") { 118 previousIdentifier = tokenValue; 119 if (tokenValue && previousToken === "function") { 120 // A named function: "function f...". 121 currentFunction = { line: i, column: column, name: tokenValue }; 122 addedFunction = true; 123 previousIdentifier = null; 124 } 125 } else if (tokenType === "javascript-keyword") { 126 if (tokenValue === "function") { 127 if (previousIdentifier && (previousToken === "=" || previousToken === ":")) { 128 // Anonymous function assigned to an identifier: "...f = function..." 129 // or "funcName: function...". 130 currentFunction = { line: i, column: column, name: previousIdentifier }; 131 addedFunction = true; 132 previousIdentifier = null; 133 } 134 } 135 } else if (tokenValue === "." && previousTokenType === "javascript-ident") 136 previousIdentifier += "."; 137 else if (tokenValue === "(" && addedFunction) 138 isReadingArguments = true; 139 if (isReadingArguments && tokenValue) 140 argumentsText += tokenValue; 141 142 if (tokenValue === ")" && isReadingArguments) { 143 addedFunction = false; 144 isReadingArguments = false; 145 currentFunction.arguments = argumentsText.replace(/,[\r\n\s]*/g, ", ").replace(/([^,])[\r\n\s]+/g, "$1"); 146 argumentsText = ""; 147 outlineChunk.push(currentFunction); 148 } 149 150 if (tokenValue.trim().length) { 151 // Skip whitespace tokens. 152 previousToken = tokenValue; 153 previousTokenType = tokenType; 154 } 155 processedChunkCharacters += newColumn - column; 156 157 if (processedChunkCharacters >= chunkSize) { 158 postMessage({ chunk: outlineChunk, total: chunkCount, index: currentChunk++ }); 159 outlineChunk = []; 160 processedChunkCharacters = 0; 161 } 162 } 163 164 postMessage({ chunk: outlineChunk, total: chunkCount, index: chunkCount }); 165} 166 167/** 168 * @param {string} content 169 * @param {!{original: !Array.<number>, formatted: !Array.<number>}} mapping 170 * @param {number} offset 171 * @param {number} formattedOffset 172 * @param {string} indentString 173 * @return {string} 174 */ 175FormatterWorker._formatScript = function(content, mapping, offset, formattedOffset, indentString) 176{ 177 var formattedContent; 178 try { 179 var tokenizer = new FormatterWorker.JavaScriptTokenizer(content); 180 var builder = new FormatterWorker.JavaScriptFormattedContentBuilder(tokenizer.content(), mapping, offset, formattedOffset, indentString); 181 var formatter = new FormatterWorker.JavaScriptFormatter(tokenizer, builder); 182 formatter.format(); 183 formattedContent = builder.content(); 184 } catch (e) { 185 formattedContent = content; 186 } 187 return formattedContent; 188} 189 190/** 191 * @param {string} content 192 * @param {!{original: !Array.<number>, formatted: !Array.<number>}} mapping 193 * @param {number} offset 194 * @param {number} formattedOffset 195 * @param {string} indentString 196 * @return {string} 197 */ 198FormatterWorker._formatCSS = function(content, mapping, offset, formattedOffset, indentString) 199{ 200 var formattedContent; 201 try { 202 var builder = new FormatterWorker.CSSFormattedContentBuilder(content, mapping, offset, formattedOffset, indentString); 203 var formatter = new FormatterWorker.CSSFormatter(content, builder); 204 formatter.format(); 205 formattedContent = builder.content(); 206 } catch (e) { 207 formattedContent = content; 208 } 209 return formattedContent; 210} 211 212/** 213 * @constructor 214 * @param {string} indentString 215 */ 216FormatterWorker.HTMLFormatter = function(indentString) 217{ 218 this._indentString = indentString; 219} 220 221FormatterWorker.HTMLFormatter.prototype = { 222 /** 223 * @param {string} content 224 */ 225 format: function(content) 226 { 227 this.line = content; 228 this._content = content; 229 this._formattedContent = ""; 230 this._mapping = { original: [0], formatted: [0] }; 231 this._position = 0; 232 233 var scriptOpened = false; 234 var styleOpened = false; 235 var tokenizer = WebInspector.CodeMirrorUtils.createTokenizer("text/html"); 236 237 /** 238 * @this {FormatterWorker.HTMLFormatter} 239 */ 240 function processToken(tokenValue, tokenType, tokenStart, tokenEnd) { 241 if (tokenType !== "xml-tag") 242 return; 243 if (tokenValue.toLowerCase() === "<script") { 244 scriptOpened = true; 245 } else if (scriptOpened && tokenValue === ">") { 246 scriptOpened = false; 247 this._scriptStarted(tokenEnd); 248 } else if (tokenValue.toLowerCase() === "</script") { 249 this._scriptEnded(tokenStart); 250 } else if (tokenValue.toLowerCase() === "<style") { 251 styleOpened = true; 252 } else if (styleOpened && tokenValue === ">") { 253 styleOpened = false; 254 this._styleStarted(tokenEnd); 255 } else if (tokenValue.toLowerCase() === "</style") { 256 this._styleEnded(tokenStart); 257 } 258 } 259 tokenizer(content, processToken.bind(this)); 260 261 this._formattedContent += this._content.substring(this._position); 262 return { content: this._formattedContent, mapping: this._mapping }; 263 }, 264 265 /** 266 * @param {number} cursor 267 */ 268 _scriptStarted: function(cursor) 269 { 270 this._handleSubFormatterStart(cursor); 271 }, 272 273 /** 274 * @param {number} cursor 275 */ 276 _scriptEnded: function(cursor) 277 { 278 this._handleSubFormatterEnd(FormatterWorker._formatScript, cursor); 279 }, 280 281 /** 282 * @param {number} cursor 283 */ 284 _styleStarted: function(cursor) 285 { 286 this._handleSubFormatterStart(cursor); 287 }, 288 289 /** 290 * @param {number} cursor 291 */ 292 _styleEnded: function(cursor) 293 { 294 this._handleSubFormatterEnd(FormatterWorker._formatCSS, cursor); 295 }, 296 297 /** 298 * @param {number} cursor 299 */ 300 _handleSubFormatterStart: function(cursor) 301 { 302 this._formattedContent += this._content.substring(this._position, cursor); 303 this._formattedContent += "\n"; 304 this._position = cursor; 305 }, 306 307 /** 308 * @param {function(string, {formatted: !Array.<number>, original: !Array.<number>}, number, number, string)} formatFunction 309 * @param {number} cursor 310 */ 311 _handleSubFormatterEnd: function(formatFunction, cursor) 312 { 313 if (cursor === this._position) 314 return; 315 316 var scriptContent = this._content.substring(this._position, cursor); 317 this._mapping.original.push(this._position); 318 this._mapping.formatted.push(this._formattedContent.length); 319 var formattedScriptContent = formatFunction(scriptContent, this._mapping, this._position, this._formattedContent.length, this._indentString); 320 321 this._formattedContent += formattedScriptContent; 322 this._position = cursor; 323 } 324} 325 326Array.prototype.keySet = function() 327{ 328 var keys = {}; 329 for (var i = 0; i < this.length; ++i) 330 keys[this[i]] = true; 331 return keys; 332}; 333 334function require() 335{ 336 return parse; 337} 338 339/** 340 * @type {!{tokenizer}} 341 */ 342var exports = { tokenizer: null }; 343importScripts("UglifyJS/parse-js.js"); 344var parse = exports; 345 346importScripts("JavaScriptFormatter.js"); 347importScripts("CSSFormatter.js"); 348