Bug 1874684 - Part 33: Defer allocation of options object for CalendarDateFromFields...
[gecko.git] / remote / shared / Format.sys.mjs
blob5da8bc9161ce72d7c08430f3146e395a59c302dd
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/. */
5 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
7 const lazy = {};
9 ChromeUtils.defineESModuleGetters(lazy, {
10   Log: "chrome://remote/content/shared/Log.sys.mjs",
11 });
13 ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());
15 XPCOMUtils.defineLazyPreferenceGetter(
16   lazy,
17   "truncateLog",
18   "remote.log.truncate",
19   false
22 const ELEMENT_NODE = 1;
23 const MAX_STRING_LENGTH = 250;
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   }
59   function prettyElement(el) {
60     let attrs = ["id", "class", "href", "name", "src", "type"];
62     let idents = "";
63     for (let attr of attrs) {
64       if (el.hasAttribute(attr)) {
65         idents += ` ${attr}="${el.getAttribute(attr)}"`;
66       }
67     }
69     return `<${el.localName}${idents}>`;
70   }
72   function prettyWindowGlobal(win) {
73     let proto = Object.prototype.toString.call(win);
74     return `[${proto.substring(1, proto.length - 1)} ${win.location}]`;
75   }
77   function prettyAttr(obj) {
78     return `[object Attr ${obj.name}="${obj.value}"]`;
79   }
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   }
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("");
114  * Template literal that truncates string values in arbitrary objects.
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.
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.
126  * Usage::
128  *     truncate`Hello ${"x".repeat(260)}!`;
129  *     // Hello xxx ... xxx!
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);
137     switch (typ) {
138       case "[object Undefined]":
139       case "[object Null]":
140       case "[object Boolean]":
141       case "[object Number]":
142         return obj;
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;
152       case "[object Array]":
153         return obj.map(walk);
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         }
164         let rv = {};
165         for (let prop in obj) {
166           rv[prop] = walk(obj[prop]);
167         }
168         return rv;
169     }
170   }
172   let res = [];
173   for (let i = 0; i < strings.length; ++i) {
174     res.push(strings[i]);
175     if (i < values.length) {
176       let obj = walk(values[i]);
177       let t = Object.prototype.toString.call(obj);
178       if (t == "[object Array]" || t == "[object Object]") {
179         res.push(JSON.stringify(obj));
180       } else {
181         res.push(obj);
182       }
183     }
184   }
185   return res.join("");