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