2017-09-30 19:06:29 +03:00
|
|
|
/* 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/. */
|
|
|
|
|
|
|
|
"use strict";
|
|
|
|
|
2018-06-06 16:40:05 +03:00
|
|
|
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
2017-12-03 18:48:13 +03:00
|
|
|
|
2018-06-06 16:40:05 +03:00
|
|
|
const {Log} = ChromeUtils.import("chrome://marionette/content/log.js", {});
|
|
|
|
|
|
|
|
XPCOMUtils.defineLazyGetter(this, "log", Log.get);
|
2017-09-30 19:06:29 +03:00
|
|
|
|
2018-06-06 16:40:05 +03:00
|
|
|
this.EXPORTED_SYMBOLS = ["pprint", "truncate"];
|
2017-12-03 18:48:13 +03:00
|
|
|
|
2018-06-23 16:00:50 +03:00
|
|
|
const ELEMENT_NODE = 1;
|
2017-09-30 19:06:29 +03:00
|
|
|
const MAX_STRING_LENGTH = 250;
|
|
|
|
|
2017-10-13 19:59:30 +03:00
|
|
|
/**
|
|
|
|
* Pretty-print values passed to template strings.
|
|
|
|
*
|
2018-06-23 16:00:50 +03:00
|
|
|
* Usage::
|
2017-10-13 19:59:30 +03:00
|
|
|
*
|
|
|
|
* const {pprint} = Cu.import("chrome://marionette/content/error.js", {});
|
|
|
|
* 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}`;
|
2017-10-18 21:54:13 +03:00
|
|
|
* => 'Expected element <input id="foo" class="bar baz" type="input">'
|
2017-12-03 18:48:13 +03:00
|
|
|
*
|
|
|
|
* pprint`Current window: ${window}`;
|
|
|
|
* => '[object Window https://www.mozilla.org/]'
|
2017-10-13 19:59:30 +03:00
|
|
|
*/
|
|
|
|
function pprint(ss, ...values) {
|
2017-12-03 18:48:13 +03:00
|
|
|
function pretty(val) {
|
|
|
|
let proto = Object.prototype.toString.call(val);
|
2018-06-23 16:00:50 +03:00
|
|
|
if (typeof val == "object" && val !== null &&
|
|
|
|
"nodeType" in val && val.nodeType === ELEMENT_NODE) {
|
2017-12-03 18:48:13 +03:00
|
|
|
return prettyElement(val);
|
|
|
|
} else if (["[object Window]", "[object ChromeWindow]"].includes(proto)) {
|
|
|
|
return prettyWindowGlobal(val);
|
2018-06-18 18:05:39 +03:00
|
|
|
} else if (proto == "[object Attr]") {
|
|
|
|
return prettyAttr(val);
|
2017-10-13 19:59:30 +03:00
|
|
|
}
|
2017-12-03 18:48:13 +03:00
|
|
|
return prettyObject(val);
|
2017-10-13 19:59:30 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
function prettyElement(el) {
|
2017-10-18 21:54:13 +03:00
|
|
|
let attrs = ["id", "class", "href", "name", "src", "type"];
|
2017-10-13 19:59:30 +03:00
|
|
|
|
|
|
|
let idents = "";
|
2017-10-18 21:54:13 +03:00
|
|
|
for (let attr of attrs) {
|
|
|
|
if (el.hasAttribute(attr)) {
|
|
|
|
idents += ` ${attr}="${el.getAttribute(attr)}"`;
|
|
|
|
}
|
2017-10-13 19:59:30 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return `<${el.localName}${idents}>`;
|
|
|
|
}
|
|
|
|
|
2017-12-03 18:48:13 +03:00
|
|
|
function prettyWindowGlobal(win) {
|
|
|
|
let proto = Object.prototype.toString.call(win);
|
|
|
|
return `[${proto.substring(1, proto.length - 1)} ${win.location}]`;
|
|
|
|
}
|
|
|
|
|
2018-06-18 18:05:39 +03:00
|
|
|
function prettyAttr(obj) {
|
|
|
|
return `[object Attr ${obj.name}="${obj.value}"]`;
|
|
|
|
}
|
|
|
|
|
2017-12-03 18:48:13 +03:00
|
|
|
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}`;
|
|
|
|
}
|
|
|
|
|
2017-10-13 19:59:30 +03:00
|
|
|
let res = [];
|
|
|
|
for (let i = 0; i < ss.length; i++) {
|
|
|
|
res.push(ss[i]);
|
|
|
|
if (i < values.length) {
|
|
|
|
let s;
|
|
|
|
try {
|
2017-12-03 18:48:13 +03:00
|
|
|
s = pretty(values[i]);
|
2017-10-13 19:59:30 +03:00
|
|
|
} catch (e) {
|
2017-12-03 18:48:13 +03:00
|
|
|
log.warn("Problem pretty printing:", e);
|
|
|
|
s = typeof values[i];
|
2017-10-13 19:59:30 +03:00
|
|
|
}
|
|
|
|
res.push(s);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return res.join("");
|
|
|
|
}
|
|
|
|
this.pprint = pprint;
|
|
|
|
|
2017-09-30 19:06:29 +03:00
|
|
|
/**
|
|
|
|
* 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:
|
|
|
|
*
|
|
|
|
* <pre><code>
|
|
|
|
* truncate`Hello ${"x".repeat(260)}!`;
|
|
|
|
* // Hello xxx ... xxx!
|
|
|
|
* </code></pre>
|
|
|
|
*
|
|
|
|
* Functions named <code>toJSON</code> or <code>toString</code>
|
|
|
|
* on objects will be called.
|
|
|
|
*/
|
|
|
|
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 (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("");
|
|
|
|
}
|
|
|
|
this.truncate = truncate;
|