123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395 |
- /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
- /* 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;
- }
- },
- };
|