summaryrefslogtreecommitdiffstats
path: root/devtools/client/webconsole/utils.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/webconsole/utils.js')
-rw-r--r--devtools/client/webconsole/utils.js395
1 files changed, 395 insertions, 0 deletions
diff --git a/devtools/client/webconsole/utils.js b/devtools/client/webconsole/utils.js
new file mode 100644
index 000000000..ae2f1809f
--- /dev/null
+++ b/devtools/client/webconsole/utils.js
@@ -0,0 +1,395 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft= javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+"use strict";
+
+const {Cc, Ci, Cu, components} = require("chrome");
+const Services = require("Services");
+const {LocalizationHelper} = require("devtools/shared/l10n");
+
+// 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*\(/;
+
+// Number of terminal entries for the self-xss prevention to go away
+const CONSOLE_ENTRY_THRESHOLD = 5;
+
+const CONSOLE_WORKER_IDS = exports.CONSOLE_WORKER_IDS = [
+ "SharedWorker",
+ "ServiceWorker",
+ "Worker"
+];
+
+var WebConsoleUtils = {
+
+ /**
+ * Wrap a string in an nsISupportsString object.
+ *
+ * @param string string
+ * @return nsISupportsString
+ */
+ supportsString: function (string) {
+ let str = Cc["@mozilla.org/supports-string;1"]
+ .createInstance(Ci.nsISupportsString);
+ str.data = string;
+ return str;
+ },
+
+ /**
+ * Clone an object.
+ *
+ * @param object object
+ * The object you want cloned.
+ * @param boolean recursive
+ * Tells if you want to dig deeper into the object, to clone
+ * recursively.
+ * @param function [filter]
+ * 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.
+ */
+ cloneObject: function (object, recursive, filter) {
+ if (typeof object != "object") {
+ return object;
+ }
+
+ let temp;
+
+ if (Array.isArray(object)) {
+ temp = [];
+ Array.forEach(object, function (value, index) {
+ if (!filter || filter(index, value, object)) {
+ temp.push(recursive ? WebConsoleUtils.cloneObject(value) : value);
+ }
+ });
+ } else {
+ temp = {};
+ 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;
+ }
+ }
+ }
+
+ return temp;
+ },
+
+ /**
+ * Copies certain style attributes from one element to another.
+ *
+ * @param nsIDOMNode from
+ * The target node.
+ * @param nsIDOMNode to
+ * The destination node.
+ */
+ copyTextStyles: function (from, to) {
+ 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;
+ },
+
+ /**
+ * Create a grip for the given value. If the value is an object,
+ * an object wrapper will be created.
+ *
+ * @param mixed value
+ * The value you want to create a grip for, before sending it to the
+ * client.
+ * @param function objectWrapper
+ * If the value is an object then the objectWrapper function is
+ * invoked to give us an object grip. See this.getObjectGrip().
+ * @return mixed
+ * The value grip.
+ */
+ createValueGrip: function (value, objectWrapper) {
+ switch (typeof value) {
+ case "boolean":
+ return value;
+ case "string":
+ return objectWrapper(value);
+ case "number":
+ if (value === Infinity) {
+ return { type: "Infinity" };
+ } else if (value === -Infinity) {
+ return { type: "-Infinity" };
+ } else if (Number.isNaN(value)) {
+ return { type: "NaN" };
+ } else if (!value && 1 / value === -Infinity) {
+ return { type: "-0" };
+ }
+ return value;
+ case "undefined":
+ return { type: "undefined" };
+ case "object":
+ if (value === null) {
+ return { type: "null" };
+ }
+ // Fall through.
+ case "function":
+ return objectWrapper(value);
+ default:
+ console.error("Failed to provide a grip for value of " + typeof value
+ + ": " + value);
+ return null;
+ }
+ },
+
+ /**
+ * Determine if the given request mixes HTTP with HTTPS content.
+ *
+ * @param string request
+ * Location of the requested content.
+ * @param string location
+ * Location of the current page.
+ * @return boolean
+ * True if the content is mixed, false if not.
+ */
+ isMixedHTTPSRequest: function (request, location) {
+ try {
+ let requestURI = Services.io.newURI(request, null, null);
+ let contentURI = Services.io.newURI(location, null, null);
+ return (contentURI.scheme == "https" && requestURI.scheme != "https");
+ } catch (ex) {
+ return false;
+ }
+ },
+
+ /**
+ * Helper function to deduce the name of the provided function.
+ *
+ * @param funtion function
+ * The function whose name will be returned.
+ * @return string
+ * Function name.
+ */
+ getFunctionName: function (func) {
+ let name = null;
+ if (func.name) {
+ name = func.name;
+ } else {
+ let desc;
+ try {
+ desc = func.getOwnPropertyDescriptor("displayName");
+ } catch (ex) {
+ // Ignore.
+ }
+ if (desc && typeof desc.value == "string") {
+ name = desc.value;
+ }
+ }
+ if (!name) {
+ try {
+ let str = (func.toString() || func.toSource()) + "";
+ name = (str.match(REGEX_MATCH_FUNCTION_NAME) || [])[1];
+ } catch (ex) {
+ // Ignore.
+ }
+ }
+ return name;
+ },
+
+ /**
+ * Get the object class name. For example, the |window| object has the Window
+ * class name (based on [object Window]).
+ *
+ * @param object object
+ * The object you want to get the class name for.
+ * @return string
+ * The object class name.
+ */
+ getObjectClassName: function (object) {
+ if (object === null) {
+ return "null";
+ }
+ if (object === undefined) {
+ return "undefined";
+ }
+
+ let type = typeof object;
+ if (type != "object") {
+ // Grip class names should start with an uppercase letter.
+ return type.charAt(0).toUpperCase() + type.substr(1);
+ }
+
+ let className;
+
+ try {
+ className = ((object + "").match(/^\[object (\S+)\]$/) || [])[1];
+ if (!className) {
+ className = ((object.constructor + "")
+ .match(/^\[object (\S+)\]$/) || [])[1];
+ }
+ if (!className && typeof object.constructor == "function") {
+ className = this.getFunctionName(object.constructor);
+ }
+ } catch (ex) {
+ // Ignore.
+ }
+
+ return className;
+ },
+
+ /**
+ * Check if the given value is a grip with an actor.
+ *
+ * @param mixed grip
+ * 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.
+ */
+ isActorGrip: function (grip) {
+ return grip && typeof (grip) == "object" && grip.actor;
+ },
+
+ /**
+ * Value of devtools.selfxss.count preference
+ *
+ * @type number
+ * @private
+ */
+ _usageCount: 0,
+ get usageCount() {
+ if (WebConsoleUtils._usageCount < CONSOLE_ENTRY_THRESHOLD) {
+ WebConsoleUtils._usageCount =
+ Services.prefs.getIntPref("devtools.selfxss.count");
+ if (Services.prefs.getBoolPref("devtools.chrome.enabled")) {
+ WebConsoleUtils.usageCount = CONSOLE_ENTRY_THRESHOLD;
+ }
+ }
+ return WebConsoleUtils._usageCount;
+ },
+ set usageCount(newUC) {
+ if (newUC <= CONSOLE_ENTRY_THRESHOLD) {
+ WebConsoleUtils._usageCount = newUC;
+ Services.prefs.setIntPref("devtools.selfxss.count", newUC);
+ }
+ },
+ /**
+ * The inputNode "paste" event handler generator. Helps prevent
+ * self-xss attacks
+ *
+ * @param nsIDOMElement inputField
+ * @param nsIDOMElement notificationBox
+ * @returns A function to be added as a handler to 'paste' and
+ *'drop' events on the input field
+ */
+ pasteHandlerGen: function (inputField, notificationBox, msg, okstring) {
+ let handler = function (event) {
+ if (WebConsoleUtils.usageCount >= CONSOLE_ENTRY_THRESHOLD) {
+ inputField.removeEventListener("paste", handler);
+ inputField.removeEventListener("drop", handler);
+ return true;
+ }
+ if (notificationBox.getNotificationWithValue("selfxss-notification")) {
+ event.preventDefault();
+ event.stopPropagation();
+ return false;
+ }
+
+ let notification = notificationBox.appendNotification(msg,
+ "selfxss-notification", null,
+ notificationBox.PRIORITY_WARNING_HIGH, null,
+ function (eventType) {
+ // Cleanup function if notification is dismissed
+ if (eventType == "removed") {
+ inputField.removeEventListener("keyup", pasteKeyUpHandler);
+ }
+ });
+
+ function pasteKeyUpHandler(event2) {
+ let value = inputField.value || inputField.textContent;
+ if (value.includes(okstring)) {
+ notificationBox.removeNotification(notification);
+ inputField.removeEventListener("keyup", pasteKeyUpHandler);
+ WebConsoleUtils.usageCount = CONSOLE_ENTRY_THRESHOLD;
+ }
+ }
+ inputField.addEventListener("keyup", pasteKeyUpHandler);
+
+ event.preventDefault();
+ event.stopPropagation();
+ return false;
+ };
+ return handler;
+ },
+};
+
+exports.Utils = WebConsoleUtils;
+
+// Localization
+
+WebConsoleUtils.L10n = function (bundleURI) {
+ this._helper = new LocalizationHelper(bundleURI);
+};
+
+WebConsoleUtils.L10n.prototype = {
+ /**
+ * Generates a formatted timestamp string for displaying in console messages.
+ *
+ * @param integer [milliseconds]
+ * Optional, allows you to specify the timestamp in milliseconds since
+ * the UNIX epoch.
+ * @return string
+ * The timestamp formatted for display.
+ */
+ timestampString: function (milliseconds) {
+ let d = new Date(milliseconds ? milliseconds : null);
+ let hours = d.getHours(), minutes = d.getMinutes();
+ let seconds = d.getSeconds();
+ milliseconds = d.getMilliseconds();
+ let parameters = [hours, minutes, seconds, milliseconds];
+ return this.getFormatStr("timestampFormat", parameters);
+ },
+
+ /**
+ * Retrieve a localized string.
+ *
+ * @param string name
+ * The string name you want from the Web Console string bundle.
+ * @return string
+ * The localized string.
+ */
+ getStr: function (name) {
+ try {
+ return this._helper.getStr(name);
+ } catch (ex) {
+ console.error("Failed to get string: " + name);
+ throw ex;
+ }
+ },
+
+ /**
+ * Retrieve a localized string formatted with values coming from the given
+ * array.
+ *
+ * @param string name
+ * The string name you want from the Web Console string bundle.
+ * @param array array
+ * The array of values you want in the formatted string.
+ * @return string
+ * The formatted local string.
+ */
+ getFormatStr: function (name, array) {
+ try {
+ return this._helper.getFormatStr(name, ...array);
+ } catch (ex) {
+ console.error("Failed to format string: " + name);
+ throw ex;
+ }
+ },
+};