summaryrefslogtreecommitdiffstats
path: root/remote/shared/Format.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'remote/shared/Format.sys.mjs')
-rw-r--r--remote/shared/Format.sys.mjs186
1 files changed, 186 insertions, 0 deletions
diff --git a/remote/shared/Format.sys.mjs b/remote/shared/Format.sys.mjs
new file mode 100644
index 0000000000..5da8bc9161
--- /dev/null
+++ b/remote/shared/Format.sys.mjs
@@ -0,0 +1,186 @@
+/* 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/. */
+
+import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ Log: "chrome://remote/content/shared/Log.sys.mjs",
+});
+
+ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());
+
+XPCOMUtils.defineLazyPreferenceGetter(
+ lazy,
+ "truncateLog",
+ "remote.log.truncate",
+ false
+);
+
+const ELEMENT_NODE = 1;
+const MAX_STRING_LENGTH = 250;
+
+/**
+ * Pretty-print values passed to template strings.
+ *
+ * Usage::
+ *
+ * let bool = {value: true};
+ * pprint`Expected boolean, got ${bool}`;
+ * => 'Expected boolean, got [object Object] {"value": true}'
+ *
+ * let htmlElement = document.querySelector("input#foo");
+ * pprint`Expected element ${htmlElement}`;
+ * => 'Expected element <input id="foo" class="bar baz" type="input">'
+ *
+ * pprint`Current window: ${window}`;
+ * => '[object Window https://www.mozilla.org/]'
+ */
+export function pprint(ss, ...values) {
+ function pretty(val) {
+ let proto = Object.prototype.toString.call(val);
+ if (
+ typeof val == "object" &&
+ val !== null &&
+ "nodeType" in val &&
+ val.nodeType === ELEMENT_NODE
+ ) {
+ return prettyElement(val);
+ } else if (["[object Window]", "[object ChromeWindow]"].includes(proto)) {
+ return prettyWindowGlobal(val);
+ } else if (proto == "[object Attr]") {
+ return prettyAttr(val);
+ }
+ return prettyObject(val);
+ }
+
+ function prettyElement(el) {
+ let attrs = ["id", "class", "href", "name", "src", "type"];
+
+ let idents = "";
+ for (let attr of attrs) {
+ if (el.hasAttribute(attr)) {
+ idents += ` ${attr}="${el.getAttribute(attr)}"`;
+ }
+ }
+
+ return `<${el.localName}${idents}>`;
+ }
+
+ function prettyWindowGlobal(win) {
+ let proto = Object.prototype.toString.call(win);
+ return `[${proto.substring(1, proto.length - 1)} ${win.location}]`;
+ }
+
+ function prettyAttr(obj) {
+ return `[object Attr ${obj.name}="${obj.value}"]`;
+ }
+
+ function prettyObject(obj) {
+ let proto = Object.prototype.toString.call(obj);
+ let s = "";
+ try {
+ s = JSON.stringify(obj);
+ } catch (e) {
+ if (e instanceof TypeError) {
+ s = `<${e.message}>`;
+ } else {
+ throw e;
+ }
+ }
+ return `${proto} ${s}`;
+ }
+
+ let res = [];
+ for (let i = 0; i < ss.length; i++) {
+ res.push(ss[i]);
+ if (i < values.length) {
+ let s;
+ try {
+ s = pretty(values[i]);
+ } catch (e) {
+ lazy.logger.warn("Problem pretty printing:", e);
+ s = typeof values[i];
+ }
+ res.push(s);
+ }
+ }
+ return res.join("");
+}
+
+/**
+ * Template literal that truncates string values in arbitrary objects.
+ *
+ * Given any object, the template will walk the object and truncate
+ * any strings it comes across to a reasonable limit. This is suitable
+ * when you have arbitrary data and data integrity is not important.
+ *
+ * The strings are truncated in the middle so that the beginning and
+ * the end is preserved. This will make a long, truncated string look
+ * like "X <...> Y", where X and Y are half the number of characters
+ * of the maximum string length from either side of the string.
+ *
+ *
+ * Usage::
+ *
+ * truncate`Hello ${"x".repeat(260)}!`;
+ * // Hello xxx ... xxx!
+ *
+ * Functions named `toJSON` or `toString` on objects will be called.
+ */
+export function truncate(strings, ...values) {
+ function walk(obj) {
+ const typ = Object.prototype.toString.call(obj);
+
+ switch (typ) {
+ case "[object Undefined]":
+ case "[object Null]":
+ case "[object Boolean]":
+ case "[object Number]":
+ return obj;
+
+ case "[object String]":
+ if (lazy.truncateLog && obj.length > MAX_STRING_LENGTH) {
+ let s1 = obj.substring(0, MAX_STRING_LENGTH / 2);
+ let s2 = obj.substring(obj.length - MAX_STRING_LENGTH / 2);
+ return `${s1} ... ${s2}`;
+ }
+ return obj;
+
+ case "[object Array]":
+ return obj.map(walk);
+
+ // arbitrary object
+ default:
+ if (
+ Object.getOwnPropertyNames(obj).includes("toString") &&
+ typeof obj.toString == "function"
+ ) {
+ return walk(obj.toString());
+ }
+
+ let rv = {};
+ for (let prop in obj) {
+ rv[prop] = walk(obj[prop]);
+ }
+ return rv;
+ }
+ }
+
+ let res = [];
+ for (let i = 0; i < strings.length; ++i) {
+ res.push(strings[i]);
+ if (i < values.length) {
+ let obj = walk(values[i]);
+ let t = Object.prototype.toString.call(obj);
+ if (t == "[object Array]" || t == "[object Object]") {
+ res.push(JSON.stringify(obj));
+ } else {
+ res.push(obj);
+ }
+ }
+ }
+ return res.join("");
+}