diff options
Diffstat (limited to 'remote/shared/Format.sys.mjs')
-rw-r--r-- | remote/shared/Format.sys.mjs | 186 |
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..95e9ff339f --- /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", +}); + +XPCOMUtils.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(""); +} |