blob: e2460852295ae4a4ea7a63a636320010b88e645e [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @unrestricted
*/
Formatter.FormatterWorkerPool = class {
constructor() {
this._taskQueue = [];
/** @type {!Map<!Common.Worker, ?Formatter.FormatterWorkerPool.Task>} */
this._workerTasks = new Map();
}
/**
* @return {!Common.Worker}
*/
_createWorker() {
var worker = new Common.Worker('formatter_worker');
worker.onmessage = this._onWorkerMessage.bind(this, worker);
worker.onerror = this._onWorkerError.bind(this, worker);
return worker;
}
_processNextTask() {
if (!this._taskQueue.length)
return;
var freeWorker = this._workerTasks.keysArray().find(worker => !this._workerTasks.get(worker));
if (!freeWorker && this._workerTasks.size < Formatter.FormatterWorkerPool.MaxWorkers)
freeWorker = this._createWorker();
if (!freeWorker)
return;
var task = this._taskQueue.shift();
this._workerTasks.set(freeWorker, task);
freeWorker.postMessage({method: task.method, params: task.params});
}
/**
* @param {!Common.Worker} worker
* @param {!MessageEvent} event
*/
_onWorkerMessage(worker, event) {
var task = this._workerTasks.get(worker);
if (task.isChunked && event.data && !event.data['isLastChunk']) {
task.callback(event.data);
return;
}
this._workerTasks.set(worker, null);
this._processNextTask();
task.callback(event.data ? event.data : null);
}
/**
* @param {!Common.Worker} worker
* @param {!Event} event
*/
_onWorkerError(worker, event) {
console.error(event);
var task = this._workerTasks.get(worker);
worker.terminate();
this._workerTasks.delete(worker);
var newWorker = this._createWorker();
this._workerTasks.set(newWorker, null);
this._processNextTask();
task.callback(null);
}
/**
* @param {string} methodName
* @param {!Object<string, string>} params
* @param {function(boolean, *)} callback
*/
_runChunkedTask(methodName, params, callback) {
var task = new Formatter.FormatterWorkerPool.Task(methodName, params, onData, true);
this._taskQueue.push(task);
this._processNextTask();
/**
* @param {?Object} data
*/
function onData(data) {
if (!data) {
callback(true, null);
return;
}
var isLastChunk = !!data['isLastChunk'];
var chunk = data['chunk'];
callback(isLastChunk, chunk);
}
}
/**
* @param {string} methodName
* @param {!Object<string, string>} params
* @return {!Promise<*>}
*/
_runTask(methodName, params) {
var callback;
var promise = new Promise(fulfill => callback = fulfill);
var task = new Formatter.FormatterWorkerPool.Task(methodName, params, callback, false);
this._taskQueue.push(task);
this._processNextTask();
return promise;
}
/**
* @param {string} content
* @return {!Promise<*>}
*/
parseJSONRelaxed(content) {
return this._runTask('parseJSONRelaxed', {content: content});
}
/**
* @param {string} content
* @return {!Promise<!Array<!Formatter.FormatterWorkerPool.SCSSRule>>}
*/
parseSCSS(content) {
return this._runTask('parseSCSS', {content: content}).then(rules => rules || []);
}
/**
* @param {string} mimeType
* @param {string} content
* @param {string} indentString
* @return {!Promise<!Formatter.FormatterWorkerPool.FormatResult>}
*/
format(mimeType, content, indentString) {
var parameters = {mimeType: mimeType, content: content, indentString: indentString};
return /** @type {!Promise<!Formatter.FormatterWorkerPool.FormatResult>} */ (this._runTask('format', parameters));
}
/**
* @param {string} content
* @return {!Promise<!Array<!{name: string, offset: number}>>}
*/
javaScriptIdentifiers(content) {
return this._runTask('javaScriptIdentifiers', {content: content}).then(ids => ids || []);
}
/**
* @param {string} content
* @return {!Promise<string>}
*/
evaluatableJavaScriptSubstring(content) {
return this._runTask('evaluatableJavaScriptSubstring', {content: content}).then(text => text || '');
}
/**
* @param {string} content
* @return {!Promise<string>}
*/
preprocessTopLevelAwaitExpressions(content) {
return this._runTask('preprocessTopLevelAwaitExpressions', {content: content}).then(text => text || '');
}
/**
* @param {string} content
* @param {function(boolean, !Array<!Formatter.FormatterWorkerPool.CSSRule>)} callback
*/
parseCSS(content, callback) {
this._runChunkedTask('parseCSS', {content: content}, onDataChunk);
/**
* @param {boolean} isLastChunk
* @param {*} data
*/
function onDataChunk(isLastChunk, data) {
var rules = /** @type {!Array<!Formatter.FormatterWorkerPool.CSSRule>} */ (data || []);
callback(isLastChunk, rules);
}
}
/**
* @param {string} content
* @param {function(boolean, !Array<!Formatter.FormatterWorkerPool.JSOutlineItem>)} callback
*/
javaScriptOutline(content, callback) {
this._runChunkedTask('javaScriptOutline', {content: content}, onDataChunk);
/**
* @param {boolean} isLastChunk
* @param {*} data
*/
function onDataChunk(isLastChunk, data) {
var items = /** @type {!Array.<!Formatter.FormatterWorkerPool.JSOutlineItem>} */ (data || []);
callback(isLastChunk, items);
}
}
/**
* @param {string} content
* @param {string} mimeType
* @param {function(boolean, !Array<!Formatter.FormatterWorkerPool.OutlineItem>)} callback
* @return {boolean}
*/
outlineForMimetype(content, mimeType, callback) {
switch (mimeType) {
case 'text/html':
case 'text/javascript':
this.javaScriptOutline(content, javaScriptCallback);
return true;
case 'text/css':
this.parseCSS(content, cssCallback);
return true;
}
return false;
/**
* @param {boolean} isLastChunk
* @param {!Array<!Formatter.FormatterWorkerPool.JSOutlineItem>} items
*/
function javaScriptCallback(isLastChunk, items) {
callback(
isLastChunk,
items.map(item => ({line: item.line, column: item.column, title: item.name, subtitle: item.arguments})));
}
/**
* @param {boolean} isLastChunk
* @param {!Array<!Formatter.FormatterWorkerPool.CSSRule>} rules
*/
function cssCallback(isLastChunk, rules) {
callback(
isLastChunk,
rules.map(
rule => ({line: rule.lineNumber, column: rule.columnNumber, title: rule.selectorText || rule.atRule})));
}
}
};
Formatter.FormatterWorkerPool.MaxWorkers = 2;
/**
* @unrestricted
*/
Formatter.FormatterWorkerPool.Task = class {
/**
* @param {string} method
* @param {!Object<string, string>} params
* @param {function(?MessageEvent)} callback
* @param {boolean=} isChunked
*/
constructor(method, params, callback, isChunked) {
this.method = method;
this.params = params;
this.callback = callback;
this.isChunked = isChunked;
}
};
Formatter.FormatterWorkerPool.FormatResult = class {
constructor() {
/** @type {string} */
this.content;
/** @type {!Formatter.FormatterWorkerPool.FormatMapping} */
this.mapping;
}
};
/** @typedef {{original: !Array<number>, formatted: !Array<number>}} */
Formatter.FormatterWorkerPool.FormatMapping;
/** @typedef {{line: number, column: number, title: string, subtitle: (string|undefined) }} */
Formatter.FormatterWorkerPool.OutlineItem;
Formatter.FormatterWorkerPool.JSOutlineItem = class {
constructor() {
/** @type {string} */
this.name;
/** @type {(string|undefined)} */
this.arguments;
/** @type {number} */
this.line;
/** @type {number} */
this.column;
}
};
/**
* @typedef {{startLine: number, startColumn: number, endLine: number, endColumn: number}}
*/
Formatter.FormatterWorkerPool.TextRange;
Formatter.FormatterWorkerPool.CSSProperty = class {
constructor() {
/** @type {string} */
this.name;
/** @type {!Formatter.FormatterWorkerPool.TextRange} */
this.nameRange;
/** @type {string} */
this.value;
/** @type {!Formatter.FormatterWorkerPool.TextRange} */
this.valueRange;
/** @type {!Formatter.FormatterWorkerPool.TextRange} */
this.range;
/** @type {(boolean|undefined)} */
this.disabled;
}
};
Formatter.FormatterWorkerPool.CSSStyleRule = class {
constructor() {
/** @type {string} */
this.selectorText;
/** @type {!Formatter.FormatterWorkerPool.TextRange} */
this.styleRange;
/** @type {number} */
this.lineNumber;
/** @type {number} */
this.columnNumber;
/** @type {!Array.<!Formatter.FormatterWorkerPool.CSSProperty>} */
this.properties;
}
};
/**
* @typedef {{atRule: string, lineNumber: number, columnNumber: number}}
*/
Formatter.FormatterWorkerPool.CSSAtRule;
/**
* @typedef {(Formatter.FormatterWorkerPool.CSSStyleRule|Formatter.FormatterWorkerPool.CSSAtRule)}
*/
Formatter.FormatterWorkerPool.CSSRule;
Formatter.FormatterWorkerPool.SCSSProperty = class {
constructor() {
/** @type {!Formatter.FormatterWorkerPool.TextRange} */
this.range;
/** @type {!Formatter.FormatterWorkerPool.TextRange} */
this.name;
/** @type {!Formatter.FormatterWorkerPool.TextRange} */
this.value;
/** @type {boolean} */
this.disabled;
}
};
Formatter.FormatterWorkerPool.SCSSRule = class {
constructor() {
/** @type {!Array<!Formatter.FormatterWorkerPool.TextRange>} */
this.selectors;
/** @type {!Array<!Formatter.FormatterWorkerPool.SCSSProperty>} */
this.properties;
/** @type {!Formatter.FormatterWorkerPool.TextRange} */
this.styleRange;
}
};
/**
* @return {!Formatter.FormatterWorkerPool}
*/
Formatter.formatterWorkerPool = function() {
if (!Formatter._formatterWorkerPool)
Formatter._formatterWorkerPool = new Formatter.FormatterWorkerPool();
return Formatter._formatterWorkerPool;
};