From 93b57c3bbc6eaaffc2f73f4d2ee1f1c4cff079ea Mon Sep 17 00:00:00 2001 From: Tim van der Lippe Date: Thu, 20 Feb 2020 17:38:44 +0000 Subject: [PATCH] Remove prototype wrangling for String formatters They are now moved to the StringUtilities file. Sadly, `String.sprintf` is still used in the TestRunners that can't use ES modules. The Multimap required exporting, as the NetworkManager has a reference to it as well. Bug: 1050549 Change-Id: Ia7b38dc212282a9800ee8c3083dac7f4a5e046e1 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2067057 Commit-Queue: Tim van der Lippe Reviewed-by: Jack Franklin --- front_end/color_picker/Spectrum.js | 10 +- front_end/common/BUILD.gn | 2 + front_end/common/Color.js | 18 +- front_end/common/UIString.js | 11 +- front_end/components/ImagePreview.js | 3 +- front_end/console/ConsoleSidebar.js | 3 +- front_end/console/ConsoleView.js | 3 +- front_end/console/ConsoleViewMessage.js | 5 +- front_end/emulation/MediaQueryInspector.js | 3 +- front_end/extensions/ExtensionServer.js | 3 +- .../object_ui/ObjectPropertiesSection.js | 3 +- front_end/platform/BUILD.gn | 9 + front_end/platform/platform.js | 1 + front_end/platform/string-utilities.js | 202 +++++++++++++++++ front_end/platform/utilities.js | 214 +----------------- front_end/sdk/CSSProperty.js | 3 +- front_end/sdk/NetworkManager.js | 3 +- front_end/source_frame/FontView.js | 3 +- front_end/sources/CSSPlugin.js | 3 +- front_end/timeline/TimelineUIUtils.js | 4 +- front_end/ui/UIUtils.js | 4 +- 21 files changed, 274 insertions(+), 236 deletions(-) create mode 100644 front_end/platform/BUILD.gn diff --git a/front_end/color_picker/Spectrum.js b/front_end/color_picker/Spectrum.js index 2e4220c945..9d97890b06 100644 --- a/front_end/color_picker/Spectrum.js +++ b/front_end/color_picker/Spectrum.js @@ -28,6 +28,7 @@ import * as Common from '../common/common.js'; import * as Host from '../host/host.js'; +import * as Platform from '../platform/platform.js'; import * as SDK from '../sdk/sdk.js'; import * as UI from '../ui/ui.js'; @@ -414,7 +415,8 @@ export class Spectrum extends UI.Widget.VBox { */ _createPaletteColor(colorText, colorName, animationDelay) { const element = createElementWithClass('div', 'spectrum-palette-color'); - element.style.background = String.sprintf('linear-gradient(%s, %s), url(Images/checker.png)', colorText, colorText); + element.style.background = + Platform.StringUtilities.sprintf('linear-gradient(%s, %s), url(Images/checker.png)', colorText, colorText); if (animationDelay) { element.animate([{opacity: 0}, {opacity: 1}], {duration: 100, delay: animationDelay, fill: 'backwards'}); } @@ -1018,8 +1020,8 @@ export class Spectrum extends UI.Widget.VBox { this._colorDragElement.style.backgroundColor = /** @type {string} */ (this._color().asString(Common.Color.Format.RGBA)); const noAlpha = Common.Color.Color.fromHSVA(this._hsv.slice(0, 3).concat(1)); - this._alphaElementBackground.style.backgroundImage = - String.sprintf('linear-gradient(to right, rgba(0,0,0,0), %s)', noAlpha.asString(Common.Color.Format.RGB)); + this._alphaElementBackground.style.backgroundImage = Platform.StringUtilities.sprintf( + 'linear-gradient(to right, rgba(0,0,0,0), %s)', noAlpha.asString(Common.Color.Format.RGB)); } _formatViewSwitch() { @@ -1061,7 +1063,7 @@ export class Spectrum extends UI.Widget.VBox { } else { const format = this._colorFormat === cf.RGB ? 'rgba' : 'hsla'; const values = this._textValues.map(elementValue).join(', '); - colorString = String.sprintf('%s(%s)', format, values); + colorString = Platform.StringUtilities.sprintf('%s(%s)', format, values); } const color = Common.Color.Color.parse(colorString); diff --git a/front_end/common/BUILD.gn b/front_end/common/BUILD.gn index b01a534c01..aef8e9a03d 100644 --- a/front_end/common/BUILD.gn +++ b/front_end/common/BUILD.gn @@ -17,4 +17,6 @@ ts_library("trie") { ts_library("color") { sources = [ "Color.js" ] + + deps = [ "../platform" ] } diff --git a/front_end/common/Color.js b/front_end/common/Color.js index b342560e74..43804a4376 100644 --- a/front_end/common/Color.js +++ b/front_end/common/Color.js @@ -27,6 +27,8 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +import * as Platform from '../platform/platform.js'; + /** @type {?Map} */ let _rgbaToNickname; @@ -579,10 +581,10 @@ export class Color { if (this.hasAlpha()) { return null; } - return String.sprintf( + return Platform.StringUtilities.sprintf( 'rgb(%d, %d, %d)', toRgbValue(this._rgba[0]), toRgbValue(this._rgba[1]), toRgbValue(this._rgba[2])); case Format.RGBA: - return String.sprintf( + return Platform.StringUtilities.sprintf( 'rgba(%d, %d, %d, %f)', toRgbValue(this._rgba[0]), toRgbValue(this._rgba[1]), toRgbValue(this._rgba[2]), this._rgba[3]); case Format.HSL: @@ -590,15 +592,15 @@ export class Color { return null; } const hsl = this.hsla(); - return String.sprintf( + return Platform.StringUtilities.sprintf( 'hsl(%d, %d%, %d%)', Math.round(hsl[0] * 360), Math.round(hsl[1] * 100), Math.round(hsl[2] * 100)); case Format.HSLA: const hsla = this.hsla(); - return String.sprintf( + return Platform.StringUtilities.sprintf( 'hsla(%d, %d%, %d%, %f)', Math.round(hsla[0] * 360), Math.round(hsla[1] * 100), Math.round(hsla[2] * 100), hsla[3]); case Format.HEXA: - return String + return Platform.StringUtilities .sprintf( '#%s%s%s%s', toHexValue(this._rgba[0]), toHexValue(this._rgba[1]), toHexValue(this._rgba[2]), toHexValue(this._rgba[3])) @@ -607,7 +609,7 @@ export class Color { if (this.hasAlpha()) { return null; } - return String + return Platform.StringUtilities .sprintf('#%s%s%s', toHexValue(this._rgba[0]), toHexValue(this._rgba[1]), toHexValue(this._rgba[2])) .toLowerCase(); case Format.ShortHEXA: @@ -615,7 +617,7 @@ export class Color { if (hexFormat !== Format.ShortHEXA && hexFormat !== Format.ShortHEX) { return null; } - return String + return Platform.StringUtilities .sprintf( '#%s%s%s%s', toShortHexValue(this._rgba[0]), toShortHexValue(this._rgba[1]), toShortHexValue(this._rgba[2]), toShortHexValue(this._rgba[3])) @@ -627,7 +629,7 @@ export class Color { if (this.detectHEXFormat() !== Format.ShortHEX) { return null; } - return String + return Platform.StringUtilities .sprintf( '#%s%s%s', toShortHexValue(this._rgba[0]), toShortHexValue(this._rgba[1]), toShortHexValue(this._rgba[2])) diff --git a/front_end/common/UIString.js b/front_end/common/UIString.js index df3db0a33e..98e04dcb2c 100644 --- a/front_end/common/UIString.js +++ b/front_end/common/UIString.js @@ -29,13 +29,15 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +import * as Platform from '../platform/platform.js'; + /** * @param {string} string * @param {...*} vararg * @return {string} */ export function UIString(string, vararg) { - return String.vsprintf(localize(string), Array.prototype.slice.call(arguments, 1)); + return Platform.StringUtilities.vsprintf(localize(string), Array.prototype.slice.call(arguments, 1)); } /** @@ -80,7 +82,8 @@ export class UIStringFormat { /** @type {string} */ this._localizedFormat = localize(format); /** @type {!Array.} */ - this._tokenizedFormat = String.tokenizeFormatString(this._localizedFormat, String.standardFormatters); + this._tokenizedFormat = Platform.StringUtilities.tokenizeFormatString( + this._localizedFormat, Platform.StringUtilities.standardFormatters); } /** @@ -97,9 +100,9 @@ export class UIStringFormat { * @return {string} */ format(vararg) { - return String + return Platform.StringUtilities .format( - this._localizedFormat, arguments, String.standardFormatters, '', UIStringFormat._append, + this._localizedFormat, arguments, Platform.StringUtilities.standardFormatters, '', UIStringFormat._append, this._tokenizedFormat) .formattedResult; } diff --git a/front_end/components/ImagePreview.js b/front_end/components/ImagePreview.js index 6e175b5acf..6498938f6f 100644 --- a/front_end/components/ImagePreview.js +++ b/front_end/components/ImagePreview.js @@ -3,6 +3,7 @@ // found in the LICENSE file. import * as Common from '../common/common.js'; +import * as Platform from '../platform/platform.js'; import * as UI from '../ui/ui.js'; export class ImagePreview { @@ -72,7 +73,7 @@ export class ImagePreview { } if (imageURL !== originalImageURL) { container.createChild('tr').createChild('td').createChild('span', 'description').textContent = - String.sprintf('currentSrc: %s', imageURL.trimMiddle(100)); + Platform.StringUtilities.sprintf('currentSrc: %s', imageURL.trimMiddle(100)); } fulfill(container); } diff --git a/front_end/console/ConsoleSidebar.js b/front_end/console/ConsoleSidebar.js index 4c5273390e..0ab6bfcdd9 100644 --- a/front_end/console/ConsoleSidebar.js +++ b/front_end/console/ConsoleSidebar.js @@ -3,6 +3,7 @@ // found in the LICENSE file. import * as Common from '../common/common.js'; +import * as Platform from '../platform/platform.js'; import * as SDK from '../sdk/sdk.js'; import * as TextUtils from '../text_utils/text_utils.js'; // eslint-disable-line no-unused-vars import * as UI from '../ui/ui.js'; @@ -172,7 +173,7 @@ export class FilterTreeElement extends UI.TreeOutline.TreeElement { } else if (this._messageCount === 1) { this.title = _groupSingularTitleMap.get(this._filter.name); } else { - this.title = String.sprintf(_groupPluralTitleMap.get(this._filter.name), this._messageCount); + this.title = Platform.StringUtilities.sprintf(_groupPluralTitleMap.get(this._filter.name), this._messageCount); } this.setExpandable(!!this.childCount()); diff --git a/front_end/console/ConsoleView.js b/front_end/console/ConsoleView.js index 23839104ad..07ddbd5931 100644 --- a/front_end/console/ConsoleView.js +++ b/front_end/console/ConsoleView.js @@ -31,6 +31,7 @@ import * as Bindings from '../bindings/bindings.js'; import * as Common from '../common/common.js'; import * as Components from '../components/components.js'; import * as Host from '../host/host.js'; +import * as Platform from '../platform/platform.js'; import * as SDK from '../sdk/sdk.js'; import * as TextUtils from '../text_utils/text_utils.js'; import * as UI from '../ui/ui.js'; @@ -760,7 +761,7 @@ export class ConsoleView extends UI.Widget.VBox { async _saveConsole() { const url = self.SDK.targetManager.mainTarget().inspectedURL(); const parsedURL = Common.ParsedURL.ParsedURL.fromString(url); - const filename = String.sprintf('%s-%d.log', parsedURL ? parsedURL.host : 'console', Date.now()); + const filename = Platform.StringUtilities.sprintf('%s-%d.log', parsedURL ? parsedURL.host : 'console', Date.now()); const stream = new Bindings.FileUtils.FileOutputStream(); const progressIndicator = new UI.ProgressIndicator.ProgressIndicator(); diff --git a/front_end/console/ConsoleViewMessage.js b/front_end/console/ConsoleViewMessage.js index 726a66dfc0..423c3927b7 100644 --- a/front_end/console/ConsoleViewMessage.js +++ b/front_end/console/ConsoleViewMessage.js @@ -32,6 +32,7 @@ import * as Common from '../common/common.js'; import * as Components from '../components/components.js'; import * as DataGrid from '../data_grid/data_grid.js'; import * as ObjectUI from '../object_ui/object_ui.js'; +import * as Platform from '../platform/platform.js'; import * as SDK from '../sdk/sdk.js'; import * as TextUtils from '../text_utils/text_utils.js'; import * as UI from '../ui/ui.js'; @@ -963,8 +964,8 @@ export class ConsoleViewMessage { } } - // String.format does treat formattedResult like a Builder, result is an object. - return String.format(format, parameters, formatters, formattedResult, append.bind(this)); + // Platform.StringUtilities.format does treat formattedResult like a Builder, result is an object. + return Platform.StringUtilities.format(format, parameters, formatters, formattedResult, append.bind(this)); } /** diff --git a/front_end/emulation/MediaQueryInspector.js b/front_end/emulation/MediaQueryInspector.js index 818695f448..c57b44b7a0 100644 --- a/front_end/emulation/MediaQueryInspector.js +++ b/front_end/emulation/MediaQueryInspector.js @@ -3,6 +3,7 @@ // found in the LICENSE file. import * as Common from '../common/common.js'; +import * as Platform from '../platform/platform.js'; import * as SDK from '../sdk/sdk.js'; import * as UI from '../ui/ui.js'; import * as Workspace from '../workspace/workspace.js'; // eslint-disable-line no-unused-vars @@ -122,7 +123,7 @@ export class MediaQueryInspector extends UI.Widget.Widget { if (!uiLocation) { continue; } - const descriptor = String.sprintf( + const descriptor = Platform.StringUtilities.sprintf( '%s:%d:%d', uiLocation.uiSourceCode.url(), uiLocation.lineNumber + 1, uiLocation.columnNumber + 1); uiLocations.set(descriptor, uiLocation); } diff --git a/front_end/extensions/ExtensionServer.js b/front_end/extensions/ExtensionServer.js index eb4d0891d6..03da686382 100644 --- a/front_end/extensions/ExtensionServer.js +++ b/front_end/extensions/ExtensionServer.js @@ -32,6 +32,7 @@ import * as Bindings from '../bindings/bindings.js'; import * as Common from '../common/common.js'; import * as Components from '../components/components.js'; import * as Host from '../host/host.js'; +import * as Platform from '../platform/platform.js'; import * as ProtocolModule from '../protocol/protocol.js'; // eslint-disable-line no-unused-vars import * as SDK from '../sdk/sdk.js'; import * as TextUtils from '../text_utils/text_utils.js'; // eslint-disable-line no-unused-vars @@ -1027,7 +1028,7 @@ export class ExtensionStatus { const status = {code: code, description: description, details: details}; if (code !== 'OK') { status.isError = true; - console.error('Extension server error: ' + String.vsprintf(description, details)); + console.error('Extension server error: ' + Platform.StringUtilities.vsprintf(description, details)); } return status; } diff --git a/front_end/object_ui/ObjectPropertiesSection.js b/front_end/object_ui/ObjectPropertiesSection.js index 05d7d9acf8..315dac3572 100644 --- a/front_end/object_ui/ObjectPropertiesSection.js +++ b/front_end/object_ui/ObjectPropertiesSection.js @@ -27,6 +27,7 @@ import * as Common from '../common/common.js'; import * as Components from '../components/components.js'; // eslint-disable-line no-unused-vars import * as Host from '../host/host.js'; +import * as Platform from '../platform/platform.js'; import * as SDK from '../sdk/sdk.js'; import * as TextUtils from '../text_utils/text_utils.js'; import * as UI from '../ui/ui.js'; @@ -1178,7 +1179,7 @@ export class ArrayGroupingTreeElement extends UI.TreeOutline.TreeElement { * @param {!Components.Linkifier.Linkifier=} linkifier */ constructor(object, fromIndex, toIndex, propertyCount, linkifier) { - super(String.sprintf('[%d \u2026 %d]', fromIndex, toIndex), true); + super(Platform.StringUtilities.sprintf('[%d \u2026 %d]', fromIndex, toIndex), true); this.toggleOnClick = true; this._fromIndex = fromIndex; this._toIndex = toIndex; diff --git a/front_end/platform/BUILD.gn b/front_end/platform/BUILD.gn new file mode 100644 index 0000000000..77b8b1b265 --- /dev/null +++ b/front_end/platform/BUILD.gn @@ -0,0 +1,9 @@ +import("../../third_party/typescript/typescript.gni") + +ts_library("platform") { + sources = [ + "platform.js", + "string-utilities.js", + "utilities.js", + ] +} diff --git a/front_end/platform/platform.js b/front_end/platform/platform.js index f666b7622f..006f86fdd2 100644 --- a/front_end/platform/platform.js +++ b/front_end/platform/platform.js @@ -32,4 +32,5 @@ import './utilities.js'; import * as StringUtilities from './string-utilities.js'; +export {Multimap} from './utilities.js'; export {StringUtilities}; diff --git a/front_end/platform/string-utilities.js b/front_end/platform/string-utilities.js index 357c8b3aa3..5eb286ef3f 100644 --- a/front_end/platform/string-utilities.js +++ b/front_end/platform/string-utilities.js @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// @ts-nocheck + /** * @param {string} inputString * @param {string} charsToEscape @@ -30,3 +32,203 @@ export const escapeCharacters = (inputString, charsToEscape) => { return result; }; + +/** + * @param {string} formatString + * @param {!Object.} formatters + * @return {!Array.} + */ +export const tokenizeFormatString = function(formatString, formatters) { + const tokens = []; + + function addStringToken(str) { + if (!str) { + return; + } + if (tokens.length && tokens[tokens.length - 1].type === 'string') { + tokens[tokens.length - 1].value += str; + } else { + tokens.push({type: 'string', value: str}); + } + } + + function addSpecifierToken(specifier, precision, substitutionIndex) { + tokens.push({type: 'specifier', specifier: specifier, precision: precision, substitutionIndex: substitutionIndex}); + } + + function addAnsiColor(code) { + const types = {3: 'color', 9: 'colorLight', 4: 'bgColor', 10: 'bgColorLight'}; + const colorCodes = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'lightGray', '', 'default']; + const colorCodesLight = + ['darkGray', 'lightRed', 'lightGreen', 'lightYellow', 'lightBlue', 'lightMagenta', 'lightCyan', 'white', '']; + const colors = {color: colorCodes, colorLight: colorCodesLight, bgColor: colorCodes, bgColorLight: colorCodesLight}; + const type = types[Math.floor(code / 10)]; + if (!type) { + return; + } + const color = colors[type][code % 10]; + if (!color) { + return; + } + tokens.push({ + type: 'specifier', + specifier: 'c', + value: {description: (type.startsWith('bg') ? 'background : ' : 'color: ') + color} + }); + } + + let textStart = 0; + let substitutionIndex = 0; + const re = + new RegExp(`%%|%(?:(\\d+)\\$)?(?:\\.(\\d*))?([${Object.keys(formatters).join('')}])|\\u001b\\[(\\d+)m`, 'g'); + for (let match = re.exec(formatString); !!match; match = re.exec(formatString)) { + const matchStart = match.index; + if (matchStart > textStart) { + addStringToken(formatString.substring(textStart, matchStart)); + } + + if (match[0] === '%%') { + addStringToken('%'); + } else if (match[0].startsWith('%')) { + // eslint-disable-next-line no-unused-vars + const [_, substitionString, precisionString, specifierString] = match; + if (substitionString && Number(substitionString) > 0) { + substitutionIndex = Number(substitionString) - 1; + } + const precision = precisionString ? Number(precisionString) : -1; + addSpecifierToken(specifierString, precision, substitutionIndex); + ++substitutionIndex; + } else { + const code = Number(match[4]); + addAnsiColor(code); + } + textStart = matchStart + match[0].length; + } + addStringToken(formatString.substring(textStart)); + return tokens; +}; + +/** + * @param {string} formatString + * @param {?ArrayLike} substitutions + * @param {!Object.} formatters + * @param {!T} initialValue + * @param {function(T, Q): T|undefined} append + * @param {!Array.=} tokenizedFormat + * @return {!{formattedResult: T, unusedSubstitutions: ?ArrayLike}}; + * @template T, Q + */ +export const format = function(formatString, substitutions, formatters, initialValue, append, tokenizedFormat) { + if (!formatString || ((!substitutions || !substitutions.length) && formatString.search(/\u001b\[(\d+)m/) === -1)) { + return {formattedResult: append(initialValue, formatString), unusedSubstitutions: substitutions}; + } + + function prettyFunctionName() { + return 'String.format("' + formatString + '", "' + Array.prototype.join.call(substitutions, '", "') + '")'; + } + + function warn(msg) { + console.warn(prettyFunctionName() + ': ' + msg); + } + + function error(msg) { + console.error(prettyFunctionName() + ': ' + msg); + } + + let result = initialValue; + const tokens = tokenizedFormat || tokenizeFormatString(formatString, formatters); + const usedSubstitutionIndexes = {}; + + for (let i = 0; i < tokens.length; ++i) { + const token = tokens[i]; + + if (token.type === 'string') { + result = append(result, token.value); + continue; + } + + if (token.type !== 'specifier') { + error('Unknown token type "' + token.type + '" found.'); + continue; + } + + if (!token.value && token.substitutionIndex >= substitutions.length) { + // If there are not enough substitutions for the current substitutionIndex + // just output the format specifier literally and move on. + error( + 'not enough substitution arguments. Had ' + substitutions.length + ' but needed ' + + (token.substitutionIndex + 1) + ', so substitution was skipped.'); + result = append(result, '%' + (token.precision > -1 ? token.precision : '') + token.specifier); + continue; + } + + if (!token.value) { + usedSubstitutionIndexes[token.substitutionIndex] = true; + } + + if (!(token.specifier in formatters)) { + // Encountered an unsupported format character, treat as a string. + warn('unsupported format character \u201C' + token.specifier + '\u201D. Treating as a string.'); + result = append(result, token.value ? '' : substitutions[token.substitutionIndex]); + continue; + } + + result = append(result, formatters[token.specifier](token.value || substitutions[token.substitutionIndex], token)); + } + + const unusedSubstitutions = []; + for (let i = 0; i < substitutions.length; ++i) { + if (i in usedSubstitutionIndexes) { + continue; + } + unusedSubstitutions.push(substitutions[i]); + } + + return {formattedResult: result, unusedSubstitutions: unusedSubstitutions}; +}; + +export const standardFormatters = { + /** + * @return {number} + */ + d: function(substitution) { + return !isNaN(substitution) ? substitution : 0; + }, + + /** + * @return {number} + */ + f: function(substitution, token) { + if (substitution && token.precision > -1) { + substitution = substitution.toFixed(token.precision); + } + return !isNaN(substitution) ? substitution : (token.precision > -1 ? Number(0).toFixed(token.precision) : 0); + }, + + /** + * @return {string} + */ + s: function(substitution) { + return substitution; + } +}; + +/** + * @param {string} formatString + * @param {!Array.<*>} substitutions + * @return {string} + */ +export const vsprintf = function(formatString, substitutions) { + return format(formatString, substitutions, standardFormatters, '', function(a, b) { + return a + b; + }).formattedResult; +}; + +/** + * @param {string} format + * @param {...*} var_arg + * @return {string} + */ +export const sprintf = function(format, var_arg) { + return vsprintf(format, Array.prototype.slice.call(arguments, 1)); +}; diff --git a/front_end/platform/utilities.js b/front_end/platform/utilities.js index a182fc2d73..f2c083ea6a 100644 --- a/front_end/platform/utilities.js +++ b/front_end/platform/utilities.js @@ -26,6 +26,7 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +// @ts-nocheck /* the long term goal here is to remove all functions in this file and * replace them with ES Module functions rather than prototype @@ -34,6 +35,10 @@ */ import * as StringUtilities from './string-utilities.js'; + +// Still used in the test runners that can't use ES modules :( +String.sprintf = StringUtilities.sprintf; + /** * @param {number} m * @param {number} n @@ -702,210 +707,6 @@ Object.defineProperty(Array.prototype, 'peekLast', { }); })(); -/** - * @param {string} format - * @param {...*} var_arg - * @return {string} - */ -String.sprintf = function(format, var_arg) { - return String.vsprintf(format, Array.prototype.slice.call(arguments, 1)); -}; - -/** - * @param {string} format - * @param {!Object.} formatters - * @return {!Array.} - */ -String.tokenizeFormatString = function(format, formatters) { - const tokens = []; - - function addStringToken(str) { - if (!str) { - return; - } - if (tokens.length && tokens[tokens.length - 1].type === 'string') { - tokens[tokens.length - 1].value += str; - } else { - tokens.push({type: 'string', value: str}); - } - } - - function addSpecifierToken(specifier, precision, substitutionIndex) { - tokens.push({type: 'specifier', specifier: specifier, precision: precision, substitutionIndex: substitutionIndex}); - } - - function addAnsiColor(code) { - const types = {3: 'color', 9: 'colorLight', 4: 'bgColor', 10: 'bgColorLight'}; - const colorCodes = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'lightGray', '', 'default']; - const colorCodesLight = - ['darkGray', 'lightRed', 'lightGreen', 'lightYellow', 'lightBlue', 'lightMagenta', 'lightCyan', 'white', '']; - const colors = {color: colorCodes, colorLight: colorCodesLight, bgColor: colorCodes, bgColorLight: colorCodesLight}; - const type = types[Math.floor(code / 10)]; - if (!type) { - return; - } - const color = colors[type][code % 10]; - if (!color) { - return; - } - tokens.push({ - type: 'specifier', - specifier: 'c', - value: {description: (type.startsWith('bg') ? 'background : ' : 'color: ') + color} - }); - } - - let textStart = 0; - let substitutionIndex = 0; - const re = - new RegExp(`%%|%(?:(\\d+)\\$)?(?:\\.(\\d*))?([${Object.keys(formatters).join('')}])|\\u001b\\[(\\d+)m`, 'g'); - for (let match = re.exec(format); !!match; match = re.exec(format)) { - const matchStart = match.index; - if (matchStart > textStart) { - addStringToken(format.substring(textStart, matchStart)); - } - - if (match[0] === '%%') { - addStringToken('%'); - } else if (match[0].startsWith('%')) { - // eslint-disable-next-line no-unused-vars - const [_, substitionString, precisionString, specifierString] = match; - if (substitionString && Number(substitionString) > 0) { - substitutionIndex = Number(substitionString) - 1; - } - const precision = precisionString ? Number(precisionString) : -1; - addSpecifierToken(specifierString, precision, substitutionIndex); - ++substitutionIndex; - } else { - const code = Number(match[4]); - addAnsiColor(code); - } - textStart = matchStart + match[0].length; - } - addStringToken(format.substring(textStart)); - return tokens; -}; - -String.standardFormatters = { - /** - * @return {number} - */ - d: function(substitution) { - return !isNaN(substitution) ? substitution : 0; - }, - - /** - * @return {number} - */ - f: function(substitution, token) { - if (substitution && token.precision > -1) { - substitution = substitution.toFixed(token.precision); - } - return !isNaN(substitution) ? substitution : (token.precision > -1 ? Number(0).toFixed(token.precision) : 0); - }, - - /** - * @return {string} - */ - s: function(substitution) { - return substitution; - } -}; - -/** - * @param {string} format - * @param {!Array.<*>} substitutions - * @return {string} - */ -String.vsprintf = function(format, substitutions) { - return String - .format( - format, substitutions, String.standardFormatters, '', - function(a, b) { - return a + b; - }) - .formattedResult; -}; - -/** - * @param {string} format - * @param {?ArrayLike} substitutions - * @param {!Object.} formatters - * @param {!T} initialValue - * @param {function(T, Q): T|undefined} append - * @param {!Array.=} tokenizedFormat - * @return {!{formattedResult: T, unusedSubstitutions: ?ArrayLike}}; - * @template T, Q - */ -String.format = function(format, substitutions, formatters, initialValue, append, tokenizedFormat) { - if (!format || ((!substitutions || !substitutions.length) && format.search(/\u001b\[(\d+)m/) === -1)) { - return {formattedResult: append(initialValue, format), unusedSubstitutions: substitutions}; - } - - function prettyFunctionName() { - return 'String.format("' + format + '", "' + Array.prototype.join.call(substitutions, '", "') + '")'; - } - - function warn(msg) { - console.warn(prettyFunctionName() + ': ' + msg); - } - - function error(msg) { - console.error(prettyFunctionName() + ': ' + msg); - } - - let result = initialValue; - const tokens = tokenizedFormat || String.tokenizeFormatString(format, formatters); - const usedSubstitutionIndexes = {}; - - for (let i = 0; i < tokens.length; ++i) { - const token = tokens[i]; - - if (token.type === 'string') { - result = append(result, token.value); - continue; - } - - if (token.type !== 'specifier') { - error('Unknown token type "' + token.type + '" found.'); - continue; - } - - if (!token.value && token.substitutionIndex >= substitutions.length) { - // If there are not enough substitutions for the current substitutionIndex - // just output the format specifier literally and move on. - error( - 'not enough substitution arguments. Had ' + substitutions.length + ' but needed ' + - (token.substitutionIndex + 1) + ', so substitution was skipped.'); - result = append(result, '%' + (token.precision > -1 ? token.precision : '') + token.specifier); - continue; - } - - if (!token.value) { - usedSubstitutionIndexes[token.substitutionIndex] = true; - } - - if (!(token.specifier in formatters)) { - // Encountered an unsupported format character, treat as a string. - warn('unsupported format character \u201C' + token.specifier + '\u201D. Treating as a string.'); - result = append(result, token.value ? '' : substitutions[token.substitutionIndex]); - continue; - } - - result = append(result, formatters[token.specifier](token.value || substitutions[token.substitutionIndex], token)); - } - - const unusedSubstitutions = []; - for (let i = 0; i < substitutions.length; ++i) { - if (i in usedSubstitutionIndexes) { - continue; - } - unusedSubstitutions.push(substitutions[i]); - } - - return {formattedResult: result, unusedSubstitutions: unusedSubstitutions}; -}; - /** * @param {string} query * @param {boolean} caseSensitive @@ -1015,7 +816,7 @@ Map.prototype.inverse = function() { /** * @template K, V */ -const Multimap = class { +export class Multimap { constructor() { /** @type {!Map.>} */ this._map = new Map(); @@ -1115,7 +916,7 @@ const Multimap = class { clear() { this._map.clear(); } -}; +} /** * @param {string} url @@ -1176,6 +977,7 @@ Promise.prototype.catchException = function(defaultValue) { /** * TODO: move into its own module * @param {function()} callback + * @suppressGlobalPropertiesCheck */ self.runOnWindowLoad = function(callback) { /** diff --git a/front_end/sdk/CSSProperty.js b/front_end/sdk/CSSProperty.js index a3e414a199..ec6fd7b0be 100644 --- a/front_end/sdk/CSSProperty.js +++ b/front_end/sdk/CSSProperty.js @@ -3,6 +3,7 @@ // found in the LICENSE file. import * as HostModule from '../host/host.js'; +import * as Platform from '../platform/platform.js'; import {cssMetadata, GridAreaRowRegex} from './CSSMetadata.js'; import {Edit} from './CSSModel.js'; // eslint-disable-line no-unused-vars @@ -183,7 +184,7 @@ export class CSSProperty { self.Common.settings.moduleSetting('textEditorIndent').get(); const endIndentation = this.ownerStyle.cssText ? indentation.substring(0, this.ownerStyle.range.endColumn) : ''; const text = new TextUtils.Text(this.ownerStyle.cssText || ''); - const newStyleText = text.replaceRange(range, String.sprintf(';%s;', propertyText)); + const newStyleText = text.replaceRange(range, Platform.StringUtilities.sprintf(';%s;', propertyText)); const tokenizerFactory = await self.runtime.extension(TextUtils.TokenizerFactory).instance(); const styleText = CSSProperty._formatStyle(newStyleText, indentation, endIndentation, tokenizerFactory); diff --git a/front_end/sdk/NetworkManager.js b/front_end/sdk/NetworkManager.js index 1db16c5d81..c155307fc9 100644 --- a/front_end/sdk/NetworkManager.js +++ b/front_end/sdk/NetworkManager.js @@ -30,6 +30,7 @@ import * as Common from '../common/common.js'; import * as Host from '../host/host.js'; +import * as Platform from '../platform/platform.js'; import * as ProtocolModule from '../protocol/protocol.js'; import {Cookie} from './Cookie.js'; @@ -1047,7 +1048,7 @@ export class MultitargetNetworkManager extends Common.ObjectWrapper.ObjectWrappe if (chromeMatch && chromeMatch.length > 1) { // "1.2.3.4" becomes "1.0.100.0" const additionalAppVersion = chromeMatch[1].split('.', 1)[0] + '.0.100.0'; - return String.sprintf(uaString, chromeMatch[1], additionalAppVersion); + return Platform.StringUtilities.sprintf(uaString, chromeMatch[1], additionalAppVersion); } return uaString; } diff --git a/front_end/source_frame/FontView.js b/front_end/source_frame/FontView.js index 3805f10df3..56316720eb 100644 --- a/front_end/source_frame/FontView.js +++ b/front_end/source_frame/FontView.js @@ -27,6 +27,7 @@ */ import * as Common from '../common/common.js'; +import * as Platform from '../platform/platform.js'; import * as UI from '../ui/ui.js'; /** @@ -64,7 +65,7 @@ export class FontView extends UI.View.SimpleView { const {content} = deferredContent; const url = content ? Common.ContentProvider.contentAsDataURL(content, this._mimeType, true) : this._url; this.fontStyleElement.textContent = - String.sprintf('@font-face { font-family: "%s"; src: url(%s); }', uniqueFontName, url); + Platform.StringUtilities.sprintf('@font-face { font-family: "%s"; src: url(%s); }', uniqueFontName, url); } _createContentIfNeeded() { diff --git a/front_end/sources/CSSPlugin.js b/front_end/sources/CSSPlugin.js index d0e4392aa1..eea9817a58 100644 --- a/front_end/sources/CSSPlugin.js +++ b/front_end/sources/CSSPlugin.js @@ -31,6 +31,7 @@ import * as ColorPicker from '../color_picker/color_picker.js'; import * as Common from '../common/common.js'; import * as InlineEditor from '../inline_editor/inline_editor.js'; +import * as Platform from '../platform/platform.js'; import * as SDK from '../sdk/sdk.js'; import * as SourceFrame from '../source_frame/source_frame.js'; import * as TextUtils from '../text_utils/text_utils.js'; @@ -123,7 +124,7 @@ export class CSSPlugin extends Plugin { return null; } const tail = unit.substring((unitValue).toString().length); - return String.sprintf('%d%s', unitValue + change, tail); + return Platform.StringUtilities.sprintf('%d%s', unitValue + change, tail); } /** diff --git a/front_end/timeline/TimelineUIUtils.js b/front_end/timeline/TimelineUIUtils.js index c87c993938..b17829a583 100644 --- a/front_end/timeline/TimelineUIUtils.js +++ b/front_end/timeline/TimelineUIUtils.js @@ -33,6 +33,7 @@ import * as Bindings from '../bindings/bindings.js'; import * as Common from '../common/common.js'; import * as Components from '../components/components.js'; import * as PerfUI from '../perf_ui/perf_ui.js'; +import * as Platform from '../platform/platform.js'; import * as SDK from '../sdk/sdk.js'; import * as TimelineModel from '../timeline_model/timeline_model.js'; import * as UI from '../ui/ui.js'; @@ -2496,7 +2497,8 @@ export class TimelineDetailsContentHelper { return; } locationContent.appendChild(link); - locationContent.createTextChild(String.sprintf(' [%s\u2026%s]', startLine + 1, endLine + 1 || '')); + locationContent.createTextChild( + Platform.StringUtilities.sprintf(' [%s\u2026%s]', startLine + 1, endLine + 1 || '')); this.appendElementRow(title, locationContent); } diff --git a/front_end/ui/UIUtils.js b/front_end/ui/UIUtils.js index 9d9c1097ba..85ee4735b3 100644 --- a/front_end/ui/UIUtils.js +++ b/front_end/ui/UIUtils.js @@ -32,6 +32,7 @@ import * as Common from '../common/common.js'; import * as Host from '../host/host.js'; +import * as Platform from '../platform/platform.js'; import * as ARIAUtils from './ARIAUtils.js'; import {Dialog} from './Dialog.js'; @@ -711,7 +712,8 @@ export function formatLocalized(format, substitutions) { a.appendChild(typeof b === 'string' ? createTextNode(b) : b); return a; } - return String.format(Common.UIString.UIString(format), substitutions, formatters, createElement('span'), append) + return Platform.StringUtilities + .format(Common.UIString.UIString(format), substitutions, formatters, createElement('span'), append) .formattedResult; }