2016-01-25 22:19:58 +03:00
|
|
|
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
|
|
|
/* vim: set ft= javascript ts=2 et sw=2 tw=80: */
|
2012-05-10 17:15:10 +04:00
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
2016-01-25 22:19:58 +03:00
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
2012-05-10 17:15:10 +04:00
|
|
|
|
|
|
|
"use strict";
|
|
|
|
|
2013-10-22 11:43:00 +04:00
|
|
|
const {Cc, Ci, Cu, components} = require("chrome");
|
2016-02-27 15:51:10 +03:00
|
|
|
const Services = require("Services");
|
2016-08-25 18:02:50 +03:00
|
|
|
const {LocalizationHelper} = require("devtools/shared/l10n");
|
2015-11-16 19:04:12 +03:00
|
|
|
|
2012-09-26 21:02:04 +04:00
|
|
|
// Match the function name from the result of toString() or toSource().
|
|
|
|
//
|
|
|
|
// Examples:
|
|
|
|
// (function foobar(a, b) { ...
|
|
|
|
// function foobar2(a) { ...
|
|
|
|
// function() { ...
|
|
|
|
const REGEX_MATCH_FUNCTION_NAME = /^\(?function\s+([^(\s]+)\s*\(/;
|
|
|
|
|
2014-05-22 02:34:00 +04:00
|
|
|
// Number of terminal entries for the self-xss prevention to go away
|
2015-04-06 16:41:00 +03:00
|
|
|
const CONSOLE_ENTRY_THRESHOLD = 5;
|
2014-08-06 15:56:00 +04:00
|
|
|
|
2016-03-16 10:16:00 +03:00
|
|
|
const CONSOLE_WORKER_IDS = exports.CONSOLE_WORKER_IDS = [
|
|
|
|
"SharedWorker",
|
|
|
|
"ServiceWorker",
|
|
|
|
"Worker"
|
|
|
|
];
|
2014-08-06 15:56:00 +04:00
|
|
|
|
2015-09-15 21:19:45 +03:00
|
|
|
var WebConsoleUtils = {
|
2012-05-10 17:15:10 +04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Wrap a string in an nsISupportsString object.
|
|
|
|
*
|
2016-03-16 10:16:00 +03:00
|
|
|
* @param string string
|
2012-05-10 17:15:10 +04:00
|
|
|
* @return nsISupportsString
|
|
|
|
*/
|
2016-04-27 05:32:42 +03:00
|
|
|
supportsString: function (string) {
|
2016-03-16 10:16:00 +03:00
|
|
|
let str = Cc["@mozilla.org/supports-string;1"]
|
|
|
|
.createInstance(Ci.nsISupportsString);
|
|
|
|
str.data = string;
|
2012-05-10 17:15:10 +04:00
|
|
|
return str;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Clone an object.
|
|
|
|
*
|
2016-03-16 10:16:00 +03:00
|
|
|
* @param object object
|
2012-05-10 17:15:10 +04:00
|
|
|
* The object you want cloned.
|
2016-03-16 10:16:00 +03:00
|
|
|
* @param boolean recursive
|
2012-05-10 17:15:10 +04:00
|
|
|
* Tells if you want to dig deeper into the object, to clone
|
|
|
|
* recursively.
|
2016-03-16 10:16:00 +03:00
|
|
|
* @param function [filter]
|
2012-05-10 17:15:10 +04:00
|
|
|
* Optional, filter function, called for every property. Three
|
|
|
|
* arguments are passed: key, value and object. Return true if the
|
|
|
|
* property should be added to the cloned object. Return false to skip
|
|
|
|
* the property.
|
|
|
|
* @return object
|
|
|
|
* The cloned object.
|
|
|
|
*/
|
2016-04-27 05:32:42 +03:00
|
|
|
cloneObject: function (object, recursive, filter) {
|
2016-03-16 10:16:00 +03:00
|
|
|
if (typeof object != "object") {
|
|
|
|
return object;
|
2012-05-10 17:15:10 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
let temp;
|
|
|
|
|
2016-03-16 10:16:00 +03:00
|
|
|
if (Array.isArray(object)) {
|
2012-05-10 17:15:10 +04:00
|
|
|
temp = [];
|
2016-04-27 05:32:42 +03:00
|
|
|
Array.forEach(object, function (value, index) {
|
2016-03-16 10:16:00 +03:00
|
|
|
if (!filter || filter(index, value, object)) {
|
|
|
|
temp.push(recursive ? WebConsoleUtils.cloneObject(value) : value);
|
2012-05-10 17:15:10 +04:00
|
|
|
}
|
|
|
|
});
|
2016-03-16 10:16:00 +03:00
|
|
|
} else {
|
2012-05-10 17:15:10 +04:00
|
|
|
temp = {};
|
2016-03-16 10:16:00 +03:00
|
|
|
for (let key in object) {
|
|
|
|
let value = object[key];
|
|
|
|
if (object.hasOwnProperty(key) &&
|
|
|
|
(!filter || filter(key, value, object))) {
|
|
|
|
temp[key] = recursive ? WebConsoleUtils.cloneObject(value) : value;
|
2012-05-10 17:15:10 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return temp;
|
|
|
|
},
|
|
|
|
|
2013-11-09 16:36:23 +04:00
|
|
|
/**
|
|
|
|
* Copies certain style attributes from one element to another.
|
|
|
|
*
|
2016-03-16 10:16:00 +03:00
|
|
|
* @param nsIDOMNode from
|
2013-11-09 16:36:23 +04:00
|
|
|
* The target node.
|
2016-03-16 10:16:00 +03:00
|
|
|
* @param nsIDOMNode to
|
2013-11-09 16:36:23 +04:00
|
|
|
* The destination node.
|
|
|
|
*/
|
2016-04-27 05:32:42 +03:00
|
|
|
copyTextStyles: function (from, to) {
|
2016-03-16 10:16:00 +03:00
|
|
|
let win = from.ownerDocument.defaultView;
|
|
|
|
let style = win.getComputedStyle(from);
|
|
|
|
to.style.fontFamily = style.getPropertyCSSValue("font-family").cssText;
|
|
|
|
to.style.fontSize = style.getPropertyCSSValue("font-size").cssText;
|
|
|
|
to.style.fontWeight = style.getPropertyCSSValue("font-weight").cssText;
|
|
|
|
to.style.fontStyle = style.getPropertyCSSValue("font-style").cssText;
|
2013-11-09 16:36:23 +04:00
|
|
|
},
|
|
|
|
|
2012-09-26 21:02:04 +04:00
|
|
|
/**
|
|
|
|
* Create a grip for the given value. If the value is an object,
|
|
|
|
* an object wrapper will be created.
|
|
|
|
*
|
2016-03-16 10:16:00 +03:00
|
|
|
* @param mixed value
|
2012-09-26 21:02:04 +04:00
|
|
|
* The value you want to create a grip for, before sending it to the
|
|
|
|
* client.
|
2016-03-16 10:16:00 +03:00
|
|
|
* @param function objectWrapper
|
|
|
|
* If the value is an object then the objectWrapper function is
|
2012-09-26 21:02:04 +04:00
|
|
|
* invoked to give us an object grip. See this.getObjectGrip().
|
|
|
|
* @return mixed
|
|
|
|
* The value grip.
|
|
|
|
*/
|
2016-04-27 05:32:42 +03:00
|
|
|
createValueGrip: function (value, objectWrapper) {
|
2016-03-16 10:16:00 +03:00
|
|
|
switch (typeof value) {
|
2012-09-26 21:02:04 +04:00
|
|
|
case "boolean":
|
2016-03-16 10:16:00 +03:00
|
|
|
return value;
|
2012-11-05 20:41:59 +04:00
|
|
|
case "string":
|
2016-03-16 10:16:00 +03:00
|
|
|
return objectWrapper(value);
|
2013-08-12 21:15:22 +04:00
|
|
|
case "number":
|
2016-03-16 10:16:00 +03:00
|
|
|
if (value === Infinity) {
|
2013-08-12 21:15:22 +04:00
|
|
|
return { type: "Infinity" };
|
2016-03-16 10:16:00 +03:00
|
|
|
} else if (value === -Infinity) {
|
2013-08-12 21:15:22 +04:00
|
|
|
return { type: "-Infinity" };
|
2016-03-16 10:16:00 +03:00
|
|
|
} else if (Number.isNaN(value)) {
|
2013-08-12 21:15:22 +04:00
|
|
|
return { type: "NaN" };
|
2016-03-16 10:16:00 +03:00
|
|
|
} else if (!value && 1 / value === -Infinity) {
|
2013-08-12 21:15:22 +04:00
|
|
|
return { type: "-0" };
|
|
|
|
}
|
2016-03-16 10:16:00 +03:00
|
|
|
return value;
|
2013-08-12 21:15:22 +04:00
|
|
|
case "undefined":
|
|
|
|
return { type: "undefined" };
|
|
|
|
case "object":
|
2016-03-16 10:16:00 +03:00
|
|
|
if (value === null) {
|
2012-09-26 21:02:04 +04:00
|
|
|
return { type: "null" };
|
|
|
|
}
|
2016-03-16 10:16:00 +03:00
|
|
|
// Fall through.
|
2013-08-12 21:15:22 +04:00
|
|
|
case "function":
|
2016-03-16 10:16:00 +03:00
|
|
|
return objectWrapper(value);
|
2013-08-12 21:15:22 +04:00
|
|
|
default:
|
2016-05-11 04:55:00 +03:00
|
|
|
console.error("Failed to provide a grip for value of " + typeof value
|
|
|
|
+ ": " + value);
|
2012-09-26 21:02:04 +04:00
|
|
|
return null;
|
|
|
|
}
|
2012-05-10 17:15:10 +04:00
|
|
|
},
|
|
|
|
|
2012-07-26 19:06:04 +04:00
|
|
|
/**
|
|
|
|
* Determine if the given request mixes HTTP with HTTPS content.
|
|
|
|
*
|
2016-03-16 10:16:00 +03:00
|
|
|
* @param string request
|
2012-07-26 19:06:04 +04:00
|
|
|
* Location of the requested content.
|
2016-03-16 10:16:00 +03:00
|
|
|
* @param string location
|
2012-07-26 19:06:04 +04:00
|
|
|
* Location of the current page.
|
|
|
|
* @return boolean
|
|
|
|
* True if the content is mixed, false if not.
|
|
|
|
*/
|
2016-04-27 05:32:42 +03:00
|
|
|
isMixedHTTPSRequest: function (request, location) {
|
2012-07-26 19:06:04 +04:00
|
|
|
try {
|
2016-03-16 10:16:00 +03:00
|
|
|
let requestURI = Services.io.newURI(request, null, null);
|
|
|
|
let contentURI = Services.io.newURI(location, null, null);
|
2012-07-26 19:06:04 +04:00
|
|
|
return (contentURI.scheme == "https" && requestURI.scheme != "https");
|
2016-03-16 10:16:00 +03:00
|
|
|
} catch (ex) {
|
2012-07-26 19:06:04 +04:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
},
|
2012-09-26 21:02:04 +04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper function to deduce the name of the provided function.
|
|
|
|
*
|
2016-03-16 10:16:00 +03:00
|
|
|
* @param funtion function
|
2012-09-26 21:02:04 +04:00
|
|
|
* The function whose name will be returned.
|
|
|
|
* @return string
|
|
|
|
* Function name.
|
|
|
|
*/
|
2016-04-27 05:32:42 +03:00
|
|
|
getFunctionName: function (func) {
|
2012-09-26 21:02:04 +04:00
|
|
|
let name = null;
|
2016-03-16 10:16:00 +03:00
|
|
|
if (func.name) {
|
|
|
|
name = func.name;
|
|
|
|
} else {
|
2012-09-26 21:02:04 +04:00
|
|
|
let desc;
|
|
|
|
try {
|
2016-03-16 10:16:00 +03:00
|
|
|
desc = func.getOwnPropertyDescriptor("displayName");
|
|
|
|
} catch (ex) {
|
|
|
|
// Ignore.
|
2012-09-26 21:02:04 +04:00
|
|
|
}
|
|
|
|
if (desc && typeof desc.value == "string") {
|
|
|
|
name = desc.value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!name) {
|
|
|
|
try {
|
2016-03-16 10:16:00 +03:00
|
|
|
let str = (func.toString() || func.toSource()) + "";
|
2012-09-26 21:02:04 +04:00
|
|
|
name = (str.match(REGEX_MATCH_FUNCTION_NAME) || [])[1];
|
2016-03-16 10:16:00 +03:00
|
|
|
} catch (ex) {
|
|
|
|
// Ignore.
|
2012-09-26 21:02:04 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return name;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the object class name. For example, the |window| object has the Window
|
|
|
|
* class name (based on [object Window]).
|
|
|
|
*
|
2016-03-16 10:16:00 +03:00
|
|
|
* @param object object
|
2012-09-26 21:02:04 +04:00
|
|
|
* The object you want to get the class name for.
|
|
|
|
* @return string
|
|
|
|
* The object class name.
|
|
|
|
*/
|
2016-04-27 05:32:42 +03:00
|
|
|
getObjectClassName: function (object) {
|
2016-03-16 10:16:00 +03:00
|
|
|
if (object === null) {
|
2012-09-26 21:02:04 +04:00
|
|
|
return "null";
|
|
|
|
}
|
2016-03-16 10:16:00 +03:00
|
|
|
if (object === undefined) {
|
2012-09-26 21:02:04 +04:00
|
|
|
return "undefined";
|
|
|
|
}
|
|
|
|
|
2016-03-16 10:16:00 +03:00
|
|
|
let type = typeof object;
|
2012-09-26 21:02:04 +04:00
|
|
|
if (type != "object") {
|
2013-01-22 01:59:30 +04:00
|
|
|
// Grip class names should start with an uppercase letter.
|
|
|
|
return type.charAt(0).toUpperCase() + type.substr(1);
|
2012-09-26 21:02:04 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
let className;
|
|
|
|
|
|
|
|
try {
|
2016-03-16 10:16:00 +03:00
|
|
|
className = ((object + "").match(/^\[object (\S+)\]$/) || [])[1];
|
2012-09-26 21:02:04 +04:00
|
|
|
if (!className) {
|
2016-03-16 10:16:00 +03:00
|
|
|
className = ((object.constructor + "")
|
|
|
|
.match(/^\[object (\S+)\]$/) || [])[1];
|
2012-09-26 21:02:04 +04:00
|
|
|
}
|
2016-03-16 10:16:00 +03:00
|
|
|
if (!className && typeof object.constructor == "function") {
|
|
|
|
className = this.getFunctionName(object.constructor);
|
2012-09-26 21:02:04 +04:00
|
|
|
}
|
2016-03-16 10:16:00 +03:00
|
|
|
} catch (ex) {
|
|
|
|
// Ignore.
|
2012-09-26 21:02:04 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
return className;
|
|
|
|
},
|
|
|
|
|
2013-03-30 15:31:10 +04:00
|
|
|
/**
|
|
|
|
* Check if the given value is a grip with an actor.
|
|
|
|
*
|
2016-03-16 10:16:00 +03:00
|
|
|
* @param mixed grip
|
2013-03-30 15:31:10 +04:00
|
|
|
* Value you want to check if it is a grip with an actor.
|
|
|
|
* @return boolean
|
|
|
|
* True if the given value is a grip with an actor.
|
|
|
|
*/
|
2016-04-27 05:32:42 +03:00
|
|
|
isActorGrip: function (grip) {
|
2016-03-16 10:16:00 +03:00
|
|
|
return grip && typeof (grip) == "object" && grip.actor;
|
2013-03-30 15:31:10 +04:00
|
|
|
},
|
2016-03-16 10:16:00 +03:00
|
|
|
|
2014-05-22 02:34:00 +04:00
|
|
|
/**
|
|
|
|
* Value of devtools.selfxss.count preference
|
|
|
|
*
|
|
|
|
* @type number
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_usageCount: 0,
|
|
|
|
get usageCount() {
|
|
|
|
if (WebConsoleUtils._usageCount < CONSOLE_ENTRY_THRESHOLD) {
|
2016-03-16 10:16:00 +03:00
|
|
|
WebConsoleUtils._usageCount =
|
|
|
|
Services.prefs.getIntPref("devtools.selfxss.count");
|
2014-05-31 22:10:00 +04:00
|
|
|
if (Services.prefs.getBoolPref("devtools.chrome.enabled")) {
|
|
|
|
WebConsoleUtils.usageCount = CONSOLE_ENTRY_THRESHOLD;
|
|
|
|
}
|
2014-05-22 02:34:00 +04:00
|
|
|
}
|
|
|
|
return WebConsoleUtils._usageCount;
|
|
|
|
},
|
|
|
|
set usageCount(newUC) {
|
|
|
|
if (newUC <= CONSOLE_ENTRY_THRESHOLD) {
|
|
|
|
WebConsoleUtils._usageCount = newUC;
|
|
|
|
Services.prefs.setIntPref("devtools.selfxss.count", newUC);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
/**
|
2016-03-16 10:16:00 +03:00
|
|
|
* The inputNode "paste" event handler generator. Helps prevent
|
|
|
|
* self-xss attacks
|
2014-05-22 02:34:00 +04:00
|
|
|
*
|
|
|
|
* @param nsIDOMElement inputField
|
|
|
|
* @param nsIDOMElement notificationBox
|
2016-03-16 10:16:00 +03:00
|
|
|
* @returns A function to be added as a handler to 'paste' and
|
|
|
|
*'drop' events on the input field
|
2014-05-22 02:34:00 +04:00
|
|
|
*/
|
2016-04-27 05:32:42 +03:00
|
|
|
pasteHandlerGen: function (inputField, notificationBox, msg, okstring) {
|
|
|
|
let handler = function (event) {
|
2014-05-22 02:34:00 +04:00
|
|
|
if (WebConsoleUtils.usageCount >= CONSOLE_ENTRY_THRESHOLD) {
|
|
|
|
inputField.removeEventListener("paste", handler);
|
|
|
|
inputField.removeEventListener("drop", handler);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (notificationBox.getNotificationWithValue("selfxss-notification")) {
|
2016-03-16 10:16:00 +03:00
|
|
|
event.preventDefault();
|
|
|
|
event.stopPropagation();
|
2014-05-22 02:34:00 +04:00
|
|
|
return false;
|
|
|
|
}
|
2014-08-14 15:03:00 +04:00
|
|
|
|
2014-05-22 02:34:00 +04:00
|
|
|
let notification = notificationBox.appendNotification(msg,
|
2016-03-16 10:16:00 +03:00
|
|
|
"selfxss-notification", null,
|
|
|
|
notificationBox.PRIORITY_WARNING_HIGH, null,
|
2016-04-27 05:32:42 +03:00
|
|
|
function (eventType) {
|
2014-05-22 02:34:00 +04:00
|
|
|
// Cleanup function if notification is dismissed
|
|
|
|
if (eventType == "removed") {
|
|
|
|
inputField.removeEventListener("keyup", pasteKeyUpHandler);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2016-03-16 10:16:00 +03:00
|
|
|
function pasteKeyUpHandler(event2) {
|
2014-05-22 02:34:00 +04:00
|
|
|
let value = inputField.value || inputField.textContent;
|
2015-04-29 18:32:05 +03:00
|
|
|
if (value.includes(okstring)) {
|
2014-05-22 02:34:00 +04:00
|
|
|
notificationBox.removeNotification(notification);
|
|
|
|
inputField.removeEventListener("keyup", pasteKeyUpHandler);
|
|
|
|
WebConsoleUtils.usageCount = CONSOLE_ENTRY_THRESHOLD;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
inputField.addEventListener("keyup", pasteKeyUpHandler);
|
|
|
|
|
2016-03-16 10:16:00 +03:00
|
|
|
event.preventDefault();
|
|
|
|
event.stopPropagation();
|
2014-05-22 02:34:00 +04:00
|
|
|
return false;
|
|
|
|
};
|
|
|
|
return handler;
|
|
|
|
},
|
2012-05-10 17:15:10 +04:00
|
|
|
};
|
2014-05-22 02:34:00 +04:00
|
|
|
|
2013-08-03 14:29:48 +04:00
|
|
|
exports.Utils = WebConsoleUtils;
|
2012-05-10 17:15:10 +04:00
|
|
|
|
|
|
|
// Localization
|
|
|
|
|
2016-04-27 05:32:42 +03:00
|
|
|
WebConsoleUtils.L10n = function (bundleURI) {
|
2016-08-19 21:31:50 +03:00
|
|
|
this._helper = new LocalizationHelper(bundleURI);
|
2012-09-26 21:07:57 +04:00
|
|
|
};
|
|
|
|
|
2016-02-13 08:56:12 +03:00
|
|
|
WebConsoleUtils.L10n.prototype = {
|
2012-05-10 17:15:10 +04:00
|
|
|
/**
|
|
|
|
* Generates a formatted timestamp string for displaying in console messages.
|
|
|
|
*
|
2016-03-16 10:16:00 +03:00
|
|
|
* @param integer [milliseconds]
|
2012-05-10 17:15:10 +04:00
|
|
|
* Optional, allows you to specify the timestamp in milliseconds since
|
|
|
|
* the UNIX epoch.
|
|
|
|
* @return string
|
|
|
|
* The timestamp formatted for display.
|
|
|
|
*/
|
2016-04-27 05:32:42 +03:00
|
|
|
timestampString: function (milliseconds) {
|
2016-03-16 10:16:00 +03:00
|
|
|
let d = new Date(milliseconds ? milliseconds : null);
|
2012-05-10 17:15:10 +04:00
|
|
|
let hours = d.getHours(), minutes = d.getMinutes();
|
2016-03-16 10:16:00 +03:00
|
|
|
let seconds = d.getSeconds();
|
|
|
|
milliseconds = d.getMilliseconds();
|
2012-05-10 17:15:10 +04:00
|
|
|
let parameters = [hours, minutes, seconds, milliseconds];
|
|
|
|
return this.getFormatStr("timestampFormat", parameters);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieve a localized string.
|
|
|
|
*
|
2016-03-16 10:16:00 +03:00
|
|
|
* @param string name
|
2012-05-10 17:15:10 +04:00
|
|
|
* The string name you want from the Web Console string bundle.
|
|
|
|
* @return string
|
|
|
|
* The localized string.
|
|
|
|
*/
|
2016-04-27 05:32:42 +03:00
|
|
|
getStr: function (name) {
|
2012-05-10 17:15:10 +04:00
|
|
|
try {
|
2016-08-19 21:31:50 +03:00
|
|
|
return this._helper.getStr(name);
|
2016-03-16 10:16:00 +03:00
|
|
|
} catch (ex) {
|
2016-05-11 04:55:00 +03:00
|
|
|
console.error("Failed to get string: " + name);
|
2012-05-10 17:15:10 +04:00
|
|
|
throw ex;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieve a localized string formatted with values coming from the given
|
|
|
|
* array.
|
|
|
|
*
|
2016-03-16 10:16:00 +03:00
|
|
|
* @param string name
|
2012-05-10 17:15:10 +04:00
|
|
|
* The string name you want from the Web Console string bundle.
|
2016-03-16 10:16:00 +03:00
|
|
|
* @param array array
|
2012-05-10 17:15:10 +04:00
|
|
|
* The array of values you want in the formatted string.
|
|
|
|
* @return string
|
|
|
|
* The formatted local string.
|
|
|
|
*/
|
2016-04-27 05:32:42 +03:00
|
|
|
getFormatStr: function (name, array) {
|
2012-05-10 17:15:10 +04:00
|
|
|
try {
|
2016-08-19 21:31:50 +03:00
|
|
|
return this._helper.getFormatStr(name, ...array);
|
2016-03-16 10:16:00 +03:00
|
|
|
} catch (ex) {
|
2016-05-11 04:55:00 +03:00
|
|
|
console.error("Failed to format string: " + name);
|
2012-05-10 17:15:10 +04:00
|
|
|
throw ex;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
};
|