• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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