Format.sys.mjs (4957B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; 6 7 const lazy = {}; 8 9 ChromeUtils.defineESModuleGetters(lazy, { 10 Log: "chrome://remote/content/shared/Log.sys.mjs", 11 }); 12 13 ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get()); 14 15 XPCOMUtils.defineLazyPreferenceGetter( 16 lazy, 17 "truncateLog", 18 "remote.log.truncate", 19 false 20 ); 21 22 const ELEMENT_NODE = 1; 23 const MAX_STRING_LENGTH = 250; 24 25 /** 26 * Pretty-print values passed to template strings. 27 * 28 * Usage:: 29 * 30 * let bool = {value: true}; 31 * pprint`Expected boolean, got ${bool}`; 32 * => 'Expected boolean, got [object Object] {"value": true}' 33 * 34 * let htmlElement = document.querySelector("input#foo"); 35 * pprint`Expected element ${htmlElement}`; 36 * => 'Expected element <input id="foo" class="bar baz" type="input">' 37 * 38 * pprint`Current window: ${window}`; 39 * => '[object Window https://www.mozilla.org/]' 40 */ 41 export function pprint(ss, ...values) { 42 function pretty(val) { 43 let proto = Object.prototype.toString.call(val); 44 if ( 45 typeof val == "object" && 46 val !== null && 47 "nodeType" in val && 48 val.nodeType === ELEMENT_NODE 49 ) { 50 return prettyElement(val); 51 } else if (["[object Window]", "[object ChromeWindow]"].includes(proto)) { 52 return prettyWindowGlobal(val); 53 } else if (proto == "[object Attr]") { 54 return prettyAttr(val); 55 } 56 return prettyObject(val); 57 } 58 59 function prettyElement(el) { 60 let attrs = ["id", "class", "href", "name", "src", "type"]; 61 62 let idents = ""; 63 for (let attr of attrs) { 64 if (el.hasAttribute(attr)) { 65 idents += ` ${attr}="${el.getAttribute(attr)}"`; 66 } 67 } 68 69 return `<${el.localName}${idents}>`; 70 } 71 72 function prettyWindowGlobal(win) { 73 let proto = Object.prototype.toString.call(win); 74 return `[${proto.substring(1, proto.length - 1)} ${win.location}]`; 75 } 76 77 function prettyAttr(obj) { 78 return `[object Attr ${obj.name}="${obj.value}"]`; 79 } 80 81 function prettyObject(obj) { 82 let proto = Object.prototype.toString.call(obj); 83 let s = ""; 84 try { 85 s = JSON.stringify(obj); 86 } catch (e) { 87 if (e instanceof TypeError) { 88 s = `<${e.message}>`; 89 } else { 90 throw e; 91 } 92 } 93 return `${proto} ${s}`; 94 } 95 96 let res = []; 97 for (let i = 0; i < ss.length; i++) { 98 res.push(ss[i]); 99 if (i < values.length) { 100 let s; 101 try { 102 s = pretty(values[i]); 103 } catch (e) { 104 lazy.logger.warn("Problem pretty printing:", e); 105 s = typeof values[i]; 106 } 107 res.push(s); 108 } 109 } 110 return res.join(""); 111 } 112 113 /** 114 * Template literal that truncates string values in arbitrary objects. 115 * 116 * Given any object, the template will walk the object and truncate 117 * any strings it comes across to a reasonable limit. This is suitable 118 * when you have arbitrary data and data integrity is not important. 119 * 120 * The strings are truncated in the middle so that the beginning and 121 * the end is preserved. This will make a long, truncated string look 122 * like "X <...> Y", where X and Y are half the number of characters 123 * of the maximum string length from either side of the string. 124 * 125 * 126 * Usage:: 127 * 128 * truncate`Hello ${"x".repeat(260)}!`; 129 * // Hello xxx ... xxx! 130 * 131 * Functions named `toJSON` or `toString` on objects will be called. 132 */ 133 export function truncate(strings, ...values) { 134 function walk(obj) { 135 const typ = Object.prototype.toString.call(obj); 136 137 switch (typ) { 138 case "[object Undefined]": 139 case "[object Null]": 140 case "[object Boolean]": 141 case "[object Number]": 142 return obj; 143 144 case "[object String]": 145 if (lazy.truncateLog && obj.length > MAX_STRING_LENGTH) { 146 let s1 = obj.substring(0, MAX_STRING_LENGTH / 2); 147 let s2 = obj.substring(obj.length - MAX_STRING_LENGTH / 2); 148 return `${s1} ... ${s2}`; 149 } 150 return obj; 151 152 case "[object Array]": 153 return obj.map(walk); 154 155 // arbitrary object 156 default: { 157 if ( 158 Object.getOwnPropertyNames(obj).includes("toString") && 159 typeof obj.toString == "function" 160 ) { 161 return walk(obj.toString()); 162 } 163 164 let rv = {}; 165 for (let prop in obj) { 166 rv[prop] = walk(obj[prop]); 167 } 168 return rv; 169 } 170 } 171 } 172 173 let res = []; 174 for (let i = 0; i < strings.length; ++i) { 175 res.push(strings[i]); 176 if (i < values.length) { 177 let obj = walk(values[i]); 178 let t = Object.prototype.toString.call(obj); 179 if (t == "[object Array]" || t == "[object Object]") { 180 res.push(JSON.stringify(obj)); 181 } else { 182 res.push(obj); 183 } 184 } 185 } 186 return res.join(""); 187 }