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